From a104f8ac4376ced7bd5a252757ab2c6f6b7efa5b Mon Sep 17 00:00:00 2001 From: WillChilds-Klein Date: Mon, 14 Jul 2025 21:19:58 +0000 Subject: [PATCH 001/220] Adapt @hugovk's proof-of-concept CI definition refactor Suggested by @hugovk [1]. [1]: https://github.com/hugovk/cpython/commit/a3f2ba9eb0c9bd1927d9a34faed98234afe88c70 --- .github/workflows/build.yml | 131 +++++++------------------- .github/workflows/reusable-ubuntu.yml | 2 +- 2 files changed, 35 insertions(+), 98 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 05f20e12f4653d..b068aaf96755e5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -260,8 +260,8 @@ jobs: free-threading: ${{ matrix.free-threading }} os: ${{ matrix.os }} - build-ubuntu-ssltests-openssl: - name: 'Ubuntu SSL tests with OpenSSL' + build-ubuntu-ssltests: + name: 'Ubuntu SSL tests' runs-on: ${{ matrix.os }} timeout-minutes: 60 needs: build-context @@ -269,75 +269,19 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-24.04] - openssl_ver: [3.0.16, 3.1.8, 3.2.4, 3.3.3, 3.4.1] + include: + - { os: ubuntu-24.04, ssl: openssl, ssl_ver: 3.0.16 } + - { os: ubuntu-24.04, ssl: openssl, ssl_ver: 3.1.8 } + - { os: ubuntu-24.04, ssl: openssl, ssl_ver: 3.2.4 } + - { os: ubuntu-24.04, ssl: openssl, ssl_ver: 3.3.3 } + - { os: ubuntu-24.04, ssl: openssl, ssl_ver: 3.4.1 } + - { os: ubuntu-24.04, ssl: aws-lc, ssl_ver: 1.55.0 } # See Tools/ssl/make_ssl_data.py for notes on adding a new version env: - OPENSSL_VER: ${{ matrix.openssl_ver }} - MULTISSL_DIR: ${{ github.workspace }}/multissl - OPENSSL_DIR: ${{ github.workspace }}/multissl/openssl/${{ matrix.openssl_ver }} - LD_LIBRARY_PATH: ${{ github.workspace }}/multissl/openssl/${{ matrix.openssl_ver }}/lib - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Runner image version - run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV" - - name: Restore config.cache - uses: actions/cache@v4 - with: - path: config.cache - key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.build-context.outputs.config-hash }} - - name: Register gcc problem matcher - run: echo "::add-matcher::.github/problem-matchers/gcc.json" - - name: Install dependencies - run: sudo ./.github/workflows/posix-deps-apt.sh - - name: Configure OpenSSL env vars - run: | - echo "MULTISSL_DIR=${GITHUB_WORKSPACE}/multissl" >> "$GITHUB_ENV" - echo "OPENSSL_DIR=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}" >> "$GITHUB_ENV" - echo "LD_LIBRARY_PATH=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}/lib" >> "$GITHUB_ENV" - - name: 'Restore OpenSSL build' - id: cache-openssl - uses: actions/cache@v4 - with: - path: ./multissl/openssl/${{ env.OPENSSL_VER }} - key: ${{ matrix.os }}-multissl-openssl-${{ env.OPENSSL_VER }} - - name: Install OpenSSL - if: steps.cache-openssl.outputs.cache-hit != 'true' - run: python3 Tools/ssl/multissltests.py --steps=library --base-directory "$MULTISSL_DIR" --openssl "$OPENSSL_VER" --system Linux - - name: Add ccache to PATH - run: | - echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV" - - name: Configure ccache action - uses: hendrikmuhs/ccache-action@v1.2 - with: - save: false - - name: Configure CPython - run: ./configure CFLAGS="-fdiagnostics-format=json" --config-cache --enable-slower-safety --with-pydebug --with-openssl="$OPENSSL_DIR" - - name: Build CPython - run: make -j4 - - name: Display build info - run: make pythoninfo - - name: SSL tests - run: ./python Lib/test/ssltests.py - - build-ubuntu-ssltests-awslc: - name: 'Ubuntu SSL tests with AWS-LC' - runs-on: ${{ matrix.os }} - timeout-minutes: 60 - needs: build-context - if: needs.build-context.outputs.run-tests == 'true' - strategy: - fail-fast: false - matrix: - os: [ubuntu-24.04] - awslc_ver: [1.55.0] - env: - AWSLC_VER: ${{ matrix.awslc_ver}} + SSL_VER: ${{ matrix.ssl_ver }} MULTISSL_DIR: ${{ github.workspace }}/multissl - OPENSSL_DIR: ${{ github.workspace }}/multissl/aws-lc/${{ matrix.awslc_ver }} - LD_LIBRARY_PATH: ${{ github.workspace }}/multissl/aws-lc/${{ matrix.awslc_ver }}/lib + SSL_DIR: ${{ github.workspace }}/multissl/${{ matrix.ssl }}/${{ matrix.ssl_ver }} + LD_LIBRARY_PATH: ${{ github.workspace }}/multissl/${{ matrix.ssl }}/${{ matrix.ssl_ver }}/lib steps: - uses: actions/checkout@v4 with: @@ -356,22 +300,18 @@ jobs: - name: Configure SSL lib env vars run: | echo "MULTISSL_DIR=${GITHUB_WORKSPACE}/multissl" >> "$GITHUB_ENV" - echo "OPENSSL_DIR=${GITHUB_WORKSPACE}/multissl/aws-lc/${AWSLC_VER}" >> "$GITHUB_ENV" - echo "LD_LIBRARY_PATH=${GITHUB_WORKSPACE}/multissl/aws-lc/${AWSLC_VER}/lib" >> "$GITHUB_ENV" - - name: 'Restore AWS-LC build' - id: cache-aws-lc + echo "SSL_DIR=${GITHUB_WORKSPACE}/multissl/${{ matrix.ssl }}/${SSL_VER}" >> "$GITHUB_ENV" + echo "LD_LIBRARY_PATH=${GITHUB_WORKSPACE}/multissl/${{ matrix.ssl }}/${SSL_VER}/lib" >> "$GITHUB_ENV" + - name: 'Restore SSL build' + id: cache-ssl uses: actions/cache@v4 with: - path: ./multissl/aws-lc/${{ matrix.awslc_ver }} - key: ${{ matrix.os }}-multissl-aws-lc-${{ matrix.awslc_ver }} - - name: Install AWS-LC - if: steps.cache-aws-lc.outputs.cache-hit != 'true' + path: ./multissl/${{ env.SSL }}/${{ env.SSL_VER }} + key: ${{ matrix.os }}-multissl-${{ env.SSL }}-${{ env.SSL_VER }} + - name: Install SSL + if: steps.cache-ssl.outputs.cache-hit != 'true' run: | - python3 Tools/ssl/multissltests.py \ - --steps=library \ - --base-directory "$MULTISSL_DIR" \ - --awslc ${{ matrix.awslc_ver }} \ - --system Linux + python3 Tools/ssl/multissltests.py --steps=library --base-directory "$MULTISSL_DIR" --system Linux --ssl ${{ matrix.ssl }} --ssl-versions ${{ matrix.ssl_ver }} - name: Add ccache to PATH run: | echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV" @@ -381,18 +321,18 @@ jobs: save: false - name: Configure CPython run: | - ./configure CFLAGS="-fdiagnostics-format=json" \ - --config-cache \ - --enable-slower-safety \ - --with-pydebug \ - --with-openssl="$OPENSSL_DIR" \ - --with-builtin-hashlib-hashes=blake2 \ - --with-ssl-default-suites=openssl + CMD=(./configure CFLAGS="-fdiagnostics-format=json" --config-cache --enable-slower-safety --with-pydebug --with-openssl="$SSL_DIR") + if [ "${{ matrix.ssl }}" = "openssl" ]; then + "${CMD[@]}" + else + "${CMD[@]}" --with-builtin-hashlib-hashes=blake2 --with-ssl-default-suites=openssl + fi - name: Build CPython - run: make -j + run: make -j4 - name: Display build info run: make pythoninfo - name: Verify python is linked to AWS-LC + if: matrix.ssl == 'aws-lc' run: ./python -c 'import ssl; print(ssl.OPENSSL_VERSION)' | grep AWS-LC - name: SSL tests run: ./python Lib/test/ssltests.py @@ -435,7 +375,7 @@ jobs: key: ${{ runner.os }}-multissl-openssl-${{ env.OPENSSL_VER }} - name: Install OpenSSL if: steps.cache-openssl.outputs.cache-hit != 'true' - run: python3 Tools/ssl/multissltests.py --steps=library --base-directory "$MULTISSL_DIR" --openssl "$OPENSSL_VER" --system Linux + run: python3 Tools/ssl/multissltests.py --steps=library --base-directory "$MULTISSL_DIR" --ssl 'openssl' --ssl-versions "$OPENSSL_VER" --system Linux - name: Add ccache to PATH run: | echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV" @@ -567,7 +507,7 @@ jobs: key: ${{ matrix.os }}-multissl-openssl-${{ env.OPENSSL_VER }} - name: Install OpenSSL if: steps.cache-openssl.outputs.cache-hit != 'true' - run: python3 Tools/ssl/multissltests.py --steps=library --base-directory "$MULTISSL_DIR" --openssl "$OPENSSL_VER" --system Linux + run: python3 Tools/ssl/multissltests.py --steps=library --base-directory "$MULTISSL_DIR" --ssl 'openssl' --ssl-versions "$OPENSSL_VER" --system Linux - name: Add ccache to PATH run: | echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV" @@ -703,8 +643,7 @@ jobs: - build-windows-msi - build-macos - build-ubuntu - - build-ubuntu-ssltests-awslc - - build-ubuntu-ssltests-openssl + - build-ubuntu-ssltests - build-wasi - test-hypothesis - build-asan @@ -719,8 +658,7 @@ jobs: with: allowed-failures: >- build-windows-msi, - build-ubuntu-ssltests-awslc, - build-ubuntu-ssltests-openssl, + build-ubuntu-ssltests, test-hypothesis, cifuzz, allowed-skips: >- @@ -738,8 +676,7 @@ jobs: check-generated-files, build-macos, build-ubuntu, - build-ubuntu-ssltests-awslc, - build-ubuntu-ssltests-openssl, + build-ubuntu-ssltests, build-wasi, test-hypothesis, build-asan, diff --git a/.github/workflows/reusable-ubuntu.yml b/.github/workflows/reusable-ubuntu.yml index 76b19fd5d1a72e..607e7949161812 100644 --- a/.github/workflows/reusable-ubuntu.yml +++ b/.github/workflows/reusable-ubuntu.yml @@ -60,7 +60,7 @@ jobs: key: ${{ inputs.os }}-multissl-openssl-${{ env.OPENSSL_VER }} - name: Install OpenSSL if: steps.cache-openssl.outputs.cache-hit != 'true' - run: python3 Tools/ssl/multissltests.py --steps=library --base-directory "$MULTISSL_DIR" --openssl "$OPENSSL_VER" --system Linux + run: python3 Tools/ssl/multissltests.py --steps=library --base-directory "$MULTISSL_DIR" --ssl openssl --ssl-versions "$OPENSSL_VER" --system Linux - name: Add ccache to PATH run: | echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV" From 3fcbe0d66363d53223d74d7542cbb6d3f3059959 Mon Sep 17 00:00:00 2001 From: WillChilds-Klein Date: Mon, 14 Jul 2025 21:21:29 +0000 Subject: [PATCH 002/220] Preliminary refactor of multissltests.py, TODO migrate to classes --- Tools/ssl/multissltests.py | 74 ++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/Tools/ssl/multissltests.py b/Tools/ssl/multissltests.py index f4c8fde8346fd9..969d5cc4b91e5d 100755 --- a/Tools/ssl/multissltests.py +++ b/Tools/ssl/multissltests.py @@ -86,33 +86,19 @@ parser.add_argument( '--disable-ancient', action='store_true', - help="Don't test OpenSSL and LibreSSL versions without upstream support", + help="Don't test SSL versions without upstream support", ) parser.add_argument( - '--openssl', - nargs='+', - default=(), - help=( - "OpenSSL versions, defaults to '{}' (ancient: '{}') if no " - "OpenSSL and LibreSSL versions are given." - ).format(OPENSSL_RECENT_VERSIONS, OPENSSL_OLD_VERSIONS) -) -parser.add_argument( - '--libressl', - nargs='+', - default=(), - help=( - "LibreSSL versions, defaults to '{}' (ancient: '{}') if no " - "OpenSSL and LibreSSL versions are given." - ).format(LIBRESSL_RECENT_VERSIONS, LIBRESSL_OLD_VERSIONS) + '--ssl', + choices=['openssl', 'awslc', 'libressl'], + default=None, + help="Which SSL lib to test. If not specified, all are tested.", ) parser.add_argument( - '--awslc', + '--ssl-versions', nargs='+', - default=(), - help=( - "AWS-LC versions, defaults to '{}' if no crypto library versions are given." - ).format(AWSLC_RECENT_VERSIONS) + default=None, + help="SSL lib version(s), default depends on value passed to --ssl", ) parser.add_argument( '--tests', @@ -507,19 +493,6 @@ def configure_make(): def main(): args = parser.parse_args() - if not args.openssl and not args.libressl and not args.awslc: - args.openssl = list(OPENSSL_RECENT_VERSIONS) - args.libressl = list(LIBRESSL_RECENT_VERSIONS) - args.awslc = list(AWSLC_RECENT_VERSIONS) - if not args.disable_ancient: - args.openssl.extend(OPENSSL_OLD_VERSIONS) - args.libressl.extend(LIBRESSL_OLD_VERSIONS) - - logging.basicConfig( - level=logging.DEBUG if args.debug else logging.INFO, - format="*** %(levelname)s %(message)s" - ) - start = datetime.now() if args.steps in {'modules', 'tests'}: @@ -535,13 +508,34 @@ def main(): # check for configure and run make configure_make() + logging.basicConfig( + level=logging.DEBUG if args.debug else logging.INFO, + format="*** %(levelname)s %(message)s" + ) + + ssl_libs = { + "openssl": [ + BuildOpenSSL, OPENSSL_OLD_VERSIONS, OPENSSL_RECENT_VERSIONS, [] + ], + "libressl": [ + BuildLibreSSL, LIBRESSL_OLD_VERSIONS, LIBRESSL_RECENT_VERSIONS, [] + ], + "awslc": [BuildAWSLC, [], AWSLC_RECENT_VERSIONS, []], + } + if args.ssl and args.ssl_versions: + ssl_libs[args.ssl][3] += args.ssl_versions + elif args.ssl: + ssl_libs[args.ssl][3] += ssl_libs[args.ssl][2] + else: + ssl_libs["openssl"][3] += ssl_libs["openssl"][2] + ssl_libs["libressl"][3] += ssl_libs["libressl"][2] + ssl_libs["awslc"][3] += ssl_libs["awslc"][2] + if not args.disable_ancient: + ssl_libs["openssl"][3] += ssl_libs["openssl"][1] + ssl_libs["libressl"][3] += ssl_libs["libressl"][1] # download and register builder builds = [] - for build_class, versions in [ - (BuildOpenSSL, args.openssl), - (BuildLibreSSL, args.libressl), - (BuildAWSLC, args.awslc), - ]: + for build_class, _, _, versions in ssl_libs.values(): for version in versions: build = build_class(version, args) build.install() From 5d8ec9aaea218a1e1f298e9065c9ba63cd9e05d9 Mon Sep 17 00:00:00 2001 From: WillChilds-Klein Date: Mon, 14 Jul 2025 21:27:53 +0000 Subject: [PATCH 003/220] Fix aws-lc/awslc lib name discrepancy --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b068aaf96755e5..665befea8787c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -275,7 +275,7 @@ jobs: - { os: ubuntu-24.04, ssl: openssl, ssl_ver: 3.2.4 } - { os: ubuntu-24.04, ssl: openssl, ssl_ver: 3.3.3 } - { os: ubuntu-24.04, ssl: openssl, ssl_ver: 3.4.1 } - - { os: ubuntu-24.04, ssl: aws-lc, ssl_ver: 1.55.0 } + - { os: ubuntu-24.04, ssl: awslc, ssl_ver: 1.55.0 } # See Tools/ssl/make_ssl_data.py for notes on adding a new version env: SSL_VER: ${{ matrix.ssl_ver }} From 991c6b28096b4bfbb6634dcd07d725c7d9141f0d Mon Sep 17 00:00:00 2001 From: WillChilds-Klein Date: Mon, 14 Jul 2025 21:45:13 +0000 Subject: [PATCH 004/220] Migrate AbstractBuilder to abc --- Tools/ssl/multissltests.py | 104 +++++++++++++++++++++++++++---------- 1 file changed, 78 insertions(+), 26 deletions(-) diff --git a/Tools/ssl/multissltests.py b/Tools/ssl/multissltests.py index 969d5cc4b91e5d..916d1fd989ba92 100755 --- a/Tools/ssl/multissltests.py +++ b/Tools/ssl/multissltests.py @@ -24,6 +24,7 @@ """ from __future__ import print_function +import abc import argparse from datetime import datetime import logging @@ -146,11 +147,7 @@ ) -class AbstractBuilder(object): - library = None - url_templates = None - src_template = None - build_template = None +class AbstractBuilder(object, metaclass=abc.ABCMeta): depend_target = None install_target = 'install' if hasattr(os, 'process_cpu_count'): @@ -158,6 +155,26 @@ class AbstractBuilder(object): else: jobs = os.cpu_count() + @property + @abstractmethod + def library(self): + pass + + @property + @abstractmethod + def url_templates(self): + pass + + @property + @abstractmethod + def src_template(self): + pass + + @property + @abstractmethod + def build_template(self): + pass + module_files = ( os.path.join(PYTHONROOT, "Modules/_ssl.c"), os.path.join(PYTHONROOT, "Modules/_hashopenssl.c"), @@ -167,9 +184,10 @@ class AbstractBuilder(object): def __init__(self, version, args): self.version = version self.args = args + libdir = self.library.lower().replace("-", "") # installation directory self.install_dir = os.path.join( - os.path.join(args.base_directory, self.library.lower()), version + os.path.join(args.base_directory, libdir), version ) # source file self.src_dir = os.path.join(args.base_directory, 'src') @@ -396,18 +414,30 @@ def run_python_tests(self, tests, network=True): class BuildOpenSSL(AbstractBuilder): - library = "OpenSSL" - url_templates = ( - "https://github.com/openssl/openssl/releases/download/openssl-{v}/openssl-{v}.tar.gz", - "https://www.openssl.org/source/openssl-{v}.tar.gz", - "https://www.openssl.org/source/old/{s}/openssl-{v}.tar.gz" - ) - src_template = "openssl-{}.tar.gz" - build_template = "openssl-{}" # only install software, skip docs install_target = 'install_sw' depend_target = 'depend' + @property + def library(self): + return "OpenSSL" + + @property + def url_templates(self): + return ( + "https://github.com/openssl/openssl/releases/download/openssl-{v}/openssl-{v}.tar.gz", + "https://www.openssl.org/source/openssl-{v}.tar.gz", + "https://www.openssl.org/source/old/{s}/openssl-{v}.tar.gz", + ) + + @property + def src_template(self): + return "openssl-{}.tar.gz" + + @property + def build_template(self): + return "openssl-{}" + def _post_install(self): if self.version.startswith("3."): self._post_install_3xx() @@ -443,21 +473,43 @@ def short_version(self): class BuildLibreSSL(AbstractBuilder): - library = "LibreSSL" - url_templates = ( - "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-{v}.tar.gz", - ) - src_template = "libressl-{}.tar.gz" - build_template = "libressl-{}" + @property + def library(self): + return "LibreSSL" + + @property + def url_templates(self): + return ( + "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-{v}.tar.gz", + ) + + @property + def src_template(self): + return "libressl-{}.tar.gz" + + @property + def build_template(self): + "libressl-{}" class BuildAWSLC(AbstractBuilder): - library = "AWS-LC" - url_templates = ( - "https://github.com/aws/aws-lc/archive/refs/tags/v{v}.tar.gz", - ) - src_template = "aws-lc-{}.tar.gz" - build_template = "aws-lc-{}" + @property + def library(self): + return "AWS-LC" + + @property + def url_templates(self): + return ( + "https://github.com/aws/aws-lc/archive/refs/tags/v{v}.tar.gz", + ) + + @property + def src_template(self): + return "aws-lc-{}.tar.gz" + + @property + def build_template(self): + return "aws-lc-{}" def _build_src(self, config_args=()): cwd = self.build_dir From 7b5149941e3a43cd041d9f3a8a9d73c2bbad600b Mon Sep 17 00:00:00 2001 From: WillChilds-Klein Date: Mon, 14 Jul 2025 21:53:45 +0000 Subject: [PATCH 005/220] Fix imports --- Tools/ssl/multissltests.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tools/ssl/multissltests.py b/Tools/ssl/multissltests.py index 916d1fd989ba92..4a20d41a529189 100755 --- a/Tools/ssl/multissltests.py +++ b/Tools/ssl/multissltests.py @@ -24,7 +24,8 @@ """ from __future__ import print_function -import abc +from abc import abstractmethod +from abc import ABCMeta import argparse from datetime import datetime import logging @@ -147,7 +148,7 @@ ) -class AbstractBuilder(object, metaclass=abc.ABCMeta): +class AbstractBuilder(object, metaclass=ABCMeta): depend_target = None install_target = 'install' if hasattr(os, 'process_cpu_count'): From 66381275b0896a153fc50fbd5f35fa3b0ba48d1e Mon Sep 17 00:00:00 2001 From: WillChilds-Klein Date: Wed, 16 Jul 2025 19:36:43 +0000 Subject: [PATCH 006/220] Complete ABC refactor --- Tools/ssl/multissltests.py | 146 +++++++++++++++++++------------------ 1 file changed, 77 insertions(+), 69 deletions(-) diff --git a/Tools/ssl/multissltests.py b/Tools/ssl/multissltests.py index 4a20d41a529189..69305c638b5acb 100755 --- a/Tools/ssl/multissltests.py +++ b/Tools/ssl/multissltests.py @@ -44,29 +44,6 @@ log = logging.getLogger("multissl") -OPENSSL_OLD_VERSIONS = [ - "1.1.1w", -] - -OPENSSL_RECENT_VERSIONS = [ - "3.0.16", - "3.1.8", - "3.2.4", - "3.3.3", - "3.4.1", - # See make_ssl_data.py for notes on adding a new version. -] - -LIBRESSL_OLD_VERSIONS = [ -] - -LIBRESSL_RECENT_VERSIONS = [ -] - -AWSLC_RECENT_VERSIONS = [ - "1.55.0", -] - # store files in ../multissl HERE = os.path.dirname(os.path.abspath(__file__)) PYTHONROOT = os.path.abspath(os.path.join(HERE, '..', '..')) @@ -155,32 +132,40 @@ class AbstractBuilder(object, metaclass=ABCMeta): jobs = os.process_cpu_count() else: jobs = os.cpu_count() + module_files = ( + os.path.join(PYTHONROOT, "Modules/_ssl.c"), + os.path.join(PYTHONROOT, "Modules/_hashopenssl.c"), + ) + module_libs = ("_ssl", "_hashlib") @property @abstractmethod - def library(self): + def library(self=None): pass @property @abstractmethod - def url_templates(self): + def url_templates(self=None): pass @property @abstractmethod - def src_template(self): + def src_template(self=None): pass @property @abstractmethod - def build_template(self): + def build_template(self=None): pass - module_files = ( - os.path.join(PYTHONROOT, "Modules/_ssl.c"), - os.path.join(PYTHONROOT, "Modules/_hashopenssl.c"), - ) - module_libs = ("_ssl", "_hashlib") + @property + @abstractmethod + def recent_versions(): + pass + + @property + def old_versions(): + return [] def __init__(self, version, args): self.version = version @@ -420,11 +405,11 @@ class BuildOpenSSL(AbstractBuilder): depend_target = 'depend' @property - def library(self): + def library(self=None): return "OpenSSL" @property - def url_templates(self): + def url_templates(self=None): return ( "https://github.com/openssl/openssl/releases/download/openssl-{v}/openssl-{v}.tar.gz", "https://www.openssl.org/source/openssl-{v}.tar.gz", @@ -432,13 +417,28 @@ def url_templates(self): ) @property - def src_template(self): + def src_template(self=None): return "openssl-{}.tar.gz" @property - def build_template(self): + def build_template(self=None): return "openssl-{}" + @property + def recent_versions(): + return [ + "3.0.16", + "3.1.8", + "3.2.4", + "3.3.3", + "3.4.1", + # See make_ssl_data.py for notes on adding a new version. + ] + + @property + def old_versions(): + return [ "1.1.1w" ] + def _post_install(self): if self.version.startswith("3."): self._post_install_3xx() @@ -475,43 +475,53 @@ def short_version(self): class BuildLibreSSL(AbstractBuilder): @property - def library(self): + def library(self=None): return "LibreSSL" @property - def url_templates(self): + def url_templates(self=None): return ( "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-{v}.tar.gz", ) @property - def src_template(self): + def src_template(self=None): return "libressl-{}.tar.gz" @property - def build_template(self): + def build_template(self=None): "libressl-{}" + @property + def recent_versions(): + return [] + class BuildAWSLC(AbstractBuilder): @property - def library(self): + def library(self=None): return "AWS-LC" @property - def url_templates(self): + def url_templates(self=None): return ( "https://github.com/aws/aws-lc/archive/refs/tags/v{v}.tar.gz", ) @property - def src_template(self): + def src_template(self=None): return "aws-lc-{}.tar.gz" @property - def build_template(self): + def build_template(self=None): return "aws-lc-{}" + @property + def recent_versions(): + return [ + "1.55.0", + ] + def _build_src(self, config_args=()): cwd = self.build_dir log.info("Running build in {}".format(cwd)) @@ -566,33 +576,31 @@ def main(): format="*** %(levelname)s %(message)s" ) - ssl_libs = { - "openssl": [ - BuildOpenSSL, OPENSSL_OLD_VERSIONS, OPENSSL_RECENT_VERSIONS, [] - ], - "libressl": [ - BuildLibreSSL, LIBRESSL_OLD_VERSIONS, LIBRESSL_RECENT_VERSIONS, [] - ], - "awslc": [BuildAWSLC, [], AWSLC_RECENT_VERSIONS, []], - } - if args.ssl and args.ssl_versions: - ssl_libs[args.ssl][3] += args.ssl_versions - elif args.ssl: - ssl_libs[args.ssl][3] += ssl_libs[args.ssl][2] + versions = [] + ssl_libs = AbstractBuilder.__subclasses__() + if args.ssl: + lib_name = lambda x: x.library.fget().lower().replace("-", "") + libs = [l for l in ssl_libs if lib_name(l) == args.ssl] + assert len(libs) == 1 + cls = libs.pop() + if args.ssl_versions: + versions += [(cls, v) for v in args.ssl_versions] + else: + versions += [(cls, v) for v in cls.recent_versions.fget()] else: - ssl_libs["openssl"][3] += ssl_libs["openssl"][2] - ssl_libs["libressl"][3] += ssl_libs["libressl"][2] - ssl_libs["awslc"][3] += ssl_libs["awslc"][2] - if not args.disable_ancient: - ssl_libs["openssl"][3] += ssl_libs["openssl"][1] - ssl_libs["libressl"][3] += ssl_libs["libressl"][1] - # download and register builder + if args.ssl_versions: + print("ERROR: SSL versions specified without specifying library") + exit(1) + for cls in ssl_libs: + versions += [(cls, v) for v in cls.recent_versions.fget()] + if not args.disable_ancient: + versions += [(cls, v) for v in cls.old_versions.fget()] + builds = [] - for build_class, _, _, versions in ssl_libs.values(): - for version in versions: - build = build_class(version, args) - build.install() - builds.append(build) + for build_class, version in versions: + build = build_class(version, args) + build.install() + builds.append(build) if args.steps in {'modules', 'tests'}: for build in builds: From 4e0a8caa51ac461604b7a6e7b7d082db02a790d3 Mon Sep 17 00:00:00 2001 From: WillChilds-Klein Date: Wed, 16 Jul 2025 19:52:47 +0000 Subject: [PATCH 007/220] Colorize parser --- Tools/ssl/multissltests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/ssl/multissltests.py b/Tools/ssl/multissltests.py index 69305c638b5acb..3fa1776d734e0f 100755 --- a/Tools/ssl/multissltests.py +++ b/Tools/ssl/multissltests.py @@ -57,6 +57,7 @@ "versions." ), ) +parser.color = True parser.add_argument( '--debug', action='store_true', From 1a90e0c3414f9c77607658e5a9c5450ea3de05d9 Mon Sep 17 00:00:00 2001 From: WillChilds-Klein Date: Wed, 16 Jul 2025 19:54:46 +0000 Subject: [PATCH 008/220] Adjust compatibility comment --- Tools/ssl/multissltests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tools/ssl/multissltests.py b/Tools/ssl/multissltests.py index 3fa1776d734e0f..88e2f0c7c78ad8 100755 --- a/Tools/ssl/multissltests.py +++ b/Tools/ssl/multissltests.py @@ -18,7 +18,8 @@ search paths for header files and shared libraries. It's known to work on Linux with GCC and clang. -Please keep this script compatible with Python 2.7, and 3.4 to 3.7. +Please keep this script compatible with all currently-maintained Python +versions. (c) 2013-2017 Christian Heimes """ From 1fcb49f9d440dd2d8cff58a4902a57e747f3fbea Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 20:08:58 +0000 Subject: [PATCH 009/220] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by?= =?UTF-8?q?=20blurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Tests/2025-07-16-20-08-54.gh-issue-136728.RG4zP1.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Tests/2025-07-16-20-08-54.gh-issue-136728.RG4zP1.rst diff --git a/Misc/NEWS.d/next/Tests/2025-07-16-20-08-54.gh-issue-136728.RG4zP1.rst b/Misc/NEWS.d/next/Tests/2025-07-16-20-08-54.gh-issue-136728.RG4zP1.rst new file mode 100644 index 00000000000000..2cee0f7c034a06 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2025-07-16-20-08-54.gh-issue-136728.RG4zP1.rst @@ -0,0 +1 @@ +Refactor multissltests.py and build.yml to better support testing additional cryptography libraries in the future. From dd969fbaf1a84aa0a97c2024ec43c837c3d9a9ef Mon Sep 17 00:00:00 2001 From: WillChilds-Klein Date: Wed, 16 Jul 2025 20:10:08 +0000 Subject: [PATCH 010/220] =?UTF-8?q?Revert=20"=F0=9F=93=9C=F0=9F=A4=96=20Ad?= =?UTF-8?q?ded=20by=20blurb=5Fit."?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 1fcb49f9d440dd2d8cff58a4902a57e747f3fbea. --- .../next/Tests/2025-07-16-20-08-54.gh-issue-136728.RG4zP1.rst | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Misc/NEWS.d/next/Tests/2025-07-16-20-08-54.gh-issue-136728.RG4zP1.rst diff --git a/Misc/NEWS.d/next/Tests/2025-07-16-20-08-54.gh-issue-136728.RG4zP1.rst b/Misc/NEWS.d/next/Tests/2025-07-16-20-08-54.gh-issue-136728.RG4zP1.rst deleted file mode 100644 index 2cee0f7c034a06..00000000000000 --- a/Misc/NEWS.d/next/Tests/2025-07-16-20-08-54.gh-issue-136728.RG4zP1.rst +++ /dev/null @@ -1 +0,0 @@ -Refactor multissltests.py and build.yml to better support testing additional cryptography libraries in the future. From 3343120065d82d492326063e59ef2df381f808f3 Mon Sep 17 00:00:00 2001 From: WillChilds-Klein Date: Wed, 16 Jul 2025 20:12:33 +0000 Subject: [PATCH 011/220] Include old versions when using default versions --- Tools/ssl/multissltests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tools/ssl/multissltests.py b/Tools/ssl/multissltests.py index 88e2f0c7c78ad8..2c8264dd09076b 100755 --- a/Tools/ssl/multissltests.py +++ b/Tools/ssl/multissltests.py @@ -589,6 +589,8 @@ def main(): versions += [(cls, v) for v in args.ssl_versions] else: versions += [(cls, v) for v in cls.recent_versions.fget()] + if not args.disable_ancient: + versions += [(cls, v) for v in cls.old_versions.fget()] else: if args.ssl_versions: print("ERROR: SSL versions specified without specifying library") From 6536fab19410ce701575b553d381cf805d3ef323 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 23 Dec 2025 16:31:10 +0100 Subject: [PATCH 012/220] gh-130796: Undeprecate locale.getdefaultlocale() (#143069) --- Doc/deprecations/pending-removal-in-3.15.rst | 10 ---------- Doc/library/locale.rst | 2 -- Doc/whatsnew/3.15.rst | 3 +++ Lib/locale.py | 6 ------ Lib/test/test_locale.py | 4 +--- .../2025-12-23-11-43-05.gh-issue-130796.TkzUGx.rst | 2 ++ 6 files changed, 6 insertions(+), 21 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-12-23-11-43-05.gh-issue-130796.TkzUGx.rst diff --git a/Doc/deprecations/pending-removal-in-3.15.rst b/Doc/deprecations/pending-removal-in-3.15.rst index 3b9cf892fe913d..00266b1725c8a1 100644 --- a/Doc/deprecations/pending-removal-in-3.15.rst +++ b/Doc/deprecations/pending-removal-in-3.15.rst @@ -33,16 +33,6 @@ Pending removal in Python 3.15 * ``load_module()`` method: use ``exec_module()`` instead. -* :class:`locale`: - - * The :func:`~locale.getdefaultlocale` function - has been deprecated since Python 3.11. - Its removal was originally planned for Python 3.13 (:gh:`90817`), - but has been postponed to Python 3.15. - Use :func:`~locale.getlocale`, :func:`~locale.setlocale`, - and :func:`~locale.getencoding` instead. - (Contributed by Hugo van Kemenade in :gh:`111187`.) - * :mod:`pathlib`: * :meth:`!.PurePath.is_reserved` diff --git a/Doc/library/locale.rst b/Doc/library/locale.rst index ce025670c92999..00dd616830bf55 100644 --- a/Doc/library/locale.rst +++ b/Doc/library/locale.rst @@ -370,8 +370,6 @@ The :mod:`locale` module defines the following exception and functions: determined. The "C" locale is represented as ``(None, None)``. - .. deprecated-removed:: 3.11 3.15 - .. function:: getlocale(category=LC_CTYPE) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index aa138c9cacb021..b1b533d8cde014 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -562,6 +562,9 @@ locale but included in the language code. (Contributed by Serhiy Storchaka in :gh:`137729`.) +* Undeprecate the :func:`locale.getdefaultlocale` function. + (Contributed by Victor Stinner in :gh:`130796`.) + math ---- diff --git a/Lib/locale.py b/Lib/locale.py index 0f1b429ea419b0..dea3ee55cf4d24 100644 --- a/Lib/locale.py +++ b/Lib/locale.py @@ -559,12 +559,6 @@ def getdefaultlocale(envvars=('LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE')): """ - import warnings - warnings._deprecated( - "locale.getdefaultlocale", - "{name!r} is deprecated and slated for removal in Python {remove}. " - "Use setlocale(), getencoding() and getlocale() instead.", - remove=(3, 15)) return _getdefaultlocale(envvars) diff --git a/Lib/test/test_locale.py b/Lib/test/test_locale.py index d49f78c91da1b3..a06c600cf56689 100644 --- a/Lib/test/test_locale.py +++ b/Lib/test/test_locale.py @@ -1,7 +1,6 @@ from decimal import Decimal from test import support from test.support import cpython_only, verbose, is_android, linked_to_musl, os_helper -from test.support.warnings_helper import check_warnings from test.support.import_helper import ensure_lazy_imports, import_fresh_module from unittest import mock import unittest @@ -654,8 +653,7 @@ def test_defaults_UTF8(self): env.unset('LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE') env.set('LC_CTYPE', 'UTF-8') - with check_warnings(('', DeprecationWarning)): - self.assertEqual(locale.getdefaultlocale(), (None, 'UTF-8')) + self.assertEqual(locale.getdefaultlocale(), (None, 'UTF-8')) finally: if orig_getlocale is not None: _locale._getdefaultlocale = orig_getlocale diff --git a/Misc/NEWS.d/next/Library/2025-12-23-11-43-05.gh-issue-130796.TkzUGx.rst b/Misc/NEWS.d/next/Library/2025-12-23-11-43-05.gh-issue-130796.TkzUGx.rst new file mode 100644 index 00000000000000..a078561a1014fa --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-23-11-43-05.gh-issue-130796.TkzUGx.rst @@ -0,0 +1,2 @@ +Undeprecate the :func:`locale.getdefaultlocale` function. +Patch by Victor Stinner. From f783cc37ebdc2fb9a2b5f967b99caaa886ff4ae4 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 23 Dec 2025 17:51:02 +0200 Subject: [PATCH 013/220] Update pre-commit with zizmor and Ruff fixes (#143095) --- .github/workflows/tail-call.yml | 13 +++++++------ .pre-commit-config.yaml | 12 ++++++------ Doc/tools/check-warnings.py | 7 +++++-- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.github/workflows/tail-call.yml b/.github/workflows/tail-call.yml index 76a8c05aa52ed2..1bc1bf20de0e06 100644 --- a/.github/workflows/tail-call.yml +++ b/.github/workflows/tail-call.yml @@ -81,22 +81,23 @@ jobs: - name: Native Windows MSVC (release) if: runner.os == 'Windows' && matrix.architecture != 'ARM64' - shell: cmd + shell: pwsh run: | choco install visualstudio2026buildtools --no-progress -y --force --params "--add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --locale en-US --passive" $env:PATH = "C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools\MSBuild\Current\bin;$env:PATH" - ./PCbuild/build.bat --tail-call-interp -c Release -p ${{ matrix.architecture }} "/p:PlatformToolset=v145" + $env:PlatformToolset = "v145" + ./PCbuild/build.bat --tail-call-interp -c Release -p ${{ matrix.architecture }} ./PCbuild/rt.bat -p ${{ matrix.architecture }} -q --multiprocess 0 --timeout 4500 --verbose2 --verbose3 # No tests (yet): - name: Emulated Windows Clang (release) if: runner.os == 'Windows' && matrix.architecture == 'ARM64' - shell: cmd + shell: pwsh run: | choco install llvm --allow-downgrade --no-progress --version ${{ matrix.llvm }}.1.0 - set PlatformToolset=clangcl - set LLVMToolsVersion=${{ matrix.llvm }}.1.0 - set LLVMInstallDir=C:\Program Files\LLVM + $env:PlatformToolset = "clangcl" + $env:LLVMToolsVersion = "${{ matrix.llvm }}.1.0" + $env:LLVMInstallDir = "C:\Program Files\LLVM" ./PCbuild/build.bat --tail-call-interp -p ${{ matrix.architecture }} - name: Native macOS (release) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ee89e18db35e15..ed88e9ca81b49c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.13.2 + rev: v0.14.10 hooks: - id: ruff-check name: Run Ruff (lint) on Apple/ @@ -52,7 +52,7 @@ repos: files: ^Tools/wasm/ - repo: https://github.com/psf/black-pre-commit-mirror - rev: 25.9.0 + rev: 25.12.0 hooks: - id: black name: Run Black on Tools/jit/ @@ -83,24 +83,24 @@ repos: files: '^\.github/CODEOWNERS|\.(gram)$' - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.34.0 + rev: 0.36.0 hooks: - id: check-dependabot - id: check-github-workflows - id: check-readthedocs - repo: https://github.com/rhysd/actionlint - rev: v1.7.7 + rev: v1.7.9 hooks: - id: actionlint - repo: https://github.com/woodruffw/zizmor-pre-commit - rev: v1.14.1 + rev: v1.19.0 hooks: - id: zizmor - repo: https://github.com/sphinx-contrib/sphinx-lint - rev: v1.0.0 + rev: v1.0.2 hooks: - id: sphinx-lint args: [--enable=default-role] diff --git a/Doc/tools/check-warnings.py b/Doc/tools/check-warnings.py index 2f2bb9e2dcb7ef..859bc1e2d5f5b5 100644 --- a/Doc/tools/check-warnings.py +++ b/Doc/tools/check-warnings.py @@ -311,8 +311,11 @@ def main(argv: list[str] | None = None) -> int: if not Path("Doc").exists() or not Path("Doc").is_dir(): raise RuntimeError(wrong_directory_msg) - with Path("Doc/sphinx-warnings.txt").open(encoding="UTF-8") as f: - warnings = f.read().splitlines() + warnings = ( + Path("Doc/sphinx-warnings.txt") + .read_text(encoding="UTF-8") + .splitlines() + ) cwd = str(Path.cwd()) + os.path.sep files_with_nits = { From c8b80f5e23cdbeec377c0aed36c9ac648b28a0b1 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Wed, 24 Dec 2025 00:47:46 +0800 Subject: [PATCH 014/220] gh-134584: Add another contributor to whats new 3.15 (GH-143107) Add another contributor to whats new 3.15 --- Doc/whatsnew/3.15.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index b1b533d8cde014..0d35eed38f303d 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -938,8 +938,8 @@ code results in constants, the code can be simplified by the JIT. The JIT avoids :term:`reference count`\ s where possible. This generally reduces the cost of most operations in Python. -(Contributed by Ken Jin, Donghee Na, Zheao Li, Savannah Ostrowski, -Noam Cohen, Tomas Roun, PuQing in :gh:`134584`.) +(Contributed by Ken Jin, Donghee Na, Zheao Li, Hai Zhu, Savannah Ostrowski, +Noam Cohen, Tomas Roun, and PuQing in :gh:`134584`.) .. rubric:: Better machine code generation From 25c294b6eafbd371865ae60138884262a2178077 Mon Sep 17 00:00:00 2001 From: "Tomas R." Date: Tue, 23 Dec 2025 18:01:10 +0100 Subject: [PATCH 015/220] gh-134584: Eliminate redundant refcounting from `_CALL_TYPE_1` (GH-135818) --- Include/internal/pycore_opcode_metadata.h | 2 +- Include/internal/pycore_uop_ids.h | 1560 +++++++++-------- Include/internal/pycore_uop_metadata.h | 43 +- Lib/test/test_capi/test_opt.py | 18 +- ...-06-23-20-54-15.gh-issue-134584.ZNcziF.rst | 1 + Python/bytecodes.c | 18 +- Python/executor_cases.c.h | 172 +- Python/generated_cases.c.h | 11 +- Python/optimizer_analysis.c | 6 + Python/optimizer_bytecodes.c | 10 +- Python/optimizer_cases.c.h | 25 +- 11 files changed, 1047 insertions(+), 819 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-06-23-20-54-15.gh-issue-134584.ZNcziF.rst diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index e0d2e2a3c430e2..351cf56355b7d0 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1377,7 +1377,7 @@ _PyOpcode_macro_expansion[256] = { [CALL_PY_GENERAL] = { .nuops = 6, .uops = { { _CHECK_PEP_523, OPARG_SIMPLE, 1 }, { _CHECK_FUNCTION_VERSION, 2, 1 }, { _CHECK_RECURSION_REMAINING, OPARG_SIMPLE, 3 }, { _PY_FRAME_GENERAL, OPARG_SIMPLE, 3 }, { _SAVE_RETURN_OFFSET, OPARG_SAVE_RETURN_OFFSET, 3 }, { _PUSH_FRAME, OPARG_SIMPLE, 3 } } }, [CALL_STR_1] = { .nuops = 5, .uops = { { _GUARD_NOS_NULL, OPARG_SIMPLE, 3 }, { _GUARD_CALLABLE_STR_1, OPARG_SIMPLE, 3 }, { _CALL_STR_1, OPARG_SIMPLE, 3 }, { _POP_TOP, OPARG_SIMPLE, 3 }, { _CHECK_PERIODIC_AT_END, OPARG_REPLACED, 3 } } }, [CALL_TUPLE_1] = { .nuops = 5, .uops = { { _GUARD_NOS_NULL, OPARG_SIMPLE, 3 }, { _GUARD_CALLABLE_TUPLE_1, OPARG_SIMPLE, 3 }, { _CALL_TUPLE_1, OPARG_SIMPLE, 3 }, { _POP_TOP, OPARG_SIMPLE, 3 }, { _CHECK_PERIODIC_AT_END, OPARG_REPLACED, 3 } } }, - [CALL_TYPE_1] = { .nuops = 3, .uops = { { _GUARD_NOS_NULL, OPARG_SIMPLE, 3 }, { _GUARD_CALLABLE_TYPE_1, OPARG_SIMPLE, 3 }, { _CALL_TYPE_1, OPARG_SIMPLE, 3 } } }, + [CALL_TYPE_1] = { .nuops = 4, .uops = { { _GUARD_NOS_NULL, OPARG_SIMPLE, 3 }, { _GUARD_CALLABLE_TYPE_1, OPARG_SIMPLE, 3 }, { _CALL_TYPE_1, OPARG_SIMPLE, 3 }, { _POP_TOP, OPARG_SIMPLE, 3 } } }, [CHECK_EG_MATCH] = { .nuops = 1, .uops = { { _CHECK_EG_MATCH, OPARG_SIMPLE, 0 } } }, [CHECK_EXC_MATCH] = { .nuops = 1, .uops = { { _CHECK_EXC_MATCH, OPARG_SIMPLE, 0 } } }, [COMPARE_OP] = { .nuops = 1, .uops = { { _COMPARE_OP, OPARG_SIMPLE, 0 } } }, diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index c8aa2765a34ef4..204210ff101efe 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -314,795 +314,803 @@ extern "C" { #define _SET_ADD SET_ADD #define _SET_FUNCTION_ATTRIBUTE SET_FUNCTION_ATTRIBUTE #define _SET_UPDATE SET_UPDATE -#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW 524 -#define _SPILL_OR_RELOAD 525 -#define _START_EXECUTOR 526 -#define _STORE_ATTR 527 -#define _STORE_ATTR_INSTANCE_VALUE 528 -#define _STORE_ATTR_SLOT 529 -#define _STORE_ATTR_WITH_HINT 530 +#define _SHUFFLE_2_LOAD_CONST_INLINE_BORROW 524 +#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW 525 +#define _SPILL_OR_RELOAD 526 +#define _START_EXECUTOR 527 +#define _STORE_ATTR 528 +#define _STORE_ATTR_INSTANCE_VALUE 529 +#define _STORE_ATTR_SLOT 530 +#define _STORE_ATTR_WITH_HINT 531 #define _STORE_DEREF STORE_DEREF -#define _STORE_FAST 531 -#define _STORE_FAST_0 532 -#define _STORE_FAST_1 533 -#define _STORE_FAST_2 534 -#define _STORE_FAST_3 535 -#define _STORE_FAST_4 536 -#define _STORE_FAST_5 537 -#define _STORE_FAST_6 538 -#define _STORE_FAST_7 539 +#define _STORE_FAST 532 +#define _STORE_FAST_0 533 +#define _STORE_FAST_1 534 +#define _STORE_FAST_2 535 +#define _STORE_FAST_3 536 +#define _STORE_FAST_4 537 +#define _STORE_FAST_5 538 +#define _STORE_FAST_6 539 +#define _STORE_FAST_7 540 #define _STORE_GLOBAL STORE_GLOBAL #define _STORE_NAME STORE_NAME -#define _STORE_SLICE 540 -#define _STORE_SUBSCR 541 -#define _STORE_SUBSCR_DICT 542 -#define _STORE_SUBSCR_LIST_INT 543 -#define _SWAP 544 -#define _SWAP_2 545 -#define _SWAP_3 546 -#define _TIER2_RESUME_CHECK 547 -#define _TO_BOOL 548 +#define _STORE_SLICE 541 +#define _STORE_SUBSCR 542 +#define _STORE_SUBSCR_DICT 543 +#define _STORE_SUBSCR_LIST_INT 544 +#define _SWAP 545 +#define _SWAP_2 546 +#define _SWAP_3 547 +#define _TIER2_RESUME_CHECK 548 +#define _TO_BOOL 549 #define _TO_BOOL_BOOL TO_BOOL_BOOL #define _TO_BOOL_INT TO_BOOL_INT -#define _TO_BOOL_LIST 549 +#define _TO_BOOL_LIST 550 #define _TO_BOOL_NONE TO_BOOL_NONE -#define _TO_BOOL_STR 550 +#define _TO_BOOL_STR 551 #define _TRACE_RECORD TRACE_RECORD #define _UNARY_INVERT UNARY_INVERT #define _UNARY_NEGATIVE UNARY_NEGATIVE #define _UNARY_NOT UNARY_NOT #define _UNPACK_EX UNPACK_EX -#define _UNPACK_SEQUENCE 551 -#define _UNPACK_SEQUENCE_LIST 552 -#define _UNPACK_SEQUENCE_TUPLE 553 -#define _UNPACK_SEQUENCE_TWO_TUPLE 554 +#define _UNPACK_SEQUENCE 552 +#define _UNPACK_SEQUENCE_LIST 553 +#define _UNPACK_SEQUENCE_TUPLE 554 +#define _UNPACK_SEQUENCE_TWO_TUPLE 555 #define _WITH_EXCEPT_START WITH_EXCEPT_START #define _YIELD_VALUE YIELD_VALUE -#define MAX_UOP_ID 554 -#define _BINARY_OP_r21 555 -#define _BINARY_OP_ADD_FLOAT_r03 556 -#define _BINARY_OP_ADD_FLOAT_r13 557 -#define _BINARY_OP_ADD_FLOAT_r23 558 -#define _BINARY_OP_ADD_INT_r03 559 -#define _BINARY_OP_ADD_INT_r13 560 -#define _BINARY_OP_ADD_INT_r23 561 -#define _BINARY_OP_ADD_UNICODE_r03 562 -#define _BINARY_OP_ADD_UNICODE_r13 563 -#define _BINARY_OP_ADD_UNICODE_r23 564 -#define _BINARY_OP_EXTEND_r21 565 -#define _BINARY_OP_INPLACE_ADD_UNICODE_r20 566 -#define _BINARY_OP_MULTIPLY_FLOAT_r03 567 -#define _BINARY_OP_MULTIPLY_FLOAT_r13 568 -#define _BINARY_OP_MULTIPLY_FLOAT_r23 569 -#define _BINARY_OP_MULTIPLY_INT_r03 570 -#define _BINARY_OP_MULTIPLY_INT_r13 571 -#define _BINARY_OP_MULTIPLY_INT_r23 572 -#define _BINARY_OP_SUBSCR_CHECK_FUNC_r23 573 -#define _BINARY_OP_SUBSCR_DICT_r21 574 -#define _BINARY_OP_SUBSCR_INIT_CALL_r01 575 -#define _BINARY_OP_SUBSCR_INIT_CALL_r11 576 -#define _BINARY_OP_SUBSCR_INIT_CALL_r21 577 -#define _BINARY_OP_SUBSCR_INIT_CALL_r31 578 -#define _BINARY_OP_SUBSCR_LIST_INT_r23 579 -#define _BINARY_OP_SUBSCR_LIST_SLICE_r21 580 -#define _BINARY_OP_SUBSCR_STR_INT_r23 581 -#define _BINARY_OP_SUBSCR_TUPLE_INT_r21 582 -#define _BINARY_OP_SUBTRACT_FLOAT_r03 583 -#define _BINARY_OP_SUBTRACT_FLOAT_r13 584 -#define _BINARY_OP_SUBTRACT_FLOAT_r23 585 -#define _BINARY_OP_SUBTRACT_INT_r03 586 -#define _BINARY_OP_SUBTRACT_INT_r13 587 -#define _BINARY_OP_SUBTRACT_INT_r23 588 -#define _BINARY_SLICE_r31 589 -#define _BUILD_INTERPOLATION_r01 590 -#define _BUILD_LIST_r01 591 -#define _BUILD_MAP_r01 592 -#define _BUILD_SET_r01 593 -#define _BUILD_SLICE_r01 594 -#define _BUILD_STRING_r01 595 -#define _BUILD_TEMPLATE_r21 596 -#define _BUILD_TUPLE_r01 597 -#define _CALL_BUILTIN_CLASS_r01 598 -#define _CALL_BUILTIN_FAST_r01 599 -#define _CALL_BUILTIN_FAST_WITH_KEYWORDS_r01 600 -#define _CALL_BUILTIN_O_r03 601 -#define _CALL_INTRINSIC_1_r11 602 -#define _CALL_INTRINSIC_2_r21 603 -#define _CALL_ISINSTANCE_r31 604 -#define _CALL_KW_NON_PY_r11 605 -#define _CALL_LEN_r33 606 -#define _CALL_LIST_APPEND_r02 607 -#define _CALL_LIST_APPEND_r12 608 -#define _CALL_LIST_APPEND_r22 609 -#define _CALL_LIST_APPEND_r32 610 -#define _CALL_METHOD_DESCRIPTOR_FAST_r01 611 -#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_r01 612 -#define _CALL_METHOD_DESCRIPTOR_NOARGS_r01 613 -#define _CALL_METHOD_DESCRIPTOR_O_r01 614 -#define _CALL_NON_PY_GENERAL_r01 615 -#define _CALL_STR_1_r32 616 -#define _CALL_TUPLE_1_r32 617 -#define _CALL_TYPE_1_r31 618 -#define _CHECK_AND_ALLOCATE_OBJECT_r00 619 -#define _CHECK_ATTR_CLASS_r01 620 -#define _CHECK_ATTR_CLASS_r11 621 -#define _CHECK_ATTR_CLASS_r22 622 -#define _CHECK_ATTR_CLASS_r33 623 -#define _CHECK_ATTR_METHOD_LAZY_DICT_r01 624 -#define _CHECK_ATTR_METHOD_LAZY_DICT_r11 625 -#define _CHECK_ATTR_METHOD_LAZY_DICT_r22 626 -#define _CHECK_ATTR_METHOD_LAZY_DICT_r33 627 -#define _CHECK_CALL_BOUND_METHOD_EXACT_ARGS_r00 628 -#define _CHECK_EG_MATCH_r22 629 -#define _CHECK_EXC_MATCH_r22 630 -#define _CHECK_FUNCTION_EXACT_ARGS_r00 631 -#define _CHECK_FUNCTION_VERSION_r00 632 -#define _CHECK_FUNCTION_VERSION_INLINE_r00 633 -#define _CHECK_FUNCTION_VERSION_INLINE_r11 634 -#define _CHECK_FUNCTION_VERSION_INLINE_r22 635 -#define _CHECK_FUNCTION_VERSION_INLINE_r33 636 -#define _CHECK_FUNCTION_VERSION_KW_r11 637 -#define _CHECK_IS_NOT_PY_CALLABLE_r00 638 -#define _CHECK_IS_NOT_PY_CALLABLE_KW_r11 639 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r01 640 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r11 641 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r22 642 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r33 643 -#define _CHECK_METHOD_VERSION_r00 644 -#define _CHECK_METHOD_VERSION_KW_r11 645 -#define _CHECK_PEP_523_r00 646 -#define _CHECK_PEP_523_r11 647 -#define _CHECK_PEP_523_r22 648 -#define _CHECK_PEP_523_r33 649 -#define _CHECK_PERIODIC_r00 650 -#define _CHECK_PERIODIC_AT_END_r00 651 -#define _CHECK_PERIODIC_IF_NOT_YIELD_FROM_r00 652 -#define _CHECK_RECURSION_REMAINING_r00 653 -#define _CHECK_RECURSION_REMAINING_r11 654 -#define _CHECK_RECURSION_REMAINING_r22 655 -#define _CHECK_RECURSION_REMAINING_r33 656 -#define _CHECK_STACK_SPACE_r00 657 -#define _CHECK_STACK_SPACE_OPERAND_r00 658 -#define _CHECK_STACK_SPACE_OPERAND_r11 659 -#define _CHECK_STACK_SPACE_OPERAND_r22 660 -#define _CHECK_STACK_SPACE_OPERAND_r33 661 -#define _CHECK_VALIDITY_r00 662 -#define _CHECK_VALIDITY_r11 663 -#define _CHECK_VALIDITY_r22 664 -#define _CHECK_VALIDITY_r33 665 -#define _COLD_DYNAMIC_EXIT_r00 666 -#define _COLD_EXIT_r00 667 -#define _COMPARE_OP_r21 668 -#define _COMPARE_OP_FLOAT_r01 669 -#define _COMPARE_OP_FLOAT_r11 670 -#define _COMPARE_OP_FLOAT_r21 671 -#define _COMPARE_OP_FLOAT_r32 672 -#define _COMPARE_OP_INT_r23 673 -#define _COMPARE_OP_STR_r21 674 -#define _CONTAINS_OP_r21 675 -#define _CONTAINS_OP_DICT_r21 676 -#define _CONTAINS_OP_SET_r21 677 -#define _CONVERT_VALUE_r11 678 -#define _COPY_r01 679 -#define _COPY_1_r02 680 -#define _COPY_1_r12 681 -#define _COPY_1_r23 682 -#define _COPY_2_r03 683 -#define _COPY_2_r13 684 -#define _COPY_2_r23 685 -#define _COPY_3_r03 686 -#define _COPY_3_r13 687 -#define _COPY_3_r23 688 -#define _COPY_3_r33 689 -#define _COPY_FREE_VARS_r00 690 -#define _COPY_FREE_VARS_r11 691 -#define _COPY_FREE_VARS_r22 692 -#define _COPY_FREE_VARS_r33 693 -#define _CREATE_INIT_FRAME_r01 694 -#define _DELETE_ATTR_r10 695 -#define _DELETE_DEREF_r00 696 -#define _DELETE_FAST_r00 697 -#define _DELETE_GLOBAL_r00 698 -#define _DELETE_NAME_r00 699 -#define _DELETE_SUBSCR_r20 700 -#define _DEOPT_r00 701 -#define _DEOPT_r10 702 -#define _DEOPT_r20 703 -#define _DEOPT_r30 704 -#define _DICT_MERGE_r10 705 -#define _DICT_UPDATE_r10 706 -#define _DO_CALL_r01 707 -#define _DO_CALL_FUNCTION_EX_r31 708 -#define _DO_CALL_KW_r11 709 -#define _DYNAMIC_EXIT_r00 710 -#define _DYNAMIC_EXIT_r10 711 -#define _DYNAMIC_EXIT_r20 712 -#define _DYNAMIC_EXIT_r30 713 -#define _END_FOR_r10 714 -#define _END_SEND_r21 715 -#define _ERROR_POP_N_r00 716 -#define _EXIT_INIT_CHECK_r10 717 -#define _EXIT_TRACE_r00 718 -#define _EXIT_TRACE_r10 719 -#define _EXIT_TRACE_r20 720 -#define _EXIT_TRACE_r30 721 -#define _EXPAND_METHOD_r00 722 -#define _EXPAND_METHOD_KW_r11 723 -#define _FATAL_ERROR_r00 724 -#define _FATAL_ERROR_r11 725 -#define _FATAL_ERROR_r22 726 -#define _FATAL_ERROR_r33 727 -#define _FORMAT_SIMPLE_r11 728 -#define _FORMAT_WITH_SPEC_r21 729 -#define _FOR_ITER_r23 730 -#define _FOR_ITER_GEN_FRAME_r03 731 -#define _FOR_ITER_GEN_FRAME_r13 732 -#define _FOR_ITER_GEN_FRAME_r23 733 -#define _FOR_ITER_TIER_TWO_r23 734 -#define _GET_AITER_r11 735 -#define _GET_ANEXT_r12 736 -#define _GET_AWAITABLE_r11 737 -#define _GET_ITER_r12 738 -#define _GET_LEN_r12 739 -#define _GET_YIELD_FROM_ITER_r11 740 -#define _GUARD_BINARY_OP_EXTEND_r22 741 -#define _GUARD_CALLABLE_ISINSTANCE_r03 742 -#define _GUARD_CALLABLE_ISINSTANCE_r13 743 -#define _GUARD_CALLABLE_ISINSTANCE_r23 744 -#define _GUARD_CALLABLE_ISINSTANCE_r33 745 -#define _GUARD_CALLABLE_LEN_r03 746 -#define _GUARD_CALLABLE_LEN_r13 747 -#define _GUARD_CALLABLE_LEN_r23 748 -#define _GUARD_CALLABLE_LEN_r33 749 -#define _GUARD_CALLABLE_LIST_APPEND_r03 750 -#define _GUARD_CALLABLE_LIST_APPEND_r13 751 -#define _GUARD_CALLABLE_LIST_APPEND_r23 752 -#define _GUARD_CALLABLE_LIST_APPEND_r33 753 -#define _GUARD_CALLABLE_STR_1_r03 754 -#define _GUARD_CALLABLE_STR_1_r13 755 -#define _GUARD_CALLABLE_STR_1_r23 756 -#define _GUARD_CALLABLE_STR_1_r33 757 -#define _GUARD_CALLABLE_TUPLE_1_r03 758 -#define _GUARD_CALLABLE_TUPLE_1_r13 759 -#define _GUARD_CALLABLE_TUPLE_1_r23 760 -#define _GUARD_CALLABLE_TUPLE_1_r33 761 -#define _GUARD_CALLABLE_TYPE_1_r03 762 -#define _GUARD_CALLABLE_TYPE_1_r13 763 -#define _GUARD_CALLABLE_TYPE_1_r23 764 -#define _GUARD_CALLABLE_TYPE_1_r33 765 -#define _GUARD_DORV_NO_DICT_r01 766 -#define _GUARD_DORV_NO_DICT_r11 767 -#define _GUARD_DORV_NO_DICT_r22 768 -#define _GUARD_DORV_NO_DICT_r33 769 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r01 770 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r11 771 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r22 772 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r33 773 -#define _GUARD_GLOBALS_VERSION_r00 774 -#define _GUARD_GLOBALS_VERSION_r11 775 -#define _GUARD_GLOBALS_VERSION_r22 776 -#define _GUARD_GLOBALS_VERSION_r33 777 -#define _GUARD_IP_RETURN_GENERATOR_r00 778 -#define _GUARD_IP_RETURN_GENERATOR_r11 779 -#define _GUARD_IP_RETURN_GENERATOR_r22 780 -#define _GUARD_IP_RETURN_GENERATOR_r33 781 -#define _GUARD_IP_RETURN_VALUE_r00 782 -#define _GUARD_IP_RETURN_VALUE_r11 783 -#define _GUARD_IP_RETURN_VALUE_r22 784 -#define _GUARD_IP_RETURN_VALUE_r33 785 -#define _GUARD_IP_YIELD_VALUE_r00 786 -#define _GUARD_IP_YIELD_VALUE_r11 787 -#define _GUARD_IP_YIELD_VALUE_r22 788 -#define _GUARD_IP_YIELD_VALUE_r33 789 -#define _GUARD_IP__PUSH_FRAME_r00 790 -#define _GUARD_IP__PUSH_FRAME_r11 791 -#define _GUARD_IP__PUSH_FRAME_r22 792 -#define _GUARD_IP__PUSH_FRAME_r33 793 -#define _GUARD_IS_FALSE_POP_r00 794 -#define _GUARD_IS_FALSE_POP_r10 795 -#define _GUARD_IS_FALSE_POP_r21 796 -#define _GUARD_IS_FALSE_POP_r32 797 -#define _GUARD_IS_NONE_POP_r00 798 -#define _GUARD_IS_NONE_POP_r10 799 -#define _GUARD_IS_NONE_POP_r21 800 -#define _GUARD_IS_NONE_POP_r32 801 -#define _GUARD_IS_NOT_NONE_POP_r10 802 -#define _GUARD_IS_TRUE_POP_r00 803 -#define _GUARD_IS_TRUE_POP_r10 804 -#define _GUARD_IS_TRUE_POP_r21 805 -#define _GUARD_IS_TRUE_POP_r32 806 -#define _GUARD_KEYS_VERSION_r01 807 -#define _GUARD_KEYS_VERSION_r11 808 -#define _GUARD_KEYS_VERSION_r22 809 -#define _GUARD_KEYS_VERSION_r33 810 -#define _GUARD_NOS_DICT_r02 811 -#define _GUARD_NOS_DICT_r12 812 -#define _GUARD_NOS_DICT_r22 813 -#define _GUARD_NOS_DICT_r33 814 -#define _GUARD_NOS_FLOAT_r02 815 -#define _GUARD_NOS_FLOAT_r12 816 -#define _GUARD_NOS_FLOAT_r22 817 -#define _GUARD_NOS_FLOAT_r33 818 -#define _GUARD_NOS_INT_r02 819 -#define _GUARD_NOS_INT_r12 820 -#define _GUARD_NOS_INT_r22 821 -#define _GUARD_NOS_INT_r33 822 -#define _GUARD_NOS_LIST_r02 823 -#define _GUARD_NOS_LIST_r12 824 -#define _GUARD_NOS_LIST_r22 825 -#define _GUARD_NOS_LIST_r33 826 -#define _GUARD_NOS_NOT_NULL_r02 827 -#define _GUARD_NOS_NOT_NULL_r12 828 -#define _GUARD_NOS_NOT_NULL_r22 829 -#define _GUARD_NOS_NOT_NULL_r33 830 -#define _GUARD_NOS_NULL_r02 831 -#define _GUARD_NOS_NULL_r12 832 -#define _GUARD_NOS_NULL_r22 833 -#define _GUARD_NOS_NULL_r33 834 -#define _GUARD_NOS_OVERFLOWED_r02 835 -#define _GUARD_NOS_OVERFLOWED_r12 836 -#define _GUARD_NOS_OVERFLOWED_r22 837 -#define _GUARD_NOS_OVERFLOWED_r33 838 -#define _GUARD_NOS_TUPLE_r02 839 -#define _GUARD_NOS_TUPLE_r12 840 -#define _GUARD_NOS_TUPLE_r22 841 -#define _GUARD_NOS_TUPLE_r33 842 -#define _GUARD_NOS_UNICODE_r02 843 -#define _GUARD_NOS_UNICODE_r12 844 -#define _GUARD_NOS_UNICODE_r22 845 -#define _GUARD_NOS_UNICODE_r33 846 -#define _GUARD_NOT_EXHAUSTED_LIST_r02 847 -#define _GUARD_NOT_EXHAUSTED_LIST_r12 848 -#define _GUARD_NOT_EXHAUSTED_LIST_r22 849 -#define _GUARD_NOT_EXHAUSTED_LIST_r33 850 -#define _GUARD_NOT_EXHAUSTED_RANGE_r02 851 -#define _GUARD_NOT_EXHAUSTED_RANGE_r12 852 -#define _GUARD_NOT_EXHAUSTED_RANGE_r22 853 -#define _GUARD_NOT_EXHAUSTED_RANGE_r33 854 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r02 855 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r12 856 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r22 857 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r33 858 -#define _GUARD_THIRD_NULL_r03 859 -#define _GUARD_THIRD_NULL_r13 860 -#define _GUARD_THIRD_NULL_r23 861 -#define _GUARD_THIRD_NULL_r33 862 -#define _GUARD_TOS_ANY_SET_r01 863 -#define _GUARD_TOS_ANY_SET_r11 864 -#define _GUARD_TOS_ANY_SET_r22 865 -#define _GUARD_TOS_ANY_SET_r33 866 -#define _GUARD_TOS_DICT_r01 867 -#define _GUARD_TOS_DICT_r11 868 -#define _GUARD_TOS_DICT_r22 869 -#define _GUARD_TOS_DICT_r33 870 -#define _GUARD_TOS_FLOAT_r01 871 -#define _GUARD_TOS_FLOAT_r11 872 -#define _GUARD_TOS_FLOAT_r22 873 -#define _GUARD_TOS_FLOAT_r33 874 -#define _GUARD_TOS_INT_r01 875 -#define _GUARD_TOS_INT_r11 876 -#define _GUARD_TOS_INT_r22 877 -#define _GUARD_TOS_INT_r33 878 -#define _GUARD_TOS_LIST_r01 879 -#define _GUARD_TOS_LIST_r11 880 -#define _GUARD_TOS_LIST_r22 881 -#define _GUARD_TOS_LIST_r33 882 -#define _GUARD_TOS_OVERFLOWED_r01 883 -#define _GUARD_TOS_OVERFLOWED_r11 884 -#define _GUARD_TOS_OVERFLOWED_r22 885 -#define _GUARD_TOS_OVERFLOWED_r33 886 -#define _GUARD_TOS_SLICE_r01 887 -#define _GUARD_TOS_SLICE_r11 888 -#define _GUARD_TOS_SLICE_r22 889 -#define _GUARD_TOS_SLICE_r33 890 -#define _GUARD_TOS_TUPLE_r01 891 -#define _GUARD_TOS_TUPLE_r11 892 -#define _GUARD_TOS_TUPLE_r22 893 -#define _GUARD_TOS_TUPLE_r33 894 -#define _GUARD_TOS_UNICODE_r01 895 -#define _GUARD_TOS_UNICODE_r11 896 -#define _GUARD_TOS_UNICODE_r22 897 -#define _GUARD_TOS_UNICODE_r33 898 -#define _GUARD_TYPE_VERSION_r01 899 -#define _GUARD_TYPE_VERSION_r11 900 -#define _GUARD_TYPE_VERSION_r22 901 -#define _GUARD_TYPE_VERSION_r33 902 -#define _GUARD_TYPE_VERSION_AND_LOCK_r01 903 -#define _GUARD_TYPE_VERSION_AND_LOCK_r11 904 -#define _GUARD_TYPE_VERSION_AND_LOCK_r22 905 -#define _GUARD_TYPE_VERSION_AND_LOCK_r33 906 -#define _HANDLE_PENDING_AND_DEOPT_r00 907 -#define _HANDLE_PENDING_AND_DEOPT_r10 908 -#define _HANDLE_PENDING_AND_DEOPT_r20 909 -#define _HANDLE_PENDING_AND_DEOPT_r30 910 -#define _IMPORT_FROM_r12 911 -#define _IMPORT_NAME_r21 912 -#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS_r00 913 -#define _INIT_CALL_PY_EXACT_ARGS_r01 914 -#define _INIT_CALL_PY_EXACT_ARGS_0_r01 915 -#define _INIT_CALL_PY_EXACT_ARGS_1_r01 916 -#define _INIT_CALL_PY_EXACT_ARGS_2_r01 917 -#define _INIT_CALL_PY_EXACT_ARGS_3_r01 918 -#define _INIT_CALL_PY_EXACT_ARGS_4_r01 919 -#define _INSERT_NULL_r10 920 -#define _INSTRUMENTED_FOR_ITER_r23 921 -#define _INSTRUMENTED_INSTRUCTION_r00 922 -#define _INSTRUMENTED_JUMP_FORWARD_r00 923 -#define _INSTRUMENTED_JUMP_FORWARD_r11 924 -#define _INSTRUMENTED_JUMP_FORWARD_r22 925 -#define _INSTRUMENTED_JUMP_FORWARD_r33 926 -#define _INSTRUMENTED_LINE_r00 927 -#define _INSTRUMENTED_NOT_TAKEN_r00 928 -#define _INSTRUMENTED_NOT_TAKEN_r11 929 -#define _INSTRUMENTED_NOT_TAKEN_r22 930 -#define _INSTRUMENTED_NOT_TAKEN_r33 931 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r00 932 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r10 933 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r21 934 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r32 935 -#define _INSTRUMENTED_POP_JUMP_IF_NONE_r10 936 -#define _INSTRUMENTED_POP_JUMP_IF_NOT_NONE_r10 937 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r00 938 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r10 939 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r21 940 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r32 941 -#define _IS_NONE_r11 942 -#define _IS_OP_r21 943 -#define _ITER_CHECK_LIST_r02 944 -#define _ITER_CHECK_LIST_r12 945 -#define _ITER_CHECK_LIST_r22 946 -#define _ITER_CHECK_LIST_r33 947 -#define _ITER_CHECK_RANGE_r02 948 -#define _ITER_CHECK_RANGE_r12 949 -#define _ITER_CHECK_RANGE_r22 950 -#define _ITER_CHECK_RANGE_r33 951 -#define _ITER_CHECK_TUPLE_r02 952 -#define _ITER_CHECK_TUPLE_r12 953 -#define _ITER_CHECK_TUPLE_r22 954 -#define _ITER_CHECK_TUPLE_r33 955 -#define _ITER_JUMP_LIST_r02 956 -#define _ITER_JUMP_LIST_r12 957 -#define _ITER_JUMP_LIST_r22 958 -#define _ITER_JUMP_LIST_r33 959 -#define _ITER_JUMP_RANGE_r02 960 -#define _ITER_JUMP_RANGE_r12 961 -#define _ITER_JUMP_RANGE_r22 962 -#define _ITER_JUMP_RANGE_r33 963 -#define _ITER_JUMP_TUPLE_r02 964 -#define _ITER_JUMP_TUPLE_r12 965 -#define _ITER_JUMP_TUPLE_r22 966 -#define _ITER_JUMP_TUPLE_r33 967 -#define _ITER_NEXT_LIST_r23 968 -#define _ITER_NEXT_LIST_TIER_TWO_r23 969 -#define _ITER_NEXT_RANGE_r03 970 -#define _ITER_NEXT_RANGE_r13 971 -#define _ITER_NEXT_RANGE_r23 972 -#define _ITER_NEXT_TUPLE_r03 973 -#define _ITER_NEXT_TUPLE_r13 974 -#define _ITER_NEXT_TUPLE_r23 975 -#define _JUMP_BACKWARD_NO_INTERRUPT_r00 976 -#define _JUMP_BACKWARD_NO_INTERRUPT_r11 977 -#define _JUMP_BACKWARD_NO_INTERRUPT_r22 978 -#define _JUMP_BACKWARD_NO_INTERRUPT_r33 979 -#define _JUMP_TO_TOP_r00 980 -#define _LIST_APPEND_r10 981 -#define _LIST_EXTEND_r10 982 -#define _LOAD_ATTR_r10 983 -#define _LOAD_ATTR_CLASS_r11 984 -#define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN_r11 985 -#define _LOAD_ATTR_INSTANCE_VALUE_r02 986 -#define _LOAD_ATTR_INSTANCE_VALUE_r12 987 -#define _LOAD_ATTR_INSTANCE_VALUE_r23 988 -#define _LOAD_ATTR_METHOD_LAZY_DICT_r02 989 -#define _LOAD_ATTR_METHOD_LAZY_DICT_r12 990 -#define _LOAD_ATTR_METHOD_LAZY_DICT_r23 991 -#define _LOAD_ATTR_METHOD_NO_DICT_r02 992 -#define _LOAD_ATTR_METHOD_NO_DICT_r12 993 -#define _LOAD_ATTR_METHOD_NO_DICT_r23 994 -#define _LOAD_ATTR_METHOD_WITH_VALUES_r02 995 -#define _LOAD_ATTR_METHOD_WITH_VALUES_r12 996 -#define _LOAD_ATTR_METHOD_WITH_VALUES_r23 997 -#define _LOAD_ATTR_MODULE_r11 998 -#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT_r11 999 -#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES_r11 1000 -#define _LOAD_ATTR_PROPERTY_FRAME_r11 1001 -#define _LOAD_ATTR_SLOT_r11 1002 -#define _LOAD_ATTR_WITH_HINT_r12 1003 -#define _LOAD_BUILD_CLASS_r01 1004 -#define _LOAD_BYTECODE_r00 1005 -#define _LOAD_COMMON_CONSTANT_r01 1006 -#define _LOAD_COMMON_CONSTANT_r12 1007 -#define _LOAD_COMMON_CONSTANT_r23 1008 -#define _LOAD_CONST_r01 1009 -#define _LOAD_CONST_r12 1010 -#define _LOAD_CONST_r23 1011 -#define _LOAD_CONST_INLINE_r01 1012 -#define _LOAD_CONST_INLINE_r12 1013 -#define _LOAD_CONST_INLINE_r23 1014 -#define _LOAD_CONST_INLINE_BORROW_r01 1015 -#define _LOAD_CONST_INLINE_BORROW_r12 1016 -#define _LOAD_CONST_INLINE_BORROW_r23 1017 -#define _LOAD_CONST_UNDER_INLINE_r02 1018 -#define _LOAD_CONST_UNDER_INLINE_r12 1019 -#define _LOAD_CONST_UNDER_INLINE_r23 1020 -#define _LOAD_CONST_UNDER_INLINE_BORROW_r02 1021 -#define _LOAD_CONST_UNDER_INLINE_BORROW_r12 1022 -#define _LOAD_CONST_UNDER_INLINE_BORROW_r23 1023 -#define _LOAD_DEREF_r01 1024 -#define _LOAD_FAST_r01 1025 -#define _LOAD_FAST_r12 1026 -#define _LOAD_FAST_r23 1027 -#define _LOAD_FAST_0_r01 1028 -#define _LOAD_FAST_0_r12 1029 -#define _LOAD_FAST_0_r23 1030 -#define _LOAD_FAST_1_r01 1031 -#define _LOAD_FAST_1_r12 1032 -#define _LOAD_FAST_1_r23 1033 -#define _LOAD_FAST_2_r01 1034 -#define _LOAD_FAST_2_r12 1035 -#define _LOAD_FAST_2_r23 1036 -#define _LOAD_FAST_3_r01 1037 -#define _LOAD_FAST_3_r12 1038 -#define _LOAD_FAST_3_r23 1039 -#define _LOAD_FAST_4_r01 1040 -#define _LOAD_FAST_4_r12 1041 -#define _LOAD_FAST_4_r23 1042 -#define _LOAD_FAST_5_r01 1043 -#define _LOAD_FAST_5_r12 1044 -#define _LOAD_FAST_5_r23 1045 -#define _LOAD_FAST_6_r01 1046 -#define _LOAD_FAST_6_r12 1047 -#define _LOAD_FAST_6_r23 1048 -#define _LOAD_FAST_7_r01 1049 -#define _LOAD_FAST_7_r12 1050 -#define _LOAD_FAST_7_r23 1051 -#define _LOAD_FAST_AND_CLEAR_r01 1052 -#define _LOAD_FAST_AND_CLEAR_r12 1053 -#define _LOAD_FAST_AND_CLEAR_r23 1054 -#define _LOAD_FAST_BORROW_r01 1055 -#define _LOAD_FAST_BORROW_r12 1056 -#define _LOAD_FAST_BORROW_r23 1057 -#define _LOAD_FAST_BORROW_0_r01 1058 -#define _LOAD_FAST_BORROW_0_r12 1059 -#define _LOAD_FAST_BORROW_0_r23 1060 -#define _LOAD_FAST_BORROW_1_r01 1061 -#define _LOAD_FAST_BORROW_1_r12 1062 -#define _LOAD_FAST_BORROW_1_r23 1063 -#define _LOAD_FAST_BORROW_2_r01 1064 -#define _LOAD_FAST_BORROW_2_r12 1065 -#define _LOAD_FAST_BORROW_2_r23 1066 -#define _LOAD_FAST_BORROW_3_r01 1067 -#define _LOAD_FAST_BORROW_3_r12 1068 -#define _LOAD_FAST_BORROW_3_r23 1069 -#define _LOAD_FAST_BORROW_4_r01 1070 -#define _LOAD_FAST_BORROW_4_r12 1071 -#define _LOAD_FAST_BORROW_4_r23 1072 -#define _LOAD_FAST_BORROW_5_r01 1073 -#define _LOAD_FAST_BORROW_5_r12 1074 -#define _LOAD_FAST_BORROW_5_r23 1075 -#define _LOAD_FAST_BORROW_6_r01 1076 -#define _LOAD_FAST_BORROW_6_r12 1077 -#define _LOAD_FAST_BORROW_6_r23 1078 -#define _LOAD_FAST_BORROW_7_r01 1079 -#define _LOAD_FAST_BORROW_7_r12 1080 -#define _LOAD_FAST_BORROW_7_r23 1081 -#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r02 1082 -#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r13 1083 -#define _LOAD_FAST_CHECK_r01 1084 -#define _LOAD_FAST_CHECK_r12 1085 -#define _LOAD_FAST_CHECK_r23 1086 -#define _LOAD_FAST_LOAD_FAST_r02 1087 -#define _LOAD_FAST_LOAD_FAST_r13 1088 -#define _LOAD_FROM_DICT_OR_DEREF_r11 1089 -#define _LOAD_FROM_DICT_OR_GLOBALS_r11 1090 -#define _LOAD_GLOBAL_r00 1091 -#define _LOAD_GLOBAL_BUILTINS_r01 1092 -#define _LOAD_GLOBAL_MODULE_r01 1093 -#define _LOAD_LOCALS_r01 1094 -#define _LOAD_LOCALS_r12 1095 -#define _LOAD_LOCALS_r23 1096 -#define _LOAD_NAME_r01 1097 -#define _LOAD_SMALL_INT_r01 1098 -#define _LOAD_SMALL_INT_r12 1099 -#define _LOAD_SMALL_INT_r23 1100 -#define _LOAD_SMALL_INT_0_r01 1101 -#define _LOAD_SMALL_INT_0_r12 1102 -#define _LOAD_SMALL_INT_0_r23 1103 -#define _LOAD_SMALL_INT_1_r01 1104 -#define _LOAD_SMALL_INT_1_r12 1105 -#define _LOAD_SMALL_INT_1_r23 1106 -#define _LOAD_SMALL_INT_2_r01 1107 -#define _LOAD_SMALL_INT_2_r12 1108 -#define _LOAD_SMALL_INT_2_r23 1109 -#define _LOAD_SMALL_INT_3_r01 1110 -#define _LOAD_SMALL_INT_3_r12 1111 -#define _LOAD_SMALL_INT_3_r23 1112 -#define _LOAD_SPECIAL_r00 1113 -#define _LOAD_SUPER_ATTR_ATTR_r31 1114 -#define _LOAD_SUPER_ATTR_METHOD_r32 1115 -#define _MAKE_CALLARGS_A_TUPLE_r33 1116 -#define _MAKE_CELL_r00 1117 -#define _MAKE_FUNCTION_r11 1118 -#define _MAKE_WARM_r00 1119 -#define _MAKE_WARM_r11 1120 -#define _MAKE_WARM_r22 1121 -#define _MAKE_WARM_r33 1122 -#define _MAP_ADD_r20 1123 -#define _MATCH_CLASS_r31 1124 -#define _MATCH_KEYS_r23 1125 -#define _MATCH_MAPPING_r02 1126 -#define _MATCH_MAPPING_r12 1127 -#define _MATCH_MAPPING_r23 1128 -#define _MATCH_SEQUENCE_r02 1129 -#define _MATCH_SEQUENCE_r12 1130 -#define _MATCH_SEQUENCE_r23 1131 -#define _MAYBE_EXPAND_METHOD_r00 1132 -#define _MAYBE_EXPAND_METHOD_KW_r11 1133 -#define _MONITOR_CALL_r00 1134 -#define _MONITOR_CALL_KW_r11 1135 -#define _MONITOR_JUMP_BACKWARD_r00 1136 -#define _MONITOR_JUMP_BACKWARD_r11 1137 -#define _MONITOR_JUMP_BACKWARD_r22 1138 -#define _MONITOR_JUMP_BACKWARD_r33 1139 -#define _MONITOR_RESUME_r00 1140 -#define _NOP_r00 1141 -#define _NOP_r11 1142 -#define _NOP_r22 1143 -#define _NOP_r33 1144 -#define _POP_CALL_r20 1145 -#define _POP_CALL_LOAD_CONST_INLINE_BORROW_r21 1146 -#define _POP_CALL_ONE_r30 1147 -#define _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW_r31 1148 -#define _POP_CALL_TWO_r30 1149 -#define _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW_r31 1150 -#define _POP_EXCEPT_r10 1151 -#define _POP_ITER_r20 1152 -#define _POP_JUMP_IF_FALSE_r00 1153 -#define _POP_JUMP_IF_FALSE_r10 1154 -#define _POP_JUMP_IF_FALSE_r21 1155 -#define _POP_JUMP_IF_FALSE_r32 1156 -#define _POP_JUMP_IF_TRUE_r00 1157 -#define _POP_JUMP_IF_TRUE_r10 1158 -#define _POP_JUMP_IF_TRUE_r21 1159 -#define _POP_JUMP_IF_TRUE_r32 1160 -#define _POP_TOP_r10 1161 -#define _POP_TOP_FLOAT_r00 1162 -#define _POP_TOP_FLOAT_r10 1163 -#define _POP_TOP_FLOAT_r21 1164 -#define _POP_TOP_FLOAT_r32 1165 -#define _POP_TOP_INT_r00 1166 -#define _POP_TOP_INT_r10 1167 -#define _POP_TOP_INT_r21 1168 -#define _POP_TOP_INT_r32 1169 -#define _POP_TOP_LOAD_CONST_INLINE_r11 1170 -#define _POP_TOP_LOAD_CONST_INLINE_BORROW_r11 1171 -#define _POP_TOP_NOP_r00 1172 -#define _POP_TOP_NOP_r10 1173 -#define _POP_TOP_NOP_r21 1174 -#define _POP_TOP_NOP_r32 1175 -#define _POP_TOP_UNICODE_r00 1176 -#define _POP_TOP_UNICODE_r10 1177 -#define _POP_TOP_UNICODE_r21 1178 -#define _POP_TOP_UNICODE_r32 1179 -#define _POP_TWO_r20 1180 -#define _POP_TWO_LOAD_CONST_INLINE_BORROW_r21 1181 -#define _PUSH_EXC_INFO_r02 1182 -#define _PUSH_EXC_INFO_r12 1183 -#define _PUSH_EXC_INFO_r23 1184 -#define _PUSH_FRAME_r10 1185 -#define _PUSH_NULL_r01 1186 -#define _PUSH_NULL_r12 1187 -#define _PUSH_NULL_r23 1188 -#define _PUSH_NULL_CONDITIONAL_r00 1189 -#define _PY_FRAME_GENERAL_r01 1190 -#define _PY_FRAME_KW_r11 1191 -#define _QUICKEN_RESUME_r00 1192 -#define _QUICKEN_RESUME_r11 1193 -#define _QUICKEN_RESUME_r22 1194 -#define _QUICKEN_RESUME_r33 1195 -#define _REPLACE_WITH_TRUE_r11 1196 -#define _RESUME_CHECK_r00 1197 -#define _RESUME_CHECK_r11 1198 -#define _RESUME_CHECK_r22 1199 -#define _RESUME_CHECK_r33 1200 -#define _RETURN_GENERATOR_r01 1201 -#define _RETURN_VALUE_r11 1202 -#define _SAVE_RETURN_OFFSET_r00 1203 -#define _SAVE_RETURN_OFFSET_r11 1204 -#define _SAVE_RETURN_OFFSET_r22 1205 -#define _SAVE_RETURN_OFFSET_r33 1206 -#define _SEND_r22 1207 -#define _SEND_GEN_FRAME_r22 1208 -#define _SETUP_ANNOTATIONS_r00 1209 -#define _SET_ADD_r10 1210 -#define _SET_FUNCTION_ATTRIBUTE_r01 1211 -#define _SET_FUNCTION_ATTRIBUTE_r11 1212 -#define _SET_FUNCTION_ATTRIBUTE_r21 1213 -#define _SET_FUNCTION_ATTRIBUTE_r32 1214 -#define _SET_IP_r00 1215 -#define _SET_IP_r11 1216 -#define _SET_IP_r22 1217 -#define _SET_IP_r33 1218 -#define _SET_UPDATE_r10 1219 -#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03 1220 -#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13 1221 -#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23 1222 -#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r33 1223 -#define _SPILL_OR_RELOAD_r01 1224 -#define _SPILL_OR_RELOAD_r02 1225 -#define _SPILL_OR_RELOAD_r03 1226 -#define _SPILL_OR_RELOAD_r10 1227 -#define _SPILL_OR_RELOAD_r12 1228 -#define _SPILL_OR_RELOAD_r13 1229 -#define _SPILL_OR_RELOAD_r20 1230 -#define _SPILL_OR_RELOAD_r21 1231 -#define _SPILL_OR_RELOAD_r23 1232 -#define _SPILL_OR_RELOAD_r30 1233 -#define _SPILL_OR_RELOAD_r31 1234 -#define _SPILL_OR_RELOAD_r32 1235 -#define _START_EXECUTOR_r00 1236 -#define _STORE_ATTR_r20 1237 -#define _STORE_ATTR_INSTANCE_VALUE_r21 1238 -#define _STORE_ATTR_SLOT_r21 1239 -#define _STORE_ATTR_WITH_HINT_r21 1240 -#define _STORE_DEREF_r10 1241 -#define _STORE_FAST_r10 1242 -#define _STORE_FAST_0_r10 1243 -#define _STORE_FAST_1_r10 1244 -#define _STORE_FAST_2_r10 1245 -#define _STORE_FAST_3_r10 1246 -#define _STORE_FAST_4_r10 1247 -#define _STORE_FAST_5_r10 1248 -#define _STORE_FAST_6_r10 1249 -#define _STORE_FAST_7_r10 1250 -#define _STORE_FAST_LOAD_FAST_r11 1251 -#define _STORE_FAST_STORE_FAST_r20 1252 -#define _STORE_GLOBAL_r10 1253 -#define _STORE_NAME_r10 1254 -#define _STORE_SLICE_r30 1255 -#define _STORE_SUBSCR_r30 1256 -#define _STORE_SUBSCR_DICT_r31 1257 -#define _STORE_SUBSCR_LIST_INT_r32 1258 -#define _SWAP_r11 1259 -#define _SWAP_2_r02 1260 -#define _SWAP_2_r12 1261 -#define _SWAP_2_r22 1262 -#define _SWAP_2_r33 1263 -#define _SWAP_3_r03 1264 -#define _SWAP_3_r13 1265 -#define _SWAP_3_r23 1266 -#define _SWAP_3_r33 1267 -#define _TIER2_RESUME_CHECK_r00 1268 -#define _TIER2_RESUME_CHECK_r11 1269 -#define _TIER2_RESUME_CHECK_r22 1270 -#define _TIER2_RESUME_CHECK_r33 1271 -#define _TO_BOOL_r11 1272 -#define _TO_BOOL_BOOL_r01 1273 -#define _TO_BOOL_BOOL_r11 1274 -#define _TO_BOOL_BOOL_r22 1275 -#define _TO_BOOL_BOOL_r33 1276 -#define _TO_BOOL_INT_r11 1277 -#define _TO_BOOL_LIST_r11 1278 -#define _TO_BOOL_NONE_r01 1279 -#define _TO_BOOL_NONE_r11 1280 -#define _TO_BOOL_NONE_r22 1281 -#define _TO_BOOL_NONE_r33 1282 -#define _TO_BOOL_STR_r11 1283 -#define _TRACE_RECORD_r00 1284 -#define _UNARY_INVERT_r11 1285 -#define _UNARY_NEGATIVE_r11 1286 -#define _UNARY_NOT_r01 1287 -#define _UNARY_NOT_r11 1288 -#define _UNARY_NOT_r22 1289 -#define _UNARY_NOT_r33 1290 -#define _UNPACK_EX_r10 1291 -#define _UNPACK_SEQUENCE_r10 1292 -#define _UNPACK_SEQUENCE_LIST_r10 1293 -#define _UNPACK_SEQUENCE_TUPLE_r10 1294 -#define _UNPACK_SEQUENCE_TWO_TUPLE_r12 1295 -#define _WITH_EXCEPT_START_r33 1296 -#define _YIELD_VALUE_r11 1297 -#define MAX_UOP_REGS_ID 1297 +#define MAX_UOP_ID 555 +#define _BINARY_OP_r21 556 +#define _BINARY_OP_ADD_FLOAT_r03 557 +#define _BINARY_OP_ADD_FLOAT_r13 558 +#define _BINARY_OP_ADD_FLOAT_r23 559 +#define _BINARY_OP_ADD_INT_r03 560 +#define _BINARY_OP_ADD_INT_r13 561 +#define _BINARY_OP_ADD_INT_r23 562 +#define _BINARY_OP_ADD_UNICODE_r03 563 +#define _BINARY_OP_ADD_UNICODE_r13 564 +#define _BINARY_OP_ADD_UNICODE_r23 565 +#define _BINARY_OP_EXTEND_r21 566 +#define _BINARY_OP_INPLACE_ADD_UNICODE_r20 567 +#define _BINARY_OP_MULTIPLY_FLOAT_r03 568 +#define _BINARY_OP_MULTIPLY_FLOAT_r13 569 +#define _BINARY_OP_MULTIPLY_FLOAT_r23 570 +#define _BINARY_OP_MULTIPLY_INT_r03 571 +#define _BINARY_OP_MULTIPLY_INT_r13 572 +#define _BINARY_OP_MULTIPLY_INT_r23 573 +#define _BINARY_OP_SUBSCR_CHECK_FUNC_r23 574 +#define _BINARY_OP_SUBSCR_DICT_r21 575 +#define _BINARY_OP_SUBSCR_INIT_CALL_r01 576 +#define _BINARY_OP_SUBSCR_INIT_CALL_r11 577 +#define _BINARY_OP_SUBSCR_INIT_CALL_r21 578 +#define _BINARY_OP_SUBSCR_INIT_CALL_r31 579 +#define _BINARY_OP_SUBSCR_LIST_INT_r23 580 +#define _BINARY_OP_SUBSCR_LIST_SLICE_r21 581 +#define _BINARY_OP_SUBSCR_STR_INT_r23 582 +#define _BINARY_OP_SUBSCR_TUPLE_INT_r21 583 +#define _BINARY_OP_SUBTRACT_FLOAT_r03 584 +#define _BINARY_OP_SUBTRACT_FLOAT_r13 585 +#define _BINARY_OP_SUBTRACT_FLOAT_r23 586 +#define _BINARY_OP_SUBTRACT_INT_r03 587 +#define _BINARY_OP_SUBTRACT_INT_r13 588 +#define _BINARY_OP_SUBTRACT_INT_r23 589 +#define _BINARY_SLICE_r31 590 +#define _BUILD_INTERPOLATION_r01 591 +#define _BUILD_LIST_r01 592 +#define _BUILD_MAP_r01 593 +#define _BUILD_SET_r01 594 +#define _BUILD_SLICE_r01 595 +#define _BUILD_STRING_r01 596 +#define _BUILD_TEMPLATE_r21 597 +#define _BUILD_TUPLE_r01 598 +#define _CALL_BUILTIN_CLASS_r01 599 +#define _CALL_BUILTIN_FAST_r01 600 +#define _CALL_BUILTIN_FAST_WITH_KEYWORDS_r01 601 +#define _CALL_BUILTIN_O_r03 602 +#define _CALL_INTRINSIC_1_r11 603 +#define _CALL_INTRINSIC_2_r21 604 +#define _CALL_ISINSTANCE_r31 605 +#define _CALL_KW_NON_PY_r11 606 +#define _CALL_LEN_r33 607 +#define _CALL_LIST_APPEND_r02 608 +#define _CALL_LIST_APPEND_r12 609 +#define _CALL_LIST_APPEND_r22 610 +#define _CALL_LIST_APPEND_r32 611 +#define _CALL_METHOD_DESCRIPTOR_FAST_r01 612 +#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_r01 613 +#define _CALL_METHOD_DESCRIPTOR_NOARGS_r01 614 +#define _CALL_METHOD_DESCRIPTOR_O_r01 615 +#define _CALL_NON_PY_GENERAL_r01 616 +#define _CALL_STR_1_r32 617 +#define _CALL_TUPLE_1_r32 618 +#define _CALL_TYPE_1_r02 619 +#define _CALL_TYPE_1_r12 620 +#define _CALL_TYPE_1_r22 621 +#define _CALL_TYPE_1_r32 622 +#define _CHECK_AND_ALLOCATE_OBJECT_r00 623 +#define _CHECK_ATTR_CLASS_r01 624 +#define _CHECK_ATTR_CLASS_r11 625 +#define _CHECK_ATTR_CLASS_r22 626 +#define _CHECK_ATTR_CLASS_r33 627 +#define _CHECK_ATTR_METHOD_LAZY_DICT_r01 628 +#define _CHECK_ATTR_METHOD_LAZY_DICT_r11 629 +#define _CHECK_ATTR_METHOD_LAZY_DICT_r22 630 +#define _CHECK_ATTR_METHOD_LAZY_DICT_r33 631 +#define _CHECK_CALL_BOUND_METHOD_EXACT_ARGS_r00 632 +#define _CHECK_EG_MATCH_r22 633 +#define _CHECK_EXC_MATCH_r22 634 +#define _CHECK_FUNCTION_EXACT_ARGS_r00 635 +#define _CHECK_FUNCTION_VERSION_r00 636 +#define _CHECK_FUNCTION_VERSION_INLINE_r00 637 +#define _CHECK_FUNCTION_VERSION_INLINE_r11 638 +#define _CHECK_FUNCTION_VERSION_INLINE_r22 639 +#define _CHECK_FUNCTION_VERSION_INLINE_r33 640 +#define _CHECK_FUNCTION_VERSION_KW_r11 641 +#define _CHECK_IS_NOT_PY_CALLABLE_r00 642 +#define _CHECK_IS_NOT_PY_CALLABLE_KW_r11 643 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r01 644 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r11 645 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r22 646 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r33 647 +#define _CHECK_METHOD_VERSION_r00 648 +#define _CHECK_METHOD_VERSION_KW_r11 649 +#define _CHECK_PEP_523_r00 650 +#define _CHECK_PEP_523_r11 651 +#define _CHECK_PEP_523_r22 652 +#define _CHECK_PEP_523_r33 653 +#define _CHECK_PERIODIC_r00 654 +#define _CHECK_PERIODIC_AT_END_r00 655 +#define _CHECK_PERIODIC_IF_NOT_YIELD_FROM_r00 656 +#define _CHECK_RECURSION_REMAINING_r00 657 +#define _CHECK_RECURSION_REMAINING_r11 658 +#define _CHECK_RECURSION_REMAINING_r22 659 +#define _CHECK_RECURSION_REMAINING_r33 660 +#define _CHECK_STACK_SPACE_r00 661 +#define _CHECK_STACK_SPACE_OPERAND_r00 662 +#define _CHECK_STACK_SPACE_OPERAND_r11 663 +#define _CHECK_STACK_SPACE_OPERAND_r22 664 +#define _CHECK_STACK_SPACE_OPERAND_r33 665 +#define _CHECK_VALIDITY_r00 666 +#define _CHECK_VALIDITY_r11 667 +#define _CHECK_VALIDITY_r22 668 +#define _CHECK_VALIDITY_r33 669 +#define _COLD_DYNAMIC_EXIT_r00 670 +#define _COLD_EXIT_r00 671 +#define _COMPARE_OP_r21 672 +#define _COMPARE_OP_FLOAT_r01 673 +#define _COMPARE_OP_FLOAT_r11 674 +#define _COMPARE_OP_FLOAT_r21 675 +#define _COMPARE_OP_FLOAT_r32 676 +#define _COMPARE_OP_INT_r23 677 +#define _COMPARE_OP_STR_r21 678 +#define _CONTAINS_OP_r21 679 +#define _CONTAINS_OP_DICT_r21 680 +#define _CONTAINS_OP_SET_r21 681 +#define _CONVERT_VALUE_r11 682 +#define _COPY_r01 683 +#define _COPY_1_r02 684 +#define _COPY_1_r12 685 +#define _COPY_1_r23 686 +#define _COPY_2_r03 687 +#define _COPY_2_r13 688 +#define _COPY_2_r23 689 +#define _COPY_3_r03 690 +#define _COPY_3_r13 691 +#define _COPY_3_r23 692 +#define _COPY_3_r33 693 +#define _COPY_FREE_VARS_r00 694 +#define _COPY_FREE_VARS_r11 695 +#define _COPY_FREE_VARS_r22 696 +#define _COPY_FREE_VARS_r33 697 +#define _CREATE_INIT_FRAME_r01 698 +#define _DELETE_ATTR_r10 699 +#define _DELETE_DEREF_r00 700 +#define _DELETE_FAST_r00 701 +#define _DELETE_GLOBAL_r00 702 +#define _DELETE_NAME_r00 703 +#define _DELETE_SUBSCR_r20 704 +#define _DEOPT_r00 705 +#define _DEOPT_r10 706 +#define _DEOPT_r20 707 +#define _DEOPT_r30 708 +#define _DICT_MERGE_r10 709 +#define _DICT_UPDATE_r10 710 +#define _DO_CALL_r01 711 +#define _DO_CALL_FUNCTION_EX_r31 712 +#define _DO_CALL_KW_r11 713 +#define _DYNAMIC_EXIT_r00 714 +#define _DYNAMIC_EXIT_r10 715 +#define _DYNAMIC_EXIT_r20 716 +#define _DYNAMIC_EXIT_r30 717 +#define _END_FOR_r10 718 +#define _END_SEND_r21 719 +#define _ERROR_POP_N_r00 720 +#define _EXIT_INIT_CHECK_r10 721 +#define _EXIT_TRACE_r00 722 +#define _EXIT_TRACE_r10 723 +#define _EXIT_TRACE_r20 724 +#define _EXIT_TRACE_r30 725 +#define _EXPAND_METHOD_r00 726 +#define _EXPAND_METHOD_KW_r11 727 +#define _FATAL_ERROR_r00 728 +#define _FATAL_ERROR_r11 729 +#define _FATAL_ERROR_r22 730 +#define _FATAL_ERROR_r33 731 +#define _FORMAT_SIMPLE_r11 732 +#define _FORMAT_WITH_SPEC_r21 733 +#define _FOR_ITER_r23 734 +#define _FOR_ITER_GEN_FRAME_r03 735 +#define _FOR_ITER_GEN_FRAME_r13 736 +#define _FOR_ITER_GEN_FRAME_r23 737 +#define _FOR_ITER_TIER_TWO_r23 738 +#define _GET_AITER_r11 739 +#define _GET_ANEXT_r12 740 +#define _GET_AWAITABLE_r11 741 +#define _GET_ITER_r12 742 +#define _GET_LEN_r12 743 +#define _GET_YIELD_FROM_ITER_r11 744 +#define _GUARD_BINARY_OP_EXTEND_r22 745 +#define _GUARD_CALLABLE_ISINSTANCE_r03 746 +#define _GUARD_CALLABLE_ISINSTANCE_r13 747 +#define _GUARD_CALLABLE_ISINSTANCE_r23 748 +#define _GUARD_CALLABLE_ISINSTANCE_r33 749 +#define _GUARD_CALLABLE_LEN_r03 750 +#define _GUARD_CALLABLE_LEN_r13 751 +#define _GUARD_CALLABLE_LEN_r23 752 +#define _GUARD_CALLABLE_LEN_r33 753 +#define _GUARD_CALLABLE_LIST_APPEND_r03 754 +#define _GUARD_CALLABLE_LIST_APPEND_r13 755 +#define _GUARD_CALLABLE_LIST_APPEND_r23 756 +#define _GUARD_CALLABLE_LIST_APPEND_r33 757 +#define _GUARD_CALLABLE_STR_1_r03 758 +#define _GUARD_CALLABLE_STR_1_r13 759 +#define _GUARD_CALLABLE_STR_1_r23 760 +#define _GUARD_CALLABLE_STR_1_r33 761 +#define _GUARD_CALLABLE_TUPLE_1_r03 762 +#define _GUARD_CALLABLE_TUPLE_1_r13 763 +#define _GUARD_CALLABLE_TUPLE_1_r23 764 +#define _GUARD_CALLABLE_TUPLE_1_r33 765 +#define _GUARD_CALLABLE_TYPE_1_r03 766 +#define _GUARD_CALLABLE_TYPE_1_r13 767 +#define _GUARD_CALLABLE_TYPE_1_r23 768 +#define _GUARD_CALLABLE_TYPE_1_r33 769 +#define _GUARD_DORV_NO_DICT_r01 770 +#define _GUARD_DORV_NO_DICT_r11 771 +#define _GUARD_DORV_NO_DICT_r22 772 +#define _GUARD_DORV_NO_DICT_r33 773 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r01 774 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r11 775 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r22 776 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r33 777 +#define _GUARD_GLOBALS_VERSION_r00 778 +#define _GUARD_GLOBALS_VERSION_r11 779 +#define _GUARD_GLOBALS_VERSION_r22 780 +#define _GUARD_GLOBALS_VERSION_r33 781 +#define _GUARD_IP_RETURN_GENERATOR_r00 782 +#define _GUARD_IP_RETURN_GENERATOR_r11 783 +#define _GUARD_IP_RETURN_GENERATOR_r22 784 +#define _GUARD_IP_RETURN_GENERATOR_r33 785 +#define _GUARD_IP_RETURN_VALUE_r00 786 +#define _GUARD_IP_RETURN_VALUE_r11 787 +#define _GUARD_IP_RETURN_VALUE_r22 788 +#define _GUARD_IP_RETURN_VALUE_r33 789 +#define _GUARD_IP_YIELD_VALUE_r00 790 +#define _GUARD_IP_YIELD_VALUE_r11 791 +#define _GUARD_IP_YIELD_VALUE_r22 792 +#define _GUARD_IP_YIELD_VALUE_r33 793 +#define _GUARD_IP__PUSH_FRAME_r00 794 +#define _GUARD_IP__PUSH_FRAME_r11 795 +#define _GUARD_IP__PUSH_FRAME_r22 796 +#define _GUARD_IP__PUSH_FRAME_r33 797 +#define _GUARD_IS_FALSE_POP_r00 798 +#define _GUARD_IS_FALSE_POP_r10 799 +#define _GUARD_IS_FALSE_POP_r21 800 +#define _GUARD_IS_FALSE_POP_r32 801 +#define _GUARD_IS_NONE_POP_r00 802 +#define _GUARD_IS_NONE_POP_r10 803 +#define _GUARD_IS_NONE_POP_r21 804 +#define _GUARD_IS_NONE_POP_r32 805 +#define _GUARD_IS_NOT_NONE_POP_r10 806 +#define _GUARD_IS_TRUE_POP_r00 807 +#define _GUARD_IS_TRUE_POP_r10 808 +#define _GUARD_IS_TRUE_POP_r21 809 +#define _GUARD_IS_TRUE_POP_r32 810 +#define _GUARD_KEYS_VERSION_r01 811 +#define _GUARD_KEYS_VERSION_r11 812 +#define _GUARD_KEYS_VERSION_r22 813 +#define _GUARD_KEYS_VERSION_r33 814 +#define _GUARD_NOS_DICT_r02 815 +#define _GUARD_NOS_DICT_r12 816 +#define _GUARD_NOS_DICT_r22 817 +#define _GUARD_NOS_DICT_r33 818 +#define _GUARD_NOS_FLOAT_r02 819 +#define _GUARD_NOS_FLOAT_r12 820 +#define _GUARD_NOS_FLOAT_r22 821 +#define _GUARD_NOS_FLOAT_r33 822 +#define _GUARD_NOS_INT_r02 823 +#define _GUARD_NOS_INT_r12 824 +#define _GUARD_NOS_INT_r22 825 +#define _GUARD_NOS_INT_r33 826 +#define _GUARD_NOS_LIST_r02 827 +#define _GUARD_NOS_LIST_r12 828 +#define _GUARD_NOS_LIST_r22 829 +#define _GUARD_NOS_LIST_r33 830 +#define _GUARD_NOS_NOT_NULL_r02 831 +#define _GUARD_NOS_NOT_NULL_r12 832 +#define _GUARD_NOS_NOT_NULL_r22 833 +#define _GUARD_NOS_NOT_NULL_r33 834 +#define _GUARD_NOS_NULL_r02 835 +#define _GUARD_NOS_NULL_r12 836 +#define _GUARD_NOS_NULL_r22 837 +#define _GUARD_NOS_NULL_r33 838 +#define _GUARD_NOS_OVERFLOWED_r02 839 +#define _GUARD_NOS_OVERFLOWED_r12 840 +#define _GUARD_NOS_OVERFLOWED_r22 841 +#define _GUARD_NOS_OVERFLOWED_r33 842 +#define _GUARD_NOS_TUPLE_r02 843 +#define _GUARD_NOS_TUPLE_r12 844 +#define _GUARD_NOS_TUPLE_r22 845 +#define _GUARD_NOS_TUPLE_r33 846 +#define _GUARD_NOS_UNICODE_r02 847 +#define _GUARD_NOS_UNICODE_r12 848 +#define _GUARD_NOS_UNICODE_r22 849 +#define _GUARD_NOS_UNICODE_r33 850 +#define _GUARD_NOT_EXHAUSTED_LIST_r02 851 +#define _GUARD_NOT_EXHAUSTED_LIST_r12 852 +#define _GUARD_NOT_EXHAUSTED_LIST_r22 853 +#define _GUARD_NOT_EXHAUSTED_LIST_r33 854 +#define _GUARD_NOT_EXHAUSTED_RANGE_r02 855 +#define _GUARD_NOT_EXHAUSTED_RANGE_r12 856 +#define _GUARD_NOT_EXHAUSTED_RANGE_r22 857 +#define _GUARD_NOT_EXHAUSTED_RANGE_r33 858 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r02 859 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r12 860 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r22 861 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r33 862 +#define _GUARD_THIRD_NULL_r03 863 +#define _GUARD_THIRD_NULL_r13 864 +#define _GUARD_THIRD_NULL_r23 865 +#define _GUARD_THIRD_NULL_r33 866 +#define _GUARD_TOS_ANY_SET_r01 867 +#define _GUARD_TOS_ANY_SET_r11 868 +#define _GUARD_TOS_ANY_SET_r22 869 +#define _GUARD_TOS_ANY_SET_r33 870 +#define _GUARD_TOS_DICT_r01 871 +#define _GUARD_TOS_DICT_r11 872 +#define _GUARD_TOS_DICT_r22 873 +#define _GUARD_TOS_DICT_r33 874 +#define _GUARD_TOS_FLOAT_r01 875 +#define _GUARD_TOS_FLOAT_r11 876 +#define _GUARD_TOS_FLOAT_r22 877 +#define _GUARD_TOS_FLOAT_r33 878 +#define _GUARD_TOS_INT_r01 879 +#define _GUARD_TOS_INT_r11 880 +#define _GUARD_TOS_INT_r22 881 +#define _GUARD_TOS_INT_r33 882 +#define _GUARD_TOS_LIST_r01 883 +#define _GUARD_TOS_LIST_r11 884 +#define _GUARD_TOS_LIST_r22 885 +#define _GUARD_TOS_LIST_r33 886 +#define _GUARD_TOS_OVERFLOWED_r01 887 +#define _GUARD_TOS_OVERFLOWED_r11 888 +#define _GUARD_TOS_OVERFLOWED_r22 889 +#define _GUARD_TOS_OVERFLOWED_r33 890 +#define _GUARD_TOS_SLICE_r01 891 +#define _GUARD_TOS_SLICE_r11 892 +#define _GUARD_TOS_SLICE_r22 893 +#define _GUARD_TOS_SLICE_r33 894 +#define _GUARD_TOS_TUPLE_r01 895 +#define _GUARD_TOS_TUPLE_r11 896 +#define _GUARD_TOS_TUPLE_r22 897 +#define _GUARD_TOS_TUPLE_r33 898 +#define _GUARD_TOS_UNICODE_r01 899 +#define _GUARD_TOS_UNICODE_r11 900 +#define _GUARD_TOS_UNICODE_r22 901 +#define _GUARD_TOS_UNICODE_r33 902 +#define _GUARD_TYPE_VERSION_r01 903 +#define _GUARD_TYPE_VERSION_r11 904 +#define _GUARD_TYPE_VERSION_r22 905 +#define _GUARD_TYPE_VERSION_r33 906 +#define _GUARD_TYPE_VERSION_AND_LOCK_r01 907 +#define _GUARD_TYPE_VERSION_AND_LOCK_r11 908 +#define _GUARD_TYPE_VERSION_AND_LOCK_r22 909 +#define _GUARD_TYPE_VERSION_AND_LOCK_r33 910 +#define _HANDLE_PENDING_AND_DEOPT_r00 911 +#define _HANDLE_PENDING_AND_DEOPT_r10 912 +#define _HANDLE_PENDING_AND_DEOPT_r20 913 +#define _HANDLE_PENDING_AND_DEOPT_r30 914 +#define _IMPORT_FROM_r12 915 +#define _IMPORT_NAME_r21 916 +#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS_r00 917 +#define _INIT_CALL_PY_EXACT_ARGS_r01 918 +#define _INIT_CALL_PY_EXACT_ARGS_0_r01 919 +#define _INIT_CALL_PY_EXACT_ARGS_1_r01 920 +#define _INIT_CALL_PY_EXACT_ARGS_2_r01 921 +#define _INIT_CALL_PY_EXACT_ARGS_3_r01 922 +#define _INIT_CALL_PY_EXACT_ARGS_4_r01 923 +#define _INSERT_NULL_r10 924 +#define _INSTRUMENTED_FOR_ITER_r23 925 +#define _INSTRUMENTED_INSTRUCTION_r00 926 +#define _INSTRUMENTED_JUMP_FORWARD_r00 927 +#define _INSTRUMENTED_JUMP_FORWARD_r11 928 +#define _INSTRUMENTED_JUMP_FORWARD_r22 929 +#define _INSTRUMENTED_JUMP_FORWARD_r33 930 +#define _INSTRUMENTED_LINE_r00 931 +#define _INSTRUMENTED_NOT_TAKEN_r00 932 +#define _INSTRUMENTED_NOT_TAKEN_r11 933 +#define _INSTRUMENTED_NOT_TAKEN_r22 934 +#define _INSTRUMENTED_NOT_TAKEN_r33 935 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r00 936 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r10 937 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r21 938 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r32 939 +#define _INSTRUMENTED_POP_JUMP_IF_NONE_r10 940 +#define _INSTRUMENTED_POP_JUMP_IF_NOT_NONE_r10 941 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r00 942 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r10 943 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r21 944 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r32 945 +#define _IS_NONE_r11 946 +#define _IS_OP_r21 947 +#define _ITER_CHECK_LIST_r02 948 +#define _ITER_CHECK_LIST_r12 949 +#define _ITER_CHECK_LIST_r22 950 +#define _ITER_CHECK_LIST_r33 951 +#define _ITER_CHECK_RANGE_r02 952 +#define _ITER_CHECK_RANGE_r12 953 +#define _ITER_CHECK_RANGE_r22 954 +#define _ITER_CHECK_RANGE_r33 955 +#define _ITER_CHECK_TUPLE_r02 956 +#define _ITER_CHECK_TUPLE_r12 957 +#define _ITER_CHECK_TUPLE_r22 958 +#define _ITER_CHECK_TUPLE_r33 959 +#define _ITER_JUMP_LIST_r02 960 +#define _ITER_JUMP_LIST_r12 961 +#define _ITER_JUMP_LIST_r22 962 +#define _ITER_JUMP_LIST_r33 963 +#define _ITER_JUMP_RANGE_r02 964 +#define _ITER_JUMP_RANGE_r12 965 +#define _ITER_JUMP_RANGE_r22 966 +#define _ITER_JUMP_RANGE_r33 967 +#define _ITER_JUMP_TUPLE_r02 968 +#define _ITER_JUMP_TUPLE_r12 969 +#define _ITER_JUMP_TUPLE_r22 970 +#define _ITER_JUMP_TUPLE_r33 971 +#define _ITER_NEXT_LIST_r23 972 +#define _ITER_NEXT_LIST_TIER_TWO_r23 973 +#define _ITER_NEXT_RANGE_r03 974 +#define _ITER_NEXT_RANGE_r13 975 +#define _ITER_NEXT_RANGE_r23 976 +#define _ITER_NEXT_TUPLE_r03 977 +#define _ITER_NEXT_TUPLE_r13 978 +#define _ITER_NEXT_TUPLE_r23 979 +#define _JUMP_BACKWARD_NO_INTERRUPT_r00 980 +#define _JUMP_BACKWARD_NO_INTERRUPT_r11 981 +#define _JUMP_BACKWARD_NO_INTERRUPT_r22 982 +#define _JUMP_BACKWARD_NO_INTERRUPT_r33 983 +#define _JUMP_TO_TOP_r00 984 +#define _LIST_APPEND_r10 985 +#define _LIST_EXTEND_r10 986 +#define _LOAD_ATTR_r10 987 +#define _LOAD_ATTR_CLASS_r11 988 +#define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN_r11 989 +#define _LOAD_ATTR_INSTANCE_VALUE_r02 990 +#define _LOAD_ATTR_INSTANCE_VALUE_r12 991 +#define _LOAD_ATTR_INSTANCE_VALUE_r23 992 +#define _LOAD_ATTR_METHOD_LAZY_DICT_r02 993 +#define _LOAD_ATTR_METHOD_LAZY_DICT_r12 994 +#define _LOAD_ATTR_METHOD_LAZY_DICT_r23 995 +#define _LOAD_ATTR_METHOD_NO_DICT_r02 996 +#define _LOAD_ATTR_METHOD_NO_DICT_r12 997 +#define _LOAD_ATTR_METHOD_NO_DICT_r23 998 +#define _LOAD_ATTR_METHOD_WITH_VALUES_r02 999 +#define _LOAD_ATTR_METHOD_WITH_VALUES_r12 1000 +#define _LOAD_ATTR_METHOD_WITH_VALUES_r23 1001 +#define _LOAD_ATTR_MODULE_r11 1002 +#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT_r11 1003 +#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES_r11 1004 +#define _LOAD_ATTR_PROPERTY_FRAME_r11 1005 +#define _LOAD_ATTR_SLOT_r11 1006 +#define _LOAD_ATTR_WITH_HINT_r12 1007 +#define _LOAD_BUILD_CLASS_r01 1008 +#define _LOAD_BYTECODE_r00 1009 +#define _LOAD_COMMON_CONSTANT_r01 1010 +#define _LOAD_COMMON_CONSTANT_r12 1011 +#define _LOAD_COMMON_CONSTANT_r23 1012 +#define _LOAD_CONST_r01 1013 +#define _LOAD_CONST_r12 1014 +#define _LOAD_CONST_r23 1015 +#define _LOAD_CONST_INLINE_r01 1016 +#define _LOAD_CONST_INLINE_r12 1017 +#define _LOAD_CONST_INLINE_r23 1018 +#define _LOAD_CONST_INLINE_BORROW_r01 1019 +#define _LOAD_CONST_INLINE_BORROW_r12 1020 +#define _LOAD_CONST_INLINE_BORROW_r23 1021 +#define _LOAD_CONST_UNDER_INLINE_r02 1022 +#define _LOAD_CONST_UNDER_INLINE_r12 1023 +#define _LOAD_CONST_UNDER_INLINE_r23 1024 +#define _LOAD_CONST_UNDER_INLINE_BORROW_r02 1025 +#define _LOAD_CONST_UNDER_INLINE_BORROW_r12 1026 +#define _LOAD_CONST_UNDER_INLINE_BORROW_r23 1027 +#define _LOAD_DEREF_r01 1028 +#define _LOAD_FAST_r01 1029 +#define _LOAD_FAST_r12 1030 +#define _LOAD_FAST_r23 1031 +#define _LOAD_FAST_0_r01 1032 +#define _LOAD_FAST_0_r12 1033 +#define _LOAD_FAST_0_r23 1034 +#define _LOAD_FAST_1_r01 1035 +#define _LOAD_FAST_1_r12 1036 +#define _LOAD_FAST_1_r23 1037 +#define _LOAD_FAST_2_r01 1038 +#define _LOAD_FAST_2_r12 1039 +#define _LOAD_FAST_2_r23 1040 +#define _LOAD_FAST_3_r01 1041 +#define _LOAD_FAST_3_r12 1042 +#define _LOAD_FAST_3_r23 1043 +#define _LOAD_FAST_4_r01 1044 +#define _LOAD_FAST_4_r12 1045 +#define _LOAD_FAST_4_r23 1046 +#define _LOAD_FAST_5_r01 1047 +#define _LOAD_FAST_5_r12 1048 +#define _LOAD_FAST_5_r23 1049 +#define _LOAD_FAST_6_r01 1050 +#define _LOAD_FAST_6_r12 1051 +#define _LOAD_FAST_6_r23 1052 +#define _LOAD_FAST_7_r01 1053 +#define _LOAD_FAST_7_r12 1054 +#define _LOAD_FAST_7_r23 1055 +#define _LOAD_FAST_AND_CLEAR_r01 1056 +#define _LOAD_FAST_AND_CLEAR_r12 1057 +#define _LOAD_FAST_AND_CLEAR_r23 1058 +#define _LOAD_FAST_BORROW_r01 1059 +#define _LOAD_FAST_BORROW_r12 1060 +#define _LOAD_FAST_BORROW_r23 1061 +#define _LOAD_FAST_BORROW_0_r01 1062 +#define _LOAD_FAST_BORROW_0_r12 1063 +#define _LOAD_FAST_BORROW_0_r23 1064 +#define _LOAD_FAST_BORROW_1_r01 1065 +#define _LOAD_FAST_BORROW_1_r12 1066 +#define _LOAD_FAST_BORROW_1_r23 1067 +#define _LOAD_FAST_BORROW_2_r01 1068 +#define _LOAD_FAST_BORROW_2_r12 1069 +#define _LOAD_FAST_BORROW_2_r23 1070 +#define _LOAD_FAST_BORROW_3_r01 1071 +#define _LOAD_FAST_BORROW_3_r12 1072 +#define _LOAD_FAST_BORROW_3_r23 1073 +#define _LOAD_FAST_BORROW_4_r01 1074 +#define _LOAD_FAST_BORROW_4_r12 1075 +#define _LOAD_FAST_BORROW_4_r23 1076 +#define _LOAD_FAST_BORROW_5_r01 1077 +#define _LOAD_FAST_BORROW_5_r12 1078 +#define _LOAD_FAST_BORROW_5_r23 1079 +#define _LOAD_FAST_BORROW_6_r01 1080 +#define _LOAD_FAST_BORROW_6_r12 1081 +#define _LOAD_FAST_BORROW_6_r23 1082 +#define _LOAD_FAST_BORROW_7_r01 1083 +#define _LOAD_FAST_BORROW_7_r12 1084 +#define _LOAD_FAST_BORROW_7_r23 1085 +#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r02 1086 +#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r13 1087 +#define _LOAD_FAST_CHECK_r01 1088 +#define _LOAD_FAST_CHECK_r12 1089 +#define _LOAD_FAST_CHECK_r23 1090 +#define _LOAD_FAST_LOAD_FAST_r02 1091 +#define _LOAD_FAST_LOAD_FAST_r13 1092 +#define _LOAD_FROM_DICT_OR_DEREF_r11 1093 +#define _LOAD_FROM_DICT_OR_GLOBALS_r11 1094 +#define _LOAD_GLOBAL_r00 1095 +#define _LOAD_GLOBAL_BUILTINS_r01 1096 +#define _LOAD_GLOBAL_MODULE_r01 1097 +#define _LOAD_LOCALS_r01 1098 +#define _LOAD_LOCALS_r12 1099 +#define _LOAD_LOCALS_r23 1100 +#define _LOAD_NAME_r01 1101 +#define _LOAD_SMALL_INT_r01 1102 +#define _LOAD_SMALL_INT_r12 1103 +#define _LOAD_SMALL_INT_r23 1104 +#define _LOAD_SMALL_INT_0_r01 1105 +#define _LOAD_SMALL_INT_0_r12 1106 +#define _LOAD_SMALL_INT_0_r23 1107 +#define _LOAD_SMALL_INT_1_r01 1108 +#define _LOAD_SMALL_INT_1_r12 1109 +#define _LOAD_SMALL_INT_1_r23 1110 +#define _LOAD_SMALL_INT_2_r01 1111 +#define _LOAD_SMALL_INT_2_r12 1112 +#define _LOAD_SMALL_INT_2_r23 1113 +#define _LOAD_SMALL_INT_3_r01 1114 +#define _LOAD_SMALL_INT_3_r12 1115 +#define _LOAD_SMALL_INT_3_r23 1116 +#define _LOAD_SPECIAL_r00 1117 +#define _LOAD_SUPER_ATTR_ATTR_r31 1118 +#define _LOAD_SUPER_ATTR_METHOD_r32 1119 +#define _MAKE_CALLARGS_A_TUPLE_r33 1120 +#define _MAKE_CELL_r00 1121 +#define _MAKE_FUNCTION_r11 1122 +#define _MAKE_WARM_r00 1123 +#define _MAKE_WARM_r11 1124 +#define _MAKE_WARM_r22 1125 +#define _MAKE_WARM_r33 1126 +#define _MAP_ADD_r20 1127 +#define _MATCH_CLASS_r31 1128 +#define _MATCH_KEYS_r23 1129 +#define _MATCH_MAPPING_r02 1130 +#define _MATCH_MAPPING_r12 1131 +#define _MATCH_MAPPING_r23 1132 +#define _MATCH_SEQUENCE_r02 1133 +#define _MATCH_SEQUENCE_r12 1134 +#define _MATCH_SEQUENCE_r23 1135 +#define _MAYBE_EXPAND_METHOD_r00 1136 +#define _MAYBE_EXPAND_METHOD_KW_r11 1137 +#define _MONITOR_CALL_r00 1138 +#define _MONITOR_CALL_KW_r11 1139 +#define _MONITOR_JUMP_BACKWARD_r00 1140 +#define _MONITOR_JUMP_BACKWARD_r11 1141 +#define _MONITOR_JUMP_BACKWARD_r22 1142 +#define _MONITOR_JUMP_BACKWARD_r33 1143 +#define _MONITOR_RESUME_r00 1144 +#define _NOP_r00 1145 +#define _NOP_r11 1146 +#define _NOP_r22 1147 +#define _NOP_r33 1148 +#define _POP_CALL_r20 1149 +#define _POP_CALL_LOAD_CONST_INLINE_BORROW_r21 1150 +#define _POP_CALL_ONE_r30 1151 +#define _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW_r31 1152 +#define _POP_CALL_TWO_r30 1153 +#define _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW_r31 1154 +#define _POP_EXCEPT_r10 1155 +#define _POP_ITER_r20 1156 +#define _POP_JUMP_IF_FALSE_r00 1157 +#define _POP_JUMP_IF_FALSE_r10 1158 +#define _POP_JUMP_IF_FALSE_r21 1159 +#define _POP_JUMP_IF_FALSE_r32 1160 +#define _POP_JUMP_IF_TRUE_r00 1161 +#define _POP_JUMP_IF_TRUE_r10 1162 +#define _POP_JUMP_IF_TRUE_r21 1163 +#define _POP_JUMP_IF_TRUE_r32 1164 +#define _POP_TOP_r10 1165 +#define _POP_TOP_FLOAT_r00 1166 +#define _POP_TOP_FLOAT_r10 1167 +#define _POP_TOP_FLOAT_r21 1168 +#define _POP_TOP_FLOAT_r32 1169 +#define _POP_TOP_INT_r00 1170 +#define _POP_TOP_INT_r10 1171 +#define _POP_TOP_INT_r21 1172 +#define _POP_TOP_INT_r32 1173 +#define _POP_TOP_LOAD_CONST_INLINE_r11 1174 +#define _POP_TOP_LOAD_CONST_INLINE_BORROW_r11 1175 +#define _POP_TOP_NOP_r00 1176 +#define _POP_TOP_NOP_r10 1177 +#define _POP_TOP_NOP_r21 1178 +#define _POP_TOP_NOP_r32 1179 +#define _POP_TOP_UNICODE_r00 1180 +#define _POP_TOP_UNICODE_r10 1181 +#define _POP_TOP_UNICODE_r21 1182 +#define _POP_TOP_UNICODE_r32 1183 +#define _POP_TWO_r20 1184 +#define _POP_TWO_LOAD_CONST_INLINE_BORROW_r21 1185 +#define _PUSH_EXC_INFO_r02 1186 +#define _PUSH_EXC_INFO_r12 1187 +#define _PUSH_EXC_INFO_r23 1188 +#define _PUSH_FRAME_r10 1189 +#define _PUSH_NULL_r01 1190 +#define _PUSH_NULL_r12 1191 +#define _PUSH_NULL_r23 1192 +#define _PUSH_NULL_CONDITIONAL_r00 1193 +#define _PY_FRAME_GENERAL_r01 1194 +#define _PY_FRAME_KW_r11 1195 +#define _QUICKEN_RESUME_r00 1196 +#define _QUICKEN_RESUME_r11 1197 +#define _QUICKEN_RESUME_r22 1198 +#define _QUICKEN_RESUME_r33 1199 +#define _REPLACE_WITH_TRUE_r11 1200 +#define _RESUME_CHECK_r00 1201 +#define _RESUME_CHECK_r11 1202 +#define _RESUME_CHECK_r22 1203 +#define _RESUME_CHECK_r33 1204 +#define _RETURN_GENERATOR_r01 1205 +#define _RETURN_VALUE_r11 1206 +#define _SAVE_RETURN_OFFSET_r00 1207 +#define _SAVE_RETURN_OFFSET_r11 1208 +#define _SAVE_RETURN_OFFSET_r22 1209 +#define _SAVE_RETURN_OFFSET_r33 1210 +#define _SEND_r22 1211 +#define _SEND_GEN_FRAME_r22 1212 +#define _SETUP_ANNOTATIONS_r00 1213 +#define _SET_ADD_r10 1214 +#define _SET_FUNCTION_ATTRIBUTE_r01 1215 +#define _SET_FUNCTION_ATTRIBUTE_r11 1216 +#define _SET_FUNCTION_ATTRIBUTE_r21 1217 +#define _SET_FUNCTION_ATTRIBUTE_r32 1218 +#define _SET_IP_r00 1219 +#define _SET_IP_r11 1220 +#define _SET_IP_r22 1221 +#define _SET_IP_r33 1222 +#define _SET_UPDATE_r10 1223 +#define _SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r02 1224 +#define _SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r12 1225 +#define _SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r22 1226 +#define _SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r32 1227 +#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03 1228 +#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13 1229 +#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23 1230 +#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r33 1231 +#define _SPILL_OR_RELOAD_r01 1232 +#define _SPILL_OR_RELOAD_r02 1233 +#define _SPILL_OR_RELOAD_r03 1234 +#define _SPILL_OR_RELOAD_r10 1235 +#define _SPILL_OR_RELOAD_r12 1236 +#define _SPILL_OR_RELOAD_r13 1237 +#define _SPILL_OR_RELOAD_r20 1238 +#define _SPILL_OR_RELOAD_r21 1239 +#define _SPILL_OR_RELOAD_r23 1240 +#define _SPILL_OR_RELOAD_r30 1241 +#define _SPILL_OR_RELOAD_r31 1242 +#define _SPILL_OR_RELOAD_r32 1243 +#define _START_EXECUTOR_r00 1244 +#define _STORE_ATTR_r20 1245 +#define _STORE_ATTR_INSTANCE_VALUE_r21 1246 +#define _STORE_ATTR_SLOT_r21 1247 +#define _STORE_ATTR_WITH_HINT_r21 1248 +#define _STORE_DEREF_r10 1249 +#define _STORE_FAST_r10 1250 +#define _STORE_FAST_0_r10 1251 +#define _STORE_FAST_1_r10 1252 +#define _STORE_FAST_2_r10 1253 +#define _STORE_FAST_3_r10 1254 +#define _STORE_FAST_4_r10 1255 +#define _STORE_FAST_5_r10 1256 +#define _STORE_FAST_6_r10 1257 +#define _STORE_FAST_7_r10 1258 +#define _STORE_FAST_LOAD_FAST_r11 1259 +#define _STORE_FAST_STORE_FAST_r20 1260 +#define _STORE_GLOBAL_r10 1261 +#define _STORE_NAME_r10 1262 +#define _STORE_SLICE_r30 1263 +#define _STORE_SUBSCR_r30 1264 +#define _STORE_SUBSCR_DICT_r31 1265 +#define _STORE_SUBSCR_LIST_INT_r32 1266 +#define _SWAP_r11 1267 +#define _SWAP_2_r02 1268 +#define _SWAP_2_r12 1269 +#define _SWAP_2_r22 1270 +#define _SWAP_2_r33 1271 +#define _SWAP_3_r03 1272 +#define _SWAP_3_r13 1273 +#define _SWAP_3_r23 1274 +#define _SWAP_3_r33 1275 +#define _TIER2_RESUME_CHECK_r00 1276 +#define _TIER2_RESUME_CHECK_r11 1277 +#define _TIER2_RESUME_CHECK_r22 1278 +#define _TIER2_RESUME_CHECK_r33 1279 +#define _TO_BOOL_r11 1280 +#define _TO_BOOL_BOOL_r01 1281 +#define _TO_BOOL_BOOL_r11 1282 +#define _TO_BOOL_BOOL_r22 1283 +#define _TO_BOOL_BOOL_r33 1284 +#define _TO_BOOL_INT_r11 1285 +#define _TO_BOOL_LIST_r11 1286 +#define _TO_BOOL_NONE_r01 1287 +#define _TO_BOOL_NONE_r11 1288 +#define _TO_BOOL_NONE_r22 1289 +#define _TO_BOOL_NONE_r33 1290 +#define _TO_BOOL_STR_r11 1291 +#define _TRACE_RECORD_r00 1292 +#define _UNARY_INVERT_r11 1293 +#define _UNARY_NEGATIVE_r11 1294 +#define _UNARY_NOT_r01 1295 +#define _UNARY_NOT_r11 1296 +#define _UNARY_NOT_r22 1297 +#define _UNARY_NOT_r33 1298 +#define _UNPACK_EX_r10 1299 +#define _UNPACK_SEQUENCE_r10 1300 +#define _UNPACK_SEQUENCE_LIST_r10 1301 +#define _UNPACK_SEQUENCE_TUPLE_r10 1302 +#define _UNPACK_SEQUENCE_TWO_TUPLE_r12 1303 +#define _WITH_EXCEPT_START_r33 1304 +#define _YIELD_VALUE_r11 1305 +#define MAX_UOP_REGS_ID 1305 #ifdef __cplusplus } diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index d84c88c92435fe..f751f642b81ff8 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -269,7 +269,7 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = { [_GUARD_NOS_NOT_NULL] = HAS_EXIT_FLAG, [_GUARD_THIRD_NULL] = HAS_DEOPT_FLAG, [_GUARD_CALLABLE_TYPE_1] = HAS_DEOPT_FLAG, - [_CALL_TYPE_1] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, + [_CALL_TYPE_1] = HAS_ARG_FLAG, [_GUARD_CALLABLE_STR_1] = HAS_DEOPT_FLAG, [_CALL_STR_1] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG, [_GUARD_CALLABLE_TUPLE_1] = HAS_DEOPT_FLAG, @@ -335,6 +335,7 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = { [_POP_TWO_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG, [_POP_CALL_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG, [_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG, + [_SHUFFLE_2_LOAD_CONST_INLINE_BORROW] = 0, [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW] = 0, [_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG, [_LOAD_CONST_UNDER_INLINE] = 0, @@ -2473,12 +2474,12 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { }, }, [_CALL_TYPE_1] = { - .best = { 3, 3, 3, 3 }, + .best = { 0, 1, 2, 3 }, .entries = { - { -1, -1, -1 }, - { -1, -1, -1 }, - { -1, -1, -1 }, - { 1, 3, _CALL_TYPE_1_r31 }, + { 2, 0, _CALL_TYPE_1_r02 }, + { 2, 1, _CALL_TYPE_1_r12 }, + { 2, 2, _CALL_TYPE_1_r22 }, + { 2, 3, _CALL_TYPE_1_r32 }, }, }, [_GUARD_CALLABLE_STR_1] = { @@ -3066,6 +3067,15 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { { 1, 3, _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW_r31 }, }, }, + [_SHUFFLE_2_LOAD_CONST_INLINE_BORROW] = { + .best = { 0, 1, 2, 3 }, + .entries = { + { 2, 0, _SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r02 }, + { 2, 1, _SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r12 }, + { 2, 2, _SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r22 }, + { 2, 3, _SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r32 }, + }, + }, [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW] = { .best = { 0, 1, 2, 3 }, .entries = { @@ -3698,7 +3708,10 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = { [_GUARD_CALLABLE_TYPE_1_r13] = _GUARD_CALLABLE_TYPE_1, [_GUARD_CALLABLE_TYPE_1_r23] = _GUARD_CALLABLE_TYPE_1, [_GUARD_CALLABLE_TYPE_1_r33] = _GUARD_CALLABLE_TYPE_1, - [_CALL_TYPE_1_r31] = _CALL_TYPE_1, + [_CALL_TYPE_1_r02] = _CALL_TYPE_1, + [_CALL_TYPE_1_r12] = _CALL_TYPE_1, + [_CALL_TYPE_1_r22] = _CALL_TYPE_1, + [_CALL_TYPE_1_r32] = _CALL_TYPE_1, [_GUARD_CALLABLE_STR_1_r03] = _GUARD_CALLABLE_STR_1, [_GUARD_CALLABLE_STR_1_r13] = _GUARD_CALLABLE_STR_1, [_GUARD_CALLABLE_STR_1_r23] = _GUARD_CALLABLE_STR_1, @@ -3829,6 +3842,10 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = { [_POP_TWO_LOAD_CONST_INLINE_BORROW_r21] = _POP_TWO_LOAD_CONST_INLINE_BORROW, [_POP_CALL_LOAD_CONST_INLINE_BORROW_r21] = _POP_CALL_LOAD_CONST_INLINE_BORROW, [_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW_r31] = _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW, + [_SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r02] = _SHUFFLE_2_LOAD_CONST_INLINE_BORROW, + [_SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r12] = _SHUFFLE_2_LOAD_CONST_INLINE_BORROW, + [_SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r22] = _SHUFFLE_2_LOAD_CONST_INLINE_BORROW, + [_SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r32] = _SHUFFLE_2_LOAD_CONST_INLINE_BORROW, [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03] = _SHUFFLE_3_LOAD_CONST_INLINE_BORROW, [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13] = _SHUFFLE_3_LOAD_CONST_INLINE_BORROW, [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23] = _SHUFFLE_3_LOAD_CONST_INLINE_BORROW, @@ -4017,7 +4034,10 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_CALL_TUPLE_1] = "_CALL_TUPLE_1", [_CALL_TUPLE_1_r32] = "_CALL_TUPLE_1_r32", [_CALL_TYPE_1] = "_CALL_TYPE_1", - [_CALL_TYPE_1_r31] = "_CALL_TYPE_1_r31", + [_CALL_TYPE_1_r02] = "_CALL_TYPE_1_r02", + [_CALL_TYPE_1_r12] = "_CALL_TYPE_1_r12", + [_CALL_TYPE_1_r22] = "_CALL_TYPE_1_r22", + [_CALL_TYPE_1_r32] = "_CALL_TYPE_1_r32", [_CHECK_AND_ALLOCATE_OBJECT] = "_CHECK_AND_ALLOCATE_OBJECT", [_CHECK_AND_ALLOCATE_OBJECT_r00] = "_CHECK_AND_ALLOCATE_OBJECT_r00", [_CHECK_ATTR_CLASS] = "_CHECK_ATTR_CLASS", @@ -4780,6 +4800,11 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_SET_IP_r33] = "_SET_IP_r33", [_SET_UPDATE] = "_SET_UPDATE", [_SET_UPDATE_r10] = "_SET_UPDATE_r10", + [_SHUFFLE_2_LOAD_CONST_INLINE_BORROW] = "_SHUFFLE_2_LOAD_CONST_INLINE_BORROW", + [_SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r02] = "_SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r02", + [_SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r12] = "_SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r12", + [_SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r22] = "_SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r22", + [_SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r32] = "_SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r32", [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW] = "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW", [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03] = "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03", [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13] = "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13", @@ -5502,6 +5527,8 @@ int _PyUop_num_popped(int opcode, int oparg) return 2; case _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW: return 3; + case _SHUFFLE_2_LOAD_CONST_INLINE_BORROW: + return 3; case _SHUFFLE_3_LOAD_CONST_INLINE_BORROW: return 3; case _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW: diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 570fb857e19254..3d2b61d355a309 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -1925,9 +1925,10 @@ def testfunc(n): self.assertIsNotNone(ex) uops = get_opnames(ex) # When the result of type(...) is known, _CALL_TYPE_1 is replaced with - # _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW which is optimized away in + # _SHUFFLE_2_LOAD_CONST_INLINE_BORROW which is optimized away in # remove_unneeded_uops. self.assertNotIn("_CALL_TYPE_1", uops) + self.assertNotIn("_SHUFFLE_2_LOAD_CONST_INLINE_BORROW", uops) self.assertNotIn("_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW", uops) self.assertNotIn("_POP_CALL_LOAD_CONST_INLINE_BORROW", uops) self.assertNotIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops) @@ -1947,6 +1948,21 @@ def testfunc(n): uops = get_opnames(ex) self.assertNotIn("_GUARD_IS_NOT_NONE_POP", uops) + def test_call_type_1_pop_top(self): + def testfunc(n): + x = 0 + for _ in range(n): + foo = eval('42') + x += type(foo) is int + return x + + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual(res, TIER2_THRESHOLD) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertIn("_CALL_TYPE_1", uops) + self.assertIn("_POP_TOP_NOP", uops) + def test_call_tuple_1_pop_top(self): def testfunc(n): x = 0 diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-23-20-54-15.gh-issue-134584.ZNcziF.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-23-20-54-15.gh-issue-134584.ZNcziF.rst new file mode 100644 index 00000000000000..520690bc25c4cf --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-23-20-54-15.gh-issue-134584.ZNcziF.rst @@ -0,0 +1 @@ +Eliminate redundant refcounting from ``_CALL_TYPE_1``. Patch by Tomas Roun diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 5c23b2f0cc989a..d21c17d072c3f9 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -3986,17 +3986,14 @@ dummy_func( DEOPT_IF(callable_o != (PyObject *)&PyType_Type); } - op(_CALL_TYPE_1, (callable, null, arg -- res)) { + op(_CALL_TYPE_1, (callable, null, arg -- res, a)) { PyObject *arg_o = PyStackRef_AsPyObjectBorrow(arg); assert(oparg == 1); - DEAD(null); - DEAD(callable); - (void)callable; // Silence compiler warnings about unused variables - (void)null; STAT_INC(CALL, hit); + a = arg; + INPUTS_DEAD(); res = PyStackRef_FromPyObjectNew(Py_TYPE(arg_o)); - PyStackRef_CLOSE(arg); } macro(CALL_TYPE_1) = @@ -4004,7 +4001,8 @@ dummy_func( unused/2 + _GUARD_NOS_NULL + _GUARD_CALLABLE_TYPE_1 + - _CALL_TYPE_1; + _CALL_TYPE_1 + + POP_TOP; op(_GUARD_CALLABLE_STR_1, (callable, unused, unused -- callable, unused, unused)) { PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable); @@ -5258,6 +5256,12 @@ dummy_func( value = PyStackRef_FromPyObjectBorrow(ptr); } + tier2 op(_SHUFFLE_2_LOAD_CONST_INLINE_BORROW, (ptr/4, callable, null, arg -- res, a)) { + res = PyStackRef_FromPyObjectBorrow(ptr); + a = arg; + INPUTS_DEAD(); + } + tier2 op(_SHUFFLE_3_LOAD_CONST_INLINE_BORROW, (ptr/4, callable, null, arg -- res, a, c)) { res = PyStackRef_FromPyObjectBorrow(ptr); a = arg; diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index c47c485fb7c09a..f64747d6f27f2a 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -12810,38 +12810,94 @@ break; } - case _CALL_TYPE_1_r31: { + case _CALL_TYPE_1_r02: { + CHECK_CURRENT_CACHED_VALUES(0); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + _PyStackRef arg; + _PyStackRef res; + _PyStackRef a; + oparg = CURRENT_OPARG(); + arg = stack_pointer[-1]; + PyObject *arg_o = PyStackRef_AsPyObjectBorrow(arg); + assert(oparg == 1); + STAT_INC(CALL, hit); + a = arg; + res = PyStackRef_FromPyObjectNew(Py_TYPE(arg_o)); + _tos_cache1 = a; + _tos_cache0 = res; + SET_CURRENT_CACHED_VALUES(2); + stack_pointer += -3; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + break; + } + + case _CALL_TYPE_1_r12: { + CHECK_CURRENT_CACHED_VALUES(1); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + _PyStackRef arg; + _PyStackRef res; + _PyStackRef a; + _PyStackRef _stack_item_0 = _tos_cache0; + oparg = CURRENT_OPARG(); + arg = _stack_item_0; + PyObject *arg_o = PyStackRef_AsPyObjectBorrow(arg); + assert(oparg == 1); + STAT_INC(CALL, hit); + a = arg; + res = PyStackRef_FromPyObjectNew(Py_TYPE(arg_o)); + _tos_cache1 = a; + _tos_cache0 = res; + SET_CURRENT_CACHED_VALUES(2); + stack_pointer += -2; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + break; + } + + case _CALL_TYPE_1_r22: { + CHECK_CURRENT_CACHED_VALUES(2); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + _PyStackRef arg; + _PyStackRef res; + _PyStackRef a; + _PyStackRef _stack_item_0 = _tos_cache0; + _PyStackRef _stack_item_1 = _tos_cache1; + oparg = CURRENT_OPARG(); + arg = _stack_item_1; + PyObject *arg_o = PyStackRef_AsPyObjectBorrow(arg); + assert(oparg == 1); + STAT_INC(CALL, hit); + a = arg; + res = PyStackRef_FromPyObjectNew(Py_TYPE(arg_o)); + _tos_cache1 = a; + _tos_cache0 = res; + SET_CURRENT_CACHED_VALUES(2); + stack_pointer += -1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + break; + } + + case _CALL_TYPE_1_r32: { CHECK_CURRENT_CACHED_VALUES(3); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); _PyStackRef arg; - _PyStackRef null; - _PyStackRef callable; _PyStackRef res; + _PyStackRef a; _PyStackRef _stack_item_0 = _tos_cache0; _PyStackRef _stack_item_1 = _tos_cache1; _PyStackRef _stack_item_2 = _tos_cache2; oparg = CURRENT_OPARG(); arg = _stack_item_2; - null = _stack_item_1; - callable = _stack_item_0; PyObject *arg_o = PyStackRef_AsPyObjectBorrow(arg); assert(oparg == 1); - (void)callable; - (void)null; STAT_INC(CALL, hit); + a = arg; res = PyStackRef_FromPyObjectNew(Py_TYPE(arg_o)); - stack_pointer[0] = res; - stack_pointer += 1; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); - _PyFrame_SetStackPointer(frame, stack_pointer); - PyStackRef_CLOSE(arg); - stack_pointer = _PyFrame_GetStackPointer(frame); + _tos_cache1 = a; _tos_cache0 = res; - _tos_cache1 = PyStackRef_ZERO_BITS; - _tos_cache2 = PyStackRef_ZERO_BITS; - SET_CURRENT_CACHED_VALUES(1); - stack_pointer += -1; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + SET_CURRENT_CACHED_VALUES(2); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); break; } @@ -16599,6 +16655,86 @@ break; } + case _SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r02: { + CHECK_CURRENT_CACHED_VALUES(0); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + _PyStackRef arg; + _PyStackRef res; + _PyStackRef a; + arg = stack_pointer[-1]; + PyObject *ptr = (PyObject *)CURRENT_OPERAND0_64(); + res = PyStackRef_FromPyObjectBorrow(ptr); + a = arg; + _tos_cache1 = a; + _tos_cache0 = res; + SET_CURRENT_CACHED_VALUES(2); + stack_pointer += -3; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + break; + } + + case _SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r12: { + CHECK_CURRENT_CACHED_VALUES(1); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + _PyStackRef arg; + _PyStackRef res; + _PyStackRef a; + _PyStackRef _stack_item_0 = _tos_cache0; + arg = _stack_item_0; + PyObject *ptr = (PyObject *)CURRENT_OPERAND0_64(); + res = PyStackRef_FromPyObjectBorrow(ptr); + a = arg; + _tos_cache1 = a; + _tos_cache0 = res; + SET_CURRENT_CACHED_VALUES(2); + stack_pointer += -2; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + break; + } + + case _SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r22: { + CHECK_CURRENT_CACHED_VALUES(2); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + _PyStackRef arg; + _PyStackRef res; + _PyStackRef a; + _PyStackRef _stack_item_0 = _tos_cache0; + _PyStackRef _stack_item_1 = _tos_cache1; + arg = _stack_item_1; + PyObject *ptr = (PyObject *)CURRENT_OPERAND0_64(); + res = PyStackRef_FromPyObjectBorrow(ptr); + a = arg; + _tos_cache1 = a; + _tos_cache0 = res; + SET_CURRENT_CACHED_VALUES(2); + stack_pointer += -1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + break; + } + + case _SHUFFLE_2_LOAD_CONST_INLINE_BORROW_r32: { + CHECK_CURRENT_CACHED_VALUES(3); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + _PyStackRef arg; + _PyStackRef res; + _PyStackRef a; + _PyStackRef _stack_item_0 = _tos_cache0; + _PyStackRef _stack_item_1 = _tos_cache1; + _PyStackRef _stack_item_2 = _tos_cache2; + arg = _stack_item_2; + PyObject *ptr = (PyObject *)CURRENT_OPERAND0_64(); + res = PyStackRef_FromPyObjectBorrow(ptr); + a = arg; + _tos_cache1 = a; + _tos_cache0 = res; + SET_CURRENT_CACHED_VALUES(2); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + break; + } + case _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03: { CHECK_CURRENT_CACHED_VALUES(0); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 791df8a9750480..baf199969de94e 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -4144,6 +4144,8 @@ _PyStackRef callable; _PyStackRef arg; _PyStackRef res; + _PyStackRef a; + _PyStackRef value; /* Skip 1 cache entry */ /* Skip 2 cache entries */ // _GUARD_NOS_NULL @@ -4170,15 +4172,18 @@ arg = stack_pointer[-1]; PyObject *arg_o = PyStackRef_AsPyObjectBorrow(arg); assert(oparg == 1); - (void)callable; - (void)null; STAT_INC(CALL, hit); + a = arg; res = PyStackRef_FromPyObjectNew(Py_TYPE(arg_o)); + } + // _POP_TOP + { + value = a; stack_pointer[-3] = res; stack_pointer += -2; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); - PyStackRef_CLOSE(arg); + PyStackRef_XCLOSE(value); stack_pointer = _PyFrame_GetStackPointer(frame); } DISPATCH(); diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index a0210fdcbff3d3..81479a8f28e319 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -446,7 +446,9 @@ const uint16_t op_without_push[MAX_UOP_ID + 1] = { [_POP_TOP_LOAD_CONST_INLINE] = _POP_TOP, [_POP_TOP_LOAD_CONST_INLINE_BORROW] = _POP_TOP, [_POP_TWO_LOAD_CONST_INLINE_BORROW] = _POP_TWO, + [_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW] = _POP_CALL_ONE, [_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW] = _POP_CALL_TWO, + [_SHUFFLE_2_LOAD_CONST_INLINE_BORROW] = _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW, }; const bool op_skip[MAX_UOP_ID + 1] = { @@ -458,6 +460,10 @@ const bool op_skip[MAX_UOP_ID + 1] = { const uint16_t op_without_pop[MAX_UOP_ID + 1] = { [_POP_TOP] = _NOP, + [_POP_TOP_NOP] = _NOP, + [_POP_TOP_INT] = _NOP, + [_POP_TOP_FLOAT] = _NOP, + [_POP_TOP_UNICODE] = _NOP, [_POP_TOP_LOAD_CONST_INLINE] = _LOAD_CONST_INLINE, [_POP_TOP_LOAD_CONST_INLINE_BORROW] = _LOAD_CONST_INLINE_BORROW, [_POP_TWO] = _POP_TOP, diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 2bca0a3cd7cc85..b40b597643dc94 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -533,6 +533,11 @@ dummy_func(void) { value = PyJitRef_Borrow(sym_new_const(ctx, ptr)); } + op(_SHUFFLE_2_LOAD_CONST_INLINE_BORROW, (ptr/4, unused, unused, arg -- res, a)) { + res = PyJitRef_Borrow(sym_new_const(ctx, ptr)); + a = arg; + } + op(_POP_TOP, (value -- )) { PyTypeObject *typ = sym_get_type(value); if (PyJitRef_IsBorrowed(value) || @@ -981,16 +986,17 @@ dummy_func(void) { next = sym_new_type(ctx, &PyLong_Type); } - op(_CALL_TYPE_1, (unused, unused, arg -- res)) { + op(_CALL_TYPE_1, (unused, unused, arg -- res, a)) { PyObject* type = (PyObject *)sym_get_type(arg); if (type) { res = sym_new_const(ctx, type); - REPLACE_OP(this_instr, _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW, 0, + REPLACE_OP(this_instr, _SHUFFLE_2_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)type); } else { res = sym_new_not_null(ctx); } + a = arg; } op(_CALL_STR_1, (unused, unused, arg -- res, a)) { diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index e52d4e1db467b2..a17a5688847e07 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -2680,19 +2680,22 @@ case _CALL_TYPE_1: { JitOptRef arg; JitOptRef res; + JitOptRef a; arg = stack_pointer[-1]; PyObject* type = (PyObject *)sym_get_type(arg); if (type) { res = sym_new_const(ctx, type); - REPLACE_OP(this_instr, _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW, 0, + REPLACE_OP(this_instr, _SHUFFLE_2_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)type); } else { res = sym_new_not_null(ctx); } - CHECK_STACK_BOUNDS(-2); + a = arg; + CHECK_STACK_BOUNDS(-1); stack_pointer[-3] = res; - stack_pointer += -2; + stack_pointer[-2] = a; + stack_pointer += -1; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); break; } @@ -3450,6 +3453,22 @@ break; } + case _SHUFFLE_2_LOAD_CONST_INLINE_BORROW: { + JitOptRef arg; + JitOptRef res; + JitOptRef a; + arg = stack_pointer[-1]; + PyObject *ptr = (PyObject *)this_instr->operand0; + res = PyJitRef_Borrow(sym_new_const(ctx, ptr)); + a = arg; + CHECK_STACK_BOUNDS(-1); + stack_pointer[-3] = res; + stack_pointer[-2] = a; + stack_pointer += -1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + break; + } + case _SHUFFLE_3_LOAD_CONST_INLINE_BORROW: { JitOptRef res; JitOptRef a; From 20aeb3a4631beebd255781ba4932b53011edb011 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 23 Dec 2025 17:19:34 +0000 Subject: [PATCH 016/220] GH-143026: Fix assertion error in executor management. (GH-143104) --- Include/internal/pycore_object.h | 25 ------------------------- Include/internal/pycore_optimizer.h | 1 + Lib/test/test_capi/test_opt.py | 20 ++++++++++++++++++++ Python/optimizer.c | 9 +++++++-- Tools/cases_generator/analyzer.py | 1 - 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 6b91e4334b169e..d14cee6af66103 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -252,25 +252,6 @@ _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) } } -static inline void -_Py_DECREF_NO_DEALLOC(PyObject *op) -{ - if (_Py_IsImmortal(op)) { - _Py_DECREF_IMMORTAL_STAT_INC(); - return; - } - _Py_DECREF_STAT_INC(); -#ifdef Py_REF_DEBUG - _Py_DEC_REFTOTAL(PyInterpreterState_Get()); -#endif - op->ob_refcnt--; -#ifdef Py_DEBUG - if (op->ob_refcnt <= 0) { - _Py_FatalRefcountError("Expected a positive remaining refcount"); - } -#endif -} - #else // TODO: implement Py_DECREF specializations for Py_GIL_DISABLED build static inline void @@ -279,12 +260,6 @@ _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) Py_DECREF(op); } -static inline void -_Py_DECREF_NO_DEALLOC(PyObject *op) -{ - Py_DECREF(op); -} - static inline int _Py_REF_IS_MERGED(Py_ssize_t ob_ref_shared) { diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 3ee62f1728321d..6a0fc1a59e7965 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -27,6 +27,7 @@ typedef struct { uint8_t valid; uint8_t chain_depth; // Must be big enough for MAX_CHAIN_DEPTH - 1. bool warm; + uint8_t pending_deletion; int32_t index; // Index of ENTER_EXECUTOR (if code isn't NULL, below). _PyBloomFilter bloom; _PyExecutorLinkListNode links; diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 3d2b61d355a309..16288a447e20fe 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -3114,6 +3114,26 @@ def testfunc(n): self.assertNotIn("_POP_TOP_INT", uops) self.assertIn("_POP_TOP_NOP", uops) + def test_143026(self): + # https://github.com/python/cpython/issues/143026 + + result = script_helper.run_python_until_end('-c', textwrap.dedent(""" + import gc + thresholds = gc.get_threshold() + try: + gc.set_threshold(1) + + def f1(): + for i in range(5000): + globals()[''] = i + + f1() + finally: + gc.set_threshold(*thresholds) + """), PYTHON_JIT="1") + self.assertEqual(result[0].rc, 0, result) + + def global_identity(x): return x diff --git a/Python/optimizer.c b/Python/optimizer.c index 869889bf2598d8..0f8ddb4ba558d3 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -310,7 +310,7 @@ _Py_ClearExecutorDeletionList(PyInterpreterState *interp) while (ts) { _PyExecutorObject *current = (_PyExecutorObject *)ts->current_executor; if (current != NULL) { - _Py_DECREF_NO_DEALLOC((PyObject *)current); + Py_DECREF((PyObject *)current); } ts = ts->next; } @@ -320,6 +320,10 @@ _Py_ClearExecutorDeletionList(PyInterpreterState *interp) static void add_to_pending_deletion_list(_PyExecutorObject *self) { + if (self->vm_data.pending_deletion) { + return; + } + self->vm_data.pending_deletion = 1; PyInterpreterState *interp = PyInterpreterState_Get(); self->vm_data.links.previous = NULL; self->vm_data.links.next = interp->executor_deletion_list_head; @@ -627,7 +631,7 @@ _PyJit_translate_single_bytecode_to_trace( uint32_t target = 0; target = Py_IsNone((PyObject *)old_code) - ? (int)(target_instr - _Py_INTERPRETER_TRAMPOLINE_INSTRUCTIONS_PTR) + ? (uint32_t)(target_instr - _Py_INTERPRETER_TRAMPOLINE_INSTRUCTIONS_PTR) : INSTR_IP(target_instr, old_code); // Rewind EXTENDED_ARG so that we see the whole thing. @@ -1666,6 +1670,7 @@ void _Py_ExecutorInit(_PyExecutorObject *executor, const _PyBloomFilter *dependency_set) { executor->vm_data.valid = true; + executor->vm_data.pending_deletion = 0; for (int i = 0; i < _Py_BLOOM_FILTER_WORDS; i++) { executor->vm_data.bloom.bits[i] = dependency_set->bits[i]; } diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index 659befe312afaf..2001fb7c37931a 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -673,7 +673,6 @@ def has_error_without_pop(op: parser.CodeDef) -> bool: "_PyUnicode_Equal", "_PyUnicode_JoinArray", "_Py_CHECK_EMSCRIPTEN_SIGNALS_PERIODICALLY", - "_Py_DECREF_NO_DEALLOC", "_Py_ID", "_Py_IsImmortal", "_Py_IsOwnedByCurrentThread", From 450e836aefd5973fb1ecc130dafa9055d122838f Mon Sep 17 00:00:00 2001 From: Diego Russo Date: Tue, 23 Dec 2025 17:50:00 +0000 Subject: [PATCH 017/220] JIT: don't leak shim memory when shutting down the interpreter (#142984) --- Include/internal/pycore_jit.h | 1 + Python/jit.c | 23 +++++++++++++++++++++++ Python/pylifecycle.c | 8 ++++++++ 3 files changed, 32 insertions(+) diff --git a/Include/internal/pycore_jit.h b/Include/internal/pycore_jit.h index a7041ef8d4b000..b96ac879289673 100644 --- a/Include/internal/pycore_jit.h +++ b/Include/internal/pycore_jit.h @@ -25,6 +25,7 @@ typedef _Py_CODEUNIT *(*jit_func)( int _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction *trace, size_t length); void _PyJIT_Free(_PyExecutorObject *executor); +void _PyJIT_Fini(void); #endif // _Py_JIT diff --git a/Python/jit.c b/Python/jit.c index 4ce90edf73a8cb..5ca9313aadfb30 100644 --- a/Python/jit.c +++ b/Python/jit.c @@ -151,6 +151,8 @@ typedef struct { uintptr_t instruction_starts[UOP_MAX_TRACE_LENGTH]; } jit_state; +static size_t _Py_jit_shim_size = 0; + // Warning! AArch64 requires you to get your hands dirty. These are your gloves: // value[value_start : value_start + len] @@ -676,6 +678,7 @@ _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction trace[], siz * We compile this once only as it effectively a normal * function, but we need to use the JIT because it needs * to understand the jit-specific calling convention. + * Don't forget to call _PyJIT_Fini later! */ static _PyJitEntryFuncPtr compile_shim(void) @@ -717,6 +720,7 @@ compile_shim(void) jit_free(memory, total_size); return NULL; } + _Py_jit_shim_size = total_size; return (_PyJitEntryFuncPtr)memory; } @@ -739,6 +743,7 @@ _Py_LazyJitShim( return _Py_jit_entry(executor, frame, stack_pointer, tstate); } +// Free executor's memory allocated with _PyJIT_Compile void _PyJIT_Free(_PyExecutorObject *executor) { @@ -754,4 +759,22 @@ _PyJIT_Free(_PyExecutorObject *executor) } } +// Free shim memory allocated with compile_shim +void +_PyJIT_Fini(void) +{ + PyMutex_Lock(&lazy_jit_mutex); + unsigned char *memory = (unsigned char *)_Py_jit_entry; + size_t size = _Py_jit_shim_size; + if (size) { + _Py_jit_entry = _Py_LazyJitShim; + _Py_jit_shim_size = 0; + if (jit_free(memory, size)) { + PyErr_FormatUnraisable("Exception ignored while " + "freeing JIT entry code"); + } + } + PyMutex_Unlock(&lazy_jit_mutex); +} + #endif // _Py_JIT diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 2527dca71d774e..45b585faf9c980 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -35,6 +35,9 @@ #include "pycore_uniqueid.h" // _PyObject_FinalizeUniqueIdPool() #include "pycore_warnings.h" // _PyWarnings_InitState() #include "pycore_weakref.h" // _PyWeakref_GET_REF() +#ifdef _Py_JIT +#include "pycore_jit.h" // _PyJIT_Fini() +#endif #include "opcode.h" @@ -2267,6 +2270,7 @@ _Py_Finalize(_PyRuntimeState *runtime) /* Print debug stats if any */ _PyEval_Fini(); + /* Flush sys.stdout and sys.stderr (again, in case more was printed) */ if (flush_std_files() < 0) { status = -1; @@ -2346,6 +2350,10 @@ _Py_Finalize(_PyRuntimeState *runtime) finalize_interp_clear(tstate); +#ifdef _Py_JIT + /* Free JIT shim memory */ + _PyJIT_Fini(); +#endif #ifdef Py_TRACE_REFS /* Display addresses (& refcnts) of all objects still alive. From c2202a7e661d40b1837cc0109cdb9ab40ec4e486 Mon Sep 17 00:00:00 2001 From: AN Long Date: Wed, 24 Dec 2025 04:28:32 +0900 Subject: [PATCH 018/220] gh-109263: Start process from spawn context in multiprocessing no longer have side effect (GH-135813) --- Lib/multiprocessing/spawn.py | 2 +- Lib/test/_test_multiprocessing.py | 20 +++++++++++++++++++ ...-06-22-18-57-19.gh-issue-109263.f92V95.rst | 2 ++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2025-06-22-18-57-19.gh-issue-109263.f92V95.rst diff --git a/Lib/multiprocessing/spawn.py b/Lib/multiprocessing/spawn.py index daac1ecc34b55e..d43864c939cb63 100644 --- a/Lib/multiprocessing/spawn.py +++ b/Lib/multiprocessing/spawn.py @@ -184,7 +184,7 @@ def get_preparation_data(name): sys_argv=sys.argv, orig_dir=process.ORIGINAL_DIR, dir=os.getcwd(), - start_method=get_start_method(), + start_method=get_start_method(allow_none=True), ) # Figure out whether to initialise main in the subprocess as a module diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index d03eb1dfb253ec..c8c386101a0669 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -5967,6 +5967,26 @@ def test_context(self): self.assertRaises(ValueError, ctx.set_start_method, None) self.check_context(ctx) + @staticmethod + def _dummy_func(): + pass + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + def test_spawn_dont_set_context(self): + # Run a process with spawn or forkserver context may change + # the global start method, see gh-109263. + for method in ('fork', 'spawn', 'forkserver'): + multiprocessing.set_start_method(None, force=True) + + try: + ctx = multiprocessing.get_context(method) + except ValueError: + continue + process = ctx.Process(target=self._dummy_func) + process.start() + process.join() + self.assertIsNone(multiprocessing.get_start_method(allow_none=True)) + def test_context_check_module_types(self): try: ctx = multiprocessing.get_context('forkserver') diff --git a/Misc/NEWS.d/next/Library/2025-06-22-18-57-19.gh-issue-109263.f92V95.rst b/Misc/NEWS.d/next/Library/2025-06-22-18-57-19.gh-issue-109263.f92V95.rst new file mode 100644 index 00000000000000..6b96b5b9b2a0de --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-06-22-18-57-19.gh-issue-109263.f92V95.rst @@ -0,0 +1,2 @@ +Starting a process from spawn context in :mod:`multiprocessing` no longer +sets the start method globally. From cbe0cb779ae0b50c5b5653e9279ba451cc9e8557 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 23 Dec 2025 15:52:59 -0500 Subject: [PATCH 019/220] gh-143100: Add temporary suppression for set_swap_bodies (gh-143114) --- Tools/tsan/suppressions_free_threading.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Tools/tsan/suppressions_free_threading.txt b/Tools/tsan/suppressions_free_threading.txt index e8b1501c34bfc1..e2cf6d58b0cfd9 100644 --- a/Tools/tsan/suppressions_free_threading.txt +++ b/Tools/tsan/suppressions_free_threading.txt @@ -26,3 +26,6 @@ thread:pthread_create # PyObject_Realloc internally does memcpy which isn't atomic so can race # with non-locking reads. See #132070 race:PyObject_Realloc + +# gh-143100: set_swap_bodies in setobject.c calls memcpy, which isn't atomic +race:set_swap_bodies From cc48bf0fde8025d60a577a86bcb68cfd472e0c79 Mon Sep 17 00:00:00 2001 From: Hai Zhu <35182391+cocolato@users.noreply.github.com> Date: Wed, 24 Dec 2025 05:47:12 +0800 Subject: [PATCH 020/220] gh-134584: Eliminate redundant refcounting from `_BINARY_OP_SUBSCR_TUPLE_INT` (GH-143094) --- Include/internal/pycore_opcode_metadata.h | 2 +- Include/internal/pycore_uop_ids.h | 2 +- Include/internal/pycore_uop_metadata.h | 8 ++++---- Lib/test/test_capi/test_opt.py | 19 ++++++++++++++++++ Python/bytecodes.c | 9 +++++---- Python/executor_cases.c.h | 24 ++++++++--------------- Python/generated_cases.c.h | 21 +++++++++++++++----- Python/optimizer_bytecodes.c | 4 +++- Python/optimizer_cases.c.h | 10 ++++++++-- 9 files changed, 65 insertions(+), 34 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 351cf56355b7d0..08bddfbfbe6619 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1341,7 +1341,7 @@ _PyOpcode_macro_expansion[256] = { [BINARY_OP_SUBSCR_LIST_INT] = { .nuops = 5, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_LIST, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_LIST_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 }, { _POP_TOP, OPARG_SIMPLE, 5 } } }, [BINARY_OP_SUBSCR_LIST_SLICE] = { .nuops = 3, .uops = { { _GUARD_TOS_SLICE, OPARG_SIMPLE, 0 }, { _GUARD_NOS_LIST, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_LIST_SLICE, OPARG_SIMPLE, 5 } } }, [BINARY_OP_SUBSCR_STR_INT] = { .nuops = 5, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_UNICODE, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_STR_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 }, { _POP_TOP, OPARG_SIMPLE, 5 } } }, - [BINARY_OP_SUBSCR_TUPLE_INT] = { .nuops = 3, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_TUPLE, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_TUPLE_INT, OPARG_SIMPLE, 5 } } }, + [BINARY_OP_SUBSCR_TUPLE_INT] = { .nuops = 5, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_TUPLE, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_TUPLE_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 }, { _POP_TOP, OPARG_SIMPLE, 5 } } }, [BINARY_OP_SUBTRACT_FLOAT] = { .nuops = 5, .uops = { { _GUARD_TOS_FLOAT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_FLOAT, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBTRACT_FLOAT, OPARG_SIMPLE, 5 }, { _POP_TOP_FLOAT, OPARG_SIMPLE, 5 }, { _POP_TOP_FLOAT, OPARG_SIMPLE, 5 } } }, [BINARY_OP_SUBTRACT_INT] = { .nuops = 5, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_INT, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBTRACT_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 } } }, [BINARY_SLICE] = { .nuops = 1, .uops = { { _BINARY_SLICE, OPARG_SIMPLE, 0 } } }, diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index 204210ff101efe..69f1c3829cd239 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -387,7 +387,7 @@ extern "C" { #define _BINARY_OP_SUBSCR_LIST_INT_r23 580 #define _BINARY_OP_SUBSCR_LIST_SLICE_r21 581 #define _BINARY_OP_SUBSCR_STR_INT_r23 582 -#define _BINARY_OP_SUBSCR_TUPLE_INT_r21 583 +#define _BINARY_OP_SUBSCR_TUPLE_INT_r23 583 #define _BINARY_OP_SUBTRACT_FLOAT_r03 584 #define _BINARY_OP_SUBTRACT_FLOAT_r13 585 #define _BINARY_OP_SUBTRACT_FLOAT_r23 586 diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index f751f642b81ff8..0a49231e53f44a 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -122,7 +122,7 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = { [_BINARY_OP_SUBSCR_STR_INT] = HAS_DEOPT_FLAG, [_GUARD_NOS_TUPLE] = HAS_EXIT_FLAG, [_GUARD_TOS_TUPLE] = HAS_EXIT_FLAG, - [_BINARY_OP_SUBSCR_TUPLE_INT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, + [_BINARY_OP_SUBSCR_TUPLE_INT] = HAS_DEOPT_FLAG, [_GUARD_NOS_DICT] = HAS_EXIT_FLAG, [_GUARD_TOS_DICT] = HAS_EXIT_FLAG, [_BINARY_OP_SUBSCR_DICT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, @@ -1155,7 +1155,7 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { .entries = { { -1, -1, -1 }, { -1, -1, -1 }, - { 1, 2, _BINARY_OP_SUBSCR_TUPLE_INT_r21 }, + { 3, 2, _BINARY_OP_SUBSCR_TUPLE_INT_r23 }, { -1, -1, -1 }, }, }, @@ -3453,7 +3453,7 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = { [_GUARD_TOS_TUPLE_r11] = _GUARD_TOS_TUPLE, [_GUARD_TOS_TUPLE_r22] = _GUARD_TOS_TUPLE, [_GUARD_TOS_TUPLE_r33] = _GUARD_TOS_TUPLE, - [_BINARY_OP_SUBSCR_TUPLE_INT_r21] = _BINARY_OP_SUBSCR_TUPLE_INT, + [_BINARY_OP_SUBSCR_TUPLE_INT_r23] = _BINARY_OP_SUBSCR_TUPLE_INT, [_GUARD_NOS_DICT_r02] = _GUARD_NOS_DICT, [_GUARD_NOS_DICT_r12] = _GUARD_NOS_DICT, [_GUARD_NOS_DICT_r22] = _GUARD_NOS_DICT, @@ -3969,7 +3969,7 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_BINARY_OP_SUBSCR_STR_INT] = "_BINARY_OP_SUBSCR_STR_INT", [_BINARY_OP_SUBSCR_STR_INT_r23] = "_BINARY_OP_SUBSCR_STR_INT_r23", [_BINARY_OP_SUBSCR_TUPLE_INT] = "_BINARY_OP_SUBSCR_TUPLE_INT", - [_BINARY_OP_SUBSCR_TUPLE_INT_r21] = "_BINARY_OP_SUBSCR_TUPLE_INT_r21", + [_BINARY_OP_SUBSCR_TUPLE_INT_r23] = "_BINARY_OP_SUBSCR_TUPLE_INT_r23", [_BINARY_OP_SUBTRACT_FLOAT] = "_BINARY_OP_SUBTRACT_FLOAT", [_BINARY_OP_SUBTRACT_FLOAT_r03] = "_BINARY_OP_SUBTRACT_FLOAT_r03", [_BINARY_OP_SUBTRACT_FLOAT_r13] = "_BINARY_OP_SUBTRACT_FLOAT_r13", diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 16288a447e20fe..8f7314d579df6f 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -3114,6 +3114,25 @@ def testfunc(n): self.assertNotIn("_POP_TOP_INT", uops) self.assertIn("_POP_TOP_NOP", uops) + def test_binary_subscr_tuple_int(self): + def testfunc(n): + t = (1,) + x = 0 + for _ in range(n): + y = t[0] + x += y + return x + + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual(res, TIER2_THRESHOLD) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + + self.assertIn("_BINARY_OP_SUBSCR_TUPLE_INT", uops) + self.assertNotIn("_POP_TOP", uops) + self.assertNotIn("_POP_TOP_INT", uops) + self.assertIn("_POP_TOP_NOP", uops) + def test_143026(self): # https://github.com/python/cpython/issues/143026 diff --git a/Python/bytecodes.c b/Python/bytecodes.c index d21c17d072c3f9..950b9f08f2ead1 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -975,9 +975,9 @@ dummy_func( } macro(BINARY_OP_SUBSCR_TUPLE_INT) = - _GUARD_TOS_INT + _GUARD_NOS_TUPLE + unused/5 + _BINARY_OP_SUBSCR_TUPLE_INT; + _GUARD_TOS_INT + _GUARD_NOS_TUPLE + unused/5 + _BINARY_OP_SUBSCR_TUPLE_INT + _POP_TOP_INT + POP_TOP; - op(_BINARY_OP_SUBSCR_TUPLE_INT, (tuple_st, sub_st -- res)) { + op(_BINARY_OP_SUBSCR_TUPLE_INT, (tuple_st, sub_st -- res, ts, ss)) { PyObject *sub = PyStackRef_AsPyObjectBorrow(sub_st); PyObject *tuple = PyStackRef_AsPyObjectBorrow(tuple_st); @@ -991,9 +991,10 @@ dummy_func( STAT_INC(BINARY_OP, hit); PyObject *res_o = PyTuple_GET_ITEM(tuple, index); assert(res_o != NULL); - PyStackRef_CLOSE_SPECIALIZED(sub_st, _PyLong_ExactDealloc); res = PyStackRef_FromPyObjectNew(res_o); - DECREF_INPUTS(); + ts = tuple_st; + ss = sub_st; + INPUTS_DEAD(); } op(_GUARD_NOS_DICT, (nos, unused -- nos, unused)) { diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index f64747d6f27f2a..dc21a4aa90cf46 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -4888,12 +4888,14 @@ break; } - case _BINARY_OP_SUBSCR_TUPLE_INT_r21: { + case _BINARY_OP_SUBSCR_TUPLE_INT_r23: { CHECK_CURRENT_CACHED_VALUES(2); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); _PyStackRef sub_st; _PyStackRef tuple_st; _PyStackRef res; + _PyStackRef ts; + _PyStackRef ss; _PyStackRef _stack_item_0 = _tos_cache0; _PyStackRef _stack_item_1 = _tos_cache1; sub_st = _stack_item_1; @@ -4920,23 +4922,13 @@ STAT_INC(BINARY_OP, hit); PyObject *res_o = PyTuple_GET_ITEM(tuple, index); assert(res_o != NULL); - PyStackRef_CLOSE_SPECIALIZED(sub_st, _PyLong_ExactDealloc); res = PyStackRef_FromPyObjectNew(res_o); - stack_pointer[0] = tuple_st; - stack_pointer += 1; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); - _PyFrame_SetStackPointer(frame, stack_pointer); - _PyStackRef tmp = tuple_st; - tuple_st = res; - stack_pointer[-1] = tuple_st; - PyStackRef_CLOSE(tmp); - stack_pointer = _PyFrame_GetStackPointer(frame); + ts = tuple_st; + ss = sub_st; + _tos_cache2 = ss; + _tos_cache1 = ts; _tos_cache0 = res; - _tos_cache1 = PyStackRef_ZERO_BITS; - _tos_cache2 = PyStackRef_ZERO_BITS; - SET_CURRENT_CACHED_VALUES(1); - stack_pointer += -1; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + SET_CURRENT_CACHED_VALUES(3); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); break; } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index baf199969de94e..5f326d5c3caffc 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -1024,6 +1024,8 @@ _PyStackRef tuple_st; _PyStackRef sub_st; _PyStackRef res; + _PyStackRef ts; + _PyStackRef ss; // _GUARD_TOS_INT { value = stack_pointer[-1]; @@ -1067,15 +1069,24 @@ STAT_INC(BINARY_OP, hit); PyObject *res_o = PyTuple_GET_ITEM(tuple, index); assert(res_o != NULL); - PyStackRef_CLOSE_SPECIALIZED(sub_st, _PyLong_ExactDealloc); res = PyStackRef_FromPyObjectNew(res_o); + ts = tuple_st; + ss = sub_st; + } + // _POP_TOP_INT + { + value = ss; + assert(PyLong_CheckExact(PyStackRef_AsPyObjectBorrow(value))); + PyStackRef_CLOSE_SPECIALIZED(value, _PyLong_ExactDealloc); + } + // _POP_TOP + { + value = ts; + stack_pointer[-2] = res; stack_pointer += -1; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); - _PyStackRef tmp = tuple_st; - tuple_st = res; - stack_pointer[-1] = tuple_st; - PyStackRef_CLOSE(tmp); + PyStackRef_XCLOSE(value); stack_pointer = _PyFrame_GetStackPointer(frame); } DISPATCH(); diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index b40b597643dc94..c53a2fb7570c0d 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -335,7 +335,7 @@ dummy_func(void) { i = sub_st; } - op(_BINARY_OP_SUBSCR_TUPLE_INT, (tuple_st, sub_st -- res)) { + op(_BINARY_OP_SUBSCR_TUPLE_INT, (tuple_st, sub_st -- res, ts, ss)) { assert(sym_matches_type(tuple_st, &PyTuple_Type)); if (sym_is_const(ctx, sub_st)) { assert(PyLong_CheckExact(sym_get_const(ctx, sub_st))); @@ -354,6 +354,8 @@ dummy_func(void) { else { res = sym_new_not_null(ctx); } + ts = tuple_st; + ss = sub_st; } op(_TO_BOOL, (value -- res)) { diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index a17a5688847e07..9f1337e7ef3b92 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -836,6 +836,8 @@ JitOptRef sub_st; JitOptRef tuple_st; JitOptRef res; + JitOptRef ts; + JitOptRef ss; sub_st = stack_pointer[-1]; tuple_st = stack_pointer[-2]; assert(sym_matches_type(tuple_st, &PyTuple_Type)); @@ -855,9 +857,13 @@ else { res = sym_new_not_null(ctx); } - CHECK_STACK_BOUNDS(-1); + ts = tuple_st; + ss = sub_st; + CHECK_STACK_BOUNDS(1); stack_pointer[-2] = res; - stack_pointer += -1; + stack_pointer[-1] = ts; + stack_pointer[0] = ss; + stack_pointer += 1; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); break; } From 50ecd6b880c4ce6765fcee28d1a0ce715800a21c Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 23 Dec 2025 20:12:55 -0500 Subject: [PATCH 021/220] gh-143108: Don't instrument faulthandler.c for TSan (#143109) The dumping of tracebacks has data races and that's okay (it's best effort). --- Include/internal/pycore_interpframe.h | 4 ++-- Python/traceback.c | 12 ++++++------ Tools/tsan/suppressions_free_threading.txt | 6 ------ 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/Include/internal/pycore_interpframe.h b/Include/internal/pycore_interpframe.h index 8949d6cc2fc4bb..2e9fbd39b276b1 100644 --- a/Include/internal/pycore_interpframe.h +++ b/Include/internal/pycore_interpframe.h @@ -27,7 +27,7 @@ static inline PyCodeObject *_PyFrame_GetCode(_PyInterpreterFrame *f) { // Similar to _PyFrame_GetCode(), but return NULL if the frame is invalid or // freed. Used by dump_frame() in Python/traceback.c. The function uses // heuristics to detect freed memory, it's not 100% reliable. -static inline PyCodeObject* +static inline PyCodeObject* _Py_NO_SANITIZE_THREAD _PyFrame_SafeGetCode(_PyInterpreterFrame *f) { // globals and builtins may be NULL on a legit frame, but it's unlikely. @@ -70,7 +70,7 @@ _PyFrame_GetBytecode(_PyInterpreterFrame *f) // Similar to PyUnstable_InterpreterFrame_GetLasti(), but return NULL if the // frame is invalid or freed. Used by dump_frame() in Python/traceback.c. The // function uses heuristics to detect freed memory, it's not 100% reliable. -static inline int +static inline int _Py_NO_SANITIZE_THREAD _PyFrame_SafeGetLasti(struct _PyInterpreterFrame *f) { // Code based on _PyFrame_GetBytecode() but replace _PyFrame_GetCode() diff --git a/Python/traceback.c b/Python/traceback.c index 264f034dea7fa5..40e19c7cc82075 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1035,7 +1035,7 @@ _Py_DumpWideString(int fd, wchar_t *str) Return 0 on success. Return -1 if the frame is invalid. */ -static int +static int _Py_NO_SANITIZE_THREAD dump_frame(int fd, _PyInterpreterFrame *frame) { if (frame->owner == FRAME_OWNED_BY_INTERPRETER) { @@ -1088,7 +1088,7 @@ dump_frame(int fd, _PyInterpreterFrame *frame) return res; } -static int +static int _Py_NO_SANITIZE_THREAD tstate_is_freed(PyThreadState *tstate) { if (_PyMem_IsPtrFreed(tstate)) { @@ -1104,14 +1104,14 @@ tstate_is_freed(PyThreadState *tstate) } -static int +static int _Py_NO_SANITIZE_THREAD interp_is_freed(PyInterpreterState *interp) { return _PyMem_IsPtrFreed(interp); } -static void +static void _Py_NO_SANITIZE_THREAD dump_traceback(int fd, PyThreadState *tstate, int write_header) { if (write_header) { @@ -1263,7 +1263,7 @@ write_thread_id(int fd, PyThreadState *tstate, int is_current) The caller is responsible to call PyErr_CheckSignals() to call Python signal handlers if signals were received. */ -const char* +const char* _Py_NO_SANITIZE_THREAD _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp, PyThreadState *current_tstate) { @@ -1332,7 +1332,7 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp, } dump_traceback(fd, tstate, 0); - tstate = PyThreadState_Next(tstate); + tstate = tstate->next; nthreads++; } while (tstate != NULL); _Py_END_SUPPRESS_IPH diff --git a/Tools/tsan/suppressions_free_threading.txt b/Tools/tsan/suppressions_free_threading.txt index e2cf6d58b0cfd9..f05e0ded9865f8 100644 --- a/Tools/tsan/suppressions_free_threading.txt +++ b/Tools/tsan/suppressions_free_threading.txt @@ -12,13 +12,7 @@ # These warnings trigger directly in a CPython function. -race_top:dump_traceback -race_top:fatal_error -race_top:_PyFrame_GetCode -race_top:_PyFrame_Initialize race_top:_PyObject_TryGetInstanceAttribute -race_top:PyUnstable_InterpreterFrame_GetLine -race_top:write_thread_id # https://gist.github.com/mpage/6962e8870606cfc960e159b407a0cb40 thread:pthread_create From fc2f0fea6bdcaa8940adee52ff10aab71036b985 Mon Sep 17 00:00:00 2001 From: Diego Russo Date: Wed, 24 Dec 2025 09:44:16 +0000 Subject: [PATCH 022/220] JIT: Move executor to a register (#143072) --- Include/internal/pycore_jit.h | 2 +- Python/bytecodes.c | 2 +- Python/executor_cases.c.h | 2 +- Tools/jit/_stencils.py | 3 --- Tools/jit/jit.h | 2 +- Tools/jit/shim.c | 3 ++- Tools/jit/template.c | 10 +++++----- 7 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Include/internal/pycore_jit.h b/Include/internal/pycore_jit.h index b96ac879289673..89d5bb53643930 100644 --- a/Include/internal/pycore_jit.h +++ b/Include/internal/pycore_jit.h @@ -19,7 +19,7 @@ extern "C" { #ifdef _Py_JIT typedef _Py_CODEUNIT *(*jit_func)( - _PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate, + _PyExecutorObject *executor, _PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate, _PyStackRef _tos_cache0, _PyStackRef _tos_cache1, _PyStackRef _tos_cache2 ); diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 950b9f08f2ead1..9232a16551d8a2 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -5296,7 +5296,7 @@ dummy_func( assert(current_executor == (_PyExecutorObject*)executor); #endif assert(tstate->jit_exit == NULL || tstate->jit_exit->executor == current_executor); - tstate->current_executor = (PyObject *)executor; + tstate->current_executor = (PyObject *)current_executor; if (!current_executor->vm_data.valid) { assert(tstate->jit_exit->executor == current_executor); assert(tstate->current_executor == executor); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index dc21a4aa90cf46..aa98d4019dbdff 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -16992,7 +16992,7 @@ assert(current_executor == (_PyExecutorObject*)executor); #endif assert(tstate->jit_exit == NULL || tstate->jit_exit->executor == current_executor); - tstate->current_executor = (PyObject *)executor; + tstate->current_executor = (PyObject *)current_executor; if (!current_executor->vm_data.valid) { assert(tstate->jit_exit->executor == current_executor); assert(tstate->current_executor == executor); diff --git a/Tools/jit/_stencils.py b/Tools/jit/_stencils.py index 2b78d8013af5db..258de8ab3136a4 100644 --- a/Tools/jit/_stencils.py +++ b/Tools/jit/_stencils.py @@ -19,8 +19,6 @@ class HoleValue(enum.Enum): CODE = enum.auto() # The base address of the read-only data for this uop: DATA = enum.auto() - # The address of the current executor (exposed as _JIT_EXECUTOR): - EXECUTOR = enum.auto() # The base address of the "global" offset table located in the read-only data. # Shouldn't be present in the final stencils, since these are all replaced with # equivalent DATA values: @@ -108,7 +106,6 @@ class HoleValue(enum.Enum): _HOLE_EXPRS = { HoleValue.CODE: "(uintptr_t)code", HoleValue.DATA: "(uintptr_t)data", - HoleValue.EXECUTOR: "(uintptr_t)executor", HoleValue.GOT: "", # These should all have been turned into DATA values by process_relocations: HoleValue.OPARG: "instruction->oparg", diff --git a/Tools/jit/jit.h b/Tools/jit/jit.h index d5cf288c660f00..05e73ac6b39e8b 100644 --- a/Tools/jit/jit.h +++ b/Tools/jit/jit.h @@ -9,5 +9,5 @@ typedef jit_func __attribute__((preserve_none)) jit_func_preserve_none; #define DECLARE_TARGET(NAME) \ _Py_CODEUNIT *__attribute__((preserve_none, visibility("hidden"))) \ - NAME(_PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate, \ + NAME(_PyExecutorObject *executor, _PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate, \ _PyStackRef _tos_cache0, _PyStackRef _tos_cache1, _PyStackRef _tos_cache2); diff --git a/Tools/jit/shim.c b/Tools/jit/shim.c index 698e491aeb349b..8ec4885a48354f 100644 --- a/Tools/jit/shim.c +++ b/Tools/jit/shim.c @@ -12,5 +12,6 @@ _JIT_ENTRY( ) { // Note that this is *not* a tail call jit_func_preserve_none jitted = (jit_func_preserve_none)exec->jit_code; - return jitted(frame, stack_pointer, tstate, PyStackRef_ZERO_BITS, PyStackRef_ZERO_BITS, PyStackRef_ZERO_BITS); + return jitted(exec, frame, stack_pointer, tstate, + PyStackRef_ZERO_BITS, PyStackRef_ZERO_BITS, PyStackRef_ZERO_BITS); } diff --git a/Tools/jit/template.c b/Tools/jit/template.c index 064b401bc3aca4..3537c74a820365 100644 --- a/Tools/jit/template.c +++ b/Tools/jit/template.c @@ -76,7 +76,7 @@ do { \ OPT_STAT_INC(traces_executed); \ _PyExecutorObject *_executor = (EXECUTOR); \ jit_func_preserve_none jitted = _executor->jit_code; \ - __attribute__((musttail)) return jitted(frame, stack_pointer, tstate, \ + __attribute__((musttail)) return jitted(_executor, frame, stack_pointer, tstate, \ _tos_cache0, _tos_cache1, _tos_cache2); \ } while (0) @@ -100,7 +100,7 @@ do { \ #define PATCH_JUMP(ALIAS) \ do { \ DECLARE_TARGET(ALIAS); \ - __attribute__((musttail)) return ALIAS(frame, stack_pointer, tstate, \ + __attribute__((musttail)) return ALIAS(current_executor, frame, stack_pointer, tstate, \ _tos_cache0, _tos_cache1, _tos_cache2); \ } while (0) @@ -120,11 +120,11 @@ do { \ __attribute__((preserve_none)) _Py_CODEUNIT * _JIT_ENTRY( - _PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate, - _PyStackRef _tos_cache0, _PyStackRef _tos_cache1, _PyStackRef _tos_cache2 + _PyExecutorObject *executor, _PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate, + _PyStackRef _tos_cache0, _PyStackRef _tos_cache1, _PyStackRef _tos_cache2 ) { // Locals that the instruction implementations expect to exist: - PATCH_VALUE(_PyExecutorObject *, current_executor, _JIT_EXECUTOR) + _PyExecutorObject *current_executor = executor; int oparg; int uopcode = _JIT_OPCODE; _Py_CODEUNIT *next_instr; From 9af7a20caeb2912a05dd0fa07bbb4bfe7fb874e4 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Wed, 24 Dec 2025 12:38:17 +0000 Subject: [PATCH 023/220] gh-136186: Fix flaky tests in test_external_inspection (#143110) --- Lib/test/test_external_inspection.py | 233 ++++++++++++++++----------- 1 file changed, 135 insertions(+), 98 deletions(-) diff --git a/Lib/test/test_external_inspection.py b/Lib/test/test_external_inspection.py index 4c502cd1de7418..b1a3a8e65a9802 100644 --- a/Lib/test/test_external_inspection.py +++ b/Lib/test/test_external_inspection.py @@ -253,6 +253,31 @@ def get_all_awaited_by(pid): raise RuntimeError("Failed to get all awaited_by after retries") +def _get_stack_trace_with_retry(unwinder, timeout=SHORT_TIMEOUT, condition=None): + """Get stack trace from an existing unwinder with retry for transient errors. + + This handles the case where we want to reuse an existing RemoteUnwinder + instance but still handle transient failures like "Failed to parse initial + frame in chain" that can occur when sampling at an inopportune moment. + If condition is provided, keeps retrying until condition(traces) is True. + """ + last_error = None + for _ in busy_retry(timeout): + try: + traces = unwinder.get_stack_trace() + if condition is None or condition(traces): + return traces + # Condition not met yet, keep retrying + except TRANSIENT_ERRORS as e: + last_error = e + continue + if last_error: + raise RuntimeError( + f"Failed to get stack trace after retries: {last_error}" + ) + raise RuntimeError("Condition never satisfied within timeout") + + # ============================================================================ # Base test class with shared infrastructure # ============================================================================ @@ -1708,16 +1733,16 @@ def main_work(): # Get stack trace with all threads unwinder_all = RemoteUnwinder(p.pid, all_threads=True) - for _ in range(MAX_TRIES): - all_traces = unwinder_all.get_stack_trace() - found = self._find_frame_in_trace( - all_traces, - lambda f: f.funcname == "main_work" - and f.location.lineno > 12, - ) - if found: - break - time.sleep(RETRY_DELAY) + for _ in busy_retry(SHORT_TIMEOUT): + with contextlib.suppress(*TRANSIENT_ERRORS): + all_traces = unwinder_all.get_stack_trace() + found = self._find_frame_in_trace( + all_traces, + lambda f: f.funcname == "main_work" + and f.location.lineno > 12, + ) + if found: + break else: self.fail( "Main thread did not start its busy work on time" @@ -1727,7 +1752,7 @@ def main_work(): unwinder_gil = RemoteUnwinder( p.pid, only_active_thread=True ) - gil_traces = unwinder_gil.get_stack_trace() + gil_traces = _get_stack_trace_with_retry(unwinder_gil) # Count threads total_threads = sum( @@ -2002,15 +2027,15 @@ def busy(): mode=mode, skip_non_matching_threads=False, ) - for _ in range(MAX_TRIES): - traces = unwinder.get_stack_trace() - statuses = self._get_thread_statuses(traces) + for _ in busy_retry(SHORT_TIMEOUT): + with contextlib.suppress(*TRANSIENT_ERRORS): + traces = unwinder.get_stack_trace() + statuses = self._get_thread_statuses(traces) - if check_condition( - statuses, sleeper_tid, busy_tid - ): - break - time.sleep(0.5) + if check_condition( + statuses, sleeper_tid, busy_tid + ): + break return statuses, sleeper_tid, busy_tid finally: @@ -2154,29 +2179,29 @@ def busy_thread(): mode=PROFILING_MODE_ALL, skip_non_matching_threads=False, ) - for _ in range(MAX_TRIES): - traces = unwinder.get_stack_trace() - statuses = self._get_thread_statuses(traces) - - # Check ALL mode provides both GIL and CPU info - if ( - sleeper_tid in statuses - and busy_tid in statuses - and not ( - statuses[sleeper_tid] - & THREAD_STATUS_ON_CPU - ) - and not ( - statuses[sleeper_tid] - & THREAD_STATUS_HAS_GIL - ) - and (statuses[busy_tid] & THREAD_STATUS_ON_CPU) - and ( - statuses[busy_tid] & THREAD_STATUS_HAS_GIL - ) - ): - break - time.sleep(0.5) + for _ in busy_retry(SHORT_TIMEOUT): + with contextlib.suppress(*TRANSIENT_ERRORS): + traces = unwinder.get_stack_trace() + statuses = self._get_thread_statuses(traces) + + # Check ALL mode provides both GIL and CPU info + if ( + sleeper_tid in statuses + and busy_tid in statuses + and not ( + statuses[sleeper_tid] + & THREAD_STATUS_ON_CPU + ) + and not ( + statuses[sleeper_tid] + & THREAD_STATUS_HAS_GIL + ) + and (statuses[busy_tid] & THREAD_STATUS_ON_CPU) + and ( + statuses[busy_tid] & THREAD_STATUS_HAS_GIL + ) + ): + break self.assertIsNotNone( sleeper_tid, "Sleeper thread id not received" @@ -2300,18 +2325,18 @@ def test_thread_status_exception_detection(self): mode=PROFILING_MODE_ALL, skip_non_matching_threads=False, ) - for _ in range(MAX_TRIES): - traces = unwinder.get_stack_trace() - statuses = self._get_thread_statuses(traces) - - if ( - exception_tid in statuses - and normal_tid in statuses - and (statuses[exception_tid] & THREAD_STATUS_HAS_EXCEPTION) - and not (statuses[normal_tid] & THREAD_STATUS_HAS_EXCEPTION) - ): - break - time.sleep(0.5) + for _ in busy_retry(SHORT_TIMEOUT): + with contextlib.suppress(*TRANSIENT_ERRORS): + traces = unwinder.get_stack_trace() + statuses = self._get_thread_statuses(traces) + + if ( + exception_tid in statuses + and normal_tid in statuses + and (statuses[exception_tid] & THREAD_STATUS_HAS_EXCEPTION) + and not (statuses[normal_tid] & THREAD_STATUS_HAS_EXCEPTION) + ): + break self.assertIn(exception_tid, statuses) self.assertIn(normal_tid, statuses) @@ -2343,18 +2368,18 @@ def test_thread_status_exception_mode_filtering(self): mode=PROFILING_MODE_EXCEPTION, skip_non_matching_threads=True, ) - for _ in range(MAX_TRIES): - traces = unwinder.get_stack_trace() - statuses = self._get_thread_statuses(traces) - - if exception_tid in statuses: - self.assertNotIn( - normal_tid, - statuses, - "Normal thread should be filtered out in exception mode", - ) - return - time.sleep(0.5) + for _ in busy_retry(SHORT_TIMEOUT): + with contextlib.suppress(*TRANSIENT_ERRORS): + traces = unwinder.get_stack_trace() + statuses = self._get_thread_statuses(traces) + + if exception_tid in statuses: + self.assertNotIn( + normal_tid, + statuses, + "Normal thread should be filtered out in exception mode", + ) + return self.fail("Never found exception thread in exception mode") @@ -2497,8 +2522,23 @@ def _run_scenario_process(self, scenario): finally: _cleanup_sockets(client_socket, server_socket) - def _check_exception_status(self, p, thread_tid, expect_exception): - """Helper to check if thread has expected exception status.""" + def _check_thread_status( + self, p, thread_tid, condition, condition_name="condition" + ): + """Helper to check thread status with a custom condition. + + This waits until we see 3 consecutive samples where the condition + returns True, which confirms the thread has reached and is stable + in the expected state. Samples that don't match are ignored (the + thread may not have reached the expected state yet). + + Args: + p: Process object with pid attribute + thread_tid: Thread ID to check + condition: Callable(statuses, thread_tid) -> bool that returns + True when the thread is in the expected state + condition_name: Description of condition for error messages + """ unwinder = RemoteUnwinder( p.pid, all_threads=True, @@ -2506,40 +2546,37 @@ def _check_exception_status(self, p, thread_tid, expect_exception): skip_non_matching_threads=False, ) - # Collect multiple samples for reliability - results = [] - for _ in range(MAX_TRIES): - try: + # Wait for 3 consecutive samples matching expected state + matching_samples = 0 + for _ in busy_retry(SHORT_TIMEOUT): + with contextlib.suppress(*TRANSIENT_ERRORS): traces = unwinder.get_stack_trace() - except TRANSIENT_ERRORS: - time.sleep(RETRY_DELAY) - continue - statuses = self._get_thread_statuses(traces) - - if thread_tid in statuses: - has_exc = bool(statuses[thread_tid] & THREAD_STATUS_HAS_EXCEPTION) - results.append(has_exc) + statuses = self._get_thread_statuses(traces) - if len(results) >= 3: - break + if thread_tid in statuses: + if condition(statuses, thread_tid): + matching_samples += 1 + if matching_samples >= 3: + return # Success - confirmed stable in expected state + else: + # Thread not yet in expected state, reset counter + matching_samples = 0 - time.sleep(RETRY_DELAY) + self.fail( + f"Thread did not stabilize in expected state " + f"({condition_name}) within timeout" + ) - # Check majority of samples match expected - if not results: - self.fail("Never found target thread in stack traces") + def _check_exception_status(self, p, thread_tid, expect_exception): + """Helper to check if thread has expected exception status.""" + def condition(statuses, tid): + has_exc = bool(statuses[tid] & THREAD_STATUS_HAS_EXCEPTION) + return has_exc == expect_exception - majority = sum(results) > len(results) // 2 - if expect_exception: - self.assertTrue( - majority, - f"Thread should have HAS_EXCEPTION flag, got {results}" - ) - else: - self.assertFalse( - majority, - f"Thread should NOT have HAS_EXCEPTION flag, got {results}" - ) + self._check_thread_status( + p, thread_tid, condition, + condition_name=f"expect_exception={expect_exception}" + ) @unittest.skipIf( sys.platform not in ("linux", "darwin", "win32"), @@ -3445,7 +3482,7 @@ def test_get_stats(self): _wait_for_signal(client_socket, b"ready") # Take a sample - unwinder.get_stack_trace() + _get_stack_trace_with_retry(unwinder) stats = unwinder.get_stats() client_socket.sendall(b"done") From 57937a8e5e293f0dcba5115f7b7a11b1e0c9a273 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Wed, 24 Dec 2025 08:01:45 -0500 Subject: [PATCH 024/220] gh-142145: Avoid timing measurements in quadratic behavior test (gh-143105) Count the number of Element attribute accesses as a proxy for work done. With double the amount of work, a ratio of 2.0 indicates linear scaling and 4.0 quadratic scaling. Use 3.2 as an intermediate threshold. --- Lib/test/test_minidom.py | 48 +++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py index 69fae957ec7fc9..46249e5138aed5 100644 --- a/Lib/test/test_minidom.py +++ b/Lib/test/test_minidom.py @@ -2,7 +2,6 @@ import copy import pickle -import time import io from test import support import unittest @@ -178,23 +177,36 @@ def testAppendChild(self): def testAppendChildNoQuadraticComplexity(self): impl = getDOMImplementation() - newdoc = impl.createDocument(None, "some_tag", None) - top_element = newdoc.documentElement - children = [newdoc.createElement(f"child-{i}") for i in range(1, 2 ** 15 + 1)] - element = top_element - - start = time.monotonic() - for child in children: - element.appendChild(child) - element = child - end = time.monotonic() - - # This example used to take at least 30 seconds. - # Conservative assertion due to the wide variety of systems and - # build configs timing based tests wind up run under. - # A --with-address-sanitizer --with-pydebug build on a rpi5 still - # completes this loop in <0.5 seconds. - self.assertLess(end - start, 4) + def work(n): + doc = impl.createDocument(None, "some_tag", None) + element = doc.documentElement + total_calls = 0 + + # Count attribute accesses as a proxy for work done + def getattribute_counter(self, attr): + nonlocal total_calls + total_calls += 1 + return object.__getattribute__(self, attr) + + with support.swap_attr(Element, "__getattribute__", getattribute_counter): + for _ in range(n): + child = doc.createElement("child") + element.appendChild(child) + element = child + return total_calls + + # Doubling N should not ~quadruple the work. + w1 = work(1024) + w2 = work(2048) + w3 = work(4096) + + self.assertGreater(w1, 0) + r1 = w2 / w1 + r2 = w3 / w2 + self.assertLess( + max(r1, r2), 3.2, + msg=f"Possible quadratic behavior: work={w1,w2,w3} ratios={r1,r2}" + ) def testSetAttributeNodeWithoutOwnerDocument(self): # regression test for gh-142754 From 4ee6929d606fa7b976eba229de24219f0edac3d7 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Wed, 24 Dec 2025 08:02:02 -0500 Subject: [PATCH 025/220] gh-143121: Skip test that leak threads under TSan (gh-143125) --- Lib/test/_test_multiprocessing.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index c8c386101a0669..844539104e3a3e 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -3392,6 +3392,7 @@ class _TestMyManager(BaseTestCase): ALLOWED_TYPES = ('manager',) @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @support.skip_if_sanitizer('TSan: leaks threads', thread=True) def test_mymanager(self): manager = MyManager(shutdown_timeout=SHUTDOWN_TIMEOUT) manager.start() @@ -3404,6 +3405,7 @@ def test_mymanager(self): self.assertIn(manager._process.exitcode, (0, -signal.SIGTERM)) @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @support.skip_if_sanitizer('TSan: leaks threads', thread=True) def test_mymanager_context(self): manager = MyManager(shutdown_timeout=SHUTDOWN_TIMEOUT) with manager: @@ -3414,6 +3416,7 @@ def test_mymanager_context(self): self.assertIn(manager._process.exitcode, (0, -signal.SIGTERM)) @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @support.skip_if_sanitizer('TSan: leaks threads', thread=True) def test_mymanager_context_prestarted(self): manager = MyManager(shutdown_timeout=SHUTDOWN_TIMEOUT) manager.start() @@ -3485,6 +3488,7 @@ def _putter(cls, address, authkey): queue.put(tuple(cls.values)) @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @support.skip_if_sanitizer('TSan: leaks threads', thread=True) def test_remote(self): authkey = os.urandom(32) @@ -3527,6 +3531,7 @@ def _putter(cls, address, authkey): queue.put('hello world') @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @support.skip_if_sanitizer("TSan: leaks threads", thread=True) def test_rapid_restart(self): authkey = os.urandom(32) manager = QueueManager( From e8e044eda343b4b3dd7a7e532c88c2c97242000d Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Wed, 24 Dec 2025 08:02:19 -0500 Subject: [PATCH 026/220] gh-143100: Fix memcpy data race in setobject.c (gh-143127) --- Objects/setobject.c | 16 ++++++++++++++++ Tools/tsan/suppressions_free_threading.txt | 3 --- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Objects/setobject.c b/Objects/setobject.c index 5f868c858273ee..55e30fe2cdd8f7 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -1420,6 +1420,17 @@ set_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return make_new_set(type, NULL); } +#ifdef Py_GIL_DISABLED +static void +copy_small_table(setentry *dest, setentry *src) +{ + for (Py_ssize_t i = 0; i < PySet_MINSIZE; i++) { + _Py_atomic_store_ptr_release(&dest[i].key, src[i].key); + _Py_atomic_store_ssize_relaxed(&dest[i].hash, src[i].hash); + } +} +#endif + /* set_swap_bodies() switches the contents of any two sets by moving their internal data pointers and, if needed, copying the internal smalltables. Semantically equivalent to: @@ -1462,8 +1473,13 @@ set_swap_bodies(PySetObject *a, PySetObject *b) if (a_table == a->smalltable || b_table == b->smalltable) { memcpy(tab, a->smalltable, sizeof(tab)); +#ifndef Py_GIL_DISABLED memcpy(a->smalltable, b->smalltable, sizeof(tab)); memcpy(b->smalltable, tab, sizeof(tab)); +#else + copy_small_table(a->smalltable, b->smalltable); + copy_small_table(b->smalltable, tab); +#endif } if (PyType_IsSubtype(Py_TYPE(a), &PyFrozenSet_Type) && diff --git a/Tools/tsan/suppressions_free_threading.txt b/Tools/tsan/suppressions_free_threading.txt index f05e0ded9865f8..a3e1e54284f0ae 100644 --- a/Tools/tsan/suppressions_free_threading.txt +++ b/Tools/tsan/suppressions_free_threading.txt @@ -20,6 +20,3 @@ thread:pthread_create # PyObject_Realloc internally does memcpy which isn't atomic so can race # with non-locking reads. See #132070 race:PyObject_Realloc - -# gh-143100: set_swap_bodies in setobject.c calls memcpy, which isn't atomic -race:set_swap_bodies From d4dc3dd9aab6e860ea29e3bd133147a3f795cf60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20Kiss=20Koll=C3=A1r?= Date: Wed, 24 Dec 2025 13:46:33 +0000 Subject: [PATCH 027/220] gh-138122: Replace --interval with --sampling-rate (#143085) --- Doc/library/profiling.sampling.rst | 37 ++++--- Lib/profiling/sampling/_child_monitor.py | 4 +- Lib/profiling/sampling/_sync_coordinator.py | 8 +- Lib/profiling/sampling/cli.py | 98 ++++++++++++++----- Lib/profiling/sampling/constants.py | 4 + .../sampling/live_collector/__init__.py | 4 +- .../sampling/live_collector/collector.py | 14 +-- .../sampling/live_collector/constants.py | 2 +- .../sampling/live_collector/widgets.py | 8 +- Lib/profiling/sampling/pstats_collector.py | 5 +- .../test_sampling_profiler/test_advanced.py | 4 +- .../test_sampling_profiler/test_children.py | 26 ++--- .../test_sampling_profiler/test_cli.py | 6 +- .../test_live_collector_interaction.py | 26 ++--- .../test_sampling_profiler/test_modes.py | 8 +- 15 files changed, 154 insertions(+), 100 deletions(-) diff --git a/Doc/library/profiling.sampling.rst b/Doc/library/profiling.sampling.rst index b5e6a2c7a0ed8e..370bbcd3242526 100644 --- a/Doc/library/profiling.sampling.rst +++ b/Doc/library/profiling.sampling.rst @@ -53,7 +53,7 @@ counts**, not direct measurements. Tachyon counts how many times each function appears in the collected samples, then multiplies by the sampling interval to estimate time. -For example, with a 100 microsecond sampling interval over a 10-second profile, +For example, with a 10 kHz sampling rate over a 10-second profile, Tachyon collects approximately 100,000 samples. If a function appears in 5,000 samples (5% of total), Tachyon estimates it consumed 5% of the 10-second duration, or about 500 milliseconds. This is a statistical estimate, not a @@ -142,7 +142,7 @@ Use live mode for real-time monitoring (press ``q`` to quit):: Profile for 60 seconds with a faster sampling rate:: - python -m profiling.sampling run -d 60 -i 50 script.py + python -m profiling.sampling run -d 60 -r 20khz script.py Generate a line-by-line heatmap:: @@ -326,8 +326,8 @@ The default configuration works well for most use cases: * - Option - Default - * - Default for ``--interval`` / ``-i`` - - 100 µs between samples (~10,000 samples/sec) + * - Default for ``--sampling-rate`` / ``-r`` + - 1 kHz * - Default for ``--duration`` / ``-d`` - 10 seconds * - Default for ``--all-threads`` / ``-a`` @@ -346,23 +346,22 @@ The default configuration works well for most use cases: - Disabled (non-blocking sampling) -Sampling interval and duration ------------------------------- +Sampling rate and duration +-------------------------- -The two most fundamental parameters are the sampling interval and duration. +The two most fundamental parameters are the sampling rate and duration. Together, these determine how many samples will be collected during a profiling session. -The :option:`--interval` option (:option:`-i`) sets the time between samples in -microseconds. The default is 100 microseconds, which produces approximately -10,000 samples per second:: +The :option:`--sampling-rate` option (:option:`-r`) sets how frequently samples +are collected. The default is 1 kHz (10,000 samples per second):: - python -m profiling.sampling run -i 50 script.py + python -m profiling.sampling run -r 20khz script.py -Lower intervals capture more samples and provide finer-grained data at the -cost of slightly higher profiler CPU usage. Higher intervals reduce profiler +Higher rates capture more samples and provide finer-grained data at the +cost of slightly higher profiler CPU usage. Lower rates reduce profiler overhead but may miss short-lived functions. For most applications, the -default interval provides a good balance between accuracy and overhead. +default rate provides a good balance between accuracy and overhead. The :option:`--duration` option (:option:`-d`) sets how long to profile in seconds. The default is 10 seconds:: @@ -573,9 +572,9 @@ appended: - For pstats format (which defaults to stdout), subprocesses produce files like ``profile_12345.pstats`` -The subprocess profilers inherit most sampling options from the parent (interval, -duration, thread selection, native frames, GC frames, async-aware mode, and -output format). All Python descendant processes are profiled recursively, +The subprocess profilers inherit most sampling options from the parent (sampling +rate, duration, thread selection, native frames, GC frames, async-aware mode, +and output format). All Python descendant processes are profiled recursively, including grandchildren and further descendants. Subprocess detection works by periodically scanning for new descendants of @@ -1389,9 +1388,9 @@ Global options Sampling options ---------------- -.. option:: -i , --interval +.. option:: -r , --sampling-rate - Sampling interval in microseconds. Default: 100. + Sampling rate (for example, ``10000``, ``10khz``, ``10k``). Default: ``1khz``. .. option:: -d , --duration diff --git a/Lib/profiling/sampling/_child_monitor.py b/Lib/profiling/sampling/_child_monitor.py index e06c550d938b13..ec56f75719f9d1 100644 --- a/Lib/profiling/sampling/_child_monitor.py +++ b/Lib/profiling/sampling/_child_monitor.py @@ -16,7 +16,7 @@ _CHILD_POLL_INTERVAL_SEC = 0.1 # Default timeout for waiting on child profilers -_DEFAULT_WAIT_TIMEOUT = 30.0 +_DEFAULT_WAIT_TIMEOUT_SEC = 30.0 # Maximum number of child profilers to spawn (prevents resource exhaustion) _MAX_CHILD_PROFILERS = 100 @@ -138,7 +138,7 @@ def spawned_profilers(self): with self._lock: return list(self._spawned_profilers) - def wait_for_profilers(self, timeout=_DEFAULT_WAIT_TIMEOUT): + def wait_for_profilers(self, timeout=_DEFAULT_WAIT_TIMEOUT_SEC): """ Wait for all spawned child profilers to complete. diff --git a/Lib/profiling/sampling/_sync_coordinator.py b/Lib/profiling/sampling/_sync_coordinator.py index 1a4af42588a3f5..63d057043f0416 100644 --- a/Lib/profiling/sampling/_sync_coordinator.py +++ b/Lib/profiling/sampling/_sync_coordinator.py @@ -73,8 +73,8 @@ def _validate_arguments(args: List[str]) -> tuple[int, str, List[str]]: # Constants for socket communication _MAX_RETRIES = 3 -_INITIAL_RETRY_DELAY = 0.1 -_SOCKET_TIMEOUT = 2.0 +_INITIAL_RETRY_DELAY_SEC = 0.1 +_SOCKET_TIMEOUT_SEC = 2.0 _READY_MESSAGE = b"ready" @@ -93,14 +93,14 @@ def _signal_readiness(sync_port: int) -> None: for attempt in range(_MAX_RETRIES): try: # Use context manager for automatic cleanup - with socket.create_connection(("127.0.0.1", sync_port), timeout=_SOCKET_TIMEOUT) as sock: + with socket.create_connection(("127.0.0.1", sync_port), timeout=_SOCKET_TIMEOUT_SEC) as sock: sock.send(_READY_MESSAGE) return except (socket.error, OSError) as e: last_error = e if attempt < _MAX_RETRIES - 1: # Exponential backoff before retry - time.sleep(_INITIAL_RETRY_DELAY * (2 ** attempt)) + time.sleep(_INITIAL_RETRY_DELAY_SEC * (2 ** attempt)) # If we get here, all retries failed raise SyncError(f"Failed to signal readiness after {_MAX_RETRIES} attempts: {last_error}") from last_error diff --git a/Lib/profiling/sampling/cli.py b/Lib/profiling/sampling/cli.py index ccd6e954d79698..9e60961943a8d0 100644 --- a/Lib/profiling/sampling/cli.py +++ b/Lib/profiling/sampling/cli.py @@ -4,6 +4,7 @@ import importlib.util import locale import os +import re import selectors import socket import subprocess @@ -20,6 +21,7 @@ from .binary_collector import BinaryCollector from .binary_reader import BinaryReader from .constants import ( + MICROSECONDS_PER_SECOND, PROFILING_MODE_ALL, PROFILING_MODE_WALL, PROFILING_MODE_CPU, @@ -66,8 +68,8 @@ class CustomFormatter( # Constants for socket synchronization -_SYNC_TIMEOUT = 5.0 -_PROCESS_KILL_TIMEOUT = 2.0 +_SYNC_TIMEOUT_SEC = 5.0 +_PROCESS_KILL_TIMEOUT_SEC = 2.0 _READY_MESSAGE = b"ready" _RECV_BUFFER_SIZE = 1024 @@ -116,7 +118,8 @@ def _build_child_profiler_args(args): child_args = [] # Sampling options - child_args.extend(["-i", str(args.interval)]) + hz = MICROSECONDS_PER_SECOND // args.sample_interval_usec + child_args.extend(["-r", str(hz)]) child_args.extend(["-d", str(args.duration)]) if args.all_threads: @@ -239,7 +242,7 @@ def _run_with_sync(original_cmd, suppress_output=False): sync_sock.bind(("127.0.0.1", 0)) # Let OS choose a free port sync_port = sync_sock.getsockname()[1] sync_sock.listen(1) - sync_sock.settimeout(_SYNC_TIMEOUT) + sync_sock.settimeout(_SYNC_TIMEOUT_SEC) # Get current working directory to preserve it cwd = os.getcwd() @@ -268,7 +271,7 @@ def _run_with_sync(original_cmd, suppress_output=False): process = subprocess.Popen(cmd, **popen_kwargs) try: - _wait_for_ready_signal(sync_sock, process, _SYNC_TIMEOUT) + _wait_for_ready_signal(sync_sock, process, _SYNC_TIMEOUT_SEC) # Close stderr pipe if we were capturing it if process.stderr: @@ -279,7 +282,7 @@ def _run_with_sync(original_cmd, suppress_output=False): if process.poll() is None: process.terminate() try: - process.wait(timeout=_PROCESS_KILL_TIMEOUT) + process.wait(timeout=_PROCESS_KILL_TIMEOUT_SEC) except subprocess.TimeoutExpired: process.kill() process.wait() @@ -290,16 +293,64 @@ def _run_with_sync(original_cmd, suppress_output=False): return process +_RATE_PATTERN = re.compile(r''' + ^ # Start of string + ( # Group 1: The numeric value + \d+ # One or more digits (integer part) + (?:\.\d+)? # Optional: decimal point followed by digits + ) # Examples: "10", "0.5", "100.25" + ( # Group 2: Optional unit suffix + hz # "hz" - hertz + | khz # "khz" - kilohertz + | k # "k" - shorthand for kilohertz + )? # Suffix is optional (bare number = Hz) + $ # End of string + ''', re.VERBOSE | re.IGNORECASE) + + +def _parse_sampling_rate(rate_str: str) -> int: + """Parse sampling rate string to microseconds.""" + rate_str = rate_str.strip().lower() + + match = _RATE_PATTERN.match(rate_str) + if not match: + raise argparse.ArgumentTypeError( + f"Invalid sampling rate format: {rate_str}. " + "Expected: number followed by optional suffix (hz, khz, k) with no spaces (e.g., 10khz)" + ) + + number_part = match.group(1) + suffix = match.group(2) or '' + + # Determine multiplier based on suffix + suffix_map = { + 'hz': 1, + 'khz': 1000, + 'k': 1000, + } + multiplier = suffix_map.get(suffix, 1) + hz = float(number_part) * multiplier + if hz <= 0: + raise argparse.ArgumentTypeError(f"Sampling rate must be positive: {rate_str}") + + interval_usec = int(MICROSECONDS_PER_SECOND / hz) + if interval_usec < 1: + raise argparse.ArgumentTypeError(f"Sampling rate too high: {rate_str}") + + return interval_usec + + def _add_sampling_options(parser): """Add sampling configuration options to a parser.""" sampling_group = parser.add_argument_group("Sampling configuration") sampling_group.add_argument( - "-i", - "--interval", - type=int, - default=100, - metavar="MICROSECONDS", - help="sampling interval", + "-r", + "--sampling-rate", + type=_parse_sampling_rate, + default="1khz", + metavar="RATE", + dest="sample_interval_usec", + help="sampling rate (e.g., 10000, 10khz, 10k)", ) sampling_group.add_argument( "-d", @@ -487,14 +538,13 @@ def _sort_to_mode(sort_choice): } return sort_map.get(sort_choice, SORT_MODE_NSAMPLES) - -def _create_collector(format_type, interval, skip_idle, opcodes=False, +def _create_collector(format_type, sample_interval_usec, skip_idle, opcodes=False, output_file=None, compression='auto'): """Create the appropriate collector based on format type. Args: format_type: The output format ('pstats', 'collapsed', 'flamegraph', 'gecko', 'heatmap', 'binary') - interval: Sampling interval in microseconds + sample_interval_usec: Sampling interval in microseconds skip_idle: Whether to skip idle samples opcodes: Whether to collect opcode information (only used by gecko format for creating interval markers in Firefox Profiler) @@ -519,9 +569,9 @@ def _create_collector(format_type, interval, skip_idle, opcodes=False, # and is the only format that uses opcodes for interval markers if format_type == "gecko": skip_idle = False - return collector_class(interval, skip_idle=skip_idle, opcodes=opcodes) + return collector_class(sample_interval_usec, skip_idle=skip_idle, opcodes=opcodes) - return collector_class(interval, skip_idle=skip_idle) + return collector_class(sample_interval_usec, skip_idle=skip_idle) def _generate_output_filename(format_type, pid): @@ -725,8 +775,8 @@ def _main(): # Generate flamegraph from a script `python -m profiling.sampling run --flamegraph -o output.html script.py` - # Profile with custom interval and duration - `python -m profiling.sampling run -i 50 -d 30 script.py` + # Profile with custom rate and duration + `python -m profiling.sampling run -r 5khz -d 30 script.py` # Save collapsed stacks to file `python -m profiling.sampling run --collapsed -o stacks.txt script.py` @@ -860,7 +910,7 @@ def _handle_attach(args): # Create the appropriate collector collector = _create_collector( - args.format, args.interval, skip_idle, args.opcodes, + args.format, args.sample_interval_usec, skip_idle, args.opcodes, output_file=output_file, compression=getattr(args, 'compression', 'auto') ) @@ -938,7 +988,7 @@ def _handle_run(args): # Create the appropriate collector collector = _create_collector( - args.format, args.interval, skip_idle, args.opcodes, + args.format, args.sample_interval_usec, skip_idle, args.opcodes, output_file=output_file, compression=getattr(args, 'compression', 'auto') ) @@ -965,7 +1015,7 @@ def _handle_run(args): if process.poll() is None: process.terminate() try: - process.wait(timeout=_PROCESS_KILL_TIMEOUT) + process.wait(timeout=_PROCESS_KILL_TIMEOUT_SEC) except subprocess.TimeoutExpired: process.kill() process.wait() @@ -980,7 +1030,7 @@ def _handle_live_attach(args, pid): # Create live collector with default settings collector = LiveStatsCollector( - args.interval, + args.sample_interval_usec, skip_idle=skip_idle, sort_by="tottime", # Default initial sort limit=20, # Default limit @@ -1027,7 +1077,7 @@ def _handle_live_run(args): # Create live collector with default settings collector = LiveStatsCollector( - args.interval, + args.sample_interval_usec, skip_idle=skip_idle, sort_by="tottime", # Default initial sort limit=20, # Default limit diff --git a/Lib/profiling/sampling/constants.py b/Lib/profiling/sampling/constants.py index 34b85ba4b3c61d..366cbb38365c9f 100644 --- a/Lib/profiling/sampling/constants.py +++ b/Lib/profiling/sampling/constants.py @@ -1,5 +1,9 @@ """Constants for the sampling profiler.""" +# Time unit conversion constants +MICROSECONDS_PER_SECOND = 1_000_000 +MILLISECONDS_PER_SECOND = 1_000 + # Profiling mode constants PROFILING_MODE_WALL = 0 PROFILING_MODE_CPU = 1 diff --git a/Lib/profiling/sampling/live_collector/__init__.py b/Lib/profiling/sampling/live_collector/__init__.py index 175e4610d232c5..59d50955e52959 100644 --- a/Lib/profiling/sampling/live_collector/__init__.py +++ b/Lib/profiling/sampling/live_collector/__init__.py @@ -114,7 +114,7 @@ from .constants import ( MICROSECONDS_PER_SECOND, DISPLAY_UPDATE_HZ, - DISPLAY_UPDATE_INTERVAL, + DISPLAY_UPDATE_INTERVAL_SEC, MIN_TERMINAL_WIDTH, MIN_TERMINAL_HEIGHT, WIDTH_THRESHOLD_SAMPLE_PCT, @@ -165,7 +165,7 @@ # Constants "MICROSECONDS_PER_SECOND", "DISPLAY_UPDATE_HZ", - "DISPLAY_UPDATE_INTERVAL", + "DISPLAY_UPDATE_INTERVAL_SEC", "MIN_TERMINAL_WIDTH", "MIN_TERMINAL_HEIGHT", "WIDTH_THRESHOLD_SAMPLE_PCT", diff --git a/Lib/profiling/sampling/live_collector/collector.py b/Lib/profiling/sampling/live_collector/collector.py index dcb9fcabe32779..cdf95a77eeccd8 100644 --- a/Lib/profiling/sampling/live_collector/collector.py +++ b/Lib/profiling/sampling/live_collector/collector.py @@ -24,7 +24,7 @@ ) from .constants import ( MICROSECONDS_PER_SECOND, - DISPLAY_UPDATE_INTERVAL, + DISPLAY_UPDATE_INTERVAL_SEC, MIN_TERMINAL_WIDTH, MIN_TERMINAL_HEIGHT, HEADER_LINES, @@ -157,7 +157,7 @@ def __init__( self.max_sample_rate = 0 # Track maximum sample rate seen self.successful_samples = 0 # Track samples that captured frames self.failed_samples = 0 # Track samples that failed to capture frames - self.display_update_interval = DISPLAY_UPDATE_INTERVAL # Instance variable for display refresh rate + self.display_update_interval_sec = DISPLAY_UPDATE_INTERVAL_SEC # Instance variable for display refresh rate # Thread status statistics (bit flags) self.thread_status_counts = { @@ -410,7 +410,7 @@ def collect(self, stack_frames, timestamp_us=None): if ( self._last_display_update is None or (current_time - self._last_display_update) - >= self.display_update_interval + >= self.display_update_interval_sec ): self._update_display() self._last_display_update = current_time @@ -987,14 +987,14 @@ def _handle_input(self): elif ch == ord("+") or ch == ord("="): # Decrease update interval (faster refresh) - self.display_update_interval = max( - 0.05, self.display_update_interval - 0.05 + self.display_update_interval_sec = max( + 0.05, self.display_update_interval_sec - 0.05 ) # Min 20Hz elif ch == ord("-") or ch == ord("_"): # Increase update interval (slower refresh) - self.display_update_interval = min( - 1.0, self.display_update_interval + 0.05 + self.display_update_interval_sec = min( + 1.0, self.display_update_interval_sec + 0.05 ) # Max 1Hz elif ch == ord("c") or ch == ord("C"): diff --git a/Lib/profiling/sampling/live_collector/constants.py b/Lib/profiling/sampling/live_collector/constants.py index 8462c0de3fd680..4f4575f7b7aae2 100644 --- a/Lib/profiling/sampling/live_collector/constants.py +++ b/Lib/profiling/sampling/live_collector/constants.py @@ -5,7 +5,7 @@ # Display update constants DISPLAY_UPDATE_HZ = 10 -DISPLAY_UPDATE_INTERVAL = 1.0 / DISPLAY_UPDATE_HZ # 0.1 seconds +DISPLAY_UPDATE_INTERVAL_SEC = 1.0 / DISPLAY_UPDATE_HZ # 0.1 seconds # Terminal size constraints MIN_TERMINAL_WIDTH = 60 diff --git a/Lib/profiling/sampling/live_collector/widgets.py b/Lib/profiling/sampling/live_collector/widgets.py index 314f3796a093ad..cf04f3aa3254ef 100644 --- a/Lib/profiling/sampling/live_collector/widgets.py +++ b/Lib/profiling/sampling/live_collector/widgets.py @@ -13,7 +13,7 @@ WIDTH_THRESHOLD_CUMUL_PCT, WIDTH_THRESHOLD_CUMTIME, MICROSECONDS_PER_SECOND, - DISPLAY_UPDATE_INTERVAL, + DISPLAY_UPDATE_INTERVAL_SEC, MIN_BAR_WIDTH, MAX_SAMPLE_RATE_BAR_WIDTH, MAX_EFFICIENCY_BAR_WIDTH, @@ -181,7 +181,7 @@ def draw_header_info(self, line, width, elapsed): # Calculate display refresh rate refresh_hz = ( - 1.0 / self.collector.display_update_interval if self.collector.display_update_interval > 0 else 0 + 1.0 / self.collector.display_update_interval_sec if self.collector.display_update_interval_sec > 0 else 0 ) # Get current view mode and thread display @@ -235,8 +235,8 @@ def draw_header_info(self, line, width, elapsed): def format_rate_with_units(self, rate_hz): """Format a rate in Hz with appropriate units (Hz, KHz, MHz).""" - if rate_hz >= 1_000_000: - return f"{rate_hz / 1_000_000:.1f}MHz" + if rate_hz >= MICROSECONDS_PER_SECOND: + return f"{rate_hz / MICROSECONDS_PER_SECOND:.1f}MHz" elif rate_hz >= 1_000: return f"{rate_hz / 1_000:.1f}KHz" else: diff --git a/Lib/profiling/sampling/pstats_collector.py b/Lib/profiling/sampling/pstats_collector.py index 1b2fe6a77278ee..e0dc9ab6bb7edb 100644 --- a/Lib/profiling/sampling/pstats_collector.py +++ b/Lib/profiling/sampling/pstats_collector.py @@ -3,6 +3,7 @@ from _colorize import ANSIColors from .collector import Collector, extract_lineno +from .constants import MICROSECONDS_PER_SECOND class PstatsCollector(Collector): @@ -68,7 +69,7 @@ def _dump_stats(self, file): # Needed for compatibility with pstats.Stats def create_stats(self): - sample_interval_sec = self.sample_interval_usec / 1_000_000 + sample_interval_sec = self.sample_interval_usec / MICROSECONDS_PER_SECOND callers = {} for fname, call_counts in self.result.items(): total = call_counts["direct_calls"] * sample_interval_sec @@ -263,7 +264,7 @@ def _determine_best_unit(max_value): elif max_value >= 0.001: return "ms", 1000.0 else: - return "μs", 1000000.0 + return "μs", float(MICROSECONDS_PER_SECOND) def _print_summary(self, stats_list, total_samples): """Print summary of interesting functions.""" diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_advanced.py b/Lib/test/test_profiling/test_sampling_profiler/test_advanced.py index ef9ea64b67af61..bcd4de7f5d7ebe 100644 --- a/Lib/test/test_profiling/test_sampling_profiler/test_advanced.py +++ b/Lib/test/test_profiling/test_sampling_profiler/test_advanced.py @@ -219,8 +219,8 @@ def worker(x): "run", "-d", "5", - "-i", - "100000", + "-r", + "10", script, stdout=subprocess.PIPE, stderr=subprocess.PIPE, diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_children.py b/Lib/test/test_profiling/test_sampling_profiler/test_children.py index 4007b3e8d7a41f..b7dc878a238f8d 100644 --- a/Lib/test/test_profiling/test_sampling_profiler/test_children.py +++ b/Lib/test/test_profiling/test_sampling_profiler/test_children.py @@ -279,11 +279,11 @@ def test_monitor_creation(self): monitor = ChildProcessMonitor( pid=os.getpid(), - cli_args=["-i", "100", "-d", "5"], + cli_args=["-r", "10khz", "-d", "5"], output_pattern="test_{pid}.pstats", ) self.assertEqual(monitor.parent_pid, os.getpid()) - self.assertEqual(monitor.cli_args, ["-i", "100", "-d", "5"]) + self.assertEqual(monitor.cli_args, ["-r", "10khz", "-d", "5"]) self.assertEqual(monitor.output_pattern, "test_{pid}.pstats") def test_monitor_lifecycle(self): @@ -386,7 +386,7 @@ def test_build_child_profiler_args(self): from profiling.sampling.cli import _build_child_profiler_args args = argparse.Namespace( - interval=200, + sample_interval_usec=200, duration=15, all_threads=True, realtime_stats=False, @@ -420,7 +420,7 @@ def assert_flag_value_pair(flag, value): f"'{child_args[flag_index + 1]}' in args: {child_args}", ) - assert_flag_value_pair("-i", 200) + assert_flag_value_pair("-r", 5000) assert_flag_value_pair("-d", 15) assert_flag_value_pair("--mode", "cpu") @@ -444,7 +444,7 @@ def test_build_child_profiler_args_no_gc(self): from profiling.sampling.cli import _build_child_profiler_args args = argparse.Namespace( - interval=100, + sample_interval_usec=100, duration=5, all_threads=False, realtime_stats=False, @@ -510,7 +510,7 @@ def test_setup_child_monitor(self): from profiling.sampling.cli import _setup_child_monitor args = argparse.Namespace( - interval=100, + sample_interval_usec=100, duration=5, all_threads=False, realtime_stats=False, @@ -690,7 +690,7 @@ def test_monitor_respects_max_limit(self): # Create a monitor monitor = ChildProcessMonitor( pid=os.getpid(), - cli_args=["-i", "100", "-d", "5"], + cli_args=["-r", "10khz", "-d", "5"], output_pattern="test_{pid}.pstats", ) @@ -927,8 +927,8 @@ def test_subprocesses_flag_spawns_child_and_creates_output(self): "--subprocesses", "-d", "3", - "-i", - "10000", + "-r", + "100", "-o", output_file, script_file, @@ -989,8 +989,8 @@ def test_subprocesses_flag_with_flamegraph_output(self): "--subprocesses", "-d", "2", - "-i", - "10000", + "-r", + "100", "--flamegraph", "-o", output_file, @@ -1043,8 +1043,8 @@ def test_subprocesses_flag_no_crash_on_quick_child(self): "--subprocesses", "-d", "2", - "-i", - "10000", + "-r", + "100", "-o", output_file, script_file, diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_cli.py b/Lib/test/test_profiling/test_sampling_profiler/test_cli.py index 9b2b16d6e1965b..fb4816a0b6085a 100644 --- a/Lib/test/test_profiling/test_sampling_profiler/test_cli.py +++ b/Lib/test/test_profiling/test_sampling_profiler/test_cli.py @@ -232,7 +232,7 @@ def test_cli_module_with_profiler_options(self): test_args = [ "profiling.sampling.cli", "run", - "-i", + "-r", "1000", "-d", "30", @@ -265,8 +265,8 @@ def test_cli_script_with_profiler_options(self): test_args = [ "profiling.sampling.cli", "run", - "-i", - "2000", + "-r", + "500", "-d", "60", "--collapsed", diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_interaction.py b/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_interaction.py index a5870366552854..38f1d03e4939f1 100644 --- a/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_interaction.py +++ b/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_interaction.py @@ -35,7 +35,7 @@ def setUp(self): ) self.collector.start_time = time.perf_counter() # Set a consistent display update interval for tests - self.collector.display_update_interval = 0.1 + self.collector.display_update_interval_sec = 0.1 def tearDown(self): """Clean up after test.""" @@ -110,45 +110,45 @@ def test_reset_stats(self): def test_increase_refresh_rate(self): """Test increasing refresh rate (faster updates).""" - initial_interval = self.collector.display_update_interval + initial_interval = self.collector.display_update_interval_sec # Simulate '+' key press (faster = smaller interval) self.display.simulate_input(ord("+")) self.collector._handle_input() - self.assertLess(self.collector.display_update_interval, initial_interval) + self.assertLess(self.collector.display_update_interval_sec, initial_interval) def test_decrease_refresh_rate(self): """Test decreasing refresh rate (slower updates).""" - initial_interval = self.collector.display_update_interval + initial_interval = self.collector.display_update_interval_sec # Simulate '-' key press (slower = larger interval) self.display.simulate_input(ord("-")) self.collector._handle_input() - self.assertGreater(self.collector.display_update_interval, initial_interval) + self.assertGreater(self.collector.display_update_interval_sec, initial_interval) def test_refresh_rate_minimum(self): """Test that refresh rate has a minimum (max speed).""" - self.collector.display_update_interval = 0.05 # Set to minimum + self.collector.display_update_interval_sec = 0.05 # Set to minimum # Try to go faster self.display.simulate_input(ord("+")) self.collector._handle_input() # Should stay at minimum - self.assertEqual(self.collector.display_update_interval, 0.05) + self.assertEqual(self.collector.display_update_interval_sec, 0.05) def test_refresh_rate_maximum(self): """Test that refresh rate has a maximum (min speed).""" - self.collector.display_update_interval = 1.0 # Set to maximum + self.collector.display_update_interval_sec = 1.0 # Set to maximum # Try to go slower self.display.simulate_input(ord("-")) self.collector._handle_input() # Should stay at maximum - self.assertEqual(self.collector.display_update_interval, 1.0) + self.assertEqual(self.collector.display_update_interval_sec, 1.0) def test_help_toggle(self): """Test help screen toggle.""" @@ -289,23 +289,23 @@ def test_filter_clear_uppercase(self): def test_increase_refresh_rate_with_equals(self): """Test increasing refresh rate with '=' key.""" - initial_interval = self.collector.display_update_interval + initial_interval = self.collector.display_update_interval_sec # Simulate '=' key press (alternative to '+') self.display.simulate_input(ord("=")) self.collector._handle_input() - self.assertLess(self.collector.display_update_interval, initial_interval) + self.assertLess(self.collector.display_update_interval_sec, initial_interval) def test_decrease_refresh_rate_with_underscore(self): """Test decreasing refresh rate with '_' key.""" - initial_interval = self.collector.display_update_interval + initial_interval = self.collector.display_update_interval_sec # Simulate '_' key press (alternative to '-') self.display.simulate_input(ord("_")) self.collector._handle_input() - self.assertGreater(self.collector.display_update_interval, initial_interval) + self.assertGreater(self.collector.display_update_interval_sec, initial_interval) def test_finished_state_displays_banner(self): """Test that finished state shows prominent banner.""" diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_modes.py b/Lib/test/test_profiling/test_sampling_profiler/test_modes.py index 247416389daa07..877237866b1e65 100644 --- a/Lib/test/test_profiling/test_sampling_profiler/test_modes.py +++ b/Lib/test/test_profiling/test_sampling_profiler/test_modes.py @@ -306,8 +306,8 @@ def test_gil_mode_cli_argument_parsing(self): "12345", "--mode", "gil", - "-i", - "500", + "-r", + "2000", "-d", "5", ] @@ -488,8 +488,8 @@ def test_exception_mode_cli_argument_parsing(self): "12345", "--mode", "exception", - "-i", - "500", + "-r", + "2000", "-d", "5", ] From 1e17ccd030a2285ad53db5952360fffa33a8a877 Mon Sep 17 00:00:00 2001 From: "R. David Murray" Date: Wed, 24 Dec 2025 09:14:39 -0500 Subject: [PATCH 028/220] Correctly fold unknown-8bit originating from encoded words. (#142517) The unknown-8bit trick was designed to deal with unknown bytes in an ASCII message, and it works fine for that. However, I also tried to extend it to handle bytes that can't be decoded using the charset specified in an encoded word, and there it fails because there can be other non-ASCII characters that were *successfully* decoded. The fix is simple: do the unknown-8bit encoding using the utf-8 codec. This is especially appropriate since anyone trying to do recovery on an unknown byte string will probably attempt utf-8 first. --- Lib/email/_encoded_words.py | 2 +- Lib/test/test_email/test__header_value_parser.py | 8 ++++++++ .../2025-12-10-10-00-06.gh-issue-142517.fG4hbe.rst | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2025-12-10-10-00-06.gh-issue-142517.fG4hbe.rst diff --git a/Lib/email/_encoded_words.py b/Lib/email/_encoded_words.py index 6795a606de037e..05a34a4c105233 100644 --- a/Lib/email/_encoded_words.py +++ b/Lib/email/_encoded_words.py @@ -219,7 +219,7 @@ def encode(string, charset='utf-8', encoding=None, lang=''): """ if charset == 'unknown-8bit': - bstring = string.encode('ascii', 'surrogateescape') + bstring = string.encode('utf-8', 'surrogateescape') else: bstring = string.encode(charset) if encoding is None: diff --git a/Lib/test/test_email/test__header_value_parser.py b/Lib/test/test_email/test__header_value_parser.py index f33844910beee4..426ec4644e3096 100644 --- a/Lib/test/test_email/test__header_value_parser.py +++ b/Lib/test/test_email/test__header_value_parser.py @@ -3340,5 +3340,13 @@ def test_fold_unfoldable_element_stealing_whitespace(self): token = parser.get_address_list(text)[0] 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?=' + )[0], + ' =?unknown-8bit?b?5a6i5oi25q2j6KaP5Lqk5w==?=\n', + ) + + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2025-12-10-10-00-06.gh-issue-142517.fG4hbe.rst b/Misc/NEWS.d/next/Library/2025-12-10-10-00-06.gh-issue-142517.fG4hbe.rst new file mode 100644 index 00000000000000..388fff0e2acb96 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-10-10-00-06.gh-issue-142517.fG4hbe.rst @@ -0,0 +1,4 @@ +The non-``compat32`` :mod:`email` policies now correctly handle refolding +encoded words that contain bytes that can not be decoded in their specified +character set. Previously this resulting in an encoding exception during +folding. From 7c44f37170cf87a898a8b3ff008c845b8e780c3d Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Wed, 24 Dec 2025 16:15:11 +0000 Subject: [PATCH 029/220] gh-138122: Extend binary profiling format with full source location and opcode (#143088) Co-authored-by: Stan Ulbrych --- InternalDocs/profiling_binary_format.md | 86 ++++++-- Lib/profiling/sampling/cli.py | 2 +- .../test_binary_format.py | 184 ++++++++++++++---- Modules/_remote_debugging/binary_io.h | 24 ++- Modules/_remote_debugging/binary_io_reader.c | 99 +++++++--- Modules/_remote_debugging/binary_io_writer.c | 120 ++++++++++-- 6 files changed, 411 insertions(+), 104 deletions(-) diff --git a/InternalDocs/profiling_binary_format.md b/InternalDocs/profiling_binary_format.md index b3ebdfd22edf8c..7e4592a0d89705 100644 --- a/InternalDocs/profiling_binary_format.md +++ b/InternalDocs/profiling_binary_format.md @@ -272,33 +272,85 @@ byte. ## Frame Table -The frame table stores deduplicated frame entries: +The frame table stores deduplicated frame entries with full source position +information and bytecode opcode: ``` -+----------------------+ -| filename_idx: varint | -| funcname_idx: varint | -| lineno: svarint | -+----------------------+ (repeated for each frame) ++----------------------------+ +| filename_idx: varint | +| funcname_idx: varint | +| lineno: svarint | +| end_lineno_delta: svarint | +| column: svarint | +| end_column_delta: svarint | +| opcode: u8 | ++----------------------------+ (repeated for each frame) ``` -Each unique (filename, funcname, lineno) combination gets one entry. Two -calls to the same function at different line numbers produce different -frame entries; two calls at the same line number share one entry. +### Field Definitions + +| Field | Type | Description | +|------------------|---------------|----------------------------------------------------------| +| filename_idx | varint | Index into string table for file name | +| funcname_idx | varint | Index into string table for function name | +| lineno | zigzag varint | Start line number (-1 for synthetic frames) | +| end_lineno_delta | zigzag varint | Delta from lineno (end_lineno = lineno + delta) | +| column | zigzag varint | Start column offset in UTF-8 bytes (-1 if not available) | +| end_column_delta | zigzag varint | Delta from column (end_column = column + delta) | +| opcode | u8 | Python bytecode opcode (0-254) or 255 for None | + +### Delta Encoding + +Position end values use delta encoding for efficiency: + +- `end_lineno = lineno + end_lineno_delta` +- `end_column = column + end_column_delta` + +Typical values: +- `end_lineno_delta`: Usually 0 (single-line expressions) → encodes to 1 byte +- `end_column_delta`: Usually 5-20 (expression width) → encodes to 1 byte + +This saves ~1-2 bytes per frame compared to absolute encoding. When the base +value (lineno or column) is -1 (not available), the delta is stored as 0 and +the reconstructed value is -1. + +### Sentinel Values + +- `opcode = 255`: No opcode captured +- `lineno = -1`: Synthetic frame (no source location) +- `column = -1`: Column offset not available + +### Deduplication + +Each unique (filename, funcname, lineno, end_lineno, column, end_column, +opcode) combination gets one entry. This enables instruction-level profiling +where multiple bytecode instructions on the same line can be distinguished. Strings and frames are deduplicated separately because they have different cardinalities and reference patterns. A codebase might have hundreds of unique source files but thousands of unique functions. Many functions share the same filename, so storing the filename index in each frame entry (rather than the full string) provides an additional layer of deduplication. A frame -entry is just three varints (typically 3-6 bytes) rather than two full -strings plus a line number. - -Line numbers use signed varint (zigzag encoding) rather than unsigned to -handle edge cases. Synthetic frames—generated frames that don't correspond -directly to Python source code, such as C extension boundaries or internal -interpreter frames—use line number 0 or -1 to indicate the absence of a -source location. Zigzag encoding ensures these small negative values encode +entry is typically 7-9 bytes rather than two full strings plus location data. + +### Size Analysis + +Typical frame size with delta encoding: +- file_idx: 1-2 bytes +- func_idx: 1-2 bytes +- lineno: 1-2 bytes +- end_lineno_delta: 1 byte (usually 0) +- column: 1 byte (usually < 64) +- end_column_delta: 1 byte (usually < 64) +- opcode: 1 byte + +**Total: ~7-9 bytes per frame** + +Line numbers and columns use signed varint (zigzag encoding) to handle +sentinel values efficiently. Synthetic frames—generated frames that don't +correspond directly to Python source code, such as C extension boundaries or +internal interpreter frames—use -1 to indicate the absence of a source +location. Zigzag encoding ensures these small negative values encode efficiently (−1 becomes 1, which is one byte) rather than requiring the maximum varint length. diff --git a/Lib/profiling/sampling/cli.py b/Lib/profiling/sampling/cli.py index 9e60961943a8d0..10341c1570ceca 100644 --- a/Lib/profiling/sampling/cli.py +++ b/Lib/profiling/sampling/cli.py @@ -715,7 +715,7 @@ def _validate_args(args, parser): ) # Validate --opcodes is only used with compatible formats - opcodes_compatible_formats = ("live", "gecko", "flamegraph", "heatmap") + opcodes_compatible_formats = ("live", "gecko", "flamegraph", "heatmap", "binary") if getattr(args, 'opcodes', False) and args.format not in opcodes_compatible_formats: parser.error( f"--opcodes is only compatible with {', '.join('--' + f for f in opcodes_compatible_formats)}." diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_binary_format.py b/Lib/test/test_profiling/test_sampling_profiler/test_binary_format.py index 2bc005901e321f..033a533fe5444e 100644 --- a/Lib/test/test_profiling/test_sampling_profiler/test_binary_format.py +++ b/Lib/test/test_profiling/test_sampling_profiler/test_binary_format.py @@ -29,10 +29,17 @@ ) -def make_frame(filename, lineno, funcname): - """Create a FrameInfo struct sequence.""" - location = LocationInfo((lineno, lineno, -1, -1)) - return FrameInfo((filename, location, funcname, None)) +def make_frame(filename, lineno, funcname, end_lineno=None, column=None, + end_column=None, opcode=None): + """Create a FrameInfo struct sequence with full location info and opcode.""" + if end_lineno is None: + end_lineno = lineno + if column is None: + column = 0 + if end_column is None: + end_column = 0 + location = LocationInfo((lineno, end_lineno, column, end_column)) + return FrameInfo((filename, location, funcname, opcode)) def make_thread(thread_id, frames, status=0): @@ -54,6 +61,36 @@ def extract_lineno(location): return location +def extract_location(location): + """Extract full location info as dict from location tuple or None.""" + if location is None: + return {"lineno": 0, "end_lineno": 0, "column": 0, "end_column": 0} + if isinstance(location, tuple) and len(location) >= 4: + return { + "lineno": location[0] if location[0] is not None else 0, + "end_lineno": location[1] if location[1] is not None else 0, + "column": location[2] if location[2] is not None else 0, + "end_column": location[3] if location[3] is not None else 0, + } + # Fallback for old-style location + lineno = location[0] if isinstance(location, tuple) else location + return {"lineno": lineno or 0, "end_lineno": lineno or 0, "column": 0, "end_column": 0} + + +def frame_to_dict(frame): + """Convert a FrameInfo to a dict.""" + loc = extract_location(frame.location) + return { + "filename": frame.filename, + "funcname": frame.funcname, + "lineno": loc["lineno"], + "end_lineno": loc["end_lineno"], + "column": loc["column"], + "end_column": loc["end_column"], + "opcode": frame.opcode, + } + + class RawCollector: """Collector that captures all raw data grouped by thread.""" @@ -68,15 +105,7 @@ def collect(self, stack_frames, timestamps_us): count = len(timestamps_us) for interp in stack_frames: for thread in interp.threads: - frames = [] - for frame in thread.frame_info: - frames.append( - { - "filename": frame.filename, - "funcname": frame.funcname, - "lineno": extract_lineno(frame.location), - } - ) + frames = [frame_to_dict(f) for f in thread.frame_info] key = (interp.interpreter_id, thread.thread_id) sample = {"status": thread.status, "frames": frames} for _ in range(count): @@ -93,15 +122,7 @@ def samples_to_by_thread(samples): for sample in samples: for interp in sample: for thread in interp.threads: - frames = [] - for frame in thread.frame_info: - frames.append( - { - "filename": frame.filename, - "funcname": frame.funcname, - "lineno": extract_lineno(frame.location), - } - ) + frames = [frame_to_dict(f) for f in thread.frame_info] key = (interp.interpreter_id, thread.thread_id) by_thread[key].append( { @@ -187,25 +208,15 @@ def assert_samples_equal(self, expected_samples, collector): for j, (exp_frame, act_frame) in enumerate( zip(exp["frames"], act["frames"]) ): - self.assertEqual( - exp_frame["filename"], - act_frame["filename"], - f"Thread ({interp_id}, {thread_id}), sample {i}, " - f"frame {j}: filename mismatch", - ) - self.assertEqual( - exp_frame["funcname"], - act_frame["funcname"], - f"Thread ({interp_id}, {thread_id}), sample {i}, " - f"frame {j}: funcname mismatch", - ) - self.assertEqual( - exp_frame["lineno"], - act_frame["lineno"], - f"Thread ({interp_id}, {thread_id}), sample {i}, " - f"frame {j}: lineno mismatch " - f"(expected {exp_frame['lineno']}, got {act_frame['lineno']})", - ) + for field in ("filename", "funcname", "lineno", "end_lineno", + "column", "end_column", "opcode"): + self.assertEqual( + exp_frame[field], + act_frame[field], + f"Thread ({interp_id}, {thread_id}), sample {i}, " + f"frame {j}: {field} mismatch " + f"(expected {exp_frame[field]!r}, got {act_frame[field]!r})", + ) class TestBinaryRoundTrip(BinaryFormatTestBase): @@ -484,6 +495,97 @@ def test_threads_interleaved_samples(self): self.assertEqual(count, 60) self.assert_samples_equal(samples, collector) + def test_full_location_roundtrip(self): + """Full source location (end_lineno, column, end_column) roundtrips.""" + frames = [ + make_frame("test.py", 10, "func1", end_lineno=12, column=4, end_column=20), + make_frame("test.py", 20, "func2", end_lineno=20, column=8, end_column=45), + make_frame("test.py", 30, "func3", end_lineno=35, column=0, end_column=100), + ] + samples = [[make_interpreter(0, [make_thread(1, frames)])]] + collector, count = self.roundtrip(samples) + self.assertEqual(count, 1) + self.assert_samples_equal(samples, collector) + + def test_opcode_roundtrip(self): + """Opcode values roundtrip exactly.""" + opcodes = [0, 1, 50, 100, 150, 200, 254] # Valid Python opcodes + samples = [] + for opcode in opcodes: + frame = make_frame("test.py", 10, "func", opcode=opcode) + samples.append([make_interpreter(0, [make_thread(1, [frame])])]) + collector, count = self.roundtrip(samples) + self.assertEqual(count, len(opcodes)) + self.assert_samples_equal(samples, collector) + + def test_opcode_none_roundtrip(self): + """Opcode=None (sentinel 255) roundtrips as None.""" + frame = make_frame("test.py", 10, "func", opcode=None) + samples = [[make_interpreter(0, [make_thread(1, [frame])])]] + collector, count = self.roundtrip(samples) + self.assertEqual(count, 1) + self.assert_samples_equal(samples, collector) + + def test_mixed_location_and_opcode(self): + """Mixed full location and opcode data roundtrips.""" + frames = [ + make_frame("a.py", 10, "a", end_lineno=15, column=4, end_column=30, opcode=100), + make_frame("b.py", 20, "b", end_lineno=20, column=0, end_column=50, opcode=None), + make_frame("c.py", 30, "c", end_lineno=32, column=8, end_column=25, opcode=50), + ] + samples = [[make_interpreter(0, [make_thread(1, frames)])]] + collector, count = self.roundtrip(samples) + self.assertEqual(count, 1) + self.assert_samples_equal(samples, collector) + + def test_delta_encoding_multiline(self): + """Multi-line spans (large end_lineno delta) roundtrip correctly.""" + # This tests the delta encoding: end_lineno = lineno + delta + frames = [ + make_frame("test.py", 1, "small", end_lineno=1, column=0, end_column=10), + make_frame("test.py", 100, "medium", end_lineno=110, column=0, end_column=50), + make_frame("test.py", 1000, "large", end_lineno=1500, column=0, end_column=200), + ] + samples = [[make_interpreter(0, [make_thread(1, frames)])]] + collector, count = self.roundtrip(samples) + self.assertEqual(count, 1) + self.assert_samples_equal(samples, collector) + + def test_column_positions_preserved(self): + """Various column positions are preserved exactly.""" + columns = [(0, 10), (4, 50), (8, 100), (100, 200)] + samples = [] + for col, end_col in columns: + frame = make_frame("test.py", 10, "func", column=col, end_column=end_col) + samples.append([make_interpreter(0, [make_thread(1, [frame])])]) + collector, count = self.roundtrip(samples) + self.assertEqual(count, len(columns)) + self.assert_samples_equal(samples, collector) + + def test_same_line_different_opcodes(self): + """Same line with different opcodes creates distinct frames.""" + # This tests that opcode is part of the frame key + frames = [ + make_frame("test.py", 10, "func", opcode=100), + make_frame("test.py", 10, "func", opcode=101), + make_frame("test.py", 10, "func", opcode=102), + ] + samples = [[make_interpreter(0, [make_thread(1, [f])]) for f in frames]] + collector, count = self.roundtrip(samples) + # Verify all three opcodes are preserved distinctly + self.assertEqual(count, 3) + + def test_same_line_different_columns(self): + """Same line with different columns creates distinct frames.""" + frames = [ + make_frame("test.py", 10, "func", column=0, end_column=10), + make_frame("test.py", 10, "func", column=15, end_column=25), + make_frame("test.py", 10, "func", column=30, end_column=40), + ] + samples = [[make_interpreter(0, [make_thread(1, [f])]) for f in frames]] + collector, count = self.roundtrip(samples) + self.assertEqual(count, 3) + class TestBinaryEdgeCases(BinaryFormatTestBase): """Tests for edge cases in binary format.""" diff --git a/Modules/_remote_debugging/binary_io.h b/Modules/_remote_debugging/binary_io.h index bdfe35f5f2ce04..f8399f4aebe74b 100644 --- a/Modules/_remote_debugging/binary_io.h +++ b/Modules/_remote_debugging/binary_io.h @@ -25,6 +25,10 @@ extern "C" { #define BINARY_FORMAT_MAGIC_SWAPPED 0x48434154 /* Byte-swapped magic for endianness detection */ #define BINARY_FORMAT_VERSION 1 +/* Sentinel values for optional frame fields */ +#define OPCODE_NONE 255 /* No opcode captured (u8 sentinel) */ +#define LOCATION_NOT_AVAILABLE (-1) /* lineno/column not available (zigzag sentinel) */ + /* Conditional byte-swap macros for cross-endian file reading. * Uses Python's optimized byte-swap functions from pycore_bitutils.h */ #define SWAP16_IF(swap, x) ((swap) ? _Py_bswap16(x) : (x)) @@ -172,18 +176,28 @@ typedef struct { size_t compressed_buffer_size; } ZstdCompressor; -/* Frame entry - combines all frame data for better cache locality */ +/* Frame entry - combines all frame data for better cache locality. + * Stores full source position (line, end_line, column, end_column) and opcode. + * Delta values are computed during serialization for efficiency. */ typedef struct { uint32_t filename_idx; uint32_t funcname_idx; - int32_t lineno; + int32_t lineno; /* Start line number (-1 for synthetic frames) */ + int32_t end_lineno; /* End line number (-1 if not available) */ + int32_t column; /* Start column in UTF-8 bytes (-1 if not available) */ + int32_t end_column; /* End column in UTF-8 bytes (-1 if not available) */ + uint8_t opcode; /* Python opcode (0-254) or OPCODE_NONE (255) */ } FrameEntry; -/* Frame key for hash table lookup */ +/* Frame key for hash table lookup - includes all fields for proper deduplication */ typedef struct { uint32_t filename_idx; uint32_t funcname_idx; int32_t lineno; + int32_t end_lineno; + int32_t column; + int32_t end_column; + uint8_t opcode; } FrameKey; /* Pending RLE sample - buffered for run-length encoding */ @@ -305,8 +319,8 @@ typedef struct { PyObject **strings; uint32_t strings_count; - /* Parsed frame table: packed as [filename_idx, funcname_idx, lineno] */ - uint32_t *frame_data; + /* Parsed frame table: array of FrameEntry structures */ + FrameEntry *frames; uint32_t frames_count; /* Sample data region */ diff --git a/Modules/_remote_debugging/binary_io_reader.c b/Modules/_remote_debugging/binary_io_reader.c index f47e3a1767f622..cb58a0ed199d4a 100644 --- a/Modules/_remote_debugging/binary_io_reader.c +++ b/Modules/_remote_debugging/binary_io_reader.c @@ -276,47 +276,86 @@ reader_parse_string_table(BinaryReader *reader, const uint8_t *data, size_t file static inline int reader_parse_frame_table(BinaryReader *reader, const uint8_t *data, size_t file_size) { - /* Check for integer overflow in allocation size calculation. - Only needed on 32-bit where SIZE_MAX can be exceeded by uint32_t * 12. */ + /* Check for integer overflow in allocation size calculation. */ #if SIZEOF_SIZE_T < 8 - if (reader->frames_count > SIZE_MAX / (3 * sizeof(uint32_t))) { + if (reader->frames_count > SIZE_MAX / sizeof(FrameEntry)) { PyErr_SetString(PyExc_OverflowError, "Frame count too large for allocation"); return -1; } #endif - size_t alloc_size = (size_t)reader->frames_count * 3 * sizeof(uint32_t); - reader->frame_data = PyMem_Malloc(alloc_size); - if (!reader->frame_data && reader->frames_count > 0) { + size_t alloc_size = (size_t)reader->frames_count * sizeof(FrameEntry); + reader->frames = PyMem_Malloc(alloc_size); + if (!reader->frames && reader->frames_count > 0) { PyErr_NoMemory(); return -1; } size_t offset = reader->frame_table_offset; for (uint32_t i = 0; i < reader->frames_count; i++) { - size_t base = (size_t)i * 3; + FrameEntry *frame = &reader->frames[i]; size_t prev_offset; prev_offset = offset; - reader->frame_data[base] = decode_varint_u32(data, &offset, file_size); + frame->filename_idx = decode_varint_u32(data, &offset, file_size); if (offset == prev_offset) { PyErr_SetString(PyExc_ValueError, "Malformed varint in frame table (filename)"); return -1; } prev_offset = offset; - reader->frame_data[base + 1] = decode_varint_u32(data, &offset, file_size); + frame->funcname_idx = decode_varint_u32(data, &offset, file_size); if (offset == prev_offset) { PyErr_SetString(PyExc_ValueError, "Malformed varint in frame table (funcname)"); return -1; } prev_offset = offset; - reader->frame_data[base + 2] = (uint32_t)decode_varint_i32(data, &offset, file_size); + frame->lineno = decode_varint_i32(data, &offset, file_size); if (offset == prev_offset) { PyErr_SetString(PyExc_ValueError, "Malformed varint in frame table (lineno)"); return -1; } + + prev_offset = offset; + int32_t end_lineno_delta = decode_varint_i32(data, &offset, file_size); + if (offset == prev_offset) { + PyErr_SetString(PyExc_ValueError, "Malformed varint in frame table (end_lineno_delta)"); + return -1; + } + /* Reconstruct end_lineno from delta. If lineno is -1, result is -1. */ + if (frame->lineno == LOCATION_NOT_AVAILABLE) { + frame->end_lineno = LOCATION_NOT_AVAILABLE; + } else { + frame->end_lineno = frame->lineno + end_lineno_delta; + } + + prev_offset = offset; + frame->column = decode_varint_i32(data, &offset, file_size); + if (offset == prev_offset) { + PyErr_SetString(PyExc_ValueError, "Malformed varint in frame table (column)"); + return -1; + } + + prev_offset = offset; + int32_t end_column_delta = decode_varint_i32(data, &offset, file_size); + if (offset == prev_offset) { + PyErr_SetString(PyExc_ValueError, "Malformed varint in frame table (end_column_delta)"); + return -1; + } + /* Reconstruct end_column from delta. If column is -1, result is -1. */ + if (frame->column == LOCATION_NOT_AVAILABLE) { + frame->end_column = LOCATION_NOT_AVAILABLE; + } else { + frame->end_column = frame->column + end_column_delta; + } + + /* Read opcode byte */ + if (offset >= file_size) { + PyErr_SetString(PyExc_ValueError, "Unexpected end of frame table (opcode)"); + return -1; + } + frame->opcode = data[offset++]; } return 0; @@ -683,13 +722,10 @@ build_frame_list(RemoteDebuggingState *state, BinaryReader *reader, goto error; } - size_t base = frame_idx * 3; - uint32_t filename_idx = reader->frame_data[base]; - uint32_t funcname_idx = reader->frame_data[base + 1]; - int32_t lineno = (int32_t)reader->frame_data[base + 2]; + FrameEntry *frame = &reader->frames[frame_idx]; - if (filename_idx >= reader->strings_count || - funcname_idx >= reader->strings_count) { + if (frame->filename_idx >= reader->strings_count || + frame->funcname_idx >= reader->strings_count) { PyErr_SetString(PyExc_ValueError, "Invalid string index in frame"); goto error; } @@ -699,9 +735,14 @@ build_frame_list(RemoteDebuggingState *state, BinaryReader *reader, goto error; } + /* Build location tuple with full position info */ PyObject *location; - if (lineno > 0) { - location = Py_BuildValue("(iiii)", lineno, lineno, 0, 0); + if (frame->lineno != LOCATION_NOT_AVAILABLE) { + location = Py_BuildValue("(iiii)", + frame->lineno, + frame->end_lineno != LOCATION_NOT_AVAILABLE ? frame->end_lineno : frame->lineno, + frame->column != LOCATION_NOT_AVAILABLE ? frame->column : 0, + frame->end_column != LOCATION_NOT_AVAILABLE ? frame->end_column : 0); if (!location) { Py_DECREF(frame_info); goto error; @@ -711,10 +752,24 @@ build_frame_list(RemoteDebuggingState *state, BinaryReader *reader, location = Py_NewRef(Py_None); } - PyStructSequence_SetItem(frame_info, 0, Py_NewRef(reader->strings[filename_idx])); + /* Build opcode object */ + PyObject *opcode_obj; + if (frame->opcode != OPCODE_NONE) { + opcode_obj = PyLong_FromLong(frame->opcode); + if (!opcode_obj) { + Py_DECREF(location); + Py_DECREF(frame_info); + goto error; + } + } + else { + opcode_obj = Py_NewRef(Py_None); + } + + PyStructSequence_SetItem(frame_info, 0, Py_NewRef(reader->strings[frame->filename_idx])); PyStructSequence_SetItem(frame_info, 1, location); - PyStructSequence_SetItem(frame_info, 2, Py_NewRef(reader->strings[funcname_idx])); - PyStructSequence_SetItem(frame_info, 3, Py_NewRef(Py_None)); + PyStructSequence_SetItem(frame_info, 2, Py_NewRef(reader->strings[frame->funcname_idx])); + PyStructSequence_SetItem(frame_info, 3, opcode_obj); PyList_SET_ITEM(frame_list, k, frame_info); } @@ -1192,7 +1247,7 @@ binary_reader_close(BinaryReader *reader) PyMem_Free(reader->strings); } - PyMem_Free(reader->frame_data); + PyMem_Free(reader->frames); if (reader->thread_states) { for (size_t i = 0; i < reader->thread_state_count; i++) { diff --git a/Modules/_remote_debugging/binary_io_writer.c b/Modules/_remote_debugging/binary_io_writer.c index 3a20f3463b0384..c8857cec6218be 100644 --- a/Modules/_remote_debugging/binary_io_writer.c +++ b/Modules/_remote_debugging/binary_io_writer.c @@ -11,6 +11,7 @@ #include "binary_io.h" #include "_remote_debugging.h" +#include "pycore_opcode_utils.h" // MAX_REAL_OPCODE #include #ifdef HAVE_ZSTD @@ -32,6 +33,16 @@ /* File structure sizes */ #define FILE_FOOTER_SIZE 32 +/* Helper macro: convert PyLong to int32, using default_val if conversion fails */ +#define PYLONG_TO_INT32_OR_DEFAULT(obj, var, default_val) \ + do { \ + (var) = (int32_t)PyLong_AsLong(obj); \ + if (UNLIKELY(PyErr_Occurred() != NULL)) { \ + PyErr_Clear(); \ + (var) = (default_val); \ + } \ + } while (0) + /* ============================================================================ * WRITER-SPECIFIC UTILITY HELPERS * ============================================================================ */ @@ -311,7 +322,7 @@ static Py_uhash_t frame_key_hash_func(const void *key) { const FrameKey *fk = (const FrameKey *)key; - /* FNV-1a style hash combining all three values */ + /* FNV-1a style hash combining all fields */ Py_uhash_t hash = 2166136261u; hash ^= fk->filename_idx; hash *= 16777619u; @@ -319,6 +330,14 @@ frame_key_hash_func(const void *key) hash *= 16777619u; hash ^= (uint32_t)fk->lineno; hash *= 16777619u; + hash ^= (uint32_t)fk->end_lineno; + hash *= 16777619u; + hash ^= (uint32_t)fk->column; + hash *= 16777619u; + hash ^= (uint32_t)fk->end_column; + hash *= 16777619u; + hash ^= fk->opcode; + hash *= 16777619u; return hash; } @@ -329,7 +348,11 @@ frame_key_compare_func(const void *key1, const void *key2) const FrameKey *fk2 = (const FrameKey *)key2; return (fk1->filename_idx == fk2->filename_idx && fk1->funcname_idx == fk2->funcname_idx && - fk1->lineno == fk2->lineno); + fk1->lineno == fk2->lineno && + fk1->end_lineno == fk2->end_lineno && + fk1->column == fk2->column && + fk1->end_column == fk2->end_column && + fk1->opcode == fk2->opcode); } static void @@ -388,10 +411,14 @@ writer_intern_string(BinaryWriter *writer, PyObject *string, uint32_t *index) } static inline int -writer_intern_frame(BinaryWriter *writer, uint32_t filename_idx, uint32_t funcname_idx, - int32_t lineno, uint32_t *index) +writer_intern_frame(BinaryWriter *writer, const FrameEntry *entry, uint32_t *index) { - FrameKey lookup_key = {filename_idx, funcname_idx, lineno}; + FrameKey lookup_key = { + entry->filename_idx, entry->funcname_idx, + entry->lineno, entry->end_lineno, + entry->column, entry->end_column, + entry->opcode + }; void *existing = _Py_hashtable_get(writer->frame_hash, &lookup_key); if (existing != NULL) { @@ -412,10 +439,7 @@ writer_intern_frame(BinaryWriter *writer, uint32_t filename_idx, uint32_t funcna *key = lookup_key; *index = (uint32_t)writer->frame_count; - FrameEntry *fe = &writer->frame_entries[writer->frame_count]; - fe->filename_idx = filename_idx; - fe->funcname_idx = funcname_idx; - fe->lineno = lineno; + writer->frame_entries[writer->frame_count] = *entry; if (_Py_hashtable_set(writer->frame_hash, key, (void *)(uintptr_t)(*index + 1)) < 0) { PyMem_Free(key); @@ -810,22 +834,49 @@ build_frame_stack(BinaryWriter *writer, PyObject *frame_list, /* Use unchecked accessors since we control the data structures */ PyObject *frame_info = PyList_GET_ITEM(frame_list, k); - /* Get filename, location, funcname from FrameInfo using unchecked access */ + /* Get filename, location, funcname, opcode from FrameInfo using unchecked access */ PyObject *filename = PyStructSequence_GET_ITEM(frame_info, 0); PyObject *location = PyStructSequence_GET_ITEM(frame_info, 1); PyObject *funcname = PyStructSequence_GET_ITEM(frame_info, 2); + PyObject *opcode_obj = PyStructSequence_GET_ITEM(frame_info, 3); + + /* Extract location fields (can be None for synthetic frames) */ + int32_t lineno = LOCATION_NOT_AVAILABLE; + int32_t end_lineno = LOCATION_NOT_AVAILABLE; + int32_t column = LOCATION_NOT_AVAILABLE; + int32_t end_column = LOCATION_NOT_AVAILABLE; - /* Extract lineno from location (can be None for synthetic frames) */ - int32_t lineno = 0; if (location != Py_None) { - /* Use unchecked access - first element is lineno */ + /* LocationInfo is a struct sequence or tuple with: + * (lineno, end_lineno, column, end_column) */ PyObject *lineno_obj = PyTuple_Check(location) ? PyTuple_GET_ITEM(location, 0) : PyStructSequence_GET_ITEM(location, 0); - lineno = (int32_t)PyLong_AsLong(lineno_obj); + PyObject *end_lineno_obj = PyTuple_Check(location) ? + PyTuple_GET_ITEM(location, 1) : + PyStructSequence_GET_ITEM(location, 1); + PyObject *column_obj = PyTuple_Check(location) ? + PyTuple_GET_ITEM(location, 2) : + PyStructSequence_GET_ITEM(location, 2); + PyObject *end_column_obj = PyTuple_Check(location) ? + PyTuple_GET_ITEM(location, 3) : + PyStructSequence_GET_ITEM(location, 3); + + PYLONG_TO_INT32_OR_DEFAULT(lineno_obj, lineno, LOCATION_NOT_AVAILABLE); + PYLONG_TO_INT32_OR_DEFAULT(end_lineno_obj, end_lineno, LOCATION_NOT_AVAILABLE); + PYLONG_TO_INT32_OR_DEFAULT(column_obj, column, LOCATION_NOT_AVAILABLE); + PYLONG_TO_INT32_OR_DEFAULT(end_column_obj, end_column, LOCATION_NOT_AVAILABLE); + } + + /* Extract opcode (can be None) */ + uint8_t opcode = OPCODE_NONE; + if (opcode_obj != Py_None) { + long opcode_long = PyLong_AsLong(opcode_obj); if (UNLIKELY(PyErr_Occurred() != NULL)) { PyErr_Clear(); - lineno = 0; + opcode = OPCODE_NONE; + } else if (opcode_long >= 0 && opcode_long <= MAX_REAL_OPCODE) { + opcode = (uint8_t)opcode_long; } } @@ -841,9 +892,18 @@ build_frame_stack(BinaryWriter *writer, PyObject *frame_list, return -1; } - /* Intern frame */ + /* Intern frame with full location info */ + FrameEntry frame_entry = { + .filename_idx = filename_idx, + .funcname_idx = funcname_idx, + .lineno = lineno, + .end_lineno = end_lineno, + .column = column, + .end_column = end_column, + .opcode = opcode + }; uint32_t frame_idx; - if (writer_intern_frame(writer, filename_idx, funcname_idx, lineno, &frame_idx) < 0) { + if (writer_intern_frame(writer, &frame_entry, &frame_idx) < 0) { return -1; } @@ -1038,10 +1098,33 @@ binary_writer_finalize(BinaryWriter *writer) for (size_t i = 0; i < writer->frame_count; i++) { FrameEntry *entry = &writer->frame_entries[i]; - uint8_t buf[30]; + uint8_t buf[64]; /* Increased buffer for additional fields */ size_t pos = encode_varint_u32(buf, entry->filename_idx); pos += encode_varint_u32(buf + pos, entry->funcname_idx); pos += encode_varint_i32(buf + pos, entry->lineno); + + /* Delta encode end_lineno: store (end_lineno - lineno) as zigzag. + * When lineno is -1, store delta as 0 (result will be -1). */ + int32_t end_lineno_delta = 0; + if (entry->lineno != LOCATION_NOT_AVAILABLE && + entry->end_lineno != LOCATION_NOT_AVAILABLE) { + end_lineno_delta = entry->end_lineno - entry->lineno; + } + pos += encode_varint_i32(buf + pos, end_lineno_delta); + + pos += encode_varint_i32(buf + pos, entry->column); + + /* Delta encode end_column: store (end_column - column) as zigzag. + * When column is -1, store delta as 0 (result will be -1). */ + int32_t end_column_delta = 0; + if (entry->column != LOCATION_NOT_AVAILABLE && + entry->end_column != LOCATION_NOT_AVAILABLE) { + end_column_delta = entry->end_column - entry->column; + } + pos += encode_varint_i32(buf + pos, end_column_delta); + + buf[pos++] = entry->opcode; + if (fwrite_checked_allow_threads(buf, pos, writer->fp) < 0) { return -1; } @@ -1156,3 +1239,4 @@ binary_writer_destroy(BinaryWriter *writer) PyMem_Free(writer); } +#undef PYLONG_TO_INT32_OR_DEFAULT From 84b7e6970f4252ec6b82f50997e880662ec0a146 Mon Sep 17 00:00:00 2001 From: Tom Kuson Date: Wed, 24 Dec 2025 17:30:20 +0000 Subject: [PATCH 030/220] gh-140717: Add `exc_text` to LogRecord attributes table (GH-140718) --- Doc/library/logging.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst index 0cf5b1c0d9bc3e..d17f36bc7131d6 100644 --- a/Doc/library/logging.rst +++ b/Doc/library/logging.rst @@ -1011,6 +1011,11 @@ the options available to you. | exc_info | You shouldn't need to | Exception tuple (à la ``sys.exc_info``) or, | | | format this yourself. | if no exception has occurred, ``None``. | +----------------+-------------------------+-----------------------------------------------+ +| exc_text | You shouldn't need to | Exception information formatted as a string. | +| | format this yourself. | This is set when :meth:`Formatter.format` is | +| | | invoked, or ``None`` if no exception has | +| | | occurred. | ++----------------+-------------------------+-----------------------------------------------+ | filename | ``%(filename)s`` | Filename portion of ``pathname``. | +----------------+-------------------------+-----------------------------------------------+ | funcName | ``%(funcName)s`` | Name of function containing the logging call. | From 3509fa5a12855805f0c6d7f8a6a3b162744a8fd4 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> Date: Wed, 24 Dec 2025 17:56:59 +0000 Subject: [PATCH 031/220] gh-143135: Fix sys.flags.inspect when PYTHONINSPECT=0 (GH-143136) --- .../2025-12-24-11-39-59.gh-issue-143135.3d5ovx.rst | 2 ++ Python/initconfig.c | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-12-24-11-39-59.gh-issue-143135.3d5ovx.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-24-11-39-59.gh-issue-143135.3d5ovx.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-24-11-39-59.gh-issue-143135.3d5ovx.rst new file mode 100644 index 00000000000000..2ff08b44355704 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-24-11-39-59.gh-issue-143135.3d5ovx.rst @@ -0,0 +1,2 @@ +Set :data:`sys.flags.inspect` to ``1`` when :envvar:`PYTHONINSPECT` is ``0``. +Previously, it was set to ``0`` in this case. diff --git a/Python/initconfig.c b/Python/initconfig.c index 7176670c110d69..9cdc10c4e78071 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -1846,7 +1846,9 @@ config_read_env_vars(PyConfig *config) _Py_get_env_flag(use_env, &config->parser_debug, "PYTHONDEBUG"); _Py_get_env_flag(use_env, &config->verbose, "PYTHONVERBOSE"); _Py_get_env_flag(use_env, &config->optimization_level, "PYTHONOPTIMIZE"); - _Py_get_env_flag(use_env, &config->inspect, "PYTHONINSPECT"); + if (!config->inspect && _Py_GetEnv(use_env, "PYTHONINSPECT")) { + config->inspect = 1; + } int dont_write_bytecode = 0; _Py_get_env_flag(use_env, &dont_write_bytecode, "PYTHONDONTWRITEBYTECODE"); From 7342890ed710abaea09fdc011723cbba0ca9ad24 Mon Sep 17 00:00:00 2001 From: "R. David Murray" Date: Wed, 24 Dec 2025 13:14:23 -0500 Subject: [PATCH 032/220] gh-142517: Fix typo in news item. (#143150) --- .../next/Library/2025-12-10-10-00-06.gh-issue-142517.fG4hbe.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-12-10-10-00-06.gh-issue-142517.fG4hbe.rst b/Misc/NEWS.d/next/Library/2025-12-10-10-00-06.gh-issue-142517.fG4hbe.rst index 388fff0e2acb96..d58e532ac43ebe 100644 --- a/Misc/NEWS.d/next/Library/2025-12-10-10-00-06.gh-issue-142517.fG4hbe.rst +++ b/Misc/NEWS.d/next/Library/2025-12-10-10-00-06.gh-issue-142517.fG4hbe.rst @@ -1,4 +1,4 @@ The non-``compat32`` :mod:`email` policies now correctly handle refolding encoded words that contain bytes that can not be decoded in their specified -character set. Previously this resulting in an encoding exception during +character set. Previously this resulted in an encoding exception during folding. From 305aff0a66ad86274a58d5a5aae445713a4c4cca Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Wed, 24 Dec 2025 11:03:10 -0800 Subject: [PATCH 033/220] Move News for gh-142560 to Core and Builtins (GH-143154) --- .../2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/{Library => Core_and_Builtins}/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst (100%) diff --git a/Misc/NEWS.d/next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst similarity index 100% rename from Misc/NEWS.d/next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst rename to Misc/NEWS.d/next/Core_and_Builtins/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst From 594a4631c3afd4139b6783f15034a92878c8eff1 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Wed, 24 Dec 2025 16:10:43 -0500 Subject: [PATCH 034/220] gh-120321: Fix TSan reported races on gi_frame_state (gh-143128) --- Objects/genobject.c | 2 +- Python/frame.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/genobject.c b/Objects/genobject.c index 020af903a3f828..d1fcda3d608320 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -559,7 +559,7 @@ gen_set_exception(PyObject *typ, PyObject *val, PyObject *tb) static PyObject * gen_throw_current_exception(PyGenObject *gen) { - assert(gen->gi_frame_state == FRAME_EXECUTING); + assert(FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state) == FRAME_EXECUTING); PyObject *result; if (gen_send_ex2(gen, Py_None, &result, 1) == PYGEN_RETURN) { diff --git a/Python/frame.c b/Python/frame.c index ce216797e47cda..da8f9037e8287a 100644 --- a/Python/frame.c +++ b/Python/frame.c @@ -109,7 +109,7 @@ _PyFrame_ClearExceptCode(_PyInterpreterFrame *frame) /* It is the responsibility of the owning generator/coroutine * to have cleared the enclosing generator, if any. */ assert(frame->owner != FRAME_OWNED_BY_GENERATOR || - _PyGen_GetGeneratorFromFrame(frame)->gi_frame_state == FRAME_CLEARED); + FT_ATOMIC_LOAD_INT8_RELAXED(_PyGen_GetGeneratorFromFrame(frame)->gi_frame_state) == FRAME_CLEARED); // GH-99729: Clearing this frame can expose the stack (via finalizers). It's // crucial that this frame has been unlinked, and is no longer visible: assert(_PyThreadState_GET()->current_frame != frame); From cf6758ff9ebd6df8ac2a87755cdbad674c49c9cb Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Thu, 25 Dec 2025 06:03:00 +0800 Subject: [PATCH 035/220] gh-143092: Make CALL_LIST_APPEND and BINARY_OP_INPLACE_ADD_UNICODE normal instructions (GH-143124) These super instructions need many special cases in the interpreter, specializer, and JIT. It's best we convert them to normal instructions. --- Include/internal/pycore_opcode_metadata.h | 4 +- Include/internal/pycore_tstate.h | 1 - Include/internal/pycore_uop_ids.h | 10 +- Include/internal/pycore_uop_metadata.h | 30 +++--- Lib/_opcode_metadata.py | 2 + Lib/test/test_capi/test_opt.py | 41 ++++++++ ...-12-23-23-06-11.gh-issue-143092.6MISbb.rst | 1 + Python/bytecodes.c | 58 ++++------- Python/executor_cases.c.h | 97 +++++++++---------- Python/generated_cases.c.h | 48 ++++----- Python/optimizer.c | 7 -- Python/optimizer_bytecodes.c | 10 +- Python/optimizer_cases.c.h | 59 +++-------- Python/specialize.c | 4 +- 14 files changed, 167 insertions(+), 205 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-12-23-23-06-11.gh-issue-143092.6MISbb.rst diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 08bddfbfbe6619..8920dd42d7384a 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -534,7 +534,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg) { case BINARY_OP_EXTEND: return 1; case BINARY_OP_INPLACE_ADD_UNICODE: - return 0; + return 1; case BINARY_OP_MULTIPLY_FLOAT: return 1; case BINARY_OP_MULTIPLY_INT: @@ -610,7 +610,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg) { case CALL_LEN: return 1; case CALL_LIST_APPEND: - return 0; + return 1; case CALL_METHOD_DESCRIPTOR_FAST: return 1; case CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h index a57f1f45c135a6..1a7ebb01403208 100644 --- a/Include/internal/pycore_tstate.h +++ b/Include/internal/pycore_tstate.h @@ -37,7 +37,6 @@ typedef struct _PyJitTracerInitialState { typedef struct _PyJitTracerPreviousState { bool dependencies_still_valid; - bool instr_is_super; int code_max_size; int code_curr_size; int instr_oparg; diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index 69f1c3829cd239..42fb9464b6a13d 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -371,7 +371,7 @@ extern "C" { #define _BINARY_OP_ADD_UNICODE_r13 564 #define _BINARY_OP_ADD_UNICODE_r23 565 #define _BINARY_OP_EXTEND_r21 566 -#define _BINARY_OP_INPLACE_ADD_UNICODE_r20 567 +#define _BINARY_OP_INPLACE_ADD_UNICODE_r21 567 #define _BINARY_OP_MULTIPLY_FLOAT_r03 568 #define _BINARY_OP_MULTIPLY_FLOAT_r13 569 #define _BINARY_OP_MULTIPLY_FLOAT_r23 570 @@ -412,10 +412,10 @@ extern "C" { #define _CALL_ISINSTANCE_r31 605 #define _CALL_KW_NON_PY_r11 606 #define _CALL_LEN_r33 607 -#define _CALL_LIST_APPEND_r02 608 -#define _CALL_LIST_APPEND_r12 609 -#define _CALL_LIST_APPEND_r22 610 -#define _CALL_LIST_APPEND_r32 611 +#define _CALL_LIST_APPEND_r03 608 +#define _CALL_LIST_APPEND_r13 609 +#define _CALL_LIST_APPEND_r23 610 +#define _CALL_LIST_APPEND_r33 611 #define _CALL_METHOD_DESCRIPTOR_FAST_r01 612 #define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_r01 613 #define _CALL_METHOD_DESCRIPTOR_NOARGS_r01 614 diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 0a49231e53f44a..8b14ca794ce8d5 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -1065,7 +1065,7 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { .entries = { { -1, -1, -1 }, { -1, -1, -1 }, - { 0, 2, _BINARY_OP_INPLACE_ADD_UNICODE_r20 }, + { 1, 2, _BINARY_OP_INPLACE_ADD_UNICODE_r21 }, { -1, -1, -1 }, }, }, @@ -2629,10 +2629,10 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { [_CALL_LIST_APPEND] = { .best = { 0, 1, 2, 3 }, .entries = { - { 2, 0, _CALL_LIST_APPEND_r02 }, - { 2, 1, _CALL_LIST_APPEND_r12 }, - { 2, 2, _CALL_LIST_APPEND_r22 }, - { 2, 3, _CALL_LIST_APPEND_r32 }, + { 3, 0, _CALL_LIST_APPEND_r03 }, + { 3, 1, _CALL_LIST_APPEND_r13 }, + { 3, 2, _CALL_LIST_APPEND_r23 }, + { 3, 3, _CALL_LIST_APPEND_r33 }, }, }, [_CALL_METHOD_DESCRIPTOR_O] = { @@ -3437,7 +3437,7 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = { [_BINARY_OP_ADD_UNICODE_r03] = _BINARY_OP_ADD_UNICODE, [_BINARY_OP_ADD_UNICODE_r13] = _BINARY_OP_ADD_UNICODE, [_BINARY_OP_ADD_UNICODE_r23] = _BINARY_OP_ADD_UNICODE, - [_BINARY_OP_INPLACE_ADD_UNICODE_r20] = _BINARY_OP_INPLACE_ADD_UNICODE, + [_BINARY_OP_INPLACE_ADD_UNICODE_r21] = _BINARY_OP_INPLACE_ADD_UNICODE, [_GUARD_BINARY_OP_EXTEND_r22] = _GUARD_BINARY_OP_EXTEND, [_BINARY_OP_EXTEND_r21] = _BINARY_OP_EXTEND, [_BINARY_SLICE_r31] = _BINARY_SLICE, @@ -3743,10 +3743,10 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = { [_GUARD_CALLABLE_LIST_APPEND_r13] = _GUARD_CALLABLE_LIST_APPEND, [_GUARD_CALLABLE_LIST_APPEND_r23] = _GUARD_CALLABLE_LIST_APPEND, [_GUARD_CALLABLE_LIST_APPEND_r33] = _GUARD_CALLABLE_LIST_APPEND, - [_CALL_LIST_APPEND_r02] = _CALL_LIST_APPEND, - [_CALL_LIST_APPEND_r12] = _CALL_LIST_APPEND, - [_CALL_LIST_APPEND_r22] = _CALL_LIST_APPEND, - [_CALL_LIST_APPEND_r32] = _CALL_LIST_APPEND, + [_CALL_LIST_APPEND_r03] = _CALL_LIST_APPEND, + [_CALL_LIST_APPEND_r13] = _CALL_LIST_APPEND, + [_CALL_LIST_APPEND_r23] = _CALL_LIST_APPEND, + [_CALL_LIST_APPEND_r33] = _CALL_LIST_APPEND, [_CALL_METHOD_DESCRIPTOR_O_r01] = _CALL_METHOD_DESCRIPTOR_O, [_CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_r01] = _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS, [_CALL_METHOD_DESCRIPTOR_NOARGS_r01] = _CALL_METHOD_DESCRIPTOR_NOARGS, @@ -3944,7 +3944,7 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_BINARY_OP_EXTEND] = "_BINARY_OP_EXTEND", [_BINARY_OP_EXTEND_r21] = "_BINARY_OP_EXTEND_r21", [_BINARY_OP_INPLACE_ADD_UNICODE] = "_BINARY_OP_INPLACE_ADD_UNICODE", - [_BINARY_OP_INPLACE_ADD_UNICODE_r20] = "_BINARY_OP_INPLACE_ADD_UNICODE_r20", + [_BINARY_OP_INPLACE_ADD_UNICODE_r21] = "_BINARY_OP_INPLACE_ADD_UNICODE_r21", [_BINARY_OP_MULTIPLY_FLOAT] = "_BINARY_OP_MULTIPLY_FLOAT", [_BINARY_OP_MULTIPLY_FLOAT_r03] = "_BINARY_OP_MULTIPLY_FLOAT_r03", [_BINARY_OP_MULTIPLY_FLOAT_r13] = "_BINARY_OP_MULTIPLY_FLOAT_r13", @@ -4015,10 +4015,10 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_CALL_LEN] = "_CALL_LEN", [_CALL_LEN_r33] = "_CALL_LEN_r33", [_CALL_LIST_APPEND] = "_CALL_LIST_APPEND", - [_CALL_LIST_APPEND_r02] = "_CALL_LIST_APPEND_r02", - [_CALL_LIST_APPEND_r12] = "_CALL_LIST_APPEND_r12", - [_CALL_LIST_APPEND_r22] = "_CALL_LIST_APPEND_r22", - [_CALL_LIST_APPEND_r32] = "_CALL_LIST_APPEND_r32", + [_CALL_LIST_APPEND_r03] = "_CALL_LIST_APPEND_r03", + [_CALL_LIST_APPEND_r13] = "_CALL_LIST_APPEND_r13", + [_CALL_LIST_APPEND_r23] = "_CALL_LIST_APPEND_r23", + [_CALL_LIST_APPEND_r33] = "_CALL_LIST_APPEND_r33", [_CALL_METHOD_DESCRIPTOR_FAST] = "_CALL_METHOD_DESCRIPTOR_FAST", [_CALL_METHOD_DESCRIPTOR_FAST_r01] = "_CALL_METHOD_DESCRIPTOR_FAST_r01", [_CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = "_CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index e681cb17e43e04..b3dce93710a23f 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -28,6 +28,7 @@ "BINARY_OP_SUBSCR_STR_INT", "BINARY_OP_SUBSCR_DICT", "BINARY_OP_SUBSCR_GETITEM", + "BINARY_OP_INPLACE_ADD_UNICODE", "BINARY_OP_EXTEND", "BINARY_OP_INPLACE_ADD_UNICODE", ], @@ -125,6 +126,7 @@ 'BINARY_OP_ADD_UNICODE': 131, 'BINARY_OP_EXTEND': 132, 'BINARY_OP_INPLACE_ADD_UNICODE': 3, + 'BINARY_OP_INPLACE_ADD_UNICODE': 3, 'BINARY_OP_MULTIPLY_FLOAT': 133, 'BINARY_OP_MULTIPLY_INT': 134, 'BINARY_OP_SUBSCR_DICT': 135, diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 8f7314d579df6f..9c9135411f9078 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -3152,6 +3152,47 @@ def f1(): """), PYTHON_JIT="1") self.assertEqual(result[0].rc, 0, result) + def test_143092(self): + def f1(): + a = "a" + for i in range(50): + x = a[i % len(a)] + + s = "" + for _ in range(10): + s += "" + + class A: ... + class B: ... + + match s: + case int(): ... + case str(): ... + case dict(): ... + + ( + u0, + *u1, + u2, + u4, + u5, + u6, + u7, + u8, + u9, u10, u11, + u12, u13, u14, u15, u16, u17, u18, u19, u20, u21, u22, u23, u24, u25, u26, u27, u28, u29, + ) = [None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None,] + + s = "" + for _ in range(10): + s += "" + s += "" + + for i in range(TIER2_THRESHOLD * 10): + f1() def global_identity(x): return x diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-23-23-06-11.gh-issue-143092.6MISbb.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-23-23-06-11.gh-issue-143092.6MISbb.rst new file mode 100644 index 00000000000000..dddfc56c8f7038 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-23-23-06-11.gh-issue-143092.6MISbb.rst @@ -0,0 +1 @@ +Fix a crash in the JIT when dealing with ``list.append(x)`` style code. diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 9232a16551d8a2..b909ee187950ec 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -588,7 +588,7 @@ dummy_func( BINARY_OP_SUBSCR_STR_INT, BINARY_OP_SUBSCR_DICT, BINARY_OP_SUBSCR_GETITEM, - // BINARY_OP_INPLACE_ADD_UNICODE, // See comments at that opcode. + BINARY_OP_INPLACE_ADD_UNICODE, BINARY_OP_EXTEND, }; @@ -762,13 +762,10 @@ dummy_func( macro(BINARY_OP_ADD_UNICODE) = _GUARD_TOS_UNICODE + _GUARD_NOS_UNICODE + unused/5 + _BINARY_OP_ADD_UNICODE + _POP_TOP_UNICODE + _POP_TOP_UNICODE; - // This is a subtle one. It's a super-instruction for - // BINARY_OP_ADD_UNICODE followed by STORE_FAST - // where the store goes into the left argument. - // So the inputs are the same as for all BINARY_OP - // specializations, but there is no output. - // At the end we just skip over the STORE_FAST. - op(_BINARY_OP_INPLACE_ADD_UNICODE, (left, right --)) { + // This is a subtle one. We write NULL to the local + // of the following STORE_FAST and leave the result for STORE_FAST + // later to store. + op(_BINARY_OP_INPLACE_ADD_UNICODE, (left, right -- res)) { PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); assert(PyUnicode_CheckExact(left_o)); assert(PyUnicode_CheckExact(PyStackRef_AsPyObjectBorrow(right))); @@ -796,20 +793,16 @@ dummy_func( * that the string is safe to mutate. */ assert(Py_REFCNT(left_o) >= 2 || !PyStackRef_IsHeapSafe(left)); - PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc); - DEAD(left); PyObject *temp = PyStackRef_AsPyObjectSteal(*target_local); - PyObject *right_o = PyStackRef_AsPyObjectSteal(right); + PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); PyUnicode_Append(&temp, right_o); - *target_local = PyStackRef_FromPyObjectSteal(temp); - Py_DECREF(right_o); - ERROR_IF(PyStackRef_IsNull(*target_local)); - #if TIER_ONE - // The STORE_FAST is already done. This is done here in tier one, - // and during trace projection in tier two: - assert(next_instr->op.code == STORE_FAST); - SKIP_OVER(1); - #endif + PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc); + DEAD(right); + PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc); + DEAD(left); + ERROR_IF(temp == NULL); + res = PyStackRef_FromPyObjectSteal(temp); + *target_local = PyStackRef_NULL; } op(_GUARD_BINARY_OP_EXTEND, (descr/4, left, right -- left, right)) { @@ -4330,8 +4323,7 @@ dummy_func( DEOPT_IF(callable_o != interp->callable_cache.list_append); } - // This is secretly a super-instruction - op(_CALL_LIST_APPEND, (callable, self, arg -- c, s)) { + op(_CALL_LIST_APPEND, (callable, self, arg -- none, c, s)) { assert(oparg == 1); PyObject *self_o = PyStackRef_AsPyObjectBorrow(self); @@ -4344,13 +4336,9 @@ dummy_func( } c = callable; s = self; - INPUTS_DEAD(); - #if TIER_ONE - // Skip the following POP_TOP. This is done here in tier one, and - // during trace projection in tier two: - assert(next_instr->op.code == POP_TOP); - SKIP_OVER(1); - #endif + DEAD(callable); + DEAD(self); + none = PyStackRef_None; } op(_CALL_METHOD_DESCRIPTOR_O, (callable, self_or_null, args[oparg] -- res)) { @@ -5598,15 +5586,9 @@ dummy_func( // Super instructions. Instruction deopted. There's a mismatch in what the stack expects // in the optimizer. So we have to reflect in the trace correctly. _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; - if ((_tstate->jit_tracer_state.prev_state.instr->op.code == CALL_LIST_APPEND && - opcode == POP_TOP) || - (_tstate->jit_tracer_state.prev_state.instr->op.code == BINARY_OP_INPLACE_ADD_UNICODE && - opcode == STORE_FAST)) { - _tstate->jit_tracer_state.prev_state.instr_is_super = true; - } - else { - _tstate->jit_tracer_state.prev_state.instr = next_instr; - } + // JIT should have disabled super instructions, as we can + // do these optimizations ourselves in the JIT. + _tstate->jit_tracer_state.prev_state.instr = next_instr; PyObject *prev_code = PyStackRef_AsPyObjectBorrow(frame->f_executable); if (_tstate->jit_tracer_state.prev_state.instr_code != (PyCodeObject *)prev_code) { Py_SETREF(_tstate->jit_tracer_state.prev_state.instr_code, (PyCodeObject*)Py_NewRef((prev_code))); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index aa98d4019dbdff..b50347615c02f8 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -4291,11 +4291,12 @@ break; } - case _BINARY_OP_INPLACE_ADD_UNICODE_r20: { + case _BINARY_OP_INPLACE_ADD_UNICODE_r21: { CHECK_CURRENT_CACHED_VALUES(2); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); _PyStackRef right; _PyStackRef left; + _PyStackRef res; _PyStackRef _stack_item_0 = _tos_cache0; _PyStackRef _stack_item_1 = _tos_cache1; right = _stack_item_1; @@ -4321,29 +4322,31 @@ } STAT_INC(BINARY_OP, hit); assert(Py_REFCNT(left_o) >= 2 || !PyStackRef_IsHeapSafe(left)); - PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc); PyObject *temp = PyStackRef_AsPyObjectSteal(*target_local); - PyObject *right_o = PyStackRef_AsPyObjectSteal(right); + PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); + stack_pointer[0] = left; + stack_pointer[1] = right; + stack_pointer += 2; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); PyUnicode_Append(&temp, right_o); stack_pointer = _PyFrame_GetStackPointer(frame); - *target_local = PyStackRef_FromPyObjectSteal(temp); - _PyFrame_SetStackPointer(frame, stack_pointer); - Py_DECREF(right_o); - stack_pointer = _PyFrame_GetStackPointer(frame); - if (PyStackRef_IsNull(*target_local)) { + PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc); + PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc); + if (temp == NULL) { + stack_pointer += -2; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); SET_CURRENT_CACHED_VALUES(0); JUMP_TO_ERROR(); } - #if TIER_ONE - - assert(next_instr->op.code == STORE_FAST); - SKIP_OVER(1); - #endif - _tos_cache0 = PyStackRef_ZERO_BITS; + res = PyStackRef_FromPyObjectSteal(temp); + *target_local = PyStackRef_NULL; + _tos_cache0 = res; _tos_cache1 = PyStackRef_ZERO_BITS; _tos_cache2 = PyStackRef_ZERO_BITS; - SET_CURRENT_CACHED_VALUES(0); + SET_CURRENT_CACHED_VALUES(1); + stack_pointer += -2; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); break; } @@ -13902,12 +13905,13 @@ break; } - case _CALL_LIST_APPEND_r02: { + case _CALL_LIST_APPEND_r03: { CHECK_CURRENT_CACHED_VALUES(0); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); _PyStackRef arg; _PyStackRef self; _PyStackRef callable; + _PyStackRef none; _PyStackRef c; _PyStackRef s; oparg = CURRENT_OPARG(); @@ -13930,26 +13934,24 @@ } c = callable; s = self; - #if TIER_ONE - - assert(next_instr->op.code == POP_TOP); - SKIP_OVER(1); - #endif - _tos_cache1 = s; - _tos_cache0 = c; - SET_CURRENT_CACHED_VALUES(2); + none = PyStackRef_None; + _tos_cache2 = s; + _tos_cache1 = c; + _tos_cache0 = none; + SET_CURRENT_CACHED_VALUES(3); stack_pointer += -3; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); break; } - case _CALL_LIST_APPEND_r12: { + case _CALL_LIST_APPEND_r13: { CHECK_CURRENT_CACHED_VALUES(1); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); _PyStackRef arg; _PyStackRef self; _PyStackRef callable; + _PyStackRef none; _PyStackRef c; _PyStackRef s; _PyStackRef _stack_item_0 = _tos_cache0; @@ -13976,26 +13978,24 @@ } c = callable; s = self; - #if TIER_ONE - - assert(next_instr->op.code == POP_TOP); - SKIP_OVER(1); - #endif - _tos_cache1 = s; - _tos_cache0 = c; - SET_CURRENT_CACHED_VALUES(2); + none = PyStackRef_None; + _tos_cache2 = s; + _tos_cache1 = c; + _tos_cache0 = none; + SET_CURRENT_CACHED_VALUES(3); stack_pointer += -2; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); break; } - case _CALL_LIST_APPEND_r22: { + case _CALL_LIST_APPEND_r23: { CHECK_CURRENT_CACHED_VALUES(2); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); _PyStackRef arg; _PyStackRef self; _PyStackRef callable; + _PyStackRef none; _PyStackRef c; _PyStackRef s; _PyStackRef _stack_item_0 = _tos_cache0; @@ -14025,26 +14025,24 @@ } c = callable; s = self; - #if TIER_ONE - - assert(next_instr->op.code == POP_TOP); - SKIP_OVER(1); - #endif - _tos_cache1 = s; - _tos_cache0 = c; - SET_CURRENT_CACHED_VALUES(2); + none = PyStackRef_None; + _tos_cache2 = s; + _tos_cache1 = c; + _tos_cache0 = none; + SET_CURRENT_CACHED_VALUES(3); stack_pointer += -1; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); break; } - case _CALL_LIST_APPEND_r32: { + case _CALL_LIST_APPEND_r33: { CHECK_CURRENT_CACHED_VALUES(3); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); _PyStackRef arg; _PyStackRef self; _PyStackRef callable; + _PyStackRef none; _PyStackRef c; _PyStackRef s; _PyStackRef _stack_item_0 = _tos_cache0; @@ -14077,14 +14075,11 @@ } c = callable; s = self; - #if TIER_ONE - - assert(next_instr->op.code == POP_TOP); - SKIP_OVER(1); - #endif - _tos_cache1 = s; - _tos_cache0 = c; - SET_CURRENT_CACHED_VALUES(2); + none = PyStackRef_None; + _tos_cache2 = s; + _tos_cache1 = c; + _tos_cache0 = none; + SET_CURRENT_CACHED_VALUES(3); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); break; } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 5f326d5c3caffc..50759933814c4f 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -382,6 +382,7 @@ _PyStackRef nos; _PyStackRef left; _PyStackRef right; + _PyStackRef res; // _GUARD_TOS_UNICODE { value = stack_pointer[-1]; @@ -426,27 +427,22 @@ } STAT_INC(BINARY_OP, hit); assert(Py_REFCNT(left_o) >= 2 || !PyStackRef_IsHeapSafe(left)); - PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc); PyObject *temp = PyStackRef_AsPyObjectSteal(*target_local); - PyObject *right_o = PyStackRef_AsPyObjectSteal(right); - stack_pointer += -2; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); _PyFrame_SetStackPointer(frame, stack_pointer); PyUnicode_Append(&temp, right_o); stack_pointer = _PyFrame_GetStackPointer(frame); - *target_local = PyStackRef_FromPyObjectSteal(temp); - _PyFrame_SetStackPointer(frame, stack_pointer); - Py_DECREF(right_o); - stack_pointer = _PyFrame_GetStackPointer(frame); - if (PyStackRef_IsNull(*target_local)) { - JUMP_TO_LABEL(error); + PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc); + PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc); + if (temp == NULL) { + JUMP_TO_LABEL(pop_2_error); } - #if TIER_ONE - - assert(next_instr->op.code == STORE_FAST); - SKIP_OVER(1); - #endif + res = PyStackRef_FromPyObjectSteal(temp); + *target_local = PyStackRef_NULL; } + stack_pointer[-2] = res; + stack_pointer += -1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); DISPATCH(); } @@ -3250,6 +3246,7 @@ _PyStackRef nos; _PyStackRef self; _PyStackRef arg; + _PyStackRef none; _PyStackRef c; _PyStackRef s; _PyStackRef value; @@ -3304,17 +3301,14 @@ } c = callable; s = self; - #if TIER_ONE - - assert(next_instr->op.code == POP_TOP); - SKIP_OVER(1); - #endif + none = PyStackRef_None; } // _POP_TOP { value = s; - stack_pointer[-3] = c; - stack_pointer += -2; + stack_pointer[-3] = none; + stack_pointer[-2] = c; + stack_pointer += -1; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); PyStackRef_XCLOSE(value); @@ -11425,15 +11419,7 @@ DISPATCH(); } _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; - if ((_tstate->jit_tracer_state.prev_state.instr->op.code == CALL_LIST_APPEND && - opcode == POP_TOP) || - (_tstate->jit_tracer_state.prev_state.instr->op.code == BINARY_OP_INPLACE_ADD_UNICODE && - opcode == STORE_FAST)) { - _tstate->jit_tracer_state.prev_state.instr_is_super = true; - } - else { - _tstate->jit_tracer_state.prev_state.instr = next_instr; - } + _tstate->jit_tracer_state.prev_state.instr = next_instr; PyObject *prev_code = PyStackRef_AsPyObjectBorrow(frame->f_executable); if (_tstate->jit_tracer_state.prev_state.instr_code != (PyCodeObject *)prev_code) { _PyFrame_SetStackPointer(frame, stack_pointer); diff --git a/Python/optimizer.c b/Python/optimizer.c index 0f8ddb4ba558d3..5e97f20f869efd 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -703,12 +703,6 @@ _PyJit_translate_single_bytecode_to_trace( } #endif - // Skip over super instructions. - if (_tstate->jit_tracer_state.prev_state.instr_is_super) { - _tstate->jit_tracer_state.prev_state.instr_is_super = false; - return 1; - } - if (opcode == ENTER_EXECUTOR) { goto full; } @@ -1077,7 +1071,6 @@ _PyJit_TryInitializeTracing( _tstate->jit_tracer_state.prev_state.instr_frame = frame; _tstate->jit_tracer_state.prev_state.instr_oparg = oparg; _tstate->jit_tracer_state.prev_state.instr_stacklevel = curr_stackdepth; - _tstate->jit_tracer_state.prev_state.instr_is_super = false; assert(curr_instr->op.code == JUMP_BACKWARD_JIT || (exit != NULL)); _tstate->jit_tracer_state.initial_state.jump_backward_instr = curr_instr; diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index c53a2fb7570c0d..1575b9e38424c5 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -299,14 +299,12 @@ dummy_func(void) { } op(_BINARY_OP_ADD_UNICODE, (left, right -- res, l, r)) { - REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); res = sym_new_type(ctx, &PyUnicode_Type); l = left; r = right; } - op(_BINARY_OP_INPLACE_ADD_UNICODE, (left, right --)) { - JitOptRef res; + op(_BINARY_OP_INPLACE_ADD_UNICODE, (left, right -- res)) { if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { assert(PyUnicode_CheckExact(sym_get_const(ctx, left))); assert(PyUnicode_CheckExact(sym_get_const(ctx, right))); @@ -320,8 +318,7 @@ dummy_func(void) { else { res = sym_new_type(ctx, &PyUnicode_Type); } - // _STORE_FAST: - GETLOCAL(this_instr->operand0) = res; + GETLOCAL(this_instr->operand0) = sym_new_null(ctx); } op(_BINARY_OP_SUBSCR_INIT_CALL, (container, sub, getitem -- new_frame)) { @@ -1043,10 +1040,11 @@ dummy_func(void) { sym_set_const(flag, Py_True); } - op(_CALL_LIST_APPEND, (callable, self, arg -- c, s)) { + op(_CALL_LIST_APPEND, (callable, self, arg -- none, c, s)) { (void)(arg); c = callable; s = self; + none = sym_new_const(ctx, Py_None); } op(_GUARD_IS_FALSE_POP, (flag -- )) { diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 9f1337e7ef3b92..a8b043fbc875d2 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -646,42 +646,6 @@ JitOptRef r; right = stack_pointer[-1]; left = stack_pointer[-2]; - if ( - sym_is_safe_const(ctx, left) && - sym_is_safe_const(ctx, right) - ) { - JitOptRef left_sym = left; - JitOptRef right_sym = right; - _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); - _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); - _PyStackRef res_stackref; - _PyStackRef l_stackref; - _PyStackRef r_stackref; - /* Start of uop copied from bytecodes for constant evaluation */ - PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); - PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); - assert(PyUnicode_CheckExact(left_o)); - assert(PyUnicode_CheckExact(right_o)); - STAT_INC(BINARY_OP, hit); - PyObject *res_o = PyUnicode_Concat(left_o, right_o); - res_stackref = PyStackRef_FromPyObjectSteal(res_o); - if (PyStackRef_IsNull(res)) { - JUMP_TO_LABEL(error); - } - l_stackref = left; - r_stackref = right; - /* End of uop copied from bytecodes for constant evaluation */ - res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); - l = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(l_stackref)); - r = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(r_stackref)); - CHECK_STACK_BOUNDS(1); - stack_pointer[-2] = res; - stack_pointer[-1] = l; - stack_pointer[0] = r; - stack_pointer += 1; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); - break; - } res = sym_new_type(ctx, &PyUnicode_Type); l = left; r = right; @@ -697,9 +661,9 @@ case _BINARY_OP_INPLACE_ADD_UNICODE: { JitOptRef right; JitOptRef left; + JitOptRef res; right = stack_pointer[-1]; left = stack_pointer[-2]; - JitOptRef res; if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { assert(PyUnicode_CheckExact(sym_get_const(ctx, left))); assert(PyUnicode_CheckExact(sym_get_const(ctx, right))); @@ -708,15 +672,18 @@ goto error; } res = sym_new_const(ctx, temp); + CHECK_STACK_BOUNDS(-1); + stack_pointer[-2] = res; + stack_pointer += -1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); Py_DECREF(temp); } else { res = sym_new_type(ctx, &PyUnicode_Type); + stack_pointer += -1; } - GETLOCAL(this_instr->operand0) = res; - CHECK_STACK_BOUNDS(-2); - stack_pointer += -2; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + GETLOCAL(this_instr->operand0) = sym_new_null(ctx); + stack_pointer[-1] = res; break; } @@ -2958,6 +2925,7 @@ JitOptRef arg; JitOptRef self; JitOptRef callable; + JitOptRef none; JitOptRef c; JitOptRef s; arg = stack_pointer[-1]; @@ -2966,11 +2934,10 @@ (void)(arg); c = callable; s = self; - CHECK_STACK_BOUNDS(-1); - stack_pointer[-3] = c; - stack_pointer[-2] = s; - stack_pointer += -1; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + none = sym_new_const(ctx, Py_None); + stack_pointer[-3] = none; + stack_pointer[-2] = c; + stack_pointer[-1] = s; break; } diff --git a/Python/specialize.c b/Python/specialize.c index 19433bc7a74319..e67078afdd9df3 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -1623,10 +1623,8 @@ specialize_method_descriptor(PyMethodDescrObject *descr, PyObject *self_or_null, } PyInterpreterState *interp = _PyInterpreterState_GET(); PyObject *list_append = interp->callable_cache.list_append; - _Py_CODEUNIT next = instr[INLINE_CACHE_ENTRIES_CALL + 1]; - bool pop = (next.op.code == POP_TOP); int oparg = instr->op.arg; - if ((PyObject *)descr == list_append && oparg == 1 && pop) { + if ((PyObject *)descr == list_append && oparg == 1) { assert(self_or_null != NULL); if (PyList_CheckExact(self_or_null)) { specialize(instr, CALL_LIST_APPEND); From 86d904588e8c84c7fccb8faf84b343f03461970d Mon Sep 17 00:00:00 2001 From: kaushal trivedi <155625932+Kaushalt2004@users.noreply.github.com> Date: Thu, 25 Dec 2025 14:43:39 +0530 Subject: [PATCH 036/220] gh-143004: Fix possible use-after-free in collections.Counter.update() (GH-143044) This happened when the Counter was mutated when incrementing the value for an existing key. --- Lib/test/test_collections.py | 13 +++++++++++++ ...5-12-22-00-00-00.gh-issue-143004.uaf-counter.rst | 2 ++ Modules/_collectionsmodule.c | 5 +++++ 3 files changed, 20 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-12-22-00-00-00.gh-issue-143004.uaf-counter.rst diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 22595239252814..fad639b20a1801 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -2135,6 +2135,19 @@ def test_basics(self): self.assertEqual(c.setdefault('e', 5), 5) self.assertEqual(c['e'], 5) + def test_update_reentrant_add_clears_counter(self): + c = Counter() + key = object() + + class Evil(int): + def __add__(self, other): + c.clear() + return NotImplemented + + c[key] = Evil() + c.update([key]) + self.assertEqual(c[key], 1) + def test_init(self): self.assertEqual(list(Counter(self=42).items()), [('self', 42)]) self.assertEqual(list(Counter(iterable=42).items()), [('iterable', 42)]) diff --git a/Misc/NEWS.d/next/Library/2025-12-22-00-00-00.gh-issue-143004.uaf-counter.rst b/Misc/NEWS.d/next/Library/2025-12-22-00-00-00.gh-issue-143004.uaf-counter.rst new file mode 100644 index 00000000000000..278066e9b706bf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-22-00-00-00.gh-issue-143004.uaf-counter.rst @@ -0,0 +1,2 @@ +Fix a potential use-after-free in :meth:`collections.Counter.update` when user code +mutates the Counter during an update. diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c index 3b14a21fa8428e..45ca63e6d7c77f 100644 --- a/Modules/_collectionsmodule.c +++ b/Modules/_collectionsmodule.c @@ -2577,7 +2577,12 @@ _collections__count_elements_impl(PyObject *module, PyObject *mapping, if (_PyDict_SetItem_KnownHash(mapping, key, one, hash) < 0) goto done; } else { + /* oldval is a borrowed reference. Keep it alive across + PyNumber_Add(), which can execute arbitrary user code and + mutate (or even clear) the underlying dict. */ + Py_INCREF(oldval); newval = PyNumber_Add(oldval, one); + Py_DECREF(oldval); if (newval == NULL) goto done; if (_PyDict_SetItem_KnownHash(mapping, key, newval, hash) < 0) From 8d46f961c30b795357df9a8cde479742562d8fc5 Mon Sep 17 00:00:00 2001 From: Hauke D Date: Thu, 25 Dec 2025 12:34:44 +0100 Subject: [PATCH 037/220] gh-143103: Added pad parameter to base64.z85encode() (GH-143106) This makes it analogous to a85encode() and b85encode() and allows the user to more easily meet the Z85 specification, which requires input lengths to be a multiple of 4. --- Doc/library/base64.rst | 8 +++++++- Lib/base64.py | 4 ++-- Lib/test/test_base64.py | 16 ++++++++++++++++ Misc/ACKS | 1 + ...025-12-23-17-07-22.gh-issue-143103.LRjXEW.rst | 1 + 5 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-12-23-17-07-22.gh-issue-143103.LRjXEW.rst diff --git a/Doc/library/base64.rst b/Doc/library/base64.rst index 529a7242443820..2d901824335145 100644 --- a/Doc/library/base64.rst +++ b/Doc/library/base64.rst @@ -267,14 +267,20 @@ Refer to the documentation of the individual functions for more information. .. versionadded:: 3.4 -.. function:: z85encode(s) +.. function:: z85encode(s, pad=False) Encode the :term:`bytes-like object` *s* using Z85 (as used in ZeroMQ) and return the encoded :class:`bytes`. See `Z85 specification `_ for more information. + If *pad* is true, the input is padded with ``b'\0'`` so its length is a + multiple of 4 bytes before encoding. + .. versionadded:: 3.13 + .. versionchanged:: next + The *pad* parameter was added. + .. function:: z85decode(s) diff --git a/Lib/base64.py b/Lib/base64.py index 341bf8eaf1891e..c2fdee8eab9690 100644 --- a/Lib/base64.py +++ b/Lib/base64.py @@ -508,9 +508,9 @@ def b85decode(b): ) _z85_encode_translation = bytes.maketrans(_b85alphabet, _z85alphabet) -def z85encode(s): +def z85encode(s, pad=False): """Encode bytes-like object b in z85 format and return a bytes object.""" - return b85encode(s).translate(_z85_encode_translation) + return b85encode(s, pad).translate(_z85_encode_translation) def z85decode(s): """Decode the z85-encoded bytes-like object or ASCII string b diff --git a/Lib/test/test_base64.py b/Lib/test/test_base64.py index ac3f09405457df..288caf663e8321 100644 --- a/Lib/test/test_base64.py +++ b/Lib/test/test_base64.py @@ -665,6 +665,7 @@ def test_z85encode(self): tests = { b'': b'', + b'\x86\x4F\xD2\x6F\xB5\x59\xF7\x5B': b'HelloWorld', b'www.python.org': b'CxXl-AcVLsz/dgCA+t', bytes(range(255)): b"""009c61o!#m2NH?C3>iWS5d]J*6CRx17-skh9337x""" b"""ar.{NbQB=+c[cR@eg&FcfFLssg=mfIi5%2YjuU>)kTv.7l}6Nnnj=AD""" @@ -840,6 +841,21 @@ def test_b85_padding(self): eq(base64.b85decode(b'czAet'), b"xxxx") eq(base64.b85decode(b'czAetcmMzZ'), b"xxxxx\x00\x00\x00") + def test_z85_padding(self): + eq = self.assertEqual + + eq(base64.z85encode(b"x", pad=True), b'CMmZz') + eq(base64.z85encode(b"xx", pad=True), b'CZ6h*') + eq(base64.z85encode(b"xxx", pad=True), b'CZaDk') + eq(base64.z85encode(b"xxxx", pad=True), b'CZaET') + eq(base64.z85encode(b"xxxxx", pad=True), b'CZaETCMmZz') + + eq(base64.z85decode(b'CMmZz'), b"x\x00\x00\x00") + eq(base64.z85decode(b'CZ6h*'), b"xx\x00\x00") + eq(base64.z85decode(b'CZaDk'), b"xxx\x00") + eq(base64.z85decode(b'CZaET'), b"xxxx") + eq(base64.z85decode(b'CZaETCMmZz'), b"xxxxx\x00\x00\x00") + def test_a85decode_errors(self): illegal = (set(range(32)) | set(range(118, 256))) - set(b' \t\n\r\v') for c in illegal: diff --git a/Misc/ACKS b/Misc/ACKS index a14089a39cce82..bb6b6bde822a4e 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -418,6 +418,7 @@ Lisandro Dalcin Darren Dale Andrew Dalke Lars Damerow +Hauke Dämpfling Evan Dandrea Eric Daniel Scott David Daniels diff --git a/Misc/NEWS.d/next/Library/2025-12-23-17-07-22.gh-issue-143103.LRjXEW.rst b/Misc/NEWS.d/next/Library/2025-12-23-17-07-22.gh-issue-143103.LRjXEW.rst new file mode 100644 index 00000000000000..b00c03707ca352 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-23-17-07-22.gh-issue-143103.LRjXEW.rst @@ -0,0 +1 @@ +Add padding support to :func:`base64.z85encode` via the ``pad`` parameter. From 579c5b496b467a2b175cb30caa4f6873cb13c9a1 Mon Sep 17 00:00:00 2001 From: Yongtao Huang Date: Thu, 25 Dec 2025 22:24:25 +0800 Subject: [PATCH 038/220] gh-143145: Fix possible reference leak in ctypes _build_result() (GH-143131) The result tuple was leaked if __ctypes_from_outparam__() failed for any item. Signed-off-by: Yongtao Huang --- .../next/Library/2025-12-24-14-18-52.gh-issue-143145.eXLw8D.rst | 1 + Modules/_ctypes/_ctypes.c | 1 + 2 files changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-12-24-14-18-52.gh-issue-143145.eXLw8D.rst diff --git a/Misc/NEWS.d/next/Library/2025-12-24-14-18-52.gh-issue-143145.eXLw8D.rst b/Misc/NEWS.d/next/Library/2025-12-24-14-18-52.gh-issue-143145.eXLw8D.rst new file mode 100644 index 00000000000000..2aff1090b1812f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-24-14-18-52.gh-issue-143145.eXLw8D.rst @@ -0,0 +1 @@ +Fixed a possible reference leak in ctypes when constructing results with multiple output parameters on error. diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 774ac71ce9ec56..563e95a762599b 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -4557,6 +4557,7 @@ _build_result(PyObject *result, PyObject *callargs, v = PyTuple_GET_ITEM(callargs, i); v = PyObject_CallMethodNoArgs(v, &_Py_ID(__ctypes_from_outparam__)); if (v == NULL || numretvals == 1) { + Py_XDECREF(tup); Py_DECREF(callargs); return v; } From 8611f74e089d9ac9de84dd42be9d251db27889aa Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 25 Dec 2025 11:31:41 -0500 Subject: [PATCH 039/220] gh-142975: During GC, mark frozen objects with a merged zero refcount for destruction (GH-143156) --- Lib/test/test_free_threading/test_gc.py | 32 +++++++++++++++++++ ...-12-24-13-44-24.gh-issue-142975.8C4vIP.rst | 2 ++ Python/gc_free_threading.c | 6 +++- 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-12-24-13-44-24.gh-issue-142975.8C4vIP.rst diff --git a/Lib/test/test_free_threading/test_gc.py b/Lib/test/test_free_threading/test_gc.py index 3b83e0431efa6b..8b45b6e2150c28 100644 --- a/Lib/test/test_free_threading/test_gc.py +++ b/Lib/test/test_free_threading/test_gc.py @@ -62,6 +62,38 @@ def mutator_thread(): with threading_helper.start_threads(gcs + mutators): pass + def test_freeze_object_in_brc_queue(self): + # GH-142975: Freezing objects in the BRC queue could result in some + # objects having a zero refcount without being deallocated. + + class Weird: + # We need a destructor to trigger the check for object resurrection + def __del__(self): + pass + + # This is owned by the main thread, so the subthread will have to increment + # this object's reference count. + weird = Weird() + + def evil(): + gc.freeze() + + # Decrement the reference count from this thread, which will trigger the + # slow path during resurrection and add our weird object to the BRC queue. + nonlocal weird + del weird + + # Collection will merge the object's reference count and make it zero. + gc.collect() + + # Unfreeze the object, making it visible to the GC. + gc.unfreeze() + gc.collect() + + thread = Thread(target=evil) + thread.start() + thread.join() + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-24-13-44-24.gh-issue-142975.8C4vIP.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-24-13-44-24.gh-issue-142975.8C4vIP.rst new file mode 100644 index 00000000000000..9d7f57ee60aa47 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-24-13-44-24.gh-issue-142975.8C4vIP.rst @@ -0,0 +1,2 @@ +Fix crash after unfreezing all objects tracked by the garbage collector on +the :term:`free threaded ` build. diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 04b9b8f3f85603..51261cea0cfe2c 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -906,7 +906,11 @@ gc_visit_thread_stacks_mark_alive(PyInterpreterState *interp, gc_mark_args_t *ar static void queue_untracked_obj_decref(PyObject *op, struct collection_state *state) { - if (!_PyObject_GC_IS_TRACKED(op)) { + assert(Py_REFCNT(op) == 0); + // gh-142975: We have to treat frozen objects as untracked in this function + // or else they might be picked up in a future collection, which breaks the + // assumption that all incoming objects have a non-zero reference count. + if (!_PyObject_GC_IS_TRACKED(op) || gc_is_frozen(op)) { // GC objects with zero refcount are handled subsequently by the // GC as if they were cyclic trash, but we have to handle dead // non-GC objects here. Add one to the refcount so that we can From b9a48064306229287d7211e9510f578065e457fc Mon Sep 17 00:00:00 2001 From: Yongtao Huang Date: Fri, 26 Dec 2025 01:08:43 +0800 Subject: [PATCH 040/220] gh-143164: Fix incorrect error message for ctypes bitfield overflow (GH-143165) Signed-off-by: Yongtao Huang --- Lib/test/test_ctypes/test_struct_fields.py | 15 +++++++++++++++ ...2025-12-25-08-58-55.gh-issue-142164.XrFztf.rst | 1 + Modules/_ctypes/cfield.c | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-12-25-08-58-55.gh-issue-142164.XrFztf.rst diff --git a/Lib/test/test_ctypes/test_struct_fields.py b/Lib/test/test_ctypes/test_struct_fields.py index b50bbcbb65c423..dc26e26d8a9fb1 100644 --- a/Lib/test/test_ctypes/test_struct_fields.py +++ b/Lib/test/test_ctypes/test_struct_fields.py @@ -130,6 +130,21 @@ class S(Structure): self.check_struct(S) self.assertEqual(S.largeField.bit_size, size * 8) + def test_bitfield_overflow_error_message(self): + with self.assertRaisesRegex( + ValueError, + r"bit field 'x' overflows its type \(2 \+ 7 > 8\)", + ): + CField( + name="x", + type=c_byte, + byte_size=1, + byte_offset=0, + index=0, + _internal_use=True, + bit_size=7, + bit_offset=2, + ) # __set__ and __get__ should raise a TypeError in case their self # argument is not a ctype instance. diff --git a/Misc/NEWS.d/next/Library/2025-12-25-08-58-55.gh-issue-142164.XrFztf.rst b/Misc/NEWS.d/next/Library/2025-12-25-08-58-55.gh-issue-142164.XrFztf.rst new file mode 100644 index 00000000000000..e75270b9e94c03 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-25-08-58-55.gh-issue-142164.XrFztf.rst @@ -0,0 +1 @@ +Fix the ctypes bitfield overflow error message to report the correct offset and size calculation. diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c index 547e2471a1cbc0..4ebca0e0b3db0a 100644 --- a/Modules/_ctypes/cfield.c +++ b/Modules/_ctypes/cfield.c @@ -160,8 +160,8 @@ PyCField_new_impl(PyTypeObject *type, PyObject *name, PyObject *proto, if ((bitfield_size + bit_offset) > byte_size * 8) { PyErr_Format( PyExc_ValueError, - "bit field %R overflows its type (%zd + %zd >= %zd)", - name, bit_offset, byte_size*8); + "bit field %R overflows its type (%zd + %zd > %zd)", + name, bit_offset, bitfield_size, byte_size * 8); goto error; } } From 59ede34c8c4b45d8ede5416e34d88db172fbea75 Mon Sep 17 00:00:00 2001 From: ivonastojanovic <80911834+ivonastojanovic@users.noreply.github.com> Date: Thu, 25 Dec 2025 19:22:54 +0100 Subject: [PATCH 041/220] gh-138122: Convert GIL/GC/exception stats from tiles to progress bars (#143177) --- .../_flamegraph_assets/flamegraph.css | 97 ++++++++----------- .../sampling/_flamegraph_assets/flamegraph.js | 24 ++++- .../flamegraph_template.html | 79 +++++++++------ 3 files changed, 110 insertions(+), 90 deletions(-) diff --git a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.css b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.css index 03eb2274d23e68..e8fda417428104 100644 --- a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.css +++ b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.css @@ -405,21 +405,18 @@ body.resizing-sidebar { text-overflow: ellipsis; } -/* Efficiency Bar */ -.efficiency-section { - margin-top: 10px; - padding-top: 10px; - border-top: 1px solid var(--border); -} +/* -------------------------------------------------------------------------- + Progress Bars + -------------------------------------------------------------------------- */ -.efficiency-header { +.bar-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; } -.efficiency-label { +.bar-label { font-size: 9px; font-weight: 600; color: var(--text-secondary); @@ -427,21 +424,21 @@ body.resizing-sidebar { letter-spacing: 0.2px; } -.efficiency-value { +.bar-value { font-family: var(--font-mono); font-size: 11px; font-weight: 700; color: var(--accent); } -.efficiency-bar { +.bar { height: 6px; background: var(--bg-tertiary); border-radius: 3px; overflow: hidden; } -.efficiency-fill { +.bar-fill { height: 100%; background: linear-gradient(90deg, #28a745 0%, #20c997 50%, #17a2b8 100%); border-radius: 3px; @@ -450,7 +447,7 @@ body.resizing-sidebar { overflow: hidden; } -.efficiency-fill::after { +.bar-fill::after { content: ''; position: absolute; top: 0; @@ -467,68 +464,56 @@ body.resizing-sidebar { } /* -------------------------------------------------------------------------- - Thread Stats Grid (in Sidebar) + Efficiency Section Container + -------------------------------------------------------------------------- */ + +.efficiency-section { + margin-top: 10px; + padding-top: 10px; + border-top: 1px solid var(--border); +} + +/* -------------------------------------------------------------------------- + Thread Stats Progress Bars (in Sidebar) -------------------------------------------------------------------------- */ .thread-stats-section { display: block; } -.stats-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 8px; +.stats-container { + display: flex; + flex-direction: column; + gap: 10px; } -.stat-tile { - background: var(--bg-primary); - border-radius: 8px; - padding: 10px; - text-align: center; - border: 2px solid var(--border); - transition: all var(--transition-fast); +.stat-item { animation: fadeIn 0.4s ease-out backwards; animation-delay: calc(var(--i, 0) * 0.05s); } -.stat-tile:nth-child(1) { --i: 0; } -.stat-tile:nth-child(2) { --i: 1; } -.stat-tile:nth-child(3) { --i: 2; } -.stat-tile:nth-child(4) { --i: 3; } +.stat-item:nth-child(1) { --i: 0; } +.stat-item:nth-child(2) { --i: 1; } +.stat-item:nth-child(3) { --i: 2; } +.stat-item:nth-child(4) { --i: 3; } +.stat-item:nth-child(5) { --i: 4; } -.stat-tile:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-sm); +/* Color variants for bar-fill */ +.bar-fill--green { + background: linear-gradient(90deg, #28a745 0%, #20c997 100%); } -.stat-tile-value { - font-family: var(--font-mono); - font-size: 16px; - font-weight: 700; - color: var(--text-primary); - line-height: 1.2; +.bar-fill--yellow { + background: linear-gradient(90deg, #ffc107 0%, #ffdb4d 100%); } -.stat-tile-label { - font-size: 9px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.3px; - color: var(--text-muted); - margin-top: 2px; +.bar-fill--purple { + background: linear-gradient(90deg, #6f42c1 0%, #9b6dd6 100%); } -/* Stat tile color variants */ -.stat-tile--green { --tile-color: 40, 167, 69; --tile-text: #28a745; } -.stat-tile--red { --tile-color: 220, 53, 69; --tile-text: #dc3545; } -.stat-tile--yellow { --tile-color: 255, 193, 7; --tile-text: #d39e00; } -.stat-tile--purple { --tile-color: 111, 66, 193; --tile-text: #6f42c1; } - -.stat-tile[class*="--"] { - border-color: rgba(var(--tile-color), 0.4); - background: linear-gradient(135deg, rgba(var(--tile-color), 0.08) 0%, var(--bg-primary) 100%); +.bar-fill--red { + background: linear-gradient(90deg, #dc3545 0%, #ff6b7a 100%); } -.stat-tile[class*="--"] .stat-tile-value { color: var(--tile-text); } /* -------------------------------------------------------------------------- Hotspot Cards @@ -985,10 +970,6 @@ body.resizing-sidebar { .brand-info { display: none; } - - .stats-grid { - grid-template-columns: 1fr; - } } /* -------------------------------------------------------------------------- diff --git a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js index 17fd95af859587..1a51802ffefac7 100644 --- a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js +++ b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js @@ -742,24 +742,38 @@ function populateThreadStats(data, selectedThreadId = null) { if (gilReleasedStat) gilReleasedStat.style.display = 'block'; if (gilWaitingStat) gilWaitingStat.style.display = 'block'; + const gilHeldPct = threadStats.has_gil_pct || 0; const gilHeldPctElem = document.getElementById('gil-held-pct'); - if (gilHeldPctElem) gilHeldPctElem.textContent = `${(threadStats.has_gil_pct || 0).toFixed(1)}%`; + if (gilHeldPctElem) gilHeldPctElem.textContent = `${gilHeldPct.toFixed(1)}%`; + const gilHeldFill = document.getElementById('gil-held-fill'); + if (gilHeldFill) gilHeldFill.style.width = `${gilHeldPct}%`; - const gilReleasedPctElem = document.getElementById('gil-released-pct'); // GIL Released = not holding GIL and not waiting for it const gilReleasedPct = Math.max(0, 100 - (threadStats.has_gil_pct || 0) - (threadStats.gil_requested_pct || 0)); + const gilReleasedPctElem = document.getElementById('gil-released-pct'); if (gilReleasedPctElem) gilReleasedPctElem.textContent = `${gilReleasedPct.toFixed(1)}%`; + const gilReleasedFill = document.getElementById('gil-released-fill'); + if (gilReleasedFill) gilReleasedFill.style.width = `${gilReleasedPct}%`; + const gilWaitingPct = threadStats.gil_requested_pct || 0; const gilWaitingPctElem = document.getElementById('gil-waiting-pct'); - if (gilWaitingPctElem) gilWaitingPctElem.textContent = `${(threadStats.gil_requested_pct || 0).toFixed(1)}%`; + if (gilWaitingPctElem) gilWaitingPctElem.textContent = `${gilWaitingPct.toFixed(1)}%`; + const gilWaitingFill = document.getElementById('gil-waiting-fill'); + if (gilWaitingFill) gilWaitingFill.style.width = `${gilWaitingPct}%`; } + const gcPct = threadStats.gc_pct || 0; const gcPctElem = document.getElementById('gc-pct'); - if (gcPctElem) gcPctElem.textContent = `${(threadStats.gc_pct || 0).toFixed(1)}%`; + if (gcPctElem) gcPctElem.textContent = `${gcPct.toFixed(1)}%`; + const gcFill = document.getElementById('gc-fill'); + if (gcFill) gcFill.style.width = `${gcPct}%`; // Exception stats + const excPct = threadStats.has_exception_pct || 0; const excPctElem = document.getElementById('exc-pct'); - if (excPctElem) excPctElem.textContent = `${(threadStats.has_exception_pct || 0).toFixed(1)}%`; + if (excPctElem) excPctElem.textContent = `${excPct.toFixed(1)}%`; + const excFill = document.getElementById('exc-fill'); + if (excFill) excFill.style.width = `${excPct}%`; } // ============================================================================ diff --git a/Lib/profiling/sampling/_flamegraph_assets/flamegraph_template.html b/Lib/profiling/sampling/_flamegraph_assets/flamegraph_template.html index 936c9adfc8c519..195a555d68e98b 100644 --- a/Lib/profiling/sampling/_flamegraph_assets/flamegraph_template.html +++ b/Lib/profiling/sampling/_flamegraph_assets/flamegraph_template.html @@ -159,21 +159,21 @@

Profile Summary