From 5ecfd750b4f511f270c38f0d748da9cffa279295 Mon Sep 17 00:00:00 2001 From: Skip Montanaro Date: Sun, 28 Jan 2024 08:51:25 -0600 Subject: [PATCH 001/507] Correction Skip Montanaro's email address (#114677) --- Doc/library/atexit.rst | 4 ++-- Doc/library/csv.rst | 2 +- Doc/library/readline.rst | 2 +- Doc/library/urllib.robotparser.rst | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/library/atexit.rst b/Doc/library/atexit.rst index 3dbef69580d9b30..43a8bd2d7cd1339 100644 --- a/Doc/library/atexit.rst +++ b/Doc/library/atexit.rst @@ -4,8 +4,8 @@ .. module:: atexit :synopsis: Register and execute cleanup functions. -.. moduleauthor:: Skip Montanaro -.. sectionauthor:: Skip Montanaro +.. moduleauthor:: Skip Montanaro +.. sectionauthor:: Skip Montanaro -------------- diff --git a/Doc/library/csv.rst b/Doc/library/csv.rst index 7a5589e68b30525..07f38f5690bb540 100644 --- a/Doc/library/csv.rst +++ b/Doc/library/csv.rst @@ -4,7 +4,7 @@ .. module:: csv :synopsis: Write and read tabular data to and from delimited files. -.. sectionauthor:: Skip Montanaro +.. sectionauthor:: Skip Montanaro **Source code:** :source:`Lib/csv.py` diff --git a/Doc/library/readline.rst b/Doc/library/readline.rst index 1adafcaa02eab97..54c6d9f3b32b1aa 100644 --- a/Doc/library/readline.rst +++ b/Doc/library/readline.rst @@ -5,7 +5,7 @@ :platform: Unix :synopsis: GNU readline support for Python. -.. sectionauthor:: Skip Montanaro +.. sectionauthor:: Skip Montanaro -------------- diff --git a/Doc/library/urllib.robotparser.rst b/Doc/library/urllib.robotparser.rst index f063e463753e0b8..b5a49d9c5923876 100644 --- a/Doc/library/urllib.robotparser.rst +++ b/Doc/library/urllib.robotparser.rst @@ -5,7 +5,7 @@ :synopsis: Load a robots.txt file and answer questions about fetchability of other URLs. -.. sectionauthor:: Skip Montanaro +.. sectionauthor:: Skip Montanaro **Source code:** :source:`Lib/urllib/robotparser.py` From d00fbed68ffcd5823acbb32a0e47e2e5f9732ff7 Mon Sep 17 00:00:00 2001 From: Bhushan Mohanraj <50306448+bhushan-mohanraj@users.noreply.github.com> Date: Sun, 28 Jan 2024 15:10:32 -0500 Subject: [PATCH 002/507] Fix indentation in `__post_init__` documentation. (gh-114666) --- Doc/library/dataclasses.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/dataclasses.rst b/Doc/library/dataclasses.rst index 88f2e0251b1e519..4ada69d63abada1 100644 --- a/Doc/library/dataclasses.rst +++ b/Doc/library/dataclasses.rst @@ -538,8 +538,8 @@ that has to be called, it is common to call this method in a class Rectangle: def __init__(self, height, width): - self.height = height - self.width = width + self.height = height + self.width = width @dataclass class Square(Rectangle): From 3bb6912d8832e6e0a98c74de360dc1b23906c4b3 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 28 Jan 2024 22:28:25 +0200 Subject: [PATCH 003/507] gh-100734: Add 'Notable change in 3.11.x' to `whatsnew/3.11.rst` (#114657) Co-authored-by: Serhiy Storchaka --- Doc/whatsnew/3.11.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index a8a7dfda0c5309b..4f4c1de8d8d5964 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -2701,4 +2701,30 @@ Removed (Contributed by Inada Naoki in :issue:`44029`.) +Notable changes in 3.11.4 +========================= + +tarfile +------- + +* The extraction methods in :mod:`tarfile`, and :func:`shutil.unpack_archive`, + have a new a *filter* argument that allows limiting tar features than may be + surprising or dangerous, such as creating files outside the destination + directory. + See :ref:`tarfile-extraction-filter` for details. + In Python 3.12, use without the *filter* argument will show a + :exc:`DeprecationWarning`. + In Python 3.14, the default will switch to ``'data'``. + (Contributed by Petr Viktorin in :pep:`706`.) + + +Notable changes in 3.11.5 +========================= + +OpenSSL +------- + +* Windows builds and macOS installers from python.org now use OpenSSL 3.0. + + .. _libb2: https://www.blake2.net/ From f7c05d7ad3075a1dbeed86b6b12903032e4afba6 Mon Sep 17 00:00:00 2001 From: Furkan Onder Date: Mon, 29 Jan 2024 02:05:29 +0300 Subject: [PATCH 004/507] gh-55664: Add warning when creating a type using a namespace dictionary with non-string keys. (GH-105338) Co-authored-by: Daniel Urban --- Lib/test/test_descr.py | 17 ++++++++++++++++- ...023-06-06-19-09-00.gh-issue-55664.vYYl0V.rst | 1 + Objects/typeobject.c | 11 +++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-06-06-19-09-00.gh-issue-55664.vYYl0V.rst diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index fd0af9b30a0a71a..beeab6cb7f254c1 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -4734,6 +4734,20 @@ class X(object): with self.assertRaises(AttributeError): del X.__abstractmethods__ + def test_gh55664(self): + # gh-55664: issue a warning when the + # __dict__ of a class contains non-string keys + with self.assertWarnsRegex(RuntimeWarning, 'MyClass'): + MyClass = type('MyClass', (), {1: 2}) + + class meta(type): + def __new__(mcls, name, bases, ns): + ns[1] = 2 + return super().__new__(mcls, name, bases, ns) + + with self.assertWarnsRegex(RuntimeWarning, 'MyClass'): + MyClass = meta('MyClass', (), {}) + def test_proxy_call(self): class FakeStr: __class__ = str @@ -5151,7 +5165,8 @@ class Base2(object): mykey = 'from Base2' mykey2 = 'from Base2' - X = type('X', (Base,), {MyKey(): 5}) + with self.assertWarnsRegex(RuntimeWarning, 'X'): + X = type('X', (Base,), {MyKey(): 5}) # mykey is read from Base self.assertEqual(X.mykey, 'from Base') # mykey2 is read from Base2 because MyKey.__eq__ has set __bases__ diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-06-06-19-09-00.gh-issue-55664.vYYl0V.rst b/Misc/NEWS.d/next/Core and Builtins/2023-06-06-19-09-00.gh-issue-55664.vYYl0V.rst new file mode 100644 index 000000000000000..438be9854966501 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-06-06-19-09-00.gh-issue-55664.vYYl0V.rst @@ -0,0 +1 @@ +Add warning when creating :class:`type` using a namespace dictionary with non-string keys. Patched by Daniel Urban and Furkan Onder. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 114cf21f95e744b..a850473cad813dd 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -3828,6 +3828,17 @@ type_new_impl(type_new_ctx *ctx) // Put the proper slots in place fixup_slot_dispatchers(type); + if (!_PyDict_HasOnlyStringKeys(type->tp_dict)) { + if (PyErr_WarnFormat( + PyExc_RuntimeWarning, + 1, + "non-string key in the __dict__ of class %.200s", + type->tp_name) == -1) + { + goto error; + } + } + if (type_new_set_names(type) < 0) { goto error; } From f6d9e5926b6138994eaa60d1c36462e36105733d Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Sun, 28 Jan 2024 18:48:48 -0800 Subject: [PATCH 005/507] GH-113464: Add a JIT backend for tier 2 (GH-113465) Add an option (--enable-experimental-jit for configure-based builds or --experimental-jit for PCbuild-based ones) to build an *experimental* just-in-time compiler, based on copy-and-patch (https://fredrikbk.com/publications/copy-and-patch.pdf). See Tools/jit/README.md for more information on how to install the required build-time tooling. --- .github/workflows/jit.yml | 112 +++++ .github/workflows/mypy.yml | 2 + .gitignore | 1 + Include/cpython/optimizer.h | 2 + Include/internal/pycore_jit.h | 25 ++ Include/internal/pycore_object.h | 4 +- Makefile.pre.in | 11 +- ...-12-24-03-25-28.gh-issue-113464.dvjQmA.rst | 4 + PCbuild/_freeze_module.vcxproj | 1 + PCbuild/_freeze_module.vcxproj.filters | 3 + PCbuild/build.bat | 3 + PCbuild/pythoncore.vcxproj | 3 + PCbuild/pythoncore.vcxproj.filters | 6 + PCbuild/regen.targets | 23 +- Python/ceval.c | 20 +- Python/jit.c | 369 ++++++++++++++++ Python/optimizer.c | 12 + Python/pylifecycle.c | 7 + Tools/jit/README.md | 46 ++ Tools/jit/_llvm.py | 99 +++++ Tools/jit/_schema.py | 99 +++++ Tools/jit/_stencils.py | 220 ++++++++++ Tools/jit/_targets.py | 394 ++++++++++++++++++ Tools/jit/_writer.py | 95 +++++ Tools/jit/build.py | 28 ++ Tools/jit/mypy.ini | 5 + Tools/jit/template.c | 98 +++++ configure | 31 ++ configure.ac | 20 + 29 files changed, 1738 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/jit.yml create mode 100644 Include/internal/pycore_jit.h create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-24-03-25-28.gh-issue-113464.dvjQmA.rst create mode 100644 Python/jit.c create mode 100644 Tools/jit/README.md create mode 100644 Tools/jit/_llvm.py create mode 100644 Tools/jit/_schema.py create mode 100644 Tools/jit/_stencils.py create mode 100644 Tools/jit/_targets.py create mode 100644 Tools/jit/_writer.py create mode 100644 Tools/jit/build.py create mode 100644 Tools/jit/mypy.ini create mode 100644 Tools/jit/template.c diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml new file mode 100644 index 000000000000000..e137fd21b0a0dd7 --- /dev/null +++ b/.github/workflows/jit.yml @@ -0,0 +1,112 @@ +name: JIT +on: + pull_request: + paths: '**jit**' + push: + paths: '**jit**' + workflow_dispatch: +jobs: + jit: + name: ${{ matrix.target }} (${{ matrix.debug && 'Debug' || 'Release' }}) + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + target: + - i686-pc-windows-msvc/msvc + - x86_64-pc-windows-msvc/msvc + - x86_64-apple-darwin/clang + - x86_64-unknown-linux-gnu/gcc + - x86_64-unknown-linux-gnu/clang + - aarch64-unknown-linux-gnu/gcc + - aarch64-unknown-linux-gnu/clang + debug: + - true + - false + llvm: + - 16 + include: + - target: i686-pc-windows-msvc/msvc + architecture: Win32 + runner: windows-latest + compiler: msvc + - target: x86_64-pc-windows-msvc/msvc + architecture: x64 + runner: windows-latest + compiler: msvc + - target: x86_64-apple-darwin/clang + architecture: x86_64 + runner: macos-latest + compiler: clang + exclude: test_embed + - target: x86_64-unknown-linux-gnu/gcc + architecture: x86_64 + runner: ubuntu-latest + compiler: gcc + - target: x86_64-unknown-linux-gnu/clang + architecture: x86_64 + runner: ubuntu-latest + compiler: clang + - target: aarch64-unknown-linux-gnu/gcc + architecture: aarch64 + runner: ubuntu-latest + compiler: gcc + # These fail because of emulation, not because of the JIT: + exclude: test_unix_events test_init test_process_pool test_shutdown test_multiprocessing_fork test_cmd_line test_faulthandler test_os test_perf_profiler test_posix test_signal test_socket test_subprocess test_threading test_venv + - target: aarch64-unknown-linux-gnu/clang + architecture: aarch64 + runner: ubuntu-latest + compiler: clang + # These fail because of emulation, not because of the JIT: + exclude: test_unix_events test_init test_process_pool test_shutdown test_multiprocessing_fork test_cmd_line test_faulthandler test_os test_perf_profiler test_posix test_signal test_socket test_subprocess test_threading test_venv + env: + CC: ${{ matrix.compiler }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Windows + if: runner.os == 'Windows' + run: | + choco install llvm --allow-downgrade --no-progress --version ${{ matrix.llvm }} + ./PCbuild/build.bat --experimental-jit ${{ matrix.debug && '-d' || '--pgo' }} -p ${{ matrix.architecture }} + ./PCbuild/rt.bat ${{ matrix.debug && '-d' }} -p ${{ matrix.architecture }} -q --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3 + + - name: macOS + if: runner.os == 'macOS' + run: | + brew install llvm@${{ matrix.llvm }} + export SDKROOT="$(xcrun --show-sdk-path)" + ./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations --with-lto' }} + make all --jobs 3 + ./python.exe -m test --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3 + + - name: Native Linux + if: runner.os == 'Linux' && matrix.architecture == 'x86_64' + run: | + sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }} + export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH" + ./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations --with-lto' }} + make all --jobs 4 + ./python -m test --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3 + - name: Emulated Linux + if: runner.os == 'Linux' && matrix.architecture != 'x86_64' + run: | + sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }} + export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH" + ./configure --prefix="$(pwd)/../build" + make install --jobs 4 + make clean --jobs 4 + export HOST=${{ matrix.architecture }}-linux-gnu + sudo apt install --yes "gcc-$HOST" qemu-user + ${{ !matrix.debug && matrix.compiler == 'clang' && './configure --enable-optimizations' || '' }} + ${{ !matrix.debug && matrix.compiler == 'clang' && 'make profile-run-stamp --jobs 4' || '' }} + export CC="${{ matrix.compiler == 'clang' && 'clang --target=$HOST' || '$HOST-gcc' }}" + export CPP="$CC --preprocess" + export HOSTRUNNER=qemu-${{ matrix.architecture }} + export QEMU_LD_PREFIX="/usr/$HOST" + ./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations --with-lto' }} --build=x86_64-linux-gnu --host="$HOST" --with-build-python=../build/bin/python3 --with-pkg-config=no ac_cv_buggy_getaddrinfo=no ac_cv_file__dev_ptc=no ac_cv_file__dev_ptmx=yes + make all --jobs 4 + ./python -m test --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3 diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 11928e72b9b43a1..b766785de405d2e 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -12,6 +12,7 @@ on: - "Tools/build/generate_sbom.py" - "Tools/cases_generator/**" - "Tools/clinic/**" + - "Tools/jit/**" - "Tools/peg_generator/**" - "Tools/requirements-dev.txt" - "Tools/wasm/**" @@ -38,6 +39,7 @@ jobs: "Tools/build/", "Tools/cases_generator", "Tools/clinic", + "Tools/jit", "Tools/peg_generator", "Tools/wasm", ] diff --git a/.gitignore b/.gitignore index c424a894c2a6e09..18eb2a9f0632ce2 100644 --- a/.gitignore +++ b/.gitignore @@ -126,6 +126,7 @@ Tools/unicode/data/ # hendrikmuhs/ccache-action@v1 /.ccache /cross-build/ +/jit_stencils.h /platform /profile-clean-stamp /profile-run-stamp diff --git a/Include/cpython/optimizer.h b/Include/cpython/optimizer.h index 96e829f8fbe97db..ecf3cae4cbc3f10 100644 --- a/Include/cpython/optimizer.h +++ b/Include/cpython/optimizer.h @@ -39,6 +39,8 @@ typedef struct { typedef struct _PyExecutorObject { PyObject_VAR_HEAD _PyVMData vm_data; /* Used by the VM, but opaque to the optimizer */ + void *jit_code; + size_t jit_size; _PyUOpInstruction trace[1]; } _PyExecutorObject; diff --git a/Include/internal/pycore_jit.h b/Include/internal/pycore_jit.h new file mode 100644 index 000000000000000..0b71eb6f758ac62 --- /dev/null +++ b/Include/internal/pycore_jit.h @@ -0,0 +1,25 @@ +#ifndef Py_INTERNAL_JIT_H +#define Py_INTERNAL_JIT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef Py_BUILD_CORE +# error "this header requires Py_BUILD_CORE define" +#endif + +#ifdef _Py_JIT + +typedef _Py_CODEUNIT *(*jit_func)(_PyInterpreterFrame *frame, PyObject **stack_pointer, PyThreadState *tstate); + +int _PyJIT_Compile(_PyExecutorObject *executor, _PyUOpInstruction *trace, size_t length); +void _PyJIT_Free(_PyExecutorObject *executor); + +#endif // _Py_JIT + +#ifdef __cplusplus +} +#endif + +#endif // !Py_INTERNAL_JIT_H diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 4e52ffc77c5956b..e32ea2f528940ae 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -178,7 +178,7 @@ _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) } _Py_DECREF_STAT_INC(); #ifdef Py_REF_DEBUG - _Py_DEC_REFTOTAL(_PyInterpreterState_GET()); + _Py_DEC_REFTOTAL(PyInterpreterState_Get()); #endif if (--op->ob_refcnt != 0) { assert(op->ob_refcnt > 0); @@ -199,7 +199,7 @@ _Py_DECREF_NO_DEALLOC(PyObject *op) } _Py_DECREF_STAT_INC(); #ifdef Py_REF_DEBUG - _Py_DEC_REFTOTAL(_PyInterpreterState_GET()); + _Py_DEC_REFTOTAL(PyInterpreterState_Get()); #endif op->ob_refcnt--; #ifdef Py_DEBUG diff --git a/Makefile.pre.in b/Makefile.pre.in index 37a8b06987c7107..fff3d3c4914e7a7 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -433,6 +433,7 @@ PYTHON_OBJS= \ Python/initconfig.o \ Python/instrumentation.o \ Python/intrinsics.o \ + Python/jit.o \ Python/legacy_tracing.o \ Python/lock.o \ Python/marshal.o \ @@ -1365,7 +1366,7 @@ regen-unicodedata: regen-all: regen-cases regen-typeslots \ regen-token regen-ast regen-keyword regen-sre regen-frozen \ regen-pegen-metaparser regen-pegen regen-test-frozenmain \ - regen-test-levenshtein regen-global-objects regen-sbom + regen-test-levenshtein regen-global-objects regen-sbom regen-jit @echo @echo "Note: make regen-stdlib-module-names, make regen-limited-abi, " @echo "make regen-configure and make regen-unicodedata should be run manually" @@ -1846,6 +1847,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/internal/pycore_initconfig.h \ $(srcdir)/Include/internal/pycore_interp.h \ $(srcdir)/Include/internal/pycore_intrinsics.h \ + $(srcdir)/Include/internal/pycore_jit.h \ $(srcdir)/Include/internal/pycore_list.h \ $(srcdir)/Include/internal/pycore_llist.h \ $(srcdir)/Include/internal/pycore_lock.h \ @@ -2641,6 +2643,12 @@ config.status: $(srcdir)/configure Python/asm_trampoline.o: $(srcdir)/Python/asm_trampoline.S $(CC) -c $(PY_CORE_CFLAGS) -o $@ $< +Python/jit.o: regen-jit + +.PHONY: regen-jit +regen-jit: + @REGEN_JIT_COMMAND@ + # Some make's put the object file in the current directory .c.o: $(CC) -c $(PY_CORE_CFLAGS) -o $@ $< @@ -2733,6 +2741,7 @@ clean-retain-profile: pycremoval -rm -f Python/deepfreeze/*.[co] -rm -f Python/frozen_modules/*.h -rm -f Python/frozen_modules/MANIFEST + -rm -f jit_stencils.h -find build -type f -a ! -name '*.gc??' -exec rm -f {} ';' -rm -f Include/pydtrace_probes.h -rm -f profile-gen-stamp diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-24-03-25-28.gh-issue-113464.dvjQmA.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-24-03-25-28.gh-issue-113464.dvjQmA.rst new file mode 100644 index 000000000000000..bdee4d645f61c83 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-24-03-25-28.gh-issue-113464.dvjQmA.rst @@ -0,0 +1,4 @@ +Add an option (``--enable-experimental-jit`` for ``configure``-based builds +or ``--experimental-jit`` for ``PCbuild``-based ones) to build an +*experimental* just-in-time compiler, based on `copy-and-patch +`_ diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index dde801fc0fd525e..35788ec4503e8f6 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -224,6 +224,7 @@ + diff --git a/PCbuild/_freeze_module.vcxproj.filters b/PCbuild/_freeze_module.vcxproj.filters index 90ccb954b424bc1..7a44179e3561059 100644 --- a/PCbuild/_freeze_module.vcxproj.filters +++ b/PCbuild/_freeze_module.vcxproj.filters @@ -250,6 +250,9 @@ Source Files + + Source Files + Source Files diff --git a/PCbuild/build.bat b/PCbuild/build.bat index e61267b5852a8f2..83b50db44670337 100644 --- a/PCbuild/build.bat +++ b/PCbuild/build.bat @@ -36,6 +36,7 @@ echo. overrides -c and -d echo. --disable-gil Enable experimental support for running without the GIL. echo. --test-marker Enable the test marker within the build. echo. --regen Regenerate all opcodes, grammar and tokens. +echo. --experimental-jit Enable the experimental just-in-time compiler. echo. echo.Available flags to avoid building certain modules. echo.These flags have no effect if '-e' is not given: @@ -85,6 +86,7 @@ if "%~1"=="--disable-gil" (set UseDisableGil=true) & shift & goto CheckOpts if "%~1"=="--test-marker" (set UseTestMarker=true) & shift & goto CheckOpts if "%~1"=="-V" shift & goto Version if "%~1"=="--regen" (set Regen=true) & shift & goto CheckOpts +if "%~1"=="--experimental-jit" (set UseJIT=true) & shift & goto CheckOpts rem These use the actual property names used by MSBuild. We could just let rem them in through the environment, but we specify them on the command line rem anyway for visibility so set defaults after this @@ -176,6 +178,7 @@ echo on /p:IncludeSSL=%IncludeSSL% /p:IncludeTkinter=%IncludeTkinter%^ /p:DisableGil=%UseDisableGil%^ /p:UseTestMarker=%UseTestMarker% %GITProperty%^ + /p:UseJIT=%UseJIT%^ %1 %2 %3 %4 %5 %6 %7 %8 %9 @echo off diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index e0b9fc137457a08..e1ff97659659eea 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -104,6 +104,7 @@ $(zlibDir);%(AdditionalIncludeDirectories) _USRDLL;Py_BUILD_CORE;Py_BUILD_CORE_BUILTIN;Py_ENABLE_SHARED;MS_DLL_ID="$(SysWinVer)";%(PreprocessorDefinitions) _Py_HAVE_ZLIB;%(PreprocessorDefinitions) + _Py_JIT;%(PreprocessorDefinitions) version.lib;ws2_32.lib;pathcch.lib;bcrypt.lib;%(AdditionalDependencies) @@ -247,6 +248,7 @@ + @@ -585,6 +587,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index fd79436f5add970..4c55f23006b2f0c 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -669,6 +669,9 @@ Include\cpython + + Include\internal + Include\internal @@ -1337,6 +1340,9 @@ Source Files + + Python + Source Files diff --git a/PCbuild/regen.targets b/PCbuild/regen.targets index cc9469c7ddd726b..a90620d6ca8b7d1 100644 --- a/PCbuild/regen.targets +++ b/PCbuild/regen.targets @@ -28,6 +28,9 @@ <_KeywordSources Include="$(PySourcePath)Grammar\python.gram;$(PySourcePath)Grammar\Tokens" /> <_KeywordOutputs Include="$(PySourcePath)Lib\keyword.py" /> + + <_JITSources Include="$(PySourcePath)Python\executor_cases.c.h;$(GeneratedPyConfigDir)pyconfig.h;$(PySourcePath)Tools\jit\**"/> + <_JITOutputs Include="$(GeneratedPyConfigDir)jit_stencils.h"/> @@ -76,10 +79,28 @@ + + + + aarch64-pc-windows-msvc + i686-pc-windows-msvc + x86_64-pc-windows-msvc + $(JITArgs) --debug + + + - + + + diff --git a/Python/ceval.c b/Python/ceval.c index 49388cd20377c0c..4f2080090861914 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -11,6 +11,7 @@ #include "pycore_function.h" #include "pycore_instruments.h" #include "pycore_intrinsics.h" +#include "pycore_jit.h" #include "pycore_long.h" // _PyLong_GetZero() #include "pycore_moduleobject.h" // PyModuleObject #include "pycore_object.h" // _PyObject_GC_TRACK() @@ -955,9 +956,24 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int -// The Tier 2 interpreter is also here! +// Tier 2 is also here! enter_tier_two: +#ifdef _Py_JIT + + ; // ;) + jit_func jitted = current_executor->jit_code; + next_instr = jitted(frame, stack_pointer, tstate); + frame = tstate->current_frame; + Py_DECREF(current_executor); + if (next_instr == NULL) { + goto resume_with_error; + } + stack_pointer = _PyFrame_GetStackPointer(frame); + DISPATCH(); + +#else + #undef LOAD_IP #define LOAD_IP(UNUSED) (void)0 @@ -1073,6 +1089,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int Py_DECREF(current_executor); DISPATCH(); +#endif // _Py_JIT + } #if defined(__GNUC__) # pragma GCC diagnostic pop diff --git a/Python/jit.c b/Python/jit.c new file mode 100644 index 000000000000000..22949c082da05a2 --- /dev/null +++ b/Python/jit.c @@ -0,0 +1,369 @@ +#ifdef _Py_JIT + +#include "Python.h" + +#include "pycore_abstract.h" +#include "pycore_call.h" +#include "pycore_ceval.h" +#include "pycore_dict.h" +#include "pycore_intrinsics.h" +#include "pycore_long.h" +#include "pycore_opcode_metadata.h" +#include "pycore_opcode_utils.h" +#include "pycore_optimizer.h" +#include "pycore_pyerrors.h" +#include "pycore_setobject.h" +#include "pycore_sliceobject.h" +#include "pycore_jit.h" + +#include "jit_stencils.h" + +// Memory management stuff: //////////////////////////////////////////////////// + +#ifndef MS_WINDOWS + #include +#endif + +static size_t +get_page_size(void) +{ +#ifdef MS_WINDOWS + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwPageSize; +#else + return sysconf(_SC_PAGESIZE); +#endif +} + +static void +jit_error(const char *message) +{ +#ifdef MS_WINDOWS + int hint = GetLastError(); +#else + int hint = errno; +#endif + PyErr_Format(PyExc_RuntimeWarning, "JIT %s (%d)", message, hint); +} + +static char * +jit_alloc(size_t size) +{ + assert(size); + assert(size % get_page_size() == 0); +#ifdef MS_WINDOWS + int flags = MEM_COMMIT | MEM_RESERVE; + char *memory = VirtualAlloc(NULL, size, flags, PAGE_READWRITE); + int failed = memory == NULL; +#else + int flags = MAP_ANONYMOUS | MAP_PRIVATE; + char *memory = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, -1, 0); + int failed = memory == MAP_FAILED; +#endif + if (failed) { + jit_error("unable to allocate memory"); + return NULL; + } + return memory; +} + +static int +jit_free(char *memory, size_t size) +{ + assert(size); + assert(size % get_page_size() == 0); +#ifdef MS_WINDOWS + int failed = !VirtualFree(memory, 0, MEM_RELEASE); +#else + int failed = munmap(memory, size); +#endif + if (failed) { + jit_error("unable to free memory"); + return -1; + } + return 0; +} + +static int +mark_executable(char *memory, size_t size) +{ + if (size == 0) { + return 0; + } + assert(size % get_page_size() == 0); + // Do NOT ever leave the memory writable! Also, don't forget to flush the + // i-cache (I cannot begin to tell you how horrible that is to debug): +#ifdef MS_WINDOWS + if (!FlushInstructionCache(GetCurrentProcess(), memory, size)) { + jit_error("unable to flush instruction cache"); + return -1; + } + int old; + int failed = !VirtualProtect(memory, size, PAGE_EXECUTE_READ, &old); +#else + __builtin___clear_cache((char *)memory, (char *)memory + size); + int failed = mprotect(memory, size, PROT_EXEC | PROT_READ); +#endif + if (failed) { + jit_error("unable to protect executable memory"); + return -1; + } + return 0; +} + +static int +mark_readable(char *memory, size_t size) +{ + if (size == 0) { + return 0; + } + assert(size % get_page_size() == 0); +#ifdef MS_WINDOWS + DWORD old; + int failed = !VirtualProtect(memory, size, PAGE_READONLY, &old); +#else + int failed = mprotect(memory, size, PROT_READ); +#endif + if (failed) { + jit_error("unable to protect readable memory"); + return -1; + } + return 0; +} + +// JIT compiler stuff: ///////////////////////////////////////////////////////// + +// Warning! AArch64 requires you to get your hands dirty. These are your gloves: + +// value[value_start : value_start + len] +static uint32_t +get_bits(uint64_t value, uint8_t value_start, uint8_t width) +{ + assert(width <= 32); + return (value >> value_start) & ((1ULL << width) - 1); +} + +// *loc[loc_start : loc_start + width] = value[value_start : value_start + width] +static void +set_bits(uint32_t *loc, uint8_t loc_start, uint64_t value, uint8_t value_start, + uint8_t width) +{ + assert(loc_start + width <= 32); + // Clear the bits we're about to patch: + *loc &= ~(((1ULL << width) - 1) << loc_start); + assert(get_bits(*loc, loc_start, width) == 0); + // Patch the bits: + *loc |= get_bits(value, value_start, width) << loc_start; + assert(get_bits(*loc, loc_start, width) == get_bits(value, value_start, width)); +} + +// See https://developer.arm.com/documentation/ddi0602/2023-09/Base-Instructions +// for instruction encodings: +#define IS_AARCH64_ADD_OR_SUB(I) (((I) & 0x11C00000) == 0x11000000) +#define IS_AARCH64_ADRP(I) (((I) & 0x9F000000) == 0x90000000) +#define IS_AARCH64_BRANCH(I) (((I) & 0x7C000000) == 0x14000000) +#define IS_AARCH64_LDR_OR_STR(I) (((I) & 0x3B000000) == 0x39000000) +#define IS_AARCH64_MOV(I) (((I) & 0x9F800000) == 0x92800000) + +// Fill all of stencil's holes in the memory pointed to by base, using the +// values in patches. +static void +patch(char *base, const Stencil *stencil, uint64_t *patches) +{ + for (uint64_t i = 0; i < stencil->holes_size; i++) { + const Hole *hole = &stencil->holes[i]; + void *location = base + hole->offset; + uint64_t value = patches[hole->value] + (uint64_t)hole->symbol + hole->addend; + uint32_t *loc32 = (uint32_t *)location; + uint64_t *loc64 = (uint64_t *)location; + // LLD is a great reference for performing relocations... just keep in + // mind that Tools/jit/build.py does filtering and preprocessing for us! + // Here's a good place to start for each platform: + // - aarch64-apple-darwin: + // - https://github.com/llvm/llvm-project/blob/main/lld/MachO/Arch/ARM64Common.cpp + // - https://github.com/llvm/llvm-project/blob/main/lld/MachO/Arch/ARM64Common.h + // - aarch64-unknown-linux-gnu: + // - https://github.com/llvm/llvm-project/blob/main/lld/ELF/Arch/AArch64.cpp + // - i686-pc-windows-msvc: + // - https://github.com/llvm/llvm-project/blob/main/lld/COFF/Chunks.cpp + // - x86_64-apple-darwin: + // - https://github.com/llvm/llvm-project/blob/main/lld/MachO/Arch/X86_64.cpp + // - x86_64-pc-windows-msvc: + // - https://github.com/llvm/llvm-project/blob/main/lld/COFF/Chunks.cpp + // - x86_64-unknown-linux-gnu: + // - https://github.com/llvm/llvm-project/blob/main/lld/ELF/Arch/X86_64.cpp + switch (hole->kind) { + case HoleKind_IMAGE_REL_I386_DIR32: + // 32-bit absolute address. + // Check that we're not out of range of 32 unsigned bits: + assert(value < (1ULL << 32)); + *loc32 = (uint32_t)value; + continue; + case HoleKind_ARM64_RELOC_UNSIGNED: + case HoleKind_IMAGE_REL_AMD64_ADDR64: + case HoleKind_R_AARCH64_ABS64: + case HoleKind_X86_64_RELOC_UNSIGNED: + case HoleKind_R_X86_64_64: + // 64-bit absolute address. + *loc64 = value; + continue; + case HoleKind_R_AARCH64_CALL26: + case HoleKind_R_AARCH64_JUMP26: + // 28-bit relative branch. + assert(IS_AARCH64_BRANCH(*loc32)); + value -= (uint64_t)location; + // Check that we're not out of range of 28 signed bits: + assert((int64_t)value >= -(1 << 27)); + assert((int64_t)value < (1 << 27)); + // Since instructions are 4-byte aligned, only use 26 bits: + assert(get_bits(value, 0, 2) == 0); + set_bits(loc32, 0, value, 2, 26); + continue; + case HoleKind_R_AARCH64_MOVW_UABS_G0_NC: + // 16-bit low part of an absolute address. + assert(IS_AARCH64_MOV(*loc32)); + // Check the implicit shift (this is "part 0 of 3"): + assert(get_bits(*loc32, 21, 2) == 0); + set_bits(loc32, 5, value, 0, 16); + continue; + case HoleKind_R_AARCH64_MOVW_UABS_G1_NC: + // 16-bit middle-low part of an absolute address. + assert(IS_AARCH64_MOV(*loc32)); + // Check the implicit shift (this is "part 1 of 3"): + assert(get_bits(*loc32, 21, 2) == 1); + set_bits(loc32, 5, value, 16, 16); + continue; + case HoleKind_R_AARCH64_MOVW_UABS_G2_NC: + // 16-bit middle-high part of an absolute address. + assert(IS_AARCH64_MOV(*loc32)); + // Check the implicit shift (this is "part 2 of 3"): + assert(get_bits(*loc32, 21, 2) == 2); + set_bits(loc32, 5, value, 32, 16); + continue; + case HoleKind_R_AARCH64_MOVW_UABS_G3: + // 16-bit high part of an absolute address. + assert(IS_AARCH64_MOV(*loc32)); + // Check the implicit shift (this is "part 3 of 3"): + assert(get_bits(*loc32, 21, 2) == 3); + set_bits(loc32, 5, value, 48, 16); + continue; + case HoleKind_ARM64_RELOC_GOT_LOAD_PAGE21: + // 21-bit count of pages between this page and an absolute address's + // page... I know, I know, it's weird. Pairs nicely with + // ARM64_RELOC_GOT_LOAD_PAGEOFF12 (below). + assert(IS_AARCH64_ADRP(*loc32)); + // Number of pages between this page and the value's page: + value = (value >> 12) - ((uint64_t)location >> 12); + // Check that we're not out of range of 21 signed bits: + assert((int64_t)value >= -(1 << 20)); + assert((int64_t)value < (1 << 20)); + // value[0:2] goes in loc[29:31]: + set_bits(loc32, 29, value, 0, 2); + // value[2:21] goes in loc[5:26]: + set_bits(loc32, 5, value, 2, 19); + continue; + case HoleKind_ARM64_RELOC_GOT_LOAD_PAGEOFF12: + // 12-bit low part of an absolute address. Pairs nicely with + // ARM64_RELOC_GOT_LOAD_PAGE21 (above). + assert(IS_AARCH64_LDR_OR_STR(*loc32) || IS_AARCH64_ADD_OR_SUB(*loc32)); + // There might be an implicit shift encoded in the instruction: + uint8_t shift = 0; + if (IS_AARCH64_LDR_OR_STR(*loc32)) { + shift = (uint8_t)get_bits(*loc32, 30, 2); + // If both of these are set, the shift is supposed to be 4. + // That's pretty weird, and it's never actually been observed... + assert(get_bits(*loc32, 23, 1) == 0 || get_bits(*loc32, 26, 1) == 0); + } + value = get_bits(value, 0, 12); + assert(get_bits(value, 0, shift) == 0); + set_bits(loc32, 10, value, shift, 12); + continue; + } + Py_UNREACHABLE(); + } +} + +static void +copy_and_patch(char *base, const Stencil *stencil, uint64_t *patches) +{ + memcpy(base, stencil->body, stencil->body_size); + patch(base, stencil, patches); +} + +static void +emit(const StencilGroup *group, uint64_t patches[]) +{ + copy_and_patch((char *)patches[HoleValue_CODE], &group->code, patches); + copy_and_patch((char *)patches[HoleValue_DATA], &group->data, patches); +} + +// Compiles executor in-place. Don't forget to call _PyJIT_Free later! +int +_PyJIT_Compile(_PyExecutorObject *executor, _PyUOpInstruction *trace, size_t length) +{ + // Loop once to find the total compiled size: + size_t code_size = 0; + size_t data_size = 0; + for (size_t i = 0; i < length; i++) { + _PyUOpInstruction *instruction = &trace[i]; + const StencilGroup *group = &stencil_groups[instruction->opcode]; + code_size += group->code.body_size; + data_size += group->data.body_size; + } + // Round up to the nearest page (code and data need separate pages): + size_t page_size = get_page_size(); + assert((page_size & (page_size - 1)) == 0); + code_size += page_size - (code_size & (page_size - 1)); + data_size += page_size - (data_size & (page_size - 1)); + char *memory = jit_alloc(code_size + data_size); + if (memory == NULL) { + return -1; + } + // Loop again to emit the code: + char *code = memory; + char *data = memory + code_size; + for (size_t i = 0; i < length; i++) { + _PyUOpInstruction *instruction = &trace[i]; + const StencilGroup *group = &stencil_groups[instruction->opcode]; + // Think of patches as a dictionary mapping HoleValue to uint64_t: + uint64_t patches[] = GET_PATCHES(); + patches[HoleValue_CODE] = (uint64_t)code; + patches[HoleValue_CONTINUE] = (uint64_t)code + group->code.body_size; + patches[HoleValue_DATA] = (uint64_t)data; + patches[HoleValue_EXECUTOR] = (uint64_t)executor; + patches[HoleValue_OPARG] = instruction->oparg; + patches[HoleValue_OPERAND] = instruction->operand; + patches[HoleValue_TARGET] = instruction->target; + patches[HoleValue_TOP] = (uint64_t)memory; + patches[HoleValue_ZERO] = 0; + emit(group, patches); + code += group->code.body_size; + data += group->data.body_size; + } + if (mark_executable(memory, code_size) || + mark_readable(memory + code_size, data_size)) + { + jit_free(memory, code_size + data_size); + return -1; + } + executor->jit_code = memory; + executor->jit_size = code_size + data_size; + return 0; +} + +void +_PyJIT_Free(_PyExecutorObject *executor) +{ + char *memory = (char *)executor->jit_code; + size_t size = executor->jit_size; + if (memory) { + executor->jit_code = NULL; + executor->jit_size = 0; + if (jit_free(memory, size)) { + PyErr_WriteUnraisable(NULL); + } + } +} + +#endif // _Py_JIT diff --git a/Python/optimizer.c b/Python/optimizer.c index db615068ff517f6..0d04b09fef1e846 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -7,6 +7,7 @@ #include "pycore_optimizer.h" // _Py_uop_analyze_and_optimize() #include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_uop_ids.h" +#include "pycore_jit.h" #include "cpython/optimizer.h" #include #include @@ -227,6 +228,9 @@ static PyMethodDef executor_methods[] = { static void uop_dealloc(_PyExecutorObject *self) { _Py_ExecutorClear(self); +#ifdef _Py_JIT + _PyJIT_Free(self); +#endif PyObject_Free(self); } @@ -789,6 +793,14 @@ make_executor_from_uops(_PyUOpInstruction *buffer, _PyBloomFilter *dependencies) executor->trace[i].operand); } } +#endif +#ifdef _Py_JIT + executor->jit_code = NULL; + executor->jit_size = 0; + if (_PyJIT_Compile(executor, executor->trace, Py_SIZE(executor))) { + Py_DECREF(executor); + return NULL; + } #endif return executor; } diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index fff64dd63d6b219..372f60602375b65 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1240,12 +1240,19 @@ init_interp_main(PyThreadState *tstate) // Turn on experimental tier 2 (uops-based) optimizer if (is_main_interp) { +#ifndef _Py_JIT + // No JIT, maybe use the tier two interpreter: char *envvar = Py_GETENV("PYTHON_UOPS"); int enabled = envvar != NULL && *envvar > '0'; if (_Py_get_xoption(&config->xoptions, L"uops") != NULL) { enabled = 1; } if (enabled) { +#else + // Always enable tier two for JIT builds (ignoring the environment + // variable and command-line option above): + if (true) { +#endif PyObject *opt = PyUnstable_Optimizer_NewUOpOptimizer(); if (opt == NULL) { return _PyStatus_ERR("can't initialize optimizer"); diff --git a/Tools/jit/README.md b/Tools/jit/README.md new file mode 100644 index 000000000000000..04a6c0780bf9727 --- /dev/null +++ b/Tools/jit/README.md @@ -0,0 +1,46 @@ +The JIT Compiler +================ + +This version of CPython can be built with an experimental just-in-time compiler. While most everything you already know about building and using CPython is unchanged, you will probably need to install a compatible version of LLVM first. + +## Installing LLVM + +The JIT compiler does not require end users to install any third-party dependencies, but part of it must be *built* using LLVM[^why-llvm]. You are *not* required to build the rest of CPython using LLVM, or even the same version of LLVM (in fact, this is uncommon). + +LLVM version 16 is required. Both `clang` and `llvm-readobj` need to be installed and discoverable (version suffixes, like `clang-16`, are okay). It's highly recommended that you also have `llvm-objdump` available, since this allows the build script to dump human-readable assembly for the generated code. + +It's easy to install all of the required tools: + +### Linux + +Install LLVM 16 on Ubuntu/Debian: + +```sh +wget https://apt.llvm.org/llvm.sh +chmod +x llvm.sh +sudo ./llvm.sh 16 +``` + +### macOS + +Install LLVM 16 with [Homebrew](https://brew.sh): + +```sh +brew install llvm@16 +``` + +Homebrew won't add any of the tools to your `$PATH`. That's okay; the build script knows how to find them. + +### Windows + +Install LLVM 16 [by searching for it on LLVM's GitHub releases page](https://github.com/llvm/llvm-project/releases?q=16), clicking on "Assets", downloading the appropriate Windows installer for your platform (likely the file ending with `-win64.exe`), and running it. **When installing, be sure to select the option labeled "Add LLVM to the system PATH".** + +## Building + +For `PCbuild`-based builds, pass the new `--experimental-jit` option to `build.bat`. + +For all other builds, pass the new `--enable-experimental-jit` option to `configure`. + +Otherwise, just configure and build as you normally would. Cross-compiling "just works", since the JIT is built for the host platform. + +[^why-llvm]: Clang is specifically needed because it's the only C compiler with support for guaranteed tail calls (`musttail`), which are required by CPython's continuation-passing-style approach to JIT compilation. Since LLVM also includes other functionalities we need (namely, object file parsing and disassembly), it's convenient to only support one toolchain at this time. diff --git a/Tools/jit/_llvm.py b/Tools/jit/_llvm.py new file mode 100644 index 000000000000000..603bbef59ba2e63 --- /dev/null +++ b/Tools/jit/_llvm.py @@ -0,0 +1,99 @@ +"""Utilities for invoking LLVM tools.""" +import asyncio +import functools +import os +import re +import shlex +import subprocess +import typing + +_LLVM_VERSION = 16 +_LLVM_VERSION_PATTERN = re.compile(rf"version\s+{_LLVM_VERSION}\.\d+\.\d+\s+") + +_P = typing.ParamSpec("_P") +_R = typing.TypeVar("_R") +_C = typing.Callable[_P, typing.Awaitable[_R]] + + +def _async_cache(f: _C[_P, _R]) -> _C[_P, _R]: + cache = {} + lock = asyncio.Lock() + + @functools.wraps(f) + async def wrapper( + *args: _P.args, **kwargs: _P.kwargs # pylint: disable = no-member + ) -> _R: + async with lock: + if args not in cache: + cache[args] = await f(*args, **kwargs) + return cache[args] + + return wrapper + + +_CORES = asyncio.BoundedSemaphore(os.cpu_count() or 1) + + +async def _run(tool: str, args: typing.Iterable[str], echo: bool = False) -> str | None: + command = [tool, *args] + async with _CORES: + if echo: + print(shlex.join(command)) + try: + process = await asyncio.create_subprocess_exec( + *command, stdout=subprocess.PIPE + ) + except FileNotFoundError: + return None + out, _ = await process.communicate() + if process.returncode: + raise RuntimeError(f"{tool} exited with return code {process.returncode}") + return out.decode() + + +@_async_cache +async def _check_tool_version(name: str, *, echo: bool = False) -> bool: + output = await _run(name, ["--version"], echo=echo) + return bool(output and _LLVM_VERSION_PATTERN.search(output)) + + +@_async_cache +async def _get_brew_llvm_prefix(*, echo: bool = False) -> str | None: + output = await _run("brew", ["--prefix", f"llvm@{_LLVM_VERSION}"], echo=echo) + return output and output.removesuffix("\n") + + +@_async_cache +async def _find_tool(tool: str, *, echo: bool = False) -> str | None: + # Unversioned executables: + path = tool + if await _check_tool_version(path, echo=echo): + return path + # Versioned executables: + path = f"{tool}-{_LLVM_VERSION}" + if await _check_tool_version(path, echo=echo): + return path + # Homebrew-installed executables: + prefix = await _get_brew_llvm_prefix(echo=echo) + if prefix is not None: + path = os.path.join(prefix, "bin", tool) + if await _check_tool_version(path, echo=echo): + return path + # Nothing found: + return None + + +async def maybe_run( + tool: str, args: typing.Iterable[str], echo: bool = False +) -> str | None: + """Run an LLVM tool if it can be found. Otherwise, return None.""" + path = await _find_tool(tool, echo=echo) + return path and await _run(path, args, echo=echo) + + +async def run(tool: str, args: typing.Iterable[str], echo: bool = False) -> str: + """Run an LLVM tool if it can be found. Otherwise, raise RuntimeError.""" + output = await maybe_run(tool, args, echo=echo) + if output is None: + raise RuntimeError(f"Can't find {tool}-{_LLVM_VERSION}!") + return output diff --git a/Tools/jit/_schema.py b/Tools/jit/_schema.py new file mode 100644 index 000000000000000..8eeb78e6cd69eee --- /dev/null +++ b/Tools/jit/_schema.py @@ -0,0 +1,99 @@ +"""Schema for the JSON produced by llvm-readobj --elf-output-style=JSON.""" +import typing + +HoleKind: typing.TypeAlias = typing.Literal[ + "ARM64_RELOC_GOT_LOAD_PAGE21", + "ARM64_RELOC_GOT_LOAD_PAGEOFF12", + "ARM64_RELOC_UNSIGNED", + "IMAGE_REL_AMD64_ADDR64", + "IMAGE_REL_I386_DIR32", + "R_AARCH64_ABS64", + "R_AARCH64_CALL26", + "R_AARCH64_JUMP26", + "R_AARCH64_MOVW_UABS_G0_NC", + "R_AARCH64_MOVW_UABS_G1_NC", + "R_AARCH64_MOVW_UABS_G2_NC", + "R_AARCH64_MOVW_UABS_G3", + "R_X86_64_64", + "X86_64_RELOC_UNSIGNED", +] + + +class COFFRelocation(typing.TypedDict): + """A COFF object file relocation record.""" + + Type: dict[typing.Literal["Value"], HoleKind] + Symbol: str + Offset: int + + +class ELFRelocation(typing.TypedDict): + """An ELF object file relocation record.""" + + Addend: int + Offset: int + Symbol: dict[typing.Literal["Value"], str] + Type: dict[typing.Literal["Value"], HoleKind] + + +class MachORelocation(typing.TypedDict): + """A Mach-O object file relocation record.""" + + Offset: int + Section: typing.NotRequired[dict[typing.Literal["Value"], str]] + Symbol: typing.NotRequired[dict[typing.Literal["Value"], str]] + Type: dict[typing.Literal["Value"], HoleKind] + + +class _COFFSymbol(typing.TypedDict): + Name: str + Value: int + + +class _ELFSymbol(typing.TypedDict): + Name: dict[typing.Literal["Value"], str] + Value: int + + +class _MachOSymbol(typing.TypedDict): + Name: dict[typing.Literal["Value"], str] + Value: int + + +class COFFSection(typing.TypedDict): + """A COFF object file section.""" + + Characteristics: dict[ + typing.Literal["Flags"], list[dict[typing.Literal["Name"], str]] + ] + Number: int + RawDataSize: int + Relocations: list[dict[typing.Literal["Relocation"], COFFRelocation]] + SectionData: typing.NotRequired[dict[typing.Literal["Bytes"], list[int]]] + Symbols: list[dict[typing.Literal["Symbol"], _COFFSymbol]] + + +class ELFSection(typing.TypedDict): + """An ELF object file section.""" + + Flags: dict[typing.Literal["Flags"], list[dict[typing.Literal["Name"], str]]] + Index: int + Info: int + Relocations: list[dict[typing.Literal["Relocation"], ELFRelocation]] + SectionData: dict[typing.Literal["Bytes"], list[int]] + Symbols: list[dict[typing.Literal["Symbol"], _ELFSymbol]] + Type: dict[typing.Literal["Value"], str] + + +class MachOSection(typing.TypedDict): + """A Mach-O object file section.""" + + Address: int + Attributes: dict[typing.Literal["Flags"], list[dict[typing.Literal["Name"], str]]] + Index: int + Name: dict[typing.Literal["Value"], str] + Relocations: typing.NotRequired[ + list[dict[typing.Literal["Relocation"], MachORelocation]] + ] + SectionData: typing.NotRequired[dict[typing.Literal["Bytes"], list[int]]] + Symbols: typing.NotRequired[list[dict[typing.Literal["Symbol"], _MachOSymbol]]] diff --git a/Tools/jit/_stencils.py b/Tools/jit/_stencils.py new file mode 100644 index 000000000000000..71c678e04fbfd5a --- /dev/null +++ b/Tools/jit/_stencils.py @@ -0,0 +1,220 @@ +"""Core data structures for compiled code templates.""" +import dataclasses +import enum +import sys + +import _schema + + +@enum.unique +class HoleValue(enum.Enum): + """ + Different "base" values that can be patched into holes (usually combined with the + address of a symbol and/or an addend). + """ + + # The base address of the machine code for the current uop (exposed as _JIT_ENTRY): + CODE = enum.auto() + # The base address of the machine code for the next uop (exposed as _JIT_CONTINUE): + CONTINUE = 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: + GOT = enum.auto() + # The current uop's oparg (exposed as _JIT_OPARG): + OPARG = enum.auto() + # The current uop's operand (exposed as _JIT_OPERAND): + OPERAND = enum.auto() + # The current uop's target (exposed as _JIT_TARGET): + TARGET = enum.auto() + # The base address of the machine code for the first uop (exposed as _JIT_TOP): + TOP = enum.auto() + # A hardcoded value of zero (used for symbol lookups): + ZERO = enum.auto() + + +@dataclasses.dataclass +class Hole: + """ + A "hole" in the stencil to be patched with a computed runtime value. + + Analogous to relocation records in an object file. + """ + + offset: int + kind: _schema.HoleKind + # Patch with this base value: + value: HoleValue + # ...plus the address of this symbol: + symbol: str | None + # ...plus this addend: + addend: int + # Convenience method: + replace = dataclasses.replace + + def as_c(self) -> str: + """Dump this hole as an initialization of a C Hole struct.""" + parts = [ + f"{self.offset:#x}", + f"HoleKind_{self.kind}", + f"HoleValue_{self.value.name}", + f"&{self.symbol}" if self.symbol else "NULL", + _format_addend(self.addend), + ] + return f"{{{', '.join(parts)}}}" + + +@dataclasses.dataclass +class Stencil: + """ + A contiguous block of machine code or data to be copied-and-patched. + + Analogous to a section or segment in an object file. + """ + + body: bytearray = dataclasses.field(default_factory=bytearray, init=False) + holes: list[Hole] = dataclasses.field(default_factory=list, init=False) + disassembly: list[str] = dataclasses.field(default_factory=list, init=False) + + def pad(self, alignment: int) -> None: + """Pad the stencil to the given alignment.""" + offset = len(self.body) + padding = -offset % alignment + self.disassembly.append(f"{offset:x}: {' '.join(['00'] * padding)}") + self.body.extend([0] * padding) + + def emit_aarch64_trampoline(self, hole: Hole) -> None: + """Even with the large code model, AArch64 Linux insists on 28-bit jumps.""" + base = len(self.body) + where = slice(hole.offset, hole.offset + 4) + instruction = int.from_bytes(self.body[where], sys.byteorder) + instruction &= 0xFC000000 + instruction |= ((base - hole.offset) >> 2) & 0x03FFFFFF + self.body[where] = instruction.to_bytes(4, sys.byteorder) + self.disassembly += [ + f"{base + 4 * 0: x}: d2800008 mov x8, #0x0", + f"{base + 4 * 0:016x}: R_AARCH64_MOVW_UABS_G0_NC {hole.symbol}", + f"{base + 4 * 1:x}: f2a00008 movk x8, #0x0, lsl #16", + f"{base + 4 * 1:016x}: R_AARCH64_MOVW_UABS_G1_NC {hole.symbol}", + f"{base + 4 * 2:x}: f2c00008 movk x8, #0x0, lsl #32", + f"{base + 4 * 2:016x}: R_AARCH64_MOVW_UABS_G2_NC {hole.symbol}", + f"{base + 4 * 3:x}: f2e00008 movk x8, #0x0, lsl #48", + f"{base + 4 * 3:016x}: R_AARCH64_MOVW_UABS_G3 {hole.symbol}", + f"{base + 4 * 4:x}: d61f0100 br x8", + ] + for code in [ + 0xD2800008.to_bytes(4, sys.byteorder), + 0xF2A00008.to_bytes(4, sys.byteorder), + 0xF2C00008.to_bytes(4, sys.byteorder), + 0xF2E00008.to_bytes(4, sys.byteorder), + 0xD61F0100.to_bytes(4, sys.byteorder), + ]: + self.body.extend(code) + for i, kind in enumerate( + [ + "R_AARCH64_MOVW_UABS_G0_NC", + "R_AARCH64_MOVW_UABS_G1_NC", + "R_AARCH64_MOVW_UABS_G2_NC", + "R_AARCH64_MOVW_UABS_G3", + ] + ): + self.holes.append(hole.replace(offset=base + 4 * i, kind=kind)) + + +@dataclasses.dataclass +class StencilGroup: + """ + Code and data corresponding to a given micro-opcode. + + Analogous to an entire object file. + """ + + code: Stencil = dataclasses.field(default_factory=Stencil, init=False) + data: Stencil = dataclasses.field(default_factory=Stencil, init=False) + symbols: dict[int | str, tuple[HoleValue, int]] = dataclasses.field( + default_factory=dict, init=False + ) + _got: dict[str, int] = dataclasses.field(default_factory=dict, init=False) + + def process_relocations(self, *, alignment: int = 1) -> None: + """Fix up all GOT and internal relocations for this stencil group.""" + self.code.pad(alignment) + self.data.pad(8) + for stencil in [self.code, self.data]: + holes = [] + for hole in stencil.holes: + if hole.value is HoleValue.GOT: + assert hole.symbol is not None + hole.value = HoleValue.DATA + hole.addend += self._global_offset_table_lookup(hole.symbol) + hole.symbol = None + elif hole.symbol in self.symbols: + hole.value, addend = self.symbols[hole.symbol] + hole.addend += addend + hole.symbol = None + elif ( + hole.kind in {"R_AARCH64_CALL26", "R_AARCH64_JUMP26"} + and hole.value is HoleValue.ZERO + ): + self.code.emit_aarch64_trampoline(hole) + continue + holes.append(hole) + stencil.holes[:] = holes + self.code.pad(alignment) + self._emit_global_offset_table() + self.code.holes.sort(key=lambda hole: hole.offset) + self.data.holes.sort(key=lambda hole: hole.offset) + + def _global_offset_table_lookup(self, symbol: str) -> int: + return len(self.data.body) + self._got.setdefault(symbol, 8 * len(self._got)) + + def _emit_global_offset_table(self) -> None: + got = len(self.data.body) + for s, offset in self._got.items(): + if s in self.symbols: + value, addend = self.symbols[s] + symbol = None + else: + value, symbol = symbol_to_value(s) + addend = 0 + self.data.holes.append( + Hole(got + offset, "R_X86_64_64", value, symbol, addend) + ) + value_part = value.name if value is not HoleValue.ZERO else "" + if value_part and not symbol and not addend: + addend_part = "" + else: + addend_part = f"&{symbol}" if symbol else "" + addend_part += _format_addend(addend, signed=symbol is not None) + if value_part: + value_part += "+" + self.data.disassembly.append( + f"{len(self.data.body):x}: {value_part}{addend_part}" + ) + self.data.body.extend([0] * 8) + + +def symbol_to_value(symbol: str) -> tuple[HoleValue, str | None]: + """ + Convert a symbol name to a HoleValue and a symbol name. + + Some symbols (starting with "_JIT_") are special and are converted to their + own HoleValues. + """ + if symbol.startswith("_JIT_"): + try: + return HoleValue[symbol.removeprefix("_JIT_")], None + except KeyError: + pass + return HoleValue.ZERO, symbol + + +def _format_addend(addend: int, signed: bool = False) -> str: + addend %= 1 << 64 + if addend & (1 << 63): + addend -= 1 << 64 + return f"{addend:{'+#x' if signed else '#x'}}" diff --git a/Tools/jit/_targets.py b/Tools/jit/_targets.py new file mode 100644 index 000000000000000..51b091eb2464131 --- /dev/null +++ b/Tools/jit/_targets.py @@ -0,0 +1,394 @@ +"""Target-specific code generation, parsing, and processing.""" +import asyncio +import dataclasses +import hashlib +import json +import os +import pathlib +import re +import sys +import tempfile +import typing + +import _llvm +import _schema +import _stencils +import _writer + +if sys.version_info < (3, 11): + raise RuntimeError("Building the JIT compiler requires Python 3.11 or newer!") + +TOOLS_JIT_BUILD = pathlib.Path(__file__).resolve() +TOOLS_JIT = TOOLS_JIT_BUILD.parent +TOOLS = TOOLS_JIT.parent +CPYTHON = TOOLS.parent +PYTHON_EXECUTOR_CASES_C_H = CPYTHON / "Python" / "executor_cases.c.h" +TOOLS_JIT_TEMPLATE_C = TOOLS_JIT / "template.c" + + +_S = typing.TypeVar("_S", _schema.COFFSection, _schema.ELFSection, _schema.MachOSection) +_R = typing.TypeVar( + "_R", _schema.COFFRelocation, _schema.ELFRelocation, _schema.MachORelocation +) + + +@dataclasses.dataclass +class _Target(typing.Generic[_S, _R]): + triple: str + _: dataclasses.KW_ONLY + alignment: int = 1 + prefix: str = "" + debug: bool = False + force: bool = False + verbose: bool = False + + def _compute_digest(self, out: pathlib.Path) -> str: + hasher = hashlib.sha256() + hasher.update(self.triple.encode()) + hasher.update(self.alignment.to_bytes()) + hasher.update(self.prefix.encode()) + # These dependencies are also reflected in _JITSources in regen.targets: + hasher.update(PYTHON_EXECUTOR_CASES_C_H.read_bytes()) + hasher.update((out / "pyconfig.h").read_bytes()) + for dirpath, _, filenames in sorted(os.walk(TOOLS_JIT)): + for filename in filenames: + hasher.update(pathlib.Path(dirpath, filename).read_bytes()) + return hasher.hexdigest() + + async def _parse(self, path: pathlib.Path) -> _stencils.StencilGroup: + group = _stencils.StencilGroup() + args = ["--disassemble", "--reloc", f"{path}"] + output = await _llvm.maybe_run("llvm-objdump", args, echo=self.verbose) + if output is not None: + group.code.disassembly.extend( + line.expandtabs().strip() + for line in output.splitlines() + if not line.isspace() + ) + args = [ + "--elf-output-style=JSON", + "--expand-relocs", + # "--pretty-print", + "--section-data", + "--section-relocations", + "--section-symbols", + "--sections", + f"{path}", + ] + output = await _llvm.run("llvm-readobj", args, echo=self.verbose) + # --elf-output-style=JSON is only *slightly* broken on Mach-O... + output = output.replace("PrivateExtern\n", "\n") + output = output.replace("Extern\n", "\n") + # ...and also COFF: + output = output[output.index("[", 1, None) :] + output = output[: output.rindex("]", None, -1) + 1] + sections: list[dict[typing.Literal["Section"], _S]] = json.loads(output) + for wrapped_section in sections: + self._handle_section(wrapped_section["Section"], group) + assert group.symbols["_JIT_ENTRY"] == (_stencils.HoleValue.CODE, 0) + if group.data.body: + line = f"0: {str(bytes(group.data.body)).removeprefix('b')}" + group.data.disassembly.append(line) + group.process_relocations() + return group + + def _handle_section(self, section: _S, group: _stencils.StencilGroup) -> None: + raise NotImplementedError(type(self)) + + def _handle_relocation( + self, base: int, relocation: _R, raw: bytes + ) -> _stencils.Hole: + raise NotImplementedError(type(self)) + + async def _compile( + self, opname: str, c: pathlib.Path, tempdir: pathlib.Path + ) -> _stencils.StencilGroup: + o = tempdir / f"{opname}.o" + args = [ + f"--target={self.triple}", + "-DPy_BUILD_CORE", + "-D_DEBUG" if self.debug else "-DNDEBUG", + f"-D_JIT_OPCODE={opname}", + "-D_PyJIT_ACTIVE", + "-D_Py_JIT", + "-I.", + f"-I{CPYTHON / 'Include'}", + f"-I{CPYTHON / 'Include' / 'internal'}", + f"-I{CPYTHON / 'Include' / 'internal' / 'mimalloc'}", + f"-I{CPYTHON / 'Python'}", + "-O3", + "-c", + "-fno-asynchronous-unwind-tables", + # SET_FUNCTION_ATTRIBUTE on 32-bit Windows debug builds: + "-fno-jump-tables", + # Position-independent code adds indirection to every load and jump: + "-fno-pic", + # Don't make calls to weird stack-smashing canaries: + "-fno-stack-protector", + # We have three options for code model: + # - "small": the default, assumes that code and data reside in the + # lowest 2GB of memory (128MB on aarch64) + # - "medium": assumes that code resides in the lowest 2GB of memory, + # and makes no assumptions about data (not available on aarch64) + # - "large": makes no assumptions about either code or data + "-mcmodel=large", + "-o", + f"{o}", + "-std=c11", + f"{c}", + ] + await _llvm.run("clang", args, echo=self.verbose) + return await self._parse(o) + + async def _build_stencils(self) -> dict[str, _stencils.StencilGroup]: + generated_cases = PYTHON_EXECUTOR_CASES_C_H.read_text() + opnames = sorted(re.findall(r"\n {8}case (\w+): \{\n", generated_cases)) + tasks = [] + with tempfile.TemporaryDirectory() as tempdir: + work = pathlib.Path(tempdir).resolve() + async with asyncio.TaskGroup() as group: + for opname in opnames: + coro = self._compile(opname, TOOLS_JIT_TEMPLATE_C, work) + tasks.append(group.create_task(coro, name=opname)) + return {task.get_name(): task.result() for task in tasks} + + def build(self, out: pathlib.Path, *, comment: str = "") -> None: + """Build jit_stencils.h in the given directory.""" + digest = f"// {self._compute_digest(out)}\n" + jit_stencils = out / "jit_stencils.h" + if ( + not self.force + and jit_stencils.exists() + and jit_stencils.read_text().startswith(digest) + ): + return + stencil_groups = asyncio.run(self._build_stencils()) + with jit_stencils.open("w") as file: + file.write(digest) + if comment: + file.write(f"// {comment}\n") + file.write("") + for line in _writer.dump(stencil_groups): + file.write(f"{line}\n") + + +class _COFF( + _Target[_schema.COFFSection, _schema.COFFRelocation] +): # pylint: disable = too-few-public-methods + def _handle_section( + self, section: _schema.COFFSection, group: _stencils.StencilGroup + ) -> None: + flags = {flag["Name"] for flag in section["Characteristics"]["Flags"]} + if "SectionData" in section: + section_data_bytes = section["SectionData"]["Bytes"] + else: + # Zeroed BSS data, seen with printf debugging calls: + section_data_bytes = [0] * section["RawDataSize"] + if "IMAGE_SCN_MEM_EXECUTE" in flags: + value = _stencils.HoleValue.CODE + stencil = group.code + elif "IMAGE_SCN_MEM_READ" in flags: + value = _stencils.HoleValue.DATA + stencil = group.data + else: + return + base = len(stencil.body) + group.symbols[section["Number"]] = value, base + stencil.body.extend(section_data_bytes) + for wrapped_symbol in section["Symbols"]: + symbol = wrapped_symbol["Symbol"] + offset = base + symbol["Value"] + name = symbol["Name"] + name = name.removeprefix(self.prefix) + group.symbols[name] = value, offset + for wrapped_relocation in section["Relocations"]: + relocation = wrapped_relocation["Relocation"] + hole = self._handle_relocation(base, relocation, stencil.body) + stencil.holes.append(hole) + + def _handle_relocation( + self, base: int, relocation: _schema.COFFRelocation, raw: bytes + ) -> _stencils.Hole: + match relocation: + case { + "Offset": offset, + "Symbol": s, + "Type": {"Value": "IMAGE_REL_AMD64_ADDR64" as kind}, + }: + offset += base + s = s.removeprefix(self.prefix) + value, symbol = _stencils.symbol_to_value(s) + addend = int.from_bytes(raw[offset : offset + 8], "little") + case { + "Offset": offset, + "Symbol": s, + "Type": {"Value": "IMAGE_REL_I386_DIR32" as kind}, + }: + offset += base + s = s.removeprefix(self.prefix) + value, symbol = _stencils.symbol_to_value(s) + addend = int.from_bytes(raw[offset : offset + 4], "little") + case _: + raise NotImplementedError(relocation) + return _stencils.Hole(offset, kind, value, symbol, addend) + + +class _ELF( + _Target[_schema.ELFSection, _schema.ELFRelocation] +): # pylint: disable = too-few-public-methods + def _handle_section( + self, section: _schema.ELFSection, group: _stencils.StencilGroup + ) -> None: + section_type = section["Type"]["Value"] + flags = {flag["Name"] for flag in section["Flags"]["Flags"]} + if section_type == "SHT_RELA": + assert "SHF_INFO_LINK" in flags, flags + assert not section["Symbols"] + value, base = group.symbols[section["Info"]] + if value is _stencils.HoleValue.CODE: + stencil = group.code + else: + assert value is _stencils.HoleValue.DATA + stencil = group.data + for wrapped_relocation in section["Relocations"]: + relocation = wrapped_relocation["Relocation"] + hole = self._handle_relocation(base, relocation, stencil.body) + stencil.holes.append(hole) + elif section_type == "SHT_PROGBITS": + if "SHF_ALLOC" not in flags: + return + if "SHF_EXECINSTR" in flags: + value = _stencils.HoleValue.CODE + stencil = group.code + else: + value = _stencils.HoleValue.DATA + stencil = group.data + group.symbols[section["Index"]] = value, len(stencil.body) + for wrapped_symbol in section["Symbols"]: + symbol = wrapped_symbol["Symbol"] + offset = len(stencil.body) + symbol["Value"] + name = symbol["Name"]["Value"] + name = name.removeprefix(self.prefix) + group.symbols[name] = value, offset + stencil.body.extend(section["SectionData"]["Bytes"]) + assert not section["Relocations"] + else: + assert section_type in { + "SHT_GROUP", + "SHT_LLVM_ADDRSIG", + "SHT_NULL", + "SHT_STRTAB", + "SHT_SYMTAB", + }, section_type + + def _handle_relocation( + self, base: int, relocation: _schema.ELFRelocation, raw: bytes + ) -> _stencils.Hole: + match relocation: + case { + "Addend": addend, + "Offset": offset, + "Symbol": {"Value": s}, + "Type": {"Value": kind}, + }: + offset += base + s = s.removeprefix(self.prefix) + value, symbol = _stencils.symbol_to_value(s) + case _: + raise NotImplementedError(relocation) + return _stencils.Hole(offset, kind, value, symbol, addend) + + +class _MachO( + _Target[_schema.MachOSection, _schema.MachORelocation] +): # pylint: disable = too-few-public-methods + def _handle_section( + self, section: _schema.MachOSection, group: _stencils.StencilGroup + ) -> None: + assert section["Address"] >= len(group.code.body) + assert "SectionData" in section + flags = {flag["Name"] for flag in section["Attributes"]["Flags"]} + name = section["Name"]["Value"] + name = name.removeprefix(self.prefix) + if "SomeInstructions" in flags: + value = _stencils.HoleValue.CODE + stencil = group.code + start_address = 0 + group.symbols[name] = value, section["Address"] - start_address + else: + value = _stencils.HoleValue.DATA + stencil = group.data + start_address = len(group.code.body) + group.symbols[name] = value, len(group.code.body) + base = section["Address"] - start_address + group.symbols[section["Index"]] = value, base + stencil.body.extend( + [0] * (section["Address"] - len(group.code.body) - len(group.data.body)) + ) + stencil.body.extend(section["SectionData"]["Bytes"]) + assert "Symbols" in section + for wrapped_symbol in section["Symbols"]: + symbol = wrapped_symbol["Symbol"] + offset = symbol["Value"] - start_address + name = symbol["Name"]["Value"] + name = name.removeprefix(self.prefix) + group.symbols[name] = value, offset + assert "Relocations" in section + for wrapped_relocation in section["Relocations"]: + relocation = wrapped_relocation["Relocation"] + hole = self._handle_relocation(base, relocation, stencil.body) + stencil.holes.append(hole) + + def _handle_relocation( + self, base: int, relocation: _schema.MachORelocation, raw: bytes + ) -> _stencils.Hole: + symbol: str | None + match relocation: + case { + "Offset": offset, + "Symbol": {"Value": s}, + "Type": { + "Value": "ARM64_RELOC_GOT_LOAD_PAGE21" + | "ARM64_RELOC_GOT_LOAD_PAGEOFF12" as kind + }, + }: + offset += base + s = s.removeprefix(self.prefix) + value, symbol = _stencils.HoleValue.GOT, s + addend = 0 + case { + "Offset": offset, + "Section": {"Value": s}, + "Type": {"Value": kind}, + } | { + "Offset": offset, + "Symbol": {"Value": s}, + "Type": {"Value": kind}, + }: + offset += base + s = s.removeprefix(self.prefix) + value, symbol = _stencils.symbol_to_value(s) + addend = 0 + case _: + raise NotImplementedError(relocation) + # Turn Clang's weird __bzero calls into normal bzero calls: + if symbol == "__bzero": + symbol = "bzero" + return _stencils.Hole(offset, kind, value, symbol, addend) + + +def get_target(host: str) -> _COFF | _ELF | _MachO: + """Build a _Target for the given host "triple" and options.""" + if re.fullmatch(r"aarch64-apple-darwin.*", host): + return _MachO(host, alignment=8, prefix="_") + if re.fullmatch(r"aarch64-.*-linux-gnu", host): + return _ELF(host, alignment=8) + if re.fullmatch(r"i686-pc-windows-msvc", host): + return _COFF(host, prefix="_") + if re.fullmatch(r"x86_64-apple-darwin.*", host): + return _MachO(host, prefix="_") + if re.fullmatch(r"x86_64-pc-windows-msvc", host): + return _COFF(host) + if re.fullmatch(r"x86_64-.*-linux-gnu", host): + return _ELF(host) + raise ValueError(host) diff --git a/Tools/jit/_writer.py b/Tools/jit/_writer.py new file mode 100644 index 000000000000000..8a2a42e75cfb9b3 --- /dev/null +++ b/Tools/jit/_writer.py @@ -0,0 +1,95 @@ +"""Utilities for writing StencilGroups out to a C header file.""" +import typing + +import _schema +import _stencils + + +def _dump_header() -> typing.Iterator[str]: + yield "typedef enum {" + for kind in typing.get_args(_schema.HoleKind): + yield f" HoleKind_{kind}," + yield "} HoleKind;" + yield "" + yield "typedef enum {" + for value in _stencils.HoleValue: + yield f" HoleValue_{value.name}," + yield "} HoleValue;" + yield "" + yield "typedef struct {" + yield " const uint64_t offset;" + yield " const HoleKind kind;" + yield " const HoleValue value;" + yield " const void *symbol;" + yield " const uint64_t addend;" + yield "} Hole;" + yield "" + yield "typedef struct {" + yield " const size_t body_size;" + yield " const unsigned char * const body;" + yield " const size_t holes_size;" + yield " const Hole * const holes;" + yield "} Stencil;" + yield "" + yield "typedef struct {" + yield " const Stencil code;" + yield " const Stencil data;" + yield "} StencilGroup;" + yield "" + + +def _dump_footer(opnames: typing.Iterable[str]) -> typing.Iterator[str]: + yield "#define INIT_STENCIL(STENCIL) { \\" + yield " .body_size = Py_ARRAY_LENGTH(STENCIL##_body) - 1, \\" + yield " .body = STENCIL##_body, \\" + yield " .holes_size = Py_ARRAY_LENGTH(STENCIL##_holes) - 1, \\" + yield " .holes = STENCIL##_holes, \\" + yield "}" + yield "" + yield "#define INIT_STENCIL_GROUP(OP) { \\" + yield " .code = INIT_STENCIL(OP##_code), \\" + yield " .data = INIT_STENCIL(OP##_data), \\" + yield "}" + yield "" + yield "static const StencilGroup stencil_groups[512] = {" + for opname in opnames: + yield f" [{opname}] = INIT_STENCIL_GROUP({opname})," + yield "};" + yield "" + yield "#define GET_PATCHES() { \\" + for value in _stencils.HoleValue: + yield f" [HoleValue_{value.name}] = (uint64_t)0xBADBADBADBADBADB, \\" + yield "}" + + +def _dump_stencil(opname: str, group: _stencils.StencilGroup) -> typing.Iterator[str]: + yield f"// {opname}" + for part, stencil in [("code", group.code), ("data", group.data)]: + for line in stencil.disassembly: + yield f"// {line}" + if stencil.body: + size = len(stencil.body) + 1 + yield f"static const unsigned char {opname}_{part}_body[{size}] = {{" + for i in range(0, len(stencil.body), 8): + row = " ".join(f"{byte:#04x}," for byte in stencil.body[i : i + 8]) + yield f" {row}" + yield "};" + else: + yield f"static const unsigned char {opname}_{part}_body[1];" + if stencil.holes: + size = len(stencil.holes) + 1 + yield f"static const Hole {opname}_{part}_holes[{size}] = {{" + for hole in stencil.holes: + yield f" {hole.as_c()}," + yield "};" + else: + yield f"static const Hole {opname}_{part}_holes[1];" + yield "" + + +def dump(groups: dict[str, _stencils.StencilGroup]) -> typing.Iterator[str]: + """Yield a JIT compiler line-by-line as a C header file.""" + yield from _dump_header() + for opname, group in groups.items(): + yield from _dump_stencil(opname, group) + yield from _dump_footer(groups) diff --git a/Tools/jit/build.py b/Tools/jit/build.py new file mode 100644 index 000000000000000..4d4ace14ebf26c4 --- /dev/null +++ b/Tools/jit/build.py @@ -0,0 +1,28 @@ +"""Build an experimental just-in-time compiler for CPython.""" +import argparse +import pathlib +import shlex +import sys + +import _targets + +if __name__ == "__main__": + comment = f"$ {shlex.join([sys.executable] + sys.argv)}" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "target", type=_targets.get_target, help="a PEP 11 target triple to compile for" + ) + parser.add_argument( + "-d", "--debug", action="store_true", help="compile for a debug build of Python" + ) + parser.add_argument( + "-f", "--force", action="store_true", help="force the entire JIT to be rebuilt" + ) + parser.add_argument( + "-v", "--verbose", action="store_true", help="echo commands as they are run" + ) + args = parser.parse_args() + args.target.debug = args.debug + args.target.force = args.force + args.target.verbose = args.verbose + args.target.build(pathlib.Path.cwd(), comment=comment) diff --git a/Tools/jit/mypy.ini b/Tools/jit/mypy.ini new file mode 100644 index 000000000000000..768d0028516abd0 --- /dev/null +++ b/Tools/jit/mypy.ini @@ -0,0 +1,5 @@ +[mypy] +files = Tools/jit +pretty = True +python_version = 3.11 +strict = True diff --git a/Tools/jit/template.c b/Tools/jit/template.c new file mode 100644 index 000000000000000..12303a550d8879e --- /dev/null +++ b/Tools/jit/template.c @@ -0,0 +1,98 @@ +#include "Python.h" + +#include "pycore_call.h" +#include "pycore_ceval.h" +#include "pycore_dict.h" +#include "pycore_emscripten_signal.h" +#include "pycore_intrinsics.h" +#include "pycore_jit.h" +#include "pycore_long.h" +#include "pycore_opcode_metadata.h" +#include "pycore_opcode_utils.h" +#include "pycore_range.h" +#include "pycore_setobject.h" +#include "pycore_sliceobject.h" + +#include "ceval_macros.h" + +#undef CURRENT_OPARG +#define CURRENT_OPARG() (_oparg) + +#undef CURRENT_OPERAND +#define CURRENT_OPERAND() (_operand) + +#undef DEOPT_IF +#define DEOPT_IF(COND, INSTNAME) \ + do { \ + if ((COND)) { \ + goto deoptimize; \ + } \ + } while (0) + +#undef ENABLE_SPECIALIZATION +#define ENABLE_SPECIALIZATION (0) + +#undef GOTO_ERROR +#define GOTO_ERROR(LABEL) \ + do { \ + goto LABEL ## _tier_two; \ + } while (0) + +#undef LOAD_IP +#define LOAD_IP(UNUSED) \ + do { \ + } while (0) + +#define PATCH_VALUE(TYPE, NAME, ALIAS) \ + extern void ALIAS; \ + TYPE NAME = (TYPE)(uint64_t)&ALIAS; + +#define PATCH_JUMP(ALIAS) \ + extern void ALIAS; \ + __attribute__((musttail)) \ + return ((jit_func)&ALIAS)(frame, stack_pointer, tstate); + +_Py_CODEUNIT * +_JIT_ENTRY(_PyInterpreterFrame *frame, PyObject **stack_pointer, PyThreadState *tstate) +{ + // Locals that the instruction implementations expect to exist: + PATCH_VALUE(_PyExecutorObject *, current_executor, _JIT_EXECUTOR) + int oparg; + int opcode = _JIT_OPCODE; + _PyUOpInstruction *next_uop; + // Other stuff we need handy: + PATCH_VALUE(uint16_t, _oparg, _JIT_OPARG) + PATCH_VALUE(uint64_t, _operand, _JIT_OPERAND) + PATCH_VALUE(uint32_t, _target, _JIT_TARGET) + // The actual instruction definitions (only one will be used): + if (opcode == _JUMP_TO_TOP) { + CHECK_EVAL_BREAKER(); + PATCH_JUMP(_JIT_TOP); + } + switch (opcode) { +#include "executor_cases.c.h" + default: + Py_UNREACHABLE(); + } + PATCH_JUMP(_JIT_CONTINUE); + // Labels that the instruction implementations expect to exist: +unbound_local_error_tier_two: + _PyEval_FormatExcCheckArg( + tstate, PyExc_UnboundLocalError, UNBOUNDLOCAL_ERROR_MSG, + PyTuple_GetItem(_PyFrame_GetCode(frame)->co_localsplusnames, oparg)); + goto error_tier_two; +pop_4_error_tier_two: + STACK_SHRINK(1); +pop_3_error_tier_two: + STACK_SHRINK(1); +pop_2_error_tier_two: + STACK_SHRINK(1); +pop_1_error_tier_two: + STACK_SHRINK(1); +error_tier_two: + _PyFrame_SetStackPointer(frame, stack_pointer); + return NULL; +deoptimize: + _PyFrame_SetStackPointer(frame, stack_pointer); + return _PyCode_CODE(_PyFrame_GetCode(frame)) + _target; +} diff --git a/configure b/configure index b1153df4d7ec523..c563c3f5d3c7e6d 100755 --- a/configure +++ b/configure @@ -920,6 +920,7 @@ LLVM_AR PROFILE_TASK DEF_MAKE_RULE DEF_MAKE_ALL_RULE +REGEN_JIT_COMMAND ABIFLAGS LN MKDIR_P @@ -1074,6 +1075,7 @@ with_pydebug with_trace_refs enable_pystats with_assertions +enable_experimental_jit enable_optimizations with_lto enable_bolt @@ -1801,6 +1803,9 @@ Optional Features: --disable-gil enable experimental support for running without the GIL (default is no) --enable-pystats enable internal statistics gathering (default is no) + --enable-experimental-jit + build the experimental just-in-time compiler + (default is no) --enable-optimizations enable expensive, stable optimizations (PGO, etc.) (default is no) --enable-bolt enable usage of the llvm-bolt post-link optimizer @@ -7997,6 +8002,32 @@ else printf "%s\n" "no" >&6; } fi +# Check for --enable-experimental-jit: +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --enable-experimental-jit" >&5 +printf %s "checking for --enable-experimental-jit... " >&6; } +# Check whether --enable-experimental-jit was given. +if test ${enable_experimental_jit+y} +then : + enableval=$enable_experimental_jit; +else $as_nop + enable_experimental_jit=no +fi + +if test "x$enable_experimental_jit" = xno +then : + +else $as_nop + as_fn_append CFLAGS_NODIST " -D_Py_JIT" + REGEN_JIT_COMMAND="\$(PYTHON_FOR_REGEN) \$(srcdir)/Tools/jit/build.py $host" + if test "x$Py_DEBUG" = xtrue +then : + as_fn_append REGEN_JIT_COMMAND " --debug" +fi +fi + +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $enable_experimental_jit" >&5 +printf "%s\n" "$enable_experimental_jit" >&6; } + # Enable optimization flags diff --git a/configure.ac b/configure.ac index 9587e6d63499aac..13c46b3e80151dc 100644 --- a/configure.ac +++ b/configure.ac @@ -1579,6 +1579,26 @@ else AC_MSG_RESULT([no]) fi +# Check for --enable-experimental-jit: +AC_MSG_CHECKING([for --enable-experimental-jit]) +AC_ARG_ENABLE([experimental-jit], + [AS_HELP_STRING([--enable-experimental-jit], + [build the experimental just-in-time compiler (default is no)])], + [], + [enable_experimental_jit=no]) +AS_VAR_IF([enable_experimental_jit], + [no], + [], + [AS_VAR_APPEND([CFLAGS_NODIST], [" -D_Py_JIT"]) + AS_VAR_SET([REGEN_JIT_COMMAND], + ["\$(PYTHON_FOR_REGEN) \$(srcdir)/Tools/jit/build.py $host"]) + AS_VAR_IF([Py_DEBUG], + [true], + [AS_VAR_APPEND([REGEN_JIT_COMMAND], [" --debug"])], + [])]) +AC_SUBST([REGEN_JIT_COMMAND]) +AC_MSG_RESULT([$enable_experimental_jit]) + # Enable optimization flags AC_SUBST([DEF_MAKE_ALL_RULE]) AC_SUBST([DEF_MAKE_RULE]) From a16a9f978f42b8a09297c1efbf33877f6388c403 Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Sun, 28 Jan 2024 18:52:58 -0800 Subject: [PATCH 006/507] GH-113464: A JIT backend for tier 2 (GH-113465) Add an option (--enable-experimental-jit for configure-based builds or --experimental-jit for PCbuild-based ones) to build an *experimental* just-in-time compiler, based on copy-and-patch (https://fredrikbk.com/publications/copy-and-patch.pdf). See Tools/jit/README.md for more information, including how to install the required build-time tooling. Merry JIT-mas! ;) From d7d0d13cd37651990586d31d8974c59bd25e1045 Mon Sep 17 00:00:00 2001 From: Stanley <46876382+slateny@users.noreply.github.com> Date: Mon, 29 Jan 2024 01:19:22 -0800 Subject: [PATCH 007/507] gh-89159: Add some TarFile attribute types (GH-114520) Co-authored-by: Stanley <46876382+slateny@users.noreply.github.com> --- Doc/library/tarfile.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst index 34a738a7f1c41f0..2134293a0bb0de1 100644 --- a/Doc/library/tarfile.rst +++ b/Doc/library/tarfile.rst @@ -673,6 +673,7 @@ be finalized; only the internally used file object will be closed. See the .. attribute:: TarFile.pax_headers + :type: dict A dictionary containing key-value pairs of pax global headers. @@ -838,26 +839,31 @@ A ``TarInfo`` object has the following public data attributes: attribute. .. attribute:: TarInfo.chksum + :type: int Header checksum. .. attribute:: TarInfo.devmajor + :type: int Device major number. .. attribute:: TarInfo.devminor + :type: int Device minor number. .. attribute:: TarInfo.offset + :type: int The tar header starts here. .. attribute:: TarInfo.offset_data + :type: int The file's data starts here. From 2124a3ddcc0e274521f74d239f0e94060e17dd7f Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Mon, 29 Jan 2024 01:30:22 -0800 Subject: [PATCH 008/507] gh-109653: Improve import time of importlib.metadata / email.utils (#114664) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit My criterion for delayed imports is that they're only worth it if the majority of users of the module would benefit from it, otherwise you're just moving latency around unpredictably. mktime_tz is not used anywhere in the standard library and grep.app indicates it's not got much use in the ecosystem either. Distribution.files is not nearly as widely used as other importlib.metadata APIs, so we defer the csv import. Before: ``` λ hyperfine -w 8 './python -c "import importlib.metadata"' Benchmark 1: ./python -c "import importlib.metadata" Time (mean ± σ): 65.1 ms ± 0.5 ms [User: 55.3 ms, System: 9.8 ms] Range (min … max): 64.4 ms … 66.4 ms 44 runs ``` After: ``` λ hyperfine -w 8 './python -c "import importlib.metadata"' Benchmark 1: ./python -c "import importlib.metadata" Time (mean ± σ): 62.0 ms ± 0.3 ms [User: 52.5 ms, System: 9.6 ms] Range (min … max): 61.3 ms … 62.8 ms 46 runs ``` for about a 3ms saving with warm disk cache, maybe 7-11ms with cold disk cache. --- Lib/email/_parseaddr.py | 5 ++++- Lib/importlib/metadata/__init__.py | 5 ++++- .../Library/2024-01-28-00-48-12.gh-issue-109653.vF4exe.rst | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-28-00-48-12.gh-issue-109653.vF4exe.rst diff --git a/Lib/email/_parseaddr.py b/Lib/email/_parseaddr.py index febe411355d6be3..0f1bf8e4253ec4b 100644 --- a/Lib/email/_parseaddr.py +++ b/Lib/email/_parseaddr.py @@ -13,7 +13,7 @@ 'quote', ] -import time, calendar +import time SPACE = ' ' EMPTYSTRING = '' @@ -194,6 +194,9 @@ def mktime_tz(data): # No zone info, so localtime is better assumption than GMT return time.mktime(data[:8] + (-1,)) else: + # Delay the import, since mktime_tz is rarely used + import calendar + t = calendar.timegm(data) return t - data[9] diff --git a/Lib/importlib/metadata/__init__.py b/Lib/importlib/metadata/__init__.py index 7b142e786e829ef..c612fbefee2e802 100644 --- a/Lib/importlib/metadata/__init__.py +++ b/Lib/importlib/metadata/__init__.py @@ -1,7 +1,6 @@ import os import re import abc -import csv import sys import json import email @@ -478,6 +477,10 @@ def make_file(name, hash=None, size_str=None): @pass_none def make_files(lines): + # Delay csv import, since Distribution.files is not as widely used + # as other parts of importlib.metadata + import csv + return starmap(make_file, csv.reader(lines)) @pass_none diff --git a/Misc/NEWS.d/next/Library/2024-01-28-00-48-12.gh-issue-109653.vF4exe.rst b/Misc/NEWS.d/next/Library/2024-01-28-00-48-12.gh-issue-109653.vF4exe.rst new file mode 100644 index 000000000000000..fb3382098853b30 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-28-00-48-12.gh-issue-109653.vF4exe.rst @@ -0,0 +1 @@ +Improve import time of :mod:`importlib.metadata` and :mod:`email.utils`. From 1ac1b2f9536a581f1656f0ac9330a7382420cda1 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Mon, 29 Jan 2024 12:37:06 +0300 Subject: [PATCH 009/507] gh-114685: Fix incorrect use of PyBUF_READ in import.c (GH-114686) --- Python/import.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/import.c b/Python/import.c index 2dd95d8364a0be0..2fd0c08a6bb5aec 100644 --- a/Python/import.c +++ b/Python/import.c @@ -3544,7 +3544,7 @@ _imp_get_frozen_object_impl(PyObject *module, PyObject *name, struct frozen_info info = {0}; Py_buffer buf = {0}; if (PyObject_CheckBuffer(dataobj)) { - if (PyObject_GetBuffer(dataobj, &buf, PyBUF_READ) != 0) { + if (PyObject_GetBuffer(dataobj, &buf, PyBUF_SIMPLE) != 0) { return NULL; } info.data = (const char *)buf.buf; From 3b86891fd69093b60141300862f278614ba80613 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Mon, 29 Jan 2024 01:37:28 -0800 Subject: [PATCH 010/507] gh-110893: Improve the documentation for __future__ module (#114642) nedbat took issue with the phrasing "real module". I'm actually fine with that phrasing, but I do think the `__future__` page should be clear about the way in which the `__future__` module is special. (Yes, there was a footnote linking to the future statements part of the reference, but there should be upfront discussion). I'm sympathetic to nedbat's claim that no one really cares about `__future__._Feature`, so I've moved the interesting table up to the top. --- Doc/library/__future__.rst | 98 ++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/Doc/library/__future__.rst b/Doc/library/__future__.rst index d261e4a4f338a56..762f8b4695b3dd9 100644 --- a/Doc/library/__future__.rst +++ b/Doc/library/__future__.rst @@ -8,20 +8,68 @@ -------------- -:mod:`__future__` is a real module, and serves three purposes: +Imports of the form ``from __future__ import feature`` are called +:ref:`future statements `. These are special-cased by the Python compiler +to allow the use of new Python features in modules containing the future statement +before the release in which the feature becomes standard. + +While these future statements are given additional special meaning by the +Python compiler, they are still executed like any other import statement and +the :mod:`__future__` exists and is handled by the import system the same way +any other Python module would be. This design serves three purposes: * To avoid confusing existing tools that analyze import statements and expect to find the modules they're importing. -* To ensure that :ref:`future statements ` run under releases prior to - 2.1 at least yield runtime exceptions (the import of :mod:`__future__` will - fail, because there was no module of that name prior to 2.1). - * To document when incompatible changes were introduced, and when they will be --- or were --- made mandatory. This is a form of executable documentation, and can be inspected programmatically via importing :mod:`__future__` and examining its contents. +* To ensure that :ref:`future statements ` run under releases prior to + Python 2.1 at least yield runtime exceptions (the import of :mod:`__future__` + will fail, because there was no module of that name prior to 2.1). + +Module Contents +--------------- + +No feature description will ever be deleted from :mod:`__future__`. Since its +introduction in Python 2.1 the following features have found their way into the +language using this mechanism: + ++------------------+-------------+--------------+---------------------------------------------+ +| feature | optional in | mandatory in | effect | ++==================+=============+==============+=============================================+ +| nested_scopes | 2.1.0b1 | 2.2 | :pep:`227`: | +| | | | *Statically Nested Scopes* | ++------------------+-------------+--------------+---------------------------------------------+ +| generators | 2.2.0a1 | 2.3 | :pep:`255`: | +| | | | *Simple Generators* | ++------------------+-------------+--------------+---------------------------------------------+ +| division | 2.2.0a2 | 3.0 | :pep:`238`: | +| | | | *Changing the Division Operator* | ++------------------+-------------+--------------+---------------------------------------------+ +| absolute_import | 2.5.0a1 | 3.0 | :pep:`328`: | +| | | | *Imports: Multi-Line and Absolute/Relative* | ++------------------+-------------+--------------+---------------------------------------------+ +| with_statement | 2.5.0a1 | 2.6 | :pep:`343`: | +| | | | *The "with" Statement* | ++------------------+-------------+--------------+---------------------------------------------+ +| print_function | 2.6.0a2 | 3.0 | :pep:`3105`: | +| | | | *Make print a function* | ++------------------+-------------+--------------+---------------------------------------------+ +| unicode_literals | 2.6.0a2 | 3.0 | :pep:`3112`: | +| | | | *Bytes literals in Python 3000* | ++------------------+-------------+--------------+---------------------------------------------+ +| generator_stop | 3.5.0b1 | 3.7 | :pep:`479`: | +| | | | *StopIteration handling inside generators* | ++------------------+-------------+--------------+---------------------------------------------+ +| annotations | 3.7.0b1 | TBD [1]_ | :pep:`563`: | +| | | | *Postponed evaluation of annotations* | ++------------------+-------------+--------------+---------------------------------------------+ + +.. XXX Adding a new entry? Remember to update simple_stmts.rst, too. + .. _future-classes: .. class:: _Feature @@ -65,43 +113,6 @@ dynamically compiled code. This flag is stored in the :attr:`_Feature.compiler_flag` attribute on :class:`_Feature` instances. -No feature description will ever be deleted from :mod:`__future__`. Since its -introduction in Python 2.1 the following features have found their way into the -language using this mechanism: - -+------------------+-------------+--------------+---------------------------------------------+ -| feature | optional in | mandatory in | effect | -+==================+=============+==============+=============================================+ -| nested_scopes | 2.1.0b1 | 2.2 | :pep:`227`: | -| | | | *Statically Nested Scopes* | -+------------------+-------------+--------------+---------------------------------------------+ -| generators | 2.2.0a1 | 2.3 | :pep:`255`: | -| | | | *Simple Generators* | -+------------------+-------------+--------------+---------------------------------------------+ -| division | 2.2.0a2 | 3.0 | :pep:`238`: | -| | | | *Changing the Division Operator* | -+------------------+-------------+--------------+---------------------------------------------+ -| absolute_import | 2.5.0a1 | 3.0 | :pep:`328`: | -| | | | *Imports: Multi-Line and Absolute/Relative* | -+------------------+-------------+--------------+---------------------------------------------+ -| with_statement | 2.5.0a1 | 2.6 | :pep:`343`: | -| | | | *The "with" Statement* | -+------------------+-------------+--------------+---------------------------------------------+ -| print_function | 2.6.0a2 | 3.0 | :pep:`3105`: | -| | | | *Make print a function* | -+------------------+-------------+--------------+---------------------------------------------+ -| unicode_literals | 2.6.0a2 | 3.0 | :pep:`3112`: | -| | | | *Bytes literals in Python 3000* | -+------------------+-------------+--------------+---------------------------------------------+ -| generator_stop | 3.5.0b1 | 3.7 | :pep:`479`: | -| | | | *StopIteration handling inside generators* | -+------------------+-------------+--------------+---------------------------------------------+ -| annotations | 3.7.0b1 | TBD [1]_ | :pep:`563`: | -| | | | *Postponed evaluation of annotations* | -+------------------+-------------+--------------+---------------------------------------------+ - -.. XXX Adding a new entry? Remember to update simple_stmts.rst, too. - .. [1] ``from __future__ import annotations`` was previously scheduled to become mandatory in Python 3.10, but the Python Steering Council @@ -115,3 +126,6 @@ language using this mechanism: :ref:`future` How the compiler treats future imports. + + :pep:`236` - Back to the __future__ + The original proposal for the __future__ mechanism. From 97fb2480e4807a34b8197243ad57566ed7769e24 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Mon, 29 Jan 2024 12:56:11 +0300 Subject: [PATCH 011/507] gh-101100: Fix sphinx warnings in `Doc/c-api/memoryview.rst` (GH-114669) --- Doc/c-api/memoryview.rst | 13 +++++++++++++ Doc/tools/.nitignore | 1 - 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Doc/c-api/memoryview.rst b/Doc/c-api/memoryview.rst index 2aa43318e7a455c..f6038032805259f 100644 --- a/Doc/c-api/memoryview.rst +++ b/Doc/c-api/memoryview.rst @@ -20,6 +20,17 @@ any other object. read/write, otherwise it may be either read-only or read/write at the discretion of the exporter. + +.. c:macro:: PyBUF_READ + + Flag to request a readonly buffer. + + +.. c:macro:: PyBUF_WRITE + + Flag to request a writable buffer. + + .. c:function:: PyObject *PyMemoryView_FromMemory(char *mem, Py_ssize_t size, int flags) Create a memoryview object using *mem* as the underlying buffer. @@ -41,6 +52,8 @@ any other object. original memory. Otherwise, a copy is made and the memoryview points to a new bytes object. + *buffertype* can be one of :c:macro:`PyBUF_READ` or :c:macro:`PyBUF_WRITE`. + .. c:function:: int PyMemoryView_Check(PyObject *obj) diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 8b6847ef2a7d76a..b8b7c2299ca9f46 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -9,7 +9,6 @@ Doc/c-api/gcsupport.rst Doc/c-api/init.rst Doc/c-api/init_config.rst Doc/c-api/intro.rst -Doc/c-api/memoryview.rst Doc/c-api/module.rst Doc/c-api/stable.rst Doc/c-api/sys.rst From b7a12ab2146f946ae57e2d8019372cafe94d8375 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 29 Jan 2024 15:12:19 +0200 Subject: [PATCH 012/507] gh-101100: Fix Sphinx warnings in `whatsnew/2.2.rst` (#112366) Co-authored-by: Hugo van Kemenade --- Doc/tools/.nitignore | 1 - Doc/whatsnew/2.2.rst | 140 +++++++++++++++++++++---------------------- 2 files changed, 70 insertions(+), 71 deletions(-) diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index b8b7c2299ca9f46..763503205e16709 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -90,7 +90,6 @@ Doc/tutorial/datastructures.rst Doc/using/windows.rst Doc/whatsnew/2.0.rst Doc/whatsnew/2.1.rst -Doc/whatsnew/2.2.rst Doc/whatsnew/2.4.rst Doc/whatsnew/2.5.rst Doc/whatsnew/2.6.rst diff --git a/Doc/whatsnew/2.2.rst b/Doc/whatsnew/2.2.rst index 6efc23a82de9232..968bd7a126bdf0b 100644 --- a/Doc/whatsnew/2.2.rst +++ b/Doc/whatsnew/2.2.rst @@ -53,9 +53,9 @@ A long time ago I wrote a web page listing flaws in Python's design. One of the most significant flaws was that it's impossible to subclass Python types implemented in C. In particular, it's not possible to subclass built-in types, so you can't just subclass, say, lists in order to add a single useful method to -them. The :mod:`UserList` module provides a class that supports all of the +them. The :mod:`!UserList` module provides a class that supports all of the methods of lists and that can be subclassed further, but there's lots of C code -that expects a regular Python list and won't accept a :class:`UserList` +that expects a regular Python list and won't accept a :class:`!UserList` instance. Python 2.2 fixes this, and in the process adds some exciting new capabilities. @@ -69,7 +69,7 @@ A brief summary: * It's also possible to automatically call methods on accessing or setting an instance attribute by using a new mechanism called :dfn:`properties`. Many uses - of :meth:`__getattr__` can be rewritten to use properties instead, making the + of :meth:`!__getattr__` can be rewritten to use properties instead, making the resulting code simpler and faster. As a small side benefit, attributes can now have docstrings, too. @@ -120,7 +120,7 @@ added so if no built-in type is suitable, you can just subclass This means that :keyword:`class` statements that don't have any base classes are always classic classes in Python 2.2. (Actually you can also change this by -setting a module-level variable named :attr:`__metaclass__` --- see :pep:`253` +setting a module-level variable named :attr:`!__metaclass__` --- see :pep:`253` for the details --- but it's easier to just subclass :class:`object`.) The type objects for the built-in types are available as built-ins, named using @@ -134,8 +134,8 @@ type objects that behave as factories when called. :: 123 To make the set of types complete, new type objects such as :func:`dict` and -:func:`file` have been added. Here's a more interesting example, adding a -:meth:`lock` method to file objects:: +:func:`!file` have been added. Here's a more interesting example, adding a +:meth:`!lock` method to file objects:: class LockableFile(file): def lock (self, operation, length=0, start=0, whence=0): @@ -146,7 +146,7 @@ To make the set of types complete, new type objects such as :func:`dict` and The now-obsolete :mod:`!posixfile` module contained a class that emulated all of a file object's methods and also added a :meth:`!lock` method, but this class couldn't be passed to internal functions that expected a built-in file, -something which is possible with our new :class:`LockableFile`. +something which is possible with our new :class:`!LockableFile`. Descriptors @@ -154,11 +154,11 @@ Descriptors In previous versions of Python, there was no consistent way to discover what attributes and methods were supported by an object. There were some informal -conventions, such as defining :attr:`__members__` and :attr:`__methods__` +conventions, such as defining :attr:`!__members__` and :attr:`!__methods__` attributes that were lists of names, but often the author of an extension type or a class wouldn't bother to define them. You could fall back on inspecting the :attr:`~object.__dict__` of an object, but when class inheritance or an arbitrary -:meth:`__getattr__` hook were in use this could still be inaccurate. +:meth:`!__getattr__` hook were in use this could still be inaccurate. The one big idea underlying the new class model is that an API for describing the attributes of an object using :dfn:`descriptors` has been formalized. @@ -171,7 +171,7 @@ attributes of their own: * :attr:`~definition.__name__` is the attribute's name. -* :attr:`__doc__` is the attribute's docstring. +* :attr:`!__doc__` is the attribute's docstring. * ``__get__(object)`` is a method that retrieves the attribute value from *object*. @@ -186,7 +186,7 @@ are:: descriptor = obj.__class__.x descriptor.__get__(obj) -For methods, :meth:`descriptor.__get__` returns a temporary object that's +For methods, :meth:`!descriptor.__get__` returns a temporary object that's callable, and wraps up the instance and the method to be called on it. This is also why static methods and class methods are now possible; they have descriptors that wrap up just the method, or the method and the class. As a @@ -204,7 +204,7 @@ methods are defined like this:: ... g = classmethod(g) -The :func:`staticmethod` function takes the function :func:`f`, and returns it +The :func:`staticmethod` function takes the function :func:`!f`, and returns it wrapped up in a descriptor so it can be stored in the class object. You might expect there to be special syntax for creating such methods (``def static f``, ``defstatic f()``, or something like that) but no such syntax has been defined @@ -232,10 +232,10 @@ like this:: f = eiffelmethod(f, pre_f, post_f) -Note that a person using the new :func:`eiffelmethod` doesn't have to understand +Note that a person using the new :func:`!eiffelmethod` doesn't have to understand anything about descriptors. This is why I think the new features don't increase the basic complexity of the language. There will be a few wizards who need to -know about it in order to write :func:`eiffelmethod` or the ZODB or whatever, +know about it in order to write :func:`!eiffelmethod` or the ZODB or whatever, but most users will just write code on top of the resulting libraries and ignore the implementation details. @@ -263,10 +263,10 @@ from :pep:`253` by Guido van Rossum):: The lookup rule for classic classes is simple but not very smart; the base classes are searched depth-first, going from left to right. A reference to -:meth:`D.save` will search the classes :class:`D`, :class:`B`, and then -:class:`A`, where :meth:`save` would be found and returned. :meth:`C.save` -would never be found at all. This is bad, because if :class:`C`'s :meth:`save` -method is saving some internal state specific to :class:`C`, not calling it will +:meth:`!D.save` will search the classes :class:`!D`, :class:`!B`, and then +:class:`!A`, where :meth:`!save` would be found and returned. :meth:`!C.save` +would never be found at all. This is bad, because if :class:`!C`'s :meth:`!save` +method is saving some internal state specific to :class:`!C`, not calling it will result in that state never getting saved. New-style classes follow a different algorithm that's a bit more complicated to @@ -276,22 +276,22 @@ produces more useful results for really complicated inheritance graphs.) #. List all the base classes, following the classic lookup rule and include a class multiple times if it's visited repeatedly. In the above example, the list - of visited classes is [:class:`D`, :class:`B`, :class:`A`, :class:`C`, - :class:`A`]. + of visited classes is [:class:`!D`, :class:`!B`, :class:`!A`, :class:`!C`, + :class:`!A`]. #. Scan the list for duplicated classes. If any are found, remove all but one occurrence, leaving the *last* one in the list. In the above example, the list - becomes [:class:`D`, :class:`B`, :class:`C`, :class:`A`] after dropping + becomes [:class:`!D`, :class:`!B`, :class:`!C`, :class:`!A`] after dropping duplicates. -Following this rule, referring to :meth:`D.save` will return :meth:`C.save`, +Following this rule, referring to :meth:`!D.save` will return :meth:`!C.save`, which is the behaviour we're after. This lookup rule is the same as the one followed by Common Lisp. A new built-in function, :func:`super`, provides a way to get at a class's superclasses without having to reimplement Python's algorithm. The most commonly used form will be ``super(class, obj)``, which returns a bound superclass object (not the actual class object). This form will be used in methods to call a method in the superclass; for example, -:class:`D`'s :meth:`save` method would look like this:: +:class:`!D`'s :meth:`!save` method would look like this:: class D (B,C): def save (self): @@ -309,7 +309,7 @@ Attribute Access ---------------- A fair number of sophisticated Python classes define hooks for attribute access -using :meth:`__getattr__`; most commonly this is done for convenience, to make +using :meth:`~object.__getattr__`; most commonly this is done for convenience, to make code more readable by automatically mapping an attribute access such as ``obj.parent`` into a method call such as ``obj.get_parent``. Python 2.2 adds some new ways of controlling attribute access. @@ -321,22 +321,22 @@ instance's dictionary. New-style classes also support a new method, ``__getattribute__(attr_name)``. The difference between the two methods is -that :meth:`__getattribute__` is *always* called whenever any attribute is -accessed, while the old :meth:`__getattr__` is only called if ``foo`` isn't +that :meth:`~object.__getattribute__` is *always* called whenever any attribute is +accessed, while the old :meth:`~object.__getattr__` is only called if ``foo`` isn't found in the instance's dictionary. However, Python 2.2's support for :dfn:`properties` will often be a simpler way -to trap attribute references. Writing a :meth:`__getattr__` method is +to trap attribute references. Writing a :meth:`!__getattr__` method is complicated because to avoid recursion you can't use regular attribute accesses inside them, and instead have to mess around with the contents of -:attr:`~object.__dict__`. :meth:`__getattr__` methods also end up being called by Python -when it checks for other methods such as :meth:`__repr__` or :meth:`__coerce__`, +:attr:`~object.__dict__`. :meth:`~object.__getattr__` methods also end up being called by Python +when it checks for other methods such as :meth:`~object.__repr__` or :meth:`!__coerce__`, and so have to be written with this in mind. Finally, calling a function on every attribute access results in a sizable performance loss. :class:`property` is a new built-in type that packages up three functions that get, set, or delete an attribute, and a docstring. For example, if you want to -define a :attr:`size` attribute that's computed, but also settable, you could +define a :attr:`!size` attribute that's computed, but also settable, you could write:: class C(object): @@ -355,9 +355,9 @@ write:: "Storage size of this instance") That is certainly clearer and easier to write than a pair of -:meth:`__getattr__`/:meth:`__setattr__` methods that check for the :attr:`size` +:meth:`!__getattr__`/:meth:`!__setattr__` methods that check for the :attr:`!size` attribute and handle it specially while retrieving all other attributes from the -instance's :attr:`~object.__dict__`. Accesses to :attr:`size` are also the only ones +instance's :attr:`~object.__dict__`. Accesses to :attr:`!size` are also the only ones which have to perform the work of calling a function, so references to other attributes run at their usual speed. @@ -447,7 +447,7 @@ an iterator for the object *obj*, while ``iter(C, sentinel)`` returns an iterator that will invoke the callable object *C* until it returns *sentinel* to signal that the iterator is done. -Python classes can define an :meth:`__iter__` method, which should create and +Python classes can define an :meth:`!__iter__` method, which should create and return a new iterator for the object; if the object is its own iterator, this method can just return ``self``. In particular, iterators will usually be their own iterators. Extension types implemented in C can implement a :c:member:`~PyTypeObject.tp_iter` @@ -478,7 +478,7 @@ there are no more values to be returned, calling :meth:`next` should raise the In 2.2, Python's :keyword:`for` statement no longer expects a sequence; it expects something for which :func:`iter` will return an iterator. For backward compatibility and convenience, an iterator is automatically constructed for -sequences that don't implement :meth:`__iter__` or a :c:member:`~PyTypeObject.tp_iter` slot, so +sequences that don't implement :meth:`!__iter__` or a :c:member:`~PyTypeObject.tp_iter` slot, so ``for i in [1,2,3]`` will still work. Wherever the Python interpreter loops over a sequence, it's been changed to use the iterator protocol. This means you can do things like this:: @@ -510,8 +510,8 @@ Iterator support has been added to some of Python's basic types. Calling Oct 10 That's just the default behaviour. If you want to iterate over keys, values, or -key/value pairs, you can explicitly call the :meth:`iterkeys`, -:meth:`itervalues`, or :meth:`iteritems` methods to get an appropriate iterator. +key/value pairs, you can explicitly call the :meth:`!iterkeys`, +:meth:`!itervalues`, or :meth:`!iteritems` methods to get an appropriate iterator. In a minor related change, the :keyword:`in` operator now works on dictionaries, so ``key in dict`` is now equivalent to ``dict.has_key(key)``. @@ -580,7 +580,7 @@ allowed inside the :keyword:`!try` block of a :keyword:`try`...\ :keyword:`finally` statement; read :pep:`255` for a full explanation of the interaction between :keyword:`!yield` and exceptions.) -Here's a sample usage of the :func:`generate_ints` generator:: +Here's a sample usage of the :func:`!generate_ints` generator:: >>> gen = generate_ints(3) >>> gen @@ -641,7 +641,7 @@ like:: sentence := "Store it in the neighboring harbor" if (i := find("or", sentence)) > 5 then write(i) -In Icon the :func:`find` function returns the indexes at which the substring +In Icon the :func:`!find` function returns the indexes at which the substring "or" is found: 3, 23, 33. In the :keyword:`if` statement, ``i`` is first assigned a value of 3, but 3 is less than 5, so the comparison fails, and Icon retries it with the second value of 23. 23 is greater than 5, so the comparison @@ -671,7 +671,7 @@ PEP 237: Unifying Long Integers and Integers In recent versions, the distinction between regular integers, which are 32-bit values on most machines, and long integers, which can be of arbitrary size, was becoming an annoyance. For example, on platforms that support files larger than -``2**32`` bytes, the :meth:`tell` method of file objects has to return a long +``2**32`` bytes, the :meth:`!tell` method of file objects has to return a long integer. However, there were various bits of Python that expected plain integers and would raise an error if a long integer was provided instead. For example, in Python 1.5, only regular integers could be used as a slice index, and @@ -752,7 +752,7 @@ Here are the changes 2.2 introduces: 0.5. Without the ``__future__`` statement, ``/`` still means classic division. The default meaning of ``/`` will not change until Python 3.0. -* Classes can define methods called :meth:`__truediv__` and :meth:`__floordiv__` +* Classes can define methods called :meth:`~object.__truediv__` and :meth:`~object.__floordiv__` to overload the two division operators. At the C level, there are also slots in the :c:type:`PyNumberMethods` structure so extension types can define the two operators. @@ -785,17 +785,17 @@ support.) When built to use UCS-4 (a "wide Python"), the interpreter can natively handle Unicode characters from U+000000 to U+110000, so the range of legal values for -the :func:`unichr` function is expanded accordingly. Using an interpreter +the :func:`!unichr` function is expanded accordingly. Using an interpreter compiled to use UCS-2 (a "narrow Python"), values greater than 65535 will still -cause :func:`unichr` to raise a :exc:`ValueError` exception. This is all +cause :func:`!unichr` to raise a :exc:`ValueError` exception. This is all described in :pep:`261`, "Support for 'wide' Unicode characters"; consult it for further details. Another change is simpler to explain. Since their introduction, Unicode strings -have supported an :meth:`encode` method to convert the string to a selected +have supported an :meth:`!encode` method to convert the string to a selected encoding such as UTF-8 or Latin-1. A symmetric ``decode([*encoding*])`` method has been added to 8-bit strings (though not to Unicode strings) in 2.2. -:meth:`decode` assumes that the string is in the specified encoding and decodes +:meth:`!decode` assumes that the string is in the specified encoding and decodes it, returning whatever is returned by the codec. Using this new feature, codecs have been added for tasks not directly related to @@ -819,10 +819,10 @@ encoding, and compression with the :mod:`zlib` module:: >>> "sheesh".encode('rot-13') 'furrfu' -To convert a class instance to Unicode, a :meth:`__unicode__` method can be -defined by a class, analogous to :meth:`__str__`. +To convert a class instance to Unicode, a :meth:`!__unicode__` method can be +defined by a class, analogous to :meth:`!__str__`. -:meth:`encode`, :meth:`decode`, and :meth:`__unicode__` were implemented by +:meth:`!encode`, :meth:`!decode`, and :meth:`!__unicode__` were implemented by Marc-André Lemburg. The changes to support using UCS-4 internally were implemented by Fredrik Lundh and Martin von Löwis. @@ -859,7 +859,7 @@ doesn't work:: return g(value-1) + 1 ... -The function :func:`g` will always raise a :exc:`NameError` exception, because +The function :func:`!g` will always raise a :exc:`NameError` exception, because the binding of the name ``g`` isn't in either its local namespace or in the module-level namespace. This isn't much of a problem in practice (how often do you recursively define interior functions like this?), but this also made using @@ -915,7 +915,7 @@ To make the preceding explanation a bit clearer, here's an example:: Line 4 containing the ``exec`` statement is a syntax error, since ``exec`` would define a new local variable named ``x`` whose value should -be accessed by :func:`g`. +be accessed by :func:`!g`. This shouldn't be much of a limitation, since ``exec`` is rarely used in most Python code (and when it is used, it's often a sign of a poor design @@ -933,7 +933,7 @@ anyway). New and Improved Modules ======================== -* The :mod:`xmlrpclib` module was contributed to the standard library by Fredrik +* The :mod:`!xmlrpclib` module was contributed to the standard library by Fredrik Lundh, providing support for writing XML-RPC clients. XML-RPC is a simple remote procedure call protocol built on top of HTTP and XML. For example, the following snippet retrieves a list of RSS channels from the O'Reilly Network, @@ -956,7 +956,7 @@ New and Improved Modules # 'description': 'A utility which converts HTML to XSL FO.', # 'title': 'html2fo 0.3 (Default)'}, ... ] - The :mod:`SimpleXMLRPCServer` module makes it easy to create straightforward + The :mod:`!SimpleXMLRPCServer` module makes it easy to create straightforward XML-RPC servers. See http://xmlrpc.scripting.com/ for more information about XML-RPC. * The new :mod:`hmac` module implements the HMAC algorithm described by @@ -964,9 +964,9 @@ New and Improved Modules * Several functions that originally returned lengthy tuples now return pseudo-sequences that still behave like tuples but also have mnemonic attributes such - as memberst_mtime or :attr:`tm_year`. The enhanced functions include - :func:`stat`, :func:`fstat`, :func:`statvfs`, and :func:`fstatvfs` in the - :mod:`os` module, and :func:`localtime`, :func:`gmtime`, and :func:`strptime` in + as :attr:`!memberst_mtime` or :attr:`!tm_year`. The enhanced functions include + :func:`~os.stat`, :func:`~os.fstat`, :func:`~os.statvfs`, and :func:`~os.fstatvfs` in the + :mod:`os` module, and :func:`~time.localtime`, :func:`~time.gmtime`, and :func:`~time.strptime` in the :mod:`time` module. For example, to obtain a file's size using the old tuples, you'd end up writing @@ -999,7 +999,7 @@ New and Improved Modules underlying the :mod:`re` module. For example, the :func:`re.sub` and :func:`re.split` functions have been rewritten in C. Another contributed patch speeds up certain Unicode character ranges by a factor of two, and a new - :meth:`finditer` method that returns an iterator over all the non-overlapping + :meth:`~re.finditer` method that returns an iterator over all the non-overlapping matches in a given string. (SRE is maintained by Fredrik Lundh. The BIGCHARSET patch was contributed by Martin von Löwis.) @@ -1012,33 +1012,33 @@ New and Improved Modules new extensions: the NAMESPACE extension defined in :rfc:`2342`, SORT, GETACL and SETACL. (Contributed by Anthony Baxter and Michel Pelletier.) -* The :mod:`rfc822` module's parsing of email addresses is now compliant with +* The :mod:`!rfc822` module's parsing of email addresses is now compliant with :rfc:`2822`, an update to :rfc:`822`. (The module's name is *not* going to be changed to ``rfc2822``.) A new package, :mod:`email`, has also been added for parsing and generating e-mail messages. (Contributed by Barry Warsaw, and arising out of his work on Mailman.) -* The :mod:`difflib` module now contains a new :class:`Differ` class for +* The :mod:`difflib` module now contains a new :class:`!Differ` class for producing human-readable lists of changes (a "delta") between two sequences of - lines of text. There are also two generator functions, :func:`ndiff` and - :func:`restore`, which respectively return a delta from two sequences, or one of + lines of text. There are also two generator functions, :func:`!ndiff` and + :func:`!restore`, which respectively return a delta from two sequences, or one of the original sequences from a delta. (Grunt work contributed by David Goodger, from ndiff.py code by Tim Peters who then did the generatorization.) -* New constants :const:`ascii_letters`, :const:`ascii_lowercase`, and - :const:`ascii_uppercase` were added to the :mod:`string` module. There were - several modules in the standard library that used :const:`string.letters` to +* New constants :const:`!ascii_letters`, :const:`!ascii_lowercase`, and + :const:`!ascii_uppercase` were added to the :mod:`string` module. There were + several modules in the standard library that used :const:`!string.letters` to mean the ranges A-Za-z, but that assumption is incorrect when locales are in - use, because :const:`string.letters` varies depending on the set of legal + use, because :const:`!string.letters` varies depending on the set of legal characters defined by the current locale. The buggy modules have all been fixed - to use :const:`ascii_letters` instead. (Reported by an unknown person; fixed by + to use :const:`!ascii_letters` instead. (Reported by an unknown person; fixed by Fred L. Drake, Jr.) * The :mod:`mimetypes` module now makes it easier to use alternative MIME-type - databases by the addition of a :class:`MimeTypes` class, which takes a list of + databases by the addition of a :class:`~mimetypes.MimeTypes` class, which takes a list of filenames to be parsed. (Contributed by Fred L. Drake, Jr.) -* A :class:`Timer` class was added to the :mod:`threading` module that allows +* A :class:`~threading.Timer` class was added to the :mod:`threading` module that allows scheduling an activity to happen at some future time. (Contributed by Itamar Shtull-Trauring.) @@ -1114,7 +1114,7 @@ code, none of the changes described here will affect you very much. * Two new wrapper functions, :c:func:`PyOS_snprintf` and :c:func:`PyOS_vsnprintf` were added to provide cross-platform implementations for the relatively new :c:func:`snprintf` and :c:func:`vsnprintf` C lib APIs. In contrast to the standard - :c:func:`sprintf` and :c:func:`vsprintf` functions, the Python versions check the + :c:func:`sprintf` and :c:func:`!vsprintf` functions, the Python versions check the bounds of the buffer used to protect against buffer overruns. (Contributed by M.-A. Lemburg.) @@ -1212,12 +1212,12 @@ Some of the more notable changes are: * The :file:`Tools/scripts/ftpmirror.py` script now parses a :file:`.netrc` file, if you have one. (Contributed by Mike Romberg.) -* Some features of the object returned by the :func:`xrange` function are now +* Some features of the object returned by the :func:`!xrange` function are now deprecated, and trigger warnings when they're accessed; they'll disappear in - Python 2.3. :class:`xrange` objects tried to pretend they were full sequence + Python 2.3. :class:`!xrange` objects tried to pretend they were full sequence types by supporting slicing, sequence multiplication, and the :keyword:`in` operator, but these features were rarely used and therefore buggy. The - :meth:`tolist` method and the :attr:`start`, :attr:`stop`, and :attr:`step` + :meth:`!tolist` method and the :attr:`!start`, :attr:`!stop`, and :attr:`!step` attributes are also being deprecated. At the C level, the fourth argument to the :c:func:`!PyRange_New` function, ``repeat``, has also been deprecated. From e8b8f5e9c2da6a436360ce648061c90bdfcba863 Mon Sep 17 00:00:00 2001 From: Skip Montanaro Date: Mon, 29 Jan 2024 08:43:44 -0600 Subject: [PATCH 013/507] gh-101100: Fix datetime reference warnings (GH-114661) Co-authored-by: Serhiy Storchaka --- Doc/conf.py | 5 ++ Doc/library/datetime.rst | 106 +++++++++++++++++++-------------------- Doc/tools/.nitignore | 1 - 3 files changed, 58 insertions(+), 54 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index 458954370debe28..a96e7787d167a37 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -89,20 +89,25 @@ nitpick_ignore = [ # Standard C functions ('c:func', 'calloc'), + ('c:func', 'ctime'), ('c:func', 'dlopen'), ('c:func', 'exec'), ('c:func', 'fcntl'), ('c:func', 'fork'), ('c:func', 'free'), + ('c:func', 'gettimeofday'), ('c:func', 'gmtime'), + ('c:func', 'localeconv'), ('c:func', 'localtime'), ('c:func', 'main'), ('c:func', 'malloc'), + ('c:func', 'mktime'), ('c:func', 'printf'), ('c:func', 'realloc'), ('c:func', 'snprintf'), ('c:func', 'sprintf'), ('c:func', 'stat'), + ('c:func', 'strftime'), ('c:func', 'system'), ('c:func', 'time'), ('c:func', 'vsnprintf'), diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index b36f8c19cd6040b..47ecb0ba331bdc4 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -14,7 +14,7 @@ .. XXX what order should the types be discussed in? -The :mod:`datetime` module supplies classes for manipulating dates and times. +The :mod:`!datetime` module supplies classes for manipulating dates and times. While date and time arithmetic is supported, the focus of the implementation is on efficient attribute extraction for output formatting and manipulation. @@ -70,7 +70,7 @@ These :class:`tzinfo` objects capture information about the offset from UTC time, the time zone name, and whether daylight saving time is in effect. Only one concrete :class:`tzinfo` class, the :class:`timezone` class, is -supplied by the :mod:`datetime` module. The :class:`timezone` class can +supplied by the :mod:`!datetime` module. The :class:`timezone` class can represent simple timezones with fixed offsets from UTC, such as UTC itself or North American EST and EDT timezones. Supporting timezones at deeper levels of detail is up to the application. The rules for time adjustment across the @@ -80,7 +80,7 @@ standard suitable for every application aside from UTC. Constants --------- -The :mod:`datetime` module exports the following constants: +The :mod:`!datetime` module exports the following constants: .. data:: MINYEAR @@ -631,7 +631,7 @@ Notes: date2.toordinal()``. Date comparison raises :exc:`TypeError` if the other comparand isn't also a :class:`date` object. However, ``NotImplemented`` is returned instead if the other comparand has a - :meth:`timetuple` attribute. This hook gives other kinds of date objects a + :attr:`~date.timetuple` attribute. This hook gives other kinds of date objects a chance at implementing mixed-type comparison. If not, when a :class:`date` object is compared to an object of a different type, :exc:`TypeError` is raised unless the comparison is ``==`` or ``!=``. The latter cases return @@ -1215,7 +1215,7 @@ Supported operations: object addresses, datetime comparison normally raises :exc:`TypeError` if the other comparand isn't also a :class:`.datetime` object. However, ``NotImplemented`` is returned instead if the other comparand has a - :meth:`timetuple` attribute. This hook gives other kinds of date objects a + :attr:`~.datetime.timetuple` attribute. This hook gives other kinds of date objects a chance at implementing mixed-type comparison. If not, when a :class:`.datetime` object is compared to an object of a different type, :exc:`TypeError` is raised unless the comparison is ``==`` or ``!=``. The latter cases return @@ -1347,22 +1347,22 @@ Instance methods: where ``yday = d.toordinal() - date(d.year, 1, 1).toordinal() + 1`` is the day number within the current year starting with ``1`` for January - 1st. The :attr:`tm_isdst` flag of the result is set according to the + 1st. The :attr:`~time.struct_time.tm_isdst` flag of the result is set according to the :meth:`dst` method: :attr:`.tzinfo` is ``None`` or :meth:`dst` returns - ``None``, :attr:`tm_isdst` is set to ``-1``; else if :meth:`dst` returns a - non-zero value, :attr:`tm_isdst` is set to ``1``; else :attr:`tm_isdst` is + ``None``, :attr:`!tm_isdst` is set to ``-1``; else if :meth:`dst` returns a + non-zero value, :attr:`!tm_isdst` is set to ``1``; else :attr:`!tm_isdst` is set to ``0``. .. method:: datetime.utctimetuple() If :class:`.datetime` instance *d* is naive, this is the same as - ``d.timetuple()`` except that :attr:`tm_isdst` is forced to 0 regardless of what + ``d.timetuple()`` except that :attr:`~.time.struct_time.tm_isdst` is forced to 0 regardless of what ``d.dst()`` returns. DST is never in effect for a UTC time. If *d* is aware, *d* is normalized to UTC time, by subtracting ``d.utcoffset()``, and a :class:`time.struct_time` for the - normalized time is returned. :attr:`tm_isdst` is forced to 0. Note + normalized time is returned. :attr:`!tm_isdst` is forced to 0. Note that an :exc:`OverflowError` may be raised if *d*.year was ``MINYEAR`` or ``MAXYEAR`` and UTC adjustment spills over a year boundary. @@ -1550,7 +1550,7 @@ Instance methods: Examples of Usage: :class:`.datetime` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Examples of working with :class:`~datetime.datetime` objects: +Examples of working with :class:`.datetime` objects: .. doctest:: @@ -1761,9 +1761,9 @@ is aware, :exc:`TypeError` is raised if an order comparison is attempted. For eq comparisons, naive instances are never equal to aware instances. If both comparands are aware, and have -the same :attr:`~time.tzinfo` attribute, the common :attr:`~time.tzinfo` attribute is +the same :attr:`~.time.tzinfo` attribute, the common :attr:`!tzinfo` attribute is ignored and the base times are compared. If both comparands are aware and -have different :attr:`~time.tzinfo` attributes, the comparands are first adjusted by +have different :attr:`!tzinfo` attributes, the comparands are first adjusted by subtracting their UTC offsets (obtained from ``self.utcoffset()``). In order to stop mixed-type comparisons from falling back to the default comparison by object address, when a :class:`.time` object is compared to an object of a @@ -1771,7 +1771,7 @@ different type, :exc:`TypeError` is raised unless the comparison is ``==`` or ``!=``. The latter cases return :const:`False` or :const:`True`, respectively. .. versionchanged:: 3.3 - Equality comparisons between aware and naive :class:`~datetime.time` instances + Equality comparisons between aware and naive :class:`.time` instances don't raise :exc:`TypeError`. In Boolean contexts, a :class:`.time` object is always considered to be true. @@ -1981,7 +1981,7 @@ Examples of working with a :class:`.time` object:: You need to derive a concrete subclass, and (at least) supply implementations of the standard :class:`tzinfo` methods needed by the - :class:`.datetime` methods you use. The :mod:`datetime` module provides + :class:`.datetime` methods you use. The :mod:`!datetime` module provides :class:`timezone`, a simple concrete subclass of :class:`tzinfo` which can represent timezones with fixed offset from UTC such as UTC itself or North American EST and EDT. @@ -1994,7 +1994,7 @@ Examples of working with a :class:`.time` object:: A concrete subclass of :class:`tzinfo` may need to implement the following methods. Exactly which methods are needed depends on the uses made of aware - :mod:`datetime` objects. If in doubt, simply implement all of them. + :mod:`!datetime` objects. If in doubt, simply implement all of them. .. method:: tzinfo.utcoffset(dt) @@ -2035,7 +2035,7 @@ Examples of working with a :class:`.time` object:: already been added to the UTC offset returned by :meth:`utcoffset`, so there's no need to consult :meth:`dst` unless you're interested in obtaining DST info separately. For example, :meth:`datetime.timetuple` calls its :attr:`~.datetime.tzinfo` - attribute's :meth:`dst` method to determine how the :attr:`tm_isdst` flag + attribute's :meth:`dst` method to determine how the :attr:`~time.struct_time.tm_isdst` flag should be set, and :meth:`tzinfo.fromutc` calls :meth:`dst` to account for DST changes when crossing time zones. @@ -2051,7 +2051,7 @@ Examples of working with a :class:`.time` object:: relies on this, but cannot detect violations; it's the programmer's responsibility to ensure it. If a :class:`tzinfo` subclass cannot guarantee this, it may be able to override the default implementation of - :meth:`tzinfo.fromutc` to work correctly with :meth:`astimezone` regardless. + :meth:`tzinfo.fromutc` to work correctly with :meth:`~.datetime.astimezone` regardless. Most implementations of :meth:`dst` will probably look like one of these two:: @@ -2080,7 +2080,7 @@ Examples of working with a :class:`.time` object:: .. method:: tzinfo.tzname(dt) Return the time zone name corresponding to the :class:`.datetime` object *dt*, as - a string. Nothing about string names is defined by the :mod:`datetime` module, + a string. Nothing about string names is defined by the :mod:`!datetime` module, and there's no requirement that it mean anything in particular. For example, "GMT", "UTC", "-500", "-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies. Return ``None`` if a string name isn't known. Note that this is @@ -2128,7 +2128,7 @@ There is one more :class:`tzinfo` method that a subclass may wish to override: different years. An example of a time zone the default :meth:`fromutc` implementation may not handle correctly in all cases is one where the standard offset (from UTC) depends on the specific date and time passed, which can happen - for political reasons. The default implementations of :meth:`astimezone` and + for political reasons. The default implementations of :meth:`~.datetime.astimezone` and :meth:`fromutc` may not produce the result you want if the result is one of the hours straddling the moment the standard offset changes. @@ -2194,10 +2194,10 @@ hour that can't be spelled unambiguously in local wall time: the last hour of daylight time. In Eastern, that's times of the form 5:MM UTC on the day daylight time ends. The local wall clock leaps from 1:59 (daylight time) back to 1:00 (standard time) again. Local times of the form 1:MM are ambiguous. -:meth:`astimezone` mimics the local clock's behavior by mapping two adjacent UTC +:meth:`~.datetime.astimezone` mimics the local clock's behavior by mapping two adjacent UTC hours into the same local hour then. In the Eastern example, UTC times of the form 5:MM and 6:MM both map to 1:MM when converted to Eastern, but earlier times -have the :attr:`~datetime.fold` attribute set to 0 and the later times have it set to 1. +have the :attr:`~.datetime.fold` attribute set to 0 and the later times have it set to 1. For example, at the Fall back transition of 2016, we get:: >>> u0 = datetime(2016, 11, 6, 4, tzinfo=timezone.utc) @@ -2212,10 +2212,10 @@ For example, at the Fall back transition of 2016, we get:: 07:00:00 UTC = 02:00:00 EST 0 Note that the :class:`.datetime` instances that differ only by the value of the -:attr:`~datetime.fold` attribute are considered equal in comparisons. +:attr:`~.datetime.fold` attribute are considered equal in comparisons. Applications that can't bear wall-time ambiguities should explicitly check the -value of the :attr:`~datetime.fold` attribute or avoid using hybrid +value of the :attr:`~.datetime.fold` attribute or avoid using hybrid :class:`tzinfo` subclasses; there are no ambiguities when using :class:`timezone`, or any other fixed-offset :class:`tzinfo` subclass (such as a class representing only EST (fixed offset -5 hours), or only EDT (fixed offset -4 hours)). @@ -2223,7 +2223,7 @@ only EST (fixed offset -5 hours), or only EDT (fixed offset -4 hours)). .. seealso:: :mod:`zoneinfo` - The :mod:`datetime` module has a basic :class:`timezone` class (for + The :mod:`!datetime` module has a basic :class:`timezone` class (for handling arbitrary fixed offsets from UTC) and its :attr:`timezone.utc` attribute (a UTC timezone instance). @@ -2241,7 +2241,7 @@ only EST (fixed offset -5 hours), or only EDT (fixed offset -4 hours)). .. _datetime-timezone: :class:`timezone` Objects --------------------------- +------------------------- The :class:`timezone` class is a subclass of :class:`tzinfo`, each instance of which represents a timezone defined by a fixed offset from @@ -2316,8 +2316,8 @@ Class attributes: .. _strftime-strptime-behavior: -:meth:`strftime` and :meth:`strptime` Behavior ----------------------------------------------- +:meth:`~.datetime.strftime` and :meth:`~.datetime.strptime` Behavior +-------------------------------------------------------------------- :class:`date`, :class:`.datetime`, and :class:`.time` objects all support a ``strftime(format)`` method, to create a string representing the time under the @@ -2327,8 +2327,8 @@ Conversely, the :meth:`datetime.strptime` class method creates a :class:`.datetime` object from a string representing a date and time and a corresponding format string. -The table below provides a high-level comparison of :meth:`strftime` -versus :meth:`strptime`: +The table below provides a high-level comparison of :meth:`~.datetime.strftime` +versus :meth:`~.datetime.strptime`: +----------------+--------------------------------------------------------+------------------------------------------------------------------------------+ | | ``strftime`` | ``strptime`` | @@ -2345,8 +2345,8 @@ versus :meth:`strptime`: .. _format-codes: -:meth:`strftime` and :meth:`strptime` Format Codes -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +:meth:`~.datetime.strftime` and :meth:`~.datetime.strptime` Format Codes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ These methods accept format codes that can be used to parse and format dates:: @@ -2485,13 +2485,13 @@ convenience. These parameters all correspond to ISO 8601 date values. | | naive). | -03:07:12.345216 | | +-----------+--------------------------------+------------------------+-------+ -These may not be available on all platforms when used with the :meth:`strftime` +These may not be available on all platforms when used with the :meth:`~.datetime.strftime` method. The ISO 8601 year and ISO 8601 week directives are not interchangeable -with the year and week number directives above. Calling :meth:`strptime` with +with the year and week number directives above. Calling :meth:`~.datetime.strptime` with incomplete or ambiguous ISO 8601 directives will raise a :exc:`ValueError`. The full set of format codes supported varies across platforms, because Python -calls the platform C library's :func:`strftime` function, and platform +calls the platform C library's :c:func:`strftime` function, and platform variations are common. To see the full set of format codes supported on your platform, consult the :manpage:`strftime(3)` documentation. There are also differences between platforms in handling of unsupported format specifiers. @@ -2507,9 +2507,9 @@ Technical Detail Broadly speaking, ``d.strftime(fmt)`` acts like the :mod:`time` module's ``time.strftime(fmt, d.timetuple())`` although not all objects support a -:meth:`timetuple` method. +:meth:`~date.timetuple` method. -For the :meth:`datetime.strptime` class method, the default value is +For the :meth:`.datetime.strptime` class method, the default value is ``1900-01-01T00:00:00.000``: any components not specified in the format string will be pulled from the default value. [#]_ @@ -2544,27 +2544,27 @@ Notes: contain non-ASCII characters. (2) - The :meth:`strptime` method can parse years in the full [1, 9999] range, but + The :meth:`~.datetime.strptime` method can parse years in the full [1, 9999] range, but years < 1000 must be zero-filled to 4-digit width. .. versionchanged:: 3.2 - In previous versions, :meth:`strftime` method was restricted to + In previous versions, :meth:`~.datetime.strftime` method was restricted to years >= 1900. .. versionchanged:: 3.3 - In version 3.2, :meth:`strftime` method was restricted to + In version 3.2, :meth:`~.datetime.strftime` method was restricted to years >= 1000. (3) - When used with the :meth:`strptime` method, the ``%p`` directive only affects + When used with the :meth:`~.datetime.strptime` method, the ``%p`` directive only affects the output hour field if the ``%I`` directive is used to parse the hour. (4) - Unlike the :mod:`time` module, the :mod:`datetime` module does not support + Unlike the :mod:`time` module, the :mod:`!datetime` module does not support leap seconds. (5) - When used with the :meth:`strptime` method, the ``%f`` directive + When used with the :meth:`~.datetime.strptime` method, the ``%f`` directive accepts from one to six digits and zero pads on the right. ``%f`` is an extension to the set of format characters in the C standard (but implemented separately in datetime objects, and therefore always @@ -2577,7 +2577,7 @@ Notes: For an aware object: ``%z`` - :meth:`utcoffset` is transformed into a string of the form + :meth:`~.datetime.utcoffset` is transformed into a string of the form ``±HHMM[SS[.ffffff]]``, where ``HH`` is a 2-digit string giving the number of UTC offset hours, ``MM`` is a 2-digit string giving the number of UTC offset minutes, ``SS`` is a 2-digit string giving the number of UTC offset @@ -2585,14 +2585,14 @@ Notes: offset microseconds. The ``ffffff`` part is omitted when the offset is a whole number of seconds and both the ``ffffff`` and the ``SS`` part is omitted when the offset is a whole number of minutes. For example, if - :meth:`utcoffset` returns ``timedelta(hours=-3, minutes=-30)``, ``%z`` is + :meth:`~.datetime.utcoffset` returns ``timedelta(hours=-3, minutes=-30)``, ``%z`` is replaced with the string ``'-0330'``. .. versionchanged:: 3.7 The UTC offset is not restricted to a whole number of minutes. .. versionchanged:: 3.7 - When the ``%z`` directive is provided to the :meth:`strptime` method, + When the ``%z`` directive is provided to the :meth:`~.datetime.strptime` method, the UTC offsets can have a colon as a separator between hours, minutes and seconds. For example, ``'+01:00:00'`` will be parsed as an offset of one hour. @@ -2603,11 +2603,11 @@ Notes: hours, minutes and seconds. ``%Z`` - In :meth:`strftime`, ``%Z`` is replaced by an empty string if - :meth:`tzname` returns ``None``; otherwise ``%Z`` is replaced by the + In :meth:`~.datetime.strftime`, ``%Z`` is replaced by an empty string if + :meth:`~.datetime.tzname` returns ``None``; otherwise ``%Z`` is replaced by the returned value, which must be a string. - :meth:`strptime` only accepts certain values for ``%Z``: + :meth:`~.datetime.strptime` only accepts certain values for ``%Z``: 1. any value in ``time.tzname`` for your machine's locale 2. the hard-coded values ``UTC`` and ``GMT`` @@ -2617,23 +2617,23 @@ Notes: invalid values. .. versionchanged:: 3.2 - When the ``%z`` directive is provided to the :meth:`strptime` method, an + When the ``%z`` directive is provided to the :meth:`~.datetime.strptime` method, an aware :class:`.datetime` object will be produced. The ``tzinfo`` of the result will be set to a :class:`timezone` instance. (7) - When used with the :meth:`strptime` method, ``%U`` and ``%W`` are only used + When used with the :meth:`~.datetime.strptime` method, ``%U`` and ``%W`` are only used in calculations when the day of the week and the calendar year (``%Y``) are specified. (8) Similar to ``%U`` and ``%W``, ``%V`` is only used in calculations when the day of the week and the ISO year (``%G``) are specified in a - :meth:`strptime` format string. Also note that ``%G`` and ``%Y`` are not + :meth:`~.datetime.strptime` format string. Also note that ``%G`` and ``%Y`` are not interchangeable. (9) - When used with the :meth:`strptime` method, the leading zero is optional + When used with the :meth:`~.datetime.strptime` method, the leading zero is optional for formats ``%d``, ``%m``, ``%H``, ``%I``, ``%M``, ``%S``, ``%j``, ``%U``, ``%W``, and ``%V``. Format ``%y`` does require a leading zero. diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 763503205e16709..bba4fe0d5f2425e 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -26,7 +26,6 @@ Doc/library/asyncio-subprocess.rst Doc/library/bdb.rst Doc/library/collections.rst Doc/library/csv.rst -Doc/library/datetime.rst Doc/library/dbm.rst Doc/library/decimal.rst Doc/library/email.charset.rst From c87233fd3fa77067013c35328f8c4884f0567a59 Mon Sep 17 00:00:00 2001 From: mpage Date: Mon, 29 Jan 2024 07:08:23 -0800 Subject: [PATCH 014/507] gh-112050: Adapt collections.deque to Argument Clinic (#113963) --- .../pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + .../internal/pycore_runtime_init_generated.h | 1 + .../internal/pycore_unicodeobject_generated.h | 3 + ...-01-11-22-58-45.gh-issue-112050.hDuvDW.rst | 1 + Modules/_collectionsmodule.c | 412 ++++++++++------- Modules/clinic/_collectionsmodule.c.h | 418 +++++++++++++++++- 7 files changed, 685 insertions(+), 152 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-11-22-58-45.gh-issue-112050.hDuvDW.rst diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index e92707051c12b7f..57505b5388fd6c3 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1049,6 +1049,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(max_length)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxdigits)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxevents)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxlen)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxmem)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxsplit)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxvalue)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index eb60b80c964d423..0f4f3b619102414 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -538,6 +538,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(max_length) STRUCT_FOR_ID(maxdigits) STRUCT_FOR_ID(maxevents) + STRUCT_FOR_ID(maxlen) STRUCT_FOR_ID(maxmem) STRUCT_FOR_ID(maxsplit) STRUCT_FOR_ID(maxvalue) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 9b39de1d69c6c7f..63a2b54c839a4b5 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1047,6 +1047,7 @@ extern "C" { INIT_ID(max_length), \ INIT_ID(maxdigits), \ INIT_ID(maxevents), \ + INIT_ID(maxlen), \ INIT_ID(maxmem), \ INIT_ID(maxsplit), \ INIT_ID(maxvalue), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 898d386f4cfd05d..bf8cdd85e4be5c1 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -1455,6 +1455,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(maxevents); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(maxlen); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(maxmem); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-11-22-58-45.gh-issue-112050.hDuvDW.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-11-22-58-45.gh-issue-112050.hDuvDW.rst new file mode 100644 index 000000000000000..e5f3d5ea0cea254 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-11-22-58-45.gh-issue-112050.hDuvDW.rst @@ -0,0 +1 @@ +Convert :class:`collections.deque` to use Argument Clinic. diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c index c8cd53de5e22622..ef77d34b10e47b9 100644 --- a/Modules/_collectionsmodule.c +++ b/Modules/_collectionsmodule.c @@ -44,8 +44,11 @@ find_module_state_by_def(PyTypeObject *type) /*[clinic input] module _collections class _tuplegetter "_tuplegetterobject *" "clinic_state()->tuplegetter_type" +class _collections.deque "dequeobject *" "clinic_state()->deque_type" [clinic start generated code]*/ -/*[clinic end generated code: output=da39a3ee5e6b4b0d input=7356042a89862e0e]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=a033cc2a8476b3f1]*/ + +typedef struct dequeobject dequeobject; /* We can safely assume type to be the defining class, * since tuplegetter is not a base type */ @@ -53,6 +56,12 @@ class _tuplegetter "_tuplegetterobject *" "clinic_state()->tuplegetter_type" #include "clinic/_collectionsmodule.c.h" #undef clinic_state +/*[python input] +class dequeobject_converter(self_converter): + type = "dequeobject *" +[python start generated code]*/ +/*[python end generated code: output=da39a3ee5e6b4b0d input=b6ae4a3ff852be2f]*/ + /* collections module implementation of a deque() datatype Written and maintained by Raymond D. Hettinger */ @@ -121,7 +130,7 @@ typedef struct BLOCK { struct BLOCK *rightlink; } block; -typedef struct { +struct dequeobject { PyObject_VAR_HEAD block *leftblock; block *rightblock; @@ -132,7 +141,7 @@ typedef struct { Py_ssize_t numfreeblocks; block *freeblocks[MAXFREEBLOCKS]; PyObject *weakreflist; -} dequeobject; +}; /* For debug builds, add error checking to track the endpoints * in the chain of links. The goal is to make sure that link @@ -219,8 +228,17 @@ deque_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return (PyObject *)deque; } +/*[clinic input] +_collections.deque.pop as deque_pop + + deque: dequeobject + +Remove and return the rightmost element. +[clinic start generated code]*/ + static PyObject * -deque_pop(dequeobject *deque, PyObject *unused) +deque_pop_impl(dequeobject *deque) +/*[clinic end generated code: output=2e5f7890c4251f07 input=eb6e6d020f877dec]*/ { PyObject *item; block *prevblock; @@ -254,10 +272,17 @@ deque_pop(dequeobject *deque, PyObject *unused) return item; } -PyDoc_STRVAR(pop_doc, "Remove and return the rightmost element."); +/*[clinic input] +_collections.deque.popleft as deque_popleft + + deque: dequeobject + +Remove and return the leftmost element. +[clinic start generated code]*/ static PyObject * -deque_popleft(dequeobject *deque, PyObject *unused) +deque_popleft_impl(dequeobject *deque) +/*[clinic end generated code: output=62b154897097ff68 input=acb41b9af50a9d9b]*/ { PyObject *item; block *prevblock; @@ -292,8 +317,6 @@ deque_popleft(dequeobject *deque, PyObject *unused) return item; } -PyDoc_STRVAR(popleft_doc, "Remove and return the leftmost element."); - /* The deque's size limit is d.maxlen. The limit can be zero or positive. * If there is no limit, then d.maxlen == -1. * @@ -326,7 +349,7 @@ deque_append_internal(dequeobject *deque, PyObject *item, Py_ssize_t maxlen) deque->rightindex++; deque->rightblock->data[deque->rightindex] = item; if (NEEDS_TRIM(deque, maxlen)) { - PyObject *olditem = deque_popleft(deque, NULL); + PyObject *olditem = deque_popleft_impl(deque); Py_DECREF(olditem); } else { deque->state++; @@ -334,16 +357,25 @@ deque_append_internal(dequeobject *deque, PyObject *item, Py_ssize_t maxlen) return 0; } +/*[clinic input] +_collections.deque.append as deque_append + + deque: dequeobject + item: object + / + +Add an element to the right side of the deque. +[clinic start generated code]*/ + static PyObject * deque_append(dequeobject *deque, PyObject *item) +/*[clinic end generated code: output=507b13efc4853ecc input=f112b83c380528e3]*/ { if (deque_append_internal(deque, Py_NewRef(item), deque->maxlen) < 0) return NULL; Py_RETURN_NONE; } -PyDoc_STRVAR(append_doc, "Add an element to the right side of the deque."); - static inline int deque_appendleft_internal(dequeobject *deque, PyObject *item, Py_ssize_t maxlen) { @@ -362,7 +394,7 @@ deque_appendleft_internal(dequeobject *deque, PyObject *item, Py_ssize_t maxlen) deque->leftindex--; deque->leftblock->data[deque->leftindex] = item; if (NEEDS_TRIM(deque, deque->maxlen)) { - PyObject *olditem = deque_pop(deque, NULL); + PyObject *olditem = deque_pop_impl(deque); Py_DECREF(olditem); } else { deque->state++; @@ -370,16 +402,25 @@ deque_appendleft_internal(dequeobject *deque, PyObject *item, Py_ssize_t maxlen) return 0; } +/*[clinic input] +_collections.deque.appendleft as deque_appendleft + + deque: dequeobject + item: object + / + +Add an element to the left side of the deque. +[clinic start generated code]*/ + static PyObject * deque_appendleft(dequeobject *deque, PyObject *item) +/*[clinic end generated code: output=de0335a64800ffd8 input=bbdaa60a3e956062]*/ { if (deque_appendleft_internal(deque, Py_NewRef(item), deque->maxlen) < 0) return NULL; Py_RETURN_NONE; } -PyDoc_STRVAR(appendleft_doc, "Add an element to the left side of the deque."); - static PyObject* finalize_iterator(PyObject *it) { @@ -410,8 +451,19 @@ consume_iterator(PyObject *it) return finalize_iterator(it); } +/*[clinic input] +_collections.deque.extend as deque_extend + + deque: dequeobject + iterable: object + / + +Extend the right side of the deque with elements from the iterable. +[clinic start generated code]*/ + static PyObject * deque_extend(dequeobject *deque, PyObject *iterable) +/*[clinic end generated code: output=a3a6e74d17063f8d input=cfebfd34d5383339]*/ { PyObject *it, *item; PyObject *(*iternext)(PyObject *); @@ -454,11 +506,19 @@ deque_extend(dequeobject *deque, PyObject *iterable) return finalize_iterator(it); } -PyDoc_STRVAR(extend_doc, -"Extend the right side of the deque with elements from the iterable"); +/*[clinic input] +_collections.deque.extendleft as deque_extendleft + + deque: dequeobject + iterable: object + / + +Extend the left side of the deque with elements from the iterable. +[clinic start generated code]*/ static PyObject * deque_extendleft(dequeobject *deque, PyObject *iterable) +/*[clinic end generated code: output=2dba946c50498c67 input=f4820e695a6f9416]*/ { PyObject *it, *item; PyObject *(*iternext)(PyObject *); @@ -501,9 +561,6 @@ deque_extendleft(dequeobject *deque, PyObject *iterable) return finalize_iterator(it); } -PyDoc_STRVAR(extendleft_doc, -"Extend the left side of the deque with elements from the iterable"); - static PyObject * deque_inplace_concat(dequeobject *deque, PyObject *other) { @@ -517,8 +574,17 @@ deque_inplace_concat(dequeobject *deque, PyObject *other) return (PyObject *)deque; } +/*[clinic input] +_collections.deque.copy as deque_copy + + deque: dequeobject + +Return a shallow copy of a deque. +[clinic start generated code]*/ + static PyObject * -deque_copy(PyObject *deque, PyObject *Py_UNUSED(ignored)) +deque_copy_impl(dequeobject *deque) +/*[clinic end generated code: output=6409b3d1ad2898b5 input=0e22f138bc1fcbee]*/ { PyObject *result; dequeobject *old_deque = (dequeobject *)deque; @@ -537,7 +603,7 @@ deque_copy(PyObject *deque, PyObject *Py_UNUSED(ignored)) PyObject *item = old_deque->leftblock->data[old_deque->leftindex]; rv = deque_append(new_deque, item); } else { - rv = deque_extend(new_deque, deque); + rv = deque_extend(new_deque, (PyObject *)deque); } if (rv != NULL) { Py_DECREF(rv); @@ -547,7 +613,8 @@ deque_copy(PyObject *deque, PyObject *Py_UNUSED(ignored)) return NULL; } if (old_deque->maxlen < 0) - result = PyObject_CallOneArg((PyObject *)(Py_TYPE(deque)), deque); + result = PyObject_CallOneArg((PyObject *)(Py_TYPE(deque)), + (PyObject *)deque); else result = PyObject_CallFunction((PyObject *)(Py_TYPE(deque)), "Oi", deque, old_deque->maxlen, NULL); @@ -561,7 +628,18 @@ deque_copy(PyObject *deque, PyObject *Py_UNUSED(ignored)) return result; } -PyDoc_STRVAR(copy_doc, "Return a shallow copy of a deque."); +/*[clinic input] +_collections.deque.__copy__ as deque___copy__ = _collections.deque.copy + +Return a shallow copy of a deque. +[clinic start generated code]*/ + +static PyObject * +deque___copy___impl(dequeobject *deque) +/*[clinic end generated code: output=7c5821504342bf23 input=fce05df783e7912b]*/ +{ + return deque_copy_impl(deque); +} static PyObject * deque_concat(dequeobject *deque, PyObject *other) @@ -580,7 +658,7 @@ deque_concat(dequeobject *deque, PyObject *other) return NULL; } - new_deque = deque_copy((PyObject *)deque, NULL); + new_deque = deque_copy_impl(deque); if (new_deque == NULL) return NULL; result = deque_extend((dequeobject *)new_deque, other); @@ -669,22 +747,29 @@ deque_clear(dequeobject *deque) alternate_method: while (Py_SIZE(deque)) { - item = deque_pop(deque, NULL); + item = deque_pop_impl(deque); assert (item != NULL); Py_DECREF(item); } return 0; } +/*[clinic input] +_collections.deque.clear as deque_clearmethod + + deque: dequeobject + +Remove all elements from the deque. +[clinic start generated code]*/ + static PyObject * -deque_clearmethod(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +deque_clearmethod_impl(dequeobject *deque) +/*[clinic end generated code: output=79b2513e097615c1 input=20488eb932f89f9e]*/ { deque_clear(deque); Py_RETURN_NONE; } -PyDoc_STRVAR(clear_doc, "Remove all elements from the deque."); - static PyObject * deque_inplace_repeat(dequeobject *deque, Py_ssize_t n) { @@ -768,7 +853,7 @@ deque_repeat(dequeobject *deque, Py_ssize_t n) dequeobject *new_deque; PyObject *rv; - new_deque = (dequeobject *)deque_copy((PyObject *) deque, NULL); + new_deque = (dequeobject *)deque_copy_impl(deque); if (new_deque == NULL) return NULL; rv = deque_inplace_repeat(new_deque, n); @@ -925,36 +1010,36 @@ _deque_rotate(dequeobject *deque, Py_ssize_t n) return rv; } -static PyObject * -deque_rotate(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) -{ - Py_ssize_t n=1; +/*[clinic input] +_collections.deque.rotate as deque_rotate - if (!_PyArg_CheckPositional("deque.rotate", nargs, 0, 1)) { - return NULL; - } - if (nargs) { - PyObject *index = _PyNumber_Index(args[0]); - if (index == NULL) { - return NULL; - } - n = PyLong_AsSsize_t(index); - Py_DECREF(index); - if (n == -1 && PyErr_Occurred()) { - return NULL; - } - } + deque: dequeobject + n: Py_ssize_t = 1 + / +Rotate the deque n steps to the right. If n is negative, rotates left. +[clinic start generated code]*/ + +static PyObject * +deque_rotate_impl(dequeobject *deque, Py_ssize_t n) +/*[clinic end generated code: output=96c2402a371eb15d input=d22070f49cc06c76]*/ +{ if (!_deque_rotate(deque, n)) Py_RETURN_NONE; return NULL; } -PyDoc_STRVAR(rotate_doc, -"Rotate the deque n steps to the right (default n=1). If n is negative, rotates left."); +/*[clinic input] +_collections.deque.reverse as deque_reverse + + deque: dequeobject + +Reverse *IN PLACE*. +[clinic start generated code]*/ static PyObject * -deque_reverse(dequeobject *deque, PyObject *unused) +deque_reverse_impl(dequeobject *deque) +/*[clinic end generated code: output=bdeebc2cf8c1f064 input=f139787f406101c9]*/ { block *leftblock = deque->leftblock; block *rightblock = deque->rightblock; @@ -991,11 +1076,19 @@ deque_reverse(dequeobject *deque, PyObject *unused) Py_RETURN_NONE; } -PyDoc_STRVAR(reverse_doc, -"D.reverse() -- reverse *IN PLACE*"); +/*[clinic input] +_collections.deque.count as deque_count + + deque: dequeobject + value as v: object + / + +Return number of occurrences of value. +[clinic start generated code]*/ static PyObject * deque_count(dequeobject *deque, PyObject *v) +/*[clinic end generated code: output=7405d289d94d7b9b input=1892925260ff5d78]*/ { block *b = deque->leftblock; Py_ssize_t index = deque->leftindex; @@ -1030,9 +1123,6 @@ deque_count(dequeobject *deque, PyObject *v) return PyLong_FromSsize_t(count); } -PyDoc_STRVAR(count_doc, -"D.count(value) -- return number of occurrences of value"); - static int deque_contains(dequeobject *deque, PyObject *v) { @@ -1071,22 +1161,33 @@ deque_len(dequeobject *deque) return Py_SIZE(deque); } +/*[clinic input] +@text_signature "($self, value, [start, [stop]])" +_collections.deque.index as deque_index + + deque: dequeobject + value as v: object + start: object(converter='_PyEval_SliceIndexNotNone', type='Py_ssize_t', c_default='0') = NULL + stop: object(converter='_PyEval_SliceIndexNotNone', type='Py_ssize_t', c_default='Py_SIZE(deque)') = NULL + / + +Return first index of value. + +Raises ValueError if the value is not present. +[clinic start generated code]*/ + static PyObject * -deque_index(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) +deque_index_impl(dequeobject *deque, PyObject *v, Py_ssize_t start, + Py_ssize_t stop) +/*[clinic end generated code: output=df45132753175ef9 input=140210c099830f64]*/ { - Py_ssize_t i, n, start=0, stop=Py_SIZE(deque); - PyObject *v, *item; + Py_ssize_t i, n; + PyObject *item; block *b = deque->leftblock; Py_ssize_t index = deque->leftindex; size_t start_state = deque->state; int cmp; - if (!_PyArg_ParseStack(args, nargs, "O|O&O&:index", &v, - _PyEval_SliceIndexNotNone, &start, - _PyEval_SliceIndexNotNone, &stop)) { - return NULL; - } - if (start < 0) { start += Py_SIZE(deque); if (start < 0) @@ -1138,10 +1239,6 @@ deque_index(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) return NULL; } -PyDoc_STRVAR(index_doc, -"D.index(value, [start, [stop]]) -- return first index of value.\n" -"Raises ValueError if the value is not present."); - /* insert(), remove(), and delitem() are implemented in terms of rotate() for simplicity and reasonable performance near the end points. If for some reason these methods become popular, it is not @@ -1150,18 +1247,24 @@ PyDoc_STRVAR(index_doc, boost (by moving each pointer only once instead of twice). */ +/*[clinic input] +_collections.deque.insert as deque_insert + + deque: dequeobject + index: Py_ssize_t + value: object + / + +Insert value before index. +[clinic start generated code]*/ + static PyObject * -deque_insert(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) +deque_insert_impl(dequeobject *deque, Py_ssize_t index, PyObject *value) +/*[clinic end generated code: output=ef4d2c15d5532b80 input=3e5c1c120d70c0e6]*/ { - Py_ssize_t index; Py_ssize_t n = Py_SIZE(deque); - PyObject *value; PyObject *rv; - if (!_PyArg_ParseStack(args, nargs, "nO:insert", &index, &value)) { - return NULL; - } - if (deque->maxlen == Py_SIZE(deque)) { PyErr_SetString(PyExc_IndexError, "deque already at its maximum size"); return NULL; @@ -1184,12 +1287,6 @@ deque_insert(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) Py_RETURN_NONE; } -PyDoc_STRVAR(insert_doc, -"D.insert(index, object) -- insert object before index"); - -PyDoc_STRVAR(remove_doc, -"D.remove(value) -- remove first occurrence of value."); - static int valid_index(Py_ssize_t i, Py_ssize_t limit) { @@ -1246,15 +1343,26 @@ deque_del_item(dequeobject *deque, Py_ssize_t i) assert (i >= 0 && i < Py_SIZE(deque)); if (_deque_rotate(deque, -i)) return -1; - item = deque_popleft(deque, NULL); + item = deque_popleft_impl(deque); rv = _deque_rotate(deque, i); assert (item != NULL); Py_DECREF(item); return rv; } +/*[clinic input] +_collections.deque.remove as deque_remove + + deque: dequeobject + value: object + / + +Remove first occurrence of value. +[clinic start generated code]*/ + static PyObject * deque_remove(dequeobject *deque, PyObject *value) +/*[clinic end generated code: output=49e1666d612fe911 input=d972f32d15990880]*/ { PyObject *item; block *b = deque->leftblock; @@ -1375,8 +1483,17 @@ deque_traverse(dequeobject *deque, visitproc visit, void *arg) return 0; } +/*[clinic input] +_collections.deque.__reduce__ as deque___reduce__ + + deque: dequeobject + +Return state information for pickling. +[clinic start generated code]*/ + static PyObject * -deque_reduce(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +deque___reduce___impl(dequeobject *deque) +/*[clinic end generated code: output=cb85d9e0b7d2c5ad input=991a933a5bc7a526]*/ { PyObject *state, *it; @@ -1510,26 +1627,23 @@ deque_richcompare(PyObject *v, PyObject *w, int op) return NULL; } +/*[clinic input] +@text_signature "([iterable[, maxlen]])" +_collections.deque.__init__ as deque_init + + deque: dequeobject + iterable: object = NULL + maxlen as maxlenobj: object = NULL + +A list-like sequence optimized for data accesses near its endpoints. +[clinic start generated code]*/ + static int -deque_init(dequeobject *deque, PyObject *args, PyObject *kwdargs) +deque_init_impl(dequeobject *deque, PyObject *iterable, PyObject *maxlenobj) +/*[clinic end generated code: output=7084a39d71218dcd input=5ebdffc48a2d27ae]*/ + { - PyObject *iterable = NULL; - PyObject *maxlenobj = NULL; Py_ssize_t maxlen = -1; - char *kwlist[] = {"iterable", "maxlen", 0}; - - if (kwdargs == NULL && PyTuple_GET_SIZE(args) <= 2) { - if (PyTuple_GET_SIZE(args) > 0) { - iterable = PyTuple_GET_ITEM(args, 0); - } - if (PyTuple_GET_SIZE(args) > 1) { - maxlenobj = PyTuple_GET_ITEM(args, 1); - } - } else { - if (!PyArg_ParseTupleAndKeywords(args, kwdargs, "|OO:deque", kwlist, - &iterable, &maxlenobj)) - return -1; - } if (maxlenobj != NULL && maxlenobj != Py_None) { maxlen = PyLong_AsSsize_t(maxlenobj); if (maxlen == -1 && PyErr_Occurred()) @@ -1551,8 +1665,17 @@ deque_init(dequeobject *deque, PyObject *args, PyObject *kwdargs) return 0; } +/*[clinic input] +_collections.deque.__sizeof__ as deque___sizeof__ + + deque: dequeobject + +Return the size of the deque in memory, in bytes. +[clinic start generated code]*/ + static PyObject * -deque_sizeof(dequeobject *deque, void *unused) +deque___sizeof___impl(dequeobject *deque) +/*[clinic end generated code: output=4d36e9fb4f30bbaf input=4e7c9a00c03c3290]*/ { size_t res = _PyObject_SIZE(Py_TYPE(deque)); size_t blocks; @@ -1563,9 +1686,6 @@ deque_sizeof(dequeobject *deque, void *unused) return PyLong_FromSize_t(res); } -PyDoc_STRVAR(sizeof_doc, -"D.__sizeof__() -- size of D in memory, in bytes"); - static PyObject * deque_get_maxlen(dequeobject *deque, void *Py_UNUSED(ignored)) { @@ -1574,6 +1694,22 @@ deque_get_maxlen(dequeobject *deque, void *Py_UNUSED(ignored)) return PyLong_FromSsize_t(deque->maxlen); } +static PyObject *deque_reviter(dequeobject *deque); + +/*[clinic input] +_collections.deque.__reversed__ as deque___reversed__ + + deque: dequeobject + +Return a reverse iterator over the deque. +[clinic start generated code]*/ + +static PyObject * +deque___reversed___impl(dequeobject *deque) +/*[clinic end generated code: output=3e7e7e715883cf2e input=3d494c25a6fe5c7e]*/ +{ + return deque_reviter(deque); +} /* deque object ********************************************************/ @@ -1584,47 +1720,26 @@ static PyGetSetDef deque_getset[] = { }; static PyObject *deque_iter(dequeobject *deque); -static PyObject *deque_reviter(dequeobject *deque, PyObject *Py_UNUSED(ignored)); -PyDoc_STRVAR(reversed_doc, - "D.__reversed__() -- return a reverse iterator over the deque"); static PyMethodDef deque_methods[] = { - {"append", (PyCFunction)deque_append, - METH_O, append_doc}, - {"appendleft", (PyCFunction)deque_appendleft, - METH_O, appendleft_doc}, - {"clear", (PyCFunction)deque_clearmethod, - METH_NOARGS, clear_doc}, - {"__copy__", deque_copy, - METH_NOARGS, copy_doc}, - {"copy", deque_copy, - METH_NOARGS, copy_doc}, - {"count", (PyCFunction)deque_count, - METH_O, count_doc}, - {"extend", (PyCFunction)deque_extend, - METH_O, extend_doc}, - {"extendleft", (PyCFunction)deque_extendleft, - METH_O, extendleft_doc}, - {"index", _PyCFunction_CAST(deque_index), - METH_FASTCALL, index_doc}, - {"insert", _PyCFunction_CAST(deque_insert), - METH_FASTCALL, insert_doc}, - {"pop", (PyCFunction)deque_pop, - METH_NOARGS, pop_doc}, - {"popleft", (PyCFunction)deque_popleft, - METH_NOARGS, popleft_doc}, - {"__reduce__", (PyCFunction)deque_reduce, - METH_NOARGS, reduce_doc}, - {"remove", (PyCFunction)deque_remove, - METH_O, remove_doc}, - {"__reversed__", (PyCFunction)deque_reviter, - METH_NOARGS, reversed_doc}, - {"reverse", (PyCFunction)deque_reverse, - METH_NOARGS, reverse_doc}, - {"rotate", _PyCFunction_CAST(deque_rotate), - METH_FASTCALL, rotate_doc}, - {"__sizeof__", (PyCFunction)deque_sizeof, - METH_NOARGS, sizeof_doc}, + DEQUE_APPEND_METHODDEF + DEQUE_APPENDLEFT_METHODDEF + DEQUE_CLEARMETHOD_METHODDEF + DEQUE___COPY___METHODDEF + DEQUE_COPY_METHODDEF + DEQUE_COUNT_METHODDEF + DEQUE_EXTEND_METHODDEF + DEQUE_EXTENDLEFT_METHODDEF + DEQUE_INDEX_METHODDEF + DEQUE_INSERT_METHODDEF + DEQUE_POP_METHODDEF + DEQUE_POPLEFT_METHODDEF + DEQUE___REDUCE___METHODDEF + DEQUE_REMOVE_METHODDEF + DEQUE___REVERSED___METHODDEF + DEQUE_REVERSE_METHODDEF + DEQUE_ROTATE_METHODDEF + DEQUE___SIZEOF___METHODDEF {"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")}, {NULL, NULL} /* sentinel */ @@ -1635,17 +1750,12 @@ static PyMemberDef deque_members[] = { {NULL}, }; -PyDoc_STRVAR(deque_doc, -"deque([iterable[, maxlen]]) --> deque object\n\ -\n\ -A list-like sequence optimized for data accesses near its endpoints."); - static PyType_Slot deque_slots[] = { {Py_tp_dealloc, deque_dealloc}, {Py_tp_repr, deque_repr}, {Py_tp_hash, PyObject_HashNotImplemented}, {Py_tp_getattro, PyObject_GenericGetAttr}, - {Py_tp_doc, (void *)deque_doc}, + {Py_tp_doc, (void *)deque_init__doc__}, {Py_tp_traverse, deque_traverse}, {Py_tp_clear, deque_clear}, {Py_tp_richcompare, deque_richcompare}, @@ -1834,7 +1944,7 @@ static PyType_Spec dequeiter_spec = { /*********************** Deque Reverse Iterator **************************/ static PyObject * -deque_reviter(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +deque_reviter(dequeobject *deque) { dequeiterobject *it; collections_state *state = find_module_state_by_def(Py_TYPE(deque)); @@ -1889,7 +1999,7 @@ dequereviter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return NULL; assert(type == state->dequereviter_type); - it = (dequeiterobject*)deque_reviter((dequeobject *)deque, NULL); + it = (dequeiterobject *)deque_reviter((dequeobject *)deque); if (!it) return NULL; /* consume items from the queue */ diff --git a/Modules/clinic/_collectionsmodule.c.h b/Modules/clinic/_collectionsmodule.c.h index 591ab50c76a8e8f..60fb12a22316195 100644 --- a/Modules/clinic/_collectionsmodule.c.h +++ b/Modules/clinic/_collectionsmodule.c.h @@ -2,9 +2,425 @@ preserve [clinic start generated code]*/ +#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) +# include "pycore_gc.h" // PyGC_Head +# include "pycore_runtime.h" // _Py_ID() +#endif #include "pycore_abstract.h" // _PyNumber_Index() #include "pycore_modsupport.h" // _PyArg_CheckPositional() +PyDoc_STRVAR(deque_pop__doc__, +"pop($self, /)\n" +"--\n" +"\n" +"Remove and return the rightmost element."); + +#define DEQUE_POP_METHODDEF \ + {"pop", (PyCFunction)deque_pop, METH_NOARGS, deque_pop__doc__}, + +static PyObject * +deque_pop_impl(dequeobject *deque); + +static PyObject * +deque_pop(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +{ + return deque_pop_impl(deque); +} + +PyDoc_STRVAR(deque_popleft__doc__, +"popleft($self, /)\n" +"--\n" +"\n" +"Remove and return the leftmost element."); + +#define DEQUE_POPLEFT_METHODDEF \ + {"popleft", (PyCFunction)deque_popleft, METH_NOARGS, deque_popleft__doc__}, + +static PyObject * +deque_popleft_impl(dequeobject *deque); + +static PyObject * +deque_popleft(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +{ + return deque_popleft_impl(deque); +} + +PyDoc_STRVAR(deque_append__doc__, +"append($self, item, /)\n" +"--\n" +"\n" +"Add an element to the right side of the deque."); + +#define DEQUE_APPEND_METHODDEF \ + {"append", (PyCFunction)deque_append, METH_O, deque_append__doc__}, + +PyDoc_STRVAR(deque_appendleft__doc__, +"appendleft($self, item, /)\n" +"--\n" +"\n" +"Add an element to the left side of the deque."); + +#define DEQUE_APPENDLEFT_METHODDEF \ + {"appendleft", (PyCFunction)deque_appendleft, METH_O, deque_appendleft__doc__}, + +PyDoc_STRVAR(deque_extend__doc__, +"extend($self, iterable, /)\n" +"--\n" +"\n" +"Extend the right side of the deque with elements from the iterable."); + +#define DEQUE_EXTEND_METHODDEF \ + {"extend", (PyCFunction)deque_extend, METH_O, deque_extend__doc__}, + +PyDoc_STRVAR(deque_extendleft__doc__, +"extendleft($self, iterable, /)\n" +"--\n" +"\n" +"Extend the left side of the deque with elements from the iterable."); + +#define DEQUE_EXTENDLEFT_METHODDEF \ + {"extendleft", (PyCFunction)deque_extendleft, METH_O, deque_extendleft__doc__}, + +PyDoc_STRVAR(deque_copy__doc__, +"copy($self, /)\n" +"--\n" +"\n" +"Return a shallow copy of a deque."); + +#define DEQUE_COPY_METHODDEF \ + {"copy", (PyCFunction)deque_copy, METH_NOARGS, deque_copy__doc__}, + +static PyObject * +deque_copy_impl(dequeobject *deque); + +static PyObject * +deque_copy(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +{ + return deque_copy_impl(deque); +} + +PyDoc_STRVAR(deque___copy____doc__, +"__copy__($self, /)\n" +"--\n" +"\n" +"Return a shallow copy of a deque."); + +#define DEQUE___COPY___METHODDEF \ + {"__copy__", (PyCFunction)deque___copy__, METH_NOARGS, deque___copy____doc__}, + +static PyObject * +deque___copy___impl(dequeobject *deque); + +static PyObject * +deque___copy__(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +{ + return deque___copy___impl(deque); +} + +PyDoc_STRVAR(deque_clearmethod__doc__, +"clear($self, /)\n" +"--\n" +"\n" +"Remove all elements from the deque."); + +#define DEQUE_CLEARMETHOD_METHODDEF \ + {"clear", (PyCFunction)deque_clearmethod, METH_NOARGS, deque_clearmethod__doc__}, + +static PyObject * +deque_clearmethod_impl(dequeobject *deque); + +static PyObject * +deque_clearmethod(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +{ + return deque_clearmethod_impl(deque); +} + +PyDoc_STRVAR(deque_rotate__doc__, +"rotate($self, n=1, /)\n" +"--\n" +"\n" +"Rotate the deque n steps to the right. If n is negative, rotates left."); + +#define DEQUE_ROTATE_METHODDEF \ + {"rotate", _PyCFunction_CAST(deque_rotate), METH_FASTCALL, deque_rotate__doc__}, + +static PyObject * +deque_rotate_impl(dequeobject *deque, Py_ssize_t n); + +static PyObject * +deque_rotate(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + Py_ssize_t n = 1; + + if (!_PyArg_CheckPositional("rotate", nargs, 0, 1)) { + goto exit; + } + if (nargs < 1) { + goto skip_optional; + } + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(args[0]); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + n = ival; + } +skip_optional: + return_value = deque_rotate_impl(deque, n); + +exit: + return return_value; +} + +PyDoc_STRVAR(deque_reverse__doc__, +"reverse($self, /)\n" +"--\n" +"\n" +"Reverse *IN PLACE*."); + +#define DEQUE_REVERSE_METHODDEF \ + {"reverse", (PyCFunction)deque_reverse, METH_NOARGS, deque_reverse__doc__}, + +static PyObject * +deque_reverse_impl(dequeobject *deque); + +static PyObject * +deque_reverse(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +{ + return deque_reverse_impl(deque); +} + +PyDoc_STRVAR(deque_count__doc__, +"count($self, value, /)\n" +"--\n" +"\n" +"Return number of occurrences of value."); + +#define DEQUE_COUNT_METHODDEF \ + {"count", (PyCFunction)deque_count, METH_O, deque_count__doc__}, + +PyDoc_STRVAR(deque_index__doc__, +"index($self, value, [start, [stop]])\n" +"--\n" +"\n" +"Return first index of value.\n" +"\n" +"Raises ValueError if the value is not present."); + +#define DEQUE_INDEX_METHODDEF \ + {"index", _PyCFunction_CAST(deque_index), METH_FASTCALL, deque_index__doc__}, + +static PyObject * +deque_index_impl(dequeobject *deque, PyObject *v, Py_ssize_t start, + Py_ssize_t stop); + +static PyObject * +deque_index(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *v; + Py_ssize_t start = 0; + Py_ssize_t stop = Py_SIZE(deque); + + if (!_PyArg_CheckPositional("index", nargs, 1, 3)) { + goto exit; + } + v = args[0]; + if (nargs < 2) { + goto skip_optional; + } + if (!_PyEval_SliceIndexNotNone(args[1], &start)) { + goto exit; + } + if (nargs < 3) { + goto skip_optional; + } + if (!_PyEval_SliceIndexNotNone(args[2], &stop)) { + goto exit; + } +skip_optional: + return_value = deque_index_impl(deque, v, start, stop); + +exit: + return return_value; +} + +PyDoc_STRVAR(deque_insert__doc__, +"insert($self, index, value, /)\n" +"--\n" +"\n" +"Insert value before index."); + +#define DEQUE_INSERT_METHODDEF \ + {"insert", _PyCFunction_CAST(deque_insert), METH_FASTCALL, deque_insert__doc__}, + +static PyObject * +deque_insert_impl(dequeobject *deque, Py_ssize_t index, PyObject *value); + +static PyObject * +deque_insert(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + Py_ssize_t index; + PyObject *value; + + if (!_PyArg_CheckPositional("insert", nargs, 2, 2)) { + goto exit; + } + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(args[0]); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + index = ival; + } + value = args[1]; + return_value = deque_insert_impl(deque, index, value); + +exit: + return return_value; +} + +PyDoc_STRVAR(deque_remove__doc__, +"remove($self, value, /)\n" +"--\n" +"\n" +"Remove first occurrence of value."); + +#define DEQUE_REMOVE_METHODDEF \ + {"remove", (PyCFunction)deque_remove, METH_O, deque_remove__doc__}, + +PyDoc_STRVAR(deque___reduce____doc__, +"__reduce__($self, /)\n" +"--\n" +"\n" +"Return state information for pickling."); + +#define DEQUE___REDUCE___METHODDEF \ + {"__reduce__", (PyCFunction)deque___reduce__, METH_NOARGS, deque___reduce____doc__}, + +static PyObject * +deque___reduce___impl(dequeobject *deque); + +static PyObject * +deque___reduce__(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +{ + return deque___reduce___impl(deque); +} + +PyDoc_STRVAR(deque_init__doc__, +"deque([iterable[, maxlen]])\n" +"--\n" +"\n" +"A list-like sequence optimized for data accesses near its endpoints."); + +static int +deque_init_impl(dequeobject *deque, PyObject *iterable, PyObject *maxlenobj); + +static int +deque_init(PyObject *deque, PyObject *args, PyObject *kwargs) +{ + int return_value = -1; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(iterable), &_Py_ID(maxlen), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"iterable", "maxlen", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "deque", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + PyObject * const *fastargs; + Py_ssize_t nargs = PyTuple_GET_SIZE(args); + Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0; + PyObject *iterable = NULL; + PyObject *maxlenobj = NULL; + + fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 0, 2, 0, argsbuf); + if (!fastargs) { + goto exit; + } + if (!noptargs) { + goto skip_optional_pos; + } + if (fastargs[0]) { + iterable = fastargs[0]; + if (!--noptargs) { + goto skip_optional_pos; + } + } + maxlenobj = fastargs[1]; +skip_optional_pos: + return_value = deque_init_impl((dequeobject *)deque, iterable, maxlenobj); + +exit: + return return_value; +} + +PyDoc_STRVAR(deque___sizeof____doc__, +"__sizeof__($self, /)\n" +"--\n" +"\n" +"Return the size of the deque in memory, in bytes."); + +#define DEQUE___SIZEOF___METHODDEF \ + {"__sizeof__", (PyCFunction)deque___sizeof__, METH_NOARGS, deque___sizeof____doc__}, + +static PyObject * +deque___sizeof___impl(dequeobject *deque); + +static PyObject * +deque___sizeof__(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +{ + return deque___sizeof___impl(deque); +} + +PyDoc_STRVAR(deque___reversed____doc__, +"__reversed__($self, /)\n" +"--\n" +"\n" +"Return a reverse iterator over the deque."); + +#define DEQUE___REVERSED___METHODDEF \ + {"__reversed__", (PyCFunction)deque___reversed__, METH_NOARGS, deque___reversed____doc__}, + +static PyObject * +deque___reversed___impl(dequeobject *deque); + +static PyObject * +deque___reversed__(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +{ + return deque___reversed___impl(deque); +} + PyDoc_STRVAR(_collections__count_elements__doc__, "_count_elements($module, mapping, iterable, /)\n" "--\n" @@ -72,4 +488,4 @@ tuplegetter_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=c896a72f8c45930d input=a9049054013a1b77]*/ +/*[clinic end generated code: output=3633a5cbc23e8440 input=a9049054013a1b77]*/ From 15fe8cea174772060b24c96d335a498aba3b8ed4 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 29 Jan 2024 16:45:31 +0100 Subject: [PATCH 015/507] gh-91325: Skip Stable ABI checks with Py_TRACE_REFS special build (GH-92046) Skip Stable ABI checks with Py_TRACE_REFS special build This build is not compatible with Py_LIMITED_API nor with the stable ABI. --- Lib/test/test_stable_abi_ctypes.py | 36 +++++++++++++++++++--------- Misc/stable_abi.toml | 4 ++++ Modules/_testcapi_feature_macros.inc | 9 +++++++ Tools/build/stable_abi.py | 19 ++++++++------- 4 files changed, 48 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 4976ac3642bbe46..90d452728384208 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -9,6 +9,13 @@ from _testcapi import get_feature_macros feature_macros = get_feature_macros() + +# Stable ABI is incompatible with Py_TRACE_REFS builds due to PyObject +# layout differences. +# See https://github.com/python/cpython/issues/88299#issuecomment-1113366226 +if feature_macros['Py_TRACE_REFS']: + raise unittest.SkipTest("incompatible with Py_TRACE_REFS.") + ctypes_test = import_module('ctypes') class TestStableABIAvailability(unittest.TestCase): @@ -441,7 +448,9 @@ def test_windows_feature_macros(self): "PyModule_AddObjectRef", "PyModule_AddStringConstant", "PyModule_AddType", + "PyModule_Create2", "PyModule_ExecDef", + "PyModule_FromDefAndSpec2", "PyModule_GetDef", "PyModule_GetDict", "PyModule_GetFilename", @@ -911,6 +920,13 @@ def test_windows_feature_macros(self): "_Py_TrueStruct", "_Py_VaBuildValue_SizeT", ) +if feature_macros['HAVE_FORK']: + SYMBOL_NAMES += ( + 'PyOS_AfterFork', + 'PyOS_AfterFork_Child', + 'PyOS_AfterFork_Parent', + 'PyOS_BeforeFork', + ) if feature_macros['MS_WINDOWS']: SYMBOL_NAMES += ( 'PyErr_SetExcFromWindowsErr', @@ -926,17 +942,6 @@ def test_windows_feature_macros(self): 'PyUnicode_DecodeMBCSStateful', 'PyUnicode_EncodeCodePage', ) -if feature_macros['HAVE_FORK']: - SYMBOL_NAMES += ( - 'PyOS_AfterFork', - 'PyOS_AfterFork_Child', - 'PyOS_AfterFork_Parent', - 'PyOS_BeforeFork', - ) -if feature_macros['USE_STACKCHECK']: - SYMBOL_NAMES += ( - 'PyOS_CheckStack', - ) if feature_macros['PY_HAVE_THREAD_NATIVE_ID']: SYMBOL_NAMES += ( 'PyThread_get_thread_native_id', @@ -946,14 +951,23 @@ def test_windows_feature_macros(self): '_Py_NegativeRefcount', '_Py_RefTotal', ) +if feature_macros['Py_TRACE_REFS']: + SYMBOL_NAMES += ( + ) +if feature_macros['USE_STACKCHECK']: + SYMBOL_NAMES += ( + 'PyOS_CheckStack', + ) EXPECTED_FEATURE_MACROS = set(['HAVE_FORK', 'MS_WINDOWS', 'PY_HAVE_THREAD_NATIVE_ID', 'Py_REF_DEBUG', + 'Py_TRACE_REFS', 'USE_STACKCHECK']) WINDOWS_FEATURE_MACROS = {'HAVE_FORK': False, 'MS_WINDOWS': True, 'PY_HAVE_THREAD_NATIVE_ID': True, 'Py_REF_DEBUG': 'maybe', + 'Py_TRACE_REFS': 'maybe', 'USE_STACKCHECK': 'maybe'} diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 22b25dd0ec141fd..2e6b0fff9cd7704 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -78,6 +78,10 @@ [feature_macro.Py_REF_DEBUG] doc = 'when Python is compiled in debug mode (with Py_REF_DEBUG)' windows = 'maybe' +[feature_macro.Py_TRACE_REFS] + # nb. This mode is not compatible with Stable ABI/Limited API. + doc = 'when Python is compiled with Py_TRACE_REFS' + windows = 'maybe' # Mentioned in PEP 384: diff --git a/Modules/_testcapi_feature_macros.inc b/Modules/_testcapi_feature_macros.inc index a076e7149800743..f5f3524f2c0177f 100644 --- a/Modules/_testcapi_feature_macros.inc +++ b/Modules/_testcapi_feature_macros.inc @@ -38,6 +38,15 @@ if (res) { Py_DECREF(result); return NULL; } +#ifdef Py_TRACE_REFS + res = PyDict_SetItemString(result, "Py_TRACE_REFS", Py_True); +#else + res = PyDict_SetItemString(result, "Py_TRACE_REFS", Py_False); +#endif +if (res) { + Py_DECREF(result); return NULL; +} + #ifdef USE_STACKCHECK res = PyDict_SetItemString(result, "USE_STACKCHECK", Py_True); #else diff --git a/Tools/build/stable_abi.py b/Tools/build/stable_abi.py index 85c437d521a15ad..83146622c74f941 100644 --- a/Tools/build/stable_abi.py +++ b/Tools/build/stable_abi.py @@ -278,6 +278,13 @@ def gen_ctypes_test(manifest, args, outfile): from _testcapi import get_feature_macros feature_macros = get_feature_macros() + + # Stable ABI is incompatible with Py_TRACE_REFS builds due to PyObject + # layout differences. + # See https://github.com/python/cpython/issues/88299#issuecomment-1113366226 + if feature_macros['Py_TRACE_REFS']: + raise unittest.SkipTest("incompatible with Py_TRACE_REFS.") + ctypes_test = import_module('ctypes') class TestStableABIAvailability(unittest.TestCase): @@ -308,16 +315,11 @@ def test_windows_feature_macros(self): {'function', 'data'}, include_abi_only=True, ) - optional_items = {} + feature_macros = list(manifest.select({'feature_macro'})) + optional_items = {m.name: [] for m in feature_macros} for item in items: - if item.name in ( - # Some symbols aren't exported on all platforms. - # This is a bug: https://bugs.python.org/issue44133 - 'PyModule_Create2', 'PyModule_FromDefAndSpec2', - ): - continue if item.ifdef: - optional_items.setdefault(item.ifdef, []).append(item.name) + optional_items[item.ifdef].append(item.name) else: write(f' "{item.name}",') write(")") @@ -328,7 +330,6 @@ def test_windows_feature_macros(self): write(f" {name!r},") write(" )") write("") - feature_macros = list(manifest.select({'feature_macro'})) feature_names = sorted(m.name for m in feature_macros) write(f"EXPECTED_FEATURE_MACROS = set({pprint.pformat(feature_names)})") From 0f54ee4c6cdba74492183eb2dd142393c7dba403 Mon Sep 17 00:00:00 2001 From: Steven Ward Date: Mon, 29 Jan 2024 11:00:15 -0500 Subject: [PATCH 016/507] Remove limit in calendar CLI help message for year arg (GH-114719) The limit was removed in 66c88ce30ca2b23daa37038e1a3c0de98f241f50 (GH-4109). --- Lib/calendar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/calendar.py b/Lib/calendar.py index 03469d8ac96bcd9..3c79540f986b63c 100644 --- a/Lib/calendar.py +++ b/Lib/calendar.py @@ -737,7 +737,7 @@ def main(args=None): parser.add_argument( "year", nargs='?', type=int, - help="year number (1-9999)" + help="year number" ) parser.add_argument( "month", From e351ca3c205860e94cad5da25c74bd76933f5f11 Mon Sep 17 00:00:00 2001 From: Soumendra Ganguly <67527439+8vasu@users.noreply.github.com> Date: Mon, 29 Jan 2024 17:10:28 +0100 Subject: [PATCH 017/507] gh-85984: Add POSIX pseudo-terminal functions. (GH-102413) Signed-off-by: Soumendra Ganguly Co-authored-by: Gregory P. Smith Co-authored-by: Petr Viktorin --- Doc/conf.py | 5 + Doc/library/os.rst | 59 ++++++ Lib/test/test_os.py | 45 ++++- ...3-03-15-03-21-18.gh-issue-85984.Xaq6ZN.rst | 2 + Modules/clinic/posixmodule.c.h | 168 +++++++++++++++++- Modules/posixmodule.c | 147 +++++++++++++++ configure | 30 ++++ configure.ac | 8 +- pyconfig.h.in | 15 ++ 9 files changed, 468 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-03-15-03-21-18.gh-issue-85984.Xaq6ZN.rst diff --git a/Doc/conf.py b/Doc/conf.py index a96e7787d167a37..e12128ad356e1be 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -97,12 +97,16 @@ ('c:func', 'free'), ('c:func', 'gettimeofday'), ('c:func', 'gmtime'), + ('c:func', 'grantpt'), ('c:func', 'localeconv'), ('c:func', 'localtime'), ('c:func', 'main'), ('c:func', 'malloc'), ('c:func', 'mktime'), + ('c:func', 'posix_openpt'), ('c:func', 'printf'), + ('c:func', 'ptsname'), + ('c:func', 'ptsname_r'), ('c:func', 'realloc'), ('c:func', 'snprintf'), ('c:func', 'sprintf'), @@ -110,6 +114,7 @@ ('c:func', 'strftime'), ('c:func', 'system'), ('c:func', 'time'), + ('c:func', 'unlockpt'), ('c:func', 'vsnprintf'), # Standard C types ('c:type', 'FILE'), diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 0008ec6a40c76ff..cc9f3e75a80c517 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1122,6 +1122,20 @@ as internal buffering of data. .. versionchanged:: 3.12 Added support for pipes on Windows. + +.. function:: grantpt(fd, /) + + Grant access to the slave pseudo-terminal device associated with the + master pseudo-terminal device to which the file descriptor *fd* refers. + The file descriptor *fd* is not closed upon failure. + + Calls the C standard library function :c:func:`grantpt`. + + .. availability:: Unix, not Emscripten, not WASI. + + .. versionadded:: 3.13 + + .. function:: isatty(fd, /) Return ``True`` if the file descriptor *fd* is open and connected to a @@ -1429,6 +1443,23 @@ or `the MSDN `_ on Windo .. versionadded:: 3.3 +.. function:: posix_openpt(oflag, /) + + Open and return a file descriptor for a master pseudo-terminal device. + + Calls the C standard library function :c:func:`posix_openpt`. The *oflag* + argument is used to set file status flags and file access modes as + specified in the manual page of :c:func:`posix_openpt` of your system. + + The returned file descriptor is :ref:`non-inheritable `. + If the value :data:`O_CLOEXEC` is available on the system, it is added to + *oflag*. + + .. availability:: Unix, not Emscripten, not WASI. + + .. versionadded:: 3.13 + + .. function:: preadv(fd, buffers, offset, flags=0, /) Read from a file descriptor *fd* at a position of *offset* into mutable @@ -1486,6 +1517,21 @@ or `the MSDN `_ on Windo .. versionadded:: 3.7 +.. function:: ptsname(fd, /) + + Return the name of the slave pseudo-terminal device associated with the + master pseudo-terminal device to which the file descriptor *fd* refers. + The file descriptor *fd* is not closed upon failure. + + Calls the reentrant C standard library function :c:func:`ptsname_r` if + it is available; otherwise, the C standard library function + :c:func:`ptsname`, which is not guaranteed to be thread-safe, is called. + + .. availability:: Unix, not Emscripten, not WASI. + + .. versionadded:: 3.13 + + .. function:: pwrite(fd, str, offset, /) Write the bytestring in *str* to file descriptor *fd* at position of @@ -1738,6 +1784,19 @@ or `the MSDN `_ on Windo .. availability:: Unix. +.. function:: unlockpt(fd, /) + + Unlock the slave pseudo-terminal device associated with the master + pseudo-terminal device to which the file descriptor *fd* refers. + The file descriptor *fd* is not closed upon failure. + + Calls the C standard library function :c:func:`unlockpt`. + + .. availability:: Unix, not Emscripten, not WASI. + + .. versionadded:: 3.13 + + .. function:: write(fd, str, /) Write the bytestring in *str* to file descriptor *fd*. diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index e6f0dfde8cb4aea..ed79a2c24ef30bf 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -4536,13 +4536,46 @@ def test_dup2(self): self.assertEqual(os.dup2(fd, fd3, inheritable=False), fd3) self.assertFalse(os.get_inheritable(fd3)) - @unittest.skipUnless(hasattr(os, 'openpty'), "need os.openpty()") +@unittest.skipUnless(hasattr(os, 'openpty'), "need os.openpty()") +class PseudoterminalTests(unittest.TestCase): + def open_pty(self): + """Open a pty fd-pair, and schedule cleanup for it""" + main_fd, second_fd = os.openpty() + self.addCleanup(os.close, main_fd) + self.addCleanup(os.close, second_fd) + return main_fd, second_fd + def test_openpty(self): - master_fd, slave_fd = os.openpty() - self.addCleanup(os.close, master_fd) - self.addCleanup(os.close, slave_fd) - self.assertEqual(os.get_inheritable(master_fd), False) - self.assertEqual(os.get_inheritable(slave_fd), False) + main_fd, second_fd = self.open_pty() + self.assertEqual(os.get_inheritable(main_fd), False) + self.assertEqual(os.get_inheritable(second_fd), False) + + @unittest.skipUnless(hasattr(os, 'ptsname'), "need os.ptsname()") + @unittest.skipUnless(hasattr(os, 'O_RDWR'), "need os.O_RDWR") + @unittest.skipUnless(hasattr(os, 'O_NOCTTY'), "need os.O_NOCTTY") + def test_open_via_ptsname(self): + main_fd, second_fd = self.open_pty() + second_path = os.ptsname(main_fd) + reopened_second_fd = os.open(second_path, os.O_RDWR|os.O_NOCTTY) + self.addCleanup(os.close, reopened_second_fd) + os.write(reopened_second_fd, b'foo') + self.assertEqual(os.read(main_fd, 3), b'foo') + + @unittest.skipUnless(hasattr(os, 'posix_openpt'), "need os.posix_openpt()") + @unittest.skipUnless(hasattr(os, 'grantpt'), "need os.grantpt()") + @unittest.skipUnless(hasattr(os, 'unlockpt'), "need os.unlockpt()") + @unittest.skipUnless(hasattr(os, 'ptsname'), "need os.ptsname()") + @unittest.skipUnless(hasattr(os, 'O_RDWR'), "need os.O_RDWR") + @unittest.skipUnless(hasattr(os, 'O_NOCTTY'), "need os.O_NOCTTY") + def test_posix_pty_functions(self): + mother_fd = os.posix_openpt(os.O_RDWR|os.O_NOCTTY) + self.addCleanup(os.close, mother_fd) + os.grantpt(mother_fd) + os.unlockpt(mother_fd) + son_path = os.ptsname(mother_fd) + son_fd = os.open(son_path, os.O_RDWR|os.O_NOCTTY) + self.addCleanup(os.close, son_fd) + self.assertEqual(os.ptsname(mother_fd), os.ttyname(son_fd)) @unittest.skipUnless(hasattr(os, 'spawnl'), "need os.openpty()") def test_pipe_spawnl(self): diff --git a/Misc/NEWS.d/next/Library/2023-03-15-03-21-18.gh-issue-85984.Xaq6ZN.rst b/Misc/NEWS.d/next/Library/2023-03-15-03-21-18.gh-issue-85984.Xaq6ZN.rst new file mode 100644 index 000000000000000..0e54a1fe3c8a1cf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-03-15-03-21-18.gh-issue-85984.Xaq6ZN.rst @@ -0,0 +1,2 @@ +Add POSIX pseudo-terminal functions :func:`os.posix_openpt`, +:func:`os.grantpt`, :func:`os.unlockpt`, and :func:`os.ptsname`. diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index ba3e1cfa8dbc21b..1373bdef03ba5e5 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -4465,6 +4465,156 @@ os_sched_getaffinity(PyObject *module, PyObject *arg) #endif /* defined(HAVE_SCHED_H) && defined(HAVE_SCHED_SETAFFINITY) */ +#if defined(HAVE_POSIX_OPENPT) + +PyDoc_STRVAR(os_posix_openpt__doc__, +"posix_openpt($module, oflag, /)\n" +"--\n" +"\n" +"Open and return a file descriptor for a master pseudo-terminal device.\n" +"\n" +"Performs a posix_openpt() C function call. The oflag argument is used to\n" +"set file status flags and file access modes as specified in the manual page\n" +"of posix_openpt() of your system."); + +#define OS_POSIX_OPENPT_METHODDEF \ + {"posix_openpt", (PyCFunction)os_posix_openpt, METH_O, os_posix_openpt__doc__}, + +static int +os_posix_openpt_impl(PyObject *module, int oflag); + +static PyObject * +os_posix_openpt(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + int oflag; + int _return_value; + + oflag = PyLong_AsInt(arg); + if (oflag == -1 && PyErr_Occurred()) { + goto exit; + } + _return_value = os_posix_openpt_impl(module, oflag); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyLong_FromLong((long)_return_value); + +exit: + return return_value; +} + +#endif /* defined(HAVE_POSIX_OPENPT) */ + +#if defined(HAVE_GRANTPT) + +PyDoc_STRVAR(os_grantpt__doc__, +"grantpt($module, fd, /)\n" +"--\n" +"\n" +"Grant access to the slave pseudo-terminal device.\n" +"\n" +" fd\n" +" File descriptor of a master pseudo-terminal device.\n" +"\n" +"Performs a grantpt() C function call."); + +#define OS_GRANTPT_METHODDEF \ + {"grantpt", (PyCFunction)os_grantpt, METH_O, os_grantpt__doc__}, + +static PyObject * +os_grantpt_impl(PyObject *module, int fd); + +static PyObject * +os_grantpt(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + int fd; + + if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + goto exit; + } + return_value = os_grantpt_impl(module, fd); + +exit: + return return_value; +} + +#endif /* defined(HAVE_GRANTPT) */ + +#if defined(HAVE_UNLOCKPT) + +PyDoc_STRVAR(os_unlockpt__doc__, +"unlockpt($module, fd, /)\n" +"--\n" +"\n" +"Unlock a pseudo-terminal master/slave pair.\n" +"\n" +" fd\n" +" File descriptor of a master pseudo-terminal device.\n" +"\n" +"Performs an unlockpt() C function call."); + +#define OS_UNLOCKPT_METHODDEF \ + {"unlockpt", (PyCFunction)os_unlockpt, METH_O, os_unlockpt__doc__}, + +static PyObject * +os_unlockpt_impl(PyObject *module, int fd); + +static PyObject * +os_unlockpt(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + int fd; + + if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + goto exit; + } + return_value = os_unlockpt_impl(module, fd); + +exit: + return return_value; +} + +#endif /* defined(HAVE_UNLOCKPT) */ + +#if (defined(HAVE_PTSNAME) || defined(HAVE_PTSNAME_R)) + +PyDoc_STRVAR(os_ptsname__doc__, +"ptsname($module, fd, /)\n" +"--\n" +"\n" +"Return the name of the slave pseudo-terminal device.\n" +"\n" +" fd\n" +" File descriptor of a master pseudo-terminal device.\n" +"\n" +"If the ptsname_r() C function is available, it is called;\n" +"otherwise, performs a ptsname() C function call."); + +#define OS_PTSNAME_METHODDEF \ + {"ptsname", (PyCFunction)os_ptsname, METH_O, os_ptsname__doc__}, + +static PyObject * +os_ptsname_impl(PyObject *module, int fd); + +static PyObject * +os_ptsname(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + int fd; + + if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + goto exit; + } + return_value = os_ptsname_impl(module, fd); + +exit: + return return_value; +} + +#endif /* (defined(HAVE_PTSNAME) || defined(HAVE_PTSNAME_R)) */ + #if (defined(HAVE_OPENPTY) || defined(HAVE__GETPTY) || defined(HAVE_DEV_PTMX)) PyDoc_STRVAR(os_openpty__doc__, @@ -11991,6 +12141,22 @@ os__supports_virtual_terminal(PyObject *module, PyObject *Py_UNUSED(ignored)) #define OS_SCHED_GETAFFINITY_METHODDEF #endif /* !defined(OS_SCHED_GETAFFINITY_METHODDEF) */ +#ifndef OS_POSIX_OPENPT_METHODDEF + #define OS_POSIX_OPENPT_METHODDEF +#endif /* !defined(OS_POSIX_OPENPT_METHODDEF) */ + +#ifndef OS_GRANTPT_METHODDEF + #define OS_GRANTPT_METHODDEF +#endif /* !defined(OS_GRANTPT_METHODDEF) */ + +#ifndef OS_UNLOCKPT_METHODDEF + #define OS_UNLOCKPT_METHODDEF +#endif /* !defined(OS_UNLOCKPT_METHODDEF) */ + +#ifndef OS_PTSNAME_METHODDEF + #define OS_PTSNAME_METHODDEF +#endif /* !defined(OS_PTSNAME_METHODDEF) */ + #ifndef OS_OPENPTY_METHODDEF #define OS_OPENPTY_METHODDEF #endif /* !defined(OS_OPENPTY_METHODDEF) */ @@ -12422,4 +12588,4 @@ os__supports_virtual_terminal(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #define OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #endif /* !defined(OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF) */ -/*[clinic end generated code: output=18c128534c355d84 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=43e4e557c771358a input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 007fc1cb116f84c..40ff131b119d66f 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8358,6 +8358,149 @@ os_sched_getaffinity_impl(PyObject *module, pid_t pid) #endif /* HAVE_SCHED_H */ +#ifdef HAVE_POSIX_OPENPT +/*[clinic input] +os.posix_openpt -> int + + oflag: int + / + +Open and return a file descriptor for a master pseudo-terminal device. + +Performs a posix_openpt() C function call. The oflag argument is used to +set file status flags and file access modes as specified in the manual page +of posix_openpt() of your system. +[clinic start generated code]*/ + +static int +os_posix_openpt_impl(PyObject *module, int oflag) +/*[clinic end generated code: output=ee0bc2624305fc79 input=0de33d0e29693caa]*/ +{ + int fd; + +#if defined(O_CLOEXEC) + oflag |= O_CLOEXEC; +#endif + + fd = posix_openpt(oflag); + if (fd == -1) { + posix_error(); + return -1; + } + + // Just in case, likely a no-op given O_CLOEXEC above. + if (_Py_set_inheritable(fd, 0, NULL) < 0) { + close(fd); + return -1; + } + + return fd; +} +#endif /* HAVE_POSIX_OPENPT */ + +#ifdef HAVE_GRANTPT +/*[clinic input] +os.grantpt + + fd: fildes + File descriptor of a master pseudo-terminal device. + / + +Grant access to the slave pseudo-terminal device. + +Performs a grantpt() C function call. +[clinic start generated code]*/ + +static PyObject * +os_grantpt_impl(PyObject *module, int fd) +/*[clinic end generated code: output=dfd580015cf548ab input=0668e3b96760e849]*/ +{ + int ret; + int saved_errno; + PyOS_sighandler_t sig_saved; + + sig_saved = PyOS_setsig(SIGCHLD, SIG_DFL); + + ret = grantpt(fd); + if (ret == -1) + saved_errno = errno; + + PyOS_setsig(SIGCHLD, sig_saved); + + if (ret == -1) { + errno = saved_errno; + return posix_error(); + } + + Py_RETURN_NONE; +} +#endif /* HAVE_GRANTPT */ + +#ifdef HAVE_UNLOCKPT +/*[clinic input] +os.unlockpt + + fd: fildes + File descriptor of a master pseudo-terminal device. + / + +Unlock a pseudo-terminal master/slave pair. + +Performs an unlockpt() C function call. +[clinic start generated code]*/ + +static PyObject * +os_unlockpt_impl(PyObject *module, int fd) +/*[clinic end generated code: output=e08d354dec12d30c input=de7ab1f59f69a2b4]*/ +{ + if (unlockpt(fd) == -1) + return posix_error(); + + Py_RETURN_NONE; +} +#endif /* HAVE_UNLOCKPT */ + +#if defined(HAVE_PTSNAME) || defined(HAVE_PTSNAME_R) +/*[clinic input] +os.ptsname + + fd: fildes + File descriptor of a master pseudo-terminal device. + / + +Return the name of the slave pseudo-terminal device. + +If the ptsname_r() C function is available, it is called; +otherwise, performs a ptsname() C function call. +[clinic start generated code]*/ + +static PyObject * +os_ptsname_impl(PyObject *module, int fd) +/*[clinic end generated code: output=ef300fadc5675872 input=1369ccc0546f3130]*/ +{ +#ifdef HAVE_PTSNAME_R + int ret; + char name[MAXPATHLEN+1]; + + ret = ptsname_r(fd, name, sizeof(name)); + if (ret != 0) { + errno = ret; + return posix_error(); + } +#else + char *name; + + name = ptsname(fd); + /* POSIX manpage: Upon failure, ptsname() shall return a null pointer and may set errno. + *MAY* set errno? Hmm... */ + if (name == NULL) + return posix_error(); +#endif /* HAVE_PTSNAME_R */ + + return PyUnicode_DecodeFSDefault(name); +} +#endif /* defined(HAVE_PTSNAME) || defined(HAVE_PTSNAME_R) */ + /* AIX uses /dev/ptc but is otherwise the same as /dev/ptmx */ #if defined(HAVE_DEV_PTC) && !defined(HAVE_DEV_PTMX) # define DEV_PTY_FILE "/dev/ptc" @@ -16275,6 +16418,10 @@ static PyMethodDef posix_methods[] = { OS_SCHED_YIELD_METHODDEF OS_SCHED_SETAFFINITY_METHODDEF OS_SCHED_GETAFFINITY_METHODDEF + OS_POSIX_OPENPT_METHODDEF + OS_GRANTPT_METHODDEF + OS_UNLOCKPT_METHODDEF + OS_PTSNAME_METHODDEF OS_OPENPTY_METHODDEF OS_LOGIN_TTY_METHODDEF OS_FORKPTY_METHODDEF diff --git a/configure b/configure index c563c3f5d3c7e6d..adc5a8f014c795a 100755 --- a/configure +++ b/configure @@ -17637,6 +17637,12 @@ if test "x$ac_cv_func_getwd" = xyes then : printf "%s\n" "#define HAVE_GETWD 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "grantpt" "ac_cv_func_grantpt" +if test "x$ac_cv_func_grantpt" = xyes +then : + printf "%s\n" "#define HAVE_GRANTPT 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "if_nameindex" "ac_cv_func_if_nameindex" if test "x$ac_cv_func_if_nameindex" = xyes @@ -17823,6 +17829,12 @@ if test "x$ac_cv_func_posix_fallocate" = xyes then : printf "%s\n" "#define HAVE_POSIX_FALLOCATE 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "posix_openpt" "ac_cv_func_posix_openpt" +if test "x$ac_cv_func_posix_openpt" = xyes +then : + printf "%s\n" "#define HAVE_POSIX_OPENPT 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "posix_spawn" "ac_cv_func_posix_spawn" if test "x$ac_cv_func_posix_spawn" = xyes @@ -17877,6 +17889,18 @@ if test "x$ac_cv_func_pthread_kill" = xyes then : printf "%s\n" "#define HAVE_PTHREAD_KILL 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "ptsname" "ac_cv_func_ptsname" +if test "x$ac_cv_func_ptsname" = xyes +then : + printf "%s\n" "#define HAVE_PTSNAME 1" >>confdefs.h + +fi +ac_fn_c_check_func "$LINENO" "ptsname_r" "ac_cv_func_ptsname_r" +if test "x$ac_cv_func_ptsname_r" = xyes +then : + printf "%s\n" "#define HAVE_PTSNAME_R 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "pwrite" "ac_cv_func_pwrite" if test "x$ac_cv_func_pwrite" = xyes @@ -18285,6 +18309,12 @@ if test "x$ac_cv_func_unlinkat" = xyes then : printf "%s\n" "#define HAVE_UNLINKAT 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "unlockpt" "ac_cv_func_unlockpt" +if test "x$ac_cv_func_unlockpt" = xyes +then : + printf "%s\n" "#define HAVE_UNLOCKPT 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "utimensat" "ac_cv_func_utimensat" if test "x$ac_cv_func_utimensat" = xyes diff --git a/configure.ac b/configure.ac index 13c46b3e80151dc..f5fa17fd8b7d0dc 100644 --- a/configure.ac +++ b/configure.ac @@ -4791,12 +4791,12 @@ AC_CHECK_FUNCS([ \ getgrnam_r getgrouplist getgroups gethostname getitimer getloadavg getlogin \ getpeername getpgid getpid getppid getpriority _getpty \ getpwent getpwnam_r getpwuid getpwuid_r getresgid getresuid getrusage getsid getspent \ - getspnam getuid getwd if_nameindex initgroups kill killpg lchown linkat \ + getspnam getuid getwd grantpt if_nameindex initgroups kill killpg lchown linkat \ lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \ mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ - pipe2 plock poll posix_fadvise posix_fallocate posix_spawn posix_spawnp \ + pipe2 plock poll posix_fadvise posix_fallocate posix_openpt posix_spawn posix_spawnp \ posix_spawn_file_actions_addclosefrom_np \ - pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill \ + pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill ptsname ptsname_r \ pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \ sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \ @@ -4806,7 +4806,7 @@ AC_CHECK_FUNCS([ \ sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \ sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \ sysconf system tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ - tmpnam tmpnam_r truncate ttyname umask uname unlinkat utimensat utimes vfork \ + tmpnam tmpnam_r truncate ttyname umask uname unlinkat unlockpt utimensat utimes vfork \ wait wait3 wait4 waitid waitpid wcscoll wcsftime wcsxfrm wmemcmp writev \ ]) diff --git a/pyconfig.h.in b/pyconfig.h.in index d8a9f68951afbd1..02e33c7007196d3 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -601,6 +601,9 @@ bcopy. */ #undef HAVE_GLIBC_MEMMOVE_BUG +/* Define to 1 if you have the `grantpt' function. */ +#undef HAVE_GRANTPT + /* Define to 1 if you have the header file. */ #undef HAVE_GRP_H @@ -899,6 +902,9 @@ /* Define to 1 if you have the `posix_fallocate' function. */ #undef HAVE_POSIX_FALLOCATE +/* Define to 1 if you have the `posix_openpt' function. */ +#undef HAVE_POSIX_OPENPT + /* Define to 1 if you have the `posix_spawn' function. */ #undef HAVE_POSIX_SPAWN @@ -951,6 +957,12 @@ /* Define if platform requires stubbed pthreads support */ #undef HAVE_PTHREAD_STUBS +/* Define to 1 if you have the `ptsname' function. */ +#undef HAVE_PTSNAME + +/* Define to 1 if you have the `ptsname_r' function. */ +#undef HAVE_PTSNAME_R + /* Define to 1 if you have the header file. */ #undef HAVE_PTY_H @@ -1459,6 +1471,9 @@ /* Define to 1 if you have the `unlinkat' function. */ #undef HAVE_UNLINKAT +/* Define to 1 if you have the `unlockpt' function. */ +#undef HAVE_UNLOCKPT + /* Define to 1 if you have the `unshare' function. */ #undef HAVE_UNSHARE From 39c766b579cabc71a4a50773d299d4350221a70b Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 29 Jan 2024 18:20:13 +0200 Subject: [PATCH 018/507] Fix more references to datetime and time classes (GH-114717) They could be confused with references to datetime and time modules. --- Doc/library/datetime.rst | 8 ++++---- Doc/library/mailbox.rst | 8 ++++---- Doc/whatsnew/3.8.rst | 4 ++-- Misc/NEWS.d/3.13.0a1.rst | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 47ecb0ba331bdc4..4ff049c8709289c 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -1255,7 +1255,7 @@ Instance methods: ``tzinfo=None`` can be specified to create a naive datetime from an aware datetime with no conversion of date and time data. - :class:`datetime` objects are also supported by generic function + :class:`.datetime` objects are also supported by generic function :func:`copy.replace`. .. versionchanged:: 3.6 @@ -1678,7 +1678,7 @@ Usage of ``KabulTz`` from above:: :class:`.time` Objects ---------------------- -A :class:`time` object represents a (local) time of day, independent of any particular +A :class:`.time` object represents a (local) time of day, independent of any particular day, and subject to adjustment via a :class:`tzinfo` object. .. class:: time(hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0) @@ -1836,7 +1836,7 @@ Instance methods: ``tzinfo=None`` can be specified to create a naive :class:`.time` from an aware :class:`.time`, without conversion of the time data. - :class:`time` objects are also supported by generic function + :class:`.time` objects are also supported by generic function :func:`copy.replace`. .. versionchanged:: 3.6 @@ -2522,7 +2522,7 @@ information, which are supported in ``datetime.strptime`` but are discarded by ``time.strptime``. For :class:`.time` objects, the format codes for year, month, and day should not -be used, as :class:`time` objects have no such values. If they're used anyway, +be used, as :class:`!time` objects have no such values. If they're used anyway, ``1900`` is substituted for the year, and ``1`` for the month and day. For :class:`date` objects, the format codes for hours, minutes, seconds, and diff --git a/Doc/library/mailbox.rst b/Doc/library/mailbox.rst index fa5b273093f583f..a613548c9e518e0 100644 --- a/Doc/library/mailbox.rst +++ b/Doc/library/mailbox.rst @@ -1136,8 +1136,8 @@ When a :class:`!MaildirMessage` instance is created based upon a leading "From " or trailing newline. For convenience, *time_* may be specified and will be formatted appropriately and appended to *from_*. If *time_* is specified, it should be a :class:`time.struct_time` instance, a - tuple suitable for passing to :meth:`time.strftime`, or ``True`` (to use - :meth:`time.gmtime`). + tuple suitable for passing to :func:`time.strftime`, or ``True`` (to use + :func:`time.gmtime`). .. method:: get_flags() @@ -1508,8 +1508,8 @@ When a :class:`!BabylMessage` instance is created based upon an leading "From " or trailing newline. For convenience, *time_* may be specified and will be formatted appropriately and appended to *from_*. If *time_* is specified, it should be a :class:`time.struct_time` instance, a - tuple suitable for passing to :meth:`time.strftime`, or ``True`` (to use - :meth:`time.gmtime`). + tuple suitable for passing to :func:`time.strftime`, or ``True`` (to use + :func:`time.gmtime`). .. method:: get_flags() diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 304d1b4ef4efe80..b041e592d61ed11 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -754,8 +754,8 @@ datetime -------- Added new alternate constructors :meth:`datetime.date.fromisocalendar` and -:meth:`datetime.datetime.fromisocalendar`, which construct :class:`date` and -:class:`datetime` objects respectively from ISO year, week number, and weekday; +:meth:`datetime.datetime.fromisocalendar`, which construct :class:`~datetime.date` and +:class:`~datetime.datetime` objects respectively from ISO year, week number, and weekday; these are the inverse of each class's ``isocalendar`` method. (Contributed by Paul Ganssle in :issue:`36004`.) diff --git a/Misc/NEWS.d/3.13.0a1.rst b/Misc/NEWS.d/3.13.0a1.rst index 102bddcee5c5c2a..d385b6a4504f97a 100644 --- a/Misc/NEWS.d/3.13.0a1.rst +++ b/Misc/NEWS.d/3.13.0a1.rst @@ -2276,7 +2276,7 @@ creation. .. nonce: m2H5Bk .. section: Library -Remove unnecessary extra ``__slots__`` in :py:class:`datetime`\'s pure +Remove unnecessary extra ``__slots__`` in :class:`~datetime.datetime`\'s pure python implementation to reduce memory size, as they are defined in the superclass. Patch by James Hilton-Balfe From 2c089b09ac0872e08d146c55ed60d754154761c3 Mon Sep 17 00:00:00 2001 From: Steven Ward Date: Mon, 29 Jan 2024 11:58:21 -0500 Subject: [PATCH 019/507] gh-112240: Add option to calendar module CLI to specify the weekday to start each week (GH-112241) --- Doc/library/calendar.rst | 8 +++++++- Lib/calendar.py | 7 +++++++ .../2023-11-18-16-30-21.gh-issue-112240.YXS0tj.rst | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-18-16-30-21.gh-issue-112240.YXS0tj.rst diff --git a/Doc/library/calendar.rst b/Doc/library/calendar.rst index 6586f539a8da4fc..c4dcf5641d60663 100644 --- a/Doc/library/calendar.rst +++ b/Doc/library/calendar.rst @@ -586,10 +586,16 @@ The following options are accepted: or as an HTML document. +.. option:: --first-weekday WEEKDAY, -f WEEKDAY + + The weekday to start each week. + Must be a number between 0 (Monday) and 6 (Sunday). + Defaults to 0. + + .. option:: year The year to print the calendar for. - Must be a number between 1 and 9999. Defaults to the current year. diff --git a/Lib/calendar.py b/Lib/calendar.py index 3c79540f986b63c..833ce331b14a0cc 100644 --- a/Lib/calendar.py +++ b/Lib/calendar.py @@ -734,6 +734,11 @@ def main(args=None): choices=("text", "html"), help="output type (text or html)" ) + parser.add_argument( + "-f", "--first-weekday", + type=int, default=0, + help="weekday (0 is Monday, 6 is Sunday) to start each week (default 0)" + ) parser.add_argument( "year", nargs='?', type=int, @@ -761,6 +766,7 @@ def main(args=None): cal = LocaleHTMLCalendar(locale=locale) else: cal = HTMLCalendar() + cal.setfirstweekday(options.first_weekday) encoding = options.encoding if encoding is None: encoding = sys.getdefaultencoding() @@ -775,6 +781,7 @@ def main(args=None): cal = LocaleTextCalendar(locale=locale) else: cal = TextCalendar() + cal.setfirstweekday(options.first_weekday) optdict = dict(w=options.width, l=options.lines) if options.month is None: optdict["c"] = options.spacing diff --git a/Misc/NEWS.d/next/Library/2023-11-18-16-30-21.gh-issue-112240.YXS0tj.rst b/Misc/NEWS.d/next/Library/2023-11-18-16-30-21.gh-issue-112240.YXS0tj.rst new file mode 100644 index 000000000000000..686f0311e80dcbf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-18-16-30-21.gh-issue-112240.YXS0tj.rst @@ -0,0 +1,2 @@ +Add option to calendar module CLI to specify the weekday to start each week. +Patch by Steven Ward. From 3d716655d22dc14e79ac0d30f33eef0a49efdac0 Mon Sep 17 00:00:00 2001 From: Dino Viehland Date: Mon, 29 Jan 2024 09:38:03 -0800 Subject: [PATCH 020/507] gh-112075: Use PyMem_* for allocating dict keys objects (#114543) Use PyMem_* for keys allocation --- Objects/dictobject.c | 66 +++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 43 deletions(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index e608b91679b5688..c5477ab15f8dc9a 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -262,7 +262,7 @@ _PyDict_ClearFreeList(PyInterpreterState *interp) PyObject_GC_Del(op); } while (state->keys_numfree) { - PyObject_Free(state->keys_free_list[--state->keys_numfree]); + PyMem_Free(state->keys_free_list[--state->keys_numfree]); } #endif } @@ -332,6 +332,22 @@ dictkeys_decref(PyInterpreterState *interp, PyDictKeysObject *dk) _Py_DecRefTotal(_PyInterpreterState_GET()); #endif if (--dk->dk_refcnt == 0) { + if (DK_IS_UNICODE(dk)) { + PyDictUnicodeEntry *entries = DK_UNICODE_ENTRIES(dk); + Py_ssize_t i, n; + for (i = 0, n = dk->dk_nentries; i < n; i++) { + Py_XDECREF(entries[i].me_key); + Py_XDECREF(entries[i].me_value); + } + } + else { + PyDictKeyEntry *entries = DK_ENTRIES(dk); + Py_ssize_t i, n; + for (i = 0, n = dk->dk_nentries; i < n; i++) { + Py_XDECREF(entries[i].me_key); + Py_XDECREF(entries[i].me_value); + } + } free_keys_object(interp, dk); } } @@ -640,9 +656,9 @@ new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode) else #endif { - dk = PyObject_Malloc(sizeof(PyDictKeysObject) - + ((size_t)1 << log2_bytes) - + entry_size * usable); + dk = PyMem_Malloc(sizeof(PyDictKeysObject) + + ((size_t)1 << log2_bytes) + + entry_size * usable); if (dk == NULL) { PyErr_NoMemory(); return NULL; @@ -666,23 +682,6 @@ new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode) static void free_keys_object(PyInterpreterState *interp, PyDictKeysObject *keys) { - assert(keys != Py_EMPTY_KEYS); - if (DK_IS_UNICODE(keys)) { - PyDictUnicodeEntry *entries = DK_UNICODE_ENTRIES(keys); - Py_ssize_t i, n; - for (i = 0, n = keys->dk_nentries; i < n; i++) { - Py_XDECREF(entries[i].me_key); - Py_XDECREF(entries[i].me_value); - } - } - else { - PyDictKeyEntry *entries = DK_ENTRIES(keys); - Py_ssize_t i, n; - for (i = 0, n = keys->dk_nentries; i < n; i++) { - Py_XDECREF(entries[i].me_key); - Py_XDECREF(entries[i].me_value); - } - } #if PyDict_MAXFREELIST > 0 struct _Py_dict_state *state = get_dict_state(interp); #ifdef Py_DEBUG @@ -697,7 +696,7 @@ free_keys_object(PyInterpreterState *interp, PyDictKeysObject *keys) return; } #endif - PyObject_Free(keys); + PyMem_Free(keys); } static inline PyDictValues* @@ -798,7 +797,7 @@ clone_combined_dict_keys(PyDictObject *orig) assert(orig->ma_keys->dk_refcnt == 1); size_t keys_size = _PyDict_KeysSize(orig->ma_keys); - PyDictKeysObject *keys = PyObject_Malloc(keys_size); + PyDictKeysObject *keys = PyMem_Malloc(keys_size); if (keys == NULL) { PyErr_NoMemory(); return NULL; @@ -1544,32 +1543,13 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp, } } - // We can not use free_keys_object here because key's reference - // are moved already. if (oldkeys != Py_EMPTY_KEYS) { #ifdef Py_REF_DEBUG _Py_DecRefTotal(_PyInterpreterState_GET()); #endif assert(oldkeys->dk_kind != DICT_KEYS_SPLIT); assert(oldkeys->dk_refcnt == 1); -#if PyDict_MAXFREELIST > 0 - struct _Py_dict_state *state = get_dict_state(interp); -#ifdef Py_DEBUG - // dictresize() must not be called after _PyDict_Fini() - assert(state->keys_numfree != -1); -#endif - if (DK_LOG_SIZE(oldkeys) == PyDict_LOG_MINSIZE && - DK_IS_UNICODE(oldkeys) && - state->keys_numfree < PyDict_MAXFREELIST) - { - state->keys_free_list[state->keys_numfree++] = oldkeys; - OBJECT_STAT_INC(to_freelist); - } - else -#endif - { - PyObject_Free(oldkeys); - } + free_keys_object(interp, oldkeys); } } From 0cd9bacb8ad41fe86f95b326e9199caa749539eb Mon Sep 17 00:00:00 2001 From: Dino Viehland Date: Mon, 29 Jan 2024 09:47:54 -0800 Subject: [PATCH 021/507] gh-112075: Dictionary global version counter should use atomic increments (#114568) Dictionary global version counter should use atomic increments --- Include/internal/pycore_dict.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index d96870e9197bbf0..b4e1f8cf1e320b3 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -209,8 +209,14 @@ static inline PyDictUnicodeEntry* DK_UNICODE_ENTRIES(PyDictKeysObject *dk) { #define DICT_VERSION_INCREMENT (1 << DICT_MAX_WATCHERS) #define DICT_VERSION_MASK (DICT_VERSION_INCREMENT - 1) +#ifdef Py_GIL_DISABLED +#define DICT_NEXT_VERSION(INTERP) \ + (_Py_atomic_add_uint64(&(INTERP)->dict_state.global_version, DICT_VERSION_INCREMENT) + DICT_VERSION_INCREMENT) + +#else #define DICT_NEXT_VERSION(INTERP) \ ((INTERP)->dict_state.global_version += DICT_VERSION_INCREMENT) +#endif void _PyDict_SendEvent(int watcher_bits, From aa3402ad451777d8dd3ec560e14cb16dc8540c0e Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 29 Jan 2024 19:58:31 +0200 Subject: [PATCH 022/507] gh-114678: Fix incorrect deprecation warning for 'N' specifier in Decimal format (GH-114683) Co-authored-by: Stefan Krah --- Lib/test/test_decimal.py | 10 +++++++++- .../2024-01-28-19-40-40.gh-issue-114678.kYKcJw.rst | 3 +++ Modules/_decimal/_decimal.c | 14 ++++++++------ 3 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-28-19-40-40.gh-issue-114678.kYKcJw.rst diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 7a5fe62b4673720..1423bc61c7f6906 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -41,6 +41,7 @@ darwin_malloc_err_warning, is_emscripten) from test.support.import_helper import import_fresh_module from test.support import threading_helper +from test.support import warnings_helper import random import inspect import threading @@ -1237,7 +1238,14 @@ def test_deprecated_N_format(self): else: self.assertRaises(ValueError, format, h, 'N') self.assertRaises(ValueError, format, h, '010.3N') - + with warnings_helper.check_no_warnings(self): + self.assertEqual(format(h, 'N>10.3'), 'NN6.63E-34') + self.assertEqual(format(h, 'N>10.3n'), 'NN6.63e-34') + self.assertEqual(format(h, 'N>10.3e'), 'N6.626e-34') + self.assertEqual(format(h, 'N>10.3f'), 'NNNNN0.000') + self.assertRaises(ValueError, format, h, '>Nf') + self.assertRaises(ValueError, format, h, '10Nf') + self.assertRaises(ValueError, format, h, 'Nx') @run_with_locale('LC_ALL', 'ps_AF') def test_wide_char_separator_decimal_point(self): diff --git a/Misc/NEWS.d/next/Library/2024-01-28-19-40-40.gh-issue-114678.kYKcJw.rst b/Misc/NEWS.d/next/Library/2024-01-28-19-40-40.gh-issue-114678.kYKcJw.rst new file mode 100644 index 000000000000000..2306af4a39dcf62 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-28-19-40-40.gh-issue-114678.kYKcJw.rst @@ -0,0 +1,3 @@ +Ensure that deprecation warning for 'N' specifier in :class:`~decimal.Decimal` +format is not raised for cases where 'N' appears in other places +in the format specifier. Based on patch by Stefan Krah. diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 8b93f8e2cbcf0bb..127f5f2887d4cd1 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3446,6 +3446,14 @@ dec_format(PyObject *dec, PyObject *args) if (fmt == NULL) { return NULL; } + + if (size > 0 && fmt[size-1] == 'N') { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Format specifier 'N' is deprecated", 1) < 0) { + return NULL; + } + } + /* NOTE: If https://github.com/python/cpython/pull/29438 lands, the * format string manipulation below can be eliminated by enhancing * the forked mpd_parse_fmt_str(). */ @@ -3593,12 +3601,6 @@ dec_format(PyObject *dec, PyObject *args) if (replace_fillchar) { dec_replace_fillchar(decstring); } - if (strchr(fmt, 'N') != NULL) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Format specifier 'N' is deprecated", 1) < 0) { - goto finish; - } - } result = PyUnicode_DecodeUTF8(decstring, size, NULL); From 29952c86f3f8a972203a1ccd8381448efe145ada Mon Sep 17 00:00:00 2001 From: Matan Perelman Date: Mon, 29 Jan 2024 21:12:33 +0200 Subject: [PATCH 023/507] TaskGroup: Use explicit None check for cancellation error (#114708) --- Lib/asyncio/taskgroups.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py index e1c56d140bef7de..f322b1f6653f6a2 100644 --- a/Lib/asyncio/taskgroups.py +++ b/Lib/asyncio/taskgroups.py @@ -132,7 +132,7 @@ async def __aexit__(self, et, exc, tb): # Propagate CancelledError if there is one, except if there # are other errors -- those have priority. - if propagate_cancellation_error and not self._errors: + if propagate_cancellation_error is not None and not self._errors: raise propagate_cancellation_error if et is not None and not issubclass(et, exceptions.CancelledError): From 53d921ed96e1c57b2e42f984d3a5ca8347fedb81 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 29 Jan 2024 21:48:49 +0100 Subject: [PATCH 024/507] gh-114569: Use PyMem_* APIs for non-PyObjects in unicodeobject.c (#114690) --- Objects/unicodeobject.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 4b03cc3f4da5fab..b236ddba9cdc69f 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -996,7 +996,7 @@ resize_compact(PyObject *unicode, Py_ssize_t length) new_size = (struct_size + (length + 1) * char_size); if (_PyUnicode_HAS_UTF8_MEMORY(unicode)) { - PyObject_Free(_PyUnicode_UTF8(unicode)); + PyMem_Free(_PyUnicode_UTF8(unicode)); _PyUnicode_UTF8(unicode) = NULL; _PyUnicode_UTF8_LENGTH(unicode) = 0; } @@ -1049,7 +1049,7 @@ resize_inplace(PyObject *unicode, Py_ssize_t length) if (!share_utf8 && _PyUnicode_HAS_UTF8_MEMORY(unicode)) { - PyObject_Free(_PyUnicode_UTF8(unicode)); + PyMem_Free(_PyUnicode_UTF8(unicode)); _PyUnicode_UTF8(unicode) = NULL; _PyUnicode_UTF8_LENGTH(unicode) = 0; } @@ -1590,10 +1590,10 @@ unicode_dealloc(PyObject *unicode) return; } if (_PyUnicode_HAS_UTF8_MEMORY(unicode)) { - PyObject_Free(_PyUnicode_UTF8(unicode)); + PyMem_Free(_PyUnicode_UTF8(unicode)); } if (!PyUnicode_IS_COMPACT(unicode) && _PyUnicode_DATA_ANY(unicode)) { - PyObject_Free(_PyUnicode_DATA_ANY(unicode)); + PyMem_Free(_PyUnicode_DATA_ANY(unicode)); } Py_TYPE(unicode)->tp_free(unicode); @@ -5203,7 +5203,7 @@ unicode_fill_utf8(PyObject *unicode) PyBytes_AS_STRING(writer.buffer); Py_ssize_t len = end - start; - char *cache = PyObject_Malloc(len + 1); + char *cache = PyMem_Malloc(len + 1); if (cache == NULL) { _PyBytesWriter_Dealloc(&writer); PyErr_NoMemory(); @@ -14674,7 +14674,7 @@ unicode_subtype_new(PyTypeObject *type, PyObject *unicode) PyErr_NoMemory(); goto onError; } - data = PyObject_Malloc((length + 1) * char_size); + data = PyMem_Malloc((length + 1) * char_size); if (data == NULL) { PyErr_NoMemory(); goto onError; From 3996cbdd33a479b7e59757b81489cbb3370f85e5 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 29 Jan 2024 23:24:21 +0200 Subject: [PATCH 025/507] Set `hosted_on` for Read the Docs builds (#114697) --- Doc/conf.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index e12128ad356e1be..c2d57696aeeaa37 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -6,7 +6,9 @@ # The contents of this file are pickled, so don't put values in the namespace # that aren't pickleable (module imports are okay, they're removed automatically). -import sys, os, time +import os +import sys +import time sys.path.append(os.path.abspath('tools/extensions')) sys.path.append(os.path.abspath('includes')) @@ -55,7 +57,7 @@ # General substitutions. project = 'Python' -copyright = '2001-%s, Python Software Foundation' % time.strftime('%Y') +copyright = f"2001-{time.strftime('%Y')}, Python Software Foundation" # We look for the Include/patchlevel.h file in the current Python source tree # and replace the values accordingly. @@ -302,6 +304,9 @@ 'root_include_title': False # We use the version switcher instead. } +if os.getenv("READTHEDOCS"): + html_theme_options["hosted_on"] = 'Read the Docs' + # Override stylesheet fingerprinting for Windows CHM htmlhelp to fix GH-91207 # https://github.com/python/cpython/issues/91207 if any('htmlhelp' in arg for arg in sys.argv): @@ -310,7 +315,7 @@ print("It may be removed in the future\n") # Short title used e.g. for HTML tags. -html_short_title = '%s Documentation' % release +html_short_title = f'{release} Documentation' # Deployment preview information # (See .readthedocs.yml and https://docs.readthedocs.io/en/stable/reference/environment-variables.html) @@ -359,12 +364,9 @@ latex_engine = 'xelatex' -# Get LaTeX to handle Unicode correctly latex_elements = { -} - -# Additional stuff for the LaTeX preamble. -latex_elements['preamble'] = r''' + # For the LaTeX preamble. + 'preamble': r''' \authoraddress{ \sphinxstrong{Python Software Foundation}\\ Email: \sphinxemail{docs@python.org} @@ -372,13 +374,12 @@ \let\Verbatim=\OriginalVerbatim \let\endVerbatim=\endOriginalVerbatim \setcounter{tocdepth}{2} -''' - -# The paper size ('letter' or 'a4'). -latex_elements['papersize'] = 'a4' - -# The font size ('10pt', '11pt' or '12pt'). -latex_elements['pointsize'] = '10pt' +''', + # The paper size ('letter' or 'a4'). + 'papersize': 'a4', + # The font size ('10pt', '11pt' or '12pt'). + 'pointsize': '10pt', +} # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, document class [howto/manual]). @@ -441,9 +442,9 @@ # Regexes to find C items in the source files. coverage_c_regexes = { - 'cfunction': (r'^PyAPI_FUNC\(.*\)\s+([^_][\w_]+)'), - 'data': (r'^PyAPI_DATA\(.*\)\s+([^_][\w_]+)'), - 'macro': (r'^#define ([^_][\w_]+)\(.*\)[\s|\\]'), + 'cfunction': r'^PyAPI_FUNC\(.*\)\s+([^_][\w_]+)', + 'data': r'^PyAPI_DATA\(.*\)\s+([^_][\w_]+)', + 'macro': r'^#define ([^_][\w_]+)\(.*\)[\s|\\]', } # The coverage checker will ignore all C items whose names match these regexes From 8612230c1cacab6d48bfbeb9e17d04ef5a9acf21 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Tue, 30 Jan 2024 00:04:34 +0100 Subject: [PATCH 026/507] gh-114569: Use PyMem_* APIs for non-PyObjects in compiler (#114587) --- Python/compile.c | 25 ++++++++++++------------- Python/flowgraph.c | 6 +++--- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index 7cf05dd06831195..4c1d3bb2d2b475a 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -160,7 +160,7 @@ _PyCompile_EnsureArrayLargeEnough(int idx, void **array, int *alloc, if (idx >= new_alloc) { new_alloc = idx + default_alloc; } - arr = PyObject_Calloc(new_alloc, item_size); + arr = PyMem_Calloc(new_alloc, item_size); if (arr == NULL) { PyErr_NoMemory(); return ERROR; @@ -181,7 +181,7 @@ _PyCompile_EnsureArrayLargeEnough(int idx, void **array, int *alloc, } assert(newsize > 0); - void *tmp = PyObject_Realloc(arr, newsize); + void *tmp = PyMem_Realloc(arr, newsize); if (tmp == NULL) { PyErr_NoMemory(); return ERROR; @@ -282,10 +282,10 @@ instr_sequence_insert_instruction(instr_sequence *seq, int pos, static void instr_sequence_fini(instr_sequence *seq) { - PyObject_Free(seq->s_labelmap); + PyMem_Free(seq->s_labelmap); seq->s_labelmap = NULL; - PyObject_Free(seq->s_instrs); + PyMem_Free(seq->s_instrs); seq->s_instrs = NULL; } @@ -690,7 +690,7 @@ compiler_unit_free(struct compiler_unit *u) Py_CLEAR(u->u_metadata.u_cellvars); Py_CLEAR(u->u_metadata.u_fasthidden); Py_CLEAR(u->u_private); - PyObject_Free(u); + PyMem_Free(u); } static int @@ -1262,8 +1262,7 @@ compiler_enter_scope(struct compiler *c, identifier name, struct compiler_unit *u; - u = (struct compiler_unit *)PyObject_Calloc(1, sizeof( - struct compiler_unit)); + u = (struct compiler_unit *)PyMem_Calloc(1, sizeof(struct compiler_unit)); if (!u) { PyErr_NoMemory(); return ERROR; @@ -6657,7 +6656,7 @@ ensure_fail_pop(struct compiler *c, pattern_context *pc, Py_ssize_t n) return SUCCESS; } Py_ssize_t needed = sizeof(jump_target_label) * size; - jump_target_label *resized = PyObject_Realloc(pc->fail_pop, needed); + jump_target_label *resized = PyMem_Realloc(pc->fail_pop, needed); if (resized == NULL) { PyErr_NoMemory(); return ERROR; @@ -6696,13 +6695,13 @@ emit_and_reset_fail_pop(struct compiler *c, location loc, USE_LABEL(c, pc->fail_pop[pc->fail_pop_size]); if (codegen_addop_noarg(INSTR_SEQUENCE(c), POP_TOP, loc) < 0) { pc->fail_pop_size = 0; - PyObject_Free(pc->fail_pop); + PyMem_Free(pc->fail_pop); pc->fail_pop = NULL; return ERROR; } } USE_LABEL(c, pc->fail_pop[0]); - PyObject_Free(pc->fail_pop); + PyMem_Free(pc->fail_pop); pc->fail_pop = NULL; return SUCCESS; } @@ -7206,7 +7205,7 @@ compiler_pattern_or(struct compiler *c, pattern_ty p, pattern_context *pc) Py_DECREF(pc->stores); *pc = old_pc; Py_INCREF(pc->stores); - // Need to NULL this for the PyObject_Free call in the error block. + // Need to NULL this for the PyMem_Free call in the error block. old_pc.fail_pop = NULL; // No match. Pop the remaining copy of the subject and fail: if (codegen_addop_noarg(INSTR_SEQUENCE(c), POP_TOP, LOC(p)) < 0 || @@ -7252,7 +7251,7 @@ compiler_pattern_or(struct compiler *c, pattern_ty p, pattern_context *pc) diff: compiler_error(c, LOC(p), "alternative patterns bind different names"); error: - PyObject_Free(old_pc.fail_pop); + PyMem_Free(old_pc.fail_pop); Py_DECREF(old_pc.stores); Py_XDECREF(control); return ERROR; @@ -7453,7 +7452,7 @@ compiler_match(struct compiler *c, stmt_ty s) pattern_context pc; pc.fail_pop = NULL; int result = compiler_match_inner(c, s, &pc); - PyObject_Free(pc.fail_pop); + PyMem_Free(pc.fail_pop); return result; } diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 96610b3cb11a43d..bfc23a298ff492d 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -162,7 +162,7 @@ basicblock_last_instr(const basicblock *b) { static basicblock * cfg_builder_new_block(cfg_builder *g) { - basicblock *b = (basicblock *)PyObject_Calloc(1, sizeof(basicblock)); + basicblock *b = (basicblock *)PyMem_Calloc(1, sizeof(basicblock)); if (b == NULL) { PyErr_NoMemory(); return NULL; @@ -437,10 +437,10 @@ _PyCfgBuilder_Free(cfg_builder *g) basicblock *b = g->g_block_list; while (b != NULL) { if (b->b_instr) { - PyObject_Free((void *)b->b_instr); + PyMem_Free((void *)b->b_instr); } basicblock *next = b->b_list; - PyObject_Free((void *)b); + PyMem_Free((void *)b); b = next; } PyMem_Free(g); From 742ba6081c92744ba30f16a0bb17ef9d9e809611 Mon Sep 17 00:00:00 2001 From: Brandt Bucher <brandtbucher@microsoft.com> Date: Mon, 29 Jan 2024 16:29:54 -0800 Subject: [PATCH 027/507] GH-113464: Make Brandt a codeowner for JIT stuff (GH-114739) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ae915423ece9551..f4d0411504a8325 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -21,6 +21,7 @@ configure* @erlend-aasland @corona10 **/*context* @1st1 **/*genobject* @markshannon **/*hamt* @1st1 +**/*jit* @brandtbucher Objects/set* @rhettinger Objects/dict* @methane @markshannon Objects/typevarobject.c @JelleZijlstra @@ -37,7 +38,6 @@ Python/ast_opt.c @isidentical Python/bytecodes.c @markshannon @gvanrossum Python/optimizer*.c @markshannon @gvanrossum Lib/test/test_patma.py @brandtbucher -Lib/test/test_peepholer.py @brandtbucher Lib/test/test_type_*.py @JelleZijlstra Lib/test/test_capi/test_misc.py @markshannon @gvanrossum Tools/c-analyzer/ @ericsnowcurrently From 963904335e579bfe39101adf3fd6a0cf705975ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sviatoslav=20Sydorenko=20=28=D0=A1=D0=B2=D1=8F=D1=82=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1=D0=B8=D0=B4=D0=BE=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE=29?= <wk@sydorenko.org.ua> Date: Tue, 30 Jan 2024 02:25:31 +0100 Subject: [PATCH 028/507] GH-80789: Get rid of the ``ensurepip`` infra for many wheels (#109245) Co-authored-by: vstinner@python.org Co-authored-by: Pradyun Gedam <pradyunsg@gmail.com> Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com> --- Lib/ensurepip/__init__.py | 129 ++++++++++--------------- Lib/test/test_ensurepip.py | 46 ++++----- Tools/build/verify_ensurepip_wheels.py | 6 +- 3 files changed, 73 insertions(+), 108 deletions(-) diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py index a09bf3201e1fb7d..80ee125cfd4ed32 100644 --- a/Lib/ensurepip/__init__.py +++ b/Lib/ensurepip/__init__.py @@ -1,78 +1,64 @@ -import collections import os -import os.path import subprocess import sys import sysconfig import tempfile +from contextlib import nullcontext from importlib import resources +from pathlib import Path +from shutil import copy2 __all__ = ["version", "bootstrap"] -_PACKAGE_NAMES = ('pip',) _PIP_VERSION = "23.3.2" -_PROJECTS = [ - ("pip", _PIP_VERSION, "py3"), -] - -# Packages bundled in ensurepip._bundled have wheel_name set. -# Packages from WHEEL_PKG_DIR have wheel_path set. -_Package = collections.namedtuple('Package', - ('version', 'wheel_name', 'wheel_path')) # Directory of system wheel packages. Some Linux distribution packaging # policies recommend against bundling dependencies. For example, Fedora # installs wheel packages in the /usr/share/python-wheels/ directory and don't # install the ensurepip._bundled package. -_WHEEL_PKG_DIR = sysconfig.get_config_var('WHEEL_PKG_DIR') +if (_pkg_dir := sysconfig.get_config_var('WHEEL_PKG_DIR')) is not None: + _WHEEL_PKG_DIR = Path(_pkg_dir).resolve() +else: + _WHEEL_PKG_DIR = None + +def _find_wheel_pkg_dir_pip(): + if _WHEEL_PKG_DIR is None: + # NOTE: The compile-time `WHEEL_PKG_DIR` is unset so there is no place + # NOTE: for looking up the wheels. + return None -def _find_packages(path): - packages = {} + dist_matching_wheels = _WHEEL_PKG_DIR.glob('pip-*.whl') try: - filenames = os.listdir(path) - except OSError: - # Ignore: path doesn't exist or permission error - filenames = () - # Make the code deterministic if a directory contains multiple wheel files - # of the same package, but don't attempt to implement correct version - # comparison since this case should not happen. - filenames = sorted(filenames) - for filename in filenames: - # filename is like 'pip-21.2.4-py3-none-any.whl' - if not filename.endswith(".whl"): - continue - for name in _PACKAGE_NAMES: - prefix = name + '-' - if filename.startswith(prefix): - break - else: - continue - - # Extract '21.2.4' from 'pip-21.2.4-py3-none-any.whl' - version = filename.removeprefix(prefix).partition('-')[0] - wheel_path = os.path.join(path, filename) - packages[name] = _Package(version, None, wheel_path) - return packages - - -def _get_packages(): - global _PACKAGES, _WHEEL_PKG_DIR - if _PACKAGES is not None: - return _PACKAGES - - packages = {} - for name, version, py_tag in _PROJECTS: - wheel_name = f"{name}-{version}-{py_tag}-none-any.whl" - packages[name] = _Package(version, wheel_name, None) - if _WHEEL_PKG_DIR: - dir_packages = _find_packages(_WHEEL_PKG_DIR) - # only used the wheel package directory if all packages are found there - if all(name in dir_packages for name in _PACKAGE_NAMES): - packages = dir_packages - _PACKAGES = packages - return packages -_PACKAGES = None + last_matching_dist_wheel = sorted(dist_matching_wheels)[-1] + except IndexError: + # NOTE: `WHEEL_PKG_DIR` does not contain any wheel files for `pip`. + return None + + return nullcontext(last_matching_dist_wheel) + + +def _get_pip_whl_path_ctx(): + # Prefer pip from the wheel package directory, if present. + if (alternative_pip_wheel_path := _find_wheel_pkg_dir_pip()) is not None: + return alternative_pip_wheel_path + + return resources.as_file( + resources.files('ensurepip') + / '_bundled' + / f'pip-{_PIP_VERSION}-py3-none-any.whl' + ) + + +def _get_pip_version(): + with _get_pip_whl_path_ctx() as bundled_wheel_path: + wheel_name = bundled_wheel_path.name + return ( + # Extract '21.2.4' from 'pip-21.2.4-py3-none-any.whl' + wheel_name. + removeprefix('pip-'). + partition('-')[0] + ) def _run_pip(args, additional_paths=None): @@ -105,7 +91,7 @@ def version(): """ Returns a string specifying the bundled version of pip. """ - return _get_packages()['pip'].version + return _get_pip_version() def _disable_pip_configuration_settings(): @@ -167,24 +153,10 @@ def _bootstrap(*, root=None, upgrade=False, user=False, with tempfile.TemporaryDirectory() as tmpdir: # Put our bundled wheels into a temporary directory and construct the # additional paths that need added to sys.path - additional_paths = [] - for name, package in _get_packages().items(): - if package.wheel_name: - # Use bundled wheel package - wheel_name = package.wheel_name - wheel_path = resources.files("ensurepip") / "_bundled" / wheel_name - whl = wheel_path.read_bytes() - else: - # Use the wheel package directory - with open(package.wheel_path, "rb") as fp: - whl = fp.read() - wheel_name = os.path.basename(package.wheel_path) - - filename = os.path.join(tmpdir, wheel_name) - with open(filename, "wb") as fp: - fp.write(whl) - - additional_paths.append(filename) + tmpdir_path = Path(tmpdir) + with _get_pip_whl_path_ctx() as bundled_wheel_path: + tmp_wheel_path = tmpdir_path / bundled_wheel_path.name + copy2(bundled_wheel_path, tmp_wheel_path) # Construct the arguments to be passed to the pip command args = ["install", "--no-cache-dir", "--no-index", "--find-links", tmpdir] @@ -197,7 +169,8 @@ def _bootstrap(*, root=None, upgrade=False, user=False, if verbosity: args += ["-" + "v" * verbosity] - return _run_pip([*args, *_PACKAGE_NAMES], additional_paths) + return _run_pip([*args, "pip"], [os.fsdecode(tmp_wheel_path)]) + def _uninstall_helper(*, verbosity=0): """Helper to support a clean default uninstall process on Windows @@ -227,7 +200,7 @@ def _uninstall_helper(*, verbosity=0): if verbosity: args += ["-" + "v" * verbosity] - return _run_pip([*args, *reversed(_PACKAGE_NAMES)]) + return _run_pip([*args, "pip"]) def _main(argv=None): diff --git a/Lib/test/test_ensurepip.py b/Lib/test/test_ensurepip.py index 69ab2a4feaa9389..a4b36a90d8815ea 100644 --- a/Lib/test/test_ensurepip.py +++ b/Lib/test/test_ensurepip.py @@ -6,6 +6,8 @@ import test.support import unittest import unittest.mock +from importlib.resources.abc import Traversable +from pathlib import Path import ensurepip import ensurepip._uninstall @@ -20,41 +22,35 @@ def test_version(self): # Test version() with tempfile.TemporaryDirectory() as tmpdir: self.touch(tmpdir, "pip-1.2.3b1-py2.py3-none-any.whl") - with (unittest.mock.patch.object(ensurepip, '_PACKAGES', None), - unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', tmpdir)): + with unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', Path(tmpdir)): self.assertEqual(ensurepip.version(), '1.2.3b1') - def test_get_packages_no_dir(self): - # Test _get_packages() without a wheel package directory - with (unittest.mock.patch.object(ensurepip, '_PACKAGES', None), - unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', None)): - packages = ensurepip._get_packages() - - # when bundled wheel packages are used, we get _PIP_VERSION + def test_version_no_dir(self): + # Test version() without a wheel package directory + with unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', None): + # when the bundled pip wheel is used, we get _PIP_VERSION self.assertEqual(ensurepip._PIP_VERSION, ensurepip.version()) - # use bundled wheel packages - self.assertIsNotNone(packages['pip'].wheel_name) + def test_selected_wheel_path_no_dir(self): + pip_filename = f'pip-{ensurepip._PIP_VERSION}-py3-none-any.whl' + with unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', None): + with ensurepip._get_pip_whl_path_ctx() as bundled_wheel_path: + self.assertEqual(pip_filename, bundled_wheel_path.name) - def test_get_packages_with_dir(self): - # Test _get_packages() with a wheel package directory + def test_selected_wheel_path_with_dir(self): + # Test _get_pip_whl_path_ctx() with a wheel package directory pip_filename = "pip-20.2.2-py2.py3-none-any.whl" with tempfile.TemporaryDirectory() as tmpdir: self.touch(tmpdir, pip_filename) - # not used, make sure that it's ignored + # not used, make sure that they're ignored + self.touch(tmpdir, "pip-1.2.3-py2.py3-none-any.whl") self.touch(tmpdir, "wheel-0.34.2-py2.py3-none-any.whl") + self.touch(tmpdir, "pip-script.py") - with (unittest.mock.patch.object(ensurepip, '_PACKAGES', None), - unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', tmpdir)): - packages = ensurepip._get_packages() - - self.assertEqual(packages['pip'].version, '20.2.2') - self.assertEqual(packages['pip'].wheel_path, - os.path.join(tmpdir, pip_filename)) - - # wheel package is ignored - self.assertEqual(sorted(packages), ['pip']) + with unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', Path(tmpdir)): + with ensurepip._get_pip_whl_path_ctx() as bundled_wheel_path: + self.assertEqual(pip_filename, bundled_wheel_path.name) class EnsurepipMixin: @@ -69,7 +65,7 @@ def setUp(self): real_devnull = os.devnull os_patch = unittest.mock.patch("ensurepip.os") patched_os = os_patch.start() - # But expose os.listdir() used by _find_packages() + # But expose os.listdir() used by _find_wheel_pkg_dir_pip() patched_os.listdir = os.listdir self.addCleanup(os_patch.stop) patched_os.devnull = real_devnull diff --git a/Tools/build/verify_ensurepip_wheels.py b/Tools/build/verify_ensurepip_wheels.py index 29897425da6c036..a37da2f70757e58 100755 --- a/Tools/build/verify_ensurepip_wheels.py +++ b/Tools/build/verify_ensurepip_wheels.py @@ -14,7 +14,6 @@ from pathlib import Path from urllib.request import urlopen -PACKAGE_NAMES = ("pip",) ENSURE_PIP_ROOT = Path(__file__).parent.parent.parent / "Lib/ensurepip" WHEEL_DIR = ENSURE_PIP_ROOT / "_bundled" ENSURE_PIP_INIT_PY_TEXT = (ENSURE_PIP_ROOT / "__init__.py").read_text(encoding="utf-8") @@ -97,8 +96,5 @@ def verify_wheel(package_name: str) -> bool: if __name__ == "__main__": - exit_status = 0 - for package_name in PACKAGE_NAMES: - if not verify_wheel(package_name): - exit_status = 1 + exit_status = int(not verify_wheel("pip")) raise SystemExit(exit_status) From 58f883b91bd8dd4cac38b58a026397363104a129 Mon Sep 17 00:00:00 2001 From: Victor Stinner <vstinner@python.org> Date: Tue, 30 Jan 2024 11:47:58 +0100 Subject: [PATCH 029/507] gh-103323: Remove current_fast_get() unused parameter (#114593) The current_fast_get() static inline function doesn't use its 'runtime' parameter, so just remove it. --- Python/pystate.c | 50 +++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index 8e097c848cf4a18..430121a6a35d7f9 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -67,7 +67,7 @@ _Py_thread_local PyThreadState *_Py_tss_tstate = NULL; #endif static inline PyThreadState * -current_fast_get(_PyRuntimeState *Py_UNUSED(runtime)) +current_fast_get(void) { #ifdef HAVE_THREAD_LOCAL return _Py_tss_tstate; @@ -101,14 +101,14 @@ current_fast_clear(_PyRuntimeState *Py_UNUSED(runtime)) } #define tstate_verify_not_active(tstate) \ - if (tstate == current_fast_get((tstate)->interp->runtime)) { \ + if (tstate == current_fast_get()) { \ _Py_FatalErrorFormat(__func__, "tstate %p is still current", tstate); \ } PyThreadState * _PyThreadState_GetCurrent(void) { - return current_fast_get(&_PyRuntime); + return current_fast_get(); } @@ -360,10 +360,9 @@ holds_gil(PyThreadState *tstate) // XXX Fall back to tstate->interp->runtime->ceval.gil.last_holder // (and tstate->interp->runtime->ceval.gil.locked). assert(tstate != NULL); - _PyRuntimeState *runtime = tstate->interp->runtime; /* Must be the tstate for this thread */ - assert(tstate == gilstate_tss_get(runtime)); - return tstate == current_fast_get(runtime); + assert(tstate == gilstate_tss_get(tstate->interp->runtime)); + return tstate == current_fast_get(); } @@ -723,7 +722,7 @@ PyInterpreterState * PyInterpreterState_New(void) { // tstate can be NULL - PyThreadState *tstate = current_fast_get(&_PyRuntime); + PyThreadState *tstate = current_fast_get(); PyInterpreterState *interp; PyStatus status = _PyInterpreterState_New(tstate, &interp); @@ -882,7 +881,7 @@ PyInterpreterState_Clear(PyInterpreterState *interp) // Use the current Python thread state to call audit hooks and to collect // garbage. It can be different than the current Python thread state // of 'interp'. - PyThreadState *current_tstate = current_fast_get(interp->runtime); + PyThreadState *current_tstate = current_fast_get(); _PyImport_ClearCore(interp); interpreter_clear(interp, current_tstate); } @@ -908,7 +907,7 @@ PyInterpreterState_Delete(PyInterpreterState *interp) // XXX Clearing the "current" thread state should happen before // we start finalizing the interpreter (or the current thread state). - PyThreadState *tcur = current_fast_get(runtime); + PyThreadState *tcur = current_fast_get(); if (tcur != NULL && interp == tcur->interp) { /* Unset current thread. After this, many C API calls become crashy. */ _PyThreadState_Detach(tcur); @@ -1010,7 +1009,7 @@ _PyInterpreterState_SetRunningMain(PyInterpreterState *interp) if (_PyInterpreterState_FailIfRunningMain(interp) < 0) { return -1; } - PyThreadState *tstate = current_fast_get(&_PyRuntime); + PyThreadState *tstate = current_fast_get(); _Py_EnsureTstateNotNULL(tstate); if (tstate->interp != interp) { PyErr_SetString(PyExc_RuntimeError, @@ -1025,7 +1024,7 @@ void _PyInterpreterState_SetNotRunningMain(PyInterpreterState *interp) { PyThreadState *tstate = interp->threads.main; - assert(tstate == current_fast_get(&_PyRuntime)); + assert(tstate == current_fast_get()); if (tstate->on_delete != NULL) { // The threading module was imported for the first time in this @@ -1178,7 +1177,7 @@ PyInterpreterState_GetDict(PyInterpreterState *interp) PyInterpreterState* PyInterpreterState_Get(void) { - PyThreadState *tstate = current_fast_get(&_PyRuntime); + PyThreadState *tstate = current_fast_get(); _Py_EnsureTstateNotNULL(tstate); PyInterpreterState *interp = tstate->interp; if (interp == NULL) { @@ -1474,7 +1473,7 @@ void PyThreadState_Clear(PyThreadState *tstate) { assert(tstate->_status.initialized && !tstate->_status.cleared); - assert(current_fast_get(&_PyRuntime)->interp == tstate->interp); + assert(current_fast_get()->interp == tstate->interp); // XXX assert(!tstate->_status.bound || tstate->_status.unbound); tstate->_status.finalizing = 1; // just in case @@ -1656,7 +1655,7 @@ _PyThreadState_DeleteCurrent(PyThreadState *tstate) void PyThreadState_DeleteCurrent(void) { - PyThreadState *tstate = current_fast_get(&_PyRuntime); + PyThreadState *tstate = current_fast_get(); _PyThreadState_DeleteCurrent(tstate); } @@ -1732,7 +1731,7 @@ _PyThreadState_GetDict(PyThreadState *tstate) PyObject * PyThreadState_GetDict(void) { - PyThreadState *tstate = current_fast_get(&_PyRuntime); + PyThreadState *tstate = current_fast_get(); if (tstate == NULL) { return NULL; } @@ -1853,7 +1852,7 @@ _PyThreadState_Attach(PyThreadState *tstate) #endif _Py_EnsureTstateNotNULL(tstate); - if (current_fast_get(&_PyRuntime) != NULL) { + if (current_fast_get() != NULL) { Py_FatalError("non-NULL old thread state"); } @@ -1883,7 +1882,7 @@ detach_thread(PyThreadState *tstate, int detached_state) { // XXX assert(tstate_is_alive(tstate) && tstate_is_bound(tstate)); assert(tstate->state == _Py_THREAD_ATTACHED); - assert(tstate == current_fast_get(&_PyRuntime)); + assert(tstate == current_fast_get()); if (tstate->critical_section != 0) { _PyCriticalSection_SuspendAll(tstate); } @@ -2168,14 +2167,14 @@ PyThreadState_SetAsyncExc(unsigned long id, PyObject *exc) PyThreadState * PyThreadState_GetUnchecked(void) { - return current_fast_get(&_PyRuntime); + return current_fast_get(); } PyThreadState * PyThreadState_Get(void) { - PyThreadState *tstate = current_fast_get(&_PyRuntime); + PyThreadState *tstate = current_fast_get(); _Py_EnsureTstateNotNULL(tstate); return tstate; } @@ -2183,7 +2182,7 @@ PyThreadState_Get(void) PyThreadState * _PyThreadState_Swap(_PyRuntimeState *runtime, PyThreadState *newts) { - PyThreadState *oldts = current_fast_get(runtime); + PyThreadState *oldts = current_fast_get(); if (oldts != NULL) { _PyThreadState_Detach(oldts); } @@ -2278,7 +2277,7 @@ PyObject * _PyThread_CurrentFrames(void) { _PyRuntimeState *runtime = &_PyRuntime; - PyThreadState *tstate = current_fast_get(runtime); + PyThreadState *tstate = current_fast_get(); if (_PySys_Audit(tstate, "sys._current_frames", NULL) < 0) { return NULL; } @@ -2339,7 +2338,7 @@ PyObject * _PyThread_CurrentExceptions(void) { _PyRuntimeState *runtime = &_PyRuntime; - PyThreadState *tstate = current_fast_get(runtime); + PyThreadState *tstate = current_fast_get(); _Py_EnsureTstateNotNULL(tstate); @@ -2481,7 +2480,7 @@ PyGILState_Check(void) return 1; } - PyThreadState *tstate = current_fast_get(runtime); + PyThreadState *tstate = current_fast_get(); if (tstate == NULL) { return 0; } @@ -2579,7 +2578,7 @@ PyGILState_Release(PyGILState_STATE oldstate) * races; see bugs 225673 and 1061968 (that nasty bug has a * habit of coming back). */ - assert(current_fast_get(runtime) == tstate); + assert(current_fast_get() == tstate); _PyThreadState_DeleteCurrent(tstate); } /* Release the lock if necessary */ @@ -2645,9 +2644,8 @@ _PyInterpreterState_GetConfigCopy(PyConfig *config) const PyConfig* _Py_GetConfig(void) { - _PyRuntimeState *runtime = &_PyRuntime; assert(PyGILState_Check()); - PyThreadState *tstate = current_fast_get(runtime); + PyThreadState *tstate = current_fast_get(); _Py_EnsureTstateNotNULL(tstate); return _PyInterpreterState_GetConfig(tstate->interp); } From ea30a28c3e89b69a214c536e61402660242c0f2a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Tue, 30 Jan 2024 14:21:12 +0200 Subject: [PATCH 030/507] gh-113732: Fix support of QUOTE_NOTNULL and QUOTE_STRINGS in csv.reader (GH-113738) --- Doc/whatsnew/3.12.rst | 2 +- Lib/test/test_csv.py | 25 ++++++++++ ...-01-05-16-27-34.gh-issue-113732.fgDRXA.rst | 2 + Modules/_csv.c | 46 ++++++++++++------- 4 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-05-16-27-34.gh-issue-113732.fgDRXA.rst diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 77b12f9284ba0d9..100312a5940b796 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -690,7 +690,7 @@ csv * Add :const:`csv.QUOTE_NOTNULL` and :const:`csv.QUOTE_STRINGS` flags to provide finer grained control of ``None`` and empty strings by - :class:`csv.writer` objects. + :class:`~csv.reader` and :class:`~csv.writer` objects. dis --- diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py index 69fef5945ae66f5..21a4cb586ff6658 100644 --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -392,10 +392,26 @@ def test_read_quoting(self): # will this fail where locale uses comma for decimals? self._read_test([',3,"5",7.3, 9'], [['', 3, '5', 7.3, 9]], quoting=csv.QUOTE_NONNUMERIC) + self._read_test([',3,"5",7.3, 9'], [[None, '3', '5', '7.3', ' 9']], + quoting=csv.QUOTE_NOTNULL) + self._read_test([',3,"5",7.3, 9'], [[None, 3, '5', 7.3, 9]], + quoting=csv.QUOTE_STRINGS) + + self._read_test([',,"",'], [['', '', '', '']]) + self._read_test([',,"",'], [['', '', '', '']], + quoting=csv.QUOTE_NONNUMERIC) + self._read_test([',,"",'], [[None, None, '', None]], + quoting=csv.QUOTE_NOTNULL) + self._read_test([',,"",'], [[None, None, '', None]], + quoting=csv.QUOTE_STRINGS) + self._read_test(['"a\nb", 7'], [['a\nb', ' 7']]) self.assertRaises(ValueError, self._read_test, ['abc,3'], [[]], quoting=csv.QUOTE_NONNUMERIC) + self.assertRaises(ValueError, self._read_test, + ['abc,3'], [[]], + quoting=csv.QUOTE_STRINGS) self._read_test(['1,@,3,@,5'], [['1', ',3,', '5']], quotechar='@') self._read_test(['1,\0,3,\0,5'], [['1', ',3,', '5']], quotechar='\0') @@ -403,6 +419,15 @@ def test_read_skipinitialspace(self): self._read_test(['no space, space, spaces,\ttab'], [['no space', 'space', 'spaces', '\ttab']], skipinitialspace=True) + self._read_test([' , , '], + [['', '', '']], + skipinitialspace=True) + self._read_test([' , , '], + [[None, None, None]], + skipinitialspace=True, quoting=csv.QUOTE_NOTNULL) + self._read_test([' , , '], + [[None, None, None]], + skipinitialspace=True, quoting=csv.QUOTE_STRINGS) def test_read_bigfield(self): # This exercises the buffer realloc functionality and field size diff --git a/Misc/NEWS.d/next/Library/2024-01-05-16-27-34.gh-issue-113732.fgDRXA.rst b/Misc/NEWS.d/next/Library/2024-01-05-16-27-34.gh-issue-113732.fgDRXA.rst new file mode 100644 index 000000000000000..7582603dcf95f5f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-05-16-27-34.gh-issue-113732.fgDRXA.rst @@ -0,0 +1,2 @@ +Fix support of :data:`~csv.QUOTE_NOTNULL` and :data:`~csv.QUOTE_STRINGS` in +:func:`csv.reader`. diff --git a/Modules/_csv.c b/Modules/_csv.c index 929c21584ac2ef0..3aa648b8e9cec44 100644 --- a/Modules/_csv.c +++ b/Modules/_csv.c @@ -131,7 +131,7 @@ typedef struct { Py_UCS4 *field; /* temporary buffer */ Py_ssize_t field_size; /* size of allocated buffer */ Py_ssize_t field_len; /* length of current field */ - int numeric_field; /* treat field as numeric */ + bool unquoted_field; /* true if no quotes around the current field */ unsigned long line_num; /* Source-file line number */ } ReaderObj; @@ -644,22 +644,33 @@ _call_dialect(_csvstate *module_state, PyObject *dialect_inst, PyObject *kwargs) static int parse_save_field(ReaderObj *self) { + int quoting = self->dialect->quoting; PyObject *field; - field = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, - (void *) self->field, self->field_len); - if (field == NULL) - return -1; - self->field_len = 0; - if (self->numeric_field) { - PyObject *tmp; - - self->numeric_field = 0; - tmp = PyNumber_Float(field); - Py_DECREF(field); - if (tmp == NULL) + if (self->unquoted_field && + self->field_len == 0 && + (quoting == QUOTE_NOTNULL || quoting == QUOTE_STRINGS)) + { + field = Py_NewRef(Py_None); + } + else { + field = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, + (void *) self->field, self->field_len); + if (field == NULL) { return -1; - field = tmp; + } + if (self->unquoted_field && + self->field_len != 0 && + (quoting == QUOTE_NONNUMERIC || quoting == QUOTE_STRINGS)) + { + PyObject *tmp = PyNumber_Float(field); + Py_DECREF(field); + if (tmp == NULL) { + return -1; + } + field = tmp; + } + self->field_len = 0; } if (PyList_Append(self->fields, field) < 0) { Py_DECREF(field); @@ -721,6 +732,7 @@ parse_process_char(ReaderObj *self, _csvstate *module_state, Py_UCS4 c) /* fallthru */ case START_FIELD: /* expecting field */ + self->unquoted_field = true; if (c == '\n' || c == '\r' || c == EOL) { /* save empty field - return [fields] */ if (parse_save_field(self) < 0) @@ -730,10 +742,12 @@ parse_process_char(ReaderObj *self, _csvstate *module_state, Py_UCS4 c) else if (c == dialect->quotechar && dialect->quoting != QUOTE_NONE) { /* start quoted field */ + self->unquoted_field = false; self->state = IN_QUOTED_FIELD; } else if (c == dialect->escapechar) { /* possible escaped character */ + self->unquoted_field = false; self->state = ESCAPED_CHAR; } else if (c == ' ' && dialect->skipinitialspace) @@ -746,8 +760,6 @@ parse_process_char(ReaderObj *self, _csvstate *module_state, Py_UCS4 c) } else { /* begin new unquoted field */ - if (dialect->quoting == QUOTE_NONNUMERIC) - self->numeric_field = 1; if (parse_add_char(self, module_state, c) < 0) return -1; self->state = IN_FIELD; @@ -892,7 +904,7 @@ parse_reset(ReaderObj *self) return -1; self->field_len = 0; self->state = START_RECORD; - self->numeric_field = 0; + self->unquoted_field = false; return 0; } From e21754d7f8336d4647e28f355d8a3dbd5a2c7545 Mon Sep 17 00:00:00 2001 From: Vinay Sajip <vinay_sajip@yahoo.co.uk> Date: Tue, 30 Jan 2024 12:34:18 +0000 Subject: [PATCH 031/507] gh-114706: Allow QueueListener.stop() to be called more than once. (GH-114748) --- Lib/logging/handlers.py | 7 ++++--- Lib/test/test_logging.py | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/logging/handlers.py b/Lib/logging/handlers.py index 9840b7b0aeba884..e7f1322e4ba3d9d 100644 --- a/Lib/logging/handlers.py +++ b/Lib/logging/handlers.py @@ -1586,6 +1586,7 @@ def stop(self): Note that if you don't call this before your application exits, there may be some records still left on the queue, which won't be processed. """ - self.enqueue_sentinel() - self._thread.join() - self._thread = None + if self._thread: # see gh-114706 - allow calling this more than once + self.enqueue_sentinel() + self._thread.join() + self._thread = None diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 908e242b85f5e7a..888523227c2ac43 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -4089,6 +4089,7 @@ def test_queue_listener(self): self.que_logger.critical(self.next_message()) finally: listener.stop() + listener.stop() # gh-114706 - ensure no crash if called again self.assertTrue(handler.matches(levelno=logging.WARNING, message='1')) self.assertTrue(handler.matches(levelno=logging.ERROR, message='2')) self.assertTrue(handler.matches(levelno=logging.CRITICAL, message='3')) From 809eed48058ea7391df57ead09dff53bcc5d81e9 Mon Sep 17 00:00:00 2001 From: Barney Gale <barney.gale@gmail.com> Date: Tue, 30 Jan 2024 14:25:16 +0000 Subject: [PATCH 032/507] GH-114610: Fix `pathlib._abc.PurePathBase.with_suffix('.ext')` handling of stems (#114613) Raise `ValueError` if `with_suffix('.ext')` is called on a path without a stem. Paths may only have a non-empty suffix if they also have a non-empty stem. ABC-only bugfix; no effect on public classes. --- Lib/pathlib/_abc.py | 7 +++++-- Lib/test/test_pathlib/test_pathlib.py | 7 ------- Lib/test/test_pathlib/test_pathlib_abc.py | 5 ++--- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index ad5684829ebc800..580d631cbf3b530 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -299,10 +299,13 @@ def with_suffix(self, suffix): has no suffix, add given suffix. If the given suffix is an empty string, remove the suffix from the path. """ + stem = self.stem if not suffix: - return self.with_name(self.stem) + return self.with_name(stem) + elif not stem: + raise ValueError(f"{self!r} has an empty name") elif suffix.startswith('.') and len(suffix) > 1: - return self.with_name(self.stem + suffix) + return self.with_name(stem + suffix) else: raise ValueError(f"Invalid suffix {suffix!r}") diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index 5ce3b605c58e637..a8cc30ef0ab63f6 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -327,13 +327,6 @@ def test_with_stem_empty(self): self.assertRaises(ValueError, P('a/b').with_stem, '') self.assertRaises(ValueError, P('a/b').with_stem, '.') - def test_with_suffix_empty(self): - # Path doesn't have a "filename" component. - P = self.cls - self.assertRaises(ValueError, P('').with_suffix, '.gz') - self.assertRaises(ValueError, P('.').with_suffix, '.gz') - self.assertRaises(ValueError, P('/').with_suffix, '.gz') - def test_relative_to_several_args(self): P = self.cls p = P('a/b') diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index ab989cb5503f99d..18a612f6b81bace 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -977,9 +977,8 @@ def test_with_suffix_windows(self): def test_with_suffix_empty(self): P = self.cls # Path doesn't have a "filename" component. - self.assertEqual(P('').with_suffix('.gz'), P('.gz')) - self.assertEqual(P('.').with_suffix('.gz'), P('..gz')) - self.assertEqual(P('/').with_suffix('.gz'), P('/.gz')) + self.assertRaises(ValueError, P('').with_suffix, '.gz') + self.assertRaises(ValueError, P('/').with_suffix, '.gz') def test_with_suffix_seps(self): P = self.cls From 4287e8608bcabcc5bde851d838d4709db5d69089 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 30 Jan 2024 17:12:11 +0200 Subject: [PATCH 033/507] gh-109975: Copyedit "What's New in Python 3.13" (#114401) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> --- Doc/whatsnew/3.13.rst | 218 +++++++++++++++++++++--------------------- 1 file changed, 108 insertions(+), 110 deletions(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 985e34b453f63a5..fec1e55e0daf0e6 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -146,14 +146,6 @@ New Modules Improved Modules ================ -ast ---- - -* :func:`ast.parse` now accepts an optional argument ``optimize`` - which is passed on to the :func:`compile` built-in. This makes it - possible to obtain an optimized ``AST``. - (Contributed by Irit Katriel in :gh:`108113`). - array ----- @@ -161,6 +153,14 @@ array It can be used instead of ``'u'`` type code, which is deprecated. (Contributed by Inada Naoki in :gh:`80480`.) +ast +--- + +* :func:`ast.parse` now accepts an optional argument ``optimize`` + which is passed on to the :func:`compile` built-in. This makes it + possible to obtain an optimized ``AST``. + (Contributed by Irit Katriel in :gh:`108113`.) + asyncio ------- @@ -180,6 +180,13 @@ copy any user classes which define the :meth:`!__replace__` method. (Contributed by Serhiy Storchaka in :gh:`108751`.) +dbm +--- + +* Add :meth:`dbm.gnu.gdbm.clear` and :meth:`dbm.ndbm.ndbm.clear` methods that remove all items + from the database. + (Contributed by Donghee Na in :gh:`107122`.) + dis --- @@ -189,13 +196,6 @@ dis the ``show_offsets`` parameter. (Contributed by Irit Katriel in :gh:`112137`.) -dbm ---- - -* Add :meth:`dbm.gnu.gdbm.clear` and :meth:`dbm.ndbm.ndbm.clear` methods that remove all items - from the database. - (Contributed by Donghee Na in :gh:`107122`.) - doctest ------- @@ -213,7 +213,7 @@ email parameter to these two functions: use ``strict=False`` to get the old behavior, accept malformed inputs. ``getattr(email.utils, 'supports_strict_parsing', False)`` can be use to - check if the *strict* paramater is available. + check if the *strict* parameter is available. (Contributed by Thomas Dwyer and Victor Stinner for :gh:`102988` to improve the CVE-2023-27043 fix.) @@ -223,7 +223,7 @@ fractions * Formatting for objects of type :class:`fractions.Fraction` now supports the standard format specification mini-language rules for fill, alignment, sign handling, minimum width and grouping. (Contributed by Mark Dickinson - in :gh:`111320`) + in :gh:`111320`.) glob ---- @@ -297,17 +297,17 @@ os the new environment variable :envvar:`PYTHON_CPU_COUNT` or the new command-line option :option:`-X cpu_count <-X>`. This option is useful for users who need to limit CPU resources of a container system without having to modify the container (application code). - (Contributed by Donghee Na in :gh:`109595`) + (Contributed by Donghee Na in :gh:`109595`.) * Add support of :func:`os.lchmod` and the *follow_symlinks* argument in :func:`os.chmod` on Windows. Note that the default value of *follow_symlinks* in :func:`!os.lchmod` is ``False`` on Windows. - (Contributed by Serhiy Storchaka in :gh:`59616`) + (Contributed by Serhiy Storchaka in :gh:`59616`.) * Add support of :func:`os.fchmod` and a file descriptor in :func:`os.chmod` on Windows. - (Contributed by Serhiy Storchaka in :gh:`113191`) + (Contributed by Serhiy Storchaka in :gh:`113191`.) * :func:`os.posix_spawn` now accepts ``env=None``, which makes the newly spawned process use the current process environment. @@ -357,7 +357,7 @@ pdb the new ``exceptions [exc_number]`` command for Pdb. (Contributed by Matthias Bussonnier in :gh:`106676`.) -* Expressions/Statements whose prefix is a pdb command are now correctly +* Expressions/statements whose prefix is a pdb command are now correctly identified and executed. (Contributed by Tian Gao in :gh:`108464`.) @@ -487,34 +487,69 @@ Deprecated Replace ``ctypes.ARRAY(item_type, size)`` with ``item_type * size``. (Contributed by Victor Stinner in :gh:`105733`.) +* :mod:`decimal`: Deprecate non-standard format specifier "N" for + :class:`decimal.Decimal`. + It was not documented and only supported in the C implementation. + (Contributed by Serhiy Storchaka in :gh:`89902`.) + +* :mod:`dis`: The ``dis.HAVE_ARGUMENT`` separator is deprecated. Check + membership in :data:`~dis.hasarg` instead. + (Contributed by Irit Katriel in :gh:`109319`.) + * :mod:`getopt` and :mod:`optparse` modules: They are now - :term:`soft deprecated`: the :mod:`argparse` should be used for new projects. + :term:`soft deprecated`: the :mod:`argparse` module should be used for new projects. Previously, the :mod:`optparse` module was already deprecated, its removal was not scheduled, and no warnings was emitted: so there is no change in practice. (Contributed by Victor Stinner in :gh:`106535`.) +* :mod:`gettext`: Emit deprecation warning for non-integer numbers in + :mod:`gettext` functions and methods that consider plural forms even if the + translation was not found. + (Contributed by Serhiy Storchaka in :gh:`88434`.) + * :mod:`http.server`: :class:`http.server.CGIHTTPRequestHandler` now emits a - :exc:`DeprecationWarning` as it will be removed in 3.15. Process based CGI - http servers have been out of favor for a very long time. This code was + :exc:`DeprecationWarning` as it will be removed in 3.15. Process-based CGI + HTTP servers have been out of favor for a very long time. This code was outdated, unmaintained, and rarely used. It has a high potential for both security and functionality bugs. This includes removal of the ``--cgi`` flag to the ``python -m http.server`` command line in 3.15. * :mod:`pathlib`: + :meth:`pathlib.PurePath.is_reserved` is deprecated and scheduled for + removal in Python 3.15. Use :func:`os.path.isreserved` to detect reserved + paths on Windows. + +* :mod:`pydoc`: Deprecate undocumented :func:`!pydoc.ispackage` function. + (Contributed by Zackery Spytz in :gh:`64020`.) + +* :mod:`sqlite3`: Passing more than one positional argument to + :func:`sqlite3.connect` and the :class:`sqlite3.Connection` constructor is + deprecated. The remaining parameters will become keyword-only in Python 3.15. + + Deprecate passing name, number of arguments, and the callable as keyword + arguments for the following :class:`sqlite3.Connection` APIs: + + * :meth:`~sqlite3.Connection.create_function` + * :meth:`~sqlite3.Connection.create_aggregate` + + Deprecate passing the callback callable by keyword for the following + :class:`sqlite3.Connection` APIs: + + * :meth:`~sqlite3.Connection.set_authorizer` + * :meth:`~sqlite3.Connection.set_progress_handler` + * :meth:`~sqlite3.Connection.set_trace_callback` + + The affected parameters will become positional-only in Python 3.15. - * :meth:`pathlib.PurePath.is_reserved` is deprecated and scheduled for - removal in Python 3.15. Use :func:`os.path.isreserved` to detect reserved - paths on Windows. + (Contributed by Erlend E. Aasland in :gh:`107948` and :gh:`108278`.) * :mod:`sys`: :func:`sys._enablelegacywindowsfsencoding` function. - Replace it with :envvar:`PYTHONLEGACYWINDOWSFSENCODING` environment variable. + Replace it with the :envvar:`PYTHONLEGACYWINDOWSFSENCODING` environment variable. (Contributed by Inada Naoki in :gh:`73427`.) -* :mod:`traceback`: - - * The field *exc_type* of :class:`traceback.TracebackException` is - deprecated. Use *exc_type_str* instead. +* :mod:`traceback`: The field *exc_type* of :class:`traceback.TracebackException` + is deprecated. Use *exc_type_str* instead. * :mod:`typing`: @@ -550,39 +585,6 @@ Deprecated They will be removed in Python 3.15. (Contributed by Victor Stinner in :gh:`105096`.) -* Passing more than one positional argument to :func:`sqlite3.connect` and the - :class:`sqlite3.Connection` constructor is deprecated. The remaining - parameters will become keyword-only in Python 3.15. - - Deprecate passing name, number of arguments, and the callable as keyword - arguments, for the following :class:`sqlite3.Connection` APIs: - - * :meth:`~sqlite3.Connection.create_function` - * :meth:`~sqlite3.Connection.create_aggregate` - - Deprecate passing the callback callable by keyword for the following - :class:`sqlite3.Connection` APIs: - - * :meth:`~sqlite3.Connection.set_authorizer` - * :meth:`~sqlite3.Connection.set_progress_handler` - * :meth:`~sqlite3.Connection.set_trace_callback` - - The affected parameters will become positional-only in Python 3.15. - - (Contributed by Erlend E. Aasland in :gh:`107948` and :gh:`108278`.) - -* The ``dis.HAVE_ARGUMENT`` separator is deprecated. Check membership - in :data:`~dis.hasarg` instead. - (Contributed by Irit Katriel in :gh:`109319`.) - -* Deprecate non-standard format specifier "N" for :class:`decimal.Decimal`. - It was not documented and only supported in the C implementation. - (Contributed by Serhiy Storchaka in :gh:`89902`.) - -* Emit deprecation warning for non-integer numbers in :mod:`gettext` functions - and methods that consider plural forms even if the translation was not found. - (Contributed by Serhiy Storchaka in :gh:`88434`.) - * Calling :meth:`frame.clear` on a suspended frame raises :exc:`RuntimeError` (as has always been the case for an executing frame). (Contributed by Irit Katriel in :gh:`79932`.) @@ -593,9 +595,6 @@ Deprecated coroutine. (Contributed by Irit Katriel in :gh:`81137`.) -* Deprecate undocumented :func:`!pydoc.ispackage` function. - (Contributed by Zackery Spytz in :gh:`64020`.) - Pending Removal in Python 3.14 ------------------------------ @@ -657,11 +656,11 @@ Pending Removal in Python 3.14 :func:`~multiprocessing.set_start_method` APIs to explicitly specify when your code *requires* ``'fork'``. See :ref:`multiprocessing-start-methods`. -* :mod:`pathlib`: :meth:`~pathlib.PurePath.is_relative_to`, +* :mod:`pathlib`: :meth:`~pathlib.PurePath.is_relative_to` and :meth:`~pathlib.PurePath.relative_to`: passing additional arguments is deprecated. -* :func:`pkgutil.find_loader` and :func:`pkgutil.get_loader` +* :mod:`pkgutil`: :func:`~pkgutil.find_loader` and :func:`~pkgutil.get_loader` now raise :exc:`DeprecationWarning`; use :func:`importlib.util.find_spec` instead. (Contributed by Nikita Sobolev in :gh:`97850`.) @@ -719,10 +718,16 @@ Pending Removal in Python 3.15 (Contributed by Hugo van Kemenade in :gh:`111187`.) * :mod:`pathlib`: + :meth:`pathlib.PurePath.is_reserved` is deprecated and scheduled for + removal in Python 3.15. Use :func:`os.path.isreserved` to detect reserved + paths on Windows. - * :meth:`pathlib.PurePath.is_reserved` is deprecated and scheduled for - removal in Python 3.15. Use :func:`os.path.isreserved` to detect reserved - paths on Windows. +* :mod:`threading`: + Passing any arguments to :func:`threading.RLock` is now deprecated. + C version allows any numbers of args and kwargs, + but they are just ignored. Python version does not allow any arguments. + All arguments will be removed from :func:`threading.RLock` in Python 3.15. + (Contributed by Nikita Sobolev in :gh:`102029`.) * :class:`typing.NamedTuple`: @@ -749,12 +754,6 @@ Pending Removal in Python 3.15 They will be removed in Python 3.15. (Contributed by Victor Stinner in :gh:`105096`.) -* Passing any arguments to :func:`threading.RLock` is now deprecated. - C version allows any numbers of args and kwargs, - but they are just ignored. Python version does not allow any arguments. - All arguments will be removed from :func:`threading.RLock` in Python 3.15. - (Contributed by Nikita Sobolev in :gh:`102029`.) - Pending Removal in Python 3.16 ------------------------------ @@ -801,6 +800,9 @@ although there is currently no date scheduled for their removal. :data:`calendar.FEBRUARY`. (Contributed by Prince Roshan in :gh:`103636`.) +* :attr:`codeobject.co_lnotab`: use the :meth:`codeobject.co_lines` method + instead. + * :mod:`datetime`: * :meth:`~datetime.datetime.utcnow`: @@ -836,11 +838,13 @@ although there is currently no date scheduled for their removal. underscore. (Contributed by Serhiy Storchaka in :gh:`91760`.) +* :mod:`!sre_compile`, :mod:`!sre_constants` and :mod:`!sre_parse` modules. + * :mod:`ssl` options and protocols: * :class:`ssl.SSLContext` without protocol argument is deprecated. * :class:`ssl.SSLContext`: :meth:`~ssl.SSLContext.set_npn_protocols` and - :meth:`!~ssl.SSLContext.selected_npn_protocol` are deprecated: use ALPN + :meth:`!selected_npn_protocol` are deprecated: use ALPN instead. * ``ssl.OP_NO_SSL*`` options * ``ssl.OP_NO_TLS*`` options @@ -853,13 +857,6 @@ although there is currently no date scheduled for their removal. * ``ssl.TLSVersion.TLSv1`` * ``ssl.TLSVersion.TLSv1_1`` -* :mod:`!sre_compile`, :mod:`!sre_constants` and :mod:`!sre_parse` modules. - -* :attr:`codeobject.co_lnotab`: use the :meth:`codeobject.co_lines` method - instead. - -* :class:`typing.Text` (:gh:`92332`). - * :func:`sysconfig.is_python_build` *check_home* parameter is deprecated and ignored. @@ -874,14 +871,10 @@ although there is currently no date scheduled for their removal. * :meth:`!threading.currentThread`: use :meth:`threading.current_thread`. * :meth:`!threading.activeCount`: use :meth:`threading.active_count`. -* :class:`unittest.IsolatedAsyncioTestCase`: it is deprecated to return a value - that is not None from a test case. - -* :mod:`urllib.request`: :class:`~urllib.request.URLopener` and - :class:`~urllib.request.FancyURLopener` style of invoking requests is - deprecated. Use newer :func:`~urllib.request.urlopen` functions and methods. +* :class:`typing.Text` (:gh:`92332`). -* :func:`!urllib.parse.to_bytes`. +* :class:`unittest.IsolatedAsyncioTestCase`: it is deprecated to return a value + that is not ``None`` from a test case. * :mod:`urllib.parse` deprecated functions: :func:`~urllib.parse.urlparse` instead @@ -895,6 +888,11 @@ although there is currently no date scheduled for their removal. * ``splittype()`` * ``splituser()`` * ``splitvalue()`` + * ``to_bytes()`` + +* :mod:`urllib.request`: :class:`~urllib.request.URLopener` and + :class:`~urllib.request.FancyURLopener` style of invoking requests is + deprecated. Use newer :func:`~urllib.request.urlopen` functions and methods. * :mod:`wsgiref`: ``SimpleHandler.stdout.write()`` should not do partial writes. @@ -1190,10 +1188,10 @@ Changes in the Python API * Functions :c:func:`PyDict_GetItem`, :c:func:`PyDict_GetItemString`, :c:func:`PyMapping_HasKey`, :c:func:`PyMapping_HasKeyString`, :c:func:`PyObject_HasAttr`, :c:func:`PyObject_HasAttrString`, and - :c:func:`PySys_GetObject`, which clear all errors occurred during calling - the function, report now them using :func:`sys.unraisablehook`. - You can consider to replace these functions with other functions as - recomended in the documentation. + :c:func:`PySys_GetObject`, which clear all errors which occurred when calling + them, now report them using :func:`sys.unraisablehook`. + You may replace them with other functions as + recommended in the documentation. (Contributed by Serhiy Storchaka in :gh:`106672`.) * An :exc:`OSError` is now raised by :func:`getpass.getuser` for any failure to @@ -1202,7 +1200,7 @@ Changes in the Python API * The :mod:`threading` module now expects the :mod:`!_thread` module to have an ``_is_main_interpreter`` attribute. It is a function with no - arguments that returns ``True`` if the current interpreter is the + arguments that return ``True`` if the current interpreter is the main interpreter. Any library or application that provides a custom ``_thread`` module @@ -1225,7 +1223,7 @@ Build Changes (Contributed by Erlend Aasland in :gh:`105875`.) * Python built with :file:`configure` :option:`--with-trace-refs` (tracing - references) is now ABI compatible with Python release build and + references) is now ABI compatible with the Python release build and :ref:`debug build <debug-build>`. (Contributed by Victor Stinner in :gh:`108634`.) @@ -1252,7 +1250,7 @@ New Features (Contributed by Inada Naoki in :gh:`104922`.) * The *keywords* parameter of :c:func:`PyArg_ParseTupleAndKeywords` and - :c:func:`PyArg_VaParseTupleAndKeywords` has now type :c:expr:`char * const *` + :c:func:`PyArg_VaParseTupleAndKeywords` now has type :c:expr:`char * const *` in C and :c:expr:`const char * const *` in C++, instead of :c:expr:`char **`. It makes these functions compatible with arguments of type :c:expr:`const char * const *`, :c:expr:`const char **` or @@ -1309,14 +1307,14 @@ New Features always steals a reference to the value. (Contributed by Serhiy Storchaka in :gh:`86493`.) -* Added :c:func:`PyDict_GetItemRef` and :c:func:`PyDict_GetItemStringRef` +* Add :c:func:`PyDict_GetItemRef` and :c:func:`PyDict_GetItemStringRef` functions: similar to :c:func:`PyDict_GetItemWithError` but returning a :term:`strong reference` instead of a :term:`borrowed reference`. Moreover, these functions return -1 on error and so checking ``PyErr_Occurred()`` is not needed. (Contributed by Victor Stinner in :gh:`106004`.) -* Added :c:func:`PyDict_ContainsString` function: same as +* Add :c:func:`PyDict_ContainsString` function: same as :c:func:`PyDict_Contains`, but *key* is specified as a :c:expr:`const char*` UTF-8 encoded bytes string, rather than a :c:expr:`PyObject*`. (Contributed by Victor Stinner in :gh:`108314`.) @@ -1374,7 +1372,7 @@ New Features (Contributed by Victor Stinner in :gh:`85283`.) * Add :c:func:`PyErr_FormatUnraisable` function: similar to - :c:func:`PyErr_WriteUnraisable`, but allow to customize the warning mesage. + :c:func:`PyErr_WriteUnraisable`, but allow customizing the warning message. (Contributed by Serhiy Storchaka in :gh:`108082`.) * Add :c:func:`PyList_Extend` and :c:func:`PyList_Clear` functions: similar to @@ -1384,7 +1382,7 @@ New Features * Add :c:func:`PyDict_Pop` and :c:func:`PyDict_PopString` functions: remove a key from a dictionary and optionally return the removed value. This is similar to :meth:`dict.pop`, but without the default value and not raising - :exc:`KeyError` if the key missing. + :exc:`KeyError` if the key is missing. (Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.) * Add :c:func:`Py_HashPointer` function to hash a pointer. @@ -1497,7 +1495,7 @@ Removed ------- * Removed chained :class:`classmethod` descriptors (introduced in - :issue:`19072`). This can no longer be used to wrap other descriptors + :gh:`63272`). This can no longer be used to wrap other descriptors such as :class:`property`. The core design of this feature was flawed and caused a number of downstream problems. To "pass-through" a :class:`classmethod`, consider using the :attr:`!__wrapped__` @@ -1511,14 +1509,14 @@ Removed add ``cc @vstinner`` to the issue to notify Victor Stinner. (Contributed by Victor Stinner in :gh:`106320`.) -* Remove functions deprecated in Python 3.9. +* Remove functions deprecated in Python 3.9: * ``PyEval_CallObject()``, ``PyEval_CallObjectWithKeywords()``: use :c:func:`PyObject_CallNoArgs` or :c:func:`PyObject_Call` instead. Warning: :c:func:`PyObject_Call` positional arguments must be a - :class:`tuple` and must not be *NULL*, keyword arguments must be a - :class:`dict` or *NULL*, whereas removed functions checked arguments type - and accepted *NULL* positional and keyword arguments. + :class:`tuple` and must not be ``NULL``, keyword arguments must be a + :class:`dict` or ``NULL``, whereas removed functions checked arguments type + and accepted ``NULL`` positional and keyword arguments. To replace ``PyEval_CallObjectWithKeywords(func, NULL, kwargs)`` with :c:func:`PyObject_Call`, pass an empty tuple as positional arguments using :c:func:`PyTuple_New(0) <PyTuple_New>`. From 1f515e8a109204f7399d85b7fd806135166422d9 Mon Sep 17 00:00:00 2001 From: Eugene Toder <eltoder@users.noreply.github.com> Date: Tue, 30 Jan 2024 10:19:46 -0500 Subject: [PATCH 034/507] gh-112919: Speed-up datetime, date and time.replace() (GH-112921) Use argument clinic and call new_* functions directly. This speeds up these functions 6x to 7.5x when calling with keyword arguments. --- .../pycore_global_objects_fini_generated.h | 8 + Include/internal/pycore_global_strings.h | 8 + .../internal/pycore_runtime_init_generated.h | 8 + .../internal/pycore_unicodeobject_generated.h | 24 ++ ...-12-09-23-31-17.gh-issue-112919.S5k9QN.rst | 2 + Modules/_datetimemodule.c | 170 ++++----- Modules/clinic/_datetimemodule.c.h | 352 +++++++++++++++++- 7 files changed, 476 insertions(+), 96 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-09-23-31-17.gh-issue-112919.S5k9QN.rst diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 57505b5388fd6c3..dd09ff40f39fe6f 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -876,6 +876,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(d)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(data)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(database)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(day)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(decode)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(decoder)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(default)); @@ -945,6 +946,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fix_imports)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(flags)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(flush)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fold)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(follow_symlinks)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(format)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(from_param)); @@ -975,6 +977,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(headers)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hi)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hook)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hour)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(id)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(ident)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(identity_hint)); @@ -1059,11 +1062,14 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(metaclass)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(metadata)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(method)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(microsecond)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(minute)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mod)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mode)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(module)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(module_globals)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(modules)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(month)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mro)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(msg)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mycmp)); @@ -1168,6 +1174,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(salt)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(sched_priority)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(scheduler)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(second)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(seek)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(seekable)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(selectors)); @@ -1244,6 +1251,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(type)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(type_params)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(tz)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(tzinfo)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(tzname)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(uid)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(unlink)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 0f4f3b619102414..79d6509abcdfd91 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -365,6 +365,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(d) STRUCT_FOR_ID(data) STRUCT_FOR_ID(database) + STRUCT_FOR_ID(day) STRUCT_FOR_ID(decode) STRUCT_FOR_ID(decoder) STRUCT_FOR_ID(default) @@ -434,6 +435,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(fix_imports) STRUCT_FOR_ID(flags) STRUCT_FOR_ID(flush) + STRUCT_FOR_ID(fold) STRUCT_FOR_ID(follow_symlinks) STRUCT_FOR_ID(format) STRUCT_FOR_ID(from_param) @@ -464,6 +466,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(headers) STRUCT_FOR_ID(hi) STRUCT_FOR_ID(hook) + STRUCT_FOR_ID(hour) STRUCT_FOR_ID(id) STRUCT_FOR_ID(ident) STRUCT_FOR_ID(identity_hint) @@ -548,11 +551,14 @@ struct _Py_global_strings { STRUCT_FOR_ID(metaclass) STRUCT_FOR_ID(metadata) STRUCT_FOR_ID(method) + STRUCT_FOR_ID(microsecond) + STRUCT_FOR_ID(minute) STRUCT_FOR_ID(mod) STRUCT_FOR_ID(mode) STRUCT_FOR_ID(module) STRUCT_FOR_ID(module_globals) STRUCT_FOR_ID(modules) + STRUCT_FOR_ID(month) STRUCT_FOR_ID(mro) STRUCT_FOR_ID(msg) STRUCT_FOR_ID(mycmp) @@ -657,6 +663,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(salt) STRUCT_FOR_ID(sched_priority) STRUCT_FOR_ID(scheduler) + STRUCT_FOR_ID(second) STRUCT_FOR_ID(seek) STRUCT_FOR_ID(seekable) STRUCT_FOR_ID(selectors) @@ -733,6 +740,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(type) STRUCT_FOR_ID(type_params) STRUCT_FOR_ID(tz) + STRUCT_FOR_ID(tzinfo) STRUCT_FOR_ID(tzname) STRUCT_FOR_ID(uid) STRUCT_FOR_ID(unlink) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 63a2b54c839a4b5..f3c55acfb3c282f 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -874,6 +874,7 @@ extern "C" { INIT_ID(d), \ INIT_ID(data), \ INIT_ID(database), \ + INIT_ID(day), \ INIT_ID(decode), \ INIT_ID(decoder), \ INIT_ID(default), \ @@ -943,6 +944,7 @@ extern "C" { INIT_ID(fix_imports), \ INIT_ID(flags), \ INIT_ID(flush), \ + INIT_ID(fold), \ INIT_ID(follow_symlinks), \ INIT_ID(format), \ INIT_ID(from_param), \ @@ -973,6 +975,7 @@ extern "C" { INIT_ID(headers), \ INIT_ID(hi), \ INIT_ID(hook), \ + INIT_ID(hour), \ INIT_ID(id), \ INIT_ID(ident), \ INIT_ID(identity_hint), \ @@ -1057,11 +1060,14 @@ extern "C" { INIT_ID(metaclass), \ INIT_ID(metadata), \ INIT_ID(method), \ + INIT_ID(microsecond), \ + INIT_ID(minute), \ INIT_ID(mod), \ INIT_ID(mode), \ INIT_ID(module), \ INIT_ID(module_globals), \ INIT_ID(modules), \ + INIT_ID(month), \ INIT_ID(mro), \ INIT_ID(msg), \ INIT_ID(mycmp), \ @@ -1166,6 +1172,7 @@ extern "C" { INIT_ID(salt), \ INIT_ID(sched_priority), \ INIT_ID(scheduler), \ + INIT_ID(second), \ INIT_ID(seek), \ INIT_ID(seekable), \ INIT_ID(selectors), \ @@ -1242,6 +1249,7 @@ extern "C" { INIT_ID(type), \ INIT_ID(type_params), \ INIT_ID(tz), \ + INIT_ID(tzinfo), \ INIT_ID(tzname), \ INIT_ID(uid), \ INIT_ID(unlink), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index bf8cdd85e4be5c1..2e9572382fe0332 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -936,6 +936,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(database); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(day); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(decode); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1143,6 +1146,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(flush); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(fold); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(follow_symlinks); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1233,6 +1239,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(hook); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(hour); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(id); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1485,6 +1494,12 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(method); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(microsecond); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(minute); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(mod); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1500,6 +1515,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(modules); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(month); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(mro); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1812,6 +1830,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(scheduler); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(second); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(seek); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -2040,6 +2061,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(tz); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(tzinfo); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(tzname); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Misc/NEWS.d/next/Library/2023-12-09-23-31-17.gh-issue-112919.S5k9QN.rst b/Misc/NEWS.d/next/Library/2023-12-09-23-31-17.gh-issue-112919.S5k9QN.rst new file mode 100644 index 000000000000000..3e99d480139cbea --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-09-23-31-17.gh-issue-112919.S5k9QN.rst @@ -0,0 +1,2 @@ +Speed-up :func:`datetime.datetime.replace`, :func:`datetime.date.replace` and +:func:`datetime.time.replace`. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index cb5403e8461ff0e..9b8e0a719d9048c 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -61,16 +61,6 @@ static datetime_state _datetime_global_state; #define STATIC_STATE() (&_datetime_global_state) -/*[clinic input] -module datetime -class datetime.datetime "PyDateTime_DateTime *" "&PyDateTime_DateTimeType" -class datetime.date "PyDateTime_Date *" "&PyDateTime_DateType" -class datetime.IsoCalendarDate "PyDateTime_IsoCalendarDate *" "&PyDateTime_IsoCalendarDateType" -[clinic start generated code]*/ -/*[clinic end generated code: output=da39a3ee5e6b4b0d input=81bec0fa19837f63]*/ - -#include "clinic/_datetimemodule.c.h" - /* We require that C int be at least 32 bits, and use int virtually * everywhere. In just a few cases we use a temp long, where a Python * API returns a C long. In such cases, we have to ensure that the @@ -161,6 +151,17 @@ static PyTypeObject PyDateTime_TimeZoneType; static int check_tzinfo_subclass(PyObject *p); +/*[clinic input] +module datetime +class datetime.datetime "PyDateTime_DateTime *" "&PyDateTime_DateTimeType" +class datetime.date "PyDateTime_Date *" "&PyDateTime_DateType" +class datetime.time "PyDateTime_Time *" "&PyDateTime_TimeType" +class datetime.IsoCalendarDate "PyDateTime_IsoCalendarDate *" "&PyDateTime_IsoCalendarDateType" +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=6f65a48dd22fa40f]*/ + +#include "clinic/_datetimemodule.c.h" + /* --------------------------------------------------------------------------- * Math utilities. @@ -3466,24 +3467,22 @@ date_timetuple(PyDateTime_Date *self, PyObject *Py_UNUSED(ignored)) 0, 0, 0, -1); } +/*[clinic input] +datetime.date.replace + + year: int(c_default="GET_YEAR(self)") = unchanged + month: int(c_default="GET_MONTH(self)") = unchanged + day: int(c_default="GET_DAY(self)") = unchanged + +Return date with new specified fields. +[clinic start generated code]*/ + static PyObject * -date_replace(PyDateTime_Date *self, PyObject *args, PyObject *kw) +datetime_date_replace_impl(PyDateTime_Date *self, int year, int month, + int day) +/*[clinic end generated code: output=2a9430d1e6318aeb input=0d1f02685b3e90f6]*/ { - PyObject *clone; - PyObject *tuple; - int year = GET_YEAR(self); - int month = GET_MONTH(self); - int day = GET_DAY(self); - - if (! PyArg_ParseTupleAndKeywords(args, kw, "|iii:replace", date_kws, - &year, &month, &day)) - return NULL; - tuple = Py_BuildValue("iii", year, month, day); - if (tuple == NULL) - return NULL; - clone = date_new(Py_TYPE(self), tuple, NULL); - Py_DECREF(tuple); - return clone; + return new_date_ex(year, month, day, Py_TYPE(self)); } static Py_hash_t @@ -3596,10 +3595,9 @@ static PyMethodDef date_methods[] = { PyDoc_STR("Return the day of the week represented by the date.\n" "Monday == 0 ... Sunday == 6")}, - {"replace", _PyCFunction_CAST(date_replace), METH_VARARGS | METH_KEYWORDS, - PyDoc_STR("Return date with new specified fields.")}, + DATETIME_DATE_REPLACE_METHODDEF - {"__replace__", _PyCFunction_CAST(date_replace), METH_VARARGS | METH_KEYWORDS}, + {"__replace__", _PyCFunction_CAST(datetime_date_replace), METH_FASTCALL | METH_KEYWORDS}, {"__reduce__", (PyCFunction)date_reduce, METH_NOARGS, PyDoc_STR("__reduce__() -> (cls, state)")}, @@ -4573,36 +4571,28 @@ time_hash(PyDateTime_Time *self) return self->hashcode; } +/*[clinic input] +datetime.time.replace + + hour: int(c_default="TIME_GET_HOUR(self)") = unchanged + minute: int(c_default="TIME_GET_MINUTE(self)") = unchanged + second: int(c_default="TIME_GET_SECOND(self)") = unchanged + microsecond: int(c_default="TIME_GET_MICROSECOND(self)") = unchanged + tzinfo: object(c_default="HASTZINFO(self) ? self->tzinfo : Py_None") = unchanged + * + fold: int(c_default="TIME_GET_FOLD(self)") = unchanged + +Return time with new specified fields. +[clinic start generated code]*/ + static PyObject * -time_replace(PyDateTime_Time *self, PyObject *args, PyObject *kw) +datetime_time_replace_impl(PyDateTime_Time *self, int hour, int minute, + int second, int microsecond, PyObject *tzinfo, + int fold) +/*[clinic end generated code: output=0b89a44c299e4f80 input=9b6a35b1e704b0ca]*/ { - PyObject *clone; - PyObject *tuple; - int hh = TIME_GET_HOUR(self); - int mm = TIME_GET_MINUTE(self); - int ss = TIME_GET_SECOND(self); - int us = TIME_GET_MICROSECOND(self); - PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None; - int fold = TIME_GET_FOLD(self); - - if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO$i:replace", - time_kws, - &hh, &mm, &ss, &us, &tzinfo, &fold)) - return NULL; - if (fold != 0 && fold != 1) { - PyErr_SetString(PyExc_ValueError, - "fold must be either 0 or 1"); - return NULL; - } - tuple = Py_BuildValue("iiiiO", hh, mm, ss, us, tzinfo); - if (tuple == NULL) - return NULL; - clone = time_new(Py_TYPE(self), tuple, NULL); - if (clone != NULL) { - TIME_SET_FOLD(clone, fold); - } - Py_DECREF(tuple); - return clone; + return new_time_ex2(hour, minute, second, microsecond, tzinfo, fold, + Py_TYPE(self)); } static PyObject * @@ -4732,10 +4722,9 @@ static PyMethodDef time_methods[] = { {"dst", (PyCFunction)time_dst, METH_NOARGS, PyDoc_STR("Return self.tzinfo.dst(self).")}, - {"replace", _PyCFunction_CAST(time_replace), METH_VARARGS | METH_KEYWORDS, - PyDoc_STR("Return time with new specified fields.")}, + DATETIME_TIME_REPLACE_METHODDEF - {"__replace__", _PyCFunction_CAST(time_replace), METH_VARARGS | METH_KEYWORDS}, + {"__replace__", _PyCFunction_CAST(datetime_time_replace), METH_FASTCALL | METH_KEYWORDS}, {"fromisoformat", (PyCFunction)time_fromisoformat, METH_O | METH_CLASS, PyDoc_STR("string -> time from a string in ISO 8601 format")}, @@ -6042,40 +6031,32 @@ datetime_hash(PyDateTime_DateTime *self) return self->hashcode; } +/*[clinic input] +datetime.datetime.replace + + year: int(c_default="GET_YEAR(self)") = unchanged + month: int(c_default="GET_MONTH(self)") = unchanged + day: int(c_default="GET_DAY(self)") = unchanged + hour: int(c_default="DATE_GET_HOUR(self)") = unchanged + minute: int(c_default="DATE_GET_MINUTE(self)") = unchanged + second: int(c_default="DATE_GET_SECOND(self)") = unchanged + microsecond: int(c_default="DATE_GET_MICROSECOND(self)") = unchanged + tzinfo: object(c_default="HASTZINFO(self) ? self->tzinfo : Py_None") = unchanged + * + fold: int(c_default="DATE_GET_FOLD(self)") = unchanged + +Return datetime with new specified fields. +[clinic start generated code]*/ + static PyObject * -datetime_replace(PyDateTime_DateTime *self, PyObject *args, PyObject *kw) +datetime_datetime_replace_impl(PyDateTime_DateTime *self, int year, + int month, int day, int hour, int minute, + int second, int microsecond, PyObject *tzinfo, + int fold) +/*[clinic end generated code: output=00bc96536833fddb input=9b38253d56d9bcad]*/ { - PyObject *clone; - PyObject *tuple; - int y = GET_YEAR(self); - int m = GET_MONTH(self); - int d = GET_DAY(self); - int hh = DATE_GET_HOUR(self); - int mm = DATE_GET_MINUTE(self); - int ss = DATE_GET_SECOND(self); - int us = DATE_GET_MICROSECOND(self); - PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None; - int fold = DATE_GET_FOLD(self); - - if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiiiiO$i:replace", - datetime_kws, - &y, &m, &d, &hh, &mm, &ss, &us, - &tzinfo, &fold)) - return NULL; - if (fold != 0 && fold != 1) { - PyErr_SetString(PyExc_ValueError, - "fold must be either 0 or 1"); - return NULL; - } - tuple = Py_BuildValue("iiiiiiiO", y, m, d, hh, mm, ss, us, tzinfo); - if (tuple == NULL) - return NULL; - clone = datetime_new(Py_TYPE(self), tuple, NULL); - if (clone != NULL) { - DATE_SET_FOLD(clone, fold); - } - Py_DECREF(tuple); - return clone; + return new_datetime_ex2(year, month, day, hour, minute, second, + microsecond, tzinfo, fold, Py_TYPE(self)); } static PyObject * @@ -6597,10 +6578,9 @@ static PyMethodDef datetime_methods[] = { {"dst", (PyCFunction)datetime_dst, METH_NOARGS, PyDoc_STR("Return self.tzinfo.dst(self).")}, - {"replace", _PyCFunction_CAST(datetime_replace), METH_VARARGS | METH_KEYWORDS, - PyDoc_STR("Return datetime with new specified fields.")}, + DATETIME_DATETIME_REPLACE_METHODDEF - {"__replace__", _PyCFunction_CAST(datetime_replace), METH_VARARGS | METH_KEYWORDS}, + {"__replace__", _PyCFunction_CAST(datetime_datetime_replace), METH_FASTCALL | METH_KEYWORDS}, {"astimezone", _PyCFunction_CAST(datetime_astimezone), METH_VARARGS | METH_KEYWORDS, PyDoc_STR("tz -> convert to local time in new timezone tz\n")}, diff --git a/Modules/clinic/_datetimemodule.c.h b/Modules/clinic/_datetimemodule.c.h index 1ee50fc2a137621..48499e0aaf7783d 100644 --- a/Modules/clinic/_datetimemodule.c.h +++ b/Modules/clinic/_datetimemodule.c.h @@ -82,6 +82,207 @@ iso_calendar_date_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) return return_value; } +PyDoc_STRVAR(datetime_date_replace__doc__, +"replace($self, /, year=unchanged, month=unchanged, day=unchanged)\n" +"--\n" +"\n" +"Return date with new specified fields."); + +#define DATETIME_DATE_REPLACE_METHODDEF \ + {"replace", _PyCFunction_CAST(datetime_date_replace), METH_FASTCALL|METH_KEYWORDS, datetime_date_replace__doc__}, + +static PyObject * +datetime_date_replace_impl(PyDateTime_Date *self, int year, int month, + int day); + +static PyObject * +datetime_date_replace(PyDateTime_Date *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 3 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(year), &_Py_ID(month), &_Py_ID(day), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"year", "month", "day", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "replace", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + int year = GET_YEAR(self); + int month = GET_MONTH(self); + int day = GET_DAY(self); + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 3, 0, argsbuf); + if (!args) { + goto exit; + } + if (!noptargs) { + goto skip_optional_pos; + } + if (args[0]) { + year = PyLong_AsInt(args[0]); + if (year == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[1]) { + month = PyLong_AsInt(args[1]); + if (month == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + day = PyLong_AsInt(args[2]); + if (day == -1 && PyErr_Occurred()) { + goto exit; + } +skip_optional_pos: + return_value = datetime_date_replace_impl(self, year, month, day); + +exit: + return return_value; +} + +PyDoc_STRVAR(datetime_time_replace__doc__, +"replace($self, /, hour=unchanged, minute=unchanged, second=unchanged,\n" +" microsecond=unchanged, tzinfo=unchanged, *, fold=unchanged)\n" +"--\n" +"\n" +"Return time with new specified fields."); + +#define DATETIME_TIME_REPLACE_METHODDEF \ + {"replace", _PyCFunction_CAST(datetime_time_replace), METH_FASTCALL|METH_KEYWORDS, datetime_time_replace__doc__}, + +static PyObject * +datetime_time_replace_impl(PyDateTime_Time *self, int hour, int minute, + int second, int microsecond, PyObject *tzinfo, + int fold); + +static PyObject * +datetime_time_replace(PyDateTime_Time *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 6 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(hour), &_Py_ID(minute), &_Py_ID(second), &_Py_ID(microsecond), &_Py_ID(tzinfo), &_Py_ID(fold), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"hour", "minute", "second", "microsecond", "tzinfo", "fold", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "replace", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[6]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + int hour = TIME_GET_HOUR(self); + int minute = TIME_GET_MINUTE(self); + int second = TIME_GET_SECOND(self); + int microsecond = TIME_GET_MICROSECOND(self); + PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None; + int fold = TIME_GET_FOLD(self); + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 5, 0, argsbuf); + if (!args) { + goto exit; + } + if (!noptargs) { + goto skip_optional_pos; + } + if (args[0]) { + hour = PyLong_AsInt(args[0]); + if (hour == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[1]) { + minute = PyLong_AsInt(args[1]); + if (minute == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[2]) { + second = PyLong_AsInt(args[2]); + if (second == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[3]) { + microsecond = PyLong_AsInt(args[3]); + if (microsecond == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[4]) { + tzinfo = args[4]; + if (!--noptargs) { + goto skip_optional_pos; + } + } +skip_optional_pos: + if (!noptargs) { + goto skip_optional_kwonly; + } + fold = PyLong_AsInt(args[5]); + if (fold == -1 && PyErr_Occurred()) { + goto exit; + } +skip_optional_kwonly: + return_value = datetime_time_replace_impl(self, hour, minute, second, microsecond, tzinfo, fold); + +exit: + return return_value; +} + PyDoc_STRVAR(datetime_datetime_now__doc__, "now($type, /, tz=None)\n" "--\n" @@ -146,4 +347,153 @@ datetime_datetime_now(PyTypeObject *type, PyObject *const *args, Py_ssize_t narg exit: return return_value; } -/*[clinic end generated code: output=562813dd3e164794 input=a9049054013a1b77]*/ + +PyDoc_STRVAR(datetime_datetime_replace__doc__, +"replace($self, /, year=unchanged, month=unchanged, day=unchanged,\n" +" hour=unchanged, minute=unchanged, second=unchanged,\n" +" microsecond=unchanged, tzinfo=unchanged, *, fold=unchanged)\n" +"--\n" +"\n" +"Return datetime with new specified fields."); + +#define DATETIME_DATETIME_REPLACE_METHODDEF \ + {"replace", _PyCFunction_CAST(datetime_datetime_replace), METH_FASTCALL|METH_KEYWORDS, datetime_datetime_replace__doc__}, + +static PyObject * +datetime_datetime_replace_impl(PyDateTime_DateTime *self, int year, + int month, int day, int hour, int minute, + int second, int microsecond, PyObject *tzinfo, + int fold); + +static PyObject * +datetime_datetime_replace(PyDateTime_DateTime *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 9 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(year), &_Py_ID(month), &_Py_ID(day), &_Py_ID(hour), &_Py_ID(minute), &_Py_ID(second), &_Py_ID(microsecond), &_Py_ID(tzinfo), &_Py_ID(fold), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"year", "month", "day", "hour", "minute", "second", "microsecond", "tzinfo", "fold", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "replace", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[9]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + int year = GET_YEAR(self); + int month = GET_MONTH(self); + int day = GET_DAY(self); + int hour = DATE_GET_HOUR(self); + int minute = DATE_GET_MINUTE(self); + int second = DATE_GET_SECOND(self); + int microsecond = DATE_GET_MICROSECOND(self); + PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None; + int fold = DATE_GET_FOLD(self); + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 8, 0, argsbuf); + if (!args) { + goto exit; + } + if (!noptargs) { + goto skip_optional_pos; + } + if (args[0]) { + year = PyLong_AsInt(args[0]); + if (year == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[1]) { + month = PyLong_AsInt(args[1]); + if (month == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[2]) { + day = PyLong_AsInt(args[2]); + if (day == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[3]) { + hour = PyLong_AsInt(args[3]); + if (hour == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[4]) { + minute = PyLong_AsInt(args[4]); + if (minute == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[5]) { + second = PyLong_AsInt(args[5]); + if (second == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[6]) { + microsecond = PyLong_AsInt(args[6]); + if (microsecond == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[7]) { + tzinfo = args[7]; + if (!--noptargs) { + goto skip_optional_pos; + } + } +skip_optional_pos: + if (!noptargs) { + goto skip_optional_kwonly; + } + fold = PyLong_AsInt(args[8]); + if (fold == -1 && PyErr_Occurred()) { + goto exit; + } +skip_optional_kwonly: + return_value = datetime_datetime_replace_impl(self, year, month, day, hour, minute, second, microsecond, tzinfo, fold); + +exit: + return return_value; +} +/*[clinic end generated code: output=c7a04b865b1e0890 input=a9049054013a1b77]*/ From 39d102c2ee8eec8ab0bacbcd62d62a72742ecc7c Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado <Pablogsal@gmail.com> Date: Tue, 30 Jan 2024 16:21:30 +0000 Subject: [PATCH 035/507] gh-113744: Add a new IncompleteInputError exception to improve incomplete input detection in the codeop module (#113745) Signed-off-by: Pablo Galindo <pablogsal@gmail.com> --- Doc/data/stable_abi.dat | 1 + Include/pyerrors.h | 1 + Lib/codeop.py | 5 +++-- Lib/test/exception_hierarchy.txt | 1 + Lib/test/test_pickle.py | 3 ++- Lib/test/test_stable_abi_ctypes.py | 1 + Misc/stable_abi.toml | 2 ++ Objects/exceptions.c | 6 ++++++ PC/python3dll.c | 1 + Parser/pegen.c | 2 +- Tools/c-analyzer/cpython/globals-to-fix.tsv | 2 ++ 11 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 811b1bd84d24174..da28a2be60bc1bc 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -220,6 +220,7 @@ var,PyExc_GeneratorExit,3.2,, var,PyExc_IOError,3.2,, var,PyExc_ImportError,3.2,, var,PyExc_ImportWarning,3.2,, +var,PyExc_IncompleteInputError,3.13,, var,PyExc_IndentationError,3.2,, var,PyExc_IndexError,3.2,, var,PyExc_InterruptedError,3.7,, diff --git a/Include/pyerrors.h b/Include/pyerrors.h index 5d0028c116e2d86..68d7985dac8876b 100644 --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -108,6 +108,7 @@ PyAPI_DATA(PyObject *) PyExc_NotImplementedError; PyAPI_DATA(PyObject *) PyExc_SyntaxError; PyAPI_DATA(PyObject *) PyExc_IndentationError; PyAPI_DATA(PyObject *) PyExc_TabError; +PyAPI_DATA(PyObject *) PyExc_IncompleteInputError; PyAPI_DATA(PyObject *) PyExc_ReferenceError; PyAPI_DATA(PyObject *) PyExc_SystemError; PyAPI_DATA(PyObject *) PyExc_SystemExit; diff --git a/Lib/codeop.py b/Lib/codeop.py index 91146be2c438e2c..6ad60e7f85098d8 100644 --- a/Lib/codeop.py +++ b/Lib/codeop.py @@ -65,9 +65,10 @@ def _maybe_compile(compiler, source, filename, symbol): try: compiler(source + "\n", filename, symbol) return None + except IncompleteInputError as e: + return None except SyntaxError as e: - if "incomplete input" in str(e): - return None + pass # fallthrough return compiler(source, filename, symbol, incomplete_input=False) diff --git a/Lib/test/exception_hierarchy.txt b/Lib/test/exception_hierarchy.txt index 1eca123be0fecbf..217ee15d4c8af54 100644 --- a/Lib/test/exception_hierarchy.txt +++ b/Lib/test/exception_hierarchy.txt @@ -44,6 +44,7 @@ BaseException ├── StopAsyncIteration ├── StopIteration ├── SyntaxError + │ └── IncompleteInputError │ └── IndentationError │ └── TabError ├── SystemError diff --git a/Lib/test/test_pickle.py b/Lib/test/test_pickle.py index b2245ddf72f7085..5e187e5189d1179 100644 --- a/Lib/test/test_pickle.py +++ b/Lib/test/test_pickle.py @@ -567,7 +567,8 @@ def test_exceptions(self): RecursionError, EncodingWarning, BaseExceptionGroup, - ExceptionGroup): + ExceptionGroup, + IncompleteInputError): continue if exc is not OSError and issubclass(exc, OSError): self.assertEqual(reverse_mapping('builtins', name), diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 90d452728384208..054e7f0feb1a19d 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -261,6 +261,7 @@ def test_windows_feature_macros(self): "PyExc_IOError", "PyExc_ImportError", "PyExc_ImportWarning", + "PyExc_IncompleteInputError", "PyExc_IndentationError", "PyExc_IndexError", "PyExc_InterruptedError", diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 2e6b0fff9cd7704..ae19d25809ec867 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2485,3 +2485,5 @@ [function._Py_SetRefcnt] added = '3.13' abi_only = true +[data.PyExc_IncompleteInputError] + added = '3.13' diff --git a/Objects/exceptions.c b/Objects/exceptions.c index a685ed803cd02db..cff55d05163b6ba 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2566,6 +2566,11 @@ MiddlingExtendsException(PyExc_SyntaxError, IndentationError, SyntaxError, MiddlingExtendsException(PyExc_IndentationError, TabError, SyntaxError, "Improper mixture of spaces and tabs."); +/* + * IncompleteInputError extends SyntaxError + */ +MiddlingExtendsException(PyExc_SyntaxError, IncompleteInputError, SyntaxError, + "incomplete input."); /* * LookupError extends Exception @@ -3635,6 +3640,7 @@ static struct static_exception static_exceptions[] = { // Level 4: Other subclasses ITEM(IndentationError), // base: SyntaxError(Exception) + ITEM(IncompleteInputError), // base: SyntaxError(Exception) ITEM(IndexError), // base: LookupError(Exception) ITEM(KeyError), // base: LookupError(Exception) ITEM(ModuleNotFoundError), // base: ImportError(Exception) diff --git a/PC/python3dll.c b/PC/python3dll.c index 07aa84c91f9fc7f..09ecf98fe56ea62 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -830,6 +830,7 @@ EXPORT_DATA(PyExc_FutureWarning) EXPORT_DATA(PyExc_GeneratorExit) EXPORT_DATA(PyExc_ImportError) EXPORT_DATA(PyExc_ImportWarning) +EXPORT_DATA(PyExc_IncompleteInputError) EXPORT_DATA(PyExc_IndentationError) EXPORT_DATA(PyExc_IndexError) EXPORT_DATA(PyExc_InterruptedError) diff --git a/Parser/pegen.c b/Parser/pegen.c index 7766253a76066f0..3d3e64559403b16 100644 --- a/Parser/pegen.c +++ b/Parser/pegen.c @@ -844,7 +844,7 @@ _PyPegen_run_parser(Parser *p) if (res == NULL) { if ((p->flags & PyPARSE_ALLOW_INCOMPLETE_INPUT) && _is_end_of_source(p)) { PyErr_Clear(); - return RAISE_SYNTAX_ERROR("incomplete input"); + return _PyPegen_raise_error(p, PyExc_IncompleteInputError, 0, "incomplete input"); } if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_SyntaxError)) { return NULL; diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index e3a1b5d532bda2a..0b02ad01d399836 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -197,6 +197,7 @@ Objects/exceptions.c - _PyExc_AttributeError - Objects/exceptions.c - _PyExc_SyntaxError - Objects/exceptions.c - _PyExc_IndentationError - Objects/exceptions.c - _PyExc_TabError - +Objects/exceptions.c - _PyExc_IncompleteInputError - Objects/exceptions.c - _PyExc_LookupError - Objects/exceptions.c - _PyExc_IndexError - Objects/exceptions.c - _PyExc_KeyError - @@ -261,6 +262,7 @@ Objects/exceptions.c - PyExc_AttributeError - Objects/exceptions.c - PyExc_SyntaxError - Objects/exceptions.c - PyExc_IndentationError - Objects/exceptions.c - PyExc_TabError - +Objects/exceptions.c - PyExc_IncompleteInputError - Objects/exceptions.c - PyExc_LookupError - Objects/exceptions.c - PyExc_IndexError - Objects/exceptions.c - PyExc_KeyError - From 0990d55725cb649e74739c983b67cf08c58e8439 Mon Sep 17 00:00:00 2001 From: Dino Viehland <dinoviehland@meta.com> Date: Tue, 30 Jan 2024 09:33:36 -0800 Subject: [PATCH 036/507] gh-112075: refactor dictionary lookup functions for better re-usability (#114629) Refactor dict lookup functions to use force inline helpers --- Objects/dictobject.c | 192 +++++++++++++++++++++---------------------- 1 file changed, 95 insertions(+), 97 deletions(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index c5477ab15f8dc9a..23d7e9b5e38a356 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -874,11 +874,11 @@ lookdict_index(PyDictKeysObject *k, Py_hash_t hash, Py_ssize_t index) Py_UNREACHABLE(); } -// Search non-Unicode key from Unicode table -static Py_ssize_t -unicodekeys_lookup_generic(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) +static inline Py_ALWAYS_INLINE Py_ssize_t +do_lookup(PyDictObject *mp, PyDictKeysObject *dk, PyObject *key, Py_hash_t hash, + Py_ssize_t (*check_lookup)(PyDictObject *, PyDictKeysObject *, void *, Py_ssize_t ix, PyObject *key, Py_hash_t)) { - PyDictUnicodeEntry *ep0 = DK_UNICODE_ENTRIES(dk); + void *ep0 = _DK_ENTRIES(dk); size_t mask = DK_MASK(dk); size_t perturb = hash; size_t i = (size_t)hash & mask; @@ -886,73 +886,26 @@ unicodekeys_lookup_generic(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key for (;;) { ix = dictkeys_get_index(dk, i); if (ix >= 0) { - PyDictUnicodeEntry *ep = &ep0[ix]; - assert(ep->me_key != NULL); - assert(PyUnicode_CheckExact(ep->me_key)); - if (ep->me_key == key) { + Py_ssize_t cmp = check_lookup(mp, dk, ep0, ix, key, hash); + if (cmp < 0) { + return cmp; + } else if (cmp) { return ix; } - if (unicode_get_hash(ep->me_key) == hash) { - PyObject *startkey = ep->me_key; - Py_INCREF(startkey); - int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); - Py_DECREF(startkey); - if (cmp < 0) { - return DKIX_ERROR; - } - if (dk == mp->ma_keys && ep->me_key == startkey) { - if (cmp > 0) { - return ix; - } - } - else { - /* The dict was mutated, restart */ - return DKIX_KEY_CHANGED; - } - } } else if (ix == DKIX_EMPTY) { return DKIX_EMPTY; } perturb >>= PERTURB_SHIFT; i = mask & (i*5 + perturb + 1); - } - Py_UNREACHABLE(); -} -// Search Unicode key from Unicode table. -static Py_ssize_t _Py_HOT_FUNCTION -unicodekeys_lookup_unicode(PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) -{ - PyDictUnicodeEntry *ep0 = DK_UNICODE_ENTRIES(dk); - size_t mask = DK_MASK(dk); - size_t perturb = hash; - size_t i = (size_t)hash & mask; - Py_ssize_t ix; - for (;;) { - ix = dictkeys_get_index(dk, i); - if (ix >= 0) { - PyDictUnicodeEntry *ep = &ep0[ix]; - assert(ep->me_key != NULL); - assert(PyUnicode_CheckExact(ep->me_key)); - if (ep->me_key == key || - (unicode_get_hash(ep->me_key) == hash && unicode_eq(ep->me_key, key))) { - return ix; - } - } - else if (ix == DKIX_EMPTY) { - return DKIX_EMPTY; - } - perturb >>= PERTURB_SHIFT; - i = mask & (i*5 + perturb + 1); // Manual loop unrolling ix = dictkeys_get_index(dk, i); if (ix >= 0) { - PyDictUnicodeEntry *ep = &ep0[ix]; - assert(ep->me_key != NULL); - assert(PyUnicode_CheckExact(ep->me_key)); - if (ep->me_key == key || - (unicode_get_hash(ep->me_key) == hash && unicode_eq(ep->me_key, key))) { + Py_ssize_t cmp = check_lookup(mp, dk, ep0, ix, key, hash); + if (cmp < 0) { + return cmp; + } else if (cmp) { return ix; } } @@ -965,49 +918,94 @@ unicodekeys_lookup_unicode(PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) Py_UNREACHABLE(); } -// Search key from Generic table. +static inline Py_ALWAYS_INLINE Py_ssize_t +compare_unicode_generic(PyDictObject *mp, PyDictKeysObject *dk, + void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash) +{ + PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix]; + assert(ep->me_key != NULL); + assert(PyUnicode_CheckExact(ep->me_key)); + assert(!PyUnicode_CheckExact(key)); + // TODO: Thread safety + + if (unicode_get_hash(ep->me_key) == hash) { + PyObject *startkey = ep->me_key; + Py_INCREF(startkey); + int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); + Py_DECREF(startkey); + if (cmp < 0) { + return DKIX_ERROR; + } + if (dk == mp->ma_keys && ep->me_key == startkey) { + return cmp; + } + else { + /* The dict was mutated, restart */ + return DKIX_KEY_CHANGED; + } + } + return 0; +} + +// Search non-Unicode key from Unicode table static Py_ssize_t -dictkeys_generic_lookup(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) +unicodekeys_lookup_generic(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) { - PyDictKeyEntry *ep0 = DK_ENTRIES(dk); - size_t mask = DK_MASK(dk); - size_t perturb = hash; - size_t i = (size_t)hash & mask; - Py_ssize_t ix; - for (;;) { - ix = dictkeys_get_index(dk, i); - if (ix >= 0) { - PyDictKeyEntry *ep = &ep0[ix]; - assert(ep->me_key != NULL); - if (ep->me_key == key) { - return ix; - } - if (ep->me_hash == hash) { - PyObject *startkey = ep->me_key; - Py_INCREF(startkey); - int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); - Py_DECREF(startkey); - if (cmp < 0) { - return DKIX_ERROR; - } - if (dk == mp->ma_keys && ep->me_key == startkey) { - if (cmp > 0) { - return ix; - } - } - else { - /* The dict was mutated, restart */ - return DKIX_KEY_CHANGED; - } - } + return do_lookup(mp, dk, key, hash, compare_unicode_generic); +} + +static inline Py_ALWAYS_INLINE Py_ssize_t +compare_unicode_unicode(PyDictObject *mp, PyDictKeysObject *dk, + void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash) +{ + PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix]; + assert(ep->me_key != NULL); + assert(PyUnicode_CheckExact(ep->me_key)); + if (ep->me_key == key || + (unicode_get_hash(ep->me_key) == hash && unicode_eq(ep->me_key, key))) { + return 1; + } + return 0; +} + +static Py_ssize_t _Py_HOT_FUNCTION +unicodekeys_lookup_unicode(PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) +{ + return do_lookup(NULL, dk, key, hash, compare_unicode_unicode); +} + +static inline Py_ALWAYS_INLINE Py_ssize_t +compare_generic(PyDictObject *mp, PyDictKeysObject *dk, + void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash) +{ + PyDictKeyEntry *ep = &((PyDictKeyEntry *)ep0)[ix]; + assert(ep->me_key != NULL); + if (ep->me_key == key) { + return 1; + } + if (ep->me_hash == hash) { + PyObject *startkey = ep->me_key; + Py_INCREF(startkey); + int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); + Py_DECREF(startkey); + if (cmp < 0) { + return DKIX_ERROR; } - else if (ix == DKIX_EMPTY) { - return DKIX_EMPTY; + if (dk == mp->ma_keys && ep->me_key == startkey) { + return cmp; + } + else { + /* The dict was mutated, restart */ + return DKIX_KEY_CHANGED; } - perturb >>= PERTURB_SHIFT; - i = mask & (i*5 + perturb + 1); } - Py_UNREACHABLE(); + return 0; +} + +static Py_ssize_t +dictkeys_generic_lookup(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) +{ + return do_lookup(mp, dk, key, hash, compare_generic); } /* Lookup a string in a (all unicode) dict keys. From a1332a99cf1eb9b879d4b1f28761b096b5749a0d Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy <tjreedy@udel.edu> Date: Tue, 30 Jan 2024 13:40:54 -0500 Subject: [PATCH 037/507] Clarify one-item tuple (#114745) A 'single tuple' means 'one typle, of whatever length. Remove the unneeded and slight distracting parenthetical 'singleton' comment. --- Doc/reference/expressions.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index 87ebdc1ca1c9c6a..50e0f97a6534af2 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -1890,8 +1890,9 @@ the unpacking. .. index:: pair: trailing; comma -The trailing comma is required only to create a single tuple (a.k.a. a -*singleton*); it is optional in all other cases. A single expression without a +A trailing comma is required only to create a one-item tuple, +such as ``1,``; it is optional in all other cases. +A single expression without a trailing comma doesn't create a tuple, but rather yields the value of that expression. (To create an empty tuple, use an empty pair of parentheses: ``()``.) From 6de8aa31f39b3d8dbfba132e6649724eb07b8348 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Tue, 30 Jan 2024 21:44:09 +0300 Subject: [PATCH 038/507] ``importlib/_bootstrap.py``: Reduce size of ``_List`` instances (GH-114747) Reduce size of _List instances --- Lib/importlib/_bootstrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py index d942045f3de666f..6d6292f95592534 100644 --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -53,7 +53,7 @@ def _new_module(name): # For a list that can have a weakref to it. class _List(list): - pass + __slots__ = ("__weakref__",) # Copied from weakref.py with some simplifications and modifications unique to From fda7445ca50b892955fc31bd72a3615fef1d70c6 Mon Sep 17 00:00:00 2001 From: Barney Gale <barney.gale@gmail.com> Date: Tue, 30 Jan 2024 19:52:53 +0000 Subject: [PATCH 039/507] GH-70303: Make `pathlib.Path.glob('**')` return both files and directories (#114684) Return files and directories from `pathlib.Path.glob()` if the pattern ends with `**`. This is more compatible with `PurePath.full_match()` and with other glob implementations such as bash and `glob.glob()`. Users can add a trailing slash to match only directories. In my previous patch I added a `FutureWarning` with the intention of fixing this in Python 3.15. Upon further reflection I think this was an unnecessarily cautious remedy to a clear bug. --- Doc/library/pathlib.rst | 5 ++--- Doc/whatsnew/3.13.rst | 10 +++++++++ Lib/pathlib/__init__.py | 8 ------- Lib/test/test_pathlib/test_pathlib.py | 12 ----------- Lib/test/test_pathlib/test_pathlib_abc.py | 21 +++++++++++++++++++ ...4-01-28-18-38-18.gh-issue-70303._Lt_pj.rst | 2 ++ 6 files changed, 35 insertions(+), 23 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-28-18-38-18.gh-issue-70303._Lt_pj.rst diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index f1aba793fda03e7..f94b6fb38056847 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -1038,9 +1038,8 @@ call fails (for example because the path doesn't exist). The *follow_symlinks* parameter was added. .. versionchanged:: 3.13 - Emits :exc:`FutureWarning` if the pattern ends with "``**``". In a - future Python release, patterns with this ending will match both files - and directories. Add a trailing slash to match only directories. + Return files and directories if *pattern* ends with "``**``". In + previous versions, only directories were returned. .. versionchanged:: 3.13 The *pattern* parameter accepts a :term:`path-like object`. diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index fec1e55e0daf0e6..b33203efbb05c0c 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -350,6 +350,11 @@ pathlib (Contributed by Barney Gale in :gh:`77609` and :gh:`105793`, and Kamil Turek in :gh:`107962`). +* Return files and directories from :meth:`pathlib.Path.glob` and + :meth:`~pathlib.Path.rglob` when given a pattern that ends with "``**``". In + earlier versions, only directories were returned. + (Contributed by Barney Gale in :gh:`70303`). + pdb --- @@ -1211,6 +1216,11 @@ Changes in the Python API * :class:`mailbox.Maildir` now ignores files with a leading dot. (Contributed by Zackery Spytz in :gh:`65559`.) +* :meth:`pathlib.Path.glob` and :meth:`~pathlib.Path.rglob` now return both + files and directories if a pattern that ends with "``**``" is given, rather + than directories only. Users may add a trailing slash to match only + directories. + Build Changes ============= diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index cc159edab5796f5..4447f98e3e86897 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -465,14 +465,6 @@ def _pattern_stack(self): elif pattern[-1] in (self.pathmod.sep, self.pathmod.altsep): # GH-65238: pathlib doesn't preserve trailing slash. Add it back. parts.append('') - elif parts[-1] == '**': - # GH-70303: '**' only matches directories. Add trailing slash. - warnings.warn( - "Pattern ending '**' will match files and directories in a " - "future Python release. Add a trailing slash to match only " - "directories and remove this warning.", - FutureWarning, 4) - parts.append('') parts.reverse() return parts diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index a8cc30ef0ab63f6..3bee9b8c762b805 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -1258,18 +1258,6 @@ def test_glob_above_recursion_limit(self): with set_recursion_limit(recursion_limit): list(base.glob('**/')) - def test_glob_recursive_no_trailing_slash(self): - P = self.cls - p = P(self.base) - with self.assertWarns(FutureWarning): - p.glob('**') - with self.assertWarns(FutureWarning): - p.glob('*/**') - with self.assertWarns(FutureWarning): - p.rglob('**') - with self.assertWarns(FutureWarning): - p.rglob('*/**') - def test_glob_pathlike(self): P = self.cls p = P(self.base) diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 18a612f6b81bace..0e12182c162c142 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -1765,16 +1765,26 @@ def _check(path, glob, expected): _check(p, "*/fileB", ["dirB/fileB", "linkB/fileB"]) _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"]) _check(p, "dir*/*/..", ["dirC/dirD/..", "dirA/linkC/..", "dirB/linkD/.."]) + _check(p, "dir*/**", [ + "dirA", "dirA/linkC", "dirA/linkC/fileB", "dirA/linkC/linkD", "dirA/linkC/linkD/fileB", + "dirB", "dirB/fileB", "dirB/linkD", "dirB/linkD/fileB", + "dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt", + "dirE"]) _check(p, "dir*/**/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", "dirC/", "dirC/dirD/", "dirE/"]) _check(p, "dir*/**/..", ["dirA/..", "dirA/linkC/..", "dirB/..", "dirB/linkD/..", "dirA/linkC/linkD/..", "dirC/..", "dirC/dirD/..", "dirE/.."]) + _check(p, "dir*/*/**", [ + "dirA/linkC", "dirA/linkC/linkD", "dirA/linkC/fileB", "dirA/linkC/linkD/fileB", + "dirB/linkD", "dirB/linkD/fileB", + "dirC/dirD", "dirC/dirD/fileD"]) _check(p, "dir*/*/**/", ["dirA/linkC/", "dirA/linkC/linkD/", "dirB/linkD/", "dirC/dirD/"]) _check(p, "dir*/*/**/..", ["dirA/linkC/..", "dirA/linkC/linkD/..", "dirB/linkD/..", "dirC/dirD/.."]) _check(p, "dir*/**/fileC", ["dirC/fileC"]) _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) + _check(p, "*/dirD/**", ["dirC/dirD", "dirC/dirD/fileD"]) _check(p, "*/dirD/**/", ["dirC/dirD/"]) @needs_symlinks @@ -1791,12 +1801,20 @@ def _check(path, glob, expected): _check(p, "*/fileB", ["dirB/fileB"]) _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/"]) _check(p, "dir*/*/..", ["dirC/dirD/.."]) + _check(p, "dir*/**", [ + "dirA", "dirA/linkC", + "dirB", "dirB/fileB", "dirB/linkD", + "dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt", + "dirE"]) _check(p, "dir*/**/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"]) _check(p, "dir*/**/..", ["dirA/..", "dirB/..", "dirC/..", "dirC/dirD/..", "dirE/.."]) + _check(p, "dir*/*/**", ["dirC/dirD", "dirC/dirD/fileD"]) _check(p, "dir*/*/**/", ["dirC/dirD/"]) _check(p, "dir*/*/**/..", ["dirC/dirD/.."]) _check(p, "dir*/**/fileC", ["dirC/fileC"]) + _check(p, "dir*/*/../dirD/**", ["dirC/dirD/../dirD", "dirC/dirD/../dirD/fileD"]) _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) + _check(p, "*/dirD/**", ["dirC/dirD", "dirC/dirD/fileD"]) _check(p, "*/dirD/**/", ["dirC/dirD/"]) def test_rglob_common(self): @@ -1833,10 +1851,13 @@ def _check(glob, expected): "dirC/dirD", "dirC/dirD/fileD"]) _check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"]) _check(p.rglob("**/file*"), ["dirC/fileC", "dirC/dirD/fileD"]) + _check(p.rglob("dir*/**"), ["dirC/dirD", "dirC/dirD/fileD"]) _check(p.rglob("dir*/**/"), ["dirC/dirD/"]) _check(p.rglob("*/*"), ["dirC/dirD/fileD"]) _check(p.rglob("*/"), ["dirC/dirD/"]) _check(p.rglob(""), ["dirC/", "dirC/dirD/"]) + _check(p.rglob("**"), [ + "dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt"]) _check(p.rglob("**/"), ["dirC/", "dirC/dirD/"]) # gh-91616, a re module regression _check(p.rglob("*.txt"), ["dirC/novel.txt"]) diff --git a/Misc/NEWS.d/next/Library/2024-01-28-18-38-18.gh-issue-70303._Lt_pj.rst b/Misc/NEWS.d/next/Library/2024-01-28-18-38-18.gh-issue-70303._Lt_pj.rst new file mode 100644 index 000000000000000..dedda24b481241e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-28-18-38-18.gh-issue-70303._Lt_pj.rst @@ -0,0 +1,2 @@ +Return both files and directories from :meth:`pathlib.Path.glob` if a +pattern ends with "``**``". Previously only directories were returned. From e5e186609fdd74bc53e8478da22b76440d996baa Mon Sep 17 00:00:00 2001 From: Matt Prodani <mattp@nyu.edu> Date: Tue, 30 Jan 2024 16:22:17 -0500 Subject: [PATCH 040/507] gh-112606: Use pthread_cond_timedwait_relative_np() in parking_lot.c when available (#112616) Add a configure define for HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP and replaces pthread_cond_timedwait() with pthread_cond_timedwait_relative_np() for relative time when supported in semaphore waiting logic. --- Python/parking_lot.c | 6 +++++- configure | 6 ++++++ configure.ac | 4 ++-- pyconfig.h.in | 4 ++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Python/parking_lot.c b/Python/parking_lot.c index d44c1b4b93b4d2b..c83d7443e289c56 100644 --- a/Python/parking_lot.c +++ b/Python/parking_lot.c @@ -158,11 +158,15 @@ _PySemaphore_PlatformWait(_PySemaphore *sema, _PyTime_t timeout) if (sema->counter == 0) { if (timeout >= 0) { struct timespec ts; - +#if defined(HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP) + _PyTime_AsTimespec_clamp(timeout, &ts); + err = pthread_cond_timedwait_relative_np(&sema->cond, &sema->mutex, &ts); +#else _PyTime_t deadline = _PyTime_Add(_PyTime_GetSystemClock(), timeout); _PyTime_AsTimespec_clamp(deadline, &ts); err = pthread_cond_timedwait(&sema->cond, &sema->mutex, &ts); +#endif // HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP } else { err = pthread_cond_wait(&sema->cond, &sema->mutex); diff --git a/configure b/configure index adc5a8f014c795a..7b2119ff7f4f784 100755 --- a/configure +++ b/configure @@ -17871,6 +17871,12 @@ if test "x$ac_cv_func_preadv2" = xyes then : printf "%s\n" "#define HAVE_PREADV2 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "pthread_cond_timedwait_relative_np" "ac_cv_func_pthread_cond_timedwait_relative_np" +if test "x$ac_cv_func_pthread_cond_timedwait_relative_np" = xyes +then : + printf "%s\n" "#define HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "pthread_condattr_setclock" "ac_cv_func_pthread_condattr_setclock" if test "x$ac_cv_func_pthread_condattr_setclock" = xyes diff --git a/configure.ac b/configure.ac index f5fa17fd8b7d0dc..5bef2351c987e83 100644 --- a/configure.ac +++ b/configure.ac @@ -4796,8 +4796,8 @@ AC_CHECK_FUNCS([ \ mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ pipe2 plock poll posix_fadvise posix_fallocate posix_openpt posix_spawn posix_spawnp \ posix_spawn_file_actions_addclosefrom_np \ - pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill ptsname ptsname_r \ - pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ + pread preadv preadv2 pthread_cond_timedwait_relative_np pthread_condattr_setclock pthread_init \ + pthread_kill ptsname ptsname_r pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \ sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \ sem_timedwait sem_unlink sendfile setegid seteuid setgid sethostname \ diff --git a/pyconfig.h.in b/pyconfig.h.in index 02e33c7007196d3..b22740710bcbee5 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -936,6 +936,10 @@ /* Define to 1 if you have the `pthread_condattr_setclock' function. */ #undef HAVE_PTHREAD_CONDATTR_SETCLOCK +/* Define to 1 if you have the `pthread_cond_timedwait_relative_np' function. + */ +#undef HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP + /* Defined for Solaris 2.6 bug in pthread header. */ #undef HAVE_PTHREAD_DESTRUCTOR From 3911b42cc0d404e0eac87fce30b740b08618ff06 Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Tue, 30 Jan 2024 15:54:37 -0600 Subject: [PATCH 041/507] gh-101100: Fix references in csv docs (GH-114658) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/library/csv.rst | 14 +++++++------- Doc/tools/.nitignore | 1 - 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Doc/library/csv.rst b/Doc/library/csv.rst index 07f38f5690bb540..66888c22b7cc28a 100644 --- a/Doc/library/csv.rst +++ b/Doc/library/csv.rst @@ -88,7 +88,7 @@ The :mod:`csv` module defines the following functions: Return a writer object responsible for converting the user's data into delimited strings on the given file-like object. *csvfile* can be any object with a - :func:`write` method. If *csvfile* is a file object, it should be opened with + :meth:`~io.TextIOBase.write` method. If *csvfile* is a file object, it should be opened with ``newline=''`` [1]_. An optional *dialect* parameter can be given which is used to define a set of parameters specific to a particular CSV dialect. It may be an instance of a subclass of the @@ -197,10 +197,10 @@ The :mod:`csv` module defines the following classes: Create an object which operates like a regular writer but maps dictionaries onto output rows. The *fieldnames* parameter is a :mod:`sequence <collections.abc>` of keys that identify the order in which values in the - dictionary passed to the :meth:`writerow` method are written to file + dictionary passed to the :meth:`~csvwriter.writerow` method are written to file *f*. The optional *restval* parameter specifies the value to be written if the dictionary is missing a key in *fieldnames*. If the - dictionary passed to the :meth:`writerow` method contains a key not found in + dictionary passed to the :meth:`~csvwriter.writerow` method contains a key not found in *fieldnames*, the optional *extrasaction* parameter indicates what action to take. If it is set to ``'raise'``, the default value, a :exc:`ValueError` @@ -374,8 +374,8 @@ Dialects and Formatting Parameters To make it easier to specify the format of input and output records, specific formatting parameters are grouped together into dialects. A dialect is a -subclass of the :class:`Dialect` class having a set of specific methods and a -single :meth:`validate` method. When creating :class:`reader` or +subclass of the :class:`Dialect` class containing various attributes +describing the format of the CSV file. When creating :class:`reader` or :class:`writer` objects, the programmer can specify a string or a subclass of the :class:`Dialect` class as the dialect parameter. In addition to, or instead of, the *dialect* parameter, the programmer can also specify individual @@ -492,9 +492,9 @@ DictReader objects have the following public attribute: Writer Objects -------------- -:class:`Writer` objects (:class:`DictWriter` instances and objects returned by +:class:`writer` objects (:class:`DictWriter` instances and objects returned by the :func:`writer` function) have the following public methods. A *row* must be -an iterable of strings or numbers for :class:`Writer` objects and a dictionary +an iterable of strings or numbers for :class:`writer` objects and a dictionary mapping fieldnames to strings or numbers (by passing them through :func:`str` first) for :class:`DictWriter` objects. Note that complex numbers are written out surrounded by parens. This may cause some problems for other programs which diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index bba4fe0d5f2425e..7eacb46d6299b3c 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -25,7 +25,6 @@ Doc/library/asyncio-policy.rst Doc/library/asyncio-subprocess.rst Doc/library/bdb.rst Doc/library/collections.rst -Doc/library/csv.rst Doc/library/dbm.rst Doc/library/decimal.rst Doc/library/email.charset.rst From 348a72ce3fda91de5e9ac1bf6b8c4387ec3007a5 Mon Sep 17 00:00:00 2001 From: Brandt Bucher <brandtbucher@microsoft.com> Date: Tue, 30 Jan 2024 14:08:53 -0800 Subject: [PATCH 042/507] GH-113464: Add aarch64-apple-darwin/clang to JIT CI (GH-114759) --- .github/workflows/jit.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml index e137fd21b0a0dd7..3da729191812551 100644 --- a/.github/workflows/jit.yml +++ b/.github/workflows/jit.yml @@ -16,6 +16,7 @@ jobs: - i686-pc-windows-msvc/msvc - x86_64-pc-windows-msvc/msvc - x86_64-apple-darwin/clang + - aarch64-apple-darwin/clang - x86_64-unknown-linux-gnu/gcc - x86_64-unknown-linux-gnu/clang - aarch64-unknown-linux-gnu/gcc @@ -36,9 +37,12 @@ jobs: compiler: msvc - target: x86_64-apple-darwin/clang architecture: x86_64 - runner: macos-latest + runner: macos-13 + compiler: clang + - target: aarch64-apple-darwin/clang + architecture: aarch64 + runner: macos-14 compiler: clang - exclude: test_embed - target: x86_64-unknown-linux-gnu/gcc architecture: x86_64 runner: ubuntu-latest @@ -80,7 +84,7 @@ jobs: brew install llvm@${{ matrix.llvm }} export SDKROOT="$(xcrun --show-sdk-path)" ./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations --with-lto' }} - make all --jobs 3 + make all --jobs 4 ./python.exe -m test --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3 - name: Native Linux @@ -91,6 +95,7 @@ jobs: ./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations --with-lto' }} make all --jobs 4 ./python -m test --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3 + - name: Emulated Linux if: runner.os == 'Linux' && matrix.architecture != 'x86_64' run: | From dc4cd2c9ba60e2ee7e534e2f6e93c4c135df23b9 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Wed, 31 Jan 2024 00:15:33 +0200 Subject: [PATCH 043/507] gh-106392: Fix inconsistency in deprecation warnings in datetime module (GH-114761) --- Lib/_pydatetime.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index 355145387e355bb..54c12d3b2f3f16a 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -1809,7 +1809,7 @@ def fromtimestamp(cls, timestamp, tz=None): def utcfromtimestamp(cls, t): """Construct a naive UTC datetime from a POSIX timestamp.""" import warnings - warnings.warn("datetime.utcfromtimestamp() is deprecated and scheduled " + warnings.warn("datetime.datetime.utcfromtimestamp() is deprecated and scheduled " "for removal in a future version. Use timezone-aware " "objects to represent datetimes in UTC: " "datetime.datetime.fromtimestamp(t, datetime.UTC).", @@ -1827,8 +1827,8 @@ def now(cls, tz=None): def utcnow(cls): "Construct a UTC datetime from time.time()." import warnings - warnings.warn("datetime.utcnow() is deprecated and scheduled for " - "removal in a future version. Instead, Use timezone-aware " + warnings.warn("datetime.datetime.utcnow() is deprecated and scheduled for " + "removal in a future version. Use timezone-aware " "objects to represent datetimes in UTC: " "datetime.datetime.now(datetime.UTC).", DeprecationWarning, From a06b606462740058b5d52fefdcdcd679d4f40260 Mon Sep 17 00:00:00 2001 From: Diego Russo <diego.russo@arm.com> Date: Tue, 30 Jan 2024 23:53:04 +0000 Subject: [PATCH 044/507] gh-110190: Fix ctypes structs with array on Windows ARM64 (GH-114753) --- .../next/Library/2024-01-30-15-34-08.gh-issue-110190.Z5PQQX.rst | 1 + Modules/_ctypes/stgdict.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-30-15-34-08.gh-issue-110190.Z5PQQX.rst diff --git a/Misc/NEWS.d/next/Library/2024-01-30-15-34-08.gh-issue-110190.Z5PQQX.rst b/Misc/NEWS.d/next/Library/2024-01-30-15-34-08.gh-issue-110190.Z5PQQX.rst new file mode 100644 index 000000000000000..af77e409963e04d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-30-15-34-08.gh-issue-110190.Z5PQQX.rst @@ -0,0 +1 @@ +Fix ctypes structs with array on Windows ARM64 platform by setting ``MAX_STRUCT_SIZE`` to 32 in stgdict. Patch by Diego Russo diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c index 2397015ba658895..deafa696fdd0d03 100644 --- a/Modules/_ctypes/stgdict.c +++ b/Modules/_ctypes/stgdict.c @@ -707,7 +707,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct /* * The value of MAX_STRUCT_SIZE depends on the platform Python is running on. */ -#if defined(__aarch64__) || defined(__arm__) +#if defined(__aarch64__) || defined(__arm__) || defined(_M_ARM64) # define MAX_STRUCT_SIZE 32 #elif defined(__powerpc64__) # define MAX_STRUCT_SIZE 64 From 1667c2868633a1091b3519594103ca7662d64d75 Mon Sep 17 00:00:00 2001 From: Barney Gale <barney.gale@gmail.com> Date: Wed, 31 Jan 2024 00:38:01 +0000 Subject: [PATCH 045/507] pathlib ABCs: raise `UnsupportedOperation` directly. (#114776) Raise `UnsupportedOperation` directly, rather than via an `_unsupported()` helper, to give human readers and IDEs/typecheckers/etc a bigger hint that these methods are abstract. --- Lib/pathlib/__init__.py | 5 ++-- Lib/pathlib/_abc.py | 59 ++++++++++++++++++++--------------------- 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index 4447f98e3e86897..65ce836765c42bb 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -514,9 +514,8 @@ class Path(_abc.PathBase, PurePath): as_uri = PurePath.as_uri @classmethod - def _unsupported(cls, method_name): - msg = f"{cls.__name__}.{method_name}() is unsupported on this system" - raise UnsupportedOperation(msg) + def _unsupported_msg(cls, attribute): + return f"{cls.__name__}.{attribute} is unsupported on this system" def __init__(self, *args, **kwargs): if kwargs: diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 580d631cbf3b530..85884bc4b4cb47b 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -149,39 +149,39 @@ class PathModuleBase: """ @classmethod - def _unsupported(cls, attr): - raise UnsupportedOperation(f"{cls.__name__}.{attr} is unsupported") + def _unsupported_msg(cls, attribute): + return f"{cls.__name__}.{attribute} is unsupported" @property def sep(self): """The character used to separate path components.""" - self._unsupported('sep') + raise UnsupportedOperation(self._unsupported_msg('sep')) def join(self, path, *paths): """Join path segments.""" - self._unsupported('join()') + raise UnsupportedOperation(self._unsupported_msg('join()')) def split(self, path): """Split the path into a pair (head, tail), where *head* is everything before the final path separator, and *tail* is everything after. Either part may be empty. """ - self._unsupported('split()') + raise UnsupportedOperation(self._unsupported_msg('split()')) def splitdrive(self, path): """Split the path into a 2-item tuple (drive, tail), where *drive* is a device name or mount point, and *tail* is everything after the drive. Either part may be empty.""" - self._unsupported('splitdrive()') + raise UnsupportedOperation(self._unsupported_msg('splitdrive()')) def normcase(self, path): """Normalize the case of the path.""" - self._unsupported('normcase()') + raise UnsupportedOperation(self._unsupported_msg('normcase()')) def isabs(self, path): """Returns whether the path is absolute, i.e. unaffected by the current directory or drive.""" - self._unsupported('isabs()') + raise UnsupportedOperation(self._unsupported_msg('isabs()')) class PurePathBase: @@ -505,16 +505,15 @@ class PathBase(PurePathBase): _max_symlinks = 40 @classmethod - def _unsupported(cls, method_name): - msg = f"{cls.__name__}.{method_name}() is unsupported" - raise UnsupportedOperation(msg) + def _unsupported_msg(cls, attribute): + return f"{cls.__name__}.{attribute} is unsupported" def stat(self, *, follow_symlinks=True): """ Return the result of the stat() system call on this path, like os.stat() does. """ - self._unsupported("stat") + raise UnsupportedOperation(self._unsupported_msg('stat()')) def lstat(self): """ @@ -703,7 +702,7 @@ def open(self, mode='r', buffering=-1, encoding=None, Open the file pointed by this path and return a file object, as the built-in open() function does. """ - self._unsupported("open") + raise UnsupportedOperation(self._unsupported_msg('open()')) def read_bytes(self): """ @@ -744,7 +743,7 @@ def iterdir(self): The children are yielded in arbitrary order, and the special entries '.' and '..' are not included. """ - self._unsupported("iterdir") + raise UnsupportedOperation(self._unsupported_msg('iterdir()')) def _scandir(self): # Emulate os.scandir(), which returns an object that can be used as a @@ -871,7 +870,7 @@ def absolute(self): Use resolve() to resolve symlinks and remove '..' segments. """ - self._unsupported("absolute") + raise UnsupportedOperation(self._unsupported_msg('absolute()')) @classmethod def cwd(cls): @@ -886,7 +885,7 @@ def expanduser(self): """ Return a new path with expanded ~ and ~user constructs (as returned by os.path.expanduser) """ - self._unsupported("expanduser") + raise UnsupportedOperation(self._unsupported_msg('expanduser()')) @classmethod def home(cls): @@ -898,7 +897,7 @@ def readlink(self): """ Return the path to which the symbolic link points. """ - self._unsupported("readlink") + raise UnsupportedOperation(self._unsupported_msg('readlink()')) readlink._supported = False def resolve(self, strict=False): @@ -973,7 +972,7 @@ def symlink_to(self, target, target_is_directory=False): Make this path a symlink pointing to the target path. Note the order of arguments (link, target) is the reverse of os.symlink. """ - self._unsupported("symlink_to") + raise UnsupportedOperation(self._unsupported_msg('symlink_to()')) def hardlink_to(self, target): """ @@ -981,19 +980,19 @@ def hardlink_to(self, target): Note the order of arguments (self, target) is the reverse of os.link's. """ - self._unsupported("hardlink_to") + raise UnsupportedOperation(self._unsupported_msg('hardlink_to()')) def touch(self, mode=0o666, exist_ok=True): """ Create this file with the given access mode, if it doesn't exist. """ - self._unsupported("touch") + raise UnsupportedOperation(self._unsupported_msg('touch()')) def mkdir(self, mode=0o777, parents=False, exist_ok=False): """ Create a new directory at this given path. """ - self._unsupported("mkdir") + raise UnsupportedOperation(self._unsupported_msg('mkdir()')) def rename(self, target): """ @@ -1005,7 +1004,7 @@ def rename(self, target): Returns the new Path instance pointing to the target path. """ - self._unsupported("rename") + raise UnsupportedOperation(self._unsupported_msg('rename()')) def replace(self, target): """ @@ -1017,13 +1016,13 @@ def replace(self, target): Returns the new Path instance pointing to the target path. """ - self._unsupported("replace") + raise UnsupportedOperation(self._unsupported_msg('replace()')) def chmod(self, mode, *, follow_symlinks=True): """ Change the permissions of the path, like os.chmod(). """ - self._unsupported("chmod") + raise UnsupportedOperation(self._unsupported_msg('chmod()')) def lchmod(self, mode): """ @@ -1037,31 +1036,31 @@ def unlink(self, missing_ok=False): Remove this file or link. If the path is a directory, use rmdir() instead. """ - self._unsupported("unlink") + raise UnsupportedOperation(self._unsupported_msg('unlink()')) def rmdir(self): """ Remove this directory. The directory must be empty. """ - self._unsupported("rmdir") + raise UnsupportedOperation(self._unsupported_msg('rmdir()')) def owner(self, *, follow_symlinks=True): """ Return the login name of the file owner. """ - self._unsupported("owner") + raise UnsupportedOperation(self._unsupported_msg('owner()')) def group(self, *, follow_symlinks=True): """ Return the group name of the file gid. """ - self._unsupported("group") + raise UnsupportedOperation(self._unsupported_msg('group()')) @classmethod def from_uri(cls, uri): """Return a new path from the given 'file' URI.""" - cls._unsupported("from_uri") + raise UnsupportedOperation(cls._unsupported_msg('from_uri()')) def as_uri(self): """Return the path as a URI.""" - self._unsupported("as_uri") + raise UnsupportedOperation(self._unsupported_msg('as_uri()')) From 574291963f6b0eb7da3fde1ae9763236e7ece306 Mon Sep 17 00:00:00 2001 From: Barney Gale <barney.gale@gmail.com> Date: Wed, 31 Jan 2024 00:59:33 +0000 Subject: [PATCH 046/507] pathlib ABCs: drop partial, broken, untested support for `bytes` paths. (#114777) Methods like `full_match()`, `glob()`, etc, are difficult to make work with byte paths, and it's not worth the effort. This patch makes `PurePathBase` raise `TypeError` when given non-`str` path segments. --- Lib/pathlib/_abc.py | 7 +++---- Lib/test/test_pathlib/test_pathlib.py | 18 +--------------- Lib/test/test_pathlib/test_pathlib_abc.py | 25 +++++++++++++++++++++++ 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 85884bc4b4cb47b..91f5cd6c01e9d08 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -207,6 +207,9 @@ class PurePathBase: def __init__(self, path, *paths): self._raw_path = self.pathmod.join(path, *paths) if paths else path + if not isinstance(self._raw_path, str): + raise TypeError( + f"path should be a str, not {type(self._raw_path).__name__!r}") self._resolving = False def with_segments(self, *pathsegments): @@ -321,8 +324,6 @@ def relative_to(self, other, *, walk_up=False): other = self.with_segments(other) anchor0, parts0 = self._stack anchor1, parts1 = other._stack - if isinstance(anchor0, str) != isinstance(anchor1, str): - raise TypeError(f"{self._raw_path!r} and {other._raw_path!r} have different types") if anchor0 != anchor1: raise ValueError(f"{self._raw_path!r} and {other._raw_path!r} have different anchors") while parts0 and parts1 and parts0[-1] == parts1[-1]: @@ -346,8 +347,6 @@ def is_relative_to(self, other): other = self.with_segments(other) anchor0, parts0 = self._stack anchor1, parts1 = other._stack - if isinstance(anchor0, str) != isinstance(anchor1, str): - raise TypeError(f"{self._raw_path!r} and {other._raw_path!r} have different types") if anchor0 != anchor1: return False while parts0 and parts1 and parts0[-1] == parts1[-1]: diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index 3bee9b8c762b805..2b166451243775b 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -189,7 +189,7 @@ def test_fspath_common(self): self._check_str(p.__fspath__(), ('a/b',)) self._check_str(os.fspath(p), ('a/b',)) - def test_bytes(self): + def test_bytes_exc_message(self): P = self.cls message = (r"argument should be a str or an os\.PathLike object " r"where __fspath__ returns a str, not 'bytes'") @@ -199,22 +199,6 @@ def test_bytes(self): P(b'a', 'b') with self.assertRaisesRegex(TypeError, message): P('a', b'b') - with self.assertRaises(TypeError): - P('a').joinpath(b'b') - with self.assertRaises(TypeError): - P('a') / b'b' - with self.assertRaises(TypeError): - b'a' / P('b') - with self.assertRaises(TypeError): - P('a').match(b'b') - with self.assertRaises(TypeError): - P('a').relative_to(b'b') - with self.assertRaises(TypeError): - P('a').with_name(b'b') - with self.assertRaises(TypeError): - P('a').with_stem(b'b') - with self.assertRaises(TypeError): - P('a').with_suffix(b'b') def test_as_bytes_common(self): sep = os.fsencode(self.sep) diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 0e12182c162c142..207579ccbf443b7 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -155,6 +155,31 @@ def test_constructor_common(self): P('a/b/c') P('/a/b/c') + def test_bytes(self): + P = self.cls + with self.assertRaises(TypeError): + P(b'a') + with self.assertRaises(TypeError): + P(b'a', 'b') + with self.assertRaises(TypeError): + P('a', b'b') + with self.assertRaises(TypeError): + P('a').joinpath(b'b') + with self.assertRaises(TypeError): + P('a') / b'b' + with self.assertRaises(TypeError): + b'a' / P('b') + with self.assertRaises(TypeError): + P('a').match(b'b') + with self.assertRaises(TypeError): + P('a').relative_to(b'b') + with self.assertRaises(TypeError): + P('a').with_name(b'b') + with self.assertRaises(TypeError): + P('a').with_stem(b'b') + with self.assertRaises(TypeError): + P('a').with_suffix(b'b') + def _check_str_subclass(self, *args): # Issue #21127: it should be possible to construct a PurePath object # from a str subclass instance, and it then gets converted to From 2ed8f924ee05ec17ce0639a424e5ca7661b09a6b Mon Sep 17 00:00:00 2001 From: Brett Cannon <brett@python.org> Date: Tue, 30 Jan 2024 17:49:27 -0800 Subject: [PATCH 047/507] GH-114743: Set a low recursion limit for `test_main_recursion_error()` in `test_runpy` (GH-114772) This can fail under a debug build of WASI when directly executing test.test_runpy. --- .gitignore | 2 +- Lib/test/test_runpy.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 18eb2a9f0632ce2..6ed7197e3ab6269 100644 --- a/.gitignore +++ b/.gitignore @@ -159,5 +159,5 @@ Python/frozen_modules/MANIFEST /python !/Python/ -# main branch only: ABI files are not checked/maintained +# main branch only: ABI files are not checked/maintained. Doc/data/python*.abi diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py index 57fe859e366b5b7..9d76764c75be3eb 100644 --- a/Lib/test/test_runpy.py +++ b/Lib/test/test_runpy.py @@ -12,7 +12,8 @@ import textwrap import unittest import warnings -from test.support import no_tracing, verbose, requires_subprocess, requires_resource +from test.support import (infinite_recursion, no_tracing, verbose, + requires_subprocess, requires_resource) from test.support.import_helper import forget, make_legacy_pyc, unload from test.support.os_helper import create_empty_file, temp_dir from test.support.script_helper import make_script, make_zip_script @@ -743,7 +744,8 @@ def test_main_recursion_error(self): "runpy.run_path(%r)\n") % dummy_dir script_name = self._make_test_script(script_dir, mod_name, source) zip_name, fname = make_zip_script(script_dir, 'test_zip', script_name) - self.assertRaises(RecursionError, run_path, zip_name) + with infinite_recursion(25): + self.assertRaises(RecursionError, run_path, zip_name) def test_encoding(self): with temp_dir() as script_dir: From c8cf5d7d148944f2850f25b02334400dd0238cb0 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Wed, 31 Jan 2024 07:59:34 +0100 Subject: [PATCH 048/507] Docs: mark up dbm.gnu.open() and dbm.ndbm.open() using param list (#114762) --- Doc/library/dbm.rst | 117 +++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 61 deletions(-) diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index 076e86143d06a6f..9bb5e5f89509568 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -52,6 +52,10 @@ the Oracle Berkeley DB. .. |flag_n| replace:: Always create a new, empty database, open for reading and writing. +.. |mode_param_doc| replace:: + The Unix file access mode of the file (default: octal ``0o666``), + used only when the database has to be created. + .. |incompat_note| replace:: The file formats created by :mod:`dbm.gnu` and :mod:`dbm.ndbm` are incompatible and can not be used interchangeably. @@ -69,14 +73,13 @@ the Oracle Berkeley DB. :type file: :term:`path-like object` :param str flag: - * ``'r'`` (default), |flag_r| - * ``'w'``, |flag_w| - * ``'c'``, |flag_c| - * ``'n'``, |flag_n| + * ``'r'`` (default): |flag_r| + * ``'w'``: |flag_w| + * ``'c'``: |flag_c| + * ``'n'``: |flag_n| :param int mode: - The Unix file access mode of the file (default: octal ``0o666``), - used only when the database has to be created. + |mode_param_doc| .. versionchanged:: 3.11 *file* accepts a :term:`path-like object`. @@ -171,47 +174,45 @@ and the :meth:`!items` and :meth:`!values` methods are not supported. .. function:: open(filename, flag="r", mode=0o666, /) - Open a GDBM database and return a :class:`!gdbm` object. The *filename* - argument is the name of the database file. - - The optional *flag* argument can be: + Open a GDBM database and return a :class:`!gdbm` object. - .. csv-table:: - :header: "Value", "Meaning" + :param filename: + The database file to open. + :type filename: :term:`path-like object` - ``'r'`` (default), |flag_r| - ``'w'``, |flag_w| - ``'c'``, |flag_c| - ``'n'``, |flag_n| + :param str flag: + * ``'r'`` (default): |flag_r| + * ``'w'``: |flag_w| + * ``'c'``: |flag_c| + * ``'n'``: |flag_n| - The following additional characters may be appended to the flag to control - how the database is opened: + The following additional characters may be appended + to control how the database is opened: - +---------+--------------------------------------------+ - | Value | Meaning | - +=========+============================================+ - | ``'f'`` | Open the database in fast mode. Writes | - | | to the database will not be synchronized. | - +---------+--------------------------------------------+ - | ``'s'`` | Synchronized mode. This will cause changes | - | | to the database to be immediately written | - | | to the file. | - +---------+--------------------------------------------+ - | ``'u'`` | Do not lock database. | - +---------+--------------------------------------------+ + * ``'f'``: Open the database in fast mode. + Writes to the database will not be synchronized. + * ``'s'``: Synchronized mode. + Changes to the database will be written immediately to the file. + * ``'u'``: Do not lock database. - Not all flags are valid for all versions of GDBM. The module constant - :const:`open_flags` is a string of supported flag characters. The exception - :exc:`error` is raised if an invalid flag is specified. + Not all flags are valid for all versions of GDBM. + See the :data:`open_flags` member for a list of supported flag characters. - The optional *mode* argument is the Unix mode of the file, used only when the - database has to be created. It defaults to octal ``0o666``. + :param int mode: + |mode_param_doc| - In addition to the dictionary-like methods, :class:`gdbm` objects have the - following methods: + :raises error: + If an invalid *flag* argument is passed. .. versionchanged:: 3.11 - Accepts :term:`path-like object` for filename. + *filename* accepts a :term:`path-like object`. + + .. data:: open_flags + + A string of characters the *flag* parameter of :meth:`~dbm.gnu.open` supports. + + In addition to the dictionary-like methods, :class:`gdbm` objects have the + following methods and attributes: .. method:: gdbm.firstkey() @@ -298,22 +299,20 @@ This module can be used with the "classic" NDBM interface or the .. function:: open(filename, flag="r", mode=0o666, /) Open an NDBM database and return an :class:`!ndbm` object. - The *filename* argument is the name of the database file - (without the :file:`.dir` or :file:`.pag` extensions). - - The optional *flag* argument must be one of these values: - .. csv-table:: - :header: "Value", "Meaning" + :param filename: + The basename of the database file + (without the :file:`.dir` or :file:`.pag` extensions). + :type filename: :term:`path-like object` - ``'r'`` (default), |flag_r| - ``'w'``, |flag_w| - ``'c'``, |flag_c| - ``'n'``, |flag_n| + :param str flag: + * ``'r'`` (default): |flag_r| + * ``'w'``: |flag_w| + * ``'c'``: |flag_c| + * ``'n'``: |flag_n| - The optional *mode* argument is the Unix mode of the file, used only when the - database has to be created. It defaults to octal ``0o666`` (and will be - modified by the prevailing umask). + :param int mode: + |mode_param_doc| In addition to the dictionary-like methods, :class:`!ndbm` objects provide the following method: @@ -382,17 +381,13 @@ The :mod:`!dbm.dumb` module defines the following: :type database: :term:`path-like object` :param str flag: - .. csv-table:: - :header: "Value", "Meaning" - - ``'r'``, |flag_r| - ``'w'``, |flag_w| - ``'c'`` (default), |flag_c| - ``'n'``, |flag_n| + * ``'r'``: |flag_r| + * ``'w'``: |flag_w| + * ``'c'`` (default): |flag_c| + * ``'n'``: |flag_n| :param int mode: - The Unix file access mode of the file (default: ``0o666``), - used only when the database has to be created. + |mode_param_doc| .. warning:: It is possible to crash the Python interpreter when loading a database @@ -400,7 +395,7 @@ The :mod:`!dbm.dumb` module defines the following: Python's AST compiler. .. versionchanged:: 3.5 - :func:`open` always creates a new database when *flag* is ``'n'``. + :func:`~dbm.dumb.open` always creates a new database when *flag* is ``'n'``. .. versionchanged:: 3.8 A database opened read-only if *flag* is ``'r'``. From 5e390a0fc825f21952beb158e2bda3c5e007fac9 Mon Sep 17 00:00:00 2001 From: Daniel Hollas <danekhollas@gmail.com> Date: Wed, 31 Jan 2024 09:29:44 +0000 Subject: [PATCH 049/507] gh-109653: Speedup import of threading module (#114509) Avoiding an import of functools leads to 50% speedup of import time. Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> --- Lib/threading.py | 4 +--- .../Library/2024-01-23-23-13-47.gh-issue-109653.KLBHmT.rst | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-23-23-13-47.gh-issue-109653.KLBHmT.rst diff --git a/Lib/threading.py b/Lib/threading.py index 00b95f8d92a1f06..75a08e5aac97d60 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -3,7 +3,6 @@ import os as _os import sys as _sys import _thread -import functools import warnings from time import monotonic as _time @@ -1630,8 +1629,7 @@ def _register_atexit(func, *arg, **kwargs): if _SHUTTING_DOWN: raise RuntimeError("can't register atexit after shutdown") - call = functools.partial(func, *arg, **kwargs) - _threading_atexits.append(call) + _threading_atexits.append(lambda: func(*arg, **kwargs)) from _thread import stack_size diff --git a/Misc/NEWS.d/next/Library/2024-01-23-23-13-47.gh-issue-109653.KLBHmT.rst b/Misc/NEWS.d/next/Library/2024-01-23-23-13-47.gh-issue-109653.KLBHmT.rst new file mode 100644 index 000000000000000..76074df9c76fa61 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-23-23-13-47.gh-issue-109653.KLBHmT.rst @@ -0,0 +1 @@ +Reduce the import time of :mod:`threading` module by ~50%. Patch by Daniel Hollas. From 7a93db44257c0404dc407ff2ddc997f4bb8890ed Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Wed, 31 Jan 2024 03:33:10 -0600 Subject: [PATCH 050/507] gh-101100: Fix class reference in library/test.rst (GH-114769) The text clearly seems to be referencing `TestFuncAcceptsSequencesMixin`, for which no target is available. Name the class properly and suppress the dangling reference. --- Doc/library/test.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/test.rst b/Doc/library/test.rst index 9173db07fd00718..cad1023021a5129 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -143,7 +143,7 @@ guidelines to be followed: arg = (1, 2, 3) When using this pattern, remember that all classes that inherit from - :class:`unittest.TestCase` are run as tests. The :class:`Mixin` class in the example above + :class:`unittest.TestCase` are run as tests. The :class:`!TestFuncAcceptsSequencesMixin` class in the example above does not have any data and so can't be run by itself, thus it does not inherit from :class:`unittest.TestCase`. From b7688ef71eddcaf14f71b1c22ff2f164f34b2c74 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Wed, 31 Jan 2024 13:11:35 +0200 Subject: [PATCH 051/507] gh-114685: Check flags in PyObject_GetBuffer() (GH-114707) PyObject_GetBuffer() now raises a SystemError if called with PyBUF_READ or PyBUF_WRITE as flags. These flags should only be used with the PyMemoryView_* C API. --- Lib/test/test_buffer.py | 6 ++++++ .../C API/2024-01-29-12-13-24.gh-issue-114685.B07RME.rst | 3 +++ Modules/_testcapi/buffer.c | 6 ++++-- Objects/abstract.c | 6 ++++++ 4 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2024-01-29-12-13-24.gh-issue-114685.B07RME.rst diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py index 72a06d6af450e32..535b795f508a242 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -4585,6 +4585,12 @@ def test_c_buffer(self): buf.__release_buffer__(mv) self.assertEqual(buf.references, 0) + @unittest.skipIf(_testcapi is None, "requires _testcapi") + def test_c_buffer_invalid_flags(self): + buf = _testcapi.testBuf() + self.assertRaises(SystemError, buf.__buffer__, PyBUF_READ) + self.assertRaises(SystemError, buf.__buffer__, PyBUF_WRITE) + def test_inheritance(self): class A(bytearray): def __buffer__(self, flags): diff --git a/Misc/NEWS.d/next/C API/2024-01-29-12-13-24.gh-issue-114685.B07RME.rst b/Misc/NEWS.d/next/C API/2024-01-29-12-13-24.gh-issue-114685.B07RME.rst new file mode 100644 index 000000000000000..55b02d1d8e1e9fc --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-01-29-12-13-24.gh-issue-114685.B07RME.rst @@ -0,0 +1,3 @@ +:c:func:`PyObject_GetBuffer` now raises a :exc:`SystemError` if called with +:c:macro:`PyBUF_READ` or :c:macro:`PyBUF_WRITE` as flags. These flags should +only be used with the ``PyMemoryView_*`` C API. diff --git a/Modules/_testcapi/buffer.c b/Modules/_testcapi/buffer.c index 942774156c6c47f..7e2f6e5e29482c2 100644 --- a/Modules/_testcapi/buffer.c +++ b/Modules/_testcapi/buffer.c @@ -54,8 +54,10 @@ static int testbuf_getbuf(testBufObject *self, Py_buffer *view, int flags) { int buf = PyObject_GetBuffer(self->obj, view, flags); - Py_SETREF(view->obj, Py_NewRef(self)); - self->references++; + if (buf == 0) { + Py_SETREF(view->obj, Py_NewRef(self)); + self->references++; + } return buf; } diff --git a/Objects/abstract.c b/Objects/abstract.c index 1ec5c5b8c3dc2f5..daf04eb4ab2cda3 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -425,6 +425,12 @@ PyObject_AsWriteBuffer(PyObject *obj, int PyObject_GetBuffer(PyObject *obj, Py_buffer *view, int flags) { + if (flags != PyBUF_SIMPLE) { /* fast path */ + if (flags == PyBUF_READ || flags == PyBUF_WRITE) { + PyErr_BadInternalCall(); + return -1; + } + } PyBufferProcs *pb = Py_TYPE(obj)->tp_as_buffer; if (pb == NULL || pb->bf_getbuffer == NULL) { From 66f95ea6a65deff547cab0d312b8c8c8a4cf8beb Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Wed, 31 Jan 2024 06:22:24 -0500 Subject: [PATCH 052/507] gh-114737: Revert change to ElementTree.iterparse "root" attribute (GH-114755) Prior to gh-114269, the iterator returned by ElementTree.iterparse was initialized with the root attribute as None. This restores the previous behavior. --- Lib/test/test_xml_etree.py | 2 ++ Lib/xml/etree/ElementTree.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index b9e7937b0bbc00a..221545b315fa44d 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -536,7 +536,9 @@ def test_iterparse(self): iterparse = ET.iterparse context = iterparse(SIMPLE_XMLFILE) + self.assertIsNone(context.root) action, elem = next(context) + self.assertIsNone(context.root) self.assertEqual((action, elem.tag), ('end', 'element')) self.assertEqual([(action, elem.tag) for action, elem in context], [ ('end', 'element'), diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index ae6575028be11cb..bb7362d1634a726 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -1256,8 +1256,8 @@ def __del__(self): source.close() it = IterParseIterator() + it.root = None wr = weakref.ref(it) - del IterParseIterator return it From 25ce7f872df661de9392122df17111c75c77dee0 Mon Sep 17 00:00:00 2001 From: Alex Waygood <Alex.Waygood@Gmail.com> Date: Wed, 31 Jan 2024 11:28:23 +0000 Subject: [PATCH 053/507] Remove Alex Waygood as an Argument Clinic CODEOWNER (#114796) --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f4d0411504a8325..7933d3195505767 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -230,8 +230,8 @@ Doc/c-api/stable.rst @encukou **/*zipfile/_path/* @jaraco # Argument Clinic -/Tools/clinic/** @erlend-aasland @AlexWaygood -/Lib/test/test_clinic.py @erlend-aasland @AlexWaygood +/Tools/clinic/** @erlend-aasland +/Lib/test/test_clinic.py @erlend-aasland Doc/howto/clinic.rst @erlend-aasland # Subinterpreters From 1c2ea8b33c6b1f995db0aca0b223a9cc22426708 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Wed, 31 Jan 2024 14:32:27 +0300 Subject: [PATCH 054/507] gh-114790: Do not execute `workflows/require-pr-label.yml` on forks (#114791) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- .github/workflows/require-pr-label.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/require-pr-label.yml b/.github/workflows/require-pr-label.yml index 080204bcfd3b94f..ff5cbdf3eda749a 100644 --- a/.github/workflows/require-pr-label.yml +++ b/.github/workflows/require-pr-label.yml @@ -11,6 +11,7 @@ permissions: jobs: label: name: DO-NOT-MERGE / unresolved review + if: github.repository_owner == 'python' runs-on: ubuntu-latest timeout-minutes: 10 From 765b9ce9fb357bdb79a50ce51207c827fbd13dd8 Mon Sep 17 00:00:00 2001 From: Tian Gao <gaogaotiantian@hotmail.com> Date: Wed, 31 Jan 2024 05:03:05 -0800 Subject: [PATCH 055/507] gh-59013: Set breakpoint on the first executable line of function when using `break func` in pdb (#112470) --- Lib/pdb.py | 51 ++++++++++++------- Lib/test/test_pdb.py | 31 +++++++++-- ...3-11-27-19-54-43.gh-issue-59013.chpQ0e.rst | 1 + 3 files changed, 61 insertions(+), 22 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-27-19-54-43.gh-issue-59013.chpQ0e.rst diff --git a/Lib/pdb.py b/Lib/pdb.py index 6f7719eb9ba6c5b..0754e8b628cf570 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -97,17 +97,47 @@ class Restart(Exception): __all__ = ["run", "pm", "Pdb", "runeval", "runctx", "runcall", "set_trace", "post_mortem", "help"] + +def find_first_executable_line(code): + """ Try to find the first executable line of the code object. + + Equivalently, find the line number of the instruction that's + after RESUME + + Return code.co_firstlineno if no executable line is found. + """ + prev = None + for instr in dis.get_instructions(code): + if prev is not None and prev.opname == 'RESUME': + if instr.positions.lineno is not None: + return instr.positions.lineno + return code.co_firstlineno + prev = instr + return code.co_firstlineno + def find_function(funcname, filename): cre = re.compile(r'def\s+%s\s*[(]' % re.escape(funcname)) try: fp = tokenize.open(filename) except OSError: return None + funcdef = "" + funcstart = None # consumer of this info expects the first line to be 1 with fp: for lineno, line in enumerate(fp, start=1): if cre.match(line): - return funcname, filename, lineno + funcstart, funcdef = lineno, line + elif funcdef: + funcdef += line + + if funcdef: + try: + funccode = compile(funcdef, filename, 'exec').co_consts[0] + except SyntaxError: + continue + lineno_offset = find_first_executable_line(funccode) + return funcname, filename, funcstart + lineno_offset - 1 return None def lasti2lineno(code, lasti): @@ -975,7 +1005,7 @@ def do_break(self, arg, temporary = 0): #use co_name to identify the bkpt (function names #could be aliased, but co_name is invariant) funcname = code.co_name - lineno = self._find_first_executable_line(code) + lineno = find_first_executable_line(code) filename = code.co_filename except: # last thing to try @@ -1078,23 +1108,6 @@ def checkline(self, filename, lineno): return 0 return lineno - def _find_first_executable_line(self, code): - """ Try to find the first executable line of the code object. - - Equivalently, find the line number of the instruction that's - after RESUME - - Return code.co_firstlineno if no executable line is found. - """ - prev = None - for instr in dis.get_instructions(code): - if prev is not None and prev.opname == 'RESUME': - if instr.positions.lineno is not None: - return instr.positions.lineno - return code.co_firstlineno - prev = instr - return code.co_firstlineno - def do_enable(self, arg): """enable bpnumber [bpnumber ...] diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index c64df62c761471d..b2283cff6cb4622 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -2661,7 +2661,7 @@ def quux(): pass """.encode(), 'bœr', - ('bœr', 4), + ('bœr', 5), ) def test_find_function_found_with_encoding_cookie(self): @@ -2678,7 +2678,7 @@ def quux(): pass """.encode('iso-8859-15'), 'bœr', - ('bœr', 5), + ('bœr', 6), ) def test_find_function_found_with_bom(self): @@ -2688,9 +2688,34 @@ def bœr(): pass """.encode(), 'bœr', - ('bœr', 1), + ('bœr', 2), ) + def test_find_function_first_executable_line(self): + code = textwrap.dedent("""\ + def foo(): pass + + def bar(): + pass # line 4 + + def baz(): + # comment + pass # line 8 + + def mul(): + # code on multiple lines + code = compile( # line 12 + 'def f()', + '<string>', + 'exec', + ) + """).encode() + + self._assert_find_function(code, 'foo', ('foo', 1)) + self._assert_find_function(code, 'bar', ('bar', 4)) + self._assert_find_function(code, 'baz', ('baz', 8)) + self._assert_find_function(code, 'mul', ('mul', 12)) + def test_issue7964(self): # open the file as binary so we can force \r\n newline with open(os_helper.TESTFN, 'wb') as f: diff --git a/Misc/NEWS.d/next/Library/2023-11-27-19-54-43.gh-issue-59013.chpQ0e.rst b/Misc/NEWS.d/next/Library/2023-11-27-19-54-43.gh-issue-59013.chpQ0e.rst new file mode 100644 index 000000000000000..a2be2fb8eacf17c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-27-19-54-43.gh-issue-59013.chpQ0e.rst @@ -0,0 +1 @@ +Set breakpoint on the first executable line of the function, instead of the line of function definition when the user do ``break func`` using :mod:`pdb` From b905fad83819ec9102ecfb97e3d8ab0aaddd9784 Mon Sep 17 00:00:00 2001 From: Nachtalb <9467802+Nachtalb@users.noreply.github.com> Date: Wed, 31 Jan 2024 16:33:46 +0100 Subject: [PATCH 056/507] gh-111741: Recognise image/webp as a standard format in the mimetypes module (GH-111742) Previously it was supported as a non-standard type. --- Lib/mimetypes.py | 2 +- Lib/test/test_mimetypes.py | 3 +-- .../Library/2023-11-04-22-32-27.gh-issue-111741.f1ufr8.rst | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-04-22-32-27.gh-issue-111741.f1ufr8.rst diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index 37228de4828de59..51b99701c9d727b 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py @@ -528,6 +528,7 @@ def _default_mime_types(): '.tiff' : 'image/tiff', '.tif' : 'image/tiff', '.ico' : 'image/vnd.microsoft.icon', + '.webp' : 'image/webp', '.ras' : 'image/x-cmu-raster', '.pnm' : 'image/x-portable-anymap', '.pbm' : 'image/x-portable-bitmap', @@ -587,7 +588,6 @@ def _default_mime_types(): '.pict': 'image/pict', '.pct' : 'image/pict', '.pic' : 'image/pict', - '.webp': 'image/webp', '.xul' : 'text/xul', } diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index d64aee71fc48b13..01bba0ac2eed5a4 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -96,14 +96,12 @@ def test_non_standard_types(self): # First try strict eq(self.db.guess_type('foo.xul', strict=True), (None, None)) eq(self.db.guess_extension('image/jpg', strict=True), None) - eq(self.db.guess_extension('image/webp', strict=True), None) # And then non-strict eq(self.db.guess_type('foo.xul', strict=False), ('text/xul', None)) eq(self.db.guess_type('foo.XUL', strict=False), ('text/xul', None)) eq(self.db.guess_type('foo.invalid', strict=False), (None, None)) eq(self.db.guess_extension('image/jpg', strict=False), '.jpg') eq(self.db.guess_extension('image/JPG', strict=False), '.jpg') - eq(self.db.guess_extension('image/webp', strict=False), '.webp') def test_filename_with_url_delimiters(self): # bpo-38449: URL delimiters cases should be handled also. @@ -183,6 +181,7 @@ def check_extensions(): self.assertEqual(mimetypes.guess_extension('application/xml'), '.xsl') self.assertEqual(mimetypes.guess_extension('audio/mpeg'), '.mp3') self.assertEqual(mimetypes.guess_extension('image/avif'), '.avif') + self.assertEqual(mimetypes.guess_extension('image/webp'), '.webp') self.assertEqual(mimetypes.guess_extension('image/jpeg'), '.jpg') self.assertEqual(mimetypes.guess_extension('image/tiff'), '.tiff') self.assertEqual(mimetypes.guess_extension('message/rfc822'), '.eml') diff --git a/Misc/NEWS.d/next/Library/2023-11-04-22-32-27.gh-issue-111741.f1ufr8.rst b/Misc/NEWS.d/next/Library/2023-11-04-22-32-27.gh-issue-111741.f1ufr8.rst new file mode 100644 index 000000000000000..e43f93a270ce9c8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-04-22-32-27.gh-issue-111741.f1ufr8.rst @@ -0,0 +1 @@ +Recognise ``image/webp`` as a standard format in the :mod:`mimetypes` module. From 78c254582b1757c15098ae65e97a2589ae663cd7 Mon Sep 17 00:00:00 2001 From: Albert Zeyer <albzey@gmail.com> Date: Wed, 31 Jan 2024 20:14:44 +0100 Subject: [PATCH 057/507] gh-113939: Frame clear, clear locals (#113940) --- Lib/test/test_frame.py | 22 +++++++++++++++++++ ...-01-12-16-40-07.gh-issue-113939.Yi3L-e.rst | 4 ++++ Objects/frameobject.c | 1 + 3 files changed, 27 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-12-16-40-07.gh-issue-113939.Yi3L-e.rst diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py index 7f17666a8d9697b..244ce8af7cdf088 100644 --- a/Lib/test/test_frame.py +++ b/Lib/test/test_frame.py @@ -55,6 +55,28 @@ class C: # The reference was released by .clear() self.assertIs(None, wr()) + def test_clear_locals_after_f_locals_access(self): + # see gh-113939 + class C: + pass + + wr = None + def inner(): + nonlocal wr + c = C() + wr = weakref.ref(c) + 1/0 + + try: + inner() + except ZeroDivisionError as exc: + support.gc_collect() + self.assertIsNotNone(wr()) + print(exc.__traceback__.tb_next.tb_frame.f_locals) + exc.__traceback__.tb_next.tb_frame.clear() + support.gc_collect() + self.assertIsNone(wr()) + def test_clear_does_not_clear_specials(self): class C: pass diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-12-16-40-07.gh-issue-113939.Yi3L-e.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-12-16-40-07.gh-issue-113939.Yi3L-e.rst new file mode 100644 index 000000000000000..28b8e4bdda6be4e --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-12-16-40-07.gh-issue-113939.Yi3L-e.rst @@ -0,0 +1,4 @@ +frame.clear(): +Clear frame.f_locals as well, and not only the fast locals. +This is relevant once frame.f_locals was accessed, +which would contain also references to all the locals. diff --git a/Objects/frameobject.c b/Objects/frameobject.c index cafe4ef6141d9af..a914c61aac2fd51 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -926,6 +926,7 @@ frame_tp_clear(PyFrameObject *f) Py_CLEAR(locals[i]); } f->f_frame->stacktop = 0; + Py_CLEAR(f->f_frame->f_locals); return 0; } From b25b7462d520f38049d25888f220f20f759bc077 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Wed, 31 Jan 2024 22:51:18 +0300 Subject: [PATCH 058/507] gh-114788: Do not run JIT workflow on unrelated changes (#114789) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- .github/workflows/jit.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml index 3da729191812551..22e0bdba53ffd6f 100644 --- a/.github/workflows/jit.yml +++ b/.github/workflows/jit.yml @@ -1,10 +1,19 @@ name: JIT on: pull_request: - paths: '**jit**' + paths: + - '**jit**' + - 'Python/bytecodes.c' push: - paths: '**jit**' + paths: + - '**jit**' + - 'Python/bytecodes.c' workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: jit: name: ${{ matrix.target }} (${{ matrix.debug && 'Debug' || 'Release' }}) From 1836f674c0d86ec3375189a550c8f4a52ff89ae8 Mon Sep 17 00:00:00 2001 From: Bradley Reynolds <bradley.reynolds@darbia.dev> Date: Wed, 31 Jan 2024 15:33:28 -0600 Subject: [PATCH 059/507] Add note to `sys.orig_argv` clarifying the difference from `sys.argv` (#114630) Co-authored-by: Ned Batchelder <ned@nedbatchelder.com> --- Doc/library/sys.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index abf2c393a44928d..a97a369b77b88a8 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -1293,7 +1293,10 @@ always available. The list of the original command line arguments passed to the Python executable. - See also :data:`sys.argv`. + The elements of :data:`sys.orig_argv` are the arguments to the Python interpreter, + while the elements of :data:`sys.argv` are the arguments to the user's program. + Arguments consumed by the interpreter itself will be present in :data:`sys.orig_argv` + and missing from :data:`sys.argv`. .. versionadded:: 3.10 From 7b9d406729e7e7adc482b5b8c920de1874c234d0 Mon Sep 17 00:00:00 2001 From: Donghee Na <donghee.na@python.org> Date: Thu, 1 Feb 2024 08:58:08 +0900 Subject: [PATCH 060/507] gh-112087: Make PyList_{Append,Size,GetSlice} to be thread-safe (gh-114651) --- Include/internal/pycore_list.h | 3 ++- Include/object.h | 4 ++++ Objects/listobject.c | 22 +++++++++++++++------- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Include/internal/pycore_list.h b/Include/internal/pycore_list.h index 6c29d882335512e..4536f90e4144939 100644 --- a/Include/internal/pycore_list.h +++ b/Include/internal/pycore_list.h @@ -24,12 +24,13 @@ extern void _PyList_Fini(_PyFreeListState *); extern int _PyList_AppendTakeRefListResize(PyListObject *self, PyObject *newitem); +// In free-threaded build: self should be locked by the caller, if it should be thread-safe. static inline int _PyList_AppendTakeRef(PyListObject *self, PyObject *newitem) { assert(self != NULL && newitem != NULL); assert(PyList_Check(self)); - Py_ssize_t len = PyList_GET_SIZE(self); + Py_ssize_t len = Py_SIZE(self); Py_ssize_t allocated = self->allocated; assert((size_t)len + 1 < PY_SSIZE_T_MAX); if (allocated > len) { diff --git a/Include/object.h b/Include/object.h index ef3fb721c2b0122..568d315d7606c42 100644 --- a/Include/object.h +++ b/Include/object.h @@ -428,7 +428,11 @@ static inline void Py_SET_TYPE(PyObject *ob, PyTypeObject *type) { static inline void Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size) { assert(ob->ob_base.ob_type != &PyLong_Type); assert(ob->ob_base.ob_type != &PyBool_Type); +#ifdef Py_GIL_DISABLED + _Py_atomic_store_ssize_relaxed(&ob->ob_size, size); +#else ob->ob_size = size; +#endif } #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000 # define Py_SET_SIZE(ob, size) Py_SET_SIZE(_PyVarObject_CAST(ob), (size)) diff --git a/Objects/listobject.c b/Objects/listobject.c index 56785e5f37a450f..80a1f1da55b8bc8 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -30,7 +30,6 @@ get_list_state(void) } #endif - /* Ensure ob_item has room for at least newsize elements, and set * ob_size to newsize. If newsize > ob_size on entry, the content * of the new slots at exit is undefined heap trash; it's the caller's @@ -221,8 +220,9 @@ PyList_Size(PyObject *op) PyErr_BadInternalCall(); return -1; } - else - return Py_SIZE(op); + else { + return PyList_GET_SIZE(op); + } } static inline int @@ -328,7 +328,7 @@ PyList_Insert(PyObject *op, Py_ssize_t where, PyObject *newitem) int _PyList_AppendTakeRefListResize(PyListObject *self, PyObject *newitem) { - Py_ssize_t len = PyList_GET_SIZE(self); + Py_ssize_t len = Py_SIZE(self); assert(self->allocated == -1 || self->allocated == len); if (list_resize(self, len + 1) < 0) { Py_DECREF(newitem); @@ -342,7 +342,11 @@ int PyList_Append(PyObject *op, PyObject *newitem) { if (PyList_Check(op) && (newitem != NULL)) { - return _PyList_AppendTakeRef((PyListObject *)op, Py_NewRef(newitem)); + int ret; + Py_BEGIN_CRITICAL_SECTION(op); + ret = _PyList_AppendTakeRef((PyListObject *)op, Py_NewRef(newitem)); + Py_END_CRITICAL_SECTION(); + return ret; } PyErr_BadInternalCall(); return -1; @@ -473,7 +477,7 @@ static PyObject * list_item(PyObject *aa, Py_ssize_t i) { PyListObject *a = (PyListObject *)aa; - if (!valid_index(i, Py_SIZE(a))) { + if (!valid_index(i, PyList_GET_SIZE(a))) { PyErr_SetObject(PyExc_IndexError, &_Py_STR(list_err)); return NULL; } @@ -511,6 +515,8 @@ PyList_GetSlice(PyObject *a, Py_ssize_t ilow, Py_ssize_t ihigh) PyErr_BadInternalCall(); return NULL; } + PyObject *ret; + Py_BEGIN_CRITICAL_SECTION(a); if (ilow < 0) { ilow = 0; } @@ -523,7 +529,9 @@ PyList_GetSlice(PyObject *a, Py_ssize_t ilow, Py_ssize_t ihigh) else if (ihigh > Py_SIZE(a)) { ihigh = Py_SIZE(a); } - return list_slice((PyListObject *)a, ilow, ihigh); + ret = list_slice((PyListObject *)a, ilow, ihigh); + Py_END_CRITICAL_SECTION(); + return ret; } static PyObject * From 80aa7b3688b8fdc85cd53d4113cb5f6ce5500027 Mon Sep 17 00:00:00 2001 From: Jamie Phan <jamie@ordinarylab.dev> Date: Thu, 1 Feb 2024 07:42:17 +0700 Subject: [PATCH 061/507] gh-109534: fix reference leak when SSL handshake fails (#114074) --- Lib/asyncio/selector_events.py | 4 ++++ Lib/asyncio/sslproto.py | 1 + .../Library/2024-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst | 3 +++ 3 files changed, 8 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index dcd5e0aa345029a..10fbdd76e93f79c 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -235,6 +235,10 @@ async def _accept_connection2( await waiter except BaseException: transport.close() + # gh-109534: When an exception is raised by the SSLProtocol object the + # exception set in this future can keep the protocol object alive and + # cause a reference cycle. + waiter = None raise # It's now up to the protocol to handle the connection. diff --git a/Lib/asyncio/sslproto.py b/Lib/asyncio/sslproto.py index 599e91ba0003d10..fa99d4533aa0a6a 100644 --- a/Lib/asyncio/sslproto.py +++ b/Lib/asyncio/sslproto.py @@ -579,6 +579,7 @@ def _on_handshake_complete(self, handshake_exc): peercert = sslobj.getpeercert() except Exception as exc: + handshake_exc = None self._set_state(SSLProtocolState.UNWRAPPED) if isinstance(exc, ssl.CertificateError): msg = 'SSL handshake failed on verifying the certificate' diff --git a/Misc/NEWS.d/next/Library/2024-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst b/Misc/NEWS.d/next/Library/2024-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst new file mode 100644 index 000000000000000..fc9a765a230037d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst @@ -0,0 +1,3 @@ +Fix a reference leak in +:class:`asyncio.selector_events.BaseSelectorEventLoop` when SSL handshakes +fail. Patch contributed by Jamie Phan. From a79a27242f75fc33416d4d135a4a542898d140e5 Mon Sep 17 00:00:00 2001 From: Aidan Holm <alfh@google.com> Date: Thu, 1 Feb 2024 08:42:38 +0800 Subject: [PATCH 062/507] gh-111112: Avoid potential confusion in TCP server example. (#111113) Improve misleading TCP server docs and example. socket.recv(), as documented by the Python reference documentation, returns at most `bufsize` bytes, and the underlying TCP protocol means there is no guaranteed correspondence between what is sent by the client and what is received by the server. This conflation could mislead readers into thinking that TCP is datagram-based or has similar semantics, which will likely appear to work for simple cases, but introduce difficult to reproduce bugs. --- Doc/library/socketserver.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Doc/library/socketserver.rst b/Doc/library/socketserver.rst index 5fd213fa613c8d4..864b1dadb785623 100644 --- a/Doc/library/socketserver.rst +++ b/Doc/library/socketserver.rst @@ -494,7 +494,7 @@ This is the server side:: def handle(self): # self.request is the TCP socket connected to the client self.data = self.request.recv(1024).strip() - print("{} wrote:".format(self.client_address[0])) + print("Received from {}:".format(self.client_address[0])) print(self.data) # just send back the same data, but upper-cased self.request.sendall(self.data.upper()) @@ -525,8 +525,9 @@ objects that simplify communication by providing the standard file interface):: The difference is that the ``readline()`` call in the second handler will call ``recv()`` multiple times until it encounters a newline character, while the -single ``recv()`` call in the first handler will just return what has been sent -from the client in one ``sendall()`` call. +single ``recv()`` call in the first handler will just return what has been +received so far from the client's ``sendall()`` call (typically all of it, but +this is not guaranteed by the TCP protocol). This is the client side:: From 854e2bc42340b22cdeea5d16ac8b1ef3762c6909 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 1 Feb 2024 03:35:48 +0200 Subject: [PATCH 063/507] CI: Test on macOS M1 (#114766) Test on macOS M1 --- .github/workflows/reusable-macos.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable-macos.yml b/.github/workflows/reusable-macos.yml index c24b6e963ddfd67..28e9dc52fd50eed 100644 --- a/.github/workflows/reusable-macos.yml +++ b/.github/workflows/reusable-macos.yml @@ -12,20 +12,27 @@ on: jobs: build_macos: name: 'build and test' - runs-on: macos-latest timeout-minutes: 60 env: HOMEBREW_NO_ANALYTICS: 1 HOMEBREW_NO_AUTO_UPDATE: 1 HOMEBREW_NO_INSTALL_CLEANUP: 1 PYTHONSTRICTEXTENSIONBUILD: 1 + strategy: + fail-fast: false + matrix: + os: [ + "macos-14", # M1 + "macos-13", # Intel + ] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Restore config.cache uses: actions/cache@v3 with: path: config.cache - key: ${{ github.job }}-${{ runner.os }}-${{ inputs.config_hash }} + key: ${{ github.job }}-${{ matrix.os }}-${{ inputs.config_hash }} - name: Install Homebrew dependencies run: brew install pkg-config openssl@3.0 xz gdbm tcl-tk - name: Configure CPython From ff8939e5abaad7cd87f4d1f07ca7f6d176090f6c Mon Sep 17 00:00:00 2001 From: Pradyot Ranjan <99216956+prady0t@users.noreply.github.com> Date: Thu, 1 Feb 2024 07:18:39 +0530 Subject: [PATCH 064/507] gh-114811: Change '\*' to '*' in warnings.rst (#114819) Regression in 3.12. --- Doc/library/warnings.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/warnings.rst b/Doc/library/warnings.rst index a9c469707e82273..500398636e11ae8 100644 --- a/Doc/library/warnings.rst +++ b/Doc/library/warnings.rst @@ -396,7 +396,7 @@ Available Functions ------------------- -.. function:: warn(message, category=None, stacklevel=1, source=None, \*, skip_file_prefixes=None) +.. function:: warn(message, category=None, stacklevel=1, source=None, *, skip_file_prefixes=None) Issue a warning, or maybe ignore it or raise an exception. The *category* argument, if given, must be a :ref:`warning category class <warning-categories>`; it From 586057e9f80d57f16334c0eee8431931e4aa8cff Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Wed, 31 Jan 2024 21:11:16 -0600 Subject: [PATCH 065/507] gh-67230: Add versionadded notes for QUOTE_NOTNULL and QUOTE_STRINGS (#114816) As @GPHemsley pointed out, #29469 omitted `versionadded` notes for the 2 new items. --- Doc/library/csv.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/library/csv.rst b/Doc/library/csv.rst index 66888c22b7cc28a..fd62b225fcebb80 100644 --- a/Doc/library/csv.rst +++ b/Doc/library/csv.rst @@ -351,6 +351,8 @@ The :mod:`csv` module defines the following constants: Instructs :class:`reader` objects to interpret an empty (unquoted) field as None and to otherwise behave as :data:`QUOTE_ALL`. + .. versionadded:: 3.12 + .. data:: QUOTE_STRINGS Instructs :class:`writer` objects to always place quotes around fields @@ -360,6 +362,8 @@ The :mod:`csv` module defines the following constants: Instructs :class:`reader` objects to interpret an empty (unquoted) string as ``None`` and to otherwise behave as :data:`QUOTE_NONNUMERIC`. + .. versionadded:: 3.12 + The :mod:`csv` module defines the following exception: From 57c3e775df5a5ca0982adf15010ed80a158b1b80 Mon Sep 17 00:00:00 2001 From: srinivasan <shivnaren@gmail.com> Date: Thu, 1 Feb 2024 09:16:49 +0530 Subject: [PATCH 066/507] gh-114648: Add IndexError exception to tutorial datastructures list.pop entry (#114681) Remove redundant explanation of optional argument. --- Doc/tutorial/datastructures.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Doc/tutorial/datastructures.rst b/Doc/tutorial/datastructures.rst index 87614d082a1d4e0..de2827461e2f241 100644 --- a/Doc/tutorial/datastructures.rst +++ b/Doc/tutorial/datastructures.rst @@ -48,10 +48,9 @@ objects: :noindex: Remove the item at the given position in the list, and return it. If no index - is specified, ``a.pop()`` removes and returns the last item in the list. (The - square brackets around the *i* in the method signature denote that the parameter - is optional, not that you should type square brackets at that position. You - will see this notation frequently in the Python Library Reference.) + is specified, ``a.pop()`` removes and returns the last item in the list. + It raises an :exc:`IndexError` if the list is empty or the index is + outside the list range. .. method:: list.clear() From 5ce193e65a7e6f239337a8c5305895cf8a4d2726 Mon Sep 17 00:00:00 2001 From: technillogue <wisepoison@gmail.com> Date: Thu, 1 Feb 2024 01:03:58 -0500 Subject: [PATCH 067/507] gh-114364: Fix awkward wording about mmap.mmap.seekable (#114374) --------- Co-authored-by: Kirill Podoprigora <kirill.bast9@mail.ru> Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu> Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com> --- Doc/whatsnew/3.13.rst | 4 ++-- Misc/NEWS.d/3.13.0a2.rst | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index b33203efbb05c0c..6b94a3771406faf 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -259,8 +259,8 @@ mmap ---- * The :class:`mmap.mmap` class now has an :meth:`~mmap.mmap.seekable` method - that can be used where it requires a file-like object with seekable and - the :meth:`~mmap.mmap.seek` method return the new absolute position. + that can be used when a seekable file-like object is required. + The :meth:`~mmap.mmap.seek` method now returns the new absolute position. (Contributed by Donghee Na and Sylvie Liberman in :gh:`111835`.) * :class:`mmap.mmap` now has a *trackfd* parameter on Unix; if it is ``False``, the file descriptor specified by *fileno* will not be duplicated. diff --git a/Misc/NEWS.d/3.13.0a2.rst b/Misc/NEWS.d/3.13.0a2.rst index d4be4fb8a3d3ab6..e5841e14c02efbf 100644 --- a/Misc/NEWS.d/3.13.0a2.rst +++ b/Misc/NEWS.d/3.13.0a2.rst @@ -565,9 +565,9 @@ part of a :exc:`BaseExceptionGroup`, in addition to the recent support for .. section: Library The :class:`mmap.mmap` class now has an :meth:`~mmap.mmap.seekable` method -that can be used where it requires a file-like object with seekable and the -:meth:`~mmap.mmap.seek` method return the new absolute position. Patch by -Donghee Na. +that can be used when a seekable file-like object is required. +The :meth:`~mmap.mmap.seek` method now returns the new absolute position. +Patch by Donghee Na. .. From e6d6d5dcc00af50446761b0c4d20bd6e92380135 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Thu, 1 Feb 2024 04:26:23 -0500 Subject: [PATCH 068/507] gh-114746: Avoid quadratic behavior in free-threaded GC (GH-114817) The free-threaded build's GC implementation is non-generational, but was scheduled as if it were collecting a young generation leading to quadratic behavior. This increases the minimum threshold and scales it to the number of live objects as we do for the old generation in the default build. Note that the scheduling is still not thread-safe without the GIL. Those changes will come in later PRs. A few tests, like "test_sneaky_frame_object" rely on prompt scheduling of the GC. For now, to keep that test passing, we disable the scaled threshold after calls like `gc.set_threshold(1, 0, 0)`. --- Python/gc_free_threading.c | 102 +++++++++++-------------------------- 1 file changed, 29 insertions(+), 73 deletions(-) diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index f2cd84981461a4f..a6513a2c4aba2a6 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -46,6 +46,7 @@ struct collection_state { GCState *gcstate; Py_ssize_t collected; Py_ssize_t uncollectable; + Py_ssize_t long_lived_total; struct worklist unreachable; struct worklist legacy_finalizers; struct worklist wrcb_to_call; @@ -443,7 +444,7 @@ scan_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area, else { // object is reachable, restore `ob_tid`; we're done with these objects gc_restore_tid(op); - state->gcstate->long_lived_total++; + state->long_lived_total++; } return true; @@ -605,6 +606,8 @@ get_gc_state(void) void _PyGC_InitState(GCState *gcstate) { + // TODO: move to pycore_runtime_init.h once the incremental GC lands. + gcstate->generations[0].threshold = 2000; } @@ -885,62 +888,6 @@ invoke_gc_callback(PyThreadState *tstate, const char *phase, assert(!_PyErr_Occurred(tstate)); } - -/* Find the oldest generation (highest numbered) where the count - * exceeds the threshold. Objects in the that generation and - * generations younger than it will be collected. */ -static int -gc_select_generation(GCState *gcstate) -{ - for (int i = NUM_GENERATIONS-1; i >= 0; i--) { - if (gcstate->generations[i].count > gcstate->generations[i].threshold) { - /* Avoid quadratic performance degradation in number - of tracked objects (see also issue #4074): - - To limit the cost of garbage collection, there are two strategies; - - make each collection faster, e.g. by scanning fewer objects - - do less collections - This heuristic is about the latter strategy. - - In addition to the various configurable thresholds, we only trigger a - full collection if the ratio - - long_lived_pending / long_lived_total - - is above a given value (hardwired to 25%). - - The reason is that, while "non-full" collections (i.e., collections of - the young and middle generations) will always examine roughly the same - number of objects -- determined by the aforementioned thresholds --, - the cost of a full collection is proportional to the total number of - long-lived objects, which is virtually unbounded. - - Indeed, it has been remarked that doing a full collection every - <constant number> of object creations entails a dramatic performance - degradation in workloads which consist in creating and storing lots of - long-lived objects (e.g. building a large list of GC-tracked objects would - show quadratic performance, instead of linear as expected: see issue #4074). - - Using the above ratio, instead, yields amortized linear performance in - the total number of objects (the effect of which can be summarized - thusly: "each full garbage collection is more and more costly as the - number of objects grows, but we do fewer and fewer of them"). - - This heuristic was suggested by Martin von Löwis on python-dev in - June 2008. His original analysis and proposal can be found at: - http://mail.python.org/pipermail/python-dev/2008-June/080579.html - */ - if (i == NUM_GENERATIONS - 1 - && gcstate->long_lived_pending < gcstate->long_lived_total / 4) - { - continue; - } - return i; - } - } - return -1; -} - static void cleanup_worklist(struct worklist *worklist) { @@ -952,6 +899,21 @@ cleanup_worklist(struct worklist *worklist) } } +static bool +gc_should_collect(GCState *gcstate) +{ + int count = _Py_atomic_load_int_relaxed(&gcstate->generations[0].count); + int threshold = gcstate->generations[0].threshold; + if (count <= threshold || threshold == 0 || !gcstate->enabled) { + return false; + } + // Avoid quadratic behavior by scaling threshold to the number of live + // objects. A few tests rely on immediate scheduling of the GC so we ignore + // the scaled threshold if generations[1].threshold is set to zero. + return (count > gcstate->long_lived_total / 4 || + gcstate->generations[1].threshold == 0); +} + static void gc_collect_internal(PyInterpreterState *interp, struct collection_state *state) { @@ -1029,15 +991,10 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) return 0; } - if (generation == GENERATION_AUTO) { - // Select the oldest generation that needs collecting. We will collect - // objects from that generation and all generations younger than it. - generation = gc_select_generation(gcstate); - if (generation < 0) { - // No generation needs to be collected. - _Py_atomic_store_int(&gcstate->collecting, 0); - return 0; - } + if (reason == _Py_GC_REASON_HEAP && !gc_should_collect(gcstate)) { + // Don't collect if the threshold is not exceeded. + _Py_atomic_store_int(&gcstate->collecting, 0); + return 0; } assert(generation >= 0 && generation < NUM_GENERATIONS); @@ -1082,6 +1039,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) m = state.collected; n = state.uncollectable; + gcstate->long_lived_total = state.long_lived_total; if (gcstate->debug & _PyGC_DEBUG_STATS) { double d = _PyTime_AsSecondsDouble(_PyTime_GetPerfCounter() - t1); @@ -1523,12 +1481,10 @@ _PyObject_GC_Link(PyObject *op) { PyThreadState *tstate = _PyThreadState_GET(); GCState *gcstate = &tstate->interp->gc; - gcstate->generations[0].count++; /* number of allocated GC objects */ - if (gcstate->generations[0].count > gcstate->generations[0].threshold && - gcstate->enabled && - gcstate->generations[0].threshold && - !_Py_atomic_load_int_relaxed(&gcstate->collecting) && - !_PyErr_Occurred(tstate)) + gcstate->generations[0].count++; + + if (gc_should_collect(gcstate) && + !_Py_atomic_load_int_relaxed(&gcstate->collecting)) { _Py_ScheduleGC(tstate->interp); } @@ -1537,7 +1493,7 @@ _PyObject_GC_Link(PyObject *op) void _Py_RunGC(PyThreadState *tstate) { - gc_collect_main(tstate, GENERATION_AUTO, _Py_GC_REASON_HEAP); + gc_collect_main(tstate, 0, _Py_GC_REASON_HEAP); } static PyObject * From de6f97cd3519c5d8528d8ca1bb00fce4e9969671 Mon Sep 17 00:00:00 2001 From: Christophe Nanteuil <35002064+christopheNan@users.noreply.github.com> Date: Thu, 1 Feb 2024 10:34:04 +0100 Subject: [PATCH 069/507] Fix typos in ElementTree documentation (GH-108848) PI objects instead of comment objects. --- Doc/library/xml.etree.elementtree.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/xml.etree.elementtree.rst b/Doc/library/xml.etree.elementtree.rst index fe92400fb08dfdc..bb6773c361a9b41 100644 --- a/Doc/library/xml.etree.elementtree.rst +++ b/Doc/library/xml.etree.elementtree.rst @@ -664,7 +664,7 @@ Functions given. Returns an element instance, representing a processing instruction. Note that :class:`XMLParser` skips over processing instructions - in the input instead of creating comment objects for them. An + in the input instead of creating PI objects for them. An :class:`ElementTree` will only contain processing instruction nodes if they have been inserted into to the tree using one of the :class:`Element` methods. @@ -1302,8 +1302,8 @@ TreeBuilder Objects .. method:: pi(target, text) - Creates a comment with the given *target* name and *text*. If - ``insert_pis`` is true, this will also add it to the tree. + Creates a process instruction with the given *target* name and *text*. + If ``insert_pis`` is true, this will also add it to the tree. .. versionadded:: 3.8 From 21c01a009f970ccf73d2d3e4176b61fc8986adfa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 09:52:05 +0000 Subject: [PATCH 070/507] build(deps-dev): bump types-setuptools from 69.0.0.0 to 69.0.0.20240125 in /Tools (#114853) build(deps-dev): bump types-setuptools in /Tools Bumps [types-setuptools](https://github.com/python/typeshed) from 69.0.0.0 to 69.0.0.20240125. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-setuptools dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Tools/requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/requirements-dev.txt b/Tools/requirements-dev.txt index b89f86a35d61153..11cce505f60d160 100644 --- a/Tools/requirements-dev.txt +++ b/Tools/requirements-dev.txt @@ -4,4 +4,4 @@ mypy==1.8.0 # needed for peg_generator: types-psutil==5.9.5.17 -types-setuptools==69.0.0.0 +types-setuptools==69.0.0.20240125 From 93bfaa858c22742b7229897ae7c15e9b6e456fc3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 10:42:35 +0000 Subject: [PATCH 071/507] build(deps): bump hypothesis from 6.92.2 to 6.97.4 in /Tools (#114851) Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.92.2 to 6.97.4. - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.92.2...hypothesis-python-6.97.4) --- updated-dependencies: - dependency-name: hypothesis dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Tools/requirements-hypothesis.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/requirements-hypothesis.txt b/Tools/requirements-hypothesis.txt index 0e6e16ae198162c..064731a236ee863 100644 --- a/Tools/requirements-hypothesis.txt +++ b/Tools/requirements-hypothesis.txt @@ -1,4 +1,4 @@ # Requirements file for hypothesis that # we use to run our property-based tests in CI. -hypothesis==6.92.2 +hypothesis==6.97.4 From d4c5ec24c2bbb1e1d02d17b75709028aca84398e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 12:49:07 +0200 Subject: [PATCH 072/507] build(deps): bump actions/cache from 3 to 4 (#114856) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 16 ++++++++-------- .github/workflows/reusable-docs.yml | 2 +- .github/workflows/reusable-macos.yml | 2 +- .github/workflows/reusable-ubuntu.yml | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cc5ecc09fbc5923..949c4ae95da07f0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -132,7 +132,7 @@ jobs: with: python-version: '3.x' - name: Restore config.cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: config.cache key: ${{ github.job }}-${{ runner.os }}-${{ needs.check_source.outputs.config_hash }}-${{ env.pythonLocation }} @@ -259,7 +259,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Restore config.cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: config.cache key: ${{ github.job }}-${{ runner.os }}-${{ needs.check_source.outputs.config_hash }} @@ -274,7 +274,7 @@ jobs: echo "LD_LIBRARY_PATH=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}/lib" >> $GITHUB_ENV - name: 'Restore OpenSSL build' id: cache-openssl - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./multissl/openssl/${{ env.OPENSSL_VER }} key: ${{ runner.os }}-multissl-openssl-${{ env.OPENSSL_VER }} @@ -319,7 +319,7 @@ jobs: echo "LD_LIBRARY_PATH=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}/lib" >> $GITHUB_ENV - name: 'Restore OpenSSL build' id: cache-openssl - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./multissl/openssl/${{ env.OPENSSL_VER }} key: ${{ runner.os }}-multissl-openssl-${{ env.OPENSSL_VER }} @@ -342,7 +342,7 @@ jobs: - name: Bind mount sources read-only run: sudo mount --bind -o ro $GITHUB_WORKSPACE $CPYTHON_RO_SRCDIR - name: Restore config.cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.CPYTHON_BUILDDIR }}/config.cache key: ${{ github.job }}-${{ runner.os }}-${{ needs.check_source.outputs.config_hash }} @@ -375,7 +375,7 @@ jobs: ./python -m venv $VENV_LOC && $VENV_PYTHON -m pip install -r ${GITHUB_WORKSPACE}/Tools/requirements-hypothesis.txt - name: 'Restore Hypothesis database' id: cache-hypothesis-database - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./hypothesis key: hypothesis-database-${{ github.head_ref || github.run_id }} @@ -421,7 +421,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Restore config.cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: config.cache key: ${{ github.job }}-${{ runner.os }}-${{ needs.check_source.outputs.config_hash }} @@ -440,7 +440,7 @@ jobs: echo "LD_LIBRARY_PATH=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}/lib" >> $GITHUB_ENV - name: 'Restore OpenSSL build' id: cache-openssl - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./multissl/openssl/${{ env.OPENSSL_VER }} key: ${{ runner.os }}-multissl-openssl-${{ env.OPENSSL_VER }} diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml index e534751ee1011da..cea8f93d67b29c1 100644 --- a/.github/workflows/reusable-docs.yml +++ b/.github/workflows/reusable-docs.yml @@ -89,7 +89,7 @@ jobs: timeout-minutes: 60 steps: - uses: actions/checkout@v4 - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ~/.cache/pip key: ubuntu-doc-${{ hashFiles('Doc/requirements.txt') }} diff --git a/.github/workflows/reusable-macos.yml b/.github/workflows/reusable-macos.yml index 28e9dc52fd50eed..cad619b78ce5f2c 100644 --- a/.github/workflows/reusable-macos.yml +++ b/.github/workflows/reusable-macos.yml @@ -29,7 +29,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Restore config.cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: config.cache key: ${{ github.job }}-${{ matrix.os }}-${{ inputs.config_hash }} diff --git a/.github/workflows/reusable-ubuntu.yml b/.github/workflows/reusable-ubuntu.yml index c2194280c0a50f4..ef52d99c15191b9 100644 --- a/.github/workflows/reusable-ubuntu.yml +++ b/.github/workflows/reusable-ubuntu.yml @@ -29,7 +29,7 @@ jobs: echo "LD_LIBRARY_PATH=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}/lib" >> $GITHUB_ENV - name: 'Restore OpenSSL build' id: cache-openssl - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./multissl/openssl/${{ env.OPENSSL_VER }} key: ${{ runner.os }}-multissl-openssl-${{ env.OPENSSL_VER }} @@ -53,7 +53,7 @@ jobs: - name: Bind mount sources read-only run: sudo mount --bind -o ro $GITHUB_WORKSPACE $CPYTHON_RO_SRCDIR - name: Restore config.cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.CPYTHON_BUILDDIR }}/config.cache key: ${{ github.job }}-${{ runner.os }}-${{ inputs.config_hash }} From 59ae215387d69119f0e77b2776e8214ca4bb8a5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 10:50:08 +0000 Subject: [PATCH 073/507] build(deps-dev): bump types-psutil from 5.9.5.17 to 5.9.5.20240106 in /Tools (#114852) build(deps-dev): bump types-psutil in /Tools Bumps [types-psutil](https://github.com/python/typeshed) from 5.9.5.17 to 5.9.5.20240106. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-psutil dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Tools/requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/requirements-dev.txt b/Tools/requirements-dev.txt index 11cce505f60d160..c0a63b40ff4155f 100644 --- a/Tools/requirements-dev.txt +++ b/Tools/requirements-dev.txt @@ -3,5 +3,5 @@ mypy==1.8.0 # needed for peg_generator: -types-psutil==5.9.5.17 +types-psutil==5.9.5.20240106 types-setuptools==69.0.0.20240125 From 0bf42dae7e73febc76ea96fd58af6b765a12b8a7 Mon Sep 17 00:00:00 2001 From: Tomas R <tomas.roun8@gmail.com> Date: Thu, 1 Feb 2024 12:49:01 +0100 Subject: [PATCH 074/507] gh-107461 ctypes: Add a testcase for nested `_as_parameter_` lookup (GH-107462) --- Lib/test/test_ctypes/test_as_parameter.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Lib/test/test_ctypes/test_as_parameter.py b/Lib/test/test_ctypes/test_as_parameter.py index a1a8745e737fa25..ca75e748256083b 100644 --- a/Lib/test/test_ctypes/test_as_parameter.py +++ b/Lib/test/test_ctypes/test_as_parameter.py @@ -221,5 +221,16 @@ class AsParamPropertyWrapperTestCase(BasicWrapTestCase): wrap = AsParamPropertyWrapper +class AsParamNestedWrapperTestCase(BasicWrapTestCase): + """Test that _as_parameter_ is evaluated recursively. + + The _as_parameter_ attribute can be another object which + defines its own _as_parameter_ attribute. + """ + + def wrap(self, param): + return AsParamWrapper(AsParamWrapper(AsParamWrapper(param))) + + if __name__ == '__main__': unittest.main() From 4dbb198d279a06fed74ea4c38f93d658baf38170 Mon Sep 17 00:00:00 2001 From: Ayappan Perumal <ayappap2@in.ibm.com> Date: Thu, 1 Feb 2024 17:22:54 +0530 Subject: [PATCH 075/507] gh-105089: Fix test_create_directory_with_write test failure in AIX (GH-105228) --- Lib/test/test_zipfile/test_core.py | 2 +- .../next/Tests/2023-06-02-05-04-15.gh-issue-105089.KaZFtU.rst | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Tests/2023-06-02-05-04-15.gh-issue-105089.KaZFtU.rst diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index 9bdb08aeabb7817..a177044d735bed7 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -2959,7 +2959,7 @@ def test_create_directory_with_write(self): directory = os.path.join(TESTFN2, "directory2") os.mkdir(directory) - mode = os.stat(directory).st_mode + mode = os.stat(directory).st_mode & 0xFFFF zf.write(directory, arcname="directory2/") zinfo = zf.filelist[1] self.assertEqual(zinfo.filename, "directory2/") diff --git a/Misc/NEWS.d/next/Tests/2023-06-02-05-04-15.gh-issue-105089.KaZFtU.rst b/Misc/NEWS.d/next/Tests/2023-06-02-05-04-15.gh-issue-105089.KaZFtU.rst new file mode 100644 index 000000000000000..d04ef435dd572d8 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2023-06-02-05-04-15.gh-issue-105089.KaZFtU.rst @@ -0,0 +1,4 @@ +Fix +``test.test_zipfile.test_core.TestWithDirectory.test_create_directory_with_write`` +test in AIX by doing a bitwise AND of 0xFFFF on mode , so that it will be in +sync with ``zinfo.external_attr`` From 84e0e32184f658b8174b400e6ca9c418bfe8e0fc Mon Sep 17 00:00:00 2001 From: Anders Kaseorg <andersk@mit.edu> Date: Thu, 1 Feb 2024 11:26:22 -0500 Subject: [PATCH 076/507] Remove unused Py_XDECREF from _PyFrame_ClearExceptCode (GH-106158) frame->frame_obj was set to NULL a few lines earlier. Signed-off-by: Anders Kaseorg <andersk@mit.edu> --- Python/frame.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Python/frame.c b/Python/frame.c index 2865b2eab603c27..ddf6ef6ba5465cf 100644 --- a/Python/frame.c +++ b/Python/frame.c @@ -139,7 +139,6 @@ _PyFrame_ClearExceptCode(_PyInterpreterFrame *frame) for (int i = 0; i < frame->stacktop; i++) { Py_XDECREF(frame->localsplus[i]); } - Py_XDECREF(frame->frame_obj); Py_XDECREF(frame->f_locals); Py_DECREF(frame->f_funcobj); } From 2dea1cf7fd9b1f6a914e363ecb17a853f4b99b6b Mon Sep 17 00:00:00 2001 From: Guido van Rossum <guido@python.org> Date: Thu, 1 Feb 2024 08:54:44 -0800 Subject: [PATCH 077/507] Write about Tier 2 and JIT in "what's new 3.13" (#114826) (This will soon be superseded by Ken Jin's much more detailed version.) --- Doc/whatsnew/3.13.rst | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 6b94a3771406faf..887c3009f885041 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -81,6 +81,13 @@ Important deprecations, removals or restrictions: * Python 3.13 and later have two years of full support, followed by three years of security fixes. +Interpreter improvements: + +* A basic :ref:`JIT compiler <whatsnew313-jit-compiler>` was added. + It is currently disabled by default (though we may turn it on later). + Performance improvements are modest -- we expect to be improving this + over the next few releases. + New Features ============ @@ -477,6 +484,46 @@ Optimizations FreeBSD and Solaris. See the ``subprocess`` section above for details. (Contributed by Jakub Kulik in :gh:`113117`.) +.. _whatsnew313-jit-compiler: + +Experimental JIT Compiler +========================= + +When CPython is configured using the ``--enable-experimental-jit`` option, +a just-in-time compiler is added which can speed up some Python programs. + +The internal architecture is roughly as follows. + +* We start with specialized *Tier 1 bytecode*. + See :ref:`What's new in 3.11 <whatsnew311-pep659>` for details. + +* When the Tier 1 bytecode gets hot enough, it gets translated + to a new, purely internal *Tier 2 IR*, a.k.a. micro-ops ("uops"). + +* The Tier 2 IR uses the same stack-based VM as Tier 1, but the + instruction format is better suited to translation to machine code. + +* We have several optimization passes for Tier 2 IR, which are applied + before it is interpreted or translated to machine code. + +* There is a Tier 2 interpreter, but it is mostly intended for debugging + the earlier stages of the optimization pipeline. If the JIT is not + enabled, the Tier 2 interpreter can be invoked by passing Python the + ``-X uops`` option or by setting the ``PYTHON_UOPS`` environment + variable to ``1``. + +* When the ``--enable-experimental-jit`` option is used, the optimized + Tier 2 IR is translated to machine code, which is then executed. + This does not require additional runtime options. + +* The machine code translation process uses an architecture called + *copy-and-patch*. It has no runtime dependencies, but there is a new + build-time dependency on LLVM. + +(JIT by Brandt Bucher, inspired by a paper by Haoran Xu and Fredrik Kjolstad. +Tier 2 IR by Mark Shannon and Guido van Rossum. +Tier 2 optimizer by Ken Jin.) + Deprecated ========== From 6d7ad57385e6c18545f19714b8f520644d305715 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Thu, 1 Feb 2024 19:56:24 +0300 Subject: [PATCH 078/507] Update outdated info in ``Tools/cases_generator/README.md`` (#114844) --- Tools/cases_generator/README.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/Tools/cases_generator/README.md b/Tools/cases_generator/README.md index ed802e44f31ad5f..7fec8a882336cdf 100644 --- a/Tools/cases_generator/README.md +++ b/Tools/cases_generator/README.md @@ -5,16 +5,30 @@ Documentation for the instruction definitions in `Python/bytecodes.c` What's currently here: +- `analyzer.py`: code for converting `AST` generated by `Parser` + to more high-level structure for easier interaction - `lexer.py`: lexer for C, originally written by Mark Shannon - `plexer.py`: OO interface on top of lexer.py; main class: `PLexer` -- `parsing.py`: Parser for instruction definition DSL; main class `Parser` -- `generate_cases.py`: driver script to read `Python/bytecodes.c` and +- `parsing.py`: Parser for instruction definition DSL; main class: `Parser` +- `parser.py` helper for interactions with `parsing.py` +- `tierN_generator.py`: a couple of driver scripts to read `Python/bytecodes.c` and write `Python/generated_cases.c.h` (and several other files) -- `analysis.py`: `Analyzer` class used to read the input files -- `flags.py`: abstractions related to metadata flags for instructions -- `formatting.py`: `Formatter` class used to write the output files -- `instructions.py`: classes to analyze and write instructions -- `stacking.py`: code to handle generalized stack effects +- `stack.py`: code to handle generalized stack effects +- `cwriter.py`: code which understands tokens and how to format C code; + main class: `CWriter` +- `generators_common.py`: helpers for generators +- `opcode_id_generator.py`: generate a list of opcodes and write them to + `Include/opcode_ids.h` +- `opcode_metadata_generator.py`: reads the instruction definitions and + write the metadata to `Include/internal/pycore_opcode_metadata.h` +- `py_metadata_generator.py`: reads the instruction definitions and + write the metadata to `Lib/_opcode_metadata.py` +- `target_generator.py`: generate targets for computed goto dispatch and + write them to `Python/opcode_targets.h` +- `uop_id_generator.py`: generate a list of uop IDs and write them to + `Include/internal/pycore_uop_ids.h` +- `uop_metadata_generator.py`: reads the instruction definitions and + write the metadata to `Include/internal/pycore_uop_metadata.h` Note that there is some dummy C code at the top and bottom of `Python/bytecodes.c` From e9dab656380ec03d628979975646748330b76b9b Mon Sep 17 00:00:00 2001 From: Nicholas Hollander <31573882+nhhollander@users.noreply.github.com> Date: Thu, 1 Feb 2024 12:24:15 -0500 Subject: [PATCH 079/507] gh-105031: Clarify datetime documentation for ISO8601 (GH-105049) --- Doc/library/datetime.rst | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 4ff049c8709289c..db9a92ae4111e35 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -536,7 +536,15 @@ Other constructors, all class methods: .. classmethod:: date.fromisoformat(date_string) Return a :class:`date` corresponding to a *date_string* given in any valid - ISO 8601 format, except ordinal dates (e.g. ``YYYY-DDD``):: + ISO 8601 format, with the following exceptions: + + 1. Reduced precision dates are not currently supported (``YYYY-MM``, + ``YYYY``). + 2. Extended date representations are not currently supported + (``±YYYYYY-MM-DD``). + 3. Ordinal dates are not currently supported (``YYYY-OOO``). + + Examples:: >>> from datetime import date >>> date.fromisoformat('2019-12-04') @@ -1017,8 +1025,12 @@ Other constructors, all class methods: 1. Time zone offsets may have fractional seconds. 2. The ``T`` separator may be replaced by any single unicode character. - 3. Ordinal dates are not currently supported. - 4. Fractional hours and minutes are not supported. + 3. Fractional hours and minutes are not supported. + 4. Reduced precision dates are not currently supported (``YYYY-MM``, + ``YYYY``). + 5. Extended date representations are not currently supported + (``±YYYYYY-MM-DD``). + 6. Ordinal dates are not currently supported (``YYYY-OOO``). Examples:: From c9c6e04380ffedd25ea2e582f9057ab9612960c9 Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Thu, 1 Feb 2024 12:07:16 -0600 Subject: [PATCH 080/507] Correct description of inheriting from another class (#114660) "inherits <someclass>" grates to this reader. I think it should be "inherits from <someclass>". --- Doc/library/gzip.rst | 3 +-- Doc/library/io.rst | 24 ++++++++++++------------ Doc/library/pickle.rst | 6 +++--- Doc/library/symtable.rst | 4 ++-- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/Doc/library/gzip.rst b/Doc/library/gzip.rst index 50cde09fa10a9d9..79be215a7660454 100644 --- a/Doc/library/gzip.rst +++ b/Doc/library/gzip.rst @@ -61,7 +61,7 @@ The module defines the following items: .. exception:: BadGzipFile - An exception raised for invalid gzip files. It inherits :exc:`OSError`. + An exception raised for invalid gzip files. It inherits from :exc:`OSError`. :exc:`EOFError` and :exc:`zlib.error` can also be raised for invalid gzip files. @@ -287,4 +287,3 @@ Command line options .. option:: -h, --help Show the help message. - diff --git a/Doc/library/io.rst b/Doc/library/io.rst index 6736aa9ee2b0efc..8eb531aa4ea2487 100644 --- a/Doc/library/io.rst +++ b/Doc/library/io.rst @@ -466,7 +466,7 @@ I/O Base Classes .. class:: RawIOBase - Base class for raw binary streams. It inherits :class:`IOBase`. + Base class for raw binary streams. It inherits from :class:`IOBase`. Raw binary streams typically provide low-level access to an underlying OS device or API, and do not try to encapsulate it in high-level primitives @@ -519,7 +519,7 @@ I/O Base Classes .. class:: BufferedIOBase Base class for binary streams that support some kind of buffering. - It inherits :class:`IOBase`. + It inherits from :class:`IOBase`. The main difference with :class:`RawIOBase` is that methods :meth:`read`, :meth:`readinto` and :meth:`write` will try (respectively) to read as much @@ -633,7 +633,7 @@ Raw File I/O .. class:: FileIO(name, mode='r', closefd=True, opener=None) A raw binary stream representing an OS-level file containing bytes data. It - inherits :class:`RawIOBase`. + inherits from :class:`RawIOBase`. The *name* can be one of two things: @@ -696,7 +696,7 @@ than raw I/O does. .. class:: BytesIO(initial_bytes=b'') - A binary stream using an in-memory bytes buffer. It inherits + A binary stream using an in-memory bytes buffer. It inherits from :class:`BufferedIOBase`. The buffer is discarded when the :meth:`~IOBase.close` method is called. @@ -745,7 +745,7 @@ than raw I/O does. .. class:: BufferedReader(raw, buffer_size=DEFAULT_BUFFER_SIZE) A buffered binary stream providing higher-level access to a readable, non - seekable :class:`RawIOBase` raw binary stream. It inherits + seekable :class:`RawIOBase` raw binary stream. It inherits from :class:`BufferedIOBase`. When reading data from this object, a larger amount of data may be @@ -783,7 +783,7 @@ than raw I/O does. .. class:: BufferedWriter(raw, buffer_size=DEFAULT_BUFFER_SIZE) A buffered binary stream providing higher-level access to a writeable, non - seekable :class:`RawIOBase` raw binary stream. It inherits + seekable :class:`RawIOBase` raw binary stream. It inherits from :class:`BufferedIOBase`. When writing to this object, data is normally placed into an internal @@ -818,7 +818,7 @@ than raw I/O does. .. class:: BufferedRandom(raw, buffer_size=DEFAULT_BUFFER_SIZE) A buffered binary stream providing higher-level access to a seekable - :class:`RawIOBase` raw binary stream. It inherits :class:`BufferedReader` + :class:`RawIOBase` raw binary stream. It inherits from :class:`BufferedReader` and :class:`BufferedWriter`. The constructor creates a reader and writer for a seekable raw stream, given @@ -834,7 +834,7 @@ than raw I/O does. A buffered binary stream providing higher-level access to two non seekable :class:`RawIOBase` raw binary streams---one readable, the other writeable. - It inherits :class:`BufferedIOBase`. + It inherits from :class:`BufferedIOBase`. *reader* and *writer* are :class:`RawIOBase` objects that are readable and writeable respectively. If the *buffer_size* is omitted it defaults to @@ -857,7 +857,7 @@ Text I/O .. class:: TextIOBase Base class for text streams. This class provides a character and line based - interface to stream I/O. It inherits :class:`IOBase`. + interface to stream I/O. It inherits from :class:`IOBase`. :class:`TextIOBase` provides or overrides these data attributes and methods in addition to those from :class:`IOBase`: @@ -946,7 +946,7 @@ Text I/O line_buffering=False, write_through=False) A buffered text stream providing higher-level access to a - :class:`BufferedIOBase` buffered binary stream. It inherits + :class:`BufferedIOBase` buffered binary stream. It inherits from :class:`TextIOBase`. *encoding* gives the name of the encoding that the stream will be decoded or @@ -1073,7 +1073,7 @@ Text I/O .. class:: StringIO(initial_value='', newline='\n') - A text stream using an in-memory text buffer. It inherits + A text stream using an in-memory text buffer. It inherits from :class:`TextIOBase`. The text buffer is discarded when the :meth:`~IOBase.close` method is @@ -1124,7 +1124,7 @@ Text I/O .. class:: IncrementalNewlineDecoder A helper codec that decodes newlines for :term:`universal newlines` mode. - It inherits :class:`codecs.IncrementalDecoder`. + It inherits from :class:`codecs.IncrementalDecoder`. Performance diff --git a/Doc/library/pickle.rst b/Doc/library/pickle.rst index cfb251fca5c7cd9..1b718abfa481a02 100644 --- a/Doc/library/pickle.rst +++ b/Doc/library/pickle.rst @@ -272,13 +272,13 @@ The :mod:`pickle` module defines three exceptions: .. exception:: PickleError - Common base class for the other pickling exceptions. It inherits + Common base class for the other pickling exceptions. It inherits from :exc:`Exception`. .. exception:: PicklingError Error raised when an unpicklable object is encountered by :class:`Pickler`. - It inherits :exc:`PickleError`. + It inherits from :exc:`PickleError`. Refer to :ref:`pickle-picklable` to learn what kinds of objects can be pickled. @@ -286,7 +286,7 @@ The :mod:`pickle` module defines three exceptions: .. exception:: UnpicklingError Error raised when there is a problem unpickling an object, such as a data - corruption or a security violation. It inherits :exc:`PickleError`. + corruption or a security violation. It inherits from :exc:`PickleError`. Note that other exceptions may also be raised during unpickling, including (but not necessarily limited to) AttributeError, EOFError, ImportError, and diff --git a/Doc/library/symtable.rst b/Doc/library/symtable.rst index 46159dcef940e7c..47568387f9a7ce0 100644 --- a/Doc/library/symtable.rst +++ b/Doc/library/symtable.rst @@ -97,7 +97,7 @@ Examining Symbol Tables .. class:: Function - A namespace for a function or method. This class inherits + A namespace for a function or method. This class inherits from :class:`SymbolTable`. .. method:: get_parameters() @@ -123,7 +123,7 @@ Examining Symbol Tables .. class:: Class - A namespace of a class. This class inherits :class:`SymbolTable`. + A namespace of a class. This class inherits from :class:`SymbolTable`. .. method:: get_methods() From dc01b919c721f43ad024ba444a5d19541370e581 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Thu, 1 Feb 2024 21:37:55 +0300 Subject: [PATCH 081/507] gh-101100: Fix sphinx warnings in `howto/logging.rst` (#114846) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/howto/logging.rst | 43 +++++++++++++++++++++-------------------- Doc/library/logging.rst | 16 +++++++++++++-- Doc/tools/.nitignore | 1 - 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/Doc/howto/logging.rst b/Doc/howto/logging.rst index f164b461c93b9cd..347330e98dd00c9 100644 --- a/Doc/howto/logging.rst +++ b/Doc/howto/logging.rst @@ -520,7 +520,7 @@ custom handlers) are the following configuration methods: * The :meth:`~Handler.setLevel` method, just as in logger objects, specifies the lowest severity that will be dispatched to the appropriate destination. Why - are there two :func:`setLevel` methods? The level set in the logger + are there two :meth:`~Handler.setLevel` methods? The level set in the logger determines which severity of messages it will pass to its handlers. The level set in each handler determines which messages that handler will send on. @@ -774,29 +774,29 @@ What happens if no configuration is provided If no logging configuration is provided, it is possible to have a situation where a logging event needs to be output, but no handlers can be found to -output the event. The behaviour of the logging package in these -circumstances is dependent on the Python version. +output the event. -For versions of Python prior to 3.2, the behaviour is as follows: +The event is output using a 'handler of last resort', stored in +:data:`lastResort`. This internal handler is not associated with any +logger, and acts like a :class:`~logging.StreamHandler` which writes the +event description message to the current value of ``sys.stderr`` (therefore +respecting any redirections which may be in effect). No formatting is +done on the message - just the bare event description message is printed. +The handler's level is set to ``WARNING``, so all events at this and +greater severities will be output. -* If *logging.raiseExceptions* is ``False`` (production mode), the event is - silently dropped. +.. versionchanged:: 3.2 -* If *logging.raiseExceptions* is ``True`` (development mode), a message - 'No handlers could be found for logger X.Y.Z' is printed once. + For versions of Python prior to 3.2, the behaviour is as follows: -In Python 3.2 and later, the behaviour is as follows: + * If :data:`raiseExceptions` is ``False`` (production mode), the event is + silently dropped. -* The event is output using a 'handler of last resort', stored in - ``logging.lastResort``. This internal handler is not associated with any - logger, and acts like a :class:`~logging.StreamHandler` which writes the - event description message to the current value of ``sys.stderr`` (therefore - respecting any redirections which may be in effect). No formatting is - done on the message - just the bare event description message is printed. - The handler's level is set to ``WARNING``, so all events at this and - greater severities will be output. + * If :data:`raiseExceptions` is ``True`` (development mode), a message + 'No handlers could be found for logger X.Y.Z' is printed once. -To obtain the pre-3.2 behaviour, ``logging.lastResort`` can be set to ``None``. + To obtain the pre-3.2 behaviour, + :data:`lastResort` can be set to ``None``. .. _library-config: @@ -998,7 +998,7 @@ Logged messages are formatted for presentation through instances of the use with the % operator and a dictionary. For formatting multiple messages in a batch, instances of -:class:`~handlers.BufferingFormatter` can be used. In addition to the format +:class:`BufferingFormatter` can be used. In addition to the format string (which is applied to each message in the batch), there is provision for header and trailer format strings. @@ -1034,7 +1034,8 @@ checks to see if a module-level variable, :data:`raiseExceptions`, is set. If set, a traceback is printed to :data:`sys.stderr`. If not set, the exception is swallowed. -.. note:: The default value of :data:`raiseExceptions` is ``True``. This is +.. note:: + The default value of :data:`raiseExceptions` is ``True``. This is because during development, you typically want to be notified of any exceptions that occur. It's advised that you set :data:`raiseExceptions` to ``False`` for production usage. @@ -1072,7 +1073,7 @@ You can write code like this:: expensive_func2()) so that if the logger's threshold is set above ``DEBUG``, the calls to -:func:`expensive_func1` and :func:`expensive_func2` are never made. +``expensive_func1`` and ``expensive_func2`` are never made. .. note:: In some cases, :meth:`~Logger.isEnabledFor` can itself be more expensive than you'd like (e.g. for deeply nested loggers where an explicit diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst index 4b756d10b4c586c..39eb41ce1f16708 100644 --- a/Doc/library/logging.rst +++ b/Doc/library/logging.rst @@ -531,12 +531,12 @@ subclasses. However, the :meth:`!__init__` method in subclasses needs to call This method should be called from handlers when an exception is encountered during an :meth:`emit` call. If the module-level attribute - ``raiseExceptions`` is ``False``, exceptions get silently ignored. This is + :data:`raiseExceptions` is ``False``, exceptions get silently ignored. This is what is mostly wanted for a logging system - most users will not care about errors in the logging system, they are more interested in application errors. You could, however, replace this with a custom handler if you wish. The specified record is the one which was being processed when the exception - occurred. (The default value of ``raiseExceptions`` is ``True``, as that is + occurred. (The default value of :data:`raiseExceptions` is ``True``, as that is more useful during development). @@ -1494,6 +1494,18 @@ Module-Level Attributes .. versionadded:: 3.2 +.. attribute:: raiseExceptions + + Used to see if exceptions during handling should be propagated. + + Default: ``True``. + + If :data:`raiseExceptions` is ``False``, + exceptions get silently ignored. This is what is mostly wanted + for a logging system - most users will not care about errors in + the logging system, they are more interested in application errors. + + Integration with the warnings module ------------------------------------ diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 7eacb46d6299b3c..7127f30f240ce7a 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -18,7 +18,6 @@ Doc/extending/extending.rst Doc/glossary.rst Doc/howto/descriptor.rst Doc/howto/enum.rst -Doc/howto/logging.rst Doc/library/ast.rst Doc/library/asyncio-extending.rst Doc/library/asyncio-policy.rst From 97cc58f9777ee8b8e91f4ca8726cdb9f79cf906c Mon Sep 17 00:00:00 2001 From: He Weidong <67892702+zlhwdsz@users.noreply.github.com> Date: Fri, 2 Feb 2024 03:27:53 +0800 Subject: [PATCH 082/507] Fix comment in pycore_runtime.h (GH-110540) --- Include/internal/pycore_runtime.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index 02ab22b967b38ff..7c705d1224f915b 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -268,7 +268,7 @@ typedef struct pyruntimestate { a pointer type. */ - /* PyInterpreterState.interpreters.main */ + /* _PyRuntimeState.interpreters.main */ PyInterpreterState _main_interpreter; #if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE) From e66d0399cc2e78fcdb6a0113cd757d2ce567ca7c Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Thu, 1 Feb 2024 19:39:32 +0000 Subject: [PATCH 083/507] GH-114806. Don't specialize calls to classes with metaclasses. (GH-114870) --- Lib/test/test_class.py | 16 ++++++++++++++++ ...024-02-01-18-16-52.gh-issue-114806.wrH2J6.rst | 3 +++ Python/specialize.c | 5 +++++ 3 files changed, 24 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-01-18-16-52.gh-issue-114806.wrH2J6.rst diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 1531aad4f1f779d..d59271435e9eb05 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -771,6 +771,22 @@ def add_one_level(): with self.assertRaises(RecursionError): add_one_level() + def testMetaclassCallOptimization(self): + calls = 0 + + class TypeMetaclass(type): + def __call__(cls, *args, **kwargs): + nonlocal calls + calls += 1 + return type.__call__(cls, *args, **kwargs) + + class Type(metaclass=TypeMetaclass): + def __init__(self, obj): + self._obj = obj + + for i in range(100): + Type(i) + self.assertEqual(calls, 100) if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-01-18-16-52.gh-issue-114806.wrH2J6.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-01-18-16-52.gh-issue-114806.wrH2J6.rst new file mode 100644 index 000000000000000..795f2529df82074 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-01-18-16-52.gh-issue-114806.wrH2J6.rst @@ -0,0 +1,3 @@ +No longer specialize calls to classes, if those classes have metaclasses. +Fixes bug where the ``__call__`` method of the metaclass was not being +called. diff --git a/Python/specialize.c b/Python/specialize.c index a9efbe0453b94e2..e38e3556a6d6425 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -540,6 +540,7 @@ _PyCode_Quicken(PyCodeObject *code) #define SPEC_FAIL_CALL_METHOD_WRAPPER 28 #define SPEC_FAIL_CALL_OPERATOR_WRAPPER 29 #define SPEC_FAIL_CALL_INIT_NOT_SIMPLE 30 +#define SPEC_FAIL_CALL_METACLASS 31 /* COMPARE_OP */ #define SPEC_FAIL_COMPARE_OP_DIFFERENT_TYPES 12 @@ -1757,6 +1758,10 @@ specialize_class_call(PyObject *callable, _Py_CODEUNIT *instr, int nargs) SPEC_FAIL_CALL_STR : SPEC_FAIL_CALL_CLASS_NO_VECTORCALL); return -1; } + if (Py_TYPE(tp) != &PyType_Type) { + SPECIALIZATION_FAIL(CALL, SPEC_FAIL_CALL_METACLASS); + return -1; + } if (tp->tp_new == PyBaseObject_Type.tp_new) { PyFunctionObject *init = get_init_for_simple_managed_python_class(tp); if (type_get_version(tp, CALL) == 0) { From 500ede01178a8063bb2a3c664172dffa1b40d7c9 Mon Sep 17 00:00:00 2001 From: Oleg Iarygin <oleg@arhadthedev.net> Date: Thu, 1 Feb 2024 22:57:36 +0300 Subject: [PATCH 084/507] gh-89891: Refer SharedMemory implementation as POSIX (GH-104678) It only uses POSIX API. --- Doc/library/multiprocessing.shared_memory.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/multiprocessing.shared_memory.rst b/Doc/library/multiprocessing.shared_memory.rst index 10d7f061fb759b7..933fd07d62418a8 100644 --- a/Doc/library/multiprocessing.shared_memory.rst +++ b/Doc/library/multiprocessing.shared_memory.rst @@ -23,7 +23,7 @@ processes, a :class:`~multiprocessing.managers.BaseManager` subclass, :class:`~multiprocessing.managers.SharedMemoryManager`, is also provided in the :mod:`multiprocessing.managers` module. -In this module, shared memory refers to "System V style" shared memory blocks +In this module, shared memory refers to "POSIX style" shared memory blocks (though is not necessarily implemented explicitly as such) and does not refer to "distributed shared memory". This style of shared memory permits distinct processes to potentially read and write to a common (or shared) region of From 587d4802034749e2aace9c00b00bd73eccdae1e7 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Thu, 1 Feb 2024 15:29:19 -0500 Subject: [PATCH 085/507] gh-112529: Remove PyGC_Head from object pre-header in free-threaded build (#114564) * gh-112529: Remove PyGC_Head from object pre-header in free-threaded build This avoids allocating space for PyGC_Head in the free-threaded build. The GC implementation for free-threaded CPython does not use the PyGC_Head structure. * The trashcan mechanism uses the `ob_tid` field instead of `_gc_prev` in the free-threaded build. * The GDB libpython.py file now determines the offset of the managed dict field based on whether the running process is a free-threaded build. Those are identified by the `ob_ref_local` field in PyObject. * Fixes `_PySys_GetSizeOf()` which incorrectly incorrectly included the size of `PyGC_Head` in the size of static `PyTypeObject`. --- Include/internal/pycore_object.h | 27 ++++++++++++------- Include/object.h | 5 ++-- Lib/test/test_sys.py | 5 ++-- ...-01-25-18-50-49.gh-issue-112529.IbbApA.rst | 4 +++ Modules/_testinternalcapi.c | 12 ++++++++- Objects/object.c | 13 +++++++-- Python/gc_free_threading.c | 21 +++++++++++---- Python/sysmodule.c | 10 ++++++- Tools/gdb/libpython.py | 15 ++++++++--- 9 files changed, 86 insertions(+), 26 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-25-18-50-49.gh-issue-112529.IbbApA.rst diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index e32ea2f528940ae..34a83ea228e8b10 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -315,16 +315,15 @@ static inline void _PyObject_GC_TRACK( _PyObject_ASSERT_FROM(op, !_PyObject_GC_IS_TRACKED(op), "object already tracked by the garbage collector", filename, lineno, __func__); - +#ifdef Py_GIL_DISABLED + op->ob_gc_bits |= _PyGC_BITS_TRACKED; +#else PyGC_Head *gc = _Py_AS_GC(op); _PyObject_ASSERT_FROM(op, (gc->_gc_prev & _PyGC_PREV_MASK_COLLECTING) == 0, "object is in generation which is garbage collected", filename, lineno, __func__); -#ifdef Py_GIL_DISABLED - op->ob_gc_bits |= _PyGC_BITS_TRACKED; -#else PyInterpreterState *interp = _PyInterpreterState_GET(); PyGC_Head *generation0 = interp->gc.generation0; PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev); @@ -594,8 +593,12 @@ _PyObject_IS_GC(PyObject *obj) static inline size_t _PyType_PreHeaderSize(PyTypeObject *tp) { - return _PyType_IS_GC(tp) * sizeof(PyGC_Head) + - _PyType_HasFeature(tp, Py_TPFLAGS_PREHEADER) * 2 * sizeof(PyObject *); + return ( +#ifndef Py_GIL_DISABLED + _PyType_IS_GC(tp) * sizeof(PyGC_Head) + +#endif + _PyType_HasFeature(tp, Py_TPFLAGS_PREHEADER) * 2 * sizeof(PyObject *) + ); } void _PyObject_GC_Link(PyObject *op); @@ -625,6 +628,14 @@ extern int _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values, PyObject * _PyObject_GetInstanceAttribute(PyObject *obj, PyDictValues *values, PyObject *name); +#ifdef Py_GIL_DISABLED +# define MANAGED_DICT_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-1) +# define MANAGED_WEAKREF_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-2) +#else +# define MANAGED_DICT_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-3) +# define MANAGED_WEAKREF_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-4) +#endif + typedef union { PyObject *dict; /* Use a char* to generate a warning if directly assigning a PyDictValues */ @@ -635,7 +646,7 @@ static inline PyDictOrValues * _PyObject_DictOrValuesPointer(PyObject *obj) { assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT); - return ((PyDictOrValues *)obj)-3; + return (PyDictOrValues *)((char *)obj + MANAGED_DICT_OFFSET); } static inline int @@ -664,8 +675,6 @@ _PyDictOrValues_SetValues(PyDictOrValues *ptr, PyDictValues *values) ptr->values = ((char *)values) - 1; } -#define MANAGED_WEAKREF_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-4) - extern PyObject ** _PyObject_ComputedDictPointer(PyObject *); extern void _PyObject_FreeInstanceAttributes(PyObject *obj); extern int _PyObject_IsInstanceDictEmpty(PyObject *); diff --git a/Include/object.h b/Include/object.h index 568d315d7606c42..05187fe5dc4f20d 100644 --- a/Include/object.h +++ b/Include/object.h @@ -212,8 +212,9 @@ struct _object { struct _PyMutex { uint8_t v; }; struct _object { - // ob_tid stores the thread id (or zero). It is also used by the GC to - // store linked lists and the computed "gc_refs" refcount. + // ob_tid stores the thread id (or zero). It is also used by the GC and the + // trashcan mechanism as a linked list pointer and by the GC to store the + // computed "gc_refs" refcount. uintptr_t ob_tid; uint16_t _padding; struct _PyMutex ob_mutex; // per-object lock diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 6c87dfabad9f0f7..71671a5a984256d 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1392,6 +1392,7 @@ def setUp(self): self.longdigit = sys.int_info.sizeof_digit import _testinternalcapi self.gc_headsize = _testinternalcapi.SIZEOF_PYGC_HEAD + self.managed_pre_header_size = _testinternalcapi.SIZEOF_MANAGED_PRE_HEADER check_sizeof = test.support.check_sizeof @@ -1427,7 +1428,7 @@ class OverflowSizeof(int): def __sizeof__(self): return int(self) self.assertEqual(sys.getsizeof(OverflowSizeof(sys.maxsize)), - sys.maxsize + self.gc_headsize*2) + sys.maxsize + self.gc_headsize + self.managed_pre_header_size) with self.assertRaises(OverflowError): sys.getsizeof(OverflowSizeof(sys.maxsize + 1)) with self.assertRaises(ValueError): @@ -1650,7 +1651,7 @@ def delx(self): del self.__x # type # static type: PyTypeObject fmt = 'P2nPI13Pl4Pn9Pn12PIPc' - s = vsize('2P' + fmt) + s = vsize(fmt) check(int, s) # class s = vsize(fmt + # PyTypeObject diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-25-18-50-49.gh-issue-112529.IbbApA.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-25-18-50-49.gh-issue-112529.IbbApA.rst new file mode 100644 index 000000000000000..2a6d74fb2227023 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-25-18-50-49.gh-issue-112529.IbbApA.rst @@ -0,0 +1,4 @@ +The free-threaded build no longer allocates space for the ``PyGC_Head`` +structure in objects that support cyclic garbage collection. A number of +other fields and data structures are used as replacements, including +``ob_gc_bits``, ``ob_tid``, and mimalloc internal data structures. diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index c4a648a1816392f..0bb739b5398b113 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -1752,8 +1752,18 @@ module_exec(PyObject *module) return 1; } + Py_ssize_t sizeof_gc_head = 0; +#ifndef Py_GIL_DISABLED + sizeof_gc_head = sizeof(PyGC_Head); +#endif + if (PyModule_Add(module, "SIZEOF_PYGC_HEAD", - PyLong_FromSsize_t(sizeof(PyGC_Head))) < 0) { + PyLong_FromSsize_t(sizeof_gc_head)) < 0) { + return 1; + } + + if (PyModule_Add(module, "SIZEOF_MANAGED_PRE_HEADER", + PyLong_FromSsize_t(2 * sizeof(PyObject*))) < 0) { return 1; } diff --git a/Objects/object.c b/Objects/object.c index 587c5528c01345b..bbf7f98ae3daf92 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2671,7 +2671,12 @@ _PyTrash_thread_deposit_object(struct _py_trashcan *trash, PyObject *op) _PyObject_ASSERT(op, _PyObject_IS_GC(op)); _PyObject_ASSERT(op, !_PyObject_GC_IS_TRACKED(op)); _PyObject_ASSERT(op, Py_REFCNT(op) == 0); +#ifdef Py_GIL_DISABLED + _PyObject_ASSERT(op, op->ob_tid == 0); + op->ob_tid = (uintptr_t)trash->delete_later; +#else _PyGCHead_SET_PREV(_Py_AS_GC(op), (PyGC_Head*)trash->delete_later); +#endif trash->delete_later = op; } @@ -2697,8 +2702,12 @@ _PyTrash_thread_destroy_chain(struct _py_trashcan *trash) PyObject *op = trash->delete_later; destructor dealloc = Py_TYPE(op)->tp_dealloc; - trash->delete_later = - (PyObject*) _PyGCHead_PREV(_Py_AS_GC(op)); +#ifdef Py_GIL_DISABLED + trash->delete_later = (PyObject*) op->ob_tid; + op->ob_tid = 0; +#else + trash->delete_later = (PyObject*) _PyGCHead_PREV(_Py_AS_GC(op)); +#endif /* Call the deallocator directly. This used to try to * fool Py_DECREF into calling it indirectly, but diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index a6513a2c4aba2a6..53f927bfa653104 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -25,7 +25,10 @@ typedef struct _gc_runtime_state GCState; // Automatically choose the generation that needs collecting. #define GENERATION_AUTO (-1) -// A linked-list of objects using the `ob_tid` field as the next pointer. +// A linked list of objects using the `ob_tid` field as the next pointer. +// The linked list pointers are distinct from any real thread ids, because the +// thread ids returned by _Py_ThreadId() are also pointers to distinct objects. +// No thread will confuse its own id with a linked list pointer. struct worklist { uintptr_t head; }; @@ -221,7 +224,7 @@ gc_visit_heaps_lock_held(PyInterpreterState *interp, mi_block_visit_fun *visitor struct visitor_args *arg) { // Offset of PyObject header from start of memory block. - Py_ssize_t offset_base = sizeof(PyGC_Head); + Py_ssize_t offset_base = 0; if (_PyMem_DebugEnabled()) { // The debug allocator adds two words at the beginning of each block. offset_base += 2 * sizeof(size_t); @@ -331,8 +334,14 @@ update_refs(const mi_heap_t *heap, const mi_heap_area_t *area, Py_ssize_t refcount = Py_REFCNT(op); _PyObject_ASSERT(op, refcount >= 0); - // Add the actual refcount to ob_tid. + // We repurpose ob_tid to compute "gc_refs", the number of external + // references to the object (i.e., from outside the GC heaps). This means + // that ob_tid is no longer a valid thread id until it is restored by + // scan_heap_visitor(). Until then, we cannot use the standard reference + // counting functions or allow other threads to run Python code. gc_maybe_init_refs(op); + + // Add the actual refcount to ob_tid. gc_add_refs(op, refcount); // Subtract internal references from ob_tid. Objects with ob_tid > 0 @@ -1508,8 +1517,10 @@ gc_alloc(PyTypeObject *tp, size_t basicsize, size_t presize) if (mem == NULL) { return _PyErr_NoMemory(tstate); } - ((PyObject **)mem)[0] = NULL; - ((PyObject **)mem)[1] = NULL; + if (presize) { + ((PyObject **)mem)[0] = NULL; + ((PyObject **)mem)[1] = NULL; + } PyObject *op = (PyObject *)(mem + presize); _PyObject_GC_Link(op); return op; diff --git a/Python/sysmodule.c b/Python/sysmodule.c index f558a00a6916ebc..437d7f8dfc49580 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1878,7 +1878,15 @@ _PySys_GetSizeOf(PyObject *o) return (size_t)-1; } - return (size_t)size + _PyType_PreHeaderSize(Py_TYPE(o)); + size_t presize = 0; + if (!Py_IS_TYPE(o, &PyType_Type) || + PyType_HasFeature((PyTypeObject *)o, Py_TPFLAGS_HEAPTYPE)) + { + /* Add the size of the pre-header if "o" is not a static type */ + presize = _PyType_PreHeaderSize(Py_TYPE(o)); + } + + return (size_t)size + presize; } static PyObject * diff --git a/Tools/gdb/libpython.py b/Tools/gdb/libpython.py index 5ef55524c11be20..483f28b46dfec74 100755 --- a/Tools/gdb/libpython.py +++ b/Tools/gdb/libpython.py @@ -70,6 +70,14 @@ def _type_unsigned_int_ptr(): def _sizeof_void_p(): return gdb.lookup_type('void').pointer().sizeof +def _managed_dict_offset(): + # See pycore_object.h + pyobj = gdb.lookup_type("PyObject") + if any(field.name == "ob_ref_local" for field in pyobj.fields()): + return -1 * _sizeof_void_p() + else: + return -3 * _sizeof_void_p() + Py_TPFLAGS_MANAGED_DICT = (1 << 4) Py_TPFLAGS_HEAPTYPE = (1 << 9) @@ -457,7 +465,7 @@ def get_attr_dict(self): if dictoffset < 0: if int_from_int(typeobj.field('tp_flags')) & Py_TPFLAGS_MANAGED_DICT: assert dictoffset == -1 - dictoffset = -3 * _sizeof_void_p() + dictoffset = _managed_dict_offset() else: type_PyVarObject_ptr = gdb.lookup_type('PyVarObject').pointer() tsize = int_from_int(self._gdbval.cast(type_PyVarObject_ptr)['ob_size']) @@ -485,9 +493,8 @@ def get_keys_values(self): has_values = int_from_int(typeobj.field('tp_flags')) & Py_TPFLAGS_MANAGED_DICT if not has_values: return None - charptrptr_t = _type_char_ptr().pointer() - ptr = self._gdbval.cast(charptrptr_t) - 3 - char_ptr = ptr.dereference() + ptr = self._gdbval.cast(_type_char_ptr()) + _managed_dict_offset() + char_ptr = ptr.cast(_type_char_ptr().pointer()).dereference() if (int(char_ptr) & 1) == 0: return None char_ptr += 1 From 13907968d73b3b602c81e240fb7892a2627974d6 Mon Sep 17 00:00:00 2001 From: Donghee Na <donghee.na@python.org> Date: Fri, 2 Feb 2024 05:53:53 +0900 Subject: [PATCH 086/507] gh-111968: Use per-thread freelists for dict in free-threading (gh-114323) --- Include/internal/pycore_dict.h | 3 +- Include/internal/pycore_dict_state.h | 19 ------ Include/internal/pycore_freelist.h | 13 ++++ Include/internal/pycore_gc.h | 2 +- Include/internal/pycore_interp.h | 2 +- Objects/dictobject.c | 88 ++++++++++++---------------- Objects/floatobject.c | 4 ++ Objects/genobject.c | 4 ++ Objects/listobject.c | 4 ++ Python/context.c | 4 ++ Python/gc_free_threading.c | 2 - Python/gc_gil.c | 2 - Python/pystate.c | 3 + 13 files changed, 75 insertions(+), 75 deletions(-) diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index b4e1f8cf1e320b3..60acd89cf6c34a6 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -9,6 +9,7 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif +#include "pycore_freelist.h" // _PyFreeListState #include "pycore_identifier.h" // _Py_Identifier #include "pycore_object.h" // PyDictOrValues @@ -69,7 +70,7 @@ extern PyObject* _PyDictView_Intersect(PyObject* self, PyObject *other); /* runtime lifecycle */ -extern void _PyDict_Fini(PyInterpreterState *interp); +extern void _PyDict_Fini(PyInterpreterState *state); /* other API */ diff --git a/Include/internal/pycore_dict_state.h b/Include/internal/pycore_dict_state.h index ece0f10ca251707..a6dd63d36e040e6 100644 --- a/Include/internal/pycore_dict_state.h +++ b/Include/internal/pycore_dict_state.h @@ -8,16 +8,6 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif - -#ifndef WITH_FREELISTS -// without freelists -# define PyDict_MAXFREELIST 0 -#endif - -#ifndef PyDict_MAXFREELIST -# define PyDict_MAXFREELIST 80 -#endif - #define DICT_MAX_WATCHERS 8 struct _Py_dict_state { @@ -26,15 +16,6 @@ struct _Py_dict_state { * time that a dictionary is modified. */ uint64_t global_version; uint32_t next_keys_version; - -#if PyDict_MAXFREELIST > 0 - /* Dictionary reuse scheme to save calls to malloc and free */ - PyDictObject *free_list[PyDict_MAXFREELIST]; - PyDictKeysObject *keys_free_list[PyDict_MAXFREELIST]; - int numfree; - int keys_numfree; -#endif - PyDict_WatchCallback watchers[DICT_MAX_WATCHERS]; }; diff --git a/Include/internal/pycore_freelist.h b/Include/internal/pycore_freelist.h index b91d2bc066b783b..82a42300991eccd 100644 --- a/Include/internal/pycore_freelist.h +++ b/Include/internal/pycore_freelist.h @@ -17,6 +17,7 @@ extern "C" { # define PyTuple_NFREELISTS PyTuple_MAXSAVESIZE # define PyTuple_MAXFREELIST 2000 # define PyList_MAXFREELIST 80 +# define PyDict_MAXFREELIST 80 # define PyFloat_MAXFREELIST 100 # define PyContext_MAXFREELIST 255 # define _PyAsyncGen_MAXFREELIST 80 @@ -25,6 +26,7 @@ extern "C" { # define PyTuple_NFREELISTS 0 # define PyTuple_MAXFREELIST 0 # define PyList_MAXFREELIST 0 +# define PyDict_MAXFREELIST 0 # define PyFloat_MAXFREELIST 0 # define PyContext_MAXFREELIST 0 # define _PyAsyncGen_MAXFREELIST 0 @@ -65,6 +67,16 @@ struct _Py_float_state { #endif }; +struct _Py_dict_freelist { +#ifdef WITH_FREELISTS + /* Dictionary reuse scheme to save calls to malloc and free */ + PyDictObject *free_list[PyDict_MAXFREELIST]; + PyDictKeysObject *keys_free_list[PyDict_MAXFREELIST]; + int numfree; + int keys_numfree; +#endif +}; + struct _Py_slice_state { #ifdef WITH_FREELISTS /* Using a cache is very effective since typically only a single slice is @@ -106,6 +118,7 @@ typedef struct _Py_freelist_state { struct _Py_float_state floats; struct _Py_tuple_state tuples; struct _Py_list_state lists; + struct _Py_dict_freelist dicts; struct _Py_slice_state slices; struct _Py_context_state contexts; struct _Py_async_gen_state async_gens; diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h index b362a294a59042a..ca1d9fdf5253b8d 100644 --- a/Include/internal/pycore_gc.h +++ b/Include/internal/pycore_gc.h @@ -267,7 +267,7 @@ extern void _PyTuple_ClearFreeList(_PyFreeListState *state, int is_finalization) extern void _PyFloat_ClearFreeList(_PyFreeListState *state, int is_finalization); extern void _PyList_ClearFreeList(_PyFreeListState *state, int is_finalization); extern void _PySlice_ClearCache(_PyFreeListState *state); -extern void _PyDict_ClearFreeList(PyInterpreterState *interp); +extern void _PyDict_ClearFreeList(_PyFreeListState *state, int is_finalization); extern void _PyAsyncGen_ClearFreeLists(_PyFreeListState *state, int is_finalization); extern void _PyContext_ClearFreeList(_PyFreeListState *state, int is_finalization); extern void _Py_ScheduleGC(PyInterpreterState *interp); diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 04e75940dcb5734..c4732b1534199b4 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -20,6 +20,7 @@ extern "C" { #include "pycore_dtoa.h" // struct _dtoa_state #include "pycore_exceptions.h" // struct _Py_exc_state #include "pycore_floatobject.h" // struct _Py_float_state +#include "pycore_freelist.h" // struct _Py_freelist_state #include "pycore_function.h" // FUNC_MAX_WATCHERS #include "pycore_gc.h" // struct _gc_runtime_state #include "pycore_genobject.h" // struct _Py_async_gen_state @@ -230,7 +231,6 @@ struct _is { struct _dtoa_state dtoa; struct _py_func_state func_state; - struct _Py_tuple_state tuple; struct _Py_dict_state dict_state; struct _Py_exc_state exc_state; diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 23d7e9b5e38a356..e24887b7d781bb5 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -118,6 +118,7 @@ As a consequence of this, split keys have a maximum size of 16. #include "pycore_ceval.h" // _PyEval_GetBuiltin() #include "pycore_code.h" // stats #include "pycore_dict.h" // export _PyDict_SizeOf() +#include "pycore_freelist.h" // _PyFreeListState_GET() #include "pycore_gc.h" // _PyObject_GC_IS_TRACKED() #include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats() #include "pycore_pyerrors.h" // _PyErr_GetRaisedException() @@ -242,40 +243,44 @@ static PyObject* dict_iter(PyObject *dict); #include "clinic/dictobject.c.h" -#if PyDict_MAXFREELIST > 0 -static struct _Py_dict_state * -get_dict_state(PyInterpreterState *interp) +#ifdef WITH_FREELISTS +static struct _Py_dict_freelist * +get_dict_state(void) { - return &interp->dict_state; + _PyFreeListState *state = _PyFreeListState_GET(); + return &state->dicts; } #endif void -_PyDict_ClearFreeList(PyInterpreterState *interp) +_PyDict_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) { -#if PyDict_MAXFREELIST > 0 - struct _Py_dict_state *state = &interp->dict_state; - while (state->numfree) { +#ifdef WITH_FREELISTS + struct _Py_dict_freelist *state = &freelist_state->dicts; + while (state->numfree > 0) { PyDictObject *op = state->free_list[--state->numfree]; assert(PyDict_CheckExact(op)); PyObject_GC_Del(op); } - while (state->keys_numfree) { + while (state->keys_numfree > 0) { PyMem_Free(state->keys_free_list[--state->keys_numfree]); } + if (is_finalization) { + state->numfree = -1; + state->keys_numfree = -1; + } #endif } - void -_PyDict_Fini(PyInterpreterState *interp) +_PyDict_Fini(PyInterpreterState *Py_UNUSED(interp)) { - _PyDict_ClearFreeList(interp); -#if defined(Py_DEBUG) && PyDict_MAXFREELIST > 0 - struct _Py_dict_state *state = &interp->dict_state; - state->numfree = -1; - state->keys_numfree = -1; + // With Py_GIL_DISABLED: + // the freelists for the current thread state have already been cleared. +#ifndef Py_GIL_DISABLED + _PyFreeListState *state = _PyFreeListState_GET(); + _PyDict_ClearFreeList(state, 1); #endif } @@ -290,9 +295,8 @@ unicode_get_hash(PyObject *o) void _PyDict_DebugMallocStats(FILE *out) { -#if PyDict_MAXFREELIST > 0 - PyInterpreterState *interp = _PyInterpreterState_GET(); - struct _Py_dict_state *state = get_dict_state(interp); +#ifdef WITH_FREELISTS + struct _Py_dict_freelist *state = get_dict_state(); _PyDebugAllocatorStats(out, "free PyDictObject", state->numfree, sizeof(PyDictObject)); #endif @@ -300,7 +304,7 @@ _PyDict_DebugMallocStats(FILE *out) #define DK_MASK(dk) (DK_SIZE(dk)-1) -static void free_keys_object(PyInterpreterState *interp, PyDictKeysObject *keys); +static void free_keys_object(PyDictKeysObject *keys); /* PyDictKeysObject has refcounts like PyObject does, so we have the following two functions to mirror what Py_INCREF() and Py_DECREF() do. @@ -348,7 +352,7 @@ dictkeys_decref(PyInterpreterState *interp, PyDictKeysObject *dk) Py_XDECREF(entries[i].me_value); } } - free_keys_object(interp, dk); + free_keys_object(dk); } } @@ -643,12 +647,8 @@ new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode) log2_bytes = log2_size + 2; } -#if PyDict_MAXFREELIST > 0 - struct _Py_dict_state *state = get_dict_state(interp); -#ifdef Py_DEBUG - // new_keys_object() must not be called after _PyDict_Fini() - assert(state->keys_numfree != -1); -#endif +#ifdef WITH_FREELISTS + struct _Py_dict_freelist *state = get_dict_state(); if (log2_size == PyDict_LOG_MINSIZE && unicode && state->keys_numfree > 0) { dk = state->keys_free_list[--state->keys_numfree]; OBJECT_STAT_INC(from_freelist); @@ -680,16 +680,13 @@ new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode) } static void -free_keys_object(PyInterpreterState *interp, PyDictKeysObject *keys) +free_keys_object(PyDictKeysObject *keys) { -#if PyDict_MAXFREELIST > 0 - struct _Py_dict_state *state = get_dict_state(interp); -#ifdef Py_DEBUG - // free_keys_object() must not be called after _PyDict_Fini() - assert(state->keys_numfree != -1); -#endif +#ifdef WITH_FREELISTS + struct _Py_dict_freelist *state = get_dict_state(); if (DK_LOG_SIZE(keys) == PyDict_LOG_MINSIZE && state->keys_numfree < PyDict_MAXFREELIST + && state->keys_numfree >= 0 && DK_IS_UNICODE(keys)) { state->keys_free_list[state->keys_numfree++] = keys; OBJECT_STAT_INC(to_freelist); @@ -730,13 +727,9 @@ new_dict(PyInterpreterState *interp, { PyDictObject *mp; assert(keys != NULL); -#if PyDict_MAXFREELIST > 0 - struct _Py_dict_state *state = get_dict_state(interp); -#ifdef Py_DEBUG - // new_dict() must not be called after _PyDict_Fini() - assert(state->numfree != -1); -#endif - if (state->numfree) { +#ifdef WITH_FREELISTS + struct _Py_dict_freelist *state = get_dict_state(); + if (state->numfree > 0) { mp = state->free_list[--state->numfree]; assert (mp != NULL); assert (Py_IS_TYPE(mp, &PyDict_Type)); @@ -1547,7 +1540,7 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp, #endif assert(oldkeys->dk_kind != DICT_KEYS_SPLIT); assert(oldkeys->dk_refcnt == 1); - free_keys_object(interp, oldkeys); + free_keys_object(oldkeys); } } @@ -2458,13 +2451,10 @@ dict_dealloc(PyObject *self) assert(keys->dk_refcnt == 1 || keys == Py_EMPTY_KEYS); dictkeys_decref(interp, keys); } -#if PyDict_MAXFREELIST > 0 - struct _Py_dict_state *state = get_dict_state(interp); -#ifdef Py_DEBUG - // new_dict() must not be called after _PyDict_Fini() - assert(state->numfree != -1); -#endif - if (state->numfree < PyDict_MAXFREELIST && Py_IS_TYPE(mp, &PyDict_Type)) { +#ifdef WITH_FREELISTS + struct _Py_dict_freelist *state = get_dict_state(); + if (state->numfree < PyDict_MAXFREELIST && state->numfree >=0 && + Py_IS_TYPE(mp, &PyDict_Type)) { state->free_list[state->numfree++] = mp; OBJECT_STAT_INC(to_freelist); } diff --git a/Objects/floatobject.c b/Objects/floatobject.c index b7611d5f96ac3be..c440e0dab0e79fa 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -2013,7 +2013,11 @@ _PyFloat_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) void _PyFloat_Fini(_PyFreeListState *state) { + // With Py_GIL_DISABLED: + // the freelists for the current thread state have already been cleared. +#ifndef Py_GIL_DISABLED _PyFloat_ClearFreeList(state, 1); +#endif } void diff --git a/Objects/genobject.c b/Objects/genobject.c index f47197330fdd801..ab523e46cceaa31 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -1685,7 +1685,11 @@ _PyAsyncGen_ClearFreeLists(_PyFreeListState *freelist_state, int is_finalization void _PyAsyncGen_Fini(_PyFreeListState *state) { + // With Py_GIL_DISABLED: + // the freelists for the current thread state have already been cleared. +#ifndef Py_GIL_DISABLED _PyAsyncGen_ClearFreeLists(state, 1); +#endif } diff --git a/Objects/listobject.c b/Objects/listobject.c index 80a1f1da55b8bc8..da2b9cc32697dda 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -138,7 +138,11 @@ _PyList_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) void _PyList_Fini(_PyFreeListState *state) { + // With Py_GIL_DISABLED: + // the freelists for the current thread state have already been cleared. +#ifndef Py_GIL_DISABLED _PyList_ClearFreeList(state, 1); +#endif } /* Print summary info about the state of the optimized allocator */ diff --git a/Python/context.c b/Python/context.c index 294485e5b407dfe..793dfa2b72c7e3f 100644 --- a/Python/context.c +++ b/Python/context.c @@ -1287,7 +1287,11 @@ _PyContext_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) void _PyContext_Fini(_PyFreeListState *state) { + // With Py_GIL_DISABLED: + // the freelists for the current thread state have already been cleared. +#ifndef Py_GIL_DISABLED _PyContext_ClearFreeList(state, 1); +#endif } diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 53f927bfa653104..8fbcdb15109b76c 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -1676,8 +1676,6 @@ PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void *arg) void _PyGC_ClearAllFreeLists(PyInterpreterState *interp) { - _PyDict_ClearFreeList(interp); - HEAD_LOCK(&_PyRuntime); _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)interp->threads.head; while (tstate != NULL) { diff --git a/Python/gc_gil.c b/Python/gc_gil.c index 04c1c184250c609..4e2aa8f7af746c2 100644 --- a/Python/gc_gil.c +++ b/Python/gc_gil.c @@ -11,8 +11,6 @@ void _PyGC_ClearAllFreeLists(PyInterpreterState *interp) { - _PyDict_ClearFreeList(interp); - _Py_ClearFreeLists(&interp->freelist_state, 0); } diff --git a/Python/pystate.c b/Python/pystate.c index 430121a6a35d7f9..27b6d0573ade3b3 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1461,9 +1461,12 @@ clear_datastack(PyThreadState *tstate) void _Py_ClearFreeLists(_PyFreeListState *state, int is_finalization) { + // In the free-threaded build, freelists are per-PyThreadState and cleared in PyThreadState_Clear() + // In the default build, freelists are per-interpreter and cleared in finalize_interp_types() _PyFloat_ClearFreeList(state, is_finalization); _PyTuple_ClearFreeList(state, is_finalization); _PyList_ClearFreeList(state, is_finalization); + _PyDict_ClearFreeList(state, is_finalization); _PyContext_ClearFreeList(state, is_finalization); _PyAsyncGen_ClearFreeLists(state, is_finalization); _PyObjectStackChunk_ClearFreeList(state, is_finalization); From 618d7256e78da8200f6e2c6235094a1ef885dca4 Mon Sep 17 00:00:00 2001 From: Zachary Ware <zach@python.org> Date: Thu, 1 Feb 2024 17:54:02 -0600 Subject: [PATCH 087/507] gh-111239: Update Windows build to use zlib 1.3.1 (GH-114877) --- .../next/Windows/2024-02-01-14-35-05.gh-issue-111239.SO7SUF.rst | 1 + PCbuild/get_externals.bat | 2 +- PCbuild/python.props | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2024-02-01-14-35-05.gh-issue-111239.SO7SUF.rst diff --git a/Misc/NEWS.d/next/Windows/2024-02-01-14-35-05.gh-issue-111239.SO7SUF.rst b/Misc/NEWS.d/next/Windows/2024-02-01-14-35-05.gh-issue-111239.SO7SUF.rst new file mode 100644 index 000000000000000..ea82c3b941f8026 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-02-01-14-35-05.gh-issue-111239.SO7SUF.rst @@ -0,0 +1 @@ +Update Windows builds to use zlib v1.3.1. diff --git a/PCbuild/get_externals.bat b/PCbuild/get_externals.bat index 3919c0592ec00d0..de73d923d8f4df1 100644 --- a/PCbuild/get_externals.bat +++ b/PCbuild/get_externals.bat @@ -58,7 +58,7 @@ set libraries=%libraries% sqlite-3.44.2.0 if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tcl-core-8.6.13.1 if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tk-8.6.13.1 set libraries=%libraries% xz-5.2.5 -set libraries=%libraries% zlib-1.2.13 +set libraries=%libraries% zlib-1.3.1 for %%e in (%libraries%) do ( if exist "%EXTERNALS_DIR%\%%e" ( diff --git a/PCbuild/python.props b/PCbuild/python.props index e8796081c4eaf37..2cb16693e546b1b 100644 --- a/PCbuild/python.props +++ b/PCbuild/python.props @@ -78,7 +78,7 @@ <opensslOutDir Condition="$(opensslOutDir) == ''">$(ExternalsDir)openssl-bin-3.0.11\$(ArchName)\</opensslOutDir> <opensslIncludeDir Condition="$(opensslIncludeDir) == ''">$(opensslOutDir)include</opensslIncludeDir> <nasmDir Condition="$(nasmDir) == ''">$(ExternalsDir)\nasm-2.11.06\</nasmDir> - <zlibDir Condition="$(zlibDir) == ''">$(ExternalsDir)\zlib-1.2.13\</zlibDir> + <zlibDir Condition="$(zlibDir) == ''">$(ExternalsDir)\zlib-1.3.1\</zlibDir> </PropertyGroup> <PropertyGroup> From 1aec0644447e69e981d582449849761b23702ec8 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Fri, 2 Feb 2024 04:44:01 +0300 Subject: [PATCH 088/507] GH-114849: Set a 60-minute timeout for JIT CI jobs (GH-114850) --- .github/workflows/jit.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml index 22e0bdba53ffd6f..69648d87947ad66 100644 --- a/.github/workflows/jit.yml +++ b/.github/workflows/jit.yml @@ -18,6 +18,7 @@ jobs: jit: name: ${{ matrix.target }} (${{ matrix.debug && 'Debug' || 'Release' }}) runs-on: ${{ matrix.runner }} + timeout-minutes: 60 strategy: fail-fast: false matrix: From 53339a0ef72fcfc15221792b117c4670b07a0b20 Mon Sep 17 00:00:00 2001 From: Michal Kaptur <kaptur.michal@gmail.com> Date: Fri, 2 Feb 2024 11:00:18 +0100 Subject: [PATCH 089/507] Move "format" param doc of shutil.make_archive() on its own paragraph (GH-103829) --- Doc/library/shutil.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index d9ec2cbc47e6114..7a7dd23177e6721 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -586,7 +586,9 @@ provided. They rely on the :mod:`zipfile` and :mod:`tarfile` modules. Create an archive file (such as zip or tar) and return its name. *base_name* is the name of the file to create, including the path, minus - any format-specific extension. *format* is the archive format: one of + any format-specific extension. + + *format* is the archive format: one of "zip" (if the :mod:`zlib` module is available), "tar", "gztar" (if the :mod:`zlib` module is available), "bztar" (if the :mod:`bz2` module is available), or "xztar" (if the :mod:`lzma` module is available). From d25d4ee60cc789a8b9c222859bb720ade1ab2e30 Mon Sep 17 00:00:00 2001 From: Christopher Chavez <chrischavez@gmx.us> Date: Fri, 2 Feb 2024 04:38:43 -0600 Subject: [PATCH 090/507] gh-103820: IDLE: Do not interpret buttons 4/5 as scrolling on non-X11 (GH-103821) Also fix test_mousewheel: do not skip a check which was broken due to incorrect delta on Aqua and XQuartz, and probably not because of `.update_idletasks()`. --- Lib/idlelib/editor.py | 5 +++-- Lib/idlelib/idle_test/test_sidebar.py | 22 ++++++++++++------- Lib/idlelib/tree.py | 10 +++++---- ...-04-25-03-01-23.gh-issue-103820.LCSpza.rst | 2 ++ 4 files changed, 25 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/IDLE/2023-04-25-03-01-23.gh-issue-103820.LCSpza.rst diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 6ad383f460c7ee2..8ee8eba64367a50 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -166,8 +166,9 @@ def __init__(self, flist=None, filename=None, key=None, root=None): text.bind("<3>",self.right_menu_event) text.bind('<MouseWheel>', wheel_event) - text.bind('<Button-4>', wheel_event) - text.bind('<Button-5>', wheel_event) + if text._windowingsystem == 'x11': + text.bind('<Button-4>', wheel_event) + text.bind('<Button-5>', wheel_event) text.bind('<Configure>', self.handle_winconfig) text.bind("<<cut>>", self.cut) text.bind("<<copy>>", self.copy) diff --git a/Lib/idlelib/idle_test/test_sidebar.py b/Lib/idlelib/idle_test/test_sidebar.py index fb52b3a0179553d..605e7a892570d7d 100644 --- a/Lib/idlelib/idle_test/test_sidebar.py +++ b/Lib/idlelib/idle_test/test_sidebar.py @@ -690,16 +690,22 @@ def test_mousewheel(self): last_lineno = get_end_linenumber(text) self.assertIsNotNone(text.dlineinfo(text.index(f'{last_lineno}.0'))) - # Scroll up using the <MouseWheel> event. - # The meaning of delta is platform-dependent. - delta = -1 if sys.platform == 'darwin' else 120 - sidebar.canvas.event_generate('<MouseWheel>', x=0, y=0, delta=delta) + # Delta for <MouseWheel>, whose meaning is platform-dependent. + delta = 1 if sidebar.canvas._windowingsystem == 'aqua' else 120 + + # Scroll up. + if sidebar.canvas._windowingsystem == 'x11': + sidebar.canvas.event_generate('<Button-4>', x=0, y=0) + else: + sidebar.canvas.event_generate('<MouseWheel>', x=0, y=0, delta=delta) yield - if sys.platform != 'darwin': # .update_idletasks() does not work. - self.assertIsNone(text.dlineinfo(text.index(f'{last_lineno}.0'))) + self.assertIsNone(text.dlineinfo(text.index(f'{last_lineno}.0'))) - # Scroll back down using the <Button-5> event. - sidebar.canvas.event_generate('<Button-5>', x=0, y=0) + # Scroll back down. + if sidebar.canvas._windowingsystem == 'x11': + sidebar.canvas.event_generate('<Button-5>', x=0, y=0) + else: + sidebar.canvas.event_generate('<MouseWheel>', x=0, y=0, delta=-delta) yield self.assertIsNotNone(text.dlineinfo(text.index(f'{last_lineno}.0'))) diff --git a/Lib/idlelib/tree.py b/Lib/idlelib/tree.py index 9c2eb47b24aec94..0726d7e23660f64 100644 --- a/Lib/idlelib/tree.py +++ b/Lib/idlelib/tree.py @@ -285,8 +285,9 @@ def drawtext(self): self.label.bind("<1>", self.select_or_edit) self.label.bind("<Double-1>", self.flip) self.label.bind("<MouseWheel>", lambda e: wheel_event(e, self.canvas)) - self.label.bind("<Button-4>", lambda e: wheel_event(e, self.canvas)) - self.label.bind("<Button-5>", lambda e: wheel_event(e, self.canvas)) + if self.label._windowingsystem == 'x11': + self.label.bind("<Button-4>", lambda e: wheel_event(e, self.canvas)) + self.label.bind("<Button-5>", lambda e: wheel_event(e, self.canvas)) self.text_id = id def select_or_edit(self, event=None): @@ -460,8 +461,9 @@ def __init__(self, master, **opts): self.canvas.bind("<Key-Up>", self.unit_up) self.canvas.bind("<Key-Down>", self.unit_down) self.canvas.bind("<MouseWheel>", wheel_event) - self.canvas.bind("<Button-4>", wheel_event) - self.canvas.bind("<Button-5>", wheel_event) + if self.canvas._windowingsystem == 'x11': + self.canvas.bind("<Button-4>", wheel_event) + self.canvas.bind("<Button-5>", wheel_event) #if isinstance(master, Toplevel) or isinstance(master, Tk): self.canvas.bind("<Alt-Key-2>", self.zoom_height) self.canvas.focus_set() diff --git a/Misc/NEWS.d/next/IDLE/2023-04-25-03-01-23.gh-issue-103820.LCSpza.rst b/Misc/NEWS.d/next/IDLE/2023-04-25-03-01-23.gh-issue-103820.LCSpza.rst new file mode 100644 index 000000000000000..b9d7faf047b28e7 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2023-04-25-03-01-23.gh-issue-103820.LCSpza.rst @@ -0,0 +1,2 @@ +Revise IDLE bindings so that events from mouse button 4/5 on non-X11 +windowing systems (i.e. Win32 and Aqua) are not mistaken for scrolling. From 41fde89e471003b3e70fdd76d6726fba9982a1eb Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Fri, 2 Feb 2024 10:41:28 +0000 Subject: [PATCH 091/507] GH-113655 Lower C recursion limit from 4000 to 3000 on Windows. (GH-114896) --- Include/cpython/pystate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 1dbf97660f382fd..9bc8758e72bd8f2 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -229,7 +229,7 @@ struct _ts { #elif defined(__s390x__) # define Py_C_RECURSION_LIMIT 800 #elif defined(_WIN32) -# define Py_C_RECURSION_LIMIT 4000 +# define Py_C_RECURSION_LIMIT 3000 #elif defined(_Py_ADDRESS_SANITIZER) # define Py_C_RECURSION_LIMIT 4000 #else From 2091fb2a85c1aa2d9b22c02736b07831bd875c2a Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Fri, 2 Feb 2024 11:26:31 +0000 Subject: [PATCH 092/507] gh-107901: make compiler inline basic blocks with no line number and no fallthrough (#114750) --- Lib/test/test_compile.py | 62 ++++++++++++++++++++++++------ Lib/test/test_monitoring.py | 10 ++--- Python/flowgraph.c | 75 ++++++++++++++++++++++++++----------- 3 files changed, 108 insertions(+), 39 deletions(-) diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 3b1ceceaa6305f7..ebb479f2de7c633 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -1104,6 +1104,17 @@ async def test(aseq): code_lines = self.get_code_lines(test.__code__) self.assertEqual(expected_lines, code_lines) + def check_line_numbers(self, code, opnames=None): + # Check that all instructions whose op matches opnames + # have a line number. opnames can be a single name, or + # a sequence of names. If it is None, match all ops. + + if isinstance(opnames, str): + opnames = (opnames, ) + for inst in dis.Bytecode(code): + if opnames and inst.opname in opnames: + self.assertIsNotNone(inst.positions.lineno) + def test_line_number_synthetic_jump_multiple_predecessors(self): def f(): for x in it: @@ -1113,25 +1124,52 @@ def f(): except OSError: pass - # Ensure that all JUMP_BACKWARDs have line number - code = f.__code__ - for inst in dis.Bytecode(code): - if inst.opname == 'JUMP_BACKWARD': - self.assertIsNotNone(inst.positions.lineno) + self.check_line_numbers(f.__code__, 'JUMP_BACKWARD') - def test_lineno_of_backward_jump(self): + def test_line_number_synthetic_jump_multiple_predecessors_nested(self): + def f(): + for x in it: + try: + X = 3 + except OSError: + try: + if C3: + X = 4 + except OSError: + pass + return 42 + + self.check_line_numbers(f.__code__, 'JUMP_BACKWARD') + + def test_line_number_synthetic_jump_multiple_predecessors_more_nested(self): + def f(): + for x in it: + try: + X = 3 + except OSError: + try: + if C3: + if C4: + X = 4 + except OSError: + try: + if C3: + if C4: + X = 5 + except OSError: + pass + return 42 + + self.check_line_numbers(f.__code__, 'JUMP_BACKWARD') + + def test_lineno_of_backward_jump_conditional_in_loop(self): # Issue gh-107901 def f(): for i in x: if y: pass - linenos = list(inst.positions.lineno - for inst in dis.get_instructions(f.__code__) - if inst.opname == 'JUMP_BACKWARD') - - self.assertTrue(len(linenos) > 0) - self.assertTrue(all(l is not None for l in linenos)) + self.check_line_numbers(f.__code__, 'JUMP_BACKWARD') def test_big_dict_literal(self): # The compiler has a flushing point in "compiler_dict" that calls compiles diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index a64d1ed79decd83..60b6326bfbad5e9 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -1466,9 +1466,8 @@ def func(): ('branch', 'func', 4, 4), ('line', 'func', 5), ('line', 'meth', 1), - ('jump', 'func', 5, 5), - ('jump', 'func', 5, '[offset=114]'), - ('branch', 'func', '[offset=120]', '[offset=124]'), + ('jump', 'func', 5, '[offset=118]'), + ('branch', 'func', '[offset=122]', '[offset=126]'), ('line', 'get_events', 11)]) self.check_events(func, recorders = FLOW_AND_LINE_RECORDERS, expected = [ @@ -1482,9 +1481,8 @@ def func(): ('line', 'func', 5), ('line', 'meth', 1), ('return', 'meth', None), - ('jump', 'func', 5, 5), - ('jump', 'func', 5, '[offset=114]'), - ('branch', 'func', '[offset=120]', '[offset=124]'), + ('jump', 'func', 5, '[offset=118]'), + ('branch', 'func', '[offset=122]', '[offset=126]'), ('return', 'func', None), ('line', 'get_events', 11)]) diff --git a/Python/flowgraph.c b/Python/flowgraph.c index bfc23a298ff492d..1a648edf0880c02 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -212,14 +212,14 @@ basicblock_add_jump(basicblock *b, int opcode, basicblock *target, location loc) } static inline int -basicblock_append_instructions(basicblock *target, basicblock *source) +basicblock_append_instructions(basicblock *to, basicblock *from) { - for (int i = 0; i < source->b_iused; i++) { - int n = basicblock_next_instr(target); + for (int i = 0; i < from->b_iused; i++) { + int n = basicblock_next_instr(to); if (n < 0) { return ERROR; } - target->b_instr[n] = source->b_instr[i]; + to->b_instr[n] = from->b_instr[i]; } return SUCCESS; } @@ -292,9 +292,9 @@ static void dump_basicblock(const basicblock *b) { const char *b_return = basicblock_returns(b) ? "return " : ""; - fprintf(stderr, "%d: [EH=%d CLD=%d WRM=%d NO_FT=%d %p] used: %d, depth: %d, %s\n", + fprintf(stderr, "%d: [EH=%d CLD=%d WRM=%d NO_FT=%d %p] used: %d, depth: %d, preds: %d %s\n", b->b_label.id, b->b_except_handler, b->b_cold, b->b_warm, BB_NO_FALLTHROUGH(b), b, b->b_iused, - b->b_startdepth, b_return); + b->b_startdepth, b->b_predecessors, b_return); if (b->b_instr) { int i; for (i = 0; i < b->b_iused; i++) { @@ -1165,15 +1165,26 @@ remove_redundant_jumps(cfg_builder *g) { return changes; } +static inline bool +basicblock_has_no_lineno(basicblock *b) { + for (int i = 0; i < b->b_iused; i++) { + if (b->b_instr[i].i_loc.lineno >= 0) { + return false; + } + } + return true; +} + /* Maximum size of basic block that should be copied in optimizer */ #define MAX_COPY_SIZE 4 -/* If this block ends with an unconditional jump to a small exit block, then +/* If this block ends with an unconditional jump to a small exit block or + * a block that has no line numbers (and no fallthrough), then * remove the jump and extend this block with the target. * Returns 1 if extended, 0 if no change, and -1 on error. */ static int -inline_small_exit_blocks(basicblock *bb) { +basicblock_inline_small_or_no_lineno_blocks(basicblock *bb) { cfg_instr *last = basicblock_last_instr(bb); if (last == NULL) { return 0; @@ -1182,14 +1193,46 @@ inline_small_exit_blocks(basicblock *bb) { return 0; } basicblock *target = last->i_target; - if (basicblock_exits_scope(target) && target->b_iused <= MAX_COPY_SIZE) { + bool small_exit_block = (basicblock_exits_scope(target) && + target->b_iused <= MAX_COPY_SIZE); + bool no_lineno_no_fallthrough = (basicblock_has_no_lineno(target) && + !BB_HAS_FALLTHROUGH(target)); + if (small_exit_block || no_lineno_no_fallthrough) { + assert(is_jump(last)); + int removed_jump_opcode = last->i_opcode; INSTR_SET_OP0(last, NOP); RETURN_IF_ERROR(basicblock_append_instructions(bb, target)); + if (no_lineno_no_fallthrough) { + last = basicblock_last_instr(bb); + if (IS_UNCONDITIONAL_JUMP_OPCODE(last->i_opcode) && + removed_jump_opcode == JUMP) + { + /* Make sure we don't lose eval breaker checks */ + last->i_opcode = JUMP; + } + } + target->b_predecessors--; return 1; } return 0; } +static int +inline_small_or_no_lineno_blocks(basicblock *entryblock) { + bool changes; + do { + changes = false; + for (basicblock *b = entryblock; b != NULL; b = b->b_next) { + int res = basicblock_inline_small_or_no_lineno_blocks(b); + RETURN_IF_ERROR(res); + if (res) { + changes = true; + } + } + } while(changes); /* every change removes a jump, ensuring convergence */ + return changes; +} + // Attempt to eliminate jumps to jumps by updating inst to jump to // target->i_target using the provided opcode. Return whether or not the // optimization was successful. @@ -1804,9 +1847,7 @@ optimize_cfg(cfg_builder *g, PyObject *consts, PyObject *const_cache, int firstl { assert(PyDict_CheckExact(const_cache)); RETURN_IF_ERROR(check_cfg(g)); - for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { - RETURN_IF_ERROR(inline_small_exit_blocks(b)); - } + RETURN_IF_ERROR(inline_small_or_no_lineno_blocks(g->g_entryblock)); RETURN_IF_ERROR(remove_unreachable(g->g_entryblock)); RETURN_IF_ERROR(resolve_line_numbers(g, firstlineno)); RETURN_IF_ERROR(optimize_load_const(const_cache, g, consts)); @@ -1814,9 +1855,6 @@ optimize_cfg(cfg_builder *g, PyObject *consts, PyObject *const_cache, int firstl RETURN_IF_ERROR(optimize_basic_block(const_cache, b, consts)); } RETURN_IF_ERROR(remove_redundant_nops_and_pairs(g->g_entryblock)); - for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { - RETURN_IF_ERROR(inline_small_exit_blocks(b)); - } RETURN_IF_ERROR(remove_unreachable(g->g_entryblock)); int removed_nops, removed_jumps; @@ -2333,12 +2371,7 @@ convert_pseudo_ops(cfg_builder *g) static inline bool is_exit_or_eval_check_without_lineno(basicblock *b) { if (basicblock_exits_scope(b) || basicblock_has_eval_break(b)) { - for (int i = 0; i < b->b_iused; i++) { - if (b->b_instr[i].i_loc.lineno >= 0) { - return false; - } - } - return true; + return basicblock_has_no_lineno(b); } else { return false; From 0e71a295e9530c939a5efcb45db23cf31e0303b4 Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Fri, 2 Feb 2024 12:14:34 +0000 Subject: [PATCH 093/507] GH-113710: Add a "globals to constants" pass (GH-114592) Converts specializations of `LOAD_GLOBAL` into constants during tier 2 optimization. --- Include/cpython/dictobject.h | 3 + Include/cpython/optimizer.h | 8 +- Include/internal/pycore_dict.h | 6 +- Include/internal/pycore_dict_state.h | 1 + Include/internal/pycore_interp.h | 2 +- Include/internal/pycore_optimizer.h | 5 +- Include/internal/pycore_uop_ids.h | 8 +- Include/internal/pycore_uop_metadata.h | 8 + Lib/test/test_capi/test_watchers.py | 12 +- Modules/_testcapi/watchers.c | 4 +- Objects/dictobject.c | 3 +- Python/bytecodes.c | 24 +++ Python/executor_cases.c.h | 42 +++++ Python/optimizer.c | 52 +++--- Python/optimizer_analysis.c | 230 ++++++++++++++++++++++++- Python/pylifecycle.c | 22 ++- 16 files changed, 375 insertions(+), 55 deletions(-) diff --git a/Include/cpython/dictobject.h b/Include/cpython/dictobject.h index 944965fb9e5351a..1720fe6f01ea37d 100644 --- a/Include/cpython/dictobject.h +++ b/Include/cpython/dictobject.h @@ -17,6 +17,9 @@ typedef struct { /* Dictionary version: globally unique, value change each time the dictionary is modified */ #ifdef Py_BUILD_CORE + /* Bits 0-7 are for dict watchers. + * Bits 8-11 are for the watched mutation counter (used by tier2 optimization) + * The remaining bits (12-63) are the actual version tag. */ uint64_t ma_version_tag; #else Py_DEPRECATED(3.12) uint64_t ma_version_tag; diff --git a/Include/cpython/optimizer.h b/Include/cpython/optimizer.h index ecf3cae4cbc3f10..5a9ccaea3b22098 100644 --- a/Include/cpython/optimizer.h +++ b/Include/cpython/optimizer.h @@ -47,7 +47,10 @@ typedef struct _PyExecutorObject { typedef struct _PyOptimizerObject _PyOptimizerObject; /* Should return > 0 if a new executor is created. O if no executor is produced and < 0 if an error occurred. */ -typedef int (*optimize_func)(_PyOptimizerObject* self, PyCodeObject *code, _Py_CODEUNIT *instr, _PyExecutorObject **, int curr_stackentries); +typedef int (*optimize_func)( + _PyOptimizerObject* self, struct _PyInterpreterFrame *frame, + _Py_CODEUNIT *instr, _PyExecutorObject **exec_ptr, + int curr_stackentries); typedef struct _PyOptimizerObject { PyObject_HEAD @@ -94,6 +97,9 @@ PyAPI_FUNC(PyObject *)PyUnstable_Optimizer_NewUOpOptimizer(void); /* Minimum of 16 additional executions before retry */ #define MINIMUM_TIER2_BACKOFF 4 +#define _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS 3 +#define _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS 6 + #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index 60acd89cf6c34a6..233da058f464d17 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -207,8 +207,8 @@ static inline PyDictUnicodeEntry* DK_UNICODE_ENTRIES(PyDictKeysObject *dk) { #define DK_IS_UNICODE(dk) ((dk)->dk_kind != DICT_KEYS_GENERAL) -#define DICT_VERSION_INCREMENT (1 << DICT_MAX_WATCHERS) -#define DICT_VERSION_MASK (DICT_VERSION_INCREMENT - 1) +#define DICT_VERSION_INCREMENT (1 << (DICT_MAX_WATCHERS + DICT_WATCHED_MUTATION_BITS)) +#define DICT_WATCHER_MASK ((1 << DICT_MAX_WATCHERS) - 1) #ifdef Py_GIL_DISABLED #define DICT_NEXT_VERSION(INTERP) \ @@ -234,7 +234,7 @@ _PyDict_NotifyEvent(PyInterpreterState *interp, PyObject *value) { assert(Py_REFCNT((PyObject*)mp) > 0); - int watcher_bits = mp->ma_version_tag & DICT_VERSION_MASK; + int watcher_bits = mp->ma_version_tag & DICT_WATCHER_MASK; if (watcher_bits) { _PyDict_SendEvent(watcher_bits, event, mp, key, value); return DICT_NEXT_VERSION(interp) | watcher_bits; diff --git a/Include/internal/pycore_dict_state.h b/Include/internal/pycore_dict_state.h index a6dd63d36e040e6..1a44755c7a01a3a 100644 --- a/Include/internal/pycore_dict_state.h +++ b/Include/internal/pycore_dict_state.h @@ -9,6 +9,7 @@ extern "C" { #endif #define DICT_MAX_WATCHERS 8 +#define DICT_WATCHED_MUTATION_BITS 4 struct _Py_dict_state { /*Global counter used to set ma_version_tag field of dictionary. diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index c4732b1534199b4..f7c332ed747cfac 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -72,7 +72,6 @@ typedef struct _rare_events { uint8_t set_eval_frame_func; /* Modifying the builtins, __builtins__.__dict__[var] = ... */ uint8_t builtin_dict; - int builtins_dict_watcher_id; /* Modifying a function, e.g. func.__defaults__ = ..., etc. */ uint8_t func_modification; } _rare_events; @@ -243,6 +242,7 @@ struct _is { uint16_t optimizer_backedge_threshold; uint32_t next_func_version; _rare_events rare_events; + PyDict_WatchCallback builtins_dict_watcher; _Py_GlobalMonitors monitors; bool sys_profile_initialized; diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 31f30c673f207a1..e21412fc815540d 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -8,8 +8,9 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif -int _Py_uop_analyze_and_optimize(PyCodeObject *code, - _PyUOpInstruction *trace, int trace_len, int curr_stackentries); +int _Py_uop_analyze_and_optimize(_PyInterpreterFrame *frame, + _PyUOpInstruction *trace, int trace_len, int curr_stackentries, + _PyBloomFilter *dependencies); extern PyTypeObject _PyCounterExecutor_Type; extern PyTypeObject _PyCounterOptimizer_Type; diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index a7056586ff04c01..b2476e1c6e5c4b5 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -232,8 +232,12 @@ extern "C" { #define _CHECK_VALIDITY 379 #define _LOAD_CONST_INLINE 380 #define _LOAD_CONST_INLINE_BORROW 381 -#define _INTERNAL_INCREMENT_OPT_COUNTER 382 -#define MAX_UOP_ID 382 +#define _LOAD_CONST_INLINE_WITH_NULL 382 +#define _LOAD_CONST_INLINE_BORROW_WITH_NULL 383 +#define _CHECK_GLOBALS 384 +#define _CHECK_BUILTINS 385 +#define _INTERNAL_INCREMENT_OPT_COUNTER 386 +#define MAX_UOP_ID 386 #ifdef __cplusplus } diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 14d3382e895cdf4..2b5b37e6b8d6a43 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -204,6 +204,10 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_CHECK_VALIDITY] = HAS_DEOPT_FLAG, [_LOAD_CONST_INLINE] = 0, [_LOAD_CONST_INLINE_BORROW] = 0, + [_LOAD_CONST_INLINE_WITH_NULL] = 0, + [_LOAD_CONST_INLINE_BORROW_WITH_NULL] = 0, + [_CHECK_GLOBALS] = HAS_DEOPT_FLAG, + [_CHECK_BUILTINS] = HAS_DEOPT_FLAG, [_INTERNAL_INCREMENT_OPT_COUNTER] = 0, }; @@ -250,10 +254,12 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_CHECK_ATTR_METHOD_LAZY_DICT] = "_CHECK_ATTR_METHOD_LAZY_DICT", [_CHECK_ATTR_MODULE] = "_CHECK_ATTR_MODULE", [_CHECK_ATTR_WITH_HINT] = "_CHECK_ATTR_WITH_HINT", + [_CHECK_BUILTINS] = "_CHECK_BUILTINS", [_CHECK_CALL_BOUND_METHOD_EXACT_ARGS] = "_CHECK_CALL_BOUND_METHOD_EXACT_ARGS", [_CHECK_EG_MATCH] = "_CHECK_EG_MATCH", [_CHECK_EXC_MATCH] = "_CHECK_EXC_MATCH", [_CHECK_FUNCTION_EXACT_ARGS] = "_CHECK_FUNCTION_EXACT_ARGS", + [_CHECK_GLOBALS] = "_CHECK_GLOBALS", [_CHECK_MANAGED_OBJECT_HAS_VALUES] = "_CHECK_MANAGED_OBJECT_HAS_VALUES", [_CHECK_PEP_523] = "_CHECK_PEP_523", [_CHECK_STACK_SPACE] = "_CHECK_STACK_SPACE", @@ -332,6 +338,8 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_LOAD_CONST] = "_LOAD_CONST", [_LOAD_CONST_INLINE] = "_LOAD_CONST_INLINE", [_LOAD_CONST_INLINE_BORROW] = "_LOAD_CONST_INLINE_BORROW", + [_LOAD_CONST_INLINE_BORROW_WITH_NULL] = "_LOAD_CONST_INLINE_BORROW_WITH_NULL", + [_LOAD_CONST_INLINE_WITH_NULL] = "_LOAD_CONST_INLINE_WITH_NULL", [_LOAD_DEREF] = "_LOAD_DEREF", [_LOAD_FAST] = "_LOAD_FAST", [_LOAD_FAST_AND_CLEAR] = "_LOAD_FAST_AND_CLEAR", diff --git a/Lib/test/test_capi/test_watchers.py b/Lib/test/test_capi/test_watchers.py index 5981712c80c3a96..ae062b1bda26b7c 100644 --- a/Lib/test/test_capi/test_watchers.py +++ b/Lib/test/test_capi/test_watchers.py @@ -151,8 +151,8 @@ def test_watch_out_of_range_watcher_id(self): def test_watch_unassigned_watcher_id(self): d = {} - with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 1"): - self.watch(1, d) + with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 3"): + self.watch(3, d) def test_unwatch_non_dict(self): with self.watcher() as wid: @@ -168,8 +168,8 @@ def test_unwatch_out_of_range_watcher_id(self): def test_unwatch_unassigned_watcher_id(self): d = {} - with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 1"): - self.unwatch(1, d) + with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 3"): + self.unwatch(3, d) def test_clear_out_of_range_watcher_id(self): with self.assertRaisesRegex(ValueError, r"Invalid dict watcher ID -1"): @@ -178,8 +178,8 @@ def test_clear_out_of_range_watcher_id(self): self.clear_watcher(8) # DICT_MAX_WATCHERS = 8 def test_clear_unassigned_watcher_id(self): - with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 1"): - self.clear_watcher(1) + with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 3"): + self.clear_watcher(3) class TestTypeWatchers(unittest.TestCase): diff --git a/Modules/_testcapi/watchers.c b/Modules/_testcapi/watchers.c index a763ff46a3c2901..1eb0db2c2e65761 100644 --- a/Modules/_testcapi/watchers.c +++ b/Modules/_testcapi/watchers.c @@ -15,8 +15,8 @@ module _testcapi /*[clinic end generated code: output=da39a3ee5e6b4b0d input=6361033e795369fc]*/ // Test dict watching -static PyObject *g_dict_watch_events; -static int g_dict_watchers_installed; +static PyObject *g_dict_watch_events = NULL; +static int g_dict_watchers_installed = 0; static int dict_watch_callback(PyDict_WatchEvent event, diff --git a/Objects/dictobject.c b/Objects/dictobject.c index e24887b7d781bb5..4bb818b90a4a72d 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -5943,7 +5943,8 @@ PyDict_AddWatcher(PyDict_WatchCallback callback) { PyInterpreterState *interp = _PyInterpreterState_GET(); - for (int i = 0; i < DICT_MAX_WATCHERS; i++) { + /* Start at 2, as 0 and 1 are reserved for CPython */ + for (int i = 2; i < DICT_MAX_WATCHERS; i++) { if (!interp->dict_state.watchers[i]) { interp->dict_state.watchers[i] = callback; return i; diff --git a/Python/bytecodes.c b/Python/bytecodes.c index ebd5b06abb2d4e4..6fb4d719e43991c 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -4071,11 +4071,35 @@ dummy_func( } op(_LOAD_CONST_INLINE, (ptr/4 -- value)) { + TIER_TWO_ONLY value = Py_NewRef(ptr); } op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) { + TIER_TWO_ONLY + value = ptr; + } + + op(_LOAD_CONST_INLINE_WITH_NULL, (ptr/4 -- value, null)) { + TIER_TWO_ONLY + value = Py_NewRef(ptr); + null = NULL; + } + + op(_LOAD_CONST_INLINE_BORROW_WITH_NULL, (ptr/4 -- value, null)) { + TIER_TWO_ONLY value = ptr; + null = NULL; + } + + op(_CHECK_GLOBALS, (dict/4 -- )) { + TIER_TWO_ONLY + DEOPT_IF(GLOBALS() != dict); + } + + op(_CHECK_BUILTINS, (dict/4 -- )) { + TIER_TWO_ONLY + DEOPT_IF(BUILTINS() != dict); } /* Internal -- for testing executors */ diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 241b9056207715d..2d914b82dbf88f4 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -3393,6 +3393,7 @@ case _LOAD_CONST_INLINE: { PyObject *value; PyObject *ptr = (PyObject *)CURRENT_OPERAND(); + TIER_TWO_ONLY value = Py_NewRef(ptr); stack_pointer[0] = value; stack_pointer += 1; @@ -3402,12 +3403,53 @@ case _LOAD_CONST_INLINE_BORROW: { PyObject *value; PyObject *ptr = (PyObject *)CURRENT_OPERAND(); + TIER_TWO_ONLY value = ptr; stack_pointer[0] = value; stack_pointer += 1; break; } + case _LOAD_CONST_INLINE_WITH_NULL: { + PyObject *value; + PyObject *null; + PyObject *ptr = (PyObject *)CURRENT_OPERAND(); + TIER_TWO_ONLY + value = Py_NewRef(ptr); + null = NULL; + stack_pointer[0] = value; + stack_pointer[1] = null; + stack_pointer += 2; + break; + } + + case _LOAD_CONST_INLINE_BORROW_WITH_NULL: { + PyObject *value; + PyObject *null; + PyObject *ptr = (PyObject *)CURRENT_OPERAND(); + TIER_TWO_ONLY + value = ptr; + null = NULL; + stack_pointer[0] = value; + stack_pointer[1] = null; + stack_pointer += 2; + break; + } + + case _CHECK_GLOBALS: { + PyObject *dict = (PyObject *)CURRENT_OPERAND(); + TIER_TWO_ONLY + if (GLOBALS() != dict) goto deoptimize; + break; + } + + case _CHECK_BUILTINS: { + PyObject *dict = (PyObject *)CURRENT_OPERAND(); + TIER_TWO_ONLY + if (BUILTINS() != dict) goto deoptimize; + break; + } + case _INTERNAL_INCREMENT_OPT_COUNTER: { PyObject *opt; opt = stack_pointer[-1]; diff --git a/Python/optimizer.c b/Python/optimizer.c index 0d04b09fef1e846..d71ca0aef0e11ac 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -108,16 +108,14 @@ PyUnstable_Replace_Executor(PyCodeObject *code, _Py_CODEUNIT *instr, _PyExecutor } static int -error_optimize( +never_optimize( _PyOptimizerObject* self, - PyCodeObject *code, + _PyInterpreterFrame *frame, _Py_CODEUNIT *instr, _PyExecutorObject **exec, int Py_UNUSED(stack_entries)) { - assert(0); - PyErr_Format(PyExc_SystemError, "Should never call error_optimize"); - return -1; + return 0; } PyTypeObject _PyDefaultOptimizer_Type = { @@ -130,7 +128,7 @@ PyTypeObject _PyDefaultOptimizer_Type = { _PyOptimizerObject _PyOptimizer_Default = { PyObject_HEAD_INIT(&_PyDefaultOptimizer_Type) - .optimize = error_optimize, + .optimize = never_optimize, .resume_threshold = INT16_MAX, .backedge_threshold = INT16_MAX, }; @@ -174,7 +172,7 @@ _PyOptimizer_Optimize(_PyInterpreterFrame *frame, _Py_CODEUNIT *start, PyObject } _PyOptimizerObject *opt = interp->optimizer; _PyExecutorObject *executor = NULL; - int err = opt->optimize(opt, code, start, &executor, (int)(stack_pointer - _PyFrame_Stackbase(frame))); + int err = opt->optimize(opt, frame, start, &executor, (int)(stack_pointer - _PyFrame_Stackbase(frame))); if (err <= 0) { assert(executor == NULL); return err; @@ -363,7 +361,8 @@ BRANCH_TO_GUARD[4][2] = { ADD_TO_TRACE(_EXIT_TRACE, 0, 0, 0); \ goto done; \ } \ - trace_stack[trace_stack_depth].code = code; \ + assert(func->func_code == (PyObject *)code); \ + trace_stack[trace_stack_depth].func = func; \ trace_stack[trace_stack_depth].instr = instr; \ trace_stack_depth++; #define TRACE_STACK_POP() \ @@ -371,7 +370,8 @@ BRANCH_TO_GUARD[4][2] = { Py_FatalError("Trace stack underflow\n"); \ } \ trace_stack_depth--; \ - code = trace_stack[trace_stack_depth].code; \ + func = trace_stack[trace_stack_depth].func; \ + code = (PyCodeObject *)trace_stack[trace_stack_depth].func->func_code; \ instr = trace_stack[trace_stack_depth].instr; /* Returns 1 on success, @@ -380,20 +380,23 @@ BRANCH_TO_GUARD[4][2] = { */ static int translate_bytecode_to_trace( - PyCodeObject *code, + _PyInterpreterFrame *frame, _Py_CODEUNIT *instr, _PyUOpInstruction *trace, int buffer_size, _PyBloomFilter *dependencies) { bool progress_needed = true; + PyCodeObject *code = (PyCodeObject *)frame->f_executable; + PyFunctionObject *func = (PyFunctionObject *)frame->f_funcobj; + assert(PyFunction_Check(func)); PyCodeObject *initial_code = code; _Py_BloomFilter_Add(dependencies, initial_code); _Py_CODEUNIT *initial_instr = instr; int trace_length = 0; int max_length = buffer_size; struct { - PyCodeObject *code; + PyFunctionObject *func; _Py_CODEUNIT *instr; } trace_stack[TRACE_STACK_SIZE]; int trace_stack_depth = 0; @@ -593,9 +596,9 @@ translate_bytecode_to_trace( ADD_TO_TRACE(uop, oparg, operand, target); if (uop == _POP_FRAME) { TRACE_STACK_POP(); - /* Set the operand to the code object returned to, + /* Set the operand to the function object returned to, * to assist optimization passes */ - trace[trace_length-1].operand = (uintptr_t)code; + trace[trace_length-1].operand = (uintptr_t)func; DPRINTF(2, "Returning to %s (%s:%d) at byte offset %d\n", PyUnicode_AsUTF8(code->co_qualname), @@ -611,10 +614,10 @@ translate_bytecode_to_trace( // Add one to account for the actual opcode/oparg pair: + 1; uint32_t func_version = read_u32(&instr[func_version_offset].cache); - PyFunctionObject *func = _PyFunction_LookupByVersion(func_version); + PyFunctionObject *new_func = _PyFunction_LookupByVersion(func_version); DPRINTF(3, "Function object: %p\n", func); - if (func != NULL) { - PyCodeObject *new_code = (PyCodeObject *)PyFunction_GET_CODE(func); + if (new_func != NULL) { + PyCodeObject *new_code = (PyCodeObject *)PyFunction_GET_CODE(new_func); if (new_code == code) { // Recursive call, bail (we could be here forever). DPRINTF(2, "Bailing on recursive call to %s (%s:%d)\n", @@ -639,8 +642,9 @@ translate_bytecode_to_trace( _Py_BloomFilter_Add(dependencies, new_code); /* Set the operand to the callee's code object, * to assist optimization passes */ - trace[trace_length-1].operand = (uintptr_t)new_code; + trace[trace_length-1].operand = (uintptr_t)new_func; code = new_code; + func = new_func; instr = _PyCode_CODE(code); DPRINTF(2, "Continuing in %s (%s:%d) at byte offset %d\n", @@ -808,7 +812,7 @@ make_executor_from_uops(_PyUOpInstruction *buffer, _PyBloomFilter *dependencies) static int uop_optimize( _PyOptimizerObject *self, - PyCodeObject *code, + _PyInterpreterFrame *frame, _Py_CODEUNIT *instr, _PyExecutorObject **exec_ptr, int curr_stackentries) @@ -816,7 +820,7 @@ uop_optimize( _PyBloomFilter dependencies; _Py_BloomFilter_Init(&dependencies); _PyUOpInstruction buffer[UOP_MAX_TRACE_LENGTH]; - int err = translate_bytecode_to_trace(code, instr, buffer, UOP_MAX_TRACE_LENGTH, &dependencies); + int err = translate_bytecode_to_trace(frame, instr, buffer, UOP_MAX_TRACE_LENGTH, &dependencies); if (err <= 0) { // Error or nothing translated return err; @@ -824,9 +828,10 @@ uop_optimize( OPT_STAT_INC(traces_created); char *uop_optimize = Py_GETENV("PYTHONUOPSOPTIMIZE"); if (uop_optimize == NULL || *uop_optimize > '0') { - err = _Py_uop_analyze_and_optimize(code, buffer, UOP_MAX_TRACE_LENGTH, curr_stackentries); - if (err < 0) { - return -1; + err = _Py_uop_analyze_and_optimize(frame, buffer, + UOP_MAX_TRACE_LENGTH, curr_stackentries, &dependencies); + if (err <= 0) { + return err; } } _PyExecutorObject *executor = make_executor_from_uops(buffer, &dependencies); @@ -887,12 +892,13 @@ PyTypeObject _PyCounterExecutor_Type = { static int counter_optimize( _PyOptimizerObject* self, - PyCodeObject *code, + _PyInterpreterFrame *frame, _Py_CODEUNIT *instr, _PyExecutorObject **exec_ptr, int Py_UNUSED(curr_stackentries) ) { + PyCodeObject *code = (PyCodeObject *)frame->f_executable; int oparg = instr->op.arg; while (instr->op.code == EXTENDED_ARG) { instr++; diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index d1225997e10be2b..2cfbf4b349d0f52 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -1,10 +1,12 @@ #include "Python.h" #include "opcode.h" +#include "pycore_dict.h" #include "pycore_interp.h" #include "pycore_opcode_metadata.h" #include "pycore_opcode_utils.h" #include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_uop_metadata.h" +#include "pycore_dict.h" #include "pycore_long.h" #include "cpython/optimizer.h" #include <stdbool.h> @@ -12,9 +14,210 @@ #include <stddef.h> #include "pycore_optimizer.h" +static int +get_mutations(PyObject* dict) { + assert(PyDict_CheckExact(dict)); + PyDictObject *d = (PyDictObject *)dict; + return (d->ma_version_tag >> DICT_MAX_WATCHERS) & ((1 << DICT_WATCHED_MUTATION_BITS)-1); +} + static void -peephole_opt(PyCodeObject *co, _PyUOpInstruction *buffer, int buffer_size) +increment_mutations(PyObject* dict) { + assert(PyDict_CheckExact(dict)); + PyDictObject *d = (PyDictObject *)dict; + d->ma_version_tag += (1 << DICT_MAX_WATCHERS); +} + +static int +globals_watcher_callback(PyDict_WatchEvent event, PyObject* dict, + PyObject* key, PyObject* new_value) +{ + if (event == PyDict_EVENT_CLONED) { + return 0; + } + uint64_t watched_mutations = get_mutations(dict); + if (watched_mutations < _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS) { + _Py_Executors_InvalidateDependency(_PyInterpreterState_GET(), dict); + increment_mutations(dict); + } + else { + PyDict_Unwatch(1, dict); + } + return 0; +} + + +static void +global_to_const(_PyUOpInstruction *inst, PyObject *obj) +{ + assert(inst->opcode == _LOAD_GLOBAL_MODULE || inst->opcode == _LOAD_GLOBAL_BUILTINS); + assert(PyDict_CheckExact(obj)); + PyDictObject *dict = (PyDictObject *)obj; + assert(dict->ma_keys->dk_kind == DICT_KEYS_UNICODE); + PyDictUnicodeEntry *entries = DK_UNICODE_ENTRIES(dict->ma_keys); + assert(inst->operand <= UINT16_MAX); + PyObject *res = entries[inst->operand].me_value; + if (res == NULL) { + return; + } + if (_Py_IsImmortal(res)) { + inst->opcode = (inst->oparg & 1) ? _LOAD_CONST_INLINE_BORROW_WITH_NULL : _LOAD_CONST_INLINE_BORROW; + } + else { + inst->opcode = (inst->oparg & 1) ? _LOAD_CONST_INLINE_WITH_NULL : _LOAD_CONST_INLINE; + } + inst->operand = (uint64_t)res; +} + +static int +incorrect_keys(_PyUOpInstruction *inst, PyObject *obj) { + if (!PyDict_CheckExact(obj)) { + return 1; + } + PyDictObject *dict = (PyDictObject *)obj; + if (dict->ma_keys->dk_version != inst->operand) { + return 1; + } + return 0; +} + +/* The first two dict watcher IDs are reserved for CPython, + * so we don't need to check that they haven't been used */ +#define BUILTINS_WATCHER_ID 0 +#define GLOBALS_WATCHER_ID 1 + +/* Returns 1 if successfully optimized + * 0 if the trace is not suitable for optimization (yet) + * -1 if there was an error. */ +static int +remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, + int buffer_size, _PyBloomFilter *dependencies) +{ + PyInterpreterState *interp = _PyInterpreterState_GET(); + PyObject *builtins = frame->f_builtins; + if (builtins != interp->builtins) { + return 1; + } + PyObject *globals = frame->f_globals; + assert(PyFunction_Check(((PyFunctionObject *)frame->f_funcobj))); + assert(((PyFunctionObject *)frame->f_funcobj)->func_builtins == builtins); + assert(((PyFunctionObject *)frame->f_funcobj)->func_globals == globals); + /* In order to treat globals as constants, we need to + * know that the globals dict is the one we expected, and + * that it hasn't changed + * In order to treat builtins as constants, we need to + * know that the builtins dict is the one we expected, and + * that it hasn't changed and that the global dictionary's + * keys have not changed */ + + /* These values represent stacks of booleans (one bool per bit). + * Pushing a frame shifts left, popping a frame shifts right. */ + uint32_t builtins_checked = 0; + uint32_t builtins_watched = 0; + uint32_t globals_checked = 0; + uint32_t globals_watched = 0; + if (interp->dict_state.watchers[1] == NULL) { + interp->dict_state.watchers[1] = globals_watcher_callback; + } + for (int pc = 0; pc < buffer_size; pc++) { + _PyUOpInstruction *inst = &buffer[pc]; + int opcode = inst->opcode; + switch(opcode) { + case _GUARD_BUILTINS_VERSION: + if (incorrect_keys(inst, builtins)) { + return 0; + } + if (interp->rare_events.builtin_dict >= _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS) { + continue; + } + if ((builtins_watched & 1) == 0) { + PyDict_Watch(BUILTINS_WATCHER_ID, builtins); + builtins_watched |= 1; + } + if (builtins_checked & 1) { + buffer[pc].opcode = NOP; + } + else { + buffer[pc].opcode = _CHECK_BUILTINS; + buffer[pc].operand = (uintptr_t)builtins; + builtins_checked |= 1; + } + break; + case _GUARD_GLOBALS_VERSION: + if (incorrect_keys(inst, globals)) { + return 0; + } + uint64_t watched_mutations = get_mutations(globals); + if (watched_mutations >= _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS) { + continue; + } + if ((globals_watched & 1) == 0) { + PyDict_Watch(GLOBALS_WATCHER_ID, globals); + _Py_BloomFilter_Add(dependencies, globals); + globals_watched |= 1; + } + if (globals_checked & 1) { + buffer[pc].opcode = NOP; + } + else { + buffer[pc].opcode = _CHECK_GLOBALS; + buffer[pc].operand = (uintptr_t)globals; + globals_checked |= 1; + } + break; + case _LOAD_GLOBAL_BUILTINS: + if (globals_checked & builtins_checked & globals_watched & builtins_watched & 1) { + global_to_const(inst, builtins); + } + break; + case _LOAD_GLOBAL_MODULE: + if (globals_checked & globals_watched & 1) { + global_to_const(inst, globals); + } + break; + case _PUSH_FRAME: + { + globals_checked <<= 1; + globals_watched <<= 1; + builtins_checked <<= 1; + builtins_watched <<= 1; + PyFunctionObject *func = (PyFunctionObject *)buffer[pc].operand; + if (func == NULL) { + return 1; + } + assert(PyFunction_Check(func)); + globals = func->func_globals; + builtins = func->func_builtins; + if (builtins != interp->builtins) { + return 1; + } + break; + } + case _POP_FRAME: + { + globals_checked >>= 1; + globals_watched >>= 1; + builtins_checked >>= 1; + builtins_watched >>= 1; + PyFunctionObject *func = (PyFunctionObject *)buffer[pc].operand; + assert(PyFunction_Check(func)); + globals = func->func_globals; + builtins = func->func_builtins; + break; + } + case _JUMP_TO_TOP: + case _EXIT_TRACE: + return 1; + } + } + return 0; +} + +static void +peephole_opt(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, int buffer_size) +{ + PyCodeObject *co = (PyCodeObject *)frame->f_executable; for (int pc = 0; pc < buffer_size; pc++) { int opcode = buffer[pc].opcode; switch(opcode) { @@ -36,8 +239,17 @@ peephole_opt(PyCodeObject *co, _PyUOpInstruction *buffer, int buffer_size) } case _PUSH_FRAME: case _POP_FRAME: - co = (PyCodeObject *)buffer[pc].operand; + { + PyFunctionObject *func = (PyFunctionObject *)buffer[pc].operand; + if (func == NULL) { + co = NULL; + } + else { + assert(PyFunction_Check(func)); + co = (PyCodeObject *)func->func_code; + } break; + } case _JUMP_TO_TOP: case _EXIT_TRACE: return; @@ -83,16 +295,20 @@ remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size) } } - int _Py_uop_analyze_and_optimize( - PyCodeObject *co, + _PyInterpreterFrame *frame, _PyUOpInstruction *buffer, int buffer_size, - int curr_stacklen + int curr_stacklen, + _PyBloomFilter *dependencies ) { - peephole_opt(co, buffer, buffer_size); + int err = remove_globals(frame, buffer, buffer_size, dependencies); + if (err <= 0) { + return err; + } + peephole_opt(frame, buffer, buffer_size); remove_unneeded_uops(buffer, buffer_size); - return 0; + return 1; } diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 372f60602375b65..0cac7109340129c 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -32,6 +32,7 @@ #include "pycore_typevarobject.h" // _Py_clear_generic_types() #include "pycore_unicodeobject.h" // _PyUnicode_InitTypes() #include "pycore_weakref.h" // _PyWeakref_GET_REF() +#include "cpython/optimizer.h" // _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS #include "pycore_obmalloc.h" // _PyMem_init_obmalloc() #include "opcode.h" @@ -609,7 +610,11 @@ init_interp_create_gil(PyThreadState *tstate, int gil) static int builtins_dict_watcher(PyDict_WatchEvent event, PyObject *dict, PyObject *key, PyObject *new_value) { - RARE_EVENT_INC(builtin_dict); + PyInterpreterState *interp = _PyInterpreterState_GET(); + if (event != PyDict_EVENT_CLONED && interp->rare_events.builtin_dict < _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS) { + _Py_Executors_InvalidateAll(interp); + } + RARE_EVENT_INTERP_INC(interp, builtin_dict); return 0; } @@ -1287,11 +1292,9 @@ init_interp_main(PyThreadState *tstate) } } - if ((interp->rare_events.builtins_dict_watcher_id = PyDict_AddWatcher(&builtins_dict_watcher)) == -1) { - return _PyStatus_ERR("failed to add builtin dict watcher"); - } - if (PyDict_Watch(interp->rare_events.builtins_dict_watcher_id, interp->builtins) != 0) { + interp->dict_state.watchers[0] = &builtins_dict_watcher; + if (PyDict_Watch(0, interp->builtins) != 0) { return _PyStatus_ERR("failed to set builtin dict watcher"); } @@ -1622,8 +1625,13 @@ finalize_modules(PyThreadState *tstate) { PyInterpreterState *interp = tstate->interp; - // Stop collecting stats on __builtin__ modifications during teardown - PyDict_Unwatch(interp->rare_events.builtins_dict_watcher_id, interp->builtins); + // Invalidate all executors and turn off tier 2 optimizer + _Py_Executors_InvalidateAll(interp); + Py_XDECREF(interp->optimizer); + interp->optimizer = &_PyOptimizer_Default; + + // Stop watching __builtin__ modifications + PyDict_Unwatch(0, interp->builtins); PyObject *modules = _PyImport_GetModules(interp); if (modules == NULL) { From d0f1307580a69372611d27b04bbf2551dc85a1ef Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Fri, 2 Feb 2024 08:03:15 -0500 Subject: [PATCH 094/507] gh-114329: Add `PyList_GetItemRef` function (GH-114504) The new `PyList_GetItemRef` is similar to `PyList_GetItem`, but returns a strong reference instead of a borrowed reference. Additionally, if the passed "list" object is not a list, the function sets a `TypeError` instead of calling `PyErr_BadInternalCall()`. --- Doc/c-api/list.rst | 12 ++++++++-- Doc/data/refcounts.dat | 4 ++++ Doc/data/stable_abi.dat | 1 + Doc/whatsnew/3.13.rst | 4 ++++ Include/listobject.h | 1 + Lib/test/test_capi/test_list.py | 22 +++++++++++-------- Lib/test/test_stable_abi_ctypes.py | 1 + ...-01-23-21-45-02.gh-issue-114329.YRaBoe.rst | 3 +++ Misc/stable_abi.toml | 2 ++ Modules/_testcapi/list.c | 13 +++++++++++ Objects/listobject.c | 15 +++++++++++++ PC/python3dll.c | 1 + 12 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2024-01-23-21-45-02.gh-issue-114329.YRaBoe.rst diff --git a/Doc/c-api/list.rst b/Doc/c-api/list.rst index c8b64bad702f508..53eb54d3e1021a3 100644 --- a/Doc/c-api/list.rst +++ b/Doc/c-api/list.rst @@ -56,13 +56,21 @@ List Objects Similar to :c:func:`PyList_Size`, but without error checking. -.. c:function:: PyObject* PyList_GetItem(PyObject *list, Py_ssize_t index) +.. c:function:: PyObject* PyList_GetItemRef(PyObject *list, Py_ssize_t index) Return the object at position *index* in the list pointed to by *list*. The position must be non-negative; indexing from the end of the list is not - supported. If *index* is out of bounds (<0 or >=len(list)), + supported. If *index* is out of bounds (:code:`<0 or >=len(list)`), return ``NULL`` and set an :exc:`IndexError` exception. + .. versionadded:: 3.13 + + +.. c:function:: PyObject* PyList_GetItem(PyObject *list, Py_ssize_t index) + + Like :c:func:`PyList_GetItemRef`, but returns a + :term:`borrowed reference` instead of a :term:`strong reference`. + .. c:function:: PyObject* PyList_GET_ITEM(PyObject *list, Py_ssize_t i) diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat index f719ce153b239a9..62a96146d605ff7 100644 --- a/Doc/data/refcounts.dat +++ b/Doc/data/refcounts.dat @@ -1133,6 +1133,10 @@ PyList_GetItem:PyObject*::0: PyList_GetItem:PyObject*:list:0: PyList_GetItem:Py_ssize_t:index:: +PyList_GetItemRef:PyObject*::+1: +PyList_GetItemRef:PyObject*:list:0: +PyList_GetItemRef:Py_ssize_t:index:: + PyList_GetSlice:PyObject*::+1: PyList_GetSlice:PyObject*:list:0: PyList_GetSlice:Py_ssize_t:low:: diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index da28a2be60bc1bc..def1903204add7a 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -336,6 +336,7 @@ var,PyListRevIter_Type,3.2,, function,PyList_Append,3.2,, function,PyList_AsTuple,3.2,, function,PyList_GetItem,3.2,, +function,PyList_GetItemRef,3.13,, function,PyList_GetSlice,3.2,, function,PyList_Insert,3.2,, function,PyList_New,3.2,, diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 887c3009f885041..f17c6ec0775beff 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1376,6 +1376,10 @@ New Features UTF-8 encoded bytes string, rather than a :c:expr:`PyObject*`. (Contributed by Victor Stinner in :gh:`108314`.) +* Added :c:func:`PyList_GetItemRef` function: similar to + :c:func:`PyList_GetItem` but returns a :term:`strong reference` instead of + a :term:`borrowed reference`. + * Add :c:func:`Py_IsFinalizing` function: check if the main Python interpreter is :term:`shutting down <interpreter shutdown>`. (Contributed by Victor Stinner in :gh:`108014`.) diff --git a/Include/listobject.h b/Include/listobject.h index 6b7041ba0b05d59..4e4084b43483a2d 100644 --- a/Include/listobject.h +++ b/Include/listobject.h @@ -29,6 +29,7 @@ PyAPI_FUNC(PyObject *) PyList_New(Py_ssize_t size); PyAPI_FUNC(Py_ssize_t) PyList_Size(PyObject *); PyAPI_FUNC(PyObject *) PyList_GetItem(PyObject *, Py_ssize_t); +PyAPI_FUNC(PyObject *) PyList_GetItemRef(PyObject *, Py_ssize_t); PyAPI_FUNC(int) PyList_SetItem(PyObject *, Py_ssize_t, PyObject *); PyAPI_FUNC(int) PyList_Insert(PyObject *, Py_ssize_t, PyObject *); PyAPI_FUNC(int) PyList_Append(PyObject *, PyObject *); diff --git a/Lib/test/test_capi/test_list.py b/Lib/test/test_capi/test_list.py index eb03d51d3def378..dceb4fce3c077bf 100644 --- a/Lib/test/test_capi/test_list.py +++ b/Lib/test/test_capi/test_list.py @@ -82,10 +82,8 @@ def test_list_get_size(self): # CRASHES size(UserList()) # CRASHES size(NULL) - - def test_list_getitem(self): - # Test PyList_GetItem() - getitem = _testcapi.list_getitem + def check_list_get_item(self, getitem, exctype): + # Common test cases for PyList_GetItem() and PyList_GetItemRef() lst = [1, 2, 3] self.assertEqual(getitem(lst, 0), 1) self.assertEqual(getitem(lst, 2), 3) @@ -93,12 +91,19 @@ def test_list_getitem(self): self.assertRaises(IndexError, getitem, lst, -1) self.assertRaises(IndexError, getitem, lst, PY_SSIZE_T_MIN) self.assertRaises(IndexError, getitem, lst, PY_SSIZE_T_MAX) - self.assertRaises(SystemError, getitem, 42, 1) - self.assertRaises(SystemError, getitem, (1, 2, 3), 1) - self.assertRaises(SystemError, getitem, {1: 2}, 1) - + self.assertRaises(exctype, getitem, 42, 1) + self.assertRaises(exctype, getitem, (1, 2, 3), 1) + self.assertRaises(exctype, getitem, {1: 2}, 1) # CRASHES getitem(NULL, 1) + def test_list_getitem(self): + # Test PyList_GetItem() + self.check_list_get_item(_testcapi.list_getitem, SystemError) + + def test_list_get_item_ref(self): + # Test PyList_GetItemRef() + self.check_list_get_item(_testcapi.list_get_item_ref, TypeError) + def test_list_get_item(self): # Test PyList_GET_ITEM() get_item = _testcapi.list_get_item @@ -112,7 +117,6 @@ def test_list_get_item(self): # CRASHES get_item(21, 2) # CRASHES get_item(NULL, 1) - def test_list_setitem(self): # Test PyList_SetItem() setitem = _testcapi.list_setitem diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 054e7f0feb1a19d..8bd373976426ef3 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -372,6 +372,7 @@ def test_windows_feature_macros(self): "PyList_Append", "PyList_AsTuple", "PyList_GetItem", + "PyList_GetItemRef", "PyList_GetSlice", "PyList_Insert", "PyList_New", diff --git a/Misc/NEWS.d/next/C API/2024-01-23-21-45-02.gh-issue-114329.YRaBoe.rst b/Misc/NEWS.d/next/C API/2024-01-23-21-45-02.gh-issue-114329.YRaBoe.rst new file mode 100644 index 000000000000000..62d4ce0cfb8de54 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-01-23-21-45-02.gh-issue-114329.YRaBoe.rst @@ -0,0 +1,3 @@ +Add :c:func:`PyList_GetItemRef`, which is similar to +:c:func:`PyList_GetItem` but returns a :term:`strong reference` instead of a +:term:`borrowed reference`. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index ae19d25809ec867..a9875f6ffd1a56a 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2487,3 +2487,5 @@ abi_only = true [data.PyExc_IncompleteInputError] added = '3.13' +[function.PyList_GetItemRef] + added = '3.13' diff --git a/Modules/_testcapi/list.c b/Modules/_testcapi/list.c index 10e18699f01bc1f..2cb6499e28336df 100644 --- a/Modules/_testcapi/list.c +++ b/Modules/_testcapi/list.c @@ -59,6 +59,18 @@ list_get_item(PyObject *Py_UNUSED(module), PyObject *args) return Py_XNewRef(PyList_GET_ITEM(obj, i)); } +static PyObject * +list_get_item_ref(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *obj; + Py_ssize_t i; + if (!PyArg_ParseTuple(args, "On", &obj, &i)) { + return NULL; + } + NULLABLE(obj); + return PyList_GetItemRef(obj, i); +} + static PyObject * list_setitem(PyObject *Py_UNUSED(module), PyObject *args) { @@ -191,6 +203,7 @@ static PyMethodDef test_methods[] = { {"list_get_size", list_get_size, METH_O}, {"list_getitem", list_getitem, METH_VARARGS}, {"list_get_item", list_get_item, METH_VARARGS}, + {"list_get_item_ref", list_get_item_ref, METH_VARARGS}, {"list_setitem", list_setitem, METH_VARARGS}, {"list_set_item", list_set_item, METH_VARARGS}, {"list_insert", list_insert, METH_VARARGS}, diff --git a/Objects/listobject.c b/Objects/listobject.c index da2b9cc32697dda..82a4ba952de07dc 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -257,6 +257,21 @@ PyList_GetItem(PyObject *op, Py_ssize_t i) return ((PyListObject *)op) -> ob_item[i]; } +PyObject * +PyList_GetItemRef(PyObject *op, Py_ssize_t i) +{ + if (!PyList_Check(op)) { + PyErr_SetString(PyExc_TypeError, "expected a list"); + return NULL; + } + if (!valid_index(i, Py_SIZE(op))) { + _Py_DECLARE_STR(list_err, "list index out of range"); + PyErr_SetObject(PyExc_IndexError, &_Py_STR(list_err)); + return NULL; + } + return Py_NewRef(PyList_GET_ITEM(op, i)); +} + int PyList_SetItem(PyObject *op, Py_ssize_t i, PyObject *newitem) diff --git a/PC/python3dll.c b/PC/python3dll.c index 09ecf98fe56ea62..aa6bfe2c4022db0 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -324,6 +324,7 @@ EXPORT_FUNC(PyIter_Send) EXPORT_FUNC(PyList_Append) EXPORT_FUNC(PyList_AsTuple) EXPORT_FUNC(PyList_GetItem) +EXPORT_FUNC(PyList_GetItemRef) EXPORT_FUNC(PyList_GetSlice) EXPORT_FUNC(PyList_Insert) EXPORT_FUNC(PyList_New) From d29f57f6036353b4e705a42637177442bf7e07e5 Mon Sep 17 00:00:00 2001 From: Justin Williams <97240811+juswil@users.noreply.github.com> Date: Fri, 2 Feb 2024 08:32:46 -0500 Subject: [PATCH 095/507] gh-103360: Add link in stdtypes.rst to escape sequences in lexical_analysis.rst (GH-103638) --- Doc/library/stdtypes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 9028ff5c134fa92..1a4c12590c1018d 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -1528,7 +1528,7 @@ between them will be implicitly converted to a single string literal. That is, ``("spam " "eggs") == "spam eggs"``. See :ref:`strings` for more about the various forms of string literal, -including supported escape sequences, and the ``r`` ("raw") prefix that +including supported :ref:`escape sequences <escape-sequences>`, and the ``r`` ("raw") prefix that disables most escape sequence processing. Strings may also be created from other objects using the :class:`str` From b3f0b698daf2438a6e59d5d19ccb34acdba0bffc Mon Sep 17 00:00:00 2001 From: Andrew Rogers <32688592+adr26@users.noreply.github.com> Date: Fri, 2 Feb 2024 13:50:51 +0000 Subject: [PATCH 096/507] gh-104530: Enable native Win32 condition variables by default (GH-104531) --- Include/internal/pycore_condvar.h | 10 ++++---- ...-05-16-06-52-34.gh-issue-104530.mJnA0W.rst | 1 + Python/ceval_gil.c | 8 +++++++ Python/condvar.h | 23 +++++++++++-------- Python/pystate.c | 12 +++++++++- Python/thread_nt.h | 22 ++---------------- 6 files changed, 41 insertions(+), 35 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-05-16-06-52-34.gh-issue-104530.mJnA0W.rst diff --git a/Include/internal/pycore_condvar.h b/Include/internal/pycore_condvar.h index 34c21aaad43197b..ee9533484e80488 100644 --- a/Include/internal/pycore_condvar.h +++ b/Include/internal/pycore_condvar.h @@ -35,14 +35,14 @@ #include <windows.h> // CRITICAL_SECTION /* options */ -/* non-emulated condition variables are provided for those that want - * to target Windows Vista. Modify this macro to enable them. +/* emulated condition variables are provided for those that want + * to target Windows XP or earlier. Modify this macro to enable them. */ #ifndef _PY_EMULATED_WIN_CV -#define _PY_EMULATED_WIN_CV 1 /* use emulated condition variables */ +#define _PY_EMULATED_WIN_CV 0 /* use non-emulated condition variables */ #endif -/* fall back to emulation if not targeting Vista */ +/* fall back to emulation if targeting earlier than Vista */ #if !defined NTDDI_VISTA || NTDDI_VERSION < NTDDI_VISTA #undef _PY_EMULATED_WIN_CV #define _PY_EMULATED_WIN_CV 1 @@ -77,7 +77,7 @@ typedef struct _PyCOND_T #else /* !_PY_EMULATED_WIN_CV */ -/* Use native Win7 primitives if build target is Win7 or higher */ +/* Use native Windows primitives if build target is Vista or higher */ /* SRWLOCK is faster and better than CriticalSection */ typedef SRWLOCK PyMUTEX_T; diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-05-16-06-52-34.gh-issue-104530.mJnA0W.rst b/Misc/NEWS.d/next/Core and Builtins/2023-05-16-06-52-34.gh-issue-104530.mJnA0W.rst new file mode 100644 index 000000000000000..8643a25ae51b13f --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-05-16-06-52-34.gh-issue-104530.mJnA0W.rst @@ -0,0 +1 @@ +Use native Win32 condition variables. diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c index f3b169241535f37..ad90359318761a5 100644 --- a/Python/ceval_gil.c +++ b/Python/ceval_gil.c @@ -610,8 +610,16 @@ PyEval_SaveThread(void) void PyEval_RestoreThread(PyThreadState *tstate) { +#ifdef MS_WINDOWS + int err = GetLastError(); +#endif + _Py_EnsureTstateNotNULL(tstate); _PyThreadState_Attach(tstate); + +#ifdef MS_WINDOWS + SetLastError(err); +#endif } diff --git a/Python/condvar.h b/Python/condvar.h index d54db94f2c871d4..dcabed6d55928ce 100644 --- a/Python/condvar.h +++ b/Python/condvar.h @@ -260,13 +260,13 @@ PyMUTEX_UNLOCK(PyMUTEX_T *cs) return 0; } - Py_LOCAL_INLINE(int) PyCOND_INIT(PyCOND_T *cv) { InitializeConditionVariable(cv); return 0; } + Py_LOCAL_INLINE(int) PyCOND_FINI(PyCOND_T *cv) { @@ -279,27 +279,32 @@ PyCOND_WAIT(PyCOND_T *cv, PyMUTEX_T *cs) return SleepConditionVariableSRW(cv, cs, INFINITE, 0) ? 0 : -1; } -/* This implementation makes no distinction about timeouts. Signal - * 2 to indicate that we don't know. - */ +/* return 0 for success, 1 on timeout, -1 on error */ Py_LOCAL_INLINE(int) PyCOND_TIMEDWAIT(PyCOND_T *cv, PyMUTEX_T *cs, long long us) { - return SleepConditionVariableSRW(cv, cs, (DWORD)(us/1000), 0) ? 2 : -1; + BOOL success = SleepConditionVariableSRW(cv, cs, (DWORD)(us/1000), 0); + if (!success) { + if (GetLastError() == ERROR_TIMEOUT) { + return 1; + } + return -1; + } + return 0; } Py_LOCAL_INLINE(int) PyCOND_SIGNAL(PyCOND_T *cv) { - WakeConditionVariable(cv); - return 0; + WakeConditionVariable(cv); + return 0; } Py_LOCAL_INLINE(int) PyCOND_BROADCAST(PyCOND_T *cv) { - WakeAllConditionVariable(cv); - return 0; + WakeAllConditionVariable(cv); + return 0; } diff --git a/Python/pystate.c b/Python/pystate.c index 27b6d0573ade3b3..7836c172bbfb618 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2488,7 +2488,17 @@ PyGILState_Check(void) return 0; } - return (tstate == gilstate_tss_get(runtime)); +#ifdef MS_WINDOWS + int err = GetLastError(); +#endif + + PyThreadState *tcur = gilstate_tss_get(runtime); + +#ifdef MS_WINDOWS + SetLastError(err); +#endif + + return (tstate == tcur); } PyGILState_STATE diff --git a/Python/thread_nt.h b/Python/thread_nt.h index 14b9cddc24c0ec3..044e9fa111e9797 100644 --- a/Python/thread_nt.h +++ b/Python/thread_nt.h @@ -444,16 +444,7 @@ PyThread_set_key_value(int key, void *value) void * PyThread_get_key_value(int key) { - /* because TLS is used in the Py_END_ALLOW_THREAD macro, - * it is necessary to preserve the windows error state, because - * it is assumed to be preserved across the call to the macro. - * Ideally, the macro should be fixed, but it is simpler to - * do it here. - */ - DWORD error = GetLastError(); - void *result = TlsGetValue(key); - SetLastError(error); - return result; + return TlsGetValue(key); } void @@ -525,14 +516,5 @@ void * PyThread_tss_get(Py_tss_t *key) { assert(key != NULL); - /* because TSS is used in the Py_END_ALLOW_THREAD macro, - * it is necessary to preserve the windows error state, because - * it is assumed to be preserved across the call to the macro. - * Ideally, the macro should be fixed, but it is simpler to - * do it here. - */ - DWORD error = GetLastError(); - void *result = TlsGetValue(key->_key); - SetLastError(error); - return result; + return TlsGetValue(key->_key); } From ee66c333493105e014678be118850e138e3c62a8 Mon Sep 17 00:00:00 2001 From: Steven Ward <planet36@users.noreply.github.com> Date: Fri, 2 Feb 2024 10:13:00 -0500 Subject: [PATCH 097/507] gh-114909: Add --first-weekday option to usage message (#114910) --- Doc/library/calendar.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/library/calendar.rst b/Doc/library/calendar.rst index c4dcf5641d60663..e699a7284ac8023 100644 --- a/Doc/library/calendar.rst +++ b/Doc/library/calendar.rst @@ -512,7 +512,7 @@ to interactively print a calendar. python -m calendar [-h] [-L LOCALE] [-e ENCODING] [-t {text,html}] [-w WIDTH] [-l LINES] [-s SPACING] [-m MONTHS] [-c CSS] - [year] [month] + [-f FIRST_WEEKDAY] [year] [month] For example, to print a calendar for the year 2000: @@ -586,12 +586,13 @@ The following options are accepted: or as an HTML document. -.. option:: --first-weekday WEEKDAY, -f WEEKDAY +.. option:: --first-weekday FIRST_WEEKDAY, -f FIRST_WEEKDAY The weekday to start each week. Must be a number between 0 (Monday) and 6 (Sunday). Defaults to 0. + .. versionadded:: 3.13 .. option:: year From 9872855a31720f514b84373848b49fca09d66ecd Mon Sep 17 00:00:00 2001 From: patenaud <33957588+patenaud@users.noreply.github.com> Date: Fri, 2 Feb 2024 11:31:55 -0500 Subject: [PATCH 098/507] GH-69695: Update ``PyImport_ImportModule`` description (GH-103836) Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/c-api/import.rst | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/Doc/c-api/import.rst b/Doc/c-api/import.rst index 51c20b202f091c5..7c74e9e88678dc0 100644 --- a/Doc/c-api/import.rst +++ b/Doc/c-api/import.rst @@ -13,20 +13,8 @@ Importing Modules single: __all__ (package variable) single: modules (in module sys) - This is a simplified interface to :c:func:`PyImport_ImportModuleEx` below, - leaving the *globals* and *locals* arguments set to ``NULL`` and *level* set - to 0. When the *name* - argument contains a dot (when it specifies a submodule of a package), the - *fromlist* argument is set to the list ``['*']`` so that the return value is the - named module rather than the top-level package containing it as would otherwise - be the case. (Unfortunately, this has an additional side effect when *name* in - fact specifies a subpackage instead of a submodule: the submodules specified in - the package's ``__all__`` variable are loaded.) Return a new reference to the - imported module, or ``NULL`` with an exception set on failure. A failing - import of a module doesn't leave the module in :data:`sys.modules`. - - This function always uses absolute imports. - + This is a wrapper around :c:func:`PyImport_Import()` which takes a + :c:expr:`const char *` as an argument instead of a :c:expr:`PyObject *`. .. c:function:: PyObject* PyImport_ImportModuleNoBlock(const char *name) From c12240ed28aac6494750e00143bc550c4d6d8ad1 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Fri, 2 Feb 2024 20:53:24 +0200 Subject: [PATCH 099/507] gh-114728: Fix documentation for comparison of objects in datetime module (GH-114749) --- Doc/library/datetime.rst | 118 +++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 68 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index db9a92ae4111e35..096670634ee9569 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -400,30 +400,7 @@ objects (see below). the :func:`divmod` function. True division and multiplication of a :class:`timedelta` object by a :class:`float` object are now supported. - -Comparisons of :class:`timedelta` objects are supported, with some caveats. - -The comparisons ``==`` or ``!=`` *always* return a :class:`bool`, no matter -the type of the compared object:: - - >>> from datetime import timedelta - >>> delta1 = timedelta(seconds=57) - >>> delta2 = timedelta(hours=25, seconds=2) - >>> delta2 != delta1 - True - >>> delta2 == 5 - False - -For all other comparisons (such as ``<`` and ``>``), when a :class:`timedelta` -object is compared to an object of a different type, :exc:`TypeError` -is raised:: - - >>> delta2 > delta1 - True - >>> delta2 > 5 - Traceback (most recent call last): - File "<stdin>", line 1, in <module> - TypeError: '>' not supported between instances of 'datetime.timedelta' and 'int' +:class:`timedelta` objects support equality and order comparisons. In Boolean contexts, a :class:`timedelta` object is considered to be true if and only if it isn't equal to ``timedelta(0)``. @@ -614,8 +591,13 @@ Supported operations: +-------------------------------+----------------------------------------------+ | ``timedelta = date1 - date2`` | \(3) | +-------------------------------+----------------------------------------------+ -| ``date1 < date2`` | *date1* is considered less than *date2* when | -| | *date1* precedes *date2* in time. (4) | +| | ``date1 == date2`` | Equality comparison. (4) | +| | ``date1 != date2`` | | ++-------------------------------+----------------------------------------------+ +| | ``date1 < date2`` | Order comparison. (5) | +| | ``date1 > date2`` | | +| | ``date1 <= date2`` | | +| | ``date1 >= date2`` | | +-------------------------------+----------------------------------------------+ Notes: @@ -635,15 +617,12 @@ Notes: timedelta.microseconds are 0, and date2 + timedelta == date1 after. (4) + :class:`date` objects are equal if they represent the same date. + +(5) + *date1* is considered less than *date2* when *date1* precedes *date2* in time. In other words, ``date1 < date2`` if and only if ``date1.toordinal() < - date2.toordinal()``. Date comparison raises :exc:`TypeError` if - the other comparand isn't also a :class:`date` object. However, - ``NotImplemented`` is returned instead if the other comparand has a - :attr:`~date.timetuple` attribute. This hook gives other kinds of date objects a - chance at implementing mixed-type comparison. If not, when a :class:`date` - object is compared to an object of a different type, :exc:`TypeError` is raised - unless the comparison is ``==`` or ``!=``. The latter cases return - :const:`False` or :const:`True`, respectively. + date2.toordinal()``. In Boolean contexts, all :class:`date` objects are considered to be true. @@ -1170,8 +1149,13 @@ Supported operations: +---------------------------------------+--------------------------------+ | ``timedelta = datetime1 - datetime2`` | \(3) | +---------------------------------------+--------------------------------+ -| ``datetime1 < datetime2`` | Compares :class:`.datetime` to | -| | :class:`.datetime`. (4) | +| | ``datetime1 == datetime2`` | Equality comparison. (4) | +| | ``datetime1 != datetime2`` | | ++---------------------------------------+--------------------------------+ +| | ``datetime1 < datetime2`` | Order comparison. (5) | +| | ``datetime1 > datetime2`` | | +| | ``datetime1 <= datetime2`` | | +| | ``datetime1 >= datetime2`` | | +---------------------------------------+--------------------------------+ (1) @@ -1199,40 +1183,41 @@ Supported operations: are done in this case. If both are aware and have different :attr:`~.datetime.tzinfo` attributes, ``a-b`` acts - as if *a* and *b* were first converted to naive UTC datetimes first. The + as if *a* and *b* were first converted to naive UTC datetimes. The result is ``(a.replace(tzinfo=None) - a.utcoffset()) - (b.replace(tzinfo=None) - b.utcoffset())`` except that the implementation never overflows. (4) + :class:`.datetime` objects are equal if they represent the same date + and time, taking into account the time zone. + + Naive and aware :class:`!datetime` objects are never equal. + :class:`!datetime` objects are never equal to :class:`date` objects + that are not also :class:`!datetime` instances, even if they represent + the same date. + + If both comparands are aware and have different :attr:`~.datetime.tzinfo` + attributes, the comparison acts as comparands were first converted to UTC + datetimes except that the implementation never overflows. + :class:`!datetime` instances in a repeated interval are never equal to + :class:`!datetime` instances in other time zone. + +(5) *datetime1* is considered less than *datetime2* when *datetime1* precedes - *datetime2* in time. + *datetime2* in time, taking into account the time zone. - If one comparand is naive and the other is aware, :exc:`TypeError` - is raised if an order comparison is attempted. For equality - comparisons, naive instances are never equal to aware instances. + Order comparison between naive and aware :class:`.datetime` objects, + as well as a :class:`!datetime` object and a :class:`!date` object + that is not also a :class:`!datetime` instance, raises :exc:`TypeError`. - If both comparands are aware, and have the same :attr:`~.datetime.tzinfo` attribute, the - common :attr:`~.datetime.tzinfo` attribute is ignored and the base datetimes are - compared. If both comparands are aware and have different :attr:`~.datetime.tzinfo` - attributes, the comparands are first adjusted by subtracting their UTC - offsets (obtained from ``self.utcoffset()``). + If both comparands are aware and have different :attr:`~.datetime.tzinfo` + attributes, the comparison acts as comparands were first converted to UTC + datetimes except that the implementation never overflows. .. versionchanged:: 3.3 Equality comparisons between aware and naive :class:`.datetime` instances don't raise :exc:`TypeError`. - .. note:: - - In order to stop comparison from falling back to the default scheme of comparing - object addresses, datetime comparison normally raises :exc:`TypeError` if the - other comparand isn't also a :class:`.datetime` object. However, - ``NotImplemented`` is returned instead if the other comparand has a - :attr:`~.datetime.timetuple` attribute. This hook gives other kinds of date objects a - chance at implementing mixed-type comparison. If not, when a :class:`.datetime` - object is compared to an object of a different type, :exc:`TypeError` is raised - unless the comparison is ``==`` or ``!=``. The latter cases return - :const:`False` or :const:`True`, respectively. - Instance methods: .. method:: datetime.date() @@ -1766,21 +1751,18 @@ Instance attributes (read-only): .. versionadded:: 3.6 -:class:`.time` objects support comparison of :class:`.time` to :class:`.time`, -where *a* is considered less -than *b* when *a* precedes *b* in time. If one comparand is naive and the other -is aware, :exc:`TypeError` is raised if an order comparison is attempted. For equality -comparisons, naive instances are never equal to aware instances. +:class:`.time` objects support equality and order comparisons, +where *a* is considered less than *b* when *a* precedes *b* in time. + +Naive and aware :class:`!time` objects are never equal. +Order comparison between naive and aware :class:`!time` objects raises +:exc:`TypeError`. If both comparands are aware, and have the same :attr:`~.time.tzinfo` attribute, the common :attr:`!tzinfo` attribute is ignored and the base times are compared. If both comparands are aware and have different :attr:`!tzinfo` attributes, the comparands are first adjusted by -subtracting their UTC offsets (obtained from ``self.utcoffset()``). In order -to stop mixed-type comparisons from falling back to the default comparison by -object address, when a :class:`.time` object is compared to an object of a -different type, :exc:`TypeError` is raised unless the comparison is ``==`` or -``!=``. The latter cases return :const:`False` or :const:`True`, respectively. +subtracting their UTC offsets (obtained from ``self.utcoffset()``). .. versionchanged:: 3.3 Equality comparisons between aware and naive :class:`.time` instances From 7e2703bbff09c3c72c225790e5c71f423351b2d1 Mon Sep 17 00:00:00 2001 From: GILGAMESH <~@gilgamesh.cc> Date: Fri, 2 Feb 2024 10:59:53 -0800 Subject: [PATCH 100/507] Update venv activate.bat to escape custom PROMPT variables on Windows (GH-114885) --- Lib/venv/scripts/nt/activate.bat | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/venv/scripts/nt/activate.bat b/Lib/venv/scripts/nt/activate.bat index 2c98122362a060f..dd5ea8eb67b90a7 100644 --- a/Lib/venv/scripts/nt/activate.bat +++ b/Lib/venv/scripts/nt/activate.bat @@ -15,8 +15,8 @@ if not defined PROMPT set PROMPT=$P$G if defined _OLD_VIRTUAL_PROMPT set PROMPT=%_OLD_VIRTUAL_PROMPT% if defined _OLD_VIRTUAL_PYTHONHOME set PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME% -set _OLD_VIRTUAL_PROMPT=%PROMPT% -set PROMPT=(__VENV_PROMPT__) %PROMPT% +set "_OLD_VIRTUAL_PROMPT=%PROMPT%" +set "PROMPT=(__VENV_PROMPT__) %PROMPT%" if defined PYTHONHOME set _OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME% set PYTHONHOME= From 920b89f62751e64a35fa1bebc03701af6d6f31f2 Mon Sep 17 00:00:00 2001 From: Alex Waygood <Alex.Waygood@Gmail.com> Date: Fri, 2 Feb 2024 21:04:15 +0000 Subject: [PATCH 101/507] Bump ruff to 0.2.0 (#114932) --- .pre-commit-config.yaml | 2 +- Lib/test/.ruff.toml | 8 +++++--- Tools/clinic/.ruff.toml | 2 ++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 19033ce243d9d35..69d85238985150c 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.1.7 + rev: v0.2.0 hooks: - id: ruff name: Run Ruff on Lib/test/ diff --git a/Lib/test/.ruff.toml b/Lib/test/.ruff.toml index d6c1d8745036ece..1c9bac507209b12 100644 --- a/Lib/test/.ruff.toml +++ b/Lib/test/.ruff.toml @@ -1,7 +1,4 @@ fix = true -select = [ - "F811", # Redefinition of unused variable (useful for finding test methods with the same name) -] extend-exclude = [ # Excluded (run with the other AC files in its own separate ruff job in pre-commit) "test_clinic.py", @@ -21,3 +18,8 @@ extend-exclude = [ "test_pkg.py", "test_yield_from.py", ] + +[lint] +select = [ + "F811", # Redefinition of unused variable (useful for finding test methods with the same name) +] diff --git a/Tools/clinic/.ruff.toml b/Tools/clinic/.ruff.toml index cbb3a9a8f3a8c2c..c019572d0cb1864 100644 --- a/Tools/clinic/.ruff.toml +++ b/Tools/clinic/.ruff.toml @@ -1,5 +1,7 @@ target-version = "py310" fix = true + +[lint] select = [ "F", # Enable all pyflakes rules "UP", # Enable all pyupgrade rules by default From b27812d6320e35d62d91f1b3714be1e38021101a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Fri, 2 Feb 2024 23:09:16 +0200 Subject: [PATCH 102/507] Fix indentation of "versionchanged" in datetime.rst (GH-114933) --- Doc/library/datetime.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 096670634ee9569..735c3b24f81509e 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -1214,9 +1214,9 @@ Supported operations: attributes, the comparison acts as comparands were first converted to UTC datetimes except that the implementation never overflows. - .. versionchanged:: 3.3 - Equality comparisons between aware and naive :class:`.datetime` - instances don't raise :exc:`TypeError`. +.. versionchanged:: 3.3 + Equality comparisons between aware and naive :class:`.datetime` + instances don't raise :exc:`TypeError`. Instance methods: From 73d20cafb54193c94577ca60df1ba0410b3ced74 Mon Sep 17 00:00:00 2001 From: John Belmonte <john@neggie.net> Date: Sat, 3 Feb 2024 06:42:17 +0900 Subject: [PATCH 103/507] Correct timedelta description (GH-101417) It only represents the difference between two datetime or date objects, not between two time objects. --- Doc/library/datetime.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 735c3b24f81509e..930af6cbbe9e8d1 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -130,8 +130,8 @@ Available Types .. class:: timedelta :noindex: - A duration expressing the difference between two :class:`date`, :class:`.time`, - or :class:`.datetime` instances to microsecond resolution. + A duration expressing the difference between two :class:`.datetime` + or :class:`date` instances to microsecond resolution. .. class:: tzinfo @@ -203,7 +203,7 @@ objects. -------------------------- A :class:`timedelta` object represents a duration, the difference between two -dates or times. +:class:`.datetime` or :class:`date` instances. .. class:: timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0) From c4a2e8a2c5188c3288d57b80852e92c83f46f6f3 Mon Sep 17 00:00:00 2001 From: Jokimax <77680901+Jokimax@users.noreply.github.com> Date: Sat, 3 Feb 2024 00:13:00 +0200 Subject: [PATCH 104/507] gh-101599: argparse: simplify the option help string (GH-103372) If the option with argument has short and long names, output argument only once, after the long name: -o, --option ARG description instead of -o ARG, --option ARG description --- Lib/argparse.py | 10 +++------- Lib/test/test_argparse.py | 6 +++--- .../2023-04-08-11-41-07.gh-issue-101599.PaWNFh.rst | 1 + 3 files changed, 7 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-04-08-11-41-07.gh-issue-101599.PaWNFh.rst diff --git a/Lib/argparse.py b/Lib/argparse.py index a32884db80d1ea1..9e19f39fadd87bc 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -564,22 +564,18 @@ def _format_action_invocation(self, action): return metavar else: - parts = [] # if the Optional doesn't take a value, format is: # -s, --long if action.nargs == 0: - parts.extend(action.option_strings) + return ', '.join(action.option_strings) # if the Optional takes a value, format is: - # -s ARGS, --long ARGS + # -s, --long ARGS else: default = self._get_default_metavar_for_optional(action) args_string = self._format_args(action, default) - for option_string in action.option_strings: - parts.append('%s %s' % (option_string, args_string)) - - return ', '.join(parts) + return ', '.join(action.option_strings) + ' ' + args_string def _metavar_formatter(self, action, default_metavar): if action.metavar is not None: diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 7c1f5d36999a3d5..940d7e95f96e20a 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -3922,7 +3922,7 @@ class TestHelpUsageWithParentheses(HelpTestCase): options: -h, --help show this help message and exit - -p {1 (option A), 2 (option B)}, --optional {1 (option A), 2 (option B)} + -p, --optional {1 (option A), 2 (option B)} ''' version = '' @@ -4405,8 +4405,8 @@ class TestHelpAlternatePrefixChars(HelpTestCase): help = usage + '''\ options: - ^^foo foo help - ;b BAR, ;;bar BAR bar help + ^^foo foo help + ;b, ;;bar BAR bar help ''' version = '' diff --git a/Misc/NEWS.d/next/Library/2023-04-08-11-41-07.gh-issue-101599.PaWNFh.rst b/Misc/NEWS.d/next/Library/2023-04-08-11-41-07.gh-issue-101599.PaWNFh.rst new file mode 100644 index 000000000000000..a1608a1ae0d2fae --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-04-08-11-41-07.gh-issue-101599.PaWNFh.rst @@ -0,0 +1 @@ +Changed argparse flag options formatting to remove redundancy. From 1183f1e6bfba06ae6c8ea362f96e977bc288e627 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy <tjreedy@udel.edu> Date: Fri, 2 Feb 2024 18:14:32 -0500 Subject: [PATCH 105/507] gh-114913: Add newline to subprocess doc (#114941) *creationflags* is a separate topic from *startupinfo*. Start sentence with 'If given', like previous sentence. --- Doc/library/subprocess.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst index c437ce770b37d0d..f63ca73b3ec067e 100644 --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -664,7 +664,8 @@ functions. If given, *startupinfo* will be a :class:`STARTUPINFO` object, which is passed to the underlying ``CreateProcess`` function. - *creationflags*, if given, can be one or more of the following flags: + + If given, *creationflags*, can be one or more of the following flags: * :data:`CREATE_NEW_CONSOLE` * :data:`CREATE_NEW_PROCESS_GROUP` From f35c7c070ca6b49c5d6a97be34e6c02f828f5873 Mon Sep 17 00:00:00 2001 From: Malcolm Smith <smith@chaquo.com> Date: Fri, 2 Feb 2024 23:30:52 +0000 Subject: [PATCH 106/507] gh-114875: Require getgrent for building the grp extension module (#114876) Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com> --- .../Build/2024-02-01-20-08-11.gh-issue-114875.x_2iZ9.rst | 1 + configure | 9 ++++++++- configure.ac | 6 ++++-- pyconfig.h.in | 3 +++ 4 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2024-02-01-20-08-11.gh-issue-114875.x_2iZ9.rst diff --git a/Misc/NEWS.d/next/Build/2024-02-01-20-08-11.gh-issue-114875.x_2iZ9.rst b/Misc/NEWS.d/next/Build/2024-02-01-20-08-11.gh-issue-114875.x_2iZ9.rst new file mode 100644 index 000000000000000..20e9d6376b973cb --- /dev/null +++ b/Misc/NEWS.d/next/Build/2024-02-01-20-08-11.gh-issue-114875.x_2iZ9.rst @@ -0,0 +1 @@ +Add :c:func:`!getgrent` as a prerequisite for building the :mod:`grp` module. diff --git a/configure b/configure index 7b2119ff7f4f784..1d41d7ba66470d7 100755 --- a/configure +++ b/configure @@ -17475,6 +17475,12 @@ if test "x$ac_cv_func_getgid" = xyes then : printf "%s\n" "#define HAVE_GETGID 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "getgrent" "ac_cv_func_getgrent" +if test "x$ac_cv_func_getgrent" = xyes +then : + printf "%s\n" "#define HAVE_GETGRENT 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "getgrgid" "ac_cv_func_getgrgid" if test "x$ac_cv_func_getgrgid" = xyes @@ -28968,7 +28974,8 @@ then : if true then : - if test "$ac_cv_func_getgrgid" = yes -o "$ac_cv_func_getgrgid_r" = yes + if test "$ac_cv_func_getgrent" = "yes" && + { test "$ac_cv_func_getgrgid" = "yes" || test "$ac_cv_func_getgrgid_r" = "yes"; } then : py_cv_module_grp=yes else $as_nop diff --git a/configure.ac b/configure.ac index 5bef2351c987e83..b29cd028f8f2004 100644 --- a/configure.ac +++ b/configure.ac @@ -4787,7 +4787,7 @@ AC_CHECK_FUNCS([ \ copy_file_range ctermid dup dup3 execv explicit_bzero explicit_memset \ faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \ fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \ - gai_strerror getegid getentropy geteuid getgid getgrgid getgrgid_r \ + gai_strerror getegid getentropy geteuid getgid getgrent getgrgid getgrgid_r \ getgrnam_r getgrouplist getgroups gethostname getitimer getloadavg getlogin \ getpeername getpgid getpid getppid getpriority _getpty \ getpwent getpwnam_r getpwuid getpwuid_r getresgid getresuid getrusage getsid getspent \ @@ -7313,7 +7313,9 @@ PY_STDLIB_MOD([_socket], -a "$ac_cv_header_netinet_in_h" = "yes"])) dnl platform specific extensions -PY_STDLIB_MOD([grp], [], [test "$ac_cv_func_getgrgid" = yes -o "$ac_cv_func_getgrgid_r" = yes]) +PY_STDLIB_MOD([grp], [], + [test "$ac_cv_func_getgrent" = "yes" && + { test "$ac_cv_func_getgrgid" = "yes" || test "$ac_cv_func_getgrgid_r" = "yes"; }]) PY_STDLIB_MOD([pwd], [], [test "$ac_cv_func_getpwuid" = yes -o "$ac_cv_func_getpwuid_r" = yes]) PY_STDLIB_MOD([resource], [], [test "$ac_cv_header_sys_resource_h" = yes]) PY_STDLIB_MOD([_scproxy], diff --git a/pyconfig.h.in b/pyconfig.h.in index b22740710bcbee5..2b4bb1a2b528662 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -474,6 +474,9 @@ /* Define to 1 if you have the `getgid' function. */ #undef HAVE_GETGID +/* Define to 1 if you have the `getgrent' function. */ +#undef HAVE_GETGRENT + /* Define to 1 if you have the `getgrgid' function. */ #undef HAVE_GETGRGID From f3cdd64de8a9d5bad122cc0b285b5c44cd9b202b Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Sat, 3 Feb 2024 04:52:58 +0300 Subject: [PATCH 107/507] ``Tools/cases_generator``: Fix typos and incorrect comments. (#114892) --- Tools/cases_generator/opcode_id_generator.py | 2 +- Tools/cases_generator/opcode_metadata_generator.py | 4 ++-- Tools/cases_generator/py_metadata_generator.py | 4 ++-- Tools/cases_generator/uop_metadata_generator.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Tools/cases_generator/opcode_id_generator.py b/Tools/cases_generator/opcode_id_generator.py index dbea3d0b622c87f..5a3009a5c04c27b 100644 --- a/Tools/cases_generator/opcode_id_generator.py +++ b/Tools/cases_generator/opcode_id_generator.py @@ -1,6 +1,6 @@ """Generate the list of opcode IDs. Reads the instruction definitions from bytecodes.c. -Writes the IDs to opcode._ids.h by default. +Writes the IDs to opcode_ids.h by default. """ import argparse diff --git a/Tools/cases_generator/opcode_metadata_generator.py b/Tools/cases_generator/opcode_metadata_generator.py index 1826a0b645c3b86..3e9fa3e26daa539 100644 --- a/Tools/cases_generator/opcode_metadata_generator.py +++ b/Tools/cases_generator/opcode_metadata_generator.py @@ -1,6 +1,6 @@ -"""Generate uop metedata. +"""Generate opcode metadata. Reads the instruction definitions from bytecodes.c. -Writes the metadata to pycore_uop_metadata.h by default. +Writes the metadata to pycore_opcode_metadata.h by default. """ import argparse diff --git a/Tools/cases_generator/py_metadata_generator.py b/Tools/cases_generator/py_metadata_generator.py index 43811fdacc8a9e1..0dbcd599f9d4d98 100644 --- a/Tools/cases_generator/py_metadata_generator.py +++ b/Tools/cases_generator/py_metadata_generator.py @@ -1,6 +1,6 @@ -"""Generate uop metedata. +"""Generate opcode metadata for Python. Reads the instruction definitions from bytecodes.c. -Writes the metadata to pycore_uop_metadata.h by default. +Writes the metadata to _opcode_metadata.py by default. """ import argparse diff --git a/Tools/cases_generator/uop_metadata_generator.py b/Tools/cases_generator/uop_metadata_generator.py index d4f3a096d2acc18..9083ecc48bdf5b8 100644 --- a/Tools/cases_generator/uop_metadata_generator.py +++ b/Tools/cases_generator/uop_metadata_generator.py @@ -1,4 +1,4 @@ -"""Generate uop metedata. +"""Generate uop metadata. Reads the instruction definitions from bytecodes.c. Writes the metadata to pycore_uop_metadata.h by default. """ From 00d7109075dfaadf438362c084e8a1890c53d4c8 Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Fri, 2 Feb 2024 19:56:00 -0600 Subject: [PATCH 108/507] Normalize heading underline in multiprocessing.rst (#114923) This gets rid of the mildly confusing `>>>>>>>' underlines which look vaguely like `diff` punctuation. --- Doc/library/multiprocessing.rst | 52 ++++++++++++++++----------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst index 789a84b02d59d23..b104a6483b70e6f 100644 --- a/Doc/library/multiprocessing.rst +++ b/Doc/library/multiprocessing.rst @@ -56,7 +56,7 @@ will print to standard output :: The :class:`Process` class -~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^ In :mod:`multiprocessing`, processes are spawned by creating a :class:`Process` object and then calling its :meth:`~Process.start` method. :class:`Process` @@ -102,7 +102,7 @@ necessary, see :ref:`multiprocessing-programming`. .. _multiprocessing-start-methods: Contexts and start methods -~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^ Depending on the platform, :mod:`multiprocessing` supports three ways to start a process. These *start methods* are @@ -231,7 +231,7 @@ library user. Exchanging objects between processes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :mod:`multiprocessing` supports two types of communication channel between processes: @@ -283,7 +283,7 @@ processes: Synchronization between processes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :mod:`multiprocessing` contains equivalents of all the synchronization primitives from :mod:`threading`. For instance one can use a lock to ensure @@ -309,7 +309,7 @@ mixed up. Sharing state between processes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ As mentioned above, when doing concurrent programming it is usually best to avoid using shared state as far as possible. This is particularly true when @@ -399,7 +399,7 @@ However, if you really do need to use some shared data then Using a pool of workers -~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^ The :class:`~multiprocessing.pool.Pool` class represents a pool of worker processes. It has methods which allows tasks to be offloaded to the worker @@ -490,7 +490,7 @@ The :mod:`multiprocessing` package mostly replicates the API of the :class:`Process` and exceptions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. class:: Process(group=None, target=None, name=None, args=(), kwargs={}, \ *, daemon=None) @@ -724,7 +724,7 @@ The :mod:`multiprocessing` package mostly replicates the API of the Raised by methods with a timeout when the timeout expires. Pipes and Queues -~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^ When using multiple processes, one generally uses message passing for communication between processes and avoids having to use any synchronization @@ -981,7 +981,7 @@ For an example of the usage of queues for interprocess communication see Miscellaneous -~~~~~~~~~~~~~ +^^^^^^^^^^^^^ .. function:: active_children() @@ -1150,7 +1150,7 @@ Miscellaneous Connection Objects -~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^ .. currentmodule:: multiprocessing.connection @@ -1292,7 +1292,7 @@ For example: Synchronization primitives -~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^ .. currentmodule:: multiprocessing @@ -1481,7 +1481,7 @@ object -- see :ref:`multiprocessing-managers`. Shared :mod:`ctypes` Objects -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ It is possible to create shared objects using shared memory which can be inherited by child processes. @@ -1543,7 +1543,7 @@ inherited by child processes. The :mod:`multiprocessing.sharedctypes` module ->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +"""""""""""""""""""""""""""""""""""""""""""""" .. module:: multiprocessing.sharedctypes :synopsis: Allocate ctypes objects from shared memory. @@ -1709,7 +1709,7 @@ The results printed are :: .. _multiprocessing-managers: Managers -~~~~~~~~ +^^^^^^^^ Managers provide a way to create data which can be shared between different processes, including sharing over a network between processes running on @@ -1954,7 +1954,7 @@ their parent process exits. The manager classes are defined in the Customized managers ->>>>>>>>>>>>>>>>>>> +""""""""""""""""""" To create one's own manager, one creates a subclass of :class:`BaseManager` and uses the :meth:`~BaseManager.register` classmethod to register new types or @@ -1981,7 +1981,7 @@ callables with the manager class. For example:: Using a remote manager ->>>>>>>>>>>>>>>>>>>>>> +"""""""""""""""""""""" It is possible to run a manager server on one machine and have clients use it from other machines (assuming that the firewalls involved allow it). @@ -2044,7 +2044,7 @@ client to access it remotely:: .. _multiprocessing-proxy_objects: Proxy Objects -~~~~~~~~~~~~~ +^^^^^^^^^^^^^ A proxy is an object which *refers* to a shared object which lives (presumably) in a different process. The shared object is said to be the *referent* of the @@ -2196,7 +2196,7 @@ demonstrates a level of control over the synchronization. Cleanup ->>>>>>> +""""""" A proxy object uses a weakref callback so that when it gets garbage collected it deregisters itself from the manager which owns its referent. @@ -2206,7 +2206,7 @@ any proxies referring to it. Process Pools -~~~~~~~~~~~~~ +^^^^^^^^^^^^^ .. module:: multiprocessing.pool :synopsis: Create pools of processes. @@ -2442,7 +2442,7 @@ The following example demonstrates the use of a pool:: .. _multiprocessing-listeners-clients: Listeners and Clients -~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^ .. module:: multiprocessing.connection :synopsis: API for dealing with sockets. @@ -2665,7 +2665,7 @@ wait for messages from multiple processes at once:: .. _multiprocessing-address-formats: Address Formats ->>>>>>>>>>>>>>> +""""""""""""""" * An ``'AF_INET'`` address is a tuple of the form ``(hostname, port)`` where *hostname* is a string and *port* is an integer. @@ -2685,7 +2685,7 @@ an ``'AF_PIPE'`` address rather than an ``'AF_UNIX'`` address. .. _multiprocessing-auth-keys: Authentication keys -~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^ When one uses :meth:`Connection.recv <Connection.recv>`, the data received is automatically @@ -2711,7 +2711,7 @@ Suitable authentication keys can also be generated by using :func:`os.urandom`. Logging -~~~~~~~ +^^^^^^^ Some support for logging is available. Note, however, that the :mod:`logging` package does not use process shared locks so it is possible (depending on the @@ -2759,7 +2759,7 @@ For a full table of logging levels, see the :mod:`logging` module. The :mod:`multiprocessing.dummy` module -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. module:: multiprocessing.dummy :synopsis: Dumb wrapper around threading. @@ -2818,7 +2818,7 @@ There are certain guidelines and idioms which should be adhered to when using All start methods -~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^ The following applies to all start methods. @@ -2977,7 +2977,7 @@ Beware of replacing :data:`sys.stdin` with a "file like object" For more information, see :issue:`5155`, :issue:`5313` and :issue:`5331` The *spawn* and *forkserver* start methods -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ There are a few extra restriction which don't apply to the *fork* start method. From 28bb2961ba2f650452c949fcfc75ccfe0b5517e9 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak <felisiak.mariusz@gmail.com> Date: Sat, 3 Feb 2024 08:37:21 +0100 Subject: [PATCH 109/507] Update LOGGING example taken from Django docs. (#114903) For example, Django no longer provides a custom NullHandler https://github.com/django/django/commit/6c66a41c3dc697dc3bda4e31e8b05084d2ede915 * Remove require_debug_true. --- Doc/howto/logging-cookbook.rst | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/Doc/howto/logging-cookbook.rst b/Doc/howto/logging-cookbook.rst index ea494f2fdbbce42..80147e31fcbae11 100644 --- a/Doc/howto/logging-cookbook.rst +++ b/Doc/howto/logging-cookbook.rst @@ -1933,30 +1933,28 @@ This dictionary is passed to :func:`~config.dictConfig` to put the configuration LOGGING = { 'version': 1, - 'disable_existing_loggers': True, + 'disable_existing_loggers': False, 'formatters': { 'verbose': { - 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' + 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}', + 'style': '{', }, 'simple': { - 'format': '%(levelname)s %(message)s' + 'format': '{levelname} {message}', + 'style': '{', }, }, 'filters': { 'special': { '()': 'project.logging.SpecialFilter', 'foo': 'bar', - } + }, }, 'handlers': { - 'null': { - 'level':'DEBUG', - 'class':'django.utils.log.NullHandler', - }, - 'console':{ - 'level':'DEBUG', - 'class':'logging.StreamHandler', - 'formatter': 'simple' + 'console': { + 'level': 'INFO', + 'class': 'logging.StreamHandler', + 'formatter': 'simple', }, 'mail_admins': { 'level': 'ERROR', @@ -1966,9 +1964,8 @@ This dictionary is passed to :func:`~config.dictConfig` to put the configuration }, 'loggers': { 'django': { - 'handlers':['null'], + 'handlers': ['console'], 'propagate': True, - 'level':'INFO', }, 'django.request': { 'handlers': ['mail_admins'], From efc489021c2a5dba46979bd304563aee0c479a31 Mon Sep 17 00:00:00 2001 From: Jason Zhang <yurenzhang2017@gmail.com> Date: Sat, 3 Feb 2024 15:11:10 +0000 Subject: [PATCH 110/507] gh-111417: Remove unused code block in math.trunc() and round() (GH-111454) _PyObject_LookupSpecial() now ensures that the type is ready. --- Modules/mathmodule.c | 5 ----- Python/bltinmodule.c | 5 ----- 2 files changed, 10 deletions(-) diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 0be46b1574c1feb..a877bfcd6afb687 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -2074,11 +2074,6 @@ math_trunc(PyObject *module, PyObject *x) return PyFloat_Type.tp_as_number->nb_int(x); } - if (!_PyType_IsReady(Py_TYPE(x))) { - if (PyType_Ready(Py_TYPE(x)) < 0) - return NULL; - } - math_module_state *state = get_math_module_state(module); trunc = _PyObject_LookupSpecial(x, state->str___trunc__); if (trunc == NULL) { diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index e54d5cbacdc96f9..31c1bf07e8fb913 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -2382,11 +2382,6 @@ builtin_round_impl(PyObject *module, PyObject *number, PyObject *ndigits) { PyObject *round, *result; - if (!_PyType_IsReady(Py_TYPE(number))) { - if (PyType_Ready(Py_TYPE(number)) < 0) - return NULL; - } - round = _PyObject_LookupSpecial(number, &_Py_ID(__round__)); if (round == NULL) { if (!PyErr_Occurred()) From b4240fd68ecd2c22ec82ac549eabfe5fd35fab2a Mon Sep 17 00:00:00 2001 From: AN Long <aisk@users.noreply.github.com> Date: Sat, 3 Feb 2024 23:33:58 +0800 Subject: [PATCH 111/507] gh-114955: Add clear to MutableSequence's mixin methods in document (gh-114956) --- Doc/library/collections.abc.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/collections.abc.rst b/Doc/library/collections.abc.rst index 582bb18f752bd59..7bcaba60c6ddbd9 100644 --- a/Doc/library/collections.abc.rst +++ b/Doc/library/collections.abc.rst @@ -136,8 +136,8 @@ ABC Inherits from Abstract Methods Mi :class:`Collection` ``__len__`` ``index``, and ``count`` :class:`MutableSequence` :class:`Sequence` ``__getitem__``, Inherited :class:`Sequence` methods and - ``__setitem__``, ``append``, ``reverse``, ``extend``, ``pop``, - ``__delitem__``, ``remove``, and ``__iadd__`` + ``__setitem__``, ``append``, ``clear``, ``reverse``, ``extend``, + ``__delitem__``, ``pop``, ``remove``, and ``__iadd__`` ``__len__``, ``insert`` From 96bce033c4a4da7112792ba335ef3eb9a3eb0da0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sat, 3 Feb 2024 18:18:46 +0200 Subject: [PATCH 112/507] gh-114959: tarfile: do not ignore errors when extract a directory on top of a file (GH-114960) Also, add tests common to tarfile and zipfile. --- Lib/tarfile.py | 3 +- Lib/test/archiver_tests.py | 155 ++++++++++++++++++ Lib/test/test_tarfile.py | 33 ++++ Lib/test/test_zipfile/test_core.py | 28 ++++ ...-02-03-16-59-25.gh-issue-114959.dCfAG2.rst | 2 + 5 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 Lib/test/archiver_tests.py create mode 100644 Misc/NEWS.d/next/Library/2024-02-03-16-59-25.gh-issue-114959.dCfAG2.rst diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 20e0394507f5db1..9775040cbe372cd 100755 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -2456,7 +2456,8 @@ def makedir(self, tarinfo, targetpath): # later in _extract_member(). os.mkdir(targetpath, 0o700) except FileExistsError: - pass + if not os.path.isdir(targetpath): + raise def makefile(self, tarinfo, targetpath): """Make a file called targetpath. diff --git a/Lib/test/archiver_tests.py b/Lib/test/archiver_tests.py new file mode 100644 index 000000000000000..1a4bbb9e5706c53 --- /dev/null +++ b/Lib/test/archiver_tests.py @@ -0,0 +1,155 @@ +"""Tests common to tarfile and zipfile.""" + +import os +import sys + +from test.support import os_helper + +class OverwriteTests: + + def setUp(self): + os.makedirs(self.testdir) + self.addCleanup(os_helper.rmtree, self.testdir) + + def create_file(self, path, content=b''): + with open(path, 'wb') as f: + f.write(content) + + def open(self, path): + raise NotImplementedError + + def extractall(self, ar): + raise NotImplementedError + + + def test_overwrite_file_as_file(self): + target = os.path.join(self.testdir, 'test') + self.create_file(target, b'content') + with self.open(self.ar_with_file) as ar: + self.extractall(ar) + self.assertTrue(os.path.isfile(target)) + with open(target, 'rb') as f: + self.assertEqual(f.read(), b'newcontent') + + def test_overwrite_dir_as_dir(self): + target = os.path.join(self.testdir, 'test') + os.mkdir(target) + with self.open(self.ar_with_dir) as ar: + self.extractall(ar) + self.assertTrue(os.path.isdir(target)) + + def test_overwrite_dir_as_implicit_dir(self): + target = os.path.join(self.testdir, 'test') + os.mkdir(target) + with self.open(self.ar_with_implicit_dir) as ar: + self.extractall(ar) + self.assertTrue(os.path.isdir(target)) + self.assertTrue(os.path.isfile(os.path.join(target, 'file'))) + with open(os.path.join(target, 'file'), 'rb') as f: + self.assertEqual(f.read(), b'newcontent') + + def test_overwrite_dir_as_file(self): + target = os.path.join(self.testdir, 'test') + os.mkdir(target) + with self.open(self.ar_with_file) as ar: + with self.assertRaises(PermissionError if sys.platform == 'win32' + else IsADirectoryError): + self.extractall(ar) + self.assertTrue(os.path.isdir(target)) + + def test_overwrite_file_as_dir(self): + target = os.path.join(self.testdir, 'test') + self.create_file(target, b'content') + with self.open(self.ar_with_dir) as ar: + with self.assertRaises(FileExistsError): + self.extractall(ar) + self.assertTrue(os.path.isfile(target)) + with open(target, 'rb') as f: + self.assertEqual(f.read(), b'content') + + def test_overwrite_file_as_implicit_dir(self): + target = os.path.join(self.testdir, 'test') + self.create_file(target, b'content') + with self.open(self.ar_with_implicit_dir) as ar: + with self.assertRaises(FileNotFoundError if sys.platform == 'win32' + else NotADirectoryError): + self.extractall(ar) + self.assertTrue(os.path.isfile(target)) + with open(target, 'rb') as f: + self.assertEqual(f.read(), b'content') + + @os_helper.skip_unless_symlink + def test_overwrite_file_symlink_as_file(self): + # XXX: It is potential security vulnerability. + target = os.path.join(self.testdir, 'test') + target2 = os.path.join(self.testdir, 'test2') + self.create_file(target2, b'content') + os.symlink('test2', target) + with self.open(self.ar_with_file) as ar: + self.extractall(ar) + self.assertTrue(os.path.islink(target)) + self.assertTrue(os.path.isfile(target2)) + with open(target2, 'rb') as f: + self.assertEqual(f.read(), b'newcontent') + + @os_helper.skip_unless_symlink + def test_overwrite_broken_file_symlink_as_file(self): + # XXX: It is potential security vulnerability. + target = os.path.join(self.testdir, 'test') + target2 = os.path.join(self.testdir, 'test2') + os.symlink('test2', target) + with self.open(self.ar_with_file) as ar: + self.extractall(ar) + self.assertTrue(os.path.islink(target)) + self.assertTrue(os.path.isfile(target2)) + with open(target2, 'rb') as f: + self.assertEqual(f.read(), b'newcontent') + + @os_helper.skip_unless_symlink + def test_overwrite_dir_symlink_as_dir(self): + # XXX: It is potential security vulnerability. + target = os.path.join(self.testdir, 'test') + target2 = os.path.join(self.testdir, 'test2') + os.mkdir(target2) + os.symlink('test2', target, target_is_directory=True) + with self.open(self.ar_with_dir) as ar: + self.extractall(ar) + self.assertTrue(os.path.islink(target)) + self.assertTrue(os.path.isdir(target2)) + + @os_helper.skip_unless_symlink + def test_overwrite_dir_symlink_as_implicit_dir(self): + # XXX: It is potential security vulnerability. + target = os.path.join(self.testdir, 'test') + target2 = os.path.join(self.testdir, 'test2') + os.mkdir(target2) + os.symlink('test2', target, target_is_directory=True) + with self.open(self.ar_with_implicit_dir) as ar: + self.extractall(ar) + self.assertTrue(os.path.islink(target)) + self.assertTrue(os.path.isdir(target2)) + self.assertTrue(os.path.isfile(os.path.join(target2, 'file'))) + with open(os.path.join(target2, 'file'), 'rb') as f: + self.assertEqual(f.read(), b'newcontent') + + @os_helper.skip_unless_symlink + def test_overwrite_broken_dir_symlink_as_dir(self): + target = os.path.join(self.testdir, 'test') + target2 = os.path.join(self.testdir, 'test2') + os.symlink('test2', target, target_is_directory=True) + with self.open(self.ar_with_dir) as ar: + with self.assertRaises(FileExistsError): + self.extractall(ar) + self.assertTrue(os.path.islink(target)) + self.assertFalse(os.path.exists(target2)) + + @os_helper.skip_unless_symlink + def test_overwrite_broken_dir_symlink_as_implicit_dir(self): + target = os.path.join(self.testdir, 'test') + target2 = os.path.join(self.testdir, 'test2') + os.symlink('test2', target, target_is_directory=True) + with self.open(self.ar_with_implicit_dir) as ar: + with self.assertRaises(FileExistsError): + self.extractall(ar) + self.assertTrue(os.path.islink(target)) + self.assertFalse(os.path.exists(target2)) diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index da5009126b3815b..51f070e96047a6e 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -15,6 +15,7 @@ import unittest.mock import tarfile +from test import archiver_tests from test import support from test.support import os_helper from test.support import script_helper @@ -4135,6 +4136,38 @@ def valueerror_filter(tarinfo, path): self.expect_exception(TypeError) # errorlevel is not int +class OverwriteTests(archiver_tests.OverwriteTests, unittest.TestCase): + testdir = os.path.join(TEMPDIR, "testoverwrite") + + @classmethod + def setUpClass(cls): + p = cls.ar_with_file = os.path.join(TEMPDIR, 'tar-with-file.tar') + cls.addClassCleanup(os_helper.unlink, p) + with tarfile.open(p, 'w') as tar: + t = tarfile.TarInfo('test') + t.size = 10 + tar.addfile(t, io.BytesIO(b'newcontent')) + + p = cls.ar_with_dir = os.path.join(TEMPDIR, 'tar-with-dir.tar') + cls.addClassCleanup(os_helper.unlink, p) + with tarfile.open(p, 'w') as tar: + tar.addfile(tar.gettarinfo(os.curdir, 'test')) + + p = os.path.join(TEMPDIR, 'tar-with-implicit-dir.tar') + cls.ar_with_implicit_dir = p + cls.addClassCleanup(os_helper.unlink, p) + with tarfile.open(p, 'w') as tar: + t = tarfile.TarInfo('test/file') + t.size = 10 + tar.addfile(t, io.BytesIO(b'newcontent')) + + def open(self, path): + return tarfile.open(path, 'r') + + def extractall(self, ar): + ar.extractall(self.testdir, filter='fully_trusted') + + def setUpModule(): os_helper.unlink(TEMPDIR) os.makedirs(TEMPDIR) diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index a177044d735bed7..087fa8d65cc3368 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -18,6 +18,7 @@ from tempfile import TemporaryFile from random import randint, random, randbytes +from test import archiver_tests from test.support import script_helper from test.support import ( findfile, requires_zlib, requires_bz2, requires_lzma, @@ -1687,6 +1688,33 @@ def _test_extract_hackers_arcnames(self, hacknames): unlink(TESTFN2) +class OverwriteTests(archiver_tests.OverwriteTests, unittest.TestCase): + testdir = TESTFN + + @classmethod + def setUpClass(cls): + p = cls.ar_with_file = TESTFN + '-with-file.zip' + cls.addClassCleanup(unlink, p) + with zipfile.ZipFile(p, 'w') as zipfp: + zipfp.writestr('test', b'newcontent') + + p = cls.ar_with_dir = TESTFN + '-with-dir.zip' + cls.addClassCleanup(unlink, p) + with zipfile.ZipFile(p, 'w') as zipfp: + zipfp.mkdir('test') + + p = cls.ar_with_implicit_dir = TESTFN + '-with-implicit-dir.zip' + cls.addClassCleanup(unlink, p) + with zipfile.ZipFile(p, 'w') as zipfp: + zipfp.writestr('test/file', b'newcontent') + + def open(self, path): + return zipfile.ZipFile(path, 'r') + + def extractall(self, ar): + ar.extractall(self.testdir) + + class OtherTests(unittest.TestCase): def test_open_via_zip_info(self): # Create the ZIP archive diff --git a/Misc/NEWS.d/next/Library/2024-02-03-16-59-25.gh-issue-114959.dCfAG2.rst b/Misc/NEWS.d/next/Library/2024-02-03-16-59-25.gh-issue-114959.dCfAG2.rst new file mode 100644 index 000000000000000..5c6eaa7525e3b0c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-03-16-59-25.gh-issue-114959.dCfAG2.rst @@ -0,0 +1,2 @@ +:mod:`tarfile` no longer ignores errors when trying to extract a directory on +top of a file. From 6b53d5fe04eadad76fb3706f0a4cc42d8f19f948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= <sweskman@gmail.com> Date: Sat, 3 Feb 2024 16:19:37 +0000 Subject: [PATCH 113/507] gh-112202: Ensure that condition.notify() succeeds even when racing with Task.cancel() (#112201) Also did a general cleanup of asyncio locks.py comments and docstrings. --- Doc/library/asyncio-sync.rst | 12 +- Lib/asyncio/locks.py | 112 ++++++++++-------- Lib/test/test_asyncio/test_locks.py | 92 ++++++++++++++ ...-01-12-09-35-07.gh-issue-112202.t_0V1m.rst | 1 + 4 files changed, 165 insertions(+), 52 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-12-09-35-07.gh-issue-112202.t_0V1m.rst diff --git a/Doc/library/asyncio-sync.rst b/Doc/library/asyncio-sync.rst index 05bdf5488af143c..3cf8e2737e85dcd 100644 --- a/Doc/library/asyncio-sync.rst +++ b/Doc/library/asyncio-sync.rst @@ -216,8 +216,8 @@ Condition .. method:: notify(n=1) - Wake up at most *n* tasks (1 by default) waiting on this - condition. The method is no-op if no tasks are waiting. + Wake up *n* tasks (1 by default) waiting on this + condition. If fewer than *n* tasks are waiting they are all awakened. The lock must be acquired before this method is called and released shortly after. If called with an *unlocked* lock @@ -257,12 +257,18 @@ Condition Once awakened, the Condition re-acquires its lock and this method returns ``True``. + Note that a task *may* return from this call spuriously, + which is why the caller should always re-check the state + and be prepared to :meth:`wait` again. For this reason, you may + prefer to use :meth:`wait_for` instead. + .. coroutinemethod:: wait_for(predicate) Wait until a predicate becomes *true*. The predicate must be a callable which result will be - interpreted as a boolean value. The final value is the + interpreted as a boolean value. The method will repeatedly + :meth:`wait` until the predicate evaluates to *true*. The final value is the return value. diff --git a/Lib/asyncio/locks.py b/Lib/asyncio/locks.py index 04158e667a895fc..aaee8ff07029233 100644 --- a/Lib/asyncio/locks.py +++ b/Lib/asyncio/locks.py @@ -24,25 +24,23 @@ class Lock(_ContextManagerMixin, mixins._LoopBoundMixin): """Primitive lock objects. A primitive lock is a synchronization primitive that is not owned - by a particular coroutine when locked. A primitive lock is in one + by a particular task when locked. A primitive lock is in one of two states, 'locked' or 'unlocked'. It is created in the unlocked state. It has two basic methods, acquire() and release(). When the state is unlocked, acquire() changes the state to locked and returns immediately. When the state is locked, acquire() blocks until a call to release() in - another coroutine changes it to unlocked, then the acquire() call + another task changes it to unlocked, then the acquire() call resets it to locked and returns. The release() method should only be called in the locked state; it changes the state to unlocked and returns immediately. If an attempt is made to release an unlocked lock, a RuntimeError will be raised. - When more than one coroutine is blocked in acquire() waiting for - the state to turn to unlocked, only one coroutine proceeds when a - release() call resets the state to unlocked; first coroutine which - is blocked in acquire() is being processed. - - acquire() is a coroutine and should be called with 'await'. + When more than one task is blocked in acquire() waiting for + the state to turn to unlocked, only one task proceeds when a + release() call resets the state to unlocked; successive release() + calls will unblock tasks in FIFO order. Locks also support the asynchronous context management protocol. 'async with lock' statement should be used. @@ -130,7 +128,7 @@ def release(self): """Release a lock. When the lock is locked, reset it to unlocked, and return. - If any other coroutines are blocked waiting for the lock to become + If any other tasks are blocked waiting for the lock to become unlocked, allow exactly one of them to proceed. When invoked on an unlocked lock, a RuntimeError is raised. @@ -182,8 +180,8 @@ def is_set(self): return self._value def set(self): - """Set the internal flag to true. All coroutines waiting for it to - become true are awakened. Coroutine that call wait() once the flag is + """Set the internal flag to true. All tasks waiting for it to + become true are awakened. Tasks that call wait() once the flag is true will not block at all. """ if not self._value: @@ -194,7 +192,7 @@ def set(self): fut.set_result(True) def clear(self): - """Reset the internal flag to false. Subsequently, coroutines calling + """Reset the internal flag to false. Subsequently, tasks calling wait() will block until set() is called to set the internal flag to true again.""" self._value = False @@ -203,7 +201,7 @@ async def wait(self): """Block until the internal flag is true. If the internal flag is true on entry, return True - immediately. Otherwise, block until another coroutine calls + immediately. Otherwise, block until another task calls set() to set the flag to true, then return True. """ if self._value: @@ -222,8 +220,8 @@ class Condition(_ContextManagerMixin, mixins._LoopBoundMixin): """Asynchronous equivalent to threading.Condition. This class implements condition variable objects. A condition variable - allows one or more coroutines to wait until they are notified by another - coroutine. + allows one or more tasks to wait until they are notified by another + task. A new Lock object is created and used as the underlying lock. """ @@ -250,50 +248,64 @@ def __repr__(self): async def wait(self): """Wait until notified. - If the calling coroutine has not acquired the lock when this + If the calling task has not acquired the lock when this method is called, a RuntimeError is raised. This method releases the underlying lock, and then blocks until it is awakened by a notify() or notify_all() call for - the same condition variable in another coroutine. Once + the same condition variable in another task. Once awakened, it re-acquires the lock and returns True. + + This method may return spuriously, + which is why the caller should always + re-check the state and be prepared to wait() again. """ if not self.locked(): raise RuntimeError('cannot wait on un-acquired lock') + fut = self._get_loop().create_future() self.release() try: - fut = self._get_loop().create_future() - self._waiters.append(fut) try: - await fut - return True - finally: - self._waiters.remove(fut) - - finally: - # Must re-acquire lock even if wait is cancelled. - # We only catch CancelledError here, since we don't want any - # other (fatal) errors with the future to cause us to spin. - err = None - while True: - try: - await self.acquire() - break - except exceptions.CancelledError as e: - err = e - - if err: + self._waiters.append(fut) try: - raise err # Re-raise most recent exception instance. + await fut + return True finally: - err = None # Break reference cycles. + self._waiters.remove(fut) + + finally: + # Must re-acquire lock even if wait is cancelled. + # We only catch CancelledError here, since we don't want any + # other (fatal) errors with the future to cause us to spin. + err = None + while True: + try: + await self.acquire() + break + except exceptions.CancelledError as e: + err = e + + if err is not None: + try: + raise err # Re-raise most recent exception instance. + finally: + err = None # Break reference cycles. + except BaseException: + # Any error raised out of here _may_ have occurred after this Task + # believed to have been successfully notified. + # Make sure to notify another Task instead. This may result + # in a "spurious wakeup", which is allowed as part of the + # Condition Variable protocol. + self._notify(1) + raise async def wait_for(self, predicate): """Wait until a predicate becomes true. - The predicate should be a callable which result will be - interpreted as a boolean value. The final predicate value is + The predicate should be a callable whose result will be + interpreted as a boolean value. The method will repeatedly + wait() until it evaluates to true. The final predicate value is the return value. """ result = predicate() @@ -303,20 +315,22 @@ async def wait_for(self, predicate): return result def notify(self, n=1): - """By default, wake up one coroutine waiting on this condition, if any. - If the calling coroutine has not acquired the lock when this method + """By default, wake up one task waiting on this condition, if any. + If the calling task has not acquired the lock when this method is called, a RuntimeError is raised. - This method wakes up at most n of the coroutines waiting for the - condition variable; it is a no-op if no coroutines are waiting. + This method wakes up n of the tasks waiting for the condition + variable; if fewer than n are waiting, they are all awoken. - Note: an awakened coroutine does not actually return from its + Note: an awakened task does not actually return from its wait() call until it can reacquire the lock. Since notify() does not release the lock, its caller should. """ if not self.locked(): raise RuntimeError('cannot notify on un-acquired lock') + self._notify(n) + def _notify(self, n): idx = 0 for fut in self._waiters: if idx >= n: @@ -374,7 +388,7 @@ async def acquire(self): If the internal counter is larger than zero on entry, decrement it by one and return True immediately. If it is - zero on entry, block, waiting until some other coroutine has + zero on entry, block, waiting until some other task has called release() to make it larger than 0, and then return True. """ @@ -414,8 +428,8 @@ async def acquire(self): def release(self): """Release a semaphore, incrementing the internal counter by one. - When it was zero on entry and another coroutine is waiting for it to - become larger than zero again, wake up that coroutine. + When it was zero on entry and another task is waiting for it to + become larger than zero again, wake up that task. """ self._value += 1 self._wake_up_next() diff --git a/Lib/test/test_asyncio/test_locks.py b/Lib/test/test_asyncio/test_locks.py index 9029efd2355b46f..a0884bffe6b0de1 100644 --- a/Lib/test/test_asyncio/test_locks.py +++ b/Lib/test/test_asyncio/test_locks.py @@ -816,6 +816,98 @@ async def func(): # originally raised. self.assertIs(err.exception, raised) + async def test_cancelled_wakeup(self): + # Test that a task cancelled at the "same" time as it is woken + # up as part of a Condition.notify() does not result in a lost wakeup. + # This test simulates a cancel while the target task is awaiting initial + # wakeup on the wakeup queue. + condition = asyncio.Condition() + state = 0 + async def consumer(): + nonlocal state + async with condition: + while True: + await condition.wait_for(lambda: state != 0) + if state < 0: + return + state -= 1 + + # create two consumers + c = [asyncio.create_task(consumer()) for _ in range(2)] + # wait for them to settle + await asyncio.sleep(0) + async with condition: + # produce one item and wake up one + state += 1 + condition.notify(1) + + # Cancel it while it is awaiting to be run. + # This cancellation could come from the outside + c[0].cancel() + + # now wait for the item to be consumed + # if it doesn't means that our "notify" didn"t take hold. + # because it raced with a cancel() + try: + async with asyncio.timeout(0.01): + await condition.wait_for(lambda: state == 0) + except TimeoutError: + pass + self.assertEqual(state, 0) + + # clean up + state = -1 + condition.notify_all() + await c[1] + + async def test_cancelled_wakeup_relock(self): + # Test that a task cancelled at the "same" time as it is woken + # up as part of a Condition.notify() does not result in a lost wakeup. + # This test simulates a cancel while the target task is acquiring the lock + # again. + condition = asyncio.Condition() + state = 0 + async def consumer(): + nonlocal state + async with condition: + while True: + await condition.wait_for(lambda: state != 0) + if state < 0: + return + state -= 1 + + # create two consumers + c = [asyncio.create_task(consumer()) for _ in range(2)] + # wait for them to settle + await asyncio.sleep(0) + async with condition: + # produce one item and wake up one + state += 1 + condition.notify(1) + + # now we sleep for a bit. This allows the target task to wake up and + # settle on re-aquiring the lock + await asyncio.sleep(0) + + # Cancel it while awaiting the lock + # This cancel could come the outside. + c[0].cancel() + + # now wait for the item to be consumed + # if it doesn't means that our "notify" didn"t take hold. + # because it raced with a cancel() + try: + async with asyncio.timeout(0.01): + await condition.wait_for(lambda: state == 0) + except TimeoutError: + pass + self.assertEqual(state, 0) + + # clean up + state = -1 + condition.notify_all() + await c[1] + class SemaphoreTests(unittest.IsolatedAsyncioTestCase): def test_initial_value_zero(self): diff --git a/Misc/NEWS.d/next/Library/2024-01-12-09-35-07.gh-issue-112202.t_0V1m.rst b/Misc/NEWS.d/next/Library/2024-01-12-09-35-07.gh-issue-112202.t_0V1m.rst new file mode 100644 index 000000000000000..9abde13bbf8571b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-12-09-35-07.gh-issue-112202.t_0V1m.rst @@ -0,0 +1 @@ +Ensure that a :func:`asyncio.Condition.notify` call does not get lost if the awakened ``Task`` is simultaneously cancelled or encounters any other error. From 94ec2b9c9ce898723c3fe61fbc64d6c8f4f68700 Mon Sep 17 00:00:00 2001 From: Travis Howse <tjhowse@gmail.com> Date: Sun, 4 Feb 2024 03:14:02 +1000 Subject: [PATCH 114/507] gh-114887 Reject only sockets of type SOCK_STREAM in create_datagram_endpoint() (#114893) Also improve exception message. Co-authored-by: Donghee Na <donghee.na92@gmail.com> --- Lib/asyncio/base_events.py | 4 ++-- Lib/test/test_asyncio/test_base_events.py | 2 +- .../2024-02-03-04-07-18.gh-issue-114887.uLSFmN.rst | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-03-04-07-18.gh-issue-114887.uLSFmN.rst diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index c60d7688ef8c772..aadc4f478f8b560 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -1340,9 +1340,9 @@ async def create_datagram_endpoint(self, protocol_factory, allow_broadcast=None, sock=None): """Create datagram connection.""" if sock is not None: - if sock.type != socket.SOCK_DGRAM: + if sock.type == socket.SOCK_STREAM: raise ValueError( - f'A UDP Socket was expected, got {sock!r}') + f'A datagram socket was expected, got {sock!r}') if (local_addr or remote_addr or family or proto or flags or reuse_port or allow_broadcast): diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index c2080977e9d5877..82071edb2525706 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -1232,7 +1232,7 @@ def test_create_datagram_endpoint_wrong_sock(self): with sock: coro = self.loop.create_datagram_endpoint(MyProto, sock=sock) with self.assertRaisesRegex(ValueError, - 'A UDP Socket was expected'): + 'A datagram socket was expected'): self.loop.run_until_complete(coro) def test_create_connection_no_host_port_sock(self): diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-03-04-07-18.gh-issue-114887.uLSFmN.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-03-04-07-18.gh-issue-114887.uLSFmN.rst new file mode 100644 index 000000000000000..b4d8cf4089d7238 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-03-04-07-18.gh-issue-114887.uLSFmN.rst @@ -0,0 +1,2 @@ +Changed socket type validation in :meth:`~asyncio.loop.create_datagram_endpoint` to accept all non-stream sockets. +This fixes a regression in compatibility with raw sockets. From a4c298c1494b602a9650b597aad50b48e3fa1f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= <stephane.bidoul@gmail.com> Date: Sat, 3 Feb 2024 18:45:09 +0100 Subject: [PATCH 115/507] gh-114965: Updated bundled pip to 24.0 (gh-114966) Updated bundled pip to 24.0 --- Lib/ensurepip/__init__.py | 2 +- ...none-any.whl => pip-24.0-py3-none-any.whl} | Bin 2109393 -> 2110226 bytes ...-02-03-17-54-17.gh-issue-114965.gHksCK.rst | 1 + Misc/sbom.spdx.json | 28 +++++++++--------- 4 files changed, 16 insertions(+), 15 deletions(-) rename Lib/ensurepip/_bundled/{pip-23.3.2-py3-none-any.whl => pip-24.0-py3-none-any.whl} (83%) create mode 100644 Misc/NEWS.d/next/Library/2024-02-03-17-54-17.gh-issue-114965.gHksCK.rst diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py index 80ee125cfd4ed32..e8dd253bb555209 100644 --- a/Lib/ensurepip/__init__.py +++ b/Lib/ensurepip/__init__.py @@ -10,7 +10,7 @@ __all__ = ["version", "bootstrap"] -_PIP_VERSION = "23.3.2" +_PIP_VERSION = "24.0" # Directory of system wheel packages. Some Linux distribution packaging # policies recommend against bundling dependencies. For example, Fedora diff --git a/Lib/ensurepip/_bundled/pip-23.3.2-py3-none-any.whl b/Lib/ensurepip/_bundled/pip-24.0-py3-none-any.whl similarity index 83% rename from Lib/ensurepip/_bundled/pip-23.3.2-py3-none-any.whl rename to Lib/ensurepip/_bundled/pip-24.0-py3-none-any.whl index ae78b8a6ce073797af05cd7a833ff23d14cff185..2e6aa9d2cb99232f70d3ffcd0ed8e0b8a730e3ab 100644 GIT binary patch delta 299723 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 delta 299082 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%0Dq*;SPLW^zV~GX;IsiZ5|>$a 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%0AVO0M@LEgMvA1@C_<QYLU^j`XgHD?OC8H* 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 diff --git a/Misc/NEWS.d/next/Library/2024-02-03-17-54-17.gh-issue-114965.gHksCK.rst b/Misc/NEWS.d/next/Library/2024-02-03-17-54-17.gh-issue-114965.gHksCK.rst new file mode 100644 index 000000000000000..d59ff991993792a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-03-17-54-17.gh-issue-114965.gHksCK.rst @@ -0,0 +1 @@ +Update bundled pip to 24.0 diff --git a/Misc/sbom.spdx.json b/Misc/sbom.spdx.json index 94566772338b108..e94dcb83dd4e404 100644 --- a/Misc/sbom.spdx.json +++ b/Misc/sbom.spdx.json @@ -1570,18 +1570,18 @@ "fileName": "Modules/_decimal/libmpdec/vcdiv64.asm" }, { - "SPDXID": "SPDXRef-FILE-Lib-ensurepip-bundled-pip-23.3.2-py3-none-any.whl", + "SPDXID": "SPDXRef-FILE-Lib-ensurepip-bundled-pip-24.0-py3-none-any.whl", "checksums": [ { "algorithm": "SHA1", - "checksumValue": "8e48f55ab2965ee64bd55cc91a8077d184a33e30" + "checksumValue": "e44313ae1e6af3c2bd3b60ab2fa8c34308d00555" }, { "algorithm": "SHA256", - "checksumValue": "5052d7889c1f9d05224cd41741acb7c5d6fa735ab34e339624a614eaaa7e7d76" + "checksumValue": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc" } ], - "fileName": "Lib/ensurepip/_bundled/pip-23.3.2-py3-none-any.whl" + "fileName": "Lib/ensurepip/_bundled/pip-24.0-py3-none-any.whl" } ], "packages": [ @@ -1742,21 +1742,21 @@ "checksums": [ { "algorithm": "SHA256", - "checksumValue": "f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e" + "checksumValue": "034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784" } ], - "downloadLocation": "https://files.pythonhosted.org/packages/76/cb/6bbd2b10170ed991cf64e8c8b85e01f2fb38f95d1bc77617569e0b0b26ac/distlib-0.3.6-py2.py3-none-any.whl", + "downloadLocation": "https://files.pythonhosted.org/packages/8e/41/9307e4f5f9976bc8b7fea0b66367734e8faf3ec84bc0d412d8cfabbb66cd/distlib-0.3.8-py2.py3-none-any.whl", "externalRefs": [ { "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/distlib@0.3.6", + "referenceLocator": "pkg:pypi/distlib@0.3.8", "referenceType": "purl" } ], "licenseConcluded": "MIT", "name": "distlib", "primaryPackagePurpose": "SOURCE", - "versionInfo": "0.3.6" + "versionInfo": "0.3.8" }, { "SPDXID": "SPDXRef-PACKAGE-distro", @@ -2204,19 +2204,19 @@ "checksums": [ { "algorithm": "SHA256", - "checksumValue": "5052d7889c1f9d05224cd41741acb7c5d6fa735ab34e339624a614eaaa7e7d76" + "checksumValue": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc" } ], - "downloadLocation": "https://files.pythonhosted.org/packages/15/aa/3f4c7bcee2057a76562a5b33ecbd199be08cdb4443a02e26bd2c3cf6fc39/pip-23.3.2-py3-none-any.whl", + "downloadLocation": "https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl", "externalRefs": [ { "referenceCategory": "SECURITY", - "referenceLocator": "cpe:2.3:a:pypa:pip:23.3.2:*:*:*:*:*:*:*", + "referenceLocator": "cpe:2.3:a:pypa:pip:24.0:*:*:*:*:*:*:*", "referenceType": "cpe23Type" }, { "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/pip@23.3.2", + "referenceLocator": "pkg:pypi/pip@24.0", "referenceType": "purl" } ], @@ -2224,7 +2224,7 @@ "name": "pip", "originator": "Organization: Python Packaging Authority", "primaryPackagePurpose": "SOURCE", - "versionInfo": "23.3.2" + "versionInfo": "24.0" } ], "relationships": [ @@ -2909,7 +2909,7 @@ "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" }, { - "relatedSpdxElement": "SPDXRef-FILE-Lib-ensurepip-bundled-pip-23.3.2-py3-none-any.whl", + "relatedSpdxElement": "SPDXRef-FILE-Lib-ensurepip-bundled-pip-24.0-py3-none-any.whl", "relationshipType": "CONTAINS", "spdxElementId": "SPDXRef-PACKAGE-pip" } From ab76d37948fd506af44762dc1c3e32f27d1327a8 Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Sat, 3 Feb 2024 12:45:49 -0600 Subject: [PATCH 116/507] gh-101100: Fix Sphinx reference warnings in the glossary (#114729) Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> --- Doc/glossary.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Doc/glossary.rst b/Doc/glossary.rst index 098bfffb104ef69..f656e32514c7178 100644 --- a/Doc/glossary.rst +++ b/Doc/glossary.rst @@ -341,7 +341,7 @@ Glossary docstring A string literal which appears as the first expression in a class, function or module. While ignored when the suite is executed, it is - recognized by the compiler and put into the :attr:`__doc__` attribute + recognized by the compiler and put into the :attr:`!__doc__` attribute of the enclosing class, function or module. Since it is available via introspection, it is the canonical place for documentation of the object. @@ -1104,10 +1104,12 @@ Glossary The :class:`collections.abc.Sequence` abstract base class defines a much richer interface that goes beyond just :meth:`~object.__getitem__` and :meth:`~object.__len__`, adding - :meth:`count`, :meth:`index`, :meth:`~object.__contains__`, and + :meth:`!count`, :meth:`!index`, :meth:`~object.__contains__`, and :meth:`~object.__reversed__`. Types that implement this expanded interface can be registered explicitly using - :func:`~abc.ABCMeta.register`. + :func:`~abc.ABCMeta.register`. For more documentation on sequence + methods generally, see + :ref:`Common Sequence Operations <typesseq-common>`. set comprehension A compact way to process all or part of the elements in an iterable and From 72d2d0f10d5623bceb98a2014926ea0b87594ecb Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Sun, 4 Feb 2024 00:55:38 +0300 Subject: [PATCH 117/507] gh-114803: Mention that `@dataclass` should not be applied on enums (GH-114891) Co-authored-by: Kirill Podoprigora <kirill.bast9@mail.ru> Co-authored-by: Ethan Furman <ethan@stoneleaf.us> --- Doc/howto/enum.rst | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Doc/howto/enum.rst b/Doc/howto/enum.rst index 1e9ac9b6761b647..30be15230fc088e 100644 --- a/Doc/howto/enum.rst +++ b/Doc/howto/enum.rst @@ -497,13 +497,30 @@ the :meth:`~Enum.__repr__` omits the inherited class' name. For example:: >>> Creature.DOG <Creature.DOG: size='medium', legs=4> -Use the :func:`!dataclass` argument ``repr=False`` +Use the :func:`~dataclasses.dataclass` argument ``repr=False`` to use the standard :func:`repr`. .. versionchanged:: 3.12 Only the dataclass fields are shown in the value area, not the dataclass' name. +.. note:: + + Adding :func:`~dataclasses.dataclass` decorator to :class:`Enum` + and its subclasses is not supported. It will not raise any errors, + but it will produce very strange results at runtime, such as members + being equal to each other:: + + >>> @dataclass # don't do this: it does not make any sense + ... class Color(Enum): + ... RED = 1 + ... BLUE = 2 + ... + >>> Color.RED is Color.BLUE + False + >>> Color.RED == Color.BLUE # problem is here: they should not be equal + True + Pickling -------- From 1032326fe46afaef57c3e01160a4f889dadfee95 Mon Sep 17 00:00:00 2001 From: Zachary Ware <zach@python.org> Date: Sat, 3 Feb 2024 17:16:03 -0600 Subject: [PATCH 118/507] gh-114883: Fix Makefile dependency tree for non-jit builds (GH-114884) --- Makefile.pre.in | 13 ++++++++++++- configure | 3 +++ configure.ac | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index fff3d3c4914e7a7..aad637876ead809 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2643,7 +2643,18 @@ config.status: $(srcdir)/configure Python/asm_trampoline.o: $(srcdir)/Python/asm_trampoline.S $(CC) -c $(PY_CORE_CFLAGS) -o $@ $< -Python/jit.o: regen-jit + +JIT_DEPS = \ + $(srcdir)/Tools/jit/*.c \ + $(srcdir)/Tools/jit/*.py \ + $(srcdir)/Python/executor_cases.c.h \ + pyconfig.h + +jit_stencils.h: $(JIT_DEPS) + @REGEN_JIT_COMMAND@ + +Python/jit.o: $(srcdir)/Python/jit.c @JIT_STENCILS_H@ + $(CC) -c $(PY_CORE_CFLAGS) -o $@ $< .PHONY: regen-jit regen-jit: diff --git a/configure b/configure index 1d41d7ba66470d7..0375565c2945528 100755 --- a/configure +++ b/configure @@ -920,6 +920,7 @@ LLVM_AR PROFILE_TASK DEF_MAKE_RULE DEF_MAKE_ALL_RULE +JIT_STENCILS_H REGEN_JIT_COMMAND ABIFLAGS LN @@ -8019,12 +8020,14 @@ then : else $as_nop as_fn_append CFLAGS_NODIST " -D_Py_JIT" REGEN_JIT_COMMAND="\$(PYTHON_FOR_REGEN) \$(srcdir)/Tools/jit/build.py $host" + JIT_STENCILS_H="jit_stencils.h" if test "x$Py_DEBUG" = xtrue then : as_fn_append REGEN_JIT_COMMAND " --debug" fi fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $enable_experimental_jit" >&5 printf "%s\n" "$enable_experimental_jit" >&6; } diff --git a/configure.ac b/configure.ac index b29cd028f8f2004..e121e893a1d0d9e 100644 --- a/configure.ac +++ b/configure.ac @@ -1592,11 +1592,13 @@ AS_VAR_IF([enable_experimental_jit], [AS_VAR_APPEND([CFLAGS_NODIST], [" -D_Py_JIT"]) AS_VAR_SET([REGEN_JIT_COMMAND], ["\$(PYTHON_FOR_REGEN) \$(srcdir)/Tools/jit/build.py $host"]) + AS_VAR_SET([JIT_STENCILS_H], ["jit_stencils.h"]) AS_VAR_IF([Py_DEBUG], [true], [AS_VAR_APPEND([REGEN_JIT_COMMAND], [" --debug"])], [])]) AC_SUBST([REGEN_JIT_COMMAND]) +AC_SUBST([JIT_STENCILS_H]) AC_MSG_RESULT([$enable_experimental_jit]) # Enable optimization flags From 80734a6872105de874a424478cd0001e23286098 Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Sat, 3 Feb 2024 18:16:30 -0600 Subject: [PATCH 119/507] Update README.md (#114974) Trivial edit Co-authored-by: Carol Willing <carolcode@willingconsulting.com> --- Tools/wasm/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/wasm/README.md b/Tools/wasm/README.md index beb857f69e40da1..23b38c8e93638ad 100644 --- a/Tools/wasm/README.md +++ b/Tools/wasm/README.md @@ -83,7 +83,7 @@ embuilder --pic build zlib bzip2 MINIMAL_PIC ``` -#### Compile a build Python interpreter +### Compile and build Python interpreter From within the container, run the following command: From 848c86786be588312bff948441929816cdd19e28 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 4 Feb 2024 11:45:35 +0200 Subject: [PATCH 120/507] gh-101100: Fix Sphinx warnings from PEP 3108 stdlib re-organisation (#114327) * Fix Sphinx warnings from PEP 3108 stdblib re-organisation * Apply suggestions from code review Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> * Update Doc/whatsnew/2.2.rst Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> * Apply suggestions from code review Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> --- Doc/whatsnew/2.0.rst | 12 ++++++------ Doc/whatsnew/2.2.rst | 10 +++++----- Doc/whatsnew/2.4.rst | 16 ++++++++-------- Doc/whatsnew/2.5.rst | 18 +++++++++--------- Doc/whatsnew/2.6.rst | 42 +++++++++++++++++++++--------------------- Doc/whatsnew/2.7.rst | 44 ++++++++++++++++++++++---------------------- Doc/whatsnew/3.0.rst | 36 ++++++++++++++++++------------------ Doc/whatsnew/3.5.rst | 2 +- 8 files changed, 90 insertions(+), 90 deletions(-) diff --git a/Doc/whatsnew/2.0.rst b/Doc/whatsnew/2.0.rst index f4a9d23699de53c..af8171487fbcfa2 100644 --- a/Doc/whatsnew/2.0.rst +++ b/Doc/whatsnew/2.0.rst @@ -1039,12 +1039,12 @@ is an implementation of the Secure Socket Layer, which encrypts the data being sent over a socket. When compiling Python, you can edit :file:`Modules/Setup` to include SSL support, which adds an additional function to the :mod:`socket` module: ``socket.ssl(socket, keyfile, certfile)``, which takes a socket -object and returns an SSL socket. The :mod:`httplib` and :mod:`urllib` modules +object and returns an SSL socket. The :mod:`httplib <http>` and :mod:`urllib` modules were also changed to support ``https://`` URLs, though no one has implemented FTP or SMTP over SSL. -The :mod:`httplib` module has been rewritten by Greg Stein to support HTTP/1.1. -Backward compatibility with the 1.5 version of :mod:`httplib` is provided, +The :mod:`httplib <http>` module has been rewritten by Greg Stein to support HTTP/1.1. +Backward compatibility with the 1.5 version of :mod:`!httplib` is provided, though using HTTP/1.1 features such as pipelining will require rewriting code to use a different set of interfaces. @@ -1108,7 +1108,7 @@ module. * :mod:`pyexpat`: An interface to the Expat XML parser. (Contributed by Paul Prescod.) -* :mod:`robotparser`: Parse a :file:`robots.txt` file, which is used for writing +* :mod:`robotparser <urllib.robotparser>`: Parse a :file:`robots.txt` file, which is used for writing web spiders that politely avoid certain areas of a web site. The parser accepts the contents of a :file:`robots.txt` file, builds a set of rules from it, and can then answer questions about the fetchability of a given URL. (Contributed @@ -1129,10 +1129,10 @@ module. :file:`Tools/idle/BrowserControl.py`, and adapted for the standard library by Fred.) -* :mod:`_winreg`: An interface to the Windows registry. :mod:`_winreg` is an +* :mod:`_winreg <winreg>`: An interface to the Windows registry. :mod:`!_winreg` is an adaptation of functions that have been part of PythonWin since 1995, but has now been added to the core distribution, and enhanced to support Unicode. - :mod:`_winreg` was written by Bill Tutt and Mark Hammond. + :mod:`!_winreg` was written by Bill Tutt and Mark Hammond. * :mod:`zipfile`: A module for reading and writing ZIP-format archives. These are archives produced by :program:`PKZIP` on DOS/Windows or :program:`zip` on diff --git a/Doc/whatsnew/2.2.rst b/Doc/whatsnew/2.2.rst index 968bd7a126bdf0b..e6c13f957b8d54a 100644 --- a/Doc/whatsnew/2.2.rst +++ b/Doc/whatsnew/2.2.rst @@ -55,7 +55,7 @@ implemented in C. In particular, it's not possible to subclass built-in types, so you can't just subclass, say, lists in order to add a single useful method to them. The :mod:`!UserList` module provides a class that supports all of the methods of lists and that can be subclassed further, but there's lots of C code -that expects a regular Python list and won't accept a :class:`!UserList` +that expects a regular Python list and won't accept a :class:`~collections.UserList` instance. Python 2.2 fixes this, and in the process adds some exciting new capabilities. @@ -69,7 +69,7 @@ A brief summary: * It's also possible to automatically call methods on accessing or setting an instance attribute by using a new mechanism called :dfn:`properties`. Many uses - of :meth:`!__getattr__` can be rewritten to use properties instead, making the + of :meth:`~object.__getattr__` can be rewritten to use properties instead, making the resulting code simpler and faster. As a small side benefit, attributes can now have docstrings, too. @@ -933,7 +933,7 @@ anyway). New and Improved Modules ======================== -* The :mod:`!xmlrpclib` module was contributed to the standard library by Fredrik +* The :mod:`xmlrpclib <xmlrpc.client>` module was contributed to the standard library by Fredrik Lundh, providing support for writing XML-RPC clients. XML-RPC is a simple remote procedure call protocol built on top of HTTP and XML. For example, the following snippet retrieves a list of RSS channels from the O'Reilly Network, @@ -956,7 +956,7 @@ New and Improved Modules # 'description': 'A utility which converts HTML to XSL FO.', # 'title': 'html2fo 0.3 (Default)'}, ... ] - The :mod:`!SimpleXMLRPCServer` module makes it easy to create straightforward + The :mod:`SimpleXMLRPCServer <xmlrpc.server>` module makes it easy to create straightforward XML-RPC servers. See http://xmlrpc.scripting.com/ for more information about XML-RPC. * The new :mod:`hmac` module implements the HMAC algorithm described by @@ -964,7 +964,7 @@ New and Improved Modules * Several functions that originally returned lengthy tuples now return pseudo-sequences that still behave like tuples but also have mnemonic attributes such - as :attr:`!memberst_mtime` or :attr:`!tm_year`. The enhanced functions include + as :attr:`!memberst_mtime` or :attr:`~time.struct_time.tm_year`. The enhanced functions include :func:`~os.stat`, :func:`~os.fstat`, :func:`~os.statvfs`, and :func:`~os.fstatvfs` in the :mod:`os` module, and :func:`~time.localtime`, :func:`~time.gmtime`, and :func:`~time.strptime` in the :mod:`time` module. diff --git a/Doc/whatsnew/2.4.rst b/Doc/whatsnew/2.4.rst index 15d4003622c5065..7e235d4370edaa9 100644 --- a/Doc/whatsnew/2.4.rst +++ b/Doc/whatsnew/2.4.rst @@ -1081,7 +1081,7 @@ complete list of changes, or look through the CVS logs for all the details. :func:`nsmallest` that use heaps to find the N largest or smallest values in a dataset without the expense of a full sort. (Contributed by Raymond Hettinger.) -* The :mod:`httplib` module now contains constants for HTTP status codes defined +* The :mod:`httplib <http>` module now contains constants for HTTP status codes defined in various HTTP-related RFC documents. Constants have names such as :const:`OK`, :const:`CREATED`, :const:`CONTINUE`, and :const:`MOVED_PERMANENTLY`; use pydoc to get a full list. (Contributed by @@ -1218,10 +1218,10 @@ complete list of changes, or look through the CVS logs for all the details. now include the string ``'%default'``, which will be replaced by the option's default value. (Contributed by Greg Ward.) -* The long-term plan is to deprecate the :mod:`rfc822` module in some future +* The long-term plan is to deprecate the :mod:`!rfc822` module in some future Python release in favor of the :mod:`email` package. To this end, the - :func:`email.Utils.formatdate` function has been changed to make it usable as a - replacement for :func:`rfc822.formatdate`. You may want to write new e-mail + :func:`email.Utils.formatdate <email.utils.formatdate>` function has been changed to make it usable as a + replacement for :func:`!rfc822.formatdate`. You may want to write new e-mail processing code with this in mind. (Change implemented by Anthony Baxter.) * A new ``urandom(n)`` function was added to the :mod:`os` module, returning @@ -1308,7 +1308,7 @@ complete list of changes, or look through the CVS logs for all the details. sockets, and regular expression pattern objects. (Contributed by Raymond Hettinger.) -* The :mod:`xmlrpclib` module now supports a multi-call extension for +* The :mod:`xmlrpclib <xmlrpc.client>` module now supports a multi-call extension for transmitting multiple XML-RPC calls in a single HTTP operation. (Contributed by Brian Quinlan.) @@ -1323,8 +1323,8 @@ complete list of changes, or look through the CVS logs for all the details. cookielib --------- -The :mod:`cookielib` library supports client-side handling for HTTP cookies, -mirroring the :mod:`Cookie` module's server-side cookie support. Cookies are +The :mod:`cookielib <http.cookiejar>` library supports client-side handling for HTTP cookies, +mirroring the :mod:`Cookie <http.cookies>` module's server-side cookie support. Cookies are stored in cookie jars; the library transparently stores cookies offered by the web server in the cookie jar, and fetches the cookie from the jar when connecting to the server. As in web browsers, policy objects control whether @@ -1335,7 +1335,7 @@ are provided: one that stores cookies in the Netscape format so applications can use the Mozilla or Lynx cookie files, and one that stores cookies in the same format as the Perl libwww library. -:mod:`urllib2` has been changed to interact with :mod:`cookielib`: +:mod:`urllib2 <urllib.request>` has been changed to interact with :mod:`cookielib <http.cookiejar>`: :class:`HTTPCookieProcessor` manages a cookie jar that is used when accessing URLs. diff --git a/Doc/whatsnew/2.5.rst b/Doc/whatsnew/2.5.rst index f45d70ea5a19a03..2ae26e7a106a0bf 100644 --- a/Doc/whatsnew/2.5.rst +++ b/Doc/whatsnew/2.5.rst @@ -1478,8 +1478,8 @@ complete list of changes, or look through the SVN logs for all the details. .. Patch 790710 -* The :mod:`pickle` and :mod:`cPickle` modules no longer accept a return value - of ``None`` from the :meth:`__reduce__` method; the method must return a tuple +* The :mod:`pickle` and :mod:`!cPickle` modules no longer accept a return value + of ``None`` from the :meth:`~object.__reduce__` method; the method must return a tuple of arguments instead. The ability to return ``None`` was deprecated in Python 2.4, so this completes the removal of the feature. @@ -1519,7 +1519,7 @@ complete list of changes, or look through the SVN logs for all the details. .. Patch #1472854 -* The :mod:`SimpleXMLRPCServer` and :mod:`DocXMLRPCServer` classes now have a +* The :mod:`SimpleXMLRPCServer <xmlrpc.server>` and :mod:`DocXMLRPCServer <xmlrpc.server>` classes now have a :attr:`rpc_paths` attribute that constrains XML-RPC operations to a limited set of URL paths; the default is to allow only ``'/'`` and ``'/RPC2'``. Setting :attr:`rpc_paths` to ``None`` or an empty tuple disables this path checking. @@ -1650,9 +1650,9 @@ complete list of changes, or look through the SVN logs for all the details. .. Patch #754022 -* The :mod:`xmlrpclib` module now supports returning :class:`~datetime.datetime` objects - for the XML-RPC date type. Supply ``use_datetime=True`` to the :func:`loads` - function or the :class:`Unmarshaller` class to enable this feature. (Contributed +* The :mod:`xmlrpclib <xmlrpc.client>` module now supports returning :class:`~datetime.datetime` objects + for the XML-RPC date type. Supply ``use_datetime=True`` to the :func:`~xmlrpc.client.loads` + function or the :class:`!Unmarshaller` class to enable this feature. (Contributed by Skip Montanaro.) .. Patch 1120353 @@ -2253,12 +2253,12 @@ code: appeared. In Python 2.5, the argument must be exactly one %char specifier with no surrounding text. -* Library: The :mod:`pickle` and :mod:`cPickle` modules no longer accept a - return value of ``None`` from the :meth:`__reduce__` method; the method must +* Library: The :mod:`pickle` and :mod:`!cPickle` modules no longer accept a + return value of ``None`` from the :meth:`~object.__reduce__` method; the method must return a tuple of arguments instead. The modules also no longer accept the deprecated *bin* keyword parameter. -* Library: The :mod:`SimpleXMLRPCServer` and :mod:`DocXMLRPCServer` classes now +* Library: The :mod:`SimpleXMLRPCServer <xmlrpc.server>` and :mod:`DocXMLRPCServer <xmlrpc.server>` classes now have a :attr:`rpc_paths` attribute that constrains XML-RPC operations to a limited set of URL paths; the default is to allow only ``'/'`` and ``'/RPC2'``. Setting :attr:`rpc_paths` to ``None`` or an empty tuple disables this path diff --git a/Doc/whatsnew/2.6.rst b/Doc/whatsnew/2.6.rst index c6bab93b7efdda1..7d3769a22286e28 100644 --- a/Doc/whatsnew/2.6.rst +++ b/Doc/whatsnew/2.6.rst @@ -1082,7 +1082,7 @@ the :mod:`io` module: (In Python 2.6, :class:`io.StringIO` is implemented in pure Python, so it's pretty slow. You should therefore stick with the - existing :mod:`StringIO` module or :mod:`cStringIO` for now. At some + existing :mod:`!StringIO` module or :mod:`!cStringIO` for now. At some point Python 3.0's :mod:`io` module will be rewritten into C for speed, and perhaps the C implementation will be backported to the 2.x releases.) @@ -1807,7 +1807,7 @@ changes, or look through the Subversion logs for all the details. Nubis; :issue:`1817`.) The :func:`parse_qs` and :func:`parse_qsl` functions have been - relocated from the :mod:`!cgi` module to the :mod:`urlparse` module. + relocated from the :mod:`!cgi` module to the :mod:`urlparse <urllib.parse>` module. The versions still available in the :mod:`!cgi` module will trigger :exc:`PendingDeprecationWarning` messages in 2.6 (:issue:`600362`). @@ -1895,8 +1895,8 @@ changes, or look through the Subversion logs for all the details. (Contributed by Raymond Hettinger.) -* The :mod:`Cookie` module's :class:`Morsel` objects now support an - :attr:`httponly` attribute. In some browsers. cookies with this attribute +* The :mod:`Cookie <http.cookies>` module's :class:`~http.cookies.Morsel` objects now support an + :attr:`~http.cookies.Morsel.httponly` attribute. In some browsers. cookies with this attribute set cannot be accessed or manipulated by JavaScript code. (Contributed by Arvin Schnell; :issue:`1638033`.) @@ -1987,8 +1987,8 @@ changes, or look through the Subversion logs for all the details. (Contributed by Raymond Hettinger.) * An optional ``timeout`` parameter, specifying a timeout measured in - seconds, was added to the :class:`httplib.HTTPConnection` and - :class:`HTTPSConnection` class constructors. (Added by Facundo + seconds, was added to the :class:`httplib.HTTPConnection <http.client.HTTPConnection>` and + :class:`HTTPSConnection <http.client.HTTPSConnection>` class constructors. (Added by Facundo Batista.) * Most of the :mod:`inspect` module's functions, such as @@ -2371,10 +2371,10 @@ changes, or look through the Subversion logs for all the details. ``socket(socket.AF_INET, ...)`` may be all that's required to make your code work with IPv6. -* The base classes in the :mod:`SocketServer` module now support - calling a :meth:`handle_timeout` method after a span of inactivity - specified by the server's :attr:`timeout` attribute. (Contributed - by Michael Pomraning.) The :meth:`serve_forever` method +* The base classes in the :mod:`SocketServer <socketserver>` module now support + calling a :meth:`~socketserver.BaseServer.handle_timeout` method after a span of inactivity + specified by the server's :attr:`~socketserver.BaseServer.timeout` attribute. (Contributed + by Michael Pomraning.) The :meth:`~socketserver.BaseServer.serve_forever` method now takes an optional poll interval measured in seconds, controlling how often the server will check for a shutdown request. (Contributed by Pedro Werneck and Jeffrey Yasskin; @@ -2478,9 +2478,9 @@ changes, or look through the Subversion logs for all the details. ``with tempfile.NamedTemporaryFile() as tmp: ...``. (Contributed by Alexander Belopolsky; :issue:`2021`.) -* The :mod:`test.test_support` module gained a number +* The :mod:`test.test_support <test.support>` module gained a number of context managers useful for writing tests. - :func:`EnvironmentVarGuard` is a + :func:`~test.support.os_helper.EnvironmentVarGuard` is a context manager that temporarily changes environment variables and automatically restores them to their old values. @@ -2577,9 +2577,9 @@ changes, or look through the Subversion logs for all the details. (:issue:`1513695`) * An optional ``timeout`` parameter was added to the - :func:`urllib.urlopen` function and the + :func:`urllib.urlopen <urllib.request.urlopen>` function and the :class:`urllib.ftpwrapper` class constructor, as well as the - :func:`urllib2.urlopen` function. The parameter specifies a timeout + :func:`urllib2.urlopen <urllib.request.urlopen>` function. The parameter specifies a timeout measured in seconds. For example:: >>> u = urllib2.urlopen("http://slow.example.com", @@ -2604,7 +2604,7 @@ changes, or look through the Subversion logs for all the details. intended for testing purposes that lets you temporarily modify the warning filters and then restore their original values (:issue:`3781`). -* The XML-RPC :class:`SimpleXMLRPCServer` and :class:`DocXMLRPCServer` +* The XML-RPC :class:`SimpleXMLRPCServer <xmlrpc.server>` and :class:`DocXMLRPCServer <xmlrpc.server>` classes can now be prevented from immediately opening and binding to their socket by passing ``False`` as the *bind_and_activate* constructor parameter. This can be used to modify the instance's @@ -2621,11 +2621,11 @@ changes, or look through the Subversion logs for all the details. information. (Contributed by Alan McIntyre as part of his project for Google's Summer of Code 2007.) -* The :mod:`xmlrpclib` module no longer automatically converts +* The :mod:`xmlrpclib <xmlrpc.client>` module no longer automatically converts :class:`datetime.date` and :class:`datetime.time` to the - :class:`xmlrpclib.DateTime` type; the conversion semantics were + :class:`xmlrpclib.DateTime <xmlrpc.client.DateTime>` type; the conversion semantics were not necessarily correct for all applications. Code using - :mod:`xmlrpclib` should convert :class:`date` and :class:`~datetime.time` + :mod:`!xmlrpclib` should convert :class:`date` and :class:`~datetime.time` instances. (:issue:`1330538`) The code can also handle dates before 1900 (contributed by Ralf Schmitt; :issue:`2014`) and 64-bit integers represented by using ``<i8>`` in XML-RPC responses @@ -3274,11 +3274,11 @@ that may require changes to your code: :exc:`StandardError` but now it is, through :exc:`IOError`. (Implemented by Gregory P. Smith; :issue:`1706815`.) -* The :mod:`xmlrpclib` module no longer automatically converts +* The :mod:`xmlrpclib <xmlrpc.client>` module no longer automatically converts :class:`datetime.date` and :class:`datetime.time` to the - :class:`xmlrpclib.DateTime` type; the conversion semantics were + :class:`xmlrpclib.DateTime <xmlrpc.client.DateTime>` type; the conversion semantics were not necessarily correct for all applications. Code using - :mod:`xmlrpclib` should convert :class:`date` and :class:`~datetime.time` + :mod:`!xmlrpclib` should convert :class:`date` and :class:`~datetime.time` instances. (:issue:`1330538`) * (3.0-warning mode) The :class:`Exception` class now warns diff --git a/Doc/whatsnew/2.7.rst b/Doc/whatsnew/2.7.rst index 524967b45242344..ada05aa22b46f66 100644 --- a/Doc/whatsnew/2.7.rst +++ b/Doc/whatsnew/2.7.rst @@ -915,7 +915,7 @@ used with the :option:`-W` switch, separated by commas. (Contributed by Brian Curtin; :issue:`7301`.) For example, the following setting will print warnings every time -they occur, but turn warnings from the :mod:`Cookie` module into an +they occur, but turn warnings from the :mod:`Cookie <http.cookies>` module into an error. (The exact syntax for setting an environment variable varies across operating systems and shells.) @@ -1012,12 +1012,12 @@ Several performance enhancements have been added: scan. This is sometimes faster by a factor of 10. (Added by Florent Xicluna; :issue:`7462` and :issue:`7622`.) -* The :mod:`pickle` and :mod:`cPickle` modules now automatically +* The :mod:`pickle` and :mod:`!cPickle` modules now automatically intern the strings used for attribute names, reducing memory usage of the objects resulting from unpickling. (Contributed by Jake McGuire; :issue:`5084`.) -* The :mod:`cPickle` module now special-cases dictionaries, +* The :mod:`!cPickle` module now special-cases dictionaries, nearly halving the time required to pickle them. (Contributed by Collin Winter; :issue:`5670`.) @@ -1163,7 +1163,7 @@ changes, or look through the Subversion logs for all the details. statement, has been deprecated, because the :keyword:`!with` statement now supports multiple context managers. -* The :mod:`cookielib` module now ignores cookies that have an invalid +* The :mod:`cookielib <http.cookiejar>` module now ignores cookies that have an invalid version field, one that doesn't contain an integer value. (Fixed by John J. Lee; :issue:`3924`.) @@ -1306,11 +1306,11 @@ changes, or look through the Subversion logs for all the details. ``('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512')``. (Contributed by Carl Chenet; :issue:`7418`.) -* The default :class:`~httplib.HTTPResponse` class used by the :mod:`httplib` module now +* The default :class:`~http.client.HTTPResponse` class used by the :mod:`httplib <http>` module now supports buffering, resulting in much faster reading of HTTP responses. (Contributed by Kristján Valur Jónsson; :issue:`4879`.) - The :class:`~httplib.HTTPConnection` and :class:`~httplib.HTTPSConnection` classes + The :class:`~http.client.HTTPConnection` and :class:`~http.client.HTTPSConnection` classes now support a *source_address* parameter, a ``(host, port)`` 2-tuple giving the source address that will be used for the connection. (Contributed by Eldon Ziegler; :issue:`3972`.) @@ -1518,16 +1518,16 @@ changes, or look through the Subversion logs for all the details. the :class:`bytearray` and :class:`memoryview` objects. (Implemented by Antoine Pitrou; :issue:`8104`.) -* The :mod:`SocketServer` module's :class:`~SocketServer.TCPServer` class now +* The :mod:`SocketServer <socketserver>` module's :class:`~socketserver.TCPServer` class now supports socket timeouts and disabling the Nagle algorithm. - The :attr:`~SocketServer.TCPServer.disable_nagle_algorithm` class attribute + The :attr:`!disable_nagle_algorithm` class attribute defaults to ``False``; if overridden to be true, new request connections will have the TCP_NODELAY option set to prevent buffering many small sends into a single TCP packet. - The :attr:`~SocketServer.BaseServer.timeout` class attribute can hold + The :attr:`~socketserver.BaseServer.timeout` class attribute can hold a timeout in seconds that will be applied to the request socket; if - no request is received within that time, :meth:`~SocketServer.BaseServer.handle_timeout` - will be called and :meth:`~SocketServer.BaseServer.handle_request` will return. + no request is received within that time, :meth:`~socketserver.BaseServer.handle_timeout` + will be called and :meth:`~socketserver.BaseServer.handle_request` will return. (Contributed by Kristján Valur Jónsson; :issue:`6192` and :issue:`6267`.) * Updated module: the :mod:`sqlite3` module has been updated to @@ -1648,7 +1648,7 @@ changes, or look through the Subversion logs for all the details. and has been updated to version 5.2.0 (updated by Florent Xicluna; :issue:`8024`). -* The :mod:`urlparse` module's :func:`~urlparse.urlsplit` now handles +* The :mod:`urlparse <urllib.parse>` module's :func:`~urllib.parse.urlsplit` now handles unknown URL schemes in a fashion compliant with :rfc:`3986`: if the URL is of the form ``"<something>://..."``, the text before the ``://`` is treated as the scheme, even if it's a made-up scheme that @@ -1675,7 +1675,7 @@ changes, or look through the Subversion logs for all the details. (Python 2.7 actually produces slightly different output, since it returns a named tuple instead of a standard tuple.) - The :mod:`urlparse` module also supports IPv6 literal addresses as defined by + The :mod:`urlparse <urllib.parse>` module also supports IPv6 literal addresses as defined by :rfc:`2732` (contributed by Senthil Kumaran; :issue:`2987`). .. doctest:: @@ -1697,8 +1697,8 @@ changes, or look through the Subversion logs for all the details. or comment (which looks like ``<!-- comment -->``). (Patch by Neil Muller; :issue:`2746`.) -* The XML-RPC client and server, provided by the :mod:`xmlrpclib` and - :mod:`SimpleXMLRPCServer` modules, have improved performance by +* The XML-RPC client and server, provided by the :mod:`xmlrpclib <xmlrpc.client>` and + :mod:`SimpleXMLRPCServer <xmlrpc.server>` modules, have improved performance by supporting HTTP/1.1 keep-alive and by optionally using gzip encoding to compress the XML being exchanged. The gzip compression is controlled by the :attr:`encode_threshold` attribute of @@ -2334,11 +2334,11 @@ Port-Specific Changes: Windows and :data:`LIBRARIES_ASSEMBLY_NAME_PREFIX`. (Contributed by David Cournapeau; :issue:`4365`.) -* The :mod:`_winreg` module for accessing the registry now implements - the :func:`~_winreg.CreateKeyEx` and :func:`~_winreg.DeleteKeyEx` +* The :mod:`_winreg <winreg>` module for accessing the registry now implements + the :func:`~winreg.CreateKeyEx` and :func:`~winreg.DeleteKeyEx` functions, extended versions of previously supported functions that - take several extra arguments. The :func:`~_winreg.DisableReflectionKey`, - :func:`~_winreg.EnableReflectionKey`, and :func:`~_winreg.QueryReflectionKey` + take several extra arguments. The :func:`~winreg.DisableReflectionKey`, + :func:`~winreg.EnableReflectionKey`, and :func:`~winreg.QueryReflectionKey` were also tested and documented. (Implemented by Brian Curtin: :issue:`7347`.) @@ -2508,7 +2508,7 @@ In the standard library: which raises an exception if there's an error. (Changed by Lars Gustäbel; :issue:`7357`.) -* The :mod:`urlparse` module's :func:`~urlparse.urlsplit` now handles +* The :mod:`urlparse <urllib.parse>` module's :func:`~urllib.parse.urlsplit` now handles unknown URL schemes in a fashion compliant with :rfc:`3986`: if the URL is of the form ``"<something>://..."``, the text before the ``://`` is treated as the scheme, even if it's a made-up scheme that @@ -2711,8 +2711,8 @@ and :ref:`setuptools-index`. PEP 476: Enabling certificate verification by default for stdlib http clients ----------------------------------------------------------------------------- -:pep:`476` updated :mod:`httplib` and modules which use it, such as -:mod:`urllib2` and :mod:`xmlrpclib`, to now verify that the server +:pep:`476` updated :mod:`httplib <http>` and modules which use it, such as +:mod:`urllib2 <urllib.request>` and :mod:`xmlrpclib`, to now verify that the server presents a certificate which is signed by a Certificate Authority in the platform trust store and whose hostname matches the hostname being requested by default, significantly improving security for many applications. This diff --git a/Doc/whatsnew/3.0.rst b/Doc/whatsnew/3.0.rst index 1df5209f22c6a5b..888e6279754fc29 100644 --- a/Doc/whatsnew/3.0.rst +++ b/Doc/whatsnew/3.0.rst @@ -337,7 +337,7 @@ changed. (However, the standard library remains ASCII-only with the exception of contributor names in comments.) -* The :mod:`StringIO` and :mod:`cStringIO` modules are gone. Instead, +* The :mod:`!StringIO` and :mod:`!cStringIO` modules are gone. Instead, import the :mod:`io` module and use :class:`io.StringIO` or :class:`io.BytesIO` for text and data respectively. @@ -563,7 +563,7 @@ review: removal in Python 3.0 due to lack of use or because a better replacement exists. See :pep:`3108` for an exhaustive list. -* The :mod:`bsddb3` package was removed because its presence in the +* The :mod:`!bsddb3` package was removed because its presence in the core standard library has proved over time to be a particular burden for the core developers due to testing instability and Berkeley DB's release schedule. However, the package is alive and well, @@ -588,40 +588,40 @@ review: * A common pattern in Python 2.x is to have one version of a module implemented in pure Python, with an optional accelerated version implemented as a C extension; for example, :mod:`pickle` and - :mod:`cPickle`. This places the burden of importing the accelerated + :mod:`!cPickle`. This places the burden of importing the accelerated version and falling back on the pure Python version on each user of these modules. In Python 3.0, the accelerated versions are considered implementation details of the pure Python versions. Users should always import the standard version, which attempts to import the accelerated version and falls back to the pure Python - version. The :mod:`pickle` / :mod:`cPickle` pair received this + version. The :mod:`pickle` / :mod:`!cPickle` pair received this treatment. The :mod:`profile` module is on the list for 3.1. The - :mod:`StringIO` module has been turned into a class in the :mod:`io` + :mod:`!StringIO` module has been turned into a class in the :mod:`io` module. * Some related modules have been grouped into packages, and usually the submodule names have been simplified. The resulting new packages are: - * :mod:`dbm` (:mod:`anydbm`, :mod:`dbhash`, :mod:`dbm`, - :mod:`dumbdbm`, :mod:`gdbm`, :mod:`whichdb`). + * :mod:`dbm` (:mod:`!anydbm`, :mod:`!dbhash`, :mod:`!dbm`, + :mod:`!dumbdbm`, :mod:`!gdbm`, :mod:`!whichdb`). - * :mod:`html` (:mod:`HTMLParser`, :mod:`htmlentitydefs`). + * :mod:`html` (:mod:`!HTMLParser`, :mod:`!htmlentitydefs`). - * :mod:`http` (:mod:`httplib`, :mod:`BaseHTTPServer`, - :mod:`CGIHTTPServer`, :mod:`SimpleHTTPServer`, :mod:`Cookie`, - :mod:`cookielib`). + * :mod:`http` (:mod:`!httplib`, :mod:`!BaseHTTPServer`, + :mod:`!CGIHTTPServer`, :mod:`!SimpleHTTPServer`, :mod:`!Cookie`, + :mod:`!cookielib`). * :mod:`tkinter` (all :mod:`Tkinter`-related modules except :mod:`turtle`). The target audience of :mod:`turtle` doesn't really care about :mod:`tkinter`. Also note that as of Python 2.6, the functionality of :mod:`turtle` has been greatly enhanced. - * :mod:`urllib` (:mod:`urllib`, :mod:`urllib2`, :mod:`urlparse`, - :mod:`robotparse`). + * :mod:`urllib` (:mod:`!urllib`, :mod:`!urllib2`, :mod:`!urlparse`, + :mod:`!robotparse`). - * :mod:`xmlrpc` (:mod:`xmlrpclib`, :mod:`DocXMLRPCServer`, - :mod:`SimpleXMLRPCServer`). + * :mod:`xmlrpc` (:mod:`!xmlrpclib`, :mod:`!DocXMLRPCServer`, + :mod:`!SimpleXMLRPCServer`). Some other changes to standard library modules, not covered by :pep:`3108`: @@ -642,9 +642,9 @@ Some other changes to standard library modules, not covered by * Cleanup of the :mod:`operator` module: removed :func:`sequenceIncludes` and :func:`isCallable`. -* Cleanup of the :mod:`thread` module: :func:`acquire_lock` and - :func:`release_lock` are gone; use :func:`acquire` and - :func:`release` instead. +* Cleanup of the :mod:`!thread` module: :func:`!acquire_lock` and + :func:`!release_lock` are gone; use :meth:`~threading.Lock.acquire` and + :meth:`~threading.Lock.release` instead. * Cleanup of the :mod:`random` module: removed the :func:`jumpahead` API. diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst index 1c7a9270af0aabc..5c2ec230441b426 100644 --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -2418,7 +2418,7 @@ Changes in the Python API (Contributed by Victor Stinner in :issue:`21205`.) * The deprecated "strict" mode and argument of :class:`~html.parser.HTMLParser`, - :meth:`HTMLParser.error`, and the :exc:`HTMLParserError` exception have been + :meth:`!HTMLParser.error`, and the :exc:`!HTMLParserError` exception have been removed. (Contributed by Ezio Melotti in :issue:`15114`.) The *convert_charrefs* argument of :class:`~html.parser.HTMLParser` is now ``True`` by default. (Contributed by Berker Peksag in :issue:`21047`.) From ec69e1d0ddc9906e0fb755a5234aeabdc96450ab Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Sun, 4 Feb 2024 08:24:24 -0600 Subject: [PATCH 121/507] gh-101100: Fix dangling references in pickle.rst (#114972) Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> --- Doc/library/pickle.rst | 51 +++++++++++++++++++++--------------------- Doc/tools/.nitignore | 1 - 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Doc/library/pickle.rst b/Doc/library/pickle.rst index 1b718abfa481a02..acada092afb679b 100644 --- a/Doc/library/pickle.rst +++ b/Doc/library/pickle.rst @@ -356,7 +356,7 @@ The :mod:`pickle` module exports three classes, :class:`Pickler`, :func:`copyreg.pickle`. It is a mapping whose keys are classes and whose values are reduction functions. A reduction function takes a single argument of the associated class and should - conform to the same interface as a :meth:`__reduce__` + conform to the same interface as a :meth:`~object.__reduce__` method. By default, a pickler object will not have a @@ -376,7 +376,7 @@ The :mod:`pickle` module exports three classes, :class:`Pickler`, Special reducer that can be defined in :class:`Pickler` subclasses. This method has priority over any reducer in the :attr:`dispatch_table`. It - should conform to the same interface as a :meth:`__reduce__` method, and + should conform to the same interface as a :meth:`~object.__reduce__` method, and can optionally return ``NotImplemented`` to fallback on :attr:`dispatch_table`-registered reducers to pickle ``obj``. @@ -516,7 +516,7 @@ The following types can be pickled: * classes accessible from the top level of a module; -* instances of such classes whose the result of calling :meth:`__getstate__` +* instances of such classes whose the result of calling :meth:`~object.__getstate__` is picklable (see section :ref:`pickle-inst` for details). Attempts to pickle unpicklable objects will raise the :exc:`PicklingError` @@ -552,7 +552,7 @@ purpose, so you can fix bugs in a class or add methods to the class and still load objects that were created with an earlier version of the class. If you plan to have long-lived objects that will see many versions of a class, it may be worthwhile to put a version number in the objects so that suitable -conversions can be made by the class's :meth:`__setstate__` method. +conversions can be made by the class's :meth:`~object.__setstate__` method. .. _pickle-inst: @@ -567,7 +567,7 @@ customize, and control how class instances are pickled and unpickled. In most cases, no additional code is needed to make instances picklable. By default, pickle will retrieve the class and the attributes of an instance via -introspection. When a class instance is unpickled, its :meth:`__init__` method +introspection. When a class instance is unpickled, its :meth:`~object.__init__` method is usually *not* invoked. The default behaviour first creates an uninitialized instance and then restores the saved attributes. The following code shows an implementation of this behaviour:: @@ -658,30 +658,30 @@ methods: Refer to the section :ref:`pickle-state` for more information about how to use -the methods :meth:`__getstate__` and :meth:`__setstate__`. +the methods :meth:`~object.__getstate__` and :meth:`~object.__setstate__`. .. note:: - At unpickling time, some methods like :meth:`__getattr__`, - :meth:`__getattribute__`, or :meth:`__setattr__` may be called upon the + At unpickling time, some methods like :meth:`~object.__getattr__`, + :meth:`~object.__getattribute__`, or :meth:`~object.__setattr__` may be called upon the instance. In case those methods rely on some internal invariant being - true, the type should implement :meth:`__new__` to establish such an - invariant, as :meth:`__init__` is not called when unpickling an + true, the type should implement :meth:`~object.__new__` to establish such an + invariant, as :meth:`~object.__init__` is not called when unpickling an instance. .. index:: pair: copy; protocol As we shall see, pickle does not use directly the methods described above. In fact, these methods are part of the copy protocol which implements the -:meth:`__reduce__` special method. The copy protocol provides a unified +:meth:`~object.__reduce__` special method. The copy protocol provides a unified interface for retrieving the data necessary for pickling and copying objects. [#]_ -Although powerful, implementing :meth:`__reduce__` directly in your classes is +Although powerful, implementing :meth:`~object.__reduce__` directly in your classes is error prone. For this reason, class designers should use the high-level -interface (i.e., :meth:`__getnewargs_ex__`, :meth:`__getstate__` and -:meth:`__setstate__`) whenever possible. We will show, however, cases where -using :meth:`__reduce__` is the only option or leads to more efficient pickling +interface (i.e., :meth:`~object.__getnewargs_ex__`, :meth:`~object.__getstate__` and +:meth:`~object.__setstate__`) whenever possible. We will show, however, cases where +using :meth:`!__reduce__` is the only option or leads to more efficient pickling or both. .. method:: object.__reduce__() @@ -716,8 +716,9 @@ or both. These items will be appended to the object either using ``obj.append(item)`` or, in batch, using ``obj.extend(list_of_items)``. This is primarily used for list subclasses, but may be used by other - classes as long as they have :meth:`append` and :meth:`extend` methods with - the appropriate signature. (Whether :meth:`append` or :meth:`extend` is + classes as long as they have + :ref:`append and extend methods <typesseq-common>` with + the appropriate signature. (Whether :meth:`!append` or :meth:`!extend` is used depends on which pickle protocol version is used as well as the number of items to append, so both must be supported.) @@ -793,8 +794,8 @@ any other code which depends on pickling, then one can create a pickler with a private dispatch table. The global dispatch table managed by the :mod:`copyreg` module is -available as :data:`copyreg.dispatch_table`. Therefore, one may -choose to use a modified copy of :data:`copyreg.dispatch_table` as a +available as :data:`!copyreg.dispatch_table`. Therefore, one may +choose to use a modified copy of :data:`!copyreg.dispatch_table` as a private dispatch table. For example :: @@ -833,12 +834,12 @@ Handling Stateful Objects single: __setstate__() (copy protocol) Here's an example that shows how to modify pickling behavior for a class. -The :class:`TextReader` class opens a text file, and returns the line number and +The :class:`!TextReader` class below opens a text file, and returns the line number and line contents each time its :meth:`!readline` method is called. If a -:class:`TextReader` instance is pickled, all attributes *except* the file object +:class:`!TextReader` instance is pickled, all attributes *except* the file object member are saved. When the instance is unpickled, the file is reopened, and -reading resumes from the last location. The :meth:`__setstate__` and -:meth:`__getstate__` methods are used to implement this behavior. :: +reading resumes from the last location. The :meth:`!__setstate__` and +:meth:`!__getstate__` methods are used to implement this behavior. :: class TextReader: """Print and number lines in a text file.""" @@ -903,7 +904,7 @@ functions and classes. For those cases, it is possible to subclass from the :class:`Pickler` class and implement a :meth:`~Pickler.reducer_override` method. This method can return an -arbitrary reduction tuple (see :meth:`__reduce__`). It can alternatively return +arbitrary reduction tuple (see :meth:`~object.__reduce__`). It can alternatively return ``NotImplemented`` to fallback to the traditional behavior. If both the :attr:`~Pickler.dispatch_table` and @@ -971,7 +972,7 @@ provided by pickle protocol 5 and higher. Provider API ^^^^^^^^^^^^ -The large data objects to be pickled must implement a :meth:`__reduce_ex__` +The large data objects to be pickled must implement a :meth:`~object.__reduce_ex__` method specialized for protocol 5 and higher, which returns a :class:`PickleBuffer` instance (instead of e.g. a :class:`bytes` object) for any large data. diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 7127f30f240ce7a..f96478b45e44c00 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -46,7 +46,6 @@ Doc/library/mmap.rst Doc/library/multiprocessing.rst Doc/library/optparse.rst Doc/library/os.rst -Doc/library/pickle.rst Doc/library/pickletools.rst Doc/library/platform.rst Doc/library/plistlib.rst From ff7588b729a2a414ea189a2012904da3fbd1401c Mon Sep 17 00:00:00 2001 From: Ethan Furman <ethan@stoneleaf.us> Date: Sun, 4 Feb 2024 07:22:55 -0800 Subject: [PATCH 122/507] gh-114071: [Enum] update docs and code for tuples/subclasses (GH-114871) Update documentation with `__new__` and `__init__` entries. Support use of `auto()` in tuple subclasses on member assignment lines. Previously, auto() was only supported on the member definition line either solo or as part of a tuple: RED = auto() BLUE = auto(), 'azul' However, since Python itself supports using tuple subclasses where tuples are expected, e.g.: from collections import namedtuple T = namedtuple('T', 'first second third') def test(one, two, three): print(one, two, three) test(*T(4, 5, 6)) # 4 5 6 it made sense to also support tuple subclasses in enum definitions. --- Doc/library/enum.rst | 29 ++++++++++++++-- Lib/enum.py | 10 ++++-- Lib/test/test_enum.py | 34 +++++++++++++++++++ ...-02-01-10-19-11.gh-issue-114071.vkm2G_.rst | 1 + 4 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-01-10-19-11.gh-issue-114071.vkm2G_.rst diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index 07b15e23b2c10a4..f31e6ea848f3b2e 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -337,6 +337,17 @@ Data Types >>> PowersOfThree.SECOND.value 9 + .. method:: Enum.__init__(self, \*args, \**kwds) + + By default, does nothing. If multiple values are given in the member + assignment, those values become separate arguments to ``__init__``; e.g. + + >>> from enum import Enum + >>> class Weekday(Enum): + ... MONDAY = 1, 'Mon' + + ``Weekday.__init__()`` would be called as ``Weekday.__init__(self, 1, 'Mon')`` + .. method:: Enum.__init_subclass__(cls, \**kwds) A *classmethod* that is used to further configure subsequent subclasses. @@ -364,6 +375,18 @@ Data Types >>> Build('deBUG') <Build.DEBUG: 'debug'> + .. method:: Enum.__new__(cls, \*args, \**kwds) + + By default, doesn't exist. If specified, either in the enum class + definition or in a mixin class (such as ``int``), all values given + in the member assignment will be passed; e.g. + + >>> from enum import Enum + >>> class MyIntEnum(Enum): + ... SEVENTEEN = '1a', 16 + + results in the call ``int('1a', 16)`` and a value of ``17`` for the member. + .. method:: Enum.__repr__(self) Returns the string used for *repr()* calls. By default, returns the @@ -477,9 +500,9 @@ Data Types .. class:: Flag - *Flag* members support the bitwise operators ``&`` (*AND*), ``|`` (*OR*), - ``^`` (*XOR*), and ``~`` (*INVERT*); the results of those operators are members - of the enumeration. + ``Flag`` is the same as :class:`Enum`, but its members support the bitwise + operators ``&`` (*AND*), ``|`` (*OR*), ``^`` (*XOR*), and ``~`` (*INVERT*); + the results of those operators are members of the enumeration. .. method:: __contains__(self, value) diff --git a/Lib/enum.py b/Lib/enum.py index a8a50a583803758..98a8966f5eb1599 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -409,10 +409,11 @@ def __setitem__(self, key, value): if isinstance(value, auto): single = True value = (value, ) - if type(value) is tuple and any(isinstance(v, auto) for v in value): + if isinstance(value, tuple) and any(isinstance(v, auto) for v in value): # insist on an actual tuple, no subclasses, in keeping with only supporting # top-level auto() usage (not contained in any other data structure) auto_valued = [] + t = type(value) for v in value: if isinstance(v, auto): non_auto_store = False @@ -427,7 +428,12 @@ def __setitem__(self, key, value): if single: value = auto_valued[0] else: - value = tuple(auto_valued) + try: + # accepts iterable as multiple arguments? + value = t(auto_valued) + except TypeError: + # then pass them in singlely + value = t(*auto_valued) self._member_names[key] = None if non_auto_store: self._last_values.append(value) diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index d045739efa46b8b..39c1ae0ad5a0787 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -2344,6 +2344,40 @@ class SomeTuple(tuple, Enum): globals()['SomeTuple'] = SomeTuple test_pickle_dump_load(self.assertIs, SomeTuple.first) + def test_tuple_subclass_with_auto_1(self): + from collections import namedtuple + T = namedtuple('T', 'index desc') + class SomeEnum(T, Enum): + __qualname__ = 'SomeEnum' # needed for pickle protocol 4 + first = auto(), 'for the money' + second = auto(), 'for the show' + third = auto(), 'for the music' + self.assertIs(type(SomeEnum.first), SomeEnum) + self.assertEqual(SomeEnum.third.value, (3, 'for the music')) + self.assertIsInstance(SomeEnum.third.value, T) + self.assertEqual(SomeEnum.first.index, 1) + self.assertEqual(SomeEnum.second.desc, 'for the show') + globals()['SomeEnum'] = SomeEnum + globals()['T'] = T + test_pickle_dump_load(self.assertIs, SomeEnum.first) + + def test_tuple_subclass_with_auto_2(self): + from collections import namedtuple + T = namedtuple('T', 'index desc') + class SomeEnum(Enum): + __qualname__ = 'SomeEnum' # needed for pickle protocol 4 + first = T(auto(), 'for the money') + second = T(auto(), 'for the show') + third = T(auto(), 'for the music') + self.assertIs(type(SomeEnum.first), SomeEnum) + self.assertEqual(SomeEnum.third.value, (3, 'for the music')) + self.assertIsInstance(SomeEnum.third.value, T) + self.assertEqual(SomeEnum.first.value.index, 1) + self.assertEqual(SomeEnum.second.value.desc, 'for the show') + globals()['SomeEnum'] = SomeEnum + globals()['T'] = T + test_pickle_dump_load(self.assertIs, SomeEnum.first) + def test_duplicate_values_give_unique_enum_items(self): class AutoNumber(Enum): first = () diff --git a/Misc/NEWS.d/next/Library/2024-02-01-10-19-11.gh-issue-114071.vkm2G_.rst b/Misc/NEWS.d/next/Library/2024-02-01-10-19-11.gh-issue-114071.vkm2G_.rst new file mode 100644 index 000000000000000..587ce4d21576371 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-01-10-19-11.gh-issue-114071.vkm2G_.rst @@ -0,0 +1 @@ +Support tuple subclasses using auto() for enum member value. From fc060969117f5a5dc96c220eb91b1e2f863d71cf Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 4 Feb 2024 17:23:26 +0200 Subject: [PATCH 123/507] gh-83383: Always mark the dbm.dumb database as unmodified after open() and sync() (GH-114560) The directory file for a newly created database is now created immediately after opening instead of deferring this until synchronizing or closing. --- Lib/dbm/dumb.py | 4 +- Lib/test/test_dbm_dumb.py | 72 +++++++++++++++++++ ...4-01-25-19-22-17.gh-issue-83383.3GwO9v.rst | 5 ++ 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-25-19-22-17.gh-issue-83383.3GwO9v.rst diff --git a/Lib/dbm/dumb.py b/Lib/dbm/dumb.py index 754624ccc8f5008..def120ffc3778b6 100644 --- a/Lib/dbm/dumb.py +++ b/Lib/dbm/dumb.py @@ -98,7 +98,8 @@ def _update(self, flag): except OSError: if flag not in ('c', 'n'): raise - self._modified = True + with self._io.open(self._dirfile, 'w', encoding="Latin-1") as f: + self._chmod(self._dirfile) else: with f: for line in f: @@ -134,6 +135,7 @@ def _commit(self): # position; UTF-8, though, does care sometimes. entry = "%r, %r\n" % (key.decode('Latin-1'), pos_and_siz_pair) f.write(entry) + self._modified = False sync = _commit diff --git a/Lib/test/test_dbm_dumb.py b/Lib/test/test_dbm_dumb.py index a481175b3bfdbde..672f9092207cf62 100644 --- a/Lib/test/test_dbm_dumb.py +++ b/Lib/test/test_dbm_dumb.py @@ -246,9 +246,27 @@ def test_missing_data(self): _delete_files() with self.assertRaises(FileNotFoundError): dumbdbm.open(_fname, value) + self.assertFalse(os.path.exists(_fname + '.dat')) self.assertFalse(os.path.exists(_fname + '.dir')) self.assertFalse(os.path.exists(_fname + '.bak')) + for value in ('c', 'n'): + _delete_files() + with dumbdbm.open(_fname, value) as f: + self.assertTrue(os.path.exists(_fname + '.dat')) + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + self.assertFalse(os.path.exists(_fname + '.bak')) + + for value in ('c', 'n'): + _delete_files() + with dumbdbm.open(_fname, value) as f: + f['key'] = 'value' + self.assertTrue(os.path.exists(_fname + '.dat')) + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + self.assertTrue(os.path.exists(_fname + '.bak')) + def test_missing_index(self): with dumbdbm.open(_fname, 'n') as f: pass @@ -259,6 +277,60 @@ def test_missing_index(self): self.assertFalse(os.path.exists(_fname + '.dir')) self.assertFalse(os.path.exists(_fname + '.bak')) + for value in ('c', 'n'): + with dumbdbm.open(_fname, value) as f: + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + self.assertFalse(os.path.exists(_fname + '.bak')) + os.unlink(_fname + '.dir') + + for value in ('c', 'n'): + with dumbdbm.open(_fname, value) as f: + f['key'] = 'value' + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + self.assertTrue(os.path.exists(_fname + '.bak')) + os.unlink(_fname + '.dir') + os.unlink(_fname + '.bak') + + def test_sync_empty_unmodified(self): + with dumbdbm.open(_fname, 'n') as f: + pass + os.unlink(_fname + '.dir') + for value in ('c', 'n'): + with dumbdbm.open(_fname, value) as f: + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + f.sync() + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + os.unlink(_fname + '.dir') + f.sync() + self.assertFalse(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + self.assertFalse(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + + def test_sync_nonempty_unmodified(self): + with dumbdbm.open(_fname, 'n') as f: + pass + os.unlink(_fname + '.dir') + for value in ('c', 'n'): + with dumbdbm.open(_fname, value) as f: + f['key'] = 'value' + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + f.sync() + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertTrue(os.path.exists(_fname + '.bak')) + os.unlink(_fname + '.dir') + os.unlink(_fname + '.bak') + f.sync() + self.assertFalse(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + self.assertFalse(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + def test_invalid_flag(self): for flag in ('x', 'rf', None): with self.assertRaisesRegex(ValueError, diff --git a/Misc/NEWS.d/next/Library/2024-01-25-19-22-17.gh-issue-83383.3GwO9v.rst b/Misc/NEWS.d/next/Library/2024-01-25-19-22-17.gh-issue-83383.3GwO9v.rst new file mode 100644 index 000000000000000..e6336204dfa2369 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-25-19-22-17.gh-issue-83383.3GwO9v.rst @@ -0,0 +1,5 @@ +Synchronization of the :mod:`dbm.dumb` database is now no-op if there was no +modification since opening or last synchronization. +The directory file for a newly created empty :mod:`dbm.dumb` database is now +created immediately after opening instead of deferring this until +synchronizing or closing. From ca715e56a13feabc15c368898df6511613d18987 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 4 Feb 2024 17:25:21 +0200 Subject: [PATCH 124/507] gh-69893: Add the close() method for xml.etree.ElementTree.iterparse() iterator (GH-114534) --- Doc/library/xml.etree.elementtree.rst | 5 ++ Doc/whatsnew/3.13.rst | 8 ++ Lib/test/test_xml_etree.py | 85 ++++++++++++++++++- Lib/xml/etree/ElementTree.py | 9 +- ...4-01-24-17-25-18.gh-issue-69893.PQq5fR.rst | 2 + 5 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-24-17-25-18.gh-issue-69893.PQq5fR.rst diff --git a/Doc/library/xml.etree.elementtree.rst b/Doc/library/xml.etree.elementtree.rst index bb6773c361a9b41..75a7915c15240d2 100644 --- a/Doc/library/xml.etree.elementtree.rst +++ b/Doc/library/xml.etree.elementtree.rst @@ -625,6 +625,8 @@ Functions target. Returns an :term:`iterator` providing ``(event, elem)`` pairs; it has a ``root`` attribute that references the root element of the resulting XML tree once *source* is fully read. + The iterator has the :meth:`!close` method that closes the internal + file object if *source* is a filename. Note that while :func:`iterparse` builds the tree incrementally, it issues blocking reads on *source* (or the file it names). As such, it's unsuitable @@ -647,6 +649,9 @@ Functions .. versionchanged:: 3.8 The ``comment`` and ``pi`` events were added. + .. versionchanged:: 3.13 + Added the :meth:`!close` method. + .. function:: parse(source, parser=None) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index f17c6ec0775beff..77f4fce6c321fee 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -472,6 +472,14 @@ warnings warning may also be emitted when a decorated function or class is used at runtime. See :pep:`702`. (Contributed by Jelle Zijlstra in :gh:`104003`.) +xml.etree.ElementTree +--------------------- + +* Add the :meth:`!close` method for the iterator returned by + :func:`~xml.etree.ElementTree.iterparse` for explicit cleaning up. + (Contributed by Serhiy Storchaka in :gh:`69893`.) + + Optimizations ============= diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index 221545b315fa44d..a435ec7822ea0cb 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -555,6 +555,17 @@ def test_iterparse(self): ('end', '{namespace}root'), ]) + with open(SIMPLE_XMLFILE, 'rb') as source: + context = iterparse(source) + action, elem = next(context) + self.assertEqual((action, elem.tag), ('end', 'element')) + self.assertEqual([(action, elem.tag) for action, elem in context], [ + ('end', 'element'), + ('end', 'empty-element'), + ('end', 'root'), + ]) + self.assertEqual(context.root.tag, 'root') + events = () context = iterparse(SIMPLE_XMLFILE, events) self.assertEqual([(action, elem.tag) for action, elem in context], []) @@ -646,12 +657,81 @@ def test_iterparse(self): # Not exhausting the iterator still closes the resource (bpo-43292) with warnings_helper.check_no_resource_warning(self): - it = iterparse(TESTFN) + it = iterparse(SIMPLE_XMLFILE) del it + with warnings_helper.check_no_resource_warning(self): + it = iterparse(SIMPLE_XMLFILE) + it.close() + del it + + with warnings_helper.check_no_resource_warning(self): + it = iterparse(SIMPLE_XMLFILE) + action, elem = next(it) + self.assertEqual((action, elem.tag), ('end', 'element')) + del it, elem + + with warnings_helper.check_no_resource_warning(self): + it = iterparse(SIMPLE_XMLFILE) + action, elem = next(it) + it.close() + self.assertEqual((action, elem.tag), ('end', 'element')) + del it, elem + with self.assertRaises(FileNotFoundError): iterparse("nonexistent") + def test_iterparse_close(self): + iterparse = ET.iterparse + + it = iterparse(SIMPLE_XMLFILE) + it.close() + with self.assertRaises(StopIteration): + next(it) + it.close() # idempotent + + with open(SIMPLE_XMLFILE, 'rb') as source: + it = iterparse(source) + it.close() + self.assertFalse(source.closed) + with self.assertRaises(StopIteration): + next(it) + it.close() # idempotent + + it = iterparse(SIMPLE_XMLFILE) + action, elem = next(it) + self.assertEqual((action, elem.tag), ('end', 'element')) + it.close() + with self.assertRaises(StopIteration): + next(it) + it.close() # idempotent + + with open(SIMPLE_XMLFILE, 'rb') as source: + it = iterparse(source) + action, elem = next(it) + self.assertEqual((action, elem.tag), ('end', 'element')) + it.close() + self.assertFalse(source.closed) + with self.assertRaises(StopIteration): + next(it) + it.close() # idempotent + + it = iterparse(SIMPLE_XMLFILE) + list(it) + it.close() + with self.assertRaises(StopIteration): + next(it) + it.close() # idempotent + + with open(SIMPLE_XMLFILE, 'rb') as source: + it = iterparse(source) + list(it) + it.close() + self.assertFalse(source.closed) + with self.assertRaises(StopIteration): + next(it) + it.close() # idempotent + def test_writefile(self): elem = ET.Element("tag") elem.text = "text" @@ -3044,8 +3124,7 @@ def test_basic(self): # With an explicit parser too (issue #9708) sourcefile = serialize(doc, to_string=False) parser = ET.XMLParser(target=ET.TreeBuilder()) - self.assertEqual(next(ET.iterparse(sourcefile, parser=parser))[0], - 'end') + self.assertEqual(next(ET.iterparse(sourcefile, parser=parser))[0], 'end') tree = ET.ElementTree(None) self.assertRaises(AttributeError, tree.iter) diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index bb7362d1634a726..a37fead41b750e4 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -1248,10 +1248,17 @@ def iterator(source): if close_source: source.close() + gen = iterator(source) class IterParseIterator(collections.abc.Iterator): - __next__ = iterator(source).__next__ + __next__ = gen.__next__ + def close(self): + if close_source: + source.close() + gen.close() def __del__(self): + # TODO: Emit a ResourceWarning if it was not explicitly closed. + # (When the close() method will be supported in all maintained Python versions.) if close_source: source.close() diff --git a/Misc/NEWS.d/next/Library/2024-01-24-17-25-18.gh-issue-69893.PQq5fR.rst b/Misc/NEWS.d/next/Library/2024-01-24-17-25-18.gh-issue-69893.PQq5fR.rst new file mode 100644 index 000000000000000..1ebf434c33187bf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-24-17-25-18.gh-issue-69893.PQq5fR.rst @@ -0,0 +1,2 @@ +Add the :meth:`!close` method for the iterator returned by +:func:`xml.etree.ElementTree.iterparse`. From ecabff98c41453f15ecd26ac255d531b571b9bc1 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 4 Feb 2024 17:27:42 +0200 Subject: [PATCH 125/507] gh-113267: Revert "gh-106584: Fix exit code for unittest in Python 3.12 (#106588)" (GH-114470) This reverts commit 8fc071345b50dd3de61ebeeaa287ccef21d061b2. --- Lib/test/test_unittest/test_discovery.py | 2 +- Lib/test/test_unittest/test_skipping.py | 12 ++++++------ Lib/unittest/case.py | 4 +--- Lib/unittest/result.py | 10 ++++------ .../2024-01-23-11-04-21.gh-issue-113267.xe_Pxe.rst | 2 ++ 5 files changed, 14 insertions(+), 16 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-23-11-04-21.gh-issue-113267.xe_Pxe.rst diff --git a/Lib/test/test_unittest/test_discovery.py b/Lib/test/test_unittest/test_discovery.py index dcb72d73efceabb..004898ed4318348 100644 --- a/Lib/test/test_unittest/test_discovery.py +++ b/Lib/test/test_unittest/test_discovery.py @@ -571,7 +571,7 @@ def _get_module_from_name(name): result = unittest.TestResult() suite.run(result) self.assertEqual(len(result.skipped), 1) - self.assertEqual(result.testsRun, 0) + self.assertEqual(result.testsRun, 1) self.assertEqual(import_calls, ['my_package']) # Check picklability diff --git a/Lib/test/test_unittest/test_skipping.py b/Lib/test/test_unittest/test_skipping.py index 1a6af06d32b4339..f146dcac18ecc09 100644 --- a/Lib/test/test_unittest/test_skipping.py +++ b/Lib/test/test_unittest/test_skipping.py @@ -103,16 +103,16 @@ def test_dont_skip(self): pass result = LoggingResult(events) self.assertIs(suite.run(result), result) self.assertEqual(len(result.skipped), 1) - expected = ['addSkip', 'stopTest', 'startTest', - 'addSuccess', 'stopTest'] + expected = ['startTest', 'addSkip', 'stopTest', + 'startTest', 'addSuccess', 'stopTest'] self.assertEqual(events, expected) - self.assertEqual(result.testsRun, 1) + self.assertEqual(result.testsRun, 2) self.assertEqual(result.skipped, [(test_do_skip, "testing")]) self.assertTrue(result.wasSuccessful()) events = [] result = test_do_skip.run() - self.assertEqual(events, ['startTestRun', 'addSkip', + self.assertEqual(events, ['startTestRun', 'startTest', 'addSkip', 'stopTest', 'stopTestRun']) self.assertEqual(result.skipped, [(test_do_skip, "testing")]) @@ -135,13 +135,13 @@ def test_1(self): test = Foo("test_1") suite = unittest.TestSuite([test]) self.assertIs(suite.run(result), result) - self.assertEqual(events, ['addSkip', 'stopTest']) + self.assertEqual(events, ['startTest', 'addSkip', 'stopTest']) self.assertEqual(result.skipped, [(test, "testing")]) self.assertEqual(record, []) events = [] result = test.run() - self.assertEqual(events, ['startTestRun', 'addSkip', + self.assertEqual(events, ['startTestRun', 'startTest', 'addSkip', 'stopTest', 'stopTestRun']) self.assertEqual(result.skipped, [(test, "testing")]) self.assertEqual(record, []) diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index 811557498bb30ed..001b640dc43ad69 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -606,6 +606,7 @@ def run(self, result=None): else: stopTestRun = None + result.startTest(self) try: testMethod = getattr(self, self._testMethodName) if (getattr(self.__class__, "__unittest_skip__", False) or @@ -616,9 +617,6 @@ def run(self, result=None): _addSkip(result, self, skip_why) return result - # Increase the number of tests only if it hasn't been skipped - result.startTest(self) - expecting_failure = ( getattr(self, "__unittest_expecting_failure__", False) or getattr(testMethod, "__unittest_expecting_failure__", False) diff --git a/Lib/unittest/result.py b/Lib/unittest/result.py index 9e56f658027f4de..3ace0a5b7bf2efb 100644 --- a/Lib/unittest/result.py +++ b/Lib/unittest/result.py @@ -97,12 +97,10 @@ def _restoreStdout(self): sys.stdout = self._original_stdout sys.stderr = self._original_stderr - if self._stdout_buffer is not None: - self._stdout_buffer.seek(0) - self._stdout_buffer.truncate() - if self._stderr_buffer is not None: - self._stderr_buffer.seek(0) - self._stderr_buffer.truncate() + self._stdout_buffer.seek(0) + self._stdout_buffer.truncate() + self._stderr_buffer.seek(0) + self._stderr_buffer.truncate() def stopTestRun(self): """Called once after all tests are executed. diff --git a/Misc/NEWS.d/next/Library/2024-01-23-11-04-21.gh-issue-113267.xe_Pxe.rst b/Misc/NEWS.d/next/Library/2024-01-23-11-04-21.gh-issue-113267.xe_Pxe.rst new file mode 100644 index 000000000000000..ad8aaf9250f6d8e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-23-11-04-21.gh-issue-113267.xe_Pxe.rst @@ -0,0 +1,2 @@ +Revert changes in :gh:`106584` which made calls of ``TestResult`` methods +``startTest()`` and ``stopTest()`` unbalanced. From 0ea366240b75380ed7568acbe95d72e481a734f7 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 4 Feb 2024 17:28:07 +0200 Subject: [PATCH 126/507] gh-113280: Always close socket if SSLSocket creation failed (GH-114659) Co-authored-by: Thomas Grainger <tagrain@gmail.com> --- Lib/ssl.py | 107 +++++++++--------- Lib/test/test_ssl.py | 33 ++++-- ...-01-27-20-11-24.gh-issue-113280.CZPQMf.rst | 2 + 3 files changed, 78 insertions(+), 64 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-27-20-11-24.gh-issue-113280.CZPQMf.rst diff --git a/Lib/ssl.py b/Lib/ssl.py index 74a9d2d8fd4fb01..03d0121891ff4cb 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -994,71 +994,67 @@ def _create(cls, sock, server_side=False, do_handshake_on_connect=True, if context.check_hostname and not server_hostname: raise ValueError("check_hostname requires server_hostname") + sock_timeout = sock.gettimeout() kwargs = dict( family=sock.family, type=sock.type, proto=sock.proto, fileno=sock.fileno() ) self = cls.__new__(cls, **kwargs) super(SSLSocket, self).__init__(**kwargs) - sock_timeout = sock.gettimeout() sock.detach() - - self._context = context - self._session = session - self._closed = False - self._sslobj = None - self.server_side = server_side - self.server_hostname = context._encode_hostname(server_hostname) - self.do_handshake_on_connect = do_handshake_on_connect - self.suppress_ragged_eofs = suppress_ragged_eofs - - # See if we are connected + # Now SSLSocket is responsible for closing the file descriptor. try: - self.getpeername() - except OSError as e: - if e.errno != errno.ENOTCONN: - raise - connected = False - blocking = self.getblocking() - self.setblocking(False) + self._context = context + self._session = session + self._closed = False + self._sslobj = None + self.server_side = server_side + self.server_hostname = context._encode_hostname(server_hostname) + self.do_handshake_on_connect = do_handshake_on_connect + self.suppress_ragged_eofs = suppress_ragged_eofs + + # See if we are connected try: - # We are not connected so this is not supposed to block, but - # testing revealed otherwise on macOS and Windows so we do - # the non-blocking dance regardless. Our raise when any data - # is found means consuming the data is harmless. - notconn_pre_handshake_data = self.recv(1) + self.getpeername() except OSError as e: - # EINVAL occurs for recv(1) on non-connected on unix sockets. - if e.errno not in (errno.ENOTCONN, errno.EINVAL): + if e.errno != errno.ENOTCONN: raise - notconn_pre_handshake_data = b'' - self.setblocking(blocking) - if notconn_pre_handshake_data: - # This prevents pending data sent to the socket before it was - # closed from escaping to the caller who could otherwise - # presume it came through a successful TLS connection. - reason = "Closed before TLS handshake with data in recv buffer." - notconn_pre_handshake_data_error = SSLError(e.errno, reason) - # Add the SSLError attributes that _ssl.c always adds. - notconn_pre_handshake_data_error.reason = reason - notconn_pre_handshake_data_error.library = None - try: - self.close() - except OSError: - pass + connected = False + blocking = self.getblocking() + self.setblocking(False) try: - raise notconn_pre_handshake_data_error - finally: - # Explicitly break the reference cycle. - notconn_pre_handshake_data_error = None - else: - connected = True + # We are not connected so this is not supposed to block, but + # testing revealed otherwise on macOS and Windows so we do + # the non-blocking dance regardless. Our raise when any data + # is found means consuming the data is harmless. + notconn_pre_handshake_data = self.recv(1) + except OSError as e: + # EINVAL occurs for recv(1) on non-connected on unix sockets. + if e.errno not in (errno.ENOTCONN, errno.EINVAL): + raise + notconn_pre_handshake_data = b'' + self.setblocking(blocking) + if notconn_pre_handshake_data: + # This prevents pending data sent to the socket before it was + # closed from escaping to the caller who could otherwise + # presume it came through a successful TLS connection. + reason = "Closed before TLS handshake with data in recv buffer." + notconn_pre_handshake_data_error = SSLError(e.errno, reason) + # Add the SSLError attributes that _ssl.c always adds. + notconn_pre_handshake_data_error.reason = reason + notconn_pre_handshake_data_error.library = None + try: + raise notconn_pre_handshake_data_error + finally: + # Explicitly break the reference cycle. + notconn_pre_handshake_data_error = None + else: + connected = True - self.settimeout(sock_timeout) # Must come after setblocking() calls. - self._connected = connected - if connected: - # create the SSL object - try: + self.settimeout(sock_timeout) # Must come after setblocking() calls. + self._connected = connected + if connected: + # create the SSL object self._sslobj = self._context._wrap_socket( self, server_side, self.server_hostname, owner=self, session=self._session, @@ -1069,9 +1065,12 @@ def _create(cls, sock, server_side=False, do_handshake_on_connect=True, # non-blocking raise ValueError("do_handshake_on_connect should not be specified for non-blocking sockets") self.do_handshake() - except (OSError, ValueError): + except: + try: self.close() - raise + except OSError: + pass + raise return self @property diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 3fdfa2960503b8f..1b18230d83577dc 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -2206,14 +2206,15 @@ def _test_get_server_certificate(test, host, port, cert=None): sys.stdout.write("\nVerified certificate for %s:%s is\n%s\n" % (host, port ,pem)) def _test_get_server_certificate_fail(test, host, port): - try: - pem = ssl.get_server_certificate((host, port), ca_certs=CERTFILE) - except ssl.SSLError as x: - #should fail - if support.verbose: - sys.stdout.write("%s\n" % x) - else: - test.fail("Got server certificate %s for %s:%s!" % (pem, host, port)) + with warnings_helper.check_no_resource_warning(test): + try: + pem = ssl.get_server_certificate((host, port), ca_certs=CERTFILE) + except ssl.SSLError as x: + #should fail + if support.verbose: + sys.stdout.write("%s\n" % x) + else: + test.fail("Got server certificate %s for %s:%s!" % (pem, host, port)) from test.ssl_servers import make_https_server @@ -3026,6 +3027,16 @@ def test_check_hostname_idn(self): server_hostname="python.example.org") as s: with self.assertRaises(ssl.CertificateError): s.connect((HOST, server.port)) + with ThreadedEchoServer(context=server_context, chatty=True) as server: + with warnings_helper.check_no_resource_warning(self): + with self.assertRaises(UnicodeError): + context.wrap_socket(socket.socket(), + server_hostname='.pythontest.net') + with ThreadedEchoServer(context=server_context, chatty=True) as server: + with warnings_helper.check_no_resource_warning(self): + with self.assertRaises(UnicodeDecodeError): + context.wrap_socket(socket.socket(), + server_hostname=b'k\xf6nig.idn.pythontest.net') def test_wrong_cert_tls12(self): """Connecting when the server rejects the client's certificate @@ -4983,7 +4994,8 @@ def call_after_accept(conn_to_client): self.assertIsNone(wrap_error.library, msg="attr must exist") finally: # gh-108342: Explicitly break the reference cycle - wrap_error = None + with warnings_helper.check_no_resource_warning(self): + wrap_error = None server = None def test_https_client_non_tls_response_ignored(self): @@ -5032,7 +5044,8 @@ def call_after_accept(conn_to_client): # socket; that fails if the connection is broken. It may seem pointless # to test this. It serves as an illustration of something that we never # want to happen... properly not happening. - with self.assertRaises(OSError): + with warnings_helper.check_no_resource_warning(self), \ + self.assertRaises(OSError): connection.request("HEAD", "/test", headers={"Host": "localhost"}) response = connection.getresponse() diff --git a/Misc/NEWS.d/next/Library/2024-01-27-20-11-24.gh-issue-113280.CZPQMf.rst b/Misc/NEWS.d/next/Library/2024-01-27-20-11-24.gh-issue-113280.CZPQMf.rst new file mode 100644 index 000000000000000..3dcdbcf0995616a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-27-20-11-24.gh-issue-113280.CZPQMf.rst @@ -0,0 +1,2 @@ +Fix a leak of open socket in rare cases when error occurred in +:class:`ssl.SSLSocket` creation. From 3ddc5152550ea62280124c37d0b4339030ff7df4 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 4 Feb 2024 17:32:25 +0200 Subject: [PATCH 127/507] gh-114388: Fix warnings when assign an unsigned integer member (GH-114391) * Fix a RuntimeWarning emitted when assign an integer-like value that is not an instance of int to an attribute that corresponds to a C struct member of type T_UINT and T_ULONG. * Fix a double RuntimeWarning emitted when assign a negative integer value to an attribute that corresponds to a C struct member of type T_UINT. --- Lib/test/test_capi/test_structmembers.py | 37 +++++++++ ...-01-21-17-29-32.gh-issue-114388.UVGO4K.rst | 5 ++ Python/structmember.c | 83 ++++++++++++------- 3 files changed, 97 insertions(+), 28 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-21-17-29-32.gh-issue-114388.UVGO4K.rst diff --git a/Lib/test/test_capi/test_structmembers.py b/Lib/test/test_capi/test_structmembers.py index 2cf46b203478dc2..415b8033bd16b3c 100644 --- a/Lib/test/test_capi/test_structmembers.py +++ b/Lib/test/test_capi/test_structmembers.py @@ -14,6 +14,13 @@ PY_SSIZE_T_MAX, PY_SSIZE_T_MIN, ) + +class Index: + def __init__(self, value): + self.value = value + def __index__(self): + return self.value + # There are two classes: one using <structmember.h> and another using # `Py_`-prefixed API. They should behave the same in Python @@ -72,6 +79,10 @@ def test_int(self): self.assertEqual(ts.T_INT, INT_MIN) ts.T_UINT = UINT_MAX self.assertEqual(ts.T_UINT, UINT_MAX) + ts.T_UINT = Index(0) + self.assertEqual(ts.T_UINT, 0) + ts.T_UINT = Index(INT_MAX) + self.assertEqual(ts.T_UINT, INT_MAX) def test_long(self): ts = self.ts @@ -81,6 +92,10 @@ def test_long(self): self.assertEqual(ts.T_LONG, LONG_MIN) ts.T_ULONG = ULONG_MAX self.assertEqual(ts.T_ULONG, ULONG_MAX) + ts.T_ULONG = Index(0) + self.assertEqual(ts.T_ULONG, 0) + ts.T_ULONG = Index(LONG_MAX) + self.assertEqual(ts.T_ULONG, LONG_MAX) def test_py_ssize_t(self): ts = self.ts @@ -173,6 +188,28 @@ def test_ushort_max(self): with warnings_helper.check_warnings(('', RuntimeWarning)): ts.T_USHORT = USHRT_MAX+1 + def test_int(self): + ts = self.ts + if LONG_MIN < INT_MIN: + with self.assertWarns(RuntimeWarning): + ts.T_INT = INT_MIN-1 + if LONG_MAX > INT_MAX: + with self.assertWarns(RuntimeWarning): + ts.T_INT = INT_MAX+1 + + def test_uint(self): + ts = self.ts + with self.assertWarns(RuntimeWarning): + ts.T_UINT = -1 + if ULONG_MAX > UINT_MAX: + with self.assertWarns(RuntimeWarning): + ts.T_UINT = UINT_MAX+1 + + def test_ulong(self): + ts = self.ts + with self.assertWarns(RuntimeWarning): + ts.T_ULONG = -1 + class TestWarnings_OldAPI(TestWarnings, unittest.TestCase): cls = _test_structmembersType_OldAPI diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-21-17-29-32.gh-issue-114388.UVGO4K.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-21-17-29-32.gh-issue-114388.UVGO4K.rst new file mode 100644 index 000000000000000..52c2742001d9ca6 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-21-17-29-32.gh-issue-114388.UVGO4K.rst @@ -0,0 +1,5 @@ +Fix a :exc:`RuntimeWarning` emitted when assign an integer-like value that +is not an instance of :class:`int` to an attribute that corresponds to a C +struct member of :ref:`type <PyMemberDef-types>` T_UINT and T_ULONG. Fix a +double :exc:`RuntimeWarning` emitted when assign a negative integer value to +an attribute that corresponds to a C struct member of type T_UINT. diff --git a/Python/structmember.c b/Python/structmember.c index 7a5a6a49d231167..18bd486952419b7 100644 --- a/Python/structmember.c +++ b/Python/structmember.c @@ -197,45 +197,72 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v) WARN("Truncation of value to int"); break; } - case Py_T_UINT:{ - unsigned long ulong_val = PyLong_AsUnsignedLong(v); - if ((ulong_val == (unsigned long)-1) && PyErr_Occurred()) { - /* XXX: For compatibility, accept negative int values - as well. */ - PyErr_Clear(); - ulong_val = PyLong_AsLong(v); - if ((ulong_val == (unsigned long)-1) && - PyErr_Occurred()) + case Py_T_UINT: { + /* XXX: For compatibility, accept negative int values + as well. */ + int overflow; + long long_val = PyLong_AsLongAndOverflow(v, &overflow); + if (long_val == -1 && PyErr_Occurred()) { + return -1; + } + if (overflow < 0) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C long"); + } + else if (!overflow) { + *(unsigned int *)addr = (unsigned int)(unsigned long)long_val; + if (long_val < 0) { + WARN("Writing negative value into unsigned field"); + } + else if ((unsigned long)long_val > UINT_MAX) { + WARN("Truncation of value to unsigned short"); + } + } + else { + unsigned long ulong_val = PyLong_AsUnsignedLong(v); + if (ulong_val == (unsigned long)-1 && PyErr_Occurred()) { return -1; - *(unsigned int *)addr = (unsigned int)ulong_val; - WARN("Writing negative value into unsigned field"); - } else - *(unsigned int *)addr = (unsigned int)ulong_val; - if (ulong_val > UINT_MAX) - WARN("Truncation of value to unsigned int"); - break; + } + *(unsigned int*)addr = (unsigned int)ulong_val; + if (ulong_val > UINT_MAX) { + WARN("Truncation of value to unsigned int"); + } } + break; + } case Py_T_LONG:{ *(long*)addr = PyLong_AsLong(v); if ((*(long*)addr == -1) && PyErr_Occurred()) return -1; break; } - case Py_T_ULONG:{ - *(unsigned long*)addr = PyLong_AsUnsignedLong(v); - if ((*(unsigned long*)addr == (unsigned long)-1) - && PyErr_Occurred()) { - /* XXX: For compatibility, accept negative int values - as well. */ - PyErr_Clear(); - *(unsigned long*)addr = PyLong_AsLong(v); - if ((*(unsigned long*)addr == (unsigned long)-1) - && PyErr_Occurred()) + case Py_T_ULONG: { + /* XXX: For compatibility, accept negative int values + as well. */ + int overflow; + long long_val = PyLong_AsLongAndOverflow(v, &overflow); + if (long_val == -1 && PyErr_Occurred()) { + return -1; + } + if (overflow < 0) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C long"); + } + else if (!overflow) { + *(unsigned long *)addr = (unsigned long)long_val; + if (long_val < 0) { + WARN("Writing negative value into unsigned field"); + } + } + else { + unsigned long ulong_val = PyLong_AsUnsignedLong(v); + if (ulong_val == (unsigned long)-1 && PyErr_Occurred()) { return -1; - WARN("Writing negative value into unsigned field"); + } + *(unsigned long*)addr = ulong_val; } break; - } + } case Py_T_PYSSIZET:{ *(Py_ssize_t*)addr = PyLong_AsSsize_t(v); if ((*(Py_ssize_t*)addr == (Py_ssize_t)-1) From 7e42fddf608337e83b30401910d76fd75d5cf20a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 4 Feb 2024 17:49:42 +0200 Subject: [PATCH 128/507] gh-113951: Tkinter: "tag_unbind(tag, sequence, funcid)" now only unbinds "funcid" (GH-113955) Previously, "tag_unbind(tag, sequence, funcid)" methods of Text and Canvas widgets destroyed the current binding for "sequence", leaving "sequence" unbound, and deleted the "funcid" command. Now they remove only "funcid" from the binding for "sequence", keeping other commands, and delete the "funcid" command. They leave "sequence" unbound only if "funcid" was the last bound command. --- Lib/test/test_tkinter/test_misc.py | 95 +++++++++++++++++++ Lib/tkinter/__init__.py | 26 ++--- ...-01-11-20-47-49.gh-issue-113951.AzlqFK.rst | 7 ++ 3 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-11-20-47-49.gh-issue-113951.AzlqFK.rst diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index dc8a810235fc9bd..71553503005c48b 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -706,6 +706,101 @@ def test3(e): pass self.assertCommandExist(funcid2) self.assertCommandExist(funcid3) + def _test_tag_bind(self, w): + tag = 'sel' + event = '<Control-Alt-Key-a>' + w.pack() + self.assertRaises(TypeError, w.tag_bind) + tag_bind = w._tag_bind if isinstance(w, tkinter.Text) else w.tag_bind + if isinstance(w, tkinter.Text): + self.assertRaises(TypeError, w.tag_bind, tag) + self.assertRaises(TypeError, w.tag_bind, tag, event) + self.assertEqual(tag_bind(tag), ()) + self.assertEqual(tag_bind(tag, event), '') + def test1(e): pass + def test2(e): pass + + funcid = w.tag_bind(tag, event, test1) + self.assertEqual(tag_bind(tag), (event,)) + script = tag_bind(tag, event) + self.assertIn(funcid, script) + self.assertCommandExist(funcid) + + funcid2 = w.tag_bind(tag, event, test2, add=True) + script = tag_bind(tag, event) + self.assertIn(funcid, script) + self.assertIn(funcid2, script) + self.assertCommandExist(funcid) + self.assertCommandExist(funcid2) + + def _test_tag_unbind(self, w): + tag = 'sel' + event = '<Control-Alt-Key-b>' + w.pack() + tag_bind = w._tag_bind if isinstance(w, tkinter.Text) else w.tag_bind + self.assertEqual(tag_bind(tag), ()) + self.assertEqual(tag_bind(tag, event), '') + def test1(e): pass + def test2(e): pass + + funcid = w.tag_bind(tag, event, test1) + funcid2 = w.tag_bind(tag, event, test2, add=True) + + self.assertRaises(TypeError, w.tag_unbind, tag) + w.tag_unbind(tag, event) + self.assertEqual(tag_bind(tag, event), '') + self.assertEqual(tag_bind(tag), ()) + + def _test_tag_bind_rebind(self, w): + tag = 'sel' + event = '<Control-Alt-Key-d>' + w.pack() + tag_bind = w._tag_bind if isinstance(w, tkinter.Text) else w.tag_bind + self.assertEqual(tag_bind(tag), ()) + self.assertEqual(tag_bind(tag, event), '') + def test1(e): pass + def test2(e): pass + def test3(e): pass + + funcid = w.tag_bind(tag, event, test1) + funcid2 = w.tag_bind(tag, event, test2, add=True) + script = tag_bind(tag, event) + self.assertIn(funcid2, script) + self.assertIn(funcid, script) + self.assertCommandExist(funcid) + self.assertCommandExist(funcid2) + + funcid3 = w.tag_bind(tag, event, test3) + script = tag_bind(tag, event) + self.assertNotIn(funcid, script) + self.assertNotIn(funcid2, script) + self.assertIn(funcid3, script) + self.assertCommandExist(funcid3) + + def test_canvas_tag_bind(self): + c = tkinter.Canvas(self.frame) + self._test_tag_bind(c) + + def test_canvas_tag_unbind(self): + c = tkinter.Canvas(self.frame) + self._test_tag_unbind(c) + + def test_canvas_tag_bind_rebind(self): + c = tkinter.Canvas(self.frame) + self._test_tag_bind_rebind(c) + + def test_text_tag_bind(self): + t = tkinter.Text(self.frame) + self._test_tag_bind(t) + + def test_text_tag_unbind(self): + t = tkinter.Text(self.frame) + self._test_tag_unbind(t) + + def test_text_tag_bind_rebind(self): + t = tkinter.Text(self.frame) + self._test_tag_bind_rebind(t) + def test_bindtags(self): f = self.frame self.assertEqual(self.root.bindtags(), ('.', 'Tk', 'all')) diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index e0db41dd915ece9..a1567d332ae6efc 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -1537,16 +1537,19 @@ def unbind(self, sequence, funcid=None): Otherwise destroy the current binding for SEQUENCE, leaving SEQUENCE unbound. """ + self._unbind(('bind', self._w, sequence), funcid) + + def _unbind(self, what, funcid=None): if funcid is None: - self.tk.call('bind', self._w, sequence, '') + self.tk.call(*what, '') else: - lines = self.tk.call('bind', self._w, sequence).split('\n') + lines = self.tk.call(what).split('\n') prefix = f'if {{"[{funcid} ' keep = '\n'.join(line for line in lines if not line.startswith(prefix)) if not keep.strip(): keep = '' - self.tk.call('bind', self._w, sequence, keep) + self.tk.call(*what, keep) self.deletecommand(funcid) def bind_all(self, sequence=None, func=None, add=None): @@ -1558,7 +1561,7 @@ def bind_all(self, sequence=None, func=None, add=None): def unbind_all(self, sequence): """Unbind for all widgets for event SEQUENCE all functions.""" - self.tk.call('bind', 'all' , sequence, '') + self._root()._unbind(('bind', 'all', sequence)) def bind_class(self, className, sequence=None, func=None, add=None): """Bind to widgets with bindtag CLASSNAME at event @@ -1573,7 +1576,7 @@ def bind_class(self, className, sequence=None, func=None, add=None): def unbind_class(self, className, sequence): """Unbind for all widgets with bindtag CLASSNAME for event SEQUENCE all functions.""" - self.tk.call('bind', className , sequence, '') + self._root()._unbind(('bind', className, sequence)) def mainloop(self, n=0): """Call the mainloop of Tk.""" @@ -2885,9 +2888,7 @@ def bbox(self, *args): def tag_unbind(self, tagOrId, sequence, funcid=None): """Unbind for all items with TAGORID for event SEQUENCE the function identified with FUNCID.""" - self.tk.call(self._w, 'bind', tagOrId, sequence, '') - if funcid: - self.deletecommand(funcid) + self._unbind((self._w, 'bind', tagOrId, sequence), funcid) def tag_bind(self, tagOrId, sequence=None, func=None, add=None): """Bind to all items with TAGORID at event SEQUENCE a call to function FUNC. @@ -3997,9 +3998,7 @@ def tag_add(self, tagName, index1, *args): def tag_unbind(self, tagName, sequence, funcid=None): """Unbind for all characters with TAGNAME for event SEQUENCE the function identified with FUNCID.""" - self.tk.call(self._w, 'tag', 'bind', tagName, sequence, '') - if funcid: - self.deletecommand(funcid) + return self._unbind((self._w, 'tag', 'bind', tagName, sequence), funcid) def tag_bind(self, tagName, sequence, func, add=None): """Bind to all characters with TAGNAME at event SEQUENCE a call to function FUNC. @@ -4010,6 +4009,11 @@ def tag_bind(self, tagName, sequence, func, add=None): return self._bind((self._w, 'tag', 'bind', tagName), sequence, func, add) + def _tag_bind(self, tagName, sequence=None, func=None, add=None): + # For tests only + return self._bind((self._w, 'tag', 'bind', tagName), + sequence, func, add) + def tag_cget(self, tagName, option): """Return the value of OPTION for tag TAGNAME.""" if option[:1] != '-': diff --git a/Misc/NEWS.d/next/Library/2024-01-11-20-47-49.gh-issue-113951.AzlqFK.rst b/Misc/NEWS.d/next/Library/2024-01-11-20-47-49.gh-issue-113951.AzlqFK.rst new file mode 100644 index 000000000000000..e683472e59b8a49 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-11-20-47-49.gh-issue-113951.AzlqFK.rst @@ -0,0 +1,7 @@ +Fix the behavior of ``tag_unbind()`` methods of :class:`tkinter.Text` and +:class:`tkinter.Canvas` classes with three arguments. Previously, +``widget.tag_unbind(tag, sequence, funcid)`` destroyed the current binding +for *sequence*, leaving *sequence* unbound, and deleted the *funcid* +command. Now it removes only *funcid* from the binding for *sequence*, +keeping other commands, and deletes the *funcid* command. It leaves +*sequence* unbound only if *funcid* was the last bound command. From d466052ad48091a00a50c5298f33238aff591028 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 4 Feb 2024 19:06:22 +0200 Subject: [PATCH 129/507] gh-114388: Fix an error in GH-114391 (GH-115000) --- Python/structmember.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Python/structmember.c b/Python/structmember.c index 18bd486952419b7..c9f03a464078d0b 100644 --- a/Python/structmember.c +++ b/Python/structmember.c @@ -208,6 +208,7 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v) if (overflow < 0) { PyErr_SetString(PyExc_OverflowError, "Python int too large to convert to C long"); + return -1; } else if (!overflow) { *(unsigned int *)addr = (unsigned int)(unsigned long)long_val; @@ -247,6 +248,7 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v) if (overflow < 0) { PyErr_SetString(PyExc_OverflowError, "Python int too large to convert to C long"); + return -1; } else if (!overflow) { *(unsigned long *)addr = (unsigned long)long_val; From da8f9fb2ea65cc2784c2400fc39ad8c800a67a42 Mon Sep 17 00:00:00 2001 From: Dai Wentao <dwt136@gmail.com> Date: Mon, 5 Feb 2024 02:42:58 +0800 Subject: [PATCH 130/507] gh-113803: Fix inaccurate documentation for shutil.move when dst is an existing directory (#113837) * fix the usage of dst and destination in shutil.move doc * update shutil.move doc --- Doc/library/shutil.rst | 25 ++++++++++++++----------- Lib/shutil.py | 10 +++++----- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 7a7dd23177e6721..ff8c9a189ab3de2 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -360,21 +360,24 @@ Directory and files operations .. function:: move(src, dst, copy_function=copy2) - Recursively move a file or directory (*src*) to another location (*dst*) - and return the destination. + Recursively move a file or directory (*src*) to another location and return + the destination. - If the destination is an existing directory, then *src* is moved inside that - directory. If the destination already exists but is not a directory, it may - be overwritten depending on :func:`os.rename` semantics. + If *dst* is an existing directory or a symlink to a directory, then *src* + is moved inside that directory. The destination path in that directory must + not already exist. + + If *dst* already exists but is not a directory, it may be overwritten + depending on :func:`os.rename` semantics. If the destination is on the current filesystem, then :func:`os.rename` is - used. Otherwise, *src* is copied to *dst* using *copy_function* and then - removed. In case of symlinks, a new symlink pointing to the target of *src* - will be created in or as *dst* and *src* will be removed. + used. Otherwise, *src* is copied to the destination using *copy_function* + and then removed. In case of symlinks, a new symlink pointing to the target + of *src* will be created as the destination and *src* will be removed. - If *copy_function* is given, it must be a callable that takes two arguments - *src* and *dst*, and will be used to copy *src* to *dst* if - :func:`os.rename` cannot be used. If the source is a directory, + If *copy_function* is given, it must be a callable that takes two arguments, + *src* and the destination, and will be used to copy *src* to the destination + if :func:`os.rename` cannot be used. If the source is a directory, :func:`copytree` is called, passing it the *copy_function*. The default *copy_function* is :func:`copy2`. Using :func:`~shutil.copy` as the *copy_function* allows the move to succeed when it is not possible to also diff --git a/Lib/shutil.py b/Lib/shutil.py index acc9419be4dfca6..c19ea0607208afa 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -861,12 +861,12 @@ def move(src, dst, copy_function=copy2): similar to the Unix "mv" command. Return the file or directory's destination. - If the destination is a directory or a symlink to a directory, the source - is moved inside the directory. The destination path must not already - exist. + If dst is an existing directory or a symlink to a directory, then src is + moved inside that directory. The destination path in that directory must + not already exist. - If the destination already exists but is not a directory, it may be - overwritten depending on os.rename() semantics. + If dst already exists but is not a directory, it may be overwritten + depending on os.rename() semantics. If the destination is on our current filesystem, then rename() is used. Otherwise, src is copied to the destination and then removed. Symlinks are From 929d44e15a5667151beadb2d3a2528cd641639d6 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Sun, 4 Feb 2024 22:16:43 +0300 Subject: [PATCH 131/507] gh-114685: PyBuffer_FillInfo() now raises on PyBUF_{READ,WRITE} (GH-114802) --- Lib/test/test_buffer.py | 21 +++++++++++++++++++ ...-01-31-15-43-35.gh-issue-114685.n7aRmX.rst | 3 +++ Modules/_testcapimodule.c | 21 +++++++++++++++++++ Objects/abstract.c | 16 +++++++++----- 4 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2024-01-31-15-43-35.gh-issue-114685.n7aRmX.rst diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py index 535b795f508a242..5b1b95b9c82064c 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -4591,6 +4591,27 @@ def test_c_buffer_invalid_flags(self): self.assertRaises(SystemError, buf.__buffer__, PyBUF_READ) self.assertRaises(SystemError, buf.__buffer__, PyBUF_WRITE) + @unittest.skipIf(_testcapi is None, "requires _testcapi") + def test_c_fill_buffer_invalid_flags(self): + # PyBuffer_FillInfo + source = b"abc" + self.assertRaises(SystemError, _testcapi.buffer_fill_info, + source, 0, PyBUF_READ) + self.assertRaises(SystemError, _testcapi.buffer_fill_info, + source, 0, PyBUF_WRITE) + + @unittest.skipIf(_testcapi is None, "requires _testcapi") + def test_c_fill_buffer_readonly_and_writable(self): + source = b"abc" + with _testcapi.buffer_fill_info(source, 1, PyBUF_SIMPLE) as m: + self.assertEqual(bytes(m), b"abc") + self.assertTrue(m.readonly) + with _testcapi.buffer_fill_info(source, 0, PyBUF_WRITABLE) as m: + self.assertEqual(bytes(m), b"abc") + self.assertFalse(m.readonly) + self.assertRaises(BufferError, _testcapi.buffer_fill_info, + source, 1, PyBUF_WRITABLE) + def test_inheritance(self): class A(bytearray): def __buffer__(self, flags): diff --git a/Misc/NEWS.d/next/C API/2024-01-31-15-43-35.gh-issue-114685.n7aRmX.rst b/Misc/NEWS.d/next/C API/2024-01-31-15-43-35.gh-issue-114685.n7aRmX.rst new file mode 100644 index 000000000000000..76ff00645fe57d2 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-01-31-15-43-35.gh-issue-114685.n7aRmX.rst @@ -0,0 +1,3 @@ +:c:func:`PyBuffer_FillInfo` now raises a :exc:`SystemError` if called with +:c:macro:`PyBUF_READ` or :c:macro:`PyBUF_WRITE` as flags. These flags should +only be used with the ``PyMemoryView_*`` C API. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 6def680190b1a63..e67de3eeb6e17eb 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -1261,6 +1261,26 @@ make_memoryview_from_NULL_pointer(PyObject *self, PyObject *Py_UNUSED(ignored)) return PyMemoryView_FromBuffer(&info); } +static PyObject * +buffer_fill_info(PyObject *self, PyObject *args) +{ + Py_buffer info; + const char *data; + Py_ssize_t size; + int readonly; + int flags; + + if (!PyArg_ParseTuple(args, "s#ii:buffer_fill_info", + &data, &size, &readonly, &flags)) { + return NULL; + } + + if (PyBuffer_FillInfo(&info, NULL, (void *)data, size, readonly, flags) < 0) { + return NULL; + } + return PyMemoryView_FromBuffer(&info); +} + static PyObject * test_from_contiguous(PyObject* self, PyObject *Py_UNUSED(ignored)) { @@ -3314,6 +3334,7 @@ static PyMethodDef TestMethods[] = { {"eval_code_ex", eval_eval_code_ex, METH_VARARGS}, {"make_memoryview_from_NULL_pointer", make_memoryview_from_NULL_pointer, METH_NOARGS}, + {"buffer_fill_info", buffer_fill_info, METH_VARARGS}, {"crash_no_current_thread", crash_no_current_thread, METH_NOARGS}, {"test_current_tstate_matches", test_current_tstate_matches, METH_NOARGS}, {"run_in_subinterp", run_in_subinterp, METH_VARARGS}, diff --git a/Objects/abstract.c b/Objects/abstract.c index daf04eb4ab2cda3..07d4b89fe188c84 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -767,11 +767,17 @@ PyBuffer_FillInfo(Py_buffer *view, PyObject *obj, void *buf, Py_ssize_t len, return -1; } - if (((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) && - (readonly == 1)) { - PyErr_SetString(PyExc_BufferError, - "Object is not writable."); - return -1; + if (flags != PyBUF_SIMPLE) { /* fast path */ + if (flags == PyBUF_READ || flags == PyBUF_WRITE) { + PyErr_BadInternalCall(); + return -1; + } + if (((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) && + (readonly == 1)) { + PyErr_SetString(PyExc_BufferError, + "Object is not writable."); + return -1; + } } view->obj = Py_XNewRef(obj); From 15f6f048a6ecdf0f6f4fc076d013be3d110f8ed6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 4 Feb 2024 22:19:06 +0200 Subject: [PATCH 132/507] gh-114392: Improve test_capi.test_structmembers (GH-114393) Test all integer member types with extreme values and values outside of the valid range. Test support of integer-like objects. Test warnings for wrapped out values. --- Lib/test/test_capi/test_structmembers.py | 217 ++++++++++------------- 1 file changed, 93 insertions(+), 124 deletions(-) diff --git a/Lib/test/test_capi/test_structmembers.py b/Lib/test/test_capi/test_structmembers.py index 415b8033bd16b3c..a294c3b13a5c30c 100644 --- a/Lib/test/test_capi/test_structmembers.py +++ b/Lib/test/test_capi/test_structmembers.py @@ -45,83 +45,115 @@ class ReadWriteTests: def setUp(self): self.ts = _make_test_object(self.cls) + def _test_write(self, name, value, expected=None): + if expected is None: + expected = value + ts = self.ts + setattr(ts, name, value) + self.assertEqual(getattr(ts, name), expected) + + def _test_warn(self, name, value, expected=None): + ts = self.ts + self.assertWarns(RuntimeWarning, setattr, ts, name, value) + if expected is not None: + self.assertEqual(getattr(ts, name), expected) + + def _test_overflow(self, name, value): + ts = self.ts + self.assertRaises(OverflowError, setattr, ts, name, value) + + def _test_int_range(self, name, minval, maxval, *, hardlimit=None, + indexlimit=None): + if hardlimit is None: + hardlimit = (minval, maxval) + ts = self.ts + self._test_write(name, minval) + self._test_write(name, maxval) + hardminval, hardmaxval = hardlimit + self._test_overflow(name, hardminval-1) + self._test_overflow(name, hardmaxval+1) + self._test_overflow(name, 2**1000) + self._test_overflow(name, -2**1000) + if hardminval < minval: + self._test_warn(name, hardminval) + self._test_warn(name, minval-1, maxval) + if maxval < hardmaxval: + self._test_warn(name, maxval+1, minval) + self._test_warn(name, hardmaxval) + + if indexlimit is None: + indexlimit = hardlimit + if not indexlimit: + self.assertRaises(TypeError, setattr, ts, name, Index(minval)) + self.assertRaises(TypeError, setattr, ts, name, Index(maxval)) + else: + hardminindexval, hardmaxindexval = indexlimit + self._test_write(name, Index(minval), minval) + if minval < hardminindexval: + self._test_write(name, Index(hardminindexval), hardminindexval) + if maxval < hardmaxindexval: + self._test_write(name, Index(maxval), maxval) + else: + self._test_write(name, Index(hardmaxindexval), hardmaxindexval) + self._test_overflow(name, Index(hardminindexval-1)) + if name in ('T_UINT', 'T_ULONG'): + self.assertRaises(TypeError, setattr, self.ts, name, + Index(hardmaxindexval+1)) + self.assertRaises(TypeError, setattr, self.ts, name, + Index(2**1000)) + else: + self._test_overflow(name, Index(hardmaxindexval+1)) + self._test_overflow(name, Index(2**1000)) + self._test_overflow(name, Index(-2**1000)) + if hardminindexval < minval and name != 'T_ULONGLONG': + self._test_warn(name, Index(hardminindexval)) + self._test_warn(name, Index(minval-1)) + if maxval < hardmaxindexval: + self._test_warn(name, Index(maxval+1)) + self._test_warn(name, Index(hardmaxindexval)) + def test_bool(self): ts = self.ts ts.T_BOOL = True - self.assertEqual(ts.T_BOOL, True) + self.assertIs(ts.T_BOOL, True) ts.T_BOOL = False - self.assertEqual(ts.T_BOOL, False) + self.assertIs(ts.T_BOOL, False) self.assertRaises(TypeError, setattr, ts, 'T_BOOL', 1) + self.assertRaises(TypeError, setattr, ts, 'T_BOOL', 0) + self.assertRaises(TypeError, setattr, ts, 'T_BOOL', None) def test_byte(self): - ts = self.ts - ts.T_BYTE = CHAR_MAX - self.assertEqual(ts.T_BYTE, CHAR_MAX) - ts.T_BYTE = CHAR_MIN - self.assertEqual(ts.T_BYTE, CHAR_MIN) - ts.T_UBYTE = UCHAR_MAX - self.assertEqual(ts.T_UBYTE, UCHAR_MAX) + self._test_int_range('T_BYTE', CHAR_MIN, CHAR_MAX, + hardlimit=(LONG_MIN, LONG_MAX)) + self._test_int_range('T_UBYTE', 0, UCHAR_MAX, + hardlimit=(LONG_MIN, LONG_MAX)) def test_short(self): - ts = self.ts - ts.T_SHORT = SHRT_MAX - self.assertEqual(ts.T_SHORT, SHRT_MAX) - ts.T_SHORT = SHRT_MIN - self.assertEqual(ts.T_SHORT, SHRT_MIN) - ts.T_USHORT = USHRT_MAX - self.assertEqual(ts.T_USHORT, USHRT_MAX) + self._test_int_range('T_SHORT', SHRT_MIN, SHRT_MAX, + hardlimit=(LONG_MIN, LONG_MAX)) + self._test_int_range('T_USHORT', 0, USHRT_MAX, + hardlimit=(LONG_MIN, LONG_MAX)) def test_int(self): - ts = self.ts - ts.T_INT = INT_MAX - self.assertEqual(ts.T_INT, INT_MAX) - ts.T_INT = INT_MIN - self.assertEqual(ts.T_INT, INT_MIN) - ts.T_UINT = UINT_MAX - self.assertEqual(ts.T_UINT, UINT_MAX) - ts.T_UINT = Index(0) - self.assertEqual(ts.T_UINT, 0) - ts.T_UINT = Index(INT_MAX) - self.assertEqual(ts.T_UINT, INT_MAX) + self._test_int_range('T_INT', INT_MIN, INT_MAX, + hardlimit=(LONG_MIN, LONG_MAX)) + self._test_int_range('T_UINT', 0, UINT_MAX, + hardlimit=(LONG_MIN, ULONG_MAX), + indexlimit=(LONG_MIN, LONG_MAX)) def test_long(self): - ts = self.ts - ts.T_LONG = LONG_MAX - self.assertEqual(ts.T_LONG, LONG_MAX) - ts.T_LONG = LONG_MIN - self.assertEqual(ts.T_LONG, LONG_MIN) - ts.T_ULONG = ULONG_MAX - self.assertEqual(ts.T_ULONG, ULONG_MAX) - ts.T_ULONG = Index(0) - self.assertEqual(ts.T_ULONG, 0) - ts.T_ULONG = Index(LONG_MAX) - self.assertEqual(ts.T_ULONG, LONG_MAX) + self._test_int_range('T_LONG', LONG_MIN, LONG_MAX) + self._test_int_range('T_ULONG', 0, ULONG_MAX, + hardlimit=(LONG_MIN, ULONG_MAX), + indexlimit=(LONG_MIN, LONG_MAX)) def test_py_ssize_t(self): - ts = self.ts - ts.T_PYSSIZET = PY_SSIZE_T_MAX - self.assertEqual(ts.T_PYSSIZET, PY_SSIZE_T_MAX) - ts.T_PYSSIZET = PY_SSIZE_T_MIN - self.assertEqual(ts.T_PYSSIZET, PY_SSIZE_T_MIN) + self._test_int_range('T_PYSSIZET', PY_SSIZE_T_MIN, PY_SSIZE_T_MAX, indexlimit=False) def test_longlong(self): - ts = self.ts - if not hasattr(ts, "T_LONGLONG"): - self.skipTest("long long not present") - - ts.T_LONGLONG = LLONG_MAX - self.assertEqual(ts.T_LONGLONG, LLONG_MAX) - ts.T_LONGLONG = LLONG_MIN - self.assertEqual(ts.T_LONGLONG, LLONG_MIN) - - ts.T_ULONGLONG = ULLONG_MAX - self.assertEqual(ts.T_ULONGLONG, ULLONG_MAX) - - ## make sure these will accept a plain int as well as a long - ts.T_LONGLONG = 3 - self.assertEqual(ts.T_LONGLONG, 3) - ts.T_ULONGLONG = 4 - self.assertEqual(ts.T_ULONGLONG, 4) + self._test_int_range('T_LONGLONG', LLONG_MIN, LLONG_MAX) + self._test_int_range('T_ULONGLONG', 0, ULLONG_MAX, + indexlimit=(LONG_MIN, LONG_MAX)) def test_bad_assignments(self): ts = self.ts @@ -131,10 +163,9 @@ def test_bad_assignments(self): 'T_SHORT', 'T_USHORT', 'T_INT', 'T_UINT', 'T_LONG', 'T_ULONG', + 'T_LONGLONG', 'T_ULONGLONG', 'T_PYSSIZET' ] - if hasattr(ts, 'T_LONGLONG'): - integer_attributes.extend(['T_LONGLONG', 'T_ULONGLONG']) # issue8014: this produced 'bad argument to internal function' # internal error @@ -154,68 +185,6 @@ class ReadWriteTests_OldAPI(ReadWriteTests, unittest.TestCase): class ReadWriteTests_NewAPI(ReadWriteTests, unittest.TestCase): cls = _test_structmembersType_NewAPI -class TestWarnings: - def setUp(self): - self.ts = _make_test_object(self.cls) - - def test_byte_max(self): - ts = self.ts - with warnings_helper.check_warnings(('', RuntimeWarning)): - ts.T_BYTE = CHAR_MAX+1 - - def test_byte_min(self): - ts = self.ts - with warnings_helper.check_warnings(('', RuntimeWarning)): - ts.T_BYTE = CHAR_MIN-1 - - def test_ubyte_max(self): - ts = self.ts - with warnings_helper.check_warnings(('', RuntimeWarning)): - ts.T_UBYTE = UCHAR_MAX+1 - - def test_short_max(self): - ts = self.ts - with warnings_helper.check_warnings(('', RuntimeWarning)): - ts.T_SHORT = SHRT_MAX+1 - - def test_short_min(self): - ts = self.ts - with warnings_helper.check_warnings(('', RuntimeWarning)): - ts.T_SHORT = SHRT_MIN-1 - - def test_ushort_max(self): - ts = self.ts - with warnings_helper.check_warnings(('', RuntimeWarning)): - ts.T_USHORT = USHRT_MAX+1 - - def test_int(self): - ts = self.ts - if LONG_MIN < INT_MIN: - with self.assertWarns(RuntimeWarning): - ts.T_INT = INT_MIN-1 - if LONG_MAX > INT_MAX: - with self.assertWarns(RuntimeWarning): - ts.T_INT = INT_MAX+1 - - def test_uint(self): - ts = self.ts - with self.assertWarns(RuntimeWarning): - ts.T_UINT = -1 - if ULONG_MAX > UINT_MAX: - with self.assertWarns(RuntimeWarning): - ts.T_UINT = UINT_MAX+1 - - def test_ulong(self): - ts = self.ts - with self.assertWarns(RuntimeWarning): - ts.T_ULONG = -1 - -class TestWarnings_OldAPI(TestWarnings, unittest.TestCase): - cls = _test_structmembersType_OldAPI - -class TestWarnings_NewAPI(TestWarnings, unittest.TestCase): - cls = _test_structmembersType_NewAPI - if __name__ == "__main__": unittest.main() From 391659b3da570bfa28fed5fbdb6f2d9c26ab3dd0 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee <russell@keith-magee.com> Date: Mon, 5 Feb 2024 08:04:57 +0800 Subject: [PATCH 133/507] gh-114099: Add test exclusions to support running the test suite on iOS (#114889) Add test annotations required to run the test suite on iOS (PEP 730). The majority of the change involve annotating tests that use subprocess, but are skipped on Emscripten/WASI for other reasons, and including iOS/tvOS/watchOS under the same umbrella as macOS/darwin checks. `is_apple` and `is_apple_mobile` test helpers have been added to identify *any* Apple platform, and "any Apple platform except macOS", respectively. --- Lib/test/support/__init__.py | 26 ++++- Lib/test/support/os_helper.py | 8 +- Lib/test/test_asyncio/test_events.py | 14 +++ Lib/test/test_asyncio/test_streams.py | 3 +- Lib/test/test_asyncio/test_subprocess.py | 2 + Lib/test/test_asyncio/test_unix_events.py | 2 +- Lib/test/test_cmd_line_script.py | 16 +-- Lib/test/test_code_module.py | 1 + Lib/test/test_fcntl.py | 12 ++- Lib/test/test_ftplib.py | 3 + Lib/test/test_genericpath.py | 22 ++-- Lib/test/test_httpservers.py | 18 ++-- Lib/test/test_io.py | 16 ++- Lib/test/test_marshal.py | 4 +- Lib/test/test_mmap.py | 4 +- Lib/test/test_os.py | 4 +- Lib/test/test_pdb.py | 102 +++++++++--------- Lib/test/test_platform.py | 3 +- Lib/test/test_posix.py | 15 +-- Lib/test/test_pty.py | 18 ++-- Lib/test/test_selectors.py | 5 +- Lib/test/test_shutil.py | 2 + Lib/test/test_signal.py | 9 +- Lib/test/test_socket.py | 24 +++-- Lib/test/test_sqlite3/test_dbapi.py | 6 +- Lib/test/test_stat.py | 5 +- Lib/test/test_sys_settrace.py | 3 +- Lib/test/test_unicode_file_functions.py | 14 +-- Lib/test/test_urllib2.py | 2 + Lib/test/test_venv.py | 10 +- ...-02-02-13-18-55.gh-issue-114099.C_ycWg.rst | 1 + 31 files changed, 224 insertions(+), 150 deletions(-) create mode 100644 Misc/NEWS.d/next/Tests/2024-02-02-13-18-55.gh-issue-114099.C_ycWg.rst diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index f2e6af078a5f291..5b091fb2fd32dc6 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -43,7 +43,7 @@ "requires_limited_api", "requires_specialization", # sys "MS_WINDOWS", "is_jython", "is_android", "is_emscripten", "is_wasi", - "check_impl_detail", "unix_shell", "setswitchinterval", + "is_apple_mobile", "check_impl_detail", "unix_shell", "setswitchinterval", # os "get_pagesize", # network @@ -522,7 +522,7 @@ def requires_debug_ranges(reason='requires co_positions / debug_ranges'): is_android = hasattr(sys, 'getandroidapilevel') -if sys.platform not in ('win32', 'vxworks'): +if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos"}: unix_shell = '/system/bin/sh' if is_android else '/bin/sh' else: unix_shell = None @@ -532,19 +532,35 @@ def requires_debug_ranges(reason='requires co_positions / debug_ranges'): is_emscripten = sys.platform == "emscripten" is_wasi = sys.platform == "wasi" -has_fork_support = hasattr(os, "fork") and not is_emscripten and not is_wasi +# Apple mobile platforms (iOS/tvOS/watchOS) are POSIX-like but do not +# have subprocess or fork support. +is_apple_mobile = sys.platform in {"ios", "tvos", "watchos"} +is_apple = is_apple_mobile or sys.platform == "darwin" + +has_fork_support = hasattr(os, "fork") and not ( + is_emscripten + or is_wasi + or is_apple_mobile +) def requires_fork(): return unittest.skipUnless(has_fork_support, "requires working os.fork()") -has_subprocess_support = not is_emscripten and not is_wasi +has_subprocess_support = not ( + is_emscripten + or is_wasi + or is_apple_mobile +) def requires_subprocess(): """Used for subprocess, os.spawn calls, fd inheritance""" return unittest.skipUnless(has_subprocess_support, "requires subprocess support") # Emscripten's socket emulation and WASI sockets have limitations. -has_socket_support = not is_emscripten and not is_wasi +has_socket_support = not ( + is_emscripten + or is_wasi +) def requires_working_socket(*, module=False): """Skip tests or modules that require working sockets diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py index 20f38fd36a8876e..22787e32b5f3abe 100644 --- a/Lib/test/support/os_helper.py +++ b/Lib/test/support/os_helper.py @@ -22,8 +22,8 @@ # TESTFN_UNICODE is a non-ascii filename TESTFN_UNICODE = TESTFN_ASCII + "-\xe0\xf2\u0258\u0141\u011f" -if sys.platform == 'darwin': - # In Mac OS X's VFS API file names are, by definition, canonically +if support.is_apple: + # On Apple's VFS API file names are, by definition, canonically # decomposed Unicode, encoded using UTF-8. See QA1173: # http://developer.apple.com/mac/library/qa/qa2001/qa1173.html import unicodedata @@ -48,8 +48,8 @@ 'encoding (%s). Unicode filename tests may not be effective' % (TESTFN_UNENCODABLE, sys.getfilesystemencoding())) TESTFN_UNENCODABLE = None -# macOS and Emscripten deny unencodable filenames (invalid utf-8) -elif sys.platform not in {'darwin', 'emscripten', 'wasi'}: +# Apple and Emscripten deny unencodable filenames (invalid utf-8) +elif not support.is_apple and sys.platform not in {"emscripten", "wasi"}: try: # ascii and utf-8 cannot encode the byte 0xff b'\xff'.decode(sys.getfilesystemencoding()) diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index b25c0975736e20e..c92c88bd5b2429c 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -1815,6 +1815,7 @@ def check_killed(self, returncode): else: self.assertEqual(-signal.SIGKILL, returncode) + @support.requires_subprocess() def test_subprocess_exec(self): prog = os.path.join(os.path.dirname(__file__), 'echo.py') @@ -1836,6 +1837,7 @@ def test_subprocess_exec(self): self.check_killed(proto.returncode) self.assertEqual(b'Python The Winner', proto.data[1]) + @support.requires_subprocess() def test_subprocess_interactive(self): prog = os.path.join(os.path.dirname(__file__), 'echo.py') @@ -1863,6 +1865,7 @@ def test_subprocess_interactive(self): self.loop.run_until_complete(proto.completed) self.check_killed(proto.returncode) + @support.requires_subprocess() def test_subprocess_shell(self): connect = self.loop.subprocess_shell( functools.partial(MySubprocessProtocol, self.loop), @@ -1879,6 +1882,7 @@ def test_subprocess_shell(self): self.assertEqual(proto.data[2], b'') transp.close() + @support.requires_subprocess() def test_subprocess_exitcode(self): connect = self.loop.subprocess_shell( functools.partial(MySubprocessProtocol, self.loop), @@ -1890,6 +1894,7 @@ def test_subprocess_exitcode(self): self.assertEqual(7, proto.returncode) transp.close() + @support.requires_subprocess() def test_subprocess_close_after_finish(self): connect = self.loop.subprocess_shell( functools.partial(MySubprocessProtocol, self.loop), @@ -1904,6 +1909,7 @@ def test_subprocess_close_after_finish(self): self.assertEqual(7, proto.returncode) self.assertIsNone(transp.close()) + @support.requires_subprocess() def test_subprocess_kill(self): prog = os.path.join(os.path.dirname(__file__), 'echo.py') @@ -1920,6 +1926,7 @@ def test_subprocess_kill(self): self.check_killed(proto.returncode) transp.close() + @support.requires_subprocess() def test_subprocess_terminate(self): prog = os.path.join(os.path.dirname(__file__), 'echo.py') @@ -1937,6 +1944,7 @@ def test_subprocess_terminate(self): transp.close() @unittest.skipIf(sys.platform == 'win32', "Don't have SIGHUP") + @support.requires_subprocess() def test_subprocess_send_signal(self): # bpo-31034: Make sure that we get the default signal handler (killing # the process). The parent process may have decided to ignore SIGHUP, @@ -1961,6 +1969,7 @@ def test_subprocess_send_signal(self): finally: signal.signal(signal.SIGHUP, old_handler) + @support.requires_subprocess() def test_subprocess_stderr(self): prog = os.path.join(os.path.dirname(__file__), 'echo2.py') @@ -1982,6 +1991,7 @@ def test_subprocess_stderr(self): self.assertTrue(proto.data[2].startswith(b'ERR:test'), proto.data[2]) self.assertEqual(0, proto.returncode) + @support.requires_subprocess() def test_subprocess_stderr_redirect_to_stdout(self): prog = os.path.join(os.path.dirname(__file__), 'echo2.py') @@ -2007,6 +2017,7 @@ def test_subprocess_stderr_redirect_to_stdout(self): transp.close() self.assertEqual(0, proto.returncode) + @support.requires_subprocess() def test_subprocess_close_client_stream(self): prog = os.path.join(os.path.dirname(__file__), 'echo3.py') @@ -2041,6 +2052,7 @@ def test_subprocess_close_client_stream(self): self.loop.run_until_complete(proto.completed) self.check_killed(proto.returncode) + @support.requires_subprocess() def test_subprocess_wait_no_same_group(self): # start the new process in a new session connect = self.loop.subprocess_shell( @@ -2053,6 +2065,7 @@ def test_subprocess_wait_no_same_group(self): self.assertEqual(7, proto.returncode) transp.close() + @support.requires_subprocess() def test_subprocess_exec_invalid_args(self): async def connect(**kwds): await self.loop.subprocess_exec( @@ -2066,6 +2079,7 @@ async def connect(**kwds): with self.assertRaises(ValueError): self.loop.run_until_complete(connect(shell=True)) + @support.requires_subprocess() def test_subprocess_shell_invalid_args(self): async def connect(cmd=None, **kwds): diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index 3c8cc5f3649180d..210990593adfa9b 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -10,7 +10,6 @@ import unittest from unittest import mock import warnings -from test.support import socket_helper try: import ssl except ImportError: @@ -18,6 +17,7 @@ import asyncio from test.test_asyncio import utils as test_utils +from test.support import requires_subprocess, socket_helper def tearDownModule(): @@ -770,6 +770,7 @@ async def client(addr): self.assertEqual(msg2, b"hello world 2!\n") @unittest.skipIf(sys.platform == 'win32', "Don't have pipes") + @requires_subprocess() def test_read_all_from_pipe_reader(self): # See asyncio issue 168. This test is derived from the example # subprocess_attach_read_pipe.py, but we configure the diff --git a/Lib/test/test_asyncio/test_subprocess.py b/Lib/test/test_asyncio/test_subprocess.py index 808b21c66175511..f50a9ebc031ba8a 100644 --- a/Lib/test/test_asyncio/test_subprocess.py +++ b/Lib/test/test_asyncio/test_subprocess.py @@ -47,6 +47,7 @@ def _start(self, *args, **kwargs): self._proc.pid = -1 +@support.requires_subprocess() class SubprocessTransportTests(test_utils.TestCase): def setUp(self): super().setUp() @@ -110,6 +111,7 @@ def test_subprocess_repr(self): transport.close() +@support.requires_subprocess() class SubprocessMixin: def test_stdin_stdout(self): diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py index d2c8cba6acfa31c..59ef9f5f58cabc3 100644 --- a/Lib/test/test_asyncio/test_unix_events.py +++ b/Lib/test/test_asyncio/test_unix_events.py @@ -1874,7 +1874,7 @@ async def runner(): wsock.close() -@unittest.skipUnless(hasattr(os, 'fork'), 'requires os.fork()') +@support.requires_fork() class TestFork(unittest.IsolatedAsyncioTestCase): async def test_fork_not_share_event_loop(self): diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index 48754d5a63da3b1..3a5a8abf81e43d4 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -14,8 +14,7 @@ import textwrap from test import support -from test.support import import_helper -from test.support import os_helper +from test.support import import_helper, is_apple, os_helper from test.support.script_helper import ( make_pkg, make_script, make_zip_pkg, make_zip_script, assert_python_ok, assert_python_failure, spawn_python, kill_python) @@ -557,12 +556,17 @@ def test_pep_409_verbiage(self): self.assertTrue(text[3].startswith('NameError')) def test_non_ascii(self): - # Mac OS X denies the creation of a file with an invalid UTF-8 name. + # Apple platforms deny the creation of a file with an invalid UTF-8 name. # Windows allows creating a name with an arbitrary bytes name, but # Python cannot a undecodable bytes argument to a subprocess. - # WASI does not permit invalid UTF-8 names. - if (os_helper.TESTFN_UNDECODABLE - and sys.platform not in ('win32', 'darwin', 'emscripten', 'wasi')): + # Emscripten/WASI does not permit invalid UTF-8 names. + if ( + os_helper.TESTFN_UNDECODABLE + and sys.platform not in { + "win32", "emscripten", "wasi" + } + and not is_apple + ): name = os.fsdecode(os_helper.TESTFN_UNDECODABLE) elif os_helper.TESTFN_NONASCII: name = os_helper.TESTFN_NONASCII diff --git a/Lib/test/test_code_module.py b/Lib/test/test_code_module.py index 747c0f9683c19c7..259778a5cade98f 100644 --- a/Lib/test/test_code_module.py +++ b/Lib/test/test_code_module.py @@ -160,6 +160,7 @@ def setUp(self): self.console = code.InteractiveConsole(local_exit=True) self.mock_sys() + @unittest.skipIf(sys.flags.no_site, "exit() isn't defined unless there's a site module") def test_exit(self): # default exit message self.infunc.side_effect = ["exit()"] diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py index 203dd6fe57dcd99..6d734d052454d34 100644 --- a/Lib/test/test_fcntl.py +++ b/Lib/test/test_fcntl.py @@ -6,7 +6,9 @@ import struct import sys import unittest -from test.support import verbose, cpython_only, get_pagesize +from test.support import ( + cpython_only, get_pagesize, is_apple, requires_subprocess, verbose +) from test.support.import_helper import import_module from test.support.os_helper import TESTFN, unlink @@ -56,8 +58,10 @@ def get_lockdata(): else: start_len = "qq" - if (sys.platform.startswith(('netbsd', 'freebsd', 'openbsd')) - or sys.platform == 'darwin'): + if ( + sys.platform.startswith(('netbsd', 'freebsd', 'openbsd')) + or is_apple + ): if struct.calcsize('l') == 8: off_t = 'l' pid_t = 'i' @@ -157,6 +161,7 @@ def test_flock(self): self.assertRaises(TypeError, fcntl.flock, 'spam', fcntl.LOCK_SH) @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError") + @requires_subprocess() def test_lockf_exclusive(self): self.f = open(TESTFN, 'wb+') cmd = fcntl.LOCK_EX | fcntl.LOCK_NB @@ -169,6 +174,7 @@ def test_lockf_exclusive(self): self.assertEqual(p.exitcode, 0) @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError") + @requires_subprocess() def test_lockf_share(self): self.f = open(TESTFN, 'wb+') cmd = fcntl.LOCK_SH | fcntl.LOCK_NB diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py index 2f191ea7a44c161..81115e9db888cf2 100644 --- a/Lib/test/test_ftplib.py +++ b/Lib/test/test_ftplib.py @@ -18,6 +18,7 @@ from unittest import TestCase, skipUnless from test import support +from test.support import requires_subprocess from test.support import threading_helper from test.support import socket_helper from test.support import warnings_helper @@ -900,6 +901,7 @@ def retr(): @skipUnless(ssl, "SSL not available") +@requires_subprocess() class TestTLS_FTPClassMixin(TestFTPClass): """Repeat TestFTPClass tests starting the TLS layer for both control and data connections first. @@ -916,6 +918,7 @@ def setUp(self, encoding=DEFAULT_ENCODING): @skipUnless(ssl, "SSL not available") +@requires_subprocess() class TestTLS_FTPClass(TestCase): """Specific TLS_FTP class tests.""" diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py index 4f311c2d498e9f4..b77cd4c67d6b2ae 100644 --- a/Lib/test/test_genericpath.py +++ b/Lib/test/test_genericpath.py @@ -7,9 +7,9 @@ import sys import unittest import warnings -from test.support import is_emscripten -from test.support import os_helper -from test.support import warnings_helper +from test.support import ( + is_apple, is_emscripten, os_helper, warnings_helper +) from test.support.script_helper import assert_python_ok from test.support.os_helper import FakePath @@ -483,12 +483,16 @@ def test_abspath_issue3426(self): self.assertIsInstance(abspath(path), str) def test_nonascii_abspath(self): - if (os_helper.TESTFN_UNDECODABLE - # macOS and Emscripten deny the creation of a directory with an - # invalid UTF-8 name. Windows allows creating a directory with an - # arbitrary bytes name, but fails to enter this directory - # (when the bytes name is used). - and sys.platform not in ('win32', 'darwin', 'emscripten', 'wasi')): + if ( + os_helper.TESTFN_UNDECODABLE + # Apple platforms and Emscripten/WASI deny the creation of a + # directory with an invalid UTF-8 name. Windows allows creating a + # directory with an arbitrary bytes name, but fails to enter this + # directory (when the bytes name is used). + and sys.platform not in { + "win32", "emscripten", "wasi" + } and not is_apple + ): name = os_helper.TESTFN_UNDECODABLE elif os_helper.TESTFN_NONASCII: name = os_helper.TESTFN_NONASCII diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 9fa6ecf9c08e279..d762ec6102ab8aa 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -30,8 +30,9 @@ import unittest from test import support -from test.support import os_helper -from test.support import threading_helper +from test.support import ( + is_apple, os_helper, requires_subprocess, threading_helper +) support.requires_working_socket(module=True) @@ -410,8 +411,8 @@ def close_conn(): reader.close() return body - @unittest.skipIf(sys.platform == 'darwin', - 'undecodable name cannot always be decoded on macOS') + @unittest.skipIf(is_apple, + 'undecodable name cannot always be decoded on Apple platforms') @unittest.skipIf(sys.platform == 'win32', 'undecodable name cannot be decoded on win32') @unittest.skipUnless(os_helper.TESTFN_UNDECODABLE, @@ -422,11 +423,11 @@ def test_undecodable_filename(self): with open(os.path.join(self.tempdir, filename), 'wb') as f: f.write(os_helper.TESTFN_UNDECODABLE) response = self.request(self.base_url + '/') - if sys.platform == 'darwin': - # On Mac OS the HFS+ filesystem replaces bytes that aren't valid - # UTF-8 into a percent-encoded value. + if is_apple: + # On Apple platforms the HFS+ filesystem replaces bytes that + # aren't valid UTF-8 into a percent-encoded value. for name in os.listdir(self.tempdir): - if name != 'test': # Ignore a filename created in setUp(). + if name != 'test': # Ignore a filename created in setUp(). filename = name break body = self.check_status_and_reason(response, HTTPStatus.OK) @@ -697,6 +698,7 @@ def test_html_escape_filename(self): @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0, "This test can't be run reliably as root (issue #13308).") +@requires_subprocess() class CGIHTTPServerTestCase(BaseTestCase): class request_handler(NoLogRequestHandler, CGIHTTPRequestHandler): _test_case_self = None # populated by each setUp() method call. diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 9e28b936e00bd57..73669ecc7927763 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -39,11 +39,9 @@ from test import support from test.support.script_helper import ( assert_python_ok, assert_python_failure, run_python_until_end) -from test.support import import_helper -from test.support import os_helper -from test.support import threading_helper -from test.support import warnings_helper -from test.support import skip_if_sanitizer +from test.support import ( + import_helper, is_apple, os_helper, skip_if_sanitizer, threading_helper, warnings_helper +) from test.support.os_helper import FakePath import codecs @@ -606,10 +604,10 @@ def test_raw_bytes_io(self): self.read_ops(f, True) def test_large_file_ops(self): - # On Windows and Mac OSX this test consumes large resources; It takes - # a long time to build the >2 GiB file and takes >2 GiB of disk space - # therefore the resource must be enabled to run this test. - if sys.platform[:3] == 'win' or sys.platform == 'darwin': + # On Windows and Apple platforms this test consumes large resources; It + # takes a long time to build the >2 GiB file and takes >2 GiB of disk + # space therefore the resource must be enabled to run this test. + if sys.platform[:3] == 'win' or is_apple: support.requires( 'largefile', 'test requires %s bytes and a long time to run' % self.LARGE) diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py index 6e17e010e7f355d..615568e6af21028 100644 --- a/Lib/test/test_marshal.py +++ b/Lib/test/test_marshal.py @@ -1,5 +1,5 @@ from test import support -from test.support import os_helper, requires_debug_ranges +from test.support import is_apple_mobile, os_helper, requires_debug_ranges from test.support.script_helper import assert_python_ok import array import io @@ -286,7 +286,7 @@ def test_recursion_limit(self): #if os.name == 'nt' and support.Py_DEBUG: if os.name == 'nt': MAX_MARSHAL_STACK_DEPTH = 1000 - elif sys.platform == 'wasi': + elif sys.platform == 'wasi' or is_apple_mobile: MAX_MARSHAL_STACK_DEPTH = 1500 else: MAX_MARSHAL_STACK_DEPTH = 2000 diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index b89621e08577be0..ac759757d24659c 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -1,5 +1,5 @@ from test.support import ( - requires, _2G, _4G, gc_collect, cpython_only, is_emscripten + requires, _2G, _4G, gc_collect, cpython_only, is_emscripten, is_apple, ) from test.support.import_helper import import_module from test.support.os_helper import TESTFN, unlink @@ -1067,7 +1067,7 @@ def tearDown(self): unlink(TESTFN) def _make_test_file(self, num_zeroes, tail): - if sys.platform[:3] == 'win' or sys.platform == 'darwin': + if sys.platform[:3] == 'win' or is_apple: requires('largefile', 'test requires %s bytes and a long time to run' % str(0x180000000)) f = open(TESTFN, 'w+b') diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index ed79a2c24ef30bf..86af1a8ed8ee154 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -3848,6 +3848,7 @@ def test_does_not_crash(self): self.assertGreaterEqual(size.columns, 0) self.assertGreaterEqual(size.lines, 0) + @support.requires_subprocess() def test_stty_match(self): """Check if stty returns the same results @@ -4577,7 +4578,8 @@ def test_posix_pty_functions(self): self.addCleanup(os.close, son_fd) self.assertEqual(os.ptsname(mother_fd), os.ttyname(son_fd)) - @unittest.skipUnless(hasattr(os, 'spawnl'), "need os.openpty()") + @unittest.skipUnless(hasattr(os, 'spawnl'), "need os.spawnl()") + @support.requires_subprocess() def test_pipe_spawnl(self): # gh-77046: On Windows, os.pipe() file descriptors must be created with # _O_NOINHERIT to make them non-inheritable. UCRT has no public API to diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index b2283cff6cb4622..2b0795cdad707e1 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -779,58 +779,62 @@ def test_pdb_where_command(): (Pdb) continue """ -def test_pdb_interact_command(): - """Test interact command - >>> g = 0 - >>> dict_g = {} +# skip this test if sys.flags.no_site = True; +# exit() isn't defined unless there's a site module. +if not sys.flags.no_site: + def test_pdb_interact_command(): + """Test interact command - >>> def test_function(): - ... x = 1 - ... lst_local = [] - ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + >>> g = 0 + >>> dict_g = {} - >>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE - ... 'interact', - ... 'x', - ... 'g', - ... 'x = 2', - ... 'g = 3', - ... 'dict_g["a"] = True', - ... 'lst_local.append(x)', - ... 'exit()', - ... 'p x', - ... 'p g', - ... 'p dict_g', - ... 'p lst_local', - ... 'continue', - ... ]): - ... test_function() - --Return-- - > <doctest test.test_pdb.test_pdb_interact_command[2]>(4)test_function()->None - -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() - (Pdb) interact - *pdb interact start* - ... x - 1 - ... g - 0 - ... x = 2 - ... g = 3 - ... dict_g["a"] = True - ... lst_local.append(x) - ... exit() - *exit from pdb interact command* - (Pdb) p x - 1 - (Pdb) p g - 0 - (Pdb) p dict_g - {'a': True} - (Pdb) p lst_local - [2] - (Pdb) continue - """ + >>> def test_function(): + ... x = 1 + ... lst_local = [] + ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + + >>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE + ... 'interact', + ... 'x', + ... 'g', + ... 'x = 2', + ... 'g = 3', + ... 'dict_g["a"] = True', + ... 'lst_local.append(x)', + ... 'exit()', + ... 'p x', + ... 'p g', + ... 'p dict_g', + ... 'p lst_local', + ... 'continue', + ... ]): + ... test_function() + --Return-- + > <doctest test.test_pdb.test_pdb_interact_command[2]>(4)test_function()->None + -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + (Pdb) interact + *pdb interact start* + ... x + 1 + ... g + 0 + ... x = 2 + ... g = 3 + ... dict_g["a"] = True + ... lst_local.append(x) + ... exit() + *exit from pdb interact command* + (Pdb) p x + 1 + (Pdb) p g + 0 + (Pdb) p dict_g + {'a': True} + (Pdb) p lst_local + [2] + (Pdb) continue + """ def test_convenience_variables(): """Test convenience variables diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 216973350319fe6..648e18d0150ef08 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -472,7 +472,8 @@ def test_macos(self): 'root:xnu-4570.71.2~1/RELEASE_X86_64'), 'x86_64', 'i386') arch = ('64bit', '') - with mock.patch.object(platform, 'uname', return_value=uname), \ + with mock.patch.object(sys, "platform", "darwin"), \ + mock.patch.object(platform, 'uname', return_value=uname), \ mock.patch.object(platform, 'architecture', return_value=arch): for mac_ver, expected_terse, expected in [ # darwin: mac_ver() returns empty strings diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 9c382ace806e0f8..72e348fbbdcbc14 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1,7 +1,7 @@ "Test posix functions" from test import support -from test.support import import_helper +from test.support import is_apple from test.support import os_helper from test.support import warnings_helper from test.support.script_helper import assert_python_ok @@ -781,9 +781,10 @@ def check_stat(uid, gid): check_stat(uid, gid) self.assertRaises(OSError, chown_func, first_param, 0, -1) check_stat(uid, gid) - if 0 not in os.getgroups(): - self.assertRaises(OSError, chown_func, first_param, -1, 0) - check_stat(uid, gid) + if hasattr(os, 'getgroups'): + if 0 not in os.getgroups(): + self.assertRaises(OSError, chown_func, first_param, -1, 0) + check_stat(uid, gid) # test illegal types for t in str, float: self.assertRaises(TypeError, chown_func, first_param, t(uid), gid) @@ -1256,8 +1257,8 @@ def test_sched_priority(self): self.assertIsInstance(lo, int) self.assertIsInstance(hi, int) self.assertGreaterEqual(hi, lo) - # OSX evidently just returns 15 without checking the argument. - if sys.platform != "darwin": + # Apple plaforms return 15 without checking the argument. + if not is_apple: self.assertRaises(OSError, posix.sched_get_priority_min, -23) self.assertRaises(OSError, posix.sched_get_priority_max, -23) @@ -2028,11 +2029,13 @@ def test_dup2(self): @unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn") +@support.requires_subprocess() class TestPosixSpawn(unittest.TestCase, _PosixSpawnMixin): spawn_func = getattr(posix, 'posix_spawn', None) @unittest.skipUnless(hasattr(os, 'posix_spawnp'), "test needs os.posix_spawnp") +@support.requires_subprocess() class TestPosixSpawnP(unittest.TestCase, _PosixSpawnMixin): spawn_func = getattr(posix, 'posix_spawnp', None) diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py index 51e3a46d0df1780..3f2bac0155fd9e8 100644 --- a/Lib/test/test_pty.py +++ b/Lib/test/test_pty.py @@ -1,12 +1,17 @@ -from test.support import verbose, reap_children -from test.support.os_helper import TESTFN, unlink +import sys +import unittest +from test.support import ( + is_apple_mobile, is_emscripten, is_wasi, reap_children, verbose +) from test.support.import_helper import import_module +from test.support.os_helper import TESTFN, unlink -# Skip these tests if termios or fcntl are not available +# Skip these tests if termios is not available import_module('termios') -# fcntl is a proxy for not being one of the wasm32 platforms even though we -# don't use this module... a proper check for what crashes those is needed. -import_module("fcntl") + +# Skip tests on WASM platforms, plus iOS/tvOS/watchOS +if is_apple_mobile or is_emscripten or is_wasi: + raise unittest.SkipTest(f"pty tests not required on {sys.platform}") import errno import os @@ -17,7 +22,6 @@ import signal import socket import io # readline -import unittest import warnings TEST_STRING_1 = b"I wish to buy a fish license.\n" diff --git a/Lib/test/test_selectors.py b/Lib/test/test_selectors.py index 677349c2bfca93d..643775597c56c67 100644 --- a/Lib/test/test_selectors.py +++ b/Lib/test/test_selectors.py @@ -6,8 +6,7 @@ import socket import sys from test import support -from test.support import os_helper -from test.support import socket_helper +from test.support import is_apple, os_helper, socket_helper from time import sleep import unittest import unittest.mock @@ -526,7 +525,7 @@ def test_above_fd_setsize(self): try: fds = s.select() except OSError as e: - if e.errno == errno.EINVAL and sys.platform == 'darwin': + if e.errno == errno.EINVAL and is_apple: # unexplainable errors on macOS don't need to fail the test self.skipTest("Invalid argument error calling poll()") raise diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 8edd75e9907ec02..d96dad4eb9475d2 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2148,6 +2148,7 @@ def check_chown(path, uid=None, gid=None): check_chown(dirname, uid, gid) +@support.requires_subprocess() class TestWhich(BaseTest, unittest.TestCase): def setUp(self): @@ -3181,6 +3182,7 @@ def test_bad_environ(self): self.assertGreaterEqual(size.lines, 0) @unittest.skipUnless(os.isatty(sys.__stdout__.fileno()), "not on tty") + @support.requires_subprocess() @unittest.skipUnless(hasattr(os, 'get_terminal_size'), 'need os.get_terminal_size()') def test_stty_match(self): diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index 637a0ca3b369726..61fb047caf6dab6 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -13,9 +13,10 @@ import time import unittest from test import support -from test.support import os_helper +from test.support import ( + is_apple, is_apple_mobile, os_helper, threading_helper +) from test.support.script_helper import assert_python_ok, spawn_python -from test.support import threading_helper try: import _testcapi except ImportError: @@ -832,7 +833,7 @@ def test_itimer_real(self): self.assertEqual(self.hndl_called, True) # Issue 3864, unknown if this affects earlier versions of freebsd also - @unittest.skipIf(sys.platform in ('netbsd5',), + @unittest.skipIf(sys.platform in ('netbsd5',) or is_apple_mobile, 'itimer not reliable (does not mix well with threading) on some BSDs.') def test_itimer_virtual(self): self.itimer = signal.ITIMER_VIRTUAL @@ -1344,7 +1345,7 @@ def handler(signum, frame): # Python handler self.assertEqual(len(sigs), N, "Some signals were lost") - @unittest.skipIf(sys.platform == "darwin", "crashes due to system bug (FB13453490)") + @unittest.skipIf(is_apple, "crashes due to system bug (FB13453490)") @unittest.skipUnless(hasattr(signal, "SIGUSR1"), "test needs SIGUSR1") @threading_helper.requires_working_threading() diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 231448c75f01db0..179642349920627 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1,9 +1,8 @@ import unittest from test import support -from test.support import os_helper -from test.support import socket_helper -from test.support import threading_helper -from test.support import refleak_helper +from test.support import ( + is_apple, os_helper, refleak_helper, socket_helper, threading_helper +) import _thread as thread import array @@ -1196,8 +1195,11 @@ def testGetServBy(self): # Find one service that exists, then check all the related interfaces. # I've ordered this by protocols that have both a tcp and udp # protocol, at least for modern Linuxes. - if (sys.platform.startswith(('freebsd', 'netbsd', 'gnukfreebsd')) - or sys.platform in ('linux', 'darwin')): + if ( + sys.platform.startswith(('freebsd', 'netbsd', 'gnukfreebsd')) + or sys.platform == 'linux' + or is_apple + ): # avoid the 'echo' service on this platform, as there is an # assumption breaking non-standard port/protocol entry services = ('daytime', 'qotd', 'domain') @@ -3708,7 +3710,7 @@ def testFDPassCMSG_LEN(self): def _testFDPassCMSG_LEN(self): self.createAndSendFDs(1) - @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") + @unittest.skipIf(is_apple, "skipping, see issue #12958") @unittest.skipIf(AIX, "skipping, see issue #22397") @requireAttrs(socket, "CMSG_SPACE") def testFDPassSeparate(self): @@ -3719,7 +3721,7 @@ def testFDPassSeparate(self): maxcmsgs=2) @testFDPassSeparate.client_skip - @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") + @unittest.skipIf(is_apple, "skipping, see issue #12958") @unittest.skipIf(AIX, "skipping, see issue #22397") def _testFDPassSeparate(self): fd0, fd1 = self.newFDs(2) @@ -3732,7 +3734,7 @@ def _testFDPassSeparate(self): array.array("i", [fd1]))]), len(MSG)) - @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") + @unittest.skipIf(is_apple, "skipping, see issue #12958") @unittest.skipIf(AIX, "skipping, see issue #22397") @requireAttrs(socket, "CMSG_SPACE") def testFDPassSeparateMinSpace(self): @@ -3746,7 +3748,7 @@ def testFDPassSeparateMinSpace(self): maxcmsgs=2, ignoreflags=socket.MSG_CTRUNC) @testFDPassSeparateMinSpace.client_skip - @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") + @unittest.skipIf(is_apple, "skipping, see issue #12958") @unittest.skipIf(AIX, "skipping, see issue #22397") def _testFDPassSeparateMinSpace(self): fd0, fd1 = self.newFDs(2) @@ -3770,7 +3772,7 @@ def sendAncillaryIfPossible(self, msg, ancdata): nbytes = self.sendmsgToServer([msg]) self.assertEqual(nbytes, len(msg)) - @unittest.skipIf(sys.platform == "darwin", "see issue #24725") + @unittest.skipIf(is_apple, "skipping, see issue #12958") def testFDPassEmpty(self): # Try to pass an empty FD array. Can receive either no array # or an empty array. diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index f3efe0f52f4fd73..588272448bbfda6 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -31,7 +31,7 @@ from test.support import ( SHORT_TIMEOUT, check_disallow_instantiation, requires_subprocess, - is_emscripten, is_wasi + is_apple, is_emscripten, is_wasi ) from test.support import gc_collect from test.support import threading_helper @@ -667,7 +667,7 @@ def test_open_with_path_like_object(self): cx.execute(self._sql) @unittest.skipIf(sys.platform == "win32", "skipped on Windows") - @unittest.skipIf(sys.platform == "darwin", "skipped on macOS") + @unittest.skipIf(is_apple, "skipped on Apple platforms") @unittest.skipIf(is_emscripten or is_wasi, "not supported on Emscripten/WASI") @unittest.skipUnless(TESTFN_UNDECODABLE, "only works if there are undecodable paths") def test_open_with_undecodable_path(self): @@ -713,7 +713,7 @@ def test_open_uri_readonly(self): cx.execute(self._sql) @unittest.skipIf(sys.platform == "win32", "skipped on Windows") - @unittest.skipIf(sys.platform == "darwin", "skipped on macOS") + @unittest.skipIf(is_apple, "skipped on Apple platforms") @unittest.skipIf(is_emscripten or is_wasi, "not supported on Emscripten/WASI") @unittest.skipUnless(TESTFN_UNDECODABLE, "only works if there are undecodable paths") def test_open_undecodable_uri(self): diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py index d6b6dd6e7417002..49013a4bcd8af6b 100644 --- a/Lib/test/test_stat.py +++ b/Lib/test/test_stat.py @@ -2,8 +2,7 @@ import os import socket import sys -from test.support import os_helper -from test.support import socket_helper +from test.support import is_apple, os_helper, socket_helper from test.support.import_helper import import_fresh_module from test.support.os_helper import TESTFN @@ -247,7 +246,7 @@ def test_flags_consistent(self): for flag in self.file_flags: if flag.startswith("UF"): self.assertTrue(getattr(self.statmod, flag) & self.statmod.UF_SETTABLE, f"{flag} not in UF_SETTABLE") - elif sys.platform == 'darwin' and self.statmod is c_stat and flag == 'SF_DATALESS': + elif is_apple and self.statmod is c_stat and flag == 'SF_DATALESS': self.assertTrue(self.statmod.SF_DATALESS & self.statmod.SF_SYNTHETIC, "SF_DATALESS not in SF_SYNTHETIC") self.assertFalse(self.statmod.SF_DATALESS & self.statmod.SF_SETTABLE, "SF_DATALESS in SF_SETTABLE") else: diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py index ae6e192a7ab6eff..125f40227118f63 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -7,7 +7,7 @@ import gc from functools import wraps import asyncio -from test.support import import_helper +from test.support import import_helper, requires_subprocess import contextlib import os import tempfile @@ -1810,6 +1810,7 @@ def compare_events(self, line_offset, events, expected_events): def make_tracer(): return Tracer(trace_opcode_events=True) + @requires_subprocess() def test_trace_opcodes_after_settrace(self): """Make sure setting f_trace_opcodes after starting trace works even if it's the first time f_trace_opcodes is being set. GH-103615""" diff --git a/Lib/test/test_unicode_file_functions.py b/Lib/test/test_unicode_file_functions.py index 47619c8807bafe3..25c16e3a0b7e430 100644 --- a/Lib/test/test_unicode_file_functions.py +++ b/Lib/test/test_unicode_file_functions.py @@ -5,7 +5,7 @@ import unittest import warnings from unicodedata import normalize -from test.support import os_helper +from test.support import is_apple, os_helper from test import support @@ -23,13 +23,13 @@ '10_\u1fee\u1ffd', ] -# Mac OS X decomposes Unicode names, using Normal Form D. +# Apple platforms decompose Unicode names, using Normal Form D. # http://developer.apple.com/mac/library/qa/qa2001/qa1173.html # "However, most volume formats do not follow the exact specification for # these normal forms. For example, HFS Plus uses a variant of Normal Form D # in which U+2000 through U+2FFF, U+F900 through U+FAFF, and U+2F800 through # U+2FAFF are not decomposed." -if sys.platform != 'darwin': +if not is_apple: filenames.extend([ # Specific code points: NFC(fn), NFD(fn), NFKC(fn) and NFKD(fn) all different '11_\u0385\u03d3\u03d4', @@ -119,11 +119,11 @@ def test_open(self): os.stat(name) self._apply_failure(os.listdir, name, self._listdir_failure) - # Skip the test on darwin, because darwin does normalize the filename to + # Skip the test on Apple platforms, because they don't normalize the filename to # NFD (a variant of Unicode NFD form). Normalize the filename to NFC, NFKC, # NFKD in Python is useless, because darwin will normalize it later and so # open(), os.stat(), etc. don't raise any exception. - @unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X') + @unittest.skipIf(is_apple, 'irrelevant test on Apple platforms') @unittest.skipIf( support.is_emscripten or support.is_wasi, "test fails on Emscripten/WASI when host platform is macOS." @@ -142,10 +142,10 @@ def test_normalize(self): self._apply_failure(os.remove, name) self._apply_failure(os.listdir, name) - # Skip the test on darwin, because darwin uses a normalization different + # Skip the test on Apple platforms, because they use a normalization different # than Python NFD normalization: filenames are different even if we use # Python NFD normalization. - @unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X') + @unittest.skipIf(is_apple, 'irrelevant test on Apple platforms') def test_listdir(self): sf0 = set(self.files) with warnings.catch_warnings(): diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py index 99c9e24994732fa..fa528a675892b5e 100644 --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -1,6 +1,7 @@ import unittest from test import support from test.support import os_helper +from test.support import requires_subprocess from test.support import warnings_helper from test import test_urllib from unittest import mock @@ -998,6 +999,7 @@ def test_http_body_fileobj(self): file_obj.close() + @requires_subprocess() def test_http_body_pipe(self): # A file reading from a pipe. # A pipe cannot be seek'ed. There is no way to determine the diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 6dda00efd7bbb69..ba31beb81e80b09 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -19,8 +19,8 @@ import tempfile from test.support import (captured_stdout, captured_stderr, skip_if_broken_multiprocessing_synchronize, verbose, - requires_subprocess, is_emscripten, is_wasi, - requires_venv_with_pip, TEST_HOME_DIR, + requires_subprocess, is_apple_mobile, is_emscripten, + is_wasi, requires_venv_with_pip, TEST_HOME_DIR, requires_resource, copy_python_src_ignore) from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree) import unittest @@ -39,8 +39,10 @@ or sys._base_executable != sys.executable, 'cannot run venv.create from within a venv on this platform') -if is_emscripten or is_wasi: - raise unittest.SkipTest("venv is not available on Emscripten/WASI.") +# Skip tests on WASM platforms, plus iOS/tvOS/watchOS +if is_apple_mobile or is_emscripten or is_wasi: + raise unittest.SkipTest(f"venv tests not required on {sys.platform}") + @requires_subprocess() def check_output(cmd, encoding=None): diff --git a/Misc/NEWS.d/next/Tests/2024-02-02-13-18-55.gh-issue-114099.C_ycWg.rst b/Misc/NEWS.d/next/Tests/2024-02-02-13-18-55.gh-issue-114099.C_ycWg.rst new file mode 100644 index 000000000000000..487cd5062fc75b5 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-02-02-13-18-55.gh-issue-114099.C_ycWg.rst @@ -0,0 +1 @@ +Added test exclusions required to run the test suite on iOS. From e207cc181fbb0ceb30542fd0d68140c916305f57 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy <tjreedy@udel.edu> Date: Sun, 4 Feb 2024 20:57:54 -0500 Subject: [PATCH 134/507] gh-114628: Display csv.Error without context (#115005) When cvs.Error is raised when TypeError is caught, the TypeError display and 'During handling' note is just noise with duplicate information. Suppress with 'from None'. --- Lib/csv.py | 4 ++-- .../Library/2024-02-04-13-17-33.gh-issue-114628.WJpqqS.rst | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-04-13-17-33.gh-issue-114628.WJpqqS.rst diff --git a/Lib/csv.py b/Lib/csv.py index a079279b8b8cbce..75e35b23236795f 100644 --- a/Lib/csv.py +++ b/Lib/csv.py @@ -113,8 +113,8 @@ def _validate(self): try: _Dialect(self) except TypeError as e: - # We do this for compatibility with py2.3 - raise Error(str(e)) + # Re-raise to get a traceback showing more user code. + raise Error(str(e)) from None class excel(Dialect): """Describe the usual properties of Excel-generated CSV files.""" diff --git a/Misc/NEWS.d/next/Library/2024-02-04-13-17-33.gh-issue-114628.WJpqqS.rst b/Misc/NEWS.d/next/Library/2024-02-04-13-17-33.gh-issue-114628.WJpqqS.rst new file mode 100644 index 000000000000000..8138adc62c95f32 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-04-13-17-33.gh-issue-114628.WJpqqS.rst @@ -0,0 +1,2 @@ +When csv.Error is raised when handling TypeError, do not print the TypeError +traceback. From 39ec7fbba84663ab760853da2ac422c2e988d189 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy <tjreedy@udel.edu> Date: Sun, 4 Feb 2024 23:11:31 -0500 Subject: [PATCH 135/507] Remove bogus syntax error marker in csv doc (#115017) --- Doc/library/csv.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/Doc/library/csv.rst b/Doc/library/csv.rst index fd62b225fcebb80..4ee7820585d3a21 100644 --- a/Doc/library/csv.rst +++ b/Doc/library/csv.rst @@ -244,7 +244,6 @@ The :mod:`csv` module defines the following classes: with open('students.csv', 'w', newline='') as csvfile: writer = csv.writer(csvfile, dialect='unix') - ^^^^^^^^^^^^^^ .. class:: excel() From f71bdd34085d31a826148b2e5da57e0302655056 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Mon, 5 Feb 2024 13:20:34 +0300 Subject: [PATCH 136/507] gh-115020: Remove a debugging print in test_frame (GH-115021) --- Lib/test/test_frame.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py index 244ce8af7cdf088..baed03d92b9e561 100644 --- a/Lib/test/test_frame.py +++ b/Lib/test/test_frame.py @@ -72,7 +72,6 @@ def inner(): except ZeroDivisionError as exc: support.gc_collect() self.assertIsNotNone(wr()) - print(exc.__traceback__.tb_next.tb_frame.f_locals) exc.__traceback__.tb_next.tb_frame.clear() support.gc_collect() self.assertIsNone(wr()) From 87cd20a567aca56369010689e22a524bc1f1ac03 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Mon, 5 Feb 2024 13:45:09 +0300 Subject: [PATCH 137/507] gh-115026: Argument Clinic: handle PyBuffer_FillInfo errors in generated code (#115027) --- Modules/_sqlite/clinic/connection.c.h | 6 ++++-- Modules/clinic/_codecsmodule.c.h | 18 +++++++++++++----- Modules/clinic/_ssl.c.h | 6 ++++-- Tools/clinic/clinic.py | 4 +++- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Modules/_sqlite/clinic/connection.c.h b/Modules/_sqlite/clinic/connection.c.h index db5eb77891e52e5..f2cff6a7b421f3b 100644 --- a/Modules/_sqlite/clinic/connection.c.h +++ b/Modules/_sqlite/clinic/connection.c.h @@ -1551,7 +1551,9 @@ deserialize(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs, if (ptr == NULL) { goto exit; } - PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, 0); + if (PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, PyBUF_SIMPLE) < 0) { + goto exit; + } } else { /* any bytes-like object */ if (PyObject_GetBuffer(args[0], &data, PyBUF_SIMPLE) != 0) { @@ -1818,4 +1820,4 @@ getconfig(pysqlite_Connection *self, PyObject *arg) #ifndef DESERIALIZE_METHODDEF #define DESERIALIZE_METHODDEF #endif /* !defined(DESERIALIZE_METHODDEF) */ -/*[clinic end generated code: output=90b5b9c14261b8d7 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=99299d3ee2c247ab input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_codecsmodule.c.h b/Modules/clinic/_codecsmodule.c.h index 12fea806ab52093..1c0f37442ab3509 100644 --- a/Modules/clinic/_codecsmodule.c.h +++ b/Modules/clinic/_codecsmodule.c.h @@ -297,7 +297,9 @@ _codecs_escape_decode(PyObject *module, PyObject *const *args, Py_ssize_t nargs) if (ptr == NULL) { goto exit; } - PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, 0); + if (PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, PyBUF_SIMPLE) < 0) { + goto exit; + } } else { /* any bytes-like object */ if (PyObject_GetBuffer(args[0], &data, PyBUF_SIMPLE) != 0) { @@ -1099,7 +1101,9 @@ _codecs_unicode_escape_decode(PyObject *module, PyObject *const *args, Py_ssize_ if (ptr == NULL) { goto exit; } - PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, 0); + if (PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, PyBUF_SIMPLE) < 0) { + goto exit; + } } else { /* any bytes-like object */ if (PyObject_GetBuffer(args[0], &data, PyBUF_SIMPLE) != 0) { @@ -1175,7 +1179,9 @@ _codecs_raw_unicode_escape_decode(PyObject *module, PyObject *const *args, Py_ss if (ptr == NULL) { goto exit; } - PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, 0); + if (PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, PyBUF_SIMPLE) < 0) { + goto exit; + } } else { /* any bytes-like object */ if (PyObject_GetBuffer(args[0], &data, PyBUF_SIMPLE) != 0) { @@ -1644,7 +1650,9 @@ _codecs_readbuffer_encode(PyObject *module, PyObject *const *args, Py_ssize_t na if (ptr == NULL) { goto exit; } - PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, 0); + if (PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, PyBUF_SIMPLE) < 0) { + goto exit; + } } else { /* any bytes-like object */ if (PyObject_GetBuffer(args[0], &data, PyBUF_SIMPLE) != 0) { @@ -2738,4 +2746,4 @@ _codecs_lookup_error(PyObject *module, PyObject *arg) #ifndef _CODECS_CODE_PAGE_ENCODE_METHODDEF #define _CODECS_CODE_PAGE_ENCODE_METHODDEF #endif /* !defined(_CODECS_CODE_PAGE_ENCODE_METHODDEF) */ -/*[clinic end generated code: output=d8d9e372f7ccba35 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=e50d5fdf65bd45fa input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_ssl.c.h b/Modules/clinic/_ssl.c.h index 19c0f619b92f45c..2940f16a2cb7f6a 100644 --- a/Modules/clinic/_ssl.c.h +++ b/Modules/clinic/_ssl.c.h @@ -1297,7 +1297,9 @@ _ssl_RAND_add(PyObject *module, PyObject *const *args, Py_ssize_t nargs) if (ptr == NULL) { goto exit; } - PyBuffer_FillInfo(&view, args[0], (void *)ptr, len, 1, 0); + if (PyBuffer_FillInfo(&view, args[0], (void *)ptr, len, 1, PyBUF_SIMPLE) < 0) { + goto exit; + } } else { /* any bytes-like object */ if (PyObject_GetBuffer(args[0], &view, PyBUF_SIMPLE) != 0) { @@ -1662,4 +1664,4 @@ _ssl_enum_crls(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje #ifndef _SSL_ENUM_CRLS_METHODDEF #define _SSL_ENUM_CRLS_METHODDEF #endif /* !defined(_SSL_ENUM_CRLS_METHODDEF) */ -/*[clinic end generated code: output=6342ea0062ab16c7 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=fd1c3378fbba5240 input=a9049054013a1b77]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 770878a3f8d2c78..c1df83a72bd8cea 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -4368,7 +4368,9 @@ def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> st if (ptr == NULL) {{{{ goto exit; }}}} - PyBuffer_FillInfo(&{paramname}, {argname}, (void *)ptr, len, 1, 0); + if (PyBuffer_FillInfo(&{paramname}, {argname}, (void *)ptr, len, 1, PyBUF_SIMPLE) < 0) {{{{ + goto exit; + }}}} }}}} else {{{{ /* any bytes-like object */ if (PyObject_GetBuffer({argname}, &{paramname}, PyBUF_SIMPLE) != 0) {{{{ From 992446dd5bd3fff92ea0f8064fb19eebfe105cef Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Mon, 5 Feb 2024 16:20:54 +0000 Subject: [PATCH 138/507] GH-113462: Limit the number of versions that a single class can use. (GH-114900) --- Include/cpython/object.h | 1 + Lib/test/test_type_cache.py | 13 +++++++++++++ .../2024-02-02-05-27-48.gh-issue-113462.VMml8q.rst | 2 ++ Objects/typeobject.c | 7 ++++++- 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-02-05-27-48.gh-issue-113462.VMml8q.rst diff --git a/Include/cpython/object.h b/Include/cpython/object.h index c93931634fee051..7512bb70c760fdd 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -229,6 +229,7 @@ struct _typeobject { /* bitset of which type-watchers care about this type */ unsigned char tp_watched; + uint16_t tp_versions_used; }; /* This struct is used by the specializer diff --git a/Lib/test/test_type_cache.py b/Lib/test/test_type_cache.py index 295df78a17374a3..58572c6f4d31578 100644 --- a/Lib/test/test_type_cache.py +++ b/Lib/test/test_type_cache.py @@ -79,6 +79,19 @@ class C: _clear_type_cache() + def test_per_class_limit(self): + class C: + x = 0 + + type_assign_version(C) + orig_version = type_get_version(C) + for i in range(1001): + C.x = i + type_assign_version(C) + + new_version = type_get_version(C) + self.assertEqual(new_version, 0) + @support.cpython_only class TypeCacheWithSpecializationTests(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-02-05-27-48.gh-issue-113462.VMml8q.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-02-05-27-48.gh-issue-113462.VMml8q.rst new file mode 100644 index 000000000000000..1a401ecebf019aa --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-02-05-27-48.gh-issue-113462.VMml8q.rst @@ -0,0 +1,2 @@ +Limit the number of versions that a single class can use. Prevents a few +wayward classes using up all the version numbers. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index a850473cad813dd..e220d10ce563c29 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -908,6 +908,8 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) { } } +#define MAX_VERSIONS_PER_CLASS 1000 + static int assign_version_tag(PyInterpreterState *interp, PyTypeObject *type) { @@ -922,7 +924,10 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type) if (!_PyType_HasFeature(type, Py_TPFLAGS_READY)) { return 0; } - + if (type->tp_versions_used >= MAX_VERSIONS_PER_CLASS) { + return 0; + } + type->tp_versions_used++; if (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE) { /* static types */ if (NEXT_GLOBAL_VERSION_TAG > _Py_MAX_GLOBAL_TYPE_VERSION_TAG) { From b4ba0f73d6eef3da321bb96aafd09dfbc572e95d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Mon, 5 Feb 2024 18:24:54 +0200 Subject: [PATCH 139/507] gh-43457: Tkinter: fix design flaws in wm_attributes() (GH-111404) * When called with a single argument to get a value, it allow to omit the minus prefix. * It can be called with keyword arguments to set attributes. * w.wm_attributes(return_python_dict=True) returns a dict instead of a tuple (it will be the default in future). * Setting wantobjects to 0 no longer affects the result. --- Doc/whatsnew/3.13.rst | 9 +++ Lib/test/test_tkinter/support.py | 2 +- Lib/test/test_tkinter/test_misc.py | 55 +++++++++++++++++++ Lib/tkinter/__init__.py | 51 ++++++++++------- Lib/tkinter/simpledialog.py | 2 +- ...3-10-27-19-24-58.gh-issue-43457.84lx9H.rst | 8 +++ 6 files changed, 106 insertions(+), 21 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-10-27-19-24-58.gh-issue-43457.84lx9H.rst diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 77f4fce6c321fee..c25d41351c2f3cd 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -421,6 +421,15 @@ tkinter :meth:`!tk_busy_current`, and :meth:`!tk_busy_status`. (Contributed by Miguel, klappnase and Serhiy Storchaka in :gh:`72684`.) +* The :mod:`tkinter` widget method :meth:`!wm_attributes` now accepts + the attribute name without the minus prefix to get window attributes, + e.g. ``w.wm_attributes('alpha')`` and allows to specify attributes and + values to set as keyword arguments, e.g. ``w.wm_attributes(alpha=0.5)``. + Add new optional keyword-only parameter *return_python_dict*: calling + ``w.wm_attributes(return_python_dict=True)`` returns the attributes as + a dict instead of a tuple. + (Contributed by Serhiy Storchaka in :gh:`43457`.) + * Add support of the "vsapi" element type in the :meth:`~tkinter.ttk.Style.element_create` method of :class:`tkinter.ttk.Style`. diff --git a/Lib/test/test_tkinter/support.py b/Lib/test/test_tkinter/support.py index a37705f0ae6febb..ebb9e00ff91bf0e 100644 --- a/Lib/test/test_tkinter/support.py +++ b/Lib/test/test_tkinter/support.py @@ -14,7 +14,7 @@ def setUpClass(cls): # Some window managers can maximize new windows. cls.root.wm_state('normal') try: - cls.root.wm_attributes('-zoomed', False) + cls.root.wm_attributes(zoomed=False) except tkinter.TclError: pass diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index 71553503005c48b..81a20b698a72eb4 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -437,6 +437,61 @@ def test_info_patchlevel(self): self.assertTrue(str(vi).startswith(f'{vi.major}.{vi.minor}')) +class WmTest(AbstractTkTest, unittest.TestCase): + + def test_wm_attribute(self): + w = self.root + attributes = w.wm_attributes(return_python_dict=True) + self.assertIsInstance(attributes, dict) + attributes2 = w.wm_attributes() + self.assertIsInstance(attributes2, tuple) + self.assertEqual(attributes2[::2], + tuple('-' + k for k in attributes)) + self.assertEqual(attributes2[1::2], tuple(attributes.values())) + # silently deprecated + attributes3 = w.wm_attributes(None) + if self.wantobjects: + self.assertEqual(attributes3, attributes2) + else: + self.assertIsInstance(attributes3, str) + + for name in attributes: + self.assertEqual(w.wm_attributes(name), attributes[name]) + # silently deprecated + for name in attributes: + self.assertEqual(w.wm_attributes('-' + name), attributes[name]) + + self.assertIn('alpha', attributes) + self.assertIn('fullscreen', attributes) + self.assertIn('topmost', attributes) + if w._windowingsystem == "win32": + self.assertIn('disabled', attributes) + self.assertIn('toolwindow', attributes) + self.assertIn('transparentcolor', attributes) + if w._windowingsystem == "aqua": + self.assertIn('modified', attributes) + self.assertIn('notify', attributes) + self.assertIn('titlepath', attributes) + self.assertIn('transparent', attributes) + if w._windowingsystem == "x11": + self.assertIn('type', attributes) + self.assertIn('zoomed', attributes) + + w.wm_attributes(alpha=0.5) + self.assertEqual(w.wm_attributes('alpha'), + 0.5 if self.wantobjects else '0.5') + w.wm_attributes(alpha=1.0) + self.assertEqual(w.wm_attributes('alpha'), + 1.0 if self.wantobjects else '1.0') + # silently deprecated + w.wm_attributes('-alpha', 0.5) + self.assertEqual(w.wm_attributes('alpha'), + 0.5 if self.wantobjects else '0.5') + w.wm_attributes(alpha=1.0) + self.assertEqual(w.wm_attributes('alpha'), + 1.0 if self.wantobjects else '1.0') + + class BindTest(AbstractTkTest, unittest.TestCase): def setUp(self): diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index a1567d332ae6efc..2be9da2cfb92993 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -2108,26 +2108,39 @@ def wm_aspect(self, aspect = wm_aspect - def wm_attributes(self, *args): - """This subcommand returns or sets platform specific attributes - - The first form returns a list of the platform specific flags and - their values. The second form returns the value for the specific - option. The third form sets one or more of the values. The values - are as follows: - - On Windows, -disabled gets or sets whether the window is in a - disabled state. -toolwindow gets or sets the style of the window - to toolwindow (as defined in the MSDN). -topmost gets or sets - whether this is a topmost window (displays above all other - windows). - - On Macintosh, XXXXX - - On Unix, there are currently no special attribute values. + def wm_attributes(self, *args, return_python_dict=False, **kwargs): + """Return or sets platform specific attributes. + + When called with a single argument return_python_dict=True, + return a dict of the platform specific attributes and their values. + When called without arguments or with a single argument + return_python_dict=False, return a tuple containing intermixed + attribute names with the minus prefix and their values. + + When called with a single string value, return the value for the + specific option. When called with keyword arguments, set the + corresponding attributes. """ - args = ('wm', 'attributes', self._w) + args - return self.tk.call(args) + if not kwargs: + if not args: + res = self.tk.call('wm', 'attributes', self._w) + if return_python_dict: + return _splitdict(self.tk, res) + else: + return self.tk.splitlist(res) + if len(args) == 1 and args[0] is not None: + option = args[0] + if option[0] == '-': + # TODO: deprecate + option = option[1:] + return self.tk.call('wm', 'attributes', self._w, '-' + option) + # TODO: deprecate + return self.tk.call('wm', 'attributes', self._w, *args) + elif args: + raise TypeError('wm_attribute() options have been specified as ' + 'positional and keyword arguments') + else: + self.tk.call('wm', 'attributes', self._w, *self._options(kwargs)) attributes = wm_attributes diff --git a/Lib/tkinter/simpledialog.py b/Lib/tkinter/simpledialog.py index 538bbfc318d7044..0f0dc66460f7987 100644 --- a/Lib/tkinter/simpledialog.py +++ b/Lib/tkinter/simpledialog.py @@ -262,7 +262,7 @@ def _setup_dialog(w): w.tk.call("::tk::unsupported::MacWindowStyle", "style", w, "moveableModal", "") elif w._windowingsystem == "x11": - w.wm_attributes("-type", "dialog") + w.wm_attributes(type="dialog") # -------------------------------------------------------------------- # convenience dialogues diff --git a/Misc/NEWS.d/next/Library/2023-10-27-19-24-58.gh-issue-43457.84lx9H.rst b/Misc/NEWS.d/next/Library/2023-10-27-19-24-58.gh-issue-43457.84lx9H.rst new file mode 100644 index 000000000000000..401a532ce03e777 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-27-19-24-58.gh-issue-43457.84lx9H.rst @@ -0,0 +1,8 @@ +Fix the :mod:`tkinter` widget method :meth:`!wm_attributes`. It now +accepts the attribute name without the minus prefix to get window attributes +and allows to specify attributes and values to set as keyword arguments. +Add new optional keyword argument *return_python_dict*: calling +``w.wm_attributes(return_python_dict=True)`` returns the attributes as +a dict instead of a tuple. +Calling ``w.wm_attributes()`` now returns a tuple instead of string if +*wantobjects* was set to 0. From 36518e69d74607e5f094ce55286188e4545a947d Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Mon, 5 Feb 2024 18:28:51 +0000 Subject: [PATCH 140/507] GH-108362: Incremental GC implementation (GH-108038) --- Doc/whatsnew/3.13.rst | 34 + Include/internal/pycore_gc.h | 42 +- Include/internal/pycore_object.h | 17 +- Include/internal/pycore_runtime_init.h | 8 +- Lib/test/test_gc.py | 22 +- ...-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst | 13 + Modules/gcmodule.c | 23 +- Objects/object.c | 15 + Objects/structseq.c | 5 +- Python/gc.c | 824 +++++++++++------- Python/gc_free_threading.c | 27 +- Python/import.c | 2 +- Tools/gdb/libpython.py | 7 +- 13 files changed, 647 insertions(+), 392 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index c25d41351c2f3cd..0770e28d230b4b3 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -92,6 +92,10 @@ Interpreter improvements: New Features ============ +* The cyclic garbage collector is now incremental. + This means that maximum pause times are reduced, + by an order of magnitude or more for larger heaps. + Improved Error Messages ----------------------- @@ -101,6 +105,13 @@ Improved Error Messages variables. See also :ref:`using-on-controlling-color`. (Contributed by Pablo Galindo Salgado in :gh:`112730`.) +Incremental Garbage Collection +------------------------------ + +* The cycle garbage collector is now incremental. + This means that maximum pause times are reduced + by an order of magnitude or more for larger heaps. + Other Language Changes ====================== @@ -232,6 +243,29 @@ fractions sign handling, minimum width and grouping. (Contributed by Mark Dickinson in :gh:`111320`.) +gc +-- +* The cyclic garbage collector is now incremental, which changes the meanings + of the results of :meth:`gc.get_threshold` and :meth:`gc.get_threshold` as + well as :meth:`gc.get_count` and :meth:`gc.get_stats`. +* :meth:`gc.get_threshold` returns a three-tuple for backwards compatibility, + the first value is the threshold for young collections, as before, the second + value determines the rate at which the old collection is scanned; the + default is 10 and higher values mean that the old collection is scanned more slowly. + The third value is meangless and is always zero. +* :meth:`gc.set_threshold` ignores any items after the second. +* :meth:`gc.get_count` and :meth:`gc.get_stats`. + These functions return the same format of results as before. + The only difference is that instead of the results refering to + the young, aging and old generations, the results refer to the + young generation and the aging and collecting spaces of the old generation. + +In summary, code that attempted to manipulate the behavior of the cycle GC may +not work as well as intended, but it is very unlikely to harmful. +All other code will work just fine. +Uses should avoid calling :meth:`gc.collect` unless their workload is episodic, +but that has always been the case to some extent. + glob ---- diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h index ca1d9fdf5253b8d..d2f5c69b45ee399 100644 --- a/Include/internal/pycore_gc.h +++ b/Include/internal/pycore_gc.h @@ -71,11 +71,15 @@ static inline int _PyObject_GC_MAY_BE_TRACKED(PyObject *obj) { /* Bit flags for _gc_prev */ /* Bit 0 is set when tp_finalize is called */ -#define _PyGC_PREV_MASK_FINALIZED (1) +#define _PyGC_PREV_MASK_FINALIZED 1 /* Bit 1 is set when the object is in generation which is GCed currently. */ -#define _PyGC_PREV_MASK_COLLECTING (2) +#define _PyGC_PREV_MASK_COLLECTING 2 + +/* Bit 0 is set if the object belongs to old space 1 */ +#define _PyGC_NEXT_MASK_OLD_SPACE_1 1 + /* The (N-2) most significant bits contain the real address. */ -#define _PyGC_PREV_SHIFT (2) +#define _PyGC_PREV_SHIFT 2 #define _PyGC_PREV_MASK (((uintptr_t) -1) << _PyGC_PREV_SHIFT) /* set for debugging information */ @@ -101,11 +105,13 @@ typedef enum { // Lowest bit of _gc_next is used for flags only in GC. // But it is always 0 for normal code. static inline PyGC_Head* _PyGCHead_NEXT(PyGC_Head *gc) { - uintptr_t next = gc->_gc_next; + uintptr_t next = gc->_gc_next & _PyGC_PREV_MASK; return (PyGC_Head*)next; } static inline void _PyGCHead_SET_NEXT(PyGC_Head *gc, PyGC_Head *next) { - gc->_gc_next = (uintptr_t)next; + uintptr_t unext = (uintptr_t)next; + assert((unext & ~_PyGC_PREV_MASK) == 0); + gc->_gc_next = (gc->_gc_next & ~_PyGC_PREV_MASK) | unext; } // Lowest two bits of _gc_prev is used for _PyGC_PREV_MASK_* flags. @@ -113,6 +119,7 @@ static inline PyGC_Head* _PyGCHead_PREV(PyGC_Head *gc) { uintptr_t prev = (gc->_gc_prev & _PyGC_PREV_MASK); return (PyGC_Head*)prev; } + static inline void _PyGCHead_SET_PREV(PyGC_Head *gc, PyGC_Head *prev) { uintptr_t uprev = (uintptr_t)prev; assert((uprev & ~_PyGC_PREV_MASK) == 0); @@ -198,6 +205,13 @@ struct gc_generation { generations */ }; +struct gc_collection_stats { + /* number of collected objects */ + Py_ssize_t collected; + /* total number of uncollectable objects (put into gc.garbage) */ + Py_ssize_t uncollectable; +}; + /* Running stats per generation */ struct gc_generation_stats { /* total number of collections */ @@ -219,8 +233,8 @@ struct _gc_runtime_state { int enabled; int debug; /* linked lists of container objects */ - struct gc_generation generations[NUM_GENERATIONS]; - PyGC_Head *generation0; + struct gc_generation young; + struct gc_generation old[2]; /* a permanent generation which won't be collected */ struct gc_generation permanent_generation; struct gc_generation_stats generation_stats[NUM_GENERATIONS]; @@ -233,22 +247,20 @@ struct _gc_runtime_state { /* This is the number of objects that survived the last full collection. It approximates the number of long lived objects tracked by the GC. - (by "full collection", we mean a collection of the oldest generation). */ Py_ssize_t long_lived_total; - /* This is the number of objects that survived all "non-full" - collections, and are awaiting to undergo a full collection for - the first time. */ - Py_ssize_t long_lived_pending; + + Py_ssize_t work_to_do; + /* Which of the old spaces is the visited space */ + int visited_space; }; extern void _PyGC_InitState(struct _gc_runtime_state *); -extern Py_ssize_t _PyGC_Collect(PyThreadState *tstate, int generation, - _PyGC_Reason reason); -extern Py_ssize_t _PyGC_CollectNoFail(PyThreadState *tstate); +extern Py_ssize_t _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason); +extern void _PyGC_CollectNoFail(PyThreadState *tstate); /* Freeze objects tracked by the GC and ignore them in future collections. */ extern void _PyGC_Freeze(PyInterpreterState *interp); diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 34a83ea228e8b10..efa712c4a0b4588 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -125,19 +125,7 @@ static inline void _Py_RefcntAdd(PyObject* op, Py_ssize_t n) } #define _Py_RefcntAdd(op, n) _Py_RefcntAdd(_PyObject_CAST(op), n) -static inline void _Py_SetImmortal(PyObject *op) -{ - if (op) { -#ifdef Py_GIL_DISABLED - op->ob_tid = _Py_UNOWNED_TID; - op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL; - op->ob_ref_shared = 0; -#else - op->ob_refcnt = _Py_IMMORTAL_REFCNT; -#endif - } -} -#define _Py_SetImmortal(op) _Py_SetImmortal(_PyObject_CAST(op)) +extern void _Py_SetImmortal(PyObject *op); // Makes an immortal object mortal again with the specified refcnt. Should only // be used during runtime finalization. @@ -325,11 +313,12 @@ static inline void _PyObject_GC_TRACK( filename, lineno, __func__); PyInterpreterState *interp = _PyInterpreterState_GET(); - PyGC_Head *generation0 = interp->gc.generation0; + PyGC_Head *generation0 = &interp->gc.young.head; PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev); _PyGCHead_SET_NEXT(last, gc); _PyGCHead_SET_PREV(gc, last); _PyGCHead_SET_NEXT(gc, generation0); + assert((gc->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1) == 0); generation0->_gc_prev = (uintptr_t)gc; #endif } diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index 0a5c92bb84b524f..4370ad05bdc0584 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -160,12 +160,12 @@ extern PyTypeObject _PyExc_MemoryError; }, \ .gc = { \ .enabled = 1, \ - .generations = { \ - /* .head is set in _PyGC_InitState(). */ \ - { .threshold = 700, }, \ - { .threshold = 10, }, \ + .young = { .threshold = 2000, }, \ + .old = { \ { .threshold = 10, }, \ + { .threshold = 0, }, \ }, \ + .work_to_do = -5000, \ }, \ .object_state = _py_object_state_INIT(INTERP), \ .dtoa = _dtoa_state_INIT(&(INTERP)), \ diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index b01f344cb14a1a7..0002852fce96438 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -383,19 +383,11 @@ def test_collect_generations(self): # each call to collect(N) x = [] gc.collect(0) - # x is now in gen 1 + # x is now in the old gen a, b, c = gc.get_count() - gc.collect(1) - # x is now in gen 2 - d, e, f = gc.get_count() - gc.collect(2) - # x is now in gen 3 - g, h, i = gc.get_count() - # We don't check a, d, g since their exact values depends on + # We don't check a since its exact values depends on # internal implementation details of the interpreter. self.assertEqual((b, c), (1, 0)) - self.assertEqual((e, f), (0, 1)) - self.assertEqual((h, i), (0, 0)) def test_trashcan(self): class Ouch: @@ -846,16 +838,6 @@ def test_get_objects_generations(self): self.assertFalse( any(l is element for element in gc.get_objects(generation=2)) ) - gc.collect(generation=1) - self.assertFalse( - any(l is element for element in gc.get_objects(generation=0)) - ) - self.assertFalse( - any(l is element for element in gc.get_objects(generation=1)) - ) - self.assertTrue( - any(l is element for element in gc.get_objects(generation=2)) - ) gc.collect(generation=2) self.assertFalse( any(l is element for element in gc.get_objects(generation=0)) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst new file mode 100644 index 000000000000000..1fe4e0f41e12951 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst @@ -0,0 +1,13 @@ +Implements an incremental cyclic garbage collector. By collecting the old +generation in increments, there is no need for a full heap scan. This can +hugely reduce maximum pause time for programs with large heaps. + +Reduces the number of generations from three to two. The old generation is +split into two spaces, "aging" and "collecting". + +Collection happens in two steps:: * First, the young generation is scanned +and the survivors moved to the end of the aging space. * Then objects are +taken from the collecting space, at such a rate that all cycles are +collected eventually. Those objects are then scanned and the survivors +moved to the end of the aging space. When the collecting space becomes +empty, the two spaces are swapped. diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index ffddef34ecce7a5..3b63dd7a9a83533 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -158,17 +158,12 @@ gc_set_threshold_impl(PyObject *module, int threshold0, int group_right_1, { GCState *gcstate = get_gc_state(); - gcstate->generations[0].threshold = threshold0; + gcstate->young.threshold = threshold0; if (group_right_1) { - gcstate->generations[1].threshold = threshold1; + gcstate->old[0].threshold = threshold1; } if (group_right_2) { - gcstate->generations[2].threshold = threshold2; - - /* generations higher than 2 get the same threshold */ - for (int i = 3; i < NUM_GENERATIONS; i++) { - gcstate->generations[i].threshold = gcstate->generations[2].threshold; - } + gcstate->old[1].threshold = threshold2; } Py_RETURN_NONE; } @@ -185,9 +180,9 @@ gc_get_threshold_impl(PyObject *module) { GCState *gcstate = get_gc_state(); return Py_BuildValue("(iii)", - gcstate->generations[0].threshold, - gcstate->generations[1].threshold, - gcstate->generations[2].threshold); + gcstate->young.threshold, + gcstate->old[0].threshold, + 0); } /*[clinic input] @@ -202,9 +197,9 @@ gc_get_count_impl(PyObject *module) { GCState *gcstate = get_gc_state(); return Py_BuildValue("(iii)", - gcstate->generations[0].count, - gcstate->generations[1].count, - gcstate->generations[2].count); + gcstate->young.count, + gcstate->old[gcstate->visited_space].count, + gcstate->old[gcstate->visited_space^1].count); } /*[clinic input] diff --git a/Objects/object.c b/Objects/object.c index bbf7f98ae3daf92..7247eb21df6b6eb 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2387,6 +2387,21 @@ _Py_NewReferenceNoTotal(PyObject *op) new_reference(op); } +void +_Py_SetImmortal(PyObject *op) +{ + if (PyObject_IS_GC(op) && _PyObject_GC_IS_TRACKED(op)) { + _PyObject_GC_UNTRACK(op); + } +#ifdef Py_GIL_DISABLED + op->ob_tid = _Py_UNOWNED_TID; + op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL; + op->ob_ref_shared = 0; +#else + op->ob_refcnt = _Py_IMMORTAL_REFCNT; +#endif +} + void _Py_ResurrectReference(PyObject *op) { diff --git a/Objects/structseq.c b/Objects/structseq.c index 581d6ad240885a0..661d96a968fb809 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -603,6 +603,9 @@ _PyStructSequence_InitBuiltinWithFlags(PyInterpreterState *interp, PyStructSequence_Desc *desc, unsigned long tp_flags) { + if (Py_TYPE(type) == NULL) { + Py_SET_TYPE(type, &PyType_Type); + } Py_ssize_t n_unnamed_members; Py_ssize_t n_members = count_members(desc, &n_unnamed_members); PyMemberDef *members = NULL; @@ -618,7 +621,7 @@ _PyStructSequence_InitBuiltinWithFlags(PyInterpreterState *interp, } initialize_static_fields(type, desc, members, tp_flags); - _Py_SetImmortal(type); + _Py_SetImmortal((PyObject *)type); } #ifndef NDEBUG else { diff --git a/Python/gc.c b/Python/gc.c index 466467602915264..cda12ff7fbc982f 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -45,7 +45,7 @@ typedef struct _gc_runtime_state GCState; // move_legacy_finalizers() removes this flag instead. // Between them, unreachable list is not normal list and we can not use // most gc_list_* functions for it. -#define NEXT_MASK_UNREACHABLE (1) +#define NEXT_MASK_UNREACHABLE 2 #define AS_GC(op) _Py_AS_GC(op) #define FROM_GC(gc) _Py_FROM_GC(gc) @@ -95,9 +95,48 @@ gc_decref(PyGC_Head *g) g->_gc_prev -= 1 << _PyGC_PREV_SHIFT; } +static inline int +gc_old_space(PyGC_Head *g) +{ + return g->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1; +} -#define GEN_HEAD(gcstate, n) (&(gcstate)->generations[n].head) +static inline int +flip_old_space(int space) +{ + assert(space == 0 || space == 1); + return space ^ _PyGC_NEXT_MASK_OLD_SPACE_1; +} +static inline void +gc_flip_old_space(PyGC_Head *g) +{ + g->_gc_next ^= _PyGC_NEXT_MASK_OLD_SPACE_1; +} + +static inline void +gc_set_old_space(PyGC_Head *g, int space) +{ + assert(space == 0 || space == _PyGC_NEXT_MASK_OLD_SPACE_1); + g->_gc_next &= ~_PyGC_NEXT_MASK_OLD_SPACE_1; + g->_gc_next |= space; +} + +static PyGC_Head * +GEN_HEAD(GCState *gcstate, int n) +{ + assert((gcstate->visited_space & (~1)) == 0); + switch(n) { + case 0: + return &gcstate->young.head; + case 1: + return &gcstate->old[gcstate->visited_space].head; + case 2: + return &gcstate->old[gcstate->visited_space^1].head; + default: + Py_UNREACHABLE(); + } +} static GCState * get_gc_state(void) @@ -116,11 +155,12 @@ _PyGC_InitState(GCState *gcstate) GEN.head._gc_prev = (uintptr_t)&GEN.head; \ } while (0) - for (int i = 0; i < NUM_GENERATIONS; i++) { - assert(gcstate->generations[i].count == 0); - INIT_HEAD(gcstate->generations[i]); - }; - gcstate->generation0 = GEN_HEAD(gcstate, 0); + assert(gcstate->young.count == 0); + assert(gcstate->old[0].count == 0); + assert(gcstate->old[1].count == 0); + INIT_HEAD(gcstate->young); + INIT_HEAD(gcstate->old[0]); + INIT_HEAD(gcstate->old[1]); INIT_HEAD(gcstate->permanent_generation); #undef INIT_HEAD @@ -218,6 +258,7 @@ gc_list_is_empty(PyGC_Head *list) static inline void gc_list_append(PyGC_Head *node, PyGC_Head *list) { + assert((list->_gc_prev & ~_PyGC_PREV_MASK) == 0); PyGC_Head *last = (PyGC_Head *)list->_gc_prev; // last <-> node @@ -275,6 +316,8 @@ gc_list_merge(PyGC_Head *from, PyGC_Head *to) PyGC_Head *from_tail = GC_PREV(from); assert(from_head != from); assert(from_tail != from); + assert(gc_list_is_empty(to) || + gc_old_space(to_tail) == gc_old_space(from_tail)); _PyGCHead_SET_NEXT(to_tail, from_head); _PyGCHead_SET_PREV(from_head, to_tail); @@ -343,8 +386,8 @@ enum flagstates {collecting_clear_unreachable_clear, static void validate_list(PyGC_Head *head, enum flagstates flags) { - assert((head->_gc_prev & PREV_MASK_COLLECTING) == 0); - assert((head->_gc_next & NEXT_MASK_UNREACHABLE) == 0); + assert((head->_gc_prev & ~_PyGC_PREV_MASK) == 0); + assert((head->_gc_next & ~_PyGC_PREV_MASK) == 0); uintptr_t prev_value = 0, next_value = 0; switch (flags) { case collecting_clear_unreachable_clear: @@ -366,7 +409,7 @@ validate_list(PyGC_Head *head, enum flagstates flags) PyGC_Head *gc = GC_NEXT(head); while (gc != head) { PyGC_Head *trueprev = GC_PREV(gc); - PyGC_Head *truenext = (PyGC_Head *)(gc->_gc_next & ~NEXT_MASK_UNREACHABLE); + PyGC_Head *truenext = GC_NEXT(gc); assert(truenext != NULL); assert(trueprev == prev); assert((gc->_gc_prev & PREV_MASK_COLLECTING) == prev_value); @@ -376,8 +419,44 @@ validate_list(PyGC_Head *head, enum flagstates flags) } assert(prev == GC_PREV(head)); } + +static void +validate_old(GCState *gcstate) +{ + for (int space = 0; space < 2; space++) { + PyGC_Head *head = &gcstate->old[space].head; + PyGC_Head *gc = GC_NEXT(head); + while (gc != head) { + PyGC_Head *next = GC_NEXT(gc); + assert(gc_old_space(gc) == space); + gc = next; + } + } +} + +static void +validate_consistent_old_space(PyGC_Head *head) +{ + PyGC_Head *prev = head; + PyGC_Head *gc = GC_NEXT(head); + if (gc == head) { + return; + } + int old_space = gc_old_space(gc); + while (gc != head) { + PyGC_Head *truenext = GC_NEXT(gc); + assert(truenext != NULL); + assert(gc_old_space(gc) == old_space); + prev = gc; + gc = truenext; + } + assert(prev == GC_PREV(head)); +} + #else #define validate_list(x, y) do{}while(0) +#define validate_old(g) do{}while(0) +#define validate_consistent_old_space(l) do{}while(0) #endif /*** end of list stuff ***/ @@ -394,15 +473,7 @@ update_refs(PyGC_Head *containers) while (gc != containers) { next = GC_NEXT(gc); - /* Move any object that might have become immortal to the - * permanent generation as the reference count is not accurately - * reflecting the actual number of live references to this object - */ - if (_Py_IsImmortal(FROM_GC(gc))) { - gc_list_move(gc, &get_gc_state()->permanent_generation.head); - gc = next; - continue; - } + assert(!_Py_IsImmortal(FROM_GC(gc))); gc_reset_refs(gc, Py_REFCNT(FROM_GC(gc))); /* Python's cyclic gc should never see an incoming refcount * of 0: if something decref'ed to 0, it should have been @@ -500,12 +571,13 @@ visit_reachable(PyObject *op, void *arg) // Manually unlink gc from unreachable list because the list functions // don't work right in the presence of NEXT_MASK_UNREACHABLE flags. PyGC_Head *prev = GC_PREV(gc); - PyGC_Head *next = (PyGC_Head*)(gc->_gc_next & ~NEXT_MASK_UNREACHABLE); + PyGC_Head *next = GC_NEXT(gc); _PyObject_ASSERT(FROM_GC(prev), prev->_gc_next & NEXT_MASK_UNREACHABLE); _PyObject_ASSERT(FROM_GC(next), next->_gc_next & NEXT_MASK_UNREACHABLE); - prev->_gc_next = gc->_gc_next; // copy NEXT_MASK_UNREACHABLE + prev->_gc_next = gc->_gc_next; // copy flag bits + gc->_gc_next &= ~NEXT_MASK_UNREACHABLE; _PyGCHead_SET_PREV(next, prev); gc_list_append(gc, reachable); @@ -557,6 +629,9 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable) * or to the right have been scanned yet. */ + validate_consistent_old_space(young); + /* Record which old space we are in, and set NEXT_MASK_UNREACHABLE bit for convenience */ + uintptr_t flags = NEXT_MASK_UNREACHABLE | (gc->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1); while (gc != young) { if (gc_get_refs(gc)) { /* gc is definitely reachable from outside the @@ -602,17 +677,18 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable) // But this may pollute the unreachable list head's 'next' pointer // too. That's semantically senseless but expedient here - the // damage is repaired when this function ends. - last->_gc_next = (NEXT_MASK_UNREACHABLE | (uintptr_t)gc); + last->_gc_next = flags | (uintptr_t)gc; _PyGCHead_SET_PREV(gc, last); - gc->_gc_next = (NEXT_MASK_UNREACHABLE | (uintptr_t)unreachable); + gc->_gc_next = flags | (uintptr_t)unreachable; unreachable->_gc_prev = (uintptr_t)gc; } - gc = (PyGC_Head*)prev->_gc_next; + gc = _PyGCHead_NEXT(prev); } // young->_gc_prev must be last element remained in the list. young->_gc_prev = (uintptr_t)prev; + young->_gc_next &= _PyGC_PREV_MASK; // don't let the pollution of the list head's next pointer leak - unreachable->_gc_next &= ~NEXT_MASK_UNREACHABLE; + unreachable->_gc_next &= _PyGC_PREV_MASK; } static void @@ -669,8 +745,8 @@ move_legacy_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers) PyObject *op = FROM_GC(gc); _PyObject_ASSERT(op, gc->_gc_next & NEXT_MASK_UNREACHABLE); + next = GC_NEXT(gc); gc->_gc_next &= ~NEXT_MASK_UNREACHABLE; - next = (PyGC_Head*)gc->_gc_next; if (has_legacy_finalizer(op)) { gc_clear_collecting(gc); @@ -689,8 +765,8 @@ clear_unreachable_mask(PyGC_Head *unreachable) assert((unreachable->_gc_next & NEXT_MASK_UNREACHABLE) == 0); for (gc = GC_NEXT(unreachable); gc != unreachable; gc = next) { _PyObject_ASSERT((PyObject*)FROM_GC(gc), gc->_gc_next & NEXT_MASK_UNREACHABLE); + next = GC_NEXT(gc); gc->_gc_next &= ~NEXT_MASK_UNREACHABLE; - next = (PyGC_Head*)gc->_gc_next; } validate_list(unreachable, collecting_set_unreachable_clear); } @@ -1023,25 +1099,6 @@ delete_garbage(PyThreadState *tstate, GCState *gcstate, } -// Show stats for objects in each generations -static void -show_stats_each_generations(GCState *gcstate) -{ - char buf[100]; - size_t pos = 0; - - for (int i = 0; i < NUM_GENERATIONS && pos < sizeof(buf); i++) { - pos += PyOS_snprintf(buf+pos, sizeof(buf)-pos, - " %zd", - gc_list_size(GEN_HEAD(gcstate, i))); - } - - PySys_FormatStderr( - "gc: objects in each generation:%s\n" - "gc: objects in permanent generation: %zd\n", - buf, gc_list_size(&gcstate->permanent_generation.head)); -} - /* Deduce which objects among "base" are unreachable from outside the list and move them to 'unreachable'. The process consist in the following steps: @@ -1115,7 +1172,6 @@ deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) { * the reachable objects instead. But this is a one-time cost, probably not * worth complicating the code to speed just a little. */ - gc_list_init(unreachable); move_unreachable(base, unreachable); // gc_prev is pointer again validate_list(base, collecting_clear_unreachable_clear); validate_list(unreachable, collecting_set_unreachable_set); @@ -1154,219 +1210,272 @@ handle_resurrected_objects(PyGC_Head *unreachable, PyGC_Head* still_unreachable, } -/* Invoke progress callbacks to notify clients that garbage collection - * is starting or stopping - */ +#define UNTRACK_TUPLES 1 +#define UNTRACK_DICTS 2 + static void -invoke_gc_callback(PyThreadState *tstate, const char *phase, - int generation, Py_ssize_t collected, - Py_ssize_t uncollectable) -{ - assert(!_PyErr_Occurred(tstate)); +gc_collect_region(PyThreadState *tstate, + PyGC_Head *from, + PyGC_Head *to, + int untrack, + struct gc_collection_stats *stats); - /* we may get called very early */ - GCState *gcstate = &tstate->interp->gc; - if (gcstate->callbacks == NULL) { - return; +static inline Py_ssize_t +gc_list_set_space(PyGC_Head *list, int space) +{ + Py_ssize_t size = 0; + PyGC_Head *gc; + for (gc = GC_NEXT(list); gc != list; gc = GC_NEXT(gc)) { + gc_set_old_space(gc, space); + size++; } + return size; +} - /* The local variable cannot be rebound, check it for sanity */ - assert(PyList_CheckExact(gcstate->callbacks)); - PyObject *info = NULL; - if (PyList_GET_SIZE(gcstate->callbacks) != 0) { - info = Py_BuildValue("{sisnsn}", - "generation", generation, - "collected", collected, - "uncollectable", uncollectable); - if (info == NULL) { - PyErr_FormatUnraisable("Exception ignored on invoking gc callbacks"); - return; + +static void +add_stats(GCState *gcstate, int gen, struct gc_collection_stats *stats) +{ + gcstate->generation_stats[gen].collected += stats->collected; + gcstate->generation_stats[gen].uncollectable += stats->uncollectable; + gcstate->generation_stats[gen].collections += 1; +} + + +/* Multiply by 4 so that the default incremental threshold of 10 + * scans objects at 40% the rate that the young gen tenures them. */ +#define SCAN_RATE_MULTIPLIER 4 + + +static void +gc_collect_young(PyThreadState *tstate, + struct gc_collection_stats *stats) +{ + GCState *gcstate = &tstate->interp->gc; + PyGC_Head *young = &gcstate->young.head; + PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head; +#ifdef Py_STATS + { + Py_ssize_t count = 0; + PyGC_Head *gc; + for (gc = GC_NEXT(young); gc != young; gc = GC_NEXT(gc)) { + count++; } } +#endif - PyObject *phase_obj = PyUnicode_FromString(phase); - if (phase_obj == NULL) { - Py_XDECREF(info); - PyErr_FormatUnraisable("Exception ignored on invoking gc callbacks"); - return; + PyGC_Head survivors; + gc_list_init(&survivors); + gc_collect_region(tstate, young, &survivors, UNTRACK_TUPLES, stats); + Py_ssize_t survivor_count = 0; + if (gcstate->visited_space) { + /* objects in visited space have bit set, so we set it here */ + survivor_count = gc_list_set_space(&survivors, 1); } - - PyObject *stack[] = {phase_obj, info}; - for (Py_ssize_t i=0; i<PyList_GET_SIZE(gcstate->callbacks); i++) { - PyObject *r, *cb = PyList_GET_ITEM(gcstate->callbacks, i); - Py_INCREF(cb); /* make sure cb doesn't go away */ - r = PyObject_Vectorcall(cb, stack, 2, NULL); - if (r == NULL) { - PyErr_WriteUnraisable(cb); - } - else { - Py_DECREF(r); + else { + PyGC_Head *gc; + for (gc = GC_NEXT(&survivors); gc != &survivors; gc = GC_NEXT(gc)) { +#ifdef GC_DEBUG + assert(gc_old_space(gc) == 0); +#endif + survivor_count++; } - Py_DECREF(cb); } - Py_DECREF(phase_obj); - Py_XDECREF(info); - assert(!_PyErr_Occurred(tstate)); + gc_list_merge(&survivors, visited); + validate_old(gcstate); + gcstate->young.count = 0; + gcstate->old[gcstate->visited_space].count++; + Py_ssize_t scale_factor = gcstate->old[0].threshold; + if (scale_factor < 1) { + scale_factor = 1; + } + gcstate->work_to_do += survivor_count + survivor_count * SCAN_RATE_MULTIPLIER / scale_factor; + add_stats(gcstate, 0, stats); +} + +static inline int +is_in_visited(PyGC_Head *gc, int visited_space) +{ + assert(visited_space == 0 || flip_old_space(visited_space) == 0); + return gc_old_space(gc) == visited_space; } +struct container_and_flag { + PyGC_Head *container; + int visited_space; +}; -/* Find the oldest generation (highest numbered) where the count - * exceeds the threshold. Objects in the that generation and - * generations younger than it will be collected. */ +/* A traversal callback for adding to container) */ static int -gc_select_generation(GCState *gcstate) -{ - for (int i = NUM_GENERATIONS-1; i >= 0; i--) { - if (gcstate->generations[i].count > gcstate->generations[i].threshold) { - /* Avoid quadratic performance degradation in number - of tracked objects (see also issue #4074): - - To limit the cost of garbage collection, there are two strategies; - - make each collection faster, e.g. by scanning fewer objects - - do less collections - This heuristic is about the latter strategy. - - In addition to the various configurable thresholds, we only trigger a - full collection if the ratio - - long_lived_pending / long_lived_total - - is above a given value (hardwired to 25%). - - The reason is that, while "non-full" collections (i.e., collections of - the young and middle generations) will always examine roughly the same - number of objects -- determined by the aforementioned thresholds --, - the cost of a full collection is proportional to the total number of - long-lived objects, which is virtually unbounded. - - Indeed, it has been remarked that doing a full collection every - <constant number> of object creations entails a dramatic performance - degradation in workloads which consist in creating and storing lots of - long-lived objects (e.g. building a large list of GC-tracked objects would - show quadratic performance, instead of linear as expected: see issue #4074). - - Using the above ratio, instead, yields amortized linear performance in - the total number of objects (the effect of which can be summarized - thusly: "each full garbage collection is more and more costly as the - number of objects grows, but we do fewer and fewer of them"). - - This heuristic was suggested by Martin von Löwis on python-dev in - June 2008. His original analysis and proposal can be found at: - http://mail.python.org/pipermail/python-dev/2008-June/080579.html - */ - if (i == NUM_GENERATIONS - 1 - && gcstate->long_lived_pending < gcstate->long_lived_total / 4) - { - continue; - } - return i; +visit_add_to_container(PyObject *op, void *arg) +{ + OBJECT_STAT_INC(object_visits); + struct container_and_flag *cf = (struct container_and_flag *)arg; + int visited = cf->visited_space; + assert(visited == get_gc_state()->visited_space); + if (_PyObject_IS_GC(op)) { + PyGC_Head *gc = AS_GC(op); + if (_PyObject_GC_IS_TRACKED(op) && + gc_old_space(gc) != visited) { + assert(!_Py_IsImmortal(op)); + gc_flip_old_space(gc); + gc_list_move(gc, cf->container); } } - return -1; + return 0; } - -/* This is the main function. Read this to understand how the - * collection process works. */ -static Py_ssize_t -gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) +static uintptr_t +expand_region_transitively_reachable(PyGC_Head *container, PyGC_Head *gc, GCState *gcstate) { - int i; - Py_ssize_t m = 0; /* # objects collected */ - Py_ssize_t n = 0; /* # unreachable objects that couldn't be collected */ - PyGC_Head *young; /* the generation we are examining */ - PyGC_Head *old; /* next older generation */ - PyGC_Head unreachable; /* non-problematic unreachable trash */ - PyGC_Head finalizers; /* objects with, & reachable from, __del__ */ - PyGC_Head *gc; - _PyTime_t t1 = 0; /* initialize to prevent a compiler warning */ - GCState *gcstate = &tstate->interp->gc; - - // gc_collect_main() must not be called before _PyGC_Init - // or after _PyGC_Fini() - assert(gcstate->garbage != NULL); - assert(!_PyErr_Occurred(tstate)); + validate_list(container, collecting_clear_unreachable_clear); + struct container_and_flag arg = { + .container = container, + .visited_space = gcstate->visited_space, + }; + uintptr_t size = 0; + assert(GC_NEXT(gc) == container); + while (gc != container) { + /* Survivors will be moved to visited space, so they should + * have been marked as visited */ + assert(is_in_visited(gc, gcstate->visited_space)); + PyObject *op = FROM_GC(gc); + if (_Py_IsImmortal(op)) { + PyGC_Head *next = GC_NEXT(gc); + gc_list_move(gc, &get_gc_state()->permanent_generation.head); + gc = next; + continue; + } + traverseproc traverse = Py_TYPE(op)->tp_traverse; + (void) traverse(op, + visit_add_to_container, + &arg); + gc = GC_NEXT(gc); + size++; + } + return size; +} - int expected = 0; - if (!_Py_atomic_compare_exchange_int(&gcstate->collecting, &expected, 1)) { - // Don't start a garbage collection if one is already in progress. - return 0; +/* Do bookkeeping for a completed GC cycle */ +static void +completed_cycle(GCState *gcstate) +{ + assert(gc_list_is_empty(&gcstate->old[gcstate->visited_space^1].head)); + assert(gc_list_is_empty(&gcstate->young.head)); + gcstate->visited_space = flip_old_space(gcstate->visited_space); + if (gcstate->work_to_do > 0) { + gcstate->work_to_do = 0; } +} - if (generation == GENERATION_AUTO) { - // Select the oldest generation that needs collecting. We will collect - // objects from that generation and all generations younger than it. - generation = gc_select_generation(gcstate); - if (generation < 0) { - // No generation needs to be collected. - _Py_atomic_store_int(&gcstate->collecting, 0); - return 0; +static void +gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats) +{ + GCState *gcstate = &tstate->interp->gc; + if (gcstate->work_to_do <= 0) { + /* No work to do */ + return; + } + PyGC_Head *not_visited = &gcstate->old[gcstate->visited_space^1].head; + PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head; + PyGC_Head increment; + gc_list_init(&increment); + if (gc_list_is_empty(not_visited)) { + completed_cycle(gcstate); + return; + } + Py_ssize_t region_size = 0; + while (region_size < gcstate->work_to_do) { + if (gc_list_is_empty(not_visited)) { + break; } + PyGC_Head *gc = _PyGCHead_NEXT(not_visited); + gc_list_move(gc, &increment); + gc_set_old_space(gc, gcstate->visited_space); + region_size += expand_region_transitively_reachable(&increment, gc, gcstate); } - - assert(generation >= 0 && generation < NUM_GENERATIONS); - -#ifdef Py_STATS - if (_Py_stats) { - _Py_stats->object_stats.object_visits = 0; + assert(region_size == gc_list_size(&increment)); + PyGC_Head survivors; + gc_list_init(&survivors); + gc_collect_region(tstate, &increment, &survivors, UNTRACK_TUPLES, stats); + gc_list_merge(&survivors, visited); + assert(gc_list_is_empty(&increment)); + gcstate->work_to_do -= region_size; + validate_old(gcstate); + add_stats(gcstate, 1, stats); + if (gc_list_is_empty(not_visited)) { + completed_cycle(gcstate); } -#endif - GC_STAT_ADD(generation, collections, 1); +} - if (reason != _Py_GC_REASON_SHUTDOWN) { - invoke_gc_callback(tstate, "start", generation, 0, 0); - } - if (gcstate->debug & _PyGC_DEBUG_STATS) { - PySys_WriteStderr("gc: collecting generation %d...\n", generation); - show_stats_each_generations(gcstate); - t1 = _PyTime_GetPerfCounter(); +static void +gc_collect_full(PyThreadState *tstate, + struct gc_collection_stats *stats) +{ + GCState *gcstate = &tstate->interp->gc; + validate_old(gcstate); + PyGC_Head *young = &gcstate->young.head; + PyGC_Head *old0 = &gcstate->old[0].head; + PyGC_Head *old1 = &gcstate->old[1].head; + /* merge all generations into old0 */ + gc_list_merge(young, old0); + gcstate->young.count = 0; + PyGC_Head *gc = GC_NEXT(old1); + while (gc != old1) { + PyGC_Head *next = GC_NEXT(gc); + gc_set_old_space(gc, 0); + gc = next; } + gc_list_merge(old1, old0); - if (PyDTrace_GC_START_ENABLED()) { - PyDTrace_GC_START(generation); - } + gc_collect_region(tstate, old0, old0, + UNTRACK_TUPLES | UNTRACK_DICTS, + stats); + gcstate->visited_space = 1; + gcstate->young.count = 0; + gcstate->old[0].count = 0; + gcstate->old[1].count = 0; - /* update collection and allocation counters */ - if (generation+1 < NUM_GENERATIONS) { - gcstate->generations[generation+1].count += 1; - } - for (i = 0; i <= generation; i++) { - gcstate->generations[i].count = 0; - } + gcstate->work_to_do = - gcstate->young.threshold * 2; - /* merge younger generations with one we are currently collecting */ - for (i = 0; i < generation; i++) { - gc_list_merge(GEN_HEAD(gcstate, i), GEN_HEAD(gcstate, generation)); - } + _PyGC_ClearAllFreeLists(tstate->interp); + validate_old(gcstate); + add_stats(gcstate, 2, stats); +} - /* handy references */ - young = GEN_HEAD(gcstate, generation); - if (generation < NUM_GENERATIONS-1) { - old = GEN_HEAD(gcstate, generation+1); - } - else { - old = young; - } - validate_list(old, collecting_clear_unreachable_clear); +/* This is the main function. Read this to understand how the + * collection process works. */ +static void +gc_collect_region(PyThreadState *tstate, + PyGC_Head *from, + PyGC_Head *to, + int untrack, + struct gc_collection_stats *stats) +{ + PyGC_Head unreachable; /* non-problematic unreachable trash */ + PyGC_Head finalizers; /* objects with, & reachable from, __del__ */ + PyGC_Head *gc; /* initialize to prevent a compiler warning */ + GCState *gcstate = &tstate->interp->gc; - deduce_unreachable(young, &unreachable); + assert(gcstate->garbage != NULL); + assert(!_PyErr_Occurred(tstate)); - untrack_tuples(young); - /* Move reachable objects to next generation. */ - if (young != old) { - if (generation == NUM_GENERATIONS - 2) { - gcstate->long_lived_pending += gc_list_size(young); - } - gc_list_merge(young, old); + gc_list_init(&unreachable); + deduce_unreachable(from, &unreachable); + validate_consistent_old_space(from); + if (untrack & UNTRACK_TUPLES) { + untrack_tuples(from); } - else { - /* We only un-track dicts in full collections, to avoid quadratic - dict build-up. See issue #14775. */ - untrack_dicts(young); - gcstate->long_lived_pending = 0; - gcstate->long_lived_total = gc_list_size(young); + if (untrack & UNTRACK_DICTS) { + untrack_dicts(from); } + validate_consistent_old_space(to); + if (from != to) { + gc_list_merge(from, to); + } + validate_consistent_old_space(to); + /* Move reachable objects to next generation. */ /* All objects in unreachable are trash, but objects reachable from * legacy finalizers (e.g. tp_del) can't safely be deleted. @@ -1380,10 +1489,8 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) * and we move those into the finalizers list too. */ move_legacy_finalizer_reachable(&finalizers); - validate_list(&finalizers, collecting_clear_unreachable_clear); validate_list(&unreachable, collecting_set_unreachable_clear); - /* Print debugging information. */ if (gcstate->debug & _PyGC_DEBUG_COLLECTABLE) { for (gc = GC_NEXT(&unreachable); gc != &unreachable; gc = GC_NEXT(gc)) { @@ -1392,89 +1499,99 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) } /* Clear weakrefs and invoke callbacks as necessary. */ - m += handle_weakrefs(&unreachable, old); - - validate_list(old, collecting_clear_unreachable_clear); + stats->collected += handle_weakrefs(&unreachable, to); + validate_list(to, collecting_clear_unreachable_clear); validate_list(&unreachable, collecting_set_unreachable_clear); /* Call tp_finalize on objects which have one. */ finalize_garbage(tstate, &unreachable); - /* Handle any objects that may have resurrected after the call * to 'finalize_garbage' and continue the collection with the * objects that are still unreachable */ PyGC_Head final_unreachable; - handle_resurrected_objects(&unreachable, &final_unreachable, old); + gc_list_init(&final_unreachable); + handle_resurrected_objects(&unreachable, &final_unreachable, to); /* Call tp_clear on objects in the final_unreachable set. This will cause * the reference cycles to be broken. It may also cause some objects * in finalizers to be freed. */ - m += gc_list_size(&final_unreachable); - delete_garbage(tstate, gcstate, &final_unreachable, old); + stats->collected += gc_list_size(&final_unreachable); + delete_garbage(tstate, gcstate, &final_unreachable, to); /* Collect statistics on uncollectable objects found and print * debugging information. */ + Py_ssize_t n = 0; for (gc = GC_NEXT(&finalizers); gc != &finalizers; gc = GC_NEXT(gc)) { n++; if (gcstate->debug & _PyGC_DEBUG_UNCOLLECTABLE) debug_cycle("uncollectable", FROM_GC(gc)); } - if (gcstate->debug & _PyGC_DEBUG_STATS) { - double d = _PyTime_AsSecondsDouble(_PyTime_GetPerfCounter() - t1); - PySys_WriteStderr( - "gc: done, %zd unreachable, %zd uncollectable, %.4fs elapsed\n", - n+m, n, d); - } - + stats->uncollectable = n; /* Append instances in the uncollectable set to a Python * reachable list of garbage. The programmer has to deal with * this if they insist on creating this type of structure. */ - handle_legacy_finalizers(tstate, gcstate, &finalizers, old); - validate_list(old, collecting_clear_unreachable_clear); + handle_legacy_finalizers(tstate, gcstate, &finalizers, to); + validate_list(to, collecting_clear_unreachable_clear); +} - /* Clear free list only during the collection of the highest - * generation */ - if (generation == NUM_GENERATIONS-1) { - _PyGC_ClearAllFreeLists(tstate->interp); - } +/* Invoke progress callbacks to notify clients that garbage collection + * is starting or stopping + */ +static void +do_gc_callback(GCState *gcstate, const char *phase, + int generation, struct gc_collection_stats *stats) +{ + assert(!PyErr_Occurred()); - if (_PyErr_Occurred(tstate)) { - if (reason == _Py_GC_REASON_SHUTDOWN) { - _PyErr_Clear(tstate); - } - else { - PyErr_FormatUnraisable("Exception ignored in garbage collection"); + /* The local variable cannot be rebound, check it for sanity */ + assert(PyList_CheckExact(gcstate->callbacks)); + PyObject *info = NULL; + if (PyList_GET_SIZE(gcstate->callbacks) != 0) { + info = Py_BuildValue("{sisnsn}", + "generation", generation, + "collected", stats->collected, + "uncollectable", stats->uncollectable); + if (info == NULL) { + PyErr_FormatUnraisable("Exception ignored on invoking gc callbacks"); + return; } } - /* Update stats */ - struct gc_generation_stats *stats = &gcstate->generation_stats[generation]; - stats->collections++; - stats->collected += m; - stats->uncollectable += n; - - GC_STAT_ADD(generation, objects_collected, m); -#ifdef Py_STATS - if (_Py_stats) { - GC_STAT_ADD(generation, object_visits, - _Py_stats->object_stats.object_visits); - _Py_stats->object_stats.object_visits = 0; + PyObject *phase_obj = PyUnicode_FromString(phase); + if (phase_obj == NULL) { + Py_XDECREF(info); + PyErr_FormatUnraisable("Exception ignored on invoking gc callbacks"); + return; } -#endif - if (PyDTrace_GC_DONE_ENABLED()) { - PyDTrace_GC_DONE(n + m); + PyObject *stack[] = {phase_obj, info}; + for (Py_ssize_t i=0; i<PyList_GET_SIZE(gcstate->callbacks); i++) { + PyObject *r, *cb = PyList_GET_ITEM(gcstate->callbacks, i); + Py_INCREF(cb); /* make sure cb doesn't go away */ + r = PyObject_Vectorcall(cb, stack, 2, NULL); + if (r == NULL) { + PyErr_WriteUnraisable(cb); + } + else { + Py_DECREF(r); + } + Py_DECREF(cb); } + Py_DECREF(phase_obj); + Py_XDECREF(info); + assert(!PyErr_Occurred()); +} - if (reason != _Py_GC_REASON_SHUTDOWN) { - invoke_gc_callback(tstate, "stop", generation, m, n); +static void +invoke_gc_callback(GCState *gcstate, const char *phase, + int generation, struct gc_collection_stats *stats) +{ + if (gcstate->callbacks == NULL) { + return; } - - assert(!_PyErr_Occurred(tstate)); - _Py_atomic_store_int(&gcstate->collecting, 0); - return n + m; + do_gc_callback(gcstate, phase, generation, stats); } static int @@ -1549,7 +1666,7 @@ _PyGC_GetObjects(PyInterpreterState *interp, Py_ssize_t generation) } } else { - if (append_objects(result, GEN_HEAD(gcstate, generation))) { + if (append_objects(result, GEN_HEAD(gcstate, (int)generation))) { goto error; } } @@ -1564,10 +1681,16 @@ void _PyGC_Freeze(PyInterpreterState *interp) { GCState *gcstate = &interp->gc; - for (int i = 0; i < NUM_GENERATIONS; ++i) { - gc_list_merge(GEN_HEAD(gcstate, i), &gcstate->permanent_generation.head); - gcstate->generations[i].count = 0; - } + gc_list_merge(&gcstate->young.head, &gcstate->permanent_generation.head); + gcstate->young.count = 0; + PyGC_Head*old0 = &gcstate->old[0].head; + PyGC_Head*old1 = &gcstate->old[1].head; + gc_list_merge(old0, &gcstate->permanent_generation.head); + gcstate->old[0].count = 0; + gc_list_set_space(old1, 0); + gc_list_merge(old1, &gcstate->permanent_generation.head); + gcstate->old[1].count = 0; + validate_old(gcstate); } void @@ -1575,7 +1698,8 @@ _PyGC_Unfreeze(PyInterpreterState *interp) { GCState *gcstate = &interp->gc; gc_list_merge(&gcstate->permanent_generation.head, - GEN_HEAD(gcstate, NUM_GENERATIONS-1)); + &gcstate->old[0].head); + validate_old(gcstate); } Py_ssize_t @@ -1611,32 +1735,100 @@ PyGC_IsEnabled(void) return gcstate->enabled; } -/* Public API to invoke gc.collect() from C */ +// Show stats for objects in each generations +static void +show_stats_each_generations(GCState *gcstate) +{ + char buf[100]; + size_t pos = 0; + + for (int i = 0; i < NUM_GENERATIONS && pos < sizeof(buf); i++) { + pos += PyOS_snprintf(buf+pos, sizeof(buf)-pos, + " %zd", + gc_list_size(GEN_HEAD(gcstate, i))); + } + + PySys_FormatStderr( + "gc: objects in each generation:%s\n" + "gc: objects in permanent generation: %zd\n", + buf, gc_list_size(&gcstate->permanent_generation.head)); +} + Py_ssize_t -PyGC_Collect(void) +_PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason) { - PyThreadState *tstate = _PyThreadState_GET(); GCState *gcstate = &tstate->interp->gc; - if (!gcstate->enabled) { + int expected = 0; + if (!_Py_atomic_compare_exchange_int(&gcstate->collecting, &expected, 1)) { + // Don't start a garbage collection if one is already in progress. return 0; } - Py_ssize_t n; + struct gc_collection_stats stats = { 0 }; + if (reason != _Py_GC_REASON_SHUTDOWN) { + invoke_gc_callback(gcstate, "start", generation, &stats); + } + _PyTime_t t1 = 0; /* initialize to prevent a compiler warning */ + if (gcstate->debug & _PyGC_DEBUG_STATS) { + PySys_WriteStderr("gc: collecting generation %d...\n", generation); + show_stats_each_generations(gcstate); + t1 = _PyTime_GetPerfCounter(); + } + if (PyDTrace_GC_START_ENABLED()) { + PyDTrace_GC_START(generation); + } + GC_STAT_ADD(generation, collections, 1); PyObject *exc = _PyErr_GetRaisedException(tstate); - n = gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_MANUAL); + switch(generation) { + case 0: + gc_collect_young(tstate, &stats); + break; + case 1: + gc_collect_young(tstate, &stats); + gc_collect_increment(tstate, &stats); + break; + case 2: + gc_collect_full(tstate, &stats); + break; + default: + Py_UNREACHABLE(); + } + if (PyDTrace_GC_DONE_ENABLED()) { + PyDTrace_GC_DONE(stats.uncollectable + stats.collected); + } + if (reason != _Py_GC_REASON_SHUTDOWN) { + invoke_gc_callback(gcstate, "stop", generation, &stats); + } _PyErr_SetRaisedException(tstate, exc); + GC_STAT_ADD(generation, objects_collected, stats.collected); +#ifdef Py_STATS + if (_Py_stats) { + GC_STAT_ADD(generation, object_visits, + _Py_stats->object_stats.object_visits); + _Py_stats->object_stats.object_visits = 0; + } +#endif + validate_old(gcstate); + if (gcstate->debug & _PyGC_DEBUG_STATS) { + double d = _PyTime_AsSecondsDouble(_PyTime_GetPerfCounter() - t1); + PySys_WriteStderr( + "gc: done, %zd collected, %zd uncollectable, %.4fs elapsed\n", + stats.collected, stats.uncollectable, d); + } - return n; + _Py_atomic_store_int(&gcstate->collecting, 0); + return stats.uncollectable + stats.collected; } +/* Public API to invoke gc.collect() from C */ Py_ssize_t -_PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason) +PyGC_Collect(void) { - return gc_collect_main(tstate, generation, reason); + return _PyGC_Collect(_PyThreadState_GET(), 2, _Py_GC_REASON_MANUAL); } -Py_ssize_t +void _PyGC_CollectNoFail(PyThreadState *tstate) { /* Ideally, this function is only called on interpreter shutdown, @@ -1645,7 +1837,7 @@ _PyGC_CollectNoFail(PyThreadState *tstate) during interpreter shutdown (and then never finish it). See http://bugs.python.org/issue8713#msg195178 for an example. */ - return gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_SHUTDOWN); + _PyGC_Collect(_PyThreadState_GET(), 2, _Py_GC_REASON_SHUTDOWN); } void @@ -1780,10 +1972,10 @@ _PyObject_GC_Link(PyObject *op) GCState *gcstate = &tstate->interp->gc; g->_gc_next = 0; g->_gc_prev = 0; - gcstate->generations[0].count++; /* number of allocated GC objects */ - if (gcstate->generations[0].count > gcstate->generations[0].threshold && + gcstate->young.count++; /* number of allocated GC objects */ + if (gcstate->young.count > gcstate->young.threshold && gcstate->enabled && - gcstate->generations[0].threshold && + gcstate->young.threshold && !_Py_atomic_load_int_relaxed(&gcstate->collecting) && !_PyErr_Occurred(tstate)) { @@ -1794,7 +1986,9 @@ _PyObject_GC_Link(PyObject *op) void _Py_RunGC(PyThreadState *tstate) { - gc_collect_main(tstate, GENERATION_AUTO, _Py_GC_REASON_HEAP); + if (tstate->interp->gc.enabled) { + _PyGC_Collect(tstate, 1, _Py_GC_REASON_HEAP); + } } static PyObject * @@ -1897,8 +2091,8 @@ PyObject_GC_Del(void *op) #endif } GCState *gcstate = get_gc_state(); - if (gcstate->generations[0].count > 0) { - gcstate->generations[0].count--; + if (gcstate->young.count > 0) { + gcstate->young.count--; } PyObject_Free(((char *)op)-presize); } @@ -1921,26 +2115,36 @@ PyObject_GC_IsFinalized(PyObject *obj) return 0; } +static int +visit_generation(gcvisitobjects_t callback, void *arg, struct gc_generation *gen) +{ + PyGC_Head *gc_list, *gc; + gc_list = &gen->head; + for (gc = GC_NEXT(gc_list); gc != gc_list; gc = GC_NEXT(gc)) { + PyObject *op = FROM_GC(gc); + Py_INCREF(op); + int res = callback(op, arg); + Py_DECREF(op); + if (!res) { + return -1; + } + } + return 0; +} + void PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void *arg) { - size_t i; GCState *gcstate = get_gc_state(); int origenstate = gcstate->enabled; gcstate->enabled = 0; - for (i = 0; i < NUM_GENERATIONS; i++) { - PyGC_Head *gc_list, *gc; - gc_list = GEN_HEAD(gcstate, i); - for (gc = GC_NEXT(gc_list); gc != gc_list; gc = GC_NEXT(gc)) { - PyObject *op = FROM_GC(gc); - Py_INCREF(op); - int res = callback(op, arg); - Py_DECREF(op); - if (!res) { - goto done; - } - } + if (visit_generation(callback, arg, &gcstate->young)) { + goto done; + } + if (visit_generation(callback, arg, &gcstate->old[0])) { + goto done; } + visit_generation(callback, arg, &gcstate->old[1]); done: gcstate->enabled = origenstate; } diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 8fbcdb15109b76c..1c4da726866e4e1 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -616,7 +616,7 @@ void _PyGC_InitState(GCState *gcstate) { // TODO: move to pycore_runtime_init.h once the incremental GC lands. - gcstate->generations[0].threshold = 2000; + gcstate->young.threshold = 2000; } @@ -911,8 +911,8 @@ cleanup_worklist(struct worklist *worklist) static bool gc_should_collect(GCState *gcstate) { - int count = _Py_atomic_load_int_relaxed(&gcstate->generations[0].count); - int threshold = gcstate->generations[0].threshold; + int count = _Py_atomic_load_int_relaxed(&gcstate->young.count); + int threshold = gcstate->young.threshold; if (count <= threshold || threshold == 0 || !gcstate->enabled) { return false; } @@ -920,7 +920,7 @@ gc_should_collect(GCState *gcstate) // objects. A few tests rely on immediate scheduling of the GC so we ignore // the scaled threshold if generations[1].threshold is set to zero. return (count > gcstate->long_lived_total / 4 || - gcstate->generations[1].threshold == 0); + gcstate->old[0].threshold == 0); } static void @@ -1031,10 +1031,15 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) /* update collection and allocation counters */ if (generation+1 < NUM_GENERATIONS) { - gcstate->generations[generation+1].count += 1; + gcstate->old[generation].count += 1; } for (i = 0; i <= generation; i++) { - gcstate->generations[i].count = 0; + if (i == 0) { + gcstate->young.count = 0; + } + else { + gcstate->old[i-1].count = 0; + } } PyInterpreterState *interp = tstate->interp; @@ -1357,7 +1362,7 @@ _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason) return gc_collect_main(tstate, generation, reason); } -Py_ssize_t +void _PyGC_CollectNoFail(PyThreadState *tstate) { /* Ideally, this function is only called on interpreter shutdown, @@ -1366,7 +1371,7 @@ _PyGC_CollectNoFail(PyThreadState *tstate) during interpreter shutdown (and then never finish it). See http://bugs.python.org/issue8713#msg195178 for an example. */ - return gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_SHUTDOWN); + gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_SHUTDOWN); } void @@ -1490,7 +1495,7 @@ _PyObject_GC_Link(PyObject *op) { PyThreadState *tstate = _PyThreadState_GET(); GCState *gcstate = &tstate->interp->gc; - gcstate->generations[0].count++; + gcstate->young.count++; if (gc_should_collect(gcstate) && !_Py_atomic_load_int_relaxed(&gcstate->collecting)) @@ -1605,8 +1610,8 @@ PyObject_GC_Del(void *op) #endif } GCState *gcstate = get_gc_state(); - if (gcstate->generations[0].count > 0) { - gcstate->generations[0].count--; + if (gcstate->young.count > 0) { + gcstate->young.count--; } PyObject_Free(((char *)op)-presize); } diff --git a/Python/import.c b/Python/import.c index 2fd0c08a6bb5aec..dfc5ec1f2f29272 100644 --- a/Python/import.c +++ b/Python/import.c @@ -1030,7 +1030,7 @@ _extensions_cache_set(PyObject *filename, PyObject *name, PyModuleDef *def) if (!already_set) { /* We assume that all module defs are statically allocated and will never be freed. Otherwise, we would incref here. */ - _Py_SetImmortal(def); + _Py_SetImmortal((PyObject *)def); } res = 0; diff --git a/Tools/gdb/libpython.py b/Tools/gdb/libpython.py index 483f28b46dfec74..96b891481d9f462 100755 --- a/Tools/gdb/libpython.py +++ b/Tools/gdb/libpython.py @@ -1753,8 +1753,11 @@ def is_waiting_for_gil(self): return (name == 'take_gil') def is_gc_collect(self): - '''Is this frame gc_collect_main() within the garbage-collector?''' - return self._gdbframe.name() in ('collect', 'gc_collect_main') + '''Is this frame a collector within the garbage-collector?''' + return self._gdbframe.name() in ( + 'collect', 'gc_collect_full', 'gc_collect_main', + 'gc_collect_young', 'gc_collect_increment' + ) def get_pyop(self): try: From bcccf1fb63870c1b7f8abe246e27b7fff343abd7 Mon Sep 17 00:00:00 2001 From: Dino Viehland <dinoviehland@meta.com> Date: Mon, 5 Feb 2024 10:35:59 -0800 Subject: [PATCH 141/507] gh-112075: Add gc shared bits (#114931) Add GC shared flags for objects to the GC bit states in free-threaded builds --- Include/internal/pycore_gc.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h index d2f5c69b45ee399..aeb07238fc83455 100644 --- a/Include/internal/pycore_gc.h +++ b/Include/internal/pycore_gc.h @@ -43,6 +43,7 @@ static inline PyObject* _Py_FROM_GC(PyGC_Head *gc) { # define _PyGC_BITS_FINALIZED (2) # define _PyGC_BITS_UNREACHABLE (4) # define _PyGC_BITS_FROZEN (8) +# define _PyGC_BITS_SHARED (16) #endif /* True if the object is currently tracked by the GC. */ @@ -68,6 +69,22 @@ static inline int _PyObject_GC_MAY_BE_TRACKED(PyObject *obj) { return 1; } +#ifdef Py_GIL_DISABLED + +/* True if an object is shared between multiple threads and + * needs special purpose when freeing to do the possibility + * of in-flight lock-free reads occuring */ +static inline int _PyObject_GC_IS_SHARED(PyObject *op) { + return (op->ob_gc_bits & _PyGC_BITS_SHARED) != 0; +} +#define _PyObject_GC_IS_SHARED(op) _PyObject_GC_IS_SHARED(_Py_CAST(PyObject*, op)) + +static inline void _PyObject_GC_SET_SHARED(PyObject *op) { + op->ob_gc_bits |= _PyGC_BITS_SHARED; +} +#define _PyObject_GC_SET_SHARED(op) _PyObject_GC_SET_SHARED(_Py_CAST(PyObject*, op)) + +#endif /* Bit flags for _gc_prev */ /* Bit 0 is set when tp_finalize is called */ From 750489cc774df44daa2c0d23e8a404fe62be93d1 Mon Sep 17 00:00:00 2001 From: HarryLHW <123lhw321@gmail.com> Date: Tue, 6 Feb 2024 04:22:57 +0800 Subject: [PATCH 142/507] gh-114967: Fix "Built-in Exceptions" documentation ambiguous wording (#114968) Change the somewhat vague "listed below" to "listed in this chapter" in Doc/library/exceptions.rst. The exceptions are listed in multiple sections after two intermediate sections. --------- Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu> --- Doc/library/exceptions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index f821776c2861331..3191315049ad5a4 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -16,7 +16,7 @@ equivalent, even if they have the same name. .. index:: pair: statement; raise -The built-in exceptions listed below can be generated by the interpreter or +The built-in exceptions listed in this chapter can be generated by the interpreter or built-in functions. Except where mentioned, they have an "associated value" indicating the detailed cause of the error. This may be a string or a tuple of several items of information (e.g., an error code and a string explaining the From 4aa4f0906df9fc9c6c6f6657f2c521468c6b1688 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Mon, 5 Feb 2024 22:42:43 +0200 Subject: [PATCH 143/507] gh-109475: Fix support of explicit option value "--" in argparse (GH-114814) For example "--option=--". --- Lib/argparse.py | 2 +- Lib/test/test_argparse.py | 16 ++++++++++++++++ ...024-01-31-20-07-11.gh-issue-109475.lmTb9S.rst | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-31-20-07-11.gh-issue-109475.lmTb9S.rst diff --git a/Lib/argparse.py b/Lib/argparse.py index 9e19f39fadd87bc..2131d729746d41e 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -2485,7 +2485,7 @@ def parse_known_intermixed_args(self, args=None, namespace=None): # ======================== def _get_values(self, action, arg_strings): # for everything but PARSER, REMAINDER args, strip out first '--' - if action.nargs not in [PARSER, REMAINDER]: + if not action.option_strings and action.nargs not in [PARSER, REMAINDER]: try: arg_strings.remove('--') except ValueError: diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 940d7e95f96e20a..d1f3d40000140d3 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -5405,6 +5405,22 @@ def test_zero_or_more_optional(self): args = parser.parse_args([]) self.assertEqual(NS(x=[]), args) + def test_double_dash(self): + parser = argparse.ArgumentParser() + parser.add_argument('-f', '--foo', nargs='*') + parser.add_argument('bar', nargs='*') + + args = parser.parse_args(['--foo=--']) + self.assertEqual(NS(foo=['--'], bar=[]), args) + args = parser.parse_args(['--foo', '--']) + self.assertEqual(NS(foo=[], bar=[]), args) + args = parser.parse_args(['-f--']) + self.assertEqual(NS(foo=['--'], bar=[]), args) + args = parser.parse_args(['-f', '--']) + self.assertEqual(NS(foo=[], bar=[]), args) + args = parser.parse_args(['--foo', 'a', 'b', '--', 'c', 'd']) + self.assertEqual(NS(foo=['a', 'b'], bar=['c', 'd']), args) + # =========================== # parse_intermixed_args tests diff --git a/Misc/NEWS.d/next/Library/2024-01-31-20-07-11.gh-issue-109475.lmTb9S.rst b/Misc/NEWS.d/next/Library/2024-01-31-20-07-11.gh-issue-109475.lmTb9S.rst new file mode 100644 index 000000000000000..7582cb2bcd76298 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-31-20-07-11.gh-issue-109475.lmTb9S.rst @@ -0,0 +1,2 @@ +Fix support of explicit option value "--" in :mod:`argparse` (e.g. +``--option=--``). From 09096a1647913526a3d4fa69a9d2056ec82a8f37 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Mon, 5 Feb 2024 21:49:17 +0100 Subject: [PATCH 144/507] gh-115015: Argument Clinic: fix generated code for METH_METHOD methods without params (#115016) --- Lib/test/clinic.test.c | 4 +- Lib/test/test_clinic.py | 20 ++++++++++ ...-02-05-02-45-51.gh-issue-115015.rgtiDB.rst | 5 +++ Modules/_io/clinic/bufferedio.c.h | 4 +- Modules/_io/clinic/bytesio.c.h | 4 +- Modules/_io/clinic/fileio.c.h | 4 +- Modules/_io/clinic/iobase.c.h | 4 +- Modules/_io/clinic/textio.c.h | 4 +- Modules/_io/clinic/winconsoleio.c.h | 4 +- Modules/_sre/clinic/sre.c.h | 6 +-- Modules/_testclinic.c | 37 +++++++++++++++++++ Modules/cjkcodecs/clinic/multibytecodec.c.h | 4 +- Modules/clinic/_asynciomodule.c.h | 6 +-- Modules/clinic/_curses_panel.c.h | 12 +++--- Modules/clinic/_dbmmodule.c.h | 6 +-- Modules/clinic/_elementtree.c.h | 6 +-- Modules/clinic/_gdbmmodule.c.h | 12 +++--- Modules/clinic/_lsprof.c.h | 4 +- Modules/clinic/_pickle.c.h | 4 +- Modules/clinic/_queuemodule.c.h | 4 +- Modules/clinic/_testclinic.c.h | 24 +++++++++++- Modules/clinic/_testmultiphase.c.h | 8 ++-- Modules/clinic/arraymodule.c.h | 4 +- Modules/clinic/md5module.c.h | 4 +- Modules/clinic/posixmodule.c.h | 4 +- Modules/clinic/sha1module.c.h | 4 +- Modules/clinic/sha2module.c.h | 6 +-- Modules/clinic/zlibmodule.c.h | 10 ++--- Tools/c-analyzer/cpython/globals-to-fix.tsv | 1 + Tools/clinic/clinic.py | 2 +- 30 files changed, 153 insertions(+), 68 deletions(-) create mode 100644 Misc/NEWS.d/next/Tools-Demos/2024-02-05-02-45-51.gh-issue-115015.rgtiDB.rst diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index b15aeb898d35a12..168f6f73f6186fa 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -4625,7 +4625,7 @@ Test_cls_no_params_impl(TestObj *self, PyTypeObject *cls); static PyObject * Test_cls_no_params(TestObj *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "cls_no_params() takes no arguments"); return NULL; } @@ -4634,7 +4634,7 @@ Test_cls_no_params(TestObj *self, PyTypeObject *cls, PyObject *const *args, Py_s static PyObject * Test_cls_no_params_impl(TestObj *self, PyTypeObject *cls) -/*[clinic end generated code: output=cc8845f22cff3dcb input=e7e2e4e344e96a11]*/ +/*[clinic end generated code: output=4d68b4652c144af3 input=e7e2e4e344e96a11]*/ /*[clinic input] diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 7323bdd801f4be0..e987ce546054979 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -3288,6 +3288,26 @@ def test_cloned_func_with_converter_exception_message(self): func = getattr(ac_tester, name) self.assertEqual(func(), name) + def test_meth_method_no_params(self): + obj = ac_tester.TestClass() + meth = obj.meth_method_no_params + check = partial(self.assertRaisesRegex, TypeError, "no arguments") + check(meth, 1) + check(meth, a=1) + + def test_meth_method_no_params_capi(self): + from _testcapi import pyobject_vectorcall + obj = ac_tester.TestClass() + meth = obj.meth_method_no_params + pyobject_vectorcall(meth, None, None) + pyobject_vectorcall(meth, (), None) + pyobject_vectorcall(meth, (), ()) + pyobject_vectorcall(meth, None, ()) + + check = partial(self.assertRaisesRegex, TypeError, "no arguments") + check(pyobject_vectorcall, meth, (1,), None) + check(pyobject_vectorcall, meth, (1,), ("a",)) + def test_depr_star_new(self): cls = ac_tester.DeprStarNew cls() diff --git a/Misc/NEWS.d/next/Tools-Demos/2024-02-05-02-45-51.gh-issue-115015.rgtiDB.rst b/Misc/NEWS.d/next/Tools-Demos/2024-02-05-02-45-51.gh-issue-115015.rgtiDB.rst new file mode 100644 index 000000000000000..d8739d28eb2b73c --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2024-02-05-02-45-51.gh-issue-115015.rgtiDB.rst @@ -0,0 +1,5 @@ +Fix a bug in Argument Clinic that generated incorrect code for methods with +no parameters that use the :ref:`METH_METHOD | METH_FASTCALL | METH_KEYWORDS +<METH_METHOD-METH_FASTCALL-METH_KEYWORDS>` calling convention. Only the +positional parameter count was checked; any keyword argument passed would be +silently accepted. diff --git a/Modules/_io/clinic/bufferedio.c.h b/Modules/_io/clinic/bufferedio.c.h index d5bec5f71f5be88..64eddcd314a803e 100644 --- a/Modules/_io/clinic/bufferedio.c.h +++ b/Modules/_io/clinic/bufferedio.c.h @@ -96,7 +96,7 @@ _io__BufferedIOBase_detach_impl(PyObject *self, PyTypeObject *cls); static PyObject * _io__BufferedIOBase_detach(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "detach() takes no arguments"); return NULL; } @@ -1245,4 +1245,4 @@ _io_BufferedRandom___init__(PyObject *self, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=442b05b9a117df6c input=a9049054013a1b77]*/ +/*[clinic end generated code: output=4249187a725a3b3e input=a9049054013a1b77]*/ diff --git a/Modules/_io/clinic/bytesio.c.h b/Modules/_io/clinic/bytesio.c.h index 37023e49087647e..620e9e3b84ea192 100644 --- a/Modules/_io/clinic/bytesio.c.h +++ b/Modules/_io/clinic/bytesio.c.h @@ -96,7 +96,7 @@ _io_BytesIO_getbuffer_impl(bytesio *self, PyTypeObject *cls); static PyObject * _io_BytesIO_getbuffer(bytesio *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "getbuffer() takes no arguments"); return NULL; } @@ -534,4 +534,4 @@ _io_BytesIO___init__(PyObject *self, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=2be0e05a8871b7e2 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=ef116925b8b9e535 input=a9049054013a1b77]*/ diff --git a/Modules/_io/clinic/fileio.c.h b/Modules/_io/clinic/fileio.c.h index cf3ba28b066cf7f..5b5487d63eba900 100644 --- a/Modules/_io/clinic/fileio.c.h +++ b/Modules/_io/clinic/fileio.c.h @@ -27,7 +27,7 @@ _io_FileIO_close_impl(fileio *self, PyTypeObject *cls); static PyObject * _io_FileIO_close(fileio *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "close() takes no arguments"); return NULL; } @@ -528,4 +528,4 @@ _io_FileIO_isatty(fileio *self, PyObject *Py_UNUSED(ignored)) #ifndef _IO_FILEIO_TRUNCATE_METHODDEF #define _IO_FILEIO_TRUNCATE_METHODDEF #endif /* !defined(_IO_FILEIO_TRUNCATE_METHODDEF) */ -/*[clinic end generated code: output=1c0f4a36f76b0c6a input=a9049054013a1b77]*/ +/*[clinic end generated code: output=e3d9446b4087020e input=a9049054013a1b77]*/ diff --git a/Modules/_io/clinic/iobase.c.h b/Modules/_io/clinic/iobase.c.h index 6bdfa1444015acc..bae80a265fab075 100644 --- a/Modules/_io/clinic/iobase.c.h +++ b/Modules/_io/clinic/iobase.c.h @@ -262,7 +262,7 @@ _io__IOBase_fileno_impl(PyObject *self, PyTypeObject *cls); static PyObject * _io__IOBase_fileno(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "fileno() takes no arguments"); return NULL; } @@ -438,4 +438,4 @@ _io__RawIOBase_readall(PyObject *self, PyObject *Py_UNUSED(ignored)) { return _io__RawIOBase_readall_impl(self); } -/*[clinic end generated code: output=5a22bc5db0ecaacb input=a9049054013a1b77]*/ +/*[clinic end generated code: output=e7326fbefc52bfba input=a9049054013a1b77]*/ diff --git a/Modules/_io/clinic/textio.c.h b/Modules/_io/clinic/textio.c.h index 23b3cc8d71e098c..f04ee729abc9edc 100644 --- a/Modules/_io/clinic/textio.c.h +++ b/Modules/_io/clinic/textio.c.h @@ -27,7 +27,7 @@ _io__TextIOBase_detach_impl(PyObject *self, PyTypeObject *cls); static PyObject * _io__TextIOBase_detach(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "detach() takes no arguments"); return NULL; } @@ -1292,4 +1292,4 @@ _io_TextIOWrapper__CHUNK_SIZE_set(textio *self, PyObject *value, void *Py_UNUSED return return_value; } -/*[clinic end generated code: output=d01aa598647c1385 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=93a5a91a22100a28 input=a9049054013a1b77]*/ diff --git a/Modules/_io/clinic/winconsoleio.c.h b/Modules/_io/clinic/winconsoleio.c.h index 6cab295c44611d4..4696ecc5c843e6e 100644 --- a/Modules/_io/clinic/winconsoleio.c.h +++ b/Modules/_io/clinic/winconsoleio.c.h @@ -29,7 +29,7 @@ _io__WindowsConsoleIO_close_impl(winconsoleio *self, PyTypeObject *cls); static PyObject * _io__WindowsConsoleIO_close(winconsoleio *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "close() takes no arguments"); return NULL; } @@ -457,4 +457,4 @@ _io__WindowsConsoleIO_isatty(winconsoleio *self, PyObject *Py_UNUSED(ignored)) #ifndef _IO__WINDOWSCONSOLEIO_ISATTY_METHODDEF #define _IO__WINDOWSCONSOLEIO_ISATTY_METHODDEF #endif /* !defined(_IO__WINDOWSCONSOLEIO_ISATTY_METHODDEF) */ -/*[clinic end generated code: output=04108fc26b187386 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=2c2bc86713b21dd6 input=a9049054013a1b77]*/ diff --git a/Modules/_sre/clinic/sre.c.h b/Modules/_sre/clinic/sre.c.h index cd3fbbc720bdf1e..48336c7a2fca262 100644 --- a/Modules/_sre/clinic/sre.c.h +++ b/Modules/_sre/clinic/sre.c.h @@ -1434,7 +1434,7 @@ _sre_SRE_Scanner_match_impl(ScannerObject *self, PyTypeObject *cls); static PyObject * _sre_SRE_Scanner_match(ScannerObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "match() takes no arguments"); return NULL; } @@ -1455,10 +1455,10 @@ _sre_SRE_Scanner_search_impl(ScannerObject *self, PyTypeObject *cls); static PyObject * _sre_SRE_Scanner_search(ScannerObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "search() takes no arguments"); return NULL; } return _sre_SRE_Scanner_search_impl(self, cls); } -/*[clinic end generated code: output=ad513f31b99505fa input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c3e711f0b2f43d66 input=a9049054013a1b77]*/ diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c index 15e0093f15ba1e6..fb0936bbccd3183 100644 --- a/Modules/_testclinic.c +++ b/Modules/_testclinic.c @@ -1213,6 +1213,40 @@ clone_with_conv_f2_impl(PyObject *module, custom_t path) } +/*[clinic input] +class _testclinic.TestClass "PyObject *" "PyObject" +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=668a591c65bec947]*/ + +/*[clinic input] +_testclinic.TestClass.meth_method_no_params + cls: defining_class + / +[clinic start generated code]*/ + +static PyObject * +_testclinic_TestClass_meth_method_no_params_impl(PyObject *self, + PyTypeObject *cls) +/*[clinic end generated code: output=c140f100080c2fc8 input=6bd34503d11c63c1]*/ +{ + Py_RETURN_NONE; +} + +static struct PyMethodDef test_class_methods[] = { + _TESTCLINIC_TESTCLASS_METH_METHOD_NO_PARAMS_METHODDEF + {NULL, NULL} +}; + +static PyTypeObject TestClass = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "_testclinic.TestClass", + .tp_basicsize = sizeof(PyObject), + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = PyType_GenericNew, + .tp_methods = test_class_methods, +}; + + /*[clinic input] output push destination deprstar new file '{dirname}/clinic/_testclinic_depr.c.h' @@ -1906,6 +1940,9 @@ PyInit__testclinic(void) if (m == NULL) { return NULL; } + if (PyModule_AddType(m, &TestClass) < 0) { + goto error; + } if (PyModule_AddType(m, &DeprStarNew) < 0) { goto error; } diff --git a/Modules/cjkcodecs/clinic/multibytecodec.c.h b/Modules/cjkcodecs/clinic/multibytecodec.c.h index 305ade17b1f1aa8..b5639d5cf10a22a 100644 --- a/Modules/cjkcodecs/clinic/multibytecodec.c.h +++ b/Modules/cjkcodecs/clinic/multibytecodec.c.h @@ -668,7 +668,7 @@ _multibytecodec_MultibyteStreamWriter_reset_impl(MultibyteStreamWriterObject *se static PyObject * _multibytecodec_MultibyteStreamWriter_reset(MultibyteStreamWriterObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "reset() takes no arguments"); return NULL; } @@ -682,4 +682,4 @@ PyDoc_STRVAR(_multibytecodec___create_codec__doc__, #define _MULTIBYTECODEC___CREATE_CODEC_METHODDEF \ {"__create_codec", (PyCFunction)_multibytecodec___create_codec, METH_O, _multibytecodec___create_codec__doc__}, -/*[clinic end generated code: output=219a363662d2fbff input=a9049054013a1b77]*/ +/*[clinic end generated code: output=ee767a6d93c7108a input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_asynciomodule.c.h b/Modules/clinic/_asynciomodule.c.h index d941c280a4300ba..6a9c8ff6d8fdd9e 100644 --- a/Modules/clinic/_asynciomodule.c.h +++ b/Modules/clinic/_asynciomodule.c.h @@ -120,7 +120,7 @@ _asyncio_Future_exception_impl(FutureObj *self, PyTypeObject *cls); static PyObject * _asyncio_Future_exception(FutureObj *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "exception() takes no arguments"); return NULL; } @@ -453,7 +453,7 @@ _asyncio_Future_get_loop_impl(FutureObj *self, PyTypeObject *cls); static PyObject * _asyncio_Future_get_loop(FutureObj *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "get_loop() takes no arguments"); return NULL; } @@ -1487,4 +1487,4 @@ _asyncio_current_task(PyObject *module, PyObject *const *args, Py_ssize_t nargs, exit: return return_value; } -/*[clinic end generated code: output=f3864d8e2af7635f input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b26155080c82c472 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_curses_panel.c.h b/Modules/clinic/_curses_panel.c.h index 7945d93b5433f7d..457f71370afda9f 100644 --- a/Modules/clinic/_curses_panel.c.h +++ b/Modules/clinic/_curses_panel.c.h @@ -19,7 +19,7 @@ _curses_panel_panel_bottom_impl(PyCursesPanelObject *self, PyTypeObject *cls); static PyObject * _curses_panel_panel_bottom(PyCursesPanelObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "bottom() takes no arguments"); return NULL; } @@ -43,7 +43,7 @@ _curses_panel_panel_hide_impl(PyCursesPanelObject *self, PyTypeObject *cls); static PyObject * _curses_panel_panel_hide(PyCursesPanelObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "hide() takes no arguments"); return NULL; } @@ -65,7 +65,7 @@ _curses_panel_panel_show_impl(PyCursesPanelObject *self, PyTypeObject *cls); static PyObject * _curses_panel_panel_show(PyCursesPanelObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "show() takes no arguments"); return NULL; } @@ -87,7 +87,7 @@ _curses_panel_panel_top_impl(PyCursesPanelObject *self, PyTypeObject *cls); static PyObject * _curses_panel_panel_top(PyCursesPanelObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "top() takes no arguments"); return NULL; } @@ -327,7 +327,7 @@ _curses_panel_panel_userptr_impl(PyCursesPanelObject *self, static PyObject * _curses_panel_panel_userptr(PyCursesPanelObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "userptr() takes no arguments"); return NULL; } @@ -418,4 +418,4 @@ _curses_panel_update_panels(PyObject *module, PyObject *Py_UNUSED(ignored)) { return _curses_panel_update_panels_impl(module); } -/*[clinic end generated code: output=636beecf71d96ff1 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=7bac14e9a1194c87 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_dbmmodule.c.h b/Modules/clinic/_dbmmodule.c.h index 5a4aba2825e03a6..d06271e18a49b25 100644 --- a/Modules/clinic/_dbmmodule.c.h +++ b/Modules/clinic/_dbmmodule.c.h @@ -37,7 +37,7 @@ _dbm_dbm_keys_impl(dbmobject *self, PyTypeObject *cls); static PyObject * _dbm_dbm_keys(dbmobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "keys() takes no arguments"); return NULL; } @@ -149,7 +149,7 @@ _dbm_dbm_clear_impl(dbmobject *self, PyTypeObject *cls); static PyObject * _dbm_dbm_clear(dbmobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "clear() takes no arguments"); return NULL; } @@ -218,4 +218,4 @@ dbmopen(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=96fdd4bd7bd256c5 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=743ce0cea116747e input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_elementtree.c.h b/Modules/clinic/_elementtree.c.h index 02375c8a61e73e4..9622591a1aa8552 100644 --- a/Modules/clinic/_elementtree.c.h +++ b/Modules/clinic/_elementtree.c.h @@ -87,7 +87,7 @@ _elementtree_Element___copy___impl(ElementObject *self, PyTypeObject *cls); static PyObject * _elementtree_Element___copy__(ElementObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "__copy__() takes no arguments"); return NULL; } @@ -644,7 +644,7 @@ _elementtree_Element_itertext_impl(ElementObject *self, PyTypeObject *cls); static PyObject * _elementtree_Element_itertext(ElementObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "itertext() takes no arguments"); return NULL; } @@ -1219,4 +1219,4 @@ _elementtree_XMLParser__setevents(XMLParserObject *self, PyObject *const *args, exit: return return_value; } -/*[clinic end generated code: output=8fdaa17d3262800a input=a9049054013a1b77]*/ +/*[clinic end generated code: output=218ec9e6a889f796 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_gdbmmodule.c.h b/Modules/clinic/_gdbmmodule.c.h index c7164e519d0e7de..626e4678809d4f5 100644 --- a/Modules/clinic/_gdbmmodule.c.h +++ b/Modules/clinic/_gdbmmodule.c.h @@ -106,7 +106,7 @@ _gdbm_gdbm_keys_impl(gdbmobject *self, PyTypeObject *cls); static PyObject * _gdbm_gdbm_keys(gdbmobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "keys() takes no arguments"); return NULL; } @@ -132,7 +132,7 @@ _gdbm_gdbm_firstkey_impl(gdbmobject *self, PyTypeObject *cls); static PyObject * _gdbm_gdbm_firstkey(gdbmobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "firstkey() takes no arguments"); return NULL; } @@ -211,7 +211,7 @@ _gdbm_gdbm_reorganize_impl(gdbmobject *self, PyTypeObject *cls); static PyObject * _gdbm_gdbm_reorganize(gdbmobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "reorganize() takes no arguments"); return NULL; } @@ -236,7 +236,7 @@ _gdbm_gdbm_sync_impl(gdbmobject *self, PyTypeObject *cls); static PyObject * _gdbm_gdbm_sync(gdbmobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "sync() takes no arguments"); return NULL; } @@ -258,7 +258,7 @@ _gdbm_gdbm_clear_impl(gdbmobject *self, PyTypeObject *cls); static PyObject * _gdbm_gdbm_clear(gdbmobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "clear() takes no arguments"); return NULL; } @@ -340,4 +340,4 @@ dbmopen(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=c5ee922363d5a81f input=a9049054013a1b77]*/ +/*[clinic end generated code: output=6b4c19905ac9967d input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_lsprof.c.h b/Modules/clinic/_lsprof.c.h index dfc003eb54774ca..b3b7fda5660bfd5 100644 --- a/Modules/clinic/_lsprof.c.h +++ b/Modules/clinic/_lsprof.c.h @@ -39,10 +39,10 @@ _lsprof_Profiler_getstats_impl(ProfilerObject *self, PyTypeObject *cls); static PyObject * _lsprof_Profiler_getstats(ProfilerObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "getstats() takes no arguments"); return NULL; } return _lsprof_Profiler_getstats_impl(self, cls); } -/*[clinic end generated code: output=0615a53cce828f06 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=5c9d87d89863dc83 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_pickle.c.h b/Modules/clinic/_pickle.c.h index fb086925e3941d6..5a6ae7be6b6ea77 100644 --- a/Modules/clinic/_pickle.c.h +++ b/Modules/clinic/_pickle.c.h @@ -328,7 +328,7 @@ _pickle_Unpickler_load_impl(UnpicklerObject *self, PyTypeObject *cls); static PyObject * _pickle_Unpickler_load(UnpicklerObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "load() takes no arguments"); return NULL; } @@ -1077,4 +1077,4 @@ _pickle_loads(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObjec exit: return return_value; } -/*[clinic end generated code: output=ebe78653233827a6 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=bd63c85a8737b0aa input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_queuemodule.c.h b/Modules/clinic/_queuemodule.c.h index b3b6b8e96c135e4..6f4c715c7229651 100644 --- a/Modules/clinic/_queuemodule.c.h +++ b/Modules/clinic/_queuemodule.c.h @@ -278,7 +278,7 @@ _queue_SimpleQueue_get_nowait(simplequeueobject *self, PyTypeObject *cls, PyObje { PyObject *return_value = NULL; - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "get_nowait() takes no arguments"); goto exit; } @@ -349,4 +349,4 @@ _queue_SimpleQueue_qsize(simplequeueobject *self, PyObject *Py_UNUSED(ignored)) exit: return return_value; } -/*[clinic end generated code: output=242950edc8f7dfd7 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=44a718f40072018a input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_testclinic.c.h b/Modules/clinic/_testclinic.c.h index fea30e778381dea..bb516be37ec3f09 100644 --- a/Modules/clinic/_testclinic.c.h +++ b/Modules/clinic/_testclinic.c.h @@ -3141,4 +3141,26 @@ clone_with_conv_f2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, Py exit: return return_value; } -/*[clinic end generated code: output=90743ac900d60f9f input=a9049054013a1b77]*/ + +PyDoc_STRVAR(_testclinic_TestClass_meth_method_no_params__doc__, +"meth_method_no_params($self, /)\n" +"--\n" +"\n"); + +#define _TESTCLINIC_TESTCLASS_METH_METHOD_NO_PARAMS_METHODDEF \ + {"meth_method_no_params", _PyCFunction_CAST(_testclinic_TestClass_meth_method_no_params), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _testclinic_TestClass_meth_method_no_params__doc__}, + +static PyObject * +_testclinic_TestClass_meth_method_no_params_impl(PyObject *self, + PyTypeObject *cls); + +static PyObject * +_testclinic_TestClass_meth_method_no_params(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "meth_method_no_params() takes no arguments"); + return NULL; + } + return _testclinic_TestClass_meth_method_no_params_impl(self, cls); +} +/*[clinic end generated code: output=6520c1ca5392a3f0 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_testmultiphase.c.h b/Modules/clinic/_testmultiphase.c.h index c0a00954c16cbe2..7ac361ece1acc3e 100644 --- a/Modules/clinic/_testmultiphase.c.h +++ b/Modules/clinic/_testmultiphase.c.h @@ -27,7 +27,7 @@ _testmultiphase_StateAccessType_get_defining_module_impl(StateAccessTypeObject * static PyObject * _testmultiphase_StateAccessType_get_defining_module(StateAccessTypeObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "get_defining_module() takes no arguments"); return NULL; } @@ -50,7 +50,7 @@ _testmultiphase_StateAccessType_getmodulebydef_bad_def_impl(StateAccessTypeObjec static PyObject * _testmultiphase_StateAccessType_getmodulebydef_bad_def(StateAccessTypeObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "getmodulebydef_bad_def() takes no arguments"); return NULL; } @@ -156,10 +156,10 @@ _testmultiphase_StateAccessType_get_count_impl(StateAccessTypeObject *self, static PyObject * _testmultiphase_StateAccessType_get_count(StateAccessTypeObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "get_count() takes no arguments"); return NULL; } return _testmultiphase_StateAccessType_get_count_impl(self, cls); } -/*[clinic end generated code: output=d8c262af27b3b98d input=a9049054013a1b77]*/ +/*[clinic end generated code: output=2c199bad52e9cda7 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/arraymodule.c.h b/Modules/clinic/arraymodule.c.h index dbce03135416499..0b764e43e194375 100644 --- a/Modules/clinic/arraymodule.c.h +++ b/Modules/clinic/arraymodule.c.h @@ -652,7 +652,7 @@ array_arrayiterator___reduce___impl(arrayiterobject *self, PyTypeObject *cls); static PyObject * array_arrayiterator___reduce__(arrayiterobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "__reduce__() takes no arguments"); return NULL; } @@ -667,4 +667,4 @@ PyDoc_STRVAR(array_arrayiterator___setstate____doc__, #define ARRAY_ARRAYITERATOR___SETSTATE___METHODDEF \ {"__setstate__", (PyCFunction)array_arrayiterator___setstate__, METH_O, array_arrayiterator___setstate____doc__}, -/*[clinic end generated code: output=bf086c01e7e482bf input=a9049054013a1b77]*/ +/*[clinic end generated code: output=3be987238a4bb431 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/md5module.c.h b/Modules/clinic/md5module.c.h index 7d4d3108dab9b6c..ee7fb3d7c613f22 100644 --- a/Modules/clinic/md5module.c.h +++ b/Modules/clinic/md5module.c.h @@ -23,7 +23,7 @@ MD5Type_copy_impl(MD5object *self, PyTypeObject *cls); static PyObject * MD5Type_copy(MD5object *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "copy() takes no arguments"); return NULL; } @@ -148,4 +148,4 @@ _md5_md5(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw exit: return return_value; } -/*[clinic end generated code: output=bfadda44914804a8 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=4dbca39332d3f52f input=a9049054013a1b77]*/ diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 1373bdef03ba5e5..b49d64d4281889e 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -11212,7 +11212,7 @@ os_DirEntry_is_symlink(DirEntry *self, PyTypeObject *defining_class, PyObject *c PyObject *return_value = NULL; int _return_value; - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "is_symlink() takes no arguments"); goto exit; } @@ -12588,4 +12588,4 @@ os__supports_virtual_terminal(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #define OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #endif /* !defined(OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF) */ -/*[clinic end generated code: output=43e4e557c771358a input=a9049054013a1b77]*/ +/*[clinic end generated code: output=268af5cbc8baa9d4 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/sha1module.c.h b/Modules/clinic/sha1module.c.h index ee391656fb67c31..b89c7e505c788e9 100644 --- a/Modules/clinic/sha1module.c.h +++ b/Modules/clinic/sha1module.c.h @@ -23,7 +23,7 @@ SHA1Type_copy_impl(SHA1object *self, PyTypeObject *cls); static PyObject * SHA1Type_copy(SHA1object *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "copy() takes no arguments"); return NULL; } @@ -148,4 +148,4 @@ _sha1_sha1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject * exit: return return_value; } -/*[clinic end generated code: output=41fc7579213b57b4 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=af5a640df662066f input=a9049054013a1b77]*/ diff --git a/Modules/clinic/sha2module.c.h b/Modules/clinic/sha2module.c.h index ec31d5545be4c15..cf4b88d52856b86 100644 --- a/Modules/clinic/sha2module.c.h +++ b/Modules/clinic/sha2module.c.h @@ -23,7 +23,7 @@ SHA256Type_copy_impl(SHA256object *self, PyTypeObject *cls); static PyObject * SHA256Type_copy(SHA256object *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "copy() takes no arguments"); return NULL; } @@ -45,7 +45,7 @@ SHA512Type_copy_impl(SHA512object *self, PyTypeObject *cls); static PyObject * SHA512Type_copy(SHA512object *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "copy() takes no arguments"); return NULL; } @@ -437,4 +437,4 @@ _sha2_sha384(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject exit: return return_value; } -/*[clinic end generated code: output=1482d9de086e45c4 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b46da764024b1764 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/zlibmodule.c.h b/Modules/clinic/zlibmodule.c.h index 6b09abe309bf486..7ff3edf5a557f80 100644 --- a/Modules/clinic/zlibmodule.c.h +++ b/Modules/clinic/zlibmodule.c.h @@ -637,7 +637,7 @@ zlib_Compress_copy_impl(compobject *self, PyTypeObject *cls); static PyObject * zlib_Compress_copy(compobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "copy() takes no arguments"); return NULL; } @@ -662,7 +662,7 @@ zlib_Compress___copy___impl(compobject *self, PyTypeObject *cls); static PyObject * zlib_Compress___copy__(compobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "__copy__() takes no arguments"); return NULL; } @@ -735,7 +735,7 @@ zlib_Decompress_copy_impl(compobject *self, PyTypeObject *cls); static PyObject * zlib_Decompress_copy(compobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "copy() takes no arguments"); return NULL; } @@ -760,7 +760,7 @@ zlib_Decompress___copy___impl(compobject *self, PyTypeObject *cls); static PyObject * zlib_Decompress___copy__(compobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "__copy__() takes no arguments"); return NULL; } @@ -1098,4 +1098,4 @@ zlib_crc32(PyObject *module, PyObject *const *args, Py_ssize_t nargs) #ifndef ZLIB_DECOMPRESS___DEEPCOPY___METHODDEF #define ZLIB_DECOMPRESS___DEEPCOPY___METHODDEF #endif /* !defined(ZLIB_DECOMPRESS___DEEPCOPY___METHODDEF) */ -/*[clinic end generated code: output=6dd97dc851c39031 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=8bb840fb6af43dd4 input=a9049054013a1b77]*/ diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 0b02ad01d399836..1d9576d083d8dc7 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -334,6 +334,7 @@ Modules/_testclinic.c - DeprStarNew - Modules/_testclinic.c - DeprKwdInit - Modules/_testclinic.c - DeprKwdInitNoInline - Modules/_testclinic.c - DeprKwdNew - +Modules/_testclinic.c - TestClass - ################################## diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index c1df83a72bd8cea..db57d17899af939 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -960,7 +960,7 @@ def parser_body( return_error = ('return NULL;' if simple_return else 'goto exit;') parser_code = [libclinic.normalize_snippet(""" - if (nargs) {{ + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {{ PyErr_SetString(PyExc_TypeError, "{name}() takes no arguments"); %s }} From 652fbf88c4c422ff17fefd4dcb5e06b5c0e26e74 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Mon, 5 Feb 2024 22:51:11 +0200 Subject: [PATCH 145/507] gh-82626: Emit a warning when bool is used as a file descriptor (GH-111275) --- Doc/whatsnew/3.13.rst | 5 +++++ Lib/_pyio.py | 5 +++++ Lib/test/test_fileio.py | 8 ++++++++ Lib/test/test_genericpath.py | 6 ++++++ Lib/test/test_os.py | 14 ++++++++++++++ Lib/test/test_posix.py | 7 +++++++ .../2023-10-24-19-19-54.gh-issue-82626._hfLRf.rst | 2 ++ Modules/_io/fileio.c | 7 +++++++ Modules/faulthandler.c | 7 +++++++ Modules/posixmodule.c | 7 +++++++ Objects/fileobject.c | 7 +++++++ 11 files changed, 75 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-10-24-19-19-54.gh-issue-82626._hfLRf.rst diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 0770e28d230b4b3..9bac36ba0bffb8c 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -145,6 +145,11 @@ Other Language Changes is rejected when the global is used in the :keyword:`else` block. (Contributed by Irit Katriel in :gh:`111123`.) +* Many functions now emit a warning if a boolean value is passed as + a file descriptor argument. + This can help catch some errors earlier. + (Contributed by Serhiy Storchaka in :gh:`82626`.) + * Added a new environment variable :envvar:`PYTHON_FROZEN_MODULES`. It determines whether or not frozen modules are ignored by the import machinery, equivalent of the :option:`-X frozen_modules <-X>` command-line option. diff --git a/Lib/_pyio.py b/Lib/_pyio.py index df2c29bfa9caeed..8a0d0dc4b1a0b85 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -1495,6 +1495,11 @@ def __init__(self, file, mode='r', closefd=True, opener=None): if isinstance(file, float): raise TypeError('integer argument expected, got float') if isinstance(file, int): + if isinstance(file, bool): + import warnings + warnings.warn("bool is used as a file descriptor", + RuntimeWarning, stacklevel=2) + file = int(file) fd = file if fd < 0: raise ValueError('negative file descriptor') diff --git a/Lib/test/test_fileio.py b/Lib/test/test_fileio.py index 06d9b454add34cd..06d5a8abf320835 100644 --- a/Lib/test/test_fileio.py +++ b/Lib/test/test_fileio.py @@ -484,6 +484,14 @@ def testInvalidFd(self): import msvcrt self.assertRaises(OSError, msvcrt.get_osfhandle, make_bad_fd()) + def testBooleanFd(self): + for fd in False, True: + with self.assertWarnsRegex(RuntimeWarning, + 'bool is used as a file descriptor') as cm: + f = self.FileIO(fd, closefd=False) + f.close() + self.assertEqual(cm.filename, __file__) + def testBadModeArgument(self): # verify that we get a sensible error message for bad mode argument bad_mode = "qwerty" diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py index b77cd4c67d6b2ae..f407ee3caf154c2 100644 --- a/Lib/test/test_genericpath.py +++ b/Lib/test/test_genericpath.py @@ -165,6 +165,12 @@ def test_exists_fd(self): os.close(w) self.assertFalse(self.pathmodule.exists(r)) + def test_exists_bool(self): + for fd in False, True: + with self.assertWarnsRegex(RuntimeWarning, + 'bool is used as a file descriptor'): + self.pathmodule.exists(fd) + def test_isdir(self): filename = os_helper.TESTFN bfilename = os.fsencode(filename) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 86af1a8ed8ee154..2c8823ae47c726e 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -2195,12 +2195,15 @@ def test_chmod(self): class TestInvalidFD(unittest.TestCase): singles = ["fchdir", "dup", "fdatasync", "fstat", "fstatvfs", "fsync", "tcgetpgrp", "ttyname"] + singles_fildes = {"fchdir", "fdatasync", "fsync"} #singles.append("close") #We omit close because it doesn't raise an exception on some platforms def get_single(f): def helper(self): if hasattr(os, f): self.check(getattr(os, f)) + if f in self.singles_fildes: + self.check_bool(getattr(os, f)) return helper for f in singles: locals()["test_"+f] = get_single(f) @@ -2214,8 +2217,16 @@ def check(self, f, *args, **kwargs): self.fail("%r didn't raise an OSError with a bad file descriptor" % f) + def check_bool(self, f, *args, **kwargs): + with warnings.catch_warnings(): + warnings.simplefilter("error", RuntimeWarning) + for fd in False, True: + with self.assertRaises(RuntimeWarning): + f(fd, *args, **kwargs) + def test_fdopen(self): self.check(os.fdopen, encoding="utf-8") + self.check_bool(os.fdopen, encoding="utf-8") @unittest.skipUnless(hasattr(os, 'isatty'), 'test needs os.isatty()') def test_isatty(self): @@ -2277,11 +2288,14 @@ def test_fchown(self): def test_fpathconf(self): self.check(os.pathconf, "PC_NAME_MAX") self.check(os.fpathconf, "PC_NAME_MAX") + self.check_bool(os.pathconf, "PC_NAME_MAX") + self.check_bool(os.fpathconf, "PC_NAME_MAX") @unittest.skipUnless(hasattr(os, 'ftruncate'), 'test needs os.ftruncate()') def test_ftruncate(self): self.check(os.truncate, 0) self.check(os.ftruncate, 0) + self.check_bool(os.truncate, 0) @unittest.skipUnless(hasattr(os, 'lseek'), 'test needs os.lseek()') def test_lseek(self): diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 72e348fbbdcbc14..a45f620e18dc1d0 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1514,6 +1514,13 @@ def test_stat_dir_fd(self): self.assertRaises(OverflowError, posix.stat, name, dir_fd=10**20) + for fd in False, True: + with self.assertWarnsRegex(RuntimeWarning, + 'bool is used as a file descriptor') as cm: + with self.assertRaises(OSError): + posix.stat('nonexisting', dir_fd=fd) + self.assertEqual(cm.filename, __file__) + @unittest.skipUnless(os.utime in os.supports_dir_fd, "test needs dir_fd support in os.utime()") def test_utime_dir_fd(self): with self.prepare_file() as (dir_fd, name, fullname): diff --git a/Misc/NEWS.d/next/Library/2023-10-24-19-19-54.gh-issue-82626._hfLRf.rst b/Misc/NEWS.d/next/Library/2023-10-24-19-19-54.gh-issue-82626._hfLRf.rst new file mode 100644 index 000000000000000..92a66b5bf0f635c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-24-19-19-54.gh-issue-82626._hfLRf.rst @@ -0,0 +1,2 @@ +Many functions now emit a warning if a boolean value is passed as a file +descriptor argument. diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c index 9cf268ca0b26c81..6bb156e41fe43cb 100644 --- a/Modules/_io/fileio.c +++ b/Modules/_io/fileio.c @@ -269,6 +269,13 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode, self->fd = -1; } + if (PyBool_Check(nameobj)) { + if (PyErr_WarnEx(PyExc_RuntimeWarning, + "bool is used as a file descriptor", 1)) + { + return -1; + } + } fd = PyLong_AsInt(nameobj); if (fd < 0) { if (!PyErr_Occurred()) { diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index a2e3c2300b3ce85..95d646c9c65b3c4 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -119,6 +119,13 @@ faulthandler_get_fileno(PyObject **file_ptr) } } else if (PyLong_Check(file)) { + if (PyBool_Check(file)) { + if (PyErr_WarnEx(PyExc_RuntimeWarning, + "bool is used as a file descriptor", 1)) + { + return -1; + } + } fd = PyLong_AsInt(file); if (fd == -1 && PyErr_Occurred()) return -1; diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 40ff131b119d66f..22891135bde0af7 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -969,6 +969,13 @@ _fd_converter(PyObject *o, int *p) int overflow; long long_value; + if (PyBool_Check(o)) { + if (PyErr_WarnEx(PyExc_RuntimeWarning, + "bool is used as a file descriptor", 1)) + { + return 0; + } + } PyObject *index = _PyNumber_Index(o); if (index == NULL) { return 0; diff --git a/Objects/fileobject.c b/Objects/fileobject.c index 5522eba34eace9b..e30ab952dff571b 100644 --- a/Objects/fileobject.c +++ b/Objects/fileobject.c @@ -174,6 +174,13 @@ PyObject_AsFileDescriptor(PyObject *o) PyObject *meth; if (PyLong_Check(o)) { + if (PyBool_Check(o)) { + if (PyErr_WarnEx(PyExc_RuntimeWarning, + "bool is used as a file descriptor", 1)) + { + return -1; + } + } fd = PyLong_AsInt(o); } else if (PyObject_GetOptionalAttr(o, &_Py_ID(fileno), &meth) < 0) { From c32bae52904723d99e1f98e2ef54570268d86467 Mon Sep 17 00:00:00 2001 From: mpage <mpage@meta.com> Date: Mon, 5 Feb 2024 13:48:37 -0800 Subject: [PATCH 146/507] gh-114944: Fix race between `_PyParkingLot_Park` and `_PyParkingLot_UnparkAll` when handling interrupts (#114945) Fix race between `_PyParkingLot_Park` and `_PyParkingLot_UnparkAll` when handling interrupts There is a potential race when `_PyParkingLot_UnparkAll` is executing in one thread and another thread is unblocked because of an interrupt in `_PyParkingLot_Park`. Consider the following scenario: 1. Thread T0 is blocked[^1] in `_PyParkingLot_Park` on address `A`. 2. Thread T1 executes `_PyParkingLot_UnparkAll` on address `A`. It finds the `wait_entry` for `T0` and unlinks[^2] its list node. 3. Immediately after (2), T0 is woken up due to an interrupt. It then segfaults trying to unlink[^3] the node that was previously unlinked in (2). To fix this we mark each waiter as unparking before releasing the bucket lock. `_PyParkingLot_Park` will wait to handle the coming wakeup, and not attempt to unlink the node, when this field is set. `_PyParkingLot_Unpark` does this already, presumably to handle this case. --- .../2024-02-03-01-48-38.gh-issue-114944.4J5ELD.rst | 1 + Python/parking_lot.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-03-01-48-38.gh-issue-114944.4J5ELD.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-03-01-48-38.gh-issue-114944.4J5ELD.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-03-01-48-38.gh-issue-114944.4J5ELD.rst new file mode 100644 index 000000000000000..fb41caf7c5f4fae --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-03-01-48-38.gh-issue-114944.4J5ELD.rst @@ -0,0 +1 @@ +Fixes a race between ``PyParkingLot_Park`` and ``_PyParkingLot_UnparkAll``. diff --git a/Python/parking_lot.c b/Python/parking_lot.c index c83d7443e289c56..8ba50fc1353ebdd 100644 --- a/Python/parking_lot.c +++ b/Python/parking_lot.c @@ -244,6 +244,7 @@ dequeue(Bucket *bucket, const void *address) if (wait->addr == (uintptr_t)address) { llist_remove(node); --bucket->num_waiters; + wait->is_unparking = true; return wait; } } @@ -262,6 +263,7 @@ dequeue_all(Bucket *bucket, const void *address, struct llist_node *dst) llist_remove(node); llist_insert_tail(dst, node); --bucket->num_waiters; + wait->is_unparking = true; } } } @@ -337,8 +339,6 @@ _PyParkingLot_Unpark(const void *addr, _Py_unpark_fn_t *fn, void *arg) _PyRawMutex_Lock(&bucket->mutex); struct wait_entry *waiter = dequeue(bucket, addr); if (waiter) { - waiter->is_unparking = true; - int has_more_waiters = (bucket->num_waiters > 0); fn(arg, waiter->park_arg, has_more_waiters); } From bb57ffdb38e9e8df8f9ea71f1430dbbe4bf2d3ac Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Tue, 6 Feb 2024 00:41:34 +0200 Subject: [PATCH 147/507] gh-83648: Support deprecation of options, arguments and subcommands in argparse (GH-114086) --- Doc/library/argparse.rst | 47 +++++- Doc/whatsnew/3.13.rst | 9 ++ Lib/argparse.py | 92 +++++++++--- Lib/test/test_argparse.py | 139 +++++++++++++++++- ...4-01-15-20-21-33.gh-issue-83648.HzD_fY.rst | 2 + 5 files changed, 262 insertions(+), 27 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-83648.HzD_fY.rst diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 1395d457f874b0c..952643a46416d21 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -777,6 +777,8 @@ The add_argument() method * dest_ - The name of the attribute to be added to the object returned by :meth:`parse_args`. + * deprecated_ - Whether or not use of the argument is deprecated. + The following sections describe how each of these are used. @@ -1439,6 +1441,34 @@ behavior:: >>> parser.parse_args('--foo XXX'.split()) Namespace(bar='XXX') + +.. _deprecated: + +deprecated +^^^^^^^^^^ + +During a project's lifetime, some arguments may need to be removed from the +command line. Before removing them, you should inform +your users that the arguments are deprecated and will be removed. +The ``deprecated`` keyword argument of +:meth:`~ArgumentParser.add_argument`, which defaults to ``False``, +specifies if the argument is deprecated and will be removed +in the future. +For arguments, if ``deprecated`` is ``True``, then a warning will be +printed to standard error when the argument is used:: + + >>> import argparse + >>> parser = argparse.ArgumentParser(prog='snake.py') + >>> parser.add_argument('--legs', default=0, type=int, deprecated=True) + >>> parser.parse_args([]) + Namespace(legs=0) + >>> parser.parse_args(['--legs', '4']) # doctest: +SKIP + snake.py: warning: option '--legs' is deprecated + Namespace(legs=4) + +.. versionchanged:: 3.13 + + Action classes ^^^^^^^^^^^^^^ @@ -1842,7 +1872,8 @@ Sub-commands {foo,bar} additional help - Furthermore, ``add_parser`` supports an additional ``aliases`` argument, + Furthermore, :meth:`~_SubParsersAction.add_parser` supports an additional + *aliases* argument, which allows multiple strings to refer to the same subparser. This example, like ``svn``, aliases ``co`` as a shorthand for ``checkout``:: @@ -1853,6 +1884,20 @@ Sub-commands >>> parser.parse_args(['co', 'bar']) Namespace(foo='bar') + :meth:`~_SubParsersAction.add_parser` supports also an additional + *deprecated* argument, which allows to deprecate the subparser. + + >>> import argparse + >>> parser = argparse.ArgumentParser(prog='chicken.py') + >>> subparsers = parser.add_subparsers() + >>> run = subparsers.add_parser('run') + >>> fly = subparsers.add_parser('fly', deprecated=True) + >>> parser.parse_args(['fly']) # doctest: +SKIP + chicken.py: warning: command 'fly' is deprecated + Namespace() + + .. versionadded:: 3.13 + One particularly effective way of handling sub-commands is to combine the use of the :meth:`add_subparsers` method with calls to :meth:`set_defaults` so that each subparser knows which Python function it should execute. For diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 9bac36ba0bffb8c..5e5f1e295f4d706 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -169,6 +169,15 @@ New Modules Improved Modules ================ +argparse +-------- + +* Add parameter *deprecated* in methods + :meth:`~argparse.ArgumentParser.add_argument` and :meth:`!add_parser` + which allows to deprecate command-line options, positional arguments and + subcommands. + (Contributed by Serhiy Storchaka in :gh:`83648`). + array ----- diff --git a/Lib/argparse.py b/Lib/argparse.py index 2131d729746d41e..04ee3b19aca755e 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -843,7 +843,8 @@ def __init__(self, choices=None, required=False, help=None, - metavar=None): + metavar=None, + deprecated=False): self.option_strings = option_strings self.dest = dest self.nargs = nargs @@ -854,6 +855,7 @@ def __init__(self, self.required = required self.help = help self.metavar = metavar + self.deprecated = deprecated def _get_kwargs(self): names = [ @@ -867,6 +869,7 @@ def _get_kwargs(self): 'required', 'help', 'metavar', + 'deprecated', ] return [(name, getattr(self, name)) for name in names] @@ -889,7 +892,8 @@ def __init__(self, choices=_deprecated_default, required=False, help=None, - metavar=_deprecated_default): + metavar=_deprecated_default, + deprecated=False): _option_strings = [] for option_string in option_strings: @@ -927,7 +931,8 @@ def __init__(self, choices=choices, required=required, help=help, - metavar=metavar) + metavar=metavar, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): @@ -950,7 +955,8 @@ def __init__(self, choices=None, required=False, help=None, - metavar=None): + metavar=None, + deprecated=False): if nargs == 0: raise ValueError('nargs for store actions must be != 0; if you ' 'have nothing to store, actions such as store ' @@ -967,7 +973,8 @@ def __init__(self, choices=choices, required=required, help=help, - metavar=metavar) + metavar=metavar, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, values) @@ -982,7 +989,8 @@ def __init__(self, default=None, required=False, help=None, - metavar=None): + metavar=None, + deprecated=False): super(_StoreConstAction, self).__init__( option_strings=option_strings, dest=dest, @@ -990,7 +998,8 @@ def __init__(self, const=const, default=default, required=required, - help=help) + help=help, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, self.const) @@ -1003,14 +1012,16 @@ def __init__(self, dest, default=False, required=False, - help=None): + help=None, + deprecated=False): super(_StoreTrueAction, self).__init__( option_strings=option_strings, dest=dest, const=True, - default=default, + deprecated=deprecated, required=required, - help=help) + help=help, + default=default) class _StoreFalseAction(_StoreConstAction): @@ -1020,14 +1031,16 @@ def __init__(self, dest, default=True, required=False, - help=None): + help=None, + deprecated=False): super(_StoreFalseAction, self).__init__( option_strings=option_strings, dest=dest, const=False, default=default, required=required, - help=help) + help=help, + deprecated=deprecated) class _AppendAction(Action): @@ -1042,7 +1055,8 @@ def __init__(self, choices=None, required=False, help=None, - metavar=None): + metavar=None, + deprecated=False): if nargs == 0: raise ValueError('nargs for append actions must be != 0; if arg ' 'strings are not supplying the value to append, ' @@ -1059,7 +1073,8 @@ def __init__(self, choices=choices, required=required, help=help, - metavar=metavar) + metavar=metavar, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): items = getattr(namespace, self.dest, None) @@ -1077,7 +1092,8 @@ def __init__(self, default=None, required=False, help=None, - metavar=None): + metavar=None, + deprecated=False): super(_AppendConstAction, self).__init__( option_strings=option_strings, dest=dest, @@ -1086,7 +1102,8 @@ def __init__(self, default=default, required=required, help=help, - metavar=metavar) + metavar=metavar, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): items = getattr(namespace, self.dest, None) @@ -1102,14 +1119,16 @@ def __init__(self, dest, default=None, required=False, - help=None): + help=None, + deprecated=False): super(_CountAction, self).__init__( option_strings=option_strings, dest=dest, nargs=0, default=default, required=required, - help=help) + help=help, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): count = getattr(namespace, self.dest, None) @@ -1124,13 +1143,15 @@ def __init__(self, option_strings, dest=SUPPRESS, default=SUPPRESS, - help=None): + help=None, + deprecated=False): super(_HelpAction, self).__init__( option_strings=option_strings, dest=dest, default=default, nargs=0, - help=help) + help=help, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): parser.print_help() @@ -1144,7 +1165,8 @@ def __init__(self, version=None, dest=SUPPRESS, default=SUPPRESS, - help="show program's version number and exit"): + help="show program's version number and exit", + deprecated=False): super(_VersionAction, self).__init__( option_strings=option_strings, dest=dest, @@ -1188,6 +1210,7 @@ def __init__(self, self._parser_class = parser_class self._name_parser_map = {} self._choices_actions = [] + self._deprecated = set() super(_SubParsersAction, self).__init__( option_strings=option_strings, @@ -1198,7 +1221,7 @@ def __init__(self, help=help, metavar=metavar) - def add_parser(self, name, **kwargs): + def add_parser(self, name, *, deprecated=False, **kwargs): # set prog from the existing prefix if kwargs.get('prog') is None: kwargs['prog'] = '%s %s' % (self._prog_prefix, name) @@ -1226,6 +1249,10 @@ def add_parser(self, name, **kwargs): for alias in aliases: self._name_parser_map[alias] = parser + if deprecated: + self._deprecated.add(name) + self._deprecated.update(aliases) + return parser def _get_subactions(self): @@ -1241,13 +1268,17 @@ def __call__(self, parser, namespace, values, option_string=None): # select the parser try: - parser = self._name_parser_map[parser_name] + subparser = self._name_parser_map[parser_name] except KeyError: args = {'parser_name': parser_name, 'choices': ', '.join(self._name_parser_map)} msg = _('unknown parser %(parser_name)r (choices: %(choices)s)') % args raise ArgumentError(self, msg) + if parser_name in self._deprecated: + parser._warning(_("command '%(parser_name)s' is deprecated") % + {'parser_name': parser_name}) + # parse all the remaining options into the namespace # store any unrecognized options on the object, so that the top # level parser can decide what to do with them @@ -1255,7 +1286,7 @@ def __call__(self, parser, namespace, values, option_string=None): # In case this subparser defines new defaults, we parse them # in a new namespace object and then update the original # namespace for the relevant parts. - subnamespace, arg_strings = parser.parse_known_args(arg_strings, None) + subnamespace, arg_strings = subparser.parse_known_args(arg_strings, None) for key, value in vars(subnamespace).items(): setattr(namespace, key, value) @@ -1975,6 +2006,7 @@ def _parse_known_args(self, arg_strings, namespace): # converts arg strings to the appropriate and then takes the action seen_actions = set() seen_non_default_actions = set() + warned = set() def take_action(action, argument_strings, option_string=None): seen_actions.add(action) @@ -2070,6 +2102,10 @@ def consume_optional(start_index): # the Optional's string args stopped assert action_tuples for action, args, option_string in action_tuples: + if action.deprecated and option_string not in warned: + self._warning(_("option '%(option)s' is deprecated") % + {'option': option_string}) + warned.add(option_string) take_action(action, args, option_string) return stop @@ -2089,6 +2125,10 @@ def consume_positionals(start_index): for action, arg_count in zip(positionals, arg_counts): args = arg_strings[start_index: start_index + arg_count] start_index += arg_count + if args and action.deprecated and action.dest not in warned: + self._warning(_("argument '%(argument_name)s' is deprecated") % + {'argument_name': action.dest}) + warned.add(action.dest) take_action(action, args) # slice off the Positionals that we just parsed and return the @@ -2650,3 +2690,7 @@ def error(self, message): self.print_usage(_sys.stderr) args = {'prog': self.prog, 'message': message} self.exit(2, _('%(prog)s: error: %(message)s\n') % args) + + def _warning(self, message): + args = {'prog': self.prog, 'message': message} + self._print_message(_('%(prog)s: warning: %(message)s\n') % args, _sys.stderr) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index d1f3d40000140d3..86d6e81a71642b7 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -5099,7 +5099,8 @@ def test_optional(self): string = ( "Action(option_strings=['--foo', '-a', '-b'], dest='b', " "nargs='+', const=None, default=42, type='int', " - "choices=[1, 2, 3], required=False, help='HELP', metavar='METAVAR')") + "choices=[1, 2, 3], required=False, help='HELP', " + "metavar='METAVAR', deprecated=False)") self.assertStringEqual(option, string) def test_argument(self): @@ -5116,7 +5117,8 @@ def test_argument(self): string = ( "Action(option_strings=[], dest='x', nargs='?', " "const=None, default=2.5, type=%r, choices=[0.5, 1.5, 2.5], " - "required=True, help='H HH H', metavar='MV MV MV')" % float) + "required=True, help='H HH H', metavar='MV MV MV', " + "deprecated=False)" % float) self.assertStringEqual(argument, string) def test_namespace(self): @@ -5308,6 +5310,139 @@ def spam(string_to_convert): args = parser.parse_args('--foo spam!'.split()) self.assertEqual(NS(foo='foo_converted'), args) + +# ============================================== +# Check that deprecated arguments output warning +# ============================================== + +class TestDeprecatedArguments(TestCase): + + def test_deprecated_option(self): + parser = argparse.ArgumentParser() + parser.add_argument('-f', '--foo', deprecated=True) + + with captured_stderr() as stderr: + parser.parse_args(['--foo', 'spam']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: option '--foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['-f', 'spam']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: option '-f' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['--foo', 'spam', '-f', 'ham']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: option '--foo' is deprecated") + self.assertRegex(stderr, "warning: option '-f' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 2) + + with captured_stderr() as stderr: + parser.parse_args(['--foo', 'spam', '--foo', 'ham']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: option '--foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + def test_deprecated_boolean_option(self): + parser = argparse.ArgumentParser() + parser.add_argument('-f', '--foo', action=argparse.BooleanOptionalAction, deprecated=True) + + with captured_stderr() as stderr: + parser.parse_args(['--foo']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: option '--foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['-f']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: option '-f' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['--no-foo']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: option '--no-foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['--foo', '--no-foo']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: option '--foo' is deprecated") + self.assertRegex(stderr, "warning: option '--no-foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 2) + + def test_deprecated_arguments(self): + parser = argparse.ArgumentParser() + parser.add_argument('foo', nargs='?', deprecated=True) + parser.add_argument('bar', nargs='?', deprecated=True) + + with captured_stderr() as stderr: + parser.parse_args([]) + stderr = stderr.getvalue() + self.assertEqual(stderr.count('is deprecated'), 0) + + with captured_stderr() as stderr: + parser.parse_args(['spam']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: argument 'foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['spam', 'ham']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: argument 'foo' is deprecated") + self.assertRegex(stderr, "warning: argument 'bar' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 2) + + def test_deprecated_varargument(self): + parser = argparse.ArgumentParser() + parser.add_argument('foo', nargs='*', deprecated=True) + + with captured_stderr() as stderr: + parser.parse_args([]) + stderr = stderr.getvalue() + self.assertEqual(stderr.count('is deprecated'), 0) + + with captured_stderr() as stderr: + parser.parse_args(['spam']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: argument 'foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['spam', 'ham']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: argument 'foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + def test_deprecated_subparser(self): + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers() + subparsers.add_parser('foo', aliases=['baz'], deprecated=True) + subparsers.add_parser('bar') + + with captured_stderr() as stderr: + parser.parse_args(['bar']) + stderr = stderr.getvalue() + self.assertEqual(stderr.count('is deprecated'), 0) + + with captured_stderr() as stderr: + parser.parse_args(['foo']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: command 'foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['baz']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: command 'baz' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + # ================================================================== # Check semantics regarding the default argument and type conversion # ================================================================== diff --git a/Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-83648.HzD_fY.rst b/Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-83648.HzD_fY.rst new file mode 100644 index 000000000000000..bd3e27b4be0cf58 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-83648.HzD_fY.rst @@ -0,0 +1,2 @@ +Support deprecation of options, positional arguments and subcommands in +:mod:`argparse`. From 01dceba13e872e9ca24b8e00a2b75db3d0d6c1a3 Mon Sep 17 00:00:00 2001 From: Zachary Ware <zach@python.org> Date: Mon, 5 Feb 2024 17:10:55 -0600 Subject: [PATCH 148/507] gh-109991: Update Windows build to use OpenSSL 3.0.13 (#115043) --- .../Windows/2024-02-05-16-53-12.gh-issue-109991.YqjnDz.rst | 1 + PCbuild/get_externals.bat | 4 ++-- PCbuild/python.props | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2024-02-05-16-53-12.gh-issue-109991.YqjnDz.rst diff --git a/Misc/NEWS.d/next/Windows/2024-02-05-16-53-12.gh-issue-109991.YqjnDz.rst b/Misc/NEWS.d/next/Windows/2024-02-05-16-53-12.gh-issue-109991.YqjnDz.rst new file mode 100644 index 000000000000000..d9923c35c2726e6 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-02-05-16-53-12.gh-issue-109991.YqjnDz.rst @@ -0,0 +1 @@ +Update Windows build to use OpenSSL 3.0.13. diff --git a/PCbuild/get_externals.bat b/PCbuild/get_externals.bat index de73d923d8f4df1..0989bd46a580f77 100644 --- a/PCbuild/get_externals.bat +++ b/PCbuild/get_externals.bat @@ -53,7 +53,7 @@ echo.Fetching external libraries... set libraries= set libraries=%libraries% bzip2-1.0.8 if NOT "%IncludeLibffiSrc%"=="false" set libraries=%libraries% libffi-3.4.4 -if NOT "%IncludeSSLSrc%"=="false" set libraries=%libraries% openssl-3.0.11 +if NOT "%IncludeSSLSrc%"=="false" set libraries=%libraries% openssl-3.0.13 set libraries=%libraries% sqlite-3.44.2.0 if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tcl-core-8.6.13.1 if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tk-8.6.13.1 @@ -76,7 +76,7 @@ echo.Fetching external binaries... set binaries= if NOT "%IncludeLibffi%"=="false" set binaries=%binaries% libffi-3.4.4 -if NOT "%IncludeSSL%"=="false" set binaries=%binaries% openssl-bin-3.0.11 +if NOT "%IncludeSSL%"=="false" set binaries=%binaries% openssl-bin-3.0.13 if NOT "%IncludeTkinter%"=="false" set binaries=%binaries% tcltk-8.6.13.1 if NOT "%IncludeSSLSrc%"=="false" set binaries=%binaries% nasm-2.11.06 diff --git a/PCbuild/python.props b/PCbuild/python.props index 2cb16693e546b1b..54553db40572888 100644 --- a/PCbuild/python.props +++ b/PCbuild/python.props @@ -74,8 +74,8 @@ <libffiDir Condition="$(libffiDir) == ''">$(ExternalsDir)libffi-3.4.4\</libffiDir> <libffiOutDir Condition="$(libffiOutDir) == ''">$(libffiDir)$(ArchName)\</libffiOutDir> <libffiIncludeDir Condition="$(libffiIncludeDir) == ''">$(libffiOutDir)include</libffiIncludeDir> - <opensslDir Condition="$(opensslDir) == ''">$(ExternalsDir)openssl-3.0.11\</opensslDir> - <opensslOutDir Condition="$(opensslOutDir) == ''">$(ExternalsDir)openssl-bin-3.0.11\$(ArchName)\</opensslOutDir> + <opensslDir Condition="$(opensslDir) == ''">$(ExternalsDir)openssl-3.0.13\</opensslDir> + <opensslOutDir Condition="$(opensslOutDir) == ''">$(ExternalsDir)openssl-bin-3.0.13\$(ArchName)\</opensslOutDir> <opensslIncludeDir Condition="$(opensslIncludeDir) == ''">$(opensslOutDir)include</opensslIncludeDir> <nasmDir Condition="$(nasmDir) == ''">$(ExternalsDir)\nasm-2.11.06\</nasmDir> <zlibDir Condition="$(zlibDir) == ''">$(ExternalsDir)\zlib-1.3.1\</zlibDir> From 638e811a3c54a81d8af5a4c08b9497d210823f78 Mon Sep 17 00:00:00 2001 From: Ned Deily <nad@python.org> Date: Mon, 5 Feb 2024 20:59:25 -0500 Subject: [PATCH 149/507] gh-109991: Update macOS installer to use OpenSSL 3.0.13. (GH-115052) --- Mac/BuildScript/build-installer.py | 6 +++--- .../macOS/2024-02-05-18-30-27.gh-issue-109991.tun6Yu.rst | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/macOS/2024-02-05-18-30-27.gh-issue-109991.tun6Yu.rst diff --git a/Mac/BuildScript/build-installer.py b/Mac/BuildScript/build-installer.py index 32de56bcf130863..9000fb8973659db 100755 --- a/Mac/BuildScript/build-installer.py +++ b/Mac/BuildScript/build-installer.py @@ -246,9 +246,9 @@ def library_recipes(): result.extend([ dict( - name="OpenSSL 3.0.11", - url="https://www.openssl.org/source/openssl-3.0.11.tar.gz", - checksum='b3425d3bb4a2218d0697eb41f7fc0cdede016ed19ca49d168b78e8d947887f55', + name="OpenSSL 3.0.13", + url="https://www.openssl.org/source/openssl-3.0.13.tar.gz", + checksum='88525753f79d3bec27d2fa7c66aa0b92b3aa9498dafd93d7cfa4b3780cdae313', buildrecipe=build_universal_openssl, configure=None, install=None, diff --git a/Misc/NEWS.d/next/macOS/2024-02-05-18-30-27.gh-issue-109991.tun6Yu.rst b/Misc/NEWS.d/next/macOS/2024-02-05-18-30-27.gh-issue-109991.tun6Yu.rst new file mode 100644 index 000000000000000..79b45e7d51da3f5 --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2024-02-05-18-30-27.gh-issue-109991.tun6Yu.rst @@ -0,0 +1 @@ +Update macOS installer to use OpenSSL 3.0.13. From 299e16ca0f303a1e00bd0e04679862a5d4db5ab2 Mon Sep 17 00:00:00 2001 From: Ned Deily <nad@python.org> Date: Mon, 5 Feb 2024 21:10:11 -0500 Subject: [PATCH 150/507] gh-109991: Update GitHub CI workflows to use OpenSSL 3.0.13. (#115050) Also update multissltests to use 1.1.1w, 3.0.13, 3.1.5, and 3.2.1. --- .github/workflows/build.yml | 6 +++--- .github/workflows/reusable-ubuntu.yml | 2 +- .../2024-02-05-19-00-32.gh-issue-109991.yJSEkw.rst | 2 ++ Tools/ssl/multissltests.py | 5 +++-- 4 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Tools-Demos/2024-02-05-19-00-32.gh-issue-109991.yJSEkw.rst diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 949c4ae95da07f0..0a2f6da50ed8a09 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -250,7 +250,7 @@ jobs: strategy: fail-fast: false matrix: - openssl_ver: [1.1.1w, 3.0.11, 3.1.3] + openssl_ver: [1.1.1w, 3.0.13, 3.1.5, 3.2.1] env: OPENSSL_VER: ${{ matrix.openssl_ver }} MULTISSL_DIR: ${{ github.workspace }}/multissl @@ -304,7 +304,7 @@ jobs: needs: check_source if: needs.check_source.outputs.run_tests == 'true' && needs.check_source.outputs.run_hypothesis == 'true' env: - OPENSSL_VER: 3.0.11 + OPENSSL_VER: 3.0.13 PYTHONSTRICTEXTENSIONBUILD: 1 steps: - uses: actions/checkout@v4 @@ -415,7 +415,7 @@ jobs: needs: check_source if: needs.check_source.outputs.run_tests == 'true' env: - OPENSSL_VER: 3.0.11 + OPENSSL_VER: 3.0.13 PYTHONSTRICTEXTENSIONBUILD: 1 ASAN_OPTIONS: detect_leaks=0:allocator_may_return_null=1:handle_segv=0 steps: diff --git a/.github/workflows/reusable-ubuntu.yml b/.github/workflows/reusable-ubuntu.yml index ef52d99c15191b9..0cbad57f0c6572e 100644 --- a/.github/workflows/reusable-ubuntu.yml +++ b/.github/workflows/reusable-ubuntu.yml @@ -14,7 +14,7 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-20.04 env: - OPENSSL_VER: 3.0.11 + OPENSSL_VER: 3.0.13 PYTHONSTRICTEXTENSIONBUILD: 1 steps: - uses: actions/checkout@v4 diff --git a/Misc/NEWS.d/next/Tools-Demos/2024-02-05-19-00-32.gh-issue-109991.yJSEkw.rst b/Misc/NEWS.d/next/Tools-Demos/2024-02-05-19-00-32.gh-issue-109991.yJSEkw.rst new file mode 100644 index 000000000000000..4eb4d39629b9bcd --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2024-02-05-19-00-32.gh-issue-109991.yJSEkw.rst @@ -0,0 +1,2 @@ +Update GitHub CI workflows to use OpenSSL 3.0.13 and multissltests to use +1.1.1w, 3.0.13, 3.1.5, and 3.2.1. diff --git a/Tools/ssl/multissltests.py b/Tools/ssl/multissltests.py index 120e3883adc795d..baa16102068aa08 100755 --- a/Tools/ssl/multissltests.py +++ b/Tools/ssl/multissltests.py @@ -47,8 +47,9 @@ OPENSSL_RECENT_VERSIONS = [ "1.1.1w", - "3.0.11", - "3.1.3", + "3.0.13", + "3.1.5", + "3.2.1", ] LIBRESSL_OLD_VERSIONS = [ From 1b1f8398d0ffe3c8ba2cca79d0c0f19a6a34e72a Mon Sep 17 00:00:00 2001 From: Barney Gale <barney.gale@gmail.com> Date: Tue, 6 Feb 2024 02:48:18 +0000 Subject: [PATCH 151/507] GH-106747: Make pathlib ABC globbing more consistent with `glob.glob()` (#115056) When expanding `**` wildcards, ensure we add a trailing slash to the topmost directory path. This matches `glob.glob()` behaviour: >>> glob.glob('dirA/**', recursive=True) ['dirA/', 'dirA/dirB', 'dirA/dirB/dirC'] This does not affect `pathlib.Path.glob()`, because trailing slashes aren't supported in pathlib proper. --- Lib/pathlib/_abc.py | 2 +- Lib/test/test_pathlib/test_pathlib_abc.py | 34 +++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 91f5cd6c01e9d08..e4b1201a3703c32 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -95,7 +95,7 @@ def _select_recursive(parent_paths, dir_only, follow_symlinks): if follow_symlinks is None: follow_symlinks = False for parent_path in parent_paths: - paths = [parent_path] + paths = [parent_path._make_child_relpath('')] while paths: path = paths.pop() yield path diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 207579ccbf443b7..1d30deca8f7a1bb 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -1791,25 +1791,25 @@ def _check(path, glob, expected): _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"]) _check(p, "dir*/*/..", ["dirC/dirD/..", "dirA/linkC/..", "dirB/linkD/.."]) _check(p, "dir*/**", [ - "dirA", "dirA/linkC", "dirA/linkC/fileB", "dirA/linkC/linkD", "dirA/linkC/linkD/fileB", - "dirB", "dirB/fileB", "dirB/linkD", "dirB/linkD/fileB", - "dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt", - "dirE"]) + "dirA/", "dirA/linkC", "dirA/linkC/fileB", "dirA/linkC/linkD", "dirA/linkC/linkD/fileB", + "dirB/", "dirB/fileB", "dirB/linkD", "dirB/linkD/fileB", + "dirC/", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt", + "dirE/"]) _check(p, "dir*/**/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", "dirC/", "dirC/dirD/", "dirE/"]) _check(p, "dir*/**/..", ["dirA/..", "dirA/linkC/..", "dirB/..", "dirB/linkD/..", "dirA/linkC/linkD/..", "dirC/..", "dirC/dirD/..", "dirE/.."]) _check(p, "dir*/*/**", [ - "dirA/linkC", "dirA/linkC/linkD", "dirA/linkC/fileB", "dirA/linkC/linkD/fileB", - "dirB/linkD", "dirB/linkD/fileB", - "dirC/dirD", "dirC/dirD/fileD"]) + "dirA/linkC/", "dirA/linkC/linkD", "dirA/linkC/fileB", "dirA/linkC/linkD/fileB", + "dirB/linkD/", "dirB/linkD/fileB", + "dirC/dirD/", "dirC/dirD/fileD"]) _check(p, "dir*/*/**/", ["dirA/linkC/", "dirA/linkC/linkD/", "dirB/linkD/", "dirC/dirD/"]) _check(p, "dir*/*/**/..", ["dirA/linkC/..", "dirA/linkC/linkD/..", "dirB/linkD/..", "dirC/dirD/.."]) _check(p, "dir*/**/fileC", ["dirC/fileC"]) _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) - _check(p, "*/dirD/**", ["dirC/dirD", "dirC/dirD/fileD"]) + _check(p, "*/dirD/**", ["dirC/dirD/", "dirC/dirD/fileD"]) _check(p, "*/dirD/**/", ["dirC/dirD/"]) @needs_symlinks @@ -1827,19 +1827,19 @@ def _check(path, glob, expected): _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/"]) _check(p, "dir*/*/..", ["dirC/dirD/.."]) _check(p, "dir*/**", [ - "dirA", "dirA/linkC", - "dirB", "dirB/fileB", "dirB/linkD", - "dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt", - "dirE"]) + "dirA/", "dirA/linkC", + "dirB/", "dirB/fileB", "dirB/linkD", + "dirC/", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt", + "dirE/"]) _check(p, "dir*/**/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"]) _check(p, "dir*/**/..", ["dirA/..", "dirB/..", "dirC/..", "dirC/dirD/..", "dirE/.."]) - _check(p, "dir*/*/**", ["dirC/dirD", "dirC/dirD/fileD"]) + _check(p, "dir*/*/**", ["dirC/dirD/", "dirC/dirD/fileD"]) _check(p, "dir*/*/**/", ["dirC/dirD/"]) _check(p, "dir*/*/**/..", ["dirC/dirD/.."]) _check(p, "dir*/**/fileC", ["dirC/fileC"]) - _check(p, "dir*/*/../dirD/**", ["dirC/dirD/../dirD", "dirC/dirD/../dirD/fileD"]) + _check(p, "dir*/*/../dirD/**", ["dirC/dirD/../dirD/", "dirC/dirD/../dirD/fileD"]) _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) - _check(p, "*/dirD/**", ["dirC/dirD", "dirC/dirD/fileD"]) + _check(p, "*/dirD/**", ["dirC/dirD/", "dirC/dirD/fileD"]) _check(p, "*/dirD/**/", ["dirC/dirD/"]) def test_rglob_common(self): @@ -1876,13 +1876,13 @@ def _check(glob, expected): "dirC/dirD", "dirC/dirD/fileD"]) _check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"]) _check(p.rglob("**/file*"), ["dirC/fileC", "dirC/dirD/fileD"]) - _check(p.rglob("dir*/**"), ["dirC/dirD", "dirC/dirD/fileD"]) + _check(p.rglob("dir*/**"), ["dirC/dirD/", "dirC/dirD/fileD"]) _check(p.rglob("dir*/**/"), ["dirC/dirD/"]) _check(p.rglob("*/*"), ["dirC/dirD/fileD"]) _check(p.rglob("*/"), ["dirC/dirD/"]) _check(p.rglob(""), ["dirC/", "dirC/dirD/"]) _check(p.rglob("**"), [ - "dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt"]) + "dirC/", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt"]) _check(p.rglob("**/"), ["dirC/", "dirC/dirD/"]) # gh-91616, a re module regression _check(p.rglob("*.txt"), ["dirC/novel.txt"]) From 13eb5215c9de9dd302f116ef0bca4ae23b02842b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Tue, 6 Feb 2024 11:04:35 +0100 Subject: [PATCH 152/507] gh-115009: Update macOS installer to use SQLite 3.45.1 (#115066) Co-authored-by: Ned Deily <nad@python.org> --- Mac/BuildScript/build-installer.py | 6 +++--- .../macOS/2024-02-06-09-01-10.gh-issue-115009.ysau7e.rst | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/macOS/2024-02-06-09-01-10.gh-issue-115009.ysau7e.rst diff --git a/Mac/BuildScript/build-installer.py b/Mac/BuildScript/build-installer.py index 9000fb8973659db..0af90563cbbb2b7 100755 --- a/Mac/BuildScript/build-installer.py +++ b/Mac/BuildScript/build-installer.py @@ -359,9 +359,9 @@ def library_recipes(): ), ), dict( - name="SQLite 3.44.2", - url="https://sqlite.org/2023/sqlite-autoconf-3440200.tar.gz", - checksum="c02f40fd4f809ced95096250adc5764a", + name="SQLite 3.45.1", + url="https://sqlite.org/2024/sqlite-autoconf-3450100.tar.gz", + checksum="cd9c27841b7a5932c9897651e20b86c701dd740556989b01ca596fcfa3d49a0a", extra_cflags=('-Os ' '-DSQLITE_ENABLE_FTS5 ' '-DSQLITE_ENABLE_FTS4 ' diff --git a/Misc/NEWS.d/next/macOS/2024-02-06-09-01-10.gh-issue-115009.ysau7e.rst b/Misc/NEWS.d/next/macOS/2024-02-06-09-01-10.gh-issue-115009.ysau7e.rst new file mode 100644 index 000000000000000..47ec488c3cced2d --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2024-02-06-09-01-10.gh-issue-115009.ysau7e.rst @@ -0,0 +1 @@ +Update macOS installer to use SQLite 3.45.1. From 4bf41879d03b1da3c6d38c39a04331e3ae2e7545 Mon Sep 17 00:00:00 2001 From: Seth Michael Larson <seth@python.org> Date: Tue, 6 Feb 2024 04:25:58 -0600 Subject: [PATCH 153/507] gh-112302: Change 'licenseConcluded' field to 'NOASSERTION' (#115038) --- Misc/sbom.spdx.json | 60 ++++++++++++++++++------------------ Tools/build/generate_sbom.py | 12 +++++--- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/Misc/sbom.spdx.json b/Misc/sbom.spdx.json index e94dcb83dd4e404..d783d14255e66f1 100644 --- a/Misc/sbom.spdx.json +++ b/Misc/sbom.spdx.json @@ -1601,7 +1601,7 @@ "referenceType": "cpe23Type" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "expat", "originator": "Organization: Expat development team", "primaryPackagePurpose": "SOURCE", @@ -1623,7 +1623,7 @@ "referenceType": "cpe23Type" } ], - "licenseConcluded": "Apache-2.0", + "licenseConcluded": "NOASSERTION", "name": "hacl-star", "originator": "Organization: HACL* Developers", "primaryPackagePurpose": "SOURCE", @@ -1645,7 +1645,7 @@ "referenceType": "cpe23Type" } ], - "licenseConcluded": "CC0-1.0", + "licenseConcluded": "NOASSERTION", "name": "libb2", "originator": "Organization: BLAKE2 - fast secure hashing", "primaryPackagePurpose": "SOURCE", @@ -1667,7 +1667,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "macholib", "originator": "Person: Ronald Oussoren (ronaldoussoren@mac.com)", "primaryPackagePurpose": "SOURCE", @@ -1689,7 +1689,7 @@ "referenceType": "cpe23Type" } ], - "licenseConcluded": "BSD-2-Clause", + "licenseConcluded": "NOASSERTION", "name": "mpdecimal", "originator": "Organization: bytereef.org", "primaryPackagePurpose": "SOURCE", @@ -1711,7 +1711,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "cachecontrol", "primaryPackagePurpose": "SOURCE", "versionInfo": "0.13.1" @@ -1732,7 +1732,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "colorama", "primaryPackagePurpose": "SOURCE", "versionInfo": "0.4.6" @@ -1753,7 +1753,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "distlib", "primaryPackagePurpose": "SOURCE", "versionInfo": "0.3.8" @@ -1774,7 +1774,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "distro", "primaryPackagePurpose": "SOURCE", "versionInfo": "1.8.0" @@ -1795,7 +1795,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "msgpack", "primaryPackagePurpose": "SOURCE", "versionInfo": "1.0.5" @@ -1816,7 +1816,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "packaging", "primaryPackagePurpose": "SOURCE", "versionInfo": "21.3" @@ -1837,7 +1837,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "platformdirs", "primaryPackagePurpose": "SOURCE", "versionInfo": "3.8.1" @@ -1858,7 +1858,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "pyparsing", "primaryPackagePurpose": "SOURCE", "versionInfo": "3.1.0" @@ -1879,7 +1879,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "pyproject-hooks", "primaryPackagePurpose": "SOURCE", "versionInfo": "1.0.0" @@ -1900,7 +1900,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "requests", "primaryPackagePurpose": "SOURCE", "versionInfo": "2.31.0" @@ -1921,7 +1921,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "certifi", "primaryPackagePurpose": "SOURCE", "versionInfo": "2023.7.22" @@ -1942,7 +1942,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "chardet", "primaryPackagePurpose": "SOURCE", "versionInfo": "5.1.0" @@ -1963,7 +1963,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "idna", "primaryPackagePurpose": "SOURCE", "versionInfo": "3.4" @@ -1984,7 +1984,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "rich", "primaryPackagePurpose": "SOURCE", "versionInfo": "13.4.2" @@ -2005,7 +2005,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "pygments", "primaryPackagePurpose": "SOURCE", "versionInfo": "2.15.1" @@ -2026,7 +2026,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "typing_extensions", "primaryPackagePurpose": "SOURCE", "versionInfo": "4.7.1" @@ -2047,7 +2047,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "resolvelib", "primaryPackagePurpose": "SOURCE", "versionInfo": "1.0.1" @@ -2068,7 +2068,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "setuptools", "primaryPackagePurpose": "SOURCE", "versionInfo": "68.0.0" @@ -2089,7 +2089,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "six", "primaryPackagePurpose": "SOURCE", "versionInfo": "1.16.0" @@ -2110,7 +2110,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "tenacity", "primaryPackagePurpose": "SOURCE", "versionInfo": "8.2.2" @@ -2131,7 +2131,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "tomli", "primaryPackagePurpose": "SOURCE", "versionInfo": "2.0.1" @@ -2152,7 +2152,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "truststore", "primaryPackagePurpose": "SOURCE", "versionInfo": "0.8.0" @@ -2173,7 +2173,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "webencodings", "primaryPackagePurpose": "SOURCE", "versionInfo": "0.5.1" @@ -2194,7 +2194,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "urllib3", "primaryPackagePurpose": "SOURCE", "versionInfo": "1.26.17" @@ -2220,7 +2220,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "pip", "originator": "Organization: Python Packaging Authority", "primaryPackagePurpose": "SOURCE", diff --git a/Tools/build/generate_sbom.py b/Tools/build/generate_sbom.py index aceb13f141cba40..442487f2d2546b4 100644 --- a/Tools/build/generate_sbom.py +++ b/Tools/build/generate_sbom.py @@ -338,7 +338,7 @@ def discover_pip_sbom_package(sbom_data: dict[str, typing.Any]) -> None: "name": "pip", "versionInfo": pip_version, "originator": "Organization: Python Packaging Authority", - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "downloadLocation": pip_download_url, "checksums": [ {"algorithm": "SHA256", "checksumValue": pip_checksum_sha256} @@ -383,9 +383,11 @@ def main() -> None: discover_pip_sbom_package(sbom_data) # Ensure all packages in this tool are represented also in the SBOM file. + actual_names = {package["name"] for package in sbom_data["packages"]} + expected_names = set(PACKAGE_TO_FILES) error_if( - {package["name"] for package in sbom_data["packages"]} != set(PACKAGE_TO_FILES), - "Packages defined in SBOM tool don't match those defined in SBOM file.", + actual_names != expected_names, + f"Packages defined in SBOM tool don't match those defined in SBOM file: {actual_names}, {expected_names}", ) # Make a bunch of assertions about the SBOM data to ensure it's consistent. @@ -422,8 +424,8 @@ def main() -> None: # License must be on the approved list for SPDX. license_concluded = package["licenseConcluded"] error_if( - license_concluded not in ALLOWED_LICENSE_EXPRESSIONS, - f"License identifier '{license_concluded}' not in SBOM tool allowlist" + license_concluded != "NOASSERTION", + f"License identifier must be 'NOASSERTION'" ) # We call 'sorted()' here a lot to avoid filesystem scan order issues. From 1a10437a14b13100bdf41cbdab819c33258deb65 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak <felisiak.mariusz@gmail.com> Date: Tue, 6 Feb 2024 12:34:56 +0100 Subject: [PATCH 154/507] gh-91602: Add iterdump() support for filtering database objects (#114501) Add optional 'filter' parameter to iterdump() that allows a "LIKE" pattern for filtering database objects to dump. Co-authored-by: Erlend E. Aasland <erlend@python.org> --- Doc/library/sqlite3.rst | 11 ++- Doc/whatsnew/3.13.rst | 4 ++ .../pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + .../internal/pycore_runtime_init_generated.h | 1 + .../internal/pycore_unicodeobject_generated.h | 3 + Lib/sqlite3/dump.py | 19 +++-- Lib/test/test_sqlite3/test_dump.py | 70 +++++++++++++++++++ ...4-01-24-20-51-49.gh-issue-91602.8fOH8l.rst | 3 + Modules/_sqlite/clinic/connection.c.h | 60 ++++++++++++++-- Modules/_sqlite/connection.c | 20 ++++-- 11 files changed, 176 insertions(+), 17 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index c3406b166c3d89d..87d5ef1e42ca3ac 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1137,12 +1137,19 @@ Connection objects .. _Loading an Extension: https://www.sqlite.org/loadext.html#loading_an_extension_ - .. method:: iterdump + .. method:: iterdump(*, filter=None) Return an :term:`iterator` to dump the database as SQL source code. Useful when saving an in-memory database for later restoration. Similar to the ``.dump`` command in the :program:`sqlite3` shell. + :param filter: + + An optional ``LIKE`` pattern for database objects to dump, e.g. ``prefix_%``. + If ``None`` (the default), all database objects will be included. + + :type filter: str | None + Example: .. testcode:: @@ -1158,6 +1165,8 @@ Connection objects :ref:`sqlite3-howto-encoding` + .. versionchanged:: 3.13 + Added the *filter* parameter. .. method:: backup(target, *, pages=-1, progress=None, name="main", sleep=0.250) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 5e5f1e295f4d706..372757759b986f9 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -438,6 +438,10 @@ sqlite3 object is not :meth:`closed <sqlite3.Connection.close>` explicitly. (Contributed by Erlend E. Aasland in :gh:`105539`.) +* Add *filter* keyword-only parameter to :meth:`sqlite3.Connection.iterdump` + for filtering database objects to dump. + (Contributed by Mariusz Felisiak in :gh:`91602`.) + subprocess ---------- diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index dd09ff40f39fe6f..932738c3049882b 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -940,6 +940,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fileno)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(filepath)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fillvalue)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(filter)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(filters)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(final)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(find_class)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 79d6509abcdfd91..da62b4f0a951ff8 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -429,6 +429,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(fileno) STRUCT_FOR_ID(filepath) STRUCT_FOR_ID(fillvalue) + STRUCT_FOR_ID(filter) STRUCT_FOR_ID(filters) STRUCT_FOR_ID(final) STRUCT_FOR_ID(find_class) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index f3c55acfb3c282f..68fbbcb4378e17f 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -938,6 +938,7 @@ extern "C" { INIT_ID(fileno), \ INIT_ID(filepath), \ INIT_ID(fillvalue), \ + INIT_ID(filter), \ INIT_ID(filters), \ INIT_ID(final), \ INIT_ID(find_class), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 2e9572382fe0332..c8458b4e36ccc93 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -1128,6 +1128,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(fillvalue); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(filter); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(filters); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Lib/sqlite3/dump.py b/Lib/sqlite3/dump.py index 719dfc8947697d4..9dcce7dc76ced48 100644 --- a/Lib/sqlite3/dump.py +++ b/Lib/sqlite3/dump.py @@ -15,7 +15,7 @@ def _quote_value(value): return "'{0}'".format(value.replace("'", "''")) -def _iterdump(connection): +def _iterdump(connection, *, filter=None): """ Returns an iterator to the dump of the database in an SQL text format. @@ -32,15 +32,23 @@ def _iterdump(connection): yield('PRAGMA foreign_keys=OFF;') yield('BEGIN TRANSACTION;') + if filter: + # Return database objects which match the filter pattern. + filter_name_clause = 'AND "name" LIKE ?' + params = [filter] + else: + filter_name_clause = "" + params = [] # sqlite_master table contains the SQL CREATE statements for the database. - q = """ + q = f""" SELECT "name", "type", "sql" FROM "sqlite_master" WHERE "sql" NOT NULL AND "type" == 'table' + {filter_name_clause} ORDER BY "name" """ - schema_res = cu.execute(q) + schema_res = cu.execute(q, params) sqlite_sequence = [] for table_name, type, sql in schema_res.fetchall(): if table_name == 'sqlite_sequence': @@ -82,13 +90,14 @@ def _iterdump(connection): yield("{0};".format(row[0])) # Now when the type is 'index', 'trigger', or 'view' - q = """ + q = f""" SELECT "name", "type", "sql" FROM "sqlite_master" WHERE "sql" NOT NULL AND "type" IN ('index', 'trigger', 'view') + {filter_name_clause} """ - schema_res = cu.execute(q) + schema_res = cu.execute(q, params) for name, type, sql in schema_res.fetchall(): yield('{0};'.format(sql)) diff --git a/Lib/test/test_sqlite3/test_dump.py b/Lib/test/test_sqlite3/test_dump.py index 2e1f0b80c10f46e..7261b7f0dc93d0a 100644 --- a/Lib/test/test_sqlite3/test_dump.py +++ b/Lib/test/test_sqlite3/test_dump.py @@ -54,6 +54,76 @@ def test_table_dump(self): [self.assertEqual(expected_sqls[i], actual_sqls[i]) for i in range(len(expected_sqls))] + def test_table_dump_filter(self): + all_table_sqls = [ + """CREATE TABLE "some_table_2" ("id_1" INTEGER);""", + """INSERT INTO "some_table_2" VALUES(3);""", + """INSERT INTO "some_table_2" VALUES(4);""", + """CREATE TABLE "test_table_1" ("id_2" INTEGER);""", + """INSERT INTO "test_table_1" VALUES(1);""", + """INSERT INTO "test_table_1" VALUES(2);""", + ] + all_views_sqls = [ + """CREATE VIEW "view_1" AS SELECT * FROM "some_table_2";""", + """CREATE VIEW "view_2" AS SELECT * FROM "test_table_1";""", + ] + # Create database structure. + for sql in [*all_table_sqls, *all_views_sqls]: + self.cu.execute(sql) + # %_table_% matches all tables. + dump_sqls = list(self.cx.iterdump(filter="%_table_%")) + self.assertEqual( + dump_sqls, + ["BEGIN TRANSACTION;", *all_table_sqls, "COMMIT;"], + ) + # view_% matches all views. + dump_sqls = list(self.cx.iterdump(filter="view_%")) + self.assertEqual( + dump_sqls, + ["BEGIN TRANSACTION;", *all_views_sqls, "COMMIT;"], + ) + # %_1 matches tables and views with the _1 suffix. + dump_sqls = list(self.cx.iterdump(filter="%_1")) + self.assertEqual( + dump_sqls, + [ + "BEGIN TRANSACTION;", + """CREATE TABLE "test_table_1" ("id_2" INTEGER);""", + """INSERT INTO "test_table_1" VALUES(1);""", + """INSERT INTO "test_table_1" VALUES(2);""", + """CREATE VIEW "view_1" AS SELECT * FROM "some_table_2";""", + "COMMIT;" + ], + ) + # some_% matches some_table_2. + dump_sqls = list(self.cx.iterdump(filter="some_%")) + self.assertEqual( + dump_sqls, + [ + "BEGIN TRANSACTION;", + """CREATE TABLE "some_table_2" ("id_1" INTEGER);""", + """INSERT INTO "some_table_2" VALUES(3);""", + """INSERT INTO "some_table_2" VALUES(4);""", + "COMMIT;" + ], + ) + # Only single object. + dump_sqls = list(self.cx.iterdump(filter="view_2")) + self.assertEqual( + dump_sqls, + [ + "BEGIN TRANSACTION;", + """CREATE VIEW "view_2" AS SELECT * FROM "test_table_1";""", + "COMMIT;" + ], + ) + # % matches all objects. + dump_sqls = list(self.cx.iterdump(filter="%")) + self.assertEqual( + dump_sqls, + ["BEGIN TRANSACTION;", *all_table_sqls, *all_views_sqls, "COMMIT;"], + ) + def test_dump_autoincrement(self): expected = [ 'CREATE TABLE "t1" (id integer primary key autoincrement);', diff --git a/Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst b/Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst new file mode 100644 index 000000000000000..21d39df43e035b6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst @@ -0,0 +1,3 @@ +Add *filter* keyword-only parameter to +:meth:`sqlite3.Connection.iterdump` for filtering database objects to dump. +Patch by Mariusz Felisiak. diff --git a/Modules/_sqlite/clinic/connection.c.h b/Modules/_sqlite/clinic/connection.c.h index f2cff6a7b421f3b..811314b5cd8aed8 100644 --- a/Modules/_sqlite/clinic/connection.c.h +++ b/Modules/_sqlite/clinic/connection.c.h @@ -1204,21 +1204,67 @@ pysqlite_connection_interrupt(pysqlite_Connection *self, PyObject *Py_UNUSED(ign } PyDoc_STRVAR(pysqlite_connection_iterdump__doc__, -"iterdump($self, /)\n" +"iterdump($self, /, *, filter=None)\n" "--\n" "\n" -"Returns iterator to the dump of the database in an SQL text format."); +"Returns iterator to the dump of the database in an SQL text format.\n" +"\n" +" filter\n" +" An optional LIKE pattern for database objects to dump"); #define PYSQLITE_CONNECTION_ITERDUMP_METHODDEF \ - {"iterdump", (PyCFunction)pysqlite_connection_iterdump, METH_NOARGS, pysqlite_connection_iterdump__doc__}, + {"iterdump", _PyCFunction_CAST(pysqlite_connection_iterdump), METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_iterdump__doc__}, static PyObject * -pysqlite_connection_iterdump_impl(pysqlite_Connection *self); +pysqlite_connection_iterdump_impl(pysqlite_Connection *self, + PyObject *filter); static PyObject * -pysqlite_connection_iterdump(pysqlite_Connection *self, PyObject *Py_UNUSED(ignored)) +pysqlite_connection_iterdump(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - return pysqlite_connection_iterdump_impl(self); + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(filter), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"filter", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "iterdump", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + PyObject *filter = Py_None; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 0, 0, argsbuf); + if (!args) { + goto exit; + } + if (!noptargs) { + goto skip_optional_kwonly; + } + filter = args[0]; +skip_optional_kwonly: + return_value = pysqlite_connection_iterdump_impl(self, filter); + +exit: + return return_value; } PyDoc_STRVAR(pysqlite_connection_backup__doc__, @@ -1820,4 +1866,4 @@ getconfig(pysqlite_Connection *self, PyObject *arg) #ifndef DESERIALIZE_METHODDEF #define DESERIALIZE_METHODDEF #endif /* !defined(DESERIALIZE_METHODDEF) */ -/*[clinic end generated code: output=99299d3ee2c247ab input=a9049054013a1b77]*/ +/*[clinic end generated code: output=3c6d0b748fac016f input=a9049054013a1b77]*/ diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 0a6633972cc5ef7..f97afcf5fcf16ef 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -1979,12 +1979,17 @@ pysqlite_connection_interrupt_impl(pysqlite_Connection *self) /*[clinic input] _sqlite3.Connection.iterdump as pysqlite_connection_iterdump + * + filter: object = None + An optional LIKE pattern for database objects to dump + Returns iterator to the dump of the database in an SQL text format. [clinic start generated code]*/ static PyObject * -pysqlite_connection_iterdump_impl(pysqlite_Connection *self) -/*[clinic end generated code: output=586997aaf9808768 input=1911ca756066da89]*/ +pysqlite_connection_iterdump_impl(pysqlite_Connection *self, + PyObject *filter) +/*[clinic end generated code: output=fd81069c4bdeb6b0 input=4ae6d9a898f108df]*/ { if (!pysqlite_check_connection(self)) { return NULL; @@ -1998,9 +2003,16 @@ pysqlite_connection_iterdump_impl(pysqlite_Connection *self) } return NULL; } - - PyObject *retval = PyObject_CallOneArg(iterdump, (PyObject *)self); + PyObject *args[3] = {NULL, (PyObject *)self, filter}; + PyObject *kwnames = Py_BuildValue("(s)", "filter"); + if (!kwnames) { + Py_DECREF(iterdump); + return NULL; + } + Py_ssize_t nargsf = 1 | PY_VECTORCALL_ARGUMENTS_OFFSET; + PyObject *retval = PyObject_Vectorcall(iterdump, args + 1, nargsf, kwnames); Py_DECREF(iterdump); + Py_DECREF(kwnames); return retval; } From d7334e2c2012defaf7aae920d6a56689464509d1 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Tue, 6 Feb 2024 16:08:56 +0300 Subject: [PATCH 155/507] gh-106233: Fix stacklevel in zoneinfo.InvalidTZPathWarning (GH-106234) --- Lib/test/test_zoneinfo/test_zoneinfo.py | 17 +++++++++++-- Lib/zoneinfo/_tzpath.py | 24 ++++++++++++------- ...-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst | 2 ++ 3 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 18eab5b33540c9e..8414721555731eb 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -20,7 +20,7 @@ from test.support import MISSING_C_DOCSTRINGS from test.test_zoneinfo import _support as test_support from test.test_zoneinfo._support import OS_ENV_LOCK, TZPATH_TEST_LOCK, ZoneInfoTestBase -from test.support.import_helper import import_module +from test.support.import_helper import import_module, CleanImport lzma = import_module('lzma') py_zoneinfo, c_zoneinfo = test_support.get_modules() @@ -1720,13 +1720,26 @@ def test_env_variable_relative_paths(self): with self.subTest("warning", path_var=path_var): # Note: Per PEP 615 the warning is implementation-defined # behavior, other implementations need not warn. - with self.assertWarns(self.module.InvalidTZPathWarning): + with self.assertWarns(self.module.InvalidTZPathWarning) as w: self.module.reset_tzpath() + self.assertEqual(w.warnings[0].filename, __file__) tzpath = self.module.TZPATH with self.subTest("filtered", path_var=path_var): self.assertSequenceEqual(tzpath, expected_paths) + def test_env_variable_relative_paths_warning_location(self): + path_var = "path/to/somewhere" + + with self.python_tzpath_context(path_var): + with CleanImport("zoneinfo", "zoneinfo._tzpath"): + with self.assertWarns(RuntimeWarning) as w: + import zoneinfo + InvalidTZPathWarning = zoneinfo.InvalidTZPathWarning + self.assertIsInstance(w.warnings[0].message, InvalidTZPathWarning) + # It should represent the current file: + self.assertEqual(w.warnings[0].filename, __file__) + def test_reset_tzpath_kwarg(self): self.module.reset_tzpath(to=[f"{DRIVE}/a/b/c"]) diff --git a/Lib/zoneinfo/_tzpath.py b/Lib/zoneinfo/_tzpath.py index 4985dce2dc36d0e..5db17bea045d8c8 100644 --- a/Lib/zoneinfo/_tzpath.py +++ b/Lib/zoneinfo/_tzpath.py @@ -2,7 +2,7 @@ import sysconfig -def reset_tzpath(to=None): +def _reset_tzpath(to=None, stacklevel=4): global TZPATH tzpaths = to @@ -18,17 +18,22 @@ def reset_tzpath(to=None): base_tzpath = tzpaths else: env_var = os.environ.get("PYTHONTZPATH", None) - if env_var is not None: - base_tzpath = _parse_python_tzpath(env_var) - else: - base_tzpath = _parse_python_tzpath( - sysconfig.get_config_var("TZPATH") - ) + if env_var is None: + env_var = sysconfig.get_config_var("TZPATH") + base_tzpath = _parse_python_tzpath(env_var, stacklevel) TZPATH = tuple(base_tzpath) -def _parse_python_tzpath(env_var): +def reset_tzpath(to=None): + """Reset global TZPATH.""" + # We need `_reset_tzpath` helper function because it produces a warning, + # it is used as both a module-level call and a public API. + # This is how we equalize the stacklevel for both calls. + _reset_tzpath(to) + + +def _parse_python_tzpath(env_var, stacklevel): if not env_var: return () @@ -45,6 +50,7 @@ def _parse_python_tzpath(env_var): "Invalid paths specified in PYTHONTZPATH environment variable. " + msg, InvalidTZPathWarning, + stacklevel=stacklevel, ) return new_tzpath @@ -172,4 +178,4 @@ class InvalidTZPathWarning(RuntimeWarning): TZPATH = () -reset_tzpath() +_reset_tzpath(stacklevel=5) diff --git a/Misc/NEWS.d/next/Library/2023-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst b/Misc/NEWS.d/next/Library/2023-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst new file mode 100644 index 000000000000000..345c8b20815c951 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst @@ -0,0 +1,2 @@ +Fix stacklevel in ``InvalidTZPathWarning`` during :mod:`zoneinfo` module +import. From 0e2ab73dc31e0b8ea1827ec24bae93ae2644c617 Mon Sep 17 00:00:00 2001 From: da-woods <dw-git@d-woods.co.uk> Date: Tue, 6 Feb 2024 15:55:44 +0000 Subject: [PATCH 156/507] gh-114756: Update FAQ section on removing the GIL (#114957) Update FAQ section on removing the GIL to reflect recent progress on PEP 703 and PEP 684. Co-authored-by: AN Long <aisk@users.noreply.github.com> --- Doc/faq/library.rst | 52 +++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/Doc/faq/library.rst b/Doc/faq/library.rst index 476a43d9c288f18..e2f8004c7e3aea5 100644 --- a/Doc/faq/library.rst +++ b/Doc/faq/library.rst @@ -405,22 +405,37 @@ lists. When in doubt, use a mutex! Can't we get rid of the Global Interpreter Lock? ------------------------------------------------ -.. XXX link to dbeazley's talk about GIL? - The :term:`global interpreter lock` (GIL) is often seen as a hindrance to Python's deployment on high-end multiprocessor server machines, because a multi-threaded Python program effectively only uses one CPU, due to the insistence that (almost) all Python code can only run while the GIL is held. -Back in the days of Python 1.5, Greg Stein actually implemented a comprehensive +With the approval of :pep:`703` work is now underway to remove the GIL from the +CPython implementation of Python. Initially it will be implemented as an +optional compiler flag when building the interpreter, and so separate +builds will be available with and without the GIL. Long-term, the hope is +to settle on a single build, once the performance implications of removing the +GIL are fully understood. Python 3.13 is likely to be the first release +containing this work, although it may not be completely functional in this +release. + +The current work to remove the GIL is based on a +`fork of Python 3.9 with the GIL removed <https://github.com/colesbury/nogil>`_ +by Sam Gross. +Prior to that, +in the days of Python 1.5, Greg Stein actually implemented a comprehensive patch set (the "free threading" patches) that removed the GIL and replaced it -with fine-grained locking. Adam Olsen recently did a similar experiment +with fine-grained locking. Adam Olsen did a similar experiment in his `python-safethread <https://code.google.com/archive/p/python-safethread>`_ -project. Unfortunately, both experiments exhibited a sharp drop in single-thread +project. Unfortunately, both of these earlier experiments exhibited a sharp +drop in single-thread performance (at least 30% slower), due to the amount of fine-grained locking -necessary to compensate for the removal of the GIL. +necessary to compensate for the removal of the GIL. The Python 3.9 fork +is the first attempt at removing the GIL with an acceptable performance +impact. -This doesn't mean that you can't make good use of Python on multi-CPU machines! +The presence of the GIL in current Python releases +doesn't mean that you can't make good use of Python on multi-CPU machines! You just have to be creative with dividing the work up between multiple *processes* rather than multiple *threads*. The :class:`~concurrent.futures.ProcessPoolExecutor` class in the new @@ -434,22 +449,13 @@ thread of execution is in the C code and allow other threads to get some work done. Some standard library modules such as :mod:`zlib` and :mod:`hashlib` already do this. -It has been suggested that the GIL should be a per-interpreter-state lock rather -than truly global; interpreters then wouldn't be able to share objects. -Unfortunately, this isn't likely to happen either. It would be a tremendous -amount of work, because many object implementations currently have global state. -For example, small integers and short strings are cached; these caches would -have to be moved to the interpreter state. Other object types have their own -free list; these free lists would have to be moved to the interpreter state. -And so on. - -And I doubt that it can even be done in finite time, because the same problem -exists for 3rd party extensions. It is likely that 3rd party extensions are -being written at a faster rate than you can convert them to store all their -global state in the interpreter state. - -And finally, once you have multiple interpreters not sharing any state, what -have you gained over running each interpreter in a separate process? +An alternative approach to reducing the impact of the GIL is +to make the GIL a per-interpreter-state lock rather than truly global. +This was :ref:`first implemented in Python 3.12 <whatsnew312-pep684>` and is +available in the C API. A Python interface to it is expected in Python 3.13. +The main limitation to it at the moment is likely to be 3rd party extension +modules, since these must be written with multiple interpreters in mind in +order to be usable, so many older extension modules will not be usable. Input and Output From de61d4bd4db868ce49a729a283763b94f2fda961 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Tue, 6 Feb 2024 11:36:23 -0500 Subject: [PATCH 157/507] gh-112066: Add `PyDict_SetDefaultRef` function. (#112123) The `PyDict_SetDefaultRef` function is similar to `PyDict_SetDefault`, but returns a strong reference through the optional `**result` pointer instead of a borrowed reference. Co-authored-by: Petr Viktorin <encukou@gmail.com> --- Doc/c-api/dict.rst | 20 ++++ Doc/whatsnew/3.13.rst | 6 ++ Include/cpython/dictobject.h | 10 ++ Lib/test/test_capi/test_dict.py | 22 +++++ ...-11-15-13-47-48.gh-issue-112066.22WsqR.rst | 5 + Modules/_testcapi/dict.c | 26 ++++++ Objects/dictobject.c | 91 +++++++++++++++---- 7 files changed, 160 insertions(+), 20 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2023-11-15-13-47-48.gh-issue-112066.22WsqR.rst diff --git a/Doc/c-api/dict.rst b/Doc/c-api/dict.rst index 8471c98d0448721..03f3d28187bfe9a 100644 --- a/Doc/c-api/dict.rst +++ b/Doc/c-api/dict.rst @@ -174,6 +174,26 @@ Dictionary Objects .. versionadded:: 3.4 +.. c:function:: int PyDict_SetDefaultRef(PyObject *p, PyObject *key, PyObject *default_value, PyObject **result) + + Inserts *default_value* into the dictionary *p* with a key of *key* if the + key is not already present in the dictionary. If *result* is not ``NULL``, + then *\*result* is set to a :term:`strong reference` to either + *default_value*, if the key was not present, or the existing value, if *key* + was already present in the dictionary. + Returns ``1`` if the key was present and *default_value* was not inserted, + or ``0`` if the key was not present and *default_value* was inserted. + On failure, returns ``-1``, sets an exception, and sets ``*result`` + to ``NULL``. + + For clarity: if you have a strong reference to *default_value* before + calling this function, then after it returns, you hold a strong reference + to both *default_value* and *\*result* (if it's not ``NULL``). + These may refer to the same object: in that case you hold two separate + references to it. + .. versionadded:: 3.13 + + .. c:function:: int PyDict_Pop(PyObject *p, PyObject *key, PyObject **result) Remove *key* from dictionary *p* and optionally return the removed value. diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 372757759b986f9..e034d34c5fb5abc 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1440,6 +1440,12 @@ New Features not needed. (Contributed by Victor Stinner in :gh:`106004`.) +* Added :c:func:`PyDict_SetDefaultRef`, which is similar to + :c:func:`PyDict_SetDefault` but returns a :term:`strong reference` instead of + a :term:`borrowed reference`. This function returns ``-1`` on error, ``0`` on + insertion, and ``1`` if the key was already present in the dictionary. + (Contributed by Sam Gross in :gh:`112066`.) + * Add :c:func:`PyDict_ContainsString` function: same as :c:func:`PyDict_Contains`, but *key* is specified as a :c:expr:`const char*` UTF-8 encoded bytes string, rather than a :c:expr:`PyObject*`. diff --git a/Include/cpython/dictobject.h b/Include/cpython/dictobject.h index 1720fe6f01ea37d..35b6a822a0dfffd 100644 --- a/Include/cpython/dictobject.h +++ b/Include/cpython/dictobject.h @@ -41,6 +41,16 @@ PyAPI_FUNC(PyObject *) _PyDict_GetItemStringWithError(PyObject *, const char *); PyAPI_FUNC(PyObject *) PyDict_SetDefault( PyObject *mp, PyObject *key, PyObject *defaultobj); +// Inserts `key` with a value `default_value`, if `key` is not already present +// in the dictionary. If `result` is not NULL, then the value associated +// with `key` is returned in `*result` (either the existing value, or the now +// inserted `default_value`). +// Returns: +// -1 on error +// 0 if `key` was not present and `default_value` was inserted +// 1 if `key` was present and `default_value` was not inserted +PyAPI_FUNC(int) PyDict_SetDefaultRef(PyObject *mp, PyObject *key, PyObject *default_value, PyObject **result); + /* Get the number of items of a dictionary. */ static inline Py_ssize_t PyDict_GET_SIZE(PyObject *op) { PyDictObject *mp; diff --git a/Lib/test/test_capi/test_dict.py b/Lib/test/test_capi/test_dict.py index 57a7238588eae09..cca6145bc90c047 100644 --- a/Lib/test/test_capi/test_dict.py +++ b/Lib/test/test_capi/test_dict.py @@ -339,6 +339,28 @@ def test_dict_setdefault(self): # CRASHES setdefault({}, 'a', NULL) # CRASHES setdefault(NULL, 'a', 5) + def test_dict_setdefaultref(self): + setdefault = _testcapi.dict_setdefaultref + dct = {} + self.assertEqual(setdefault(dct, 'a', 5), 5) + self.assertEqual(dct, {'a': 5}) + self.assertEqual(setdefault(dct, 'a', 8), 5) + self.assertEqual(dct, {'a': 5}) + + dct2 = DictSubclass() + self.assertEqual(setdefault(dct2, 'a', 5), 5) + self.assertEqual(dct2, {'a': 5}) + self.assertEqual(setdefault(dct2, 'a', 8), 5) + self.assertEqual(dct2, {'a': 5}) + + self.assertRaises(TypeError, setdefault, {}, [], 5) # unhashable + self.assertRaises(SystemError, setdefault, UserDict(), 'a', 5) + self.assertRaises(SystemError, setdefault, [1], 0, 5) + self.assertRaises(SystemError, setdefault, 42, 'a', 5) + # CRASHES setdefault({}, NULL, 5) + # CRASHES setdefault({}, 'a', NULL) + # CRASHES setdefault(NULL, 'a', 5) + def test_mapping_keys_valuesitems(self): class BadMapping(dict): def keys(self): diff --git a/Misc/NEWS.d/next/C API/2023-11-15-13-47-48.gh-issue-112066.22WsqR.rst b/Misc/NEWS.d/next/C API/2023-11-15-13-47-48.gh-issue-112066.22WsqR.rst new file mode 100644 index 000000000000000..ae2b8b2444de972 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-11-15-13-47-48.gh-issue-112066.22WsqR.rst @@ -0,0 +1,5 @@ +Add :c:func:`PyDict_SetDefaultRef`: insert a key and value into a dictionary +if the key is not already present. This is similar to +:meth:`dict.setdefault`, but returns an integer value indicating if the key +was already present. It is also similar to :c:func:`PyDict_SetDefault`, but +returns a strong reference instead of a borrowed reference. diff --git a/Modules/_testcapi/dict.c b/Modules/_testcapi/dict.c index 42e056b7d07a31f..fe03c24f75e196b 100644 --- a/Modules/_testcapi/dict.c +++ b/Modules/_testcapi/dict.c @@ -225,6 +225,31 @@ dict_setdefault(PyObject *self, PyObject *args) return PyDict_SetDefault(mapping, key, defaultobj); } +static PyObject * +dict_setdefaultref(PyObject *self, PyObject *args) +{ + PyObject *obj, *key, *default_value, *result = UNINITIALIZED_PTR; + if (!PyArg_ParseTuple(args, "OOO", &obj, &key, &default_value)) { + return NULL; + } + NULLABLE(obj); + NULLABLE(key); + NULLABLE(default_value); + switch (PyDict_SetDefaultRef(obj, key, default_value, &result)) { + case -1: + assert(result == NULL); + return NULL; + case 0: + assert(result == default_value); + return result; + case 1: + return result; + default: + Py_FatalError("PyDict_SetDefaultRef() returned invalid code"); + Py_UNREACHABLE(); + } +} + static PyObject * dict_delitem(PyObject *self, PyObject *args) { @@ -433,6 +458,7 @@ static PyMethodDef test_methods[] = { {"dict_delitem", dict_delitem, METH_VARARGS}, {"dict_delitemstring", dict_delitemstring, METH_VARARGS}, {"dict_setdefault", dict_setdefault, METH_VARARGS}, + {"dict_setdefaultref", dict_setdefaultref, METH_VARARGS}, {"dict_keys", dict_keys, METH_O}, {"dict_values", dict_values, METH_O}, {"dict_items", dict_items, METH_O}, diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 4bb818b90a4a72d..11b388d9f4adb05 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -3355,8 +3355,9 @@ dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value) return Py_NewRef(val); } -PyObject * -PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj) +static int +dict_setdefault_ref(PyObject *d, PyObject *key, PyObject *default_value, + PyObject **result, int incref_result) { PyDictObject *mp = (PyDictObject *)d; PyObject *value; @@ -3365,41 +3366,64 @@ PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj) if (!PyDict_Check(d)) { PyErr_BadInternalCall(); - return NULL; + if (result) { + *result = NULL; + } + return -1; } if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) { hash = PyObject_Hash(key); - if (hash == -1) - return NULL; + if (hash == -1) { + if (result) { + *result = NULL; + } + return -1; + } } if (mp->ma_keys == Py_EMPTY_KEYS) { if (insert_to_emptydict(interp, mp, Py_NewRef(key), hash, - Py_NewRef(defaultobj)) < 0) { - return NULL; + Py_NewRef(default_value)) < 0) { + if (result) { + *result = NULL; + } + return -1; + } + if (result) { + *result = incref_result ? Py_NewRef(default_value) : default_value; } - return defaultobj; + return 0; } if (!PyUnicode_CheckExact(key) && DK_IS_UNICODE(mp->ma_keys)) { if (insertion_resize(interp, mp, 0) < 0) { - return NULL; + if (result) { + *result = NULL; + } + return -1; } } Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &value); - if (ix == DKIX_ERROR) - return NULL; + if (ix == DKIX_ERROR) { + if (result) { + *result = NULL; + } + return -1; + } if (ix == DKIX_EMPTY) { uint64_t new_version = _PyDict_NotifyEvent( - interp, PyDict_EVENT_ADDED, mp, key, defaultobj); + interp, PyDict_EVENT_ADDED, mp, key, default_value); mp->ma_keys->dk_version = 0; - value = defaultobj; + value = default_value; if (mp->ma_keys->dk_usable <= 0) { if (insertion_resize(interp, mp, 1) < 0) { - return NULL; + if (result) { + *result = NULL; + } + return -1; } } Py_ssize_t hashpos = find_empty_slot(mp->ma_keys, hash); @@ -3431,11 +3455,16 @@ PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj) mp->ma_keys->dk_usable--; mp->ma_keys->dk_nentries++; assert(mp->ma_keys->dk_usable >= 0); + ASSERT_CONSISTENT(mp); + if (result) { + *result = incref_result ? Py_NewRef(value) : value; + } + return 0; } else if (value == NULL) { uint64_t new_version = _PyDict_NotifyEvent( - interp, PyDict_EVENT_ADDED, mp, key, defaultobj); - value = defaultobj; + interp, PyDict_EVENT_ADDED, mp, key, default_value); + value = default_value; assert(_PyDict_HasSplitTable(mp)); assert(mp->ma_values->values[ix] == NULL); MAINTAIN_TRACKING(mp, key, value); @@ -3443,10 +3472,33 @@ PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj) _PyDictValues_AddToInsertionOrder(mp->ma_values, ix); mp->ma_used++; mp->ma_version_tag = new_version; + ASSERT_CONSISTENT(mp); + if (result) { + *result = incref_result ? Py_NewRef(value) : value; + } + return 0; } ASSERT_CONSISTENT(mp); - return value; + if (result) { + *result = incref_result ? Py_NewRef(value) : value; + } + return 1; +} + +int +PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value, + PyObject **result) +{ + return dict_setdefault_ref(d, key, default_value, result, 1); +} + +PyObject * +PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj) +{ + PyObject *result; + dict_setdefault_ref(d, key, defaultobj, &result, 0); + return result; } /*[clinic input] @@ -3467,9 +3519,8 @@ dict_setdefault_impl(PyDictObject *self, PyObject *key, /*[clinic end generated code: output=f8c1101ebf69e220 input=0f063756e815fd9d]*/ { PyObject *val; - - val = PyDict_SetDefault((PyObject *)self, key, default_value); - return Py_XNewRef(val); + PyDict_SetDefaultRef((PyObject *)self, key, default_value, &val); + return val; } From f7a22a7055d97c05406512577bdfcb6d3f134b91 Mon Sep 17 00:00:00 2001 From: Donghee Na <donghee.na@python.org> Date: Wed, 7 Feb 2024 01:41:18 +0900 Subject: [PATCH 158/507] gh-112087: Make list_{count, index, contains} to be thread-safe. (gh-114916) --- Objects/listobject.c | 52 ++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index 82a4ba952de07dc..307b8f1bd76cac8 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -272,6 +272,15 @@ PyList_GetItemRef(PyObject *op, Py_ssize_t i) return Py_NewRef(PyList_GET_ITEM(op, i)); } +static inline PyObject* +list_get_item_ref(PyListObject *op, Py_ssize_t i) +{ + if (!valid_index(i, Py_SIZE(op))) { + return NULL; + } + return Py_NewRef(PyList_GET_ITEM(op, i)); +} + int PyList_SetItem(PyObject *op, Py_ssize_t i, PyObject *newitem) @@ -478,18 +487,20 @@ list_length(PyObject *a) static int list_contains(PyObject *aa, PyObject *el) { - PyListObject *a = (PyListObject *)aa; - PyObject *item; - Py_ssize_t i; - int cmp; - for (i = 0, cmp = 0 ; cmp == 0 && i < Py_SIZE(a); ++i) { - item = PyList_GET_ITEM(a, i); - Py_INCREF(item); - cmp = PyObject_RichCompareBool(item, el, Py_EQ); + for (Py_ssize_t i = 0; ; i++) { + PyObject *item = list_get_item_ref((PyListObject *)aa, i); + if (item == NULL) { + // out-of-bounds + return 0; + } + int cmp = PyObject_RichCompareBool(item, el, Py_EQ); Py_DECREF(item); + if (cmp != 0) { + return cmp; + } } - return cmp; + return 0; } static PyObject * @@ -2724,8 +2735,6 @@ list_index_impl(PyListObject *self, PyObject *value, Py_ssize_t start, Py_ssize_t stop) /*[clinic end generated code: output=ec51b88787e4e481 input=40ec5826303a0eb1]*/ { - Py_ssize_t i; - if (start < 0) { start += Py_SIZE(self); if (start < 0) @@ -2736,9 +2745,12 @@ list_index_impl(PyListObject *self, PyObject *value, Py_ssize_t start, if (stop < 0) stop = 0; } - for (i = start; i < stop && i < Py_SIZE(self); i++) { - PyObject *obj = self->ob_item[i]; - Py_INCREF(obj); + for (Py_ssize_t i = start; i < stop; i++) { + PyObject *obj = list_get_item_ref(self, i); + if (obj == NULL) { + // out-of-bounds + break; + } int cmp = PyObject_RichCompareBool(obj, value, Py_EQ); Py_DECREF(obj); if (cmp > 0) @@ -2764,15 +2776,17 @@ list_count(PyListObject *self, PyObject *value) /*[clinic end generated code: output=b1f5d284205ae714 input=3bdc3a5e6f749565]*/ { Py_ssize_t count = 0; - Py_ssize_t i; - - for (i = 0; i < Py_SIZE(self); i++) { - PyObject *obj = self->ob_item[i]; + for (Py_ssize_t i = 0; ; i++) { + PyObject *obj = list_get_item_ref(self, i); + if (obj == NULL) { + // out-of-bounds + break; + } if (obj == value) { count++; + Py_DECREF(obj); continue; } - Py_INCREF(obj); int cmp = PyObject_RichCompareBool(obj, value, Py_EQ); Py_DECREF(obj); if (cmp > 0) From 7fdd4235d790559372bbb1bf0c2384191a9bb5f3 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Tue, 6 Feb 2024 11:45:42 -0500 Subject: [PATCH 159/507] gh-112529: Stop the world around gc.get_referents (#114823) We do not want to add locking in `tp_traverse` slot implementations. Instead, stop the world when calling `gc.get_referents`. Note that the the stop the world call is a no-op in the default build. Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com> --- Modules/gcmodule.c | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 3b63dd7a9a83533..3a42654b41b2acb 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -230,6 +230,26 @@ referentsvisit(PyObject *obj, void *arg) return PyList_Append(list, obj) < 0; } +static int +append_referrents(PyObject *result, PyObject *args) +{ + for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(args); i++) { + PyObject *obj = PyTuple_GET_ITEM(args, i); + if (!_PyObject_IS_GC(obj)) { + continue; + } + + traverseproc traverse = Py_TYPE(obj)->tp_traverse; + if (!traverse) { + continue; + } + if (traverse(obj, referentsvisit, result)) { + return -1; + } + } + return 0; +} + /*[clinic input] gc.get_referents @@ -242,29 +262,24 @@ static PyObject * gc_get_referents_impl(PyObject *module, PyObject *args) /*[clinic end generated code: output=d47dc02cefd06fe8 input=b3ceab0c34038cbf]*/ { - Py_ssize_t i; if (PySys_Audit("gc.get_referents", "(O)", args) < 0) { return NULL; } + PyInterpreterState *interp = _PyInterpreterState_GET(); PyObject *result = PyList_New(0); if (result == NULL) return NULL; - for (i = 0; i < PyTuple_GET_SIZE(args); i++) { - traverseproc traverse; - PyObject *obj = PyTuple_GET_ITEM(args, i); + // NOTE: stop the world is a no-op in default build + _PyEval_StopTheWorld(interp); + int err = append_referrents(result, args); + _PyEval_StartTheWorld(interp); - if (!_PyObject_IS_GC(obj)) - continue; - traverse = Py_TYPE(obj)->tp_traverse; - if (! traverse) - continue; - if (traverse(obj, referentsvisit, result)) { - Py_DECREF(result); - return NULL; - } + if (err < 0) { + Py_CLEAR(result); } + return result; } From 76108b8b05040fc49a6bc50eb2e990576595c57c Mon Sep 17 00:00:00 2001 From: Matthieu Caneill <matthieucan@users.noreply.github.com> Date: Tue, 6 Feb 2024 19:44:12 +0100 Subject: [PATCH 160/507] #gh-75705: Set unixfrom envelope in mailbox._mboxMMDF (GH-107117) --- Lib/mailbox.py | 5 +++-- Lib/test/test_mailbox.py | 12 +++++++++++- .../2023-07-23-12-28-26.gh-issue-75705.aB2-Ww.rst | 1 + 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-07-23-12-28-26.gh-issue-75705.aB2-Ww.rst diff --git a/Lib/mailbox.py b/Lib/mailbox.py index 81ea210cf815a48..746811bd559412d 100644 --- a/Lib/mailbox.py +++ b/Lib/mailbox.py @@ -830,10 +830,11 @@ def get_message(self, key): """Return a Message representation or raise a KeyError.""" start, stop = self._lookup(key) self._file.seek(start) - from_line = self._file.readline().replace(linesep, b'') + from_line = self._file.readline().replace(linesep, b'').decode('ascii') string = self._file.read(stop - self._file.tell()) msg = self._message_factory(string.replace(linesep, b'\n')) - msg.set_from(from_line[5:].decode('ascii')) + msg.set_unixfrom(from_line) + msg.set_from(from_line[5:]) return msg def get_string(self, key, from_=False): diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py index d84faad0eb34069..c52c014185bec7a 100644 --- a/Lib/test/test_mailbox.py +++ b/Lib/test/test_mailbox.py @@ -1127,12 +1127,14 @@ def test_add_from_string(self): # Add a string starting with 'From ' to the mailbox key = self._box.add('From foo@bar blah\nFrom: foo\n\n0\n') self.assertEqual(self._box[key].get_from(), 'foo@bar blah') + self.assertEqual(self._box[key].get_unixfrom(), 'From foo@bar blah') self.assertEqual(self._box[key].get_payload(), '0\n') def test_add_from_bytes(self): # Add a byte string starting with 'From ' to the mailbox key = self._box.add(b'From foo@bar blah\nFrom: foo\n\n0\n') self.assertEqual(self._box[key].get_from(), 'foo@bar blah') + self.assertEqual(self._box[key].get_unixfrom(), 'From foo@bar blah') self.assertEqual(self._box[key].get_payload(), '0\n') def test_add_mbox_or_mmdf_message(self): @@ -1667,18 +1669,23 @@ def test_initialize_with_unixfrom(self): msg = mailbox.Message(_sample_message) msg.set_unixfrom('From foo@bar blah') msg = mailbox.mboxMessage(msg) - self.assertEqual(msg.get_from(), 'foo@bar blah', msg.get_from()) + self.assertEqual(msg.get_from(), 'foo@bar blah') + self.assertEqual(msg.get_unixfrom(), 'From foo@bar blah') def test_from(self): # Get and set "From " line msg = mailbox.mboxMessage(_sample_message) self._check_from(msg) + self.assertIsNone(msg.get_unixfrom()) msg.set_from('foo bar') self.assertEqual(msg.get_from(), 'foo bar') + self.assertIsNone(msg.get_unixfrom()) msg.set_from('foo@bar', True) self._check_from(msg, 'foo@bar') + self.assertIsNone(msg.get_unixfrom()) msg.set_from('blah@temp', time.localtime()) self._check_from(msg, 'blah@temp') + self.assertIsNone(msg.get_unixfrom()) def test_flags(self): # Use get_flags(), set_flags(), add_flag(), remove_flag() @@ -1866,6 +1873,7 @@ def test_maildir_to_mboxmmdf(self): self.assertEqual(msg.get_flags(), result) self.assertEqual(msg.get_from(), 'MAILER-DAEMON %s' % time.asctime(time.gmtime(0.0))) + self.assertIsNone(msg.get_unixfrom()) msg_maildir.set_subdir('cur') self.assertEqual(class_(msg_maildir).get_flags(), 'RODFA') @@ -1914,10 +1922,12 @@ def test_mboxmmdf_to_mboxmmdf(self): msg_mboxMMDF = class_(_sample_message) msg_mboxMMDF.set_flags('RODFA') msg_mboxMMDF.set_from('foo@bar') + self.assertIsNone(msg_mboxMMDF.get_unixfrom()) for class2_ in (mailbox.mboxMessage, mailbox.MMDFMessage): msg2 = class2_(msg_mboxMMDF) self.assertEqual(msg2.get_flags(), 'RODFA') self.assertEqual(msg2.get_from(), 'foo@bar') + self.assertIsNone(msg2.get_unixfrom()) def test_mboxmmdf_to_mh(self): # Convert mboxMessage and MMDFMessage to MHMessage diff --git a/Misc/NEWS.d/next/Library/2023-07-23-12-28-26.gh-issue-75705.aB2-Ww.rst b/Misc/NEWS.d/next/Library/2023-07-23-12-28-26.gh-issue-75705.aB2-Ww.rst new file mode 100644 index 000000000000000..272e31d64cfbd9b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-07-23-12-28-26.gh-issue-75705.aB2-Ww.rst @@ -0,0 +1 @@ +Set unixfrom envelope in :class:`mailbox.mbox` and :class:`mailbox.MMDF`. From 71239d50b54c90afd3fdde260848e0c6d73a5c27 Mon Sep 17 00:00:00 2001 From: Artem Mukhin <artem.m.mukhin@gmail.com> Date: Tue, 6 Feb 2024 20:32:07 +0100 Subject: [PATCH 161/507] gh-103224: Resolve paths properly in test_sysconfig (GH-103292) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To pass tests when executed through a Python symlink. Co-authored-by: Miro Hrončok <miro@hroncok.cz> --- Lib/test/test_sysconfig.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index be609a0abd29c87..bb87bf00dc2d1a7 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -154,17 +154,21 @@ def test_posix_venv_scheme(self): 'python%d.%d' % sys.version_info[:2], 'site-packages') - # Resolve the paths in prefix - binpath = os.path.join(sys.prefix, binpath) - incpath = os.path.join(sys.prefix, incpath) - libpath = os.path.join(sys.prefix, libpath) + # Resolve the paths in an imaginary venv/ directory + binpath = os.path.join('venv', binpath) + incpath = os.path.join('venv', incpath) + libpath = os.path.join('venv', libpath) - self.assertEqual(binpath, sysconfig.get_path('scripts', scheme='posix_venv')) - self.assertEqual(libpath, sysconfig.get_path('purelib', scheme='posix_venv')) + # Mimic the venv module, set all bases to the venv directory + bases = ('base', 'platbase', 'installed_base', 'installed_platbase') + vars = {base: 'venv' for base in bases} + + self.assertEqual(binpath, sysconfig.get_path('scripts', scheme='posix_venv', vars=vars)) + self.assertEqual(libpath, sysconfig.get_path('purelib', scheme='posix_venv', vars=vars)) # The include directory on POSIX isn't exactly the same as before, # but it is "within" - sysconfig_includedir = sysconfig.get_path('include', scheme='posix_venv') + sysconfig_includedir = sysconfig.get_path('include', scheme='posix_venv', vars=vars) self.assertTrue(sysconfig_includedir.startswith(incpath + os.sep)) def test_nt_venv_scheme(self): @@ -174,14 +178,19 @@ def test_nt_venv_scheme(self): incpath = 'Include' libpath = os.path.join('Lib', 'site-packages') - # Resolve the paths in prefix - binpath = os.path.join(sys.prefix, binpath) - incpath = os.path.join(sys.prefix, incpath) - libpath = os.path.join(sys.prefix, libpath) + # Resolve the paths in an imaginary venv\ directory + venv = 'venv' + binpath = os.path.join(venv, binpath) + incpath = os.path.join(venv, incpath) + libpath = os.path.join(venv, libpath) + + # Mimic the venv module, set all bases to the venv directory + bases = ('base', 'platbase', 'installed_base', 'installed_platbase') + vars = {base: 'venv' for base in bases} - self.assertEqual(binpath, sysconfig.get_path('scripts', scheme='nt_venv')) - self.assertEqual(incpath, sysconfig.get_path('include', scheme='nt_venv')) - self.assertEqual(libpath, sysconfig.get_path('purelib', scheme='nt_venv')) + self.assertEqual(binpath, sysconfig.get_path('scripts', scheme='nt_venv', vars=vars)) + self.assertEqual(incpath, sysconfig.get_path('include', scheme='nt_venv', vars=vars)) + self.assertEqual(libpath, sysconfig.get_path('purelib', scheme='nt_venv', vars=vars)) def test_venv_scheme(self): if sys.platform == 'win32': From b6228b521b4692b2de1c1c12f4aa5623f8319084 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Tue, 6 Feb 2024 14:45:04 -0500 Subject: [PATCH 162/507] gh-115035: Mark ThreadHandles as non-joinable earlier after forking (#115042) This marks dead ThreadHandles as non-joinable earlier in `PyOS_AfterFork_Child()` before we execute any Python code. The handles are stored in a global linked list in `_PyRuntimeState` because `fork()` affects the entire process. --- Include/internal/pycore_pythread.h | 15 +++++--- Include/internal/pycore_runtime_init.h | 2 + Lib/threading.py | 5 +-- Modules/_threadmodule.c | 53 +++++++++++++++++--------- Python/pystate.c | 2 + Python/thread_nt.h | 4 -- Python/thread_pthread.h | 10 ----- 7 files changed, 50 insertions(+), 41 deletions(-) diff --git a/Include/internal/pycore_pythread.h b/Include/internal/pycore_pythread.h index 9c9a09f60f34418..265299d7574838b 100644 --- a/Include/internal/pycore_pythread.h +++ b/Include/internal/pycore_pythread.h @@ -9,6 +9,7 @@ extern "C" { #endif #include "dynamic_annotations.h" // _Py_ANNOTATE_PURE_HAPPENS_BEFORE_MUTEX +#include "pycore_llist.h" // struct llist_node // Get _POSIX_THREADS and _POSIX_SEMAPHORES macros if available #if (defined(HAVE_UNISTD_H) && !defined(_POSIX_THREADS) \ @@ -75,14 +76,22 @@ struct _pythread_runtime_state { struct py_stub_tls_entry tls_entries[PTHREAD_KEYS_MAX]; } stubs; #endif + + // Linked list of ThreadHandleObjects + struct llist_node handles; }; +#define _pythread_RUNTIME_INIT(pythread) \ + { \ + .handles = LLIST_INIT(pythread.handles), \ + } #ifdef HAVE_FORK /* Private function to reinitialize a lock at fork in the child process. Reset the lock to the unlocked state. Return 0 on success, return -1 on error. */ extern int _PyThread_at_fork_reinit(PyThread_type_lock *lock); +extern void _PyThread_AfterFork(struct _pythread_runtime_state *state); #endif /* HAVE_FORK */ @@ -143,12 +152,6 @@ PyAPI_FUNC(int) PyThread_join_thread(PyThread_handle_t); */ PyAPI_FUNC(int) PyThread_detach_thread(PyThread_handle_t); -/* - * Obtain the new thread ident and handle in a forked child process. - */ -PyAPI_FUNC(void) PyThread_update_thread_after_fork(PyThread_ident_t* ident, - PyThread_handle_t* handle); - #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index 4370ad05bdc0584..2ad1347ad48a596 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -16,6 +16,7 @@ extern "C" { #include "pycore_parser.h" // _parser_runtime_state_INIT #include "pycore_pyhash.h" // pyhash_state_INIT #include "pycore_pymem_init.h" // _pymem_allocators_standard_INIT +#include "pycore_pythread.h" // _pythread_RUNTIME_INIT #include "pycore_runtime_init_generated.h" // _Py_bytes_characters_INIT #include "pycore_signal.h" // _signals_RUNTIME_INIT #include "pycore_tracemalloc.h" // _tracemalloc_runtime_state_INIT @@ -90,6 +91,7 @@ extern PyTypeObject _PyExc_MemoryError; }, \ .obmalloc = _obmalloc_global_state_INIT, \ .pyhash_state = pyhash_state_INIT, \ + .threads = _pythread_RUNTIME_INIT(runtime.threads), \ .signals = _signals_RUNTIME_INIT, \ .interpreters = { \ /* This prevents interpreters from getting created \ diff --git a/Lib/threading.py b/Lib/threading.py index 75a08e5aac97d60..b6ff00acadd58fe 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -949,7 +949,6 @@ def _after_fork(self, new_ident=None): # This thread is alive. self._ident = new_ident if self._handle is not None: - self._handle.after_fork_alive() assert self._handle.ident == new_ident # bpo-42350: If the fork happens when the thread is already stopped # (ex: after threading._shutdown() has been called), _tstate_lock @@ -965,9 +964,7 @@ def _after_fork(self, new_ident=None): self._is_stopped = True self._tstate_lock = None self._join_lock = None - if self._handle is not None: - self._handle.after_fork_dead() - self._handle = None + self._handle = None def __repr__(self): assert self._initialized, "Thread.__init__() was not called" diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 5cceb84658deb78..df02b023012fbde 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -44,6 +44,7 @@ get_thread_state(PyObject *module) typedef struct { PyObject_HEAD + struct llist_node node; // linked list node (see _pythread_runtime_state) PyThread_ident_t ident; PyThread_handle_t handle; char joinable; @@ -59,6 +60,11 @@ new_thread_handle(thread_module_state* state) self->ident = 0; self->handle = 0; self->joinable = 0; + + HEAD_LOCK(&_PyRuntime); + llist_insert_tail(&_PyRuntime.threads.handles, &self->node); + HEAD_UNLOCK(&_PyRuntime); + return self; } @@ -66,6 +72,14 @@ static void ThreadHandle_dealloc(ThreadHandleObject *self) { PyObject *tp = (PyObject *) Py_TYPE(self); + + // Remove ourself from the global list of handles + HEAD_LOCK(&_PyRuntime); + if (self->node.next != NULL) { + llist_remove(&self->node); + } + HEAD_UNLOCK(&_PyRuntime); + if (self->joinable) { int ret = PyThread_detach_thread(self->handle); if (ret) { @@ -77,6 +91,28 @@ ThreadHandle_dealloc(ThreadHandleObject *self) Py_DECREF(tp); } +void +_PyThread_AfterFork(struct _pythread_runtime_state *state) +{ + // gh-115035: We mark ThreadHandles as not joinable early in the child's + // after-fork handler. We do this before calling any Python code to ensure + // that it happens before any ThreadHandles are deallocated, such as by a + // GC cycle. + PyThread_ident_t current = PyThread_get_thread_ident_ex(); + + struct llist_node *node; + llist_for_each_safe(node, &state->handles) { + ThreadHandleObject *hobj = llist_data(node, ThreadHandleObject, node); + if (hobj->ident == current) { + continue; + } + + // Disallow calls to detach() and join() as they could crash. + hobj->joinable = 0; + llist_remove(node); + } +} + static PyObject * ThreadHandle_repr(ThreadHandleObject *self) { @@ -91,21 +127,6 @@ ThreadHandle_get_ident(ThreadHandleObject *self, void *ignored) } -static PyObject * -ThreadHandle_after_fork_alive(ThreadHandleObject *self, void* ignored) -{ - PyThread_update_thread_after_fork(&self->ident, &self->handle); - Py_RETURN_NONE; -} - -static PyObject * -ThreadHandle_after_fork_dead(ThreadHandleObject *self, void* ignored) -{ - // Disallow calls to detach() and join() as they could crash. - self->joinable = 0; - Py_RETURN_NONE; -} - static PyObject * ThreadHandle_detach(ThreadHandleObject *self, void* ignored) { @@ -157,8 +178,6 @@ static PyGetSetDef ThreadHandle_getsetlist[] = { static PyMethodDef ThreadHandle_methods[] = { - {"after_fork_alive", (PyCFunction)ThreadHandle_after_fork_alive, METH_NOARGS}, - {"after_fork_dead", (PyCFunction)ThreadHandle_after_fork_dead, METH_NOARGS}, {"detach", (PyCFunction)ThreadHandle_detach, METH_NOARGS}, {"join", (PyCFunction)ThreadHandle_join, METH_NOARGS}, {0, 0} diff --git a/Python/pystate.c b/Python/pystate.c index 7836c172bbfb618..e77e5bfa7e2df86 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -517,6 +517,8 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime) return _PyStatus_NO_MEMORY(); } + _PyThread_AfterFork(&runtime->threads); + return _PyStatus_OK(); } #endif diff --git a/Python/thread_nt.h b/Python/thread_nt.h index 044e9fa111e9797..ad467e0e7840e7b 100644 --- a/Python/thread_nt.h +++ b/Python/thread_nt.h @@ -242,10 +242,6 @@ PyThread_detach_thread(PyThread_handle_t handle) { return (CloseHandle(hThread) == 0); } -void -PyThread_update_thread_after_fork(PyThread_ident_t* ident, PyThread_handle_t* handle) { -} - /* * Return the thread Id instead of a handle. The Id is said to uniquely identify the * thread in the system diff --git a/Python/thread_pthread.h b/Python/thread_pthread.h index fb3b79fc160502f..556e3de0b071f81 100644 --- a/Python/thread_pthread.h +++ b/Python/thread_pthread.h @@ -339,16 +339,6 @@ PyThread_detach_thread(PyThread_handle_t th) { return pthread_detach((pthread_t) th); } -void -PyThread_update_thread_after_fork(PyThread_ident_t* ident, PyThread_handle_t* handle) { - // The thread id might have been updated in the forked child - pthread_t th = pthread_self(); - *ident = (PyThread_ident_t) th; - *handle = (PyThread_handle_t) th; - assert(th == (pthread_t) *ident); - assert(th == (pthread_t) *handle); -} - /* XXX This implementation is considered (to quote Tim Peters) "inherently hosed" because: - It does not guarantee the promise that a non-zero integer is returned. From 92abb0124037e5bc938fa870461a26f64c56095b Mon Sep 17 00:00:00 2001 From: Dino Viehland <dinoviehland@meta.com> Date: Tue, 6 Feb 2024 14:03:43 -0800 Subject: [PATCH 163/507] gh-112075: Add critical sections for most dict APIs (#114508) Starts adding thread safety to dict objects. Use @critical_section for APIs which are exposed via argument clinic and don't directly correlate with a public C API which needs to acquire the lock Use a _lock_held suffix for keeping changes to complicated functions simple and just wrapping them with a critical section Acquire and release the lock in an existing function where it won't be overly disruptive to the existing logic --- Include/internal/pycore_critical_section.h | 46 ++ Modules/_sre/sre.c | 27 +- Objects/clinic/dictobject.c.h | 30 +- Objects/dictobject.c | 866 +++++++++++++++------ Objects/odictobject.c | 19 +- Objects/setobject.c | 78 +- 6 files changed, 782 insertions(+), 284 deletions(-) diff --git a/Include/internal/pycore_critical_section.h b/Include/internal/pycore_critical_section.h index bf2bbfffc38bd0f..38ed8cd69804ba9 100644 --- a/Include/internal/pycore_critical_section.h +++ b/Include/internal/pycore_critical_section.h @@ -104,12 +104,37 @@ extern "C" { # define Py_END_CRITICAL_SECTION2() \ _PyCriticalSection2_End(&_cs2); \ } + +// Asserts that the mutex is locked. The mutex must be held by the +// top-most critical section otherwise there's the possibility +// that the mutex would be swalled out in some code paths. +#define _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(mutex) \ + _PyCriticalSection_AssertHeld(mutex) + +// Asserts that the mutex for the given object is locked. The mutex must +// be held by the top-most critical section otherwise there's the +// possibility that the mutex would be swalled out in some code paths. +#ifdef Py_DEBUG + +#define _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op) \ + if (Py_REFCNT(op) != 1) { \ + _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(&_PyObject_CAST(op)->ob_mutex); \ + } + +#else /* Py_DEBUG */ + +#define _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op) + +#endif /* Py_DEBUG */ + #else /* !Py_GIL_DISABLED */ // The critical section APIs are no-ops with the GIL. # define Py_BEGIN_CRITICAL_SECTION(op) # define Py_END_CRITICAL_SECTION() # define Py_BEGIN_CRITICAL_SECTION2(a, b) # define Py_END_CRITICAL_SECTION2() +# define _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(mutex) +# define _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op) #endif /* !Py_GIL_DISABLED */ typedef struct { @@ -236,6 +261,27 @@ _PyCriticalSection2_End(_PyCriticalSection2 *c) PyAPI_FUNC(void) _PyCriticalSection_SuspendAll(PyThreadState *tstate); +#ifdef Py_GIL_DISABLED + +static inline void +_PyCriticalSection_AssertHeld(PyMutex *mutex) { +#ifdef Py_DEBUG + PyThreadState *tstate = _PyThreadState_GET(); + uintptr_t prev = tstate->critical_section; + if (prev & _Py_CRITICAL_SECTION_TWO_MUTEXES) { + _PyCriticalSection2 *cs = (_PyCriticalSection2 *)(prev & ~_Py_CRITICAL_SECTION_MASK); + assert(cs != NULL && (cs->base.mutex == mutex || cs->mutex2 == mutex)); + } + else { + _PyCriticalSection *cs = (_PyCriticalSection *)(tstate->critical_section & ~_Py_CRITICAL_SECTION_MASK); + assert(cs != NULL && cs->mutex == mutex); + } + +#endif +} + +#endif + #ifdef __cplusplus } #endif diff --git a/Modules/_sre/sre.c b/Modules/_sre/sre.c index d451974b9cf81e6..00fbd9674b8cdda 100644 --- a/Modules/_sre/sre.c +++ b/Modules/_sre/sre.c @@ -39,13 +39,14 @@ static const char copyright[] = " SRE 2.2.2 Copyright (c) 1997-2002 by Secret Labs AB "; #include "Python.h" -#include "pycore_dict.h" // _PyDict_Next() -#include "pycore_long.h" // _PyLong_GetZero() -#include "pycore_moduleobject.h" // _PyModule_GetState() +#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION +#include "pycore_dict.h" // _PyDict_Next() +#include "pycore_long.h" // _PyLong_GetZero() +#include "pycore_moduleobject.h" // _PyModule_GetState() -#include "sre.h" // SRE_CODE +#include "sre.h" // SRE_CODE -#include <ctype.h> // tolower(), toupper(), isalnum() +#include <ctype.h> // tolower(), toupper(), isalnum() #define SRE_CODE_BITS (8 * sizeof(SRE_CODE)) @@ -2349,26 +2350,28 @@ _sre_SRE_Match_groupdict_impl(MatchObject *self, PyObject *default_value) if (!result || !self->pattern->groupindex) return result; + Py_BEGIN_CRITICAL_SECTION(self->pattern->groupindex); while (_PyDict_Next(self->pattern->groupindex, &pos, &key, &value, &hash)) { int status; Py_INCREF(key); value = match_getslice(self, key, default_value); if (!value) { Py_DECREF(key); - goto failed; + Py_CLEAR(result); + goto exit; } status = _PyDict_SetItem_KnownHash(result, key, value, hash); Py_DECREF(value); Py_DECREF(key); - if (status < 0) - goto failed; + if (status < 0) { + Py_CLEAR(result); + goto exit; + } } +exit: + Py_END_CRITICAL_SECTION(); return result; - -failed: - Py_DECREF(result); - return NULL; } /*[clinic input] diff --git a/Objects/clinic/dictobject.c.h b/Objects/clinic/dictobject.c.h index 8f532f454156dec..daaef211b1db494 100644 --- a/Objects/clinic/dictobject.c.h +++ b/Objects/clinic/dictobject.c.h @@ -2,6 +2,7 @@ preserve [clinic start generated code]*/ +#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION() #include "pycore_modsupport.h" // _PyArg_CheckPositional() PyDoc_STRVAR(dict_fromkeys__doc__, @@ -65,6 +66,21 @@ PyDoc_STRVAR(dict___contains____doc__, #define DICT___CONTAINS___METHODDEF \ {"__contains__", (PyCFunction)dict___contains__, METH_O|METH_COEXIST, dict___contains____doc__}, +static PyObject * +dict___contains___impl(PyDictObject *self, PyObject *key); + +static PyObject * +dict___contains__(PyDictObject *self, PyObject *key) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = dict___contains___impl(self, key); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(dict_get__doc__, "get($self, key, default=None, /)\n" "--\n" @@ -93,7 +109,9 @@ dict_get(PyDictObject *self, PyObject *const *args, Py_ssize_t nargs) } default_value = args[1]; skip_optional: + Py_BEGIN_CRITICAL_SECTION(self); return_value = dict_get_impl(self, key, default_value); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -130,7 +148,9 @@ dict_setdefault(PyDictObject *self, PyObject *const *args, Py_ssize_t nargs) } default_value = args[1]; skip_optional: + Py_BEGIN_CRITICAL_SECTION(self); return_value = dict_setdefault_impl(self, key, default_value); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -209,7 +229,13 @@ dict_popitem_impl(PyDictObject *self); static PyObject * dict_popitem(PyDictObject *self, PyObject *Py_UNUSED(ignored)) { - return dict_popitem_impl(self); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = dict_popitem_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; } PyDoc_STRVAR(dict___sizeof____doc__, @@ -301,4 +327,4 @@ dict_values(PyDictObject *self, PyObject *Py_UNUSED(ignored)) { return dict_values_impl(self); } -/*[clinic end generated code: output=f3ac47dfbf341b23 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c8fda06bac5b05f3 input=a9049054013a1b77]*/ diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 11b388d9f4adb05..2df95e977a180fa 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -113,18 +113,19 @@ As a consequence of this, split keys have a maximum size of 16. #define PyDict_MINSIZE 8 #include "Python.h" -#include "pycore_bitutils.h" // _Py_bit_length -#include "pycore_call.h" // _PyObject_CallNoArgs() -#include "pycore_ceval.h" // _PyEval_GetBuiltin() -#include "pycore_code.h" // stats -#include "pycore_dict.h" // export _PyDict_SizeOf() -#include "pycore_freelist.h" // _PyFreeListState_GET() -#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED() -#include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats() -#include "pycore_pyerrors.h" // _PyErr_GetRaisedException() -#include "pycore_pystate.h" // _PyThreadState_GET() -#include "pycore_setobject.h" // _PySet_NextEntry() -#include "stringlib/eq.h" // unicode_eq() +#include "pycore_bitutils.h" // _Py_bit_length +#include "pycore_call.h" // _PyObject_CallNoArgs() +#include "pycore_ceval.h" // _PyEval_GetBuiltin() +#include "pycore_code.h" // stats +#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION, Py_END_CRITICAL_SECTION +#include "pycore_dict.h" // export _PyDict_SizeOf() +#include "pycore_freelist.h" // _PyFreeListState_GET() +#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED() +#include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats() +#include "pycore_pyerrors.h" // _PyErr_GetRaisedException() +#include "pycore_pystate.h" // _PyThreadState_GET() +#include "pycore_setobject.h" // _PySet_NextEntry() +#include "stringlib/eq.h" // unicode_eq() #include <stdbool.h> @@ -141,6 +142,21 @@ To avoid slowing down lookups on a near-full table, we resize the table when it's USABLE_FRACTION (currently two-thirds) full. */ +#ifdef Py_GIL_DISABLED + +static inline void +ASSERT_DICT_LOCKED(PyObject *op) +{ + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op); +} +#define ASSERT_DICT_LOCKED(op) ASSERT_DICT_LOCKED(_Py_CAST(PyObject*, op)) + +#else + +#define ASSERT_DICT_LOCKED(op) + +#endif + #define PERTURB_SHIFT 5 /* @@ -240,6 +256,16 @@ static int dictresize(PyInterpreterState *interp, PyDictObject *mp, static PyObject* dict_iter(PyObject *dict); +static int +contains_lock_held(PyDictObject *mp, PyObject *key); +static int +contains_known_hash_lock_held(PyDictObject *mp, PyObject *key, Py_ssize_t hash); +static int +setitem_lock_held(PyDictObject *mp, PyObject *key, PyObject *value); +static int +dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_value, + PyObject **result, int incref_result); + #include "clinic/dictobject.c.h" @@ -789,6 +815,8 @@ clone_combined_dict_keys(PyDictObject *orig) assert(orig->ma_keys != Py_EMPTY_KEYS); assert(orig->ma_keys->dk_refcnt == 1); + ASSERT_DICT_LOCKED(orig); + size_t keys_size = _PyDict_KeysSize(orig->ma_keys); PyDictKeysObject *keys = PyMem_Malloc(keys_size); if (keys == NULL) { @@ -1230,6 +1258,8 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp, { PyObject *old_value; + ASSERT_DICT_LOCKED(mp); + if (DK_IS_UNICODE(mp->ma_keys) && !PyUnicode_CheckExact(key)) { if (insertion_resize(interp, mp, 0) < 0) goto Fail; @@ -1326,6 +1356,7 @@ insert_to_emptydict(PyInterpreterState *interp, PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value) { assert(mp->ma_keys == Py_EMPTY_KEYS); + ASSERT_DICT_LOCKED(mp); uint64_t new_version = _PyDict_NotifyEvent( interp, PyDict_EVENT_ADDED, mp, key, value); @@ -1419,6 +1450,8 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp, PyDictKeysObject *oldkeys; PyDictValues *oldvalues; + ASSERT_DICT_LOCKED(mp); + if (log2_newsize >= SIZEOF_SIZE_T*8) { PyErr_NoMemory(); return -1; @@ -1613,7 +1646,7 @@ _PyDict_FromItems(PyObject *const *keys, Py_ssize_t keys_offset, for (Py_ssize_t i = 0; i < length; i++) { PyObject *key = *ks; PyObject *value = *vs; - if (PyDict_SetItem(dict, key, value) < 0) { + if (setitem_lock_held((PyDictObject *)dict, key, value) < 0) { Py_DECREF(dict); return NULL; } @@ -1688,6 +1721,7 @@ PyDict_GetItem(PyObject *op, PyObject *key) Py_ssize_t _PyDict_LookupIndex(PyDictObject *mp, PyObject *key) { + // TODO: Thread safety PyObject *value; assert(PyDict_CheckExact((PyObject*)mp)); assert(PyUnicode_CheckExact(key)); @@ -1864,9 +1898,11 @@ _PyDict_LoadGlobal(PyDictObject *globals, PyDictObject *builtins, PyObject *key) } /* Consumes references to key and value */ -int -_PyDict_SetItem_Take2(PyDictObject *mp, PyObject *key, PyObject *value) +static int +setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value) { + ASSERT_DICT_LOCKED(mp); + assert(key); assert(value); assert(PyDict_Check(mp)); @@ -1879,7 +1915,9 @@ _PyDict_SetItem_Take2(PyDictObject *mp, PyObject *key, PyObject *value) return -1; } } + PyInterpreterState *interp = _PyInterpreterState_GET(); + if (mp->ma_keys == Py_EMPTY_KEYS) { return insert_to_emptydict(interp, mp, key, hash, value); } @@ -1887,6 +1925,16 @@ _PyDict_SetItem_Take2(PyDictObject *mp, PyObject *key, PyObject *value) return insertdict(interp, mp, key, hash, value); } +int +_PyDict_SetItem_Take2(PyDictObject *mp, PyObject *key, PyObject *value) +{ + int res; + Py_BEGIN_CRITICAL_SECTION(mp); + res = setitem_take2_lock_held(mp, key, value); + Py_END_CRITICAL_SECTION(); + return res; +} + /* CAUTION: PyDict_SetItem() must guarantee that it won't resize the * dictionary if it's merely replacing the value for an existing key. * This means that it's safe to loop over a dictionary with PyDict_Next() @@ -1906,6 +1954,16 @@ PyDict_SetItem(PyObject *op, PyObject *key, PyObject *value) Py_NewRef(key), Py_NewRef(value)); } +static int +setitem_lock_held(PyDictObject *mp, PyObject *key, PyObject *value) +{ + assert(key); + assert(value); + return setitem_take2_lock_held(mp, + Py_NewRef(key), Py_NewRef(value)); +} + + int _PyDict_SetItem_KnownHash(PyObject *op, PyObject *key, PyObject *value, Py_hash_t hash) @@ -1921,12 +1979,21 @@ _PyDict_SetItem_KnownHash(PyObject *op, PyObject *key, PyObject *value, assert(hash != -1); mp = (PyDictObject *)op; + int res; PyInterpreterState *interp = _PyInterpreterState_GET(); + + Py_BEGIN_CRITICAL_SECTION(mp); + if (mp->ma_keys == Py_EMPTY_KEYS) { - return insert_to_emptydict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value)); + res = insert_to_emptydict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value)); } - /* insertdict() handles any resizing that might be necessary */ - return insertdict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value)); + else { + /* insertdict() handles any resizing that might be necessary */ + res = insertdict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value)); + } + + Py_END_CRITICAL_SECTION(); + return res; } static void @@ -1951,6 +2018,8 @@ delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix, { PyObject *old_key; + ASSERT_DICT_LOCKED(mp); + Py_ssize_t hashpos = lookdict_index(mp->ma_keys, hash, ix); assert(hashpos >= 0); @@ -2002,8 +2071,8 @@ PyDict_DelItem(PyObject *op, PyObject *key) return _PyDict_DelItem_KnownHash(op, key, hash); } -int -_PyDict_DelItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash) +static int +delitem_knownhash_lock_held(PyObject *op, PyObject *key, Py_hash_t hash) { Py_ssize_t ix; PyDictObject *mp; @@ -2013,6 +2082,9 @@ _PyDict_DelItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash) PyErr_BadInternalCall(); return -1; } + + ASSERT_DICT_LOCKED(op); + assert(key); assert(hash != -1); mp = (PyDictObject *)op; @@ -2030,13 +2102,19 @@ _PyDict_DelItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash) return delitem_common(mp, hash, ix, old_value, new_version); } -/* This function promises that the predicate -> deletion sequence is atomic - * (i.e. protected by the GIL), assuming the predicate itself doesn't - * release the GIL. - */ int -_PyDict_DelItemIf(PyObject *op, PyObject *key, - int (*predicate)(PyObject *value)) +_PyDict_DelItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash) +{ + int res; + Py_BEGIN_CRITICAL_SECTION(op); + res = delitem_knownhash_lock_held(op, key, hash); + Py_END_CRITICAL_SECTION(); + return res; +} + +static int +delitemif_lock_held(PyObject *op, PyObject *key, + int (*predicate)(PyObject *value)) { Py_ssize_t hashpos, ix; PyDictObject *mp; @@ -2044,6 +2122,8 @@ _PyDict_DelItemIf(PyObject *op, PyObject *key, PyObject *old_value; int res; + ASSERT_DICT_LOCKED(op); + if (!PyDict_Check(op)) { PyErr_BadInternalCall(); return -1; @@ -2077,16 +2157,32 @@ _PyDict_DelItemIf(PyObject *op, PyObject *key, return 0; } } +/* This function promises that the predicate -> deletion sequence is atomic + * (i.e. protected by the GIL or the per-dict mutex in free threaded builds), + * assuming the predicate itself doesn't release the GIL (or cause re-entrancy + * which would release the per-dict mutex) + */ +int +_PyDict_DelItemIf(PyObject *op, PyObject *key, + int (*predicate)(PyObject *value)) +{ + int res; + Py_BEGIN_CRITICAL_SECTION(op); + res = delitemif_lock_held(op, key, predicate); + Py_END_CRITICAL_SECTION(); + return res; +} - -void -PyDict_Clear(PyObject *op) +static void +clear_lock_held(PyObject *op) { PyDictObject *mp; PyDictKeysObject *oldkeys; PyDictValues *oldvalues; Py_ssize_t i, n; + ASSERT_DICT_LOCKED(op); + if (!PyDict_Check(op)) return; mp = ((PyDictObject *)op); @@ -2119,6 +2215,14 @@ PyDict_Clear(PyObject *op) ASSERT_CONSISTENT(mp); } +void +PyDict_Clear(PyObject *op) +{ + Py_BEGIN_CRITICAL_SECTION(op); + clear_lock_held(op); + Py_END_CRITICAL_SECTION(); +} + /* Internal version of PyDict_Next that returns a hash value in addition * to the key and value. * Return 1 on success, return 0 when the reached the end of the dictionary @@ -2135,6 +2239,9 @@ _PyDict_Next(PyObject *op, Py_ssize_t *ppos, PyObject **pkey, if (!PyDict_Check(op)) return 0; + + ASSERT_DICT_LOCKED(op); + mp = (PyDictObject *)op; i = *ppos; if (mp->ma_values) { @@ -2208,7 +2315,11 @@ _PyDict_Next(PyObject *op, Py_ssize_t *ppos, PyObject **pkey, int PyDict_Next(PyObject *op, Py_ssize_t *ppos, PyObject **pkey, PyObject **pvalue) { - return _PyDict_Next(op, ppos, pkey, pvalue, NULL); + int res; + Py_BEGIN_CRITICAL_SECTION(op); + res = _PyDict_Next(op, ppos, pkey, pvalue, NULL); + Py_END_CRITICAL_SECTION(); + return res; } @@ -2219,6 +2330,8 @@ _PyDict_Pop_KnownHash(PyDictObject *mp, PyObject *key, Py_hash_t hash, { assert(PyDict_Check(mp)); + ASSERT_DICT_LOCKED(mp); + if (mp->ma_used == 0) { if (result) { *result = NULL; @@ -2258,10 +2371,11 @@ _PyDict_Pop_KnownHash(PyDictObject *mp, PyObject *key, Py_hash_t hash, return 1; } - -int -PyDict_Pop(PyObject *op, PyObject *key, PyObject **result) +static int +pop_lock_held(PyObject *op, PyObject *key, PyObject **result) { + ASSERT_DICT_LOCKED(op); + if (!PyDict_Check(op)) { if (result) { *result = NULL; @@ -2291,6 +2405,17 @@ PyDict_Pop(PyObject *op, PyObject *key, PyObject **result) return _PyDict_Pop_KnownHash(dict, key, hash, result); } +int +PyDict_Pop(PyObject *op, PyObject *key, PyObject **result) +{ + int err; + Py_BEGIN_CRITICAL_SECTION(op); + err = pop_lock_held(op, key, result); + Py_END_CRITICAL_SECTION(); + + return err; +} + int PyDict_PopString(PyObject *op, const char *key, PyObject **result) @@ -2323,6 +2448,55 @@ _PyDict_Pop(PyObject *dict, PyObject *key, PyObject *default_value) return result; } +static PyDictObject * +dict_dict_fromkeys(PyInterpreterState *interp, PyDictObject *mp, + PyObject *iterable, PyObject *value) +{ + PyObject *oldvalue; + Py_ssize_t pos = 0; + PyObject *key; + Py_hash_t hash; + int unicode = DK_IS_UNICODE(((PyDictObject*)iterable)->ma_keys); + uint8_t new_size = Py_MAX( + estimate_log2_keysize(PyDict_GET_SIZE(iterable)), + DK_LOG_SIZE(mp->ma_keys)); + if (dictresize(interp, mp, new_size, unicode)) { + Py_DECREF(mp); + return NULL; + } + + while (_PyDict_Next(iterable, &pos, &key, &oldvalue, &hash)) { + if (insertdict(interp, mp, + Py_NewRef(key), hash, Py_NewRef(value))) { + Py_DECREF(mp); + return NULL; + } + } + return mp; +} + +static PyDictObject * +dict_set_fromkeys(PyInterpreterState *interp, PyDictObject *mp, + PyObject *iterable, PyObject *value) +{ + Py_ssize_t pos = 0; + PyObject *key; + Py_hash_t hash; + + if (dictresize(interp, mp, + estimate_log2_keysize(PySet_GET_SIZE(iterable)), 0)) { + Py_DECREF(mp); + return NULL; + } + + while (_PySet_NextEntry(iterable, &pos, &key, &hash)) { + if (insertdict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value))) { + Py_DECREF(mp); + return NULL; + } + } + return mp; +} /* Internal version of dict.from_keys(). It is subclass-friendly. */ PyObject * @@ -2338,49 +2512,22 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value) if (d == NULL) return NULL; - if (PyDict_CheckExact(d) && ((PyDictObject *)d)->ma_used == 0) { + + if (PyDict_CheckExact(d)) { if (PyDict_CheckExact(iterable)) { PyDictObject *mp = (PyDictObject *)d; - PyObject *oldvalue; - Py_ssize_t pos = 0; - PyObject *key; - Py_hash_t hash; - - int unicode = DK_IS_UNICODE(((PyDictObject*)iterable)->ma_keys); - if (dictresize(interp, mp, - estimate_log2_keysize(PyDict_GET_SIZE(iterable)), - unicode)) { - Py_DECREF(d); - return NULL; - } - while (_PyDict_Next(iterable, &pos, &key, &oldvalue, &hash)) { - if (insertdict(interp, mp, - Py_NewRef(key), hash, Py_NewRef(value))) { - Py_DECREF(d); - return NULL; - } - } + Py_BEGIN_CRITICAL_SECTION2(d, iterable); + d = (PyObject *)dict_dict_fromkeys(interp, mp, iterable, value); + Py_END_CRITICAL_SECTION2(); return d; } - if (PyAnySet_CheckExact(iterable)) { + else if (PyAnySet_CheckExact(iterable)) { PyDictObject *mp = (PyDictObject *)d; - Py_ssize_t pos = 0; - PyObject *key; - Py_hash_t hash; - - if (dictresize(interp, mp, - estimate_log2_keysize(PySet_GET_SIZE(iterable)), 0)) { - Py_DECREF(d); - return NULL; - } - while (_PySet_NextEntry(iterable, &pos, &key, &hash)) { - if (insertdict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value))) { - Py_DECREF(d); - return NULL; - } - } + Py_BEGIN_CRITICAL_SECTION2(d, iterable); + d = (PyObject *)dict_set_fromkeys(interp, mp, iterable, value); + Py_END_CRITICAL_SECTION2(); return d; } } @@ -2392,12 +2539,17 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value) } if (PyDict_CheckExact(d)) { + Py_BEGIN_CRITICAL_SECTION(d); while ((key = PyIter_Next(it)) != NULL) { - status = PyDict_SetItem(d, key, value); + status = setitem_lock_held((PyDictObject *)d, key, value); Py_DECREF(key); - if (status < 0) - goto Fail; + if (status < 0) { + assert(PyErr_Occurred()); + goto dict_iter_exit; + } } +dict_iter_exit: + Py_END_CRITICAL_SECTION(); } else { while ((key = PyIter_Next(it)) != NULL) { status = PyObject_SetItem(d, key, value); @@ -2468,7 +2620,7 @@ dict_dealloc(PyObject *self) static PyObject * -dict_repr(PyObject *self) +dict_repr_lock_held(PyObject *self) { PyDictObject *mp = (PyDictObject *)self; Py_ssize_t i; @@ -2498,7 +2650,7 @@ dict_repr(PyObject *self) Note that repr may mutate the dict. */ i = 0; first = 1; - while (PyDict_Next((PyObject *)mp, &i, &key, &value)) { + while (_PyDict_Next((PyObject *)mp, &i, &key, &value, NULL)) { PyObject *s; int res; @@ -2551,15 +2703,25 @@ dict_repr(PyObject *self) return NULL; } +static PyObject * +dict_repr(PyObject *self) +{ + PyObject *res; + Py_BEGIN_CRITICAL_SECTION(self); + res = dict_repr_lock_held(self); + Py_END_CRITICAL_SECTION(); + return res; +} + static Py_ssize_t dict_length(PyObject *self) { PyDictObject *mp = (PyDictObject *)self; - return mp->ma_used; + return _Py_atomic_load_ssize_relaxed(&mp->ma_used); } static PyObject * -dict_subscript(PyObject *self, PyObject *key) +dict_subscript_lock_held(PyObject *self, PyObject *key) { PyDictObject *mp = (PyDictObject *)self; Py_ssize_t ix; @@ -2594,6 +2756,16 @@ dict_subscript(PyObject *self, PyObject *key) return Py_NewRef(value); } +static PyObject * +dict_subscript(PyObject *self, PyObject *key) +{ + PyObject *res; + Py_BEGIN_CRITICAL_SECTION(self); + res = dict_subscript_lock_held(self, key); + Py_END_CRITICAL_SECTION(); + return res; +} + static int dict_ass_sub(PyObject *mp, PyObject *v, PyObject *w) { @@ -2609,9 +2781,11 @@ static PyMappingMethods dict_as_mapping = { dict_ass_sub, /*mp_ass_subscript*/ }; -PyObject * -PyDict_Keys(PyObject *dict) +static PyObject * +keys_lock_held(PyObject *dict) { + ASSERT_DICT_LOCKED(dict); + if (dict == NULL || !PyDict_Check(dict)) { PyErr_BadInternalCall(); return NULL; @@ -2646,8 +2820,21 @@ PyDict_Keys(PyObject *dict) } PyObject * -PyDict_Values(PyObject *dict) +PyDict_Keys(PyObject *dict) +{ + PyObject *res; + Py_BEGIN_CRITICAL_SECTION(dict); + res = keys_lock_held(dict); + Py_END_CRITICAL_SECTION(); + + return res; +} + +static PyObject * +values_lock_held(PyObject *dict) { + ASSERT_DICT_LOCKED(dict); + if (dict == NULL || !PyDict_Check(dict)) { PyErr_BadInternalCall(); return NULL; @@ -2682,8 +2869,20 @@ PyDict_Values(PyObject *dict) } PyObject * -PyDict_Items(PyObject *dict) +PyDict_Values(PyObject *dict) +{ + PyObject *res; + Py_BEGIN_CRITICAL_SECTION(dict); + res = values_lock_held(dict); + Py_END_CRITICAL_SECTION(); + return res; +} + +static PyObject * +items_lock_held(PyObject *dict) { + ASSERT_DICT_LOCKED(dict); + if (dict == NULL || !PyDict_Check(dict)) { PyErr_BadInternalCall(); return NULL; @@ -2732,6 +2931,17 @@ PyDict_Items(PyObject *dict) return v; } +PyObject * +PyDict_Items(PyObject *dict) +{ + PyObject *res; + Py_BEGIN_CRITICAL_SECTION(dict); + res = items_lock_held(dict); + Py_END_CRITICAL_SECTION(); + + return res; +} + /*[clinic input] @classmethod dict.fromkeys @@ -2810,8 +3020,8 @@ dict_update(PyObject *self, PyObject *args, PyObject *kwds) producing iterable objects of length 2. */ -int -PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override) +static int +merge_from_seq2_lock_held(PyObject *d, PyObject *seq2, int override) { PyObject *it; /* iter(seq2) */ Py_ssize_t i; /* index into seq2 of current element */ @@ -2863,14 +3073,14 @@ PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override) Py_INCREF(key); Py_INCREF(value); if (override) { - if (PyDict_SetItem(d, key, value) < 0) { + if (setitem_lock_held((PyDictObject *)d, key, value) < 0) { Py_DECREF(key); Py_DECREF(value); goto Fail; } } else { - if (PyDict_SetDefault(d, key, value) == NULL) { + if (dict_setdefault_ref_lock_held(d, key, value, NULL, 0) < 0) { Py_DECREF(key); Py_DECREF(value); goto Fail; @@ -2895,6 +3105,117 @@ PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override) return Py_SAFE_DOWNCAST(i, Py_ssize_t, int); } +int +PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override) +{ + int res; + Py_BEGIN_CRITICAL_SECTION(d); + res = merge_from_seq2_lock_held(d, seq2, override); + Py_END_CRITICAL_SECTION(); + + return res; +} + +static int +dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *other, int override) +{ + if (other == mp || other->ma_used == 0) + /* a.update(a) or a.update({}); nothing to do */ + return 0; + if (mp->ma_used == 0) { + /* Since the target dict is empty, PyDict_GetItem() + * always returns NULL. Setting override to 1 + * skips the unnecessary test. + */ + override = 1; + PyDictKeysObject *okeys = other->ma_keys; + + // If other is clean, combined, and just allocated, just clone it. + if (other->ma_values == NULL && + other->ma_used == okeys->dk_nentries && + (DK_LOG_SIZE(okeys) == PyDict_LOG_MINSIZE || + USABLE_FRACTION(DK_SIZE(okeys)/2) < other->ma_used)) { + uint64_t new_version = _PyDict_NotifyEvent( + interp, PyDict_EVENT_CLONED, mp, (PyObject *)other, NULL); + PyDictKeysObject *keys = clone_combined_dict_keys(other); + if (keys == NULL) + return -1; + + dictkeys_decref(interp, mp->ma_keys); + mp->ma_keys = keys; + if (mp->ma_values != NULL) { + free_values(mp->ma_values); + mp->ma_values = NULL; + } + + mp->ma_used = other->ma_used; + mp->ma_version_tag = new_version; + ASSERT_CONSISTENT(mp); + + if (_PyObject_GC_IS_TRACKED(other) && !_PyObject_GC_IS_TRACKED(mp)) { + /* Maintain tracking. */ + _PyObject_GC_TRACK(mp); + } + + return 0; + } + } + /* Do one big resize at the start, rather than + * incrementally resizing as we insert new items. Expect + * that there will be no (or few) overlapping keys. + */ + if (USABLE_FRACTION(DK_SIZE(mp->ma_keys)) < other->ma_used) { + int unicode = DK_IS_UNICODE(other->ma_keys); + if (dictresize(interp, mp, + estimate_log2_keysize(mp->ma_used + other->ma_used), + unicode)) { + return -1; + } + } + + Py_ssize_t orig_size = other->ma_keys->dk_nentries; + Py_ssize_t pos = 0; + Py_hash_t hash; + PyObject *key, *value; + + while (_PyDict_Next((PyObject*)other, &pos, &key, &value, &hash)) { + int err = 0; + Py_INCREF(key); + Py_INCREF(value); + if (override == 1) { + err = insertdict(interp, mp, + Py_NewRef(key), hash, Py_NewRef(value)); + } + else { + err = contains_known_hash_lock_held(mp, key, hash); + if (err == 0) { + err = insertdict(interp, mp, + Py_NewRef(key), hash, Py_NewRef(value)); + } + else if (err > 0) { + if (override != 0) { + _PyErr_SetKeyError(key); + Py_DECREF(value); + Py_DECREF(key); + return -1; + } + err = 0; + } + } + Py_DECREF(value); + Py_DECREF(key); + if (err != 0) + return -1; + + if (orig_size != other->ma_keys->dk_nentries) { + PyErr_SetString(PyExc_RuntimeError, + "dict mutated during update"); + return -1; + } + } + return 0; +} + static int dict_merge(PyInterpreterState *interp, PyObject *a, PyObject *b, int override) { @@ -2912,127 +3233,44 @@ dict_merge(PyInterpreterState *interp, PyObject *a, PyObject *b, int override) return -1; } mp = (PyDictObject*)a; + int res = 0; if (PyDict_Check(b) && (Py_TYPE(b)->tp_iter == dict_iter)) { other = (PyDictObject*)b; - if (other == mp || other->ma_used == 0) - /* a.update(a) or a.update({}); nothing to do */ - return 0; - if (mp->ma_used == 0) { - /* Since the target dict is empty, PyDict_GetItem() - * always returns NULL. Setting override to 1 - * skips the unnecessary test. - */ - override = 1; - PyDictKeysObject *okeys = other->ma_keys; - - // If other is clean, combined, and just allocated, just clone it. - if (other->ma_values == NULL && - other->ma_used == okeys->dk_nentries && - (DK_LOG_SIZE(okeys) == PyDict_LOG_MINSIZE || - USABLE_FRACTION(DK_SIZE(okeys)/2) < other->ma_used)) { - uint64_t new_version = _PyDict_NotifyEvent( - interp, PyDict_EVENT_CLONED, mp, b, NULL); - PyDictKeysObject *keys = clone_combined_dict_keys(other); - if (keys == NULL) { - return -1; - } - - dictkeys_decref(interp, mp->ma_keys); - mp->ma_keys = keys; - if (mp->ma_values != NULL) { - free_values(mp->ma_values); - mp->ma_values = NULL; - } - - mp->ma_used = other->ma_used; - mp->ma_version_tag = new_version; - ASSERT_CONSISTENT(mp); - - if (_PyObject_GC_IS_TRACKED(other) && !_PyObject_GC_IS_TRACKED(mp)) { - /* Maintain tracking. */ - _PyObject_GC_TRACK(mp); - } - - return 0; - } - } - /* Do one big resize at the start, rather than - * incrementally resizing as we insert new items. Expect - * that there will be no (or few) overlapping keys. - */ - if (USABLE_FRACTION(DK_SIZE(mp->ma_keys)) < other->ma_used) { - int unicode = DK_IS_UNICODE(other->ma_keys); - if (dictresize(interp, mp, - estimate_log2_keysize(mp->ma_used + other->ma_used), - unicode)) { - return -1; - } - } - - Py_ssize_t orig_size = other->ma_keys->dk_nentries; - Py_ssize_t pos = 0; - Py_hash_t hash; - PyObject *key, *value; - - while (_PyDict_Next((PyObject*)other, &pos, &key, &value, &hash)) { - int err = 0; - Py_INCREF(key); - Py_INCREF(value); - if (override == 1) { - err = insertdict(interp, mp, - Py_NewRef(key), hash, Py_NewRef(value)); - } - else { - err = _PyDict_Contains_KnownHash(a, key, hash); - if (err == 0) { - err = insertdict(interp, mp, - Py_NewRef(key), hash, Py_NewRef(value)); - } - else if (err > 0) { - if (override != 0) { - _PyErr_SetKeyError(key); - Py_DECREF(value); - Py_DECREF(key); - return -1; - } - err = 0; - } - } - Py_DECREF(value); - Py_DECREF(key); - if (err != 0) - return -1; - - if (orig_size != other->ma_keys->dk_nentries) { - PyErr_SetString(PyExc_RuntimeError, - "dict mutated during update"); - return -1; - } - } + int res; + Py_BEGIN_CRITICAL_SECTION2(a, b); + res = dict_dict_merge(interp, (PyDictObject *)a, other, override); + ASSERT_CONSISTENT(a); + Py_END_CRITICAL_SECTION2(); + return res; } else { /* Do it the generic, slower way */ + Py_BEGIN_CRITICAL_SECTION(a); PyObject *keys = PyMapping_Keys(b); PyObject *iter; PyObject *key, *value; int status; - if (keys == NULL) + if (keys == NULL) { /* Docstring says this is equivalent to E.keys() so * if E doesn't have a .keys() method we want * AttributeError to percolate up. Might as well * do the same for any other error. */ - return -1; + res = -1; + goto slow_exit; + } iter = PyObject_GetIter(keys); Py_DECREF(keys); - if (iter == NULL) - return -1; + if (iter == NULL) { + res = -1; + goto slow_exit; + } for (key = PyIter_Next(iter); key; key = PyIter_Next(iter)) { if (override != 1) { - status = PyDict_Contains(a, key); + status = contains_lock_held(mp, key); if (status != 0) { if (status > 0) { if (override == 0) { @@ -3043,30 +3281,39 @@ dict_merge(PyInterpreterState *interp, PyObject *a, PyObject *b, int override) } Py_DECREF(key); Py_DECREF(iter); - return -1; + res = -1; + goto slow_exit; } } value = PyObject_GetItem(b, key); if (value == NULL) { Py_DECREF(iter); Py_DECREF(key); - return -1; + res = -1; + goto slow_exit; } - status = PyDict_SetItem(a, key, value); + status = setitem_lock_held(mp, key, value); Py_DECREF(key); Py_DECREF(value); if (status < 0) { Py_DECREF(iter); + res = -1; + goto slow_exit; return -1; } } Py_DECREF(iter); - if (PyErr_Occurred()) + if (PyErr_Occurred()) { /* Iterator completed, via error */ - return -1; + res = -1; + goto slow_exit; + } + +slow_exit: + ASSERT_CONSISTENT(a); + Py_END_CRITICAL_SECTION(); + return res; } - ASSERT_CONSISTENT(a); - return 0; } int @@ -3104,17 +3351,14 @@ dict_copy_impl(PyDictObject *self) return PyDict_Copy((PyObject *)self); } -PyObject * -PyDict_Copy(PyObject *o) +static PyObject * +copy_lock_held(PyObject *o) { PyObject *copy; PyDictObject *mp; PyInterpreterState *interp = _PyInterpreterState_GET(); - if (o == NULL || !PyDict_Check(o)) { - PyErr_BadInternalCall(); - return NULL; - } + ASSERT_DICT_LOCKED(o); mp = (PyDictObject *)o; if (mp->ma_used == 0) { @@ -3197,6 +3441,23 @@ PyDict_Copy(PyObject *o) return NULL; } +PyObject * +PyDict_Copy(PyObject *o) +{ + if (o == NULL || !PyDict_Check(o)) { + PyErr_BadInternalCall(); + return NULL; + } + + PyObject *res; + Py_BEGIN_CRITICAL_SECTION(o); + + res = copy_lock_held(o); + + Py_END_CRITICAL_SECTION(); + return res; +} + Py_ssize_t PyDict_Size(PyObject *mp) { @@ -3212,10 +3473,13 @@ PyDict_Size(PyObject *mp) * Uses only Py_EQ comparison. */ static int -dict_equal(PyDictObject *a, PyDictObject *b) +dict_equal_lock_held(PyDictObject *a, PyDictObject *b) { Py_ssize_t i; + ASSERT_DICT_LOCKED(a); + ASSERT_DICT_LOCKED(b); + if (a->ma_used != b->ma_used) /* can't be equal if # of entries differ */ return 0; @@ -3270,6 +3534,17 @@ dict_equal(PyDictObject *a, PyDictObject *b) return 1; } +static int +dict_equal(PyDictObject *a, PyDictObject *b) +{ + int res; + Py_BEGIN_CRITICAL_SECTION2(a, b); + res = dict_equal_lock_held(a, b); + Py_END_CRITICAL_SECTION2(); + + return res; +} + static PyObject * dict_richcompare(PyObject *v, PyObject *w, int op) { @@ -3293,6 +3568,7 @@ dict_richcompare(PyObject *v, PyObject *w, int op) /*[clinic input] @coexist +@critical_section dict.__contains__ key: object @@ -3302,8 +3578,8 @@ True if the dictionary has the specified key, else False. [clinic start generated code]*/ static PyObject * -dict___contains__(PyDictObject *self, PyObject *key) -/*[clinic end generated code: output=a3d03db709ed6e6b input=fe1cb42ad831e820]*/ +dict___contains___impl(PyDictObject *self, PyObject *key) +/*[clinic end generated code: output=1b314e6da7687dae input=bc76ec9c157cb81b]*/ { register PyDictObject *mp = self; Py_hash_t hash; @@ -3324,6 +3600,7 @@ dict___contains__(PyDictObject *self, PyObject *key) } /*[clinic input] +@critical_section dict.get key: object @@ -3335,7 +3612,7 @@ Return the value for key if key is in the dictionary, else default. static PyObject * dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value) -/*[clinic end generated code: output=bba707729dee05bf input=279ddb5790b6b107]*/ +/*[clinic end generated code: output=bba707729dee05bf input=a631d3f18f584c60]*/ { PyObject *val = NULL; Py_hash_t hash; @@ -3356,7 +3633,7 @@ dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value) } static int -dict_setdefault_ref(PyObject *d, PyObject *key, PyObject *default_value, +dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_value, PyObject **result, int incref_result) { PyDictObject *mp = (PyDictObject *)d; @@ -3364,6 +3641,8 @@ dict_setdefault_ref(PyObject *d, PyObject *key, PyObject *default_value, Py_hash_t hash; PyInterpreterState *interp = _PyInterpreterState_GET(); + ASSERT_DICT_LOCKED(d); + if (!PyDict_Check(d)) { PyErr_BadInternalCall(); if (result) { @@ -3490,18 +3769,25 @@ int PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value, PyObject **result) { - return dict_setdefault_ref(d, key, default_value, result, 1); + int res; + Py_BEGIN_CRITICAL_SECTION(d); + res = dict_setdefault_ref_lock_held(d, key, default_value, result, 1); + Py_END_CRITICAL_SECTION(); + return res; } PyObject * PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj) { PyObject *result; - dict_setdefault_ref(d, key, defaultobj, &result, 0); + Py_BEGIN_CRITICAL_SECTION(d); + dict_setdefault_ref_lock_held(d, key, defaultobj, &result, 0); + Py_END_CRITICAL_SECTION(); return result; } /*[clinic input] +@critical_section dict.setdefault key: object @@ -3516,10 +3802,10 @@ Return the value for key if key is in the dictionary, else default. static PyObject * dict_setdefault_impl(PyDictObject *self, PyObject *key, PyObject *default_value) -/*[clinic end generated code: output=f8c1101ebf69e220 input=0f063756e815fd9d]*/ +/*[clinic end generated code: output=f8c1101ebf69e220 input=9237af9a0a224302]*/ { PyObject *val; - PyDict_SetDefaultRef((PyObject *)self, key, default_value, &val); + dict_setdefault_ref_lock_held((PyObject *)self, key, default_value, &val, 1); return val; } @@ -3559,6 +3845,7 @@ dict_pop_impl(PyDictObject *self, PyObject *key, PyObject *default_value) } /*[clinic input] +@critical_section dict.popitem Remove and return a (key, value) pair as a 2-tuple. @@ -3569,7 +3856,7 @@ Raises KeyError if the dict is empty. static PyObject * dict_popitem_impl(PyDictObject *self) -/*[clinic end generated code: output=e65fcb04420d230d input=1c38a49f21f64941]*/ +/*[clinic end generated code: output=e65fcb04420d230d input=ef28b4da5f0f762e]*/ { Py_ssize_t i, j; PyObject *res; @@ -3695,8 +3982,8 @@ dict_tp_clear(PyObject *op) static PyObject *dictiter_new(PyDictObject *, PyTypeObject *); -Py_ssize_t -_PyDict_SizeOf(PyDictObject *mp) +static Py_ssize_t +sizeof_lock_held(PyDictObject *mp) { size_t res = _PyObject_SIZE(Py_TYPE(mp)); if (mp->ma_values) { @@ -3711,6 +3998,17 @@ _PyDict_SizeOf(PyDictObject *mp) return (Py_ssize_t)res; } +Py_ssize_t +_PyDict_SizeOf(PyDictObject *mp) +{ + Py_ssize_t res; + Py_BEGIN_CRITICAL_SECTION(mp); + res = sizeof_lock_held(mp); + Py_END_CRITICAL_SECTION(); + + return res; +} + size_t _PyDict_KeysSize(PyDictKeysObject *keys) { @@ -3794,15 +4092,29 @@ static PyMethodDef mapp_methods[] = { {NULL, NULL} /* sentinel */ }; -/* Return 1 if `key` is in dict `op`, 0 if not, and -1 on error. */ -int -PyDict_Contains(PyObject *op, PyObject *key) +static int +contains_known_hash_lock_held(PyDictObject *mp, PyObject *key, Py_ssize_t hash) +{ + Py_ssize_t ix; + PyObject *value; + + ASSERT_DICT_LOCKED(mp); + + ix = _Py_dict_lookup(mp, key, hash, &value); + if (ix == DKIX_ERROR) + return -1; + return (ix != DKIX_EMPTY && value != NULL); +} + +static int +contains_lock_held(PyDictObject *mp, PyObject *key) { Py_hash_t hash; Py_ssize_t ix; - PyDictObject *mp = (PyDictObject *)op; PyObject *value; + ASSERT_DICT_LOCKED(mp); + if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) { hash = PyObject_Hash(key); if (hash == -1) @@ -3814,6 +4126,17 @@ PyDict_Contains(PyObject *op, PyObject *key) return (ix != DKIX_EMPTY && value != NULL); } +/* Return 1 if `key` is in dict `op`, 0 if not, and -1 on error. */ +int +PyDict_Contains(PyObject *op, PyObject *key) +{ + int res; + Py_BEGIN_CRITICAL_SECTION(op); + res = contains_lock_held((PyDictObject *)op, key); + Py_END_CRITICAL_SECTION(); + return res; +} + int PyDict_ContainsString(PyObject *op, const char *key) { @@ -4180,17 +4503,15 @@ static PyMethodDef dictiter_methods[] = { }; static PyObject* -dictiter_iternextkey(PyObject *self) +dictiter_iternextkey_lock_held(PyDictObject *d, PyObject *self) { dictiterobject *di = (dictiterobject *)self; PyObject *key; Py_ssize_t i; PyDictKeysObject *k; - PyDictObject *d = di->di_dict; - if (d == NULL) - return NULL; assert (PyDict_Check(d)); + ASSERT_DICT_LOCKED(d); if (di->di_used != d->ma_used) { PyErr_SetString(PyExc_RuntimeError, @@ -4248,6 +4569,23 @@ dictiter_iternextkey(PyObject *self) return NULL; } +static PyObject* +dictiter_iternextkey(PyObject *self) +{ + dictiterobject *di = (dictiterobject *)self; + PyDictObject *d = di->di_dict; + + if (d == NULL) + return NULL; + + PyObject *value; + Py_BEGIN_CRITICAL_SECTION(d); + value = dictiter_iternextkey_lock_held(d, self); + Py_END_CRITICAL_SECTION(); + + return value; +} + PyTypeObject PyDictIterKey_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "dict_keyiterator", /* tp_name */ @@ -4282,16 +4620,14 @@ PyTypeObject PyDictIterKey_Type = { }; static PyObject * -dictiter_iternextvalue(PyObject *self) +dictiter_iternextvalue_lock_held(PyDictObject *d, PyObject *self) { dictiterobject *di = (dictiterobject *)self; PyObject *value; Py_ssize_t i; - PyDictObject *d = di->di_dict; - if (d == NULL) - return NULL; assert (PyDict_Check(d)); + ASSERT_DICT_LOCKED(d); if (di->di_used != d->ma_used) { PyErr_SetString(PyExc_RuntimeError, @@ -4348,6 +4684,23 @@ dictiter_iternextvalue(PyObject *self) return NULL; } +static PyObject * +dictiter_iternextvalue(PyObject *self) +{ + dictiterobject *di = (dictiterobject *)self; + PyDictObject *d = di->di_dict; + + if (d == NULL) + return NULL; + + PyObject *value; + Py_BEGIN_CRITICAL_SECTION(d); + value = dictiter_iternextvalue_lock_held(d, self); + Py_END_CRITICAL_SECTION(); + + return value; +} + PyTypeObject PyDictIterValue_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "dict_valueiterator", /* tp_name */ @@ -4382,15 +4735,12 @@ PyTypeObject PyDictIterValue_Type = { }; static PyObject * -dictiter_iternextitem(PyObject *self) +dictiter_iternextitem_lock_held(PyDictObject *d, PyObject *self) { dictiterobject *di = (dictiterobject *)self; PyObject *key, *value, *result; Py_ssize_t i; - PyDictObject *d = di->di_dict; - if (d == NULL) - return NULL; assert (PyDict_Check(d)); if (di->di_used != d->ma_used) { @@ -4473,6 +4823,22 @@ dictiter_iternextitem(PyObject *self) return NULL; } +static PyObject * +dictiter_iternextitem(PyObject *self) +{ + dictiterobject *di = (dictiterobject *)self; + PyDictObject *d = di->di_dict; + + if (d == NULL) + return NULL; + + PyObject *item; + Py_BEGIN_CRITICAL_SECTION(d); + item = dictiter_iternextitem_lock_held(d, self); + Py_END_CRITICAL_SECTION(); + return item; +} + PyTypeObject PyDictIterItem_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "dict_itemiterator", /* tp_name */ @@ -4510,15 +4876,12 @@ PyTypeObject PyDictIterItem_Type = { /* dictreviter */ static PyObject * -dictreviter_iternext(PyObject *self) +dictreviter_iter_PyDict_Next(PyDictObject *d, PyObject *self) { dictiterobject *di = (dictiterobject *)self; - PyDictObject *d = di->di_dict; - if (d == NULL) { - return NULL; - } assert (PyDict_Check(d)); + ASSERT_DICT_LOCKED(d); if (di->di_used != d->ma_used) { PyErr_SetString(PyExc_RuntimeError, @@ -4609,6 +4972,23 @@ dictreviter_iternext(PyObject *self) return NULL; } +static PyObject * +dictreviter_iternext(PyObject *self) +{ + dictiterobject *di = (dictiterobject *)self; + PyDictObject *d = di->di_dict; + + if (d == NULL) + return NULL; + + PyObject *value; + Py_BEGIN_CRITICAL_SECTION(d); + value = dictreviter_iter_PyDict_Next(d, self); + Py_END_CRITICAL_SECTION(); + + return value; +} + PyTypeObject PyDictRevIterKey_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "dict_reversekeyiterator", @@ -5037,14 +5417,12 @@ dictviews_or(PyObject* self, PyObject *other) } static PyObject * -dictitems_xor(PyObject *self, PyObject *other) +dictitems_xor_lock_held(PyObject *d1, PyObject *d2) { - assert(PyDictItems_Check(self)); - assert(PyDictItems_Check(other)); - PyObject *d1 = (PyObject *)((_PyDictViewObject *)self)->dv_dict; - PyObject *d2 = (PyObject *)((_PyDictViewObject *)other)->dv_dict; + ASSERT_DICT_LOCKED(d1); + ASSERT_DICT_LOCKED(d2); - PyObject *temp_dict = PyDict_Copy(d1); + PyObject *temp_dict = copy_lock_held(d1); if (temp_dict == NULL) { return NULL; } @@ -5122,6 +5500,22 @@ dictitems_xor(PyObject *self, PyObject *other) return NULL; } +static PyObject * +dictitems_xor(PyObject *self, PyObject *other) +{ + assert(PyDictItems_Check(self)); + assert(PyDictItems_Check(other)); + PyObject *d1 = (PyObject *)((_PyDictViewObject *)self)->dv_dict; + PyObject *d2 = (PyObject *)((_PyDictViewObject *)other)->dv_dict; + + PyObject *res; + Py_BEGIN_CRITICAL_SECTION2(d1, d2); + res = dictitems_xor_lock_held(d1, d2); + Py_END_CRITICAL_SECTION2(); + + return res; +} + static PyObject* dictviews_xor(PyObject* self, PyObject *other) { diff --git a/Objects/odictobject.c b/Objects/odictobject.c index b5280c39e1be542..421bc52992d7354 100644 --- a/Objects/odictobject.c +++ b/Objects/odictobject.c @@ -465,12 +465,13 @@ Potential Optimizations */ #include "Python.h" -#include "pycore_call.h" // _PyObject_CallNoArgs() -#include "pycore_ceval.h" // _PyEval_GetBuiltin() -#include "pycore_dict.h" // _Py_dict_lookup() -#include "pycore_object.h" // _PyObject_GC_UNTRACK() -#include "pycore_pyerrors.h" // _PyErr_ChainExceptions1() -#include <stddef.h> // offsetof() +#include "pycore_call.h" // _PyObject_CallNoArgs() +#include "pycore_ceval.h" // _PyEval_GetBuiltin() +#include "pycore_critical_section.h" //_Py_BEGIN_CRITICAL_SECTION +#include "pycore_dict.h" // _Py_dict_lookup() +#include "pycore_object.h" // _PyObject_GC_UNTRACK() +#include "pycore_pyerrors.h" // _PyErr_ChainExceptions1() +#include <stddef.h> // offsetof() #include "clinic/odictobject.c.h" @@ -1039,6 +1040,8 @@ _odict_popkey_hash(PyObject *od, PyObject *key, PyObject *failobj, { PyObject *value = NULL; + Py_BEGIN_CRITICAL_SECTION(od); + _ODictNode *node = _odict_find_node_hash((PyODictObject *)od, key, hash); if (node != NULL) { /* Pop the node first to avoid a possible dict resize (due to @@ -1046,7 +1049,7 @@ _odict_popkey_hash(PyObject *od, PyObject *key, PyObject *failobj, resolution. */ int res = _odict_clear_node((PyODictObject *)od, node, key, hash); if (res < 0) { - return NULL; + goto done; } /* Now delete the value from the dict. */ if (_PyDict_Pop_KnownHash((PyDictObject *)od, key, hash, @@ -1063,6 +1066,8 @@ _odict_popkey_hash(PyObject *od, PyObject *key, PyObject *failobj, PyErr_SetObject(PyExc_KeyError, key); } } + Py_END_CRITICAL_SECTION(); +done: return value; } diff --git a/Objects/setobject.c b/Objects/setobject.c index 93de8e84f2ddf99..3acf2a7a74890b1 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -32,13 +32,14 @@ */ #include "Python.h" -#include "pycore_ceval.h" // _PyEval_GetBuiltin() -#include "pycore_dict.h" // _PyDict_Contains_KnownHash() -#include "pycore_modsupport.h" // _PyArg_NoKwnames() -#include "pycore_object.h" // _PyObject_GC_UNTRACK() -#include "pycore_pyerrors.h" // _PyErr_SetKeyError() -#include "pycore_setobject.h" // _PySet_NextEntry() definition -#include <stddef.h> // offsetof() +#include "pycore_ceval.h" // _PyEval_GetBuiltin() +#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION, Py_END_CRITICAL_SECTION +#include "pycore_dict.h" // _PyDict_Contains_KnownHash() +#include "pycore_modsupport.h" // _PyArg_NoKwnames() +#include "pycore_object.h" // _PyObject_GC_UNTRACK() +#include "pycore_pyerrors.h" // _PyErr_SetKeyError() +#include "pycore_setobject.h" // _PySet_NextEntry() definition +#include <stddef.h> // offsetof() /* Object used as dummy key to fill deleted entries */ static PyObject _dummy_struct; @@ -903,11 +904,17 @@ set_update_internal(PySetObject *so, PyObject *other) if (set_table_resize(so, (so->used + dictsize)*2) != 0) return -1; } + int err = 0; + Py_BEGIN_CRITICAL_SECTION(other); while (_PyDict_Next(other, &pos, &key, &value, &hash)) { - if (set_add_entry(so, key, hash)) - return -1; + if (set_add_entry(so, key, hash)) { + err = -1; + goto exit; + } } - return 0; +exit: + Py_END_CRITICAL_SECTION(); + return err; } it = PyObject_GetIter(other); @@ -1620,6 +1627,33 @@ set_isub(PySetObject *so, PyObject *other) return Py_NewRef(so); } +static PyObject * +set_symmetric_difference_update_dict(PySetObject *so, PyObject *other) +{ + PyObject *key; + Py_ssize_t pos = 0; + Py_hash_t hash; + PyObject *value; + int rv; + + while (_PyDict_Next(other, &pos, &key, &value, &hash)) { + Py_INCREF(key); + rv = set_discard_entry(so, key, hash); + if (rv < 0) { + Py_DECREF(key); + return NULL; + } + if (rv == DISCARD_NOTFOUND) { + if (set_add_entry(so, key, hash)) { + Py_DECREF(key); + return NULL; + } + } + Py_DECREF(key); + } + Py_RETURN_NONE; +} + static PyObject * set_symmetric_difference_update(PySetObject *so, PyObject *other) { @@ -1634,23 +1668,13 @@ set_symmetric_difference_update(PySetObject *so, PyObject *other) return set_clear(so, NULL); if (PyDict_CheckExact(other)) { - PyObject *value; - while (_PyDict_Next(other, &pos, &key, &value, &hash)) { - Py_INCREF(key); - rv = set_discard_entry(so, key, hash); - if (rv < 0) { - Py_DECREF(key); - return NULL; - } - if (rv == DISCARD_NOTFOUND) { - if (set_add_entry(so, key, hash)) { - Py_DECREF(key); - return NULL; - } - } - Py_DECREF(key); - } - Py_RETURN_NONE; + PyObject *res; + + Py_BEGIN_CRITICAL_SECTION(other); + res = set_symmetric_difference_update_dict(so, other); + Py_END_CRITICAL_SECTION(); + + return res; } if (PyAnySet_Check(other)) { From 11ac6f5354ec7a4da2a7e052d27d636b5a41c714 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Tue, 6 Feb 2024 23:44:14 +0100 Subject: [PATCH 164/507] gh-115009: Update Windows installer to use SQLite 3.45.1 (#115065) --- .../next/Windows/2024-02-06-09-05-13.gh-issue-115009.ShMjZs.rst | 1 + PCbuild/get_externals.bat | 2 +- PCbuild/python.props | 2 +- PCbuild/readme.txt | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2024-02-06-09-05-13.gh-issue-115009.ShMjZs.rst diff --git a/Misc/NEWS.d/next/Windows/2024-02-06-09-05-13.gh-issue-115009.ShMjZs.rst b/Misc/NEWS.d/next/Windows/2024-02-06-09-05-13.gh-issue-115009.ShMjZs.rst new file mode 100644 index 000000000000000..5bdb6963a243118 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-02-06-09-05-13.gh-issue-115009.ShMjZs.rst @@ -0,0 +1 @@ +Update Windows installer to use SQLite 3.45.1. diff --git a/PCbuild/get_externals.bat b/PCbuild/get_externals.bat index 0989bd46a580f77..60ce12b725e2331 100644 --- a/PCbuild/get_externals.bat +++ b/PCbuild/get_externals.bat @@ -54,7 +54,7 @@ set libraries= set libraries=%libraries% bzip2-1.0.8 if NOT "%IncludeLibffiSrc%"=="false" set libraries=%libraries% libffi-3.4.4 if NOT "%IncludeSSLSrc%"=="false" set libraries=%libraries% openssl-3.0.13 -set libraries=%libraries% sqlite-3.44.2.0 +set libraries=%libraries% sqlite-3.45.1.0 if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tcl-core-8.6.13.1 if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tk-8.6.13.1 set libraries=%libraries% xz-5.2.5 diff --git a/PCbuild/python.props b/PCbuild/python.props index 54553db40572888..e21f1f60464bc8c 100644 --- a/PCbuild/python.props +++ b/PCbuild/python.props @@ -68,7 +68,7 @@ <Import Project="$(ExternalProps)" Condition="$(ExternalProps) != '' and Exists('$(ExternalProps)')" /> <PropertyGroup> - <sqlite3Dir Condition="$(sqlite3Dir) == ''">$(ExternalsDir)sqlite-3.44.2.0\</sqlite3Dir> + <sqlite3Dir Condition="$(sqlite3Dir) == ''">$(ExternalsDir)sqlite-3.45.1.0\</sqlite3Dir> <bz2Dir Condition="$(bz2Dir) == ''">$(ExternalsDir)bzip2-1.0.8\</bz2Dir> <lzmaDir Condition="$(lzmaDir) == ''">$(ExternalsDir)xz-5.2.5\</lzmaDir> <libffiDir Condition="$(libffiDir) == ''">$(ExternalsDir)libffi-3.4.4\</libffiDir> diff --git a/PCbuild/readme.txt b/PCbuild/readme.txt index b9d76515c383f77..387565515fa0b02 100644 --- a/PCbuild/readme.txt +++ b/PCbuild/readme.txt @@ -189,7 +189,7 @@ _ssl again when building. _sqlite3 - Wraps SQLite 3.44.2, which is itself built by sqlite3.vcxproj + Wraps SQLite 3.45.1, which is itself built by sqlite3.vcxproj Homepage: https://www.sqlite.org/ _tkinter From 3f71c416c085cfaed49ef325f70eb374a4966256 Mon Sep 17 00:00:00 2001 From: Finite State Machine <38001514+finite-state-machine@users.noreply.github.com> Date: Tue, 6 Feb 2024 20:28:01 -0500 Subject: [PATCH 165/507] gh-115106 docs: 'enum.Flag.__iter__()' did not exist prior to Python 3.11 (GH-115107) change versionchanged to versionadded --- Doc/library/enum.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index f31e6ea848f3b2e..534939943d33267 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -534,9 +534,7 @@ Data Types >>> list(purple) [<Color.RED: 1>, <Color.BLUE: 4>] - .. versionchanged:: 3.11 - - Aliases are no longer returned during iteration. + .. versionadded:: 3.11 .. method:: __len__(self): From 60375a38092b4d4dec9a826818a20adc5d4ff2f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= <16805946+edgarrmondragon@users.noreply.github.com> Date: Tue, 6 Feb 2024 23:22:47 -0600 Subject: [PATCH 166/507] gh-115114: Add missing slash to file URI prefix `file:/` (#115115) Add missing slash to file URI prefix `file:/` --- Doc/whatsnew/3.13.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index e034d34c5fb5abc..c75d44065313940 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -391,7 +391,7 @@ pathlib (Contributed by Barney Gale in :gh:`89812`.) * Add :meth:`pathlib.Path.from_uri`, a new constructor to create a :class:`pathlib.Path` - object from a 'file' URI (``file:/``). + object from a 'file' URI (``file://``). (Contributed by Barney Gale in :gh:`107465`.) * Add :meth:`pathlib.PurePath.full_match` for matching paths with From 2afc7182e66635b3ec7efb59d2a6c18a7ad1f215 Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Wed, 7 Feb 2024 02:50:24 -0600 Subject: [PATCH 167/507] gh-114505: Add missing header file dependencies (#114513) Also move PYTHON_HEADERS up and make _testembed.o depend on it. --- Makefile.pre.in | 500 +++++++++++++++++++++++++----------------------- 1 file changed, 259 insertions(+), 241 deletions(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index aad637876ead809..07b2ec7adde78a5 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -936,6 +936,261 @@ python.html: $(srcdir)/Tools/wasm/python.html python.worker.js python.worker.js: $(srcdir)/Tools/wasm/python.worker.js @cp $(srcdir)/Tools/wasm/python.worker.js $@ +############################################################################ +# Header files + +PYTHON_HEADERS= \ + $(srcdir)/Include/Python.h \ + $(srcdir)/Include/abstract.h \ + $(srcdir)/Include/bltinmodule.h \ + $(srcdir)/Include/boolobject.h \ + $(srcdir)/Include/bytearrayobject.h \ + $(srcdir)/Include/bytesobject.h \ + $(srcdir)/Include/ceval.h \ + $(srcdir)/Include/codecs.h \ + $(srcdir)/Include/compile.h \ + $(srcdir)/Include/complexobject.h \ + $(srcdir)/Include/descrobject.h \ + $(srcdir)/Include/dictobject.h \ + $(srcdir)/Include/dynamic_annotations.h \ + $(srcdir)/Include/enumobject.h \ + $(srcdir)/Include/errcode.h \ + $(srcdir)/Include/exports.h \ + $(srcdir)/Include/fileobject.h \ + $(srcdir)/Include/fileutils.h \ + $(srcdir)/Include/floatobject.h \ + $(srcdir)/Include/frameobject.h \ + $(srcdir)/Include/genericaliasobject.h \ + $(srcdir)/Include/import.h \ + $(srcdir)/Include/interpreteridobject.h \ + $(srcdir)/Include/intrcheck.h \ + $(srcdir)/Include/iterobject.h \ + $(srcdir)/Include/listobject.h \ + $(srcdir)/Include/longobject.h \ + $(srcdir)/Include/marshal.h \ + $(srcdir)/Include/memoryobject.h \ + $(srcdir)/Include/methodobject.h \ + $(srcdir)/Include/modsupport.h \ + $(srcdir)/Include/moduleobject.h \ + $(srcdir)/Include/object.h \ + $(srcdir)/Include/objimpl.h \ + $(srcdir)/Include/opcode.h \ + $(srcdir)/Include/opcode_ids.h \ + $(srcdir)/Include/osdefs.h \ + $(srcdir)/Include/osmodule.h \ + $(srcdir)/Include/patchlevel.h \ + $(srcdir)/Include/pyatomic.h \ + $(srcdir)/Include/pybuffer.h \ + $(srcdir)/Include/pycapsule.h \ + $(srcdir)/Include/pydtrace.h \ + $(srcdir)/Include/pyerrors.h \ + $(srcdir)/Include/pyexpat.h \ + $(srcdir)/Include/pyframe.h \ + $(srcdir)/Include/pyhash.h \ + $(srcdir)/Include/pylifecycle.h \ + $(srcdir)/Include/pymacconfig.h \ + $(srcdir)/Include/pymacro.h \ + $(srcdir)/Include/pymath.h \ + $(srcdir)/Include/pymem.h \ + $(srcdir)/Include/pyport.h \ + $(srcdir)/Include/pystate.h \ + $(srcdir)/Include/pystats.h \ + $(srcdir)/Include/pystrcmp.h \ + $(srcdir)/Include/pystrtod.h \ + $(srcdir)/Include/pythonrun.h \ + $(srcdir)/Include/pythread.h \ + $(srcdir)/Include/pytypedefs.h \ + $(srcdir)/Include/rangeobject.h \ + $(srcdir)/Include/setobject.h \ + $(srcdir)/Include/sliceobject.h \ + $(srcdir)/Include/structmember.h \ + $(srcdir)/Include/structseq.h \ + $(srcdir)/Include/sysmodule.h \ + $(srcdir)/Include/traceback.h \ + $(srcdir)/Include/tupleobject.h \ + $(srcdir)/Include/typeslots.h \ + $(srcdir)/Include/unicodeobject.h \ + $(srcdir)/Include/warnings.h \ + $(srcdir)/Include/weakrefobject.h \ + \ + pyconfig.h \ + $(PARSER_HEADERS) \ + \ + $(srcdir)/Include/cpython/abstract.h \ + $(srcdir)/Include/cpython/bytearrayobject.h \ + $(srcdir)/Include/cpython/bytesobject.h \ + $(srcdir)/Include/cpython/cellobject.h \ + $(srcdir)/Include/cpython/ceval.h \ + $(srcdir)/Include/cpython/classobject.h \ + $(srcdir)/Include/cpython/code.h \ + $(srcdir)/Include/cpython/compile.h \ + $(srcdir)/Include/cpython/complexobject.h \ + $(srcdir)/Include/cpython/context.h \ + $(srcdir)/Include/cpython/descrobject.h \ + $(srcdir)/Include/cpython/dictobject.h \ + $(srcdir)/Include/cpython/fileobject.h \ + $(srcdir)/Include/cpython/fileutils.h \ + $(srcdir)/Include/cpython/floatobject.h \ + $(srcdir)/Include/cpython/frameobject.h \ + $(srcdir)/Include/cpython/funcobject.h \ + $(srcdir)/Include/cpython/genobject.h \ + $(srcdir)/Include/cpython/import.h \ + $(srcdir)/Include/cpython/initconfig.h \ + $(srcdir)/Include/cpython/interpreteridobject.h \ + $(srcdir)/Include/cpython/listobject.h \ + $(srcdir)/Include/cpython/longintrepr.h \ + $(srcdir)/Include/cpython/longobject.h \ + $(srcdir)/Include/cpython/memoryobject.h \ + $(srcdir)/Include/cpython/methodobject.h \ + $(srcdir)/Include/cpython/object.h \ + $(srcdir)/Include/cpython/objimpl.h \ + $(srcdir)/Include/cpython/odictobject.h \ + $(srcdir)/Include/cpython/optimizer.h \ + $(srcdir)/Include/cpython/picklebufobject.h \ + $(srcdir)/Include/cpython/pthread_stubs.h \ + $(srcdir)/Include/cpython/pyatomic.h \ + $(srcdir)/Include/cpython/pyatomic_gcc.h \ + $(srcdir)/Include/cpython/pyatomic_std.h \ + $(srcdir)/Include/cpython/pyctype.h \ + $(srcdir)/Include/cpython/pydebug.h \ + $(srcdir)/Include/cpython/pyerrors.h \ + $(srcdir)/Include/cpython/pyfpe.h \ + $(srcdir)/Include/cpython/pyframe.h \ + $(srcdir)/Include/cpython/pyhash.h \ + $(srcdir)/Include/cpython/pylifecycle.h \ + $(srcdir)/Include/cpython/pymem.h \ + $(srcdir)/Include/cpython/pystate.h \ + $(srcdir)/Include/cpython/pystats.h \ + $(srcdir)/Include/cpython/pythonrun.h \ + $(srcdir)/Include/cpython/pythread.h \ + $(srcdir)/Include/cpython/setobject.h \ + $(srcdir)/Include/cpython/sysmodule.h \ + $(srcdir)/Include/cpython/traceback.h \ + $(srcdir)/Include/cpython/tracemalloc.h \ + $(srcdir)/Include/cpython/tupleobject.h \ + $(srcdir)/Include/cpython/unicodeobject.h \ + $(srcdir)/Include/cpython/warnings.h \ + $(srcdir)/Include/cpython/weakrefobject.h \ + \ + $(MIMALLOC_HEADERS) \ + \ + $(srcdir)/Include/internal/pycore_abstract.h \ + $(srcdir)/Include/internal/pycore_asdl.h \ + $(srcdir)/Include/internal/pycore_ast.h \ + $(srcdir)/Include/internal/pycore_ast_state.h \ + $(srcdir)/Include/internal/pycore_atexit.h \ + $(srcdir)/Include/internal/pycore_bitutils.h \ + $(srcdir)/Include/internal/pycore_blocks_output_buffer.h \ + $(srcdir)/Include/internal/pycore_bytes_methods.h \ + $(srcdir)/Include/internal/pycore_bytesobject.h \ + $(srcdir)/Include/internal/pycore_call.h \ + $(srcdir)/Include/internal/pycore_capsule.h \ + $(srcdir)/Include/internal/pycore_ceval.h \ + $(srcdir)/Include/internal/pycore_ceval_state.h \ + $(srcdir)/Include/internal/pycore_code.h \ + $(srcdir)/Include/internal/pycore_codecs.h \ + $(srcdir)/Include/internal/pycore_compile.h \ + $(srcdir)/Include/internal/pycore_complexobject.h \ + $(srcdir)/Include/internal/pycore_condvar.h \ + $(srcdir)/Include/internal/pycore_context.h \ + $(srcdir)/Include/internal/pycore_critical_section.h \ + $(srcdir)/Include/internal/pycore_crossinterp.h \ + $(srcdir)/Include/internal/pycore_descrobject.h \ + $(srcdir)/Include/internal/pycore_dict.h \ + $(srcdir)/Include/internal/pycore_dict_state.h \ + $(srcdir)/Include/internal/pycore_dtoa.h \ + $(srcdir)/Include/internal/pycore_exceptions.h \ + $(srcdir)/Include/internal/pycore_faulthandler.h \ + $(srcdir)/Include/internal/pycore_fileutils.h \ + $(srcdir)/Include/internal/pycore_floatobject.h \ + $(srcdir)/Include/internal/pycore_flowgraph.h \ + $(srcdir)/Include/internal/pycore_format.h \ + $(srcdir)/Include/internal/pycore_frame.h \ + $(srcdir)/Include/internal/pycore_freelist.h \ + $(srcdir)/Include/internal/pycore_function.h \ + $(srcdir)/Include/internal/pycore_gc.h \ + $(srcdir)/Include/internal/pycore_genobject.h \ + $(srcdir)/Include/internal/pycore_getopt.h \ + $(srcdir)/Include/internal/pycore_gil.h \ + $(srcdir)/Include/internal/pycore_global_objects.h \ + $(srcdir)/Include/internal/pycore_global_objects_fini_generated.h \ + $(srcdir)/Include/internal/pycore_global_strings.h \ + $(srcdir)/Include/internal/pycore_hamt.h \ + $(srcdir)/Include/internal/pycore_hashtable.h \ + $(srcdir)/Include/internal/pycore_identifier.h \ + $(srcdir)/Include/internal/pycore_import.h \ + $(srcdir)/Include/internal/pycore_importdl.h \ + $(srcdir)/Include/internal/pycore_initconfig.h \ + $(srcdir)/Include/internal/pycore_instruments.h \ + $(srcdir)/Include/internal/pycore_interp.h \ + $(srcdir)/Include/internal/pycore_intrinsics.h \ + $(srcdir)/Include/internal/pycore_jit.h \ + $(srcdir)/Include/internal/pycore_list.h \ + $(srcdir)/Include/internal/pycore_llist.h \ + $(srcdir)/Include/internal/pycore_lock.h \ + $(srcdir)/Include/internal/pycore_long.h \ + $(srcdir)/Include/internal/pycore_memoryobject.h \ + $(srcdir)/Include/internal/pycore_mimalloc.h \ + $(srcdir)/Include/internal/pycore_modsupport.h \ + $(srcdir)/Include/internal/pycore_moduleobject.h \ + $(srcdir)/Include/internal/pycore_namespace.h \ + $(srcdir)/Include/internal/pycore_object.h \ + $(srcdir)/Include/internal/pycore_object_alloc.h \ + $(srcdir)/Include/internal/pycore_object_stack.h \ + $(srcdir)/Include/internal/pycore_object_state.h \ + $(srcdir)/Include/internal/pycore_obmalloc.h \ + $(srcdir)/Include/internal/pycore_obmalloc_init.h \ + $(srcdir)/Include/internal/pycore_opcode_metadata.h \ + $(srcdir)/Include/internal/pycore_opcode_utils.h \ + $(srcdir)/Include/internal/pycore_optimizer.h \ + $(srcdir)/Include/internal/pycore_parking_lot.h \ + $(srcdir)/Include/internal/pycore_parser.h \ + $(srcdir)/Include/internal/pycore_pathconfig.h \ + $(srcdir)/Include/internal/pycore_pyarena.h \ + $(srcdir)/Include/internal/pycore_pybuffer.h \ + $(srcdir)/Include/internal/pycore_pyerrors.h \ + $(srcdir)/Include/internal/pycore_pyhash.h \ + $(srcdir)/Include/internal/pycore_pylifecycle.h \ + $(srcdir)/Include/internal/pycore_pymath.h \ + $(srcdir)/Include/internal/pycore_pymem.h \ + $(srcdir)/Include/internal/pycore_pymem_init.h \ + $(srcdir)/Include/internal/pycore_pystate.h \ + $(srcdir)/Include/internal/pycore_pystats.h \ + $(srcdir)/Include/internal/pycore_pythonrun.h \ + $(srcdir)/Include/internal/pycore_pythread.h \ + $(srcdir)/Include/internal/pycore_range.h \ + $(srcdir)/Include/internal/pycore_runtime.h \ + $(srcdir)/Include/internal/pycore_runtime_init.h \ + $(srcdir)/Include/internal/pycore_runtime_init_generated.h \ + $(srcdir)/Include/internal/pycore_semaphore.h \ + $(srcdir)/Include/internal/pycore_setobject.h \ + $(srcdir)/Include/internal/pycore_signal.h \ + $(srcdir)/Include/internal/pycore_sliceobject.h \ + $(srcdir)/Include/internal/pycore_strhex.h \ + $(srcdir)/Include/internal/pycore_structseq.h \ + $(srcdir)/Include/internal/pycore_symtable.h \ + $(srcdir)/Include/internal/pycore_sysmodule.h \ + $(srcdir)/Include/internal/pycore_time.h \ + $(srcdir)/Include/internal/pycore_token.h \ + $(srcdir)/Include/internal/pycore_traceback.h \ + $(srcdir)/Include/internal/pycore_tracemalloc.h \ + $(srcdir)/Include/internal/pycore_tstate.h \ + $(srcdir)/Include/internal/pycore_tuple.h \ + $(srcdir)/Include/internal/pycore_typeobject.h \ + $(srcdir)/Include/internal/pycore_typevarobject.h \ + $(srcdir)/Include/internal/pycore_ucnhash.h \ + $(srcdir)/Include/internal/pycore_unicodeobject.h \ + $(srcdir)/Include/internal/pycore_unicodeobject_generated.h \ + $(srcdir)/Include/internal/pycore_unionobject.h \ + $(srcdir)/Include/internal/pycore_uop_ids.h \ + $(srcdir)/Include/internal/pycore_uop_metadata.h \ + $(srcdir)/Include/internal/pycore_warnings.h \ + $(srcdir)/Include/internal/pycore_weakref.h \ + $(DTRACE_HEADERS) \ + @PLATFORM_HEADERS@ \ + \ + $(srcdir)/Python/stdlib_module_names.h + ########################################################################## # Build static libmpdec.a LIBMPDEC_CFLAGS=@LIBMPDEC_CFLAGS@ $(PY_STDMODULE_CFLAGS) $(CCSHARED) @@ -1400,7 +1655,7 @@ Modules/getpath.o: $(srcdir)/Modules/getpath.c Python/frozen_modules/getpath.h M Programs/python.o: $(srcdir)/Programs/python.c $(CC) -c $(PY_CORE_CFLAGS) -o $@ $(srcdir)/Programs/python.c -Programs/_testembed.o: $(srcdir)/Programs/_testembed.c Programs/test_frozenmain.h +Programs/_testembed.o: $(srcdir)/Programs/_testembed.c Programs/test_frozenmain.h $(PYTHON_HEADERS) $(CC) -c $(PY_CORE_CFLAGS) -o $@ $(srcdir)/Programs/_testembed.c Modules/_sre/sre.o: $(srcdir)/Modules/_sre/sre.c $(srcdir)/Modules/_sre/sre.h $(srcdir)/Modules/_sre/sre_constants.h $(srcdir)/Modules/_sre/sre_lib.h @@ -1669,246 +1924,6 @@ regen-typeslots: $(srcdir)/Objects/typeslots.inc.new $(UPDATE_FILE) $(srcdir)/Objects/typeslots.inc $(srcdir)/Objects/typeslots.inc.new -############################################################################ -# Header files - -PYTHON_HEADERS= \ - $(srcdir)/Include/Python.h \ - $(srcdir)/Include/abstract.h \ - $(srcdir)/Include/bltinmodule.h \ - $(srcdir)/Include/boolobject.h \ - $(srcdir)/Include/bytearrayobject.h \ - $(srcdir)/Include/bytesobject.h \ - $(srcdir)/Include/ceval.h \ - $(srcdir)/Include/codecs.h \ - $(srcdir)/Include/compile.h \ - $(srcdir)/Include/complexobject.h \ - $(srcdir)/Include/descrobject.h \ - $(srcdir)/Include/dictobject.h \ - $(srcdir)/Include/dynamic_annotations.h \ - $(srcdir)/Include/enumobject.h \ - $(srcdir)/Include/errcode.h \ - $(srcdir)/Include/fileobject.h \ - $(srcdir)/Include/fileutils.h \ - $(srcdir)/Include/floatobject.h \ - $(srcdir)/Include/frameobject.h \ - $(srcdir)/Include/import.h \ - $(srcdir)/Include/interpreteridobject.h \ - $(srcdir)/Include/intrcheck.h \ - $(srcdir)/Include/iterobject.h \ - $(srcdir)/Include/listobject.h \ - $(srcdir)/Include/longobject.h \ - $(srcdir)/Include/marshal.h \ - $(srcdir)/Include/memoryobject.h \ - $(srcdir)/Include/methodobject.h \ - $(srcdir)/Include/modsupport.h \ - $(srcdir)/Include/moduleobject.h \ - $(srcdir)/Include/object.h \ - $(srcdir)/Include/objimpl.h \ - $(srcdir)/Include/opcode.h \ - $(srcdir)/Include/opcode_ids.h \ - $(srcdir)/Include/osdefs.h \ - $(srcdir)/Include/osmodule.h \ - $(srcdir)/Include/patchlevel.h \ - $(srcdir)/Include/pybuffer.h \ - $(srcdir)/Include/pycapsule.h \ - $(srcdir)/Include/pydtrace.h \ - $(srcdir)/Include/pyerrors.h \ - $(srcdir)/Include/pyframe.h \ - $(srcdir)/Include/pyhash.h \ - $(srcdir)/Include/pylifecycle.h \ - $(srcdir)/Include/pymacconfig.h \ - $(srcdir)/Include/pymacro.h \ - $(srcdir)/Include/pymath.h \ - $(srcdir)/Include/pymem.h \ - $(srcdir)/Include/pyport.h \ - $(srcdir)/Include/pystate.h \ - $(srcdir)/Include/pystats.h \ - $(srcdir)/Include/pystrcmp.h \ - $(srcdir)/Include/pystrtod.h \ - $(srcdir)/Include/pythonrun.h \ - $(srcdir)/Include/pythread.h \ - $(srcdir)/Include/pytypedefs.h \ - $(srcdir)/Include/rangeobject.h \ - $(srcdir)/Include/setobject.h \ - $(srcdir)/Include/sliceobject.h \ - $(srcdir)/Include/structmember.h \ - $(srcdir)/Include/structseq.h \ - $(srcdir)/Include/sysmodule.h \ - $(srcdir)/Include/traceback.h \ - $(srcdir)/Include/tupleobject.h \ - $(srcdir)/Include/unicodeobject.h \ - $(srcdir)/Include/warnings.h \ - $(srcdir)/Include/weakrefobject.h \ - \ - pyconfig.h \ - $(PARSER_HEADERS) \ - \ - $(srcdir)/Include/cpython/abstract.h \ - $(srcdir)/Include/cpython/bytearrayobject.h \ - $(srcdir)/Include/cpython/bytesobject.h \ - $(srcdir)/Include/cpython/cellobject.h \ - $(srcdir)/Include/cpython/ceval.h \ - $(srcdir)/Include/cpython/classobject.h \ - $(srcdir)/Include/cpython/code.h \ - $(srcdir)/Include/cpython/compile.h \ - $(srcdir)/Include/cpython/complexobject.h \ - $(srcdir)/Include/cpython/context.h \ - $(srcdir)/Include/cpython/descrobject.h \ - $(srcdir)/Include/cpython/dictobject.h \ - $(srcdir)/Include/cpython/fileobject.h \ - $(srcdir)/Include/cpython/fileutils.h \ - $(srcdir)/Include/cpython/floatobject.h \ - $(srcdir)/Include/cpython/frameobject.h \ - $(srcdir)/Include/cpython/funcobject.h \ - $(srcdir)/Include/cpython/genobject.h \ - $(srcdir)/Include/cpython/import.h \ - $(srcdir)/Include/cpython/initconfig.h \ - $(srcdir)/Include/cpython/interpreteridobject.h \ - $(srcdir)/Include/cpython/listobject.h \ - $(srcdir)/Include/cpython/longintrepr.h \ - $(srcdir)/Include/cpython/longobject.h \ - $(srcdir)/Include/cpython/memoryobject.h \ - $(srcdir)/Include/cpython/methodobject.h \ - $(srcdir)/Include/cpython/object.h \ - $(srcdir)/Include/cpython/objimpl.h \ - $(srcdir)/Include/cpython/odictobject.h \ - $(srcdir)/Include/cpython/optimizer.h \ - $(srcdir)/Include/cpython/picklebufobject.h \ - $(srcdir)/Include/cpython/pthread_stubs.h \ - $(srcdir)/Include/cpython/pyatomic.h \ - $(srcdir)/Include/cpython/pyatomic_gcc.h \ - $(srcdir)/Include/cpython/pyatomic_std.h \ - $(srcdir)/Include/cpython/pyctype.h \ - $(srcdir)/Include/cpython/pydebug.h \ - $(srcdir)/Include/cpython/pyerrors.h \ - $(srcdir)/Include/cpython/pyfpe.h \ - $(srcdir)/Include/cpython/pyframe.h \ - $(srcdir)/Include/cpython/pyhash.h \ - $(srcdir)/Include/cpython/pylifecycle.h \ - $(srcdir)/Include/cpython/pymem.h \ - $(srcdir)/Include/cpython/pystate.h \ - $(srcdir)/Include/cpython/pystats.h \ - $(srcdir)/Include/cpython/pythonrun.h \ - $(srcdir)/Include/cpython/pythread.h \ - $(srcdir)/Include/cpython/setobject.h \ - $(srcdir)/Include/cpython/sysmodule.h \ - $(srcdir)/Include/cpython/traceback.h \ - $(srcdir)/Include/cpython/tracemalloc.h \ - $(srcdir)/Include/cpython/tupleobject.h \ - $(srcdir)/Include/cpython/unicodeobject.h \ - $(srcdir)/Include/cpython/warnings.h \ - $(srcdir)/Include/cpython/weakrefobject.h \ - \ - $(MIMALLOC_HEADERS) \ - \ - $(srcdir)/Include/internal/pycore_abstract.h \ - $(srcdir)/Include/internal/pycore_asdl.h \ - $(srcdir)/Include/internal/pycore_ast.h \ - $(srcdir)/Include/internal/pycore_ast_state.h \ - $(srcdir)/Include/internal/pycore_atexit.h \ - $(srcdir)/Include/internal/pycore_bitutils.h \ - $(srcdir)/Include/internal/pycore_bytes_methods.h \ - $(srcdir)/Include/internal/pycore_bytesobject.h \ - $(srcdir)/Include/internal/pycore_call.h \ - $(srcdir)/Include/internal/pycore_capsule.h \ - $(srcdir)/Include/internal/pycore_ceval.h \ - $(srcdir)/Include/internal/pycore_ceval_state.h \ - $(srcdir)/Include/internal/pycore_code.h \ - $(srcdir)/Include/internal/pycore_codecs.h \ - $(srcdir)/Include/internal/pycore_compile.h \ - $(srcdir)/Include/internal/pycore_complexobject.h \ - $(srcdir)/Include/internal/pycore_condvar.h \ - $(srcdir)/Include/internal/pycore_context.h \ - $(srcdir)/Include/internal/pycore_critical_section.h \ - $(srcdir)/Include/internal/pycore_crossinterp.h \ - $(srcdir)/Include/internal/pycore_dict.h \ - $(srcdir)/Include/internal/pycore_dict_state.h \ - $(srcdir)/Include/internal/pycore_descrobject.h \ - $(srcdir)/Include/internal/pycore_dtoa.h \ - $(srcdir)/Include/internal/pycore_exceptions.h \ - $(srcdir)/Include/internal/pycore_faulthandler.h \ - $(srcdir)/Include/internal/pycore_fileutils.h \ - $(srcdir)/Include/internal/pycore_floatobject.h \ - $(srcdir)/Include/internal/pycore_format.h \ - $(srcdir)/Include/internal/pycore_frame.h \ - $(srcdir)/Include/internal/pycore_freelist.h \ - $(srcdir)/Include/internal/pycore_function.h \ - $(srcdir)/Include/internal/pycore_gc.h \ - $(srcdir)/Include/internal/pycore_genobject.h \ - $(srcdir)/Include/internal/pycore_getopt.h \ - $(srcdir)/Include/internal/pycore_gil.h \ - $(srcdir)/Include/internal/pycore_global_objects.h \ - $(srcdir)/Include/internal/pycore_global_objects_fini_generated.h \ - $(srcdir)/Include/internal/pycore_hamt.h \ - $(srcdir)/Include/internal/pycore_hashtable.h \ - $(srcdir)/Include/internal/pycore_identifier.h \ - $(srcdir)/Include/internal/pycore_import.h \ - $(srcdir)/Include/internal/pycore_initconfig.h \ - $(srcdir)/Include/internal/pycore_interp.h \ - $(srcdir)/Include/internal/pycore_intrinsics.h \ - $(srcdir)/Include/internal/pycore_jit.h \ - $(srcdir)/Include/internal/pycore_list.h \ - $(srcdir)/Include/internal/pycore_llist.h \ - $(srcdir)/Include/internal/pycore_lock.h \ - $(srcdir)/Include/internal/pycore_long.h \ - $(srcdir)/Include/internal/pycore_modsupport.h \ - $(srcdir)/Include/internal/pycore_moduleobject.h \ - $(srcdir)/Include/internal/pycore_namespace.h \ - $(srcdir)/Include/internal/pycore_object.h \ - $(srcdir)/Include/internal/pycore_object_alloc.h \ - $(srcdir)/Include/internal/pycore_object_stack.h \ - $(srcdir)/Include/internal/pycore_object_state.h \ - $(srcdir)/Include/internal/pycore_obmalloc.h \ - $(srcdir)/Include/internal/pycore_obmalloc_init.h \ - $(srcdir)/Include/internal/pycore_opcode_metadata.h \ - $(srcdir)/Include/internal/pycore_opcode_utils.h \ - $(srcdir)/Include/internal/pycore_optimizer.h \ - $(srcdir)/Include/internal/pycore_parking_lot.h \ - $(srcdir)/Include/internal/pycore_pathconfig.h \ - $(srcdir)/Include/internal/pycore_pyarena.h \ - $(srcdir)/Include/internal/pycore_pybuffer.h \ - $(srcdir)/Include/internal/pycore_pyerrors.h \ - $(srcdir)/Include/internal/pycore_pyhash.h \ - $(srcdir)/Include/internal/pycore_pylifecycle.h \ - $(srcdir)/Include/internal/pycore_pymem.h \ - $(srcdir)/Include/internal/pycore_pymem_init.h \ - $(srcdir)/Include/internal/pycore_pystate.h \ - $(srcdir)/Include/internal/pycore_pystats.h \ - $(srcdir)/Include/internal/pycore_pythonrun.h \ - $(srcdir)/Include/internal/pycore_pythread.h \ - $(srcdir)/Include/internal/pycore_range.h \ - $(srcdir)/Include/internal/pycore_runtime.h \ - $(srcdir)/Include/internal/pycore_runtime_init_generated.h \ - $(srcdir)/Include/internal/pycore_runtime_init.h \ - $(srcdir)/Include/internal/pycore_semaphore.h \ - $(srcdir)/Include/internal/pycore_setobject.h \ - $(srcdir)/Include/internal/pycore_signal.h \ - $(srcdir)/Include/internal/pycore_sliceobject.h \ - $(srcdir)/Include/internal/pycore_strhex.h \ - $(srcdir)/Include/internal/pycore_structseq.h \ - $(srcdir)/Include/internal/pycore_symtable.h \ - $(srcdir)/Include/internal/pycore_sysmodule.h \ - $(srcdir)/Include/internal/pycore_time.h \ - $(srcdir)/Include/internal/pycore_token.h \ - $(srcdir)/Include/internal/pycore_traceback.h \ - $(srcdir)/Include/internal/pycore_tracemalloc.h \ - $(srcdir)/Include/internal/pycore_tstate.h \ - $(srcdir)/Include/internal/pycore_tuple.h \ - $(srcdir)/Include/internal/pycore_typeobject.h \ - $(srcdir)/Include/internal/pycore_typevarobject.h \ - $(srcdir)/Include/internal/pycore_ucnhash.h \ - $(srcdir)/Include/internal/pycore_unionobject.h \ - $(srcdir)/Include/internal/pycore_unicodeobject.h \ - $(srcdir)/Include/internal/pycore_unicodeobject_generated.h \ - $(srcdir)/Include/internal/pycore_uop_metadata.h \ - $(srcdir)/Include/internal/pycore_warnings.h \ - $(srcdir)/Include/internal/pycore_weakref.h \ - $(DTRACE_HEADERS) \ - @PLATFORM_HEADERS@ \ - \ - $(srcdir)/Python/stdlib_module_names.h - $(LIBRARY_OBJS) $(MODOBJS) Programs/python.o: $(PYTHON_HEADERS) @@ -2877,6 +2892,9 @@ Python/thread.o: @THREADHEADERS@ $(srcdir)/Python/condvar.h MODULE_DEPS_STATIC=Modules/config.c MODULE_DEPS_SHARED=$(MODULE_DEPS_STATIC) $(EXPORTSYMS) +MODULE__CURSES_DEPS=$(srcdir)/Include/py_curses.h +MODULE__CURSES_PANEL_DEPS=$(srcdir)/Include/py_curses.h +MODULE__DATETIME_DEPS=$(srcdir)/Include/datetime.h MODULE_CMATH_DEPS=$(srcdir)/Modules/_math.h MODULE_MATH_DEPS=$(srcdir)/Modules/_math.h MODULE_PYEXPAT_DEPS=@LIBEXPAT_INTERNAL@ From d0322fdf2c1a7292a43959fe5a572d783b88a1c4 Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Wed, 7 Feb 2024 04:48:42 -0600 Subject: [PATCH 168/507] gh-101100: Fix Py_DEBUG dangling Sphinx references (#115003) --- Doc/c-api/intro.rst | 11 ++++++----- Doc/library/test.rst | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Doc/c-api/intro.rst b/Doc/c-api/intro.rst index 4dbca92b18b5cd2..dcda1071a58f35b 100644 --- a/Doc/c-api/intro.rst +++ b/Doc/c-api/intro.rst @@ -148,7 +148,7 @@ complete listing. worse performances (due to increased code size for example). The compiler is usually smarter than the developer for the cost/benefit analysis. - If Python is :ref:`built in debug mode <debug-build>` (if the ``Py_DEBUG`` + If Python is :ref:`built in debug mode <debug-build>` (if the :c:macro:`Py_DEBUG` macro is defined), the :c:macro:`Py_ALWAYS_INLINE` macro does nothing. It must be specified before the function return type. Usage:: @@ -812,12 +812,14 @@ available that support tracing of reference counts, debugging the memory allocator, or low-level profiling of the main interpreter loop. Only the most frequently used builds will be described in the remainder of this section. -Compiling the interpreter with the :c:macro:`Py_DEBUG` macro defined produces +.. c:macro:: Py_DEBUG + +Compiling the interpreter with the :c:macro:`!Py_DEBUG` macro defined produces what is generally meant by :ref:`a debug build of Python <debug-build>`. -:c:macro:`Py_DEBUG` is enabled in the Unix build by adding +:c:macro:`!Py_DEBUG` is enabled in the Unix build by adding :option:`--with-pydebug` to the :file:`./configure` command. It is also implied by the presence of the -not-Python-specific :c:macro:`_DEBUG` macro. When :c:macro:`Py_DEBUG` is enabled +not-Python-specific :c:macro:`!_DEBUG` macro. When :c:macro:`!Py_DEBUG` is enabled in the Unix build, compiler optimization is disabled. In addition to the reference count debugging described below, extra checks are @@ -832,4 +834,3 @@ after every statement run by the interpreter.) Please refer to :file:`Misc/SpecialBuilds.txt` in the Python source distribution for more detailed information. - diff --git a/Doc/library/test.rst b/Doc/library/test.rst index cad1023021a5129..7d28f6253457263 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -324,9 +324,9 @@ The :mod:`test.support` module defines the following constants: .. data:: Py_DEBUG - True if Python is built with the :c:macro:`Py_DEBUG` macro defined: if - Python is :ref:`built in debug mode <debug-build>` - (:option:`./configure --with-pydebug <--with-pydebug>`). + True if Python was built with the :c:macro:`Py_DEBUG` macro + defined, that is, if + Python was :ref:`built in debug mode <debug-build>`. .. versionadded:: 3.12 From 8a3c499ffe7e15297dd4c0b446a0b97b4d32108a Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Wed, 7 Feb 2024 12:38:34 +0000 Subject: [PATCH 169/507] GH-108362: Revert "GH-108362: Incremental GC implementation (GH-108038)" (#115132) Revert "GH-108362: Incremental GC implementation (GH-108038)" This reverts commit 36518e69d74607e5f094ce55286188e4545a947d. --- Doc/whatsnew/3.13.rst | 34 - Include/internal/pycore_gc.h | 42 +- Include/internal/pycore_object.h | 17 +- Include/internal/pycore_runtime_init.h | 8 +- Lib/test/test_gc.py | 22 +- ...-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst | 13 - Modules/gcmodule.c | 23 +- Objects/object.c | 15 - Objects/structseq.c | 5 +- Python/gc.c | 824 +++++++----------- Python/gc_free_threading.c | 27 +- Python/import.c | 2 +- Tools/gdb/libpython.py | 7 +- 13 files changed, 392 insertions(+), 647 deletions(-) delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index c75d44065313940..2ac5afa8ce601cb 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -92,10 +92,6 @@ Interpreter improvements: New Features ============ -* The cyclic garbage collector is now incremental. - This means that maximum pause times are reduced, - by an order of magnitude or more for larger heaps. - Improved Error Messages ----------------------- @@ -105,13 +101,6 @@ Improved Error Messages variables. See also :ref:`using-on-controlling-color`. (Contributed by Pablo Galindo Salgado in :gh:`112730`.) -Incremental Garbage Collection ------------------------------- - -* The cycle garbage collector is now incremental. - This means that maximum pause times are reduced - by an order of magnitude or more for larger heaps. - Other Language Changes ====================== @@ -257,29 +246,6 @@ fractions sign handling, minimum width and grouping. (Contributed by Mark Dickinson in :gh:`111320`.) -gc --- -* The cyclic garbage collector is now incremental, which changes the meanings - of the results of :meth:`gc.get_threshold` and :meth:`gc.get_threshold` as - well as :meth:`gc.get_count` and :meth:`gc.get_stats`. -* :meth:`gc.get_threshold` returns a three-tuple for backwards compatibility, - the first value is the threshold for young collections, as before, the second - value determines the rate at which the old collection is scanned; the - default is 10 and higher values mean that the old collection is scanned more slowly. - The third value is meangless and is always zero. -* :meth:`gc.set_threshold` ignores any items after the second. -* :meth:`gc.get_count` and :meth:`gc.get_stats`. - These functions return the same format of results as before. - The only difference is that instead of the results refering to - the young, aging and old generations, the results refer to the - young generation and the aging and collecting spaces of the old generation. - -In summary, code that attempted to manipulate the behavior of the cycle GC may -not work as well as intended, but it is very unlikely to harmful. -All other code will work just fine. -Uses should avoid calling :meth:`gc.collect` unless their workload is episodic, -but that has always been the case to some extent. - glob ---- diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h index aeb07238fc83455..8d0bc2a218e48de 100644 --- a/Include/internal/pycore_gc.h +++ b/Include/internal/pycore_gc.h @@ -88,15 +88,11 @@ static inline void _PyObject_GC_SET_SHARED(PyObject *op) { /* Bit flags for _gc_prev */ /* Bit 0 is set when tp_finalize is called */ -#define _PyGC_PREV_MASK_FINALIZED 1 +#define _PyGC_PREV_MASK_FINALIZED (1) /* Bit 1 is set when the object is in generation which is GCed currently. */ -#define _PyGC_PREV_MASK_COLLECTING 2 - -/* Bit 0 is set if the object belongs to old space 1 */ -#define _PyGC_NEXT_MASK_OLD_SPACE_1 1 - +#define _PyGC_PREV_MASK_COLLECTING (2) /* The (N-2) most significant bits contain the real address. */ -#define _PyGC_PREV_SHIFT 2 +#define _PyGC_PREV_SHIFT (2) #define _PyGC_PREV_MASK (((uintptr_t) -1) << _PyGC_PREV_SHIFT) /* set for debugging information */ @@ -122,13 +118,11 @@ typedef enum { // Lowest bit of _gc_next is used for flags only in GC. // But it is always 0 for normal code. static inline PyGC_Head* _PyGCHead_NEXT(PyGC_Head *gc) { - uintptr_t next = gc->_gc_next & _PyGC_PREV_MASK; + uintptr_t next = gc->_gc_next; return (PyGC_Head*)next; } static inline void _PyGCHead_SET_NEXT(PyGC_Head *gc, PyGC_Head *next) { - uintptr_t unext = (uintptr_t)next; - assert((unext & ~_PyGC_PREV_MASK) == 0); - gc->_gc_next = (gc->_gc_next & ~_PyGC_PREV_MASK) | unext; + gc->_gc_next = (uintptr_t)next; } // Lowest two bits of _gc_prev is used for _PyGC_PREV_MASK_* flags. @@ -136,7 +130,6 @@ static inline PyGC_Head* _PyGCHead_PREV(PyGC_Head *gc) { uintptr_t prev = (gc->_gc_prev & _PyGC_PREV_MASK); return (PyGC_Head*)prev; } - static inline void _PyGCHead_SET_PREV(PyGC_Head *gc, PyGC_Head *prev) { uintptr_t uprev = (uintptr_t)prev; assert((uprev & ~_PyGC_PREV_MASK) == 0); @@ -222,13 +215,6 @@ struct gc_generation { generations */ }; -struct gc_collection_stats { - /* number of collected objects */ - Py_ssize_t collected; - /* total number of uncollectable objects (put into gc.garbage) */ - Py_ssize_t uncollectable; -}; - /* Running stats per generation */ struct gc_generation_stats { /* total number of collections */ @@ -250,8 +236,8 @@ struct _gc_runtime_state { int enabled; int debug; /* linked lists of container objects */ - struct gc_generation young; - struct gc_generation old[2]; + struct gc_generation generations[NUM_GENERATIONS]; + PyGC_Head *generation0; /* a permanent generation which won't be collected */ struct gc_generation permanent_generation; struct gc_generation_stats generation_stats[NUM_GENERATIONS]; @@ -264,20 +250,22 @@ struct _gc_runtime_state { /* This is the number of objects that survived the last full collection. It approximates the number of long lived objects tracked by the GC. + (by "full collection", we mean a collection of the oldest generation). */ Py_ssize_t long_lived_total; - - Py_ssize_t work_to_do; - /* Which of the old spaces is the visited space */ - int visited_space; + /* This is the number of objects that survived all "non-full" + collections, and are awaiting to undergo a full collection for + the first time. */ + Py_ssize_t long_lived_pending; }; extern void _PyGC_InitState(struct _gc_runtime_state *); -extern Py_ssize_t _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason); -extern void _PyGC_CollectNoFail(PyThreadState *tstate); +extern Py_ssize_t _PyGC_Collect(PyThreadState *tstate, int generation, + _PyGC_Reason reason); +extern Py_ssize_t _PyGC_CollectNoFail(PyThreadState *tstate); /* Freeze objects tracked by the GC and ignore them in future collections. */ extern void _PyGC_Freeze(PyInterpreterState *interp); diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index efa712c4a0b4588..34a83ea228e8b10 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -125,7 +125,19 @@ static inline void _Py_RefcntAdd(PyObject* op, Py_ssize_t n) } #define _Py_RefcntAdd(op, n) _Py_RefcntAdd(_PyObject_CAST(op), n) -extern void _Py_SetImmortal(PyObject *op); +static inline void _Py_SetImmortal(PyObject *op) +{ + if (op) { +#ifdef Py_GIL_DISABLED + op->ob_tid = _Py_UNOWNED_TID; + op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL; + op->ob_ref_shared = 0; +#else + op->ob_refcnt = _Py_IMMORTAL_REFCNT; +#endif + } +} +#define _Py_SetImmortal(op) _Py_SetImmortal(_PyObject_CAST(op)) // Makes an immortal object mortal again with the specified refcnt. Should only // be used during runtime finalization. @@ -313,12 +325,11 @@ static inline void _PyObject_GC_TRACK( filename, lineno, __func__); PyInterpreterState *interp = _PyInterpreterState_GET(); - PyGC_Head *generation0 = &interp->gc.young.head; + PyGC_Head *generation0 = interp->gc.generation0; PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev); _PyGCHead_SET_NEXT(last, gc); _PyGCHead_SET_PREV(gc, last); _PyGCHead_SET_NEXT(gc, generation0); - assert((gc->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1) == 0); generation0->_gc_prev = (uintptr_t)gc; #endif } diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index 2ad1347ad48a596..571a7d612c94e25 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -162,12 +162,12 @@ extern PyTypeObject _PyExc_MemoryError; }, \ .gc = { \ .enabled = 1, \ - .young = { .threshold = 2000, }, \ - .old = { \ + .generations = { \ + /* .head is set in _PyGC_InitState(). */ \ + { .threshold = 700, }, \ + { .threshold = 10, }, \ { .threshold = 10, }, \ - { .threshold = 0, }, \ }, \ - .work_to_do = -5000, \ }, \ .object_state = _py_object_state_INIT(INTERP), \ .dtoa = _dtoa_state_INIT(&(INTERP)), \ diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index 0002852fce96438..b01f344cb14a1a7 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -383,11 +383,19 @@ def test_collect_generations(self): # each call to collect(N) x = [] gc.collect(0) - # x is now in the old gen + # x is now in gen 1 a, b, c = gc.get_count() - # We don't check a since its exact values depends on + gc.collect(1) + # x is now in gen 2 + d, e, f = gc.get_count() + gc.collect(2) + # x is now in gen 3 + g, h, i = gc.get_count() + # We don't check a, d, g since their exact values depends on # internal implementation details of the interpreter. self.assertEqual((b, c), (1, 0)) + self.assertEqual((e, f), (0, 1)) + self.assertEqual((h, i), (0, 0)) def test_trashcan(self): class Ouch: @@ -838,6 +846,16 @@ def test_get_objects_generations(self): self.assertFalse( any(l is element for element in gc.get_objects(generation=2)) ) + gc.collect(generation=1) + self.assertFalse( + any(l is element for element in gc.get_objects(generation=0)) + ) + self.assertFalse( + any(l is element for element in gc.get_objects(generation=1)) + ) + self.assertTrue( + any(l is element for element in gc.get_objects(generation=2)) + ) gc.collect(generation=2) self.assertFalse( any(l is element for element in gc.get_objects(generation=0)) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst deleted file mode 100644 index 1fe4e0f41e12951..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst +++ /dev/null @@ -1,13 +0,0 @@ -Implements an incremental cyclic garbage collector. By collecting the old -generation in increments, there is no need for a full heap scan. This can -hugely reduce maximum pause time for programs with large heaps. - -Reduces the number of generations from three to two. The old generation is -split into two spaces, "aging" and "collecting". - -Collection happens in two steps:: * First, the young generation is scanned -and the survivors moved to the end of the aging space. * Then objects are -taken from the collecting space, at such a rate that all cycles are -collected eventually. Those objects are then scanned and the survivors -moved to the end of the aging space. When the collecting space becomes -empty, the two spaces are swapped. diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 3a42654b41b2acb..a2b66b9b78c169d 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -158,12 +158,17 @@ gc_set_threshold_impl(PyObject *module, int threshold0, int group_right_1, { GCState *gcstate = get_gc_state(); - gcstate->young.threshold = threshold0; + gcstate->generations[0].threshold = threshold0; if (group_right_1) { - gcstate->old[0].threshold = threshold1; + gcstate->generations[1].threshold = threshold1; } if (group_right_2) { - gcstate->old[1].threshold = threshold2; + gcstate->generations[2].threshold = threshold2; + + /* generations higher than 2 get the same threshold */ + for (int i = 3; i < NUM_GENERATIONS; i++) { + gcstate->generations[i].threshold = gcstate->generations[2].threshold; + } } Py_RETURN_NONE; } @@ -180,9 +185,9 @@ gc_get_threshold_impl(PyObject *module) { GCState *gcstate = get_gc_state(); return Py_BuildValue("(iii)", - gcstate->young.threshold, - gcstate->old[0].threshold, - 0); + gcstate->generations[0].threshold, + gcstate->generations[1].threshold, + gcstate->generations[2].threshold); } /*[clinic input] @@ -197,9 +202,9 @@ gc_get_count_impl(PyObject *module) { GCState *gcstate = get_gc_state(); return Py_BuildValue("(iii)", - gcstate->young.count, - gcstate->old[gcstate->visited_space].count, - gcstate->old[gcstate->visited_space^1].count); + gcstate->generations[0].count, + gcstate->generations[1].count, + gcstate->generations[2].count); } /*[clinic input] diff --git a/Objects/object.c b/Objects/object.c index 7247eb21df6b6eb..bbf7f98ae3daf92 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2387,21 +2387,6 @@ _Py_NewReferenceNoTotal(PyObject *op) new_reference(op); } -void -_Py_SetImmortal(PyObject *op) -{ - if (PyObject_IS_GC(op) && _PyObject_GC_IS_TRACKED(op)) { - _PyObject_GC_UNTRACK(op); - } -#ifdef Py_GIL_DISABLED - op->ob_tid = _Py_UNOWNED_TID; - op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL; - op->ob_ref_shared = 0; -#else - op->ob_refcnt = _Py_IMMORTAL_REFCNT; -#endif -} - void _Py_ResurrectReference(PyObject *op) { diff --git a/Objects/structseq.c b/Objects/structseq.c index 661d96a968fb809..581d6ad240885a0 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -603,9 +603,6 @@ _PyStructSequence_InitBuiltinWithFlags(PyInterpreterState *interp, PyStructSequence_Desc *desc, unsigned long tp_flags) { - if (Py_TYPE(type) == NULL) { - Py_SET_TYPE(type, &PyType_Type); - } Py_ssize_t n_unnamed_members; Py_ssize_t n_members = count_members(desc, &n_unnamed_members); PyMemberDef *members = NULL; @@ -621,7 +618,7 @@ _PyStructSequence_InitBuiltinWithFlags(PyInterpreterState *interp, } initialize_static_fields(type, desc, members, tp_flags); - _Py_SetImmortal((PyObject *)type); + _Py_SetImmortal(type); } #ifndef NDEBUG else { diff --git a/Python/gc.c b/Python/gc.c index cda12ff7fbc982f..466467602915264 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -45,7 +45,7 @@ typedef struct _gc_runtime_state GCState; // move_legacy_finalizers() removes this flag instead. // Between them, unreachable list is not normal list and we can not use // most gc_list_* functions for it. -#define NEXT_MASK_UNREACHABLE 2 +#define NEXT_MASK_UNREACHABLE (1) #define AS_GC(op) _Py_AS_GC(op) #define FROM_GC(gc) _Py_FROM_GC(gc) @@ -95,48 +95,9 @@ gc_decref(PyGC_Head *g) g->_gc_prev -= 1 << _PyGC_PREV_SHIFT; } -static inline int -gc_old_space(PyGC_Head *g) -{ - return g->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1; -} -static inline int -flip_old_space(int space) -{ - assert(space == 0 || space == 1); - return space ^ _PyGC_NEXT_MASK_OLD_SPACE_1; -} +#define GEN_HEAD(gcstate, n) (&(gcstate)->generations[n].head) -static inline void -gc_flip_old_space(PyGC_Head *g) -{ - g->_gc_next ^= _PyGC_NEXT_MASK_OLD_SPACE_1; -} - -static inline void -gc_set_old_space(PyGC_Head *g, int space) -{ - assert(space == 0 || space == _PyGC_NEXT_MASK_OLD_SPACE_1); - g->_gc_next &= ~_PyGC_NEXT_MASK_OLD_SPACE_1; - g->_gc_next |= space; -} - -static PyGC_Head * -GEN_HEAD(GCState *gcstate, int n) -{ - assert((gcstate->visited_space & (~1)) == 0); - switch(n) { - case 0: - return &gcstate->young.head; - case 1: - return &gcstate->old[gcstate->visited_space].head; - case 2: - return &gcstate->old[gcstate->visited_space^1].head; - default: - Py_UNREACHABLE(); - } -} static GCState * get_gc_state(void) @@ -155,12 +116,11 @@ _PyGC_InitState(GCState *gcstate) GEN.head._gc_prev = (uintptr_t)&GEN.head; \ } while (0) - assert(gcstate->young.count == 0); - assert(gcstate->old[0].count == 0); - assert(gcstate->old[1].count == 0); - INIT_HEAD(gcstate->young); - INIT_HEAD(gcstate->old[0]); - INIT_HEAD(gcstate->old[1]); + for (int i = 0; i < NUM_GENERATIONS; i++) { + assert(gcstate->generations[i].count == 0); + INIT_HEAD(gcstate->generations[i]); + }; + gcstate->generation0 = GEN_HEAD(gcstate, 0); INIT_HEAD(gcstate->permanent_generation); #undef INIT_HEAD @@ -258,7 +218,6 @@ gc_list_is_empty(PyGC_Head *list) static inline void gc_list_append(PyGC_Head *node, PyGC_Head *list) { - assert((list->_gc_prev & ~_PyGC_PREV_MASK) == 0); PyGC_Head *last = (PyGC_Head *)list->_gc_prev; // last <-> node @@ -316,8 +275,6 @@ gc_list_merge(PyGC_Head *from, PyGC_Head *to) PyGC_Head *from_tail = GC_PREV(from); assert(from_head != from); assert(from_tail != from); - assert(gc_list_is_empty(to) || - gc_old_space(to_tail) == gc_old_space(from_tail)); _PyGCHead_SET_NEXT(to_tail, from_head); _PyGCHead_SET_PREV(from_head, to_tail); @@ -386,8 +343,8 @@ enum flagstates {collecting_clear_unreachable_clear, static void validate_list(PyGC_Head *head, enum flagstates flags) { - assert((head->_gc_prev & ~_PyGC_PREV_MASK) == 0); - assert((head->_gc_next & ~_PyGC_PREV_MASK) == 0); + assert((head->_gc_prev & PREV_MASK_COLLECTING) == 0); + assert((head->_gc_next & NEXT_MASK_UNREACHABLE) == 0); uintptr_t prev_value = 0, next_value = 0; switch (flags) { case collecting_clear_unreachable_clear: @@ -409,7 +366,7 @@ validate_list(PyGC_Head *head, enum flagstates flags) PyGC_Head *gc = GC_NEXT(head); while (gc != head) { PyGC_Head *trueprev = GC_PREV(gc); - PyGC_Head *truenext = GC_NEXT(gc); + PyGC_Head *truenext = (PyGC_Head *)(gc->_gc_next & ~NEXT_MASK_UNREACHABLE); assert(truenext != NULL); assert(trueprev == prev); assert((gc->_gc_prev & PREV_MASK_COLLECTING) == prev_value); @@ -419,44 +376,8 @@ validate_list(PyGC_Head *head, enum flagstates flags) } assert(prev == GC_PREV(head)); } - -static void -validate_old(GCState *gcstate) -{ - for (int space = 0; space < 2; space++) { - PyGC_Head *head = &gcstate->old[space].head; - PyGC_Head *gc = GC_NEXT(head); - while (gc != head) { - PyGC_Head *next = GC_NEXT(gc); - assert(gc_old_space(gc) == space); - gc = next; - } - } -} - -static void -validate_consistent_old_space(PyGC_Head *head) -{ - PyGC_Head *prev = head; - PyGC_Head *gc = GC_NEXT(head); - if (gc == head) { - return; - } - int old_space = gc_old_space(gc); - while (gc != head) { - PyGC_Head *truenext = GC_NEXT(gc); - assert(truenext != NULL); - assert(gc_old_space(gc) == old_space); - prev = gc; - gc = truenext; - } - assert(prev == GC_PREV(head)); -} - #else #define validate_list(x, y) do{}while(0) -#define validate_old(g) do{}while(0) -#define validate_consistent_old_space(l) do{}while(0) #endif /*** end of list stuff ***/ @@ -473,7 +394,15 @@ update_refs(PyGC_Head *containers) while (gc != containers) { next = GC_NEXT(gc); - assert(!_Py_IsImmortal(FROM_GC(gc))); + /* Move any object that might have become immortal to the + * permanent generation as the reference count is not accurately + * reflecting the actual number of live references to this object + */ + if (_Py_IsImmortal(FROM_GC(gc))) { + gc_list_move(gc, &get_gc_state()->permanent_generation.head); + gc = next; + continue; + } gc_reset_refs(gc, Py_REFCNT(FROM_GC(gc))); /* Python's cyclic gc should never see an incoming refcount * of 0: if something decref'ed to 0, it should have been @@ -571,13 +500,12 @@ visit_reachable(PyObject *op, void *arg) // Manually unlink gc from unreachable list because the list functions // don't work right in the presence of NEXT_MASK_UNREACHABLE flags. PyGC_Head *prev = GC_PREV(gc); - PyGC_Head *next = GC_NEXT(gc); + PyGC_Head *next = (PyGC_Head*)(gc->_gc_next & ~NEXT_MASK_UNREACHABLE); _PyObject_ASSERT(FROM_GC(prev), prev->_gc_next & NEXT_MASK_UNREACHABLE); _PyObject_ASSERT(FROM_GC(next), next->_gc_next & NEXT_MASK_UNREACHABLE); - prev->_gc_next = gc->_gc_next; // copy flag bits - gc->_gc_next &= ~NEXT_MASK_UNREACHABLE; + prev->_gc_next = gc->_gc_next; // copy NEXT_MASK_UNREACHABLE _PyGCHead_SET_PREV(next, prev); gc_list_append(gc, reachable); @@ -629,9 +557,6 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable) * or to the right have been scanned yet. */ - validate_consistent_old_space(young); - /* Record which old space we are in, and set NEXT_MASK_UNREACHABLE bit for convenience */ - uintptr_t flags = NEXT_MASK_UNREACHABLE | (gc->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1); while (gc != young) { if (gc_get_refs(gc)) { /* gc is definitely reachable from outside the @@ -677,18 +602,17 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable) // But this may pollute the unreachable list head's 'next' pointer // too. That's semantically senseless but expedient here - the // damage is repaired when this function ends. - last->_gc_next = flags | (uintptr_t)gc; + last->_gc_next = (NEXT_MASK_UNREACHABLE | (uintptr_t)gc); _PyGCHead_SET_PREV(gc, last); - gc->_gc_next = flags | (uintptr_t)unreachable; + gc->_gc_next = (NEXT_MASK_UNREACHABLE | (uintptr_t)unreachable); unreachable->_gc_prev = (uintptr_t)gc; } - gc = _PyGCHead_NEXT(prev); + gc = (PyGC_Head*)prev->_gc_next; } // young->_gc_prev must be last element remained in the list. young->_gc_prev = (uintptr_t)prev; - young->_gc_next &= _PyGC_PREV_MASK; // don't let the pollution of the list head's next pointer leak - unreachable->_gc_next &= _PyGC_PREV_MASK; + unreachable->_gc_next &= ~NEXT_MASK_UNREACHABLE; } static void @@ -745,8 +669,8 @@ move_legacy_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers) PyObject *op = FROM_GC(gc); _PyObject_ASSERT(op, gc->_gc_next & NEXT_MASK_UNREACHABLE); - next = GC_NEXT(gc); gc->_gc_next &= ~NEXT_MASK_UNREACHABLE; + next = (PyGC_Head*)gc->_gc_next; if (has_legacy_finalizer(op)) { gc_clear_collecting(gc); @@ -765,8 +689,8 @@ clear_unreachable_mask(PyGC_Head *unreachable) assert((unreachable->_gc_next & NEXT_MASK_UNREACHABLE) == 0); for (gc = GC_NEXT(unreachable); gc != unreachable; gc = next) { _PyObject_ASSERT((PyObject*)FROM_GC(gc), gc->_gc_next & NEXT_MASK_UNREACHABLE); - next = GC_NEXT(gc); gc->_gc_next &= ~NEXT_MASK_UNREACHABLE; + next = (PyGC_Head*)gc->_gc_next; } validate_list(unreachable, collecting_set_unreachable_clear); } @@ -1099,6 +1023,25 @@ delete_garbage(PyThreadState *tstate, GCState *gcstate, } +// Show stats for objects in each generations +static void +show_stats_each_generations(GCState *gcstate) +{ + char buf[100]; + size_t pos = 0; + + for (int i = 0; i < NUM_GENERATIONS && pos < sizeof(buf); i++) { + pos += PyOS_snprintf(buf+pos, sizeof(buf)-pos, + " %zd", + gc_list_size(GEN_HEAD(gcstate, i))); + } + + PySys_FormatStderr( + "gc: objects in each generation:%s\n" + "gc: objects in permanent generation: %zd\n", + buf, gc_list_size(&gcstate->permanent_generation.head)); +} + /* Deduce which objects among "base" are unreachable from outside the list and move them to 'unreachable'. The process consist in the following steps: @@ -1172,6 +1115,7 @@ deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) { * the reachable objects instead. But this is a one-time cost, probably not * worth complicating the code to speed just a little. */ + gc_list_init(unreachable); move_unreachable(base, unreachable); // gc_prev is pointer again validate_list(base, collecting_clear_unreachable_clear); validate_list(unreachable, collecting_set_unreachable_set); @@ -1210,272 +1154,219 @@ handle_resurrected_objects(PyGC_Head *unreachable, PyGC_Head* still_unreachable, } -#define UNTRACK_TUPLES 1 -#define UNTRACK_DICTS 2 - -static void -gc_collect_region(PyThreadState *tstate, - PyGC_Head *from, - PyGC_Head *to, - int untrack, - struct gc_collection_stats *stats); - -static inline Py_ssize_t -gc_list_set_space(PyGC_Head *list, int space) -{ - Py_ssize_t size = 0; - PyGC_Head *gc; - for (gc = GC_NEXT(list); gc != list; gc = GC_NEXT(gc)) { - gc_set_old_space(gc, space); - size++; - } - return size; -} - - +/* Invoke progress callbacks to notify clients that garbage collection + * is starting or stopping + */ static void -add_stats(GCState *gcstate, int gen, struct gc_collection_stats *stats) +invoke_gc_callback(PyThreadState *tstate, const char *phase, + int generation, Py_ssize_t collected, + Py_ssize_t uncollectable) { - gcstate->generation_stats[gen].collected += stats->collected; - gcstate->generation_stats[gen].uncollectable += stats->uncollectable; - gcstate->generation_stats[gen].collections += 1; -} - - -/* Multiply by 4 so that the default incremental threshold of 10 - * scans objects at 40% the rate that the young gen tenures them. */ -#define SCAN_RATE_MULTIPLIER 4 - + assert(!_PyErr_Occurred(tstate)); -static void -gc_collect_young(PyThreadState *tstate, - struct gc_collection_stats *stats) -{ + /* we may get called very early */ GCState *gcstate = &tstate->interp->gc; - PyGC_Head *young = &gcstate->young.head; - PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head; -#ifdef Py_STATS - { - Py_ssize_t count = 0; - PyGC_Head *gc; - for (gc = GC_NEXT(young); gc != young; gc = GC_NEXT(gc)) { - count++; - } + if (gcstate->callbacks == NULL) { + return; } -#endif - PyGC_Head survivors; - gc_list_init(&survivors); - gc_collect_region(tstate, young, &survivors, UNTRACK_TUPLES, stats); - Py_ssize_t survivor_count = 0; - if (gcstate->visited_space) { - /* objects in visited space have bit set, so we set it here */ - survivor_count = gc_list_set_space(&survivors, 1); - } - else { - PyGC_Head *gc; - for (gc = GC_NEXT(&survivors); gc != &survivors; gc = GC_NEXT(gc)) { -#ifdef GC_DEBUG - assert(gc_old_space(gc) == 0); -#endif - survivor_count++; + /* The local variable cannot be rebound, check it for sanity */ + assert(PyList_CheckExact(gcstate->callbacks)); + PyObject *info = NULL; + if (PyList_GET_SIZE(gcstate->callbacks) != 0) { + info = Py_BuildValue("{sisnsn}", + "generation", generation, + "collected", collected, + "uncollectable", uncollectable); + if (info == NULL) { + PyErr_FormatUnraisable("Exception ignored on invoking gc callbacks"); + return; } } - gc_list_merge(&survivors, visited); - validate_old(gcstate); - gcstate->young.count = 0; - gcstate->old[gcstate->visited_space].count++; - Py_ssize_t scale_factor = gcstate->old[0].threshold; - if (scale_factor < 1) { - scale_factor = 1; - } - gcstate->work_to_do += survivor_count + survivor_count * SCAN_RATE_MULTIPLIER / scale_factor; - add_stats(gcstate, 0, stats); -} -static inline int -is_in_visited(PyGC_Head *gc, int visited_space) -{ - assert(visited_space == 0 || flip_old_space(visited_space) == 0); - return gc_old_space(gc) == visited_space; -} - -struct container_and_flag { - PyGC_Head *container; - int visited_space; -}; + PyObject *phase_obj = PyUnicode_FromString(phase); + if (phase_obj == NULL) { + Py_XDECREF(info); + PyErr_FormatUnraisable("Exception ignored on invoking gc callbacks"); + return; + } -/* A traversal callback for adding to container) */ -static int -visit_add_to_container(PyObject *op, void *arg) -{ - OBJECT_STAT_INC(object_visits); - struct container_and_flag *cf = (struct container_and_flag *)arg; - int visited = cf->visited_space; - assert(visited == get_gc_state()->visited_space); - if (_PyObject_IS_GC(op)) { - PyGC_Head *gc = AS_GC(op); - if (_PyObject_GC_IS_TRACKED(op) && - gc_old_space(gc) != visited) { - assert(!_Py_IsImmortal(op)); - gc_flip_old_space(gc); - gc_list_move(gc, cf->container); + PyObject *stack[] = {phase_obj, info}; + for (Py_ssize_t i=0; i<PyList_GET_SIZE(gcstate->callbacks); i++) { + PyObject *r, *cb = PyList_GET_ITEM(gcstate->callbacks, i); + Py_INCREF(cb); /* make sure cb doesn't go away */ + r = PyObject_Vectorcall(cb, stack, 2, NULL); + if (r == NULL) { + PyErr_WriteUnraisable(cb); } + else { + Py_DECREF(r); + } + Py_DECREF(cb); } - return 0; + Py_DECREF(phase_obj); + Py_XDECREF(info); + assert(!_PyErr_Occurred(tstate)); } -static uintptr_t -expand_region_transitively_reachable(PyGC_Head *container, PyGC_Head *gc, GCState *gcstate) -{ - validate_list(container, collecting_clear_unreachable_clear); - struct container_and_flag arg = { - .container = container, - .visited_space = gcstate->visited_space, - }; - uintptr_t size = 0; - assert(GC_NEXT(gc) == container); - while (gc != container) { - /* Survivors will be moved to visited space, so they should - * have been marked as visited */ - assert(is_in_visited(gc, gcstate->visited_space)); - PyObject *op = FROM_GC(gc); - if (_Py_IsImmortal(op)) { - PyGC_Head *next = GC_NEXT(gc); - gc_list_move(gc, &get_gc_state()->permanent_generation.head); - gc = next; - continue; + +/* Find the oldest generation (highest numbered) where the count + * exceeds the threshold. Objects in the that generation and + * generations younger than it will be collected. */ +static int +gc_select_generation(GCState *gcstate) +{ + for (int i = NUM_GENERATIONS-1; i >= 0; i--) { + if (gcstate->generations[i].count > gcstate->generations[i].threshold) { + /* Avoid quadratic performance degradation in number + of tracked objects (see also issue #4074): + + To limit the cost of garbage collection, there are two strategies; + - make each collection faster, e.g. by scanning fewer objects + - do less collections + This heuristic is about the latter strategy. + + In addition to the various configurable thresholds, we only trigger a + full collection if the ratio + + long_lived_pending / long_lived_total + + is above a given value (hardwired to 25%). + + The reason is that, while "non-full" collections (i.e., collections of + the young and middle generations) will always examine roughly the same + number of objects -- determined by the aforementioned thresholds --, + the cost of a full collection is proportional to the total number of + long-lived objects, which is virtually unbounded. + + Indeed, it has been remarked that doing a full collection every + <constant number> of object creations entails a dramatic performance + degradation in workloads which consist in creating and storing lots of + long-lived objects (e.g. building a large list of GC-tracked objects would + show quadratic performance, instead of linear as expected: see issue #4074). + + Using the above ratio, instead, yields amortized linear performance in + the total number of objects (the effect of which can be summarized + thusly: "each full garbage collection is more and more costly as the + number of objects grows, but we do fewer and fewer of them"). + + This heuristic was suggested by Martin von Löwis on python-dev in + June 2008. His original analysis and proposal can be found at: + http://mail.python.org/pipermail/python-dev/2008-June/080579.html + */ + if (i == NUM_GENERATIONS - 1 + && gcstate->long_lived_pending < gcstate->long_lived_total / 4) + { + continue; + } + return i; } - traverseproc traverse = Py_TYPE(op)->tp_traverse; - (void) traverse(op, - visit_add_to_container, - &arg); - gc = GC_NEXT(gc); - size++; } - return size; + return -1; } -/* Do bookkeeping for a completed GC cycle */ -static void -completed_cycle(GCState *gcstate) -{ - assert(gc_list_is_empty(&gcstate->old[gcstate->visited_space^1].head)); - assert(gc_list_is_empty(&gcstate->young.head)); - gcstate->visited_space = flip_old_space(gcstate->visited_space); - if (gcstate->work_to_do > 0) { - gcstate->work_to_do = 0; - } -} -static void -gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats) +/* This is the main function. Read this to understand how the + * collection process works. */ +static Py_ssize_t +gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) { + int i; + Py_ssize_t m = 0; /* # objects collected */ + Py_ssize_t n = 0; /* # unreachable objects that couldn't be collected */ + PyGC_Head *young; /* the generation we are examining */ + PyGC_Head *old; /* next older generation */ + PyGC_Head unreachable; /* non-problematic unreachable trash */ + PyGC_Head finalizers; /* objects with, & reachable from, __del__ */ + PyGC_Head *gc; + _PyTime_t t1 = 0; /* initialize to prevent a compiler warning */ GCState *gcstate = &tstate->interp->gc; - if (gcstate->work_to_do <= 0) { - /* No work to do */ - return; - } - PyGC_Head *not_visited = &gcstate->old[gcstate->visited_space^1].head; - PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head; - PyGC_Head increment; - gc_list_init(&increment); - if (gc_list_is_empty(not_visited)) { - completed_cycle(gcstate); - return; + + // gc_collect_main() must not be called before _PyGC_Init + // or after _PyGC_Fini() + assert(gcstate->garbage != NULL); + assert(!_PyErr_Occurred(tstate)); + + int expected = 0; + if (!_Py_atomic_compare_exchange_int(&gcstate->collecting, &expected, 1)) { + // Don't start a garbage collection if one is already in progress. + return 0; } - Py_ssize_t region_size = 0; - while (region_size < gcstate->work_to_do) { - if (gc_list_is_empty(not_visited)) { - break; + + if (generation == GENERATION_AUTO) { + // Select the oldest generation that needs collecting. We will collect + // objects from that generation and all generations younger than it. + generation = gc_select_generation(gcstate); + if (generation < 0) { + // No generation needs to be collected. + _Py_atomic_store_int(&gcstate->collecting, 0); + return 0; } - PyGC_Head *gc = _PyGCHead_NEXT(not_visited); - gc_list_move(gc, &increment); - gc_set_old_space(gc, gcstate->visited_space); - region_size += expand_region_transitively_reachable(&increment, gc, gcstate); - } - assert(region_size == gc_list_size(&increment)); - PyGC_Head survivors; - gc_list_init(&survivors); - gc_collect_region(tstate, &increment, &survivors, UNTRACK_TUPLES, stats); - gc_list_merge(&survivors, visited); - assert(gc_list_is_empty(&increment)); - gcstate->work_to_do -= region_size; - validate_old(gcstate); - add_stats(gcstate, 1, stats); - if (gc_list_is_empty(not_visited)) { - completed_cycle(gcstate); } -} + assert(generation >= 0 && generation < NUM_GENERATIONS); -static void -gc_collect_full(PyThreadState *tstate, - struct gc_collection_stats *stats) -{ - GCState *gcstate = &tstate->interp->gc; - validate_old(gcstate); - PyGC_Head *young = &gcstate->young.head; - PyGC_Head *old0 = &gcstate->old[0].head; - PyGC_Head *old1 = &gcstate->old[1].head; - /* merge all generations into old0 */ - gc_list_merge(young, old0); - gcstate->young.count = 0; - PyGC_Head *gc = GC_NEXT(old1); - while (gc != old1) { - PyGC_Head *next = GC_NEXT(gc); - gc_set_old_space(gc, 0); - gc = next; +#ifdef Py_STATS + if (_Py_stats) { + _Py_stats->object_stats.object_visits = 0; } - gc_list_merge(old1, old0); - - gc_collect_region(tstate, old0, old0, - UNTRACK_TUPLES | UNTRACK_DICTS, - stats); - gcstate->visited_space = 1; - gcstate->young.count = 0; - gcstate->old[0].count = 0; - gcstate->old[1].count = 0; +#endif + GC_STAT_ADD(generation, collections, 1); - gcstate->work_to_do = - gcstate->young.threshold * 2; + if (reason != _Py_GC_REASON_SHUTDOWN) { + invoke_gc_callback(tstate, "start", generation, 0, 0); + } - _PyGC_ClearAllFreeLists(tstate->interp); - validate_old(gcstate); - add_stats(gcstate, 2, stats); -} + if (gcstate->debug & _PyGC_DEBUG_STATS) { + PySys_WriteStderr("gc: collecting generation %d...\n", generation); + show_stats_each_generations(gcstate); + t1 = _PyTime_GetPerfCounter(); + } -/* This is the main function. Read this to understand how the - * collection process works. */ -static void -gc_collect_region(PyThreadState *tstate, - PyGC_Head *from, - PyGC_Head *to, - int untrack, - struct gc_collection_stats *stats) -{ - PyGC_Head unreachable; /* non-problematic unreachable trash */ - PyGC_Head finalizers; /* objects with, & reachable from, __del__ */ - PyGC_Head *gc; /* initialize to prevent a compiler warning */ - GCState *gcstate = &tstate->interp->gc; + if (PyDTrace_GC_START_ENABLED()) { + PyDTrace_GC_START(generation); + } - assert(gcstate->garbage != NULL); - assert(!_PyErr_Occurred(tstate)); + /* update collection and allocation counters */ + if (generation+1 < NUM_GENERATIONS) { + gcstate->generations[generation+1].count += 1; + } + for (i = 0; i <= generation; i++) { + gcstate->generations[i].count = 0; + } - gc_list_init(&unreachable); - deduce_unreachable(from, &unreachable); - validate_consistent_old_space(from); - if (untrack & UNTRACK_TUPLES) { - untrack_tuples(from); + /* merge younger generations with one we are currently collecting */ + for (i = 0; i < generation; i++) { + gc_list_merge(GEN_HEAD(gcstate, i), GEN_HEAD(gcstate, generation)); } - if (untrack & UNTRACK_DICTS) { - untrack_dicts(from); + + /* handy references */ + young = GEN_HEAD(gcstate, generation); + if (generation < NUM_GENERATIONS-1) { + old = GEN_HEAD(gcstate, generation+1); } - validate_consistent_old_space(to); - if (from != to) { - gc_list_merge(from, to); + else { + old = young; } - validate_consistent_old_space(to); + validate_list(old, collecting_clear_unreachable_clear); + + deduce_unreachable(young, &unreachable); + + untrack_tuples(young); /* Move reachable objects to next generation. */ + if (young != old) { + if (generation == NUM_GENERATIONS - 2) { + gcstate->long_lived_pending += gc_list_size(young); + } + gc_list_merge(young, old); + } + else { + /* We only un-track dicts in full collections, to avoid quadratic + dict build-up. See issue #14775. */ + untrack_dicts(young); + gcstate->long_lived_pending = 0; + gcstate->long_lived_total = gc_list_size(young); + } /* All objects in unreachable are trash, but objects reachable from * legacy finalizers (e.g. tp_del) can't safely be deleted. @@ -1489,8 +1380,10 @@ gc_collect_region(PyThreadState *tstate, * and we move those into the finalizers list too. */ move_legacy_finalizer_reachable(&finalizers); + validate_list(&finalizers, collecting_clear_unreachable_clear); validate_list(&unreachable, collecting_set_unreachable_clear); + /* Print debugging information. */ if (gcstate->debug & _PyGC_DEBUG_COLLECTABLE) { for (gc = GC_NEXT(&unreachable); gc != &unreachable; gc = GC_NEXT(gc)) { @@ -1499,99 +1392,89 @@ gc_collect_region(PyThreadState *tstate, } /* Clear weakrefs and invoke callbacks as necessary. */ - stats->collected += handle_weakrefs(&unreachable, to); - validate_list(to, collecting_clear_unreachable_clear); + m += handle_weakrefs(&unreachable, old); + + validate_list(old, collecting_clear_unreachable_clear); validate_list(&unreachable, collecting_set_unreachable_clear); /* Call tp_finalize on objects which have one. */ finalize_garbage(tstate, &unreachable); + /* Handle any objects that may have resurrected after the call * to 'finalize_garbage' and continue the collection with the * objects that are still unreachable */ PyGC_Head final_unreachable; - gc_list_init(&final_unreachable); - handle_resurrected_objects(&unreachable, &final_unreachable, to); + handle_resurrected_objects(&unreachable, &final_unreachable, old); /* Call tp_clear on objects in the final_unreachable set. This will cause * the reference cycles to be broken. It may also cause some objects * in finalizers to be freed. */ - stats->collected += gc_list_size(&final_unreachable); - delete_garbage(tstate, gcstate, &final_unreachable, to); + m += gc_list_size(&final_unreachable); + delete_garbage(tstate, gcstate, &final_unreachable, old); /* Collect statistics on uncollectable objects found and print * debugging information. */ - Py_ssize_t n = 0; for (gc = GC_NEXT(&finalizers); gc != &finalizers; gc = GC_NEXT(gc)) { n++; if (gcstate->debug & _PyGC_DEBUG_UNCOLLECTABLE) debug_cycle("uncollectable", FROM_GC(gc)); } - stats->uncollectable = n; + if (gcstate->debug & _PyGC_DEBUG_STATS) { + double d = _PyTime_AsSecondsDouble(_PyTime_GetPerfCounter() - t1); + PySys_WriteStderr( + "gc: done, %zd unreachable, %zd uncollectable, %.4fs elapsed\n", + n+m, n, d); + } + /* Append instances in the uncollectable set to a Python * reachable list of garbage. The programmer has to deal with * this if they insist on creating this type of structure. */ - handle_legacy_finalizers(tstate, gcstate, &finalizers, to); - validate_list(to, collecting_clear_unreachable_clear); -} + handle_legacy_finalizers(tstate, gcstate, &finalizers, old); + validate_list(old, collecting_clear_unreachable_clear); -/* Invoke progress callbacks to notify clients that garbage collection - * is starting or stopping - */ -static void -do_gc_callback(GCState *gcstate, const char *phase, - int generation, struct gc_collection_stats *stats) -{ - assert(!PyErr_Occurred()); + /* Clear free list only during the collection of the highest + * generation */ + if (generation == NUM_GENERATIONS-1) { + _PyGC_ClearAllFreeLists(tstate->interp); + } - /* The local variable cannot be rebound, check it for sanity */ - assert(PyList_CheckExact(gcstate->callbacks)); - PyObject *info = NULL; - if (PyList_GET_SIZE(gcstate->callbacks) != 0) { - info = Py_BuildValue("{sisnsn}", - "generation", generation, - "collected", stats->collected, - "uncollectable", stats->uncollectable); - if (info == NULL) { - PyErr_FormatUnraisable("Exception ignored on invoking gc callbacks"); - return; + if (_PyErr_Occurred(tstate)) { + if (reason == _Py_GC_REASON_SHUTDOWN) { + _PyErr_Clear(tstate); + } + else { + PyErr_FormatUnraisable("Exception ignored in garbage collection"); } } - PyObject *phase_obj = PyUnicode_FromString(phase); - if (phase_obj == NULL) { - Py_XDECREF(info); - PyErr_FormatUnraisable("Exception ignored on invoking gc callbacks"); - return; + /* Update stats */ + struct gc_generation_stats *stats = &gcstate->generation_stats[generation]; + stats->collections++; + stats->collected += m; + stats->uncollectable += n; + + GC_STAT_ADD(generation, objects_collected, m); +#ifdef Py_STATS + if (_Py_stats) { + GC_STAT_ADD(generation, object_visits, + _Py_stats->object_stats.object_visits); + _Py_stats->object_stats.object_visits = 0; } +#endif - PyObject *stack[] = {phase_obj, info}; - for (Py_ssize_t i=0; i<PyList_GET_SIZE(gcstate->callbacks); i++) { - PyObject *r, *cb = PyList_GET_ITEM(gcstate->callbacks, i); - Py_INCREF(cb); /* make sure cb doesn't go away */ - r = PyObject_Vectorcall(cb, stack, 2, NULL); - if (r == NULL) { - PyErr_WriteUnraisable(cb); - } - else { - Py_DECREF(r); - } - Py_DECREF(cb); + if (PyDTrace_GC_DONE_ENABLED()) { + PyDTrace_GC_DONE(n + m); } - Py_DECREF(phase_obj); - Py_XDECREF(info); - assert(!PyErr_Occurred()); -} -static void -invoke_gc_callback(GCState *gcstate, const char *phase, - int generation, struct gc_collection_stats *stats) -{ - if (gcstate->callbacks == NULL) { - return; + if (reason != _Py_GC_REASON_SHUTDOWN) { + invoke_gc_callback(tstate, "stop", generation, m, n); } - do_gc_callback(gcstate, phase, generation, stats); + + assert(!_PyErr_Occurred(tstate)); + _Py_atomic_store_int(&gcstate->collecting, 0); + return n + m; } static int @@ -1666,7 +1549,7 @@ _PyGC_GetObjects(PyInterpreterState *interp, Py_ssize_t generation) } } else { - if (append_objects(result, GEN_HEAD(gcstate, (int)generation))) { + if (append_objects(result, GEN_HEAD(gcstate, generation))) { goto error; } } @@ -1681,16 +1564,10 @@ void _PyGC_Freeze(PyInterpreterState *interp) { GCState *gcstate = &interp->gc; - gc_list_merge(&gcstate->young.head, &gcstate->permanent_generation.head); - gcstate->young.count = 0; - PyGC_Head*old0 = &gcstate->old[0].head; - PyGC_Head*old1 = &gcstate->old[1].head; - gc_list_merge(old0, &gcstate->permanent_generation.head); - gcstate->old[0].count = 0; - gc_list_set_space(old1, 0); - gc_list_merge(old1, &gcstate->permanent_generation.head); - gcstate->old[1].count = 0; - validate_old(gcstate); + for (int i = 0; i < NUM_GENERATIONS; ++i) { + gc_list_merge(GEN_HEAD(gcstate, i), &gcstate->permanent_generation.head); + gcstate->generations[i].count = 0; + } } void @@ -1698,8 +1575,7 @@ _PyGC_Unfreeze(PyInterpreterState *interp) { GCState *gcstate = &interp->gc; gc_list_merge(&gcstate->permanent_generation.head, - &gcstate->old[0].head); - validate_old(gcstate); + GEN_HEAD(gcstate, NUM_GENERATIONS-1)); } Py_ssize_t @@ -1735,100 +1611,32 @@ PyGC_IsEnabled(void) return gcstate->enabled; } -// Show stats for objects in each generations -static void -show_stats_each_generations(GCState *gcstate) -{ - char buf[100]; - size_t pos = 0; - - for (int i = 0; i < NUM_GENERATIONS && pos < sizeof(buf); i++) { - pos += PyOS_snprintf(buf+pos, sizeof(buf)-pos, - " %zd", - gc_list_size(GEN_HEAD(gcstate, i))); - } - - PySys_FormatStderr( - "gc: objects in each generation:%s\n" - "gc: objects in permanent generation: %zd\n", - buf, gc_list_size(&gcstate->permanent_generation.head)); -} - +/* Public API to invoke gc.collect() from C */ Py_ssize_t -_PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason) +PyGC_Collect(void) { + PyThreadState *tstate = _PyThreadState_GET(); GCState *gcstate = &tstate->interp->gc; - int expected = 0; - if (!_Py_atomic_compare_exchange_int(&gcstate->collecting, &expected, 1)) { - // Don't start a garbage collection if one is already in progress. + if (!gcstate->enabled) { return 0; } - struct gc_collection_stats stats = { 0 }; - if (reason != _Py_GC_REASON_SHUTDOWN) { - invoke_gc_callback(gcstate, "start", generation, &stats); - } - _PyTime_t t1 = 0; /* initialize to prevent a compiler warning */ - if (gcstate->debug & _PyGC_DEBUG_STATS) { - PySys_WriteStderr("gc: collecting generation %d...\n", generation); - show_stats_each_generations(gcstate); - t1 = _PyTime_GetPerfCounter(); - } - if (PyDTrace_GC_START_ENABLED()) { - PyDTrace_GC_START(generation); - } - GC_STAT_ADD(generation, collections, 1); + Py_ssize_t n; PyObject *exc = _PyErr_GetRaisedException(tstate); - switch(generation) { - case 0: - gc_collect_young(tstate, &stats); - break; - case 1: - gc_collect_young(tstate, &stats); - gc_collect_increment(tstate, &stats); - break; - case 2: - gc_collect_full(tstate, &stats); - break; - default: - Py_UNREACHABLE(); - } - if (PyDTrace_GC_DONE_ENABLED()) { - PyDTrace_GC_DONE(stats.uncollectable + stats.collected); - } - if (reason != _Py_GC_REASON_SHUTDOWN) { - invoke_gc_callback(gcstate, "stop", generation, &stats); - } + n = gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_MANUAL); _PyErr_SetRaisedException(tstate, exc); - GC_STAT_ADD(generation, objects_collected, stats.collected); -#ifdef Py_STATS - if (_Py_stats) { - GC_STAT_ADD(generation, object_visits, - _Py_stats->object_stats.object_visits); - _Py_stats->object_stats.object_visits = 0; - } -#endif - validate_old(gcstate); - if (gcstate->debug & _PyGC_DEBUG_STATS) { - double d = _PyTime_AsSecondsDouble(_PyTime_GetPerfCounter() - t1); - PySys_WriteStderr( - "gc: done, %zd collected, %zd uncollectable, %.4fs elapsed\n", - stats.collected, stats.uncollectable, d); - } - _Py_atomic_store_int(&gcstate->collecting, 0); - return stats.uncollectable + stats.collected; + return n; } -/* Public API to invoke gc.collect() from C */ Py_ssize_t -PyGC_Collect(void) +_PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason) { - return _PyGC_Collect(_PyThreadState_GET(), 2, _Py_GC_REASON_MANUAL); + return gc_collect_main(tstate, generation, reason); } -void +Py_ssize_t _PyGC_CollectNoFail(PyThreadState *tstate) { /* Ideally, this function is only called on interpreter shutdown, @@ -1837,7 +1645,7 @@ _PyGC_CollectNoFail(PyThreadState *tstate) during interpreter shutdown (and then never finish it). See http://bugs.python.org/issue8713#msg195178 for an example. */ - _PyGC_Collect(_PyThreadState_GET(), 2, _Py_GC_REASON_SHUTDOWN); + return gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_SHUTDOWN); } void @@ -1972,10 +1780,10 @@ _PyObject_GC_Link(PyObject *op) GCState *gcstate = &tstate->interp->gc; g->_gc_next = 0; g->_gc_prev = 0; - gcstate->young.count++; /* number of allocated GC objects */ - if (gcstate->young.count > gcstate->young.threshold && + gcstate->generations[0].count++; /* number of allocated GC objects */ + if (gcstate->generations[0].count > gcstate->generations[0].threshold && gcstate->enabled && - gcstate->young.threshold && + gcstate->generations[0].threshold && !_Py_atomic_load_int_relaxed(&gcstate->collecting) && !_PyErr_Occurred(tstate)) { @@ -1986,9 +1794,7 @@ _PyObject_GC_Link(PyObject *op) void _Py_RunGC(PyThreadState *tstate) { - if (tstate->interp->gc.enabled) { - _PyGC_Collect(tstate, 1, _Py_GC_REASON_HEAP); - } + gc_collect_main(tstate, GENERATION_AUTO, _Py_GC_REASON_HEAP); } static PyObject * @@ -2091,8 +1897,8 @@ PyObject_GC_Del(void *op) #endif } GCState *gcstate = get_gc_state(); - if (gcstate->young.count > 0) { - gcstate->young.count--; + if (gcstate->generations[0].count > 0) { + gcstate->generations[0].count--; } PyObject_Free(((char *)op)-presize); } @@ -2115,36 +1921,26 @@ PyObject_GC_IsFinalized(PyObject *obj) return 0; } -static int -visit_generation(gcvisitobjects_t callback, void *arg, struct gc_generation *gen) -{ - PyGC_Head *gc_list, *gc; - gc_list = &gen->head; - for (gc = GC_NEXT(gc_list); gc != gc_list; gc = GC_NEXT(gc)) { - PyObject *op = FROM_GC(gc); - Py_INCREF(op); - int res = callback(op, arg); - Py_DECREF(op); - if (!res) { - return -1; - } - } - return 0; -} - void PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void *arg) { + size_t i; GCState *gcstate = get_gc_state(); int origenstate = gcstate->enabled; gcstate->enabled = 0; - if (visit_generation(callback, arg, &gcstate->young)) { - goto done; - } - if (visit_generation(callback, arg, &gcstate->old[0])) { - goto done; + for (i = 0; i < NUM_GENERATIONS; i++) { + PyGC_Head *gc_list, *gc; + gc_list = GEN_HEAD(gcstate, i); + for (gc = GC_NEXT(gc_list); gc != gc_list; gc = GC_NEXT(gc)) { + PyObject *op = FROM_GC(gc); + Py_INCREF(op); + int res = callback(op, arg); + Py_DECREF(op); + if (!res) { + goto done; + } + } } - visit_generation(callback, arg, &gcstate->old[1]); done: gcstate->enabled = origenstate; } diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 1c4da726866e4e1..8fbcdb15109b76c 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -616,7 +616,7 @@ void _PyGC_InitState(GCState *gcstate) { // TODO: move to pycore_runtime_init.h once the incremental GC lands. - gcstate->young.threshold = 2000; + gcstate->generations[0].threshold = 2000; } @@ -911,8 +911,8 @@ cleanup_worklist(struct worklist *worklist) static bool gc_should_collect(GCState *gcstate) { - int count = _Py_atomic_load_int_relaxed(&gcstate->young.count); - int threshold = gcstate->young.threshold; + int count = _Py_atomic_load_int_relaxed(&gcstate->generations[0].count); + int threshold = gcstate->generations[0].threshold; if (count <= threshold || threshold == 0 || !gcstate->enabled) { return false; } @@ -920,7 +920,7 @@ gc_should_collect(GCState *gcstate) // objects. A few tests rely on immediate scheduling of the GC so we ignore // the scaled threshold if generations[1].threshold is set to zero. return (count > gcstate->long_lived_total / 4 || - gcstate->old[0].threshold == 0); + gcstate->generations[1].threshold == 0); } static void @@ -1031,15 +1031,10 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) /* update collection and allocation counters */ if (generation+1 < NUM_GENERATIONS) { - gcstate->old[generation].count += 1; + gcstate->generations[generation+1].count += 1; } for (i = 0; i <= generation; i++) { - if (i == 0) { - gcstate->young.count = 0; - } - else { - gcstate->old[i-1].count = 0; - } + gcstate->generations[i].count = 0; } PyInterpreterState *interp = tstate->interp; @@ -1362,7 +1357,7 @@ _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason) return gc_collect_main(tstate, generation, reason); } -void +Py_ssize_t _PyGC_CollectNoFail(PyThreadState *tstate) { /* Ideally, this function is only called on interpreter shutdown, @@ -1371,7 +1366,7 @@ _PyGC_CollectNoFail(PyThreadState *tstate) during interpreter shutdown (and then never finish it). See http://bugs.python.org/issue8713#msg195178 for an example. */ - gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_SHUTDOWN); + return gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_SHUTDOWN); } void @@ -1495,7 +1490,7 @@ _PyObject_GC_Link(PyObject *op) { PyThreadState *tstate = _PyThreadState_GET(); GCState *gcstate = &tstate->interp->gc; - gcstate->young.count++; + gcstate->generations[0].count++; if (gc_should_collect(gcstate) && !_Py_atomic_load_int_relaxed(&gcstate->collecting)) @@ -1610,8 +1605,8 @@ PyObject_GC_Del(void *op) #endif } GCState *gcstate = get_gc_state(); - if (gcstate->young.count > 0) { - gcstate->young.count--; + if (gcstate->generations[0].count > 0) { + gcstate->generations[0].count--; } PyObject_Free(((char *)op)-presize); } diff --git a/Python/import.c b/Python/import.c index dfc5ec1f2f29272..2fd0c08a6bb5aec 100644 --- a/Python/import.c +++ b/Python/import.c @@ -1030,7 +1030,7 @@ _extensions_cache_set(PyObject *filename, PyObject *name, PyModuleDef *def) if (!already_set) { /* We assume that all module defs are statically allocated and will never be freed. Otherwise, we would incref here. */ - _Py_SetImmortal((PyObject *)def); + _Py_SetImmortal(def); } res = 0; diff --git a/Tools/gdb/libpython.py b/Tools/gdb/libpython.py index 96b891481d9f462..483f28b46dfec74 100755 --- a/Tools/gdb/libpython.py +++ b/Tools/gdb/libpython.py @@ -1753,11 +1753,8 @@ def is_waiting_for_gil(self): return (name == 'take_gil') def is_gc_collect(self): - '''Is this frame a collector within the garbage-collector?''' - return self._gdbframe.name() in ( - 'collect', 'gc_collect_full', 'gc_collect_main', - 'gc_collect_young', 'gc_collect_increment' - ) + '''Is this frame gc_collect_main() within the garbage-collector?''' + return self._gdbframe.name() in ('collect', 'gc_collect_main') def get_pyop(self): try: From fedbf77191ea9d6515b39f958cc9e588d23517c9 Mon Sep 17 00:00:00 2001 From: Carl Meyer <carl@oddbird.net> Date: Wed, 7 Feb 2024 11:56:16 -0500 Subject: [PATCH 170/507] gh-114828: Fix __class__ in class-scope inlined comprehensions (#115139) --- Lib/test/test_listcomps.py | 12 ++++++++++++ ...-02-07-07-50-12.gh-issue-114828.nSXwMi.rst | 2 ++ Python/symtable.c | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-07-07-50-12.gh-issue-114828.nSXwMi.rst diff --git a/Lib/test/test_listcomps.py b/Lib/test/test_listcomps.py index f95a78aff0c7118..2868dd01545b95f 100644 --- a/Lib/test/test_listcomps.py +++ b/Lib/test/test_listcomps.py @@ -156,6 +156,18 @@ def method(self): self.assertEqual(C.y, [4, 4, 4, 4, 4]) self.assertIs(C().method(), C) + def test_references_super(self): + code = """ + res = [super for x in [1]] + """ + self._check_in_scopes(code, outputs={"res": [super]}) + + def test_references___class__(self): + code = """ + res = [__class__ for x in [1]] + """ + self._check_in_scopes(code, raises=NameError) + def test_inner_cell_shadows_outer(self): code = """ items = [(lambda: i) for i in range(5)] diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-07-07-50-12.gh-issue-114828.nSXwMi.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-07-07-50-12.gh-issue-114828.nSXwMi.rst new file mode 100644 index 000000000000000..b1c63e0a1518fdc --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-07-07-50-12.gh-issue-114828.nSXwMi.rst @@ -0,0 +1,2 @@ +Fix compilation crashes in uncommon code examples using :func:`super` inside +a comprehension in a class body. diff --git a/Python/symtable.c b/Python/symtable.c index 743029956e32faf..d69516351efba22 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -758,6 +758,8 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp, { PyObject *k, *v; Py_ssize_t pos = 0; + int remove_dunder_class = 0; + while (PyDict_Next(comp->ste_symbols, &pos, &k, &v)) { // skip comprehension parameter long comp_flags = PyLong_AS_LONG(v); @@ -779,6 +781,19 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp, if (!existing) { // name does not exist in scope, copy from comprehension assert(scope != FREE || PySet_Contains(comp_free, k) == 1); + if (scope == FREE && ste->ste_type == ClassBlock && + _PyUnicode_EqualToASCIIString(k, "__class__")) { + // if __class__ is unbound in the enclosing class scope and free + // in the comprehension scope, it needs special handling; just + // letting it be marked as free in class scope will break due to + // drop_class_free + scope = GLOBAL_IMPLICIT; + only_flags &= ~DEF_FREE; + if (PySet_Discard(comp_free, k) < 0) { + return 0; + } + remove_dunder_class = 1; + } PyObject *v_flags = PyLong_FromLong(only_flags); if (v_flags == NULL) { return 0; @@ -803,6 +818,10 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp, } } } + comp->ste_free = PySet_Size(comp_free) > 0; + if (remove_dunder_class && PyDict_DelItemString(comp->ste_symbols, "__class__") < 0) { + return 0; + } return 1; } From ef3ceab09d2d0959c343c662461123d5b0e0b64b Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Wed, 7 Feb 2024 13:43:18 -0500 Subject: [PATCH 171/507] gh-112066: Use `PyDict_SetDefaultRef` in place of `PyDict_SetDefault`. (#112211) This changes a number of internal usages of `PyDict_SetDefault` to use `PyDict_SetDefaultRef`. Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com> --- Modules/_json.c | 5 ++--- Modules/posixmodule.c | 2 +- Modules/pyexpat.c | 3 ++- Objects/typeobject.c | 6 +++--- Objects/unicodeobject.c | 12 +++++++----- Python/compile.c | 29 +++++++++++++++++------------ 6 files changed, 32 insertions(+), 25 deletions(-) diff --git a/Modules/_json.c b/Modules/_json.c index 24b292ce70e5ebd..c55299899e77fe5 100644 --- a/Modules/_json.c +++ b/Modules/_json.c @@ -691,11 +691,10 @@ _parse_object_unicode(PyScannerObject *s, PyObject *memo, PyObject *pystr, Py_ss key = scanstring_unicode(pystr, idx + 1, s->strict, &next_idx); if (key == NULL) goto bail; - memokey = PyDict_SetDefault(memo, key, key); - if (memokey == NULL) { + if (PyDict_SetDefaultRef(memo, key, key, &memokey) < 0) { goto bail; } - Py_SETREF(key, Py_NewRef(memokey)); + Py_SETREF(key, memokey); idx = next_idx; /* skip whitespace between key and : delimiter, read :, skip whitespace */ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 22891135bde0af7..e26265fc874ebb4 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -1627,7 +1627,7 @@ convertenviron(void) Py_DECREF(d); return NULL; } - if (PyDict_SetDefault(d, k, v) == NULL) { + if (PyDict_SetDefaultRef(d, k, v, NULL) < 0) { Py_DECREF(v); Py_DECREF(k); Py_DECREF(d); diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c index 7c08eda83e66b2a..62cd262a7885e93 100644 --- a/Modules/pyexpat.c +++ b/Modules/pyexpat.c @@ -1615,7 +1615,8 @@ static int init_handler_descrs(pyexpat_state *state) if (descr == NULL) return -1; - if (PyDict_SetDefault(state->xml_parse_type->tp_dict, PyDescr_NAME(descr), descr) == NULL) { + if (PyDict_SetDefaultRef(state->xml_parse_type->tp_dict, + PyDescr_NAME(descr), descr, NULL) < 0) { Py_DECREF(descr); return -1; } diff --git a/Objects/typeobject.c b/Objects/typeobject.c index e220d10ce563c29..c65d0ec2acae526 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6683,7 +6683,7 @@ type_add_method(PyTypeObject *type, PyMethodDef *meth) int err; PyObject *dict = lookup_tp_dict(type); if (!(meth->ml_flags & METH_COEXIST)) { - err = PyDict_SetDefault(dict, name, descr) == NULL; + err = PyDict_SetDefaultRef(dict, name, descr, NULL) < 0; } else { err = PyDict_SetItem(dict, name, descr) < 0; @@ -6731,7 +6731,7 @@ type_add_members(PyTypeObject *type) if (descr == NULL) return -1; - if (PyDict_SetDefault(dict, PyDescr_NAME(descr), descr) == NULL) { + if (PyDict_SetDefaultRef(dict, PyDescr_NAME(descr), descr, NULL) < 0) { Py_DECREF(descr); return -1; } @@ -6756,7 +6756,7 @@ type_add_getset(PyTypeObject *type) return -1; } - if (PyDict_SetDefault(dict, PyDescr_NAME(descr), descr) == NULL) { + if (PyDict_SetDefaultRef(dict, PyDescr_NAME(descr), descr, NULL) < 0) { Py_DECREF(descr); return -1; } diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index b236ddba9cdc69f..0a569a950e88e29 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -14894,16 +14894,18 @@ _PyUnicode_InternInPlace(PyInterpreterState *interp, PyObject **p) PyObject *interned = get_interned_dict(interp); assert(interned != NULL); - PyObject *t = PyDict_SetDefault(interned, s, s); - if (t == NULL) { + PyObject *t; + int res = PyDict_SetDefaultRef(interned, s, s, &t); + if (res < 0) { PyErr_Clear(); return; } - - if (t != s) { - Py_SETREF(*p, Py_NewRef(t)); + else if (res == 1) { + // value was already present (not inserted) + Py_SETREF(*p, t); return; } + Py_DECREF(t); if (_Py_IsImmortal(s)) { // XXX Restrict this to the main interpreter? diff --git a/Python/compile.c b/Python/compile.c index 4c1d3bb2d2b475a..15e5cf38a37b97a 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -958,14 +958,15 @@ merge_consts_recursive(PyObject *const_cache, PyObject *o) return NULL; } - // t is borrowed reference - PyObject *t = PyDict_SetDefault(const_cache, key, key); - if (t != key) { - // o is registered in const_cache. Just use it. - Py_XINCREF(t); + PyObject *t; + int res = PyDict_SetDefaultRef(const_cache, key, key, &t); + if (res != 0) { + // o was not inserted into const_cache. t is either the existing value + // or NULL (on error). Py_DECREF(key); return t; } + Py_DECREF(t); // We registered o in const_cache. // When o is a tuple or frozenset, we want to merge its @@ -7527,22 +7528,26 @@ _PyCompile_ConstCacheMergeOne(PyObject *const_cache, PyObject **obj) return ERROR; } - // t is borrowed reference - PyObject *t = PyDict_SetDefault(const_cache, key, key); + PyObject *t; + int res = PyDict_SetDefaultRef(const_cache, key, key, &t); Py_DECREF(key); - if (t == NULL) { + if (res < 0) { return ERROR; } - if (t == key) { // obj is new constant. + if (res == 0) { // inserted: obj is new constant. + Py_DECREF(t); return SUCCESS; } if (PyTuple_CheckExact(t)) { - // t is still borrowed reference - t = PyTuple_GET_ITEM(t, 1); + PyObject *item = PyTuple_GET_ITEM(t, 1); + Py_SETREF(*obj, Py_NewRef(item)); + Py_DECREF(t); + } + else { + Py_SETREF(*obj, t); } - Py_SETREF(*obj, Py_NewRef(t)); return SUCCESS; } From 8f0998e844c2fd8c0c94681d0a6331c34ee31562 Mon Sep 17 00:00:00 2001 From: Carl Meyer <carl@oddbird.net> Date: Wed, 7 Feb 2024 15:19:47 -0500 Subject: [PATCH 172/507] gh-114828: parenthesize non-atomic macro definitions in pycore_symtable.h (#115143) --- Include/internal/pycore_symtable.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Include/internal/pycore_symtable.h b/Include/internal/pycore_symtable.h index 1d782ca2c96e055..b44393b56446735 100644 --- a/Include/internal/pycore_symtable.h +++ b/Include/internal/pycore_symtable.h @@ -109,18 +109,18 @@ extern PyObject* _Py_Mangle(PyObject *p, PyObject *name); /* Flags for def-use information */ -#define DEF_GLOBAL 1 /* global stmt */ -#define DEF_LOCAL 2 /* assignment in code block */ -#define DEF_PARAM 2<<1 /* formal parameter */ -#define DEF_NONLOCAL 2<<2 /* nonlocal stmt */ -#define USE 2<<3 /* name is used */ -#define DEF_FREE 2<<4 /* name used but not defined in nested block */ -#define DEF_FREE_CLASS 2<<5 /* free variable from class's method */ -#define DEF_IMPORT 2<<6 /* assignment occurred via import */ -#define DEF_ANNOT 2<<7 /* this name is annotated */ -#define DEF_COMP_ITER 2<<8 /* this name is a comprehension iteration variable */ -#define DEF_TYPE_PARAM 2<<9 /* this name is a type parameter */ -#define DEF_COMP_CELL 2<<10 /* this name is a cell in an inlined comprehension */ +#define DEF_GLOBAL 1 /* global stmt */ +#define DEF_LOCAL 2 /* assignment in code block */ +#define DEF_PARAM (2<<1) /* formal parameter */ +#define DEF_NONLOCAL (2<<2) /* nonlocal stmt */ +#define USE (2<<3) /* name is used */ +#define DEF_FREE (2<<4) /* name used but not defined in nested block */ +#define DEF_FREE_CLASS (2<<5) /* free variable from class's method */ +#define DEF_IMPORT (2<<6) /* assignment occurred via import */ +#define DEF_ANNOT (2<<7) /* this name is annotated */ +#define DEF_COMP_ITER (2<<8) /* this name is a comprehension iteration variable */ +#define DEF_TYPE_PARAM (2<<9) /* this name is a type parameter */ +#define DEF_COMP_CELL (2<<10) /* this name is a cell in an inlined comprehension */ #define DEF_BOUND (DEF_LOCAL | DEF_PARAM | DEF_IMPORT) From 38b970dfcc3cdebc87a456f17ef1e0f06dde7375 Mon Sep 17 00:00:00 2001 From: Alex Gaynor <alex.gaynor@gmail.com> Date: Wed, 7 Feb 2024 17:21:33 -0500 Subject: [PATCH 173/507] When the Py_CompileStringExFlags fuzzer encounters a SystemError, abort (#115147) This allows us to catch bugs beyond memory corruption and assertions. --- Modules/_xxtestfuzz/fuzzer.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Modules/_xxtestfuzz/fuzzer.c b/Modules/_xxtestfuzz/fuzzer.c index e133b4d3c444809..6ea9f64d6285304 100644 --- a/Modules/_xxtestfuzz/fuzzer.c +++ b/Modules/_xxtestfuzz/fuzzer.c @@ -502,7 +502,6 @@ static int fuzz_elementtree_parsewhole(const char* data, size_t size) { } #define MAX_PYCOMPILE_TEST_SIZE 16384 -static char pycompile_scratch[MAX_PYCOMPILE_TEST_SIZE]; static const int start_vals[] = {Py_eval_input, Py_single_input, Py_file_input}; const size_t NUM_START_VALS = sizeof(start_vals) / sizeof(start_vals[0]); @@ -531,6 +530,8 @@ static int fuzz_pycompile(const char* data, size_t size) { unsigned char optimize_idx = (unsigned char) data[1]; int optimize = optimize_vals[optimize_idx % NUM_OPTIMIZE_VALS]; + char pycompile_scratch[MAX_PYCOMPILE_TEST_SIZE]; + // Create a NUL-terminated C string from the remaining input memcpy(pycompile_scratch, data + 2, size - 2); // Put a NUL terminator just after the copied data. (Space was reserved already.) @@ -549,7 +550,13 @@ static int fuzz_pycompile(const char* data, size_t size) { PyObject *result = Py_CompileStringExFlags(pycompile_scratch, "<fuzz input>", start, flags, optimize); if (result == NULL) { - /* compilation failed, most likely from a syntax error */ + /* Compilation failed, most likely from a syntax error. If it was a + SystemError we abort. There's no non-bug reason to raise a + SystemError. */ + if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_SystemError)) { + PyErr_Print(); + abort(); + } PyErr_Clear(); } else { Py_DECREF(result); From 4a7f63869aa61b24a7cc2d33f8a5e5a7fd0d76a4 Mon Sep 17 00:00:00 2001 From: Justin Applegate <70449145+Legoclones@users.noreply.github.com> Date: Thu, 8 Feb 2024 01:12:58 -0700 Subject: [PATCH 174/507] gh-115146: Fix typo in pickletools.py documentation (GH-115148) --- Lib/pickletools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/pickletools.py b/Lib/pickletools.py index 95a77aeb2afe2af..51ee4a7a2632ac0 100644 --- a/Lib/pickletools.py +++ b/Lib/pickletools.py @@ -1253,7 +1253,7 @@ def __init__(self, name, code, arg, stack_before=[], stack_after=[pyint], proto=2, - doc="""Long integer using found-byte length. + doc="""Long integer using four-byte length. A more efficient encoding of a Python long; the long4 encoding says it all."""), From 9e90313320a2af2d9ff7049ed3842344ed236630 Mon Sep 17 00:00:00 2001 From: Artem Chernyshev <62871052+dTenebrae@users.noreply.github.com> Date: Thu, 8 Feb 2024 11:40:38 +0300 Subject: [PATCH 175/507] gh-115136: Fix possible NULL deref in getpath_joinpath() (GH-115137) Signed-off-by: Artem Chernyshev <artem.chernyshev@red-soft.ru> --- Modules/getpath.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Modules/getpath.c b/Modules/getpath.c index a3c8fc269d1c3cf..abed139028244ad 100644 --- a/Modules/getpath.c +++ b/Modules/getpath.c @@ -262,6 +262,10 @@ getpath_joinpath(PyObject *Py_UNUSED(self), PyObject *args) } /* Convert all parts to wchar and accumulate max final length */ wchar_t **parts = (wchar_t **)PyMem_Malloc(n * sizeof(wchar_t *)); + if (parts == NULL) { + PyErr_NoMemory(); + return NULL; + } memset(parts, 0, n * sizeof(wchar_t *)); Py_ssize_t cchFinal = 0; Py_ssize_t first = 0; From 17689e3c41d9f61bcd1928b24d3c50c37ceaf3f2 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Thu, 8 Feb 2024 01:04:41 -0800 Subject: [PATCH 176/507] gh-107944: Improve error message for getargs with bad keyword arguments (#114792) --- Doc/whatsnew/3.13.rst | 11 +++ Lib/test/test_call.py | 32 ++++++++- Lib/test/test_capi/test_getargs.py | 26 +++---- Lib/test/test_exceptions.py | 2 +- ...-01-31-09-10-10.gh-issue-107944.XWm1B-.rst | 1 + Python/getargs.c | 70 +++++++++++++++---- 6 files changed, 113 insertions(+), 29 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-31-09-10-10.gh-issue-107944.XWm1B-.rst diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 2ac5afa8ce601cb..50a2a69c75ac70b 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -101,6 +101,17 @@ Improved Error Messages variables. See also :ref:`using-on-controlling-color`. (Contributed by Pablo Galindo Salgado in :gh:`112730`.) +* When an incorrect keyword argument is passed to a function, the error message + now potentially suggests the correct keyword argument. + (Contributed by Pablo Galindo Salgado and Shantanu Jain in :gh:`107944`.) + + >>> "better error messages!".split(max_split=1) + Traceback (most recent call last): + File "<stdin>", line 1, in <module> + "better error messages!".split(max_split=1) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + TypeError: split() got an unexpected keyword argument 'max_split'. Did you mean 'maxsplit'? + Other Language Changes ====================== diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 3c8fc35e3c116d0..2a6a5d287b04ee9 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -155,7 +155,7 @@ def test_varargs16_kw(self): min, 0, default=1, key=2, foo=3) def test_varargs17_kw(self): - msg = r"'foo' is an invalid keyword argument for print\(\)$" + msg = r"print\(\) got an unexpected keyword argument 'foo'$" self.assertRaisesRegex(TypeError, msg, print, 0, sep=1, end=2, file=3, flush=4, foo=5) @@ -928,7 +928,7 @@ def check_suggestion_includes(self, message): self.assertIn(f"Did you mean '{message}'?", str(cm.exception)) @contextlib.contextmanager - def check_suggestion_not_pressent(self): + def check_suggestion_not_present(self): with self.assertRaises(TypeError) as cm: yield self.assertNotIn("Did you mean", str(cm.exception)) @@ -946,7 +946,7 @@ def foo(blech=None, /, aaa=None, *args, late1=None): for keyword, suggestion in cases: with self.subTest(keyword): - ctx = self.check_suggestion_includes(suggestion) if suggestion else self.check_suggestion_not_pressent() + ctx = self.check_suggestion_includes(suggestion) if suggestion else self.check_suggestion_not_present() with ctx: foo(**{keyword:None}) @@ -987,6 +987,32 @@ def case_change_over_substitution(BLuch=None, Luch = None, fluch = None): with self.check_suggestion_includes(suggestion): func(bluch=None) + def test_unexpected_keyword_suggestion_via_getargs(self): + with self.check_suggestion_includes("maxsplit"): + "foo".split(maxsplt=1) + + self.assertRaisesRegex( + TypeError, r"split\(\) got an unexpected keyword argument 'blech'$", + "foo".split, blech=1 + ) + with self.check_suggestion_not_present(): + "foo".split(blech=1) + with self.check_suggestion_not_present(): + "foo".split(more_noise=1, maxsplt=1) + + # Also test the vgetargskeywords path + with self.check_suggestion_includes("name"): + ImportError(namez="oops") + + self.assertRaisesRegex( + TypeError, r"ImportError\(\) got an unexpected keyword argument 'blech'$", + ImportError, blech=1 + ) + with self.check_suggestion_not_present(): + ImportError(blech=1) + with self.check_suggestion_not_present(): + ImportError(blech=1, namez="oops") + @cpython_only class TestRecursion(unittest.TestCase): diff --git a/Lib/test/test_capi/test_getargs.py b/Lib/test/test_capi/test_getargs.py index 9b6aef27625ad0e..12039803ba543eb 100644 --- a/Lib/test/test_capi/test_getargs.py +++ b/Lib/test/test_capi/test_getargs.py @@ -667,7 +667,7 @@ def test_invalid_keyword(self): try: getargs_keywords((1,2),3,arg5=10,arg666=666) except TypeError as err: - self.assertEqual(str(err), "'arg666' is an invalid keyword argument for this function") + self.assertEqual(str(err), "this function got an unexpected keyword argument 'arg666'") else: self.fail('TypeError should have been raised') @@ -675,7 +675,7 @@ def test_surrogate_keyword(self): try: getargs_keywords((1,2), 3, (4,(5,6)), (7,8,9), **{'\uDC80': 10}) except TypeError as err: - self.assertEqual(str(err), "'\udc80' is an invalid keyword argument for this function") + self.assertEqual(str(err), "this function got an unexpected keyword argument '\udc80'") else: self.fail('TypeError should have been raised') @@ -742,12 +742,12 @@ def test_too_many_args(self): def test_invalid_keyword(self): # extraneous keyword arg with self.assertRaisesRegex(TypeError, - "'monster' is an invalid keyword argument for this function"): + "this function got an unexpected keyword argument 'monster'"): getargs_keyword_only(1, 2, monster=666) def test_surrogate_keyword(self): with self.assertRaisesRegex(TypeError, - "'\udc80' is an invalid keyword argument for this function"): + "this function got an unexpected keyword argument '\udc80'"): getargs_keyword_only(1, 2, **{'\uDC80': 10}) def test_weird_str_subclass(self): @@ -761,7 +761,7 @@ def __hash__(self): "invalid keyword argument for this function"): getargs_keyword_only(1, 2, **{BadStr("keyword_only"): 3}) with self.assertRaisesRegex(TypeError, - "invalid keyword argument for this function"): + "this function got an unexpected keyword argument"): getargs_keyword_only(1, 2, **{BadStr("monster"): 666}) def test_weird_str_subclass2(self): @@ -774,7 +774,7 @@ def __hash__(self): "invalid keyword argument for this function"): getargs_keyword_only(1, 2, **{BadStr("keyword_only"): 3}) with self.assertRaisesRegex(TypeError, - "invalid keyword argument for this function"): + "this function got an unexpected keyword argument"): getargs_keyword_only(1, 2, **{BadStr("monster"): 666}) @@ -807,7 +807,7 @@ def test_required_args(self): def test_empty_keyword(self): with self.assertRaisesRegex(TypeError, - "'' is an invalid keyword argument for this function"): + "this function got an unexpected keyword argument ''"): self.getargs(1, 2, **{'': 666}) @@ -1204,7 +1204,7 @@ def test_basic(self): "function missing required argument 'a'"): parse((), {}, 'O', ['a']) with self.assertRaisesRegex(TypeError, - "'b' is an invalid keyword argument"): + "this function got an unexpected keyword argument 'b'"): parse((), {'b': 1}, '|O', ['a']) with self.assertRaisesRegex(TypeError, fr"argument for function given by name \('a'\) " @@ -1278,10 +1278,10 @@ def test_nonascii_keywords(self): fr"and position \(1\)"): parse((1,), {name: 2}, 'O|O', [name, 'b']) with self.assertRaisesRegex(TypeError, - f"'{name}' is an invalid keyword argument"): + f"this function got an unexpected keyword argument '{name}'"): parse((), {name: 1}, '|O', ['b']) with self.assertRaisesRegex(TypeError, - "'b' is an invalid keyword argument"): + "this function got an unexpected keyword argument 'b'"): parse((), {'b': 1}, '|O', [name]) invalid = name.encode() + (name.encode()[:-1] or b'\x80') @@ -1301,17 +1301,17 @@ def test_nonascii_keywords(self): for name2 in ('b', 'ë', 'ĉ', 'Ɐ', '𐀁'): with self.subTest(name2=name2): with self.assertRaisesRegex(TypeError, - f"'{name2}' is an invalid keyword argument"): + f"this function got an unexpected keyword argument '{name2}'"): parse((), {name2: 1}, '|O', [name]) name2 = name.encode().decode('latin1') if name2 != name: with self.assertRaisesRegex(TypeError, - f"'{name2}' is an invalid keyword argument"): + f"this function got an unexpected keyword argument '{name2}'"): parse((), {name2: 1}, '|O', [name]) name3 = name + '3' with self.assertRaisesRegex(TypeError, - f"'{name2}' is an invalid keyword argument"): + f"this function got an unexpected keyword argument '{name2}'"): parse((), {name2: 1, name3: 2}, '|OO', [name, name3]) def test_nested_tuple(self): diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index c57488e44aecc64..c7e76414ff07154 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1917,7 +1917,7 @@ def test_attributes(self): self.assertEqual(exc.name, 'somename') self.assertEqual(exc.path, 'somepath') - msg = "'invalid' is an invalid keyword argument for ImportError" + msg = r"ImportError\(\) got an unexpected keyword argument 'invalid'" with self.assertRaisesRegex(TypeError, msg): ImportError('test', invalid='keyword') diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-31-09-10-10.gh-issue-107944.XWm1B-.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-31-09-10-10.gh-issue-107944.XWm1B-.rst new file mode 100644 index 000000000000000..8e3fb786c11055b --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-31-09-10-10.gh-issue-107944.XWm1B-.rst @@ -0,0 +1 @@ +Improve error message for function calls with bad keyword arguments via getargs diff --git a/Python/getargs.c b/Python/getargs.c index 0c4ce282f487648..08e97ee3e627b5a 100644 --- a/Python/getargs.c +++ b/Python/getargs.c @@ -8,6 +8,7 @@ #include "pycore_modsupport.h" // export _PyArg_NoKeywords() #include "pycore_pylifecycle.h" // _PyArg_Fini #include "pycore_tuple.h" // _PyTuple_ITEMS() +#include "pycore_pyerrors.h" // _Py_CalculateSuggestions() /* Export Stable ABIs (abi only) */ PyAPI_FUNC(int) _PyArg_Parse_SizeT(PyObject *, const char *, ...); @@ -1424,12 +1425,31 @@ error_unexpected_keyword_arg(PyObject *kwargs, PyObject *kwnames, PyObject *kwtu int match = PySequence_Contains(kwtuple, keyword); if (match <= 0) { if (!match) { - PyErr_Format(PyExc_TypeError, - "'%S' is an invalid keyword " - "argument for %.200s%s", - keyword, - (fname == NULL) ? "this function" : fname, - (fname == NULL) ? "" : "()"); + PyObject *kwlist = PySequence_List(kwtuple); + if (!kwlist) { + return; + } + PyObject *suggestion_keyword = _Py_CalculateSuggestions(kwlist, keyword); + Py_DECREF(kwlist); + + if (suggestion_keyword) { + PyErr_Format(PyExc_TypeError, + "%.200s%s got an unexpected keyword argument '%S'." + " Did you mean '%S'?", + (fname == NULL) ? "this function" : fname, + (fname == NULL) ? "" : "()", + keyword, + suggestion_keyword); + Py_DECREF(suggestion_keyword); + } + else { + PyErr_Format(PyExc_TypeError, + "%.200s%s got an unexpected keyword argument '%S'", + (fname == NULL) ? "this function" : fname, + (fname == NULL) ? "" : "()", + keyword); + } + } return; } @@ -1457,6 +1477,9 @@ PyArg_ValidateKeywordArguments(PyObject *kwargs) return 1; } +static PyObject * +new_kwtuple(const char * const *keywords, int total, int pos); + #define IS_END_OF_FORMAT(c) (c == '\0' || c == ';' || c == ':') static int @@ -1722,12 +1745,35 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format, } } if (!match) { - PyErr_Format(PyExc_TypeError, - "'%U' is an invalid keyword " - "argument for %.200s%s", - key, - (fname == NULL) ? "this function" : fname, - (fname == NULL) ? "" : "()"); + PyObject *_pykwtuple = new_kwtuple(kwlist, len, pos); + if (!_pykwtuple) { + return cleanreturn(0, &freelist); + } + PyObject *pykwlist = PySequence_List(_pykwtuple); + Py_DECREF(_pykwtuple); + if (!pykwlist) { + return cleanreturn(0, &freelist); + } + PyObject *suggestion_keyword = _Py_CalculateSuggestions(pykwlist, key); + Py_DECREF(pykwlist); + + if (suggestion_keyword) { + PyErr_Format(PyExc_TypeError, + "%.200s%s got an unexpected keyword argument '%S'." + " Did you mean '%S'?", + (fname == NULL) ? "this function" : fname, + (fname == NULL) ? "" : "()", + key, + suggestion_keyword); + Py_DECREF(suggestion_keyword); + } + else { + PyErr_Format(PyExc_TypeError, + "%.200s%s got an unexpected keyword argument '%S'", + (fname == NULL) ? "this function" : fname, + (fname == NULL) ? "" : "()", + key); + } return cleanreturn(0, &freelist); } } From ed1a8daf10bc471f929c14c2d1e0474d44a63b00 Mon Sep 17 00:00:00 2001 From: Tomas R <tomas.roun8@gmail.com> Date: Thu, 8 Feb 2024 17:47:27 +0100 Subject: [PATCH 177/507] gh-112069: Adapt set/frozenset methods to Argument Clinic (#115112) --- ...-02-07-00-18-42.gh-issue-112069.jRDRR5.rst | 1 + Objects/clinic/setobject.c.h | 414 +++++++++++++++++ Objects/setobject.c | 416 +++++++++++------- 3 files changed, 674 insertions(+), 157 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-07-00-18-42.gh-issue-112069.jRDRR5.rst create mode 100644 Objects/clinic/setobject.c.h diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-07-00-18-42.gh-issue-112069.jRDRR5.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-07-00-18-42.gh-issue-112069.jRDRR5.rst new file mode 100644 index 000000000000000..51ba6bd1ddaac35 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-07-00-18-42.gh-issue-112069.jRDRR5.rst @@ -0,0 +1 @@ +Adapt :class:`set` and :class:`frozenset` methods to Argument Clinic. diff --git a/Objects/clinic/setobject.c.h b/Objects/clinic/setobject.c.h new file mode 100644 index 000000000000000..f3c96995ede60dd --- /dev/null +++ b/Objects/clinic/setobject.c.h @@ -0,0 +1,414 @@ +/*[clinic input] +preserve +[clinic start generated code]*/ + +#include "pycore_modsupport.h" // _PyArg_CheckPositional() + +PyDoc_STRVAR(set_pop__doc__, +"pop($self, /)\n" +"--\n" +"\n" +"Remove and return an arbitrary set element.\n" +"\n" +"Raises KeyError if the set is empty."); + +#define SET_POP_METHODDEF \ + {"pop", (PyCFunction)set_pop, METH_NOARGS, set_pop__doc__}, + +static PyObject * +set_pop_impl(PySetObject *so); + +static PyObject * +set_pop(PySetObject *so, PyObject *Py_UNUSED(ignored)) +{ + return set_pop_impl(so); +} + +PyDoc_STRVAR(set_update__doc__, +"update($self, /, *others)\n" +"--\n" +"\n" +"Update the set, adding elements from all others."); + +#define SET_UPDATE_METHODDEF \ + {"update", _PyCFunction_CAST(set_update), METH_FASTCALL, set_update__doc__}, + +static PyObject * +set_update_impl(PySetObject *so, PyObject *args); + +static PyObject * +set_update(PySetObject *so, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *__clinic_args = NULL; + + if (!_PyArg_CheckPositional("update", nargs, 0, PY_SSIZE_T_MAX)) { + goto exit; + } + __clinic_args = PyTuple_New(nargs - 0); + if (!__clinic_args) { + goto exit; + } + for (Py_ssize_t i = 0; i < nargs - 0; ++i) { + PyTuple_SET_ITEM(__clinic_args, i, Py_NewRef(args[0 + i])); + } + return_value = set_update_impl(so, __clinic_args); + +exit: + Py_XDECREF(__clinic_args); + return return_value; +} + +PyDoc_STRVAR(set_copy__doc__, +"copy($self, /)\n" +"--\n" +"\n" +"Return a shallow copy of a set."); + +#define SET_COPY_METHODDEF \ + {"copy", (PyCFunction)set_copy, METH_NOARGS, set_copy__doc__}, + +static PyObject * +set_copy_impl(PySetObject *so); + +static PyObject * +set_copy(PySetObject *so, PyObject *Py_UNUSED(ignored)) +{ + return set_copy_impl(so); +} + +PyDoc_STRVAR(frozenset_copy__doc__, +"copy($self, /)\n" +"--\n" +"\n" +"Return a shallow copy of a set."); + +#define FROZENSET_COPY_METHODDEF \ + {"copy", (PyCFunction)frozenset_copy, METH_NOARGS, frozenset_copy__doc__}, + +static PyObject * +frozenset_copy_impl(PySetObject *so); + +static PyObject * +frozenset_copy(PySetObject *so, PyObject *Py_UNUSED(ignored)) +{ + return frozenset_copy_impl(so); +} + +PyDoc_STRVAR(set_clear__doc__, +"clear($self, /)\n" +"--\n" +"\n" +"Remove all elements from this set."); + +#define SET_CLEAR_METHODDEF \ + {"clear", (PyCFunction)set_clear, METH_NOARGS, set_clear__doc__}, + +static PyObject * +set_clear_impl(PySetObject *so); + +static PyObject * +set_clear(PySetObject *so, PyObject *Py_UNUSED(ignored)) +{ + return set_clear_impl(so); +} + +PyDoc_STRVAR(set_union__doc__, +"union($self, /, *others)\n" +"--\n" +"\n" +"Return a new set with elements from the set and all others."); + +#define SET_UNION_METHODDEF \ + {"union", _PyCFunction_CAST(set_union), METH_FASTCALL, set_union__doc__}, + +static PyObject * +set_union_impl(PySetObject *so, PyObject *args); + +static PyObject * +set_union(PySetObject *so, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *__clinic_args = NULL; + + if (!_PyArg_CheckPositional("union", nargs, 0, PY_SSIZE_T_MAX)) { + goto exit; + } + __clinic_args = PyTuple_New(nargs - 0); + if (!__clinic_args) { + goto exit; + } + for (Py_ssize_t i = 0; i < nargs - 0; ++i) { + PyTuple_SET_ITEM(__clinic_args, i, Py_NewRef(args[0 + i])); + } + return_value = set_union_impl(so, __clinic_args); + +exit: + Py_XDECREF(__clinic_args); + return return_value; +} + +PyDoc_STRVAR(set_intersection_multi__doc__, +"intersection($self, /, *others)\n" +"--\n" +"\n" +"Return a new set with elements common to the set and all others."); + +#define SET_INTERSECTION_MULTI_METHODDEF \ + {"intersection", _PyCFunction_CAST(set_intersection_multi), METH_FASTCALL, set_intersection_multi__doc__}, + +static PyObject * +set_intersection_multi_impl(PySetObject *so, PyObject *args); + +static PyObject * +set_intersection_multi(PySetObject *so, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *__clinic_args = NULL; + + if (!_PyArg_CheckPositional("intersection", nargs, 0, PY_SSIZE_T_MAX)) { + goto exit; + } + __clinic_args = PyTuple_New(nargs - 0); + if (!__clinic_args) { + goto exit; + } + for (Py_ssize_t i = 0; i < nargs - 0; ++i) { + PyTuple_SET_ITEM(__clinic_args, i, Py_NewRef(args[0 + i])); + } + return_value = set_intersection_multi_impl(so, __clinic_args); + +exit: + Py_XDECREF(__clinic_args); + return return_value; +} + +PyDoc_STRVAR(set_intersection_update_multi__doc__, +"intersection_update($self, /, *others)\n" +"--\n" +"\n" +"Update the set, keeping only elements found in it and all others."); + +#define SET_INTERSECTION_UPDATE_MULTI_METHODDEF \ + {"intersection_update", _PyCFunction_CAST(set_intersection_update_multi), METH_FASTCALL, set_intersection_update_multi__doc__}, + +static PyObject * +set_intersection_update_multi_impl(PySetObject *so, PyObject *args); + +static PyObject * +set_intersection_update_multi(PySetObject *so, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *__clinic_args = NULL; + + if (!_PyArg_CheckPositional("intersection_update", nargs, 0, PY_SSIZE_T_MAX)) { + goto exit; + } + __clinic_args = PyTuple_New(nargs - 0); + if (!__clinic_args) { + goto exit; + } + for (Py_ssize_t i = 0; i < nargs - 0; ++i) { + PyTuple_SET_ITEM(__clinic_args, i, Py_NewRef(args[0 + i])); + } + return_value = set_intersection_update_multi_impl(so, __clinic_args); + +exit: + Py_XDECREF(__clinic_args); + return return_value; +} + +PyDoc_STRVAR(set_isdisjoint__doc__, +"isdisjoint($self, other, /)\n" +"--\n" +"\n" +"Return True if two sets have a null intersection."); + +#define SET_ISDISJOINT_METHODDEF \ + {"isdisjoint", (PyCFunction)set_isdisjoint, METH_O, set_isdisjoint__doc__}, + +PyDoc_STRVAR(set_difference_update__doc__, +"difference_update($self, /, *others)\n" +"--\n" +"\n" +"Update the set, removing elements found in others."); + +#define SET_DIFFERENCE_UPDATE_METHODDEF \ + {"difference_update", _PyCFunction_CAST(set_difference_update), METH_FASTCALL, set_difference_update__doc__}, + +static PyObject * +set_difference_update_impl(PySetObject *so, PyObject *args); + +static PyObject * +set_difference_update(PySetObject *so, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *__clinic_args = NULL; + + if (!_PyArg_CheckPositional("difference_update", nargs, 0, PY_SSIZE_T_MAX)) { + goto exit; + } + __clinic_args = PyTuple_New(nargs - 0); + if (!__clinic_args) { + goto exit; + } + for (Py_ssize_t i = 0; i < nargs - 0; ++i) { + PyTuple_SET_ITEM(__clinic_args, i, Py_NewRef(args[0 + i])); + } + return_value = set_difference_update_impl(so, __clinic_args); + +exit: + Py_XDECREF(__clinic_args); + return return_value; +} + +PyDoc_STRVAR(set_difference_multi__doc__, +"difference($self, /, *others)\n" +"--\n" +"\n" +"Return a new set with elements in the set that are not in the others."); + +#define SET_DIFFERENCE_MULTI_METHODDEF \ + {"difference", _PyCFunction_CAST(set_difference_multi), METH_FASTCALL, set_difference_multi__doc__}, + +static PyObject * +set_difference_multi_impl(PySetObject *so, PyObject *args); + +static PyObject * +set_difference_multi(PySetObject *so, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *__clinic_args = NULL; + + if (!_PyArg_CheckPositional("difference", nargs, 0, PY_SSIZE_T_MAX)) { + goto exit; + } + __clinic_args = PyTuple_New(nargs - 0); + if (!__clinic_args) { + goto exit; + } + for (Py_ssize_t i = 0; i < nargs - 0; ++i) { + PyTuple_SET_ITEM(__clinic_args, i, Py_NewRef(args[0 + i])); + } + return_value = set_difference_multi_impl(so, __clinic_args); + +exit: + Py_XDECREF(__clinic_args); + return return_value; +} + +PyDoc_STRVAR(set_symmetric_difference_update__doc__, +"symmetric_difference_update($self, other, /)\n" +"--\n" +"\n" +"Update the set, keeping only elements found in either set, but not in both."); + +#define SET_SYMMETRIC_DIFFERENCE_UPDATE_METHODDEF \ + {"symmetric_difference_update", (PyCFunction)set_symmetric_difference_update, METH_O, set_symmetric_difference_update__doc__}, + +PyDoc_STRVAR(set_symmetric_difference__doc__, +"symmetric_difference($self, other, /)\n" +"--\n" +"\n" +"Return a new set with elements in either the set or other but not both."); + +#define SET_SYMMETRIC_DIFFERENCE_METHODDEF \ + {"symmetric_difference", (PyCFunction)set_symmetric_difference, METH_O, set_symmetric_difference__doc__}, + +PyDoc_STRVAR(set_issubset__doc__, +"issubset($self, other, /)\n" +"--\n" +"\n" +"Report whether another set contains this set."); + +#define SET_ISSUBSET_METHODDEF \ + {"issubset", (PyCFunction)set_issubset, METH_O, set_issubset__doc__}, + +PyDoc_STRVAR(set_issuperset__doc__, +"issuperset($self, other, /)\n" +"--\n" +"\n" +"Report whether this set contains another set."); + +#define SET_ISSUPERSET_METHODDEF \ + {"issuperset", (PyCFunction)set_issuperset, METH_O, set_issuperset__doc__}, + +PyDoc_STRVAR(set_add__doc__, +"add($self, object, /)\n" +"--\n" +"\n" +"Add an element to a set.\n" +"\n" +"This has no effect if the element is already present."); + +#define SET_ADD_METHODDEF \ + {"add", (PyCFunction)set_add, METH_O, set_add__doc__}, + +PyDoc_STRVAR(set___contains____doc__, +"__contains__($self, object, /)\n" +"--\n" +"\n" +"x.__contains__(y) <==> y in x."); + +#define SET___CONTAINS___METHODDEF \ + {"__contains__", (PyCFunction)set___contains__, METH_O|METH_COEXIST, set___contains____doc__}, + +PyDoc_STRVAR(set_remove__doc__, +"remove($self, object, /)\n" +"--\n" +"\n" +"Remove an element from a set; it must be a member.\n" +"\n" +"If the element is not a member, raise a KeyError."); + +#define SET_REMOVE_METHODDEF \ + {"remove", (PyCFunction)set_remove, METH_O, set_remove__doc__}, + +PyDoc_STRVAR(set_discard__doc__, +"discard($self, object, /)\n" +"--\n" +"\n" +"Remove an element from a set if it is a member.\n" +"\n" +"Unlike set.remove(), the discard() method does not raise\n" +"an exception when an element is missing from the set."); + +#define SET_DISCARD_METHODDEF \ + {"discard", (PyCFunction)set_discard, METH_O, set_discard__doc__}, + +PyDoc_STRVAR(set___reduce____doc__, +"__reduce__($self, /)\n" +"--\n" +"\n" +"Return state information for pickling."); + +#define SET___REDUCE___METHODDEF \ + {"__reduce__", (PyCFunction)set___reduce__, METH_NOARGS, set___reduce____doc__}, + +static PyObject * +set___reduce___impl(PySetObject *so); + +static PyObject * +set___reduce__(PySetObject *so, PyObject *Py_UNUSED(ignored)) +{ + return set___reduce___impl(so); +} + +PyDoc_STRVAR(set___sizeof____doc__, +"__sizeof__($self, /)\n" +"--\n" +"\n" +"S.__sizeof__() -> size of S in memory, in bytes."); + +#define SET___SIZEOF___METHODDEF \ + {"__sizeof__", (PyCFunction)set___sizeof__, METH_NOARGS, set___sizeof____doc__}, + +static PyObject * +set___sizeof___impl(PySetObject *so); + +static PyObject * +set___sizeof__(PySetObject *so, PyObject *Py_UNUSED(ignored)) +{ + return set___sizeof___impl(so); +} +/*[clinic end generated code: output=34a30591148da884 input=a9049054013a1b77]*/ diff --git a/Objects/setobject.c b/Objects/setobject.c index 3acf2a7a74890b1..6a4c8c45f0836d1 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -40,6 +40,19 @@ #include "pycore_pyerrors.h" // _PyErr_SetKeyError() #include "pycore_setobject.h" // _PySet_NextEntry() definition #include <stddef.h> // offsetof() +#include "clinic/setobject.c.h" + +/*[clinic input] +class set "PySetObject *" "&PySet_Type" +class frozenset "PySetObject *" "&PyFrozenSet_Type" +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=97ad1d3e9f117079]*/ + +/*[python input] +class setobject_converter(self_converter): + type = "PySetObject *" +[python start generated code]*/ +/*[python end generated code: output=da39a3ee5e6b4b0d input=33a44506d4d57793]*/ /* Object used as dummy key to fill deleted entries */ static PyObject _dummy_struct; @@ -631,8 +644,18 @@ set_merge(PySetObject *so, PyObject *otherset) return 0; } +/*[clinic input] +set.pop + so: setobject + +Remove and return an arbitrary set element. + +Raises KeyError if the set is empty. +[clinic start generated code]*/ + static PyObject * -set_pop(PySetObject *so, PyObject *Py_UNUSED(ignored)) +set_pop_impl(PySetObject *so) +/*[clinic end generated code: output=4d65180f1271871b input=4a3f5552e660a260]*/ { /* Make sure the search finger is in bounds */ setentry *entry = so->table + (so->finger & so->mask); @@ -656,9 +679,6 @@ set_pop(PySetObject *so, PyObject *Py_UNUSED(ignored)) return key; } -PyDoc_STRVAR(pop_doc, "Remove and return an arbitrary set element.\n\ -Raises KeyError if the set is empty."); - static int set_traverse(PySetObject *so, visitproc visit, void *arg) { @@ -935,8 +955,18 @@ set_update_internal(PySetObject *so, PyObject *other) return 0; } +/*[clinic input] +set.update + so: setobject + *others as args: object + / + +Update the set, adding elements from all others. +[clinic start generated code]*/ + static PyObject * -set_update(PySetObject *so, PyObject *args) +set_update_impl(PySetObject *so, PyObject *args) +/*[clinic end generated code: output=34f6371704974c8a input=eb47c4fbaeb3286e]*/ { Py_ssize_t i; @@ -948,12 +978,6 @@ set_update(PySetObject *so, PyObject *args) Py_RETURN_NONE; } -PyDoc_STRVAR(update_doc, -"update($self, /, *others)\n\ ---\n\ -\n\ -Update the set, adding elements from all others."); - /* XXX Todo: If aligned memory allocations become available, make the set object 64 byte aligned so that most of the fields @@ -1101,14 +1125,30 @@ set_swap_bodies(PySetObject *a, PySetObject *b) } } +/*[clinic input] +set.copy + so: setobject + +Return a shallow copy of a set. +[clinic start generated code]*/ + static PyObject * -set_copy(PySetObject *so, PyObject *Py_UNUSED(ignored)) +set_copy_impl(PySetObject *so) +/*[clinic end generated code: output=c9223a1e1cc6b041 input=2b80b288d47b8cf1]*/ { return make_new_set_basetype(Py_TYPE(so), (PyObject *)so); } +/*[clinic input] +frozenset.copy + so: setobject + +Return a shallow copy of a set. +[clinic start generated code]*/ + static PyObject * -frozenset_copy(PySetObject *so, PyObject *Py_UNUSED(ignored)) +frozenset_copy_impl(PySetObject *so) +/*[clinic end generated code: output=b356263526af9e70 input=3dc65577d344eff7]*/ { if (PyFrozenSet_CheckExact(so)) { return Py_NewRef(so); @@ -1116,19 +1156,33 @@ frozenset_copy(PySetObject *so, PyObject *Py_UNUSED(ignored)) return set_copy(so, NULL); } -PyDoc_STRVAR(copy_doc, "Return a shallow copy of a set."); +/*[clinic input] +set.clear + so: setobject + +Remove all elements from this set. +[clinic start generated code]*/ static PyObject * -set_clear(PySetObject *so, PyObject *Py_UNUSED(ignored)) +set_clear_impl(PySetObject *so) +/*[clinic end generated code: output=4e71d5a83904161a input=74ac19794da81a39]*/ { set_clear_internal(so); Py_RETURN_NONE; } -PyDoc_STRVAR(clear_doc, "Remove all elements from this set."); +/*[clinic input] +set.union + so: setobject + *others as args: object + / + +Return a new set with elements from the set and all others. +[clinic start generated code]*/ static PyObject * -set_union(PySetObject *so, PyObject *args) +set_union_impl(PySetObject *so, PyObject *args) +/*[clinic end generated code: output=2c83d05a446a1477 input=2e2024fa1e40ac84]*/ { PySetObject *result; PyObject *other; @@ -1150,12 +1204,6 @@ set_union(PySetObject *so, PyObject *args) return (PyObject *)result; } -PyDoc_STRVAR(union_doc, -"union($self, /, *others)\n\ ---\n\ -\n\ -Return a new set with elements from the set and all others."); - static PyObject * set_or(PySetObject *so, PyObject *other) { @@ -1270,8 +1318,18 @@ set_intersection(PySetObject *so, PyObject *other) return NULL; } +/*[clinic input] +set.intersection as set_intersection_multi + so: setobject + *others as args: object + / + +Return a new set with elements common to the set and all others. +[clinic start generated code]*/ + static PyObject * -set_intersection_multi(PySetObject *so, PyObject *args) +set_intersection_multi_impl(PySetObject *so, PyObject *args) +/*[clinic end generated code: output=2406ef3387adbe2f input=04108ea6d7f0532b]*/ { Py_ssize_t i; @@ -1291,12 +1349,6 @@ set_intersection_multi(PySetObject *so, PyObject *args) return result; } -PyDoc_STRVAR(intersection_doc, -"intersection($self, /, *others)\n\ ---\n\ -\n\ -Return a new set with elements common to the set and all others."); - static PyObject * set_intersection_update(PySetObject *so, PyObject *other) { @@ -1310,12 +1362,22 @@ set_intersection_update(PySetObject *so, PyObject *other) Py_RETURN_NONE; } +/*[clinic input] +set.intersection_update as set_intersection_update_multi + so: setobject + *others as args: object + / + +Update the set, keeping only elements found in it and all others. +[clinic start generated code]*/ + static PyObject * -set_intersection_update_multi(PySetObject *so, PyObject *args) +set_intersection_update_multi_impl(PySetObject *so, PyObject *args) +/*[clinic end generated code: output=251c1f729063609d input=ff8f119f97458d16]*/ { PyObject *tmp; - tmp = set_intersection_multi(so, args); + tmp = set_intersection_multi_impl(so, args); if (tmp == NULL) return NULL; set_swap_bodies(so, (PySetObject *)tmp); @@ -1323,12 +1385,6 @@ set_intersection_update_multi(PySetObject *so, PyObject *args) Py_RETURN_NONE; } -PyDoc_STRVAR(intersection_update_doc, -"intersection_update($self, /, *others)\n\ ---\n\ -\n\ -Update the set, keeping only elements found in it and all others."); - static PyObject * set_and(PySetObject *so, PyObject *other) { @@ -1351,8 +1407,18 @@ set_iand(PySetObject *so, PyObject *other) return Py_NewRef(so); } +/*[clinic input] +set.isdisjoint + so: setobject + other: object + / + +Return True if two sets have a null intersection. +[clinic start generated code]*/ + static PyObject * set_isdisjoint(PySetObject *so, PyObject *other) +/*[clinic end generated code: output=a92bbf9a2db6a3da input=c254ddec8a2326e3]*/ { PyObject *key, *it, *tmp; int rv; @@ -1410,9 +1476,6 @@ set_isdisjoint(PySetObject *so, PyObject *other) Py_RETURN_TRUE; } -PyDoc_STRVAR(isdisjoint_doc, -"Return True if two sets have a null intersection."); - static int set_difference_update_internal(PySetObject *so, PyObject *other) { @@ -1471,8 +1534,18 @@ set_difference_update_internal(PySetObject *so, PyObject *other) return set_table_resize(so, so->used>50000 ? so->used*2 : so->used*4); } +/*[clinic input] +set.difference_update + so: setobject + *others as args: object + / + +Update the set, removing elements found in others. +[clinic start generated code]*/ + static PyObject * -set_difference_update(PySetObject *so, PyObject *args) +set_difference_update_impl(PySetObject *so, PyObject *args) +/*[clinic end generated code: output=28685b2fc63e41c4 input=e7abb43c9f2c5a73]*/ { Py_ssize_t i; @@ -1484,12 +1557,6 @@ set_difference_update(PySetObject *so, PyObject *args) Py_RETURN_NONE; } -PyDoc_STRVAR(difference_update_doc, -"difference_update($self, /, *others)\n\ ---\n\ -\n\ -Update the set, removing elements found in others."); - static PyObject * set_copy_and_difference(PySetObject *so, PyObject *other) { @@ -1580,8 +1647,18 @@ set_difference(PySetObject *so, PyObject *other) return result; } +/*[clinic input] +set.difference as set_difference_multi + so: setobject + *others as args: object + / + +Return a new set with elements in the set that are not in the others. +[clinic start generated code]*/ + static PyObject * -set_difference_multi(PySetObject *so, PyObject *args) +set_difference_multi_impl(PySetObject *so, PyObject *args) +/*[clinic end generated code: output=3130c3bb3cac873d input=d8ae9bb6d518ab95]*/ { Py_ssize_t i; PyObject *result, *other; @@ -1604,11 +1681,6 @@ set_difference_multi(PySetObject *so, PyObject *args) return result; } -PyDoc_STRVAR(difference_doc, -"difference($self, /, *others)\n\ ---\n\ -\n\ -Return a new set with elements in the set that are not in the others."); static PyObject * set_sub(PySetObject *so, PyObject *other) { @@ -1654,8 +1726,18 @@ set_symmetric_difference_update_dict(PySetObject *so, PyObject *other) Py_RETURN_NONE; } +/*[clinic input] +set.symmetric_difference_update + so: setobject + other: object + / + +Update the set, keeping only elements found in either set, but not in both. +[clinic start generated code]*/ + static PyObject * set_symmetric_difference_update(PySetObject *so, PyObject *other) +/*[clinic end generated code: output=fbb049c0806028de input=a50acf0365e1f0a5]*/ { PySetObject *otherset; PyObject *key; @@ -1708,14 +1790,18 @@ set_symmetric_difference_update(PySetObject *so, PyObject *other) Py_RETURN_NONE; } -PyDoc_STRVAR(symmetric_difference_update_doc, -"symmetric_difference_update($self, other, /)\n\ ---\n\ -\n\ -Update the set, keeping only elements found in either set, but not in both."); +/*[clinic input] +set.symmetric_difference + so: setobject + other: object + / + +Return a new set with elements in either the set or other but not both. +[clinic start generated code]*/ static PyObject * set_symmetric_difference(PySetObject *so, PyObject *other) +/*[clinic end generated code: output=f95364211b88775a input=f18af370ad72ebac]*/ { PyObject *rv; PySetObject *otherset; @@ -1732,12 +1818,6 @@ set_symmetric_difference(PySetObject *so, PyObject *other) return (PyObject *)otherset; } -PyDoc_STRVAR(symmetric_difference_doc, -"symmetric_difference($self, other, /)\n\ ---\n\ -\n\ -Return a new set with elements in either the set or other but not both."); - static PyObject * set_xor(PySetObject *so, PyObject *other) { @@ -1760,8 +1840,18 @@ set_ixor(PySetObject *so, PyObject *other) return Py_NewRef(so); } +/*[clinic input] +set.issubset + so: setobject + other: object + / + +Report whether another set contains this set. +[clinic start generated code]*/ + static PyObject * set_issubset(PySetObject *so, PyObject *other) +/*[clinic end generated code: output=78aef1f377aedef1 input=37fbc579b609db0c]*/ { setentry *entry; Py_ssize_t pos = 0; @@ -1794,14 +1884,18 @@ set_issubset(PySetObject *so, PyObject *other) Py_RETURN_TRUE; } -PyDoc_STRVAR(issubset_doc, -"issubset($self, other, /)\n\ ---\n\ -\n\ -Test whether every element in the set is in other."); +/*[clinic input] +set.issuperset + so: setobject + other: object + / + +Report whether this set contains another set. +[clinic start generated code]*/ static PyObject * set_issuperset(PySetObject *so, PyObject *other) +/*[clinic end generated code: output=7d2b71dd714a7ec7 input=fd5dab052f2e9bb3]*/ { if (PyAnySet_Check(other)) { return set_issubset((PySetObject *)other, (PyObject *)so); @@ -1830,12 +1924,6 @@ set_issuperset(PySetObject *so, PyObject *other) Py_RETURN_TRUE; } -PyDoc_STRVAR(issuperset_doc, -"issuperset($self, other, /)\n\ ---\n\ -\n\ -Test whether every element in other is in the set."); - static PyObject * set_richcompare(PySetObject *v, PyObject *w, int op) { @@ -1879,19 +1967,26 @@ set_richcompare(PySetObject *v, PyObject *w, int op) Py_RETURN_NOTIMPLEMENTED; } +/*[clinic input] +set.add + so: setobject + object as key: object + / + +Add an element to a set. + +This has no effect if the element is already present. +[clinic start generated code]*/ + static PyObject * set_add(PySetObject *so, PyObject *key) +/*[clinic end generated code: output=cd9c2d5c2069c2ba input=96f1efe029e47972]*/ { if (set_add_key(so, key)) return NULL; Py_RETURN_NONE; } -PyDoc_STRVAR(add_doc, -"Add an element to a set.\n\ -\n\ -This has no effect if the element is already present."); - static int set_contains(PySetObject *so, PyObject *key) { @@ -1912,8 +2007,19 @@ set_contains(PySetObject *so, PyObject *key) return rv; } +/*[clinic input] +@coexist +set.__contains__ + so: setobject + object as key: object + / + +x.__contains__(y) <==> y in x. +[clinic start generated code]*/ + static PyObject * -set_direct_contains(PySetObject *so, PyObject *key) +set___contains__(PySetObject *so, PyObject *key) +/*[clinic end generated code: output=b5948bc5c590d3ca input=cf4c72db704e4cf0]*/ { long result; @@ -1923,10 +2029,20 @@ set_direct_contains(PySetObject *so, PyObject *key) return PyBool_FromLong(result); } -PyDoc_STRVAR(contains_doc, "x.__contains__(y) <==> y in x."); +/*[clinic input] +set.remove + so: setobject + object as key: object + / + +Remove an element from a set; it must be a member. + +If the element is not a member, raise a KeyError. +[clinic start generated code]*/ static PyObject * set_remove(PySetObject *so, PyObject *key) +/*[clinic end generated code: output=08ae496d0cd2b8c1 input=10132515dfe8ebd7]*/ { PyObject *tmpkey; int rv; @@ -1952,13 +2068,21 @@ set_remove(PySetObject *so, PyObject *key) Py_RETURN_NONE; } -PyDoc_STRVAR(remove_doc, -"Remove an element from a set; it must be a member.\n\ -\n\ -If the element is not a member, raise a KeyError."); +/*[clinic input] +set.discard + so: setobject + object as key: object + / + +Remove an element from a set if it is a member. + +Unlike set.remove(), the discard() method does not raise +an exception when an element is missing from the set. +[clinic start generated code]*/ static PyObject * set_discard(PySetObject *so, PyObject *key) +/*[clinic end generated code: output=9181b60d7bb7d480 input=82a689eba94d5ad9]*/ { PyObject *tmpkey; int rv; @@ -1979,14 +2103,16 @@ set_discard(PySetObject *so, PyObject *key) Py_RETURN_NONE; } -PyDoc_STRVAR(discard_doc, -"Remove an element from a set if it is a member.\n\ -\n\ -Unlike set.remove(), the discard() method does not raise\n\ -an exception when an element is missing from the set."); +/*[clinic input] +set.__reduce__ + so: setobject + +Return state information for pickling. +[clinic start generated code]*/ static PyObject * -set_reduce(PySetObject *so, PyObject *Py_UNUSED(ignored)) +set___reduce___impl(PySetObject *so) +/*[clinic end generated code: output=9af7d0e029df87ee input=531375e87a24a449]*/ { PyObject *keys=NULL, *args=NULL, *result=NULL, *state=NULL; @@ -2007,8 +2133,16 @@ set_reduce(PySetObject *so, PyObject *Py_UNUSED(ignored)) return result; } +/*[clinic input] +set.__sizeof__ + so: setobject + +S.__sizeof__() -> size of S in memory, in bytes. +[clinic start generated code]*/ + static PyObject * -set_sizeof(PySetObject *so, PyObject *Py_UNUSED(ignored)) +set___sizeof___impl(PySetObject *so) +/*[clinic end generated code: output=4bfa3df7bd38ed88 input=0f214fc2225319fc]*/ { size_t res = _PyObject_SIZE(Py_TYPE(so)); if (so->table != so->smalltable) { @@ -2017,7 +2151,6 @@ set_sizeof(PySetObject *so, PyObject *Py_UNUSED(ignored)) return PyLong_FromSize_t(res); } -PyDoc_STRVAR(sizeof_doc, "S.__sizeof__() -> size of S in memory, in bytes"); static int set_init(PySetObject *self, PyObject *args, PyObject *kwds) { @@ -2071,46 +2204,26 @@ static PySequenceMethods set_as_sequence = { /* set object ********************************************************/ static PyMethodDef set_methods[] = { - {"add", (PyCFunction)set_add, METH_O, - add_doc}, - {"clear", (PyCFunction)set_clear, METH_NOARGS, - clear_doc}, - {"__contains__",(PyCFunction)set_direct_contains, METH_O | METH_COEXIST, - contains_doc}, - {"copy", (PyCFunction)set_copy, METH_NOARGS, - copy_doc}, - {"discard", (PyCFunction)set_discard, METH_O, - discard_doc}, - {"difference", (PyCFunction)set_difference_multi, METH_VARARGS, - difference_doc}, - {"difference_update", (PyCFunction)set_difference_update, METH_VARARGS, - difference_update_doc}, - {"intersection",(PyCFunction)set_intersection_multi, METH_VARARGS, - intersection_doc}, - {"intersection_update",(PyCFunction)set_intersection_update_multi, METH_VARARGS, - intersection_update_doc}, - {"isdisjoint", (PyCFunction)set_isdisjoint, METH_O, - isdisjoint_doc}, - {"issubset", (PyCFunction)set_issubset, METH_O, - issubset_doc}, - {"issuperset", (PyCFunction)set_issuperset, METH_O, - issuperset_doc}, - {"pop", (PyCFunction)set_pop, METH_NOARGS, - pop_doc}, - {"__reduce__", (PyCFunction)set_reduce, METH_NOARGS, - reduce_doc}, - {"remove", (PyCFunction)set_remove, METH_O, - remove_doc}, - {"__sizeof__", (PyCFunction)set_sizeof, METH_NOARGS, - sizeof_doc}, - {"symmetric_difference",(PyCFunction)set_symmetric_difference, METH_O, - symmetric_difference_doc}, - {"symmetric_difference_update",(PyCFunction)set_symmetric_difference_update, METH_O, - symmetric_difference_update_doc}, - {"union", (PyCFunction)set_union, METH_VARARGS, - union_doc}, - {"update", (PyCFunction)set_update, METH_VARARGS, - update_doc}, + SET_ADD_METHODDEF + SET_CLEAR_METHODDEF + SET___CONTAINS___METHODDEF + SET_COPY_METHODDEF + SET_DISCARD_METHODDEF + SET_DIFFERENCE_MULTI_METHODDEF + SET_DIFFERENCE_UPDATE_METHODDEF + SET_INTERSECTION_MULTI_METHODDEF + SET_INTERSECTION_UPDATE_MULTI_METHODDEF + SET_ISDISJOINT_METHODDEF + SET_ISSUBSET_METHODDEF + SET_ISSUPERSET_METHODDEF + SET_POP_METHODDEF + SET___REDUCE___METHODDEF + SET_REMOVE_METHODDEF + SET___SIZEOF___METHODDEF + SET_SYMMETRIC_DIFFERENCE_METHODDEF + SET_SYMMETRIC_DIFFERENCE_UPDATE_METHODDEF + SET_UNION_METHODDEF + SET_UPDATE_METHODDEF {"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")}, {NULL, NULL} /* sentinel */ }; @@ -2203,28 +2316,17 @@ PyTypeObject PySet_Type = { static PyMethodDef frozenset_methods[] = { - {"__contains__",(PyCFunction)set_direct_contains, METH_O | METH_COEXIST, - contains_doc}, - {"copy", (PyCFunction)frozenset_copy, METH_NOARGS, - copy_doc}, - {"difference", (PyCFunction)set_difference_multi, METH_VARARGS, - difference_doc}, - {"intersection", (PyCFunction)set_intersection_multi, METH_VARARGS, - intersection_doc}, - {"isdisjoint", (PyCFunction)set_isdisjoint, METH_O, - isdisjoint_doc}, - {"issubset", (PyCFunction)set_issubset, METH_O, - issubset_doc}, - {"issuperset", (PyCFunction)set_issuperset, METH_O, - issuperset_doc}, - {"__reduce__", (PyCFunction)set_reduce, METH_NOARGS, - reduce_doc}, - {"__sizeof__", (PyCFunction)set_sizeof, METH_NOARGS, - sizeof_doc}, - {"symmetric_difference",(PyCFunction)set_symmetric_difference, METH_O, - symmetric_difference_doc}, - {"union", (PyCFunction)set_union, METH_VARARGS, - union_doc}, + SET___CONTAINS___METHODDEF + FROZENSET_COPY_METHODDEF + SET_DIFFERENCE_MULTI_METHODDEF + SET_INTERSECTION_MULTI_METHODDEF + SET_ISDISJOINT_METHODDEF + SET_ISSUBSET_METHODDEF + SET_ISSUPERSET_METHODDEF + SET___REDUCE___METHODDEF + SET___SIZEOF___METHODDEF + SET_SYMMETRIC_DIFFERENCE_METHODDEF + SET_UNION_METHODDEF {"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")}, {NULL, NULL} /* sentinel */ }; From 5914a211ef5542edd1f792c2684e373a42647b04 Mon Sep 17 00:00:00 2001 From: adang1345 <adang1345@gmail.com> Date: Thu, 8 Feb 2024 16:42:45 -0500 Subject: [PATCH 178/507] gh-115167: Exclude vcruntime140_threads.dll from Windows build output (GH-115176) --- .../next/Build/2024-02-08-19-36-20.gh-issue-115167.LB9nDK.rst | 1 + PCbuild/pyproject.props | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Build/2024-02-08-19-36-20.gh-issue-115167.LB9nDK.rst diff --git a/Misc/NEWS.d/next/Build/2024-02-08-19-36-20.gh-issue-115167.LB9nDK.rst b/Misc/NEWS.d/next/Build/2024-02-08-19-36-20.gh-issue-115167.LB9nDK.rst new file mode 100644 index 000000000000000..c60c4a93fe8906c --- /dev/null +++ b/Misc/NEWS.d/next/Build/2024-02-08-19-36-20.gh-issue-115167.LB9nDK.rst @@ -0,0 +1 @@ +Avoid vendoring ``vcruntime140_threads.dll`` when building with Visual Studio 2022 version 17.8. diff --git a/PCbuild/pyproject.props b/PCbuild/pyproject.props index fd5fbc9e910eee4..9c85e5efa4af4ad 100644 --- a/PCbuild/pyproject.props +++ b/PCbuild/pyproject.props @@ -250,7 +250,7 @@ public override bool Execute() { <VCRuntimeDLL Include="$(VCRuntimeDLL)" /> </ItemGroup> <ItemGroup Condition="$(VCInstallDir) != '' and $(VCRuntimeDLL) == ''"> - <VCRuntimeDLL Include="$(VCRedistDir)\Microsoft.VC*.CRT\vcruntime*.dll" /> + <VCRuntimeDLL Include="$(VCRedistDir)\Microsoft.VC*.CRT\vcruntime*.dll" Exclude="$(VCRedistDir)\Microsoft.VC*.CRT\vcruntime*_threads.dll" /> </ItemGroup> <Warning Text="vcruntime*.dll not found under $(VCRedistDir)." Condition="@(VCRuntimeDLL) == ''" /> From 553c90ccc2f5b15be76a2bb6e38d23e58d739e2f Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Fri, 9 Feb 2024 09:40:28 +0300 Subject: [PATCH 179/507] gh-101100: Fix sphinx warnings in `library/enum.rst` (#114696) Co-authored-by: Ethan Furman <ethan@stoneleaf.us> --- Doc/library/enum.rst | 17 +++++++++++++++-- Doc/tools/.nitignore | 1 - 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index 534939943d33267..30d80ce8d488ccb 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -286,6 +286,19 @@ Data Types appropriate value will be chosen for you. See :class:`auto` for the details. + .. attribute:: Enum._name_ + + Name of the member. + + .. attribute:: Enum._value_ + + Value of the member, can be set in :meth:`~object.__new__`. + + .. attribute:: Enum._order_ + + No longer used, kept for backward compatibility. + (class attribute, removed during class creation). + .. attribute:: Enum._ignore_ ``_ignore_`` is only used during creation and is removed from the @@ -823,8 +836,8 @@ Supported ``_sunder_`` names - :attr:`~Enum._ignore_` -- a list of names, either as a :class:`list` or a :class:`str`, that will not be transformed into members, and will be removed from the final class -- :attr:`~Enum._order_` -- used in Python 2/3 code to ensure member order is - consistent (class attribute, removed during class creation) +- :attr:`~Enum._order_` -- no longer used, kept for backward + compatibility (class attribute, removed during class creation) - :meth:`~Enum._generate_next_value_` -- used to get an appropriate value for an enum member; may be overridden diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index f96478b45e44c00..9db02c5c3c73c90 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -31,7 +31,6 @@ Doc/library/email.compat32-message.rst Doc/library/email.errors.rst Doc/library/email.parser.rst Doc/library/email.policy.rst -Doc/library/enum.rst Doc/library/exceptions.rst Doc/library/faulthandler.rst Doc/library/fcntl.rst From c968dc7ff3041137bb702436ff944692dede1ad1 Mon Sep 17 00:00:00 2001 From: Brett Cannon <brett@python.org> Date: Fri, 9 Feb 2024 00:21:49 -0800 Subject: [PATCH 180/507] GH-113632: update configure.ac for WebAssembly support tiers (#115192) Move WASI to tier 2 and drop Emscripten. --- Doc/whatsnew/3.13.rst | 6 ++++++ .../Build/2024-02-08-17-38-56.gh-issue-113632.y9KIGb.rst | 2 ++ configure | 6 ++---- configure.ac | 3 +-- 4 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2024-02-08-17-38-56.gh-issue-113632.y9KIGb.rst diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 50a2a69c75ac70b..b05e4badc9e58bf 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1339,6 +1339,12 @@ Build Changes :ref:`limited C API <limited-c-api>`. (Contributed by Victor Stinner in :gh:`85283`.) +* ``wasm32-wasi`` is now a tier 2 platform. + (Contributed by Brett Cannon in :gh:`115192`.) + +* ``wasm32-emscripten`` is no longer a supported platform. + (Contributed by Brett Cannon in :gh:`115192`.) + C API Changes ============= diff --git a/Misc/NEWS.d/next/Build/2024-02-08-17-38-56.gh-issue-113632.y9KIGb.rst b/Misc/NEWS.d/next/Build/2024-02-08-17-38-56.gh-issue-113632.y9KIGb.rst new file mode 100644 index 000000000000000..8b02b1b2cd08c90 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2024-02-08-17-38-56.gh-issue-113632.y9KIGb.rst @@ -0,0 +1,2 @@ +Promote WASI to a tier 2 platform and drop Emscripten from tier 3 in +configure.ac. diff --git a/configure b/configure index 0375565c2945528..705a778cafced35 100755 --- a/configure +++ b/configure @@ -6805,6 +6805,8 @@ case $host/$ac_cv_cc_name in #( aarch64-*-linux-gnu/clang) : PY_SUPPORT_TIER=2 ;; #( powerpc64le-*-linux-gnu/gcc) : + PY_SUPPORT_TIER=2 ;; #( + wasm32-unknown-wasi/clang) : PY_SUPPORT_TIER=2 ;; #( x86_64-*-linux-gnu/clang) : PY_SUPPORT_TIER=2 ;; #( @@ -6817,10 +6819,6 @@ case $host/$ac_cv_cc_name in #( PY_SUPPORT_TIER=3 ;; #( s390x-*-linux-gnu/gcc) : PY_SUPPORT_TIER=3 ;; #( - wasm32-unknown-emscripten/clang) : - PY_SUPPORT_TIER=3 ;; #( - wasm32-unknown-wasi/clang) : - PY_SUPPORT_TIER=3 ;; #( x86_64-*-freebsd*/clang) : PY_SUPPORT_TIER=3 ;; #( *) : diff --git a/configure.ac b/configure.ac index e121e893a1d0d9e..dee7ed552b370f0 100644 --- a/configure.ac +++ b/configure.ac @@ -973,14 +973,13 @@ AS_CASE([$host/$ac_cv_cc_name], [aarch64-*-linux-gnu/gcc], [PY_SUPPORT_TIER=2], dnl Linux ARM64, glibc, gcc+clang [aarch64-*-linux-gnu/clang], [PY_SUPPORT_TIER=2], [powerpc64le-*-linux-gnu/gcc], [PY_SUPPORT_TIER=2], dnl Linux on PPC64 little endian, glibc, gcc + [wasm32-unknown-wasi/clang], [PY_SUPPORT_TIER=2], dnl WebAssembly System Interface, clang [x86_64-*-linux-gnu/clang], [PY_SUPPORT_TIER=2], dnl Linux on AMD64, any vendor, glibc, clang [aarch64-pc-windows-msvc/msvc], [PY_SUPPORT_TIER=3], dnl Windows ARM64, MSVC [armv7l-*-linux-gnueabihf/gcc], [PY_SUPPORT_TIER=3], dnl ARMv7 LE with hardware floats, any vendor, glibc, gcc [powerpc64le-*-linux-gnu/clang], [PY_SUPPORT_TIER=3], dnl Linux on PPC64 little endian, glibc, clang [s390x-*-linux-gnu/gcc], [PY_SUPPORT_TIER=3], dnl Linux on 64bit s390x (big endian), glibc, gcc - [wasm32-unknown-emscripten/clang], [PY_SUPPORT_TIER=3], dnl WebAssembly Emscripten - [wasm32-unknown-wasi/clang], [PY_SUPPORT_TIER=3], dnl WebAssembly System Interface [x86_64-*-freebsd*/clang], [PY_SUPPORT_TIER=3], dnl FreeBSD on AMD64 [PY_SUPPORT_TIER=0] ) From 846fd721d518dda88a7d427ec3d2c03c45d9fa90 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Fri, 9 Feb 2024 12:36:12 +0200 Subject: [PATCH 181/507] gh-115059: Flush the underlying write buffer in io.BufferedRandom.read1() (GH-115163) --- Lib/test/test_io.py | 52 +++++++++++++++++++ ...-02-08-13-26-14.gh-issue-115059.DqP9dr.rst | 1 + Modules/_io/bufferedio.c | 10 ++++ 3 files changed, 63 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-02-08-13-26-14.gh-issue-115059.DqP9dr.rst diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 73669ecc7927763..a24579dcc878cfb 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -2497,6 +2497,28 @@ def test_interleaved_read_write(self): f.flush() self.assertEqual(raw.getvalue(), b'a2c') + def test_read1_after_write(self): + with self.BytesIO(b'abcdef') as raw: + with self.tp(raw, 3) as f: + f.write(b"1") + self.assertEqual(f.read1(1), b'b') + f.flush() + self.assertEqual(raw.getvalue(), b'1bcdef') + with self.BytesIO(b'abcdef') as raw: + with self.tp(raw, 3) as f: + f.write(b"1") + self.assertEqual(f.read1(), b'bcd') + f.flush() + self.assertEqual(raw.getvalue(), b'1bcdef') + with self.BytesIO(b'abcdef') as raw: + with self.tp(raw, 3) as f: + f.write(b"1") + # XXX: read(100) returns different numbers of bytes + # in Python and C implementations. + self.assertEqual(f.read1(100)[:3], b'bcd') + f.flush() + self.assertEqual(raw.getvalue(), b'1bcdef') + def test_interleaved_readline_write(self): with self.BytesIO(b'ab\ncdef\ng\n') as raw: with self.tp(raw) as f: @@ -2509,6 +2531,36 @@ def test_interleaved_readline_write(self): f.flush() self.assertEqual(raw.getvalue(), b'1b\n2def\n3\n') + def test_xxx(self): + with self.BytesIO(b'abcdefgh') as raw: + with self.tp(raw) as f: + f.write(b'123') + self.assertEqual(f.read(), b'defgh') + f.write(b'456') + f.flush() + self.assertEqual(raw.getvalue(), b'123defgh456') + with self.BytesIO(b'abcdefgh') as raw: + with self.tp(raw) as f: + f.write(b'123') + self.assertEqual(f.read(3), b'def') + f.write(b'456') + f.flush() + self.assertEqual(raw.getvalue(), b'123def456') + with self.BytesIO(b'abcdefgh') as raw: + with self.tp(raw) as f: + f.write(b'123') + self.assertEqual(f.read1(), b'defgh') + f.write(b'456') + f.flush() + self.assertEqual(raw.getvalue(), b'123defgh456') + with self.BytesIO(b'abcdefgh') as raw: + with self.tp(raw) as f: + f.write(b'123') + self.assertEqual(f.read1(3), b'def') + f.write(b'456') + f.flush() + self.assertEqual(raw.getvalue(), b'123def456') + # You can't construct a BufferedRandom over a non-seekable stream. test_unseekable = None diff --git a/Misc/NEWS.d/next/Library/2024-02-08-13-26-14.gh-issue-115059.DqP9dr.rst b/Misc/NEWS.d/next/Library/2024-02-08-13-26-14.gh-issue-115059.DqP9dr.rst new file mode 100644 index 000000000000000..331baedd3b24c5c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-08-13-26-14.gh-issue-115059.DqP9dr.rst @@ -0,0 +1 @@ +:meth:`io.BufferedRandom.read1` now flushes the underlying write buffer. diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index f02207ace9f3d26..8ebe9ec7095586f 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -1050,6 +1050,16 @@ _io__Buffered_read1_impl(buffered *self, Py_ssize_t n) Py_DECREF(res); return NULL; } + /* Flush the write buffer if necessary */ + if (self->writable) { + PyObject *r = buffered_flush_and_rewind_unlocked(self); + if (r == NULL) { + LEAVE_BUFFERED(self) + Py_DECREF(res); + return NULL; + } + Py_DECREF(r); + } _bufferedreader_reset_buf(self); r = _bufferedreader_raw_read(self, PyBytes_AS_STRING(res), n); LEAVE_BUFFERED(self) From 769d4448260aaec687d9306950225316f9faefce Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Fri, 9 Feb 2024 15:11:36 +0100 Subject: [PATCH 182/507] Docs: correctly link to code objects (#115214) --- Doc/c-api/code.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/code.rst b/Doc/c-api/code.rst index 5082b0cb6ad3f3e..11c12e685fcace4 100644 --- a/Doc/c-api/code.rst +++ b/Doc/c-api/code.rst @@ -22,12 +22,13 @@ bound into a function. .. c:var:: PyTypeObject PyCode_Type This is an instance of :c:type:`PyTypeObject` representing the Python - :class:`code` type. + :ref:`code object <code-objects>`. .. c:function:: int PyCode_Check(PyObject *co) - Return true if *co* is a :class:`code` object. This function always succeeds. + Return true if *co* is a :ref:`code object <code-objects>`. + This function always succeeds. .. c:function:: int PyCode_GetNumFree(PyCodeObject *co) From 31633f4473966b3bcd470440bab7f348711be48f Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Fri, 9 Feb 2024 09:23:12 -0500 Subject: [PATCH 183/507] gh-115184: Fix refleak tracking issues in free-threaded build (#115188) Fixes a few issues related to refleak tracking in the free-threaded build: - Count blocks in abandoned segments - Call `_mi_page_free_collect` earlier during heap traversal in order to get an accurate count of blocks in use. - Add missing refcount tracking in `_Py_DecRefSharedDebug` and `_Py_ExplicitMergeRefcount`. - Pause threads in `get_num_global_allocated_blocks` to ensure that traversing the mimalloc heaps is safe. --- Objects/mimalloc/heap.c | 2 +- Objects/object.c | 11 +++++++---- Objects/obmalloc.c | 9 ++++++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Objects/mimalloc/heap.c b/Objects/mimalloc/heap.c index 164b28f0fab2402..154dad0b1284805 100644 --- a/Objects/mimalloc/heap.c +++ b/Objects/mimalloc/heap.c @@ -538,7 +538,6 @@ bool _mi_heap_area_visit_blocks(const mi_heap_area_t* area, mi_page_t *page, mi_ mi_assert(page != NULL); if (page == NULL) return true; - _mi_page_free_collect(page,true); mi_assert_internal(page->local_free == NULL); if (page->used == 0) return true; @@ -635,6 +634,7 @@ bool _mi_heap_area_visit_blocks(const mi_heap_area_t* area, mi_page_t *page, mi_ typedef bool (mi_heap_area_visit_fun)(const mi_heap_t* heap, const mi_heap_area_ex_t* area, void* arg); void _mi_heap_area_init(mi_heap_area_t* area, mi_page_t* page) { + _mi_page_free_collect(page,true); const size_t bsize = mi_page_block_size(page); const size_t ubsize = mi_page_usable_block_size(page); area->reserved = page->reserved * bsize; diff --git a/Objects/object.c b/Objects/object.c index bbf7f98ae3daf92..37a4b7a417e35fa 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -346,6 +346,9 @@ _Py_DecRefSharedDebug(PyObject *o, const char *filename, int lineno) if (should_queue) { // TODO: the inter-thread queue is not yet implemented. For now, // we just merge the refcount here. +#ifdef Py_REF_DEBUG + _Py_IncRefTotal(_PyInterpreterState_GET()); +#endif Py_ssize_t refcount = _Py_ExplicitMergeRefcount(o, -1); if (refcount == 0) { _Py_Dealloc(o); @@ -399,10 +402,6 @@ _Py_ExplicitMergeRefcount(PyObject *op, Py_ssize_t extra) Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared); do { refcnt = Py_ARITHMETIC_RIGHT_SHIFT(Py_ssize_t, shared, _Py_REF_SHARED_SHIFT); - if (_Py_REF_IS_MERGED(shared)) { - return refcnt; - } - refcnt += (Py_ssize_t)op->ob_ref_local; refcnt += extra; @@ -410,6 +409,10 @@ _Py_ExplicitMergeRefcount(PyObject *op, Py_ssize_t extra) } while (!_Py_atomic_compare_exchange_ssize(&op->ob_ref_shared, &shared, new_shared)); +#ifdef Py_REF_DEBUG + _Py_AddRefTotal(_PyInterpreterState_GET(), extra); +#endif + _Py_atomic_store_uint32_relaxed(&op->ob_ref_local, 0); _Py_atomic_store_uintptr_relaxed(&op->ob_tid, 0); return refcnt; diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index bea4ea85332bdda..6a12c3dca38b36d 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -1073,7 +1073,12 @@ get_mimalloc_allocated_blocks(PyInterpreterState *interp) mi_heap_visit_blocks(heap, false, &count_blocks, &allocated_blocks); } } - // TODO(sgross): count blocks in abandoned segments. + + mi_abandoned_pool_t *pool = &interp->mimalloc.abandoned_pool; + for (uint8_t tag = 0; tag < _Py_MIMALLOC_HEAP_COUNT; tag++) { + _mi_abandoned_pool_visit_blocks(pool, tag, false, &count_blocks, + &allocated_blocks); + } #else // TODO(sgross): this only counts the current thread's blocks. mi_heap_t *heap = mi_heap_get_default(); @@ -1189,6 +1194,7 @@ get_num_global_allocated_blocks(_PyRuntimeState *runtime) } } else { + _PyEval_StopTheWorldAll(&_PyRuntime); HEAD_LOCK(runtime); PyInterpreterState *interp = PyInterpreterState_Head(); assert(interp != NULL); @@ -1208,6 +1214,7 @@ get_num_global_allocated_blocks(_PyRuntimeState *runtime) } } HEAD_UNLOCK(runtime); + _PyEval_StartTheWorldAll(&_PyRuntime); #ifdef Py_DEBUG assert(got_main); #endif From f8931adc597aa696a0f60439e8f9a9047d51ef1c Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Fri, 9 Feb 2024 19:59:41 +0300 Subject: [PATCH 184/507] gh-115142: Skip test_optimizer if _testinternalcapi module is not available (GH-115175) --- Lib/test/test_optimizer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_optimizer.py b/Lib/test/test_optimizer.py index b56bf3cfd9560ea..c8554c40df4b2de 100644 --- a/Lib/test/test_optimizer.py +++ b/Lib/test/test_optimizer.py @@ -1,6 +1,9 @@ -import _testinternalcapi import unittest import types +from test.support import import_helper + + +_testinternalcapi = import_helper.import_module("_testinternalcapi") class TestRareEventCounters(unittest.TestCase): From 5a173efa693a053bf4a059c82c1c06c82a9fa8fb Mon Sep 17 00:00:00 2001 From: Peter Lazorchak <lazorchakp@gmail.com> Date: Fri, 9 Feb 2024 09:06:14 -0800 Subject: [PATCH 185/507] Add Peter L to ACKS (GH-115222) --- Misc/ACKS | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/ACKS b/Misc/ACKS index 466023f390a421d..8a80e02ecba26a8 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1051,6 +1051,7 @@ Mark Lawrence Chris Laws Michael Layzell Michael Lazar +Peter Lazorchak Brian Leair Mathieu Leduc-Hamel Amandine Lee From a225520af941fb125a4ede77a617501dfb8b46da Mon Sep 17 00:00:00 2001 From: Carl Meyer <carl@oddbird.net> Date: Fri, 9 Feb 2024 12:19:09 -0700 Subject: [PATCH 186/507] gh-112903: Handle non-types in _BaseGenericAlias.__mro_entries__() (#115191) Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> --- Lib/test/test_typing.py | 69 +++++++++++++++++++ Lib/typing.py | 22 +++++- ...-02-08-17-04-58.gh-issue-112903.SN_vUs.rst | 2 + 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-08-17-04-58.gh-issue-112903.SN_vUs.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index b684af4f33ed71d..58566c4bfc821c4 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -4920,6 +4920,75 @@ class B(Generic[S]): ... class C(List[int], B): ... self.assertEqual(C.__mro__, (C, list, B, Generic, object)) + def test_multiple_inheritance_non_type_with___mro_entries__(self): + class GoodEntries: + def __mro_entries__(self, bases): + return (object,) + + class A(List[int], GoodEntries()): ... + + self.assertEqual(A.__mro__, (A, list, Generic, object)) + + def test_multiple_inheritance_non_type_without___mro_entries__(self): + # Error should be from the type machinery, not from typing.py + with self.assertRaisesRegex(TypeError, r"^bases must be types"): + class A(List[int], object()): ... + + def test_multiple_inheritance_non_type_bad___mro_entries__(self): + class BadEntries: + def __mro_entries__(self, bases): + return None + + # Error should be from the type machinery, not from typing.py + with self.assertRaisesRegex( + TypeError, + r"^__mro_entries__ must return a tuple", + ): + class A(List[int], BadEntries()): ... + + def test_multiple_inheritance___mro_entries___returns_non_type(self): + class BadEntries: + def __mro_entries__(self, bases): + return (object(),) + + # Error should be from the type machinery, not from typing.py + with self.assertRaisesRegex( + TypeError, + r"^bases must be types", + ): + class A(List[int], BadEntries()): ... + + def test_multiple_inheritance_with_genericalias(self): + class A(typing.Sized, list[int]): ... + + self.assertEqual( + A.__mro__, + (A, collections.abc.Sized, Generic, list, object), + ) + + def test_multiple_inheritance_with_genericalias_2(self): + T = TypeVar("T") + + class BaseSeq(typing.Sequence[T]): ... + class MySeq(List[T], BaseSeq[T]): ... + + self.assertEqual( + MySeq.__mro__, + ( + MySeq, + list, + BaseSeq, + collections.abc.Sequence, + collections.abc.Reversible, + collections.abc.Collection, + collections.abc.Sized, + collections.abc.Iterable, + collections.abc.Container, + Generic, + object, + ), + ) + def test_init_subclass_super_called(self): class FinalException(Exception): pass diff --git a/Lib/typing.py b/Lib/typing.py index d278b4effc7ebaf..347373f00956c77 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1135,9 +1135,29 @@ def __mro_entries__(self, bases): res = [] if self.__origin__ not in bases: res.append(self.__origin__) + + # Check if any base that occurs after us in `bases` is either itself a + # subclass of Generic, or something which will add a subclass of Generic + # to `__bases__` via its `__mro_entries__`. If not, add Generic + # ourselves. The goal is to ensure that Generic (or a subclass) will + # appear exactly once in the final bases tuple. If we let it appear + # multiple times, we risk "can't form a consistent MRO" errors. i = bases.index(self) for b in bases[i+1:]: - if isinstance(b, _BaseGenericAlias) or issubclass(b, Generic): + if isinstance(b, _BaseGenericAlias): + break + if not isinstance(b, type): + meth = getattr(b, "__mro_entries__", None) + new_bases = meth(bases) if meth else None + if ( + isinstance(new_bases, tuple) and + any( + isinstance(b2, type) and issubclass(b2, Generic) + for b2 in new_bases + ) + ): + break + elif issubclass(b, Generic): break else: res.append(Generic) diff --git a/Misc/NEWS.d/next/Library/2024-02-08-17-04-58.gh-issue-112903.SN_vUs.rst b/Misc/NEWS.d/next/Library/2024-02-08-17-04-58.gh-issue-112903.SN_vUs.rst new file mode 100644 index 000000000000000..e27f5832553c136 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-08-17-04-58.gh-issue-112903.SN_vUs.rst @@ -0,0 +1,2 @@ +Fix "issubclass() arg 1 must be a class" errors in certain cases of multiple +inheritance with generic aliases (regression in early 3.13 alpha releases). From a3af3cb4f424034b56404704fdf8f18e8c0a9982 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Fri, 9 Feb 2024 17:08:32 -0500 Subject: [PATCH 187/507] gh-110481: Implement inter-thread queue for biased reference counting (#114824) Biased reference counting maintains two refcount fields in each object: `ob_ref_local` and `ob_ref_shared`. The true refcount is the sum of these two fields. In some cases, when refcounting operations are split across threads, the ob_ref_shared field can be negative (although the total refcount must be at least zero). In this case, the thread that decremented the refcount requests that the owning thread give up ownership and merge the refcount fields. --- Include/internal/pycore_brc.h | 74 +++++++ Include/internal/pycore_ceval.h | 1 + Include/internal/pycore_interp.h | 1 + Include/internal/pycore_object_stack.h | 6 + Include/internal/pycore_tstate.h | 2 + Lib/test/test_code.py | 1 + Lib/test/test_concurrent_futures/executor.py | 17 +- .../test_process_pool.py | 1 + Makefile.pre.in | 2 + Modules/posixmodule.c | 4 + Objects/dictobject.c | 16 +- Objects/object.c | 8 +- PCbuild/_freeze_module.vcxproj | 1 + PCbuild/_freeze_module.vcxproj.filters | 3 + PCbuild/pythoncore.vcxproj | 2 + PCbuild/pythoncore.vcxproj.filters | 6 + Python/brc.c | 198 ++++++++++++++++++ Python/ceval_gil.c | 8 + Python/gc_free_threading.c | 46 +++- Python/object_stack.c | 21 ++ Python/pystate.c | 11 + 21 files changed, 418 insertions(+), 11 deletions(-) create mode 100644 Include/internal/pycore_brc.h create mode 100644 Python/brc.c diff --git a/Include/internal/pycore_brc.h b/Include/internal/pycore_brc.h new file mode 100644 index 000000000000000..3453d83b57ca97e --- /dev/null +++ b/Include/internal/pycore_brc.h @@ -0,0 +1,74 @@ +#ifndef Py_INTERNAL_BRC_H +#define Py_INTERNAL_BRC_H + +#include <stdint.h> +#include "pycore_llist.h" // struct llist_node +#include "pycore_lock.h" // PyMutex +#include "pycore_object_stack.h" // _PyObjectStack + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef Py_BUILD_CORE +# error "this header requires Py_BUILD_CORE define" +#endif + +#ifdef Py_GIL_DISABLED + +// Prime number to avoid correlations with memory addresses. +#define _Py_BRC_NUM_BUCKETS 257 + +// Hash table bucket +struct _brc_bucket { + // Mutex protects both the bucket and thread state queues in this bucket. + PyMutex mutex; + + // Linked list of _PyThreadStateImpl objects hashed to this bucket. + struct llist_node root; +}; + +// Per-interpreter biased reference counting state +struct _brc_state { + // Hash table of thread states by thread-id. Thread states within a bucket + // are chained using a doubly-linked list. + struct _brc_bucket table[_Py_BRC_NUM_BUCKETS]; +}; + +// Per-thread biased reference counting state +struct _brc_thread_state { + // Linked-list of thread states per hash bucket + struct llist_node bucket_node; + + // Thread-id as determined by _PyThread_Id() + uintptr_t tid; + + // Objects with refcounts to be merged (protected by bucket mutex) + _PyObjectStack objects_to_merge; + + // Local stack of objects to be merged (not accessed by other threads) + _PyObjectStack local_objects_to_merge; +}; + +// Initialize/finalize the per-thread biased reference counting state +void _Py_brc_init_thread(PyThreadState *tstate); +void _Py_brc_remove_thread(PyThreadState *tstate); + +// Initialize per-interpreter state +void _Py_brc_init_state(PyInterpreterState *interp); + +void _Py_brc_after_fork(PyInterpreterState *interp); + +// Enqueues an object to be merged by it's owning thread (tid). This +// steals a reference to the object. +void _Py_brc_queue_object(PyObject *ob); + +// Merge the refcounts of queued objects for the current thread. +void _Py_brc_merge_refcounts(PyThreadState *tstate); + +#endif /* Py_GIL_DISABLED */ + +#ifdef __cplusplus +} +#endif +#endif /* !Py_INTERNAL_BRC_H */ diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index a66af1389541dd7..b158fc9ff5ebc1b 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -206,6 +206,7 @@ void _PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame *frame) #define _PY_ASYNC_EXCEPTION_BIT 3 #define _PY_GC_SCHEDULED_BIT 4 #define _PY_EVAL_PLEASE_STOP_BIT 5 +#define _PY_EVAL_EXPLICIT_MERGE_BIT 6 /* Reserve a few bits for future use */ #define _PY_EVAL_EVENTS_BITS 8 diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index f7c332ed747cfac..31d88071e19d0cf 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -201,6 +201,7 @@ struct _is { #if defined(Py_GIL_DISABLED) struct _mimalloc_interp_state mimalloc; + struct _brc_state brc; // biased reference counting state #endif // Per-interpreter state for the obmalloc allocator. For the main diff --git a/Include/internal/pycore_object_stack.h b/Include/internal/pycore_object_stack.h index 1dc1c1591525ded..d042be2a98090a0 100644 --- a/Include/internal/pycore_object_stack.h +++ b/Include/internal/pycore_object_stack.h @@ -1,6 +1,8 @@ #ifndef Py_INTERNAL_OBJECT_STACK_H #define Py_INTERNAL_OBJECT_STACK_H +#include "pycore_freelist.h" // _PyFreeListState + #ifdef __cplusplus extern "C" { #endif @@ -74,6 +76,10 @@ _PyObjectStack_Pop(_PyObjectStack *stack) return obj; } +// Merge src into dst, leaving src empty +extern void +_PyObjectStack_Merge(_PyObjectStack *dst, _PyObjectStack *src); + // Remove all items from the stack extern void _PyObjectStack_Clear(_PyObjectStack *stack); diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h index 472fa08154e8f92..77a1dc59163d212 100644 --- a/Include/internal/pycore_tstate.h +++ b/Include/internal/pycore_tstate.h @@ -10,6 +10,7 @@ extern "C" { #include "pycore_freelist.h" // struct _Py_freelist_state #include "pycore_mimalloc.h" // struct _mimalloc_thread_state +#include "pycore_brc.h" // struct _brc_thread_state // Every PyThreadState is actually allocated as a _PyThreadStateImpl. The @@ -22,6 +23,7 @@ typedef struct _PyThreadStateImpl { #ifdef Py_GIL_DISABLED struct _mimalloc_thread_state mimalloc; struct _Py_freelist_state freelist_state; + struct _brc_thread_state brc; #endif } _PyThreadStateImpl; diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index d8fb826edeb6816..46bebfc7af675b1 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -865,6 +865,7 @@ def __init__(self, f, test): self.test = test def run(self): del self.f + gc_collect() self.test.assertEqual(LAST_FREED, 500) SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(500)) diff --git a/Lib/test/test_concurrent_futures/executor.py b/Lib/test/test_concurrent_futures/executor.py index 1e7d4344740943d..6a79fe69ec37cf4 100644 --- a/Lib/test/test_concurrent_futures/executor.py +++ b/Lib/test/test_concurrent_futures/executor.py @@ -1,8 +1,10 @@ import threading import time +import unittest import weakref from concurrent import futures from test import support +from test.support import Py_GIL_DISABLED def mul(x, y): @@ -83,10 +85,21 @@ def test_no_stale_references(self): my_object_collected = threading.Event() my_object_callback = weakref.ref( my_object, lambda obj: my_object_collected.set()) - # Deliberately discarding the future. - self.executor.submit(my_object.my_method) + fut = self.executor.submit(my_object.my_method) del my_object + if Py_GIL_DISABLED: + # Due to biased reference counting, my_object might only be + # deallocated while the thread that created it runs -- if the + # thread is paused waiting on an event, it may not merge the + # refcount of the queued object. For that reason, we wait for the + # task to finish (so that it's no longer referenced) and force a + # GC to ensure that it is collected. + fut.result() # Wait for the task to finish. + support.gc_collect() + else: + del fut # Deliberately discard the future. + collected = my_object_collected.wait(timeout=support.SHORT_TIMEOUT) self.assertTrue(collected, "Stale reference not collected within timeout.") diff --git a/Lib/test/test_concurrent_futures/test_process_pool.py b/Lib/test/test_concurrent_futures/test_process_pool.py index 3e61b0c9387c6fa..7fc59a05f3deac0 100644 --- a/Lib/test/test_concurrent_futures/test_process_pool.py +++ b/Lib/test/test_concurrent_futures/test_process_pool.py @@ -98,6 +98,7 @@ def test_ressources_gced_in_workers(self): # explicitly destroy the object to ensure that EventfulGCObj.__del__() # is called while manager is still running. + support.gc_collect() obj = None support.gc_collect() diff --git a/Makefile.pre.in b/Makefile.pre.in index 07b2ec7adde78a5..4dabe328ce0362f 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -405,6 +405,7 @@ PYTHON_OBJS= \ Python/ast_opt.o \ Python/ast_unparse.o \ Python/bltinmodule.o \ + Python/brc.o \ Python/ceval.o \ Python/codecs.o \ Python/compile.o \ @@ -1081,6 +1082,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/internal/pycore_atexit.h \ $(srcdir)/Include/internal/pycore_bitutils.h \ $(srcdir)/Include/internal/pycore_blocks_output_buffer.h \ + $(srcdir)/Include/internal/pycore_brc.h \ $(srcdir)/Include/internal/pycore_bytes_methods.h \ $(srcdir)/Include/internal/pycore_bytesobject.h \ $(srcdir)/Include/internal/pycore_call.h \ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index e26265fc874ebb4..230c961a2ac3c04 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -637,6 +637,10 @@ PyOS_AfterFork_Child(void) tstate->native_thread_id = PyThread_get_thread_native_id(); #endif +#ifdef Py_GIL_DISABLED + _Py_brc_after_fork(tstate->interp); +#endif + status = _PyEval_ReInitThreads(tstate); if (_PyStatus_EXCEPTION(status)) { goto fatal_error; diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 2df95e977a180fa..9b1defa5cbc609f 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -5989,6 +5989,18 @@ _PyObject_MakeDictFromInstanceAttributes(PyObject *obj, PyDictValues *values) return make_dict_from_instance_attributes(interp, keys, values); } +static bool +has_unique_reference(PyObject *op) +{ +#ifdef Py_GIL_DISABLED + return (_Py_IsOwnedByCurrentThread(op) && + op->ob_ref_local == 1 && + _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared) == 0); +#else + return Py_REFCNT(op) == 1; +#endif +} + // Return true if the dict was dematerialized, false otherwise. bool _PyObject_MakeInstanceAttributesFromDict(PyObject *obj, PyDictOrValues *dorv) @@ -6005,7 +6017,9 @@ _PyObject_MakeInstanceAttributesFromDict(PyObject *obj, PyDictOrValues *dorv) return false; } assert(_PyType_HasFeature(Py_TYPE(obj), Py_TPFLAGS_HEAPTYPE)); - if (dict->ma_keys != CACHED_KEYS(Py_TYPE(obj)) || Py_REFCNT(dict) != 1) { + if (dict->ma_keys != CACHED_KEYS(Py_TYPE(obj)) || + !has_unique_reference((PyObject *)dict)) + { return false; } assert(dict->ma_values); diff --git a/Objects/object.c b/Objects/object.c index 37a4b7a417e35fa..61e6131c6e99bbb 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2,6 +2,7 @@ /* Generic object operations; and implementation of None */ #include "Python.h" +#include "pycore_brc.h" // _Py_brc_queue_object() #include "pycore_call.h" // _PyObject_CallNoArgs() #include "pycore_ceval.h" // _Py_EnterRecursiveCallTstate() #include "pycore_context.h" // _PyContextTokenMissing_Type @@ -344,15 +345,10 @@ _Py_DecRefSharedDebug(PyObject *o, const char *filename, int lineno) &shared, new_shared)); if (should_queue) { - // TODO: the inter-thread queue is not yet implemented. For now, - // we just merge the refcount here. #ifdef Py_REF_DEBUG _Py_IncRefTotal(_PyInterpreterState_GET()); #endif - Py_ssize_t refcount = _Py_ExplicitMergeRefcount(o, -1); - if (refcount == 0) { - _Py_Dealloc(o); - } + _Py_brc_queue_object(o); } else if (new_shared == _Py_REF_MERGED) { // refcount is zero AND merged diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index 35788ec4503e8f6..49f529ebbc2f9b0 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -191,6 +191,7 @@ <ClCompile Include="..\Python\ast_opt.c" /> <ClCompile Include="..\Python\ast_unparse.c" /> <ClCompile Include="..\Python\bltinmodule.c" /> + <ClCompile Include="..\Python\brc.c" /> <ClCompile Include="..\Python\bootstrap_hash.c" /> <ClCompile Include="..\Python\ceval.c" /> <ClCompile Include="..\Python\codecs.c" /> diff --git a/PCbuild/_freeze_module.vcxproj.filters b/PCbuild/_freeze_module.vcxproj.filters index 7a44179e3561059..5b1bd7552b4cd98 100644 --- a/PCbuild/_freeze_module.vcxproj.filters +++ b/PCbuild/_freeze_module.vcxproj.filters @@ -46,6 +46,9 @@ <ClCompile Include="..\Python\bltinmodule.c"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="..\Python\brc.c"> + <Filter>Python</Filter> + </ClCompile> <ClCompile Include="..\Objects\boolobject.c"> <Filter>Source Files</Filter> </ClCompile> diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index e1ff97659659eea..4cc0ca4b9af8deb 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -206,6 +206,7 @@ <ClInclude Include="..\Include\internal\pycore_ast_state.h" /> <ClInclude Include="..\Include\internal\pycore_atexit.h" /> <ClInclude Include="..\Include\internal\pycore_bitutils.h" /> + <ClInclude Include="..\Include\internal\pycore_brc.h" /> <ClInclude Include="..\Include\internal\pycore_bytes_methods.h" /> <ClInclude Include="..\Include\internal\pycore_bytesobject.h" /> <ClInclude Include="..\Include\internal\pycore_call.h" /> @@ -553,6 +554,7 @@ <ClCompile Include="..\Python\ast_unparse.c" /> <ClCompile Include="..\Python\bltinmodule.c" /> <ClCompile Include="..\Python\bootstrap_hash.c" /> + <ClCompile Include="..\Python\brc.c" /> <ClCompile Include="..\Python\ceval.c" /> <ClCompile Include="..\Python\codecs.c" /> <ClCompile Include="..\Python\compile.c" /> diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 4c55f23006b2f0c..ceaa21217267cf1 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -546,6 +546,9 @@ <ClInclude Include="..\Include\internal\pycore_bitutils.h"> <Filter>Include\internal</Filter> </ClInclude> + <ClInclude Include="..\Include\internal\pycore_brc.h"> + <Filter>Include\internal</Filter> + </ClInclude> <ClInclude Include="..\Include\internal\pycore_bytes_methods.h"> <Filter>Include\internal</Filter> </ClInclude> @@ -1253,6 +1256,9 @@ <ClCompile Include="..\Python\bltinmodule.c"> <Filter>Python</Filter> </ClCompile> + <ClCompile Include="..\Python\brc.c"> + <Filter>Python</Filter> + </ClCompile> <ClCompile Include="..\Python\ceval.c"> <Filter>Python</Filter> </ClCompile> diff --git a/Python/brc.c b/Python/brc.c new file mode 100644 index 000000000000000..f1fd57a2964cf55 --- /dev/null +++ b/Python/brc.c @@ -0,0 +1,198 @@ +// Implementation of biased reference counting inter-thread queue. +// +// Biased reference counting maintains two refcount fields in each object: +// ob_ref_local and ob_ref_shared. The true refcount is the sum of these two +// fields. In some cases, when refcounting operations are split across threads, +// the ob_ref_shared field can be negative (although the total refcount must +// be at least zero). In this case, the thread that decremented the refcount +// requests that the owning thread give up ownership and merge the refcount +// fields. This file implements the mechanism for doing so. +// +// Each thread state maintains a queue of objects whose refcounts it should +// merge. The thread states are stored in a per-interpreter hash table by +// thread id. The hash table has a fixed size and uses a linked list to store +// thread states within each bucket. +// +// The queueing thread uses the eval breaker mechanism to notify the owning +// thread that it has objects to merge. Additionaly, all queued objects are +// merged during GC. +#include "Python.h" +#include "pycore_object.h" // _Py_ExplicitMergeRefcount +#include "pycore_brc.h" // struct _brc_thread_state +#include "pycore_ceval.h" // _Py_set_eval_breaker_bit +#include "pycore_llist.h" // struct llist_node +#include "pycore_pystate.h" // _PyThreadStateImpl + +#ifdef Py_GIL_DISABLED + +// Get the hashtable bucket for a given thread id. +static struct _brc_bucket * +get_bucket(PyInterpreterState *interp, uintptr_t tid) +{ + return &interp->brc.table[tid % _Py_BRC_NUM_BUCKETS]; +} + +// Find the thread state in a hash table bucket by thread id. +static _PyThreadStateImpl * +find_thread_state(struct _brc_bucket *bucket, uintptr_t thread_id) +{ + struct llist_node *node; + llist_for_each(node, &bucket->root) { + // Get the containing _PyThreadStateImpl from the linked-list node. + _PyThreadStateImpl *ts = llist_data(node, _PyThreadStateImpl, + brc.bucket_node); + if (ts->brc.tid == thread_id) { + return ts; + } + } + return NULL; +} + +// Enqueue an object to be merged by the owning thread. This steals a +// reference to the object. +void +_Py_brc_queue_object(PyObject *ob) +{ + PyInterpreterState *interp = _PyInterpreterState_GET(); + + uintptr_t ob_tid = _Py_atomic_load_uintptr(&ob->ob_tid); + if (ob_tid == 0) { + // The owning thread may have concurrently decided to merge the + // refcount fields. + Py_DECREF(ob); + return; + } + + struct _brc_bucket *bucket = get_bucket(interp, ob_tid); + PyMutex_Lock(&bucket->mutex); + _PyThreadStateImpl *tstate = find_thread_state(bucket, ob_tid); + if (tstate == NULL) { + // If we didn't find the owning thread then it must have already exited. + // It's safe (and necessary) to merge the refcount. Subtract one when + // merging because we've stolen a reference. + Py_ssize_t refcount = _Py_ExplicitMergeRefcount(ob, -1); + PyMutex_Unlock(&bucket->mutex); + if (refcount == 0) { + _Py_Dealloc(ob); + } + return; + } + + if (_PyObjectStack_Push(&tstate->brc.objects_to_merge, ob) < 0) { + PyMutex_Unlock(&bucket->mutex); + + // Fall back to stopping all threads and manually merging the refcount + // if we can't enqueue the object to be merged. + _PyEval_StopTheWorld(interp); + Py_ssize_t refcount = _Py_ExplicitMergeRefcount(ob, -1); + _PyEval_StartTheWorld(interp); + + if (refcount == 0) { + _Py_Dealloc(ob); + } + return; + } + + // Notify owning thread + _Py_set_eval_breaker_bit(interp, _PY_EVAL_EXPLICIT_MERGE_BIT, 1); + + PyMutex_Unlock(&bucket->mutex); +} + +static void +merge_queued_objects(_PyObjectStack *to_merge) +{ + PyObject *ob; + while ((ob = _PyObjectStack_Pop(to_merge)) != NULL) { + // Subtract one when merging because the queue had a reference. + Py_ssize_t refcount = _Py_ExplicitMergeRefcount(ob, -1); + if (refcount == 0) { + _Py_Dealloc(ob); + } + } +} + +// Process this thread's queue of objects to merge. +void +_Py_brc_merge_refcounts(PyThreadState *tstate) +{ + struct _brc_thread_state *brc = &((_PyThreadStateImpl *)tstate)->brc; + struct _brc_bucket *bucket = get_bucket(tstate->interp, brc->tid); + + // Append all objects into a local stack. We don't want to hold the lock + // while calling destructors. + PyMutex_Lock(&bucket->mutex); + _PyObjectStack_Merge(&brc->local_objects_to_merge, &brc->objects_to_merge); + PyMutex_Unlock(&bucket->mutex); + + // Process the local stack until it's empty + merge_queued_objects(&brc->local_objects_to_merge); +} + +void +_Py_brc_init_state(PyInterpreterState *interp) +{ + struct _brc_state *brc = &interp->brc; + for (Py_ssize_t i = 0; i < _Py_BRC_NUM_BUCKETS; i++) { + llist_init(&brc->table[i].root); + } +} + +void +_Py_brc_init_thread(PyThreadState *tstate) +{ + struct _brc_thread_state *brc = &((_PyThreadStateImpl *)tstate)->brc; + brc->tid = _Py_ThreadId(); + + // Add ourself to the hashtable + struct _brc_bucket *bucket = get_bucket(tstate->interp, brc->tid); + PyMutex_Lock(&bucket->mutex); + llist_insert_tail(&bucket->root, &brc->bucket_node); + PyMutex_Unlock(&bucket->mutex); +} + +void +_Py_brc_remove_thread(PyThreadState *tstate) +{ + struct _brc_thread_state *brc = &((_PyThreadStateImpl *)tstate)->brc; + struct _brc_bucket *bucket = get_bucket(tstate->interp, brc->tid); + + // We need to fully process any objects to merge before removing ourself + // from the hashtable. It is not safe to perform any refcount operations + // after we are removed. After that point, other threads treat our objects + // as abandoned and may merge the objects' refcounts directly. + bool empty = false; + while (!empty) { + // Process the local stack until it's empty + merge_queued_objects(&brc->local_objects_to_merge); + + PyMutex_Lock(&bucket->mutex); + empty = (brc->objects_to_merge.head == NULL); + if (empty) { + llist_remove(&brc->bucket_node); + } + else { + _PyObjectStack_Merge(&brc->local_objects_to_merge, + &brc->objects_to_merge); + } + PyMutex_Unlock(&bucket->mutex); + } + + assert(brc->local_objects_to_merge.head == NULL); + assert(brc->objects_to_merge.head == NULL); +} + +void +_Py_brc_after_fork(PyInterpreterState *interp) +{ + // Unlock all bucket mutexes. Some of the buckets may be locked because + // locks can be handed off to a parked thread (see lock.c). We don't have + // to worry about consistency here, becuase no thread can be actively + // modifying a bucket, but it might be paused (not yet woken up) on a + // PyMutex_Lock while holding that lock. + for (Py_ssize_t i = 0; i < _Py_BRC_NUM_BUCKETS; i++) { + _PyMutex_at_fork_reinit(&interp->brc.table[i].mutex); + } +} + +#endif /* Py_GIL_DISABLED */ diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c index ad90359318761a5..deb9741291fca73 100644 --- a/Python/ceval_gil.c +++ b/Python/ceval_gil.c @@ -980,6 +980,14 @@ _Py_HandlePending(PyThreadState *tstate) } } +#ifdef Py_GIL_DISABLED + /* Objects with refcounts to merge */ + if (_Py_eval_breaker_bit_is_set(interp, _PY_EVAL_EXPLICIT_MERGE_BIT)) { + _Py_set_eval_breaker_bit(interp, _PY_EVAL_EXPLICIT_MERGE_BIT, 0); + _Py_brc_merge_refcounts(tstate); + } +#endif + /* GC scheduled to run */ if (_Py_eval_breaker_bit_is_set(interp, _PY_GC_SCHEDULED_BIT)) { _Py_set_eval_breaker_bit(interp, _PY_GC_SCHEDULED_BIT, 0); diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 8fbcdb15109b76c..5d3b097dee93e87 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -1,5 +1,6 @@ // Cyclic garbage collector implementation for free-threaded build. #include "Python.h" +#include "pycore_brc.h" // struct _brc_thread_state #include "pycore_ceval.h" // _Py_set_eval_breaker_bit() #include "pycore_context.h" #include "pycore_dict.h" // _PyDict_MaybeUntrack() @@ -152,8 +153,7 @@ gc_decref(PyObject *op) op->ob_tid -= 1; } -// Merge refcounts while the world is stopped. -static void +static Py_ssize_t merge_refcount(PyObject *op, Py_ssize_t extra) { assert(_PyInterpreterState_GET()->stoptheworld.world_stopped); @@ -169,6 +169,7 @@ merge_refcount(PyObject *op, Py_ssize_t extra) op->ob_tid = 0; op->ob_ref_local = 0; op->ob_ref_shared = _Py_REF_SHARED(refcount, _Py_REF_MERGED); + return refcount; } static void @@ -282,6 +283,41 @@ gc_visit_heaps(PyInterpreterState *interp, mi_block_visit_fun *visitor, return err; } +static void +merge_queued_objects(_PyThreadStateImpl *tstate, struct collection_state *state) +{ + struct _brc_thread_state *brc = &tstate->brc; + _PyObjectStack_Merge(&brc->local_objects_to_merge, &brc->objects_to_merge); + + PyObject *op; + while ((op = _PyObjectStack_Pop(&brc->local_objects_to_merge)) != NULL) { + // Subtract one when merging because the queue had a reference. + Py_ssize_t refcount = merge_refcount(op, -1); + + if (!_PyObject_GC_IS_TRACKED(op) && refcount == 0) { + // 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 + // decref and deallocate the object once we start the world again. + op->ob_ref_shared += (1 << _Py_REF_SHARED_SHIFT); +#ifdef Py_REF_DEBUG + _Py_IncRefTotal(_PyInterpreterState_GET()); +#endif + worklist_push(&state->objs_to_decref, op); + } + } +} + +static void +merge_all_queued_objects(PyInterpreterState *interp, struct collection_state *state) +{ + HEAD_LOCK(&_PyRuntime); + for (PyThreadState *p = interp->threads.head; p != NULL; p = p->next) { + merge_queued_objects((_PyThreadStateImpl *)p, state); + } + HEAD_UNLOCK(&_PyRuntime); +} + // Subtract an incoming reference from the computed "gc_refs" refcount. static int visit_decref(PyObject *op, void *arg) @@ -927,6 +963,9 @@ static void gc_collect_internal(PyInterpreterState *interp, struct collection_state *state) { _PyEval_StopTheWorld(interp); + // merge refcounts for all queued objects + merge_all_queued_objects(interp, state); + // Find unreachable objects int err = deduce_unreachable_heap(interp, state); if (err < 0) { @@ -946,6 +985,9 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state) clear_weakrefs(state); _PyEval_StartTheWorld(interp); + // Deallocate any object from the refcount merge step + cleanup_worklist(&state->objs_to_decref); + // Call weakref callbacks and finalizers after unpausing other threads to // avoid potential deadlocks. call_weakref_callbacks(state); diff --git a/Python/object_stack.c b/Python/object_stack.c index 8544892eb71dcb6..ced4460da00f442 100644 --- a/Python/object_stack.c +++ b/Python/object_stack.c @@ -67,6 +67,27 @@ _PyObjectStack_Clear(_PyObjectStack *queue) } } +void +_PyObjectStack_Merge(_PyObjectStack *dst, _PyObjectStack *src) +{ + if (src->head == NULL) { + return; + } + + if (dst->head != NULL) { + // First, append dst to the bottom of src + _PyObjectStackChunk *last = src->head; + while (last->prev != NULL) { + last = last->prev; + } + last->prev = dst->head; + } + + // Now that src has all the chunks, set dst to src + dst->head = src->head; + src->head = NULL; +} + void _PyObjectStackChunk_ClearFreeList(_PyFreeListState *free_lists, int is_finalization) { diff --git a/Python/pystate.c b/Python/pystate.c index e77e5bfa7e2df86..6cd034743ddf4c3 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -611,6 +611,9 @@ init_interpreter(PyInterpreterState *interp, _PyGC_InitState(&interp->gc); PyConfig_InitPythonConfig(&interp->config); _PyType_InitCache(interp); +#ifdef Py_GIL_DISABLED + _Py_brc_init_state(interp); +#endif for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) { interp->monitors.tools[i] = 0; } @@ -1336,6 +1339,11 @@ init_threadstate(_PyThreadStateImpl *_tstate, tstate->datastack_limit = NULL; tstate->what_event = -1; +#ifdef Py_GIL_DISABLED + // Initialize biased reference counting inter-thread queue + _Py_brc_init_thread(tstate); +#endif + if (interp->stoptheworld.requested || _PyRuntime.stoptheworld.requested) { // Start in the suspended state if there is an ongoing stop-the-world. tstate->state = _Py_THREAD_SUSPENDED; @@ -1561,6 +1569,9 @@ PyThreadState_Clear(PyThreadState *tstate) _PyFreeListState *freelist_state = &((_PyThreadStateImpl*)tstate)->freelist_state; _Py_ClearFreeLists(freelist_state, 1); _PySlice_ClearCache(freelist_state); + + // Remove ourself from the biased reference counting table of threads. + _Py_brc_remove_thread(tstate); #endif _PyThreadState_ClearMimallocHeaps(tstate); From 564385612cdf72c2fa8e629a68225fb2cd3b3d99 Mon Sep 17 00:00:00 2001 From: dave-shawley <daveshawley@gmail.com> Date: Fri, 9 Feb 2024 17:11:37 -0500 Subject: [PATCH 188/507] gh-115165: Fix `typing.Annotated` for immutable types (#115213) The return value from an annotated callable can raise any exception from __setattr__ for the `__orig_class__` property. --- Lib/test/test_typing.py | 21 +++++++++++++++++++ Lib/typing.py | 4 +++- ...-02-09-07-20-16.gh-issue-115165.yfJLXA.rst | 4 ++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 58566c4bfc821c4..c3a092f3af30097 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -4323,6 +4323,16 @@ class C(B[int]): c.bar = 'abc' self.assertEqual(c.__dict__, {'bar': 'abc'}) + def test_setattr_exceptions(self): + class Immutable[T]: + def __setattr__(self, key, value): + raise RuntimeError("immutable") + + # gh-115165: This used to cause RuntimeError to be raised + # when we tried to set `__orig_class__` on the `Immutable` instance + # returned by the `Immutable[int]()` call + self.assertIsInstance(Immutable[int](), Immutable) + def test_subscripted_generics_as_proxies(self): T = TypeVar('T') class C(Generic[T]): @@ -8561,6 +8571,17 @@ def test_instantiate_generic(self): self.assertEqual(MyCount([4, 4, 5]), {4: 2, 5: 1}) self.assertEqual(MyCount[int]([4, 4, 5]), {4: 2, 5: 1}) + def test_instantiate_immutable(self): + class C: + def __setattr__(self, key, value): + raise Exception("should be ignored") + + A = Annotated[C, "a decoration"] + # gh-115165: This used to cause RuntimeError to be raised + # when we tried to set `__orig_class__` on the `C` instance + # returned by the `A()` call + self.assertIsInstance(A(), C) + def test_cannot_instantiate_forward(self): A = Annotated["int", (5, 6)] with self.assertRaises(TypeError): diff --git a/Lib/typing.py b/Lib/typing.py index 347373f00956c77..914ddeaf504cd0e 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1127,7 +1127,9 @@ def __call__(self, *args, **kwargs): result = self.__origin__(*args, **kwargs) try: result.__orig_class__ = self - except AttributeError: + # Some objects raise TypeError (or something even more exotic) + # if you try to set attributes on them; we guard against that here + except Exception: pass return result diff --git a/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst b/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst new file mode 100644 index 000000000000000..73d3d001f07f3f4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst @@ -0,0 +1,4 @@ +Most exceptions are now ignored when attempting to set the ``__orig_class__`` +attribute on objects returned when calling :mod:`typing` generic aliases +(including generic aliases created using :data:`typing.Annotated`). +Previously only :exc:`AttributeError`` was ignored. Patch by Dave Shawley. From d4d5bae1471788b345155e8e93a2fe4ab92d09dc Mon Sep 17 00:00:00 2001 From: Donghee Na <donghee.na@python.org> Date: Sat, 10 Feb 2024 09:57:04 +0900 Subject: [PATCH 189/507] gh-111968: Refactor _PyXXX_Fini to integrate with _PyObject_ClearFreeLists (gh-114899) --- Include/internal/pycore_context.h | 1 - Include/internal/pycore_floatobject.h | 1 - Include/internal/pycore_freelist.h | 10 ++++++++++ Include/internal/pycore_gc.h | 8 -------- Include/internal/pycore_genobject.h | 4 ---- Include/internal/pycore_list.h | 6 ------ Include/internal/pycore_object_stack.h | 3 --- Include/internal/pycore_sliceobject.h | 2 -- Include/internal/pycore_tuple.h | 1 - Objects/floatobject.c | 10 ---------- Objects/genobject.c | 11 ----------- Objects/listobject.c | 10 ---------- Objects/object.c | 15 +++++++++++++++ Objects/sliceobject.c | 12 ++++-------- Objects/tupleobject.c | 5 ----- Python/context.c | 11 ----------- Python/gc_free_threading.c | 2 +- Python/gc_gil.c | 2 +- Python/pylifecycle.c | 12 +++++------- Python/pystate.c | 19 ++----------------- 20 files changed, 38 insertions(+), 107 deletions(-) diff --git a/Include/internal/pycore_context.h b/Include/internal/pycore_context.h index 3284efba2b6f4cb..ae5c47f195eb7f0 100644 --- a/Include/internal/pycore_context.h +++ b/Include/internal/pycore_context.h @@ -14,7 +14,6 @@ extern PyTypeObject _PyContextTokenMissing_Type; /* runtime lifecycle */ PyStatus _PyContext_Init(PyInterpreterState *); -void _PyContext_Fini(_PyFreeListState *); /* other API */ diff --git a/Include/internal/pycore_floatobject.h b/Include/internal/pycore_floatobject.h index 038578e1f9680a6..3767df5506d43fb 100644 --- a/Include/internal/pycore_floatobject.h +++ b/Include/internal/pycore_floatobject.h @@ -15,7 +15,6 @@ extern "C" { extern void _PyFloat_InitState(PyInterpreterState *); extern PyStatus _PyFloat_InitTypes(PyInterpreterState *); -extern void _PyFloat_Fini(_PyFreeListState *); extern void _PyFloat_FiniType(PyInterpreterState *); diff --git a/Include/internal/pycore_freelist.h b/Include/internal/pycore_freelist.h index 82a42300991eccd..1bc551914794f0c 100644 --- a/Include/internal/pycore_freelist.h +++ b/Include/internal/pycore_freelist.h @@ -125,6 +125,16 @@ typedef struct _Py_freelist_state { struct _Py_object_stack_state object_stacks; } _PyFreeListState; +extern void _PyObject_ClearFreeLists(_PyFreeListState *state, int is_finalization); +extern void _PyTuple_ClearFreeList(_PyFreeListState *state, int is_finalization); +extern void _PyFloat_ClearFreeList(_PyFreeListState *state, int is_finalization); +extern void _PyList_ClearFreeList(_PyFreeListState *state, int is_finalization); +extern void _PySlice_ClearFreeList(_PyFreeListState *state, int is_finalization); +extern void _PyDict_ClearFreeList(_PyFreeListState *state, int is_finalization); +extern void _PyAsyncGen_ClearFreeLists(_PyFreeListState *state, int is_finalization); +extern void _PyContext_ClearFreeList(_PyFreeListState *state, int is_finalization); +extern void _PyObjectStackChunk_ClearFreeList(_PyFreeListState *state, int is_finalization); + #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h index 8d0bc2a218e48de..582a16bf5218cef 100644 --- a/Include/internal/pycore_gc.h +++ b/Include/internal/pycore_gc.h @@ -279,14 +279,6 @@ extern PyObject *_PyGC_GetReferrers(PyInterpreterState *interp, PyObject *objs); // Functions to clear types free lists extern void _PyGC_ClearAllFreeLists(PyInterpreterState *interp); -extern void _Py_ClearFreeLists(_PyFreeListState *state, int is_finalization); -extern void _PyTuple_ClearFreeList(_PyFreeListState *state, int is_finalization); -extern void _PyFloat_ClearFreeList(_PyFreeListState *state, int is_finalization); -extern void _PyList_ClearFreeList(_PyFreeListState *state, int is_finalization); -extern void _PySlice_ClearCache(_PyFreeListState *state); -extern void _PyDict_ClearFreeList(_PyFreeListState *state, int is_finalization); -extern void _PyAsyncGen_ClearFreeLists(_PyFreeListState *state, int is_finalization); -extern void _PyContext_ClearFreeList(_PyFreeListState *state, int is_finalization); extern void _Py_ScheduleGC(PyInterpreterState *interp); extern void _Py_RunGC(PyThreadState *tstate); diff --git a/Include/internal/pycore_genobject.h b/Include/internal/pycore_genobject.h index 5ad63658051e86d..b2aa017598409f7 100644 --- a/Include/internal/pycore_genobject.h +++ b/Include/internal/pycore_genobject.h @@ -26,10 +26,6 @@ extern PyTypeObject _PyCoroWrapper_Type; extern PyTypeObject _PyAsyncGenWrappedValue_Type; extern PyTypeObject _PyAsyncGenAThrow_Type; -/* runtime lifecycle */ - -extern void _PyAsyncGen_Fini(_PyFreeListState *); - #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_list.h b/Include/internal/pycore_list.h index 4536f90e4144939..50dc13c4da4487c 100644 --- a/Include/internal/pycore_list.h +++ b/Include/internal/pycore_list.h @@ -13,12 +13,6 @@ extern "C" { extern PyObject* _PyList_Extend(PyListObject *, PyObject *); extern void _PyList_DebugMallocStats(FILE *out); - -/* runtime lifecycle */ - -extern void _PyList_Fini(_PyFreeListState *); - - #define _PyList_ITEMS(op) _Py_RVALUE(_PyList_CAST(op)->ob_item) extern int diff --git a/Include/internal/pycore_object_stack.h b/Include/internal/pycore_object_stack.h index d042be2a98090a0..fc130b1e9920b41 100644 --- a/Include/internal/pycore_object_stack.h +++ b/Include/internal/pycore_object_stack.h @@ -34,9 +34,6 @@ _PyObjectStackChunk_New(void); extern void _PyObjectStackChunk_Free(_PyObjectStackChunk *); -extern void -_PyObjectStackChunk_ClearFreeList(_PyFreeListState *state, int is_finalization); - // Push an item onto the stack. Return -1 on allocation failure, 0 on success. static inline int _PyObjectStack_Push(_PyObjectStack *stack, PyObject *obj) diff --git a/Include/internal/pycore_sliceobject.h b/Include/internal/pycore_sliceobject.h index 0c72d3ee6225c51..89086f67683a2f5 100644 --- a/Include/internal/pycore_sliceobject.h +++ b/Include/internal/pycore_sliceobject.h @@ -11,8 +11,6 @@ extern "C" { /* runtime lifecycle */ -extern void _PySlice_Fini(_PyFreeListState *); - extern PyObject * _PyBuildSlice_ConsumeRefs(PyObject *start, PyObject *stop); diff --git a/Include/internal/pycore_tuple.h b/Include/internal/pycore_tuple.h index b348339a505b0f7..4605f355ccbc38f 100644 --- a/Include/internal/pycore_tuple.h +++ b/Include/internal/pycore_tuple.h @@ -14,7 +14,6 @@ extern void _PyTuple_DebugMallocStats(FILE *out); /* runtime lifecycle */ extern PyStatus _PyTuple_InitGlobalObjects(PyInterpreterState *); -extern void _PyTuple_Fini(_PyFreeListState *); /* other API */ diff --git a/Objects/floatobject.c b/Objects/floatobject.c index c440e0dab0e79fa..9b322c52d4daea6 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -2010,16 +2010,6 @@ _PyFloat_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) #endif } -void -_PyFloat_Fini(_PyFreeListState *state) -{ - // With Py_GIL_DISABLED: - // the freelists for the current thread state have already been cleared. -#ifndef Py_GIL_DISABLED - _PyFloat_ClearFreeList(state, 1); -#endif -} - void _PyFloat_FiniType(PyInterpreterState *interp) { diff --git a/Objects/genobject.c b/Objects/genobject.c index ab523e46cceaa31..59ab7abf6180bd1 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -1682,17 +1682,6 @@ _PyAsyncGen_ClearFreeLists(_PyFreeListState *freelist_state, int is_finalization #endif } -void -_PyAsyncGen_Fini(_PyFreeListState *state) -{ - // With Py_GIL_DISABLED: - // the freelists for the current thread state have already been cleared. -#ifndef Py_GIL_DISABLED - _PyAsyncGen_ClearFreeLists(state, 1); -#endif -} - - static PyObject * async_gen_unwrap_value(PyAsyncGenObject *gen, PyObject *result) { diff --git a/Objects/listobject.c b/Objects/listobject.c index 307b8f1bd76cac8..7fdb91eab890b5f 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -135,16 +135,6 @@ _PyList_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) #endif } -void -_PyList_Fini(_PyFreeListState *state) -{ - // With Py_GIL_DISABLED: - // the freelists for the current thread state have already been cleared. -#ifndef Py_GIL_DISABLED - _PyList_ClearFreeList(state, 1); -#endif -} - /* Print summary info about the state of the optimized allocator */ void _PyList_DebugMallocStats(FILE *out) diff --git a/Objects/object.c b/Objects/object.c index 61e6131c6e99bbb..275aa6713c8c217 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -793,6 +793,21 @@ PyObject_Bytes(PyObject *v) return PyBytes_FromObject(v); } +void +_PyObject_ClearFreeLists(_PyFreeListState *state, int is_finalization) +{ + // In the free-threaded build, freelists are per-PyThreadState and cleared in PyThreadState_Clear() + // In the default build, freelists are per-interpreter and cleared in finalize_interp_types() + _PyFloat_ClearFreeList(state, is_finalization); + _PyTuple_ClearFreeList(state, is_finalization); + _PyList_ClearFreeList(state, is_finalization); + _PyDict_ClearFreeList(state, is_finalization); + _PyContext_ClearFreeList(state, is_finalization); + _PyAsyncGen_ClearFreeLists(state, is_finalization); + // Only be cleared if is_finalization is true. + _PyObjectStackChunk_ClearFreeList(state, is_finalization); + _PySlice_ClearFreeList(state, is_finalization); +} /* def _PyObject_FunctionStr(x): diff --git a/Objects/sliceobject.c b/Objects/sliceobject.c index 8b9d6bbfd858b75..9880c123c80f95e 100644 --- a/Objects/sliceobject.c +++ b/Objects/sliceobject.c @@ -103,8 +103,11 @@ PyObject _Py_EllipsisObject = _PyObject_HEAD_INIT(&PyEllipsis_Type); /* Slice object implementation */ -void _PySlice_ClearCache(_PyFreeListState *state) +void _PySlice_ClearFreeList(_PyFreeListState *state, int is_finalization) { + if (!is_finalization) { + return; + } #ifdef WITH_FREELISTS PySliceObject *obj = state->slices.slice_cache; if (obj != NULL) { @@ -114,13 +117,6 @@ void _PySlice_ClearCache(_PyFreeListState *state) #endif } -void _PySlice_Fini(_PyFreeListState *state) -{ -#ifdef WITH_FREELISTS - _PySlice_ClearCache(state); -#endif -} - /* start, stop, and step are python objects with None indicating no index is present. */ diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index b9bf6cd48f61292..7d73c3fb0f7f2cc 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -964,11 +964,6 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize) static void maybe_freelist_clear(_PyFreeListState *, int); -void -_PyTuple_Fini(_PyFreeListState *state) -{ - maybe_freelist_clear(state, 1); -} void _PyTuple_ClearFreeList(_PyFreeListState *state, int is_finalization) diff --git a/Python/context.c b/Python/context.c index 793dfa2b72c7e3f..e44fef705c36e0e 100644 --- a/Python/context.c +++ b/Python/context.c @@ -1284,17 +1284,6 @@ _PyContext_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) } -void -_PyContext_Fini(_PyFreeListState *state) -{ - // With Py_GIL_DISABLED: - // the freelists for the current thread state have already been cleared. -#ifndef Py_GIL_DISABLED - _PyContext_ClearFreeList(state, 1); -#endif -} - - PyStatus _PyContext_Init(PyInterpreterState *interp) { diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 5d3b097dee93e87..93e1168002b6f7d 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -1721,7 +1721,7 @@ _PyGC_ClearAllFreeLists(PyInterpreterState *interp) HEAD_LOCK(&_PyRuntime); _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)interp->threads.head; while (tstate != NULL) { - _Py_ClearFreeLists(&tstate->freelist_state, 0); + _PyObject_ClearFreeLists(&tstate->freelist_state, 0); tstate = (_PyThreadStateImpl *)tstate->base.next; } HEAD_UNLOCK(&_PyRuntime); diff --git a/Python/gc_gil.c b/Python/gc_gil.c index 4e2aa8f7af746c2..5f1365f509deb0a 100644 --- a/Python/gc_gil.c +++ b/Python/gc_gil.c @@ -11,7 +11,7 @@ void _PyGC_ClearAllFreeLists(PyInterpreterState *interp) { - _Py_ClearFreeLists(&interp->freelist_state, 0); + _PyObject_ClearFreeLists(&interp->freelist_state, 0); } #endif diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 0cac7109340129c..61c9d4f9ea95754 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1790,16 +1790,14 @@ finalize_interp_types(PyInterpreterState *interp) // a dict internally. _PyUnicode_ClearInterned(interp); - _PyDict_Fini(interp); _PyUnicode_Fini(interp); +#ifndef Py_GIL_DISABLED + // With Py_GIL_DISABLED: + // the freelists for the current thread state have already been cleared. _PyFreeListState *state = _PyFreeListState_GET(); - _PyTuple_Fini(state); - _PyList_Fini(state); - _PyFloat_Fini(state); - _PySlice_Fini(state); - _PyContext_Fini(state); - _PyAsyncGen_Fini(state); + _PyObject_ClearFreeLists(state, 1); +#endif #ifdef Py_DEBUG _PyStaticObjects_CheckRefcnt(interp); diff --git a/Python/pystate.c b/Python/pystate.c index 6cd034743ddf4c3..937c43033b068d4 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1468,20 +1468,6 @@ clear_datastack(PyThreadState *tstate) } } -void -_Py_ClearFreeLists(_PyFreeListState *state, int is_finalization) -{ - // In the free-threaded build, freelists are per-PyThreadState and cleared in PyThreadState_Clear() - // In the default build, freelists are per-interpreter and cleared in finalize_interp_types() - _PyFloat_ClearFreeList(state, is_finalization); - _PyTuple_ClearFreeList(state, is_finalization); - _PyList_ClearFreeList(state, is_finalization); - _PyDict_ClearFreeList(state, is_finalization); - _PyContext_ClearFreeList(state, is_finalization); - _PyAsyncGen_ClearFreeLists(state, is_finalization); - _PyObjectStackChunk_ClearFreeList(state, is_finalization); -} - void PyThreadState_Clear(PyThreadState *tstate) { @@ -1566,9 +1552,8 @@ PyThreadState_Clear(PyThreadState *tstate) } #ifdef Py_GIL_DISABLED // Each thread should clear own freelists in free-threading builds. - _PyFreeListState *freelist_state = &((_PyThreadStateImpl*)tstate)->freelist_state; - _Py_ClearFreeLists(freelist_state, 1); - _PySlice_ClearCache(freelist_state); + _PyFreeListState *freelist_state = _PyFreeListState_GET(); + _PyObject_ClearFreeLists(freelist_state, 1); // Remove ourself from the biased reference counting table of threads. _Py_brc_remove_thread(tstate); From b2d9d134dcb5633deebebf2b0118cd4f7ca598a2 Mon Sep 17 00:00:00 2001 From: Laurie O <laurie_opperman@hotmail.com> Date: Sat, 10 Feb 2024 14:58:30 +1000 Subject: [PATCH 190/507] gh-96471: Add shutdown() method to queue.Queue (#104750) Co-authored-by: Duprat <yduprat@gmail.com> --- Doc/library/queue.rst | 38 ++ Doc/whatsnew/3.13.rst | 7 + Lib/queue.py | 50 +++ Lib/test/test_queue.py | 378 ++++++++++++++++++ ...3-05-06-04-57-10.gh-issue-96471.C9wAU7.rst | 1 + 5 files changed, 474 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-05-06-04-57-10.gh-issue-96471.C9wAU7.rst diff --git a/Doc/library/queue.rst b/Doc/library/queue.rst index b2b787c5a8260cf..1421fc2e552f0e3 100644 --- a/Doc/library/queue.rst +++ b/Doc/library/queue.rst @@ -93,6 +93,14 @@ The :mod:`queue` module defines the following classes and exceptions: on a :class:`Queue` object which is full. +.. exception:: ShutDown + + Exception raised when :meth:`~Queue.put` or :meth:`~Queue.get` is called on + a :class:`Queue` object which has been shut down. + + .. versionadded:: 3.13 + + .. _queueobjects: Queue Objects @@ -135,6 +143,8 @@ provide the public methods described below. immediately available, else raise the :exc:`Full` exception (*timeout* is ignored in that case). + Raises :exc:`ShutDown` if the queue has been shut down. + .. method:: Queue.put_nowait(item) @@ -155,6 +165,9 @@ provide the public methods described below. an uninterruptible wait on an underlying lock. This means that no exceptions can occur, and in particular a SIGINT will not trigger a :exc:`KeyboardInterrupt`. + Raises :exc:`ShutDown` if the queue has been shut down and is empty, or if + the queue has been shut down immediately. + .. method:: Queue.get_nowait() @@ -177,6 +190,8 @@ fully processed by daemon consumer threads. Raises a :exc:`ValueError` if called more times than there were items placed in the queue. + Raises :exc:`ShutDown` if the queue has been shut down immediately. + .. method:: Queue.join() @@ -187,6 +202,8 @@ fully processed by daemon consumer threads. indicate that the item was retrieved and all work on it is complete. When the count of unfinished tasks drops to zero, :meth:`join` unblocks. + Raises :exc:`ShutDown` if the queue has been shut down immediately. + Example of how to wait for enqueued tasks to be completed:: @@ -214,6 +231,27 @@ Example of how to wait for enqueued tasks to be completed:: print('All work completed') +Terminating queues +^^^^^^^^^^^^^^^^^^ + +:class:`Queue` objects can be made to prevent further interaction by shutting +them down. + +.. method:: Queue.shutdown(immediate=False) + + Shut down the queue, making :meth:`~Queue.get` and :meth:`~Queue.put` raise + :exc:`ShutDown`. + + By default, :meth:`~Queue.get` on a shut down queue will only raise once the + queue is empty. Set *immediate* to true to make :meth:`~Queue.get` raise + immediately instead. + + All blocked callers of :meth:`~Queue.put` will be unblocked. If *immediate* + is true, also unblock callers of :meth:`~Queue.get` and :meth:`~Queue.join`. + + .. versionadded:: 3.13 + + SimpleQueue Objects ------------------- diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index b05e4badc9e58bf..de79bd979aff80e 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -403,6 +403,13 @@ pdb command line option or :envvar:`PYTHONSAFEPATH` environment variable). (Contributed by Tian Gao and Christian Walther in :gh:`111762`.) +queue +----- + +* Add :meth:`queue.Queue.shutdown` (along with :exc:`queue.ShutDown`) for queue + termination. + (Contributed by Laurie Opperman and Yves Duprat in :gh:`104750`.) + re -- * Rename :exc:`!re.error` to :exc:`re.PatternError` for improved clarity. diff --git a/Lib/queue.py b/Lib/queue.py index 55f50088460f9e5..467ff4fcecb1340 100644 --- a/Lib/queue.py +++ b/Lib/queue.py @@ -25,6 +25,10 @@ class Full(Exception): pass +class ShutDown(Exception): + '''Raised when put/get with shut-down queue.''' + + class Queue: '''Create a queue object with a given maximum size. @@ -54,6 +58,9 @@ def __init__(self, maxsize=0): self.all_tasks_done = threading.Condition(self.mutex) self.unfinished_tasks = 0 + # Queue shutdown state + self.is_shutdown = False + def task_done(self): '''Indicate that a formerly enqueued task is complete. @@ -67,6 +74,8 @@ def task_done(self): Raises a ValueError if called more times than there were items placed in the queue. + + Raises ShutDown if the queue has been shut down immediately. ''' with self.all_tasks_done: unfinished = self.unfinished_tasks - 1 @@ -84,6 +93,8 @@ def join(self): to indicate the item was retrieved and all work on it is complete. When the count of unfinished tasks drops to zero, join() unblocks. + + Raises ShutDown if the queue has been shut down immediately. ''' with self.all_tasks_done: while self.unfinished_tasks: @@ -129,8 +140,12 @@ def put(self, item, block=True, timeout=None): Otherwise ('block' is false), put an item on the queue if a free slot is immediately available, else raise the Full exception ('timeout' is ignored in that case). + + Raises ShutDown if the queue has been shut down. ''' with self.not_full: + if self.is_shutdown: + raise ShutDown if self.maxsize > 0: if not block: if self._qsize() >= self.maxsize: @@ -138,6 +153,8 @@ def put(self, item, block=True, timeout=None): elif timeout is None: while self._qsize() >= self.maxsize: self.not_full.wait() + if self.is_shutdown: + raise ShutDown elif timeout < 0: raise ValueError("'timeout' must be a non-negative number") else: @@ -147,6 +164,8 @@ def put(self, item, block=True, timeout=None): if remaining <= 0.0: raise Full self.not_full.wait(remaining) + if self.is_shutdown: + raise ShutDown self._put(item) self.unfinished_tasks += 1 self.not_empty.notify() @@ -161,14 +180,21 @@ def get(self, block=True, timeout=None): Otherwise ('block' is false), return an item if one is immediately available, else raise the Empty exception ('timeout' is ignored in that case). + + Raises ShutDown if the queue has been shut down and is empty, + or if the queue has been shut down immediately. ''' with self.not_empty: + if self.is_shutdown and not self._qsize(): + raise ShutDown if not block: if not self._qsize(): raise Empty elif timeout is None: while not self._qsize(): self.not_empty.wait() + if self.is_shutdown and not self._qsize(): + raise ShutDown elif timeout < 0: raise ValueError("'timeout' must be a non-negative number") else: @@ -178,6 +204,8 @@ def get(self, block=True, timeout=None): if remaining <= 0.0: raise Empty self.not_empty.wait(remaining) + if self.is_shutdown and not self._qsize(): + raise ShutDown item = self._get() self.not_full.notify() return item @@ -198,6 +226,28 @@ def get_nowait(self): ''' return self.get(block=False) + def shutdown(self, immediate=False): + '''Shut-down the queue, making queue gets and puts raise. + + By default, gets will only raise once the queue is empty. Set + 'immediate' to True to make gets raise immediately instead. + + All blocked callers of put() will be unblocked, and also get() + and join() if 'immediate'. The ShutDown exception is raised. + ''' + with self.mutex: + self.is_shutdown = True + if immediate: + n_items = self._qsize() + while self._qsize(): + self._get() + if self.unfinished_tasks > 0: + self.unfinished_tasks -= 1 + self.not_empty.notify_all() + # release all blocked threads in `join()` + self.all_tasks_done.notify_all() + self.not_full.notify_all() + # Override these methods to implement other queue organizations # (e.g. stack or priority queue). # These will only be called with appropriate locks held diff --git a/Lib/test/test_queue.py b/Lib/test/test_queue.py index 33113a72e6b6a9d..e3d4d566cdda48a 100644 --- a/Lib/test/test_queue.py +++ b/Lib/test/test_queue.py @@ -241,6 +241,384 @@ def test_shrinking_queue(self): with self.assertRaises(self.queue.Full): q.put_nowait(4) + def test_shutdown_empty(self): + q = self.type2test() + q.shutdown() + with self.assertRaises(self.queue.ShutDown): + q.put("data") + with self.assertRaises(self.queue.ShutDown): + q.get() + + def test_shutdown_nonempty(self): + q = self.type2test() + q.put("data") + q.shutdown() + q.get() + with self.assertRaises(self.queue.ShutDown): + q.get() + + def test_shutdown_immediate(self): + q = self.type2test() + q.put("data") + q.shutdown(immediate=True) + with self.assertRaises(self.queue.ShutDown): + q.get() + + def test_shutdown_allowed_transitions(self): + # allowed transitions would be from alive via shutdown to immediate + q = self.type2test() + self.assertFalse(q.is_shutdown) + + q.shutdown() + self.assertTrue(q.is_shutdown) + + q.shutdown(immediate=True) + self.assertTrue(q.is_shutdown) + + q.shutdown(immediate=False) + + def _shutdown_all_methods_in_one_thread(self, immediate): + q = self.type2test(2) + q.put("L") + q.put_nowait("O") + q.shutdown(immediate) + + with self.assertRaises(self.queue.ShutDown): + q.put("E") + with self.assertRaises(self.queue.ShutDown): + q.put_nowait("W") + if immediate: + with self.assertRaises(self.queue.ShutDown): + q.get() + with self.assertRaises(self.queue.ShutDown): + q.get_nowait() + with self.assertRaises(ValueError): + q.task_done() + q.join() + else: + self.assertIn(q.get(), "LO") + q.task_done() + self.assertIn(q.get(), "LO") + q.task_done() + q.join() + # on shutdown(immediate=False) + # when queue is empty, should raise ShutDown Exception + with self.assertRaises(self.queue.ShutDown): + q.get() # p.get(True) + with self.assertRaises(self.queue.ShutDown): + q.get_nowait() # p.get(False) + with self.assertRaises(self.queue.ShutDown): + q.get(True, 1.0) + + def test_shutdown_all_methods_in_one_thread(self): + return self._shutdown_all_methods_in_one_thread(False) + + def test_shutdown_immediate_all_methods_in_one_thread(self): + return self._shutdown_all_methods_in_one_thread(True) + + def _write_msg_thread(self, q, n, results, delay, + i_when_exec_shutdown, + event_start, event_end): + event_start.wait() + for i in range(1, n+1): + try: + q.put((i, "YDLO")) + results.append(True) + except self.queue.ShutDown: + results.append(False) + # triggers shutdown of queue + if i == i_when_exec_shutdown: + event_end.set() + time.sleep(delay) + # end of all puts + q.join() + + def _read_msg_thread(self, q, nb, results, delay, event_start): + event_start.wait() + block = True + while nb: + time.sleep(delay) + try: + # Get at least one message + q.get(block) + block = False + q.task_done() + results.append(True) + nb -= 1 + except self.queue.ShutDown: + results.append(False) + nb -= 1 + except self.queue.Empty: + pass + q.join() + + def _shutdown_thread(self, q, event_end, immediate): + event_end.wait() + q.shutdown(immediate) + q.join() + + def _join_thread(self, q, delay, event_start): + event_start.wait() + time.sleep(delay) + q.join() + + def _shutdown_all_methods_in_many_threads(self, immediate): + q = self.type2test() + ps = [] + ev_start = threading.Event() + ev_exec_shutdown = threading.Event() + res_puts = [] + res_gets = [] + delay = 1e-4 + read_process = 4 + nb_msgs = read_process * 16 + nb_msgs_r = nb_msgs // read_process + when_exec_shutdown = nb_msgs // 2 + lprocs = ( + (self._write_msg_thread, 1, (q, nb_msgs, res_puts, delay, + when_exec_shutdown, + ev_start, ev_exec_shutdown)), + (self._read_msg_thread, read_process, (q, nb_msgs_r, + res_gets, delay*2, + ev_start)), + (self._join_thread, 2, (q, delay*2, ev_start)), + (self._shutdown_thread, 1, (q, ev_exec_shutdown, immediate)), + ) + # start all threds + for func, n, args in lprocs: + for i in range(n): + ps.append(threading.Thread(target=func, args=args)) + ps[-1].start() + # set event in order to run q.shutdown() + ev_start.set() + + if not immediate: + assert(len(res_gets) == len(res_puts)) + assert(res_gets.count(True) == res_puts.count(True)) + else: + assert(len(res_gets) <= len(res_puts)) + assert(res_gets.count(True) <= res_puts.count(True)) + + for thread in ps[1:]: + thread.join() + + def test_shutdown_all_methods_in_many_threads(self): + return self._shutdown_all_methods_in_many_threads(False) + + def test_shutdown_immediate_all_methods_in_many_threads(self): + return self._shutdown_all_methods_in_many_threads(True) + + def _get(self, q, go, results, shutdown=False): + go.wait() + try: + msg = q.get() + results.append(not shutdown) + return not shutdown + except self.queue.ShutDown: + results.append(shutdown) + return shutdown + + def _get_shutdown(self, q, go, results): + return self._get(q, go, results, True) + + def _get_task_done(self, q, go, results): + go.wait() + try: + msg = q.get() + q.task_done() + results.append(True) + return msg + except self.queue.ShutDown: + results.append(False) + return False + + def _put(self, q, msg, go, results, shutdown=False): + go.wait() + try: + q.put(msg) + results.append(not shutdown) + return not shutdown + except self.queue.ShutDown: + results.append(shutdown) + return shutdown + + def _put_shutdown(self, q, msg, go, results): + return self._put(q, msg, go, results, True) + + def _join(self, q, results, shutdown=False): + try: + q.join() + results.append(not shutdown) + return not shutdown + except self.queue.ShutDown: + results.append(shutdown) + return shutdown + + def _join_shutdown(self, q, results): + return self._join(q, results, True) + + def _shutdown_get(self, immediate): + q = self.type2test(2) + results = [] + go = threading.Event() + q.put("Y") + q.put("D") + # queue full + + if immediate: + thrds = ( + (self._get_shutdown, (q, go, results)), + (self._get_shutdown, (q, go, results)), + ) + else: + thrds = ( + # on shutdown(immediate=False) + # one of these threads shoud raise Shutdown + (self._get, (q, go, results)), + (self._get, (q, go, results)), + (self._get, (q, go, results)), + ) + threads = [] + for func, params in thrds: + threads.append(threading.Thread(target=func, args=params)) + threads[-1].start() + q.shutdown(immediate) + go.set() + for t in threads: + t.join() + if immediate: + self.assertListEqual(results, [True, True]) + else: + self.assertListEqual(sorted(results), [False] + [True]*(len(thrds)-1)) + + def test_shutdown_get(self): + return self._shutdown_get(False) + + def test_shutdown_immediate_get(self): + return self._shutdown_get(True) + + def _shutdown_put(self, immediate): + q = self.type2test(2) + results = [] + go = threading.Event() + q.put("Y") + q.put("D") + # queue fulled + + thrds = ( + (self._put_shutdown, (q, "E", go, results)), + (self._put_shutdown, (q, "W", go, results)), + ) + threads = [] + for func, params in thrds: + threads.append(threading.Thread(target=func, args=params)) + threads[-1].start() + q.shutdown() + go.set() + for t in threads: + t.join() + + self.assertEqual(results, [True]*len(thrds)) + + def test_shutdown_put(self): + return self._shutdown_put(False) + + def test_shutdown_immediate_put(self): + return self._shutdown_put(True) + + def _shutdown_join(self, immediate): + q = self.type2test() + results = [] + q.put("Y") + go = threading.Event() + nb = q.qsize() + + thrds = ( + (self._join, (q, results)), + (self._join, (q, results)), + ) + threads = [] + for func, params in thrds: + threads.append(threading.Thread(target=func, args=params)) + threads[-1].start() + if not immediate: + res = [] + for i in range(nb): + threads.append(threading.Thread(target=self._get_task_done, args=(q, go, res))) + threads[-1].start() + q.shutdown(immediate) + go.set() + for t in threads: + t.join() + + self.assertEqual(results, [True]*len(thrds)) + + def test_shutdown_immediate_join(self): + return self._shutdown_join(True) + + def test_shutdown_join(self): + return self._shutdown_join(False) + + def _shutdown_put_join(self, immediate): + q = self.type2test(2) + results = [] + go = threading.Event() + q.put("Y") + nb = q.qsize() + # queue not fulled + + thrds = ( + (self._put_shutdown, (q, "E", go, results)), + (self._join, (q, results)), + ) + threads = [] + for func, params in thrds: + threads.append(threading.Thread(target=func, args=params)) + threads[-1].start() + self.assertEqual(q.unfinished_tasks, nb) + for i in range(nb): + t = threading.Thread(target=q.task_done) + t.start() + threads.append(t) + q.shutdown(immediate) + go.set() + for t in threads: + t.join() + + self.assertEqual(results, [True]*len(thrds)) + + def test_shutdown_immediate_put_join(self): + return self._shutdown_put_join(True) + + def test_shutdown_put_join(self): + return self._shutdown_put_join(False) + + def test_shutdown_get_task_done_join(self): + q = self.type2test(2) + results = [] + go = threading.Event() + q.put("Y") + q.put("D") + self.assertEqual(q.unfinished_tasks, q.qsize()) + + thrds = ( + (self._get_task_done, (q, go, results)), + (self._get_task_done, (q, go, results)), + (self._join, (q, results)), + (self._join, (q, results)), + ) + threads = [] + for func, params in thrds: + threads.append(threading.Thread(target=func, args=params)) + threads[-1].start() + go.set() + q.shutdown(False) + for t in threads: + t.join() + + self.assertEqual(results, [True]*len(thrds)) + + class QueueTest(BaseQueueTestMixin): def setUp(self): diff --git a/Misc/NEWS.d/next/Library/2023-05-06-04-57-10.gh-issue-96471.C9wAU7.rst b/Misc/NEWS.d/next/Library/2023-05-06-04-57-10.gh-issue-96471.C9wAU7.rst new file mode 100644 index 000000000000000..0bace8d8bd425c7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-05-06-04-57-10.gh-issue-96471.C9wAU7.rst @@ -0,0 +1 @@ +Add :py:class:`queue.Queue` termination with :py:meth:`~queue.Queue.shutdown`. From e19103a346f0277c44a43dfaebad9a5aa468bf1e Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Sat, 10 Feb 2024 11:34:23 +0300 Subject: [PATCH 191/507] gh-114552: Update `__dir__` method docs: it allows returning an iterable (#114662) --- Doc/reference/datamodel.rst | 6 +++--- Lib/test/test_builtin.py | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 0a1c1d58558e94f..885ee825c122965 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1988,8 +1988,8 @@ access (use of, assignment to, or deletion of ``x.name``) for class instances. .. method:: object.__dir__(self) - Called when :func:`dir` is called on the object. A sequence must be - returned. :func:`dir` converts the returned sequence to a list and sorts it. + Called when :func:`dir` is called on the object. An iterable must be + returned. :func:`dir` converts the returned iterable to a list and sorts it. Customizing module attribute access @@ -2009,7 +2009,7 @@ not found on a module object through the normal lookup, i.e. the module ``__dict__`` before raising an :exc:`AttributeError`. If found, it is called with the attribute name and the result is returned. -The ``__dir__`` function should accept no arguments, and return a sequence of +The ``__dir__`` function should accept no arguments, and return an iterable of strings that represents the names accessible on module. If present, this function overrides the standard :func:`dir` search on a module. diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index fcddd147bac63ef..7a3ab2274a58f20 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -611,6 +611,14 @@ def __dir__(self): self.assertIsInstance(res, list) self.assertTrue(res == ["a", "b", "c"]) + # dir(obj__dir__iterable) + class Foo(object): + def __dir__(self): + return {"b", "c", "a"} + res = dir(Foo()) + self.assertIsInstance(res, list) + self.assertEqual(sorted(res), ["a", "b", "c"]) + # dir(obj__dir__not_sequence) class Foo(object): def __dir__(self): From 6e222a55b1d63de994a2ca39afd4bbf4d2fbdd34 Mon Sep 17 00:00:00 2001 From: Ronald Oussoren <ronaldoussoren@mac.com> Date: Sat, 10 Feb 2024 11:16:45 +0100 Subject: [PATCH 192/507] GH-87804: Fix counter overflow in statvfs on macOS (#99570) On macOS the statvfs interface returns block counts as 32-bit integers, and that results in bad reporting for larger disks. Therefore reimplement statvfs in terms of statfs, which does use 64-bit integers for block counts. Tested using a sparse filesystem image of 100TB. --- ...2-11-18-10-05-35.gh-issue-87804.rhlDmD.rst | 1 + Modules/posixmodule.c | 101 ++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 Misc/NEWS.d/next/macOS/2022-11-18-10-05-35.gh-issue-87804.rhlDmD.rst diff --git a/Misc/NEWS.d/next/macOS/2022-11-18-10-05-35.gh-issue-87804.rhlDmD.rst b/Misc/NEWS.d/next/macOS/2022-11-18-10-05-35.gh-issue-87804.rhlDmD.rst new file mode 100644 index 000000000000000..e6554d5c9f1e1ec --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2022-11-18-10-05-35.gh-issue-87804.rhlDmD.rst @@ -0,0 +1 @@ +On macOS the result of ``os.statvfs`` and ``os.fstatvfs`` now correctly report the size of very large disks, in previous versions the reported number of blocks was wrong for disks with at least 2**32 blocks. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 230c961a2ac3c04..d05b4ba723ce8c8 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -52,6 +52,12 @@ # define EX_OK EXIT_SUCCESS #endif +#ifdef __APPLE__ + /* Needed for the implementation of os.statvfs */ +# include <sys/param.h> +# include <sys/mount.h> +#endif + /* On android API level 21, 'AT_EACCESS' is not declared although * HAVE_FACCESSAT is defined. */ #ifdef __ANDROID__ @@ -12886,6 +12892,59 @@ os_WSTOPSIG_impl(PyObject *module, int status) #endif #include <sys/statvfs.h> +#ifdef __APPLE__ +/* On macOS struct statvfs uses 32-bit integers for block counts, + * resulting in overflow when filesystems are larger tan 4TB. Therefore + * os.statvfs is implemented in terms of statfs(2). + */ + +static PyObject* +_pystatvfs_fromstructstatfs(PyObject *module, struct statfs st) { + PyObject *StatVFSResultType = get_posix_state(module)->StatVFSResultType; + PyObject *v = PyStructSequence_New((PyTypeObject *)StatVFSResultType); + if (v == NULL) + return NULL; + + long flags = 0; + if (st.f_flags & MNT_RDONLY) { + flags |= ST_RDONLY; + } + if (st.f_flags & MNT_NOSUID) { + flags |= ST_NOSUID; + } + + _Static_assert(sizeof(st.f_blocks) == sizeof(long long), "assuming large file"); + + PyStructSequence_SET_ITEM(v, 0, PyLong_FromLong((long) st.f_iosize)); + PyStructSequence_SET_ITEM(v, 1, PyLong_FromLong((long) st.f_bsize)); + PyStructSequence_SET_ITEM(v, 2, + PyLong_FromLongLong((long long) st.f_blocks)); + PyStructSequence_SET_ITEM(v, 3, + PyLong_FromLongLong((long long) st.f_bfree)); + PyStructSequence_SET_ITEM(v, 4, + PyLong_FromLongLong((long long) st.f_bavail)); + PyStructSequence_SET_ITEM(v, 5, + PyLong_FromLongLong((long long) st.f_files)); + PyStructSequence_SET_ITEM(v, 6, + PyLong_FromLongLong((long long) st.f_ffree)); + PyStructSequence_SET_ITEM(v, 7, + PyLong_FromLongLong((long long) st.f_ffree)); + PyStructSequence_SET_ITEM(v, 8, PyLong_FromLong((long) flags)); + + PyStructSequence_SET_ITEM(v, 9, PyLong_FromLong((long) NAME_MAX)); + PyStructSequence_SET_ITEM(v, 10, PyLong_FromUnsignedLong(st.f_fsid.val[0])); + if (PyErr_Occurred()) { + Py_DECREF(v); + return NULL; + } + + return v; +} + +#else + + + static PyObject* _pystatvfs_fromstructstatvfs(PyObject *module, struct statvfs st) { PyObject *StatVFSResultType = get_posix_state(module)->StatVFSResultType; @@ -12937,6 +12996,8 @@ _pystatvfs_fromstructstatvfs(PyObject *module, struct statvfs st) { return v; } +#endif + /*[clinic input] os.fstatvfs @@ -12954,6 +13015,22 @@ os_fstatvfs_impl(PyObject *module, int fd) { int result; int async_err = 0; +#ifdef __APPLE__ + struct statfs st; + /* On macOS os.fstatvfs is implemented using fstatfs(2) because + * the former uses 32-bit values for block counts. + */ + do { + Py_BEGIN_ALLOW_THREADS + result = fstatfs(fd, &st); + Py_END_ALLOW_THREADS + } while (result != 0 && errno == EINTR && + !(async_err = PyErr_CheckSignals())); + if (result != 0) + return (!async_err) ? posix_error() : NULL; + + return _pystatvfs_fromstructstatfs(module, st); +#else struct statvfs st; do { @@ -12966,6 +13043,7 @@ os_fstatvfs_impl(PyObject *module, int fd) return (!async_err) ? posix_error() : NULL; return _pystatvfs_fromstructstatvfs(module, st); +#endif } #endif /* defined(HAVE_FSTATVFS) && defined(HAVE_SYS_STATVFS_H) */ @@ -12989,6 +13067,28 @@ os_statvfs_impl(PyObject *module, path_t *path) /*[clinic end generated code: output=87106dd1beb8556e input=3f5c35791c669bd9]*/ { int result; + +#ifdef __APPLE__ + /* On macOS os.statvfs is implemented using statfs(2)/fstatfs(2) because + * the former uses 32-bit values for block counts. + */ + struct statfs st; + + Py_BEGIN_ALLOW_THREADS + if (path->fd != -1) { + result = fstatfs(path->fd, &st); + } + else + result = statfs(path->narrow, &st); + Py_END_ALLOW_THREADS + + if (result) { + return path_error(path); + } + + return _pystatvfs_fromstructstatfs(module, st); + +#else struct statvfs st; Py_BEGIN_ALLOW_THREADS @@ -13006,6 +13106,7 @@ os_statvfs_impl(PyObject *module, path_t *path) } return _pystatvfs_fromstructstatvfs(module, st); +#endif } #endif /* defined(HAVE_STATVFS) && defined(HAVE_SYS_STATVFS_H) */ From e2c403892400878707a20d4b7e183de505a64ca5 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sat, 10 Feb 2024 12:21:35 +0200 Subject: [PATCH 193/507] gh-76763: Make chr() always raising ValueError for out-of-range values (GH-114882) Previously it raised OverflowError for very large or very small values. --- Lib/test/test_builtin.py | 11 +++++--- ...4-02-01-23-43-49.gh-issue-76763.o_2J6i.rst | 3 +++ Python/bltinmodule.c | 25 ++++++++++++++++--- Python/clinic/bltinmodule.c.h | 21 +--------------- 4 files changed, 32 insertions(+), 28 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-01-23-43-49.gh-issue-76763.o_2J6i.rst diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 7a3ab2274a58f20..9a0bf524e3943fd 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -308,14 +308,13 @@ class C3(C2): pass self.assertTrue(callable(c3)) def test_chr(self): + self.assertEqual(chr(0), '\0') self.assertEqual(chr(32), ' ') self.assertEqual(chr(65), 'A') self.assertEqual(chr(97), 'a') self.assertEqual(chr(0xff), '\xff') - self.assertRaises(ValueError, chr, 1<<24) - self.assertEqual(chr(sys.maxunicode), - str('\\U0010ffff'.encode("ascii"), 'unicode-escape')) self.assertRaises(TypeError, chr) + self.assertRaises(TypeError, chr, 65.0) self.assertEqual(chr(0x0000FFFF), "\U0000FFFF") self.assertEqual(chr(0x00010000), "\U00010000") self.assertEqual(chr(0x00010001), "\U00010001") @@ -327,7 +326,11 @@ def test_chr(self): self.assertEqual(chr(0x0010FFFF), "\U0010FFFF") self.assertRaises(ValueError, chr, -1) self.assertRaises(ValueError, chr, 0x00110000) - self.assertRaises((OverflowError, ValueError), chr, 2**32) + self.assertRaises(ValueError, chr, 1<<24) + self.assertRaises(ValueError, chr, 2**32-1) + self.assertRaises(ValueError, chr, -2**32) + self.assertRaises(ValueError, chr, 2**1000) + self.assertRaises(ValueError, chr, -2**1000) def test_cmp(self): self.assertTrue(not hasattr(builtins, "cmp")) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-01-23-43-49.gh-issue-76763.o_2J6i.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-01-23-43-49.gh-issue-76763.o_2J6i.rst new file mode 100644 index 000000000000000..d35d3d87073dddd --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-01-23-43-49.gh-issue-76763.o_2J6i.rst @@ -0,0 +1,3 @@ +The :func:`chr` builtin function now always raises :exc:`ValueError` for +values outside the valid range. Previously it raised :exc:`OverflowError` for +very large or small values. diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 31c1bf07e8fb913..b0074962b737992 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -703,17 +703,34 @@ builtin_format_impl(PyObject *module, PyObject *value, PyObject *format_spec) /*[clinic input] chr as builtin_chr - i: int + i: object / Return a Unicode string of one character with ordinal i; 0 <= i <= 0x10ffff. [clinic start generated code]*/ static PyObject * -builtin_chr_impl(PyObject *module, int i) -/*[clinic end generated code: output=c733afcd200afcb7 input=3f604ef45a70750d]*/ +builtin_chr(PyObject *module, PyObject *i) +/*[clinic end generated code: output=d34f25b8035a9b10 input=f919867f0ba2f496]*/ { - return PyUnicode_FromOrdinal(i); + int overflow; + long v = PyLong_AsLongAndOverflow(i, &overflow); + if (v == -1 && PyErr_Occurred()) { + return NULL; + } + if (overflow) { + v = overflow < 0 ? INT_MIN : INT_MAX; + /* Allow PyUnicode_FromOrdinal() to raise an exception */ + } +#if SIZEOF_INT < SIZEOF_LONG + else if (v < INT_MIN) { + v = INT_MIN; + } + else if (v > INT_MAX) { + v = INT_MAX; + } +#endif + return PyUnicode_FromOrdinal(v); } diff --git a/Python/clinic/bltinmodule.c.h b/Python/clinic/bltinmodule.c.h index 8d40e659b54a57e..3898f987cd61ea6 100644 --- a/Python/clinic/bltinmodule.c.h +++ b/Python/clinic/bltinmodule.c.h @@ -233,25 +233,6 @@ PyDoc_STRVAR(builtin_chr__doc__, #define BUILTIN_CHR_METHODDEF \ {"chr", (PyCFunction)builtin_chr, METH_O, builtin_chr__doc__}, -static PyObject * -builtin_chr_impl(PyObject *module, int i); - -static PyObject * -builtin_chr(PyObject *module, PyObject *arg) -{ - PyObject *return_value = NULL; - int i; - - i = PyLong_AsInt(arg); - if (i == -1 && PyErr_Occurred()) { - goto exit; - } - return_value = builtin_chr_impl(module, i); - -exit: - return return_value; -} - PyDoc_STRVAR(builtin_compile__doc__, "compile($module, /, source, filename, mode, flags=0,\n" " dont_inherit=False, optimize=-1, *, _feature_version=-1)\n" @@ -1212,4 +1193,4 @@ builtin_issubclass(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=31bded5d08647a57 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=643a8d5f900e0c36 input=a9049054013a1b77]*/ From 597fad07f7bf709ac7084ac20aa3647995759b01 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sat, 10 Feb 2024 15:17:33 +0200 Subject: [PATCH 194/507] gh-115059: Remove debugging code in test_io (GH-115240) --- Lib/test/test_io.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index a24579dcc878cfb..cc387afa3919099 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -2531,36 +2531,6 @@ def test_interleaved_readline_write(self): f.flush() self.assertEqual(raw.getvalue(), b'1b\n2def\n3\n') - def test_xxx(self): - with self.BytesIO(b'abcdefgh') as raw: - with self.tp(raw) as f: - f.write(b'123') - self.assertEqual(f.read(), b'defgh') - f.write(b'456') - f.flush() - self.assertEqual(raw.getvalue(), b'123defgh456') - with self.BytesIO(b'abcdefgh') as raw: - with self.tp(raw) as f: - f.write(b'123') - self.assertEqual(f.read(3), b'def') - f.write(b'456') - f.flush() - self.assertEqual(raw.getvalue(), b'123def456') - with self.BytesIO(b'abcdefgh') as raw: - with self.tp(raw) as f: - f.write(b'123') - self.assertEqual(f.read1(), b'defgh') - f.write(b'456') - f.flush() - self.assertEqual(raw.getvalue(), b'123defgh456') - with self.BytesIO(b'abcdefgh') as raw: - with self.tp(raw) as f: - f.write(b'123') - self.assertEqual(f.read1(3), b'def') - f.write(b'456') - f.flush() - self.assertEqual(raw.getvalue(), b'123def456') - # You can't construct a BufferedRandom over a non-seekable stream. test_unseekable = None From 5319c66550a6d6c6698dea75c0a0ee005873ce61 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Sat, 10 Feb 2024 17:37:19 +0300 Subject: [PATCH 195/507] gh-102840: Fix confused traceback when floordiv or mod operations happens between Fraction and complex objects (GH-102842) --- Lib/fractions.py | 13 ++++----- Lib/test/test_fractions.py | 27 +++++++++++++++++++ ...-02-10-15-24-20.gh-issue-102840.4mnDq1.rst | 3 +++ 3 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst diff --git a/Lib/fractions.py b/Lib/fractions.py index 389ab386b6a8a4d..f8c6c9c438c7379 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -579,7 +579,8 @@ def __format__(self, format_spec, /): f"for object of type {type(self).__name__!r}" ) - def _operator_fallbacks(monomorphic_operator, fallback_operator): + def _operator_fallbacks(monomorphic_operator, fallback_operator, + handle_complex=True): """Generates forward and reverse operators given a purely-rational operator and a function from the operator module. @@ -666,7 +667,7 @@ def forward(a, b): return monomorphic_operator(a, Fraction(b)) elif isinstance(b, float): return fallback_operator(float(a), b) - elif isinstance(b, complex): + elif handle_complex and isinstance(b, complex): return fallback_operator(complex(a), b) else: return NotImplemented @@ -679,7 +680,7 @@ def reverse(b, a): return monomorphic_operator(Fraction(a), b) elif isinstance(a, numbers.Real): return fallback_operator(float(a), float(b)) - elif isinstance(a, numbers.Complex): + elif handle_complex and isinstance(a, numbers.Complex): return fallback_operator(complex(a), complex(b)) else: return NotImplemented @@ -830,7 +831,7 @@ def _floordiv(a, b): """a // b""" return (a.numerator * b.denominator) // (a.denominator * b.numerator) - __floordiv__, __rfloordiv__ = _operator_fallbacks(_floordiv, operator.floordiv) + __floordiv__, __rfloordiv__ = _operator_fallbacks(_floordiv, operator.floordiv, False) def _divmod(a, b): """(a // b, a % b)""" @@ -838,14 +839,14 @@ def _divmod(a, b): div, n_mod = divmod(a.numerator * db, da * b.numerator) return div, Fraction(n_mod, da * db) - __divmod__, __rdivmod__ = _operator_fallbacks(_divmod, divmod) + __divmod__, __rdivmod__ = _operator_fallbacks(_divmod, divmod, False) def _mod(a, b): """a % b""" da, db = a.denominator, b.denominator return Fraction((a.numerator * db) % (b.numerator * da), da * db) - __mod__, __rmod__ = _operator_fallbacks(_mod, operator.mod) + __mod__, __rmod__ = _operator_fallbacks(_mod, operator.mod, False) def __pow__(a, b): """a ** b diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index af3cb214ab0ac11..b45bd098a366845 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -1314,6 +1314,33 @@ def test_float_format_testfile(self): self.assertEqual(float(format(f, fmt2)), float(rhs)) self.assertEqual(float(format(-f, fmt2)), float('-' + rhs)) + def test_complex_handling(self): + # See issue gh-102840 for more details. + + a = F(1, 2) + b = 1j + message = "unsupported operand type(s) for %s: '%s' and '%s'" + # test forward + self.assertRaisesMessage(TypeError, + message % ("%", "Fraction", "complex"), + operator.mod, a, b) + self.assertRaisesMessage(TypeError, + message % ("//", "Fraction", "complex"), + operator.floordiv, a, b) + self.assertRaisesMessage(TypeError, + message % ("divmod()", "Fraction", "complex"), + divmod, a, b) + # test reverse + self.assertRaisesMessage(TypeError, + message % ("%", "complex", "Fraction"), + operator.mod, b, a) + self.assertRaisesMessage(TypeError, + message % ("//", "complex", "Fraction"), + operator.floordiv, b, a) + self.assertRaisesMessage(TypeError, + message % ("divmod()", "complex", "Fraction"), + divmod, b, a) + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst b/Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst new file mode 100644 index 000000000000000..52668a9424a9765 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst @@ -0,0 +1,3 @@ +Fix confused traceback when floordiv, mod, or divmod operations happens +between instances of :class:`fractions.Fraction` and :class:`complex`. + From 9d1a353230f555fc28239c5ca1e82b758084e02a Mon Sep 17 00:00:00 2001 From: Mike Zimin <122507876+mikeziminio@users.noreply.github.com> Date: Sat, 10 Feb 2024 19:59:46 +0400 Subject: [PATCH 196/507] gh-114894: add array.array.clear() method (#114919) Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: AN Long <aisk@users.noreply.github.com> Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com> --- Doc/library/array.rst | 7 ++++++ Doc/whatsnew/3.13.rst | 3 +++ Lib/test/test_array.py | 23 +++++++++++++++++++ Lib/test/test_collections.py | 2 ++ ...-02-02-15-50-13.gh-issue-114894.DF-dSd.rst | 1 + Modules/arraymodule.c | 16 +++++++++++++ Modules/clinic/arraymodule.c.h | 20 +++++++++++++++- 7 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-02-15-50-13.gh-issue-114894.DF-dSd.rst diff --git a/Doc/library/array.rst b/Doc/library/array.rst index a0e8bb20a098fdc..043badf05ffc126 100644 --- a/Doc/library/array.rst +++ b/Doc/library/array.rst @@ -215,6 +215,13 @@ The module defines the following type: Remove the first occurrence of *x* from the array. + .. method:: clear() + + Remove all elements from the array. + + .. versionadded:: 3.13 + + .. method:: reverse() Reverse the order of the items in the array. diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index de79bd979aff80e..aee37737a9990ac 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -185,6 +185,9 @@ array It can be used instead of ``'u'`` type code, which is deprecated. (Contributed by Inada Naoki in :gh:`80480`.) +* Add ``clear()`` method in order to implement ``MutableSequence``. + (Contributed by Mike Zimin in :gh:`114894`.) + ast --- diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py index a219fa365e7f202..95383be9659eb9e 100755 --- a/Lib/test/test_array.py +++ b/Lib/test/test_array.py @@ -1014,6 +1014,29 @@ def test_pop(self): array.array(self.typecode, self.example[3:]+self.example[:-1]) ) + def test_clear(self): + a = array.array(self.typecode, self.example) + with self.assertRaises(TypeError): + a.clear(42) + a.clear() + self.assertEqual(len(a), 0) + self.assertEqual(a.typecode, self.typecode) + + a = array.array(self.typecode) + a.clear() + self.assertEqual(len(a), 0) + self.assertEqual(a.typecode, self.typecode) + + a = array.array(self.typecode, self.example) + a.clear() + a.append(self.example[2]) + a.append(self.example[3]) + self.assertEqual(a, array.array(self.typecode, self.example[2:4])) + + with memoryview(a): + with self.assertRaises(BufferError): + a.clear() + def test_reverse(self): a = array.array(self.typecode, self.example) self.assertRaises(TypeError, a.reverse, 42) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 7e6f811e17cfa2d..1fb492ecebd6687 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -1,5 +1,6 @@ """Unit tests for collections.py.""" +import array import collections import copy import doctest @@ -1972,6 +1973,7 @@ def test_MutableSequence(self): for sample in [list, bytearray, deque]: self.assertIsInstance(sample(), MutableSequence) self.assertTrue(issubclass(sample, MutableSequence)) + self.assertTrue(issubclass(array.array, MutableSequence)) self.assertFalse(issubclass(str, MutableSequence)) self.validate_abstract_methods(MutableSequence, '__contains__', '__iter__', '__len__', '__getitem__', '__setitem__', '__delitem__', 'insert') diff --git a/Misc/NEWS.d/next/Library/2024-02-02-15-50-13.gh-issue-114894.DF-dSd.rst b/Misc/NEWS.d/next/Library/2024-02-02-15-50-13.gh-issue-114894.DF-dSd.rst new file mode 100644 index 000000000000000..ec620f2aae3f037 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-02-15-50-13.gh-issue-114894.DF-dSd.rst @@ -0,0 +1 @@ +Add :meth:`array.array.clear`. diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c index b97ade6126fa08f..df09d9d84789f73 100644 --- a/Modules/arraymodule.c +++ b/Modules/arraymodule.c @@ -868,6 +868,21 @@ array_slice(arrayobject *a, Py_ssize_t ilow, Py_ssize_t ihigh) return (PyObject *)np; } +/*[clinic input] +array.array.clear + +Remove all items from the array. +[clinic start generated code]*/ + +static PyObject * +array_array_clear_impl(arrayobject *self) +/*[clinic end generated code: output=5efe0417062210a9 input=5dffa30e94e717a4]*/ +{ + if (array_resize(self, 0) == -1) { + return NULL; + } + Py_RETURN_NONE; +} /*[clinic input] array.array.__copy__ @@ -2342,6 +2357,7 @@ static PyMethodDef array_methods[] = { ARRAY_ARRAY_APPEND_METHODDEF ARRAY_ARRAY_BUFFER_INFO_METHODDEF ARRAY_ARRAY_BYTESWAP_METHODDEF + ARRAY_ARRAY_CLEAR_METHODDEF ARRAY_ARRAY___COPY___METHODDEF ARRAY_ARRAY_COUNT_METHODDEF ARRAY_ARRAY___DEEPCOPY___METHODDEF diff --git a/Modules/clinic/arraymodule.c.h b/Modules/clinic/arraymodule.c.h index 0b764e43e194375..60a03fe012550ee 100644 --- a/Modules/clinic/arraymodule.c.h +++ b/Modules/clinic/arraymodule.c.h @@ -5,6 +5,24 @@ preserve #include "pycore_abstract.h" // _PyNumber_Index() #include "pycore_modsupport.h" // _PyArg_CheckPositional() +PyDoc_STRVAR(array_array_clear__doc__, +"clear($self, /)\n" +"--\n" +"\n" +"Remove all items from the array."); + +#define ARRAY_ARRAY_CLEAR_METHODDEF \ + {"clear", (PyCFunction)array_array_clear, METH_NOARGS, array_array_clear__doc__}, + +static PyObject * +array_array_clear_impl(arrayobject *self); + +static PyObject * +array_array_clear(arrayobject *self, PyObject *Py_UNUSED(ignored)) +{ + return array_array_clear_impl(self); +} + PyDoc_STRVAR(array_array___copy____doc__, "__copy__($self, /)\n" "--\n" @@ -667,4 +685,4 @@ PyDoc_STRVAR(array_arrayiterator___setstate____doc__, #define ARRAY_ARRAYITERATOR___SETSTATE___METHODDEF \ {"__setstate__", (PyCFunction)array_arrayiterator___setstate__, METH_O, array_arrayiterator___setstate____doc__}, -/*[clinic end generated code: output=3be987238a4bb431 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=52c55d9b1d026c1c input=a9049054013a1b77]*/ From 6f93b4df92b8fbf80529cb6435789f5a75664a20 Mon Sep 17 00:00:00 2001 From: Barney Gale <barney.gale@gmail.com> Date: Sat, 10 Feb 2024 18:12:34 +0000 Subject: [PATCH 197/507] GH-115060: Speed up `pathlib.Path.glob()` by removing redundant regex matching (#115061) When expanding and filtering paths for a `**` wildcard segment, build an `re.Pattern` object from the subsequent pattern parts, rather than the entire pattern, and match against the `os.DirEntry` object prior to instantiating a path object. Also skip compiling a pattern when expanding a `*` wildcard segment. --- Lib/pathlib/__init__.py | 8 +- Lib/pathlib/_abc.py | 82 +++++++++++++------ Lib/test/test_pathlib/test_pathlib.py | 13 +++ ...-02-06-03-55-46.gh-issue-115060.EkWRpP.rst | 1 + 4 files changed, 76 insertions(+), 28 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-06-03-55-46.gh-issue-115060.EkWRpP.rst diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index 65ce836765c42bb..46834b1a76a6ebd 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -587,9 +587,13 @@ def iterdir(self): def _scandir(self): return os.scandir(self) - def _make_child_entry(self, entry): + def _direntry_str(self, entry): + # Transform an entry yielded from _scandir() into a path string. + return entry.name if str(self) == '.' else entry.path + + def _make_child_direntry(self, entry): # Transform an entry yielded from _scandir() into a path object. - path_str = entry.name if str(self) == '.' else entry.path + path_str = self._direntry_str(entry) path = self.with_segments(path_str) path._str = path_str path._drv = self.drive diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index e4b1201a3703c32..27c6b4e367a050f 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -86,19 +86,29 @@ def _select_children(parent_paths, dir_only, follow_symlinks, match): continue except OSError: continue - if match(entry.name): - yield parent_path._make_child_entry(entry) + # Avoid cost of making a path object for non-matching paths by + # matching against the os.DirEntry.name string. + if match is None or match(entry.name): + yield parent_path._make_child_direntry(entry) -def _select_recursive(parent_paths, dir_only, follow_symlinks): - """Yield given paths and all their subdirectories, recursively.""" +def _select_recursive(parent_paths, dir_only, follow_symlinks, match): + """Yield given paths and all their children, recursively, filtering by + string and type. + """ if follow_symlinks is None: follow_symlinks = False for parent_path in parent_paths: + if match is not None: + # If we're filtering paths through a regex, record the length of + # the parent path. We'll pass it to match(path, pos=...) later. + parent_len = len(str(parent_path._make_child_relpath('_'))) - 1 paths = [parent_path._make_child_relpath('')] while paths: path = paths.pop() - yield path + if match is None or match(str(path), parent_len): + # Yield *directory* path that matches pattern (if any). + yield path try: # We must close the scandir() object before proceeding to # avoid exhausting file descriptors when globbing deep trees. @@ -108,14 +118,22 @@ def _select_recursive(parent_paths, dir_only, follow_symlinks): pass else: for entry in entries: + # Handle directory entry. try: if entry.is_dir(follow_symlinks=follow_symlinks): - paths.append(path._make_child_entry(entry)) + # Recurse into this directory. + paths.append(path._make_child_direntry(entry)) continue except OSError: pass + + # Handle file entry. if not dir_only: - yield path._make_child_entry(entry) + # Avoid cost of making a path object for non-matching + # files by matching against the os.DirEntry object. + if match is None or match(path._direntry_str(entry), parent_len): + # Yield *file* path that matches pattern (if any). + yield path._make_child_direntry(entry) def _select_unique(paths): @@ -750,8 +768,14 @@ def _scandir(self): from contextlib import nullcontext return nullcontext(self.iterdir()) - def _make_child_entry(self, entry): + def _direntry_str(self, entry): + # Transform an entry yielded from _scandir() into a path string. + # PathBase._scandir() yields PathBase objects, so use str(). + return str(entry) + + def _make_child_direntry(self, entry): # Transform an entry yielded from _scandir() into a path object. + # PathBase._scandir() yields PathBase objects, so this is a no-op. return entry def _make_child_relpath(self, name): @@ -769,43 +793,49 @@ def glob(self, pattern, *, case_sensitive=None, follow_symlinks=None): stack = pattern._pattern_stack specials = ('', '.', '..') - filter_paths = False deduplicate_paths = False sep = self.pathmod.sep paths = iter([self] if self.is_dir() else []) while stack: part = stack.pop() if part in specials: + # Join special component (e.g. '..') onto paths. paths = _select_special(paths, part) + elif part == '**': - # Consume adjacent '**' components. + # Consume following '**' components, which have no effect. while stack and stack[-1] == '**': stack.pop() - # Consume adjacent non-special components and enable post-walk - # regex filtering, provided we're treating symlinks consistently. + # Consume following non-special components, provided we're + # treating symlinks consistently. Each component is joined + # onto 'part', which is used to generate an re.Pattern object. if follow_symlinks is not None: while stack and stack[-1] not in specials: - filter_paths = True - stack.pop() + part += sep + stack.pop() - dir_only = bool(stack) - paths = _select_recursive(paths, dir_only, follow_symlinks) + # If the previous loop consumed pattern components, compile an + # re.Pattern object based on those components. + match = _compile_pattern(part, sep, case_sensitive) if part != '**' else None + + # Recursively walk directories, filtering by type and regex. + paths = _select_recursive(paths, bool(stack), follow_symlinks, match) + + # De-duplicate if we've already seen a '**' component. if deduplicate_paths: - # De-duplicate if we've already seen a '**' component. paths = _select_unique(paths) deduplicate_paths = True + elif '**' in part: raise ValueError("Invalid pattern: '**' can only be an entire path component") + else: - dir_only = bool(stack) - match = _compile_pattern(part, sep, case_sensitive) - paths = _select_children(paths, dir_only, follow_symlinks, match) - if filter_paths: - # Filter out paths that don't match pattern. - prefix_len = len(str(self._make_child_relpath('_'))) - 1 - match = _compile_pattern(pattern._pattern_str, sep, case_sensitive) - paths = (path for path in paths if match(path._pattern_str, prefix_len)) + # If the pattern component isn't '*', compile an re.Pattern + # object based on the component. + match = _compile_pattern(part, sep, case_sensitive) if part != '*' else None + + # Iterate over directories' children filtering by type and regex. + paths = _select_children(paths, bool(stack), follow_symlinks, match) return paths def rglob(self, pattern, *, case_sensitive=None, follow_symlinks=None): @@ -854,7 +884,7 @@ def walk(self, top_down=True, on_error=None, follow_symlinks=False): if is_dir: if not top_down: - paths.append(path._make_child_entry(entry)) + paths.append(path._make_child_direntry(entry)) dirnames.append(entry.name) else: filenames.append(entry.name) diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index 2b166451243775b..c0dcf314da4bfc3 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -1250,6 +1250,19 @@ def test_glob_pathlike(self): self.assertEqual(expect, set(p.glob(P(pattern)))) self.assertEqual(expect, set(p.glob(FakePath(pattern)))) + @needs_symlinks + def test_glob_dot(self): + P = self.cls + with os_helper.change_cwd(P(self.base, "dirC")): + self.assertEqual( + set(P('.').glob('*')), {P("fileC"), P("novel.txt"), P("dirD")}) + self.assertEqual( + set(P('.').glob('**')), {P("fileC"), P("novel.txt"), P("dirD"), P("dirD/fileD"), P(".")}) + self.assertEqual( + set(P('.').glob('**/*')), {P("fileC"), P("novel.txt"), P("dirD"), P("dirD/fileD")}) + self.assertEqual( + set(P('.').glob('**/*/*')), {P("dirD/fileD")}) + def test_rglob_pathlike(self): P = self.cls p = P(self.base, "dirC") diff --git a/Misc/NEWS.d/next/Library/2024-02-06-03-55-46.gh-issue-115060.EkWRpP.rst b/Misc/NEWS.d/next/Library/2024-02-06-03-55-46.gh-issue-115060.EkWRpP.rst new file mode 100644 index 000000000000000..b358eeb569626f8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-06-03-55-46.gh-issue-115060.EkWRpP.rst @@ -0,0 +1 @@ +Speed up :meth:`pathlib.Path.glob` by removing redundant regex matching. From 33f56b743285f8419e92cfabe673fa165165a580 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Sat, 10 Feb 2024 21:34:22 +0300 Subject: [PATCH 198/507] gh-115252: Fix `test_enum` with `-OO` mode (GH-115253) --- Lib/test/test_enum.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 39c1ae0ad5a0787..f7503331c1ac1dd 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -4891,11 +4891,11 @@ class Color(enum.Enum) | | Data and other attributes defined here: | - | YELLOW = <Color.YELLOW: 3> + | CYAN = <Color.CYAN: 1> | | MAGENTA = <Color.MAGENTA: 2> | - | CYAN = <Color.CYAN: 1> + | YELLOW = <Color.YELLOW: 3> | | ---------------------------------------------------------------------- | Data descriptors inherited from enum.Enum: @@ -4905,7 +4905,18 @@ class Color(enum.Enum) | value | | ---------------------------------------------------------------------- - | Data descriptors inherited from enum.EnumType: + | Methods inherited from enum.EnumType: + | + | __contains__(value) from enum.EnumType + | + | __getitem__(name) from enum.EnumType + | + | __iter__() from enum.EnumType + | + | __len__() from enum.EnumType + | + | ---------------------------------------------------------------------- + | Readonly properties inherited from enum.EnumType: | | __members__""" From 3a5b38e3b465e00f133ff8074a2d4afb1392dfb5 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Sun, 11 Feb 2024 00:48:28 +0300 Subject: [PATCH 199/507] gh-114670: Fix `_testbuffer` module initialization (#114672) --- Modules/_testbuffer.c | 127 ++++++++++++++++++++++++------------------ 1 file changed, 74 insertions(+), 53 deletions(-) diff --git a/Modules/_testbuffer.c b/Modules/_testbuffer.c index 5101834cfe13871..5084bcadb10f85a 100644 --- a/Modules/_testbuffer.c +++ b/Modules/_testbuffer.c @@ -2816,70 +2816,91 @@ static struct PyModuleDef _testbuffermodule = { NULL }; - -PyMODINIT_FUNC -PyInit__testbuffer(void) +static int +_testbuffer_exec(PyObject *mod) { - PyObject *m; - - m = PyModule_Create(&_testbuffermodule); - if (m == NULL) - return NULL; - Py_SET_TYPE(&NDArray_Type, &PyType_Type); - Py_INCREF(&NDArray_Type); - PyModule_AddObject(m, "ndarray", (PyObject *)&NDArray_Type); + if (PyModule_AddType(mod, &NDArray_Type) < 0) { + return -1; + } Py_SET_TYPE(&StaticArray_Type, &PyType_Type); - Py_INCREF(&StaticArray_Type); - PyModule_AddObject(m, "staticarray", (PyObject *)&StaticArray_Type); + if (PyModule_AddType(mod, &StaticArray_Type) < 0) { + return -1; + } structmodule = PyImport_ImportModule("struct"); - if (structmodule == NULL) - return NULL; + if (structmodule == NULL) { + return -1; + } Struct = PyObject_GetAttrString(structmodule, "Struct"); + if (Struct == NULL) { + return -1; + } calcsize = PyObject_GetAttrString(structmodule, "calcsize"); - if (Struct == NULL || calcsize == NULL) - return NULL; + if (calcsize == NULL) { + return -1; + } simple_format = PyUnicode_FromString(simple_fmt); - if (simple_format == NULL) - return NULL; - - PyModule_AddIntMacro(m, ND_MAX_NDIM); - PyModule_AddIntMacro(m, ND_VAREXPORT); - PyModule_AddIntMacro(m, ND_WRITABLE); - PyModule_AddIntMacro(m, ND_FORTRAN); - PyModule_AddIntMacro(m, ND_SCALAR); - PyModule_AddIntMacro(m, ND_PIL); - PyModule_AddIntMacro(m, ND_GETBUF_FAIL); - PyModule_AddIntMacro(m, ND_GETBUF_UNDEFINED); - PyModule_AddIntMacro(m, ND_REDIRECT); - - PyModule_AddIntMacro(m, PyBUF_SIMPLE); - PyModule_AddIntMacro(m, PyBUF_WRITABLE); - PyModule_AddIntMacro(m, PyBUF_FORMAT); - PyModule_AddIntMacro(m, PyBUF_ND); - PyModule_AddIntMacro(m, PyBUF_STRIDES); - PyModule_AddIntMacro(m, PyBUF_INDIRECT); - PyModule_AddIntMacro(m, PyBUF_C_CONTIGUOUS); - PyModule_AddIntMacro(m, PyBUF_F_CONTIGUOUS); - PyModule_AddIntMacro(m, PyBUF_ANY_CONTIGUOUS); - PyModule_AddIntMacro(m, PyBUF_FULL); - PyModule_AddIntMacro(m, PyBUF_FULL_RO); - PyModule_AddIntMacro(m, PyBUF_RECORDS); - PyModule_AddIntMacro(m, PyBUF_RECORDS_RO); - PyModule_AddIntMacro(m, PyBUF_STRIDED); - PyModule_AddIntMacro(m, PyBUF_STRIDED_RO); - PyModule_AddIntMacro(m, PyBUF_CONTIG); - PyModule_AddIntMacro(m, PyBUF_CONTIG_RO); - - PyModule_AddIntMacro(m, PyBUF_READ); - PyModule_AddIntMacro(m, PyBUF_WRITE); - - return m; -} + if (simple_format == NULL) { + return -1; + } +#define ADD_INT_MACRO(mod, macro) \ + do { \ + if (PyModule_AddIntConstant(mod, #macro, macro) < 0) { \ + return -1; \ + } \ + } while (0) + + ADD_INT_MACRO(mod, ND_MAX_NDIM); + ADD_INT_MACRO(mod, ND_VAREXPORT); + ADD_INT_MACRO(mod, ND_WRITABLE); + ADD_INT_MACRO(mod, ND_FORTRAN); + ADD_INT_MACRO(mod, ND_SCALAR); + ADD_INT_MACRO(mod, ND_PIL); + ADD_INT_MACRO(mod, ND_GETBUF_FAIL); + ADD_INT_MACRO(mod, ND_GETBUF_UNDEFINED); + ADD_INT_MACRO(mod, ND_REDIRECT); + + ADD_INT_MACRO(mod, PyBUF_SIMPLE); + ADD_INT_MACRO(mod, PyBUF_WRITABLE); + ADD_INT_MACRO(mod, PyBUF_FORMAT); + ADD_INT_MACRO(mod, PyBUF_ND); + ADD_INT_MACRO(mod, PyBUF_STRIDES); + ADD_INT_MACRO(mod, PyBUF_INDIRECT); + ADD_INT_MACRO(mod, PyBUF_C_CONTIGUOUS); + ADD_INT_MACRO(mod, PyBUF_F_CONTIGUOUS); + ADD_INT_MACRO(mod, PyBUF_ANY_CONTIGUOUS); + ADD_INT_MACRO(mod, PyBUF_FULL); + ADD_INT_MACRO(mod, PyBUF_FULL_RO); + ADD_INT_MACRO(mod, PyBUF_RECORDS); + ADD_INT_MACRO(mod, PyBUF_RECORDS_RO); + ADD_INT_MACRO(mod, PyBUF_STRIDED); + ADD_INT_MACRO(mod, PyBUF_STRIDED_RO); + ADD_INT_MACRO(mod, PyBUF_CONTIG); + ADD_INT_MACRO(mod, PyBUF_CONTIG_RO); + + ADD_INT_MACRO(mod, PyBUF_READ); + ADD_INT_MACRO(mod, PyBUF_WRITE); + +#undef ADD_INT_MACRO + return 0; +} +PyMODINIT_FUNC +PyInit__testbuffer(void) +{ + PyObject *mod = PyModule_Create(&_testbuffermodule); + if (mod == NULL) { + return NULL; + } + if (_testbuffer_exec(mod) < 0) { + Py_DECREF(mod); + return NULL; + } + return mod; +} From b70a68fbd6b72a25b5ef430603e39c9e40f40d29 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Sun, 11 Feb 2024 00:51:05 +0300 Subject: [PATCH 200/507] gh-115254: Fix `test_property` with `-00` mode (#115255) --- Lib/test/test_property.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index c12c908d2ee32d0..8ace9fd17ab96e7 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -224,6 +224,7 @@ class PropertySubSlots(property): class PropertySubclassTests(unittest.TestCase): + @support.requires_docstrings def test_slots_docstring_copy_exception(self): # A special case error that we preserve despite the GH-98963 behavior # that would otherwise silently ignore this error. From 4821f08674e290a396d27aa8256fd5b8a121f3d6 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Sun, 11 Feb 2024 01:59:23 +0300 Subject: [PATCH 201/507] gh-101100: Fix sphinx warnings in `c-api/gcsupport.rst` (#114786) --- Doc/c-api/gcsupport.rst | 11 ++++++++--- Doc/tools/.nitignore | 1 - Misc/NEWS.d/3.12.0b1.rst | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Doc/c-api/gcsupport.rst b/Doc/c-api/gcsupport.rst index 6b2494ee4f0ed4c..621da3eb069949c 100644 --- a/Doc/c-api/gcsupport.rst +++ b/Doc/c-api/gcsupport.rst @@ -83,10 +83,15 @@ rules: .. versionadded:: 3.12 -.. c:function:: TYPE* PyObject_GC_Resize(TYPE, PyVarObject *op, Py_ssize_t newsize) +.. c:macro:: PyObject_GC_Resize(TYPE, op, newsize) - Resize an object allocated by :c:macro:`PyObject_NewVar`. Returns the - resized object or ``NULL`` on failure. *op* must not be tracked by the collector yet. + Resize an object allocated by :c:macro:`PyObject_NewVar`. + Returns the resized object of type ``TYPE*`` (refers to any C type) + or ``NULL`` on failure. + + *op* must be of type :c:expr:`PyVarObject *` + and must not be tracked by the collector yet. + *newsize* must be of type :c:type:`Py_ssize_t`. .. c:function:: void PyObject_GC_Track(PyObject *op) diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 9db02c5c3c73c90..1d1b16166e906ce 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -5,7 +5,6 @@ Doc/c-api/descriptor.rst Doc/c-api/exceptions.rst Doc/c-api/float.rst -Doc/c-api/gcsupport.rst Doc/c-api/init.rst Doc/c-api/init_config.rst Doc/c-api/intro.rst diff --git a/Misc/NEWS.d/3.12.0b1.rst b/Misc/NEWS.d/3.12.0b1.rst index 211513d05d00407..21f2c748f405481 100644 --- a/Misc/NEWS.d/3.12.0b1.rst +++ b/Misc/NEWS.d/3.12.0b1.rst @@ -2371,7 +2371,7 @@ Add a new C-API function to eagerly assign a version tag to a PyTypeObject: .. nonce: _paFIF .. section: C API -:c:func:`PyObject_GC_Resize` should calculate preheader size if needed. +:c:macro:`PyObject_GC_Resize` should calculate preheader size if needed. Patch by Donghee Na. .. From 1a6e2138773b94fdae449b658a9983cd1fc0f08a Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Sat, 10 Feb 2024 22:14:25 -0500 Subject: [PATCH 202/507] gh-115258: Temporarily disable test on Windows (#115269) The "test_shutdown_all_methods_in_many_threads" test times out on the Windows CI. This skips the test on Windows until we figure out the root cause. --- Lib/test/test_queue.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_queue.py b/Lib/test/test_queue.py index e3d4d566cdda48a..d308a212999429e 100644 --- a/Lib/test/test_queue.py +++ b/Lib/test/test_queue.py @@ -2,6 +2,7 @@ # to ensure the Queue locks remain stable. import itertools import random +import sys import threading import time import unittest @@ -402,9 +403,11 @@ def _shutdown_all_methods_in_many_threads(self, immediate): for thread in ps[1:]: thread.join() + @unittest.skipIf(sys.platform == "win32", "test times out (gh-115258)") def test_shutdown_all_methods_in_many_threads(self): return self._shutdown_all_methods_in_many_threads(False) + @unittest.skipIf(sys.platform == "win32", "test times out (gh-115258)") def test_shutdown_immediate_all_methods_in_many_threads(self): return self._shutdown_all_methods_in_many_threads(True) From 1f23837277e604f41589273aeb3a10377d416510 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Sun, 11 Feb 2024 11:00:44 +0300 Subject: [PATCH 203/507] gh-115249: Fix `test_descr` with `-OO` mode (#115250) --- Lib/test/test_descr.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index beeab6cb7f254c1..5404d8d3b99d5d4 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -1594,7 +1594,11 @@ def f(cls, arg): cm = classmethod(f) cm_dict = {'__annotations__': {}, - '__doc__': "f docstring", + '__doc__': ( + "f docstring" + if support.HAVE_DOCSTRINGS + else None + ), '__module__': __name__, '__name__': 'f', '__qualname__': f.__qualname__} From f8e9c57067e32baab4ed2fd824b892c52ecb7225 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Sun, 11 Feb 2024 11:51:25 +0300 Subject: [PATCH 204/507] gh-115274: Fix direct invocation of `testmock/testpatch.py` (#115275) --- Lib/test/test_unittest/testmock/testpatch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_unittest/testmock/testpatch.py b/Lib/test/test_unittest/testmock/testpatch.py index 833d7da1f31a206..d0046d702a53f42 100644 --- a/Lib/test/test_unittest/testmock/testpatch.py +++ b/Lib/test/test_unittest/testmock/testpatch.py @@ -1912,7 +1912,7 @@ def foo(x=0): with patch.object(foo, '__module__', "testpatch2"): self.assertEqual(foo.__module__, "testpatch2") - self.assertEqual(foo.__module__, 'test.test_unittest.testmock.testpatch') + self.assertEqual(foo.__module__, __name__) with patch.object(foo, '__annotations__', dict([('s', 1, )])): self.assertEqual(foo.__annotations__, dict([('s', 1, )])) From 1b895914742d20ccebd1b56b1b0936b7e00eb95e Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Sun, 11 Feb 2024 03:21:10 -0600 Subject: [PATCH 205/507] gh-101100: Fix dangling refs in bdb.rst (#114983) Co-authored-by: AN Long <aisk@users.noreply.github.com> --- Doc/library/bdb.rst | 18 ++++++++++-------- Doc/tools/.nitignore | 1 - 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Doc/library/bdb.rst b/Doc/library/bdb.rst index 52f0ca7c013482b..7bf4308a96d0f55 100644 --- a/Doc/library/bdb.rst +++ b/Doc/library/bdb.rst @@ -148,8 +148,8 @@ The :mod:`bdb` module also defines two classes: .. method:: reset() - Set the :attr:`botframe`, :attr:`stopframe`, :attr:`returnframe` and - :attr:`quitting` attributes with values ready to start debugging. + Set the :attr:`!botframe`, :attr:`!stopframe`, :attr:`!returnframe` and + :attr:`quitting <Bdb.set_quit>` attributes with values ready to start debugging. .. method:: trace_dispatch(frame, event, arg) @@ -182,7 +182,7 @@ The :mod:`bdb` module also defines two classes: If the debugger should stop on the current line, invoke the :meth:`user_line` method (which should be overridden in subclasses). - Raise a :exc:`BdbQuit` exception if the :attr:`Bdb.quitting` flag is set + Raise a :exc:`BdbQuit` exception if the :attr:`quitting <Bdb.set_quit>` flag is set (which can be set from :meth:`user_line`). Return a reference to the :meth:`trace_dispatch` method for further tracing in that scope. @@ -190,7 +190,7 @@ The :mod:`bdb` module also defines two classes: If the debugger should stop on this function call, invoke the :meth:`user_call` method (which should be overridden in subclasses). - Raise a :exc:`BdbQuit` exception if the :attr:`Bdb.quitting` flag is set + Raise a :exc:`BdbQuit` exception if the :attr:`quitting <Bdb.set_quit>` flag is set (which can be set from :meth:`user_call`). Return a reference to the :meth:`trace_dispatch` method for further tracing in that scope. @@ -198,7 +198,7 @@ The :mod:`bdb` module also defines two classes: If the debugger should stop on this function return, invoke the :meth:`user_return` method (which should be overridden in subclasses). - Raise a :exc:`BdbQuit` exception if the :attr:`Bdb.quitting` flag is set + Raise a :exc:`BdbQuit` exception if the :attr:`quitting <Bdb.set_quit>` flag is set (which can be set from :meth:`user_return`). Return a reference to the :meth:`trace_dispatch` method for further tracing in that scope. @@ -206,7 +206,7 @@ The :mod:`bdb` module also defines two classes: If the debugger should stop at this exception, invokes the :meth:`user_exception` method (which should be overridden in subclasses). - Raise a :exc:`BdbQuit` exception if the :attr:`Bdb.quitting` flag is set + Raise a :exc:`BdbQuit` exception if the :attr:`quitting <Bdb.set_quit>` flag is set (which can be set from :meth:`user_exception`). Return a reference to the :meth:`trace_dispatch` method for further tracing in that scope. @@ -293,7 +293,9 @@ The :mod:`bdb` module also defines two classes: .. method:: set_quit() - Set the :attr:`quitting` attribute to ``True``. This raises :exc:`BdbQuit` in + .. index:: single: quitting (bdb.Bdb attribute) + + Set the :attr:`!quitting` attribute to ``True``. This raises :exc:`BdbQuit` in the next call to one of the :meth:`!dispatch_\*` methods. @@ -383,7 +385,7 @@ The :mod:`bdb` module also defines two classes: .. method:: run(cmd, globals=None, locals=None) Debug a statement executed via the :func:`exec` function. *globals* - defaults to :attr:`__main__.__dict__`, *locals* defaults to *globals*. + defaults to :attr:`!__main__.__dict__`, *locals* defaults to *globals*. .. method:: runeval(expr, globals=None, locals=None) diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 1d1b16166e906ce..2af116c2d79c54e 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -21,7 +21,6 @@ Doc/library/ast.rst Doc/library/asyncio-extending.rst Doc/library/asyncio-policy.rst Doc/library/asyncio-subprocess.rst -Doc/library/bdb.rst Doc/library/collections.rst Doc/library/dbm.rst Doc/library/decimal.rst From 4b75032c88046505cad36157aa94a41fd37638f4 Mon Sep 17 00:00:00 2001 From: Hood Chatham <roberthoodchatham@gmail.com> Date: Sun, 11 Feb 2024 01:59:50 -0800 Subject: [PATCH 206/507] gh-114807: multiprocessing: don't raise ImportError if _multiprocessing is missing (#114808) `_multiprocessing` is only used under the `if _winapi:` block, this moves the import to be within the `_winapi` ImportError handling try/except for equivalent treatment. --- Lib/multiprocessing/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py index dbbf106f6809649..c6a66a1bc963c3a 100644 --- a/Lib/multiprocessing/connection.py +++ b/Lib/multiprocessing/connection.py @@ -19,7 +19,6 @@ import tempfile import itertools -import _multiprocessing from . import util @@ -28,6 +27,7 @@ _ForkingPickler = reduction.ForkingPickler try: + import _multiprocessing import _winapi from _winapi import WAIT_OBJECT_0, WAIT_ABANDONED_0, WAIT_TIMEOUT, INFINITE except ImportError: From 4a08e7b3431cd32a0daf22a33421cd3035343dc4 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 11 Feb 2024 12:08:39 +0200 Subject: [PATCH 207/507] gh-115133: Fix tests for XMLPullParser with Expat 2.6.0 (GH-115164) Feeding the parser by too small chunks defers parsing to prevent CVE-2023-52425. Future versions of Expat may be more reactive. --- Lib/test/test_xml_etree.py | 58 ++++++++++++------- ...-02-08-14-21-28.gh-issue-115133.ycl4ko.rst | 2 + 2 files changed, 38 insertions(+), 22 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-08-14-21-28.gh-issue-115133.ycl4ko.rst diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index a435ec7822ea0cb..c535d631bb646f7 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -13,6 +13,7 @@ import operator import os import pickle +import pyexpat import sys import textwrap import types @@ -120,6 +121,10 @@ </foo> """ +fails_with_expat_2_6_0 = (unittest.expectedFailure + if pyexpat.version_info >= (2, 6, 0) else + lambda test: test) + def checkwarnings(*filters, quiet=False): def decorator(test): def newtest(*args, **kwargs): @@ -1480,28 +1485,37 @@ def assert_event_tags(self, parser, expected, max_events=None): self.assertEqual([(action, elem.tag) for action, elem in events], expected) - def test_simple_xml(self): - for chunk_size in (None, 1, 5): - with self.subTest(chunk_size=chunk_size): - parser = ET.XMLPullParser() - self.assert_event_tags(parser, []) - self._feed(parser, "<!-- comment -->\n", chunk_size) - self.assert_event_tags(parser, []) - self._feed(parser, - "<root>\n <element key='value'>text</element", - chunk_size) - self.assert_event_tags(parser, []) - self._feed(parser, ">\n", chunk_size) - self.assert_event_tags(parser, [('end', 'element')]) - self._feed(parser, "<element>text</element>tail\n", chunk_size) - self._feed(parser, "<empty-element/>\n", chunk_size) - self.assert_event_tags(parser, [ - ('end', 'element'), - ('end', 'empty-element'), - ]) - self._feed(parser, "</root>\n", chunk_size) - self.assert_event_tags(parser, [('end', 'root')]) - self.assertIsNone(parser.close()) + def test_simple_xml(self, chunk_size=None): + parser = ET.XMLPullParser() + self.assert_event_tags(parser, []) + self._feed(parser, "<!-- comment -->\n", chunk_size) + self.assert_event_tags(parser, []) + self._feed(parser, + "<root>\n <element key='value'>text</element", + chunk_size) + self.assert_event_tags(parser, []) + self._feed(parser, ">\n", chunk_size) + self.assert_event_tags(parser, [('end', 'element')]) + self._feed(parser, "<element>text</element>tail\n", chunk_size) + self._feed(parser, "<empty-element/>\n", chunk_size) + self.assert_event_tags(parser, [ + ('end', 'element'), + ('end', 'empty-element'), + ]) + self._feed(parser, "</root>\n", chunk_size) + self.assert_event_tags(parser, [('end', 'root')]) + self.assertIsNone(parser.close()) + + @fails_with_expat_2_6_0 + def test_simple_xml_chunk_1(self): + self.test_simple_xml(chunk_size=1) + + @fails_with_expat_2_6_0 + def test_simple_xml_chunk_5(self): + self.test_simple_xml(chunk_size=5) + + def test_simple_xml_chunk_22(self): + self.test_simple_xml(chunk_size=22) def test_feed_while_iterating(self): parser = ET.XMLPullParser() diff --git a/Misc/NEWS.d/next/Library/2024-02-08-14-21-28.gh-issue-115133.ycl4ko.rst b/Misc/NEWS.d/next/Library/2024-02-08-14-21-28.gh-issue-115133.ycl4ko.rst new file mode 100644 index 000000000000000..6f1015235cc25d4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-08-14-21-28.gh-issue-115133.ycl4ko.rst @@ -0,0 +1,2 @@ +Fix tests for :class:`~xml.etree.ElementTree.XMLPullParser` with Expat +2.6.0. From 573acb30f22a84c0f2c951efa002c9946e29b6a3 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 11 Feb 2024 12:23:30 +0200 Subject: [PATCH 208/507] gh-115172: Fix explicit index extries for the C API (GH-115173) --- Doc/c-api/buffer.rst | 2 +- Doc/c-api/code.rst | 10 +-- Doc/c-api/exceptions.rst | 140 ++++++++++++++++++------------------ Doc/c-api/file.rst | 2 +- Doc/c-api/init.rst | 25 +++---- Doc/c-api/intro.rst | 38 +++++----- Doc/c-api/long.rst | 8 +-- Doc/c-api/memory.rst | 8 +-- Doc/c-api/structures.rst | 44 ++++++------ Doc/c-api/sys.rst | 8 +-- Doc/c-api/veryhigh.rst | 6 +- Doc/extending/extending.rst | 6 +- Doc/extending/newtypes.rst | 4 +- Doc/library/re.rst | 2 +- 14 files changed, 150 insertions(+), 153 deletions(-) diff --git a/Doc/c-api/buffer.rst b/Doc/c-api/buffer.rst index e572815ffd6259f..1e1cabdf242bd13 100644 --- a/Doc/c-api/buffer.rst +++ b/Doc/c-api/buffer.rst @@ -29,7 +29,7 @@ without intermediate copying. Python provides such a facility at the C level in the form of the :ref:`buffer protocol <bufferobjects>`. This protocol has two sides: -.. index:: single: PyBufferProcs +.. index:: single: PyBufferProcs (C type) - on the producer side, a type can export a "buffer interface" which allows objects of that type to expose information about their underlying buffer. diff --git a/Doc/c-api/code.rst b/Doc/c-api/code.rst index 11c12e685fcace4..382cfbff864072c 100644 --- a/Doc/c-api/code.rst +++ b/Doc/c-api/code.rst @@ -49,7 +49,7 @@ bound into a function. .. versionchanged:: 3.11 Added ``qualname`` and ``exceptiontable`` parameters. - .. index:: single: PyCode_New + .. index:: single: PyCode_New (C function) .. versionchanged:: 3.12 @@ -62,7 +62,7 @@ bound into a function. Similar to :c:func:`PyUnstable_Code_New`, but with an extra "posonlyargcount" for positional-only arguments. The same caveats that apply to ``PyUnstable_Code_New`` also apply to this function. - .. index:: single: PyCode_NewWithPosOnlyArgs + .. index:: single: PyCode_NewWithPosOnlyArgs (C function) .. versionadded:: 3.8 as ``PyCode_NewWithPosOnlyArgs`` @@ -221,7 +221,7 @@ may change without deprecation warnings. *free* will be called on non-``NULL`` data stored under the new index. Use :c:func:`Py_DecRef` when storing :c:type:`PyObject`. - .. index:: single: _PyEval_RequestCodeExtraIndex + .. index:: single: _PyEval_RequestCodeExtraIndex (C function) .. versionadded:: 3.6 as ``_PyEval_RequestCodeExtraIndex`` @@ -239,7 +239,7 @@ may change without deprecation warnings. If no data was set under the index, set *extra* to ``NULL`` and return 0 without setting an exception. - .. index:: single: _PyCode_GetExtra + .. index:: single: _PyCode_GetExtra (C function) .. versionadded:: 3.6 as ``_PyCode_GetExtra`` @@ -254,7 +254,7 @@ may change without deprecation warnings. Set the extra data stored under the given index to *extra*. Return 0 on success. Set an exception and return -1 on failure. - .. index:: single: _PyCode_SetExtra + .. index:: single: _PyCode_SetExtra (C function) .. versionadded:: 3.6 as ``_PyCode_SetExtra`` diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index c7e3cd9463e5d77..eaf723fb2cc4cf9 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -180,7 +180,7 @@ For convenience, some of these functions will always return a .. c:function:: PyObject* PyErr_SetFromErrno(PyObject *type) - .. index:: single: strerror() + .. index:: single: strerror (C function) This is a convenience function to raise an exception when a C library function has returned an error and set the C variable :c:data:`errno`. It constructs a @@ -635,7 +635,7 @@ Signal Handling .. index:: pair: module; signal - single: SIGINT + single: SIGINT (C macro) single: KeyboardInterrupt (built-in exception) This function interacts with Python's signal handling. @@ -666,7 +666,7 @@ Signal Handling .. index:: pair: module; signal - single: SIGINT + single: SIGINT (C macro) single: KeyboardInterrupt (built-in exception) Simulate the effect of a :c:macro:`!SIGINT` signal arriving. @@ -968,59 +968,59 @@ All standard Python exceptions are available as global variables whose names are the variables: .. index:: - single: PyExc_BaseException - single: PyExc_Exception - single: PyExc_ArithmeticError - single: PyExc_AssertionError - single: PyExc_AttributeError - single: PyExc_BlockingIOError - single: PyExc_BrokenPipeError - single: PyExc_BufferError - single: PyExc_ChildProcessError - single: PyExc_ConnectionAbortedError - single: PyExc_ConnectionError - single: PyExc_ConnectionRefusedError - single: PyExc_ConnectionResetError - single: PyExc_EOFError - single: PyExc_FileExistsError - single: PyExc_FileNotFoundError - single: PyExc_FloatingPointError - single: PyExc_GeneratorExit - single: PyExc_ImportError - single: PyExc_IndentationError - single: PyExc_IndexError - single: PyExc_InterruptedError - single: PyExc_IsADirectoryError - single: PyExc_KeyError - single: PyExc_KeyboardInterrupt - single: PyExc_LookupError - single: PyExc_MemoryError - single: PyExc_ModuleNotFoundError - single: PyExc_NameError - single: PyExc_NotADirectoryError - single: PyExc_NotImplementedError - single: PyExc_OSError - single: PyExc_OverflowError - single: PyExc_PermissionError - single: PyExc_ProcessLookupError - single: PyExc_RecursionError - single: PyExc_ReferenceError - single: PyExc_RuntimeError - single: PyExc_StopAsyncIteration - single: PyExc_StopIteration - single: PyExc_SyntaxError - single: PyExc_SystemError - single: PyExc_SystemExit - single: PyExc_TabError - single: PyExc_TimeoutError - single: PyExc_TypeError - single: PyExc_UnboundLocalError - single: PyExc_UnicodeDecodeError - single: PyExc_UnicodeEncodeError - single: PyExc_UnicodeError - single: PyExc_UnicodeTranslateError - single: PyExc_ValueError - single: PyExc_ZeroDivisionError + single: PyExc_BaseException (C var) + single: PyExc_Exception (C var) + single: PyExc_ArithmeticError (C var) + single: PyExc_AssertionError (C var) + single: PyExc_AttributeError (C var) + single: PyExc_BlockingIOError (C var) + single: PyExc_BrokenPipeError (C var) + single: PyExc_BufferError (C var) + single: PyExc_ChildProcessError (C var) + single: PyExc_ConnectionAbortedError (C var) + single: PyExc_ConnectionError (C var) + single: PyExc_ConnectionRefusedError (C var) + single: PyExc_ConnectionResetError (C var) + single: PyExc_EOFError (C var) + single: PyExc_FileExistsError (C var) + single: PyExc_FileNotFoundError (C var) + single: PyExc_FloatingPointError (C var) + single: PyExc_GeneratorExit (C var) + single: PyExc_ImportError (C var) + single: PyExc_IndentationError (C var) + single: PyExc_IndexError (C var) + single: PyExc_InterruptedError (C var) + single: PyExc_IsADirectoryError (C var) + single: PyExc_KeyError (C var) + single: PyExc_KeyboardInterrupt (C var) + single: PyExc_LookupError (C var) + single: PyExc_MemoryError (C var) + single: PyExc_ModuleNotFoundError (C var) + single: PyExc_NameError (C var) + single: PyExc_NotADirectoryError (C var) + single: PyExc_NotImplementedError (C var) + single: PyExc_OSError (C var) + single: PyExc_OverflowError (C var) + single: PyExc_PermissionError (C var) + single: PyExc_ProcessLookupError (C var) + single: PyExc_RecursionError (C var) + single: PyExc_ReferenceError (C var) + single: PyExc_RuntimeError (C var) + single: PyExc_StopAsyncIteration (C var) + single: PyExc_StopIteration (C var) + single: PyExc_SyntaxError (C var) + single: PyExc_SystemError (C var) + single: PyExc_SystemExit (C var) + single: PyExc_TabError (C var) + single: PyExc_TimeoutError (C var) + single: PyExc_TypeError (C var) + single: PyExc_UnboundLocalError (C var) + single: PyExc_UnicodeDecodeError (C var) + single: PyExc_UnicodeEncodeError (C var) + single: PyExc_UnicodeError (C var) + single: PyExc_UnicodeTranslateError (C var) + single: PyExc_ValueError (C var) + single: PyExc_ZeroDivisionError (C var) +-----------------------------------------+---------------------------------+----------+ | C Name | Python Name | Notes | @@ -1151,9 +1151,9 @@ the variables: These are compatibility aliases to :c:data:`PyExc_OSError`: .. index:: - single: PyExc_EnvironmentError - single: PyExc_IOError - single: PyExc_WindowsError + single: PyExc_EnvironmentError (C var) + single: PyExc_IOError (C var) + single: PyExc_WindowsError (C var) +-------------------------------------+----------+ | C Name | Notes | @@ -1188,17 +1188,17 @@ names are ``PyExc_`` followed by the Python exception name. These have the type the variables: .. index:: - single: PyExc_Warning - single: PyExc_BytesWarning - single: PyExc_DeprecationWarning - single: PyExc_FutureWarning - single: PyExc_ImportWarning - single: PyExc_PendingDeprecationWarning - single: PyExc_ResourceWarning - single: PyExc_RuntimeWarning - single: PyExc_SyntaxWarning - single: PyExc_UnicodeWarning - single: PyExc_UserWarning + single: PyExc_Warning (C var) + single: PyExc_BytesWarning (C var) + single: PyExc_DeprecationWarning (C var) + single: PyExc_FutureWarning (C var) + single: PyExc_ImportWarning (C var) + single: PyExc_PendingDeprecationWarning (C var) + single: PyExc_ResourceWarning (C var) + single: PyExc_RuntimeWarning (C var) + single: PyExc_SyntaxWarning (C var) + single: PyExc_UnicodeWarning (C var) + single: PyExc_UserWarning (C var) +------------------------------------------+---------------------------------+----------+ | C Name | Python Name | Notes | diff --git a/Doc/c-api/file.rst b/Doc/c-api/file.rst index d3a78c588454e8e..e9019a0d500f7e5 100644 --- a/Doc/c-api/file.rst +++ b/Doc/c-api/file.rst @@ -96,7 +96,7 @@ the :mod:`io` APIs instead. .. c:function:: int PyFile_WriteObject(PyObject *obj, PyObject *p, int flags) - .. index:: single: Py_PRINT_RAW + .. index:: single: Py_PRINT_RAW (C macro) Write object *obj* to file object *p*. The only supported flag for *flags* is :c:macro:`Py_PRINT_RAW`; if given, the :func:`str` of the object is written diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index f8fd48e781d6da1..e7199ad5e0c1b15 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -332,7 +332,7 @@ Initializing and finalizing the interpreter pair: module; __main__ pair: module; sys triple: module; search; path - single: Py_FinalizeEx() + single: Py_FinalizeEx (C function) Initialize the Python interpreter. In an application embedding Python, this should be called before using any other Python/C API functions; see @@ -661,7 +661,7 @@ operations could cause problems in a multi-threaded program: for example, when two threads simultaneously increment the reference count of the same object, the reference count could end up being incremented only once instead of twice. -.. index:: single: setswitchinterval() (in module sys) +.. index:: single: setswitchinterval (in module sys) Therefore, the rule exists that only the thread that has acquired the :term:`GIL` may operate on Python objects or call Python/C API functions. @@ -671,8 +671,7 @@ released around potentially blocking I/O operations like reading or writing a file, so that other Python threads can run in the meantime. .. index:: - single: PyThreadState - single: PyThreadState + single: PyThreadState (C type) The Python interpreter keeps some thread-specific bookkeeping information inside a data structure called :c:type:`PyThreadState`. There's also one @@ -698,8 +697,8 @@ This is so common that a pair of macros exists to simplify it:: Py_END_ALLOW_THREADS .. index:: - single: Py_BEGIN_ALLOW_THREADS - single: Py_END_ALLOW_THREADS + single: Py_BEGIN_ALLOW_THREADS (C macro) + single: Py_END_ALLOW_THREADS (C macro) The :c:macro:`Py_BEGIN_ALLOW_THREADS` macro opens a new block and declares a hidden local variable; the :c:macro:`Py_END_ALLOW_THREADS` macro closes the @@ -714,8 +713,8 @@ The block above expands to the following code:: PyEval_RestoreThread(_save); .. index:: - single: PyEval_RestoreThread() - single: PyEval_SaveThread() + single: PyEval_RestoreThread (C function) + single: PyEval_SaveThread (C function) Here is how these functions work: the global interpreter lock is used to protect the pointer to the current thread state. When releasing the lock and saving the thread state, @@ -1399,8 +1398,8 @@ function. You can create and destroy them using the following functions: may be stored internally on the :c:type:`PyInterpreterState`. .. index:: - single: Py_FinalizeEx() - single: Py_Initialize() + single: Py_FinalizeEx (C function) + single: Py_Initialize (C function) Extension modules are shared between (sub-)interpreters as follows: @@ -1428,7 +1427,7 @@ function. You can create and destroy them using the following functions: As with multi-phase initialization, this means that only C-level static and global variables are shared between these modules. - .. index:: single: close() (in module os) + .. index:: single: close (in module os) .. c:function:: PyThreadState* Py_NewInterpreter(void) @@ -1451,7 +1450,7 @@ function. You can create and destroy them using the following functions: .. c:function:: void Py_EndInterpreter(PyThreadState *tstate) - .. index:: single: Py_FinalizeEx() + .. index:: single: Py_FinalizeEx (C function) Destroy the (sub-)interpreter represented by the given thread state. The given thread state must be the current thread state. See the @@ -1543,8 +1542,6 @@ pointer and a void pointer argument. .. c:function:: int Py_AddPendingCall(int (*func)(void *), void *arg) - .. index:: single: Py_AddPendingCall() - Schedule a function to be called from the main interpreter thread. On success, ``0`` is returned and *func* is queued for being called in the main thread. On failure, ``-1`` is returned without setting any exception. diff --git a/Doc/c-api/intro.rst b/Doc/c-api/intro.rst index dcda1071a58f35b..8ef463e3f88ca8f 100644 --- a/Doc/c-api/intro.rst +++ b/Doc/c-api/intro.rst @@ -325,8 +325,8 @@ objects that reference each other here; for now, the solution is "don't do that.") .. index:: - single: Py_INCREF() - single: Py_DECREF() + single: Py_INCREF (C function) + single: Py_DECREF (C function) Reference counts are always manipulated explicitly. The normal way is to use the macro :c:func:`Py_INCREF` to take a new reference to an @@ -401,8 +401,8 @@ function, that function assumes that it now owns that reference, and you are not responsible for it any longer. .. index:: - single: PyList_SetItem() - single: PyTuple_SetItem() + single: PyList_SetItem (C function) + single: PyTuple_SetItem (C function) Few functions steal references; the two notable exceptions are :c:func:`PyList_SetItem` and :c:func:`PyTuple_SetItem`, which steal a reference @@ -491,8 +491,8 @@ using :c:func:`PySequence_GetItem` (which happens to take exactly the same arguments), you do own a reference to the returned object. .. index:: - single: PyList_GetItem() - single: PySequence_GetItem() + single: PyList_GetItem (C function) + single: PySequence_GetItem (C function) Here is an example of how you could write a function that computes the sum of the items in a list of integers; once using :c:func:`PyList_GetItem`, and once @@ -587,7 +587,7 @@ caller, then to the caller's caller, and so on, until they reach the top-level interpreter, where they are reported to the user accompanied by a stack traceback. -.. index:: single: PyErr_Occurred() +.. index:: single: PyErr_Occurred (C function) For C programmers, however, error checking always has to be explicit. All functions in the Python/C API can raise exceptions, unless an explicit claim is @@ -601,8 +601,8 @@ ambiguous return value, and require explicit testing for errors with :c:func:`PyErr_Occurred`. These exceptions are always explicitly documented. .. index:: - single: PyErr_SetString() - single: PyErr_Clear() + single: PyErr_SetString (C function) + single: PyErr_Clear (C function) Exception state is maintained in per-thread storage (this is equivalent to using global storage in an unthreaded application). A thread can be in one of @@ -624,7 +624,7 @@ an exception is being passed on between C functions until it reaches the Python bytecode interpreter's main loop, which takes care of transferring it to ``sys.exc_info()`` and friends. -.. index:: single: exc_info() (in module sys) +.. index:: single: exc_info (in module sys) Note that starting with Python 1.5, the preferred, thread-safe way to access the exception state from Python code is to call the function :func:`sys.exc_info`, @@ -709,9 +709,9 @@ Here is the corresponding C code, in all its glory:: .. index:: single: incr_item() .. index:: - single: PyErr_ExceptionMatches() - single: PyErr_Clear() - single: Py_XDECREF() + single: PyErr_ExceptionMatches (C function) + single: PyErr_Clear (C function) + single: Py_XDECREF (C function) This example represents an endorsed use of the ``goto`` statement in C! It illustrates the use of :c:func:`PyErr_ExceptionMatches` and @@ -735,7 +735,7 @@ the finalization, of the Python interpreter. Most functionality of the interpreter can only be used after the interpreter has been initialized. .. index:: - single: Py_Initialize() + single: Py_Initialize (C function) pair: module; builtins pair: module; __main__ pair: module; sys @@ -770,10 +770,10 @@ environment variable :envvar:`PYTHONHOME`, or insert additional directories in front of the standard path by setting :envvar:`PYTHONPATH`. .. index:: - single: Py_GetPath() - single: Py_GetPrefix() - single: Py_GetExecPrefix() - single: Py_GetProgramFullPath() + single: Py_GetPath (C function) + single: Py_GetPrefix (C function) + single: Py_GetExecPrefix (C function) + single: Py_GetProgramFullPath (C function) The embedding application can steer the search by setting :c:member:`PyConfig.program_name` *before* calling @@ -784,7 +784,7 @@ control has to provide its own implementation of :c:func:`Py_GetPath`, :c:func:`Py_GetPrefix`, :c:func:`Py_GetExecPrefix`, and :c:func:`Py_GetProgramFullPath` (all defined in :file:`Modules/getpath.c`). -.. index:: single: Py_IsInitialized() +.. index:: single: Py_IsInitialized (C function) Sometimes, it is desirable to "uninitialize" Python. For instance, the application may want to start over (make another call to diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index 045604870d3c840..f42e23db89ae399 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -117,7 +117,7 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. c:function:: long PyLong_AsLong(PyObject *obj) .. index:: - single: LONG_MAX + single: LONG_MAX (C macro) single: OverflowError (built-in exception) Return a C :c:expr:`long` representation of *obj*. If *obj* is not an @@ -210,7 +210,7 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. c:function:: Py_ssize_t PyLong_AsSsize_t(PyObject *pylong) .. index:: - single: PY_SSIZE_T_MAX + single: PY_SSIZE_T_MAX (C macro) single: OverflowError (built-in exception) Return a C :c:type:`Py_ssize_t` representation of *pylong*. *pylong* must @@ -225,7 +225,7 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. c:function:: unsigned long PyLong_AsUnsignedLong(PyObject *pylong) .. index:: - single: ULONG_MAX + single: ULONG_MAX (C macro) single: OverflowError (built-in exception) Return a C :c:expr:`unsigned long` representation of *pylong*. *pylong* @@ -241,7 +241,7 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. c:function:: size_t PyLong_AsSize_t(PyObject *pylong) .. index:: - single: SIZE_MAX + single: SIZE_MAX (C macro) single: OverflowError (built-in exception) Return a C :c:type:`size_t` representation of *pylong*. *pylong* must be diff --git a/Doc/c-api/memory.rst b/Doc/c-api/memory.rst index c05282ffc595219..9da09a21607f617 100644 --- a/Doc/c-api/memory.rst +++ b/Doc/c-api/memory.rst @@ -41,10 +41,10 @@ buffers is performed on demand by the Python memory manager through the Python/C API functions listed in this document. .. index:: - single: malloc() - single: calloc() - single: realloc() - single: free() + single: malloc (C function) + single: calloc (C function) + single: realloc (C function) + single: free (C function) To avoid memory corruption, extension writers should never try to operate on Python objects with the functions exported by the C library: :c:func:`malloc`, diff --git a/Doc/c-api/structures.rst b/Doc/c-api/structures.rst index 0032da9659636c5..77f2b6991d770eb 100644 --- a/Doc/c-api/structures.rst +++ b/Doc/c-api/structures.rst @@ -561,9 +561,9 @@ The following flags can be used with :c:member:`PyMemberDef.flags`: :c:member:`PyMemberDef.offset` to the offset from the ``PyObject`` struct. .. index:: - single: READ_RESTRICTED - single: WRITE_RESTRICTED - single: RESTRICTED + single: READ_RESTRICTED (C macro) + single: WRITE_RESTRICTED (C macro) + single: RESTRICTED (C macro) .. versionchanged:: 3.10 @@ -574,7 +574,7 @@ The following flags can be used with :c:member:`PyMemberDef.flags`: :c:macro:`Py_AUDIT_READ`; :c:macro:`!WRITE_RESTRICTED` does nothing. .. index:: - single: READONLY + single: READONLY (C macro) .. versionchanged:: 3.12 @@ -637,24 +637,24 @@ Macro name C type Python type Reading a ``NULL`` pointer raises :py:exc:`AttributeError`. .. index:: - single: T_BYTE - single: T_SHORT - single: T_INT - single: T_LONG - single: T_LONGLONG - single: T_UBYTE - single: T_USHORT - single: T_UINT - single: T_ULONG - single: T_ULONGULONG - single: T_PYSSIZET - single: T_FLOAT - single: T_DOUBLE - single: T_BOOL - single: T_CHAR - single: T_STRING - single: T_STRING_INPLACE - single: T_OBJECT_EX + single: T_BYTE (C macro) + single: T_SHORT (C macro) + single: T_INT (C macro) + single: T_LONG (C macro) + single: T_LONGLONG (C macro) + single: T_UBYTE (C macro) + single: T_USHORT (C macro) + single: T_UINT (C macro) + single: T_ULONG (C macro) + single: T_ULONGULONG (C macro) + single: T_PYSSIZET (C macro) + single: T_FLOAT (C macro) + single: T_DOUBLE (C macro) + single: T_BOOL (C macro) + single: T_CHAR (C macro) + single: T_STRING (C macro) + single: T_STRING_INPLACE (C macro) + single: T_OBJECT_EX (C macro) single: structmember.h .. versionadded:: 3.12 diff --git a/Doc/c-api/sys.rst b/Doc/c-api/sys.rst index e3c54b075114ff7..35969b30120d2a9 100644 --- a/Doc/c-api/sys.rst +++ b/Doc/c-api/sys.rst @@ -371,7 +371,7 @@ Process Control .. c:function:: void Py_FatalError(const char *message) - .. index:: single: abort() + .. index:: single: abort (C function) Print a fatal error message and kill the process. No cleanup is performed. This function should only be invoked when a condition is detected that would @@ -391,8 +391,8 @@ Process Control .. c:function:: void Py_Exit(int status) .. index:: - single: Py_FinalizeEx() - single: exit() + single: Py_FinalizeEx (C function) + single: exit (C function) Exit the current process. This calls :c:func:`Py_FinalizeEx` and then calls the standard C library function ``exit(status)``. If :c:func:`Py_FinalizeEx` @@ -405,7 +405,7 @@ Process Control .. c:function:: int Py_AtExit(void (*func) ()) .. index:: - single: Py_FinalizeEx() + single: Py_FinalizeEx (C function) single: cleanup functions Register a cleanup function to be called by :c:func:`Py_FinalizeEx`. The cleanup diff --git a/Doc/c-api/veryhigh.rst b/Doc/c-api/veryhigh.rst index 324518c035096b4..67167444d0a6852 100644 --- a/Doc/c-api/veryhigh.rst +++ b/Doc/c-api/veryhigh.rst @@ -322,7 +322,7 @@ the same library that the Python runtime is using. .. c:var:: int Py_eval_input - .. index:: single: Py_CompileString() + .. index:: single: Py_CompileString (C function) The start symbol from the Python grammar for isolated expressions; for use with :c:func:`Py_CompileString`. @@ -330,7 +330,7 @@ the same library that the Python runtime is using. .. c:var:: int Py_file_input - .. index:: single: Py_CompileString() + .. index:: single: Py_CompileString (C function) The start symbol from the Python grammar for sequences of statements as read from a file or other source; for use with :c:func:`Py_CompileString`. This is @@ -339,7 +339,7 @@ the same library that the Python runtime is using. .. c:var:: int Py_single_input - .. index:: single: Py_CompileString() + .. index:: single: Py_CompileString (C function) The start symbol from the Python grammar for a single statement; for use with :c:func:`Py_CompileString`. This is the symbol used for the interactive diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 745fc10a22d1612..b70e1b1fe57e67c 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -547,7 +547,7 @@ reference count of an object and are safe in the presence of ``NULL`` pointers (but note that *temp* will not be ``NULL`` in this context). More info on them in section :ref:`refcounts`. -.. index:: single: PyObject_CallObject() +.. index:: single: PyObject_CallObject (C function) Later, when it is time to call the function, you call the C function :c:func:`PyObject_CallObject`. This function has two arguments, both pointers to @@ -638,7 +638,7 @@ the above example, we use :c:func:`Py_BuildValue` to construct the dictionary. : Extracting Parameters in Extension Functions ============================================ -.. index:: single: PyArg_ParseTuple() +.. index:: single: PyArg_ParseTuple (C function) The :c:func:`PyArg_ParseTuple` function is declared as follows:: @@ -730,7 +730,7 @@ Some example calls:: Keyword Parameters for Extension Functions ========================================== -.. index:: single: PyArg_ParseTupleAndKeywords() +.. index:: single: PyArg_ParseTupleAndKeywords (C function) The :c:func:`PyArg_ParseTupleAndKeywords` function is declared as follows:: diff --git a/Doc/extending/newtypes.rst b/Doc/extending/newtypes.rst index 7a92b3257c6cd31..473a418809cff10 100644 --- a/Doc/extending/newtypes.rst +++ b/Doc/extending/newtypes.rst @@ -89,8 +89,8 @@ If your type supports garbage collection, the destructor should call } .. index:: - single: PyErr_Fetch() - single: PyErr_Restore() + single: PyErr_Fetch (C function) + single: PyErr_Restore (C function) One important requirement of the deallocator function is that it leaves any pending exceptions alone. This is important since deallocators are frequently diff --git a/Doc/library/re.rst b/Doc/library/re.rst index 0a8c88b50cdeec5..a5bd5c73f2fac71 100644 --- a/Doc/library/re.rst +++ b/Doc/library/re.rst @@ -1597,7 +1597,7 @@ To find out what card the pair consists of, one could use the Simulating scanf() ^^^^^^^^^^^^^^^^^^ -.. index:: single: scanf() +.. index:: single: scanf (C function) Python does not currently have an equivalent to :c:func:`!scanf`. Regular expressions are generally more powerful, though also more verbose, than From aeffc7f8951e04258f0fd8cadfa6cd8b704730f6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 11 Feb 2024 12:24:13 +0200 Subject: [PATCH 209/507] gh-79382: Fix recursive glob() with trailing "**" (GH-115134) Trailing "**" no longer allows to match files and non-existing paths in recursive glob(). --- Lib/glob.py | 3 ++- Lib/test/test_glob.py | 11 +++++++++++ .../2024-02-07-12-37-52.gh-issue-79382.Yz_5WB.rst | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-07-12-37-52.gh-issue-79382.Yz_5WB.rst diff --git a/Lib/glob.py b/Lib/glob.py index 4a335a10766cf40..343be78a73b20a3 100644 --- a/Lib/glob.py +++ b/Lib/glob.py @@ -132,7 +132,8 @@ def glob1(dirname, pattern): def _glob2(dirname, pattern, dir_fd, dironly, include_hidden=False): assert _isrecursive(pattern) - yield pattern[:0] + if not dirname or _isdir(dirname, dir_fd): + yield pattern[:0] yield from _rlistdir(dirname, dir_fd, dironly, include_hidden=include_hidden) diff --git a/Lib/test/test_glob.py b/Lib/test/test_glob.py index aa5fac8eca13542..8b2ea8f89f5dafb 100644 --- a/Lib/test/test_glob.py +++ b/Lib/test/test_glob.py @@ -333,6 +333,17 @@ def test_recursive_glob(self): eq(glob.glob('**', recursive=True, include_hidden=True), [join(*i) for i in full+rec]) + def test_glob_non_directory(self): + eq = self.assertSequencesEqual_noorder + eq(self.rglob('EF'), self.joins(('EF',))) + eq(self.rglob('EF', ''), []) + eq(self.rglob('EF', '*'), []) + eq(self.rglob('EF', '**'), []) + eq(self.rglob('nonexistent'), []) + eq(self.rglob('nonexistent', ''), []) + eq(self.rglob('nonexistent', '*'), []) + eq(self.rglob('nonexistent', '**'), []) + def test_glob_many_open_files(self): depth = 30 base = os.path.join(self.tempdir, 'deep') diff --git a/Misc/NEWS.d/next/Library/2024-02-07-12-37-52.gh-issue-79382.Yz_5WB.rst b/Misc/NEWS.d/next/Library/2024-02-07-12-37-52.gh-issue-79382.Yz_5WB.rst new file mode 100644 index 000000000000000..5eb1888943186a5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-07-12-37-52.gh-issue-79382.Yz_5WB.rst @@ -0,0 +1,2 @@ +Trailing ``**`` no longer allows to match files and non-existing paths in +recursive :func:`~glob.glob`. From bf75f1b147b8cc4c5506df7c4bb30b9950ceda1a Mon Sep 17 00:00:00 2001 From: Soumendra Ganguly <67527439+8vasu@users.noreply.github.com> Date: Sun, 11 Feb 2024 11:29:44 +0100 Subject: [PATCH 210/507] gh-85984: Add _POSIX_VDISABLE from unistd.h to termios module. (#114985) Signed-off-by: Soumendra Ganguly <soumendraganguly@gmail.com> Co-authored-by: Gregory P. Smith <greg@krypto.org> --- .../Library/2024-02-04-02-28-37.gh-issue-85984.NHZVTQ.rst | 1 + Modules/termios.c | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-04-02-28-37.gh-issue-85984.NHZVTQ.rst diff --git a/Misc/NEWS.d/next/Library/2024-02-04-02-28-37.gh-issue-85984.NHZVTQ.rst b/Misc/NEWS.d/next/Library/2024-02-04-02-28-37.gh-issue-85984.NHZVTQ.rst new file mode 100644 index 000000000000000..bfa7e676f923067 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-04-02-28-37.gh-issue-85984.NHZVTQ.rst @@ -0,0 +1 @@ +Added ``_POSIX_VDISABLE`` from C's ``<unistd.h>`` to :mod:`termios`. diff --git a/Modules/termios.c b/Modules/termios.c index 69dbd88be5fcc20..4635fefb8f3f5ae 100644 --- a/Modules/termios.c +++ b/Modules/termios.c @@ -27,9 +27,7 @@ #include <termios.h> #include <sys/ioctl.h> -#if defined(__sun) && defined(__SVR4) -# include <unistd.h> // ioctl() -#endif +#include <unistd.h> // _POSIX_VDISABLE /* HP-UX requires that this be included to pick up MDCD, MCTS, MDSR, * MDTR, MRI, and MRTS (apparently used internally by some things @@ -1315,6 +1313,9 @@ static struct constant { #ifdef TIOCTTYGSTRUCT {"TIOCTTYGSTRUCT", TIOCTTYGSTRUCT}, #endif +#ifdef _POSIX_VDISABLE + {"_POSIX_VDISABLE", _POSIX_VDISABLE}, +#endif /* sentinel */ {NULL, 0} From 5d2794a16bc1639e6053300c08a78d60526aadf2 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 11 Feb 2024 12:38:07 +0200 Subject: [PATCH 211/507] gh-67837, gh-112998: Fix dirs creation in concurrent extraction (GH-115082) Avoid race conditions in the creation of directories during concurrent extraction in tarfile and zipfile. Co-authored-by: Samantha Hughes <shughes-uk@users.noreply.github.com> Co-authored-by: Peder Bergebakken Sundt <pbsds@hotmail.com> --- Lib/tarfile.py | 2 +- Lib/test/archiver_tests.py | 22 +++++++++++++++++++ Lib/zipfile/__init__.py | 8 +++++-- ...4-02-06-15-16-28.gh-issue-67837._JKa73.rst | 2 ++ 4 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-06-15-16-28.gh-issue-67837._JKa73.rst diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 9775040cbe372cd..f4dd0fdab4a3e4d 100755 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -2411,7 +2411,7 @@ def _extract_member(self, tarinfo, targetpath, set_attrs=True, if upperdirs and not os.path.exists(upperdirs): # Create directories that are not part of the archive with # default permissions. - os.makedirs(upperdirs) + os.makedirs(upperdirs, exist_ok=True) if tarinfo.islnk() or tarinfo.issym(): self._dbg(1, "%s -> %s" % (tarinfo.name, tarinfo.linkname)) diff --git a/Lib/test/archiver_tests.py b/Lib/test/archiver_tests.py index 1a4bbb9e5706c53..24745941b089239 100644 --- a/Lib/test/archiver_tests.py +++ b/Lib/test/archiver_tests.py @@ -3,6 +3,7 @@ import os import sys +from test.support import swap_attr from test.support import os_helper class OverwriteTests: @@ -153,3 +154,24 @@ def test_overwrite_broken_dir_symlink_as_implicit_dir(self): self.extractall(ar) self.assertTrue(os.path.islink(target)) self.assertFalse(os.path.exists(target2)) + + def test_concurrent_extract_dir(self): + target = os.path.join(self.testdir, 'test') + def concurrent_mkdir(*args, **kwargs): + orig_mkdir(*args, **kwargs) + orig_mkdir(*args, **kwargs) + with swap_attr(os, 'mkdir', concurrent_mkdir) as orig_mkdir: + with self.open(self.ar_with_dir) as ar: + self.extractall(ar) + self.assertTrue(os.path.isdir(target)) + + def test_concurrent_extract_implicit_dir(self): + target = os.path.join(self.testdir, 'test') + def concurrent_mkdir(*args, **kwargs): + orig_mkdir(*args, **kwargs) + orig_mkdir(*args, **kwargs) + with swap_attr(os, 'mkdir', concurrent_mkdir) as orig_mkdir: + with self.open(self.ar_with_implicit_dir) as ar: + self.extractall(ar) + self.assertTrue(os.path.isdir(target)) + self.assertTrue(os.path.isfile(os.path.join(target, 'file'))) diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py index 8005b4b34ccf76c..cc08f602fe44e05 100644 --- a/Lib/zipfile/__init__.py +++ b/Lib/zipfile/__init__.py @@ -1802,11 +1802,15 @@ def _extract_member(self, member, targetpath, pwd): # Create all upper directories if necessary. upperdirs = os.path.dirname(targetpath) if upperdirs and not os.path.exists(upperdirs): - os.makedirs(upperdirs) + os.makedirs(upperdirs, exist_ok=True) if member.is_dir(): if not os.path.isdir(targetpath): - os.mkdir(targetpath) + try: + os.mkdir(targetpath) + except FileExistsError: + if not os.path.isdir(targetpath): + raise return targetpath with self.open(member, pwd=pwd) as source, \ diff --git a/Misc/NEWS.d/next/Library/2024-02-06-15-16-28.gh-issue-67837._JKa73.rst b/Misc/NEWS.d/next/Library/2024-02-06-15-16-28.gh-issue-67837._JKa73.rst new file mode 100644 index 000000000000000..340b65f18839427 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-06-15-16-28.gh-issue-67837._JKa73.rst @@ -0,0 +1,2 @@ +Avoid race conditions in the creation of directories during concurrent +extraction in :mod:`tarfile` and :mod:`zipfile`. From d2c4baa41ff93cd5695c201d40e20a88458ecc26 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 11 Feb 2024 12:43:14 +0200 Subject: [PATCH 212/507] gh-97928: Partially restore the behavior of tkinter.Text.count() by default (GH-115031) By default, it preserves an inconsistent behavior of older Python versions: packs the count into a 1-tuple if only one or none options are specified (including 'update'), returns None instead of 0. Except that setting wantobjects to 0 no longer affects the result. Add a new parameter return_ints: specifying return_ints=True makes Text.count() always returning the single count as an integer instead of a 1-tuple or None. --- Doc/whatsnew/3.13.rst | 13 +++--- Lib/idlelib/sidebar.py | 2 +- Lib/test/test_tkinter/test_text.py | 44 ++++++++++++++----- Lib/tkinter/__init__.py | 23 ++++++---- ...4-02-05-16-48-06.gh-issue-97928.JZCies.rst | 5 +++ 5 files changed, 59 insertions(+), 28 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-05-16-48-06.gh-issue-97928.JZCies.rst diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index aee37737a9990ac..1b803278ae0d5bd 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -469,6 +469,12 @@ tkinter a dict instead of a tuple. (Contributed by Serhiy Storchaka in :gh:`43457`.) +* Add new optional keyword-only parameter *return_ints* in + the :meth:`!Text.count` method. + Passing ``return_ints=True`` makes it always returning the single count + as an integer instead of a 1-tuple or ``None``. + (Contributed by Serhiy Storchaka in :gh:`97928`.) + * Add support of the "vsapi" element type in the :meth:`~tkinter.ttk.Style.element_create` method of :class:`tkinter.ttk.Style`. @@ -1286,13 +1292,6 @@ that may require changes to your code. Changes in the Python API ------------------------- -* :meth:`!tkinter.Text.count` now always returns an integer if one or less - counting options are specified. - Previously it could return a single count as a 1-tuple, an integer (only if - option ``"update"`` was specified) or ``None`` if no items found. - The result is now the same if ``wantobjects`` is set to ``0``. - (Contributed by Serhiy Storchaka in :gh:`97928`.) - * Functions :c:func:`PyDict_GetItem`, :c:func:`PyDict_GetItemString`, :c:func:`PyMapping_HasKey`, :c:func:`PyMapping_HasKeyString`, :c:func:`PyObject_HasAttr`, :c:func:`PyObject_HasAttrString`, and diff --git a/Lib/idlelib/sidebar.py b/Lib/idlelib/sidebar.py index ff77b568a786e0f..aa19a24e3edef28 100644 --- a/Lib/idlelib/sidebar.py +++ b/Lib/idlelib/sidebar.py @@ -27,7 +27,7 @@ def get_displaylines(text, index): """Display height, in lines, of a logical line in a Tk text widget.""" return text.count(f"{index} linestart", f"{index} lineend", - "displaylines") + "displaylines", return_ints=True) def get_widget_padding(widget): """Get the total padding of a Tk widget, including its border.""" diff --git a/Lib/test/test_tkinter/test_text.py b/Lib/test/test_tkinter/test_text.py index f809c4510e3a1f3..b26956930d34028 100644 --- a/Lib/test/test_tkinter/test_text.py +++ b/Lib/test/test_tkinter/test_text.py @@ -52,27 +52,47 @@ def test_count(self): options = ('chars', 'indices', 'lines', 'displaychars', 'displayindices', 'displaylines', 'xpixels', 'ypixels') + self.assertEqual(len(text.count('1.0', 'end', *options, return_ints=True)), 8) self.assertEqual(len(text.count('1.0', 'end', *options)), 8) - self.assertEqual(text.count('1.0', 'end', 'chars', 'lines'), (124, 4)) + self.assertEqual(text.count('1.0', 'end', 'chars', 'lines', return_ints=True), + (124, 4)) self.assertEqual(text.count('1.3', '4.5', 'chars', 'lines'), (92, 3)) + self.assertEqual(text.count('4.5', '1.3', 'chars', 'lines', return_ints=True), + (-92, -3)) self.assertEqual(text.count('4.5', '1.3', 'chars', 'lines'), (-92, -3)) + self.assertEqual(text.count('1.3', '1.3', 'chars', 'lines', return_ints=True), + (0, 0)) self.assertEqual(text.count('1.3', '1.3', 'chars', 'lines'), (0, 0)) - self.assertEqual(text.count('1.0', 'end', 'lines'), 4) - self.assertEqual(text.count('end', '1.0', 'lines'), -4) - self.assertEqual(text.count('1.3', '1.5', 'lines'), 0) - self.assertEqual(text.count('1.3', '1.3', 'lines'), 0) - self.assertEqual(text.count('1.0', 'end'), 124) # 'indices' by default - self.assertEqual(text.count('1.0', 'end', 'indices'), 124) + self.assertEqual(text.count('1.0', 'end', 'lines', return_ints=True), 4) + self.assertEqual(text.count('1.0', 'end', 'lines'), (4,)) + self.assertEqual(text.count('end', '1.0', 'lines', return_ints=True), -4) + self.assertEqual(text.count('end', '1.0', 'lines'), (-4,)) + self.assertEqual(text.count('1.3', '1.5', 'lines', return_ints=True), 0) + self.assertEqual(text.count('1.3', '1.5', 'lines'), None) + self.assertEqual(text.count('1.3', '1.3', 'lines', return_ints=True), 0) + self.assertEqual(text.count('1.3', '1.3', 'lines'), None) + # Count 'indices' by default. + self.assertEqual(text.count('1.0', 'end', return_ints=True), 124) + self.assertEqual(text.count('1.0', 'end'), (124,)) + self.assertEqual(text.count('1.0', 'end', 'indices', return_ints=True), 124) + self.assertEqual(text.count('1.0', 'end', 'indices'), (124,)) self.assertRaises(tkinter.TclError, text.count, '1.0', 'end', 'spam') self.assertRaises(tkinter.TclError, text.count, '1.0', 'end', '-lines') - self.assertIsInstance(text.count('1.3', '1.5', 'ypixels'), int) + self.assertIsInstance(text.count('1.3', '1.5', 'ypixels', return_ints=True), int) + self.assertIsInstance(text.count('1.3', '1.5', 'ypixels'), tuple) + self.assertIsInstance(text.count('1.3', '1.5', 'update', 'ypixels', return_ints=True), int) self.assertIsInstance(text.count('1.3', '1.5', 'update', 'ypixels'), int) - self.assertEqual(text.count('1.3', '1.3', 'update', 'ypixels'), 0) + self.assertEqual(text.count('1.3', '1.3', 'update', 'ypixels', return_ints=True), 0) + self.assertEqual(text.count('1.3', '1.3', 'update', 'ypixels'), None) + self.assertEqual(text.count('1.3', '1.5', 'update', 'indices', return_ints=True), 2) self.assertEqual(text.count('1.3', '1.5', 'update', 'indices'), 2) - self.assertEqual(text.count('1.3', '1.3', 'update', 'indices'), 0) - self.assertEqual(text.count('1.3', '1.5', 'update'), 2) - self.assertEqual(text.count('1.3', '1.3', 'update'), 0) + self.assertEqual(text.count('1.3', '1.3', 'update', 'indices', return_ints=True), 0) + self.assertEqual(text.count('1.3', '1.3', 'update', 'indices'), None) + self.assertEqual(text.count('1.3', '1.5', 'update', return_ints=True), 2) + self.assertEqual(text.count('1.3', '1.5', 'update'), (2,)) + self.assertEqual(text.count('1.3', '1.3', 'update', return_ints=True), 0) + self.assertEqual(text.count('1.3', '1.3', 'update'), None) if __name__ == "__main__": diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index 2be9da2cfb92993..175bfbd7d912d2e 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -3745,7 +3745,7 @@ def compare(self, index1, op, index2): return self.tk.getboolean(self.tk.call( self._w, 'compare', index1, op, index2)) - def count(self, index1, index2, *options): # new in Tk 8.5 + def count(self, index1, index2, *options, return_ints=False): # new in Tk 8.5 """Counts the number of relevant things between the two indices. If INDEX1 is after INDEX2, the result will be a negative number @@ -3753,19 +3753,26 @@ def count(self, index1, index2, *options): # new in Tk 8.5 The actual items which are counted depends on the options given. The result is a tuple of integers, one for the result of each - counting option given, if more than one option is specified, - otherwise it is an integer. Valid counting options are "chars", - "displaychars", "displayindices", "displaylines", "indices", - "lines", "xpixels" and "ypixels". The default value, if no - option is specified, is "indices". There is an additional possible - option "update", which if given then all subsequent options ensure - that any possible out of date information is recalculated.""" + counting option given, if more than one option is specified or + return_ints is false (default), otherwise it is an integer. + Valid counting options are "chars", "displaychars", + "displayindices", "displaylines", "indices", "lines", "xpixels" + and "ypixels". The default value, if no option is specified, is + "indices". There is an additional possible option "update", + which if given then all subsequent options ensure that any + possible out of date information is recalculated. + """ options = ['-%s' % arg for arg in options] res = self.tk.call(self._w, 'count', *options, index1, index2) if not isinstance(res, int): res = self._getints(res) if len(res) == 1: res, = res + if not return_ints: + if not res: + res = None + elif len(options) <= 1: + res = (res,) return res def debug(self, boolean=None): diff --git a/Misc/NEWS.d/next/Library/2024-02-05-16-48-06.gh-issue-97928.JZCies.rst b/Misc/NEWS.d/next/Library/2024-02-05-16-48-06.gh-issue-97928.JZCies.rst new file mode 100644 index 000000000000000..24fed926a955133 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-05-16-48-06.gh-issue-97928.JZCies.rst @@ -0,0 +1,5 @@ +Partially revert the behavior of :meth:`tkinter.Text.count`. By default it +preserves the behavior of older Python versions, except that setting +``wantobjects`` to 0 no longer has effect. Add a new parameter *return_ints*: +specifying ``return_ints=True`` makes ``Text.count()`` always returning the +single count as an integer instead of a 1-tuple or ``None``. From d9d6909697501a2604d5895f9f88aeec61274ab0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 11 Feb 2024 12:45:58 +0200 Subject: [PATCH 213/507] gh-115011: Improve support of __index__() in setters of members with unsigned integer type (GH-115029) Setters for members with an unsigned integer type now support the same range of valid values for objects that has a __index__() method as for int. Previously, Py_T_UINT, Py_T_ULONG and Py_T_ULLONG did not support objects that has a __index__() method larger than LONG_MAX. Py_T_ULLONG did not support negative ints. Now it supports them and emits a RuntimeWarning. --- Lib/test/test_capi/test_structmembers.py | 44 ++++------ ...-02-05-12-40-26.gh-issue-115011.L1AKF5.rst | 3 + Python/structmember.c | 81 ++++++++++--------- 3 files changed, 61 insertions(+), 67 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-05-12-40-26.gh-issue-115011.L1AKF5.rst diff --git a/Lib/test/test_capi/test_structmembers.py b/Lib/test/test_capi/test_structmembers.py index a294c3b13a5c30c..08ca1f828529cf2 100644 --- a/Lib/test/test_capi/test_structmembers.py +++ b/Lib/test/test_capi/test_structmembers.py @@ -81,36 +81,22 @@ def _test_int_range(self, name, minval, maxval, *, hardlimit=None, self._test_warn(name, maxval+1, minval) self._test_warn(name, hardmaxval) - if indexlimit is None: - indexlimit = hardlimit - if not indexlimit: + if indexlimit is False: self.assertRaises(TypeError, setattr, ts, name, Index(minval)) self.assertRaises(TypeError, setattr, ts, name, Index(maxval)) else: - hardminindexval, hardmaxindexval = indexlimit self._test_write(name, Index(minval), minval) - if minval < hardminindexval: - self._test_write(name, Index(hardminindexval), hardminindexval) - if maxval < hardmaxindexval: - self._test_write(name, Index(maxval), maxval) - else: - self._test_write(name, Index(hardmaxindexval), hardmaxindexval) - self._test_overflow(name, Index(hardminindexval-1)) - if name in ('T_UINT', 'T_ULONG'): - self.assertRaises(TypeError, setattr, self.ts, name, - Index(hardmaxindexval+1)) - self.assertRaises(TypeError, setattr, self.ts, name, - Index(2**1000)) - else: - self._test_overflow(name, Index(hardmaxindexval+1)) - self._test_overflow(name, Index(2**1000)) + self._test_write(name, Index(maxval), maxval) + self._test_overflow(name, Index(hardminval-1)) + self._test_overflow(name, Index(hardmaxval+1)) + self._test_overflow(name, Index(2**1000)) self._test_overflow(name, Index(-2**1000)) - if hardminindexval < minval and name != 'T_ULONGLONG': - self._test_warn(name, Index(hardminindexval)) - self._test_warn(name, Index(minval-1)) - if maxval < hardmaxindexval: - self._test_warn(name, Index(maxval+1)) - self._test_warn(name, Index(hardmaxindexval)) + if hardminval < minval: + self._test_warn(name, Index(hardminval)) + self._test_warn(name, Index(minval-1), maxval) + if maxval < hardmaxval: + self._test_warn(name, Index(maxval+1), minval) + self._test_warn(name, Index(hardmaxval)) def test_bool(self): ts = self.ts @@ -138,14 +124,12 @@ def test_int(self): self._test_int_range('T_INT', INT_MIN, INT_MAX, hardlimit=(LONG_MIN, LONG_MAX)) self._test_int_range('T_UINT', 0, UINT_MAX, - hardlimit=(LONG_MIN, ULONG_MAX), - indexlimit=(LONG_MIN, LONG_MAX)) + hardlimit=(LONG_MIN, ULONG_MAX)) def test_long(self): self._test_int_range('T_LONG', LONG_MIN, LONG_MAX) self._test_int_range('T_ULONG', 0, ULONG_MAX, - hardlimit=(LONG_MIN, ULONG_MAX), - indexlimit=(LONG_MIN, LONG_MAX)) + hardlimit=(LONG_MIN, ULONG_MAX)) def test_py_ssize_t(self): self._test_int_range('T_PYSSIZET', PY_SSIZE_T_MIN, PY_SSIZE_T_MAX, indexlimit=False) @@ -153,7 +137,7 @@ def test_py_ssize_t(self): def test_longlong(self): self._test_int_range('T_LONGLONG', LLONG_MIN, LLONG_MAX) self._test_int_range('T_ULONGLONG', 0, ULLONG_MAX, - indexlimit=(LONG_MIN, LONG_MAX)) + hardlimit=(LONG_MIN, ULLONG_MAX)) def test_bad_assignments(self): ts = self.ts diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-05-12-40-26.gh-issue-115011.L1AKF5.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-05-12-40-26.gh-issue-115011.L1AKF5.rst new file mode 100644 index 000000000000000..cf91a4f818bd449 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-05-12-40-26.gh-issue-115011.L1AKF5.rst @@ -0,0 +1,3 @@ +Setters for members with an unsigned integer type now support the same range +of valid values for objects that has a :meth:`~object.__index__` method as +for :class:`int`. diff --git a/Python/structmember.c b/Python/structmember.c index c9f03a464078d0b..ba881d18a0973dd 100644 --- a/Python/structmember.c +++ b/Python/structmember.c @@ -2,6 +2,8 @@ /* Map C struct members to Python object attributes */ #include "Python.h" +#include "pycore_abstract.h" // _PyNumber_Index() +#include "pycore_long.h" // _PyLong_IsNegative() PyObject * @@ -200,27 +202,22 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v) case Py_T_UINT: { /* XXX: For compatibility, accept negative int values as well. */ - int overflow; - long long_val = PyLong_AsLongAndOverflow(v, &overflow); - if (long_val == -1 && PyErr_Occurred()) { - return -1; - } - if (overflow < 0) { - PyErr_SetString(PyExc_OverflowError, - "Python int too large to convert to C long"); + v = _PyNumber_Index(v); + if (v == NULL) { return -1; } - else if (!overflow) { - *(unsigned int *)addr = (unsigned int)(unsigned long)long_val; - if (long_val < 0) { - WARN("Writing negative value into unsigned field"); - } - else if ((unsigned long)long_val > UINT_MAX) { - WARN("Truncation of value to unsigned short"); + if (_PyLong_IsNegative((PyLongObject *)v)) { + long long_val = PyLong_AsLong(v); + Py_DECREF(v); + if (long_val == -1 && PyErr_Occurred()) { + return -1; } + *(unsigned int *)addr = (unsigned int)(unsigned long)long_val; + WARN("Writing negative value into unsigned field"); } else { unsigned long ulong_val = PyLong_AsUnsignedLong(v); + Py_DECREF(v); if (ulong_val == (unsigned long)-1 && PyErr_Occurred()) { return -1; } @@ -240,24 +237,22 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v) case Py_T_ULONG: { /* XXX: For compatibility, accept negative int values as well. */ - int overflow; - long long_val = PyLong_AsLongAndOverflow(v, &overflow); - if (long_val == -1 && PyErr_Occurred()) { - return -1; - } - if (overflow < 0) { - PyErr_SetString(PyExc_OverflowError, - "Python int too large to convert to C long"); + v = _PyNumber_Index(v); + if (v == NULL) { return -1; } - else if (!overflow) { - *(unsigned long *)addr = (unsigned long)long_val; - if (long_val < 0) { - WARN("Writing negative value into unsigned field"); + if (_PyLong_IsNegative((PyLongObject *)v)) { + long long_val = PyLong_AsLong(v); + Py_DECREF(v); + if (long_val == -1 && PyErr_Occurred()) { + return -1; } + *(unsigned long *)addr = (unsigned long)long_val; + WARN("Writing negative value into unsigned field"); } else { unsigned long ulong_val = PyLong_AsUnsignedLong(v); + Py_DECREF(v); if (ulong_val == (unsigned long)-1 && PyErr_Occurred()) { return -1; } @@ -313,18 +308,30 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v) return -1; break; } - case Py_T_ULONGLONG:{ - unsigned long long value; - /* ??? PyLong_AsLongLong accepts an int, but PyLong_AsUnsignedLongLong - doesn't ??? */ - if (PyLong_Check(v)) - *(unsigned long long*)addr = value = PyLong_AsUnsignedLongLong(v); - else - *(unsigned long long*)addr = value = PyLong_AsLong(v); - if ((value == (unsigned long long)-1) && PyErr_Occurred()) + case Py_T_ULONGLONG: { + v = _PyNumber_Index(v); + if (v == NULL) { return -1; - break; } + if (_PyLong_IsNegative((PyLongObject *)v)) { + long long_val = PyLong_AsLong(v); + Py_DECREF(v); + if (long_val == -1 && PyErr_Occurred()) { + return -1; + } + *(unsigned long long *)addr = (unsigned long long)(long long)long_val; + WARN("Writing negative value into unsigned field"); + } + else { + unsigned long long ulonglong_val = PyLong_AsUnsignedLongLong(v); + Py_DECREF(v); + if (ulonglong_val == (unsigned long long)-1 && PyErr_Occurred()) { + return -1; + } + *(unsigned long long*)addr = ulonglong_val; + } + break; + } default: PyErr_Format(PyExc_SystemError, "bad memberdescr type for %s", l->name); From b1043607884d774acabd255ecdcebb159f76a2fb Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 11 Feb 2024 13:06:43 +0200 Subject: [PATCH 214/507] gh-49766: Make date-datetime comparison more symmetric and flexible (GH-114760) Now the special comparison methods like `__eq__` and `__lt__` return NotImplemented if one of comparands is date and other is datetime instead of ignoring the time part and the time zone or forcefully return "not equal" or raise TypeError. It makes comparison of date and datetime subclasses more symmetric and allows to change the default behavior by overriding the special comparison methods in subclasses. It is now the same as if date and datetime was independent classes. --- Doc/library/datetime.rst | 32 ++++++++-- Lib/_pydatetime.py | 35 ++++------ Lib/test/datetimetester.py | 64 +++++++++++-------- ...4-01-30-22-10-50.gh-issue-49766.yulJL_.rst | 8 +++ Modules/_datetimemodule.c | 36 +++-------- 5 files changed, 91 insertions(+), 84 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-30-22-10-50.gh-issue-49766.yulJL_.rst diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 930af6cbbe9e8d1..a46eed35ee23290 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -619,11 +619,27 @@ Notes: (4) :class:`date` objects are equal if they represent the same date. + :class:`!date` objects that are not also :class:`.datetime` instances + are never equal to :class:`!datetime` objects, even if they represent + the same date. + (5) *date1* is considered less than *date2* when *date1* precedes *date2* in time. In other words, ``date1 < date2`` if and only if ``date1.toordinal() < date2.toordinal()``. + Order comparison between a :class:`!date` object that is not also a + :class:`.datetime` instance and a :class:`!datetime` object raises + :exc:`TypeError`. + +.. versionchanged:: 3.13 + Comparison between :class:`.datetime` object and an instance of + the :class:`date` subclass that is not a :class:`!datetime` subclass + no longer coverts the latter to :class:`!date`, ignoring the time part + and the time zone. + The default behavior can be changed by overriding the special comparison + methods in subclasses. + In Boolean contexts, all :class:`date` objects are considered to be true. Instance methods: @@ -1192,9 +1208,6 @@ Supported operations: and time, taking into account the time zone. Naive and aware :class:`!datetime` objects are never equal. - :class:`!datetime` objects are never equal to :class:`date` objects - that are not also :class:`!datetime` instances, even if they represent - the same date. If both comparands are aware and have different :attr:`~.datetime.tzinfo` attributes, the comparison acts as comparands were first converted to UTC @@ -1206,9 +1219,8 @@ Supported operations: *datetime1* is considered less than *datetime2* when *datetime1* precedes *datetime2* in time, taking into account the time zone. - Order comparison between naive and aware :class:`.datetime` objects, - as well as a :class:`!datetime` object and a :class:`!date` object - that is not also a :class:`!datetime` instance, raises :exc:`TypeError`. + Order comparison between naive and aware :class:`.datetime` objects + raises :exc:`TypeError`. If both comparands are aware and have different :attr:`~.datetime.tzinfo` attributes, the comparison acts as comparands were first converted to UTC @@ -1218,6 +1230,14 @@ Supported operations: Equality comparisons between aware and naive :class:`.datetime` instances don't raise :exc:`TypeError`. +.. versionchanged:: 3.13 + Comparison between :class:`.datetime` object and an instance of + the :class:`date` subclass that is not a :class:`!datetime` subclass + no longer coverts the latter to :class:`!date`, ignoring the time part + and the time zone. + The default behavior can be changed by overriding the special comparison + methods in subclasses. + Instance methods: .. method:: datetime.date() diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index 54c12d3b2f3f16a..b7d569cc41740e0 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -556,10 +556,6 @@ def _check_tzinfo_arg(tz): if tz is not None and not isinstance(tz, tzinfo): raise TypeError("tzinfo argument must be None or of a tzinfo subclass") -def _cmperror(x, y): - raise TypeError("can't compare '%s' to '%s'" % ( - type(x).__name__, type(y).__name__)) - def _divide_and_round(a, b): """divide a by b and round result to the nearest integer @@ -1113,32 +1109,33 @@ def replace(self, year=None, month=None, day=None): # Comparisons of date objects with other. def __eq__(self, other): - if isinstance(other, date): + if isinstance(other, date) and not isinstance(other, datetime): return self._cmp(other) == 0 return NotImplemented def __le__(self, other): - if isinstance(other, date): + if isinstance(other, date) and not isinstance(other, datetime): return self._cmp(other) <= 0 return NotImplemented def __lt__(self, other): - if isinstance(other, date): + if isinstance(other, date) and not isinstance(other, datetime): return self._cmp(other) < 0 return NotImplemented def __ge__(self, other): - if isinstance(other, date): + if isinstance(other, date) and not isinstance(other, datetime): return self._cmp(other) >= 0 return NotImplemented def __gt__(self, other): - if isinstance(other, date): + if isinstance(other, date) and not isinstance(other, datetime): return self._cmp(other) > 0 return NotImplemented def _cmp(self, other): assert isinstance(other, date) + assert not isinstance(other, datetime) y, m, d = self._year, self._month, self._day y2, m2, d2 = other._year, other._month, other._day return _cmp((y, m, d), (y2, m2, d2)) @@ -2137,42 +2134,32 @@ def dst(self): def __eq__(self, other): if isinstance(other, datetime): return self._cmp(other, allow_mixed=True) == 0 - elif not isinstance(other, date): - return NotImplemented else: - return False + return NotImplemented def __le__(self, other): if isinstance(other, datetime): return self._cmp(other) <= 0 - elif not isinstance(other, date): - return NotImplemented else: - _cmperror(self, other) + return NotImplemented def __lt__(self, other): if isinstance(other, datetime): return self._cmp(other) < 0 - elif not isinstance(other, date): - return NotImplemented else: - _cmperror(self, other) + return NotImplemented def __ge__(self, other): if isinstance(other, datetime): return self._cmp(other) >= 0 - elif not isinstance(other, date): - return NotImplemented else: - _cmperror(self, other) + return NotImplemented def __gt__(self, other): if isinstance(other, datetime): return self._cmp(other) > 0 - elif not isinstance(other, date): - return NotImplemented else: - _cmperror(self, other) + return NotImplemented def _cmp(self, other, allow_mixed=False): assert isinstance(other, datetime) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 53ad5e57ada0178..980a8e6c1b18369 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -5435,42 +5435,50 @@ def fromutc(self, dt): class Oddballs(unittest.TestCase): - def test_bug_1028306(self): + def test_date_datetime_comparison(self): + # bpo-1028306, bpo-5516 (gh-49766) # Trying to compare a date to a datetime should act like a mixed- # type comparison, despite that datetime is a subclass of date. as_date = date.today() as_datetime = datetime.combine(as_date, time()) - self.assertTrue(as_date != as_datetime) - self.assertTrue(as_datetime != as_date) - self.assertFalse(as_date == as_datetime) - self.assertFalse(as_datetime == as_date) - self.assertRaises(TypeError, lambda: as_date < as_datetime) - self.assertRaises(TypeError, lambda: as_datetime < as_date) - self.assertRaises(TypeError, lambda: as_date <= as_datetime) - self.assertRaises(TypeError, lambda: as_datetime <= as_date) - self.assertRaises(TypeError, lambda: as_date > as_datetime) - self.assertRaises(TypeError, lambda: as_datetime > as_date) - self.assertRaises(TypeError, lambda: as_date >= as_datetime) - self.assertRaises(TypeError, lambda: as_datetime >= as_date) - - # Nevertheless, comparison should work with the base-class (date) - # projection if use of a date method is forced. - self.assertEqual(as_date.__eq__(as_datetime), True) - different_day = (as_date.day + 1) % 20 + 1 - as_different = as_datetime.replace(day= different_day) - self.assertEqual(as_date.__eq__(as_different), False) + date_sc = SubclassDate(as_date.year, as_date.month, as_date.day) + datetime_sc = SubclassDatetime(as_date.year, as_date.month, + as_date.day, 0, 0, 0) + for d in (as_date, date_sc): + for dt in (as_datetime, datetime_sc): + for x, y in (d, dt), (dt, d): + self.assertTrue(x != y) + self.assertFalse(x == y) + self.assertRaises(TypeError, lambda: x < y) + self.assertRaises(TypeError, lambda: x <= y) + self.assertRaises(TypeError, lambda: x > y) + self.assertRaises(TypeError, lambda: x >= y) # And date should compare with other subclasses of date. If a # subclass wants to stop this, it's up to the subclass to do so. - date_sc = SubclassDate(as_date.year, as_date.month, as_date.day) - self.assertEqual(as_date, date_sc) - self.assertEqual(date_sc, as_date) - # Ditto for datetimes. - datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month, - as_date.day, 0, 0, 0) - self.assertEqual(as_datetime, datetime_sc) - self.assertEqual(datetime_sc, as_datetime) + for x, y in ((as_date, date_sc), + (date_sc, as_date), + (as_datetime, datetime_sc), + (datetime_sc, as_datetime)): + self.assertTrue(x == y) + self.assertFalse(x != y) + self.assertFalse(x < y) + self.assertFalse(x > y) + self.assertTrue(x <= y) + self.assertTrue(x >= y) + + # Nevertheless, comparison should work if other object is an instance + # of date or datetime class with overridden comparison operators. + # So special methods should return NotImplemented, as if + # date and datetime were independent classes. + for x, y in (as_date, as_datetime), (as_datetime, as_date): + self.assertEqual(x.__eq__(y), NotImplemented) + self.assertEqual(x.__ne__(y), NotImplemented) + self.assertEqual(x.__lt__(y), NotImplemented) + self.assertEqual(x.__gt__(y), NotImplemented) + self.assertEqual(x.__gt__(y), NotImplemented) + self.assertEqual(x.__ge__(y), NotImplemented) def test_extra_attributes(self): with self.assertWarns(DeprecationWarning): diff --git a/Misc/NEWS.d/next/Library/2024-01-30-22-10-50.gh-issue-49766.yulJL_.rst b/Misc/NEWS.d/next/Library/2024-01-30-22-10-50.gh-issue-49766.yulJL_.rst new file mode 100644 index 000000000000000..eaaa3ba1cb6f093 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-30-22-10-50.gh-issue-49766.yulJL_.rst @@ -0,0 +1,8 @@ +Fix :class:`~datetime.date`-:class:`~datetime.datetime` comparison. Now the +special comparison methods like ``__eq__`` and ``__lt__`` return +:data:`NotImplemented` if one of comparands is :class:`!date` and other is +:class:`!datetime` instead of ignoring the time part and the time zone or +forcefully return "not equal" or raise :exc:`TypeError`. It makes comparison +of :class:`!date` and :class:`!datetime` subclasses more symmetric and +allows to change the default behavior by overriding the special comparison +methods in subclasses. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 9b8e0a719d9048c..b984ea61b82f0fb 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -1816,16 +1816,6 @@ diff_to_bool(int diff, int op) Py_RETURN_RICHCOMPARE(diff, 0, op); } -/* Raises a "can't compare" TypeError and returns NULL. */ -static PyObject * -cmperror(PyObject *a, PyObject *b) -{ - PyErr_Format(PyExc_TypeError, - "can't compare %s to %s", - Py_TYPE(a)->tp_name, Py_TYPE(b)->tp_name); - return NULL; -} - /* --------------------------------------------------------------------------- * Class implementations. */ @@ -3448,7 +3438,15 @@ date_isocalendar(PyDateTime_Date *self, PyObject *Py_UNUSED(ignored)) static PyObject * date_richcompare(PyObject *self, PyObject *other, int op) { - if (PyDate_Check(other)) { + /* Since DateTime is a subclass of Date, if the other object is + * a DateTime, it would compute an equality testing or an ordering + * based on the date part alone, and we don't want that. + * So return NotImplemented here in that case. + * If a subclass wants to change this, it's up to the subclass to do so. + * The behavior is the same as if Date and DateTime were independent + * classes. + */ + if (PyDate_Check(other) && !PyDateTime_Check(other)) { int diff = memcmp(((PyDateTime_Date *)self)->data, ((PyDateTime_Date *)other)->data, _PyDateTime_DATE_DATASIZE); @@ -5880,21 +5878,7 @@ datetime_richcompare(PyObject *self, PyObject *other, int op) PyObject *offset1, *offset2; int diff; - if (! PyDateTime_Check(other)) { - if (PyDate_Check(other)) { - /* Prevent invocation of date_richcompare. We want to - return NotImplemented here to give the other object - a chance. But since DateTime is a subclass of - Date, if the other object is a Date, it would - compute an ordering based on the date part alone, - and we don't want that. So force unequal or - uncomparable here in that case. */ - if (op == Py_EQ) - Py_RETURN_FALSE; - if (op == Py_NE) - Py_RETURN_TRUE; - return cmperror(self, other); - } + if (!PyDateTime_Check(other)) { Py_RETURN_NOTIMPLEMENTED; } From 2939ad02be62110ffa2ac6c4d9211c85e1d1720f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 11 Feb 2024 15:19:44 +0200 Subject: [PATCH 215/507] gh-97959: Fix rendering of routines in pydoc (GH-113941) * Class methods no longer have "method of builtins.type instance" note. * Corresponding notes are now added for class and unbound methods. * Method and function aliases now have references to the module or the class where the origin was defined if it differs from the current. * Bound methods are now listed in the static methods section. * Methods of builtin classes are now supported as well as methods of Python classes. --- Lib/pydoc.py | 149 ++++++++++---- Lib/test/pydocfodder.py | 48 ++++- Lib/test/test_enum.py | 10 +- Lib/test/test_pydoc.py | 191 +++++++++++++++--- ...4-01-11-15-10-53.gh-issue-97959.UOj6d4.rst | 7 + 5 files changed, 333 insertions(+), 72 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-11-15-10-53.gh-issue-97959.UOj6d4.rst diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 96aa1dfc1aacf64..17f7346e5cc6192 100755 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -225,6 +225,19 @@ def classname(object, modname): name = object.__module__ + '.' + name return name +def parentname(object, modname): + """Get a name of the enclosing class (qualified it with a module name + if necessary) or module.""" + if '.' in object.__qualname__: + name = object.__qualname__.rpartition('.')[0] + if object.__module__ != modname: + return object.__module__ + '.' + name + else: + return name + else: + if object.__module__ != modname: + return object.__module__ + def isdata(object): """Check if an object is of a type that probably means it's data.""" return not (inspect.ismodule(object) or inspect.isclass(object) or @@ -319,13 +332,15 @@ def visiblename(name, all=None, obj=None): return not name.startswith('_') def classify_class_attrs(object): - """Wrap inspect.classify_class_attrs, with fixup for data descriptors.""" + """Wrap inspect.classify_class_attrs, with fixup for data descriptors and bound methods.""" results = [] for (name, kind, cls, value) in inspect.classify_class_attrs(object): if inspect.isdatadescriptor(value): kind = 'data descriptor' if isinstance(value, property) and value.fset is None: kind = 'readonly property' + elif kind == 'method' and _is_bound_method(value): + kind = 'static method' results.append((name, kind, cls, value)) return results @@ -681,6 +696,25 @@ def classlink(self, object, modname): module.__name__, name, classname(object, modname)) return classname(object, modname) + def parentlink(self, object, modname): + """Make a link for the enclosing class or module.""" + link = None + name, module = object.__name__, sys.modules.get(object.__module__) + if hasattr(module, name) and getattr(module, name) is object: + if '.' in object.__qualname__: + name = object.__qualname__.rpartition('.')[0] + if object.__module__ != modname: + link = '%s.html#%s' % (module.__name__, name) + else: + link = '#%s' % name + else: + if object.__module__ != modname: + link = '%s.html' % module.__name__ + if link: + return '<a href="%s">%s</a>' % (link, parentname(object, modname)) + else: + return parentname(object, modname) + def modulelink(self, object): """Make a link for a module.""" return '<a href="%s.html">%s</a>' % (object.__name__, object.__name__) @@ -925,7 +959,7 @@ def spill(msg, attrs, predicate): push(self.docdata(value, name, mod)) else: push(self.document(value, name, mod, - funcs, classes, mdict, object)) + funcs, classes, mdict, object, homecls)) push('\n') return attrs @@ -1043,24 +1077,44 @@ def formatvalue(self, object): return self.grey('=' + self.repr(object)) def docroutine(self, object, name=None, mod=None, - funcs={}, classes={}, methods={}, cl=None): + funcs={}, classes={}, methods={}, cl=None, homecls=None): """Produce HTML documentation for a function or method object.""" realname = object.__name__ name = name or realname - anchor = (cl and cl.__name__ or '') + '-' + name + if homecls is None: + homecls = cl + anchor = ('' if cl is None else cl.__name__) + '-' + name note = '' - skipdocs = 0 + skipdocs = False + imfunc = None if _is_bound_method(object): - imclass = object.__self__.__class__ - if cl: - if imclass is not cl: - note = ' from ' + self.classlink(imclass, mod) + imself = object.__self__ + if imself is cl: + imfunc = getattr(object, '__func__', None) + elif inspect.isclass(imself): + note = ' class method of %s' % self.classlink(imself, mod) else: - if object.__self__ is not None: - note = ' method of %s instance' % self.classlink( - object.__self__.__class__, mod) - else: - note = ' unbound %s method' % self.classlink(imclass,mod) + note = ' method of %s instance' % self.classlink( + imself.__class__, mod) + elif (inspect.ismethoddescriptor(object) or + inspect.ismethodwrapper(object)): + try: + objclass = object.__objclass__ + except AttributeError: + pass + else: + if cl is None: + note = ' unbound %s method' % self.classlink(objclass, mod) + elif objclass is not homecls: + note = ' from ' + self.classlink(objclass, mod) + else: + imfunc = object + if inspect.isfunction(imfunc) and homecls is not None and ( + imfunc.__module__ != homecls.__module__ or + imfunc.__qualname__ != homecls.__qualname__ + '.' + realname): + pname = self.parentlink(imfunc, mod) + if pname: + note = ' from %s' % pname if (inspect.iscoroutinefunction(object) or inspect.isasyncgenfunction(object)): @@ -1071,10 +1125,13 @@ def docroutine(self, object, name=None, mod=None, if name == realname: title = '<a name="%s"><strong>%s</strong></a>' % (anchor, realname) else: - if cl and inspect.getattr_static(cl, realname, []) is object: + if (cl is not None and + inspect.getattr_static(cl, realname, []) is object): reallink = '<a href="#%s">%s</a>' % ( cl.__name__ + '-' + realname, realname) - skipdocs = 1 + skipdocs = True + if note.startswith(' from '): + note = '' else: reallink = realname title = '<a name="%s"><strong>%s</strong></a> = %s' % ( @@ -1102,7 +1159,7 @@ def docroutine(self, object, name=None, mod=None, doc = doc and '<dd><span class="code">%s</span></dd>' % doc return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc) - def docdata(self, object, name=None, mod=None, cl=None): + def docdata(self, object, name=None, mod=None, cl=None, *ignored): """Produce html documentation for a data descriptor.""" results = [] push = results.append @@ -1213,7 +1270,7 @@ def formattree(self, tree, modname, parent=None, prefix=''): entry, modname, c, prefix + ' ') return result - def docmodule(self, object, name=None, mod=None): + def docmodule(self, object, name=None, mod=None, *ignored): """Produce text documentation for a given module object.""" name = object.__name__ # ignore the passed-in name synop, desc = splitdoc(getdoc(object)) @@ -1392,7 +1449,7 @@ def spill(msg, attrs, predicate): push(self.docdata(value, name, mod)) else: push(self.document(value, - name, mod, object)) + name, mod, object, homecls)) return attrs def spilldescriptors(msg, attrs, predicate): @@ -1467,23 +1524,43 @@ def formatvalue(self, object): """Format an argument default value as text.""" return '=' + self.repr(object) - def docroutine(self, object, name=None, mod=None, cl=None): + def docroutine(self, object, name=None, mod=None, cl=None, homecls=None): """Produce text documentation for a function or method object.""" realname = object.__name__ name = name or realname + if homecls is None: + homecls = cl note = '' - skipdocs = 0 + skipdocs = False + imfunc = None if _is_bound_method(object): - imclass = object.__self__.__class__ - if cl: - if imclass is not cl: - note = ' from ' + classname(imclass, mod) + imself = object.__self__ + if imself is cl: + imfunc = getattr(object, '__func__', None) + elif inspect.isclass(imself): + note = ' class method of %s' % classname(imself, mod) else: - if object.__self__ is not None: - note = ' method of %s instance' % classname( - object.__self__.__class__, mod) - else: - note = ' unbound %s method' % classname(imclass,mod) + note = ' method of %s instance' % classname( + imself.__class__, mod) + elif (inspect.ismethoddescriptor(object) or + inspect.ismethodwrapper(object)): + try: + objclass = object.__objclass__ + except AttributeError: + pass + else: + if cl is None: + note = ' unbound %s method' % classname(objclass, mod) + elif objclass is not homecls: + note = ' from ' + classname(objclass, mod) + else: + imfunc = object + if inspect.isfunction(imfunc) and homecls is not None and ( + imfunc.__module__ != homecls.__module__ or + imfunc.__qualname__ != homecls.__qualname__ + '.' + realname): + pname = parentname(imfunc, mod) + if pname: + note = ' from %s' % pname if (inspect.iscoroutinefunction(object) or inspect.isasyncgenfunction(object)): @@ -1494,8 +1571,11 @@ def docroutine(self, object, name=None, mod=None, cl=None): if name == realname: title = self.bold(realname) else: - if cl and inspect.getattr_static(cl, realname, []) is object: - skipdocs = 1 + if (cl is not None and + inspect.getattr_static(cl, realname, []) is object): + skipdocs = True + if note.startswith(' from '): + note = '' title = self.bold(name) + ' = ' + realname argspec = None @@ -1517,7 +1597,7 @@ def docroutine(self, object, name=None, mod=None, cl=None): doc = getdoc(object) or '' return decl + '\n' + (doc and self.indent(doc).rstrip() + '\n') - def docdata(self, object, name=None, mod=None, cl=None): + def docdata(self, object, name=None, mod=None, cl=None, *ignored): """Produce text documentation for a data descriptor.""" results = [] push = results.append @@ -1533,7 +1613,8 @@ def docdata(self, object, name=None, mod=None, cl=None): docproperty = docdata - def docother(self, object, name=None, mod=None, parent=None, maxlen=None, doc=None): + def docother(self, object, name=None, mod=None, parent=None, *ignored, + maxlen=None, doc=None): """Produce text documentation for a data object.""" repr = self.repr(object) if maxlen: diff --git a/Lib/test/pydocfodder.py b/Lib/test/pydocfodder.py index a3ef22312439549..27037e048db8198 100644 --- a/Lib/test/pydocfodder.py +++ b/Lib/test/pydocfodder.py @@ -2,6 +2,12 @@ import types +def global_func(x, y): + """Module global function""" + +def global_func2(x, y): + """Module global function 2""" + class A: "A class." @@ -26,7 +32,7 @@ def A_classmethod(cls, x): "A class method defined in A." A_classmethod = classmethod(A_classmethod) - def A_staticmethod(): + def A_staticmethod(x, y): "A static method defined in A." A_staticmethod = staticmethod(A_staticmethod) @@ -61,6 +67,28 @@ def BD_method(self): def BCD_method(self): "Method defined in B, C and D." + @classmethod + def B_classmethod(cls, x): + "A class method defined in B." + + global_func = global_func # same name + global_func_alias = global_func + global_func2_alias = global_func2 + B_classmethod_alias = B_classmethod + A_classmethod_ref = A.A_classmethod + A_staticmethod = A.A_staticmethod # same name + A_staticmethod_alias = A.A_staticmethod + A_method_ref = A().A_method + A_method_alias = A.A_method + B_method_alias = B_method + __repr__ = object.__repr__ # same name + object_repr = object.__repr__ + get = {}.get # same name + dict_get = {}.get + +B.B_classmethod_ref = B.B_classmethod + + class C(A): "A class, derived from A." @@ -136,3 +164,21 @@ def __call__(self, inst): submodule = types.ModuleType(__name__ + '.submodule', """A submodule, which should appear in its parent's summary""") + +global_func_alias = global_func +A_classmethod = A.A_classmethod # same name +A_classmethod2 = A.A_classmethod +A_classmethod3 = B.A_classmethod +A_staticmethod = A.A_staticmethod # same name +A_staticmethod_alias = A.A_staticmethod +A_staticmethod_ref = A().A_staticmethod +A_staticmethod_ref2 = B().A_staticmethod +A_method = A().A_method # same name +A_method2 = A().A_method +A_method3 = B().A_method +B_method = B.B_method # same name +B_method2 = B.B_method +count = list.count # same name +list_count = list.count +get = {}.get # same name +dict_get = {}.get diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index f7503331c1ac1dd..5d7dae8829574b9 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -4851,22 +4851,22 @@ class Color(enum.Enum) | The value of the Enum member. | | ---------------------------------------------------------------------- - | Methods inherited from enum.EnumType: + | Static methods inherited from enum.EnumType: | - | __contains__(value) from enum.EnumType + | __contains__(value) | Return True if `value` is in `cls`. | | `value` is in `cls` if: | 1) `value` is a member of `cls`, or | 2) `value` is the value of one of the `cls`'s members. | - | __getitem__(name) from enum.EnumType + | __getitem__(name) | Return the member matching `name`. | - | __iter__() from enum.EnumType + | __iter__() | Return members in definition order. | - | __len__() from enum.EnumType + | __len__() | Return the number of members (no aliases) | | ---------------------------------------------------------------------- diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py index 99b19d01783a10f..f3c26624c624f5d 100644 --- a/Lib/test/test_pydoc.py +++ b/Lib/test/test_pydoc.py @@ -35,6 +35,7 @@ requires_docstrings, MISSING_C_DOCSTRINGS) from test.support.os_helper import (TESTFN, rmtree, unlink) from test import pydoc_mod +from test import pydocfodder class nonascii: @@ -102,7 +103,7 @@ class C(builtins.object) | ---------------------------------------------------------------------- | Class methods defined here: | - | __class_getitem__(item) from builtins.type + | __class_getitem__(item) | | ---------------------------------------------------------------------- | Data descriptors defined here: @@ -166,7 +167,7 @@ class A(builtins.object) Methods defined here: __init__() Wow, I have no function! - + ---------------------------------------------------------------------- Data descriptors defined here: __dict__ dictionary for instance variables @@ -179,6 +180,7 @@ class B(builtins.object) dictionary for instance variables __weakref__ list of weak references to the object + ---------------------------------------------------------------------- Data and other attributes defined here: NO_MEANING = 'eggs' __annotations__ = {'NO_MEANING': <class 'str'>} @@ -191,8 +193,10 @@ class C(builtins.object) is_it_true(self) Return self.get_answer() say_no(self) + ---------------------------------------------------------------------- Class methods defined here: - __class_getitem__(item) from builtins.type + __class_getitem__(item) + ---------------------------------------------------------------------- Data descriptors defined here: __dict__ dictionary for instance variables @@ -330,6 +334,10 @@ def get_pydoc_html(module): loc = "<br><a href=\"" + loc + "\">Module Docs</a>" return output.strip(), loc +def clean_text(doc): + # clean up the extra text formatting that pydoc performs + return re.sub('\b.', '', doc) + def get_pydoc_link(module): "Returns a documentation web link of a module" abspath = os.path.abspath @@ -347,10 +355,7 @@ def get_pydoc_text(module): loc = "\nMODULE DOCS\n " + loc + "\n" output = doc.docmodule(module) - - # clean up the extra text formatting that pydoc performs - patt = re.compile('\b.') - output = patt.sub('', output) + output = clean_text(output) return output.strip(), loc def get_html_title(text): @@ -367,6 +372,7 @@ def html2text(html): Tailored for pydoc tests only. """ html = html.replace("<dd>", "\n") + html = html.replace("<hr>", "-"*70) html = re.sub("<.*?>", "", html) html = pydoc.replace(html, " ", " ", ">", ">", "<", "<") return html @@ -798,8 +804,7 @@ def itemconfigure(self, tagOrId, cnf=None, **kw): b_size = A.a_size doc = pydoc.render_doc(B) - # clean up the extra text formatting that pydoc performs - doc = re.sub('\b.', '', doc) + doc = clean_text(doc) self.assertEqual(doc, '''\ Python Library Documentation: class B in module %s @@ -887,8 +892,7 @@ def __init__(self, ... doc = pydoc.render_doc(A) - # clean up the extra text formatting that pydoc performs - doc = re.sub('\b.', '', doc) + doc = clean_text(doc) self.assertEqual(doc, '''Python Library Documentation: class A in module %s class A(builtins.object) @@ -925,8 +929,7 @@ def func( ... doc = pydoc.render_doc(func) - # clean up the extra text formatting that pydoc performs - doc = re.sub('\b.', '', doc) + doc = clean_text(doc) self.assertEqual(doc, '''Python Library Documentation: function func in module %s func( @@ -942,8 +945,7 @@ def function_with_really_long_name_so_annotations_can_be_rather_small( ... doc = pydoc.render_doc(function_with_really_long_name_so_annotations_can_be_rather_small) - # clean up the extra text formatting that pydoc performs - doc = re.sub('\b.', '', doc) + doc = clean_text(doc) self.assertEqual(doc, '''Python Library Documentation: function function_with_really_long_name_so_annotations_can_be_rather_small in module %s function_with_really_long_name_so_annotations_can_be_rather_small( @@ -957,8 +959,7 @@ def function_with_really_long_name_so_annotations_can_be_rather_small( second_very_long_parameter_name: ... doc = pydoc.render_doc(does_not_have_name) - # clean up the extra text formatting that pydoc performs - doc = re.sub('\b.', '', doc) + doc = clean_text(doc) self.assertEqual(doc, '''Python Library Documentation: function <lambda> in module %s <lambda> lambda very_long_parameter_name_that_should_not_fit_into_a_single_line, second_very_long_parameter_name @@ -1244,7 +1245,7 @@ def test_unbound_python_method(self): @requires_docstrings def test_unbound_builtin_method(self): self.assertEqual(self._get_summary_line(_pickle.Pickler.dump), - "dump(self, obj, /)") + "dump(self, obj, /) unbound _pickle.Pickler method") # these no longer include "self" def test_bound_python_method(self): @@ -1296,7 +1297,7 @@ def test_module_level_callable_o(self): def test_unbound_builtin_method_noargs(self): self.assertEqual(self._get_summary_line(str.lower), - "lower(self, /)") + "lower(self, /) unbound builtins.str method") def test_bound_builtin_method_noargs(self): self.assertEqual(self._get_summary_line(''.lower), @@ -1304,7 +1305,7 @@ def test_bound_builtin_method_noargs(self): def test_unbound_builtin_method_o(self): self.assertEqual(self._get_summary_line(set.add), - "add(self, object, /)") + "add(self, object, /) unbound builtins.set method") def test_bound_builtin_method_o(self): self.assertEqual(self._get_summary_line(set().add), @@ -1312,7 +1313,7 @@ def test_bound_builtin_method_o(self): def test_unbound_builtin_method_coexist_o(self): self.assertEqual(self._get_summary_line(set.__contains__), - "__contains__(self, object, /)") + "__contains__(self, object, /) unbound builtins.set method") def test_bound_builtin_method_coexist_o(self): self.assertEqual(self._get_summary_line(set().__contains__), @@ -1320,19 +1321,19 @@ def test_bound_builtin_method_coexist_o(self): def test_unbound_builtin_classmethod_noargs(self): self.assertEqual(self._get_summary_line(datetime.datetime.__dict__['utcnow']), - "utcnow(type, /)") + "utcnow(type, /) unbound datetime.datetime method") def test_bound_builtin_classmethod_noargs(self): self.assertEqual(self._get_summary_line(datetime.datetime.utcnow), - "utcnow() method of builtins.type instance") + "utcnow() class method of datetime.datetime") def test_unbound_builtin_classmethod_o(self): self.assertEqual(self._get_summary_line(dict.__dict__['__class_getitem__']), - "__class_getitem__(type, object, /)") + "__class_getitem__(type, object, /) unbound builtins.dict method") def test_bound_builtin_classmethod_o(self): self.assertEqual(self._get_summary_line(dict.__class_getitem__), - "__class_getitem__(object, /) method of builtins.type instance") + "__class_getitem__(object, /) class method of builtins.dict") @support.cpython_only @requires_docstrings @@ -1356,11 +1357,13 @@ def test_builtin_staticmethod_unrepresentable_default(self): @requires_docstrings def test_unbound_builtin_method_unrepresentable_default(self): self.assertEqual(self._get_summary_line(dict.pop), - "pop(self, key, default=<unrepresentable>, /)") + "pop(self, key, default=<unrepresentable>, /) " + "unbound builtins.dict method") import _testcapi cls = _testcapi.DocStringUnrepresentableSignatureTest self.assertEqual(self._get_summary_line(cls.meth), - "meth(self, /, a, b=<x>)") + "meth(self, /, a, b=<x>) unbound " + "_testcapi.DocStringUnrepresentableSignatureTest method") @support.cpython_only @requires_docstrings @@ -1381,7 +1384,8 @@ def test_unbound_builtin_classmethod_unrepresentable_default(self): cls = _testcapi.DocStringUnrepresentableSignatureTest descr = cls.__dict__['classmeth'] self.assertEqual(self._get_summary_line(descr), - "classmeth(type, /, a, b=<x>)") + "classmeth(type, /, a, b=<x>) unbound " + "_testcapi.DocStringUnrepresentableSignatureTest method") @support.cpython_only @requires_docstrings @@ -1389,7 +1393,8 @@ def test_bound_builtin_classmethod_unrepresentable_default(self): import _testcapi cls = _testcapi.DocStringUnrepresentableSignatureTest self.assertEqual(self._get_summary_line(cls.classmeth), - "classmeth(a, b=<x>) method of builtins.type instance") + "classmeth(a, b=<x>) class method of " + "_testcapi.DocStringUnrepresentableSignatureTest") def test_overridden_text_signature(self): class C: @@ -1423,7 +1428,7 @@ def smeth(*args, **kwargs): "meth" + bound + " method of test.test_pydoc.C instance") C.cmeth.__func__.__text_signature__ = text_signature self.assertEqual(self._get_summary_line(C.cmeth), - "cmeth" + bound + " method of builtins.type instance") + "cmeth" + bound + " class method of test.test_pydoc.C") C.smeth.__text_signature__ = text_signature self.assertEqual(self._get_summary_line(C.smeth), "smeth" + unbound) @@ -1460,13 +1465,13 @@ def cm(cls, x): 'cm(...)\n' ' A class method\n') self.assertEqual(self._get_summary_lines(X.cm), """\ -cm(x) method of builtins.type instance +cm(x) class method of test.test_pydoc.X A class method """) self.assertIn(""" | Class methods defined here: | - | cm(x) from builtins.type + | cm(x) | A class method """, pydoc.plain(pydoc.render_doc(X))) @@ -1623,6 +1628,128 @@ def a_fn_with_https_link(): ) +class PydocFodderTest(unittest.TestCase): + + def getsection(self, text, beginline, endline): + lines = text.splitlines() + beginindex, endindex = 0, None + if beginline is not None: + beginindex = lines.index(beginline) + if endline is not None: + endindex = lines.index(endline, beginindex) + return lines[beginindex:endindex] + + def test_text_doc_routines_in_class(self, cls=pydocfodder.B): + doc = pydoc.TextDoc() + result = doc.docclass(cls) + result = clean_text(result) + where = 'defined here' if cls is pydocfodder.B else 'inherited from B' + lines = self.getsection(result, f' | Methods {where}:', ' | ' + '-'*70) + self.assertIn(' | A_method_alias = A_method(self)', lines) + self.assertIn(' | B_method_alias = B_method(self)', lines) + self.assertIn(' | A_staticmethod(x, y) from test.pydocfodder.A', lines) + self.assertIn(' | A_staticmethod_alias = A_staticmethod(x, y)', lines) + self.assertIn(' | global_func(x, y) from test.pydocfodder', lines) + self.assertIn(' | global_func_alias = global_func(x, y)', lines) + self.assertIn(' | global_func2_alias = global_func2(x, y) from test.pydocfodder', lines) + self.assertIn(' | __repr__(self, /) from builtins.object', lines) + self.assertIn(' | object_repr = __repr__(self, /)', lines) + + lines = self.getsection(result, f' | Static methods {where}:', ' | ' + '-'*70) + self.assertIn(' | A_classmethod_ref = A_classmethod(x) class method of test.pydocfodder.A', lines) + note = '' if cls is pydocfodder.B else ' class method of test.pydocfodder.B' + self.assertIn(' | B_classmethod_ref = B_classmethod(x)' + note, lines) + self.assertIn(' | A_method_ref = A_method() method of test.pydocfodder.A instance', lines) + self.assertIn(' | get(key, default=None, /) method of builtins.dict instance', lines) + self.assertIn(' | dict_get = get(key, default=None, /) method of builtins.dict instance', lines) + + lines = self.getsection(result, f' | Class methods {where}:', ' | ' + '-'*70) + self.assertIn(' | B_classmethod(x)', lines) + self.assertIn(' | B_classmethod_alias = B_classmethod(x)', lines) + + def test_html_doc_routines_in_class(self, cls=pydocfodder.B): + doc = pydoc.HTMLDoc() + result = doc.docclass(cls) + result = html2text(result) + where = 'defined here' if cls is pydocfodder.B else 'inherited from B' + lines = self.getsection(result, f'Methods {where}:', '-'*70) + self.assertIn('A_method_alias = A_method(self)', lines) + self.assertIn('B_method_alias = B_method(self)', lines) + self.assertIn('A_staticmethod(x, y) from test.pydocfodder.A', lines) + self.assertIn('A_staticmethod_alias = A_staticmethod(x, y)', lines) + self.assertIn('global_func(x, y) from test.pydocfodder', lines) + self.assertIn('global_func_alias = global_func(x, y)', lines) + self.assertIn('global_func2_alias = global_func2(x, y) from test.pydocfodder', lines) + self.assertIn('__repr__(self, /) from builtins.object', lines) + self.assertIn('object_repr = __repr__(self, /)', lines) + + lines = self.getsection(result, f'Static methods {where}:', '-'*70) + self.assertIn('A_classmethod_ref = A_classmethod(x) class method of test.pydocfodder.A', lines) + note = '' if cls is pydocfodder.B else ' class method of test.pydocfodder.B' + self.assertIn('B_classmethod_ref = B_classmethod(x)' + note, lines) + self.assertIn('A_method_ref = A_method() method of test.pydocfodder.A instance', lines) + + lines = self.getsection(result, f'Class methods {where}:', '-'*70) + self.assertIn('B_classmethod(x)', lines) + self.assertIn('B_classmethod_alias = B_classmethod(x)', lines) + + def test_text_doc_inherited_routines_in_class(self): + self.test_text_doc_routines_in_class(pydocfodder.D) + + def test_html_doc_inherited_routines_in_class(self): + self.test_html_doc_routines_in_class(pydocfodder.D) + + def test_text_doc_routines_in_module(self): + doc = pydoc.TextDoc() + result = doc.docmodule(pydocfodder) + result = clean_text(result) + lines = self.getsection(result, 'FUNCTIONS', 'FILE') + # function alias + self.assertIn(' global_func_alias = global_func(x, y)', lines) + self.assertIn(' A_staticmethod(x, y)', lines) + self.assertIn(' A_staticmethod_alias = A_staticmethod(x, y)', lines) + # bound class methods + self.assertIn(' A_classmethod(x) class method of A', lines) + self.assertIn(' A_classmethod2 = A_classmethod(x) class method of A', lines) + self.assertIn(' A_classmethod3 = A_classmethod(x) class method of B', lines) + # bound methods + self.assertIn(' A_method() method of A instance', lines) + self.assertIn(' A_method2 = A_method() method of A instance', lines) + self.assertIn(' A_method3 = A_method() method of B instance', lines) + self.assertIn(' A_staticmethod_ref = A_staticmethod(x, y)', lines) + self.assertIn(' A_staticmethod_ref2 = A_staticmethod(y) method of B instance', lines) + self.assertIn(' get(key, default=None, /) method of builtins.dict instance', lines) + self.assertIn(' dict_get = get(key, default=None, /) method of builtins.dict instance', lines) + # unbound methods + self.assertIn(' B_method(self)', lines) + self.assertIn(' B_method2 = B_method(self)', lines) + + def test_html_doc_routines_in_module(self): + doc = pydoc.HTMLDoc() + result = doc.docmodule(pydocfodder) + result = html2text(result) + lines = self.getsection(result, ' Functions', None) + # function alias + self.assertIn(' global_func_alias = global_func(x, y)', lines) + self.assertIn(' A_staticmethod(x, y)', lines) + self.assertIn(' A_staticmethod_alias = A_staticmethod(x, y)', lines) + # bound class methods + self.assertIn('A_classmethod(x) class method of A', lines) + self.assertIn(' A_classmethod2 = A_classmethod(x) class method of A', lines) + self.assertIn(' A_classmethod3 = A_classmethod(x) class method of B', lines) + # bound methods + self.assertIn(' A_method() method of A instance', lines) + self.assertIn(' A_method2 = A_method() method of A instance', lines) + self.assertIn(' A_method3 = A_method() method of B instance', lines) + self.assertIn(' A_staticmethod_ref = A_staticmethod(x, y)', lines) + self.assertIn(' A_staticmethod_ref2 = A_staticmethod(y) method of B instance', lines) + self.assertIn(' get(key, default=None, /) method of builtins.dict instance', lines) + self.assertIn(' dict_get = get(key, default=None, /) method of builtins.dict instance', lines) + # unbound methods + self.assertIn(' B_method(self)', lines) + self.assertIn(' B_method2 = B_method(self)', lines) + + @unittest.skipIf( is_emscripten or is_wasi, "Socket server not available on Emscripten/WASI." diff --git a/Misc/NEWS.d/next/Library/2024-01-11-15-10-53.gh-issue-97959.UOj6d4.rst b/Misc/NEWS.d/next/Library/2024-01-11-15-10-53.gh-issue-97959.UOj6d4.rst new file mode 100644 index 000000000000000..a317271947dc377 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-11-15-10-53.gh-issue-97959.UOj6d4.rst @@ -0,0 +1,7 @@ +Fix rendering class methods, bound methods, method and function aliases in +:mod:`pydoc`. Class methods no longer have "method of builtins.type +instance" note. Corresponding notes are now added for class and unbound +methods. Method and function aliases now have references to the module or +the class where the origin was defined if it differs from the current. Bound +methods are now listed in the static methods section. Methods of builtin +classes are now supported as well as methods of Python classes. From cc573c70b7d5e169de2a6e4297068de407dc8d4d Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Sun, 11 Feb 2024 19:07:08 +0300 Subject: [PATCH 216/507] gh-115282: Fix direct invocation of `test_traceback.py` (#115283) --- Lib/test/test_traceback.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 372fc48bf81a6a3..dd9b1850adf0860 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -3124,10 +3124,13 @@ def test_smoke_user_exception(self): class MyException(Exception): pass - self.do_test_smoke( - MyException('bad things happened'), - ('test.test_traceback.TestTracebackException.' - 'test_smoke_user_exception.<locals>.MyException')) + if __name__ == '__main__': + expected = ('TestTracebackException.' + 'test_smoke_user_exception.<locals>.MyException') + else: + expected = ('test.test_traceback.TestTracebackException.' + 'test_smoke_user_exception.<locals>.MyException') + self.do_test_smoke(MyException('bad things happened'), expected) def test_from_exception(self): # Check all the parameters are accepted. From e1552fd19de17e7a6daa3c2a6d1ca207bb8eaf8e Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Sun, 11 Feb 2024 12:51:07 -0600 Subject: [PATCH 217/507] gh-101100: Clean up Doc/c-api/exceptions.rst and Doc/c-api/sys.rst (GH-114825) --- Doc/c-api/exceptions.rst | 14 +++++++------- Doc/c-api/sys.rst | 33 ++++++++++++++++++++++----------- Doc/tools/.nitignore | 2 -- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index eaf723fb2cc4cf9..e6309ae7614d347 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -396,7 +396,7 @@ an error value). .. c:function:: int PyErr_ResourceWarning(PyObject *source, Py_ssize_t stack_level, const char *format, ...) Function similar to :c:func:`PyErr_WarnFormat`, but *category* is - :exc:`ResourceWarning` and it passes *source* to :func:`warnings.WarningMessage`. + :exc:`ResourceWarning` and it passes *source* to :class:`!warnings.WarningMessage`. .. versionadded:: 3.6 @@ -732,7 +732,7 @@ Exception Classes This creates a class object derived from :exc:`Exception` (accessible in C as :c:data:`PyExc_Exception`). - The :attr:`__module__` attribute of the new class is set to the first part (up + The :attr:`!__module__` attribute of the new class is set to the first part (up to the last dot) of the *name* argument, and the class name is set to the last part (after the last dot). The *base* argument can be used to specify alternate base classes; it can either be only one class or a tuple of classes. The *dict* @@ -904,8 +904,8 @@ because the :ref:`call protocol <call>` takes care of recursion handling. Marks a point where a recursive C-level call is about to be performed. - If :c:macro:`USE_STACKCHECK` is defined, this function checks if the OS - stack overflowed using :c:func:`PyOS_CheckStack`. In this is the case, it + If :c:macro:`!USE_STACKCHECK` is defined, this function checks if the OS + stack overflowed using :c:func:`PyOS_CheckStack`. If this is the case, it sets a :exc:`MemoryError` and returns a nonzero value. The function then checks if the recursion limit is reached. If this is the @@ -1158,11 +1158,11 @@ These are compatibility aliases to :c:data:`PyExc_OSError`: +-------------------------------------+----------+ | C Name | Notes | +=====================================+==========+ -| :c:data:`PyExc_EnvironmentError` | | +| :c:data:`!PyExc_EnvironmentError` | | +-------------------------------------+----------+ -| :c:data:`PyExc_IOError` | | +| :c:data:`!PyExc_IOError` | | +-------------------------------------+----------+ -| :c:data:`PyExc_WindowsError` | [2]_ | +| :c:data:`!PyExc_WindowsError` | [2]_ | +-------------------------------------+----------+ .. versionchanged:: 3.3 diff --git a/Doc/c-api/sys.rst b/Doc/c-api/sys.rst index 35969b30120d2a9..d6fca1a0b0a219c 100644 --- a/Doc/c-api/sys.rst +++ b/Doc/c-api/sys.rst @@ -5,6 +5,7 @@ Operating System Utilities ========================== + .. c:function:: PyObject* PyOS_FSPath(PyObject *path) Return the file system representation for *path*. If the object is a @@ -97,27 +98,30 @@ Operating System Utilities .. c:function:: int PyOS_CheckStack() + .. index:: single: USE_STACKCHECK (C macro) + Return true when the interpreter runs out of stack space. This is a reliable - check, but is only available when :c:macro:`USE_STACKCHECK` is defined (currently + check, but is only available when :c:macro:`!USE_STACKCHECK` is defined (currently on certain versions of Windows using the Microsoft Visual C++ compiler). - :c:macro:`USE_STACKCHECK` will be defined automatically; you should never + :c:macro:`!USE_STACKCHECK` will be defined automatically; you should never change the definition in your own code. +.. c:type:: void (*PyOS_sighandler_t)(int) + + .. c:function:: PyOS_sighandler_t PyOS_getsig(int i) Return the current signal handler for signal *i*. This is a thin wrapper around either :c:func:`!sigaction` or :c:func:`!signal`. Do not call those functions - directly! :c:type:`PyOS_sighandler_t` is a typedef alias for :c:expr:`void - (\*)(int)`. + directly! .. c:function:: PyOS_sighandler_t PyOS_setsig(int i, PyOS_sighandler_t h) Set the signal handler for signal *i* to be *h*; return the old signal handler. This is a thin wrapper around either :c:func:`!sigaction` or :c:func:`!signal`. Do - not call those functions directly! :c:type:`PyOS_sighandler_t` is a typedef - alias for :c:expr:`void (\*)(int)`. + not call those functions directly! .. c:function:: wchar_t* Py_DecodeLocale(const char* arg, size_t *size) @@ -342,10 +346,8 @@ accessible to C code. They all work with the current interpreter thread's silently abort the operation by raising an error subclassed from :class:`Exception` (other errors will not be silenced). - The hook function is of type :c:expr:`int (*)(const char *event, PyObject - *args, void *userData)`, where *args* is guaranteed to be a - :c:type:`PyTupleObject`. The hook function is always called with the GIL - held by the Python interpreter that raised the event. + The hook function is always called with the GIL held by the Python + interpreter that raised the event. See :pep:`578` for a detailed description of auditing. Functions in the runtime and standard library that raise events are listed in the @@ -354,12 +356,21 @@ accessible to C code. They all work with the current interpreter thread's .. audit-event:: sys.addaudithook "" c.PySys_AddAuditHook - If the interpreter is initialized, this function raises a auditing event + If the interpreter is initialized, this function raises an auditing event ``sys.addaudithook`` with no arguments. If any existing hooks raise an exception derived from :class:`Exception`, the new hook will not be added and the exception is cleared. As a result, callers cannot assume that their hook has been added unless they control all existing hooks. + .. c:namespace:: NULL + .. c:type:: int (*Py_AuditHookFunction) (const char *event, PyObject *args, void *userData) + + The type of the hook function. + *event* is the C string event argument passed to :c:func:`PySys_Audit` or + :c:func:`PySys_AuditTuple`. + *args* is guaranteed to be a :c:type:`PyTupleObject`. + *userData* is the argument passed to PySys_AddAuditHook(). + .. versionadded:: 3.8 diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 2af116c2d79c54e..33129e898e51d68 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -3,14 +3,12 @@ # Keep lines sorted lexicographically to help avoid merge conflicts. Doc/c-api/descriptor.rst -Doc/c-api/exceptions.rst Doc/c-api/float.rst Doc/c-api/init.rst Doc/c-api/init_config.rst Doc/c-api/intro.rst Doc/c-api/module.rst Doc/c-api/stable.rst -Doc/c-api/sys.rst Doc/c-api/type.rst Doc/c-api/typeobj.rst Doc/extending/extending.rst From 54bde5dcc3c04c4ddebcc9df2904ab325fa0b486 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Mon, 12 Feb 2024 10:27:12 +0300 Subject: [PATCH 218/507] gh-87804: Fix error handling and style in `_pystatvfs_fromstructstatfs` (#115236) --- Modules/posixmodule.c | 64 ++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index d05b4ba723ce8c8..17032d9d490c782 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -12894,7 +12894,7 @@ os_WSTOPSIG_impl(PyObject *module, int status) #ifdef __APPLE__ /* On macOS struct statvfs uses 32-bit integers for block counts, - * resulting in overflow when filesystems are larger tan 4TB. Therefore + * resulting in overflow when filesystems are larger than 4TB. Therefore * os.statvfs is implemented in terms of statfs(2). */ @@ -12902,41 +12902,43 @@ static PyObject* _pystatvfs_fromstructstatfs(PyObject *module, struct statfs st) { PyObject *StatVFSResultType = get_posix_state(module)->StatVFSResultType; PyObject *v = PyStructSequence_New((PyTypeObject *)StatVFSResultType); - if (v == NULL) + if (v == NULL) { return NULL; + } - long flags = 0; - if (st.f_flags & MNT_RDONLY) { - flags |= ST_RDONLY; - } - if (st.f_flags & MNT_NOSUID) { - flags |= ST_NOSUID; - } + long flags = 0; + if (st.f_flags & MNT_RDONLY) { + flags |= ST_RDONLY; + } + if (st.f_flags & MNT_NOSUID) { + flags |= ST_NOSUID; + } - _Static_assert(sizeof(st.f_blocks) == sizeof(long long), "assuming large file"); + _Static_assert(sizeof(st.f_blocks) == sizeof(long long), "assuming large file"); - PyStructSequence_SET_ITEM(v, 0, PyLong_FromLong((long) st.f_iosize)); - PyStructSequence_SET_ITEM(v, 1, PyLong_FromLong((long) st.f_bsize)); - PyStructSequence_SET_ITEM(v, 2, - PyLong_FromLongLong((long long) st.f_blocks)); - PyStructSequence_SET_ITEM(v, 3, - PyLong_FromLongLong((long long) st.f_bfree)); - PyStructSequence_SET_ITEM(v, 4, - PyLong_FromLongLong((long long) st.f_bavail)); - PyStructSequence_SET_ITEM(v, 5, - PyLong_FromLongLong((long long) st.f_files)); - PyStructSequence_SET_ITEM(v, 6, - PyLong_FromLongLong((long long) st.f_ffree)); - PyStructSequence_SET_ITEM(v, 7, - PyLong_FromLongLong((long long) st.f_ffree)); - PyStructSequence_SET_ITEM(v, 8, PyLong_FromLong((long) flags)); +#define SET_ITEM(v, index, item) \ + do { \ + if (item == NULL) { \ + Py_DECREF(v); \ + return NULL; \ + } \ + PyStructSequence_SET_ITEM(v, index, item); \ + } while (0) \ - PyStructSequence_SET_ITEM(v, 9, PyLong_FromLong((long) NAME_MAX)); - PyStructSequence_SET_ITEM(v, 10, PyLong_FromUnsignedLong(st.f_fsid.val[0])); - if (PyErr_Occurred()) { - Py_DECREF(v); - return NULL; - } + SET_ITEM(v, 0, PyLong_FromLong((long) st.f_iosize)); + SET_ITEM(v, 1, PyLong_FromLong((long) st.f_bsize)); + SET_ITEM(v, 2, PyLong_FromLongLong((long long) st.f_blocks)); + SET_ITEM(v, 3, PyLong_FromLongLong((long long) st.f_bfree)); + SET_ITEM(v, 4, PyLong_FromLongLong((long long) st.f_bavail)); + SET_ITEM(v, 5, PyLong_FromLongLong((long long) st.f_files)); + SET_ITEM(v, 6, PyLong_FromLongLong((long long) st.f_ffree)); + SET_ITEM(v, 7, PyLong_FromLongLong((long long) st.f_ffree)); + SET_ITEM(v, 8, PyLong_FromLong((long) flags)); + + SET_ITEM(v, 9, PyLong_FromLong((long) NAME_MAX)); + SET_ITEM(v, 10, PyLong_FromUnsignedLong(st.f_fsid.val[0])); + +#undef SET_ITEM return v; } From 235cacff81931a68e8c400bb3919ae6e55462fb5 Mon Sep 17 00:00:00 2001 From: Brandt Bucher <brandtbucher@microsoft.com> Date: Mon, 12 Feb 2024 01:04:36 -0800 Subject: [PATCH 219/507] GH-114695: Add `sys._clear_internal_caches` (GH-115152) --- Doc/library/sys.rst | 13 +++- Include/cpython/optimizer.h | 3 +- Lib/test/libregrtest/refleak.py | 4 +- Lib/test/test_capi/test_opt.py | 16 +++++ Lib/test/test_mailbox.py | 4 ++ ...-02-07-18-04-36.gh-issue-114695.o9wP5P.rst | 3 + Objects/codeobject.c | 22 ++----- Python/bytecodes.c | 23 ++----- Python/clinic/sysmodule.c.h | 20 +++++- Python/generated_cases.c.h | 25 ++------ Python/optimizer.c | 64 ++++++++++--------- Python/sysmodule.c | 17 +++++ 12 files changed, 130 insertions(+), 84 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-07-18-04-36.gh-issue-114695.o9wP5P.rst diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index a97a369b77b88a8..ad8857fc2807f70 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -195,6 +195,17 @@ always available. This function should be used for internal and specialized purposes only. + .. deprecated:: 3.13 + Use the more general :func:`_clear_internal_caches` function instead. + + +.. function:: _clear_internal_caches() + + Clear all internal performance-related caches. Use this function *only* to + release unnecessary references and memory blocks when hunting for leaks. + + .. versionadded:: 3.13 + .. function:: _current_frames() @@ -724,7 +735,7 @@ always available. regardless of their size. This function is mainly useful for tracking and debugging memory leaks. Because of the interpreter's internal caches, the result can vary from call to call; you may have to call - :func:`_clear_type_cache()` and :func:`gc.collect()` to get more + :func:`_clear_internal_caches()` and :func:`gc.collect()` to get more predictable results. If a Python build or implementation cannot reasonably compute this diff --git a/Include/cpython/optimizer.h b/Include/cpython/optimizer.h index 5a9ccaea3b22098..3928eca583ba5b7 100644 --- a/Include/cpython/optimizer.h +++ b/Include/cpython/optimizer.h @@ -24,9 +24,10 @@ typedef struct { uint8_t opcode; uint8_t oparg; uint8_t valid; - uint8_t linked; + int index; // Index of ENTER_EXECUTOR (if code isn't NULL, below). _PyBloomFilter bloom; _PyExecutorLinkListNode links; + PyCodeObject *code; // Weak (NULL if no corresponding ENTER_EXECUTOR). } _PyVMData; typedef struct { diff --git a/Lib/test/libregrtest/refleak.py b/Lib/test/libregrtest/refleak.py index 7da16cf721f0973..71a70af6882d16f 100644 --- a/Lib/test/libregrtest/refleak.py +++ b/Lib/test/libregrtest/refleak.py @@ -201,8 +201,8 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs): # Clear caches clear_caches() - # Clear type cache at the end: previous function calls can modify types - sys._clear_type_cache() + # Clear other caches last (previous function calls can re-populate them): + sys._clear_internal_caches() def warm_caches(): diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 5c8c0596610303b..e6b1b554c9af10e 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -1,5 +1,6 @@ import contextlib import opcode +import sys import textwrap import unittest @@ -181,6 +182,21 @@ def f(): _testinternalcapi.invalidate_executors(f.__code__) self.assertFalse(exe.is_valid()) + def test_sys__clear_internal_caches(self): + def f(): + for _ in range(1000): + pass + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + f() + exe = get_first_executor(f) + self.assertIsNotNone(exe) + self.assertTrue(exe.is_valid()) + sys._clear_internal_caches() + self.assertFalse(exe.is_valid()) + exe = get_first_executor(f) + self.assertIsNone(exe) + class TestUops(unittest.TestCase): def test_basic_loop(self): diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py index c52c014185bec7a..d4628f91daf7e85 100644 --- a/Lib/test/test_mailbox.py +++ b/Lib/test/test_mailbox.py @@ -10,6 +10,7 @@ import tempfile from test import support from test.support import os_helper +from test.support import refleak_helper from test.support import socket_helper import unittest import textwrap @@ -2443,6 +2444,9 @@ def test__all__(self): def tearDownModule(): support.reap_children() + # reap_children may have re-populated caches: + if refleak_helper.hunting_for_refleaks(): + sys._clear_internal_caches() if __name__ == '__main__': diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-07-18-04-36.gh-issue-114695.o9wP5P.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-07-18-04-36.gh-issue-114695.o9wP5P.rst new file mode 100644 index 000000000000000..a1db4de393eecb3 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-07-18-04-36.gh-issue-114695.o9wP5P.rst @@ -0,0 +1,3 @@ +Add :func:`sys._clear_internal_caches`, which clears all internal +performance-related caches (and deprecate the less-general +:func:`sys._clear_type_cache` function). diff --git a/Objects/codeobject.c b/Objects/codeobject.c index dc46b773c26528a..30336fa86111a71 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -1489,27 +1489,19 @@ PyCode_GetFreevars(PyCodeObject *code) static void clear_executors(PyCodeObject *co) { + assert(co->co_executors); for (int i = 0; i < co->co_executors->size; i++) { - Py_CLEAR(co->co_executors->executors[i]); + if (co->co_executors->executors[i]) { + _Py_ExecutorClear(co->co_executors->executors[i]); + } } PyMem_Free(co->co_executors); co->co_executors = NULL; } void -_PyCode_Clear_Executors(PyCodeObject *code) { - int code_len = (int)Py_SIZE(code); - for (int i = 0; i < code_len; i += _PyInstruction_GetLength(code, i)) { - _Py_CODEUNIT *instr = &_PyCode_CODE(code)[i]; - uint8_t opcode = instr->op.code; - uint8_t oparg = instr->op.arg; - if (opcode == ENTER_EXECUTOR) { - _PyExecutorObject *exec = code->co_executors->executors[oparg]; - assert(exec->vm_data.opcode != ENTER_EXECUTOR); - instr->op.code = exec->vm_data.opcode; - instr->op.arg = exec->vm_data.oparg; - } - } +_PyCode_Clear_Executors(PyCodeObject *code) +{ clear_executors(code); } @@ -2360,10 +2352,10 @@ _PyCode_ConstantKey(PyObject *op) void _PyStaticCode_Fini(PyCodeObject *co) { - deopt_code(co, _PyCode_CODE(co)); if (co->co_executors != NULL) { clear_executors(co); } + deopt_code(co, _PyCode_CODE(co)); PyMem_Free(co->co_extra); if (co->_co_cached != NULL) { Py_CLEAR(co->_co_cached->_co_code); diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 6fb4d719e43991c..197dff4b9888ce7 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2370,23 +2370,12 @@ dummy_func( CHECK_EVAL_BREAKER(); PyCodeObject *code = _PyFrame_GetCode(frame); - _PyExecutorObject *executor = code->co_executors->executors[oparg & 255]; - if (executor->vm_data.valid) { - Py_INCREF(executor); - current_executor = executor; - GOTO_TIER_TWO(); - } - else { - /* ENTER_EXECUTOR will be the first code unit of the instruction */ - assert(oparg < 256); - code->co_executors->executors[oparg] = NULL; - opcode = this_instr->op.code = executor->vm_data.opcode; - this_instr->op.arg = executor->vm_data.oparg; - oparg = executor->vm_data.oparg; - Py_DECREF(executor); - next_instr = this_instr; - DISPATCH_GOTO(); - } + current_executor = code->co_executors->executors[oparg & 255]; + assert(current_executor->vm_data.index == INSTR_OFFSET() - 1); + assert(current_executor->vm_data.code == code); + assert(current_executor->vm_data.valid); + Py_INCREF(current_executor); + GOTO_TIER_TWO(); } replaced op(_POP_JUMP_IF_FALSE, (cond -- )) { diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h index 93b8385a5b4097c..13f4ea81eb89842 100644 --- a/Python/clinic/sysmodule.c.h +++ b/Python/clinic/sysmodule.c.h @@ -1131,6 +1131,24 @@ sys__clear_type_cache(PyObject *module, PyObject *Py_UNUSED(ignored)) return sys__clear_type_cache_impl(module); } +PyDoc_STRVAR(sys__clear_internal_caches__doc__, +"_clear_internal_caches($module, /)\n" +"--\n" +"\n" +"Clear all internal performance-related caches."); + +#define SYS__CLEAR_INTERNAL_CACHES_METHODDEF \ + {"_clear_internal_caches", (PyCFunction)sys__clear_internal_caches, METH_NOARGS, sys__clear_internal_caches__doc__}, + +static PyObject * +sys__clear_internal_caches_impl(PyObject *module); + +static PyObject * +sys__clear_internal_caches(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return sys__clear_internal_caches_impl(module); +} + PyDoc_STRVAR(sys_is_finalizing__doc__, "is_finalizing($module, /)\n" "--\n" @@ -1486,4 +1504,4 @@ sys__get_cpu_count_config(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef SYS_GETANDROIDAPILEVEL_METHODDEF #define SYS_GETANDROIDAPILEVEL_METHODDEF #endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */ -/*[clinic end generated code: output=3dc3b2cb0ce38ebb input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b8b1c53e04c3b20c input=a9049054013a1b77]*/ diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 16f1db30620d722..e5244147d499af8 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -2363,29 +2363,18 @@ } TARGET(ENTER_EXECUTOR) { - _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(ENTER_EXECUTOR); TIER_ONE_ONLY CHECK_EVAL_BREAKER(); PyCodeObject *code = _PyFrame_GetCode(frame); - _PyExecutorObject *executor = code->co_executors->executors[oparg & 255]; - if (executor->vm_data.valid) { - Py_INCREF(executor); - current_executor = executor; - GOTO_TIER_TWO(); - } - else { - /* ENTER_EXECUTOR will be the first code unit of the instruction */ - assert(oparg < 256); - code->co_executors->executors[oparg] = NULL; - opcode = this_instr->op.code = executor->vm_data.opcode; - this_instr->op.arg = executor->vm_data.oparg; - oparg = executor->vm_data.oparg; - Py_DECREF(executor); - next_instr = this_instr; - DISPATCH_GOTO(); - } + current_executor = code->co_executors->executors[oparg & 255]; + assert(current_executor->vm_data.index == INSTR_OFFSET() - 1); + assert(current_executor->vm_data.code == code); + assert(current_executor->vm_data.valid); + Py_INCREF(current_executor); + GOTO_TIER_TWO(); DISPATCH(); } diff --git a/Python/optimizer.c b/Python/optimizer.c index d71ca0aef0e11ac..ad9ac382d300ef1 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -73,25 +73,21 @@ insert_executor(PyCodeObject *code, _Py_CODEUNIT *instr, int index, _PyExecutorO Py_INCREF(executor); if (instr->op.code == ENTER_EXECUTOR) { assert(index == instr->op.arg); - _PyExecutorObject *old = code->co_executors->executors[index]; - executor->vm_data.opcode = old->vm_data.opcode; - executor->vm_data.oparg = old->vm_data.oparg; - old->vm_data.opcode = 0; - code->co_executors->executors[index] = executor; - Py_DECREF(old); + _Py_ExecutorClear(code->co_executors->executors[index]); } else { assert(code->co_executors->size == index); assert(code->co_executors->capacity > index); - executor->vm_data.opcode = instr->op.code; - executor->vm_data.oparg = instr->op.arg; - code->co_executors->executors[index] = executor; - assert(index < MAX_EXECUTORS_SIZE); - instr->op.code = ENTER_EXECUTOR; - instr->op.arg = index; code->co_executors->size++; } - return; + executor->vm_data.opcode = instr->op.code; + executor->vm_data.oparg = instr->op.arg; + executor->vm_data.code = code; + executor->vm_data.index = (int)(instr - _PyCode_CODE(code)); + code->co_executors->executors[index] = executor; + assert(index < MAX_EXECUTORS_SIZE); + instr->op.code = ENTER_EXECUTOR; + instr->op.arg = index; } int @@ -1071,7 +1067,7 @@ link_executor(_PyExecutorObject *executor) } head->vm_data.links.next = executor; } - executor->vm_data.linked = true; + executor->vm_data.valid = true; /* executor_list_head must be first in list */ assert(interp->executor_list_head->vm_data.links.previous == NULL); } @@ -1079,7 +1075,7 @@ link_executor(_PyExecutorObject *executor) static void unlink_executor(_PyExecutorObject *executor) { - if (!executor->vm_data.linked) { + if (!executor->vm_data.valid) { return; } _PyExecutorLinkListNode *links = &executor->vm_data.links; @@ -1097,7 +1093,7 @@ unlink_executor(_PyExecutorObject *executor) assert(interp->executor_list_head == executor); interp->executor_list_head = next; } - executor->vm_data.linked = false; + executor->vm_data.valid = false; } /* This must be called by optimizers before using the executor */ @@ -1116,12 +1112,24 @@ void _Py_ExecutorClear(_PyExecutorObject *executor) { unlink_executor(executor); + PyCodeObject *code = executor->vm_data.code; + if (code == NULL) { + return; + } + _Py_CODEUNIT *instruction = &_PyCode_CODE(code)[executor->vm_data.index]; + assert(instruction->op.code == ENTER_EXECUTOR); + int index = instruction->op.arg; + assert(code->co_executors->executors[index] == executor); + instruction->op.code = executor->vm_data.opcode; + instruction->op.arg = executor->vm_data.oparg; + executor->vm_data.code = NULL; + Py_CLEAR(code->co_executors->executors[index]); } void _Py_Executor_DependsOn(_PyExecutorObject *executor, void *obj) { - assert(executor->vm_data.valid = true); + assert(executor->vm_data.valid); _Py_BloomFilter_Add(&executor->vm_data.bloom, obj); } @@ -1140,8 +1148,7 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj) assert(exec->vm_data.valid); _PyExecutorObject *next = exec->vm_data.links.next; if (bloom_filter_may_contain(&exec->vm_data.bloom, &obj_filter)) { - exec->vm_data.valid = false; - unlink_executor(exec); + _Py_ExecutorClear(exec); } exec = next; } @@ -1151,15 +1158,14 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj) void _Py_Executors_InvalidateAll(PyInterpreterState *interp) { - /* Walk the list of executors */ - for (_PyExecutorObject *exec = interp->executor_list_head; exec != NULL;) { - assert(exec->vm_data.valid); - _PyExecutorObject *next = exec->vm_data.links.next; - exec->vm_data.links.next = NULL; - exec->vm_data.links.previous = NULL; - exec->vm_data.valid = false; - exec->vm_data.linked = false; - exec = next; + while (interp->executor_list_head) { + _PyExecutorObject *executor = interp->executor_list_head; + if (executor->vm_data.code) { + // Clear the entire code object so its co_executors array be freed: + _PyCode_Clear_Executors(executor->vm_data.code); + } + else { + _Py_ExecutorClear(executor); + } } - interp->executor_list_head = NULL; } diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 437d7f8dfc49580..69b6d886ccc3e90 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2127,6 +2127,22 @@ sys__clear_type_cache_impl(PyObject *module) Py_RETURN_NONE; } +/*[clinic input] +sys._clear_internal_caches + +Clear all internal performance-related caches. +[clinic start generated code]*/ + +static PyObject * +sys__clear_internal_caches_impl(PyObject *module) +/*[clinic end generated code: output=0ee128670a4966d6 input=253e741ca744f6e8]*/ +{ + PyInterpreterState *interp = _PyInterpreterState_GET(); + _Py_Executors_InvalidateAll(interp); + PyType_ClearCache(); + Py_RETURN_NONE; +} + /* Note that, for now, we do not have a per-interpreter equivalent for sys.is_finalizing(). */ @@ -2461,6 +2477,7 @@ static PyMethodDef sys_methods[] = { {"audit", _PyCFunction_CAST(sys_audit), METH_FASTCALL, audit_doc }, {"breakpointhook", _PyCFunction_CAST(sys_breakpointhook), METH_FASTCALL | METH_KEYWORDS, breakpointhook_doc}, + SYS__CLEAR_INTERNAL_CACHES_METHODDEF SYS__CLEAR_TYPE_CACHE_METHODDEF SYS__CURRENT_FRAMES_METHODDEF SYS__CURRENT_EXCEPTIONS_METHODDEF From 72340d15cdfdfa4796fdd7c702094c852c2b32d2 Mon Sep 17 00:00:00 2001 From: John Belmonte <john@neggie.net> Date: Mon, 12 Feb 2024 20:17:51 +0900 Subject: [PATCH 220/507] gh-114563: C decimal falls back to pydecimal for unsupported format strings (GH-114879) Immediate merits: * eliminate complex workarounds for 'z' format support (NOTE: mpdecimal recently added 'z' support, so this becomes efficient in the long term.) * fix 'z' format memory leak * fix 'z' format applied to 'F' * fix missing '#' format support Suggested and prototyped by Stefan Krah. Fixes gh-114563, gh-91060 Co-authored-by: Stefan Krah <skrah@bytereef.org> --- Lib/test/test_decimal.py | 22 +++ ...-02-11-20-23-36.gh-issue-114563.RzxNYT.rst | 4 + Modules/_decimal/_decimal.c | 184 ++++++------------ 3 files changed, 88 insertions(+), 122 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-11-20-23-36.gh-issue-114563.RzxNYT.rst diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 1423bc61c7f6906..f23ea8af0c8772f 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -1110,6 +1110,13 @@ def test_formatting(self): ('z>z6.1f', '-0.', 'zzz0.0'), ('x>z6.1f', '-0.', 'xxx0.0'), ('🖤>z6.1f', '-0.', '🖤🖤🖤0.0'), # multi-byte fill char + ('\x00>z6.1f', '-0.', '\x00\x00\x000.0'), # null fill char + + # issue 114563 ('z' format on F type in cdecimal) + ('z3,.10F', '-6.24E-323', '0.0000000000'), + + # issue 91060 ('#' format in cdecimal) + ('#', '0', '0.'), # issue 6850 ('a=-7.0', '0.12345', 'aaaa0.1'), @@ -5726,6 +5733,21 @@ def test_c_signaldict_segfault(self): with self.assertRaisesRegex(ValueError, err_msg): sd.copy() + def test_format_fallback_capitals(self): + # Fallback to _pydecimal formatting (triggered by `#` format which + # is unsupported by mpdecimal) should honor the current context. + x = C.Decimal('6.09e+23') + self.assertEqual(format(x, '#'), '6.09E+23') + with C.localcontext(capitals=0): + self.assertEqual(format(x, '#'), '6.09e+23') + + def test_format_fallback_rounding(self): + y = C.Decimal('6.09') + self.assertEqual(format(y, '#.1f'), '6.1') + with C.localcontext(rounding=C.ROUND_DOWN): + self.assertEqual(format(y, '#.1f'), '6.0') + + @requires_docstrings @requires_cdecimal class SignatureTest(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2024-02-11-20-23-36.gh-issue-114563.RzxNYT.rst b/Misc/NEWS.d/next/Library/2024-02-11-20-23-36.gh-issue-114563.RzxNYT.rst new file mode 100644 index 000000000000000..013b6db8e6dbd78 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-11-20-23-36.gh-issue-114563.RzxNYT.rst @@ -0,0 +1,4 @@ +Fix several :func:`format()` bugs when using the C implementation of :class:`~decimal.Decimal`: +* memory leak in some rare cases when using the ``z`` format option (coerce negative 0) +* incorrect output when applying the ``z`` format option to type ``F`` (fixed-point with capital ``NAN`` / ``INF``) +* incorrect output when applying the ``#`` format option (alternate form) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 127f5f2887d4cd1..5b053c73e20bc95 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -82,6 +82,9 @@ typedef struct { /* Convert rationals for comparison */ PyObject *Rational; + /* Invariant: NULL or pointer to _pydecimal.Decimal */ + PyObject *PyDecimal; + PyObject *SignalTuple; struct DecCondMap *signal_map; @@ -3336,56 +3339,6 @@ dotsep_as_utf8(const char *s) return utf8; } -/* copy of libmpdec _mpd_round() */ -static void -_mpd_round(mpd_t *result, const mpd_t *a, mpd_ssize_t prec, - const mpd_context_t *ctx, uint32_t *status) -{ - mpd_ssize_t exp = a->exp + a->digits - prec; - - if (prec <= 0) { - mpd_seterror(result, MPD_Invalid_operation, status); - return; - } - if (mpd_isspecial(a) || mpd_iszero(a)) { - mpd_qcopy(result, a, status); - return; - } - - mpd_qrescale_fmt(result, a, exp, ctx, status); - if (result->digits > prec) { - mpd_qrescale_fmt(result, result, exp+1, ctx, status); - } -} - -/* Locate negative zero "z" option within a UTF-8 format spec string. - * Returns pointer to "z", else NULL. - * The portion of the spec we're working with is [[fill]align][sign][z] */ -static const char * -format_spec_z_search(char const *fmt, Py_ssize_t size) { - char const *pos = fmt; - char const *fmt_end = fmt + size; - /* skip over [[fill]align] (fill may be multi-byte character) */ - pos += 1; - while (pos < fmt_end && *pos & 0x80) { - pos += 1; - } - if (pos < fmt_end && strchr("<>=^", *pos) != NULL) { - pos += 1; - } else { - /* fill not present-- skip over [align] */ - pos = fmt; - if (pos < fmt_end && strchr("<>=^", *pos) != NULL) { - pos += 1; - } - } - /* skip over [sign] */ - if (pos < fmt_end && strchr("+- ", *pos) != NULL) { - pos += 1; - } - return pos < fmt_end && *pos == 'z' ? pos : NULL; -} - static int dict_get_item_string(PyObject *dict, const char *key, PyObject **valueobj, const char **valuestr) { @@ -3411,6 +3364,48 @@ dict_get_item_string(PyObject *dict, const char *key, PyObject **valueobj, const return 0; } +/* + * Fallback _pydecimal formatting for new format specifiers that mpdecimal does + * not yet support. As documented, libmpdec follows the PEP-3101 format language: + * https://www.bytereef.org/mpdecimal/doc/libmpdec/assign-convert.html#to-string + */ +static PyObject * +pydec_format(PyObject *dec, PyObject *context, PyObject *fmt, decimal_state *state) +{ + PyObject *result; + PyObject *pydec; + PyObject *u; + + if (state->PyDecimal == NULL) { + state->PyDecimal = _PyImport_GetModuleAttrString("_pydecimal", "Decimal"); + if (state->PyDecimal == NULL) { + return NULL; + } + } + + u = dec_str(dec); + if (u == NULL) { + return NULL; + } + + pydec = PyObject_CallOneArg(state->PyDecimal, u); + Py_DECREF(u); + if (pydec == NULL) { + return NULL; + } + + result = PyObject_CallMethod(pydec, "__format__", "(OO)", fmt, context); + Py_DECREF(pydec); + + if (result == NULL && PyErr_ExceptionMatches(PyExc_ValueError)) { + /* Do not confuse users with the _pydecimal exception */ + PyErr_Clear(); + PyErr_SetString(PyExc_ValueError, "invalid format string"); + } + + return result; +} + /* Formatted representation of a PyDecObject. */ static PyObject * dec_format(PyObject *dec, PyObject *args) @@ -3423,16 +3418,11 @@ dec_format(PyObject *dec, PyObject *args) PyObject *fmtarg; PyObject *context; mpd_spec_t spec; - char const *fmt; - char *fmt_copy = NULL; + char *fmt; char *decstring = NULL; uint32_t status = 0; int replace_fillchar = 0; - int no_neg_0 = 0; Py_ssize_t size; - mpd_t *mpd = MPD(dec); - mpd_uint_t dt[MPD_MINALLOC_MAX]; - mpd_t tmp = {MPD_STATIC|MPD_STATIC_DATA,0,0,0,MPD_MINALLOC_MAX,dt}; decimal_state *state = get_module_state_by_def(Py_TYPE(dec)); @@ -3442,7 +3432,7 @@ dec_format(PyObject *dec, PyObject *args) } if (PyUnicode_Check(fmtarg)) { - fmt = PyUnicode_AsUTF8AndSize(fmtarg, &size); + fmt = (char *)PyUnicode_AsUTF8AndSize(fmtarg, &size); if (fmt == NULL) { return NULL; } @@ -3454,35 +3444,15 @@ dec_format(PyObject *dec, PyObject *args) } } - /* NOTE: If https://github.com/python/cpython/pull/29438 lands, the - * format string manipulation below can be eliminated by enhancing - * the forked mpd_parse_fmt_str(). */ if (size > 0 && fmt[0] == '\0') { /* NUL fill character: must be replaced with a valid UTF-8 char before calling mpd_parse_fmt_str(). */ replace_fillchar = 1; - fmt = fmt_copy = dec_strdup(fmt, size); - if (fmt_copy == NULL) { + fmt = dec_strdup(fmt, size); + if (fmt == NULL) { return NULL; } - fmt_copy[0] = '_'; - } - /* Strip 'z' option, which isn't understood by mpd_parse_fmt_str(). - * NOTE: fmt is always null terminated by PyUnicode_AsUTF8AndSize() */ - char const *z_position = format_spec_z_search(fmt, size); - if (z_position != NULL) { - no_neg_0 = 1; - size_t z_index = z_position - fmt; - if (fmt_copy == NULL) { - fmt = fmt_copy = dec_strdup(fmt, size); - if (fmt_copy == NULL) { - return NULL; - } - } - /* Shift characters (including null terminator) left, - overwriting the 'z' option. */ - memmove(fmt_copy + z_index, fmt_copy + z_index + 1, size - z_index); - size -= 1; + fmt[0] = '_'; } } else { @@ -3492,10 +3462,13 @@ dec_format(PyObject *dec, PyObject *args) } if (!mpd_parse_fmt_str(&spec, fmt, CtxCaps(context))) { - PyErr_SetString(PyExc_ValueError, - "invalid format string"); - goto finish; + if (replace_fillchar) { + PyMem_Free(fmt); + } + + return pydec_format(dec, context, fmtarg, state); } + if (replace_fillchar) { /* In order to avoid clobbering parts of UTF-8 thousands separators or decimal points when the substitution is reversed later, the actual @@ -3548,45 +3521,8 @@ dec_format(PyObject *dec, PyObject *args) } } - if (no_neg_0 && mpd_isnegative(mpd) && !mpd_isspecial(mpd)) { - /* Round into a temporary (carefully mirroring the rounding - of mpd_qformat_spec()), and check if the result is negative zero. - If so, clear the sign and format the resulting positive zero. */ - mpd_ssize_t prec; - mpd_qcopy(&tmp, mpd, &status); - if (spec.prec >= 0) { - switch (spec.type) { - case 'f': - mpd_qrescale(&tmp, &tmp, -spec.prec, CTX(context), &status); - break; - case '%': - tmp.exp += 2; - mpd_qrescale(&tmp, &tmp, -spec.prec, CTX(context), &status); - break; - case 'g': - prec = (spec.prec == 0) ? 1 : spec.prec; - if (tmp.digits > prec) { - _mpd_round(&tmp, &tmp, prec, CTX(context), &status); - } - break; - case 'e': - if (!mpd_iszero(&tmp)) { - _mpd_round(&tmp, &tmp, spec.prec+1, CTX(context), &status); - } - break; - } - } - if (status & MPD_Errors) { - PyErr_SetString(PyExc_ValueError, "unexpected error when rounding"); - goto finish; - } - if (mpd_iszero(&tmp)) { - mpd_set_positive(&tmp); - mpd = &tmp; - } - } - decstring = mpd_qformat_spec(mpd, &spec, CTX(context), &status); + decstring = mpd_qformat_spec(MPD(dec), &spec, CTX(context), &status); if (decstring == NULL) { if (status & MPD_Malloc_error) { PyErr_NoMemory(); @@ -3609,7 +3545,7 @@ dec_format(PyObject *dec, PyObject *args) Py_XDECREF(grouping); Py_XDECREF(sep); Py_XDECREF(dot); - if (fmt_copy) PyMem_Free(fmt_copy); + if (replace_fillchar) PyMem_Free(fmt); if (decstring) mpd_free(decstring); return result; } @@ -5987,6 +5923,9 @@ _decimal_exec(PyObject *m) Py_CLEAR(collections_abc); Py_CLEAR(MutableMapping); + /* For format specifiers not yet supported by libmpdec */ + state->PyDecimal = NULL; + /* Add types to the module */ CHECK_INT(PyModule_AddType(m, state->PyDec_Type)); CHECK_INT(PyModule_AddType(m, state->PyDecContext_Type)); @@ -6192,6 +6131,7 @@ decimal_clear(PyObject *module) Py_CLEAR(state->extended_context_template); Py_CLEAR(state->Rational); Py_CLEAR(state->SignalTuple); + Py_CLEAR(state->PyDecimal); PyMem_Free(state->signal_map); PyMem_Free(state->cond_map); From 705c76d4a202f1faf41027d48d44eac0e76bb1f0 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Mon, 12 Feb 2024 14:59:58 +0300 Subject: [PATCH 221/507] gh-114785: Remove content from `Porting from Python2` how-to (#114805) Keep the page though, because people might still rely on it (the traffic shows that they do). Instead of our own manual we now give links to the 3rd-party ones. Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/howto/index.rst | 1 - Doc/howto/pyporting.rst | 429 ++-------------------------------------- README.rst | 9 - 3 files changed, 19 insertions(+), 420 deletions(-) diff --git a/Doc/howto/index.rst b/Doc/howto/index.rst index a835bb5f13bd1c9..bb507953582639c 100644 --- a/Doc/howto/index.rst +++ b/Doc/howto/index.rst @@ -13,7 +13,6 @@ Currently, the HOWTOs are: .. toctree:: :maxdepth: 1 - pyporting.rst cporting.rst curses.rst descriptor.rst diff --git a/Doc/howto/pyporting.rst b/Doc/howto/pyporting.rst index 501b16d82d4d6fc..d560364107bd125 100644 --- a/Doc/howto/pyporting.rst +++ b/Doc/howto/pyporting.rst @@ -1,3 +1,5 @@ +:orphan: + .. _pyporting-howto: ************************************* @@ -6,423 +8,30 @@ How to port Python 2 Code to Python 3 :author: Brett Cannon -.. topic:: Abstract - - Python 2 reached its official end-of-life at the start of 2020. This means - that no new bug reports, fixes, or changes will be made to Python 2 - it's - no longer supported. - - This guide is intended to provide you with a path to Python 3 for your - code, that includes compatibility with Python 2 as a first step. - - If you are looking to port an extension module instead of pure Python code, - please see :ref:`cporting-howto`. - - The archived python-porting_ mailing list may contain some useful guidance. - - -The Short Explanation -===================== - -To achieve Python 2/3 compatibility in a single code base, the basic steps -are: - -#. Only worry about supporting Python 2.7 -#. Make sure you have good test coverage (coverage.py_ can help; - ``python -m pip install coverage``) -#. Learn the differences between Python 2 and 3 -#. Use Futurize_ (or Modernize_) to update your code (e.g. ``python -m pip install future``) -#. Use Pylint_ to help make sure you don't regress on your Python 3 support - (``python -m pip install pylint``) -#. Use caniusepython3_ to find out which of your dependencies are blocking your - use of Python 3 (``python -m pip install caniusepython3``) -#. Once your dependencies are no longer blocking you, use continuous integration - to make sure you stay compatible with Python 2 and 3 (tox_ can help test - against multiple versions of Python; ``python -m pip install tox``) -#. Consider using optional :term:`static type checking <static type checker>` - to make sure your type usage - works in both Python 2 and 3 (e.g. use mypy_ to check your typing under both - Python 2 and Python 3; ``python -m pip install mypy``). - -.. note:: - - Note: Using ``python -m pip install`` guarantees that the ``pip`` you invoke - is the one installed for the Python currently in use, whether it be - a system-wide ``pip`` or one installed within a - :ref:`virtual environment <tut-venv>`. - -Details -======= - -Even if other factors - say, dependencies over which you have no control - -still require you to support Python 2, that does not prevent you taking the -step of including Python 3 support. - -Most changes required to support Python 3 lead to cleaner code using newer -practices even in Python 2 code. - - -Different versions of Python 2 ------------------------------- - -Ideally, your code should be compatible with Python 2.7, which was the -last supported version of Python 2. - -Some of the tools mentioned in this guide will not work with Python 2.6. - -If absolutely necessary, the six_ project can help you support Python 2.5 and -3 simultaneously. Do realize, though, that nearly all the projects listed in -this guide will not be available to you. - -If you are able to skip Python 2.5 and older, the required changes to your -code will be minimal. At worst you will have to use a function instead of a -method in some instances or have to import a function instead of using a -built-in one. - - -Make sure you specify the proper version support in your ``setup.py`` file --------------------------------------------------------------------------- - -In your ``setup.py`` file you should have the proper `trove classifier`_ -specifying what versions of Python you support. As your project does not support -Python 3 yet you should at least have -``Programming Language :: Python :: 2 :: Only`` specified. Ideally you should -also specify each major/minor version of Python that you do support, e.g. -``Programming Language :: Python :: 2.7``. - - -Have good test coverage ------------------------ - -Once you have your code supporting the oldest version of Python 2 you want it -to, you will want to make sure your test suite has good coverage. A good rule of -thumb is that if you want to be confident enough in your test suite that any -failures that appear after having tools rewrite your code are actual bugs in the -tools and not in your code. If you want a number to aim for, try to get over 80% -coverage (and don't feel bad if you find it hard to get better than 90% -coverage). If you don't already have a tool to measure test coverage then -coverage.py_ is recommended. - - -Be aware of the differences between Python 2 and 3 --------------------------------------------------- - -Once you have your code well-tested you are ready to begin porting your code to -Python 3! But to fully understand how your code is going to change and what -you want to look out for while you code, you will want to learn what changes -Python 3 makes in terms of Python 2. - -Some resources for understanding the differences and their implications for you -code: - -* the :ref:`"What's New" <whatsnew-index>` doc for each release of Python 3 -* the `Porting to Python 3`_ book (which is free online) -* the handy `cheat sheet`_ from the Python-Future project. - - -Update your code ----------------- - -There are tools available that can port your code automatically. - -Futurize_ does its best to make Python 3 idioms and practices exist in Python -2, e.g. backporting the ``bytes`` type from Python 3 so that you have -semantic parity between the major versions of Python. This is the better -approach for most cases. - -Modernize_, on the other hand, is more conservative and targets a Python 2/3 -subset of Python, directly relying on six_ to help provide compatibility. - -A good approach is to run the tool over your test suite first and visually -inspect the diff to make sure the transformation is accurate. After you have -transformed your test suite and verified that all the tests still pass as -expected, then you can transform your application code knowing that any tests -which fail is a translation failure. - -Unfortunately the tools can't automate everything to make your code work under -Python 3, and you will also need to read the tools' documentation in case some -options you need are turned off by default. - -Key issues to be aware of and check for: - -Division -++++++++ - -In Python 3, ``5 / 2 == 2.5`` and not ``2`` as it was in Python 2; all -division between ``int`` values result in a ``float``. This change has -actually been planned since Python 2.2 which was released in 2002. Since then -users have been encouraged to add ``from __future__ import division`` to any -and all files which use the ``/`` and ``//`` operators or to be running the -interpreter with the ``-Q`` flag. If you have not been doing this then you -will need to go through your code and do two things: - -#. Add ``from __future__ import division`` to your files -#. Update any division operator as necessary to either use ``//`` to use floor - division or continue using ``/`` and expect a float - -The reason that ``/`` isn't simply translated to ``//`` automatically is that if -an object defines a ``__truediv__`` method but not ``__floordiv__`` then your -code would begin to fail (e.g. a user-defined class that uses ``/`` to -signify some operation but not ``//`` for the same thing or at all). +Python 2 reached its official end-of-life at the start of 2020. This means +that no new bug reports, fixes, or changes will be made to Python 2 - it's +no longer supported: see :pep:`373` and +`status of Python versions <https://devguide.python.org/versions>`_. +If you are looking to port an extension module instead of pure Python code, +please see :ref:`cporting-howto`. -Text versus binary data -+++++++++++++++++++++++ +The archived python-porting_ mailing list may contain some useful guidance. -In Python 2 you could use the ``str`` type for both text and binary data. -Unfortunately this confluence of two different concepts could lead to brittle -code which sometimes worked for either kind of data, sometimes not. It also -could lead to confusing APIs if people didn't explicitly state that something -that accepted ``str`` accepted either text or binary data instead of one -specific type. This complicated the situation especially for anyone supporting -multiple languages as APIs wouldn't bother explicitly supporting ``unicode`` -when they claimed text data support. +Since Python 3.13 the original porting guide was discontinued. +You can find the old guide in the +`archive <https://docs.python.org/3.12/howto/pyporting.html>`_. -Python 3 made text and binary data distinct types that cannot simply be mixed -together. For any code that deals only with text or only binary data, this -separation doesn't pose an issue. But for code that has to deal with both, it -does mean you might have to now care about when you are using text compared -to binary data, which is why this cannot be entirely automated. -Decide which APIs take text and which take binary (it is **highly** recommended -you don't design APIs that can take both due to the difficulty of keeping the -code working; as stated earlier it is difficult to do well). In Python 2 this -means making sure the APIs that take text can work with ``unicode`` and those -that work with binary data work with the ``bytes`` type from Python 3 -(which is a subset of ``str`` in Python 2 and acts as an alias for ``bytes`` -type in Python 2). Usually the biggest issue is realizing which methods exist -on which types in Python 2 and 3 simultaneously (for text that's ``unicode`` -in Python 2 and ``str`` in Python 3, for binary that's ``str``/``bytes`` in -Python 2 and ``bytes`` in Python 3). +Third-party guides +================== -The following table lists the **unique** methods of each data type across -Python 2 and 3 (e.g., the ``decode()`` method is usable on the equivalent binary -data type in either Python 2 or 3, but it can't be used by the textual data -type consistently between Python 2 and 3 because ``str`` in Python 3 doesn't -have the method). Do note that as of Python 3.5 the ``__mod__`` method was -added to the bytes type. +There are also multiple third-party guides that might be useful: -======================== ===================== -**Text data** **Binary data** ------------------------- --------------------- -\ decode ------------------------- --------------------- -encode ------------------------- --------------------- -format ------------------------- --------------------- -isdecimal ------------------------- --------------------- -isnumeric -======================== ===================== +- `Guide by Fedora <https://portingguide.readthedocs.io>`_ +- `PyCon 2020 tutorial <https://www.youtube.com/watch?v=JgIgEjASOlk>`_ +- `Guide by DigitalOcean <https://www.digitalocean.com/community/tutorials/how-to-port-python-2-code-to-python-3>`_ +- `Guide by ActiveState <https://www.activestate.com/blog/how-to-migrate-python-2-applications-to-python-3>`_ -Making the distinction easier to handle can be accomplished by encoding and -decoding between binary data and text at the edge of your code. This means that -when you receive text in binary data, you should immediately decode it. And if -your code needs to send text as binary data then encode it as late as possible. -This allows your code to work with only text internally and thus eliminates -having to keep track of what type of data you are working with. -The next issue is making sure you know whether the string literals in your code -represent text or binary data. You should add a ``b`` prefix to any -literal that presents binary data. For text you should add a ``u`` prefix to -the text literal. (There is a :mod:`__future__` import to force all unspecified -literals to be Unicode, but usage has shown it isn't as effective as adding a -``b`` or ``u`` prefix to all literals explicitly) - -You also need to be careful about opening files. Possibly you have not always -bothered to add the ``b`` mode when opening a binary file (e.g., ``rb`` for -binary reading). Under Python 3, binary files and text files are clearly -distinct and mutually incompatible; see the :mod:`io` module for details. -Therefore, you **must** make a decision of whether a file will be used for -binary access (allowing binary data to be read and/or written) or textual access -(allowing text data to be read and/or written). You should also use :func:`io.open` -for opening files instead of the built-in :func:`open` function as the :mod:`io` -module is consistent from Python 2 to 3 while the built-in :func:`open` function -is not (in Python 3 it's actually :func:`io.open`). Do not bother with the -outdated practice of using :func:`codecs.open` as that's only necessary for -keeping compatibility with Python 2.5. - -The constructors of both ``str`` and ``bytes`` have different semantics for the -same arguments between Python 2 and 3. Passing an integer to ``bytes`` in Python 2 -will give you the string representation of the integer: ``bytes(3) == '3'``. -But in Python 3, an integer argument to ``bytes`` will give you a bytes object -as long as the integer specified, filled with null bytes: -``bytes(3) == b'\x00\x00\x00'``. A similar worry is necessary when passing a -bytes object to ``str``. In Python 2 you just get the bytes object back: -``str(b'3') == b'3'``. But in Python 3 you get the string representation of the -bytes object: ``str(b'3') == "b'3'"``. - -Finally, the indexing of binary data requires careful handling (slicing does -**not** require any special handling). In Python 2, -``b'123'[1] == b'2'`` while in Python 3 ``b'123'[1] == 50``. Because binary data -is simply a collection of binary numbers, Python 3 returns the integer value for -the byte you index on. But in Python 2 because ``bytes == str``, indexing -returns a one-item slice of bytes. The six_ project has a function -named ``six.indexbytes()`` which will return an integer like in Python 3: -``six.indexbytes(b'123', 1)``. - -To summarize: - -#. Decide which of your APIs take text and which take binary data -#. Make sure that your code that works with text also works with ``unicode`` and - code for binary data works with ``bytes`` in Python 2 (see the table above - for what methods you cannot use for each type) -#. Mark all binary literals with a ``b`` prefix, textual literals with a ``u`` - prefix -#. Decode binary data to text as soon as possible, encode text as binary data as - late as possible -#. Open files using :func:`io.open` and make sure to specify the ``b`` mode when - appropriate -#. Be careful when indexing into binary data - - -Use feature detection instead of version detection -++++++++++++++++++++++++++++++++++++++++++++++++++ - -Inevitably you will have code that has to choose what to do based on what -version of Python is running. The best way to do this is with feature detection -of whether the version of Python you're running under supports what you need. -If for some reason that doesn't work then you should make the version check be -against Python 2 and not Python 3. To help explain this, let's look at an -example. - -Let's pretend that you need access to a feature of :mod:`importlib` that -is available in Python's standard library since Python 3.3 and available for -Python 2 through importlib2_ on PyPI. You might be tempted to write code to -access e.g. the :mod:`importlib.abc` module by doing the following:: - - import sys - - if sys.version_info[0] == 3: - from importlib import abc - else: - from importlib2 import abc - -The problem with this code is what happens when Python 4 comes out? It would -be better to treat Python 2 as the exceptional case instead of Python 3 and -assume that future Python versions will be more compatible with Python 3 than -Python 2:: - - import sys - - if sys.version_info[0] > 2: - from importlib import abc - else: - from importlib2 import abc - -The best solution, though, is to do no version detection at all and instead rely -on feature detection. That avoids any potential issues of getting the version -detection wrong and helps keep you future-compatible:: - - try: - from importlib import abc - except ImportError: - from importlib2 import abc - - -Prevent compatibility regressions ---------------------------------- - -Once you have fully translated your code to be compatible with Python 3, you -will want to make sure your code doesn't regress and stop working under -Python 3. This is especially true if you have a dependency which is blocking you -from actually running under Python 3 at the moment. - -To help with staying compatible, any new modules you create should have -at least the following block of code at the top of it:: - - from __future__ import absolute_import - from __future__ import division - from __future__ import print_function - -You can also run Python 2 with the ``-3`` flag to be warned about various -compatibility issues your code triggers during execution. If you turn warnings -into errors with ``-Werror`` then you can make sure that you don't accidentally -miss a warning. - -You can also use the Pylint_ project and its ``--py3k`` flag to lint your code -to receive warnings when your code begins to deviate from Python 3 -compatibility. This also prevents you from having to run Modernize_ or Futurize_ -over your code regularly to catch compatibility regressions. This does require -you only support Python 2.7 and Python 3.4 or newer as that is Pylint's -minimum Python version support. - - -Check which dependencies block your transition ----------------------------------------------- - -**After** you have made your code compatible with Python 3 you should begin to -care about whether your dependencies have also been ported. The caniusepython3_ -project was created to help you determine which projects --- directly or indirectly -- are blocking you from supporting Python 3. There -is both a command-line tool as well as a web interface at -https://caniusepython3.com. - -The project also provides code which you can integrate into your test suite so -that you will have a failing test when you no longer have dependencies blocking -you from using Python 3. This allows you to avoid having to manually check your -dependencies and to be notified quickly when you can start running on Python 3. - - -Update your ``setup.py`` file to denote Python 3 compatibility --------------------------------------------------------------- - -Once your code works under Python 3, you should update the classifiers in -your ``setup.py`` to contain ``Programming Language :: Python :: 3`` and to not -specify sole Python 2 support. This will tell anyone using your code that you -support Python 2 **and** 3. Ideally you will also want to add classifiers for -each major/minor version of Python you now support. - - -Use continuous integration to stay compatible ---------------------------------------------- - -Once you are able to fully run under Python 3 you will want to make sure your -code always works under both Python 2 and 3. Probably the best tool for running -your tests under multiple Python interpreters is tox_. You can then integrate -tox with your continuous integration system so that you never accidentally break -Python 2 or 3 support. - -You may also want to use the ``-bb`` flag with the Python 3 interpreter to -trigger an exception when you are comparing bytes to strings or bytes to an int -(the latter is available starting in Python 3.5). By default type-differing -comparisons simply return ``False``, but if you made a mistake in your -separation of text/binary data handling or indexing on bytes you wouldn't easily -find the mistake. This flag will raise an exception when these kinds of -comparisons occur, making the mistake much easier to track down. - - -Consider using optional static type checking --------------------------------------------- - -Another way to help port your code is to use a :term:`static type checker` like -mypy_ or pytype_ on your code. These tools can be used to analyze your code as -if it's being run under Python 2, then you can run the tool a second time as if -your code is running under Python 3. By running a static type checker twice like -this you can discover if you're e.g. misusing binary data type in one version -of Python compared to another. If you add optional type hints to your code you -can also explicitly state whether your APIs use textual or binary data, helping -to make sure everything functions as expected in both versions of Python. - - -.. _caniusepython3: https://pypi.org/project/caniusepython3 -.. _cheat sheet: https://python-future.org/compatible_idioms.html -.. _coverage.py: https://pypi.org/project/coverage -.. _Futurize: https://python-future.org/automatic_conversion.html -.. _importlib2: https://pypi.org/project/importlib2 -.. _Modernize: https://python-modernize.readthedocs.io/ -.. _mypy: https://mypy-lang.org/ -.. _Porting to Python 3: http://python3porting.com/ -.. _Pylint: https://pypi.org/project/pylint - -.. _Python 3 Q & A: https://ncoghlan-devs-python-notes.readthedocs.io/en/latest/python3/questions_and_answers.html - -.. _pytype: https://github.com/google/pytype -.. _python-future: https://python-future.org/ .. _python-porting: https://mail.python.org/pipermail/python-porting/ -.. _six: https://pypi.org/project/six -.. _tox: https://pypi.org/project/tox -.. _trove classifier: https://pypi.org/classifiers - -.. _Why Python 3 exists: https://snarky.ca/why-python-3-exists diff --git a/README.rst b/README.rst index fbfae16a7dbb0b1..1145fd437558406 100644 --- a/README.rst +++ b/README.rst @@ -161,15 +161,6 @@ For information about building Python's documentation, refer to `Doc/README.rst <https://github.com/python/cpython/blob/main/Doc/README.rst>`_. -Converting From Python 2.x to 3.x ---------------------------------- - -Significant backward incompatible changes were made for the release of Python -3.0, which may cause programs written for Python 2 to fail when run with Python -3. For more information about porting your code from Python 2 to Python 3, see -the `Porting HOWTO <https://docs.python.org/3/howto/pyporting.html>`_. - - Testing ------- From 92483b21b30d451586c54dc4923665f7f7eedd7a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 12 Feb 2024 14:40:41 +0200 Subject: [PATCH 222/507] gh-101100: Fix Sphinx warnings in `whatsnew/2.7.rst` and related (#115319) Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> --- Doc/library/asyncio-protocol.rst | 4 +- Doc/library/asyncio-subprocess.rst | 6 +- Doc/library/msvcrt.rst | 15 +++ Doc/library/multiprocessing.rst | 4 +- Doc/library/subprocess.rst | 4 +- Doc/whatsnew/2.6.rst | 8 +- Doc/whatsnew/2.7.rst | 170 +++++++++++++++-------------- Doc/whatsnew/3.1.rst | 2 +- Doc/whatsnew/3.2.rst | 6 +- 9 files changed, 119 insertions(+), 100 deletions(-) diff --git a/Doc/library/asyncio-protocol.rst b/Doc/library/asyncio-protocol.rst index 3f734f544afe216..ecd8cdc709af7d2 100644 --- a/Doc/library/asyncio-protocol.rst +++ b/Doc/library/asyncio-protocol.rst @@ -417,8 +417,8 @@ Subprocess Transports Stop the subprocess. - On POSIX systems, this method sends SIGTERM to the subprocess. - On Windows, the Windows API function TerminateProcess() is called to + On POSIX systems, this method sends :py:const:`~signal.SIGTERM` to the subprocess. + On Windows, the Windows API function :c:func:`!TerminateProcess` is called to stop the subprocess. See also :meth:`subprocess.Popen.terminate`. diff --git a/Doc/library/asyncio-subprocess.rst b/Doc/library/asyncio-subprocess.rst index bf35b1cb798aee1..817a6ff3052f4aa 100644 --- a/Doc/library/asyncio-subprocess.rst +++ b/Doc/library/asyncio-subprocess.rst @@ -240,7 +240,7 @@ their completion. .. note:: - On Windows, :py:data:`SIGTERM` is an alias for :meth:`terminate`. + On Windows, :py:const:`~signal.SIGTERM` is an alias for :meth:`terminate`. ``CTRL_C_EVENT`` and ``CTRL_BREAK_EVENT`` can be sent to processes started with a *creationflags* parameter which includes ``CREATE_NEW_PROCESS_GROUP``. @@ -249,10 +249,10 @@ their completion. Stop the child process. - On POSIX systems this method sends :py:const:`signal.SIGTERM` to the + On POSIX systems this method sends :py:const:`~signal.SIGTERM` to the child process. - On Windows the Win32 API function :c:func:`TerminateProcess` is + On Windows the Win32 API function :c:func:`!TerminateProcess` is called to stop the child process. .. method:: kill() diff --git a/Doc/library/msvcrt.rst b/Doc/library/msvcrt.rst index 2a6d980ab78a609..ac3458c86fd4c4f 100644 --- a/Doc/library/msvcrt.rst +++ b/Doc/library/msvcrt.rst @@ -252,3 +252,18 @@ Other Functions .. data:: CRTDBG_REPORT_MODE Returns current *mode* for the specified *type*. + + +.. data:: CRT_ASSEMBLY_VERSION + + The CRT Assembly version, from the :file:`crtassem.h` header file. + + +.. data:: VC_ASSEMBLY_PUBLICKEYTOKEN + + The VC Assembly public key token, from the :file:`crtassem.h` header file. + + +.. data:: LIBRARIES_ASSEMBLY_NAME_PREFIX + + The Libraries Assembly name prefix, from the :file:`crtassem.h` header file. diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst index b104a6483b70e6f..d570d4eb0dae78d 100644 --- a/Doc/library/multiprocessing.rst +++ b/Doc/library/multiprocessing.rst @@ -649,8 +649,8 @@ The :mod:`multiprocessing` package mostly replicates the API of the .. method:: terminate() - Terminate the process. On POSIX this is done using the ``SIGTERM`` signal; - on Windows :c:func:`TerminateProcess` is used. Note that exit handlers and + Terminate the process. On POSIX this is done using the :py:const:`~signal.SIGTERM` signal; + on Windows :c:func:`!TerminateProcess` is used. Note that exit handlers and finally clauses, etc., will not be executed. Note that descendant processes of the process will *not* be terminated -- diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst index f63ca73b3ec067e..1dcfea58a8e89f6 100644 --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -857,8 +857,8 @@ Instances of the :class:`Popen` class have the following methods: .. method:: Popen.terminate() - Stop the child. On POSIX OSs the method sends SIGTERM to the - child. On Windows the Win32 API function :c:func:`TerminateProcess` is called + Stop the child. On POSIX OSs the method sends :py:const:`~signal.SIGTERM` to the + child. On Windows the Win32 API function :c:func:`!TerminateProcess` is called to stop the child. diff --git a/Doc/whatsnew/2.6.rst b/Doc/whatsnew/2.6.rst index 7d3769a22286e28..05c21d313aae035 100644 --- a/Doc/whatsnew/2.6.rst +++ b/Doc/whatsnew/2.6.rst @@ -2388,11 +2388,11 @@ changes, or look through the Subversion logs for all the details. using the format character ``'?'``. (Contributed by David Remahl.) -* The :class:`Popen` objects provided by the :mod:`subprocess` module - now have :meth:`terminate`, :meth:`kill`, and :meth:`send_signal` methods. - On Windows, :meth:`send_signal` only supports the :const:`SIGTERM` +* The :class:`~subprocess.Popen` objects provided by the :mod:`subprocess` module + now have :meth:`~subprocess.Popen.terminate`, :meth:`~subprocess.Popen.kill`, and :meth:`~subprocess.Popen.send_signal` methods. + On Windows, :meth:`!send_signal` only supports the :py:const:`~signal.SIGTERM` signal, and all these methods are aliases for the Win32 API function - :c:func:`TerminateProcess`. + :c:func:`!TerminateProcess`. (Contributed by Christian Heimes.) * A new variable in the :mod:`sys` module, :attr:`float_info`, is an diff --git a/Doc/whatsnew/2.7.rst b/Doc/whatsnew/2.7.rst index ada05aa22b46f66..2a42664c02852c2 100644 --- a/Doc/whatsnew/2.7.rst +++ b/Doc/whatsnew/2.7.rst @@ -196,7 +196,7 @@ A partial list of 3.1 features that were backported to 2.7: Other new Python3-mode warnings include: -* :func:`operator.isCallable` and :func:`operator.sequenceIncludes`, +* :func:`!operator.isCallable` and :func:`!operator.sequenceIncludes`, which are not supported in 3.x, now trigger warnings. * The :option:`!-3` switch now automatically enables the :option:`!-Qwarn` switch that causes warnings @@ -455,11 +455,11 @@ a varying number of handlers. All this flexibility can require a lot of configuration. You can write Python statements to create objects and set their properties, but a complex set-up requires verbose but boring code. -:mod:`logging` also supports a :func:`~logging.fileConfig` +:mod:`logging` also supports a :func:`~logging.config.fileConfig` function that parses a file, but the file format doesn't support configuring filters, and it's messier to generate programmatically. -Python 2.7 adds a :func:`~logging.dictConfig` function that +Python 2.7 adds a :func:`~logging.config.dictConfig` function that uses a dictionary to configure logging. There are many ways to produce a dictionary from different sources: construct one with code; parse a file containing JSON; or use a YAML parsing library if one is @@ -533,7 +533,7 @@ implemented by Vinay Sajip, are: ``getLogger('app.network.listen')``. * The :class:`~logging.LoggerAdapter` class gained an - :meth:`~logging.LoggerAdapter.isEnabledFor` method that takes a + :meth:`~logging.Logger.isEnabledFor` method that takes a *level* and returns whether the underlying logger would process a message of that level of importance. @@ -554,8 +554,8 @@ called a :dfn:`view` instead of a fully materialized list. It's not possible to change the return values of :meth:`~dict.keys`, :meth:`~dict.values`, and :meth:`~dict.items` in Python 2.7 because too much code would break. Instead the 3.x versions were added -under the new names :meth:`~dict.viewkeys`, :meth:`~dict.viewvalues`, -and :meth:`~dict.viewitems`. +under the new names :meth:`!viewkeys`, :meth:`!viewvalues`, +and :meth:`!viewitems`. :: @@ -720,7 +720,7 @@ Some smaller changes made to the core Python language are: with B() as b: ... suite of statements ... - The :func:`contextlib.nested` function provides a very similar + The :func:`!contextlib.nested` function provides a very similar function, so it's no longer necessary and has been deprecated. (Proposed in https://codereview.appspot.com/53094; implemented by @@ -785,7 +785,7 @@ Some smaller changes made to the core Python language are: implemented by Mark Dickinson; :issue:`1811`.) * Implicit coercion for complex numbers has been removed; the interpreter - will no longer ever attempt to call a :meth:`__coerce__` method on complex + will no longer ever attempt to call a :meth:`!__coerce__` method on complex objects. (Removed by Meador Inge and Mark Dickinson; :issue:`5211`.) * The :meth:`str.format` method now supports automatic numbering of the replacement @@ -817,7 +817,7 @@ Some smaller changes made to the core Python language are: A low-level change: the :meth:`object.__format__` method now triggers a :exc:`PendingDeprecationWarning` if it's passed a format string, - because the :meth:`__format__` method for :class:`object` converts + because the :meth:`!__format__` method for :class:`object` converts the object to a string representation and formats that. Previously the method silently applied the format string to the string representation, but that could hide mistakes in Python code. If @@ -825,7 +825,7 @@ Some smaller changes made to the core Python language are: precision, presumably you're expecting the formatting to be applied in some object-specific way. (Fixed by Eric Smith; :issue:`7994`.) -* The :func:`int` and :func:`long` types gained a ``bit_length`` +* The :func:`int` and :func:`!long` types gained a ``bit_length`` method that returns the number of bits necessary to represent its argument in binary:: @@ -848,8 +848,8 @@ Some smaller changes made to the core Python language are: statements that were only working by accident. (Fixed by Meador Inge; :issue:`7902`.) -* It's now possible for a subclass of the built-in :class:`unicode` type - to override the :meth:`__unicode__` method. (Implemented by +* It's now possible for a subclass of the built-in :class:`!unicode` type + to override the :meth:`!__unicode__` method. (Implemented by Victor Stinner; :issue:`1583863`.) * The :class:`bytearray` type's :meth:`~bytearray.translate` method now accepts @@ -876,7 +876,7 @@ Some smaller changes made to the core Python language are: Forgeot d'Arc in :issue:`1616979`; CP858 contributed by Tim Hatch in :issue:`8016`.) -* The :class:`file` object will now set the :attr:`filename` attribute +* The :class:`!file` object will now set the :attr:`!filename` attribute on the :exc:`IOError` exception when trying to open a directory on POSIX platforms (noted by Jan Kaliszewski; :issue:`4764`), and now explicitly checks for and forbids writing to read-only file objects @@ -966,7 +966,7 @@ Several performance enhancements have been added: Apart from the performance improvements this change should be invisible to end users, with one exception: for testing and - debugging purposes there's a new structseq :data:`sys.long_info` that + debugging purposes there's a new structseq :data:`!sys.long_info` that provides information about the internal format, giving the number of bits per digit and the size in bytes of the C type used to store each digit:: @@ -1005,8 +1005,8 @@ Several performance enhancements have been added: conversion function that supports arbitrary bases. (Patch by Gawain Bolton; :issue:`6713`.) -* The :meth:`split`, :meth:`replace`, :meth:`rindex`, - :meth:`rpartition`, and :meth:`rsplit` methods of string-like types +* The :meth:`!split`, :meth:`!replace`, :meth:`!rindex`, + :meth:`!rpartition`, and :meth:`!rsplit` methods of string-like types (strings, Unicode strings, and :class:`bytearray` objects) now use a fast reverse-search algorithm instead of a character-by-character scan. This is sometimes faster by a factor of 10. (Added by @@ -1044,7 +1044,7 @@ changes, or look through the Subversion logs for all the details. used with :class:`memoryview` instances and other similar buffer objects. (Backported from 3.x by Florent Xicluna; :issue:`7703`.) -* Updated module: the :mod:`bsddb` module has been updated from 4.7.2devel9 +* Updated module: the :mod:`!bsddb` module has been updated from 4.7.2devel9 to version 4.8.4 of `the pybsddb package <https://www.jcea.es/programacion/pybsddb.htm>`__. The new version features better Python 3.x compatibility, various bug fixes, @@ -1129,7 +1129,7 @@ changes, or look through the Subversion logs for all the details. (Added by Raymond Hettinger; :issue:`1818`.) - Finally, the :class:`~collections.Mapping` abstract base class now + Finally, the :class:`~collections.abc.Mapping` abstract base class now returns :const:`NotImplemented` if a mapping is compared to another type that isn't a :class:`Mapping`. (Fixed by Daniel Stutzbach; :issue:`8729`.) @@ -1158,7 +1158,7 @@ changes, or look through the Subversion logs for all the details. (Contributed by Mats Kindahl; :issue:`7005`.) -* Deprecated function: :func:`contextlib.nested`, which allows +* Deprecated function: :func:`!contextlib.nested`, which allows handling more than one context manager with a single :keyword:`with` statement, has been deprecated, because the :keyword:`!with` statement now supports multiple context managers. @@ -1184,7 +1184,7 @@ changes, or look through the Subversion logs for all the details. * New method: the :class:`~decimal.Decimal` class gained a :meth:`~decimal.Decimal.from_float` class method that performs an exact - conversion of a floating-point number to a :class:`~decimal.Decimal`. + conversion of a floating-point number to a :class:`!Decimal`. This exact conversion strives for the closest decimal approximation to the floating-point representation's value; the resulting decimal value will therefore still include the inaccuracy, @@ -1198,9 +1198,9 @@ changes, or look through the Subversion logs for all the details. of the operands. Previously such comparisons would fall back to Python's default rules for comparing objects, which produced arbitrary results based on their type. Note that you still cannot combine - :class:`Decimal` and floating-point in other operations such as addition, + :class:`!Decimal` and floating-point in other operations such as addition, since you should be explicitly choosing how to convert between float and - :class:`~decimal.Decimal`. (Fixed by Mark Dickinson; :issue:`2531`.) + :class:`!Decimal`. (Fixed by Mark Dickinson; :issue:`2531`.) The constructor for :class:`~decimal.Decimal` now accepts floating-point numbers (added by Raymond Hettinger; :issue:`8257`) @@ -1218,7 +1218,7 @@ changes, or look through the Subversion logs for all the details. more sensible for numeric types. (Changed by Mark Dickinson; :issue:`6857`.) Comparisons involving a signaling NaN value (or ``sNAN``) now signal - :const:`InvalidOperation` instead of silently returning a true or + :const:`~decimal.InvalidOperation` instead of silently returning a true or false value depending on the comparison operator. Quiet NaN values (or ``NaN``) are now hashable. (Fixed by Mark Dickinson; :issue:`7279`.) @@ -1235,13 +1235,13 @@ changes, or look through the Subversion logs for all the details. created some new files that should be included. (Fixed by Tarek Ziadé; :issue:`8688`.) -* The :mod:`doctest` module's :const:`IGNORE_EXCEPTION_DETAIL` flag +* The :mod:`doctest` module's :const:`~doctest.IGNORE_EXCEPTION_DETAIL` flag will now ignore the name of the module containing the exception being tested. (Patch by Lennart Regebro; :issue:`7490`.) * The :mod:`email` module's :class:`~email.message.Message` class will now accept a Unicode-valued payload, automatically converting the - payload to the encoding specified by :attr:`output_charset`. + payload to the encoding specified by :attr:`!output_charset`. (Added by R. David Murray; :issue:`1368247`.) * The :class:`~fractions.Fraction` class now accepts a single float or @@ -1268,10 +1268,10 @@ changes, or look through the Subversion logs for all the details. :issue:`6845`.) * New class decorator: :func:`~functools.total_ordering` in the :mod:`functools` - module takes a class that defines an :meth:`__eq__` method and one of - :meth:`__lt__`, :meth:`__le__`, :meth:`__gt__`, or :meth:`__ge__`, + module takes a class that defines an :meth:`~object.__eq__` method and one of + :meth:`~object.__lt__`, :meth:`~object.__le__`, :meth:`~object.__gt__`, or :meth:`~object.__ge__`, and generates the missing comparison methods. Since the - :meth:`__cmp__` method is being deprecated in Python 3.x, + :meth:`!__cmp__` method is being deprecated in Python 3.x, this decorator makes it easier to define ordered classes. (Added by Raymond Hettinger; :issue:`5479`.) @@ -1300,7 +1300,7 @@ changes, or look through the Subversion logs for all the details. :mod:`gzip` module will now consume these trailing bytes. (Fixed by Tadek Pietraszek and Brian Curtin; :issue:`2846`.) -* New attribute: the :mod:`hashlib` module now has an :attr:`~hashlib.hashlib.algorithms` +* New attribute: the :mod:`hashlib` module now has an :attr:`!algorithms` attribute containing a tuple naming the supported algorithms. In Python 2.7, ``hashlib.algorithms`` contains ``('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512')``. @@ -1348,10 +1348,10 @@ changes, or look through the Subversion logs for all the details. * Updated module: The :mod:`io` library has been upgraded to the version shipped with Python 3.1. For 3.1, the I/O library was entirely rewritten in C and is 2 to 20 times faster depending on the task being performed. The - original Python version was renamed to the :mod:`_pyio` module. + original Python version was renamed to the :mod:`!_pyio` module. One minor resulting change: the :class:`io.TextIOBase` class now - has an :attr:`errors` attribute giving the error setting + has an :attr:`~io.TextIOBase.errors` attribute giving the error setting used for encoding and decoding errors (one of ``'strict'``, ``'replace'``, ``'ignore'``). @@ -1423,10 +1423,10 @@ changes, or look through the Subversion logs for all the details. passed to the callable. (Contributed by lekma; :issue:`5585`.) - The :class:`~multiprocessing.Pool` class, which controls a pool of worker processes, + The :class:`~multiprocessing.pool.Pool` class, which controls a pool of worker processes, now has an optional *maxtasksperchild* parameter. Worker processes will perform the specified number of tasks and then exit, causing the - :class:`~multiprocessing.Pool` to start a new worker. This is useful if tasks may leak + :class:`!Pool` to start a new worker. This is useful if tasks may leak memory or other resources, or if some tasks will cause the worker to become very large. (Contributed by Charles Cazabon; :issue:`6963`.) @@ -1498,7 +1498,7 @@ changes, or look through the Subversion logs for all the details. global site-packages directories, :func:`~site.getusersitepackages` returns the path of the user's site-packages directory, and - :func:`~site.getuserbase` returns the value of the :envvar:`USER_BASE` + :func:`~site.getuserbase` returns the value of the :data:`~site.USER_BASE` environment variable, giving the path to a directory that can be used to store data. (Contributed by Tarek Ziadé; :issue:`6693`.) @@ -1540,11 +1540,11 @@ changes, or look through the Subversion logs for all the details. * The :mod:`ssl` module's :class:`~ssl.SSLSocket` objects now support the buffer API, which fixed a test suite failure (fix by Antoine Pitrou; :issue:`7133`) and automatically set - OpenSSL's :c:macro:`SSL_MODE_AUTO_RETRY`, which will prevent an error + OpenSSL's :c:macro:`!SSL_MODE_AUTO_RETRY`, which will prevent an error code being returned from :meth:`recv` operations that trigger an SSL renegotiation (fix by Antoine Pitrou; :issue:`8222`). - The :func:`ssl.wrap_socket` constructor function now takes a + The :func:`~ssl.SSLContext.wrap_socket` constructor function now takes a *ciphers* argument that's a string listing the encryption algorithms to be allowed; the format of the string is described `in the OpenSSL documentation @@ -1568,8 +1568,8 @@ changes, or look through the Subversion logs for all the details. code (one of ``bBhHiIlLqQ``); it now always raises a :exc:`struct.error` exception. (Changed by Mark Dickinson; :issue:`1523`.) The :func:`~struct.pack` function will also - attempt to use :meth:`__index__` to convert and pack non-integers - before trying the :meth:`__int__` method or reporting an error. + attempt to use :meth:`~object.__index__` to convert and pack non-integers + before trying the :meth:`~object.__int__` method or reporting an error. (Changed by Mark Dickinson; :issue:`8300`.) * New function: the :mod:`subprocess` module's @@ -1590,7 +1590,7 @@ changes, or look through the Subversion logs for all the details. (Contributed by Gregory P. Smith.) The :mod:`subprocess` module will now retry its internal system calls - on receiving an :const:`EINTR` signal. (Reported by several people; final + on receiving an :const:`~errno.EINTR` signal. (Reported by several people; final patch by Gregory P. Smith in :issue:`1068268`.) * New function: :func:`~symtable.Symbol.is_declared_global` in the :mod:`symtable` module @@ -1602,16 +1602,16 @@ changes, or look through the Subversion logs for all the details. identifier instead of the previous default value of ``'python'``. (Changed by Sean Reifschneider; :issue:`8451`.) -* The ``sys.version_info`` value is now a named tuple, with attributes - named :attr:`major`, :attr:`minor`, :attr:`micro`, - :attr:`releaselevel`, and :attr:`serial`. (Contributed by Ross +* The :attr:`sys.version_info` value is now a named tuple, with attributes + named :attr:`!major`, :attr:`!minor`, :attr:`!micro`, + :attr:`!releaselevel`, and :attr:`!serial`. (Contributed by Ross Light; :issue:`4285`.) :func:`sys.getwindowsversion` also returns a named tuple, - with attributes named :attr:`major`, :attr:`minor`, :attr:`build`, - :attr:`platform`, :attr:`service_pack`, :attr:`service_pack_major`, - :attr:`service_pack_minor`, :attr:`suite_mask`, and - :attr:`product_type`. (Contributed by Brian Curtin; :issue:`7766`.) + with attributes named :attr:`!major`, :attr:`!minor`, :attr:`!build`, + :attr:`!platform`, :attr:`!service_pack`, :attr:`!service_pack_major`, + :attr:`!service_pack_minor`, :attr:`!suite_mask`, and + :attr:`!product_type`. (Contributed by Brian Curtin; :issue:`7766`.) * The :mod:`tarfile` module's default error handling has changed, to no longer suppress fatal errors. The default error level was previously 0, @@ -1691,7 +1691,7 @@ changes, or look through the Subversion logs for all the details. (Originally implemented in Python 3.x by Raymond Hettinger, and backported to 2.7 by Michael Foord.) -* The ElementTree library, :mod:`xml.etree`, no longer escapes +* The :mod:`xml.etree.ElementTree` library, no longer escapes ampersands and angle brackets when outputting an XML processing instruction (which looks like ``<?xml-stylesheet href="#style1"?>``) or comment (which looks like ``<!-- comment -->``). @@ -1701,8 +1701,8 @@ changes, or look through the Subversion logs for all the details. :mod:`SimpleXMLRPCServer <xmlrpc.server>` modules, have improved performance by supporting HTTP/1.1 keep-alive and by optionally using gzip encoding to compress the XML being exchanged. The gzip compression is - controlled by the :attr:`encode_threshold` attribute of - :class:`SimpleXMLRPCRequestHandler`, which contains a size in bytes; + controlled by the :attr:`!encode_threshold` attribute of + :class:`~xmlrpc.server.SimpleXMLRPCRequestHandler`, which contains a size in bytes; responses larger than this will be compressed. (Contributed by Kristján Valur Jónsson; :issue:`6267`.) @@ -1713,7 +1713,8 @@ changes, or look through the Subversion logs for all the details. :mod:`zipfile` now also supports archiving empty directories and extracts them correctly. (Fixed by Kuba Wieczorek; :issue:`4710`.) Reading files out of an archive is faster, and interleaving - :meth:`~zipfile.ZipFile.read` and :meth:`~zipfile.ZipFile.readline` now works correctly. + :meth:`read() <io.BufferedIOBase.read>` and + :meth:`readline() <io.IOBase.readline>` now works correctly. (Contributed by Nir Aides; :issue:`7610`.) The :func:`~zipfile.is_zipfile` function now @@ -1807,14 +1808,14 @@ closely resemble the native platform's widgets. This widget set was originally called Tile, but was renamed to Ttk (for "themed Tk") on being added to Tcl/Tck release 8.5. -To learn more, read the :mod:`ttk` module documentation. You may also +To learn more, read the :mod:`~tkinter.ttk` module documentation. You may also wish to read the Tcl/Tk manual page describing the Ttk theme engine, available at -https://www.tcl.tk/man/tcl8.5/TkCmd/ttk_intro.htm. Some +https://www.tcl.tk/man/tcl8.5/TkCmd/ttk_intro.html. Some screenshots of the Python/Ttk code in use are at https://code.google.com/archive/p/python-ttk/wikis/Screenshots.wiki. -The :mod:`ttk` module was written by Guilherme Polo and added in +The :mod:`tkinter.ttk` module was written by Guilherme Polo and added in :issue:`2983`. An alternate version called ``Tile.py``, written by Martin Franklin and maintained by Kevin Walzer, was proposed for inclusion in :issue:`2618`, but the authors argued that Guilherme @@ -1830,7 +1831,7 @@ The :mod:`unittest` module was greatly enhanced; many new features were added. Most of these features were implemented by Michael Foord, unless otherwise noted. The enhanced version of the module is downloadable separately for use with Python versions 2.4 to 2.6, -packaged as the :mod:`unittest2` package, from +packaged as the :mod:`!unittest2` package, from https://pypi.org/project/unittest2. When used from the command line, the module can automatically discover @@ -1938,19 +1939,20 @@ GvR worked on merging them into Python's version of :mod:`unittest`. differences in the two strings. This comparison is now used by default when Unicode strings are compared with :meth:`~unittest.TestCase.assertEqual`. -* :meth:`~unittest.TestCase.assertRegexpMatches` and - :meth:`~unittest.TestCase.assertNotRegexpMatches` checks whether the +* :meth:`assertRegexpMatches() <unittest.TestCase.assertRegex>` and + :meth:`assertNotRegexpMatches() <unittest.TestCase.assertNotRegex>` checks whether the first argument is a string matching or not matching the regular expression provided as the second argument (:issue:`8038`). -* :meth:`~unittest.TestCase.assertRaisesRegexp` checks whether a particular exception +* :meth:`assertRaisesRegexp() <unittest.TestCase.assertRaisesRegex>` checks + whether a particular exception is raised, and then also checks that the string representation of the exception matches the provided regular expression. * :meth:`~unittest.TestCase.assertIn` and :meth:`~unittest.TestCase.assertNotIn` tests whether *first* is or is not in *second*. -* :meth:`~unittest.TestCase.assertItemsEqual` tests whether two provided sequences +* :meth:`assertItemsEqual() <unittest.TestCase.assertCountEqual>` tests whether two provided sequences contain the same elements. * :meth:`~unittest.TestCase.assertSetEqual` compares whether two sets are equal, and @@ -1966,7 +1968,7 @@ GvR worked on merging them into Python's version of :mod:`unittest`. * :meth:`~unittest.TestCase.assertDictEqual` compares two dictionaries and reports the differences; it's now used by default when you compare two dictionaries - using :meth:`~unittest.TestCase.assertEqual`. :meth:`~unittest.TestCase.assertDictContainsSubset` checks whether + using :meth:`~unittest.TestCase.assertEqual`. :meth:`!assertDictContainsSubset` checks whether all of the key/value pairs in *first* are found in *second*. * :meth:`~unittest.TestCase.assertAlmostEqual` and :meth:`~unittest.TestCase.assertNotAlmostEqual` test @@ -2023,8 +2025,8 @@ version 1.3. Some of the new features are: p = ET.XMLParser(encoding='utf-8') t = ET.XML("""<root/>""", parser=p) - Errors in parsing XML now raise a :exc:`ParseError` exception, whose - instances have a :attr:`position` attribute + Errors in parsing XML now raise a :exc:`~xml.etree.ElementTree.ParseError` exception, whose + instances have a :attr:`!position` attribute containing a (*line*, *column*) tuple giving the location of the problem. * ElementTree's code for converting trees to a string has been @@ -2034,7 +2036,8 @@ version 1.3. Some of the new features are: "xml" (the default), "html", or "text". HTML mode will output empty elements as ``<empty></empty>`` instead of ``<empty/>``, and text mode will skip over elements and only output the text chunks. If - you set the :attr:`tag` attribute of an element to ``None`` but + you set the :attr:`~xml.etree.ElementTree.Element.tag` attribute of an + element to ``None`` but leave its children in place, the element will be omitted when the tree is written out, so you don't need to do more extensive rearrangement to remove a single element. @@ -2064,14 +2067,14 @@ version 1.3. Some of the new features are: # Outputs <root><item>1</item>...</root> print ET.tostring(new) -* New :class:`Element` method: +* New :class:`~xml.etree.ElementTree.Element` method: :meth:`~xml.etree.ElementTree.Element.iter` yields the children of the element as a generator. It's also possible to write ``for child in elem:`` to loop over an element's children. The existing method - :meth:`getiterator` is now deprecated, as is :meth:`getchildren` + :meth:`!getiterator` is now deprecated, as is :meth:`!getchildren` which constructs and returns a list of children. -* New :class:`Element` method: +* New :class:`~xml.etree.ElementTree.Element` method: :meth:`~xml.etree.ElementTree.Element.itertext` yields all chunks of text that are descendants of the element. For example:: @@ -2227,7 +2230,7 @@ Changes to Python's build process and to the C API include: (Fixed by Thomas Wouters; :issue:`1590864`.) * The :c:func:`Py_Finalize` function now calls the internal - :func:`threading._shutdown` function; this prevents some exceptions from + :func:`!threading._shutdown` function; this prevents some exceptions from being raised when an interpreter shuts down. (Patch by Adam Olsen; :issue:`1722344`.) @@ -2242,7 +2245,7 @@ Changes to Python's build process and to the C API include: Heller; :issue:`3102`.) * New configure option: the :option:`!--with-system-expat` switch allows - building the :mod:`pyexpat` module to use the system Expat library. + building the :mod:`pyexpat <xml.parsers.expat>` module to use the system Expat library. (Contributed by Arfrever Frehtes Taifersar Arahesis; :issue:`7609`.) * New configure option: the @@ -2329,9 +2332,9 @@ Port-Specific Changes: Windows * The :mod:`msvcrt` module now contains some constants from the :file:`crtassem.h` header file: - :data:`CRT_ASSEMBLY_VERSION`, - :data:`VC_ASSEMBLY_PUBLICKEYTOKEN`, - and :data:`LIBRARIES_ASSEMBLY_NAME_PREFIX`. + :data:`~msvcrt.CRT_ASSEMBLY_VERSION`, + :data:`~msvcrt.VC_ASSEMBLY_PUBLICKEYTOKEN`, + and :data:`~msvcrt.LIBRARIES_ASSEMBLY_NAME_PREFIX`. (Contributed by David Cournapeau; :issue:`4365`.) * The :mod:`_winreg <winreg>` module for accessing the registry now implements @@ -2342,21 +2345,21 @@ Port-Specific Changes: Windows were also tested and documented. (Implemented by Brian Curtin: :issue:`7347`.) -* The new :c:func:`_beginthreadex` API is used to start threads, and +* The new :c:func:`!_beginthreadex` API is used to start threads, and the native thread-local storage functions are now used. (Contributed by Kristján Valur Jónsson; :issue:`3582`.) * The :func:`os.kill` function now works on Windows. The signal value - can be the constants :const:`CTRL_C_EVENT`, - :const:`CTRL_BREAK_EVENT`, or any integer. The first two constants + can be the constants :const:`~signal.CTRL_C_EVENT`, + :const:`~signal.CTRL_BREAK_EVENT`, or any integer. The first two constants will send :kbd:`Control-C` and :kbd:`Control-Break` keystroke events to - subprocesses; any other value will use the :c:func:`TerminateProcess` + subprocesses; any other value will use the :c:func:`!TerminateProcess` API. (Contributed by Miki Tebeka; :issue:`1220212`.) * The :func:`os.listdir` function now correctly fails for an empty path. (Fixed by Hirokazu Yamamoto; :issue:`5913`.) -* The :mod:`mimelib` module will now read the MIME database from +* The :mod:`mimetypes` module will now read the MIME database from the Windows registry when initializing. (Patch by Gabriel Genellina; :issue:`4969`.) @@ -2385,7 +2388,7 @@ Port-Specific Changes: Mac OS X Port-Specific Changes: FreeBSD ----------------------------------- -* FreeBSD 7.1's :const:`SO_SETFIB` constant, used with the :func:`~socket.socket` methods +* FreeBSD 7.1's :const:`!SO_SETFIB` constant, used with the :func:`~socket.socket` methods :func:`~socket.socket.getsockopt`/:func:`~socket.socket.setsockopt` to select an alternate routing table, is now available in the :mod:`socket` module. (Added by Kyle VanderBeek; :issue:`8235`.) @@ -2441,7 +2444,7 @@ This section lists previously described changes and other bugfixes that may require changes to your code: * The :func:`range` function processes its arguments more - consistently; it will now call :meth:`__int__` on non-float, + consistently; it will now call :meth:`~object.__int__` on non-float, non-integer arguments that are supplied to it. (Fixed by Alexander Belopolsky; :issue:`1533`.) @@ -2486,13 +2489,13 @@ In the standard library: (or ``NaN``) are now hashable. (Fixed by Mark Dickinson; :issue:`7279`.) -* The ElementTree library, :mod:`xml.etree`, no longer escapes +* The :mod:`xml.etree.ElementTree` library no longer escapes ampersands and angle brackets when outputting an XML processing instruction (which looks like ``<?xml-stylesheet href="#style1"?>``) or comment (which looks like ``<!-- comment -->``). (Patch by Neil Muller; :issue:`2746`.) -* The :meth:`~StringIO.StringIO.readline` method of :class:`~StringIO.StringIO` objects now does +* The :meth:`!readline` method of :class:`~io.StringIO` objects now does nothing when a negative length is requested, as other file-like objects do. (:issue:`7348`). @@ -2577,11 +2580,11 @@ Two new environment variables for debug mode -------------------------------------------- In debug mode, the ``[xxx refs]`` statistic is not written by default, the -:envvar:`PYTHONSHOWREFCOUNT` environment variable now must also be set. +:envvar:`!PYTHONSHOWREFCOUNT` environment variable now must also be set. (Contributed by Victor Stinner; :issue:`31733`.) When Python is compiled with ``COUNT_ALLOC`` defined, allocation counts are no -longer dumped by default anymore: the :envvar:`PYTHONSHOWALLOCCOUNT` environment +longer dumped by default anymore: the :envvar:`!PYTHONSHOWALLOCCOUNT` environment variable must now also be set. Moreover, allocation counts are now dumped into stderr, rather than stdout. (Contributed by Victor Stinner; :issue:`31692`.) @@ -2712,7 +2715,8 @@ PEP 476: Enabling certificate verification by default for stdlib http clients ----------------------------------------------------------------------------- :pep:`476` updated :mod:`httplib <http>` and modules which use it, such as -:mod:`urllib2 <urllib.request>` and :mod:`xmlrpclib`, to now verify that the server +:mod:`urllib2 <urllib.request>` and :mod:`xmlrpclib <xmlrpc.client>`, to now +verify that the server presents a certificate which is signed by a Certificate Authority in the platform trust store and whose hostname matches the hostname being requested by default, significantly improving security for many applications. This @@ -2753,7 +2757,7 @@ entire Python process back to the default permissive behaviour of Python 2.7.8 and earlier. For cases where the connection establishment code can't be modified, but the -overall application can be, the new :func:`ssl._https_verify_certificates` +overall application can be, the new :func:`!ssl._https_verify_certificates` function can be used to adjust the default behaviour at runtime. diff --git a/Doc/whatsnew/3.1.rst b/Doc/whatsnew/3.1.rst index e237179f4b1829f..c912a928ee45972 100644 --- a/Doc/whatsnew/3.1.rst +++ b/Doc/whatsnew/3.1.rst @@ -169,7 +169,7 @@ Some smaller changes made to the core Python language are: ... if '<critical>' in line: ... outfile.write(line) - With the new syntax, the :func:`contextlib.nested` function is no longer + With the new syntax, the :func:`!contextlib.nested` function is no longer needed and is now deprecated. (Contributed by Georg Brandl and Mattias Brändström; diff --git a/Doc/whatsnew/3.2.rst b/Doc/whatsnew/3.2.rst index 9834bc03dc4b742..4f70d902243d4d5 100644 --- a/Doc/whatsnew/3.2.rst +++ b/Doc/whatsnew/3.2.rst @@ -743,8 +743,8 @@ Several new and useful functions and methods have been added: Two methods have been deprecated: -* :meth:`xml.etree.ElementTree.getchildren` use ``list(elem)`` instead. -* :meth:`xml.etree.ElementTree.getiterator` use ``Element.iter`` instead. +* :meth:`!xml.etree.ElementTree.getchildren` use ``list(elem)`` instead. +* :meth:`!xml.etree.ElementTree.getiterator` use ``Element.iter`` instead. For details of the update, see `Introducing ElementTree <https://web.archive.org/web/20200703234532/http://effbot.org/zone/elementtree-13-intro.htm>`_ @@ -2682,7 +2682,7 @@ require changes to your code: (Contributed by Georg Brandl; :issue:`5675`.) -* The previously deprecated :func:`contextlib.nested` function has been removed +* The previously deprecated :func:`!contextlib.nested` function has been removed in favor of a plain :keyword:`with` statement which can accept multiple context managers. The latter technique is faster (because it is built-in), and it does a better job finalizing multiple context managers when one of them From 46190d9ea8a878a03d95b4e1bdcdc9ed576cf3fa Mon Sep 17 00:00:00 2001 From: Eugene Toder <eltoder@users.noreply.github.com> Date: Mon, 12 Feb 2024 07:44:56 -0500 Subject: [PATCH 223/507] gh-89039: Call subclass constructors in datetime.*.replace (GH-114780) When replace() method is called on a subclass of datetime, date or time, properly call derived constructor. Previously, only the base class's constructor was called. Also, make sure to pass non-zero fold values when creating subclasses in various methods. Previously, fold was silently ignored. --- Lib/test/datetimetester.py | 62 +++++++++++++-- ...3-12-18-20-10-50.gh-issue-89039.gqFdtU.rst | 6 ++ Modules/_datetimemodule.c | 77 +++++++++++++++---- 3 files changed, 124 insertions(+), 21 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-18-20-10-50.gh-issue-89039.gqFdtU.rst diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 980a8e6c1b18369..31fc383e29707a7 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -1723,11 +1723,24 @@ def test_replace(self): def test_subclass_replace(self): class DateSubclass(self.theclass): - pass + def __new__(cls, *args, **kwargs): + result = self.theclass.__new__(cls, *args, **kwargs) + result.extra = 7 + return result dt = DateSubclass(2012, 1, 1) - self.assertIs(type(dt.replace(year=2013)), DateSubclass) - self.assertIs(type(copy.replace(dt, year=2013)), DateSubclass) + + test_cases = [ + ('self.replace', dt.replace(year=2013)), + ('copy.replace', copy.replace(dt, year=2013)), + ] + + for name, res in test_cases: + with self.subTest(name): + self.assertIs(type(res), DateSubclass) + self.assertEqual(res.year, 2013) + self.assertEqual(res.month, 1) + self.assertEqual(res.extra, 7) def test_subclass_date(self): @@ -3025,6 +3038,26 @@ def __new__(cls, *args, **kwargs): self.assertIsInstance(dt, DateTimeSubclass) self.assertEqual(dt.extra, 7) + def test_subclass_replace_fold(self): + class DateTimeSubclass(self.theclass): + pass + + dt = DateTimeSubclass(2012, 1, 1) + dt2 = DateTimeSubclass(2012, 1, 1, fold=1) + + test_cases = [ + ('self.replace', dt.replace(year=2013), 0), + ('self.replace', dt2.replace(year=2013), 1), + ('copy.replace', copy.replace(dt, year=2013), 0), + ('copy.replace', copy.replace(dt2, year=2013), 1), + ] + + for name, res, fold in test_cases: + with self.subTest(name, fold=fold): + self.assertIs(type(res), DateTimeSubclass) + self.assertEqual(res.year, 2013) + self.assertEqual(res.fold, fold) + def test_fromisoformat_datetime(self): # Test that isoformat() is reversible base_dates = [ @@ -3705,11 +3738,28 @@ def test_replace(self): def test_subclass_replace(self): class TimeSubclass(self.theclass): - pass + def __new__(cls, *args, **kwargs): + result = self.theclass.__new__(cls, *args, **kwargs) + result.extra = 7 + return result ctime = TimeSubclass(12, 30) - self.assertIs(type(ctime.replace(hour=10)), TimeSubclass) - self.assertIs(type(copy.replace(ctime, hour=10)), TimeSubclass) + ctime2 = TimeSubclass(12, 30, fold=1) + + test_cases = [ + ('self.replace', ctime.replace(hour=10), 0), + ('self.replace', ctime2.replace(hour=10), 1), + ('copy.replace', copy.replace(ctime, hour=10), 0), + ('copy.replace', copy.replace(ctime2, hour=10), 1), + ] + + for name, res, fold in test_cases: + with self.subTest(name, fold=fold): + self.assertIs(type(res), TimeSubclass) + self.assertEqual(res.hour, 10) + self.assertEqual(res.minute, 30) + self.assertEqual(res.extra, 7) + self.assertEqual(res.fold, fold) def test_subclass_time(self): diff --git a/Misc/NEWS.d/next/Library/2023-12-18-20-10-50.gh-issue-89039.gqFdtU.rst b/Misc/NEWS.d/next/Library/2023-12-18-20-10-50.gh-issue-89039.gqFdtU.rst new file mode 100644 index 000000000000000..d1998d75e9fd76b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-18-20-10-50.gh-issue-89039.gqFdtU.rst @@ -0,0 +1,6 @@ +When replace() method is called on a subclass of datetime, date or time, +properly call derived constructor. Previously, only the base class's +constructor was called. + +Also, make sure to pass non-zero fold values when creating subclasses in +various methods. Previously, fold was silently ignored. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index b984ea61b82f0fb..014ccdd3f6effe4 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -1045,6 +1045,40 @@ new_datetime_ex(int year, int month, int day, int hour, int minute, new_datetime_ex2(y, m, d, hh, mm, ss, us, tzinfo, fold, \ &PyDateTime_DateTimeType) +static PyObject * +call_subclass_fold(PyObject *cls, int fold, const char *format, ...) +{ + PyObject *kwargs = NULL, *res = NULL; + va_list va; + + va_start(va, format); + PyObject *args = Py_VaBuildValue(format, va); + va_end(va); + if (args == NULL) { + return NULL; + } + if (fold) { + kwargs = PyDict_New(); + if (kwargs == NULL) { + goto Done; + } + PyObject *obj = PyLong_FromLong(fold); + if (obj == NULL) { + goto Done; + } + int err = PyDict_SetItemString(kwargs, "fold", obj); + Py_DECREF(obj); + if (err < 0) { + goto Done; + } + } + res = PyObject_Call(cls, args, kwargs); +Done: + Py_DECREF(args); + Py_XDECREF(kwargs); + return res; +} + static PyObject * new_datetime_subclass_fold_ex(int year, int month, int day, int hour, int minute, int second, int usecond, PyObject *tzinfo, @@ -1054,17 +1088,11 @@ new_datetime_subclass_fold_ex(int year, int month, int day, int hour, int minute // Use the fast path constructor dt = new_datetime(year, month, day, hour, minute, second, usecond, tzinfo, fold); - } else { + } + else { // Subclass - dt = PyObject_CallFunction(cls, "iiiiiiiO", - year, - month, - day, - hour, - minute, - second, - usecond, - tzinfo); + dt = call_subclass_fold(cls, fold, "iiiiiiiO", year, month, day, + hour, minute, second, usecond, tzinfo); } return dt; @@ -1120,6 +1148,24 @@ new_time_ex(int hour, int minute, int second, int usecond, #define new_time(hh, mm, ss, us, tzinfo, fold) \ new_time_ex2(hh, mm, ss, us, tzinfo, fold, &PyDateTime_TimeType) +static PyObject * +new_time_subclass_fold_ex(int hour, int minute, int second, int usecond, + PyObject *tzinfo, int fold, PyObject *cls) +{ + PyObject *t; + if ((PyTypeObject*)cls == &PyDateTime_TimeType) { + // Use the fast path constructor + t = new_time(hour, minute, second, usecond, tzinfo, fold); + } + else { + // Subclass + t = call_subclass_fold(cls, fold, "iiiiO", hour, minute, second, + usecond, tzinfo); + } + + return t; +} + /* Create a timedelta instance. Normalize the members iff normalize is * true. Passing false is a speed optimization, if you know for sure * that seconds and microseconds are already in their proper ranges. In any @@ -3480,7 +3526,7 @@ datetime_date_replace_impl(PyDateTime_Date *self, int year, int month, int day) /*[clinic end generated code: output=2a9430d1e6318aeb input=0d1f02685b3e90f6]*/ { - return new_date_ex(year, month, day, Py_TYPE(self)); + return new_date_subclass_ex(year, month, day, (PyObject *)Py_TYPE(self)); } static Py_hash_t @@ -4589,8 +4635,8 @@ datetime_time_replace_impl(PyDateTime_Time *self, int hour, int minute, int fold) /*[clinic end generated code: output=0b89a44c299e4f80 input=9b6a35b1e704b0ca]*/ { - return new_time_ex2(hour, minute, second, microsecond, tzinfo, fold, - Py_TYPE(self)); + return new_time_subclass_fold_ex(hour, minute, second, microsecond, tzinfo, + fold, (PyObject *)Py_TYPE(self)); } static PyObject * @@ -6039,8 +6085,9 @@ datetime_datetime_replace_impl(PyDateTime_DateTime *self, int year, int fold) /*[clinic end generated code: output=00bc96536833fddb input=9b38253d56d9bcad]*/ { - return new_datetime_ex2(year, month, day, hour, minute, second, - microsecond, tzinfo, fold, Py_TYPE(self)); + return new_datetime_subclass_fold_ex(year, month, day, hour, minute, + second, microsecond, tzinfo, fold, + (PyObject *)Py_TYPE(self)); } static PyObject * From dc8893af7df706138161d82ce7d1d2f9132d14f9 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 12 Feb 2024 16:16:16 +0200 Subject: [PATCH 224/507] Add missing sections to blurbs (#114553) --- Misc/NEWS.d/3.5.3.rst | 1 + Misc/NEWS.d/3.6.0.rst | 1 + Misc/NEWS.d/3.6.2.rst | 1 + 3 files changed, 3 insertions(+) diff --git a/Misc/NEWS.d/3.5.3.rst b/Misc/NEWS.d/3.5.3.rst index c3fcb67a4563f98..25db389ba5734fd 100644 --- a/Misc/NEWS.d/3.5.3.rst +++ b/Misc/NEWS.d/3.5.3.rst @@ -3,5 +3,6 @@ .. no changes: True .. nonce: zYPqUK .. release date: 2017-01-17 +.. section: Library There were no code changes between 3.5.3rc1 and 3.5.3 final. diff --git a/Misc/NEWS.d/3.6.0.rst b/Misc/NEWS.d/3.6.0.rst index f9805cab28615e7..d5c41f38838d936 100644 --- a/Misc/NEWS.d/3.6.0.rst +++ b/Misc/NEWS.d/3.6.0.rst @@ -3,5 +3,6 @@ .. no changes: True .. nonce: F9ENBV .. release date: 2016-12-23 +.. section: Library No changes since release candidate 2 diff --git a/Misc/NEWS.d/3.6.2.rst b/Misc/NEWS.d/3.6.2.rst index dba43d146df9547..ee50670bd9f4426 100644 --- a/Misc/NEWS.d/3.6.2.rst +++ b/Misc/NEWS.d/3.6.2.rst @@ -3,5 +3,6 @@ .. no changes: True .. nonce: F9ENBV .. release date: 2017-07-17 +.. section: Library No changes since release candidate 2 From 95ebd45613d6bf0a8b76778454f1d413d54209db Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Mon, 12 Feb 2024 17:23:54 +0300 Subject: [PATCH 225/507] Remove outdated comment about py3.6 in `test_typing` (#115318) --- Lib/test/test_typing.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index c3a092f3af30097..176623171c98888 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -6156,8 +6156,6 @@ def test_overload_registry_repeated(self): self.assertEqual(list(get_overloads(impl)), overloads) -# Definitions needed for features introduced in Python 3.6 - from test.typinganndata import ( ann_module, ann_module2, ann_module3, ann_module5, ann_module6, ) From 93ac78ac3ee124942bca7492149c3ff0003b6e30 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Mon, 12 Feb 2024 19:05:30 +0300 Subject: [PATCH 226/507] gh-115058: Add ``reset_rare_event_counters`` function in `_testinternalcapi` (GH-115128) --- Lib/test/test_optimizer.py | 3 +++ Modules/_testinternalcapi.c | 15 +++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/Lib/test/test_optimizer.py b/Lib/test/test_optimizer.py index c8554c40df4b2de..dfea8be3c6956f3 100644 --- a/Lib/test/test_optimizer.py +++ b/Lib/test/test_optimizer.py @@ -7,6 +7,9 @@ class TestRareEventCounters(unittest.TestCase): + def setUp(self): + _testinternalcapi.reset_rare_event_counters() + def test_set_class(self): class A: pass diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 0bb739b5398b113..3834f00009cea4e 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -1650,6 +1650,20 @@ get_rare_event_counters(PyObject *self, PyObject *type) ); } +static PyObject * +reset_rare_event_counters(PyObject *self, PyObject *Py_UNUSED(type)) +{ + PyInterpreterState *interp = PyInterpreterState_Get(); + + interp->rare_events.set_class = 0; + interp->rare_events.set_bases = 0; + interp->rare_events.set_eval_frame_func = 0; + interp->rare_events.builtin_dict = 0; + interp->rare_events.func_modification = 0; + + return Py_None; +} + #ifdef Py_GIL_DISABLED static PyObject * @@ -1727,6 +1741,7 @@ static PyMethodDef module_functions[] = { _TESTINTERNALCAPI_TEST_LONG_NUMBITS_METHODDEF {"get_type_module_name", get_type_module_name, METH_O}, {"get_rare_event_counters", get_rare_event_counters, METH_NOARGS}, + {"reset_rare_event_counters", reset_rare_event_counters, METH_NOARGS}, #ifdef Py_GIL_DISABLED {"py_thread_id", get_py_thread_id, METH_NOARGS}, #endif From 814466101790d4381ca4800c3d3b0cc0aad50c62 Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Mon, 12 Feb 2024 16:07:38 +0000 Subject: [PATCH 227/507] GH-113710: Fix updating of dict version tag and add watched dict stats (GH-115221) --- Include/cpython/pystats.h | 3 +++ Include/internal/pycore_dict.h | 5 +++-- Python/optimizer_analysis.c | 31 ++++++++++++------------------- Python/pylifecycle.c | 2 +- Python/specialize.c | 2 ++ Tools/scripts/summarize_stats.py | 2 +- 6 files changed, 22 insertions(+), 23 deletions(-) diff --git a/Include/cpython/pystats.h b/Include/cpython/pystats.h index bf0cfe4cb695b45..0f50439b73848e7 100644 --- a/Include/cpython/pystats.h +++ b/Include/cpython/pystats.h @@ -133,6 +133,9 @@ typedef struct _rare_event_stats { uint64_t builtin_dict; /* Modifying a function, e.g. func.__defaults__ = ..., etc. */ uint64_t func_modification; + /* Modifying a dict that is being watched */ + uint64_t watched_dict_modification; + uint64_t watched_globals_modification; } RareEventStats; typedef struct _stats { diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index 233da058f464d17..0ebe701bc16f812 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -209,6 +209,7 @@ static inline PyDictUnicodeEntry* DK_UNICODE_ENTRIES(PyDictKeysObject *dk) { #define DICT_VERSION_INCREMENT (1 << (DICT_MAX_WATCHERS + DICT_WATCHED_MUTATION_BITS)) #define DICT_WATCHER_MASK ((1 << DICT_MAX_WATCHERS) - 1) +#define DICT_WATCHER_AND_MODIFICATION_MASK ((1 << (DICT_MAX_WATCHERS + DICT_WATCHED_MUTATION_BITS)) - 1) #ifdef Py_GIL_DISABLED #define DICT_NEXT_VERSION(INTERP) \ @@ -236,10 +237,10 @@ _PyDict_NotifyEvent(PyInterpreterState *interp, assert(Py_REFCNT((PyObject*)mp) > 0); int watcher_bits = mp->ma_version_tag & DICT_WATCHER_MASK; if (watcher_bits) { + RARE_EVENT_STAT_INC(watched_dict_modification); _PyDict_SendEvent(watcher_bits, event, mp, key, value); - return DICT_NEXT_VERSION(interp) | watcher_bits; } - return DICT_NEXT_VERSION(interp); + return DICT_NEXT_VERSION(interp) | (mp->ma_version_tag & DICT_WATCHER_AND_MODIFICATION_MASK); } extern PyObject *_PyObject_MakeDictFromInstanceAttributes(PyObject *obj, PyDictValues *values); diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 2cfbf4b349d0f52..b14e6950b4a06b5 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -28,25 +28,23 @@ increment_mutations(PyObject* dict) { d->ma_version_tag += (1 << DICT_MAX_WATCHERS); } +/* The first two dict watcher IDs are reserved for CPython, + * so we don't need to check that they haven't been used */ +#define BUILTINS_WATCHER_ID 0 +#define GLOBALS_WATCHER_ID 1 + static int globals_watcher_callback(PyDict_WatchEvent event, PyObject* dict, PyObject* key, PyObject* new_value) { - if (event == PyDict_EVENT_CLONED) { - return 0; - } - uint64_t watched_mutations = get_mutations(dict); - if (watched_mutations < _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS) { - _Py_Executors_InvalidateDependency(_PyInterpreterState_GET(), dict); - increment_mutations(dict); - } - else { - PyDict_Unwatch(1, dict); - } + RARE_EVENT_STAT_INC(watched_globals_modification); + assert(get_mutations(dict) < _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS); + _Py_Executors_InvalidateDependency(_PyInterpreterState_GET(), dict); + increment_mutations(dict); + PyDict_Unwatch(GLOBALS_WATCHER_ID, dict); return 0; } - static void global_to_const(_PyUOpInstruction *inst, PyObject *obj) { @@ -82,11 +80,6 @@ incorrect_keys(_PyUOpInstruction *inst, PyObject *obj) return 0; } -/* The first two dict watcher IDs are reserved for CPython, - * so we don't need to check that they haven't been used */ -#define BUILTINS_WATCHER_ID 0 -#define GLOBALS_WATCHER_ID 1 - /* Returns 1 if successfully optimized * 0 if the trace is not suitable for optimization (yet) * -1 if there was an error. */ @@ -117,8 +110,8 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, uint32_t builtins_watched = 0; uint32_t globals_checked = 0; uint32_t globals_watched = 0; - if (interp->dict_state.watchers[1] == NULL) { - interp->dict_state.watchers[1] = globals_watcher_callback; + if (interp->dict_state.watchers[GLOBALS_WATCHER_ID] == NULL) { + interp->dict_state.watchers[GLOBALS_WATCHER_ID] = globals_watcher_callback; } for (int pc = 0; pc < buffer_size; pc++) { _PyUOpInstruction *inst = &buffer[pc]; diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 61c9d4f9ea95754..230018068d751c5 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -611,7 +611,7 @@ static int builtins_dict_watcher(PyDict_WatchEvent event, PyObject *dict, PyObject *key, PyObject *new_value) { PyInterpreterState *interp = _PyInterpreterState_GET(); - if (event != PyDict_EVENT_CLONED && interp->rare_events.builtin_dict < _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS) { + if (interp->rare_events.builtin_dict < _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS) { _Py_Executors_InvalidateAll(interp); } RARE_EVENT_INTERP_INC(interp, builtin_dict); diff --git a/Python/specialize.c b/Python/specialize.c index e38e3556a6d6425..ea2638570f22d07 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -275,6 +275,8 @@ print_rare_event_stats(FILE *out, RareEventStats *stats) fprintf(out, "Rare event (set_eval_frame_func): %" PRIu64 "\n", stats->set_eval_frame_func); fprintf(out, "Rare event (builtin_dict): %" PRIu64 "\n", stats->builtin_dict); fprintf(out, "Rare event (func_modification): %" PRIu64 "\n", stats->func_modification); + fprintf(out, "Rare event (watched_dict_modification): %" PRIu64 "\n", stats->watched_dict_modification); + fprintf(out, "Rare event (watched_globals_modification): %" PRIu64 "\n", stats->watched_globals_modification); } static void diff --git a/Tools/scripts/summarize_stats.py b/Tools/scripts/summarize_stats.py index 9b7e7b999ea7c7d..7891b9cf923d339 100644 --- a/Tools/scripts/summarize_stats.py +++ b/Tools/scripts/summarize_stats.py @@ -415,7 +415,7 @@ def get_histogram(self, prefix: str) -> list[tuple[int, int]]: def get_rare_events(self) -> list[tuple[str, int]]: prefix = "Rare event " return [ - (key[len(prefix) + 1:-1], val) + (key[len(prefix) + 1:-1].replace("_", " "), val) for key, val in self._data.items() if key.startswith(prefix) ] From 91822018eeba12a6c9eabbc748363b2fd4291b30 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Mon, 12 Feb 2024 18:24:45 +0200 Subject: [PATCH 228/507] gh-115233: Fix an example in the Logging Cookbook (GH-115325) Also add more tests for LoggerAdapter. Also support stacklevel in LoggerAdapter._log(). --- Doc/howto/logging-cookbook.rst | 10 +- Lib/logging/__init__.py | 11 +-- Lib/test/test_logging.py | 91 +++++++++++++++++-- ...-02-12-12-26-17.gh-issue-115233.aug6r9.rst | 1 + 4 files changed, 90 insertions(+), 23 deletions(-) create mode 100644 Misc/NEWS.d/next/Documentation/2024-02-12-12-26-17.gh-issue-115233.aug6r9.rst diff --git a/Doc/howto/logging-cookbook.rst b/Doc/howto/logging-cookbook.rst index 80147e31fcbae11..f7d885ec88483d7 100644 --- a/Doc/howto/logging-cookbook.rst +++ b/Doc/howto/logging-cookbook.rst @@ -1744,13 +1744,11 @@ to the above, as in the following example:: return self.fmt.format(*self.args) class StyleAdapter(logging.LoggerAdapter): - def __init__(self, logger, extra=None): - super().__init__(logger, extra or {}) - - def log(self, level, msg, /, *args, **kwargs): + def log(self, level, msg, /, *args, stacklevel=1, **kwargs): if self.isEnabledFor(level): msg, kwargs = self.process(msg, kwargs) - self.logger._log(level, Message(msg, args), (), **kwargs) + self.logger.log(level, Message(msg, args), **kwargs, + stacklevel=stacklevel+1) logger = StyleAdapter(logging.getLogger(__name__)) @@ -1762,7 +1760,7 @@ to the above, as in the following example:: main() The above script should log the message ``Hello, world!`` when run with -Python 3.2 or later. +Python 3.8 or later. .. currentmodule:: logging diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 684b58d5548f91f..fcec9e76b986618 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -1949,18 +1949,11 @@ def hasHandlers(self): """ return self.logger.hasHandlers() - def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False): + def _log(self, level, msg, args, **kwargs): """ Low-level log implementation, proxied to allow nested logger adapters. """ - return self.logger._log( - level, - msg, - args, - exc_info=exc_info, - extra=extra, - stack_info=stack_info, - ) + return self.logger._log(level, msg, args, **kwargs) @property def manager(self): diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 888523227c2ac43..cf09bad4c9187bc 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -5478,6 +5478,7 @@ def test_critical(self): self.assertEqual(record.levelno, logging.CRITICAL) self.assertEqual(record.msg, msg) self.assertEqual(record.args, (self.recording,)) + self.assertEqual(record.funcName, 'test_critical') def test_is_enabled_for(self): old_disable = self.adapter.logger.manager.disable @@ -5496,15 +5497,9 @@ def test_has_handlers(self): self.assertFalse(self.adapter.hasHandlers()) def test_nested(self): - class Adapter(logging.LoggerAdapter): - prefix = 'Adapter' - - def process(self, msg, kwargs): - return f"{self.prefix} {msg}", kwargs - msg = 'Adapters can be nested, yo.' - adapter = Adapter(logger=self.logger, extra=None) - adapter_adapter = Adapter(logger=adapter, extra=None) + adapter = PrefixAdapter(logger=self.logger, extra=None) + adapter_adapter = PrefixAdapter(logger=adapter, extra=None) adapter_adapter.prefix = 'AdapterAdapter' self.assertEqual(repr(adapter), repr(adapter_adapter)) adapter_adapter.log(logging.CRITICAL, msg, self.recording) @@ -5513,6 +5508,7 @@ def process(self, msg, kwargs): self.assertEqual(record.levelno, logging.CRITICAL) self.assertEqual(record.msg, f"Adapter AdapterAdapter {msg}") self.assertEqual(record.args, (self.recording,)) + self.assertEqual(record.funcName, 'test_nested') orig_manager = adapter_adapter.manager self.assertIs(adapter.manager, orig_manager) self.assertIs(self.logger.manager, orig_manager) @@ -5528,6 +5524,61 @@ def process(self, msg, kwargs): self.assertIs(adapter.manager, orig_manager) self.assertIs(self.logger.manager, orig_manager) + def test_styled_adapter(self): + # Test an example from the Cookbook. + records = self.recording.records + adapter = StyleAdapter(self.logger) + adapter.warning('Hello, {}!', 'world') + self.assertEqual(str(records[-1].msg), 'Hello, world!') + self.assertEqual(records[-1].funcName, 'test_styled_adapter') + adapter.log(logging.WARNING, 'Goodbye {}.', 'world') + self.assertEqual(str(records[-1].msg), 'Goodbye world.') + self.assertEqual(records[-1].funcName, 'test_styled_adapter') + + def test_nested_styled_adapter(self): + records = self.recording.records + adapter = PrefixAdapter(self.logger) + adapter.prefix = '{}' + adapter2 = StyleAdapter(adapter) + adapter2.warning('Hello, {}!', 'world') + self.assertEqual(str(records[-1].msg), '{} Hello, world!') + self.assertEqual(records[-1].funcName, 'test_nested_styled_adapter') + adapter2.log(logging.WARNING, 'Goodbye {}.', 'world') + self.assertEqual(str(records[-1].msg), '{} Goodbye world.') + self.assertEqual(records[-1].funcName, 'test_nested_styled_adapter') + + def test_find_caller_with_stacklevel(self): + the_level = 1 + trigger = self.adapter.warning + + def innermost(): + trigger('test', stacklevel=the_level) + + def inner(): + innermost() + + def outer(): + inner() + + records = self.recording.records + outer() + self.assertEqual(records[-1].funcName, 'innermost') + lineno = records[-1].lineno + the_level += 1 + outer() + self.assertEqual(records[-1].funcName, 'inner') + self.assertGreater(records[-1].lineno, lineno) + lineno = records[-1].lineno + the_level += 1 + outer() + self.assertEqual(records[-1].funcName, 'outer') + self.assertGreater(records[-1].lineno, lineno) + lineno = records[-1].lineno + the_level += 1 + outer() + self.assertEqual(records[-1].funcName, 'test_find_caller_with_stacklevel') + self.assertGreater(records[-1].lineno, lineno) + def test_extra_in_records(self): self.adapter = logging.LoggerAdapter(logger=self.logger, extra={'foo': '1'}) @@ -5569,6 +5620,30 @@ def test_extra_merged_log_call_has_precedence(self): self.assertEqual(record.foo, '2') +class PrefixAdapter(logging.LoggerAdapter): + prefix = 'Adapter' + + def process(self, msg, kwargs): + return f"{self.prefix} {msg}", kwargs + + +class Message: + def __init__(self, fmt, args): + self.fmt = fmt + self.args = args + + def __str__(self): + return self.fmt.format(*self.args) + + +class StyleAdapter(logging.LoggerAdapter): + def log(self, level, msg, /, *args, stacklevel=1, **kwargs): + if self.isEnabledFor(level): + msg, kwargs = self.process(msg, kwargs) + self.logger.log(level, Message(msg, args), **kwargs, + stacklevel=stacklevel+1) + + class LoggerTest(BaseTest, AssertErrorMessage): def setUp(self): diff --git a/Misc/NEWS.d/next/Documentation/2024-02-12-12-26-17.gh-issue-115233.aug6r9.rst b/Misc/NEWS.d/next/Documentation/2024-02-12-12-26-17.gh-issue-115233.aug6r9.rst new file mode 100644 index 000000000000000..f37f94d12d4cf14 --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2024-02-12-12-26-17.gh-issue-115233.aug6r9.rst @@ -0,0 +1 @@ +Fix an example for :class:`~logging.LoggerAdapter` in the Logging Cookbook. From 91bf01d4b15a40be4510fd9ee5e6dc8e9c019fce Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Mon, 12 Feb 2024 19:27:27 +0300 Subject: [PATCH 229/507] gh-87804: Fix the refleak in error handling of `_pystatvfs_fromstructstatfs` (#115335) It was the macro expansion! Sorry! --- Modules/posixmodule.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 17032d9d490c782..ef6d65623bf0388 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -12916,14 +12916,15 @@ _pystatvfs_fromstructstatfs(PyObject *module, struct statfs st) { _Static_assert(sizeof(st.f_blocks) == sizeof(long long), "assuming large file"); -#define SET_ITEM(v, index, item) \ - do { \ - if (item == NULL) { \ - Py_DECREF(v); \ - return NULL; \ - } \ - PyStructSequence_SET_ITEM(v, index, item); \ - } while (0) \ +#define SET_ITEM(SEQ, INDEX, EXPR) \ + do { \ + PyObject *obj = (EXPR); \ + if (obj == NULL) { \ + Py_DECREF((SEQ)); \ + return NULL; \ + } \ + PyStructSequence_SET_ITEM((SEQ), (INDEX), obj); \ + } while (0) SET_ITEM(v, 0, PyLong_FromLong((long) st.f_iosize)); SET_ITEM(v, 1, PyLong_FromLong((long) st.f_bsize)); From c39272e143b346bd6a3c04ca4fbf299163888277 Mon Sep 17 00:00:00 2001 From: Steve Dower <steve.dower@python.org> Date: Mon, 12 Feb 2024 17:05:38 +0000 Subject: [PATCH 230/507] gh-115049: Fix py.exe failing when user has no LocalAppData. (GH-115185) Also ensure we always display a debug message or error for RC_INTERNAL_ERROR --- ...2024-02-08-21-37-22.gh-issue-115049.X1ObpJ.rst | 1 + PC/launcher2.c | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2024-02-08-21-37-22.gh-issue-115049.X1ObpJ.rst diff --git a/Misc/NEWS.d/next/Windows/2024-02-08-21-37-22.gh-issue-115049.X1ObpJ.rst b/Misc/NEWS.d/next/Windows/2024-02-08-21-37-22.gh-issue-115049.X1ObpJ.rst new file mode 100644 index 000000000000000..a679391857dcb32 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-02-08-21-37-22.gh-issue-115049.X1ObpJ.rst @@ -0,0 +1 @@ +Fixes ``py.exe`` launcher failing when run as users without user profiles. diff --git a/PC/launcher2.c b/PC/launcher2.c index e426eccd7000447..90b0fdebd3bdfb9 100644 --- a/PC/launcher2.c +++ b/PC/launcher2.c @@ -1594,6 +1594,7 @@ _registryReadLegacyEnvironment(const SearchInfo *search, HKEY root, EnvironmentI int count = swprintf_s(realTag, tagLength + 4, L"%s-32", env->tag); if (count == -1) { + debug(L"# Failed to generate 32bit tag\n"); free(realTag); return RC_INTERNAL_ERROR; } @@ -1749,10 +1750,18 @@ appxSearch(const SearchInfo *search, EnvironmentInfo **result, const wchar_t *pa exeName = search->windowed ? L"pythonw.exe" : L"python.exe"; } - if (FAILED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, buffer)) || - !join(buffer, MAXLEN, L"Microsoft\\WindowsApps") || + // Failure to get LocalAppData may just mean we're running as a user who + // doesn't have a profile directory. + // In this case, return "not found", but don't fail. + // Chances are they can't launch Store installs anyway. + if (FAILED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, buffer))) { + return RC_NO_PYTHON; + } + + if (!join(buffer, MAXLEN, L"Microsoft\\WindowsApps") || !join(buffer, MAXLEN, packageFamilyName) || !join(buffer, MAXLEN, exeName)) { + debug(L"# Failed to construct App Execution Alias path\n"); return RC_INTERNAL_ERROR; } @@ -1982,6 +1991,7 @@ collectEnvironments(const SearchInfo *search, EnvironmentInfo **result) EnvironmentInfo *env = NULL; if (!result) { + debug(L"# collectEnvironments() was passed a NULL result\n"); return RC_INTERNAL_ERROR; } *result = NULL; @@ -2276,6 +2286,7 @@ int selectEnvironment(const SearchInfo *search, EnvironmentInfo *root, EnvironmentInfo **best) { if (!best) { + debug(L"# selectEnvironment() was passed a NULL best\n"); return RC_INTERNAL_ERROR; } if (!root) { From 879f4546bfbc9c47ef228e7c3d2f126f3d8d64bf Mon Sep 17 00:00:00 2001 From: Petr Viktorin <encukou@gmail.com> Date: Mon, 12 Feb 2024 18:13:10 +0100 Subject: [PATCH 231/507] gh-110850: Add PyTime_t C API (GH-115215) * gh-110850: Add PyTime_t C API Add PyTime_t API: * PyTime_t type. * PyTime_MIN and PyTime_MAX constants. * PyTime_AsSecondsDouble(), PyTime_Monotonic(), PyTime_PerfCounter() and PyTime_GetSystemClock() functions. Co-authored-by: Victor Stinner <vstinner@python.org> --- Doc/c-api/time.rst | 83 ++++++++++++++ Doc/c-api/utilities.rst | 1 + Doc/conf.py | 13 ++- Doc/whatsnew/3.13.rst | 10 ++ Include/Python.h | 1 + Include/cpython/pytime.h | 23 ++++ Include/internal/pycore_time.h | 99 +++++++++-------- Lib/test/test_capi/test_time.py | 71 ++++++++++++ Lib/test/test_time.py | 17 +-- ...-11-16-02-07-48.gh-issue-110850.DQGNfF.rst | 9 ++ Modules/Setup.stdlib.in | 2 +- Modules/_randommodule.c | 2 +- Modules/_testcapi/parts.h | 1 + Modules/_testcapi/time.c | 105 ++++++++++++++++++ Modules/_testcapimodule.c | 3 + Modules/_testinternalcapi/pytime.c | 16 --- PCbuild/_testcapi.vcxproj | 1 + PCbuild/_testcapi.vcxproj.filters | 3 + Python/pytime.c | 102 +++++++++++------ 19 files changed, 448 insertions(+), 114 deletions(-) create mode 100644 Doc/c-api/time.rst create mode 100644 Include/cpython/pytime.h create mode 100644 Lib/test/test_capi/test_time.py create mode 100644 Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst create mode 100644 Modules/_testcapi/time.c diff --git a/Doc/c-api/time.rst b/Doc/c-api/time.rst new file mode 100644 index 000000000000000..7791cdb17810550 --- /dev/null +++ b/Doc/c-api/time.rst @@ -0,0 +1,83 @@ +.. highlight:: c + +PyTime C API +============ + +.. versionadded:: 3.13 + +The clock C API provides access to system clocks. +It is similar to the Python :mod:`time` module. + +For C API related to the :mod:`datetime` module, see :ref:`datetimeobjects`. + + +Types +----- + +.. c:type:: PyTime_t + + A timestamp or duration in nanoseconds, represented as a signed 64-bit + integer. + + The reference point for timestamps depends on the clock used. For example, + :c:func:`PyTime_Time` returns timestamps relative to the UNIX epoch. + + The supported range is around [-292.3 years; +292.3 years]. + Using the Unix epoch (January 1st, 1970) as reference, the supported date + range is around [1677-09-21; 2262-04-11]. + The exact limits are exposed as constants: + +.. c:var:: PyTime_t PyTime_MIN + + Minimum value of :c:type:`PyTime_t`. + +.. c:var:: PyTime_t PyTime_MAX + + Maximum value of :c:type:`PyTime_t`. + + +Clock Functions +--------------- + +The following functions take a pointer to a :c:expr:`PyTime_t` that they +set to the value of a particular clock. +Details of each clock are given in the documentation of the corresponding +Python function. + +The functions return ``0`` on success, or ``-1`` (with an exception set) +on failure. + +On integer overflow, they set the :c:data:`PyExc_OverflowError` exception and +set ``*result`` to the value clamped to the ``[PyTime_MIN; PyTime_MAX]`` +range. +(On current systems, integer overflows are likely caused by misconfigured +system time.) + +As any other C API (unless otherwise specified), the functions must be called +with the :term:`GIL` held. + +.. c:function:: int PyTime_Monotonic(PyTime_t *result) + + Read the monotonic clock. + See :func:`time.monotonic` for important details on this clock. + +.. c:function:: int PyTime_PerfCounter(PyTime_t *result) + + Read the performance counter. + See :func:`time.perf_counter` for important details on this clock. + +.. c:function:: int PyTime_Time(PyTime_t *result) + + Read the “wall clock” time. + See :func:`time.time` for details important on this clock. + + +Conversion functions +-------------------- + +.. c:function:: double PyTime_AsSecondsDouble(PyTime_t t) + + Convert a timestamp to a number of seconds as a C :c:expr:`double`. + + The function cannot fail, but note that :c:expr:`double` has limited + accuracy for large values. diff --git a/Doc/c-api/utilities.rst b/Doc/c-api/utilities.rst index 48ae54acebe8879..9d0abf440f791d4 100644 --- a/Doc/c-api/utilities.rst +++ b/Doc/c-api/utilities.rst @@ -20,4 +20,5 @@ and parsing function arguments and constructing Python values from C values. hash.rst reflection.rst codec.rst + time.rst perfmaps.rst diff --git a/Doc/conf.py b/Doc/conf.py index c2d57696aeeaa37..aa7f85bc1b3efa8 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -135,11 +135,14 @@ ('c:type', 'wchar_t'), ('c:type', '__int64'), ('c:type', 'unsigned __int64'), + ('c:type', 'double'), # Standard C structures ('c:struct', 'in6_addr'), ('c:struct', 'in_addr'), ('c:struct', 'stat'), ('c:struct', 'statvfs'), + ('c:struct', 'timeval'), + ('c:struct', 'timespec'), # Standard C macros ('c:macro', 'LLONG_MAX'), ('c:macro', 'LLONG_MIN'), @@ -269,12 +272,12 @@ ('py:meth', 'index'), # list.index, tuple.index, etc. ] -# gh-106948: Copy standard C types declared in the "c:type" domain to the -# "c:identifier" domain, since "c:function" markup looks for types in the -# "c:identifier" domain. Use list() to not iterate on items which are being -# added +# gh-106948: Copy standard C types declared in the "c:type" domain and C +# structures declared in the "c:struct" domain to the "c:identifier" domain, +# since "c:function" markup looks for types in the "c:identifier" domain. Use +# list() to not iterate on items which are being added for role, name in list(nitpick_ignore): - if role == 'c:type': + if role in ('c:type', 'c:struct'): nitpick_ignore.append(('c:identifier', name)) del role, name diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 1b803278ae0d5bd..191657061f74033 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1516,6 +1516,16 @@ New Features * Add :c:func:`Py_HashPointer` function to hash a pointer. (Contributed by Victor Stinner in :gh:`111545`.) +* Add PyTime C API: + + * :c:type:`PyTime_t` type. + * :c:var:`PyTime_MIN` and :c:var:`PyTime_MAX` constants. + * :c:func:`PyTime_AsSecondsDouble` + :c:func:`PyTime_Monotonic`, :c:func:`PyTime_PerfCounter`, and + :c:func:`PyTime_Time` functions. + + (Contributed by Victor Stinner and Petr Viktorin in :gh:`110850`.) + Porting to Python 3.13 ---------------------- diff --git a/Include/Python.h b/Include/Python.h index 196751c3201e620..01fc45137a17bbc 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -97,6 +97,7 @@ #include "weakrefobject.h" #include "structseq.h" #include "cpython/picklebufobject.h" +#include "cpython/pytime.h" #include "codecs.h" #include "pyerrors.h" #include "pythread.h" diff --git a/Include/cpython/pytime.h b/Include/cpython/pytime.h new file mode 100644 index 000000000000000..d8244700d614ceb --- /dev/null +++ b/Include/cpython/pytime.h @@ -0,0 +1,23 @@ +// PyTime_t C API: see Doc/c-api/time.rst for the documentation. + +#ifndef Py_LIMITED_API +#ifndef Py_PYTIME_H +#define Py_PYTIME_H +#ifdef __cplusplus +extern "C" { +#endif + +typedef int64_t PyTime_t; +#define PyTime_MIN INT64_MIN +#define PyTime_MAX INT64_MAX + +PyAPI_FUNC(double) PyTime_AsSecondsDouble(PyTime_t t); +PyAPI_FUNC(int) PyTime_Monotonic(PyTime_t *result); +PyAPI_FUNC(int) PyTime_PerfCounter(PyTime_t *result); +PyAPI_FUNC(int) PyTime_Time(PyTime_t *result); + +#ifdef __cplusplus +} +#endif +#endif /* Py_PYTIME_H */ +#endif /* Py_LIMITED_API */ diff --git a/Include/internal/pycore_time.h b/Include/internal/pycore_time.h index dabbd7b41556cdf..1aad6ccea69ae35 100644 --- a/Include/internal/pycore_time.h +++ b/Include/internal/pycore_time.h @@ -1,34 +1,39 @@ -// The _PyTime_t API is written to use timestamp and timeout values stored in -// various formats and to read clocks. +// Internal PyTime_t C API: see Doc/c-api/time.rst for the documentation. // -// The _PyTime_t type is an integer to support directly common arithmetic -// operations like t1 + t2. +// The PyTime_t type is an integer to support directly common arithmetic +// operations such as t1 + t2. // -// The _PyTime_t API supports a resolution of 1 nanosecond. The _PyTime_t type -// is signed to support negative timestamps. The supported range is around -// [-292.3 years; +292.3 years]. Using the Unix epoch (January 1st, 1970), the -// supported date range is around [1677-09-21; 2262-04-11]. +// Time formats: // -// Formats: +// * Seconds. +// * Seconds as a floating point number (C double). +// * Milliseconds (10^-3 seconds). +// * Microseconds (10^-6 seconds). +// * 100 nanoseconds (10^-7 seconds), used on Windows. +// * Nanoseconds (10^-9 seconds). +// * timeval structure, 1 microsecond (10^-6 seconds). +// * timespec structure, 1 nanosecond (10^-9 seconds). // -// * seconds -// * seconds as a floating pointer number (C double) -// * milliseconds (10^-3 seconds) -// * microseconds (10^-6 seconds) -// * 100 nanoseconds (10^-7 seconds) -// * nanoseconds (10^-9 seconds) -// * timeval structure, 1 microsecond resolution (10^-6 seconds) -// * timespec structure, 1 nanosecond resolution (10^-9 seconds) +// Note that PyTime_t is now specified as int64_t, in nanoseconds. +// (If we need to change this, we'll need new public API with new names.) +// Previously, PyTime_t was configurable (in theory); some comments and code +// might still allude to that. // // Integer overflows are detected and raise OverflowError. Conversion to a -// resolution worse than 1 nanosecond is rounded correctly with the requested -// rounding mode. There are 4 rounding modes: floor (towards -inf), ceiling -// (towards +inf), half even and up (away from zero). +// resolution larger than 1 nanosecond is rounded correctly with the requested +// rounding mode. Available rounding modes: // -// Some functions clamp the result in the range [_PyTime_MIN; _PyTime_MAX], so -// the caller doesn't have to handle errors and doesn't need to hold the GIL. -// For example, _PyTime_Add(t1, t2) computes t1+t2 and clamp the result on -// overflow. +// * Round towards minus infinity (-inf). For example, used to read a clock. +// * Round towards infinity (+inf). For example, used for timeout to wait "at +// least" N seconds. +// * Round to nearest with ties going to nearest even integer. For example, used +// to round from a Python float. +// * Round away from zero. For example, used for timeout. +// +// Some functions clamp the result in the range [PyTime_MIN; PyTime_MAX]. The +// caller doesn't have to handle errors and so doesn't need to hold the GIL to +// handle exceptions. For example, _PyTime_Add(t1, t2) computes t1+t2 and +// clamps the result on overflow. // // Clocks: // @@ -36,10 +41,11 @@ // * Monotonic clock // * Performance counter // -// Operations like (t * k / q) with integers are implemented in a way to reduce -// the risk of integer overflow. Such operation is used to convert a clock -// value expressed in ticks with a frequency to _PyTime_t, like -// QueryPerformanceCounter() with QueryPerformanceFrequency(). +// Internally, operations like (t * k / q) with integers are implemented in a +// way to reduce the risk of integer overflow. Such operation is used to convert a +// clock value expressed in ticks with a frequency to PyTime_t, like +// QueryPerformanceCounter() with QueryPerformanceFrequency() on Windows. + #ifndef Py_INTERNAL_TIME_H #define Py_INTERNAL_TIME_H @@ -56,14 +62,7 @@ extern "C" { struct timeval; #endif -// _PyTime_t: Python timestamp with subsecond precision. It can be used to -// store a duration, and so indirectly a date (related to another date, like -// UNIX epoch). -typedef int64_t _PyTime_t; -// _PyTime_MIN nanoseconds is around -292.3 years -#define _PyTime_MIN INT64_MIN -// _PyTime_MAX nanoseconds is around +292.3 years -#define _PyTime_MAX INT64_MAX +typedef PyTime_t _PyTime_t; #define _SIZEOF_PYTIME_T 8 typedef enum { @@ -147,7 +146,7 @@ PyAPI_FUNC(_PyTime_t) _PyTime_FromSecondsDouble(double seconds, _PyTime_round_t PyAPI_FUNC(_PyTime_t) _PyTime_FromNanoseconds(_PyTime_t ns); // Create a timestamp from a number of microseconds. -// Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow. +// Clamp to [PyTime_MIN; PyTime_MAX] on overflow. extern _PyTime_t _PyTime_FromMicrosecondsClamp(_PyTime_t us); // Create a timestamp from nanoseconds (Python int). @@ -169,10 +168,6 @@ PyAPI_FUNC(int) _PyTime_FromMillisecondsObject(_PyTime_t *t, PyObject *obj, _PyTime_round_t round); -// Convert a timestamp to a number of seconds as a C double. -// Export for '_socket' shared extension. -PyAPI_FUNC(double) _PyTime_AsSecondsDouble(_PyTime_t t); - // Convert timestamp to a number of milliseconds (10^-3 seconds). // Export for '_ssl' shared extension. PyAPI_FUNC(_PyTime_t) _PyTime_AsMilliseconds(_PyTime_t t, @@ -183,9 +178,6 @@ PyAPI_FUNC(_PyTime_t) _PyTime_AsMilliseconds(_PyTime_t t, PyAPI_FUNC(_PyTime_t) _PyTime_AsMicroseconds(_PyTime_t t, _PyTime_round_t round); -// Convert timestamp to a number of nanoseconds (10^-9 seconds). -extern _PyTime_t _PyTime_AsNanoseconds(_PyTime_t t); - #ifdef MS_WINDOWS // Convert timestamp to a number of 100 nanoseconds (10^-7 seconds). extern _PyTime_t _PyTime_As100Nanoseconds(_PyTime_t t, @@ -250,7 +242,7 @@ PyAPI_FUNC(void) _PyTime_AsTimespec_clamp(_PyTime_t t, struct timespec *ts); #endif -// Compute t1 + t2. Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow. +// Compute t1 + t2. Clamp to [PyTime_MIN; PyTime_MAX] on overflow. extern _PyTime_t _PyTime_Add(_PyTime_t t1, _PyTime_t t2); // Structure used by time.get_clock_info() @@ -267,7 +259,8 @@ typedef struct { // On integer overflow, silently ignore the overflow and clamp the clock to // [_PyTime_MIN; _PyTime_MAX]. // -// Use _PyTime_GetSystemClockWithInfo() to check for failure. +// Use _PyTime_GetSystemClockWithInfo or the public PyTime_Time() to check +// for failure. // Export for '_random' shared extension. PyAPI_FUNC(_PyTime_t) _PyTime_GetSystemClock(void); @@ -287,7 +280,8 @@ extern int _PyTime_GetSystemClockWithInfo( // On integer overflow, silently ignore the overflow and clamp the clock to // [_PyTime_MIN; _PyTime_MAX]. // -// Use _PyTime_GetMonotonicClockWithInfo() to check for failure. +// Use _PyTime_GetMonotonicClockWithInfo or the public PyTime_Monotonic() +// to check for failure. // Export for '_random' shared extension. PyAPI_FUNC(_PyTime_t) _PyTime_GetMonotonicClock(void); @@ -322,10 +316,12 @@ PyAPI_FUNC(int) _PyTime_gmtime(time_t t, struct tm *tm); // On integer overflow, silently ignore the overflow and clamp the clock to // [_PyTime_MIN; _PyTime_MAX]. // -// Use _PyTime_GetPerfCounterWithInfo() to check for failure. +// Use _PyTime_GetPerfCounterWithInfo() or the public PyTime_PerfCounter +// to check for failure. // Export for '_lsprof' shared extension. PyAPI_FUNC(_PyTime_t) _PyTime_GetPerfCounter(void); + // Get the performance counter: clock with the highest available resolution to // measure a short duration. // @@ -336,6 +332,13 @@ extern int _PyTime_GetPerfCounterWithInfo( _PyTime_t *t, _Py_clock_info_t *info); +// Alias for backward compatibility +#define _PyTime_MIN PyTime_MIN +#define _PyTime_MAX PyTime_MAX +#define _PyTime_AsSecondsDouble PyTime_AsSecondsDouble + + +// --- _PyDeadline ----------------------------------------------------------- // Create a deadline. // Pseudo code: _PyTime_GetMonotonicClock() + timeout. diff --git a/Lib/test/test_capi/test_time.py b/Lib/test/test_capi/test_time.py new file mode 100644 index 000000000000000..10b7fbf2c372a3b --- /dev/null +++ b/Lib/test/test_capi/test_time.py @@ -0,0 +1,71 @@ +import time +import unittest +from test.support import import_helper +_testcapi = import_helper.import_module('_testcapi') + + +PyTime_MIN = _testcapi.PyTime_MIN +PyTime_MAX = _testcapi.PyTime_MAX +SEC_TO_NS = 10 ** 9 +DAY_TO_SEC = (24 * 60 * 60) +# Worst clock resolution: maximum delta between two clock reads. +CLOCK_RES = 0.050 + + +class CAPITest(unittest.TestCase): + def test_min_max(self): + # PyTime_t is just int64_t + self.assertEqual(PyTime_MIN, -2**63) + self.assertEqual(PyTime_MAX, 2**63 - 1) + + def check_clock(self, c_func, py_func): + t1 = c_func() + t2 = py_func() + self.assertAlmostEqual(t1, t2, delta=CLOCK_RES) + + def test_assecondsdouble(self): + # Test PyTime_AsSecondsDouble() + def ns_to_sec(ns): + if abs(ns) % SEC_TO_NS == 0: + return float(ns // SEC_TO_NS) + else: + return float(ns) / SEC_TO_NS + + seconds = ( + 0, + 1, + DAY_TO_SEC, + 365 * DAY_TO_SEC, + ) + values = { + PyTime_MIN, + PyTime_MIN + 1, + PyTime_MAX - 1, + PyTime_MAX, + } + for second in seconds: + ns = second * SEC_TO_NS + values.add(ns) + # test nanosecond before/after to test rounding + values.add(ns - 1) + values.add(ns + 1) + for ns in list(values): + if (-ns) > PyTime_MAX: + continue + values.add(-ns) + for ns in sorted(values): + with self.subTest(ns=ns): + self.assertEqual(_testcapi.PyTime_AsSecondsDouble(ns), + ns_to_sec(ns)) + + def test_monotonic(self): + # Test PyTime_Monotonic() + self.check_clock(_testcapi.PyTime_Monotonic, time.monotonic) + + def test_perf_counter(self): + # Test PyTime_PerfCounter() + self.check_clock(_testcapi.PyTime_PerfCounter, time.perf_counter) + + def test_time(self): + # Test PyTime_time() + self.check_clock(_testcapi.PyTime_Time, time.time) diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 3b5640abdb6b892..a0aeea515afbd69 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -43,8 +43,8 @@ class _PyTime(enum.IntEnum): ROUND_UP = 3 # _PyTime_t is int64_t -_PyTime_MIN = -2 ** 63 -_PyTime_MAX = 2 ** 63 - 1 +PyTime_MIN = -2 ** 63 +PyTime_MAX = 2 ** 63 - 1 # Rounding modes supported by PyTime ROUNDING_MODES = ( @@ -934,7 +934,7 @@ def test_FromSecondsObject(self): _PyTime_FromSecondsObject(float('nan'), time_rnd) def test_AsSecondsDouble(self): - from _testinternalcapi import _PyTime_AsSecondsDouble + from _testcapi import PyTime_AsSecondsDouble def float_converter(ns): if abs(ns) % SEC_TO_NS == 0: @@ -942,15 +942,10 @@ def float_converter(ns): else: return float(ns) / SEC_TO_NS - self.check_int_rounding(lambda ns, rnd: _PyTime_AsSecondsDouble(ns), + self.check_int_rounding(lambda ns, rnd: PyTime_AsSecondsDouble(ns), float_converter, NS_TO_SEC) - # test nan - for time_rnd, _ in ROUNDING_MODES: - with self.assertRaises(TypeError): - _PyTime_AsSecondsDouble(float('nan')) - def create_decimal_converter(self, denominator): denom = decimal.Decimal(denominator) @@ -1009,7 +1004,7 @@ def test_AsTimeval_clamp(self): tv_sec_max = self.time_t_max tv_sec_min = self.time_t_min - for t in (_PyTime_MIN, _PyTime_MAX): + for t in (PyTime_MIN, PyTime_MAX): ts = _PyTime_AsTimeval_clamp(t, _PyTime.ROUND_CEILING) with decimal.localcontext() as context: context.rounding = decimal.ROUND_CEILING @@ -1028,7 +1023,7 @@ def test_AsTimeval_clamp(self): def test_AsTimespec_clamp(self): from _testinternalcapi import _PyTime_AsTimespec_clamp - for t in (_PyTime_MIN, _PyTime_MAX): + for t in (PyTime_MIN, PyTime_MAX): ts = _PyTime_AsTimespec_clamp(t) tv_sec, tv_nsec = divmod(t, NS_TO_SEC) if self.time_t_max < tv_sec: diff --git a/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst b/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst new file mode 100644 index 000000000000000..998d4426dd53f92 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst @@ -0,0 +1,9 @@ +Add PyTime C API: + +* :c:type:`PyTime_t` type. +* :c:var:`PyTime_MIN` and :c:var:`PyTime_MAX` constants. +* :c:func:`PyTime_AsSecondsDouble`, + :c:func:`PyTime_Monotonic`, :c:func:`PyTime_PerfCounter`, and + :c:func:`PyTime_Time` functions. + +Patch by Victor Stinner. diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 8a65a9cffb1b9d8..e98775a48087653 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -162,7 +162,7 @@ @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c -@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/pyos.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c _testcapi/hash.c +@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/pyos.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c _testcapi/hash.c _testcapi/time.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c diff --git a/Modules/_randommodule.c b/Modules/_randommodule.c index 4403e1d132c0570..5481ed9b348ed77 100644 --- a/Modules/_randommodule.c +++ b/Modules/_randommodule.c @@ -262,7 +262,7 @@ random_seed_urandom(RandomObject *self) static void random_seed_time_pid(RandomObject *self) { - _PyTime_t now; + PyTime_t now; uint32_t key[5]; now = _PyTime_GetSystemClock(); diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h index 29817edd69b1348..e8cfb2423500d4b 100644 --- a/Modules/_testcapi/parts.h +++ b/Modules/_testcapi/parts.h @@ -59,6 +59,7 @@ int _PyTestCapi_Init_Immortal(PyObject *module); int _PyTestCapi_Init_GC(PyObject *module); int _PyTestCapi_Init_Sys(PyObject *module); int _PyTestCapi_Init_Hash(PyObject *module); +int _PyTestCapi_Init_Time(PyObject *module); int _PyTestCapi_Init_VectorcallLimited(PyObject *module); int _PyTestCapi_Init_HeaptypeRelative(PyObject *module); diff --git a/Modules/_testcapi/time.c b/Modules/_testcapi/time.c new file mode 100644 index 000000000000000..4fbf7dd14ebb663 --- /dev/null +++ b/Modules/_testcapi/time.c @@ -0,0 +1,105 @@ +#include "parts.h" + + +static int +pytime_from_nanoseconds(PyTime_t *tp, PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expect int, got %s", + Py_TYPE(obj)->tp_name); + return -1; + } + + long long nsec = PyLong_AsLongLong(obj); + if (nsec == -1 && PyErr_Occurred()) { + return -1; + } + + Py_BUILD_ASSERT(sizeof(long long) == sizeof(PyTime_t)); + *tp = (PyTime_t)nsec; + return 0; +} + + +static PyObject * +test_pytime_assecondsdouble(PyObject *Py_UNUSED(self), PyObject *args) +{ + PyObject *obj; + if (!PyArg_ParseTuple(args, "O", &obj)) { + return NULL; + } + PyTime_t ts; + if (pytime_from_nanoseconds(&ts, obj) < 0) { + return NULL; + } + double d = PyTime_AsSecondsDouble(ts); + return PyFloat_FromDouble(d); +} + + +static PyObject* +pytime_as_float(PyTime_t t) +{ + return PyFloat_FromDouble(PyTime_AsSecondsDouble(t)); +} + + + +static PyObject* +test_pytime_monotonic(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) +{ + PyTime_t t; + if (PyTime_Monotonic(&t) < 0) { + return NULL; + } + return pytime_as_float(t); +} + + +static PyObject* +test_pytime_perf_counter(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) +{ + PyTime_t t; + if (PyTime_PerfCounter(&t) < 0) { + return NULL; + } + return pytime_as_float(t); +} + + +static PyObject* +test_pytime_time(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) +{ + PyTime_t t; + if (PyTime_Time(&t) < 0) { + printf("ERR! %d\n", (int)t); + return NULL; + } + printf("... %d\n", (int)t); + return pytime_as_float(t); +} + + +static PyMethodDef test_methods[] = { + {"PyTime_AsSecondsDouble", test_pytime_assecondsdouble, METH_VARARGS}, + {"PyTime_Monotonic", test_pytime_monotonic, METH_NOARGS}, + {"PyTime_PerfCounter", test_pytime_perf_counter, METH_NOARGS}, + {"PyTime_Time", test_pytime_time, METH_NOARGS}, + {NULL}, +}; + +int +_PyTestCapi_Init_Time(PyObject *m) +{ + if (PyModule_AddFunctions(m, test_methods) < 0) { + return -1; + } + Py_BUILD_ASSERT(sizeof(long long) == sizeof(PyTime_t)); + if (PyModule_AddObject(m, "PyTime_MIN", PyLong_FromLongLong(PyTime_MIN)) < 0) { + return 1; + } + if (PyModule_AddObject(m, "PyTime_MAX", PyLong_FromLongLong(PyTime_MAX)) < 0) { + return 1; + } + return 0; +} diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index e67de3eeb6e17eb..b03f871b089c8a3 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -4107,6 +4107,9 @@ PyInit__testcapi(void) if (_PyTestCapi_Init_Hash(m) < 0) { return NULL; } + if (_PyTestCapi_Init_Time(m) < 0) { + return NULL; + } PyState_AddModule(m, &_testcapimodule); return m; diff --git a/Modules/_testinternalcapi/pytime.c b/Modules/_testinternalcapi/pytime.c index 2b5f9eb0ef28511..f0f758ea032df82 100644 --- a/Modules/_testinternalcapi/pytime.c +++ b/Modules/_testinternalcapi/pytime.c @@ -52,21 +52,6 @@ test_pytime_fromsecondsobject(PyObject *self, PyObject *args) return _PyTime_AsNanosecondsObject(ts); } -static PyObject * -test_pytime_assecondsdouble(PyObject *self, PyObject *args) -{ - PyObject *obj; - if (!PyArg_ParseTuple(args, "O", &obj)) { - return NULL; - } - _PyTime_t ts; - if (_PyTime_FromNanosecondsObject(&ts, obj) < 0) { - return NULL; - } - double d = _PyTime_AsSecondsDouble(ts); - return PyFloat_FromDouble(d); -} - static PyObject * test_PyTime_AsTimeval(PyObject *self, PyObject *args) { @@ -254,7 +239,6 @@ test_pytime_object_to_timespec(PyObject *self, PyObject *args) static PyMethodDef TestMethods[] = { {"_PyTime_AsMicroseconds", test_PyTime_AsMicroseconds, METH_VARARGS}, {"_PyTime_AsMilliseconds", test_PyTime_AsMilliseconds, METH_VARARGS}, - {"_PyTime_AsSecondsDouble", test_pytime_assecondsdouble, METH_VARARGS}, #ifdef HAVE_CLOCK_GETTIME {"_PyTime_AsTimespec", test_PyTime_AsTimespec, METH_VARARGS}, {"_PyTime_AsTimespec_clamp", test_PyTime_AsTimespec_clamp, METH_VARARGS}, diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj index 6911aacab29b979..66df0a61b5b5a66 100644 --- a/PCbuild/_testcapi.vcxproj +++ b/PCbuild/_testcapi.vcxproj @@ -125,6 +125,7 @@ <ClCompile Include="..\Modules\_testcapi\codec.c" /> <ClCompile Include="..\Modules\_testcapi\sys.c" /> <ClCompile Include="..\Modules\_testcapi\hash.c" /> + <ClCompile Include="..\Modules\_testcapi\time.c" /> <ClCompile Include="..\Modules\_testcapi\immortal.c" /> <ClCompile Include="..\Modules\_testcapi\gc.c" /> </ItemGroup> diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters index 6059959bb9a040f..651eb1d6ba0b7f3 100644 --- a/PCbuild/_testcapi.vcxproj.filters +++ b/PCbuild/_testcapi.vcxproj.filters @@ -105,6 +105,9 @@ <ClCompile Include="..\Modules\_testcapi\hash.c"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="..\Modules\_testcapi\time.c"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="..\Modules\_testcapi\gc.c"> <Filter>Source Files</Filter> </ClCompile> diff --git a/Python/pytime.c b/Python/pytime.c index 77cb95f8feb179f..fb0ed85c541e68c 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -50,7 +50,7 @@ # error "time_t is not a two's complement integer type" #endif -#if _PyTime_MIN + _PyTime_MAX != -1 +#if PyTime_MIN + PyTime_MAX != -1 # error "_PyTime_t is not a two's complement integer type" #endif @@ -124,16 +124,16 @@ pytime_as_nanoseconds(_PyTime_t t) } -// Compute t1 + t2. Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow. +// Compute t1 + t2. Clamp to [PyTime_MIN; PyTime_MAX] on overflow. static inline int pytime_add(_PyTime_t *t1, _PyTime_t t2) { - if (t2 > 0 && *t1 > _PyTime_MAX - t2) { - *t1 = _PyTime_MAX; + if (t2 > 0 && *t1 > PyTime_MAX - t2) { + *t1 = PyTime_MAX; return -1; } - else if (t2 < 0 && *t1 < _PyTime_MIN - t2) { - *t1 = _PyTime_MIN; + else if (t2 < 0 && *t1 < PyTime_MIN - t2) { + *t1 = PyTime_MIN; return -1; } else { @@ -156,7 +156,7 @@ pytime_mul_check_overflow(_PyTime_t a, _PyTime_t b) { if (b != 0) { assert(b > 0); - return ((a < _PyTime_MIN / b) || (_PyTime_MAX / b < a)); + return ((a < PyTime_MIN / b) || (PyTime_MAX / b < a)); } else { return 0; @@ -164,13 +164,13 @@ pytime_mul_check_overflow(_PyTime_t a, _PyTime_t b) } -// Compute t * k. Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow. +// Compute t * k. Clamp to [PyTime_MIN; PyTime_MAX] on overflow. static inline int pytime_mul(_PyTime_t *t, _PyTime_t k) { assert(k >= 0); if (pytime_mul_check_overflow(*t, k)) { - *t = (*t >= 0) ? _PyTime_MAX : _PyTime_MIN; + *t = (*t >= 0) ? PyTime_MAX : PyTime_MIN; return -1; } else { @@ -180,7 +180,7 @@ pytime_mul(_PyTime_t *t, _PyTime_t k) } -// Compute t * k. Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow. +// Compute t * k. Clamp to [PyTime_MIN; PyTime_MAX] on overflow. static inline _PyTime_t _PyTime_Mul(_PyTime_t t, _PyTime_t k) { @@ -459,12 +459,12 @@ _PyTime_FromSeconds(int seconds) /* ensure that integer overflow cannot happen, int type should have 32 bits, whereas _PyTime_t type has at least 64 bits (SEC_TO_NS takes 30 bits). */ - static_assert(INT_MAX <= _PyTime_MAX / SEC_TO_NS, "_PyTime_t overflow"); - static_assert(INT_MIN >= _PyTime_MIN / SEC_TO_NS, "_PyTime_t underflow"); + static_assert(INT_MAX <= PyTime_MAX / SEC_TO_NS, "_PyTime_t overflow"); + static_assert(INT_MIN >= PyTime_MIN / SEC_TO_NS, "_PyTime_t underflow"); _PyTime_t t = (_PyTime_t)seconds; - assert((t >= 0 && t <= _PyTime_MAX / SEC_TO_NS) - || (t < 0 && t >= _PyTime_MIN / SEC_TO_NS)); + assert((t >= 0 && t <= PyTime_MAX / SEC_TO_NS) + || (t < 0 && t >= PyTime_MIN / SEC_TO_NS)); t *= SEC_TO_NS; return pytime_from_nanoseconds(t); } @@ -587,7 +587,7 @@ pytime_from_double(_PyTime_t *tp, double value, _PyTime_round_t round, d = pytime_round(d, round); /* See comments in pytime_double_to_denominator */ - if (!((double)_PyTime_MIN <= d && d < -(double)_PyTime_MIN)) { + if (!((double)PyTime_MIN <= d && d < -(double)PyTime_MIN)) { pytime_time_t_overflow(); return -1; } @@ -649,12 +649,12 @@ _PyTime_FromMillisecondsObject(_PyTime_t *tp, PyObject *obj, _PyTime_round_t rou double -_PyTime_AsSecondsDouble(_PyTime_t t) +PyTime_AsSecondsDouble(PyTime_t t) { /* volatile avoids optimization changing how numbers are rounded */ volatile double d; - _PyTime_t ns = pytime_as_nanoseconds(t); + PyTime_t ns = pytime_as_nanoseconds(t); if (ns % SEC_TO_NS == 0) { /* Divide using integers to avoid rounding issues on the integer part. 1e-9 cannot be stored exactly in IEEE 64-bit. */ @@ -695,7 +695,7 @@ pytime_divide_round_up(const _PyTime_t t, const _PyTime_t k) assert(k > 1); if (t >= 0) { // Don't use (t + k - 1) / k to avoid integer overflow - // if t is equal to _PyTime_MAX + // if t is equal to PyTime_MAX _PyTime_t q = t / k; if (t % k) { q += 1; @@ -704,7 +704,7 @@ pytime_divide_round_up(const _PyTime_t t, const _PyTime_t k) } else { // Don't use (t - (k - 1)) / k to avoid integer overflow - // if t is equals to _PyTime_MIN. + // if t is equals to PyTime_MIN. _PyTime_t q = t / k; if (t % k) { q -= 1; @@ -759,7 +759,7 @@ pytime_divide(const _PyTime_t t, const _PyTime_t k, // Compute (t / k, t % k) in (pq, pr). // Make sure that 0 <= pr < k. // Return 0 on success. -// Return -1 on underflow and store (_PyTime_MIN, 0) in (pq, pr). +// Return -1 on underflow and store (PyTime_MIN, 0) in (pq, pr). static int pytime_divmod(const _PyTime_t t, const _PyTime_t k, _PyTime_t *pq, _PyTime_t *pr) @@ -768,8 +768,8 @@ pytime_divmod(const _PyTime_t t, const _PyTime_t k, _PyTime_t q = t / k; _PyTime_t r = t % k; if (r < 0) { - if (q == _PyTime_MIN) { - *pq = _PyTime_MIN; + if (q == PyTime_MIN) { + *pq = PyTime_MIN; *pr = 0; return -1; } @@ -784,13 +784,6 @@ pytime_divmod(const _PyTime_t t, const _PyTime_t k, } -_PyTime_t -_PyTime_AsNanoseconds(_PyTime_t t) -{ - return pytime_as_nanoseconds(t); -} - - #ifdef MS_WINDOWS _PyTime_t _PyTime_As100Nanoseconds(_PyTime_t t, _PyTime_round_t round) @@ -926,6 +919,7 @@ _PyTime_AsTimespec(_PyTime_t t, struct timespec *ts) #endif +// N.B. If raise_exc=0, this may be called without the GIL. static int py_get_system_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) { @@ -1050,6 +1044,18 @@ _PyTime_GetSystemClock(void) } +int +PyTime_Time(PyTime_t *result) +{ + if (py_get_system_clock(result, NULL, 1) < 0) { + // If clock_gettime(CLOCK_REALTIME) or gettimeofday() fails: + // silently ignore the failure and return 0. + *result = 0; + return -1; + } + return 1; +} + int _PyTime_GetSystemClockWithInfo(_PyTime_t *t, _Py_clock_info_t *info) { @@ -1092,6 +1098,7 @@ py_mach_timebase_info(_PyTimeFraction *base, int raise) #endif +// N.B. If raise_exc=0, this may be called without the GIL. static int py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) { @@ -1102,13 +1109,13 @@ py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) static_assert(sizeof(ticks) <= sizeof(_PyTime_t), "ULONGLONG is larger than _PyTime_t"); _PyTime_t t; - if (ticks <= (ULONGLONG)_PyTime_MAX) { + if (ticks <= (ULONGLONG)PyTime_MAX) { t = (_PyTime_t)ticks; } else { // GetTickCount64() maximum is larger than _PyTime_t maximum: // ULONGLONG is unsigned, whereas _PyTime_t is signed. - t = _PyTime_MAX; + t = PyTime_MAX; } int res = pytime_mul(&t, MS_TO_NS); @@ -1151,7 +1158,7 @@ py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) uint64_t uticks = mach_absolute_time(); // unsigned => signed - assert(uticks <= (uint64_t)_PyTime_MAX); + assert(uticks <= (uint64_t)PyTime_MAX); _PyTime_t ticks = (_PyTime_t)uticks; _PyTime_t ns = _PyTimeFraction_Mul(ticks, &base); @@ -1229,6 +1236,17 @@ _PyTime_GetMonotonicClock(void) } +int +PyTime_Monotonic(PyTime_t *result) +{ + if (py_get_monotonic_clock(result, NULL, 1) < 0) { + *result = 0; + return -1; + } + return 0; +} + + int _PyTime_GetMonotonicClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) { @@ -1268,6 +1286,7 @@ py_win_perf_counter_frequency(_PyTimeFraction *base, int raise) } +// N.B. If raise_exc=0, this may be called without the GIL. static int py_get_win_perf_counter(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) { @@ -1335,6 +1354,25 @@ _PyTime_GetPerfCounter(void) } +int +PyTime_PerfCounter(PyTime_t *result) +{ + int res; +#ifdef MS_WINDOWS + res = py_get_win_perf_counter(result, NULL, 1); +#else + res = py_get_monotonic_clock(result, NULL, 1); +#endif + if (res < 0) { + // If py_win_perf_counter_frequency() or py_get_monotonic_clock() + // fails: silently ignore the failure and return 0. + *result = 0; + return -1; + } + return 0; +} + + int _PyTime_localtime(time_t t, struct tm *tm) { From de7d67b19b9f31d7712de7211ffac5bf6018157f Mon Sep 17 00:00:00 2001 From: mpage <mpage@meta.com> Date: Mon, 12 Feb 2024 09:44:00 -0800 Subject: [PATCH 232/507] gh-114271: Make `PyInterpreterState.threads.count` thread-safe in free-threaded builds (gh-115093) Use atomics to mutate PyInterpreterState.threads.count. --- Include/internal/pycore_interp.h | 2 +- Modules/_threadmodule.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 31d88071e19d0cf..485b1914a448854 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -112,7 +112,7 @@ struct _is { /* The thread currently executing in the __main__ module, if any. */ PyThreadState *main; /* Used in Modules/_threadmodule.c. */ - long count; + Py_ssize_t count; /* Support for runtime thread stack size tuning. A value of 0 means using the platform's default stack size or the size specified by the THREAD_STACK_SIZE macro. */ diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index df02b023012fbde..d7840eaf45e8d69 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1244,7 +1244,7 @@ thread_run(void *boot_raw) _PyThreadState_Bind(tstate); PyEval_AcquireThread(tstate); - tstate->interp->threads.count++; + _Py_atomic_add_ssize(&tstate->interp->threads.count, 1); PyObject *res = PyObject_Call(boot->func, boot->args, boot->kwargs); if (res == NULL) { @@ -1262,7 +1262,7 @@ thread_run(void *boot_raw) thread_bootstate_free(boot, 1); - tstate->interp->threads.count--; + _Py_atomic_add_ssize(&tstate->interp->threads.count, -1); PyThreadState_Clear(tstate); _PyThreadState_DeleteCurrent(tstate); @@ -1539,7 +1539,7 @@ static PyObject * thread__count(PyObject *self, PyObject *Py_UNUSED(ignored)) { PyInterpreterState *interp = _PyInterpreterState_GET(); - return PyLong_FromLong(interp->threads.count); + return PyLong_FromSsize_t(_Py_atomic_load_ssize(&interp->threads.count)); } PyDoc_STRVAR(_count_doc, From 4297d7301b97aba2e0df9f9cc5fa4010e53a8950 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Mon, 12 Feb 2024 21:31:07 +0300 Subject: [PATCH 233/507] gh-115285: Fix `test_dataclasses` with `-OO` mode (#115286) --- Lib/test/test_dataclasses/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index 272d427875ae407..ede74b0dd15ccfc 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -22,6 +22,8 @@ import typing # Needed for the string "typing.ClassVar[int]" to work as an annotation. import dataclasses # Needed for the string "dataclasses.InitVar[int]" to work as an annotation. +from test import support + # Just any custom exception we can catch. class CustomError(Exception): pass @@ -2216,6 +2218,7 @@ def assertDocStrEqual(self, a, b): # whitespace stripped. self.assertEqual(a.replace(' ', ''), b.replace(' ', '')) + @support.requires_docstrings def test_existing_docstring_not_overridden(self): @dataclass class C: From a82fbc13d0e352b9af7d7ffbef4bc04cf635f07f Mon Sep 17 00:00:00 2001 From: Ezio Melotti <ezio.melotti@gmail.com> Date: Mon, 12 Feb 2024 20:23:45 +0100 Subject: [PATCH 234/507] Remove stray backtick in NEWS entry (#115356) --- .../next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst b/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst index 73d3d001f07f3f4..3e6eef183ad5249 100644 --- a/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst +++ b/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst @@ -1,4 +1,4 @@ Most exceptions are now ignored when attempting to set the ``__orig_class__`` attribute on objects returned when calling :mod:`typing` generic aliases (including generic aliases created using :data:`typing.Annotated`). -Previously only :exc:`AttributeError`` was ignored. Patch by Dave Shawley. +Previously only :exc:`AttributeError` was ignored. Patch by Dave Shawley. From 7861dfd26a41e40c2b4361eb0bb1356b9b4a064b Mon Sep 17 00:00:00 2001 From: Steve Dower <steve.dower@python.org> Date: Mon, 12 Feb 2024 20:13:13 +0000 Subject: [PATCH 235/507] gh-111140: Adds PyLong_AsNativeBytes and PyLong_FromNative[Unsigned]Bytes functions (GH-114886) --- Doc/c-api/long.rst | 66 ++++++ Doc/whatsnew/3.13.rst | 7 +- Include/cpython/longobject.h | 36 ++- Lib/test/test_capi/test_long.py | 145 ++++++++++++ ...-02-05-17-11-15.gh-issue-111140.WMEjid.rst | 2 + Modules/_io/textio.c | 2 +- Modules/_pickle.c | 3 +- Modules/_randommodule.c | 3 +- Modules/_sqlite/util.c | 2 +- Modules/_struct.c | 20 +- Modules/_testcapi/long.c | 48 +++- Modules/_tkinter.c | 3 +- Modules/cjkcodecs/multibytecodec.c | 6 +- Objects/longobject.c | 216 +++++++++++++++++- 14 files changed, 533 insertions(+), 26 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2024-02-05-17-11-15.gh-issue-111140.WMEjid.rst diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index f42e23db89ae399..c39823e5e6787f0 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -113,6 +113,28 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. retrieved from the resulting value using :c:func:`PyLong_AsVoidPtr`. +.. c:function:: PyObject* PyLong_FromNativeBytes(const void* buffer, size_t n_bytes, int endianness) + + Create a Python integer from the value contained in the first *n_bytes* of + *buffer*, interpreted as a two's-complement signed number. + + *endianness* may be passed ``-1`` for the native endian that CPython was + compiled with, or else ``0`` for big endian and ``1`` for little. + + .. versionadded:: 3.13 + + +.. c:function:: PyObject* PyLong_FromUnsignedNativeBytes(const void* buffer, size_t n_bytes, int endianness) + + Create a Python integer from the value contained in the first *n_bytes* of + *buffer*, interpreted as an unsigned number. + + *endianness* may be passed ``-1`` for the native endian that CPython was + compiled with, or else ``0`` for big endian and ``1`` for little. + + .. versionadded:: 3.13 + + .. XXX alias PyLong_AS_LONG (for now) .. c:function:: long PyLong_AsLong(PyObject *obj) @@ -332,6 +354,50 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. Returns ``NULL`` on error. Use :c:func:`PyErr_Occurred` to disambiguate. +.. c:function:: Py_ssize_t PyLong_AsNativeBytes(PyObject *pylong, void* buffer, Py_ssize_t n_bytes, int endianness) + + Copy the Python integer value to a native *buffer* of size *n_bytes*:: + + int value; + Py_ssize_t bytes = PyLong_CopyBits(v, &value, sizeof(value), -1); + if (bytes < 0) { + // Error occurred + return NULL; + } + else if (bytes > sizeof(value)) { + // Overflow occurred, but 'value' contains as much as could fit + } + + *endianness* may be passed ``-1`` for the native endian that CPython was + compiled with, or ``0`` for big endian and ``1`` for little. + + Return ``-1`` with an exception raised if *pylong* cannot be interpreted as + an integer. Otherwise, return the size of the buffer required to store the + value. If this is equal to or less than *n_bytes*, the entire value was + copied. + + Unless an exception is raised, all *n_bytes* of the buffer will be written + with as much of the value as can fit. This allows the caller to ignore all + non-negative results if the intent is to match the typical behavior of a + C-style downcast. + + Values are always copied as twos-complement, and sufficient size will be + requested for a sign bit. For example, this may cause an value that fits into + 8 bytes when treated as unsigned to request 9 bytes, even though all eight + bytes were copied into the buffer. What has been omitted is the zero sign + bit, which is redundant when the intention is to treat the value as unsigned. + + Passing *n_bytes* of zero will always return the requested buffer size. + + .. note:: + + When the value does not fit in the provided buffer, the requested size + returned from the function may be larger than necessary. Passing 0 to this + function is not an accurate way to determine the bit length of a value. + + .. versionadded:: 3.13 + + .. c:function:: int PyUnstable_Long_IsCompact(const PyLongObject* op) Return 1 if *op* is compact, 0 otherwise. diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 191657061f74033..b96720df0a2f2d1 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -587,6 +587,7 @@ Tier 2 IR by Mark Shannon and Guido van Rossum. Tier 2 optimizer by Ken Jin.) + Deprecated ========== @@ -1526,6 +1527,11 @@ New Features (Contributed by Victor Stinner and Petr Viktorin in :gh:`110850`.) +* Add :c:func:`PyLong_AsNativeBytes`, :c:func:`PyLong_FromNativeBytes` and + :c:func:`PyLong_FromUnsignedNativeBytes` functions to simplify converting + between native integer types and Python :class:`int` objects. + (Contributed by Steve Dower in :gh:`111140`.) + Porting to Python 3.13 ---------------------- @@ -1585,7 +1591,6 @@ Porting to Python 3.13 platforms, the ``HAVE_STDDEF_H`` macro is only defined on Windows. (Contributed by Victor Stinner in :gh:`108765`.) - Deprecated ---------- diff --git a/Include/cpython/longobject.h b/Include/cpython/longobject.h index fd1be29ed397d18..07251db6bcc203c 100644 --- a/Include/cpython/longobject.h +++ b/Include/cpython/longobject.h @@ -4,6 +4,40 @@ PyAPI_FUNC(PyObject*) PyLong_FromUnicodeObject(PyObject *u, int base); +/* PyLong_AsNativeBytes: Copy the integer value to a native variable. + buffer points to the first byte of the variable. + n_bytes is the number of bytes available in the buffer. Pass 0 to request + the required size for the value. + endianness is -1 for native endian, 0 for big endian or 1 for little. + Big endian mode will write the most significant byte into the address + directly referenced by buffer; little endian will write the least significant + byte into that address. + + If an exception is raised, returns a negative value. + Otherwise, returns the number of bytes that are required to store the value. + To check that the full value is represented, ensure that the return value is + equal or less than n_bytes. + All n_bytes are guaranteed to be written (unless an exception occurs), and + so ignoring a positive return value is the equivalent of a downcast in C. + In cases where the full value could not be represented, the returned value + may be larger than necessary - this function is not an accurate way to + calculate the bit length of an integer object. + */ +PyAPI_FUNC(Py_ssize_t) PyLong_AsNativeBytes(PyObject* v, void* buffer, + Py_ssize_t n_bytes, int endianness); + +/* PyLong_FromNativeBytes: Create an int value from a native integer + n_bytes is the number of bytes to read from the buffer. Passing 0 will + always produce the zero int. + PyLong_FromUnsignedNativeBytes always produces a non-negative int. + endianness is -1 for native endian, 0 for big endian or 1 for little. + + Returns the int object, or NULL with an exception set. */ +PyAPI_FUNC(PyObject*) PyLong_FromNativeBytes(const void* buffer, size_t n_bytes, + int endianness); +PyAPI_FUNC(PyObject*) PyLong_FromUnsignedNativeBytes(const void* buffer, + size_t n_bytes, int endianness); + PyAPI_FUNC(int) PyUnstable_Long_IsCompact(const PyLongObject* op); PyAPI_FUNC(Py_ssize_t) PyUnstable_Long_CompactValue(const PyLongObject* op); @@ -50,7 +84,7 @@ PyAPI_FUNC(PyObject *) _PyLong_FromByteArray( */ PyAPI_FUNC(int) _PyLong_AsByteArray(PyLongObject* v, unsigned char* bytes, size_t n, - int little_endian, int is_signed); + int little_endian, int is_signed, int with_exceptions); /* For use by the gcd function in mathmodule.c */ PyAPI_FUNC(PyObject *) _PyLong_GCD(PyObject *, PyObject *); diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index 8e3ef25d1ff86fc..fc82cbfa66ea7aa 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -1,5 +1,6 @@ import unittest import sys +import test.support as support from test.support import import_helper @@ -423,6 +424,150 @@ def test_long_asvoidptr(self): self.assertRaises(OverflowError, asvoidptr, -2**1000) # CRASHES asvoidptr(NULL) + def test_long_asnativebytes(self): + import math + from _testcapi import ( + pylong_asnativebytes as asnativebytes, + SIZE_MAX, + ) + + # Abbreviate sizeof(Py_ssize_t) to SZ because we use it a lot + SZ = int(math.ceil(math.log(SIZE_MAX + 1) / math.log(2)) / 8) + MAX_SSIZE = 2 ** (SZ * 8 - 1) - 1 + MAX_USIZE = 2 ** (SZ * 8) - 1 + if support.verbose: + print(f"SIZEOF_SIZE={SZ}\n{MAX_SSIZE=:016X}\n{MAX_USIZE=:016X}") + + # These tests check that the requested buffer size is correct + for v, expect in [ + (0, SZ), + (512, SZ), + (-512, SZ), + (MAX_SSIZE, SZ), + (MAX_USIZE, SZ + 1), + (-MAX_SSIZE, SZ), + (-MAX_USIZE, SZ + 1), + (2**255-1, 32), + (-(2**255-1), 32), + (2**256-1, 33), + (-(2**256-1), 33), + ]: + with self.subTest(f"sizeof-{v:X}"): + buffer = bytearray(1) + self.assertEqual(expect, asnativebytes(v, buffer, 0, -1), + "PyLong_AsNativeBytes(v, NULL, 0, -1)") + # Also check via the __index__ path + self.assertEqual(expect, asnativebytes(Index(v), buffer, 0, -1), + "PyLong_AsNativeBytes(Index(v), NULL, 0, -1)") + + # We request as many bytes as `expect_be` contains, and always check + # the result (both big and little endian). We check the return value + # independently, since the buffer should always be filled correctly even + # if we need more bytes + for v, expect_be, expect_n in [ + (0, b'\x00', 1), + (0, b'\x00' * 2, 2), + (0, b'\x00' * 8, min(8, SZ)), + (1, b'\x01', 1), + (1, b'\x00' * 10 + b'\x01', min(11, SZ)), + (42, b'\x2a', 1), + (42, b'\x00' * 10 + b'\x2a', min(11, SZ)), + (-1, b'\xff', 1), + (-1, b'\xff' * 10, min(11, SZ)), + (-42, b'\xd6', 1), + (-42, b'\xff' * 10 + b'\xd6', min(11, SZ)), + # Extracts 255 into a single byte, but requests sizeof(Py_ssize_t) + (255, b'\xff', SZ), + (255, b'\x00\xff', 2), + (256, b'\x01\x00', 2), + # Extracts successfully (unsigned), but requests 9 bytes + (2**63, b'\x80' + b'\x00' * 7, 9), + # "Extracts", but requests 9 bytes + (-2**63, b'\x80' + b'\x00' * 7, 9), + (2**63, b'\x00\x80' + b'\x00' * 7, 9), + (-2**63, b'\xff\x80' + b'\x00' * 7, 9), + + (2**255-1, b'\x7f' + b'\xff' * 31, 32), + (-(2**255-1), b'\x80' + b'\x00' * 30 + b'\x01', 32), + # Request extra bytes, but result says we only needed 32 + (-(2**255-1), b'\xff\x80' + b'\x00' * 30 + b'\x01', 32), + (-(2**255-1), b'\xff\xff\x80' + b'\x00' * 30 + b'\x01', 32), + + # Extracting 256 bits of integer will request 33 bytes, but still + # copy as many bits as possible into the buffer. So we *can* copy + # into a 32-byte buffer, though negative number may be unrecoverable + (2**256-1, b'\xff' * 32, 33), + (2**256-1, b'\x00' + b'\xff' * 32, 33), + (-(2**256-1), b'\x00' * 31 + b'\x01', 33), + (-(2**256-1), b'\xff' + b'\x00' * 31 + b'\x01', 33), + (-(2**256-1), b'\xff\xff' + b'\x00' * 31 + b'\x01', 33), + + # The classic "Windows HRESULT as negative number" case + # HRESULT hr; + # PyLong_CopyBits(<-2147467259>, &hr, sizeof(HRESULT)) + # assert(hr == E_FAIL) + (-2147467259, b'\x80\x00\x40\x05', 4), + ]: + with self.subTest(f"{v:X}-{len(expect_be)}bytes"): + n = len(expect_be) + buffer = bytearray(n) + expect_le = expect_be[::-1] + + self.assertEqual(expect_n, asnativebytes(v, buffer, n, 0), + f"PyLong_AsNativeBytes(v, buffer, {n}, <big>)") + self.assertEqual(expect_be, buffer[:n], "<big>") + self.assertEqual(expect_n, asnativebytes(v, buffer, n, 1), + f"PyLong_AsNativeBytes(v, buffer, {n}, <little>)") + self.assertEqual(expect_le, buffer[:n], "<little>") + + # Check a few error conditions. These are validated in code, but are + # unspecified in docs, so if we make changes to the implementation, it's + # fine to just update these tests rather than preserve the behaviour. + with self.assertRaises(SystemError): + asnativebytes(1, buffer, 0, 2) + with self.assertRaises(TypeError): + asnativebytes('not a number', buffer, 0, -1) + + def test_long_fromnativebytes(self): + import math + from _testcapi import ( + pylong_fromnativebytes as fromnativebytes, + SIZE_MAX, + ) + + # Abbreviate sizeof(Py_ssize_t) to SZ because we use it a lot + SZ = int(math.ceil(math.log(SIZE_MAX + 1) / math.log(2)) / 8) + MAX_SSIZE = 2 ** (SZ * 8 - 1) - 1 + MAX_USIZE = 2 ** (SZ * 8) - 1 + + for v_be, expect_s, expect_u in [ + (b'\x00', 0, 0), + (b'\x01', 1, 1), + (b'\xff', -1, 255), + (b'\x00\xff', 255, 255), + (b'\xff\xff', -1, 65535), + ]: + with self.subTest(f"{expect_s}-{expect_u:X}-{len(v_be)}bytes"): + n = len(v_be) + v_le = v_be[::-1] + + self.assertEqual(expect_s, fromnativebytes(v_be, n, 0, 1), + f"PyLong_FromNativeBytes(buffer, {n}, <big>)") + self.assertEqual(expect_s, fromnativebytes(v_le, n, 1, 1), + f"PyLong_FromNativeBytes(buffer, {n}, <little>)") + self.assertEqual(expect_u, fromnativebytes(v_be, n, 0, 0), + f"PyLong_FromUnsignedNativeBytes(buffer, {n}, <big>)") + self.assertEqual(expect_u, fromnativebytes(v_le, n, 1, 0), + f"PyLong_FromUnsignedNativeBytes(buffer, {n}, <little>)") + + # Check native endian when the result would be the same either + # way and we can test it. + if v_be == v_le: + self.assertEqual(expect_s, fromnativebytes(v_be, n, -1, 1), + f"PyLong_FromNativeBytes(buffer, {n}, <native>)") + self.assertEqual(expect_u, fromnativebytes(v_be, n, -1, 0), + f"PyLong_FromUnsignedNativeBytes(buffer, {n}, <native>)") + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/C API/2024-02-05-17-11-15.gh-issue-111140.WMEjid.rst b/Misc/NEWS.d/next/C API/2024-02-05-17-11-15.gh-issue-111140.WMEjid.rst new file mode 100644 index 000000000000000..a8aa191b5eb3bac --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-02-05-17-11-15.gh-issue-111140.WMEjid.rst @@ -0,0 +1,2 @@ +Adds :c:func:`PyLong_AsNativeBytes`, :c:func:`PyLong_FromNativeBytes` and +:c:func:`PyLong_FromUnsignedNativeBytes` functions. diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index d794af8de2b8f02..a3239ec0f529609 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -2393,7 +2393,7 @@ textiowrapper_parse_cookie(cookie_type *cookie, PyObject *cookieObj) return -1; if (_PyLong_AsByteArray(cookieLong, buffer, sizeof(buffer), - PY_LITTLE_ENDIAN, 0) < 0) { + PY_LITTLE_ENDIAN, 0, 1) < 0) { Py_DECREF(cookieLong); return -1; } diff --git a/Modules/_pickle.c b/Modules/_pickle.c index f210c0ca2059911..0d83261168185d9 100644 --- a/Modules/_pickle.c +++ b/Modules/_pickle.c @@ -2162,7 +2162,8 @@ save_long(PicklerObject *self, PyObject *obj) pdata = (unsigned char *)PyBytes_AS_STRING(repr); i = _PyLong_AsByteArray((PyLongObject *)obj, pdata, nbytes, - 1 /* little endian */ , 1 /* signed */ ); + 1 /* little endian */ , 1 /* signed */ , + 1 /* with exceptions */); if (i < 0) goto error; /* If the int is negative, this may be a byte more than diff --git a/Modules/_randommodule.c b/Modules/_randommodule.c index 5481ed9b348ed77..4463157d62248dd 100644 --- a/Modules/_randommodule.c +++ b/Modules/_randommodule.c @@ -342,7 +342,8 @@ random_seed(RandomObject *self, PyObject *arg) res = _PyLong_AsByteArray((PyLongObject *)n, (unsigned char *)key, keyused * 4, PY_LITTLE_ENDIAN, - 0); /* unsigned */ + 0, /* unsigned */ + 1); /* with exceptions */ if (res == -1) { goto Done; } diff --git a/Modules/_sqlite/util.c b/Modules/_sqlite/util.c index 833a666301d8ff9..9e8613ef67916e9 100644 --- a/Modules/_sqlite/util.c +++ b/Modules/_sqlite/util.c @@ -162,7 +162,7 @@ _pysqlite_long_as_int64(PyObject * py_val) sqlite_int64 int64val; if (_PyLong_AsByteArray((PyLongObject *)py_val, (unsigned char *)&int64val, sizeof(int64val), - IS_LITTLE_ENDIAN, 1 /* signed */) >= 0) { + IS_LITTLE_ENDIAN, 1 /* signed */, 0) >= 0) { return int64val; } } diff --git a/Modules/_struct.c b/Modules/_struct.c index bd16fa89f189455..fa2cd37e003e0a2 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -1000,9 +1000,10 @@ bp_longlong(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) (unsigned char *)p, 8, 0, /* little_endian */ - 1 /* signed */); + 1, /* signed */ + 0 /* !with_exceptions */); Py_DECREF(v); - if (res == -1 && PyErr_Occurred()) { + if (res < 0) { PyErr_Format(state->StructError, "'%c' format requires %lld <= number <= %lld", f->format, @@ -1024,9 +1025,10 @@ bp_ulonglong(_structmodulestate *state, char *p, PyObject *v, const formatdef *f (unsigned char *)p, 8, 0, /* little_endian */ - 0 /* signed */); + 0, /* signed */ + 0 /* !with_exceptions */); Py_DECREF(v); - if (res == -1 && PyErr_Occurred()) { + if (res < 0) { PyErr_Format(state->StructError, "'%c' format requires 0 <= number <= %llu", f->format, @@ -1260,9 +1262,10 @@ lp_longlong(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) (unsigned char *)p, 8, 1, /* little_endian */ - 1 /* signed */); + 1, /* signed */ + 0 /* !with_exceptions */); Py_DECREF(v); - if (res == -1 && PyErr_Occurred()) { + if (res < 0) { PyErr_Format(state->StructError, "'%c' format requires %lld <= number <= %lld", f->format, @@ -1284,9 +1287,10 @@ lp_ulonglong(_structmodulestate *state, char *p, PyObject *v, const formatdef *f (unsigned char *)p, 8, 1, /* little_endian */ - 0 /* signed */); + 0, /* signed */ + 0 /* !with_exceptions */); Py_DECREF(v); - if (res == -1 && PyErr_Occurred()) { + if (res < 0) { PyErr_Format(state->StructError, "'%c' format requires 0 <= number <= %llu", f->format, diff --git a/Modules/_testcapi/long.c b/Modules/_testcapi/long.c index 32ad8d32ab85239..dc21cf9f4752289 100644 --- a/Modules/_testcapi/long.c +++ b/Modules/_testcapi/long.c @@ -776,6 +776,51 @@ pylong_asvoidptr(PyObject *module, PyObject *arg) return Py_NewRef((PyObject *)value); } +static PyObject * +pylong_asnativebytes(PyObject *module, PyObject *args) +{ + PyObject *v; + Py_buffer buffer; + Py_ssize_t n, endianness; + if (!PyArg_ParseTuple(args, "Ow*nn", &v, &buffer, &n, &endianness)) { + return NULL; + } + if (buffer.readonly) { + PyErr_SetString(PyExc_TypeError, "buffer must be writable"); + PyBuffer_Release(&buffer); + return NULL; + } + if (buffer.len < n) { + PyErr_SetString(PyExc_ValueError, "buffer must be at least 'n' bytes"); + PyBuffer_Release(&buffer); + return NULL; + } + Py_ssize_t res = PyLong_AsNativeBytes(v, buffer.buf, n, (int)endianness); + PyBuffer_Release(&buffer); + return res >= 0 ? PyLong_FromSsize_t(res) : NULL; +} + +static PyObject * +pylong_fromnativebytes(PyObject *module, PyObject *args) +{ + Py_buffer buffer; + Py_ssize_t n, endianness, signed_; + if (!PyArg_ParseTuple(args, "y*nnn", &buffer, &n, &endianness, &signed_)) { + return NULL; + } + if (buffer.len < n) { + PyErr_SetString(PyExc_ValueError, "buffer must be at least 'n' bytes"); + PyBuffer_Release(&buffer); + return NULL; + } + PyObject *res = signed_ + ? PyLong_FromNativeBytes(buffer.buf, n, (int)endianness) + : PyLong_FromUnsignedNativeBytes(buffer.buf, n, (int)endianness); + PyBuffer_Release(&buffer); + return res; +} + + static PyMethodDef test_methods[] = { _TESTCAPI_TEST_LONG_AND_OVERFLOW_METHODDEF _TESTCAPI_TEST_LONG_API_METHODDEF @@ -804,6 +849,8 @@ static PyMethodDef test_methods[] = { {"pylong_as_size_t", pylong_as_size_t, METH_O}, {"pylong_asdouble", pylong_asdouble, METH_O}, {"pylong_asvoidptr", pylong_asvoidptr, METH_O}, + {"pylong_asnativebytes", pylong_asnativebytes, METH_VARARGS}, + {"pylong_fromnativebytes", pylong_fromnativebytes, METH_VARARGS}, {NULL}, }; @@ -813,6 +860,5 @@ _PyTestCapi_Init_Long(PyObject *mod) if (PyModule_AddFunctions(mod, test_methods) < 0) { return -1; } - return 0; } diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index f6181168a85ae15..e3789867dc085fd 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -926,7 +926,8 @@ AsObj(PyObject *value) (unsigned char *)(void *)&wideValue, sizeof(wideValue), PY_LITTLE_ENDIAN, - /* signed */ 1) == 0) { + /* signed */ 1, + /* with_exceptions */ 1) == 0) { return Tcl_NewWideIntObj(wideValue); } PyErr_Clear(); diff --git a/Modules/cjkcodecs/multibytecodec.c b/Modules/cjkcodecs/multibytecodec.c index 5d3c16a98423baa..2125da437963d2d 100644 --- a/Modules/cjkcodecs/multibytecodec.c +++ b/Modules/cjkcodecs/multibytecodec.c @@ -973,7 +973,8 @@ _multibytecodec_MultibyteIncrementalEncoder_setstate_impl(MultibyteIncrementalEn if (_PyLong_AsByteArray(statelong, statebytes, sizeof(statebytes), 1 /* little-endian */ , - 0 /* unsigned */ ) < 0) { + 0 /* unsigned */ , + 1 /* with_exceptions */) < 0) { goto errorexit; } @@ -1255,7 +1256,8 @@ _multibytecodec_MultibyteIncrementalDecoder_setstate_impl(MultibyteIncrementalDe if (_PyLong_AsByteArray(statelong, statebytes, sizeof(statebytes), 1 /* little-endian */ , - 0 /* unsigned */ ) < 0) { + 0 /* unsigned */ , + 1 /* with_exceptions */) < 0) { return NULL; } diff --git a/Objects/longobject.c b/Objects/longobject.c index e655ba19e8f1c12..932111f58425f22 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -928,7 +928,8 @@ _PyLong_FromByteArray(const unsigned char* bytes, size_t n, int _PyLong_AsByteArray(PyLongObject* v, unsigned char* bytes, size_t n, - int little_endian, int is_signed) + int little_endian, int is_signed, + int with_exceptions) { Py_ssize_t i; /* index into v->long_value.ob_digit */ Py_ssize_t ndigits; /* number of digits */ @@ -945,8 +946,10 @@ _PyLong_AsByteArray(PyLongObject* v, ndigits = _PyLong_DigitCount(v); if (_PyLong_IsNegative(v)) { if (!is_signed) { - PyErr_SetString(PyExc_OverflowError, - "can't convert negative int to unsigned"); + if (with_exceptions) { + PyErr_SetString(PyExc_OverflowError, + "can't convert negative int to unsigned"); + } return -1; } do_twos_comp = 1; @@ -967,7 +970,12 @@ _PyLong_AsByteArray(PyLongObject* v, /* Copy over all the Python digits. It's crucial that every Python digit except for the MSD contribute exactly PyLong_SHIFT bits to the total, so first assert that the int is - normalized. */ + normalized. + NOTE: PyLong_AsNativeBytes() assumes that this function will fill in 'n' + bytes even if it eventually fails to convert the whole number. Make sure + you account for that if you are changing this algorithm to return without + doing that. + */ assert(ndigits == 0 || v->long_value.ob_digit[ndigits - 1] != 0); j = 0; accum = 0; @@ -1052,11 +1060,203 @@ _PyLong_AsByteArray(PyLongObject* v, return 0; Overflow: - PyErr_SetString(PyExc_OverflowError, "int too big to convert"); + if (with_exceptions) { + PyErr_SetString(PyExc_OverflowError, "int too big to convert"); + } return -1; } +// Refactored out for readability, not reuse +static inline int +_fits_in_n_bits(Py_ssize_t v, Py_ssize_t n) +{ + if (n >= (Py_ssize_t)sizeof(Py_ssize_t) * 8) { + return 1; + } + // If all bits above n are the same, we fit. + // (Use n-1 if we require the sign bit to be consistent.) + Py_ssize_t v_extended = v >> ((int)n - 1); + return v_extended == 0 || v_extended == -1; +} + +static inline int +_resolve_endianness(int *endianness) +{ + if (*endianness < 0) { + *endianness = PY_LITTLE_ENDIAN; + } + if (*endianness != 0 && *endianness != 1) { + PyErr_SetString(PyExc_SystemError, "invalid 'endianness' value"); + return -1; + } + return 0; +} + +Py_ssize_t +PyLong_AsNativeBytes(PyObject* vv, void* buffer, Py_ssize_t n, int endianness) +{ + PyLongObject *v; + union { + Py_ssize_t v; + unsigned char b[sizeof(Py_ssize_t)]; + } cv; + int do_decref = 0; + Py_ssize_t res = 0; + + if (vv == NULL || n < 0) { + PyErr_BadInternalCall(); + return -1; + } + + int little_endian = endianness; + if (_resolve_endianness(&little_endian) < 0) { + return -1; + } + + if (PyLong_Check(vv)) { + v = (PyLongObject *)vv; + } + else { + v = (PyLongObject *)_PyNumber_Index(vv); + if (v == NULL) { + return -1; + } + do_decref = 1; + } + + if (_PyLong_IsCompact(v)) { + res = 0; + cv.v = _PyLong_CompactValue(v); + /* Most paths result in res = sizeof(compact value). Only the case + * where 0 < n < sizeof(compact value) do we need to check and adjust + * our return value. */ + res = sizeof(cv.b); + if (n <= 0) { + // nothing to do! + } + else if (n <= sizeof(cv.b)) { +#if PY_LITTLE_ENDIAN + if (little_endian) { + memcpy(buffer, cv.b, n); + } + else { + for (Py_ssize_t i = 0; i < n; ++i) { + ((unsigned char*)buffer)[n - i - 1] = cv.b[i]; + } + } +#else + if (little_endian) { + for (Py_ssize_t i = 0; i < n; ++i) { + ((unsigned char*)buffer)[i] = cv.b[sizeof(cv.b) - i - 1]; + } + } + else { + memcpy(buffer, &cv.b[sizeof(cv.b) - n], n); + } +#endif + + /* If we fit, return the requested number of bytes */ + if (_fits_in_n_bits(cv.v, n * 8)) { + res = n; + } + } + else { + unsigned char fill = cv.v < 0 ? 0xFF : 0x00; +#if PY_LITTLE_ENDIAN + if (little_endian) { + memcpy(buffer, cv.b, sizeof(cv.b)); + memset((char *)buffer + sizeof(cv.b), fill, n - sizeof(cv.b)); + } + else { + unsigned char *b = (unsigned char *)buffer; + for (Py_ssize_t i = 0; i < n - (int)sizeof(cv.b); ++i) { + *b++ = fill; + } + for (Py_ssize_t i = sizeof(cv.b); i > 0; --i) { + *b++ = cv.b[i - 1]; + } + } +#else + if (little_endian) { + unsigned char *b = (unsigned char *)buffer; + for (Py_ssize_t i = sizeof(cv.b); i > 0; --i) { + *b++ = cv.b[i - 1]; + } + for (Py_ssize_t i = 0; i < n - sizeof(cv.b); ++i) { + *b++ = fill; + } + } + else { + memset(buffer, fill, n - sizeof(cv.b)); + memcpy((char *)buffer + n - sizeof(cv.b), cv.b, sizeof(cv.b)); + } +#endif + } + } + else { + if (n > 0) { + _PyLong_AsByteArray(v, buffer, (size_t)n, little_endian, 1, 0); + } + + // More efficient calculation for number of bytes required? + size_t nb = _PyLong_NumBits((PyObject *)v); + /* Normally this would be((nb - 1) / 8) + 1 to avoid rounding up + * multiples of 8 to the next byte, but we add an implied bit for + * the sign and it cancels out. */ + size_t n_needed = (nb / 8) + 1; + res = (Py_ssize_t)n_needed; + if ((size_t)res != n_needed) { + PyErr_SetString(PyExc_OverflowError, + "value too large to convert"); + res = -1; + } + } + + if (do_decref) { + Py_DECREF(v); + } + + return res; +} + + +PyObject * +PyLong_FromNativeBytes(const void* buffer, size_t n, int endianness) +{ + if (!buffer) { + PyErr_BadInternalCall(); + return NULL; + } + + int little_endian = endianness; + if (_resolve_endianness(&little_endian) < 0) { + return NULL; + } + + return _PyLong_FromByteArray((const unsigned char *)buffer, n, + little_endian, 1); +} + + +PyObject * +PyLong_FromUnsignedNativeBytes(const void* buffer, size_t n, int endianness) +{ + if (!buffer) { + PyErr_BadInternalCall(); + return NULL; + } + + int little_endian = endianness; + if (_resolve_endianness(&little_endian) < 0) { + return NULL; + } + + return _PyLong_FromByteArray((const unsigned char *)buffer, n, + little_endian, 0); +} + + /* Create a new int object from a C pointer */ PyObject * @@ -1231,7 +1431,7 @@ PyLong_AsLongLong(PyObject *vv) } else { res = _PyLong_AsByteArray((PyLongObject *)v, (unsigned char *)&bytes, - SIZEOF_LONG_LONG, PY_LITTLE_ENDIAN, 1); + SIZEOF_LONG_LONG, PY_LITTLE_ENDIAN, 1, 1); } if (do_decref) { Py_DECREF(v); @@ -1270,7 +1470,7 @@ PyLong_AsUnsignedLongLong(PyObject *vv) } else { res = _PyLong_AsByteArray((PyLongObject *)vv, (unsigned char *)&bytes, - SIZEOF_LONG_LONG, PY_LITTLE_ENDIAN, 0); + SIZEOF_LONG_LONG, PY_LITTLE_ENDIAN, 0, 1); } /* Plan 9 can't handle long long in ? : expressions */ @@ -6068,7 +6268,7 @@ int_to_bytes_impl(PyObject *self, Py_ssize_t length, PyObject *byteorder, if (_PyLong_AsByteArray((PyLongObject *)self, (unsigned char *)PyBytes_AS_STRING(bytes), - length, little_endian, is_signed) < 0) { + length, little_endian, is_signed, 1) < 0) { Py_DECREF(bytes); return NULL; } From bee2a11946a8d6df6b6c384abccf3dfb4e75d3fc Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Mon, 12 Feb 2024 23:52:25 +0300 Subject: [PATCH 236/507] gh-115258: Temporarily skip some `queue` tests on all platforms (#115361) --- Lib/test/test_queue.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_queue.py b/Lib/test/test_queue.py index d308a212999429e..92d670ca6f8f5bd 100644 --- a/Lib/test/test_queue.py +++ b/Lib/test/test_queue.py @@ -403,11 +403,11 @@ def _shutdown_all_methods_in_many_threads(self, immediate): for thread in ps[1:]: thread.join() - @unittest.skipIf(sys.platform == "win32", "test times out (gh-115258)") + @unittest.skip("test times out (gh-115258)") def test_shutdown_all_methods_in_many_threads(self): return self._shutdown_all_methods_in_many_threads(False) - @unittest.skipIf(sys.platform == "win32", "test times out (gh-115258)") + @unittest.skip("test times out (gh-115258)") def test_shutdown_immediate_all_methods_in_many_threads(self): return self._shutdown_all_methods_in_many_threads(True) From 341d7874f063dcb141672b09f62c19ffedd0a557 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 13 Feb 2024 00:17:33 +0200 Subject: [PATCH 237/507] gh-115317: Rewrite changelog filter to use vanilla JavaScript (#115324) Co-authored-by: Tomas R <tomas.roun8@gmail.com> --- .editorconfig | 4 +- Doc/tools/static/changelog_search.js | 102 ++++++++++++++------------- 2 files changed, 56 insertions(+), 50 deletions(-) diff --git a/.editorconfig b/.editorconfig index 0169eed951cd3f1..a6187d64f3ce46e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,6 +1,6 @@ root = true -[*.{py,c,cpp,h,rst,md,yml}] +[*.{py,c,cpp,h,js,rst,md,yml}] trim_trailing_whitespace = true insert_final_newline = true indent_style = space @@ -11,5 +11,5 @@ indent_size = 4 [*.rst] indent_size = 3 -[*.yml] +[*.{js,yml}] indent_size = 2 diff --git a/Doc/tools/static/changelog_search.js b/Doc/tools/static/changelog_search.js index c881a9bd4c84a76..0a77c0d71ae9375 100644 --- a/Doc/tools/static/changelog_search.js +++ b/Doc/tools/static/changelog_search.js @@ -1,53 +1,59 @@ -$(document).ready(function() { - // add the search form and bind the events - $('h1').after([ - '<p>Filter entries by content:', - '<input type="text" value="" id="searchbox" style="width: 50%">', - '<input type="submit" id="searchbox-submit" value="Filter"></p>' - ].join('\n')); +document.addEventListener("DOMContentLoaded", function () { + // add the search form and bind the events + document + .querySelector("h1") + .insertAdjacentHTML( + "afterend", + [ + "<p>Filter entries by content:", + '<input type="text" value="" id="searchbox" style="width: 50%">', + '<input type="submit" id="searchbox-submit" value="Filter"></p>', + ].join("\n"), + ); - function dofilter() { - try { - var query = new RegExp($('#searchbox').val(), 'i'); + function doFilter() { + let query; + try { + query = new RegExp(document.querySelector("#searchbox").value, "i"); + } catch (e) { + return; // not a valid regex (yet) + } + // find headers for the versions (What's new in Python X.Y.Z?) + const h2s = document.querySelectorAll("#changelog h2"); + for (const h2 of h2s) { + let sections_found = 0; + // find headers for the sections (Core, Library, etc.) + const h3s = h2.parentNode.querySelectorAll("h3"); + for (const h3 of h3s) { + let entries_found = 0; + // find all the entries + const lis = h3.parentNode.querySelectorAll("li"); + for (let li of lis) { + // check if the query matches the entry + if (query.test(li.textContent)) { + li.style.display = "block"; + entries_found++; + } else { + li.style.display = "none"; + } } - catch (e) { - return; // not a valid regex (yet) + // if there are entries, show the section, otherwise hide it + if (entries_found > 0) { + h3.parentNode.style.display = "block"; + sections_found++; + } else { + h3.parentNode.style.display = "none"; } - // find headers for the versions (What's new in Python X.Y.Z?) - $('#changelog h2').each(function(index1, h2) { - var h2_parent = $(h2).parent(); - var sections_found = 0; - // find headers for the sections (Core, Library, etc.) - h2_parent.find('h3').each(function(index2, h3) { - var h3_parent = $(h3).parent(); - var entries_found = 0; - // find all the entries - h3_parent.find('li').each(function(index3, li) { - var li = $(li); - // check if the query matches the entry - if (query.test(li.text())) { - li.show(); - entries_found++; - } - else { - li.hide(); - } - }); - // if there are entries, show the section, otherwise hide it - if (entries_found > 0) { - h3_parent.show(); - sections_found++; - } - else { - h3_parent.hide(); - } - }); - if (sections_found > 0) - h2_parent.show(); - else - h2_parent.hide(); - }); + } + if (sections_found > 0) { + h2.parentNode.style.display = "block"; + } else { + h2.parentNode.style.display = "none"; + } } - $('#searchbox').keyup(dofilter); - $('#searchbox-submit').click(dofilter); + } + document.querySelector("#searchbox").addEventListener("keyup", doFilter); + document + .querySelector("#searchbox-submit") + .addEventListener("click", doFilter); }); From 10756b10ff8e47ece33f7fbf62c9a06f8a866fed Mon Sep 17 00:00:00 2001 From: Steve Dower <steve.dower@python.org> Date: Mon, 12 Feb 2024 22:28:36 +0000 Subject: [PATCH 238/507] gh-111140: Minor doc fixes for PyLong_AsNativeBytes (GH-115375) --- Doc/c-api/long.rst | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index c39823e5e6787f0..f24282e76a33d15 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -359,13 +359,16 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. Copy the Python integer value to a native *buffer* of size *n_bytes*:: int value; - Py_ssize_t bytes = PyLong_CopyBits(v, &value, sizeof(value), -1); + Py_ssize_t bytes = PyLong_AsNativeBytes(v, &value, sizeof(value), -1); if (bytes < 0) { // Error occurred return NULL; } - else if (bytes > sizeof(value)) { - // Overflow occurred, but 'value' contains as much as could fit + else if (bytes <= (Py_ssize_t)sizeof(value)) { + // Success! + } + else { + // Overflow occurred, but 'value' contains truncated value } *endianness* may be passed ``-1`` for the native endian that CPython was @@ -379,15 +382,16 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. Unless an exception is raised, all *n_bytes* of the buffer will be written with as much of the value as can fit. This allows the caller to ignore all non-negative results if the intent is to match the typical behavior of a - C-style downcast. + C-style downcast. No exception is set for this case. - Values are always copied as twos-complement, and sufficient size will be - requested for a sign bit. For example, this may cause an value that fits into - 8 bytes when treated as unsigned to request 9 bytes, even though all eight - bytes were copied into the buffer. What has been omitted is the zero sign - bit, which is redundant when the intention is to treat the value as unsigned. + Values are always copied as two's-complement, and sufficient buffer will be + requested to include a sign bit. For example, this may cause an value that + fits into 8 bytes when treated as unsigned to request 9 bytes, even though + all eight bytes were copied into the buffer. What has been omitted is the + zero sign bit, which is redundant when the intention is to treat the value as + unsigned. - Passing *n_bytes* of zero will always return the requested buffer size. + Passing zero to *n_bytes* will return the requested buffer size. .. note:: From 2f0778675ad0eaf346924ef6a2f60529b92ffcfa Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee <russell@keith-magee.com> Date: Tue, 13 Feb 2024 07:10:24 +0800 Subject: [PATCH 239/507] gh-114099: Refactor configure and Makefile to accomodate non-macOS frameworks (#115120) Part of the PEP 730 work to add iOS support. This change lays the groundwork for introducing iOS/tvOS/watchOS frameworks; it includes the structural refactoring needed so that iOS branches can be added into in a subsequent PR. Summary of changes: * Updates config.sub to the 2024-01-01 release. This is the "as released" version of config.sub. * Adds a RESSRCDIR variable to allow sharing of macOS and iOS Makefile steps. * Adds an INSTALLTARGETS variable so platforms can customise which targets are actually installed. This will be used to exclude certain targets (e.g., binaries, manfiles) from iOS framework installs. * Adds a PYTHONFRAMEWORKINSTALLNAMEPREFIX variable; this is used as the install name for the library. This is needed to allow for iOS frameworks to specify an @rpath-based install name. * Evaluates MACHDEP earlier in the configure process so that ac_sys_system is available. * Modifies _PYTHON_HOST_PLATFORM evaluation for cross-platform builds so that the CPU architecture is differentiated from the host identifier. This will be used to generate a _PYTHON_HOST_PLATFORM definition that includes ABI information, not just CPU architecture. * Differentiates between SOABI_PLATFORM and PLATFORM_TRIPLET. SOABI_PLATFORM is used in binary module names, and includes the ABI, but not the OS or CPU architecture (e.g., math.cpython-313-iphonesimulator.dylib). PLATFORM_TRIPLET is used as the sys._multiarch value, and on iOS will contains the ABI and architecture (e.g., iphoneos-arm64). This differentiation hasn't historically been needed because while macOS is a multiarch platform, it uses a bare darwin as PLATFORM_TRIPLE. * Removes the use of the deprecated -Wl,-single_module flag when compiling macOS frameworks. * Some whitespace normalisation where there was a mix of spaces and tabs in a single block. --- Makefile.pre.in | 11 +- ...-02-07-08-23-48.gh-issue-114099.XcEXEZ.rst | 2 + config.sub | 251 ++++++++---- configure | 383 +++++++++-------- configure.ac | 385 ++++++++++-------- 5 files changed, 600 insertions(+), 432 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2024-02-07-08-23-48.gh-issue-114099.XcEXEZ.rst diff --git a/Makefile.pre.in b/Makefile.pre.in index 4dabe328ce0362f..e0527633ccd03bb 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -184,6 +184,8 @@ PYTHONFRAMEWORK= @PYTHONFRAMEWORK@ PYTHONFRAMEWORKDIR= @PYTHONFRAMEWORKDIR@ PYTHONFRAMEWORKPREFIX= @PYTHONFRAMEWORKPREFIX@ PYTHONFRAMEWORKINSTALLDIR= @PYTHONFRAMEWORKINSTALLDIR@ +PYTHONFRAMEWORKINSTALLNAMEPREFIX= @PYTHONFRAMEWORKINSTALLNAMEPREFIX@ +RESSRCDIR= @RESSRCDIR@ # Deployment target selected during configure, to be checked # by distutils. The export statement is needed to ensure that the # deployment target is active during build. @@ -866,7 +868,7 @@ libpython3.so: libpython$(LDVERSION).so $(BLDSHARED) $(NO_AS_NEEDED) -o $@ -Wl,-h$@ $^ libpython$(LDVERSION).dylib: $(LIBRARY_OBJS) - $(CC) -dynamiclib -Wl,-single_module $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ + $(CC) -dynamiclib $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(PYTHONFRAMEWORKINSTALLNAMEPREFIX)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ libpython$(VERSION).sl: $(LIBRARY_OBJS) @@ -891,14 +893,13 @@ $(BUILDPYTHON)-gdb.py: $(SRC_GDB_HOOKS) # This rule is here for OPENSTEP/Rhapsody/MacOSX. It builds a temporary # minimal framework (not including the Lib directory and such) in the current # directory. -RESSRCDIR=Mac/Resources/framework $(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK): \ $(LIBRARY) \ $(RESSRCDIR)/Info.plist $(INSTALL) -d -m $(DIRMODE) $(PYTHONFRAMEWORKDIR)/Versions/$(VERSION) $(CC) -o $(LDLIBRARY) $(PY_CORE_LDFLAGS) -dynamiclib \ - -all_load $(LIBRARY) -Wl,-single_module \ - -install_name $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK) \ + -all_load $(LIBRARY) \ + -install_name $(DESTDIR)$(PYTHONFRAMEWORKINSTALLNAMEPREFIX)/$(PYTHONFRAMEWORK) \ -compatibility_version $(VERSION) \ -current_version $(VERSION) \ -framework CoreFoundation $(LIBS); @@ -2000,7 +2001,7 @@ multissltest: all # which can lead to two parallel `./python setup.py build` processes that # step on each others toes. .PHONY: install -install: @FRAMEWORKINSTALLFIRST@ commoninstall bininstall maninstall @FRAMEWORKINSTALLLAST@ +install: @FRAMEWORKINSTALLFIRST@ @INSTALLTARGETS@ @FRAMEWORKINSTALLLAST@ if test "x$(ENSUREPIP)" != "xno" ; then \ case $(ENSUREPIP) in \ upgrade) ensurepip="--upgrade" ;; \ diff --git a/Misc/NEWS.d/next/Build/2024-02-07-08-23-48.gh-issue-114099.XcEXEZ.rst b/Misc/NEWS.d/next/Build/2024-02-07-08-23-48.gh-issue-114099.XcEXEZ.rst new file mode 100644 index 000000000000000..5e4acfba8a69494 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2024-02-07-08-23-48.gh-issue-114099.XcEXEZ.rst @@ -0,0 +1,2 @@ +configure and Makefile were refactored to accomodate framework builds on +Apple platforms other than macOS. diff --git a/config.sub b/config.sub index d74fb6deac942a4..2c6a07ab3c34eab 100755 --- a/config.sub +++ b/config.sub @@ -1,14 +1,14 @@ #! /bin/sh # Configuration validation subroutine script. -# Copyright 1992-2021 Free Software Foundation, Inc. +# Copyright 1992-2024 Free Software Foundation, Inc. # shellcheck disable=SC2006,SC2268 # see below for rationale -timestamp='2021-08-14' +timestamp='2024-01-01' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or +# the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but @@ -76,13 +76,13 @@ Report bugs and patches to <config-patches@gnu.org>." version="\ GNU config.sub ($timestamp) -Copyright 1992-2021 Free Software Foundation, Inc. +Copyright 1992-2024 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" -Try \`$me --help' for more information." +Try '$me --help' for more information." # Parse command line while test $# -gt 0 ; do @@ -130,7 +130,7 @@ IFS=$saved_IFS # Separate into logical components for further validation case $1 in *-*-*-*-*) - echo Invalid configuration \`"$1"\': more than four components >&2 + echo "Invalid configuration '$1': more than four components" >&2 exit 1 ;; *-*-*-*) @@ -145,7 +145,8 @@ case $1 in nto-qnx* | linux-* | uclinux-uclibc* \ | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \ | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \ - | storm-chaos* | os2-emx* | rtmk-nova*) + | storm-chaos* | os2-emx* | rtmk-nova* | managarm-* \ + | windows-* ) basic_machine=$field1 basic_os=$maybe_os ;; @@ -943,7 +944,7 @@ $basic_machine EOF IFS=$saved_IFS ;; - # We use `pc' rather than `unknown' + # We use 'pc' rather than 'unknown' # because (1) that's what they normally are, and # (2) the word "unknown" tends to confuse beginning users. i*86 | x86_64) @@ -1020,6 +1021,11 @@ case $cpu-$vendor in ;; # Here we normalize CPU types with a missing or matching vendor + armh-unknown | armh-alt) + cpu=armv7l + vendor=alt + basic_os=${basic_os:-linux-gnueabihf} + ;; dpx20-unknown | dpx20-bull) cpu=rs6000 vendor=bull @@ -1070,7 +1076,7 @@ case $cpu-$vendor in pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) cpu=i586 ;; - pentiumpro-* | p6-* | 6x86-* | athlon-* | athalon_*-*) + pentiumpro-* | p6-* | 6x86-* | athlon-* | athlon_*-*) cpu=i686 ;; pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) @@ -1121,7 +1127,7 @@ case $cpu-$vendor in xscale-* | xscalee[bl]-*) cpu=`echo "$cpu" | sed 's/^xscale/arm/'` ;; - arm64-*) + arm64-* | aarch64le-*) cpu=aarch64 ;; @@ -1175,7 +1181,7 @@ case $cpu-$vendor in case $cpu in 1750a | 580 \ | a29k \ - | aarch64 | aarch64_be \ + | aarch64 | aarch64_be | aarch64c | arm64ec \ | abacus \ | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] \ | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] \ @@ -1194,50 +1200,29 @@ case $cpu-$vendor in | d10v | d30v | dlx | dsp16xx \ | e2k | elxsi | epiphany \ | f30[01] | f700 | fido | fr30 | frv | ft32 | fx80 \ + | javascript \ | h8300 | h8500 \ | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ | hexagon \ | i370 | i*86 | i860 | i960 | ia16 | ia64 \ | ip2k | iq2000 \ | k1om \ + | kvx \ | le32 | le64 \ | lm32 \ - | loongarch32 | loongarch64 | loongarchx32 \ + | loongarch32 | loongarch64 \ | m32c | m32r | m32rle \ | m5200 | m68000 | m680[012346]0 | m68360 | m683?2 | m68k \ | m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x \ | m88110 | m88k | maxq | mb | mcore | mep | metag \ | microblaze | microblazeel \ - | mips | mipsbe | mipseb | mipsel | mipsle \ - | mips16 \ - | mips64 | mips64eb | mips64el \ - | mips64octeon | mips64octeonel \ - | mips64orion | mips64orionel \ - | mips64r5900 | mips64r5900el \ - | mips64vr | mips64vrel \ - | mips64vr4100 | mips64vr4100el \ - | mips64vr4300 | mips64vr4300el \ - | mips64vr5000 | mips64vr5000el \ - | mips64vr5900 | mips64vr5900el \ - | mipsisa32 | mipsisa32el \ - | mipsisa32r2 | mipsisa32r2el \ - | mipsisa32r3 | mipsisa32r3el \ - | mipsisa32r5 | mipsisa32r5el \ - | mipsisa32r6 | mipsisa32r6el \ - | mipsisa64 | mipsisa64el \ - | mipsisa64r2 | mipsisa64r2el \ - | mipsisa64r3 | mipsisa64r3el \ - | mipsisa64r5 | mipsisa64r5el \ - | mipsisa64r6 | mipsisa64r6el \ - | mipsisa64sb1 | mipsisa64sb1el \ - | mipsisa64sr71k | mipsisa64sr71kel \ - | mipsr5900 | mipsr5900el \ - | mipstx39 | mipstx39el \ + | mips* \ | mmix \ | mn10200 | mn10300 \ | moxie \ | mt \ | msp430 \ + | nanomips* \ | nds32 | nds32le | nds32be \ | nfp \ | nios | nios2 | nios2eb | nios2el \ @@ -1269,6 +1254,7 @@ case $cpu-$vendor in | ubicom32 \ | v70 | v850 | v850e | v850e1 | v850es | v850e2 | v850e2v3 \ | vax \ + | vc4 \ | visium \ | w65 \ | wasm32 | wasm64 \ @@ -1280,7 +1266,7 @@ case $cpu-$vendor in ;; *) - echo Invalid configuration \`"$1"\': machine \`"$cpu-$vendor"\' not recognized 1>&2 + echo "Invalid configuration '$1': machine '$cpu-$vendor' not recognized" 1>&2 exit 1 ;; esac @@ -1301,11 +1287,12 @@ esac # Decode manufacturer-specific aliases for certain operating systems. -if test x$basic_os != x +if test x"$basic_os" != x then -# First recognize some ad-hoc caes, or perhaps split kernel-os, or else just +# First recognize some ad-hoc cases, or perhaps split kernel-os, or else just # set os. +obj= case $basic_os in gnu/linux*) kernel=linux @@ -1336,6 +1323,10 @@ EOF kernel=linux os=`echo "$basic_os" | sed -e 's|linux|gnu|'` ;; + managarm*) + kernel=managarm + os=`echo "$basic_os" | sed -e 's|managarm|mlibc|'` + ;; *) kernel= os=$basic_os @@ -1501,10 +1492,16 @@ case $os in os=eabi ;; *) - os=elf + os= + obj=elf ;; esac ;; + aout* | coff* | elf* | pe*) + # These are machine code file formats, not OSes + obj=$os + os= + ;; *) # No normalization, but not necessarily accepted, that comes below. ;; @@ -1523,12 +1520,15 @@ else # system, and we'll never get to this point. kernel= +obj= case $cpu-$vendor in score-*) - os=elf + os= + obj=elf ;; spu-*) - os=elf + os= + obj=elf ;; *-acorn) os=riscix1.2 @@ -1538,28 +1538,35 @@ case $cpu-$vendor in os=gnu ;; arm*-semi) - os=aout + os= + obj=aout ;; c4x-* | tic4x-*) - os=coff + os= + obj=coff ;; c8051-*) - os=elf + os= + obj=elf ;; clipper-intergraph) os=clix ;; hexagon-*) - os=elf + os= + obj=elf ;; tic54x-*) - os=coff + os= + obj=coff ;; tic55x-*) - os=coff + os= + obj=coff ;; tic6x-*) - os=coff + os= + obj=coff ;; # This must come before the *-dec entry. pdp10-*) @@ -1581,19 +1588,24 @@ case $cpu-$vendor in os=sunos3 ;; m68*-cisco) - os=aout + os= + obj=aout ;; mep-*) - os=elf + os= + obj=elf ;; mips*-cisco) - os=elf + os= + obj=elf ;; - mips*-*) - os=elf + mips*-*|nanomips*-*) + os= + obj=elf ;; or32-*) - os=coff + os= + obj=coff ;; *-tti) # must be before sparc entry or we get the wrong os. os=sysv3 @@ -1602,7 +1614,8 @@ case $cpu-$vendor in os=sunos4.1.1 ;; pru-*) - os=elf + os= + obj=elf ;; *-be) os=beos @@ -1683,10 +1696,12 @@ case $cpu-$vendor in os=uxpv ;; *-rom68k) - os=coff + os= + obj=coff ;; *-*bug) - os=coff + os= + obj=coff ;; *-apple) os=macos @@ -1704,10 +1719,11 @@ esac fi -# Now, validate our (potentially fixed-up) OS. +# Now, validate our (potentially fixed-up) individual pieces (OS, OBJ). + case $os in # Sometimes we do "kernel-libc", so those need to count as OSes. - musl* | newlib* | relibc* | uclibc*) + llvm* | musl* | newlib* | relibc* | uclibc*) ;; # Likewise for "kernel-abi" eabi* | gnueabi*) @@ -1715,6 +1731,9 @@ case $os in # VxWorks passes extra cpu info in the 4th filed. simlinux | simwindows | spe) ;; + # See `case $cpu-$os` validation below + ghcjs) + ;; # Now accept the basic system types. # The portable systems comes first. # Each alternative MUST end in a * to match a version number. @@ -1723,7 +1742,7 @@ case $os in | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \ | sym* | plan9* | psp* | sim* | xray* | os68k* | v88r* \ | hiux* | abug | nacl* | netware* | windows* \ - | os9* | macos* | osx* | ios* \ + | os9* | macos* | osx* | ios* | tvos* | watchos* \ | mpw* | magic* | mmixware* | mon960* | lnews* \ | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \ | aos* | aros* | cloudabi* | sortix* | twizzler* \ @@ -1732,11 +1751,11 @@ case $os in | mirbsd* | netbsd* | dicos* | openedition* | ose* \ | bitrig* | openbsd* | secbsd* | solidbsd* | libertybsd* | os108* \ | ekkobsd* | freebsd* | riscix* | lynxos* | os400* \ - | bosx* | nextstep* | cxux* | aout* | elf* | oabi* \ - | ptx* | coff* | ecoff* | winnt* | domain* | vsta* \ + | bosx* | nextstep* | cxux* | oabi* \ + | ptx* | ecoff* | winnt* | domain* | vsta* \ | udi* | lites* | ieee* | go32* | aux* | hcos* \ | chorusrdb* | cegcc* | glidix* | serenity* \ - | cygwin* | msys* | pe* | moss* | proelf* | rtems* \ + | cygwin* | msys* | moss* | proelf* | rtems* \ | midipix* | mingw32* | mingw64* | mint* \ | uxpv* | beos* | mpeix* | udk* | moxiebox* \ | interix* | uwin* | mks* | rhapsody* | darwin* \ @@ -1748,49 +1767,117 @@ case $os in | skyos* | haiku* | rdos* | toppers* | drops* | es* \ | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \ | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \ - | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr*) + | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr* \ + | fiwix* | mlibc* | cos* | mbr* | ironclad* ) ;; # This one is extra strict with allowed versions sco3.2v2 | sco3.2v[4-9]* | sco5v6*) # Don't forget version if it is 3.2v4 or newer. ;; + # This refers to builds using the UEFI calling convention + # (which depends on the architecture) and PE file format. + # Note that this is both a different calling convention and + # different file format than that of GNU-EFI + # (x86_64-w64-mingw32). + uefi) + ;; none) ;; + kernel* | msvc* ) + # Restricted further below + ;; + '') + if test x"$obj" = x + then + echo "Invalid configuration '$1': Blank OS only allowed with explicit machine code file format" 1>&2 + fi + ;; *) - echo Invalid configuration \`"$1"\': OS \`"$os"\' not recognized 1>&2 + echo "Invalid configuration '$1': OS '$os' not recognized" 1>&2 + exit 1 + ;; +esac + +case $obj in + aout* | coff* | elf* | pe*) + ;; + '') + # empty is fine + ;; + *) + echo "Invalid configuration '$1': Machine code format '$obj' not recognized" 1>&2 + exit 1 + ;; +esac + +# Here we handle the constraint that a (synthetic) cpu and os are +# valid only in combination with each other and nowhere else. +case $cpu-$os in + # The "javascript-unknown-ghcjs" triple is used by GHC; we + # accept it here in order to tolerate that, but reject any + # variations. + javascript-ghcjs) + ;; + javascript-* | *-ghcjs) + echo "Invalid configuration '$1': cpu '$cpu' is not valid with os '$os$obj'" 1>&2 exit 1 ;; esac # As a final step for OS-related things, validate the OS-kernel combination # (given a valid OS), if there is a kernel. -case $kernel-$os in - linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \ - | linux-musl* | linux-relibc* | linux-uclibc* ) +case $kernel-$os-$obj in + linux-gnu*- | linux-android*- | linux-dietlibc*- | linux-llvm*- \ + | linux-mlibc*- | linux-musl*- | linux-newlib*- \ + | linux-relibc*- | linux-uclibc*- ) + ;; + uclinux-uclibc*- ) + ;; + managarm-mlibc*- | managarm-kernel*- ) ;; - uclinux-uclibc* ) + windows*-msvc*-) ;; - -dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* ) + -dietlibc*- | -llvm*- | -mlibc*- | -musl*- | -newlib*- | -relibc*- \ + | -uclibc*- ) # These are just libc implementations, not actual OSes, and thus # require a kernel. - echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2 + echo "Invalid configuration '$1': libc '$os' needs explicit kernel." 1>&2 exit 1 ;; - kfreebsd*-gnu* | kopensolaris*-gnu*) + -kernel*- ) + echo "Invalid configuration '$1': '$os' needs explicit kernel." 1>&2 + exit 1 ;; - vxworks-simlinux | vxworks-simwindows | vxworks-spe) + *-kernel*- ) + echo "Invalid configuration '$1': '$kernel' does not support '$os'." 1>&2 + exit 1 ;; - nto-qnx*) + *-msvc*- ) + echo "Invalid configuration '$1': '$os' needs 'windows'." 1>&2 + exit 1 ;; - os2-emx) + kfreebsd*-gnu*- | kopensolaris*-gnu*-) + ;; + vxworks-simlinux- | vxworks-simwindows- | vxworks-spe-) + ;; + nto-qnx*-) + ;; + os2-emx-) ;; - *-eabi* | *-gnueabi*) + *-eabi*- | *-gnueabi*-) ;; - -*) + none--*) + # None (no kernel, i.e. freestanding / bare metal), + # can be paired with an machine code file format + ;; + -*-) # Blank kernel with real OS is always fine. ;; - *-*) - echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2 + --*) + # Blank kernel and OS with real machine code file format is always fine. + ;; + *-*-*) + echo "Invalid configuration '$1': Kernel '$kernel' not known to work with OS '$os'." 1>&2 exit 1 ;; esac @@ -1873,7 +1960,7 @@ case $vendor in ;; esac -echo "$cpu-$vendor-${kernel:+$kernel-}$os" +echo "$cpu-$vendor${kernel:+-$kernel}${os:+-$os}${obj:+-$obj}" exit # Local variables: diff --git a/configure b/configure index 705a778cafced35..ba2d49df7c65fee 100755 --- a/configure +++ b/configure @@ -972,7 +972,7 @@ HAS_XCRUN EXPORT_MACOSX_DEPLOYMENT_TARGET CONFIGURE_MACOSX_DEPLOYMENT_TARGET _PYTHON_HOST_PLATFORM -MACHDEP +INSTALLTARGETS FRAMEWORKINSTALLAPPSPREFIX FRAMEWORKUNIXTOOLSPREFIX FRAMEWORKPYTHONW @@ -980,6 +980,8 @@ FRAMEWORKALTINSTALLLAST FRAMEWORKALTINSTALLFIRST FRAMEWORKINSTALLLAST FRAMEWORKINSTALLFIRST +RESSRCDIR +PYTHONFRAMEWORKINSTALLNAMEPREFIX PYTHONFRAMEWORKINSTALLDIR PYTHONFRAMEWORKPREFIX PYTHONFRAMEWORKDIR @@ -989,6 +991,7 @@ LIPO_INTEL64_FLAGS LIPO_32BIT_FLAGS ARCH_RUN_32BIT UNIVERSALSDK +MACHDEP PKG_CONFIG_LIBDIR PKG_CONFIG_PATH PKG_CONFIG @@ -4004,6 +4007,77 @@ if test "$with_pkg_config" = yes -a -z "$PKG_CONFIG"; then as_fn_error $? "pkg-config is required" "$LINENO" 5] fi +# Set name for machine-dependent library files + +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking MACHDEP" >&5 +printf %s "checking MACHDEP... " >&6; } +if test -z "$MACHDEP" +then + # avoid using uname for cross builds + if test "$cross_compiling" = yes; then + # ac_sys_system and ac_sys_release are used for setting + # a lot of different things including 'define_xopen_source' + # in the case statement below. + case "$host" in + *-*-linux-android*) + ac_sys_system=Linux-android + ;; + *-*-linux*) + ac_sys_system=Linux + ;; + *-*-cygwin*) + ac_sys_system=Cygwin + ;; + *-*-vxworks*) + ac_sys_system=VxWorks + ;; + *-*-emscripten) + ac_sys_system=Emscripten + ;; + *-*-wasi) + ac_sys_system=WASI + ;; + *) + # for now, limit cross builds to known configurations + MACHDEP="unknown" + as_fn_error $? "cross build not supported for $host" "$LINENO" 5 + esac + ac_sys_release= + else + ac_sys_system=`uname -s` + if test "$ac_sys_system" = "AIX" \ + -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then + ac_sys_release=`uname -v` + else + ac_sys_release=`uname -r` + fi + fi + ac_md_system=`echo $ac_sys_system | + tr -d '/ ' | tr '[A-Z]' '[a-z]'` + ac_md_release=`echo $ac_sys_release | + tr -d '/ ' | sed 's/^[A-Z]\.//' | sed 's/\..*//'` + MACHDEP="$ac_md_system$ac_md_release" + + case $MACHDEP in + aix*) MACHDEP="aix";; + linux*) MACHDEP="linux";; + cygwin*) MACHDEP="cygwin";; + darwin*) MACHDEP="darwin";; + '') MACHDEP="unknown";; + esac + + if test "$ac_sys_system" = "SunOS"; then + # For Solaris, there isn't an OS version specific macro defined + # in most compilers, so we define one here. + SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\(0-9\)$!.0\1!g' | tr -d '.'` + +printf "%s\n" "#define Py_SUNOS_VERSION $SUNOS_VERSION" >>confdefs.h + + fi +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: \"$MACHDEP\"" >&5 +printf "%s\n" "\"$MACHDEP\"" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --enable-universalsdk" >&5 printf %s "checking for --enable-universalsdk... " >&6; } # Check whether --enable-universalsdk was given. @@ -4127,11 +4201,15 @@ then : PYTHONFRAMEWORKDIR=no-framework PYTHONFRAMEWORKPREFIX= PYTHONFRAMEWORKINSTALLDIR= + PYTHONFRAMEWORKINSTALLNAMEPREFIX= + RESSRCDIR= FRAMEWORKINSTALLFIRST= FRAMEWORKINSTALLLAST= FRAMEWORKALTINSTALLFIRST= FRAMEWORKALTINSTALLLAST= FRAMEWORKPYTHONW= + INSTALLTARGETS="commoninstall bininstall maninstall" + if test "x${prefix}" = "xNONE"; then FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" else @@ -4144,65 +4222,76 @@ then : PYTHONFRAMEWORKINSTALLDIR=$PYTHONFRAMEWORKPREFIX/$PYTHONFRAMEWORKDIR FRAMEWORKINSTALLFIRST="frameworkinstallstructure" FRAMEWORKALTINSTALLFIRST="frameworkinstallstructure " - FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" - FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" - FRAMEWORKPYTHONW="frameworkpythonw" - FRAMEWORKINSTALLAPPSPREFIX="/Applications" - if test "x${prefix}" = "xNONE" ; then - FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" + case $ac_sys_system in #( + Darwin) : + FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" + FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" + FRAMEWORKPYTHONW="frameworkpythonw" + FRAMEWORKINSTALLAPPSPREFIX="/Applications" + INSTALLTARGETS="commoninstall bininstall maninstall" - else - FRAMEWORKUNIXTOOLSPREFIX="${prefix}" - fi + if test "x${prefix}" = "xNONE" ; then + FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" - case "${enableval}" in - /System*) - FRAMEWORKINSTALLAPPSPREFIX="/Applications" - if test "${prefix}" = "NONE" ; then - # See below - FRAMEWORKUNIXTOOLSPREFIX="/usr" - fi - ;; + else + FRAMEWORKUNIXTOOLSPREFIX="${prefix}" + fi - /Library*) - FRAMEWORKINSTALLAPPSPREFIX="/Applications" - ;; + case "${enableval}" in + /System*) + FRAMEWORKINSTALLAPPSPREFIX="/Applications" + if test "${prefix}" = "NONE" ; then + # See below + FRAMEWORKUNIXTOOLSPREFIX="/usr" + fi + ;; + + /Library*) + FRAMEWORKINSTALLAPPSPREFIX="/Applications" + ;; + + */Library/Frameworks) + MDIR="`dirname "${enableval}"`" + MDIR="`dirname "${MDIR}"`" + FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications" + + if test "${prefix}" = "NONE"; then + # User hasn't specified the + # --prefix option, but wants to install + # the framework in a non-default location, + # ensure that the compatibility links get + # installed relative to that prefix as well + # instead of in /usr/local. + FRAMEWORKUNIXTOOLSPREFIX="${MDIR}" + fi + ;; - */Library/Frameworks) - MDIR="`dirname "${enableval}"`" - MDIR="`dirname "${MDIR}"`" - FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications" - - if test "${prefix}" = "NONE"; then - # User hasn't specified the - # --prefix option, but wants to install - # the framework in a non-default location, - # ensure that the compatibility links get - # installed relative to that prefix as well - # instead of in /usr/local. - FRAMEWORKUNIXTOOLSPREFIX="${MDIR}" - fi - ;; - - *) - FRAMEWORKINSTALLAPPSPREFIX="/Applications" - ;; - esac + *) + FRAMEWORKINSTALLAPPSPREFIX="/Applications" + ;; + esac - prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION + prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION + PYTHONFRAMEWORKINSTALLNAMEPREFIX=${prefix} + RESSRCDIR=Mac/Resources/framework - # Add files for Mac specific code to the list of output - # files: - ac_config_files="$ac_config_files Mac/Makefile" + # Add files for Mac specific code to the list of output + # files: + ac_config_files="$ac_config_files Mac/Makefile" - ac_config_files="$ac_config_files Mac/PythonLauncher/Makefile" + ac_config_files="$ac_config_files Mac/PythonLauncher/Makefile" - ac_config_files="$ac_config_files Mac/Resources/framework/Info.plist" + ac_config_files="$ac_config_files Mac/Resources/framework/Info.plist" - ac_config_files="$ac_config_files Mac/Resources/app/Info.plist" + ac_config_files="$ac_config_files Mac/Resources/app/Info.plist" - esac + ;; + *) + as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 + ;; + esac + esac else $as_nop @@ -4210,11 +4299,14 @@ else $as_nop PYTHONFRAMEWORKDIR=no-framework PYTHONFRAMEWORKPREFIX= PYTHONFRAMEWORKINSTALLDIR= + PYTHONFRAMEWORKINSTALLNAMEPREFIX= + RESSRCDIR= FRAMEWORKINSTALLFIRST= FRAMEWORKINSTALLLAST= FRAMEWORKALTINSTALLFIRST= FRAMEWORKALTINSTALLLAST= FRAMEWORKPYTHONW= + INSTALLTARGETS="commoninstall bininstall maninstall" if test "x${prefix}" = "xNONE" ; then FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" else @@ -4239,79 +4331,11 @@ fi -printf "%s\n" "#define _PYTHONFRAMEWORK \"${PYTHONFRAMEWORK}\"" >>confdefs.h -# Set name for machine-dependent library files -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking MACHDEP" >&5 -printf %s "checking MACHDEP... " >&6; } -if test -z "$MACHDEP" -then - # avoid using uname for cross builds - if test "$cross_compiling" = yes; then - # ac_sys_system and ac_sys_release are used for setting - # a lot of different things including 'define_xopen_source' - # in the case statement below. - case "$host" in - *-*-linux-android*) - ac_sys_system=Linux-android - ;; - *-*-linux*) - ac_sys_system=Linux - ;; - *-*-cygwin*) - ac_sys_system=Cygwin - ;; - *-*-vxworks*) - ac_sys_system=VxWorks - ;; - *-*-emscripten) - ac_sys_system=Emscripten - ;; - *-*-wasi) - ac_sys_system=WASI - ;; - *) - # for now, limit cross builds to known configurations - MACHDEP="unknown" - as_fn_error $? "cross build not supported for $host" "$LINENO" 5 - esac - ac_sys_release= - else - ac_sys_system=`uname -s` - if test "$ac_sys_system" = "AIX" \ - -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then - ac_sys_release=`uname -v` - else - ac_sys_release=`uname -r` - fi - fi - ac_md_system=`echo $ac_sys_system | - tr -d '/ ' | tr '[A-Z]' '[a-z]'` - ac_md_release=`echo $ac_sys_release | - tr -d '/ ' | sed 's/^[A-Z]\.//' | sed 's/\..*//'` - MACHDEP="$ac_md_system$ac_md_release" - - case $MACHDEP in - aix*) MACHDEP="aix";; - linux*) MACHDEP="linux";; - cygwin*) MACHDEP="cygwin";; - darwin*) MACHDEP="darwin";; - '') MACHDEP="unknown";; - esac - - if test "$ac_sys_system" = "SunOS"; then - # For Solaris, there isn't an OS version specific macro defined - # in most compilers, so we define one here. - SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\(0-9\)$!.0\1!g' | tr -d '.'` +printf "%s\n" "#define _PYTHONFRAMEWORK \"${PYTHONFRAMEWORK}\"" >>confdefs.h -printf "%s\n" "#define Py_SUNOS_VERSION $SUNOS_VERSION" >>confdefs.h - - fi -fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: \"$MACHDEP\"" >&5 -printf "%s\n" "\"$MACHDEP\"" >&6; } if test "$cross_compiling" = yes; then @@ -4319,27 +4343,27 @@ if test "$cross_compiling" = yes; then *-*-linux*) case "$host_cpu" in arm*) - _host_cpu=arm + _host_ident=arm ;; *) - _host_cpu=$host_cpu + _host_ident=$host_cpu esac ;; *-*-cygwin*) - _host_cpu= + _host_ident= ;; *-*-vxworks*) - _host_cpu=$host_cpu + _host_ident=$host_cpu ;; wasm32-*-* | wasm64-*-*) - _host_cpu=$host_cpu + _host_ident=$host_cpu ;; *) # for now, limit cross builds to known configurations MACHDEP="unknown" as_fn_error $? "cross build not supported for $host" "$LINENO" 5 esac - _PYTHON_HOST_PLATFORM="$MACHDEP${_host_cpu:+-$_host_cpu}" + _PYTHON_HOST_PLATFORM="$MACHDEP${_host_ident:+-$_host_ident}" fi # Some systems cannot stand _XOPEN_SOURCE being defined at all; they @@ -6769,8 +6793,6 @@ case $ac_sys_system in #( ;; esac -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MULTIARCH" >&5 -printf "%s\n" "$MULTIARCH" >&6; } if test x$PLATFORM_TRIPLET != x && test x$MULTIARCH != x; then if test x$PLATFORM_TRIPLET != x$MULTIARCH; then @@ -6780,6 +6802,14 @@ elif test x$PLATFORM_TRIPLET != x && test x$MULTIARCH = x; then MULTIARCH=$PLATFORM_TRIPLET fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MULTIARCH" >&5 +printf "%s\n" "$MULTIARCH" >&6; } + +case $ac_sys_system in #( + *) : + SOABI_PLATFORM=$PLATFORM_TRIPLET + ;; +esac if test x$MULTIARCH != x; then MULTIARCH_CPPFLAGS="-DMULTIARCH=\\\"$MULTIARCH\\\"" @@ -7271,7 +7301,7 @@ fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking LDLIBRARY" >&5 printf %s "checking LDLIBRARY... " >&6; } -# MacOSX framework builds need more magic. LDLIBRARY is the dynamic +# Apple framework builds need more magic. LDLIBRARY is the dynamic # library that we build, but we do not want to link against it (we # will find it with a -framework option). For this reason there is an # extra variable BLDLIBRARY against which Python and the extension @@ -7279,9 +7309,14 @@ printf %s "checking LDLIBRARY... " >&6; } # LDLIBRARY, but empty for MacOSX framework builds. if test "$enable_framework" then - LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' - RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}} + case $ac_sys_system in + Darwin) + LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; + *) + as_fn_error $? "Unknown platform for framework build" "$LINENO" 5;; + esac BLDLIBRARY='' + RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}} else BLDLIBRARY='$(LDLIBRARY)' fi @@ -7294,64 +7329,64 @@ printf "%s\n" "#define Py_ENABLE_SHARED 1" >>confdefs.h case $ac_sys_system in CYGWIN*) - LDLIBRARY='libpython$(LDVERSION).dll.a' - DLLLIBRARY='libpython$(LDVERSION).dll' - ;; + LDLIBRARY='libpython$(LDVERSION).dll.a' + DLLLIBRARY='libpython$(LDVERSION).dll' + ;; SunOS*) - LDLIBRARY='libpython$(LDVERSION).so' - BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)' - RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} - INSTSONAME="$LDLIBRARY".$SOVERSION - if test "$with_pydebug" != yes - then - PY3LIBRARY=libpython3.so - fi - ;; + LDLIBRARY='libpython$(LDVERSION).so' + BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)' + RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} + INSTSONAME="$LDLIBRARY".$SOVERSION + if test "$with_pydebug" != yes + then + PY3LIBRARY=libpython3.so + fi + ;; Linux*|GNU*|NetBSD*|FreeBSD*|DragonFly*|OpenBSD*|VxWorks*) - LDLIBRARY='libpython$(LDVERSION).so' - BLDLIBRARY='-L. -lpython$(LDVERSION)' - RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} - INSTSONAME="$LDLIBRARY".$SOVERSION - if test "$with_pydebug" != yes - then - PY3LIBRARY=libpython3.so - fi - ;; + LDLIBRARY='libpython$(LDVERSION).so' + BLDLIBRARY='-L. -lpython$(LDVERSION)' + RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} + INSTSONAME="$LDLIBRARY".$SOVERSION + if test "$with_pydebug" != yes + then + PY3LIBRARY=libpython3.so + fi + ;; hp*|HP*) - case `uname -m` in - ia64) - LDLIBRARY='libpython$(LDVERSION).so' - ;; - *) - LDLIBRARY='libpython$(LDVERSION).sl' - ;; - esac - BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)' - RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}} - ;; + case `uname -m` in + ia64) + LDLIBRARY='libpython$(LDVERSION).so' + ;; + *) + LDLIBRARY='libpython$(LDVERSION).sl' + ;; + esac + BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)' + RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}} + ;; Darwin*) - LDLIBRARY='libpython$(LDVERSION).dylib' - BLDLIBRARY='-L. -lpython$(LDVERSION)' - RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} - ;; + LDLIBRARY='libpython$(LDVERSION).dylib' + BLDLIBRARY='-L. -lpython$(LDVERSION)' + RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} + ;; AIX*) - LDLIBRARY='libpython$(LDVERSION).so' - RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} - ;; + LDLIBRARY='libpython$(LDVERSION).so' + RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} + ;; esac else # shared is disabled PY_ENABLE_SHARED=0 case $ac_sys_system in CYGWIN*) - BLDLIBRARY='$(LIBRARY)' - LDLIBRARY='libpython$(LDVERSION).dll.a' - ;; + BLDLIBRARY='$(LIBRARY)' + LDLIBRARY='libpython$(LDVERSION).dll.a' + ;; esac fi if test "$cross_compiling" = yes; then - RUNSHARED= + RUNSHARED= fi @@ -23898,7 +23933,7 @@ printf %s "checking ABIFLAGS... " >&6; } printf "%s\n" "$ABIFLAGS" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking SOABI" >&5 printf %s "checking SOABI... " >&6; } -SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET} +SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${SOABI_PLATFORM:+-$SOABI_PLATFORM} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $SOABI" >&5 printf "%s\n" "$SOABI" >&6; } @@ -23907,7 +23942,7 @@ printf "%s\n" "$SOABI" >&6; } if test "$Py_DEBUG" = 'true'; then # Similar to SOABI but remove "d" flag from ABIFLAGS - ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET} + ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${SOABI_PLATFORM:+-$SOABI_PLATFORM} printf "%s\n" "#define ALT_SOABI \"${ALT_SOABI}\"" >>confdefs.h diff --git a/configure.ac b/configure.ac index dee7ed552b370f0..b39af7422c4c7ca 100644 --- a/configure.ac +++ b/configure.ac @@ -307,6 +307,74 @@ if test "$with_pkg_config" = yes -a -z "$PKG_CONFIG"; then AC_MSG_ERROR([pkg-config is required])] fi +# Set name for machine-dependent library files +AC_ARG_VAR([MACHDEP], [name for machine-dependent library files]) +AC_MSG_CHECKING([MACHDEP]) +if test -z "$MACHDEP" +then + # avoid using uname for cross builds + if test "$cross_compiling" = yes; then + # ac_sys_system and ac_sys_release are used for setting + # a lot of different things including 'define_xopen_source' + # in the case statement below. + case "$host" in + *-*-linux-android*) + ac_sys_system=Linux-android + ;; + *-*-linux*) + ac_sys_system=Linux + ;; + *-*-cygwin*) + ac_sys_system=Cygwin + ;; + *-*-vxworks*) + ac_sys_system=VxWorks + ;; + *-*-emscripten) + ac_sys_system=Emscripten + ;; + *-*-wasi) + ac_sys_system=WASI + ;; + *) + # for now, limit cross builds to known configurations + MACHDEP="unknown" + AC_MSG_ERROR([cross build not supported for $host]) + esac + ac_sys_release= + else + ac_sys_system=`uname -s` + if test "$ac_sys_system" = "AIX" \ + -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then + ac_sys_release=`uname -v` + else + ac_sys_release=`uname -r` + fi + fi + ac_md_system=`echo $ac_sys_system | + tr -d '[/ ]' | tr '[[A-Z]]' '[[a-z]]'` + ac_md_release=`echo $ac_sys_release | + tr -d '[/ ]' | sed 's/^[[A-Z]]\.//' | sed 's/\..*//'` + MACHDEP="$ac_md_system$ac_md_release" + + case $MACHDEP in + aix*) MACHDEP="aix";; + linux*) MACHDEP="linux";; + cygwin*) MACHDEP="cygwin";; + darwin*) MACHDEP="darwin";; + '') MACHDEP="unknown";; + esac + + if test "$ac_sys_system" = "SunOS"; then + # For Solaris, there isn't an OS version specific macro defined + # in most compilers, so we define one here. + SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\([0-9]\)$!.0\1!g' | tr -d '.'` + AC_DEFINE_UNQUOTED([Py_SUNOS_VERSION], [$SUNOS_VERSION], + [The version of SunOS/Solaris as reported by `uname -r' without the dot.]) + fi +fi +AC_MSG_RESULT(["$MACHDEP"]) + AC_MSG_CHECKING([for --enable-universalsdk]) AC_ARG_ENABLE([universalsdk], AS_HELP_STRING([--enable-universalsdk@<:@=SDKDIR@:>@], @@ -424,11 +492,15 @@ AC_ARG_ENABLE([framework], PYTHONFRAMEWORKDIR=no-framework PYTHONFRAMEWORKPREFIX= PYTHONFRAMEWORKINSTALLDIR= + PYTHONFRAMEWORKINSTALLNAMEPREFIX= + RESSRCDIR= FRAMEWORKINSTALLFIRST= FRAMEWORKINSTALLLAST= FRAMEWORKALTINSTALLFIRST= FRAMEWORKALTINSTALLLAST= FRAMEWORKPYTHONW= + INSTALLTARGETS="commoninstall bininstall maninstall" + if test "x${prefix}" = "xNONE"; then FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" else @@ -441,71 +513,85 @@ AC_ARG_ENABLE([framework], PYTHONFRAMEWORKINSTALLDIR=$PYTHONFRAMEWORKPREFIX/$PYTHONFRAMEWORKDIR FRAMEWORKINSTALLFIRST="frameworkinstallstructure" FRAMEWORKALTINSTALLFIRST="frameworkinstallstructure " - FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" - FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" - FRAMEWORKPYTHONW="frameworkpythonw" - FRAMEWORKINSTALLAPPSPREFIX="/Applications" - if test "x${prefix}" = "xNONE" ; then - FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" + case $ac_sys_system in #( + Darwin) : + FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" + FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" + FRAMEWORKPYTHONW="frameworkpythonw" + FRAMEWORKINSTALLAPPSPREFIX="/Applications" + INSTALLTARGETS="commoninstall bininstall maninstall" - else - FRAMEWORKUNIXTOOLSPREFIX="${prefix}" - fi + if test "x${prefix}" = "xNONE" ; then + FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" - case "${enableval}" in - /System*) - FRAMEWORKINSTALLAPPSPREFIX="/Applications" - if test "${prefix}" = "NONE" ; then - # See below - FRAMEWORKUNIXTOOLSPREFIX="/usr" - fi - ;; + else + FRAMEWORKUNIXTOOLSPREFIX="${prefix}" + fi - /Library*) - FRAMEWORKINSTALLAPPSPREFIX="/Applications" - ;; + case "${enableval}" in + /System*) + FRAMEWORKINSTALLAPPSPREFIX="/Applications" + if test "${prefix}" = "NONE" ; then + # See below + FRAMEWORKUNIXTOOLSPREFIX="/usr" + fi + ;; + + /Library*) + FRAMEWORKINSTALLAPPSPREFIX="/Applications" + ;; + + */Library/Frameworks) + MDIR="`dirname "${enableval}"`" + MDIR="`dirname "${MDIR}"`" + FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications" + + if test "${prefix}" = "NONE"; then + # User hasn't specified the + # --prefix option, but wants to install + # the framework in a non-default location, + # ensure that the compatibility links get + # installed relative to that prefix as well + # instead of in /usr/local. + FRAMEWORKUNIXTOOLSPREFIX="${MDIR}" + fi + ;; - */Library/Frameworks) - MDIR="`dirname "${enableval}"`" - MDIR="`dirname "${MDIR}"`" - FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications" - - if test "${prefix}" = "NONE"; then - # User hasn't specified the - # --prefix option, but wants to install - # the framework in a non-default location, - # ensure that the compatibility links get - # installed relative to that prefix as well - # instead of in /usr/local. - FRAMEWORKUNIXTOOLSPREFIX="${MDIR}" - fi - ;; + *) + FRAMEWORKINSTALLAPPSPREFIX="/Applications" + ;; + esac - *) - FRAMEWORKINSTALLAPPSPREFIX="/Applications" - ;; + prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION + PYTHONFRAMEWORKINSTALLNAMEPREFIX=${prefix} + RESSRCDIR=Mac/Resources/framework + + # Add files for Mac specific code to the list of output + # files: + AC_CONFIG_FILES([Mac/Makefile]) + AC_CONFIG_FILES([Mac/PythonLauncher/Makefile]) + AC_CONFIG_FILES([Mac/Resources/framework/Info.plist]) + AC_CONFIG_FILES([Mac/Resources/app/Info.plist]) + ;; + *) + AC_MSG_ERROR([Unknown platform for framework build]) + ;; + esac esac - - prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION - - # Add files for Mac specific code to the list of output - # files: - AC_CONFIG_FILES([Mac/Makefile]) - AC_CONFIG_FILES([Mac/PythonLauncher/Makefile]) - AC_CONFIG_FILES([Mac/Resources/framework/Info.plist]) - AC_CONFIG_FILES([Mac/Resources/app/Info.plist]) - esac ],[ PYTHONFRAMEWORK= PYTHONFRAMEWORKDIR=no-framework PYTHONFRAMEWORKPREFIX= PYTHONFRAMEWORKINSTALLDIR= + PYTHONFRAMEWORKINSTALLNAMEPREFIX= + RESSRCDIR= FRAMEWORKINSTALLFIRST= FRAMEWORKINSTALLLAST= FRAMEWORKALTINSTALLFIRST= FRAMEWORKALTINSTALLLAST= FRAMEWORKPYTHONW= + INSTALLTARGETS="commoninstall bininstall maninstall" if test "x${prefix}" = "xNONE" ; then FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" else @@ -519,6 +605,8 @@ AC_SUBST([PYTHONFRAMEWORKIDENTIFIER]) AC_SUBST([PYTHONFRAMEWORKDIR]) AC_SUBST([PYTHONFRAMEWORKPREFIX]) AC_SUBST([PYTHONFRAMEWORKINSTALLDIR]) +AC_SUBST([PYTHONFRAMEWORKINSTALLNAMEPREFIX]) +AC_SUBST([RESSRCDIR]) AC_SUBST([FRAMEWORKINSTALLFIRST]) AC_SUBST([FRAMEWORKINSTALLLAST]) AC_SUBST([FRAMEWORKALTINSTALLFIRST]) @@ -526,105 +614,38 @@ AC_SUBST([FRAMEWORKALTINSTALLLAST]) AC_SUBST([FRAMEWORKPYTHONW]) AC_SUBST([FRAMEWORKUNIXTOOLSPREFIX]) AC_SUBST([FRAMEWORKINSTALLAPPSPREFIX]) +AC_SUBST([INSTALLTARGETS]) AC_DEFINE_UNQUOTED([_PYTHONFRAMEWORK], ["${PYTHONFRAMEWORK}"], [framework name]) -# Set name for machine-dependent library files -AC_ARG_VAR([MACHDEP], [name for machine-dependent library files]) -AC_MSG_CHECKING([MACHDEP]) -if test -z "$MACHDEP" -then - # avoid using uname for cross builds - if test "$cross_compiling" = yes; then - # ac_sys_system and ac_sys_release are used for setting - # a lot of different things including 'define_xopen_source' - # in the case statement below. - case "$host" in - *-*-linux-android*) - ac_sys_system=Linux-android - ;; - *-*-linux*) - ac_sys_system=Linux - ;; - *-*-cygwin*) - ac_sys_system=Cygwin - ;; - *-*-vxworks*) - ac_sys_system=VxWorks - ;; - *-*-emscripten) - ac_sys_system=Emscripten - ;; - *-*-wasi) - ac_sys_system=WASI - ;; - *) - # for now, limit cross builds to known configurations - MACHDEP="unknown" - AC_MSG_ERROR([cross build not supported for $host]) - esac - ac_sys_release= - else - ac_sys_system=`uname -s` - if test "$ac_sys_system" = "AIX" \ - -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then - ac_sys_release=`uname -v` - else - ac_sys_release=`uname -r` - fi - fi - ac_md_system=`echo $ac_sys_system | - tr -d '[/ ]' | tr '[[A-Z]]' '[[a-z]]'` - ac_md_release=`echo $ac_sys_release | - tr -d '[/ ]' | sed 's/^[[A-Z]]\.//' | sed 's/\..*//'` - MACHDEP="$ac_md_system$ac_md_release" - - case $MACHDEP in - aix*) MACHDEP="aix";; - linux*) MACHDEP="linux";; - cygwin*) MACHDEP="cygwin";; - darwin*) MACHDEP="darwin";; - '') MACHDEP="unknown";; - esac - - if test "$ac_sys_system" = "SunOS"; then - # For Solaris, there isn't an OS version specific macro defined - # in most compilers, so we define one here. - SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\([0-9]\)$!.0\1!g' | tr -d '.'` - AC_DEFINE_UNQUOTED([Py_SUNOS_VERSION], [$SUNOS_VERSION], - [The version of SunOS/Solaris as reported by `uname -r' without the dot.]) - fi -fi -AC_MSG_RESULT(["$MACHDEP"]) - AC_SUBST([_PYTHON_HOST_PLATFORM]) if test "$cross_compiling" = yes; then case "$host" in *-*-linux*) case "$host_cpu" in arm*) - _host_cpu=arm + _host_ident=arm ;; *) - _host_cpu=$host_cpu + _host_ident=$host_cpu esac ;; *-*-cygwin*) - _host_cpu= + _host_ident= ;; *-*-vxworks*) - _host_cpu=$host_cpu + _host_ident=$host_cpu ;; wasm32-*-* | wasm64-*-*) - _host_cpu=$host_cpu + _host_ident=$host_cpu ;; *) # for now, limit cross builds to known configurations MACHDEP="unknown" AC_MSG_ERROR([cross build not supported for $host]) esac - _PYTHON_HOST_PLATFORM="$MACHDEP${_host_cpu:+-$_host_cpu}" + _PYTHON_HOST_PLATFORM="$MACHDEP${_host_ident:+-$_host_ident}" fi # Some systems cannot stand _XOPEN_SOURCE being defined at all; they @@ -935,6 +956,14 @@ else fi rm -f conftest.out +dnl On some platforms, using a true "triplet" for MULTIARCH would be redundant. +dnl For example, `arm64-apple-darwin` is redundant, because there isn't a +dnl non-Apple Darwin. Including the CPU architecture can also be potentially +dnl redundant - on macOS, for example, it's possible to do a single compile +dnl pass that includes multiple architectures, so it would be misleading for +dnl MULTIARCH (and thus the sysconfigdata module name) to include a single CPU +dnl architecture. PLATFORM_TRIPLET will be a pair or single value for these +dnl platforms. AC_MSG_CHECKING([for multiarch]) AS_CASE([$ac_sys_system], [Darwin*], [MULTIARCH=""], @@ -942,7 +971,6 @@ AS_CASE([$ac_sys_system], [MULTIARCH=$($CC --print-multiarch 2>/dev/null)] ) AC_SUBST([MULTIARCH]) -AC_MSG_RESULT([$MULTIARCH]) if test x$PLATFORM_TRIPLET != x && test x$MULTIARCH != x; then if test x$PLATFORM_TRIPLET != x$MULTIARCH; then @@ -952,6 +980,16 @@ elif test x$PLATFORM_TRIPLET != x && test x$MULTIARCH = x; then MULTIARCH=$PLATFORM_TRIPLET fi AC_SUBST([PLATFORM_TRIPLET]) +AC_MSG_RESULT([$MULTIARCH]) + +dnl Even if we *do* include the CPU architecture in the MULTIARCH value, some +dnl platforms don't need the CPU architecture in the SOABI tag. These platforms +dnl will have multiple sysconfig modules (one for each CPU architecture), but +dnl use a single "fat" binary at runtime. SOABI_PLATFORM is the component of +dnl the PLATFORM_TRIPLET that will be used in binary module extensions. +AS_CASE([$ac_sys_system], + [SOABI_PLATFORM=$PLATFORM_TRIPLET] +) if test x$MULTIARCH != x; then MULTIARCH_CPPFLAGS="-DMULTIARCH=\\\"$MULTIARCH\\\"" @@ -1294,7 +1332,7 @@ fi AC_MSG_CHECKING([LDLIBRARY]) -# MacOSX framework builds need more magic. LDLIBRARY is the dynamic +# Apple framework builds need more magic. LDLIBRARY is the dynamic # library that we build, but we do not want to link against it (we # will find it with a -framework option). For this reason there is an # extra variable BLDLIBRARY against which Python and the extension @@ -1302,9 +1340,14 @@ AC_MSG_CHECKING([LDLIBRARY]) # LDLIBRARY, but empty for MacOSX framework builds. if test "$enable_framework" then - LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' - RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}} + case $ac_sys_system in + Darwin) + LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; + *) + AC_MSG_ERROR([Unknown platform for framework build]);; + esac BLDLIBRARY='' + RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}} else BLDLIBRARY='$(LDLIBRARY)' fi @@ -1316,64 +1359,64 @@ if test $enable_shared = "yes"; then [Defined if Python is built as a shared library.]) case $ac_sys_system in CYGWIN*) - LDLIBRARY='libpython$(LDVERSION).dll.a' - DLLLIBRARY='libpython$(LDVERSION).dll' - ;; + LDLIBRARY='libpython$(LDVERSION).dll.a' + DLLLIBRARY='libpython$(LDVERSION).dll' + ;; SunOS*) - LDLIBRARY='libpython$(LDVERSION).so' - BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)' - RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} - INSTSONAME="$LDLIBRARY".$SOVERSION - if test "$with_pydebug" != yes - then - PY3LIBRARY=libpython3.so - fi - ;; + LDLIBRARY='libpython$(LDVERSION).so' + BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)' + RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} + INSTSONAME="$LDLIBRARY".$SOVERSION + if test "$with_pydebug" != yes + then + PY3LIBRARY=libpython3.so + fi + ;; Linux*|GNU*|NetBSD*|FreeBSD*|DragonFly*|OpenBSD*|VxWorks*) - LDLIBRARY='libpython$(LDVERSION).so' - BLDLIBRARY='-L. -lpython$(LDVERSION)' - RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} - INSTSONAME="$LDLIBRARY".$SOVERSION - if test "$with_pydebug" != yes - then - PY3LIBRARY=libpython3.so - fi - ;; + LDLIBRARY='libpython$(LDVERSION).so' + BLDLIBRARY='-L. -lpython$(LDVERSION)' + RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} + INSTSONAME="$LDLIBRARY".$SOVERSION + if test "$with_pydebug" != yes + then + PY3LIBRARY=libpython3.so + fi + ;; hp*|HP*) - case `uname -m` in - ia64) - LDLIBRARY='libpython$(LDVERSION).so' - ;; - *) - LDLIBRARY='libpython$(LDVERSION).sl' - ;; - esac - BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)' - RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}} - ;; + case `uname -m` in + ia64) + LDLIBRARY='libpython$(LDVERSION).so' + ;; + *) + LDLIBRARY='libpython$(LDVERSION).sl' + ;; + esac + BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)' + RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}} + ;; Darwin*) - LDLIBRARY='libpython$(LDVERSION).dylib' - BLDLIBRARY='-L. -lpython$(LDVERSION)' - RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} - ;; + LDLIBRARY='libpython$(LDVERSION).dylib' + BLDLIBRARY='-L. -lpython$(LDVERSION)' + RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} + ;; AIX*) - LDLIBRARY='libpython$(LDVERSION).so' - RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} - ;; + LDLIBRARY='libpython$(LDVERSION).so' + RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} + ;; esac else # shared is disabled PY_ENABLE_SHARED=0 case $ac_sys_system in CYGWIN*) - BLDLIBRARY='$(LIBRARY)' - LDLIBRARY='libpython$(LDVERSION).dll.a' - ;; + BLDLIBRARY='$(LIBRARY)' + LDLIBRARY='libpython$(LDVERSION).dll.a' + ;; esac fi if test "$cross_compiling" = yes; then - RUNSHARED= + RUNSHARED= fi AC_ARG_VAR([HOSTRUNNER], [Program to run CPython for the host platform]) @@ -5824,7 +5867,7 @@ AC_SUBST([SOABI]) AC_MSG_CHECKING([ABIFLAGS]) AC_MSG_RESULT([$ABIFLAGS]) AC_MSG_CHECKING([SOABI]) -SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET} +SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${SOABI_PLATFORM:+-$SOABI_PLATFORM} AC_MSG_RESULT([$SOABI]) # Release build, debug build (Py_DEBUG), and trace refs build (Py_TRACE_REFS) @@ -5832,7 +5875,7 @@ AC_MSG_RESULT([$SOABI]) if test "$Py_DEBUG" = 'true'; then # Similar to SOABI but remove "d" flag from ABIFLAGS AC_SUBST([ALT_SOABI]) - ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET} + ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${SOABI_PLATFORM:+-$SOABI_PLATFORM} AC_DEFINE_UNQUOTED([ALT_SOABI], ["${ALT_SOABI}"], [Alternative SOABI used in debug build to load C extensions built in release mode]) fi From ea25f32d5f7d9ae4358338a3fb49bba9b68051a5 Mon Sep 17 00:00:00 2001 From: Steve Dower <steve.dower@python.org> Date: Tue, 13 Feb 2024 00:28:35 +0000 Subject: [PATCH 240/507] gh-89240: Enable multiprocessing on Windows to use large process pools (GH-107873) We add _winapi.BatchedWaitForMultipleObjects to wait for larger numbers of handles. This is an internal module, hence undocumented, and should be used with caution. Check the docstring for info before using BatchedWaitForMultipleObjects. --- .../pycore_global_objects_fini_generated.h | 10 + Include/internal/pycore_global_strings.h | 10 + .../internal/pycore_runtime_init_generated.h | 10 + .../internal/pycore_unicodeobject_generated.h | 30 ++ Lib/multiprocessing/connection.py | 14 +- Lib/test/_test_multiprocessing.py | 18 + Lib/test/test_winapi.py | 94 ++++ ...3-08-11-18-21-38.gh-issue-89240.dtSOLG.rst | 1 + Modules/_winapi.c | 506 ++++++++++++++++++ Modules/clinic/_winapi.c.h | 498 ++++++++++++++++- Objects/exceptions.c | 7 +- PC/errmap.h | 3 + 12 files changed, 1195 insertions(+), 6 deletions(-) create mode 100644 Lib/test/test_winapi.py create mode 100644 Misc/NEWS.d/next/Windows/2023-08-11-18-21-38.gh-issue-89240.dtSOLG.rst diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 932738c3049882b..11755210d65432d 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -883,6 +883,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(defaultaction)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(delete)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(depth)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(desired_access)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(detect_types)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(deterministic)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(device)); @@ -973,6 +974,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(groups)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(h)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(handle)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(handle_seq)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hash_name)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(header)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(headers)); @@ -990,9 +992,12 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(indexgroup)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(inf)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(infer_variance)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(inherit_handle)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(inheritable)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(initial)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(initial_bytes)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(initial_owner)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(initial_state)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(initial_value)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(initval)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(inner_size)); @@ -1048,6 +1053,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(locals)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(logoption)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(loop)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(manual_reset)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mapping)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(match)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(max_length)); @@ -1064,6 +1070,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(metadata)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(method)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(microsecond)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(milliseconds)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(minute)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mod)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mode)); @@ -1073,6 +1080,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(month)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mro)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(msg)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mutex)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mycmp)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(n)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(n_arg)); @@ -1176,6 +1184,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(sched_priority)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(scheduler)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(second)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(security_attributes)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(seek)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(seekable)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(selectors)); @@ -1263,6 +1272,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(values)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(version)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(volume)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(wait_all)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(warnings)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(warnoptions)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(wbits)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index da62b4f0a951ff8..576ac703ca15081 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -372,6 +372,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(defaultaction) STRUCT_FOR_ID(delete) STRUCT_FOR_ID(depth) + STRUCT_FOR_ID(desired_access) STRUCT_FOR_ID(detect_types) STRUCT_FOR_ID(deterministic) STRUCT_FOR_ID(device) @@ -462,6 +463,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(groups) STRUCT_FOR_ID(h) STRUCT_FOR_ID(handle) + STRUCT_FOR_ID(handle_seq) STRUCT_FOR_ID(hash_name) STRUCT_FOR_ID(header) STRUCT_FOR_ID(headers) @@ -479,9 +481,12 @@ struct _Py_global_strings { STRUCT_FOR_ID(indexgroup) STRUCT_FOR_ID(inf) STRUCT_FOR_ID(infer_variance) + STRUCT_FOR_ID(inherit_handle) STRUCT_FOR_ID(inheritable) STRUCT_FOR_ID(initial) STRUCT_FOR_ID(initial_bytes) + STRUCT_FOR_ID(initial_owner) + STRUCT_FOR_ID(initial_state) STRUCT_FOR_ID(initial_value) STRUCT_FOR_ID(initval) STRUCT_FOR_ID(inner_size) @@ -537,6 +542,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(locals) STRUCT_FOR_ID(logoption) STRUCT_FOR_ID(loop) + STRUCT_FOR_ID(manual_reset) STRUCT_FOR_ID(mapping) STRUCT_FOR_ID(match) STRUCT_FOR_ID(max_length) @@ -553,6 +559,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(metadata) STRUCT_FOR_ID(method) STRUCT_FOR_ID(microsecond) + STRUCT_FOR_ID(milliseconds) STRUCT_FOR_ID(minute) STRUCT_FOR_ID(mod) STRUCT_FOR_ID(mode) @@ -562,6 +569,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(month) STRUCT_FOR_ID(mro) STRUCT_FOR_ID(msg) + STRUCT_FOR_ID(mutex) STRUCT_FOR_ID(mycmp) STRUCT_FOR_ID(n) STRUCT_FOR_ID(n_arg) @@ -665,6 +673,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(sched_priority) STRUCT_FOR_ID(scheduler) STRUCT_FOR_ID(second) + STRUCT_FOR_ID(security_attributes) STRUCT_FOR_ID(seek) STRUCT_FOR_ID(seekable) STRUCT_FOR_ID(selectors) @@ -752,6 +761,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(values) STRUCT_FOR_ID(version) STRUCT_FOR_ID(volume) + STRUCT_FOR_ID(wait_all) STRUCT_FOR_ID(warnings) STRUCT_FOR_ID(warnoptions) STRUCT_FOR_ID(wbits) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 68fbbcb4378e17f..e682c97e7c0248d 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -881,6 +881,7 @@ extern "C" { INIT_ID(defaultaction), \ INIT_ID(delete), \ INIT_ID(depth), \ + INIT_ID(desired_access), \ INIT_ID(detect_types), \ INIT_ID(deterministic), \ INIT_ID(device), \ @@ -971,6 +972,7 @@ extern "C" { INIT_ID(groups), \ INIT_ID(h), \ INIT_ID(handle), \ + INIT_ID(handle_seq), \ INIT_ID(hash_name), \ INIT_ID(header), \ INIT_ID(headers), \ @@ -988,9 +990,12 @@ extern "C" { INIT_ID(indexgroup), \ INIT_ID(inf), \ INIT_ID(infer_variance), \ + INIT_ID(inherit_handle), \ INIT_ID(inheritable), \ INIT_ID(initial), \ INIT_ID(initial_bytes), \ + INIT_ID(initial_owner), \ + INIT_ID(initial_state), \ INIT_ID(initial_value), \ INIT_ID(initval), \ INIT_ID(inner_size), \ @@ -1046,6 +1051,7 @@ extern "C" { INIT_ID(locals), \ INIT_ID(logoption), \ INIT_ID(loop), \ + INIT_ID(manual_reset), \ INIT_ID(mapping), \ INIT_ID(match), \ INIT_ID(max_length), \ @@ -1062,6 +1068,7 @@ extern "C" { INIT_ID(metadata), \ INIT_ID(method), \ INIT_ID(microsecond), \ + INIT_ID(milliseconds), \ INIT_ID(minute), \ INIT_ID(mod), \ INIT_ID(mode), \ @@ -1071,6 +1078,7 @@ extern "C" { INIT_ID(month), \ INIT_ID(mro), \ INIT_ID(msg), \ + INIT_ID(mutex), \ INIT_ID(mycmp), \ INIT_ID(n), \ INIT_ID(n_arg), \ @@ -1174,6 +1182,7 @@ extern "C" { INIT_ID(sched_priority), \ INIT_ID(scheduler), \ INIT_ID(second), \ + INIT_ID(security_attributes), \ INIT_ID(seek), \ INIT_ID(seekable), \ INIT_ID(selectors), \ @@ -1261,6 +1270,7 @@ extern "C" { INIT_ID(values), \ INIT_ID(version), \ INIT_ID(volume), \ + INIT_ID(wait_all), \ INIT_ID(warnings), \ INIT_ID(warnoptions), \ INIT_ID(wbits), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index c8458b4e36ccc93..739af0e73c23ff8 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -957,6 +957,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(depth); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(desired_access); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(detect_types); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1227,6 +1230,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(handle); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(handle_seq); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(hash_name); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1278,6 +1284,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(infer_variance); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(inherit_handle); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(inheritable); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1287,6 +1296,12 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(initial_bytes); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(initial_owner); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(initial_state); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(initial_value); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1452,6 +1467,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(loop); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(manual_reset); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(mapping); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1500,6 +1518,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(microsecond); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(milliseconds); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(minute); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1527,6 +1548,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(msg); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(mutex); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(mycmp); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1836,6 +1860,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(second); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(security_attributes); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(seek); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -2097,6 +2124,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(volume); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(wait_all); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(warnings); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py index c6a66a1bc963c3a..58d697fdecacc05 100644 --- a/Lib/multiprocessing/connection.py +++ b/Lib/multiprocessing/connection.py @@ -1011,8 +1011,20 @@ def _exhaustive_wait(handles, timeout): # returning the first signalled might create starvation issues.) L = list(handles) ready = [] + # Windows limits WaitForMultipleObjects at 64 handles, and we use a + # few for synchronisation, so we switch to batched waits at 60. + if len(L) > 60: + try: + res = _winapi.BatchedWaitForMultipleObjects(L, False, timeout) + except TimeoutError: + return [] + ready.extend(L[i] for i in res) + if res: + L = [h for i, h in enumerate(L) if i > res[0] & i not in res] + timeout = 0 while L: - res = _winapi.WaitForMultipleObjects(L, False, timeout) + short_L = L[:60] if len(L) > 60 else L + res = _winapi.WaitForMultipleObjects(short_L, False, timeout) if res == WAIT_TIMEOUT: break elif WAIT_OBJECT_0 <= res < WAIT_OBJECT_0 + len(L): diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index c0d3ca50f17d69d..94ce85cac754ae5 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -6113,6 +6113,24 @@ def test_spawn_sys_executable_none_allows_import(self): self.assertEqual(rc, 0) self.assertFalse(err, msg=err.decode('utf-8')) + def test_large_pool(self): + # + # gh-89240: Check that large pools are always okay + # + testfn = os_helper.TESTFN + self.addCleanup(os_helper.unlink, testfn) + with open(testfn, 'w', encoding='utf-8') as f: + f.write(textwrap.dedent('''\ + import multiprocessing + def f(x): return x*x + if __name__ == '__main__': + with multiprocessing.Pool(200) as p: + print(sum(p.map(f, range(1000)))) + ''')) + rc, out, err = script_helper.assert_python_ok(testfn) + self.assertEqual("332833500", out.decode('utf-8').strip()) + self.assertFalse(err, msg=err.decode('utf-8')) + # # Mixins diff --git a/Lib/test/test_winapi.py b/Lib/test/test_winapi.py new file mode 100644 index 000000000000000..014aeea7239e2b1 --- /dev/null +++ b/Lib/test/test_winapi.py @@ -0,0 +1,94 @@ +# Test the Windows-only _winapi module + +import random +import threading +import time +import unittest +from test.support import import_helper + +_winapi = import_helper.import_module('_winapi', required_on=['win']) + +MAXIMUM_WAIT_OBJECTS = 64 +MAXIMUM_BATCHED_WAIT_OBJECTS = (MAXIMUM_WAIT_OBJECTS - 1) ** 2 + +class WinAPIBatchedWaitForMultipleObjectsTests(unittest.TestCase): + def _events_waitall_test(self, n): + evts = [_winapi.CreateEventW(0, False, False, None) for _ in range(n)] + + with self.assertRaises(TimeoutError): + _winapi.BatchedWaitForMultipleObjects(evts, True, 100) + + # Ensure no errors raised when all are triggered + for e in evts: + _winapi.SetEvent(e) + try: + _winapi.BatchedWaitForMultipleObjects(evts, True, 100) + except TimeoutError: + self.fail("expected wait to complete immediately") + + # Choose 8 events to set, distributed throughout the list, to make sure + # we don't always have them in the first chunk + chosen = [i * (len(evts) // 8) for i in range(8)] + + # Replace events with invalid handles to make sure we fail + for i in chosen: + old_evt = evts[i] + evts[i] = -1 + with self.assertRaises(OSError): + _winapi.BatchedWaitForMultipleObjects(evts, True, 100) + evts[i] = old_evt + + + def _events_waitany_test(self, n): + evts = [_winapi.CreateEventW(0, False, False, None) for _ in range(n)] + + with self.assertRaises(TimeoutError): + _winapi.BatchedWaitForMultipleObjects(evts, False, 100) + + # Choose 8 events to set, distributed throughout the list, to make sure + # we don't always have them in the first chunk + chosen = [i * (len(evts) // 8) for i in range(8)] + + # Trigger one by one. They are auto-reset events, so will only trigger once + for i in chosen: + with self.subTest(f"trigger event {i} of {len(evts)}"): + _winapi.SetEvent(evts[i]) + triggered = _winapi.BatchedWaitForMultipleObjects(evts, False, 10000) + self.assertSetEqual(set(triggered), {i}) + + # Trigger all at once. This may require multiple calls + for i in chosen: + _winapi.SetEvent(evts[i]) + triggered = set() + while len(triggered) < len(chosen): + triggered.update(_winapi.BatchedWaitForMultipleObjects(evts, False, 10000)) + self.assertSetEqual(triggered, set(chosen)) + + # Replace events with invalid handles to make sure we fail + for i in chosen: + with self.subTest(f"corrupt event {i} of {len(evts)}"): + old_evt = evts[i] + evts[i] = -1 + with self.assertRaises(OSError): + _winapi.BatchedWaitForMultipleObjects(evts, False, 100) + evts[i] = old_evt + + + def test_few_events_waitall(self): + self._events_waitall_test(16) + + def test_many_events_waitall(self): + self._events_waitall_test(256) + + def test_max_events_waitall(self): + self._events_waitall_test(MAXIMUM_BATCHED_WAIT_OBJECTS) + + + def test_few_events_waitany(self): + self._events_waitany_test(16) + + def test_many_events_waitany(self): + self._events_waitany_test(256) + + def test_max_events_waitany(self): + self._events_waitany_test(MAXIMUM_BATCHED_WAIT_OBJECTS) diff --git a/Misc/NEWS.d/next/Windows/2023-08-11-18-21-38.gh-issue-89240.dtSOLG.rst b/Misc/NEWS.d/next/Windows/2023-08-11-18-21-38.gh-issue-89240.dtSOLG.rst new file mode 100644 index 000000000000000..8ffe328b16598a0 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2023-08-11-18-21-38.gh-issue-89240.dtSOLG.rst @@ -0,0 +1 @@ +Allows :mod:`multiprocessing` to create pools of greater than 62 processes. diff --git a/Modules/_winapi.c b/Modules/_winapi.c index 5e5eb123c4ccfff..83a4ccd4802ae01 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -438,6 +438,39 @@ _winapi_ConnectNamedPipe_impl(PyObject *module, HANDLE handle, Py_RETURN_NONE; } +/*[clinic input] +_winapi.CreateEventW -> HANDLE + + security_attributes: LPSECURITY_ATTRIBUTES + manual_reset: BOOL + initial_state: BOOL + name: LPCWSTR(accept={str, NoneType}) +[clinic start generated code]*/ + +static HANDLE +_winapi_CreateEventW_impl(PyObject *module, + LPSECURITY_ATTRIBUTES security_attributes, + BOOL manual_reset, BOOL initial_state, + LPCWSTR name) +/*[clinic end generated code: output=2d4c7d5852ecb298 input=4187cee28ac763f8]*/ +{ + HANDLE handle; + + if (PySys_Audit("_winapi.CreateEventW", "bbu", manual_reset, initial_state, name) < 0) { + return INVALID_HANDLE_VALUE; + } + + Py_BEGIN_ALLOW_THREADS + handle = CreateEventW(security_attributes, manual_reset, initial_state, name); + Py_END_ALLOW_THREADS + + if (handle == INVALID_HANDLE_VALUE) { + PyErr_SetFromWindowsErr(0); + } + + return handle; +} + /*[clinic input] _winapi.CreateFile -> HANDLE @@ -674,6 +707,37 @@ _winapi_CreateJunction_impl(PyObject *module, LPCWSTR src_path, Py_RETURN_NONE; } +/*[clinic input] +_winapi.CreateMutexW -> HANDLE + + security_attributes: LPSECURITY_ATTRIBUTES + initial_owner: BOOL + name: LPCWSTR(accept={str, NoneType}) +[clinic start generated code]*/ + +static HANDLE +_winapi_CreateMutexW_impl(PyObject *module, + LPSECURITY_ATTRIBUTES security_attributes, + BOOL initial_owner, LPCWSTR name) +/*[clinic end generated code: output=31b9ee8fc37e49a5 input=7d54b921e723254a]*/ +{ + HANDLE handle; + + if (PySys_Audit("_winapi.CreateMutexW", "bu", initial_owner, name) < 0) { + return INVALID_HANDLE_VALUE; + } + + Py_BEGIN_ALLOW_THREADS + handle = CreateMutexW(security_attributes, initial_owner, name); + Py_END_ALLOW_THREADS + + if (handle == INVALID_HANDLE_VALUE) { + PyErr_SetFromWindowsErr(0); + } + + return handle; +} + /*[clinic input] _winapi.CreateNamedPipe -> HANDLE @@ -1590,6 +1654,67 @@ _winapi_UnmapViewOfFile_impl(PyObject *module, LPCVOID address) Py_RETURN_NONE; } +/*[clinic input] +_winapi.OpenEventW -> HANDLE + + desired_access: DWORD + inherit_handle: BOOL + name: LPCWSTR +[clinic start generated code]*/ + +static HANDLE +_winapi_OpenEventW_impl(PyObject *module, DWORD desired_access, + BOOL inherit_handle, LPCWSTR name) +/*[clinic end generated code: output=c4a45e95545a4bd2 input=dec26598748d35aa]*/ +{ + HANDLE handle; + + if (PySys_Audit("_winapi.OpenEventW", "Iu", desired_access, name) < 0) { + return INVALID_HANDLE_VALUE; + } + + Py_BEGIN_ALLOW_THREADS + handle = OpenEventW(desired_access, inherit_handle, name); + Py_END_ALLOW_THREADS + + if (handle == INVALID_HANDLE_VALUE) { + PyErr_SetFromWindowsErr(0); + } + + return handle; +} + + +/*[clinic input] +_winapi.OpenMutexW -> HANDLE + + desired_access: DWORD + inherit_handle: BOOL + name: LPCWSTR +[clinic start generated code]*/ + +static HANDLE +_winapi_OpenMutexW_impl(PyObject *module, DWORD desired_access, + BOOL inherit_handle, LPCWSTR name) +/*[clinic end generated code: output=dda39d7844397bf0 input=f3a7b466c5307712]*/ +{ + HANDLE handle; + + if (PySys_Audit("_winapi.OpenMutexW", "Iu", desired_access, name) < 0) { + return INVALID_HANDLE_VALUE; + } + + Py_BEGIN_ALLOW_THREADS + handle = OpenMutexW(desired_access, inherit_handle, name); + Py_END_ALLOW_THREADS + + if (handle == INVALID_HANDLE_VALUE) { + PyErr_SetFromWindowsErr(0); + } + + return handle; +} + /*[clinic input] _winapi.OpenFileMapping -> HANDLE @@ -1820,6 +1945,75 @@ _winapi_ReadFile_impl(PyObject *module, HANDLE handle, DWORD size, return Py_BuildValue("NI", buf, err); } +/*[clinic input] +_winapi.ReleaseMutex + + mutex: HANDLE +[clinic start generated code]*/ + +static PyObject * +_winapi_ReleaseMutex_impl(PyObject *module, HANDLE mutex) +/*[clinic end generated code: output=5b9001a72dd8af37 input=49e9d20de3559d84]*/ +{ + int err = 0; + + Py_BEGIN_ALLOW_THREADS + if (!ReleaseMutex(mutex)) { + err = GetLastError(); + } + Py_END_ALLOW_THREADS + if (err) { + return PyErr_SetFromWindowsErr(err); + } + Py_RETURN_NONE; +} + +/*[clinic input] +_winapi.ResetEvent + + event: HANDLE +[clinic start generated code]*/ + +static PyObject * +_winapi_ResetEvent_impl(PyObject *module, HANDLE event) +/*[clinic end generated code: output=81c8501d57c0530d input=e2d42d990322e87a]*/ +{ + int err = 0; + + Py_BEGIN_ALLOW_THREADS + if (!ResetEvent(event)) { + err = GetLastError(); + } + Py_END_ALLOW_THREADS + if (err) { + return PyErr_SetFromWindowsErr(err); + } + Py_RETURN_NONE; +} + +/*[clinic input] +_winapi.SetEvent + + event: HANDLE +[clinic start generated code]*/ + +static PyObject * +_winapi_SetEvent_impl(PyObject *module, HANDLE event) +/*[clinic end generated code: output=c18ba09eb9aa774d input=e660e830a37c09f8]*/ +{ + int err = 0; + + Py_BEGIN_ALLOW_THREADS + if (!SetEvent(event)) { + err = GetLastError(); + } + Py_END_ALLOW_THREADS + if (err) { + return PyErr_SetFromWindowsErr(err); + } + Py_RETURN_NONE; +} + /*[clinic input] _winapi.SetNamedPipeHandleState @@ -1942,6 +2136,310 @@ _winapi_WaitNamedPipe_impl(PyObject *module, LPCTSTR name, DWORD timeout) Py_RETURN_NONE; } + +typedef struct { + HANDLE handles[MAXIMUM_WAIT_OBJECTS]; + HANDLE cancel_event; + DWORD handle_base; + DWORD handle_count; + HANDLE thread; + volatile DWORD result; +} BatchedWaitData; + +static DWORD WINAPI +_batched_WaitForMultipleObjects_thread(LPVOID param) +{ + BatchedWaitData *data = (BatchedWaitData *)param; + data->result = WaitForMultipleObjects( + data->handle_count, + data->handles, + FALSE, + INFINITE + ); + if (data->result == WAIT_FAILED) { + DWORD err = GetLastError(); + SetEvent(data->cancel_event); + return err; + } else if (data->result >= WAIT_ABANDONED_0 && data->result < WAIT_ABANDONED_0 + MAXIMUM_WAIT_OBJECTS) { + data->result = WAIT_FAILED; + SetEvent(data->cancel_event); + return ERROR_ABANDONED_WAIT_0; + } + return 0; +} + +/*[clinic input] +_winapi.BatchedWaitForMultipleObjects + + handle_seq: object + wait_all: BOOL + milliseconds: DWORD(c_default='INFINITE') = _winapi.INFINITE + +Supports a larger number of handles than WaitForMultipleObjects + +Note that the handles may be waited on other threads, which could cause +issues for objects like mutexes that become associated with the thread +that was waiting for them. Objects may also be left signalled, even if +the wait fails. + +It is recommended to use WaitForMultipleObjects whenever possible, and +only switch to BatchedWaitForMultipleObjects for scenarios where you +control all the handles involved, such as your own thread pool or +files, and all wait objects are left unmodified by a wait (for example, +manual reset events, threads, and files/pipes). + +Overlapped handles returned from this module use manual reset events. +[clinic start generated code]*/ + +static PyObject * +_winapi_BatchedWaitForMultipleObjects_impl(PyObject *module, + PyObject *handle_seq, + BOOL wait_all, DWORD milliseconds) +/*[clinic end generated code: output=d21c1a4ad0a252fd input=7e196f29005dc77b]*/ +{ + Py_ssize_t thread_count = 0, handle_count = 0, i, j; + Py_ssize_t nhandles; + BatchedWaitData *thread_data[MAXIMUM_WAIT_OBJECTS]; + HANDLE handles[MAXIMUM_WAIT_OBJECTS]; + HANDLE sigint_event = NULL; + HANDLE cancel_event = NULL; + DWORD result; + + const Py_ssize_t _MAXIMUM_TOTAL_OBJECTS = (MAXIMUM_WAIT_OBJECTS - 1) * (MAXIMUM_WAIT_OBJECTS - 1); + + if (!PySequence_Check(handle_seq)) { + PyErr_Format(PyExc_TypeError, + "sequence type expected, got '%s'", + Py_TYPE(handle_seq)->tp_name); + return NULL; + } + nhandles = PySequence_Length(handle_seq); + if (nhandles == -1) { + return NULL; + } + if (nhandles == 0) { + return wait_all ? Py_NewRef(Py_None) : PyList_New(0); + } + + /* If this is the main thread then make the wait interruptible + by Ctrl-C. When waiting for *all* handles, it is only checked + in between batches. */ + if (_PyOS_IsMainThread()) { + sigint_event = _PyOS_SigintEvent(); + assert(sigint_event != NULL); + } + + if (nhandles < 0 || nhandles > _MAXIMUM_TOTAL_OBJECTS) { + PyErr_Format(PyExc_ValueError, + "need at most %zd handles, got a sequence of length %zd", + _MAXIMUM_TOTAL_OBJECTS, nhandles); + return NULL; + } + + if (!wait_all) { + cancel_event = CreateEventW(NULL, TRUE, FALSE, NULL); + if (!cancel_event) { + PyErr_SetExcFromWindowsErr(PyExc_OSError, 0); + return NULL; + } + } + + i = 0; + while (i < nhandles) { + BatchedWaitData *data = (BatchedWaitData*)PyMem_Malloc(sizeof(BatchedWaitData)); + if (!data) { + goto error; + } + thread_data[thread_count++] = data; + data->thread = NULL; + data->cancel_event = cancel_event; + data->handle_base = Py_SAFE_DOWNCAST(i, Py_ssize_t, DWORD); + data->handle_count = Py_SAFE_DOWNCAST(nhandles - i, Py_ssize_t, DWORD); + if (data->handle_count > MAXIMUM_WAIT_OBJECTS - 1) { + data->handle_count = MAXIMUM_WAIT_OBJECTS - 1; + } + for (j = 0; j < data->handle_count; ++i, ++j) { + PyObject *v = PySequence_GetItem(handle_seq, i); + if (!v || !PyArg_Parse(v, F_HANDLE, &data->handles[j])) { + Py_XDECREF(v); + goto error; + } + Py_DECREF(v); + } + if (!wait_all) { + data->handles[data->handle_count++] = cancel_event; + } + } + + DWORD err = 0; + + /* We need to use different strategies when waiting for ALL handles + as opposed to ANY handle. This is because there is no way to + (safely) interrupt a thread that is waiting for all handles in a + group. So for ALL handles, we loop over each set and wait. For + ANY handle, we use threads and wait on them. */ + if (wait_all) { + Py_BEGIN_ALLOW_THREADS + long long deadline = 0; + if (milliseconds != INFINITE) { + deadline = (long long)GetTickCount64() + milliseconds; + } + + for (i = 0; !err && i < thread_count; ++i) { + DWORD timeout = milliseconds; + if (deadline) { + long long time_to_deadline = deadline - GetTickCount64(); + if (time_to_deadline <= 0) { + err = WAIT_TIMEOUT; + break; + } else if (time_to_deadline < UINT_MAX) { + timeout = (DWORD)time_to_deadline; + } + } + result = WaitForMultipleObjects(thread_data[i]->handle_count, + thread_data[i]->handles, TRUE, timeout); + // ABANDONED is not possible here because we own all the handles + if (result == WAIT_FAILED) { + err = GetLastError(); + } else if (result == WAIT_TIMEOUT) { + err = WAIT_TIMEOUT; + } + + if (!err && sigint_event) { + result = WaitForSingleObject(sigint_event, 0); + if (result == WAIT_OBJECT_0) { + err = ERROR_CONTROL_C_EXIT; + } else if (result == WAIT_FAILED) { + err = GetLastError(); + } + } + } + + CloseHandle(cancel_event); + + Py_END_ALLOW_THREADS + } else { + Py_BEGIN_ALLOW_THREADS + + for (i = 0; i < thread_count; ++i) { + BatchedWaitData *data = thread_data[i]; + data->thread = CreateThread( + NULL, + 1, // smallest possible initial stack + _batched_WaitForMultipleObjects_thread, + (LPVOID)data, + CREATE_SUSPENDED, + NULL + ); + if (!data->thread) { + err = GetLastError(); + break; + } + handles[handle_count++] = data->thread; + } + Py_END_ALLOW_THREADS + + if (err) { + PyErr_SetExcFromWindowsErr(PyExc_OSError, err); + goto error; + } + if (handle_count > MAXIMUM_WAIT_OBJECTS - 1) { + // basically an assert, but stronger + PyErr_SetString(PyExc_SystemError, "allocated too many wait objects"); + goto error; + } + + Py_BEGIN_ALLOW_THREADS + + // Once we start resuming threads, can no longer "goto error" + for (i = 0; i < thread_count; ++i) { + ResumeThread(thread_data[i]->thread); + } + if (sigint_event) { + handles[handle_count++] = sigint_event; + } + result = WaitForMultipleObjects((DWORD)handle_count, handles, wait_all, milliseconds); + // ABANDONED is not possible here because we own all the handles + if (result == WAIT_FAILED) { + err = GetLastError(); + } else if (result == WAIT_TIMEOUT) { + err = WAIT_TIMEOUT; + } else if (sigint_event && result == WAIT_OBJECT_0 + handle_count) { + err = ERROR_CONTROL_C_EXIT; + } + + SetEvent(cancel_event); + + // Wait for all threads to finish before we start freeing their memory + if (sigint_event) { + handle_count -= 1; + } + WaitForMultipleObjects((DWORD)handle_count, handles, TRUE, INFINITE); + + for (i = 0; i < thread_count; ++i) { + if (!err && thread_data[i]->result == WAIT_FAILED) { + if (!GetExitCodeThread(thread_data[i]->thread, &err)) { + err = GetLastError(); + } + } + CloseHandle(thread_data[i]->thread); + } + + CloseHandle(cancel_event); + + Py_END_ALLOW_THREADS + + } + + PyObject *triggered_indices; + if (sigint_event != NULL && err == ERROR_CONTROL_C_EXIT) { + errno = EINTR; + PyErr_SetFromErrno(PyExc_OSError); + triggered_indices = NULL; + } else if (err) { + PyErr_SetExcFromWindowsErr(PyExc_OSError, err); + triggered_indices = NULL; + } else if (wait_all) { + triggered_indices = Py_NewRef(Py_None); + } else { + triggered_indices = PyList_New(0); + if (triggered_indices) { + for (i = 0; i < thread_count; ++i) { + Py_ssize_t triggered = (Py_ssize_t)thread_data[i]->result - WAIT_OBJECT_0; + if (triggered >= 0 && triggered < thread_data[i]->handle_count - 1) { + PyObject *v = PyLong_FromSsize_t(thread_data[i]->handle_base + triggered); + if (!v || PyList_Append(triggered_indices, v) < 0) { + Py_XDECREF(v); + Py_CLEAR(triggered_indices); + break; + } + Py_DECREF(v); + } + } + } + } + + for (i = 0; i < thread_count; ++i) { + PyMem_Free((void *)thread_data[i]); + } + + return triggered_indices; + +error: + // We should only enter here before any threads start running. + // Once we start resuming threads, different cleanup is required + CloseHandle(cancel_event); + while (--thread_count >= 0) { + HANDLE t = thread_data[thread_count]->thread; + if (t) { + TerminateThread(t, WAIT_ABANDONED_0); + CloseHandle(t); + } + PyMem_Free((void *)thread_data[thread_count]); + } + return NULL; +} + /*[clinic input] _winapi.WaitForMultipleObjects @@ -2335,8 +2833,10 @@ _winapi_CopyFile2_impl(PyObject *module, LPCWSTR existing_file_name, static PyMethodDef winapi_functions[] = { _WINAPI_CLOSEHANDLE_METHODDEF _WINAPI_CONNECTNAMEDPIPE_METHODDEF + _WINAPI_CREATEEVENTW_METHODDEF _WINAPI_CREATEFILE_METHODDEF _WINAPI_CREATEFILEMAPPING_METHODDEF + _WINAPI_CREATEMUTEXW_METHODDEF _WINAPI_CREATENAMEDPIPE_METHODDEF _WINAPI_CREATEPIPE_METHODDEF _WINAPI_CREATEPROCESS_METHODDEF @@ -2350,17 +2850,23 @@ static PyMethodDef winapi_functions[] = { _WINAPI_GETSTDHANDLE_METHODDEF _WINAPI_GETVERSION_METHODDEF _WINAPI_MAPVIEWOFFILE_METHODDEF + _WINAPI_OPENEVENTW_METHODDEF _WINAPI_OPENFILEMAPPING_METHODDEF + _WINAPI_OPENMUTEXW_METHODDEF _WINAPI_OPENPROCESS_METHODDEF _WINAPI_PEEKNAMEDPIPE_METHODDEF _WINAPI_LCMAPSTRINGEX_METHODDEF _WINAPI_READFILE_METHODDEF + _WINAPI_RELEASEMUTEX_METHODDEF + _WINAPI_RESETEVENT_METHODDEF + _WINAPI_SETEVENT_METHODDEF _WINAPI_SETNAMEDPIPEHANDLESTATE_METHODDEF _WINAPI_TERMINATEPROCESS_METHODDEF _WINAPI_UNMAPVIEWOFFILE_METHODDEF _WINAPI_VIRTUALQUERYSIZE_METHODDEF _WINAPI_WAITNAMEDPIPE_METHODDEF _WINAPI_WAITFORMULTIPLEOBJECTS_METHODDEF + _WINAPI_BATCHEDWAITFORMULTIPLEOBJECTS_METHODDEF _WINAPI_WAITFORSINGLEOBJECT_METHODDEF _WINAPI_WRITEFILE_METHODDEF _WINAPI_GETACP_METHODDEF diff --git a/Modules/clinic/_winapi.c.h b/Modules/clinic/_winapi.c.h index d1052f38919ddef..468457e624c6916 100644 --- a/Modules/clinic/_winapi.c.h +++ b/Modules/clinic/_winapi.c.h @@ -151,6 +151,76 @@ _winapi_ConnectNamedPipe(PyObject *module, PyObject *const *args, Py_ssize_t nar return return_value; } +PyDoc_STRVAR(_winapi_CreateEventW__doc__, +"CreateEventW($module, /, security_attributes, manual_reset,\n" +" initial_state, name)\n" +"--\n" +"\n"); + +#define _WINAPI_CREATEEVENTW_METHODDEF \ + {"CreateEventW", _PyCFunction_CAST(_winapi_CreateEventW), METH_FASTCALL|METH_KEYWORDS, _winapi_CreateEventW__doc__}, + +static HANDLE +_winapi_CreateEventW_impl(PyObject *module, + LPSECURITY_ATTRIBUTES security_attributes, + BOOL manual_reset, BOOL initial_state, + LPCWSTR name); + +static PyObject * +_winapi_CreateEventW(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 4 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(security_attributes), &_Py_ID(manual_reset), &_Py_ID(initial_state), &_Py_ID(name), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"security_attributes", "manual_reset", "initial_state", "name", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .format = "" F_POINTER "iiO&:CreateEventW", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + LPSECURITY_ATTRIBUTES security_attributes; + BOOL manual_reset; + BOOL initial_state; + LPCWSTR name = NULL; + HANDLE _return_value; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &security_attributes, &manual_reset, &initial_state, _PyUnicode_WideCharString_Opt_Converter, &name)) { + goto exit; + } + _return_value = _winapi_CreateEventW_impl(module, security_attributes, manual_reset, initial_state, name); + if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { + goto exit; + } + if (_return_value == NULL) { + Py_RETURN_NONE; + } + return_value = HANDLE_TO_PYNUM(_return_value); + +exit: + /* Cleanup for name */ + PyMem_Free((void *)name); + + return return_value; +} + PyDoc_STRVAR(_winapi_CreateFile__doc__, "CreateFile($module, file_name, desired_access, share_mode,\n" " security_attributes, creation_disposition,\n" @@ -297,6 +367,73 @@ _winapi_CreateJunction(PyObject *module, PyObject *const *args, Py_ssize_t nargs return return_value; } +PyDoc_STRVAR(_winapi_CreateMutexW__doc__, +"CreateMutexW($module, /, security_attributes, initial_owner, name)\n" +"--\n" +"\n"); + +#define _WINAPI_CREATEMUTEXW_METHODDEF \ + {"CreateMutexW", _PyCFunction_CAST(_winapi_CreateMutexW), METH_FASTCALL|METH_KEYWORDS, _winapi_CreateMutexW__doc__}, + +static HANDLE +_winapi_CreateMutexW_impl(PyObject *module, + LPSECURITY_ATTRIBUTES security_attributes, + BOOL initial_owner, LPCWSTR name); + +static PyObject * +_winapi_CreateMutexW(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 3 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(security_attributes), &_Py_ID(initial_owner), &_Py_ID(name), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"security_attributes", "initial_owner", "name", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .format = "" F_POINTER "iO&:CreateMutexW", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + LPSECURITY_ATTRIBUTES security_attributes; + BOOL initial_owner; + LPCWSTR name = NULL; + HANDLE _return_value; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &security_attributes, &initial_owner, _PyUnicode_WideCharString_Opt_Converter, &name)) { + goto exit; + } + _return_value = _winapi_CreateMutexW_impl(module, security_attributes, initial_owner, name); + if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { + goto exit; + } + if (_return_value == NULL) { + Py_RETURN_NONE; + } + return_value = HANDLE_TO_PYNUM(_return_value); + +exit: + /* Cleanup for name */ + PyMem_Free((void *)name); + + return return_value; +} + PyDoc_STRVAR(_winapi_CreateNamedPipe__doc__, "CreateNamedPipe($module, name, open_mode, pipe_mode, max_instances,\n" " out_buffer_size, in_buffer_size, default_timeout,\n" @@ -771,6 +908,138 @@ _winapi_UnmapViewOfFile(PyObject *module, PyObject *arg) return return_value; } +PyDoc_STRVAR(_winapi_OpenEventW__doc__, +"OpenEventW($module, /, desired_access, inherit_handle, name)\n" +"--\n" +"\n"); + +#define _WINAPI_OPENEVENTW_METHODDEF \ + {"OpenEventW", _PyCFunction_CAST(_winapi_OpenEventW), METH_FASTCALL|METH_KEYWORDS, _winapi_OpenEventW__doc__}, + +static HANDLE +_winapi_OpenEventW_impl(PyObject *module, DWORD desired_access, + BOOL inherit_handle, LPCWSTR name); + +static PyObject * +_winapi_OpenEventW(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 3 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(desired_access), &_Py_ID(inherit_handle), &_Py_ID(name), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"desired_access", "inherit_handle", "name", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .format = "kiO&:OpenEventW", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + DWORD desired_access; + BOOL inherit_handle; + LPCWSTR name = NULL; + HANDLE _return_value; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &desired_access, &inherit_handle, _PyUnicode_WideCharString_Converter, &name)) { + goto exit; + } + _return_value = _winapi_OpenEventW_impl(module, desired_access, inherit_handle, name); + if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { + goto exit; + } + if (_return_value == NULL) { + Py_RETURN_NONE; + } + return_value = HANDLE_TO_PYNUM(_return_value); + +exit: + /* Cleanup for name */ + PyMem_Free((void *)name); + + return return_value; +} + +PyDoc_STRVAR(_winapi_OpenMutexW__doc__, +"OpenMutexW($module, /, desired_access, inherit_handle, name)\n" +"--\n" +"\n"); + +#define _WINAPI_OPENMUTEXW_METHODDEF \ + {"OpenMutexW", _PyCFunction_CAST(_winapi_OpenMutexW), METH_FASTCALL|METH_KEYWORDS, _winapi_OpenMutexW__doc__}, + +static HANDLE +_winapi_OpenMutexW_impl(PyObject *module, DWORD desired_access, + BOOL inherit_handle, LPCWSTR name); + +static PyObject * +_winapi_OpenMutexW(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 3 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(desired_access), &_Py_ID(inherit_handle), &_Py_ID(name), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"desired_access", "inherit_handle", "name", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .format = "kiO&:OpenMutexW", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + DWORD desired_access; + BOOL inherit_handle; + LPCWSTR name = NULL; + HANDLE _return_value; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &desired_access, &inherit_handle, _PyUnicode_WideCharString_Converter, &name)) { + goto exit; + } + _return_value = _winapi_OpenMutexW_impl(module, desired_access, inherit_handle, name); + if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { + goto exit; + } + if (_return_value == NULL) { + Py_RETURN_NONE; + } + return_value = HANDLE_TO_PYNUM(_return_value); + +exit: + /* Cleanup for name */ + PyMem_Free((void *)name); + + return return_value; +} + PyDoc_STRVAR(_winapi_OpenFileMapping__doc__, "OpenFileMapping($module, desired_access, inherit_handle, name, /)\n" "--\n" @@ -991,6 +1260,162 @@ _winapi_ReadFile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyOb return return_value; } +PyDoc_STRVAR(_winapi_ReleaseMutex__doc__, +"ReleaseMutex($module, /, mutex)\n" +"--\n" +"\n"); + +#define _WINAPI_RELEASEMUTEX_METHODDEF \ + {"ReleaseMutex", _PyCFunction_CAST(_winapi_ReleaseMutex), METH_FASTCALL|METH_KEYWORDS, _winapi_ReleaseMutex__doc__}, + +static PyObject * +_winapi_ReleaseMutex_impl(PyObject *module, HANDLE mutex); + +static PyObject * +_winapi_ReleaseMutex(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(mutex), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"mutex", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .format = "" F_HANDLE ":ReleaseMutex", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + HANDLE mutex; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &mutex)) { + goto exit; + } + return_value = _winapi_ReleaseMutex_impl(module, mutex); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_ResetEvent__doc__, +"ResetEvent($module, /, event)\n" +"--\n" +"\n"); + +#define _WINAPI_RESETEVENT_METHODDEF \ + {"ResetEvent", _PyCFunction_CAST(_winapi_ResetEvent), METH_FASTCALL|METH_KEYWORDS, _winapi_ResetEvent__doc__}, + +static PyObject * +_winapi_ResetEvent_impl(PyObject *module, HANDLE event); + +static PyObject * +_winapi_ResetEvent(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(event), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"event", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .format = "" F_HANDLE ":ResetEvent", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + HANDLE event; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &event)) { + goto exit; + } + return_value = _winapi_ResetEvent_impl(module, event); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_SetEvent__doc__, +"SetEvent($module, /, event)\n" +"--\n" +"\n"); + +#define _WINAPI_SETEVENT_METHODDEF \ + {"SetEvent", _PyCFunction_CAST(_winapi_SetEvent), METH_FASTCALL|METH_KEYWORDS, _winapi_SetEvent__doc__}, + +static PyObject * +_winapi_SetEvent_impl(PyObject *module, HANDLE event); + +static PyObject * +_winapi_SetEvent(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(event), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"event", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .format = "" F_HANDLE ":SetEvent", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + HANDLE event; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &event)) { + goto exit; + } + return_value = _winapi_SetEvent_impl(module, event); + +exit: + return return_value; +} + PyDoc_STRVAR(_winapi_SetNamedPipeHandleState__doc__, "SetNamedPipeHandleState($module, named_pipe, mode,\n" " max_collection_count, collect_data_timeout, /)\n" @@ -1114,6 +1539,77 @@ _winapi_WaitNamedPipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return return_value; } +PyDoc_STRVAR(_winapi_BatchedWaitForMultipleObjects__doc__, +"BatchedWaitForMultipleObjects($module, /, handle_seq, wait_all,\n" +" milliseconds=_winapi.INFINITE)\n" +"--\n" +"\n" +"Supports a larger number of handles than WaitForMultipleObjects\n" +"\n" +"Note that the handles may be waited on other threads, which could cause\n" +"issues for objects like mutexes that become associated with the thread\n" +"that was waiting for them. Objects may also be left signalled, even if\n" +"the wait fails.\n" +"\n" +"It is recommended to use WaitForMultipleObjects whenever possible, and\n" +"only switch to BatchedWaitForMultipleObjects for scenarios where you\n" +"control all the handles involved, such as your own thread pool or\n" +"files, and all wait objects are left unmodified by a wait (for example,\n" +"manual reset events, threads, and files/pipes).\n" +"\n" +"Overlapped handles returned from this module use manual reset events."); + +#define _WINAPI_BATCHEDWAITFORMULTIPLEOBJECTS_METHODDEF \ + {"BatchedWaitForMultipleObjects", _PyCFunction_CAST(_winapi_BatchedWaitForMultipleObjects), METH_FASTCALL|METH_KEYWORDS, _winapi_BatchedWaitForMultipleObjects__doc__}, + +static PyObject * +_winapi_BatchedWaitForMultipleObjects_impl(PyObject *module, + PyObject *handle_seq, + BOOL wait_all, DWORD milliseconds); + +static PyObject * +_winapi_BatchedWaitForMultipleObjects(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 3 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(handle_seq), &_Py_ID(wait_all), &_Py_ID(milliseconds), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"handle_seq", "wait_all", "milliseconds", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .format = "Oi|k:BatchedWaitForMultipleObjects", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *handle_seq; + BOOL wait_all; + DWORD milliseconds = INFINITE; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &handle_seq, &wait_all, &milliseconds)) { + goto exit; + } + return_value = _winapi_BatchedWaitForMultipleObjects_impl(module, handle_seq, wait_all, milliseconds); + +exit: + return return_value; +} + PyDoc_STRVAR(_winapi_WaitForMultipleObjects__doc__, "WaitForMultipleObjects($module, handle_seq, wait_flag,\n" " milliseconds=_winapi.INFINITE, /)\n" @@ -1482,4 +1978,4 @@ _winapi_CopyFile2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyO return return_value; } -/*[clinic end generated code: output=2350d4f2275d3a6f input=a9049054013a1b77]*/ +/*[clinic end generated code: output=1f5bbcfa8d1847c5 input=a9049054013a1b77]*/ diff --git a/Objects/exceptions.c b/Objects/exceptions.c index cff55d05163b6ba..3df3a9b3b1a2530 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -3539,7 +3539,6 @@ SimpleExtendsException(PyExc_Warning, ResourceWarning, #undef EOPNOTSUPP #undef EPROTONOSUPPORT #undef EPROTOTYPE -#undef ETIMEDOUT #undef EWOULDBLOCK #if defined(WSAEALREADY) && !defined(EALREADY) @@ -3560,9 +3559,6 @@ SimpleExtendsException(PyExc_Warning, ResourceWarning, #if defined(WSAESHUTDOWN) && !defined(ESHUTDOWN) #define ESHUTDOWN WSAESHUTDOWN #endif -#if defined(WSAETIMEDOUT) && !defined(ETIMEDOUT) -#define ETIMEDOUT WSAETIMEDOUT -#endif #if defined(WSAEWOULDBLOCK) && !defined(EWOULDBLOCK) #define EWOULDBLOCK WSAEWOULDBLOCK #endif @@ -3747,6 +3743,9 @@ _PyExc_InitState(PyInterpreterState *interp) #endif ADD_ERRNO(ProcessLookupError, ESRCH); ADD_ERRNO(TimeoutError, ETIMEDOUT); +#ifdef WSAETIMEDOUT + ADD_ERRNO(TimeoutError, WSAETIMEDOUT); +#endif return _PyStatus_OK(); diff --git a/PC/errmap.h b/PC/errmap.h index a7489ab75c65618..a064ecb80b1ed93 100644 --- a/PC/errmap.h +++ b/PC/errmap.h @@ -129,6 +129,9 @@ winerror_to_errno(int winerror) case ERROR_NO_UNICODE_TRANSLATION: // 1113 return EILSEQ; + case WAIT_TIMEOUT: // 258 + return ETIMEDOUT; + case ERROR_INVALID_FUNCTION: // 1 case ERROR_INVALID_ACCESS: // 12 case ERROR_INVALID_DATA: // 13 From 0a6e1a4119864bec0247b04a5c99fdd9799cd8eb Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 13 Feb 2024 08:31:49 +0200 Subject: [PATCH 241/507] Update "Using Python on a Mac" (#115024) --- Doc/conf.py | 4 ++ Doc/using/mac.rst | 105 ++++++++++++++++++++++++++-------------------- 2 files changed, 63 insertions(+), 46 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index aa7f85bc1b3efa8..677d139046e5d07 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -64,6 +64,10 @@ import patchlevel version, release = patchlevel.get_version_info() +rst_epilog = f""" +.. |python_version_literal| replace:: ``Python {version}`` +""" + # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: today = '' diff --git a/Doc/using/mac.rst b/Doc/using/mac.rst index eb1413af2cbc3df..e99993238895f91 100644 --- a/Doc/using/mac.rst +++ b/Doc/using/mac.rst @@ -10,41 +10,46 @@ Using Python on a Mac Python on a Mac running macOS is in principle very similar to Python on any other Unix platform, but there are a number of additional features such as -the IDE and the Package Manager that are worth pointing out. +the integrated development environment (IDE) and the Package Manager that are +worth pointing out. + .. _getting-osx: +.. _getting-and-installing-macpython: -Getting and Installing MacPython -================================ +Getting and Installing Python +============================= macOS used to come with Python 2.7 pre-installed between versions 10.8 and `12.3 <https://developer.apple.com/documentation/macos-release-notes/macos-12_3-release-notes#Python>`_. -You are invited to install the most recent version of Python 3 from the Python -website (https://www.python.org). A current "universal binary" build of Python, -which runs natively on the Mac's new Intel and legacy PPC CPU's, is available -there. +You are invited to install the most recent version of Python 3 from the `Python +website <https://www.python.org/downloads/macos/>`__. +A current "universal2 binary" build of Python, which runs natively on the Mac's +new Apple Silicon and legacy Intel processors, is available there. What you get after installing is a number of things: -* A :file:`Python 3.12` folder in your :file:`Applications` folder. In here +* A |python_version_literal| folder in your :file:`Applications` folder. In here you find IDLE, the development environment that is a standard part of official - Python distributions; and PythonLauncher, which handles double-clicking Python + Python distributions; and :program:`Python Launcher`, which handles double-clicking Python scripts from the Finder. * A framework :file:`/Library/Frameworks/Python.framework`, which includes the Python executable and libraries. The installer adds this location to your shell - path. To uninstall MacPython, you can simply remove these three things. A - symlink to the Python executable is placed in /usr/local/bin/. - -The Apple-provided build of Python is installed in -:file:`/System/Library/Frameworks/Python.framework` and :file:`/usr/bin/python`, -respectively. You should never modify or delete these, as they are -Apple-controlled and are used by Apple- or third-party software. Remember that -if you choose to install a newer Python version from python.org, you will have -two different but functional Python installations on your computer, so it will -be important that your paths and usages are consistent with what you want to do. - -IDLE includes a help menu that allows you to access Python documentation. If you + path. To uninstall Python, you can remove these three things. A + symlink to the Python executable is placed in :file:`/usr/local/bin/`. + +.. note:: + + On macOS 10.8-12.3, the Apple-provided build of Python is installed in + :file:`/System/Library/Frameworks/Python.framework` and :file:`/usr/bin/python`, + respectively. You should never modify or delete these, as they are + Apple-controlled and are used by Apple- or third-party software. Remember that + if you choose to install a newer Python version from python.org, you will have + two different but functional Python installations on your computer, so it will + be important that your paths and usages are consistent with what you want to do. + +IDLE includes a Help menu that allows you to access Python documentation. If you are completely new to Python you should start reading the tutorial introduction in that document. @@ -56,29 +61,29 @@ How to run a Python script -------------------------- Your best way to get started with Python on macOS is through the IDLE -integrated development environment, see section :ref:`ide` and use the Help menu +integrated development environment; see section :ref:`ide` and use the Help menu when the IDE is running. If you want to run Python scripts from the Terminal window command line or from the Finder you first need an editor to create your script. macOS comes with a -number of standard Unix command line editors, :program:`vim` and -:program:`emacs` among them. If you want a more Mac-like editor, -:program:`BBEdit` or :program:`TextWrangler` from Bare Bones Software (see -http://www.barebones.com/products/bbedit/index.html) are good choices, as is -:program:`TextMate` (see https://macromates.com/). Other editors include -:program:`Gvim` (https://macvim.org/macvim/) and :program:`Aquamacs` -(http://aquamacs.org/). +number of standard Unix command line editors, :program:`vim` +:program:`nano` among them. If you want a more Mac-like editor, +:program:`BBEdit` from Bare Bones Software (see +https://www.barebones.com/products/bbedit/index.html) are good choices, as is +:program:`TextMate` (see https://macromates.com). Other editors include +:program:`MacVim` (https://macvim.org) and :program:`Aquamacs` +(https://aquamacs.org). To run your script from the Terminal window you must make sure that :file:`/usr/local/bin` is in your shell search path. To run your script from the Finder you have two options: -* Drag it to :program:`PythonLauncher` +* Drag it to :program:`Python Launcher`. -* Select :program:`PythonLauncher` as the default application to open your - script (or any .py script) through the finder Info window and double-click it. - :program:`PythonLauncher` has various preferences to control how your script is +* Select :program:`Python Launcher` as the default application to open your + script (or any ``.py`` script) through the finder Info window and double-click it. + :program:`Python Launcher` has various preferences to control how your script is launched. Option-dragging allows you to change these for one invocation, or use its Preferences menu to change things globally. @@ -103,10 +108,11 @@ Python on macOS honors all standard Unix environment variables such as :envvar:`PYTHONPATH`, but setting these variables for programs started from the Finder is non-standard as the Finder does not read your :file:`.profile` or :file:`.cshrc` at startup. You need to create a file -:file:`~/.MacOSX/environment.plist`. See Apple's Technical Document QA1067 for -details. +:file:`~/.MacOSX/environment.plist`. See Apple's +`Technical Q&A QA1067 <https://developer.apple.com/library/archive/qa/qa1067/_index.html>`__ +for details. -For more information on installation Python packages in MacPython, see section +For more information on installation Python packages, see section :ref:`mac-package-manager`. @@ -115,9 +121,9 @@ For more information on installation Python packages in MacPython, see section The IDE ======= -MacPython ships with the standard IDLE development environment. A good +Python ships with the standard IDLE development environment. A good introduction to using IDLE can be found at -http://www.hashcollision.org/hkn/python/idle_intro/index.html. +https://www.hashcollision.org/hkn/python/idle_intro/index.html. .. _mac-package-manager: @@ -130,8 +136,10 @@ This section has moved to the `Python Packaging User Guide`_. .. _Python Packaging User Guide: https://packaging.python.org/en/latest/tutorials/installing-packages/ -GUI Programming on the Mac -========================== +.. _gui-programming-on-the-mac: + +GUI Programming +=============== There are several options for building GUI applications on the Mac with Python. @@ -151,20 +159,25 @@ macOS. Packages and documentation are available from https://www.wxpython.org. macOS. More information can be found at https://riverbankcomputing.com/software/pyqt/intro. +*PySide* is another cross-platform Qt-based toolkit. More information at +https://www.qt.io/qt-for-python. + -Distributing Python Applications on the Mac -=========================================== +.. _distributing-python-applications-on-the-mac: + +Distributing Python Applications +================================ The standard tool for deploying standalone Python applications on the Mac is -:program:`py2app`. More information on installing and using py2app can be found -at https://pypi.org/project/py2app/. +:program:`py2app`. More information on installing and using :program:`py2app` +can be found at https://pypi.org/project/py2app/. Other Resources =============== -The MacPython mailing list is an excellent support resource for Python users and -developers on the Mac: +The Pythonmac-SIG mailing list is an excellent support resource for Python users +and developers on the Mac: https://www.python.org/community/sigs/current/pythonmac-sig/ From d823c235495e69fb4c1286b4ed751731bb31bda9 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak <felisiak.mariusz@gmail.com> Date: Tue, 13 Feb 2024 09:47:40 +0100 Subject: [PATCH 242/507] gh-115032: Update DictConfigurator.configure_formatter() comment about `fmt` retry. (GH-115303) --- Lib/logging/config.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Lib/logging/config.py b/Lib/logging/config.py index de06090942d9658..ea37dd7544564a2 100644 --- a/Lib/logging/config.py +++ b/Lib/logging/config.py @@ -667,10 +667,9 @@ def configure_formatter(self, config): except TypeError as te: if "'format'" not in str(te): raise - #Name of parameter changed from fmt to format. - #Retry with old name. - #This is so that code can be used with older Python versions - #(e.g. by Django) + # logging.Formatter and its subclasses expect the `fmt` + # parameter instead of `format`. Retry passing configuration + # with `fmt`. config['fmt'] = config.pop('format') config['()'] = factory result = self.configure_custom(config) From ca3604a3e33d833ef698b44a4b82c5bc8c771fcb Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Tue, 13 Feb 2024 12:21:20 +0200 Subject: [PATCH 243/507] gh-115252: Fix test_enum with -OO mode again (GH-115334) --- Lib/test/test_enum.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 5d7dae8829574b9..61060f3dc29fd46 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -4905,15 +4905,15 @@ class Color(enum.Enum) | value | | ---------------------------------------------------------------------- - | Methods inherited from enum.EnumType: + | Static methods inherited from enum.EnumType: | - | __contains__(value) from enum.EnumType + | __contains__(value) | - | __getitem__(name) from enum.EnumType + | __getitem__(name) | - | __iter__() from enum.EnumType + | __iter__() | - | __len__() from enum.EnumType + | __len__() | | ---------------------------------------------------------------------- | Readonly properties inherited from enum.EnumType: From ccc76c3e88647e416184bb1f5210b4e8946ae358 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Tue, 13 Feb 2024 13:40:40 +0300 Subject: [PATCH 244/507] gh-108303: Move all `pydoc` related test files to new `test.test_pydoc` package (#114506) --- Lib/pydoc.py | 2 +- Lib/test/libregrtest/findtests.py | 1 + Lib/test/test_pydoc/__init__.py | 6 +++ Lib/test/{ => test_pydoc}/pydoc_mod.py | 0 Lib/test/{ => test_pydoc}/pydocfodder.py | 0 Lib/test/{ => test_pydoc}/test_pydoc.py | 51 ++++++++++++------------ Makefile.pre.in | 1 + 7 files changed, 34 insertions(+), 27 deletions(-) create mode 100644 Lib/test/test_pydoc/__init__.py rename Lib/test/{ => test_pydoc}/pydoc_mod.py (100%) rename Lib/test/{ => test_pydoc}/pydocfodder.py (100%) rename Lib/test/{ => test_pydoc}/test_pydoc.py (98%) diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 17f7346e5cc6192..6d145abda9d4abe 100755 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -552,7 +552,7 @@ def getdocloc(self, object, basedir=sysconfig.get_path('stdlib')): '_thread', 'zipimport') or (file.startswith(basedir) and not file.startswith(os.path.join(basedir, 'site-packages')))) and - object.__name__ not in ('xml.etree', 'test.pydoc_mod')): + object.__name__ not in ('xml.etree', 'test.test_pydoc.pydoc_mod')): if docloc.startswith(("http://", "https://")): docloc = "{}/{}.html".format(docloc.rstrip("/"), object.__name__.lower()) else: diff --git a/Lib/test/libregrtest/findtests.py b/Lib/test/libregrtest/findtests.py index ee890b5b1db4cdc..4ac95e23a56b8f8 100644 --- a/Lib/test/libregrtest/findtests.py +++ b/Lib/test/libregrtest/findtests.py @@ -23,6 +23,7 @@ "test_future_stmt", "test_gdb", "test_inspect", + "test_pydoc", "test_multiprocessing_fork", "test_multiprocessing_forkserver", "test_multiprocessing_spawn", diff --git a/Lib/test/test_pydoc/__init__.py b/Lib/test/test_pydoc/__init__.py new file mode 100644 index 000000000000000..f2a39a3fe29c7f0 --- /dev/null +++ b/Lib/test/test_pydoc/__init__.py @@ -0,0 +1,6 @@ +import os +from test import support + + +def load_tests(*args): + return support.load_package_tests(os.path.dirname(__file__), *args) diff --git a/Lib/test/pydoc_mod.py b/Lib/test/test_pydoc/pydoc_mod.py similarity index 100% rename from Lib/test/pydoc_mod.py rename to Lib/test/test_pydoc/pydoc_mod.py diff --git a/Lib/test/pydocfodder.py b/Lib/test/test_pydoc/pydocfodder.py similarity index 100% rename from Lib/test/pydocfodder.py rename to Lib/test/test_pydoc/pydocfodder.py diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py similarity index 98% rename from Lib/test/test_pydoc.py rename to Lib/test/test_pydoc/test_pydoc.py index f3c26624c624f5d..0dd24e6d3473647 100644 --- a/Lib/test/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -34,8 +34,8 @@ captured_stderr, is_emscripten, is_wasi, requires_docstrings, MISSING_C_DOCSTRINGS) from test.support.os_helper import (TESTFN, rmtree, unlink) -from test import pydoc_mod -from test import pydocfodder +from test.test_pydoc import pydoc_mod +from test.test_pydoc import pydocfodder class nonascii: @@ -52,7 +52,7 @@ class nonascii: expected_text_pattern = """ NAME - test.pydoc_mod - This is a test module for test_pydoc + test.test_pydoc.pydoc_mod - This is a test module for test_pydoc %s CLASSES builtins.object @@ -125,7 +125,7 @@ class C(builtins.object) DATA __xyz__ = 'X, Y and Z' - c_alias = test.pydoc_mod.C[int] + c_alias = test.test_pydoc.pydoc_mod.C[int] list_alias1 = typing.List[int] list_alias2 = list[int] type_union1 = typing.Union[int, str] @@ -148,7 +148,7 @@ class C(builtins.object) for s in expected_data_docstrings) html2text_of_expected = """ -test.pydoc_mod (version 1.2.3.4) +test.test_pydoc.pydoc_mod (version 1.2.3.4) This is a test module for test_pydoc Modules @@ -213,7 +213,7 @@ class C(builtins.object) Data __xyz__ = 'X, Y and Z' - c_alias = test.pydoc_mod.C[int] + c_alias = test.test_pydoc.pydoc_mod.C[int] list_alias1 = typing.List[int] list_alias2 = list[int] type_union1 = typing.Union[int, str] @@ -342,7 +342,7 @@ def get_pydoc_link(module): "Returns a documentation web link of a module" abspath = os.path.abspath dirname = os.path.dirname - basedir = dirname(dirname(abspath(__file__))) + basedir = dirname(dirname(dirname(abspath(__file__)))) doc = pydoc.TextDoc() loc = doc.getdocloc(module, basedir=basedir) return loc @@ -489,7 +489,7 @@ def test_not_here(self): @requires_docstrings def test_not_ascii(self): - result = run_pydoc('test.test_pydoc.nonascii', PYTHONIOENCODING='ascii') + result = run_pydoc('test.test_pydoc.test_pydoc.nonascii', PYTHONIOENCODING='ascii') encoded = nonascii.__doc__.encode('ascii', 'backslashreplace') self.assertIn(encoded, result) @@ -669,9 +669,9 @@ def test_help_output_redirect(self): buf = StringIO() helper = pydoc.Helper(output=buf) unused, doc_loc = get_pydoc_text(pydoc_mod) - module = "test.pydoc_mod" + module = "test.test_pydoc.pydoc_mod" help_header = """ - Help on module test.pydoc_mod in test: + Help on module test.test_pydoc.pydoc_mod in test.test_pydoc: """.lstrip() help_header = textwrap.dedent(help_header) @@ -1142,7 +1142,6 @@ class TestDescriptions(unittest.TestCase): def test_module(self): # Check that pydocfodder module can be described - from test import pydocfodder doc = pydoc.render_doc(pydocfodder) self.assertIn("pydocfodder", doc) @@ -1425,10 +1424,10 @@ def smeth(*args, **kwargs): self.assertEqual(self._get_summary_line(C.meth), "meth" + unbound) self.assertEqual(self._get_summary_line(C().meth), - "meth" + bound + " method of test.test_pydoc.C instance") + "meth" + bound + " method of test.test_pydoc.test_pydoc.C instance") C.cmeth.__func__.__text_signature__ = text_signature self.assertEqual(self._get_summary_line(C.cmeth), - "cmeth" + bound + " class method of test.test_pydoc.C") + "cmeth" + bound + " class method of test.test_pydoc.test_pydoc.C") C.smeth.__text_signature__ = text_signature self.assertEqual(self._get_summary_line(C.smeth), "smeth" + unbound) @@ -1465,7 +1464,7 @@ def cm(cls, x): 'cm(...)\n' ' A class method\n') self.assertEqual(self._get_summary_lines(X.cm), """\ -cm(x) class method of test.test_pydoc.X +cm(x) class method of test.test_pydoc.test_pydoc.X A class method """) self.assertIn(""" @@ -1647,19 +1646,19 @@ def test_text_doc_routines_in_class(self, cls=pydocfodder.B): lines = self.getsection(result, f' | Methods {where}:', ' | ' + '-'*70) self.assertIn(' | A_method_alias = A_method(self)', lines) self.assertIn(' | B_method_alias = B_method(self)', lines) - self.assertIn(' | A_staticmethod(x, y) from test.pydocfodder.A', lines) + self.assertIn(' | A_staticmethod(x, y) from test.test_pydoc.pydocfodder.A', lines) self.assertIn(' | A_staticmethod_alias = A_staticmethod(x, y)', lines) - self.assertIn(' | global_func(x, y) from test.pydocfodder', lines) + self.assertIn(' | global_func(x, y) from test.test_pydoc.pydocfodder', lines) self.assertIn(' | global_func_alias = global_func(x, y)', lines) - self.assertIn(' | global_func2_alias = global_func2(x, y) from test.pydocfodder', lines) + self.assertIn(' | global_func2_alias = global_func2(x, y) from test.test_pydoc.pydocfodder', lines) self.assertIn(' | __repr__(self, /) from builtins.object', lines) self.assertIn(' | object_repr = __repr__(self, /)', lines) lines = self.getsection(result, f' | Static methods {where}:', ' | ' + '-'*70) - self.assertIn(' | A_classmethod_ref = A_classmethod(x) class method of test.pydocfodder.A', lines) - note = '' if cls is pydocfodder.B else ' class method of test.pydocfodder.B' + self.assertIn(' | A_classmethod_ref = A_classmethod(x) class method of test.test_pydoc.pydocfodder.A', lines) + note = '' if cls is pydocfodder.B else ' class method of test.test_pydoc.pydocfodder.B' self.assertIn(' | B_classmethod_ref = B_classmethod(x)' + note, lines) - self.assertIn(' | A_method_ref = A_method() method of test.pydocfodder.A instance', lines) + self.assertIn(' | A_method_ref = A_method() method of test.test_pydoc.pydocfodder.A instance', lines) self.assertIn(' | get(key, default=None, /) method of builtins.dict instance', lines) self.assertIn(' | dict_get = get(key, default=None, /) method of builtins.dict instance', lines) @@ -1675,19 +1674,19 @@ def test_html_doc_routines_in_class(self, cls=pydocfodder.B): lines = self.getsection(result, f'Methods {where}:', '-'*70) self.assertIn('A_method_alias = A_method(self)', lines) self.assertIn('B_method_alias = B_method(self)', lines) - self.assertIn('A_staticmethod(x, y) from test.pydocfodder.A', lines) + self.assertIn('A_staticmethod(x, y) from test.test_pydoc.pydocfodder.A', lines) self.assertIn('A_staticmethod_alias = A_staticmethod(x, y)', lines) - self.assertIn('global_func(x, y) from test.pydocfodder', lines) + self.assertIn('global_func(x, y) from test.test_pydoc.pydocfodder', lines) self.assertIn('global_func_alias = global_func(x, y)', lines) - self.assertIn('global_func2_alias = global_func2(x, y) from test.pydocfodder', lines) + self.assertIn('global_func2_alias = global_func2(x, y) from test.test_pydoc.pydocfodder', lines) self.assertIn('__repr__(self, /) from builtins.object', lines) self.assertIn('object_repr = __repr__(self, /)', lines) lines = self.getsection(result, f'Static methods {where}:', '-'*70) - self.assertIn('A_classmethod_ref = A_classmethod(x) class method of test.pydocfodder.A', lines) - note = '' if cls is pydocfodder.B else ' class method of test.pydocfodder.B' + self.assertIn('A_classmethod_ref = A_classmethod(x) class method of test.test_pydoc.pydocfodder.A', lines) + note = '' if cls is pydocfodder.B else ' class method of test.test_pydoc.pydocfodder.B' self.assertIn('B_classmethod_ref = B_classmethod(x)' + note, lines) - self.assertIn('A_method_ref = A_method() method of test.pydocfodder.A instance', lines) + self.assertIn('A_method_ref = A_method() method of test.test_pydoc.pydocfodder.A instance', lines) lines = self.getsection(result, f'Class methods {where}:', '-'*70) self.assertIn('B_classmethod(x)', lines) diff --git a/Makefile.pre.in b/Makefile.pre.in index e0527633ccd03bb..cf182980c120eef 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2307,6 +2307,7 @@ TESTSUBDIRS= idlelib/idle_test \ test/test_module \ test/test_pathlib \ test/test_peg_generator \ + test/test_pydoc \ test/test_sqlite3 \ test/test_tkinter \ test/test_tomllib \ From 7cce8576226249461baa91c4a89770a1823b44a4 Mon Sep 17 00:00:00 2001 From: Ken Jin <kenjin@python.org> Date: Tue, 13 Feb 2024 21:24:48 +0800 Subject: [PATCH 245/507] gh-114058: Foundations of the Tier2 redundancy eliminator (GH-115085) --------- Co-authored-by: Mark Shannon <9448417+markshannon@users.noreply.github.com> Co-authored-by: Jules <57632293+JuliaPoo@users.noreply.github.com> Co-authored-by: Guido van Rossum <gvanrossum@users.noreply.github.com> --- .gitattributes | 1 + Include/cpython/pystats.h | 3 + Include/internal/pycore_opcode_metadata.h | 10 +- Include/internal/pycore_optimizer.h | 7 + Include/internal/pycore_uop_metadata.h | 10 +- Lib/test/test_capi/test_opt.py | 209 ++ Lib/test/test_generated_cases.py | 153 ++ Makefile.pre.in | 8 +- ...-01-16-14-41-54.gh-issue-114058.Cb2b8h.rst | 1 + Python/bytecodes.c | 46 +- Python/executor_cases.c.h | 2 +- Python/generated_cases.c.h | 2 +- Python/optimizer.c | 13 +- Python/optimizer_analysis.c | 555 +++++- Python/specialize.c | 5 + .../tier2_redundancy_eliminator_bytecodes.c | 272 +++ Python/tier2_redundancy_eliminator_cases.c.h | 1676 +++++++++++++++++ Tools/c-analyzer/cpython/_parser.py | 2 + Tools/c-analyzer/cpython/ignored.tsv | 2 +- Tools/cases_generator/README.md | 3 + Tools/cases_generator/analyzer.py | 6 +- .../cases_generator/interpreter_definition.md | 26 +- Tools/cases_generator/parsing.py | 26 +- Tools/cases_generator/stack.py | 4 +- .../tier2_abstract_generator.py | 235 +++ 25 files changed, 3137 insertions(+), 140 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-16-14-41-54.gh-issue-114058.Cb2b8h.rst create mode 100644 Python/tier2_redundancy_eliminator_bytecodes.c create mode 100644 Python/tier2_redundancy_eliminator_cases.c.h create mode 100644 Tools/cases_generator/tier2_abstract_generator.py diff --git a/.gitattributes b/.gitattributes index 2a48df079e1aeb0..07d877027b09f68 100644 --- a/.gitattributes +++ b/.gitattributes @@ -94,6 +94,7 @@ Programs/test_frozenmain.h generated Python/Python-ast.c generated Python/executor_cases.c.h generated Python/generated_cases.c.h generated +Python/tier2_redundancy_eliminator_bytecodes.c.h generated Python/opcode_targets.h generated Python/stdlib_module_names.h generated Tools/peg_generator/pegen/grammar_parser.py generated diff --git a/Include/cpython/pystats.h b/Include/cpython/pystats.h index 0f50439b73848e7..db9aaedec950e45 100644 --- a/Include/cpython/pystats.h +++ b/Include/cpython/pystats.h @@ -120,6 +120,9 @@ typedef struct _optimization_stats { uint64_t trace_length_hist[_Py_UOP_HIST_SIZE]; uint64_t trace_run_length_hist[_Py_UOP_HIST_SIZE]; uint64_t optimized_trace_length_hist[_Py_UOP_HIST_SIZE]; + uint64_t optimizer_attempts; + uint64_t optimizer_successes; + uint64_t optimizer_failure_reason_no_memory; } OptimizationStats; typedef struct _rare_event_stats { diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 75d7f44025328ee..6b60a6fbffdc5e4 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1094,7 +1094,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[268] = { [MATCH_KEYS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [MATCH_MAPPING] = { true, INSTR_FMT_IX, 0 }, [MATCH_SEQUENCE] = { true, INSTR_FMT_IX, 0 }, - [NOP] = { true, INSTR_FMT_IX, 0 }, + [NOP] = { true, INSTR_FMT_IX, HAS_PURE_FLAG }, [POP_EXCEPT] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, [POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [POP_JUMP_IF_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, @@ -1156,10 +1156,10 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[268] = { [LOAD_SUPER_METHOD] = { true, -1, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_ZERO_SUPER_ATTR] = { true, -1, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_ZERO_SUPER_METHOD] = { true, -1, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [POP_BLOCK] = { true, -1, 0 }, - [SETUP_CLEANUP] = { true, -1, HAS_ARG_FLAG }, - [SETUP_FINALLY] = { true, -1, HAS_ARG_FLAG }, - [SETUP_WITH] = { true, -1, HAS_ARG_FLAG }, + [POP_BLOCK] = { true, -1, HAS_PURE_FLAG }, + [SETUP_CLEANUP] = { true, -1, HAS_PURE_FLAG | HAS_ARG_FLAG }, + [SETUP_FINALLY] = { true, -1, HAS_PURE_FLAG | HAS_ARG_FLAG }, + [SETUP_WITH] = { true, -1, HAS_PURE_FLAG | HAS_ARG_FLAG }, [STORE_FAST_MAYBE_NULL] = { true, -1, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, }; #endif diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index e21412fc815540d..eee71c700d4904a 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -8,6 +8,13 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif +#include "pycore_uop_ids.h" + +// This is the length of the trace we project initially. +#define UOP_MAX_TRACE_LENGTH 512 + +#define TRACE_STACK_SIZE 5 + int _Py_uop_analyze_and_optimize(_PyInterpreterFrame *frame, _PyUOpInstruction *trace, int trace_len, int curr_stackentries, _PyBloomFilter *dependencies); diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 2b5b37e6b8d6a43..30dc5a881574e79 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -16,7 +16,7 @@ extern const char * const _PyOpcode_uop_name[MAX_UOP_ID+1]; #ifdef NEED_OPCODE_METADATA const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { - [_NOP] = 0, + [_NOP] = HAS_PURE_FLAG, [_RESUME_CHECK] = HAS_DEOPT_FLAG, [_LOAD_FAST_CHECK] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG, [_LOAD_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_PURE_FLAG, @@ -202,10 +202,10 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_SAVE_RETURN_OFFSET] = HAS_ARG_FLAG, [_EXIT_TRACE] = HAS_DEOPT_FLAG, [_CHECK_VALIDITY] = HAS_DEOPT_FLAG, - [_LOAD_CONST_INLINE] = 0, - [_LOAD_CONST_INLINE_BORROW] = 0, - [_LOAD_CONST_INLINE_WITH_NULL] = 0, - [_LOAD_CONST_INLINE_BORROW_WITH_NULL] = 0, + [_LOAD_CONST_INLINE] = HAS_PURE_FLAG, + [_LOAD_CONST_INLINE_BORROW] = HAS_PURE_FLAG, + [_LOAD_CONST_INLINE_WITH_NULL] = HAS_PURE_FLAG, + [_LOAD_CONST_INLINE_BORROW_WITH_NULL] = HAS_PURE_FLAG, [_CHECK_GLOBALS] = HAS_DEOPT_FLAG, [_CHECK_BUILTINS] = HAS_DEOPT_FLAG, [_INTERNAL_INCREMENT_OPT_COUNTER] = 0, diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index e6b1b554c9af10e..b64aed10d2d653e 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -3,6 +3,7 @@ import sys import textwrap import unittest +import gc import _testinternalcapi @@ -556,6 +557,214 @@ def testfunc(n): # too much already. self.assertEqual(count, 1) +class TestUopsOptimization(unittest.TestCase): + + def test_int_type_propagation(self): + def testfunc(loops): + num = 0 + while num < loops: + x = num + num + a = x + 1 + num += 1 + return a + + opt = _testinternalcapi.get_uop_optimizer() + res = None + with temporary_optimizer(opt): + res = testfunc(32) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + self.assertEqual(res, 63) + binop_count = [opname for opname, _, _ in ex if opname == "_BINARY_OP_ADD_INT"] + guard_both_int_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_INT"] + self.assertGreaterEqual(len(binop_count), 3) + self.assertLessEqual(len(guard_both_int_count), 1) + + def test_int_type_propagation_through_frame(self): + def double(x): + return x + x + def testfunc(loops): + num = 0 + while num < loops: + x = num + num + a = double(x) + num += 1 + return a + + opt = _testinternalcapi.get_uop_optimizer() + res = None + with temporary_optimizer(opt): + res = testfunc(32) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + self.assertEqual(res, 124) + binop_count = [opname for opname, _, _ in ex if opname == "_BINARY_OP_ADD_INT"] + guard_both_int_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_INT"] + self.assertGreaterEqual(len(binop_count), 3) + self.assertLessEqual(len(guard_both_int_count), 1) + + def test_int_type_propagation_from_frame(self): + def double(x): + return x + x + def testfunc(loops): + num = 0 + while num < loops: + a = double(num) + x = a + a + num += 1 + return x + + opt = _testinternalcapi.get_uop_optimizer() + res = None + with temporary_optimizer(opt): + res = testfunc(32) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + self.assertEqual(res, 124) + binop_count = [opname for opname, _, _ in ex if opname == "_BINARY_OP_ADD_INT"] + guard_both_int_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_INT"] + self.assertGreaterEqual(len(binop_count), 3) + self.assertLessEqual(len(guard_both_int_count), 1) + + def test_int_impure_region(self): + def testfunc(loops): + num = 0 + while num < loops: + x = num + num + y = 1 + x // 2 + a = x + y + num += 1 + return a + + opt = _testinternalcapi.get_uop_optimizer() + res = None + with temporary_optimizer(opt): + res = testfunc(64) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + binop_count = [opname for opname, _, _ in ex if opname == "_BINARY_OP_ADD_INT"] + self.assertGreaterEqual(len(binop_count), 3) + + def test_call_py_exact_args(self): + def testfunc(n): + def dummy(x): + return x+1 + for i in range(n): + dummy(i) + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(20) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_PUSH_FRAME", uops) + self.assertIn("_BINARY_OP_ADD_INT", uops) + self.assertNotIn("_CHECK_PEP_523", uops) + + def test_int_type_propagate_through_range(self): + def testfunc(n): + + for i in range(n): + x = i + i + return x + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + res = testfunc(20) + + ex = get_first_executor(testfunc) + self.assertEqual(res, 19 * 2) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertNotIn("_GUARD_BOTH_INT", uops) + + def test_int_value_numbering(self): + def testfunc(n): + + y = 1 + for i in range(n): + x = y + z = x + a = z + b = a + res = x + z + a + b + return res + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + res = testfunc(20) + + ex = get_first_executor(testfunc) + self.assertEqual(res, 4) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_GUARD_BOTH_INT", uops) + guard_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_INT"] + self.assertEqual(len(guard_count), 1) + + def test_comprehension(self): + def testfunc(n): + for _ in range(n): + return [i for i in range(n)] + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(20) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertNotIn("_BINARY_OP_ADD_INT", uops) + + def test_call_py_exact_args_disappearing(self): + def dummy(x): + return x+1 + + def testfunc(n): + for i in range(n): + dummy(i) + + opt = _testinternalcapi.get_uop_optimizer() + # Trigger specialization + testfunc(8) + with temporary_optimizer(opt): + del dummy + gc.collect() + + def dummy(x): + return x + 2 + testfunc(10) + + ex = get_first_executor(testfunc) + # Honestly as long as it doesn't crash it's fine. + # Whether we get an executor or not is non-deterministic, + # because it's decided by when the function is freed. + # This test is a little implementation specific. + + def test_promote_globals_to_constants(self): + def testfunc(n): + for i in range(n): + x = range(i) + return x + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(20) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertNotIn("_LOAD_GLOBAL_BUILTIN", uops) + self.assertIn("_LOAD_CONST_INLINE_BORROW_WITH_NULL", uops) + + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index ca1228ee7008a9a..a7ad6c7320b4ee4 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -33,6 +33,7 @@ def skip_if_different_mount_drives(): import parser from stack import Stack import tier1_generator + import tier2_abstract_generator def handle_stderr(): @@ -793,5 +794,157 @@ def test_annotated_op(self): self.run_cases_test(input, output) +class TestGeneratedAbstractCases(unittest.TestCase): + def setUp(self) -> None: + super().setUp() + self.maxDiff = None + + self.temp_dir = tempfile.gettempdir() + self.temp_input_filename = os.path.join(self.temp_dir, "input.txt") + self.temp_input2_filename = os.path.join(self.temp_dir, "input2.txt") + self.temp_output_filename = os.path.join(self.temp_dir, "output.txt") + + def tearDown(self) -> None: + for filename in [ + self.temp_input_filename, + self.temp_input2_filename, + self.temp_output_filename, + ]: + try: + os.remove(filename) + except: + pass + super().tearDown() + + def run_cases_test(self, input: str, input2: str, expected: str): + with open(self.temp_input_filename, "w+") as temp_input: + temp_input.write(parser.BEGIN_MARKER) + temp_input.write(input) + temp_input.write(parser.END_MARKER) + temp_input.flush() + + with open(self.temp_input2_filename, "w+") as temp_input: + temp_input.write(parser.BEGIN_MARKER) + temp_input.write(input2) + temp_input.write(parser.END_MARKER) + temp_input.flush() + + with handle_stderr(): + tier2_abstract_generator.generate_tier2_abstract_from_files( + [self.temp_input_filename, self.temp_input2_filename], + self.temp_output_filename + ) + + with open(self.temp_output_filename) as temp_output: + lines = temp_output.readlines() + while lines and lines[0].startswith(("// ", "#", " #", "\n")): + lines.pop(0) + while lines and lines[-1].startswith(("#", "\n")): + lines.pop(-1) + actual = "".join(lines) + self.assertEqual(actual.strip(), expected.strip()) + + def test_overridden_abstract(self): + input = """ + pure op(OP, (--)) { + spam(); + } + """ + input2 = """ + pure op(OP, (--)) { + eggs(); + } + """ + output = """ + case OP: { + eggs(); + break; + } + """ + self.run_cases_test(input, input2, output) + + def test_overridden_abstract_args(self): + input = """ + pure op(OP, (arg1 -- out)) { + spam(); + } + op(OP2, (arg1 -- out)) { + eggs(); + } + """ + input2 = """ + op(OP, (arg1 -- out)) { + eggs(); + } + """ + output = """ + case OP: { + _Py_UOpsSymType *arg1; + _Py_UOpsSymType *out; + arg1 = stack_pointer[-1]; + eggs(); + stack_pointer[-1] = out; + break; + } + + case OP2: { + _Py_UOpsSymType *out; + out = sym_new_unknown(ctx); + if (out == NULL) goto out_of_space; + stack_pointer[-1] = out; + break; + } + """ + self.run_cases_test(input, input2, output) + + def test_no_overridden_case(self): + input = """ + pure op(OP, (arg1 -- out)) { + spam(); + } + + pure op(OP2, (arg1 -- out)) { + } + + """ + input2 = """ + pure op(OP2, (arg1 -- out)) { + } + """ + output = """ + case OP: { + _Py_UOpsSymType *out; + out = sym_new_unknown(ctx); + if (out == NULL) goto out_of_space; + stack_pointer[-1] = out; + break; + } + + case OP2: { + _Py_UOpsSymType *arg1; + _Py_UOpsSymType *out; + arg1 = stack_pointer[-1]; + stack_pointer[-1] = out; + break; + } + """ + self.run_cases_test(input, input2, output) + + def test_missing_override_failure(self): + input = """ + pure op(OP, (arg1 -- out)) { + spam(); + } + """ + input2 = """ + pure op(OTHER, (arg1 -- out)) { + } + """ + output = """ + """ + with self.assertRaisesRegex(AssertionError, "All abstract uops"): + self.run_cases_test(input, input2, output) + + if __name__ == "__main__": unittest.main() diff --git a/Makefile.pre.in b/Makefile.pre.in index cf182980c120eef..d3b18acad61ce5a 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1863,6 +1863,10 @@ regen-cases: -o $(srcdir)/Python/generated_cases.c.h.new $(srcdir)/Python/bytecodes.c $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/tier2_generator.py \ -o $(srcdir)/Python/executor_cases.c.h.new $(srcdir)/Python/bytecodes.c + $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/tier2_abstract_generator.py \ + -o $(srcdir)/Python/tier2_redundancy_eliminator_cases.c.h.new \ + $(srcdir)/Python/tier2_redundancy_eliminator_bytecodes.c \ + $(srcdir)/Python/bytecodes.c $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/opcode_metadata_generator.py \ -o $(srcdir)/Include/internal/pycore_opcode_metadata.h.new $(srcdir)/Python/bytecodes.c $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/uop_metadata_generator.py -o \ @@ -1874,6 +1878,7 @@ regen-cases: $(UPDATE_FILE) $(srcdir)/Include/internal/pycore_opcode_metadata.h $(srcdir)/Include/internal/pycore_opcode_metadata.h.new $(UPDATE_FILE) $(srcdir)/Include/internal/pycore_uop_metadata.h $(srcdir)/Include/internal/pycore_uop_metadata.h.new $(UPDATE_FILE) $(srcdir)/Python/executor_cases.c.h $(srcdir)/Python/executor_cases.c.h.new + $(UPDATE_FILE) $(srcdir)/Python/tier2_redundancy_eliminator_cases.c.h $(srcdir)/Python/tier2_redundancy_eliminator_cases.c.h.new $(UPDATE_FILE) $(srcdir)/Lib/_opcode_metadata.py $(srcdir)/Lib/_opcode_metadata.py.new Python/compile.o: $(srcdir)/Include/internal/pycore_opcode_metadata.h @@ -1895,7 +1900,8 @@ Python/optimizer.o: \ Python/optimizer_analysis.o: \ $(srcdir)/Include/internal/pycore_opcode_metadata.h \ - $(srcdir)/Include/internal/pycore_optimizer.h + $(srcdir)/Include/internal/pycore_optimizer.h \ + $(srcdir)/Python/tier2_redundancy_eliminator_cases.c.h Python/frozen.o: $(FROZEN_FILES_OUT) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-16-14-41-54.gh-issue-114058.Cb2b8h.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-16-14-41-54.gh-issue-114058.Cb2b8h.rst new file mode 100644 index 000000000000000..beb82dbcd3cccd6 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-16-14-41-54.gh-issue-114058.Cb2b8h.rst @@ -0,0 +1 @@ +Implement the foundations of the Tier 2 redundancy eliminator. diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 197dff4b9888ce7..f7c7e3669b7e6f7 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -133,7 +133,7 @@ dummy_func( switch (opcode) { // BEGIN BYTECODES // - inst(NOP, (--)) { + pure inst(NOP, (--)) { } family(RESUME, 0) = { @@ -411,12 +411,12 @@ dummy_func( // BINARY_OP_INPLACE_ADD_UNICODE, // See comments at that opcode. }; - op(_GUARD_BOTH_INT, (left, right -- left: &PYLONG_TYPE, right: &PYLONG_TYPE)) { + op(_GUARD_BOTH_INT, (left, right -- left, right)) { DEOPT_IF(!PyLong_CheckExact(left)); DEOPT_IF(!PyLong_CheckExact(right)); } - pure op(_BINARY_OP_MULTIPLY_INT, (left, right -- res: &PYLONG_TYPE)) { + pure op(_BINARY_OP_MULTIPLY_INT, (left, right -- res)) { STAT_INC(BINARY_OP, hit); res = _PyLong_Multiply((PyLongObject *)left, (PyLongObject *)right); _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); @@ -424,7 +424,7 @@ dummy_func( ERROR_IF(res == NULL, error); } - pure op(_BINARY_OP_ADD_INT, (left, right -- res: &PYLONG_TYPE)) { + pure op(_BINARY_OP_ADD_INT, (left, right -- res)) { STAT_INC(BINARY_OP, hit); res = _PyLong_Add((PyLongObject *)left, (PyLongObject *)right); _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); @@ -432,7 +432,7 @@ dummy_func( ERROR_IF(res == NULL, error); } - pure op(_BINARY_OP_SUBTRACT_INT, (left, right -- res: &PYLONG_TYPE)) { + pure op(_BINARY_OP_SUBTRACT_INT, (left, right -- res)) { STAT_INC(BINARY_OP, hit); res = _PyLong_Subtract((PyLongObject *)left, (PyLongObject *)right); _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); @@ -447,12 +447,12 @@ dummy_func( macro(BINARY_OP_SUBTRACT_INT) = _GUARD_BOTH_INT + unused/1 + _BINARY_OP_SUBTRACT_INT; - op(_GUARD_BOTH_FLOAT, (left, right -- left: &PYFLOAT_TYPE, right: &PYFLOAT_TYPE)) { + op(_GUARD_BOTH_FLOAT, (left, right -- left, right)) { DEOPT_IF(!PyFloat_CheckExact(left)); DEOPT_IF(!PyFloat_CheckExact(right)); } - pure op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res: &PYFLOAT_TYPE)) { + pure op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { STAT_INC(BINARY_OP, hit); double dres = ((PyFloatObject *)left)->ob_fval * @@ -460,7 +460,7 @@ dummy_func( DECREF_INPUTS_AND_REUSE_FLOAT(left, right, dres, res); } - pure op(_BINARY_OP_ADD_FLOAT, (left, right -- res: &PYFLOAT_TYPE)) { + pure op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { STAT_INC(BINARY_OP, hit); double dres = ((PyFloatObject *)left)->ob_fval + @@ -468,7 +468,7 @@ dummy_func( DECREF_INPUTS_AND_REUSE_FLOAT(left, right, dres, res); } - pure op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res: &PYFLOAT_TYPE)) { + pure op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) { STAT_INC(BINARY_OP, hit); double dres = ((PyFloatObject *)left)->ob_fval - @@ -483,12 +483,12 @@ dummy_func( macro(BINARY_OP_SUBTRACT_FLOAT) = _GUARD_BOTH_FLOAT + unused/1 + _BINARY_OP_SUBTRACT_FLOAT; - op(_GUARD_BOTH_UNICODE, (left, right -- left: &PYUNICODE_TYPE, right: &PYUNICODE_TYPE)) { + op(_GUARD_BOTH_UNICODE, (left, right -- left, right)) { DEOPT_IF(!PyUnicode_CheckExact(left)); DEOPT_IF(!PyUnicode_CheckExact(right)); } - pure op(_BINARY_OP_ADD_UNICODE, (left, right -- res: &PYUNICODE_TYPE)) { + pure op(_BINARY_OP_ADD_UNICODE, (left, right -- res)) { STAT_INC(BINARY_OP, hit); res = PyUnicode_Concat(left, right); _Py_DECREF_SPECIALIZED(left, _PyUnicode_ExactDealloc); @@ -1877,7 +1877,7 @@ dummy_func( something was returned by a descriptor protocol). Set the second element of the stack to NULL, to signal CALL that it's not a method call. - NULL | meth | arg1 | ... | argN + meth | NULL | arg1 | ... | argN */ DECREF_INPUTS(); ERROR_IF(attr == NULL, error); @@ -1901,7 +1901,7 @@ dummy_func( LOAD_ATTR, }; - op(_GUARD_TYPE_VERSION, (type_version/2, owner -- owner: &(GUARD_TYPE_VERSION_TYPE + type_version))) { + op(_GUARD_TYPE_VERSION, (type_version/2, owner -- owner)) { PyTypeObject *tp = Py_TYPE(owner); assert(type_version != 0); DEOPT_IF(tp->tp_version_tag != type_version); @@ -2082,7 +2082,7 @@ dummy_func( DISPATCH_INLINED(new_frame); } - op(_GUARD_DORV_VALUES, (owner -- owner: &GUARD_DORV_VALUES_TYPE)) { + op(_GUARD_DORV_VALUES, (owner -- owner)) { assert(Py_TYPE(owner)->tp_flags & Py_TPFLAGS_MANAGED_DICT); PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); DEOPT_IF(!_PyDictOrValues_IsValues(dorv)); @@ -2711,7 +2711,7 @@ dummy_func( DEOPT_IF(r->len <= 0); } - op(_ITER_NEXT_RANGE, (iter -- iter, next: &PYLONG_TYPE)) { + op(_ITER_NEXT_RANGE, (iter -- iter, next)) { _PyRangeIterObject *r = (_PyRangeIterObject *)iter; assert(Py_TYPE(r) == &PyRangeIter_Type); assert(r->len > 0); @@ -2869,13 +2869,13 @@ dummy_func( exc_info->exc_value = Py_NewRef(new_exc); } - op(_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT, (owner -- owner: &GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_TYPE)) { + op(_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT, (owner -- owner)) { assert(Py_TYPE(owner)->tp_flags & Py_TPFLAGS_MANAGED_DICT); PyDictOrValues *dorv = _PyObject_DictOrValuesPointer(owner); DEOPT_IF(!_PyDictOrValues_IsValues(*dorv) && !_PyObject_MakeInstanceAttributesFromDict(owner, dorv)); } - op(_GUARD_KEYS_VERSION, (keys_version/2, owner -- owner: &(GUARD_KEYS_VERSION_TYPE + keys_version))) { + op(_GUARD_KEYS_VERSION, (keys_version/2, owner -- owner)) { PyTypeObject *owner_cls = Py_TYPE(owner); PyHeapTypeObject *owner_heap_type = (PyHeapTypeObject *)owner_cls; DEOPT_IF(owner_heap_type->ht_cached_keys->dk_version != keys_version); @@ -3090,7 +3090,7 @@ dummy_func( macro(CALL) = _SPECIALIZE_CALL + unused/2 + _CALL; - op(_CHECK_CALL_BOUND_METHOD_EXACT_ARGS, (callable, null, unused[oparg] -- callable: &PYMETHOD_TYPE, null: &NULL_TYPE, unused[oparg])) { + op(_CHECK_CALL_BOUND_METHOD_EXACT_ARGS, (callable, null, unused[oparg] -- callable, null, unused[oparg])) { DEOPT_IF(null != NULL); DEOPT_IF(Py_TYPE(callable) != &PyMethod_Type); } @@ -3108,7 +3108,7 @@ dummy_func( DEOPT_IF(tstate->interp->eval_frame); } - op(_CHECK_FUNCTION_EXACT_ARGS, (func_version/2, callable, self_or_null, unused[oparg] -- callable: &(PYFUNCTION_TYPE_VERSION_TYPE + func_version), self_or_null, unused[oparg])) { + op(_CHECK_FUNCTION_EXACT_ARGS, (func_version/2, callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) { DEOPT_IF(!PyFunction_Check(callable)); PyFunctionObject *func = (PyFunctionObject *)callable; DEOPT_IF(func->func_version != func_version); @@ -4059,23 +4059,23 @@ dummy_func( DEOPT_IF(!current_executor->vm_data.valid); } - op(_LOAD_CONST_INLINE, (ptr/4 -- value)) { + pure op(_LOAD_CONST_INLINE, (ptr/4 -- value)) { TIER_TWO_ONLY value = Py_NewRef(ptr); } - op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) { + pure op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) { TIER_TWO_ONLY value = ptr; } - op(_LOAD_CONST_INLINE_WITH_NULL, (ptr/4 -- value, null)) { + pure op(_LOAD_CONST_INLINE_WITH_NULL, (ptr/4 -- value, null)) { TIER_TWO_ONLY value = Py_NewRef(ptr); null = NULL; } - op(_LOAD_CONST_INLINE_BORROW_WITH_NULL, (ptr/4 -- value, null)) { + pure op(_LOAD_CONST_INLINE_BORROW_WITH_NULL, (ptr/4 -- value, null)) { TIER_TWO_ONLY value = ptr; null = NULL; diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 2d914b82dbf88f4..7d48d6a05a17b06 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1598,7 +1598,7 @@ something was returned by a descriptor protocol). Set the second element of the stack to NULL, to signal CALL that it's not a method call. - NULL | meth | arg1 | ... | argN + meth | NULL | arg1 | ... | argN */ Py_DECREF(owner); if (attr == NULL) goto pop_1_error_tier_two; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index e5244147d499af8..afb6650e5920fbb 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -3420,7 +3420,7 @@ something was returned by a descriptor protocol). Set the second element of the stack to NULL, to signal CALL that it's not a method call. - NULL | meth | arg1 | ... | argN + meth | NULL | arg1 | ... | argN */ Py_DECREF(owner); if (attr == NULL) goto pop_1_error; diff --git a/Python/optimizer.c b/Python/optimizer.c index ad9ac382d300ef1..f31f83113d3f250 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -17,8 +17,6 @@ #include "pycore_uop_metadata.h" // Uop tables #undef NEED_OPCODE_METADATA -#define UOP_MAX_TRACE_LENGTH 512 - #define MAX_EXECUTORS_SIZE 256 @@ -308,8 +306,6 @@ BRANCH_TO_GUARD[4][2] = { [POP_JUMP_IF_NOT_NONE - POP_JUMP_IF_FALSE][1] = _GUARD_IS_NOT_NONE_POP, }; -#define TRACE_STACK_SIZE 5 - #define CONFIDENCE_RANGE 1000 #define CONFIDENCE_CUTOFF 333 @@ -323,10 +319,11 @@ BRANCH_TO_GUARD[4][2] = { #define ADD_TO_TRACE(OPCODE, OPARG, OPERAND, TARGET) \ DPRINTF(2, \ - " ADD_TO_TRACE(%s, %d, %" PRIu64 ")\n", \ + " ADD_TO_TRACE(%s, %d, %" PRIu64 ", %d)\n", \ _PyUOpName(OPCODE), \ (OPARG), \ - (uint64_t)(OPERAND)); \ + (uint64_t)(OPERAND), \ + TARGET); \ assert(trace_length < max_length); \ trace[trace_length].opcode = (OPCODE); \ trace[trace_length].oparg = (OPARG); \ @@ -825,11 +822,13 @@ uop_optimize( char *uop_optimize = Py_GETENV("PYTHONUOPSOPTIMIZE"); if (uop_optimize == NULL || *uop_optimize > '0') { err = _Py_uop_analyze_and_optimize(frame, buffer, - UOP_MAX_TRACE_LENGTH, curr_stackentries, &dependencies); + UOP_MAX_TRACE_LENGTH, + curr_stackentries, &dependencies); if (err <= 0) { return err; } } + assert(err == 1); _PyExecutorObject *executor = make_executor_from_uops(buffer, &dependencies); if (executor == NULL) { return -1; diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index b14e6950b4a06b5..e02ca4d6acf6c14 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -1,3 +1,14 @@ +/* + * This file contains the support code for CPython's uops redundancy eliminator. + * It also performs some simple optimizations. + * It performs a traditional data-flow analysis[1] over the trace of uops. + * Using the information gained, it chooses to emit, or skip certain instructions + * if possible. + * + * [1] For information on data-flow analysis, please see + * https://clang.llvm.org/docs/DataFlowAnalysisIntro.html + * + * */ #include "Python.h" #include "opcode.h" #include "pycore_dict.h" @@ -9,10 +20,355 @@ #include "pycore_dict.h" #include "pycore_long.h" #include "cpython/optimizer.h" +#include "pycore_optimizer.h" +#include "pycore_object.h" +#include "pycore_dict.h" +#include "pycore_function.h" +#include "pycore_uop_metadata.h" +#include "pycore_uop_ids.h" +#include "pycore_range.h" + +#include <stdarg.h> #include <stdbool.h> #include <stdint.h> #include <stddef.h> -#include "pycore_optimizer.h" + +// Holds locals, stack, locals, stack ... co_consts (in that order) +#define MAX_ABSTRACT_INTERP_SIZE 4096 + +#define OVERALLOCATE_FACTOR 5 + +#define TY_ARENA_SIZE (UOP_MAX_TRACE_LENGTH * OVERALLOCATE_FACTOR) + +// Need extras for root frame and for overflow frame (see TRACE_STACK_PUSH()) +#define MAX_ABSTRACT_FRAME_DEPTH (TRACE_STACK_SIZE + 2) + +#ifdef Py_DEBUG + static const char *const DEBUG_ENV = "PYTHON_OPT_DEBUG"; + static inline int get_lltrace(void) { + char *uop_debug = Py_GETENV(DEBUG_ENV); + int lltrace = 0; + if (uop_debug != NULL && *uop_debug >= '0') { + lltrace = *uop_debug - '0'; // TODO: Parse an int and all that + } + return lltrace; + } + #define DPRINTF(level, ...) \ + if (get_lltrace() >= (level)) { printf(__VA_ARGS__); } +#else + #define DPRINTF(level, ...) +#endif + + +// Flags for below. +#define KNOWN 1 << 0 +#define TRUE_CONST 1 << 1 +#define IS_NULL 1 << 2 +#define NOT_NULL 1 << 3 + +typedef struct { + int flags; + PyTypeObject *typ; + // constant propagated value (might be NULL) + PyObject *const_val; +} _Py_UOpsSymType; + + +typedef struct _Py_UOpsAbstractFrame { + // Max stacklen + int stack_len; + int locals_len; + + _Py_UOpsSymType **stack_pointer; + _Py_UOpsSymType **stack; + _Py_UOpsSymType **locals; +} _Py_UOpsAbstractFrame; + + +typedef struct ty_arena { + int ty_curr_number; + int ty_max_number; + _Py_UOpsSymType arena[TY_ARENA_SIZE]; +} ty_arena; + +// Tier 2 types meta interpreter +typedef struct _Py_UOpsAbstractInterpContext { + PyObject_HEAD + // The current "executing" frame. + _Py_UOpsAbstractFrame *frame; + _Py_UOpsAbstractFrame frames[MAX_ABSTRACT_FRAME_DEPTH]; + int curr_frame_depth; + + // Arena for the symbolic types. + ty_arena t_arena; + + _Py_UOpsSymType **n_consumed; + _Py_UOpsSymType **limit; + _Py_UOpsSymType *locals_and_stack[MAX_ABSTRACT_INTERP_SIZE]; +} _Py_UOpsAbstractInterpContext; + +static inline _Py_UOpsSymType* sym_new_unknown(_Py_UOpsAbstractInterpContext *ctx); + +// 0 on success, -1 on error. +static _Py_UOpsAbstractFrame * +ctx_frame_new( + _Py_UOpsAbstractInterpContext *ctx, + PyCodeObject *co, + _Py_UOpsSymType **localsplus_start, + int n_locals_already_filled, + int curr_stackentries +) +{ + assert(ctx->curr_frame_depth < MAX_ABSTRACT_FRAME_DEPTH); + _Py_UOpsAbstractFrame *frame = &ctx->frames[ctx->curr_frame_depth]; + + frame->stack_len = co->co_stacksize; + frame->locals_len = co->co_nlocalsplus; + + frame->locals = localsplus_start; + frame->stack = frame->locals + co->co_nlocalsplus; + frame->stack_pointer = frame->stack + curr_stackentries; + ctx->n_consumed = localsplus_start + (co->co_nlocalsplus + co->co_stacksize); + if (ctx->n_consumed >= ctx->limit) { + return NULL; + } + + + // Initialize with the initial state of all local variables + for (int i = n_locals_already_filled; i < co->co_nlocalsplus; i++) { + _Py_UOpsSymType *local = sym_new_unknown(ctx); + if (local == NULL) { + return NULL; + } + frame->locals[i] = local; + } + + + // Initialize the stack as well + for (int i = 0; i < curr_stackentries; i++) { + _Py_UOpsSymType *stackvar = sym_new_unknown(ctx); + if (stackvar == NULL) { + return NULL; + } + frame->stack[i] = stackvar; + } + + return frame; +} + +static void +abstractcontext_fini(_Py_UOpsAbstractInterpContext *ctx) +{ + if (ctx == NULL) { + return; + } + ctx->curr_frame_depth = 0; + int tys = ctx->t_arena.ty_curr_number; + for (int i = 0; i < tys; i++) { + Py_CLEAR(ctx->t_arena.arena[i].const_val); + } +} + +static int +abstractcontext_init( + _Py_UOpsAbstractInterpContext *ctx, + PyCodeObject *co, + int curr_stacklen, + int ir_entries +) +{ + ctx->limit = ctx->locals_and_stack + MAX_ABSTRACT_INTERP_SIZE; + ctx->n_consumed = ctx->locals_and_stack; +#ifdef Py_DEBUG // Aids debugging a little. There should never be NULL in the abstract interpreter. + for (int i = 0 ; i < MAX_ABSTRACT_INTERP_SIZE; i++) { + ctx->locals_and_stack[i] = NULL; + } +#endif + + // Setup the arena for sym expressions. + ctx->t_arena.ty_curr_number = 0; + ctx->t_arena.ty_max_number = TY_ARENA_SIZE; + + // Frame setup + ctx->curr_frame_depth = 0; + _Py_UOpsAbstractFrame *frame = ctx_frame_new(ctx, co, ctx->n_consumed, 0, curr_stacklen); + if (frame == NULL) { + return -1; + } + ctx->curr_frame_depth++; + ctx->frame = frame; + return 0; +} + + +static int +ctx_frame_pop( + _Py_UOpsAbstractInterpContext *ctx +) +{ + _Py_UOpsAbstractFrame *frame = ctx->frame; + + ctx->n_consumed = frame->locals; + ctx->curr_frame_depth--; + assert(ctx->curr_frame_depth >= 1); + ctx->frame = &ctx->frames[ctx->curr_frame_depth - 1]; + + return 0; +} + + +// Takes a borrowed reference to const_val, turns that into a strong reference. +static _Py_UOpsSymType* +sym_new(_Py_UOpsAbstractInterpContext *ctx, + PyObject *const_val) +{ + _Py_UOpsSymType *self = &ctx->t_arena.arena[ctx->t_arena.ty_curr_number]; + if (ctx->t_arena.ty_curr_number >= ctx->t_arena.ty_max_number) { + OPT_STAT_INC(optimizer_failure_reason_no_memory); + DPRINTF(1, "out of space for symbolic expression type\n"); + return NULL; + } + ctx->t_arena.ty_curr_number++; + self->const_val = NULL; + self->typ = NULL; + self->flags = 0; + + if (const_val != NULL) { + self->const_val = Py_NewRef(const_val); + } + + return self; +} + +static inline void +sym_set_flag(_Py_UOpsSymType *sym, int flag) +{ + sym->flags |= flag; +} + +static inline void +sym_clear_flag(_Py_UOpsSymType *sym, int flag) +{ + sym->flags &= (~flag); +} + +static inline bool +sym_has_flag(_Py_UOpsSymType *sym, int flag) +{ + return (sym->flags & flag) != 0; +} + +static inline bool +sym_is_known(_Py_UOpsSymType *sym) +{ + return sym_has_flag(sym, KNOWN); +} + +static inline bool +sym_is_not_null(_Py_UOpsSymType *sym) +{ + return (sym->flags & (IS_NULL | NOT_NULL)) == NOT_NULL; +} + +static inline bool +sym_is_null(_Py_UOpsSymType *sym) +{ + return (sym->flags & (IS_NULL | NOT_NULL)) == IS_NULL; +} + +static inline void +sym_set_type(_Py_UOpsSymType *sym, PyTypeObject *tp) +{ + assert(PyType_Check(tp)); + sym->typ = tp; + sym_set_flag(sym, KNOWN); + sym_set_flag(sym, NOT_NULL); +} + +static inline void +sym_set_null(_Py_UOpsSymType *sym) +{ + sym_set_flag(sym, IS_NULL); + sym_set_flag(sym, KNOWN); +} + + +static inline _Py_UOpsSymType* +sym_new_unknown(_Py_UOpsAbstractInterpContext *ctx) +{ + return sym_new(ctx,NULL); +} + +static inline _Py_UOpsSymType* +sym_new_known_notnull(_Py_UOpsAbstractInterpContext *ctx) +{ + _Py_UOpsSymType *res = sym_new_unknown(ctx); + if (res == NULL) { + return NULL; + } + sym_set_flag(res, NOT_NULL); + return res; +} + +static inline _Py_UOpsSymType* +sym_new_known_type(_Py_UOpsAbstractInterpContext *ctx, + PyTypeObject *typ) +{ + _Py_UOpsSymType *res = sym_new(ctx,NULL); + if (res == NULL) { + return NULL; + } + sym_set_type(res, typ); + return res; +} + +// Takes a borrowed reference to const_val. +static inline _Py_UOpsSymType* +sym_new_const(_Py_UOpsAbstractInterpContext *ctx, PyObject *const_val) +{ + assert(const_val != NULL); + _Py_UOpsSymType *temp = sym_new( + ctx, + const_val + ); + if (temp == NULL) { + return NULL; + } + sym_set_type(temp, Py_TYPE(const_val)); + sym_set_flag(temp, TRUE_CONST); + sym_set_flag(temp, KNOWN); + sym_set_flag(temp, NOT_NULL); + return temp; +} + +static _Py_UOpsSymType* +sym_new_null(_Py_UOpsAbstractInterpContext *ctx) +{ + _Py_UOpsSymType *null_sym = sym_new_unknown(ctx); + if (null_sym == NULL) { + return NULL; + } + sym_set_null(null_sym); + return null_sym; +} + + +static inline bool +sym_matches_type(_Py_UOpsSymType *sym, PyTypeObject *typ) +{ + assert(typ == NULL || PyType_Check(typ)); + if (!sym_has_flag(sym, KNOWN)) { + return false; + } + return sym->typ == typ; +} + + +static inline bool +op_is_end(uint32_t opcode) +{ + return opcode == _EXIT_TRACE || opcode == _JUMP_TO_TOP; +} static int get_mutations(PyObject* dict) { @@ -199,14 +555,138 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, builtins = func->func_builtins; break; } - case _JUMP_TO_TOP: - case _EXIT_TRACE: - return 1; + default: + if (op_is_end(opcode)) { + return 1; + } + break; + } + } + return 0; +} + + + +#define STACK_LEVEL() ((int)(stack_pointer - ctx->frame->stack)) + +#define GETLOCAL(idx) ((ctx->frame->locals[idx])) + +#define REPLACE_OP(INST, OP, ARG, OPERAND) \ + INST->opcode = OP; \ + INST->oparg = ARG; \ + INST->operand = OPERAND; + +#define _LOAD_ATTR_NOT_NULL \ + do { \ + attr = sym_new_known_notnull(ctx); \ + if (attr == NULL) { \ + goto error; \ + } \ + null = sym_new_null(ctx); \ + if (null == NULL) { \ + goto error; \ + } \ + } while (0); + + +/* 1 for success, 0 for not ready, cannot error at the moment. */ +static int +uop_redundancy_eliminator( + PyCodeObject *co, + _PyUOpInstruction *trace, + int trace_len, + int curr_stacklen +) +{ + + _Py_UOpsAbstractInterpContext context; + _Py_UOpsAbstractInterpContext *ctx = &context; + + if (abstractcontext_init( + ctx, + co, curr_stacklen, + trace_len) < 0) { + goto out_of_space; + } + + for (_PyUOpInstruction *this_instr = trace; + this_instr < trace + trace_len && !op_is_end(this_instr->opcode); + this_instr++) { + + int oparg = this_instr->oparg; + uint32_t opcode = this_instr->opcode; + + _Py_UOpsSymType **stack_pointer = ctx->frame->stack_pointer; + + DPRINTF(3, "Abstract interpreting %s:%d ", + _PyOpcode_uop_name[opcode], + oparg); + switch (opcode) { +#include "tier2_redundancy_eliminator_cases.c.h" + + default: + DPRINTF(1, "Unknown opcode in abstract interpreter\n"); + Py_UNREACHABLE(); } + assert(ctx->frame != NULL); + DPRINTF(3, " stack_level %d\n", STACK_LEVEL()); + ctx->frame->stack_pointer = stack_pointer; + assert(STACK_LEVEL() >= 0); } + + abstractcontext_fini(ctx); + return 1; + +out_of_space: + DPRINTF(1, "Out of space in abstract interpreter\n"); + abstractcontext_fini(ctx); + return 0; + +error: + DPRINTF(1, "Encountered error in abstract interpreter\n"); + abstractcontext_fini(ctx); return 0; } + +static void +remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size) +{ + int last_set_ip = -1; + bool maybe_invalid = false; + for (int pc = 0; pc < buffer_size; pc++) { + int opcode = buffer[pc].opcode; + if (opcode == _SET_IP) { + buffer[pc].opcode = NOP; + last_set_ip = pc; + } + else if (opcode == _CHECK_VALIDITY) { + if (maybe_invalid) { + maybe_invalid = false; + } + else { + buffer[pc].opcode = NOP; + } + } + else if (op_is_end(opcode)) { + break; + } + else { + if (_PyUop_Flags[opcode] & HAS_ESCAPES_FLAG) { + maybe_invalid = true; + if (last_set_ip >= 0) { + buffer[last_set_ip].opcode = _SET_IP; + } + } + if ((_PyUop_Flags[opcode] & HAS_ERROR_FLAG) || opcode == _PUSH_FRAME) { + if (last_set_ip >= 0) { + buffer[last_set_ip].opcode = _SET_IP; + } + } + } + } +} + static void peephole_opt(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, int buffer_size) { @@ -250,44 +730,9 @@ peephole_opt(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, int buffer_s } } -static void -remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size) -{ - int last_set_ip = -1; - bool maybe_invalid = false; - for (int pc = 0; pc < buffer_size; pc++) { - int opcode = buffer[pc].opcode; - if (opcode == _SET_IP) { - buffer[pc].opcode = NOP; - last_set_ip = pc; - } - else if (opcode == _CHECK_VALIDITY) { - if (maybe_invalid) { - maybe_invalid = false; - } - else { - buffer[pc].opcode = NOP; - } - } - else if (opcode == _JUMP_TO_TOP || opcode == _EXIT_TRACE) { - break; - } - else { - if (_PyUop_Flags[opcode] & HAS_ESCAPES_FLAG) { - maybe_invalid = true; - if (last_set_ip >= 0) { - buffer[last_set_ip].opcode = _SET_IP; - } - } - if ((_PyUop_Flags[opcode] & HAS_ERROR_FLAG) || opcode == _PUSH_FRAME) { - if (last_set_ip >= 0) { - buffer[last_set_ip].opcode = _SET_IP; - } - } - } - } -} - +// 0 - failure, no error raised, just fall back to Tier 1 +// -1 - failure, and raise error +// 1 - optimizer success int _Py_uop_analyze_and_optimize( _PyInterpreterFrame *frame, @@ -297,11 +742,33 @@ _Py_uop_analyze_and_optimize( _PyBloomFilter *dependencies ) { + OPT_STAT_INC(optimizer_attempts); + int err = remove_globals(frame, buffer, buffer_size, dependencies); - if (err <= 0) { - return err; + if (err == 0) { + goto not_ready; + } + if (err < 0) { + goto error; } + peephole_opt(frame, buffer, buffer_size); + + err = uop_redundancy_eliminator( + (PyCodeObject *)frame->f_executable, buffer, + buffer_size, curr_stacklen); + + if (err == 0) { + goto not_ready; + } + assert(err == 1); + remove_unneeded_uops(buffer, buffer_size); + + OPT_STAT_INC(optimizer_successes); return 1; +not_ready: + return 0; +error: + return -1; } diff --git a/Python/specialize.c b/Python/specialize.c index ea2638570f22d07..2256d79b387c565 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -240,6 +240,11 @@ print_optimization_stats(FILE *out, OptimizationStats *stats) print_histogram(out, "Trace run length", stats->trace_run_length_hist); print_histogram(out, "Optimized trace length", stats->optimized_trace_length_hist); + fprintf(out, "Optimization optimizer attempts: %" PRIu64 "\n", stats->optimizer_attempts); + fprintf(out, "Optimization optimizer successes: %" PRIu64 "\n", stats->optimizer_successes); + fprintf(out, "Optimization optimizer failure no memory: %" PRIu64 "\n", + stats->optimizer_failure_reason_no_memory); + const char* const* names; for (int i = 0; i < 512; i++) { if (i < 256) { diff --git a/Python/tier2_redundancy_eliminator_bytecodes.c b/Python/tier2_redundancy_eliminator_bytecodes.c new file mode 100644 index 000000000000000..3272b187f20d0eb --- /dev/null +++ b/Python/tier2_redundancy_eliminator_bytecodes.c @@ -0,0 +1,272 @@ +#include "Python.h" +#include "pycore_uops.h" +#include "pycore_uop_ids.h" + +#define op(name, ...) /* NAME is ignored */ + +typedef struct _Py_UOpsSymType _Py_UOpsSymType; +typedef struct _Py_UOpsAbstractInterpContext _Py_UOpsAbstractInterpContext; +typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame; + +static int +dummy_func(void) { + + PyCodeObject *code; + int oparg; + _Py_UOpsSymType *flag; + _Py_UOpsSymType *left; + _Py_UOpsSymType *right; + _Py_UOpsSymType *value; + _Py_UOpsSymType *res; + _Py_UOpsSymType *iter; + _Py_UOpsSymType *top; + _Py_UOpsSymType *bottom; + _Py_UOpsAbstractFrame *frame; + _Py_UOpsAbstractInterpContext *ctx; + _PyUOpInstruction *this_instr; + _PyBloomFilter *dependencies; + int modified; + +// BEGIN BYTECODES // + + op(_LOAD_FAST_CHECK, (-- value)) { + value = GETLOCAL(oparg); + // We guarantee this will error - just bail and don't optimize it. + if (sym_is_null(value)) { + goto out_of_space; + } + } + + op(_LOAD_FAST, (-- value)) { + value = GETLOCAL(oparg); + } + + op(_LOAD_FAST_AND_CLEAR, (-- value)) { + value = GETLOCAL(oparg); + _Py_UOpsSymType *temp = sym_new_null(ctx); + if (temp == NULL) { + goto out_of_space; + } + GETLOCAL(oparg) = temp; + } + + op(_STORE_FAST, (value --)) { + GETLOCAL(oparg) = value; + } + + op(_PUSH_NULL, (-- res)) { + res = sym_new_null(ctx); + if (res == NULL) { + goto out_of_space; + }; + } + + op(_GUARD_BOTH_INT, (left, right -- left, right)) { + if (sym_matches_type(left, &PyLong_Type) && + sym_matches_type(right, &PyLong_Type)) { + REPLACE_OP(this_instr, _NOP, 0, 0); + } + sym_set_type(left, &PyLong_Type); + sym_set_type(right, &PyLong_Type); + } + + op(_GUARD_BOTH_FLOAT, (left, right -- left, right)) { + if (sym_matches_type(left, &PyFloat_Type) && + sym_matches_type(right, &PyFloat_Type)) { + REPLACE_OP(this_instr, _NOP, 0 ,0); + } + sym_set_type(left, &PyFloat_Type); + sym_set_type(right, &PyFloat_Type); + } + + + op(_BINARY_OP_ADD_INT, (left, right -- res)) { + // TODO constant propagation + (void)left; + (void)right; + res = sym_new_known_type(ctx, &PyLong_Type); + if (res == NULL) { + goto out_of_space; + } + } + + op(_LOAD_CONST, (-- value)) { + // There should be no LOAD_CONST. It should be all + // replaced by peephole_opt. + Py_UNREACHABLE(); + } + + op(_LOAD_CONST_INLINE, (ptr/4 -- value)) { + value = sym_new_const(ctx, ptr); + if (value == NULL) { + goto out_of_space; + } + } + + op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) { + value = sym_new_const(ctx, ptr); + if (value == NULL) { + goto out_of_space; + } + } + + op(_LOAD_CONST_INLINE_WITH_NULL, (ptr/4 -- value, null)) { + value = sym_new_const(ctx, ptr); + if (value == NULL) { + goto out_of_space; + } + null = sym_new_null(ctx); + if (null == NULL) { + goto out_of_space; + } + } + + op(_LOAD_CONST_INLINE_BORROW_WITH_NULL, (ptr/4 -- value, null)) { + value = sym_new_const(ctx, ptr); + if (value == NULL) { + goto out_of_space; + } + null = sym_new_null(ctx); + if (null == NULL) { + goto out_of_space; + } + } + + + op(_COPY, (bottom, unused[oparg-1] -- bottom, unused[oparg-1], top)) { + assert(oparg > 0); + top = bottom; + } + + op(_SWAP, (bottom, unused[oparg-2], top -- + top, unused[oparg-2], bottom)) { + } + + op(_LOAD_ATTR_INSTANCE_VALUE, (index/1, owner -- attr, null if (oparg & 1))) { + _LOAD_ATTR_NOT_NULL + (void)index; + (void)owner; + } + + op(_LOAD_ATTR_MODULE, (index/1, owner -- attr, null if (oparg & 1))) { + _LOAD_ATTR_NOT_NULL + (void)index; + (void)owner; + } + + op(_LOAD_ATTR_WITH_HINT, (hint/1, owner -- attr, null if (oparg & 1))) { + _LOAD_ATTR_NOT_NULL + (void)hint; + (void)owner; + } + + op(_LOAD_ATTR_SLOT, (index/1, owner -- attr, null if (oparg & 1))) { + _LOAD_ATTR_NOT_NULL + (void)index; + (void)owner; + } + + op(_LOAD_ATTR_CLASS, (descr/4, owner -- attr, null if (oparg & 1))) { + _LOAD_ATTR_NOT_NULL + (void)descr; + (void)owner; + } + + op(_CHECK_FUNCTION_EXACT_ARGS, (func_version/2, callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) { + sym_set_type(callable, &PyFunction_Type); + (void)self_or_null; + (void)func_version; + } + + op(_CHECK_CALL_BOUND_METHOD_EXACT_ARGS, (callable, null, unused[oparg] -- callable, null, unused[oparg])) { + sym_set_null(null); + sym_set_type(callable, &PyMethod_Type); + } + + op(_INIT_CALL_PY_EXACT_ARGS, (callable, self_or_null, args[oparg] -- new_frame: _Py_UOpsAbstractFrame *)) { + int argcount = oparg; + + (void)callable; + + PyFunctionObject *func = (PyFunctionObject *)(this_instr + 2)->operand; + if (func == NULL) { + goto error; + } + PyCodeObject *co = (PyCodeObject *)func->func_code; + + assert(self_or_null != NULL); + assert(args != NULL); + if (sym_is_not_null(self_or_null)) { + // Bound method fiddling, same as _INIT_CALL_PY_EXACT_ARGS in VM + args--; + argcount++; + } + + _Py_UOpsSymType **localsplus_start = ctx->n_consumed; + int n_locals_already_filled = 0; + // Can determine statically, so we interleave the new locals + // and make the current stack the new locals. + // This also sets up for true call inlining. + if (sym_is_known(self_or_null)) { + localsplus_start = args; + n_locals_already_filled = argcount; + } + new_frame = ctx_frame_new(ctx, co, localsplus_start, n_locals_already_filled, 0); + if (new_frame == NULL){ + goto out_of_space; + } + } + + op(_POP_FRAME, (retval -- res)) { + SYNC_SP(); + ctx->frame->stack_pointer = stack_pointer; + ctx_frame_pop(ctx); + stack_pointer = ctx->frame->stack_pointer; + res = retval; + } + + op(_PUSH_FRAME, (new_frame: _Py_UOpsAbstractFrame * -- unused if (0))) { + SYNC_SP(); + ctx->frame->stack_pointer = stack_pointer; + ctx->frame = new_frame; + ctx->curr_frame_depth++; + stack_pointer = new_frame->stack_pointer; + } + + op(_UNPACK_SEQUENCE, (seq -- values[oparg])) { + /* This has to be done manually */ + (void)seq; + for (int i = 0; i < oparg; i++) { + values[i] = sym_new_unknown(ctx); + if (values[i] == NULL) { + goto out_of_space; + } + } + } + + op(_UNPACK_EX, (seq -- values[oparg & 0xFF], unused, unused[oparg >> 8])) { + /* This has to be done manually */ + (void)seq; + int totalargs = (oparg & 0xFF) + (oparg >> 8) + 1; + for (int i = 0; i < totalargs; i++) { + values[i] = sym_new_unknown(ctx); + if (values[i] == NULL) { + goto out_of_space; + } + } + } + + op(_ITER_NEXT_RANGE, (iter -- iter, next)) { + next = sym_new_known_type(ctx, &PyLong_Type); + if (next == NULL) { + goto out_of_space; + } + (void)iter; + } + + + + +// END BYTECODES // + +} \ No newline at end of file diff --git a/Python/tier2_redundancy_eliminator_cases.c.h b/Python/tier2_redundancy_eliminator_cases.c.h new file mode 100644 index 000000000000000..77a7f5b2360c3b5 --- /dev/null +++ b/Python/tier2_redundancy_eliminator_cases.c.h @@ -0,0 +1,1676 @@ +// This file is generated by Tools/cases_generator/tier2_abstract_generator.py +// from: +// Python/tier2_redundancy_eliminator_bytecodes.c +// Do not edit! + + case _NOP: { + break; + } + + case _RESUME_CHECK: { + break; + } + + /* _INSTRUMENTED_RESUME is not a viable micro-op for tier 2 */ + + case _LOAD_FAST_CHECK: { + _Py_UOpsSymType *value; + value = GETLOCAL(oparg); + // We guarantee this will error - just bail and don't optimize it. + if (sym_is_null(value)) { + goto out_of_space; + } + stack_pointer[0] = value; + stack_pointer += 1; + break; + } + + case _LOAD_FAST: { + _Py_UOpsSymType *value; + value = GETLOCAL(oparg); + stack_pointer[0] = value; + stack_pointer += 1; + break; + } + + case _LOAD_FAST_AND_CLEAR: { + _Py_UOpsSymType *value; + value = GETLOCAL(oparg); + _Py_UOpsSymType *temp = sym_new_null(ctx); + if (temp == NULL) { + goto out_of_space; + } + GETLOCAL(oparg) = temp; + stack_pointer[0] = value; + stack_pointer += 1; + break; + } + + case _LOAD_CONST: { + _Py_UOpsSymType *value; + // There should be no LOAD_CONST. It should be all + // replaced by peephole_opt. + Py_UNREACHABLE(); + stack_pointer[0] = value; + stack_pointer += 1; + break; + } + + case _STORE_FAST: { + _Py_UOpsSymType *value; + value = stack_pointer[-1]; + GETLOCAL(oparg) = value; + stack_pointer += -1; + break; + } + + case _POP_TOP: { + stack_pointer += -1; + break; + } + + case _PUSH_NULL: { + _Py_UOpsSymType *res; + res = sym_new_null(ctx); + if (res == NULL) { + goto out_of_space; + }; + stack_pointer[0] = res; + stack_pointer += 1; + break; + } + + case _END_SEND: { + _Py_UOpsSymType *value; + value = sym_new_unknown(ctx); + if (value == NULL) goto out_of_space; + stack_pointer[-2] = value; + stack_pointer += -1; + break; + } + + case _UNARY_NEGATIVE: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-1] = res; + break; + } + + case _UNARY_NOT: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-1] = res; + break; + } + + case _TO_BOOL: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-1] = res; + break; + } + + case _TO_BOOL_BOOL: { + break; + } + + case _TO_BOOL_INT: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-1] = res; + break; + } + + case _TO_BOOL_LIST: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-1] = res; + break; + } + + case _TO_BOOL_NONE: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-1] = res; + break; + } + + case _TO_BOOL_STR: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-1] = res; + break; + } + + case _TO_BOOL_ALWAYS_TRUE: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-1] = res; + break; + } + + case _UNARY_INVERT: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-1] = res; + break; + } + + case _GUARD_BOTH_INT: { + _Py_UOpsSymType *right; + _Py_UOpsSymType *left; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + if (sym_matches_type(left, &PyLong_Type) && + sym_matches_type(right, &PyLong_Type)) { + REPLACE_OP(this_instr, _NOP, 0, 0); + } + sym_set_type(left, &PyLong_Type); + sym_set_type(right, &PyLong_Type); + break; + } + + case _BINARY_OP_MULTIPLY_INT: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _BINARY_OP_ADD_INT: { + _Py_UOpsSymType *right; + _Py_UOpsSymType *left; + _Py_UOpsSymType *res; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + // TODO constant propagation + (void)left; + (void)right; + res = sym_new_known_type(ctx, &PyLong_Type); + if (res == NULL) { + goto out_of_space; + } + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _BINARY_OP_SUBTRACT_INT: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _GUARD_BOTH_FLOAT: { + _Py_UOpsSymType *right; + _Py_UOpsSymType *left; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + if (sym_matches_type(left, &PyFloat_Type) && + sym_matches_type(right, &PyFloat_Type)) { + REPLACE_OP(this_instr, _NOP, 0 ,0); + } + sym_set_type(left, &PyFloat_Type); + sym_set_type(right, &PyFloat_Type); + break; + } + + case _BINARY_OP_MULTIPLY_FLOAT: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _BINARY_OP_ADD_FLOAT: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _BINARY_OP_SUBTRACT_FLOAT: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _GUARD_BOTH_UNICODE: { + break; + } + + case _BINARY_OP_ADD_UNICODE: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _BINARY_SUBSCR: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _BINARY_SLICE: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-3] = res; + stack_pointer += -2; + break; + } + + case _STORE_SLICE: { + stack_pointer += -4; + break; + } + + case _BINARY_SUBSCR_LIST_INT: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _BINARY_SUBSCR_STR_INT: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _BINARY_SUBSCR_TUPLE_INT: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _BINARY_SUBSCR_DICT: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + /* _BINARY_SUBSCR_GETITEM is not a viable micro-op for tier 2 */ + + case _LIST_APPEND: { + stack_pointer += -1; + break; + } + + case _SET_ADD: { + stack_pointer += -1; + break; + } + + case _STORE_SUBSCR: { + stack_pointer += -3; + break; + } + + case _STORE_SUBSCR_LIST_INT: { + stack_pointer += -3; + break; + } + + case _STORE_SUBSCR_DICT: { + stack_pointer += -3; + break; + } + + case _DELETE_SUBSCR: { + stack_pointer += -2; + break; + } + + case _CALL_INTRINSIC_1: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-1] = res; + break; + } + + case _CALL_INTRINSIC_2: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _POP_FRAME: { + _Py_UOpsSymType *retval; + _Py_UOpsSymType *res; + retval = stack_pointer[-1]; + stack_pointer += -1; + ctx->frame->stack_pointer = stack_pointer; + ctx_frame_pop(ctx); + stack_pointer = ctx->frame->stack_pointer; + res = retval; + stack_pointer[0] = res; + stack_pointer += 1; + break; + } + + /* _INSTRUMENTED_RETURN_VALUE is not a viable micro-op for tier 2 */ + + /* _INSTRUMENTED_RETURN_CONST is not a viable micro-op for tier 2 */ + + case _GET_AITER: { + _Py_UOpsSymType *iter; + iter = sym_new_unknown(ctx); + if (iter == NULL) goto out_of_space; + stack_pointer[-1] = iter; + break; + } + + case _GET_ANEXT: { + _Py_UOpsSymType *awaitable; + awaitable = sym_new_unknown(ctx); + if (awaitable == NULL) goto out_of_space; + stack_pointer[0] = awaitable; + stack_pointer += 1; + break; + } + + case _GET_AWAITABLE: { + _Py_UOpsSymType *iter; + iter = sym_new_unknown(ctx); + if (iter == NULL) goto out_of_space; + stack_pointer[-1] = iter; + break; + } + + /* _SEND is not a viable micro-op for tier 2 */ + + /* _SEND_GEN is not a viable micro-op for tier 2 */ + + /* _INSTRUMENTED_YIELD_VALUE is not a viable micro-op for tier 2 */ + + case _POP_EXCEPT: { + stack_pointer += -1; + break; + } + + case _LOAD_ASSERTION_ERROR: { + _Py_UOpsSymType *value; + value = sym_new_unknown(ctx); + if (value == NULL) goto out_of_space; + stack_pointer[0] = value; + stack_pointer += 1; + break; + } + + case _LOAD_BUILD_CLASS: { + _Py_UOpsSymType *bc; + bc = sym_new_unknown(ctx); + if (bc == NULL) goto out_of_space; + stack_pointer[0] = bc; + stack_pointer += 1; + break; + } + + case _STORE_NAME: { + stack_pointer += -1; + break; + } + + case _DELETE_NAME: { + break; + } + + case _UNPACK_SEQUENCE: { + _Py_UOpsSymType *seq; + _Py_UOpsSymType **values; + seq = stack_pointer[-1]; + values = &stack_pointer[-1]; + /* This has to be done manually */ + (void)seq; + for (int i = 0; i < oparg; i++) { + values[i] = sym_new_unknown(ctx); + if (values[i] == NULL) { + goto out_of_space; + } + } + stack_pointer += -1 + oparg; + break; + } + + case _UNPACK_SEQUENCE_TWO_TUPLE: { + _Py_UOpsSymType **values; + values = &stack_pointer[-1]; + for (int _i = oparg; --_i >= 0;) { + values[_i] = sym_new_unknown(ctx); + if (values[_i] == NULL) goto out_of_space; + } + stack_pointer += -1 + oparg; + break; + } + + case _UNPACK_SEQUENCE_TUPLE: { + _Py_UOpsSymType **values; + values = &stack_pointer[-1]; + for (int _i = oparg; --_i >= 0;) { + values[_i] = sym_new_unknown(ctx); + if (values[_i] == NULL) goto out_of_space; + } + stack_pointer += -1 + oparg; + break; + } + + case _UNPACK_SEQUENCE_LIST: { + _Py_UOpsSymType **values; + values = &stack_pointer[-1]; + for (int _i = oparg; --_i >= 0;) { + values[_i] = sym_new_unknown(ctx); + if (values[_i] == NULL) goto out_of_space; + } + stack_pointer += -1 + oparg; + break; + } + + case _UNPACK_EX: { + _Py_UOpsSymType *seq; + _Py_UOpsSymType **values; + seq = stack_pointer[-1]; + values = &stack_pointer[-1]; + /* This has to be done manually */ + (void)seq; + int totalargs = (oparg & 0xFF) + (oparg >> 8) + 1; + for (int i = 0; i < totalargs; i++) { + values[i] = sym_new_unknown(ctx); + if (values[i] == NULL) { + goto out_of_space; + } + } + stack_pointer += (oparg >> 8) + (oparg & 0xFF); + break; + } + + case _STORE_ATTR: { + stack_pointer += -2; + break; + } + + case _DELETE_ATTR: { + stack_pointer += -1; + break; + } + + case _STORE_GLOBAL: { + stack_pointer += -1; + break; + } + + case _DELETE_GLOBAL: { + break; + } + + case _LOAD_LOCALS: { + _Py_UOpsSymType *locals; + locals = sym_new_unknown(ctx); + if (locals == NULL) goto out_of_space; + stack_pointer[0] = locals; + stack_pointer += 1; + break; + } + + case _LOAD_FROM_DICT_OR_GLOBALS: { + _Py_UOpsSymType *v; + v = sym_new_unknown(ctx); + if (v == NULL) goto out_of_space; + stack_pointer[-1] = v; + break; + } + + case _LOAD_NAME: { + _Py_UOpsSymType *v; + v = sym_new_unknown(ctx); + if (v == NULL) goto out_of_space; + stack_pointer[0] = v; + stack_pointer += 1; + break; + } + + case _LOAD_GLOBAL: { + _Py_UOpsSymType *res; + _Py_UOpsSymType *null = NULL; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + null = sym_new_null(ctx); + if (null == NULL) goto out_of_space; + stack_pointer[0] = res; + if (oparg & 1) stack_pointer[1] = null; + stack_pointer += 1 + (oparg & 1); + break; + } + + case _GUARD_GLOBALS_VERSION: { + break; + } + + case _GUARD_BUILTINS_VERSION: { + break; + } + + case _LOAD_GLOBAL_MODULE: { + _Py_UOpsSymType *res; + _Py_UOpsSymType *null = NULL; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + null = sym_new_null(ctx); + if (null == NULL) goto out_of_space; + stack_pointer[0] = res; + if (oparg & 1) stack_pointer[1] = null; + stack_pointer += 1 + (oparg & 1); + break; + } + + case _LOAD_GLOBAL_BUILTINS: { + _Py_UOpsSymType *res; + _Py_UOpsSymType *null = NULL; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + null = sym_new_null(ctx); + if (null == NULL) goto out_of_space; + stack_pointer[0] = res; + if (oparg & 1) stack_pointer[1] = null; + stack_pointer += 1 + (oparg & 1); + break; + } + + case _DELETE_FAST: { + break; + } + + case _MAKE_CELL: { + break; + } + + case _DELETE_DEREF: { + break; + } + + case _LOAD_FROM_DICT_OR_DEREF: { + _Py_UOpsSymType *value; + value = sym_new_unknown(ctx); + if (value == NULL) goto out_of_space; + stack_pointer[-1] = value; + break; + } + + case _LOAD_DEREF: { + _Py_UOpsSymType *value; + value = sym_new_unknown(ctx); + if (value == NULL) goto out_of_space; + stack_pointer[0] = value; + stack_pointer += 1; + break; + } + + case _STORE_DEREF: { + stack_pointer += -1; + break; + } + + case _COPY_FREE_VARS: { + break; + } + + case _BUILD_STRING: { + _Py_UOpsSymType *str; + str = sym_new_unknown(ctx); + if (str == NULL) goto out_of_space; + stack_pointer[-oparg] = str; + stack_pointer += 1 - oparg; + break; + } + + case _BUILD_TUPLE: { + _Py_UOpsSymType *tup; + tup = sym_new_unknown(ctx); + if (tup == NULL) goto out_of_space; + stack_pointer[-oparg] = tup; + stack_pointer += 1 - oparg; + break; + } + + case _BUILD_LIST: { + _Py_UOpsSymType *list; + list = sym_new_unknown(ctx); + if (list == NULL) goto out_of_space; + stack_pointer[-oparg] = list; + stack_pointer += 1 - oparg; + break; + } + + case _LIST_EXTEND: { + stack_pointer += -1; + break; + } + + case _SET_UPDATE: { + stack_pointer += -1; + break; + } + + case _BUILD_SET: { + _Py_UOpsSymType *set; + set = sym_new_unknown(ctx); + if (set == NULL) goto out_of_space; + stack_pointer[-oparg] = set; + stack_pointer += 1 - oparg; + break; + } + + case _BUILD_MAP: { + _Py_UOpsSymType *map; + map = sym_new_unknown(ctx); + if (map == NULL) goto out_of_space; + stack_pointer[-oparg*2] = map; + stack_pointer += 1 - oparg*2; + break; + } + + case _SETUP_ANNOTATIONS: { + break; + } + + case _BUILD_CONST_KEY_MAP: { + _Py_UOpsSymType *map; + map = sym_new_unknown(ctx); + if (map == NULL) goto out_of_space; + stack_pointer[-1 - oparg] = map; + stack_pointer += -oparg; + break; + } + + case _DICT_UPDATE: { + stack_pointer += -1; + break; + } + + case _DICT_MERGE: { + stack_pointer += -1; + break; + } + + case _MAP_ADD: { + stack_pointer += -2; + break; + } + + /* _INSTRUMENTED_LOAD_SUPER_ATTR is not a viable micro-op for tier 2 */ + + case _LOAD_SUPER_ATTR_ATTR: { + _Py_UOpsSymType *attr; + attr = sym_new_unknown(ctx); + if (attr == NULL) goto out_of_space; + stack_pointer[-3] = attr; + stack_pointer += -2 + ((0) ? 1 : 0); + break; + } + + case _LOAD_SUPER_ATTR_METHOD: { + _Py_UOpsSymType *attr; + _Py_UOpsSymType *self_or_null; + attr = sym_new_unknown(ctx); + if (attr == NULL) goto out_of_space; + self_or_null = sym_new_unknown(ctx); + if (self_or_null == NULL) goto out_of_space; + stack_pointer[-3] = attr; + stack_pointer[-2] = self_or_null; + stack_pointer += -1; + break; + } + + case _LOAD_ATTR: { + _Py_UOpsSymType *attr; + _Py_UOpsSymType *self_or_null = NULL; + attr = sym_new_unknown(ctx); + if (attr == NULL) goto out_of_space; + self_or_null = sym_new_unknown(ctx); + if (self_or_null == NULL) goto out_of_space; + stack_pointer[-1] = attr; + if (oparg & 1) stack_pointer[0] = self_or_null; + stack_pointer += (oparg & 1); + break; + } + + case _GUARD_TYPE_VERSION: { + break; + } + + case _CHECK_MANAGED_OBJECT_HAS_VALUES: { + break; + } + + case _LOAD_ATTR_INSTANCE_VALUE: { + _Py_UOpsSymType *owner; + _Py_UOpsSymType *attr; + _Py_UOpsSymType *null = NULL; + owner = stack_pointer[-1]; + uint16_t index = (uint16_t)this_instr->operand; + _LOAD_ATTR_NOT_NULL + (void)index; + (void)owner; + stack_pointer[-1] = attr; + if (oparg & 1) stack_pointer[0] = null; + stack_pointer += (oparg & 1); + break; + } + + case _CHECK_ATTR_MODULE: { + break; + } + + case _LOAD_ATTR_MODULE: { + _Py_UOpsSymType *owner; + _Py_UOpsSymType *attr; + _Py_UOpsSymType *null = NULL; + owner = stack_pointer[-1]; + uint16_t index = (uint16_t)this_instr->operand; + _LOAD_ATTR_NOT_NULL + (void)index; + (void)owner; + stack_pointer[-1] = attr; + if (oparg & 1) stack_pointer[0] = null; + stack_pointer += (oparg & 1); + break; + } + + case _CHECK_ATTR_WITH_HINT: { + break; + } + + case _LOAD_ATTR_WITH_HINT: { + _Py_UOpsSymType *owner; + _Py_UOpsSymType *attr; + _Py_UOpsSymType *null = NULL; + owner = stack_pointer[-1]; + uint16_t hint = (uint16_t)this_instr->operand; + _LOAD_ATTR_NOT_NULL + (void)hint; + (void)owner; + stack_pointer[-1] = attr; + if (oparg & 1) stack_pointer[0] = null; + stack_pointer += (oparg & 1); + break; + } + + case _LOAD_ATTR_SLOT: { + _Py_UOpsSymType *owner; + _Py_UOpsSymType *attr; + _Py_UOpsSymType *null = NULL; + owner = stack_pointer[-1]; + uint16_t index = (uint16_t)this_instr->operand; + _LOAD_ATTR_NOT_NULL + (void)index; + (void)owner; + stack_pointer[-1] = attr; + if (oparg & 1) stack_pointer[0] = null; + stack_pointer += (oparg & 1); + break; + } + + case _CHECK_ATTR_CLASS: { + break; + } + + case _LOAD_ATTR_CLASS: { + _Py_UOpsSymType *owner; + _Py_UOpsSymType *attr; + _Py_UOpsSymType *null = NULL; + owner = stack_pointer[-1]; + PyObject *descr = (PyObject *)this_instr->operand; + _LOAD_ATTR_NOT_NULL + (void)descr; + (void)owner; + stack_pointer[-1] = attr; + if (oparg & 1) stack_pointer[0] = null; + stack_pointer += (oparg & 1); + break; + } + + /* _LOAD_ATTR_PROPERTY is not a viable micro-op for tier 2 */ + + /* _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN is not a viable micro-op for tier 2 */ + + case _GUARD_DORV_VALUES: { + break; + } + + case _STORE_ATTR_INSTANCE_VALUE: { + stack_pointer += -2; + break; + } + + /* _STORE_ATTR_WITH_HINT is not a viable micro-op for tier 2 */ + + case _STORE_ATTR_SLOT: { + stack_pointer += -2; + break; + } + + case _COMPARE_OP: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _COMPARE_OP_FLOAT: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _COMPARE_OP_INT: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _COMPARE_OP_STR: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _IS_OP: { + _Py_UOpsSymType *b; + b = sym_new_unknown(ctx); + if (b == NULL) goto out_of_space; + stack_pointer[-2] = b; + stack_pointer += -1; + break; + } + + case _CONTAINS_OP: { + _Py_UOpsSymType *b; + b = sym_new_unknown(ctx); + if (b == NULL) goto out_of_space; + stack_pointer[-2] = b; + stack_pointer += -1; + break; + } + + case _CHECK_EG_MATCH: { + _Py_UOpsSymType *rest; + _Py_UOpsSymType *match; + rest = sym_new_unknown(ctx); + if (rest == NULL) goto out_of_space; + match = sym_new_unknown(ctx); + if (match == NULL) goto out_of_space; + stack_pointer[-2] = rest; + stack_pointer[-1] = match; + break; + } + + case _CHECK_EXC_MATCH: { + _Py_UOpsSymType *b; + b = sym_new_unknown(ctx); + if (b == NULL) goto out_of_space; + stack_pointer[-1] = b; + break; + } + + /* _JUMP_BACKWARD is not a viable micro-op for tier 2 */ + + /* _POP_JUMP_IF_FALSE is not a viable micro-op for tier 2 */ + + /* _POP_JUMP_IF_TRUE is not a viable micro-op for tier 2 */ + + case _IS_NONE: { + _Py_UOpsSymType *b; + b = sym_new_unknown(ctx); + if (b == NULL) goto out_of_space; + stack_pointer[-1] = b; + break; + } + + case _GET_LEN: { + _Py_UOpsSymType *len_o; + len_o = sym_new_unknown(ctx); + if (len_o == NULL) goto out_of_space; + stack_pointer[0] = len_o; + stack_pointer += 1; + break; + } + + case _MATCH_CLASS: { + _Py_UOpsSymType *attrs; + attrs = sym_new_unknown(ctx); + if (attrs == NULL) goto out_of_space; + stack_pointer[-3] = attrs; + stack_pointer += -2; + break; + } + + case _MATCH_MAPPING: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[0] = res; + stack_pointer += 1; + break; + } + + case _MATCH_SEQUENCE: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[0] = res; + stack_pointer += 1; + break; + } + + case _MATCH_KEYS: { + _Py_UOpsSymType *values_or_none; + values_or_none = sym_new_unknown(ctx); + if (values_or_none == NULL) goto out_of_space; + stack_pointer[0] = values_or_none; + stack_pointer += 1; + break; + } + + case _GET_ITER: { + _Py_UOpsSymType *iter; + iter = sym_new_unknown(ctx); + if (iter == NULL) goto out_of_space; + stack_pointer[-1] = iter; + break; + } + + case _GET_YIELD_FROM_ITER: { + _Py_UOpsSymType *iter; + iter = sym_new_unknown(ctx); + if (iter == NULL) goto out_of_space; + stack_pointer[-1] = iter; + break; + } + + /* _FOR_ITER is not a viable micro-op for tier 2 */ + + case _FOR_ITER_TIER_TWO: { + _Py_UOpsSymType *next; + next = sym_new_unknown(ctx); + if (next == NULL) goto out_of_space; + stack_pointer[0] = next; + stack_pointer += 1; + break; + } + + /* _INSTRUMENTED_FOR_ITER is not a viable micro-op for tier 2 */ + + case _ITER_CHECK_LIST: { + break; + } + + /* _ITER_JUMP_LIST is not a viable micro-op for tier 2 */ + + case _GUARD_NOT_EXHAUSTED_LIST: { + break; + } + + case _ITER_NEXT_LIST: { + _Py_UOpsSymType *next; + next = sym_new_unknown(ctx); + if (next == NULL) goto out_of_space; + stack_pointer[0] = next; + stack_pointer += 1; + break; + } + + case _ITER_CHECK_TUPLE: { + break; + } + + /* _ITER_JUMP_TUPLE is not a viable micro-op for tier 2 */ + + case _GUARD_NOT_EXHAUSTED_TUPLE: { + break; + } + + case _ITER_NEXT_TUPLE: { + _Py_UOpsSymType *next; + next = sym_new_unknown(ctx); + if (next == NULL) goto out_of_space; + stack_pointer[0] = next; + stack_pointer += 1; + break; + } + + case _ITER_CHECK_RANGE: { + break; + } + + /* _ITER_JUMP_RANGE is not a viable micro-op for tier 2 */ + + case _GUARD_NOT_EXHAUSTED_RANGE: { + break; + } + + case _ITER_NEXT_RANGE: { + _Py_UOpsSymType *iter; + _Py_UOpsSymType *next; + iter = stack_pointer[-1]; + next = sym_new_known_type(ctx, &PyLong_Type); + if (next == NULL) { + goto out_of_space; + } + (void)iter; + stack_pointer[0] = next; + stack_pointer += 1; + break; + } + + /* _FOR_ITER_GEN is not a viable micro-op for tier 2 */ + + case _BEFORE_ASYNC_WITH: { + _Py_UOpsSymType *exit; + _Py_UOpsSymType *res; + exit = sym_new_unknown(ctx); + if (exit == NULL) goto out_of_space; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-1] = exit; + stack_pointer[0] = res; + stack_pointer += 1; + break; + } + + case _BEFORE_WITH: { + _Py_UOpsSymType *exit; + _Py_UOpsSymType *res; + exit = sym_new_unknown(ctx); + if (exit == NULL) goto out_of_space; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-1] = exit; + stack_pointer[0] = res; + stack_pointer += 1; + break; + } + + case _WITH_EXCEPT_START: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[0] = res; + stack_pointer += 1; + break; + } + + case _PUSH_EXC_INFO: { + _Py_UOpsSymType *prev_exc; + _Py_UOpsSymType *new_exc; + prev_exc = sym_new_unknown(ctx); + if (prev_exc == NULL) goto out_of_space; + new_exc = sym_new_unknown(ctx); + if (new_exc == NULL) goto out_of_space; + stack_pointer[-1] = prev_exc; + stack_pointer[0] = new_exc; + stack_pointer += 1; + break; + } + + case _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT: { + break; + } + + case _GUARD_KEYS_VERSION: { + break; + } + + case _LOAD_ATTR_METHOD_WITH_VALUES: { + _Py_UOpsSymType *attr; + _Py_UOpsSymType *self = NULL; + attr = sym_new_unknown(ctx); + if (attr == NULL) goto out_of_space; + self = sym_new_unknown(ctx); + if (self == NULL) goto out_of_space; + stack_pointer[-1] = attr; + if (1) stack_pointer[0] = self; + stack_pointer += ((1) ? 1 : 0); + break; + } + + case _LOAD_ATTR_METHOD_NO_DICT: { + _Py_UOpsSymType *attr; + _Py_UOpsSymType *self = NULL; + attr = sym_new_unknown(ctx); + if (attr == NULL) goto out_of_space; + self = sym_new_unknown(ctx); + if (self == NULL) goto out_of_space; + stack_pointer[-1] = attr; + if (1) stack_pointer[0] = self; + stack_pointer += ((1) ? 1 : 0); + break; + } + + case _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: { + _Py_UOpsSymType *attr; + attr = sym_new_unknown(ctx); + if (attr == NULL) goto out_of_space; + stack_pointer[-1] = attr; + stack_pointer += ((0) ? 1 : 0); + break; + } + + case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: { + _Py_UOpsSymType *attr; + attr = sym_new_unknown(ctx); + if (attr == NULL) goto out_of_space; + stack_pointer[-1] = attr; + stack_pointer += ((0) ? 1 : 0); + break; + } + + case _CHECK_ATTR_METHOD_LAZY_DICT: { + break; + } + + case _LOAD_ATTR_METHOD_LAZY_DICT: { + _Py_UOpsSymType *attr; + _Py_UOpsSymType *self = NULL; + attr = sym_new_unknown(ctx); + if (attr == NULL) goto out_of_space; + self = sym_new_unknown(ctx); + if (self == NULL) goto out_of_space; + stack_pointer[-1] = attr; + if (1) stack_pointer[0] = self; + stack_pointer += ((1) ? 1 : 0); + break; + } + + /* _INSTRUMENTED_CALL is not a viable micro-op for tier 2 */ + + /* _CALL is not a viable micro-op for tier 2 */ + + case _CHECK_CALL_BOUND_METHOD_EXACT_ARGS: { + _Py_UOpsSymType *null; + _Py_UOpsSymType *callable; + null = stack_pointer[-1 - oparg]; + callable = stack_pointer[-2 - oparg]; + sym_set_null(null); + sym_set_type(callable, &PyMethod_Type); + break; + } + + case _INIT_CALL_BOUND_METHOD_EXACT_ARGS: { + _Py_UOpsSymType *func; + _Py_UOpsSymType *self; + func = sym_new_unknown(ctx); + if (func == NULL) goto out_of_space; + self = sym_new_unknown(ctx); + if (self == NULL) goto out_of_space; + stack_pointer[-2 - oparg] = func; + stack_pointer[-1 - oparg] = self; + break; + } + + case _CHECK_PEP_523: { + break; + } + + case _CHECK_FUNCTION_EXACT_ARGS: { + _Py_UOpsSymType *self_or_null; + _Py_UOpsSymType *callable; + self_or_null = stack_pointer[-1 - oparg]; + callable = stack_pointer[-2 - oparg]; + uint32_t func_version = (uint32_t)this_instr->operand; + sym_set_type(callable, &PyFunction_Type); + (void)self_or_null; + (void)func_version; + break; + } + + case _CHECK_STACK_SPACE: { + break; + } + + case _INIT_CALL_PY_EXACT_ARGS: { + _Py_UOpsSymType **args; + _Py_UOpsSymType *self_or_null; + _Py_UOpsSymType *callable; + _Py_UOpsAbstractFrame *new_frame; + args = &stack_pointer[-oparg]; + self_or_null = stack_pointer[-1 - oparg]; + callable = stack_pointer[-2 - oparg]; + int argcount = oparg; + (void)callable; + PyFunctionObject *func = (PyFunctionObject *)(this_instr + 2)->operand; + if (func == NULL) { + goto error; + } + PyCodeObject *co = (PyCodeObject *)func->func_code; + assert(self_or_null != NULL); + assert(args != NULL); + if (sym_is_not_null(self_or_null)) { + // Bound method fiddling, same as _INIT_CALL_PY_EXACT_ARGS in VM + args--; + argcount++; + } + _Py_UOpsSymType **localsplus_start = ctx->n_consumed; + int n_locals_already_filled = 0; + // Can determine statically, so we interleave the new locals + // and make the current stack the new locals. + // This also sets up for true call inlining. + if (sym_is_known(self_or_null)) { + localsplus_start = args; + n_locals_already_filled = argcount; + } + new_frame = ctx_frame_new(ctx, co, localsplus_start, n_locals_already_filled, 0); + if (new_frame == NULL){ + goto out_of_space; + } + stack_pointer[-2 - oparg] = (_Py_UOpsSymType *)new_frame; + stack_pointer += -1 - oparg; + break; + } + + case _PUSH_FRAME: { + _Py_UOpsAbstractFrame *new_frame; + new_frame = (_Py_UOpsAbstractFrame *)stack_pointer[-1]; + stack_pointer += -1; + ctx->frame->stack_pointer = stack_pointer; + ctx->frame = new_frame; + ctx->curr_frame_depth++; + stack_pointer = new_frame->stack_pointer; + stack_pointer += ((0) ? 1 : 0); + break; + } + + /* _CALL_PY_WITH_DEFAULTS is not a viable micro-op for tier 2 */ + + case _CALL_TYPE_1: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + break; + } + + case _CALL_STR_1: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + break; + } + + case _CALL_TUPLE_1: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + break; + } + + /* _CALL_ALLOC_AND_ENTER_INIT is not a viable micro-op for tier 2 */ + + case _EXIT_INIT_CHECK: { + stack_pointer += -1; + break; + } + + case _CALL_BUILTIN_CLASS: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + break; + } + + case _CALL_BUILTIN_O: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + break; + } + + case _CALL_BUILTIN_FAST: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + break; + } + + case _CALL_BUILTIN_FAST_WITH_KEYWORDS: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + break; + } + + case _CALL_LEN: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + break; + } + + case _CALL_ISINSTANCE: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + break; + } + + case _CALL_METHOD_DESCRIPTOR_O: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + break; + } + + case _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + break; + } + + case _CALL_METHOD_DESCRIPTOR_NOARGS: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + break; + } + + case _CALL_METHOD_DESCRIPTOR_FAST: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + break; + } + + /* _INSTRUMENTED_CALL_KW is not a viable micro-op for tier 2 */ + + /* _CALL_KW is not a viable micro-op for tier 2 */ + + /* _INSTRUMENTED_CALL_FUNCTION_EX is not a viable micro-op for tier 2 */ + + /* _CALL_FUNCTION_EX is not a viable micro-op for tier 2 */ + + case _MAKE_FUNCTION: { + _Py_UOpsSymType *func; + func = sym_new_unknown(ctx); + if (func == NULL) goto out_of_space; + stack_pointer[-1] = func; + break; + } + + case _SET_FUNCTION_ATTRIBUTE: { + _Py_UOpsSymType *func; + func = sym_new_unknown(ctx); + if (func == NULL) goto out_of_space; + stack_pointer[-2] = func; + stack_pointer += -1; + break; + } + + case _BUILD_SLICE: { + _Py_UOpsSymType *slice; + slice = sym_new_unknown(ctx); + if (slice == NULL) goto out_of_space; + stack_pointer[-2 - ((oparg == 3) ? 1 : 0)] = slice; + stack_pointer += -1 - ((oparg == 3) ? 1 : 0); + break; + } + + case _CONVERT_VALUE: { + _Py_UOpsSymType *result; + result = sym_new_unknown(ctx); + if (result == NULL) goto out_of_space; + stack_pointer[-1] = result; + break; + } + + case _FORMAT_SIMPLE: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-1] = res; + break; + } + + case _FORMAT_WITH_SPEC: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _COPY: { + _Py_UOpsSymType *bottom; + _Py_UOpsSymType *top; + bottom = stack_pointer[-1 - (oparg-1)]; + assert(oparg > 0); + top = bottom; + stack_pointer[0] = top; + stack_pointer += 1; + break; + } + + case _BINARY_OP: { + _Py_UOpsSymType *res; + res = sym_new_unknown(ctx); + if (res == NULL) goto out_of_space; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _SWAP: { + _Py_UOpsSymType *top; + _Py_UOpsSymType *bottom; + top = stack_pointer[-1]; + bottom = stack_pointer[-2 - (oparg-2)]; + stack_pointer[-2 - (oparg-2)] = top; + stack_pointer[-1] = bottom; + break; + } + + /* _INSTRUMENTED_INSTRUCTION is not a viable micro-op for tier 2 */ + + /* _INSTRUMENTED_JUMP_FORWARD is not a viable micro-op for tier 2 */ + + /* _INSTRUMENTED_JUMP_BACKWARD is not a viable micro-op for tier 2 */ + + /* _INSTRUMENTED_POP_JUMP_IF_TRUE is not a viable micro-op for tier 2 */ + + /* _INSTRUMENTED_POP_JUMP_IF_FALSE is not a viable micro-op for tier 2 */ + + /* _INSTRUMENTED_POP_JUMP_IF_NONE is not a viable micro-op for tier 2 */ + + /* _INSTRUMENTED_POP_JUMP_IF_NOT_NONE is not a viable micro-op for tier 2 */ + + case _GUARD_IS_TRUE_POP: { + stack_pointer += -1; + break; + } + + case _GUARD_IS_FALSE_POP: { + stack_pointer += -1; + break; + } + + case _GUARD_IS_NONE_POP: { + stack_pointer += -1; + break; + } + + case _GUARD_IS_NOT_NONE_POP: { + stack_pointer += -1; + break; + } + + case _JUMP_TO_TOP: { + break; + } + + case _SET_IP: { + break; + } + + case _SAVE_RETURN_OFFSET: { + break; + } + + case _EXIT_TRACE: { + break; + } + + case _CHECK_VALIDITY: { + break; + } + + case _LOAD_CONST_INLINE: { + _Py_UOpsSymType *value; + PyObject *ptr = (PyObject *)this_instr->operand; + value = sym_new_const(ctx, ptr); + if (value == NULL) { + goto out_of_space; + } + stack_pointer[0] = value; + stack_pointer += 1; + break; + } + + case _LOAD_CONST_INLINE_BORROW: { + _Py_UOpsSymType *value; + PyObject *ptr = (PyObject *)this_instr->operand; + value = sym_new_const(ctx, ptr); + if (value == NULL) { + goto out_of_space; + } + stack_pointer[0] = value; + stack_pointer += 1; + break; + } + + case _LOAD_CONST_INLINE_WITH_NULL: { + _Py_UOpsSymType *value; + _Py_UOpsSymType *null; + PyObject *ptr = (PyObject *)this_instr->operand; + value = sym_new_const(ctx, ptr); + if (value == NULL) { + goto out_of_space; + } + null = sym_new_null(ctx); + if (null == NULL) { + goto out_of_space; + } + stack_pointer[0] = value; + stack_pointer[1] = null; + stack_pointer += 2; + break; + } + + case _LOAD_CONST_INLINE_BORROW_WITH_NULL: { + _Py_UOpsSymType *value; + _Py_UOpsSymType *null; + PyObject *ptr = (PyObject *)this_instr->operand; + value = sym_new_const(ctx, ptr); + if (value == NULL) { + goto out_of_space; + } + null = sym_new_null(ctx); + if (null == NULL) { + goto out_of_space; + } + stack_pointer[0] = value; + stack_pointer[1] = null; + stack_pointer += 2; + break; + } + + case _CHECK_GLOBALS: { + break; + } + + case _CHECK_BUILTINS: { + break; + } + + case _INTERNAL_INCREMENT_OPT_COUNTER: { + stack_pointer += -1; + break; + } + diff --git a/Tools/c-analyzer/cpython/_parser.py b/Tools/c-analyzer/cpython/_parser.py index 444063d2148934b..be89a26058e8e86 100644 --- a/Tools/c-analyzer/cpython/_parser.py +++ b/Tools/c-analyzer/cpython/_parser.py @@ -83,9 +83,11 @@ def clean_lines(text): Python/frozen_modules/*.h Python/generated_cases.c.h Python/executor_cases.c.h +Python/tier2_redundancy_eliminator_cases.c.h # not actually source Python/bytecodes.c +Python/tier2_redundancy_eliminator_bytecodes.c # mimalloc Objects/mimalloc/*.c diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index c75aff8c1723c10..14bcd85b9eae59a 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -734,6 +734,6 @@ Modules/expat/xmlrole.c - error - ## other Modules/_io/_iomodule.c - _PyIO_Module - Modules/_sqlite/module.c - _sqlite3module - -Python/optimizer_analysis.c - _Py_PartitionRootNode_Type - +Python/optimizer_analysis.c - _Py_UOpsAbstractFrame_Type - Python/optimizer_analysis.c - _Py_UOpsAbstractInterpContext_Type - Modules/clinic/md5module.c.h _md5_md5 _keywords - diff --git a/Tools/cases_generator/README.md b/Tools/cases_generator/README.md index 7fec8a882336cdf..d35a868b42ea9ec 100644 --- a/Tools/cases_generator/README.md +++ b/Tools/cases_generator/README.md @@ -13,6 +13,9 @@ What's currently here: - `parser.py` helper for interactions with `parsing.py` - `tierN_generator.py`: a couple of driver scripts to read `Python/bytecodes.c` and write `Python/generated_cases.c.h` (and several other files) +- `tier2_abstract_generator.py`: reads `Python/bytecodes.c` and + `Python/tier2_redundancy_eliminator_bytecodes.c` and writes + `Python/tier2_redundancy_eliminator_cases.c.h` - `stack.py`: code to handle generalized stack effects - `cwriter.py`: code which understands tokens and how to format C code; main class: `CWriter` diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index b80fa66e2a159ad..3497b7fcdf35d37 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -24,7 +24,6 @@ class Properties: pure: bool passthrough: bool - guard: bool def dump(self, indent: str) -> None: print(indent, end="") @@ -51,7 +50,6 @@ def from_list(properties: list["Properties"]) -> "Properties": has_free=any(p.has_free for p in properties), pure=all(p.pure for p in properties), passthrough=all(p.passthrough for p in properties), - guard=all(p.guard for p in properties), ) @@ -73,7 +71,6 @@ def from_list(properties: list["Properties"]) -> "Properties": has_free=False, pure=False, passthrough=False, - guard=False, ) @@ -273,7 +270,7 @@ def override_error( def convert_stack_item(item: parser.StackEffect) -> StackItem: return StackItem( - item.name, item.type, item.cond, (item.size or "1"), type_prop=item.type_prop + item.name, item.type, item.cond, (item.size or "1") ) @@ -473,7 +470,6 @@ def compute_properties(op: parser.InstDef) -> Properties: has_free=has_free, pure="pure" in op.annotations, passthrough=passthrough, - guard=passthrough and deopts, ) diff --git a/Tools/cases_generator/interpreter_definition.md b/Tools/cases_generator/interpreter_definition.md index e87aff43762b11d..9b5733562f77b40 100644 --- a/Tools/cases_generator/interpreter_definition.md +++ b/Tools/cases_generator/interpreter_definition.md @@ -109,10 +109,7 @@ and a piece of C code describing its semantics:: NAME [":" type] [ "if" "(" C-expression ")" ] type: - NAME ["*"] | type_prop - - type_prop: - "&" "(" NAME ["+" NAME] ")" + NAME ["*"] stream: NAME "/" size @@ -142,26 +139,7 @@ The following definitions may occur: The optional `type` in an `object` is the C type. It defaults to `PyObject *`. The objects before the "--" are the objects on top of the stack at the start of the instruction. Those after the "--" are the objects on top of the stack at the -end of the instruction. When prefixed by a `&`, the `type` production rule follows the -`type_prop` production rule. This indicates the type of the value is of that specific type -after the operation. In this case, the type may also contain 64-bit refinement information -that is fetched from a previously defined operand in the instruction header, such as -a type version tag. This follows the format `type + refinement`. The list of possible types -and their refinements are below. They obey the following predicates: - - -* `PYLONG_TYPE`: `Py_TYPE(val) == &PyLong_Type` -* `PYFLOAT_TYPE`: `Py_TYPE(val) == &PyFloat_Type` -* `PYUNICODE_TYPE`: `Py_TYPE(val) == &PYUNICODE_TYPE` -* `NULL_TYPE`: `val == NULL` -* `GUARD_TYPE_VERSION_TYPE`: `type->tp_version_tag == auxillary` -* `GUARD_DORV_VALUES_TYPE`: `_PyDictOrValues_IsValues(obj)` -* `GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_TYPE`: - `_PyDictOrValues_IsValues(obj) || _PyObject_MakeInstanceAttributesFromDict(obj, dorv)` -* `GUARD_KEYS_VERSION_TYPE`: `owner_heap_type->ht_cached_keys->dk_version == auxillary` -* `PYMETHOD_TYPE`: `Py_TYPE(val) == &PyMethod_Type` -* `PYFUNCTION_TYPE_VERSION_TYPE`: - `PyFunction_Check(callable) && func->func_version == auxillary && code->co_argcount == oparg + (self_or_null != NULL)` +end of the instruction. An `inst` without `stack_effect` is a transitional form to allow the original C code diff --git a/Tools/cases_generator/parsing.py b/Tools/cases_generator/parsing.py index 307919cb37ce1e1..a8961f28babea1b 100644 --- a/Tools/cases_generator/parsing.py +++ b/Tools/cases_generator/parsing.py @@ -75,11 +75,6 @@ class StackEffect(Node): size: str = "" # Optional `[size]` # Note: size cannot be combined with type or cond - # Optional `(type, refinement)` - type_prop: None | tuple[str, None | str] = field( - default_factory=lambda: None, init=True, compare=False, hash=False - ) - def __repr__(self) -> str: items = [self.name, self.type, self.cond, self.size] while items and items[-1] == "": @@ -260,25 +255,14 @@ def cache_effect(self) -> CacheEffect | None: @contextual def stack_effect(self) -> StackEffect | None: - # IDENTIFIER [':' [IDENTIFIER [TIMES]] ['&' '(' IDENTIFIER ['+' IDENTIFIER] ')']] ['if' '(' expression ')'] + # IDENTIFIER [':' IDENTIFIER [TIMES]] ['if' '(' expression ')'] # | IDENTIFIER '[' expression ']' if tkn := self.expect(lx.IDENTIFIER): type_text = "" - type_prop = None if self.expect(lx.COLON): - if i := self.expect(lx.IDENTIFIER): - type_text = i.text.strip() - if self.expect(lx.TIMES): - type_text += " *" - if self.expect(lx.AND): - consumed_bracket = self.expect(lx.LPAREN) is not None - type_prop_text = self.require(lx.IDENTIFIER).text.strip() - refinement = None - if self.expect(lx.PLUS): - refinement = self.require(lx.IDENTIFIER).text.strip() - type_prop = (type_prop_text, refinement) - if consumed_bracket: - self.require(lx.RPAREN) + type_text = self.require(lx.IDENTIFIER).text.strip() + if self.expect(lx.TIMES): + type_text += " *" cond_text = "" if self.expect(lx.IF): self.require(lx.LPAREN) @@ -295,7 +279,7 @@ def stack_effect(self) -> StackEffect | None: self.require(lx.RBRACKET) type_text = "PyObject **" size_text = size.text.strip() - return StackEffect(tkn.text, type_text, cond_text, size_text, type_prop) + return StackEffect(tkn.text, type_text, cond_text, size_text) return None @contextual diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index f62ece43c1be7fe..97a301142d59c7e 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -168,11 +168,11 @@ def push(self, var: StackItem) -> str: self.top_offset.push(var) return "" - def flush(self, out: CWriter) -> None: + def flush(self, out: CWriter, cast_type: str = "PyObject *") -> None: out.start_line() for var in self.variables: if not var.peek: - cast = "(PyObject *)" if var.type else "" + cast = f"({cast_type})" if var.type else "" if var.name not in UNUSED and not var.is_array(): if var.condition: out.emit(f"if ({var.condition}) ") diff --git a/Tools/cases_generator/tier2_abstract_generator.py b/Tools/cases_generator/tier2_abstract_generator.py new file mode 100644 index 000000000000000..cc29b1660d26edf --- /dev/null +++ b/Tools/cases_generator/tier2_abstract_generator.py @@ -0,0 +1,235 @@ +"""Generate the cases for the tier 2 redundancy eliminator/abstract interpreter. +Reads the instruction definitions from bytecodes.c. and tier2_redundancy_eliminator.bytecodes.c +Writes the cases to tier2_redundancy_eliminator_cases.c.h, which is #included in Python/optimizer_analysis.c. +""" + +import argparse +import os.path +import sys + +from analyzer import ( + Analysis, + Instruction, + Uop, + Part, + analyze_files, + Skip, + StackItem, + analysis_error, +) +from generators_common import ( + DEFAULT_INPUT, + ROOT, + write_header, + emit_tokens, + emit_to, + replace_sync_sp, +) +from cwriter import CWriter +from typing import TextIO, Iterator +from lexer import Token +from stack import StackOffset, Stack, SizeMismatch, UNUSED + +DEFAULT_OUTPUT = ROOT / "Python/tier2_redundancy_eliminator_cases.c.h" +DEFAULT_ABSTRACT_INPUT = ROOT / "Python/tier2_redundancy_eliminator_bytecodes.c" + + +def validate_uop(override: Uop, uop: Uop) -> None: + # To do + pass + + +def type_name(var: StackItem) -> str: + if var.is_array(): + return f"_Py_UOpsSymType **" + if var.type: + return var.type + return f"_Py_UOpsSymType *" + + +def declare_variables(uop: Uop, out: CWriter, skip_inputs: bool) -> None: + variables = {"unused"} + if not skip_inputs: + for var in reversed(uop.stack.inputs): + if var.name not in variables: + variables.add(var.name) + if var.condition: + out.emit(f"{type_name(var)}{var.name} = NULL;\n") + else: + out.emit(f"{type_name(var)}{var.name};\n") + for var in uop.stack.outputs: + if var.peek: + continue + if var.name not in variables: + variables.add(var.name) + if var.condition: + out.emit(f"{type_name(var)}{var.name} = NULL;\n") + else: + out.emit(f"{type_name(var)}{var.name};\n") + + +def decref_inputs( + out: CWriter, + tkn: Token, + tkn_iter: Iterator[Token], + uop: Uop, + stack: Stack, + inst: Instruction | None, +) -> None: + next(tkn_iter) + next(tkn_iter) + next(tkn_iter) + out.emit_at("", tkn) + + +def emit_default(out: CWriter, uop: Uop) -> None: + for i, var in enumerate(uop.stack.outputs): + if var.name != "unused" and not var.peek: + if var.is_array(): + out.emit(f"for (int _i = {var.size}; --_i >= 0;) {{\n") + out.emit(f"{var.name}[_i] = sym_new_unknown(ctx);\n") + out.emit(f"if ({var.name}[_i] == NULL) goto out_of_space;\n") + out.emit("}\n") + elif var.name == "null": + out.emit(f"{var.name} = sym_new_null(ctx);\n") + out.emit(f"if ({var.name} == NULL) goto out_of_space;\n") + else: + out.emit(f"{var.name} = sym_new_unknown(ctx);\n") + out.emit(f"if ({var.name} == NULL) goto out_of_space;\n") + + +def write_uop( + override: Uop | None, + uop: Uop, + out: CWriter, + stack: Stack, + debug: bool, + skip_inputs: bool, +) -> None: + try: + prototype = override if override else uop + is_override = override is not None + out.start_line() + for var in reversed(prototype.stack.inputs): + res = stack.pop(var) + if not skip_inputs: + out.emit(res) + if not prototype.properties.stores_sp: + for i, var in enumerate(prototype.stack.outputs): + res = stack.push(var) + if not var.peek or is_override: + out.emit(res) + if debug: + args = [] + for var in prototype.stack.inputs: + if not var.peek or is_override: + args.append(var.name) + out.emit(f'DEBUG_PRINTF({", ".join(args)});\n') + if override: + for cache in uop.caches: + if cache.name != "unused": + if cache.size == 4: + type = cast = "PyObject *" + else: + type = f"uint{cache.size*16}_t " + cast = f"uint{cache.size*16}_t" + out.emit(f"{type}{cache.name} = ({cast})this_instr->operand;\n") + if override: + replacement_funcs = { + "DECREF_INPUTS": decref_inputs, + "SYNC_SP": replace_sync_sp, + } + emit_tokens(out, override, stack, None, replacement_funcs) + else: + emit_default(out, uop) + + if prototype.properties.stores_sp: + for i, var in enumerate(prototype.stack.outputs): + if not var.peek or is_override: + out.emit(stack.push(var)) + out.start_line() + stack.flush(out, cast_type="_Py_UOpsSymType *") + except SizeMismatch as ex: + raise analysis_error(ex.args[0], uop.body[0]) + + +SKIPS = ("_EXTENDED_ARG",) + + +def generate_abstract_interpreter( + filenames: list[str], + abstract: Analysis, + base: Analysis, + outfile: TextIO, + debug: bool, +) -> None: + write_header(__file__, filenames, outfile) + out = CWriter(outfile, 2, False) + out.emit("\n") + base_uop_names = set([uop.name for uop in base.uops.values()]) + for abstract_uop_name in abstract.uops: + assert abstract_uop_name in base_uop_names,\ + f"All abstract uops should override base uops, but {abstract_uop_name} is not." + + for uop in base.uops.values(): + override: Uop | None = None + if uop.name in abstract.uops: + override = abstract.uops[uop.name] + validate_uop(override, uop) + if uop.properties.tier_one_only: + continue + if uop.is_super(): + continue + if not uop.is_viable(): + out.emit(f"/* {uop.name} is not a viable micro-op for tier 2 */\n\n") + continue + out.emit(f"case {uop.name}: {{\n") + if override: + declare_variables(override, out, skip_inputs=False) + else: + declare_variables(uop, out, skip_inputs=True) + stack = Stack() + write_uop(override, uop, out, stack, debug, skip_inputs=(override is None)) + out.start_line() + out.emit("break;\n") + out.emit("}") + out.emit("\n\n") + + +def generate_tier2_abstract_from_files( + filenames: list[str], outfilename: str, debug: bool=False +) -> None: + assert len(filenames) == 2, "Need a base file and an abstract cases file." + base = analyze_files([filenames[0]]) + abstract = analyze_files([filenames[1]]) + with open(outfilename, "w") as outfile: + generate_abstract_interpreter(filenames, abstract, base, outfile, debug) + + +arg_parser = argparse.ArgumentParser( + description="Generate the code for the tier 2 interpreter.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, +) + +arg_parser.add_argument( + "-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT +) + + +arg_parser.add_argument("input", nargs=1, help="Abstract interpreter definition file") + +arg_parser.add_argument( + "base", nargs=argparse.REMAINDER, help="The base instruction definition file(s)" +) + +arg_parser.add_argument("-d", "--debug", help="Insert debug calls", action="store_true") + +if __name__ == "__main__": + args = arg_parser.parse_args() + if len(args.base) == 0: + args.input.append(DEFAULT_INPUT) + args.input.append(DEFAULT_ABSTRACT_INPUT) + abstract = analyze_files(args.input) + base = analyze_files(args.base) + with open(args.output, "w") as outfile: + generate_abstract_interpreter(args.input, abstract, base, outfile, args.debug) From f9f6156c5affc039d4ee6b6f4999daf0d5896428 Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Tue, 13 Feb 2024 14:16:37 +0000 Subject: [PATCH 246/507] GH-113710: Backedge counter improvements. (GH-115166) --- Include/cpython/optimizer.h | 10 +++++-- Include/internal/pycore_interp.h | 6 ++-- Python/bytecodes.c | 29 ++++++++++--------- Python/generated_cases.c.h | 29 ++++++++++--------- Python/optimizer.c | 48 ++++++++++++++++++++++---------- Python/pylifecycle.c | 4 +-- Python/pystate.c | 10 ++----- 7 files changed, 81 insertions(+), 55 deletions(-) diff --git a/Include/cpython/optimizer.h b/Include/cpython/optimizer.h index 3928eca583ba5b7..f710ca76b2ba24a 100644 --- a/Include/cpython/optimizer.h +++ b/Include/cpython/optimizer.h @@ -71,6 +71,8 @@ typedef struct { PyAPI_FUNC(int) PyUnstable_Replace_Executor(PyCodeObject *code, _Py_CODEUNIT *instr, _PyExecutorObject *executor); +_PyOptimizerObject *_Py_SetOptimizer(PyInterpreterState *interp, _PyOptimizerObject* optimizer); + PyAPI_FUNC(void) PyUnstable_SetOptimizer(_PyOptimizerObject* optimizer); PyAPI_FUNC(_PyOptimizerObject *) PyUnstable_GetOptimizer(void); @@ -80,8 +82,6 @@ PyAPI_FUNC(_PyExecutorObject *) PyUnstable_GetExecutor(PyCodeObject *code, int o int _PyOptimizer_Optimize(struct _PyInterpreterFrame *frame, _Py_CODEUNIT *start, PyObject **stack_pointer); -extern _PyOptimizerObject _PyOptimizer_Default; - void _Py_ExecutorInit(_PyExecutorObject *, _PyBloomFilter *); void _Py_ExecutorClear(_PyExecutorObject *); void _Py_BloomFilter_Init(_PyBloomFilter *); @@ -96,7 +96,11 @@ PyAPI_FUNC(PyObject *)PyUnstable_Optimizer_NewUOpOptimizer(void); #define OPTIMIZER_BITS_IN_COUNTER 4 /* Minimum of 16 additional executions before retry */ -#define MINIMUM_TIER2_BACKOFF 4 +#define MIN_TIER2_BACKOFF 4 +#define MAX_TIER2_BACKOFF (15 - OPTIMIZER_BITS_IN_COUNTER) +#define OPTIMIZER_BITS_MASK ((1 << OPTIMIZER_BITS_IN_COUNTER) - 1) +/* A value <= UINT16_MAX but large enough that when shifted is > UINT16_MAX */ +#define OPTIMIZER_UNREACHABLE_THRESHOLD UINT16_MAX #define _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS 3 #define _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS 6 diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 485b1914a448854..c244d8966f238bf 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -239,8 +239,10 @@ struct _is { struct callable_cache callable_cache; _PyOptimizerObject *optimizer; _PyExecutorObject *executor_list_head; - uint16_t optimizer_resume_threshold; - uint16_t optimizer_backedge_threshold; + /* These values are shifted and offset to speed up check in JUMP_BACKWARD */ + uint32_t optimizer_resume_threshold; + uint32_t optimizer_backedge_threshold; + uint32_t next_func_version; _rare_events rare_events; PyDict_WatchCallback builtins_dict_watcher; diff --git a/Python/bytecodes.c b/Python/bytecodes.c index f7c7e3669b7e6f7..2ad5878f52e90b7 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2318,13 +2318,16 @@ dummy_func( assert(oparg <= INSTR_OFFSET()); JUMPBY(-oparg); #if ENABLE_SPECIALIZATION - this_instr[1].cache += (1 << OPTIMIZER_BITS_IN_COUNTER); + uint16_t counter = this_instr[1].cache; + this_instr[1].cache = counter + (1 << OPTIMIZER_BITS_IN_COUNTER); /* We are using unsigned values, but we really want signed values, so - * do the 2s complement comparison manually */ - uint16_t ucounter = this_instr[1].cache + (1 << 15); - uint16_t threshold = tstate->interp->optimizer_backedge_threshold + (1 << 15); + * do the 2s complement adjustment manually */ + uint32_t offset_counter = counter ^ (1 << 15); + uint32_t threshold = tstate->interp->optimizer_backedge_threshold; + assert((threshold & OPTIMIZER_BITS_MASK) == 0); + // Use '>=' not '>' so that the optimizer/backoff bits do not effect the result. // Double-check that the opcode isn't instrumented or something: - if (ucounter > threshold && this_instr->op.code == JUMP_BACKWARD) { + if (offset_counter >= threshold && this_instr->op.code == JUMP_BACKWARD) { OPT_STAT_INC(attempts); _Py_CODEUNIT *start = this_instr; /* Back up over EXTENDED_ARGs so optimizer sees the whole instruction */ @@ -2338,18 +2341,18 @@ dummy_func( // Rewind and enter the executor: assert(start->op.code == ENTER_EXECUTOR); next_instr = start; - this_instr[1].cache &= ((1 << OPTIMIZER_BITS_IN_COUNTER) - 1); + this_instr[1].cache &= OPTIMIZER_BITS_MASK; } else { - int backoff = this_instr[1].cache & ((1 << OPTIMIZER_BITS_IN_COUNTER) - 1); - if (backoff < MINIMUM_TIER2_BACKOFF) { - backoff = MINIMUM_TIER2_BACKOFF; + int backoff = this_instr[1].cache & OPTIMIZER_BITS_MASK; + backoff++; + if (backoff < MIN_TIER2_BACKOFF) { + backoff = MIN_TIER2_BACKOFF; } - else if (backoff < 15 - OPTIMIZER_BITS_IN_COUNTER) { - backoff++; + else if (backoff > MAX_TIER2_BACKOFF) { + backoff = MAX_TIER2_BACKOFF; } - assert(backoff <= 15 - OPTIMIZER_BITS_IN_COUNTER); - this_instr[1].cache = ((1 << 16) - ((1 << OPTIMIZER_BITS_IN_COUNTER) << backoff)) | backoff; + this_instr[1].cache = ((UINT16_MAX << OPTIMIZER_BITS_IN_COUNTER) << backoff) | backoff; } } #endif /* ENABLE_SPECIALIZATION */ diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index afb6650e5920fbb..a49223e4db53186 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -3263,13 +3263,16 @@ assert(oparg <= INSTR_OFFSET()); JUMPBY(-oparg); #if ENABLE_SPECIALIZATION - this_instr[1].cache += (1 << OPTIMIZER_BITS_IN_COUNTER); + uint16_t counter = this_instr[1].cache; + this_instr[1].cache = counter + (1 << OPTIMIZER_BITS_IN_COUNTER); /* We are using unsigned values, but we really want signed values, so - * do the 2s complement comparison manually */ - uint16_t ucounter = this_instr[1].cache + (1 << 15); - uint16_t threshold = tstate->interp->optimizer_backedge_threshold + (1 << 15); + * do the 2s complement adjustment manually */ + uint32_t offset_counter = counter ^ (1 << 15); + uint32_t threshold = tstate->interp->optimizer_backedge_threshold; + assert((threshold & OPTIMIZER_BITS_MASK) == 0); + // Use '>=' not '>' so that the optimizer/backoff bits do not effect the result. // Double-check that the opcode isn't instrumented or something: - if (ucounter > threshold && this_instr->op.code == JUMP_BACKWARD) { + if (offset_counter >= threshold && this_instr->op.code == JUMP_BACKWARD) { OPT_STAT_INC(attempts); _Py_CODEUNIT *start = this_instr; /* Back up over EXTENDED_ARGs so optimizer sees the whole instruction */ @@ -3283,18 +3286,18 @@ // Rewind and enter the executor: assert(start->op.code == ENTER_EXECUTOR); next_instr = start; - this_instr[1].cache &= ((1 << OPTIMIZER_BITS_IN_COUNTER) - 1); + this_instr[1].cache &= OPTIMIZER_BITS_MASK; } else { - int backoff = this_instr[1].cache & ((1 << OPTIMIZER_BITS_IN_COUNTER) - 1); - if (backoff < MINIMUM_TIER2_BACKOFF) { - backoff = MINIMUM_TIER2_BACKOFF; + int backoff = this_instr[1].cache & OPTIMIZER_BITS_MASK; + backoff++; + if (backoff < MIN_TIER2_BACKOFF) { + backoff = MIN_TIER2_BACKOFF; } - else if (backoff < 15 - OPTIMIZER_BITS_IN_COUNTER) { - backoff++; + else if (backoff > MAX_TIER2_BACKOFF) { + backoff = MAX_TIER2_BACKOFF; } - assert(backoff <= 15 - OPTIMIZER_BITS_IN_COUNTER); - this_instr[1].cache = ((1 << 16) - ((1 << OPTIMIZER_BITS_IN_COUNTER) << backoff)) | backoff; + this_instr[1].cache = ((UINT16_MAX << OPTIMIZER_BITS_IN_COUNTER) << backoff) | backoff; } } #endif /* ENABLE_SPECIALIZATION */ diff --git a/Python/optimizer.c b/Python/optimizer.c index f31f83113d3f250..13df8c170a537c6 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -109,6 +109,9 @@ never_optimize( _PyExecutorObject **exec, int Py_UNUSED(stack_entries)) { + /* Although it should be benign for this to be called, + * it shouldn't happen, so fail in debug builds. */ + assert(0 && "never optimize should never be called"); return 0; } @@ -120,13 +123,19 @@ PyTypeObject _PyDefaultOptimizer_Type = { .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, }; -_PyOptimizerObject _PyOptimizer_Default = { +static _PyOptimizerObject _PyOptimizer_Default = { PyObject_HEAD_INIT(&_PyDefaultOptimizer_Type) .optimize = never_optimize, - .resume_threshold = INT16_MAX, - .backedge_threshold = INT16_MAX, + .resume_threshold = OPTIMIZER_UNREACHABLE_THRESHOLD, + .backedge_threshold = OPTIMIZER_UNREACHABLE_THRESHOLD, }; +static uint32_t +shift_and_offset_threshold(uint16_t threshold) +{ + return (threshold << OPTIMIZER_BITS_IN_COUNTER) + (1 << 15); +} + _PyOptimizerObject * PyUnstable_GetOptimizer(void) { @@ -134,24 +143,33 @@ PyUnstable_GetOptimizer(void) if (interp->optimizer == &_PyOptimizer_Default) { return NULL; } - assert(interp->optimizer_backedge_threshold == interp->optimizer->backedge_threshold); - assert(interp->optimizer_resume_threshold == interp->optimizer->resume_threshold); + assert(interp->optimizer_backedge_threshold == + shift_and_offset_threshold(interp->optimizer->backedge_threshold)); + assert(interp->optimizer_resume_threshold == + shift_and_offset_threshold(interp->optimizer->resume_threshold)); Py_INCREF(interp->optimizer); return interp->optimizer; } -void -PyUnstable_SetOptimizer(_PyOptimizerObject *optimizer) +_PyOptimizerObject * +_Py_SetOptimizer(PyInterpreterState *interp, _PyOptimizerObject *optimizer) { - PyInterpreterState *interp = _PyInterpreterState_GET(); if (optimizer == NULL) { optimizer = &_PyOptimizer_Default; } _PyOptimizerObject *old = interp->optimizer; Py_INCREF(optimizer); interp->optimizer = optimizer; - interp->optimizer_backedge_threshold = optimizer->backedge_threshold; - interp->optimizer_resume_threshold = optimizer->resume_threshold; + interp->optimizer_backedge_threshold = shift_and_offset_threshold(optimizer->backedge_threshold); + interp->optimizer_resume_threshold = shift_and_offset_threshold(optimizer->resume_threshold); + return old; +} + +void +PyUnstable_SetOptimizer(_PyOptimizerObject *optimizer) +{ + PyInterpreterState *interp = _PyInterpreterState_GET(); + _PyOptimizerObject *old = _Py_SetOptimizer(interp, optimizer); Py_DECREF(old); } @@ -860,10 +878,10 @@ PyUnstable_Optimizer_NewUOpOptimizer(void) return NULL; } opt->optimize = uop_optimize; - opt->resume_threshold = INT16_MAX; - // Need at least 3 iterations to settle specializations. - // A few lower bits of the counter are reserved for other flags. - opt->backedge_threshold = 16 << OPTIMIZER_BITS_IN_COUNTER; + opt->resume_threshold = OPTIMIZER_UNREACHABLE_THRESHOLD; + // Need a few iterations to settle specializations, + // and to ammortize the cost of optimization. + opt->backedge_threshold = 16; return (PyObject *)opt; } @@ -950,7 +968,7 @@ PyUnstable_Optimizer_NewCounter(void) return NULL; } opt->base.optimize = counter_optimize; - opt->base.resume_threshold = INT16_MAX; + opt->base.resume_threshold = OPTIMIZER_UNREACHABLE_THRESHOLD; opt->base.backedge_threshold = 0; opt->count = 0; return (PyObject *)opt; diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 230018068d751c5..7e4c07bb657d19e 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1627,8 +1627,8 @@ finalize_modules(PyThreadState *tstate) // Invalidate all executors and turn off tier 2 optimizer _Py_Executors_InvalidateAll(interp); - Py_XDECREF(interp->optimizer); - interp->optimizer = &_PyOptimizer_Default; + _PyOptimizerObject *old = _Py_SetOptimizer(interp, NULL); + Py_XDECREF(old); // Stop watching __builtin__ modifications PyDict_Unwatch(0, interp->builtins); diff --git a/Python/pystate.c b/Python/pystate.c index 937c43033b068d4..996f465825215f0 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -625,9 +625,7 @@ init_interpreter(PyInterpreterState *interp, } interp->sys_profile_initialized = false; interp->sys_trace_initialized = false; - interp->optimizer = &_PyOptimizer_Default; - interp->optimizer_backedge_threshold = _PyOptimizer_Default.backedge_threshold; - interp->optimizer_resume_threshold = _PyOptimizer_Default.backedge_threshold; + (void)_Py_SetOptimizer(interp, NULL); interp->next_func_version = 1; interp->executor_list_head = NULL; if (interp != &runtime->_main_interpreter) { @@ -780,10 +778,8 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate) tstate->_status.cleared = 0; } - Py_CLEAR(interp->optimizer); - interp->optimizer = &_PyOptimizer_Default; - interp->optimizer_backedge_threshold = _PyOptimizer_Default.backedge_threshold; - interp->optimizer_resume_threshold = _PyOptimizer_Default.backedge_threshold; + _PyOptimizerObject *old = _Py_SetOptimizer(interp, NULL); + Py_DECREF(old); /* It is possible that any of the objects below have a finalizer that runs Python code or otherwise relies on a thread state From 5719aa23ab7f1c7a5f03309ca4044078a98e7b59 Mon Sep 17 00:00:00 2001 From: qqwqqw689 <114795525+qqwqqw689@users.noreply.github.com> Date: Tue, 13 Feb 2024 22:23:10 +0800 Subject: [PATCH 247/507] gh-113437: Update documentation about PyUnicode_AsWideChar() function (GH-113455) --- Doc/c-api/unicode.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Doc/c-api/unicode.rst b/Doc/c-api/unicode.rst index 5541eaa521803bc..666ffe89605c56f 100644 --- a/Doc/c-api/unicode.rst +++ b/Doc/c-api/unicode.rst @@ -854,7 +854,12 @@ wchar_t Support Copy the Unicode object contents into the :c:type:`wchar_t` buffer *wstr*. At most *size* :c:type:`wchar_t` characters are copied (excluding a possibly trailing null termination character). Return the number of :c:type:`wchar_t` characters - copied or ``-1`` in case of an error. Note that the resulting :c:expr:`wchar_t*` + copied or ``-1`` in case of an error. + + When *wstr* is ``NULL``, instead return the *size* that would be required + to store all of *unicode* including a terminating null. + + Note that the resulting :c:expr:`wchar_t*` string may or may not be null-terminated. It is the responsibility of the caller to make sure that the :c:expr:`wchar_t*` string is null-terminated in case this is required by the application. Also, note that the :c:expr:`wchar_t*` string From de07941729b8899b187b8ef9690f9a74b2d6286b Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Tue, 13 Feb 2024 17:10:00 +0100 Subject: [PATCH 248/507] gh-115405: add versionadded tag for co_qualname in code objects documentation (#115411) --- Doc/reference/datamodel.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 885ee825c122965..88bc025c7c3fb4d 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1134,6 +1134,8 @@ Special read-only attributes * - .. attribute:: codeobject.co_qualname - The fully qualified function name + .. versionadded:: 3.11 + * - .. attribute:: codeobject.co_argcount - The total number of positional :term:`parameters <parameter>` (including positional-only parameters and parameters with default values) From 681778c56a9204d65b8857e7ceba57f2c638671d Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Tue, 13 Feb 2024 16:28:19 +0000 Subject: [PATCH 249/507] GH-113710: Improve `_SET_IP` and `_CHECK_VALIDITY` (GH-115248) --- Include/internal/pycore_uop_ids.h | 3 +- Include/internal/pycore_uop_metadata.h | 4 +- Python/bytecodes.c | 10 ++- Python/executor_cases.c.h | 13 +++- Python/optimizer.c | 5 +- Python/optimizer_analysis.c | 75 +++++++++++++------- Python/tier2_redundancy_eliminator_cases.c.h | 4 ++ 7 files changed, 79 insertions(+), 35 deletions(-) diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index b2476e1c6e5c4b5..9bb537d355055db 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -237,7 +237,8 @@ extern "C" { #define _CHECK_GLOBALS 384 #define _CHECK_BUILTINS 385 #define _INTERNAL_INCREMENT_OPT_COUNTER 386 -#define MAX_UOP_ID 386 +#define _CHECK_VALIDITY_AND_SET_IP 387 +#define MAX_UOP_ID 387 #ifdef __cplusplus } diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 30dc5a881574e79..163a0320aa22985 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -198,7 +198,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_GUARD_IS_NONE_POP] = HAS_DEOPT_FLAG, [_GUARD_IS_NOT_NONE_POP] = HAS_DEOPT_FLAG, [_JUMP_TO_TOP] = HAS_EVAL_BREAK_FLAG, - [_SET_IP] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, + [_SET_IP] = 0, [_SAVE_RETURN_OFFSET] = HAS_ARG_FLAG, [_EXIT_TRACE] = HAS_DEOPT_FLAG, [_CHECK_VALIDITY] = HAS_DEOPT_FLAG, @@ -209,6 +209,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_CHECK_GLOBALS] = HAS_DEOPT_FLAG, [_CHECK_BUILTINS] = HAS_DEOPT_FLAG, [_INTERNAL_INCREMENT_OPT_COUNTER] = 0, + [_CHECK_VALIDITY_AND_SET_IP] = HAS_DEOPT_FLAG, }; const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { @@ -264,6 +265,7 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_CHECK_PEP_523] = "_CHECK_PEP_523", [_CHECK_STACK_SPACE] = "_CHECK_STACK_SPACE", [_CHECK_VALIDITY] = "_CHECK_VALIDITY", + [_CHECK_VALIDITY_AND_SET_IP] = "_CHECK_VALIDITY_AND_SET_IP", [_COMPARE_OP] = "_COMPARE_OP", [_COMPARE_OP_FLOAT] = "_COMPARE_OP_FLOAT", [_COMPARE_OP_INT] = "_COMPARE_OP_INT", diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 2ad5878f52e90b7..96b97ca4be6d939 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -4037,10 +4037,9 @@ dummy_func( CHECK_EVAL_BREAKER(); } - op(_SET_IP, (--)) { + op(_SET_IP, (instr_ptr/4 --)) { TIER_TWO_ONLY - // TODO: Put the code pointer in `operand` to avoid indirection via `frame` - frame->instr_ptr = _PyCode_CODE(_PyFrame_GetCode(frame)) + oparg; + frame->instr_ptr = (_Py_CODEUNIT *)instr_ptr; } op(_SAVE_RETURN_OFFSET, (--)) { @@ -4100,6 +4099,11 @@ dummy_func( exe->count++; } + op(_CHECK_VALIDITY_AND_SET_IP, (instr_ptr/4 --)) { + TIER_TWO_ONLY + DEOPT_IF(!current_executor->vm_data.valid); + frame->instr_ptr = (_Py_CODEUNIT *)instr_ptr; + } // END BYTECODES // diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 7d48d6a05a17b06..58d238320276f4b 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -3360,10 +3360,9 @@ } case _SET_IP: { - oparg = CURRENT_OPARG(); + PyObject *instr_ptr = (PyObject *)CURRENT_OPERAND(); TIER_TWO_ONLY - // TODO: Put the code pointer in `operand` to avoid indirection via `frame` - frame->instr_ptr = _PyCode_CODE(_PyFrame_GetCode(frame)) + oparg; + frame->instr_ptr = (_Py_CODEUNIT *)instr_ptr; break; } @@ -3459,4 +3458,12 @@ break; } + case _CHECK_VALIDITY_AND_SET_IP: { + PyObject *instr_ptr = (PyObject *)CURRENT_OPERAND(); + TIER_TWO_ONLY + if (!current_executor->vm_data.valid) goto deoptimize; + frame->instr_ptr = (_Py_CODEUNIT *)instr_ptr; + break; + } + #undef TIER_TWO diff --git a/Python/optimizer.c b/Python/optimizer.c index 13df8c170a537c6..efa19680c9b1f30 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -432,9 +432,8 @@ translate_bytecode_to_trace( top: // Jump here after _PUSH_FRAME or likely branches for (;;) { target = INSTR_IP(instr, code); - RESERVE_RAW(3, "epilogue"); // Always need space for _SET_IP, _CHECK_VALIDITY and _EXIT_TRACE - ADD_TO_TRACE(_SET_IP, target, 0, target); - ADD_TO_TRACE(_CHECK_VALIDITY, 0, 0, target); + RESERVE_RAW(2, "epilogue"); // Always need space for _SET_IP, _CHECK_VALIDITY and _EXIT_TRACE + ADD_TO_TRACE(_CHECK_VALIDITY_AND_SET_IP, 0, (uintptr_t)instr, target); uint32_t opcode = instr->op.code; uint32_t oparg = instr->op.arg; diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index e02ca4d6acf6c14..49974520de924da 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -652,35 +652,62 @@ uop_redundancy_eliminator( static void remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size) { + /* Remove _SET_IP and _CHECK_VALIDITY where possible. + * _SET_IP is needed if the following instruction escapes or + * could error. _CHECK_VALIDITY is needed if the previous + * instruction could have escaped. */ int last_set_ip = -1; - bool maybe_invalid = false; + bool may_have_escaped = false; for (int pc = 0; pc < buffer_size; pc++) { int opcode = buffer[pc].opcode; - if (opcode == _SET_IP) { - buffer[pc].opcode = NOP; - last_set_ip = pc; - } - else if (opcode == _CHECK_VALIDITY) { - if (maybe_invalid) { - maybe_invalid = false; - } - else { + switch (opcode) { + case _SET_IP: buffer[pc].opcode = NOP; - } - } - else if (op_is_end(opcode)) { - break; - } - else { - if (_PyUop_Flags[opcode] & HAS_ESCAPES_FLAG) { - maybe_invalid = true; - if (last_set_ip >= 0) { - buffer[last_set_ip].opcode = _SET_IP; + last_set_ip = pc; + break; + case _CHECK_VALIDITY: + if (may_have_escaped) { + may_have_escaped = false; } - } - if ((_PyUop_Flags[opcode] & HAS_ERROR_FLAG) || opcode == _PUSH_FRAME) { - if (last_set_ip >= 0) { - buffer[last_set_ip].opcode = _SET_IP; + else { + buffer[pc].opcode = NOP; + } + break; + case _CHECK_VALIDITY_AND_SET_IP: + if (may_have_escaped) { + may_have_escaped = false; + buffer[pc].opcode = _CHECK_VALIDITY; + } + else { + buffer[pc].opcode = NOP; + } + last_set_ip = pc; + break; + case _JUMP_TO_TOP: + case _EXIT_TRACE: + return; + default: + { + bool needs_ip = false; + if (_PyUop_Flags[opcode] & HAS_ESCAPES_FLAG) { + needs_ip = true; + may_have_escaped = true; + } + if (_PyUop_Flags[opcode] & HAS_ERROR_FLAG) { + needs_ip = true; + } + if (opcode == _PUSH_FRAME) { + needs_ip = true; + } + if (needs_ip && last_set_ip >= 0) { + if (buffer[last_set_ip].opcode == _CHECK_VALIDITY) { + buffer[last_set_ip].opcode = _CHECK_VALIDITY_AND_SET_IP; + } + else { + assert(buffer[last_set_ip].opcode == _NOP); + buffer[last_set_ip].opcode = _SET_IP; + } + last_set_ip = -1; } } } diff --git a/Python/tier2_redundancy_eliminator_cases.c.h b/Python/tier2_redundancy_eliminator_cases.c.h index 77a7f5b2360c3b5..c2b7bbaf1c44810 100644 --- a/Python/tier2_redundancy_eliminator_cases.c.h +++ b/Python/tier2_redundancy_eliminator_cases.c.h @@ -1674,3 +1674,7 @@ break; } + case _CHECK_VALIDITY_AND_SET_IP: { + break; + } + From 02b63239f1e91f8a03c0b455c5201e6d07f642ab Mon Sep 17 00:00:00 2001 From: Ezio Melotti <ezio.melotti@gmail.com> Date: Tue, 13 Feb 2024 18:07:16 +0100 Subject: [PATCH 250/507] Remove more stray backticks from NEWS files (#115374) * Remove stray backtick from NEWS file * Remove more stray backticks from 3.12.0a1.rst * Remove another stray backtick in 3.13.0a1.rst --- Misc/NEWS.d/3.12.0a1.rst | 4 ++-- Misc/NEWS.d/3.12.0b1.rst | 4 ++-- Misc/NEWS.d/3.13.0a1.rst | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Misc/NEWS.d/3.12.0a1.rst b/Misc/NEWS.d/3.12.0a1.rst index f192bf086ed2598..4739e0fb89a4a8c 100644 --- a/Misc/NEWS.d/3.12.0a1.rst +++ b/Misc/NEWS.d/3.12.0a1.rst @@ -2722,7 +2722,7 @@ on future on an error - e.g. TimeoutError or KeyboardInterrupt. Fix a :mod:`sqlite3` regression where ``*args`` and ``**kwds`` were incorrectly relayed from :py:func:`~sqlite3.connect` to the :class:`~sqlite3.Connection` factory. The regression was introduced in -3.11a1 with PR 24421 (:gh:`85128`). Patch by Erlend E. Aasland.` +3.11a1 with PR 24421 (:gh:`85128`). Patch by Erlend E. Aasland. .. @@ -2988,7 +2988,7 @@ Kumar Aditya. .. section: Library Fix crash in :class:`struct.Struct` when it was not completely initialized -by initializing it in :meth:`~object.__new__``. Patch by Kumar Aditya. +by initializing it in :meth:`~object.__new__`. Patch by Kumar Aditya. .. diff --git a/Misc/NEWS.d/3.12.0b1.rst b/Misc/NEWS.d/3.12.0b1.rst index 21f2c748f405481..d9804be764c9a90 100644 --- a/Misc/NEWS.d/3.12.0b1.rst +++ b/Misc/NEWS.d/3.12.0b1.rst @@ -563,10 +563,10 @@ Complex function calls are now faster and consume no C stack space. .. nonce: fvgsCl .. section: Core and Builtins -``len()`` for 0-dimensional :class:`memoryview`` objects (such as +``len()`` for 0-dimensional :class:`memoryview` objects (such as ``memoryview(ctypes.c_uint8(42))``) now raises a :exc:`TypeError`. Previously this returned ``1``, which was not consistent with ``mem_0d[0]`` -raising an :exc:`IndexError``. +raising an :exc:`IndexError`. .. diff --git a/Misc/NEWS.d/3.13.0a1.rst b/Misc/NEWS.d/3.13.0a1.rst index d385b6a4504f97a..16715bee5a8e491 100644 --- a/Misc/NEWS.d/3.13.0a1.rst +++ b/Misc/NEWS.d/3.13.0a1.rst @@ -4380,7 +4380,7 @@ Patch by Victor Stinner. .. nonce: I6MQhb .. section: Library -:pep:`594`: Remove the :mod:`!cgi`` and :mod:`!cgitb` modules, deprecated in +:pep:`594`: Remove the :mod:`!cgi` and :mod:`!cgitb` modules, deprecated in Python 3.11. Patch by Victor Stinner. .. From 225cd55fe676d128518af31f53b63a591fc4a569 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Tue, 13 Feb 2024 20:45:37 +0300 Subject: [PATCH 251/507] gh-115417: Remove accidentally left debugging print (#115418) gh-115417: Remove debugging print --- Modules/_testcapi/time.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/_testcapi/time.c b/Modules/_testcapi/time.c index 4fbf7dd14ebb663..57eb9135d300296 100644 --- a/Modules/_testcapi/time.c +++ b/Modules/_testcapi/time.c @@ -75,7 +75,6 @@ test_pytime_time(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) printf("ERR! %d\n", (int)t); return NULL; } - printf("... %d\n", (int)t); return pytime_as_float(t); } From 4deb70590e1829081fb6d110e9dc6d060a49d95d Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Tue, 13 Feb 2024 14:35:06 -0500 Subject: [PATCH 252/507] gh-115383: Use runner version to compute config.cache key (#115409) --- .github/workflows/build.yml | 16 ++++++++++++---- .github/workflows/reusable-macos.yml | 4 +++- .github/workflows/reusable-ubuntu.yml | 4 +++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0a2f6da50ed8a09..70db2a6250e8dab 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -131,11 +131,13 @@ jobs: - uses: actions/setup-python@v5 with: python-version: '3.x' + - name: Runner image version + run: echo "IMAGE_VERSION=${ImageVersion}" >> $GITHUB_ENV - name: Restore config.cache uses: actions/cache@v4 with: path: config.cache - key: ${{ github.job }}-${{ runner.os }}-${{ needs.check_source.outputs.config_hash }}-${{ env.pythonLocation }} + key: ${{ github.job }}-${{ runner.os }}-${{ env.IMAGE_VERSION }}-${{ needs.check_source.outputs.config_hash }}-${{ env.pythonLocation }} - name: Install Dependencies run: sudo ./.github/workflows/posix-deps-apt.sh - name: Add ccache to PATH @@ -258,11 +260,13 @@ jobs: LD_LIBRARY_PATH: ${{ github.workspace }}/multissl/openssl/${{ matrix.openssl_ver }}/lib steps: - uses: actions/checkout@v4 + - name: Runner image version + run: echo "IMAGE_VERSION=${ImageVersion}" >> $GITHUB_ENV - name: Restore config.cache uses: actions/cache@v4 with: path: config.cache - key: ${{ github.job }}-${{ runner.os }}-${{ needs.check_source.outputs.config_hash }} + key: ${{ github.job }}-${{ runner.os }}-${{ env.IMAGE_VERSION }}-${{ needs.check_source.outputs.config_hash }} - name: Register gcc problem matcher run: echo "::add-matcher::.github/problem-matchers/gcc.json" - name: Install Dependencies @@ -341,11 +345,13 @@ jobs: run: mkdir -p $CPYTHON_RO_SRCDIR $CPYTHON_BUILDDIR - name: Bind mount sources read-only run: sudo mount --bind -o ro $GITHUB_WORKSPACE $CPYTHON_RO_SRCDIR + - name: Runner image version + run: echo "IMAGE_VERSION=${ImageVersion}" >> $GITHUB_ENV - name: Restore config.cache uses: actions/cache@v4 with: path: ${{ env.CPYTHON_BUILDDIR }}/config.cache - key: ${{ github.job }}-${{ runner.os }}-${{ needs.check_source.outputs.config_hash }} + key: ${{ github.job }}-${{ runner.os }}-${{ env.IMAGE_VERSION }}-${{ needs.check_source.outputs.config_hash }} - name: Configure CPython out-of-tree working-directory: ${{ env.CPYTHON_BUILDDIR }} run: | @@ -420,11 +426,13 @@ jobs: ASAN_OPTIONS: detect_leaks=0:allocator_may_return_null=1:handle_segv=0 steps: - uses: actions/checkout@v4 + - name: Runner image version + run: echo "IMAGE_VERSION=${ImageVersion}" >> $GITHUB_ENV - name: Restore config.cache uses: actions/cache@v4 with: path: config.cache - key: ${{ github.job }}-${{ runner.os }}-${{ needs.check_source.outputs.config_hash }} + key: ${{ github.job }}-${{ runner.os }}-${{ env.IMAGE_VERSION }}-${{ needs.check_source.outputs.config_hash }} - name: Register gcc problem matcher run: echo "::add-matcher::.github/problem-matchers/gcc.json" - name: Install Dependencies diff --git a/.github/workflows/reusable-macos.yml b/.github/workflows/reusable-macos.yml index cad619b78ce5f2c..ba62d9568c6b80a 100644 --- a/.github/workflows/reusable-macos.yml +++ b/.github/workflows/reusable-macos.yml @@ -28,11 +28,13 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 + - name: Runner image version + run: echo "IMAGE_VERSION=${ImageVersion}" >> $GITHUB_ENV - name: Restore config.cache uses: actions/cache@v4 with: path: config.cache - key: ${{ github.job }}-${{ matrix.os }}-${{ inputs.config_hash }} + key: ${{ github.job }}-${{ matrix.os }}-${{ env.IMAGE_VERSION }}-${{ inputs.config_hash }} - name: Install Homebrew dependencies run: brew install pkg-config openssl@3.0 xz gdbm tcl-tk - name: Configure CPython diff --git a/.github/workflows/reusable-ubuntu.yml b/.github/workflows/reusable-ubuntu.yml index 0cbad57f0c6572e..ee64fe62a0bd0a2 100644 --- a/.github/workflows/reusable-ubuntu.yml +++ b/.github/workflows/reusable-ubuntu.yml @@ -52,11 +52,13 @@ jobs: run: mkdir -p $CPYTHON_RO_SRCDIR $CPYTHON_BUILDDIR - name: Bind mount sources read-only run: sudo mount --bind -o ro $GITHUB_WORKSPACE $CPYTHON_RO_SRCDIR + - name: Runner image version + run: echo "IMAGE_VERSION=${ImageVersion}" >> $GITHUB_ENV - name: Restore config.cache uses: actions/cache@v4 with: path: ${{ env.CPYTHON_BUILDDIR }}/config.cache - key: ${{ github.job }}-${{ runner.os }}-${{ inputs.config_hash }} + key: ${{ github.job }}-${{ runner.os }}-${{ env.IMAGE_VERSION }}-${{ inputs.config_hash }} - name: Configure CPython out-of-tree working-directory: ${{ env.CPYTHON_BUILDDIR }} run: ${{ inputs.options }} From 206f73dc5f1b4c3c81119808aa7fd9038661cf90 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Tue, 13 Feb 2024 23:49:13 +0300 Subject: [PATCH 253/507] gh-115391: Fix compiler warning in `Objects/longobject.c` (GH-115368) --- Objects/longobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 932111f58425f22..fe7829833343236 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -1135,7 +1135,7 @@ PyLong_AsNativeBytes(PyObject* vv, void* buffer, Py_ssize_t n, int endianness) if (n <= 0) { // nothing to do! } - else if (n <= sizeof(cv.b)) { + else if (n <= (Py_ssize_t)sizeof(cv.b)) { #if PY_LITTLE_ENDIAN if (little_endian) { memcpy(buffer, cv.b, n); From 514b1c91b8651e8ab9129a34b7482033d2fd4e5b Mon Sep 17 00:00:00 2001 From: Eric Snow <ericsnowcurrently@gmail.com> Date: Tue, 13 Feb 2024 14:56:49 -0700 Subject: [PATCH 254/507] gh-76785: Improved Subinterpreters Compatibility with 3.12 (gh-115424) For the most part, these changes make is substantially easier to backport subinterpreter-related code to 3.12, especially the related modules (e.g. _xxsubinterpreters). The main motivation is to support releasing a PyPI package with the 3.13 capabilities compiled for 3.12. A lot of the changes here involve either hiding details behind macros/functions or splitting up some files. --- Include/internal/pycore_code.h | 9 + Include/internal/pycore_crossinterp.h | 26 + Include/internal/pycore_tstate.h | 7 + Makefile.pre.in | 8 + Modules/_interpreters_common.h | 13 + Modules/_xxinterpchannelsmodule.c | 34 +- Modules/_xxinterpqueuesmodule.c | 22 +- Modules/_xxsubinterpretersmodule.c | 55 +- Python/crossinterp.c | 720 ++------------------ Python/crossinterp_data_lookup.h | 594 ++++++++++++++++ Python/crossinterp_exceptions.h | 80 +++ Tools/c-analyzer/cpython/globals-to-fix.tsv | 8 +- 12 files changed, 857 insertions(+), 719 deletions(-) create mode 100644 Modules/_interpreters_common.h create mode 100644 Python/crossinterp_data_lookup.h create mode 100644 Python/crossinterp_exceptions.h diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h index fdd5918228455d7..855361621320729 100644 --- a/Include/internal/pycore_code.h +++ b/Include/internal/pycore_code.h @@ -8,6 +8,15 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif + +// We hide some of the newer PyCodeObject fields behind macros. +// This helps with backporting certain changes to 3.12. +#define _PyCode_HAS_EXECUTORS(CODE) \ + (CODE->co_executors != NULL) +#define _PyCode_HAS_INSTRUMENTATION(CODE) \ + (CODE->_co_instrumentation_version > 0) + + #define CODE_MAX_WATCHERS 8 /* PEP 659 diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h index d6e297a7e8e6dbc..63abef864ff87fe 100644 --- a/Include/internal/pycore_crossinterp.h +++ b/Include/internal/pycore_crossinterp.h @@ -87,6 +87,11 @@ struct _xid { PyAPI_FUNC(_PyCrossInterpreterData *) _PyCrossInterpreterData_New(void); PyAPI_FUNC(void) _PyCrossInterpreterData_Free(_PyCrossInterpreterData *data); +#define _PyCrossInterpreterData_DATA(DATA) ((DATA)->data) +#define _PyCrossInterpreterData_OBJ(DATA) ((DATA)->obj) +#define _PyCrossInterpreterData_INTERPID(DATA) ((DATA)->interpid) +// Users should not need getters for "new_object" or "free". + /* defining cross-interpreter data */ @@ -101,6 +106,25 @@ PyAPI_FUNC(int) _PyCrossInterpreterData_InitWithSize( PyAPI_FUNC(void) _PyCrossInterpreterData_Clear( PyInterpreterState *, _PyCrossInterpreterData *); +// Normally the Init* functions are sufficient. The only time +// additional initialization might be needed is to set the "free" func, +// though that should be infrequent. +#define _PyCrossInterpreterData_SET_FREE(DATA, FUNC) \ + do { \ + (DATA)->free = (FUNC); \ + } while (0) +// Additionally, some shareable types are essentially light wrappers +// around other shareable types. The crossinterpdatafunc of the wrapper +// can often be implemented by calling the wrapped object's +// crossinterpdatafunc and then changing the "new_object" function. +// We have _PyCrossInterpreterData_SET_NEW_OBJECT() here for that, +// but might be better to have a function like +// _PyCrossInterpreterData_AdaptToWrapper() instead. +#define _PyCrossInterpreterData_SET_NEW_OBJECT(DATA, FUNC) \ + do { \ + (DATA)->new_object = (FUNC); \ + } while (0) + /* using cross-interpreter data */ @@ -170,6 +194,8 @@ extern void _PyXI_Fini(PyInterpreterState *interp); extern PyStatus _PyXI_InitTypes(PyInterpreterState *interp); extern void _PyXI_FiniTypes(PyInterpreterState *interp); +#define _PyInterpreterState_GetXIState(interp) (&(interp)->xi) + /***************************/ /* short-term data sharing */ diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h index 77a1dc59163d212..3e8fcf5b6ec1fa2 100644 --- a/Include/internal/pycore_tstate.h +++ b/Include/internal/pycore_tstate.h @@ -13,6 +13,13 @@ extern "C" { #include "pycore_brc.h" // struct _brc_thread_state +static inline void +_PyThreadState_SetWhence(PyThreadState *tstate, int whence) +{ + tstate->_whence = whence; +} + + // Every PyThreadState is actually allocated as a _PyThreadStateImpl. The // PyThreadState fields are exposed as part of the C API, although most fields // are intended to be private. The _PyThreadStateImpl fields not exposed. diff --git a/Makefile.pre.in b/Makefile.pre.in index d3b18acad61ce5a..4b9d9c171b9efb3 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1671,6 +1671,14 @@ Modules/pwdmodule.o: $(srcdir)/Modules/pwdmodule.c $(srcdir)/Modules/posixmodule Modules/signalmodule.o: $(srcdir)/Modules/signalmodule.c $(srcdir)/Modules/posixmodule.h +Modules/_xxsubinterpretersmodule.o: $(srcdir)/Modules/_xxsubinterpretersmodule.c $(srcdir)/Modules/_interpreters_common.h + +Modules/_xxinterpqueuesmodule.o: $(srcdir)/Modules/_xxinterpqueuesmodule.c $(srcdir)/Modules/_interpreters_common.h + +Modules/_xxinterpchannelsmodule.o: $(srcdir)/Modules/_xxinterpchannelsmodule.c $(srcdir)/Modules/_interpreters_common.h + +Python/crossinterp.o: $(srcdir)/Python/crossinterp.c $(srcdir)/Python/crossinterp_data_lookup.h $(srcdir)/Python/crossinterp_exceptions.h + Python/dynload_shlib.o: $(srcdir)/Python/dynload_shlib.c Makefile $(CC) -c $(PY_CORE_CFLAGS) \ -DSOABI='"$(SOABI)"' \ diff --git a/Modules/_interpreters_common.h b/Modules/_interpreters_common.h new file mode 100644 index 000000000000000..5661a26d8790d18 --- /dev/null +++ b/Modules/_interpreters_common.h @@ -0,0 +1,13 @@ + +#define _RESOLVE_MODINIT_FUNC_NAME(NAME) \ + PyInit_ ## NAME +#define RESOLVE_MODINIT_FUNC_NAME(NAME) \ + _RESOLVE_MODINIT_FUNC_NAME(NAME) + + +static int +ensure_xid_class(PyTypeObject *cls, crossinterpdatafunc getdata) +{ + //assert(cls->tp_flags & Py_TPFLAGS_HEAPTYPE); + return _PyCrossInterpreterData_RegisterClass(cls, getdata); +} diff --git a/Modules/_xxinterpchannelsmodule.c b/Modules/_xxinterpchannelsmodule.c index 4e9b8a82a3f630b..a2974aced12ca06 100644 --- a/Modules/_xxinterpchannelsmodule.c +++ b/Modules/_xxinterpchannelsmodule.c @@ -17,6 +17,8 @@ #include <sched.h> // sched_yield() #endif +#include "_interpreters_common.h" + /* This module has the following process-global state: @@ -80,7 +82,9 @@ channel's queue, which are safely managed via the _PyCrossInterpreterData_*() API.. The module does not create any objects that are shared globally. */ -#define MODULE_NAME "_xxinterpchannels" +#define MODULE_NAME _xxinterpchannels +#define MODULE_NAME_STR Py_STRINGIFY(MODULE_NAME) +#define MODINIT_FUNC_NAME RESOLVE_MODINIT_FUNC_NAME(MODULE_NAME) #define GLOBAL_MALLOC(TYPE) \ @@ -101,7 +105,7 @@ static int register_xid_class(PyTypeObject *cls, crossinterpdatafunc shared, struct xid_class_registry *classes) { - int res = _PyCrossInterpreterData_RegisterClass(cls, shared); + int res = ensure_xid_class(cls, shared); if (res == 0) { assert(classes->count < MAX_XID_CLASSES); // The class has refs elsewhere, so we need to incref here. @@ -167,7 +171,7 @@ _get_current_interp(void) static PyObject * _get_current_module(void) { - PyObject *name = PyUnicode_FromString(MODULE_NAME); + PyObject *name = PyUnicode_FromString(MODULE_NAME_STR); if (name == NULL) { return NULL; } @@ -217,7 +221,7 @@ add_new_exception(PyObject *mod, const char *name, PyObject *base) } #define ADD_NEW_EXCEPTION(MOD, NAME, BASE) \ - add_new_exception(MOD, MODULE_NAME "." Py_STRINGIFY(NAME), BASE) + add_new_exception(MOD, MODULE_NAME_STR "." Py_STRINGIFY(NAME), BASE) static PyTypeObject * add_new_type(PyObject *mod, PyType_Spec *spec, crossinterpdatafunc shared, @@ -299,7 +303,7 @@ _get_current_module_state(void) if (mod == NULL) { // XXX import it? PyErr_SetString(PyExc_RuntimeError, - MODULE_NAME " module not imported yet"); + MODULE_NAME_STR " module not imported yet"); return NULL; } module_state *state = get_module_state(mod); @@ -784,7 +788,7 @@ _channelqueue_clear_interpreter(_channelqueue *queue, int64_t interpid) while (next != NULL) { _channelitem *item = next; next = item->next; - if (item->data->interpid == interpid) { + if (_PyCrossInterpreterData_INTERPID(item->data) == interpid) { if (prev == NULL) { queue->first = item->next; } @@ -2126,7 +2130,7 @@ static PyStructSequence_Field channel_info_fields[] = { }; static PyStructSequence_Desc channel_info_desc = { - .name = MODULE_NAME ".ChannelInfo", + .name = MODULE_NAME_STR ".ChannelInfo", .doc = channel_info_doc, .fields = channel_info_fields, .n_in_sequence = 8, @@ -2474,10 +2478,11 @@ struct _channelid_xid { static PyObject * _channelid_from_xid(_PyCrossInterpreterData *data) { - struct _channelid_xid *xid = (struct _channelid_xid *)data->data; + struct _channelid_xid *xid = \ + (struct _channelid_xid *)_PyCrossInterpreterData_DATA(data); // It might not be imported yet, so we can't use _get_current_module(). - PyObject *mod = PyImport_ImportModule(MODULE_NAME); + PyObject *mod = PyImport_ImportModule(MODULE_NAME_STR); if (mod == NULL) { return NULL; } @@ -2530,7 +2535,8 @@ _channelid_shared(PyThreadState *tstate, PyObject *obj, { return -1; } - struct _channelid_xid *xid = (struct _channelid_xid *)data->data; + struct _channelid_xid *xid = \ + (struct _channelid_xid *)_PyCrossInterpreterData_DATA(data); xid->cid = ((channelid *)obj)->cid; xid->end = ((channelid *)obj)->end; xid->resolve = ((channelid *)obj)->resolve; @@ -2601,7 +2607,7 @@ static PyType_Slot channelid_typeslots[] = { }; static PyType_Spec channelid_typespec = { - .name = MODULE_NAME ".ChannelID", + .name = MODULE_NAME_STR ".ChannelID", .basicsize = sizeof(channelid), .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE), @@ -2680,7 +2686,7 @@ _channelend_shared(PyThreadState *tstate, PyObject *obj, if (res < 0) { return -1; } - data->new_object = _channelend_from_xid; + _PyCrossInterpreterData_SET_NEW_OBJECT(data, _channelend_from_xid); return 0; } @@ -3379,7 +3385,7 @@ module_free(void *mod) static struct PyModuleDef moduledef = { .m_base = PyModuleDef_HEAD_INIT, - .m_name = MODULE_NAME, + .m_name = MODULE_NAME_STR, .m_doc = module_doc, .m_size = sizeof(module_state), .m_methods = module_functions, @@ -3390,7 +3396,7 @@ static struct PyModuleDef moduledef = { }; PyMODINIT_FUNC -PyInit__xxinterpchannels(void) +MODINIT_FUNC_NAME(void) { return PyModuleDef_Init(&moduledef); } diff --git a/Modules/_xxinterpqueuesmodule.c b/Modules/_xxinterpqueuesmodule.c index 537ba9188055dd1..7d8c67f49fefb85 100644 --- a/Modules/_xxinterpqueuesmodule.c +++ b/Modules/_xxinterpqueuesmodule.c @@ -8,8 +8,12 @@ #include "Python.h" #include "pycore_crossinterp.h" // struct _xid +#include "_interpreters_common.h" -#define MODULE_NAME "_xxinterpqueues" + +#define MODULE_NAME _xxinterpqueues +#define MODULE_NAME_STR Py_STRINGIFY(MODULE_NAME) +#define MODINIT_FUNC_NAME RESOLVE_MODINIT_FUNC_NAME(MODULE_NAME) #define GLOBAL_MALLOC(TYPE) \ @@ -64,7 +68,7 @@ _get_current_interp(void) static PyObject * _get_current_module(void) { - PyObject *name = PyUnicode_FromString(MODULE_NAME); + PyObject *name = PyUnicode_FromString(MODULE_NAME_STR); if (name == NULL) { return NULL; } @@ -602,7 +606,7 @@ _queue_clear_interpreter(_queue *queue, int64_t interpid) while (next != NULL) { _queueitem *item = next; next = item->next; - if (item->data->interpid == interpid) { + if (_PyCrossInterpreterData_INTERPID(item->data) == interpid) { if (prev == NULL) { queue->items.first = item->next; } @@ -1062,7 +1066,7 @@ set_external_queue_type(PyObject *module, PyTypeObject *queue_type) } state->queue_type = (PyTypeObject *)Py_NewRef(queue_type); - if (_PyCrossInterpreterData_RegisterClass(queue_type, _queueobj_shared) < 0) { + if (ensure_xid_class(queue_type, _queueobj_shared) < 0) { return -1; } @@ -1130,7 +1134,7 @@ _queueid_xid_free(void *data) static PyObject * _queueobj_from_xid(_PyCrossInterpreterData *data) { - int64_t qid = *(int64_t *)data->data; + int64_t qid = *(int64_t *)_PyCrossInterpreterData_DATA(data); PyObject *qidobj = PyLong_FromLongLong(qid); if (qidobj == NULL) { return NULL; @@ -1140,7 +1144,7 @@ _queueobj_from_xid(_PyCrossInterpreterData *data) if (mod == NULL) { // XXX import it? PyErr_SetString(PyExc_RuntimeError, - MODULE_NAME " module not imported yet"); + MODULE_NAME_STR " module not imported yet"); return NULL; } @@ -1181,7 +1185,7 @@ _queueobj_shared(PyThreadState *tstate, PyObject *queueobj, _PyCrossInterpreterData_Init(data, tstate->interp, raw, NULL, _queueobj_from_xid); Py_DECREF(qidobj); - data->free = _queueid_xid_free; + _PyCrossInterpreterData_SET_FREE(data, _queueid_xid_free); return 0; } @@ -1670,7 +1674,7 @@ module_free(void *mod) static struct PyModuleDef moduledef = { .m_base = PyModuleDef_HEAD_INIT, - .m_name = MODULE_NAME, + .m_name = MODULE_NAME_STR, .m_doc = module_doc, .m_size = sizeof(module_state), .m_methods = module_functions, @@ -1681,7 +1685,7 @@ static struct PyModuleDef moduledef = { }; PyMODINIT_FUNC -PyInit__xxinterpqueues(void) +MODINIT_FUNC_NAME(void) { return PyModuleDef_Init(&moduledef); } diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 4e9e13457a9eb3f..b4004d165078f71 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -19,8 +19,12 @@ #include "interpreteridobject.h" #include "marshal.h" // PyMarshal_ReadObjectFromString() +#include "_interpreters_common.h" -#define MODULE_NAME "_xxsubinterpreters" + +#define MODULE_NAME _xxsubinterpreters +#define MODULE_NAME_STR Py_STRINGIFY(MODULE_NAME) +#define MODINIT_FUNC_NAME RESOLVE_MODINIT_FUNC_NAME(MODULE_NAME) static PyInterpreterState * @@ -125,7 +129,7 @@ get_interpid_obj(PyInterpreterState *interp) static PyObject * _get_current_module(void) { - PyObject *name = PyUnicode_FromString(MODULE_NAME); + PyObject *name = PyUnicode_FromString(MODULE_NAME_STR); if (name == NULL) { return NULL; } @@ -152,16 +156,16 @@ typedef struct { static PyObject * xibufferview_from_xid(PyTypeObject *cls, _PyCrossInterpreterData *data) { - assert(data->data != NULL); - assert(data->obj == NULL); - assert(data->interpid >= 0); + assert(_PyCrossInterpreterData_DATA(data) != NULL); + assert(_PyCrossInterpreterData_OBJ(data) == NULL); + assert(_PyCrossInterpreterData_INTERPID(data) >= 0); XIBufferViewObject *self = PyObject_Malloc(sizeof(XIBufferViewObject)); if (self == NULL) { return NULL; } PyObject_Init((PyObject *)self, cls); - self->view = (Py_buffer *)data->data; - self->interpid = data->interpid; + self->view = (Py_buffer *)_PyCrossInterpreterData_DATA(data); + self->interpid = _PyCrossInterpreterData_INTERPID(data); return (PyObject *)self; } @@ -209,7 +213,7 @@ static PyType_Slot XIBufferViewType_slots[] = { }; static PyType_Spec XIBufferViewType_spec = { - .name = MODULE_NAME ".CrossInterpreterBufferView", + .name = MODULE_NAME_STR ".CrossInterpreterBufferView", .basicsize = sizeof(XIBufferViewObject), .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE), @@ -267,8 +271,7 @@ register_memoryview_xid(PyObject *mod, PyTypeObject **p_state) *p_state = cls; // Register XID for the builtin memoryview type. - if (_PyCrossInterpreterData_RegisterClass( - &PyMemoryView_Type, _memoryview_shared) < 0) { + if (ensure_xid_class(&PyMemoryView_Type, _memoryview_shared) < 0) { return -1; } // We don't ever bother un-registering memoryview. @@ -303,7 +306,7 @@ _get_current_module_state(void) if (mod == NULL) { // XXX import it? PyErr_SetString(PyExc_RuntimeError, - MODULE_NAME " module not imported yet"); + MODULE_NAME_STR " module not imported yet"); return NULL; } module_state *state = get_module_state(mod); @@ -372,9 +375,7 @@ check_code_object(PyCodeObject *code) } // We trust that no code objects under co_consts have unbound cell vars. - if (code->co_executors != NULL - || code->_co_instrumentation_version > 0) - { + if (_PyCode_HAS_EXECUTORS(code) || _PyCode_HAS_INSTRUMENTATION(code)) { return "only basic functions are supported"; } if (code->_co_monitoring != NULL) { @@ -602,7 +603,7 @@ interp_destroy(PyObject *self, PyObject *args, PyObject *kwds) // Destroy the interpreter. PyThreadState *tstate = PyThreadState_New(interp); - tstate->_whence = _PyThreadState_WHENCE_INTERP; + _PyThreadState_SetWhence(tstate, _PyThreadState_WHENCE_INTERP); // XXX Possible GILState issues? PyThreadState *save_tstate = PyThreadState_Swap(tstate); Py_EndInterpreter(tstate); @@ -691,7 +692,7 @@ static PyObject * interp_set___main___attrs(PyObject *self, PyObject *args) { PyObject *id, *updates; - if (!PyArg_ParseTuple(args, "OO:" MODULE_NAME ".set___main___attrs", + if (!PyArg_ParseTuple(args, "OO:" MODULE_NAME_STR ".set___main___attrs", &id, &updates)) { return NULL; @@ -856,18 +857,18 @@ interp_exec(PyObject *self, PyObject *args, PyObject *kwds) PyObject *id, *code; PyObject *shared = NULL; if (!PyArg_ParseTupleAndKeywords(args, kwds, - "OO|O:" MODULE_NAME ".exec", kwlist, + "OO|O:" MODULE_NAME_STR ".exec", kwlist, &id, &code, &shared)) { return NULL; } const char *expected = "a string, a function, or a code object"; if (PyUnicode_Check(code)) { - code = (PyObject *)convert_script_arg(code, MODULE_NAME ".exec", + code = (PyObject *)convert_script_arg(code, MODULE_NAME_STR ".exec", "argument 2", expected); } else { - code = (PyObject *)convert_code_arg(code, MODULE_NAME ".exec", + code = (PyObject *)convert_code_arg(code, MODULE_NAME_STR ".exec", "argument 2", expected); } if (code == NULL) { @@ -908,12 +909,12 @@ interp_run_string(PyObject *self, PyObject *args, PyObject *kwds) PyObject *id, *script; PyObject *shared = NULL; if (!PyArg_ParseTupleAndKeywords(args, kwds, - "OU|O:" MODULE_NAME ".run_string", kwlist, + "OU|O:" MODULE_NAME_STR ".run_string", kwlist, &id, &script, &shared)) { return NULL; } - script = (PyObject *)convert_script_arg(script, MODULE_NAME ".exec", + script = (PyObject *)convert_script_arg(script, MODULE_NAME_STR ".exec", "argument 2", "a string"); if (script == NULL) { return NULL; @@ -934,7 +935,7 @@ PyDoc_STRVAR(run_string_doc, \n\ Execute the provided string in the identified interpreter.\n\ \n\ -(See " MODULE_NAME ".exec()."); +(See " MODULE_NAME_STR ".exec()."); static PyObject * interp_run_func(PyObject *self, PyObject *args, PyObject *kwds) @@ -943,12 +944,12 @@ interp_run_func(PyObject *self, PyObject *args, PyObject *kwds) PyObject *id, *func; PyObject *shared = NULL; if (!PyArg_ParseTupleAndKeywords(args, kwds, - "OO|O:" MODULE_NAME ".run_func", kwlist, + "OO|O:" MODULE_NAME_STR ".run_func", kwlist, &id, &func, &shared)) { return NULL; } - PyCodeObject *code = convert_code_arg(func, MODULE_NAME ".exec", + PyCodeObject *code = convert_code_arg(func, MODULE_NAME_STR ".exec", "argument 2", "a function or a code object"); if (code == NULL) { @@ -972,7 +973,7 @@ Execute the body of the provided function in the identified interpreter.\n\ Code objects are also supported. In both cases, closures and args\n\ are not supported. Methods and other callables are not supported either.\n\ \n\ -(See " MODULE_NAME ".exec()."); +(See " MODULE_NAME_STR ".exec()."); static PyObject * @@ -1166,7 +1167,7 @@ module_free(void *mod) static struct PyModuleDef moduledef = { .m_base = PyModuleDef_HEAD_INIT, - .m_name = MODULE_NAME, + .m_name = MODULE_NAME_STR, .m_doc = module_doc, .m_size = sizeof(module_state), .m_methods = module_functions, @@ -1177,7 +1178,7 @@ static struct PyModuleDef moduledef = { }; PyMODINIT_FUNC -PyInit__xxsubinterpreters(void) +MODINIT_FUNC_NAME(void) { return PyModuleDef_Init(&moduledef); } diff --git a/Python/crossinterp.c b/Python/crossinterp.c index c6ed7daeb1074a8..143b261f9a53960 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -7,7 +7,6 @@ #include "pycore_initconfig.h" // _PyStatus_OK() #include "pycore_namespace.h" //_PyNamespace_New() #include "pycore_pyerrors.h" // _PyErr_Clear() -#include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_typeobject.h" // _PyType_GetModuleName() #include "pycore_weakref.h" // _PyWeakref_GET_REF() @@ -16,47 +15,12 @@ /* exceptions */ /**************/ -/* InterpreterError extends Exception */ - -static PyTypeObject _PyExc_InterpreterError = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "InterpreterError", - .tp_doc = PyDoc_STR("An interpreter was not found."), - //.tp_base = (PyTypeObject *)PyExc_BaseException, -}; -PyObject *PyExc_InterpreterError = (PyObject *)&_PyExc_InterpreterError; - -/* InterpreterNotFoundError extends InterpreterError */ - -static PyTypeObject _PyExc_InterpreterNotFoundError = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "InterpreterNotFoundError", - .tp_doc = PyDoc_STR("An interpreter was not found."), - .tp_base = &_PyExc_InterpreterError, -}; -PyObject *PyExc_InterpreterNotFoundError = (PyObject *)&_PyExc_InterpreterNotFoundError; - -/* lifecycle */ - -static int -init_exceptions(PyInterpreterState *interp) -{ - _PyExc_InterpreterError.tp_base = (PyTypeObject *)PyExc_BaseException; - if (_PyStaticType_InitBuiltin(interp, &_PyExc_InterpreterError) < 0) { - return -1; - } - if (_PyStaticType_InitBuiltin(interp, &_PyExc_InterpreterNotFoundError) < 0) { - return -1; - } - return 0; -} - -static void -fini_exceptions(PyInterpreterState *interp) -{ - _PyStaticType_Dealloc(interp, &_PyExc_InterpreterNotFoundError); - _PyStaticType_Dealloc(interp, &_PyExc_InterpreterError); -} +static int init_exceptions(PyInterpreterState *); +static void fini_exceptions(PyInterpreterState *); +static int _init_not_shareable_error_type(PyInterpreterState *); +static void _fini_not_shareable_error_type(PyInterpreterState *); +static PyObject * _get_not_shareable_error_type(PyInterpreterState *); +#include "crossinterp_exceptions.h" /***************************/ @@ -67,7 +31,7 @@ int _Py_CallInInterpreter(PyInterpreterState *interp, _Py_simple_func func, void *arg) { - if (interp == _PyThreadState_GetCurrent()->interp) { + if (interp == PyInterpreterState_Get()) { return func(arg); } // XXX Emit a warning if this fails? @@ -79,7 +43,7 @@ int _Py_CallInInterpreterAndRawFree(PyInterpreterState *interp, _Py_simple_func func, void *arg) { - if (interp == _PyThreadState_GetCurrent()->interp) { + if (interp == PyInterpreterState_Get()) { int res = func(arg); PyMem_RawFree(arg); return res; @@ -94,6 +58,20 @@ _Py_CallInInterpreterAndRawFree(PyInterpreterState *interp, /* cross-interpreter data */ /**************************/ +/* registry of {type -> crossinterpdatafunc} */ + +/* For now we use a global registry of shareable classes. An + alternative would be to add a tp_* slot for a class's + crossinterpdatafunc. It would be simpler and more efficient. */ + +static void xid_lookup_init(PyInterpreterState *); +static void xid_lookup_fini(PyInterpreterState *); +static crossinterpdatafunc lookup_getdata(PyInterpreterState *, PyObject *); +#include "crossinterp_data_lookup.h" + + +/* lifecycle */ + _PyCrossInterpreterData * _PyCrossInterpreterData_New(void) { @@ -114,38 +92,6 @@ _PyCrossInterpreterData_Free(_PyCrossInterpreterData *xid) } -/* exceptions */ - -static PyStatus -_init_not_shareable_error_type(PyInterpreterState *interp) -{ - const char *name = "_interpreters.NotShareableError"; - PyObject *base = PyExc_ValueError; - PyObject *ns = NULL; - PyObject *exctype = PyErr_NewException(name, base, ns); - if (exctype == NULL) { - PyErr_Clear(); - return _PyStatus_ERR("could not initialize NotShareableError"); - } - - interp->xi.PyExc_NotShareableError = exctype; - return _PyStatus_OK(); -} - -static void -_fini_not_shareable_error_type(PyInterpreterState *interp) -{ - Py_CLEAR(interp->xi.PyExc_NotShareableError); -} - -static PyObject * -_get_not_shareable_error_type(PyInterpreterState *interp) -{ - assert(interp->xi.PyExc_NotShareableError != NULL); - return interp->xi.PyExc_NotShareableError; -} - - /* defining cross-interpreter data */ static inline void @@ -156,7 +102,7 @@ _xidata_init(_PyCrossInterpreterData *data) assert(data->data == NULL); assert(data->obj == NULL); *data = (_PyCrossInterpreterData){0}; - data->interpid = -1; + _PyCrossInterpreterData_INTERPID(data) = -1; } static inline void @@ -193,7 +139,9 @@ _PyCrossInterpreterData_Init(_PyCrossInterpreterData *data, // Ideally every object would know its owning interpreter. // Until then, we have to rely on the caller to identify it // (but we don't need it in all cases). - data->interpid = (interp != NULL) ? interp->id : -1; + _PyCrossInterpreterData_INTERPID(data) = (interp != NULL) + ? PyInterpreterState_GetID(interp) + : -1; data->new_object = new_object; } @@ -223,8 +171,8 @@ _PyCrossInterpreterData_Clear(PyInterpreterState *interp, assert(data != NULL); // This must be called in the owning interpreter. assert(interp == NULL - || data->interpid == -1 - || data->interpid == interp->id); + || _PyCrossInterpreterData_INTERPID(data) == -1 + || _PyCrossInterpreterData_INTERPID(data) == PyInterpreterState_GetID(interp)); _xidata_clear(data); } @@ -238,13 +186,13 @@ _check_xidata(PyThreadState *tstate, _PyCrossInterpreterData *data) // data->obj may be NULL, so we don't check it. - if (data->interpid < 0) { - _PyErr_SetString(tstate, PyExc_SystemError, "missing interp"); + if (_PyCrossInterpreterData_INTERPID(data) < 0) { + PyErr_SetString(PyExc_SystemError, "missing interp"); return -1; } if (data->new_object == NULL) { - _PyErr_SetString(tstate, PyExc_SystemError, "missing new_object func"); + PyErr_SetString(PyExc_SystemError, "missing new_object func"); return -1; } @@ -253,25 +201,6 @@ _check_xidata(PyThreadState *tstate, _PyCrossInterpreterData *data) return 0; } -static crossinterpdatafunc _lookup_getdata_from_registry( - PyInterpreterState *, PyObject *); - -static crossinterpdatafunc -_lookup_getdata(PyInterpreterState *interp, PyObject *obj) -{ - /* Cross-interpreter objects are looked up by exact match on the class. - We can reassess this policy when we move from a global registry to a - tp_* slot. */ - return _lookup_getdata_from_registry(interp, obj); -} - -crossinterpdatafunc -_PyCrossInterpreterData_Lookup(PyObject *obj) -{ - PyInterpreterState *interp = _PyInterpreterState_GET(); - return _lookup_getdata(interp, obj); -} - static inline void _set_xid_lookup_failure(PyInterpreterState *interp, PyObject *obj, const char *msg) @@ -295,8 +224,8 @@ _set_xid_lookup_failure(PyInterpreterState *interp, int _PyObject_CheckCrossInterpreterData(PyObject *obj) { - PyInterpreterState *interp = _PyInterpreterState_GET(); - crossinterpdatafunc getdata = _lookup_getdata(interp, obj); + PyInterpreterState *interp = PyInterpreterState_Get(); + crossinterpdatafunc getdata = lookup_getdata(interp, obj); if (getdata == NULL) { if (!PyErr_Occurred()) { _set_xid_lookup_failure(interp, obj, NULL); @@ -309,20 +238,16 @@ _PyObject_CheckCrossInterpreterData(PyObject *obj) int _PyObject_GetCrossInterpreterData(PyObject *obj, _PyCrossInterpreterData *data) { - PyThreadState *tstate = _PyThreadState_GetCurrent(); -#ifdef Py_DEBUG - // The caller must hold the GIL - _Py_EnsureTstateNotNULL(tstate); -#endif + PyThreadState *tstate = PyThreadState_Get(); PyInterpreterState *interp = tstate->interp; // Reset data before re-populating. *data = (_PyCrossInterpreterData){0}; - data->interpid = -1; + _PyCrossInterpreterData_INTERPID(data) = -1; // Call the "getdata" func for the object. Py_INCREF(obj); - crossinterpdatafunc getdata = _lookup_getdata(interp, obj); + crossinterpdatafunc getdata = lookup_getdata(interp, obj); if (getdata == NULL) { Py_DECREF(obj); if (!PyErr_Occurred()) { @@ -337,7 +262,7 @@ _PyObject_GetCrossInterpreterData(PyObject *obj, _PyCrossInterpreterData *data) } // Fill in the blanks and validate the result. - data->interpid = interp->id; + _PyCrossInterpreterData_INTERPID(data) = PyInterpreterState_GetID(interp); if (_check_xidata(tstate, data) != 0) { (void)_PyCrossInterpreterData_Release(data); return -1; @@ -374,7 +299,8 @@ _xidata_release(_PyCrossInterpreterData *data, int rawfree) } // Switch to the original interpreter. - PyInterpreterState *interp = _PyInterpreterState_LookUpID(data->interpid); + PyInterpreterState *interp = _PyInterpreterState_LookUpID( + _PyCrossInterpreterData_INTERPID(data)); if (interp == NULL) { // The interpreter was already destroyed. // This function shouldn't have been called. @@ -408,538 +334,6 @@ _PyCrossInterpreterData_ReleaseAndRawFree(_PyCrossInterpreterData *data) } -/* registry of {type -> crossinterpdatafunc} */ - -/* For now we use a global registry of shareable classes. An - alternative would be to add a tp_* slot for a class's - crossinterpdatafunc. It would be simpler and more efficient. */ - -static inline struct _xidregistry * -_get_global_xidregistry(_PyRuntimeState *runtime) -{ - return &runtime->xi.registry; -} - -static inline struct _xidregistry * -_get_xidregistry(PyInterpreterState *interp) -{ - return &interp->xi.registry; -} - -static inline struct _xidregistry * -_get_xidregistry_for_type(PyInterpreterState *interp, PyTypeObject *cls) -{ - struct _xidregistry *registry = _get_global_xidregistry(interp->runtime); - if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) { - registry = _get_xidregistry(interp); - } - return registry; -} - -static int -_xidregistry_add_type(struct _xidregistry *xidregistry, - PyTypeObject *cls, crossinterpdatafunc getdata) -{ - struct _xidregitem *newhead = PyMem_RawMalloc(sizeof(struct _xidregitem)); - if (newhead == NULL) { - return -1; - } - *newhead = (struct _xidregitem){ - // We do not keep a reference, to avoid keeping the class alive. - .cls = cls, - .refcount = 1, - .getdata = getdata, - }; - if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) { - // XXX Assign a callback to clear the entry from the registry? - newhead->weakref = PyWeakref_NewRef((PyObject *)cls, NULL); - if (newhead->weakref == NULL) { - PyMem_RawFree(newhead); - return -1; - } - } - newhead->next = xidregistry->head; - if (newhead->next != NULL) { - newhead->next->prev = newhead; - } - xidregistry->head = newhead; - return 0; -} - -static struct _xidregitem * -_xidregistry_remove_entry(struct _xidregistry *xidregistry, - struct _xidregitem *entry) -{ - struct _xidregitem *next = entry->next; - if (entry->prev != NULL) { - assert(entry->prev->next == entry); - entry->prev->next = next; - } - else { - assert(xidregistry->head == entry); - xidregistry->head = next; - } - if (next != NULL) { - next->prev = entry->prev; - } - Py_XDECREF(entry->weakref); - PyMem_RawFree(entry); - return next; -} - -static void -_xidregistry_clear(struct _xidregistry *xidregistry) -{ - struct _xidregitem *cur = xidregistry->head; - xidregistry->head = NULL; - while (cur != NULL) { - struct _xidregitem *next = cur->next; - Py_XDECREF(cur->weakref); - PyMem_RawFree(cur); - cur = next; - } -} - -static void -_xidregistry_lock(struct _xidregistry *registry) -{ - if (registry->global) { - PyMutex_Lock(®istry->mutex); - } - // else: Within an interpreter we rely on the GIL instead of a separate lock. -} - -static void -_xidregistry_unlock(struct _xidregistry *registry) -{ - if (registry->global) { - PyMutex_Unlock(®istry->mutex); - } -} - -static struct _xidregitem * -_xidregistry_find_type(struct _xidregistry *xidregistry, PyTypeObject *cls) -{ - struct _xidregitem *cur = xidregistry->head; - while (cur != NULL) { - if (cur->weakref != NULL) { - // cur is/was a heap type. - PyObject *registered = _PyWeakref_GET_REF(cur->weakref); - if (registered == NULL) { - // The weakly ref'ed object was freed. - cur = _xidregistry_remove_entry(xidregistry, cur); - continue; - } - assert(PyType_Check(registered)); - assert(cur->cls == (PyTypeObject *)registered); - assert(cur->cls->tp_flags & Py_TPFLAGS_HEAPTYPE); - Py_DECREF(registered); - } - if (cur->cls == cls) { - return cur; - } - cur = cur->next; - } - return NULL; -} - -int -_PyCrossInterpreterData_RegisterClass(PyTypeObject *cls, - crossinterpdatafunc getdata) -{ - if (!PyType_Check(cls)) { - PyErr_Format(PyExc_ValueError, "only classes may be registered"); - return -1; - } - if (getdata == NULL) { - PyErr_Format(PyExc_ValueError, "missing 'getdata' func"); - return -1; - } - - int res = 0; - PyInterpreterState *interp = _PyInterpreterState_GET(); - struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls); - _xidregistry_lock(xidregistry); - - struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls); - if (matched != NULL) { - assert(matched->getdata == getdata); - matched->refcount += 1; - goto finally; - } - - res = _xidregistry_add_type(xidregistry, cls, getdata); - -finally: - _xidregistry_unlock(xidregistry); - return res; -} - -int -_PyCrossInterpreterData_UnregisterClass(PyTypeObject *cls) -{ - int res = 0; - PyInterpreterState *interp = _PyInterpreterState_GET(); - struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls); - _xidregistry_lock(xidregistry); - - struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls); - if (matched != NULL) { - assert(matched->refcount > 0); - matched->refcount -= 1; - if (matched->refcount == 0) { - (void)_xidregistry_remove_entry(xidregistry, matched); - } - res = 1; - } - - _xidregistry_unlock(xidregistry); - return res; -} - -static crossinterpdatafunc -_lookup_getdata_from_registry(PyInterpreterState *interp, PyObject *obj) -{ - PyTypeObject *cls = Py_TYPE(obj); - - struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls); - _xidregistry_lock(xidregistry); - - struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls); - crossinterpdatafunc func = matched != NULL ? matched->getdata : NULL; - - _xidregistry_unlock(xidregistry); - return func; -} - -/* cross-interpreter data for builtin types */ - -// bytes - -struct _shared_bytes_data { - char *bytes; - Py_ssize_t len; -}; - -static PyObject * -_new_bytes_object(_PyCrossInterpreterData *data) -{ - struct _shared_bytes_data *shared = (struct _shared_bytes_data *)(data->data); - return PyBytes_FromStringAndSize(shared->bytes, shared->len); -} - -static int -_bytes_shared(PyThreadState *tstate, PyObject *obj, - _PyCrossInterpreterData *data) -{ - if (_PyCrossInterpreterData_InitWithSize( - data, tstate->interp, sizeof(struct _shared_bytes_data), obj, - _new_bytes_object - ) < 0) - { - return -1; - } - struct _shared_bytes_data *shared = (struct _shared_bytes_data *)data->data; - if (PyBytes_AsStringAndSize(obj, &shared->bytes, &shared->len) < 0) { - _PyCrossInterpreterData_Clear(tstate->interp, data); - return -1; - } - return 0; -} - -// str - -struct _shared_str_data { - int kind; - const void *buffer; - Py_ssize_t len; -}; - -static PyObject * -_new_str_object(_PyCrossInterpreterData *data) -{ - struct _shared_str_data *shared = (struct _shared_str_data *)(data->data); - return PyUnicode_FromKindAndData(shared->kind, shared->buffer, shared->len); -} - -static int -_str_shared(PyThreadState *tstate, PyObject *obj, - _PyCrossInterpreterData *data) -{ - if (_PyCrossInterpreterData_InitWithSize( - data, tstate->interp, sizeof(struct _shared_str_data), obj, - _new_str_object - ) < 0) - { - return -1; - } - struct _shared_str_data *shared = (struct _shared_str_data *)data->data; - shared->kind = PyUnicode_KIND(obj); - shared->buffer = PyUnicode_DATA(obj); - shared->len = PyUnicode_GET_LENGTH(obj); - return 0; -} - -// int - -static PyObject * -_new_long_object(_PyCrossInterpreterData *data) -{ - return PyLong_FromSsize_t((Py_ssize_t)(data->data)); -} - -static int -_long_shared(PyThreadState *tstate, PyObject *obj, - _PyCrossInterpreterData *data) -{ - /* Note that this means the size of shareable ints is bounded by - * sys.maxsize. Hence on 32-bit architectures that is half the - * size of maximum shareable ints on 64-bit. - */ - Py_ssize_t value = PyLong_AsSsize_t(obj); - if (value == -1 && PyErr_Occurred()) { - if (PyErr_ExceptionMatches(PyExc_OverflowError)) { - PyErr_SetString(PyExc_OverflowError, "try sending as bytes"); - } - return -1; - } - _PyCrossInterpreterData_Init(data, tstate->interp, (void *)value, NULL, - _new_long_object); - // data->obj and data->free remain NULL - return 0; -} - -// float - -static PyObject * -_new_float_object(_PyCrossInterpreterData *data) -{ - double * value_ptr = data->data; - return PyFloat_FromDouble(*value_ptr); -} - -static int -_float_shared(PyThreadState *tstate, PyObject *obj, - _PyCrossInterpreterData *data) -{ - if (_PyCrossInterpreterData_InitWithSize( - data, tstate->interp, sizeof(double), NULL, - _new_float_object - ) < 0) - { - return -1; - } - double *shared = (double *)data->data; - *shared = PyFloat_AsDouble(obj); - return 0; -} - -// None - -static PyObject * -_new_none_object(_PyCrossInterpreterData *data) -{ - // XXX Singleton refcounts are problematic across interpreters... - return Py_NewRef(Py_None); -} - -static int -_none_shared(PyThreadState *tstate, PyObject *obj, - _PyCrossInterpreterData *data) -{ - _PyCrossInterpreterData_Init(data, tstate->interp, NULL, NULL, - _new_none_object); - // data->data, data->obj and data->free remain NULL - return 0; -} - -// bool - -static PyObject * -_new_bool_object(_PyCrossInterpreterData *data) -{ - if (data->data){ - Py_RETURN_TRUE; - } - Py_RETURN_FALSE; -} - -static int -_bool_shared(PyThreadState *tstate, PyObject *obj, - _PyCrossInterpreterData *data) -{ - _PyCrossInterpreterData_Init(data, tstate->interp, - (void *) (Py_IsTrue(obj) ? (uintptr_t) 1 : (uintptr_t) 0), NULL, - _new_bool_object); - // data->obj and data->free remain NULL - return 0; -} - -// tuple - -struct _shared_tuple_data { - Py_ssize_t len; - _PyCrossInterpreterData **data; -}; - -static PyObject * -_new_tuple_object(_PyCrossInterpreterData *data) -{ - struct _shared_tuple_data *shared = (struct _shared_tuple_data *)(data->data); - PyObject *tuple = PyTuple_New(shared->len); - if (tuple == NULL) { - return NULL; - } - - for (Py_ssize_t i = 0; i < shared->len; i++) { - PyObject *item = _PyCrossInterpreterData_NewObject(shared->data[i]); - if (item == NULL){ - Py_DECREF(tuple); - return NULL; - } - PyTuple_SET_ITEM(tuple, i, item); - } - return tuple; -} - -static void -_tuple_shared_free(void* data) -{ - struct _shared_tuple_data *shared = (struct _shared_tuple_data *)(data); -#ifndef NDEBUG - int64_t interpid = PyInterpreterState_GetID(_PyInterpreterState_GET()); -#endif - for (Py_ssize_t i = 0; i < shared->len; i++) { - if (shared->data[i] != NULL) { - assert(shared->data[i]->interpid == interpid); - _PyCrossInterpreterData_Release(shared->data[i]); - PyMem_RawFree(shared->data[i]); - shared->data[i] = NULL; - } - } - PyMem_Free(shared->data); - PyMem_RawFree(shared); -} - -static int -_tuple_shared(PyThreadState *tstate, PyObject *obj, - _PyCrossInterpreterData *data) -{ - Py_ssize_t len = PyTuple_GET_SIZE(obj); - if (len < 0) { - return -1; - } - struct _shared_tuple_data *shared = PyMem_RawMalloc(sizeof(struct _shared_tuple_data)); - if (shared == NULL){ - PyErr_NoMemory(); - return -1; - } - - shared->len = len; - shared->data = (_PyCrossInterpreterData **) PyMem_Calloc(shared->len, sizeof(_PyCrossInterpreterData *)); - if (shared->data == NULL) { - PyErr_NoMemory(); - return -1; - } - - for (Py_ssize_t i = 0; i < shared->len; i++) { - _PyCrossInterpreterData *data = _PyCrossInterpreterData_New(); - if (data == NULL) { - goto error; // PyErr_NoMemory already set - } - PyObject *item = PyTuple_GET_ITEM(obj, i); - - int res = -1; - if (!_Py_EnterRecursiveCallTstate(tstate, " while sharing a tuple")) { - res = _PyObject_GetCrossInterpreterData(item, data); - _Py_LeaveRecursiveCallTstate(tstate); - } - if (res < 0) { - PyMem_RawFree(data); - goto error; - } - shared->data[i] = data; - } - _PyCrossInterpreterData_Init( - data, tstate->interp, shared, obj, _new_tuple_object); - data->free = _tuple_shared_free; - return 0; - -error: - _tuple_shared_free(shared); - return -1; -} - -// registration - -static void -_register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry) -{ - // None - if (_xidregistry_add_type(xidregistry, (PyTypeObject *)PyObject_Type(Py_None), _none_shared) != 0) { - Py_FatalError("could not register None for cross-interpreter sharing"); - } - - // int - if (_xidregistry_add_type(xidregistry, &PyLong_Type, _long_shared) != 0) { - Py_FatalError("could not register int for cross-interpreter sharing"); - } - - // bytes - if (_xidregistry_add_type(xidregistry, &PyBytes_Type, _bytes_shared) != 0) { - Py_FatalError("could not register bytes for cross-interpreter sharing"); - } - - // str - if (_xidregistry_add_type(xidregistry, &PyUnicode_Type, _str_shared) != 0) { - Py_FatalError("could not register str for cross-interpreter sharing"); - } - - // bool - if (_xidregistry_add_type(xidregistry, &PyBool_Type, _bool_shared) != 0) { - Py_FatalError("could not register bool for cross-interpreter sharing"); - } - - // float - if (_xidregistry_add_type(xidregistry, &PyFloat_Type, _float_shared) != 0) { - Py_FatalError("could not register float for cross-interpreter sharing"); - } - - // tuple - if (_xidregistry_add_type(xidregistry, &PyTuple_Type, _tuple_shared) != 0) { - Py_FatalError("could not register tuple for cross-interpreter sharing"); - } -} - -/* registry lifecycle */ - -static void -_xidregistry_init(struct _xidregistry *registry) -{ - if (registry->initialized) { - return; - } - registry->initialized = 1; - - if (registry->global) { - // Registering the builtins is cheap so we don't bother doing it lazily. - assert(registry->head == NULL); - _register_builtins_for_crossinterpreter_data(registry); - } -} - -static void -_xidregistry_fini(struct _xidregistry *registry) -{ - if (!registry->initialized) { - return; - } - registry->initialized = 0; - - _xidregistry_clear(registry); -} - - /*************************/ /* convenience utilities */ /*************************/ @@ -1023,6 +417,10 @@ _convert_exc_to_TracebackException(PyObject *exc, PyObject **p_tbexc) return -1; } +// We accommodate backports here. +#ifndef _Py_EMPTY_STR +# define _Py_EMPTY_STR &_Py_STR(empty) +#endif static const char * _format_TracebackException(PyObject *tbexc) @@ -1031,7 +429,8 @@ _format_TracebackException(PyObject *tbexc) if (lines == NULL) { return NULL; } - PyObject *formatted_obj = PyUnicode_Join(&_Py_STR(empty), lines); + assert(_Py_EMPTY_STR != NULL); + PyObject *formatted_obj = PyUnicode_Join(_Py_EMPTY_STR, lines); Py_DECREF(lines); if (formatted_obj == NULL) { return NULL; @@ -1600,7 +999,7 @@ _sharednsitem_has_value(_PyXI_namespace_item *item, int64_t *p_interpid) return 0; } if (p_interpid != NULL) { - *p_interpid = item->data->interpid; + *p_interpid = _PyCrossInterpreterData_INTERPID(item->data); } return 1; } @@ -1874,7 +1273,7 @@ _PyXI_FreeNamespace(_PyXI_namespace *ns) return; } - if (interpid == PyInterpreterState_GetID(_PyInterpreterState_GET())) { + if (interpid == PyInterpreterState_GetID(PyInterpreterState_Get())) { _sharedns_free(ns); } else { @@ -2014,7 +1413,7 @@ _enter_session(_PyXI_session *session, PyInterpreterState *interp) PyThreadState *prev = tstate; if (interp != tstate->interp) { tstate = PyThreadState_New(interp); - tstate->_whence = _PyThreadState_WHENCE_EXEC; + _PyThreadState_SetWhence(tstate, _PyThreadState_WHENCE_EXEC); // XXX Possible GILState issues? session->prev_tstate = PyThreadState_Swap(tstate); assert(session->prev_tstate == prev); @@ -2073,7 +1472,7 @@ _propagate_not_shareable_error(_PyXI_session *session) if (session == NULL) { return; } - PyInterpreterState *interp = _PyInterpreterState_GET(); + PyInterpreterState *interp = PyInterpreterState_Get(); if (PyErr_ExceptionMatches(_get_not_shareable_error_type(interp))) { // We want to propagate the exception directly. session->_error_override = _PyXI_ERR_NOT_SHAREABLE; @@ -2245,18 +1644,12 @@ _PyXI_Exit(_PyXI_session *session) PyStatus _PyXI_Init(PyInterpreterState *interp) { - PyStatus status; - - // Initialize the XID registry. - if (_Py_IsMainInterpreter(interp)) { - _xidregistry_init(_get_global_xidregistry(interp->runtime)); - } - _xidregistry_init(_get_xidregistry(interp)); + // Initialize the XID lookup state (e.g. registry). + xid_lookup_init(interp); // Initialize exceptions (heap types). - status = _init_not_shareable_error_type(interp); - if (_PyStatus_EXCEPTION(status)) { - return status; + if (_init_not_shareable_error_type(interp) < 0) { + return _PyStatus_ERR("failed to initialize NotShareableError"); } return _PyStatus_OK(); @@ -2271,11 +1664,8 @@ _PyXI_Fini(PyInterpreterState *interp) // Finalize exceptions (heap types). _fini_not_shareable_error_type(interp); - // Finalize the XID registry. - _xidregistry_fini(_get_xidregistry(interp)); - if (_Py_IsMainInterpreter(interp)) { - _xidregistry_fini(_get_global_xidregistry(interp->runtime)); - } + // Finalize the XID lookup state (e.g. registry). + xid_lookup_fini(interp); } PyStatus diff --git a/Python/crossinterp_data_lookup.h b/Python/crossinterp_data_lookup.h new file mode 100644 index 000000000000000..863919ad42fb97a --- /dev/null +++ b/Python/crossinterp_data_lookup.h @@ -0,0 +1,594 @@ + +static crossinterpdatafunc _lookup_getdata_from_registry( + PyInterpreterState *, PyObject *); + +static crossinterpdatafunc +lookup_getdata(PyInterpreterState *interp, PyObject *obj) +{ + /* Cross-interpreter objects are looked up by exact match on the class. + We can reassess this policy when we move from a global registry to a + tp_* slot. */ + return _lookup_getdata_from_registry(interp, obj); +} + +crossinterpdatafunc +_PyCrossInterpreterData_Lookup(PyObject *obj) +{ + PyInterpreterState *interp = PyInterpreterState_Get(); + return lookup_getdata(interp, obj); +} + + +/***********************************************/ +/* a registry of {type -> crossinterpdatafunc} */ +/***********************************************/ + +/* For now we use a global registry of shareable classes. An + alternative would be to add a tp_* slot for a class's + crossinterpdatafunc. It would be simpler and more efficient. */ + + +/* registry lifecycle */ + +static void _register_builtins_for_crossinterpreter_data(struct _xidregistry *); + +static void +_xidregistry_init(struct _xidregistry *registry) +{ + if (registry->initialized) { + return; + } + registry->initialized = 1; + + if (registry->global) { + // Registering the builtins is cheap so we don't bother doing it lazily. + assert(registry->head == NULL); + _register_builtins_for_crossinterpreter_data(registry); + } +} + +static void _xidregistry_clear(struct _xidregistry *); + +static void +_xidregistry_fini(struct _xidregistry *registry) +{ + if (!registry->initialized) { + return; + } + registry->initialized = 0; + + _xidregistry_clear(registry); +} + +static inline struct _xidregistry * _get_global_xidregistry(_PyRuntimeState *); +static inline struct _xidregistry * _get_xidregistry(PyInterpreterState *); + +static void +xid_lookup_init(PyInterpreterState *interp) +{ + if (_Py_IsMainInterpreter(interp)) { + _xidregistry_init(_get_global_xidregistry(interp->runtime)); + } + _xidregistry_init(_get_xidregistry(interp)); +} + +static void +xid_lookup_fini(PyInterpreterState *interp) +{ + _xidregistry_fini(_get_xidregistry(interp)); + if (_Py_IsMainInterpreter(interp)) { + _xidregistry_fini(_get_global_xidregistry(interp->runtime)); + } +} + + +/* registry thread safety */ + +static void +_xidregistry_lock(struct _xidregistry *registry) +{ + if (registry->global) { + PyMutex_Lock(®istry->mutex); + } + // else: Within an interpreter we rely on the GIL instead of a separate lock. +} + +static void +_xidregistry_unlock(struct _xidregistry *registry) +{ + if (registry->global) { + PyMutex_Unlock(®istry->mutex); + } +} + + +/* accessing the registry */ + +static inline struct _xidregistry * +_get_global_xidregistry(_PyRuntimeState *runtime) +{ + return &runtime->xi.registry; +} + +static inline struct _xidregistry * +_get_xidregistry(PyInterpreterState *interp) +{ + return &interp->xi.registry; +} + +static inline struct _xidregistry * +_get_xidregistry_for_type(PyInterpreterState *interp, PyTypeObject *cls) +{ + struct _xidregistry *registry = _get_global_xidregistry(interp->runtime); + if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) { + registry = _get_xidregistry(interp); + } + return registry; +} + +static struct _xidregitem * _xidregistry_remove_entry( + struct _xidregistry *, struct _xidregitem *); + +static struct _xidregitem * +_xidregistry_find_type(struct _xidregistry *xidregistry, PyTypeObject *cls) +{ + struct _xidregitem *cur = xidregistry->head; + while (cur != NULL) { + if (cur->weakref != NULL) { + // cur is/was a heap type. + PyObject *registered = _PyWeakref_GET_REF(cur->weakref); + if (registered == NULL) { + // The weakly ref'ed object was freed. + cur = _xidregistry_remove_entry(xidregistry, cur); + continue; + } + assert(PyType_Check(registered)); + assert(cur->cls == (PyTypeObject *)registered); + assert(cur->cls->tp_flags & Py_TPFLAGS_HEAPTYPE); + Py_DECREF(registered); + } + if (cur->cls == cls) { + return cur; + } + cur = cur->next; + } + return NULL; +} + +static crossinterpdatafunc +_lookup_getdata_from_registry(PyInterpreterState *interp, PyObject *obj) +{ + PyTypeObject *cls = Py_TYPE(obj); + + struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls); + _xidregistry_lock(xidregistry); + + struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls); + crossinterpdatafunc func = matched != NULL ? matched->getdata : NULL; + + _xidregistry_unlock(xidregistry); + return func; +} + + +/* updating the registry */ + +static int +_xidregistry_add_type(struct _xidregistry *xidregistry, + PyTypeObject *cls, crossinterpdatafunc getdata) +{ + struct _xidregitem *newhead = PyMem_RawMalloc(sizeof(struct _xidregitem)); + if (newhead == NULL) { + return -1; + } + *newhead = (struct _xidregitem){ + // We do not keep a reference, to avoid keeping the class alive. + .cls = cls, + .refcount = 1, + .getdata = getdata, + }; + if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) { + // XXX Assign a callback to clear the entry from the registry? + newhead->weakref = PyWeakref_NewRef((PyObject *)cls, NULL); + if (newhead->weakref == NULL) { + PyMem_RawFree(newhead); + return -1; + } + } + newhead->next = xidregistry->head; + if (newhead->next != NULL) { + newhead->next->prev = newhead; + } + xidregistry->head = newhead; + return 0; +} + +static struct _xidregitem * +_xidregistry_remove_entry(struct _xidregistry *xidregistry, + struct _xidregitem *entry) +{ + struct _xidregitem *next = entry->next; + if (entry->prev != NULL) { + assert(entry->prev->next == entry); + entry->prev->next = next; + } + else { + assert(xidregistry->head == entry); + xidregistry->head = next; + } + if (next != NULL) { + next->prev = entry->prev; + } + Py_XDECREF(entry->weakref); + PyMem_RawFree(entry); + return next; +} + +static void +_xidregistry_clear(struct _xidregistry *xidregistry) +{ + struct _xidregitem *cur = xidregistry->head; + xidregistry->head = NULL; + while (cur != NULL) { + struct _xidregitem *next = cur->next; + Py_XDECREF(cur->weakref); + PyMem_RawFree(cur); + cur = next; + } +} + +int +_PyCrossInterpreterData_RegisterClass(PyTypeObject *cls, + crossinterpdatafunc getdata) +{ + if (!PyType_Check(cls)) { + PyErr_Format(PyExc_ValueError, "only classes may be registered"); + return -1; + } + if (getdata == NULL) { + PyErr_Format(PyExc_ValueError, "missing 'getdata' func"); + return -1; + } + + int res = 0; + PyInterpreterState *interp = _PyInterpreterState_GET(); + struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls); + _xidregistry_lock(xidregistry); + + struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls); + if (matched != NULL) { + assert(matched->getdata == getdata); + matched->refcount += 1; + goto finally; + } + + res = _xidregistry_add_type(xidregistry, cls, getdata); + +finally: + _xidregistry_unlock(xidregistry); + return res; +} + +int +_PyCrossInterpreterData_UnregisterClass(PyTypeObject *cls) +{ + int res = 0; + PyInterpreterState *interp = _PyInterpreterState_GET(); + struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls); + _xidregistry_lock(xidregistry); + + struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls); + if (matched != NULL) { + assert(matched->refcount > 0); + matched->refcount -= 1; + if (matched->refcount == 0) { + (void)_xidregistry_remove_entry(xidregistry, matched); + } + res = 1; + } + + _xidregistry_unlock(xidregistry); + return res; +} + + +/********************************************/ +/* cross-interpreter data for builtin types */ +/********************************************/ + +// bytes + +struct _shared_bytes_data { + char *bytes; + Py_ssize_t len; +}; + +static PyObject * +_new_bytes_object(_PyCrossInterpreterData *data) +{ + struct _shared_bytes_data *shared = (struct _shared_bytes_data *)(data->data); + return PyBytes_FromStringAndSize(shared->bytes, shared->len); +} + +static int +_bytes_shared(PyThreadState *tstate, PyObject *obj, + _PyCrossInterpreterData *data) +{ + if (_PyCrossInterpreterData_InitWithSize( + data, tstate->interp, sizeof(struct _shared_bytes_data), obj, + _new_bytes_object + ) < 0) + { + return -1; + } + struct _shared_bytes_data *shared = (struct _shared_bytes_data *)data->data; + if (PyBytes_AsStringAndSize(obj, &shared->bytes, &shared->len) < 0) { + _PyCrossInterpreterData_Clear(tstate->interp, data); + return -1; + } + return 0; +} + +// str + +struct _shared_str_data { + int kind; + const void *buffer; + Py_ssize_t len; +}; + +static PyObject * +_new_str_object(_PyCrossInterpreterData *data) +{ + struct _shared_str_data *shared = (struct _shared_str_data *)(data->data); + return PyUnicode_FromKindAndData(shared->kind, shared->buffer, shared->len); +} + +static int +_str_shared(PyThreadState *tstate, PyObject *obj, + _PyCrossInterpreterData *data) +{ + if (_PyCrossInterpreterData_InitWithSize( + data, tstate->interp, sizeof(struct _shared_str_data), obj, + _new_str_object + ) < 0) + { + return -1; + } + struct _shared_str_data *shared = (struct _shared_str_data *)data->data; + shared->kind = PyUnicode_KIND(obj); + shared->buffer = PyUnicode_DATA(obj); + shared->len = PyUnicode_GET_LENGTH(obj); + return 0; +} + +// int + +static PyObject * +_new_long_object(_PyCrossInterpreterData *data) +{ + return PyLong_FromSsize_t((Py_ssize_t)(data->data)); +} + +static int +_long_shared(PyThreadState *tstate, PyObject *obj, + _PyCrossInterpreterData *data) +{ + /* Note that this means the size of shareable ints is bounded by + * sys.maxsize. Hence on 32-bit architectures that is half the + * size of maximum shareable ints on 64-bit. + */ + Py_ssize_t value = PyLong_AsSsize_t(obj); + if (value == -1 && PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + PyErr_SetString(PyExc_OverflowError, "try sending as bytes"); + } + return -1; + } + _PyCrossInterpreterData_Init(data, tstate->interp, (void *)value, NULL, + _new_long_object); + // data->obj and data->free remain NULL + return 0; +} + +// float + +static PyObject * +_new_float_object(_PyCrossInterpreterData *data) +{ + double * value_ptr = data->data; + return PyFloat_FromDouble(*value_ptr); +} + +static int +_float_shared(PyThreadState *tstate, PyObject *obj, + _PyCrossInterpreterData *data) +{ + if (_PyCrossInterpreterData_InitWithSize( + data, tstate->interp, sizeof(double), NULL, + _new_float_object + ) < 0) + { + return -1; + } + double *shared = (double *)data->data; + *shared = PyFloat_AsDouble(obj); + return 0; +} + +// None + +static PyObject * +_new_none_object(_PyCrossInterpreterData *data) +{ + // XXX Singleton refcounts are problematic across interpreters... + return Py_NewRef(Py_None); +} + +static int +_none_shared(PyThreadState *tstate, PyObject *obj, + _PyCrossInterpreterData *data) +{ + _PyCrossInterpreterData_Init(data, tstate->interp, NULL, NULL, + _new_none_object); + // data->data, data->obj and data->free remain NULL + return 0; +} + +// bool + +static PyObject * +_new_bool_object(_PyCrossInterpreterData *data) +{ + if (data->data){ + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +static int +_bool_shared(PyThreadState *tstate, PyObject *obj, + _PyCrossInterpreterData *data) +{ + _PyCrossInterpreterData_Init(data, tstate->interp, + (void *) (Py_IsTrue(obj) ? (uintptr_t) 1 : (uintptr_t) 0), NULL, + _new_bool_object); + // data->obj and data->free remain NULL + return 0; +} + +// tuple + +struct _shared_tuple_data { + Py_ssize_t len; + _PyCrossInterpreterData **data; +}; + +static PyObject * +_new_tuple_object(_PyCrossInterpreterData *data) +{ + struct _shared_tuple_data *shared = (struct _shared_tuple_data *)(data->data); + PyObject *tuple = PyTuple_New(shared->len); + if (tuple == NULL) { + return NULL; + } + + for (Py_ssize_t i = 0; i < shared->len; i++) { + PyObject *item = _PyCrossInterpreterData_NewObject(shared->data[i]); + if (item == NULL){ + Py_DECREF(tuple); + return NULL; + } + PyTuple_SET_ITEM(tuple, i, item); + } + return tuple; +} + +static void +_tuple_shared_free(void* data) +{ + struct _shared_tuple_data *shared = (struct _shared_tuple_data *)(data); +#ifndef NDEBUG + int64_t interpid = PyInterpreterState_GetID(_PyInterpreterState_GET()); +#endif + for (Py_ssize_t i = 0; i < shared->len; i++) { + if (shared->data[i] != NULL) { + assert(_PyCrossInterpreterData_INTERPID(shared->data[i]) == interpid); + _PyCrossInterpreterData_Release(shared->data[i]); + PyMem_RawFree(shared->data[i]); + shared->data[i] = NULL; + } + } + PyMem_Free(shared->data); + PyMem_RawFree(shared); +} + +static int +_tuple_shared(PyThreadState *tstate, PyObject *obj, + _PyCrossInterpreterData *data) +{ + Py_ssize_t len = PyTuple_GET_SIZE(obj); + if (len < 0) { + return -1; + } + struct _shared_tuple_data *shared = PyMem_RawMalloc(sizeof(struct _shared_tuple_data)); + if (shared == NULL){ + PyErr_NoMemory(); + return -1; + } + + shared->len = len; + shared->data = (_PyCrossInterpreterData **) PyMem_Calloc(shared->len, sizeof(_PyCrossInterpreterData *)); + if (shared->data == NULL) { + PyErr_NoMemory(); + return -1; + } + + for (Py_ssize_t i = 0; i < shared->len; i++) { + _PyCrossInterpreterData *data = _PyCrossInterpreterData_New(); + if (data == NULL) { + goto error; // PyErr_NoMemory already set + } + PyObject *item = PyTuple_GET_ITEM(obj, i); + + int res = -1; + if (!_Py_EnterRecursiveCallTstate(tstate, " while sharing a tuple")) { + res = _PyObject_GetCrossInterpreterData(item, data); + _Py_LeaveRecursiveCallTstate(tstate); + } + if (res < 0) { + PyMem_RawFree(data); + goto error; + } + shared->data[i] = data; + } + _PyCrossInterpreterData_Init( + data, tstate->interp, shared, obj, _new_tuple_object); + data->free = _tuple_shared_free; + return 0; + +error: + _tuple_shared_free(shared); + return -1; +} + +// registration + +static void +_register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry) +{ + // None + if (_xidregistry_add_type(xidregistry, (PyTypeObject *)PyObject_Type(Py_None), _none_shared) != 0) { + Py_FatalError("could not register None for cross-interpreter sharing"); + } + + // int + if (_xidregistry_add_type(xidregistry, &PyLong_Type, _long_shared) != 0) { + Py_FatalError("could not register int for cross-interpreter sharing"); + } + + // bytes + if (_xidregistry_add_type(xidregistry, &PyBytes_Type, _bytes_shared) != 0) { + Py_FatalError("could not register bytes for cross-interpreter sharing"); + } + + // str + if (_xidregistry_add_type(xidregistry, &PyUnicode_Type, _str_shared) != 0) { + Py_FatalError("could not register str for cross-interpreter sharing"); + } + + // bool + if (_xidregistry_add_type(xidregistry, &PyBool_Type, _bool_shared) != 0) { + Py_FatalError("could not register bool for cross-interpreter sharing"); + } + + // float + if (_xidregistry_add_type(xidregistry, &PyFloat_Type, _float_shared) != 0) { + Py_FatalError("could not register float for cross-interpreter sharing"); + } + + // tuple + if (_xidregistry_add_type(xidregistry, &PyTuple_Type, _tuple_shared) != 0) { + Py_FatalError("could not register tuple for cross-interpreter sharing"); + } +} diff --git a/Python/crossinterp_exceptions.h b/Python/crossinterp_exceptions.h new file mode 100644 index 000000000000000..e418cf91d4a7af6 --- /dev/null +++ b/Python/crossinterp_exceptions.h @@ -0,0 +1,80 @@ + +/* InterpreterError extends Exception */ + +static PyTypeObject _PyExc_InterpreterError = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "interpreters.InterpreterError", + .tp_doc = PyDoc_STR("A cross-interpreter operation failed"), + //.tp_base = (PyTypeObject *)PyExc_BaseException, +}; +PyObject *PyExc_InterpreterError = (PyObject *)&_PyExc_InterpreterError; + +/* InterpreterNotFoundError extends InterpreterError */ + +static PyTypeObject _PyExc_InterpreterNotFoundError = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "interpreters.InterpreterNotFoundError", + .tp_doc = PyDoc_STR("An interpreter was not found"), + .tp_base = &_PyExc_InterpreterError, +}; +PyObject *PyExc_InterpreterNotFoundError = (PyObject *)&_PyExc_InterpreterNotFoundError; + +/* NotShareableError extends ValueError */ + +static int +_init_not_shareable_error_type(PyInterpreterState *interp) +{ + const char *name = "interpreters.NotShareableError"; + PyObject *base = PyExc_ValueError; + PyObject *ns = NULL; + PyObject *exctype = PyErr_NewException(name, base, ns); + if (exctype == NULL) { + return -1; + } + + _PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError = exctype; + return 0; +} + +static void +_fini_not_shareable_error_type(PyInterpreterState *interp) +{ + Py_CLEAR(_PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError); +} + +static PyObject * +_get_not_shareable_error_type(PyInterpreterState *interp) +{ + assert(_PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError != NULL); + return _PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError; +} + + +/* lifecycle */ + +static int +init_exceptions(PyInterpreterState *interp) +{ + // builtin static types + _PyExc_InterpreterError.tp_base = (PyTypeObject *)PyExc_BaseException; + if (_PyStaticType_InitBuiltin(interp, &_PyExc_InterpreterError) < 0) { + return -1; + } + if (_PyStaticType_InitBuiltin(interp, &_PyExc_InterpreterNotFoundError) < 0) { + return -1; + } + + // heap types + // We would call _init_not_shareable_error_type() here too, + // but that leads to ref leaks + + return 0; +} + +static void +fini_exceptions(PyInterpreterState *interp) +{ + // Likewise with _fini_not_shareable_error_type(). + _PyStaticType_Dealloc(interp, &_PyExc_InterpreterNotFoundError); + _PyStaticType_Dealloc(interp, &_PyExc_InterpreterError); +} diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 1d9576d083d8dc7..5c5016f71371640 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -292,10 +292,10 @@ Objects/exceptions.c - PyExc_UnicodeWarning - Objects/exceptions.c - PyExc_BytesWarning - Objects/exceptions.c - PyExc_ResourceWarning - Objects/exceptions.c - PyExc_EncodingWarning - -Python/crossinterp.c - _PyExc_InterpreterError - -Python/crossinterp.c - _PyExc_InterpreterNotFoundError - -Python/crossinterp.c - PyExc_InterpreterError - -Python/crossinterp.c - PyExc_InterpreterNotFoundError - +Python/crossinterp_exceptions.h - _PyExc_InterpreterError - +Python/crossinterp_exceptions.h - _PyExc_InterpreterNotFoundError - +Python/crossinterp_exceptions.h - PyExc_InterpreterError - +Python/crossinterp_exceptions.h - PyExc_InterpreterNotFoundError - ##----------------------- ## singletons From 518af37eb569f52a3daf2cf9f4787deed10754ca Mon Sep 17 00:00:00 2001 From: "T. Wouters" <thomas@python.org> Date: Wed, 14 Feb 2024 00:58:49 +0100 Subject: [PATCH 255/507] gh-115421: Update the list of installed test subdirectories. (#115422) Update the list of installed test subdirectories with all newly added subdirectories of Lib/test, so that the tests in those directories are properly installed. --- Makefile.pre.in | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index 4b9d9c171b9efb3..96886adf309d81f 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2234,13 +2234,13 @@ LIBSUBDIRS= asyncio \ __phello__ TESTSUBDIRS= idlelib/idle_test \ test \ - test/audiodata \ test/archivetestdata \ + test/audiodata \ test/certdata \ test/certdata/capath \ test/cjkencodings \ - test/crashers \ test/configdata \ + test/crashers \ test/data \ test/decimaltestdata \ test/dtracedata \ @@ -2254,8 +2254,10 @@ TESTSUBDIRS= idlelib/idle_test \ test/subprocessdata \ test/support \ test/support/_hypothesis_stubs \ + test/support/interpreters \ test/test_asyncio \ test/test_capi \ + test/test_concurrent_futures \ test/test_cppext \ test/test_ctypes \ test/test_dataclasses \ @@ -2264,7 +2266,6 @@ TESTSUBDIRS= idlelib/idle_test \ test/test_email/data \ test/test_future_stmt \ test/test_gdb \ - test/test_inspect \ test/test_import \ test/test_import/data \ test/test_import/data/circular_imports \ @@ -2317,8 +2318,13 @@ TESTSUBDIRS= idlelib/idle_test \ test/test_importlib/resources/zipdata01 \ test/test_importlib/resources/zipdata02 \ test/test_importlib/source \ + test/test_inspect \ + test/test_interpreters \ test/test_json \ test/test_module \ + test/test_multiprocessing_fork \ + test/test_multiprocessing_forkserver \ + test/test_multiprocessing_spawn \ test/test_pathlib \ test/test_peg_generator \ test/test_pydoc \ From f15795c9a0f206b9abfb48007b267d12cd14f4a8 Mon Sep 17 00:00:00 2001 From: Donghee Na <donghee.na@python.org> Date: Wed, 14 Feb 2024 09:32:51 +0900 Subject: [PATCH 256/507] gh-111968: Rename freelist related struct names to Eric's suggestion (gh-115329) --- Include/internal/pycore_dict.h | 6 --- Include/internal/pycore_freelist.h | 52 +++++++++++++------------- Include/internal/pycore_interp.h | 4 -- Include/internal/pycore_object_state.h | 4 ++ Include/internal/pycore_pystate.h | 6 +-- Include/internal/pycore_tstate.h | 2 +- Objects/dictobject.c | 51 ++++++++++--------------- Objects/floatobject.c | 38 +++++++++---------- Objects/genobject.c | 42 ++++++++++----------- Objects/listobject.c | 34 ++++++++--------- Objects/object.c | 18 ++++----- Objects/sliceobject.c | 20 +++++----- Objects/tupleobject.c | 42 ++++++++++----------- Python/context.c | 32 ++++++++-------- Python/gc_free_threading.c | 2 +- Python/gc_gil.c | 2 +- Python/object_stack.c | 34 ++++++++--------- Python/pylifecycle.c | 4 +- Python/pystate.c | 4 +- 19 files changed, 190 insertions(+), 207 deletions(-) diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index 0ebe701bc16f812..e5ef9a8607a83b5 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -67,12 +67,6 @@ typedef struct { extern PyObject* _PyDictView_New(PyObject *, PyTypeObject *); extern PyObject* _PyDictView_Intersect(PyObject* self, PyObject *other); - -/* runtime lifecycle */ - -extern void _PyDict_Fini(PyInterpreterState *state); - - /* other API */ typedef struct { diff --git a/Include/internal/pycore_freelist.h b/Include/internal/pycore_freelist.h index 1bc551914794f0c..b365ca337eabc87 100644 --- a/Include/internal/pycore_freelist.h +++ b/Include/internal/pycore_freelist.h @@ -33,14 +33,14 @@ extern "C" { # define _PyObjectStackChunk_MAXFREELIST 0 #endif -struct _Py_list_state { +struct _Py_list_freelist { #ifdef WITH_FREELISTS PyListObject *free_list[PyList_MAXFREELIST]; int numfree; #endif }; -struct _Py_tuple_state { +struct _Py_tuple_freelist { #if WITH_FREELISTS /* There is one freelist for each size from 1 to PyTuple_MAXSAVESIZE. The empty tuple is handled separately. @@ -57,7 +57,7 @@ struct _Py_tuple_state { #endif }; -struct _Py_float_state { +struct _Py_float_freelist { #ifdef WITH_FREELISTS /* Special free list free_list is a singly-linked list of available PyFloatObjects, @@ -77,7 +77,7 @@ struct _Py_dict_freelist { #endif }; -struct _Py_slice_state { +struct _Py_slice_freelist { #ifdef WITH_FREELISTS /* Using a cache is very effective since typically only a single slice is created and then deleted again. */ @@ -85,7 +85,7 @@ struct _Py_slice_state { #endif }; -struct _Py_context_state { +struct _Py_context_freelist { #ifdef WITH_FREELISTS // List of free PyContext objects PyContext *freelist; @@ -93,7 +93,7 @@ struct _Py_context_state { #endif }; -struct _Py_async_gen_state { +struct _Py_async_gen_freelist { #ifdef WITH_FREELISTS /* Freelists boost performance 6-10%; they also reduce memory fragmentation, as _PyAsyncGenWrappedValue and PyAsyncGenASend @@ -109,31 +109,31 @@ struct _Py_async_gen_state { struct _PyObjectStackChunk; -struct _Py_object_stack_state { +struct _Py_object_stack_freelist { struct _PyObjectStackChunk *free_list; Py_ssize_t numfree; }; -typedef struct _Py_freelist_state { - struct _Py_float_state floats; - struct _Py_tuple_state tuples; - struct _Py_list_state lists; +struct _Py_object_freelists { + struct _Py_float_freelist floats; + struct _Py_tuple_freelist tuples; + struct _Py_list_freelist lists; struct _Py_dict_freelist dicts; - struct _Py_slice_state slices; - struct _Py_context_state contexts; - struct _Py_async_gen_state async_gens; - struct _Py_object_stack_state object_stacks; -} _PyFreeListState; - -extern void _PyObject_ClearFreeLists(_PyFreeListState *state, int is_finalization); -extern void _PyTuple_ClearFreeList(_PyFreeListState *state, int is_finalization); -extern void _PyFloat_ClearFreeList(_PyFreeListState *state, int is_finalization); -extern void _PyList_ClearFreeList(_PyFreeListState *state, int is_finalization); -extern void _PySlice_ClearFreeList(_PyFreeListState *state, int is_finalization); -extern void _PyDict_ClearFreeList(_PyFreeListState *state, int is_finalization); -extern void _PyAsyncGen_ClearFreeLists(_PyFreeListState *state, int is_finalization); -extern void _PyContext_ClearFreeList(_PyFreeListState *state, int is_finalization); -extern void _PyObjectStackChunk_ClearFreeList(_PyFreeListState *state, int is_finalization); + struct _Py_slice_freelist slices; + struct _Py_context_freelist contexts; + struct _Py_async_gen_freelist async_gens; + struct _Py_object_stack_freelist object_stacks; +}; + +extern void _PyObject_ClearFreeLists(struct _Py_object_freelists *freelists, int is_finalization); +extern void _PyTuple_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalization); +extern void _PyFloat_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalization); +extern void _PyList_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalization); +extern void _PySlice_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalization); +extern void _PyDict_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalization); +extern void _PyAsyncGen_ClearFreeLists(struct _Py_object_freelists *freelists, int is_finalization); +extern void _PyContext_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalization); +extern void _PyObjectStackChunk_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalization); #ifdef __cplusplus } diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index c244d8966f238bf..c07447183d62090 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -20,7 +20,6 @@ extern "C" { #include "pycore_dtoa.h" // struct _dtoa_state #include "pycore_exceptions.h" // struct _Py_exc_state #include "pycore_floatobject.h" // struct _Py_float_state -#include "pycore_freelist.h" // struct _Py_freelist_state #include "pycore_function.h" // FUNC_MAX_WATCHERS #include "pycore_gc.h" // struct _gc_runtime_state #include "pycore_genobject.h" // struct _Py_async_gen_state @@ -222,9 +221,6 @@ struct _is { // One bit is set for each non-NULL entry in code_watchers uint8_t active_code_watchers; -#if !defined(Py_GIL_DISABLED) - struct _Py_freelist_state freelist_state; -#endif struct _py_object_state object_state; struct _Py_unicode_state unicode; struct _Py_long_state long_state; diff --git a/Include/internal/pycore_object_state.h b/Include/internal/pycore_object_state.h index 9eac27b1a9a4e3e..cd7c9335b3e611c 100644 --- a/Include/internal/pycore_object_state.h +++ b/Include/internal/pycore_object_state.h @@ -8,6 +8,7 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif +#include "pycore_freelist.h" // _PyObject_freelists #include "pycore_hashtable.h" // _Py_hashtable_t struct _py_object_runtime_state { @@ -18,6 +19,9 @@ struct _py_object_runtime_state { }; struct _py_object_state { +#if !defined(Py_GIL_DISABLED) + struct _Py_object_freelists freelists; +#endif #ifdef Py_REF_DEBUG Py_ssize_t reftotal; #endif diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 289ef28f0dd9a9c..6f9e6a332a7830b 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -268,7 +268,7 @@ PyAPI_FUNC(const PyConfig*) _Py_GetConfig(void); // See also PyInterpreterState_Get() and _PyInterpreterState_GET(). extern PyInterpreterState* _PyGILState_GetInterpreterStateUnsafe(void); -static inline _PyFreeListState* _PyFreeListState_GET(void) +static inline struct _Py_object_freelists* _Py_object_freelists_GET(void) { PyThreadState *tstate = _PyThreadState_GET(); #ifdef Py_DEBUG @@ -276,9 +276,9 @@ static inline _PyFreeListState* _PyFreeListState_GET(void) #endif #ifdef Py_GIL_DISABLED - return &((_PyThreadStateImpl*)tstate)->freelist_state; + return &((_PyThreadStateImpl*)tstate)->freelists; #else - return &tstate->interp->freelist_state; + return &tstate->interp->object_state.freelists; #endif } diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h index 3e8fcf5b6ec1fa2..97aa85a659fa7b4 100644 --- a/Include/internal/pycore_tstate.h +++ b/Include/internal/pycore_tstate.h @@ -29,7 +29,7 @@ typedef struct _PyThreadStateImpl { #ifdef Py_GIL_DISABLED struct _mimalloc_thread_state mimalloc; - struct _Py_freelist_state freelist_state; + struct _Py_object_freelists freelists; struct _brc_thread_state brc; #endif diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 9b1defa5cbc609f..11667b07ecfb4bd 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -271,19 +271,19 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu #ifdef WITH_FREELISTS static struct _Py_dict_freelist * -get_dict_state(void) +get_dict_freelist(void) { - _PyFreeListState *state = _PyFreeListState_GET(); - return &state->dicts; + struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); + return &freelists->dicts; } #endif void -_PyDict_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) +_PyDict_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalization) { #ifdef WITH_FREELISTS - struct _Py_dict_freelist *state = &freelist_state->dicts; + struct _Py_dict_freelist *state = &freelists->dicts; while (state->numfree > 0) { PyDictObject *op = state->free_list[--state->numfree]; assert(PyDict_CheckExact(op)); @@ -299,17 +299,6 @@ _PyDict_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) #endif } -void -_PyDict_Fini(PyInterpreterState *Py_UNUSED(interp)) -{ - // With Py_GIL_DISABLED: - // the freelists for the current thread state have already been cleared. -#ifndef Py_GIL_DISABLED - _PyFreeListState *state = _PyFreeListState_GET(); - _PyDict_ClearFreeList(state, 1); -#endif -} - static inline Py_hash_t unicode_get_hash(PyObject *o) { @@ -322,9 +311,9 @@ void _PyDict_DebugMallocStats(FILE *out) { #ifdef WITH_FREELISTS - struct _Py_dict_freelist *state = get_dict_state(); + struct _Py_dict_freelist *dict_freelist = get_dict_freelist(); _PyDebugAllocatorStats(out, "free PyDictObject", - state->numfree, sizeof(PyDictObject)); + dict_freelist->numfree, sizeof(PyDictObject)); #endif } @@ -674,9 +663,9 @@ new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode) } #ifdef WITH_FREELISTS - struct _Py_dict_freelist *state = get_dict_state(); - if (log2_size == PyDict_LOG_MINSIZE && unicode && state->keys_numfree > 0) { - dk = state->keys_free_list[--state->keys_numfree]; + struct _Py_dict_freelist *dict_freelist = get_dict_freelist(); + if (log2_size == PyDict_LOG_MINSIZE && unicode && dict_freelist->keys_numfree > 0) { + dk = dict_freelist->keys_free_list[--dict_freelist->keys_numfree]; OBJECT_STAT_INC(from_freelist); } else @@ -709,12 +698,12 @@ static void free_keys_object(PyDictKeysObject *keys) { #ifdef WITH_FREELISTS - struct _Py_dict_freelist *state = get_dict_state(); + struct _Py_dict_freelist *dict_freelist = get_dict_freelist(); if (DK_LOG_SIZE(keys) == PyDict_LOG_MINSIZE - && state->keys_numfree < PyDict_MAXFREELIST - && state->keys_numfree >= 0 + && dict_freelist->keys_numfree < PyDict_MAXFREELIST + && dict_freelist->keys_numfree >= 0 && DK_IS_UNICODE(keys)) { - state->keys_free_list[state->keys_numfree++] = keys; + dict_freelist->keys_free_list[dict_freelist->keys_numfree++] = keys; OBJECT_STAT_INC(to_freelist); return; } @@ -754,9 +743,9 @@ new_dict(PyInterpreterState *interp, PyDictObject *mp; assert(keys != NULL); #ifdef WITH_FREELISTS - struct _Py_dict_freelist *state = get_dict_state(); - if (state->numfree > 0) { - mp = state->free_list[--state->numfree]; + struct _Py_dict_freelist *dict_freelist = get_dict_freelist(); + if (dict_freelist->numfree > 0) { + mp = dict_freelist->free_list[--dict_freelist->numfree]; assert (mp != NULL); assert (Py_IS_TYPE(mp, &PyDict_Type)); OBJECT_STAT_INC(from_freelist); @@ -2604,10 +2593,10 @@ dict_dealloc(PyObject *self) dictkeys_decref(interp, keys); } #ifdef WITH_FREELISTS - struct _Py_dict_freelist *state = get_dict_state(); - if (state->numfree < PyDict_MAXFREELIST && state->numfree >=0 && + struct _Py_dict_freelist *dict_freelist = get_dict_freelist(); + if (dict_freelist->numfree < PyDict_MAXFREELIST && dict_freelist->numfree >=0 && Py_IS_TYPE(mp, &PyDict_Type)) { - state->free_list[state->numfree++] = mp; + dict_freelist->free_list[dict_freelist->numfree++] = mp; OBJECT_STAT_INC(to_freelist); } else diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 9b322c52d4daea6..7dac8292c7232bd 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -8,7 +8,7 @@ #include "pycore_dtoa.h" // _Py_dg_dtoa() #include "pycore_floatobject.h" // _PyFloat_FormatAdvancedWriter() #include "pycore_initconfig.h" // _PyStatus_OK() -#include "pycore_interp.h" // _PyInterpreterState.float_state +#include "pycore_interp.h" // _Py_float_freelist #include "pycore_long.h" // _PyLong_GetOne() #include "pycore_modsupport.h" // _PyArg_NoKwnames() #include "pycore_object.h" // _PyObject_Init(), _PyDebugAllocatorStats() @@ -27,12 +27,12 @@ class float "PyObject *" "&PyFloat_Type" #include "clinic/floatobject.c.h" #ifdef WITH_FREELISTS -static struct _Py_float_state * -get_float_state(void) +static struct _Py_float_freelist * +get_float_freelist(void) { - _PyFreeListState *state = _PyFreeListState_GET(); - assert(state != NULL); - return &state->floats; + struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); + assert(freelists != NULL); + return &freelists->floats; } #endif @@ -129,11 +129,11 @@ PyFloat_FromDouble(double fval) { PyFloatObject *op; #ifdef WITH_FREELISTS - struct _Py_float_state *state = get_float_state(); - op = state->free_list; + struct _Py_float_freelist *float_freelist = get_float_freelist(); + op = float_freelist->free_list; if (op != NULL) { - state->free_list = (PyFloatObject *) Py_TYPE(op); - state->numfree--; + float_freelist->free_list = (PyFloatObject *) Py_TYPE(op); + float_freelist->numfree--; OBJECT_STAT_INC(from_freelist); } else @@ -245,14 +245,14 @@ _PyFloat_ExactDealloc(PyObject *obj) assert(PyFloat_CheckExact(obj)); PyFloatObject *op = (PyFloatObject *)obj; #ifdef WITH_FREELISTS - struct _Py_float_state *state = get_float_state(); - if (state->numfree >= PyFloat_MAXFREELIST || state->numfree < 0) { + struct _Py_float_freelist *float_freelist = get_float_freelist(); + if (float_freelist->numfree >= PyFloat_MAXFREELIST || float_freelist->numfree < 0) { PyObject_Free(op); return; } - state->numfree++; - Py_SET_TYPE(op, (PyTypeObject *)state->free_list); - state->free_list = op; + float_freelist->numfree++; + Py_SET_TYPE(op, (PyTypeObject *)float_freelist->free_list); + float_freelist->free_list = op; OBJECT_STAT_INC(to_freelist); #else PyObject_Free(op); @@ -1990,10 +1990,10 @@ _PyFloat_InitTypes(PyInterpreterState *interp) } void -_PyFloat_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) +_PyFloat_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalization) { #ifdef WITH_FREELISTS - struct _Py_float_state *state = &freelist_state->floats; + struct _Py_float_freelist *state = &freelists->floats; PyFloatObject *f = state->free_list; while (f != NULL) { PyFloatObject *next = (PyFloatObject*) Py_TYPE(f); @@ -2021,10 +2021,10 @@ void _PyFloat_DebugMallocStats(FILE *out) { #ifdef WITH_FREELISTS - struct _Py_float_state *state = get_float_state(); + struct _Py_float_freelist *float_freelist = get_float_freelist(); _PyDebugAllocatorStats(out, "free PyFloatObject", - state->numfree, sizeof(PyFloatObject)); + float_freelist->numfree, sizeof(PyFloatObject)); #endif } diff --git a/Objects/genobject.c b/Objects/genobject.c index 59ab7abf6180bd1..a1b6db1b5889d39 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -7,7 +7,7 @@ #include "pycore_ceval.h" // _PyEval_EvalFrame() #include "pycore_frame.h" // _PyInterpreterFrame #include "pycore_gc.h" // _PyGC_CLEAR_FINALIZED() -#include "pycore_genobject.h" // struct _Py_async_gen_state +#include "pycore_genobject.h" // struct _Py_async_gen_freelist #include "pycore_modsupport.h" // _PyArg_CheckPositional() #include "pycore_object.h" // _PyObject_GC_UNTRACK() #include "pycore_opcode_utils.h" // RESUME_AFTER_YIELD_FROM @@ -1629,11 +1629,11 @@ PyTypeObject PyAsyncGen_Type = { #ifdef WITH_FREELISTS -static struct _Py_async_gen_state * -get_async_gen_state(void) +static struct _Py_async_gen_freelist * +get_async_gen_freelist(void) { - _PyFreeListState *state = _PyFreeListState_GET(); - return &state->async_gens; + struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); + return &freelists->async_gens; } #endif @@ -1656,10 +1656,10 @@ PyAsyncGen_New(PyFrameObject *f, PyObject *name, PyObject *qualname) void -_PyAsyncGen_ClearFreeLists(_PyFreeListState *freelist_state, int is_finalization) +_PyAsyncGen_ClearFreeLists(struct _Py_object_freelists *freelist_state, int is_finalization) { #ifdef WITH_FREELISTS - struct _Py_async_gen_state *state = &freelist_state->async_gens; + struct _Py_async_gen_freelist *state = &freelist_state->async_gens; while (state->value_numfree > 0) { _PyAsyncGenWrappedValue *o; @@ -1726,11 +1726,11 @@ async_gen_asend_dealloc(PyAsyncGenASend *o) Py_CLEAR(o->ags_gen); Py_CLEAR(o->ags_sendval); #ifdef WITH_FREELISTS - struct _Py_async_gen_state *state = get_async_gen_state(); - if (state->asend_numfree >= 0 && state->asend_numfree < _PyAsyncGen_MAXFREELIST) { + struct _Py_async_gen_freelist *async_gen_freelist = get_async_gen_freelist(); + if (async_gen_freelist->asend_numfree >= 0 && async_gen_freelist->asend_numfree < _PyAsyncGen_MAXFREELIST) { assert(PyAsyncGenASend_CheckExact(o)); _PyGC_CLEAR_FINALIZED((PyObject *)o); - state->asend_freelist[state->asend_numfree++] = o; + async_gen_freelist->asend_freelist[async_gen_freelist->asend_numfree++] = o; } else #endif @@ -1896,10 +1896,10 @@ async_gen_asend_new(PyAsyncGenObject *gen, PyObject *sendval) { PyAsyncGenASend *o; #ifdef WITH_FREELISTS - struct _Py_async_gen_state *state = get_async_gen_state(); - if (state->asend_numfree > 0) { - state->asend_numfree--; - o = state->asend_freelist[state->asend_numfree]; + struct _Py_async_gen_freelist *async_gen_freelist = get_async_gen_freelist(); + if (async_gen_freelist->asend_numfree > 0) { + async_gen_freelist->asend_numfree--; + o = async_gen_freelist->asend_freelist[async_gen_freelist->asend_numfree]; _Py_NewReference((PyObject *)o); } else @@ -1931,10 +1931,10 @@ async_gen_wrapped_val_dealloc(_PyAsyncGenWrappedValue *o) _PyObject_GC_UNTRACK((PyObject *)o); Py_CLEAR(o->agw_val); #ifdef WITH_FREELISTS - struct _Py_async_gen_state *state = get_async_gen_state(); - if (state->value_numfree >= 0 && state->value_numfree < _PyAsyncGen_MAXFREELIST) { + struct _Py_async_gen_freelist *async_gen_freelist = get_async_gen_freelist(); + if (async_gen_freelist->value_numfree >= 0 && async_gen_freelist->value_numfree < _PyAsyncGen_MAXFREELIST) { assert(_PyAsyncGenWrappedValue_CheckExact(o)); - state->value_freelist[state->value_numfree++] = o; + async_gen_freelist->value_freelist[async_gen_freelist->value_numfree++] = o; OBJECT_STAT_INC(to_freelist); } else @@ -2004,10 +2004,10 @@ _PyAsyncGenValueWrapperNew(PyThreadState *tstate, PyObject *val) assert(val); #ifdef WITH_FREELISTS - struct _Py_async_gen_state *state = get_async_gen_state(); - if (state->value_numfree > 0) { - state->value_numfree--; - o = state->value_freelist[state->value_numfree]; + struct _Py_async_gen_freelist *async_gen_freelist = get_async_gen_freelist(); + if (async_gen_freelist->value_numfree > 0) { + async_gen_freelist->value_numfree--; + o = async_gen_freelist->value_freelist[async_gen_freelist->value_numfree]; OBJECT_STAT_INC(from_freelist); assert(_PyAsyncGenWrappedValue_CheckExact(o)); _Py_NewReference((PyObject*)o); diff --git a/Objects/listobject.c b/Objects/listobject.c index 7fdb91eab890b5f..93409a82f8a4897 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -4,7 +4,7 @@ #include "pycore_abstract.h" // _PyIndex_Check() #include "pycore_ceval.h" // _PyEval_GetBuiltin() #include "pycore_interp.h" // PyInterpreterState.list -#include "pycore_list.h" // struct _Py_list_state, _PyListIterObject +#include "pycore_list.h" // struct _Py_list_freelist, _PyListIterObject #include "pycore_long.h" // _PyLong_DigitCount #include "pycore_modsupport.h" // _PyArg_NoKwnames() #include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats() @@ -21,12 +21,12 @@ class list "PyListObject *" "&PyList_Type" _Py_DECLARE_STR(list_err, "list index out of range"); #ifdef WITH_FREELISTS -static struct _Py_list_state * -get_list_state(void) +static struct _Py_list_freelist * +get_list_freelist(void) { - _PyFreeListState *state = _PyFreeListState_GET(); - assert(state != NULL); - return &state->lists; + struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); + assert(freelists != NULL); + return &freelists->lists; } #endif @@ -120,10 +120,10 @@ list_preallocate_exact(PyListObject *self, Py_ssize_t size) } void -_PyList_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) +_PyList_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalization) { #ifdef WITH_FREELISTS - struct _Py_list_state *state = &freelist_state->lists; + struct _Py_list_freelist *state = &freelists->lists; while (state->numfree > 0) { PyListObject *op = state->free_list[--state->numfree]; assert(PyList_CheckExact(op)); @@ -140,10 +140,10 @@ void _PyList_DebugMallocStats(FILE *out) { #ifdef WITH_FREELISTS - struct _Py_list_state *state = get_list_state(); + struct _Py_list_freelist *list_freelist = get_list_freelist(); _PyDebugAllocatorStats(out, "free PyListObject", - state->numfree, sizeof(PyListObject)); + list_freelist->numfree, sizeof(PyListObject)); #endif } @@ -158,10 +158,10 @@ PyList_New(Py_ssize_t size) } #ifdef WITH_FREELISTS - struct _Py_list_state *state = get_list_state(); - if (PyList_MAXFREELIST && state->numfree > 0) { - state->numfree--; - op = state->free_list[state->numfree]; + struct _Py_list_freelist *list_freelist = get_list_freelist(); + if (PyList_MAXFREELIST && list_freelist->numfree > 0) { + list_freelist->numfree--; + op = list_freelist->free_list[list_freelist->numfree]; OBJECT_STAT_INC(from_freelist); _Py_NewReference((PyObject *)op); } @@ -391,9 +391,9 @@ list_dealloc(PyObject *self) PyMem_Free(op->ob_item); } #ifdef WITH_FREELISTS - struct _Py_list_state *state = get_list_state(); - if (state->numfree < PyList_MAXFREELIST && state->numfree >= 0 && PyList_CheckExact(op)) { - state->free_list[state->numfree++] = op; + struct _Py_list_freelist *list_freelist = get_list_freelist(); + if (list_freelist->numfree < PyList_MAXFREELIST && list_freelist->numfree >= 0 && PyList_CheckExact(op)) { + list_freelist->free_list[list_freelist->numfree++] = op; OBJECT_STAT_INC(to_freelist); } else diff --git a/Objects/object.c b/Objects/object.c index 275aa6713c8c217..23eab8288a41e8b 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -794,19 +794,19 @@ PyObject_Bytes(PyObject *v) } void -_PyObject_ClearFreeLists(_PyFreeListState *state, int is_finalization) +_PyObject_ClearFreeLists(struct _Py_object_freelists *freelists, int is_finalization) { // In the free-threaded build, freelists are per-PyThreadState and cleared in PyThreadState_Clear() // In the default build, freelists are per-interpreter and cleared in finalize_interp_types() - _PyFloat_ClearFreeList(state, is_finalization); - _PyTuple_ClearFreeList(state, is_finalization); - _PyList_ClearFreeList(state, is_finalization); - _PyDict_ClearFreeList(state, is_finalization); - _PyContext_ClearFreeList(state, is_finalization); - _PyAsyncGen_ClearFreeLists(state, is_finalization); + _PyFloat_ClearFreeList(freelists, is_finalization); + _PyTuple_ClearFreeList(freelists, is_finalization); + _PyList_ClearFreeList(freelists, is_finalization); + _PyDict_ClearFreeList(freelists, is_finalization); + _PyContext_ClearFreeList(freelists, is_finalization); + _PyAsyncGen_ClearFreeLists(freelists, is_finalization); // Only be cleared if is_finalization is true. - _PyObjectStackChunk_ClearFreeList(state, is_finalization); - _PySlice_ClearFreeList(state, is_finalization); + _PyObjectStackChunk_ClearFreeList(freelists, is_finalization); + _PySlice_ClearFreeList(freelists, is_finalization); } /* diff --git a/Objects/sliceobject.c b/Objects/sliceobject.c index 9880c123c80f95e..7333aea91e5648a 100644 --- a/Objects/sliceobject.c +++ b/Objects/sliceobject.c @@ -103,15 +103,15 @@ PyObject _Py_EllipsisObject = _PyObject_HEAD_INIT(&PyEllipsis_Type); /* Slice object implementation */ -void _PySlice_ClearFreeList(_PyFreeListState *state, int is_finalization) +void _PySlice_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalization) { if (!is_finalization) { return; } #ifdef WITH_FREELISTS - PySliceObject *obj = state->slices.slice_cache; + PySliceObject *obj = freelists->slices.slice_cache; if (obj != NULL) { - state->slices.slice_cache = NULL; + freelists->slices.slice_cache = NULL; PyObject_GC_Del(obj); } #endif @@ -127,10 +127,10 @@ _PyBuildSlice_Consume2(PyObject *start, PyObject *stop, PyObject *step) assert(start != NULL && stop != NULL && step != NULL); PySliceObject *obj; #ifdef WITH_FREELISTS - _PyFreeListState *state = _PyFreeListState_GET(); - if (state->slices.slice_cache != NULL) { - obj = state->slices.slice_cache; - state->slices.slice_cache = NULL; + struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); + if (freelists->slices.slice_cache != NULL) { + obj = freelists->slices.slice_cache; + freelists->slices.slice_cache = NULL; _Py_NewReference((PyObject *)obj); } else @@ -365,9 +365,9 @@ slice_dealloc(PySliceObject *r) Py_DECREF(r->start); Py_DECREF(r->stop); #ifdef WITH_FREELISTS - _PyFreeListState *state = _PyFreeListState_GET(); - if (state->slices.slice_cache == NULL) { - state->slices.slice_cache = r; + struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); + if (freelists->slices.slice_cache == NULL) { + freelists->slices.slice_cache = r; } else #endif diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 7d73c3fb0f7f2cc..1cdf79d95ae352d 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -962,13 +962,13 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize) } -static void maybe_freelist_clear(_PyFreeListState *, int); +static void maybe_freelist_clear(struct _Py_object_freelists *, int); void -_PyTuple_ClearFreeList(_PyFreeListState *state, int is_finalization) +_PyTuple_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalization) { - maybe_freelist_clear(state, is_finalization); + maybe_freelist_clear(freelists, is_finalization); } /*********************** Tuple Iterator **************************/ @@ -1120,26 +1120,26 @@ tuple_iter(PyObject *seq) * freelists * *************/ -#define STATE (state->tuples) -#define FREELIST_FINALIZED (STATE.numfree[0] < 0) +#define TUPLE_FREELIST (freelists->tuples) +#define FREELIST_FINALIZED (TUPLE_FREELIST.numfree[0] < 0) static inline PyTupleObject * maybe_freelist_pop(Py_ssize_t size) { #ifdef WITH_FREELISTS - _PyFreeListState *state = _PyFreeListState_GET(); + struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); if (size == 0) { return NULL; } assert(size > 0); if (size < PyTuple_MAXSAVESIZE) { Py_ssize_t index = size - 1; - PyTupleObject *op = STATE.free_list[index]; + PyTupleObject *op = TUPLE_FREELIST.free_list[index]; if (op != NULL) { /* op is the head of a linked list, with the first item pointing to the next node. Here we pop off the old head. */ - STATE.free_list[index] = (PyTupleObject *) op->ob_item[0]; - STATE.numfree[index]--; + TUPLE_FREELIST.free_list[index] = (PyTupleObject *) op->ob_item[0]; + TUPLE_FREELIST.numfree[index]--; /* Inlined _PyObject_InitVar() without _PyType_HasFeature() test */ #ifdef Py_TRACE_REFS /* maybe_freelist_push() ensures these were already set. */ @@ -1161,21 +1161,21 @@ static inline int maybe_freelist_push(PyTupleObject *op) { #ifdef WITH_FREELISTS - _PyFreeListState *state = _PyFreeListState_GET(); + struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); if (Py_SIZE(op) == 0) { return 0; } Py_ssize_t index = Py_SIZE(op) - 1; if (index < PyTuple_NFREELISTS - && STATE.numfree[index] < PyTuple_MAXFREELIST - && STATE.numfree[index] >= 0 + && TUPLE_FREELIST.numfree[index] < PyTuple_MAXFREELIST + && TUPLE_FREELIST.numfree[index] >= 0 && Py_IS_TYPE(op, &PyTuple_Type)) { /* op is the head of a linked list, with the first item pointing to the next node. Here we set op as the new head. */ - op->ob_item[0] = (PyObject *) STATE.free_list[index]; - STATE.free_list[index] = op; - STATE.numfree[index]++; + op->ob_item[0] = (PyObject *) TUPLE_FREELIST.free_list[index]; + TUPLE_FREELIST.free_list[index] = op; + TUPLE_FREELIST.numfree[index]++; OBJECT_STAT_INC(to_freelist); return 1; } @@ -1184,13 +1184,13 @@ maybe_freelist_push(PyTupleObject *op) } static void -maybe_freelist_clear(_PyFreeListState *state, int fini) +maybe_freelist_clear(struct _Py_object_freelists *freelists, int fini) { #ifdef WITH_FREELISTS for (Py_ssize_t i = 0; i < PyTuple_NFREELISTS; i++) { - PyTupleObject *p = STATE.free_list[i]; - STATE.free_list[i] = NULL; - STATE.numfree[i] = fini ? -1 : 0; + PyTupleObject *p = TUPLE_FREELIST.free_list[i]; + TUPLE_FREELIST.free_list[i] = NULL; + TUPLE_FREELIST.numfree[i] = fini ? -1 : 0; while (p) { PyTupleObject *q = p; p = (PyTupleObject *)(p->ob_item[0]); @@ -1205,13 +1205,13 @@ void _PyTuple_DebugMallocStats(FILE *out) { #ifdef WITH_FREELISTS - _PyFreeListState *state = _PyFreeListState_GET(); + struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); for (int i = 0; i < PyTuple_NFREELISTS; i++) { int len = i + 1; char buf[128]; PyOS_snprintf(buf, sizeof(buf), "free %d-sized PyTupleObject", len); - _PyDebugAllocatorStats(out, buf, STATE.numfree[i], + _PyDebugAllocatorStats(out, buf, TUPLE_FREELIST.numfree[i], _PyObject_VAR_SIZE(&PyTuple_Type, len)); } #endif diff --git a/Python/context.c b/Python/context.c index e44fef705c36e0e..01a21b47da5452b 100644 --- a/Python/context.c +++ b/Python/context.c @@ -65,11 +65,11 @@ contextvar_del(PyContextVar *var); #ifdef WITH_FREELISTS -static struct _Py_context_state * -get_context_state(void) +static struct _Py_context_freelist * +get_context_freelist(void) { - _PyFreeListState *state = _PyFreeListState_GET(); - return &state->contexts; + struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); + return &freelists->contexts; } #endif @@ -341,11 +341,11 @@ _context_alloc(void) { PyContext *ctx; #ifdef WITH_FREELISTS - struct _Py_context_state *state = get_context_state(); - if (state->numfree > 0) { - state->numfree--; - ctx = state->freelist; - state->freelist = (PyContext *)ctx->ctx_weakreflist; + struct _Py_context_freelist *context_freelist = get_context_freelist(); + if (context_freelist->numfree > 0) { + context_freelist->numfree--; + ctx = context_freelist->freelist; + context_freelist->freelist = (PyContext *)ctx->ctx_weakreflist; OBJECT_STAT_INC(from_freelist); ctx->ctx_weakreflist = NULL; _Py_NewReference((PyObject *)ctx); @@ -468,11 +468,11 @@ context_tp_dealloc(PyContext *self) (void)context_tp_clear(self); #ifdef WITH_FREELISTS - struct _Py_context_state *state = get_context_state(); - if (state->numfree >= 0 && state->numfree < PyContext_MAXFREELIST) { - state->numfree++; - self->ctx_weakreflist = (PyObject *)state->freelist; - state->freelist = self; + struct _Py_context_freelist *context_freelist = get_context_freelist(); + if (context_freelist->numfree >= 0 && context_freelist->numfree < PyContext_MAXFREELIST) { + context_freelist->numfree++; + self->ctx_weakreflist = (PyObject *)context_freelist->freelist; + context_freelist->freelist = self; OBJECT_STAT_INC(to_freelist); } else @@ -1267,10 +1267,10 @@ get_token_missing(void) void -_PyContext_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) +_PyContext_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalization) { #ifdef WITH_FREELISTS - struct _Py_context_state *state = &freelist_state->contexts; + struct _Py_context_freelist *state = &freelists->contexts; for (; state->numfree > 0; state->numfree--) { PyContext *ctx = state->freelist; state->freelist = (PyContext *)ctx->ctx_weakreflist; diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 93e1168002b6f7d..3dc1dc19182eb41 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -1721,7 +1721,7 @@ _PyGC_ClearAllFreeLists(PyInterpreterState *interp) HEAD_LOCK(&_PyRuntime); _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)interp->threads.head; while (tstate != NULL) { - _PyObject_ClearFreeLists(&tstate->freelist_state, 0); + _PyObject_ClearFreeLists(&tstate->freelists, 0); tstate = (_PyThreadStateImpl *)tstate->base.next; } HEAD_UNLOCK(&_PyRuntime); diff --git a/Python/gc_gil.c b/Python/gc_gil.c index 5f1365f509deb0a..48646c7af86b7fd 100644 --- a/Python/gc_gil.c +++ b/Python/gc_gil.c @@ -11,7 +11,7 @@ void _PyGC_ClearAllFreeLists(PyInterpreterState *interp) { - _PyObject_ClearFreeLists(&interp->freelist_state, 0); + _PyObject_ClearFreeLists(&interp->object_state.freelists, 0); } #endif diff --git a/Python/object_stack.c b/Python/object_stack.c index ced4460da00f442..ff2901cdacceb82 100644 --- a/Python/object_stack.c +++ b/Python/object_stack.c @@ -8,22 +8,22 @@ extern _PyObjectStackChunk *_PyObjectStackChunk_New(void); extern void _PyObjectStackChunk_Free(_PyObjectStackChunk *); -static struct _Py_object_stack_state * -get_state(void) +static struct _Py_object_stack_freelist * +get_object_stack_freelist(void) { - _PyFreeListState *state = _PyFreeListState_GET(); - return &state->object_stacks; + struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); + return &freelists->object_stacks; } _PyObjectStackChunk * _PyObjectStackChunk_New(void) { _PyObjectStackChunk *buf; - struct _Py_object_stack_state *state = get_state(); - if (state->numfree > 0) { - buf = state->free_list; - state->free_list = buf->prev; - state->numfree--; + struct _Py_object_stack_freelist *obj_stack_freelist = get_object_stack_freelist(); + if (obj_stack_freelist->numfree > 0) { + buf = obj_stack_freelist->free_list; + obj_stack_freelist->free_list = buf->prev; + obj_stack_freelist->numfree--; } else { // NOTE: we use PyMem_RawMalloc() here because this is used by the GC @@ -43,13 +43,13 @@ void _PyObjectStackChunk_Free(_PyObjectStackChunk *buf) { assert(buf->n == 0); - struct _Py_object_stack_state *state = get_state(); - if (state->numfree >= 0 && - state->numfree < _PyObjectStackChunk_MAXFREELIST) + struct _Py_object_stack_freelist *obj_stack_freelist = get_object_stack_freelist(); + if (obj_stack_freelist->numfree >= 0 && + obj_stack_freelist->numfree < _PyObjectStackChunk_MAXFREELIST) { - buf->prev = state->free_list; - state->free_list = buf; - state->numfree++; + buf->prev = obj_stack_freelist->free_list; + obj_stack_freelist->free_list = buf; + obj_stack_freelist->numfree++; } else { PyMem_RawFree(buf); @@ -89,7 +89,7 @@ _PyObjectStack_Merge(_PyObjectStack *dst, _PyObjectStack *src) } void -_PyObjectStackChunk_ClearFreeList(_PyFreeListState *free_lists, int is_finalization) +_PyObjectStackChunk_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalization) { if (!is_finalization) { // Ignore requests to clear the free list during GC. We use object @@ -97,7 +97,7 @@ _PyObjectStackChunk_ClearFreeList(_PyFreeListState *free_lists, int is_finalizat return; } - struct _Py_object_stack_state *state = &free_lists->object_stacks; + struct _Py_object_stack_freelist *state = &freelists->object_stacks; while (state->numfree > 0) { _PyObjectStackChunk *buf = state->free_list; state->free_list = buf->prev; diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 7e4c07bb657d19e..5e5db98481150e7 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1795,8 +1795,8 @@ finalize_interp_types(PyInterpreterState *interp) #ifndef Py_GIL_DISABLED // With Py_GIL_DISABLED: // the freelists for the current thread state have already been cleared. - _PyFreeListState *state = _PyFreeListState_GET(); - _PyObject_ClearFreeLists(state, 1); + struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); + _PyObject_ClearFreeLists(freelists, 1); #endif #ifdef Py_DEBUG diff --git a/Python/pystate.c b/Python/pystate.c index 996f465825215f0..82c955882185e85 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1548,8 +1548,8 @@ PyThreadState_Clear(PyThreadState *tstate) } #ifdef Py_GIL_DISABLED // Each thread should clear own freelists in free-threading builds. - _PyFreeListState *freelist_state = _PyFreeListState_GET(); - _PyObject_ClearFreeLists(freelist_state, 1); + struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); + _PyObject_ClearFreeLists(freelists, 1); // Remove ourself from the biased reference counting table of threads. _Py_brc_remove_thread(tstate); From 46245b0d831b9f5b15b4a0483c785ea71bffef12 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 14 Feb 2024 08:55:00 +0200 Subject: [PATCH 257/507] Docs: Use substitutions instead of manual version updates (#115416) --- Doc/conf.py | 2 ++ Doc/tutorial/interpreter.rst | 4 ++-- Doc/tutorial/stdlib.rst | 2 +- Doc/tutorial/stdlib2.rst | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index 677d139046e5d07..0e84d866a22f5b1 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -66,6 +66,8 @@ rst_epilog = f""" .. |python_version_literal| replace:: ``Python {version}`` +.. |python_x_dot_y_literal| replace:: ``python{version}`` +.. |usr_local_bin_python_x_dot_y_literal| replace:: ``/usr/local/bin/python{version}`` """ # There are two options for replacing |today|: either, you set today to some diff --git a/Doc/tutorial/interpreter.rst b/Doc/tutorial/interpreter.rst index 42ebf2b3d294a8b..299b6c2777adc0e 100644 --- a/Doc/tutorial/interpreter.rst +++ b/Doc/tutorial/interpreter.rst @@ -10,7 +10,7 @@ Using the Python Interpreter Invoking the Interpreter ======================== -The Python interpreter is usually installed as :file:`/usr/local/bin/python3.13` +The Python interpreter is usually installed as |usr_local_bin_python_x_dot_y_literal| on those machines where it is available; putting :file:`/usr/local/bin` in your Unix shell's search path makes it possible to start it by typing the command: @@ -24,7 +24,7 @@ Python guru or system administrator. (E.g., :file:`/usr/local/python` is a popular alternative location.) On Windows machines where you have installed Python from the :ref:`Microsoft Store -<windows-store>`, the :file:`python3.13` command will be available. If you have +<windows-store>`, the |python_x_dot_y_literal| command will be available. If you have the :ref:`py.exe launcher <launcher>` installed, you can use the :file:`py` command. See :ref:`setting-envvars` for other ways to launch Python. diff --git a/Doc/tutorial/stdlib.rst b/Doc/tutorial/stdlib.rst index 63f4b5e1ce02073..9def2a5714950bc 100644 --- a/Doc/tutorial/stdlib.rst +++ b/Doc/tutorial/stdlib.rst @@ -15,7 +15,7 @@ operating system:: >>> import os >>> os.getcwd() # Return the current working directory - 'C:\\Python312' + 'C:\\Python313' >>> os.chdir('/server/accesslogs') # Change current working directory >>> os.system('mkdir today') # Run the command mkdir in the system shell 0 diff --git a/Doc/tutorial/stdlib2.rst b/Doc/tutorial/stdlib2.rst index 33f311db3a24d23..09b6f3d91bcfed7 100644 --- a/Doc/tutorial/stdlib2.rst +++ b/Doc/tutorial/stdlib2.rst @@ -279,7 +279,7 @@ applications include caching objects that are expensive to create:: Traceback (most recent call last): File "<stdin>", line 1, in <module> d['primary'] # entry was automatically removed - File "C:/python312/lib/weakref.py", line 46, in __getitem__ + File "C:/python313/lib/weakref.py", line 46, in __getitem__ o = self.data[key]() KeyError: 'primary' From 3fd2ad8241a61e75b2cd33c697af276863efbb51 Mon Sep 17 00:00:00 2001 From: Alex Waygood <Alex.Waygood@Gmail.com> Date: Wed, 14 Feb 2024 10:41:17 +0000 Subject: [PATCH 258/507] ftplib docs: `timeout` doesn't have to be a whole number (#115443) --- Doc/library/ftplib.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/ftplib.rst b/Doc/library/ftplib.rst index 2f98a272c297ae3..9abf7974d1936db 100644 --- a/Doc/library/ftplib.rst +++ b/Doc/library/ftplib.rst @@ -104,7 +104,7 @@ FTP objects :param timeout: A timeout in seconds for blocking operations like :meth:`connect` (default: the global default timeout setting). - :type timeout: int | None + :type timeout: float | None :param source_address: |param_doc_source_address| @@ -178,7 +178,7 @@ FTP objects :param timeout: A timeout in seconds for the connection attempt (default: the global default timeout setting). - :type timeout: int | None + :type timeout: float | None :param source_address: |param_doc_source_address| @@ -483,7 +483,7 @@ FTP_TLS objects :param timeout: A timeout in seconds for blocking operations like :meth:`~FTP.connect` (default: the global default timeout setting). - :type timeout: int | None + :type timeout: float | None :param source_address: |param_doc_source_address| From 57e4c81ae1cd605efa173885574aedc3fded4b8b Mon Sep 17 00:00:00 2001 From: Ken Jin <kenjin@python.org> Date: Wed, 14 Feb 2024 19:12:52 +0800 Subject: [PATCH 259/507] gh-114058: Fix flaky globals to constant test (#115423) Co-authored-by: Victor Stinner <vstinner@python.org> --- Lib/test/test_capi/test_opt.py | 43 +++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index b64aed10d2d653e..1a8ed3441fa8555 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -7,6 +7,8 @@ import _testinternalcapi +from test.support import script_helper + @contextlib.contextmanager def temporary_optimizer(opt): @@ -659,7 +661,7 @@ def dummy(x): opt = _testinternalcapi.get_uop_optimizer() with temporary_optimizer(opt): - testfunc(20) + testfunc(32) ex = get_first_executor(testfunc) self.assertIsNotNone(ex) @@ -677,10 +679,10 @@ def testfunc(n): opt = _testinternalcapi.get_uop_optimizer() with temporary_optimizer(opt): - res = testfunc(20) + res = testfunc(32) ex = get_first_executor(testfunc) - self.assertEqual(res, 19 * 2) + self.assertEqual(res, 62) self.assertIsNotNone(ex) uops = {opname for opname, _, _ in ex} self.assertNotIn("_GUARD_BOTH_INT", uops) @@ -699,7 +701,7 @@ def testfunc(n): opt = _testinternalcapi.get_uop_optimizer() with temporary_optimizer(opt): - res = testfunc(20) + res = testfunc(32) ex = get_first_executor(testfunc) self.assertEqual(res, 4) @@ -716,7 +718,7 @@ def testfunc(n): opt = _testinternalcapi.get_uop_optimizer() with temporary_optimizer(opt): - testfunc(20) + testfunc(32) ex = get_first_executor(testfunc) self.assertIsNotNone(ex) @@ -740,7 +742,7 @@ def testfunc(n): def dummy(x): return x + 2 - testfunc(10) + testfunc(32) ex = get_first_executor(testfunc) # Honestly as long as it doesn't crash it's fine. @@ -749,20 +751,39 @@ def dummy(x): # This test is a little implementation specific. def test_promote_globals_to_constants(self): + + result = script_helper.run_python_until_end('-c', textwrap.dedent(""" + import _testinternalcapi + import opcode + + def get_first_executor(func): + code = func.__code__ + co_code = code.co_code + JUMP_BACKWARD = opcode.opmap["JUMP_BACKWARD"] + for i in range(0, len(co_code), 2): + if co_code[i] == JUMP_BACKWARD: + try: + return _testinternalcapi.get_executor(code, i) + except ValueError: + pass + return None + def testfunc(n): for i in range(n): x = range(i) return x opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(20) + _testinternalcapi.set_optimizer(opt) + testfunc(64) ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) + assert ex is not None uops = {opname for opname, _, _ in ex} - self.assertNotIn("_LOAD_GLOBAL_BUILTIN", uops) - self.assertIn("_LOAD_CONST_INLINE_BORROW_WITH_NULL", uops) + assert "_LOAD_GLOBAL_BUILTINS" not in uops + assert "_LOAD_CONST_INLINE_BORROW_WITH_NULL" in uops + """)) + self.assertEqual(result[0].rc, 0, result) From dd5e4d90789b3a065290e264122629f31cb0b547 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Wed, 14 Feb 2024 12:14:56 +0100 Subject: [PATCH 260/507] gh-100414: Add SQLite backend to dbm (#114481) Co-authored-by: Raymond Hettinger <rhettinger@users.noreply.github.com> Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com> --- Doc/library/dbm.rst | 53 ++- Doc/whatsnew/3.13.rst | 10 + Lib/dbm/__init__.py | 8 +- Lib/dbm/sqlite3.py | 141 ++++++++ Lib/test/test_dbm.py | 28 ++ Lib/test/test_dbm_sqlite3.py | 308 ++++++++++++++++++ ...-01-23-13-03-22.gh-issue-100414.5kTdU5.rst | 2 + 7 files changed, 544 insertions(+), 6 deletions(-) create mode 100644 Lib/dbm/sqlite3.py create mode 100644 Lib/test/test_dbm_sqlite3.py create mode 100644 Misc/NEWS.d/next/Library/2024-01-23-13-03-22.gh-issue-100414.5kTdU5.rst diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index 9bb5e5f89509568..0f9c825fec9385b 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -8,8 +8,13 @@ -------------- -:mod:`dbm` is a generic interface to variants of the DBM database --- -:mod:`dbm.gnu` or :mod:`dbm.ndbm`. If none of these modules is installed, the +:mod:`dbm` is a generic interface to variants of the DBM database: + +* :mod:`dbm.sqlite3` +* :mod:`dbm.gnu` +* :mod:`dbm.ndbm` + +If none of these modules are installed, the slow-but-simple implementation in module :mod:`dbm.dumb` will be used. There is a `third party interface <https://www.jcea.es/programacion/pybsddb.htm>`_ to the Oracle Berkeley DB. @@ -25,8 +30,8 @@ the Oracle Berkeley DB. .. function:: whichdb(filename) This function attempts to guess which of the several simple database modules - available --- :mod:`dbm.gnu`, :mod:`dbm.ndbm` or :mod:`dbm.dumb` --- should - be used to open a given file. + available --- :mod:`dbm.sqlite3`, :mod:`dbm.gnu`, :mod:`dbm.ndbm`, + or :mod:`dbm.dumb` --- should be used to open a given file. Return one of the following values: @@ -144,6 +149,46 @@ then prints out the contents of the database:: The individual submodules are described in the following sections. +:mod:`dbm.sqlite3` --- SQLite backend for dbm +--------------------------------------------- + +.. module:: dbm.sqlite3 + :platform: All + :synopsis: SQLite backend for dbm + +.. versionadded:: 3.13 + +**Source code:** :source:`Lib/dbm/sqlite3.py` + +-------------- + +This module uses the standard library :mod:`sqlite3` module to provide an +SQLite backend for the :mod:`dbm` module. +The files created by :mod:`dbm.sqlite3` can thus be opened by :mod:`sqlite3`, +or any other SQLite browser, including the SQLite CLI. + +.. function:: open(filename, /, flag="r", mode=0o666) + + Open an SQLite database. + The returned object behaves like a :term:`mapping`, + implements a :meth:`!close` method, + and supports a "closing" context manager via the :keyword:`with` keyword. + + :param filename: + The path to the database to be opened. + :type filename: :term:`path-like object` + + :param str flag: + + * ``'r'`` (default): |flag_r| + * ``'w'``: |flag_w| + * ``'c'``: |flag_c| + * ``'n'``: |flag_n| + + :param mode: + The Unix file access mode of the file (default: octal ``0o666``), + used only when the database has to be created. + :mod:`dbm.gnu` --- GNU database manager --------------------------------------- diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index b96720df0a2f2d1..a265bf1734c1d32 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -231,6 +231,16 @@ dis the ``show_offsets`` parameter. (Contributed by Irit Katriel in :gh:`112137`.) +dbm +--- + +* Add :meth:`dbm.gnu.gdbm.clear` and :meth:`dbm.ndbm.ndbm.clear` methods that remove all items + from the database. + (Contributed by Donghee Na in :gh:`107122`.) + +* Add new :mod:`dbm.sqlite3` backend. + (Contributed by Raymond Hettinger and Erlend E. Aasland in :gh:`100414`.) + doctest ------- diff --git a/Lib/dbm/__init__.py b/Lib/dbm/__init__.py index 8055d3769f9dd00..97c0bb1c9ca946c 100644 --- a/Lib/dbm/__init__.py +++ b/Lib/dbm/__init__.py @@ -5,7 +5,7 @@ import dbm d = dbm.open(file, 'w', 0o666) -The returned object is a dbm.gnu, dbm.ndbm or dbm.dumb object, dependent on the +The returned object is a dbm.sqlite3, dbm.gnu, dbm.ndbm or dbm.dumb database object, dependent on the type of database being opened (determined by the whichdb function) in the case of an existing dbm. If the dbm does not exist and the create or new flag ('c' or 'n') was specified, the dbm type will be determined by the availability of @@ -38,7 +38,7 @@ class error(Exception): pass -_names = ['dbm.gnu', 'dbm.ndbm', 'dbm.dumb'] +_names = ['dbm.gnu', 'dbm.ndbm', 'dbm.sqlite3', 'dbm.dumb'] _defaultmod = None _modules = {} @@ -164,6 +164,10 @@ def whichdb(filename): if len(s) != 4: return "" + # Check for SQLite3 header string. + if s16 == b"SQLite format 3\0": + return "dbm.sqlite3" + # Convert to 4-byte int in native byte order -- return "" if impossible try: (magic,) = struct.unpack("=l", s) diff --git a/Lib/dbm/sqlite3.py b/Lib/dbm/sqlite3.py new file mode 100644 index 000000000000000..74c9d9b7e2f1d86 --- /dev/null +++ b/Lib/dbm/sqlite3.py @@ -0,0 +1,141 @@ +import os +import sqlite3 +import sys +from pathlib import Path +from contextlib import suppress, closing +from collections.abc import MutableMapping + +BUILD_TABLE = """ + CREATE TABLE IF NOT EXISTS Dict ( + key BLOB UNIQUE NOT NULL, + value BLOB NOT NULL + ) +""" +GET_SIZE = "SELECT COUNT (key) FROM Dict" +LOOKUP_KEY = "SELECT value FROM Dict WHERE key = CAST(? AS BLOB)" +STORE_KV = "REPLACE INTO Dict (key, value) VALUES (CAST(? AS BLOB), CAST(? AS BLOB))" +DELETE_KEY = "DELETE FROM Dict WHERE key = CAST(? AS BLOB)" +ITER_KEYS = "SELECT key FROM Dict" + + +class error(OSError): + pass + + +_ERR_CLOSED = "DBM object has already been closed" +_ERR_REINIT = "DBM object does not support reinitialization" + + +def _normalize_uri(path): + path = Path(path) + uri = path.absolute().as_uri() + while "//" in uri: + uri = uri.replace("//", "/") + return uri + + +class _Database(MutableMapping): + + def __init__(self, path, /, *, flag, mode): + if hasattr(self, "_cx"): + raise error(_ERR_REINIT) + + path = os.fsdecode(path) + match flag: + case "r": + flag = "ro" + case "w": + flag = "rw" + case "c": + flag = "rwc" + Path(path).touch(mode=mode, exist_ok=True) + case "n": + flag = "rwc" + Path(path).unlink(missing_ok=True) + Path(path).touch(mode=mode) + case _: + raise ValueError("Flag must be one of 'r', 'w', 'c', or 'n', " + f"not {flag!r}") + + # We use the URI format when opening the database. + uri = _normalize_uri(path) + uri = f"{uri}?mode={flag}" + + try: + self._cx = sqlite3.connect(uri, autocommit=True, uri=True) + except sqlite3.Error as exc: + raise error(str(exc)) + + # This is an optimization only; it's ok if it fails. + with suppress(sqlite3.OperationalError): + self._cx.execute("PRAGMA journal_mode = wal") + + if flag == "rwc": + self._execute(BUILD_TABLE) + + def _execute(self, *args, **kwargs): + if not self._cx: + raise error(_ERR_CLOSED) + try: + return closing(self._cx.execute(*args, **kwargs)) + except sqlite3.Error as exc: + raise error(str(exc)) + + def __len__(self): + with self._execute(GET_SIZE) as cu: + row = cu.fetchone() + return row[0] + + def __getitem__(self, key): + with self._execute(LOOKUP_KEY, (key,)) as cu: + row = cu.fetchone() + if not row: + raise KeyError(key) + return row[0] + + def __setitem__(self, key, value): + self._execute(STORE_KV, (key, value)) + + def __delitem__(self, key): + with self._execute(DELETE_KEY, (key,)) as cu: + if not cu.rowcount: + raise KeyError(key) + + def __iter__(self): + try: + with self._execute(ITER_KEYS) as cu: + for row in cu: + yield row[0] + except sqlite3.Error as exc: + raise error(str(exc)) + + def close(self): + if self._cx: + self._cx.close() + self._cx = None + + def keys(self): + return list(super().keys()) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + +def open(filename, /, flag="r", mode=0o666): + """Open a dbm.sqlite3 database and return the dbm object. + + The 'filename' parameter is the name of the database file. + + The optional 'flag' parameter can be one of ...: + 'r' (default): open an existing database for read only access + 'w': open an existing database for read/write access + 'c': create a database if it does not exist; open for read/write access + 'n': always create a new, empty database; open for read/write access + + The optional 'mode' parameter is the Unix file access mode of the database; + only used when creating a new database. Default: 0o666. + """ + return _Database(filename, flag=flag, mode=mode) diff --git a/Lib/test/test_dbm.py b/Lib/test/test_dbm.py index e3924d8ec8b5c1d..4be7c5649da68aa 100644 --- a/Lib/test/test_dbm.py +++ b/Lib/test/test_dbm.py @@ -6,6 +6,13 @@ from test.support import import_helper from test.support import os_helper + +try: + from dbm import sqlite3 as dbm_sqlite3 +except ImportError: + dbm_sqlite3 = None + + try: from dbm import ndbm except ImportError: @@ -213,6 +220,27 @@ def test_whichdb_ndbm(self): for path in fnames: self.assertIsNone(self.dbm.whichdb(path)) + @unittest.skipUnless(dbm_sqlite3, reason='Test requires dbm.sqlite3') + def test_whichdb_sqlite3(self): + # Databases created by dbm.sqlite3 are detected correctly. + with dbm_sqlite3.open(_fname, "c") as db: + db["key"] = "value" + self.assertEqual(self.dbm.whichdb(_fname), "dbm.sqlite3") + + @unittest.skipUnless(dbm_sqlite3, reason='Test requires dbm.sqlite3') + def test_whichdb_sqlite3_existing_db(self): + # Existing sqlite3 databases are detected correctly. + sqlite3 = import_helper.import_module("sqlite3") + try: + # Create an empty database. + with sqlite3.connect(_fname) as cx: + cx.execute("CREATE TABLE dummy(database)") + cx.commit() + finally: + cx.close() + self.assertEqual(self.dbm.whichdb(_fname), "dbm.sqlite3") + + def setUp(self): self.addCleanup(cleaunup_test_dir) setup_test_dir() diff --git a/Lib/test/test_dbm_sqlite3.py b/Lib/test/test_dbm_sqlite3.py new file mode 100644 index 000000000000000..7bc2a0303528358 --- /dev/null +++ b/Lib/test/test_dbm_sqlite3.py @@ -0,0 +1,308 @@ +import sqlite3 +import sys +import test.support +import unittest +from contextlib import closing +from functools import partial +from pathlib import Path +from test.support import cpython_only, import_helper, os_helper + + +dbm_sqlite3 = import_helper.import_module("dbm.sqlite3") +from dbm.sqlite3 import _normalize_uri + + +class _SQLiteDbmTests(unittest.TestCase): + + def setUp(self): + self.filename = os_helper.TESTFN + db = dbm_sqlite3.open(self.filename, "c") + db.close() + + def tearDown(self): + for suffix in "", "-wal", "-shm": + os_helper.unlink(self.filename + suffix) + + +class URI(unittest.TestCase): + + def test_uri_substitutions(self): + dataset = ( + ("/absolute/////b/c", "/absolute/b/c"), + ("PRE#MID##END", "PRE%23MID%23%23END"), + ("%#?%%#", "%25%23%3F%25%25%23"), + ) + for path, normalized in dataset: + with self.subTest(path=path, normalized=normalized): + self.assertTrue(_normalize_uri(path).endswith(normalized)) + + @unittest.skipUnless(sys.platform == "win32", "requires Windows") + def test_uri_windows(self): + dataset = ( + # Relative subdir. + (r"2018\January.xlsx", + "2018/January.xlsx"), + # Absolute with drive letter. + (r"C:\Projects\apilibrary\apilibrary.sln", + "/C:/Projects/apilibrary/apilibrary.sln"), + # Relative with drive letter. + (r"C:Projects\apilibrary\apilibrary.sln", + "/C:Projects/apilibrary/apilibrary.sln"), + ) + for path, normalized in dataset: + with self.subTest(path=path, normalized=normalized): + if not Path(path).is_absolute(): + self.skipTest(f"skipping relative path: {path!r}") + self.assertTrue(_normalize_uri(path).endswith(normalized)) + + +class ReadOnly(_SQLiteDbmTests): + + def setUp(self): + super().setUp() + with dbm_sqlite3.open(self.filename, "w") as db: + db[b"key1"] = "value1" + db[b"key2"] = "value2" + self.db = dbm_sqlite3.open(self.filename, "r") + + def tearDown(self): + self.db.close() + super().tearDown() + + def test_readonly_read(self): + self.assertEqual(self.db[b"key1"], b"value1") + self.assertEqual(self.db[b"key2"], b"value2") + + def test_readonly_write(self): + with self.assertRaises(dbm_sqlite3.error): + self.db[b"new"] = "value" + + def test_readonly_delete(self): + with self.assertRaises(dbm_sqlite3.error): + del self.db[b"key1"] + + def test_readonly_keys(self): + self.assertEqual(self.db.keys(), [b"key1", b"key2"]) + + def test_readonly_iter(self): + self.assertEqual([k for k in self.db], [b"key1", b"key2"]) + + +class ReadWrite(_SQLiteDbmTests): + + def setUp(self): + super().setUp() + self.db = dbm_sqlite3.open(self.filename, "w") + + def tearDown(self): + self.db.close() + super().tearDown() + + def db_content(self): + with closing(sqlite3.connect(self.filename)) as cx: + keys = [r[0] for r in cx.execute("SELECT key FROM Dict")] + vals = [r[0] for r in cx.execute("SELECT value FROM Dict")] + return keys, vals + + def test_readwrite_unique_key(self): + self.db["key"] = "value" + self.db["key"] = "other" + keys, vals = self.db_content() + self.assertEqual(keys, [b"key"]) + self.assertEqual(vals, [b"other"]) + + def test_readwrite_delete(self): + self.db["key"] = "value" + self.db["new"] = "other" + + del self.db[b"new"] + keys, vals = self.db_content() + self.assertEqual(keys, [b"key"]) + self.assertEqual(vals, [b"value"]) + + del self.db[b"key"] + keys, vals = self.db_content() + self.assertEqual(keys, []) + self.assertEqual(vals, []) + + def test_readwrite_null_key(self): + with self.assertRaises(dbm_sqlite3.error): + self.db[None] = "value" + + def test_readwrite_null_value(self): + with self.assertRaises(dbm_sqlite3.error): + self.db[b"key"] = None + + +class Misuse(_SQLiteDbmTests): + + def setUp(self): + super().setUp() + self.db = dbm_sqlite3.open(self.filename, "w") + + def tearDown(self): + self.db.close() + super().tearDown() + + def test_misuse_double_create(self): + self.db["key"] = "value" + with dbm_sqlite3.open(self.filename, "c") as db: + self.assertEqual(db[b"key"], b"value") + + def test_misuse_double_close(self): + self.db.close() + + def test_misuse_invalid_flag(self): + regex = "must be.*'r'.*'w'.*'c'.*'n', not 'invalid'" + with self.assertRaisesRegex(ValueError, regex): + dbm_sqlite3.open(self.filename, flag="invalid") + + def test_misuse_double_delete(self): + self.db["key"] = "value" + del self.db[b"key"] + with self.assertRaises(KeyError): + del self.db[b"key"] + + def test_misuse_invalid_key(self): + with self.assertRaises(KeyError): + self.db[b"key"] + + def test_misuse_iter_close1(self): + self.db["1"] = 1 + it = iter(self.db) + self.db.close() + with self.assertRaises(dbm_sqlite3.error): + next(it) + + def test_misuse_iter_close2(self): + self.db["1"] = 1 + self.db["2"] = 2 + it = iter(self.db) + next(it) + self.db.close() + with self.assertRaises(dbm_sqlite3.error): + next(it) + + def test_misuse_use_after_close(self): + self.db.close() + with self.assertRaises(dbm_sqlite3.error): + self.db[b"read"] + with self.assertRaises(dbm_sqlite3.error): + self.db[b"write"] = "value" + with self.assertRaises(dbm_sqlite3.error): + del self.db[b"del"] + with self.assertRaises(dbm_sqlite3.error): + len(self.db) + with self.assertRaises(dbm_sqlite3.error): + self.db.keys() + + def test_misuse_reinit(self): + with self.assertRaises(dbm_sqlite3.error): + self.db.__init__("new.db", flag="n", mode=0o666) + + def test_misuse_empty_filename(self): + for flag in "r", "w", "c", "n": + with self.assertRaises(dbm_sqlite3.error): + db = dbm_sqlite3.open("", flag="c") + + +class DataTypes(_SQLiteDbmTests): + + dataset = ( + # (raw, coerced) + (42, b"42"), + (3.14, b"3.14"), + ("string", b"string"), + (b"bytes", b"bytes"), + ) + + def setUp(self): + super().setUp() + self.db = dbm_sqlite3.open(self.filename, "w") + + def tearDown(self): + self.db.close() + super().tearDown() + + def test_datatypes_values(self): + for raw, coerced in self.dataset: + with self.subTest(raw=raw, coerced=coerced): + self.db["key"] = raw + self.assertEqual(self.db[b"key"], coerced) + + def test_datatypes_keys(self): + for raw, coerced in self.dataset: + with self.subTest(raw=raw, coerced=coerced): + self.db[raw] = "value" + self.assertEqual(self.db[coerced], b"value") + # Raw keys are silently coerced to bytes. + self.assertEqual(self.db[raw], b"value") + del self.db[raw] + + def test_datatypes_replace_coerced(self): + self.db["10"] = "value" + self.db[b"10"] = "value" + self.db[10] = "value" + self.assertEqual(self.db.keys(), [b"10"]) + + +class CorruptDatabase(_SQLiteDbmTests): + """Verify that database exceptions are raised as dbm.sqlite3.error.""" + + def setUp(self): + super().setUp() + with closing(sqlite3.connect(self.filename)) as cx: + with cx: + cx.execute("DROP TABLE IF EXISTS Dict") + cx.execute("CREATE TABLE Dict (invalid_schema)") + + def check(self, flag, fn, should_succeed=False): + with closing(dbm_sqlite3.open(self.filename, flag)) as db: + with self.assertRaises(dbm_sqlite3.error): + fn(db) + + @staticmethod + def read(db): + return db["key"] + + @staticmethod + def write(db): + db["key"] = "value" + + @staticmethod + def iter(db): + next(iter(db)) + + @staticmethod + def keys(db): + db.keys() + + @staticmethod + def del_(db): + del db["key"] + + @staticmethod + def len_(db): + len(db) + + def test_corrupt_readwrite(self): + for flag in "r", "w", "c": + with self.subTest(flag=flag): + check = partial(self.check, flag=flag) + check(fn=self.read) + check(fn=self.write) + check(fn=self.iter) + check(fn=self.keys) + check(fn=self.del_) + check(fn=self.len_) + + def test_corrupt_force_new(self): + with closing(dbm_sqlite3.open(self.filename, "n")) as db: + db["foo"] = "write" + _ = db[b"foo"] + next(iter(db)) + del db[b"foo"] + + +if __name__ == "__main__": + unittest.main() diff --git a/Misc/NEWS.d/next/Library/2024-01-23-13-03-22.gh-issue-100414.5kTdU5.rst b/Misc/NEWS.d/next/Library/2024-01-23-13-03-22.gh-issue-100414.5kTdU5.rst new file mode 100644 index 000000000000000..ffcb926a8d546ce --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-23-13-03-22.gh-issue-100414.5kTdU5.rst @@ -0,0 +1,2 @@ +Add :mod:`dbm.sqlite3` as a backend to :mod:`dbm`. +Patch by Raymond Hettinger and Erlend E. Aasland. From 029ec91d43b377535ff7eb94993e0d2add4af720 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Wed, 14 Feb 2024 14:16:09 +0100 Subject: [PATCH 261/507] gh-100414: Skip test_dbm_sqlite3 if sqlite3 is unavailable (#115449) Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> --- Lib/test/test_dbm_sqlite3.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_dbm_sqlite3.py b/Lib/test/test_dbm_sqlite3.py index 7bc2a0303528358..7a49fd2f924f8d3 100644 --- a/Lib/test/test_dbm_sqlite3.py +++ b/Lib/test/test_dbm_sqlite3.py @@ -1,4 +1,3 @@ -import sqlite3 import sys import test.support import unittest @@ -7,8 +6,12 @@ from pathlib import Path from test.support import cpython_only, import_helper, os_helper - dbm_sqlite3 = import_helper.import_module("dbm.sqlite3") +# N.B. The test will fail on some platforms without sqlite3 +# if the sqlite3 import is above the import of dbm.sqlite3. +# This is deliberate: if the import helper managed to import dbm.sqlite3, +# we must inevitably be able to import sqlite3. Else, we have a problem. +import sqlite3 from dbm.sqlite3 import _normalize_uri From ec8909a23931338f81803ea3f18dc2073f74a152 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Wed, 14 Feb 2024 16:31:28 +0300 Subject: [PATCH 262/507] gh-115450: Fix direct invocation of `test_desctut` (#115451) --- Lib/test/test_descrtut.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_descrtut.py b/Lib/test/test_descrtut.py index 13e3ea41bdb76c5..f097c4e7300baac 100644 --- a/Lib/test/test_descrtut.py +++ b/Lib/test/test_descrtut.py @@ -39,16 +39,16 @@ def merge(self, other): Here's the new type at work: >>> print(defaultdict) # show our type - <class 'test.test_descrtut.defaultdict'> + <class '%(modname)s.defaultdict'> >>> print(type(defaultdict)) # its metatype <class 'type'> >>> a = defaultdict(default=0.0) # create an instance >>> print(a) # show the instance {} >>> print(type(a)) # show its type - <class 'test.test_descrtut.defaultdict'> + <class '%(modname)s.defaultdict'> >>> print(a.__class__) # show its class - <class 'test.test_descrtut.defaultdict'> + <class '%(modname)s.defaultdict'> >>> print(type(a) is a.__class__) # its type is its class True >>> a[1] = 3.25 # modify the instance @@ -99,7 +99,7 @@ def merge(self, other): >>> print(sortdict(a.__dict__)) {'default': -1000, 'x1': 100, 'x2': 200} >>> -""" +""" % {'modname': __name__} class defaultdict2(dict): __slots__ = ['default'] @@ -264,19 +264,19 @@ def merge(self, other): ... print("classmethod", cls, y) >>> C.foo(1) - classmethod <class 'test.test_descrtut.C'> 1 + classmethod <class '%(modname)s.C'> 1 >>> c = C() >>> c.foo(1) - classmethod <class 'test.test_descrtut.C'> 1 + classmethod <class '%(modname)s.C'> 1 >>> class D(C): ... pass >>> D.foo(1) - classmethod <class 'test.test_descrtut.D'> 1 + classmethod <class '%(modname)s.D'> 1 >>> d = D() >>> d.foo(1) - classmethod <class 'test.test_descrtut.D'> 1 + classmethod <class '%(modname)s.D'> 1 This prints "classmethod __main__.D 1" both times; in other words, the class passed as the first argument of foo() is the class involved in the @@ -292,18 +292,18 @@ class passed as the first argument of foo() is the class involved in the >>> E.foo(1) E.foo() called - classmethod <class 'test.test_descrtut.C'> 1 + classmethod <class '%(modname)s.C'> 1 >>> e = E() >>> e.foo(1) E.foo() called - classmethod <class 'test.test_descrtut.C'> 1 + classmethod <class '%(modname)s.C'> 1 In this example, the call to C.foo() from E.foo() will see class C as its first argument, not class E. This is to be expected, since the call specifies the class C. But it stresses the difference between these class methods and methods defined in metaclasses (where an upcall to a metamethod would pass the target class as an explicit first argument). -""" +""" % {'modname': __name__} test_5 = """ From 6d9141ed766f4003f39362937dc397e9f734c7e5 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Wed, 14 Feb 2024 14:47:19 +0100 Subject: [PATCH 263/507] gh-100414: Make dbm.sqlite3 the preferred dbm backend (#115447) --- Doc/whatsnew/3.13.rst | 2 +- Lib/dbm/__init__.py | 2 +- .../next/Library/2024-01-23-13-03-22.gh-issue-100414.5kTdU5.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index a265bf1734c1d32..b14fb4e5392a2c3 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -238,7 +238,7 @@ dbm from the database. (Contributed by Donghee Na in :gh:`107122`.) -* Add new :mod:`dbm.sqlite3` backend. +* Add new :mod:`dbm.sqlite3` backend, and make it the default :mod:`!dbm` backend. (Contributed by Raymond Hettinger and Erlend E. Aasland in :gh:`100414`.) doctest diff --git a/Lib/dbm/__init__.py b/Lib/dbm/__init__.py index 97c0bb1c9ca946c..4fdbc54e74cfb64 100644 --- a/Lib/dbm/__init__.py +++ b/Lib/dbm/__init__.py @@ -38,7 +38,7 @@ class error(Exception): pass -_names = ['dbm.gnu', 'dbm.ndbm', 'dbm.sqlite3', 'dbm.dumb'] +_names = ['dbm.sqlite3', 'dbm.gnu', 'dbm.ndbm', 'dbm.dumb'] _defaultmod = None _modules = {} diff --git a/Misc/NEWS.d/next/Library/2024-01-23-13-03-22.gh-issue-100414.5kTdU5.rst b/Misc/NEWS.d/next/Library/2024-01-23-13-03-22.gh-issue-100414.5kTdU5.rst index ffcb926a8d546ce..0f3b3bdd7c6d261 100644 --- a/Misc/NEWS.d/next/Library/2024-01-23-13-03-22.gh-issue-100414.5kTdU5.rst +++ b/Misc/NEWS.d/next/Library/2024-01-23-13-03-22.gh-issue-100414.5kTdU5.rst @@ -1,2 +1,2 @@ -Add :mod:`dbm.sqlite3` as a backend to :mod:`dbm`. +Add :mod:`dbm.sqlite3` as a backend to :mod:`dbm`, and make it the new default :mod:`!dbm` backend. Patch by Raymond Hettinger and Erlend E. Aasland. From 6755c4e0c8803a246e632835030c0b8837b3b676 Mon Sep 17 00:00:00 2001 From: Stanislav Lyu <wallseat@gmail.com> Date: Wed, 14 Feb 2024 16:52:42 +0300 Subject: [PATCH 264/507] gh-115403: Remove extra colon after "Examples" in datetime documentation (#115452) --- Doc/library/datetime.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index a46eed35ee23290..4602132f37f7331 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -1811,7 +1811,7 @@ Other constructor: be truncated). 4. Fractional hours and minutes are not supported. - Examples:: + Examples: .. doctest:: From bb791c7728e0508ad5df28a90b27e202d66a9cfa Mon Sep 17 00:00:00 2001 From: Brian Schubert <brianm.schubert@gmail.com> Date: Wed, 14 Feb 2024 10:01:27 -0500 Subject: [PATCH 265/507] gh-115392: Fix doctest reporting incorrect line numbers for decorated functions (#115440) --- Lib/doctest.py | 2 +- Lib/test/test_doctest/decorator_mod.py | 10 ++++++++++ Lib/test/test_doctest/doctest_lineno.py | 9 +++++++++ Lib/test/test_doctest/test_doctest.py | 1 + .../2024-02-13-18-27-03.gh-issue-115392.gle5tp.rst | 2 ++ 5 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 Lib/test/test_doctest/decorator_mod.py create mode 100644 Misc/NEWS.d/next/Library/2024-02-13-18-27-03.gh-issue-115392.gle5tp.rst diff --git a/Lib/doctest.py b/Lib/doctest.py index 114aac62a34e95b..1969777b6677875 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -1140,7 +1140,7 @@ def _find_lineno(self, obj, source_lines): obj = obj.fget if inspect.isfunction(obj) and getattr(obj, '__doc__', None): # We don't use `docstring` var here, because `obj` can be changed. - obj = obj.__code__ + obj = inspect.unwrap(obj).__code__ if inspect.istraceback(obj): obj = obj.tb_frame if inspect.isframe(obj): obj = obj.f_code if inspect.iscode(obj): diff --git a/Lib/test/test_doctest/decorator_mod.py b/Lib/test/test_doctest/decorator_mod.py new file mode 100644 index 000000000000000..9f106888411202b --- /dev/null +++ b/Lib/test/test_doctest/decorator_mod.py @@ -0,0 +1,10 @@ +# This module is used in `doctest_lineno.py`. +import functools + + +def decorator(f): + @functools.wraps(f) + def inner(): + return f() + + return inner diff --git a/Lib/test/test_doctest/doctest_lineno.py b/Lib/test/test_doctest/doctest_lineno.py index 677c569cf710ebd..0dbcd9a11eaba2f 100644 --- a/Lib/test/test_doctest/doctest_lineno.py +++ b/Lib/test/test_doctest/doctest_lineno.py @@ -67,3 +67,12 @@ def property_with_doctest(self): # https://github.com/python/cpython/issues/99433 str_wrapper = object().__str__ + + +# https://github.com/python/cpython/issues/115392 +from test.test_doctest.decorator_mod import decorator + +@decorator +@decorator +def func_with_docstring_wrapped(): + """Some unrelated info.""" diff --git a/Lib/test/test_doctest/test_doctest.py b/Lib/test/test_doctest/test_doctest.py index 7015255db1f7f0e..43be200b983227b 100644 --- a/Lib/test/test_doctest/test_doctest.py +++ b/Lib/test/test_doctest/test_doctest.py @@ -685,6 +685,7 @@ def basics(): r""" None test.test_doctest.doctest_lineno.MethodWrapper.method_without_docstring 61 test.test_doctest.doctest_lineno.MethodWrapper.property_with_doctest 4 test.test_doctest.doctest_lineno.func_with_docstring + 77 test.test_doctest.doctest_lineno.func_with_docstring_wrapped 12 test.test_doctest.doctest_lineno.func_with_doctest None test.test_doctest.doctest_lineno.func_without_docstring diff --git a/Misc/NEWS.d/next/Library/2024-02-13-18-27-03.gh-issue-115392.gle5tp.rst b/Misc/NEWS.d/next/Library/2024-02-13-18-27-03.gh-issue-115392.gle5tp.rst new file mode 100644 index 000000000000000..1c3368968e4cf00 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-13-18-27-03.gh-issue-115392.gle5tp.rst @@ -0,0 +1,2 @@ +Fix a bug in :mod:`doctest` where incorrect line numbers would be +reported for decorated functions. From 81e140d10b77f0a41a5581412e3f3471cc77981f Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Wed, 14 Feb 2024 16:36:13 +0100 Subject: [PATCH 266/507] Docs: reword sentences about dbm submodule traits (#114609) Don't repeatedly say that keys and values are coerced into bytes. --- Doc/library/dbm.rst | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index 0f9c825fec9385b..b4f83d454ac6519 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -205,10 +205,6 @@ The :mod:`dbm.gnu` module provides an interface to the :abbr:`GDBM (GNU dbm)` library, similar to the :mod:`dbm.ndbm` module, but with additional functionality like crash tolerance. -:class:`!gdbm` objects behave similar to :term:`mappings <mapping>`, -except that keys and values are always converted to :class:`bytes` before storing, -and the :meth:`!items` and :meth:`!values` methods are not supported. - .. note:: |incompat_note| .. exception:: error @@ -256,8 +252,9 @@ and the :meth:`!items` and :meth:`!values` methods are not supported. A string of characters the *flag* parameter of :meth:`~dbm.gnu.open` supports. - In addition to the dictionary-like methods, :class:`gdbm` objects have the - following methods and attributes: + :class:`!gdbm` objects behave similar to :term:`mappings <mapping>`, + but :meth:`!items` and :meth:`!values` methods are not supported. + The following methods are also provided: .. method:: gdbm.firstkey() @@ -314,10 +311,6 @@ and the :meth:`!items` and :meth:`!values` methods are not supported. The :mod:`dbm.ndbm` module provides an interface to the :abbr:`NDBM (New Database Manager)` library. -:class:`!ndbm` objects behave similar to :term:`mappings <mapping>`, -except that keys and values are always stored as :class:`bytes`, -and the :meth:`!items` and :meth:`!values` methods are not supported. - This module can be used with the "classic" NDBM interface or the :abbr:`GDBM (GNU dbm)` compatibility interface. @@ -359,8 +352,9 @@ This module can be used with the "classic" NDBM interface or the :param int mode: |mode_param_doc| - In addition to the dictionary-like methods, :class:`!ndbm` objects - provide the following method: + :class:`!ndbm` objects behave similar to :term:`mappings <mapping>`, + but :meth:`!items` and :meth:`!values` methods are not supported. + The following methods are also provided: .. versionchanged:: 3.11 Accepts :term:`path-like object` for filename. @@ -399,8 +393,6 @@ The :mod:`dbm.dumb` module provides a persistent :class:`dict`-like interface which is written entirely in Python. Unlike other :mod:`dbm` backends, such as :mod:`dbm.gnu`, no external library is required. -As with other :mod:`dbm` backends, -the keys and values are always stored as :class:`bytes`. The :mod:`!dbm.dumb` module defines the following: From 671360161f0b7a5ff4c1d062e570962e851b4bde Mon Sep 17 00:00:00 2001 From: kcatss <kcats9731@gmail.com> Date: Thu, 15 Feb 2024 01:08:26 +0900 Subject: [PATCH 267/507] gh-115243: Fix crash in deque.index() when the deque is concurrently modified (GH-115247) --- Lib/test/test_deque.py | 6 +++++- .../Security/2024-02-12-00-33-01.gh-issue-115243.e1oGX8.rst | 1 + Modules/_collectionsmodule.c | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Security/2024-02-12-00-33-01.gh-issue-115243.e1oGX8.rst diff --git a/Lib/test/test_deque.py b/Lib/test/test_deque.py index ae1dfacd7262e40..4679f297fd7f4a1 100644 --- a/Lib/test/test_deque.py +++ b/Lib/test/test_deque.py @@ -166,7 +166,7 @@ def test_contains(self): with self.assertRaises(RuntimeError): n in d - def test_contains_count_stop_crashes(self): + def test_contains_count_index_stop_crashes(self): class A: def __eq__(self, other): d.clear() @@ -178,6 +178,10 @@ def __eq__(self, other): with self.assertRaises(RuntimeError): _ = d.count(3) + d = deque([A()]) + with self.assertRaises(RuntimeError): + d.index(0) + def test_extend(self): d = deque('a') self.assertRaises(TypeError, d.extend, 1) diff --git a/Misc/NEWS.d/next/Security/2024-02-12-00-33-01.gh-issue-115243.e1oGX8.rst b/Misc/NEWS.d/next/Security/2024-02-12-00-33-01.gh-issue-115243.e1oGX8.rst new file mode 100644 index 000000000000000..ae0e910c7d159c6 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2024-02-12-00-33-01.gh-issue-115243.e1oGX8.rst @@ -0,0 +1 @@ +Fix possible crashes in :meth:`collections.deque.index` when the deque is concurrently modified. diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c index ef77d34b10e47b9..4fa76d62bc3f8d8 100644 --- a/Modules/_collectionsmodule.c +++ b/Modules/_collectionsmodule.c @@ -1218,8 +1218,9 @@ deque_index_impl(dequeobject *deque, PyObject *v, Py_ssize_t start, n = stop - i; while (--n >= 0) { CHECK_NOT_END(b); - item = b->data[index]; + item = Py_NewRef(b->data[index]); cmp = PyObject_RichCompareBool(item, v, Py_EQ); + Py_DECREF(item); if (cmp > 0) return PyLong_FromSsize_t(stop - n - 1); if (cmp < 0) From 4b2d1786ccf913bc80ff571c32b196be1543ca54 Mon Sep 17 00:00:00 2001 From: Seth Michael Larson <seth@python.org> Date: Wed, 14 Feb 2024 10:29:06 -0600 Subject: [PATCH 268/507] gh-115399: Upgrade bundled libexpat to 2.6.0 (#115431) --- ...-02-13-15-14-39.gh-issue-115399.xT-scP.rst | 1 + Misc/sbom.spdx.json | 67 +-- Modules/expat/expat.h | 28 +- Modules/expat/expat_config.h | 1 + Modules/expat/internal.h | 8 +- Modules/expat/siphash.h | 10 +- Modules/expat/winconfig.h | 7 +- Modules/expat/xmlparse.c | 558 +++++++++++------- Modules/expat/xmlrole.c | 6 +- Modules/expat/xmlrole.h | 6 +- Modules/expat/xmltok.c | 29 +- Modules/expat/xmltok.h | 8 +- Modules/expat/xmltok_impl.c | 2 +- Tools/build/generate_sbom.py | 5 +- Tools/c-analyzer/cpython/_parser.py | 1 + 15 files changed, 431 insertions(+), 306 deletions(-) create mode 100644 Misc/NEWS.d/next/Security/2024-02-13-15-14-39.gh-issue-115399.xT-scP.rst diff --git a/Misc/NEWS.d/next/Security/2024-02-13-15-14-39.gh-issue-115399.xT-scP.rst b/Misc/NEWS.d/next/Security/2024-02-13-15-14-39.gh-issue-115399.xT-scP.rst new file mode 100644 index 000000000000000..e8163b6f29c1891 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2024-02-13-15-14-39.gh-issue-115399.xT-scP.rst @@ -0,0 +1 @@ +Update bundled libexpat to 2.6.0 diff --git a/Misc/sbom.spdx.json b/Misc/sbom.spdx.json index d783d14255e66f1..03b2db20553e568 100644 --- a/Misc/sbom.spdx.json +++ b/Misc/sbom.spdx.json @@ -48,29 +48,15 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "ab7bb32514d170592dfb3f76e41bbdc075a4e7e0" + "checksumValue": "90c06411f131e777e2b5c3d22b7ccf50bc46f617" }, { "algorithm": "SHA256", - "checksumValue": "f521acdad222644365b0e81a33bcd6939a98c91b225c47582cc84bd73d96febc" + "checksumValue": "3045f9176950aa13a54e53fa096385670c676c492705d636e977f888e4c72d48" } ], "fileName": "Modules/expat/expat.h" }, - { - "SPDXID": "SPDXRef-FILE-Modules-expat-expat-config.h", - "checksums": [ - { - "algorithm": "SHA1", - "checksumValue": "73627287302ee3e84347c4fe21f37a9cb828bc3b" - }, - { - "algorithm": "SHA256", - "checksumValue": "f17e59f9d95eeb05694c02508aa284d332616c22cbe2e6a802d8a0710310eaab" - } - ], - "fileName": "Modules/expat/expat_config.h" - }, { "SPDXID": "SPDXRef-FILE-Modules-expat-expat-external.h", "checksums": [ @@ -104,11 +90,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "2790d37e7de2f13dccc4f4fb352cbdf9ed6abaa2" + "checksumValue": "9f6d9211a7b627785d5c48d10cc8eda66255113f" }, { "algorithm": "SHA256", - "checksumValue": "d2efe5a1018449968a689f444cca432e3d5875aba6ad08ee18ca235d64f41bb9" + "checksumValue": "9f0bdd346dd94ac4359c636a4e60bc768f4ae53ce0e836eb05fb9246ee36c7f2" } ], "fileName": "Modules/expat/internal.h" @@ -160,11 +146,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "2b984f806f10fbfbf72d8d1b7ba2992413c15299" + "checksumValue": "4c49b5df2bc702f663ba3b5a52d1940ec363226b" }, { "algorithm": "SHA256", - "checksumValue": "fbce56cd680e690043bbf572188cc2d0a25dbfc0d47ac8cb98eb3de768d4e694" + "checksumValue": "b5ec29f6560acc183f1ee8ab92bb3aea17b87b4c2120cd2e3f78deba7a12491e" } ], "fileName": "Modules/expat/siphash.h" @@ -188,11 +174,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "e774ae6ee9391aa6ffb8f775fb74e48f4b428959" + "checksumValue": "a3a8c44efd55dbf2cfea8fcee009ec63120ec0a3" }, { "algorithm": "SHA256", - "checksumValue": "3c71cea9a6174718542331971a35db317902b2433be9d8dd1cb24239b635c0cc" + "checksumValue": "e70948500d34dfcba4e9f0b305319dfe2a937c7cbfb687905128b56e1a6f8b33" } ], "fileName": "Modules/expat/winconfig.h" @@ -202,11 +188,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "b580e827e16baa6b035586ffcd4d90301e5a353f" + "checksumValue": "3b5de0ed1de33cad85b46230707403247f2851df" }, { "algorithm": "SHA256", - "checksumValue": "483518bbd69338eefc706cd7fc0b6039df2d3e347f64097989059ed6d2385a1e" + "checksumValue": "a03abd531601eef61a87e06113d218ff139b6969e15a3d4668cd85d65fc6f79b" } ], "fileName": "Modules/expat/xmlparse.c" @@ -216,11 +202,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "5ef21312af73deb2428be3fe97a65244608e76de" + "checksumValue": "ef767128d2dda99436712dcf3465dde5dbaab876" }, { "algorithm": "SHA256", - "checksumValue": "6fcf8c72ac0112c1b98bd2039c632a66b4c3dc516ce7c1f981390951121ef3c0" + "checksumValue": "71fb52aa302cf6f56e41943009965804f49ff2210d9bd15b258f70aaf70db772" } ], "fileName": "Modules/expat/xmlrole.c" @@ -230,11 +216,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "c1a4ea6356643d0820edb9c024c20ad2aaf562dc" + "checksumValue": "c961fb1a80f7b0601a63e69fba793fe5f6dff157" }, { "algorithm": "SHA256", - "checksumValue": "2b5d674be6ef20c7e3f69295176d75e68c5616e4dfce0a186fdd5e2ed8315f7a" + "checksumValue": "228470eb9181a9a7575b63137edcb61b817ee4e0923faffdbeba29e07c939713" } ], "fileName": "Modules/expat/xmlrole.h" @@ -244,11 +230,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "e6d66ae9fd61d7950c62c5d87693c30a707e8577" + "checksumValue": "8394790c0199c8f88108542ad78f23095d28a3fe" }, { "algorithm": "SHA256", - "checksumValue": "1110f651bdccfa765ad3d6f3857a35887ab35fc0fe7f3f3488fde2b238b482e3" + "checksumValue": "5b16c671ccc42496374762768e4bf48f614aecfd2025a07925b8d94244aec645" } ], "fileName": "Modules/expat/xmltok.c" @@ -258,11 +244,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "9c2a544875fd08ba9c2397296c97263518a410aa" + "checksumValue": "7d2943a0128094455004b1a98007b98734221bae" }, { "algorithm": "SHA256", - "checksumValue": "4299a03828b98bfe47ec6809f6e279252954a9a911dc7e0f19551bd74e3af971" + "checksumValue": "6b8919dc951606dc6f2b0175f8955a9ced901ce8bd08db47f291b6c04227ae7f" } ], "fileName": "Modules/expat/xmltok.h" @@ -272,11 +258,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "aa96882de8e3d1d3083124b595aa911efe44e5ad" + "checksumValue": "7756f7c0d3625ae7dde6cf7d386685ffacb57c7e" }, { "algorithm": "SHA256", - "checksumValue": "0fbcba7931707c60301305dab78d2298d96447d0a5513926d8b18135228c0818" + "checksumValue": "a3fe18ff32b21fbcb7c190895c68158404e1b9fb449db6431bc08b261dc03938" } ], "fileName": "Modules/expat/xmltok_impl.c" @@ -1590,14 +1576,14 @@ "checksums": [ { "algorithm": "SHA256", - "checksumValue": "6b902ab103843592be5e99504f846ec109c1abb692e85347587f237a4ffa1033" + "checksumValue": "a13447b9aa67d7c860783fdf6820f33ebdea996900d6d8bbc50a628f55f099f7" } ], - "downloadLocation": "https://github.com/libexpat/libexpat/releases/download/R_2_5_0/expat-2.5.0.tar.gz", + "downloadLocation": "https://github.com/libexpat/libexpat/releases/download/R_2_6_0/expat-2.6.0.tar.gz", "externalRefs": [ { "referenceCategory": "SECURITY", - "referenceLocator": "cpe:2.3:a:libexpat_project:libexpat:2.5.0:*:*:*:*:*:*:*", + "referenceLocator": "cpe:2.3:a:libexpat_project:libexpat:2.6.0:*:*:*:*:*:*:*", "referenceType": "cpe23Type" } ], @@ -1605,7 +1591,7 @@ "name": "expat", "originator": "Organization: Expat development team", "primaryPackagePurpose": "SOURCE", - "versionInfo": "2.5.0" + "versionInfo": "2.6.0" }, { "SPDXID": "SPDXRef-PACKAGE-hacl-star", @@ -2368,11 +2354,6 @@ "relationshipType": "CONTAINS", "spdxElementId": "SPDXRef-PACKAGE-expat" }, - { - "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-expat-config.h", - "relationshipType": "CONTAINS", - "spdxElementId": "SPDXRef-PACKAGE-expat" - }, { "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-expat-external.h", "relationshipType": "CONTAINS", diff --git a/Modules/expat/expat.h b/Modules/expat/expat.h index 1c83563cbf68e74..95464b0dd177351 100644 --- a/Modules/expat/expat.h +++ b/Modules/expat/expat.h @@ -11,11 +11,13 @@ Copyright (c) 2000-2005 Fred L. Drake, Jr. <fdrake@users.sourceforge.net> Copyright (c) 2001-2002 Greg Stein <gstein@users.sourceforge.net> Copyright (c) 2002-2016 Karl Waclawek <karl@waclawek.net> - Copyright (c) 2016-2022 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2024 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2016 Cristian Rodríguez <crrodriguez@opensuse.org> Copyright (c) 2016 Thomas Beutlich <tc@tbeu.de> Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk> Copyright (c) 2022 Thijs Schreijer <thijs@thijsschreijer.nl> + Copyright (c) 2023 Hanno Böck <hanno@gentoo.org> + Copyright (c) 2023 Sony Corporation / Snild Dolkow <snild@sony.com> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -269,7 +271,7 @@ XML_ParserCreate_MM(const XML_Char *encoding, const XML_Memory_Handling_Suite *memsuite, const XML_Char *namespaceSeparator); -/* Prepare a parser object to be re-used. This is particularly +/* Prepare a parser object to be reused. This is particularly valuable when memory allocation overhead is disproportionately high, such as when a large number of small documnents need to be parsed. All handlers are cleared from the parser, except for the @@ -951,7 +953,7 @@ XMLPARSEAPI(XML_Index) XML_GetCurrentByteIndex(XML_Parser parser); XMLPARSEAPI(int) XML_GetCurrentByteCount(XML_Parser parser); -/* If XML_CONTEXT_BYTES is defined, returns the input buffer, sets +/* If XML_CONTEXT_BYTES is >=1, returns the input buffer, sets the integer pointed to by offset to the offset within this buffer of the current parse position, and sets the integer pointed to by size to the size of this buffer (the number of input bytes). Otherwise @@ -1025,7 +1027,9 @@ enum XML_FeatureEnum { XML_FEATURE_ATTR_INFO, /* Added in Expat 2.4.0. */ XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_MAXIMUM_AMPLIFICATION_DEFAULT, - XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT + XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT, + /* Added in Expat 2.6.0. */ + XML_FEATURE_GE /* Additional features must be added to the end of this enum. */ }; @@ -1038,23 +1042,29 @@ typedef struct { XMLPARSEAPI(const XML_Feature *) XML_GetFeatureList(void); -#ifdef XML_DTD -/* Added in Expat 2.4.0. */ +#if XML_GE == 1 +/* Added in Expat 2.4.0 for XML_DTD defined and + * added in Expat 2.6.0 for XML_GE == 1. */ XMLPARSEAPI(XML_Bool) XML_SetBillionLaughsAttackProtectionMaximumAmplification( XML_Parser parser, float maximumAmplificationFactor); -/* Added in Expat 2.4.0. */ +/* Added in Expat 2.4.0 for XML_DTD defined and + * added in Expat 2.6.0 for XML_GE == 1. */ XMLPARSEAPI(XML_Bool) XML_SetBillionLaughsAttackProtectionActivationThreshold( XML_Parser parser, unsigned long long activationThresholdBytes); #endif +/* Added in Expat 2.6.0. */ +XMLPARSEAPI(XML_Bool) +XML_SetReparseDeferralEnabled(XML_Parser parser, XML_Bool enabled); + /* Expat follows the semantic versioning convention. - See http://semver.org. + See https://semver.org */ #define XML_MAJOR_VERSION 2 -#define XML_MINOR_VERSION 5 +#define XML_MINOR_VERSION 6 #define XML_MICRO_VERSION 0 #ifdef __cplusplus diff --git a/Modules/expat/expat_config.h b/Modules/expat/expat_config.h index 6671f7b689ba973..e7d9499d9078d95 100644 --- a/Modules/expat/expat_config.h +++ b/Modules/expat/expat_config.h @@ -16,6 +16,7 @@ #define XML_NS 1 #define XML_DTD 1 +#define XML_GE 1 #define XML_CONTEXT_BYTES 1024 // bpo-30947: Python uses best available entropy sources to diff --git a/Modules/expat/internal.h b/Modules/expat/internal.h index e09f533b23c9df0..cce71e4c5164b51 100644 --- a/Modules/expat/internal.h +++ b/Modules/expat/internal.h @@ -28,9 +28,10 @@ Copyright (c) 2002-2003 Fred L. Drake, Jr. <fdrake@users.sourceforge.net> Copyright (c) 2002-2006 Karl Waclawek <karl@waclawek.net> Copyright (c) 2003 Greg Stein <gstein@users.sourceforge.net> - Copyright (c) 2016-2022 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2023 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2018 Yury Gribov <tetra2005@gmail.com> Copyright (c) 2019 David Loffredo <loffredo@steptools.com> + Copyright (c) 2023 Sony Corporation / Snild Dolkow <snild@sony.com> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -154,12 +155,15 @@ extern "C" { void _INTERNAL_trim_to_complete_utf8_characters(const char *from, const char **fromLimRef); -#if defined(XML_DTD) +#if XML_GE == 1 unsigned long long testingAccountingGetCountBytesDirect(XML_Parser parser); unsigned long long testingAccountingGetCountBytesIndirect(XML_Parser parser); const char *unsignedCharToPrintable(unsigned char c); #endif +extern XML_Bool g_reparseDeferralEnabledDefault; // written ONLY in runtests.c +extern unsigned int g_parseAttempts; // used for testing only + #ifdef __cplusplus } #endif diff --git a/Modules/expat/siphash.h b/Modules/expat/siphash.h index 303283ad2de98d0..a1ed99e687bd6ee 100644 --- a/Modules/expat/siphash.h +++ b/Modules/expat/siphash.h @@ -106,7 +106,7 @@ * if this code is included and compiled as C++; related GCC warning is: * warning: use of C++11 long long integer constant [-Wlong-long] */ -#define _SIP_ULL(high, low) ((((uint64_t)high) << 32) | (low)) +#define SIP_ULL(high, low) ((((uint64_t)high) << 32) | (low)) #define SIP_ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) @@ -190,10 +190,10 @@ sip_round(struct siphash *H, const int rounds) { static struct siphash * sip24_init(struct siphash *H, const struct sipkey *key) { - H->v0 = _SIP_ULL(0x736f6d65U, 0x70736575U) ^ key->k[0]; - H->v1 = _SIP_ULL(0x646f7261U, 0x6e646f6dU) ^ key->k[1]; - H->v2 = _SIP_ULL(0x6c796765U, 0x6e657261U) ^ key->k[0]; - H->v3 = _SIP_ULL(0x74656462U, 0x79746573U) ^ key->k[1]; + H->v0 = SIP_ULL(0x736f6d65U, 0x70736575U) ^ key->k[0]; + H->v1 = SIP_ULL(0x646f7261U, 0x6e646f6dU) ^ key->k[1]; + H->v2 = SIP_ULL(0x6c796765U, 0x6e657261U) ^ key->k[0]; + H->v3 = SIP_ULL(0x74656462U, 0x79746573U) ^ key->k[1]; H->p = H->buf; H->c = 0; diff --git a/Modules/expat/winconfig.h b/Modules/expat/winconfig.h index 2ecd61b5b94820c..05805514ec7fa21 100644 --- a/Modules/expat/winconfig.h +++ b/Modules/expat/winconfig.h @@ -9,7 +9,8 @@ Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net> Copyright (c) 2002 Greg Stein <gstein@users.sourceforge.net> Copyright (c) 2005 Karl Waclawek <karl@waclawek.net> - Copyright (c) 2017-2021 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2017-2023 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2023 Orgad Shaneh <orgad.shaneh@audiocodes.com> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -35,7 +36,9 @@ #ifndef WINCONFIG_H #define WINCONFIG_H -#define WIN32_LEAN_AND_MEAN +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif #include <windows.h> #undef WIN32_LEAN_AND_MEAN diff --git a/Modules/expat/xmlparse.c b/Modules/expat/xmlparse.c index b6c2eca97567baa..aaf0fa9c8f96d1e 100644 --- a/Modules/expat/xmlparse.c +++ b/Modules/expat/xmlparse.c @@ -1,4 +1,4 @@ -/* 5ab094ffadd6edfc94c3eee53af44a86951f9f1f0933ada3114bbce2bfb02c99 (2.5.0+) +/* 628e24d4966bedbd4800f6ed128d06d29703765b4bce12d3b7f099f90f842fc9 (2.6.0+) __ __ _ ___\ \/ /_ __ __ _| |_ / _ \\ /| '_ \ / _` | __| @@ -13,7 +13,7 @@ Copyright (c) 2002-2016 Karl Waclawek <karl@waclawek.net> Copyright (c) 2005-2009 Steven Solie <steven@solie.ca> Copyright (c) 2016 Eric Rahm <erahm@mozilla.com> - Copyright (c) 2016-2022 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2024 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2016 Gaurav <g.gupta@samsung.com> Copyright (c) 2016 Thomas Beutlich <tc@tbeu.de> Copyright (c) 2016 Gustavo Grieco <gustavo.grieco@imag.fr> @@ -32,10 +32,13 @@ Copyright (c) 2019 David Loffredo <loffredo@steptools.com> Copyright (c) 2019-2020 Ben Wagner <bungeman@chromium.org> Copyright (c) 2019 Vadim Zeitlin <vadim@zeitlins.org> - Copyright (c) 2021 Dong-hee Na <donghee.na@python.org> + Copyright (c) 2021 Donghee Na <donghee.na@python.org> Copyright (c) 2022 Samanta Navarro <ferivoz@riseup.net> Copyright (c) 2022 Jeffrey Walton <noloader@gmail.com> Copyright (c) 2022 Jann Horn <jannh@google.com> + Copyright (c) 2022 Sean McBride <sean@rogue-research.com> + Copyright (c) 2023 Owain Davies <owaind@bath.edu> + Copyright (c) 2023 Sony Corporation / Snild Dolkow <snild@sony.com> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -60,10 +63,25 @@ #define XML_BUILDING_EXPAT 1 -#include <expat_config.h> +#include "expat_config.h" -#if ! defined(_GNU_SOURCE) -# define _GNU_SOURCE 1 /* syscall prototype */ +#if ! defined(XML_GE) || (1 - XML_GE - 1 == 2) || (XML_GE < 0) || (XML_GE > 1) +# error XML_GE (for general entities) must be defined, non-empty, either 1 or 0 (0 to disable, 1 to enable; 1 is a common default) +#endif + +#if defined(XML_DTD) && XML_GE == 0 +# error Either undefine XML_DTD or define XML_GE to 1. +#endif + +#if ! defined(XML_CONTEXT_BYTES) || (1 - XML_CONTEXT_BYTES - 1 == 2) \ + || (XML_CONTEXT_BYTES + 0 < 0) +# error XML_CONTEXT_BYTES must be defined, non-empty and >=0 (0 to disable, >=1 to enable; 1024 is a common default) +#endif + +#if defined(HAVE_SYSCALL_GETRANDOM) +# if ! defined(_GNU_SOURCE) +# define _GNU_SOURCE 1 /* syscall prototype */ +# endif #endif #ifdef _WIN32 @@ -73,6 +91,7 @@ # endif #endif +#include <stdbool.h> #include <stddef.h> #include <string.h> /* memset(), memcpy() */ #include <assert.h> @@ -131,8 +150,8 @@ Your options include: \ * Linux >=3.17 + glibc >=2.25 (getrandom): HAVE_GETRANDOM, \ * Linux >=3.17 + glibc (including <2.25) (syscall SYS_getrandom): HAVE_SYSCALL_GETRANDOM, \ - * BSD / macOS >=10.7 (arc4random_buf): HAVE_ARC4RANDOM_BUF, \ - * BSD / macOS (including <10.7) (arc4random): HAVE_ARC4RANDOM, \ + * BSD / macOS >=10.7 / glibc >=2.36 (arc4random_buf): HAVE_ARC4RANDOM_BUF, \ + * BSD / macOS (including <10.7) / glibc >=2.36 (arc4random): HAVE_ARC4RANDOM, \ * libbsd (arc4random_buf): HAVE_ARC4RANDOM_BUF + HAVE_LIBBSD, \ * libbsd (arc4random): HAVE_ARC4RANDOM + HAVE_LIBBSD, \ * Linux (including <3.17) / BSD / macOS (including <10.7) / Solaris >=8 (/dev/urandom): XML_DEV_URANDOM, \ @@ -196,6 +215,8 @@ typedef char ICHAR; /* Do safe (NULL-aware) pointer arithmetic */ #define EXPAT_SAFE_PTR_DIFF(p, q) (((p) && (q)) ? ((p) - (q)) : 0) +#define EXPAT_MIN(a, b) (((a) < (b)) ? (a) : (b)) + #include "internal.h" #include "xmltok.h" #include "xmlrole.h" @@ -279,7 +300,7 @@ typedef struct { XML_Parse()/XML_ParseBuffer(), the buffer is re-allocated to contain the 'raw' name as well. - A parser re-uses these structures, maintaining a list of allocated + A parser reuses these structures, maintaining a list of allocated TAG objects in a free list. */ typedef struct tag { @@ -408,12 +429,12 @@ enum XML_Account { XML_ACCOUNT_NONE /* i.e. do not account, was accounted already */ }; -#ifdef XML_DTD +#if XML_GE == 1 typedef unsigned long long XmlBigCount; typedef struct accounting { XmlBigCount countBytesDirect; XmlBigCount countBytesIndirect; - int debugLevel; + unsigned long debugLevel; float maximumAmplificationFactor; // >=1.0 unsigned long long activationThresholdBytes; } ACCOUNTING; @@ -422,9 +443,9 @@ typedef struct entity_stats { unsigned int countEverOpened; unsigned int currentDepth; unsigned int maximumDepthSeen; - int debugLevel; + unsigned long debugLevel; } ENTITY_STATS; -#endif /* XML_DTD */ +#endif /* XML_GE == 1 */ typedef enum XML_Error PTRCALL Processor(XML_Parser parser, const char *start, const char *end, const char **endPtr); @@ -464,41 +485,47 @@ static enum XML_Error doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, const char *start, const char *end, const char **endPtr, XML_Bool haveMore, enum XML_Account account); -static enum XML_Error doCdataSection(XML_Parser parser, const ENCODING *, +static enum XML_Error doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, const char *end, const char **nextPtr, XML_Bool haveMore, enum XML_Account account); #ifdef XML_DTD -static enum XML_Error doIgnoreSection(XML_Parser parser, const ENCODING *, +static enum XML_Error doIgnoreSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, const char *end, const char **nextPtr, XML_Bool haveMore); #endif /* XML_DTD */ static void freeBindings(XML_Parser parser, BINDING *bindings); -static enum XML_Error storeAtts(XML_Parser parser, const ENCODING *, - const char *s, TAG_NAME *tagNamePtr, +static enum XML_Error storeAtts(XML_Parser parser, const ENCODING *enc, + const char *attStr, TAG_NAME *tagNamePtr, BINDING **bindingsPtr, enum XML_Account account); static enum XML_Error addBinding(XML_Parser parser, PREFIX *prefix, const ATTRIBUTE_ID *attId, const XML_Char *uri, BINDING **bindingsPtr); -static int defineAttribute(ELEMENT_TYPE *type, ATTRIBUTE_ID *, XML_Bool isCdata, - XML_Bool isId, const XML_Char *dfltValue, - XML_Parser parser); -static enum XML_Error storeAttributeValue(XML_Parser parser, const ENCODING *, - XML_Bool isCdata, const char *, - const char *, STRING_POOL *, +static int defineAttribute(ELEMENT_TYPE *type, ATTRIBUTE_ID *attId, + XML_Bool isCdata, XML_Bool isId, + const XML_Char *value, XML_Parser parser); +static enum XML_Error storeAttributeValue(XML_Parser parser, + const ENCODING *enc, XML_Bool isCdata, + const char *ptr, const char *end, + STRING_POOL *pool, enum XML_Account account); -static enum XML_Error appendAttributeValue(XML_Parser parser, const ENCODING *, - XML_Bool isCdata, const char *, - const char *, STRING_POOL *, +static enum XML_Error appendAttributeValue(XML_Parser parser, + const ENCODING *enc, + XML_Bool isCdata, const char *ptr, + const char *end, STRING_POOL *pool, enum XML_Account account); static ATTRIBUTE_ID *getAttributeId(XML_Parser parser, const ENCODING *enc, const char *start, const char *end); -static int setElementTypePrefix(XML_Parser parser, ELEMENT_TYPE *); +static int setElementTypePrefix(XML_Parser parser, ELEMENT_TYPE *elementType); +#if XML_GE == 1 static enum XML_Error storeEntityValue(XML_Parser parser, const ENCODING *enc, const char *start, const char *end, enum XML_Account account); +#else +static enum XML_Error storeSelfEntityValue(XML_Parser parser, ENTITY *entity); +#endif static int reportProcessingInstruction(XML_Parser parser, const ENCODING *enc, const char *start, const char *end); static int reportComment(XML_Parser parser, const ENCODING *enc, @@ -518,21 +545,22 @@ static void dtdDestroy(DTD *p, XML_Bool isDocEntity, const XML_Memory_Handling_Suite *ms); static int dtdCopy(XML_Parser oldParser, DTD *newDtd, const DTD *oldDtd, const XML_Memory_Handling_Suite *ms); -static int copyEntityTable(XML_Parser oldParser, HASH_TABLE *, STRING_POOL *, - const HASH_TABLE *); +static int copyEntityTable(XML_Parser oldParser, HASH_TABLE *newTable, + STRING_POOL *newPool, const HASH_TABLE *oldTable); static NAMED *lookup(XML_Parser parser, HASH_TABLE *table, KEY name, size_t createSize); -static void FASTCALL hashTableInit(HASH_TABLE *, +static void FASTCALL hashTableInit(HASH_TABLE *table, const XML_Memory_Handling_Suite *ms); -static void FASTCALL hashTableClear(HASH_TABLE *); -static void FASTCALL hashTableDestroy(HASH_TABLE *); -static void FASTCALL hashTableIterInit(HASH_TABLE_ITER *, const HASH_TABLE *); -static NAMED *FASTCALL hashTableIterNext(HASH_TABLE_ITER *); +static void FASTCALL hashTableClear(HASH_TABLE *table); +static void FASTCALL hashTableDestroy(HASH_TABLE *table); +static void FASTCALL hashTableIterInit(HASH_TABLE_ITER *iter, + const HASH_TABLE *table); +static NAMED *FASTCALL hashTableIterNext(HASH_TABLE_ITER *iter); -static void FASTCALL poolInit(STRING_POOL *, +static void FASTCALL poolInit(STRING_POOL *pool, const XML_Memory_Handling_Suite *ms); -static void FASTCALL poolClear(STRING_POOL *); -static void FASTCALL poolDestroy(STRING_POOL *); +static void FASTCALL poolClear(STRING_POOL *pool); +static void FASTCALL poolDestroy(STRING_POOL *pool); static XML_Char *poolAppend(STRING_POOL *pool, const ENCODING *enc, const char *ptr, const char *end); static XML_Char *poolStoreString(STRING_POOL *pool, const ENCODING *enc, @@ -562,7 +590,7 @@ static XML_Parser parserCreate(const XML_Char *encodingName, static void parserInit(XML_Parser parser, const XML_Char *encodingName); -#ifdef XML_DTD +#if XML_GE == 1 static float accountingGetCurrentAmplification(XML_Parser rootParser); static void accountingReportStats(XML_Parser originParser, const char *epilog); static void accountingOnAbort(XML_Parser originParser); @@ -585,13 +613,12 @@ static void entityTrackingOnClose(XML_Parser parser, ENTITY *entity, static XML_Parser getRootParserOf(XML_Parser parser, unsigned int *outLevelDiff); -#endif /* XML_DTD */ +#endif /* XML_GE == 1 */ static unsigned long getDebugLevel(const char *variableName, unsigned long defaultDebugLevel); #define poolStart(pool) ((pool)->start) -#define poolEnd(pool) ((pool)->ptr) #define poolLength(pool) ((pool)->ptr - (pool)->start) #define poolChop(pool) ((void)--(pool->ptr)) #define poolLastChar(pool) (((pool)->ptr)[-1]) @@ -602,21 +629,35 @@ static unsigned long getDebugLevel(const char *variableName, ? 0 \ : ((*((pool)->ptr)++ = c), 1)) +XML_Bool g_reparseDeferralEnabledDefault = XML_TRUE; // write ONLY in runtests.c +unsigned int g_parseAttempts = 0; // used for testing only + struct XML_ParserStruct { /* The first member must be m_userData so that the XML_GetUserData macro works. */ void *m_userData; void *m_handlerArg; - char *m_buffer; + + // How the four parse buffer pointers below relate in time and space: + // + // m_buffer <= m_bufferPtr <= m_bufferEnd <= m_bufferLim + // | | | | + // <--parsed-->| | | + // <---parsing--->| | + // <--unoccupied-->| + // <---------total-malloced/realloced-------->| + + char *m_buffer; // malloc/realloc base pointer of parse buffer const XML_Memory_Handling_Suite m_mem; - /* first character to be parsed */ - const char *m_bufferPtr; - /* past last character to be parsed */ - char *m_bufferEnd; - /* allocated end of m_buffer */ - const char *m_bufferLim; + const char *m_bufferPtr; // first character to be parsed + char *m_bufferEnd; // past last character to be parsed + const char *m_bufferLim; // allocated end of m_buffer + XML_Index m_parseEndByteIndex; const char *m_parseEndPtr; + size_t m_partialTokenBytesBefore; /* used in heuristic to avoid O(n^2) */ + XML_Bool m_reparseDeferralEnabled; + int m_lastBufferRequestSize; XML_Char *m_dataBuf; XML_Char *m_dataBufEnd; XML_StartElementHandler m_startElementHandler; @@ -703,7 +744,7 @@ struct XML_ParserStruct { enum XML_ParamEntityParsing m_paramEntityParsing; #endif unsigned long m_hash_secret_salt; -#ifdef XML_DTD +#if XML_GE == 1 ACCOUNTING m_accounting; ENTITY_STATS m_entity_stats; #endif @@ -948,6 +989,47 @@ get_hash_secret_salt(XML_Parser parser) { return parser->m_hash_secret_salt; } +static enum XML_Error +callProcessor(XML_Parser parser, const char *start, const char *end, + const char **endPtr) { + const size_t have_now = EXPAT_SAFE_PTR_DIFF(end, start); + + if (parser->m_reparseDeferralEnabled + && ! parser->m_parsingStatus.finalBuffer) { + // Heuristic: don't try to parse a partial token again until the amount of + // available data has increased significantly. + const size_t had_before = parser->m_partialTokenBytesBefore; + // ...but *do* try anyway if we're close to causing a reallocation. + size_t available_buffer + = EXPAT_SAFE_PTR_DIFF(parser->m_bufferPtr, parser->m_buffer); +#if XML_CONTEXT_BYTES > 0 + available_buffer -= EXPAT_MIN(available_buffer, XML_CONTEXT_BYTES); +#endif + available_buffer + += EXPAT_SAFE_PTR_DIFF(parser->m_bufferLim, parser->m_bufferEnd); + // m_lastBufferRequestSize is never assigned a value < 0, so the cast is ok + const bool enough + = (have_now >= 2 * had_before) + || ((size_t)parser->m_lastBufferRequestSize > available_buffer); + + if (! enough) { + *endPtr = start; // callers may expect this to be set + return XML_ERROR_NONE; + } + } + g_parseAttempts += 1; + const enum XML_Error ret = parser->m_processor(parser, start, end, endPtr); + if (ret == XML_ERROR_NONE) { + // if we consumed nothing, remember what we had on this parse attempt. + if (*endPtr == start) { + parser->m_partialTokenBytesBefore = have_now; + } else { + parser->m_partialTokenBytesBefore = 0; + } + } + return ret; +} + static XML_Bool /* only valid for root parser */ startParsing(XML_Parser parser) { /* hash functions must be initialized before setContext() is called */ @@ -1129,6 +1211,9 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) { parser->m_bufferEnd = parser->m_buffer; parser->m_parseEndByteIndex = 0; parser->m_parseEndPtr = NULL; + parser->m_partialTokenBytesBefore = 0; + parser->m_reparseDeferralEnabled = g_reparseDeferralEnabledDefault; + parser->m_lastBufferRequestSize = 0; parser->m_declElementType = NULL; parser->m_declAttributeId = NULL; parser->m_declEntity = NULL; @@ -1163,7 +1248,7 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) { #endif parser->m_hash_secret_salt = 0; -#ifdef XML_DTD +#if XML_GE == 1 memset(&parser->m_accounting, 0, sizeof(ACCOUNTING)); parser->m_accounting.debugLevel = getDebugLevel("EXPAT_ACCOUNTING_DEBUG", 0u); parser->m_accounting.maximumAmplificationFactor @@ -1298,6 +1383,7 @@ XML_ExternalEntityParserCreate(XML_Parser oldParser, const XML_Char *context, to worry which hash secrets each table has. */ unsigned long oldhash_secret_salt; + XML_Bool oldReparseDeferralEnabled; /* Validate the oldParser parameter before we pull everything out of it */ if (oldParser == NULL) @@ -1342,6 +1428,7 @@ XML_ExternalEntityParserCreate(XML_Parser oldParser, const XML_Char *context, to worry which hash secrets each table has. */ oldhash_secret_salt = parser->m_hash_secret_salt; + oldReparseDeferralEnabled = parser->m_reparseDeferralEnabled; #ifdef XML_DTD if (! context) @@ -1394,6 +1481,7 @@ XML_ExternalEntityParserCreate(XML_Parser oldParser, const XML_Char *context, parser->m_defaultExpandInternalEntities = oldDefaultExpandInternalEntities; parser->m_ns_triplets = oldns_triplets; parser->m_hash_secret_salt = oldhash_secret_salt; + parser->m_reparseDeferralEnabled = oldReparseDeferralEnabled; parser->m_parentParser = oldParser; #ifdef XML_DTD parser->m_paramEntityParsing = oldParamEntityParsing; @@ -1848,55 +1936,8 @@ XML_Parse(XML_Parser parser, const char *s, int len, int isFinal) { parser->m_parsingStatus.parsing = XML_PARSING; } - if (len == 0) { - parser->m_parsingStatus.finalBuffer = (XML_Bool)isFinal; - if (! isFinal) - return XML_STATUS_OK; - parser->m_positionPtr = parser->m_bufferPtr; - parser->m_parseEndPtr = parser->m_bufferEnd; - - /* If data are left over from last buffer, and we now know that these - data are the final chunk of input, then we have to check them again - to detect errors based on that fact. - */ - parser->m_errorCode - = parser->m_processor(parser, parser->m_bufferPtr, - parser->m_parseEndPtr, &parser->m_bufferPtr); - - if (parser->m_errorCode == XML_ERROR_NONE) { - switch (parser->m_parsingStatus.parsing) { - case XML_SUSPENDED: - /* It is hard to be certain, but it seems that this case - * cannot occur. This code is cleaning up a previous parse - * with no new data (since len == 0). Changing the parsing - * state requires getting to execute a handler function, and - * there doesn't seem to be an opportunity for that while in - * this circumstance. - * - * Given the uncertainty, we retain the code but exclude it - * from coverage tests. - * - * LCOV_EXCL_START - */ - XmlUpdatePosition(parser->m_encoding, parser->m_positionPtr, - parser->m_bufferPtr, &parser->m_position); - parser->m_positionPtr = parser->m_bufferPtr; - return XML_STATUS_SUSPENDED; - /* LCOV_EXCL_STOP */ - case XML_INITIALIZED: - case XML_PARSING: - parser->m_parsingStatus.parsing = XML_FINISHED; - /* fall through */ - default: - return XML_STATUS_OK; - } - } - parser->m_eventEndPtr = parser->m_eventPtr; - parser->m_processor = errorProcessor; - return XML_STATUS_ERROR; - } -#ifndef XML_CONTEXT_BYTES - else if (parser->m_bufferPtr == parser->m_bufferEnd) { +#if XML_CONTEXT_BYTES == 0 + if (parser->m_bufferPtr == parser->m_bufferEnd) { const char *end; int nLeftOver; enum XML_Status result; @@ -1907,12 +1948,15 @@ XML_Parse(XML_Parser parser, const char *s, int len, int isFinal) { parser->m_processor = errorProcessor; return XML_STATUS_ERROR; } + // though this isn't a buffer request, we assume that `len` is the app's + // preferred buffer fill size, and therefore save it here. + parser->m_lastBufferRequestSize = len; parser->m_parseEndByteIndex += len; parser->m_positionPtr = s; parser->m_parsingStatus.finalBuffer = (XML_Bool)isFinal; parser->m_errorCode - = parser->m_processor(parser, s, parser->m_parseEndPtr = s + len, &end); + = callProcessor(parser, s, parser->m_parseEndPtr = s + len, &end); if (parser->m_errorCode != XML_ERROR_NONE) { parser->m_eventEndPtr = parser->m_eventPtr; @@ -1939,23 +1983,25 @@ XML_Parse(XML_Parser parser, const char *s, int len, int isFinal) { &parser->m_position); nLeftOver = s + len - end; if (nLeftOver) { - if (parser->m_buffer == NULL - || nLeftOver > parser->m_bufferLim - parser->m_buffer) { - /* avoid _signed_ integer overflow */ - char *temp = NULL; - const int bytesToAllocate = (int)((unsigned)len * 2U); - if (bytesToAllocate > 0) { - temp = (char *)REALLOC(parser, parser->m_buffer, bytesToAllocate); - } - if (temp == NULL) { - parser->m_errorCode = XML_ERROR_NO_MEMORY; - parser->m_eventPtr = parser->m_eventEndPtr = NULL; - parser->m_processor = errorProcessor; - return XML_STATUS_ERROR; - } - parser->m_buffer = temp; - parser->m_bufferLim = parser->m_buffer + bytesToAllocate; + // Back up and restore the parsing status to avoid XML_ERROR_SUSPENDED + // (and XML_ERROR_FINISHED) from XML_GetBuffer. + const enum XML_Parsing originalStatus = parser->m_parsingStatus.parsing; + parser->m_parsingStatus.parsing = XML_PARSING; + void *const temp = XML_GetBuffer(parser, nLeftOver); + parser->m_parsingStatus.parsing = originalStatus; + // GetBuffer may have overwritten this, but we want to remember what the + // app requested, not how many bytes were left over after parsing. + parser->m_lastBufferRequestSize = len; + if (temp == NULL) { + // NOTE: parser->m_errorCode has already been set by XML_GetBuffer(). + parser->m_eventPtr = parser->m_eventEndPtr = NULL; + parser->m_processor = errorProcessor; + return XML_STATUS_ERROR; } + // Since we know that the buffer was empty and XML_CONTEXT_BYTES is 0, we + // don't have any data to preserve, and can copy straight into the start + // of the buffer rather than the GetBuffer return pointer (which may be + // pointing further into the allocated buffer). memcpy(parser->m_buffer, end, nLeftOver); } parser->m_bufferPtr = parser->m_buffer; @@ -1966,16 +2012,15 @@ XML_Parse(XML_Parser parser, const char *s, int len, int isFinal) { parser->m_eventEndPtr = parser->m_bufferPtr; return result; } -#endif /* not defined XML_CONTEXT_BYTES */ - else { - void *buff = XML_GetBuffer(parser, len); - if (buff == NULL) - return XML_STATUS_ERROR; - else { - memcpy(buff, s, len); - return XML_ParseBuffer(parser, len, isFinal); - } +#endif /* XML_CONTEXT_BYTES == 0 */ + void *buff = XML_GetBuffer(parser, len); + if (buff == NULL) + return XML_STATUS_ERROR; + if (len > 0) { + assert(s != NULL); // make sure s==NULL && len!=0 was rejected above + memcpy(buff, s, len); } + return XML_ParseBuffer(parser, len, isFinal); } enum XML_Status XMLCALL @@ -2015,8 +2060,8 @@ XML_ParseBuffer(XML_Parser parser, int len, int isFinal) { parser->m_parseEndByteIndex += len; parser->m_parsingStatus.finalBuffer = (XML_Bool)isFinal; - parser->m_errorCode = parser->m_processor( - parser, start, parser->m_parseEndPtr, &parser->m_bufferPtr); + parser->m_errorCode = callProcessor(parser, start, parser->m_parseEndPtr, + &parser->m_bufferPtr); if (parser->m_errorCode != XML_ERROR_NONE) { parser->m_eventEndPtr = parser->m_eventPtr; @@ -2061,10 +2106,14 @@ XML_GetBuffer(XML_Parser parser, int len) { default:; } - if (len > EXPAT_SAFE_PTR_DIFF(parser->m_bufferLim, parser->m_bufferEnd)) { -#ifdef XML_CONTEXT_BYTES + // whether or not the request succeeds, `len` seems to be the app's preferred + // buffer fill size; remember it. + parser->m_lastBufferRequestSize = len; + if (len > EXPAT_SAFE_PTR_DIFF(parser->m_bufferLim, parser->m_bufferEnd) + || parser->m_buffer == NULL) { +#if XML_CONTEXT_BYTES > 0 int keep; -#endif /* defined XML_CONTEXT_BYTES */ +#endif /* XML_CONTEXT_BYTES > 0 */ /* Do not invoke signed arithmetic overflow: */ int neededSize = (int)((unsigned)len + (unsigned)EXPAT_SAFE_PTR_DIFF( @@ -2073,7 +2122,7 @@ XML_GetBuffer(XML_Parser parser, int len) { parser->m_errorCode = XML_ERROR_NO_MEMORY; return NULL; } -#ifdef XML_CONTEXT_BYTES +#if XML_CONTEXT_BYTES > 0 keep = (int)EXPAT_SAFE_PTR_DIFF(parser->m_bufferPtr, parser->m_buffer); if (keep > XML_CONTEXT_BYTES) keep = XML_CONTEXT_BYTES; @@ -2083,10 +2132,11 @@ XML_GetBuffer(XML_Parser parser, int len) { return NULL; } neededSize += keep; -#endif /* defined XML_CONTEXT_BYTES */ - if (neededSize - <= EXPAT_SAFE_PTR_DIFF(parser->m_bufferLim, parser->m_buffer)) { -#ifdef XML_CONTEXT_BYTES +#endif /* XML_CONTEXT_BYTES > 0 */ + if (parser->m_buffer && parser->m_bufferPtr + && neededSize + <= EXPAT_SAFE_PTR_DIFF(parser->m_bufferLim, parser->m_buffer)) { +#if XML_CONTEXT_BYTES > 0 if (keep < EXPAT_SAFE_PTR_DIFF(parser->m_bufferPtr, parser->m_buffer)) { int offset = (int)EXPAT_SAFE_PTR_DIFF(parser->m_bufferPtr, parser->m_buffer) @@ -2099,19 +2149,17 @@ XML_GetBuffer(XML_Parser parser, int len) { parser->m_bufferPtr -= offset; } #else - if (parser->m_buffer && parser->m_bufferPtr) { - memmove(parser->m_buffer, parser->m_bufferPtr, - EXPAT_SAFE_PTR_DIFF(parser->m_bufferEnd, parser->m_bufferPtr)); - parser->m_bufferEnd - = parser->m_buffer - + EXPAT_SAFE_PTR_DIFF(parser->m_bufferEnd, parser->m_bufferPtr); - parser->m_bufferPtr = parser->m_buffer; - } -#endif /* not defined XML_CONTEXT_BYTES */ + memmove(parser->m_buffer, parser->m_bufferPtr, + EXPAT_SAFE_PTR_DIFF(parser->m_bufferEnd, parser->m_bufferPtr)); + parser->m_bufferEnd + = parser->m_buffer + + EXPAT_SAFE_PTR_DIFF(parser->m_bufferEnd, parser->m_bufferPtr); + parser->m_bufferPtr = parser->m_buffer; +#endif /* XML_CONTEXT_BYTES > 0 */ } else { char *newBuf; int bufferSize - = (int)EXPAT_SAFE_PTR_DIFF(parser->m_bufferLim, parser->m_bufferPtr); + = (int)EXPAT_SAFE_PTR_DIFF(parser->m_bufferLim, parser->m_buffer); if (bufferSize == 0) bufferSize = INIT_BUFFER_SIZE; do { @@ -2128,7 +2176,7 @@ XML_GetBuffer(XML_Parser parser, int len) { return NULL; } parser->m_bufferLim = newBuf + bufferSize; -#ifdef XML_CONTEXT_BYTES +#if XML_CONTEXT_BYTES > 0 if (parser->m_bufferPtr) { memcpy(newBuf, &parser->m_bufferPtr[-keep], EXPAT_SAFE_PTR_DIFF(parser->m_bufferEnd, parser->m_bufferPtr) @@ -2158,7 +2206,7 @@ XML_GetBuffer(XML_Parser parser, int len) { parser->m_bufferEnd = newBuf; } parser->m_bufferPtr = parser->m_buffer = newBuf; -#endif /* not defined XML_CONTEXT_BYTES */ +#endif /* XML_CONTEXT_BYTES > 0 */ } parser->m_eventPtr = parser->m_eventEndPtr = NULL; parser->m_positionPtr = NULL; @@ -2208,7 +2256,7 @@ XML_ResumeParser(XML_Parser parser) { } parser->m_parsingStatus.parsing = XML_PARSING; - parser->m_errorCode = parser->m_processor( + parser->m_errorCode = callProcessor( parser, parser->m_bufferPtr, parser->m_parseEndPtr, &parser->m_bufferPtr); if (parser->m_errorCode != XML_ERROR_NONE) { @@ -2272,7 +2320,7 @@ XML_GetCurrentByteCount(XML_Parser parser) { const char *XMLCALL XML_GetInputContext(XML_Parser parser, int *offset, int *size) { -#ifdef XML_CONTEXT_BYTES +#if XML_CONTEXT_BYTES > 0 if (parser == NULL) return NULL; if (parser->m_eventPtr && parser->m_buffer) { @@ -2286,7 +2334,7 @@ XML_GetInputContext(XML_Parser parser, int *offset, int *size) { (void)parser; (void)offset; (void)size; -#endif /* defined XML_CONTEXT_BYTES */ +#endif /* XML_CONTEXT_BYTES > 0 */ return (const char *)0; } @@ -2506,7 +2554,7 @@ XML_GetFeatureList(void) { #ifdef XML_DTD {XML_FEATURE_DTD, XML_L("XML_DTD"), 0}, #endif -#ifdef XML_CONTEXT_BYTES +#if XML_CONTEXT_BYTES > 0 {XML_FEATURE_CONTEXT_BYTES, XML_L("XML_CONTEXT_BYTES"), XML_CONTEXT_BYTES}, #endif @@ -2522,8 +2570,9 @@ XML_GetFeatureList(void) { #ifdef XML_ATTR_INFO {XML_FEATURE_ATTR_INFO, XML_L("XML_ATTR_INFO"), 0}, #endif -#ifdef XML_DTD - /* Added in Expat 2.4.0. */ +#if XML_GE == 1 + /* Added in Expat 2.4.0 for XML_DTD defined and + * added in Expat 2.6.0 for XML_GE == 1. */ {XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_MAXIMUM_AMPLIFICATION_DEFAULT, XML_L("XML_BLAP_MAX_AMP"), (long int) @@ -2531,13 +2580,15 @@ XML_GetFeatureList(void) { {XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT, XML_L("XML_BLAP_ACT_THRES"), EXPAT_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT}, + /* Added in Expat 2.6.0. */ + {XML_FEATURE_GE, XML_L("XML_GE"), 0}, #endif {XML_FEATURE_END, NULL, 0}}; return features; } -#ifdef XML_DTD +#if XML_GE == 1 XML_Bool XMLCALL XML_SetBillionLaughsAttackProtectionMaximumAmplification( XML_Parser parser, float maximumAmplificationFactor) { @@ -2559,7 +2610,16 @@ XML_SetBillionLaughsAttackProtectionActivationThreshold( parser->m_accounting.activationThresholdBytes = activationThresholdBytes; return XML_TRUE; } -#endif /* XML_DTD */ +#endif /* XML_GE == 1 */ + +XML_Bool XMLCALL +XML_SetReparseDeferralEnabled(XML_Parser parser, XML_Bool enabled) { + if (parser != NULL && (enabled == XML_TRUE || enabled == XML_FALSE)) { + parser->m_reparseDeferralEnabled = enabled; + return XML_TRUE; + } + return XML_FALSE; +} /* Initially tag->rawName always points into the parse buffer; for those TAG instances opened while the current parse buffer was @@ -2581,7 +2641,7 @@ storeRawNames(XML_Parser parser) { */ if (tag->rawName == rawNameBuf) break; - /* For re-use purposes we need to ensure that the + /* For reuse purposes we need to ensure that the size of tag->buf is a multiple of sizeof(XML_Char). */ rawNameLen = ROUND_UP(tag->rawNameLength, sizeof(XML_Char)); @@ -2645,13 +2705,13 @@ externalEntityInitProcessor2(XML_Parser parser, const char *start, int tok = XmlContentTok(parser->m_encoding, start, end, &next); switch (tok) { case XML_TOK_BOM: -#ifdef XML_DTD +#if XML_GE == 1 if (! accountingDiffTolerated(parser, tok, start, next, __LINE__, XML_ACCOUNT_DIRECT)) { accountingOnAbort(parser); return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; } -#endif /* XML_DTD */ +#endif /* XML_GE == 1 */ /* If we are at the end of the buffer, this would cause the next stage, i.e. externalEntityInitProcessor3, to pass control directly to @@ -2765,7 +2825,7 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, for (;;) { const char *next = s; /* XmlContentTok doesn't always set the last arg */ int tok = XmlContentTok(enc, s, end, &next); -#ifdef XML_DTD +#if XML_GE == 1 const char *accountAfter = ((tok == XML_TOK_TRAILING_RSQB) || (tok == XML_TOK_TRAILING_CR)) ? (haveMore ? s /* i.e. 0 bytes */ : end) @@ -2831,14 +2891,14 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, XML_Char ch = (XML_Char)XmlPredefinedEntityName( enc, s + enc->minBytesPerChar, next - enc->minBytesPerChar); if (ch) { -#ifdef XML_DTD +#if XML_GE == 1 /* NOTE: We are replacing 4-6 characters original input for 1 character * so there is no amplification and hence recording without * protection. */ accountingDiffTolerated(parser, tok, (char *)&ch, ((char *)&ch) + sizeof(XML_Char), __LINE__, XML_ACCOUNT_ENTITY_EXPANSION); -#endif /* XML_DTD */ +#endif /* XML_GE == 1 */ if (parser->m_characterDataHandler) parser->m_characterDataHandler(parser->m_handlerArg, &ch, 1); else if (parser->m_defaultHandler) @@ -3039,13 +3099,13 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, if (parser->m_ns && localPart) { /* localPart and prefix may have been overwritten in tag->name.str, since this points to the binding->uri - buffer which gets re-used; so we have to add them again + buffer which gets reused; so we have to add them again */ uri = (XML_Char *)tag->name.str + tag->name.uriLen; /* don't need to check for space - already done in storeAtts() */ while (*localPart) *uri++ = *localPart++; - prefix = (XML_Char *)tag->name.prefix; + prefix = tag->name.prefix; if (parser->m_ns_triplets && prefix) { *uri++ = parser->m_namespaceSeparator; while (*prefix) @@ -3112,7 +3172,7 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, However, now we have a start/endCdataSectionHandler, so it seems easier to let the user deal with this. */ - else if (0 && parser->m_characterDataHandler) + else if ((0) && parser->m_characterDataHandler) parser->m_characterDataHandler(parser->m_handlerArg, parser->m_dataBuf, 0); /* END disabled code */ @@ -3141,8 +3201,8 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, (int)(dataPtr - (ICHAR *)parser->m_dataBuf)); } else parser->m_characterDataHandler( - parser->m_handlerArg, (XML_Char *)s, - (int)((XML_Char *)end - (XML_Char *)s)); + parser->m_handlerArg, (const XML_Char *)s, + (int)((const XML_Char *)end - (const XML_Char *)s)); } else if (parser->m_defaultHandler) reportDefault(parser, enc, s, end); /* We are at the end of the final buffer, should we check for @@ -3175,8 +3235,8 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, *eventPP = s; } } else - charDataHandler(parser->m_handlerArg, (XML_Char *)s, - (int)((XML_Char *)next - (XML_Char *)s)); + charDataHandler(parser->m_handlerArg, (const XML_Char *)s, + (int)((const XML_Char *)next - (const XML_Char *)s)); } else if (parser->m_defaultHandler) reportDefault(parser, enc, s, next); } break; @@ -4040,7 +4100,7 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, for (;;) { const char *next = s; /* in case of XML_TOK_NONE or XML_TOK_PARTIAL */ int tok = XmlCdataSectionTok(enc, s, end, &next); -#ifdef XML_DTD +#if XML_GE == 1 if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, account)) { accountingOnAbort(parser); return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; @@ -4055,7 +4115,7 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, parser->m_endCdataSectionHandler(parser->m_handlerArg); /* BEGIN disabled code */ /* see comment under XML_TOK_CDATA_SECT_OPEN */ - else if (0 && parser->m_characterDataHandler) + else if ((0) && parser->m_characterDataHandler) parser->m_characterDataHandler(parser->m_handlerArg, parser->m_dataBuf, 0); /* END disabled code */ @@ -4091,8 +4151,8 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, *eventPP = s; } } else - charDataHandler(parser->m_handlerArg, (XML_Char *)s, - (int)((XML_Char *)next - (XML_Char *)s)); + charDataHandler(parser->m_handlerArg, (const XML_Char *)s, + (int)((const XML_Char *)next - (const XML_Char *)s)); } else if (parser->m_defaultHandler) reportDefault(parser, enc, s, next); } break; @@ -4192,7 +4252,7 @@ doIgnoreSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, *eventPP = s; *startPtr = NULL; tok = XmlIgnoreSectionTok(enc, s, end, &next); -# ifdef XML_DTD +# if XML_GE == 1 if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, XML_ACCOUNT_DIRECT)) { accountingOnAbort(parser); @@ -4284,7 +4344,7 @@ processXmlDecl(XML_Parser parser, int isGeneralTextEntity, const char *s, const XML_Char *storedversion = NULL; int standalone = -1; -#ifdef XML_DTD +#if XML_GE == 1 if (! accountingDiffTolerated(parser, XML_TOK_XML_DECL, s, next, __LINE__, XML_ACCOUNT_DIRECT)) { accountingOnAbort(parser); @@ -4482,16 +4542,16 @@ entityValueInitProcessor(XML_Parser parser, const char *s, const char *end, parser->m_processor = entityValueProcessor; return entityValueProcessor(parser, next, end, nextPtr); } - /* If we are at the end of the buffer, this would cause XmlPrologTok to - return XML_TOK_NONE on the next call, which would then cause the - function to exit with *nextPtr set to s - that is what we want for other - tokens, but not for the BOM - we would rather like to skip it; - then, when this routine is entered the next time, XmlPrologTok will - return XML_TOK_INVALID, since the BOM is still in the buffer + /* XmlPrologTok has now set the encoding based on the BOM it found, and we + must move s and nextPtr forward to consume the BOM. + + If we didn't, and got XML_TOK_NONE from the next XmlPrologTok call, we + would leave the BOM in the buffer and return. On the next call to this + function, our XmlPrologTok call would return XML_TOK_INVALID, since it + is not valid to have multiple BOMs. */ - else if (tok == XML_TOK_BOM && next == end - && ! parser->m_parsingStatus.finalBuffer) { -# ifdef XML_DTD + else if (tok == XML_TOK_BOM) { +# if XML_GE == 1 if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, XML_ACCOUNT_DIRECT)) { accountingOnAbort(parser); @@ -4500,7 +4560,7 @@ entityValueInitProcessor(XML_Parser parser, const char *s, const char *end, # endif *nextPtr = next; - return XML_ERROR_NONE; + s = next; } /* If we get this token, we have the start of what might be a normal tag, but not a declaration (i.e. it doesn't begin with @@ -4707,11 +4767,13 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, } } role = XmlTokenRole(&parser->m_prologState, tok, s, next, enc); -#ifdef XML_DTD +#if XML_GE == 1 switch (role) { case XML_ROLE_INSTANCE_START: // bytes accounted in contentProcessor case XML_ROLE_XML_DECL: // bytes accounted in processXmlDecl - case XML_ROLE_TEXT_DECL: // bytes accounted in processXmlDecl +# ifdef XML_DTD + case XML_ROLE_TEXT_DECL: // bytes accounted in processXmlDecl +# endif break; default: if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, account)) { @@ -5029,6 +5091,9 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, break; case XML_ROLE_ENTITY_VALUE: if (dtd->keepProcessing) { +#if XML_GE == 1 + // This will store the given replacement text in + // parser->m_declEntity->textPtr. enum XML_Error result = storeEntityValue(parser, enc, s + enc->minBytesPerChar, next - enc->minBytesPerChar, XML_ACCOUNT_NONE); @@ -5049,6 +5114,25 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, poolDiscard(&dtd->entityValuePool); if (result != XML_ERROR_NONE) return result; +#else + // This will store "&entity123;" in parser->m_declEntity->textPtr + // to end up as "&entity123;" in the handler. + if (parser->m_declEntity != NULL) { + const enum XML_Error result + = storeSelfEntityValue(parser, parser->m_declEntity); + if (result != XML_ERROR_NONE) + return result; + + if (parser->m_entityDeclHandler) { + *eventEndPP = s; + parser->m_entityDeclHandler( + parser->m_handlerArg, parser->m_declEntity->name, + parser->m_declEntity->is_param, parser->m_declEntity->textPtr, + parser->m_declEntity->textLen, parser->m_curBase, 0, 0, 0); + handleDefault = XML_FALSE; + } + } +#endif } break; case XML_ROLE_DOCTYPE_SYSTEM_ID: @@ -5107,6 +5191,16 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, } break; case XML_ROLE_ENTITY_COMPLETE: +#if XML_GE == 0 + // This will store "&entity123;" in entity->textPtr + // to end up as "&entity123;" in the handler. + if (parser->m_declEntity != NULL) { + const enum XML_Error result + = storeSelfEntityValue(parser, parser->m_declEntity); + if (result != XML_ERROR_NONE) + return result; + } +#endif if (dtd->keepProcessing && parser->m_declEntity && parser->m_entityDeclHandler) { *eventEndPP = s; @@ -5648,7 +5742,7 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end, for (;;) { const char *next = NULL; int tok = XmlPrologTok(parser->m_encoding, s, end, &next); -#ifdef XML_DTD +#if XML_GE == 1 if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, XML_ACCOUNT_DIRECT)) { accountingOnAbort(parser); @@ -5728,7 +5822,7 @@ processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) { return XML_ERROR_NO_MEMORY; } entity->open = XML_TRUE; -#ifdef XML_DTD +#if XML_GE == 1 entityTrackingOnOpen(parser, entity, __LINE__); #endif entity->processed = 0; @@ -5761,10 +5855,10 @@ processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) { if (textEnd != next && parser->m_parsingStatus.parsing == XML_SUSPENDED) { entity->processed = (int)(next - textStart); parser->m_processor = internalEntityProcessor; - } else { -#ifdef XML_DTD + } else if (parser->m_openInternalEntities->entity == entity) { +#if XML_GE == 1 entityTrackingOnClose(parser, entity, __LINE__); -#endif /* XML_DTD */ +#endif /* XML_GE == 1 */ entity->open = XML_FALSE; parser->m_openInternalEntities = openEntity->next; /* put openEntity back in list of free instances */ @@ -5813,7 +5907,7 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, return result; } -#ifdef XML_DTD +#if XML_GE == 1 entityTrackingOnClose(parser, entity, __LINE__); #endif entity->open = XML_FALSE; @@ -5892,7 +5986,7 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, const char *next = ptr; /* XmlAttributeValueTok doesn't always set the last arg */ int tok = XmlAttributeValueTok(enc, ptr, end, &next); -#ifdef XML_DTD +#if XML_GE == 1 if (! accountingDiffTolerated(parser, tok, ptr, next, __LINE__, account)) { accountingOnAbort(parser); return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; @@ -5957,14 +6051,14 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, XML_Char ch = (XML_Char)XmlPredefinedEntityName( enc, ptr + enc->minBytesPerChar, next - enc->minBytesPerChar); if (ch) { -#ifdef XML_DTD +#if XML_GE == 1 /* NOTE: We are replacing 4-6 characters original input for 1 character * so there is no amplification and hence recording without * protection. */ accountingDiffTolerated(parser, tok, (char *)&ch, ((char *)&ch) + sizeof(XML_Char), __LINE__, XML_ACCOUNT_ENTITY_EXPANSION); -#endif /* XML_DTD */ +#endif /* XML_GE == 1 */ if (! poolAppendChar(pool, ch)) return XML_ERROR_NO_MEMORY; break; @@ -6042,14 +6136,14 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, enum XML_Error result; const XML_Char *textEnd = entity->textPtr + entity->textLen; entity->open = XML_TRUE; -#ifdef XML_DTD +#if XML_GE == 1 entityTrackingOnOpen(parser, entity, __LINE__); #endif result = appendAttributeValue(parser, parser->m_internalEncoding, isCdata, (const char *)entity->textPtr, (const char *)textEnd, pool, XML_ACCOUNT_ENTITY_EXPANSION); -#ifdef XML_DTD +#if XML_GE == 1 entityTrackingOnClose(parser, entity, __LINE__); #endif entity->open = XML_FALSE; @@ -6079,6 +6173,7 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, /* not reached */ } +#if XML_GE == 1 static enum XML_Error storeEntityValue(XML_Parser parser, const ENCODING *enc, const char *entityTextPtr, const char *entityTextEnd, @@ -6086,12 +6181,12 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, DTD *const dtd = parser->m_dtd; /* save one level of indirection */ STRING_POOL *pool = &(dtd->entityValuePool); enum XML_Error result = XML_ERROR_NONE; -#ifdef XML_DTD +# ifdef XML_DTD int oldInEntityValue = parser->m_prologState.inEntityValue; parser->m_prologState.inEntityValue = 1; -#else +# else UNUSED_P(account); -#endif /* XML_DTD */ +# endif /* XML_DTD */ /* never return Null for the value argument in EntityDeclHandler, since this would indicate an external entity; therefore we have to make sure that entityValuePool.start is not null */ @@ -6105,18 +6200,16 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, = entityTextPtr; /* XmlEntityValueTok doesn't always set the last arg */ int tok = XmlEntityValueTok(enc, entityTextPtr, entityTextEnd, &next); -#ifdef XML_DTD if (! accountingDiffTolerated(parser, tok, entityTextPtr, next, __LINE__, account)) { accountingOnAbort(parser); result = XML_ERROR_AMPLIFICATION_LIMIT_BREACH; goto endEntityValue; } -#endif switch (tok) { case XML_TOK_PARAM_ENTITY_REF: -#ifdef XML_DTD +# ifdef XML_DTD if (parser->m_isParamEntity || enc != parser->m_encoding) { const XML_Char *name; ENTITY *entity; @@ -6178,7 +6271,7 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, } break; } -#endif /* XML_DTD */ +# endif /* XML_DTD */ /* In the internal subset, PE references are not legal within markup declarations, e.g entity values in this case. */ parser->m_eventPtr = entityTextPtr; @@ -6259,12 +6352,38 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, entityTextPtr = next; } endEntityValue: -#ifdef XML_DTD +# ifdef XML_DTD parser->m_prologState.inEntityValue = oldInEntityValue; -#endif /* XML_DTD */ +# endif /* XML_DTD */ return result; } +#else /* XML_GE == 0 */ + +static enum XML_Error +storeSelfEntityValue(XML_Parser parser, ENTITY *entity) { + // This will store "&entity123;" in entity->textPtr + // to end up as "&entity123;" in the handler. + const char *const entity_start = "&"; + const char *const entity_end = ";"; + + STRING_POOL *const pool = &(parser->m_dtd->entityValuePool); + if (! poolAppendString(pool, entity_start) + || ! poolAppendString(pool, entity->name) + || ! poolAppendString(pool, entity_end)) { + poolDiscard(pool); + return XML_ERROR_NO_MEMORY; + } + + entity->textPtr = poolStart(pool); + entity->textLen = (int)(poolLength(pool)); + poolFinish(pool); + + return XML_ERROR_NONE; +} + +#endif /* XML_GE == 0 */ + static void FASTCALL normalizeLines(XML_Char *s) { XML_Char *p; @@ -6375,8 +6494,9 @@ reportDefault(XML_Parser parser, const ENCODING *enc, const char *s, } while ((convert_res != XML_CONVERT_COMPLETED) && (convert_res != XML_CONVERT_INPUT_INCOMPLETE)); } else - parser->m_defaultHandler(parser->m_handlerArg, (XML_Char *)s, - (int)((XML_Char *)end - (XML_Char *)s)); + parser->m_defaultHandler( + parser->m_handlerArg, (const XML_Char *)s, + (int)((const XML_Char *)end - (const XML_Char *)s)); } static int @@ -6480,7 +6600,7 @@ getAttributeId(XML_Parser parser, const ENCODING *enc, const char *start, name = poolStoreString(&dtd->pool, enc, start, end); if (! name) return NULL; - /* skip quotation mark - its storage will be re-used (like in name[-1]) */ + /* skip quotation mark - its storage will be reused (like in name[-1]) */ ++name; id = (ATTRIBUTE_ID *)lookup(parser, &dtd->attributeIds, name, sizeof(ATTRIBUTE_ID)); @@ -6630,6 +6750,10 @@ getContext(XML_Parser parser) { static XML_Bool setContext(XML_Parser parser, const XML_Char *context) { + if (context == NULL) { + return XML_FALSE; + } + DTD *const dtd = parser->m_dtd; /* save one level of indirection */ const XML_Char *s = context; @@ -7220,7 +7344,7 @@ poolAppend(STRING_POOL *pool, const ENCODING *enc, const char *ptr, return NULL; for (;;) { const enum XML_Convert_Result convert_res = XmlConvert( - enc, &ptr, end, (ICHAR **)&(pool->ptr), (ICHAR *)pool->end); + enc, &ptr, end, (ICHAR **)&(pool->ptr), (const ICHAR *)pool->end); if ((convert_res == XML_CONVERT_COMPLETED) || (convert_res == XML_CONVERT_INPUT_INCOMPLETE)) break; @@ -7651,7 +7775,7 @@ copyString(const XML_Char *s, const XML_Memory_Handling_Suite *memsuite) { return result; } -#ifdef XML_DTD +#if XML_GE == 1 static float accountingGetCurrentAmplification(XML_Parser rootParser) { @@ -7672,7 +7796,7 @@ accountingReportStats(XML_Parser originParser, const char *epilog) { const XML_Parser rootParser = getRootParserOf(originParser, NULL); assert(! rootParser->m_parentParser); - if (rootParser->m_accounting.debugLevel < 1) { + if (rootParser->m_accounting.debugLevel == 0u) { return; } @@ -7709,7 +7833,7 @@ accountingReportDiff(XML_Parser rootParser, /* Note: Performance is of no concern here */ const char *walker = before; - if ((rootParser->m_accounting.debugLevel >= 3) + if ((rootParser->m_accounting.debugLevel >= 3u) || (after - before) <= (ptrdiff_t)(contextLength + ellipsisLength + contextLength)) { for (; walker < after; walker++) { @@ -7774,7 +7898,7 @@ accountingDiffTolerated(XML_Parser originParser, int tok, const char *before, || (amplificationFactor <= rootParser->m_accounting.maximumAmplificationFactor); - if (rootParser->m_accounting.debugLevel >= 2) { + if (rootParser->m_accounting.debugLevel >= 2u) { accountingReportStats(rootParser, ""); accountingReportDiff(rootParser, levelsAwayFromRootParser, before, after, bytesMore, source_line, account); @@ -7801,7 +7925,7 @@ static void entityTrackingReportStats(XML_Parser rootParser, ENTITY *entity, const char *action, int sourceLine) { assert(! rootParser->m_parentParser); - if (rootParser->m_entity_stats.debugLevel < 1) + if (rootParser->m_entity_stats.debugLevel == 0u) return; # if defined(XML_UNICODE) @@ -8382,7 +8506,7 @@ unsignedCharToPrintable(unsigned char c) { assert(0); /* never gets here */ } -#endif /* XML_DTD */ +#endif /* XML_GE == 1 */ static unsigned long getDebugLevel(const char *variableName, unsigned long defaultDebugLevel) { @@ -8393,9 +8517,9 @@ getDebugLevel(const char *variableName, unsigned long defaultDebugLevel) { const char *const value = valueOrNull; errno = 0; - char *afterValue = (char *)value; + char *afterValue = NULL; unsigned long debugLevel = strtoul(value, &afterValue, 10); - if ((errno != 0) || (afterValue[0] != '\0')) { + if ((errno != 0) || (afterValue == value) || (afterValue[0] != '\0')) { errno = 0; return defaultDebugLevel; } diff --git a/Modules/expat/xmlrole.c b/Modules/expat/xmlrole.c index 3f0f5c150c6278e..2c48bf408679538 100644 --- a/Modules/expat/xmlrole.c +++ b/Modules/expat/xmlrole.c @@ -12,10 +12,10 @@ Copyright (c) 2002-2006 Karl Waclawek <karl@waclawek.net> Copyright (c) 2002-2003 Fred L. Drake, Jr. <fdrake@users.sourceforge.net> Copyright (c) 2005-2009 Steven Solie <steven@solie.ca> - Copyright (c) 2016-2021 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2023 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk> Copyright (c) 2019 David Loffredo <loffredo@steptools.com> - Copyright (c) 2021 Dong-hee Na <donghee.na@python.org> + Copyright (c) 2021 Donghee Na <donghee.na@python.org> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -38,7 +38,7 @@ USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include <expat_config.h> +#include "expat_config.h" #include <stddef.h> diff --git a/Modules/expat/xmlrole.h b/Modules/expat/xmlrole.h index d6e1fa150a108ac..a7904274c91d4ec 100644 --- a/Modules/expat/xmlrole.h +++ b/Modules/expat/xmlrole.h @@ -10,7 +10,7 @@ Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net> Copyright (c) 2002 Karl Waclawek <karl@waclawek.net> Copyright (c) 2002 Fred L. Drake, Jr. <fdrake@users.sourceforge.net> - Copyright (c) 2017 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2017-2024 Sebastian Pipping <sebastian@pipping.org> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -127,9 +127,9 @@ typedef struct prolog_state { #endif /* XML_DTD */ } PROLOG_STATE; -void XmlPrologStateInit(PROLOG_STATE *); +void XmlPrologStateInit(PROLOG_STATE *state); #ifdef XML_DTD -void XmlPrologStateInitExternalEntity(PROLOG_STATE *); +void XmlPrologStateInitExternalEntity(PROLOG_STATE *state); #endif /* XML_DTD */ #define XmlTokenRole(state, tok, ptr, end, enc) \ diff --git a/Modules/expat/xmltok.c b/Modules/expat/xmltok.c index 2b7012a58be4196..29a66d72ceea5e5 100644 --- a/Modules/expat/xmltok.c +++ b/Modules/expat/xmltok.c @@ -12,7 +12,7 @@ Copyright (c) 2002 Greg Stein <gstein@users.sourceforge.net> Copyright (c) 2002-2016 Karl Waclawek <karl@waclawek.net> Copyright (c) 2005-2009 Steven Solie <steven@solie.ca> - Copyright (c) 2016-2022 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2024 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2016 Pascal Cuoq <cuoq@trust-in-soft.com> Copyright (c) 2016 Don Lewis <truckman@apache.org> Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk> @@ -20,8 +20,10 @@ Copyright (c) 2017 Benbuck Nason <bnason@netflix.com> Copyright (c) 2017 José Gutiérrez de la Concha <jose@zeroc.com> Copyright (c) 2019 David Loffredo <loffredo@steptools.com> - Copyright (c) 2021 Dong-hee Na <donghee.na@python.org> + Copyright (c) 2021 Donghee Na <donghee.na@python.org> Copyright (c) 2022 Martin Ettl <ettl.martin78@googlemail.com> + Copyright (c) 2022 Sean McBride <sean@rogue-research.com> + Copyright (c) 2023 Hanno Böck <hanno@gentoo.org> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -44,7 +46,7 @@ USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include <expat_config.h> +#include "expat_config.h" #include <stddef.h> #include <string.h> /* memcpy */ @@ -76,7 +78,7 @@ #define VTABLE VTABLE1, PREFIX(toUtf8), PREFIX(toUtf16) #define UCS2_GET_NAMING(pages, hi, lo) \ - (namingBitmap[(pages[hi] << 3) + ((lo) >> 5)] & (1u << ((lo)&0x1F))) + (namingBitmap[(pages[hi] << 3) + ((lo) >> 5)] & (1u << ((lo) & 0x1F))) /* A 2 byte UTF-8 representation splits the characters 11 bits between the bottom 5 and 6 bits of the bytes. We need 8 bits to index into @@ -100,7 +102,7 @@ & (1u << (((byte)[2]) & 0x1F))) /* Detection of invalid UTF-8 sequences is based on Table 3.1B - of Unicode 3.2: http://www.unicode.org/unicode/reports/tr28/ + of Unicode 3.2: https://www.unicode.org/unicode/reports/tr28/ with the additional restriction of not allowing the Unicode code points 0xFFFF and 0xFFFE (sequences EF,BF,BF and EF,BF,BE). Implementation details: @@ -225,7 +227,7 @@ struct normal_encoding { /* isNmstrt2 */ NULL, /* isNmstrt3 */ NULL, /* isNmstrt4 */ NULL, \ /* isInvalid2 */ NULL, /* isInvalid3 */ NULL, /* isInvalid4 */ NULL -static int FASTCALL checkCharRefNumber(int); +static int FASTCALL checkCharRefNumber(int result); #include "xmltok_impl.h" #include "ascii.h" @@ -243,7 +245,7 @@ static int FASTCALL checkCharRefNumber(int); #endif #define SB_BYTE_TYPE(enc, p) \ - (((struct normal_encoding *)(enc))->type[(unsigned char)*(p)]) + (((const struct normal_encoding *)(enc))->type[(unsigned char)*(p)]) #ifdef XML_MIN_SIZE static int PTRFASTCALL @@ -407,7 +409,7 @@ utf8_toUtf16(const ENCODING *enc, const char **fromP, const char *fromLim, unsigned short *to = *toP; const char *from = *fromP; while (from < fromLim && to < toLim) { - switch (((struct normal_encoding *)enc)->type[(unsigned char)*from]) { + switch (SB_BYTE_TYPE(enc, from)) { case BT_LEAD2: if (fromLim - from < 2) { res = XML_CONVERT_INPUT_INCOMPLETE; @@ -715,31 +717,26 @@ unicode_byte_type(char hi, char lo) { return res; \ } -#define SET2(ptr, ch) (((ptr)[0] = ((ch)&0xff)), ((ptr)[1] = ((ch) >> 8))) #define GET_LO(ptr) ((unsigned char)(ptr)[0]) #define GET_HI(ptr) ((unsigned char)(ptr)[1]) DEFINE_UTF16_TO_UTF8(little2_) DEFINE_UTF16_TO_UTF16(little2_) -#undef SET2 #undef GET_LO #undef GET_HI -#define SET2(ptr, ch) (((ptr)[0] = ((ch) >> 8)), ((ptr)[1] = ((ch)&0xFF))) #define GET_LO(ptr) ((unsigned char)(ptr)[1]) #define GET_HI(ptr) ((unsigned char)(ptr)[0]) DEFINE_UTF16_TO_UTF8(big2_) DEFINE_UTF16_TO_UTF16(big2_) -#undef SET2 #undef GET_LO #undef GET_HI #define LITTLE2_BYTE_TYPE(enc, p) \ - ((p)[1] == 0 ? ((struct normal_encoding *)(enc))->type[(unsigned char)*(p)] \ - : unicode_byte_type((p)[1], (p)[0])) + ((p)[1] == 0 ? SB_BYTE_TYPE(enc, p) : unicode_byte_type((p)[1], (p)[0])) #define LITTLE2_BYTE_TO_ASCII(p) ((p)[1] == 0 ? (p)[0] : -1) #define LITTLE2_CHAR_MATCHES(p, c) ((p)[1] == 0 && (p)[0] == (c)) #define LITTLE2_IS_NAME_CHAR_MINBPC(p) \ @@ -872,9 +869,7 @@ static const struct normal_encoding internal_little2_encoding #endif #define BIG2_BYTE_TYPE(enc, p) \ - ((p)[0] == 0 \ - ? ((struct normal_encoding *)(enc))->type[(unsigned char)(p)[1]] \ - : unicode_byte_type((p)[0], (p)[1])) + ((p)[0] == 0 ? SB_BYTE_TYPE(enc, p + 1) : unicode_byte_type((p)[0], (p)[1])) #define BIG2_BYTE_TO_ASCII(p) ((p)[0] == 0 ? (p)[1] : -1) #define BIG2_CHAR_MATCHES(p, c) ((p)[0] == 0 && (p)[1] == (c)) #define BIG2_IS_NAME_CHAR_MINBPC(p) \ diff --git a/Modules/expat/xmltok.h b/Modules/expat/xmltok.h index 6f630c2f9ba96d5..c51fce1ec1518be 100644 --- a/Modules/expat/xmltok.h +++ b/Modules/expat/xmltok.h @@ -10,7 +10,7 @@ Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net> Copyright (c) 2002 Fred L. Drake, Jr. <fdrake@users.sourceforge.net> Copyright (c) 2002-2005 Karl Waclawek <karl@waclawek.net> - Copyright (c) 2016-2017 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2024 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk> Licensed under the MIT license: @@ -289,7 +289,8 @@ int XmlParseXmlDecl(int isGeneralTextEntity, const ENCODING *enc, const char **encodingNamePtr, const ENCODING **namedEncodingPtr, int *standalonePtr); -int XmlInitEncoding(INIT_ENCODING *, const ENCODING **, const char *name); +int XmlInitEncoding(INIT_ENCODING *p, const ENCODING **encPtr, + const char *name); const ENCODING *XmlGetUtf8InternalEncoding(void); const ENCODING *XmlGetUtf16InternalEncoding(void); int FASTCALL XmlUtf8Encode(int charNumber, char *buf); @@ -307,7 +308,8 @@ int XmlParseXmlDeclNS(int isGeneralTextEntity, const ENCODING *enc, const char **encodingNamePtr, const ENCODING **namedEncodingPtr, int *standalonePtr); -int XmlInitEncodingNS(INIT_ENCODING *, const ENCODING **, const char *name); +int XmlInitEncodingNS(INIT_ENCODING *p, const ENCODING **encPtr, + const char *name); const ENCODING *XmlGetUtf8InternalEncodingNS(void); const ENCODING *XmlGetUtf16InternalEncodingNS(void); ENCODING *XmlInitUnknownEncodingNS(void *mem, int *table, CONVERTER convert, diff --git a/Modules/expat/xmltok_impl.c b/Modules/expat/xmltok_impl.c index 1971d74bf8c91f8..239a2d06c4512ce 100644 --- a/Modules/expat/xmltok_impl.c +++ b/Modules/expat/xmltok_impl.c @@ -126,7 +126,7 @@ # endif # define HAS_CHARS(enc, ptr, end, count) \ - ((end) - (ptr) >= ((count)*MINBPC(enc))) + ((end) - (ptr) >= ((count) * MINBPC(enc))) # define HAS_CHAR(enc, ptr, end) HAS_CHARS(enc, ptr, end, 1) diff --git a/Tools/build/generate_sbom.py b/Tools/build/generate_sbom.py index 442487f2d2546b4..82016dc408639db 100644 --- a/Tools/build/generate_sbom.py +++ b/Tools/build/generate_sbom.py @@ -59,7 +59,10 @@ class PackageFiles(typing.NamedTuple): include=["Modules/_decimal/libmpdec/**"] ), "expat": PackageFiles( - include=["Modules/expat/**"] + include=["Modules/expat/**"], + exclude=[ + "Modules/expat/expat_config.h", + ] ), "macholib": PackageFiles( include=["Lib/ctypes/macholib/**"], diff --git a/Tools/c-analyzer/cpython/_parser.py b/Tools/c-analyzer/cpython/_parser.py index be89a26058e8e86..61cd41ea8f31c19 100644 --- a/Tools/c-analyzer/cpython/_parser.py +++ b/Tools/c-analyzer/cpython/_parser.py @@ -104,6 +104,7 @@ def clean_lines(text): # The problem with xmlparse.c is that something # has gone wrong where # we handle "maybe inline actual" # in Tools/c-analyzer/c_parser/parser/_global.py. +Modules/expat/internal.h Modules/expat/xmlparse.c ''') From a2d4281415e67c62f91363376db97eb66a9fb716 Mon Sep 17 00:00:00 2001 From: Donghee Na <donghee.na@python.org> Date: Thu, 15 Feb 2024 02:00:50 +0900 Subject: [PATCH 269/507] gh-112087: Make __sizeof__ and listiter_{len, next} to be threadsafe (gh-114843) --- Lib/test/support/__init__.py | 27 ++--- Lib/test/test_iter.py | 2 +- ...-02-14-23-50-55.gh-issue-112087.H_4W_v.rst | 2 + Objects/listobject.c | 102 +++++++++--------- Python/bytecodes.c | 8 +- Python/executor_cases.c.h | 3 +- Python/generated_cases.c.h | 5 +- 7 files changed, 80 insertions(+), 69 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-14-23-50-55.gh-issue-112087.H_4W_v.rst diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 5b091fb2fd32dc6..1d03ec0f5bd12be 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -1727,19 +1727,22 @@ def _check_tracemalloc(): def check_free_after_iterating(test, iter, cls, args=()): - class A(cls): - def __del__(self): - nonlocal done - done = True - try: - next(it) - except StopIteration: - pass - done = False - it = iter(A(*args)) - # Issue 26494: Shouldn't crash - test.assertRaises(StopIteration, next, it) + def wrapper(): + class A(cls): + def __del__(self): + nonlocal done + done = True + try: + next(it) + except StopIteration: + pass + + it = iter(A(*args)) + # Issue 26494: Shouldn't crash + test.assertRaises(StopIteration, next, it) + + wrapper() # The sequence should be deallocated just after the end of iterating gc_collect() test.assertTrue(done) diff --git a/Lib/test/test_iter.py b/Lib/test/test_iter.py index 30aedb0db3bb3db..9606d5beab71cb0 100644 --- a/Lib/test/test_iter.py +++ b/Lib/test/test_iter.py @@ -302,7 +302,7 @@ def __eq__(self, other): # listiter_reduce_general self.assertEqual( run("reversed", orig["reversed"](list(range(8)))), - (iter, ([],)) + (reversed, ([],)) ) for case in types: diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-14-23-50-55.gh-issue-112087.H_4W_v.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-14-23-50-55.gh-issue-112087.H_4W_v.rst new file mode 100644 index 000000000000000..f92cdafd4ba225c --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-14-23-50-55.gh-issue-112087.H_4W_v.rst @@ -0,0 +1,2 @@ +For an empty reverse iterator for list will be reduced to :func:`reversed`. +Patch by Donghee Na diff --git a/Objects/listobject.c b/Objects/listobject.c index 93409a82f8a4897..96182a42306d957 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -20,6 +20,14 @@ class list "PyListObject *" "&PyList_Type" _Py_DECLARE_STR(list_err, "list index out of range"); +#ifdef Py_GIL_DISABLED +# define LOAD_SSIZE(value) _Py_atomic_load_ssize_relaxed(&value) +# define STORE_SSIZE(value, new_value) _Py_atomic_store_ssize_relaxed(&value, new_value) +#else +# define LOAD_SSIZE(value) value +# define STORE_SSIZE(value, new_value) value = new_value +#endif + #ifdef WITH_FREELISTS static struct _Py_list_freelist * get_list_freelist(void) @@ -2971,7 +2979,8 @@ list___sizeof___impl(PyListObject *self) /*[clinic end generated code: output=3417541f95f9a53e input=b8030a5d5ce8a187]*/ { size_t res = _PyObject_SIZE(Py_TYPE(self)); - res += (size_t)self->allocated * sizeof(void*); + Py_ssize_t allocated = LOAD_SSIZE(self->allocated); + res += (size_t)allocated * sizeof(void*); return PyLong_FromSize_t(res); } @@ -3373,33 +3382,34 @@ static PyObject * listiter_next(PyObject *self) { _PyListIterObject *it = (_PyListIterObject *)self; - PyListObject *seq; - PyObject *item; - - assert(it != NULL); - seq = it->it_seq; - if (seq == NULL) + Py_ssize_t index = LOAD_SSIZE(it->it_index); + if (index < 0) { return NULL; - assert(PyList_Check(seq)); - - if (it->it_index < PyList_GET_SIZE(seq)) { - item = PyList_GET_ITEM(seq, it->it_index); - ++it->it_index; - return Py_NewRef(item); } - it->it_seq = NULL; - Py_DECREF(seq); - return NULL; + PyObject *item = list_get_item_ref(it->it_seq, index); + if (item == NULL) { + // out-of-bounds + STORE_SSIZE(it->it_index, -1); +#ifndef Py_GIL_DISABLED + PyListObject *seq = it->it_seq; + it->it_seq = NULL; + Py_DECREF(seq); +#endif + return NULL; + } + STORE_SSIZE(it->it_index, index + 1); + return item; } static PyObject * listiter_len(PyObject *self, PyObject *Py_UNUSED(ignored)) { + assert(self != NULL); _PyListIterObject *it = (_PyListIterObject *)self; - Py_ssize_t len; - if (it->it_seq) { - len = PyList_GET_SIZE(it->it_seq) - it->it_index; + Py_ssize_t index = LOAD_SSIZE(it->it_index); + if (index >= 0) { + Py_ssize_t len = PyList_GET_SIZE(it->it_seq) - index; if (len >= 0) return PyLong_FromSsize_t(len); } @@ -3420,8 +3430,8 @@ listiter_setstate(PyObject *self, PyObject *state) if (index == -1 && PyErr_Occurred()) return NULL; if (it->it_seq != NULL) { - if (index < 0) - index = 0; + if (index < -1) + index = -1; else if (index > PyList_GET_SIZE(it->it_seq)) index = PyList_GET_SIZE(it->it_seq); /* iterator exhausted */ it->it_index = index; @@ -3526,26 +3536,24 @@ static PyObject * listreviter_next(PyObject *self) { listreviterobject *it = (listreviterobject *)self; - PyObject *item; - Py_ssize_t index; - PyListObject *seq; - assert(it != NULL); - seq = it->it_seq; - if (seq == NULL) { - return NULL; - } + PyListObject *seq = it->it_seq; assert(PyList_Check(seq)); - index = it->it_index; - if (index>=0 && index < PyList_GET_SIZE(seq)) { - item = PyList_GET_ITEM(seq, index); - it->it_index--; - return Py_NewRef(item); + Py_ssize_t index = LOAD_SSIZE(it->it_index); + if (index < 0) { + return NULL; + } + PyObject *item = list_get_item_ref(seq, index); + if (item != NULL) { + STORE_SSIZE(it->it_index, index - 1); + return item; } - it->it_index = -1; + STORE_SSIZE(it->it_index, -1); +#ifndef Py_GIL_DISABLED it->it_seq = NULL; Py_DECREF(seq); +#endif return NULL; } @@ -3553,7 +3561,8 @@ static PyObject * listreviter_len(PyObject *self, PyObject *Py_UNUSED(ignored)) { listreviterobject *it = (listreviterobject *)self; - Py_ssize_t len = it->it_index + 1; + Py_ssize_t index = LOAD_SSIZE(it->it_index); + Py_ssize_t len = index + 1; if (it->it_seq == NULL || PyList_GET_SIZE(it->it_seq) < len) len = 0; return PyLong_FromSsize_t(len); @@ -3588,6 +3597,7 @@ static PyObject * listiter_reduce_general(void *_it, int forward) { PyObject *list; + PyObject *iter; /* _PyEval_GetBuiltin can invoke arbitrary code, * call must be before access of iterator pointers. @@ -3595,29 +3605,21 @@ listiter_reduce_general(void *_it, int forward) /* the objects are not the same, index is of different types! */ if (forward) { - PyObject *iter = _PyEval_GetBuiltin(&_Py_ID(iter)); - if (!iter) { - return NULL; - } + iter = _PyEval_GetBuiltin(&_Py_ID(iter)); _PyListIterObject *it = (_PyListIterObject *)_it; - if (it->it_seq) { + if (it->it_index >= 0) { return Py_BuildValue("N(O)n", iter, it->it_seq, it->it_index); } - Py_DECREF(iter); } else { - PyObject *reversed = _PyEval_GetBuiltin(&_Py_ID(reversed)); - if (!reversed) { - return NULL; - } + iter = _PyEval_GetBuiltin(&_Py_ID(reversed)); listreviterobject *it = (listreviterobject *)_it; - if (it->it_seq) { - return Py_BuildValue("N(O)n", reversed, it->it_seq, it->it_index); + if (it->it_index >= 0) { + return Py_BuildValue("N(O)n", iter, it->it_seq, it->it_index); } - Py_DECREF(reversed); } /* empty iterator, create an empty list */ list = PyList_New(0); if (list == NULL) return NULL; - return Py_BuildValue("N(N)", _PyEval_GetBuiltin(&_Py_ID(iter)), list); + return Py_BuildValue("N(N)", iter, list); } diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 96b97ca4be6d939..28ade64e056ad70 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2606,11 +2606,14 @@ dummy_func( assert(Py_TYPE(iter) == &PyListIter_Type); STAT_INC(FOR_ITER, hit); PyListObject *seq = it->it_seq; - if (seq == NULL || it->it_index >= PyList_GET_SIZE(seq)) { + if ((size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)) { + it->it_index = -1; + #ifndef Py_GIL_DISABLED if (seq != NULL) { it->it_seq = NULL; Py_DECREF(seq); } + #endif Py_DECREF(iter); STACK_SHRINK(1); /* Jump forward oparg, then skip following END_FOR and POP_TOP instructions */ @@ -2624,8 +2627,7 @@ dummy_func( _PyListIterObject *it = (_PyListIterObject *)iter; assert(Py_TYPE(iter) == &PyListIter_Type); PyListObject *seq = it->it_seq; - DEOPT_IF(seq == NULL); - DEOPT_IF(it->it_index >= PyList_GET_SIZE(seq)); + DEOPT_IF((size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)); } op(_ITER_NEXT_LIST, (iter -- iter, next)) { diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 58d238320276f4b..7a0e0e43be019c5 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -2201,8 +2201,7 @@ _PyListIterObject *it = (_PyListIterObject *)iter; assert(Py_TYPE(iter) == &PyListIter_Type); PyListObject *seq = it->it_seq; - if (seq == NULL) goto deoptimize; - if (it->it_index >= PyList_GET_SIZE(seq)) goto deoptimize; + if ((size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)) goto deoptimize; break; } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index a49223e4db53186..177bc327454f632 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -2541,11 +2541,14 @@ assert(Py_TYPE(iter) == &PyListIter_Type); STAT_INC(FOR_ITER, hit); PyListObject *seq = it->it_seq; - if (seq == NULL || it->it_index >= PyList_GET_SIZE(seq)) { + if ((size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)) { + it->it_index = -1; + #ifndef Py_GIL_DISABLED if (seq != NULL) { it->it_seq = NULL; Py_DECREF(seq); } + #endif Py_DECREF(iter); STACK_SHRINK(1); /* Jump forward oparg, then skip following END_FOR and POP_TOP instructions */ From 17773fcb863d5aef299487b07207c2ced8e9477e Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Wed, 14 Feb 2024 12:27:39 -0500 Subject: [PATCH 270/507] gh-115441: Fix missing braces warning (#115460) Removes `_py_object_state_INIT`. We want to initialize the `object_state` field to zero. --- Include/internal/pycore_runtime_init.h | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index 571a7d612c94e25..7a05c105d7bf128 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -169,7 +169,6 @@ extern PyTypeObject _PyExc_MemoryError; { .threshold = 10, }, \ }, \ }, \ - .object_state = _py_object_state_INIT(INTERP), \ .dtoa = _dtoa_state_INIT(&(INTERP)), \ .dict_state = _dict_state_INIT, \ .func_state = { \ @@ -206,16 +205,6 @@ extern PyTypeObject _PyExc_MemoryError; .context_ver = 1, \ } -#ifdef Py_TRACE_REFS -# define _py_object_state_INIT(INTERP) \ - { \ - .refchain = NULL, \ - } -#else -# define _py_object_state_INIT(INTERP) \ - { 0 } -#endif - // global objects From 49e8fdc1df41b6547fb3255f9e3a44dfb3b81fe0 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Wed, 14 Feb 2024 19:03:20 +0100 Subject: [PATCH 271/507] Docs: spell out sentence about ndbm/gdbm file formats (#115470) --- Doc/library/dbm.rst | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index b4f83d454ac6519..227b55c4315419e 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -61,10 +61,6 @@ the Oracle Berkeley DB. The Unix file access mode of the file (default: octal ``0o666``), used only when the database has to be created. -.. |incompat_note| replace:: - The file formats created by :mod:`dbm.gnu` and :mod:`dbm.ndbm` are incompatible - and can not be used interchangeably. - .. function:: open(file, flag='r', mode=0o666) Open a database and return the corresponding database object. @@ -205,7 +201,10 @@ The :mod:`dbm.gnu` module provides an interface to the :abbr:`GDBM (GNU dbm)` library, similar to the :mod:`dbm.ndbm` module, but with additional functionality like crash tolerance. -.. note:: |incompat_note| +.. note:: + + The file formats created by :mod:`dbm.gnu` and :mod:`dbm.ndbm` are incompatible + and can not be used interchangeably. .. exception:: error @@ -314,7 +313,10 @@ The :mod:`dbm.ndbm` module provides an interface to the This module can be used with the "classic" NDBM interface or the :abbr:`GDBM (GNU dbm)` compatibility interface. -.. note:: |incompat_note| +.. note:: + + The file formats created by :mod:`dbm.gnu` and :mod:`dbm.ndbm` are incompatible + and can not be used interchangeably. .. warning:: From 889cc43cb14a1b8c532a56680a93636507b9987a Mon Sep 17 00:00:00 2001 From: Seth Michael Larson <seth@python.org> Date: Wed, 14 Feb 2024 13:47:15 -0600 Subject: [PATCH 272/507] gh-112302: Move pip SBOM discovery to release-tools (#115360) --- Misc/sbom.spdx.json | 670 ----------------------------------- Tools/build/generate_sbom.py | 251 ------------- 2 files changed, 921 deletions(-) diff --git a/Misc/sbom.spdx.json b/Misc/sbom.spdx.json index 03b2db20553e568..e28eaea81d6aae1 100644 --- a/Misc/sbom.spdx.json +++ b/Misc/sbom.spdx.json @@ -1554,20 +1554,6 @@ } ], "fileName": "Modules/_decimal/libmpdec/vcdiv64.asm" - }, - { - "SPDXID": "SPDXRef-FILE-Lib-ensurepip-bundled-pip-24.0-py3-none-any.whl", - "checksums": [ - { - "algorithm": "SHA1", - "checksumValue": "e44313ae1e6af3c2bd3b60ab2fa8c34308d00555" - }, - { - "algorithm": "SHA256", - "checksumValue": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc" - } - ], - "fileName": "Lib/ensurepip/_bundled/pip-24.0-py3-none-any.whl" } ], "packages": [ @@ -1680,660 +1666,9 @@ "originator": "Organization: bytereef.org", "primaryPackagePurpose": "SOURCE", "versionInfo": "2.5.1" - }, - { - "SPDXID": "SPDXRef-PACKAGE-cachecontrol", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "95dedbec849f46dda3137866dc28b9d133fc9af55f5b805ab1291833e4457aa4" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/1d/e3/a22348e6226dcd585d5a4b5f0175b3a16dabfd3912cbeb02f321d00e56c7/cachecontrol-0.13.1-py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/cachecontrol@0.13.1", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "cachecontrol", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "0.13.1" - }, - { - "SPDXID": "SPDXRef-PACKAGE-colorama", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/colorama@0.4.6", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "colorama", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "0.4.6" - }, - { - "SPDXID": "SPDXRef-PACKAGE-distlib", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/8e/41/9307e4f5f9976bc8b7fea0b66367734e8faf3ec84bc0d412d8cfabbb66cd/distlib-0.3.8-py2.py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/distlib@0.3.8", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "distlib", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "0.3.8" - }, - { - "SPDXID": "SPDXRef-PACKAGE-distro", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "99522ca3e365cac527b44bde033f64c6945d90eb9f769703caaec52b09bbd3ff" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/f4/2c/c90a3adaf0ddb70afe193f5ebfb539612af57cffe677c3126be533df3098/distro-1.8.0-py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/distro@1.8.0", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "distro", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "1.8.0" - }, - { - "SPDXID": "SPDXRef-PACKAGE-msgpack", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/9f/4a/36d936e54cf71e23ad276564465f6a54fb129e3d61520b76e13e0bb29167/msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/msgpack@1.0.5", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "msgpack", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "1.0.5" - }, - { - "SPDXID": "SPDXRef-PACKAGE-packaging", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/05/8e/8de486cbd03baba4deef4142bd643a3e7bbe954a784dc1bb17142572d127/packaging-21.3-py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/packaging@21.3", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "packaging", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "21.3" - }, - { - "SPDXID": "SPDXRef-PACKAGE-platformdirs", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "cec7b889196b9144d088e4c57d9ceef7374f6c39694ad1577a0aab50d27ea28c" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/9e/d8/563a9fc17153c588c8c2042d2f0f84a89057cdb1c30270f589c88b42d62c/platformdirs-3.8.1-py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/platformdirs@3.8.1", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "platformdirs", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "3.8.1" - }, - { - "SPDXID": "SPDXRef-PACKAGE-pyparsing", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "d554a96d1a7d3ddaf7183104485bc19fd80543ad6ac5bdb6426719d766fb06c1" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/a4/24/6ae4c9c45cf99d96b06b5d99e25526c060303171fb0aea9da2bfd7dbde93/pyparsing-3.1.0-py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/pyparsing@3.1.0", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "pyparsing", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "3.1.0" - }, - { - "SPDXID": "SPDXRef-PACKAGE-pyproject-hooks", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/d5/ea/9ae603de7fbb3df820b23a70f6aff92bf8c7770043254ad8d2dc9d6bcba4/pyproject_hooks-1.0.0-py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/pyproject-hooks@1.0.0", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "pyproject-hooks", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "1.0.0" - }, - { - "SPDXID": "SPDXRef-PACKAGE-requests", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/requests@2.31.0", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "requests", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "2.31.0" - }, - { - "SPDXID": "SPDXRef-PACKAGE-certifi", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/4c/dd/2234eab22353ffc7d94e8d13177aaa050113286e93e7b40eae01fbf7c3d9/certifi-2023.7.22-py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/certifi@2023.7.22", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "certifi", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "2023.7.22" - }, - { - "SPDXID": "SPDXRef-PACKAGE-chardet", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "362777fb014af596ad31334fde1e8c327dfdb076e1960d1694662d46a6917ab9" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/74/8f/8fc49109009e8d2169d94d72e6b1f4cd45c13d147ba7d6170fb41f22b08f/chardet-5.1.0-py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/chardet@5.1.0", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "chardet", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "5.1.0" - }, - { - "SPDXID": "SPDXRef-PACKAGE-idna", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/idna@3.4", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "idna", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "3.4" - }, - { - "SPDXID": "SPDXRef-PACKAGE-rich", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/fc/1e/482e5eec0b89b593e81d78f819a9412849814e22225842b598908e7ac560/rich-13.4.2-py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/rich@13.4.2", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "rich", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "13.4.2" - }, - { - "SPDXID": "SPDXRef-PACKAGE-pygments", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/34/a7/37c8d68532ba71549db4212cb036dbd6161b40e463aba336770e80c72f84/Pygments-2.15.1-py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/pygments@2.15.1", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "pygments", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "2.15.1" - }, - { - "SPDXID": "SPDXRef-PACKAGE-typing-extensions", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/ec/6b/63cc3df74987c36fe26157ee12e09e8f9db4de771e0f3404263117e75b95/typing_extensions-4.7.1-py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/typing_extensions@4.7.1", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "typing_extensions", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "4.7.1" - }, - { - "SPDXID": "SPDXRef-PACKAGE-resolvelib", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "d2da45d1a8dfee81bdd591647783e340ef3bcb104b54c383f70d422ef5cc7dbf" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/d2/fc/e9ccf0521607bcd244aa0b3fbd574f71b65e9ce6a112c83af988bbbe2e23/resolvelib-1.0.1-py2.py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/resolvelib@1.0.1", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "resolvelib", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "1.0.1" - }, - { - "SPDXID": "SPDXRef-PACKAGE-setuptools", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/c7/42/be1c7bbdd83e1bfb160c94b9cafd8e25efc7400346cf7ccdbdb452c467fa/setuptools-68.0.0-py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/setuptools@68.0.0", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "setuptools", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "68.0.0" - }, - { - "SPDXID": "SPDXRef-PACKAGE-six", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/six@1.16.0", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "six", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "1.16.0" - }, - { - "SPDXID": "SPDXRef-PACKAGE-tenacity", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "2f277afb21b851637e8f52e6a613ff08734c347dc19ade928e519d7d2d8569b0" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/e7/b0/c23bd61e1b32c9b96fbca996c87784e196a812da8d621d8d04851f6c8181/tenacity-8.2.2-py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/tenacity@8.2.2", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "tenacity", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "8.2.2" - }, - { - "SPDXID": "SPDXRef-PACKAGE-tomli", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/tomli@2.0.1", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "tomli", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "2.0.1" - }, - { - "SPDXID": "SPDXRef-PACKAGE-truststore", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "e37a5642ae9fc48caa8f120b6283d77225d600d224965a672c9e8ef49ce4bb4c" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/20/56/7811d5439b6a56374f274a8672d8f18b4deadadeb3a9f0c86424b98b6f96/truststore-0.8.0-py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/truststore@0.8.0", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "truststore", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "0.8.0" - }, - { - "SPDXID": "SPDXRef-PACKAGE-webencodings", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/webencodings@0.5.1", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "webencodings", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "0.5.1" - }, - { - "SPDXID": "SPDXRef-PACKAGE-urllib3", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/48/fe/a5c6cc46e9fe9171d7ecf0f33ee7aae14642f8d74baa7af4d7840f9358be/urllib3-1.26.17-py2.py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/urllib3@1.26.17", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "urllib3", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "1.26.17" - }, - { - "SPDXID": "SPDXRef-PACKAGE-pip", - "checksums": [ - { - "algorithm": "SHA256", - "checksumValue": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc" - } - ], - "downloadLocation": "https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl", - "externalRefs": [ - { - "referenceCategory": "SECURITY", - "referenceLocator": "cpe:2.3:a:pypa:pip:24.0:*:*:*:*:*:*:*", - "referenceType": "cpe23Type" - }, - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/pip@24.0", - "referenceType": "purl" - } - ], - "licenseConcluded": "NOASSERTION", - "name": "pip", - "originator": "Organization: Python Packaging Authority", - "primaryPackagePurpose": "SOURCE", - "versionInfo": "24.0" } ], "relationships": [ - { - "relatedSpdxElement": "SPDXRef-PACKAGE-cachecontrol", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-certifi", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-chardet", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-colorama", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-distlib", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-distro", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-idna", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-msgpack", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-packaging", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-platformdirs", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-pygments", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-pyparsing", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-pyproject-hooks", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-requests", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-resolvelib", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-rich", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-setuptools", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-six", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-tenacity", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-tomli", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-truststore", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-typing-extensions", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-urllib3", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, - { - "relatedSpdxElement": "SPDXRef-PACKAGE-webencodings", - "relationshipType": "DEPENDS_ON", - "spdxElementId": "SPDXRef-PACKAGE-pip" - }, { "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-COPYING", "relationshipType": "CONTAINS", @@ -2888,11 +2223,6 @@ "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-vcdiv64.asm", "relationshipType": "CONTAINS", "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" - }, - { - "relatedSpdxElement": "SPDXRef-FILE-Lib-ensurepip-bundled-pip-24.0-py3-none-any.whl", - "relationshipType": "CONTAINS", - "spdxElementId": "SPDXRef-PACKAGE-pip" } ], "spdxVersion": "SPDX-2.3" diff --git a/Tools/build/generate_sbom.py b/Tools/build/generate_sbom.py index 82016dc408639db..201c81c4d14d793 100644 --- a/Tools/build/generate_sbom.py +++ b/Tools/build/generate_sbom.py @@ -53,8 +53,6 @@ class PackageFiles(typing.NamedTuple): # values to 'exclude' if we create new files within tracked # directories that aren't sourced from third-party packages. PACKAGE_TO_FILES = { - # NOTE: pip's entry in this structure is automatically generated in - # the 'discover_pip_sbom_package()' function below. "mpdecimal": PackageFiles( include=["Modules/_decimal/libmpdec/**"] ), @@ -127,264 +125,15 @@ def filter_gitignored_paths(paths: list[str]) -> list[str]: return sorted([line.split()[-1] for line in git_check_ignore_lines if line.startswith("::")]) -def fetch_package_metadata_from_pypi(project: str, version: str, filename: str | None = None) -> tuple[str, str] | None: - """ - Fetches the SHA256 checksum and download location from PyPI. - If we're given a filename then we match with that, otherwise we use wheels. - """ - # Get pip's download location from PyPI. Check that the checksum is correct too. - try: - raw_text = urlopen(f"https://pypi.org/pypi/{project}/{version}/json").read() - release_metadata = json.loads(raw_text) - url: dict[str, typing.Any] - - # Look for a matching artifact filename and then check - # its remote checksum to the local one. - for url in release_metadata["urls"]: - # pip can only use Python-only dependencies, so there's - # no risk of picking the 'incorrect' wheel here. - if ( - (filename is None and url["packagetype"] == "bdist_wheel") - or (filename is not None and url["filename"] == filename) - ): - break - else: - raise ValueError(f"No matching filename on PyPI for '{filename}'") - - # Successfully found the download URL for the matching artifact. - download_url = url["url"] - checksum_sha256 = url["digests"]["sha256"] - return download_url, checksum_sha256 - - except (OSError, ValueError) as e: - # Fail if we're running in CI where we should have an internet connection. - error_if( - "CI" in os.environ, - f"Couldn't fetch metadata for project '{project}' from PyPI: {e}" - ) - return None - - -def find_ensurepip_pip_wheel() -> pathlib.Path | None: - """Try to find the pip wheel bundled in ensurepip. If missing return None""" - - ensurepip_bundled_dir = CPYTHON_ROOT_DIR / "Lib/ensurepip/_bundled" - - pip_wheels = [] - try: - for wheel_filename in os.listdir(ensurepip_bundled_dir): - if wheel_filename.startswith("pip-"): - pip_wheels.append(wheel_filename) - else: - print(f"Unexpected wheel in ensurepip: '{wheel_filename}'") - sys.exit(1) - - # Ignore this error, likely caused by downstream distributors - # deleting the 'ensurepip/_bundled' directory. - except FileNotFoundError: - pass - - if len(pip_wheels) == 0: - return None - elif len(pip_wheels) > 1: - print("Multiple pip wheels detected in 'Lib/ensurepip/_bundled'") - sys.exit(1) - # Otherwise return the one pip wheel. - return ensurepip_bundled_dir / pip_wheels[0] - - -def maybe_remove_pip_and_deps_from_sbom(sbom_data: dict[str, typing.Any]) -> None: - """ - Removes pip and its dependencies from the SBOM data - if the pip wheel is removed from ensurepip. This is done - by redistributors of Python and pip. - """ - - # If there's a wheel we don't remove anything. - if find_ensurepip_pip_wheel() is not None: - return - - # Otherwise we traverse the relationships - # to find dependent packages to remove. - sbom_pip_spdx_id = spdx_id("SPDXRef-PACKAGE-pip") - sbom_spdx_ids_to_remove = {sbom_pip_spdx_id} - - # Find all package SPDXIDs that pip depends on. - for sbom_relationship in sbom_data["relationships"]: - if ( - sbom_relationship["relationshipType"] == "DEPENDS_ON" - and sbom_relationship["spdxElementId"] == sbom_pip_spdx_id - ): - sbom_spdx_ids_to_remove.add(sbom_relationship["relatedSpdxElement"]) - - # Remove all the packages and relationships. - sbom_data["packages"] = [ - sbom_package for sbom_package in sbom_data["packages"] - if sbom_package["SPDXID"] not in sbom_spdx_ids_to_remove - ] - sbom_data["relationships"] = [ - sbom_relationship for sbom_relationship in sbom_data["relationships"] - if sbom_relationship["relatedSpdxElement"] not in sbom_spdx_ids_to_remove - ] - - -def discover_pip_sbom_package(sbom_data: dict[str, typing.Any]) -> None: - """pip is a part of a packaging ecosystem (Python, surprise!) so it's actually - automatable to discover the metadata we need like the version and checksums - so let's do that on behalf of our friends at the PyPA. This function also - discovers vendored packages within pip and fetches their metadata. - """ - global PACKAGE_TO_FILES - - pip_wheel_filepath = find_ensurepip_pip_wheel() - if pip_wheel_filepath is None: - return # There's no pip wheel, nothing to discover. - - # Add the wheel filename to the list of files so the SBOM file - # and relationship generator can work its magic on the wheel too. - PACKAGE_TO_FILES["pip"] = PackageFiles( - include=[str(pip_wheel_filepath.relative_to(CPYTHON_ROOT_DIR))] - ) - - # Wheel filename format puts the version right after the project name. - pip_version = pip_wheel_filepath.name.split("-")[1] - pip_checksum_sha256 = hashlib.sha256( - pip_wheel_filepath.read_bytes() - ).hexdigest() - - pip_metadata = fetch_package_metadata_from_pypi( - project="pip", - version=pip_version, - filename=pip_wheel_filepath.name, - ) - # We couldn't fetch any metadata from PyPI, - # so we give up on verifying if we're not in CI. - if pip_metadata is None: - return - - pip_download_url, pip_actual_sha256 = pip_metadata - if pip_actual_sha256 != pip_checksum_sha256: - raise ValueError("Unexpected") - - # Parse 'pip/_vendor/vendor.txt' from the wheel for sub-dependencies. - with zipfile.ZipFile(pip_wheel_filepath) as whl: - vendor_txt_data = whl.read("pip/_vendor/vendor.txt").decode() - - # With this version regex we're assuming that pip isn't using pre-releases. - # If any version doesn't match we get a failure below, so we're safe doing this. - version_pin_re = re.compile(r"^([a-zA-Z0-9_.-]+)==([0-9.]*[0-9])$") - sbom_pip_dependency_spdx_ids = set() - for line in vendor_txt_data.splitlines(): - line = line.partition("#")[0].strip() # Strip comments and whitespace. - if not line: # Skip empty lines. - continue - - # Non-empty lines we must be able to match. - match = version_pin_re.match(line) - error_if(match is None, f"Couldn't parse line from pip vendor.txt: '{line}'") - assert match is not None # Make mypy happy. - - # Parse out and normalize the project name. - project_name, project_version = match.groups() - project_name = project_name.lower() - - # At this point if pip's metadata fetch succeeded we should - # expect this request to also succeed. - project_metadata = ( - fetch_package_metadata_from_pypi(project_name, project_version) - ) - assert project_metadata is not None - project_download_url, project_checksum_sha256 = project_metadata - - # Update our SBOM data with what we received from PyPI. - # Don't overwrite any existing values. - sbom_project_spdx_id = spdx_id(f"SPDXRef-PACKAGE-{project_name}") - sbom_pip_dependency_spdx_ids.add(sbom_project_spdx_id) - for package in sbom_data["packages"]: - if package["SPDXID"] != sbom_project_spdx_id: - continue - - # Only thing missing from this blob is the `licenseConcluded`, - # that needs to be triaged by human maintainers if the list changes. - package.update({ - "SPDXID": sbom_project_spdx_id, - "name": project_name, - "versionInfo": project_version, - "downloadLocation": project_download_url, - "checksums": [ - {"algorithm": "SHA256", "checksumValue": project_checksum_sha256} - ], - "externalRefs": [ - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": f"pkg:pypi/{project_name}@{project_version}", - "referenceType": "purl", - }, - ], - "primaryPackagePurpose": "SOURCE" - }) - break - - PACKAGE_TO_FILES[project_name] = PackageFiles(include=None) - - # Remove pip from the existing SBOM packages if it's there - # and then overwrite its entry with our own generated one. - sbom_pip_spdx_id = spdx_id("SPDXRef-PACKAGE-pip") - sbom_data["packages"] = [ - sbom_package - for sbom_package in sbom_data["packages"] - if sbom_package["name"] != "pip" - ] - sbom_data["packages"].append( - { - "SPDXID": sbom_pip_spdx_id, - "name": "pip", - "versionInfo": pip_version, - "originator": "Organization: Python Packaging Authority", - "licenseConcluded": "NOASSERTION", - "downloadLocation": pip_download_url, - "checksums": [ - {"algorithm": "SHA256", "checksumValue": pip_checksum_sha256} - ], - "externalRefs": [ - { - "referenceCategory": "SECURITY", - "referenceLocator": f"cpe:2.3:a:pypa:pip:{pip_version}:*:*:*:*:*:*:*", - "referenceType": "cpe23Type", - }, - { - "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": f"pkg:pypi/pip@{pip_version}", - "referenceType": "purl", - }, - ], - "primaryPackagePurpose": "SOURCE", - } - ) - for sbom_dep_spdx_id in sorted(sbom_pip_dependency_spdx_ids): - sbom_data["relationships"].append({ - "spdxElementId": sbom_pip_spdx_id, - "relatedSpdxElement": sbom_dep_spdx_id, - "relationshipType": "DEPENDS_ON" - }) - - def main() -> None: sbom_path = CPYTHON_ROOT_DIR / "Misc/sbom.spdx.json" sbom_data = json.loads(sbom_path.read_bytes()) - # Check if pip should be removed if the wheel is missing. - # We can't reset the SBOM relationship data until checking this. - maybe_remove_pip_and_deps_from_sbom(sbom_data) - # We regenerate all of this information. Package information # should be preserved though since that is edited by humans. sbom_data["files"] = [] sbom_data["relationships"] = [] - # Insert pip's SBOM metadata from the wheel. - discover_pip_sbom_package(sbom_data) - # Ensure all packages in this tool are represented also in the SBOM file. actual_names = {package["name"] for package in sbom_data["packages"]} expected_names = set(PACKAGE_TO_FILES) From d9f4cbe5e1e3c31518724d87d0d379d7ce6823ca Mon Sep 17 00:00:00 2001 From: Ken Jin <kenjin@python.org> Date: Thu, 15 Feb 2024 03:48:11 +0800 Subject: [PATCH 273/507] Add myself to various CODEOWNERS (GH-115481) --- .github/CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7933d3195505767..5dbfbbb8ebaf7e9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -37,6 +37,8 @@ Python/flowgraph.c @markshannon @iritkatriel Python/ast_opt.c @isidentical Python/bytecodes.c @markshannon @gvanrossum Python/optimizer*.c @markshannon @gvanrossum +Python/optimizer_analysis.c @Fidget-Spinner +Python/tier2_redundancy_eliminator_bytecodes.c @Fidget-Spinner Lib/test/test_patma.py @brandtbucher Lib/test/test_type_*.py @JelleZijlstra Lib/test/test_capi/test_misc.py @markshannon @gvanrossum From a95b1a56bbba76a382a5c676b71db025915e8695 Mon Sep 17 00:00:00 2001 From: mpage <mpage@meta.com> Date: Wed, 14 Feb 2024 12:15:05 -0800 Subject: [PATCH 274/507] gh-115041: Add wrappers that are atomic only in free-threaded builds (#115046) These are intended to be used in places where atomics are required in free-threaded builds but not in the default build. We don't want to introduce the potential performance overhead of an atomic operation in the default build. --- .../internal/pycore_pyatomic_ft_wrappers.h | 35 +++++++++++++++++++ Makefile.pre.in | 1 + PCbuild/pythoncore.vcxproj | 1 + PCbuild/pythoncore.vcxproj.filters | 3 ++ 4 files changed, 40 insertions(+) create mode 100644 Include/internal/pycore_pyatomic_ft_wrappers.h diff --git a/Include/internal/pycore_pyatomic_ft_wrappers.h b/Include/internal/pycore_pyatomic_ft_wrappers.h new file mode 100644 index 000000000000000..d1313976da1cfce --- /dev/null +++ b/Include/internal/pycore_pyatomic_ft_wrappers.h @@ -0,0 +1,35 @@ +// This header file provides wrappers around the atomic operations found in +// `pyatomic.h` that are only atomic in free-threaded builds. +// +// These are intended to be used in places where atomics are required in +// free-threaded builds, but not in the default build, and we don't want to +// introduce the potential performance overhead of an atomic operation in the +// default build. +// +// All usages of these macros should be replaced with unconditionally atomic or +// non-atomic versions, and this file should be removed, once the dust settles +// on free threading. +#ifndef Py_ATOMIC_FT_WRAPPERS_H +#define Py_ATOMIC_FT_WRAPPERS_H +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef Py_BUILD_CORE +#error "this header requires Py_BUILD_CORE define" +#endif + +#ifdef Py_GIL_DISABLED +#define FT_ATOMIC_LOAD_SSIZE_RELAXED(value) \ + _Py_atomic_load_ssize_relaxed(&value) +#define FT_ATOMIC_STORE_SSIZE_RELAXED(value, new_value) \ + _Py_atomic_store_ssize_relaxed(&value, new_value) +#else +#define FT_ATOMIC_LOAD_SSIZE_RELAXED(value) value +#define FT_ATOMIC_STORE_SSIZE_RELAXED(value, new_value) value = new_value +#endif + +#ifdef __cplusplus +} +#endif +#endif /* !Py_ATOMIC_FT_WRAPPERS_H */ diff --git a/Makefile.pre.in b/Makefile.pre.in index 96886adf309d81f..8252e6631c5af58 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1150,6 +1150,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/internal/pycore_parser.h \ $(srcdir)/Include/internal/pycore_pathconfig.h \ $(srcdir)/Include/internal/pycore_pyarena.h \ + $(srcdir)/Include/internal/pycore_pyatomic_ft_wrappers.h \ $(srcdir)/Include/internal/pycore_pybuffer.h \ $(srcdir)/Include/internal/pycore_pyerrors.h \ $(srcdir)/Include/internal/pycore_pyhash.h \ diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 4cc0ca4b9af8deb..abfafbb2a32f45c 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -266,6 +266,7 @@ <ClInclude Include="..\Include\internal\pycore_parking_lot.h" /> <ClInclude Include="..\Include\internal\pycore_pathconfig.h" /> <ClInclude Include="..\Include\internal\pycore_pyarena.h" /> + <ClInclude Include="..\Include\internal\pycore_pyatomic_ft_wrappers.h" /> <ClInclude Include="..\Include\internal\pycore_pyerrors.h" /> <ClInclude Include="..\Include\internal\pycore_pyhash.h" /> <ClInclude Include="..\Include\internal\pycore_pylifecycle.h" /> diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index ceaa21217267cf1..d14f5a6d7fb0fca 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -723,6 +723,9 @@ <ClInclude Include="..\Include\internal\pycore_pyarena.h"> <Filter>Include\internal</Filter> </ClInclude> + <ClInclude Include="..\Include\internal\pycore_pyatomic_ft_wrappers.h"> + <Filter>Include\internal</Filter> + </ClInclude> <ClInclude Include="..\Include\internal\pycore_pyerrors.h"> <Filter>Include\internal</Filter> </ClInclude> From 326119d3731f784aa9f5d4afa7b687dd1ab1d916 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Wed, 14 Feb 2024 16:41:29 -0500 Subject: [PATCH 275/507] gh-112529: Use _PyThread_Id() in mimalloc in free-threaded build (#115488) The free-threaded GC uses mimallocs segment thread IDs to restore the overwritten `ob_tid` thread ids in PyObjects. For that reason, it's important that PyObjects and mimalloc use the same identifiers. --- Include/internal/mimalloc/mimalloc/prim.h | 8 +++++++- Include/internal/pycore_mimalloc.h | 9 ++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Include/internal/mimalloc/mimalloc/prim.h b/Include/internal/mimalloc/mimalloc/prim.h index 4b9e4dc4194d776..8a60d528458e6c2 100644 --- a/Include/internal/mimalloc/mimalloc/prim.h +++ b/Include/internal/mimalloc/mimalloc/prim.h @@ -131,7 +131,13 @@ extern bool _mi_process_is_initialized; // has mi_process_init been static inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept; -#if defined(_WIN32) +#ifdef MI_PRIM_THREAD_ID + +static inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept { + return MI_PRIM_THREAD_ID(); +} + +#elif defined(_WIN32) #define WIN32_LEAN_AND_MEAN #include <windows.h> diff --git a/Include/internal/pycore_mimalloc.h b/Include/internal/pycore_mimalloc.h index 1e7ed5a4ca62e20..14c98108ec131e0 100644 --- a/Include/internal/pycore_mimalloc.h +++ b/Include/internal/pycore_mimalloc.h @@ -20,9 +20,12 @@ typedef enum { #include "pycore_pymem.h" #ifdef WITH_MIMALLOC -#define MI_DEBUG_UNINIT PYMEM_CLEANBYTE -#define MI_DEBUG_FREED PYMEM_DEADBYTE -#define MI_DEBUG_PADDING PYMEM_FORBIDDENBYTE +# ifdef Py_GIL_DISABLED +# define MI_PRIM_THREAD_ID _Py_ThreadId +# endif +# define MI_DEBUG_UNINIT PYMEM_CLEANBYTE +# define MI_DEBUG_FREED PYMEM_DEADBYTE +# define MI_DEBUG_PADDING PYMEM_FORBIDDENBYTE #ifdef Py_DEBUG # define MI_DEBUG 1 #else From 3e7b7df5cbaad5617cc28f0c005010787c48e6d6 Mon Sep 17 00:00:00 2001 From: Victor Stinner <vstinner@python.org> Date: Wed, 14 Feb 2024 23:35:06 +0100 Subject: [PATCH 276/507] gh-114570: Add PythonFinalizationError exception (#115352) Add PythonFinalizationError exception. This exception derived from RuntimeError is raised when an operation is blocked during the Python finalization. The following functions now raise PythonFinalizationError, instead of RuntimeError: * _thread.start_new_thread() * subprocess.Popen * os.fork() * os.fork1() * os.forkpty() Morever, _winapi.Overlapped finalizer now logs an unraisable PythonFinalizationError, instead of an unraisable RuntimeError. --- Doc/library/exceptions.rst | 18 ++++++++++++++++++ Doc/library/sys.rst | 2 ++ Doc/whatsnew/3.13.rst | 15 +++++++++++++++ Include/cpython/pyerrors.h | 2 ++ Lib/test/exception_hierarchy.txt | 1 + Lib/test/test_pickle.py | 1 + ...4-02-12-17-18-26.gh-issue-114570.BzwMlJ.rst | 3 +++ Modules/_posixsubprocess.c | 2 +- Modules/_threadmodule.c | 2 +- Modules/_winapi.c | 2 +- Modules/posixmodule.c | 6 +++--- Objects/exceptions.c | 5 +++++ Tools/c-analyzer/cpython/globals-to-fix.tsv | 2 ++ 13 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-12-17-18-26.gh-issue-114570.BzwMlJ.rst diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index 3191315049ad5a4..88417b40e4aa7f9 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -416,6 +416,24 @@ The following exceptions are the exceptions that are usually raised. handling in C, most floating point operations are not checked. +.. exception:: PythonFinalizationError + + This exception is derived from :exc:`RuntimeError`. It is raised when + an operation is blocked during interpreter shutdown also known as + :term:`Python finalization <interpreter shutdown>`. + + Examples of operations which can be blocked with a + :exc:`PythonFinalizationError` during the Python finalization: + + * Creating a new Python thread. + * :func:`os.fork`. + + See also the :func:`sys.is_finalizing` function. + + .. versionadded:: 3.13 + Previously, a plain :exc:`RuntimeError` was raised. + + .. exception:: RecursionError This exception is derived from :exc:`RuntimeError`. It is raised when the diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index ad8857fc2807f70..351c44b1915159f 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -1202,6 +1202,8 @@ always available. Return :const:`True` if the main Python interpreter is :term:`shutting down <interpreter shutdown>`. Return :const:`False` otherwise. + See also the :exc:`PythonFinalizationError` exception. + .. versionadded:: 3.5 .. data:: last_exc diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index b14fb4e5392a2c3..1e0764144a28553 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -160,6 +160,21 @@ Other Language Changes (Contributed by Levi Sabah, Zackery Spytz and Hugo van Kemenade in :gh:`73965`.) +* Add :exc:`PythonFinalizationError` exception. This exception derived from + :exc:`RuntimeError` is raised when an operation is blocked during + the :term:`Python finalization <interpreter shutdown>`. + + The following functions now raise PythonFinalizationError, instead of + :exc:`RuntimeError`: + + * :func:`_thread.start_new_thread`. + * :class:`subprocess.Popen`. + * :func:`os.fork`. + * :func:`os.forkpty`. + + (Contributed by Victor Stinner in :gh:`114570`.) + + New Modules =========== diff --git a/Include/cpython/pyerrors.h b/Include/cpython/pyerrors.h index 479b908fb7058ac..32c5884cd21341c 100644 --- a/Include/cpython/pyerrors.h +++ b/Include/cpython/pyerrors.h @@ -122,4 +122,6 @@ PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalErrorFunc( PyAPI_FUNC(void) PyErr_FormatUnraisable(const char *, ...); +PyAPI_DATA(PyObject *) PyExc_PythonFinalizationError; + #define Py_FatalError(message) _Py_FatalErrorFunc(__func__, (message)) diff --git a/Lib/test/exception_hierarchy.txt b/Lib/test/exception_hierarchy.txt index 217ee15d4c8af54..65f54859e2a21d0 100644 --- a/Lib/test/exception_hierarchy.txt +++ b/Lib/test/exception_hierarchy.txt @@ -40,6 +40,7 @@ BaseException ├── ReferenceError ├── RuntimeError │ ├── NotImplementedError + │ ├── PythonFinalizationError │ └── RecursionError ├── StopAsyncIteration ├── StopIteration diff --git a/Lib/test/test_pickle.py b/Lib/test/test_pickle.py index 5e187e5189d1179..19f977971570b76 100644 --- a/Lib/test/test_pickle.py +++ b/Lib/test/test_pickle.py @@ -564,6 +564,7 @@ def test_exceptions(self): if exc in (BlockingIOError, ResourceWarning, StopAsyncIteration, + PythonFinalizationError, RecursionError, EncodingWarning, BaseExceptionGroup, diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-12-17-18-26.gh-issue-114570.BzwMlJ.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-12-17-18-26.gh-issue-114570.BzwMlJ.rst new file mode 100644 index 000000000000000..828d47d0e18d6b6 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-12-17-18-26.gh-issue-114570.BzwMlJ.rst @@ -0,0 +1,3 @@ +Add :exc:`PythonFinalizationError` exception. This exception derived from +:exc:`RuntimeError` is raised when an operation is blocked during the +:term:`Python finalization <interpreter shutdown>`. Patch by Victor Stinner. diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c index aa1a300e4378dd9..bcbbe70680b8e76 100644 --- a/Modules/_posixsubprocess.c +++ b/Modules/_posixsubprocess.c @@ -1032,7 +1032,7 @@ subprocess_fork_exec_impl(PyObject *module, PyObject *process_args, PyInterpreterState *interp = _PyInterpreterState_GET(); if ((preexec_fn != Py_None) && interp->finalizing) { - PyErr_SetString(PyExc_RuntimeError, + PyErr_SetString(PyExc_PythonFinalizationError, "preexec_fn not supported at interpreter shutdown"); return NULL; } diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index d7840eaf45e8d69..da6a8bc7b120fed 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1304,7 +1304,7 @@ do_start_new_thread(thread_module_state* state, return -1; } if (interp->finalizing) { - PyErr_SetString(PyExc_RuntimeError, + PyErr_SetString(PyExc_PythonFinalizationError, "can't create new thread at interpreter shutdown"); return -1; } diff --git a/Modules/_winapi.c b/Modules/_winapi.c index 83a4ccd4802ae01..8f9b8520bb3f349 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -139,7 +139,7 @@ overlapped_dealloc(OverlappedObject *self) { /* The operation is still pending -- give a warning. This will probably only happen on Windows XP. */ - PyErr_SetString(PyExc_RuntimeError, + PyErr_SetString(PyExc_PythonFinalizationError, "I/O operations still in flight while destroying " "Overlapped object, the process may crash"); PyErr_WriteUnraisable(NULL); diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index ef6d65623bf0388..958b5a5e6e24066 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -7841,7 +7841,7 @@ os_fork1_impl(PyObject *module) PyInterpreterState *interp = _PyInterpreterState_GET(); if (interp->finalizing) { - PyErr_SetString(PyExc_RuntimeError, + PyErr_SetString(PyExc_PythonFinalizationError, "can't fork at interpreter shutdown"); return NULL; } @@ -7885,7 +7885,7 @@ os_fork_impl(PyObject *module) pid_t pid; PyInterpreterState *interp = _PyInterpreterState_GET(); if (interp->finalizing) { - PyErr_SetString(PyExc_RuntimeError, + PyErr_SetString(PyExc_PythonFinalizationError, "can't fork at interpreter shutdown"); return NULL; } @@ -8718,7 +8718,7 @@ os_forkpty_impl(PyObject *module) PyInterpreterState *interp = _PyInterpreterState_GET(); if (interp->finalizing) { - PyErr_SetString(PyExc_RuntimeError, + PyErr_SetString(PyExc_PythonFinalizationError, "can't fork at interpreter shutdown"); return NULL; } diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 3df3a9b3b1a2530..63c461d34fb4ff2 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2177,6 +2177,10 @@ SimpleExtendsException(PyExc_Exception, RuntimeError, SimpleExtendsException(PyExc_RuntimeError, RecursionError, "Recursion limit exceeded."); +// PythonFinalizationError extends RuntimeError +SimpleExtendsException(PyExc_RuntimeError, PythonFinalizationError, + "Operation blocked during Python finalization."); + /* * NotImplementedError extends RuntimeError */ @@ -3641,6 +3645,7 @@ static struct static_exception static_exceptions[] = { ITEM(KeyError), // base: LookupError(Exception) ITEM(ModuleNotFoundError), // base: ImportError(Exception) ITEM(NotImplementedError), // base: RuntimeError(Exception) + ITEM(PythonFinalizationError), // base: RuntimeError(Exception) ITEM(RecursionError), // base: RuntimeError(Exception) ITEM(UnboundLocalError), // base: NameError(Exception) ITEM(UnicodeError), // base: ValueError(Exception) diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 5c5016f71371640..45119664af4362a 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -189,6 +189,7 @@ Objects/exceptions.c - _PyExc_ProcessLookupError - Objects/exceptions.c - _PyExc_TimeoutError - Objects/exceptions.c - _PyExc_EOFError - Objects/exceptions.c - _PyExc_RuntimeError - +Objects/exceptions.c - _PyExc_PythonFinalizationError - Objects/exceptions.c - _PyExc_RecursionError - Objects/exceptions.c - _PyExc_NotImplementedError - Objects/exceptions.c - _PyExc_NameError - @@ -254,6 +255,7 @@ Objects/exceptions.c - PyExc_ProcessLookupError - Objects/exceptions.c - PyExc_TimeoutError - Objects/exceptions.c - PyExc_EOFError - Objects/exceptions.c - PyExc_RuntimeError - +Objects/exceptions.c - PyExc_PythonFinalizationError - Objects/exceptions.c - PyExc_RecursionError - Objects/exceptions.c - PyExc_NotImplementedError - Objects/exceptions.c - PyExc_NameError - From 468430189d3ebe16f3067279f9be0fe82cdfadf6 Mon Sep 17 00:00:00 2001 From: Eric Snow <ericsnowcurrently@gmail.com> Date: Wed, 14 Feb 2024 16:07:22 -0700 Subject: [PATCH 277/507] gh-115482: Assume the Main Interpreter is Always Running "main" (gh-115484) This is a temporary fix to unblock embedders that do not call Py_Main(). _PyInterpreterState_IsRunningMain() will always return true for the main interpreter, even in corner cases where it technically should not. The (future) full solution will do the right thing in those corner cases. --- Python/pystate.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Python/pystate.c b/Python/pystate.c index 82c955882185e85..08ec586963ce114 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1044,7 +1044,14 @@ _PyInterpreterState_SetNotRunningMain(PyInterpreterState *interp) int _PyInterpreterState_IsRunningMain(PyInterpreterState *interp) { - return (interp->threads.main != NULL); + if (interp->threads.main != NULL) { + return 1; + } + // For now, we assume the main interpreter is always running. + if (_Py_IsMainInterpreter(interp)) { + return 1; + } + return 0; } int From 18343c09856fcac11f813a5a59ffe3a35928c0cc Mon Sep 17 00:00:00 2001 From: Brett Cannon <brett@python.org> Date: Wed, 14 Feb 2024 16:51:23 -0800 Subject: [PATCH 278/507] GH-113516: don't set `LDSHARED` when building for WASI (GH-115495) --- .../Tools-Demos/2024-02-14-15-58-13.gh-issue-113516.TyIHWx.rst | 1 + Tools/wasm/wasi-env | 1 - Tools/wasm/wasi.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Tools-Demos/2024-02-14-15-58-13.gh-issue-113516.TyIHWx.rst diff --git a/Misc/NEWS.d/next/Tools-Demos/2024-02-14-15-58-13.gh-issue-113516.TyIHWx.rst b/Misc/NEWS.d/next/Tools-Demos/2024-02-14-15-58-13.gh-issue-113516.TyIHWx.rst new file mode 100644 index 000000000000000..0dd1128e02de811 --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2024-02-14-15-58-13.gh-issue-113516.TyIHWx.rst @@ -0,0 +1 @@ +Don't set ``LDSHARED`` when building for WASI. diff --git a/Tools/wasm/wasi-env b/Tools/wasm/wasi-env index 48908b02e60b960..e6c6fb2d8e47e75 100755 --- a/Tools/wasm/wasi-env +++ b/Tools/wasm/wasi-env @@ -55,7 +55,6 @@ if command -v ccache >/dev/null 2>&1; then CXX="ccache ${CXX}" fi -LDSHARED="${WASI_SDK_PATH}/bin/wasm-ld" AR="${WASI_SDK_PATH}/bin/llvm-ar" RANLIB="${WASI_SDK_PATH}/bin/ranlib" diff --git a/Tools/wasm/wasi.py b/Tools/wasm/wasi.py index 46ecae74a9ecea7..1e75db5c7b83299 100644 --- a/Tools/wasm/wasi.py +++ b/Tools/wasm/wasi.py @@ -165,7 +165,7 @@ def wasi_sdk_env(context): wasi_sdk_path = context.wasi_sdk_path sysroot = wasi_sdk_path / "share" / "wasi-sysroot" env = {"CC": "clang", "CPP": "clang-cpp", "CXX": "clang++", - "LDSHARED": "wasm-ld", "AR": "llvm-ar", "RANLIB": "ranlib"} + "AR": "llvm-ar", "RANLIB": "ranlib"} for env_var, binary_name in list(env.items()): env[env_var] = os.fsdecode(wasi_sdk_path / "bin" / binary_name) From ed23839dc5ce21ea9ca087fac170fa1412005210 Mon Sep 17 00:00:00 2001 From: Ken Jin <kenjin@python.org> Date: Thu, 15 Feb 2024 14:01:24 +0800 Subject: [PATCH 279/507] Trigger JIT CI with optimizer files (#115483) * Trigger JIT CI with optimizer files --- .github/workflows/jit.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml index 69648d87947ad66..69c7b45376a411f 100644 --- a/.github/workflows/jit.yml +++ b/.github/workflows/jit.yml @@ -4,10 +4,14 @@ on: paths: - '**jit**' - 'Python/bytecodes.c' + - 'Python/optimizer*.c' + - 'Python/tier2_redundancy_eliminator_bytecodes.c' push: paths: - '**jit**' - 'Python/bytecodes.c' + - 'Python/optimizer*.c' + - 'Python/tier2_redundancy_eliminator_bytecodes.c' workflow_dispatch: concurrency: From 4ebf8fbdab1c64041ff0ea54b3d15624f6e01511 Mon Sep 17 00:00:00 2001 From: Ken Jin <kenjin@python.org> Date: Thu, 15 Feb 2024 14:02:18 +0800 Subject: [PATCH 280/507] gh-115480: Type and constant propagation for int BINARY_OPs (GH-115478) --- Python/optimizer_analysis.c | 12 ++++ .../tier2_redundancy_eliminator_bytecodes.c | 62 +++++++++++++++-- Python/tier2_redundancy_eliminator_cases.c.h | 68 ++++++++++++++++--- 3 files changed, 126 insertions(+), 16 deletions(-) diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 49974520de924da..d73bc310345f415 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -341,6 +341,18 @@ sym_new_const(_Py_UOpsAbstractInterpContext *ctx, PyObject *const_val) return temp; } +static inline bool +is_const(_Py_UOpsSymType *sym) +{ + return sym->const_val != NULL; +} + +static inline PyObject * +get_const(_Py_UOpsSymType *sym) +{ + return sym->const_val; +} + static _Py_UOpsSymType* sym_new_null(_Py_UOpsAbstractInterpContext *ctx) { diff --git a/Python/tier2_redundancy_eliminator_bytecodes.c b/Python/tier2_redundancy_eliminator_bytecodes.c index 3272b187f20d0eb..39ea0eef6276326 100644 --- a/Python/tier2_redundancy_eliminator_bytecodes.c +++ b/Python/tier2_redundancy_eliminator_bytecodes.c @@ -81,12 +81,62 @@ dummy_func(void) { op(_BINARY_OP_ADD_INT, (left, right -- res)) { - // TODO constant propagation - (void)left; - (void)right; - res = sym_new_known_type(ctx, &PyLong_Type); - if (res == NULL) { - goto out_of_space; + if (is_const(left) && is_const(right)) { + assert(PyLong_CheckExact(get_const(left))); + assert(PyLong_CheckExact(get_const(right))); + PyObject *temp = _PyLong_Add((PyLongObject *)get_const(left), + (PyLongObject *)get_const(right)); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + // TODO replace opcode with constant propagated one and add tests! + } + else { + res = sym_new_known_type(ctx, &PyLong_Type); + if (res == NULL) { + goto out_of_space; + } + } + } + + op(_BINARY_OP_SUBTRACT_INT, (left, right -- res)) { + if (is_const(left) && is_const(right)) { + assert(PyLong_CheckExact(get_const(left))); + assert(PyLong_CheckExact(get_const(right))); + PyObject *temp = _PyLong_Subtract((PyLongObject *)get_const(left), + (PyLongObject *)get_const(right)); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + // TODO replace opcode with constant propagated one and add tests! + } + else { + res = sym_new_known_type(ctx, &PyLong_Type); + if (res == NULL) { + goto out_of_space; + } + } + } + + op(_BINARY_OP_MULTIPLY_INT, (left, right -- res)) { + if (is_const(left) && is_const(right)) { + assert(PyLong_CheckExact(get_const(left))); + assert(PyLong_CheckExact(get_const(right))); + PyObject *temp = _PyLong_Multiply((PyLongObject *)get_const(left), + (PyLongObject *)get_const(right)); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + // TODO replace opcode with constant propagated one and add tests! + } + else { + res = sym_new_known_type(ctx, &PyLong_Type); + if (res == NULL) { + goto out_of_space; + } } } diff --git a/Python/tier2_redundancy_eliminator_cases.c.h b/Python/tier2_redundancy_eliminator_cases.c.h index c2b7bbaf1c44810..a9617f51ef46158 100644 --- a/Python/tier2_redundancy_eliminator_cases.c.h +++ b/Python/tier2_redundancy_eliminator_cases.c.h @@ -180,9 +180,28 @@ } case _BINARY_OP_MULTIPLY_INT: { + _Py_UOpsSymType *right; + _Py_UOpsSymType *left; _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); - if (res == NULL) goto out_of_space; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + if (is_const(left) && is_const(right)) { + assert(PyLong_CheckExact(get_const(left))); + assert(PyLong_CheckExact(get_const(right))); + PyObject *temp = _PyLong_Multiply((PyLongObject *)get_const(left), + (PyLongObject *)get_const(right)); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + // TODO replace opcode with constant propagated one and add tests! + } + else { + res = sym_new_known_type(ctx, &PyLong_Type); + if (res == NULL) { + goto out_of_space; + } + } stack_pointer[-2] = res; stack_pointer += -1; break; @@ -194,12 +213,22 @@ _Py_UOpsSymType *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - // TODO constant propagation - (void)left; - (void)right; - res = sym_new_known_type(ctx, &PyLong_Type); - if (res == NULL) { - goto out_of_space; + if (is_const(left) && is_const(right)) { + assert(PyLong_CheckExact(get_const(left))); + assert(PyLong_CheckExact(get_const(right))); + PyObject *temp = _PyLong_Add((PyLongObject *)get_const(left), + (PyLongObject *)get_const(right)); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + // TODO replace opcode with constant propagated one and add tests! + } + else { + res = sym_new_known_type(ctx, &PyLong_Type); + if (res == NULL) { + goto out_of_space; + } } stack_pointer[-2] = res; stack_pointer += -1; @@ -207,9 +236,28 @@ } case _BINARY_OP_SUBTRACT_INT: { + _Py_UOpsSymType *right; + _Py_UOpsSymType *left; _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); - if (res == NULL) goto out_of_space; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + if (is_const(left) && is_const(right)) { + assert(PyLong_CheckExact(get_const(left))); + assert(PyLong_CheckExact(get_const(right))); + PyObject *temp = _PyLong_Subtract((PyLongObject *)get_const(left), + (PyLongObject *)get_const(right)); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + // TODO replace opcode with constant propagated one and add tests! + } + else { + res = sym_new_known_type(ctx, &PyLong_Type); + if (res == NULL) { + goto out_of_space; + } + } stack_pointer[-2] = res; stack_pointer += -1; break; From 474204765bdbdd4bc84a8ba49d3a6558e9e4e3fd Mon Sep 17 00:00:00 2001 From: Ned Batchelder <ned@nedbatchelder.com> Date: Thu, 15 Feb 2024 02:14:03 -0500 Subject: [PATCH 281/507] docs: use consistent .append() in dis.rst (#115434) The STACK variable is described as like a Python list, so pushing to it should be done with .append() consistently throughout. --- Doc/library/dis.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index e654760fb91c650..190e994a12cc713 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -1606,7 +1606,7 @@ iterations of the loop. value = STACK.pop() result = func(value) - STACK.push(result) + STACK.append(result) * ``oparg == 1``: call :func:`str` on *value* * ``oparg == 2``: call :func:`repr` on *value* @@ -1623,7 +1623,7 @@ iterations of the loop. value = STACK.pop() result = value.__format__("") - STACK.push(result) + STACK.append(result) Used for implementing formatted literal strings (f-strings). @@ -1636,7 +1636,7 @@ iterations of the loop. spec = STACK.pop() value = STACK.pop() result = value.__format__(spec) - STACK.push(result) + STACK.append(result) Used for implementing formatted literal strings (f-strings). @@ -1783,7 +1783,7 @@ iterations of the loop. arg2 = STACK.pop() arg1 = STACK.pop() result = intrinsic2(arg1, arg2) - STACK.push(result) + STACK.append(result) The operand determines which intrinsic function is called: From dc978f6ab62b68c66d3b354638c310ee1cc844a6 Mon Sep 17 00:00:00 2001 From: mpage <mpage@meta.com> Date: Thu, 15 Feb 2024 00:22:47 -0800 Subject: [PATCH 282/507] gh-112050: Make collections.deque thread-safe in free-threaded builds (#113830) Use critical sections to make deque methods that operate on mutable state thread-safe when the GIL is disabled. This is mostly accomplished by using the @critical_section Argument Clinic directive, though there are a few places where this was not possible and critical sections had to be manually acquired/released. --- .../internal/pycore_pyatomic_ft_wrappers.h | 2 + ...-01-08-21-57-41.gh-issue-112050.qwgjx1.rst | 1 + Modules/_collectionsmodule.c | 239 ++++++++++++++---- Modules/clinic/_collectionsmodule.c.h | 157 +++++++++++- 4 files changed, 338 insertions(+), 61 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-08-21-57-41.gh-issue-112050.qwgjx1.rst diff --git a/Include/internal/pycore_pyatomic_ft_wrappers.h b/Include/internal/pycore_pyatomic_ft_wrappers.h index d1313976da1cfce..cbbe90e009c8d29 100644 --- a/Include/internal/pycore_pyatomic_ft_wrappers.h +++ b/Include/internal/pycore_pyatomic_ft_wrappers.h @@ -20,11 +20,13 @@ extern "C" { #endif #ifdef Py_GIL_DISABLED +#define FT_ATOMIC_LOAD_SSIZE(value) _Py_atomic_load_ssize(&value) #define FT_ATOMIC_LOAD_SSIZE_RELAXED(value) \ _Py_atomic_load_ssize_relaxed(&value) #define FT_ATOMIC_STORE_SSIZE_RELAXED(value, new_value) \ _Py_atomic_store_ssize_relaxed(&value, new_value) #else +#define FT_ATOMIC_LOAD_SSIZE(value) value #define FT_ATOMIC_LOAD_SSIZE_RELAXED(value) value #define FT_ATOMIC_STORE_SSIZE_RELAXED(value, new_value) value = new_value #endif diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-08-21-57-41.gh-issue-112050.qwgjx1.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-08-21-57-41.gh-issue-112050.qwgjx1.rst new file mode 100644 index 000000000000000..3041d6513c6597c --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-08-21-57-41.gh-issue-112050.qwgjx1.rst @@ -0,0 +1 @@ +Make methods on :class:`collections.deque` thread-safe when the GIL is disabled. diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c index 4fa76d62bc3f8d8..309d63c9bf7cbe7 100644 --- a/Modules/_collectionsmodule.c +++ b/Modules/_collectionsmodule.c @@ -3,6 +3,7 @@ #include "pycore_dict.h" // _PyDict_GetItem_KnownHash() #include "pycore_long.h" // _PyLong_GetZero() #include "pycore_moduleobject.h" // _PyModule_GetState() +#include "pycore_pyatomic_ft_wrappers.h" #include "pycore_typeobject.h" // _PyType_GetModuleState() #include <stddef.h> @@ -229,6 +230,7 @@ deque_new(PyTypeObject *type, PyObject *args, PyObject *kwds) } /*[clinic input] +@critical_section _collections.deque.pop as deque_pop deque: dequeobject @@ -238,7 +240,7 @@ Remove and return the rightmost element. static PyObject * deque_pop_impl(dequeobject *deque) -/*[clinic end generated code: output=2e5f7890c4251f07 input=eb6e6d020f877dec]*/ +/*[clinic end generated code: output=2e5f7890c4251f07 input=55c5b6a8ad51d72f]*/ { PyObject *item; block *prevblock; @@ -273,6 +275,7 @@ deque_pop_impl(dequeobject *deque) } /*[clinic input] +@critical_section _collections.deque.popleft as deque_popleft deque: dequeobject @@ -282,7 +285,7 @@ Remove and return the leftmost element. static PyObject * deque_popleft_impl(dequeobject *deque) -/*[clinic end generated code: output=62b154897097ff68 input=acb41b9af50a9d9b]*/ +/*[clinic end generated code: output=62b154897097ff68 input=1571ce88fe3053de]*/ { PyObject *item; block *prevblock; @@ -332,7 +335,7 @@ deque_popleft_impl(dequeobject *deque) #define NEEDS_TRIM(deque, maxlen) ((size_t)(maxlen) < (size_t)(Py_SIZE(deque))) static inline int -deque_append_internal(dequeobject *deque, PyObject *item, Py_ssize_t maxlen) +deque_append_lock_held(dequeobject *deque, PyObject *item, Py_ssize_t maxlen) { if (deque->rightindex == BLOCKLEN - 1) { block *b = newblock(deque); @@ -358,6 +361,7 @@ deque_append_internal(dequeobject *deque, PyObject *item, Py_ssize_t maxlen) } /*[clinic input] +@critical_section _collections.deque.append as deque_append deque: dequeobject @@ -368,16 +372,17 @@ Add an element to the right side of the deque. [clinic start generated code]*/ static PyObject * -deque_append(dequeobject *deque, PyObject *item) -/*[clinic end generated code: output=507b13efc4853ecc input=f112b83c380528e3]*/ +deque_append_impl(dequeobject *deque, PyObject *item) +/*[clinic end generated code: output=9c7bcb8b599c6362 input=b0eeeb09b9f5cf18]*/ { - if (deque_append_internal(deque, Py_NewRef(item), deque->maxlen) < 0) + if (deque_append_lock_held(deque, Py_NewRef(item), deque->maxlen) < 0) return NULL; Py_RETURN_NONE; } static inline int -deque_appendleft_internal(dequeobject *deque, PyObject *item, Py_ssize_t maxlen) +deque_appendleft_lock_held(dequeobject *deque, PyObject *item, + Py_ssize_t maxlen) { if (deque->leftindex == 0) { block *b = newblock(deque); @@ -393,7 +398,7 @@ deque_appendleft_internal(dequeobject *deque, PyObject *item, Py_ssize_t maxlen) Py_SET_SIZE(deque, Py_SIZE(deque) + 1); deque->leftindex--; deque->leftblock->data[deque->leftindex] = item; - if (NEEDS_TRIM(deque, deque->maxlen)) { + if (NEEDS_TRIM(deque, maxlen)) { PyObject *olditem = deque_pop_impl(deque); Py_DECREF(olditem); } else { @@ -403,6 +408,7 @@ deque_appendleft_internal(dequeobject *deque, PyObject *item, Py_ssize_t maxlen) } /*[clinic input] +@critical_section _collections.deque.appendleft as deque_appendleft deque: dequeobject @@ -413,10 +419,10 @@ Add an element to the left side of the deque. [clinic start generated code]*/ static PyObject * -deque_appendleft(dequeobject *deque, PyObject *item) -/*[clinic end generated code: output=de0335a64800ffd8 input=bbdaa60a3e956062]*/ +deque_appendleft_impl(dequeobject *deque, PyObject *item) +/*[clinic end generated code: output=9a192edbcd0f20db input=236c2fbceaf08e14]*/ { - if (deque_appendleft_internal(deque, Py_NewRef(item), deque->maxlen) < 0) + if (deque_appendleft_lock_held(deque, Py_NewRef(item), deque->maxlen) < 0) return NULL; Py_RETURN_NONE; } @@ -452,6 +458,7 @@ consume_iterator(PyObject *it) } /*[clinic input] +@critical_section _collections.deque.extend as deque_extend deque: dequeobject @@ -462,8 +469,8 @@ Extend the right side of the deque with elements from the iterable. [clinic start generated code]*/ static PyObject * -deque_extend(dequeobject *deque, PyObject *iterable) -/*[clinic end generated code: output=a3a6e74d17063f8d input=cfebfd34d5383339]*/ +deque_extend_impl(dequeobject *deque, PyObject *iterable) +/*[clinic end generated code: output=8b5ffa57ce82d980 input=85861954127c81da]*/ { PyObject *it, *item; PyObject *(*iternext)(PyObject *); @@ -497,7 +504,7 @@ deque_extend(dequeobject *deque, PyObject *iterable) iternext = *Py_TYPE(it)->tp_iternext; while ((item = iternext(it)) != NULL) { - if (deque_append_internal(deque, item, maxlen) == -1) { + if (deque_append_lock_held(deque, item, maxlen) == -1) { Py_DECREF(item); Py_DECREF(it); return NULL; @@ -507,6 +514,7 @@ deque_extend(dequeobject *deque, PyObject *iterable) } /*[clinic input] +@critical_section _collections.deque.extendleft as deque_extendleft deque: dequeobject @@ -517,8 +525,8 @@ Extend the left side of the deque with elements from the iterable. [clinic start generated code]*/ static PyObject * -deque_extendleft(dequeobject *deque, PyObject *iterable) -/*[clinic end generated code: output=2dba946c50498c67 input=f4820e695a6f9416]*/ +deque_extendleft_impl(dequeobject *deque, PyObject *iterable) +/*[clinic end generated code: output=ba44191aa8e35a26 input=640dabd086115689]*/ { PyObject *it, *item; PyObject *(*iternext)(PyObject *); @@ -530,7 +538,7 @@ deque_extendleft(dequeobject *deque, PyObject *iterable) PyObject *s = PySequence_List(iterable); if (s == NULL) return NULL; - result = deque_extendleft(deque, s); + result = deque_extendleft_impl(deque, s); Py_DECREF(s); return result; } @@ -552,7 +560,7 @@ deque_extendleft(dequeobject *deque, PyObject *iterable) iternext = *Py_TYPE(it)->tp_iternext; while ((item = iternext(it)) != NULL) { - if (deque_appendleft_internal(deque, item, maxlen) == -1) { + if (deque_appendleft_lock_held(deque, item, maxlen) == -1) { Py_DECREF(item); Py_DECREF(it); return NULL; @@ -566,6 +574,7 @@ deque_inplace_concat(dequeobject *deque, PyObject *other) { PyObject *result; + // deque_extend is thread-safe result = deque_extend(deque, other); if (result == NULL) return result; @@ -575,6 +584,7 @@ deque_inplace_concat(dequeobject *deque, PyObject *other) } /*[clinic input] +@critical_section _collections.deque.copy as deque_copy deque: dequeobject @@ -584,7 +594,7 @@ Return a shallow copy of a deque. static PyObject * deque_copy_impl(dequeobject *deque) -/*[clinic end generated code: output=6409b3d1ad2898b5 input=0e22f138bc1fcbee]*/ +/*[clinic end generated code: output=6409b3d1ad2898b5 input=51d2ed1a23bab5e2]*/ { PyObject *result; dequeobject *old_deque = (dequeobject *)deque; @@ -598,12 +608,16 @@ deque_copy_impl(dequeobject *deque) if (new_deque == NULL) return NULL; new_deque->maxlen = old_deque->maxlen; - /* Fast path for the deque_repeat() common case where len(deque) == 1 */ + /* Fast path for the deque_repeat() common case where len(deque) == 1 + * + * It's safe to not acquire the per-object lock for new_deque; it's + * invisible to other threads. + */ if (Py_SIZE(deque) == 1) { PyObject *item = old_deque->leftblock->data[old_deque->leftindex]; - rv = deque_append(new_deque, item); + rv = deque_append_impl(new_deque, item); } else { - rv = deque_extend(new_deque, (PyObject *)deque); + rv = deque_extend_impl(new_deque, (PyObject *)deque); } if (rv != NULL) { Py_DECREF(rv); @@ -629,6 +643,7 @@ deque_copy_impl(dequeobject *deque) } /*[clinic input] +@critical_section _collections.deque.__copy__ as deque___copy__ = _collections.deque.copy Return a shallow copy of a deque. @@ -636,13 +651,13 @@ Return a shallow copy of a deque. static PyObject * deque___copy___impl(dequeobject *deque) -/*[clinic end generated code: output=7c5821504342bf23 input=fce05df783e7912b]*/ +/*[clinic end generated code: output=7c5821504342bf23 input=f5464036f9686a55]*/ { return deque_copy_impl(deque); } static PyObject * -deque_concat(dequeobject *deque, PyObject *other) +deque_concat_lock_held(dequeobject *deque, PyObject *other) { PyObject *new_deque, *result; int rv; @@ -661,7 +676,10 @@ deque_concat(dequeobject *deque, PyObject *other) new_deque = deque_copy_impl(deque); if (new_deque == NULL) return NULL; - result = deque_extend((dequeobject *)new_deque, other); + + // It's safe to not acquire the per-object lock for new_deque; it's + // invisible to other threads. + result = deque_extend_impl((dequeobject *)new_deque, other); if (result == NULL) { Py_DECREF(new_deque); return NULL; @@ -670,6 +688,16 @@ deque_concat(dequeobject *deque, PyObject *other) return new_deque; } +static PyObject * +deque_concat(dequeobject *deque, PyObject *other) +{ + PyObject *result; + Py_BEGIN_CRITICAL_SECTION(deque); + result = deque_concat_lock_held(deque, other); + Py_END_CRITICAL_SECTION(); + return result; +} + static int deque_clear(dequeobject *deque) { @@ -755,6 +783,7 @@ deque_clear(dequeobject *deque) } /*[clinic input] +@critical_section _collections.deque.clear as deque_clearmethod deque: dequeobject @@ -764,14 +793,14 @@ Remove all elements from the deque. static PyObject * deque_clearmethod_impl(dequeobject *deque) -/*[clinic end generated code: output=79b2513e097615c1 input=20488eb932f89f9e]*/ +/*[clinic end generated code: output=79b2513e097615c1 input=3a22e9605d20c5e9]*/ { deque_clear(deque); Py_RETURN_NONE; } static PyObject * -deque_inplace_repeat(dequeobject *deque, Py_ssize_t n) +deque_inplace_repeat_lock_held(dequeobject *deque, Py_ssize_t n) { Py_ssize_t i, m, size; PyObject *seq; @@ -835,7 +864,7 @@ deque_inplace_repeat(dequeobject *deque, Py_ssize_t n) n = (deque->maxlen + size - 1) / size; for (i = 0 ; i < n-1 ; i++) { - rv = deque_extend(deque, seq); + rv = deque_extend_impl(deque, seq); if (rv == NULL) { Py_DECREF(seq); return NULL; @@ -847,16 +876,30 @@ deque_inplace_repeat(dequeobject *deque, Py_ssize_t n) return (PyObject *)deque; } +static PyObject * +deque_inplace_repeat(dequeobject *deque, Py_ssize_t n) +{ + PyObject *result; + Py_BEGIN_CRITICAL_SECTION(deque); + result = deque_inplace_repeat_lock_held(deque, n); + Py_END_CRITICAL_SECTION(); + return result; +} + static PyObject * deque_repeat(dequeobject *deque, Py_ssize_t n) { dequeobject *new_deque; PyObject *rv; + Py_BEGIN_CRITICAL_SECTION(deque); new_deque = (dequeobject *)deque_copy_impl(deque); + Py_END_CRITICAL_SECTION(); if (new_deque == NULL) return NULL; - rv = deque_inplace_repeat(new_deque, n); + // It's safe to not acquire the per-object lock for new_deque; it's + // invisible to other threads. + rv = deque_inplace_repeat_lock_held(new_deque, n); Py_DECREF(new_deque); return rv; } @@ -1011,6 +1054,7 @@ _deque_rotate(dequeobject *deque, Py_ssize_t n) } /*[clinic input] +@critical_section _collections.deque.rotate as deque_rotate deque: dequeobject @@ -1022,7 +1066,7 @@ Rotate the deque n steps to the right. If n is negative, rotates left. static PyObject * deque_rotate_impl(dequeobject *deque, Py_ssize_t n) -/*[clinic end generated code: output=96c2402a371eb15d input=d22070f49cc06c76]*/ +/*[clinic end generated code: output=96c2402a371eb15d input=5bf834296246e002]*/ { if (!_deque_rotate(deque, n)) Py_RETURN_NONE; @@ -1030,6 +1074,7 @@ deque_rotate_impl(dequeobject *deque, Py_ssize_t n) } /*[clinic input] +@critical_section _collections.deque.reverse as deque_reverse deque: dequeobject @@ -1039,7 +1084,7 @@ Reverse *IN PLACE*. static PyObject * deque_reverse_impl(dequeobject *deque) -/*[clinic end generated code: output=bdeebc2cf8c1f064 input=f139787f406101c9]*/ +/*[clinic end generated code: output=bdeebc2cf8c1f064 input=26f4167fd623027f]*/ { block *leftblock = deque->leftblock; block *rightblock = deque->rightblock; @@ -1077,6 +1122,7 @@ deque_reverse_impl(dequeobject *deque) } /*[clinic input] +@critical_section _collections.deque.count as deque_count deque: dequeobject @@ -1087,8 +1133,8 @@ Return number of occurrences of value. [clinic start generated code]*/ static PyObject * -deque_count(dequeobject *deque, PyObject *v) -/*[clinic end generated code: output=7405d289d94d7b9b input=1892925260ff5d78]*/ +deque_count_impl(dequeobject *deque, PyObject *v) +/*[clinic end generated code: output=2ca26c49b6ab0400 input=4ef67ef2b34dc1fc]*/ { block *b = deque->leftblock; Py_ssize_t index = deque->leftindex; @@ -1124,7 +1170,7 @@ deque_count(dequeobject *deque, PyObject *v) } static int -deque_contains(dequeobject *deque, PyObject *v) +deque_contains_lock_held(dequeobject *deque, PyObject *v) { block *b = deque->leftblock; Py_ssize_t index = deque->leftindex; @@ -1155,13 +1201,24 @@ deque_contains(dequeobject *deque, PyObject *v) return 0; } +static int +deque_contains(dequeobject *deque, PyObject *v) +{ + int result; + Py_BEGIN_CRITICAL_SECTION(deque); + result = deque_contains_lock_held(deque, v); + Py_END_CRITICAL_SECTION(); + return result; +} + static Py_ssize_t deque_len(dequeobject *deque) { - return Py_SIZE(deque); + return FT_ATOMIC_LOAD_SSIZE(((PyVarObject *)deque)->ob_size); } /*[clinic input] +@critical_section @text_signature "($self, value, [start, [stop]])" _collections.deque.index as deque_index @@ -1179,7 +1236,7 @@ Raises ValueError if the value is not present. static PyObject * deque_index_impl(dequeobject *deque, PyObject *v, Py_ssize_t start, Py_ssize_t stop) -/*[clinic end generated code: output=df45132753175ef9 input=140210c099830f64]*/ +/*[clinic end generated code: output=df45132753175ef9 input=90f48833a91e1743]*/ { Py_ssize_t i, n; PyObject *item; @@ -1249,6 +1306,7 @@ deque_index_impl(dequeobject *deque, PyObject *v, Py_ssize_t start, */ /*[clinic input] +@critical_section _collections.deque.insert as deque_insert deque: dequeobject @@ -1261,7 +1319,7 @@ Insert value before index. static PyObject * deque_insert_impl(dequeobject *deque, Py_ssize_t index, PyObject *value) -/*[clinic end generated code: output=ef4d2c15d5532b80 input=3e5c1c120d70c0e6]*/ +/*[clinic end generated code: output=ef4d2c15d5532b80 input=dbee706586cc9cde]*/ { Py_ssize_t n = Py_SIZE(deque); PyObject *rv; @@ -1271,15 +1329,15 @@ deque_insert_impl(dequeobject *deque, Py_ssize_t index, PyObject *value) return NULL; } if (index >= n) - return deque_append(deque, value); + return deque_append_impl(deque, value); if (index <= -n || index == 0) - return deque_appendleft(deque, value); + return deque_appendleft_impl(deque, value); if (_deque_rotate(deque, -index)) return NULL; if (index < 0) - rv = deque_append(deque, value); + rv = deque_append_impl(deque, value); else - rv = deque_appendleft(deque, value); + rv = deque_appendleft_impl(deque, value); if (rv == NULL) return NULL; Py_DECREF(rv); @@ -1297,7 +1355,7 @@ valid_index(Py_ssize_t i, Py_ssize_t limit) } static PyObject * -deque_item(dequeobject *deque, Py_ssize_t i) +deque_item_lock_held(dequeobject *deque, Py_ssize_t i) { block *b; PyObject *item; @@ -1335,6 +1393,16 @@ deque_item(dequeobject *deque, Py_ssize_t i) return Py_NewRef(item); } +static PyObject * +deque_item(dequeobject *deque, Py_ssize_t i) +{ + PyObject *result; + Py_BEGIN_CRITICAL_SECTION(deque); + result = deque_item_lock_held(deque, i); + Py_END_CRITICAL_SECTION(); + return result; +} + static int deque_del_item(dequeobject *deque, Py_ssize_t i) { @@ -1352,6 +1420,7 @@ deque_del_item(dequeobject *deque, Py_ssize_t i) } /*[clinic input] +@critical_section _collections.deque.remove as deque_remove deque: dequeobject @@ -1362,8 +1431,8 @@ Remove first occurrence of value. [clinic start generated code]*/ static PyObject * -deque_remove(dequeobject *deque, PyObject *value) -/*[clinic end generated code: output=49e1666d612fe911 input=d972f32d15990880]*/ +deque_remove_impl(dequeobject *deque, PyObject *value) +/*[clinic end generated code: output=54cff28b8ef78c5b input=60eb3f8aa4de532a]*/ { PyObject *item; block *b = deque->leftblock; @@ -1404,7 +1473,7 @@ deque_remove(dequeobject *deque, PyObject *value) } static int -deque_ass_item(dequeobject *deque, Py_ssize_t i, PyObject *v) +deque_ass_item_lock_held(dequeobject *deque, Py_ssize_t i, PyObject *v) { block *b; Py_ssize_t n, len=Py_SIZE(deque), halflen=(len+1)>>1, index=i; @@ -1435,6 +1504,16 @@ deque_ass_item(dequeobject *deque, Py_ssize_t i, PyObject *v) return 0; } +static int +deque_ass_item(dequeobject *deque, Py_ssize_t i, PyObject *v) +{ + int result; + Py_BEGIN_CRITICAL_SECTION(deque); + result = deque_ass_item_lock_held(deque, i, v); + Py_END_CRITICAL_SECTION(); + return result; +} + static void deque_dealloc(dequeobject *deque) { @@ -1509,6 +1588,8 @@ deque___reduce___impl(dequeobject *deque) return NULL; } + // It's safe to access deque->maxlen here without holding the per object + // lock for deque; deque->maxlen is only assigned during construction. if (deque->maxlen < 0) { return Py_BuildValue("O()NN", Py_TYPE(deque), state, it); } @@ -1629,6 +1710,7 @@ deque_richcompare(PyObject *v, PyObject *w, int op) } /*[clinic input] +@critical_section @text_signature "([iterable[, maxlen]])" _collections.deque.__init__ as deque_init @@ -1641,8 +1723,7 @@ A list-like sequence optimized for data accesses near its endpoints. static int deque_init_impl(dequeobject *deque, PyObject *iterable, PyObject *maxlenobj) -/*[clinic end generated code: output=7084a39d71218dcd input=5ebdffc48a2d27ae]*/ - +/*[clinic end generated code: output=7084a39d71218dcd input=2b9e37af1fd73143]*/ { Py_ssize_t maxlen = -1; if (maxlenobj != NULL && maxlenobj != Py_None) { @@ -1658,7 +1739,7 @@ deque_init_impl(dequeobject *deque, PyObject *iterable, PyObject *maxlenobj) if (Py_SIZE(deque) > 0) deque_clear(deque); if (iterable != NULL) { - PyObject *rv = deque_extend(deque, iterable); + PyObject *rv = deque_extend_impl(deque, iterable); if (rv == NULL) return -1; Py_DECREF(rv); @@ -1667,6 +1748,7 @@ deque_init_impl(dequeobject *deque, PyObject *iterable, PyObject *maxlenobj) } /*[clinic input] +@critical_section _collections.deque.__sizeof__ as deque___sizeof__ deque: dequeobject @@ -1676,7 +1758,7 @@ Return the size of the deque in memory, in bytes. static PyObject * deque___sizeof___impl(dequeobject *deque) -/*[clinic end generated code: output=4d36e9fb4f30bbaf input=4e7c9a00c03c3290]*/ +/*[clinic end generated code: output=4d36e9fb4f30bbaf input=762312f2d4813535]*/ { size_t res = _PyObject_SIZE(Py_TYPE(deque)); size_t blocks; @@ -1810,11 +1892,13 @@ deque_iter(dequeobject *deque) it = PyObject_GC_New(dequeiterobject, state->dequeiter_type); if (it == NULL) return NULL; + Py_BEGIN_CRITICAL_SECTION(deque); it->b = deque->leftblock; it->index = deque->leftindex; it->deque = (dequeobject*)Py_NewRef(deque); it->state = deque->state; it->counter = Py_SIZE(deque); + Py_END_CRITICAL_SECTION(); PyObject_GC_Track(it); return (PyObject *)it; } @@ -1846,7 +1930,7 @@ dequeiter_dealloc(dequeiterobject *dio) } static PyObject * -dequeiter_next(dequeiterobject *it) +dequeiter_next_lock_held(dequeiterobject *it, dequeobject *deque) { PyObject *item; @@ -1872,6 +1956,20 @@ dequeiter_next(dequeiterobject *it) return Py_NewRef(item); } +static PyObject * +dequeiter_next(dequeiterobject *it) +{ + PyObject *result; + // It's safe to access it->deque without holding the per-object lock for it + // here; it->deque is only assigned during construction of it. + dequeobject *deque = it->deque; + Py_BEGIN_CRITICAL_SECTION2(it, deque); + result = dequeiter_next_lock_held(it, deque); + Py_END_CRITICAL_SECTION2(); + + return result; +} + static PyObject * dequeiter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { @@ -1892,6 +1990,11 @@ dequeiter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) if (item) { Py_DECREF(item); } else { + /* + * It's safe to read directly from it without acquiring the + * per-object lock; the iterator isn't visible to any other threads + * yet. + */ if (it->counter) { Py_DECREF(it); return NULL; @@ -1905,7 +2008,8 @@ dequeiter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) static PyObject * dequeiter_len(dequeiterobject *it, PyObject *Py_UNUSED(ignored)) { - return PyLong_FromSsize_t(it->counter); + Py_ssize_t len = FT_ATOMIC_LOAD_SSIZE(it->counter); + return PyLong_FromSsize_t(len); } PyDoc_STRVAR(length_hint_doc, "Private method returning an estimate of len(list(it))."); @@ -1913,7 +2017,16 @@ PyDoc_STRVAR(length_hint_doc, "Private method returning an estimate of len(list( static PyObject * dequeiter_reduce(dequeiterobject *it, PyObject *Py_UNUSED(ignored)) { - return Py_BuildValue("O(On)", Py_TYPE(it), it->deque, Py_SIZE(it->deque) - it->counter); + PyTypeObject *ty = Py_TYPE(it); + // It's safe to access it->deque without holding the per-object lock for it + // here; it->deque is only assigned during construction of it. + dequeobject *deque = it->deque; + Py_ssize_t size, counter; + Py_BEGIN_CRITICAL_SECTION2(it, deque); + size = Py_SIZE(deque); + counter = it->counter; + Py_END_CRITICAL_SECTION2(); + return Py_BuildValue("O(On)", ty, deque, size - counter); } static PyMethodDef dequeiter_methods[] = { @@ -1953,17 +2066,19 @@ deque_reviter(dequeobject *deque) it = PyObject_GC_New(dequeiterobject, state->dequereviter_type); if (it == NULL) return NULL; + Py_BEGIN_CRITICAL_SECTION(deque); it->b = deque->rightblock; it->index = deque->rightindex; it->deque = (dequeobject*)Py_NewRef(deque); it->state = deque->state; it->counter = Py_SIZE(deque); + Py_END_CRITICAL_SECTION(); PyObject_GC_Track(it); return (PyObject *)it; } static PyObject * -dequereviter_next(dequeiterobject *it) +dequereviter_next_lock_held(dequeiterobject *it, dequeobject *deque) { PyObject *item; if (it->counter == 0) @@ -1989,6 +2104,19 @@ dequereviter_next(dequeiterobject *it) return Py_NewRef(item); } +static PyObject * +dequereviter_next(dequeiterobject *it) +{ + PyObject *item; + // It's safe to access it->deque without holding the per-object lock for it + // here; it->deque is only assigned during construction of it. + dequeobject *deque = it->deque; + Py_BEGIN_CRITICAL_SECTION2(it, deque); + item = dequereviter_next_lock_held(it, deque); + Py_END_CRITICAL_SECTION2(); + return item; +} + static PyObject * dequereviter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { @@ -2009,6 +2137,11 @@ dequereviter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) if (item) { Py_DECREF(item); } else { + /* + * It's safe to read directly from it without acquiring the + * per-object lock; the iterator isn't visible to any other threads + * yet. + */ if (it->counter) { Py_DECREF(it); return NULL; diff --git a/Modules/clinic/_collectionsmodule.c.h b/Modules/clinic/_collectionsmodule.c.h index 60fb12a22316195..fa0ff2133a05d2b 100644 --- a/Modules/clinic/_collectionsmodule.c.h +++ b/Modules/clinic/_collectionsmodule.c.h @@ -7,6 +7,7 @@ preserve # include "pycore_runtime.h" // _Py_ID() #endif #include "pycore_abstract.h" // _PyNumber_Index() +#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION() #include "pycore_modsupport.h" // _PyArg_CheckPositional() PyDoc_STRVAR(deque_pop__doc__, @@ -24,7 +25,13 @@ deque_pop_impl(dequeobject *deque); static PyObject * deque_pop(dequeobject *deque, PyObject *Py_UNUSED(ignored)) { - return deque_pop_impl(deque); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(deque); + return_value = deque_pop_impl(deque); + Py_END_CRITICAL_SECTION(); + + return return_value; } PyDoc_STRVAR(deque_popleft__doc__, @@ -42,7 +49,13 @@ deque_popleft_impl(dequeobject *deque); static PyObject * deque_popleft(dequeobject *deque, PyObject *Py_UNUSED(ignored)) { - return deque_popleft_impl(deque); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(deque); + return_value = deque_popleft_impl(deque); + Py_END_CRITICAL_SECTION(); + + return return_value; } PyDoc_STRVAR(deque_append__doc__, @@ -54,6 +67,21 @@ PyDoc_STRVAR(deque_append__doc__, #define DEQUE_APPEND_METHODDEF \ {"append", (PyCFunction)deque_append, METH_O, deque_append__doc__}, +static PyObject * +deque_append_impl(dequeobject *deque, PyObject *item); + +static PyObject * +deque_append(dequeobject *deque, PyObject *item) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(deque); + return_value = deque_append_impl(deque, item); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(deque_appendleft__doc__, "appendleft($self, item, /)\n" "--\n" @@ -63,6 +91,21 @@ PyDoc_STRVAR(deque_appendleft__doc__, #define DEQUE_APPENDLEFT_METHODDEF \ {"appendleft", (PyCFunction)deque_appendleft, METH_O, deque_appendleft__doc__}, +static PyObject * +deque_appendleft_impl(dequeobject *deque, PyObject *item); + +static PyObject * +deque_appendleft(dequeobject *deque, PyObject *item) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(deque); + return_value = deque_appendleft_impl(deque, item); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(deque_extend__doc__, "extend($self, iterable, /)\n" "--\n" @@ -72,6 +115,21 @@ PyDoc_STRVAR(deque_extend__doc__, #define DEQUE_EXTEND_METHODDEF \ {"extend", (PyCFunction)deque_extend, METH_O, deque_extend__doc__}, +static PyObject * +deque_extend_impl(dequeobject *deque, PyObject *iterable); + +static PyObject * +deque_extend(dequeobject *deque, PyObject *iterable) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(deque); + return_value = deque_extend_impl(deque, iterable); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(deque_extendleft__doc__, "extendleft($self, iterable, /)\n" "--\n" @@ -81,6 +139,21 @@ PyDoc_STRVAR(deque_extendleft__doc__, #define DEQUE_EXTENDLEFT_METHODDEF \ {"extendleft", (PyCFunction)deque_extendleft, METH_O, deque_extendleft__doc__}, +static PyObject * +deque_extendleft_impl(dequeobject *deque, PyObject *iterable); + +static PyObject * +deque_extendleft(dequeobject *deque, PyObject *iterable) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(deque); + return_value = deque_extendleft_impl(deque, iterable); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(deque_copy__doc__, "copy($self, /)\n" "--\n" @@ -96,7 +169,13 @@ deque_copy_impl(dequeobject *deque); static PyObject * deque_copy(dequeobject *deque, PyObject *Py_UNUSED(ignored)) { - return deque_copy_impl(deque); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(deque); + return_value = deque_copy_impl(deque); + Py_END_CRITICAL_SECTION(); + + return return_value; } PyDoc_STRVAR(deque___copy____doc__, @@ -114,7 +193,13 @@ deque___copy___impl(dequeobject *deque); static PyObject * deque___copy__(dequeobject *deque, PyObject *Py_UNUSED(ignored)) { - return deque___copy___impl(deque); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(deque); + return_value = deque___copy___impl(deque); + Py_END_CRITICAL_SECTION(); + + return return_value; } PyDoc_STRVAR(deque_clearmethod__doc__, @@ -132,7 +217,13 @@ deque_clearmethod_impl(dequeobject *deque); static PyObject * deque_clearmethod(dequeobject *deque, PyObject *Py_UNUSED(ignored)) { - return deque_clearmethod_impl(deque); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(deque); + return_value = deque_clearmethod_impl(deque); + Py_END_CRITICAL_SECTION(); + + return return_value; } PyDoc_STRVAR(deque_rotate__doc__, @@ -172,7 +263,9 @@ deque_rotate(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) n = ival; } skip_optional: + Py_BEGIN_CRITICAL_SECTION(deque); return_value = deque_rotate_impl(deque, n); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -193,7 +286,13 @@ deque_reverse_impl(dequeobject *deque); static PyObject * deque_reverse(dequeobject *deque, PyObject *Py_UNUSED(ignored)) { - return deque_reverse_impl(deque); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(deque); + return_value = deque_reverse_impl(deque); + Py_END_CRITICAL_SECTION(); + + return return_value; } PyDoc_STRVAR(deque_count__doc__, @@ -205,6 +304,21 @@ PyDoc_STRVAR(deque_count__doc__, #define DEQUE_COUNT_METHODDEF \ {"count", (PyCFunction)deque_count, METH_O, deque_count__doc__}, +static PyObject * +deque_count_impl(dequeobject *deque, PyObject *v); + +static PyObject * +deque_count(dequeobject *deque, PyObject *v) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(deque); + return_value = deque_count_impl(deque, v); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(deque_index__doc__, "index($self, value, [start, [stop]])\n" "--\n" @@ -245,7 +359,9 @@ deque_index(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) goto exit; } skip_optional: + Py_BEGIN_CRITICAL_SECTION(deque); return_value = deque_index_impl(deque, v, start, stop); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -286,7 +402,9 @@ deque_insert(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) index = ival; } value = args[1]; + Py_BEGIN_CRITICAL_SECTION(deque); return_value = deque_insert_impl(deque, index, value); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -301,6 +419,21 @@ PyDoc_STRVAR(deque_remove__doc__, #define DEQUE_REMOVE_METHODDEF \ {"remove", (PyCFunction)deque_remove, METH_O, deque_remove__doc__}, +static PyObject * +deque_remove_impl(dequeobject *deque, PyObject *value); + +static PyObject * +deque_remove(dequeobject *deque, PyObject *value) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(deque); + return_value = deque_remove_impl(deque, value); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(deque___reduce____doc__, "__reduce__($self, /)\n" "--\n" @@ -379,7 +512,9 @@ deque_init(PyObject *deque, PyObject *args, PyObject *kwargs) } maxlenobj = fastargs[1]; skip_optional_pos: + Py_BEGIN_CRITICAL_SECTION(deque); return_value = deque_init_impl((dequeobject *)deque, iterable, maxlenobj); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -400,7 +535,13 @@ deque___sizeof___impl(dequeobject *deque); static PyObject * deque___sizeof__(dequeobject *deque, PyObject *Py_UNUSED(ignored)) { - return deque___sizeof___impl(deque); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(deque); + return_value = deque___sizeof___impl(deque); + Py_END_CRITICAL_SECTION(); + + return return_value; } PyDoc_STRVAR(deque___reversed____doc__, @@ -488,4 +629,4 @@ tuplegetter_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=3633a5cbc23e8440 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=64c32b16df7be07a input=a9049054013a1b77]*/ From 32f8ab1ab65c13ed70f047ffd780ec1fe303ff1e Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Thu, 15 Feb 2024 09:45:21 +0100 Subject: [PATCH 283/507] gh-114258: Refactor Argument Clinic function name parser (#114930) Refactor state_modulename_name() of the parsing state machine, by adding helpers for the sections that deal with ...: 1. parsing the function name 2. normalizing "function kind" 3. dealing with cloned functions 4. resolving return converters 5. adding the function to the DSL parser --- Tools/clinic/clinic.py | 229 ++++++++++++++++++++++------------------- 1 file changed, 123 insertions(+), 106 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index db57d17899af939..e63596c30079403 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5126,8 +5126,7 @@ def state_dsl_start(self, line: str) -> None: self.next(self.state_modulename_name, line) - @staticmethod - def parse_function_names(line: str) -> FunctionNames: + def parse_function_names(self, line: str) -> FunctionNames: left, as_, right = line.partition(' as ') full_name = left.strip() c_basename = right.strip() @@ -5142,28 +5141,101 @@ def parse_function_names(line: str) -> FunctionNames: fail(f"Illegal function name: {full_name!r}") if not is_legal_c_identifier(c_basename): fail(f"Illegal C basename: {c_basename!r}") - return FunctionNames(full_name=full_name, c_basename=c_basename) + names = FunctionNames(full_name=full_name, c_basename=c_basename) + self.normalize_function_kind(names.full_name) + return names - def update_function_kind(self, fullname: str) -> None: + def normalize_function_kind(self, fullname: str) -> None: + # Fetch the method name and possibly class. fields = fullname.split('.') name = fields.pop() _, cls = self.clinic._module_and_class(fields) + + # Check special method requirements. if name in unsupported_special_methods: fail(f"{name!r} is a special method and cannot be converted to Argument Clinic!") - + if name == '__init__' and (self.kind is not CALLABLE or not cls): + fail(f"{name!r} must be a normal method; got '{self.kind}'!") + if name == '__new__' and (self.kind is not CLASS_METHOD or not cls): + fail("'__new__' must be a class method!") + if self.kind in {GETTER, SETTER} and not cls: + fail("@getter and @setter must be methods") + + # Normalise self.kind. if name == '__new__': - if (self.kind is CLASS_METHOD) and cls: - self.kind = METHOD_NEW - else: - fail("'__new__' must be a class method!") + self.kind = METHOD_NEW elif name == '__init__': - if (self.kind is CALLABLE) and cls: - self.kind = METHOD_INIT + self.kind = METHOD_INIT + + def resolve_return_converter( + self, full_name: str, forced_converter: str + ) -> CReturnConverter: + if forced_converter: + if self.kind in {GETTER, SETTER}: + fail(f"@{self.kind.name.lower()} method cannot define a return type") + ast_input = f"def x() -> {forced_converter}: pass" + try: + module_node = ast.parse(ast_input) + except SyntaxError: + fail(f"Badly formed annotation for {full_name!r}: {forced_converter!r}") + function_node = module_node.body[0] + assert isinstance(function_node, ast.FunctionDef) + try: + name, legacy, kwargs = self.parse_converter(function_node.returns) + if legacy: + fail(f"Legacy converter {name!r} not allowed as a return converter") + if name not in return_converters: + fail(f"No available return converter called {name!r}") + return return_converters[name](**kwargs) + except ValueError: + fail(f"Badly formed annotation for {full_name!r}: {forced_converter!r}") + + if self.kind is METHOD_INIT: + return init_return_converter() + return CReturnConverter() + + def parse_cloned_function(self, names: FunctionNames, existing: str) -> None: + full_name, c_basename = names + fields = [x.strip() for x in existing.split('.')] + function_name = fields.pop() + module, cls = self.clinic._module_and_class(fields) + parent = cls or module + + for existing_function in parent.functions: + if existing_function.name == function_name: + break + else: + print(f"{cls=}, {module=}, {existing=}", file=sys.stderr) + print(f"{(cls or module).functions=}", file=sys.stderr) + fail(f"Couldn't find existing function {existing!r}!") + + fields = [x.strip() for x in full_name.split('.')] + function_name = fields.pop() + module, cls = self.clinic._module_and_class(fields) + + overrides: dict[str, Any] = { + "name": function_name, + "full_name": full_name, + "module": module, + "cls": cls, + "c_basename": c_basename, + "docstring": "", + } + if not (existing_function.kind is self.kind and + existing_function.coexist == self.coexist): + # Allow __new__ or __init__ methods. + if existing_function.kind.new_or_init: + overrides["kind"] = self.kind + # Future enhancement: allow custom return converters + overrides["return_converter"] = CReturnConverter() else: - fail( - "'__init__' must be a normal method; " - f"got '{self.kind}'!" - ) + fail("'kind' of function and cloned function don't match! " + "(@classmethod/@staticmethod/@coexist)") + function = existing_function.copy(**overrides) + self.function = function + self.block.signatures.append(function) + (cls or module).functions.append(function) + self.next(self.state_function_docstring) def state_modulename_name(self, line: str) -> None: # looking for declaration, which establishes the leftmost column @@ -5188,111 +5260,56 @@ def state_modulename_name(self, line: str) -> None: # are we cloning? before, equals, existing = line.rpartition('=') if equals: - full_name, c_basename = self.parse_function_names(before) existing = existing.strip() if is_legal_py_identifier(existing): # we're cloning! - fields = [x.strip() for x in existing.split('.')] - function_name = fields.pop() - module, cls = self.clinic._module_and_class(fields) - - for existing_function in (cls or module).functions: - if existing_function.name == function_name: - break - else: - print(f"{cls=}, {module=}, {existing=}", file=sys.stderr) - print(f"{(cls or module).functions=}", file=sys.stderr) - fail(f"Couldn't find existing function {existing!r}!") - - fields = [x.strip() for x in full_name.split('.')] - function_name = fields.pop() - module, cls = self.clinic._module_and_class(fields) - - self.update_function_kind(full_name) - overrides: dict[str, Any] = { - "name": function_name, - "full_name": full_name, - "module": module, - "cls": cls, - "c_basename": c_basename, - "docstring": "", - } - if not (existing_function.kind is self.kind and - existing_function.coexist == self.coexist): - # Allow __new__ or __init__ methods. - if existing_function.kind.new_or_init: - overrides["kind"] = self.kind - # Future enhancement: allow custom return converters - overrides["return_converter"] = CReturnConverter() - else: - fail("'kind' of function and cloned function don't match! " - "(@classmethod/@staticmethod/@coexist)") - function = existing_function.copy(**overrides) - self.function = function - self.block.signatures.append(function) - (cls or module).functions.append(function) - self.next(self.state_function_docstring) - return + names = self.parse_function_names(before) + return self.parse_cloned_function(names, existing) line, _, returns = line.partition('->') returns = returns.strip() full_name, c_basename = self.parse_function_names(line) - - return_converter = None - if returns: - if self.kind in {GETTER, SETTER}: - fail(f"@{self.kind.name.lower()} method cannot define a return type") - ast_input = f"def x() -> {returns}: pass" - try: - module_node = ast.parse(ast_input) - except SyntaxError: - fail(f"Badly formed annotation for {full_name!r}: {returns!r}") - function_node = module_node.body[0] - assert isinstance(function_node, ast.FunctionDef) - try: - name, legacy, kwargs = self.parse_converter(function_node.returns) - if legacy: - fail(f"Legacy converter {name!r} not allowed as a return converter") - if name not in return_converters: - fail(f"No available return converter called {name!r}") - return_converter = return_converters[name](**kwargs) - except ValueError: - fail(f"Badly formed annotation for {full_name!r}: {returns!r}") + return_converter = self.resolve_return_converter(full_name, returns) fields = [x.strip() for x in full_name.split('.')] function_name = fields.pop() module, cls = self.clinic._module_and_class(fields) - if self.kind in {GETTER, SETTER}: - if not cls: - fail("@getter and @setter must be methods") - - self.update_function_kind(full_name) - if self.kind is METHOD_INIT and not return_converter: - return_converter = init_return_converter() - - if not return_converter: - return_converter = CReturnConverter() - - self.function = Function(name=function_name, full_name=full_name, module=module, cls=cls, c_basename=c_basename, - return_converter=return_converter, kind=self.kind, coexist=self.coexist, - critical_section=self.critical_section, - target_critical_section=self.target_critical_section) - self.block.signatures.append(self.function) - - # insert a self converter automatically - type, name = correct_name_for_self(self.function) - kwargs = {} - if cls and type == "PyObject *": - kwargs['type'] = cls.typedef - sc = self.function.self_converter = self_converter(name, name, self.function, **kwargs) - p_self = Parameter(name, inspect.Parameter.POSITIONAL_ONLY, - function=self.function, converter=sc) - self.function.parameters[name] = p_self - - (cls or module).functions.append(self.function) + func = Function( + name=function_name, + full_name=full_name, + module=module, + cls=cls, + c_basename=c_basename, + return_converter=return_converter, + kind=self.kind, + coexist=self.coexist, + critical_section=self.critical_section, + target_critical_section=self.target_critical_section + ) + self.add_function(func) + self.next(self.state_parameters_start) + def add_function(self, func: Function) -> None: + # Insert a self converter automatically. + tp, name = correct_name_for_self(func) + if func.cls and tp == "PyObject *": + func.self_converter = self_converter(name, name, func, + type=func.cls.typedef) + else: + func.self_converter = self_converter(name, name, func) + func.parameters[name] = Parameter( + name, + inspect.Parameter.POSITIONAL_ONLY, + function=func, + converter=func.self_converter + ) + + self.block.signatures.append(func) + self.function = func + (func.cls or func.module).functions.append(func) + # Now entering the parameters section. The rules, formally stated: # # * All lines must be indented with spaces only. From 9e3729bbd77fb9dcaea6a06ac760160136d80b79 Mon Sep 17 00:00:00 2001 From: David Hewitt <mail@davidhewitt.dev> Date: Thu, 15 Feb 2024 10:05:20 +0000 Subject: [PATCH 284/507] gh-114626: add PyCFunctionFast and PyCFunctionFastWithKeywords (GH-114627) Co-authored-by: Petr Viktorin <encukou@gmail.com> --- Doc/c-api/structures.rst | 22 +++++++++---------- Doc/data/stable_abi.dat | 2 ++ Include/methodobject.h | 15 +++++++++---- ...-01-26-21-54-42.gh-issue-114626.SKhbh_.rst | 1 + Misc/stable_abi.toml | 7 ++++++ Objects/descrobject.c | 4 ++-- Objects/methodobject.c | 5 ++--- Python/bytecodes.c | 14 ++++++------ Python/executor_cases.c.h | 14 ++++++------ Python/generated_cases.c.h | 14 ++++++------ 10 files changed, 57 insertions(+), 41 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2024-01-26-21-54-42.gh-issue-114626.SKhbh_.rst diff --git a/Doc/c-api/structures.rst b/Doc/c-api/structures.rst index 77f2b6991d770eb..e2943f18ddc6010 100644 --- a/Doc/c-api/structures.rst +++ b/Doc/c-api/structures.rst @@ -187,26 +187,26 @@ Implementing functions and methods PyObject *kwargs); -.. c:type:: _PyCFunctionFast +.. c:type:: PyCFunctionFast Type of the functions used to implement Python callables in C with signature :c:macro:`METH_FASTCALL`. The function signature is:: - PyObject *_PyCFunctionFast(PyObject *self, - PyObject *const *args, - Py_ssize_t nargs); + PyObject *PyCFunctionFast(PyObject *self, + PyObject *const *args, + Py_ssize_t nargs); -.. c:type:: _PyCFunctionFastWithKeywords +.. c:type:: PyCFunctionFastWithKeywords Type of the functions used to implement Python callables in C with signature :ref:`METH_FASTCALL | METH_KEYWORDS <METH_FASTCALL-METH_KEYWORDS>`. The function signature is:: - PyObject *_PyCFunctionFastWithKeywords(PyObject *self, - PyObject *const *args, - Py_ssize_t nargs, - PyObject *kwnames); + PyObject *PyCFunctionFastWithKeywords(PyObject *self, + PyObject *const *args, + Py_ssize_t nargs, + PyObject *kwnames); .. c:type:: PyCMethod @@ -290,7 +290,7 @@ There are these calling conventions: .. c:macro:: METH_FASTCALL Fast calling convention supporting only positional arguments. - The methods have the type :c:type:`_PyCFunctionFast`. + The methods have the type :c:type:`PyCFunctionFast`. The first parameter is *self*, the second parameter is a C array of :c:expr:`PyObject*` values indicating the arguments and the third parameter is the number of arguments (the length of the array). @@ -306,7 +306,7 @@ There are these calling conventions: :c:expr:`METH_FASTCALL | METH_KEYWORDS` Extension of :c:macro:`METH_FASTCALL` supporting also keyword arguments, - with methods of type :c:type:`_PyCFunctionFastWithKeywords`. + with methods of type :c:type:`PyCFunctionFastWithKeywords`. Keyword arguments are passed the same way as in the :ref:`vectorcall protocol <vectorcall>`: there is an additional fourth :c:expr:`PyObject*` parameter diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index def1903204add7a..25629b4da053da8 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -42,6 +42,8 @@ function,PyBytes_Repr,3.2,, function,PyBytes_Size,3.2,, var,PyBytes_Type,3.2,, type,PyCFunction,3.2,, +type,PyCFunctionFast,3.13,, +type,PyCFunctionFastWithKeywords,3.13,, type,PyCFunctionWithKeywords,3.2,, function,PyCFunction_GetFlags,3.2,, function,PyCFunction_GetFunction,3.2,, diff --git a/Include/methodobject.h b/Include/methodobject.h index 2381e8482b82a8b..452f891a7aba839 100644 --- a/Include/methodobject.h +++ b/Include/methodobject.h @@ -17,15 +17,22 @@ PyAPI_DATA(PyTypeObject) PyCFunction_Type; #define PyCFunction_Check(op) PyObject_TypeCheck((op), &PyCFunction_Type) typedef PyObject *(*PyCFunction)(PyObject *, PyObject *); -typedef PyObject *(*_PyCFunctionFast) (PyObject *, PyObject *const *, Py_ssize_t); +typedef PyObject *(*PyCFunctionFast) (PyObject *, PyObject *const *, Py_ssize_t); typedef PyObject *(*PyCFunctionWithKeywords)(PyObject *, PyObject *, PyObject *); -typedef PyObject *(*_PyCFunctionFastWithKeywords) (PyObject *, - PyObject *const *, Py_ssize_t, - PyObject *); +typedef PyObject *(*PyCFunctionFastWithKeywords) (PyObject *, + PyObject *const *, Py_ssize_t, + PyObject *); typedef PyObject *(*PyCMethod)(PyObject *, PyTypeObject *, PyObject *const *, size_t, PyObject *); +// For backwards compatibility. `METH_FASTCALL` was added to the stable API in +// 3.10 alongside `_PyCFunctionFastWithKeywords` and `_PyCFunctionFast`. +// Note that the underscore-prefixed names were documented in public docs; +// people may be using them. +typedef PyCFunctionFast _PyCFunctionFast; +typedef PyCFunctionWithKeywords _PyCFunctionWithKeywords; + // Cast an function to the PyCFunction type to use it with PyMethodDef. // // This macro can be used to prevent compiler warnings if the first parameter diff --git a/Misc/NEWS.d/next/C API/2024-01-26-21-54-42.gh-issue-114626.SKhbh_.rst b/Misc/NEWS.d/next/C API/2024-01-26-21-54-42.gh-issue-114626.SKhbh_.rst new file mode 100644 index 000000000000000..0da03ec122b555e --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-01-26-21-54-42.gh-issue-114626.SKhbh_.rst @@ -0,0 +1 @@ +Add ``PyCFunctionFast`` and ``PyCFunctionFastWithKeywords`` typedefs (identical to the existing ``_PyCFunctionFast`` and ``_PyCFunctionFastWithKeywords`` typedefs, just without a leading ``_`` prefix). diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index a9875f6ffd1a56a..ca7cf02961571ee 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2489,3 +2489,10 @@ added = '3.13' [function.PyList_GetItemRef] added = '3.13' +[typedef.PyCFunctionFast] + added = '3.13' + # "abi-only" since 3.10. (Callback type names aren't used in C code, + # but this function signature was expected with METH_FASTCALL.) +[typedef.PyCFunctionFastWithKeywords] + added = '3.13' + # "abi-only" since 3.10. (Same story as PyCFunctionFast.) diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 8d771adf307dc43..805de2971ba475b 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -393,7 +393,7 @@ method_vectorcall_FASTCALL( if (method_check_args(func, args, nargs, kwnames)) { return NULL; } - _PyCFunctionFast meth = (_PyCFunctionFast) + PyCFunctionFast meth = (PyCFunctionFast) method_enter_call(tstate, func); if (meth == NULL) { return NULL; @@ -412,7 +412,7 @@ method_vectorcall_FASTCALL_KEYWORDS( if (method_check_args(func, args, nargs, NULL)) { return NULL; } - _PyCFunctionFastWithKeywords meth = (_PyCFunctionFastWithKeywords) + PyCFunctionFastWithKeywords meth = (PyCFunctionFastWithKeywords) method_enter_call(tstate, func); if (meth == NULL) { return NULL; diff --git a/Objects/methodobject.c b/Objects/methodobject.c index b40b2821c3880d2..599fb05cb5874f2 100644 --- a/Objects/methodobject.c +++ b/Objects/methodobject.c @@ -417,7 +417,7 @@ cfunction_vectorcall_FASTCALL( return NULL; } Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); - _PyCFunctionFast meth = (_PyCFunctionFast) + PyCFunctionFast meth = (PyCFunctionFast) cfunction_enter_call(tstate, func); if (meth == NULL) { return NULL; @@ -433,7 +433,7 @@ cfunction_vectorcall_FASTCALL_KEYWORDS( { PyThreadState *tstate = _PyThreadState_GET(); Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); - _PyCFunctionFastWithKeywords meth = (_PyCFunctionFastWithKeywords) + PyCFunctionFastWithKeywords meth = (PyCFunctionFastWithKeywords) cfunction_enter_call(tstate, func); if (meth == NULL) { return NULL; @@ -552,4 +552,3 @@ cfunction_call(PyObject *func, PyObject *args, PyObject *kwargs) } return _Py_CheckFunctionResult(tstate, func, result, NULL); } - diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 28ade64e056ad70..6822e772e913e8d 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -3376,7 +3376,7 @@ dummy_func( STAT_INC(CALL, hit); PyCFunction cfunc = PyCFunction_GET_FUNCTION(callable); /* res = func(self, args, nargs) */ - res = ((_PyCFunctionFast)(void(*)(void))cfunc)( + res = ((PyCFunctionFast)(void(*)(void))cfunc)( PyCFunction_GET_SELF(callable), args, total_args); @@ -3407,8 +3407,8 @@ dummy_func( DEOPT_IF(PyCFunction_GET_FLAGS(callable) != (METH_FASTCALL | METH_KEYWORDS)); STAT_INC(CALL, hit); /* res = func(self, args, nargs, kwnames) */ - _PyCFunctionFastWithKeywords cfunc = - (_PyCFunctionFastWithKeywords)(void(*)(void)) + PyCFunctionFastWithKeywords cfunc = + (PyCFunctionFastWithKeywords)(void(*)(void)) PyCFunction_GET_FUNCTION(callable); res = cfunc(PyCFunction_GET_SELF(callable), args, total_args, NULL); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); @@ -3539,8 +3539,8 @@ dummy_func( DEOPT_IF(!Py_IS_TYPE(self, d_type)); STAT_INC(CALL, hit); int nargs = total_args - 1; - _PyCFunctionFastWithKeywords cfunc = - (_PyCFunctionFastWithKeywords)(void(*)(void))meth->ml_meth; + PyCFunctionFastWithKeywords cfunc = + (PyCFunctionFastWithKeywords)(void(*)(void))meth->ml_meth; res = cfunc(self, args + 1, nargs, NULL); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); @@ -3597,8 +3597,8 @@ dummy_func( PyObject *self = args[0]; DEOPT_IF(!Py_IS_TYPE(self, method->d_common.d_type)); STAT_INC(CALL, hit); - _PyCFunctionFast cfunc = - (_PyCFunctionFast)(void(*)(void))meth->ml_meth; + PyCFunctionFast cfunc = + (PyCFunctionFast)(void(*)(void))meth->ml_meth; int nargs = total_args - 1; res = cfunc(self, args + 1, nargs); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 7a0e0e43be019c5..11e2a1fe85d51dd 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -2843,7 +2843,7 @@ STAT_INC(CALL, hit); PyCFunction cfunc = PyCFunction_GET_FUNCTION(callable); /* res = func(self, args, nargs) */ - res = ((_PyCFunctionFast)(void(*)(void))cfunc)( + res = ((PyCFunctionFast)(void(*)(void))cfunc)( PyCFunction_GET_SELF(callable), args, total_args); @@ -2884,8 +2884,8 @@ if (PyCFunction_GET_FLAGS(callable) != (METH_FASTCALL | METH_KEYWORDS)) goto deoptimize; STAT_INC(CALL, hit); /* res = func(self, args, nargs, kwnames) */ - _PyCFunctionFastWithKeywords cfunc = - (_PyCFunctionFastWithKeywords)(void(*)(void)) + PyCFunctionFastWithKeywords cfunc = + (PyCFunctionFastWithKeywords)(void(*)(void)) PyCFunction_GET_FUNCTION(callable); res = cfunc(PyCFunction_GET_SELF(callable), args, total_args, NULL); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); @@ -3036,8 +3036,8 @@ if (!Py_IS_TYPE(self, d_type)) goto deoptimize; STAT_INC(CALL, hit); int nargs = total_args - 1; - _PyCFunctionFastWithKeywords cfunc = - (_PyCFunctionFastWithKeywords)(void(*)(void))meth->ml_meth; + PyCFunctionFastWithKeywords cfunc = + (PyCFunctionFastWithKeywords)(void(*)(void))meth->ml_meth; res = cfunc(self, args + 1, nargs, NULL); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); /* Free the arguments. */ @@ -3115,8 +3115,8 @@ PyObject *self = args[0]; if (!Py_IS_TYPE(self, method->d_common.d_type)) goto deoptimize; STAT_INC(CALL, hit); - _PyCFunctionFast cfunc = - (_PyCFunctionFast)(void(*)(void))meth->ml_meth; + PyCFunctionFast cfunc = + (PyCFunctionFast)(void(*)(void))meth->ml_meth; int nargs = total_args - 1; res = cfunc(self, args + 1, nargs); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 177bc327454f632..6c19adc60c690f3 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -1069,7 +1069,7 @@ STAT_INC(CALL, hit); PyCFunction cfunc = PyCFunction_GET_FUNCTION(callable); /* res = func(self, args, nargs) */ - res = ((_PyCFunctionFast)(void(*)(void))cfunc)( + res = ((PyCFunctionFast)(void(*)(void))cfunc)( PyCFunction_GET_SELF(callable), args, total_args); @@ -1115,8 +1115,8 @@ DEOPT_IF(PyCFunction_GET_FLAGS(callable) != (METH_FASTCALL | METH_KEYWORDS), CALL); STAT_INC(CALL, hit); /* res = func(self, args, nargs, kwnames) */ - _PyCFunctionFastWithKeywords cfunc = - (_PyCFunctionFastWithKeywords)(void(*)(void)) + PyCFunctionFastWithKeywords cfunc = + (PyCFunctionFastWithKeywords)(void(*)(void)) PyCFunction_GET_FUNCTION(callable); res = cfunc(PyCFunction_GET_SELF(callable), args, total_args, NULL); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); @@ -1523,8 +1523,8 @@ PyObject *self = args[0]; DEOPT_IF(!Py_IS_TYPE(self, method->d_common.d_type), CALL); STAT_INC(CALL, hit); - _PyCFunctionFast cfunc = - (_PyCFunctionFast)(void(*)(void))meth->ml_meth; + PyCFunctionFast cfunc = + (PyCFunctionFast)(void(*)(void))meth->ml_meth; int nargs = total_args - 1; res = cfunc(self, args + 1, nargs); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); @@ -1568,8 +1568,8 @@ DEOPT_IF(!Py_IS_TYPE(self, d_type), CALL); STAT_INC(CALL, hit); int nargs = total_args - 1; - _PyCFunctionFastWithKeywords cfunc = - (_PyCFunctionFastWithKeywords)(void(*)(void))meth->ml_meth; + PyCFunctionFastWithKeywords cfunc = + (PyCFunctionFastWithKeywords)(void(*)(void))meth->ml_meth; res = cfunc(self, args + 1, nargs, NULL); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); /* Free the arguments. */ From edb59d57188e5535729c3948d673d0de1b729c05 Mon Sep 17 00:00:00 2001 From: Martijn Pieters <mj@zopatista.com> Date: Thu, 15 Feb 2024 11:08:45 +0000 Subject: [PATCH 285/507] bpo-38364: unwrap partialmethods just like we unwrap partials (#16600) * bpo-38364: unwrap partialmethods just like we unwrap partials The inspect.isgeneratorfunction, inspect.iscoroutinefunction and inspect.isasyncgenfunction already unwrap functools.partial objects, this patch adds support for partialmethod objects as well. Also: Rename _partialmethod to __partialmethod__. Since we're checking this attribute on arbitrary function-like objects, we should use the namespace reserved for core Python. --------- Co-authored-by: Petr Viktorin <encukou@gmail.com> --- Doc/library/inspect.rst | 10 ++++++ Lib/functools.py | 13 +++++++- Lib/inspect.py | 6 ++-- Lib/test/test_inspect/test_inspect.py | 31 ++++++++++++++++++- .../2019-10-05-22-56-50.bpo-38364.sYTCWF.rst | 1 + 5 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-10-05-22-56-50.bpo-38364.sYTCWF.rst diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index f8b3e39c4f54f09..8a74cadb98a0dbd 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -340,6 +340,9 @@ attributes (see :ref:`import-mod-attrs` for module attributes): Functions wrapped in :func:`functools.partial` now return ``True`` if the wrapped function is a Python generator function. + .. versionchanged:: 3.13 + Functions wrapped in :func:`functools.partialmethod` now return ``True`` + if the wrapped function is a Python generator function. .. function:: isgenerator(object) @@ -363,6 +366,10 @@ attributes (see :ref:`import-mod-attrs` for module attributes): Sync functions marked with :func:`markcoroutinefunction` now return ``True``. + .. versionchanged:: 3.13 + Functions wrapped in :func:`functools.partialmethod` now return ``True`` + if the wrapped function is a :term:`coroutine function`. + .. function:: markcoroutinefunction(func) @@ -429,6 +436,9 @@ attributes (see :ref:`import-mod-attrs` for module attributes): Functions wrapped in :func:`functools.partial` now return ``True`` if the wrapped function is a :term:`asynchronous generator` function. + .. versionchanged:: 3.13 + Functions wrapped in :func:`functools.partialmethod` now return ``True`` + if the wrapped function is a :term:`coroutine function`. .. function:: isasyncgen(object) diff --git a/Lib/functools.py b/Lib/functools.py index 55990e742bf23fd..ee4197b386178d6 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -388,7 +388,7 @@ def _method(cls_or_self, /, *args, **keywords): keywords = {**self.keywords, **keywords} return self.func(cls_or_self, *self.args, *args, **keywords) _method.__isabstractmethod__ = self.__isabstractmethod__ - _method._partialmethod = self + _method.__partialmethod__ = self return _method def __get__(self, obj, cls=None): @@ -424,6 +424,17 @@ def _unwrap_partial(func): func = func.func return func +def _unwrap_partialmethod(func): + prev = None + while func is not prev: + prev = func + while isinstance(getattr(func, "__partialmethod__", None), partialmethod): + func = func.__partialmethod__ + while isinstance(func, partialmethod): + func = getattr(func, 'func') + func = _unwrap_partial(func) + return func + ################################################################################ ### LRU Cache function decorator ################################################################################ diff --git a/Lib/inspect.py b/Lib/inspect.py index f0b72662a9a0b21..450093a8b4c1ee6 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -383,8 +383,10 @@ def isfunction(object): def _has_code_flag(f, flag): """Return true if ``f`` is a function (or a method or functools.partial - wrapper wrapping a function) whose code object has the given ``flag`` + wrapper wrapping a function or a functools.partialmethod wrapping a + function) whose code object has the given ``flag`` set in its flags.""" + f = functools._unwrap_partialmethod(f) while ismethod(f): f = f.__func__ f = functools._unwrap_partial(f) @@ -2561,7 +2563,7 @@ def _signature_from_callable(obj, *, return sig try: - partialmethod = obj._partialmethod + partialmethod = obj.__partialmethod__ except AttributeError: pass else: diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 4611f62b293ff97..c5a6de5993fad49 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -206,12 +206,33 @@ def test_iscoroutine(self): gen_coro = gen_coroutine_function_example(1) coro = coroutine_function_example(1) + class PMClass: + async_generator_partialmethod_example = functools.partialmethod( + async_generator_function_example) + coroutine_partialmethod_example = functools.partialmethod( + coroutine_function_example) + gen_coroutine_partialmethod_example = functools.partialmethod( + gen_coroutine_function_example) + + # partialmethods on the class, bound to an instance + pm_instance = PMClass() + async_gen_coro_pmi = pm_instance.async_generator_partialmethod_example + gen_coro_pmi = pm_instance.gen_coroutine_partialmethod_example + coro_pmi = pm_instance.coroutine_partialmethod_example + + # partialmethods on the class, unbound but accessed via the class + async_gen_coro_pmc = PMClass.async_generator_partialmethod_example + gen_coro_pmc = PMClass.gen_coroutine_partialmethod_example + coro_pmc = PMClass.coroutine_partialmethod_example + self.assertFalse( inspect.iscoroutinefunction(gen_coroutine_function_example)) self.assertFalse( inspect.iscoroutinefunction( functools.partial(functools.partial( gen_coroutine_function_example)))) + self.assertFalse(inspect.iscoroutinefunction(gen_coro_pmi)) + self.assertFalse(inspect.iscoroutinefunction(gen_coro_pmc)) self.assertFalse(inspect.iscoroutine(gen_coro)) self.assertTrue( @@ -220,6 +241,8 @@ def test_iscoroutine(self): inspect.isgeneratorfunction( functools.partial(functools.partial( gen_coroutine_function_example)))) + self.assertTrue(inspect.isgeneratorfunction(gen_coro_pmi)) + self.assertTrue(inspect.isgeneratorfunction(gen_coro_pmc)) self.assertTrue(inspect.isgenerator(gen_coro)) async def _fn3(): @@ -285,6 +308,8 @@ def do_something_static(): inspect.iscoroutinefunction( functools.partial(functools.partial( coroutine_function_example)))) + self.assertTrue(inspect.iscoroutinefunction(coro_pmi)) + self.assertTrue(inspect.iscoroutinefunction(coro_pmc)) self.assertTrue(inspect.iscoroutine(coro)) self.assertFalse( @@ -297,6 +322,8 @@ def do_something_static(): inspect.isgeneratorfunction( functools.partial(functools.partial( coroutine_function_example)))) + self.assertFalse(inspect.isgeneratorfunction(coro_pmi)) + self.assertFalse(inspect.isgeneratorfunction(coro_pmc)) self.assertFalse(inspect.isgenerator(coro)) self.assertFalse( @@ -311,6 +338,8 @@ def do_something_static(): inspect.isasyncgenfunction( functools.partial(functools.partial( async_generator_function_example)))) + self.assertTrue(inspect.isasyncgenfunction(async_gen_coro_pmi)) + self.assertTrue(inspect.isasyncgenfunction(async_gen_coro_pmc)) self.assertTrue(inspect.isasyncgen(async_gen_coro)) coro.close(); gen_coro.close(); # silence warnings @@ -3389,7 +3418,7 @@ def test(self: 'anno', x): def test_signature_on_fake_partialmethod(self): def foo(a): pass - foo._partialmethod = 'spam' + foo.__partialmethod__ = 'spam' self.assertEqual(str(inspect.signature(foo)), '(a)') def test_signature_on_decorated(self): diff --git a/Misc/NEWS.d/next/Library/2019-10-05-22-56-50.bpo-38364.sYTCWF.rst b/Misc/NEWS.d/next/Library/2019-10-05-22-56-50.bpo-38364.sYTCWF.rst new file mode 100644 index 000000000000000..87fb5ae8fd0eeda --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-10-05-22-56-50.bpo-38364.sYTCWF.rst @@ -0,0 +1 @@ +The ``inspect`` functions ``isgeneratorfunction``, ``iscoroutinefunction``, ``isasyncgenfunction`` now support ``functools.partialmethod`` wrapped functions the same way they support ``functools.partial``. From 98ee4ecdbc07ecb8afc37383ad9f7a8466a6d795 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Thu, 15 Feb 2024 13:10:32 +0100 Subject: [PATCH 286/507] gh-113317: Argument Clinic: don't use fail() in CLI (#115513) --- Tools/clinic/clinic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index e63596c30079403..2381004f02352ab 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -2500,12 +2500,12 @@ def parse_file( extension = os.path.splitext(filename)[1][1:] if not extension: - fail(f"Can't extract file type for file {filename!r}") + raise ClinicError(f"Can't extract file type for file {filename!r}") try: language = extensions[extension](filename) except KeyError: - fail(f"Can't identify file type for file {filename!r}") + raise ClinicError(f"Can't identify file type for file {filename!r}") with open(filename, encoding="utf-8") as f: raw = f.read() From 7f074a771bc4e3e299799fabf9b054a03f6693d2 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Thu, 15 Feb 2024 13:22:21 +0100 Subject: [PATCH 287/507] gh-113317: Argument Clinic: don't use global state in warn() and fail() (#115510) --- Lib/test/test_clinic.py | 10 +++++----- Tools/clinic/clinic.py | 29 ++++++++++++++++++----------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index e987ce546054979..be35e80fb02c725 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -1900,7 +1900,7 @@ def test_parameters_no_more_than_one_vararg(self): *vararg1: object *vararg2: object """ - self.expect_failure(block, err, lineno=0) + self.expect_failure(block, err, lineno=3) def test_function_not_at_column_0(self): function = self.parse_function(""" @@ -2286,10 +2286,10 @@ def test_non_ascii_character_in_docstring(self): self.parse(block) # The line numbers are off; this is a known limitation. expected = dedent("""\ - Warning in file 'clinic_tests' on line 0: + Warning: Non-ascii characters are not allowed in docstrings: 'á' - Warning in file 'clinic_tests' on line 0: + Warning: Non-ascii characters are not allowed in docstrings: 'ü', 'á', 'ß' """) @@ -2390,7 +2390,7 @@ def test_state_func_docstring_no_summary(self): docstring1 docstring2 """ - self.expect_failure(block, err, lineno=0) + self.expect_failure(block, err, lineno=3) def test_state_func_docstring_only_one_param_template(self): err = "You may not specify {parameters} more than once in a docstring!" @@ -2404,7 +2404,7 @@ def test_state_func_docstring_only_one_param_template(self): these are the params again: {parameters} """ - self.expect_failure(block, err, lineno=0) + self.expect_failure(block, err, lineno=7) class ClinicExternalTest(TestCase): diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 2381004f02352ab..4fa07ee3db20398 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -116,11 +116,6 @@ def warn_or_fail( line_number: int | None = None, ) -> None: joined = " ".join([str(a) for a in args]) - if clinic: - if filename is None: - filename = clinic.filename - if getattr(clinic, 'block_parser', None) and (line_number is None): - line_number = clinic.block_parser.line_number error = ClinicError(joined, filename=filename, lineno=line_number) if fail: raise error @@ -277,7 +272,7 @@ class Language(metaclass=abc.ABCMeta): checksum_line = "" def __init__(self, filename: str) -> None: - ... + self.filename = filename @abc.abstractmethod def render( @@ -833,8 +828,6 @@ def output_templates( if not p.is_optional(): min_kw_only = i - max_pos elif p.is_vararg(): - if vararg != self.NO_VARARG: - fail("Too many var args") pseudo_args += 1 vararg = i - 1 else: @@ -1889,7 +1882,12 @@ def __next__(self) -> Block: raise StopIteration if self.dsl_name: - return_value = self.parse_clinic_block(self.dsl_name) + try: + return_value = self.parse_clinic_block(self.dsl_name) + except ClinicError as exc: + exc.filename = self.language.filename + exc.lineno = self.line_number + raise self.dsl_name = None self.first_block = False return return_value @@ -5071,6 +5069,7 @@ def parse(self, block: Block) -> None: self.state(line) except ClinicError as exc: exc.lineno = line_number + exc.filename = self.clinic.filename raise self.do_post_block_processing_cleanup(line_number) @@ -5078,7 +5077,8 @@ def parse(self, block: Block) -> None: if self.preserve_output: if block.output: - fail("'preserve' only works for blocks that don't produce any output!") + fail("'preserve' only works for blocks that don't produce any output!", + line_number=line_number) block.output = self.saved_output def in_docstring(self) -> bool: @@ -5503,6 +5503,8 @@ def parse_parameter(self, line: str) -> None: f"invalid parameter declaration (**kwargs?): {line!r}") if function_args.vararg: + if any(p.is_vararg() for p in self.function.parameters.values()): + fail("Too many var args") is_vararg = True parameter = function_args.vararg else: @@ -6174,7 +6176,12 @@ def do_post_block_processing_cleanup(self, lineno: int) -> None: return self.check_remaining_star(lineno) - self.function.docstring = self.format_docstring() + try: + self.function.docstring = self.format_docstring() + except ClinicError as exc: + exc.lineno = lineno + exc.filename = self.clinic.filename + raise From a0149fa6cf5792728bb18ee8e63f6f43b1c96934 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Thu, 15 Feb 2024 14:21:31 +0100 Subject: [PATCH 288/507] gh-113317: Argument Clinic: remove global clinic instance (#115517) --- Tools/clinic/clinic.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 4fa07ee3db20398..77d492a386651fd 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -277,7 +277,7 @@ def __init__(self, filename: str) -> None: @abc.abstractmethod def render( self, - clinic: Clinic | None, + clinic: Clinic, signatures: Iterable[Module | Class | Function] ) -> str: ... @@ -630,7 +630,7 @@ def parse_line(self, line: str) -> None: def render( self, - clinic: Clinic | None, + clinic: Clinic, signatures: Iterable[Module | Class | Function] ) -> str: function = None @@ -1584,7 +1584,7 @@ def render_option_group_parsing( def render_function( self, - clinic: Clinic | None, + clinic: Clinic, f: Function | None ) -> str: if f is None or clinic is None: @@ -2220,7 +2220,6 @@ def __init__(self, clinic: Clinic) -> None: ... def parse(self, block: Block) -> None: ... -clinic: Clinic | None = None class Clinic: presets_text = """ @@ -2345,9 +2344,6 @@ def __init__( assert name in self.destination_buffers preset[name] = buffer - global clinic - clinic = self - def add_include(self, name: str, reason: str, *, condition: str | None = None) -> None: try: From b0e5c35ded6d4a16d7a021c10c99bac94250edd0 Mon Sep 17 00:00:00 2001 From: "T. Wouters" <thomas@python.org> Date: Thu, 15 Feb 2024 14:24:13 +0100 Subject: [PATCH 289/507] gh-115490: Work around test.support.interpreters.channels not handling unloading (#115515) Work around test.support.interpreters.channels not handling unloading, which regrtest does when running tests sequentially, by explicitly skipping the unloading of test.support.interpreters and its submodules. This can be rolled back once test.support.interpreters.channels supports unloading, if we are keeping sequential runs in the same process around. --- Lib/test/libregrtest/main.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py index 7ca1b1cb65ae40e..b24c1b9205450ba 100644 --- a/Lib/test/libregrtest/main.py +++ b/Lib/test/libregrtest/main.py @@ -335,10 +335,15 @@ def run_tests_sequentially(self, runtests) -> None: result = self.run_test(test_name, runtests, tracer) - # Unload the newly imported test modules (best effort finalization) + # Unload the newly imported test modules (best effort + # finalization). To work around gh-115490, don't unload + # test.support.interpreters and its submodules even if they + # weren't loaded before. + keep = "test.support.interpreters" new_modules = [module for module in sys.modules if module not in save_modules and - module.startswith(("test.", "test_"))] + module.startswith(("test.", "test_")) + and not module.startswith(keep)] for module in new_modules: sys.modules.pop(module, None) # Remove the attribute of the parent module. From ad4f909e0e7890e027c4ae7fea74586667242ad3 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Thu, 15 Feb 2024 08:37:54 -0500 Subject: [PATCH 290/507] gh-115432: Add critical section variant that handles a NULL object (#115433) This adds `Py_XBEGIN_CRITICAL_SECTION` and `Py_XEND_CRITICAL_SECTION`, which accept a possibly NULL object as an argument. If the argument is NULL, then nothing is locked or unlocked. Otherwise, they behave like `Py_BEGIN/END_CRITICAL_SECTION`. --- Include/internal/pycore_critical_section.h | 29 +++++++++++++++++++ .../test_critical_sections.c | 9 ++++++ 2 files changed, 38 insertions(+) diff --git a/Include/internal/pycore_critical_section.h b/Include/internal/pycore_critical_section.h index 38ed8cd69804ba9..30820a24f5bb64c 100644 --- a/Include/internal/pycore_critical_section.h +++ b/Include/internal/pycore_critical_section.h @@ -96,6 +96,15 @@ extern "C" { _PyCriticalSection_End(&_cs); \ } +# define Py_XBEGIN_CRITICAL_SECTION(op) \ + { \ + _PyCriticalSection _cs_opt = {0}; \ + _PyCriticalSection_XBegin(&_cs_opt, _PyObject_CAST(op)) + +# define Py_XEND_CRITICAL_SECTION() \ + _PyCriticalSection_XEnd(&_cs_opt); \ + } + # define Py_BEGIN_CRITICAL_SECTION2(a, b) \ { \ _PyCriticalSection2 _cs2; \ @@ -131,6 +140,8 @@ extern "C" { // The critical section APIs are no-ops with the GIL. # define Py_BEGIN_CRITICAL_SECTION(op) # define Py_END_CRITICAL_SECTION() +# define Py_XBEGIN_CRITICAL_SECTION(op) +# define Py_XEND_CRITICAL_SECTION() # define Py_BEGIN_CRITICAL_SECTION2(a, b) # define Py_END_CRITICAL_SECTION2() # define _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(mutex) @@ -187,6 +198,16 @@ _PyCriticalSection_Begin(_PyCriticalSection *c, PyMutex *m) } } +static inline void +_PyCriticalSection_XBegin(_PyCriticalSection *c, PyObject *op) +{ +#ifdef Py_GIL_DISABLED + if (op != NULL) { + _PyCriticalSection_Begin(c, &_PyObject_CAST(op)->ob_mutex); + } +#endif +} + // Removes the top-most critical section from the thread's stack of critical // sections. If the new top-most critical section is inactive, then it is // resumed. @@ -209,6 +230,14 @@ _PyCriticalSection_End(_PyCriticalSection *c) _PyCriticalSection_Pop(c); } +static inline void +_PyCriticalSection_XEnd(_PyCriticalSection *c) +{ + if (c->mutex) { + _PyCriticalSection_End(c); + } +} + static inline void _PyCriticalSection2_Begin(_PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2) { diff --git a/Modules/_testinternalcapi/test_critical_sections.c b/Modules/_testinternalcapi/test_critical_sections.c index 1f7e311558b27cb..94da0468fcf149a 100644 --- a/Modules/_testinternalcapi/test_critical_sections.c +++ b/Modules/_testinternalcapi/test_critical_sections.c @@ -49,6 +49,15 @@ test_critical_sections(PyObject *self, PyObject *Py_UNUSED(args)) Py_END_CRITICAL_SECTION2(); assert_nogil(!PyMutex_IsLocked(&d2->ob_mutex)); + // Optional variant behaves the same if the object is non-NULL + Py_XBEGIN_CRITICAL_SECTION(d1); + assert_nogil(PyMutex_IsLocked(&d1->ob_mutex)); + Py_XEND_CRITICAL_SECTION(); + + // No-op + Py_XBEGIN_CRITICAL_SECTION(NULL); + Py_XEND_CRITICAL_SECTION(); + Py_DECREF(d2); Py_DECREF(d1); Py_RETURN_NONE; From 9d34f60783b726de5bb0352d0efb1cbd23a4ac89 Mon Sep 17 00:00:00 2001 From: Thomas Wouters <thomas@python.org> Date: Thu, 15 Feb 2024 14:30:16 +0100 Subject: [PATCH 291/507] Python 3.13.0a4 --- Include/patchlevel.h | 4 +- Lib/pydoc_data/topics.py | 68 +- Misc/NEWS.d/3.13.0a4.rst | 1433 +++++++++++++++++ ...-02-01-20-08-11.gh-issue-114875.x_2iZ9.rst | 1 - ...-02-07-08-23-48.gh-issue-114099.XcEXEZ.rst | 2 - ...-02-08-17-38-56.gh-issue-113632.y9KIGb.rst | 2 - ...-02-08-19-36-20.gh-issue-115167.LB9nDK.rst | 1 - ...-11-15-13-47-48.gh-issue-112066.22WsqR.rst | 5 - ...-11-16-02-07-48.gh-issue-110850.DQGNfF.rst | 9 - ...-01-23-21-45-02.gh-issue-114329.YRaBoe.rst | 3 - ...-01-26-21-54-42.gh-issue-114626.SKhbh_.rst | 1 - ...-01-29-12-13-24.gh-issue-114685.B07RME.rst | 3 - ...-01-31-15-43-35.gh-issue-114685.n7aRmX.rst | 3 - ...-02-05-17-11-15.gh-issue-111140.WMEjid.rst | 2 - ...-05-16-06-52-34.gh-issue-104530.mJnA0W.rst | 1 - ...3-06-06-19-09-00.gh-issue-55664.vYYl0V.rst | 1 - ...-12-22-13-21-39.gh-issue-113055.47xBMF.rst | 5 - ...-12-24-03-25-28.gh-issue-113464.dvjQmA.rst | 4 - ...-01-08-21-57-41.gh-issue-112050.qwgjx1.rst | 1 - ...-01-11-22-58-45.gh-issue-112050.hDuvDW.rst | 1 - ...-01-12-16-40-07.gh-issue-113939.Yi3L-e.rst | 4 - ...-01-16-14-41-54.gh-issue-114058.Cb2b8h.rst | 1 - ...-01-17-00-52-57.gh-issue-113884.CvEjUE.rst | 1 - ...-01-17-05-09-32.gh-issue-112354.Run9ko.rst | 2 - ...-01-17-23-39-20.gh-issue-114050.Lnv1oq.rst | 2 - ...-01-18-20-20-37.gh-issue-112529.oVNvDG.rst | 3 - ...-01-19-13-18-13.gh-issue-114265.7HAi--.rst | 1 - ...-01-21-17-29-32.gh-issue-114388.UVGO4K.rst | 5 - ...-01-22-09-49-02.gh-issue-114083.hf1-ku.rst | 1 - ...-01-22-15-10-01.gh-issue-114456.fBFEJF.rst | 1 - ...-01-25-18-50-49.gh-issue-112529.IbbApA.rst | 4 - ...-01-31-09-10-10.gh-issue-107944.XWm1B-.rst | 1 - ...-02-01-18-16-52.gh-issue-114806.wrH2J6.rst | 3 - ...4-02-01-23-43-49.gh-issue-76763.o_2J6i.rst | 3 - ...-02-02-05-27-48.gh-issue-113462.VMml8q.rst | 2 - ...-02-03-01-48-38.gh-issue-114944.4J5ELD.rst | 1 - ...-02-03-04-07-18.gh-issue-114887.uLSFmN.rst | 2 - ...-02-05-12-40-26.gh-issue-115011.L1AKF5.rst | 3 - ...-02-07-00-18-42.gh-issue-112069.jRDRR5.rst | 1 - ...-02-07-07-50-12.gh-issue-114828.nSXwMi.rst | 2 - ...-02-07-18-04-36.gh-issue-114695.o9wP5P.rst | 3 - ...-02-12-17-18-26.gh-issue-114570.BzwMlJ.rst | 3 - ...-02-14-23-50-55.gh-issue-112087.H_4W_v.rst | 2 - ...-01-17-11-40-03.gh-issue-114123.LuueXf.rst | 7 - ...-02-12-12-26-17.gh-issue-115233.aug6r9.rst | 1 - ...-04-25-03-01-23.gh-issue-103820.LCSpza.rst | 2 - ...4-01-17-23-18-15.gh-issue-96905.UYaxoU.rst | 1 - .../2019-10-05-22-56-50.bpo-38364.sYTCWF.rst | 1 - ...2-07-31-01-24-40.gh-issue-88569.eU0--b.rst | 4 - ...-03-08-00-02-30.gh-issue-102512.LiugDr.rst | 3 - ...3-03-15-03-21-18.gh-issue-85984.Xaq6ZN.rst | 2 - ...-04-08-11-41-07.gh-issue-101599.PaWNFh.rst | 1 - ...3-05-06-04-57-10.gh-issue-96471.C9wAU7.rst | 1 - ...-05-08-09-30-00.gh-issue-104282.h4c6Eb.rst | 3 - ...-05-30-18-30-11.gh-issue-105102.SnpK04.rst | 2 - ...-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst | 2 - ...3-07-23-12-28-26.gh-issue-75705.aB2-Ww.rst | 1 - ...3-09-22-22-17-45.gh-issue-38807.m9McRN.rst | 3 - ...-10-04-11-09-30.gh-issue-110345.fZU1ud.rst | 1 - ...-10-19-02-08-12.gh-issue-111051.8h1Dpk.rst | 1 - ...3-10-24-19-19-54.gh-issue-82626._hfLRf.rst | 2 - ...3-10-27-19-24-58.gh-issue-43457.84lx9H.rst | 8 - ...-11-04-22-32-27.gh-issue-111741.f1ufr8.rst | 1 - ...-11-18-16-30-21.gh-issue-112240.YXS0tj.rst | 2 - ...-11-24-19-08-50.gh-issue-112343.RarGFC.rst | 1 - ...3-11-27-19-54-43.gh-issue-59013.chpQ0e.rst | 1 - ...-12-09-23-31-17.gh-issue-112919.S5k9QN.rst | 2 - ...3-12-18-20-10-50.gh-issue-89039.gqFdtU.rst | 6 - ...-01-04-20-58-17.gh-issue-113225.-nyJM4.rst | 2 - ...-01-05-16-27-34.gh-issue-113732.fgDRXA.rst | 2 - ...-01-07-21-04-24.gh-issue-113796.6iNsCR.rst | 3 - ...4-01-11-15-10-53.gh-issue-97959.UOj6d4.rst | 7 - ...-01-11-20-47-49.gh-issue-113951.AzlqFK.rst | 7 - ...-01-12-09-35-07.gh-issue-112202.t_0V1m.rst | 1 - ...4-01-12-17-32-36.gh-issue-79634.uTSTRI.rst | 2 - ...-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst | 3 - ...-01-15-19-54-41.gh-issue-114087.Xic5vY.rst | 1 - ...4-01-15-20-21-33.gh-issue-83648.HzD_fY.rst | 2 - ...-01-16-15-59-06.gh-issue-114149.LJ8IPm.rst | 1 - ...-01-17-18-53-51.gh-issue-104522.3NyDf4.rst | 3 - ...-01-18-10-07-52.gh-issue-114198.lK4Iif.rst | 2 - ...-01-18-22-29-28.gh-issue-101438.1-uUi_.rst | 4 - ...-01-19-12-05-22.gh-issue-114281.H5JQe4.rst | 3 - ...-01-19-15-48-06.gh-issue-114328.hixxW3.rst | 4 - ...-01-19-18-41-02.gh-issue-114321.yj_Xw3.rst | 2 - ...-01-21-16-32-55.gh-issue-114257.bCFld5.rst | 2 - ...-01-22-11-43-38.gh-issue-114423.6mMoPH.rst | 1 - ...4-01-22-12-10-34.gh-issue-75128.4FGlRS.rst | 2 - ...-01-23-11-04-21.gh-issue-113267.xe_Pxe.rst | 2 - ...-01-23-13-03-22.gh-issue-100414.5kTdU5.rst | 2 - ...-01-23-14-11-49.gh-issue-114315.KeVdzl.rst | 2 - ...-01-23-21-20-40.gh-issue-114492.vKxl5o.rst | 2 - ...-01-23-23-13-47.gh-issue-109653.KLBHmT.rst | 1 - ...4-01-24-17-25-18.gh-issue-69893.PQq5fR.rst | 2 - ...-01-24-20-11-46.gh-issue-112451.7YrG4p.rst | 2 - ...4-01-24-20-51-49.gh-issue-91602.8fOH8l.rst | 3 - ...4-01-25-19-22-17.gh-issue-83383.3GwO9v.rst | 5 - ...4-01-26-16-46-21.gh-issue-77749.NY_7TS.rst | 2 - ...-01-27-20-11-24.gh-issue-113280.CZPQMf.rst | 2 - ...-01-28-00-48-12.gh-issue-109653.vF4exe.rst | 1 - ...4-01-28-18-38-18.gh-issue-70303._Lt_pj.rst | 2 - ...-01-28-19-40-40.gh-issue-114678.kYKcJw.rst | 3 - ...-01-30-15-34-08.gh-issue-110190.Z5PQQX.rst | 1 - ...4-01-30-22-10-50.gh-issue-49766.yulJL_.rst | 8 - ...-01-31-20-07-11.gh-issue-109475.lmTb9S.rst | 2 - ...-02-01-10-19-11.gh-issue-114071.vkm2G_.rst | 1 - ...-02-02-15-50-13.gh-issue-114894.DF-dSd.rst | 1 - ...-02-03-16-59-25.gh-issue-114959.dCfAG2.rst | 2 - ...-02-03-17-54-17.gh-issue-114965.gHksCK.rst | 1 - ...4-02-04-02-28-37.gh-issue-85984.NHZVTQ.rst | 1 - ...-02-04-13-17-33.gh-issue-114628.WJpqqS.rst | 2 - ...4-02-05-16-48-06.gh-issue-97928.JZCies.rst | 5 - ...-02-06-03-55-46.gh-issue-115060.EkWRpP.rst | 1 - ...4-02-06-15-16-28.gh-issue-67837._JKa73.rst | 2 - ...4-02-07-12-37-52.gh-issue-79382.Yz_5WB.rst | 2 - ...-02-08-13-26-14.gh-issue-115059.DqP9dr.rst | 1 - ...-02-08-14-21-28.gh-issue-115133.ycl4ko.rst | 2 - ...-02-08-17-04-58.gh-issue-112903.SN_vUs.rst | 2 - ...-02-09-07-20-16.gh-issue-115165.yfJLXA.rst | 4 - ...-02-10-15-24-20.gh-issue-102840.4mnDq1.rst | 3 - ...-02-11-20-23-36.gh-issue-114563.RzxNYT.rst | 4 - ...-02-13-18-27-03.gh-issue-115392.gle5tp.rst | 2 - ...-02-12-00-33-01.gh-issue-115243.e1oGX8.rst | 1 - ...-02-13-15-14-39.gh-issue-115399.xT-scP.rst | 1 - ...-06-02-05-04-15.gh-issue-105089.KaZFtU.rst | 4 - ...-02-02-13-18-55.gh-issue-114099.C_ycWg.rst | 1 - ...-02-05-02-45-51.gh-issue-115015.rgtiDB.rst | 5 - ...-02-05-19-00-32.gh-issue-109991.yJSEkw.rst | 2 - ...-02-14-15-58-13.gh-issue-113516.TyIHWx.rst | 1 - ...3-08-11-18-21-38.gh-issue-89240.dtSOLG.rst | 1 - ...-12-19-22-32-28.gh-issue-112984.F7kFMl.rst | 1 - ...-01-23-00-05-05.gh-issue-100107.lkbP_Q.rst | 1 - ...-02-01-14-35-05.gh-issue-111239.SO7SUF.rst | 1 - ...-02-05-16-53-12.gh-issue-109991.YqjnDz.rst | 1 - ...-02-06-09-05-13.gh-issue-115009.ShMjZs.rst | 1 - ...-02-08-21-37-22.gh-issue-115049.X1ObpJ.rst | 1 - ...2-11-18-10-05-35.gh-issue-87804.rhlDmD.rst | 1 - ...-01-23-11-35-26.gh-issue-114490.FrQOQ0.rst | 1 - ...-02-05-18-30-27.gh-issue-109991.tun6Yu.rst | 1 - ...-02-06-09-01-10.gh-issue-115009.ysau7e.rst | 1 - README.rst | 2 +- 141 files changed, 1471 insertions(+), 349 deletions(-) create mode 100644 Misc/NEWS.d/3.13.0a4.rst delete mode 100644 Misc/NEWS.d/next/Build/2024-02-01-20-08-11.gh-issue-114875.x_2iZ9.rst delete mode 100644 Misc/NEWS.d/next/Build/2024-02-07-08-23-48.gh-issue-114099.XcEXEZ.rst delete mode 100644 Misc/NEWS.d/next/Build/2024-02-08-17-38-56.gh-issue-113632.y9KIGb.rst delete mode 100644 Misc/NEWS.d/next/Build/2024-02-08-19-36-20.gh-issue-115167.LB9nDK.rst delete mode 100644 Misc/NEWS.d/next/C API/2023-11-15-13-47-48.gh-issue-112066.22WsqR.rst delete mode 100644 Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst delete mode 100644 Misc/NEWS.d/next/C API/2024-01-23-21-45-02.gh-issue-114329.YRaBoe.rst delete mode 100644 Misc/NEWS.d/next/C API/2024-01-26-21-54-42.gh-issue-114626.SKhbh_.rst delete mode 100644 Misc/NEWS.d/next/C API/2024-01-29-12-13-24.gh-issue-114685.B07RME.rst delete mode 100644 Misc/NEWS.d/next/C API/2024-01-31-15-43-35.gh-issue-114685.n7aRmX.rst delete mode 100644 Misc/NEWS.d/next/C API/2024-02-05-17-11-15.gh-issue-111140.WMEjid.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-05-16-06-52-34.gh-issue-104530.mJnA0W.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-06-06-19-09-00.gh-issue-55664.vYYl0V.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-24-03-25-28.gh-issue-113464.dvjQmA.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-08-21-57-41.gh-issue-112050.qwgjx1.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-11-22-58-45.gh-issue-112050.hDuvDW.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-12-16-40-07.gh-issue-113939.Yi3L-e.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-16-14-41-54.gh-issue-114058.Cb2b8h.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-17-00-52-57.gh-issue-113884.CvEjUE.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-17-05-09-32.gh-issue-112354.Run9ko.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-17-23-39-20.gh-issue-114050.Lnv1oq.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-18-20-20-37.gh-issue-112529.oVNvDG.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-19-13-18-13.gh-issue-114265.7HAi--.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-21-17-29-32.gh-issue-114388.UVGO4K.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-22-09-49-02.gh-issue-114083.hf1-ku.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-22-15-10-01.gh-issue-114456.fBFEJF.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-25-18-50-49.gh-issue-112529.IbbApA.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-31-09-10-10.gh-issue-107944.XWm1B-.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-01-18-16-52.gh-issue-114806.wrH2J6.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-01-23-43-49.gh-issue-76763.o_2J6i.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-02-05-27-48.gh-issue-113462.VMml8q.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-03-01-48-38.gh-issue-114944.4J5ELD.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-03-04-07-18.gh-issue-114887.uLSFmN.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-05-12-40-26.gh-issue-115011.L1AKF5.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-07-00-18-42.gh-issue-112069.jRDRR5.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-07-07-50-12.gh-issue-114828.nSXwMi.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-07-18-04-36.gh-issue-114695.o9wP5P.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-12-17-18-26.gh-issue-114570.BzwMlJ.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-14-23-50-55.gh-issue-112087.H_4W_v.rst delete mode 100644 Misc/NEWS.d/next/Documentation/2024-01-17-11-40-03.gh-issue-114123.LuueXf.rst delete mode 100644 Misc/NEWS.d/next/Documentation/2024-02-12-12-26-17.gh-issue-115233.aug6r9.rst delete mode 100644 Misc/NEWS.d/next/IDLE/2023-04-25-03-01-23.gh-issue-103820.LCSpza.rst delete mode 100644 Misc/NEWS.d/next/IDLE/2024-01-17-23-18-15.gh-issue-96905.UYaxoU.rst delete mode 100644 Misc/NEWS.d/next/Library/2019-10-05-22-56-50.bpo-38364.sYTCWF.rst delete mode 100644 Misc/NEWS.d/next/Library/2022-07-31-01-24-40.gh-issue-88569.eU0--b.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-03-08-00-02-30.gh-issue-102512.LiugDr.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-03-15-03-21-18.gh-issue-85984.Xaq6ZN.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-04-08-11-41-07.gh-issue-101599.PaWNFh.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-05-06-04-57-10.gh-issue-96471.C9wAU7.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-05-08-09-30-00.gh-issue-104282.h4c6Eb.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-05-30-18-30-11.gh-issue-105102.SnpK04.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-07-23-12-28-26.gh-issue-75705.aB2-Ww.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-09-22-22-17-45.gh-issue-38807.m9McRN.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-10-04-11-09-30.gh-issue-110345.fZU1ud.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-10-19-02-08-12.gh-issue-111051.8h1Dpk.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-10-24-19-19-54.gh-issue-82626._hfLRf.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-10-27-19-24-58.gh-issue-43457.84lx9H.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-11-04-22-32-27.gh-issue-111741.f1ufr8.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-11-18-16-30-21.gh-issue-112240.YXS0tj.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-11-24-19-08-50.gh-issue-112343.RarGFC.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-11-27-19-54-43.gh-issue-59013.chpQ0e.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-12-09-23-31-17.gh-issue-112919.S5k9QN.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-12-18-20-10-50.gh-issue-89039.gqFdtU.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-04-20-58-17.gh-issue-113225.-nyJM4.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-05-16-27-34.gh-issue-113732.fgDRXA.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-07-21-04-24.gh-issue-113796.6iNsCR.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-11-15-10-53.gh-issue-97959.UOj6d4.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-11-20-47-49.gh-issue-113951.AzlqFK.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-12-09-35-07.gh-issue-112202.t_0V1m.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-12-17-32-36.gh-issue-79634.uTSTRI.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-15-19-54-41.gh-issue-114087.Xic5vY.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-83648.HzD_fY.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-16-15-59-06.gh-issue-114149.LJ8IPm.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-17-18-53-51.gh-issue-104522.3NyDf4.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-18-10-07-52.gh-issue-114198.lK4Iif.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-18-22-29-28.gh-issue-101438.1-uUi_.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-19-12-05-22.gh-issue-114281.H5JQe4.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-19-15-48-06.gh-issue-114328.hixxW3.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-19-18-41-02.gh-issue-114321.yj_Xw3.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-21-16-32-55.gh-issue-114257.bCFld5.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-22-11-43-38.gh-issue-114423.6mMoPH.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-22-12-10-34.gh-issue-75128.4FGlRS.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-23-11-04-21.gh-issue-113267.xe_Pxe.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-23-13-03-22.gh-issue-100414.5kTdU5.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-23-14-11-49.gh-issue-114315.KeVdzl.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-23-21-20-40.gh-issue-114492.vKxl5o.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-23-23-13-47.gh-issue-109653.KLBHmT.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-24-17-25-18.gh-issue-69893.PQq5fR.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-24-20-11-46.gh-issue-112451.7YrG4p.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-25-19-22-17.gh-issue-83383.3GwO9v.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-26-16-46-21.gh-issue-77749.NY_7TS.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-27-20-11-24.gh-issue-113280.CZPQMf.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-28-00-48-12.gh-issue-109653.vF4exe.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-28-18-38-18.gh-issue-70303._Lt_pj.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-28-19-40-40.gh-issue-114678.kYKcJw.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-30-15-34-08.gh-issue-110190.Z5PQQX.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-30-22-10-50.gh-issue-49766.yulJL_.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-01-31-20-07-11.gh-issue-109475.lmTb9S.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-02-01-10-19-11.gh-issue-114071.vkm2G_.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-02-02-15-50-13.gh-issue-114894.DF-dSd.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-02-03-16-59-25.gh-issue-114959.dCfAG2.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-02-03-17-54-17.gh-issue-114965.gHksCK.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-02-04-02-28-37.gh-issue-85984.NHZVTQ.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-02-04-13-17-33.gh-issue-114628.WJpqqS.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-02-05-16-48-06.gh-issue-97928.JZCies.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-02-06-03-55-46.gh-issue-115060.EkWRpP.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-02-06-15-16-28.gh-issue-67837._JKa73.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-02-07-12-37-52.gh-issue-79382.Yz_5WB.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-02-08-13-26-14.gh-issue-115059.DqP9dr.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-02-08-14-21-28.gh-issue-115133.ycl4ko.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-02-08-17-04-58.gh-issue-112903.SN_vUs.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-02-11-20-23-36.gh-issue-114563.RzxNYT.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-02-13-18-27-03.gh-issue-115392.gle5tp.rst delete mode 100644 Misc/NEWS.d/next/Security/2024-02-12-00-33-01.gh-issue-115243.e1oGX8.rst delete mode 100644 Misc/NEWS.d/next/Security/2024-02-13-15-14-39.gh-issue-115399.xT-scP.rst delete mode 100644 Misc/NEWS.d/next/Tests/2023-06-02-05-04-15.gh-issue-105089.KaZFtU.rst delete mode 100644 Misc/NEWS.d/next/Tests/2024-02-02-13-18-55.gh-issue-114099.C_ycWg.rst delete mode 100644 Misc/NEWS.d/next/Tools-Demos/2024-02-05-02-45-51.gh-issue-115015.rgtiDB.rst delete mode 100644 Misc/NEWS.d/next/Tools-Demos/2024-02-05-19-00-32.gh-issue-109991.yJSEkw.rst delete mode 100644 Misc/NEWS.d/next/Tools-Demos/2024-02-14-15-58-13.gh-issue-113516.TyIHWx.rst delete mode 100644 Misc/NEWS.d/next/Windows/2023-08-11-18-21-38.gh-issue-89240.dtSOLG.rst delete mode 100644 Misc/NEWS.d/next/Windows/2023-12-19-22-32-28.gh-issue-112984.F7kFMl.rst delete mode 100644 Misc/NEWS.d/next/Windows/2024-01-23-00-05-05.gh-issue-100107.lkbP_Q.rst delete mode 100644 Misc/NEWS.d/next/Windows/2024-02-01-14-35-05.gh-issue-111239.SO7SUF.rst delete mode 100644 Misc/NEWS.d/next/Windows/2024-02-05-16-53-12.gh-issue-109991.YqjnDz.rst delete mode 100644 Misc/NEWS.d/next/Windows/2024-02-06-09-05-13.gh-issue-115009.ShMjZs.rst delete mode 100644 Misc/NEWS.d/next/Windows/2024-02-08-21-37-22.gh-issue-115049.X1ObpJ.rst delete mode 100644 Misc/NEWS.d/next/macOS/2022-11-18-10-05-35.gh-issue-87804.rhlDmD.rst delete mode 100644 Misc/NEWS.d/next/macOS/2024-01-23-11-35-26.gh-issue-114490.FrQOQ0.rst delete mode 100644 Misc/NEWS.d/next/macOS/2024-02-05-18-30-27.gh-issue-109991.tun6Yu.rst delete mode 100644 Misc/NEWS.d/next/macOS/2024-02-06-09-01-10.gh-issue-115009.ysau7e.rst diff --git a/Include/patchlevel.h b/Include/patchlevel.h index 5f9b720f8b16715..2da8da9e7f3fe7e 100644 --- a/Include/patchlevel.h +++ b/Include/patchlevel.h @@ -20,10 +20,10 @@ #define PY_MINOR_VERSION 13 #define PY_MICRO_VERSION 0 #define PY_RELEASE_LEVEL PY_RELEASE_LEVEL_ALPHA -#define PY_RELEASE_SERIAL 3 +#define PY_RELEASE_SERIAL 4 /* Version as a string */ -#define PY_VERSION "3.13.0a3+" +#define PY_VERSION "3.13.0a4" /*--end constants--*/ /* Version as a single 4-byte hex number, e.g. 0x010502B2 == 1.5.2b2. diff --git a/Lib/pydoc_data/topics.py b/Lib/pydoc_data/topics.py index d453c1f038ef075..a49c38a51d39f20 100644 --- a/Lib/pydoc_data/topics.py +++ b/Lib/pydoc_data/topics.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Autogenerated by Sphinx on Wed Jan 17 13:09:41 2024 +# Autogenerated by Sphinx on Thu Feb 15 14:30:52 2024 # as part of the release process. topics = {'assert': 'The "assert" statement\n' '**********************\n' @@ -722,9 +722,9 @@ '\n' 'object.__dir__(self)\n' '\n' - ' Called when "dir()" is called on the object. A ' - 'sequence must be\n' - ' returned. "dir()" converts the returned sequence to a ' + ' Called when "dir()" is called on the object. An ' + 'iterable must be\n' + ' returned. "dir()" converts the returned iterable to a ' 'list and\n' ' sorts it.\n' '\n' @@ -751,8 +751,8 @@ 'returned.\n' '\n' 'The "__dir__" function should accept no arguments, and ' - 'return a\n' - 'sequence of strings that represents the names accessible ' + 'return an\n' + 'iterable of strings that represents the names accessible ' 'on module. If\n' 'present, this function overrides the standard "dir()" ' 'search on a\n' @@ -4921,7 +4921,7 @@ 'and continue running without the debugger using the "continue"\n' 'command.\n' '\n' - 'New in version 3.7: The built-in "breakpoint()", when called ' + 'Changed in version 3.7: The built-in "breakpoint()", when called ' 'with\n' 'defaults, can be used instead of "import pdb; pdb.set_trace()".\n' '\n' @@ -4965,11 +4965,11 @@ 'the\n' 'debugger upon program’s exit.\n' '\n' - 'New in version 3.2: "-c" option is introduced to execute ' + 'Changed in version 3.2: Added the "-c" option to execute ' 'commands as\n' - 'if given in a ".pdbrc" file, see Debugger Commands.\n' + 'if given in a ".pdbrc" file; see Debugger Commands.\n' '\n' - 'New in version 3.7: "-m" option is introduced to execute ' + 'Changed in version 3.7: Added the "-m" option to execute ' 'modules\n' 'similar to the way "python -m" does. As with a script, the ' 'debugger\n' @@ -5115,11 +5115,11 @@ '\n' ' Raises an auditing event "pdb.Pdb" with no arguments.\n' '\n' - ' New in version 3.1: The *skip* argument.\n' + ' Changed in version 3.1: Added the *skip* parameter.\n' '\n' - ' New in version 3.2: The *nosigint* argument. Previously, a ' - 'SIGINT\n' - ' handler was never set by Pdb.\n' + ' Changed in version 3.2: Added the *nosigint* parameter. ' + 'Previously,\n' + ' a SIGINT handler was never set by Pdb.\n' '\n' ' Changed in version 3.6: The *readrc* argument.\n' '\n' @@ -5466,7 +5466,7 @@ 'differs\n' ' from the current line.\n' '\n' - ' New in version 3.2: The ">>" marker.\n' + ' Changed in version 3.2: Added the ">>" marker.\n' '\n' 'll | longlist\n' '\n' @@ -5599,9 +5599,9 @@ '\n' ' New in version 3.2.\n' '\n' - ' New in version 3.13: "exit()" and "quit()" can be used to ' + ' Changed in version 3.13: "exit()" and "quit()" can be used to ' 'exit\n' - ' "interact" command.\n' + ' the "interact" command.\n' '\n' ' Changed in version 3.13: "interact" directs its output to ' 'the\n' @@ -6470,15 +6470,15 @@ 'originally\n' 'proposed by **PEP 448**.\n' '\n' - 'The trailing comma is required only to create a single tuple ' - '(a.k.a. a\n' - '*singleton*); it is optional in all other cases. A single ' - 'expression\n' - 'without a trailing comma doesn’t create a tuple, but rather ' - 'yields the\n' - 'value of that expression. (To create an empty tuple, use an ' - 'empty pair\n' - 'of parentheses: "()".)\n', + 'A trailing comma is required only to create a one-item tuple, ' + 'such as\n' + '"1,"; it is optional in all other cases. A single expression ' + 'without a\n' + 'trailing comma doesn’t create a tuple, but rather yields the ' + 'value of\n' + 'that expression. (To create an empty tuple, use an empty pair ' + 'of\n' + 'parentheses: "()".)\n', 'floating': 'Floating point literals\n' '***********************\n' '\n' @@ -10384,9 +10384,9 @@ '\n' 'object.__dir__(self)\n' '\n' - ' Called when "dir()" is called on the object. A sequence ' + ' Called when "dir()" is called on the object. An iterable ' 'must be\n' - ' returned. "dir()" converts the returned sequence to a ' + ' returned. "dir()" converts the returned iterable to a ' 'list and\n' ' sorts it.\n' '\n' @@ -10413,8 +10413,8 @@ 'returned.\n' '\n' 'The "__dir__" function should accept no arguments, and ' - 'return a\n' - 'sequence of strings that represents the names accessible on ' + 'return an\n' + 'iterable of strings that represents the names accessible on ' 'module. If\n' 'present, this function overrides the standard "dir()" search ' 'on a\n' @@ -14543,7 +14543,9 @@ 'name |\n' '+----------------------------------------------------+----------------------------------------------------+\n' '| codeobject.co_qualname | The fully ' - 'qualified function name |\n' + 'qualified function name New in version |\n' + '| | ' + '3.11. |\n' '+----------------------------------------------------+----------------------------------------------------+\n' '| codeobject.co_argcount | The total ' 'number of positional *parameters* |\n' @@ -15008,8 +15010,8 @@ 'around another object that alters the way in which that object is\n' 'retrieved from classes and class instances. The behaviour of class\n' 'method objects upon such retrieval is described above, under ' - '“User-\n' - 'defined methods”. Class method objects are created by the built-in\n' + '“instance\n' + 'methods”. Class method objects are created by the built-in\n' '"classmethod()" constructor.\n', 'typesfunctions': 'Functions\n' '*********\n' diff --git a/Misc/NEWS.d/3.13.0a4.rst b/Misc/NEWS.d/3.13.0a4.rst new file mode 100644 index 000000000000000..39af0534cf8fb57 --- /dev/null +++ b/Misc/NEWS.d/3.13.0a4.rst @@ -0,0 +1,1433 @@ +.. date: 2024-02-13-15-14-39 +.. gh-issue: 115399 +.. nonce: xT-scP +.. release date: 2024-02-15 +.. section: Security + +Update bundled libexpat to 2.6.0 + +.. + +.. date: 2024-02-12-00-33-01 +.. gh-issue: 115243 +.. nonce: e1oGX8 +.. section: Security + +Fix possible crashes in :meth:`collections.deque.index` when the deque is +concurrently modified. + +.. + +.. date: 2024-02-14-23-50-55 +.. gh-issue: 112087 +.. nonce: H_4W_v +.. section: Core and Builtins + +For an empty reverse iterator for list will be reduced to :func:`reversed`. +Patch by Donghee Na + +.. + +.. date: 2024-02-12-17-18-26 +.. gh-issue: 114570 +.. nonce: BzwMlJ +.. section: Core and Builtins + +Add :exc:`PythonFinalizationError` exception. This exception derived from +:exc:`RuntimeError` is raised when an operation is blocked during the +:term:`Python finalization <interpreter shutdown>`. Patch by Victor Stinner. + +.. + +.. date: 2024-02-07-18-04-36 +.. gh-issue: 114695 +.. nonce: o9wP5P +.. section: Core and Builtins + +Add :func:`sys._clear_internal_caches`, which clears all internal +performance-related caches (and deprecate the less-general +:func:`sys._clear_type_cache` function). + +.. + +.. date: 2024-02-07-07-50-12 +.. gh-issue: 114828 +.. nonce: nSXwMi +.. section: Core and Builtins + +Fix compilation crashes in uncommon code examples using :func:`super` inside +a comprehension in a class body. + +.. + +.. date: 2024-02-07-00-18-42 +.. gh-issue: 112069 +.. nonce: jRDRR5 +.. section: Core and Builtins + +Adapt :class:`set` and :class:`frozenset` methods to Argument Clinic. + +.. + +.. date: 2024-02-05-12-40-26 +.. gh-issue: 115011 +.. nonce: L1AKF5 +.. section: Core and Builtins + +Setters for members with an unsigned integer type now support the same range +of valid values for objects that has a :meth:`~object.__index__` method as +for :class:`int`. + +.. + +.. date: 2024-02-03-04-07-18 +.. gh-issue: 114887 +.. nonce: uLSFmN +.. section: Core and Builtins + +Changed socket type validation in +:meth:`~asyncio.loop.create_datagram_endpoint` to accept all non-stream +sockets. This fixes a regression in compatibility with raw sockets. + +.. + +.. date: 2024-02-03-01-48-38 +.. gh-issue: 114944 +.. nonce: 4J5ELD +.. section: Core and Builtins + +Fixes a race between ``PyParkingLot_Park`` and ``_PyParkingLot_UnparkAll``. + +.. + +.. date: 2024-02-02-05-27-48 +.. gh-issue: 113462 +.. nonce: VMml8q +.. section: Core and Builtins + +Limit the number of versions that a single class can use. Prevents a few +wayward classes using up all the version numbers. + +.. + +.. date: 2024-02-01-23-43-49 +.. gh-issue: 76763 +.. nonce: o_2J6i +.. section: Core and Builtins + +The :func:`chr` builtin function now always raises :exc:`ValueError` for +values outside the valid range. Previously it raised :exc:`OverflowError` +for very large or small values. + +.. + +.. date: 2024-02-01-18-16-52 +.. gh-issue: 114806 +.. nonce: wrH2J6 +.. section: Core and Builtins + +No longer specialize calls to classes, if those classes have metaclasses. +Fixes bug where the ``__call__`` method of the metaclass was not being +called. + +.. + +.. date: 2024-01-31-09-10-10 +.. gh-issue: 107944 +.. nonce: XWm1B- +.. section: Core and Builtins + +Improve error message for function calls with bad keyword arguments via +getargs + +.. + +.. date: 2024-01-25-18-50-49 +.. gh-issue: 112529 +.. nonce: IbbApA +.. section: Core and Builtins + +The free-threaded build no longer allocates space for the ``PyGC_Head`` +structure in objects that support cyclic garbage collection. A number of +other fields and data structures are used as replacements, including +``ob_gc_bits``, ``ob_tid``, and mimalloc internal data structures. + +.. + +.. date: 2024-01-22-15-10-01 +.. gh-issue: 114456 +.. nonce: fBFEJF +.. section: Core and Builtins + +Lower the recursion limit under a debug build of WASI. + +.. + +.. date: 2024-01-22-09-49-02 +.. gh-issue: 114083 +.. nonce: hf1-ku +.. section: Core and Builtins + +Compiler applies folding of LOAD_CONST with following instruction in a +separate pass before other optimisations. This enables jump threading in +certain circumstances. + +.. + +.. date: 2024-01-21-17-29-32 +.. gh-issue: 114388 +.. nonce: UVGO4K +.. section: Core and Builtins + +Fix a :exc:`RuntimeWarning` emitted when assign an integer-like value that +is not an instance of :class:`int` to an attribute that corresponds to a C +struct member of :ref:`type <PyMemberDef-types>` T_UINT and T_ULONG. Fix a +double :exc:`RuntimeWarning` emitted when assign a negative integer value to +an attribute that corresponds to a C struct member of type T_UINT. + +.. + +.. date: 2024-01-19-13-18-13 +.. gh-issue: 114265 +.. nonce: 7HAi-- +.. section: Core and Builtins + +Compiler propagates line numbers before optimization, leading to more +optimization opportunities and removing the need for the +``guarantee_lineno_for_exits`` hack. + +.. + +.. date: 2024-01-18-20-20-37 +.. gh-issue: 112529 +.. nonce: oVNvDG +.. section: Core and Builtins + +The free-threaded build now has its own thread-safe GC implementation that +uses mimalloc to find GC tracked objects. It is non-generational, unlike the +existing GC implementation. + +.. + +.. date: 2024-01-17-23-39-20 +.. gh-issue: 114050 +.. nonce: Lnv1oq +.. section: Core and Builtins + +Fix segmentation fault caused by an incorrect format string in ``TypeError`` +exception when more than two arguments are passed to ``int``. + +.. + +.. date: 2024-01-17-05-09-32 +.. gh-issue: 112354 +.. nonce: Run9ko +.. section: Core and Builtins + +The ``END_FOR`` instruction now pops only one value. This is to better +support side exits in loops. + +.. + +.. date: 2024-01-17-00-52-57 +.. gh-issue: 113884 +.. nonce: CvEjUE +.. section: Core and Builtins + +Make :class:`queue.SimpleQueue` thread safe when the GIL is disabled. + +.. + +.. date: 2024-01-16-14-41-54 +.. gh-issue: 114058 +.. nonce: Cb2b8h +.. section: Core and Builtins + +Implement the foundations of the Tier 2 redundancy eliminator. + +.. + +.. date: 2024-01-12-16-40-07 +.. gh-issue: 113939 +.. nonce: Yi3L-e +.. section: Core and Builtins + +frame.clear(): Clear frame.f_locals as well, and not only the fast locals. +This is relevant once frame.f_locals was accessed, which would contain also +references to all the locals. + +.. + +.. date: 2024-01-11-22-58-45 +.. gh-issue: 112050 +.. nonce: hDuvDW +.. section: Core and Builtins + +Convert :class:`collections.deque` to use Argument Clinic. + +.. + +.. date: 2024-01-08-21-57-41 +.. gh-issue: 112050 +.. nonce: qwgjx1 +.. section: Core and Builtins + +Make methods on :class:`collections.deque` thread-safe when the GIL is +disabled. + +.. + +.. date: 2023-12-24-03-25-28 +.. gh-issue: 113464 +.. nonce: dvjQmA +.. section: Core and Builtins + +Add an option (``--enable-experimental-jit`` for ``configure``-based builds +or ``--experimental-jit`` for ``PCbuild``-based ones) to build an +*experimental* just-in-time compiler, based on `copy-and-patch +<https://fredrikbk.com/publications/copy-and-patch.pdf>`_ + +.. + +.. date: 2023-12-22-13-21-39 +.. gh-issue: 113055 +.. nonce: 47xBMF +.. section: Core and Builtins + +Make interp->obmalloc a pointer. For interpreters that share state with the +main interpreter, this points to the same static memory structure. For +interpreters with their own obmalloc state, it is heap allocated. Add +free_obmalloc_arenas() which will free the obmalloc arenas and radix tree +structures for interpreters with their own obmalloc state. + +.. + +.. date: 2023-06-06-19-09-00 +.. gh-issue: 55664 +.. nonce: vYYl0V +.. section: Core and Builtins + +Add warning when creating :class:`type` using a namespace dictionary with +non-string keys. Patched by Daniel Urban and Furkan Onder. + +.. + +.. date: 2023-05-16-06-52-34 +.. gh-issue: 104530 +.. nonce: mJnA0W +.. section: Core and Builtins + +Use native Win32 condition variables. + +.. + +.. date: 2024-02-13-18-27-03 +.. gh-issue: 115392 +.. nonce: gle5tp +.. section: Library + +Fix a bug in :mod:`doctest` where incorrect line numbers would be reported +for decorated functions. + +.. + +.. date: 2024-02-11-20-23-36 +.. gh-issue: 114563 +.. nonce: RzxNYT +.. section: Library + +Fix several :func:`format()` bugs when using the C implementation of +:class:`~decimal.Decimal`: * memory leak in some rare cases when using the +``z`` format option (coerce negative 0) * incorrect output when applying the +``z`` format option to type ``F`` (fixed-point with capital ``NAN`` / +``INF``) * incorrect output when applying the ``#`` format option (alternate +form) + +.. + +.. date: 2024-02-10-15-24-20 +.. gh-issue: 102840 +.. nonce: 4mnDq1 +.. section: Library + +Fix confused traceback when floordiv, mod, or divmod operations happens +between instances of :class:`fractions.Fraction` and :class:`complex`. + +.. + +.. date: 2024-02-09-07-20-16 +.. gh-issue: 115165 +.. nonce: yfJLXA +.. section: Library + +Most exceptions are now ignored when attempting to set the +``__orig_class__`` attribute on objects returned when calling :mod:`typing` +generic aliases (including generic aliases created using +:data:`typing.Annotated`). Previously only :exc:`AttributeError` was +ignored. Patch by Dave Shawley. + +.. + +.. date: 2024-02-08-17-04-58 +.. gh-issue: 112903 +.. nonce: SN_vUs +.. section: Library + +Fix "issubclass() arg 1 must be a class" errors in certain cases of multiple +inheritance with generic aliases (regression in early 3.13 alpha releases). + +.. + +.. date: 2024-02-08-14-21-28 +.. gh-issue: 115133 +.. nonce: ycl4ko +.. section: Library + +Fix tests for :class:`~xml.etree.ElementTree.XMLPullParser` with Expat +2.6.0. + +.. + +.. date: 2024-02-08-13-26-14 +.. gh-issue: 115059 +.. nonce: DqP9dr +.. section: Library + +:meth:`io.BufferedRandom.read1` now flushes the underlying write buffer. + +.. + +.. date: 2024-02-07-12-37-52 +.. gh-issue: 79382 +.. nonce: Yz_5WB +.. section: Library + +Trailing ``**`` no longer allows to match files and non-existing paths in +recursive :func:`~glob.glob`. + +.. + +.. date: 2024-02-06-15-16-28 +.. gh-issue: 67837 +.. nonce: _JKa73 +.. section: Library + +Avoid race conditions in the creation of directories during concurrent +extraction in :mod:`tarfile` and :mod:`zipfile`. + +.. + +.. date: 2024-02-06-03-55-46 +.. gh-issue: 115060 +.. nonce: EkWRpP +.. section: Library + +Speed up :meth:`pathlib.Path.glob` by removing redundant regex matching. + +.. + +.. date: 2024-02-05-16-48-06 +.. gh-issue: 97928 +.. nonce: JZCies +.. section: Library + +Partially revert the behavior of :meth:`tkinter.Text.count`. By default it +preserves the behavior of older Python versions, except that setting +``wantobjects`` to 0 no longer has effect. Add a new parameter +*return_ints*: specifying ``return_ints=True`` makes ``Text.count()`` always +returning the single count as an integer instead of a 1-tuple or ``None``. + +.. + +.. date: 2024-02-04-13-17-33 +.. gh-issue: 114628 +.. nonce: WJpqqS +.. section: Library + +When csv.Error is raised when handling TypeError, do not print the TypeError +traceback. + +.. + +.. date: 2024-02-04-02-28-37 +.. gh-issue: 85984 +.. nonce: NHZVTQ +.. section: Library + +Added ``_POSIX_VDISABLE`` from C's ``<unistd.h>`` to :mod:`termios`. + +.. + +.. date: 2024-02-03-17-54-17 +.. gh-issue: 114965 +.. nonce: gHksCK +.. section: Library + +Update bundled pip to 24.0 + +.. + +.. date: 2024-02-03-16-59-25 +.. gh-issue: 114959 +.. nonce: dCfAG2 +.. section: Library + +:mod:`tarfile` no longer ignores errors when trying to extract a directory +on top of a file. + +.. + +.. date: 2024-02-02-15-50-13 +.. gh-issue: 114894 +.. nonce: DF-dSd +.. section: Library + +Add :meth:`array.array.clear`. + +.. + +.. date: 2024-02-01-10-19-11 +.. gh-issue: 114071 +.. nonce: vkm2G_ +.. section: Library + +Support tuple subclasses using auto() for enum member value. + +.. + +.. date: 2024-01-31-20-07-11 +.. gh-issue: 109475 +.. nonce: lmTb9S +.. section: Library + +Fix support of explicit option value "--" in :mod:`argparse` (e.g. +``--option=--``). + +.. + +.. date: 2024-01-30-22-10-50 +.. gh-issue: 49766 +.. nonce: yulJL_ +.. section: Library + +Fix :class:`~datetime.date`-:class:`~datetime.datetime` comparison. Now the +special comparison methods like ``__eq__`` and ``__lt__`` return +:data:`NotImplemented` if one of comparands is :class:`!date` and other is +:class:`!datetime` instead of ignoring the time part and the time zone or +forcefully return "not equal" or raise :exc:`TypeError`. It makes comparison +of :class:`!date` and :class:`!datetime` subclasses more symmetric and +allows to change the default behavior by overriding the special comparison +methods in subclasses. + +.. + +.. date: 2024-01-30-15-34-08 +.. gh-issue: 110190 +.. nonce: Z5PQQX +.. section: Library + +Fix ctypes structs with array on Windows ARM64 platform by setting +``MAX_STRUCT_SIZE`` to 32 in stgdict. Patch by Diego Russo + +.. + +.. date: 2024-01-28-19-40-40 +.. gh-issue: 114678 +.. nonce: kYKcJw +.. section: Library + +Ensure that deprecation warning for 'N' specifier in +:class:`~decimal.Decimal` format is not raised for cases where 'N' appears +in other places in the format specifier. Based on patch by Stefan Krah. + +.. + +.. date: 2024-01-28-18-38-18 +.. gh-issue: 70303 +.. nonce: _Lt_pj +.. section: Library + +Return both files and directories from :meth:`pathlib.Path.glob` if a +pattern ends with "``**``". Previously only directories were returned. + +.. + +.. date: 2024-01-28-00-48-12 +.. gh-issue: 109653 +.. nonce: vF4exe +.. section: Library + +Improve import time of :mod:`importlib.metadata` and :mod:`email.utils`. + +.. + +.. date: 2024-01-27-20-11-24 +.. gh-issue: 113280 +.. nonce: CZPQMf +.. section: Library + +Fix a leak of open socket in rare cases when error occurred in +:class:`ssl.SSLSocket` creation. + +.. + +.. date: 2024-01-26-16-46-21 +.. gh-issue: 77749 +.. nonce: NY_7TS +.. section: Library + +:meth:`email.policy.EmailPolicy.fold` now always encodes non-ASCII +characters in headers if :attr:`~email.policy.EmailPolicy.utf8` is false. + +.. + +.. date: 2024-01-25-19-22-17 +.. gh-issue: 83383 +.. nonce: 3GwO9v +.. section: Library + +Synchronization of the :mod:`dbm.dumb` database is now no-op if there was no +modification since opening or last synchronization. The directory file for a +newly created empty :mod:`dbm.dumb` database is now created immediately +after opening instead of deferring this until synchronizing or closing. + +.. + +.. date: 2024-01-24-20-51-49 +.. gh-issue: 91602 +.. nonce: 8fOH8l +.. section: Library + +Add *filter* keyword-only parameter to :meth:`sqlite3.Connection.iterdump` +for filtering database objects to dump. Patch by Mariusz Felisiak. + +.. + +.. date: 2024-01-24-20-11-46 +.. gh-issue: 112451 +.. nonce: 7YrG4p +.. section: Library + +Prohibit subclassing pure-Python :class:`datetime.timezone`. This is +consistent with C-extension implementation. Patch by Mariusz Felisiak. + +.. + +.. date: 2024-01-24-17-25-18 +.. gh-issue: 69893 +.. nonce: PQq5fR +.. section: Library + +Add the :meth:`!close` method for the iterator returned by +:func:`xml.etree.ElementTree.iterparse`. + +.. + +.. date: 2024-01-23-23-13-47 +.. gh-issue: 109653 +.. nonce: KLBHmT +.. section: Library + +Reduce the import time of :mod:`threading` module by ~50%. Patch by Daniel +Hollas. + +.. + +.. date: 2024-01-23-21-20-40 +.. gh-issue: 114492 +.. nonce: vKxl5o +.. section: Library + +Make the result of :func:`termios.tcgetattr` reproducible on Alpine Linux. +Previously it could leave a random garbage in some fields. + +.. + +.. date: 2024-01-23-14-11-49 +.. gh-issue: 114315 +.. nonce: KeVdzl +.. section: Library + +Make :class:`threading.Lock` a real class, not a factory function. Add +``__new__`` to ``_thread.lock`` type. + +.. + +.. date: 2024-01-23-13-03-22 +.. gh-issue: 100414 +.. nonce: 5kTdU5 +.. section: Library + +Add :mod:`dbm.sqlite3` as a backend to :mod:`dbm`, and make it the new +default :mod:`!dbm` backend. Patch by Raymond Hettinger and Erlend E. +Aasland. + +.. + +.. date: 2024-01-23-11-04-21 +.. gh-issue: 113267 +.. nonce: xe_Pxe +.. section: Library + +Revert changes in :gh:`106584` which made calls of ``TestResult`` methods +``startTest()`` and ``stopTest()`` unbalanced. + +.. + +.. date: 2024-01-22-12-10-34 +.. gh-issue: 75128 +.. nonce: 4FGlRS +.. section: Library + +Ignore an :exc:`OSError` in :meth:`asyncio.BaseEventLoop.create_server` when +IPv6 is available but the interface cannot actually support it. + +.. + +.. date: 2024-01-22-11-43-38 +.. gh-issue: 114423 +.. nonce: 6mMoPH +.. section: Library + +``_DummyThread`` entries in ``threading._active`` are now automatically +removed when the related thread dies. + +.. + +.. date: 2024-01-21-16-32-55 +.. gh-issue: 114257 +.. nonce: bCFld5 +.. section: Library + +Dismiss the :exc:`FileNotFound` error in :func:`ctypes.util.find_library` +and just return ``None`` on Linux. + +.. + +.. date: 2024-01-19-18-41-02 +.. gh-issue: 114321 +.. nonce: yj_Xw3 +.. section: Library + +Expose more platform specific constants in the :mod:`fcntl` module on Linux, +macOS, FreeBSD and NetBSD. + +.. + +.. date: 2024-01-19-15-48-06 +.. gh-issue: 114328 +.. nonce: hixxW3 +.. section: Library + +The :func:`tty.setcbreak` and new :func:`tty.cfmakecbreak` no longer clears +the terminal input ICRLF flag. This fixes a regression introduced in 3.12 +that no longer matched how OSes define cbreak mode in their ``stty(1)`` +manual pages. + +.. + +.. date: 2024-01-19-12-05-22 +.. gh-issue: 114281 +.. nonce: H5JQe4 +.. section: Library + +Remove type hints from ``Lib/asyncio/staggered.py``. The annotations in the +`typeshed <https://github.com/python/typeshed>`__ project should be used +instead. + +.. + +.. date: 2024-01-18-22-29-28 +.. gh-issue: 101438 +.. nonce: 1-uUi_ +.. section: Library + +Avoid reference cycle in ElementTree.iterparse. The iterator returned by +``ElementTree.iterparse`` may hold on to a file descriptor. The reference +cycle prevented prompt clean-up of the file descriptor if the returned +iterator was not exhausted. + +.. + +.. date: 2024-01-18-10-07-52 +.. gh-issue: 114198 +.. nonce: lK4Iif +.. section: Library + +The signature for the ``__replace__`` method on :mod:`dataclasses` now has +the first argument named ``self``, rather than ``obj``. + +.. + +.. date: 2024-01-17-18-53-51 +.. gh-issue: 104522 +.. nonce: 3NyDf4 +.. section: Library + +:exc:`OSError` raised when run a subprocess now only has *filename* +attribute set to *cwd* if the error was caused by a failed attempt to change +the current directory. + +.. + +.. date: 2024-01-16-15-59-06 +.. gh-issue: 114149 +.. nonce: LJ8IPm +.. section: Library + +Enum: correctly handle tuple subclasses in custom ``__new__``. + +.. + +.. date: 2024-01-15-20-21-33 +.. gh-issue: 83648 +.. nonce: HzD_fY +.. section: Library + +Support deprecation of options, positional arguments and subcommands in +:mod:`argparse`. + +.. + +.. date: 2024-01-15-19-54-41 +.. gh-issue: 114087 +.. nonce: Xic5vY +.. section: Library + +Speed up ``dataclasses.asdict`` up to 1.35x. + +.. + +.. date: 2024-01-15-18-42-44 +.. gh-issue: 109534 +.. nonce: wYaLMZ +.. section: Library + +Fix a reference leak in +:class:`asyncio.selector_events.BaseSelectorEventLoop` when SSL handshakes +fail. Patch contributed by Jamie Phan. + +.. + +.. date: 2024-01-12-17-32-36 +.. gh-issue: 79634 +.. nonce: uTSTRI +.. section: Library + +Accept :term:`path-like objects <path-like object>` as patterns in +:meth:`pathlib.Path.glob` and :meth:`~pathlib.Path.rglob`. + +.. + +.. date: 2024-01-12-09-35-07 +.. gh-issue: 112202 +.. nonce: t_0V1m +.. section: Library + +Ensure that a :func:`asyncio.Condition.notify` call does not get lost if the +awakened ``Task`` is simultaneously cancelled or encounters any other error. + +.. + +.. date: 2024-01-11-20-47-49 +.. gh-issue: 113951 +.. nonce: AzlqFK +.. section: Library + +Fix the behavior of ``tag_unbind()`` methods of :class:`tkinter.Text` and +:class:`tkinter.Canvas` classes with three arguments. Previously, +``widget.tag_unbind(tag, sequence, funcid)`` destroyed the current binding +for *sequence*, leaving *sequence* unbound, and deleted the *funcid* +command. Now it removes only *funcid* from the binding for *sequence*, +keeping other commands, and deletes the *funcid* command. It leaves +*sequence* unbound only if *funcid* was the last bound command. + +.. + +.. date: 2024-01-11-15-10-53 +.. gh-issue: 97959 +.. nonce: UOj6d4 +.. section: Library + +Fix rendering class methods, bound methods, method and function aliases in +:mod:`pydoc`. Class methods no longer have "method of builtins.type +instance" note. Corresponding notes are now added for class and unbound +methods. Method and function aliases now have references to the module or +the class where the origin was defined if it differs from the current. Bound +methods are now listed in the static methods section. Methods of builtin +classes are now supported as well as methods of Python classes. + +.. + +.. date: 2024-01-07-21-04-24 +.. gh-issue: 113796 +.. nonce: 6iNsCR +.. section: Library + +Add more validation checks in the :class:`csv.Dialect` constructor. +:exc:`ValueError` is now raised if the same character is used in different +roles. + +.. + +.. date: 2024-01-05-16-27-34 +.. gh-issue: 113732 +.. nonce: fgDRXA +.. section: Library + +Fix support of :data:`~csv.QUOTE_NOTNULL` and :data:`~csv.QUOTE_STRINGS` in +:func:`csv.reader`. + +.. + +.. date: 2024-01-04-20-58-17 +.. gh-issue: 113225 +.. nonce: -nyJM4 +.. section: Library + +Speed up :meth:`pathlib.Path.walk` by using :attr:`os.DirEntry.path` where +possible. + +.. + +.. date: 2023-12-18-20-10-50 +.. gh-issue: 89039 +.. nonce: gqFdtU +.. section: Library + +When replace() method is called on a subclass of datetime, date or time, +properly call derived constructor. Previously, only the base class's +constructor was called. + +Also, make sure to pass non-zero fold values when creating subclasses in +various methods. Previously, fold was silently ignored. + +.. + +.. date: 2023-12-09-23-31-17 +.. gh-issue: 112919 +.. nonce: S5k9QN +.. section: Library + +Speed-up :func:`datetime.datetime.replace`, :func:`datetime.date.replace` +and :func:`datetime.time.replace`. + +.. + +.. date: 2023-11-27-19-54-43 +.. gh-issue: 59013 +.. nonce: chpQ0e +.. section: Library + +Set breakpoint on the first executable line of the function, instead of the +line of function definition when the user do ``break func`` using :mod:`pdb` + +.. + +.. date: 2023-11-24-19-08-50 +.. gh-issue: 112343 +.. nonce: RarGFC +.. section: Library + +Improve handling of pdb convenience variables to avoid replacing string +contents. + +.. + +.. date: 2023-11-18-16-30-21 +.. gh-issue: 112240 +.. nonce: YXS0tj +.. section: Library + +Add option to calendar module CLI to specify the weekday to start each week. +Patch by Steven Ward. + +.. + +.. date: 2023-11-04-22-32-27 +.. gh-issue: 111741 +.. nonce: f1ufr8 +.. section: Library + +Recognise ``image/webp`` as a standard format in the :mod:`mimetypes` +module. + +.. + +.. date: 2023-10-27-19-24-58 +.. gh-issue: 43457 +.. nonce: 84lx9H +.. section: Library + +Fix the :mod:`tkinter` widget method :meth:`!wm_attributes`. It now accepts +the attribute name without the minus prefix to get window attributes and +allows to specify attributes and values to set as keyword arguments. Add new +optional keyword argument *return_python_dict*: calling +``w.wm_attributes(return_python_dict=True)`` returns the attributes as a +dict instead of a tuple. Calling ``w.wm_attributes()`` now returns a tuple +instead of string if *wantobjects* was set to 0. + +.. + +.. date: 2023-10-24-19-19-54 +.. gh-issue: 82626 +.. nonce: _hfLRf +.. section: Library + +Many functions now emit a warning if a boolean value is passed as a file +descriptor argument. + +.. + +.. date: 2023-10-19-02-08-12 +.. gh-issue: 111051 +.. nonce: 8h1Dpk +.. section: Library + +Added check for file modification during debugging with :mod:`pdb` + +.. + +.. date: 2023-10-04-11-09-30 +.. gh-issue: 110345 +.. nonce: fZU1ud +.. section: Library + +Show the Tcl/Tk patchlevel (rather than version) in :meth:`tkinter._test`. + +.. + +.. date: 2023-09-22-22-17-45 +.. gh-issue: 38807 +.. nonce: m9McRN +.. section: Library + +Fix race condition in :mod:`trace`. Instead of checking if a directory +exists and creating it, directly call :func:`os.makedirs` with the kwarg +``exist_ok=True``. + +.. + +.. date: 2023-07-23-12-28-26 +.. gh-issue: 75705 +.. nonce: aB2-Ww +.. section: Library + +Set unixfrom envelope in :class:`mailbox.mbox` and :class:`mailbox.MMDF`. + +.. + +.. date: 2023-06-29-14-26-56 +.. gh-issue: 106233 +.. nonce: Aqw2HI +.. section: Library + +Fix stacklevel in ``InvalidTZPathWarning`` during :mod:`zoneinfo` module +import. + +.. + +.. date: 2023-05-30-18-30-11 +.. gh-issue: 105102 +.. nonce: SnpK04 +.. section: Library + +Allow :class:`ctypes.Union` to be nested in :class:`ctypes.Structure` when +the system endianness is the opposite of the classes. + +.. + +.. date: 2023-05-08-09-30-00 +.. gh-issue: 104282 +.. nonce: h4c6Eb +.. section: Library + +Fix null pointer dereference in :func:`lzma._decode_filter_properties` due +to improper handling of BCJ filters with properties of zero length. Patch by +Radislav Chugunov. + +.. + +.. date: 2023-05-06-04-57-10 +.. gh-issue: 96471 +.. nonce: C9wAU7 +.. section: Library + +Add :py:class:`queue.Queue` termination with +:py:meth:`~queue.Queue.shutdown`. + +.. + +.. date: 2023-04-08-11-41-07 +.. gh-issue: 101599 +.. nonce: PaWNFh +.. section: Library + +Changed argparse flag options formatting to remove redundancy. + +.. + +.. date: 2023-03-15-03-21-18 +.. gh-issue: 85984 +.. nonce: Xaq6ZN +.. section: Library + +Add POSIX pseudo-terminal functions :func:`os.posix_openpt`, +:func:`os.grantpt`, :func:`os.unlockpt`, and :func:`os.ptsname`. + +.. + +.. date: 2023-03-08-00-02-30 +.. gh-issue: 102512 +.. nonce: LiugDr +.. section: Library + +When :func:`os.fork` is called from a foreign thread (aka ``_DummyThread``), +the type of the thread in a child process is changed to ``_MainThread``. +Also changed its name and daemonic status, it can be now joined. + +.. + +.. date: 2022-07-31-01-24-40 +.. gh-issue: 88569 +.. nonce: eU0--b +.. section: Library + +Add :func:`os.path.isreserved`, which identifies reserved pathnames such as +"NUL", "AUX" and "CON". This function is only available on Windows. + +Deprecate :meth:`pathlib.PurePath.is_reserved`. + +.. + +.. bpo: 38364 +.. date: 2019-10-05-22-56-50 +.. nonce: sYTCWF +.. section: Library + +The ``inspect`` functions ``isgeneratorfunction``, ``iscoroutinefunction``, +``isasyncgenfunction`` now support ``functools.partialmethod`` wrapped +functions the same way they support ``functools.partial``. + +.. + +.. date: 2024-02-12-12-26-17 +.. gh-issue: 115233 +.. nonce: aug6r9 +.. section: Documentation + +Fix an example for :class:`~logging.LoggerAdapter` in the Logging Cookbook. + +.. + +.. date: 2024-01-17-11-40-03 +.. gh-issue: 114123 +.. nonce: LuueXf +.. section: Documentation + +Move the :mod:`csv` module docstring to the :mod:`!csv` module instead of +reexporting it from the internal :mod:`!_csv` module, and remove ``__doc__`` +from ``csv.__all__``. + +Move :attr:`!csv.__version__` to the :mod:`!csv` module instead of +reexporting it from the internal :mod:`!_csv` module, and remove +``__version__`` from ``csv.__all__``. + +.. + +.. date: 2024-02-02-13-18-55 +.. gh-issue: 114099 +.. nonce: C_ycWg +.. section: Tests + +Added test exclusions required to run the test suite on iOS. + +.. + +.. date: 2023-06-02-05-04-15 +.. gh-issue: 105089 +.. nonce: KaZFtU +.. section: Tests + +Fix +``test.test_zipfile.test_core.TestWithDirectory.test_create_directory_with_write`` +test in AIX by doing a bitwise AND of 0xFFFF on mode , so that it will be in +sync with ``zinfo.external_attr`` + +.. + +.. date: 2024-02-08-19-36-20 +.. gh-issue: 115167 +.. nonce: LB9nDK +.. section: Build + +Avoid vendoring ``vcruntime140_threads.dll`` when building with Visual +Studio 2022 version 17.8. + +.. + +.. date: 2024-02-08-17-38-56 +.. gh-issue: 113632 +.. nonce: y9KIGb +.. section: Build + +Promote WASI to a tier 2 platform and drop Emscripten from tier 3 in +configure.ac. + +.. + +.. date: 2024-02-07-08-23-48 +.. gh-issue: 114099 +.. nonce: XcEXEZ +.. section: Build + +configure and Makefile were refactored to accomodate framework builds on +Apple platforms other than macOS. + +.. + +.. date: 2024-02-01-20-08-11 +.. gh-issue: 114875 +.. nonce: x_2iZ9 +.. section: Build + +Add :c:func:`!getgrent` as a prerequisite for building the :mod:`grp` +module. + +.. + +.. date: 2024-02-08-21-37-22 +.. gh-issue: 115049 +.. nonce: X1ObpJ +.. section: Windows + +Fixes ``py.exe`` launcher failing when run as users without user profiles. + +.. + +.. date: 2024-02-06-09-05-13 +.. gh-issue: 115009 +.. nonce: ShMjZs +.. section: Windows + +Update Windows installer to use SQLite 3.45.1. + +.. + +.. date: 2024-02-05-16-53-12 +.. gh-issue: 109991 +.. nonce: YqjnDz +.. section: Windows + +Update Windows build to use OpenSSL 3.0.13. + +.. + +.. date: 2024-02-01-14-35-05 +.. gh-issue: 111239 +.. nonce: SO7SUF +.. section: Windows + +Update Windows builds to use zlib v1.3.1. + +.. + +.. date: 2024-01-23-00-05-05 +.. gh-issue: 100107 +.. nonce: lkbP_Q +.. section: Windows + +The ``py.exe`` launcher will no longer attempt to run the Microsoft Store +redirector when launching a script containing a ``/usr/bin/env`` shebang + +.. + +.. date: 2023-12-19-22-32-28 +.. gh-issue: 112984 +.. nonce: F7kFMl +.. section: Windows + +Adds free-threaded binaries to Windows installer as an optional component. + +.. + +.. date: 2023-08-11-18-21-38 +.. gh-issue: 89240 +.. nonce: dtSOLG +.. section: Windows + +Allows :mod:`multiprocessing` to create pools of greater than 62 processes. + +.. + +.. date: 2024-02-06-09-01-10 +.. gh-issue: 115009 +.. nonce: ysau7e +.. section: macOS + +Update macOS installer to use SQLite 3.45.1. + +.. + +.. date: 2024-02-05-18-30-27 +.. gh-issue: 109991 +.. nonce: tun6Yu +.. section: macOS + +Update macOS installer to use OpenSSL 3.0.13. + +.. + +.. date: 2024-01-23-11-35-26 +.. gh-issue: 114490 +.. nonce: FrQOQ0 +.. section: macOS + +Add Mach-O linkage support for :func:`platform.architecture()`. + +.. + +.. date: 2022-11-18-10-05-35 +.. gh-issue: 87804 +.. nonce: rhlDmD +.. section: macOS + +On macOS the result of ``os.statvfs`` and ``os.fstatvfs`` now correctly +report the size of very large disks, in previous versions the reported +number of blocks was wrong for disks with at least 2**32 blocks. + +.. + +.. date: 2024-01-17-23-18-15 +.. gh-issue: 96905 +.. nonce: UYaxoU +.. section: IDLE + +In idlelib code, stop redefining built-ins 'dict' and 'object'. + +.. + +.. date: 2023-04-25-03-01-23 +.. gh-issue: 103820 +.. nonce: LCSpza +.. section: IDLE + +Revise IDLE bindings so that events from mouse button 4/5 on non-X11 +windowing systems (i.e. Win32 and Aqua) are not mistaken for scrolling. + +.. + +.. date: 2024-02-14-15-58-13 +.. gh-issue: 113516 +.. nonce: TyIHWx +.. section: Tools/Demos + +Don't set ``LDSHARED`` when building for WASI. + +.. + +.. date: 2024-02-05-19-00-32 +.. gh-issue: 109991 +.. nonce: yJSEkw +.. section: Tools/Demos + +Update GitHub CI workflows to use OpenSSL 3.0.13 and multissltests to use +1.1.1w, 3.0.13, 3.1.5, and 3.2.1. + +.. + +.. date: 2024-02-05-02-45-51 +.. gh-issue: 115015 +.. nonce: rgtiDB +.. section: Tools/Demos + +Fix a bug in Argument Clinic that generated incorrect code for methods with +no parameters that use the :ref:`METH_METHOD | METH_FASTCALL | METH_KEYWORDS +<METH_METHOD-METH_FASTCALL-METH_KEYWORDS>` calling convention. Only the +positional parameter count was checked; any keyword argument passed would be +silently accepted. + +.. + +.. date: 2024-02-05-17-11-15 +.. gh-issue: 111140 +.. nonce: WMEjid +.. section: C API + +Adds :c:func:`PyLong_AsNativeBytes`, :c:func:`PyLong_FromNativeBytes` and +:c:func:`PyLong_FromUnsignedNativeBytes` functions. + +.. + +.. date: 2024-01-31-15-43-35 +.. gh-issue: 114685 +.. nonce: n7aRmX +.. section: C API + +:c:func:`PyBuffer_FillInfo` now raises a :exc:`SystemError` if called with +:c:macro:`PyBUF_READ` or :c:macro:`PyBUF_WRITE` as flags. These flags should +only be used with the ``PyMemoryView_*`` C API. + +.. + +.. date: 2024-01-29-12-13-24 +.. gh-issue: 114685 +.. nonce: B07RME +.. section: C API + +:c:func:`PyObject_GetBuffer` now raises a :exc:`SystemError` if called with +:c:macro:`PyBUF_READ` or :c:macro:`PyBUF_WRITE` as flags. These flags should +only be used with the ``PyMemoryView_*`` C API. + +.. + +.. date: 2024-01-26-21-54-42 +.. gh-issue: 114626 +.. nonce: SKhbh_ +.. section: C API + +Add ``PyCFunctionFast`` and ``PyCFunctionFastWithKeywords`` typedefs +(identical to the existing ``_PyCFunctionFast`` and +``_PyCFunctionFastWithKeywords`` typedefs, just without a leading ``_`` +prefix). + +.. + +.. date: 2024-01-23-21-45-02 +.. gh-issue: 114329 +.. nonce: YRaBoe +.. section: C API + +Add :c:func:`PyList_GetItemRef`, which is similar to +:c:func:`PyList_GetItem` but returns a :term:`strong reference` instead of a +:term:`borrowed reference`. + +.. + +.. date: 2023-11-16-02-07-48 +.. gh-issue: 110850 +.. nonce: DQGNfF +.. section: C API + +Add PyTime C API: + +* :c:type:`PyTime_t` type. +* :c:var:`PyTime_MIN` and :c:var:`PyTime_MAX` constants. +* :c:func:`PyTime_AsSecondsDouble`, + :c:func:`PyTime_Monotonic`, :c:func:`PyTime_PerfCounter`, and + :c:func:`PyTime_Time` functions. + +Patch by Victor Stinner. + +.. + +.. date: 2023-11-15-13-47-48 +.. gh-issue: 112066 +.. nonce: 22WsqR +.. section: C API + +Add :c:func:`PyDict_SetDefaultRef`: insert a key and value into a dictionary +if the key is not already present. This is similar to +:meth:`dict.setdefault`, but returns an integer value indicating if the key +was already present. It is also similar to :c:func:`PyDict_SetDefault`, but +returns a strong reference instead of a borrowed reference. diff --git a/Misc/NEWS.d/next/Build/2024-02-01-20-08-11.gh-issue-114875.x_2iZ9.rst b/Misc/NEWS.d/next/Build/2024-02-01-20-08-11.gh-issue-114875.x_2iZ9.rst deleted file mode 100644 index 20e9d6376b973cb..000000000000000 --- a/Misc/NEWS.d/next/Build/2024-02-01-20-08-11.gh-issue-114875.x_2iZ9.rst +++ /dev/null @@ -1 +0,0 @@ -Add :c:func:`!getgrent` as a prerequisite for building the :mod:`grp` module. diff --git a/Misc/NEWS.d/next/Build/2024-02-07-08-23-48.gh-issue-114099.XcEXEZ.rst b/Misc/NEWS.d/next/Build/2024-02-07-08-23-48.gh-issue-114099.XcEXEZ.rst deleted file mode 100644 index 5e4acfba8a69494..000000000000000 --- a/Misc/NEWS.d/next/Build/2024-02-07-08-23-48.gh-issue-114099.XcEXEZ.rst +++ /dev/null @@ -1,2 +0,0 @@ -configure and Makefile were refactored to accomodate framework builds on -Apple platforms other than macOS. diff --git a/Misc/NEWS.d/next/Build/2024-02-08-17-38-56.gh-issue-113632.y9KIGb.rst b/Misc/NEWS.d/next/Build/2024-02-08-17-38-56.gh-issue-113632.y9KIGb.rst deleted file mode 100644 index 8b02b1b2cd08c90..000000000000000 --- a/Misc/NEWS.d/next/Build/2024-02-08-17-38-56.gh-issue-113632.y9KIGb.rst +++ /dev/null @@ -1,2 +0,0 @@ -Promote WASI to a tier 2 platform and drop Emscripten from tier 3 in -configure.ac. diff --git a/Misc/NEWS.d/next/Build/2024-02-08-19-36-20.gh-issue-115167.LB9nDK.rst b/Misc/NEWS.d/next/Build/2024-02-08-19-36-20.gh-issue-115167.LB9nDK.rst deleted file mode 100644 index c60c4a93fe8906c..000000000000000 --- a/Misc/NEWS.d/next/Build/2024-02-08-19-36-20.gh-issue-115167.LB9nDK.rst +++ /dev/null @@ -1 +0,0 @@ -Avoid vendoring ``vcruntime140_threads.dll`` when building with Visual Studio 2022 version 17.8. diff --git a/Misc/NEWS.d/next/C API/2023-11-15-13-47-48.gh-issue-112066.22WsqR.rst b/Misc/NEWS.d/next/C API/2023-11-15-13-47-48.gh-issue-112066.22WsqR.rst deleted file mode 100644 index ae2b8b2444de972..000000000000000 --- a/Misc/NEWS.d/next/C API/2023-11-15-13-47-48.gh-issue-112066.22WsqR.rst +++ /dev/null @@ -1,5 +0,0 @@ -Add :c:func:`PyDict_SetDefaultRef`: insert a key and value into a dictionary -if the key is not already present. This is similar to -:meth:`dict.setdefault`, but returns an integer value indicating if the key -was already present. It is also similar to :c:func:`PyDict_SetDefault`, but -returns a strong reference instead of a borrowed reference. diff --git a/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst b/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst deleted file mode 100644 index 998d4426dd53f92..000000000000000 --- a/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst +++ /dev/null @@ -1,9 +0,0 @@ -Add PyTime C API: - -* :c:type:`PyTime_t` type. -* :c:var:`PyTime_MIN` and :c:var:`PyTime_MAX` constants. -* :c:func:`PyTime_AsSecondsDouble`, - :c:func:`PyTime_Monotonic`, :c:func:`PyTime_PerfCounter`, and - :c:func:`PyTime_Time` functions. - -Patch by Victor Stinner. diff --git a/Misc/NEWS.d/next/C API/2024-01-23-21-45-02.gh-issue-114329.YRaBoe.rst b/Misc/NEWS.d/next/C API/2024-01-23-21-45-02.gh-issue-114329.YRaBoe.rst deleted file mode 100644 index 62d4ce0cfb8de54..000000000000000 --- a/Misc/NEWS.d/next/C API/2024-01-23-21-45-02.gh-issue-114329.YRaBoe.rst +++ /dev/null @@ -1,3 +0,0 @@ -Add :c:func:`PyList_GetItemRef`, which is similar to -:c:func:`PyList_GetItem` but returns a :term:`strong reference` instead of a -:term:`borrowed reference`. diff --git a/Misc/NEWS.d/next/C API/2024-01-26-21-54-42.gh-issue-114626.SKhbh_.rst b/Misc/NEWS.d/next/C API/2024-01-26-21-54-42.gh-issue-114626.SKhbh_.rst deleted file mode 100644 index 0da03ec122b555e..000000000000000 --- a/Misc/NEWS.d/next/C API/2024-01-26-21-54-42.gh-issue-114626.SKhbh_.rst +++ /dev/null @@ -1 +0,0 @@ -Add ``PyCFunctionFast`` and ``PyCFunctionFastWithKeywords`` typedefs (identical to the existing ``_PyCFunctionFast`` and ``_PyCFunctionFastWithKeywords`` typedefs, just without a leading ``_`` prefix). diff --git a/Misc/NEWS.d/next/C API/2024-01-29-12-13-24.gh-issue-114685.B07RME.rst b/Misc/NEWS.d/next/C API/2024-01-29-12-13-24.gh-issue-114685.B07RME.rst deleted file mode 100644 index 55b02d1d8e1e9fc..000000000000000 --- a/Misc/NEWS.d/next/C API/2024-01-29-12-13-24.gh-issue-114685.B07RME.rst +++ /dev/null @@ -1,3 +0,0 @@ -:c:func:`PyObject_GetBuffer` now raises a :exc:`SystemError` if called with -:c:macro:`PyBUF_READ` or :c:macro:`PyBUF_WRITE` as flags. These flags should -only be used with the ``PyMemoryView_*`` C API. diff --git a/Misc/NEWS.d/next/C API/2024-01-31-15-43-35.gh-issue-114685.n7aRmX.rst b/Misc/NEWS.d/next/C API/2024-01-31-15-43-35.gh-issue-114685.n7aRmX.rst deleted file mode 100644 index 76ff00645fe57d2..000000000000000 --- a/Misc/NEWS.d/next/C API/2024-01-31-15-43-35.gh-issue-114685.n7aRmX.rst +++ /dev/null @@ -1,3 +0,0 @@ -:c:func:`PyBuffer_FillInfo` now raises a :exc:`SystemError` if called with -:c:macro:`PyBUF_READ` or :c:macro:`PyBUF_WRITE` as flags. These flags should -only be used with the ``PyMemoryView_*`` C API. diff --git a/Misc/NEWS.d/next/C API/2024-02-05-17-11-15.gh-issue-111140.WMEjid.rst b/Misc/NEWS.d/next/C API/2024-02-05-17-11-15.gh-issue-111140.WMEjid.rst deleted file mode 100644 index a8aa191b5eb3bac..000000000000000 --- a/Misc/NEWS.d/next/C API/2024-02-05-17-11-15.gh-issue-111140.WMEjid.rst +++ /dev/null @@ -1,2 +0,0 @@ -Adds :c:func:`PyLong_AsNativeBytes`, :c:func:`PyLong_FromNativeBytes` and -:c:func:`PyLong_FromUnsignedNativeBytes` functions. diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-05-16-06-52-34.gh-issue-104530.mJnA0W.rst b/Misc/NEWS.d/next/Core and Builtins/2023-05-16-06-52-34.gh-issue-104530.mJnA0W.rst deleted file mode 100644 index 8643a25ae51b13f..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2023-05-16-06-52-34.gh-issue-104530.mJnA0W.rst +++ /dev/null @@ -1 +0,0 @@ -Use native Win32 condition variables. diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-06-06-19-09-00.gh-issue-55664.vYYl0V.rst b/Misc/NEWS.d/next/Core and Builtins/2023-06-06-19-09-00.gh-issue-55664.vYYl0V.rst deleted file mode 100644 index 438be9854966501..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2023-06-06-19-09-00.gh-issue-55664.vYYl0V.rst +++ /dev/null @@ -1 +0,0 @@ -Add warning when creating :class:`type` using a namespace dictionary with non-string keys. Patched by Daniel Urban and Furkan Onder. diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst deleted file mode 100644 index 90f49272218c960..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst +++ /dev/null @@ -1,5 +0,0 @@ -Make interp->obmalloc a pointer. For interpreters that share state with the -main interpreter, this points to the same static memory structure. For -interpreters with their own obmalloc state, it is heap allocated. Add -free_obmalloc_arenas() which will free the obmalloc arenas and radix tree -structures for interpreters with their own obmalloc state. diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-24-03-25-28.gh-issue-113464.dvjQmA.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-24-03-25-28.gh-issue-113464.dvjQmA.rst deleted file mode 100644 index bdee4d645f61c83..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2023-12-24-03-25-28.gh-issue-113464.dvjQmA.rst +++ /dev/null @@ -1,4 +0,0 @@ -Add an option (``--enable-experimental-jit`` for ``configure``-based builds -or ``--experimental-jit`` for ``PCbuild``-based ones) to build an -*experimental* just-in-time compiler, based on `copy-and-patch -<https://fredrikbk.com/publications/copy-and-patch.pdf>`_ diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-08-21-57-41.gh-issue-112050.qwgjx1.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-08-21-57-41.gh-issue-112050.qwgjx1.rst deleted file mode 100644 index 3041d6513c6597c..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-01-08-21-57-41.gh-issue-112050.qwgjx1.rst +++ /dev/null @@ -1 +0,0 @@ -Make methods on :class:`collections.deque` thread-safe when the GIL is disabled. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-11-22-58-45.gh-issue-112050.hDuvDW.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-11-22-58-45.gh-issue-112050.hDuvDW.rst deleted file mode 100644 index e5f3d5ea0cea254..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-01-11-22-58-45.gh-issue-112050.hDuvDW.rst +++ /dev/null @@ -1 +0,0 @@ -Convert :class:`collections.deque` to use Argument Clinic. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-12-16-40-07.gh-issue-113939.Yi3L-e.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-12-16-40-07.gh-issue-113939.Yi3L-e.rst deleted file mode 100644 index 28b8e4bdda6be4e..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-01-12-16-40-07.gh-issue-113939.Yi3L-e.rst +++ /dev/null @@ -1,4 +0,0 @@ -frame.clear(): -Clear frame.f_locals as well, and not only the fast locals. -This is relevant once frame.f_locals was accessed, -which would contain also references to all the locals. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-16-14-41-54.gh-issue-114058.Cb2b8h.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-16-14-41-54.gh-issue-114058.Cb2b8h.rst deleted file mode 100644 index beb82dbcd3cccd6..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-01-16-14-41-54.gh-issue-114058.Cb2b8h.rst +++ /dev/null @@ -1 +0,0 @@ -Implement the foundations of the Tier 2 redundancy eliminator. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-17-00-52-57.gh-issue-113884.CvEjUE.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-17-00-52-57.gh-issue-113884.CvEjUE.rst deleted file mode 100644 index 6a39fd2f60ab813..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-01-17-00-52-57.gh-issue-113884.CvEjUE.rst +++ /dev/null @@ -1 +0,0 @@ -Make :class:`queue.SimpleQueue` thread safe when the GIL is disabled. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-17-05-09-32.gh-issue-112354.Run9ko.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-17-05-09-32.gh-issue-112354.Run9ko.rst deleted file mode 100644 index ed45ba49c3ad421..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-01-17-05-09-32.gh-issue-112354.Run9ko.rst +++ /dev/null @@ -1,2 +0,0 @@ -The ``END_FOR`` instruction now pops only one value. This is to better -support side exits in loops. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-17-23-39-20.gh-issue-114050.Lnv1oq.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-17-23-39-20.gh-issue-114050.Lnv1oq.rst deleted file mode 100644 index c35d2508e6bdda6..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-01-17-23-39-20.gh-issue-114050.Lnv1oq.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix segmentation fault caused by an incorrect format string -in ``TypeError`` exception when more than two arguments are passed to ``int``. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-18-20-20-37.gh-issue-112529.oVNvDG.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-18-20-20-37.gh-issue-112529.oVNvDG.rst deleted file mode 100644 index b3aa43801da488a..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-01-18-20-20-37.gh-issue-112529.oVNvDG.rst +++ /dev/null @@ -1,3 +0,0 @@ -The free-threaded build now has its own thread-safe GC implementation that -uses mimalloc to find GC tracked objects. It is non-generational, unlike the -existing GC implementation. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-19-13-18-13.gh-issue-114265.7HAi--.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-19-13-18-13.gh-issue-114265.7HAi--.rst deleted file mode 100644 index 74affbbd09ffb49..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-01-19-13-18-13.gh-issue-114265.7HAi--.rst +++ /dev/null @@ -1 +0,0 @@ -Compiler propagates line numbers before optimization, leading to more optimization opportunities and removing the need for the ``guarantee_lineno_for_exits`` hack. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-21-17-29-32.gh-issue-114388.UVGO4K.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-21-17-29-32.gh-issue-114388.UVGO4K.rst deleted file mode 100644 index 52c2742001d9ca6..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-01-21-17-29-32.gh-issue-114388.UVGO4K.rst +++ /dev/null @@ -1,5 +0,0 @@ -Fix a :exc:`RuntimeWarning` emitted when assign an integer-like value that -is not an instance of :class:`int` to an attribute that corresponds to a C -struct member of :ref:`type <PyMemberDef-types>` T_UINT and T_ULONG. Fix a -double :exc:`RuntimeWarning` emitted when assign a negative integer value to -an attribute that corresponds to a C struct member of type T_UINT. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-22-09-49-02.gh-issue-114083.hf1-ku.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-22-09-49-02.gh-issue-114083.hf1-ku.rst deleted file mode 100644 index 79be45e87b90d32..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-01-22-09-49-02.gh-issue-114083.hf1-ku.rst +++ /dev/null @@ -1 +0,0 @@ -Compiler applies folding of LOAD_CONST with following instruction in a separate pass before other optimisations. This enables jump threading in certain circumstances. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-22-15-10-01.gh-issue-114456.fBFEJF.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-22-15-10-01.gh-issue-114456.fBFEJF.rst deleted file mode 100644 index 2b30ad98fb5c79b..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-01-22-15-10-01.gh-issue-114456.fBFEJF.rst +++ /dev/null @@ -1 +0,0 @@ -Lower the recursion limit under a debug build of WASI. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-25-18-50-49.gh-issue-112529.IbbApA.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-25-18-50-49.gh-issue-112529.IbbApA.rst deleted file mode 100644 index 2a6d74fb2227023..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-01-25-18-50-49.gh-issue-112529.IbbApA.rst +++ /dev/null @@ -1,4 +0,0 @@ -The free-threaded build no longer allocates space for the ``PyGC_Head`` -structure in objects that support cyclic garbage collection. A number of -other fields and data structures are used as replacements, including -``ob_gc_bits``, ``ob_tid``, and mimalloc internal data structures. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-31-09-10-10.gh-issue-107944.XWm1B-.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-31-09-10-10.gh-issue-107944.XWm1B-.rst deleted file mode 100644 index 8e3fb786c11055b..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-01-31-09-10-10.gh-issue-107944.XWm1B-.rst +++ /dev/null @@ -1 +0,0 @@ -Improve error message for function calls with bad keyword arguments via getargs diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-01-18-16-52.gh-issue-114806.wrH2J6.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-01-18-16-52.gh-issue-114806.wrH2J6.rst deleted file mode 100644 index 795f2529df82074..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-02-01-18-16-52.gh-issue-114806.wrH2J6.rst +++ /dev/null @@ -1,3 +0,0 @@ -No longer specialize calls to classes, if those classes have metaclasses. -Fixes bug where the ``__call__`` method of the metaclass was not being -called. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-01-23-43-49.gh-issue-76763.o_2J6i.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-01-23-43-49.gh-issue-76763.o_2J6i.rst deleted file mode 100644 index d35d3d87073dddd..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-02-01-23-43-49.gh-issue-76763.o_2J6i.rst +++ /dev/null @@ -1,3 +0,0 @@ -The :func:`chr` builtin function now always raises :exc:`ValueError` for -values outside the valid range. Previously it raised :exc:`OverflowError` for -very large or small values. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-02-05-27-48.gh-issue-113462.VMml8q.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-02-05-27-48.gh-issue-113462.VMml8q.rst deleted file mode 100644 index 1a401ecebf019aa..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-02-02-05-27-48.gh-issue-113462.VMml8q.rst +++ /dev/null @@ -1,2 +0,0 @@ -Limit the number of versions that a single class can use. Prevents a few -wayward classes using up all the version numbers. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-03-01-48-38.gh-issue-114944.4J5ELD.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-03-01-48-38.gh-issue-114944.4J5ELD.rst deleted file mode 100644 index fb41caf7c5f4fae..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-02-03-01-48-38.gh-issue-114944.4J5ELD.rst +++ /dev/null @@ -1 +0,0 @@ -Fixes a race between ``PyParkingLot_Park`` and ``_PyParkingLot_UnparkAll``. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-03-04-07-18.gh-issue-114887.uLSFmN.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-03-04-07-18.gh-issue-114887.uLSFmN.rst deleted file mode 100644 index b4d8cf4089d7238..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-02-03-04-07-18.gh-issue-114887.uLSFmN.rst +++ /dev/null @@ -1,2 +0,0 @@ -Changed socket type validation in :meth:`~asyncio.loop.create_datagram_endpoint` to accept all non-stream sockets. -This fixes a regression in compatibility with raw sockets. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-05-12-40-26.gh-issue-115011.L1AKF5.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-05-12-40-26.gh-issue-115011.L1AKF5.rst deleted file mode 100644 index cf91a4f818bd449..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-02-05-12-40-26.gh-issue-115011.L1AKF5.rst +++ /dev/null @@ -1,3 +0,0 @@ -Setters for members with an unsigned integer type now support the same range -of valid values for objects that has a :meth:`~object.__index__` method as -for :class:`int`. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-07-00-18-42.gh-issue-112069.jRDRR5.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-07-00-18-42.gh-issue-112069.jRDRR5.rst deleted file mode 100644 index 51ba6bd1ddaac35..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-02-07-00-18-42.gh-issue-112069.jRDRR5.rst +++ /dev/null @@ -1 +0,0 @@ -Adapt :class:`set` and :class:`frozenset` methods to Argument Clinic. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-07-07-50-12.gh-issue-114828.nSXwMi.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-07-07-50-12.gh-issue-114828.nSXwMi.rst deleted file mode 100644 index b1c63e0a1518fdc..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-02-07-07-50-12.gh-issue-114828.nSXwMi.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix compilation crashes in uncommon code examples using :func:`super` inside -a comprehension in a class body. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-07-18-04-36.gh-issue-114695.o9wP5P.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-07-18-04-36.gh-issue-114695.o9wP5P.rst deleted file mode 100644 index a1db4de393eecb3..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-02-07-18-04-36.gh-issue-114695.o9wP5P.rst +++ /dev/null @@ -1,3 +0,0 @@ -Add :func:`sys._clear_internal_caches`, which clears all internal -performance-related caches (and deprecate the less-general -:func:`sys._clear_type_cache` function). diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-12-17-18-26.gh-issue-114570.BzwMlJ.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-12-17-18-26.gh-issue-114570.BzwMlJ.rst deleted file mode 100644 index 828d47d0e18d6b6..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-02-12-17-18-26.gh-issue-114570.BzwMlJ.rst +++ /dev/null @@ -1,3 +0,0 @@ -Add :exc:`PythonFinalizationError` exception. This exception derived from -:exc:`RuntimeError` is raised when an operation is blocked during the -:term:`Python finalization <interpreter shutdown>`. Patch by Victor Stinner. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-14-23-50-55.gh-issue-112087.H_4W_v.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-14-23-50-55.gh-issue-112087.H_4W_v.rst deleted file mode 100644 index f92cdafd4ba225c..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-02-14-23-50-55.gh-issue-112087.H_4W_v.rst +++ /dev/null @@ -1,2 +0,0 @@ -For an empty reverse iterator for list will be reduced to :func:`reversed`. -Patch by Donghee Na diff --git a/Misc/NEWS.d/next/Documentation/2024-01-17-11-40-03.gh-issue-114123.LuueXf.rst b/Misc/NEWS.d/next/Documentation/2024-01-17-11-40-03.gh-issue-114123.LuueXf.rst deleted file mode 100644 index 1d93a4228400770..000000000000000 --- a/Misc/NEWS.d/next/Documentation/2024-01-17-11-40-03.gh-issue-114123.LuueXf.rst +++ /dev/null @@ -1,7 +0,0 @@ -Move the :mod:`csv` module docstring to the :mod:`!csv` module -instead of reexporting it from the internal :mod:`!_csv` module, -and remove ``__doc__`` from ``csv.__all__``. - -Move :attr:`!csv.__version__` to the :mod:`!csv` module -instead of reexporting it from the internal :mod:`!_csv` module, -and remove ``__version__`` from ``csv.__all__``. diff --git a/Misc/NEWS.d/next/Documentation/2024-02-12-12-26-17.gh-issue-115233.aug6r9.rst b/Misc/NEWS.d/next/Documentation/2024-02-12-12-26-17.gh-issue-115233.aug6r9.rst deleted file mode 100644 index f37f94d12d4cf14..000000000000000 --- a/Misc/NEWS.d/next/Documentation/2024-02-12-12-26-17.gh-issue-115233.aug6r9.rst +++ /dev/null @@ -1 +0,0 @@ -Fix an example for :class:`~logging.LoggerAdapter` in the Logging Cookbook. diff --git a/Misc/NEWS.d/next/IDLE/2023-04-25-03-01-23.gh-issue-103820.LCSpza.rst b/Misc/NEWS.d/next/IDLE/2023-04-25-03-01-23.gh-issue-103820.LCSpza.rst deleted file mode 100644 index b9d7faf047b28e7..000000000000000 --- a/Misc/NEWS.d/next/IDLE/2023-04-25-03-01-23.gh-issue-103820.LCSpza.rst +++ /dev/null @@ -1,2 +0,0 @@ -Revise IDLE bindings so that events from mouse button 4/5 on non-X11 -windowing systems (i.e. Win32 and Aqua) are not mistaken for scrolling. diff --git a/Misc/NEWS.d/next/IDLE/2024-01-17-23-18-15.gh-issue-96905.UYaxoU.rst b/Misc/NEWS.d/next/IDLE/2024-01-17-23-18-15.gh-issue-96905.UYaxoU.rst deleted file mode 100644 index fe7dde64c7c7d51..000000000000000 --- a/Misc/NEWS.d/next/IDLE/2024-01-17-23-18-15.gh-issue-96905.UYaxoU.rst +++ /dev/null @@ -1 +0,0 @@ -In idlelib code, stop redefining built-ins 'dict' and 'object'. diff --git a/Misc/NEWS.d/next/Library/2019-10-05-22-56-50.bpo-38364.sYTCWF.rst b/Misc/NEWS.d/next/Library/2019-10-05-22-56-50.bpo-38364.sYTCWF.rst deleted file mode 100644 index 87fb5ae8fd0eeda..000000000000000 --- a/Misc/NEWS.d/next/Library/2019-10-05-22-56-50.bpo-38364.sYTCWF.rst +++ /dev/null @@ -1 +0,0 @@ -The ``inspect`` functions ``isgeneratorfunction``, ``iscoroutinefunction``, ``isasyncgenfunction`` now support ``functools.partialmethod`` wrapped functions the same way they support ``functools.partial``. diff --git a/Misc/NEWS.d/next/Library/2022-07-31-01-24-40.gh-issue-88569.eU0--b.rst b/Misc/NEWS.d/next/Library/2022-07-31-01-24-40.gh-issue-88569.eU0--b.rst deleted file mode 100644 index 31dd985bb5c3b6c..000000000000000 --- a/Misc/NEWS.d/next/Library/2022-07-31-01-24-40.gh-issue-88569.eU0--b.rst +++ /dev/null @@ -1,4 +0,0 @@ -Add :func:`os.path.isreserved`, which identifies reserved pathnames such -as "NUL", "AUX" and "CON". This function is only available on Windows. - -Deprecate :meth:`pathlib.PurePath.is_reserved`. diff --git a/Misc/NEWS.d/next/Library/2023-03-08-00-02-30.gh-issue-102512.LiugDr.rst b/Misc/NEWS.d/next/Library/2023-03-08-00-02-30.gh-issue-102512.LiugDr.rst deleted file mode 100644 index 659cba73cbf34e9..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-03-08-00-02-30.gh-issue-102512.LiugDr.rst +++ /dev/null @@ -1,3 +0,0 @@ -When :func:`os.fork` is called from a foreign thread (aka ``_DummyThread``), -the type of the thread in a child process is changed to ``_MainThread``. -Also changed its name and daemonic status, it can be now joined. diff --git a/Misc/NEWS.d/next/Library/2023-03-15-03-21-18.gh-issue-85984.Xaq6ZN.rst b/Misc/NEWS.d/next/Library/2023-03-15-03-21-18.gh-issue-85984.Xaq6ZN.rst deleted file mode 100644 index 0e54a1fe3c8a1cf..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-03-15-03-21-18.gh-issue-85984.Xaq6ZN.rst +++ /dev/null @@ -1,2 +0,0 @@ -Add POSIX pseudo-terminal functions :func:`os.posix_openpt`, -:func:`os.grantpt`, :func:`os.unlockpt`, and :func:`os.ptsname`. diff --git a/Misc/NEWS.d/next/Library/2023-04-08-11-41-07.gh-issue-101599.PaWNFh.rst b/Misc/NEWS.d/next/Library/2023-04-08-11-41-07.gh-issue-101599.PaWNFh.rst deleted file mode 100644 index a1608a1ae0d2fae..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-04-08-11-41-07.gh-issue-101599.PaWNFh.rst +++ /dev/null @@ -1 +0,0 @@ -Changed argparse flag options formatting to remove redundancy. diff --git a/Misc/NEWS.d/next/Library/2023-05-06-04-57-10.gh-issue-96471.C9wAU7.rst b/Misc/NEWS.d/next/Library/2023-05-06-04-57-10.gh-issue-96471.C9wAU7.rst deleted file mode 100644 index 0bace8d8bd425c7..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-05-06-04-57-10.gh-issue-96471.C9wAU7.rst +++ /dev/null @@ -1 +0,0 @@ -Add :py:class:`queue.Queue` termination with :py:meth:`~queue.Queue.shutdown`. diff --git a/Misc/NEWS.d/next/Library/2023-05-08-09-30-00.gh-issue-104282.h4c6Eb.rst b/Misc/NEWS.d/next/Library/2023-05-08-09-30-00.gh-issue-104282.h4c6Eb.rst deleted file mode 100644 index 569ce66a5b9d5fd..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-05-08-09-30-00.gh-issue-104282.h4c6Eb.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fix null pointer dereference in :func:`lzma._decode_filter_properties` -due to improper handling of BCJ filters with properties of zero length. -Patch by Radislav Chugunov. diff --git a/Misc/NEWS.d/next/Library/2023-05-30-18-30-11.gh-issue-105102.SnpK04.rst b/Misc/NEWS.d/next/Library/2023-05-30-18-30-11.gh-issue-105102.SnpK04.rst deleted file mode 100644 index 7ca21afefa31325..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-05-30-18-30-11.gh-issue-105102.SnpK04.rst +++ /dev/null @@ -1,2 +0,0 @@ -Allow :class:`ctypes.Union` to be nested in :class:`ctypes.Structure` when -the system endianness is the opposite of the classes. diff --git a/Misc/NEWS.d/next/Library/2023-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst b/Misc/NEWS.d/next/Library/2023-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst deleted file mode 100644 index 345c8b20815c951..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix stacklevel in ``InvalidTZPathWarning`` during :mod:`zoneinfo` module -import. diff --git a/Misc/NEWS.d/next/Library/2023-07-23-12-28-26.gh-issue-75705.aB2-Ww.rst b/Misc/NEWS.d/next/Library/2023-07-23-12-28-26.gh-issue-75705.aB2-Ww.rst deleted file mode 100644 index 272e31d64cfbd9b..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-07-23-12-28-26.gh-issue-75705.aB2-Ww.rst +++ /dev/null @@ -1 +0,0 @@ -Set unixfrom envelope in :class:`mailbox.mbox` and :class:`mailbox.MMDF`. diff --git a/Misc/NEWS.d/next/Library/2023-09-22-22-17-45.gh-issue-38807.m9McRN.rst b/Misc/NEWS.d/next/Library/2023-09-22-22-17-45.gh-issue-38807.m9McRN.rst deleted file mode 100644 index 4219723d15b9e65..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-09-22-22-17-45.gh-issue-38807.m9McRN.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fix race condition in :mod:`trace`. Instead of checking if a directory -exists and creating it, directly call :func:`os.makedirs` with the kwarg -``exist_ok=True``. diff --git a/Misc/NEWS.d/next/Library/2023-10-04-11-09-30.gh-issue-110345.fZU1ud.rst b/Misc/NEWS.d/next/Library/2023-10-04-11-09-30.gh-issue-110345.fZU1ud.rst deleted file mode 100644 index d9ccc0f12de47c2..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-10-04-11-09-30.gh-issue-110345.fZU1ud.rst +++ /dev/null @@ -1 +0,0 @@ -Show the Tcl/Tk patchlevel (rather than version) in :meth:`tkinter._test`. diff --git a/Misc/NEWS.d/next/Library/2023-10-19-02-08-12.gh-issue-111051.8h1Dpk.rst b/Misc/NEWS.d/next/Library/2023-10-19-02-08-12.gh-issue-111051.8h1Dpk.rst deleted file mode 100644 index adb3241b89ae3e6..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-10-19-02-08-12.gh-issue-111051.8h1Dpk.rst +++ /dev/null @@ -1 +0,0 @@ -Added check for file modification during debugging with :mod:`pdb` diff --git a/Misc/NEWS.d/next/Library/2023-10-24-19-19-54.gh-issue-82626._hfLRf.rst b/Misc/NEWS.d/next/Library/2023-10-24-19-19-54.gh-issue-82626._hfLRf.rst deleted file mode 100644 index 92a66b5bf0f635c..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-10-24-19-19-54.gh-issue-82626._hfLRf.rst +++ /dev/null @@ -1,2 +0,0 @@ -Many functions now emit a warning if a boolean value is passed as a file -descriptor argument. diff --git a/Misc/NEWS.d/next/Library/2023-10-27-19-24-58.gh-issue-43457.84lx9H.rst b/Misc/NEWS.d/next/Library/2023-10-27-19-24-58.gh-issue-43457.84lx9H.rst deleted file mode 100644 index 401a532ce03e777..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-10-27-19-24-58.gh-issue-43457.84lx9H.rst +++ /dev/null @@ -1,8 +0,0 @@ -Fix the :mod:`tkinter` widget method :meth:`!wm_attributes`. It now -accepts the attribute name without the minus prefix to get window attributes -and allows to specify attributes and values to set as keyword arguments. -Add new optional keyword argument *return_python_dict*: calling -``w.wm_attributes(return_python_dict=True)`` returns the attributes as -a dict instead of a tuple. -Calling ``w.wm_attributes()`` now returns a tuple instead of string if -*wantobjects* was set to 0. diff --git a/Misc/NEWS.d/next/Library/2023-11-04-22-32-27.gh-issue-111741.f1ufr8.rst b/Misc/NEWS.d/next/Library/2023-11-04-22-32-27.gh-issue-111741.f1ufr8.rst deleted file mode 100644 index e43f93a270ce9c8..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-11-04-22-32-27.gh-issue-111741.f1ufr8.rst +++ /dev/null @@ -1 +0,0 @@ -Recognise ``image/webp`` as a standard format in the :mod:`mimetypes` module. diff --git a/Misc/NEWS.d/next/Library/2023-11-18-16-30-21.gh-issue-112240.YXS0tj.rst b/Misc/NEWS.d/next/Library/2023-11-18-16-30-21.gh-issue-112240.YXS0tj.rst deleted file mode 100644 index 686f0311e80dcbf..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-11-18-16-30-21.gh-issue-112240.YXS0tj.rst +++ /dev/null @@ -1,2 +0,0 @@ -Add option to calendar module CLI to specify the weekday to start each week. -Patch by Steven Ward. diff --git a/Misc/NEWS.d/next/Library/2023-11-24-19-08-50.gh-issue-112343.RarGFC.rst b/Misc/NEWS.d/next/Library/2023-11-24-19-08-50.gh-issue-112343.RarGFC.rst deleted file mode 100644 index aaa50fce3ac962c..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-11-24-19-08-50.gh-issue-112343.RarGFC.rst +++ /dev/null @@ -1 +0,0 @@ -Improve handling of pdb convenience variables to avoid replacing string contents. diff --git a/Misc/NEWS.d/next/Library/2023-11-27-19-54-43.gh-issue-59013.chpQ0e.rst b/Misc/NEWS.d/next/Library/2023-11-27-19-54-43.gh-issue-59013.chpQ0e.rst deleted file mode 100644 index a2be2fb8eacf17c..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-11-27-19-54-43.gh-issue-59013.chpQ0e.rst +++ /dev/null @@ -1 +0,0 @@ -Set breakpoint on the first executable line of the function, instead of the line of function definition when the user do ``break func`` using :mod:`pdb` diff --git a/Misc/NEWS.d/next/Library/2023-12-09-23-31-17.gh-issue-112919.S5k9QN.rst b/Misc/NEWS.d/next/Library/2023-12-09-23-31-17.gh-issue-112919.S5k9QN.rst deleted file mode 100644 index 3e99d480139cbea..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-12-09-23-31-17.gh-issue-112919.S5k9QN.rst +++ /dev/null @@ -1,2 +0,0 @@ -Speed-up :func:`datetime.datetime.replace`, :func:`datetime.date.replace` and -:func:`datetime.time.replace`. diff --git a/Misc/NEWS.d/next/Library/2023-12-18-20-10-50.gh-issue-89039.gqFdtU.rst b/Misc/NEWS.d/next/Library/2023-12-18-20-10-50.gh-issue-89039.gqFdtU.rst deleted file mode 100644 index d1998d75e9fd76b..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-12-18-20-10-50.gh-issue-89039.gqFdtU.rst +++ /dev/null @@ -1,6 +0,0 @@ -When replace() method is called on a subclass of datetime, date or time, -properly call derived constructor. Previously, only the base class's -constructor was called. - -Also, make sure to pass non-zero fold values when creating subclasses in -various methods. Previously, fold was silently ignored. diff --git a/Misc/NEWS.d/next/Library/2024-01-04-20-58-17.gh-issue-113225.-nyJM4.rst b/Misc/NEWS.d/next/Library/2024-01-04-20-58-17.gh-issue-113225.-nyJM4.rst deleted file mode 100644 index 0c07f42fd065d25..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-04-20-58-17.gh-issue-113225.-nyJM4.rst +++ /dev/null @@ -1,2 +0,0 @@ -Speed up :meth:`pathlib.Path.walk` by using :attr:`os.DirEntry.path` where -possible. diff --git a/Misc/NEWS.d/next/Library/2024-01-05-16-27-34.gh-issue-113732.fgDRXA.rst b/Misc/NEWS.d/next/Library/2024-01-05-16-27-34.gh-issue-113732.fgDRXA.rst deleted file mode 100644 index 7582603dcf95f5f..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-05-16-27-34.gh-issue-113732.fgDRXA.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix support of :data:`~csv.QUOTE_NOTNULL` and :data:`~csv.QUOTE_STRINGS` in -:func:`csv.reader`. diff --git a/Misc/NEWS.d/next/Library/2024-01-07-21-04-24.gh-issue-113796.6iNsCR.rst b/Misc/NEWS.d/next/Library/2024-01-07-21-04-24.gh-issue-113796.6iNsCR.rst deleted file mode 100644 index e9d4aba99066770..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-07-21-04-24.gh-issue-113796.6iNsCR.rst +++ /dev/null @@ -1,3 +0,0 @@ -Add more validation checks in the :class:`csv.Dialect` constructor. -:exc:`ValueError` is now raised if the same character is used in different -roles. diff --git a/Misc/NEWS.d/next/Library/2024-01-11-15-10-53.gh-issue-97959.UOj6d4.rst b/Misc/NEWS.d/next/Library/2024-01-11-15-10-53.gh-issue-97959.UOj6d4.rst deleted file mode 100644 index a317271947dc377..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-11-15-10-53.gh-issue-97959.UOj6d4.rst +++ /dev/null @@ -1,7 +0,0 @@ -Fix rendering class methods, bound methods, method and function aliases in -:mod:`pydoc`. Class methods no longer have "method of builtins.type -instance" note. Corresponding notes are now added for class and unbound -methods. Method and function aliases now have references to the module or -the class where the origin was defined if it differs from the current. Bound -methods are now listed in the static methods section. Methods of builtin -classes are now supported as well as methods of Python classes. diff --git a/Misc/NEWS.d/next/Library/2024-01-11-20-47-49.gh-issue-113951.AzlqFK.rst b/Misc/NEWS.d/next/Library/2024-01-11-20-47-49.gh-issue-113951.AzlqFK.rst deleted file mode 100644 index e683472e59b8a49..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-11-20-47-49.gh-issue-113951.AzlqFK.rst +++ /dev/null @@ -1,7 +0,0 @@ -Fix the behavior of ``tag_unbind()`` methods of :class:`tkinter.Text` and -:class:`tkinter.Canvas` classes with three arguments. Previously, -``widget.tag_unbind(tag, sequence, funcid)`` destroyed the current binding -for *sequence*, leaving *sequence* unbound, and deleted the *funcid* -command. Now it removes only *funcid* from the binding for *sequence*, -keeping other commands, and deletes the *funcid* command. It leaves -*sequence* unbound only if *funcid* was the last bound command. diff --git a/Misc/NEWS.d/next/Library/2024-01-12-09-35-07.gh-issue-112202.t_0V1m.rst b/Misc/NEWS.d/next/Library/2024-01-12-09-35-07.gh-issue-112202.t_0V1m.rst deleted file mode 100644 index 9abde13bbf8571b..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-12-09-35-07.gh-issue-112202.t_0V1m.rst +++ /dev/null @@ -1 +0,0 @@ -Ensure that a :func:`asyncio.Condition.notify` call does not get lost if the awakened ``Task`` is simultaneously cancelled or encounters any other error. diff --git a/Misc/NEWS.d/next/Library/2024-01-12-17-32-36.gh-issue-79634.uTSTRI.rst b/Misc/NEWS.d/next/Library/2024-01-12-17-32-36.gh-issue-79634.uTSTRI.rst deleted file mode 100644 index ba19b5209e648ec..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-12-17-32-36.gh-issue-79634.uTSTRI.rst +++ /dev/null @@ -1,2 +0,0 @@ -Accept :term:`path-like objects <path-like object>` as patterns in -:meth:`pathlib.Path.glob` and :meth:`~pathlib.Path.rglob`. diff --git a/Misc/NEWS.d/next/Library/2024-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst b/Misc/NEWS.d/next/Library/2024-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst deleted file mode 100644 index fc9a765a230037d..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fix a reference leak in -:class:`asyncio.selector_events.BaseSelectorEventLoop` when SSL handshakes -fail. Patch contributed by Jamie Phan. diff --git a/Misc/NEWS.d/next/Library/2024-01-15-19-54-41.gh-issue-114087.Xic5vY.rst b/Misc/NEWS.d/next/Library/2024-01-15-19-54-41.gh-issue-114087.Xic5vY.rst deleted file mode 100644 index 68b27a7b0c9f6c9..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-15-19-54-41.gh-issue-114087.Xic5vY.rst +++ /dev/null @@ -1 +0,0 @@ -Speed up ``dataclasses.asdict`` up to 1.35x. diff --git a/Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-83648.HzD_fY.rst b/Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-83648.HzD_fY.rst deleted file mode 100644 index bd3e27b4be0cf58..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-83648.HzD_fY.rst +++ /dev/null @@ -1,2 +0,0 @@ -Support deprecation of options, positional arguments and subcommands in -:mod:`argparse`. diff --git a/Misc/NEWS.d/next/Library/2024-01-16-15-59-06.gh-issue-114149.LJ8IPm.rst b/Misc/NEWS.d/next/Library/2024-01-16-15-59-06.gh-issue-114149.LJ8IPm.rst deleted file mode 100644 index 1403d78d0d49054..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-16-15-59-06.gh-issue-114149.LJ8IPm.rst +++ /dev/null @@ -1 +0,0 @@ -Enum: correctly handle tuple subclasses in custom ``__new__``. diff --git a/Misc/NEWS.d/next/Library/2024-01-17-18-53-51.gh-issue-104522.3NyDf4.rst b/Misc/NEWS.d/next/Library/2024-01-17-18-53-51.gh-issue-104522.3NyDf4.rst deleted file mode 100644 index ca980945ea12d31..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-17-18-53-51.gh-issue-104522.3NyDf4.rst +++ /dev/null @@ -1,3 +0,0 @@ -:exc:`OSError` raised when run a subprocess now only has *filename* -attribute set to *cwd* if the error was caused by a failed attempt to change -the current directory. diff --git a/Misc/NEWS.d/next/Library/2024-01-18-10-07-52.gh-issue-114198.lK4Iif.rst b/Misc/NEWS.d/next/Library/2024-01-18-10-07-52.gh-issue-114198.lK4Iif.rst deleted file mode 100644 index fa047e288f807ed..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-18-10-07-52.gh-issue-114198.lK4Iif.rst +++ /dev/null @@ -1,2 +0,0 @@ -The signature for the ``__replace__`` method on :mod:`dataclasses` now has -the first argument named ``self``, rather than ``obj``. diff --git a/Misc/NEWS.d/next/Library/2024-01-18-22-29-28.gh-issue-101438.1-uUi_.rst b/Misc/NEWS.d/next/Library/2024-01-18-22-29-28.gh-issue-101438.1-uUi_.rst deleted file mode 100644 index 9b69b5deb1b5a06..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-18-22-29-28.gh-issue-101438.1-uUi_.rst +++ /dev/null @@ -1,4 +0,0 @@ -Avoid reference cycle in ElementTree.iterparse. The iterator returned by -``ElementTree.iterparse`` may hold on to a file descriptor. The reference -cycle prevented prompt clean-up of the file descriptor if the returned -iterator was not exhausted. diff --git a/Misc/NEWS.d/next/Library/2024-01-19-12-05-22.gh-issue-114281.H5JQe4.rst b/Misc/NEWS.d/next/Library/2024-01-19-12-05-22.gh-issue-114281.H5JQe4.rst deleted file mode 100644 index 36c54e8faf214c2..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-19-12-05-22.gh-issue-114281.H5JQe4.rst +++ /dev/null @@ -1,3 +0,0 @@ -Remove type hints from ``Lib/asyncio/staggered.py``. -The annotations in the `typeshed <https://github.com/python/typeshed>`__ -project should be used instead. diff --git a/Misc/NEWS.d/next/Library/2024-01-19-15-48-06.gh-issue-114328.hixxW3.rst b/Misc/NEWS.d/next/Library/2024-01-19-15-48-06.gh-issue-114328.hixxW3.rst deleted file mode 100644 index 42262c05fd1fbfb..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-19-15-48-06.gh-issue-114328.hixxW3.rst +++ /dev/null @@ -1,4 +0,0 @@ -The :func:`tty.setcbreak` and new :func:`tty.cfmakecbreak` no longer clears -the terminal input ICRLF flag. This fixes a regression introduced in 3.12 -that no longer matched how OSes define cbreak mode in their ``stty(1)`` -manual pages. diff --git a/Misc/NEWS.d/next/Library/2024-01-19-18-41-02.gh-issue-114321.yj_Xw3.rst b/Misc/NEWS.d/next/Library/2024-01-19-18-41-02.gh-issue-114321.yj_Xw3.rst deleted file mode 100644 index dc2934bd81a42ad..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-19-18-41-02.gh-issue-114321.yj_Xw3.rst +++ /dev/null @@ -1,2 +0,0 @@ -Expose more platform specific constants in the :mod:`fcntl` module on Linux, -macOS, FreeBSD and NetBSD. diff --git a/Misc/NEWS.d/next/Library/2024-01-21-16-32-55.gh-issue-114257.bCFld5.rst b/Misc/NEWS.d/next/Library/2024-01-21-16-32-55.gh-issue-114257.bCFld5.rst deleted file mode 100644 index 6f02ff9e62617d3..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-21-16-32-55.gh-issue-114257.bCFld5.rst +++ /dev/null @@ -1,2 +0,0 @@ -Dismiss the :exc:`FileNotFound` error in :func:`ctypes.util.find_library` and -just return ``None`` on Linux. diff --git a/Misc/NEWS.d/next/Library/2024-01-22-11-43-38.gh-issue-114423.6mMoPH.rst b/Misc/NEWS.d/next/Library/2024-01-22-11-43-38.gh-issue-114423.6mMoPH.rst deleted file mode 100644 index 7b77b73295d9483..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-22-11-43-38.gh-issue-114423.6mMoPH.rst +++ /dev/null @@ -1 +0,0 @@ -``_DummyThread`` entries in ``threading._active`` are now automatically removed when the related thread dies. diff --git a/Misc/NEWS.d/next/Library/2024-01-22-12-10-34.gh-issue-75128.4FGlRS.rst b/Misc/NEWS.d/next/Library/2024-01-22-12-10-34.gh-issue-75128.4FGlRS.rst deleted file mode 100644 index d875148e89b41b0..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-22-12-10-34.gh-issue-75128.4FGlRS.rst +++ /dev/null @@ -1,2 +0,0 @@ -Ignore an :exc:`OSError` in :meth:`asyncio.BaseEventLoop.create_server` when -IPv6 is available but the interface cannot actually support it. diff --git a/Misc/NEWS.d/next/Library/2024-01-23-11-04-21.gh-issue-113267.xe_Pxe.rst b/Misc/NEWS.d/next/Library/2024-01-23-11-04-21.gh-issue-113267.xe_Pxe.rst deleted file mode 100644 index ad8aaf9250f6d8e..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-23-11-04-21.gh-issue-113267.xe_Pxe.rst +++ /dev/null @@ -1,2 +0,0 @@ -Revert changes in :gh:`106584` which made calls of ``TestResult`` methods -``startTest()`` and ``stopTest()`` unbalanced. diff --git a/Misc/NEWS.d/next/Library/2024-01-23-13-03-22.gh-issue-100414.5kTdU5.rst b/Misc/NEWS.d/next/Library/2024-01-23-13-03-22.gh-issue-100414.5kTdU5.rst deleted file mode 100644 index 0f3b3bdd7c6d261..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-23-13-03-22.gh-issue-100414.5kTdU5.rst +++ /dev/null @@ -1,2 +0,0 @@ -Add :mod:`dbm.sqlite3` as a backend to :mod:`dbm`, and make it the new default :mod:`!dbm` backend. -Patch by Raymond Hettinger and Erlend E. Aasland. diff --git a/Misc/NEWS.d/next/Library/2024-01-23-14-11-49.gh-issue-114315.KeVdzl.rst b/Misc/NEWS.d/next/Library/2024-01-23-14-11-49.gh-issue-114315.KeVdzl.rst deleted file mode 100644 index a8a19fc525d0199..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-23-14-11-49.gh-issue-114315.KeVdzl.rst +++ /dev/null @@ -1,2 +0,0 @@ -Make :class:`threading.Lock` a real class, not a factory function. Add -``__new__`` to ``_thread.lock`` type. diff --git a/Misc/NEWS.d/next/Library/2024-01-23-21-20-40.gh-issue-114492.vKxl5o.rst b/Misc/NEWS.d/next/Library/2024-01-23-21-20-40.gh-issue-114492.vKxl5o.rst deleted file mode 100644 index 8df8299d0dffcd6..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-23-21-20-40.gh-issue-114492.vKxl5o.rst +++ /dev/null @@ -1,2 +0,0 @@ -Make the result of :func:`termios.tcgetattr` reproducible on Alpine Linux. -Previously it could leave a random garbage in some fields. diff --git a/Misc/NEWS.d/next/Library/2024-01-23-23-13-47.gh-issue-109653.KLBHmT.rst b/Misc/NEWS.d/next/Library/2024-01-23-23-13-47.gh-issue-109653.KLBHmT.rst deleted file mode 100644 index 76074df9c76fa61..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-23-23-13-47.gh-issue-109653.KLBHmT.rst +++ /dev/null @@ -1 +0,0 @@ -Reduce the import time of :mod:`threading` module by ~50%. Patch by Daniel Hollas. diff --git a/Misc/NEWS.d/next/Library/2024-01-24-17-25-18.gh-issue-69893.PQq5fR.rst b/Misc/NEWS.d/next/Library/2024-01-24-17-25-18.gh-issue-69893.PQq5fR.rst deleted file mode 100644 index 1ebf434c33187bf..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-24-17-25-18.gh-issue-69893.PQq5fR.rst +++ /dev/null @@ -1,2 +0,0 @@ -Add the :meth:`!close` method for the iterator returned by -:func:`xml.etree.ElementTree.iterparse`. diff --git a/Misc/NEWS.d/next/Library/2024-01-24-20-11-46.gh-issue-112451.7YrG4p.rst b/Misc/NEWS.d/next/Library/2024-01-24-20-11-46.gh-issue-112451.7YrG4p.rst deleted file mode 100644 index 126ca36a3b7cb1a..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-24-20-11-46.gh-issue-112451.7YrG4p.rst +++ /dev/null @@ -1,2 +0,0 @@ -Prohibit subclassing pure-Python :class:`datetime.timezone`. This is consistent -with C-extension implementation. Patch by Mariusz Felisiak. diff --git a/Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst b/Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst deleted file mode 100644 index 21d39df43e035b6..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst +++ /dev/null @@ -1,3 +0,0 @@ -Add *filter* keyword-only parameter to -:meth:`sqlite3.Connection.iterdump` for filtering database objects to dump. -Patch by Mariusz Felisiak. diff --git a/Misc/NEWS.d/next/Library/2024-01-25-19-22-17.gh-issue-83383.3GwO9v.rst b/Misc/NEWS.d/next/Library/2024-01-25-19-22-17.gh-issue-83383.3GwO9v.rst deleted file mode 100644 index e6336204dfa2369..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-25-19-22-17.gh-issue-83383.3GwO9v.rst +++ /dev/null @@ -1,5 +0,0 @@ -Synchronization of the :mod:`dbm.dumb` database is now no-op if there was no -modification since opening or last synchronization. -The directory file for a newly created empty :mod:`dbm.dumb` database is now -created immediately after opening instead of deferring this until -synchronizing or closing. diff --git a/Misc/NEWS.d/next/Library/2024-01-26-16-46-21.gh-issue-77749.NY_7TS.rst b/Misc/NEWS.d/next/Library/2024-01-26-16-46-21.gh-issue-77749.NY_7TS.rst deleted file mode 100644 index f1c99c09d2dfe1c..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-26-16-46-21.gh-issue-77749.NY_7TS.rst +++ /dev/null @@ -1,2 +0,0 @@ -:meth:`email.policy.EmailPolicy.fold` now always encodes non-ASCII characters -in headers if :attr:`~email.policy.EmailPolicy.utf8` is false. diff --git a/Misc/NEWS.d/next/Library/2024-01-27-20-11-24.gh-issue-113280.CZPQMf.rst b/Misc/NEWS.d/next/Library/2024-01-27-20-11-24.gh-issue-113280.CZPQMf.rst deleted file mode 100644 index 3dcdbcf0995616a..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-27-20-11-24.gh-issue-113280.CZPQMf.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix a leak of open socket in rare cases when error occurred in -:class:`ssl.SSLSocket` creation. diff --git a/Misc/NEWS.d/next/Library/2024-01-28-00-48-12.gh-issue-109653.vF4exe.rst b/Misc/NEWS.d/next/Library/2024-01-28-00-48-12.gh-issue-109653.vF4exe.rst deleted file mode 100644 index fb3382098853b30..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-28-00-48-12.gh-issue-109653.vF4exe.rst +++ /dev/null @@ -1 +0,0 @@ -Improve import time of :mod:`importlib.metadata` and :mod:`email.utils`. diff --git a/Misc/NEWS.d/next/Library/2024-01-28-18-38-18.gh-issue-70303._Lt_pj.rst b/Misc/NEWS.d/next/Library/2024-01-28-18-38-18.gh-issue-70303._Lt_pj.rst deleted file mode 100644 index dedda24b481241e..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-28-18-38-18.gh-issue-70303._Lt_pj.rst +++ /dev/null @@ -1,2 +0,0 @@ -Return both files and directories from :meth:`pathlib.Path.glob` if a -pattern ends with "``**``". Previously only directories were returned. diff --git a/Misc/NEWS.d/next/Library/2024-01-28-19-40-40.gh-issue-114678.kYKcJw.rst b/Misc/NEWS.d/next/Library/2024-01-28-19-40-40.gh-issue-114678.kYKcJw.rst deleted file mode 100644 index 2306af4a39dcf62..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-28-19-40-40.gh-issue-114678.kYKcJw.rst +++ /dev/null @@ -1,3 +0,0 @@ -Ensure that deprecation warning for 'N' specifier in :class:`~decimal.Decimal` -format is not raised for cases where 'N' appears in other places -in the format specifier. Based on patch by Stefan Krah. diff --git a/Misc/NEWS.d/next/Library/2024-01-30-15-34-08.gh-issue-110190.Z5PQQX.rst b/Misc/NEWS.d/next/Library/2024-01-30-15-34-08.gh-issue-110190.Z5PQQX.rst deleted file mode 100644 index af77e409963e04d..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-30-15-34-08.gh-issue-110190.Z5PQQX.rst +++ /dev/null @@ -1 +0,0 @@ -Fix ctypes structs with array on Windows ARM64 platform by setting ``MAX_STRUCT_SIZE`` to 32 in stgdict. Patch by Diego Russo diff --git a/Misc/NEWS.d/next/Library/2024-01-30-22-10-50.gh-issue-49766.yulJL_.rst b/Misc/NEWS.d/next/Library/2024-01-30-22-10-50.gh-issue-49766.yulJL_.rst deleted file mode 100644 index eaaa3ba1cb6f093..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-30-22-10-50.gh-issue-49766.yulJL_.rst +++ /dev/null @@ -1,8 +0,0 @@ -Fix :class:`~datetime.date`-:class:`~datetime.datetime` comparison. Now the -special comparison methods like ``__eq__`` and ``__lt__`` return -:data:`NotImplemented` if one of comparands is :class:`!date` and other is -:class:`!datetime` instead of ignoring the time part and the time zone or -forcefully return "not equal" or raise :exc:`TypeError`. It makes comparison -of :class:`!date` and :class:`!datetime` subclasses more symmetric and -allows to change the default behavior by overriding the special comparison -methods in subclasses. diff --git a/Misc/NEWS.d/next/Library/2024-01-31-20-07-11.gh-issue-109475.lmTb9S.rst b/Misc/NEWS.d/next/Library/2024-01-31-20-07-11.gh-issue-109475.lmTb9S.rst deleted file mode 100644 index 7582cb2bcd76298..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-31-20-07-11.gh-issue-109475.lmTb9S.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix support of explicit option value "--" in :mod:`argparse` (e.g. -``--option=--``). diff --git a/Misc/NEWS.d/next/Library/2024-02-01-10-19-11.gh-issue-114071.vkm2G_.rst b/Misc/NEWS.d/next/Library/2024-02-01-10-19-11.gh-issue-114071.vkm2G_.rst deleted file mode 100644 index 587ce4d21576371..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-01-10-19-11.gh-issue-114071.vkm2G_.rst +++ /dev/null @@ -1 +0,0 @@ -Support tuple subclasses using auto() for enum member value. diff --git a/Misc/NEWS.d/next/Library/2024-02-02-15-50-13.gh-issue-114894.DF-dSd.rst b/Misc/NEWS.d/next/Library/2024-02-02-15-50-13.gh-issue-114894.DF-dSd.rst deleted file mode 100644 index ec620f2aae3f037..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-02-15-50-13.gh-issue-114894.DF-dSd.rst +++ /dev/null @@ -1 +0,0 @@ -Add :meth:`array.array.clear`. diff --git a/Misc/NEWS.d/next/Library/2024-02-03-16-59-25.gh-issue-114959.dCfAG2.rst b/Misc/NEWS.d/next/Library/2024-02-03-16-59-25.gh-issue-114959.dCfAG2.rst deleted file mode 100644 index 5c6eaa7525e3b0c..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-03-16-59-25.gh-issue-114959.dCfAG2.rst +++ /dev/null @@ -1,2 +0,0 @@ -:mod:`tarfile` no longer ignores errors when trying to extract a directory on -top of a file. diff --git a/Misc/NEWS.d/next/Library/2024-02-03-17-54-17.gh-issue-114965.gHksCK.rst b/Misc/NEWS.d/next/Library/2024-02-03-17-54-17.gh-issue-114965.gHksCK.rst deleted file mode 100644 index d59ff991993792a..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-03-17-54-17.gh-issue-114965.gHksCK.rst +++ /dev/null @@ -1 +0,0 @@ -Update bundled pip to 24.0 diff --git a/Misc/NEWS.d/next/Library/2024-02-04-02-28-37.gh-issue-85984.NHZVTQ.rst b/Misc/NEWS.d/next/Library/2024-02-04-02-28-37.gh-issue-85984.NHZVTQ.rst deleted file mode 100644 index bfa7e676f923067..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-04-02-28-37.gh-issue-85984.NHZVTQ.rst +++ /dev/null @@ -1 +0,0 @@ -Added ``_POSIX_VDISABLE`` from C's ``<unistd.h>`` to :mod:`termios`. diff --git a/Misc/NEWS.d/next/Library/2024-02-04-13-17-33.gh-issue-114628.WJpqqS.rst b/Misc/NEWS.d/next/Library/2024-02-04-13-17-33.gh-issue-114628.WJpqqS.rst deleted file mode 100644 index 8138adc62c95f32..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-04-13-17-33.gh-issue-114628.WJpqqS.rst +++ /dev/null @@ -1,2 +0,0 @@ -When csv.Error is raised when handling TypeError, do not print the TypeError -traceback. diff --git a/Misc/NEWS.d/next/Library/2024-02-05-16-48-06.gh-issue-97928.JZCies.rst b/Misc/NEWS.d/next/Library/2024-02-05-16-48-06.gh-issue-97928.JZCies.rst deleted file mode 100644 index 24fed926a955133..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-05-16-48-06.gh-issue-97928.JZCies.rst +++ /dev/null @@ -1,5 +0,0 @@ -Partially revert the behavior of :meth:`tkinter.Text.count`. By default it -preserves the behavior of older Python versions, except that setting -``wantobjects`` to 0 no longer has effect. Add a new parameter *return_ints*: -specifying ``return_ints=True`` makes ``Text.count()`` always returning the -single count as an integer instead of a 1-tuple or ``None``. diff --git a/Misc/NEWS.d/next/Library/2024-02-06-03-55-46.gh-issue-115060.EkWRpP.rst b/Misc/NEWS.d/next/Library/2024-02-06-03-55-46.gh-issue-115060.EkWRpP.rst deleted file mode 100644 index b358eeb569626f8..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-06-03-55-46.gh-issue-115060.EkWRpP.rst +++ /dev/null @@ -1 +0,0 @@ -Speed up :meth:`pathlib.Path.glob` by removing redundant regex matching. diff --git a/Misc/NEWS.d/next/Library/2024-02-06-15-16-28.gh-issue-67837._JKa73.rst b/Misc/NEWS.d/next/Library/2024-02-06-15-16-28.gh-issue-67837._JKa73.rst deleted file mode 100644 index 340b65f18839427..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-06-15-16-28.gh-issue-67837._JKa73.rst +++ /dev/null @@ -1,2 +0,0 @@ -Avoid race conditions in the creation of directories during concurrent -extraction in :mod:`tarfile` and :mod:`zipfile`. diff --git a/Misc/NEWS.d/next/Library/2024-02-07-12-37-52.gh-issue-79382.Yz_5WB.rst b/Misc/NEWS.d/next/Library/2024-02-07-12-37-52.gh-issue-79382.Yz_5WB.rst deleted file mode 100644 index 5eb1888943186a5..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-07-12-37-52.gh-issue-79382.Yz_5WB.rst +++ /dev/null @@ -1,2 +0,0 @@ -Trailing ``**`` no longer allows to match files and non-existing paths in -recursive :func:`~glob.glob`. diff --git a/Misc/NEWS.d/next/Library/2024-02-08-13-26-14.gh-issue-115059.DqP9dr.rst b/Misc/NEWS.d/next/Library/2024-02-08-13-26-14.gh-issue-115059.DqP9dr.rst deleted file mode 100644 index 331baedd3b24c5c..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-08-13-26-14.gh-issue-115059.DqP9dr.rst +++ /dev/null @@ -1 +0,0 @@ -:meth:`io.BufferedRandom.read1` now flushes the underlying write buffer. diff --git a/Misc/NEWS.d/next/Library/2024-02-08-14-21-28.gh-issue-115133.ycl4ko.rst b/Misc/NEWS.d/next/Library/2024-02-08-14-21-28.gh-issue-115133.ycl4ko.rst deleted file mode 100644 index 6f1015235cc25d4..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-08-14-21-28.gh-issue-115133.ycl4ko.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix tests for :class:`~xml.etree.ElementTree.XMLPullParser` with Expat -2.6.0. diff --git a/Misc/NEWS.d/next/Library/2024-02-08-17-04-58.gh-issue-112903.SN_vUs.rst b/Misc/NEWS.d/next/Library/2024-02-08-17-04-58.gh-issue-112903.SN_vUs.rst deleted file mode 100644 index e27f5832553c136..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-08-17-04-58.gh-issue-112903.SN_vUs.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix "issubclass() arg 1 must be a class" errors in certain cases of multiple -inheritance with generic aliases (regression in early 3.13 alpha releases). diff --git a/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst b/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst deleted file mode 100644 index 3e6eef183ad5249..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst +++ /dev/null @@ -1,4 +0,0 @@ -Most exceptions are now ignored when attempting to set the ``__orig_class__`` -attribute on objects returned when calling :mod:`typing` generic aliases -(including generic aliases created using :data:`typing.Annotated`). -Previously only :exc:`AttributeError` was ignored. Patch by Dave Shawley. diff --git a/Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst b/Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst deleted file mode 100644 index 52668a9424a9765..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fix confused traceback when floordiv, mod, or divmod operations happens -between instances of :class:`fractions.Fraction` and :class:`complex`. - diff --git a/Misc/NEWS.d/next/Library/2024-02-11-20-23-36.gh-issue-114563.RzxNYT.rst b/Misc/NEWS.d/next/Library/2024-02-11-20-23-36.gh-issue-114563.RzxNYT.rst deleted file mode 100644 index 013b6db8e6dbd78..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-11-20-23-36.gh-issue-114563.RzxNYT.rst +++ /dev/null @@ -1,4 +0,0 @@ -Fix several :func:`format()` bugs when using the C implementation of :class:`~decimal.Decimal`: -* memory leak in some rare cases when using the ``z`` format option (coerce negative 0) -* incorrect output when applying the ``z`` format option to type ``F`` (fixed-point with capital ``NAN`` / ``INF``) -* incorrect output when applying the ``#`` format option (alternate form) diff --git a/Misc/NEWS.d/next/Library/2024-02-13-18-27-03.gh-issue-115392.gle5tp.rst b/Misc/NEWS.d/next/Library/2024-02-13-18-27-03.gh-issue-115392.gle5tp.rst deleted file mode 100644 index 1c3368968e4cf00..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-13-18-27-03.gh-issue-115392.gle5tp.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix a bug in :mod:`doctest` where incorrect line numbers would be -reported for decorated functions. diff --git a/Misc/NEWS.d/next/Security/2024-02-12-00-33-01.gh-issue-115243.e1oGX8.rst b/Misc/NEWS.d/next/Security/2024-02-12-00-33-01.gh-issue-115243.e1oGX8.rst deleted file mode 100644 index ae0e910c7d159c6..000000000000000 --- a/Misc/NEWS.d/next/Security/2024-02-12-00-33-01.gh-issue-115243.e1oGX8.rst +++ /dev/null @@ -1 +0,0 @@ -Fix possible crashes in :meth:`collections.deque.index` when the deque is concurrently modified. diff --git a/Misc/NEWS.d/next/Security/2024-02-13-15-14-39.gh-issue-115399.xT-scP.rst b/Misc/NEWS.d/next/Security/2024-02-13-15-14-39.gh-issue-115399.xT-scP.rst deleted file mode 100644 index e8163b6f29c1891..000000000000000 --- a/Misc/NEWS.d/next/Security/2024-02-13-15-14-39.gh-issue-115399.xT-scP.rst +++ /dev/null @@ -1 +0,0 @@ -Update bundled libexpat to 2.6.0 diff --git a/Misc/NEWS.d/next/Tests/2023-06-02-05-04-15.gh-issue-105089.KaZFtU.rst b/Misc/NEWS.d/next/Tests/2023-06-02-05-04-15.gh-issue-105089.KaZFtU.rst deleted file mode 100644 index d04ef435dd572d8..000000000000000 --- a/Misc/NEWS.d/next/Tests/2023-06-02-05-04-15.gh-issue-105089.KaZFtU.rst +++ /dev/null @@ -1,4 +0,0 @@ -Fix -``test.test_zipfile.test_core.TestWithDirectory.test_create_directory_with_write`` -test in AIX by doing a bitwise AND of 0xFFFF on mode , so that it will be in -sync with ``zinfo.external_attr`` diff --git a/Misc/NEWS.d/next/Tests/2024-02-02-13-18-55.gh-issue-114099.C_ycWg.rst b/Misc/NEWS.d/next/Tests/2024-02-02-13-18-55.gh-issue-114099.C_ycWg.rst deleted file mode 100644 index 487cd5062fc75b5..000000000000000 --- a/Misc/NEWS.d/next/Tests/2024-02-02-13-18-55.gh-issue-114099.C_ycWg.rst +++ /dev/null @@ -1 +0,0 @@ -Added test exclusions required to run the test suite on iOS. diff --git a/Misc/NEWS.d/next/Tools-Demos/2024-02-05-02-45-51.gh-issue-115015.rgtiDB.rst b/Misc/NEWS.d/next/Tools-Demos/2024-02-05-02-45-51.gh-issue-115015.rgtiDB.rst deleted file mode 100644 index d8739d28eb2b73c..000000000000000 --- a/Misc/NEWS.d/next/Tools-Demos/2024-02-05-02-45-51.gh-issue-115015.rgtiDB.rst +++ /dev/null @@ -1,5 +0,0 @@ -Fix a bug in Argument Clinic that generated incorrect code for methods with -no parameters that use the :ref:`METH_METHOD | METH_FASTCALL | METH_KEYWORDS -<METH_METHOD-METH_FASTCALL-METH_KEYWORDS>` calling convention. Only the -positional parameter count was checked; any keyword argument passed would be -silently accepted. diff --git a/Misc/NEWS.d/next/Tools-Demos/2024-02-05-19-00-32.gh-issue-109991.yJSEkw.rst b/Misc/NEWS.d/next/Tools-Demos/2024-02-05-19-00-32.gh-issue-109991.yJSEkw.rst deleted file mode 100644 index 4eb4d39629b9bcd..000000000000000 --- a/Misc/NEWS.d/next/Tools-Demos/2024-02-05-19-00-32.gh-issue-109991.yJSEkw.rst +++ /dev/null @@ -1,2 +0,0 @@ -Update GitHub CI workflows to use OpenSSL 3.0.13 and multissltests to use -1.1.1w, 3.0.13, 3.1.5, and 3.2.1. diff --git a/Misc/NEWS.d/next/Tools-Demos/2024-02-14-15-58-13.gh-issue-113516.TyIHWx.rst b/Misc/NEWS.d/next/Tools-Demos/2024-02-14-15-58-13.gh-issue-113516.TyIHWx.rst deleted file mode 100644 index 0dd1128e02de811..000000000000000 --- a/Misc/NEWS.d/next/Tools-Demos/2024-02-14-15-58-13.gh-issue-113516.TyIHWx.rst +++ /dev/null @@ -1 +0,0 @@ -Don't set ``LDSHARED`` when building for WASI. diff --git a/Misc/NEWS.d/next/Windows/2023-08-11-18-21-38.gh-issue-89240.dtSOLG.rst b/Misc/NEWS.d/next/Windows/2023-08-11-18-21-38.gh-issue-89240.dtSOLG.rst deleted file mode 100644 index 8ffe328b16598a0..000000000000000 --- a/Misc/NEWS.d/next/Windows/2023-08-11-18-21-38.gh-issue-89240.dtSOLG.rst +++ /dev/null @@ -1 +0,0 @@ -Allows :mod:`multiprocessing` to create pools of greater than 62 processes. diff --git a/Misc/NEWS.d/next/Windows/2023-12-19-22-32-28.gh-issue-112984.F7kFMl.rst b/Misc/NEWS.d/next/Windows/2023-12-19-22-32-28.gh-issue-112984.F7kFMl.rst deleted file mode 100644 index 429cd5bc923e09a..000000000000000 --- a/Misc/NEWS.d/next/Windows/2023-12-19-22-32-28.gh-issue-112984.F7kFMl.rst +++ /dev/null @@ -1 +0,0 @@ -Adds free-threaded binaries to Windows installer as an optional component. diff --git a/Misc/NEWS.d/next/Windows/2024-01-23-00-05-05.gh-issue-100107.lkbP_Q.rst b/Misc/NEWS.d/next/Windows/2024-01-23-00-05-05.gh-issue-100107.lkbP_Q.rst deleted file mode 100644 index 388d61a2b3bd6db..000000000000000 --- a/Misc/NEWS.d/next/Windows/2024-01-23-00-05-05.gh-issue-100107.lkbP_Q.rst +++ /dev/null @@ -1 +0,0 @@ -The ``py.exe`` launcher will no longer attempt to run the Microsoft Store redirector when launching a script containing a ``/usr/bin/env`` shebang diff --git a/Misc/NEWS.d/next/Windows/2024-02-01-14-35-05.gh-issue-111239.SO7SUF.rst b/Misc/NEWS.d/next/Windows/2024-02-01-14-35-05.gh-issue-111239.SO7SUF.rst deleted file mode 100644 index ea82c3b941f8026..000000000000000 --- a/Misc/NEWS.d/next/Windows/2024-02-01-14-35-05.gh-issue-111239.SO7SUF.rst +++ /dev/null @@ -1 +0,0 @@ -Update Windows builds to use zlib v1.3.1. diff --git a/Misc/NEWS.d/next/Windows/2024-02-05-16-53-12.gh-issue-109991.YqjnDz.rst b/Misc/NEWS.d/next/Windows/2024-02-05-16-53-12.gh-issue-109991.YqjnDz.rst deleted file mode 100644 index d9923c35c2726e6..000000000000000 --- a/Misc/NEWS.d/next/Windows/2024-02-05-16-53-12.gh-issue-109991.YqjnDz.rst +++ /dev/null @@ -1 +0,0 @@ -Update Windows build to use OpenSSL 3.0.13. diff --git a/Misc/NEWS.d/next/Windows/2024-02-06-09-05-13.gh-issue-115009.ShMjZs.rst b/Misc/NEWS.d/next/Windows/2024-02-06-09-05-13.gh-issue-115009.ShMjZs.rst deleted file mode 100644 index 5bdb6963a243118..000000000000000 --- a/Misc/NEWS.d/next/Windows/2024-02-06-09-05-13.gh-issue-115009.ShMjZs.rst +++ /dev/null @@ -1 +0,0 @@ -Update Windows installer to use SQLite 3.45.1. diff --git a/Misc/NEWS.d/next/Windows/2024-02-08-21-37-22.gh-issue-115049.X1ObpJ.rst b/Misc/NEWS.d/next/Windows/2024-02-08-21-37-22.gh-issue-115049.X1ObpJ.rst deleted file mode 100644 index a679391857dcb32..000000000000000 --- a/Misc/NEWS.d/next/Windows/2024-02-08-21-37-22.gh-issue-115049.X1ObpJ.rst +++ /dev/null @@ -1 +0,0 @@ -Fixes ``py.exe`` launcher failing when run as users without user profiles. diff --git a/Misc/NEWS.d/next/macOS/2022-11-18-10-05-35.gh-issue-87804.rhlDmD.rst b/Misc/NEWS.d/next/macOS/2022-11-18-10-05-35.gh-issue-87804.rhlDmD.rst deleted file mode 100644 index e6554d5c9f1e1ec..000000000000000 --- a/Misc/NEWS.d/next/macOS/2022-11-18-10-05-35.gh-issue-87804.rhlDmD.rst +++ /dev/null @@ -1 +0,0 @@ -On macOS the result of ``os.statvfs`` and ``os.fstatvfs`` now correctly report the size of very large disks, in previous versions the reported number of blocks was wrong for disks with at least 2**32 blocks. diff --git a/Misc/NEWS.d/next/macOS/2024-01-23-11-35-26.gh-issue-114490.FrQOQ0.rst b/Misc/NEWS.d/next/macOS/2024-01-23-11-35-26.gh-issue-114490.FrQOQ0.rst deleted file mode 100644 index abd296f86085180..000000000000000 --- a/Misc/NEWS.d/next/macOS/2024-01-23-11-35-26.gh-issue-114490.FrQOQ0.rst +++ /dev/null @@ -1 +0,0 @@ -Add Mach-O linkage support for :func:`platform.architecture()`. diff --git a/Misc/NEWS.d/next/macOS/2024-02-05-18-30-27.gh-issue-109991.tun6Yu.rst b/Misc/NEWS.d/next/macOS/2024-02-05-18-30-27.gh-issue-109991.tun6Yu.rst deleted file mode 100644 index 79b45e7d51da3f5..000000000000000 --- a/Misc/NEWS.d/next/macOS/2024-02-05-18-30-27.gh-issue-109991.tun6Yu.rst +++ /dev/null @@ -1 +0,0 @@ -Update macOS installer to use OpenSSL 3.0.13. diff --git a/Misc/NEWS.d/next/macOS/2024-02-06-09-01-10.gh-issue-115009.ysau7e.rst b/Misc/NEWS.d/next/macOS/2024-02-06-09-01-10.gh-issue-115009.ysau7e.rst deleted file mode 100644 index 47ec488c3cced2d..000000000000000 --- a/Misc/NEWS.d/next/macOS/2024-02-06-09-01-10.gh-issue-115009.ysau7e.rst +++ /dev/null @@ -1 +0,0 @@ -Update macOS installer to use SQLite 3.45.1. diff --git a/README.rst b/README.rst index 1145fd437558406..fd3e6dd1771ae17 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -This is Python version 3.13.0 alpha 3 +This is Python version 3.13.0 alpha 4 ===================================== .. image:: https://github.com/python/cpython/workflows/Tests/badge.svg From 732faf17a618d65d264ffe775fa6db4508e3d9ff Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:20:19 +0000 Subject: [PATCH 292/507] gh-115347: avoid emitting redundant NOP for the docstring with -OO (#115494) --- Lib/test/test_compile.py | 26 +++++++++++++ ...-02-14-23-50-43.gh-issue-115347.VkHvQC.rst | 2 + Python/compile.c | 38 ++++++++++--------- 3 files changed, 48 insertions(+), 18 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-14-23-50-43.gh-issue-115347.VkHvQC.rst diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index ebb479f2de7c633..4d8647be6a14f15 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -1,4 +1,6 @@ +import contextlib import dis +import io import math import os import unittest @@ -812,6 +814,30 @@ def unused_code_at_end(): 'RETURN_CONST', list(dis.get_instructions(unused_code_at_end))[-1].opname) + @support.cpython_only + def test_docstring_omitted(self): + # See gh-115347 + src = textwrap.dedent(""" + def f(): + "docstring1" + def h(): + "docstring2" + return 42 + + class C: + "docstring3" + pass + + return h + """) + for opt in [-1, 0, 1, 2]: + with self.subTest(opt=opt): + code = compile(src, "<test>", "exec", optimize=opt) + output = io.StringIO() + with contextlib.redirect_stdout(output): + dis.dis(code) + self.assertNotIn('NOP' , output.getvalue()) + def test_dont_merge_constants(self): # Issue #25843: compile() must not merge constants which are equal # but have a different type. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-14-23-50-43.gh-issue-115347.VkHvQC.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-14-23-50-43.gh-issue-115347.VkHvQC.rst new file mode 100644 index 000000000000000..16f357a944ed7a5 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-14-23-50-43.gh-issue-115347.VkHvQC.rst @@ -0,0 +1,2 @@ +Fix bug where docstring was replaced by a redundant NOP when Python is run +with ``-OO``. diff --git a/Python/compile.c b/Python/compile.c index 15e5cf38a37b97a..f95e3cd8a79fc74 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1692,16 +1692,13 @@ compiler_unwind_fblock_stack(struct compiler *c, location *ploc, static int compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) { - int i = 0; - stmt_ty st; - PyObject *docstring; /* Set current line number to the line number of first statement. This way line number for SETUP_ANNOTATIONS will always coincide with the line number of first "real" statement in module. If body is empty, then lineno will be set later in optimize_and_assemble. */ if (c->u->u_scope_type == COMPILER_SCOPE_MODULE && asdl_seq_LEN(stmts)) { - st = (stmt_ty)asdl_seq_GET(stmts, 0); + stmt_ty st = (stmt_ty)asdl_seq_GET(stmts, 0); loc = LOC(st); } /* Every annotated class and module should have __annotations__. */ @@ -1711,16 +1708,17 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) if (!asdl_seq_LEN(stmts)) { return SUCCESS; } - /* if not -OO mode, set docstring */ - if (c->c_optimize < 2) { - docstring = _PyAST_GetDocString(stmts); - if (docstring) { + Py_ssize_t first_instr = 0; + PyObject *docstring = _PyAST_GetDocString(stmts); + if (docstring) { + first_instr = 1; + /* if not -OO mode, set docstring */ + if (c->c_optimize < 2) { PyObject *cleandoc = _PyCompile_CleanDoc(docstring); if (cleandoc == NULL) { return ERROR; } - i = 1; - st = (stmt_ty)asdl_seq_GET(stmts, 0); + stmt_ty st = (stmt_ty)asdl_seq_GET(stmts, 0); assert(st->kind == Expr_kind); location loc = LOC(st->v.Expr.value); ADDOP_LOAD_CONST(c, loc, cleandoc); @@ -1728,7 +1726,7 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) RETURN_IF_ERROR(compiler_nameop(c, NO_LOCATION, &_Py_ID(__doc__), Store)); } } - for (; i < asdl_seq_LEN(stmts); i++) { + for (Py_ssize_t i = first_instr; i < asdl_seq_LEN(stmts); i++) { VISIT(c, stmt, (stmt_ty)asdl_seq_GET(stmts, i)); } return SUCCESS; @@ -2239,7 +2237,6 @@ static int compiler_function_body(struct compiler *c, stmt_ty s, int is_async, Py_ssize_t funcflags, int firstlineno) { - PyObject *docstring = NULL; arguments_ty args; identifier name; asdl_stmt_seq *body; @@ -2266,28 +2263,33 @@ compiler_function_body(struct compiler *c, stmt_ty s, int is_async, Py_ssize_t f RETURN_IF_ERROR( compiler_enter_scope(c, name, scope_type, (void *)s, firstlineno)); - /* if not -OO mode, add docstring */ - if (c->c_optimize < 2) { - docstring = _PyAST_GetDocString(body); - if (docstring) { + Py_ssize_t first_instr = 0; + PyObject *docstring = _PyAST_GetDocString(body); + if (docstring) { + first_instr = 1; + /* if not -OO mode, add docstring */ + if (c->c_optimize < 2) { docstring = _PyCompile_CleanDoc(docstring); if (docstring == NULL) { compiler_exit_scope(c); return ERROR; } } + else { + docstring = NULL; + } } if (compiler_add_const(c->c_const_cache, c->u, docstring ? docstring : Py_None) < 0) { Py_XDECREF(docstring); compiler_exit_scope(c); return ERROR; } - Py_XDECREF(docstring); + Py_CLEAR(docstring); c->u->u_metadata.u_argcount = asdl_seq_LEN(args->args); c->u->u_metadata.u_posonlyargcount = asdl_seq_LEN(args->posonlyargs); c->u->u_metadata.u_kwonlyargcount = asdl_seq_LEN(args->kwonlyargs); - for (Py_ssize_t i = docstring ? 1 : 0; i < asdl_seq_LEN(body); i++) { + for (Py_ssize_t i = first_instr; i < asdl_seq_LEN(body); i++) { VISIT_IN_SCOPE(c, stmt, (stmt_ty)asdl_seq_GET(body, i)); } if (c->u->u_ste->ste_coroutine || c->u->u_ste->ste_generator) { From 94f1334e52fbc1550feba4f433b16589d55255b9 Mon Sep 17 00:00:00 2001 From: Victor Stinner <vstinner@python.org> Date: Thu, 15 Feb 2024 15:29:42 +0100 Subject: [PATCH 293/507] gh-115124: Use _PyObject_ASSERT() in gc.c (#115125) Replace assert() with _PyObject_ASSERT() in gc.c to dump the object when an assertion fails. --- Python/gc.c | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/Python/gc.c b/Python/gc.c index 466467602915264..c6831f4c74bcac8 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -394,16 +394,17 @@ update_refs(PyGC_Head *containers) while (gc != containers) { next = GC_NEXT(gc); + PyObject *op = FROM_GC(gc); /* Move any object that might have become immortal to the * permanent generation as the reference count is not accurately * reflecting the actual number of live references to this object */ - if (_Py_IsImmortal(FROM_GC(gc))) { + if (_Py_IsImmortal(op)) { gc_list_move(gc, &get_gc_state()->permanent_generation.head); gc = next; continue; } - gc_reset_refs(gc, Py_REFCNT(FROM_GC(gc))); + gc_reset_refs(gc, Py_REFCNT(op)); /* Python's cyclic gc should never see an incoming refcount * of 0: if something decref'ed to 0, it should have been * deallocated immediately at that time. @@ -422,7 +423,7 @@ update_refs(PyGC_Head *containers) * so serious that maybe this should be a release-build * check instead of an assert? */ - _PyObject_ASSERT(FROM_GC(gc), gc_get_refs(gc) != 0); + _PyObject_ASSERT(op, gc_get_refs(gc) != 0); gc = next; } } @@ -488,7 +489,7 @@ visit_reachable(PyObject *op, void *arg) } // It would be a logic error elsewhere if the collecting flag were set on // an untracked object. - assert(gc->_gc_next != 0); + _PyObject_ASSERT(op, gc->_gc_next != 0); if (gc->_gc_next & NEXT_MASK_UNREACHABLE) { /* This had gc_refs = 0 when move_unreachable got @@ -660,7 +661,9 @@ static void move_legacy_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers) { PyGC_Head *gc, *next; - assert((unreachable->_gc_next & NEXT_MASK_UNREACHABLE) == 0); + _PyObject_ASSERT( + FROM_GC(unreachable), + (unreachable->_gc_next & NEXT_MASK_UNREACHABLE) == 0); /* March over unreachable. Move objects with finalizers into * `finalizers`. @@ -683,10 +686,14 @@ static inline void clear_unreachable_mask(PyGC_Head *unreachable) { /* Check that the list head does not have the unreachable bit set */ - assert(((uintptr_t)unreachable & NEXT_MASK_UNREACHABLE) == 0); + _PyObject_ASSERT( + FROM_GC(unreachable), + ((uintptr_t)unreachable & NEXT_MASK_UNREACHABLE) == 0); + _PyObject_ASSERT( + FROM_GC(unreachable), + (unreachable->_gc_next & NEXT_MASK_UNREACHABLE) == 0); PyGC_Head *gc, *next; - assert((unreachable->_gc_next & NEXT_MASK_UNREACHABLE) == 0); for (gc = GC_NEXT(unreachable); gc != unreachable; gc = next) { _PyObject_ASSERT((PyObject*)FROM_GC(gc), gc->_gc_next & NEXT_MASK_UNREACHABLE); gc->_gc_next &= ~NEXT_MASK_UNREACHABLE; @@ -840,7 +847,7 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old) */ if (gc_is_collecting(AS_GC((PyObject *)wr))) { /* it should already have been cleared above */ - assert(wr->wr_object == Py_None); + _PyObject_ASSERT((PyObject*)wr, wr->wr_object == Py_None); continue; } @@ -851,9 +858,8 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old) /* Move wr to wrcb_to_call, for the next pass. */ wrasgc = AS_GC((PyObject *)wr); - assert(wrasgc != next); /* wrasgc is reachable, but - next isn't, so they can't - be the same */ + // wrasgc is reachable, but next isn't, so they can't be the same + _PyObject_ASSERT((PyObject *)wr, wrasgc != next); gc_list_move(wrasgc, &wrcb_to_call); } } @@ -1773,13 +1779,14 @@ _Py_ScheduleGC(PyInterpreterState *interp) void _PyObject_GC_Link(PyObject *op) { - PyGC_Head *g = AS_GC(op); - assert(((uintptr_t)g & (sizeof(uintptr_t)-1)) == 0); // g must be correctly aligned + PyGC_Head *gc = AS_GC(op); + // gc must be correctly aligned + _PyObject_ASSERT(op, ((uintptr_t)gc & (sizeof(uintptr_t)-1)) == 0); PyThreadState *tstate = _PyThreadState_GET(); GCState *gcstate = &tstate->interp->gc; - g->_gc_next = 0; - g->_gc_prev = 0; + gc->_gc_next = 0; + gc->_gc_prev = 0; gcstate->generations[0].count++; /* number of allocated GC objects */ if (gcstate->generations[0].count > gcstate->generations[0].threshold && gcstate->enabled && From 3a9e67a9fdb4fad13bf42df6eb91126ab2ef45a1 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:32:21 +0000 Subject: [PATCH 294/507] gh-115376: fix segfault in _testinternalcapi.compiler_codegen on bad input (#115379) --- Lib/test/test_compiler_codegen.py | 7 +++- ...-02-12-22-35-01.gh-issue-115376.n9vubZ.rst | 1 + Python/compile.c | 42 ++++++++++++------- 3 files changed, 35 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Tests/2024-02-12-22-35-01.gh-issue-115376.n9vubZ.rst diff --git a/Lib/test/test_compiler_codegen.py b/Lib/test/test_compiler_codegen.py index dbeadd9ca47c63d..166294a40c1cb7b 100644 --- a/Lib/test/test_compiler_codegen.py +++ b/Lib/test/test_compiler_codegen.py @@ -8,7 +8,7 @@ class IsolatedCodeGenTests(CodegenTestCase): def codegen_test(self, snippet, expected_insts): import ast - a = ast.parse(snippet, "my_file.py", "exec"); + a = ast.parse(snippet, "my_file.py", "exec") insts = self.generate_code(a) self.assertInstructionsMatch(insts, expected_insts) @@ -54,3 +54,8 @@ def test_for_loop(self): ('RETURN_VALUE', None), ] self.codegen_test(snippet, expected) + + def test_syntax_error__return_not_in_function(self): + snippet = "return 42" + with self.assertRaisesRegex(SyntaxError, "'return' outside function"): + self.codegen_test(snippet, None) diff --git a/Misc/NEWS.d/next/Tests/2024-02-12-22-35-01.gh-issue-115376.n9vubZ.rst b/Misc/NEWS.d/next/Tests/2024-02-12-22-35-01.gh-issue-115376.n9vubZ.rst new file mode 100644 index 000000000000000..e09d78a9c4b189d --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-02-12-22-35-01.gh-issue-115376.n9vubZ.rst @@ -0,0 +1 @@ +Fix segfault in ``_testinternalcapi.compiler_codegen`` on bad input. diff --git a/Python/compile.c b/Python/compile.c index f95e3cd8a79fc74..d857239690e7b54 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1735,16 +1735,10 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) static int compiler_codegen(struct compiler *c, mod_ty mod) { - _Py_DECLARE_STR(anon_module, "<module>"); - RETURN_IF_ERROR( - compiler_enter_scope(c, &_Py_STR(anon_module), COMPILER_SCOPE_MODULE, - mod, 1)); - location loc = LOCATION(1, 1, 0, 0); switch (mod->kind) { case Module_kind: if (compiler_body(c, loc, mod->v.Module.body) < 0) { - compiler_exit_scope(c); return ERROR; } break; @@ -1753,10 +1747,10 @@ compiler_codegen(struct compiler *c, mod_ty mod) ADDOP(c, loc, SETUP_ANNOTATIONS); } c->c_interactive = 1; - VISIT_SEQ_IN_SCOPE(c, stmt, mod->v.Interactive.body); + VISIT_SEQ(c, stmt, mod->v.Interactive.body); break; case Expression_kind: - VISIT_IN_SCOPE(c, expr, mod->v.Expression.body); + VISIT(c, expr, mod->v.Expression.body); break; default: PyErr_Format(PyExc_SystemError, @@ -1767,14 +1761,29 @@ compiler_codegen(struct compiler *c, mod_ty mod) return SUCCESS; } +static int +compiler_enter_anonymous_scope(struct compiler* c, mod_ty mod) +{ + _Py_DECLARE_STR(anon_module, "<module>"); + RETURN_IF_ERROR( + compiler_enter_scope(c, &_Py_STR(anon_module), COMPILER_SCOPE_MODULE, + mod, 1)); + return SUCCESS; +} + static PyCodeObject * compiler_mod(struct compiler *c, mod_ty mod) { + PyCodeObject *co = NULL; int addNone = mod->kind != Expression_kind; - if (compiler_codegen(c, mod) < 0) { + if (compiler_enter_anonymous_scope(c, mod) < 0) { return NULL; } - PyCodeObject *co = optimize_and_assemble(c, addNone); + if (compiler_codegen(c, mod) < 0) { + goto finally; + } + co = optimize_and_assemble(c, addNone); +finally: compiler_exit_scope(c); return co; } @@ -7920,15 +7929,20 @@ _PyCompile_CodeGen(PyObject *ast, PyObject *filename, PyCompilerFlags *pflags, return NULL; } + metadata = PyDict_New(); + if (metadata == NULL) { + return NULL; + } + + if (compiler_enter_anonymous_scope(c, mod) < 0) { + return NULL; + } if (compiler_codegen(c, mod) < 0) { goto finally; } _PyCompile_CodeUnitMetadata *umd = &c->u->u_metadata; - metadata = PyDict_New(); - if (metadata == NULL) { - goto finally; - } + #define SET_MATADATA_ITEM(key, value) \ if (value != NULL) { \ if (PyDict_SetItemString(metadata, key, value) < 0) goto finally; \ From f42e112fd86edb5507a38a2eb850d0ebc6bc27a2 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:32:52 +0000 Subject: [PATCH 295/507] gh-115420: Fix translation of exception hander targets by _testinternalcapi.optimize_cfg. (#115425) --- Lib/test/test_peepholer.py | 16 ++++++++++++++++ ...024-02-13-18-24-04.gh-issue-115420.-dlzfI.rst | 2 ++ Python/flowgraph.c | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Tests/2024-02-13-18-24-04.gh-issue-115420.-dlzfI.rst diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 2ea186c85c8823e..dffedd0b1fc4767 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -1065,6 +1065,22 @@ def test_conditional_jump_backward_const_condition(self): ] self.cfg_optimization_test(insts, expected_insts, consts=list(range(5))) + def test_except_handler_label(self): + insts = [ + ('SETUP_FINALLY', handler := self.Label(), 10), + ('POP_BLOCK', 0, -1), + ('RETURN_CONST', 1, 11), + handler, + ('RETURN_CONST', 2, 12), + ] + expected_insts = [ + ('SETUP_FINALLY', handler := self.Label(), 10), + ('RETURN_CONST', 1, 11), + handler, + ('RETURN_CONST', 2, 12), + ] + self.cfg_optimization_test(insts, expected_insts, consts=list(range(5))) + def test_no_unsafe_static_swap(self): # We can't change order of two stores to the same location insts = [ diff --git a/Misc/NEWS.d/next/Tests/2024-02-13-18-24-04.gh-issue-115420.-dlzfI.rst b/Misc/NEWS.d/next/Tests/2024-02-13-18-24-04.gh-issue-115420.-dlzfI.rst new file mode 100644 index 000000000000000..1442ada3490fa09 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-02-13-18-24-04.gh-issue-115420.-dlzfI.rst @@ -0,0 +1,2 @@ +Fix translation of exception hander targets by +``_testinternalcapi.optimize_cfg``. diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 1a648edf0880c02..4d9ba9eceb86373 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -2729,7 +2729,7 @@ _PyCfg_ToInstructionSequence(cfg_builder *g, _PyCompile_InstructionSequence *seq RETURN_IF_ERROR(_PyCompile_InstructionSequence_UseLabel(seq, b->b_label.id)); for (int i = 0; i < b->b_iused; i++) { cfg_instr *instr = &b->b_instr[i]; - if (OPCODE_HAS_JUMP(instr->i_opcode)) { + if (OPCODE_HAS_JUMP(instr->i_opcode) || is_block_push(instr)) { instr->i_oparg = instr->i_target->b_label.id; } RETURN_IF_ERROR( From 298bcdc185d1a9709271e61a4cc529d33483add4 Mon Sep 17 00:00:00 2001 From: monkeyman192 <monkey_man_192@yahoo.com.au> Date: Fri, 16 Feb 2024 01:40:20 +1100 Subject: [PATCH 296/507] gh-112433: Add optional _align_ attribute to ctypes.Structure (GH-113790) --- Doc/library/ctypes.rst | 10 + .../pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + .../internal/pycore_runtime_init_generated.h | 1 + .../internal/pycore_unicodeobject_generated.h | 3 + .../test_ctypes/test_aligned_structures.py | 286 ++++++++++++++++++ ...-01-28-02-46-12.gh-issue-112433.FUX-nT.rst | 1 + Modules/_ctypes/stgdict.c | 26 +- 8 files changed, 328 insertions(+), 1 deletion(-) create mode 100644 Lib/test/test_ctypes/test_aligned_structures.py create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-28-02-46-12.gh-issue-112433.FUX-nT.rst diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index ef3a9a0f5898af4..73779547b35a1f2 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -670,6 +670,10 @@ compiler does it. It is possible to override this behavior by specifying a :attr:`~Structure._pack_` class attribute in the subclass definition. This must be set to a positive integer and specifies the maximum alignment for the fields. This is what ``#pragma pack(n)`` also does in MSVC. +It is also possible to set a minimum alignment for how the subclass itself is packed in the +same way ``#pragma align(n)`` works in MSVC. +This can be achieved by specifying a ::attr:`~Structure._align_` class attribute +in the subclass definition. :mod:`ctypes` uses the native byte order for Structures and Unions. To build structures with non-native byte order, you can use one of the @@ -2534,6 +2538,12 @@ fields, or any other data types containing pointer type fields. Setting this attribute to 0 is the same as not setting it at all. + .. attribute:: _align_ + + An optional small integer that allows overriding the alignment of + the structure when being packed or unpacked to/from memory. + Setting this attribute to 0 is the same as not setting it at all. + .. attribute:: _anonymous_ An optional sequence that lists the names of unnamed (anonymous) fields. diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 11755210d65432d..3253b5271a9b7c8 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -742,6 +742,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_abc_impl)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_abstract_)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_active)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_align_)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_annotation)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_anonymous_)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_argtypes_)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 576ac703ca15081..8780f7ef9491651 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -231,6 +231,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(_abc_impl) STRUCT_FOR_ID(_abstract_) STRUCT_FOR_ID(_active) + STRUCT_FOR_ID(_align_) STRUCT_FOR_ID(_annotation) STRUCT_FOR_ID(_anonymous_) STRUCT_FOR_ID(_argtypes_) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index e682c97e7c0248d..a9d514856dab1ff 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -740,6 +740,7 @@ extern "C" { INIT_ID(_abc_impl), \ INIT_ID(_abstract_), \ INIT_ID(_active), \ + INIT_ID(_align_), \ INIT_ID(_annotation), \ INIT_ID(_anonymous_), \ INIT_ID(_argtypes_), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 739af0e73c23ff8..f3b064e2a2cb258 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -534,6 +534,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(_active); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(_align_); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(_annotation); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Lib/test/test_ctypes/test_aligned_structures.py b/Lib/test/test_ctypes/test_aligned_structures.py new file mode 100644 index 000000000000000..a208fb9a00966ad --- /dev/null +++ b/Lib/test/test_ctypes/test_aligned_structures.py @@ -0,0 +1,286 @@ +from ctypes import ( + c_char, c_uint32, c_uint16, c_ubyte, c_byte, alignment, sizeof, + BigEndianStructure, LittleEndianStructure, + BigEndianUnion, LittleEndianUnion, +) +import struct +import unittest + + +class TestAlignedStructures(unittest.TestCase): + def test_aligned_string(self): + for base, e in ( + (LittleEndianStructure, "<"), + (BigEndianStructure, ">"), + ): + data = bytearray(struct.pack(f"{e}i12x16s", 7, b"hello world!")) + class Aligned(base): + _align_ = 16 + _fields_ = [ + ('value', c_char * 12) + ] + + class Main(base): + _fields_ = [ + ('first', c_uint32), + ('string', Aligned), + ] + + main = Main.from_buffer(data) + self.assertEqual(main.first, 7) + self.assertEqual(main.string.value, b'hello world!') + self.assertEqual(bytes(main.string), b'hello world!\0\0\0\0') + self.assertEqual(Main.string.offset, 16) + self.assertEqual(Main.string.size, 16) + self.assertEqual(alignment(main.string), 16) + self.assertEqual(alignment(main), 16) + + def test_aligned_structures(self): + for base, data in ( + (LittleEndianStructure, bytearray(b"\1\0\0\0\1\0\0\0\7\0\0\0")), + (BigEndianStructure, bytearray(b"\1\0\0\0\1\0\0\0\7\0\0\0")), + ): + class SomeBools(base): + _align_ = 4 + _fields_ = [ + ("bool1", c_ubyte), + ("bool2", c_ubyte), + ] + class Main(base): + _fields_ = [ + ("x", c_ubyte), + ("y", SomeBools), + ("z", c_ubyte), + ] + + main = Main.from_buffer(data) + self.assertEqual(alignment(SomeBools), 4) + self.assertEqual(alignment(main), 4) + self.assertEqual(alignment(main.y), 4) + self.assertEqual(Main.x.size, 1) + self.assertEqual(Main.y.offset, 4) + self.assertEqual(Main.y.size, 4) + self.assertEqual(main.y.bool1, True) + self.assertEqual(main.y.bool2, False) + self.assertEqual(Main.z.offset, 8) + self.assertEqual(main.z, 7) + + def test_oversized_structure(self): + data = bytearray(b"\0" * 8) + for base in (LittleEndianStructure, BigEndianStructure): + class SomeBoolsTooBig(base): + _align_ = 8 + _fields_ = [ + ("bool1", c_ubyte), + ("bool2", c_ubyte), + ("bool3", c_ubyte), + ] + class Main(base): + _fields_ = [ + ("y", SomeBoolsTooBig), + ("z", c_uint32), + ] + with self.assertRaises(ValueError) as ctx: + Main.from_buffer(data) + self.assertEqual( + ctx.exception.args[0], + 'Buffer size too small (4 instead of at least 8 bytes)' + ) + + def test_aligned_subclasses(self): + for base, e in ( + (LittleEndianStructure, "<"), + (BigEndianStructure, ">"), + ): + data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4)) + class UnalignedSub(base): + x: c_uint32 + _fields_ = [ + ("x", c_uint32), + ] + + class AlignedStruct(UnalignedSub): + _align_ = 8 + _fields_ = [ + ("y", c_uint32), + ] + + class Main(base): + _fields_ = [ + ("a", c_uint32), + ("b", AlignedStruct) + ] + + main = Main.from_buffer(data) + self.assertEqual(alignment(main.b), 8) + self.assertEqual(alignment(main), 8) + self.assertEqual(sizeof(main.b), 8) + self.assertEqual(sizeof(main), 16) + self.assertEqual(main.a, 1) + self.assertEqual(main.b.x, 3) + self.assertEqual(main.b.y, 4) + self.assertEqual(Main.b.offset, 8) + self.assertEqual(Main.b.size, 8) + + def test_aligned_union(self): + for sbase, ubase, e in ( + (LittleEndianStructure, LittleEndianUnion, "<"), + (BigEndianStructure, BigEndianUnion, ">"), + ): + data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4)) + class AlignedUnion(ubase): + _align_ = 8 + _fields_ = [ + ("a", c_uint32), + ("b", c_ubyte * 7), + ] + + class Main(sbase): + _fields_ = [ + ("first", c_uint32), + ("union", AlignedUnion), + ] + + main = Main.from_buffer(data) + self.assertEqual(main.first, 1) + self.assertEqual(main.union.a, 3) + self.assertEqual(bytes(main.union.b), data[8:-1]) + self.assertEqual(Main.union.offset, 8) + self.assertEqual(Main.union.size, 8) + self.assertEqual(alignment(main.union), 8) + self.assertEqual(alignment(main), 8) + + def test_aligned_struct_in_union(self): + for sbase, ubase, e in ( + (LittleEndianStructure, LittleEndianUnion, "<"), + (BigEndianStructure, BigEndianUnion, ">"), + ): + data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4)) + class Sub(sbase): + _align_ = 8 + _fields_ = [ + ("x", c_uint32), + ("y", c_uint32), + ] + + class MainUnion(ubase): + _fields_ = [ + ("a", c_uint32), + ("b", Sub), + ] + + class Main(sbase): + _fields_ = [ + ("first", c_uint32), + ("union", MainUnion), + ] + + main = Main.from_buffer(data) + self.assertEqual(Main.first.size, 4) + self.assertEqual(alignment(main.union), 8) + self.assertEqual(alignment(main), 8) + self.assertEqual(Main.union.offset, 8) + self.assertEqual(Main.union.size, 8) + self.assertEqual(main.first, 1) + self.assertEqual(main.union.a, 3) + self.assertEqual(main.union.b.x, 3) + self.assertEqual(main.union.b.y, 4) + + def test_smaller_aligned_subclassed_union(self): + for sbase, ubase, e in ( + (LittleEndianStructure, LittleEndianUnion, "<"), + (BigEndianStructure, BigEndianUnion, ">"), + ): + data = bytearray(struct.pack(f"{e}H2xI", 1, 0xD60102D7)) + class SubUnion(ubase): + _align_ = 2 + _fields_ = [ + ("unsigned", c_ubyte), + ("signed", c_byte), + ] + + class MainUnion(SubUnion): + _fields_ = [ + ("num", c_uint32) + ] + + class Main(sbase): + _fields_ = [ + ("first", c_uint16), + ("union", MainUnion), + ] + + main = Main.from_buffer(data) + self.assertEqual(main.union.num, 0xD60102D7) + self.assertEqual(main.union.unsigned, data[4]) + self.assertEqual(main.union.signed, data[4] - 256) + self.assertEqual(alignment(main), 4) + self.assertEqual(alignment(main.union), 4) + self.assertEqual(Main.union.offset, 4) + self.assertEqual(Main.union.size, 4) + self.assertEqual(Main.first.size, 2) + + def test_larger_aligned_subclassed_union(self): + for ubase, e in ( + (LittleEndianUnion, "<"), + (BigEndianUnion, ">"), + ): + data = bytearray(struct.pack(f"{e}I4x", 0xD60102D6)) + class SubUnion(ubase): + _align_ = 8 + _fields_ = [ + ("unsigned", c_ubyte), + ("signed", c_byte), + ] + + class Main(SubUnion): + _fields_ = [ + ("num", c_uint32) + ] + + main = Main.from_buffer(data) + self.assertEqual(alignment(main), 8) + self.assertEqual(sizeof(main), 8) + self.assertEqual(main.num, 0xD60102D6) + self.assertEqual(main.unsigned, 0xD6) + self.assertEqual(main.signed, -42) + + def test_aligned_packed_structures(self): + for sbase, e in ( + (LittleEndianStructure, "<"), + (BigEndianStructure, ">"), + ): + data = bytearray(struct.pack(f"{e}B2H4xB", 1, 2, 3, 4)) + + class Inner(sbase): + _align_ = 8 + _fields_ = [ + ("x", c_uint16), + ("y", c_uint16), + ] + + class Main(sbase): + _pack_ = 1 + _fields_ = [ + ("a", c_ubyte), + ("b", Inner), + ("c", c_ubyte), + ] + + main = Main.from_buffer(data) + self.assertEqual(sizeof(main), 10) + self.assertEqual(Main.b.offset, 1) + # Alignment == 8 because _pack_ wins out. + self.assertEqual(alignment(main.b), 8) + # Size is still 8 though since inside this Structure, it will have + # effect. + self.assertEqual(sizeof(main.b), 8) + self.assertEqual(Main.c.offset, 9) + self.assertEqual(main.a, 1) + self.assertEqual(main.b.x, 2) + self.assertEqual(main.b.y, 3) + self.assertEqual(main.c, 4) + + +if __name__ == '__main__': + unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-28-02-46-12.gh-issue-112433.FUX-nT.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-28-02-46-12.gh-issue-112433.FUX-nT.rst new file mode 100644 index 000000000000000..fdd11bdf4241b95 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-28-02-46-12.gh-issue-112433.FUX-nT.rst @@ -0,0 +1 @@ +Add ability to force alignment of :mod:`ctypes.Structure` by way of the new ``_align_`` attribute on the class. diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c index deafa696fdd0d03..32ee414a7a0cddd 100644 --- a/Modules/_ctypes/stgdict.c +++ b/Modules/_ctypes/stgdict.c @@ -384,6 +384,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct int bitofs; PyObject *tmp; int pack; + int forced_alignment = 1; Py_ssize_t ffi_ofs; int big_endian; int arrays_seen = 0; @@ -424,6 +425,28 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct pack = 0; } + if (PyObject_GetOptionalAttr(type, &_Py_ID(_align_), &tmp) < 0) { + return -1; + } + if (tmp) { + forced_alignment = PyLong_AsInt(tmp); + Py_DECREF(tmp); + if (forced_alignment < 0) { + if (!PyErr_Occurred() || + PyErr_ExceptionMatches(PyExc_TypeError) || + PyErr_ExceptionMatches(PyExc_OverflowError)) + { + PyErr_SetString(PyExc_ValueError, + "_align_ must be a non-negative integer"); + } + return -1; + } + } + else { + /* Setting `_align_ = 0` amounts to using the default alignment */ + forced_alignment = 1; + } + len = PySequence_Size(fields); if (len == -1) { if (PyErr_ExceptionMatches(PyExc_TypeError)) { @@ -469,6 +492,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct align = basedict->align; union_size = 0; total_align = align ? align : 1; + total_align = max(total_align, forced_alignment); stgdict->ffi_type_pointer.type = FFI_TYPE_STRUCT; stgdict->ffi_type_pointer.elements = PyMem_New(ffi_type *, basedict->length + len + 1); if (stgdict->ffi_type_pointer.elements == NULL) { @@ -488,7 +512,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct size = 0; align = 0; union_size = 0; - total_align = 1; + total_align = forced_alignment; stgdict->ffi_type_pointer.type = FFI_TYPE_STRUCT; stgdict->ffi_type_pointer.elements = PyMem_New(ffi_type *, len + 1); if (stgdict->ffi_type_pointer.elements == NULL) { From cfb26401f60a428b3674eb5d9eecf315eb55acab Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 15 Feb 2024 17:32:33 +0200 Subject: [PATCH 297/507] gh-100734: What's New in 3.x: Add missing detail from 3.x branch (#114689) --- Doc/whatsnew/2.6.rst | 36 +++++++++++++++++ Doc/whatsnew/3.1.rst | 22 +++++++++++ Doc/whatsnew/3.10.rst | 47 ++++++++++++++++++++++ Doc/whatsnew/3.12.rst | 2 + Doc/whatsnew/3.6.rst | 33 ++++++++++++++++ Doc/whatsnew/3.7.rst | 44 +++++++++++++++++++++ Doc/whatsnew/3.8.rst | 91 +++++++++++++++++++++++++++++++++++++++++++ Doc/whatsnew/3.9.rst | 52 +++++++++++++++++++++++++ 8 files changed, 327 insertions(+) diff --git a/Doc/whatsnew/2.6.rst b/Doc/whatsnew/2.6.rst index 05c21d313aae035..e4ade5ecd82b9d4 100644 --- a/Doc/whatsnew/2.6.rst +++ b/Doc/whatsnew/2.6.rst @@ -2992,6 +2992,33 @@ Changes to Python's build process and to the C API include: architectures (x86, PowerPC), 64-bit (x86-64 and PPC-64), or both. (Contributed by Ronald Oussoren.) +* A new function added in Python 2.6.6, :c:func:`!PySys_SetArgvEx`, sets + the value of ``sys.argv`` and can optionally update ``sys.path`` to + include the directory containing the script named by ``sys.argv[0]`` + depending on the value of an *updatepath* parameter. + + This function was added to close a security hole for applications + that embed Python. The old function, :c:func:`!PySys_SetArgv`, would + always update ``sys.path``, and sometimes it would add the current + directory. This meant that, if you ran an application embedding + Python in a directory controlled by someone else, attackers could + put a Trojan-horse module in the directory (say, a file named + :file:`os.py`) that your application would then import and run. + + If you maintain a C/C++ application that embeds Python, check + whether you're calling :c:func:`!PySys_SetArgv` and carefully consider + whether the application should be using :c:func:`!PySys_SetArgvEx` + with *updatepath* set to false. Note that using this function will + break compatibility with Python versions 2.6.5 and earlier; if you + have to continue working with earlier versions, you can leave + the call to :c:func:`!PySys_SetArgv` alone and call + ``PyRun_SimpleString("sys.path.pop(0)\n")`` afterwards to discard + the first ``sys.path`` component. + + Security issue reported as `CVE-2008-5983 + <http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2008-5983>`_; + discussed in :gh:`50003`, and fixed by Antoine Pitrou. + * The BerkeleyDB module now has a C API object, available as ``bsddb.db.api``. This object can be used by other C extensions that wish to use the :mod:`bsddb` module for their own purposes. @@ -3294,6 +3321,15 @@ that may require changes to your code: scoping rules, also cause warnings because such comparisons are forbidden entirely in 3.0. +For applications that embed Python: + +* The :c:func:`!PySys_SetArgvEx` function was added in Python 2.6.6, + letting applications close a security hole when the existing + :c:func:`!PySys_SetArgv` function was used. Check whether you're + calling :c:func:`!PySys_SetArgv` and carefully consider whether the + application should be using :c:func:`!PySys_SetArgvEx` with + *updatepath* set to false. + .. ====================================================================== diff --git a/Doc/whatsnew/3.1.rst b/Doc/whatsnew/3.1.rst index c912a928ee45972..b7dd8f2c7bf531f 100644 --- a/Doc/whatsnew/3.1.rst +++ b/Doc/whatsnew/3.1.rst @@ -80,6 +80,28 @@ Support was also added for third-party tools like `PyYAML <https://pyyaml.org/>` PEP written by Armin Ronacher and Raymond Hettinger. Implementation written by Raymond Hettinger. +Since an ordered dictionary remembers its insertion order, it can be used +in conjuction with sorting to make a sorted dictionary:: + + >>> # regular unsorted dictionary + >>> d = {'banana': 3, 'apple':4, 'pear': 1, 'orange': 2} + + >>> # dictionary sorted by key + >>> OrderedDict(sorted(d.items(), key=lambda t: t[0])) + OrderedDict([('apple', 4), ('banana', 3), ('orange', 2), ('pear', 1)]) + + >>> # dictionary sorted by value + >>> OrderedDict(sorted(d.items(), key=lambda t: t[1])) + OrderedDict([('pear', 1), ('orange', 2), ('banana', 3), ('apple', 4)]) + + >>> # dictionary sorted by length of the key string + >>> OrderedDict(sorted(d.items(), key=lambda t: len(t[0]))) + OrderedDict([('pear', 1), ('apple', 4), ('orange', 2), ('banana', 3)]) + +The new sorted dictionaries maintain their sort order when entries +are deleted. But when new keys are added, the keys are appended +to the end and the sort is not maintained. + PEP 378: Format Specifier for Thousands Separator ================================================= diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 7dc06e9af694d92..9770b12c8af7110 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -1517,6 +1517,13 @@ functions internally. For more details, please see their respective documentation. (Contributed by Adam Goldschmidt, Senthil Kumaran and Ken Jin in :issue:`42967`.) +The presence of newline or tab characters in parts of a URL allows for some +forms of attacks. Following the WHATWG specification that updates :rfc:`3986`, +ASCII newline ``\n``, ``\r`` and tab ``\t`` characters are stripped from the +URL by the parser in :mod:`urllib.parse` preventing such attacks. The removal +characters are controlled by a new module level variable +``urllib.parse._UNSAFE_URL_BYTES_TO_REMOVE``. (See :gh:`88048`) + xml --- @@ -2315,3 +2322,43 @@ Removed * The ``PyThreadState.use_tracing`` member has been removed to optimize Python. (Contributed by Mark Shannon in :issue:`43760`.) + + +Notable security feature in 3.10.7 +================================== + +Converting between :class:`int` and :class:`str` in bases other than 2 +(binary), 4, 8 (octal), 16 (hexadecimal), or 32 such as base 10 (decimal) +now raises a :exc:`ValueError` if the number of digits in string form is +above a limit to avoid potential denial of service attacks due to the +algorithmic complexity. This is a mitigation for `CVE-2020-10735 +<https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735>`_. +This limit can be configured or disabled by environment variable, command +line flag, or :mod:`sys` APIs. See the :ref:`integer string conversion +length limitation <int_max_str_digits>` documentation. The default limit +is 4300 digits in string form. + +Notable security feature in 3.10.8 +================================== + +The deprecated :mod:`!mailcap` module now refuses to inject unsafe text +(filenames, MIME types, parameters) into shell commands. Instead of using such +text, it will warn and act as if a match was not found (or for test commands, +as if the test failed). +(Contributed by Petr Viktorin in :gh:`98966`.) + +Notable changes in 3.10.12 +========================== + +tarfile +------- + +* The extraction methods in :mod:`tarfile`, and :func:`shutil.unpack_archive`, + have a new a *filter* argument that allows limiting tar features than may be + surprising or dangerous, such as creating files outside the destination + directory. + See :ref:`tarfile-extraction-filter` for details. + In Python 3.12, use without the *filter* argument will show a + :exc:`DeprecationWarning`. + In Python 3.14, the default will switch to ``'data'``. + (Contributed by Petr Viktorin in :pep:`706`.) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 100312a5940b796..b986e638498abd3 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -1956,6 +1956,8 @@ Build Changes :file:`!configure`. (Contributed by Christian Heimes in :gh:`89886`.) +* Windows builds and macOS installers from python.org now use OpenSSL 3.0. + C API Changes ============= diff --git a/Doc/whatsnew/3.6.rst b/Doc/whatsnew/3.6.rst index 11e1d73232a96db..d62beb0bdc86720 100644 --- a/Doc/whatsnew/3.6.rst +++ b/Doc/whatsnew/3.6.rst @@ -1472,6 +1472,10 @@ Server and client-side specific TLS protocols for :class:`~ssl.SSLContext` were added. (Contributed by Christian Heimes in :issue:`28085`.) +Added :attr:`ssl.SSLContext.post_handshake_auth` to enable and +:meth:`ssl.SSLSocket.verify_client_post_handshake` to initiate TLS 1.3 +post-handshake authentication. +(Contributed by Christian Heimes in :gh:`78851`.) statistics ---------- @@ -2063,6 +2067,15 @@ connected to and thus what Python interpreter will be used by the virtual environment. (Contributed by Brett Cannon in :issue:`25154`.) +xml +--- + +* As mitigation against DTD and external entity retrieval, the + :mod:`xml.dom.minidom` and :mod:`xml.sax` modules no longer process + external entities by default. + (Contributed by Christian Heimes in :gh:`61441`.) + + Deprecated functions and types of the C API ------------------------------------------- @@ -2430,9 +2443,13 @@ The :func:`locale.localeconv` function now sets temporarily the ``LC_CTYPE`` locale to the ``LC_NUMERIC`` locale in some cases. (Contributed by Victor Stinner in :issue:`31900`.) + Notable changes in Python 3.6.7 =============================== +:mod:`xml.dom.minidom` and :mod:`xml.sax` modules no longer process +external entities by default. See also :gh:`61441`. + In 3.6.7 the :mod:`tokenize` module now implicitly emits a ``NEWLINE`` token when provided with input that does not have a trailing new line. This behavior now matches what the C tokenizer does internally. @@ -2460,3 +2477,19 @@ separator key, with ``&`` as the default. This change also affects functions internally. For more details, please see their respective documentation. (Contributed by Adam Goldschmidt, Senthil Kumaran and Ken Jin in :issue:`42967`.) + +Notable changes in Python 3.6.14 +================================ + +A security fix alters the :class:`ftplib.FTP` behavior to not trust the +IPv4 address sent from the remote server when setting up a passive data +channel. We reuse the ftp server IP address instead. For unusual code +requiring the old behavior, set a ``trust_server_pasv_ipv4_address`` +attribute on your FTP instance to ``True``. (See :gh:`87451`) + +The presence of newline or tab characters in parts of a URL allows for some +forms of attacks. Following the WHATWG specification that updates RFC 3986, +ASCII newline ``\n``, ``\r`` and tab ``\t`` characters are stripped from the +URL by the parser :func:`urllib.parse` preventing such attacks. The removal +characters are controlled by a new module level variable +``urllib.parse._UNSAFE_URL_BYTES_TO_REMOVE``. (See :gh:`88048`) diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 402b15a277e53db..8122e0ee129b0d2 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -1380,6 +1380,10 @@ Supported protocols are indicated by several new flags, such as :data:`~ssl.HAS_TLSv1_1`. (Contributed by Christian Heimes in :issue:`32609`.) +Added :attr:`ssl.SSLContext.post_handshake_auth` to enable and +:meth:`ssl.SSLSocket.verify_client_post_handshake` to initiate TLS 1.3 +post-handshake authentication. +(Contributed by Christian Heimes in :gh:`78851`.) string ------ @@ -1599,6 +1603,15 @@ at the interactive prompt. See :ref:`whatsnew37-pep565` for details. (Contributed by Nick Coghlan in :issue:`31975`.) +xml +--- + +As mitigation against DTD and external entity retrieval, the +:mod:`xml.dom.minidom` and :mod:`xml.sax` modules no longer process +external entities by default. +(Contributed by Christian Heimes in :gh:`61441`.) + + xml.etree --------- @@ -2571,3 +2584,34 @@ separator key, with ``&`` as the default. This change also affects functions internally. For more details, please see their respective documentation. (Contributed by Adam Goldschmidt, Senthil Kumaran and Ken Jin in :issue:`42967`.) + +Notable changes in Python 3.7.11 +================================ + +A security fix alters the :class:`ftplib.FTP` behavior to not trust the +IPv4 address sent from the remote server when setting up a passive data +channel. We reuse the ftp server IP address instead. For unusual code +requiring the old behavior, set a ``trust_server_pasv_ipv4_address`` +attribute on your FTP instance to ``True``. (See :gh:`87451`) + + +The presence of newline or tab characters in parts of a URL allows for some +forms of attacks. Following the WHATWG specification that updates RFC 3986, +ASCII newline ``\n``, ``\r`` and tab ``\t`` characters are stripped from the +URL by the parser :func:`urllib.parse` preventing such attacks. The removal +characters are controlled by a new module level variable +``urllib.parse._UNSAFE_URL_BYTES_TO_REMOVE``. (See :gh:`88048`) + +Notable security feature in 3.7.14 +================================== + +Converting between :class:`int` and :class:`str` in bases other than 2 +(binary), 4, 8 (octal), 16 (hexadecimal), or 32 such as base 10 (decimal) +now raises a :exc:`ValueError` if the number of digits in string form is +above a limit to avoid potential denial of service attacks due to the +algorithmic complexity. This is a mitigation for `CVE-2020-10735 +<https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735>`_. +This limit can be configured or disabled by environment variable, command +line flag, or :mod:`sys` APIs. See the :ref:`integer string conversion +length limitation <int_max_str_digits>` documentation. The default limit +is 4300 digits in string form. diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index b041e592d61ed11..9a2652f5e336051 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -2243,6 +2243,21 @@ details, see the documentation for ``loop.create_datagram_endpoint()``. (Contributed by Kyle Stanley, Antoine Pitrou, and Yury Selivanov in :issue:`37228`.) +Notable changes in Python 3.8.2 +=============================== + +Fixed a regression with the ``ignore`` callback of :func:`shutil.copytree`. +The argument types are now str and List[str] again. +(Contributed by Manuel Barkhau and Giampaolo Rodola in :gh:`83571`.) + +Notable changes in Python 3.8.3 +=============================== + +The constant values of future flags in the :mod:`__future__` module +are updated in order to prevent collision with compiler flags. Previously +``PyCF_ALLOW_TOP_LEVEL_AWAIT`` was clashing with ``CO_FUTURE_DIVISION``. +(Contributed by Batuhan Taskaya in :gh:`83743`) + Notable changes in Python 3.8.8 =============================== @@ -2256,9 +2271,55 @@ functions internally. For more details, please see their respective documentation. (Contributed by Adam Goldschmidt, Senthil Kumaran and Ken Jin in :issue:`42967`.) +Notable changes in Python 3.8.9 +=============================== + +A security fix alters the :class:`ftplib.FTP` behavior to not trust the +IPv4 address sent from the remote server when setting up a passive data +channel. We reuse the ftp server IP address instead. For unusual code +requiring the old behavior, set a ``trust_server_pasv_ipv4_address`` +attribute on your FTP instance to ``True``. (See :gh:`87451`) + +Notable changes in Python 3.8.10 +================================ + +macOS 11.0 (Big Sur) and Apple Silicon Mac support +-------------------------------------------------- + +As of 3.8.10, Python now supports building and running on macOS 11 +(Big Sur) and on Apple Silicon Macs (based on the ``ARM64`` architecture). +A new universal build variant, ``universal2``, is now available to natively +support both ``ARM64`` and ``Intel 64`` in one set of executables. +Note that support for "weaklinking", building binaries targeted for newer +versions of macOS that will also run correctly on older versions by +testing at runtime for missing features, is not included in this backport +from Python 3.9; to support a range of macOS versions, continue to target +for and build on the oldest version in the range. + +(Originally contributed by Ronald Oussoren and Lawrence D'Anna in :gh:`85272`, +with fixes by FX Coudert and Eli Rykoff, and backported to 3.8 by Maxime Bélanger +and Ned Deily) + +Notable changes in Python 3.8.10 +================================ + +urllib.parse +------------ + +The presence of newline or tab characters in parts of a URL allows for some +forms of attacks. Following the WHATWG specification that updates :rfc:`3986`, +ASCII newline ``\n``, ``\r`` and tab ``\t`` characters are stripped from the +URL by the parser in :mod:`urllib.parse` preventing such attacks. The removal +characters are controlled by a new module level variable +``urllib.parse._UNSAFE_URL_BYTES_TO_REMOVE``. (See :issue:`43882`) + + Notable changes in Python 3.8.12 ================================ +Changes in the Python API +------------------------- + Starting with Python 3.8.12 the :mod:`ipaddress` module no longer accepts any leading zeros in IPv4 address strings. Leading zeros are ambiguous and interpreted as octal notation by some libraries. For example the legacy @@ -2268,3 +2329,33 @@ any leading zeros. (Originally contributed by Christian Heimes in :issue:`36384`, and backported to 3.8 by Achraf Merzouki.) + +Notable security feature in 3.8.14 +================================== + +Converting between :class:`int` and :class:`str` in bases other than 2 +(binary), 4, 8 (octal), 16 (hexadecimal), or 32 such as base 10 (decimal) +now raises a :exc:`ValueError` if the number of digits in string form is +above a limit to avoid potential denial of service attacks due to the +algorithmic complexity. This is a mitigation for `CVE-2020-10735 +<https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735>`_. +This limit can be configured or disabled by environment variable, command +line flag, or :mod:`sys` APIs. See the :ref:`integer string conversion +length limitation <int_max_str_digits>` documentation. The default limit +is 4300 digits in string form. + +Notable changes in 3.8.17 +========================= + +tarfile +------- + +* The extraction methods in :mod:`tarfile`, and :func:`shutil.unpack_archive`, + have a new a *filter* argument that allows limiting tar features than may be + surprising or dangerous, such as creating files outside the destination + directory. + See :ref:`tarfile-extraction-filter` for details. + In Python 3.12, use without the *filter* argument will show a + :exc:`DeprecationWarning`. + In Python 3.14, the default will switch to ``'data'``. + (Contributed by Petr Viktorin in :pep:`706`.) diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index f7ad4372325ccb1..b24c13813be2209 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -1562,3 +1562,55 @@ separator key, with ``&`` as the default. This change also affects functions internally. For more details, please see their respective documentation. (Contributed by Adam Goldschmidt, Senthil Kumaran and Ken Jin in :issue:`42967`.) + +Notable changes in Python 3.9.3 +=============================== + +A security fix alters the :class:`ftplib.FTP` behavior to not trust the +IPv4 address sent from the remote server when setting up a passive data +channel. We reuse the ftp server IP address instead. For unusual code +requiring the old behavior, set a ``trust_server_pasv_ipv4_address`` +attribute on your FTP instance to ``True``. (See :gh:`87451`) + +Notable changes in Python 3.9.5 +=============================== + +urllib.parse +------------ + +The presence of newline or tab characters in parts of a URL allows for some +forms of attacks. Following the WHATWG specification that updates :rfc:`3986`, +ASCII newline ``\n``, ``\r`` and tab ``\t`` characters are stripped from the +URL by the parser in :mod:`urllib.parse` preventing such attacks. The removal +characters are controlled by a new module level variable +``urllib.parse._UNSAFE_URL_BYTES_TO_REMOVE``. (See :gh:`88048`) + +Notable security feature in 3.9.14 +================================== + +Converting between :class:`int` and :class:`str` in bases other than 2 +(binary), 4, 8 (octal), 16 (hexadecimal), or 32 such as base 10 (decimal) +now raises a :exc:`ValueError` if the number of digits in string form is +above a limit to avoid potential denial of service attacks due to the +algorithmic complexity. This is a mitigation for `CVE-2020-10735 +<https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735>`_. +This limit can be configured or disabled by environment variable, command +line flag, or :mod:`sys` APIs. See the :ref:`integer string conversion +length limitation <int_max_str_digits>` documentation. The default limit +is 4300 digits in string form. + +Notable changes in 3.9.17 +========================= + +tarfile +------- + +* The extraction methods in :mod:`tarfile`, and :func:`shutil.unpack_archive`, + have a new a *filter* argument that allows limiting tar features than may be + surprising or dangerous, such as creating files outside the destination + directory. + See :ref:`tarfile-extraction-filter` for details. + In Python 3.12, use without the *filter* argument will show a + :exc:`DeprecationWarning`. + In Python 3.14, the default will switch to ``'data'``. + (Contributed by Petr Viktorin in :pep:`706`.) From e74fa294c9b0c67bfcbefdda5a069f0a7648f524 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Thu, 15 Feb 2024 17:03:58 +0100 Subject: [PATCH 298/507] gh-113317: Argument Clinic: inline required_type_for_self_for_parser() in self converter (#115522) Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> --- Tools/clinic/clinic.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 77d492a386651fd..7e657351b3f629b 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -4402,14 +4402,6 @@ def correct_name_for_self( return "PyTypeObject *", "type" raise AssertionError(f"Unhandled type of function f: {f.kind!r}") -def required_type_for_self_for_parser( - f: Function -) -> str | None: - type, _ = correct_name_for_self(f) - if f.kind in (METHOD_INIT, METHOD_NEW, STATIC_METHOD, CLASS_METHOD): - return type - return None - class self_converter(CConverter): """ @@ -4474,7 +4466,10 @@ def pre_render(self) -> None: @property def parser_type(self) -> str: assert self.type is not None - return required_type_for_self_for_parser(self.function) or self.type + if self.function.kind in {METHOD_INIT, METHOD_NEW, STATIC_METHOD, CLASS_METHOD}: + tp, _ = correct_name_for_self(self.function) + return tp + return self.type def render(self, parameter: Parameter, data: CRenderData) -> None: """ From ae460d450ab854ca66d509ef6971cfe1b6312405 Mon Sep 17 00:00:00 2001 From: Dino Viehland <dinoviehland@meta.com> Date: Thu, 15 Feb 2024 10:54:57 -0800 Subject: [PATCH 299/507] gh-113743: Make the MRO cache thread-safe in free-threaded builds (#113930) Makes _PyType_Lookup thread safe, including: Thread safety of the underlying cache. Make mutation of mro and type members thread safe Also _PyType_GetMRO and _PyType_GetBases are currently returning borrowed references which aren't safe. --- Include/cpython/pyatomic.h | 3 + Include/cpython/pyatomic_gcc.h | 3 + Include/cpython/pyatomic_msc.h | 11 + Include/cpython/pyatomic_std.h | 7 + Include/internal/pycore_critical_section.h | 3 +- Include/internal/pycore_lock.h | 33 ++ Include/internal/pycore_typeobject.h | 7 +- Modules/_abc.c | 15 +- Objects/typeobject.c | 424 +++++++++++++++++---- Python/lock.c | 71 ++++ Python/pystate.c | 3 + 11 files changed, 500 insertions(+), 80 deletions(-) diff --git a/Include/cpython/pyatomic.h b/Include/cpython/pyatomic.h index 5314a70436bfc39..e10d48285367cf9 100644 --- a/Include/cpython/pyatomic.h +++ b/Include/cpython/pyatomic.h @@ -469,6 +469,9 @@ _Py_atomic_store_int_release(int *obj, int value); static inline int _Py_atomic_load_int_acquire(const int *obj); +static inline uint32_t +_Py_atomic_load_uint32_acquire(const uint32_t *obj); + // --- _Py_atomic_fence ------------------------------------------------------ diff --git a/Include/cpython/pyatomic_gcc.h b/Include/cpython/pyatomic_gcc.h index 70f2b7e1b5706a9..4095e1873d8b071 100644 --- a/Include/cpython/pyatomic_gcc.h +++ b/Include/cpython/pyatomic_gcc.h @@ -495,6 +495,9 @@ static inline int _Py_atomic_load_int_acquire(const int *obj) { return __atomic_load_n(obj, __ATOMIC_ACQUIRE); } +static inline uint32_t +_Py_atomic_load_uint32_acquire(const uint32_t *obj) +{ return __atomic_load_n(obj, __ATOMIC_ACQUIRE); } // --- _Py_atomic_fence ------------------------------------------------------ diff --git a/Include/cpython/pyatomic_msc.h b/Include/cpython/pyatomic_msc.h index 601a0cf65afc1c5..b5c1ec941125621 100644 --- a/Include/cpython/pyatomic_msc.h +++ b/Include/cpython/pyatomic_msc.h @@ -938,6 +938,17 @@ _Py_atomic_load_int_acquire(const int *obj) #endif } +static inline uint32_t +_Py_atomic_load_uint32_acquire(const uint32_t *obj) +{ +#if defined(_M_X64) || defined(_M_IX86) + return *(uint32_t volatile *)obj; +#elif defined(_M_ARM64) + return (int)__ldar32((uint32_t volatile *)obj); +#else +# error "no implementation of _Py_atomic_load_uint32_acquire" +#endif +} // --- _Py_atomic_fence ------------------------------------------------------ diff --git a/Include/cpython/pyatomic_std.h b/Include/cpython/pyatomic_std.h index a05bfaec47e89d5..6c934a2c5e7b64c 100644 --- a/Include/cpython/pyatomic_std.h +++ b/Include/cpython/pyatomic_std.h @@ -870,6 +870,13 @@ _Py_atomic_load_int_acquire(const int *obj) memory_order_acquire); } +static inline uint32_t +_Py_atomic_load_uint32_acquire(const uint32_t *obj) +{ + _Py_USING_STD; + return atomic_load_explicit((const _Atomic(uint32_t)*)obj, + memory_order_acquire); +} // --- _Py_atomic_fence ------------------------------------------------------ diff --git a/Include/internal/pycore_critical_section.h b/Include/internal/pycore_critical_section.h index 30820a24f5bb64c..9163b5cf0f2e8a8 100644 --- a/Include/internal/pycore_critical_section.h +++ b/Include/internal/pycore_critical_section.h @@ -293,7 +293,8 @@ _PyCriticalSection_SuspendAll(PyThreadState *tstate); #ifdef Py_GIL_DISABLED static inline void -_PyCriticalSection_AssertHeld(PyMutex *mutex) { +_PyCriticalSection_AssertHeld(PyMutex *mutex) +{ #ifdef Py_DEBUG PyThreadState *tstate = _PyThreadState_GET(); uintptr_t prev = tstate->critical_section; diff --git a/Include/internal/pycore_lock.h b/Include/internal/pycore_lock.h index 18a8896d97a5481..674a1d170fec101 100644 --- a/Include/internal/pycore_lock.h +++ b/Include/internal/pycore_lock.h @@ -251,6 +251,39 @@ PyAPI_FUNC(void) _PyRWMutex_RUnlock(_PyRWMutex *rwmutex); PyAPI_FUNC(void) _PyRWMutex_Lock(_PyRWMutex *rwmutex); PyAPI_FUNC(void) _PyRWMutex_Unlock(_PyRWMutex *rwmutex); +// Similar to linux seqlock: https://en.wikipedia.org/wiki/Seqlock +// We use a sequence number to lock the writer, an even sequence means we're unlocked, an odd +// sequence means we're locked. Readers will read the sequence before attempting to read the +// underlying data and then read the sequence number again after reading the data. If the +// sequence has not changed the data is valid. +// +// Differs a little bit in that we use CAS on sequence as the lock, instead of a seperate spin lock. +// The writer can also detect that the undelering data has not changed and abandon the write +// and restore the previous sequence. +typedef struct { + uint32_t sequence; +} _PySeqLock; + +// Lock the sequence lock for the writer +PyAPI_FUNC(void) _PySeqLock_LockWrite(_PySeqLock *seqlock); + +// Unlock the sequence lock and move to the next sequence number. +PyAPI_FUNC(void) _PySeqLock_UnlockWrite(_PySeqLock *seqlock); + +// Abandon the current update indicating that no mutations have occured +// and restore the previous sequence value. +PyAPI_FUNC(void) _PySeqLock_AbandonWrite(_PySeqLock *seqlock); + +// Begin a read operation and return the current sequence number. +PyAPI_FUNC(uint32_t) _PySeqLock_BeginRead(_PySeqLock *seqlock); + +// End the read operation and confirm that the sequence number has not changed. +// Returns 1 if the read was successful or 0 if the read should be re-tried. +PyAPI_FUNC(uint32_t) _PySeqLock_EndRead(_PySeqLock *seqlock, uint32_t previous); + +// Check if the lock was held during a fork and clear the lock. Returns 1 +// if the lock was held and any associated datat should be cleared. +PyAPI_FUNC(uint32_t) _PySeqLock_AfterFork(_PySeqLock *seqlock); #ifdef __cplusplus } diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index c03c3d766bef616..664f6fb212a57d2 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -9,6 +9,7 @@ extern "C" { #endif #include "pycore_moduleobject.h" // PyModuleObject +#include "pycore_lock.h" // PyMutex /* state */ @@ -21,6 +22,7 @@ struct _types_runtime_state { // bpo-42745: next_version_tag remains shared by all interpreters // because of static types. unsigned int next_version_tag; + PyMutex type_mutex; }; @@ -28,6 +30,9 @@ struct _types_runtime_state { // see _PyType_Lookup(). struct type_cache_entry { unsigned int version; // initialized from type->tp_version_tag +#ifdef Py_GIL_DISABLED + _PySeqLock sequence; +#endif PyObject *name; // reference to exactly a str or None PyObject *value; // borrowed reference or NULL }; @@ -74,7 +79,7 @@ struct types_state { extern PyStatus _PyTypes_InitTypes(PyInterpreterState *); extern void _PyTypes_FiniTypes(PyInterpreterState *); extern void _PyTypes_Fini(PyInterpreterState *); - +extern void _PyTypes_AfterFork(void); /* other API */ diff --git a/Modules/_abc.c b/Modules/_abc.c index 9473905243d438d..399ecbbd6a21728 100644 --- a/Modules/_abc.c +++ b/Modules/_abc.c @@ -8,7 +8,6 @@ #include "pycore_object.h" // _PyType_GetSubclasses() #include "pycore_runtime.h" // _Py_ID() #include "pycore_setobject.h" // _PySet_NextEntry() -#include "pycore_typeobject.h" // _PyType_GetMRO() #include "pycore_weakref.h" // _PyWeakref_GET_REF() #include "clinic/_abc.c.h" @@ -744,18 +743,12 @@ _abc__abc_subclasscheck_impl(PyObject *module, PyObject *self, Py_DECREF(ok); /* 4. Check if it's a direct subclass. */ - PyObject *mro = _PyType_GetMRO((PyTypeObject *)subclass); - assert(PyTuple_Check(mro)); - for (pos = 0; pos < PyTuple_GET_SIZE(mro); pos++) { - PyObject *mro_item = PyTuple_GET_ITEM(mro, pos); - assert(mro_item != NULL); - if ((PyObject *)self == mro_item) { - if (_add_to_weak_set(&impl->_abc_cache, subclass) < 0) { - goto end; - } - result = Py_True; + if (PyType_IsSubtype((PyTypeObject *)subclass, (PyTypeObject *)self)) { + if (_add_to_weak_set(&impl->_abc_cache, subclass) < 0) { goto end; } + result = Py_True; + goto end; } /* 5. Check if it's a subclass of a registered class (recursive). */ diff --git a/Objects/typeobject.c b/Objects/typeobject.c index c65d0ec2acae526..e0711dfe8545b7e 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6,6 +6,7 @@ #include "pycore_code.h" // CO_FAST_FREE #include "pycore_dict.h" // _PyDict_KeysSize() #include "pycore_frame.h" // _PyInterpreterFrame +#include "pycore_lock.h" // _PySeqLock_* #include "pycore_long.h" // _PyLong_IsNegative() #include "pycore_memoryobject.h" // _PyMemoryView_FromBufferProc() #include "pycore_modsupport.h" // _PyArg_NoKwnames() @@ -52,6 +53,34 @@ class object "PyObject *" "&PyBaseObject_Type" #define NEXT_VERSION_TAG(interp) \ (interp)->types.next_version_tag +#ifdef Py_GIL_DISABLED + +// There's a global lock for mutation of types. This avoids having to take +// additonal locks while doing various subclass processing which may result +// in odd behaviors w.r.t. running with the GIL as the outer type lock could +// be released and reacquired during a subclass update if there's contention +// on the subclass lock. +#define BEGIN_TYPE_LOCK() \ + { \ + _PyCriticalSection _cs; \ + _PyCriticalSection_Begin(&_cs, &_PyRuntime.types.type_mutex); \ + +#define END_TYPE_LOCK() \ + _PyCriticalSection_End(&_cs); \ + } + +#define ASSERT_TYPE_LOCK_HELD() \ + _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(&_PyRuntime.types.type_mutex) + +#else + +#define BEGIN_TYPE_LOCK() +#define END_TYPE_LOCK() +#define ASSERT_TYPE_LOCK_HELD() + +#endif + + typedef struct PySlot_Offset { short subslot_offset; short slot_offset; @@ -279,8 +308,14 @@ lookup_tp_bases(PyTypeObject *self) PyObject * _PyType_GetBases(PyTypeObject *self) { - /* It returns a borrowed reference. */ - return lookup_tp_bases(self); + PyObject *res; + + BEGIN_TYPE_LOCK(); + res = lookup_tp_bases(self); + Py_INCREF(res); + END_TYPE_LOCK() + + return res; } static inline void @@ -330,14 +365,19 @@ clear_tp_bases(PyTypeObject *self) static inline PyObject * lookup_tp_mro(PyTypeObject *self) { + ASSERT_TYPE_LOCK_HELD(); return self->tp_mro; } PyObject * _PyType_GetMRO(PyTypeObject *self) { - /* It returns a borrowed reference. */ - return lookup_tp_mro(self); + PyObject *mro; + BEGIN_TYPE_LOCK(); + mro = lookup_tp_mro(self); + Py_INCREF(mro); + END_TYPE_LOCK() + return mro; } static inline void @@ -646,9 +686,15 @@ type_cache_clear(struct type_cache *cache, PyObject *value) { for (Py_ssize_t i = 0; i < (1 << MCACHE_SIZE_EXP); i++) { struct type_cache_entry *entry = &cache->hashtable[i]; +#ifdef Py_GIL_DISABLED + _PySeqLock_LockWrite(&entry->sequence); +#endif entry->version = 0; Py_XSETREF(entry->name, _Py_XNewRef(value)); entry->value = NULL; +#ifdef Py_GIL_DISABLED + _PySeqLock_UnlockWrite(&entry->sequence); +#endif } } @@ -760,8 +806,10 @@ PyType_Watch(int watcher_id, PyObject* obj) return -1; } // ensure we will get a callback on the next modification + BEGIN_TYPE_LOCK() assign_version_tag(interp, type); type->tp_watched |= (1 << watcher_id); + END_TYPE_LOCK() return 0; } @@ -781,8 +829,8 @@ PyType_Unwatch(int watcher_id, PyObject* obj) return 0; } -void -PyType_Modified(PyTypeObject *type) +static void +type_modified_unlocked(PyTypeObject *type) { /* Invalidate any cached data for the specified type and all subclasses. This function is called after the base @@ -848,6 +896,22 @@ PyType_Modified(PyTypeObject *type) } } +void +PyType_Modified(PyTypeObject *type) +{ + // Quick check without the lock held + if (!_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) { + return; + } + + BEGIN_TYPE_LOCK() + type_modified_unlocked(type); + END_TYPE_LOCK() +} + +static int +is_subtype_unlocked(PyTypeObject *a, PyTypeObject *b); + static void type_mro_modified(PyTypeObject *type, PyObject *bases) { /* @@ -866,6 +930,7 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) { int custom = !Py_IS_TYPE(type, &PyType_Type); int unbound; + ASSERT_TYPE_LOCK_HELD(); if (custom) { PyObject *mro_meth, *type_mro_meth; mro_meth = lookup_maybe_method( @@ -891,7 +956,7 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) { PyObject *b = PyTuple_GET_ITEM(bases, i); PyTypeObject *cls = _PyType_CAST(b); - if (!PyType_IsSubtype(type, cls)) { + if (!is_subtype_unlocked(type, cls)) { goto clear; } } @@ -913,6 +978,8 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) { static int assign_version_tag(PyInterpreterState *interp, PyTypeObject *type) { + ASSERT_TYPE_LOCK_HELD(); + /* Ensure that the tp_version_tag is valid and set Py_TPFLAGS_VALID_VERSION_TAG. To respect the invariant, this must first be done on all super classes. Return 0 if this @@ -961,7 +1028,11 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type) int PyUnstable_Type_AssignVersionTag(PyTypeObject *type) { PyInterpreterState *interp = _PyInterpreterState_GET(); - return assign_version_tag(interp, type); + int assigned; + BEGIN_TYPE_LOCK() + assigned = assign_version_tag(interp, type); + END_TYPE_LOCK() + return assigned; } @@ -1183,21 +1254,28 @@ type_set_abstractmethods(PyTypeObject *type, PyObject *value, void *context) static PyObject * type_get_bases(PyTypeObject *type, void *context) { - PyObject *bases = lookup_tp_bases(type); + PyObject *bases = _PyType_GetBases(type); if (bases == NULL) { Py_RETURN_NONE; } - return Py_NewRef(bases); + return bases; } static PyObject * type_get_mro(PyTypeObject *type, void *context) { - PyObject *mro = lookup_tp_mro(type); + PyObject *mro; + + BEGIN_TYPE_LOCK() + mro = lookup_tp_mro(type); if (mro == NULL) { - Py_RETURN_NONE; + mro = Py_None; + } else { + Py_INCREF(mro); } - return Py_NewRef(mro); + + END_TYPE_LOCK() + return mro; } static PyTypeObject *best_base(PyObject *); @@ -1219,6 +1297,8 @@ static int recurse_down_subclasses(PyTypeObject *type, PyObject *name, static int mro_hierarchy(PyTypeObject *type, PyObject *temp) { + ASSERT_TYPE_LOCK_HELD(); + PyObject *old_mro; int res = mro_internal(type, &old_mro); if (res <= 0) { @@ -1282,7 +1362,7 @@ mro_hierarchy(PyTypeObject *type, PyObject *temp) } static int -type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context) +type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, void *context) { // Check arguments if (!check_set_special_type_attr(type, new_bases, "__bases__")) { @@ -1313,7 +1393,7 @@ type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context) } PyTypeObject *base = (PyTypeObject*)ob; - if (PyType_IsSubtype(base, type) || + if (is_subtype_unlocked(base, type) || /* In case of reentering here again through a custom mro() the above check is not enough since it relies on base->tp_mro which would gonna be updated inside @@ -1418,6 +1498,16 @@ type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context) return -1; } +static int +type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context) +{ + int res; + BEGIN_TYPE_LOCK(); + res = type_set_bases_unlocked(type, new_bases, context); + END_TYPE_LOCK(); + return res; +} + static PyObject * type_dict(PyTypeObject *type, void *context) { @@ -2156,11 +2246,12 @@ type_is_subtype_base_chain(PyTypeObject *a, PyTypeObject *b) return (b == &PyBaseObject_Type); } -int -PyType_IsSubtype(PyTypeObject *a, PyTypeObject *b) +static int +is_subtype_unlocked(PyTypeObject *a, PyTypeObject *b) { PyObject *mro; + ASSERT_TYPE_LOCK_HELD(); mro = lookup_tp_mro(a); if (mro != NULL) { /* Deal with multiple inheritance without recursion @@ -2179,6 +2270,16 @@ PyType_IsSubtype(PyTypeObject *a, PyTypeObject *b) return type_is_subtype_base_chain(a, b); } +int +PyType_IsSubtype(PyTypeObject *a, PyTypeObject *b) +{ + int res; + BEGIN_TYPE_LOCK(); + res = is_subtype_unlocked(a, b); + END_TYPE_LOCK() + return res; +} + /* Routines to do a method lookup in the type without looking in the instance dictionary (so we can't use PyObject_GetAttr) but still binding it to the instance. @@ -2538,8 +2639,10 @@ pmerge(PyObject *acc, PyObject **to_merge, Py_ssize_t to_merge_size) } static PyObject * -mro_implementation(PyTypeObject *type) +mro_implementation_unlocked(PyTypeObject *type) { + ASSERT_TYPE_LOCK_HELD(); + if (!_PyType_IsReady(type)) { if (PyType_Ready(type) < 0) return NULL; @@ -2619,6 +2722,16 @@ mro_implementation(PyTypeObject *type) return result; } +static PyObject * +mro_implementation(PyTypeObject *type) +{ + PyObject *mro; + BEGIN_TYPE_LOCK() + mro = mro_implementation_unlocked(type); + END_TYPE_LOCK() + return mro; +} + /*[clinic input] type.mro @@ -2657,7 +2770,7 @@ mro_check(PyTypeObject *type, PyObject *mro) } PyTypeObject *base = (PyTypeObject*)obj; - if (!PyType_IsSubtype(solid, solid_base(base))) { + if (!is_subtype_unlocked(solid, solid_base(base))) { PyErr_Format( PyExc_TypeError, "mro() returned base with unsuitable layout ('%.500s')", @@ -2688,6 +2801,9 @@ mro_invoke(PyTypeObject *type) { PyObject *mro_result; PyObject *new_mro; + + ASSERT_TYPE_LOCK_HELD(); + const int custom = !Py_IS_TYPE(type, &PyType_Type); if (custom) { @@ -2700,7 +2816,7 @@ mro_invoke(PyTypeObject *type) Py_DECREF(mro_meth); } else { - mro_result = mro_implementation(type); + mro_result = mro_implementation_unlocked(type); } if (mro_result == NULL) return NULL; @@ -2747,8 +2863,10 @@ mro_invoke(PyTypeObject *type) - Returns -1 in case of an error. */ static int -mro_internal(PyTypeObject *type, PyObject **p_old_mro) +mro_internal_unlocked(PyTypeObject *type, PyObject **p_old_mro) { + ASSERT_TYPE_LOCK_HELD(); + PyObject *new_mro, *old_mro; int reent; @@ -2793,6 +2911,16 @@ mro_internal(PyTypeObject *type, PyObject **p_old_mro) return 1; } +static int +mro_internal(PyTypeObject *type, PyObject **p_old_mro) +{ + int res; + BEGIN_TYPE_LOCK() + res = mro_internal_unlocked(type, p_old_mro); + END_TYPE_LOCK() + return res; +} + /* Calculate the best base amongst multiple base classes. This is the first one that's on the path to the "solid base". */ @@ -4631,6 +4759,9 @@ PyType_GetModuleByDef(PyTypeObject *type, PyModuleDef *def) { assert(PyType_Check(type)); + PyObject *res = NULL; + BEGIN_TYPE_LOCK() + PyObject *mro = lookup_tp_mro(type); // The type must be ready assert(mro != NULL); @@ -4650,15 +4781,19 @@ PyType_GetModuleByDef(PyTypeObject *type, PyModuleDef *def) PyHeapTypeObject *ht = (PyHeapTypeObject*)super; PyObject *module = ht->ht_module; if (module && _PyModule_GetDef(module) == def) { - return module; + res = module; + break; } } + END_TYPE_LOCK() - PyErr_Format( - PyExc_TypeError, - "PyType_GetModuleByDef: No superclass of '%s' has the given module", - type->tp_name); - return NULL; + if (res == NULL) { + PyErr_Format( + PyExc_TypeError, + "PyType_GetModuleByDef: No superclass of '%s' has the given module", + type->tp_name); + } + return res; } void * @@ -4696,6 +4831,8 @@ PyObject_GetItemData(PyObject *obj) static PyObject * find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) { + ASSERT_TYPE_LOCK_HELD(); + Py_hash_t hash; if (!PyUnicode_CheckExact(name) || (hash = _PyASCIIObject_CAST(name)->hash) == -1) @@ -4765,6 +4902,62 @@ is_dunder_name(PyObject *name) return 0; } +static void +update_cache(struct type_cache_entry *entry, PyObject *name, unsigned int version_tag, PyObject *value) +{ + entry->version = version_tag; + entry->value = value; /* borrowed */ + assert(_PyASCIIObject_CAST(name)->hash != -1); + OBJECT_STAT_INC_COND(type_cache_collisions, entry->name != Py_None && entry->name != name); + // We're releasing this under the lock for simplicity sake because it's always a + // exact unicode object or Py_None so it's safe to do so. + Py_SETREF(entry->name, Py_NewRef(name)); +} + +#if Py_GIL_DISABLED + +#define TYPE_CACHE_IS_UPDATING(sequence) (sequence & 0x01) + +static void +update_cache_gil_disabled(struct type_cache_entry *entry, PyObject *name, + unsigned int version_tag, PyObject *value) +{ + _PySeqLock_LockWrite(&entry->sequence); + + // update the entry + if (entry->name == name && + entry->value == value && + entry->version == version_tag) { + // We raced with another update, bail and restore previous sequence. + _PySeqLock_AbandonWrite(&entry->sequence); + return; + } + + update_cache(entry, name, version_tag, value); + + // Then update sequence to the next valid value + _PySeqLock_UnlockWrite(&entry->sequence); +} + +#endif + +void +_PyTypes_AfterFork() +{ +#ifdef Py_GIL_DISABLED + struct type_cache *cache = get_type_cache(); + for (Py_ssize_t i = 0; i < (1 << MCACHE_SIZE_EXP); i++) { + struct type_cache_entry *entry = &cache->hashtable[i]; + if (_PySeqLock_AfterFork(&entry->sequence)) { + // Entry was in the process of updating while forking, clear it... + entry->value = NULL; + Py_SETREF(entry->name, Py_None); + entry->version = 0; + } + } +#endif +} + /* Internal API to look for a name through the MRO. This returns a borrowed reference, and doesn't set an exception! */ PyObject * @@ -4777,6 +4970,27 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) unsigned int h = MCACHE_HASH_METHOD(type, name); struct type_cache *cache = get_type_cache(); struct type_cache_entry *entry = &cache->hashtable[h]; +#ifdef Py_GIL_DISABLED + // synchronize-with other writing threads by doing an acquire load on the sequence + while (1) { + int sequence = _PySeqLock_BeginRead(&entry->sequence); + if (_Py_atomic_load_uint32_relaxed(&entry->version) == type->tp_version_tag && + _Py_atomic_load_ptr_relaxed(&entry->name) == name) { + assert(_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)); + OBJECT_STAT_INC_COND(type_cache_hits, !is_dunder_name(name)); + OBJECT_STAT_INC_COND(type_cache_dunder_hits, is_dunder_name(name)); + PyObject *value = _Py_atomic_load_ptr_relaxed(&entry->value); + + // If the sequence is still valid then we're done + if (_PySeqLock_EndRead(&entry->sequence, sequence)) { + return value; + } + } else { + // cache miss + break; + } + } +#else if (entry->version == type->tp_version_tag && entry->name == name) { assert(_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)); @@ -4784,13 +4998,27 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) OBJECT_STAT_INC_COND(type_cache_dunder_hits, is_dunder_name(name)); return entry->value; } +#endif OBJECT_STAT_INC_COND(type_cache_misses, !is_dunder_name(name)); OBJECT_STAT_INC_COND(type_cache_dunder_misses, is_dunder_name(name)); /* We may end up clearing live exceptions below, so make sure it's ours. */ assert(!PyErr_Occurred()); + // We need to atomically do the lookup and capture the version before + // anyone else can modify our mro or mutate the type. + + int has_version = 0; + int version = 0; + BEGIN_TYPE_LOCK() res = find_name_in_mro(type, name, &error); + if (MCACHE_CACHEABLE_NAME(name)) { + has_version = assign_version_tag(interp, type); + version = type->tp_version_tag; + assert(!has_version || _PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)); + } + END_TYPE_LOCK() + /* Only put NULL results into cache if there was no error. */ if (error) { /* It's not ideal to clear the error condition, @@ -4807,15 +5035,12 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) return NULL; } - if (MCACHE_CACHEABLE_NAME(name) && assign_version_tag(interp, type)) { - h = MCACHE_HASH_METHOD(type, name); - struct type_cache_entry *entry = &cache->hashtable[h]; - entry->version = type->tp_version_tag; - entry->value = res; /* borrowed */ - assert(_PyASCIIObject_CAST(name)->hash != -1); - OBJECT_STAT_INC_COND(type_cache_collisions, entry->name != Py_None && entry->name != name); - assert(_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)); - Py_SETREF(entry->name, Py_NewRef(name)); + if (has_version) { +#if Py_GIL_DISABLED + update_cache_gil_disabled(entry, name, version, res); +#else + update_cache(entry, name, version, res); +#endif } return res; } @@ -4978,6 +5203,8 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value) /* Will fail in _PyObject_GenericSetAttrWithDict. */ Py_INCREF(name); } + + BEGIN_TYPE_LOCK() res = _PyObject_GenericSetAttrWithDict((PyObject *)type, name, value, NULL); if (res == 0) { /* Clear the VALID_VERSION flag of 'type' and all its @@ -4985,13 +5212,15 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value) update_subclasses() recursion in update_slot(), but carefully: they each have their own conditions on which to stop recursing into subclasses. */ - PyType_Modified(type); + type_modified_unlocked(type); if (is_dunder_name(name)) { res = update_slot(type, name); } assert(_PyType_CheckConsistency(type)); } + END_TYPE_LOCK() + Py_DECREF(name); return res; } @@ -6796,28 +7025,28 @@ inherit_special(PyTypeObject *type, PyTypeObject *base) #undef COPYVAL /* Setup fast subclass flags */ - if (PyType_IsSubtype(base, (PyTypeObject*)PyExc_BaseException)) { + if (is_subtype_unlocked(base, (PyTypeObject*)PyExc_BaseException)) { type->tp_flags |= Py_TPFLAGS_BASE_EXC_SUBCLASS; } - else if (PyType_IsSubtype(base, &PyType_Type)) { + else if (is_subtype_unlocked(base, &PyType_Type)) { type->tp_flags |= Py_TPFLAGS_TYPE_SUBCLASS; } - else if (PyType_IsSubtype(base, &PyLong_Type)) { + else if (is_subtype_unlocked(base, &PyLong_Type)) { type->tp_flags |= Py_TPFLAGS_LONG_SUBCLASS; } - else if (PyType_IsSubtype(base, &PyBytes_Type)) { + else if (is_subtype_unlocked(base, &PyBytes_Type)) { type->tp_flags |= Py_TPFLAGS_BYTES_SUBCLASS; } - else if (PyType_IsSubtype(base, &PyUnicode_Type)) { + else if (is_subtype_unlocked(base, &PyUnicode_Type)) { type->tp_flags |= Py_TPFLAGS_UNICODE_SUBCLASS; } - else if (PyType_IsSubtype(base, &PyTuple_Type)) { + else if (is_subtype_unlocked(base, &PyTuple_Type)) { type->tp_flags |= Py_TPFLAGS_TUPLE_SUBCLASS; } - else if (PyType_IsSubtype(base, &PyList_Type)) { + else if (is_subtype_unlocked(base, &PyList_Type)) { type->tp_flags |= Py_TPFLAGS_LIST_SUBCLASS; } - else if (PyType_IsSubtype(base, &PyDict_Type)) { + else if (is_subtype_unlocked(base, &PyDict_Type)) { type->tp_flags |= Py_TPFLAGS_DICT_SUBCLASS; } @@ -7256,6 +7485,8 @@ type_ready_preheader(PyTypeObject *type) static int type_ready_mro(PyTypeObject *type) { + ASSERT_TYPE_LOCK_HELD(); + if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) { if (!_Py_IsMainInterpreter(_PyInterpreterState_GET())) { assert(lookup_tp_mro(type) != NULL); @@ -7265,7 +7496,7 @@ type_ready_mro(PyTypeObject *type) } /* Calculate method resolution order */ - if (mro_internal(type, NULL) < 0) { + if (mro_internal_unlocked(type, NULL) < 0) { return -1; } PyObject *mro = lookup_tp_mro(type); @@ -7329,6 +7560,8 @@ inherit_patma_flags(PyTypeObject *type, PyTypeObject *base) { static int type_ready_inherit(PyTypeObject *type) { + ASSERT_TYPE_LOCK_HELD(); + /* Inherit special flags from dominant base */ PyTypeObject *base = type->tp_base; if (base != NULL) { @@ -7521,6 +7754,8 @@ type_ready_post_checks(PyTypeObject *type) static int type_ready(PyTypeObject *type, int rerunbuiltin) { + ASSERT_TYPE_LOCK_HELD(); + _PyObject_ASSERT((PyObject *)type, !is_readying(type)); start_readying(type); @@ -7608,7 +7843,16 @@ PyType_Ready(PyTypeObject *type) type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE; } - return type_ready(type, 0); + int res; + BEGIN_TYPE_LOCK() + if (!(type->tp_flags & Py_TPFLAGS_READY)) { + res = type_ready(type, 0); + } else { + res = 0; + assert(_PyType_CheckConsistency(type)); + } + END_TYPE_LOCK() + return res; } int @@ -7638,7 +7882,10 @@ _PyStaticType_InitBuiltin(PyInterpreterState *interp, PyTypeObject *self) static_builtin_state_init(interp, self); - int res = type_ready(self, !ismain); + int res; + BEGIN_TYPE_LOCK(); + res = type_ready(self, !ismain); + END_TYPE_LOCK() if (res < 0) { static_builtin_state_clear(interp, self); } @@ -8040,9 +8287,12 @@ wrap_delitem(PyObject *self, PyObject *args, void *wrapped) https://mail.python.org/pipermail/python-dev/2003-April/034535.html */ static int -hackcheck(PyObject *self, setattrofunc func, const char *what) +hackcheck_unlocked(PyObject *self, setattrofunc func, const char *what) { PyTypeObject *type = Py_TYPE(self); + + ASSERT_TYPE_LOCK_HELD(); + PyObject *mro = lookup_tp_mro(type); if (!mro) { /* Probably ok not to check the call in this case. */ @@ -8084,6 +8334,20 @@ hackcheck(PyObject *self, setattrofunc func, const char *what) return 1; } +static int +hackcheck(PyObject *self, setattrofunc func, const char *what) +{ + if (!PyType_Check(self)) { + return 1; + } + + int res; + BEGIN_TYPE_LOCK(); + res = hackcheck_unlocked(self, func, what); + END_TYPE_LOCK() + return res; +} + static PyObject * wrap_setattr(PyObject *self, PyObject *args, void *wrapped) { @@ -9252,13 +9516,13 @@ slot_bf_getbuffer(PyObject *self, Py_buffer *buffer, int flags) return -1; } -static int -releasebuffer_maybe_call_super(PyObject *self, Py_buffer *buffer) +static releasebufferproc +releasebuffer_maybe_call_super_unlocked(PyObject *self, Py_buffer *buffer) { PyTypeObject *self_type = Py_TYPE(self); PyObject *mro = lookup_tp_mro(self_type); if (mro == NULL) { - return -1; + return NULL; } assert(PyTuple_Check(mro)); @@ -9272,9 +9536,8 @@ releasebuffer_maybe_call_super(PyObject *self, Py_buffer *buffer) } i++; /* skip self_type */ if (i >= n) - return -1; + return NULL; - releasebufferproc base_releasebuffer = NULL; for (; i < n; i++) { PyObject *obj = PyTuple_GET_ITEM(mro, i); if (!PyType_Check(obj)) { @@ -9284,15 +9547,25 @@ releasebuffer_maybe_call_super(PyObject *self, Py_buffer *buffer) if (base_type->tp_as_buffer != NULL && base_type->tp_as_buffer->bf_releasebuffer != NULL && base_type->tp_as_buffer->bf_releasebuffer != slot_bf_releasebuffer) { - base_releasebuffer = base_type->tp_as_buffer->bf_releasebuffer; - break; + return base_type->tp_as_buffer->bf_releasebuffer; } } + return NULL; +} + +static void +releasebuffer_maybe_call_super(PyObject *self, Py_buffer *buffer) +{ + releasebufferproc base_releasebuffer; + + BEGIN_TYPE_LOCK(); + base_releasebuffer = releasebuffer_maybe_call_super_unlocked(self, buffer); + END_TYPE_LOCK(); + if (base_releasebuffer != NULL) { base_releasebuffer(self, buffer); } - return 0; } static void @@ -9369,11 +9642,7 @@ static void slot_bf_releasebuffer(PyObject *self, Py_buffer *buffer) { releasebuffer_call_python(self, buffer); - if (releasebuffer_maybe_call_super(self, buffer) < 0) { - if (PyErr_Occurred()) { - PyErr_WriteUnraisable(self); - } - } + releasebuffer_maybe_call_super(self, buffer); } static PyObject * @@ -9828,6 +10097,8 @@ resolve_slotdups(PyTypeObject *type, PyObject *name) static pytype_slotdef * update_one_slot(PyTypeObject *type, pytype_slotdef *p) { + ASSERT_TYPE_LOCK_HELD(); + PyObject *descr; PyWrapperDescrObject *d; @@ -9876,7 +10147,7 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p) d = (PyWrapperDescrObject *)descr; if ((specific == NULL || specific == d->d_wrapped) && d->d_base->wrapper == p->wrapper && - PyType_IsSubtype(type, PyDescr_TYPE(d))) + is_subtype_unlocked(type, PyDescr_TYPE(d))) { specific = d->d_wrapped; } @@ -9941,6 +10212,8 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p) static int update_slots_callback(PyTypeObject *type, void *data) { + ASSERT_TYPE_LOCK_HELD(); + pytype_slotdef **pp = (pytype_slotdef **)data; for (; *pp; pp++) { update_one_slot(type, *pp); @@ -9957,6 +10230,7 @@ update_slot(PyTypeObject *type, PyObject *name) pytype_slotdef **pp; int offset; + ASSERT_TYPE_LOCK_HELD(); assert(PyUnicode_CheckExact(name)); assert(PyUnicode_CHECK_INTERNED(name)); @@ -9990,10 +10264,17 @@ update_slot(PyTypeObject *type, PyObject *name) static void fixup_slot_dispatchers(PyTypeObject *type) { + // This lock isn't strictly necessary because the type has not been + // exposed to anyone else yet, but update_ont_slot calls find_name_in_mro + // where we'd like to assert that the tyep is locked. + BEGIN_TYPE_LOCK() + assert(!PyErr_Occurred()); for (pytype_slotdef *p = slotdefs; p->name; ) { p = update_one_slot(type, p); } + + END_TYPE_LOCK() } static void @@ -10001,6 +10282,8 @@ update_all_slots(PyTypeObject* type) { pytype_slotdef *p; + ASSERT_TYPE_LOCK_HELD(); + /* Clear the VALID_VERSION flag of 'type' and all its subclasses. */ PyType_Modified(type); @@ -10273,7 +10556,15 @@ _super_lookup_descr(PyTypeObject *su_type, PyTypeObject *su_obj_type, PyObject * PyObject *mro, *res; Py_ssize_t i, n; + BEGIN_TYPE_LOCK(); mro = lookup_tp_mro(su_obj_type); + /* keep a strong reference to mro because su_obj_type->tp_mro can be + replaced during PyDict_GetItemRef(dict, name, &res) and because + another thread can modify it after we end the critical section + below */ + Py_XINCREF(mro); + END_TYPE_LOCK() + if (mro == NULL) return NULL; @@ -10286,12 +10577,11 @@ _super_lookup_descr(PyTypeObject *su_type, PyTypeObject *su_obj_type, PyObject * break; } i++; /* skip su->type (if any) */ - if (i >= n) + if (i >= n) { + Py_DECREF(mro); return NULL; + } - /* keep a strong reference to mro because su_obj_type->tp_mro can be - replaced during PyDict_GetItemRef(dict, name, &res) */ - Py_INCREF(mro); do { PyObject *obj = PyTuple_GET_ITEM(mro, i); PyObject *dict = lookup_tp_dict(_PyType_CAST(obj)); diff --git a/Python/lock.c b/Python/lock.c index f0ff1176941da8f..bf0143654bd6921 100644 --- a/Python/lock.c +++ b/Python/lock.c @@ -459,3 +459,74 @@ _PyRWMutex_Unlock(_PyRWMutex *rwmutex) _PyParkingLot_UnparkAll(&rwmutex->bits); } } + +#define SEQLOCK_IS_UPDATING(sequence) (sequence & 0x01) + +void _PySeqLock_LockWrite(_PySeqLock *seqlock) +{ + // lock the entry by setting by moving to an odd sequence number + uint32_t prev = _Py_atomic_load_uint32_relaxed(&seqlock->sequence); + while (1) { + if (SEQLOCK_IS_UPDATING(prev)) { + // Someone else is currently updating the cache + _Py_yield(); + prev = _Py_atomic_load_uint32_relaxed(&seqlock->sequence); + } + else if (_Py_atomic_compare_exchange_uint32(&seqlock->sequence, &prev, prev + 1)) { + // We've locked the cache + break; + } + else { + _Py_yield(); + } + } +} + +void _PySeqLock_AbandonWrite(_PySeqLock *seqlock) +{ + uint32_t new_seq = seqlock->sequence - 1; + assert(!SEQLOCK_IS_UPDATING(new_seq)); + _Py_atomic_store_uint32(&seqlock->sequence, new_seq); +} + +void _PySeqLock_UnlockWrite(_PySeqLock *seqlock) +{ + uint32_t new_seq = seqlock->sequence + 1; + assert(!SEQLOCK_IS_UPDATING(new_seq)); + _Py_atomic_store_uint32(&seqlock->sequence, new_seq); +} + +uint32_t _PySeqLock_BeginRead(_PySeqLock *seqlock) +{ + uint32_t sequence = _Py_atomic_load_uint32_acquire(&seqlock->sequence); + while (SEQLOCK_IS_UPDATING(sequence)) { + _Py_yield(); + sequence = _Py_atomic_load_uint32_acquire(&seqlock->sequence); + } + + return sequence; +} + +uint32_t _PySeqLock_EndRead(_PySeqLock *seqlock, uint32_t previous) +{ + // Synchronize again and validate that the entry hasn't been updated + // while we were readying the values. + if (_Py_atomic_load_uint32_acquire(&seqlock->sequence) == previous) { + return 1; + } + + _Py_yield(); + return 0; +} + +uint32_t _PySeqLock_AfterFork(_PySeqLock *seqlock) +{ + // Synchronize again and validate that the entry hasn't been updated + // while we were readying the values. + if (SEQLOCK_IS_UPDATING(seqlock->sequence)) { + seqlock->sequence = 0; + return 1; + } + + return 0; +} diff --git a/Python/pystate.c b/Python/pystate.c index 08ec586963ce114..b1d1a085d629b4c 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -395,6 +395,7 @@ _Py_COMP_DIAG_POP &(runtime)->atexit.mutex, \ &(runtime)->audit_hooks.mutex, \ &(runtime)->allocators.mutex, \ + &(runtime)->types.type_mutex, \ } static void @@ -499,6 +500,8 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime) _PyMutex_at_fork_reinit(locks[i]); } + _PyTypes_AfterFork(); + /* bpo-42540: id_mutex is freed by _PyInterpreterState_Delete, which does * not force the default allocator. */ if (_PyThread_at_fork_reinit(&runtime->interpreters.main->id_mutex) < 0) { From c08c0679055d96c0397cf128bf7cc8134538b36a Mon Sep 17 00:00:00 2001 From: Thomas Wouters <thomas@python.org> Date: Thu, 15 Feb 2024 21:52:49 +0100 Subject: [PATCH 300/507] Post 3.13.0a4 --- Include/patchlevel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/patchlevel.h b/Include/patchlevel.h index 2da8da9e7f3fe7e..ae2d22f80817d5d 100644 --- a/Include/patchlevel.h +++ b/Include/patchlevel.h @@ -23,7 +23,7 @@ #define PY_RELEASE_SERIAL 4 /* Version as a string */ -#define PY_VERSION "3.13.0a4" +#define PY_VERSION "3.13.0a4+" /*--end constants--*/ /* Version as a single 4-byte hex number, e.g. 0x010502B2 == 1.5.2b2. From fd2bb4be3dd802b1957cf37fe68a3634ab054b2e Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Fri, 16 Feb 2024 00:31:23 +0300 Subject: [PATCH 301/507] gh-115498: Fix `SET_COUNT` error handling in `_xxinterpchannelsmodule` (#115499) --- Modules/_xxinterpchannelsmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_xxinterpchannelsmodule.c b/Modules/_xxinterpchannelsmodule.c index a2974aced12ca06..82d2ae7fc4c9634 100644 --- a/Modules/_xxinterpchannelsmodule.c +++ b/Modules/_xxinterpchannelsmodule.c @@ -2158,7 +2158,7 @@ new_channel_info(PyObject *mod, struct channel_info *info) do { \ PyObject *obj = PyLong_FromLongLong(val); \ if (obj == NULL) { \ - Py_CLEAR(info); \ + Py_CLEAR(self); \ return NULL; \ } \ PyStructSequence_SET_ITEM(self, pos++, obj); \ From 58cb634632cd4d27e1348320665bcfa010e9cbb2 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Thu, 15 Feb 2024 23:52:20 +0100 Subject: [PATCH 302/507] gh-113317: Argument Clinic: move linear_format into libclinic (#115518) --- Lib/test/test_clinic.py | 15 ++++++- Tools/clinic/clinic.py | 65 +++++----------------------- Tools/clinic/libclinic/__init__.py | 2 + Tools/clinic/libclinic/formatting.py | 50 +++++++++++++++++++++ 4 files changed, 76 insertions(+), 56 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index be35e80fb02c725..f5e9b11ad1cc8ac 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -711,7 +711,7 @@ def fn(): class ClinicLinearFormatTest(TestCase): def _test(self, input, output, **kwargs): - computed = clinic.linear_format(input, **kwargs) + computed = libclinic.linear_format(input, **kwargs) self.assertEqual(output, computed) def test_empty_strings(self): @@ -761,6 +761,19 @@ def test_multiline_substitution(self): def """, name='bingle\nbungle\n') + def test_text_before_block_marker(self): + regex = re.escape("found before '{marker}'") + with self.assertRaisesRegex(clinic.ClinicError, regex): + libclinic.linear_format("no text before marker for you! {marker}", + marker="not allowed!") + + def test_text_after_block_marker(self): + regex = re.escape("found after '{marker}'") + with self.assertRaisesRegex(clinic.ClinicError, regex): + libclinic.linear_format("{marker} no text after marker for you!", + marker="not allowed!") + + class InertParser: def __init__(self, clinic): pass diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 7e657351b3f629b..4925f27b2937b19 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -163,50 +163,6 @@ def ensure_legal_c_identifier(s: str) -> str: return s -def linear_format(s: str, **kwargs: str) -> str: - """ - Perform str.format-like substitution, except: - * The strings substituted must be on lines by - themselves. (This line is the "source line".) - * If the substitution text is empty, the source line - is removed in the output. - * If the field is not recognized, the original line - is passed unmodified through to the output. - * If the substitution text is not empty: - * Each line of the substituted text is indented - by the indent of the source line. - * A newline will be added to the end. - """ - lines = [] - for line in s.split('\n'): - indent, curly, trailing = line.partition('{') - if not curly: - lines.extend([line, "\n"]) - continue - - name, curly, trailing = trailing.partition('}') - if not curly or name not in kwargs: - lines.extend([line, "\n"]) - continue - - if trailing: - fail(f"Text found after {{{name}}} block marker! " - "It must be on a line by itself.") - if indent.strip(): - fail(f"Non-whitespace characters found before {{{name}}} block marker! " - "It must be on a line by itself.") - - value = kwargs[name] - if not value: - continue - - stripped = [line.rstrip() for line in value.split("\n")] - value = textwrap.indent("\n".join(stripped), indent) - lines.extend([value, "\n"]) - - return "".join(lines[:-1]) - - class CRenderData: def __init__(self) -> None: @@ -915,7 +871,8 @@ def parser_body( """) for field in preamble, *fields, finale: lines.append(field) - return linear_format("\n".join(lines), parser_declarations=declarations) + return libclinic.linear_format("\n".join(lines), + parser_declarations=declarations) fastcall = not new_or_init limited_capi = clinic.limited_capi @@ -1570,7 +1527,7 @@ def render_option_group_parsing( {group_booleans} break; """ - s = linear_format(s, group_booleans=lines) + s = libclinic.linear_format(s, group_booleans=lines) s = s.format_map(d) out.append(s) @@ -1729,9 +1686,9 @@ def render_function( for name, destination in clinic.destination_buffers.items(): template = templates[name] if has_option_groups: - template = linear_format(template, + template = libclinic.linear_format(template, option_group_parsing=template_dict['option_group_parsing']) - template = linear_format(template, + template = libclinic.linear_format(template, declarations=template_dict['declarations'], return_conversion=template_dict['return_conversion'], initializers=template_dict['initializers'], @@ -1744,10 +1701,8 @@ def render_function( # Only generate the "exit:" label # if we have any gotos - need_exit_label = "goto exit;" in template - template = linear_format(template, - exit_label="exit:" if need_exit_label else '' - ) + label = "exit:" if "goto exit;" in template else "" + template = libclinic.linear_format(template, exit_label=label) s = template.format_map(template_dict) @@ -6125,9 +6080,9 @@ def format_docstring(self) -> str: parameters = self.format_docstring_parameters(params) signature = self.format_docstring_signature(f, params) docstring = "\n".join(lines) - return linear_format(docstring, - signature=signature, - parameters=parameters).rstrip() + return libclinic.linear_format(docstring, + signature=signature, + parameters=parameters).rstrip() def check_remaining_star(self, lineno: int | None = None) -> None: assert isinstance(self.function, Function) diff --git a/Tools/clinic/libclinic/__init__.py b/Tools/clinic/libclinic/__init__.py index 1b300b55acc21e6..6237809764d9e1f 100644 --- a/Tools/clinic/libclinic/__init__.py +++ b/Tools/clinic/libclinic/__init__.py @@ -9,6 +9,7 @@ docstring_for_c_string, format_escape, indent_all_lines, + linear_format, normalize_snippet, pprint_words, suffix_all_lines, @@ -33,6 +34,7 @@ "docstring_for_c_string", "format_escape", "indent_all_lines", + "linear_format", "normalize_snippet", "pprint_words", "suffix_all_lines", diff --git a/Tools/clinic/libclinic/formatting.py b/Tools/clinic/libclinic/formatting.py index 8b3ad7ba566bc84..873ece6210017a8 100644 --- a/Tools/clinic/libclinic/formatting.py +++ b/Tools/clinic/libclinic/formatting.py @@ -4,6 +4,8 @@ import textwrap from typing import Final +from libclinic import ClinicError + SIG_END_MARKER: Final = "--" @@ -171,3 +173,51 @@ def wrap_declarations(text: str, length: int = 78) -> str: lines.append(line.rstrip()) prefix = spaces return "\n".join(lines) + + +def linear_format(text: str, **kwargs: str) -> str: + """ + Perform str.format-like substitution, except: + * The strings substituted must be on lines by + themselves. (This line is the "source line".) + * If the substitution text is empty, the source line + is removed in the output. + * If the field is not recognized, the original line + is passed unmodified through to the output. + * If the substitution text is not empty: + * Each line of the substituted text is indented + by the indent of the source line. + * A newline will be added to the end. + """ + lines = [] + for line in text.split("\n"): + indent, curly, trailing = line.partition("{") + if not curly: + lines.extend([line, "\n"]) + continue + + name, curly, trailing = trailing.partition("}") + if not curly or name not in kwargs: + lines.extend([line, "\n"]) + continue + + if trailing: + raise ClinicError( + f"Text found after '{{{name}}}' block marker! " + "It must be on a line by itself." + ) + if indent.strip(): + raise ClinicError( + f"Non-whitespace characters found before '{{{name}}}' block marker! " + "It must be on a line by itself." + ) + + value = kwargs[name] + if not value: + continue + + stripped = [line.rstrip() for line in value.split("\n")] + value = textwrap.indent("\n".join(stripped), indent) + lines.extend([value, "\n"]) + + return "".join(lines[:-1]) From bce693111bff906ccf9281c22371331aaff766ab Mon Sep 17 00:00:00 2001 From: David Benjamin <davidben@google.com> Date: Thu, 15 Feb 2024 19:24:51 -0500 Subject: [PATCH 303/507] gh-114572: Fix locking in cert_store_stats and get_ca_certs (#114573) * gh-114572: Fix locking in cert_store_stats and get_ca_certs cert_store_stats and get_ca_certs query the SSLContext's X509_STORE with X509_STORE_get0_objects, but reading the result requires a lock. See https://github.com/openssl/openssl/pull/23224 for details. Instead, use X509_STORE_get1_objects, newly added in that PR. X509_STORE_get1_objects does not exist in current OpenSSLs, but we can polyfill it with X509_STORE_lock and X509_STORE_unlock. * Work around const-correctness problem * Add missing X509_STORE_get1_objects failure check * Add blurb --- ...-01-26-22-14-09.gh-issue-114572.t1QMQD.rst | 4 ++ Modules/_ssl.c | 65 +++++++++++++++++-- 2 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Security/2024-01-26-22-14-09.gh-issue-114572.t1QMQD.rst diff --git a/Misc/NEWS.d/next/Security/2024-01-26-22-14-09.gh-issue-114572.t1QMQD.rst b/Misc/NEWS.d/next/Security/2024-01-26-22-14-09.gh-issue-114572.t1QMQD.rst new file mode 100644 index 000000000000000..b4f9fe64db06157 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2024-01-26-22-14-09.gh-issue-114572.t1QMQD.rst @@ -0,0 +1,4 @@ +:meth:`ssl.SSLContext.cert_store_stats` and +:meth:`ssl.SSLContext.get_ca_certs` now correctly lock access to the +certificate store, when the :class:`ssl.SSLContext` is shared across +multiple threads. diff --git a/Modules/_ssl.c b/Modules/_ssl.c index bc3029094242278..950ee3663080e1e 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -4553,6 +4553,50 @@ set_sni_callback(PySSLContext *self, PyObject *arg, void *c) return 0; } +#if OPENSSL_VERSION_NUMBER < 0x30300000L +static X509_OBJECT *x509_object_dup(const X509_OBJECT *obj) +{ + int ok; + X509_OBJECT *ret = X509_OBJECT_new(); + if (ret == NULL) { + return NULL; + } + switch (X509_OBJECT_get_type(obj)) { + case X509_LU_X509: + ok = X509_OBJECT_set1_X509(ret, X509_OBJECT_get0_X509(obj)); + break; + case X509_LU_CRL: + /* X509_OBJECT_get0_X509_CRL was not const-correct prior to 3.0.*/ + ok = X509_OBJECT_set1_X509_CRL( + ret, X509_OBJECT_get0_X509_CRL((X509_OBJECT *)obj)); + break; + default: + /* We cannot duplicate unrecognized types in a polyfill, but it is + * safe to leave an empty object. The caller will ignore it. */ + ok = 1; + break; + } + if (!ok) { + X509_OBJECT_free(ret); + return NULL; + } + return ret; +} + +static STACK_OF(X509_OBJECT) * +X509_STORE_get1_objects(X509_STORE *store) +{ + STACK_OF(X509_OBJECT) *ret; + if (!X509_STORE_lock(store)) { + return NULL; + } + ret = sk_X509_OBJECT_deep_copy(X509_STORE_get0_objects(store), + x509_object_dup, X509_OBJECT_free); + X509_STORE_unlock(store); + return ret; +} +#endif + PyDoc_STRVAR(PySSLContext_sni_callback_doc, "Set a callback that will be called when a server name is provided by the SSL/TLS client in the SNI extension.\n\ \n\ @@ -4582,7 +4626,12 @@ _ssl__SSLContext_cert_store_stats_impl(PySSLContext *self) int x509 = 0, crl = 0, ca = 0, i; store = SSL_CTX_get_cert_store(self->ctx); - objs = X509_STORE_get0_objects(store); + objs = X509_STORE_get1_objects(store); + if (objs == NULL) { + PyErr_SetString(PyExc_MemoryError, "failed to query cert store"); + return NULL; + } + for (i = 0; i < sk_X509_OBJECT_num(objs); i++) { obj = sk_X509_OBJECT_value(objs, i); switch (X509_OBJECT_get_type(obj)) { @@ -4596,12 +4645,11 @@ _ssl__SSLContext_cert_store_stats_impl(PySSLContext *self) crl++; break; default: - /* Ignore X509_LU_FAIL, X509_LU_RETRY, X509_LU_PKEY. - * As far as I can tell they are internal states and never - * stored in a cert store */ + /* Ignore unrecognized types. */ break; } } + sk_X509_OBJECT_pop_free(objs, X509_OBJECT_free); return Py_BuildValue("{sisisi}", "x509", x509, "crl", crl, "x509_ca", ca); } @@ -4633,7 +4681,12 @@ _ssl__SSLContext_get_ca_certs_impl(PySSLContext *self, int binary_form) } store = SSL_CTX_get_cert_store(self->ctx); - objs = X509_STORE_get0_objects(store); + objs = X509_STORE_get1_objects(store); + if (objs == NULL) { + PyErr_SetString(PyExc_MemoryError, "failed to query cert store"); + goto error; + } + for (i = 0; i < sk_X509_OBJECT_num(objs); i++) { X509_OBJECT *obj; X509 *cert; @@ -4661,9 +4714,11 @@ _ssl__SSLContext_get_ca_certs_impl(PySSLContext *self, int binary_form) } Py_CLEAR(ci); } + sk_X509_OBJECT_pop_free(objs, X509_OBJECT_free); return rlist; error: + sk_X509_OBJECT_pop_free(objs, X509_OBJECT_free); Py_XDECREF(ci); Py_XDECREF(rlist); return NULL; From 454d7963e31cded1de3a90642da7259848efd232 Mon Sep 17 00:00:00 2001 From: Dino Viehland <dinoviehland@meta.com> Date: Thu, 15 Feb 2024 16:28:31 -0800 Subject: [PATCH 304/507] gh-113743: Use per-interpreter locks for types (#115541) Move type-lock to per-interpreter lock to avoid heavy contention in interpreters test --- Include/internal/pycore_typeobject.h | 2 +- Objects/typeobject.c | 5 +++-- Python/pystate.c | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index 664f6fb212a57d2..9134ab45cd00395 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -22,7 +22,6 @@ struct _types_runtime_state { // bpo-42745: next_version_tag remains shared by all interpreters // because of static types. unsigned int next_version_tag; - PyMutex type_mutex; }; @@ -71,6 +70,7 @@ struct types_state { struct type_cache type_cache; size_t num_builtins_initialized; static_builtin_state builtins[_Py_MAX_STATIC_BUILTIN_TYPES]; + PyMutex mutex; }; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index e0711dfe8545b7e..0118ee255ef0177 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -60,17 +60,18 @@ class object "PyObject *" "&PyBaseObject_Type" // in odd behaviors w.r.t. running with the GIL as the outer type lock could // be released and reacquired during a subclass update if there's contention // on the subclass lock. +#define TYPE_LOCK &PyInterpreterState_Get()->types.mutex #define BEGIN_TYPE_LOCK() \ { \ _PyCriticalSection _cs; \ - _PyCriticalSection_Begin(&_cs, &_PyRuntime.types.type_mutex); \ + _PyCriticalSection_Begin(&_cs, TYPE_LOCK); \ #define END_TYPE_LOCK() \ _PyCriticalSection_End(&_cs); \ } #define ASSERT_TYPE_LOCK_HELD() \ - _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(&_PyRuntime.types.type_mutex) + _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(TYPE_LOCK) #else diff --git a/Python/pystate.c b/Python/pystate.c index b1d1a085d629b4c..24f9b7790915ab5 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -395,7 +395,7 @@ _Py_COMP_DIAG_POP &(runtime)->atexit.mutex, \ &(runtime)->audit_hooks.mutex, \ &(runtime)->allocators.mutex, \ - &(runtime)->types.type_mutex, \ + &(runtime)->_main_interpreter.types.mutex, \ } static void From 321d13fd2b858d76cd4cbd03e6d8c4cba307449e Mon Sep 17 00:00:00 2001 From: Donghee Na <donghee.na@python.org> Date: Fri, 16 Feb 2024 10:01:36 +0900 Subject: [PATCH 305/507] gh-111968: Split _Py_dictkeys_freelist out of _Py_dict_freelist (gh-115505) --- Include/internal/pycore_freelist.h | 23 +++++++++----- Objects/dictobject.c | 51 ++++++++++++++++++------------ Objects/floatobject.c | 12 +++---- Objects/listobject.c | 6 ++-- Objects/tupleobject.c | 12 +++---- Python/context.c | 12 +++---- Python/object_stack.c | 20 ++++++------ 7 files changed, 77 insertions(+), 59 deletions(-) diff --git a/Include/internal/pycore_freelist.h b/Include/internal/pycore_freelist.h index b365ca337eabc87..9900ce98037060d 100644 --- a/Include/internal/pycore_freelist.h +++ b/Include/internal/pycore_freelist.h @@ -35,7 +35,7 @@ extern "C" { struct _Py_list_freelist { #ifdef WITH_FREELISTS - PyListObject *free_list[PyList_MAXFREELIST]; + PyListObject *items[PyList_MAXFREELIST]; int numfree; #endif }; @@ -50,7 +50,7 @@ struct _Py_tuple_freelist { object is used as the linked list node, with its first item (ob_item[0]) pointing to the next node (i.e. the previous head). Each linked list is initially NULL. */ - PyTupleObject *free_list[PyTuple_NFREELISTS]; + PyTupleObject *items[PyTuple_NFREELISTS]; int numfree[PyTuple_NFREELISTS]; #else char _unused; // Empty structs are not allowed. @@ -63,17 +63,23 @@ struct _Py_float_freelist { free_list is a singly-linked list of available PyFloatObjects, linked via abuse of their ob_type members. */ int numfree; - PyFloatObject *free_list; + PyFloatObject *items; #endif }; struct _Py_dict_freelist { #ifdef WITH_FREELISTS /* Dictionary reuse scheme to save calls to malloc and free */ - PyDictObject *free_list[PyDict_MAXFREELIST]; - PyDictKeysObject *keys_free_list[PyDict_MAXFREELIST]; + PyDictObject *items[PyDict_MAXFREELIST]; + int numfree; +#endif +}; + +struct _Py_dictkeys_freelist { +#ifdef WITH_FREELISTS + /* Dictionary keys reuse scheme to save calls to malloc and free */ + PyDictKeysObject *items[PyDict_MAXFREELIST]; int numfree; - int keys_numfree; #endif }; @@ -88,7 +94,7 @@ struct _Py_slice_freelist { struct _Py_context_freelist { #ifdef WITH_FREELISTS // List of free PyContext objects - PyContext *freelist; + PyContext *items; int numfree; #endif }; @@ -110,7 +116,7 @@ struct _Py_async_gen_freelist { struct _PyObjectStackChunk; struct _Py_object_stack_freelist { - struct _PyObjectStackChunk *free_list; + struct _PyObjectStackChunk *items; Py_ssize_t numfree; }; @@ -119,6 +125,7 @@ struct _Py_object_freelists { struct _Py_tuple_freelist tuples; struct _Py_list_freelist lists; struct _Py_dict_freelist dicts; + struct _Py_dictkeys_freelist dictkeys; struct _Py_slice_freelist slices; struct _Py_context_freelist contexts; struct _Py_async_gen_freelist async_gens; diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 11667b07ecfb4bd..25ab21881f8f746 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -276,6 +276,13 @@ get_dict_freelist(void) struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); return &freelists->dicts; } + +static struct _Py_dictkeys_freelist * +get_dictkeys_freelist(void) +{ + struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); + return &freelists->dictkeys; +} #endif @@ -283,18 +290,19 @@ void _PyDict_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalization) { #ifdef WITH_FREELISTS - struct _Py_dict_freelist *state = &freelists->dicts; - while (state->numfree > 0) { - PyDictObject *op = state->free_list[--state->numfree]; + struct _Py_dict_freelist *freelist = &freelists->dicts; + while (freelist->numfree > 0) { + PyDictObject *op = freelist->items[--freelist->numfree]; assert(PyDict_CheckExact(op)); PyObject_GC_Del(op); } - while (state->keys_numfree > 0) { - PyMem_Free(state->keys_free_list[--state->keys_numfree]); + struct _Py_dictkeys_freelist *keys_freelist = &freelists->dictkeys; + while (keys_freelist->numfree > 0) { + PyMem_Free(keys_freelist->items[--keys_freelist->numfree]); } if (is_finalization) { - state->numfree = -1; - state->keys_numfree = -1; + freelist->numfree = -1; + keys_freelist->numfree = -1; } #endif } @@ -314,6 +322,9 @@ _PyDict_DebugMallocStats(FILE *out) struct _Py_dict_freelist *dict_freelist = get_dict_freelist(); _PyDebugAllocatorStats(out, "free PyDictObject", dict_freelist->numfree, sizeof(PyDictObject)); + struct _Py_dictkeys_freelist *dictkeys_freelist = get_dictkeys_freelist(); + _PyDebugAllocatorStats(out, "free PyDictKeysObject", + dictkeys_freelist->numfree, sizeof(PyDictKeysObject)); #endif } @@ -663,9 +674,9 @@ new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode) } #ifdef WITH_FREELISTS - struct _Py_dict_freelist *dict_freelist = get_dict_freelist(); - if (log2_size == PyDict_LOG_MINSIZE && unicode && dict_freelist->keys_numfree > 0) { - dk = dict_freelist->keys_free_list[--dict_freelist->keys_numfree]; + struct _Py_dictkeys_freelist *freelist = get_dictkeys_freelist(); + if (log2_size == PyDict_LOG_MINSIZE && unicode && freelist->numfree > 0) { + dk = freelist->items[--freelist->numfree]; OBJECT_STAT_INC(from_freelist); } else @@ -698,12 +709,12 @@ static void free_keys_object(PyDictKeysObject *keys) { #ifdef WITH_FREELISTS - struct _Py_dict_freelist *dict_freelist = get_dict_freelist(); + struct _Py_dictkeys_freelist *freelist = get_dictkeys_freelist(); if (DK_LOG_SIZE(keys) == PyDict_LOG_MINSIZE - && dict_freelist->keys_numfree < PyDict_MAXFREELIST - && dict_freelist->keys_numfree >= 0 + && freelist->numfree < PyDict_MAXFREELIST + && freelist->numfree >= 0 && DK_IS_UNICODE(keys)) { - dict_freelist->keys_free_list[dict_freelist->keys_numfree++] = keys; + freelist->items[freelist->numfree++] = keys; OBJECT_STAT_INC(to_freelist); return; } @@ -743,9 +754,9 @@ new_dict(PyInterpreterState *interp, PyDictObject *mp; assert(keys != NULL); #ifdef WITH_FREELISTS - struct _Py_dict_freelist *dict_freelist = get_dict_freelist(); - if (dict_freelist->numfree > 0) { - mp = dict_freelist->free_list[--dict_freelist->numfree]; + struct _Py_dict_freelist *freelist = get_dict_freelist(); + if (freelist->numfree > 0) { + mp = freelist->items[--freelist->numfree]; assert (mp != NULL); assert (Py_IS_TYPE(mp, &PyDict_Type)); OBJECT_STAT_INC(from_freelist); @@ -2593,10 +2604,10 @@ dict_dealloc(PyObject *self) dictkeys_decref(interp, keys); } #ifdef WITH_FREELISTS - struct _Py_dict_freelist *dict_freelist = get_dict_freelist(); - if (dict_freelist->numfree < PyDict_MAXFREELIST && dict_freelist->numfree >=0 && + struct _Py_dict_freelist *freelist = get_dict_freelist(); + if (freelist->numfree < PyDict_MAXFREELIST && freelist->numfree >=0 && Py_IS_TYPE(mp, &PyDict_Type)) { - dict_freelist->free_list[dict_freelist->numfree++] = mp; + freelist->items[freelist->numfree++] = mp; OBJECT_STAT_INC(to_freelist); } else diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 7dac8292c7232bd..37d2d312a6a0b78 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -130,9 +130,9 @@ PyFloat_FromDouble(double fval) PyFloatObject *op; #ifdef WITH_FREELISTS struct _Py_float_freelist *float_freelist = get_float_freelist(); - op = float_freelist->free_list; + op = float_freelist->items; if (op != NULL) { - float_freelist->free_list = (PyFloatObject *) Py_TYPE(op); + float_freelist->items = (PyFloatObject *) Py_TYPE(op); float_freelist->numfree--; OBJECT_STAT_INC(from_freelist); } @@ -251,8 +251,8 @@ _PyFloat_ExactDealloc(PyObject *obj) return; } float_freelist->numfree++; - Py_SET_TYPE(op, (PyTypeObject *)float_freelist->free_list); - float_freelist->free_list = op; + Py_SET_TYPE(op, (PyTypeObject *)float_freelist->items); + float_freelist->items = op; OBJECT_STAT_INC(to_freelist); #else PyObject_Free(op); @@ -1994,13 +1994,13 @@ _PyFloat_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalizati { #ifdef WITH_FREELISTS struct _Py_float_freelist *state = &freelists->floats; - PyFloatObject *f = state->free_list; + PyFloatObject *f = state->items; while (f != NULL) { PyFloatObject *next = (PyFloatObject*) Py_TYPE(f); PyObject_Free(f); f = next; } - state->free_list = NULL; + state->items = NULL; if (is_finalization) { state->numfree = -1; } diff --git a/Objects/listobject.c b/Objects/listobject.c index 96182a42306d957..eb466260318ec16 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -133,7 +133,7 @@ _PyList_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalizatio #ifdef WITH_FREELISTS struct _Py_list_freelist *state = &freelists->lists; while (state->numfree > 0) { - PyListObject *op = state->free_list[--state->numfree]; + PyListObject *op = state->items[--state->numfree]; assert(PyList_CheckExact(op)); PyObject_GC_Del(op); } @@ -169,7 +169,7 @@ PyList_New(Py_ssize_t size) struct _Py_list_freelist *list_freelist = get_list_freelist(); if (PyList_MAXFREELIST && list_freelist->numfree > 0) { list_freelist->numfree--; - op = list_freelist->free_list[list_freelist->numfree]; + op = list_freelist->items[list_freelist->numfree]; OBJECT_STAT_INC(from_freelist); _Py_NewReference((PyObject *)op); } @@ -401,7 +401,7 @@ list_dealloc(PyObject *self) #ifdef WITH_FREELISTS struct _Py_list_freelist *list_freelist = get_list_freelist(); if (list_freelist->numfree < PyList_MAXFREELIST && list_freelist->numfree >= 0 && PyList_CheckExact(op)) { - list_freelist->free_list[list_freelist->numfree++] = op; + list_freelist->items[list_freelist->numfree++] = op; OBJECT_STAT_INC(to_freelist); } else diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 1cdf79d95ae352d..d9dc00da368a84c 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -1134,11 +1134,11 @@ maybe_freelist_pop(Py_ssize_t size) assert(size > 0); if (size < PyTuple_MAXSAVESIZE) { Py_ssize_t index = size - 1; - PyTupleObject *op = TUPLE_FREELIST.free_list[index]; + PyTupleObject *op = TUPLE_FREELIST.items[index]; if (op != NULL) { /* op is the head of a linked list, with the first item pointing to the next node. Here we pop off the old head. */ - TUPLE_FREELIST.free_list[index] = (PyTupleObject *) op->ob_item[0]; + TUPLE_FREELIST.items[index] = (PyTupleObject *) op->ob_item[0]; TUPLE_FREELIST.numfree[index]--; /* Inlined _PyObject_InitVar() without _PyType_HasFeature() test */ #ifdef Py_TRACE_REFS @@ -1173,8 +1173,8 @@ maybe_freelist_push(PyTupleObject *op) { /* op is the head of a linked list, with the first item pointing to the next node. Here we set op as the new head. */ - op->ob_item[0] = (PyObject *) TUPLE_FREELIST.free_list[index]; - TUPLE_FREELIST.free_list[index] = op; + op->ob_item[0] = (PyObject *) TUPLE_FREELIST.items[index]; + TUPLE_FREELIST.items[index] = op; TUPLE_FREELIST.numfree[index]++; OBJECT_STAT_INC(to_freelist); return 1; @@ -1188,8 +1188,8 @@ maybe_freelist_clear(struct _Py_object_freelists *freelists, int fini) { #ifdef WITH_FREELISTS for (Py_ssize_t i = 0; i < PyTuple_NFREELISTS; i++) { - PyTupleObject *p = TUPLE_FREELIST.free_list[i]; - TUPLE_FREELIST.free_list[i] = NULL; + PyTupleObject *p = TUPLE_FREELIST.items[i]; + TUPLE_FREELIST.items[i] = NULL; TUPLE_FREELIST.numfree[i] = fini ? -1 : 0; while (p) { PyTupleObject *q = p; diff --git a/Python/context.c b/Python/context.c index 01a21b47da5452b..3937819b3c386cb 100644 --- a/Python/context.c +++ b/Python/context.c @@ -344,8 +344,8 @@ _context_alloc(void) struct _Py_context_freelist *context_freelist = get_context_freelist(); if (context_freelist->numfree > 0) { context_freelist->numfree--; - ctx = context_freelist->freelist; - context_freelist->freelist = (PyContext *)ctx->ctx_weakreflist; + ctx = context_freelist->items; + context_freelist->items = (PyContext *)ctx->ctx_weakreflist; OBJECT_STAT_INC(from_freelist); ctx->ctx_weakreflist = NULL; _Py_NewReference((PyObject *)ctx); @@ -471,8 +471,8 @@ context_tp_dealloc(PyContext *self) struct _Py_context_freelist *context_freelist = get_context_freelist(); if (context_freelist->numfree >= 0 && context_freelist->numfree < PyContext_MAXFREELIST) { context_freelist->numfree++; - self->ctx_weakreflist = (PyObject *)context_freelist->freelist; - context_freelist->freelist = self; + self->ctx_weakreflist = (PyObject *)context_freelist->items; + context_freelist->items = self; OBJECT_STAT_INC(to_freelist); } else @@ -1272,8 +1272,8 @@ _PyContext_ClearFreeList(struct _Py_object_freelists *freelists, int is_finaliza #ifdef WITH_FREELISTS struct _Py_context_freelist *state = &freelists->contexts; for (; state->numfree > 0; state->numfree--) { - PyContext *ctx = state->freelist; - state->freelist = (PyContext *)ctx->ctx_weakreflist; + PyContext *ctx = state->items; + state->items = (PyContext *)ctx->ctx_weakreflist; ctx->ctx_weakreflist = NULL; PyObject_GC_Del(ctx); } diff --git a/Python/object_stack.c b/Python/object_stack.c index ff2901cdacceb82..bd9696822c8a9d4 100644 --- a/Python/object_stack.c +++ b/Python/object_stack.c @@ -21,8 +21,8 @@ _PyObjectStackChunk_New(void) _PyObjectStackChunk *buf; struct _Py_object_stack_freelist *obj_stack_freelist = get_object_stack_freelist(); if (obj_stack_freelist->numfree > 0) { - buf = obj_stack_freelist->free_list; - obj_stack_freelist->free_list = buf->prev; + buf = obj_stack_freelist->items; + obj_stack_freelist->items = buf->prev; obj_stack_freelist->numfree--; } else { @@ -47,8 +47,8 @@ _PyObjectStackChunk_Free(_PyObjectStackChunk *buf) if (obj_stack_freelist->numfree >= 0 && obj_stack_freelist->numfree < _PyObjectStackChunk_MAXFREELIST) { - buf->prev = obj_stack_freelist->free_list; - obj_stack_freelist->free_list = buf; + buf->prev = obj_stack_freelist->items; + obj_stack_freelist->items = buf; obj_stack_freelist->numfree++; } else { @@ -97,12 +97,12 @@ _PyObjectStackChunk_ClearFreeList(struct _Py_object_freelists *freelists, int is return; } - struct _Py_object_stack_freelist *state = &freelists->object_stacks; - while (state->numfree > 0) { - _PyObjectStackChunk *buf = state->free_list; - state->free_list = buf->prev; - state->numfree--; + struct _Py_object_stack_freelist *freelist = &freelists->object_stacks; + while (freelist->numfree > 0) { + _PyObjectStackChunk *buf = freelist->items; + freelist->items = buf->prev; + freelist->numfree--; PyMem_RawFree(buf); } - state->numfree = -1; + freelist->numfree = -1; } From 20eaf4d5dff7fa20f8a745450fef760f0923eb52 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Fri, 16 Feb 2024 08:33:17 +0300 Subject: [PATCH 306/507] gh-115503: Fix `run_presite` error handling (#115504) --- Python/pylifecycle.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 5e5db98481150e7..b354c033ae77274 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1109,7 +1109,6 @@ run_presite(PyThreadState *tstate) ); if (presite_modname == NULL) { fprintf(stderr, "Could not convert pre-site module name to unicode\n"); - Py_DECREF(presite_modname); } else { PyObject *presite = PyImport_Import(presite_modname); From 351c103134e43c2ee43deb10cdc9afb37b916a4e Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Fri, 16 Feb 2024 07:42:15 +0100 Subject: [PATCH 307/507] gh-113317: Argument Clinic: move C/Py identifier helpers into libclinic (#115520) --- Tools/clinic/clinic.py | 33 ++++----------------------- Tools/clinic/libclinic/__init__.py | 10 ++++++++ Tools/clinic/libclinic/identifiers.py | 31 +++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 29 deletions(-) create mode 100644 Tools/clinic/libclinic/identifiers.py diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 4925f27b2937b19..5d2617b3bd579fc 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -138,31 +138,6 @@ def fail( warn_or_fail(*args, filename=filename, line_number=line_number, fail=True) -is_legal_c_identifier = re.compile('^[A-Za-z_][A-Za-z0-9_]*$').match - -def is_legal_py_identifier(s: str) -> bool: - return all(is_legal_c_identifier(field) for field in s.split('.')) - -# identifiers that are okay in Python but aren't a good idea in C. -# so if they're used Argument Clinic will add "_value" to the end -# of the name in C. -c_keywords = set(""" -asm auto break case char const continue default do double -else enum extern float for goto if inline int long -register return short signed sizeof static struct switch -typedef typeof union unsigned void volatile while -""".strip().split()) - -def ensure_legal_c_identifier(s: str) -> str: - # for now, just complain if what we're given isn't legal - if not is_legal_c_identifier(s): - fail("Illegal C identifier:", s) - # but if we picked a C keyword, pick something else - if s in c_keywords: - return s + "_value" - return s - - class CRenderData: def __init__(self) -> None: @@ -2954,7 +2929,7 @@ def __init__(self, unused: bool = False, **kwargs: Any ) -> None: - self.name = ensure_legal_c_identifier(name) + self.name = libclinic.ensure_legal_c_identifier(name) self.py_name = py_name self.unused = unused self.includes: list[Include] = [] @@ -5083,9 +5058,9 @@ def parse_function_names(self, line: str) -> FunctionNames: if fields[-1] == '__new__': fields.pop() c_basename = "_".join(fields) - if not is_legal_py_identifier(full_name): + if not libclinic.is_legal_py_identifier(full_name): fail(f"Illegal function name: {full_name!r}") - if not is_legal_c_identifier(c_basename): + if not libclinic.is_legal_c_identifier(c_basename): fail(f"Illegal C basename: {c_basename!r}") names = FunctionNames(full_name=full_name, c_basename=c_basename) self.normalize_function_kind(names.full_name) @@ -5207,7 +5182,7 @@ def state_modulename_name(self, line: str) -> None: before, equals, existing = line.rpartition('=') if equals: existing = existing.strip() - if is_legal_py_identifier(existing): + if libclinic.is_legal_py_identifier(existing): # we're cloning! names = self.parse_function_names(before) return self.parse_cloned_function(names, existing) diff --git a/Tools/clinic/libclinic/__init__.py b/Tools/clinic/libclinic/__init__.py index 6237809764d9e1f..738864a48c08d3f 100644 --- a/Tools/clinic/libclinic/__init__.py +++ b/Tools/clinic/libclinic/__init__.py @@ -16,6 +16,11 @@ wrap_declarations, wrapped_c_string_literal, ) +from .identifiers import ( + ensure_legal_c_identifier, + is_legal_c_identifier, + is_legal_py_identifier, +) from .utils import ( FormatCounterFormatter, compute_checksum, @@ -41,6 +46,11 @@ "wrap_declarations", "wrapped_c_string_literal", + # Identifier helpers + "ensure_legal_c_identifier", + "is_legal_c_identifier", + "is_legal_py_identifier", + # Utility functions "FormatCounterFormatter", "compute_checksum", diff --git a/Tools/clinic/libclinic/identifiers.py b/Tools/clinic/libclinic/identifiers.py new file mode 100644 index 000000000000000..d3b80bbcef3b2b2 --- /dev/null +++ b/Tools/clinic/libclinic/identifiers.py @@ -0,0 +1,31 @@ +import re +from .errors import ClinicError + + +is_legal_c_identifier = re.compile("^[A-Za-z_][A-Za-z0-9_]*$").match + + +def is_legal_py_identifier(identifier: str) -> bool: + return all(is_legal_c_identifier(field) for field in identifier.split(".")) + + +# Identifiers that are okay in Python but aren't a good idea in C. +# So if they're used Argument Clinic will add "_value" to the end +# of the name in C. +_c_keywords = frozenset(""" +asm auto break case char const continue default do double +else enum extern float for goto if inline int long +register return short signed sizeof static struct switch +typedef typeof union unsigned void volatile while +""".strip().split() +) + + +def ensure_legal_c_identifier(identifier: str) -> str: + # For now, just complain if what we're given isn't legal. + if not is_legal_c_identifier(identifier): + raise ClinicError(f"Illegal C identifier: {identifier}") + # But if we picked a C keyword, pick something else. + if identifier in _c_keywords: + return identifier + "_value" + return identifier From 2a7a0020c9d006d268b839320979364498a5f0e6 Mon Sep 17 00:00:00 2001 From: Furkan Onder <furkanonder@protonmail.com> Date: Fri, 16 Feb 2024 15:03:46 +0300 Subject: [PATCH 308/507] gh-69990: Make Profile.print_stats support sorting by multiple values (GH-104590) Co-authored-by: Chiu-Hsiang Hsu --- Doc/library/profile.rst | 7 +++++++ Lib/cProfile.py | 4 +++- Lib/profile.py | 5 +++-- Lib/test/test_profile.py | 7 ++++++- .../Library/2023-05-17-21-33-21.gh-issue-69990.Blvz9G.rst | 1 + 5 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-05-17-21-33-21.gh-issue-69990.Blvz9G.rst diff --git a/Doc/library/profile.rst b/Doc/library/profile.rst index cc059b66fcb84b5..3ca802e024bc27f 100644 --- a/Doc/library/profile.rst +++ b/Doc/library/profile.rst @@ -299,6 +299,13 @@ functions: Create a :class:`~pstats.Stats` object based on the current profile and print the results to stdout. + The *sort* parameter specifies the sorting order of the displayed + statistics. It accepts a single key or a tuple of keys to enable + multi-level sorting, as in :func:`Stats.sort_stats <pstats.Stats.sort_stats>`. + + .. versionadded:: 3.13 + :meth:`~Profile.print_stats` now accepts a tuple of keys. + .. method:: dump_stats(filename) Write the results of the current profile to *filename*. diff --git a/Lib/cProfile.py b/Lib/cProfile.py index 135a12c3965c00e..9c132372dc4ee04 100755 --- a/Lib/cProfile.py +++ b/Lib/cProfile.py @@ -41,7 +41,9 @@ class Profile(_lsprof.Profiler): def print_stats(self, sort=-1): import pstats - pstats.Stats(self).strip_dirs().sort_stats(sort).print_stats() + if not isinstance(sort, tuple): + sort = (sort,) + pstats.Stats(self).strip_dirs().sort_stats(*sort).print_stats() def dump_stats(self, file): import marshal diff --git a/Lib/profile.py b/Lib/profile.py index 4b82523b03d64b0..f2f8c2f21333e0f 100755 --- a/Lib/profile.py +++ b/Lib/profile.py @@ -387,8 +387,9 @@ def simulate_cmd_complete(self): def print_stats(self, sort=-1): import pstats - pstats.Stats(self).strip_dirs().sort_stats(sort). \ - print_stats() + if not isinstance(sort, tuple): + sort = (sort,) + pstats.Stats(self).strip_dirs().sort_stats(*sort).print_stats() def dump_stats(self, file): with open(file, 'wb') as f: diff --git a/Lib/test/test_profile.py b/Lib/test/test_profile.py index a1dfc9abbb8ef75..0f16b92334999ce 100644 --- a/Lib/test/test_profile.py +++ b/Lib/test/test_profile.py @@ -7,7 +7,7 @@ from difflib import unified_diff from io import StringIO from test.support.os_helper import TESTFN, unlink, temp_dir, change_cwd -from contextlib import contextmanager +from contextlib import contextmanager, redirect_stdout import profile from test.profilee import testfunc, timer @@ -92,6 +92,11 @@ def test_run(self): self.profilermodule.run("int('1')", filename=TESTFN) self.assertTrue(os.path.exists(TESTFN)) + def test_run_with_sort_by_values(self): + with redirect_stdout(StringIO()) as f: + self.profilermodule.run("int('1')", sort=('tottime', 'stdname')) + self.assertIn("Ordered by: internal time, standard name", f.getvalue()) + def test_runctx(self): with silent(): self.profilermodule.runctx("testfunc()", globals(), locals()) diff --git a/Misc/NEWS.d/next/Library/2023-05-17-21-33-21.gh-issue-69990.Blvz9G.rst b/Misc/NEWS.d/next/Library/2023-05-17-21-33-21.gh-issue-69990.Blvz9G.rst new file mode 100644 index 000000000000000..b0cdf44f7b9e391 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-05-17-21-33-21.gh-issue-69990.Blvz9G.rst @@ -0,0 +1 @@ +:meth:`Profile.print_stats` has been improved to accept multiple sort arguments. Patched by Chiu-Hsiang Hsu and Furkan Onder. From b178eae3a676473e1c2287a46b4941fc0bcd193f Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Fri, 16 Feb 2024 16:10:21 +0300 Subject: [PATCH 309/507] Add `Python/tier2_redundancy_eliminator_cases.c.h` to `.gitattributes` as generated (#115551) --- .gitattributes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 07d877027b09f68..c984797e1ac7c60 100644 --- a/.gitattributes +++ b/.gitattributes @@ -94,7 +94,7 @@ Programs/test_frozenmain.h generated Python/Python-ast.c generated Python/executor_cases.c.h generated Python/generated_cases.c.h generated -Python/tier2_redundancy_eliminator_bytecodes.c.h generated +Python/tier2_redundancy_eliminator_cases.c.h generated Python/opcode_targets.h generated Python/stdlib_module_names.h generated Tools/peg_generator/pegen/grammar_parser.py generated From 144eb5605b445d22729db6c416d03cc24947ba56 Mon Sep 17 00:00:00 2001 From: Victor Stinner <vstinner@python.org> Date: Fri, 16 Feb 2024 15:49:13 +0100 Subject: [PATCH 310/507] gh-102013: Move PyUnstable_GC_VisitObjects() to Include/cpython/objimpl.h (#115560) Include/objimpl.h must only contain the limited C API, whereas PyUnstable_GC_VisitObjects() is excluded from the limited C API. --- Include/cpython/objimpl.h | 17 +++++++++++++++++ Include/objimpl.h | 19 ------------------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/Include/cpython/objimpl.h b/Include/cpython/objimpl.h index 58a30aeea6ac642..e0c2ce286f13ce3 100644 --- a/Include/cpython/objimpl.h +++ b/Include/cpython/objimpl.h @@ -85,3 +85,20 @@ PyAPI_FUNC(PyObject **) PyObject_GET_WEAKREFS_LISTPTR(PyObject *op); PyAPI_FUNC(PyObject *) PyUnstable_Object_GC_NewWithExtraData(PyTypeObject *, size_t); + + +/* Visit all live GC-capable objects, similar to gc.get_objects(None). The + * supplied callback is called on every such object with the void* arg set + * to the supplied arg. Returning 0 from the callback ends iteration, returning + * 1 allows iteration to continue. Returning any other value may result in + * undefined behaviour. + * + * If new objects are (de)allocated by the callback it is undefined if they + * will be visited. + + * Garbage collection is disabled during operation. Explicitly running a + * collection in the callback may lead to undefined behaviour e.g. visiting the + * same objects multiple times or not at all. + */ +typedef int (*gcvisitobjects_t)(PyObject*, void*); +PyAPI_FUNC(void) PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void* arg); diff --git a/Include/objimpl.h b/Include/objimpl.h index ff5fa7a8c1d3d8d..56472a72e42d341 100644 --- a/Include/objimpl.h +++ b/Include/objimpl.h @@ -153,25 +153,6 @@ PyAPI_FUNC(int) PyGC_Enable(void); PyAPI_FUNC(int) PyGC_Disable(void); PyAPI_FUNC(int) PyGC_IsEnabled(void); - -#if !defined(Py_LIMITED_API) -/* Visit all live GC-capable objects, similar to gc.get_objects(None). The - * supplied callback is called on every such object with the void* arg set - * to the supplied arg. Returning 0 from the callback ends iteration, returning - * 1 allows iteration to continue. Returning any other value may result in - * undefined behaviour. - * - * If new objects are (de)allocated by the callback it is undefined if they - * will be visited. - - * Garbage collection is disabled during operation. Explicitly running a - * collection in the callback may lead to undefined behaviour e.g. visiting the - * same objects multiple times or not at all. - */ -typedef int (*gcvisitobjects_t)(PyObject*, void*); -PyAPI_FUNC(void) PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void* arg); -#endif - /* Test if a type has a GC head */ #define PyType_IS_GC(t) PyType_HasFeature((t), Py_TPFLAGS_HAVE_GC) From f92857a93016aa26ba93959d2bdb690ef52e7f07 Mon Sep 17 00:00:00 2001 From: Ken Jin <kenjin@python.org> Date: Fri, 16 Feb 2024 22:59:43 +0800 Subject: [PATCH 311/507] gh-115480: Minor fixups in int constant propagation (GH-115507) --- Python/optimizer_analysis.c | 17 ++-- .../tier2_redundancy_eliminator_bytecodes.c | 87 ++++++------------- Python/tier2_redundancy_eliminator_cases.c.h | 87 ++++++------------- 3 files changed, 59 insertions(+), 132 deletions(-) diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index d73bc310345f415..b104d2fa7baec95 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -588,16 +588,17 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, INST->oparg = ARG; \ INST->operand = OPERAND; +#define OUT_OF_SPACE_IF_NULL(EXPR) \ + do { \ + if ((EXPR) == NULL) { \ + goto out_of_space; \ + } \ + } while (0); + #define _LOAD_ATTR_NOT_NULL \ do { \ - attr = sym_new_known_notnull(ctx); \ - if (attr == NULL) { \ - goto error; \ - } \ - null = sym_new_null(ctx); \ - if (null == NULL) { \ - goto error; \ - } \ + OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); \ + OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); \ } while (0); diff --git a/Python/tier2_redundancy_eliminator_bytecodes.c b/Python/tier2_redundancy_eliminator_bytecodes.c index 39ea0eef6276326..6aae590a8e51e47 100644 --- a/Python/tier2_redundancy_eliminator_bytecodes.c +++ b/Python/tier2_redundancy_eliminator_bytecodes.c @@ -43,10 +43,8 @@ dummy_func(void) { op(_LOAD_FAST_AND_CLEAR, (-- value)) { value = GETLOCAL(oparg); - _Py_UOpsSymType *temp = sym_new_null(ctx); - if (temp == NULL) { - goto out_of_space; - } + _Py_UOpsSymType *temp; + OUT_OF_SPACE_IF_NULL(temp = sym_new_null(ctx)); GETLOCAL(oparg) = temp; } @@ -89,14 +87,12 @@ dummy_func(void) { if (temp == NULL) { goto error; } - res = sym_new_const(ctx, temp); - // TODO replace opcode with constant propagated one and add tests! + OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + // TODO gh-115506: + // replace opcode with constant propagated one and add tests! } else { - res = sym_new_known_type(ctx, &PyLong_Type); - if (res == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyLong_Type)); } } @@ -109,14 +105,12 @@ dummy_func(void) { if (temp == NULL) { goto error; } - res = sym_new_const(ctx, temp); - // TODO replace opcode with constant propagated one and add tests! + OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + // TODO gh-115506: + // replace opcode with constant propagated one and add tests! } else { - res = sym_new_known_type(ctx, &PyLong_Type); - if (res == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyLong_Type)); } } @@ -129,14 +123,12 @@ dummy_func(void) { if (temp == NULL) { goto error; } - res = sym_new_const(ctx, temp); - // TODO replace opcode with constant propagated one and add tests! + OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + // TODO gh-115506: + // replace opcode with constant propagated one and add tests! } else { - res = sym_new_known_type(ctx, &PyLong_Type); - if (res == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyLong_Type)); } } @@ -147,39 +139,21 @@ dummy_func(void) { } op(_LOAD_CONST_INLINE, (ptr/4 -- value)) { - value = sym_new_const(ctx, ptr); - if (value == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); } op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) { - value = sym_new_const(ctx, ptr); - if (value == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); } op(_LOAD_CONST_INLINE_WITH_NULL, (ptr/4 -- value, null)) { - value = sym_new_const(ctx, ptr); - if (value == NULL) { - goto out_of_space; - } - null = sym_new_null(ctx); - if (null == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); } op(_LOAD_CONST_INLINE_BORROW_WITH_NULL, (ptr/4 -- value, null)) { - value = sym_new_const(ctx, ptr); - if (value == NULL) { - goto out_of_space; - } - null = sym_new_null(ctx); - if (null == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); } @@ -261,10 +235,8 @@ dummy_func(void) { localsplus_start = args; n_locals_already_filled = argcount; } - new_frame = ctx_frame_new(ctx, co, localsplus_start, n_locals_already_filled, 0); - if (new_frame == NULL){ - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(new_frame = + ctx_frame_new(ctx, co, localsplus_start, n_locals_already_filled, 0)); } op(_POP_FRAME, (retval -- res)) { @@ -287,10 +259,7 @@ dummy_func(void) { /* This has to be done manually */ (void)seq; for (int i = 0; i < oparg; i++) { - values[i] = sym_new_unknown(ctx); - if (values[i] == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(values[i] = sym_new_unknown(ctx)); } } @@ -299,18 +268,12 @@ dummy_func(void) { (void)seq; int totalargs = (oparg & 0xFF) + (oparg >> 8) + 1; for (int i = 0; i < totalargs; i++) { - values[i] = sym_new_unknown(ctx); - if (values[i] == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(values[i] = sym_new_unknown(ctx)); } } op(_ITER_NEXT_RANGE, (iter -- iter, next)) { - next = sym_new_known_type(ctx, &PyLong_Type); - if (next == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(next = sym_new_known_type(ctx, &PyLong_Type)); (void)iter; } diff --git a/Python/tier2_redundancy_eliminator_cases.c.h b/Python/tier2_redundancy_eliminator_cases.c.h index a9617f51ef46158..d1301ff29ac5931 100644 --- a/Python/tier2_redundancy_eliminator_cases.c.h +++ b/Python/tier2_redundancy_eliminator_cases.c.h @@ -36,10 +36,8 @@ case _LOAD_FAST_AND_CLEAR: { _Py_UOpsSymType *value; value = GETLOCAL(oparg); - _Py_UOpsSymType *temp = sym_new_null(ctx); - if (temp == NULL) { - goto out_of_space; - } + _Py_UOpsSymType *temp; + OUT_OF_SPACE_IF_NULL(temp = sym_new_null(ctx)); GETLOCAL(oparg) = temp; stack_pointer[0] = value; stack_pointer += 1; @@ -193,14 +191,12 @@ if (temp == NULL) { goto error; } - res = sym_new_const(ctx, temp); - // TODO replace opcode with constant propagated one and add tests! + OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + // TODO gh-115506: + // replace opcode with constant propagated one and add tests! } else { - res = sym_new_known_type(ctx, &PyLong_Type); - if (res == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyLong_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -221,14 +217,12 @@ if (temp == NULL) { goto error; } - res = sym_new_const(ctx, temp); - // TODO replace opcode with constant propagated one and add tests! + OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + // TODO gh-115506: + // replace opcode with constant propagated one and add tests! } else { - res = sym_new_known_type(ctx, &PyLong_Type); - if (res == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyLong_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -249,14 +243,12 @@ if (temp == NULL) { goto error; } - res = sym_new_const(ctx, temp); - // TODO replace opcode with constant propagated one and add tests! + OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + // TODO gh-115506: + // replace opcode with constant propagated one and add tests! } else { - res = sym_new_known_type(ctx, &PyLong_Type); - if (res == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyLong_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -514,10 +506,7 @@ /* This has to be done manually */ (void)seq; for (int i = 0; i < oparg; i++) { - values[i] = sym_new_unknown(ctx); - if (values[i] == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(values[i] = sym_new_unknown(ctx)); } stack_pointer += -1 + oparg; break; @@ -565,10 +554,7 @@ (void)seq; int totalargs = (oparg & 0xFF) + (oparg >> 8) + 1; for (int i = 0; i < totalargs; i++) { - values[i] = sym_new_unknown(ctx); - if (values[i] == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(values[i] = sym_new_unknown(ctx)); } stack_pointer += (oparg >> 8) + (oparg & 0xFF); break; @@ -1153,10 +1139,7 @@ _Py_UOpsSymType *iter; _Py_UOpsSymType *next; iter = stack_pointer[-1]; - next = sym_new_known_type(ctx, &PyLong_Type); - if (next == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(next = sym_new_known_type(ctx, &PyLong_Type)); (void)iter; stack_pointer[0] = next; stack_pointer += 1; @@ -1359,10 +1342,8 @@ localsplus_start = args; n_locals_already_filled = argcount; } - new_frame = ctx_frame_new(ctx, co, localsplus_start, n_locals_already_filled, 0); - if (new_frame == NULL){ - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(new_frame = + ctx_frame_new(ctx, co, localsplus_start, n_locals_already_filled, 0)); stack_pointer[-2 - oparg] = (_Py_UOpsSymType *)new_frame; stack_pointer += -1 - oparg; break; @@ -1652,10 +1633,7 @@ case _LOAD_CONST_INLINE: { _Py_UOpsSymType *value; PyObject *ptr = (PyObject *)this_instr->operand; - value = sym_new_const(ctx, ptr); - if (value == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); stack_pointer[0] = value; stack_pointer += 1; break; @@ -1664,10 +1642,7 @@ case _LOAD_CONST_INLINE_BORROW: { _Py_UOpsSymType *value; PyObject *ptr = (PyObject *)this_instr->operand; - value = sym_new_const(ctx, ptr); - if (value == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); stack_pointer[0] = value; stack_pointer += 1; break; @@ -1677,14 +1652,8 @@ _Py_UOpsSymType *value; _Py_UOpsSymType *null; PyObject *ptr = (PyObject *)this_instr->operand; - value = sym_new_const(ctx, ptr); - if (value == NULL) { - goto out_of_space; - } - null = sym_new_null(ctx); - if (null == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); stack_pointer[0] = value; stack_pointer[1] = null; stack_pointer += 2; @@ -1695,14 +1664,8 @@ _Py_UOpsSymType *value; _Py_UOpsSymType *null; PyObject *ptr = (PyObject *)this_instr->operand; - value = sym_new_const(ctx, ptr); - if (value == NULL) { - goto out_of_space; - } - null = sym_new_null(ctx); - if (null == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); stack_pointer[0] = value; stack_pointer[1] = null; stack_pointer += 2; From b24c9161a651f549ed48f4b4dba8996fe9cc4e09 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Fri, 16 Feb 2024 11:22:27 -0500 Subject: [PATCH 312/507] gh-112529: Make the GC scheduling thread-safe (#114880) The GC keeps track of the number of allocations (less deallocations) since the last GC. This buffers the count in thread-local state and uses atomic operations to modify the per-interpreter count. The thread-local buffering avoids contention on shared state. A consequence is that the GC scheduling is not as precise, so "test_sneaky_frame_object" is skipped because it requires that the GC be run exactly after allocating a frame object. --- Include/internal/pycore_gc.h | 7 ++++ Include/internal/pycore_tstate.h | 1 + Lib/test/test_frame.py | 3 +- Lib/test/test_gc.py | 1 + Modules/gcmodule.c | 10 +++++ Objects/typeobject.c | 2 + Python/gc_free_threading.c | 63 ++++++++++++++++++++++++-------- 7 files changed, 71 insertions(+), 16 deletions(-) diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h index 582a16bf5218cef..a98864f74313987 100644 --- a/Include/internal/pycore_gc.h +++ b/Include/internal/pycore_gc.h @@ -260,6 +260,13 @@ struct _gc_runtime_state { Py_ssize_t long_lived_pending; }; +#ifdef Py_GIL_DISABLED +struct _gc_thread_state { + /* Thread-local allocation count. */ + Py_ssize_t alloc_count; +}; +#endif + extern void _PyGC_InitState(struct _gc_runtime_state *); diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h index 97aa85a659fa7b4..7fb9ab2056704e1 100644 --- a/Include/internal/pycore_tstate.h +++ b/Include/internal/pycore_tstate.h @@ -28,6 +28,7 @@ typedef struct _PyThreadStateImpl { PyThreadState base; #ifdef Py_GIL_DISABLED + struct _gc_thread_state gc; struct _mimalloc_thread_state mimalloc; struct _Py_object_freelists freelists; struct _brc_thread_state brc; diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py index baed03d92b9e561..f88206de550da08 100644 --- a/Lib/test/test_frame.py +++ b/Lib/test/test_frame.py @@ -13,7 +13,7 @@ _testcapi = None from test import support -from test.support import threading_helper +from test.support import threading_helper, Py_GIL_DISABLED from test.support.script_helper import assert_python_ok @@ -294,6 +294,7 @@ def gen(): assert_python_ok("-c", code) @support.cpython_only + @unittest.skipIf(Py_GIL_DISABLED, "test requires precise GC scheduling") def test_sneaky_frame_object(self): def trace(frame, event, arg): diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index b01f344cb14a1a7..dd09643788d62fb 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -363,6 +363,7 @@ def __del__(self): # To minimize variations, though, we first store the get_count() results # and check them at the end. @refcount_test + @unittest.skipIf(Py_GIL_DISABLED, 'needs precise allocation counts') def test_get_count(self): gc.collect() a, b, c = gc.get_count() diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index a2b66b9b78c169d..961165e16a0fee9 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -201,6 +201,16 @@ gc_get_count_impl(PyObject *module) /*[clinic end generated code: output=354012e67b16398f input=a392794a08251751]*/ { GCState *gcstate = get_gc_state(); + +#ifdef Py_GIL_DISABLED + _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET(); + struct _gc_thread_state *gc = &tstate->gc; + + // Flush the local allocation count to the global count + _Py_atomic_add_int(&gcstate->generations[0].count, (int)gc->alloc_count); + gc->alloc_count = 0; +#endif + return Py_BuildValue("(iii)", gcstate->generations[0].count, gcstate->generations[1].count, diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 0118ee255ef0177..2e25c207c643829 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -1835,6 +1835,8 @@ _PyType_AllocNoTrack(PyTypeObject *type, Py_ssize_t nitems) if (presize) { ((PyObject **)alloc)[0] = NULL; ((PyObject **)alloc)[1] = NULL; + } + if (PyType_IS_GC(type)) { _PyObject_GC_Link(obj); } memset(obj, '\0', size); diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 3dc1dc19182eb41..a758c99285a5390 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -23,6 +23,11 @@ typedef struct _gc_runtime_state GCState; # define GC_DEBUG #endif +// Each thread buffers the count of allocated objects in a thread-local +// variable up to +/- this amount to reduce the overhead of updating +// the global count. +#define LOCAL_ALLOC_COUNT_THRESHOLD 512 + // Automatically choose the generation that needs collecting. #define GENERATION_AUTO (-1) @@ -959,6 +964,41 @@ gc_should_collect(GCState *gcstate) gcstate->generations[1].threshold == 0); } +static void +record_allocation(PyThreadState *tstate) +{ + struct _gc_thread_state *gc = &((_PyThreadStateImpl *)tstate)->gc; + + // We buffer the allocation count to avoid the overhead of atomic + // operations for every allocation. + gc->alloc_count++; + if (gc->alloc_count >= LOCAL_ALLOC_COUNT_THRESHOLD) { + // TODO: Use Py_ssize_t for the generation count. + GCState *gcstate = &tstate->interp->gc; + _Py_atomic_add_int(&gcstate->generations[0].count, (int)gc->alloc_count); + gc->alloc_count = 0; + + if (gc_should_collect(gcstate) && + !_Py_atomic_load_int_relaxed(&gcstate->collecting)) + { + _Py_ScheduleGC(tstate->interp); + } + } +} + +static void +record_deallocation(PyThreadState *tstate) +{ + struct _gc_thread_state *gc = &((_PyThreadStateImpl *)tstate)->gc; + + gc->alloc_count--; + if (gc->alloc_count <= -LOCAL_ALLOC_COUNT_THRESHOLD) { + GCState *gcstate = &tstate->interp->gc; + _Py_atomic_add_int(&gcstate->generations[0].count, (int)gc->alloc_count); + gc->alloc_count = 0; + } +} + static void gc_collect_internal(PyInterpreterState *interp, struct collection_state *state) { @@ -981,6 +1021,9 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state) } } + // Record the number of live GC objects + interp->gc.long_lived_total = state->long_lived_total; + // Clear weakrefs and enqueue callbacks (but do not call them). clear_weakrefs(state); _PyEval_StartTheWorld(interp); @@ -1090,7 +1133,6 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) m = state.collected; n = state.uncollectable; - gcstate->long_lived_total = state.long_lived_total; if (gcstate->debug & _PyGC_DEBUG_STATS) { double d = _PyTime_AsSecondsDouble(_PyTime_GetPerfCounter() - t1); @@ -1530,15 +1572,7 @@ _Py_ScheduleGC(PyInterpreterState *interp) void _PyObject_GC_Link(PyObject *op) { - PyThreadState *tstate = _PyThreadState_GET(); - GCState *gcstate = &tstate->interp->gc; - gcstate->generations[0].count++; - - if (gc_should_collect(gcstate) && - !_Py_atomic_load_int_relaxed(&gcstate->collecting)) - { - _Py_ScheduleGC(tstate->interp); - } + record_allocation(_PyThreadState_GET()); } void @@ -1564,7 +1598,7 @@ gc_alloc(PyTypeObject *tp, size_t basicsize, size_t presize) ((PyObject **)mem)[1] = NULL; } PyObject *op = (PyObject *)(mem + presize); - _PyObject_GC_Link(op); + record_allocation(tstate); return op; } @@ -1646,10 +1680,9 @@ PyObject_GC_Del(void *op) PyErr_SetRaisedException(exc); #endif } - GCState *gcstate = get_gc_state(); - if (gcstate->generations[0].count > 0) { - gcstate->generations[0].count--; - } + + record_deallocation(_PyThreadState_GET()); + PyObject_Free(((char *)op)-presize); } From 2ac9d9f2fbeb743ae6d6b1cbf73337c230e21f3c Mon Sep 17 00:00:00 2001 From: Benjamin Peterson <benjamin@python.org> Date: Fri, 16 Feb 2024 08:49:41 -0800 Subject: [PATCH 313/507] gh-113743: Give _PyTypes_AfterFork a prototype. (gh-115563) Fixes a compiler warning. --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 2e25c207c643829..fe3b7b87c8b4b68 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4945,7 +4945,7 @@ update_cache_gil_disabled(struct type_cache_entry *entry, PyObject *name, #endif void -_PyTypes_AfterFork() +_PyTypes_AfterFork(void) { #ifdef Py_GIL_DISABLED struct type_cache *cache = get_type_cache(); From fbb016973149d983d30351bdd1aaf00df285c776 Mon Sep 17 00:00:00 2001 From: Michael Droettboom <mdboom@gmail.com> Date: Fri, 16 Feb 2024 12:06:07 -0500 Subject: [PATCH 314/507] gh-115362: Add documentation to pystats output (#115365) --- Tools/scripts/summarize_stats.py | 301 +++++++++++++++++++++++-------- 1 file changed, 224 insertions(+), 77 deletions(-) diff --git a/Tools/scripts/summarize_stats.py b/Tools/scripts/summarize_stats.py index 7891b9cf923d339..5bc39fceb4b2a11 100644 --- a/Tools/scripts/summarize_stats.py +++ b/Tools/scripts/summarize_stats.py @@ -11,6 +11,7 @@ import argparse import collections from collections.abc import KeysView +from dataclasses import dataclass from datetime import date import enum import functools @@ -21,6 +22,7 @@ from pathlib import Path import re import sys +import textwrap from typing import Any, Callable, TextIO, TypeAlias @@ -115,6 +117,64 @@ def save_raw_data(data: RawData, json_output: TextIO): json.dump(data, json_output) +@dataclass(frozen=True) +class Doc: + text: str + doc: str + + def markdown(self) -> str: + return textwrap.dedent( + f""" + {self.text} + <details> + <summary>ⓘ</summary> + + {self.doc} + </details> + """ + ) + + +class Count(int): + def markdown(self) -> str: + return format(self, ",d") + + +@dataclass(frozen=True) +class Ratio: + num: int + den: int | None = None + percentage: bool = True + + def __float__(self): + if self.den == 0: + return 0.0 + elif self.den is None: + return self.num + else: + return self.num / self.den + + def markdown(self) -> str: + if self.den is None: + return "" + elif self.den == 0: + if self.num != 0: + return f"{self.num:,} / 0 !!" + return "" + elif self.percentage: + return f"{self.num / self.den:,.01%}" + else: + return f"{self.num / self.den:,.02f}" + + +class DiffRatio(Ratio): + def __init__(self, base: int | str, head: int | str): + if isinstance(base, str) or isinstance(head, str): + super().__init__(0, 0) + else: + super().__init__(head - base, base) + + class OpcodeStats: """ Manages the data related to specific set of opcodes, e.g. tier1 (with prefix @@ -389,17 +449,54 @@ def get_optimization_stats(self) -> dict[str, tuple[int, int | None]]: low_confidence = self._data["Optimization low confidence"] return { - "Optimization attempts": (attempts, None), - "Traces created": (created, attempts), - "Trace stack overflow": (trace_stack_overflow, attempts), - "Trace stack underflow": (trace_stack_underflow, attempts), - "Trace too long": (trace_too_long, attempts), - "Trace too short": (trace_too_short, attempts), - "Inner loop found": (inner_loop, attempts), - "Recursive call": (recursive_call, attempts), - "Low confidence": (low_confidence, attempts), - "Traces executed": (executed, None), - "Uops executed": (uops, executed), + Doc( + "Optimization attempts", + "The number of times a potential trace is identified. Specifically, this " + "occurs in the JUMP BACKWARD instruction when the counter reaches a " + "threshold.", + ): ( + attempts, + None, + ), + Doc( + "Traces created", "The number of traces that were successfully created." + ): (created, attempts), + Doc( + "Trace stack overflow", + "A trace is truncated because it would require more than 5 stack frames.", + ): (trace_stack_overflow, attempts), + Doc( + "Trace stack underflow", + "A potential trace is abandoned because it pops more frames than it pushes.", + ): (trace_stack_underflow, attempts), + Doc( + "Trace too long", + "A trace is truncated because it is longer than the instruction buffer.", + ): (trace_too_long, attempts), + Doc( + "Trace too short", + "A potential trace is abandoced because it it too short.", + ): (trace_too_short, attempts), + Doc( + "Inner loop found", "A trace is truncated because it has an inner loop" + ): (inner_loop, attempts), + Doc( + "Recursive call", + "A trace is truncated because it has a recursive call.", + ): (recursive_call, attempts), + Doc( + "Low confidence", + "A trace is abandoned because the likelihood of the jump to top being taken " + "is too low.", + ): (low_confidence, attempts), + Doc("Traces executed", "The number of traces that were executed"): ( + executed, + None, + ), + Doc("Uops executed", "The total number of uops (micro-operations) that were executed"): ( + uops, + executed, + ), } def get_histogram(self, prefix: str) -> list[tuple[int, int]]: @@ -415,52 +512,12 @@ def get_histogram(self, prefix: str) -> list[tuple[int, int]]: def get_rare_events(self) -> list[tuple[str, int]]: prefix = "Rare event " return [ - (key[len(prefix) + 1:-1].replace("_", " "), val) + (key[len(prefix) + 1 : -1].replace("_", " "), val) for key, val in self._data.items() if key.startswith(prefix) ] -class Count(int): - def markdown(self) -> str: - return format(self, ",d") - - -class Ratio: - def __init__(self, num: int, den: int | None, percentage: bool = True): - self.num = num - self.den = den - self.percentage = percentage - - def __float__(self): - if self.den == 0: - return 0.0 - elif self.den is None: - return self.num - else: - return self.num / self.den - - def markdown(self) -> str: - if self.den is None: - return "" - elif self.den == 0: - if self.num != 0: - return f"{self.num:,} / 0 !!" - return "" - elif self.percentage: - return f"{self.num / self.den:,.01%}" - else: - return f"{self.num / self.den:,.02f}" - - -class DiffRatio(Ratio): - def __init__(self, base: int | str, head: int | str): - if isinstance(base, str) or isinstance(head, str): - super().__init__(0, 0) - else: - super().__init__(head - base, base) - - class JoinMode(enum.Enum): # Join using the first column as a key SIMPLE = 0 @@ -568,13 +625,16 @@ def __init__( title: str = "", summary: str = "", part_iter=None, + *, comparative: bool = True, + doc: str = "", ): self.title = title if not summary: self.summary = title.lower() else: self.summary = summary + self.doc = textwrap.dedent(doc) if part_iter is None: part_iter = [] if isinstance(part_iter, list): @@ -620,7 +680,7 @@ def calc(stats: Stats) -> Rows: def execution_count_section() -> Section: return Section( "Execution counts", - "execution counts for all instructions", + "Execution counts for Tier 1 instructions.", [ Table( ("Name", "Count:", "Self:", "Cumulative:", "Miss ratio:"), @@ -628,6 +688,11 @@ def execution_count_section() -> Section: join_mode=JoinMode.CHANGE_ONE_COLUMN, ) ], + doc=""" + The "miss ratio" column shows the percentage of times the instruction + executed that it deoptimized. When this happens, the base unspecialized + instruction is not counted. + """, ) @@ -655,7 +720,7 @@ def calc_pair_count_table(stats: Stats) -> Rows: return Section( "Pair counts", - "Pair counts for top 100 pairs", + "Pair counts for top 100 Tier 1 instructions", [ Table( ("Pair", "Count:", "Self:", "Cumulative:"), @@ -663,6 +728,10 @@ def calc_pair_count_table(stats: Stats) -> Rows: ) ], comparative=False, + doc=""" + Pairs of specialized operations that deoptimize and are then followed by + the corresponding unspecialized instruction are not counted as pairs. + """, ) @@ -705,22 +774,33 @@ def iter_pre_succ_pairs_tables(base_stats: Stats, head_stats: Stats | None = Non return Section( "Predecessor/Successor Pairs", - "Top 5 predecessors and successors of each opcode", + "Top 5 predecessors and successors of each Tier 1 opcode.", iter_pre_succ_pairs_tables, comparative=False, + doc=""" + This does not include the unspecialized instructions that occur after a + specialized instruction deoptimizes. + """, ) def specialization_section() -> Section: def calc_specialization_table(opcode: str) -> RowCalculator: def calc(stats: Stats) -> Rows: + DOCS = { + "deferred": 'Lists the number of "deferred" (i.e. not specialized) instructions executed.', + "hit": "Specialized instructions that complete.", + "miss": "Specialized instructions that deopt.", + "deopt": "Specialized instructions that deopt.", + } + opcode_stats = stats.get_opcode_stats("opcode") total = opcode_stats.get_specialization_total(opcode) specialization_counts = opcode_stats.get_specialization_counts(opcode) return [ ( - f"{label:>12}", + Doc(label, DOCS[label]), Count(count), Ratio(count, total), ) @@ -790,7 +870,7 @@ def iter_specialization_tables(base_stats: Stats, head_stats: Stats | None = Non JoinMode.CHANGE, ), Table( - ("", "Count:", "Ratio:"), + ("Success", "Count:", "Ratio:"), calc_specialization_success_failure_table(opcode), JoinMode.CHANGE, ), @@ -804,7 +884,7 @@ def iter_specialization_tables(base_stats: Stats, head_stats: Stats | None = Non return Section( "Specialization stats", - "specialization stats by family", + "Specialization stats by family", iter_specialization_tables, ) @@ -822,19 +902,35 @@ def calc_specialization_effectiveness_table(stats: Stats) -> Rows: ) = opcode_stats.get_specialized_total_counts() return [ - ("Basic", Count(basic), Ratio(basic, total)), ( - "Not specialized", + Doc( + "Basic", + "Instructions that are not and cannot be specialized, e.g. `LOAD_FAST`.", + ), + Count(basic), + Ratio(basic, total), + ), + ( + Doc( + "Not specialized", + "Instructions that could be specialized but aren't, e.g. `LOAD_ATTR`, `BINARY_SLICE`.", + ), Count(not_specialized), Ratio(not_specialized, total), ), ( - "Specialized hits", + Doc( + "Specialized hits", + "Specialized instructions, e.g. `LOAD_ATTR_MODULE` that complete.", + ), Count(specialized_hits), Ratio(specialized_hits, total), ), ( - "Specialized misses", + Doc( + "Specialized misses", + "Specialized instructions, e.g. `LOAD_ATTR_MODULE` that deopt.", + ), Count(specialized_misses), Ratio(specialized_misses, total), ), @@ -879,7 +975,7 @@ def calc_misses_by_table(stats: Stats) -> Rows: ), Section( "Deferred by instruction", - "", + "Breakdown of deferred (not specialized) instruction counts by family", [ Table( ("Name", "Count:", "Ratio:"), @@ -890,7 +986,7 @@ def calc_misses_by_table(stats: Stats) -> Rows: ), Section( "Misses by instruction", - "", + "Breakdown of misses (specialized deopts) instruction counts by family", [ Table( ("Name", "Count:", "Ratio:"), @@ -900,6 +996,10 @@ def calc_misses_by_table(stats: Stats) -> Rows: ], ), ], + doc=""" + All entries are execution counts. Should add up to the total number of + Tier 1 instructions executed. + """, ) @@ -922,6 +1022,13 @@ def calc_call_stats_table(stats: Stats) -> Rows: JoinMode.CHANGE, ) ], + doc=""" + This shows what fraction of calls to Python functions are inlined (i.e. + not having a call at the C level) and for those that are not, where the + call comes from. The various categories overlap. + + Also includes the count of frame objects created. + """, ) @@ -935,7 +1042,7 @@ def calc_object_stats_table(stats: Stats) -> Rows: return Section( "Object stats", - "allocations, frees and dict materializatons", + "Allocations, frees and dict materializatons", [ Table( ("", "Count:", "Ratio:"), @@ -943,6 +1050,16 @@ def calc_object_stats_table(stats: Stats) -> Rows: JoinMode.CHANGE, ) ], + doc=""" + Below, "allocations" means "allocations that are not from a freelist". + Total allocations = "Allocations from freelist" + "Allocations". + + "New values" is the number of values arrays created for objects with + managed dicts. + + The cache hit/miss numbers are for the MRO cache, split into dunder and + other names. + """, ) @@ -969,6 +1086,9 @@ def calc_gc_stats(stats: Stats) -> Rows: calc_gc_stats, ) ], + doc=""" + Collected/visits gives some measure of efficiency. + """, ) @@ -1074,7 +1194,19 @@ def iter_optimization_tables(base_stats: Stats, head_stats: Stats | None = None) def rare_event_section() -> Section: def calc_rare_event_table(stats: Stats) -> Table: - return [(x, Count(y)) for x, y in stats.get_rare_events()] + DOCS = { + "set class": "Setting an object's class, `obj.__class__ = ...`", + "set bases": "Setting the bases of a class, `cls.__bases__ = ...`", + "set eval frame func": ( + "Setting the PEP 523 frame eval function " + "`_PyInterpreterState_SetFrameEvalFunc()`" + ), + "builtin dict": "Modifying the builtins, `__builtins__.__dict__[var] = ...`", + "func modification": "Modifying a function, e.g. `func.__defaults__ = ...`, etc.", + "watched dict modification": "A watched dict has been modified", + "watched globals modification": "A watched `globals()` dict has been modified", + } + return [(Doc(x, DOCS[x]), Count(y)) for x, y in stats.get_rare_events()] return Section( "Rare events", @@ -1134,6 +1266,9 @@ def to_markdown(x): print("<details>", file=out) print("<summary>", obj.summary, "</summary>", file=out) print(file=out) + if obj.doc: + print(obj.doc, file=out) + if head_stats is not None and obj.comparative is False: print("Not included in comparative output.\n") else: @@ -1149,24 +1284,36 @@ def to_markdown(x): if len(rows) == 0: return - width = len(header) - header_line = "|" - under_line = "|" + alignments = [] for item in header: - under = "---" + if item.endswith(":"): + alignments.append("right") + else: + alignments.append("left") + + print("<table>", file=out) + print("<thead>", file=out) + print("<tr>", file=out) + for item, align in zip(header, alignments): if item.endswith(":"): item = item[:-1] - under += ":" - header_line += item + " | " - under_line += under + "|" - print(header_line, file=out) - print(under_line, file=out) + print(f'<th align="{align}">{item}</th>', file=out) + print("</tr>", file=out) + print("</thead>", file=out) + + print("<tbody>", file=out) for row in rows: - if len(row) != width: + if len(row) != len(header): raise ValueError( "Wrong number of elements in row '" + str(row) + "'" ) - print("|", " | ".join(to_markdown(i) for i in row), "|", file=out) + print("<tr>", file=out) + for col, align in zip(row, alignments): + print(f'<td align="{align}">{to_markdown(col)}</td>', file=out) + print("</tr>", file=out) + print("</tbody>", file=out) + + print("</table>", file=out) print(file=out) case list(): From 13addd2bbdcbf96c5ea26a0f425c049f1b71e945 Mon Sep 17 00:00:00 2001 From: Peter Lazorchak <lazorchakp@gmail.com> Date: Fri, 16 Feb 2024 10:02:48 -0800 Subject: [PATCH 315/507] gh-115480: Type / constant propagation for float binary uops (GH-115550) Co-authored-by: Ken Jin <kenjin@python.org> --- Lib/test/test_capi/test_opt.py | 99 +++++++++++++------ .../tier2_redundancy_eliminator_bytecodes.c | 57 +++++++++++ Python/tier2_redundancy_eliminator_cases.c.h | 66 +++++++++++-- 3 files changed, 184 insertions(+), 38 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 1a8ed3441fa8555..66860c67966859f 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -561,6 +561,16 @@ def testfunc(n): class TestUopsOptimization(unittest.TestCase): + def _run_with_optimizer(self, testfunc, arg): + res = None + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + res = testfunc(arg) + + ex = get_first_executor(testfunc) + return res, ex + + def test_int_type_propagation(self): def testfunc(loops): num = 0 @@ -570,12 +580,7 @@ def testfunc(loops): num += 1 return a - opt = _testinternalcapi.get_uop_optimizer() - res = None - with temporary_optimizer(opt): - res = testfunc(32) - - ex = get_first_executor(testfunc) + res, ex = self._run_with_optimizer(testfunc, 32) self.assertIsNotNone(ex) self.assertEqual(res, 63) binop_count = [opname for opname, _, _ in ex if opname == "_BINARY_OP_ADD_INT"] @@ -642,12 +647,7 @@ def testfunc(loops): num += 1 return a - opt = _testinternalcapi.get_uop_optimizer() - res = None - with temporary_optimizer(opt): - res = testfunc(64) - - ex = get_first_executor(testfunc) + res, ex = self._run_with_optimizer(testfunc, 64) self.assertIsNotNone(ex) binop_count = [opname for opname, _, _ in ex if opname == "_BINARY_OP_ADD_INT"] self.assertGreaterEqual(len(binop_count), 3) @@ -659,11 +659,7 @@ def dummy(x): for i in range(n): dummy(i) - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(32) - - ex = get_first_executor(testfunc) + res, ex = self._run_with_optimizer(testfunc, 32) self.assertIsNotNone(ex) uops = {opname for opname, _, _ in ex} self.assertIn("_PUSH_FRAME", uops) @@ -677,11 +673,7 @@ def testfunc(n): x = i + i return x - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - res = testfunc(32) - - ex = get_first_executor(testfunc) + res, ex = self._run_with_optimizer(testfunc, 32) self.assertEqual(res, 62) self.assertIsNotNone(ex) uops = {opname for opname, _, _ in ex} @@ -699,11 +691,7 @@ def testfunc(n): res = x + z + a + b return res - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - res = testfunc(32) - - ex = get_first_executor(testfunc) + res, ex = self._run_with_optimizer(testfunc, 32) self.assertEqual(res, 4) self.assertIsNotNone(ex) uops = {opname for opname, _, _ in ex} @@ -716,11 +704,8 @@ def testfunc(n): for _ in range(n): return [i for i in range(n)] - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(32) - - ex = get_first_executor(testfunc) + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertEqual(res, list(range(32))) self.assertIsNotNone(ex) uops = {opname for opname, _, _ in ex} self.assertNotIn("_BINARY_OP_ADD_INT", uops) @@ -785,6 +770,56 @@ def testfunc(n): """)) self.assertEqual(result[0].rc, 0, result) + def test_float_add_constant_propagation(self): + def testfunc(n): + a = 1.0 + for _ in range(n): + a = a + 0.1 + return a + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertAlmostEqual(res, 4.2) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + guard_both_float_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_FLOAT"] + self.assertLessEqual(len(guard_both_float_count), 1) + # TODO gh-115506: this assertion may change after propagating constants. + # We'll also need to verify that propagation actually occurs. + self.assertIn("_BINARY_OP_ADD_FLOAT", uops) + + def test_float_subtract_constant_propagation(self): + def testfunc(n): + a = 1.0 + for _ in range(n): + a = a - 0.1 + return a + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertAlmostEqual(res, -2.2) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + guard_both_float_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_FLOAT"] + self.assertLessEqual(len(guard_both_float_count), 1) + # TODO gh-115506: this assertion may change after propagating constants. + # We'll also need to verify that propagation actually occurs. + self.assertIn("_BINARY_OP_SUBTRACT_FLOAT", uops) + + def test_float_multiply_constant_propagation(self): + def testfunc(n): + a = 1.0 + for _ in range(n): + a = a * 2.0 + return a + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertAlmostEqual(res, 2 ** 32) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + guard_both_float_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_FLOAT"] + self.assertLessEqual(len(guard_both_float_count), 1) + # TODO gh-115506: this assertion may change after propagating constants. + # We'll also need to verify that propagation actually occurs. + self.assertIn("_BINARY_OP_MULTIPLY_FLOAT", uops) if __name__ == "__main__": diff --git a/Python/tier2_redundancy_eliminator_bytecodes.c b/Python/tier2_redundancy_eliminator_bytecodes.c index 6aae590a8e51e47..3f6e8ce1bbfbadd 100644 --- a/Python/tier2_redundancy_eliminator_bytecodes.c +++ b/Python/tier2_redundancy_eliminator_bytecodes.c @@ -132,6 +132,63 @@ dummy_func(void) { } } + op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { + if (is_const(left) && is_const(right)) { + assert(PyFloat_CheckExact(get_const(left))); + assert(PyFloat_CheckExact(get_const(right))); + PyObject *temp = PyFloat_FromDouble( + PyFloat_AS_DOUBLE(get_const(left)) + + PyFloat_AS_DOUBLE(get_const(right))); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + // TODO gh-115506: + // replace opcode with constant propagated one and update tests! + } + else { + OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); + } + } + + op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) { + if (is_const(left) && is_const(right)) { + assert(PyFloat_CheckExact(get_const(left))); + assert(PyFloat_CheckExact(get_const(right))); + PyObject *temp = PyFloat_FromDouble( + PyFloat_AS_DOUBLE(get_const(left)) - + PyFloat_AS_DOUBLE(get_const(right))); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + // TODO gh-115506: + // replace opcode with constant propagated one and update tests! + } + else { + OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); + } + } + + op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { + if (is_const(left) && is_const(right)) { + assert(PyFloat_CheckExact(get_const(left))); + assert(PyFloat_CheckExact(get_const(right))); + PyObject *temp = PyFloat_FromDouble( + PyFloat_AS_DOUBLE(get_const(left)) * + PyFloat_AS_DOUBLE(get_const(right))); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + // TODO gh-115506: + // replace opcode with constant propagated one and update tests! + } + else { + OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); + } + } + op(_LOAD_CONST, (-- value)) { // There should be no LOAD_CONST. It should be all // replaced by peephole_opt. diff --git a/Python/tier2_redundancy_eliminator_cases.c.h b/Python/tier2_redundancy_eliminator_cases.c.h index d1301ff29ac5931..be2fbb9106fffc5 100644 --- a/Python/tier2_redundancy_eliminator_cases.c.h +++ b/Python/tier2_redundancy_eliminator_cases.c.h @@ -270,27 +270,81 @@ } case _BINARY_OP_MULTIPLY_FLOAT: { + _Py_UOpsSymType *right; + _Py_UOpsSymType *left; _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); - if (res == NULL) goto out_of_space; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + if (is_const(left) && is_const(right)) { + assert(PyFloat_CheckExact(get_const(left))); + assert(PyFloat_CheckExact(get_const(right))); + PyObject *temp = PyFloat_FromDouble( + PyFloat_AS_DOUBLE(get_const(left)) * + PyFloat_AS_DOUBLE(get_const(right))); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + // TODO gh-115506: + // replace opcode with constant propagated one and update tests! + } + else { + OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); + } stack_pointer[-2] = res; stack_pointer += -1; break; } case _BINARY_OP_ADD_FLOAT: { + _Py_UOpsSymType *right; + _Py_UOpsSymType *left; _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); - if (res == NULL) goto out_of_space; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + if (is_const(left) && is_const(right)) { + assert(PyFloat_CheckExact(get_const(left))); + assert(PyFloat_CheckExact(get_const(right))); + PyObject *temp = PyFloat_FromDouble( + PyFloat_AS_DOUBLE(get_const(left)) + + PyFloat_AS_DOUBLE(get_const(right))); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + // TODO gh-115506: + // replace opcode with constant propagated one and update tests! + } + else { + OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); + } stack_pointer[-2] = res; stack_pointer += -1; break; } case _BINARY_OP_SUBTRACT_FLOAT: { + _Py_UOpsSymType *right; + _Py_UOpsSymType *left; _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); - if (res == NULL) goto out_of_space; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + if (is_const(left) && is_const(right)) { + assert(PyFloat_CheckExact(get_const(left))); + assert(PyFloat_CheckExact(get_const(right))); + PyObject *temp = PyFloat_FromDouble( + PyFloat_AS_DOUBLE(get_const(left)) - + PyFloat_AS_DOUBLE(get_const(right))); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + // TODO gh-115506: + // replace opcode with constant propagated one and update tests! + } + else { + OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); + } stack_pointer[-2] = res; stack_pointer += -1; break; From f366e215044e348659df814c27bf70e78907df21 Mon Sep 17 00:00:00 2001 From: mpage <mpage@meta.com> Date: Fri, 16 Feb 2024 10:29:25 -0800 Subject: [PATCH 316/507] gh-114271: Make `thread._rlock` thread-safe in free-threaded builds (#115102) The ID of the owning thread (`rlock_owner`) may be accessed by multiple threads without holding the underlying lock; relaxed atomics are used in place of the previous loads/stores. The number of times that the lock has been acquired (`rlock_count`) is only ever accessed by the thread that holds the lock; we do not need to use atomics to access it. --- Include/cpython/pyatomic.h | 6 ++++++ Include/cpython/pyatomic_gcc.h | 9 +++++++++ Include/cpython/pyatomic_msc.h | 14 ++++++++++++++ Include/cpython/pyatomic_std.h | 17 +++++++++++++++++ Modules/_threadmodule.c | 32 ++++++++++++++++++++++---------- 5 files changed, 68 insertions(+), 10 deletions(-) diff --git a/Include/cpython/pyatomic.h b/Include/cpython/pyatomic.h index e10d48285367cf9..9b5774190ee99e9 100644 --- a/Include/cpython/pyatomic.h +++ b/Include/cpython/pyatomic.h @@ -360,6 +360,8 @@ _Py_atomic_load_ssize_relaxed(const Py_ssize_t *obj); static inline void * _Py_atomic_load_ptr_relaxed(const void *obj); +static inline unsigned long long +_Py_atomic_load_ullong_relaxed(const unsigned long long *obj); // --- _Py_atomic_store ------------------------------------------------------ // Atomically performs `*obj = value` (sequential consistency) @@ -452,6 +454,10 @@ _Py_atomic_store_ptr_relaxed(void *obj, void *value); static inline void _Py_atomic_store_ssize_relaxed(Py_ssize_t *obj, Py_ssize_t value); +static inline void +_Py_atomic_store_ullong_relaxed(unsigned long long *obj, + unsigned long long value); + // --- _Py_atomic_load_ptr_acquire / _Py_atomic_store_ptr_release ------------ diff --git a/Include/cpython/pyatomic_gcc.h b/Include/cpython/pyatomic_gcc.h index 4095e1873d8b071..bc74149f73e83cf 100644 --- a/Include/cpython/pyatomic_gcc.h +++ b/Include/cpython/pyatomic_gcc.h @@ -358,6 +358,10 @@ static inline void * _Py_atomic_load_ptr_relaxed(const void *obj) { return (void *)__atomic_load_n((const void **)obj, __ATOMIC_RELAXED); } +static inline unsigned long long +_Py_atomic_load_ullong_relaxed(const unsigned long long *obj) +{ return __atomic_load_n(obj, __ATOMIC_RELAXED); } + // --- _Py_atomic_store ------------------------------------------------------ @@ -476,6 +480,11 @@ static inline void _Py_atomic_store_ssize_relaxed(Py_ssize_t *obj, Py_ssize_t value) { __atomic_store_n(obj, value, __ATOMIC_RELAXED); } +static inline void +_Py_atomic_store_ullong_relaxed(unsigned long long *obj, + unsigned long long value) +{ __atomic_store_n(obj, value, __ATOMIC_RELAXED); } + // --- _Py_atomic_load_ptr_acquire / _Py_atomic_store_ptr_release ------------ diff --git a/Include/cpython/pyatomic_msc.h b/Include/cpython/pyatomic_msc.h index b5c1ec941125621..6ab6401cf81e8ac 100644 --- a/Include/cpython/pyatomic_msc.h +++ b/Include/cpython/pyatomic_msc.h @@ -712,6 +712,12 @@ _Py_atomic_load_ptr_relaxed(const void *obj) return *(void * volatile *)obj; } +static inline unsigned long long +_Py_atomic_load_ullong_relaxed(const unsigned long long *obj) +{ + return *(volatile unsigned long long *)obj; +} + // --- _Py_atomic_store ------------------------------------------------------ @@ -886,6 +892,14 @@ _Py_atomic_store_ssize_relaxed(Py_ssize_t *obj, Py_ssize_t value) *(volatile Py_ssize_t *)obj = value; } +static inline void +_Py_atomic_store_ullong_relaxed(unsigned long long *obj, + unsigned long long value) +{ + *(volatile unsigned long long *)obj = value; +} + + // --- _Py_atomic_load_ptr_acquire / _Py_atomic_store_ptr_release ------------ static inline void * diff --git a/Include/cpython/pyatomic_std.h b/Include/cpython/pyatomic_std.h index 6c934a2c5e7b64c..d3004dbd24ed09a 100644 --- a/Include/cpython/pyatomic_std.h +++ b/Include/cpython/pyatomic_std.h @@ -619,6 +619,14 @@ _Py_atomic_load_ptr_relaxed(const void *obj) memory_order_relaxed); } +static inline unsigned long long +_Py_atomic_load_ullong_relaxed(const unsigned long long *obj) +{ + _Py_USING_STD; + return atomic_load_explicit((const _Atomic(unsigned long long)*)obj, + memory_order_relaxed); +} + // --- _Py_atomic_store ------------------------------------------------------ @@ -835,6 +843,15 @@ _Py_atomic_store_ssize_relaxed(Py_ssize_t *obj, Py_ssize_t value) memory_order_relaxed); } +static inline void +_Py_atomic_store_ullong_relaxed(unsigned long long *obj, + unsigned long long value) +{ + _Py_USING_STD; + atomic_store_explicit((_Atomic(unsigned long long)*)obj, value, + memory_order_relaxed); +} + // --- _Py_atomic_load_ptr_acquire / _Py_atomic_store_ptr_release ------------ diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index da6a8bc7b120fed..ec23fe849eb0316 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -11,6 +11,7 @@ #include "pycore_sysmodule.h" // _PySys_GetAttr() #include "pycore_weakref.h" // _PyWeakref_GET_REF() +#include <stdbool.h> #include <stddef.h> // offsetof() #ifdef HAVE_SIGNAL_H # include <signal.h> // SIGINT @@ -489,6 +490,14 @@ rlock_dealloc(rlockobject *self) Py_DECREF(tp); } +static bool +rlock_is_owned_by(rlockobject *self, PyThread_ident_t tid) +{ + PyThread_ident_t owner_tid = + _Py_atomic_load_ullong_relaxed(&self->rlock_owner); + return owner_tid == tid && self->rlock_count > 0; +} + static PyObject * rlock_acquire(rlockobject *self, PyObject *args, PyObject *kwds) { @@ -500,7 +509,7 @@ rlock_acquire(rlockobject *self, PyObject *args, PyObject *kwds) return NULL; tid = PyThread_get_thread_ident_ex(); - if (self->rlock_count > 0 && tid == self->rlock_owner) { + if (rlock_is_owned_by(self, tid)) { unsigned long count = self->rlock_count + 1; if (count <= self->rlock_count) { PyErr_SetString(PyExc_OverflowError, @@ -513,7 +522,7 @@ rlock_acquire(rlockobject *self, PyObject *args, PyObject *kwds) r = acquire_timed(self->rlock_lock, timeout); if (r == PY_LOCK_ACQUIRED) { assert(self->rlock_count == 0); - self->rlock_owner = tid; + _Py_atomic_store_ullong_relaxed(&self->rlock_owner, tid); self->rlock_count = 1; } else if (r == PY_LOCK_INTR) { @@ -544,13 +553,13 @@ rlock_release(rlockobject *self, PyObject *Py_UNUSED(ignored)) { PyThread_ident_t tid = PyThread_get_thread_ident_ex(); - if (self->rlock_count == 0 || self->rlock_owner != tid) { + if (!rlock_is_owned_by(self, tid)) { PyErr_SetString(PyExc_RuntimeError, "cannot release un-acquired lock"); return NULL; } if (--self->rlock_count == 0) { - self->rlock_owner = 0; + _Py_atomic_store_ullong_relaxed(&self->rlock_owner, 0); PyThread_release_lock(self->rlock_lock); } Py_RETURN_NONE; @@ -589,7 +598,7 @@ rlock_acquire_restore(rlockobject *self, PyObject *args) return NULL; } assert(self->rlock_count == 0); - self->rlock_owner = owner; + _Py_atomic_store_ullong_relaxed(&self->rlock_owner, owner); self->rlock_count = count; Py_RETURN_NONE; } @@ -614,7 +623,7 @@ rlock_release_save(rlockobject *self, PyObject *Py_UNUSED(ignored)) owner = self->rlock_owner; count = self->rlock_count; self->rlock_count = 0; - self->rlock_owner = 0; + _Py_atomic_store_ullong_relaxed(&self->rlock_owner, 0); PyThread_release_lock(self->rlock_lock); return Py_BuildValue("k" Py_PARSE_THREAD_IDENT_T, count, owner); } @@ -628,8 +637,9 @@ static PyObject * rlock_recursion_count(rlockobject *self, PyObject *Py_UNUSED(ignored)) { PyThread_ident_t tid = PyThread_get_thread_ident_ex(); - return PyLong_FromUnsignedLong( - self->rlock_owner == tid ? self->rlock_count : 0UL); + PyThread_ident_t owner = + _Py_atomic_load_ullong_relaxed(&self->rlock_owner); + return PyLong_FromUnsignedLong(owner == tid ? self->rlock_count : 0UL); } PyDoc_STRVAR(rlock_recursion_count_doc, @@ -642,7 +652,7 @@ rlock_is_owned(rlockobject *self, PyObject *Py_UNUSED(ignored)) { PyThread_ident_t tid = PyThread_get_thread_ident_ex(); - if (self->rlock_count > 0 && self->rlock_owner == tid) { + if (rlock_is_owned_by(self, tid)) { Py_RETURN_TRUE; } Py_RETURN_FALSE; @@ -676,10 +686,12 @@ rlock_new(PyTypeObject *type, PyObject *args, PyObject *kwds) static PyObject * rlock_repr(rlockobject *self) { + PyThread_ident_t owner = + _Py_atomic_load_ullong_relaxed(&self->rlock_owner); return PyUnicode_FromFormat( "<%s %s object owner=%" PY_FORMAT_THREAD_IDENT_T " count=%lu at %p>", self->rlock_count ? "locked" : "unlocked", - Py_TYPE(self)->tp_name, self->rlock_owner, + Py_TYPE(self)->tp_name, owner, self->rlock_count, self); } From 74e6f4b32fceea8e8ffb0424d9bdc6589faf7ee4 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Fri, 16 Feb 2024 19:25:19 +0000 Subject: [PATCH 317/507] gh-112720: make it easier to subclass and modify dis.ArgResolver's jump arg resolution (#115564) --- Lib/dis.py | 30 ++++++++++++------- Lib/test/test_dis.py | 16 ++++++++++ ...-02-16-16-40-10.gh-issue-112720.io6_Ac.rst | 2 ++ 3 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-16-16-40-10.gh-issue-112720.io6_Ac.rst diff --git a/Lib/dis.py b/Lib/dis.py index f05ea1a24f45a71..d146bcbb5097efd 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -505,6 +505,20 @@ def __init__(self, co_consts=None, names=None, varname_from_oparg=None, labels_m self.varname_from_oparg = varname_from_oparg self.labels_map = labels_map or {} + def offset_from_jump_arg(self, op, arg, offset): + deop = _deoptop(op) + if deop in hasjabs: + return arg * 2 + elif deop in hasjrel: + signed_arg = -arg if _is_backward_jump(deop) else arg + argval = offset + 2 + signed_arg*2 + caches = _get_cache_size(_all_opname[deop]) + argval += 2 * caches + if deop == ENTER_EXECUTOR: + argval += 2 + return argval + return None + def get_label_for_offset(self, offset): return self.labels_map.get(offset, None) @@ -536,17 +550,11 @@ def get_argval_argrepr(self, op, arg, offset): argrepr = f"{argrepr} + NULL|self" else: argval, argrepr = _get_name_info(arg, get_name) - elif deop in hasjabs: - argval = arg*2 - argrepr = f"to L{self.labels_map[argval]}" - elif deop in hasjrel: - signed_arg = -arg if _is_backward_jump(deop) else arg - argval = offset + 2 + signed_arg*2 - caches = _get_cache_size(_all_opname[deop]) - argval += 2 * caches - if deop == ENTER_EXECUTOR: - argval += 2 - argrepr = f"to L{self.labels_map[argval]}" + elif deop in hasjump or deop in hasexc: + argval = self.offset_from_jump_arg(op, arg, offset) + lbl = self.get_label_for_offset(argval) + assert lbl is not None + argrepr = f"to L{lbl}" elif deop in (LOAD_FAST_LOAD_FAST, STORE_FAST_LOAD_FAST, STORE_FAST_STORE_FAST): arg1 = arg >> 4 arg2 = arg & 15 diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index a5917da346dded5..a93cb509b651c51 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -1986,6 +1986,22 @@ def f(opcode, oparg, offset, *init_args): self.assertEqual(f(opcode.opmap["BINARY_OP"], 3, *args), (3, '<<')) self.assertEqual(f(opcode.opmap["CALL_INTRINSIC_1"], 2, *args), (2, 'INTRINSIC_IMPORT_STAR')) + def test_custom_arg_resolver(self): + class MyArgResolver(dis.ArgResolver): + def offset_from_jump_arg(self, op, arg, offset): + return arg + 1 + + def get_label_for_offset(self, offset): + return 2 * offset + + def f(opcode, oparg, offset, *init_args): + arg_resolver = MyArgResolver(*init_args) + return arg_resolver.get_argval_argrepr(opcode, oparg, offset) + offset = 42 + self.assertEqual(f(opcode.opmap["JUMP_BACKWARD"], 1, offset), (2, 'to L4')) + self.assertEqual(f(opcode.opmap["SETUP_FINALLY"], 2, offset), (3, 'to L6')) + + def get_instructions(self, code): return dis._get_instructions_bytes(code) diff --git a/Misc/NEWS.d/next/Library/2024-02-16-16-40-10.gh-issue-112720.io6_Ac.rst b/Misc/NEWS.d/next/Library/2024-02-16-16-40-10.gh-issue-112720.io6_Ac.rst new file mode 100644 index 000000000000000..32916ede4dee357 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-16-16-40-10.gh-issue-112720.io6_Ac.rst @@ -0,0 +1,2 @@ +Refactor :class:`dis.ArgResolver` to make it possible to subclass and change +the way jump args are interpreted. From 711f42de2e3749208cfa7effa0d45b04e4e1fdd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= <lukasz@langa.pl> Date: Fri, 16 Feb 2024 21:24:56 +0100 Subject: [PATCH 318/507] gh-115556: Remove quotes from command-line arguments in test.bat and rt.bat (#115557) This change essentially replaces usage of `%1` with `%~1`, which removes quotes, if any. Without this change, the if statements fail due to the quotes mangling the syntax. Additionally, this change works around comma being treated as a parameter delimiter in test.bat by escaping commas at time of parsing. Tested combinations of rt and regrtest arguments, all seems to work as before but now you can specify commas in arguments like "-uall,extralargefile". --- ...-02-16-13-04-28.gh-issue-115556.rjaQ9w.rst | 2 ++ PCbuild/rt.bat | 22 ++++++++-------- Tools/buildbot/test.bat | 26 ++++++++++++------- 3 files changed, 29 insertions(+), 21 deletions(-) create mode 100644 Misc/NEWS.d/next/Tests/2024-02-16-13-04-28.gh-issue-115556.rjaQ9w.rst diff --git a/Misc/NEWS.d/next/Tests/2024-02-16-13-04-28.gh-issue-115556.rjaQ9w.rst b/Misc/NEWS.d/next/Tests/2024-02-16-13-04-28.gh-issue-115556.rjaQ9w.rst new file mode 100644 index 000000000000000..c2811b133d9314f --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-02-16-13-04-28.gh-issue-115556.rjaQ9w.rst @@ -0,0 +1,2 @@ +On Windows, commas passed in arguments to ``Tools\buildbot\test.bat`` and +``PCbuild\\rt.bat`` are now properly handled. diff --git a/PCbuild/rt.bat b/PCbuild/rt.bat index 293f99ae135faac..ac530a5206271f4 100644 --- a/PCbuild/rt.bat +++ b/PCbuild/rt.bat @@ -38,18 +38,18 @@ set regrtestargs=--fast-ci set exe= :CheckOpts -if "%1"=="-O" (set dashO=-O) & shift & goto CheckOpts -if "%1"=="-q" (set qmode=yes) & shift & goto CheckOpts -if "%1"=="-d" (set suffix=_d) & shift & goto CheckOpts +if "%~1"=="-O" (set dashO=-O) & shift & goto CheckOpts +if "%~1"=="-q" (set qmode=yes) & shift & goto CheckOpts +if "%~1"=="-d" (set suffix=_d) & shift & goto CheckOpts rem HACK: Need some way to infer the version number in this script -if "%1"=="--disable-gil" (set pyname=python3.13t) & shift & goto CheckOpts -if "%1"=="-win32" (set prefix=%pcbuild%win32) & shift & goto CheckOpts -if "%1"=="-x64" (set prefix=%pcbuild%amd64) & shift & goto CheckOpts -if "%1"=="-amd64" (set prefix=%pcbuild%amd64) & shift & goto CheckOpts -if "%1"=="-arm64" (set prefix=%pcbuild%arm64) & shift & goto CheckOpts -if "%1"=="-arm32" (set prefix=%pcbuild%arm32) & shift & goto CheckOpts -if "%1"=="-p" (call :SetPlatform %~2) & shift & shift & goto CheckOpts -if NOT "%1"=="" (set regrtestargs=%regrtestargs% %1) & shift & goto CheckOpts +if "%~1"=="--disable-gil" (set pyname=python3.13t) & shift & goto CheckOpts +if "%~1"=="-win32" (set prefix=%pcbuild%win32) & shift & goto CheckOpts +if "%~1"=="-x64" (set prefix=%pcbuild%amd64) & shift & goto CheckOpts +if "%~1"=="-amd64" (set prefix=%pcbuild%amd64) & shift & goto CheckOpts +if "%~1"=="-arm64" (set prefix=%pcbuild%arm64) & shift & goto CheckOpts +if "%~1"=="-arm32" (set prefix=%pcbuild%arm32) & shift & goto CheckOpts +if "%~1"=="-p" (call :SetPlatform %~2) & shift & shift & goto CheckOpts +if NOT "%~1"=="" (set regrtestargs=%regrtestargs% %~1) & shift & goto CheckOpts if not defined prefix set prefix=%pcbuild%amd64 set exe=%prefix%\%pyname%%suffix%.exe diff --git a/Tools/buildbot/test.bat b/Tools/buildbot/test.bat index 781f9a4c8206c82..0c47470a0ecb7aa 100644 --- a/Tools/buildbot/test.bat +++ b/Tools/buildbot/test.bat @@ -7,17 +7,10 @@ set here=%~dp0 set rt_opts=-q -d set regrtest_args= set arm32_ssh= +set cmdline_args=%* +set cmdline_args=%cmdline_args:,=#COMMA#% -:CheckOpts -if "%1"=="-x64" (set rt_opts=%rt_opts% %1) & shift & goto CheckOpts -if "%1"=="-arm64" (set rt_opts=%rt_opts% %1) & shift & goto CheckOpts -if "%1"=="-arm32" (set rt_opts=%rt_opts% %1) & (set arm32_ssh=true) & shift & goto CheckOpts -if "%1"=="-d" (set rt_opts=%rt_opts% %1) & shift & goto CheckOpts -if "%1"=="-O" (set rt_opts=%rt_opts% %1) & shift & goto CheckOpts -if "%1"=="-q" (set rt_opts=%rt_opts% %1) & shift & goto CheckOpts -if "%1"=="+d" (set rt_opts=%rt_opts:-d=%) & shift & goto CheckOpts -if "%1"=="+q" (set rt_opts=%rt_opts:-q=%) & shift & goto CheckOpts -if NOT "%1"=="" (set regrtest_args=%regrtest_args% %1) & shift & goto CheckOpts +call:CheckOpts %cmdline_args% if "%PROCESSOR_ARCHITECTURE%"=="ARM" if "%arm32_ssh%"=="true" goto NativeExecution if "%arm32_ssh%"=="true" goto :Arm32Ssh @@ -49,3 +42,16 @@ echo The test worker should have the SSH agent running. echo Also a key must be created with ssh-keygen and added to both the buildbot worker machine echo and the ARM32 worker device: see https://docs.microsoft.com/en-us/windows/iot-core/connect-your-device/ssh exit /b 127 + +:CheckOpts +set arg="%~1" +if %arg%=="-x64" (set rt_opts=%rt_opts% %1) & shift & goto CheckOpts +if %arg%=="-arm64" (set rt_opts=%rt_opts% %1) & shift & goto CheckOpts +if %arg%=="-arm32" (set rt_opts=%rt_opts% %1) & (set arm32_ssh=true) & shift & goto CheckOpts +if %arg%=="-d" (set rt_opts=%rt_opts% %1) & shift & goto CheckOpts +if %arg%=="-O" (set rt_opts=%rt_opts% %1) & shift & goto CheckOpts +if %arg%=="-q" (set rt_opts=%rt_opts% %1) & shift & goto CheckOpts +if %arg%=="+d" (set rt_opts=%rt_opts:-d=%) & shift & goto CheckOpts +if %arg%=="+q" (set rt_opts=%rt_opts:-q=%) & shift & goto CheckOpts +if NOT %arg%=="" (set regrtest_args=%regrtest_args% %arg:#COMMA#=,%) & shift & goto CheckOpts +goto:eof From 590319072773bd6cdcca655c420d3adb84838e96 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Fri, 16 Feb 2024 15:25:19 -0500 Subject: [PATCH 319/507] gh-115103: Implement delayed memory reclamation (QSBR) (#115180) This adds a safe memory reclamation scheme based on FreeBSD's "GUS" and quiescent state based reclamation (QSBR). The API provides a mechanism for callers to detect when it is safe to free memory that may be concurrently accessed by readers. --- Doc/license.rst | 32 +++ Include/cpython/pyatomic.h | 6 + Include/cpython/pyatomic_gcc.h | 8 + Include/cpython/pyatomic_msc.h | 28 ++- Include/cpython/pyatomic_std.h | 16 ++ Include/internal/pycore_interp.h | 2 + Include/internal/pycore_qsbr.h | 139 ++++++++++++ Include/internal/pycore_runtime_init.h | 5 + Include/internal/pycore_tstate.h | 5 +- Makefile.pre.in | 2 + Modules/posixmodule.c | 1 + PCbuild/_freeze_module.vcxproj | 1 + PCbuild/_freeze_module.vcxproj.filters | 3 + PCbuild/pythoncore.vcxproj | 2 + PCbuild/pythoncore.vcxproj.filters | 6 + Python/ceval_macros.h | 7 + Python/pystate.c | 30 +++ Python/qsbr.c | 286 +++++++++++++++++++++++++ 18 files changed, 577 insertions(+), 2 deletions(-) create mode 100644 Include/internal/pycore_qsbr.h create mode 100644 Python/qsbr.c diff --git a/Doc/license.rst b/Doc/license.rst index 9fc0ff7161a5914..cbe918bd1acfe30 100644 --- a/Doc/license.rst +++ b/Doc/license.rst @@ -1095,3 +1095,35 @@ which is distributed under the MIT license:: LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +Global Unbounded Sequences (GUS) +-------------------------------- + +The file :file:`Python/qsbr.c` is adapted from FreeBSD's "Global Unbounded +Sequences" safe memory reclamation scheme in +`subr_smr.c <https://github.com/freebsd/freebsd-src/blob/main/sys/kern/subr_smr.c>`_. +The file is distributed under the 2-Clause BSD License:: + + Copyright (c) 2019,2020 Jeffrey Roberson <jeff@FreeBSD.org> + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice unmodified, this list of conditions, and the following + disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Include/cpython/pyatomic.h b/Include/cpython/pyatomic.h index 9b5774190ee99e9..737eed8b12dd817 100644 --- a/Include/cpython/pyatomic.h +++ b/Include/cpython/pyatomic.h @@ -475,6 +475,12 @@ _Py_atomic_store_int_release(int *obj, int value); static inline int _Py_atomic_load_int_acquire(const int *obj); +static inline void +_Py_atomic_store_uint64_release(uint64_t *obj, uint64_t value); + +static inline uint64_t +_Py_atomic_load_uint64_acquire(const uint64_t *obj); + static inline uint32_t _Py_atomic_load_uint32_acquire(const uint32_t *obj); diff --git a/Include/cpython/pyatomic_gcc.h b/Include/cpython/pyatomic_gcc.h index bc74149f73e83cf..de23edfc6877d23 100644 --- a/Include/cpython/pyatomic_gcc.h +++ b/Include/cpython/pyatomic_gcc.h @@ -504,6 +504,14 @@ static inline int _Py_atomic_load_int_acquire(const int *obj) { return __atomic_load_n(obj, __ATOMIC_ACQUIRE); } +static inline void +_Py_atomic_store_uint64_release(uint64_t *obj, uint64_t value) +{ __atomic_store_n(obj, value, __ATOMIC_RELEASE); } + +static inline uint64_t +_Py_atomic_load_uint64_acquire(const uint64_t *obj) +{ return __atomic_load_n(obj, __ATOMIC_ACQUIRE); } + static inline uint32_t _Py_atomic_load_uint32_acquire(const uint32_t *obj) { return __atomic_load_n(obj, __ATOMIC_ACQUIRE); } diff --git a/Include/cpython/pyatomic_msc.h b/Include/cpython/pyatomic_msc.h index 6ab6401cf81e8ac..9809d9806d7b574 100644 --- a/Include/cpython/pyatomic_msc.h +++ b/Include/cpython/pyatomic_msc.h @@ -952,13 +952,39 @@ _Py_atomic_load_int_acquire(const int *obj) #endif } +static inline void +_Py_atomic_store_uint64_release(uint64_t *obj, uint64_t value) +{ +#if defined(_M_X64) || defined(_M_IX86) + *(uint64_t volatile *)obj = value; +#elif defined(_M_ARM64) + _Py_atomic_ASSERT_ARG_TYPE(unsigned __int64); + __stlr64((unsigned __int64 volatile *)obj, (unsigned __int64)value); +#else +# error "no implementation of _Py_atomic_store_uint64_release" +#endif +} + +static inline uint64_t +_Py_atomic_load_uint64_acquire(const uint64_t *obj) +{ +#if defined(_M_X64) || defined(_M_IX86) + return *(uint64_t volatile *)obj; +#elif defined(_M_ARM64) + _Py_atomic_ASSERT_ARG_TYPE(__int64); + return (uint64_t)__ldar64((unsigned __int64 volatile *)obj); +#else +# error "no implementation of _Py_atomic_load_uint64_acquire" +#endif +} + static inline uint32_t _Py_atomic_load_uint32_acquire(const uint32_t *obj) { #if defined(_M_X64) || defined(_M_IX86) return *(uint32_t volatile *)obj; #elif defined(_M_ARM64) - return (int)__ldar32((uint32_t volatile *)obj); + return (uint32_t)__ldar32((uint32_t volatile *)obj); #else # error "no implementation of _Py_atomic_load_uint32_acquire" #endif diff --git a/Include/cpython/pyatomic_std.h b/Include/cpython/pyatomic_std.h index d3004dbd24ed09a..f5bd73a8a49e31b 100644 --- a/Include/cpython/pyatomic_std.h +++ b/Include/cpython/pyatomic_std.h @@ -887,6 +887,22 @@ _Py_atomic_load_int_acquire(const int *obj) memory_order_acquire); } +static inline void +_Py_atomic_store_uint64_release(uint64_t *obj, uint64_t value) +{ + _Py_USING_STD; + atomic_store_explicit((_Atomic(uint64_t)*)obj, value, + memory_order_release); +} + +static inline uint64_t +_Py_atomic_load_uint64_acquire(const uint64_t *obj) +{ + _Py_USING_STD; + return atomic_load_explicit((const _Atomic(uint64_t)*)obj, + memory_order_acquire); +} + static inline uint32_t _Py_atomic_load_uint32_acquire(const uint32_t *obj) { diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index c07447183d62090..567d6a9bd510ab4 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -30,6 +30,7 @@ extern "C" { #include "pycore_mimalloc.h" // struct _mimalloc_interp_state #include "pycore_object_state.h" // struct _py_object_state #include "pycore_obmalloc.h" // struct _obmalloc_state +#include "pycore_qsbr.h" // struct _qsbr_state #include "pycore_tstate.h" // _PyThreadStateImpl #include "pycore_tuple.h" // struct _Py_tuple_state #include "pycore_typeobject.h" // struct types_state @@ -197,6 +198,7 @@ struct _is { struct _warnings_runtime_state warnings; struct atexit_state atexit; struct _stoptheworld_state stoptheworld; + struct _qsbr_shared qsbr; #if defined(Py_GIL_DISABLED) struct _mimalloc_interp_state mimalloc; diff --git a/Include/internal/pycore_qsbr.h b/Include/internal/pycore_qsbr.h new file mode 100644 index 000000000000000..475f00deedc2265 --- /dev/null +++ b/Include/internal/pycore_qsbr.h @@ -0,0 +1,139 @@ +// The QSBR APIs (quiescent state-based reclamation) provide a mechanism for +// the free-threaded build to safely reclaim memory when there may be +// concurrent accesses. +// +// Many operations in the free-threaded build are protected by locks. However, +// in some cases, we want to allow reads to happen concurrently with updates. +// In this case, we need to delay freeing ("reclaiming") any memory that may be +// concurrently accessed by a reader. The QSBR APIs provide a way to do this. +#ifndef Py_INTERNAL_QSBR_H +#define Py_INTERNAL_QSBR_H + +#include <stdbool.h> +#include <stdint.h> +#include "pycore_lock.h" // PyMutex + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef Py_BUILD_CORE +# error "this header requires Py_BUILD_CORE define" +#endif + +// The shared write sequence is always odd and incremented by two. Detached +// threads are indicated by a read sequence of zero. This avoids collisions +// between the offline state and any valid sequence number even if the +// sequences numbers wrap around. +#define QSBR_OFFLINE 0 +#define QSBR_INITIAL 1 +#define QSBR_INCR 2 + +struct _qsbr_shared; +struct _PyThreadStateImpl; // forward declare to avoid circular dependency + +// Per-thread state +struct _qsbr_thread_state { + // Last observed write sequence (or 0 if detached) + uint64_t seq; + + // Shared (per-interpreter) QSBR state + struct _qsbr_shared *shared; + + // Thread state (or NULL) + PyThreadState *tstate; + + // Used to defer advancing write sequence a fixed number of times + int deferrals; + + // Is this thread state allocated? + bool allocated; + struct _qsbr_thread_state *freelist_next; +}; + +// Padding to avoid false sharing +struct _qsbr_pad { + struct _qsbr_thread_state qsbr; + char __padding[64 - sizeof(struct _qsbr_thread_state)]; +}; + +// Per-interpreter state +struct _qsbr_shared { + // Write sequence: always odd, incremented by two + uint64_t wr_seq; + + // Minimum observed read sequence of all QSBR thread states + uint64_t rd_seq; + + // Array of QSBR thread states. + struct _qsbr_pad *array; + Py_ssize_t size; + + // Freelist of unused _qsbr_thread_states (protected by mutex) + PyMutex mutex; + struct _qsbr_thread_state *freelist; +}; + +static inline uint64_t +_Py_qsbr_shared_current(struct _qsbr_shared *shared) +{ + return _Py_atomic_load_uint64_acquire(&shared->wr_seq); +} + +// Reports a quiescent state: the caller no longer holds any pointer to shared +// data not protected by locks or reference counts. +static inline void +_Py_qsbr_quiescent_state(struct _qsbr_thread_state *qsbr) +{ + uint64_t seq = _Py_qsbr_shared_current(qsbr->shared); + _Py_atomic_store_uint64_release(&qsbr->seq, seq); +} + +// Advance the write sequence and return the new goal. This should be called +// after data is removed. The returned goal is used with `_Py_qsbr_poll()` to +// determine when it is safe to reclaim (free) the memory. +extern uint64_t +_Py_qsbr_advance(struct _qsbr_shared *shared); + +// Batches requests to advance the write sequence. This advances the write +// sequence every N calls, which reduces overhead but increases time to +// reclamation. Returns the new goal. +extern uint64_t +_Py_qsbr_deferred_advance(struct _qsbr_thread_state *qsbr); + +// Have the read sequences advanced to the given goal? If this returns true, +// it safe to reclaim any memory tagged with the goal (or earlier goal). +extern bool +_Py_qsbr_poll(struct _qsbr_thread_state *qsbr, uint64_t goal); + +// Called when thread attaches to interpreter +extern void +_Py_qsbr_attach(struct _qsbr_thread_state *qsbr); + +// Called when thread detaches from interpreter +extern void +_Py_qsbr_detach(struct _qsbr_thread_state *qsbr); + +// Reserves (allocates) a QSBR state and returns its index. +extern Py_ssize_t +_Py_qsbr_reserve(PyInterpreterState *interp); + +// Associates a PyThreadState with the QSBR state at the given index +extern void +_Py_qsbr_register(struct _PyThreadStateImpl *tstate, + PyInterpreterState *interp, Py_ssize_t index); + +// Disassociates a PyThreadState from the QSBR state and frees the QSBR state. +extern void +_Py_qsbr_unregister(struct _PyThreadStateImpl *tstate); + +extern void +_Py_qsbr_fini(PyInterpreterState *interp); + +extern void +_Py_qsbr_after_fork(struct _PyThreadStateImpl *tstate); + +#ifdef __cplusplus +} +#endif +#endif /* !Py_INTERNAL_QSBR_H */ diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index 7a05c105d7bf128..be81604d653814c 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -17,6 +17,7 @@ extern "C" { #include "pycore_pyhash.h" // pyhash_state_INIT #include "pycore_pymem_init.h" // _pymem_allocators_standard_INIT #include "pycore_pythread.h" // _pythread_RUNTIME_INIT +#include "pycore_qsbr.h" // QSBR_INITIAL #include "pycore_runtime_init_generated.h" // _Py_bytes_characters_INIT #include "pycore_signal.h" // _signals_RUNTIME_INIT #include "pycore_tracemalloc.h" // _tracemalloc_runtime_state_INIT @@ -169,6 +170,10 @@ extern PyTypeObject _PyExc_MemoryError; { .threshold = 10, }, \ }, \ }, \ + .qsbr = { \ + .wr_seq = QSBR_INITIAL, \ + .rd_seq = QSBR_INITIAL, \ + }, \ .dtoa = _dtoa_state_INIT(&(INTERP)), \ .dict_state = _dict_state_INIT, \ .func_state = { \ diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h index 7fb9ab2056704e1..d0f980ed49ee3e2 100644 --- a/Include/internal/pycore_tstate.h +++ b/Include/internal/pycore_tstate.h @@ -8,9 +8,10 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif +#include "pycore_brc.h" // struct _brc_thread_state #include "pycore_freelist.h" // struct _Py_freelist_state #include "pycore_mimalloc.h" // struct _mimalloc_thread_state -#include "pycore_brc.h" // struct _brc_thread_state +#include "pycore_qsbr.h" // struct qsbr static inline void @@ -27,6 +28,8 @@ typedef struct _PyThreadStateImpl { // semi-public fields are in PyThreadState. PyThreadState base; + struct _qsbr_thread_state *qsbr; // only used by free-threaded build + #ifdef Py_GIL_DISABLED struct _gc_thread_state gc; struct _mimalloc_thread_state mimalloc; diff --git a/Makefile.pre.in b/Makefile.pre.in index 8252e6631c5af58..66c4266b2f8f97b 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -458,6 +458,7 @@ PYTHON_OBJS= \ Python/pystate.o \ Python/pythonrun.o \ Python/pytime.o \ + Python/qsbr.o \ Python/bootstrap_hash.o \ Python/specialize.o \ Python/structmember.o \ @@ -1162,6 +1163,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/internal/pycore_pystats.h \ $(srcdir)/Include/internal/pycore_pythonrun.h \ $(srcdir)/Include/internal/pycore_pythread.h \ + $(srcdir)/Include/internal/pycore_qsbr.h \ $(srcdir)/Include/internal/pycore_range.h \ $(srcdir)/Include/internal/pycore_runtime.h \ $(srcdir)/Include/internal/pycore_runtime_init.h \ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 958b5a5e6e24066..9d9c9bd76b7fff6 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -645,6 +645,7 @@ PyOS_AfterFork_Child(void) #ifdef Py_GIL_DISABLED _Py_brc_after_fork(tstate->interp); + _Py_qsbr_after_fork((_PyThreadStateImpl *)tstate); #endif status = _PyEval_ReInitThreads(tstate); diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index 49f529ebbc2f9b0..00ad3e2472af04c 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -253,6 +253,7 @@ <ClCompile Include="..\Python\pythonrun.c" /> <ClCompile Include="..\Python\Python-tokenize.c" /> <ClCompile Include="..\Python\pytime.c" /> + <ClCompile Include="..\Python\qsbr.c" /> <ClCompile Include="..\Python\specialize.c" /> <ClCompile Include="..\Python\structmember.c" /> <ClCompile Include="..\Python\suggestions.c" /> diff --git a/PCbuild/_freeze_module.vcxproj.filters b/PCbuild/_freeze_module.vcxproj.filters index 5b1bd7552b4cd98..aea5f730607658e 100644 --- a/PCbuild/_freeze_module.vcxproj.filters +++ b/PCbuild/_freeze_module.vcxproj.filters @@ -376,6 +376,9 @@ <ClCompile Include="..\Python\pytime.c"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="..\Python\qsbr.c"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="..\Objects\rangeobject.c"> <Filter>Source Files</Filter> </ClCompile> diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index abfafbb2a32f45c..c7b698f0e17a396 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -276,6 +276,7 @@ <ClInclude Include="..\Include\internal\pycore_pystats.h" /> <ClInclude Include="..\Include\internal\pycore_pythonrun.h" /> <ClInclude Include="..\Include\internal\pycore_pythread.h" /> + <ClInclude Include="..\Include\internal\pycore_qsbr.h" /> <ClInclude Include="..\Include\internal\pycore_range.h" /> <ClInclude Include="..\Include\internal\pycore_runtime.h" /> <ClInclude Include="..\Include\internal\pycore_runtime_init.h" /> @@ -614,6 +615,7 @@ <ClCompile Include="..\Python\pystrcmp.c" /> <ClCompile Include="..\Python\pystrhex.c" /> <ClCompile Include="..\Python\pystrtod.c" /> + <ClCompile Include="..\Python\qsbr.c" /> <ClCompile Include="..\Python\dtoa.c" /> <ClCompile Include="..\Python\Python-ast.c" /> <ClCompile Include="..\Python\Python-tokenize.c" /> diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index d14f5a6d7fb0fca..ffe93dc787a8b8d 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -753,6 +753,9 @@ <ClInclude Include="..\Include\internal\pycore_pythread.h"> <Filter>Include\internal</Filter> </ClInclude> + <ClInclude Include="..\Include\internal\pycore_qsbr.h"> + <Filter>Include\internal</Filter> + </ClInclude> <ClInclude Include="..\Include\internal\pycore_range.h"> <Filter>Include\internal</Filter> </ClInclude> @@ -1421,6 +1424,9 @@ <ClCompile Include="..\Python\pystrtod.c"> <Filter>Python</Filter> </ClCompile> + <ClCompile Include="..\Python\qsbr.c"> + <Filter>Python</Filter> + </ClCompile> <ClCompile Include="..\Python\dtoa.c"> <Filter>Python</Filter> </ClCompile> diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h index c2550f53ad6eaaa..1043966c9a82771 100644 --- a/Python/ceval_macros.h +++ b/Python/ceval_macros.h @@ -86,6 +86,12 @@ #define PRE_DISPATCH_GOTO() ((void)0) #endif +#ifdef Py_GIL_DISABLED +#define QSBR_QUIESCENT_STATE(tstate) _Py_qsbr_quiescent_state(((_PyThreadStateImpl *)tstate)->qsbr) +#else +#define QSBR_QUIESCENT_STATE(tstate) +#endif + /* Do interpreter dispatch accounting for tracing and instrumentation */ #define DISPATCH() \ @@ -117,6 +123,7 @@ #define CHECK_EVAL_BREAKER() \ _Py_CHECK_EMSCRIPTEN_SIGNALS_PERIODICALLY(); \ + QSBR_QUIESCENT_STATE(tstate); \ if (_Py_atomic_load_uintptr_relaxed(&tstate->interp->ceval.eval_breaker) & _PY_EVAL_EVENTS_MASK) { \ if (_Py_HandlePending(tstate) != 0) { \ GOTO_ERROR(error); \ diff --git a/Python/pystate.c b/Python/pystate.c index 24f9b7790915ab5..c2ccc276449d4f3 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -953,6 +953,8 @@ PyInterpreterState_Delete(PyInterpreterState *interp) PyThread_free_lock(interp->id_mutex); } + _Py_qsbr_fini(interp); + _PyObject_FiniState(interp); free_interpreter(interp); @@ -1386,6 +1388,14 @@ new_threadstate(PyInterpreterState *interp, int whence) if (new_tstate == NULL) { return NULL; } +#ifdef Py_GIL_DISABLED + Py_ssize_t qsbr_idx = _Py_qsbr_reserve(interp); + if (qsbr_idx < 0) { + PyMem_RawFree(new_tstate); + return NULL; + } +#endif + /* We serialize concurrent creation to protect global state. */ HEAD_LOCK(runtime); @@ -1420,6 +1430,12 @@ new_threadstate(PyInterpreterState *interp, int whence) // Must be called with lock unlocked to avoid re-entrancy deadlock. PyMem_RawFree(new_tstate); } + +#ifdef Py_GIL_DISABLED + // Must be called with lock unlocked to avoid lock ordering deadlocks. + _Py_qsbr_register(tstate, interp, qsbr_idx); +#endif + return (PyThreadState *)tstate; } @@ -1611,6 +1627,10 @@ tstate_delete_common(PyThreadState *tstate) } HEAD_UNLOCK(runtime); +#ifdef Py_GIL_DISABLED + _Py_qsbr_unregister((_PyThreadStateImpl *)tstate); +#endif + // XXX Unbind in PyThreadState_Clear(), or earlier // (and assert not-equal here)? if (tstate->_status.bound_gilstate) { @@ -1652,6 +1672,9 @@ void _PyThreadState_DeleteCurrent(PyThreadState *tstate) { _Py_EnsureTstateNotNULL(tstate); +#ifdef Py_GIL_DISABLED + _Py_qsbr_detach(((_PyThreadStateImpl *)tstate)->qsbr); +#endif tstate_set_detached(tstate); tstate_delete_common(tstate); current_fast_clear(tstate->interp->runtime); @@ -1873,6 +1896,10 @@ _PyThreadState_Attach(PyThreadState *tstate) tstate_wait_attach(tstate); } +#ifdef Py_GIL_DISABLED + _Py_qsbr_attach(((_PyThreadStateImpl *)tstate)->qsbr); +#endif + // Resume previous critical section. This acquires the lock(s) from the // top-most critical section. if (tstate->critical_section != 0) { @@ -1893,6 +1920,9 @@ detach_thread(PyThreadState *tstate, int detached_state) if (tstate->critical_section != 0) { _PyCriticalSection_SuspendAll(tstate); } +#ifdef Py_GIL_DISABLED + _Py_qsbr_detach(((_PyThreadStateImpl *)tstate)->qsbr); +#endif tstate_deactivate(tstate); tstate_set_detached(tstate); current_fast_clear(&_PyRuntime); diff --git a/Python/qsbr.c b/Python/qsbr.c new file mode 100644 index 000000000000000..7f7ae03cf60d22f --- /dev/null +++ b/Python/qsbr.c @@ -0,0 +1,286 @@ +/* + * Implementation of safe memory reclamation scheme using + * quiescent states. + * + * This is dervied from the "GUS" safe memory reclamation technique + * in FreeBSD written by Jeffrey Roberson. It is heavily modified. Any bugs + * in this code are likely due to the modifications. + * + * The original copyright is preserved below. + * + * Copyright (c) 2019,2020 Jeffrey Roberson <jeff@FreeBSD.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "Python.h" +#include "pycore_initconfig.h" // _PyStatus_NO_MEMORY() +#include "pycore_lock.h" // PyMutex_Lock() +#include "pycore_qsbr.h" +#include "pycore_pystate.h" // _PyThreadState_GET() + + +// Wrap-around safe comparison. This is a holdover from the FreeBSD +// implementation, which uses 32-bit sequence numbers. We currently use 64-bit +// sequence numbers, so wrap-around is unlikely. +#define QSBR_LT(a, b) ((int64_t)((a)-(b)) < 0) +#define QSBR_LEQ(a, b) ((int64_t)((a)-(b)) <= 0) + +// Starting size of the array of qsbr thread states +#define MIN_ARRAY_SIZE 8 + +// For _Py_qsbr_deferred_advance(): the number of deferrals before advancing +// the write sequence. +#define QSBR_DEFERRED_LIMIT 10 + +// Allocate a QSBR thread state from the freelist +static struct _qsbr_thread_state * +qsbr_allocate(struct _qsbr_shared *shared) +{ + struct _qsbr_thread_state *qsbr = shared->freelist; + if (qsbr == NULL) { + return NULL; + } + shared->freelist = qsbr->freelist_next; + qsbr->freelist_next = NULL; + qsbr->shared = shared; + qsbr->allocated = true; + return qsbr; +} + +// Initialize (or reintialize) the freelist of QSBR thread states +static void +initialize_new_array(struct _qsbr_shared *shared) +{ + for (Py_ssize_t i = 0; i != shared->size; i++) { + struct _qsbr_thread_state *qsbr = &shared->array[i].qsbr; + if (qsbr->tstate != NULL) { + // Update the thread state pointer to its QSBR state + _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)qsbr->tstate; + tstate->qsbr = qsbr; + } + if (!qsbr->allocated) { + // Push to freelist + qsbr->freelist_next = shared->freelist; + shared->freelist = qsbr; + } + } +} + +// Grow the array of QSBR thread states. Returns 0 on success, -1 on failure. +static int +grow_thread_array(struct _qsbr_shared *shared) +{ + Py_ssize_t new_size = shared->size * 2; + if (new_size < MIN_ARRAY_SIZE) { + new_size = MIN_ARRAY_SIZE; + } + + struct _qsbr_pad *array = PyMem_RawCalloc(new_size, sizeof(*array)); + if (array == NULL) { + return -1; + } + + struct _qsbr_pad *old = shared->array; + if (old != NULL) { + memcpy(array, shared->array, shared->size * sizeof(*array)); + } + + shared->array = array; + shared->size = new_size; + shared->freelist = NULL; + initialize_new_array(shared); + + PyMem_RawFree(old); + return 0; +} + +uint64_t +_Py_qsbr_advance(struct _qsbr_shared *shared) +{ + // NOTE: with 64-bit sequence numbers, we don't have to worry too much + // about the wr_seq getting too far ahead of rd_seq, but if we ever use + // 32-bit sequence numbers, we'll need to be more careful. + return _Py_atomic_add_uint64(&shared->wr_seq, QSBR_INCR) + QSBR_INCR; +} + +uint64_t +_Py_qsbr_deferred_advance(struct _qsbr_thread_state *qsbr) +{ + if (++qsbr->deferrals < QSBR_DEFERRED_LIMIT) { + return _Py_qsbr_shared_current(qsbr->shared) + QSBR_INCR; + } + qsbr->deferrals = 0; + return _Py_qsbr_advance(qsbr->shared); +} + +static uint64_t +qsbr_poll_scan(struct _qsbr_shared *shared) +{ + // Synchronize with store in _Py_qsbr_attach(). We need to ensure that + // the reads from each thread's sequence number are not reordered to see + // earlier "offline" states. + _Py_atomic_fence_seq_cst(); + + // Compute the minimum sequence number of all attached threads + uint64_t min_seq = _Py_atomic_load_uint64(&shared->wr_seq); + struct _qsbr_pad *array = shared->array; + for (Py_ssize_t i = 0, size = shared->size; i != size; i++) { + struct _qsbr_thread_state *qsbr = &array[i].qsbr; + + uint64_t seq = _Py_atomic_load_uint64(&qsbr->seq); + if (seq != QSBR_OFFLINE && QSBR_LT(seq, min_seq)) { + min_seq = seq; + } + } + + // Update the shared read sequence + uint64_t rd_seq = _Py_atomic_load_uint64(&shared->rd_seq); + if (QSBR_LT(rd_seq, min_seq)) { + // It's okay if the compare-exchange failed: another thread updated it + (void)_Py_atomic_compare_exchange_uint64(&shared->rd_seq, &rd_seq, min_seq); + rd_seq = min_seq; + } + + return rd_seq; +} + +bool +_Py_qsbr_poll(struct _qsbr_thread_state *qsbr, uint64_t goal) +{ + assert(_PyThreadState_GET()->state == _Py_THREAD_ATTACHED); + + uint64_t rd_seq = _Py_atomic_load_uint64(&qsbr->shared->rd_seq); + if (QSBR_LEQ(goal, rd_seq)) { + return true; + } + + rd_seq = qsbr_poll_scan(qsbr->shared); + return QSBR_LEQ(goal, rd_seq); +} + +void +_Py_qsbr_attach(struct _qsbr_thread_state *qsbr) +{ + assert(qsbr->seq == 0 && "already attached"); + + uint64_t seq = _Py_qsbr_shared_current(qsbr->shared); + _Py_atomic_store_uint64(&qsbr->seq, seq); // needs seq_cst +} + +void +_Py_qsbr_detach(struct _qsbr_thread_state *qsbr) +{ + assert(qsbr->seq != 0 && "already detached"); + + _Py_atomic_store_uint64_release(&qsbr->seq, QSBR_OFFLINE); +} + +Py_ssize_t +_Py_qsbr_reserve(PyInterpreterState *interp) +{ + struct _qsbr_shared *shared = &interp->qsbr; + + PyMutex_Lock(&shared->mutex); + // Try allocating from our internal freelist + struct _qsbr_thread_state *qsbr = qsbr_allocate(shared); + + // If there are no free entries, we pause all threads, grow the array, + // and update the pointers in PyThreadState to entries in the new array. + if (qsbr == NULL) { + _PyEval_StopTheWorld(interp); + if (grow_thread_array(shared) == 0) { + qsbr = qsbr_allocate(shared); + } + _PyEval_StartTheWorld(interp); + } + PyMutex_Unlock(&shared->mutex); + + if (qsbr == NULL) { + return -1; + } + + // Return an index rather than the pointer because the array may be + // resized and the pointer invalidated. + return (struct _qsbr_pad *)qsbr - shared->array; +} + +void +_Py_qsbr_register(_PyThreadStateImpl *tstate, PyInterpreterState *interp, + Py_ssize_t index) +{ + // Associate the QSBR state with the thread state + struct _qsbr_shared *shared = &interp->qsbr; + + PyMutex_Lock(&shared->mutex); + struct _qsbr_thread_state *qsbr = &interp->qsbr.array[index].qsbr; + assert(qsbr->allocated && qsbr->tstate == NULL); + qsbr->tstate = (PyThreadState *)tstate; + tstate->qsbr = qsbr; + PyMutex_Unlock(&shared->mutex); +} + +void +_Py_qsbr_unregister(_PyThreadStateImpl *tstate) +{ + struct _qsbr_thread_state *qsbr = tstate->qsbr; + struct _qsbr_shared *shared = qsbr->shared; + + assert(qsbr->seq == 0 && "thread state must be detached"); + + PyMutex_Lock(&shared->mutex); + assert(qsbr->allocated && qsbr->tstate == (PyThreadState *)tstate); + tstate->qsbr = NULL; + qsbr->tstate = NULL; + qsbr->allocated = false; + qsbr->freelist_next = shared->freelist; + shared->freelist = qsbr; + PyMutex_Unlock(&shared->mutex); +} + +void +_Py_qsbr_fini(PyInterpreterState *interp) +{ + struct _qsbr_shared *shared = &interp->qsbr; + PyMem_RawFree(shared->array); + shared->array = NULL; + shared->size = 0; + shared->freelist = NULL; +} + +void +_Py_qsbr_after_fork(_PyThreadStateImpl *tstate) +{ + struct _qsbr_thread_state *this_qsbr = tstate->qsbr; + struct _qsbr_shared *shared = this_qsbr->shared; + + _PyMutex_at_fork_reinit(&shared->mutex); + + for (Py_ssize_t i = 0; i != shared->size; i++) { + struct _qsbr_thread_state *qsbr = &shared->array[i].qsbr; + if (qsbr != this_qsbr && qsbr->allocated) { + qsbr->tstate = NULL; + qsbr->allocated = false; + qsbr->freelist_next = shared->freelist; + shared->freelist = qsbr; + } + } +} From 8b776e0f41d7711f3e2be2435bf85f2d5fa6e009 Mon Sep 17 00:00:00 2001 From: Ammar Askar <ammar@ammaraskar.com> Date: Fri, 16 Feb 2024 16:17:30 -0500 Subject: [PATCH 320/507] gh-85294: Handle missing arguments to @singledispatchmethod gracefully (GH-21471) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> --- Lib/functools.py | 5 ++++- Lib/test/test_functools.py | 17 ++++++++++++++++- .../2020-07-13-23-59-42.bpo-41122.8P_Brh.rst | 3 +++ 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-07-13-23-59-42.bpo-41122.8P_Brh.rst diff --git a/Lib/functools.py b/Lib/functools.py index ee4197b386178d6..7045be551c8c491 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -918,7 +918,6 @@ def wrapper(*args, **kw): if not args: raise TypeError(f'{funcname} requires at least ' '1 positional argument') - return dispatch(args[0].__class__)(*args, **kw) funcname = getattr(func, '__name__', 'singledispatch function') @@ -968,7 +967,11 @@ def __get__(self, obj, cls=None): return _method dispatch = self.dispatcher.dispatch + funcname = getattr(self.func, '__name__', 'singledispatchmethod method') def _method(*args, **kwargs): + if not args: + raise TypeError(f'{funcname} requires at least ' + '1 positional argument') return dispatch(args[0].__class__).__get__(obj, cls)(*args, **kwargs) _method.__isabstractmethod__ = self.__isabstractmethod__ diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 7c66b906d308baf..2c814d5e8888403 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -2867,11 +2867,26 @@ def _(arg: typing.Union[int, typing.Iterable[str]]): def test_invalid_positional_argument(self): @functools.singledispatch - def f(*args): + def f(*args, **kwargs): pass msg = 'f requires at least 1 positional argument' with self.assertRaisesRegex(TypeError, msg): f() + msg = 'f requires at least 1 positional argument' + with self.assertRaisesRegex(TypeError, msg): + f(a=1) + + def test_invalid_positional_argument_singledispatchmethod(self): + class A: + @functools.singledispatchmethod + def t(self, *args, **kwargs): + pass + msg = 't requires at least 1 positional argument' + with self.assertRaisesRegex(TypeError, msg): + A().t() + msg = 't requires at least 1 positional argument' + with self.assertRaisesRegex(TypeError, msg): + A().t(a=1) def test_union(self): @functools.singledispatch diff --git a/Misc/NEWS.d/next/Library/2020-07-13-23-59-42.bpo-41122.8P_Brh.rst b/Misc/NEWS.d/next/Library/2020-07-13-23-59-42.bpo-41122.8P_Brh.rst new file mode 100644 index 000000000000000..76568d407449f53 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-07-13-23-59-42.bpo-41122.8P_Brh.rst @@ -0,0 +1,3 @@ +Failing to pass arguments properly to :func:`functools.singledispatchmethod` +now throws a TypeError instead of hitting an index out of bounds +internally. From 318f2190bc93796008b0a4241243b0851b418436 Mon Sep 17 00:00:00 2001 From: Brian Schubert <brianm.schubert@gmail.com> Date: Fri, 16 Feb 2024 17:04:17 -0500 Subject: [PATCH 321/507] docs: Add glossary term references to shutil docs (#115559) Add glossary term references to shutil docs --- Doc/library/shutil.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index ff8c9a189ab3de2..f388375045c912a 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -39,7 +39,7 @@ Directory and files operations .. function:: copyfileobj(fsrc, fdst[, length]) - Copy the contents of the file-like object *fsrc* to the file-like object *fdst*. + Copy the contents of the :term:`file-like object <file object>` *fsrc* to the file-like object *fdst*. The integer *length*, if given, is the buffer size. In particular, a negative *length* value means to copy the data without looping over the source data in chunks; by default the data is read in chunks to avoid uncontrolled memory @@ -52,7 +52,7 @@ Directory and files operations Copy the contents (no metadata) of the file named *src* to a file named *dst* and return *dst* in the most efficient way possible. - *src* and *dst* are path-like objects or path names given as strings. + *src* and *dst* are :term:`path-like objects <path-like object>` or path names given as strings. *dst* must be the complete target file name; look at :func:`~shutil.copy` for a copy that accepts a target directory path. If *src* and *dst* @@ -94,7 +94,7 @@ Directory and files operations .. function:: copymode(src, dst, *, follow_symlinks=True) Copy the permission bits from *src* to *dst*. The file contents, owner, and - group are unaffected. *src* and *dst* are path-like objects or path names + group are unaffected. *src* and *dst* are :term:`path-like objects <path-like object>` or path names given as strings. If *follow_symlinks* is false, and both *src* and *dst* are symbolic links, :func:`copymode` will attempt to modify the mode of *dst* itself (rather @@ -113,7 +113,7 @@ Directory and files operations Copy the permission bits, last access time, last modification time, and flags from *src* to *dst*. On Linux, :func:`copystat` also copies the "extended attributes" where possible. The file contents, owner, and - group are unaffected. *src* and *dst* are path-like objects or path + group are unaffected. *src* and *dst* are :term:`path-like objects <path-like object>` or path names given as strings. If *follow_symlinks* is false, and *src* and *dst* both From 8db8d7118e1ef22bc7cdc3d8b657bc10c22c2fd6 Mon Sep 17 00:00:00 2001 From: Donghee Na <donghee.na@python.org> Date: Sat, 17 Feb 2024 10:03:10 +0900 Subject: [PATCH 322/507] =?UTF-8?q?gh-111968:=20Split=20=5FPy=5Fasync=5Fge?= =?UTF-8?q?n=5Fasend=5Ffreelist=20out=20of=20=5FPy=5Fasync=5Fgen=5Ffr?= =?UTF-8?q?=E2=80=A6=20(gh-115546)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Include/internal/pycore_freelist.h | 13 +++++--- Objects/genobject.c | 51 ++++++++++++++++++------------ 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/Include/internal/pycore_freelist.h b/Include/internal/pycore_freelist.h index 9900ce98037060d..e684e084b8bef8e 100644 --- a/Include/internal/pycore_freelist.h +++ b/Include/internal/pycore_freelist.h @@ -105,11 +105,15 @@ struct _Py_async_gen_freelist { fragmentation, as _PyAsyncGenWrappedValue and PyAsyncGenASend are short-living objects that are instantiated for every __anext__() call. */ - struct _PyAsyncGenWrappedValue* value_freelist[_PyAsyncGen_MAXFREELIST]; - int value_numfree; + struct _PyAsyncGenWrappedValue* items[_PyAsyncGen_MAXFREELIST]; + int numfree; +#endif +}; - struct PyAsyncGenASend* asend_freelist[_PyAsyncGen_MAXFREELIST]; - int asend_numfree; +struct _Py_async_gen_asend_freelist { +#ifdef WITH_FREELISTS + struct PyAsyncGenASend* items[_PyAsyncGen_MAXFREELIST]; + int numfree; #endif }; @@ -129,6 +133,7 @@ struct _Py_object_freelists { struct _Py_slice_freelist slices; struct _Py_context_freelist contexts; struct _Py_async_gen_freelist async_gens; + struct _Py_async_gen_asend_freelist async_gen_asends; struct _Py_object_stack_freelist object_stacks; }; diff --git a/Objects/genobject.c b/Objects/genobject.c index a1b6db1b5889d39..8d1dbb72ba9ec21 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -1635,6 +1635,13 @@ get_async_gen_freelist(void) struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); return &freelists->async_gens; } + +static struct _Py_async_gen_asend_freelist * +get_async_gen_asend_freelist(void) +{ + struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); + return &freelists->async_gen_asends; +} #endif @@ -1659,25 +1666,27 @@ void _PyAsyncGen_ClearFreeLists(struct _Py_object_freelists *freelist_state, int is_finalization) { #ifdef WITH_FREELISTS - struct _Py_async_gen_freelist *state = &freelist_state->async_gens; + struct _Py_async_gen_freelist *freelist = &freelist_state->async_gens; - while (state->value_numfree > 0) { + while (freelist->numfree > 0) { _PyAsyncGenWrappedValue *o; - o = state->value_freelist[--state->value_numfree]; + o = freelist->items[--freelist->numfree]; assert(_PyAsyncGenWrappedValue_CheckExact(o)); PyObject_GC_Del(o); } - while (state->asend_numfree > 0) { + struct _Py_async_gen_asend_freelist *asend_freelist = &freelist_state->async_gen_asends; + + while (asend_freelist->numfree > 0) { PyAsyncGenASend *o; - o = state->asend_freelist[--state->asend_numfree]; + o = asend_freelist->items[--asend_freelist->numfree]; assert(Py_IS_TYPE(o, &_PyAsyncGenASend_Type)); PyObject_GC_Del(o); } if (is_finalization) { - state->value_numfree = -1; - state->asend_numfree = -1; + freelist->numfree = -1; + asend_freelist->numfree = -1; } #endif } @@ -1726,11 +1735,11 @@ async_gen_asend_dealloc(PyAsyncGenASend *o) Py_CLEAR(o->ags_gen); Py_CLEAR(o->ags_sendval); #ifdef WITH_FREELISTS - struct _Py_async_gen_freelist *async_gen_freelist = get_async_gen_freelist(); - if (async_gen_freelist->asend_numfree >= 0 && async_gen_freelist->asend_numfree < _PyAsyncGen_MAXFREELIST) { + struct _Py_async_gen_asend_freelist *freelist = get_async_gen_asend_freelist(); + if (freelist->numfree >= 0 && freelist->numfree < _PyAsyncGen_MAXFREELIST) { assert(PyAsyncGenASend_CheckExact(o)); _PyGC_CLEAR_FINALIZED((PyObject *)o); - async_gen_freelist->asend_freelist[async_gen_freelist->asend_numfree++] = o; + freelist->items[freelist->numfree++] = o; } else #endif @@ -1896,10 +1905,10 @@ async_gen_asend_new(PyAsyncGenObject *gen, PyObject *sendval) { PyAsyncGenASend *o; #ifdef WITH_FREELISTS - struct _Py_async_gen_freelist *async_gen_freelist = get_async_gen_freelist(); - if (async_gen_freelist->asend_numfree > 0) { - async_gen_freelist->asend_numfree--; - o = async_gen_freelist->asend_freelist[async_gen_freelist->asend_numfree]; + struct _Py_async_gen_asend_freelist *freelist = get_async_gen_asend_freelist(); + if (freelist->numfree > 0) { + freelist->numfree--; + o = freelist->items[freelist->numfree]; _Py_NewReference((PyObject *)o); } else @@ -1931,10 +1940,10 @@ async_gen_wrapped_val_dealloc(_PyAsyncGenWrappedValue *o) _PyObject_GC_UNTRACK((PyObject *)o); Py_CLEAR(o->agw_val); #ifdef WITH_FREELISTS - struct _Py_async_gen_freelist *async_gen_freelist = get_async_gen_freelist(); - if (async_gen_freelist->value_numfree >= 0 && async_gen_freelist->value_numfree < _PyAsyncGen_MAXFREELIST) { + struct _Py_async_gen_freelist *freelist = get_async_gen_freelist(); + if (freelist->numfree >= 0 && freelist->numfree < _PyAsyncGen_MAXFREELIST) { assert(_PyAsyncGenWrappedValue_CheckExact(o)); - async_gen_freelist->value_freelist[async_gen_freelist->value_numfree++] = o; + freelist->items[freelist->numfree++] = o; OBJECT_STAT_INC(to_freelist); } else @@ -2004,10 +2013,10 @@ _PyAsyncGenValueWrapperNew(PyThreadState *tstate, PyObject *val) assert(val); #ifdef WITH_FREELISTS - struct _Py_async_gen_freelist *async_gen_freelist = get_async_gen_freelist(); - if (async_gen_freelist->value_numfree > 0) { - async_gen_freelist->value_numfree--; - o = async_gen_freelist->value_freelist[async_gen_freelist->value_numfree]; + struct _Py_async_gen_freelist *freelist = get_async_gen_freelist(); + if (freelist->numfree > 0) { + freelist->numfree--; + o = freelist->items[freelist->numfree]; OBJECT_STAT_INC(from_freelist); assert(_PyAsyncGenWrappedValue_CheckExact(o)); _Py_NewReference((PyObject*)o); From 73e8637002639e565938d3f205bf46e7f1dbd6a8 Mon Sep 17 00:00:00 2001 From: Jamie Phan <jamie@ordinarylab.dev> Date: Sat, 17 Feb 2024 13:38:07 +1100 Subject: [PATCH 323/507] gh-113812: Allow DatagramTransport.sendto to send empty data (#115199) Also include the UDP packet header sizes (8 bytes per packet) in the buffer size reported to the flow control subsystem. --- Doc/library/asyncio-protocol.rst | 5 +++++ Doc/whatsnew/3.13.rst | 6 +++++ Lib/asyncio/proactor_events.py | 5 +---- Lib/asyncio/selector_events.py | 4 +--- Lib/asyncio/transports.py | 2 ++ Lib/test/test_asyncio/test_proactor_events.py | 22 ++++++++++++++----- Lib/test/test_asyncio/test_selector_events.py | 19 ++++++++++++---- ...-02-09-12-22-47.gh-issue-113812.wOraaG.rst | 3 +++ 8 files changed, 50 insertions(+), 16 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-09-12-22-47.gh-issue-113812.wOraaG.rst diff --git a/Doc/library/asyncio-protocol.rst b/Doc/library/asyncio-protocol.rst index ecd8cdc709af7d2..7c08d65f26bc27e 100644 --- a/Doc/library/asyncio-protocol.rst +++ b/Doc/library/asyncio-protocol.rst @@ -362,6 +362,11 @@ Datagram Transports This method does not block; it buffers the data and arranges for it to be sent out asynchronously. + .. versionchanged:: 3.13 + This method can be called with an empty bytes object to send a + zero-length datagram. The buffer size calculation used for flow + control is also updated to account for the datagram header. + .. method:: DatagramTransport.abort() Close the transport immediately, without waiting for pending diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 1e0764144a28553..7c6a2af28758bee 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -218,6 +218,12 @@ asyncio the Unix socket when the server is closed. (Contributed by Pierre Ossman in :gh:`111246`.) +* :meth:`asyncio.DatagramTransport.sendto` will now send zero-length + datagrams if called with an empty bytes object. The transport flow + control also now accounts for the datagram header when calculating + the buffer size. + (Contributed by Jamie Phan in :gh:`115199`.) + copy ---- diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py index 1e2a730cf368a99..a512db6367b20ac 100644 --- a/Lib/asyncio/proactor_events.py +++ b/Lib/asyncio/proactor_events.py @@ -487,9 +487,6 @@ def sendto(self, data, addr=None): raise TypeError('data argument must be bytes-like object (%r)', type(data)) - if not data: - return - if self._address is not None and addr not in (None, self._address): raise ValueError( f'Invalid address: must be None or {self._address}') @@ -502,7 +499,7 @@ def sendto(self, data, addr=None): # Ensure that what we buffer is immutable. self._buffer.append((bytes(data), addr)) - self._buffer_size += len(data) + self._buffer_size += len(data) + 8 # include header bytes if self._write_fut is None: # No current write operations are active, kick one off diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index 10fbdd76e93f79c..8e888d26ea07377 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -1241,8 +1241,6 @@ def sendto(self, data, addr=None): if not isinstance(data, (bytes, bytearray, memoryview)): raise TypeError(f'data argument must be a bytes-like object, ' f'not {type(data).__name__!r}') - if not data: - return if self._address: if addr not in (None, self._address): @@ -1278,7 +1276,7 @@ def sendto(self, data, addr=None): # Ensure that what we buffer is immutable. self._buffer.append((bytes(data), addr)) - self._buffer_size += len(data) + self._buffer_size += len(data) + 8 # include header bytes self._maybe_pause_protocol() def _sendto_ready(self): diff --git a/Lib/asyncio/transports.py b/Lib/asyncio/transports.py index 30fd41d49af71ff..34c7ad44ffd8ab3 100644 --- a/Lib/asyncio/transports.py +++ b/Lib/asyncio/transports.py @@ -181,6 +181,8 @@ def sendto(self, data, addr=None): to be sent out asynchronously. addr is target socket address. If addr is None use target address pointed on transport creation. + If data is an empty bytes object a zero-length datagram will be + sent. """ raise NotImplementedError diff --git a/Lib/test/test_asyncio/test_proactor_events.py b/Lib/test/test_asyncio/test_proactor_events.py index c42856e578b8cc8..fcaa2f6ade2b767 100644 --- a/Lib/test/test_asyncio/test_proactor_events.py +++ b/Lib/test/test_asyncio/test_proactor_events.py @@ -585,11 +585,10 @@ def test_sendto_memoryview(self): def test_sendto_no_data(self): transport = self.datagram_transport() - transport._buffer.append((b'data', ('0.0.0.0', 12345))) - transport.sendto(b'', ()) - self.assertFalse(self.sock.sendto.called) - self.assertEqual( - [(b'data', ('0.0.0.0', 12345))], list(transport._buffer)) + transport.sendto(b'', ('0.0.0.0', 1234)) + self.assertTrue(self.proactor.sendto.called) + self.proactor.sendto.assert_called_with( + self.sock, b'', addr=('0.0.0.0', 1234)) def test_sendto_buffer(self): transport = self.datagram_transport() @@ -628,6 +627,19 @@ def test_sendto_buffer_memoryview(self): list(transport._buffer)) self.assertIsInstance(transport._buffer[1][0], bytes) + def test_sendto_buffer_nodata(self): + data2 = b'' + transport = self.datagram_transport() + transport._buffer.append((b'data1', ('0.0.0.0', 12345))) + transport._write_fut = object() + transport.sendto(data2, ('0.0.0.0', 12345)) + self.assertFalse(self.proactor.sendto.called) + self.assertEqual( + [(b'data1', ('0.0.0.0', 12345)), + (b'', ('0.0.0.0', 12345))], + list(transport._buffer)) + self.assertIsInstance(transport._buffer[1][0], bytes) + @mock.patch('asyncio.proactor_events.logger') def test_sendto_exception(self, m_log): data = b'data' diff --git a/Lib/test/test_asyncio/test_selector_events.py b/Lib/test/test_asyncio/test_selector_events.py index c22b780b5edcb83..aaeda33dd0c677f 100644 --- a/Lib/test/test_asyncio/test_selector_events.py +++ b/Lib/test/test_asyncio/test_selector_events.py @@ -1280,11 +1280,10 @@ def test_sendto_memoryview(self): def test_sendto_no_data(self): transport = self.datagram_transport() - transport._buffer.append((b'data', ('0.0.0.0', 12345))) - transport.sendto(b'', ()) - self.assertFalse(self.sock.sendto.called) + transport.sendto(b'', ('0.0.0.0', 1234)) + self.assertTrue(self.sock.sendto.called) self.assertEqual( - [(b'data', ('0.0.0.0', 12345))], list(transport._buffer)) + self.sock.sendto.call_args[0], (b'', ('0.0.0.0', 1234))) def test_sendto_buffer(self): transport = self.datagram_transport() @@ -1320,6 +1319,18 @@ def test_sendto_buffer_memoryview(self): list(transport._buffer)) self.assertIsInstance(transport._buffer[1][0], bytes) + def test_sendto_buffer_nodata(self): + data2 = b'' + transport = self.datagram_transport() + transport._buffer.append((b'data1', ('0.0.0.0', 12345))) + transport.sendto(data2, ('0.0.0.0', 12345)) + self.assertFalse(self.sock.sendto.called) + self.assertEqual( + [(b'data1', ('0.0.0.0', 12345)), + (b'', ('0.0.0.0', 12345))], + list(transport._buffer)) + self.assertIsInstance(transport._buffer[1][0], bytes) + def test_sendto_tryagain(self): data = b'data' diff --git a/Misc/NEWS.d/next/Library/2024-02-09-12-22-47.gh-issue-113812.wOraaG.rst b/Misc/NEWS.d/next/Library/2024-02-09-12-22-47.gh-issue-113812.wOraaG.rst new file mode 100644 index 000000000000000..7ef7bc891cd885d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-09-12-22-47.gh-issue-113812.wOraaG.rst @@ -0,0 +1,3 @@ +:meth:`DatagramTransport.sendto` will now send zero-length datagrams if +called with an empty bytes object. The transport flow control also now +accounts for the datagram header when calculating the buffer size. From d2d78088530433f475d9304104bbc0dac2536edd Mon Sep 17 00:00:00 2001 From: Stevoisiak <S.Vascellaro@gmail.com> Date: Sat, 17 Feb 2024 03:33:28 -0500 Subject: [PATCH 324/507] gh-101699: Explain using Match.expand with \g<0> (GH-101701) Update documentation for re library to explain that a backreference `\g<0>` is expanded to the entire string when using Match.expand(). Note that numeric backreferences to group 0 (`\0`) are not supported. Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> --- Doc/library/re.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/library/re.rst b/Doc/library/re.rst index a5bd5c73f2fac71..0336121c2bc631d 100644 --- a/Doc/library/re.rst +++ b/Doc/library/re.rst @@ -1344,7 +1344,8 @@ when there is no match, you can test whether there was a match with a simple Escapes such as ``\n`` are converted to the appropriate characters, and numeric backreferences (``\1``, ``\2``) and named backreferences (``\g<1>``, ``\g<name>``) are replaced by the contents of the - corresponding group. + corresponding group. The backreference ``\g<0>`` will be + replaced by the entire match. .. versionchanged:: 3.5 Unmatched groups are replaced with an empty string. From 9fd420f53d1b1087d2ae648b0efc44107d27d867 Mon Sep 17 00:00:00 2001 From: Peter Jiping Xie <peter.jp.xie@gmail.com> Date: Sat, 17 Feb 2024 20:12:12 +1100 Subject: [PATCH 325/507] gh-101384: Add socket timeout to ThreadedVSOCKSocketStreamTest and skip it on WSL (GH-101419) --- Lib/test/test_socket.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 179642349920627..b936e9ae91daca0 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -46,6 +46,7 @@ VSOCKPORT = 1234 AIX = platform.system() == "AIX" +WSL = "microsoft-standard-WSL" in platform.release() try: import _socket @@ -510,6 +511,7 @@ def clientTearDown(self): ThreadableTest.clientTearDown(self) @unittest.skipIf(fcntl is None, "need fcntl") +@unittest.skipIf(WSL, 'VSOCK does not work on Microsoft WSL') @unittest.skipUnless(HAVE_SOCKET_VSOCK, 'VSOCK sockets required for this test.') @unittest.skipUnless(get_cid() != 2, @@ -526,6 +528,7 @@ def setUp(self): self.serv.bind((socket.VMADDR_CID_ANY, VSOCKPORT)) self.serv.listen() self.serverExplicitReady() + self.serv.settimeout(support.LOOPBACK_TIMEOUT) self.conn, self.connaddr = self.serv.accept() self.addCleanup(self.conn.close) From 30fce5f228b7e2e8fd92f07113ac5632e6baf3b2 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 17 Feb 2024 11:39:07 +0200 Subject: [PATCH 326/507] gh-101100: Fix Sphinx warnings in `whatsnew/3.1.rst` (#115575) --- Doc/tools/.nitignore | 1 - Doc/whatsnew/3.1.rst | 22 +++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 33129e898e51d68..612857867eb1a9c 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -86,7 +86,6 @@ Doc/whatsnew/2.5.rst Doc/whatsnew/2.6.rst Doc/whatsnew/2.7.rst Doc/whatsnew/3.0.rst -Doc/whatsnew/3.1.rst Doc/whatsnew/3.2.rst Doc/whatsnew/3.3.rst Doc/whatsnew/3.4.rst diff --git a/Doc/whatsnew/3.1.rst b/Doc/whatsnew/3.1.rst index b7dd8f2c7bf531f..69b273e58385d27 100644 --- a/Doc/whatsnew/3.1.rst +++ b/Doc/whatsnew/3.1.rst @@ -81,7 +81,7 @@ Support was also added for third-party tools like `PyYAML <https://pyyaml.org/>` written by Raymond Hettinger. Since an ordered dictionary remembers its insertion order, it can be used -in conjuction with sorting to make a sorted dictionary:: +in conjunction with sorting to make a sorted dictionary:: >>> # regular unsorted dictionary >>> d = {'banana': 3, 'apple':4, 'pear': 1, 'orange': 2} @@ -174,7 +174,7 @@ Some smaller changes made to the core Python language are: (Contributed by Eric Smith; :issue:`5237`.) -* The :func:`string.maketrans` function is deprecated and is replaced by new +* The :func:`!string.maketrans` function is deprecated and is replaced by new static methods, :meth:`bytes.maketrans` and :meth:`bytearray.maketrans`. This change solves the confusion around which types were supported by the :mod:`string` module. Now, :class:`str`, :class:`bytes`, and @@ -381,16 +381,20 @@ New, Improved, and Deprecated Modules x / 0 In addition, several new assertion methods were added including - :func:`assertSetEqual`, :func:`assertDictEqual`, - :func:`assertDictContainsSubset`, :func:`assertListEqual`, - :func:`assertTupleEqual`, :func:`assertSequenceEqual`, - :func:`assertRaisesRegexp`, :func:`assertIsNone`, - and :func:`assertIsNotNone`. + :meth:`~unittest.TestCase.assertSetEqual`, + :meth:`~unittest.TestCase.assertDictEqual`, + :meth:`!assertDictContainsSubset`, + :meth:`~unittest.TestCase.assertListEqual`, + :meth:`~unittest.TestCase.assertTupleEqual`, + :meth:`~unittest.TestCase.assertSequenceEqual`, + :meth:`assertRaisesRegexp() <unittest.TestCase.assertRaisesRegex>`, + :meth:`~unittest.TestCase.assertIsNone`, + and :meth:`~unittest.TestCase.assertIsNotNone`. (Contributed by Benjamin Peterson and Antoine Pitrou.) -* The :mod:`io` module has three new constants for the :meth:`seek` - method :data:`SEEK_SET`, :data:`SEEK_CUR`, and :data:`SEEK_END`. +* The :mod:`io` module has three new constants for the :meth:`~io.IOBase.seek` + method: :data:`~os.SEEK_SET`, :data:`~os.SEEK_CUR`, and :data:`~os.SEEK_END`. * The :data:`sys.version_info` tuple is now a named tuple:: From 4dff48d1f454096efa2e1e7b4596bc56c6f68c20 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 17 Feb 2024 12:03:20 +0200 Subject: [PATCH 327/507] gh-101100: Fix Sphinx warnings in `whatsnew/3.2.rst` (#115580) Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> --- Doc/conf.py | 1 + Doc/tools/.nitignore | 1 - Doc/whatsnew/3.2.rst | 85 ++++++++++++++++++++++---------------------- 3 files changed, 44 insertions(+), 43 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index 0e84d866a22f5b1..7c4817320a7de2c 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -275,6 +275,7 @@ ('py:attr', '__annotations__'), ('py:meth', '__missing__'), ('py:attr', '__wrapped__'), + ('py:attr', 'decimal.Context.clamp'), ('py:meth', 'index'), # list.index, tuple.index, etc. ] diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 612857867eb1a9c..eb45413d7cef787 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -86,7 +86,6 @@ Doc/whatsnew/2.5.rst Doc/whatsnew/2.6.rst Doc/whatsnew/2.7.rst Doc/whatsnew/3.0.rst -Doc/whatsnew/3.2.rst Doc/whatsnew/3.3.rst Doc/whatsnew/3.4.rst Doc/whatsnew/3.5.rst diff --git a/Doc/whatsnew/3.2.rst b/Doc/whatsnew/3.2.rst index 4f70d902243d4d5..52474517f5facc9 100644 --- a/Doc/whatsnew/3.2.rst +++ b/Doc/whatsnew/3.2.rst @@ -344,8 +344,8 @@ aspects that are visible to the programmer: * The :mod:`importlib.abc` module has been updated with new :term:`abstract base classes <abstract base class>` for loading bytecode files. The obsolete - ABCs, :class:`~importlib.abc.PyLoader` and - :class:`~importlib.abc.PyPycLoader`, have been deprecated (instructions on how + ABCs, :class:`!PyLoader` and + :class:`!PyPycLoader`, have been deprecated (instructions on how to stay Python 3.1 compatible are included with the documentation). .. seealso:: @@ -401,7 +401,7 @@ The *native strings* are always of type :class:`str` but are restricted to code points between *U+0000* through *U+00FF* which are translatable to bytes using *Latin-1* encoding. These strings are used for the keys and values in the environment dictionary and for response headers and statuses in the -:func:`start_response` function. They must follow :rfc:`2616` with respect to +:func:`!start_response` function. They must follow :rfc:`2616` with respect to encoding. That is, they must either be *ISO-8859-1* characters or use :rfc:`2047` MIME encoding. @@ -415,8 +415,8 @@ points: encoded in utf-8 was using ``h.encode('utf-8')`` now needs to convert from bytes to native strings using ``h.encode('utf-8').decode('latin-1')``. -* Values yielded by an application or sent using the :meth:`write` method - must be byte strings. The :func:`start_response` function and environ +* Values yielded by an application or sent using the :meth:`!write` method + must be byte strings. The :func:`!start_response` function and environ must use native strings. The two cannot be mixed. For server implementers writing CGI-to-WSGI pathways or other CGI-style @@ -499,7 +499,7 @@ Some smaller changes made to the core Python language are: * The :func:`hasattr` function works by calling :func:`getattr` and detecting whether an exception is raised. This technique allows it to detect methods - created dynamically by :meth:`__getattr__` or :meth:`__getattribute__` which + created dynamically by :meth:`~object.__getattr__` or :meth:`~object.__getattribute__` which would otherwise be absent from the class dictionary. Formerly, *hasattr* would catch any exception, possibly masking genuine errors. Now, *hasattr* has been tightened to only catch :exc:`AttributeError` and let other @@ -620,7 +620,7 @@ Some smaller changes made to the core Python language are: * :class:`range` objects now support *index* and *count* methods. This is part of an effort to make more objects fully implement the - :class:`collections.Sequence` :term:`abstract base class`. As a result, the + :class:`collections.Sequence <collections.abc.Sequence>` :term:`abstract base class`. As a result, the language will have a more uniform API. In addition, :class:`range` objects now support slicing and negative indices, even with values larger than :data:`sys.maxsize`. This makes *range* more interoperable with lists:: @@ -720,7 +720,7 @@ format. elementtree ----------- -The :mod:`xml.etree.ElementTree` package and its :mod:`xml.etree.cElementTree` +The :mod:`xml.etree.ElementTree` package and its :mod:`!xml.etree.cElementTree` counterpart have been updated to version 1.3. Several new and useful functions and methods have been added: @@ -1008,13 +1008,13 @@ datetime and time after 1900. The new supported year range is from 1000 to 9999 inclusive. * Whenever a two-digit year is used in a time tuple, the interpretation has been - governed by :data:`time.accept2dyear`. The default is ``True`` which means that + governed by :data:`!time.accept2dyear`. The default is ``True`` which means that for a two-digit year, the century is guessed according to the POSIX rules governing the ``%y`` strptime format. Starting with Py3.2, use of the century guessing heuristic will emit a :exc:`DeprecationWarning`. Instead, it is recommended that - :data:`time.accept2dyear` be set to ``False`` so that large date ranges + :data:`!time.accept2dyear` be set to ``False`` so that large date ranges can be used without guesswork:: >>> import time, warnings @@ -1032,7 +1032,7 @@ datetime and time 'Fri Jan 1 12:34:56 11' Several functions now have significantly expanded date ranges. When - :data:`time.accept2dyear` is false, the :func:`time.asctime` function will + :data:`!time.accept2dyear` is false, the :func:`time.asctime` function will accept any year that fits in a C int, while the :func:`time.mktime` and :func:`time.strftime` functions will accept the full range supported by the corresponding operating system functions. @@ -1148,15 +1148,15 @@ for slice notation are well-suited to in-place editing:: reprlib ------- -When writing a :meth:`__repr__` method for a custom container, it is easy to +When writing a :meth:`~object.__repr__` method for a custom container, it is easy to forget to handle the case where a member refers back to the container itself. Python's builtin objects such as :class:`list` and :class:`set` handle self-reference by displaying "..." in the recursive part of the representation string. -To help write such :meth:`__repr__` methods, the :mod:`reprlib` module has a new +To help write such :meth:`~object.__repr__` methods, the :mod:`reprlib` module has a new decorator, :func:`~reprlib.recursive_repr`, for detecting recursive calls to -:meth:`__repr__` and substituting a placeholder string instead:: +:meth:`!__repr__` and substituting a placeholder string instead:: >>> class MyList(list): ... @recursive_repr() @@ -1308,7 +1308,7 @@ used for the imaginary part of a number: >>> sys.hash_info # doctest: +SKIP sys.hash_info(width=64, modulus=2305843009213693951, inf=314159, nan=0, imag=1000003) -An early decision to limit the inter-operability of various numeric types has +An early decision to limit the interoperability of various numeric types has been relaxed. It is still unsupported (and ill-advised) to have implicit mixing in arithmetic expressions such as ``Decimal('1.1') + float('1.1')`` because the latter loses information in the process of constructing the binary @@ -1336,7 +1336,7 @@ Decimal('1.100000000000000088817841970012523233890533447265625') Fraction(2476979795053773, 2251799813685248) Another useful change for the :mod:`decimal` module is that the -:attr:`Context.clamp` attribute is now public. This is useful in creating +:attr:`Context.clamp <decimal.Context.clamp>` attribute is now public. This is useful in creating contexts that correspond to the decimal interchange formats specified in IEEE 754 (see :issue:`8540`). @@ -1428,7 +1428,7 @@ before compressing and decompressing: Aides and Brian Curtin in :issue:`9962`, :issue:`1675951`, :issue:`7471` and :issue:`2846`.) -Also, the :class:`zipfile.ZipExtFile` class was reworked internally to represent +Also, the :class:`zipfile.ZipExtFile <zipfile.ZipFile.open>` class was reworked internally to represent files stored inside an archive. The new implementation is significantly faster and can be wrapped in an :class:`io.BufferedReader` object for more speedups. It also solves an issue where interleaved calls to *read* and *readline* gave the @@ -1596,7 +1596,7 @@ sqlite3 The :mod:`sqlite3` module was updated to pysqlite version 2.6.0. It has two new capabilities. -* The :attr:`sqlite3.Connection.in_transit` attribute is true if there is an +* The :attr:`!sqlite3.Connection.in_transit` attribute is true if there is an active transaction for uncommitted changes. * The :meth:`sqlite3.Connection.enable_load_extension` and @@ -1643,11 +1643,11 @@ for secure (encrypted, authenticated) internet connections: other options. It includes a :meth:`~ssl.SSLContext.wrap_socket` for creating an SSL socket from an SSL context. -* A new function, :func:`ssl.match_hostname`, supports server identity +* A new function, :func:`!ssl.match_hostname`, supports server identity verification for higher-level protocols by implementing the rules of HTTPS (from :rfc:`2818`) which are also suitable for other protocols. -* The :func:`ssl.wrap_socket` constructor function now takes a *ciphers* +* The :func:`ssl.wrap_socket() <ssl.SSLContext.wrap_socket>` constructor function now takes a *ciphers* argument. The *ciphers* string lists the allowed encryption algorithms using the format described in the `OpenSSL documentation <https://www.openssl.org/docs/man1.0.2/man1/ciphers.html#CIPHER-LIST-FORMAT>`__. @@ -1759,7 +1759,7 @@ names. (Contributed by Michael Foord.) * Experimentation at the interactive prompt is now easier because the - :class:`unittest.case.TestCase` class can now be instantiated without + :class:`unittest.TestCase` class can now be instantiated without arguments: >>> from unittest import TestCase @@ -1797,7 +1797,7 @@ names. * In addition, the method names in the module have undergone a number of clean-ups. For example, :meth:`~unittest.TestCase.assertRegex` is the new name for - :meth:`~unittest.TestCase.assertRegexpMatches` which was misnamed because the + :meth:`!assertRegexpMatches` which was misnamed because the test uses :func:`re.search`, not :func:`re.match`. Other methods using regular expressions are now named using short form "Regex" in preference to "Regexp" -- this matches the names used in other unittest implementations, @@ -1812,11 +1812,11 @@ names. =============================== ============================== Old Name Preferred Name =============================== ============================== - :meth:`assert_` :meth:`.assertTrue` - :meth:`assertEquals` :meth:`.assertEqual` - :meth:`assertNotEquals` :meth:`.assertNotEqual` - :meth:`assertAlmostEquals` :meth:`.assertAlmostEqual` - :meth:`assertNotAlmostEquals` :meth:`.assertNotAlmostEqual` + :meth:`!assert_` :meth:`.assertTrue` + :meth:`!assertEquals` :meth:`.assertEqual` + :meth:`!assertNotEquals` :meth:`.assertNotEqual` + :meth:`!assertAlmostEquals` :meth:`.assertAlmostEqual` + :meth:`!assertNotAlmostEquals` :meth:`.assertNotAlmostEqual` =============================== ============================== Likewise, the ``TestCase.fail*`` methods deprecated in Python 3.1 are expected @@ -1824,7 +1824,7 @@ names. (Contributed by Ezio Melotti; :issue:`9424`.) -* The :meth:`~unittest.TestCase.assertDictContainsSubset` method was deprecated +* The :meth:`!assertDictContainsSubset` method was deprecated because it was misimplemented with the arguments in the wrong order. This created hard-to-debug optical illusions where tests like ``TestCase().assertDictContainsSubset({'a':1, 'b':2}, {'a':1})`` would fail. @@ -1997,7 +1997,7 @@ under-the-hood. dbm --- -All database modules now support the :meth:`get` and :meth:`setdefault` methods. +All database modules now support the :meth:`!get` and :meth:`!setdefault` methods. (Suggested by Ray Allen in :issue:`9523`.) @@ -2118,7 +2118,7 @@ The :mod:`pdb` debugger module gained a number of usability improvements: :file:`.pdbrc` script file. * A :file:`.pdbrc` script file can contain ``continue`` and ``next`` commands that continue debugging. -* The :class:`Pdb` class constructor now accepts a *nosigint* argument. +* The :class:`~pdb.Pdb` class constructor now accepts a *nosigint* argument. * New commands: ``l(list)``, ``ll(long list)`` and ``source`` for listing source code. * New commands: ``display`` and ``undisplay`` for showing or hiding @@ -2394,11 +2394,11 @@ A number of small performance enhancements have been added: (Contributed by Antoine Pitrou; :issue:`3001`.) -* The fast-search algorithm in stringlib is now used by the :meth:`split`, - :meth:`rsplit`, :meth:`splitlines` and :meth:`replace` methods on +* The fast-search algorithm in stringlib is now used by the :meth:`~str.split`, + :meth:`~str.rsplit`, :meth:`~str.splitlines` and :meth:`~str.replace` methods on :class:`bytes`, :class:`bytearray` and :class:`str` objects. Likewise, the - algorithm is also used by :meth:`rfind`, :meth:`rindex`, :meth:`rsplit` and - :meth:`rpartition`. + algorithm is also used by :meth:`~str.rfind`, :meth:`~str.rindex`, :meth:`~str.rsplit` and + :meth:`~str.rpartition`. (Patch by Florent Xicluna in :issue:`7622` and :issue:`7462`.) @@ -2410,8 +2410,8 @@ A number of small performance enhancements have been added: There were several other minor optimizations. Set differencing now runs faster when one operand is much larger than the other (patch by Andress Bennetts in -:issue:`8685`). The :meth:`array.repeat` method has a faster implementation -(:issue:`1569291` by Alexander Belopolsky). The :class:`BaseHTTPRequestHandler` +:issue:`8685`). The :meth:`!array.repeat` method has a faster implementation +(:issue:`1569291` by Alexander Belopolsky). The :class:`~http.server.BaseHTTPRequestHandler` has more efficient buffering (:issue:`3709` by Andrew Schaaf). The :func:`operator.attrgetter` function has been sped-up (:issue:`10160` by Christos Georgiou). And :class:`~configparser.ConfigParser` loads multi-line arguments a bit @@ -2562,11 +2562,11 @@ Changes to Python's build process and to the C API include: (Suggested by Raymond Hettinger and implemented by Benjamin Peterson; :issue:`9778`.) -* A new macro :c:macro:`Py_VA_COPY` copies the state of the variable argument +* A new macro :c:macro:`!Py_VA_COPY` copies the state of the variable argument list. It is equivalent to C99 *va_copy* but available on all Python platforms (:issue:`2443`). -* A new C API function :c:func:`PySys_SetArgvEx` allows an embedded interpreter +* A new C API function :c:func:`!PySys_SetArgvEx` allows an embedded interpreter to set :data:`sys.argv` without also modifying :data:`sys.path` (:issue:`5753`). @@ -2650,8 +2650,9 @@ require changes to your code: * :class:`bytearray` objects can no longer be used as filenames; instead, they should be converted to :class:`bytes`. -* The :meth:`array.tostring` and :meth:`array.fromstring` have been renamed to - :meth:`array.tobytes` and :meth:`array.frombytes` for clarity. The old names +* The :meth:`!array.tostring` and :meth:`!array.fromstring` have been renamed to + :meth:`array.tobytes() <array.array.tobytes>` and + :meth:`array.frombytes() <array.array.frombytes>` for clarity. The old names have been deprecated. (See :issue:`8990`.) * ``PyArg_Parse*()`` functions: @@ -2664,7 +2665,7 @@ require changes to your code: instead; the new type has a well-defined interface for passing typing safety information and a less complicated signature for calling a destructor. -* The :func:`sys.setfilesystemencoding` function was removed because +* The :func:`!sys.setfilesystemencoding` function was removed because it had a flawed design. * The :func:`random.seed` function and method now salt string seeds with an @@ -2672,7 +2673,7 @@ require changes to your code: reproduce Python 3.1 sequences, set the *version* argument to *1*, ``random.seed(s, version=1)``. -* The previously deprecated :func:`string.maketrans` function has been removed +* The previously deprecated :func:`!string.maketrans` function has been removed in favor of the static methods :meth:`bytes.maketrans` and :meth:`bytearray.maketrans`. This change solves the confusion around which types were supported by the :mod:`string` module. Now, :class:`str`, From 465db27cb983084e718a1fd9519b2726c96935cb Mon Sep 17 00:00:00 2001 From: Derek Higgins <derekh@redhat.com> Date: Sat, 17 Feb 2024 10:10:12 +0000 Subject: [PATCH 328/507] gh-100985: Consistently wrap IPv6 IP address during CONNECT (GH-100986) Update _get_hostport to always remove square brackets from IPv6 addresses. Then add them if needed in "CONNECT .." and "Host: ". --- Lib/http/client.py | 15 ++++++++++----- Lib/test/test_httplib.py | 16 ++++++++++++++++ Misc/ACKS | 1 + ...023-01-12-14-16-01.gh-issue-100985.GT5Fvd.rst | 2 ++ 4 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-01-12-14-16-01.gh-issue-100985.GT5Fvd.rst diff --git a/Lib/http/client.py b/Lib/http/client.py index 5eebfccafbca59b..a353716a8506e65 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -936,17 +936,23 @@ def _get_hostport(self, host, port): host = host[:i] else: port = self.default_port - if host and host[0] == '[' and host[-1] == ']': - host = host[1:-1] + if host and host[0] == '[' and host[-1] == ']': + host = host[1:-1] return (host, port) def set_debuglevel(self, level): self.debuglevel = level + def _wrap_ipv6(self, ip): + if b':' in ip and ip[0] != b'['[0]: + return b"[" + ip + b"]" + return ip + def _tunnel(self): connect = b"CONNECT %s:%d %s\r\n" % ( - self._tunnel_host.encode("idna"), self._tunnel_port, + self._wrap_ipv6(self._tunnel_host.encode("idna")), + self._tunnel_port, self._http_vsn_str.encode("ascii")) headers = [connect] for header, value in self._tunnel_headers.items(): @@ -1221,9 +1227,8 @@ def putrequest(self, method, url, skip_host=False, # As per RFC 273, IPv6 address should be wrapped with [] # when used as Host header - + host_enc = self._wrap_ipv6(host_enc) if ":" in host: - host_enc = b'[' + host_enc + b']' host_enc = _strip_ipv6_iface(host_enc) if port == self.default_port: diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index 089bf5be40a0e27..6e63a8872d9c6e1 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -2408,6 +2408,22 @@ def test_connect_put_request(self): self.assertIn(b'PUT / HTTP/1.1\r\nHost: %(host)s\r\n' % d, self.conn.sock.data) + def test_connect_put_request_ipv6(self): + self.conn.set_tunnel('[1:2:3::4]', 1234) + self.conn.request('PUT', '/', '') + self.assertEqual(self.conn.sock.host, self.host) + self.assertEqual(self.conn.sock.port, client.HTTP_PORT) + self.assertIn(b'CONNECT [1:2:3::4]:1234', self.conn.sock.data) + self.assertIn(b'Host: [1:2:3::4]:1234', self.conn.sock.data) + + def test_connect_put_request_ipv6_port(self): + self.conn.set_tunnel('[1:2:3::4]:1234') + self.conn.request('PUT', '/', '') + self.assertEqual(self.conn.sock.host, self.host) + self.assertEqual(self.conn.sock.port, client.HTTP_PORT) + self.assertIn(b'CONNECT [1:2:3::4]:1234', self.conn.sock.data) + self.assertIn(b'Host: [1:2:3::4]:1234', self.conn.sock.data) + def test_tunnel_debuglog(self): expected_header = 'X-Dummy: 1' response_text = 'HTTP/1.0 200 OK\r\n{}\r\n\r\n'.format(expected_header) diff --git a/Misc/ACKS b/Misc/ACKS index 8a80e02ecba26a8..f01c7a70a65dc52 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -756,6 +756,7 @@ Raymond Hettinger Lisa Hewus Fresh Kevan Heydon Wouter van Heyst +Derek Higgins Kelsey Hightower Jason Hildebrand Ryan Hileman diff --git a/Misc/NEWS.d/next/Library/2023-01-12-14-16-01.gh-issue-100985.GT5Fvd.rst b/Misc/NEWS.d/next/Library/2023-01-12-14-16-01.gh-issue-100985.GT5Fvd.rst new file mode 100644 index 000000000000000..8d8693a5edb3d4f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-01-12-14-16-01.gh-issue-100985.GT5Fvd.rst @@ -0,0 +1,2 @@ +Update HTTPSConnection to consistently wrap IPv6 Addresses when using a +proxy. From 09fab93c3d857496c0bd162797fab816c311ee48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= <thomas@t-8ch.de> Date: Sat, 17 Feb 2024 11:13:46 +0100 Subject: [PATCH 329/507] gh-100884: email/_header_value_parser: don't encode list separators (GH-100885) ListSeparator should not be encoded. This could happen when a long line pushes its separator to the next line, which would have been encoded. --- Lib/email/_header_value_parser.py | 3 ++- Lib/test/test_email/test__header_value_parser.py | 5 +++++ .../Library/2023-01-09-14-08-02.gh-issue-100884.DcmdLl.rst | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2023-01-09-14-08-02.gh-issue-100884.DcmdLl.rst diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py index 5b653f66c18554c..e4a342d446f6a38 100644 --- a/Lib/email/_header_value_parser.py +++ b/Lib/email/_header_value_parser.py @@ -949,6 +949,7 @@ class _InvalidEwError(errors.HeaderParseError): # up other parse trees. Maybe should have tests for that, too. DOT = ValueTerminal('.', 'dot') ListSeparator = ValueTerminal(',', 'list-separator') +ListSeparator.as_ew_allowed = False RouteComponentMarker = ValueTerminal('@', 'route-component-marker') # @@ -2022,7 +2023,7 @@ def get_address_list(value): address_list.defects.append(errors.InvalidHeaderDefect( "invalid address in address-list")) if value: # Must be a , at this point. - address_list.append(ValueTerminal(',', 'list-separator')) + address_list.append(ListSeparator) value = value[1:] return address_list, value diff --git a/Lib/test/test_email/test__header_value_parser.py b/Lib/test/test_email/test__header_value_parser.py index bdb0e55f21069f7..f7e80749c456f8f 100644 --- a/Lib/test/test_email/test__header_value_parser.py +++ b/Lib/test/test_email/test__header_value_parser.py @@ -2985,6 +2985,11 @@ def test_address_list_with_unicode_names_in_quotes(self): '=?utf-8?q?H=C3=BCbsch?= Kaktus <beautiful@example.com>,\n' ' =?utf-8?q?bei=C3=9Ft_bei=C3=9Ft?= <biter@example.com>\n') + def test_address_list_with_list_separator_after_fold(self): + to = '0123456789' * 8 + '@foo, ä <foo@bar>' + self._test(parser.get_address_list(to)[0], + '0123456789' * 8 + '@foo,\n =?utf-8?q?=C3=A4?= <foo@bar>\n') + # XXX Need tests with comments on various sides of a unicode token, # and with unicode tokens in the comments. Spaces inside the quotes # currently don't do the right thing. diff --git a/Misc/NEWS.d/next/Library/2023-01-09-14-08-02.gh-issue-100884.DcmdLl.rst b/Misc/NEWS.d/next/Library/2023-01-09-14-08-02.gh-issue-100884.DcmdLl.rst new file mode 100644 index 000000000000000..2a3881788108350 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-01-09-14-08-02.gh-issue-100884.DcmdLl.rst @@ -0,0 +1,2 @@ +email: fix misfolding of comma in address-lists over multiple lines in +combination with unicode encoding. From debb1386be024181c8c003c5cbf61608024aee09 Mon Sep 17 00:00:00 2001 From: Rami <72725910+ramikg@users.noreply.github.com> Date: Sat, 17 Feb 2024 12:22:19 +0200 Subject: [PATCH 330/507] gh-87688: Amend SSLContext.hostname_checks_common_name docs (GH-100517) --- Doc/library/ssl.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index f9648fa6744bdc0..fa81c3f208cff7e 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1975,7 +1975,7 @@ to speed up repeated connections from the same clients. .. versionchanged:: 3.10 - The flag had no effect with OpenSSL before version 1.1.1k. Python 3.8.9, + The flag had no effect with OpenSSL before version 1.1.1l. Python 3.8.9, 3.9.3, and 3.10 include workarounds for previous versions. .. attribute:: SSLContext.security_level From d5a30a1777f04523c7b151b894e999f5714d8e96 Mon Sep 17 00:00:00 2001 From: Furkan Onder <furkanonder@protonmail.com> Date: Sat, 17 Feb 2024 13:51:43 +0300 Subject: [PATCH 331/507] gh-56499: Update the pickle library's note section for the __setstate__ function (GH-101062) --- Doc/library/pickle.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/pickle.rst b/Doc/library/pickle.rst index acada092afb679b..cb517681fa81b96 100644 --- a/Doc/library/pickle.rst +++ b/Doc/library/pickle.rst @@ -653,8 +653,8 @@ methods: .. note:: - If :meth:`__getstate__` returns a false value, the :meth:`__setstate__` - method will not be called upon unpickling. + If :meth:`__reduce__` returns a state with value ``None`` at pickling, + the :meth:`__setstate__` method will not be called upon unpickling. Refer to the section :ref:`pickle-state` for more information about how to use From 26800cf25a0970d46934fa9a881c0ef6881d642b Mon Sep 17 00:00:00 2001 From: 6t8k <58048945+6t8k@users.noreply.github.com> Date: Sat, 17 Feb 2024 11:16:06 +0000 Subject: [PATCH 332/507] gh-95782: Fix io.BufferedReader.tell() etc. being able to return offsets < 0 (GH-99709) lseek() always returns 0 for character pseudo-devices like `/dev/urandom` (for other non-regular files, e.g. `/dev/stdin`, it always returns -1, to which CPython reacts by raising appropriate exceptions). They are thus technically seekable despite not having seek semantics. When calling read() on e.g. an instance of `io.BufferedReader` that wraps such a file, `BufferedReader` reads ahead, filling its buffer, creating a discrepancy between the number of bytes read and the internal `tell()` always returning 0, which previously resulted in e.g. `BufferedReader.tell()` or `BufferedReader.seek()` being able to return positions < 0 even though these are supposed to be always >= 0. Invariably keep the return value non-negative by returning max(former_return_value, 0) instead, and add some corresponding tests. --- Lib/_pyio.py | 3 +- Lib/test/test_io.py | 47 ++++++++++++++++++- ...2-11-22-23-17-43.gh-issue-95782.an_and.rst | 4 ++ Modules/_io/bufferedio.c | 11 ++++- 4 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-11-22-23-17-43.gh-issue-95782.an_and.rst diff --git a/Lib/_pyio.py b/Lib/_pyio.py index 8a0d0dc4b1a0b85..a3fede699218a15 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -1197,7 +1197,8 @@ def _readinto(self, buf, read1): return written def tell(self): - return _BufferedIOMixin.tell(self) - len(self._read_buf) + self._read_pos + # GH-95782: Keep return value non-negative + return max(_BufferedIOMixin.tell(self) - len(self._read_buf) + self._read_pos, 0) def seek(self, pos, whence=0): if whence not in valid_seek_flags: diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index cc387afa3919099..5491c0575dbd3f9 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -257,6 +257,27 @@ class PyMockUnseekableIO(MockUnseekableIO, pyio.BytesIO): UnsupportedOperation = pyio.UnsupportedOperation +class MockCharPseudoDevFileIO(MockFileIO): + # GH-95782 + # ftruncate() does not work on these special files (and CPython then raises + # appropriate exceptions), so truncate() does not have to be accounted for + # here. + def __init__(self, data): + super().__init__(data) + + def seek(self, *args): + return 0 + + def tell(self, *args): + return 0 + +class CMockCharPseudoDevFileIO(MockCharPseudoDevFileIO, io.BytesIO): + pass + +class PyMockCharPseudoDevFileIO(MockCharPseudoDevFileIO, pyio.BytesIO): + pass + + class MockNonBlockWriterIO: def __init__(self): @@ -1648,6 +1669,30 @@ def test_truncate_on_read_only(self): self.assertRaises(self.UnsupportedOperation, bufio.truncate) self.assertRaises(self.UnsupportedOperation, bufio.truncate, 0) + def test_tell_character_device_file(self): + # GH-95782 + # For the (former) bug in BufferedIO to manifest, the wrapped IO obj + # must be able to produce at least 2 bytes. + raw = self.MockCharPseudoDevFileIO(b"12") + buf = self.tp(raw) + self.assertEqual(buf.tell(), 0) + self.assertEqual(buf.read(1), b"1") + self.assertEqual(buf.tell(), 0) + + def test_seek_character_device_file(self): + raw = self.MockCharPseudoDevFileIO(b"12") + buf = self.tp(raw) + self.assertEqual(buf.seek(0, io.SEEK_CUR), 0) + self.assertEqual(buf.seek(1, io.SEEK_SET), 0) + self.assertEqual(buf.seek(0, io.SEEK_CUR), 0) + self.assertEqual(buf.read(1), b"1") + + # In the C implementation, tell() sets the BufferedIO's abs_pos to 0, + # which means that the next seek() could return a negative offset if it + # does not sanity-check: + self.assertEqual(buf.tell(), 0) + self.assertEqual(buf.seek(0, io.SEEK_CUR), 0) + class CBufferedReaderTest(BufferedReaderTest, SizeofTest): tp = io.BufferedReader @@ -4880,7 +4925,7 @@ def load_tests(loader, tests, pattern): # classes in the __dict__ of each test. mocks = (MockRawIO, MisbehavedRawIO, MockFileIO, CloseFailureIO, MockNonBlockWriterIO, MockUnseekableIO, MockRawIOWithoutRead, - SlowFlushRawIO) + SlowFlushRawIO, MockCharPseudoDevFileIO) all_members = io.__all__ c_io_ns = {name : getattr(io, name) for name in all_members} py_io_ns = {name : getattr(pyio, name) for name in all_members} diff --git a/Misc/NEWS.d/next/Library/2022-11-22-23-17-43.gh-issue-95782.an_and.rst b/Misc/NEWS.d/next/Library/2022-11-22-23-17-43.gh-issue-95782.an_and.rst new file mode 100644 index 000000000000000..123c3944aa3a3a0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-11-22-23-17-43.gh-issue-95782.an_and.rst @@ -0,0 +1,4 @@ +Fix :func:`io.BufferedReader.tell`, :func:`io.BufferedReader.seek`, +:func:`_pyio.BufferedReader.tell`, :func:`io.BufferedRandom.tell`, +:func:`io.BufferedRandom.seek` and :func:`_pyio.BufferedRandom.tell` +being able to return negative offsets. diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index 8ebe9ec7095586f..b3450eeaf99401f 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -1325,7 +1325,11 @@ _io__Buffered_tell_impl(buffered *self) if (pos == -1) return NULL; pos -= RAW_OFFSET(self); - /* TODO: sanity check (pos >= 0) */ + + // GH-95782 + if (pos < 0) + pos = 0; + return PyLong_FromOff_t(pos); } @@ -1395,6 +1399,11 @@ _io__Buffered_seek_impl(buffered *self, PyObject *targetobj, int whence) offset = target; if (offset >= -self->pos && offset <= avail) { self->pos += offset; + + // GH-95782 + if (current - avail + offset < 0) + return PyLong_FromOff_t(0); + return PyLong_FromOff_t(current - avail + offset); } } From e88ebc1c4028cf2f0db43659e513440257eaec01 Mon Sep 17 00:00:00 2001 From: Matthew Hughes <34972397+matthewhughes934@users.noreply.github.com> Date: Sat, 17 Feb 2024 11:57:51 +0000 Subject: [PATCH 333/507] gh-97590: Update docs and tests for ftplib.FTP.voidcmd() (GH-96825) Since 2f3941d743481ac48628b8b2c075f2b82762050b this function returns the response string, rather than nothing. --- Doc/library/ftplib.rst | 4 ++-- Lib/test/test_ftplib.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/library/ftplib.rst b/Doc/library/ftplib.rst index 9abf7974d1936db..8d1aae018ada121 100644 --- a/Doc/library/ftplib.rst +++ b/Doc/library/ftplib.rst @@ -232,8 +232,8 @@ FTP objects .. method:: FTP.voidcmd(cmd) Send a simple command string to the server and handle the response. Return - nothing if a response code corresponding to success (codes in the range - 200--299) is received. Raise :exc:`error_reply` otherwise. + the response string if the response code corresponds to success (codes in + the range 200--299). Raise :exc:`error_reply` otherwise. .. audit-event:: ftplib.sendcmd self,cmd ftplib.FTP.voidcmd diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py index 81115e9db888cf2..bed0e6d973b8da3 100644 --- a/Lib/test/test_ftplib.py +++ b/Lib/test/test_ftplib.py @@ -543,8 +543,8 @@ def test_set_pasv(self): self.assertFalse(self.client.passiveserver) def test_voidcmd(self): - self.client.voidcmd('echo 200') - self.client.voidcmd('echo 299') + self.assertEqual(self.client.voidcmd('echo 200'), '200') + self.assertEqual(self.client.voidcmd('echo 299'), '299') self.assertRaises(ftplib.error_reply, self.client.voidcmd, 'echo 199') self.assertRaises(ftplib.error_reply, self.client.voidcmd, 'echo 300') From 664965a1c141e8af5eb465d29099781a6a2fc3f3 Mon Sep 17 00:00:00 2001 From: wookie184 <wookie1840@gmail.com> Date: Sat, 17 Feb 2024 12:06:31 +0000 Subject: [PATCH 334/507] gh-96497: Mangle name before symtable lookup in 'symtable_extend_namedexpr_scope' (GH-96561) --- Lib/test/test_named_expressions.py | 22 +++++++++++++++++++ ...2-09-04-16-51-56.gh-issue-96497.HTBuIL.rst | 2 ++ Python/symtable.c | 14 ++++++++---- 3 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-09-04-16-51-56.gh-issue-96497.HTBuIL.rst diff --git a/Lib/test/test_named_expressions.py b/Lib/test/test_named_expressions.py index 7b2fa844827ae9b..f2017bdffcf9680 100644 --- a/Lib/test/test_named_expressions.py +++ b/Lib/test/test_named_expressions.py @@ -298,6 +298,16 @@ def test_named_expression_invalid_set_comprehension_iterable_expression(self): with self.assertRaisesRegex(SyntaxError, msg): exec(f"lambda: {code}", {}) # Function scope + def test_named_expression_invalid_mangled_class_variables(self): + code = """class Foo: + def bar(self): + [[(__x:=2) for _ in range(2)] for __x in range(2)] + """ + + with self.assertRaisesRegex(SyntaxError, + "assignment expression cannot rebind comprehension iteration variable '__x'"): + exec(code, {}, {}) + class NamedExpressionAssignmentTest(unittest.TestCase): @@ -674,6 +684,18 @@ def test_named_expression_scope_in_genexp(self): for idx, elem in enumerate(genexp): self.assertEqual(elem, b[idx] + a) + def test_named_expression_scope_mangled_names(self): + class Foo: + def f(self_): + global __x1 + __x1 = 0 + [_Foo__x1 := 1 for a in [2]] + self.assertEqual(__x1, 1) + [__x1 := 2 for a in [3]] + self.assertEqual(__x1, 2) + + Foo().f() + self.assertEqual(_Foo__x1, 2) if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-09-04-16-51-56.gh-issue-96497.HTBuIL.rst b/Misc/NEWS.d/next/Core and Builtins/2022-09-04-16-51-56.gh-issue-96497.HTBuIL.rst new file mode 100644 index 000000000000000..6881dde2e6cf442 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-09-04-16-51-56.gh-issue-96497.HTBuIL.rst @@ -0,0 +1,2 @@ +Fix incorrect resolution of mangled class variables used in assignment +expressions in comprehensions. diff --git a/Python/symtable.c b/Python/symtable.c index d69516351efba22..b69452bf77c5179 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -1359,16 +1359,22 @@ symtable_enter_block(struct symtable *st, identifier name, _Py_block_ty block, } static long -symtable_lookup(struct symtable *st, PyObject *name) +symtable_lookup_entry(struct symtable *st, PySTEntryObject *ste, PyObject *name) { PyObject *mangled = _Py_Mangle(st->st_private, name); if (!mangled) return 0; - long ret = _PyST_GetSymbol(st->st_cur, mangled); + long ret = _PyST_GetSymbol(ste, mangled); Py_DECREF(mangled); return ret; } +static long +symtable_lookup(struct symtable *st, PyObject *name) +{ + return symtable_lookup_entry(st, st->st_cur, name); +} + static int symtable_add_def_helper(struct symtable *st, PyObject *name, int flag, struct _symtable_entry *ste, int lineno, int col_offset, int end_lineno, int end_col_offset) @@ -2009,7 +2015,7 @@ symtable_extend_namedexpr_scope(struct symtable *st, expr_ty e) * binding conflict with iteration variables, otherwise skip it */ if (ste->ste_comprehension) { - long target_in_scope = _PyST_GetSymbol(ste, target_name); + long target_in_scope = symtable_lookup_entry(st, ste, target_name); if ((target_in_scope & DEF_COMP_ITER) && (target_in_scope & DEF_LOCAL)) { PyErr_Format(PyExc_SyntaxError, NAMED_EXPR_COMP_CONFLICT, target_name); @@ -2025,7 +2031,7 @@ symtable_extend_namedexpr_scope(struct symtable *st, expr_ty e) /* If we find a FunctionBlock entry, add as GLOBAL/LOCAL or NONLOCAL/LOCAL */ if (ste->ste_type == FunctionBlock) { - long target_in_scope = _PyST_GetSymbol(ste, target_name); + long target_in_scope = symtable_lookup_entry(st, ste, target_name); if (target_in_scope & DEF_GLOBAL) { if (!symtable_add_def(st, target_name, DEF_GLOBAL, LOCATION(e))) VISIT_QUIT(st, 0); From b9a9e3dd62326b726ad2e8e8efd87ca6327b4019 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Sat, 17 Feb 2024 15:47:51 +0300 Subject: [PATCH 335/507] gh-107155: Fix help() for lambda function with return annotation (GH-107401) --- Lib/pydoc.py | 6 +++-- Lib/test/test_pydoc/test_pydoc.py | 24 +++++++++++++++++++ ...-08-02-01-17-32.gh-issue-107155.Mj1K9L.rst | 3 +++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-08-02-01-17-32.gh-issue-107155.Mj1K9L.rst diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 6d145abda9d4abe..9bb64feca8f93e8 100755 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -1144,7 +1144,8 @@ def docroutine(self, object, name=None, mod=None, # XXX lambda's won't usually have func_annotations['return'] # since the syntax doesn't support but it is possible. # So removing parentheses isn't truly safe. - argspec = argspec[1:-1] # remove parentheses + if not object.__annotations__: + argspec = argspec[1:-1] # remove parentheses if not argspec: argspec = '(...)' @@ -1586,7 +1587,8 @@ def docroutine(self, object, name=None, mod=None, cl=None, homecls=None): # XXX lambda's won't usually have func_annotations['return'] # since the syntax doesn't support but it is possible. # So removing parentheses isn't truly safe. - argspec = argspec[1:-1] # remove parentheses + if not object.__annotations__: + argspec = argspec[1:-1] if not argspec: argspec = '(...)' decl = asyncqualifier + title + argspec + note diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index 0dd24e6d3473647..d7a333a1103eaca 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -693,6 +693,30 @@ def test_help_output_redirect(self): finally: pydoc.getpager = getpager_old + def test_lambda_with_return_annotation(self): + func = lambda a, b, c: 1 + func.__annotations__ = {"return": int} + with captured_output('stdout') as help_io: + pydoc.help(func) + helptext = help_io.getvalue() + self.assertIn("lambda (a, b, c) -> int", helptext) + + def test_lambda_without_return_annotation(self): + func = lambda a, b, c: 1 + func.__annotations__ = {"a": int, "b": int, "c": int} + with captured_output('stdout') as help_io: + pydoc.help(func) + helptext = help_io.getvalue() + self.assertIn("lambda (a: int, b: int, c: int)", helptext) + + def test_lambda_with_return_and_params_annotation(self): + func = lambda a, b, c: 1 + func.__annotations__ = {"a": int, "b": int, "c": int, "return": int} + with captured_output('stdout') as help_io: + pydoc.help(func) + helptext = help_io.getvalue() + self.assertIn("lambda (a: int, b: int, c: int) -> int", helptext) + def test_namedtuple_fields(self): Person = namedtuple('Person', ['nickname', 'firstname']) with captured_stdout() as help_io: diff --git a/Misc/NEWS.d/next/Library/2023-08-02-01-17-32.gh-issue-107155.Mj1K9L.rst b/Misc/NEWS.d/next/Library/2023-08-02-01-17-32.gh-issue-107155.Mj1K9L.rst new file mode 100644 index 000000000000000..8362dc0fcfaa748 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-08-02-01-17-32.gh-issue-107155.Mj1K9L.rst @@ -0,0 +1,3 @@ +Fix incorrect output of ``help(x)`` where ``x`` is a :keyword:`lambda` +function, which has an ``__annotations__`` dictionary attribute with a +``"return"`` key. From 04005f5021a17b191dae319faaadf1c942af3fe9 Mon Sep 17 00:00:00 2001 From: Thomas Grainger <tagrain@gmail.com> Date: Sat, 17 Feb 2024 13:13:34 +0000 Subject: [PATCH 336/507] Document use of ANY in test assertions (GH-94060) --- Doc/library/unittest.mock.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index eca20b94ec8e740..b0a5d96c38d375b 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -2426,6 +2426,14 @@ passed in. >>> m.mock_calls == [call(1), call(1, 2), ANY] True +:data:`ANY` is not limited to comparisons with call objects and so +can also be used in test assertions:: + + class TestStringMethods(unittest.TestCase): + + def test_split(self): + s = 'hello world' + self.assertEqual(s.split(), ['hello', ANY]) FILTER_DIR From 265548a4eaaebc3fb379f85f2a919848927f09e5 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Sat, 17 Feb 2024 16:17:55 +0300 Subject: [PATCH 337/507] gh-115567: Catch test_ctypes.test_callbacks.test_i38748_stackCorruption stdout output (GH-115568) --- Lib/test/test_ctypes/test_callbacks.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_ctypes/test_callbacks.py b/Lib/test/test_ctypes/test_callbacks.py index 19f4158c0ac8461..64f92ffdca6a3f8 100644 --- a/Lib/test/test_ctypes/test_callbacks.py +++ b/Lib/test/test_ctypes/test_callbacks.py @@ -148,9 +148,10 @@ def callback(a, b): print(f"a={a}, b={b}, c={c}") return c dll = cdll[_ctypes_test.__file__] - # With no fix for i38748, the next line will raise OSError and cause the test to fail. - self.assertEqual(dll._test_i38748_runCallback(callback, 5, 10), 15) - + with support.captured_stdout() as out: + # With no fix for i38748, the next line will raise OSError and cause the test to fail. + self.assertEqual(dll._test_i38748_runCallback(callback, 5, 10), 15) + self.assertEqual(out.getvalue(), "a=5, b=10, c=15\n") if hasattr(ctypes, 'WINFUNCTYPE'): class StdcallCallbacks(Callbacks): From 437924465de5cb81988d1e580797b07090c26a28 Mon Sep 17 00:00:00 2001 From: Dmitry Marakasov <amdmi3@amdmi3.ru> Date: Sat, 17 Feb 2024 17:54:47 +0300 Subject: [PATCH 338/507] Fix ProgramPriorityTests on FreeBSD with high nice value (GH-100145) It expects priority to be capped with 19, which is the cap for Linux, but for FreeBSD the cap is 20 and the test fails under the similar conditions. Tweak the condition to cover FreeBSD as well. --- Lib/test/test_os.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 2c8823ae47c726e..ce778498c61d875 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -3511,7 +3511,8 @@ def test_set_get_priority(self): os.setpriority(os.PRIO_PROCESS, os.getpid(), base + 1) try: new_prio = os.getpriority(os.PRIO_PROCESS, os.getpid()) - if base >= 19 and new_prio <= 19: + # nice value cap is 19 for linux and 20 for FreeBSD + if base >= 19 and new_prio <= base: raise unittest.SkipTest("unable to reliably test setpriority " "at current nice level of %s" % base) else: From 90dd653a6122a6c5b4b1fe5abe773c4751e5ca25 Mon Sep 17 00:00:00 2001 From: Brian Schubert <brianm.schubert@gmail.com> Date: Sat, 17 Feb 2024 11:42:57 -0500 Subject: [PATCH 339/507] gh-115596: Fix ProgramPriorityTests in test_os permanently changing the process priority (GH-115610) --- Lib/test/test_os.py | 31 +++++++++---------- ...-02-17-08-25-01.gh-issue-115596.RGPCrR.rst | 2 ++ 2 files changed, 17 insertions(+), 16 deletions(-) create mode 100644 Misc/NEWS.d/next/Tests/2024-02-17-08-25-01.gh-issue-115596.RGPCrR.rst diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index ce778498c61d875..2372ac4c21efd9f 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -3506,23 +3506,22 @@ class ProgramPriorityTests(unittest.TestCase): """Tests for os.getpriority() and os.setpriority().""" def test_set_get_priority(self): - base = os.getpriority(os.PRIO_PROCESS, os.getpid()) - os.setpriority(os.PRIO_PROCESS, os.getpid(), base + 1) - try: - new_prio = os.getpriority(os.PRIO_PROCESS, os.getpid()) - # nice value cap is 19 for linux and 20 for FreeBSD - if base >= 19 and new_prio <= base: - raise unittest.SkipTest("unable to reliably test setpriority " - "at current nice level of %s" % base) - else: - self.assertEqual(new_prio, base + 1) - finally: - try: - os.setpriority(os.PRIO_PROCESS, os.getpid(), base) - except OSError as err: - if err.errno != errno.EACCES: - raise + code = f"""if 1: + import os + os.setpriority(os.PRIO_PROCESS, os.getpid(), {base} + 1) + print(os.getpriority(os.PRIO_PROCESS, os.getpid())) + """ + + # Subprocess inherits the current process' priority. + _, out, _ = assert_python_ok("-c", code) + new_prio = int(out) + # nice value cap is 19 for linux and 20 for FreeBSD + if base >= 19 and new_prio <= base: + raise unittest.SkipTest("unable to reliably test setpriority " + "at current nice level of %s" % base) + else: + self.assertEqual(new_prio, base + 1) @unittest.skipUnless(hasattr(os, 'sendfile'), "test needs os.sendfile()") diff --git a/Misc/NEWS.d/next/Tests/2024-02-17-08-25-01.gh-issue-115596.RGPCrR.rst b/Misc/NEWS.d/next/Tests/2024-02-17-08-25-01.gh-issue-115596.RGPCrR.rst new file mode 100644 index 000000000000000..2bcb8b9ac6bcd4d --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-02-17-08-25-01.gh-issue-115596.RGPCrR.rst @@ -0,0 +1,2 @@ +Fix ``ProgramPriorityTests`` in ``test_os`` permanently changing the process +priority. From aba37d451fabb44a7f478958ba117ae5b6ea54c0 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 17 Feb 2024 23:17:21 +0200 Subject: [PATCH 340/507] gh-100176: Remove outdated Tools/{io,cc,string}bench (#101853) Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com> --- ...-02-12-19-28-08.gh-issue-100176.Kzs4Zw.rst | 1 + Tools/README | 9 - Tools/ccbench/ccbench.py | 606 ------- Tools/iobench/iobench.py | 568 ------- Tools/stringbench/README | 68 - Tools/stringbench/stringbench.py | 1482 ----------------- 6 files changed, 1 insertion(+), 2733 deletions(-) create mode 100644 Misc/NEWS.d/next/Tools-Demos/2023-02-12-19-28-08.gh-issue-100176.Kzs4Zw.rst delete mode 100644 Tools/ccbench/ccbench.py delete mode 100644 Tools/iobench/iobench.py delete mode 100644 Tools/stringbench/README delete mode 100644 Tools/stringbench/stringbench.py diff --git a/Misc/NEWS.d/next/Tools-Demos/2023-02-12-19-28-08.gh-issue-100176.Kzs4Zw.rst b/Misc/NEWS.d/next/Tools-Demos/2023-02-12-19-28-08.gh-issue-100176.Kzs4Zw.rst new file mode 100644 index 000000000000000..1a9fc76d93f2976 --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2023-02-12-19-28-08.gh-issue-100176.Kzs4Zw.rst @@ -0,0 +1 @@ +Remove outdated Tools/{io,cc,string}bench diff --git a/Tools/README b/Tools/README index 9c4b6d86e990ba1..09bd6fb4798950c 100644 --- a/Tools/README +++ b/Tools/README @@ -10,8 +10,6 @@ c-analyzer Tools to check no new global variables have been added. cases_generator Tooling to generate interpreters. -ccbench A Python threads-based concurrency benchmark. (*) - clinic A preprocessor for CPython C files in order to automate the boilerplate involved with writing argument parsing code for "builtins". @@ -28,8 +26,6 @@ i18n Tools for internationalization. pygettext.py importbench A set of micro-benchmarks for various import scenarios. -iobench Benchmark for the new Python I/O system. (*) - msi Support for packaging Python as an MSI package on Windows. nuget Files for the NuGet package manager for .NET. @@ -45,9 +41,6 @@ scripts A number of useful single-file programs, e.g. run_tests.py ssl Scripts to generate ssl_data.h from OpenSSL sources, and run tests against multiple installations of OpenSSL and LibreSSL. -stringbench A suite of micro-benchmarks for various operations on - strings (both 8-bit and unicode). (*) - tz A script to dump timezone from /usr/share/zoneinfo. unicode Tools for generating unicodedata and codecs from unicode.org @@ -60,6 +53,4 @@ unittestgui A Tkinter based GUI test runner for unittest, with test wasm Config and helpers to facilitate cross compilation of CPython to WebAssembly (WASM). -(*) A generic benchmark suite is maintained separately at https://github.com/python/performance - Note: The pynche color editor has moved to https://gitlab.com/warsaw/pynche diff --git a/Tools/ccbench/ccbench.py b/Tools/ccbench/ccbench.py deleted file mode 100644 index d52701a82948daf..000000000000000 --- a/Tools/ccbench/ccbench.py +++ /dev/null @@ -1,606 +0,0 @@ -# This file should be kept compatible with both Python 2.6 and Python >= 3.0. - -from __future__ import division -from __future__ import print_function - -""" -ccbench, a Python concurrency benchmark. -""" - -import time -import os -import sys -import itertools -import threading -import subprocess -import socket -from optparse import OptionParser, SUPPRESS_HELP -import platform - -# Compatibility -try: - xrange -except NameError: - xrange = range - -try: - map = itertools.imap -except AttributeError: - pass - - -THROUGHPUT_DURATION = 2.0 - -LATENCY_PING_INTERVAL = 0.1 -LATENCY_DURATION = 2.0 - -BANDWIDTH_PACKET_SIZE = 1024 -BANDWIDTH_DURATION = 2.0 - - -def task_pidigits(): - """Pi calculation (Python)""" - _map = map - _count = itertools.count - _islice = itertools.islice - - def calc_ndigits(n): - # From http://shootout.alioth.debian.org/ - def gen_x(): - return _map(lambda k: (k, 4*k + 2, 0, 2*k + 1), _count(1)) - - def compose(a, b): - aq, ar, as_, at = a - bq, br, bs, bt = b - return (aq * bq, - aq * br + ar * bt, - as_ * bq + at * bs, - as_ * br + at * bt) - - def extract(z, j): - q, r, s, t = z - return (q*j + r) // (s*j + t) - - def pi_digits(): - z = (1, 0, 0, 1) - x = gen_x() - while 1: - y = extract(z, 3) - while y != extract(z, 4): - z = compose(z, next(x)) - y = extract(z, 3) - z = compose((10, -10*y, 0, 1), z) - yield y - - return list(_islice(pi_digits(), n)) - - return calc_ndigits, (50, ) - -def task_regex(): - """regular expression (C)""" - # XXX this task gives horrendous latency results. - import re - # Taken from the `inspect` module - pat = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)', re.MULTILINE) - with open(__file__, "r") as f: - arg = f.read(2000) - return pat.findall, (arg, ) - -def task_sort(): - """list sorting (C)""" - def list_sort(l): - l = l[::-1] - l.sort() - - return list_sort, (list(range(1000)), ) - -def task_compress_zlib(): - """zlib compression (C)""" - import zlib - with open(__file__, "rb") as f: - arg = f.read(5000) * 3 - - def compress(s): - zlib.decompress(zlib.compress(s, 5)) - return compress, (arg, ) - -def task_compress_bz2(): - """bz2 compression (C)""" - import bz2 - with open(__file__, "rb") as f: - arg = f.read(3000) * 2 - - def compress(s): - bz2.compress(s) - return compress, (arg, ) - -def task_hashing(): - """SHA1 hashing (C)""" - import hashlib - with open(__file__, "rb") as f: - arg = f.read(5000) * 30 - - def compute(s): - hashlib.sha1(s).digest() - return compute, (arg, ) - - -throughput_tasks = [task_pidigits, task_regex] -for mod in 'bz2', 'hashlib': - try: - globals()[mod] = __import__(mod) - except ImportError: - globals()[mod] = None - -# For whatever reasons, zlib gives irregular results, so we prefer bz2 or -# hashlib if available. -# (NOTE: hashlib releases the GIL from 2.7 and 3.1 onwards) -if bz2 is not None: - throughput_tasks.append(task_compress_bz2) -elif hashlib is not None: - throughput_tasks.append(task_hashing) -else: - throughput_tasks.append(task_compress_zlib) - -latency_tasks = throughput_tasks -bandwidth_tasks = [task_pidigits] - - -class TimedLoop: - def __init__(self, func, args): - self.func = func - self.args = args - - def __call__(self, start_time, min_duration, end_event, do_yield=False): - step = 20 - niters = 0 - duration = 0.0 - _time = time.time - _sleep = time.sleep - _func = self.func - _args = self.args - t1 = start_time - while True: - for i in range(step): - _func(*_args) - t2 = _time() - # If another thread terminated, the current measurement is invalid - # => return the previous one. - if end_event: - return niters, duration - niters += step - duration = t2 - start_time - if duration >= min_duration: - end_event.append(None) - return niters, duration - if t2 - t1 < 0.01: - # Minimize interference of measurement on overall runtime - step = step * 3 // 2 - elif do_yield: - # OS scheduling of Python threads is sometimes so bad that we - # have to force thread switching ourselves, otherwise we get - # completely useless results. - _sleep(0.0001) - t1 = t2 - - -def run_throughput_test(func, args, nthreads): - assert nthreads >= 1 - - # Warm up - func(*args) - - results = [] - loop = TimedLoop(func, args) - end_event = [] - - if nthreads == 1: - # Pure single-threaded performance, without any switching or - # synchronization overhead. - start_time = time.time() - results.append(loop(start_time, THROUGHPUT_DURATION, - end_event, do_yield=False)) - return results - - started = False - ready_cond = threading.Condition() - start_cond = threading.Condition() - ready = [] - - def run(): - with ready_cond: - ready.append(None) - ready_cond.notify() - with start_cond: - while not started: - start_cond.wait() - results.append(loop(start_time, THROUGHPUT_DURATION, - end_event, do_yield=True)) - - threads = [] - for i in range(nthreads): - threads.append(threading.Thread(target=run)) - for t in threads: - t.daemon = True - t.start() - # We don't want measurements to include thread startup overhead, - # so we arrange for timing to start after all threads are ready. - with ready_cond: - while len(ready) < nthreads: - ready_cond.wait() - with start_cond: - start_time = time.time() - started = True - start_cond.notify(nthreads) - for t in threads: - t.join() - - return results - -def run_throughput_tests(max_threads): - for task in throughput_tasks: - print(task.__doc__) - print() - func, args = task() - nthreads = 1 - baseline_speed = None - while nthreads <= max_threads: - results = run_throughput_test(func, args, nthreads) - # Taking the max duration rather than average gives pessimistic - # results rather than optimistic. - speed = sum(r[0] for r in results) / max(r[1] for r in results) - print("threads=%d: %d" % (nthreads, speed), end="") - if baseline_speed is None: - print(" iterations/s.") - baseline_speed = speed - else: - print(" ( %d %%)" % (speed / baseline_speed * 100)) - nthreads += 1 - print() - - -LAT_END = "END" - -def _sendto(sock, s, addr): - sock.sendto(s.encode('ascii'), addr) - -def _recv(sock, n): - return sock.recv(n).decode('ascii') - -def latency_client(addr, nb_pings, interval): - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - try: - _time = time.time - _sleep = time.sleep - def _ping(): - _sendto(sock, "%r\n" % _time(), addr) - # The first ping signals the parent process that we are ready. - _ping() - # We give the parent a bit of time to notice. - _sleep(1.0) - for i in range(nb_pings): - _sleep(interval) - _ping() - _sendto(sock, LAT_END + "\n", addr) - finally: - sock.close() - -def run_latency_client(**kwargs): - cmd_line = [sys.executable, '-E', os.path.abspath(__file__)] - cmd_line.extend(['--latclient', repr(kwargs)]) - return subprocess.Popen(cmd_line) #, stdin=subprocess.PIPE, - #stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - -def run_latency_test(func, args, nthreads): - # Create a listening socket to receive the pings. We use UDP which should - # be painlessly cross-platform. - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.bind(("127.0.0.1", 0)) - addr = sock.getsockname() - - interval = LATENCY_PING_INTERVAL - duration = LATENCY_DURATION - nb_pings = int(duration / interval) - - results = [] - threads = [] - end_event = [] - start_cond = threading.Condition() - started = False - if nthreads > 0: - # Warm up - func(*args) - - results = [] - loop = TimedLoop(func, args) - ready = [] - ready_cond = threading.Condition() - - def run(): - with ready_cond: - ready.append(None) - ready_cond.notify() - with start_cond: - while not started: - start_cond.wait() - loop(start_time, duration * 1.5, end_event, do_yield=False) - - for i in range(nthreads): - threads.append(threading.Thread(target=run)) - for t in threads: - t.daemon = True - t.start() - # Wait for threads to be ready - with ready_cond: - while len(ready) < nthreads: - ready_cond.wait() - - # Run the client and wait for the first ping(s) to arrive before - # unblocking the background threads. - chunks = [] - process = run_latency_client(addr=sock.getsockname(), - nb_pings=nb_pings, interval=interval) - s = _recv(sock, 4096) - _time = time.time - - with start_cond: - start_time = _time() - started = True - start_cond.notify(nthreads) - - while LAT_END not in s: - s = _recv(sock, 4096) - t = _time() - chunks.append((t, s)) - - # Tell the background threads to stop. - end_event.append(None) - for t in threads: - t.join() - process.wait() - sock.close() - - for recv_time, chunk in chunks: - # NOTE: it is assumed that a line sent by a client wasn't received - # in two chunks because the lines are very small. - for line in chunk.splitlines(): - line = line.strip() - if line and line != LAT_END: - send_time = eval(line) - assert isinstance(send_time, float) - results.append((send_time, recv_time)) - - return results - -def run_latency_tests(max_threads): - for task in latency_tasks: - print("Background CPU task:", task.__doc__) - print() - func, args = task() - nthreads = 0 - while nthreads <= max_threads: - results = run_latency_test(func, args, nthreads) - n = len(results) - # We print out milliseconds - lats = [1000 * (t2 - t1) for (t1, t2) in results] - #print(list(map(int, lats))) - avg = sum(lats) / n - dev = (sum((x - avg) ** 2 for x in lats) / n) ** 0.5 - print("CPU threads=%d: %d ms. (std dev: %d ms.)" % (nthreads, avg, dev), end="") - print() - #print(" [... from %d samples]" % n) - nthreads += 1 - print() - - -BW_END = "END" - -def bandwidth_client(addr, packet_size, duration): - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.bind(("127.0.0.1", 0)) - local_addr = sock.getsockname() - _time = time.time - _sleep = time.sleep - def _send_chunk(msg): - _sendto(sock, ("%r#%s\n" % (local_addr, msg)).rjust(packet_size), addr) - # We give the parent some time to be ready. - _sleep(1.0) - try: - start_time = _time() - end_time = start_time + duration * 2.0 - i = 0 - while _time() < end_time: - _send_chunk(str(i)) - s = _recv(sock, packet_size) - assert len(s) == packet_size - i += 1 - _send_chunk(BW_END) - finally: - sock.close() - -def run_bandwidth_client(**kwargs): - cmd_line = [sys.executable, '-E', os.path.abspath(__file__)] - cmd_line.extend(['--bwclient', repr(kwargs)]) - return subprocess.Popen(cmd_line) #, stdin=subprocess.PIPE, - #stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - -def run_bandwidth_test(func, args, nthreads): - # Create a listening socket to receive the packets. We use UDP which should - # be painlessly cross-platform. - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: - sock.bind(("127.0.0.1", 0)) - addr = sock.getsockname() - - duration = BANDWIDTH_DURATION - packet_size = BANDWIDTH_PACKET_SIZE - - results = [] - threads = [] - end_event = [] - start_cond = threading.Condition() - started = False - if nthreads > 0: - # Warm up - func(*args) - - results = [] - loop = TimedLoop(func, args) - ready = [] - ready_cond = threading.Condition() - - def run(): - with ready_cond: - ready.append(None) - ready_cond.notify() - with start_cond: - while not started: - start_cond.wait() - loop(start_time, duration * 1.5, end_event, do_yield=False) - - for i in range(nthreads): - threads.append(threading.Thread(target=run)) - for t in threads: - t.daemon = True - t.start() - # Wait for threads to be ready - with ready_cond: - while len(ready) < nthreads: - ready_cond.wait() - - # Run the client and wait for the first packet to arrive before - # unblocking the background threads. - process = run_bandwidth_client(addr=addr, - packet_size=packet_size, - duration=duration) - _time = time.time - # This will also wait for the parent to be ready - s = _recv(sock, packet_size) - remote_addr = eval(s.partition('#')[0]) - - with start_cond: - start_time = _time() - started = True - start_cond.notify(nthreads) - - n = 0 - first_time = None - while not end_event and BW_END not in s: - _sendto(sock, s, remote_addr) - s = _recv(sock, packet_size) - if first_time is None: - first_time = _time() - n += 1 - end_time = _time() - - end_event.append(None) - for t in threads: - t.join() - process.kill() - - return (n - 1) / (end_time - first_time) - -def run_bandwidth_tests(max_threads): - for task in bandwidth_tasks: - print("Background CPU task:", task.__doc__) - print() - func, args = task() - nthreads = 0 - baseline_speed = None - while nthreads <= max_threads: - results = run_bandwidth_test(func, args, nthreads) - speed = results - #speed = len(results) * 1.0 / results[-1][0] - print("CPU threads=%d: %.1f" % (nthreads, speed), end="") - if baseline_speed is None: - print(" packets/s.") - baseline_speed = speed - else: - print(" ( %d %%)" % (speed / baseline_speed * 100)) - nthreads += 1 - print() - - -def main(): - usage = "usage: %prog [-h|--help] [options]" - parser = OptionParser(usage=usage) - parser.add_option("-t", "--throughput", - action="store_true", dest="throughput", default=False, - help="run throughput tests") - parser.add_option("-l", "--latency", - action="store_true", dest="latency", default=False, - help="run latency tests") - parser.add_option("-b", "--bandwidth", - action="store_true", dest="bandwidth", default=False, - help="run I/O bandwidth tests") - parser.add_option("-i", "--interval", - action="store", type="int", dest="check_interval", default=None, - help="sys.setcheckinterval() value " - "(Python 3.8 and older)") - parser.add_option("-I", "--switch-interval", - action="store", type="float", dest="switch_interval", default=None, - help="sys.setswitchinterval() value " - "(Python 3.2 and newer)") - parser.add_option("-n", "--num-threads", - action="store", type="int", dest="nthreads", default=4, - help="max number of threads in tests") - - # Hidden option to run the pinging and bandwidth clients - parser.add_option("", "--latclient", - action="store", dest="latclient", default=None, - help=SUPPRESS_HELP) - parser.add_option("", "--bwclient", - action="store", dest="bwclient", default=None, - help=SUPPRESS_HELP) - - options, args = parser.parse_args() - if args: - parser.error("unexpected arguments") - - if options.latclient: - kwargs = eval(options.latclient) - latency_client(**kwargs) - return - - if options.bwclient: - kwargs = eval(options.bwclient) - bandwidth_client(**kwargs) - return - - if not options.throughput and not options.latency and not options.bandwidth: - options.throughput = options.latency = options.bandwidth = True - if options.check_interval: - sys.setcheckinterval(options.check_interval) - if options.switch_interval: - sys.setswitchinterval(options.switch_interval) - - print("== %s %s (%s) ==" % ( - platform.python_implementation(), - platform.python_version(), - platform.python_build()[0], - )) - # Processor identification often has repeated spaces - cpu = ' '.join(platform.processor().split()) - print("== %s %s on '%s' ==" % ( - platform.machine(), - platform.system(), - cpu, - )) - print() - - if options.throughput: - print("--- Throughput ---") - print() - run_throughput_tests(options.nthreads) - - if options.latency: - print("--- Latency ---") - print() - run_latency_tests(options.nthreads) - - if options.bandwidth: - print("--- I/O bandwidth ---") - print() - run_bandwidth_tests(options.nthreads) - -if __name__ == "__main__": - main() diff --git a/Tools/iobench/iobench.py b/Tools/iobench/iobench.py deleted file mode 100644 index 4017149ec91630f..000000000000000 --- a/Tools/iobench/iobench.py +++ /dev/null @@ -1,568 +0,0 @@ -import itertools -import os -import platform -import re -import sys -import time -from optparse import OptionParser - -out = sys.stdout - -TEXT_ENCODING = 'utf8' -NEWLINES = 'lf' - - -def text_open(fn, mode, encoding=None): - try: - return open(fn, mode, encoding=encoding or TEXT_ENCODING) - except TypeError: - return open(fn, mode) - - -def get_file_sizes(): - for s in ['20 KiB', '400 KiB', '10 MiB']: - size, unit = s.split() - size = int(size) * {'KiB': 1024, 'MiB': 1024 ** 2}[unit] - yield s.replace(' ', ''), size - - -def get_binary_files(): - return ((name + ".bin", size) for name, size in get_file_sizes()) - - -def get_text_files(): - return ((f"{name}-{TEXT_ENCODING}-{NEWLINES}.txt", size) - for name, size in get_file_sizes()) - - -def with_open_mode(mode): - def decorate(f): - f.file_open_mode = mode - return f - return decorate - - -def with_sizes(*sizes): - def decorate(f): - f.file_sizes = sizes - return f - return decorate - - -# Here begin the tests - -@with_open_mode("r") -@with_sizes("medium") -def read_bytewise(f): - """ read one unit at a time """ - f.seek(0) - while f.read(1): - pass - - -@with_open_mode("r") -@with_sizes("medium") -def read_small_chunks(f): - """ read 20 units at a time """ - f.seek(0) - while f.read(20): - pass - - -@with_open_mode("r") -@with_sizes("medium") -def read_big_chunks(f): - """ read 4096 units at a time """ - f.seek(0) - while f.read(4096): - pass - - -@with_open_mode("r") -@with_sizes("small", "medium", "large") -def read_whole_file(f): - """ read whole contents at once """ - f.seek(0) - while f.read(): - pass - - -@with_open_mode("rt") -@with_sizes("medium") -def read_lines(f): - """ read one line at a time """ - f.seek(0) - for line in f: - pass - - -@with_open_mode("r") -@with_sizes("medium") -def seek_forward_bytewise(f): - """ seek forward one unit at a time """ - f.seek(0, 2) - size = f.tell() - f.seek(0, 0) - for i in range(0, size - 1): - f.seek(i, 0) - - -@with_open_mode("r") -@with_sizes("medium") -def seek_forward_blockwise(f): - """ seek forward 1000 units at a time """ - f.seek(0, 2) - size = f.tell() - f.seek(0, 0) - for i in range(0, size - 1, 1000): - f.seek(i, 0) - - -@with_open_mode("rb") -@with_sizes("medium") -def read_seek_bytewise(f): - """ alternate read & seek one unit """ - f.seek(0) - while f.read(1): - f.seek(1, 1) - - -@with_open_mode("rb") -@with_sizes("medium") -def read_seek_blockwise(f): - """ alternate read & seek 1000 units """ - f.seek(0) - while f.read(1000): - f.seek(1000, 1) - - -@with_open_mode("w") -@with_sizes("small") -def write_bytewise(f, source): - """ write one unit at a time """ - for i in range(0, len(source)): - f.write(source[i:i+1]) - - -@with_open_mode("w") -@with_sizes("medium") -def write_small_chunks(f, source): - """ write 20 units at a time """ - for i in range(0, len(source), 20): - f.write(source[i:i+20]) - - -@with_open_mode("w") -@with_sizes("medium") -def write_medium_chunks(f, source): - """ write 4096 units at a time """ - for i in range(0, len(source), 4096): - f.write(source[i:i+4096]) - - -@with_open_mode("w") -@with_sizes("large") -def write_large_chunks(f, source): - """ write 1e6 units at a time """ - for i in range(0, len(source), 1000000): - f.write(source[i:i+1000000]) - - -@with_open_mode("w+") -@with_sizes("small") -def modify_bytewise(f, source): - """ modify one unit at a time """ - f.seek(0) - for i in range(0, len(source)): - f.write(source[i:i+1]) - - -@with_open_mode("w+") -@with_sizes("medium") -def modify_small_chunks(f, source): - """ modify 20 units at a time """ - f.seek(0) - for i in range(0, len(source), 20): - f.write(source[i:i+20]) - - -@with_open_mode("w+") -@with_sizes("medium") -def modify_medium_chunks(f, source): - """ modify 4096 units at a time """ - f.seek(0) - for i in range(0, len(source), 4096): - f.write(source[i:i+4096]) - - -@with_open_mode("wb+") -@with_sizes("medium") -def modify_seek_forward_bytewise(f, source): - """ alternate write & seek one unit """ - f.seek(0) - for i in range(0, len(source), 2): - f.write(source[i:i+1]) - f.seek(i+2) - - -@with_open_mode("wb+") -@with_sizes("medium") -def modify_seek_forward_blockwise(f, source): - """ alternate write & seek 1000 units """ - f.seek(0) - for i in range(0, len(source), 2000): - f.write(source[i:i+1000]) - f.seek(i+2000) - - -# XXX the 2 following tests don't work with py3k's text IO -@with_open_mode("wb+") -@with_sizes("medium") -def read_modify_bytewise(f, source): - """ alternate read & write one unit """ - f.seek(0) - for i in range(0, len(source), 2): - f.read(1) - f.write(source[i+1:i+2]) - - -@with_open_mode("wb+") -@with_sizes("medium") -def read_modify_blockwise(f, source): - """ alternate read & write 1000 units """ - f.seek(0) - for i in range(0, len(source), 2000): - f.read(1000) - f.write(source[i+1000:i+2000]) - - -read_tests = [ - read_bytewise, read_small_chunks, read_lines, read_big_chunks, - None, read_whole_file, None, - seek_forward_bytewise, seek_forward_blockwise, - read_seek_bytewise, read_seek_blockwise, -] - -write_tests = [ - write_bytewise, write_small_chunks, write_medium_chunks, write_large_chunks, -] - -modify_tests = [ - modify_bytewise, modify_small_chunks, modify_medium_chunks, - None, - modify_seek_forward_bytewise, modify_seek_forward_blockwise, - read_modify_bytewise, read_modify_blockwise, -] - - -def run_during(duration, func): - _t = time.time - n = 0 - start = os.times() - start_timestamp = _t() - real_start = start[4] or start_timestamp - while True: - func() - n += 1 - if _t() - start_timestamp > duration: - break - end = os.times() - real = (end[4] if start[4] else time.time()) - real_start - return n, real, sum(end[0:2]) - sum(start[0:2]) - - -def warm_cache(filename): - with open(filename, "rb") as f: - f.read() - - -def run_all_tests(options): - def print_label(filename, func): - name = re.split(r'[-.]', filename)[0] - out.write( - f"[{name.center(7)}] {func.__doc__.strip()}... ".ljust(52)) - out.flush() - - def print_results(size, n, real, cpu): - bw = n * float(size) / 1024 ** 2 / real - bw = ("%4d MiB/s" if bw > 100 else "%.3g MiB/s") % bw - out.write(bw.rjust(12) + "\n") - if cpu < 0.90 * real: - out.write(" warning: test above used only " - f"{cpu / real:%} CPU, " - "result may be flawed!\n") - - def run_one_test(name, size, open_func, test_func, *args): - mode = test_func.file_open_mode - print_label(name, test_func) - if "w" not in mode or "+" in mode: - warm_cache(name) - with open_func(name) as f: - n, real, cpu = run_during(1.5, lambda: test_func(f, *args)) - print_results(size, n, real, cpu) - - def run_test_family(tests, mode_filter, files, open_func, *make_args): - for test_func in tests: - if test_func is None: - out.write("\n") - continue - if mode_filter in test_func.file_open_mode: - continue - for s in test_func.file_sizes: - name, size = files[size_names[s]] - #name += file_ext - args = tuple(f(name, size) for f in make_args) - run_one_test(name, size, - open_func, test_func, *args) - - size_names = { - "small": 0, - "medium": 1, - "large": 2, - } - - print(f"Python {sys.version}") - print("Unicode: PEP 393") - print(platform.platform()) - binary_files = list(get_binary_files()) - text_files = list(get_text_files()) - if "b" in options: - print("Binary unit = one byte") - if "t" in options: - print(f"Text unit = one character ({TEXT_ENCODING}-decoded)") - - # Binary reads - if "b" in options and "r" in options: - print("\n** Binary input **\n") - run_test_family(read_tests, "t", binary_files, lambda fn: open(fn, "rb")) - - # Text reads - if "t" in options and "r" in options: - print("\n** Text input **\n") - run_test_family(read_tests, "b", text_files, lambda fn: text_open(fn, "r")) - - # Binary writes - if "b" in options and "w" in options: - print("\n** Binary append **\n") - - def make_test_source(name, size): - with open(name, "rb") as f: - return f.read() - run_test_family(write_tests, "t", binary_files, - lambda fn: open(os.devnull, "wb"), make_test_source) - - # Text writes - if "t" in options and "w" in options: - print("\n** Text append **\n") - - def make_test_source(name, size): - with text_open(name, "r") as f: - return f.read() - run_test_family(write_tests, "b", text_files, - lambda fn: text_open(os.devnull, "w"), make_test_source) - - # Binary overwrites - if "b" in options and "w" in options: - print("\n** Binary overwrite **\n") - - def make_test_source(name, size): - with open(name, "rb") as f: - return f.read() - run_test_family(modify_tests, "t", binary_files, - lambda fn: open(fn, "r+b"), make_test_source) - - # Text overwrites - if "t" in options and "w" in options: - print("\n** Text overwrite **\n") - - def make_test_source(name, size): - with text_open(name, "r") as f: - return f.read() - run_test_family(modify_tests, "b", text_files, - lambda fn: text_open(fn, "r+"), make_test_source) - - -def prepare_files(): - print("Preparing files...") - # Binary files - for name, size in get_binary_files(): - if os.path.isfile(name) and os.path.getsize(name) == size: - continue - with open(name, "wb") as f: - f.write(os.urandom(size)) - # Text files - chunk = [] - with text_open(__file__, "r", encoding='utf8') as f: - for line in f: - if line.startswith("# <iobench text chunk marker>"): - break - else: - raise RuntimeError( - f"Couldn't find chunk marker in {__file__} !") - if NEWLINES == "all": - it = itertools.cycle(["\n", "\r", "\r\n"]) - else: - it = itertools.repeat( - {"cr": "\r", "lf": "\n", "crlf": "\r\n"}[NEWLINES]) - chunk = "".join(line.replace("\n", next(it)) for line in f) - if isinstance(chunk, bytes): - chunk = chunk.decode('utf8') - chunk = chunk.encode(TEXT_ENCODING) - for name, size in get_text_files(): - if os.path.isfile(name) and os.path.getsize(name) == size: - continue - head = chunk * (size // len(chunk)) - tail = chunk[:size % len(chunk)] - # Adjust tail to end on a character boundary - while True: - try: - tail.decode(TEXT_ENCODING) - break - except UnicodeDecodeError: - tail = tail[:-1] - with open(name, "wb") as f: - f.write(head) - f.write(tail) - - -def main(): - global TEXT_ENCODING, NEWLINES - - usage = "usage: %prog [-h|--help] [options]" - parser = OptionParser(usage=usage) - parser.add_option("-b", "--binary", - action="store_true", dest="binary", default=False, - help="run binary I/O tests") - parser.add_option("-t", "--text", - action="store_true", dest="text", default=False, - help="run text I/O tests") - parser.add_option("-r", "--read", - action="store_true", dest="read", default=False, - help="run read tests") - parser.add_option("-w", "--write", - action="store_true", dest="write", default=False, - help="run write & modify tests") - parser.add_option("-E", "--encoding", - action="store", dest="encoding", default=None, - help=f"encoding for text tests (default: {TEXT_ENCODING})") - parser.add_option("-N", "--newlines", - action="store", dest="newlines", default='lf', - help="line endings for text tests " - "(one of: {lf (default), cr, crlf, all})") - parser.add_option("-m", "--io-module", - action="store", dest="io_module", default=None, - help="io module to test (default: builtin open())") - options, args = parser.parse_args() - if args: - parser.error("unexpected arguments") - NEWLINES = options.newlines.lower() - if NEWLINES not in ('lf', 'cr', 'crlf', 'all'): - parser.error(f"invalid 'newlines' option: {NEWLINES!r}") - - test_options = "" - if options.read: - test_options += "r" - if options.write: - test_options += "w" - elif not options.read: - test_options += "rw" - if options.text: - test_options += "t" - if options.binary: - test_options += "b" - elif not options.text: - test_options += "tb" - - if options.encoding: - TEXT_ENCODING = options.encoding - - if options.io_module: - globals()['open'] = __import__(options.io_module, {}, {}, ['open']).open - - prepare_files() - run_all_tests(test_options) - - -if __name__ == "__main__": - main() - - -# -- This part to exercise text reading. Don't change anything! -- -# <iobench text chunk marker> - -""" -1. -Gáttir allar, -áðr gangi fram, -um skoðask skyli, -um skyggnast skyli, -því at óvíst er at vita, -hvar óvinir -sitja á fleti fyrir. - -2. -Gefendr heilir! -Gestr er inn kominn, -hvar skal sitja sjá? -Mjök er bráðr, -sá er á bröndum skal -síns of freista frama. - -3. -Elds er þörf, -þeims inn er kominn -ok á kné kalinn; -matar ok váða -er manni þörf, -þeim er hefr um fjall farit. - -4. -Vatns er þörf, -þeim er til verðar kemr, -þerru ok þjóðlaðar, -góðs of æðis, -ef sér geta mætti, -orðs ok endrþögu. - -5. -Vits er þörf, -þeim er víða ratar; -dælt er heima hvat; -at augabragði verðr, -sá er ekki kann -ok með snotrum sitr. - -6. -At hyggjandi sinni -skyli-t maðr hræsinn vera, -heldr gætinn at geði; -þá er horskr ok þögull -kemr heimisgarða til, -sjaldan verðr víti vörum, -því at óbrigðra vin -fær maðr aldregi -en mannvit mikit. - -7. -Inn vari gestr, -er til verðar kemr, -þunnu hljóði þegir, -eyrum hlýðir, -en augum skoðar; -svá nýsisk fróðra hverr fyrir. - -8. -Hinn er sæll, -er sér of getr -lof ok líknstafi; -ódælla er við þat, -er maðr eiga skal -annars brjóstum í. -""" - -""" -C'est revenir tard, je le sens, sur un sujet trop rebattu et déjà presque oublié. Mon état, qui ne me permet plus aucun travail suivi, mon aversion pour le genre polémique, ont causé ma lenteur à écrire et ma répugnance à publier. J'aurais même tout à fait supprimé ces Lettres, ou plutôt je lie les aurais point écrites, s'il n'eût été question que de moi : Mais ma patrie ne m'est pas tellement devenue étrangère que je puisse voir tranquillement opprimer ses citoyens, surtout lorsqu'ils n'ont compromis leurs droits qu'en défendant ma cause. Je serais le dernier des hommes si dans une telle occasion j'écoutais un sentiment qui n'est plus ni douceur ni patience, mais faiblesse et lâcheté, dans celui qu'il empêche de remplir son devoir. -Rien de moins important pour le public, j'en conviens, que la matière de ces lettres. La constitution d'une petite République, le sort d'un petit particulier, l'exposé de quelques injustices, la réfutation de quelques sophismes ; tout cela n'a rien en soi d'assez considérable pour mériter beaucoup de lecteurs : mais si mes sujets sont petits mes objets sont grands, et dignes de l'attention de tout honnête homme. Laissons Genève à sa place, et Rousseau dans sa dépression ; mais la religion, mais la liberté, la justice ! voilà, qui que vous soyez, ce qui n'est pas au-dessous de vous. -Qu'on ne cherche pas même ici dans le style le dédommagement de l'aridité de la matière. Ceux que quelques traits heureux de ma plume ont si fort irrités trouveront de quoi s'apaiser dans ces lettres, L'honneur de défendre un opprimé eût enflammé mon coeur si j'avais parlé pour un autre. Réduit au triste emploi de me défendre moi-même, j'ai dû me borner à raisonner ; m'échauffer eût été m'avilir. J'aurai donc trouvé grâce en ce point devant ceux qui s'imaginent qu'il est essentiel à la vérité d'être dite froidement ; opinion que pourtant j'ai peine à comprendre. Lorsqu'une vive persuasion nous anime, le moyen d'employer un langage glacé ? Quand Archimède tout transporté courait nu dans les rues de Syracuse, en avait-il moins trouvé la vérité parce qu'il se passionnait pour elle ? Tout au contraire, celui qui la sent ne peut s'abstenir de l'adorer ; celui qui demeure froid ne l'a pas vue. -Quoi qu'il en soit, je prie les lecteurs de vouloir bien mettre à part mon beau style, et d'examiner seulement si je raisonne bien ou mal ; car enfin, de cela seul qu'un auteur s'exprime en bons termes, je ne vois pas comment il peut s'ensuivre que cet auteur ne sait ce qu'il dit. -""" diff --git a/Tools/stringbench/README b/Tools/stringbench/README deleted file mode 100644 index a271f12632afff3..000000000000000 --- a/Tools/stringbench/README +++ /dev/null @@ -1,68 +0,0 @@ -stringbench is a set of performance tests comparing byte string -operations with unicode operations. The two string implementations -are loosely based on each other and sometimes the algorithm for one is -faster than the other. - -These test set was started at the Need For Speed sprint in Reykjavik -to identify which string methods could be sped up quickly and to -identify obvious places for improvement. - -Here is an example of a benchmark - - -@bench('"Andrew".startswith("A")', 'startswith single character', 1000) -def startswith_single(STR): - s1 = STR("Andrew") - s2 = STR("A") - s1_startswith = s1.startswith - for x in _RANGE_1000: - s1_startswith(s2) - -The bench decorator takes three parameters. The first is a short -description of how the code works. In most cases this is Python code -snippet. It is not the code which is actually run because the real -code is hand-optimized to focus on the method being tested. - -The second parameter is a group title. All benchmarks with the same -group title are listed together. This lets you compare different -implementations of the same algorithm, such as "t in s" -vs. "s.find(t)". - -The last is a count. Each benchmark loops over the algorithm either -100 or 1000 times, depending on the algorithm performance. The output -time is the time per benchmark call so the reader needs a way to know -how to scale the performance. - -These parameters become function attributes. - - -Here is an example of the output - - -========== count newlines -38.54 41.60 92.7 ...text.with.2000.newlines.count("\n") (*100) -========== early match, single character -1.14 1.18 96.8 ("A"*1000).find("A") (*1000) -0.44 0.41 105.6 "A" in "A"*1000 (*1000) -1.15 1.17 98.1 ("A"*1000).index("A") (*1000) - -The first column is the run time in milliseconds for byte strings. -The second is the run time for unicode strings. The third is a -percentage; byte time / unicode time. It's the percentage by which -unicode is faster than byte strings. - -The last column contains the code snippet and the repeat count for the -internal benchmark loop. - -The times are computed with 'timeit.py' which repeats the test more -and more times until the total time takes over 0.2 seconds, returning -the best time for a single iteration. - -The final line of the output is the cumulative time for byte and -unicode strings, and the overall performance of unicode relative to -bytes. For example - -4079.83 5432.25 75.1 TOTAL - -However, this has no meaning as it evenly weights every test. - diff --git a/Tools/stringbench/stringbench.py b/Tools/stringbench/stringbench.py deleted file mode 100644 index 5d2b41463786261..000000000000000 --- a/Tools/stringbench/stringbench.py +++ /dev/null @@ -1,1482 +0,0 @@ - -# Various microbenchmarks comparing unicode and byte string performance -# Please keep this file both 2.x and 3.x compatible! - -import timeit -import itertools -import operator -import re -import sys -import datetime -import optparse - -VERSION = '2.0' - -def p(*args): - sys.stdout.write(' '.join(str(s) for s in args) + '\n') - -if sys.version_info >= (3,): - BYTES = bytes_from_str = lambda x: x.encode('ascii') - UNICODE = unicode_from_str = lambda x: x -else: - BYTES = bytes_from_str = lambda x: x - UNICODE = unicode_from_str = lambda x: x.decode('ascii') - -class UnsupportedType(TypeError): - pass - - -p('stringbench v%s' % VERSION) -p(sys.version) -p(datetime.datetime.now()) - -REPEAT = 1 -REPEAT = 3 -#REPEAT = 7 - -if __name__ != "__main__": - raise SystemExit("Must run as main program") - -parser = optparse.OptionParser() -parser.add_option("-R", "--skip-re", dest="skip_re", - action="store_true", - help="skip regular expression tests") -parser.add_option("-8", "--8-bit", dest="bytes_only", - action="store_true", - help="only do 8-bit string benchmarks") -parser.add_option("-u", "--unicode", dest="unicode_only", - action="store_true", - help="only do Unicode string benchmarks") - - -_RANGE_1000 = list(range(1000)) -_RANGE_100 = list(range(100)) -_RANGE_10 = list(range(10)) - -dups = {} -def bench(s, group, repeat_count): - def blah(f): - if f.__name__ in dups: - raise AssertionError("Multiple functions with same name: %r" % - (f.__name__,)) - dups[f.__name__] = 1 - f.comment = s - f.is_bench = True - f.group = group - f.repeat_count = repeat_count - return f - return blah - -def uses_re(f): - f.uses_re = True - -####### 'in' comparisons - -@bench('"A" in "A"*1000', "early match, single character", 1000) -def in_test_quick_match_single_character(STR): - s1 = STR("A" * 1000) - s2 = STR("A") - for x in _RANGE_1000: - s2 in s1 - -@bench('"B" in "A"*1000', "no match, single character", 1000) -def in_test_no_match_single_character(STR): - s1 = STR("A" * 1000) - s2 = STR("B") - for x in _RANGE_1000: - s2 in s1 - - -@bench('"AB" in "AB"*1000', "early match, two characters", 1000) -def in_test_quick_match_two_characters(STR): - s1 = STR("AB" * 1000) - s2 = STR("AB") - for x in _RANGE_1000: - s2 in s1 - -@bench('"BC" in "AB"*1000', "no match, two characters", 1000) -def in_test_no_match_two_character(STR): - s1 = STR("AB" * 1000) - s2 = STR("BC") - for x in _RANGE_1000: - s2 in s1 - -@bench('"BC" in ("AB"*300+"C")', "late match, two characters", 1000) -def in_test_slow_match_two_characters(STR): - s1 = STR("AB" * 300+"C") - s2 = STR("BC") - for x in _RANGE_1000: - s2 in s1 - -@bench('s="ABC"*33; (s+"E") in ((s+"D")*300+s+"E")', - "late match, 100 characters", 100) -def in_test_slow_match_100_characters(STR): - m = STR("ABC"*33) - d = STR("D") - e = STR("E") - s1 = (m+d)*300 + m+e - s2 = m+e - for x in _RANGE_100: - s2 in s1 - -# Try with regex -@uses_re -@bench('s="ABC"*33; re.compile(s+"D").search((s+"D")*300+s+"E")', - "late match, 100 characters", 100) -def re_test_slow_match_100_characters(STR): - m = STR("ABC"*33) - d = STR("D") - e = STR("E") - s1 = (m+d)*300 + m+e - s2 = m+e - pat = re.compile(s2) - search = pat.search - for x in _RANGE_100: - search(s1) - - -#### same tests as 'in' but use 'find' - -@bench('("A"*1000).find("A")', "early match, single character", 1000) -def find_test_quick_match_single_character(STR): - s1 = STR("A" * 1000) - s2 = STR("A") - s1_find = s1.find - for x in _RANGE_1000: - s1_find(s2) - -@bench('("A"*1000).find("B")', "no match, single character", 1000) -def find_test_no_match_single_character(STR): - s1 = STR("A" * 1000) - s2 = STR("B") - s1_find = s1.find - for x in _RANGE_1000: - s1_find(s2) - - -@bench('("AB"*1000).find("AB")', "early match, two characters", 1000) -def find_test_quick_match_two_characters(STR): - s1 = STR("AB" * 1000) - s2 = STR("AB") - s1_find = s1.find - for x in _RANGE_1000: - s1_find(s2) - -@bench('("AB"*1000).find("BC")', "no match, two characters", 1000) -def find_test_no_match_two_character(STR): - s1 = STR("AB" * 1000) - s2 = STR("BC") - s1_find = s1.find - for x in _RANGE_1000: - s1_find(s2) - -@bench('("AB"*1000).find("CA")', "no match, two characters", 1000) -def find_test_no_match_two_character_bis(STR): - s1 = STR("AB" * 1000) - s2 = STR("CA") - s1_find = s1.find - for x in _RANGE_1000: - s1_find(s2) - -@bench('("AB"*300+"C").find("BC")', "late match, two characters", 1000) -def find_test_slow_match_two_characters(STR): - s1 = STR("AB" * 300+"C") - s2 = STR("BC") - s1_find = s1.find - for x in _RANGE_1000: - s1_find(s2) - -@bench('("AB"*300+"CA").find("CA")', "late match, two characters", 1000) -def find_test_slow_match_two_characters_bis(STR): - s1 = STR("AB" * 300+"CA") - s2 = STR("CA") - s1_find = s1.find - for x in _RANGE_1000: - s1_find(s2) - -@bench('s="ABC"*33; ((s+"D")*500+s+"E").find(s+"E")', - "late match, 100 characters", 100) -def find_test_slow_match_100_characters(STR): - m = STR("ABC"*33) - d = STR("D") - e = STR("E") - s1 = (m+d)*500 + m+e - s2 = m+e - s1_find = s1.find - for x in _RANGE_100: - s1_find(s2) - -@bench('s="ABC"*33; ((s+"D")*500+"E"+s).find("E"+s)', - "late match, 100 characters", 100) -def find_test_slow_match_100_characters_bis(STR): - m = STR("ABC"*33) - d = STR("D") - e = STR("E") - s1 = (m+d)*500 + e+m - s2 = e+m - s1_find = s1.find - for x in _RANGE_100: - s1_find(s2) - - -#### Same tests for 'rfind' - -@bench('("A"*1000).rfind("A")', "early match, single character", 1000) -def rfind_test_quick_match_single_character(STR): - s1 = STR("A" * 1000) - s2 = STR("A") - s1_rfind = s1.rfind - for x in _RANGE_1000: - s1_rfind(s2) - -@bench('("A"*1000).rfind("B")', "no match, single character", 1000) -def rfind_test_no_match_single_character(STR): - s1 = STR("A" * 1000) - s2 = STR("B") - s1_rfind = s1.rfind - for x in _RANGE_1000: - s1_rfind(s2) - - -@bench('("AB"*1000).rfind("AB")', "early match, two characters", 1000) -def rfind_test_quick_match_two_characters(STR): - s1 = STR("AB" * 1000) - s2 = STR("AB") - s1_rfind = s1.rfind - for x in _RANGE_1000: - s1_rfind(s2) - -@bench('("AB"*1000).rfind("BC")', "no match, two characters", 1000) -def rfind_test_no_match_two_character(STR): - s1 = STR("AB" * 1000) - s2 = STR("BC") - s1_rfind = s1.rfind - for x in _RANGE_1000: - s1_rfind(s2) - -@bench('("AB"*1000).rfind("CA")', "no match, two characters", 1000) -def rfind_test_no_match_two_character_bis(STR): - s1 = STR("AB" * 1000) - s2 = STR("CA") - s1_rfind = s1.rfind - for x in _RANGE_1000: - s1_rfind(s2) - -@bench('("C"+"AB"*300).rfind("CA")', "late match, two characters", 1000) -def rfind_test_slow_match_two_characters(STR): - s1 = STR("C" + "AB" * 300) - s2 = STR("CA") - s1_rfind = s1.rfind - for x in _RANGE_1000: - s1_rfind(s2) - -@bench('("BC"+"AB"*300).rfind("BC")', "late match, two characters", 1000) -def rfind_test_slow_match_two_characters_bis(STR): - s1 = STR("BC" + "AB" * 300) - s2 = STR("BC") - s1_rfind = s1.rfind - for x in _RANGE_1000: - s1_rfind(s2) - -@bench('s="ABC"*33; ("E"+s+("D"+s)*500).rfind("E"+s)', - "late match, 100 characters", 100) -def rfind_test_slow_match_100_characters(STR): - m = STR("ABC"*33) - d = STR("D") - e = STR("E") - s1 = e+m + (d+m)*500 - s2 = e+m - s1_rfind = s1.rfind - for x in _RANGE_100: - s1_rfind(s2) - -@bench('s="ABC"*33; (s+"E"+("D"+s)*500).rfind(s+"E")', - "late match, 100 characters", 100) -def rfind_test_slow_match_100_characters_bis(STR): - m = STR("ABC"*33) - d = STR("D") - e = STR("E") - s1 = m+e + (d+m)*500 - s2 = m+e - s1_rfind = s1.rfind - for x in _RANGE_100: - s1_rfind(s2) - - -#### Now with index. -# Skip the ones which fail because that would include exception overhead. - -@bench('("A"*1000).index("A")', "early match, single character", 1000) -def index_test_quick_match_single_character(STR): - s1 = STR("A" * 1000) - s2 = STR("A") - s1_index = s1.index - for x in _RANGE_1000: - s1_index(s2) - -@bench('("AB"*1000).index("AB")', "early match, two characters", 1000) -def index_test_quick_match_two_characters(STR): - s1 = STR("AB" * 1000) - s2 = STR("AB") - s1_index = s1.index - for x in _RANGE_1000: - s1_index(s2) - -@bench('("AB"*300+"C").index("BC")', "late match, two characters", 1000) -def index_test_slow_match_two_characters(STR): - s1 = STR("AB" * 300+"C") - s2 = STR("BC") - s1_index = s1.index - for x in _RANGE_1000: - s1_index(s2) - -@bench('s="ABC"*33; ((s+"D")*500+s+"E").index(s+"E")', - "late match, 100 characters", 100) -def index_test_slow_match_100_characters(STR): - m = STR("ABC"*33) - d = STR("D") - e = STR("E") - s1 = (m+d)*500 + m+e - s2 = m+e - s1_index = s1.index - for x in _RANGE_100: - s1_index(s2) - - -#### Same for rindex - -@bench('("A"*1000).rindex("A")', "early match, single character", 1000) -def rindex_test_quick_match_single_character(STR): - s1 = STR("A" * 1000) - s2 = STR("A") - s1_rindex = s1.rindex - for x in _RANGE_1000: - s1_rindex(s2) - -@bench('("AB"*1000).rindex("AB")', "early match, two characters", 1000) -def rindex_test_quick_match_two_characters(STR): - s1 = STR("AB" * 1000) - s2 = STR("AB") - s1_rindex = s1.rindex - for x in _RANGE_1000: - s1_rindex(s2) - -@bench('("C"+"AB"*300).rindex("CA")', "late match, two characters", 1000) -def rindex_test_slow_match_two_characters(STR): - s1 = STR("C" + "AB" * 300) - s2 = STR("CA") - s1_rindex = s1.rindex - for x in _RANGE_1000: - s1_rindex(s2) - -@bench('s="ABC"*33; ("E"+s+("D"+s)*500).rindex("E"+s)', - "late match, 100 characters", 100) -def rindex_test_slow_match_100_characters(STR): - m = STR("ABC"*33) - d = STR("D") - e = STR("E") - s1 = e + m + (d+m)*500 - s2 = e + m - s1_rindex = s1.rindex - for x in _RANGE_100: - s1_rindex(s2) - - -#### Same for partition - -@bench('("A"*1000).partition("A")', "early match, single character", 1000) -def partition_test_quick_match_single_character(STR): - s1 = STR("A" * 1000) - s2 = STR("A") - s1_partition = s1.partition - for x in _RANGE_1000: - s1_partition(s2) - -@bench('("A"*1000).partition("B")', "no match, single character", 1000) -def partition_test_no_match_single_character(STR): - s1 = STR("A" * 1000) - s2 = STR("B") - s1_partition = s1.partition - for x in _RANGE_1000: - s1_partition(s2) - - -@bench('("AB"*1000).partition("AB")', "early match, two characters", 1000) -def partition_test_quick_match_two_characters(STR): - s1 = STR("AB" * 1000) - s2 = STR("AB") - s1_partition = s1.partition - for x in _RANGE_1000: - s1_partition(s2) - -@bench('("AB"*1000).partition("BC")', "no match, two characters", 1000) -def partition_test_no_match_two_character(STR): - s1 = STR("AB" * 1000) - s2 = STR("BC") - s1_partition = s1.partition - for x in _RANGE_1000: - s1_partition(s2) - -@bench('("AB"*300+"C").partition("BC")', "late match, two characters", 1000) -def partition_test_slow_match_two_characters(STR): - s1 = STR("AB" * 300+"C") - s2 = STR("BC") - s1_partition = s1.partition - for x in _RANGE_1000: - s1_partition(s2) - -@bench('s="ABC"*33; ((s+"D")*500+s+"E").partition(s+"E")', - "late match, 100 characters", 100) -def partition_test_slow_match_100_characters(STR): - m = STR("ABC"*33) - d = STR("D") - e = STR("E") - s1 = (m+d)*500 + m+e - s2 = m+e - s1_partition = s1.partition - for x in _RANGE_100: - s1_partition(s2) - - -#### Same for rpartition - -@bench('("A"*1000).rpartition("A")', "early match, single character", 1000) -def rpartition_test_quick_match_single_character(STR): - s1 = STR("A" * 1000) - s2 = STR("A") - s1_rpartition = s1.rpartition - for x in _RANGE_1000: - s1_rpartition(s2) - -@bench('("A"*1000).rpartition("B")', "no match, single character", 1000) -def rpartition_test_no_match_single_character(STR): - s1 = STR("A" * 1000) - s2 = STR("B") - s1_rpartition = s1.rpartition - for x in _RANGE_1000: - s1_rpartition(s2) - - -@bench('("AB"*1000).rpartition("AB")', "early match, two characters", 1000) -def rpartition_test_quick_match_two_characters(STR): - s1 = STR("AB" * 1000) - s2 = STR("AB") - s1_rpartition = s1.rpartition - for x in _RANGE_1000: - s1_rpartition(s2) - -@bench('("AB"*1000).rpartition("BC")', "no match, two characters", 1000) -def rpartition_test_no_match_two_character(STR): - s1 = STR("AB" * 1000) - s2 = STR("BC") - s1_rpartition = s1.rpartition - for x in _RANGE_1000: - s1_rpartition(s2) - -@bench('("C"+"AB"*300).rpartition("CA")', "late match, two characters", 1000) -def rpartition_test_slow_match_two_characters(STR): - s1 = STR("C" + "AB" * 300) - s2 = STR("CA") - s1_rpartition = s1.rpartition - for x in _RANGE_1000: - s1_rpartition(s2) - -@bench('s="ABC"*33; ("E"+s+("D"+s)*500).rpartition("E"+s)', - "late match, 100 characters", 100) -def rpartition_test_slow_match_100_characters(STR): - m = STR("ABC"*33) - d = STR("D") - e = STR("E") - s1 = e + m + (d+m)*500 - s2 = e + m - s1_rpartition = s1.rpartition - for x in _RANGE_100: - s1_rpartition(s2) - - -#### Same for split(s, 1) - -@bench('("A"*1000).split("A", 1)', "early match, single character", 1000) -def split_test_quick_match_single_character(STR): - s1 = STR("A" * 1000) - s2 = STR("A") - s1_split = s1.split - for x in _RANGE_1000: - s1_split(s2, 1) - -@bench('("A"*1000).split("B", 1)', "no match, single character", 1000) -def split_test_no_match_single_character(STR): - s1 = STR("A" * 1000) - s2 = STR("B") - s1_split = s1.split - for x in _RANGE_1000: - s1_split(s2, 1) - - -@bench('("AB"*1000).split("AB", 1)', "early match, two characters", 1000) -def split_test_quick_match_two_characters(STR): - s1 = STR("AB" * 1000) - s2 = STR("AB") - s1_split = s1.split - for x in _RANGE_1000: - s1_split(s2, 1) - -@bench('("AB"*1000).split("BC", 1)', "no match, two characters", 1000) -def split_test_no_match_two_character(STR): - s1 = STR("AB" * 1000) - s2 = STR("BC") - s1_split = s1.split - for x in _RANGE_1000: - s1_split(s2, 1) - -@bench('("AB"*300+"C").split("BC", 1)', "late match, two characters", 1000) -def split_test_slow_match_two_characters(STR): - s1 = STR("AB" * 300+"C") - s2 = STR("BC") - s1_split = s1.split - for x in _RANGE_1000: - s1_split(s2, 1) - -@bench('s="ABC"*33; ((s+"D")*500+s+"E").split(s+"E", 1)', - "late match, 100 characters", 100) -def split_test_slow_match_100_characters(STR): - m = STR("ABC"*33) - d = STR("D") - e = STR("E") - s1 = (m+d)*500 + m+e - s2 = m+e - s1_split = s1.split - for x in _RANGE_100: - s1_split(s2, 1) - - -#### Same for rsplit(s, 1) - -@bench('("A"*1000).rsplit("A", 1)', "early match, single character", 1000) -def rsplit_test_quick_match_single_character(STR): - s1 = STR("A" * 1000) - s2 = STR("A") - s1_rsplit = s1.rsplit - for x in _RANGE_1000: - s1_rsplit(s2, 1) - -@bench('("A"*1000).rsplit("B", 1)', "no match, single character", 1000) -def rsplit_test_no_match_single_character(STR): - s1 = STR("A" * 1000) - s2 = STR("B") - s1_rsplit = s1.rsplit - for x in _RANGE_1000: - s1_rsplit(s2, 1) - - -@bench('("AB"*1000).rsplit("AB", 1)', "early match, two characters", 1000) -def rsplit_test_quick_match_two_characters(STR): - s1 = STR("AB" * 1000) - s2 = STR("AB") - s1_rsplit = s1.rsplit - for x in _RANGE_1000: - s1_rsplit(s2, 1) - -@bench('("AB"*1000).rsplit("BC", 1)', "no match, two characters", 1000) -def rsplit_test_no_match_two_character(STR): - s1 = STR("AB" * 1000) - s2 = STR("BC") - s1_rsplit = s1.rsplit - for x in _RANGE_1000: - s1_rsplit(s2, 1) - -@bench('("C"+"AB"*300).rsplit("CA", 1)', "late match, two characters", 1000) -def rsplit_test_slow_match_two_characters(STR): - s1 = STR("C" + "AB" * 300) - s2 = STR("CA") - s1_rsplit = s1.rsplit - for x in _RANGE_1000: - s1_rsplit(s2, 1) - -@bench('s="ABC"*33; ("E"+s+("D"+s)*500).rsplit("E"+s, 1)', - "late match, 100 characters", 100) -def rsplit_test_slow_match_100_characters(STR): - m = STR("ABC"*33) - d = STR("D") - e = STR("E") - s1 = e + m + (d+m)*500 - s2 = e + m - s1_rsplit = s1.rsplit - for x in _RANGE_100: - s1_rsplit(s2, 1) - - -#### Benchmark the operator-based methods - -@bench('"A"*10', "repeat 1 character 10 times", 1000) -def repeat_single_10_times(STR): - s = STR("A") - for x in _RANGE_1000: - s * 10 - -@bench('"A"*1000', "repeat 1 character 1000 times", 1000) -def repeat_single_1000_times(STR): - s = STR("A") - for x in _RANGE_1000: - s * 1000 - -@bench('"ABCDE"*10', "repeat 5 characters 10 times", 1000) -def repeat_5_10_times(STR): - s = STR("ABCDE") - for x in _RANGE_1000: - s * 10 - -@bench('"ABCDE"*1000', "repeat 5 characters 1000 times", 1000) -def repeat_5_1000_times(STR): - s = STR("ABCDE") - for x in _RANGE_1000: - s * 1000 - -# + for concat - -@bench('"Andrew"+"Dalke"', "concat two strings", 1000) -def concat_two_strings(STR): - s1 = STR("Andrew") - s2 = STR("Dalke") - for x in _RANGE_1000: - s1+s2 - -@bench('s1+s2+s3+s4+...+s20', "concat 20 strings of words length 4 to 15", - 1000) -def concat_many_strings(STR): - s1=STR('TIXSGYNREDCVBHJ') - s2=STR('PUMTLXBZVDO') - s3=STR('FVZNJ') - s4=STR('OGDXUW') - s5=STR('WEIMRNCOYVGHKB') - s6=STR('FCQTNMXPUZH') - s7=STR('TICZJYRLBNVUEAK') - s8=STR('REYB') - s9=STR('PWUOQ') - s10=STR('EQHCMKBS') - s11=STR('AEVDFOH') - s12=STR('IFHVD') - s13=STR('JGTCNLXWOHQ') - s14=STR('ITSKEPYLROZAWXF') - s15=STR('THEK') - s16=STR('GHPZFBUYCKMNJIT') - s17=STR('JMUZ') - s18=STR('WLZQMTB') - s19=STR('KPADCBW') - s20=STR('TNJHZQAGBU') - for x in _RANGE_1000: - (s1 + s2+ s3+ s4+ s5+ s6+ s7+ s8+ s9+s10+ - s11+s12+s13+s14+s15+s16+s17+s18+s19+s20) - - -#### Benchmark join - -def get_bytes_yielding_seq(STR, arg): - if STR is BYTES and sys.version_info >= (3,): - raise UnsupportedType - return STR(arg) - -@bench('"A".join("")', - "join empty string, with 1 character sep", 100) -def join_empty_single(STR): - sep = STR("A") - s2 = get_bytes_yielding_seq(STR, "") - sep_join = sep.join - for x in _RANGE_100: - sep_join(s2) - -@bench('"ABCDE".join("")', - "join empty string, with 5 character sep", 100) -def join_empty_5(STR): - sep = STR("ABCDE") - s2 = get_bytes_yielding_seq(STR, "") - sep_join = sep.join - for x in _RANGE_100: - sep_join(s2) - -@bench('"A".join("ABC..Z")', - "join string with 26 characters, with 1 character sep", 1000) -def join_alphabet_single(STR): - sep = STR("A") - s2 = get_bytes_yielding_seq(STR, "ABCDEFGHIJKLMnOPQRSTUVWXYZ") - sep_join = sep.join - for x in _RANGE_1000: - sep_join(s2) - -@bench('"ABCDE".join("ABC..Z")', - "join string with 26 characters, with 5 character sep", 1000) -def join_alphabet_5(STR): - sep = STR("ABCDE") - s2 = get_bytes_yielding_seq(STR, "ABCDEFGHIJKLMnOPQRSTUVWXYZ") - sep_join = sep.join - for x in _RANGE_1000: - sep_join(s2) - -@bench('"A".join(list("ABC..Z"))', - "join list of 26 characters, with 1 character sep", 1000) -def join_alphabet_list_single(STR): - sep = STR("A") - s2 = [STR(x) for x in "ABCDEFGHIJKLMnOPQRSTUVWXYZ"] - sep_join = sep.join - for x in _RANGE_1000: - sep_join(s2) - -@bench('"ABCDE".join(list("ABC..Z"))', - "join list of 26 characters, with 5 character sep", 1000) -def join_alphabet_list_five(STR): - sep = STR("ABCDE") - s2 = [STR(x) for x in "ABCDEFGHIJKLMnOPQRSTUVWXYZ"] - sep_join = sep.join - for x in _RANGE_1000: - sep_join(s2) - -@bench('"A".join(["Bob"]*100)', - "join list of 100 words, with 1 character sep", 1000) -def join_100_words_single(STR): - sep = STR("A") - s2 = [STR("Bob")]*100 - sep_join = sep.join - for x in _RANGE_1000: - sep_join(s2) - -@bench('"ABCDE".join(["Bob"]*100))', - "join list of 100 words, with 5 character sep", 1000) -def join_100_words_5(STR): - sep = STR("ABCDE") - s2 = [STR("Bob")]*100 - sep_join = sep.join - for x in _RANGE_1000: - sep_join(s2) - -#### split tests - -@bench('("Here are some words. "*2).split()', "split whitespace (small)", 1000) -def whitespace_split(STR): - s = STR("Here are some words. "*2) - s_split = s.split - for x in _RANGE_1000: - s_split() - -@bench('("Here are some words. "*2).rsplit()', "split whitespace (small)", 1000) -def whitespace_rsplit(STR): - s = STR("Here are some words. "*2) - s_rsplit = s.rsplit - for x in _RANGE_1000: - s_rsplit() - -@bench('("Here are some words. "*2).split(None, 1)', - "split 1 whitespace", 1000) -def whitespace_split_1(STR): - s = STR("Here are some words. "*2) - s_split = s.split - N = None - for x in _RANGE_1000: - s_split(N, 1) - -@bench('("Here are some words. "*2).rsplit(None, 1)', - "split 1 whitespace", 1000) -def whitespace_rsplit_1(STR): - s = STR("Here are some words. "*2) - s_rsplit = s.rsplit - N = None - for x in _RANGE_1000: - s_rsplit(N, 1) - -@bench('("Here are some words. "*2).partition(" ")', - "split 1 whitespace", 1000) -def whitespace_partition(STR): - sep = STR(" ") - s = STR("Here are some words. "*2) - s_partition = s.partition - for x in _RANGE_1000: - s_partition(sep) - -@bench('("Here are some words. "*2).rpartition(" ")', - "split 1 whitespace", 1000) -def whitespace_rpartition(STR): - sep = STR(" ") - s = STR("Here are some words. "*2) - s_rpartition = s.rpartition - for x in _RANGE_1000: - s_rpartition(sep) - -human_text = """\ -Python is a dynamic object-oriented programming language that can be -used for many kinds of software development. It offers strong support -for integration with other languages and tools, comes with extensive -standard libraries, and can be learned in a few days. Many Python -programmers report substantial productivity gains and feel the language -encourages the development of higher quality, more maintainable code. - -Python runs on Windows, Linux/Unix, Mac OS X, Amiga, Palm -Handhelds, and Nokia mobile phones. Python has also been ported to the -Java and .NET virtual machines. - -Python is distributed under an OSI-approved open source license that -makes it free to use, even for commercial products. -"""*25 -human_text_bytes = bytes_from_str(human_text) -human_text_unicode = unicode_from_str(human_text) -def _get_human_text(STR): - if STR is UNICODE: - return human_text_unicode - if STR is BYTES: - return human_text_bytes - raise AssertionError - -@bench('human_text.split()', "split whitespace (huge)", 10) -def whitespace_split_huge(STR): - s = _get_human_text(STR) - s_split = s.split - for x in _RANGE_10: - s_split() - -@bench('human_text.rsplit()', "split whitespace (huge)", 10) -def whitespace_rsplit_huge(STR): - s = _get_human_text(STR) - s_rsplit = s.rsplit - for x in _RANGE_10: - s_rsplit() - - - -@bench('"this\\nis\\na\\ntest\\n".split("\\n")', "split newlines", 1000) -def newlines_split(STR): - s = STR("this\nis\na\ntest\n") - s_split = s.split - nl = STR("\n") - for x in _RANGE_1000: - s_split(nl) - - -@bench('"this\\nis\\na\\ntest\\n".rsplit("\\n")', "split newlines", 1000) -def newlines_rsplit(STR): - s = STR("this\nis\na\ntest\n") - s_rsplit = s.rsplit - nl = STR("\n") - for x in _RANGE_1000: - s_rsplit(nl) - -@bench('"this\\nis\\na\\ntest\\n".splitlines()', "split newlines", 1000) -def newlines_splitlines(STR): - s = STR("this\nis\na\ntest\n") - s_splitlines = s.splitlines - for x in _RANGE_1000: - s_splitlines() - -## split text with 2000 newlines - -def _make_2000_lines(): - import random - r = random.Random(100) - chars = list(map(chr, range(32, 128))) - i = 0 - while i < len(chars): - chars[i] = " " - i += r.randrange(9) - s = "".join(chars) - s = s*4 - words = [] - for i in range(2000): - start = r.randrange(96) - n = r.randint(5, 65) - words.append(s[start:start+n]) - return "\n".join(words)+"\n" - -_text_with_2000_lines = _make_2000_lines() -_text_with_2000_lines_bytes = bytes_from_str(_text_with_2000_lines) -_text_with_2000_lines_unicode = unicode_from_str(_text_with_2000_lines) -def _get_2000_lines(STR): - if STR is UNICODE: - return _text_with_2000_lines_unicode - if STR is BYTES: - return _text_with_2000_lines_bytes - raise AssertionError - - -@bench('"...text...".split("\\n")', "split 2000 newlines", 10) -def newlines_split_2000(STR): - s = _get_2000_lines(STR) - s_split = s.split - nl = STR("\n") - for x in _RANGE_10: - s_split(nl) - -@bench('"...text...".rsplit("\\n")', "split 2000 newlines", 10) -def newlines_rsplit_2000(STR): - s = _get_2000_lines(STR) - s_rsplit = s.rsplit - nl = STR("\n") - for x in _RANGE_10: - s_rsplit(nl) - -@bench('"...text...".splitlines()', "split 2000 newlines", 10) -def newlines_splitlines_2000(STR): - s = _get_2000_lines(STR) - s_splitlines = s.splitlines - for x in _RANGE_10: - s_splitlines() - - -## split text on "--" characters -@bench( - '"this--is--a--test--of--the--emergency--broadcast--system".split("--")', - "split on multicharacter separator (small)", 1000) -def split_multichar_sep_small(STR): - s = STR("this--is--a--test--of--the--emergency--broadcast--system") - s_split = s.split - pat = STR("--") - for x in _RANGE_1000: - s_split(pat) -@bench( - '"this--is--a--test--of--the--emergency--broadcast--system".rsplit("--")', - "split on multicharacter separator (small)", 1000) -def rsplit_multichar_sep_small(STR): - s = STR("this--is--a--test--of--the--emergency--broadcast--system") - s_rsplit = s.rsplit - pat = STR("--") - for x in _RANGE_1000: - s_rsplit(pat) - -## split dna text on "ACTAT" characters -@bench('dna.split("ACTAT")', - "split on multicharacter separator (dna)", 10) -def split_multichar_sep_dna(STR): - s = _get_dna(STR) - s_split = s.split - pat = STR("ACTAT") - for x in _RANGE_10: - s_split(pat) - -@bench('dna.rsplit("ACTAT")', - "split on multicharacter separator (dna)", 10) -def rsplit_multichar_sep_dna(STR): - s = _get_dna(STR) - s_rsplit = s.rsplit - pat = STR("ACTAT") - for x in _RANGE_10: - s_rsplit(pat) - - - -## split with limits - -GFF3_example = "\t".join([ - "I", "Genomic_canonical", "region", "357208", "396183", ".", "+", ".", - "ID=Sequence:R119;note=Clone R119%3B Genbank AF063007;Name=R119"]) - -@bench('GFF3_example.split("\\t")', "tab split", 1000) -def tab_split_no_limit(STR): - sep = STR("\t") - s = STR(GFF3_example) - s_split = s.split - for x in _RANGE_1000: - s_split(sep) - -@bench('GFF3_example.split("\\t", 8)', "tab split", 1000) -def tab_split_limit(STR): - sep = STR("\t") - s = STR(GFF3_example) - s_split = s.split - for x in _RANGE_1000: - s_split(sep, 8) - -@bench('GFF3_example.rsplit("\\t")', "tab split", 1000) -def tab_rsplit_no_limit(STR): - sep = STR("\t") - s = STR(GFF3_example) - s_rsplit = s.rsplit - for x in _RANGE_1000: - s_rsplit(sep) - -@bench('GFF3_example.rsplit("\\t", 8)', "tab split", 1000) -def tab_rsplit_limit(STR): - sep = STR("\t") - s = STR(GFF3_example) - s_rsplit = s.rsplit - for x in _RANGE_1000: - s_rsplit(sep, 8) - -#### Count characters - -@bench('...text.with.2000.newlines.count("\\n")', - "count newlines", 10) -def count_newlines(STR): - s = _get_2000_lines(STR) - s_count = s.count - nl = STR("\n") - for x in _RANGE_10: - s_count(nl) - -# Orchid sequences concatenated, from Biopython -_dna = """ -CGTAACAAGGTTTCCGTAGGTGAACCTGCGGAAGGATCATTGTTGAGATCACATAATAATTGATCGGGTT -AATCTGGAGGATCTGTTTACTTTGGTCACCCATGAGCATTTGCTGTTGAAGTGACCTAGAATTGCCATCG -AGCCTCCTTGGGAGCTTTCTTGTTGGCGAGATCTAAACCCTTGCCCGGCGCAGTTTTGCTCCAAGTCGTT -TGACACATAATTGGTGAAGGGGGTGGCATCCTTCCCTGACCCTCCCCCAACTATTTTTTTAACAACTCTC -AGCAACGGAGACTCAGTCTTCGGCAAATGCGATAAATGGTGTGAATTGCAGAATCCCGTGCACCATCGAG -TCTTTGAACGCAAGTTGCGCCCGAGGCCATCAGGCCAAGGGCACGCCTGCCTGGGCATTGCGAGTCATAT -CTCTCCCTTAACGAGGCTGTCCATACATACTGTTCAGCCGGTGCGGATGTGAGTTTGGCCCCTTGTTCTT -TGGTACGGGGGGTCTAAGAGCTGCATGGGCTTTTGATGGTCCTAAATACGGCAAGAGGTGGACGAACTAT -GCTACAACAAAATTGTTGTGCAGAGGCCCCGGGTTGTCGTATTAGATGGGCCACCGTAATCTGAAGACCC -TTTTGAACCCCATTGGAGGCCCATCAACCCATGATCAGTTGATGGCCATTTGGTTGCGACCCCAGGTCAG -GTGAGCAACAGCTGTCGTAACAAGGTTTCCGTAGGGTGAACTGCGGAAGGATCATTGTTGAGATCACATA -ATAATTGATCGAGTTAATCTGGAGGATCTGTTTACTTGGGTCACCCATGGGCATTTGCTGTTGAAGTGAC -CTAGATTTGCCATCGAGCCTCCTTGGGAGCATCCTTGTTGGCGATATCTAAACCCTCAATTTTTCCCCCA -ATCAAATTACACAAAATTGGTGGAGGGGGTGGCATTCTTCCCTTACCCTCCCCCAAATATTTTTTTAACA -ACTCTCAGCAACGGATATCTCAGCTCTTGCATCGATGAAGAACCCACCGAAATGCGATAAATGGTGTGAA -TTGCAGAATCCCGTGAACCATCGAGTCTTTGAACGCAAGTTGCGCCCGAGGCCATCAGGCCAAGGGCACG -CCTGCCTGGGCATTGCGAGTCATATCTCTCCCTTAACGAGGCTGTCCATACATACTGTTCAGCCGGTGCG -GATGTGAGTTTGGCCCCTTGTTCTTTGGTACGGGGGGTCTAAGAGATGCATGGGCTTTTGATGGTCCTAA -ATACGGCAAGAGGTGGACGAACTATGCTACAACAAAATTGTTGTGCAAAGGCCCCGGGTTGTCGTATAAG -ATGGGCCACCGATATCTGAAGACCCTTTTGGACCCCATTGGAGCCCATCAACCCATGTCAGTTGATGGCC -ATTCGTAACAAGGTTTCCGTAGGTGAACCTGCGGAAGGATCATTGTTGAGATCACATAATAATTGATCGA -GTTAATCTGGAGGATCTGTTTACTTGGGTCACCCATGGGCATTTGCTGTTGAAGTGACCTAGATTTGCCA -TCGAGCCTCCTTGGGAGCTTTCTTGTTGGCGATATCTAAACCCTTGCCCGGCAGAGTTTTGGGAATCCCG -TGAACCATCGAGTCTTTGAACGCAAGTTGCGCCCGAGGCCATCAGGCCAAGGGCACGCCTGCCTGGGCAT -TGCGAGTCATATCTCTCCCTTAACGAGGCTGTCCATACACACCTGTTCAGCCGGTGCGGATGTGAGTTTG -GCCCCTTGTTCTTTGGTACGGGGGGTCTAAGAGCTGCATGGGCTTTTGATGGTCCTAAATACGGCAAGAG -GTGGACGAACTATGCTACAACAAAATTGTTGTGCAAAGGCCCCGGGTTGTCGTATTAGATGGGCCACCAT -AATCTGAAGACCCTTTTGAACCCCATTGGAGGCCCATCAACCCATGATCAGTTGATGGCCATTTGGTTGC -GACCCAGTCAGGTGAGGGTAGGTGAACCTGCGGAAGGATCATTGTTGAGATCACATAATAATTGATCGAG -TTAATCTGGAGGATCTGTTTACTTTGGTCACCCATGGGCATTTGCTGTTGAAGTGACCTAGATTTGCCAT -CGAGCCTCCTTGGGAGCTTTCTTGTTGGCGAGATCTAAACCCTTGCCCGGCGGAGTTTGGCGCCAAGTCA -TATGACACATAATTGGTGAAGGGGGTGGCATCCTGCCCTGACCCTCCCCAAATTATTTTTTTAACAACTC -TCAGCAACGGATATCTCGGCTCTTGCATCGATGAAGAACGCAGCGAAATGCGATAAATGGTGTGAATTGC -AGAATCCCGTGAACCATCGAGTCTTTGGAACGCAAGTTGCGCCCGAGGCCATCAGGCCAAGGGCACGCCT -GCCTGGGCATTGGGAATCATATCTCTCCCCTAACGAGGCTATCCAAACATACTGTTCATCCGGTGCGGAT -GTGAGTTTGGCCCCTTGTTCTTTGGTACCGGGGGTCTAAGAGCTGCATGGGCATTTGATGGTCCTCAAAA -CGGCAAGAGGTGGACGAACTATGCCACAACAAAATTGTTGTCCCAAGGCCCCGGGTTGTCGTATTAGATG -GGCCACCGTAACCTGAAGACCCTTTTGAACCCCATTGGAGGCCCATCAACCCATGATCAGTTGATGACCA -TTTGTTGCGACCCCAGTCAGCTGAGCAACCCGCTGAGTGGAAGGTCATTGCCGATATCACATAATAATTG -ATCGAGTTAATCTGGAGGATCTGTTTACTTGGTCACCCATGAGCATTTGCTGTTGAAGTGACCTAGATTT -GCCATCGAGCCTCCTTGGGAGTTTTCTTGTTGGCGAGATCTAAACCCTTGCCCGGCGGAGTTGTGCGCCA -AGTCATATGACACATAATTGGTGAAGGGGGTGGCATCCTGCCCTGACCCTCCCCAAATTATTTTTTTAAC -AACTCTCAGCAACGGATATCTCGGCTCTTGCATCGATGAAGAACGCAGCGAAATGCGATAAATGGTGTGA -ATTGCAGAATCCCGTGAACCATCGAGTCTTTGAACGCAAGTTGCGCCCGAGGCCATCAGGCCAAGGGCAC -GCCTGCCTGGGCATTGCGAGTCATATCTCTCCCTTAACGAGGCTGTCCATACATACTGTTCATCCGGTGC -GGATGTGAGTTTGGCCCCTTGTTCTTTGGTACGGGGGGTCTAAGAGCTGCATGGGCATTTGATGGTCCTC -AAAACGGCAAGAGGTGGACGAACTATGCTACAACCAAATTGTTGTCCCAAGGCCCCGGGTTGTCGTATTA -GATGGGCCACCGTAACCTGAAGACCCTTTTGAACCCCATTGGAGGCCCATCAACCCATGATCAGTTGATG -ACCATGTGTTGCGACCCCAGTCAGCTGAGCAACGCGCTGAGCGTAACAAGGTTTCCGTAGGTGGACCTCC -GGGAGGATCATTGTTGAGATCACATAATAATTGATCGAGGTAATCTGGAGGATCTGCATATTTTGGTCAC -""" -_dna = "".join(_dna.splitlines()) -_dna = _dna * 25 -_dna_bytes = bytes_from_str(_dna) -_dna_unicode = unicode_from_str(_dna) - -def _get_dna(STR): - if STR is UNICODE: - return _dna_unicode - if STR is BYTES: - return _dna_bytes - raise AssertionError - -@bench('dna.count("AACT")', "count AACT substrings in DNA example", 10) -def count_aact(STR): - seq = _get_dna(STR) - seq_count = seq.count - needle = STR("AACT") - for x in _RANGE_10: - seq_count(needle) - -##### startswith and endswith - -@bench('"Andrew".startswith("A")', 'startswith single character', 1000) -def startswith_single(STR): - s1 = STR("Andrew") - s2 = STR("A") - s1_startswith = s1.startswith - for x in _RANGE_1000: - s1_startswith(s2) - -@bench('"Andrew".startswith("Andrew")', 'startswith multiple characters', - 1000) -def startswith_multiple(STR): - s1 = STR("Andrew") - s2 = STR("Andrew") - s1_startswith = s1.startswith - for x in _RANGE_1000: - s1_startswith(s2) - -@bench('"Andrew".startswith("Anders")', - 'startswith multiple characters - not!', 1000) -def startswith_multiple_not(STR): - s1 = STR("Andrew") - s2 = STR("Anders") - s1_startswith = s1.startswith - for x in _RANGE_1000: - s1_startswith(s2) - - -# endswith - -@bench('"Andrew".endswith("w")', 'endswith single character', 1000) -def endswith_single(STR): - s1 = STR("Andrew") - s2 = STR("w") - s1_endswith = s1.endswith - for x in _RANGE_1000: - s1_endswith(s2) - -@bench('"Andrew".endswith("Andrew")', 'endswith multiple characters', 1000) -def endswith_multiple(STR): - s1 = STR("Andrew") - s2 = STR("Andrew") - s1_endswith = s1.endswith - for x in _RANGE_1000: - s1_endswith(s2) - -@bench('"Andrew".endswith("Anders")', - 'endswith multiple characters - not!', 1000) -def endswith_multiple_not(STR): - s1 = STR("Andrew") - s2 = STR("Anders") - s1_endswith = s1.endswith - for x in _RANGE_1000: - s1_endswith(s2) - -#### Strip - -@bench('"Hello!\\n".strip()', 'strip terminal newline', 1000) -def terminal_newline_strip_right(STR): - s = STR("Hello!\n") - s_strip = s.strip - for x in _RANGE_1000: - s_strip() - -@bench('"Hello!\\n".rstrip()', 'strip terminal newline', 1000) -def terminal_newline_rstrip(STR): - s = STR("Hello!\n") - s_rstrip = s.rstrip - for x in _RANGE_1000: - s_rstrip() - -@bench('"\\nHello!".strip()', 'strip terminal newline', 1000) -def terminal_newline_strip_left(STR): - s = STR("\nHello!") - s_strip = s.strip - for x in _RANGE_1000: - s_strip() - -@bench('"\\nHello!\\n".strip()', 'strip terminal newline', 1000) -def terminal_newline_strip_both(STR): - s = STR("\nHello!\n") - s_strip = s.strip - for x in _RANGE_1000: - s_strip() - -@bench('"\\nHello!".rstrip()', 'strip terminal newline', 1000) -def terminal_newline_lstrip(STR): - s = STR("\nHello!") - s_lstrip = s.lstrip - for x in _RANGE_1000: - s_lstrip() - -@bench('s="Hello!\\n"; s[:-1] if s[-1]=="\\n" else s', - 'strip terminal newline', 1000) -def terminal_newline_if_else(STR): - s = STR("Hello!\n") - NL = STR("\n") - for x in _RANGE_1000: - s[:-1] if (s[-1] == NL) else s - - -# Strip multiple spaces or tabs - -@bench('"Hello\\t \\t".strip()', 'strip terminal spaces and tabs', 1000) -def terminal_space_strip(STR): - s = STR("Hello\t \t!") - s_strip = s.strip - for x in _RANGE_1000: - s_strip() - -@bench('"Hello\\t \\t".rstrip()', 'strip terminal spaces and tabs', 1000) -def terminal_space_rstrip(STR): - s = STR("Hello!\t \t") - s_rstrip = s.rstrip - for x in _RANGE_1000: - s_rstrip() - -@bench('"\\t \\tHello".rstrip()', 'strip terminal spaces and tabs', 1000) -def terminal_space_lstrip(STR): - s = STR("\t \tHello!") - s_lstrip = s.lstrip - for x in _RANGE_1000: - s_lstrip() - - -#### replace -@bench('"This is a test".replace(" ", "\\t")', 'replace single character', - 1000) -def replace_single_character(STR): - s = STR("This is a test!") - from_str = STR(" ") - to_str = STR("\t") - s_replace = s.replace - for x in _RANGE_1000: - s_replace(from_str, to_str) - -@uses_re -@bench('re.sub(" ", "\\t", "This is a test"', 'replace single character', - 1000) -def replace_single_character_re(STR): - s = STR("This is a test!") - pat = re.compile(STR(" ")) - to_str = STR("\t") - pat_sub = pat.sub - for x in _RANGE_1000: - pat_sub(to_str, s) - -@bench('"...text.with.2000.lines...replace("\\n", " ")', - 'replace single character, big string', 10) -def replace_single_character_big(STR): - s = _get_2000_lines(STR) - from_str = STR("\n") - to_str = STR(" ") - s_replace = s.replace - for x in _RANGE_10: - s_replace(from_str, to_str) - -@uses_re -@bench('re.sub("\\n", " ", "...text.with.2000.lines...")', - 'replace single character, big string', 10) -def replace_single_character_big_re(STR): - s = _get_2000_lines(STR) - pat = re.compile(STR("\n")) - to_str = STR(" ") - pat_sub = pat.sub - for x in _RANGE_10: - pat_sub(to_str, s) - - -@bench('dna.replace("ATC", "ATT")', - 'replace multiple characters, dna', 10) -def replace_multiple_characters_dna(STR): - seq = _get_dna(STR) - from_str = STR("ATC") - to_str = STR("ATT") - seq_replace = seq.replace - for x in _RANGE_10: - seq_replace(from_str, to_str) - -# This increases the character count -@bench('"...text.with.2000.newlines...replace("\\n", "\\r\\n")', - 'replace and expand multiple characters, big string', 10) -def replace_multiple_character_big(STR): - s = _get_2000_lines(STR) - from_str = STR("\n") - to_str = STR("\r\n") - s_replace = s.replace - for x in _RANGE_10: - s_replace(from_str, to_str) - - -# This decreases the character count -@bench('"When shall we three meet again?".replace("ee", "")', - 'replace/remove multiple characters', 1000) -def replace_multiple_character_remove(STR): - s = STR("When shall we three meet again?") - from_str = STR("ee") - to_str = STR("") - s_replace = s.replace - for x in _RANGE_1000: - s_replace(from_str, to_str) - - -big_s = "A" + ("Z"*128*1024) -big_s_bytes = bytes_from_str(big_s) -big_s_unicode = unicode_from_str(big_s) -def _get_big_s(STR): - if STR is UNICODE: return big_s_unicode - if STR is BYTES: return big_s_bytes - raise AssertionError - -# The older replace implementation counted all matches in -# the string even when it only needed to make one replacement. -@bench('("A" + ("Z"*128*1024)).replace("A", "BB", 1)', - 'quick replace single character match', 10) -def quick_replace_single_match(STR): - s = _get_big_s(STR) - from_str = STR("A") - to_str = STR("BB") - s_replace = s.replace - for x in _RANGE_10: - s_replace(from_str, to_str, 1) - -@bench('("A" + ("Z"*128*1024)).replace("AZZ", "BBZZ", 1)', - 'quick replace multiple character match', 10) -def quick_replace_multiple_match(STR): - s = _get_big_s(STR) - from_str = STR("AZZ") - to_str = STR("BBZZ") - s_replace = s.replace - for x in _RANGE_10: - s_replace(from_str, to_str, 1) - - -#### - -# CCP does a lot of this, for internationalisation of ingame messages. -_format = "The %(thing)s is %(place)s the %(location)s." -_format_dict = { "thing":"THING", "place":"PLACE", "location":"LOCATION", } -_format_bytes = bytes_from_str(_format) -_format_unicode = unicode_from_str(_format) -_format_dict_bytes = dict((bytes_from_str(k), bytes_from_str(v)) for (k,v) in _format_dict.items()) -_format_dict_unicode = dict((unicode_from_str(k), unicode_from_str(v)) for (k,v) in _format_dict.items()) - -def _get_format(STR): - if STR is UNICODE: - return _format_unicode - if STR is BYTES: - if sys.version_info >= (3,): - raise UnsupportedType - return _format_bytes - raise AssertionError - -def _get_format_dict(STR): - if STR is UNICODE: - return _format_dict_unicode - if STR is BYTES: - if sys.version_info >= (3,): - raise UnsupportedType - return _format_dict_bytes - raise AssertionError - -# Formatting. -@bench('"The %(k1)s is %(k2)s the %(k3)s."%{"k1":"x","k2":"y","k3":"z",}', - 'formatting a string type with a dict', 1000) -def format_with_dict(STR): - s = _get_format(STR) - d = _get_format_dict(STR) - for x in _RANGE_1000: - s % d - - -#### Upper- and lower- case conversion - -@bench('("Where in the world is Carmen San Deigo?"*10).lower()', - "case conversion -- rare", 1000) -def lower_conversion_rare(STR): - s = STR("Where in the world is Carmen San Deigo?"*10) - s_lower = s.lower - for x in _RANGE_1000: - s_lower() - -@bench('("WHERE IN THE WORLD IS CARMEN SAN DEIGO?"*10).lower()', - "case conversion -- dense", 1000) -def lower_conversion_dense(STR): - s = STR("WHERE IN THE WORLD IS CARMEN SAN DEIGO?"*10) - s_lower = s.lower - for x in _RANGE_1000: - s_lower() - - -@bench('("wHERE IN THE WORLD IS cARMEN sAN dEIGO?"*10).upper()', - "case conversion -- rare", 1000) -def upper_conversion_rare(STR): - s = STR("Where in the world is Carmen San Deigo?"*10) - s_upper = s.upper - for x in _RANGE_1000: - s_upper() - -@bench('("where in the world is carmen san deigo?"*10).upper()', - "case conversion -- dense", 1000) -def upper_conversion_dense(STR): - s = STR("where in the world is carmen san deigo?"*10) - s_upper = s.upper - for x in _RANGE_1000: - s_upper() - - -# end of benchmarks - -################# - -class BenchTimer(timeit.Timer): - def best(self, repeat=1): - for i in range(1, 10): - number = 10**i - x = self.timeit(number) - if x > 0.02: - break - times = [x] - for i in range(1, repeat): - times.append(self.timeit(number)) - return min(times) / number - -def main(): - (options, test_names) = parser.parse_args() - if options.bytes_only and options.unicode_only: - raise SystemExit("Only one of --8-bit and --unicode are allowed") - - bench_functions = [] - for (k,v) in globals().items(): - if hasattr(v, "is_bench"): - if test_names: - for name in test_names: - if name in v.group: - break - else: - # Not selected, ignore - continue - if options.skip_re and hasattr(v, "uses_re"): - continue - - bench_functions.append( (v.group, k, v) ) - bench_functions.sort() - - p("bytes\tunicode") - p("(in ms)\t(in ms)\t%\tcomment") - - bytes_total = uni_total = 0.0 - - for title, group in itertools.groupby(bench_functions, - operator.itemgetter(0)): - # Flush buffer before each group - sys.stdout.flush() - p("="*10, title) - for (_, k, v) in group: - if hasattr(v, "is_bench"): - bytes_time = 0.0 - bytes_time_s = " - " - if not options.unicode_only: - try: - bytes_time = BenchTimer("__main__.%s(__main__.BYTES)" % (k,), - "import __main__").best(REPEAT) - bytes_time_s = "%.2f" % (1000 * bytes_time) - bytes_total += bytes_time - except UnsupportedType: - bytes_time_s = "N/A" - uni_time = 0.0 - uni_time_s = " - " - if not options.bytes_only: - try: - uni_time = BenchTimer("__main__.%s(__main__.UNICODE)" % (k,), - "import __main__").best(REPEAT) - uni_time_s = "%.2f" % (1000 * uni_time) - uni_total += uni_time - except UnsupportedType: - uni_time_s = "N/A" - try: - average = bytes_time/uni_time - except (TypeError, ZeroDivisionError): - average = 0.0 - p("%s\t%s\t%.1f\t%s (*%d)" % ( - bytes_time_s, uni_time_s, 100.*average, - v.comment, v.repeat_count)) - - if bytes_total == uni_total == 0.0: - p("That was zippy!") - else: - try: - ratio = bytes_total/uni_total - except ZeroDivisionError: - ratio = 0.0 - p("%.2f\t%.2f\t%.1f\t%s" % ( - 1000*bytes_total, 1000*uni_total, 100.*ratio, - "TOTAL")) - -if __name__ == "__main__": - main() From 090dd21ab9379d6a2a6923d6cbab697355fb7165 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sat, 17 Feb 2024 23:18:30 +0200 Subject: [PATCH 341/507] gh-115618: Remove improper Py_XDECREFs in property methods (GH-115619) --- Lib/test/test_property.py | 18 ++++++++++++++++++ ...4-02-17-18-47-12.gh-issue-115618.napiNp.rst | 3 +++ Objects/descrobject.c | 3 --- 3 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-17-18-47-12.gh-issue-115618.napiNp.rst diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index 8ace9fd17ab96e7..ad5ab5a87b5a663 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -183,6 +183,24 @@ def test_refleaks_in___init__(self): fake_prop.__init__('fget', 'fset', 'fdel', 'doc') self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10) + @support.refcount_test + def test_gh_115618(self): + # Py_XDECREF() was improperly called for None argument + # in property methods. + gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount') + prop = property() + refs_before = gettotalrefcount() + for i in range(100): + prop = prop.getter(None) + self.assertIsNone(prop.fget) + for i in range(100): + prop = prop.setter(None) + self.assertIsNone(prop.fset) + for i in range(100): + prop = prop.deleter(None) + self.assertIsNone(prop.fdel) + self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10) + def test_property_set_name_incorrect_args(self): p = property() diff --git a/Misc/NEWS.d/next/Library/2024-02-17-18-47-12.gh-issue-115618.napiNp.rst b/Misc/NEWS.d/next/Library/2024-02-17-18-47-12.gh-issue-115618.napiNp.rst new file mode 100644 index 000000000000000..cb4b147d5dc6639 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-17-18-47-12.gh-issue-115618.napiNp.rst @@ -0,0 +1,3 @@ +Fix improper decreasing the reference count for ``None`` argument in +:class:`property` methods :meth:`~property.getter`, :meth:`~property.setter` +and :meth:`~property.deleter`. diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 805de2971ba475b..c4cd51bdae45ab5 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1730,15 +1730,12 @@ property_copy(PyObject *old, PyObject *get, PyObject *set, PyObject *del) return NULL; if (get == NULL || get == Py_None) { - Py_XDECREF(get); get = pold->prop_get ? pold->prop_get : Py_None; } if (set == NULL || set == Py_None) { - Py_XDECREF(set); set = pold->prop_set ? pold->prop_set : Py_None; } if (del == NULL || del == Py_None) { - Py_XDECREF(del); del = pold->prop_del ? pold->prop_del : Py_None; } if (pold->getter_doc && get != Py_None) { From f9154f8f237e31e7c30f8698f980bee5e494f1e0 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Sun, 18 Feb 2024 10:27:14 +0300 Subject: [PATCH 342/507] gh-108303: Move `Lib/test/sortperf.py` to `Tools/scripts` (#114687) --- Lib/test/sortperf.py | 169 -------------------------------- Tools/scripts/sortperf.py | 196 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+), 169 deletions(-) delete mode 100644 Lib/test/sortperf.py create mode 100644 Tools/scripts/sortperf.py diff --git a/Lib/test/sortperf.py b/Lib/test/sortperf.py deleted file mode 100644 index 14a9d827ed57c56..000000000000000 --- a/Lib/test/sortperf.py +++ /dev/null @@ -1,169 +0,0 @@ -"""Sort performance test. - -See main() for command line syntax. -See tabulate() for output format. - -""" - -import sys -import time -import random -import marshal -import tempfile -import os - -td = tempfile.gettempdir() - -def randfloats(n): - """Return a list of n random floats in [0, 1).""" - # Generating floats is expensive, so this writes them out to a file in - # a temp directory. If the file already exists, it just reads them - # back in and shuffles them a bit. - fn = os.path.join(td, "rr%06d" % n) - try: - fp = open(fn, "rb") - except OSError: - r = random.random - result = [r() for i in range(n)] - try: - try: - fp = open(fn, "wb") - marshal.dump(result, fp) - fp.close() - fp = None - finally: - if fp: - try: - os.unlink(fn) - except OSError: - pass - except OSError as msg: - print("can't write", fn, ":", msg) - else: - result = marshal.load(fp) - fp.close() - # Shuffle it a bit... - for i in range(10): - i = random.randrange(n) - temp = result[:i] - del result[:i] - temp.reverse() - result.extend(temp) - del temp - assert len(result) == n - return result - -def flush(): - sys.stdout.flush() - -def doit(L): - t0 = time.perf_counter() - L.sort() - t1 = time.perf_counter() - print("%6.2f" % (t1-t0), end=' ') - flush() - -def tabulate(r): - r"""Tabulate sort speed for lists of various sizes. - - The sizes are 2**i for i in r (the argument, a list). - - The output displays i, 2**i, and the time to sort arrays of 2**i - floating point numbers with the following properties: - - *sort: random data - \sort: descending data - /sort: ascending data - 3sort: ascending, then 3 random exchanges - +sort: ascending, then 10 random at the end - %sort: ascending, then randomly replace 1% of the elements w/ random values - ~sort: many duplicates - =sort: all equal - !sort: worst case scenario - - """ - cases = tuple([ch + "sort" for ch in r"*\/3+%~=!"]) - fmt = ("%2s %7s" + " %6s"*len(cases)) - print(fmt % (("i", "2**i") + cases)) - for i in r: - n = 1 << i - L = randfloats(n) - print("%2d %7d" % (i, n), end=' ') - flush() - doit(L) # *sort - L.reverse() - doit(L) # \sort - doit(L) # /sort - - # Do 3 random exchanges. - for dummy in range(3): - i1 = random.randrange(n) - i2 = random.randrange(n) - L[i1], L[i2] = L[i2], L[i1] - doit(L) # 3sort - - # Replace the last 10 with random floats. - if n >= 10: - L[-10:] = [random.random() for dummy in range(10)] - doit(L) # +sort - - # Replace 1% of the elements at random. - for dummy in range(n // 100): - L[random.randrange(n)] = random.random() - doit(L) # %sort - - # Arrange for lots of duplicates. - if n > 4: - del L[4:] - L = L * (n // 4) - # Force the elements to be distinct objects, else timings can be - # artificially low. - L = list(map(lambda x: --x, L)) - doit(L) # ~sort - del L - - # All equal. Again, force the elements to be distinct objects. - L = list(map(abs, [-0.5] * n)) - doit(L) # =sort - del L - - # This one looks like [3, 2, 1, 0, 0, 1, 2, 3]. It was a bad case - # for an older implementation of quicksort, which used the median - # of the first, last and middle elements as the pivot. - half = n // 2 - L = list(range(half - 1, -1, -1)) - L.extend(range(half)) - # Force to float, so that the timings are comparable. This is - # significantly faster if we leave them as ints. - L = list(map(float, L)) - doit(L) # !sort - print() - -def main(): - """Main program when invoked as a script. - - One argument: tabulate a single row. - Two arguments: tabulate a range (inclusive). - Extra arguments are used to seed the random generator. - - """ - # default range (inclusive) - k1 = 15 - k2 = 20 - if sys.argv[1:]: - # one argument: single point - k1 = k2 = int(sys.argv[1]) - if sys.argv[2:]: - # two arguments: specify range - k2 = int(sys.argv[2]) - if sys.argv[3:]: - # derive random seed from remaining arguments - x = 1 - for a in sys.argv[3:]: - x = 69069 * x + hash(a) - random.seed(x) - r = range(k1, k2+1) # include the end point - tabulate(r) - -if __name__ == '__main__': - main() diff --git a/Tools/scripts/sortperf.py b/Tools/scripts/sortperf.py new file mode 100644 index 000000000000000..b54681524ac1731 --- /dev/null +++ b/Tools/scripts/sortperf.py @@ -0,0 +1,196 @@ +""" +List sort performance test. + +To install `pyperf` you would need to: + + python3 -m pip install pyperf + +To run: + + python3 Tools/scripts/sortperf + +Options: + + * `benchmark` name to run + * `--rnd-seed` to set random seed + * `--size` to set the sorted list size + +Based on https://github.com/python/cpython/blob/963904335e579bfe39101adf3fd6a0cf705975ff/Lib/test/sortperf.py +""" + +from __future__ import annotations + +import argparse +import time +import random + + +# =============== +# Data generation +# =============== + +def _random_data(size: int, rand: random.Random) -> list[float]: + result = [rand.random() for _ in range(size)] + # Shuffle it a bit... + for i in range(10): + i = rand.randrange(size) + temp = result[:i] + del result[:i] + temp.reverse() + result.extend(temp) + del temp + assert len(result) == size + return result + + +def list_sort(size: int, rand: random.Random) -> list[float]: + return _random_data(size, rand) + + +def list_sort_descending(size: int, rand: random.Random) -> list[float]: + return list(reversed(list_sort_ascending(size, rand))) + + +def list_sort_ascending(size: int, rand: random.Random) -> list[float]: + return sorted(_random_data(size, rand)) + + +def list_sort_ascending_exchanged(size: int, rand: random.Random) -> list[float]: + result = list_sort_ascending(size, rand) + # Do 3 random exchanges. + for _ in range(3): + i1 = rand.randrange(size) + i2 = rand.randrange(size) + result[i1], result[i2] = result[i2], result[i1] + return result + + +def list_sort_ascending_random(size: int, rand: random.Random) -> list[float]: + assert size >= 10, "This benchmark requires size to be >= 10" + result = list_sort_ascending(size, rand) + # Replace the last 10 with random floats. + result[-10:] = [rand.random() for _ in range(10)] + return result + + +def list_sort_ascending_one_percent(size: int, rand: random.Random) -> list[float]: + result = list_sort_ascending(size, rand) + # Replace 1% of the elements at random. + for _ in range(size // 100): + result[rand.randrange(size)] = rand.random() + return result + + +def list_sort_duplicates(size: int, rand: random.Random) -> list[float]: + assert size >= 4 + result = list_sort_ascending(4, rand) + # Arrange for lots of duplicates. + result = result * (size // 4) + # Force the elements to be distinct objects, else timings can be + # artificially low. + return list(map(abs, result)) + + +def list_sort_equal(size: int, rand: random.Random) -> list[float]: + # All equal. Again, force the elements to be distinct objects. + return list(map(abs, [-0.519012] * size)) + + +def list_sort_worst_case(size: int, rand: random.Random) -> list[float]: + # This one looks like [3, 2, 1, 0, 0, 1, 2, 3]. It was a bad case + # for an older implementation of quicksort, which used the median + # of the first, last and middle elements as the pivot. + half = size // 2 + result = list(range(half - 1, -1, -1)) + result.extend(range(half)) + # Force to float, so that the timings are comparable. This is + # significantly faster if we leave them as ints. + return list(map(float, result)) + + +# ========= +# Benchmark +# ========= + +class Benchmark: + def __init__(self, name: str, size: int, seed: int) -> None: + self._name = name + self._size = size + self._seed = seed + self._random = random.Random(self._seed) + + def run(self, loops: int) -> float: + all_data = self._prepare_data(loops) + start = time.perf_counter() + + for data in all_data: + data.sort() # Benching this method! + + return time.perf_counter() - start + + def _prepare_data(self, loops: int) -> list[float]: + bench = BENCHMARKS[self._name] + return [bench(self._size, self._random)] * loops + + +def add_cmdline_args(cmd: list[str], args) -> None: + if args.benchmark: + cmd.append(args.benchmark) + cmd.append(f"--size={args.size}") + cmd.append(f"--rng-seed={args.rng_seed}") + + +def add_parser_args(parser: argparse.ArgumentParser) -> None: + parser.add_argument( + "benchmark", + choices=BENCHMARKS, + nargs="?", + help="Can be any of: {0}".format(", ".join(BENCHMARKS)), + ) + parser.add_argument( + "--size", + type=int, + default=DEFAULT_SIZE, + help=f"Size of the lists to sort (default: {DEFAULT_SIZE})", + ) + parser.add_argument( + "--rng-seed", + type=int, + default=DEFAULT_RANDOM_SEED, + help=f"Random number generator seed (default: {DEFAULT_RANDOM_SEED})", + ) + + +DEFAULT_SIZE = 1 << 14 +DEFAULT_RANDOM_SEED = 0 +BENCHMARKS = { + "list_sort": list_sort, + "list_sort_descending": list_sort_descending, + "list_sort_ascending": list_sort_ascending, + "list_sort_ascending_exchanged": list_sort_ascending_exchanged, + "list_sort_ascending_random": list_sort_ascending_random, + "list_sort_ascending_one_percent": list_sort_ascending_one_percent, + "list_sort_duplicates": list_sort_duplicates, + "list_sort_equal": list_sort_equal, + "list_sort_worst_case": list_sort_worst_case, +} + +if __name__ == "__main__": + # This needs `pyperf` 3rd party library: + import pyperf + + runner = pyperf.Runner(add_cmdline_args=add_cmdline_args) + add_parser_args(runner.argparser) + args = runner.parse_args() + + runner.metadata["description"] = "Test `list.sort()` with different data" + runner.metadata["list_sort_size"] = args.size + runner.metadata["list_sort_random_seed"] = args.rng_seed + + if args.benchmark: + benchmarks = (args.benchmark,) + else: + benchmarks = sorted(BENCHMARKS) + for bench in benchmarks: + benchmark = Benchmark(bench, args.size, args.rng_seed) + runner.bench_time_func(bench, benchmark.run) From 371c9708863c23ddc716085198ab07fa49968166 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau <srittau@rittau.biz> Date: Sun, 18 Feb 2024 09:24:58 +0100 Subject: [PATCH 343/507] gh-114709: Fix exceptions raised by posixpath.commonpath (#114710) Fix the exceptions raised by posixpath.commonpath Raise ValueError, not IndexError when passed an empty iterable. Raise TypeError, not ValueError when passed None. --- Doc/library/os.path.rst | 4 ++-- Lib/posixpath.py | 3 ++- Lib/test/test_posixpath.py | 2 ++ .../Library/2024-01-29-13-46-41.gh-issue-114709.SQ998l.rst | 5 +++++ 4 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-29-13-46-41.gh-issue-114709.SQ998l.rst diff --git a/Doc/library/os.path.rst b/Doc/library/os.path.rst index 34bc76b231de921..16e654fcdb408db 100644 --- a/Doc/library/os.path.rst +++ b/Doc/library/os.path.rst @@ -79,7 +79,7 @@ the :mod:`glob` module.) .. function:: commonpath(paths) - Return the longest common sub-path of each pathname in the sequence + Return the longest common sub-path of each pathname in the iterable *paths*. Raise :exc:`ValueError` if *paths* contain both absolute and relative pathnames, the *paths* are on the different drives or if *paths* is empty. Unlike :func:`commonprefix`, this returns a @@ -90,7 +90,7 @@ the :mod:`glob` module.) .. versionadded:: 3.5 .. versionchanged:: 3.6 - Accepts a sequence of :term:`path-like objects <path-like object>`. + Accepts an iterable of :term:`path-like objects <path-like object>`. .. function:: commonprefix(list) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index e4f155e41a32216..33943b4403636a8 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -546,10 +546,11 @@ def relpath(path, start=None): def commonpath(paths): """Given a sequence of path names, returns the longest common sub-path.""" + paths = tuple(map(os.fspath, paths)) + if not paths: raise ValueError('commonpath() arg is an empty sequence') - paths = tuple(map(os.fspath, paths)) if isinstance(paths[0], bytes): sep = b'/' curdir = b'.' diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py index 86ce1b1d41ba61a..cbb7c4c52d96979 100644 --- a/Lib/test/test_posixpath.py +++ b/Lib/test/test_posixpath.py @@ -703,7 +703,9 @@ def check_error(exc, paths): self.assertRaises(exc, posixpath.commonpath, [os.fsencode(p) for p in paths]) + self.assertRaises(TypeError, posixpath.commonpath, None) self.assertRaises(ValueError, posixpath.commonpath, []) + self.assertRaises(ValueError, posixpath.commonpath, iter([])) check_error(ValueError, ['/usr', 'usr']) check_error(ValueError, ['usr', '/usr']) diff --git a/Misc/NEWS.d/next/Library/2024-01-29-13-46-41.gh-issue-114709.SQ998l.rst b/Misc/NEWS.d/next/Library/2024-01-29-13-46-41.gh-issue-114709.SQ998l.rst new file mode 100644 index 000000000000000..ca0d7902c73d1c0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-29-13-46-41.gh-issue-114709.SQ998l.rst @@ -0,0 +1,5 @@ +:func:`posixpath.commonpath()` now raises a :exc:`ValueError` exception when +passed an empty iterable. Previously, :exc:`IndexError` was raised. + +:func:`posixpath.commonpath()` now raises a :exc:`TypeError` exception when +passed ``None``. Previously, :exc:`ValueError` was raised. From 0c80da4c14d904a367968955544dd6ae58c8101c Mon Sep 17 00:00:00 2001 From: Daler <48939169+daler-sz@users.noreply.github.com> Date: Sun, 18 Feb 2024 19:13:46 +0500 Subject: [PATCH 344/507] gh-115572: Move `codeobject.replace()` docs to the data model (#115631) --- Doc/library/types.rst | 10 +--------- Doc/reference/datamodel.rst | 8 ++++++++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index c8c981024c1aeb1..b856544e44207c5 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -188,7 +188,7 @@ Standard names are defined for the following types: .. index:: pair: built-in function; compile - The type for code objects such as returned by :func:`compile`. + The type of :ref:`code objects <code-objects>` such as returned by :func:`compile`. .. audit-event:: code.__new__ code,filename,name,argcount,posonlyargcount,kwonlyargcount,nlocals,stacksize,flags types.CodeType @@ -196,14 +196,6 @@ Standard names are defined for the following types: required by the initializer. The audit event only occurs for direct instantiation of code objects, and is not raised for normal compilation. - .. method:: CodeType.replace(**kwargs) - - Return a copy of the code object with new values for the specified fields. - - Code objects are also supported by generic function :func:`copy.replace`. - - .. versionadded:: 3.8 - .. data:: CellType The type for cell objects: such objects are used as containers for diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 88bc025c7c3fb4d..afeb6596fbb9787 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1292,6 +1292,14 @@ Methods on code objects :pep:`626` - Precise line numbers for debugging and other tools. The PEP that introduced the :meth:`!co_lines` method. +.. method:: codeobject.replace(**kwargs) + + Return a copy of the code object with new values for the specified fields. + + Code objects are also supported by the generic function :func:`copy.replace`. + + .. versionadded:: 3.8 + .. _frame-objects: From 1e5719a663d5b1703ad588dda4fccd763c7d3e99 Mon Sep 17 00:00:00 2001 From: Victor Stinner <vstinner@python.org> Date: Sun, 18 Feb 2024 21:06:39 +0100 Subject: [PATCH 345/507] gh-115122: Add --bisect option to regrtest (#115123) * test.bisect_cmd now exit with code 0 on success, and code 1 on failure. Before, it was the opposite. * test.bisect_cmd now runs the test worker process with -X faulthandler. * regrtest RunTests: Add create_python_cmd() and bisect_cmd() methods. --- Lib/test/bisect_cmd.py | 13 +++-- Lib/test/libregrtest/cmdline.py | 2 + Lib/test/libregrtest/main.py | 58 ++++++++++++++++++- Lib/test/libregrtest/results.py | 13 +++-- Lib/test/libregrtest/runtests.py | 50 ++++++++++++++++ Lib/test/libregrtest/worker.py | 18 +----- Lib/test/test_regrtest.py | 52 ++++++++++++++++- ...-02-18-14-20-52.gh-issue-115122.3rGNo9.rst | 2 + 8 files changed, 178 insertions(+), 30 deletions(-) create mode 100644 Misc/NEWS.d/next/Tests/2024-02-18-14-20-52.gh-issue-115122.3rGNo9.rst diff --git a/Lib/test/bisect_cmd.py b/Lib/test/bisect_cmd.py index 5cb804bd469dc35..aee2e8ac1208527 100755 --- a/Lib/test/bisect_cmd.py +++ b/Lib/test/bisect_cmd.py @@ -51,6 +51,7 @@ def python_cmd(): cmd = [sys.executable] cmd.extend(subprocess._args_from_interpreter_flags()) cmd.extend(subprocess._optim_args_from_interpreter_flags()) + cmd.extend(('-X', 'faulthandler')) return cmd @@ -77,9 +78,13 @@ def run_tests(args, tests, huntrleaks=None): write_tests(tmp, tests) cmd = python_cmd() - cmd.extend(['-m', 'test', '--matchfile', tmp]) + cmd.extend(['-u', '-m', 'test', '--matchfile', tmp]) cmd.extend(args.test_args) print("+ %s" % format_shell_args(cmd)) + + sys.stdout.flush() + sys.stderr.flush() + proc = subprocess.run(cmd) return proc.returncode finally: @@ -137,8 +142,8 @@ def main(): ntest = max(ntest // 2, 1) subtests = random.sample(tests, ntest) - print("[+] Iteration %s: run %s tests/%s" - % (iteration, len(subtests), len(tests))) + print(f"[+] Iteration {iteration}/{args.max_iter}: " + f"run {len(subtests)} tests/{len(tests)}") print() exitcode = run_tests(args, subtests) @@ -170,10 +175,10 @@ def main(): if len(tests) <= args.max_tests: print("Bisection completed in %s iterations and %s" % (iteration, datetime.timedelta(seconds=dt))) - sys.exit(1) else: print("Bisection failed after %s iterations and %s" % (iteration, datetime.timedelta(seconds=dt))) + sys.exit(1) if __name__ == "__main__": diff --git a/Lib/test/libregrtest/cmdline.py b/Lib/test/libregrtest/cmdline.py index 0053bce4292f64f..608b12bb6f2a381 100644 --- a/Lib/test/libregrtest/cmdline.py +++ b/Lib/test/libregrtest/cmdline.py @@ -347,6 +347,8 @@ def _create_parser(): help='override the working directory for the test run') group.add_argument('--cleanup', action='store_true', help='remove old test_python_* directories') + group.add_argument('--bisect', action='store_true', + help='if some tests fail, run test.bisect_cmd on them') group.add_argument('--dont-add-python-opts', dest='_add_python_opts', action='store_false', help="internal option, don't use it") diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py index b24c1b9205450ba..6fbe3d00981ddd2 100644 --- a/Lib/test/libregrtest/main.py +++ b/Lib/test/libregrtest/main.py @@ -7,8 +7,7 @@ import time import trace -from test import support -from test.support import os_helper, MS_WINDOWS +from test.support import os_helper, MS_WINDOWS, flush_std_streams from .cmdline import _parse_args, Namespace from .findtests import findtests, split_test_packages, list_cases @@ -73,6 +72,7 @@ def __init__(self, ns: Namespace, _add_python_opts: bool = False): self.want_cleanup: bool = ns.cleanup self.want_rerun: bool = ns.rerun self.want_run_leaks: bool = ns.runleaks + self.want_bisect: bool = ns.bisect self.ci_mode: bool = (ns.fast_ci or ns.slow_ci) self.want_add_python_opts: bool = (_add_python_opts @@ -273,6 +273,55 @@ def rerun_failed_tests(self, runtests: RunTests): self.display_result(rerun_runtests) + def _run_bisect(self, runtests: RunTests, test: str, progress: str) -> bool: + print() + title = f"Bisect {test}" + if progress: + title = f"{title} ({progress})" + print(title) + print("#" * len(title)) + print() + + cmd = runtests.create_python_cmd() + cmd.extend([ + "-u", "-m", "test.bisect_cmd", + # Limit to 25 iterations (instead of 100) to not abuse CI resources + "--max-iter", "25", + "-v", + # runtests.match_tests is not used (yet) for bisect_cmd -i arg + ]) + cmd.extend(runtests.bisect_cmd_args()) + cmd.append(test) + print("+", shlex.join(cmd), flush=True) + + flush_std_streams() + + import subprocess + proc = subprocess.run(cmd, timeout=runtests.timeout) + exitcode = proc.returncode + + title = f"{title}: exit code {exitcode}" + print(title) + print("#" * len(title)) + print(flush=True) + + if exitcode: + print(f"Bisect failed with exit code {exitcode}") + return False + + return True + + def run_bisect(self, runtests: RunTests) -> None: + tests, _ = self.results.prepare_rerun(clear=False) + + for index, name in enumerate(tests, 1): + if len(tests) > 1: + progress = f"{index}/{len(tests)}" + else: + progress = "" + if not self._run_bisect(runtests, name, progress): + return + def display_result(self, runtests): # If running the test suite for PGO then no one cares about results. if runtests.pgo: @@ -466,7 +515,7 @@ def _run_tests(self, selected: TestTuple, tests: TestList | None) -> int: setup_process() - if self.hunt_refleak and not self.num_workers: + if (runtests.hunt_refleak is not None) and (not self.num_workers): # gh-109739: WindowsLoadTracker thread interfers with refleak check use_load_tracker = False else: @@ -486,6 +535,9 @@ def _run_tests(self, selected: TestTuple, tests: TestList | None) -> int: if self.want_rerun and self.results.need_rerun(): self.rerun_failed_tests(runtests) + + if self.want_bisect and self.results.need_rerun(): + self.run_bisect(runtests) finally: if use_load_tracker: self.logger.stop_load_tracker() diff --git a/Lib/test/libregrtest/results.py b/Lib/test/libregrtest/results.py index a41ea8aba028c36..85c82052eae19b0 100644 --- a/Lib/test/libregrtest/results.py +++ b/Lib/test/libregrtest/results.py @@ -138,7 +138,7 @@ def get_coverage_results(self) -> trace.CoverageResults: def need_rerun(self): return bool(self.rerun_results) - def prepare_rerun(self) -> tuple[TestTuple, FilterDict]: + def prepare_rerun(self, *, clear: bool = True) -> tuple[TestTuple, FilterDict]: tests: TestList = [] match_tests_dict = {} for result in self.rerun_results: @@ -149,11 +149,12 @@ def prepare_rerun(self) -> tuple[TestTuple, FilterDict]: if match_tests: match_tests_dict[result.test_name] = match_tests - # Clear previously failed tests - self.rerun_bad.extend(self.bad) - self.bad.clear() - self.env_changed.clear() - self.rerun_results.clear() + if clear: + # Clear previously failed tests + self.rerun_bad.extend(self.bad) + self.bad.clear() + self.env_changed.clear() + self.rerun_results.clear() return (tuple(tests), match_tests_dict) diff --git a/Lib/test/libregrtest/runtests.py b/Lib/test/libregrtest/runtests.py index edd72276320e419..8e9779524c56a2f 100644 --- a/Lib/test/libregrtest/runtests.py +++ b/Lib/test/libregrtest/runtests.py @@ -2,7 +2,9 @@ import dataclasses import json import os +import shlex import subprocess +import sys from typing import Any from test import support @@ -67,6 +69,11 @@ class HuntRefleak: runs: int filename: StrPath + def bisect_cmd_args(self) -> list[str]: + # Ignore filename since it can contain colon (":"), + # and usually it's not used. Use the default filename. + return ["-R", f"{self.warmups}:{self.runs}:"] + @dataclasses.dataclass(slots=True, frozen=True) class RunTests: @@ -137,6 +144,49 @@ def json_file_use_stdout(self) -> bool: or support.is_wasi ) + def create_python_cmd(self) -> list[str]: + python_opts = support.args_from_interpreter_flags() + if self.python_cmd is not None: + executable = self.python_cmd + # Remove -E option, since --python=COMMAND can set PYTHON + # environment variables, such as PYTHONPATH, in the worker + # process. + python_opts = [opt for opt in python_opts if opt != "-E"] + else: + executable = (sys.executable,) + cmd = [*executable, *python_opts] + if '-u' not in python_opts: + cmd.append('-u') # Unbuffered stdout and stderr + if self.coverage: + cmd.append("-Xpresite=test.cov") + return cmd + + def bisect_cmd_args(self) -> list[str]: + args = [] + if self.fail_fast: + args.append("--failfast") + if self.fail_env_changed: + args.append("--fail-env-changed") + if self.timeout: + args.append(f"--timeout={self.timeout}") + if self.hunt_refleak is not None: + args.extend(self.hunt_refleak.bisect_cmd_args()) + if self.test_dir: + args.extend(("--testdir", self.test_dir)) + if self.memory_limit: + args.extend(("--memlimit", self.memory_limit)) + if self.gc_threshold: + args.append(f"--threshold={self.gc_threshold}") + if self.use_resources: + args.extend(("-u", ','.join(self.use_resources))) + if self.python_cmd: + cmd = shlex.join(self.python_cmd) + args.extend(("--python", cmd)) + if self.randomize: + args.append(f"--randomize") + args.append(f"--randseed={self.random_seed}") + return args + @dataclasses.dataclass(slots=True, frozen=True) class WorkerRunTests(RunTests): diff --git a/Lib/test/libregrtest/worker.py b/Lib/test/libregrtest/worker.py index 7a6d33d4499943e..f8b8e45eca32768 100644 --- a/Lib/test/libregrtest/worker.py +++ b/Lib/test/libregrtest/worker.py @@ -3,7 +3,6 @@ import os from typing import Any, NoReturn -from test import support from test.support import os_helper, Py_DEBUG from .setup import setup_process, setup_test_dir @@ -19,23 +18,10 @@ def create_worker_process(runtests: WorkerRunTests, output_fd: int, tmp_dir: StrPath | None = None) -> subprocess.Popen: - python_cmd = runtests.python_cmd worker_json = runtests.as_json() - python_opts = support.args_from_interpreter_flags() - if python_cmd is not None: - executable = python_cmd - # Remove -E option, since --python=COMMAND can set PYTHON environment - # variables, such as PYTHONPATH, in the worker process. - python_opts = [opt for opt in python_opts if opt != "-E"] - else: - executable = (sys.executable,) - if runtests.coverage: - python_opts.append("-Xpresite=test.cov") - cmd = [*executable, *python_opts, - '-u', # Unbuffered stdout and stderr - '-m', 'test.libregrtest.worker', - worker_json] + cmd = runtests.create_python_cmd() + cmd.extend(['-m', 'test.libregrtest.worker', worker_json]) env = dict(os.environ) if tmp_dir is not None: diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py index 89562fa5eac62ca..b80e0524593fc7d 100644 --- a/Lib/test/test_regrtest.py +++ b/Lib/test/test_regrtest.py @@ -399,7 +399,7 @@ def test_unknown_option(self): self.checkError(['--unknown-option'], 'unrecognized arguments: --unknown-option') - def check_ci_mode(self, args, use_resources, rerun=True): + def create_regrtest(self, args): ns = cmdline._parse_args(args) # Check Regrtest attributes which are more reliable than Namespace @@ -411,6 +411,10 @@ def check_ci_mode(self, args, use_resources, rerun=True): regrtest = main.Regrtest(ns) + return regrtest + + def check_ci_mode(self, args, use_resources, rerun=True): + regrtest = self.create_regrtest(args) self.assertEqual(regrtest.num_workers, -1) self.assertEqual(regrtest.want_rerun, rerun) self.assertTrue(regrtest.randomize) @@ -455,6 +459,11 @@ def test_dont_add_python_opts(self): ns = cmdline._parse_args(args) self.assertFalse(ns._add_python_opts) + def test_bisect(self): + args = ['--bisect'] + regrtest = self.create_regrtest(args) + self.assertTrue(regrtest.want_bisect) + @dataclasses.dataclass(slots=True) class Rerun: @@ -1192,6 +1201,47 @@ def test_huntrleaks(self): def test_huntrleaks_mp(self): self.check_huntrleaks(run_workers=True) + @unittest.skipUnless(support.Py_DEBUG, 'need a debug build') + def test_huntrleaks_bisect(self): + # test --huntrleaks --bisect + code = textwrap.dedent(""" + import unittest + + GLOBAL_LIST = [] + + class RefLeakTest(unittest.TestCase): + def test1(self): + pass + + def test2(self): + pass + + def test3(self): + GLOBAL_LIST.append(object()) + + def test4(self): + pass + """) + + test = self.create_test('huntrleaks', code=code) + + filename = 'reflog.txt' + self.addCleanup(os_helper.unlink, filename) + cmd = ['--huntrleaks', '3:3:', '--bisect', test] + output = self.run_tests(*cmd, + exitcode=EXITCODE_BAD_TEST, + stderr=subprocess.STDOUT) + + self.assertIn(f"Bisect {test}", output) + self.assertIn(f"Bisect {test}: exit code 0", output) + + # test3 is the one which leaks + self.assertIn("Bisection completed in", output) + self.assertIn( + "Tests (1):\n" + f"* {test}.RefLeakTest.test3\n", + output) + @unittest.skipUnless(support.Py_DEBUG, 'need a debug build') def test_huntrleaks_fd_leak(self): # test --huntrleaks for file descriptor leak diff --git a/Misc/NEWS.d/next/Tests/2024-02-18-14-20-52.gh-issue-115122.3rGNo9.rst b/Misc/NEWS.d/next/Tests/2024-02-18-14-20-52.gh-issue-115122.3rGNo9.rst new file mode 100644 index 000000000000000..e187a40a40516b4 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-02-18-14-20-52.gh-issue-115122.3rGNo9.rst @@ -0,0 +1,2 @@ +Add ``--bisect`` option to regrtest test runner: run failed tests with +``test.bisect_cmd`` to identify failing tests. Patch by Victor Stinner. From edea0e7d9938139d53af84de817097bc12bb8f92 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sun, 18 Feb 2024 14:08:50 -0800 Subject: [PATCH 346/507] gh-114709: Mark commonpath behaviour as changed in 3.13 (#115639) --- Doc/library/os.path.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Doc/library/os.path.rst b/Doc/library/os.path.rst index 16e654fcdb408db..3ee2b7db1e511b9 100644 --- a/Doc/library/os.path.rst +++ b/Doc/library/os.path.rst @@ -90,7 +90,10 @@ the :mod:`glob` module.) .. versionadded:: 3.5 .. versionchanged:: 3.6 - Accepts an iterable of :term:`path-like objects <path-like object>`. + Accepts a sequence of :term:`path-like objects <path-like object>`. + + .. versionchanged:: 3.13 + Any iterable can now be passed, rather than just sequences. .. function:: commonprefix(list) From 53d5e67804227d541ed2f9e8efea8de5d70cb1ec Mon Sep 17 00:00:00 2001 From: Jamie Phan <jamie@ordinarylab.dev> Date: Mon, 19 Feb 2024 11:01:00 +1100 Subject: [PATCH 347/507] gh-111358: Fix timeout behaviour in BaseEventLoop.shutdown_default_executor (#115622) --- Lib/asyncio/base_events.py | 20 ++++++++++--------- Lib/test/test_asyncio/test_base_events.py | 16 +++++++++++++++ ...-02-18-12-18-12.gh-issue-111358.9yJUMD.rst | 2 ++ 3 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-18-12-18-12.gh-issue-111358.9yJUMD.rst diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index aadc4f478f8b560..6c5cf28e7c59d42 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -45,6 +45,7 @@ from . import sslproto from . import staggered from . import tasks +from . import timeouts from . import transports from . import trsock from .log import logger @@ -598,23 +599,24 @@ async def shutdown_default_executor(self, timeout=None): thread = threading.Thread(target=self._do_shutdown, args=(future,)) thread.start() try: - await future - finally: - thread.join(timeout) - - if thread.is_alive(): + async with timeouts.timeout(timeout): + await future + except TimeoutError: warnings.warn("The executor did not finishing joining " - f"its threads within {timeout} seconds.", - RuntimeWarning, stacklevel=2) + f"its threads within {timeout} seconds.", + RuntimeWarning, stacklevel=2) self._default_executor.shutdown(wait=False) + else: + thread.join() def _do_shutdown(self, future): try: self._default_executor.shutdown(wait=True) if not self.is_closed(): - self.call_soon_threadsafe(future.set_result, None) + self.call_soon_threadsafe(futures._set_result_unless_cancelled, + future, None) except Exception as ex: - if not self.is_closed(): + if not self.is_closed() and not future.cancelled(): self.call_soon_threadsafe(future.set_exception, ex) def _check_running(self): diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index 82071edb2525706..4cd872d3a5b2d89 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -231,6 +231,22 @@ def test_set_default_executor_error(self): self.assertIsNone(self.loop._default_executor) + def test_shutdown_default_executor_timeout(self): + class DummyExecutor(concurrent.futures.ThreadPoolExecutor): + def shutdown(self, wait=True, *, cancel_futures=False): + if wait: + time.sleep(0.1) + + self.loop._process_events = mock.Mock() + self.loop._write_to_self = mock.Mock() + executor = DummyExecutor() + self.loop.set_default_executor(executor) + + with self.assertWarnsRegex(RuntimeWarning, + "The executor did not finishing joining"): + self.loop.run_until_complete( + self.loop.shutdown_default_executor(timeout=0.01)) + def test_call_soon(self): def cb(): pass diff --git a/Misc/NEWS.d/next/Library/2024-02-18-12-18-12.gh-issue-111358.9yJUMD.rst b/Misc/NEWS.d/next/Library/2024-02-18-12-18-12.gh-issue-111358.9yJUMD.rst new file mode 100644 index 000000000000000..2e895f8f181ce7e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-18-12-18-12.gh-issue-111358.9yJUMD.rst @@ -0,0 +1,2 @@ +Fix a bug in :meth:`asyncio.BaseEventLoop.shutdown_default_executor` to +ensure the timeout passed to the coroutine behaves as expected. From 177b9cb52e57da4e62dd8483bcd5905990d03f9e Mon Sep 17 00:00:00 2001 From: "Simon A. Eugster" <simon.eu@gmail.com> Date: Mon, 19 Feb 2024 08:50:09 +0100 Subject: [PATCH 348/507] Docs: Add explanation about little/big endian (#109841) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com> --- Doc/library/struct.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Doc/library/struct.rst b/Doc/library/struct.rst index e2e6fc542e3e671..3e507c1c7e7c85c 100644 --- a/Doc/library/struct.rst +++ b/Doc/library/struct.rst @@ -160,6 +160,21 @@ following table: If the first character is not one of these, ``'@'`` is assumed. +.. note:: + + The number 1023 (``0x3ff`` in hexadecimal) has the following byte representations: + + * ``03 ff`` in big-endian (``>``) + * ``ff 03`` in little-endian (``<``) + + Python example: + + >>> import struct + >>> struct.pack('>h', 1023) + b'\x03\xff' + >>> struct.pack('<h', 1023) + b'\xff\x03' + Native byte order is big-endian or little-endian, depending on the host system. For example, Intel x86, AMD64 (x86-64), and Apple M1 are little-endian; IBM z and many legacy architectures are big-endian. From 1476ac2c58669b0a78d3089ebb9cc87955a1f5b0 Mon Sep 17 00:00:00 2001 From: Masayuki Moriyama <masayuki.moriyama@miraclelinux.com> Date: Mon, 19 Feb 2024 17:01:35 +0900 Subject: [PATCH 349/507] gh-102388: Add windows_31j to aliases for cp932 codec (#102389) The charset name "Windows-31J" is registered in the IANA Charset Registry[1] and is implemented in Python as the cp932 codec. [1] https://www.iana.org/assignments/charset-reg/windows-31J Signed-off-by: Masayuki Moriyama <masayuki.moriyama@miraclelinux.com> --- Doc/library/codecs.rst | 3 ++- Lib/encodings/aliases.py | 1 + .../Library/2023-03-03-09-05-42.gh-issue-102389.ucmo0_.rst | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2023-03-03-09-05-42.gh-issue-102389.ucmo0_.rst diff --git a/Doc/library/codecs.rst b/Doc/library/codecs.rst index 4617624686b1f3b..25d4e24ac162a9b 100644 --- a/Doc/library/codecs.rst +++ b/Doc/library/codecs.rst @@ -1132,7 +1132,8 @@ particular, the following variants typically exist: +-----------------+--------------------------------+--------------------------------+ | cp875 | | Greek | +-----------------+--------------------------------+--------------------------------+ -| cp932 | 932, ms932, mskanji, ms-kanji | Japanese | +| cp932 | 932, ms932, mskanji, ms-kanji, | Japanese | +| | windows-31j | | +-----------------+--------------------------------+--------------------------------+ | cp949 | 949, ms949, uhc | Korean | +-----------------+--------------------------------+--------------------------------+ diff --git a/Lib/encodings/aliases.py b/Lib/encodings/aliases.py index d85afd6d5cf704b..6a5ca046b5eb6cd 100644 --- a/Lib/encodings/aliases.py +++ b/Lib/encodings/aliases.py @@ -209,6 +209,7 @@ 'ms932' : 'cp932', 'mskanji' : 'cp932', 'ms_kanji' : 'cp932', + 'windows_31j' : 'cp932', # cp949 codec '949' : 'cp949', diff --git a/Misc/NEWS.d/next/Library/2023-03-03-09-05-42.gh-issue-102389.ucmo0_.rst b/Misc/NEWS.d/next/Library/2023-03-03-09-05-42.gh-issue-102389.ucmo0_.rst new file mode 100644 index 000000000000000..8c11567d79ba7b9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-03-03-09-05-42.gh-issue-102389.ucmo0_.rst @@ -0,0 +1 @@ +Add ``windows_31j`` to aliases for ``cp932`` codec From cbe809dfa94385bacaa24beee3976ba77a395589 Mon Sep 17 00:00:00 2001 From: 0xflotus <0xflotus@gmail.com> Date: Mon, 19 Feb 2024 09:29:32 +0100 Subject: [PATCH 350/507] gh-83648: Add missing `deprecated` arg in argparse.rst (GH-115640) --- Doc/library/argparse.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 952643a46416d21..eaddd44e2defd71 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -745,7 +745,7 @@ The add_argument() method .. method:: ArgumentParser.add_argument(name or flags..., [action], [nargs], \ [const], [default], [type], [choices], [required], \ - [help], [metavar], [dest]) + [help], [metavar], [dest], [deprecated]) Define how a single command-line argument should be parsed. Each parameter has its own more detailed description below, but in short they are: From aa8c1a0d1626b5965c2f3a1d5af15acbb110eac1 Mon Sep 17 00:00:00 2001 From: Victor Stinner <vstinner@python.org> Date: Mon, 19 Feb 2024 13:20:46 +0100 Subject: [PATCH 351/507] gh-114626: Add again _PyCFunctionFastWithKeywords name (GH-115561) Keep the old private _PyCFunctionFastWithKeywords name (Python 3.7) as an alias to the new public name PyCFunctionFastWithKeywords (Python 3.13a4). _PyCFunctionWithKeywords doesn't exist in Python 3.13a3, whereas _PyCFunctionFastWithKeywords was removed in Python 3.13a4. --- Include/methodobject.h | 2 +- .../next/C API/2024-02-16-15-56-53.gh-issue-114626.ie2esA.rst | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/C API/2024-02-16-15-56-53.gh-issue-114626.ie2esA.rst diff --git a/Include/methodobject.h b/Include/methodobject.h index 452f891a7aba839..39272815b127f4e 100644 --- a/Include/methodobject.h +++ b/Include/methodobject.h @@ -31,7 +31,7 @@ typedef PyObject *(*PyCMethod)(PyObject *, PyTypeObject *, PyObject *const *, // Note that the underscore-prefixed names were documented in public docs; // people may be using them. typedef PyCFunctionFast _PyCFunctionFast; -typedef PyCFunctionWithKeywords _PyCFunctionWithKeywords; +typedef PyCFunctionFastWithKeywords _PyCFunctionFastWithKeywords; // Cast an function to the PyCFunction type to use it with PyMethodDef. // diff --git a/Misc/NEWS.d/next/C API/2024-02-16-15-56-53.gh-issue-114626.ie2esA.rst b/Misc/NEWS.d/next/C API/2024-02-16-15-56-53.gh-issue-114626.ie2esA.rst new file mode 100644 index 000000000000000..763f4cee6d3f0b1 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-02-16-15-56-53.gh-issue-114626.ie2esA.rst @@ -0,0 +1,4 @@ +Add again ``_PyCFunctionFastWithKeywords`` name, removed in Python 3.13 +alpha 4 by mistake. Keep the old private ``_PyCFunctionFastWithKeywords`` +name (Python 3.7) as an alias to the new public name +``PyCFunctionFastWithKeywords`` (Python 3.13a4). Patch by Victor Stinner. From d504968983c5cd5ddbdf73ccd3693ffb89e7952f Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 19 Feb 2024 15:26:23 +0100 Subject: [PATCH 352/507] gh-115652: Fix indentation in the documentation of multiprocessing.get_start_method (GH-115658) --- Doc/library/multiprocessing.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst index d570d4eb0dae78d..41ccd5f88186f6b 100644 --- a/Doc/library/multiprocessing.rst +++ b/Doc/library/multiprocessing.rst @@ -1084,13 +1084,13 @@ Miscellaneous The return value can be ``'fork'``, ``'spawn'``, ``'forkserver'`` or ``None``. See :ref:`multiprocessing-start-methods`. -.. versionchanged:: 3.8 + .. versionadded:: 3.4 - On macOS, the *spawn* start method is now the default. The *fork* start - method should be considered unsafe as it can lead to crashes of the - subprocess. See :issue:`33725`. + .. versionchanged:: 3.8 - .. versionadded:: 3.4 + On macOS, the *spawn* start method is now the default. The *fork* start + method should be considered unsafe as it can lead to crashes of the + subprocess. See :issue:`33725`. .. function:: set_executable(executable) From ecf16ee50e42f979624e55fa343a8522942db2e7 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado <Pablogsal@gmail.com> Date: Mon, 19 Feb 2024 14:54:10 +0000 Subject: [PATCH 353/507] gh-115154: Fix untokenize handling of unicode named literals (#115171) --- Lib/test/test_tokenize.py | 40 ++++++++++++-- Lib/tokenize.py | 53 ++++++++++++++++--- ...-02-08-16-01-18.gh-issue-115154.ji96FV.rst | 2 + 3 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-08-16-01-18.gh-issue-115154.ji96FV.rst diff --git a/Lib/test/test_tokenize.py b/Lib/test/test_tokenize.py index 21e8637a7ca9051..4428e8cea1964cc 100644 --- a/Lib/test/test_tokenize.py +++ b/Lib/test/test_tokenize.py @@ -1877,6 +1877,43 @@ def test_roundtrip(self): " print('Can not import' # comment2\n)" "else: print('Loaded')\n") + self.check_roundtrip("f'\\N{EXCLAMATION MARK}'") + self.check_roundtrip(r"f'\\N{SNAKE}'") + self.check_roundtrip(r"f'\\N{{SNAKE}}'") + self.check_roundtrip(r"f'\N{SNAKE}'") + self.check_roundtrip(r"f'\\\N{SNAKE}'") + self.check_roundtrip(r"f'\\\\\N{SNAKE}'") + self.check_roundtrip(r"f'\\\\\\\N{SNAKE}'") + + self.check_roundtrip(r"f'\\N{1}'") + self.check_roundtrip(r"f'\\\\N{2}'") + self.check_roundtrip(r"f'\\\\\\N{3}'") + self.check_roundtrip(r"f'\\\\\\\\N{4}'") + + self.check_roundtrip(r"f'\\N{{'") + self.check_roundtrip(r"f'\\\\N{{'") + self.check_roundtrip(r"f'\\\\\\N{{'") + self.check_roundtrip(r"f'\\\\\\\\N{{'") + cases = [ + """ +if 1: + "foo" +"bar" +""", + """ +if 1: + ("foo" + "bar") +""", + """ +if 1: + "foo" + "bar" +""" ] + for case in cases: + self.check_roundtrip(case) + + def test_continuation(self): # Balancing continuation self.check_roundtrip("a = (3,4, \n" @@ -1911,9 +1948,6 @@ def test_random_files(self): tempdir = os.path.dirname(__file__) or os.curdir testfiles = glob.glob(os.path.join(glob.escape(tempdir), "test*.py")) - # TODO: Remove this once we can untokenize PEP 701 syntax - testfiles.remove(os.path.join(tempdir, "test_fstring.py")) - if not support.is_resource_enabled("cpu"): testfiles = random.sample(testfiles, 10) diff --git a/Lib/tokenize.py b/Lib/tokenize.py index 0ab1893d42f72f1..7f418bb7a1b37fc 100644 --- a/Lib/tokenize.py +++ b/Lib/tokenize.py @@ -168,6 +168,7 @@ def __init__(self): self.tokens = [] self.prev_row = 1 self.prev_col = 0 + self.prev_type = None self.encoding = None def add_whitespace(self, start): @@ -183,6 +184,29 @@ def add_whitespace(self, start): if col_offset: self.tokens.append(" " * col_offset) + def escape_brackets(self, token): + characters = [] + consume_until_next_bracket = False + for character in token: + if character == "}": + if consume_until_next_bracket: + consume_until_next_bracket = False + else: + characters.append(character) + if character == "{": + n_backslashes = sum( + 1 for char in _itertools.takewhile( + "\\".__eq__, + characters[-2::-1] + ) + ) + if n_backslashes % 2 == 0: + characters.append(character) + else: + consume_until_next_bracket = True + characters.append(character) + return "".join(characters) + def untokenize(self, iterable): it = iter(iterable) indents = [] @@ -214,11 +238,13 @@ def untokenize(self, iterable): startline = False elif tok_type == FSTRING_MIDDLE: if '{' in token or '}' in token: + token = self.escape_brackets(token) + last_line = token.splitlines()[-1] end_line, end_col = end - end = (end_line, end_col + token.count('{') + token.count('}')) - token = re.sub('{', '{{', token) - token = re.sub('}', '}}', token) - + extra_chars = last_line.count("{{") + last_line.count("}}") + end = (end_line, end_col + extra_chars) + elif tok_type in (STRING, FSTRING_START) and self.prev_type in (STRING, FSTRING_END): + self.tokens.append(" ") self.add_whitespace(start) self.tokens.append(token) @@ -226,6 +252,7 @@ def untokenize(self, iterable): if tok_type in (NEWLINE, NL): self.prev_row += 1 self.prev_col = 0 + self.prev_type = tok_type return "".join(self.tokens) def compat(self, token, iterable): @@ -233,6 +260,7 @@ def compat(self, token, iterable): toks_append = self.tokens.append startline = token[0] in (NEWLINE, NL) prevstring = False + in_fstring = 0 for tok in _itertools.chain([token], iterable): toknum, tokval = tok[:2] @@ -251,6 +279,10 @@ def compat(self, token, iterable): else: prevstring = False + if toknum == FSTRING_START: + in_fstring += 1 + elif toknum == FSTRING_END: + in_fstring -= 1 if toknum == INDENT: indents.append(tokval) continue @@ -263,11 +295,18 @@ def compat(self, token, iterable): toks_append(indents[-1]) startline = False elif toknum == FSTRING_MIDDLE: - if '{' in tokval or '}' in tokval: - tokval = re.sub('{', '{{', tokval) - tokval = re.sub('}', '}}', tokval) + tokval = self.escape_brackets(tokval) + + # Insert a space between two consecutive brackets if we are in an f-string + if tokval in {"{", "}"} and self.tokens and self.tokens[-1] == tokval and in_fstring: + tokval = ' ' + tokval + + # Insert a space between two consecutive f-strings + if toknum in (STRING, FSTRING_START) and self.prev_type in (STRING, FSTRING_END): + self.tokens.append(" ") toks_append(tokval) + self.prev_type = toknum def untokenize(iterable): diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-08-16-01-18.gh-issue-115154.ji96FV.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-08-16-01-18.gh-issue-115154.ji96FV.rst new file mode 100644 index 000000000000000..045596bfcdca431 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-08-16-01-18.gh-issue-115154.ji96FV.rst @@ -0,0 +1,2 @@ +Fix a bug that was causing the :func:`tokenize.untokenize` function to +handle unicode named literals incorrectly. Patch by Pablo Galindo From 7b25a82e83ad8fe15e4302bb7655309573affa83 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Mon, 19 Feb 2024 19:02:29 +0200 Subject: [PATCH 354/507] Fix test_compile with -O mode (GH-115346) --- Lib/test/test_compile.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 4d8647be6a14f15..0126780982059a3 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -749,7 +749,7 @@ def f(): return "unused" self.assertEqual(f.__code__.co_consts, - ("docstring", "used")) + (f.__doc__, "used")) @support.cpython_only def test_remove_unused_consts_no_docstring(self): @@ -794,7 +794,7 @@ def test_strip_unused_None(self): def f1(): "docstring" return 42 - self.assertEqual(f1.__code__.co_consts, ("docstring", 42)) + self.assertEqual(f1.__code__.co_consts, (f1.__doc__, 42)) # This is a regression test for a CPython specific peephole optimizer # implementation bug present in a few releases. It's assertion verifies @@ -1047,6 +1047,8 @@ def no_code2(): for func in (no_code1, no_code2): with self.subTest(func=func): + if func is no_code1 and no_code1.__doc__ is None: + continue code = func.__code__ [(start, end, line)] = code.co_lines() self.assertEqual(start, 0) @@ -1524,6 +1526,7 @@ def test_multiline_boolean_expression(self): self.assertOpcodeSourcePositionIs(compiled_code, 'POP_JUMP_IF_TRUE', line=4, end_line=4, column=8, end_column=13, occurrence=2) + @unittest.skipIf(sys.flags.optimize, "Assertions are disabled in optimized mode") def test_multiline_assert(self): snippet = textwrap.dedent("""\ assert (a > 0 and From 07ef9d86a5efa82d06a8e7e15dd3aff1e946aa6b Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Mon, 19 Feb 2024 19:02:51 +0200 Subject: [PATCH 355/507] Fix test_py_compile with -O mode (GH-115345) --- Lib/test/test_py_compile.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py index c4e6551f6057822..64387296e846213 100644 --- a/Lib/test/test_py_compile.py +++ b/Lib/test/test_py_compile.py @@ -227,7 +227,8 @@ class PyCompileCLITestCase(unittest.TestCase): def setUp(self): self.directory = tempfile.mkdtemp() self.source_path = os.path.join(self.directory, '_test.py') - self.cache_path = importlib.util.cache_from_source(self.source_path) + self.cache_path = importlib.util.cache_from_source(self.source_path, + optimization='' if __debug__ else 1) with open(self.source_path, 'w') as file: file.write('x = 123\n') @@ -250,6 +251,7 @@ def pycompilecmd_failure(self, *args): return script_helper.assert_python_failure('-m', 'py_compile', *args) def test_stdin(self): + self.assertFalse(os.path.exists(self.cache_path)) result = self.pycompilecmd('-', input=self.source_path) self.assertEqual(result.returncode, 0) self.assertEqual(result.stdout, b'') From 872cc9957a9c8b971448e7377fad865f351da6c9 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Mon, 19 Feb 2024 19:03:21 +0200 Subject: [PATCH 356/507] gh-115341: Fix loading unit tests with doctests in -OO mode (GH-115342) --- Lib/doctest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/doctest.py b/Lib/doctest.py index 1969777b6677875..6049423b5147a5e 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -2225,13 +2225,13 @@ def __init__(self, test, optionflags=0, setUp=None, tearDown=None, unittest.TestCase.__init__(self) self._dt_optionflags = optionflags self._dt_checker = checker - self._dt_globs = test.globs.copy() self._dt_test = test self._dt_setUp = setUp self._dt_tearDown = tearDown def setUp(self): test = self._dt_test + self._dt_globs = test.globs.copy() if self._dt_setUp is not None: self._dt_setUp(test) From e47ecbd0420528f1f9f282d9e7acfcf586a4caa1 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Mon, 19 Feb 2024 19:20:00 +0200 Subject: [PATCH 357/507] gh-60346: Improve handling single-dash options in ArgumentParser.parse_known_args() (GH-114180) --- Lib/argparse.py | 51 ++++++++++--------- Lib/test/test_argparse.py | 28 ++++++++++ ...3-04-02-21-20-35.gh-issue-60346.7mjgua.rst | 1 + 3 files changed, 57 insertions(+), 23 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-04-02-21-20-35.gh-issue-60346.7mjgua.rst diff --git a/Lib/argparse.py b/Lib/argparse.py index 04ee3b19aca755e..6ef0bea5c6cd4aa 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -2033,7 +2033,7 @@ def consume_optional(start_index): # get the optional identified at this index option_tuple = option_string_indices[start_index] - action, option_string, explicit_arg = option_tuple + action, option_string, sep, explicit_arg = option_tuple # identify additional optionals in the same arg string # (e.g. -xyz is the same as -x -y -z if no args are required) @@ -2060,18 +2060,27 @@ def consume_optional(start_index): and option_string[1] not in chars and explicit_arg != '' ): + if sep or explicit_arg[0] in chars: + msg = _('ignored explicit argument %r') + raise ArgumentError(action, msg % explicit_arg) action_tuples.append((action, [], option_string)) char = option_string[0] option_string = char + explicit_arg[0] - new_explicit_arg = explicit_arg[1:] or None optionals_map = self._option_string_actions if option_string in optionals_map: action = optionals_map[option_string] - explicit_arg = new_explicit_arg + explicit_arg = explicit_arg[1:] + if not explicit_arg: + sep = explicit_arg = None + elif explicit_arg[0] == '=': + sep = '=' + explicit_arg = explicit_arg[1:] + else: + sep = '' else: - msg = _('ignored explicit argument %r') - raise ArgumentError(action, msg % explicit_arg) - + extras.append(char + explicit_arg) + stop = start_index + 1 + break # if the action expect exactly one argument, we've # successfully matched the option; exit the loop elif arg_count == 1: @@ -2299,18 +2308,17 @@ def _parse_optional(self, arg_string): # if the option string is present in the parser, return the action if arg_string in self._option_string_actions: action = self._option_string_actions[arg_string] - return action, arg_string, None + return action, arg_string, None, None # if it's just a single character, it was meant to be positional if len(arg_string) == 1: return None # if the option string before the "=" is present, return the action - if '=' in arg_string: - option_string, explicit_arg = arg_string.split('=', 1) - if option_string in self._option_string_actions: - action = self._option_string_actions[option_string] - return action, option_string, explicit_arg + option_string, sep, explicit_arg = arg_string.partition('=') + if sep and option_string in self._option_string_actions: + action = self._option_string_actions[option_string] + return action, option_string, sep, explicit_arg # search through all possible prefixes of the option string # and all actions in the parser for possible interpretations @@ -2319,7 +2327,7 @@ def _parse_optional(self, arg_string): # if multiple actions match, the option string was ambiguous if len(option_tuples) > 1: options = ', '.join([option_string - for action, option_string, explicit_arg in option_tuples]) + for action, option_string, sep, explicit_arg in option_tuples]) args = {'option': arg_string, 'matches': options} msg = _('ambiguous option: %(option)s could match %(matches)s') self.error(msg % args) @@ -2343,7 +2351,7 @@ def _parse_optional(self, arg_string): # it was meant to be an optional but there is no such option # in this parser (though it might be a valid option in a subparser) - return None, arg_string, None + return None, arg_string, None, None def _get_option_tuples(self, option_string): result = [] @@ -2353,15 +2361,13 @@ def _get_option_tuples(self, option_string): chars = self.prefix_chars if option_string[0] in chars and option_string[1] in chars: if self.allow_abbrev: - if '=' in option_string: - option_prefix, explicit_arg = option_string.split('=', 1) - else: - option_prefix = option_string - explicit_arg = None + option_prefix, sep, explicit_arg = option_string.partition('=') + if not sep: + sep = explicit_arg = None for option_string in self._option_string_actions: if option_string.startswith(option_prefix): action = self._option_string_actions[option_string] - tup = action, option_string, explicit_arg + tup = action, option_string, sep, explicit_arg result.append(tup) # single character options can be concatenated with their arguments @@ -2369,18 +2375,17 @@ def _get_option_tuples(self, option_string): # separate elif option_string[0] in chars and option_string[1] not in chars: option_prefix = option_string - explicit_arg = None short_option_prefix = option_string[:2] short_explicit_arg = option_string[2:] for option_string in self._option_string_actions: if option_string == short_option_prefix: action = self._option_string_actions[option_string] - tup = action, option_string, short_explicit_arg + tup = action, option_string, '', short_explicit_arg result.append(tup) elif option_string.startswith(option_prefix): action = self._option_string_actions[option_string] - tup = action, option_string, explicit_arg + tup = action, option_string, None, None result.append(tup) # shouldn't ever get here diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 86d6e81a71642b7..65fd9cf1a4a567a 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2274,6 +2274,34 @@ def test_parse_known_args(self): (NS(foo=False, bar=0.5, w=7, x='b'), ['-W', '-X', 'Y', 'Z']), ) + def test_parse_known_args_with_single_dash_option(self): + parser = ErrorRaisingArgumentParser() + parser.add_argument('-k', '--known', action='count', default=0) + parser.add_argument('-n', '--new', action='count', default=0) + self.assertEqual(parser.parse_known_args(['-k', '-u']), + (NS(known=1, new=0), ['-u'])) + self.assertEqual(parser.parse_known_args(['-u', '-k']), + (NS(known=1, new=0), ['-u'])) + self.assertEqual(parser.parse_known_args(['-ku']), + (NS(known=1, new=0), ['-u'])) + self.assertArgumentParserError(parser.parse_known_args, ['-k=u']) + self.assertEqual(parser.parse_known_args(['-uk']), + (NS(known=0, new=0), ['-uk'])) + self.assertEqual(parser.parse_known_args(['-u=k']), + (NS(known=0, new=0), ['-u=k'])) + self.assertEqual(parser.parse_known_args(['-kunknown']), + (NS(known=1, new=0), ['-unknown'])) + self.assertArgumentParserError(parser.parse_known_args, ['-k=unknown']) + self.assertEqual(parser.parse_known_args(['-ku=nknown']), + (NS(known=1, new=0), ['-u=nknown'])) + self.assertEqual(parser.parse_known_args(['-knew']), + (NS(known=1, new=1), ['-ew'])) + self.assertArgumentParserError(parser.parse_known_args, ['-kn=ew']) + self.assertArgumentParserError(parser.parse_known_args, ['-k-new']) + self.assertArgumentParserError(parser.parse_known_args, ['-kn-ew']) + self.assertEqual(parser.parse_known_args(['-kne-w']), + (NS(known=1, new=1), ['-e-w'])) + def test_dest(self): parser = ErrorRaisingArgumentParser() parser.add_argument('--foo', action='store_true') diff --git a/Misc/NEWS.d/next/Library/2023-04-02-21-20-35.gh-issue-60346.7mjgua.rst b/Misc/NEWS.d/next/Library/2023-04-02-21-20-35.gh-issue-60346.7mjgua.rst new file mode 100644 index 000000000000000..c15bd6ed11d17f9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-04-02-21-20-35.gh-issue-60346.7mjgua.rst @@ -0,0 +1 @@ +Fix ArgumentParser inconsistent with parse_known_args. From b02ab65e8083185b76a7dd06f470b779580a3b03 Mon Sep 17 00:00:00 2001 From: Brian Schubert <brianm.schubert@gmail.com> Date: Mon, 19 Feb 2024 12:54:54 -0500 Subject: [PATCH 358/507] gh-115664: Fix chronological ordering of versionadded and versionchanged directives (#115676) --- Doc/library/codecs.rst | 6 +++--- Doc/library/math.rst | 8 ++++---- Doc/library/shutil.rst | 8 ++++---- Doc/library/sys.rst | 4 ++-- Doc/library/venv.rst | 8 ++++---- Doc/using/venv-create.inc | 6 +++--- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Doc/library/codecs.rst b/Doc/library/codecs.rst index 25d4e24ac162a9b..a757f19b99448c1 100644 --- a/Doc/library/codecs.rst +++ b/Doc/library/codecs.rst @@ -1541,13 +1541,13 @@ This module implements the ANSI codepage (CP_ACP). .. availability:: Windows. -.. versionchanged:: 3.3 - Support any error handler. - .. versionchanged:: 3.2 Before 3.2, the *errors* argument was ignored; ``'replace'`` was always used to encode, and ``'ignore'`` to decode. +.. versionchanged:: 3.3 + Support any error handler. + :mod:`encodings.utf_8_sig` --- UTF-8 codec with BOM signature ------------------------------------------------------------- diff --git a/Doc/library/math.rst b/Doc/library/math.rst index 9caf7230eed0aa9..3c850317f608582 100644 --- a/Doc/library/math.rst +++ b/Doc/library/math.rst @@ -239,11 +239,11 @@ Number-theoretic and representation functions See also :func:`math.ulp`. + .. versionadded:: 3.9 + .. versionchanged:: 3.12 Added the *steps* argument. - .. versionadded:: 3.9 - .. function:: perm(n, k=None) Return the number of ways to choose *k* items from *n* items @@ -680,11 +680,11 @@ Constants >>> math.isnan(float('nan')) True + .. versionadded:: 3.5 + .. versionchanged:: 3.11 It is now always available. - .. versionadded:: 3.5 - .. impl-detail:: diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index f388375045c912a..4f07b9f6040d240 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -274,16 +274,16 @@ Directory and files operations .. audit-event:: shutil.copytree src,dst shutil.copytree - .. versionchanged:: 3.3 - Copy metadata when *symlinks* is false. - Now returns *dst*. - .. versionchanged:: 3.2 Added the *copy_function* argument to be able to provide a custom copy function. Added the *ignore_dangling_symlinks* argument to silence dangling symlinks errors when *symlinks* is false. + .. versionchanged:: 3.3 + Copy metadata when *symlinks* is false. + Now returns *dst*. + .. versionchanged:: 3.8 Platform-specific fast-copy syscalls may be used internally in order to copy the file more efficiently. See diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index 351c44b1915159f..380ba1090b39b3b 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -16,12 +16,12 @@ always available. On POSIX systems where Python was built with the standard ``configure`` script, this contains the ABI flags as specified by :pep:`3149`. + .. versionadded:: 3.2 + .. versionchanged:: 3.8 Default flags became an empty string (``m`` flag for pymalloc has been removed). - .. versionadded:: 3.2 - .. availability:: Unix. diff --git a/Doc/library/venv.rst b/Doc/library/venv.rst index aa18873f223a6bc..2e7ff345a062342 100644 --- a/Doc/library/venv.rst +++ b/Doc/library/venv.rst @@ -286,15 +286,15 @@ creation according to their needs, the :class:`EnvBuilder` class. the virtual environment. - .. versionchanged:: 3.12 - The attribute ``lib_path`` was added to the context, and the context - object was documented. - .. versionchanged:: 3.11 The *venv* :ref:`sysconfig installation scheme <installation_paths>` is used to construct the paths of the created directories. + .. versionchanged:: 3.12 + The attribute ``lib_path`` was added to the context, and the context + object was documented. + .. method:: create_configuration(context) Creates the ``pyvenv.cfg`` configuration file in the environment. diff --git a/Doc/using/venv-create.inc b/Doc/using/venv-create.inc index 1cf438b198a9afa..354eb1541ceac28 100644 --- a/Doc/using/venv-create.inc +++ b/Doc/using/venv-create.inc @@ -14,14 +14,14 @@ used at environment creation time). It also creates an (initially empty) ``Lib\site-packages``). If an existing directory is specified, it will be re-used. +.. versionchanged:: 3.5 + The use of ``venv`` is now recommended for creating virtual environments. + .. deprecated:: 3.6 ``pyvenv`` was the recommended tool for creating virtual environments for Python 3.3 and 3.4, and is :ref:`deprecated in Python 3.6 <whatsnew36-venv>`. -.. versionchanged:: 3.5 - The use of ``venv`` is now recommended for creating virtual environments. - .. highlight:: none On Windows, invoke the ``venv`` command as follows:: From 8f602981ba95273f036968cfc5ac28fdcd1808fa Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Mon, 19 Feb 2024 20:03:42 +0200 Subject: [PATCH 359/507] gh-115664: Fix versionadded and versionchanged directives in multiprocessing.rst (GH-115665) --- Doc/library/multiprocessing.rst | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst index 41ccd5f88186f6b..d88c25ef4de506c 100644 --- a/Doc/library/multiprocessing.rst +++ b/Doc/library/multiprocessing.rst @@ -150,18 +150,18 @@ to start a process. These *start methods* are over Unix pipes such as Linux. -.. versionchanged:: 3.8 - - On macOS, the *spawn* start method is now the default. The *fork* start - method should be considered unsafe as it can lead to crashes of the - subprocess as macOS system libraries may start threads. See :issue:`33725`. - .. versionchanged:: 3.4 *spawn* added on all POSIX platforms, and *forkserver* added for some POSIX platforms. Child processes no longer inherit all of the parents inheritable handles on Windows. +.. versionchanged:: 3.8 + + On macOS, the *spawn* start method is now the default. The *fork* start + method should be considered unsafe as it can lead to crashes of the + subprocess as macOS system libraries may start threads. See :issue:`33725`. + On POSIX using the *spawn* or *forkserver* start methods will also start a *resource tracker* process which tracks the unlinked named system resources (such as named semaphores or @@ -519,7 +519,7 @@ The :mod:`multiprocessing` package mostly replicates the API of the to the process. .. versionchanged:: 3.3 - Added the *daemon* argument. + Added the *daemon* parameter. .. method:: run() @@ -1245,8 +1245,7 @@ Connection objects are usually created using Connection objects themselves can now be transferred between processes using :meth:`Connection.send` and :meth:`Connection.recv`. - .. versionadded:: 3.3 - Connection objects now support the context management protocol -- see + Connection objects also now support the context management protocol -- see :ref:`typecontextmanager`. :meth:`~contextmanager.__enter__` returns the connection object, and :meth:`~contextmanager.__exit__` calls :meth:`close`. @@ -2250,11 +2249,11 @@ with the :class:`Pool` class. as CPython does not assure that the finalizer of the pool will be called (see :meth:`object.__del__` for more information). - .. versionadded:: 3.2 - *maxtasksperchild* + .. versionchanged:: 3.2 + Added the *maxtasksperchild* parameter. - .. versionadded:: 3.4 - *context* + .. versionchanged:: 3.4 + Added the *context* parameter. .. versionchanged:: 3.13 *processes* uses :func:`os.process_cpu_count` by default, instead of @@ -2380,7 +2379,7 @@ with the :class:`Pool` class. Wait for the worker processes to exit. One must call :meth:`close` or :meth:`terminate` before using :meth:`join`. - .. versionadded:: 3.3 + .. versionchanged:: 3.3 Pool objects now support the context management protocol -- see :ref:`typecontextmanager`. :meth:`~contextmanager.__enter__` returns the pool object, and :meth:`~contextmanager.__exit__` calls :meth:`terminate`. @@ -2549,7 +2548,7 @@ multiple connections at the same time. The address from which the last accepted connection came. If this is unavailable then it is ``None``. - .. versionadded:: 3.3 + .. versionchanged:: 3.3 Listener objects now support the context management protocol -- see :ref:`typecontextmanager`. :meth:`~contextmanager.__enter__` returns the listener object, and :meth:`~contextmanager.__exit__` calls :meth:`close`. From 57d31ec3598429789492e0b3544efaaffca5799f Mon Sep 17 00:00:00 2001 From: Naglis Jonaitis <827324+naglis@users.noreply.github.com> Date: Mon, 19 Feb 2024 20:19:14 +0200 Subject: [PATCH 360/507] Fix typo in multiprocessing docs (#115650) --- Doc/library/multiprocessing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst index d88c25ef4de506c..0b87de4c61e6aa2 100644 --- a/Doc/library/multiprocessing.rst +++ b/Doc/library/multiprocessing.rst @@ -2978,7 +2978,7 @@ Beware of replacing :data:`sys.stdin` with a "file like object" The *spawn* and *forkserver* start methods ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -There are a few extra restriction which don't apply to the *fork* +There are a few extra restrictions which don't apply to the *fork* start method. More picklability From 6cd18c75a41a74cab69ebef0b7def3e48421bdd1 Mon Sep 17 00:00:00 2001 From: Steve Dower <steve.dower@python.org> Date: Mon, 19 Feb 2024 20:36:20 +0000 Subject: [PATCH 361/507] gh-115543: Update py.exe to know about Python 3.13 and to install 3.12 by default (GH-115544) --- .../Windows/2024-02-15-23-16-31.gh-issue-115543.otrWnw.rst | 3 +++ PC/launcher2.c | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Windows/2024-02-15-23-16-31.gh-issue-115543.otrWnw.rst diff --git a/Misc/NEWS.d/next/Windows/2024-02-15-23-16-31.gh-issue-115543.otrWnw.rst b/Misc/NEWS.d/next/Windows/2024-02-15-23-16-31.gh-issue-115543.otrWnw.rst new file mode 100644 index 000000000000000..ebd15c83b83491f --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-02-15-23-16-31.gh-issue-115543.otrWnw.rst @@ -0,0 +1,3 @@ +:ref:`launcher` can now detect Python 3.13 when installed from the Microsoft +Store, and will install Python 3.12 by default when +:envvar:`PYLAUNCHER_ALLOW_INSTALL` is set. diff --git a/PC/launcher2.c b/PC/launcher2.c index 90b0fdebd3bdfb9..139aa61bbe5cc2a 100644 --- a/PC/launcher2.c +++ b/PC/launcher2.c @@ -1962,6 +1962,7 @@ struct AppxSearchInfo { struct AppxSearchInfo APPX_SEARCH[] = { // Releases made through the Store + { L"PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0", L"3.13", 10 }, { L"PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0", L"3.12", 10 }, { L"PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0", L"3.11", 10 }, { L"PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0", L"3.10", 10 }, @@ -1971,6 +1972,7 @@ struct AppxSearchInfo APPX_SEARCH[] = { // Side-loadable releases. Note that the publisher ID changes whenever we // renew our code-signing certificate, so the newer ID has a higher // priority (lower sortKey) + { L"PythonSoftwareFoundation.Python.3.13_3847v3x7pw1km", L"3.13", 11 }, { L"PythonSoftwareFoundation.Python.3.12_3847v3x7pw1km", L"3.12", 11 }, { L"PythonSoftwareFoundation.Python.3.11_3847v3x7pw1km", L"3.11", 11 }, { L"PythonSoftwareFoundation.Python.3.11_hd69rhyc2wevp", L"3.11", 12 }, @@ -2052,7 +2054,8 @@ struct StoreSearchInfo { struct StoreSearchInfo STORE_SEARCH[] = { - { L"3", /* 3.11 */ L"9NRWMJP3717K" }, + { L"3", /* 3.12 */ L"9NCVDN91XZQP" }, + { L"3.13", L"9PNRBTZXMB4Z" }, { L"3.12", L"9NCVDN91XZQP" }, { L"3.11", L"9NRWMJP3717K" }, { L"3.10", L"9PJPW5LDXLZ5" }, From c2cb31bbe1262213085c425bc853d6587c66cae9 Mon Sep 17 00:00:00 2001 From: Jason Zhang <yurenzhang2017@gmail.com> Date: Mon, 19 Feb 2024 22:36:11 +0000 Subject: [PATCH 362/507] gh-115539: Allow enum.Flag to have None members (GH-115636) --- Lib/enum.py | 57 +++++++++++++++++++++++++++---------------- Lib/test/test_enum.py | 16 ++++++++++++ 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/Lib/enum.py b/Lib/enum.py index 98a8966f5eb1599..d10b99615981ba2 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -279,9 +279,10 @@ def __set_name__(self, enum_class, member_name): enum_member._sort_order_ = len(enum_class._member_names_) if Flag is not None and issubclass(enum_class, Flag): - enum_class._flag_mask_ |= value - if _is_single_bit(value): - enum_class._singles_mask_ |= value + if isinstance(value, int): + enum_class._flag_mask_ |= value + if _is_single_bit(value): + enum_class._singles_mask_ |= value enum_class._all_bits_ = 2 ** ((enum_class._flag_mask_).bit_length()) - 1 # If another member with the same value was already defined, the @@ -309,6 +310,7 @@ def __set_name__(self, enum_class, member_name): elif ( Flag is not None and issubclass(enum_class, Flag) + and isinstance(value, int) and _is_single_bit(value) ): # no other instances found, record this member in _member_names_ @@ -1558,37 +1560,50 @@ def __str__(self): def __bool__(self): return bool(self._value_) + def _get_value(self, flag): + if isinstance(flag, self.__class__): + return flag._value_ + elif self._member_type_ is not object and isinstance(flag, self._member_type_): + return flag + return NotImplemented + def __or__(self, other): - if isinstance(other, self.__class__): - other = other._value_ - elif self._member_type_ is not object and isinstance(other, self._member_type_): - other = other - else: + other_value = self._get_value(other) + if other_value is NotImplemented: return NotImplemented + + for flag in self, other: + if self._get_value(flag) is None: + raise TypeError(f"'{flag}' cannot be combined with other flags with |") value = self._value_ - return self.__class__(value | other) + return self.__class__(value | other_value) def __and__(self, other): - if isinstance(other, self.__class__): - other = other._value_ - elif self._member_type_ is not object and isinstance(other, self._member_type_): - other = other - else: + other_value = self._get_value(other) + if other_value is NotImplemented: return NotImplemented + + for flag in self, other: + if self._get_value(flag) is None: + raise TypeError(f"'{flag}' cannot be combined with other flags with &") value = self._value_ - return self.__class__(value & other) + return self.__class__(value & other_value) def __xor__(self, other): - if isinstance(other, self.__class__): - other = other._value_ - elif self._member_type_ is not object and isinstance(other, self._member_type_): - other = other - else: + other_value = self._get_value(other) + if other_value is NotImplemented: return NotImplemented + + for flag in self, other: + if self._get_value(flag) is None: + raise TypeError(f"'{flag}' cannot be combined with other flags with ^") value = self._value_ - return self.__class__(value ^ other) + return self.__class__(value ^ other_value) def __invert__(self): + if self._get_value(self) is None: + raise TypeError(f"'{self}' cannot be inverted") + if self._inverted_ is None: if self._boundary_ in (EJECT, KEEP): self._inverted_ = self.__class__(~self._value_) diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 61060f3dc29fd46..cf3e042de1a4b4f 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -1048,6 +1048,22 @@ class TestPlainEnumFunction(_EnumTests, _PlainOutputTests, unittest.TestCase): class TestPlainFlagClass(_EnumTests, _PlainOutputTests, _FlagTests, unittest.TestCase): enum_type = Flag + def test_none_member(self): + class FlagWithNoneMember(Flag): + A = 1 + E = None + + self.assertEqual(FlagWithNoneMember.A.value, 1) + self.assertIs(FlagWithNoneMember.E.value, None) + with self.assertRaisesRegex(TypeError, r"'FlagWithNoneMember.E' cannot be combined with other flags with |"): + FlagWithNoneMember.A | FlagWithNoneMember.E + with self.assertRaisesRegex(TypeError, r"'FlagWithNoneMember.E' cannot be combined with other flags with &"): + FlagWithNoneMember.E & FlagWithNoneMember.A + with self.assertRaisesRegex(TypeError, r"'FlagWithNoneMember.E' cannot be combined with other flags with \^"): + FlagWithNoneMember.A ^ FlagWithNoneMember.E + with self.assertRaisesRegex(TypeError, r"'FlagWithNoneMember.E' cannot be inverted"): + ~FlagWithNoneMember.E + class TestPlainFlagFunction(_EnumTests, _PlainOutputTests, _FlagTests, unittest.TestCase): enum_type = Flag From 9f8a9e8ac74d588a2958dfa04085a0d78f9dcba9 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger <rhettinger@users.noreply.github.com> Date: Mon, 19 Feb 2024 21:22:07 -0600 Subject: [PATCH 363/507] Modernize the Sorting HowTo guide (gh-115479) --- Doc/howto/sorting.rst | 60 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/Doc/howto/sorting.rst b/Doc/howto/sorting.rst index 38dd09f0a721d2f..fffef481fe7ebb7 100644 --- a/Doc/howto/sorting.rst +++ b/Doc/howto/sorting.rst @@ -4,7 +4,6 @@ Sorting HOW TO ************** :Author: Andrew Dalke and Raymond Hettinger -:Release: 0.1 Python lists have a built-in :meth:`list.sort` method that modifies the list @@ -56,7 +55,7 @@ For example, here's a case-insensitive string comparison: .. doctest:: - >>> sorted("This is a test string from Andrew".split(), key=str.lower) + >>> sorted("This is a test string from Andrew".split(), key=str.casefold) ['a', 'Andrew', 'from', 'is', 'string', 'test', 'This'] The value of the *key* parameter should be a function (or other callable) that @@ -97,10 +96,14 @@ The same technique works for objects with named attributes. For example: >>> sorted(student_objects, key=lambda student: student.age) # sort by age [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)] -Operator Module Functions -========================= +Objects with named attributes can be made by a regular class as shown +above, or they can be instances of :class:`~dataclasses.dataclass` or +a :term:`named tuple`. -The key-function patterns shown above are very common, so Python provides +Operator Module Functions and Partial Function Evaluation +========================================================= + +The :term:`key function` patterns shown above are very common, so Python provides convenience functions to make accessor functions easier and faster. The :mod:`operator` module has :func:`~operator.itemgetter`, :func:`~operator.attrgetter`, and a :func:`~operator.methodcaller` function. @@ -128,6 +131,24 @@ sort by *grade* then by *age*: >>> sorted(student_objects, key=attrgetter('grade', 'age')) [('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)] +The :mod:`functools` module provides another helpful tool for making +key-functions. The :func:`~functools.partial` function can reduce the +`arity <https://en.wikipedia.org/wiki/Arity>`_ of a multi-argument +function making it suitable for use as a key-function. + +.. doctest:: + + >>> from functools import partial + >>> from unicodedata import normalize + + >>> names = 'Zoë Åbjørn Núñez Élana Zeke Abe Nubia Eloise'.split() + + >>> sorted(names, key=partial(normalize, 'NFD')) + ['Abe', 'Åbjørn', 'Eloise', 'Élana', 'Nubia', 'Núñez', 'Zeke', 'Zoë'] + + >>> sorted(names, key=partial(normalize, 'NFC')) + ['Abe', 'Eloise', 'Nubia', 'Núñez', 'Zeke', 'Zoë', 'Åbjørn', 'Élana'] + Ascending and Descending ======================== @@ -200,6 +221,8 @@ This idiom is called Decorate-Sort-Undecorate after its three steps: For example, to sort the student data by *grade* using the DSU approach: +.. doctest:: + >>> decorated = [(student.grade, i, student) for i, student in enumerate(student_objects)] >>> decorated.sort() >>> [student for grade, i, student in decorated] # undecorate @@ -282,7 +305,11 @@ Odds and Ends [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)] However, note that ``<`` can fall back to using :meth:`~object.__gt__` if - :meth:`~object.__lt__` is not implemented (see :func:`object.__lt__`). + :meth:`~object.__lt__` is not implemented (see :func:`object.__lt__` + for details on the mechanics). To avoid surprises, :pep:`8` + recommends that all six comparison methods be implemented. + The :func:`~functools.total_ordering` decorator is provided to make that + task easier. * Key functions need not depend directly on the objects being sorted. A key function can also access external resources. For instance, if the student grades @@ -295,3 +322,24 @@ Odds and Ends >>> newgrades = {'john': 'F', 'jane':'A', 'dave': 'C'} >>> sorted(students, key=newgrades.__getitem__) ['jane', 'dave', 'john'] + +Partial Sorts +============= + +Some applications require only some of the data to be ordered. The standard +library provides several tools that do less work than a full sort: + +* :func:`min` and :func:`max` return the smallest and largest values, + respectively. These functions make a single pass over the input data and + require almost no auxiliary memory. + +* :func:`heapq.nsmallest` and :func:`heapq.nlargest` return + the *n* smallest and largest values, respectively. These functions + make a single pass over the data keeping only *n* elements in memory + at a time. For values of *n* that are small relative to the number of + inputs, these functions make far fewer comparisons than a full sort. + +* :func:`heapq.heappush` and :func:`heapq.heappop` create and maintain a + partially sorted arrangement of data that keeps the smallest element + at position ``0``. These functions are suitable for implementing + priority queues which are commonly used for task scheduling. From 2aaef562364b3dea64d7eee1e7dd9e51cb806f91 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger <rhettinger@users.noreply.github.com> Date: Tue, 20 Feb 2024 01:51:56 -0600 Subject: [PATCH 364/507] Make the title match the content (GH-115702) --- Doc/howto/descriptor.rst | 6 +++--- Doc/howto/sorting.rst | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/howto/descriptor.rst b/Doc/howto/descriptor.rst index 75346f2c7618c2e..7d787c1c42df648 100644 --- a/Doc/howto/descriptor.rst +++ b/Doc/howto/descriptor.rst @@ -1,8 +1,8 @@ .. _descriptorhowto: -====================== -Descriptor HowTo Guide -====================== +================ +Descriptor Guide +================ :Author: Raymond Hettinger :Contact: <python at rcn dot com> diff --git a/Doc/howto/sorting.rst b/Doc/howto/sorting.rst index fffef481fe7ebb7..b98f91e023bdfc7 100644 --- a/Doc/howto/sorting.rst +++ b/Doc/howto/sorting.rst @@ -1,7 +1,7 @@ .. _sortinghowto: -Sorting HOW TO -************** +Sorting Techniques +****************** :Author: Andrew Dalke and Raymond Hettinger From acda1757bc682922292215906459c2735ee99c04 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger <rhettinger@users.noreply.github.com> Date: Tue, 20 Feb 2024 01:53:25 -0600 Subject: [PATCH 365/507] gh-113157: Document and test __get__ for MethodType (gh-115492) --- Doc/howto/descriptor.rst | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Doc/howto/descriptor.rst b/Doc/howto/descriptor.rst index 7d787c1c42df648..e72386a4da4f8a4 100644 --- a/Doc/howto/descriptor.rst +++ b/Doc/howto/descriptor.rst @@ -1192,6 +1192,10 @@ roughly equivalent to: "Emulate method_getattro() in Objects/classobject.c" return getattr(self.__func__, name) + def __get__(self, obj, objtype=None): + "Emulate method_descr_get() in Objects/classobject.c" + return self + To support automatic creation of methods, functions include the :meth:`__get__` method for binding methods during attribute access. This means that functions are non-data descriptors that return bound methods @@ -1214,8 +1218,20 @@ descriptor works in practice: .. testcode:: class D: - def f(self, x): - return x + def f(self): + return self + + class D2: + pass + +.. doctest:: + :hide: + + >>> d = D() + >>> d2 = D2() + >>> d2.f = d.f.__get__(d2, D2) + >>> d2.f() is d + True The function has a :term:`qualified name` attribute to support introspection: From 7b21403ccd16c480812a1e857c0ee2deca592be0 Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Tue, 20 Feb 2024 09:39:55 +0000 Subject: [PATCH 366/507] GH-112354: Initial implementation of warm up on exits and trace-stitching (GH-114142) --- .gitattributes | 1 + Include/cpython/optimizer.h | 25 ++- Include/cpython/pystate.h | 2 + Include/internal/pycore_interp.h | 5 +- Include/internal/pycore_jit.h | 2 +- Include/internal/pycore_opcode_metadata.h | 54 ++--- Include/internal/pycore_uop_ids.h | 8 +- Include/internal/pycore_uop_metadata.h | 36 +-- Lib/test/test_frame.py | 1 + Lib/test/test_generated_cases.py | 11 + Modules/_testinternalcapi.c | 4 +- Python/bytecodes.c | 122 ++++++++--- Python/ceval.c | 47 ++-- Python/ceval_macros.h | 31 ++- Python/executor_cases.c.h | 96 ++++++-- Python/generated_cases.c.h | 31 +-- Python/jit.c | 13 +- Python/optimizer.c | 205 ++++++++++++++---- Python/pylifecycle.c | 4 +- Python/pystate.c | 2 + Python/tier2_engine.md | 150 +++++++++++++ Python/tier2_redundancy_eliminator_cases.c.h | 14 +- Tools/c-analyzer/cpython/_parser.py | 1 + Tools/c-analyzer/cpython/ignored.tsv | 5 + Tools/cases_generator/analyzer.py | 22 +- Tools/cases_generator/generators_common.py | 3 + .../opcode_metadata_generator.py | 1 + Tools/cases_generator/tier2_generator.py | 16 ++ Tools/jit/template.c | 30 ++- 29 files changed, 744 insertions(+), 198 deletions(-) create mode 100644 Python/tier2_engine.md diff --git a/.gitattributes b/.gitattributes index c984797e1ac7c60..159cd83ff7407d6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -77,6 +77,7 @@ Include/internal/pycore_opcode.h generated Include/internal/pycore_opcode_metadata.h generated Include/internal/pycore_*_generated.h generated Include/internal/pycore_uop_ids.h generated +Include/internal/pycore_uop_metadata.h generated Include/opcode.h generated Include/opcode_ids.h generated Include/token.h generated diff --git a/Include/cpython/optimizer.h b/Include/cpython/optimizer.h index f710ca76b2ba24a..fe54d1ddfe61293 100644 --- a/Include/cpython/optimizer.h +++ b/Include/cpython/optimizer.h @@ -33,16 +33,28 @@ typedef struct { typedef struct { uint16_t opcode; uint16_t oparg; - uint32_t target; + union { + uint32_t target; + uint32_t exit_index; + }; uint64_t operand; // A cache entry } _PyUOpInstruction; +typedef struct _exit_data { + uint32_t target; + int16_t temperature; + const struct _PyExecutorObject *executor; +} _PyExitData; + typedef struct _PyExecutorObject { PyObject_VAR_HEAD + const _PyUOpInstruction *trace; _PyVMData vm_data; /* Used by the VM, but opaque to the optimizer */ - void *jit_code; + uint32_t exit_count; + uint32_t code_size; size_t jit_size; - _PyUOpInstruction trace[1]; + void *jit_code; + _PyExitData exits[1]; } _PyExecutorObject; typedef struct _PyOptimizerObject _PyOptimizerObject; @@ -59,6 +71,7 @@ typedef struct _PyOptimizerObject { /* These thresholds are treated as signed so do not exceed INT16_MAX * Use INT16_MAX to indicate that the optimizer should never be called */ uint16_t resume_threshold; + uint16_t side_threshold; uint16_t backedge_threshold; /* Data needed by the optimizer goes here, but is opaque to the VM */ } _PyOptimizerObject; @@ -73,16 +86,16 @@ PyAPI_FUNC(int) PyUnstable_Replace_Executor(PyCodeObject *code, _Py_CODEUNIT *in _PyOptimizerObject *_Py_SetOptimizer(PyInterpreterState *interp, _PyOptimizerObject* optimizer); -PyAPI_FUNC(void) PyUnstable_SetOptimizer(_PyOptimizerObject* optimizer); +PyAPI_FUNC(int) PyUnstable_SetOptimizer(_PyOptimizerObject* optimizer); PyAPI_FUNC(_PyOptimizerObject *) PyUnstable_GetOptimizer(void); PyAPI_FUNC(_PyExecutorObject *) PyUnstable_GetExecutor(PyCodeObject *code, int offset); int -_PyOptimizer_Optimize(struct _PyInterpreterFrame *frame, _Py_CODEUNIT *start, PyObject **stack_pointer); +_PyOptimizer_Optimize(struct _PyInterpreterFrame *frame, _Py_CODEUNIT *start, PyObject **stack_pointer, _PyExecutorObject **exec_ptr); -void _Py_ExecutorInit(_PyExecutorObject *, _PyBloomFilter *); +void _Py_ExecutorInit(_PyExecutorObject *, const _PyBloomFilter *); void _Py_ExecutorClear(_PyExecutorObject *); void _Py_BloomFilter_Init(_PyBloomFilter *); void _Py_BloomFilter_Add(_PyBloomFilter *bloom, void *obj); diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 9bc8758e72bd8f2..b99450a8a8d0935 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -212,6 +212,8 @@ struct _ts { /* The thread's exception stack entry. (Always the last entry.) */ _PyErr_StackItem exc_state; + PyObject *previous_executor; + }; #ifdef Py_DEBUG diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 567d6a9bd510ab4..06eba665c80e930 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -237,10 +237,13 @@ struct _is { struct callable_cache callable_cache; _PyOptimizerObject *optimizer; _PyExecutorObject *executor_list_head; - /* These values are shifted and offset to speed up check in JUMP_BACKWARD */ + + /* These two values are shifted and offset to speed up check in JUMP_BACKWARD */ uint32_t optimizer_resume_threshold; uint32_t optimizer_backedge_threshold; + uint16_t optimizer_side_threshold; + uint32_t next_func_version; _rare_events rare_events; PyDict_WatchCallback builtins_dict_watcher; diff --git a/Include/internal/pycore_jit.h b/Include/internal/pycore_jit.h index 0b71eb6f758ac62..17bd23f0752be20 100644 --- a/Include/internal/pycore_jit.h +++ b/Include/internal/pycore_jit.h @@ -13,7 +13,7 @@ extern "C" { typedef _Py_CODEUNIT *(*jit_func)(_PyInterpreterFrame *frame, PyObject **stack_pointer, PyThreadState *tstate); -int _PyJIT_Compile(_PyExecutorObject *executor, _PyUOpInstruction *trace, size_t length); +int _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction *trace, size_t length); void _PyJIT_Free(_PyExecutorObject *executor); #endif // _Py_JIT diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 6b60a6fbffdc5e4..177dd302f73171a 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -909,8 +909,9 @@ enum InstructionFormat { #define HAS_DEOPT_FLAG (128) #define HAS_ERROR_FLAG (256) #define HAS_ESCAPES_FLAG (512) -#define HAS_PURE_FLAG (1024) -#define HAS_PASSTHROUGH_FLAG (2048) +#define HAS_EXIT_FLAG (1024) +#define HAS_PURE_FLAG (2048) +#define HAS_PASSTHROUGH_FLAG (4096) #define OPCODE_HAS_ARG(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_ARG_FLAG)) #define OPCODE_HAS_CONST(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_CONST_FLAG)) #define OPCODE_HAS_NAME(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_NAME_FLAG)) @@ -921,6 +922,7 @@ enum InstructionFormat { #define OPCODE_HAS_DEOPT(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_DEOPT_FLAG)) #define OPCODE_HAS_ERROR(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_ERROR_FLAG)) #define OPCODE_HAS_ESCAPES(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_ESCAPES_FLAG)) +#define OPCODE_HAS_EXIT(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_EXIT_FLAG)) #define OPCODE_HAS_PURE(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_PURE_FLAG)) #define OPCODE_HAS_PASSTHROUGH(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_PASSTHROUGH_FLAG)) @@ -945,14 +947,14 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[268] = { [BEFORE_ASYNC_WITH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BEFORE_WITH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, - [BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, - [BINARY_OP_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BINARY_OP_INPLACE_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BINARY_OP_MULTIPLY_FLOAT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, - [BINARY_OP_MULTIPLY_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, - [BINARY_OP_SUBTRACT_FLOAT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, - [BINARY_OP_SUBTRACT_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, + [BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, + [BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG }, + [BINARY_OP_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BINARY_OP_INPLACE_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BINARY_OP_MULTIPLY_FLOAT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, + [BINARY_OP_MULTIPLY_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG }, + [BINARY_OP_SUBTRACT_FLOAT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, + [BINARY_OP_SUBTRACT_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG }, [BINARY_SLICE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_SUBSCR_DICT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, @@ -1060,16 +1062,16 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[268] = { [LOAD_ATTR] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [LOAD_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR_METHOD_NO_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR_METHOD_WITH_VALUES] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, + [LOAD_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR_METHOD_NO_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR_METHOD_WITH_VALUES] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, [LOAD_ATTR_MODULE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, + [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, [LOAD_ATTR_PROPERTY] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR_SLOT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [LOAD_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR_SLOT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, + [LOAD_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, [LOAD_BUILD_CLASS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_CONST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG | HAS_PURE_FLAG }, [LOAD_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, @@ -1118,8 +1120,8 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[268] = { [SET_FUNCTION_ATTRIBUTE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, [SET_UPDATE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [STORE_ATTR] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [STORE_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IXC000, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [STORE_ATTR_SLOT] = { true, INSTR_FMT_IXC000, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [STORE_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IXC000, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, + [STORE_ATTR_SLOT] = { true, INSTR_FMT_IXC000, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, [STORE_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [STORE_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ESCAPES_FLAG }, [STORE_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, @@ -1133,12 +1135,12 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[268] = { [STORE_SUBSCR_LIST_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [SWAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_PURE_FLAG }, [TO_BOOL] = { true, INSTR_FMT_IXC00, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [TO_BOOL_ALWAYS_TRUE] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, - [TO_BOOL_BOOL] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, - [TO_BOOL_INT] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, - [TO_BOOL_LIST] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, - [TO_BOOL_NONE] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, - [TO_BOOL_STR] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, + [TO_BOOL_ALWAYS_TRUE] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, + [TO_BOOL_BOOL] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, + [TO_BOOL_INT] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, + [TO_BOOL_LIST] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, + [TO_BOOL_NONE] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, + [TO_BOOL_STR] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, [UNARY_INVERT] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [UNARY_NEGATIVE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [UNARY_NOT] = { true, INSTR_FMT_IX, HAS_PURE_FLAG }, diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index 9bb537d355055db..ed800a73796da05 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -139,7 +139,6 @@ extern "C" { #define _CONTAINS_OP CONTAINS_OP #define _CHECK_EG_MATCH CHECK_EG_MATCH #define _CHECK_EXC_MATCH CHECK_EXC_MATCH -#define _JUMP_BACKWARD JUMP_BACKWARD #define _POP_JUMP_IF_FALSE 339 #define _POP_JUMP_IF_TRUE 340 #define _IS_NONE 341 @@ -237,8 +236,11 @@ extern "C" { #define _CHECK_GLOBALS 384 #define _CHECK_BUILTINS 385 #define _INTERNAL_INCREMENT_OPT_COUNTER 386 -#define _CHECK_VALIDITY_AND_SET_IP 387 -#define MAX_UOP_ID 387 +#define _COLD_EXIT 387 +#define _START_EXECUTOR 388 +#define _FATAL_ERROR 389 +#define _CHECK_VALIDITY_AND_SET_IP 390 +#define MAX_UOP_ID 390 #ifdef __cplusplus } diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 163a0320aa22985..1e2dfecd9cf8afe 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -32,22 +32,22 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_UNARY_NEGATIVE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_UNARY_NOT] = HAS_PURE_FLAG, [_TO_BOOL] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_TO_BOOL_BOOL] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, - [_TO_BOOL_INT] = HAS_DEOPT_FLAG, - [_TO_BOOL_LIST] = HAS_DEOPT_FLAG, - [_TO_BOOL_NONE] = HAS_DEOPT_FLAG, - [_TO_BOOL_STR] = HAS_DEOPT_FLAG, - [_TO_BOOL_ALWAYS_TRUE] = HAS_DEOPT_FLAG, + [_TO_BOOL_BOOL] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_PASSTHROUGH_FLAG, + [_TO_BOOL_INT] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, + [_TO_BOOL_LIST] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, + [_TO_BOOL_NONE] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, + [_TO_BOOL_STR] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, + [_TO_BOOL_ALWAYS_TRUE] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, [_UNARY_INVERT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_GUARD_BOTH_INT] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, + [_GUARD_BOTH_INT] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_PASSTHROUGH_FLAG, [_BINARY_OP_MULTIPLY_INT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, [_BINARY_OP_ADD_INT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, [_BINARY_OP_SUBTRACT_INT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, - [_GUARD_BOTH_FLOAT] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, + [_GUARD_BOTH_FLOAT] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_PASSTHROUGH_FLAG, [_BINARY_OP_MULTIPLY_FLOAT] = HAS_PURE_FLAG, [_BINARY_OP_ADD_FLOAT] = HAS_PURE_FLAG, [_BINARY_OP_SUBTRACT_FLOAT] = HAS_PURE_FLAG, - [_GUARD_BOTH_UNICODE] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, + [_GUARD_BOTH_UNICODE] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_PASSTHROUGH_FLAG, [_BINARY_OP_ADD_UNICODE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG, [_BINARY_SUBSCR] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_BINARY_SLICE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, @@ -112,7 +112,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_LOAD_SUPER_ATTR_ATTR] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_LOAD_SUPER_ATTR_METHOD] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_LOAD_ATTR] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_GUARD_TYPE_VERSION] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, + [_GUARD_TYPE_VERSION] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_PASSTHROUGH_FLAG, [_CHECK_MANAGED_OBJECT_HAS_VALUES] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, [_LOAD_ATTR_INSTANCE_VALUE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, [_CHECK_ATTR_MODULE] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, @@ -193,14 +193,14 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_COPY] = HAS_ARG_FLAG | HAS_PURE_FLAG, [_BINARY_OP] = HAS_ARG_FLAG | HAS_ERROR_FLAG, [_SWAP] = HAS_ARG_FLAG | HAS_PURE_FLAG, - [_GUARD_IS_TRUE_POP] = HAS_DEOPT_FLAG, - [_GUARD_IS_FALSE_POP] = HAS_DEOPT_FLAG, - [_GUARD_IS_NONE_POP] = HAS_DEOPT_FLAG, - [_GUARD_IS_NOT_NONE_POP] = HAS_DEOPT_FLAG, + [_GUARD_IS_TRUE_POP] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, + [_GUARD_IS_FALSE_POP] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, + [_GUARD_IS_NONE_POP] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, + [_GUARD_IS_NOT_NONE_POP] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, [_JUMP_TO_TOP] = HAS_EVAL_BREAK_FLAG, [_SET_IP] = 0, [_SAVE_RETURN_OFFSET] = HAS_ARG_FLAG, - [_EXIT_TRACE] = HAS_DEOPT_FLAG, + [_EXIT_TRACE] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, [_CHECK_VALIDITY] = HAS_DEOPT_FLAG, [_LOAD_CONST_INLINE] = HAS_PURE_FLAG, [_LOAD_CONST_INLINE_BORROW] = HAS_PURE_FLAG, @@ -209,6 +209,9 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_CHECK_GLOBALS] = HAS_DEOPT_FLAG, [_CHECK_BUILTINS] = HAS_DEOPT_FLAG, [_INTERNAL_INCREMENT_OPT_COUNTER] = 0, + [_COLD_EXIT] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_START_EXECUTOR] = 0, + [_FATAL_ERROR] = HAS_ESCAPES_FLAG, [_CHECK_VALIDITY_AND_SET_IP] = HAS_DEOPT_FLAG, }; @@ -266,6 +269,7 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_CHECK_STACK_SPACE] = "_CHECK_STACK_SPACE", [_CHECK_VALIDITY] = "_CHECK_VALIDITY", [_CHECK_VALIDITY_AND_SET_IP] = "_CHECK_VALIDITY_AND_SET_IP", + [_COLD_EXIT] = "_COLD_EXIT", [_COMPARE_OP] = "_COMPARE_OP", [_COMPARE_OP_FLOAT] = "_COMPARE_OP_FLOAT", [_COMPARE_OP_INT] = "_COMPARE_OP_INT", @@ -285,6 +289,7 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_END_SEND] = "_END_SEND", [_EXIT_INIT_CHECK] = "_EXIT_INIT_CHECK", [_EXIT_TRACE] = "_EXIT_TRACE", + [_FATAL_ERROR] = "_FATAL_ERROR", [_FORMAT_SIMPLE] = "_FORMAT_SIMPLE", [_FORMAT_WITH_SPEC] = "_FORMAT_WITH_SPEC", [_FOR_ITER_TIER_TWO] = "_FOR_ITER_TIER_TWO", @@ -377,6 +382,7 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_SET_FUNCTION_ATTRIBUTE] = "_SET_FUNCTION_ATTRIBUTE", [_SET_IP] = "_SET_IP", [_SET_UPDATE] = "_SET_UPDATE", + [_START_EXECUTOR] = "_START_EXECUTOR", [_STORE_ATTR] = "_STORE_ATTR", [_STORE_ATTR_INSTANCE_VALUE] = "_STORE_ATTR_INSTANCE_VALUE", [_STORE_ATTR_SLOT] = "_STORE_ATTR_SLOT", diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py index f88206de550da08..f8812c281c2deba 100644 --- a/Lib/test/test_frame.py +++ b/Lib/test/test_frame.py @@ -331,6 +331,7 @@ def f(): # on the *very next* allocation: gc.collect() gc.set_threshold(1, 0, 0) + sys._clear_internal_caches() # Okay, so here's the nightmare scenario: # - We're tracing the resumption of a generator, which creates a new # frame object. diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index a7ad6c7320b4ee4..0d2ccd558d436d5 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -794,6 +794,17 @@ def test_annotated_op(self): self.run_cases_test(input, output) + def test_deopt_and_exit(self): + input = """ + pure op(OP, (arg1 -- out)) { + DEOPT_IF(1); + EXIT_IF(1); + } + """ + output = "" + with self.assertRaises(Exception): + self.run_cases_test(input, output) + class TestGeneratedAbstractCases(unittest.TestCase): def setUp(self) -> None: super().setUp() diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 3834f00009cea4e..bcc431a27001f25 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -977,7 +977,9 @@ set_optimizer(PyObject *self, PyObject *opt) if (opt == Py_None) { opt = NULL; } - PyUnstable_SetOptimizer((_PyOptimizerObject*)opt); + if (PyUnstable_SetOptimizer((_PyOptimizerObject*)opt) < 0) { + return NULL; + } Py_RETURN_NONE; } diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 6822e772e913e8d..2e0008e63f6e0c6 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -340,12 +340,12 @@ dummy_func( macro(TO_BOOL) = _SPECIALIZE_TO_BOOL + unused/2 + _TO_BOOL; inst(TO_BOOL_BOOL, (unused/1, unused/2, value -- value)) { - DEOPT_IF(!PyBool_Check(value)); + EXIT_IF(!PyBool_Check(value)); STAT_INC(TO_BOOL, hit); } inst(TO_BOOL_INT, (unused/1, unused/2, value -- res)) { - DEOPT_IF(!PyLong_CheckExact(value)); + EXIT_IF(!PyLong_CheckExact(value)); STAT_INC(TO_BOOL, hit); if (_PyLong_IsZero((PyLongObject *)value)) { assert(_Py_IsImmortal(value)); @@ -358,7 +358,7 @@ dummy_func( } inst(TO_BOOL_LIST, (unused/1, unused/2, value -- res)) { - DEOPT_IF(!PyList_CheckExact(value)); + EXIT_IF(!PyList_CheckExact(value)); STAT_INC(TO_BOOL, hit); res = Py_SIZE(value) ? Py_True : Py_False; DECREF_INPUTS(); @@ -366,13 +366,13 @@ dummy_func( inst(TO_BOOL_NONE, (unused/1, unused/2, value -- res)) { // This one is a bit weird, because we expect *some* failures: - DEOPT_IF(!Py_IsNone(value)); + EXIT_IF(!Py_IsNone(value)); STAT_INC(TO_BOOL, hit); res = Py_False; } inst(TO_BOOL_STR, (unused/1, unused/2, value -- res)) { - DEOPT_IF(!PyUnicode_CheckExact(value)); + EXIT_IF(!PyUnicode_CheckExact(value)); STAT_INC(TO_BOOL, hit); if (value == &_Py_STR(empty)) { assert(_Py_IsImmortal(value)); @@ -388,7 +388,7 @@ dummy_func( inst(TO_BOOL_ALWAYS_TRUE, (unused/1, version/2, value -- res)) { // This one is a bit weird, because we expect *some* failures: assert(version); - DEOPT_IF(Py_TYPE(value)->tp_version_tag != version); + EXIT_IF(Py_TYPE(value)->tp_version_tag != version); STAT_INC(TO_BOOL, hit); DECREF_INPUTS(); res = Py_True; @@ -412,8 +412,8 @@ dummy_func( }; op(_GUARD_BOTH_INT, (left, right -- left, right)) { - DEOPT_IF(!PyLong_CheckExact(left)); - DEOPT_IF(!PyLong_CheckExact(right)); + EXIT_IF(!PyLong_CheckExact(left)); + EXIT_IF(!PyLong_CheckExact(right)); } pure op(_BINARY_OP_MULTIPLY_INT, (left, right -- res)) { @@ -448,8 +448,8 @@ dummy_func( _GUARD_BOTH_INT + unused/1 + _BINARY_OP_SUBTRACT_INT; op(_GUARD_BOTH_FLOAT, (left, right -- left, right)) { - DEOPT_IF(!PyFloat_CheckExact(left)); - DEOPT_IF(!PyFloat_CheckExact(right)); + EXIT_IF(!PyFloat_CheckExact(left)); + EXIT_IF(!PyFloat_CheckExact(right)); } pure op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { @@ -484,8 +484,8 @@ dummy_func( _GUARD_BOTH_FLOAT + unused/1 + _BINARY_OP_SUBTRACT_FLOAT; op(_GUARD_BOTH_UNICODE, (left, right -- left, right)) { - DEOPT_IF(!PyUnicode_CheckExact(left)); - DEOPT_IF(!PyUnicode_CheckExact(right)); + EXIT_IF(!PyUnicode_CheckExact(left)); + EXIT_IF(!PyUnicode_CheckExact(right)); } pure op(_BINARY_OP_ADD_UNICODE, (left, right -- res)) { @@ -1904,7 +1904,7 @@ dummy_func( op(_GUARD_TYPE_VERSION, (type_version/2, owner -- owner)) { PyTypeObject *tp = Py_TYPE(owner); assert(type_version != 0); - DEOPT_IF(tp->tp_version_tag != type_version); + EXIT_IF(tp->tp_version_tag != type_version); } op(_CHECK_MANAGED_OBJECT_HAS_VALUES, (owner -- owner)) { @@ -2314,6 +2314,7 @@ dummy_func( } inst(JUMP_BACKWARD, (unused/1 --)) { + TIER_ONE_ONLY CHECK_EVAL_BREAKER(); assert(oparg <= INSTR_OFFSET()); JUMPBY(-oparg); @@ -2335,13 +2336,13 @@ dummy_func( oparg >>= 8; start--; } - int optimized = _PyOptimizer_Optimize(frame, start, stack_pointer); + _PyExecutorObject *executor; + int optimized = _PyOptimizer_Optimize(frame, start, stack_pointer, &executor); ERROR_IF(optimized < 0, error); if (optimized) { - // Rewind and enter the executor: - assert(start->op.code == ENTER_EXECUTOR); - next_instr = start; - this_instr[1].cache &= OPTIMIZER_BITS_MASK; + assert(tstate->previous_executor == NULL); + tstate->previous_executor = Py_None; + GOTO_TIER_TWO(executor); } else { int backoff = this_instr[1].cache & OPTIMIZER_BITS_MASK; @@ -2371,14 +2372,15 @@ dummy_func( inst(ENTER_EXECUTOR, (--)) { TIER_ONE_ONLY CHECK_EVAL_BREAKER(); - PyCodeObject *code = _PyFrame_GetCode(frame); - current_executor = code->co_executors->executors[oparg & 255]; - assert(current_executor->vm_data.index == INSTR_OFFSET() - 1); - assert(current_executor->vm_data.code == code); - assert(current_executor->vm_data.valid); - Py_INCREF(current_executor); - GOTO_TIER_TWO(); + _PyExecutorObject *executor = code->co_executors->executors[oparg & 255]; + assert(executor->vm_data.index == INSTR_OFFSET() - 1); + assert(executor->vm_data.code == code); + assert(executor->vm_data.valid); + assert(tstate->previous_executor == NULL); + tstate->previous_executor = Py_None; + Py_INCREF(executor); + GOTO_TIER_TWO(executor); } replaced op(_POP_JUMP_IF_FALSE, (cond -- )) { @@ -3997,26 +3999,26 @@ dummy_func( inst(CACHE, (--)) { TIER_ONE_ONLY assert(0 && "Executing a cache."); - Py_UNREACHABLE(); + Py_FatalError("Executing a cache."); } inst(RESERVED, (--)) { TIER_ONE_ONLY assert(0 && "Executing RESERVED instruction."); - Py_UNREACHABLE(); + Py_FatalError("Executing RESERVED instruction."); } ///////// Tier-2 only opcodes ///////// op (_GUARD_IS_TRUE_POP, (flag -- )) { SYNC_SP(); - DEOPT_IF(!Py_IsTrue(flag)); + EXIT_IF(!Py_IsTrue(flag)); assert(Py_IsTrue(flag)); } op (_GUARD_IS_FALSE_POP, (flag -- )) { SYNC_SP(); - DEOPT_IF(!Py_IsFalse(flag)); + EXIT_IF(!Py_IsFalse(flag)); assert(Py_IsFalse(flag)); } @@ -4024,18 +4026,20 @@ dummy_func( SYNC_SP(); if (!Py_IsNone(val)) { Py_DECREF(val); - DEOPT_IF(1); + EXIT_IF(1); } } op (_GUARD_IS_NOT_NONE_POP, (val -- )) { SYNC_SP(); - DEOPT_IF(Py_IsNone(val)); + EXIT_IF(Py_IsNone(val)); Py_DECREF(val); } op(_JUMP_TO_TOP, (--)) { - next_uop = current_executor->trace; +#ifndef _Py_JIT + next_uop = ¤t_executor->trace[1]; +#endif CHECK_EVAL_BREAKER(); } @@ -4055,7 +4059,7 @@ dummy_func( op(_EXIT_TRACE, (--)) { TIER_TWO_ONLY - DEOPT_IF(1); + EXIT_IF(1); } op(_CHECK_VALIDITY, (--)) { @@ -4101,6 +4105,58 @@ dummy_func( exe->count++; } + /* Only used for handling cold side exits, should never appear in + * a normal trace or as part of an instruction. + */ + op(_COLD_EXIT, (--)) { + TIER_TWO_ONLY + _PyExecutorObject *previous = (_PyExecutorObject *)tstate->previous_executor; + _PyExitData *exit = &previous->exits[oparg]; + exit->temperature++; + PyCodeObject *code = _PyFrame_GetCode(frame); + _Py_CODEUNIT *target = _PyCode_CODE(code) + exit->target; + if (exit->temperature < (int32_t)tstate->interp->optimizer_side_threshold) { + GOTO_TIER_ONE(target); + } + _PyExecutorObject *executor; + if (target->op.code == ENTER_EXECUTOR) { + executor = code->co_executors->executors[target->op.arg]; + Py_INCREF(executor); + } else { + int optimized = _PyOptimizer_Optimize(frame, target, stack_pointer, &executor); + if (optimized <= 0) { + int32_t new_temp = -1 * tstate->interp->optimizer_side_threshold; + exit->temperature = (new_temp < INT16_MIN) ? INT16_MIN : new_temp; + if (optimized < 0) { + Py_DECREF(previous); + tstate->previous_executor = Py_None; + ERROR_IF(1, error); + } + GOTO_TIER_ONE(target); + } + } + /* We need two references. One to store in exit->executor and + * one to keep the executor alive when executing. */ + Py_INCREF(executor); + exit->executor = executor; + GOTO_TIER_TWO(executor); + } + + op(_START_EXECUTOR, (executor/4 --)) { + TIER_TWO_ONLY + Py_DECREF(tstate->previous_executor); + tstate->previous_executor = NULL; +#ifndef _Py_JIT + current_executor = (_PyExecutorObject*)executor; +#endif + } + + op(_FATAL_ERROR, (--)) { + TIER_TWO_ONLY + assert(0); + Py_FatalError("Fatal error uop executed."); + } + op(_CHECK_VALIDITY_AND_SET_IP, (instr_ptr/4 --)) { TIER_TWO_ONLY DEOPT_IF(!current_executor->vm_data.valid); diff --git a/Python/ceval.c b/Python/ceval.c index 4f2080090861914..adccf8fc00f69c0 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -16,6 +16,7 @@ #include "pycore_moduleobject.h" // PyModuleObject #include "pycore_object.h" // _PyObject_GC_TRACK() #include "pycore_opcode_metadata.h" // EXTRA_CASES +#include "pycore_optimizer.h" // _PyUOpExecutor_Type #include "pycore_opcode_utils.h" // MAKE_FUNCTION_* #include "pycore_pyerrors.h" // _PyErr_GetRaisedException() #include "pycore_pystate.h" // _PyInterpreterState_GET() @@ -738,15 +739,16 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int goto resume_with_error; } - /* State shared between Tier 1 and Tier 2 interpreter */ - _PyExecutorObject *current_executor = NULL; - /* Local "register" variables. * These are cached values from the frame and code object. */ - _Py_CODEUNIT *next_instr; PyObject **stack_pointer; +#ifndef _Py_JIT + /* Tier 2 interpreter state */ + _PyExecutorObject *current_executor = NULL; + const _PyUOpInstruction *next_uop = NULL; +#endif start_frame: if (_Py_EnterRecursivePy(tstate)) { @@ -960,18 +962,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int enter_tier_two: #ifdef _Py_JIT - - ; // ;) - jit_func jitted = current_executor->jit_code; - next_instr = jitted(frame, stack_pointer, tstate); - frame = tstate->current_frame; - Py_DECREF(current_executor); - if (next_instr == NULL) { - goto resume_with_error; - } - stack_pointer = _PyFrame_GetStackPointer(frame); - DISPATCH(); - + assert(0); #else #undef LOAD_IP @@ -1007,12 +998,12 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int #endif OPT_STAT_INC(traces_executed); - _PyUOpInstruction *next_uop = current_executor->trace; uint16_t uopcode; #ifdef Py_STATS uint64_t trace_uop_execution_counter = 0; #endif + assert(next_uop->opcode == _START_EXECUTOR || next_uop->opcode == _COLD_EXIT); for (;;) { uopcode = next_uop->opcode; DPRINTF(3, @@ -1075,23 +1066,39 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int frame->return_offset = 0; // Don't leave this random _PyFrame_SetStackPointer(frame, stack_pointer); Py_DECREF(current_executor); + tstate->previous_executor = NULL; goto resume_with_error; // Jump here from DEOPT_IF() deoptimize: next_instr = next_uop[-1].target + _PyCode_CODE(_PyFrame_GetCode(frame)); - DPRINTF(2, "DEOPT: [UOp %d (%s), oparg %d, operand %" PRIu64 ", target %d @ %d -> %s]\n", + DPRINTF(2, "DEOPT: [UOp %d (%s), oparg %d, operand %" PRIu64 ", target %d -> %s]\n", uopcode, _PyUOpName(uopcode), next_uop[-1].oparg, next_uop[-1].operand, next_uop[-1].target, - (int)(next_uop - current_executor->trace - 1), - _PyOpcode_OpName[frame->instr_ptr->op.code]); + _PyOpcode_OpName[next_instr->op.code]); OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); UOP_STAT_INC(uopcode, miss); Py_DECREF(current_executor); + tstate->previous_executor = NULL; DISPATCH(); +// Jump here from EXIT_IF() +side_exit: + OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); + UOP_STAT_INC(uopcode, miss); + uint32_t exit_index = next_uop[-1].exit_index; + assert(exit_index < current_executor->exit_count); + _PyExitData *exit = ¤t_executor->exits[exit_index]; + DPRINTF(2, "SIDE EXIT: [UOp %d (%s), oparg %d, operand %" PRIu64 ", exit %u, temp %d, target %d -> %s]\n", + uopcode, _PyUOpName(uopcode), next_uop[-1].oparg, next_uop[-1].operand, exit_index, exit->temperature, + exit->target, _PyOpcode_OpName[_PyCode_CODE(_PyFrame_GetCode(frame))[exit->target].op.code]); + Py_INCREF(exit->executor); + tstate->previous_executor = (PyObject *)current_executor; + GOTO_TIER_TWO(exit->executor); + #endif // _Py_JIT } + #if defined(__GNUC__) # pragma GCC diagnostic pop #elif defined(_MSC_VER) /* MS_WINDOWS */ diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h index 1043966c9a82771..f796b60335612cb 100644 --- a/Python/ceval_macros.h +++ b/Python/ceval_macros.h @@ -394,7 +394,36 @@ stack_pointer = _PyFrame_GetStackPointer(frame); /* Tier-switching macros. */ -#define GOTO_TIER_TWO() goto enter_tier_two; +#ifdef _Py_JIT +#define GOTO_TIER_TWO(EXECUTOR) \ +do { \ + jit_func jitted = (EXECUTOR)->jit_code; \ + next_instr = jitted(frame, stack_pointer, tstate); \ + Py_DECREF(tstate->previous_executor); \ + tstate->previous_executor = NULL; \ + frame = tstate->current_frame; \ + if (next_instr == NULL) { \ + goto resume_with_error; \ + } \ + stack_pointer = _PyFrame_GetStackPointer(frame); \ + DISPATCH(); \ +} while (0) +#else +#define GOTO_TIER_TWO(EXECUTOR) \ +do { \ + next_uop = (EXECUTOR)->trace; \ + assert(next_uop->opcode == _START_EXECUTOR || next_uop->opcode == _COLD_EXIT); \ + goto enter_tier_two; \ +} while (0) +#endif + +#define GOTO_TIER_ONE(TARGET) \ +do { \ + Py_DECREF(tstate->previous_executor); \ + tstate->previous_executor = NULL; \ + next_instr = target; \ + DISPATCH(); \ +} while (0) #define CURRENT_OPARG() (next_uop[-1].oparg) diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 11e2a1fe85d51dd..a18284d89ab42cb 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -141,7 +141,7 @@ case _TO_BOOL_BOOL: { PyObject *value; value = stack_pointer[-1]; - if (!PyBool_Check(value)) goto deoptimize; + if (!PyBool_Check(value)) goto side_exit; STAT_INC(TO_BOOL, hit); break; } @@ -150,7 +150,7 @@ PyObject *value; PyObject *res; value = stack_pointer[-1]; - if (!PyLong_CheckExact(value)) goto deoptimize; + if (!PyLong_CheckExact(value)) goto side_exit; STAT_INC(TO_BOOL, hit); if (_PyLong_IsZero((PyLongObject *)value)) { assert(_Py_IsImmortal(value)); @@ -168,7 +168,7 @@ PyObject *value; PyObject *res; value = stack_pointer[-1]; - if (!PyList_CheckExact(value)) goto deoptimize; + if (!PyList_CheckExact(value)) goto side_exit; STAT_INC(TO_BOOL, hit); res = Py_SIZE(value) ? Py_True : Py_False; Py_DECREF(value); @@ -181,7 +181,7 @@ PyObject *res; value = stack_pointer[-1]; // This one is a bit weird, because we expect *some* failures: - if (!Py_IsNone(value)) goto deoptimize; + if (!Py_IsNone(value)) goto side_exit; STAT_INC(TO_BOOL, hit); res = Py_False; stack_pointer[-1] = res; @@ -192,7 +192,7 @@ PyObject *value; PyObject *res; value = stack_pointer[-1]; - if (!PyUnicode_CheckExact(value)) goto deoptimize; + if (!PyUnicode_CheckExact(value)) goto side_exit; STAT_INC(TO_BOOL, hit); if (value == &_Py_STR(empty)) { assert(_Py_IsImmortal(value)); @@ -214,7 +214,7 @@ uint32_t version = (uint32_t)CURRENT_OPERAND(); // This one is a bit weird, because we expect *some* failures: assert(version); - if (Py_TYPE(value)->tp_version_tag != version) goto deoptimize; + if (Py_TYPE(value)->tp_version_tag != version) goto side_exit; STAT_INC(TO_BOOL, hit); Py_DECREF(value); res = Py_True; @@ -238,8 +238,8 @@ PyObject *left; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (!PyLong_CheckExact(left)) goto deoptimize; - if (!PyLong_CheckExact(right)) goto deoptimize; + if (!PyLong_CheckExact(left)) goto side_exit; + if (!PyLong_CheckExact(right)) goto side_exit; break; } @@ -296,8 +296,8 @@ PyObject *left; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (!PyFloat_CheckExact(left)) goto deoptimize; - if (!PyFloat_CheckExact(right)) goto deoptimize; + if (!PyFloat_CheckExact(left)) goto side_exit; + if (!PyFloat_CheckExact(right)) goto side_exit; break; } @@ -354,8 +354,8 @@ PyObject *left; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (!PyUnicode_CheckExact(left)) goto deoptimize; - if (!PyUnicode_CheckExact(right)) goto deoptimize; + if (!PyUnicode_CheckExact(left)) goto side_exit; + if (!PyUnicode_CheckExact(right)) goto side_exit; break; } @@ -1623,7 +1623,7 @@ uint32_t type_version = (uint32_t)CURRENT_OPERAND(); PyTypeObject *tp = Py_TYPE(owner); assert(type_version != 0); - if (tp->tp_version_tag != type_version) goto deoptimize; + if (tp->tp_version_tag != type_version) goto side_exit; break; } @@ -2013,8 +2013,6 @@ break; } - /* _JUMP_BACKWARD is not a viable micro-op for tier 2 */ - /* _POP_JUMP_IF_FALSE is not a viable micro-op for tier 2 */ /* _POP_JUMP_IF_TRUE is not a viable micro-op for tier 2 */ @@ -3318,7 +3316,7 @@ PyObject *flag; flag = stack_pointer[-1]; stack_pointer += -1; - if (!Py_IsTrue(flag)) goto deoptimize; + if (!Py_IsTrue(flag)) goto side_exit; assert(Py_IsTrue(flag)); break; } @@ -3327,7 +3325,7 @@ PyObject *flag; flag = stack_pointer[-1]; stack_pointer += -1; - if (!Py_IsFalse(flag)) goto deoptimize; + if (!Py_IsFalse(flag)) goto side_exit; assert(Py_IsFalse(flag)); break; } @@ -3338,7 +3336,7 @@ stack_pointer += -1; if (!Py_IsNone(val)) { Py_DECREF(val); - if (1) goto deoptimize; + if (1) goto side_exit; } break; } @@ -3347,13 +3345,15 @@ PyObject *val; val = stack_pointer[-1]; stack_pointer += -1; - if (Py_IsNone(val)) goto deoptimize; + if (Py_IsNone(val)) goto side_exit; Py_DECREF(val); break; } case _JUMP_TO_TOP: { - next_uop = current_executor->trace; + #ifndef _Py_JIT + next_uop = ¤t_executor->trace[1]; + #endif CHECK_EVAL_BREAKER(); break; } @@ -3378,7 +3378,7 @@ case _EXIT_TRACE: { TIER_TWO_ONLY - if (1) goto deoptimize; + if (1) goto side_exit; break; } @@ -3457,6 +3457,60 @@ break; } + case _COLD_EXIT: { + oparg = CURRENT_OPARG(); + TIER_TWO_ONLY + _PyExecutorObject *previous = (_PyExecutorObject *)tstate->previous_executor; + _PyExitData *exit = &previous->exits[oparg]; + exit->temperature++; + PyCodeObject *code = _PyFrame_GetCode(frame); + _Py_CODEUNIT *target = _PyCode_CODE(code) + exit->target; + if (exit->temperature < (int32_t)tstate->interp->optimizer_side_threshold) { + GOTO_TIER_ONE(target); + } + _PyExecutorObject *executor; + if (target->op.code == ENTER_EXECUTOR) { + executor = code->co_executors->executors[target->op.arg]; + Py_INCREF(executor); + } else { + int optimized = _PyOptimizer_Optimize(frame, target, stack_pointer, &executor); + if (optimized <= 0) { + int32_t new_temp = -1 * tstate->interp->optimizer_side_threshold; + exit->temperature = (new_temp < INT16_MIN) ? INT16_MIN : new_temp; + if (optimized < 0) { + Py_DECREF(previous); + tstate->previous_executor = Py_None; + if (1) goto error_tier_two; + } + GOTO_TIER_ONE(target); + } + } + /* We need two references. One to store in exit->executor and + * one to keep the executor alive when executing. */ + Py_INCREF(executor); + exit->executor = executor; + GOTO_TIER_TWO(executor); + break; + } + + case _START_EXECUTOR: { + PyObject *executor = (PyObject *)CURRENT_OPERAND(); + TIER_TWO_ONLY + Py_DECREF(tstate->previous_executor); + tstate->previous_executor = NULL; + #ifndef _Py_JIT + current_executor = (_PyExecutorObject*)executor; + #endif + break; + } + + case _FATAL_ERROR: { + TIER_TWO_ONLY + assert(0); + Py_FatalError("Fatal error uop executed."); + break; + } + case _CHECK_VALIDITY_AND_SET_IP: { PyObject *instr_ptr = (PyObject *)CURRENT_OPERAND(); TIER_TWO_ONLY diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 6c19adc60c690f3..a520d042a4aa1e4 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -741,7 +741,8 @@ INSTRUCTION_STATS(CACHE); TIER_ONE_ONLY assert(0 && "Executing a cache."); - Py_UNREACHABLE(); + Py_FatalError("Executing a cache."); + DISPATCH(); } TARGET(CALL) { @@ -2369,12 +2370,14 @@ TIER_ONE_ONLY CHECK_EVAL_BREAKER(); PyCodeObject *code = _PyFrame_GetCode(frame); - current_executor = code->co_executors->executors[oparg & 255]; - assert(current_executor->vm_data.index == INSTR_OFFSET() - 1); - assert(current_executor->vm_data.code == code); - assert(current_executor->vm_data.valid); - Py_INCREF(current_executor); - GOTO_TIER_TWO(); + _PyExecutorObject *executor = code->co_executors->executors[oparg & 255]; + assert(executor->vm_data.index == INSTR_OFFSET() - 1); + assert(executor->vm_data.code == code); + assert(executor->vm_data.valid); + assert(tstate->previous_executor == NULL); + tstate->previous_executor = Py_None; + Py_INCREF(executor); + GOTO_TIER_TWO(executor); DISPATCH(); } @@ -3262,6 +3265,7 @@ next_instr += 2; INSTRUCTION_STATS(JUMP_BACKWARD); /* Skip 1 cache entry */ + TIER_ONE_ONLY CHECK_EVAL_BREAKER(); assert(oparg <= INSTR_OFFSET()); JUMPBY(-oparg); @@ -3283,13 +3287,13 @@ oparg >>= 8; start--; } - int optimized = _PyOptimizer_Optimize(frame, start, stack_pointer); + _PyExecutorObject *executor; + int optimized = _PyOptimizer_Optimize(frame, start, stack_pointer, &executor); if (optimized < 0) goto error; if (optimized) { - // Rewind and enter the executor: - assert(start->op.code == ENTER_EXECUTOR); - next_instr = start; - this_instr[1].cache &= OPTIMIZER_BITS_MASK; + assert(tstate->previous_executor == NULL); + tstate->previous_executor = Py_None; + GOTO_TIER_TWO(executor); } else { int backoff = this_instr[1].cache & OPTIMIZER_BITS_MASK; @@ -4778,7 +4782,8 @@ INSTRUCTION_STATS(RESERVED); TIER_ONE_ONLY assert(0 && "Executing RESERVED instruction."); - Py_UNREACHABLE(); + Py_FatalError("Executing RESERVED instruction."); + DISPATCH(); } TARGET(RESUME) { diff --git a/Python/jit.c b/Python/jit.c index 22949c082da05a2..839414bd8106778 100644 --- a/Python/jit.c +++ b/Python/jit.c @@ -300,13 +300,13 @@ emit(const StencilGroup *group, uint64_t patches[]) // Compiles executor in-place. Don't forget to call _PyJIT_Free later! int -_PyJIT_Compile(_PyExecutorObject *executor, _PyUOpInstruction *trace, size_t length) +_PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction *trace, size_t length) { // Loop once to find the total compiled size: size_t code_size = 0; size_t data_size = 0; for (size_t i = 0; i < length; i++) { - _PyUOpInstruction *instruction = &trace[i]; + _PyUOpInstruction *instruction = (_PyUOpInstruction *)&trace[i]; const StencilGroup *group = &stencil_groups[instruction->opcode]; code_size += group->code.body_size; data_size += group->data.body_size; @@ -323,8 +323,13 @@ _PyJIT_Compile(_PyExecutorObject *executor, _PyUOpInstruction *trace, size_t len // Loop again to emit the code: char *code = memory; char *data = memory + code_size; + char *top = code; + if (trace[0].opcode == _START_EXECUTOR) { + // Don't want to execute this more than once: + top += stencil_groups[_START_EXECUTOR].code.body_size; + } for (size_t i = 0; i < length; i++) { - _PyUOpInstruction *instruction = &trace[i]; + _PyUOpInstruction *instruction = (_PyUOpInstruction *)&trace[i]; const StencilGroup *group = &stencil_groups[instruction->opcode]; // Think of patches as a dictionary mapping HoleValue to uint64_t: uint64_t patches[] = GET_PATCHES(); @@ -335,7 +340,7 @@ _PyJIT_Compile(_PyExecutorObject *executor, _PyUOpInstruction *trace, size_t len patches[HoleValue_OPARG] = instruction->oparg; patches[HoleValue_OPERAND] = instruction->operand; patches[HoleValue_TARGET] = instruction->target; - patches[HoleValue_TOP] = (uint64_t)memory; + patches[HoleValue_TOP] = (uint64_t)top; patches[HoleValue_ZERO] = 0; emit(group, patches); code += group->code.body_size; diff --git a/Python/optimizer.c b/Python/optimizer.c index efa19680c9b1f30..acc1d545d2b8adf 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -2,6 +2,7 @@ #include "opcode.h" #include "pycore_interp.h" #include "pycore_bitutils.h" // _Py_popcount32() +#include "pycore_object.h" // _PyObject_GC_UNTRACK() #include "pycore_opcode_metadata.h" // _PyOpcode_OpName[] #include "pycore_opcode_utils.h" // MAX_REAL_OPCODE #include "pycore_optimizer.h" // _Py_uop_analyze_and_optimize() @@ -128,10 +129,11 @@ static _PyOptimizerObject _PyOptimizer_Default = { .optimize = never_optimize, .resume_threshold = OPTIMIZER_UNREACHABLE_THRESHOLD, .backedge_threshold = OPTIMIZER_UNREACHABLE_THRESHOLD, + .side_threshold = OPTIMIZER_UNREACHABLE_THRESHOLD, }; static uint32_t -shift_and_offset_threshold(uint16_t threshold) +shift_and_offset_threshold(uint32_t threshold) { return (threshold << OPTIMIZER_BITS_IN_COUNTER) + (1 << 15); } @@ -140,41 +142,74 @@ _PyOptimizerObject * PyUnstable_GetOptimizer(void) { PyInterpreterState *interp = _PyInterpreterState_GET(); - if (interp->optimizer == &_PyOptimizer_Default) { - return NULL; - } assert(interp->optimizer_backedge_threshold == shift_and_offset_threshold(interp->optimizer->backedge_threshold)); assert(interp->optimizer_resume_threshold == shift_and_offset_threshold(interp->optimizer->resume_threshold)); + if (interp->optimizer == &_PyOptimizer_Default) { + return NULL; + } Py_INCREF(interp->optimizer); return interp->optimizer; } +static _PyExecutorObject * +make_executor_from_uops(_PyUOpInstruction *buffer, const _PyBloomFilter *dependencies); + +static int +init_cold_exit_executor(_PyExecutorObject *executor, int oparg); + +static int cold_exits_initialized = 0; +static _PyExecutorObject COLD_EXITS[UOP_MAX_TRACE_LENGTH] = { 0 }; + +static const _PyBloomFilter EMPTY_FILTER = { 0 }; + _PyOptimizerObject * _Py_SetOptimizer(PyInterpreterState *interp, _PyOptimizerObject *optimizer) { if (optimizer == NULL) { optimizer = &_PyOptimizer_Default; } + else if (cold_exits_initialized == 0) { + cold_exits_initialized = 1; + for (int i = 0; i < UOP_MAX_TRACE_LENGTH; i++) { + if (init_cold_exit_executor(&COLD_EXITS[i], i)) { + return NULL; + } + } + } _PyOptimizerObject *old = interp->optimizer; + if (old == NULL) { + old = &_PyOptimizer_Default; + } Py_INCREF(optimizer); interp->optimizer = optimizer; interp->optimizer_backedge_threshold = shift_and_offset_threshold(optimizer->backedge_threshold); interp->optimizer_resume_threshold = shift_and_offset_threshold(optimizer->resume_threshold); + interp->optimizer_side_threshold = optimizer->side_threshold; + if (optimizer == &_PyOptimizer_Default) { + assert(interp->optimizer_backedge_threshold > (1 << 16)); + assert(interp->optimizer_resume_threshold > (1 << 16)); + } return old; } -void +int PyUnstable_SetOptimizer(_PyOptimizerObject *optimizer) { PyInterpreterState *interp = _PyInterpreterState_GET(); _PyOptimizerObject *old = _Py_SetOptimizer(interp, optimizer); - Py_DECREF(old); + Py_XDECREF(old); + return old == NULL ? -1 : 0; } +/* Returns 1 if optimized, 0 if not optimized, and -1 for an error. + * If optimized, *executor_ptr contains a new reference to the executor + */ int -_PyOptimizer_Optimize(_PyInterpreterFrame *frame, _Py_CODEUNIT *start, PyObject **stack_pointer) +_PyOptimizer_Optimize( + _PyInterpreterFrame *frame, _Py_CODEUNIT *start, + PyObject **stack_pointer, _PyExecutorObject **executor_ptr) { PyCodeObject *code = (PyCodeObject *)frame->f_executable; assert(PyCode_Check(code)); @@ -183,12 +218,11 @@ _PyOptimizer_Optimize(_PyInterpreterFrame *frame, _Py_CODEUNIT *start, PyObject return 0; } _PyOptimizerObject *opt = interp->optimizer; - _PyExecutorObject *executor = NULL; - int err = opt->optimize(opt, frame, start, &executor, (int)(stack_pointer - _PyFrame_Stackbase(frame))); + int err = opt->optimize(opt, frame, start, executor_ptr, (int)(stack_pointer - _PyFrame_Stackbase(frame))); if (err <= 0) { - assert(executor == NULL); return err; } + assert(*executor_ptr != NULL); int index = get_index_for_executor(code, start); if (index < 0) { /* Out of memory. Don't raise and assume that the @@ -197,11 +231,11 @@ _PyOptimizer_Optimize(_PyInterpreterFrame *frame, _Py_CODEUNIT *start, PyObject * If an optimizer has already produced an executor, * it might get confused by the executor disappearing, * but there is not much we can do about that here. */ - Py_DECREF(executor); + Py_DECREF(*executor_ptr); return 0; } - insert_executor(code, start, index, executor); - Py_DECREF(executor); + insert_executor(code, start, index, *executor_ptr); + assert((*executor_ptr)->vm_data.valid); return 1; } @@ -237,11 +271,12 @@ static PyMethodDef executor_methods[] = { static void uop_dealloc(_PyExecutorObject *self) { + _PyObject_GC_UNTRACK(self); _Py_ExecutorClear(self); #ifdef _Py_JIT _PyJIT_Free(self); #endif - PyObject_Free(self); + PyObject_GC_Del(self); } const char * @@ -253,7 +288,7 @@ _PyUOpName(int index) static Py_ssize_t uop_len(_PyExecutorObject *self) { - return Py_SIZE(self); + return self->code_size; } static PyObject * @@ -292,15 +327,34 @@ PySequenceMethods uop_as_sequence = { .sq_item = (ssizeargfunc)uop_item, }; +static int +executor_clear(PyObject *o) +{ + _Py_ExecutorClear((_PyExecutorObject *)o); + return 0; +} + +static int +executor_traverse(PyObject *o, visitproc visit, void *arg) +{ + _PyExecutorObject *executor = (_PyExecutorObject *)o; + for (uint32_t i = 0; i < executor->exit_count; i++) { + Py_VISIT(executor->exits[i].executor); + } + return 0; +} + PyTypeObject _PyUOpExecutor_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) .tp_name = "uop_executor", - .tp_basicsize = offsetof(_PyExecutorObject, trace), - .tp_itemsize = sizeof(_PyUOpInstruction), - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, + .tp_basicsize = offsetof(_PyExecutorObject, exits), + .tp_itemsize = 1, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_HAVE_GC, .tp_dealloc = (destructor)uop_dealloc, .tp_as_sequence = &uop_as_sequence, .tp_methods = executor_methods, + .tp_traverse = executor_traverse, + .tp_clear = executor_clear, }; /* TO DO -- Generate these tables */ @@ -324,6 +378,7 @@ BRANCH_TO_GUARD[4][2] = { [POP_JUMP_IF_NOT_NONE - POP_JUMP_IF_FALSE][1] = _GUARD_IS_NOT_NONE_POP, }; + #define CONFIDENCE_RANGE 1000 #define CONFIDENCE_CUTOFF 333 @@ -726,9 +781,10 @@ translate_bytecode_to_trace( * NOPs are excluded from the count. */ static int -compute_used(_PyUOpInstruction *buffer, uint32_t *used) +compute_used(_PyUOpInstruction *buffer, uint32_t *used, int *exit_count_ptr) { int count = 0; + int exit_count = 0; SET_BIT(used, 0); for (int i = 0; i < UOP_MAX_TRACE_LENGTH; i++) { if (!BIT_IS_SET(used, i)) { @@ -736,6 +792,9 @@ compute_used(_PyUOpInstruction *buffer, uint32_t *used) } count++; int opcode = buffer[i].opcode; + if (_PyUop_Flags[opcode] & HAS_EXIT_FLAG) { + exit_count++; + } if (opcode == _JUMP_TO_TOP || opcode == _EXIT_TRACE) { continue; } @@ -751,44 +810,76 @@ compute_used(_PyUOpInstruction *buffer, uint32_t *used) UNSET_BIT(used, i); } } + *exit_count_ptr = exit_count; return count; } +/* Executor side exits */ + +static _PyExecutorObject * +allocate_executor(int exit_count, int length) +{ + int size = exit_count*sizeof(_PyExitData) + length*sizeof(_PyUOpInstruction); + _PyExecutorObject *res = PyObject_GC_NewVar(_PyExecutorObject, &_PyUOpExecutor_Type, size); + if (res == NULL) { + return NULL; + } + res->trace = (_PyUOpInstruction *)(res->exits + exit_count); + res->code_size = length; + res->exit_count = exit_count; + return res; +} + /* Makes an executor from a buffer of uops. * Account for the buffer having gaps and NOPs by computing a "used" * bit vector and only copying the used uops. Here "used" means reachable * and not a NOP. */ static _PyExecutorObject * -make_executor_from_uops(_PyUOpInstruction *buffer, _PyBloomFilter *dependencies) +make_executor_from_uops(_PyUOpInstruction *buffer, const _PyBloomFilter *dependencies) { uint32_t used[(UOP_MAX_TRACE_LENGTH + 31)/32] = { 0 }; - int length = compute_used(buffer, used); - _PyExecutorObject *executor = PyObject_NewVar(_PyExecutorObject, &_PyUOpExecutor_Type, length); + int exit_count; + int length = compute_used(buffer, used, &exit_count); + _PyExecutorObject *executor = allocate_executor(exit_count, length+1); if (executor == NULL) { return NULL; } - int dest = length - 1; + /* Initialize exits */ + for (int i = 0; i < exit_count; i++) { + executor->exits[i].executor = &COLD_EXITS[i]; + executor->exits[i].temperature = 0; + } + int next_exit = exit_count-1; + _PyUOpInstruction *dest = (_PyUOpInstruction *)&executor->trace[length]; /* Scan backwards, so that we see the destinations of jumps before the jumps themselves. */ for (int i = UOP_MAX_TRACE_LENGTH-1; i >= 0; i--) { if (!BIT_IS_SET(used, i)) { continue; } - executor->trace[dest] = buffer[i]; + *dest = buffer[i]; int opcode = buffer[i].opcode; if (opcode == _POP_JUMP_IF_FALSE || opcode == _POP_JUMP_IF_TRUE) { /* The oparg of the target will already have been set to its new offset */ - int oparg = executor->trace[dest].oparg; - executor->trace[dest].oparg = buffer[oparg].oparg; + int oparg = dest->oparg; + dest->oparg = buffer[oparg].oparg; + } + if (_PyUop_Flags[opcode] & HAS_EXIT_FLAG) { + executor->exits[next_exit].target = buffer[i].target; + dest->exit_index = next_exit; + next_exit--; } /* Set the oparg to be the destination offset, * so that we can set the oparg of earlier jumps correctly. */ - buffer[i].oparg = dest; + buffer[i].oparg = (uint16_t)(dest - executor->trace); dest--; } - assert(dest == -1); + assert(next_exit == -1); + assert(dest == executor->trace); + dest->opcode = _START_EXECUTOR; + dest->operand = (uintptr_t)executor; _Py_ExecutorInit(executor, dependencies); #ifdef Py_DEBUG char *python_lltrace = Py_GETENV("PYTHON_LLTRACE"); @@ -811,14 +902,40 @@ make_executor_from_uops(_PyUOpInstruction *buffer, _PyBloomFilter *dependencies) #ifdef _Py_JIT executor->jit_code = NULL; executor->jit_size = 0; - if (_PyJIT_Compile(executor, executor->trace, Py_SIZE(executor))) { + if (_PyJIT_Compile(executor, executor->trace, length+1)) { Py_DECREF(executor); return NULL; } #endif + _PyObject_GC_TRACK(executor); return executor; } +static int +init_cold_exit_executor(_PyExecutorObject *executor, int oparg) +{ + _Py_SetImmortal(executor); + Py_SET_TYPE(executor, &_PyUOpExecutor_Type); + executor->trace = (_PyUOpInstruction *)executor->exits; + executor->code_size = 1; + executor->exit_count = 0; + _PyUOpInstruction *inst = (_PyUOpInstruction *)&executor->trace[0]; + inst->opcode = _COLD_EXIT; + inst->oparg = oparg; + executor->vm_data.valid = true; + for (int i = 0; i < BLOOM_FILTER_WORDS; i++) { + assert(executor->vm_data.bloom.bits[i] == 0); + } +#ifdef _Py_JIT + executor->jit_code = NULL; + executor->jit_size = 0; + if (_PyJIT_Compile(executor, executor->trace, 1)) { + return -1; + } +#endif + return 0; +} + static int uop_optimize( _PyOptimizerObject *self, @@ -880,13 +997,15 @@ PyUnstable_Optimizer_NewUOpOptimizer(void) opt->resume_threshold = OPTIMIZER_UNREACHABLE_THRESHOLD; // Need a few iterations to settle specializations, // and to ammortize the cost of optimization. + opt->side_threshold = 16; opt->backedge_threshold = 16; return (PyObject *)opt; } static void counter_dealloc(_PyExecutorObject *self) { - PyObject *opt = (PyObject *)self->trace[0].operand; + /* The optimizer is the operand of the second uop. */ + PyObject *opt = (PyObject *)self->trace[1].operand; Py_DECREF(opt); uop_dealloc(self); } @@ -894,11 +1013,13 @@ counter_dealloc(_PyExecutorObject *self) { PyTypeObject _PyCounterExecutor_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) .tp_name = "counting_executor", - .tp_basicsize = offsetof(_PyExecutorObject, trace), - .tp_itemsize = sizeof(_PyUOpInstruction), - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, + .tp_basicsize = offsetof(_PyExecutorObject, exits), + .tp_itemsize = 1, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_HAVE_GC, .tp_dealloc = (destructor)counter_dealloc, .tp_methods = executor_methods, + .tp_traverse = executor_traverse, + .tp_clear = executor_clear, }; static int @@ -926,9 +1047,7 @@ counter_optimize( { .opcode = _INTERNAL_INCREMENT_OPT_COUNTER }, { .opcode = _EXIT_TRACE, .target = (uint32_t)(target - _PyCode_CODE(code)) } }; - _PyBloomFilter empty; - _Py_BloomFilter_Init(&empty); - _PyExecutorObject *executor = make_executor_from_uops(buffer, &empty); + _PyExecutorObject *executor = make_executor_from_uops(buffer, &EMPTY_FILTER); if (executor == NULL) { return -1; } @@ -968,6 +1087,7 @@ PyUnstable_Optimizer_NewCounter(void) } opt->base.optimize = counter_optimize; opt->base.resume_threshold = OPTIMIZER_UNREACHABLE_THRESHOLD; + opt->base.side_threshold = OPTIMIZER_UNREACHABLE_THRESHOLD; opt->base.backedge_threshold = 0; opt->count = 0; return (PyObject *)opt; @@ -1091,9 +1211,6 @@ link_executor(_PyExecutorObject *executor) static void unlink_executor(_PyExecutorObject *executor) { - if (!executor->vm_data.valid) { - return; - } _PyExecutorLinkListNode *links = &executor->vm_data.links; _PyExecutorObject *next = links->next; _PyExecutorObject *prev = links->previous; @@ -1114,7 +1231,7 @@ unlink_executor(_PyExecutorObject *executor) /* This must be called by optimizers before using the executor */ void -_Py_ExecutorInit(_PyExecutorObject *executor, _PyBloomFilter *dependency_set) +_Py_ExecutorInit(_PyExecutorObject *executor, const _PyBloomFilter *dependency_set) { executor->vm_data.valid = true; for (int i = 0; i < BLOOM_FILTER_WORDS; i++) { @@ -1127,11 +1244,19 @@ _Py_ExecutorInit(_PyExecutorObject *executor, _PyBloomFilter *dependency_set) void _Py_ExecutorClear(_PyExecutorObject *executor) { + if (!executor->vm_data.valid) { + return; + } unlink_executor(executor); PyCodeObject *code = executor->vm_data.code; if (code == NULL) { return; } + for (uint32_t i = 0; i < executor->exit_count; i++) { + Py_DECREF(executor->exits[i].executor); + executor->exits[i].executor = &COLD_EXITS[i]; + executor->exits[i].temperature = INT16_MIN; + } _Py_CODEUNIT *instruction = &_PyCode_CODE(code)[executor->vm_data.index]; assert(instruction->op.code == ENTER_EXECUTOR); int index = instruction->op.arg; diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index b354c033ae77274..7b537af8c87697a 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1261,7 +1261,9 @@ init_interp_main(PyThreadState *tstate) if (opt == NULL) { return _PyStatus_ERR("can't initialize optimizer"); } - PyUnstable_SetOptimizer((_PyOptimizerObject *)opt); + if (PyUnstable_SetOptimizer((_PyOptimizerObject *)opt)) { + return _PyStatus_ERR("can't initialize optimizer"); + } Py_DECREF(opt); } } diff --git a/Python/pystate.c b/Python/pystate.c index c2ccc276449d4f3..3484beab7aaed49 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -782,6 +782,7 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate) } _PyOptimizerObject *old = _Py_SetOptimizer(interp, NULL); + assert(old != NULL); Py_DECREF(old); /* It is possible that any of the objects below have a finalizer @@ -1346,6 +1347,7 @@ init_threadstate(_PyThreadStateImpl *_tstate, tstate->datastack_top = NULL; tstate->datastack_limit = NULL; tstate->what_event = -1; + tstate->previous_executor = NULL; #ifdef Py_GIL_DISABLED // Initialize biased reference counting inter-thread queue diff --git a/Python/tier2_engine.md b/Python/tier2_engine.md new file mode 100644 index 000000000000000..df9f6c124509bdf --- /dev/null +++ b/Python/tier2_engine.md @@ -0,0 +1,150 @@ +# The tier 2 execution engine + +## General idea + +When execution in tier 1 becomes "hot", that is the counter for that point in +the code reaches some threshold, we create an executor and execute that +instead of the tier 1 bytecode. + +Since each executor must exit, we also track the "hotness" of those +exits and attach new executors to those exits. + +As the program executes, and the hot parts of the program get optimized, +a graph of executors forms. + +## Superblocks and Executors + +Once a point in the code has become hot enough, we want to optimize it. +Starting from that point we project the likely path of execution, +using information gathered by tier 1 to guide that projection to +form a "superblock", a mostly linear sequence of micro-ops. +Although mostly linear, it may include a single loop. + +We then optimize this superblock to form an optimized superblock, +which is equivalent but more efficient. + +A superblock is a representation of the code we want to execute, +but it is not in executable form. +The executable form is known as an executor. + +Executors are semantically equivalent to the superblock they are +created from, but are in a form that can be efficiently executable. + +There are two execution engines for executors, and two types of executors: +* The hardware which runs machine code executors created by the JIT compiler. +* The tier 2 interpreter runs bytecode executors. + +It would be very wasteful to support both a tier 2 interpreter and +JIT compiler in the same process. +For now, we will make the choice of engine a configuration option, +but we could make it a command line option in the future if that would prove useful. + + +### Tier 2 Interpreter + +For platforms without a JIT and for testing, we need an interpreter +for executors. It is similar in design to the tier 1 interpreter, but has a +different instruction set, and does not adapt. + +### JIT compiler + +The JIT compiler converts superblocks into machine code executors. +These have identical behavior to interpreted executors, except that +they consume more memory for the generated machine code and are a lot faster. + +## Transfering control + +There are three types of control transfer that we need to consider: +* Tier 1 to tier 2 +* Tier 2 to tier 1 +* One executor to another within tier 2 + +Since we expect the graph of executors to span most of the hot +part of the program, transfers from one executor to another should +be the most common. +Therefore, we want to make those transfers fast. + +### Tier 2 to tier 2 + +#### Cold exits + +All side exits start cold and most stay cold, but a few become +hot. We want to keep the memory consumption small for the many +cold exits, but those that become hot need to be fast. +However we cannot know in advance, which will be which. + +So that tier 2 to tier 2 transfers are fast for hot exits, +exits must be implemented as executors. In order to patch +executor exits when they get hot, a pointer to the current +executor must be passed to the exit executor. + +#### Handling reference counts + +There must be an implicit reference to the currently executing +executor, otherwise it might be freed. +Consequently, we must increment the reference count of an +executor just before executing it, and decrement it just after +executing it. + +We want to minimize the amount of data that is passed from +one executor to the next. In the JIT, this reduces the number +of arguments in the tailcall, freeing up registers for other uses. +It is less important in the interpreter, but following the same +design as the JIT simplifies debugging and is good for performance. + +Provided that we incref the new executor before executing it, we +can jump directly to the code of the executor, without needing +to pass a reference to that executor object. +However, we do need a reference to the previous executor, +so that it can be decref'd and for handling of cold exits. +To avoid messing up the JIT's register allocation, we pass a +reference to the previous executor in the thread state's +`previous_executor` field. + +#### The interpreter + +The tier 2 interpreter has a variable `current_executor` which +points to the currently live executor. When transfering from executor +`A` to executor `B` we do the following: +(Initially `current_executor` points to `A`, and the refcount of +`A` is elevated by one) + +1. Set the instruction pointer to start at the beginning of `B` +2. Increment the reference count of `B` +3. Start executing `B` + +We also make the first instruction in `B` do the following: +1. Set `current_executor` to point to `B` +2. Decrement the reference count of `A` (`A` is referenced by `tstate->previous_executor`) + +The net effect of the above is to safely decrement the refcount of `A`, +increment the refcount of `B` and set `current_executor` to point to `B`. + +#### In the JIT + +Transfering control from one executor to another is done via tailcalls. + +The compiled executor should do the same, except that there is no local +variable `current_executor`. + +### Tier 1 to tier 2 + +Since the executor doesn't know if the previous code was tier 1 or tier 2, +we need to make a transfer from tier 1 to tier 2 look like a tier 2 to tier 2 +transfer to the executor. + +We can then perform a tier 1 to tier 2 transfer by setting `current_executor` +to `None`, and then performing a tier 2 to tier 2 transfer as above. + +### Tier 2 to tier 1 + +Each micro-op that might exit to tier 1 contains a `target` value, +which is the offset of the tier 1 instruction to exit to in the +current code object. + +## Counters + +TO DO. +The implementation will change soon, so there is no point in +documenting it until then. + diff --git a/Python/tier2_redundancy_eliminator_cases.c.h b/Python/tier2_redundancy_eliminator_cases.c.h index be2fbb9106fffc5..98f0bdca01f01d5 100644 --- a/Python/tier2_redundancy_eliminator_cases.c.h +++ b/Python/tier2_redundancy_eliminator_cases.c.h @@ -1053,8 +1053,6 @@ break; } - /* _JUMP_BACKWARD is not a viable micro-op for tier 2 */ - /* _POP_JUMP_IF_FALSE is not a viable micro-op for tier 2 */ /* _POP_JUMP_IF_TRUE is not a viable micro-op for tier 2 */ @@ -1739,6 +1737,18 @@ break; } + case _COLD_EXIT: { + break; + } + + case _START_EXECUTOR: { + break; + } + + case _FATAL_ERROR: { + break; + } + case _CHECK_VALIDITY_AND_SET_IP: { break; } diff --git a/Tools/c-analyzer/cpython/_parser.py b/Tools/c-analyzer/cpython/_parser.py index 61cd41ea8f31c19..8482bf849aaacd3 100644 --- a/Tools/c-analyzer/cpython/_parser.py +++ b/Tools/c-analyzer/cpython/_parser.py @@ -321,6 +321,7 @@ def clean_lines(text): _abs('Objects/stringlib/unicode_format.h'): (10_000, 400), _abs('Objects/typeobject.c'): (35_000, 200), _abs('Python/compile.c'): (20_000, 500), + _abs('Python/optimizer.c'): (100_000, 5_000), _abs('Python/parking_lot.c'): (40_000, 1000), _abs('Python/pylifecycle.c'): (500_000, 5000), _abs('Python/pystate.c'): (500_000, 5000), diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index 14bcd85b9eae59a..94be33758495977 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -382,6 +382,11 @@ Python/optimizer.c - _PyCounterOptimizer_Type - Python/optimizer.c - _PyUOpExecutor_Type - Python/optimizer.c - _PyUOpOptimizer_Type - Python/optimizer.c - _PyOptimizer_Default - +Python/optimizer.c - _ColdExit_Type - +Python/optimizer.c - COLD_EXITS - +Python/optimizer.c - Py_FatalErrorExecutor - +Python/optimizer.c - EMPTY_FILTER - +Python/optimizer.c - cold_exits_initialized - ##----------------------- ## test code diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index 3497b7fcdf35d37..bcffd75269ac0bf 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -21,6 +21,7 @@ class Properties: uses_co_names: bool uses_locals: bool has_free: bool + side_exit: bool pure: bool passthrough: bool @@ -48,6 +49,7 @@ def from_list(properties: list["Properties"]) -> "Properties": uses_co_names=any(p.uses_co_names for p in properties), uses_locals=any(p.uses_locals for p in properties), has_free=any(p.has_free for p in properties), + side_exit=any(p.side_exit for p in properties), pure=all(p.pure for p in properties), passthrough=all(p.passthrough for p in properties), ) @@ -69,6 +71,7 @@ def from_list(properties: list["Properties"]) -> "Properties": uses_co_names=False, uses_locals=False, has_free=False, + side_exit=False, pure=False, passthrough=False, ) @@ -269,9 +272,7 @@ def override_error( def convert_stack_item(item: parser.StackEffect) -> StackItem: - return StackItem( - item.name, item.type, item.cond, (item.size or "1") - ) + return StackItem(item.name, item.type, item.cond, (item.size or "1")) def analyze_stack(op: parser.InstDef) -> StackEffect: @@ -448,13 +449,24 @@ def compute_properties(op: parser.InstDef) -> Properties: or variable_used(op, "PyCell_GET") or variable_used(op, "PyCell_SET") ) + deopts_if = variable_used(op, "DEOPT_IF") + exits_if = variable_used(op, "EXIT_IF") + if deopts_if and exits_if: + tkn = op.tokens[0] + raise lexer.make_syntax_error( + "Op cannot contain both EXIT_IF and DEOPT_IF", + tkn.filename, + tkn.line, + tkn.column, + op.name, + ) infallible = is_infallible(op) - deopts = variable_used(op, "DEOPT_IF") passthrough = stack_effect_only_peeks(op) and infallible return Properties( escapes=makes_escaping_api_call(op), infallible=infallible, - deopts=deopts, + deopts=deopts_if or exits_if, + side_exit=exits_if, oparg=variable_used(op, "oparg"), jumps=variable_used(op, "JUMPBY"), eval_breaker=variable_used(op, "CHECK_EVAL_BREAKER"), diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 2fc2ab115321cf2..54ffea71b5350b8 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -154,6 +154,7 @@ def replace_check_eval_breaker( REPLACEMENT_FUNCTIONS = { + "EXIT_IF": replace_deopt, "DEOPT_IF": replace_deopt, "ERROR_IF": replace_error, "DECREF_INPUTS": replace_decrefs, @@ -205,6 +206,8 @@ def cflags(p: Properties) -> str: flags.append("HAS_EVAL_BREAK_FLAG") if p.deopts: flags.append("HAS_DEOPT_FLAG") + if p.side_exit: + flags.append("HAS_EXIT_FLAG") if not p.infallible: flags.append("HAS_ERROR_FLAG") if p.escapes: diff --git a/Tools/cases_generator/opcode_metadata_generator.py b/Tools/cases_generator/opcode_metadata_generator.py index 3e9fa3e26daa539..52dc09e1499671d 100644 --- a/Tools/cases_generator/opcode_metadata_generator.py +++ b/Tools/cases_generator/opcode_metadata_generator.py @@ -50,6 +50,7 @@ "DEOPT", "ERROR", "ESCAPES", + "EXIT", "PURE", "PASSTHROUGH", ] diff --git a/Tools/cases_generator/tier2_generator.py b/Tools/cases_generator/tier2_generator.py index 7897b89b2752a72..8b4d1646dd5a87f 100644 --- a/Tools/cases_generator/tier2_generator.py +++ b/Tools/cases_generator/tier2_generator.py @@ -98,9 +98,25 @@ def tier2_replace_deopt( out.emit(") goto deoptimize;\n") +def tier2_replace_exit_if( + out: CWriter, + tkn: Token, + tkn_iter: Iterator[Token], + uop: Uop, + unused: Stack, + inst: Instruction | None, +) -> None: + out.emit_at("if ", tkn) + out.emit(next(tkn_iter)) + emit_to(out, tkn_iter, "RPAREN") + next(tkn_iter) # Semi colon + out.emit(") goto side_exit;\n") + + TIER2_REPLACEMENT_FUNCTIONS = REPLACEMENT_FUNCTIONS.copy() TIER2_REPLACEMENT_FUNCTIONS["ERROR_IF"] = tier2_replace_error TIER2_REPLACEMENT_FUNCTIONS["DEOPT_IF"] = tier2_replace_deopt +TIER2_REPLACEMENT_FUNCTIONS["EXIT_IF"] = tier2_replace_exit_if def write_uop(uop: Uop, out: CWriter, stack: Stack) -> None: diff --git a/Tools/jit/template.c b/Tools/jit/template.c index 12303a550d8879e..d79c6efb8f6de4e 100644 --- a/Tools/jit/template.c +++ b/Tools/jit/template.c @@ -38,6 +38,20 @@ goto LABEL ## _tier_two; \ } while (0) +#undef GOTO_TIER_TWO +#define GOTO_TIER_TWO(EXECUTOR) \ +do { \ + __attribute__((musttail)) \ + return ((jit_func)((EXECUTOR)->jit_code))(frame, stack_pointer, tstate); \ +} while (0) + +#undef GOTO_TIER_ONE +#define GOTO_TIER_ONE(TARGET) \ +do { \ + _PyFrame_SetStackPointer(frame, stack_pointer); \ + return TARGET; \ +} while (0) + #undef LOAD_IP #define LOAD_IP(UNUSED) \ do { \ @@ -59,7 +73,6 @@ _JIT_ENTRY(_PyInterpreterFrame *frame, PyObject **stack_pointer, PyThreadState * PATCH_VALUE(_PyExecutorObject *, current_executor, _JIT_EXECUTOR) int oparg; int opcode = _JIT_OPCODE; - _PyUOpInstruction *next_uop; // Other stuff we need handy: PATCH_VALUE(uint16_t, _oparg, _JIT_OPARG) PATCH_VALUE(uint64_t, _operand, _JIT_OPERAND) @@ -90,9 +103,16 @@ _JIT_ENTRY(_PyInterpreterFrame *frame, PyObject **stack_pointer, PyThreadState * pop_1_error_tier_two: STACK_SHRINK(1); error_tier_two: - _PyFrame_SetStackPointer(frame, stack_pointer); - return NULL; + tstate->previous_executor = (PyObject *)current_executor; + GOTO_TIER_ONE(NULL); deoptimize: - _PyFrame_SetStackPointer(frame, stack_pointer); - return _PyCode_CODE(_PyFrame_GetCode(frame)) + _target; + tstate->previous_executor = (PyObject *)current_executor; + GOTO_TIER_ONE(_PyCode_CODE(_PyFrame_GetCode(frame)) + _target); +side_exit: + { + _PyExitData *exit = ¤t_executor->exits[_target]; + Py_INCREF(exit->executor); + tstate->previous_executor = (PyObject *)current_executor; + GOTO_TIER_TWO(exit->executor); + } } From 626c414995bad1dab51c7222a6f7bf388255eb9e Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Tue, 20 Feb 2024 10:50:59 +0000 Subject: [PATCH 367/507] GH-115457: Support splitting and replication of micro ops. (GH-115558) --- Include/internal/pycore_opcode_metadata.h | 22 +- Include/internal/pycore_uop_ids.h | 443 ++++++++++-------- Include/internal/pycore_uop_metadata.h | 67 ++- Lib/test/test_capi/test_opt.py | 2 +- Python/bytecodes.c | 16 +- Python/ceval.c | 2 +- Python/executor_cases.c.h | 405 +++++++++++++++- Python/generated_cases.c.h | 18 +- Python/optimizer.c | 15 + Python/tier2_redundancy_eliminator_cases.c.h | 17 +- Tools/cases_generator/analyzer.py | 89 +++- Tools/cases_generator/generators_common.py | 7 +- Tools/cases_generator/lexer.py | 2 + .../opcode_metadata_generator.py | 1 + Tools/cases_generator/parsing.py | 8 +- Tools/cases_generator/stack.py | 20 +- .../tier2_abstract_generator.py | 2 + Tools/cases_generator/tier2_generator.py | 62 ++- Tools/cases_generator/uop_id_generator.py | 12 +- .../cases_generator/uop_metadata_generator.py | 7 + 20 files changed, 908 insertions(+), 309 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 177dd302f73171a..f45e5f1901b0afb 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -519,7 +519,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg) { case CALL_ALLOC_AND_ENTER_INIT: return 1; case CALL_BOUND_METHOD_EXACT_ARGS: - return ((0) ? 1 : 0); + return 0; case CALL_BUILTIN_CLASS: return 1; case CALL_BUILTIN_FAST: @@ -551,7 +551,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg) { case CALL_METHOD_DESCRIPTOR_O: return 1; case CALL_PY_EXACT_ARGS: - return ((0) ? 1 : 0); + return 0; case CALL_PY_WITH_DEFAULTS: return 1; case CALL_STR_1: @@ -697,23 +697,23 @@ int _PyOpcode_num_pushed(int opcode, int oparg) { case LOAD_ATTR_CLASS: return 1 + (oparg & 1); case LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN: - return 1 + ((0) ? 1 : 0); + return 1; case LOAD_ATTR_INSTANCE_VALUE: return 1 + (oparg & 1); case LOAD_ATTR_METHOD_LAZY_DICT: - return 1 + ((1) ? 1 : 0); + return 2; case LOAD_ATTR_METHOD_NO_DICT: - return 1 + ((1) ? 1 : 0); + return 2; case LOAD_ATTR_METHOD_WITH_VALUES: - return 1 + ((1) ? 1 : 0); + return 2; case LOAD_ATTR_MODULE: return 1 + (oparg & 1); case LOAD_ATTR_NONDESCRIPTOR_NO_DICT: - return 1 + ((0) ? 1 : 0); + return 1; case LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: - return 1 + ((0) ? 1 : 0); + return 1; case LOAD_ATTR_PROPERTY: - return 1 + ((0) ? 1 : 0); + return 1; case LOAD_ATTR_SLOT: return 1 + (oparg & 1); case LOAD_ATTR_WITH_HINT: @@ -749,7 +749,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg) { case LOAD_SUPER_ATTR: return 1 + (oparg & 1); case LOAD_SUPER_ATTR_ATTR: - return 1 + ((0) ? 1 : 0); + return 1; case LOAD_SUPER_ATTR_METHOD: return 2; case MAKE_CELL: @@ -912,6 +912,7 @@ enum InstructionFormat { #define HAS_EXIT_FLAG (1024) #define HAS_PURE_FLAG (2048) #define HAS_PASSTHROUGH_FLAG (4096) +#define HAS_OPARG_AND_1_FLAG (8192) #define OPCODE_HAS_ARG(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_ARG_FLAG)) #define OPCODE_HAS_CONST(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_CONST_FLAG)) #define OPCODE_HAS_NAME(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_NAME_FLAG)) @@ -925,6 +926,7 @@ enum InstructionFormat { #define OPCODE_HAS_EXIT(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_EXIT_FLAG)) #define OPCODE_HAS_PURE(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_PURE_FLAG)) #define OPCODE_HAS_PASSTHROUGH(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_PASSTHROUGH_FLAG)) +#define OPCODE_HAS_OPARG_AND_1(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_OPARG_AND_1_FLAG)) #define OPARG_FULL 0 #define OPARG_CACHE_1 1 diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index ed800a73796da05..e098852d941f183 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -11,236 +11,263 @@ extern "C" { #define _EXIT_TRACE 300 #define _SET_IP 301 -#define _NOP NOP -#define _RESUME_CHECK RESUME_CHECK -#define _INSTRUMENTED_RESUME INSTRUMENTED_RESUME -#define _LOAD_FAST_CHECK LOAD_FAST_CHECK -#define _LOAD_FAST LOAD_FAST -#define _LOAD_FAST_AND_CLEAR LOAD_FAST_AND_CLEAR -#define _LOAD_FAST_LOAD_FAST LOAD_FAST_LOAD_FAST -#define _LOAD_CONST LOAD_CONST -#define _STORE_FAST STORE_FAST -#define _STORE_FAST_LOAD_FAST STORE_FAST_LOAD_FAST -#define _STORE_FAST_STORE_FAST STORE_FAST_STORE_FAST -#define _POP_TOP POP_TOP -#define _PUSH_NULL PUSH_NULL -#define _END_SEND END_SEND -#define _UNARY_NEGATIVE UNARY_NEGATIVE -#define _UNARY_NOT UNARY_NOT -#define _TO_BOOL 302 -#define _TO_BOOL_BOOL TO_BOOL_BOOL -#define _TO_BOOL_INT TO_BOOL_INT -#define _TO_BOOL_LIST TO_BOOL_LIST -#define _TO_BOOL_NONE TO_BOOL_NONE -#define _TO_BOOL_STR TO_BOOL_STR -#define _TO_BOOL_ALWAYS_TRUE TO_BOOL_ALWAYS_TRUE -#define _UNARY_INVERT UNARY_INVERT -#define _GUARD_BOTH_INT 303 -#define _BINARY_OP_MULTIPLY_INT 304 -#define _BINARY_OP_ADD_INT 305 -#define _BINARY_OP_SUBTRACT_INT 306 -#define _GUARD_BOTH_FLOAT 307 -#define _BINARY_OP_MULTIPLY_FLOAT 308 -#define _BINARY_OP_ADD_FLOAT 309 -#define _BINARY_OP_SUBTRACT_FLOAT 310 -#define _GUARD_BOTH_UNICODE 311 -#define _BINARY_OP_ADD_UNICODE 312 -#define _BINARY_SUBSCR 313 +#define _BEFORE_ASYNC_WITH BEFORE_ASYNC_WITH +#define _BEFORE_WITH BEFORE_WITH +#define _BINARY_OP 302 +#define _BINARY_OP_ADD_FLOAT 303 +#define _BINARY_OP_ADD_INT 304 +#define _BINARY_OP_ADD_UNICODE 305 +#define _BINARY_OP_MULTIPLY_FLOAT 306 +#define _BINARY_OP_MULTIPLY_INT 307 +#define _BINARY_OP_SUBTRACT_FLOAT 308 +#define _BINARY_OP_SUBTRACT_INT 309 #define _BINARY_SLICE BINARY_SLICE -#define _STORE_SLICE STORE_SLICE +#define _BINARY_SUBSCR 310 +#define _BINARY_SUBSCR_DICT BINARY_SUBSCR_DICT +#define _BINARY_SUBSCR_GETITEM BINARY_SUBSCR_GETITEM #define _BINARY_SUBSCR_LIST_INT BINARY_SUBSCR_LIST_INT #define _BINARY_SUBSCR_STR_INT BINARY_SUBSCR_STR_INT #define _BINARY_SUBSCR_TUPLE_INT BINARY_SUBSCR_TUPLE_INT -#define _BINARY_SUBSCR_DICT BINARY_SUBSCR_DICT -#define _BINARY_SUBSCR_GETITEM BINARY_SUBSCR_GETITEM -#define _LIST_APPEND LIST_APPEND -#define _SET_ADD SET_ADD -#define _STORE_SUBSCR 314 -#define _STORE_SUBSCR_LIST_INT STORE_SUBSCR_LIST_INT -#define _STORE_SUBSCR_DICT STORE_SUBSCR_DICT -#define _DELETE_SUBSCR DELETE_SUBSCR +#define _BUILD_CONST_KEY_MAP BUILD_CONST_KEY_MAP +#define _BUILD_LIST BUILD_LIST +#define _BUILD_MAP BUILD_MAP +#define _BUILD_SET BUILD_SET +#define _BUILD_SLICE BUILD_SLICE +#define _BUILD_STRING BUILD_STRING +#define _BUILD_TUPLE BUILD_TUPLE +#define _CALL 311 +#define _CALL_ALLOC_AND_ENTER_INIT CALL_ALLOC_AND_ENTER_INIT +#define _CALL_BUILTIN_CLASS CALL_BUILTIN_CLASS +#define _CALL_BUILTIN_FAST CALL_BUILTIN_FAST +#define _CALL_BUILTIN_FAST_WITH_KEYWORDS CALL_BUILTIN_FAST_WITH_KEYWORDS +#define _CALL_BUILTIN_O CALL_BUILTIN_O +#define _CALL_FUNCTION_EX CALL_FUNCTION_EX #define _CALL_INTRINSIC_1 CALL_INTRINSIC_1 #define _CALL_INTRINSIC_2 CALL_INTRINSIC_2 -#define _POP_FRAME 315 -#define _INSTRUMENTED_RETURN_VALUE INSTRUMENTED_RETURN_VALUE -#define _INSTRUMENTED_RETURN_CONST INSTRUMENTED_RETURN_CONST +#define _CALL_ISINSTANCE CALL_ISINSTANCE +#define _CALL_KW CALL_KW +#define _CALL_LEN CALL_LEN +#define _CALL_METHOD_DESCRIPTOR_FAST CALL_METHOD_DESCRIPTOR_FAST +#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS +#define _CALL_METHOD_DESCRIPTOR_NOARGS CALL_METHOD_DESCRIPTOR_NOARGS +#define _CALL_METHOD_DESCRIPTOR_O CALL_METHOD_DESCRIPTOR_O +#define _CALL_PY_WITH_DEFAULTS CALL_PY_WITH_DEFAULTS +#define _CALL_STR_1 CALL_STR_1 +#define _CALL_TUPLE_1 CALL_TUPLE_1 +#define _CALL_TYPE_1 CALL_TYPE_1 +#define _CHECK_ATTR_CLASS 312 +#define _CHECK_ATTR_METHOD_LAZY_DICT 313 +#define _CHECK_ATTR_MODULE 314 +#define _CHECK_ATTR_WITH_HINT 315 +#define _CHECK_BUILTINS 316 +#define _CHECK_CALL_BOUND_METHOD_EXACT_ARGS 317 +#define _CHECK_EG_MATCH CHECK_EG_MATCH +#define _CHECK_EXC_MATCH CHECK_EXC_MATCH +#define _CHECK_FUNCTION_EXACT_ARGS 318 +#define _CHECK_GLOBALS 319 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES 320 +#define _CHECK_PEP_523 321 +#define _CHECK_STACK_SPACE 322 +#define _CHECK_VALIDITY 323 +#define _CHECK_VALIDITY_AND_SET_IP 324 +#define _COLD_EXIT 325 +#define _COMPARE_OP 326 +#define _COMPARE_OP_FLOAT COMPARE_OP_FLOAT +#define _COMPARE_OP_INT COMPARE_OP_INT +#define _COMPARE_OP_STR COMPARE_OP_STR +#define _CONTAINS_OP CONTAINS_OP +#define _CONVERT_VALUE CONVERT_VALUE +#define _COPY COPY +#define _COPY_FREE_VARS COPY_FREE_VARS +#define _DELETE_ATTR DELETE_ATTR +#define _DELETE_DEREF DELETE_DEREF +#define _DELETE_FAST DELETE_FAST +#define _DELETE_GLOBAL DELETE_GLOBAL +#define _DELETE_NAME DELETE_NAME +#define _DELETE_SUBSCR DELETE_SUBSCR +#define _DICT_MERGE DICT_MERGE +#define _DICT_UPDATE DICT_UPDATE +#define _END_SEND END_SEND +#define _EXIT_INIT_CHECK EXIT_INIT_CHECK +#define _FATAL_ERROR 327 +#define _FORMAT_SIMPLE FORMAT_SIMPLE +#define _FORMAT_WITH_SPEC FORMAT_WITH_SPEC +#define _FOR_ITER 328 +#define _FOR_ITER_GEN FOR_ITER_GEN +#define _FOR_ITER_TIER_TWO 329 #define _GET_AITER GET_AITER #define _GET_ANEXT GET_ANEXT #define _GET_AWAITABLE GET_AWAITABLE -#define _SEND 316 -#define _SEND_GEN SEND_GEN +#define _GET_ITER GET_ITER +#define _GET_LEN GET_LEN +#define _GET_YIELD_FROM_ITER GET_YIELD_FROM_ITER +#define _GUARD_BOTH_FLOAT 330 +#define _GUARD_BOTH_INT 331 +#define _GUARD_BOTH_UNICODE 332 +#define _GUARD_BUILTINS_VERSION 333 +#define _GUARD_DORV_VALUES 334 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT 335 +#define _GUARD_GLOBALS_VERSION 336 +#define _GUARD_IS_FALSE_POP 337 +#define _GUARD_IS_NONE_POP 338 +#define _GUARD_IS_NOT_NONE_POP 339 +#define _GUARD_IS_TRUE_POP 340 +#define _GUARD_KEYS_VERSION 341 +#define _GUARD_NOT_EXHAUSTED_LIST 342 +#define _GUARD_NOT_EXHAUSTED_RANGE 343 +#define _GUARD_NOT_EXHAUSTED_TUPLE 344 +#define _GUARD_TYPE_VERSION 345 +#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS 346 +#define _INIT_CALL_PY_EXACT_ARGS 347 +#define _INIT_CALL_PY_EXACT_ARGS_0 348 +#define _INIT_CALL_PY_EXACT_ARGS_1 349 +#define _INIT_CALL_PY_EXACT_ARGS_2 350 +#define _INIT_CALL_PY_EXACT_ARGS_3 351 +#define _INIT_CALL_PY_EXACT_ARGS_4 352 +#define _INSTRUMENTED_CALL INSTRUMENTED_CALL +#define _INSTRUMENTED_CALL_FUNCTION_EX INSTRUMENTED_CALL_FUNCTION_EX +#define _INSTRUMENTED_CALL_KW INSTRUMENTED_CALL_KW +#define _INSTRUMENTED_FOR_ITER INSTRUMENTED_FOR_ITER +#define _INSTRUMENTED_INSTRUCTION INSTRUMENTED_INSTRUCTION +#define _INSTRUMENTED_JUMP_BACKWARD INSTRUMENTED_JUMP_BACKWARD +#define _INSTRUMENTED_JUMP_FORWARD INSTRUMENTED_JUMP_FORWARD +#define _INSTRUMENTED_LOAD_SUPER_ATTR INSTRUMENTED_LOAD_SUPER_ATTR +#define _INSTRUMENTED_POP_JUMP_IF_FALSE INSTRUMENTED_POP_JUMP_IF_FALSE +#define _INSTRUMENTED_POP_JUMP_IF_NONE INSTRUMENTED_POP_JUMP_IF_NONE +#define _INSTRUMENTED_POP_JUMP_IF_NOT_NONE INSTRUMENTED_POP_JUMP_IF_NOT_NONE +#define _INSTRUMENTED_POP_JUMP_IF_TRUE INSTRUMENTED_POP_JUMP_IF_TRUE +#define _INSTRUMENTED_RESUME INSTRUMENTED_RESUME +#define _INSTRUMENTED_RETURN_CONST INSTRUMENTED_RETURN_CONST +#define _INSTRUMENTED_RETURN_VALUE INSTRUMENTED_RETURN_VALUE #define _INSTRUMENTED_YIELD_VALUE INSTRUMENTED_YIELD_VALUE -#define _POP_EXCEPT POP_EXCEPT +#define _INTERNAL_INCREMENT_OPT_COUNTER 353 +#define _IS_NONE 354 +#define _IS_OP IS_OP +#define _ITER_CHECK_LIST 355 +#define _ITER_CHECK_RANGE 356 +#define _ITER_CHECK_TUPLE 357 +#define _ITER_JUMP_LIST 358 +#define _ITER_JUMP_RANGE 359 +#define _ITER_JUMP_TUPLE 360 +#define _ITER_NEXT_LIST 361 +#define _ITER_NEXT_RANGE 362 +#define _ITER_NEXT_TUPLE 363 +#define _JUMP_TO_TOP 364 +#define _LIST_APPEND LIST_APPEND +#define _LIST_EXTEND LIST_EXTEND #define _LOAD_ASSERTION_ERROR LOAD_ASSERTION_ERROR +#define _LOAD_ATTR 365 +#define _LOAD_ATTR_CLASS 366 +#define _LOAD_ATTR_CLASS_0 367 +#define _LOAD_ATTR_CLASS_1 368 +#define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN +#define _LOAD_ATTR_INSTANCE_VALUE 369 +#define _LOAD_ATTR_INSTANCE_VALUE_0 370 +#define _LOAD_ATTR_INSTANCE_VALUE_1 371 +#define _LOAD_ATTR_METHOD_LAZY_DICT 372 +#define _LOAD_ATTR_METHOD_NO_DICT 373 +#define _LOAD_ATTR_METHOD_WITH_VALUES 374 +#define _LOAD_ATTR_MODULE 375 +#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT 376 +#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 377 +#define _LOAD_ATTR_PROPERTY LOAD_ATTR_PROPERTY +#define _LOAD_ATTR_SLOT 378 +#define _LOAD_ATTR_SLOT_0 379 +#define _LOAD_ATTR_SLOT_1 380 +#define _LOAD_ATTR_WITH_HINT 381 #define _LOAD_BUILD_CLASS LOAD_BUILD_CLASS -#define _STORE_NAME STORE_NAME -#define _DELETE_NAME DELETE_NAME -#define _UNPACK_SEQUENCE 317 -#define _UNPACK_SEQUENCE_TWO_TUPLE UNPACK_SEQUENCE_TWO_TUPLE -#define _UNPACK_SEQUENCE_TUPLE UNPACK_SEQUENCE_TUPLE -#define _UNPACK_SEQUENCE_LIST UNPACK_SEQUENCE_LIST -#define _UNPACK_EX UNPACK_EX -#define _STORE_ATTR 318 -#define _DELETE_ATTR DELETE_ATTR -#define _STORE_GLOBAL STORE_GLOBAL -#define _DELETE_GLOBAL DELETE_GLOBAL -#define _LOAD_LOCALS LOAD_LOCALS +#define _LOAD_CONST LOAD_CONST +#define _LOAD_CONST_INLINE 382 +#define _LOAD_CONST_INLINE_BORROW 383 +#define _LOAD_CONST_INLINE_BORROW_WITH_NULL 384 +#define _LOAD_CONST_INLINE_WITH_NULL 385 +#define _LOAD_DEREF LOAD_DEREF +#define _LOAD_FAST 386 +#define _LOAD_FAST_0 387 +#define _LOAD_FAST_1 388 +#define _LOAD_FAST_2 389 +#define _LOAD_FAST_3 390 +#define _LOAD_FAST_4 391 +#define _LOAD_FAST_5 392 +#define _LOAD_FAST_6 393 +#define _LOAD_FAST_7 394 +#define _LOAD_FAST_AND_CLEAR LOAD_FAST_AND_CLEAR +#define _LOAD_FAST_CHECK LOAD_FAST_CHECK +#define _LOAD_FAST_LOAD_FAST LOAD_FAST_LOAD_FAST +#define _LOAD_FROM_DICT_OR_DEREF LOAD_FROM_DICT_OR_DEREF #define _LOAD_FROM_DICT_OR_GLOBALS LOAD_FROM_DICT_OR_GLOBALS +#define _LOAD_GLOBAL 395 +#define _LOAD_GLOBAL_BUILTINS 396 +#define _LOAD_GLOBAL_MODULE 397 +#define _LOAD_LOCALS LOAD_LOCALS #define _LOAD_NAME LOAD_NAME -#define _LOAD_GLOBAL 319 -#define _GUARD_GLOBALS_VERSION 320 -#define _GUARD_BUILTINS_VERSION 321 -#define _LOAD_GLOBAL_MODULE 322 -#define _LOAD_GLOBAL_BUILTINS 323 -#define _DELETE_FAST DELETE_FAST -#define _MAKE_CELL MAKE_CELL -#define _DELETE_DEREF DELETE_DEREF -#define _LOAD_FROM_DICT_OR_DEREF LOAD_FROM_DICT_OR_DEREF -#define _LOAD_DEREF LOAD_DEREF -#define _STORE_DEREF STORE_DEREF -#define _COPY_FREE_VARS COPY_FREE_VARS -#define _BUILD_STRING BUILD_STRING -#define _BUILD_TUPLE BUILD_TUPLE -#define _BUILD_LIST BUILD_LIST -#define _LIST_EXTEND LIST_EXTEND -#define _SET_UPDATE SET_UPDATE -#define _BUILD_SET BUILD_SET -#define _BUILD_MAP BUILD_MAP -#define _SETUP_ANNOTATIONS SETUP_ANNOTATIONS -#define _BUILD_CONST_KEY_MAP BUILD_CONST_KEY_MAP -#define _DICT_UPDATE DICT_UPDATE -#define _DICT_MERGE DICT_MERGE -#define _MAP_ADD MAP_ADD -#define _INSTRUMENTED_LOAD_SUPER_ATTR INSTRUMENTED_LOAD_SUPER_ATTR #define _LOAD_SUPER_ATTR_ATTR LOAD_SUPER_ATTR_ATTR #define _LOAD_SUPER_ATTR_METHOD LOAD_SUPER_ATTR_METHOD -#define _LOAD_ATTR 324 -#define _GUARD_TYPE_VERSION 325 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES 326 -#define _LOAD_ATTR_INSTANCE_VALUE 327 -#define _CHECK_ATTR_MODULE 328 -#define _LOAD_ATTR_MODULE 329 -#define _CHECK_ATTR_WITH_HINT 330 -#define _LOAD_ATTR_WITH_HINT 331 -#define _LOAD_ATTR_SLOT 332 -#define _CHECK_ATTR_CLASS 333 -#define _LOAD_ATTR_CLASS 334 -#define _LOAD_ATTR_PROPERTY LOAD_ATTR_PROPERTY -#define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN -#define _GUARD_DORV_VALUES 335 -#define _STORE_ATTR_INSTANCE_VALUE 336 -#define _STORE_ATTR_WITH_HINT STORE_ATTR_WITH_HINT -#define _STORE_ATTR_SLOT 337 -#define _COMPARE_OP 338 -#define _COMPARE_OP_FLOAT COMPARE_OP_FLOAT -#define _COMPARE_OP_INT COMPARE_OP_INT -#define _COMPARE_OP_STR COMPARE_OP_STR -#define _IS_OP IS_OP -#define _CONTAINS_OP CONTAINS_OP -#define _CHECK_EG_MATCH CHECK_EG_MATCH -#define _CHECK_EXC_MATCH CHECK_EXC_MATCH -#define _POP_JUMP_IF_FALSE 339 -#define _POP_JUMP_IF_TRUE 340 -#define _IS_NONE 341 -#define _GET_LEN GET_LEN +#define _MAKE_CELL MAKE_CELL +#define _MAKE_FUNCTION MAKE_FUNCTION +#define _MAP_ADD MAP_ADD #define _MATCH_CLASS MATCH_CLASS +#define _MATCH_KEYS MATCH_KEYS #define _MATCH_MAPPING MATCH_MAPPING #define _MATCH_SEQUENCE MATCH_SEQUENCE -#define _MATCH_KEYS MATCH_KEYS -#define _GET_ITER GET_ITER -#define _GET_YIELD_FROM_ITER GET_YIELD_FROM_ITER -#define _FOR_ITER 342 -#define _FOR_ITER_TIER_TWO 343 -#define _INSTRUMENTED_FOR_ITER INSTRUMENTED_FOR_ITER -#define _ITER_CHECK_LIST 344 -#define _ITER_JUMP_LIST 345 -#define _GUARD_NOT_EXHAUSTED_LIST 346 -#define _ITER_NEXT_LIST 347 -#define _ITER_CHECK_TUPLE 348 -#define _ITER_JUMP_TUPLE 349 -#define _GUARD_NOT_EXHAUSTED_TUPLE 350 -#define _ITER_NEXT_TUPLE 351 -#define _ITER_CHECK_RANGE 352 -#define _ITER_JUMP_RANGE 353 -#define _GUARD_NOT_EXHAUSTED_RANGE 354 -#define _ITER_NEXT_RANGE 355 -#define _FOR_ITER_GEN FOR_ITER_GEN -#define _BEFORE_ASYNC_WITH BEFORE_ASYNC_WITH -#define _BEFORE_WITH BEFORE_WITH -#define _WITH_EXCEPT_START WITH_EXCEPT_START +#define _NOP NOP +#define _POP_EXCEPT POP_EXCEPT +#define _POP_FRAME 398 +#define _POP_JUMP_IF_FALSE 399 +#define _POP_JUMP_IF_TRUE 400 +#define _POP_TOP POP_TOP #define _PUSH_EXC_INFO PUSH_EXC_INFO -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT 356 -#define _GUARD_KEYS_VERSION 357 -#define _LOAD_ATTR_METHOD_WITH_VALUES 358 -#define _LOAD_ATTR_METHOD_NO_DICT 359 -#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 360 -#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT 361 -#define _CHECK_ATTR_METHOD_LAZY_DICT 362 -#define _LOAD_ATTR_METHOD_LAZY_DICT 363 -#define _INSTRUMENTED_CALL INSTRUMENTED_CALL -#define _CALL 364 -#define _CHECK_CALL_BOUND_METHOD_EXACT_ARGS 365 -#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS 366 -#define _CHECK_PEP_523 367 -#define _CHECK_FUNCTION_EXACT_ARGS 368 -#define _CHECK_STACK_SPACE 369 -#define _INIT_CALL_PY_EXACT_ARGS 370 -#define _PUSH_FRAME 371 -#define _CALL_PY_WITH_DEFAULTS CALL_PY_WITH_DEFAULTS -#define _CALL_TYPE_1 CALL_TYPE_1 -#define _CALL_STR_1 CALL_STR_1 -#define _CALL_TUPLE_1 CALL_TUPLE_1 -#define _CALL_ALLOC_AND_ENTER_INIT CALL_ALLOC_AND_ENTER_INIT -#define _EXIT_INIT_CHECK EXIT_INIT_CHECK -#define _CALL_BUILTIN_CLASS CALL_BUILTIN_CLASS -#define _CALL_BUILTIN_O CALL_BUILTIN_O -#define _CALL_BUILTIN_FAST CALL_BUILTIN_FAST -#define _CALL_BUILTIN_FAST_WITH_KEYWORDS CALL_BUILTIN_FAST_WITH_KEYWORDS -#define _CALL_LEN CALL_LEN -#define _CALL_ISINSTANCE CALL_ISINSTANCE -#define _CALL_METHOD_DESCRIPTOR_O CALL_METHOD_DESCRIPTOR_O -#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS -#define _CALL_METHOD_DESCRIPTOR_NOARGS CALL_METHOD_DESCRIPTOR_NOARGS -#define _CALL_METHOD_DESCRIPTOR_FAST CALL_METHOD_DESCRIPTOR_FAST -#define _INSTRUMENTED_CALL_KW INSTRUMENTED_CALL_KW -#define _CALL_KW CALL_KW -#define _INSTRUMENTED_CALL_FUNCTION_EX INSTRUMENTED_CALL_FUNCTION_EX -#define _CALL_FUNCTION_EX CALL_FUNCTION_EX -#define _MAKE_FUNCTION MAKE_FUNCTION +#define _PUSH_FRAME 401 +#define _PUSH_NULL PUSH_NULL +#define _RESUME_CHECK RESUME_CHECK +#define _SAVE_RETURN_OFFSET 402 +#define _SEND 403 +#define _SEND_GEN SEND_GEN +#define _SETUP_ANNOTATIONS SETUP_ANNOTATIONS +#define _SET_ADD SET_ADD #define _SET_FUNCTION_ATTRIBUTE SET_FUNCTION_ATTRIBUTE -#define _BUILD_SLICE BUILD_SLICE -#define _CONVERT_VALUE CONVERT_VALUE -#define _FORMAT_SIMPLE FORMAT_SIMPLE -#define _FORMAT_WITH_SPEC FORMAT_WITH_SPEC -#define _COPY COPY -#define _BINARY_OP 372 +#define _SET_UPDATE SET_UPDATE +#define _START_EXECUTOR 404 +#define _STORE_ATTR 405 +#define _STORE_ATTR_INSTANCE_VALUE 406 +#define _STORE_ATTR_SLOT 407 +#define _STORE_ATTR_WITH_HINT STORE_ATTR_WITH_HINT +#define _STORE_DEREF STORE_DEREF +#define _STORE_FAST 408 +#define _STORE_FAST_0 409 +#define _STORE_FAST_1 410 +#define _STORE_FAST_2 411 +#define _STORE_FAST_3 412 +#define _STORE_FAST_4 413 +#define _STORE_FAST_5 414 +#define _STORE_FAST_6 415 +#define _STORE_FAST_7 416 +#define _STORE_FAST_LOAD_FAST STORE_FAST_LOAD_FAST +#define _STORE_FAST_STORE_FAST STORE_FAST_STORE_FAST +#define _STORE_GLOBAL STORE_GLOBAL +#define _STORE_NAME STORE_NAME +#define _STORE_SLICE STORE_SLICE +#define _STORE_SUBSCR 417 +#define _STORE_SUBSCR_DICT STORE_SUBSCR_DICT +#define _STORE_SUBSCR_LIST_INT STORE_SUBSCR_LIST_INT #define _SWAP SWAP -#define _INSTRUMENTED_INSTRUCTION INSTRUMENTED_INSTRUCTION -#define _INSTRUMENTED_JUMP_FORWARD INSTRUMENTED_JUMP_FORWARD -#define _INSTRUMENTED_JUMP_BACKWARD INSTRUMENTED_JUMP_BACKWARD -#define _INSTRUMENTED_POP_JUMP_IF_TRUE INSTRUMENTED_POP_JUMP_IF_TRUE -#define _INSTRUMENTED_POP_JUMP_IF_FALSE INSTRUMENTED_POP_JUMP_IF_FALSE -#define _INSTRUMENTED_POP_JUMP_IF_NONE INSTRUMENTED_POP_JUMP_IF_NONE -#define _INSTRUMENTED_POP_JUMP_IF_NOT_NONE INSTRUMENTED_POP_JUMP_IF_NOT_NONE -#define _GUARD_IS_TRUE_POP 373 -#define _GUARD_IS_FALSE_POP 374 -#define _GUARD_IS_NONE_POP 375 -#define _GUARD_IS_NOT_NONE_POP 376 -#define _JUMP_TO_TOP 377 -#define _SAVE_RETURN_OFFSET 378 -#define _CHECK_VALIDITY 379 -#define _LOAD_CONST_INLINE 380 -#define _LOAD_CONST_INLINE_BORROW 381 -#define _LOAD_CONST_INLINE_WITH_NULL 382 -#define _LOAD_CONST_INLINE_BORROW_WITH_NULL 383 -#define _CHECK_GLOBALS 384 -#define _CHECK_BUILTINS 385 -#define _INTERNAL_INCREMENT_OPT_COUNTER 386 -#define _COLD_EXIT 387 -#define _START_EXECUTOR 388 -#define _FATAL_ERROR 389 -#define _CHECK_VALIDITY_AND_SET_IP 390 -#define MAX_UOP_ID 390 +#define _TO_BOOL 418 +#define _TO_BOOL_ALWAYS_TRUE TO_BOOL_ALWAYS_TRUE +#define _TO_BOOL_BOOL TO_BOOL_BOOL +#define _TO_BOOL_INT TO_BOOL_INT +#define _TO_BOOL_LIST TO_BOOL_LIST +#define _TO_BOOL_NONE TO_BOOL_NONE +#define _TO_BOOL_STR TO_BOOL_STR +#define _UNARY_INVERT UNARY_INVERT +#define _UNARY_NEGATIVE UNARY_NEGATIVE +#define _UNARY_NOT UNARY_NOT +#define _UNPACK_EX UNPACK_EX +#define _UNPACK_SEQUENCE 419 +#define _UNPACK_SEQUENCE_LIST UNPACK_SEQUENCE_LIST +#define _UNPACK_SEQUENCE_TUPLE UNPACK_SEQUENCE_TUPLE +#define _UNPACK_SEQUENCE_TWO_TUPLE UNPACK_SEQUENCE_TWO_TUPLE +#define _WITH_EXCEPT_START WITH_EXCEPT_START +#define MAX_UOP_ID 419 #ifdef __cplusplus } diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 1e2dfecd9cf8afe..c9def0ecdc15016 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -12,6 +12,7 @@ extern "C" { #include <stdint.h> #include "pycore_uop_ids.h" extern const uint16_t _PyUop_Flags[MAX_UOP_ID+1]; +extern const uint8_t _PyUop_Replication[MAX_UOP_ID+1]; extern const char * const _PyOpcode_uop_name[MAX_UOP_ID+1]; #ifdef NEED_OPCODE_METADATA @@ -19,10 +20,26 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_NOP] = HAS_PURE_FLAG, [_RESUME_CHECK] = HAS_DEOPT_FLAG, [_LOAD_FAST_CHECK] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG, + [_LOAD_FAST_0] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_1] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_2] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_3] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_4] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_5] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_6] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_7] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, [_LOAD_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_PURE_FLAG, [_LOAD_FAST_AND_CLEAR] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, [_LOAD_FAST_LOAD_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, [_LOAD_CONST] = HAS_ARG_FLAG | HAS_CONST_FLAG | HAS_PURE_FLAG, + [_STORE_FAST_0] = HAS_LOCAL_FLAG, + [_STORE_FAST_1] = HAS_LOCAL_FLAG, + [_STORE_FAST_2] = HAS_LOCAL_FLAG, + [_STORE_FAST_3] = HAS_LOCAL_FLAG, + [_STORE_FAST_4] = HAS_LOCAL_FLAG, + [_STORE_FAST_5] = HAS_LOCAL_FLAG, + [_STORE_FAST_6] = HAS_LOCAL_FLAG, + [_STORE_FAST_7] = HAS_LOCAL_FLAG, [_STORE_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, [_STORE_FAST_LOAD_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, [_STORE_FAST_STORE_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, @@ -114,14 +131,20 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_LOAD_ATTR] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_GUARD_TYPE_VERSION] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_PASSTHROUGH_FLAG, [_CHECK_MANAGED_OBJECT_HAS_VALUES] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, - [_LOAD_ATTR_INSTANCE_VALUE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, + [_LOAD_ATTR_INSTANCE_VALUE_0] = HAS_DEOPT_FLAG, + [_LOAD_ATTR_INSTANCE_VALUE_1] = HAS_DEOPT_FLAG, + [_LOAD_ATTR_INSTANCE_VALUE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_OPARG_AND_1_FLAG, [_CHECK_ATTR_MODULE] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, [_LOAD_ATTR_MODULE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, [_CHECK_ATTR_WITH_HINT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG | HAS_PASSTHROUGH_FLAG, [_LOAD_ATTR_WITH_HINT] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, - [_LOAD_ATTR_SLOT] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, + [_LOAD_ATTR_SLOT_0] = HAS_DEOPT_FLAG, + [_LOAD_ATTR_SLOT_1] = HAS_DEOPT_FLAG, + [_LOAD_ATTR_SLOT] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_OPARG_AND_1_FLAG, [_CHECK_ATTR_CLASS] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, - [_LOAD_ATTR_CLASS] = HAS_ARG_FLAG, + [_LOAD_ATTR_CLASS_0] = 0, + [_LOAD_ATTR_CLASS_1] = 0, + [_LOAD_ATTR_CLASS] = HAS_ARG_FLAG | HAS_OPARG_AND_1_FLAG, [_GUARD_DORV_VALUES] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, [_STORE_ATTR_INSTANCE_VALUE] = HAS_ESCAPES_FLAG, [_STORE_ATTR_SLOT] = HAS_ESCAPES_FLAG, @@ -168,6 +191,11 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_CHECK_PEP_523] = HAS_DEOPT_FLAG, [_CHECK_FUNCTION_EXACT_ARGS] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, [_CHECK_STACK_SPACE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, + [_INIT_CALL_PY_EXACT_ARGS_0] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, + [_INIT_CALL_PY_EXACT_ARGS_1] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, + [_INIT_CALL_PY_EXACT_ARGS_2] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, + [_INIT_CALL_PY_EXACT_ARGS_3] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, + [_INIT_CALL_PY_EXACT_ARGS_4] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, [_INIT_CALL_PY_EXACT_ARGS] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG, [_PUSH_FRAME] = HAS_ESCAPES_FLAG, [_CALL_TYPE_1] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, @@ -215,6 +243,12 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_CHECK_VALIDITY_AND_SET_IP] = HAS_DEOPT_FLAG, }; +const uint8_t _PyUop_Replication[MAX_UOP_ID+1] = { + [_LOAD_FAST] = 8, + [_STORE_FAST] = 8, + [_INIT_CALL_PY_EXACT_ARGS] = 5, +}; + const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_BEFORE_ASYNC_WITH] = "_BEFORE_ASYNC_WITH", [_BEFORE_WITH] = "_BEFORE_WITH", @@ -317,6 +351,11 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_GUARD_TYPE_VERSION] = "_GUARD_TYPE_VERSION", [_INIT_CALL_BOUND_METHOD_EXACT_ARGS] = "_INIT_CALL_BOUND_METHOD_EXACT_ARGS", [_INIT_CALL_PY_EXACT_ARGS] = "_INIT_CALL_PY_EXACT_ARGS", + [_INIT_CALL_PY_EXACT_ARGS_0] = "_INIT_CALL_PY_EXACT_ARGS_0", + [_INIT_CALL_PY_EXACT_ARGS_1] = "_INIT_CALL_PY_EXACT_ARGS_1", + [_INIT_CALL_PY_EXACT_ARGS_2] = "_INIT_CALL_PY_EXACT_ARGS_2", + [_INIT_CALL_PY_EXACT_ARGS_3] = "_INIT_CALL_PY_EXACT_ARGS_3", + [_INIT_CALL_PY_EXACT_ARGS_4] = "_INIT_CALL_PY_EXACT_ARGS_4", [_INTERNAL_INCREMENT_OPT_COUNTER] = "_INTERNAL_INCREMENT_OPT_COUNTER", [_IS_NONE] = "_IS_NONE", [_IS_OP] = "_IS_OP", @@ -332,7 +371,11 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_LOAD_ASSERTION_ERROR] = "_LOAD_ASSERTION_ERROR", [_LOAD_ATTR] = "_LOAD_ATTR", [_LOAD_ATTR_CLASS] = "_LOAD_ATTR_CLASS", + [_LOAD_ATTR_CLASS_0] = "_LOAD_ATTR_CLASS_0", + [_LOAD_ATTR_CLASS_1] = "_LOAD_ATTR_CLASS_1", [_LOAD_ATTR_INSTANCE_VALUE] = "_LOAD_ATTR_INSTANCE_VALUE", + [_LOAD_ATTR_INSTANCE_VALUE_0] = "_LOAD_ATTR_INSTANCE_VALUE_0", + [_LOAD_ATTR_INSTANCE_VALUE_1] = "_LOAD_ATTR_INSTANCE_VALUE_1", [_LOAD_ATTR_METHOD_LAZY_DICT] = "_LOAD_ATTR_METHOD_LAZY_DICT", [_LOAD_ATTR_METHOD_NO_DICT] = "_LOAD_ATTR_METHOD_NO_DICT", [_LOAD_ATTR_METHOD_WITH_VALUES] = "_LOAD_ATTR_METHOD_WITH_VALUES", @@ -340,6 +383,8 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = "_LOAD_ATTR_NONDESCRIPTOR_NO_DICT", [_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = "_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", [_LOAD_ATTR_SLOT] = "_LOAD_ATTR_SLOT", + [_LOAD_ATTR_SLOT_0] = "_LOAD_ATTR_SLOT_0", + [_LOAD_ATTR_SLOT_1] = "_LOAD_ATTR_SLOT_1", [_LOAD_ATTR_WITH_HINT] = "_LOAD_ATTR_WITH_HINT", [_LOAD_BUILD_CLASS] = "_LOAD_BUILD_CLASS", [_LOAD_CONST] = "_LOAD_CONST", @@ -349,6 +394,14 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_LOAD_CONST_INLINE_WITH_NULL] = "_LOAD_CONST_INLINE_WITH_NULL", [_LOAD_DEREF] = "_LOAD_DEREF", [_LOAD_FAST] = "_LOAD_FAST", + [_LOAD_FAST_0] = "_LOAD_FAST_0", + [_LOAD_FAST_1] = "_LOAD_FAST_1", + [_LOAD_FAST_2] = "_LOAD_FAST_2", + [_LOAD_FAST_3] = "_LOAD_FAST_3", + [_LOAD_FAST_4] = "_LOAD_FAST_4", + [_LOAD_FAST_5] = "_LOAD_FAST_5", + [_LOAD_FAST_6] = "_LOAD_FAST_6", + [_LOAD_FAST_7] = "_LOAD_FAST_7", [_LOAD_FAST_AND_CLEAR] = "_LOAD_FAST_AND_CLEAR", [_LOAD_FAST_CHECK] = "_LOAD_FAST_CHECK", [_LOAD_FAST_LOAD_FAST] = "_LOAD_FAST_LOAD_FAST", @@ -388,6 +441,14 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_STORE_ATTR_SLOT] = "_STORE_ATTR_SLOT", [_STORE_DEREF] = "_STORE_DEREF", [_STORE_FAST] = "_STORE_FAST", + [_STORE_FAST_0] = "_STORE_FAST_0", + [_STORE_FAST_1] = "_STORE_FAST_1", + [_STORE_FAST_2] = "_STORE_FAST_2", + [_STORE_FAST_3] = "_STORE_FAST_3", + [_STORE_FAST_4] = "_STORE_FAST_4", + [_STORE_FAST_5] = "_STORE_FAST_5", + [_STORE_FAST_6] = "_STORE_FAST_6", + [_STORE_FAST_7] = "_STORE_FAST_7", [_STORE_FAST_LOAD_FAST] = "_STORE_FAST_LOAD_FAST", [_STORE_FAST_STORE_FAST] = "_STORE_FAST_STORE_FAST", [_STORE_GLOBAL] = "_STORE_GLOBAL", diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 66860c67966859f..9d19b6c3ad3befb 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -216,7 +216,7 @@ def testfunc(x): self.assertIsNotNone(ex) uops = {opname for opname, _, _ in ex} self.assertIn("_SET_IP", uops) - self.assertIn("_LOAD_FAST", uops) + self.assertIn("_LOAD_FAST_0", uops) def test_extended_arg(self): "Check EXTENDED_ARG handling in superblock creation" diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 2e0008e63f6e0c6..27c439b71fa9d91 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -54,6 +54,8 @@ #define guard #define override #define specializing +#define split +#define replicate(TIMES) // Dummy variables for stack effects. static PyObject *value, *value1, *value2, *left, *right, *res, *sum, *prod, *sub; @@ -208,7 +210,7 @@ dummy_func( Py_INCREF(value); } - pure inst(LOAD_FAST, (-- value)) { + replicate(8) pure inst(LOAD_FAST, (-- value)) { value = GETLOCAL(oparg); assert(value != NULL); Py_INCREF(value); @@ -234,7 +236,7 @@ dummy_func( Py_INCREF(value); } - inst(STORE_FAST, (value --)) { + replicate(8) inst(STORE_FAST, (value --)) { SETLOCAL(oparg, value); } @@ -1914,7 +1916,7 @@ dummy_func( DEOPT_IF(!_PyDictOrValues_IsValues(*dorv) && !_PyObject_MakeInstanceAttributesFromDict(owner, dorv)); } - op(_LOAD_ATTR_INSTANCE_VALUE, (index/1, owner -- attr, null if (oparg & 1))) { + split op(_LOAD_ATTR_INSTANCE_VALUE, (index/1, owner -- attr, null if (oparg & 1))) { PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); attr = _PyDictOrValues_GetValues(dorv)->values[index]; DEOPT_IF(attr == NULL); @@ -1995,7 +1997,7 @@ dummy_func( _LOAD_ATTR_WITH_HINT + unused/5; - op(_LOAD_ATTR_SLOT, (index/1, owner -- attr, null if (oparg & 1))) { + split op(_LOAD_ATTR_SLOT, (index/1, owner -- attr, null if (oparg & 1))) { char *addr = (char *)owner + index; attr = *(PyObject **)addr; DEOPT_IF(attr == NULL); @@ -2018,7 +2020,7 @@ dummy_func( } - op(_LOAD_ATTR_CLASS, (descr/4, owner -- attr, null if (oparg & 1))) { + split op(_LOAD_ATTR_CLASS, (descr/4, owner -- attr, null if (oparg & 1))) { STAT_INC(LOAD_ATTR, hit); assert(descr != NULL); attr = Py_NewRef(descr); @@ -2888,7 +2890,7 @@ dummy_func( DEOPT_IF(owner_heap_type->ht_cached_keys->dk_version != keys_version); } - op(_LOAD_ATTR_METHOD_WITH_VALUES, (descr/4, owner -- attr, self if (1))) { + split op(_LOAD_ATTR_METHOD_WITH_VALUES, (descr/4, owner -- attr, self if (1))) { assert(oparg & 1); /* Cached method object */ STAT_INC(LOAD_ATTR, hit); @@ -3130,7 +3132,7 @@ dummy_func( DEOPT_IF(tstate->py_recursion_remaining <= 1); } - pure op(_INIT_CALL_PY_EXACT_ARGS, (callable, self_or_null, args[oparg] -- new_frame: _PyInterpreterFrame*)) { + replicate(5) pure op(_INIT_CALL_PY_EXACT_ARGS, (callable, self_or_null, args[oparg] -- new_frame: _PyInterpreterFrame*)) { int argcount = oparg; if (self_or_null != NULL) { args--; diff --git a/Python/ceval.c b/Python/ceval.c index adccf8fc00f69c0..6f647cfdd53d832 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1029,7 +1029,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int #ifdef Py_DEBUG { fprintf(stderr, "Unknown uop %d, oparg %d, operand %" PRIu64 " @ %d\n", - opcode, next_uop[-1].oparg, next_uop[-1].operand, + next_uop[-1].opcode, next_uop[-1].oparg, next_uop[-1].operand, (int)(next_uop - current_executor->trace - 1)); Py_FatalError("Unknown uop"); } diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index a18284d89ab42cb..b46885e0d5cbc70 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -37,6 +37,102 @@ break; } + case _LOAD_FAST_0: { + PyObject *value; + oparg = 0; + assert(oparg == CURRENT_OPARG()); + value = GETLOCAL(oparg); + assert(value != NULL); + Py_INCREF(value); + stack_pointer[0] = value; + stack_pointer += 1; + break; + } + + case _LOAD_FAST_1: { + PyObject *value; + oparg = 1; + assert(oparg == CURRENT_OPARG()); + value = GETLOCAL(oparg); + assert(value != NULL); + Py_INCREF(value); + stack_pointer[0] = value; + stack_pointer += 1; + break; + } + + case _LOAD_FAST_2: { + PyObject *value; + oparg = 2; + assert(oparg == CURRENT_OPARG()); + value = GETLOCAL(oparg); + assert(value != NULL); + Py_INCREF(value); + stack_pointer[0] = value; + stack_pointer += 1; + break; + } + + case _LOAD_FAST_3: { + PyObject *value; + oparg = 3; + assert(oparg == CURRENT_OPARG()); + value = GETLOCAL(oparg); + assert(value != NULL); + Py_INCREF(value); + stack_pointer[0] = value; + stack_pointer += 1; + break; + } + + case _LOAD_FAST_4: { + PyObject *value; + oparg = 4; + assert(oparg == CURRENT_OPARG()); + value = GETLOCAL(oparg); + assert(value != NULL); + Py_INCREF(value); + stack_pointer[0] = value; + stack_pointer += 1; + break; + } + + case _LOAD_FAST_5: { + PyObject *value; + oparg = 5; + assert(oparg == CURRENT_OPARG()); + value = GETLOCAL(oparg); + assert(value != NULL); + Py_INCREF(value); + stack_pointer[0] = value; + stack_pointer += 1; + break; + } + + case _LOAD_FAST_6: { + PyObject *value; + oparg = 6; + assert(oparg == CURRENT_OPARG()); + value = GETLOCAL(oparg); + assert(value != NULL); + Py_INCREF(value); + stack_pointer[0] = value; + stack_pointer += 1; + break; + } + + case _LOAD_FAST_7: { + PyObject *value; + oparg = 7; + assert(oparg == CURRENT_OPARG()); + value = GETLOCAL(oparg); + assert(value != NULL); + Py_INCREF(value); + stack_pointer[0] = value; + stack_pointer += 1; + break; + } + case _LOAD_FAST: { PyObject *value; oparg = CURRENT_OPARG(); @@ -69,6 +165,86 @@ break; } + case _STORE_FAST_0: { + PyObject *value; + oparg = 0; + assert(oparg == CURRENT_OPARG()); + value = stack_pointer[-1]; + SETLOCAL(oparg, value); + stack_pointer += -1; + break; + } + + case _STORE_FAST_1: { + PyObject *value; + oparg = 1; + assert(oparg == CURRENT_OPARG()); + value = stack_pointer[-1]; + SETLOCAL(oparg, value); + stack_pointer += -1; + break; + } + + case _STORE_FAST_2: { + PyObject *value; + oparg = 2; + assert(oparg == CURRENT_OPARG()); + value = stack_pointer[-1]; + SETLOCAL(oparg, value); + stack_pointer += -1; + break; + } + + case _STORE_FAST_3: { + PyObject *value; + oparg = 3; + assert(oparg == CURRENT_OPARG()); + value = stack_pointer[-1]; + SETLOCAL(oparg, value); + stack_pointer += -1; + break; + } + + case _STORE_FAST_4: { + PyObject *value; + oparg = 4; + assert(oparg == CURRENT_OPARG()); + value = stack_pointer[-1]; + SETLOCAL(oparg, value); + stack_pointer += -1; + break; + } + + case _STORE_FAST_5: { + PyObject *value; + oparg = 5; + assert(oparg == CURRENT_OPARG()); + value = stack_pointer[-1]; + SETLOCAL(oparg, value); + stack_pointer += -1; + break; + } + + case _STORE_FAST_6: { + PyObject *value; + oparg = 6; + assert(oparg == CURRENT_OPARG()); + value = stack_pointer[-1]; + SETLOCAL(oparg, value); + stack_pointer += -1; + break; + } + + case _STORE_FAST_7: { + PyObject *value; + oparg = 7; + assert(oparg == CURRENT_OPARG()); + value = stack_pointer[-1]; + SETLOCAL(oparg, value); + stack_pointer += -1; + break; + } + case _STORE_FAST: { PyObject *value; oparg = CURRENT_OPARG(); @@ -1534,7 +1710,7 @@ Py_DECREF(self); if (attr == NULL) goto pop_3_error_tier_two; stack_pointer[-3] = attr; - stack_pointer += -2 + ((0) ? 1 : 0); + stack_pointer += -2; break; } @@ -1637,11 +1813,11 @@ break; } - case _LOAD_ATTR_INSTANCE_VALUE: { + case _LOAD_ATTR_INSTANCE_VALUE_0: { PyObject *owner; PyObject *attr; PyObject *null = NULL; - oparg = CURRENT_OPARG(); + (void)null; owner = stack_pointer[-1]; uint16_t index = (uint16_t)CURRENT_OPERAND(); PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); @@ -1652,11 +1828,31 @@ null = NULL; Py_DECREF(owner); stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; - stack_pointer += (oparg & 1); break; } + case _LOAD_ATTR_INSTANCE_VALUE_1: { + PyObject *owner; + PyObject *attr; + PyObject *null = NULL; + (void)null; + owner = stack_pointer[-1]; + uint16_t index = (uint16_t)CURRENT_OPERAND(); + PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); + attr = _PyDictOrValues_GetValues(dorv)->values[index]; + if (attr == NULL) goto deoptimize; + STAT_INC(LOAD_ATTR, hit); + Py_INCREF(attr); + null = NULL; + Py_DECREF(owner); + stack_pointer[-1] = attr; + stack_pointer[0] = null; + stack_pointer += 1; + break; + } + + /* _LOAD_ATTR_INSTANCE_VALUE is split on (oparg & 1) */ + case _CHECK_ATTR_MODULE: { PyObject *owner; owner = stack_pointer[-1]; @@ -1735,11 +1931,11 @@ break; } - case _LOAD_ATTR_SLOT: { + case _LOAD_ATTR_SLOT_0: { PyObject *owner; PyObject *attr; PyObject *null = NULL; - oparg = CURRENT_OPARG(); + (void)null; owner = stack_pointer[-1]; uint16_t index = (uint16_t)CURRENT_OPERAND(); char *addr = (char *)owner + index; @@ -1750,11 +1946,31 @@ null = NULL; Py_DECREF(owner); stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; - stack_pointer += (oparg & 1); break; } + case _LOAD_ATTR_SLOT_1: { + PyObject *owner; + PyObject *attr; + PyObject *null = NULL; + (void)null; + owner = stack_pointer[-1]; + uint16_t index = (uint16_t)CURRENT_OPERAND(); + char *addr = (char *)owner + index; + attr = *(PyObject **)addr; + if (attr == NULL) goto deoptimize; + STAT_INC(LOAD_ATTR, hit); + Py_INCREF(attr); + null = NULL; + Py_DECREF(owner); + stack_pointer[-1] = attr; + stack_pointer[0] = null; + stack_pointer += 1; + break; + } + + /* _LOAD_ATTR_SLOT is split on (oparg & 1) */ + case _CHECK_ATTR_CLASS: { PyObject *owner; owner = stack_pointer[-1]; @@ -1765,11 +1981,11 @@ break; } - case _LOAD_ATTR_CLASS: { + case _LOAD_ATTR_CLASS_0: { PyObject *owner; PyObject *attr; PyObject *null = NULL; - oparg = CURRENT_OPARG(); + (void)null; owner = stack_pointer[-1]; PyObject *descr = (PyObject *)CURRENT_OPERAND(); STAT_INC(LOAD_ATTR, hit); @@ -1778,11 +1994,29 @@ null = NULL; Py_DECREF(owner); stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; - stack_pointer += (oparg & 1); break; } + case _LOAD_ATTR_CLASS_1: { + PyObject *owner; + PyObject *attr; + PyObject *null = NULL; + (void)null; + owner = stack_pointer[-1]; + PyObject *descr = (PyObject *)CURRENT_OPERAND(); + STAT_INC(LOAD_ATTR, hit); + assert(descr != NULL); + attr = Py_NewRef(descr); + null = NULL; + Py_DECREF(owner); + stack_pointer[-1] = attr; + stack_pointer[0] = null; + stack_pointer += 1; + break; + } + + /* _LOAD_ATTR_CLASS is split on (oparg & 1) */ + /* _LOAD_ATTR_PROPERTY is not a viable micro-op for tier 2 */ /* _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN is not a viable micro-op for tier 2 */ @@ -2464,8 +2698,8 @@ assert(_PyType_HasFeature(Py_TYPE(attr), Py_TPFLAGS_METHOD_DESCRIPTOR)); self = owner; stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; - stack_pointer += ((1) ? 1 : 0); + stack_pointer[0] = self; + stack_pointer += 1; break; } @@ -2484,8 +2718,8 @@ attr = Py_NewRef(descr); self = owner; stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; - stack_pointer += ((1) ? 1 : 0); + stack_pointer[0] = self; + stack_pointer += 1; break; } @@ -2501,7 +2735,6 @@ Py_DECREF(owner); attr = Py_NewRef(descr); stack_pointer[-1] = attr; - stack_pointer += ((0) ? 1 : 0); break; } @@ -2518,7 +2751,6 @@ Py_DECREF(owner); attr = Py_NewRef(descr); stack_pointer[-1] = attr; - stack_pointer += ((0) ? 1 : 0); break; } @@ -2547,8 +2779,8 @@ attr = Py_NewRef(descr); self = owner; stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; - stack_pointer += ((1) ? 1 : 0); + stack_pointer[0] = self; + stack_pointer += 1; break; } @@ -2615,6 +2847,136 @@ break; } + case _INIT_CALL_PY_EXACT_ARGS_0: { + PyObject **args; + PyObject *self_or_null; + PyObject *callable; + _PyInterpreterFrame *new_frame; + oparg = 0; + assert(oparg == CURRENT_OPARG()); + args = &stack_pointer[-oparg]; + self_or_null = stack_pointer[-1 - oparg]; + callable = stack_pointer[-2 - oparg]; + int argcount = oparg; + if (self_or_null != NULL) { + args--; + argcount++; + } + STAT_INC(CALL, hit); + PyFunctionObject *func = (PyFunctionObject *)callable; + new_frame = _PyFrame_PushUnchecked(tstate, func, argcount); + for (int i = 0; i < argcount; i++) { + new_frame->localsplus[i] = args[i]; + } + stack_pointer[-2 - oparg] = (PyObject *)new_frame; + stack_pointer += -1 - oparg; + break; + } + + case _INIT_CALL_PY_EXACT_ARGS_1: { + PyObject **args; + PyObject *self_or_null; + PyObject *callable; + _PyInterpreterFrame *new_frame; + oparg = 1; + assert(oparg == CURRENT_OPARG()); + args = &stack_pointer[-oparg]; + self_or_null = stack_pointer[-1 - oparg]; + callable = stack_pointer[-2 - oparg]; + int argcount = oparg; + if (self_or_null != NULL) { + args--; + argcount++; + } + STAT_INC(CALL, hit); + PyFunctionObject *func = (PyFunctionObject *)callable; + new_frame = _PyFrame_PushUnchecked(tstate, func, argcount); + for (int i = 0; i < argcount; i++) { + new_frame->localsplus[i] = args[i]; + } + stack_pointer[-2 - oparg] = (PyObject *)new_frame; + stack_pointer += -1 - oparg; + break; + } + + case _INIT_CALL_PY_EXACT_ARGS_2: { + PyObject **args; + PyObject *self_or_null; + PyObject *callable; + _PyInterpreterFrame *new_frame; + oparg = 2; + assert(oparg == CURRENT_OPARG()); + args = &stack_pointer[-oparg]; + self_or_null = stack_pointer[-1 - oparg]; + callable = stack_pointer[-2 - oparg]; + int argcount = oparg; + if (self_or_null != NULL) { + args--; + argcount++; + } + STAT_INC(CALL, hit); + PyFunctionObject *func = (PyFunctionObject *)callable; + new_frame = _PyFrame_PushUnchecked(tstate, func, argcount); + for (int i = 0; i < argcount; i++) { + new_frame->localsplus[i] = args[i]; + } + stack_pointer[-2 - oparg] = (PyObject *)new_frame; + stack_pointer += -1 - oparg; + break; + } + + case _INIT_CALL_PY_EXACT_ARGS_3: { + PyObject **args; + PyObject *self_or_null; + PyObject *callable; + _PyInterpreterFrame *new_frame; + oparg = 3; + assert(oparg == CURRENT_OPARG()); + args = &stack_pointer[-oparg]; + self_or_null = stack_pointer[-1 - oparg]; + callable = stack_pointer[-2 - oparg]; + int argcount = oparg; + if (self_or_null != NULL) { + args--; + argcount++; + } + STAT_INC(CALL, hit); + PyFunctionObject *func = (PyFunctionObject *)callable; + new_frame = _PyFrame_PushUnchecked(tstate, func, argcount); + for (int i = 0; i < argcount; i++) { + new_frame->localsplus[i] = args[i]; + } + stack_pointer[-2 - oparg] = (PyObject *)new_frame; + stack_pointer += -1 - oparg; + break; + } + + case _INIT_CALL_PY_EXACT_ARGS_4: { + PyObject **args; + PyObject *self_or_null; + PyObject *callable; + _PyInterpreterFrame *new_frame; + oparg = 4; + assert(oparg == CURRENT_OPARG()); + args = &stack_pointer[-oparg]; + self_or_null = stack_pointer[-1 - oparg]; + callable = stack_pointer[-2 - oparg]; + int argcount = oparg; + if (self_or_null != NULL) { + args--; + argcount++; + } + STAT_INC(CALL, hit); + PyFunctionObject *func = (PyFunctionObject *)callable; + new_frame = _PyFrame_PushUnchecked(tstate, func, argcount); + for (int i = 0; i < argcount; i++) { + new_frame->localsplus[i] = args[i]; + } + stack_pointer[-2 - oparg] = (PyObject *)new_frame; + stack_pointer += -1 - oparg; + break; + } + case _INIT_CALL_PY_EXACT_ARGS: { PyObject **args; PyObject *self_or_null; @@ -2660,7 +3022,6 @@ goto exit_unwind; } #endif - stack_pointer += ((0) ? 1 : 0); break; } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index a520d042a4aa1e4..324e53dca63a8a0 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -1005,7 +1005,6 @@ } #endif } - stack_pointer += ((0) ? 1 : 0); DISPATCH(); } @@ -1755,7 +1754,6 @@ } #endif } - stack_pointer += ((0) ? 1 : 0); DISPATCH(); } @@ -3597,8 +3595,8 @@ self = owner; } stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; - stack_pointer += ((1) ? 1 : 0); + stack_pointer[0] = self; + stack_pointer += 1; DISPATCH(); } @@ -3632,8 +3630,8 @@ self = owner; } stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; - stack_pointer += ((1) ? 1 : 0); + stack_pointer[0] = self; + stack_pointer += 1; DISPATCH(); } @@ -3679,8 +3677,8 @@ self = owner; } stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; - stack_pointer += ((1) ? 1 : 0); + stack_pointer[0] = self; + stack_pointer += 1; DISPATCH(); } @@ -3751,7 +3749,6 @@ attr = Py_NewRef(descr); } stack_pointer[-1] = attr; - stack_pointer += ((0) ? 1 : 0); DISPATCH(); } @@ -3794,7 +3791,6 @@ attr = Py_NewRef(descr); } stack_pointer[-1] = attr; - stack_pointer += ((0) ? 1 : 0); DISPATCH(); } @@ -4380,7 +4376,7 @@ Py_DECREF(self); if (attr == NULL) goto pop_3_error; stack_pointer[-3] = attr; - stack_pointer += -2 + ((0) ? 1 : 0); + stack_pointer += -2; DISPATCH(); } diff --git a/Python/optimizer.c b/Python/optimizer.c index acc1d545d2b8adf..df8f0ed234b59d2 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -963,6 +963,21 @@ uop_optimize( } } assert(err == 1); + /* Fix up */ + for (int pc = 0; pc < UOP_MAX_TRACE_LENGTH; pc++) { + int opcode = buffer[pc].opcode; + int oparg = buffer[pc].oparg; + if (_PyUop_Flags[opcode] & HAS_OPARG_AND_1_FLAG) { + buffer[pc].opcode = opcode + 1 + (oparg & 1); + } + else if (oparg < _PyUop_Replication[opcode]) { + buffer[pc].opcode = opcode + oparg + 1; + } + else if (opcode == _JUMP_TO_TOP || opcode == _EXIT_TRACE) { + break; + } + assert(_PyOpcode_uop_name[buffer[pc].opcode]); + } _PyExecutorObject *executor = make_executor_from_uops(buffer, &dependencies); if (executor == NULL) { return -1; diff --git a/Python/tier2_redundancy_eliminator_cases.c.h b/Python/tier2_redundancy_eliminator_cases.c.h index 98f0bdca01f01d5..904700a0bbe6479 100644 --- a/Python/tier2_redundancy_eliminator_cases.c.h +++ b/Python/tier2_redundancy_eliminator_cases.c.h @@ -834,7 +834,7 @@ attr = sym_new_unknown(ctx); if (attr == NULL) goto out_of_space; stack_pointer[-3] = attr; - stack_pointer += -2 + ((0) ? 1 : 0); + stack_pointer += -2; break; } @@ -1264,8 +1264,8 @@ self = sym_new_unknown(ctx); if (self == NULL) goto out_of_space; stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; - stack_pointer += ((1) ? 1 : 0); + stack_pointer[0] = self; + stack_pointer += 1; break; } @@ -1277,8 +1277,8 @@ self = sym_new_unknown(ctx); if (self == NULL) goto out_of_space; stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; - stack_pointer += ((1) ? 1 : 0); + stack_pointer[0] = self; + stack_pointer += 1; break; } @@ -1287,7 +1287,6 @@ attr = sym_new_unknown(ctx); if (attr == NULL) goto out_of_space; stack_pointer[-1] = attr; - stack_pointer += ((0) ? 1 : 0); break; } @@ -1296,7 +1295,6 @@ attr = sym_new_unknown(ctx); if (attr == NULL) goto out_of_space; stack_pointer[-1] = attr; - stack_pointer += ((0) ? 1 : 0); break; } @@ -1312,8 +1310,8 @@ self = sym_new_unknown(ctx); if (self == NULL) goto out_of_space; stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; - stack_pointer += ((1) ? 1 : 0); + stack_pointer[0] = self; + stack_pointer += 1; break; } @@ -1409,7 +1407,6 @@ ctx->frame = new_frame; ctx->curr_frame_depth++; stack_pointer = new_frame->stack_pointer; - stack_pointer += ((0) ? 1 : 0); break; } diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index bcffd75269ac0bf..49b8b426444d248 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -1,6 +1,7 @@ from dataclasses import dataclass, field import lexer import parser +import re from typing import Optional @@ -22,9 +23,10 @@ class Properties: uses_locals: bool has_free: bool side_exit: bool - pure: bool passthrough: bool + oparg_and_1: bool = False + const_oparg: int = -1 def dump(self, indent: str) -> None: print(indent, end="") @@ -141,6 +143,8 @@ class Uop: properties: Properties _size: int = -1 implicitly_created: bool = False + replicated = 0 + replicates : "Uop | None" = None def dump(self, indent: str) -> None: print( @@ -271,15 +275,19 @@ def override_error( ) -def convert_stack_item(item: parser.StackEffect) -> StackItem: - return StackItem(item.name, item.type, item.cond, (item.size or "1")) - +def convert_stack_item(item: parser.StackEffect, replace_op_arg_1: str | None) -> StackItem: + cond = item.cond + if replace_op_arg_1 and OPARG_AND_1.match(item.cond): + cond = replace_op_arg_1 + return StackItem( + item.name, item.type, cond, (item.size or "1") + ) -def analyze_stack(op: parser.InstDef) -> StackEffect: +def analyze_stack(op: parser.InstDef, replace_op_arg_1: str | None = None) -> StackEffect: inputs: list[StackItem] = [ - convert_stack_item(i) for i in op.inputs if isinstance(i, parser.StackEffect) + convert_stack_item(i, replace_op_arg_1) for i in op.inputs if isinstance(i, parser.StackEffect) ] - outputs: list[StackItem] = [convert_stack_item(i) for i in op.outputs] + outputs: list[StackItem] = [convert_stack_item(i, replace_op_arg_1) for i in op.outputs] for input, output in zip(inputs, outputs): if input.name == output.name: input.peek = output.peek = True @@ -442,6 +450,22 @@ def stack_effect_only_peeks(instr: parser.InstDef) -> bool: for s, other in zip(stack_inputs, instr.outputs) ) +OPARG_AND_1 = re.compile("\\(*oparg *& *1") + +def effect_depends_on_oparg_1(op: parser.InstDef) -> bool: + for effect in op.inputs: + if isinstance(effect, parser.CacheEffect): + continue + if not effect.cond: + continue + if OPARG_AND_1.match(effect.cond): + return True + for effect in op.outputs: + if not effect.cond: + continue + if OPARG_AND_1.match(effect.cond): + return True + return False def compute_properties(op: parser.InstDef) -> Properties: has_free = ( @@ -485,8 +509,8 @@ def compute_properties(op: parser.InstDef) -> Properties: ) -def make_uop(name: str, op: parser.InstDef, inputs: list[parser.InputEffect]) -> Uop: - return Uop( +def make_uop(name: str, op: parser.InstDef, inputs: list[parser.InputEffect], uops: dict[str, Uop]) -> Uop: + result = Uop( name=name, context=op.context, annotations=op.annotations, @@ -495,6 +519,49 @@ def make_uop(name: str, op: parser.InstDef, inputs: list[parser.InputEffect]) -> body=op.block.tokens, properties=compute_properties(op), ) + if effect_depends_on_oparg_1(op) and "split" in op.annotations: + result.properties.oparg_and_1 = True + for bit in ("0", "1"): + name_x = name + "_" + bit + properties = compute_properties(op) + if properties.oparg: + # May not need oparg anymore + properties.oparg = any(token.text == "oparg" for token in op.block.tokens) + rep = Uop( + name=name_x, + context=op.context, + annotations=op.annotations, + stack=analyze_stack(op, bit), + caches=analyze_caches(inputs), + body=op.block.tokens, + properties=properties, + ) + rep.replicates = result + uops[name_x] = rep + for anno in op.annotations: + if anno.startswith("replicate"): + result.replicated = int(anno[10:-1]) + break + else: + return result + for oparg in range(result.replicated): + name_x = name + "_" + str(oparg) + properties = compute_properties(op) + properties.oparg = False + properties.const_oparg = oparg + rep = Uop( + name=name_x, + context=op.context, + annotations=op.annotations, + stack=analyze_stack(op), + caches=analyze_caches(inputs), + body=op.block.tokens, + properties=properties, + ) + rep.replicates = result + uops[name_x] = rep + + return result def add_op(op: parser.InstDef, uops: dict[str, Uop]) -> None: @@ -504,7 +571,7 @@ def add_op(op: parser.InstDef, uops: dict[str, Uop]) -> None: raise override_error( op.name, op.context, uops[op.name].context, op.tokens[0] ) - uops[op.name] = make_uop(op.name, op, op.inputs) + uops[op.name] = make_uop(op.name, op, op.inputs, uops) def add_instruction( @@ -531,7 +598,7 @@ def desugar_inst( uop_index = len(parts) # Place holder for the uop. parts.append(Skip(0)) - uop = make_uop("_" + inst.name, inst, op_inputs) + uop = make_uop("_" + inst.name, inst, op_inputs, uops) uop.implicitly_created = True uops[inst.name] = uop if uop_index < 0: diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 54ffea71b5350b8..0b4b99c60768b5c 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -119,7 +119,10 @@ def replace_decrefs( out.emit(f"Py_DECREF({var.name}[_i]);\n") out.emit("}\n") elif var.condition: - out.emit(f"Py_XDECREF({var.name});\n") + if var.condition == "1": + out.emit(f"Py_DECREF({var.name});\n") + elif var.condition != "0": + out.emit(f"Py_XDECREF({var.name});\n") else: out.emit(f"Py_DECREF({var.name});\n") @@ -216,6 +219,8 @@ def cflags(p: Properties) -> str: flags.append("HAS_PURE_FLAG") if p.passthrough: flags.append("HAS_PASSTHROUGH_FLAG") + if p.oparg_and_1: + flags.append("HAS_OPARG_AND_1_FLAG") if flags: return " | ".join(flags) else: diff --git a/Tools/cases_generator/lexer.py b/Tools/cases_generator/lexer.py index 4f8d01c5492f51b..0077921e7d7fa12 100644 --- a/Tools/cases_generator/lexer.py +++ b/Tools/cases_generator/lexer.py @@ -222,6 +222,8 @@ def choice(*opts: str) -> str: "register", "replaced", "pure", + "split", + "replicate", } __all__ = [] diff --git a/Tools/cases_generator/opcode_metadata_generator.py b/Tools/cases_generator/opcode_metadata_generator.py index 52dc09e1499671d..24fbea6cfcb77ac 100644 --- a/Tools/cases_generator/opcode_metadata_generator.py +++ b/Tools/cases_generator/opcode_metadata_generator.py @@ -53,6 +53,7 @@ "EXIT", "PURE", "PASSTHROUGH", + "OPARG_AND_1", ] diff --git a/Tools/cases_generator/parsing.py b/Tools/cases_generator/parsing.py index a8961f28babea1b..0d54820e4e71fbe 100644 --- a/Tools/cases_generator/parsing.py +++ b/Tools/cases_generator/parsing.py @@ -179,7 +179,13 @@ def inst_header(self) -> InstHeader | None: # | annotation* op(NAME, (inputs -- outputs)) annotations = [] while anno := self.expect(lx.ANNOTATION): - annotations.append(anno.text) + if anno.text == "replicate": + self.require(lx.LPAREN) + times = self.require(lx.NUMBER) + self.require(lx.RPAREN) + annotations.append(f"replicate({times.text})") + else: + annotations.append(anno.text) tkn = self.expect(lx.INST) if not tkn: tkn = self.expect(lx.OP) diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index 97a301142d59c7e..5aecac39aef5e2d 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -23,8 +23,12 @@ def maybe_parenthesize(sym: str) -> str: def var_size(var: StackItem) -> str: if var.condition: - # Special case simplification - if var.condition == "oparg & 1" and var.size == "1": + # Special case simplifications + if var.condition == "0": + return "0" + elif var.condition == "1": + return var.size + elif var.condition == "oparg & 1" and var.size == "1": return f"({var.condition})" else: return f"(({var.condition}) ? {var.size} : 0)" @@ -154,7 +158,12 @@ def pop(self, var: StackItem) -> str: f"{var.name} = {cast}{indirect}stack_pointer[{self.base_offset.to_c()}];" ) if var.condition: - return f"if ({var.condition}) {{ {assign} }}\n" + if var.condition == "1": + return f"{assign}\n" + elif var.condition == "0": + return "" + else: + return f"if ({var.condition}) {{ {assign} }}\n" return f"{assign}\n" def push(self, var: StackItem) -> str: @@ -175,7 +184,10 @@ def flush(self, out: CWriter, cast_type: str = "PyObject *") -> None: cast = f"({cast_type})" if var.type else "" if var.name not in UNUSED and not var.is_array(): if var.condition: - out.emit(f"if ({var.condition}) ") + if var.condition == "0": + continue + elif var.condition != "1": + out.emit(f"if ({var.condition}) ") out.emit( f"stack_pointer[{self.base_offset.to_c()}] = {cast}{var.name};\n" ) diff --git a/Tools/cases_generator/tier2_abstract_generator.py b/Tools/cases_generator/tier2_abstract_generator.py index cc29b1660d26edf..47a862643d987e1 100644 --- a/Tools/cases_generator/tier2_abstract_generator.py +++ b/Tools/cases_generator/tier2_abstract_generator.py @@ -178,6 +178,8 @@ def generate_abstract_interpreter( validate_uop(override, uop) if uop.properties.tier_one_only: continue + if uop.replicates: + continue if uop.is_super(): continue if not uop.is_viable(): diff --git a/Tools/cases_generator/tier2_generator.py b/Tools/cases_generator/tier2_generator.py index 8b4d1646dd5a87f..6fbe5c355c90831 100644 --- a/Tools/cases_generator/tier2_generator.py +++ b/Tools/cases_generator/tier2_generator.py @@ -33,24 +33,29 @@ DEFAULT_OUTPUT = ROOT / "Python/executor_cases.c.h" +def declare_variable( + var: StackItem, uop: Uop, variables: set[str], out: CWriter +) -> None: + if var.name in variables: + return + type = var.type if var.type else "PyObject *" + variables.add(var.name) + if var.condition: + out.emit(f"{type}{var.name} = NULL;\n") + if uop.replicates: + # Replicas may not use all their conditional variables + # So avoid a compiler warning with a fake use + out.emit(f"(void){var.name};\n") + else: + out.emit(f"{type}{var.name};\n") + + def declare_variables(uop: Uop, out: CWriter) -> None: variables = {"unused"} for var in reversed(uop.stack.inputs): - if var.name not in variables: - type = var.type if var.type else "PyObject *" - variables.add(var.name) - if var.condition: - out.emit(f"{type}{var.name} = NULL;\n") - else: - out.emit(f"{type}{var.name};\n") + declare_variable(var, uop, variables, out) for var in uop.stack.outputs: - if var.name not in variables: - variables.add(var.name) - type = var.type if var.type else "PyObject *" - if var.condition: - out.emit(f"{type}{var.name} = NULL;\n") - else: - out.emit(f"{type}{var.name};\n") + declare_variable(var, uop, variables, out) def tier2_replace_error( @@ -113,9 +118,31 @@ def tier2_replace_exit_if( out.emit(") goto side_exit;\n") +def tier2_replace_oparg( + out: CWriter, + tkn: Token, + tkn_iter: Iterator[Token], + uop: Uop, + unused: Stack, + inst: Instruction | None, +) -> None: + if not uop.name.endswith("_0") and not uop.name.endswith("_1"): + out.emit(tkn) + return + amp = next(tkn_iter) + if amp.text != "&": + out.emit(tkn) + out.emit(amp) + return + one = next(tkn_iter) + assert one.text == "1" + out.emit_at(uop.name[-1], tkn) + + TIER2_REPLACEMENT_FUNCTIONS = REPLACEMENT_FUNCTIONS.copy() TIER2_REPLACEMENT_FUNCTIONS["ERROR_IF"] = tier2_replace_error TIER2_REPLACEMENT_FUNCTIONS["DEOPT_IF"] = tier2_replace_deopt +TIER2_REPLACEMENT_FUNCTIONS["oparg"] = tier2_replace_oparg TIER2_REPLACEMENT_FUNCTIONS["EXIT_IF"] = tier2_replace_exit_if @@ -124,6 +151,10 @@ def write_uop(uop: Uop, out: CWriter, stack: Stack) -> None: out.start_line() if uop.properties.oparg: out.emit("oparg = CURRENT_OPARG();\n") + assert uop.properties.const_oparg < 0 + elif uop.properties.const_oparg >= 0: + out.emit(f"oparg = {uop.properties.const_oparg};\n") + out.emit(f"assert(oparg == CURRENT_OPARG());\n") for var in reversed(uop.stack.inputs): out.emit(stack.pop(var)) if not uop.properties.stores_sp: @@ -165,6 +196,9 @@ def generate_tier2( for name, uop in analysis.uops.items(): if uop.properties.tier_one_only: continue + if uop.properties.oparg_and_1: + out.emit(f"/* {uop.name} is split on (oparg & 1) */\n\n") + continue if uop.is_super(): continue if not uop.is_viable(): diff --git a/Tools/cases_generator/uop_id_generator.py b/Tools/cases_generator/uop_id_generator.py index 633249f1c6b1feb..907158f27959597 100644 --- a/Tools/cases_generator/uop_id_generator.py +++ b/Tools/cases_generator/uop_id_generator.py @@ -38,15 +38,17 @@ def generate_uop_ids( next_id += 1 PRE_DEFINED = {"_EXIT_TRACE", "_SET_IP"} - for uop in analysis.uops.values(): - if uop.name in PRE_DEFINED: + uops = [(uop.name, uop) for uop in analysis.uops.values()] + # Sort so that _BASE comes immediately before _BASE_0, etc. + for name, uop in sorted(uops): + if name in PRE_DEFINED: continue if uop.properties.tier_one_only: continue - if uop.implicitly_created and not distinct_namespace: - out.emit(f"#define {uop.name} {uop.name[1:]}\n") + if uop.implicitly_created and not distinct_namespace and not uop.replicated: + out.emit(f"#define {name} {name[1:]}\n") else: - out.emit(f"#define {uop.name} {next_id}\n") + out.emit(f"#define {name} {next_id}\n") next_id += 1 out.emit(f"#define MAX_UOP_ID {next_id-1}\n") diff --git a/Tools/cases_generator/uop_metadata_generator.py b/Tools/cases_generator/uop_metadata_generator.py index 9083ecc48bdf5b8..f85f1c6ce9c817a 100644 --- a/Tools/cases_generator/uop_metadata_generator.py +++ b/Tools/cases_generator/uop_metadata_generator.py @@ -24,6 +24,7 @@ def generate_names_and_flags(analysis: Analysis, out: CWriter) -> None: out.emit("extern const uint16_t _PyUop_Flags[MAX_UOP_ID+1];\n") + out.emit("extern const uint8_t _PyUop_Replication[MAX_UOP_ID+1];\n") out.emit("extern const char * const _PyOpcode_uop_name[MAX_UOP_ID+1];\n\n") out.emit("#ifdef NEED_OPCODE_METADATA\n") out.emit("const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {\n") @@ -31,6 +32,12 @@ def generate_names_and_flags(analysis: Analysis, out: CWriter) -> None: if uop.is_viable() and not uop.properties.tier_one_only: out.emit(f"[{uop.name}] = {cflags(uop.properties)},\n") + out.emit("};\n\n") + out.emit("const uint8_t _PyUop_Replication[MAX_UOP_ID+1] = {\n") + for uop in analysis.uops.values(): + if uop.replicated: + out.emit(f"[{uop.name}] = {uop.replicated},\n") + out.emit("};\n\n") out.emit("const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = {\n") for uop in sorted(analysis.uops.values(), key=lambda t: t.name): From a2bb8ad14409c7ecb8dea437b0e281eb1f65b5d8 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Tue, 20 Feb 2024 14:10:47 +0300 Subject: [PATCH 368/507] gh-115700: Add target `_RegenCases` in Windows build for cases regeneration. (GH-115708) --- ...-02-20-12-46-20.gh-issue-115700.KLJ5r4.rst | 1 + PCbuild/regen.targets | 32 +++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-20-12-46-20.gh-issue-115700.KLJ5r4.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-20-12-46-20.gh-issue-115700.KLJ5r4.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-20-12-46-20.gh-issue-115700.KLJ5r4.rst new file mode 100644 index 000000000000000..5b7b8e410b50632 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-20-12-46-20.gh-issue-115700.KLJ5r4.rst @@ -0,0 +1 @@ +The regen-cases build stage now works on Windows. diff --git a/PCbuild/regen.targets b/PCbuild/regen.targets index a90620d6ca8b7d1..8f31803dbb752a6 100644 --- a/PCbuild/regen.targets +++ b/PCbuild/regen.targets @@ -31,11 +31,13 @@ <!-- Taken from _Target._compute_digest in Tools\jit\_targets.py: --> <_JITSources Include="$(PySourcePath)Python\executor_cases.c.h;$(GeneratedPyConfigDir)pyconfig.h;$(PySourcePath)Tools\jit\**"/> <_JITOutputs Include="$(GeneratedPyConfigDir)jit_stencils.h"/> + <_CasesSources Include="$(PySourcePath)Python\bytecodes.c;$(PySourcePath)Python\tier2_redundancy_eliminator_bytecodes.c;"/> + <_CasesOutputs Include="$(PySourcePath)Python\generated_cases.c.h;$(PySourcePath)Include\opcode_ids.h;$(PySourcePath)Include\internal\pycore_uop_ids.h;$(PySourcePath)Python\opcode_targets.h;$(PySourcePath)Include\internal\pycore_opcode_metadata.h;$(PySourcePath)Include\internal\pycore_uop_metadata.h;$(PySourcePath)Python\tier2_redundancy_eliminator_cases.c.h;$(PySourcePath)Lib\_opcode_metadata.py"/> </ItemGroup> <Target Name="_TouchRegenSources" Condition="$(ForceRegen) == 'true'"> <Message Text="Touching source files to force regeneration" Importance="high" /> - <Touch Files="@(_PegenSources);@(_ASTSources);@(_TokenSources);@(_KeywordOutputs)" + <Touch Files="@(_PegenSources);@(_ASTSources);@(_TokenSources);@(_KeywordOutputs);@(_CasesSources)" AlwaysCreate="False" /> </Target> @@ -79,7 +81,31 @@ <Exec Command="$(PythonForBuild) Tools\build\generate_global_objects.py" WorkingDirectory="$(PySourcePath)" /> </Target> - + + <Target Name="_RegenCases" + Inputs="@(_CasesSources)" Outputs="@(_CasesOutputs)" + DependsOnTargets="FindPythonForBuild"> + <Message Text="Regenerate cases" Importance="high" /> + <Exec Command="$(PythonForBuild) $(PySourcePath)Tools\cases_generator\opcode_id_generator.py $(PySourcePath)Python\bytecodes.c" + WorkingDirectory="$(PySourcePath)" /> + <Exec Command="$(PythonForBuild) $(PySourcePath)Tools\cases_generator\target_generator.py $(PySourcePath)Python\bytecodes.c" + WorkingDirectory="$(PySourcePath)" /> + <Exec Command="$(PythonForBuild) $(PySourcePath)Tools\cases_generator\uop_id_generator.py $(PySourcePath)Python\bytecodes.c" + WorkingDirectory="$(PySourcePath)" /> + <Exec Command="$(PythonForBuild) $(PySourcePath)Tools\cases_generator\py_metadata_generator.py $(PySourcePath)Python\bytecodes.c" + WorkingDirectory="$(PySourcePath)" /> + <Exec Command="$(PythonForBuild) $(PySourcePath)Tools\cases_generator\tier1_generator.py $(PySourcePath)Python\bytecodes.c" + WorkingDirectory="$(PySourcePath)" /> + <Exec Command="$(PythonForBuild) $(PySourcePath)Tools\cases_generator\tier2_generator.py $(PySourcePath)Python\bytecodes.c" + WorkingDirectory="$(PySourcePath)" /> + <Exec Command="$(PythonForBuild) $(PySourcePath)Tools\cases_generator\tier2_abstract_generator.py $(PySourcePath)Python\tier2_redundancy_eliminator_bytecodes.c $(PySourcePath)Python\bytecodes.c" + WorkingDirectory="$(PySourcePath)" /> + <Exec Command="$(PythonForBuild) $(PySourcePath)Tools\cases_generator\opcode_metadata_generator.py $(PySourcePath)Python\bytecodes.c" + WorkingDirectory="$(PySourcePath)" /> + <Exec Command="$(PythonForBuild) $(PySourcePath)Tools\cases_generator\uop_metadata_generator.py $(PySourcePath)Python\bytecodes.c" + WorkingDirectory="$(PySourcePath)" /> + </Target> + <Target Name="_RegenJIT" Condition="'$(UseJIT)' == 'true'" DependsOnTargets="_UpdatePyconfig;FindPythonForBuild" @@ -100,7 +126,7 @@ DependsOnTargets="_TouchRegenSources;_RegenPegen;_RegenAST_H;_RegenTokens;_RegenKeywords;_RegenGlobalObjects"> </Target> - <Target Name="Regen" DependsOnTargets="_RegenNoPGUpdate;_RegenJIT"> + <Target Name="Regen" DependsOnTargets="_RegenNoPGUpdate;_RegenJIT;_RegenCases"> <Message Text="Generated sources are up to date" Importance="high" /> </Target> From dcba21f905ef170b2cd0a6433b6fe6bcb4316a67 Mon Sep 17 00:00:00 2001 From: Ken Jin <kenjin@python.org> Date: Tue, 20 Feb 2024 19:30:49 +0800 Subject: [PATCH 369/507] gh-115687: Split up guards from COMPARE_OP (GH-115688) --- Include/internal/pycore_opcode_metadata.h | 12 +- Include/internal/pycore_uop_ids.h | 194 +++++++++--------- Include/internal/pycore_uop_metadata.h | 4 +- Lib/test/test_capi/test_opt.py | 59 +++++- Python/bytecodes.c | 21 +- Python/executor_cases.c.h | 6 - Python/generated_cases.c.h | 98 +++++---- .../tier2_redundancy_eliminator_bytecodes.c | 8 + Python/tier2_redundancy_eliminator_cases.c.h | 10 + 9 files changed, 249 insertions(+), 163 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index f45e5f1901b0afb..ab34366ab1066c4 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -999,9 +999,9 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[268] = { [CHECK_EXC_MATCH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [CLEANUP_THROW] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [COMPARE_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [COMPARE_OP_FLOAT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [COMPARE_OP_INT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [COMPARE_OP_STR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [COMPARE_OP_FLOAT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, + [COMPARE_OP_INT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, + [COMPARE_OP_STR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, [CONTAINS_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [CONVERT_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, [COPY] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_PURE_FLAG }, @@ -1221,9 +1221,9 @@ _PyOpcode_macro_expansion[256] = { [CHECK_EG_MATCH] = { .nuops = 1, .uops = { { _CHECK_EG_MATCH, 0, 0 } } }, [CHECK_EXC_MATCH] = { .nuops = 1, .uops = { { _CHECK_EXC_MATCH, 0, 0 } } }, [COMPARE_OP] = { .nuops = 1, .uops = { { _COMPARE_OP, 0, 0 } } }, - [COMPARE_OP_FLOAT] = { .nuops = 1, .uops = { { _COMPARE_OP_FLOAT, 0, 0 } } }, - [COMPARE_OP_INT] = { .nuops = 1, .uops = { { _COMPARE_OP_INT, 0, 0 } } }, - [COMPARE_OP_STR] = { .nuops = 1, .uops = { { _COMPARE_OP_STR, 0, 0 } } }, + [COMPARE_OP_FLOAT] = { .nuops = 2, .uops = { { _GUARD_BOTH_FLOAT, 0, 0 }, { _COMPARE_OP_FLOAT, 0, 0 } } }, + [COMPARE_OP_INT] = { .nuops = 2, .uops = { { _GUARD_BOTH_INT, 0, 0 }, { _COMPARE_OP_INT, 0, 0 } } }, + [COMPARE_OP_STR] = { .nuops = 2, .uops = { { _GUARD_BOTH_UNICODE, 0, 0 }, { _COMPARE_OP_STR, 0, 0 } } }, [CONTAINS_OP] = { .nuops = 1, .uops = { { _CONTAINS_OP, 0, 0 } } }, [CONVERT_VALUE] = { .nuops = 1, .uops = { { _CONVERT_VALUE, 0, 0 } } }, [COPY] = { .nuops = 1, .uops = { { _COPY, 0, 0 } } }, diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index e098852d941f183..3c133d97b2f03eb 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -72,9 +72,9 @@ extern "C" { #define _CHECK_VALIDITY_AND_SET_IP 324 #define _COLD_EXIT 325 #define _COMPARE_OP 326 -#define _COMPARE_OP_FLOAT COMPARE_OP_FLOAT -#define _COMPARE_OP_INT COMPARE_OP_INT -#define _COMPARE_OP_STR COMPARE_OP_STR +#define _COMPARE_OP_FLOAT 327 +#define _COMPARE_OP_INT 328 +#define _COMPARE_OP_STR 329 #define _CONTAINS_OP CONTAINS_OP #define _CONVERT_VALUE CONVERT_VALUE #define _COPY COPY @@ -89,41 +89,41 @@ extern "C" { #define _DICT_UPDATE DICT_UPDATE #define _END_SEND END_SEND #define _EXIT_INIT_CHECK EXIT_INIT_CHECK -#define _FATAL_ERROR 327 +#define _FATAL_ERROR 330 #define _FORMAT_SIMPLE FORMAT_SIMPLE #define _FORMAT_WITH_SPEC FORMAT_WITH_SPEC -#define _FOR_ITER 328 +#define _FOR_ITER 331 #define _FOR_ITER_GEN FOR_ITER_GEN -#define _FOR_ITER_TIER_TWO 329 +#define _FOR_ITER_TIER_TWO 332 #define _GET_AITER GET_AITER #define _GET_ANEXT GET_ANEXT #define _GET_AWAITABLE GET_AWAITABLE #define _GET_ITER GET_ITER #define _GET_LEN GET_LEN #define _GET_YIELD_FROM_ITER GET_YIELD_FROM_ITER -#define _GUARD_BOTH_FLOAT 330 -#define _GUARD_BOTH_INT 331 -#define _GUARD_BOTH_UNICODE 332 -#define _GUARD_BUILTINS_VERSION 333 -#define _GUARD_DORV_VALUES 334 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT 335 -#define _GUARD_GLOBALS_VERSION 336 -#define _GUARD_IS_FALSE_POP 337 -#define _GUARD_IS_NONE_POP 338 -#define _GUARD_IS_NOT_NONE_POP 339 -#define _GUARD_IS_TRUE_POP 340 -#define _GUARD_KEYS_VERSION 341 -#define _GUARD_NOT_EXHAUSTED_LIST 342 -#define _GUARD_NOT_EXHAUSTED_RANGE 343 -#define _GUARD_NOT_EXHAUSTED_TUPLE 344 -#define _GUARD_TYPE_VERSION 345 -#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS 346 -#define _INIT_CALL_PY_EXACT_ARGS 347 -#define _INIT_CALL_PY_EXACT_ARGS_0 348 -#define _INIT_CALL_PY_EXACT_ARGS_1 349 -#define _INIT_CALL_PY_EXACT_ARGS_2 350 -#define _INIT_CALL_PY_EXACT_ARGS_3 351 -#define _INIT_CALL_PY_EXACT_ARGS_4 352 +#define _GUARD_BOTH_FLOAT 333 +#define _GUARD_BOTH_INT 334 +#define _GUARD_BOTH_UNICODE 335 +#define _GUARD_BUILTINS_VERSION 336 +#define _GUARD_DORV_VALUES 337 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT 338 +#define _GUARD_GLOBALS_VERSION 339 +#define _GUARD_IS_FALSE_POP 340 +#define _GUARD_IS_NONE_POP 341 +#define _GUARD_IS_NOT_NONE_POP 342 +#define _GUARD_IS_TRUE_POP 343 +#define _GUARD_KEYS_VERSION 344 +#define _GUARD_NOT_EXHAUSTED_LIST 345 +#define _GUARD_NOT_EXHAUSTED_RANGE 346 +#define _GUARD_NOT_EXHAUSTED_TUPLE 347 +#define _GUARD_TYPE_VERSION 348 +#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS 349 +#define _INIT_CALL_PY_EXACT_ARGS 350 +#define _INIT_CALL_PY_EXACT_ARGS_0 351 +#define _INIT_CALL_PY_EXACT_ARGS_1 352 +#define _INIT_CALL_PY_EXACT_ARGS_2 353 +#define _INIT_CALL_PY_EXACT_ARGS_3 354 +#define _INIT_CALL_PY_EXACT_ARGS_4 355 #define _INSTRUMENTED_CALL INSTRUMENTED_CALL #define _INSTRUMENTED_CALL_FUNCTION_EX INSTRUMENTED_CALL_FUNCTION_EX #define _INSTRUMENTED_CALL_KW INSTRUMENTED_CALL_KW @@ -140,65 +140,65 @@ extern "C" { #define _INSTRUMENTED_RETURN_CONST INSTRUMENTED_RETURN_CONST #define _INSTRUMENTED_RETURN_VALUE INSTRUMENTED_RETURN_VALUE #define _INSTRUMENTED_YIELD_VALUE INSTRUMENTED_YIELD_VALUE -#define _INTERNAL_INCREMENT_OPT_COUNTER 353 -#define _IS_NONE 354 +#define _INTERNAL_INCREMENT_OPT_COUNTER 356 +#define _IS_NONE 357 #define _IS_OP IS_OP -#define _ITER_CHECK_LIST 355 -#define _ITER_CHECK_RANGE 356 -#define _ITER_CHECK_TUPLE 357 -#define _ITER_JUMP_LIST 358 -#define _ITER_JUMP_RANGE 359 -#define _ITER_JUMP_TUPLE 360 -#define _ITER_NEXT_LIST 361 -#define _ITER_NEXT_RANGE 362 -#define _ITER_NEXT_TUPLE 363 -#define _JUMP_TO_TOP 364 +#define _ITER_CHECK_LIST 358 +#define _ITER_CHECK_RANGE 359 +#define _ITER_CHECK_TUPLE 360 +#define _ITER_JUMP_LIST 361 +#define _ITER_JUMP_RANGE 362 +#define _ITER_JUMP_TUPLE 363 +#define _ITER_NEXT_LIST 364 +#define _ITER_NEXT_RANGE 365 +#define _ITER_NEXT_TUPLE 366 +#define _JUMP_TO_TOP 367 #define _LIST_APPEND LIST_APPEND #define _LIST_EXTEND LIST_EXTEND #define _LOAD_ASSERTION_ERROR LOAD_ASSERTION_ERROR -#define _LOAD_ATTR 365 -#define _LOAD_ATTR_CLASS 366 -#define _LOAD_ATTR_CLASS_0 367 -#define _LOAD_ATTR_CLASS_1 368 +#define _LOAD_ATTR 368 +#define _LOAD_ATTR_CLASS 369 +#define _LOAD_ATTR_CLASS_0 370 +#define _LOAD_ATTR_CLASS_1 371 #define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN -#define _LOAD_ATTR_INSTANCE_VALUE 369 -#define _LOAD_ATTR_INSTANCE_VALUE_0 370 -#define _LOAD_ATTR_INSTANCE_VALUE_1 371 -#define _LOAD_ATTR_METHOD_LAZY_DICT 372 -#define _LOAD_ATTR_METHOD_NO_DICT 373 -#define _LOAD_ATTR_METHOD_WITH_VALUES 374 -#define _LOAD_ATTR_MODULE 375 -#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT 376 -#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 377 +#define _LOAD_ATTR_INSTANCE_VALUE 372 +#define _LOAD_ATTR_INSTANCE_VALUE_0 373 +#define _LOAD_ATTR_INSTANCE_VALUE_1 374 +#define _LOAD_ATTR_METHOD_LAZY_DICT 375 +#define _LOAD_ATTR_METHOD_NO_DICT 376 +#define _LOAD_ATTR_METHOD_WITH_VALUES 377 +#define _LOAD_ATTR_MODULE 378 +#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT 379 +#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 380 #define _LOAD_ATTR_PROPERTY LOAD_ATTR_PROPERTY -#define _LOAD_ATTR_SLOT 378 -#define _LOAD_ATTR_SLOT_0 379 -#define _LOAD_ATTR_SLOT_1 380 -#define _LOAD_ATTR_WITH_HINT 381 +#define _LOAD_ATTR_SLOT 381 +#define _LOAD_ATTR_SLOT_0 382 +#define _LOAD_ATTR_SLOT_1 383 +#define _LOAD_ATTR_WITH_HINT 384 #define _LOAD_BUILD_CLASS LOAD_BUILD_CLASS #define _LOAD_CONST LOAD_CONST -#define _LOAD_CONST_INLINE 382 -#define _LOAD_CONST_INLINE_BORROW 383 -#define _LOAD_CONST_INLINE_BORROW_WITH_NULL 384 -#define _LOAD_CONST_INLINE_WITH_NULL 385 +#define _LOAD_CONST_INLINE 385 +#define _LOAD_CONST_INLINE_BORROW 386 +#define _LOAD_CONST_INLINE_BORROW_WITH_NULL 387 +#define _LOAD_CONST_INLINE_WITH_NULL 388 #define _LOAD_DEREF LOAD_DEREF -#define _LOAD_FAST 386 -#define _LOAD_FAST_0 387 -#define _LOAD_FAST_1 388 -#define _LOAD_FAST_2 389 -#define _LOAD_FAST_3 390 -#define _LOAD_FAST_4 391 -#define _LOAD_FAST_5 392 -#define _LOAD_FAST_6 393 -#define _LOAD_FAST_7 394 +#define _LOAD_FAST 389 +#define _LOAD_FAST_0 390 +#define _LOAD_FAST_1 391 +#define _LOAD_FAST_2 392 +#define _LOAD_FAST_3 393 +#define _LOAD_FAST_4 394 +#define _LOAD_FAST_5 395 +#define _LOAD_FAST_6 396 +#define _LOAD_FAST_7 397 #define _LOAD_FAST_AND_CLEAR LOAD_FAST_AND_CLEAR #define _LOAD_FAST_CHECK LOAD_FAST_CHECK #define _LOAD_FAST_LOAD_FAST LOAD_FAST_LOAD_FAST #define _LOAD_FROM_DICT_OR_DEREF LOAD_FROM_DICT_OR_DEREF #define _LOAD_FROM_DICT_OR_GLOBALS LOAD_FROM_DICT_OR_GLOBALS -#define _LOAD_GLOBAL 395 -#define _LOAD_GLOBAL_BUILTINS 396 -#define _LOAD_GLOBAL_MODULE 397 +#define _LOAD_GLOBAL 398 +#define _LOAD_GLOBAL_BUILTINS 399 +#define _LOAD_GLOBAL_MODULE 400 #define _LOAD_LOCALS LOAD_LOCALS #define _LOAD_NAME LOAD_NAME #define _LOAD_SUPER_ATTR_ATTR LOAD_SUPER_ATTR_ATTR @@ -212,46 +212,46 @@ extern "C" { #define _MATCH_SEQUENCE MATCH_SEQUENCE #define _NOP NOP #define _POP_EXCEPT POP_EXCEPT -#define _POP_FRAME 398 -#define _POP_JUMP_IF_FALSE 399 -#define _POP_JUMP_IF_TRUE 400 +#define _POP_FRAME 401 +#define _POP_JUMP_IF_FALSE 402 +#define _POP_JUMP_IF_TRUE 403 #define _POP_TOP POP_TOP #define _PUSH_EXC_INFO PUSH_EXC_INFO -#define _PUSH_FRAME 401 +#define _PUSH_FRAME 404 #define _PUSH_NULL PUSH_NULL #define _RESUME_CHECK RESUME_CHECK -#define _SAVE_RETURN_OFFSET 402 -#define _SEND 403 +#define _SAVE_RETURN_OFFSET 405 +#define _SEND 406 #define _SEND_GEN SEND_GEN #define _SETUP_ANNOTATIONS SETUP_ANNOTATIONS #define _SET_ADD SET_ADD #define _SET_FUNCTION_ATTRIBUTE SET_FUNCTION_ATTRIBUTE #define _SET_UPDATE SET_UPDATE -#define _START_EXECUTOR 404 -#define _STORE_ATTR 405 -#define _STORE_ATTR_INSTANCE_VALUE 406 -#define _STORE_ATTR_SLOT 407 +#define _START_EXECUTOR 407 +#define _STORE_ATTR 408 +#define _STORE_ATTR_INSTANCE_VALUE 409 +#define _STORE_ATTR_SLOT 410 #define _STORE_ATTR_WITH_HINT STORE_ATTR_WITH_HINT #define _STORE_DEREF STORE_DEREF -#define _STORE_FAST 408 -#define _STORE_FAST_0 409 -#define _STORE_FAST_1 410 -#define _STORE_FAST_2 411 -#define _STORE_FAST_3 412 -#define _STORE_FAST_4 413 -#define _STORE_FAST_5 414 -#define _STORE_FAST_6 415 -#define _STORE_FAST_7 416 +#define _STORE_FAST 411 +#define _STORE_FAST_0 412 +#define _STORE_FAST_1 413 +#define _STORE_FAST_2 414 +#define _STORE_FAST_3 415 +#define _STORE_FAST_4 416 +#define _STORE_FAST_5 417 +#define _STORE_FAST_6 418 +#define _STORE_FAST_7 419 #define _STORE_FAST_LOAD_FAST STORE_FAST_LOAD_FAST #define _STORE_FAST_STORE_FAST STORE_FAST_STORE_FAST #define _STORE_GLOBAL STORE_GLOBAL #define _STORE_NAME STORE_NAME #define _STORE_SLICE STORE_SLICE -#define _STORE_SUBSCR 417 +#define _STORE_SUBSCR 420 #define _STORE_SUBSCR_DICT STORE_SUBSCR_DICT #define _STORE_SUBSCR_LIST_INT STORE_SUBSCR_LIST_INT #define _SWAP SWAP -#define _TO_BOOL 418 +#define _TO_BOOL 421 #define _TO_BOOL_ALWAYS_TRUE TO_BOOL_ALWAYS_TRUE #define _TO_BOOL_BOOL TO_BOOL_BOOL #define _TO_BOOL_INT TO_BOOL_INT @@ -262,12 +262,12 @@ extern "C" { #define _UNARY_NEGATIVE UNARY_NEGATIVE #define _UNARY_NOT UNARY_NOT #define _UNPACK_EX UNPACK_EX -#define _UNPACK_SEQUENCE 419 +#define _UNPACK_SEQUENCE 422 #define _UNPACK_SEQUENCE_LIST UNPACK_SEQUENCE_LIST #define _UNPACK_SEQUENCE_TUPLE UNPACK_SEQUENCE_TUPLE #define _UNPACK_SEQUENCE_TWO_TUPLE UNPACK_SEQUENCE_TWO_TUPLE #define _WITH_EXCEPT_START WITH_EXCEPT_START -#define MAX_UOP_ID 419 +#define MAX_UOP_ID 422 #ifdef __cplusplus } diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index c9def0ecdc15016..35340fe9ee1b634 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -149,9 +149,9 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_STORE_ATTR_INSTANCE_VALUE] = HAS_ESCAPES_FLAG, [_STORE_ATTR_SLOT] = HAS_ESCAPES_FLAG, [_COMPARE_OP] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_COMPARE_OP_FLOAT] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, + [_COMPARE_OP_FLOAT] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, [_COMPARE_OP_INT] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, - [_COMPARE_OP_STR] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, + [_COMPARE_OP_STR] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, [_IS_OP] = HAS_ARG_FLAG, [_CONTAINS_OP] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_CHECK_EG_MATCH] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 9d19b6c3ad3befb..3ba38c77710b2b3 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -574,7 +574,7 @@ def _run_with_optimizer(self, testfunc, arg): def test_int_type_propagation(self): def testfunc(loops): num = 0 - while num < loops: + for i in range(loops): x = num + num a = x + 1 num += 1 @@ -593,7 +593,7 @@ def double(x): return x + x def testfunc(loops): num = 0 - while num < loops: + for i in range(loops): x = num + num a = double(x) num += 1 @@ -617,7 +617,7 @@ def double(x): return x + x def testfunc(loops): num = 0 - while num < loops: + for i in range(loops): a = double(num) x = a + a num += 1 @@ -821,6 +821,59 @@ def testfunc(n): # We'll also need to verify that propagation actually occurs. self.assertIn("_BINARY_OP_MULTIPLY_FLOAT", uops) + def test_compare_op_type_propagation_float(self): + def testfunc(n): + a = 1.0 + for _ in range(n): + x = a == a + x = a == a + x = a == a + x = a == a + return x + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertTrue(res) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + guard_both_float_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_FLOAT"] + self.assertLessEqual(len(guard_both_float_count), 1) + self.assertIn("_COMPARE_OP_FLOAT", uops) + + def test_compare_op_type_propagation_int(self): + def testfunc(n): + a = 1 + for _ in range(n): + x = a == a + x = a == a + x = a == a + x = a == a + return x + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertTrue(res) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + guard_both_float_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_INT"] + self.assertLessEqual(len(guard_both_float_count), 1) + self.assertIn("_COMPARE_OP_INT", uops) + + def test_compare_op_type_propagation_unicode(self): + def testfunc(n): + a = "" + for _ in range(n): + x = a == a + x = a == a + x = a == a + x = a == a + return x + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertTrue(res) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + guard_both_float_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_UNICODE"] + self.assertLessEqual(len(guard_both_float_count), 1) + self.assertIn("_COMPARE_OP_STR", uops) if __name__ == "__main__": unittest.main() diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 27c439b71fa9d91..10bb1525fc18019 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2200,9 +2200,16 @@ dummy_func( macro(COMPARE_OP) = _SPECIALIZE_COMPARE_OP + _COMPARE_OP; - inst(COMPARE_OP_FLOAT, (unused/1, left, right -- res)) { - DEOPT_IF(!PyFloat_CheckExact(left)); - DEOPT_IF(!PyFloat_CheckExact(right)); + macro(COMPARE_OP_FLOAT) = + _GUARD_BOTH_FLOAT + unused/1 + _COMPARE_OP_FLOAT; + + macro(COMPARE_OP_INT) = + _GUARD_BOTH_INT + unused/1 + _COMPARE_OP_INT; + + macro(COMPARE_OP_STR) = + _GUARD_BOTH_UNICODE + unused/1 + _COMPARE_OP_STR; + + op(_COMPARE_OP_FLOAT, (left, right -- res)) { STAT_INC(COMPARE_OP, hit); double dleft = PyFloat_AS_DOUBLE(left); double dright = PyFloat_AS_DOUBLE(right); @@ -2215,9 +2222,7 @@ dummy_func( } // Similar to COMPARE_OP_FLOAT - inst(COMPARE_OP_INT, (unused/1, left, right -- res)) { - DEOPT_IF(!PyLong_CheckExact(left)); - DEOPT_IF(!PyLong_CheckExact(right)); + op(_COMPARE_OP_INT, (left, right -- res)) { DEOPT_IF(!_PyLong_IsCompact((PyLongObject *)left)); DEOPT_IF(!_PyLong_IsCompact((PyLongObject *)right)); STAT_INC(COMPARE_OP, hit); @@ -2234,9 +2239,7 @@ dummy_func( } // Similar to COMPARE_OP_FLOAT, but for ==, != only - inst(COMPARE_OP_STR, (unused/1, left, right -- res)) { - DEOPT_IF(!PyUnicode_CheckExact(left)); - DEOPT_IF(!PyUnicode_CheckExact(right)); + op(_COMPARE_OP_STR, (left, right -- res)) { STAT_INC(COMPARE_OP, hit); int eq = _PyUnicode_Equal(left, right); assert((oparg >> 5) == Py_EQ || (oparg >> 5) == Py_NE); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index b46885e0d5cbc70..445f98b469e9783 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -2100,8 +2100,6 @@ oparg = CURRENT_OPARG(); right = stack_pointer[-1]; left = stack_pointer[-2]; - if (!PyFloat_CheckExact(left)) goto deoptimize; - if (!PyFloat_CheckExact(right)) goto deoptimize; STAT_INC(COMPARE_OP, hit); double dleft = PyFloat_AS_DOUBLE(left); double dright = PyFloat_AS_DOUBLE(right); @@ -2123,8 +2121,6 @@ oparg = CURRENT_OPARG(); right = stack_pointer[-1]; left = stack_pointer[-2]; - if (!PyLong_CheckExact(left)) goto deoptimize; - if (!PyLong_CheckExact(right)) goto deoptimize; if (!_PyLong_IsCompact((PyLongObject *)left)) goto deoptimize; if (!_PyLong_IsCompact((PyLongObject *)right)) goto deoptimize; STAT_INC(COMPARE_OP, hit); @@ -2150,8 +2146,6 @@ oparg = CURRENT_OPARG(); right = stack_pointer[-1]; left = stack_pointer[-2]; - if (!PyUnicode_CheckExact(left)) goto deoptimize; - if (!PyUnicode_CheckExact(right)) goto deoptimize; STAT_INC(COMPARE_OP, hit); int eq = _PyUnicode_Equal(left, right); assert((oparg >> 5) == Py_EQ || (oparg >> 5) == Py_NE); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 324e53dca63a8a0..78991066974df33 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -2026,20 +2026,26 @@ PyObject *right; PyObject *left; PyObject *res; - /* Skip 1 cache entry */ + // _GUARD_BOTH_FLOAT right = stack_pointer[-1]; left = stack_pointer[-2]; - DEOPT_IF(!PyFloat_CheckExact(left), COMPARE_OP); - DEOPT_IF(!PyFloat_CheckExact(right), COMPARE_OP); - STAT_INC(COMPARE_OP, hit); - double dleft = PyFloat_AS_DOUBLE(left); - double dright = PyFloat_AS_DOUBLE(right); - // 1 if NaN, 2 if <, 4 if >, 8 if ==; this matches low four bits of the oparg - int sign_ish = COMPARISON_BIT(dleft, dright); - _Py_DECREF_SPECIALIZED(left, _PyFloat_ExactDealloc); - _Py_DECREF_SPECIALIZED(right, _PyFloat_ExactDealloc); - res = (sign_ish & oparg) ? Py_True : Py_False; - // It's always a bool, so we don't care about oparg & 16. + { + DEOPT_IF(!PyFloat_CheckExact(left), COMPARE_OP); + DEOPT_IF(!PyFloat_CheckExact(right), COMPARE_OP); + } + /* Skip 1 cache entry */ + // _COMPARE_OP_FLOAT + { + STAT_INC(COMPARE_OP, hit); + double dleft = PyFloat_AS_DOUBLE(left); + double dright = PyFloat_AS_DOUBLE(right); + // 1 if NaN, 2 if <, 4 if >, 8 if ==; this matches low four bits of the oparg + int sign_ish = COMPARISON_BIT(dleft, dright); + _Py_DECREF_SPECIALIZED(left, _PyFloat_ExactDealloc); + _Py_DECREF_SPECIALIZED(right, _PyFloat_ExactDealloc); + res = (sign_ish & oparg) ? Py_True : Py_False; + // It's always a bool, so we don't care about oparg & 16. + } stack_pointer[-2] = res; stack_pointer += -1; DISPATCH(); @@ -2053,24 +2059,30 @@ PyObject *right; PyObject *left; PyObject *res; - /* Skip 1 cache entry */ + // _GUARD_BOTH_INT right = stack_pointer[-1]; left = stack_pointer[-2]; - DEOPT_IF(!PyLong_CheckExact(left), COMPARE_OP); - DEOPT_IF(!PyLong_CheckExact(right), COMPARE_OP); - DEOPT_IF(!_PyLong_IsCompact((PyLongObject *)left), COMPARE_OP); - DEOPT_IF(!_PyLong_IsCompact((PyLongObject *)right), COMPARE_OP); - STAT_INC(COMPARE_OP, hit); - assert(_PyLong_DigitCount((PyLongObject *)left) <= 1 && + { + DEOPT_IF(!PyLong_CheckExact(left), COMPARE_OP); + DEOPT_IF(!PyLong_CheckExact(right), COMPARE_OP); + } + /* Skip 1 cache entry */ + // _COMPARE_OP_INT + { + DEOPT_IF(!_PyLong_IsCompact((PyLongObject *)left), COMPARE_OP); + DEOPT_IF(!_PyLong_IsCompact((PyLongObject *)right), COMPARE_OP); + STAT_INC(COMPARE_OP, hit); + assert(_PyLong_DigitCount((PyLongObject *)left) <= 1 && _PyLong_DigitCount((PyLongObject *)right) <= 1); - Py_ssize_t ileft = _PyLong_CompactValue((PyLongObject *)left); - Py_ssize_t iright = _PyLong_CompactValue((PyLongObject *)right); - // 2 if <, 4 if >, 8 if ==; this matches the low 4 bits of the oparg - int sign_ish = COMPARISON_BIT(ileft, iright); - _Py_DECREF_SPECIALIZED(left, (destructor)PyObject_Free); - _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); - res = (sign_ish & oparg) ? Py_True : Py_False; - // It's always a bool, so we don't care about oparg & 16. + Py_ssize_t ileft = _PyLong_CompactValue((PyLongObject *)left); + Py_ssize_t iright = _PyLong_CompactValue((PyLongObject *)right); + // 2 if <, 4 if >, 8 if ==; this matches the low 4 bits of the oparg + int sign_ish = COMPARISON_BIT(ileft, iright); + _Py_DECREF_SPECIALIZED(left, (destructor)PyObject_Free); + _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); + res = (sign_ish & oparg) ? Py_True : Py_False; + // It's always a bool, so we don't care about oparg & 16. + } stack_pointer[-2] = res; stack_pointer += -1; DISPATCH(); @@ -2084,21 +2096,27 @@ PyObject *right; PyObject *left; PyObject *res; - /* Skip 1 cache entry */ + // _GUARD_BOTH_UNICODE right = stack_pointer[-1]; left = stack_pointer[-2]; - DEOPT_IF(!PyUnicode_CheckExact(left), COMPARE_OP); - DEOPT_IF(!PyUnicode_CheckExact(right), COMPARE_OP); - STAT_INC(COMPARE_OP, hit); - int eq = _PyUnicode_Equal(left, right); - assert((oparg >> 5) == Py_EQ || (oparg >> 5) == Py_NE); - _Py_DECREF_SPECIALIZED(left, _PyUnicode_ExactDealloc); - _Py_DECREF_SPECIALIZED(right, _PyUnicode_ExactDealloc); - assert(eq == 0 || eq == 1); - assert((oparg & 0xf) == COMPARISON_NOT_EQUALS || (oparg & 0xf) == COMPARISON_EQUALS); - assert(COMPARISON_NOT_EQUALS + 1 == COMPARISON_EQUALS); - res = ((COMPARISON_NOT_EQUALS + eq) & oparg) ? Py_True : Py_False; - // It's always a bool, so we don't care about oparg & 16. + { + DEOPT_IF(!PyUnicode_CheckExact(left), COMPARE_OP); + DEOPT_IF(!PyUnicode_CheckExact(right), COMPARE_OP); + } + /* Skip 1 cache entry */ + // _COMPARE_OP_STR + { + STAT_INC(COMPARE_OP, hit); + int eq = _PyUnicode_Equal(left, right); + assert((oparg >> 5) == Py_EQ || (oparg >> 5) == Py_NE); + _Py_DECREF_SPECIALIZED(left, _PyUnicode_ExactDealloc); + _Py_DECREF_SPECIALIZED(right, _PyUnicode_ExactDealloc); + assert(eq == 0 || eq == 1); + assert((oparg & 0xf) == COMPARISON_NOT_EQUALS || (oparg & 0xf) == COMPARISON_EQUALS); + assert(COMPARISON_NOT_EQUALS + 1 == COMPARISON_EQUALS); + res = ((COMPARISON_NOT_EQUALS + eq) & oparg) ? Py_True : Py_False; + // It's always a bool, so we don't care about oparg & 16. + } stack_pointer[-2] = res; stack_pointer += -1; DISPATCH(); diff --git a/Python/tier2_redundancy_eliminator_bytecodes.c b/Python/tier2_redundancy_eliminator_bytecodes.c index 3f6e8ce1bbfbadd..e9b556d16c37025 100644 --- a/Python/tier2_redundancy_eliminator_bytecodes.c +++ b/Python/tier2_redundancy_eliminator_bytecodes.c @@ -77,6 +77,14 @@ dummy_func(void) { sym_set_type(right, &PyFloat_Type); } + op(_GUARD_BOTH_UNICODE, (left, right -- left, right)) { + if (sym_matches_type(left, &PyUnicode_Type) && + sym_matches_type(right, &PyUnicode_Type)) { + REPLACE_OP(this_instr, _NOP, 0 ,0); + } + sym_set_type(left, &PyUnicode_Type); + sym_set_type(right, &PyUnicode_Type); + } op(_BINARY_OP_ADD_INT, (left, right -- res)) { if (is_const(left) && is_const(right)) { diff --git a/Python/tier2_redundancy_eliminator_cases.c.h b/Python/tier2_redundancy_eliminator_cases.c.h index 904700a0bbe6479..f41fe328195b4dc 100644 --- a/Python/tier2_redundancy_eliminator_cases.c.h +++ b/Python/tier2_redundancy_eliminator_cases.c.h @@ -351,6 +351,16 @@ } case _GUARD_BOTH_UNICODE: { + _Py_UOpsSymType *right; + _Py_UOpsSymType *left; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + if (sym_matches_type(left, &PyUnicode_Type) && + sym_matches_type(right, &PyUnicode_Type)) { + REPLACE_OP(this_instr, _NOP, 0 ,0); + } + sym_set_type(left, &PyUnicode_Type); + sym_set_type(right, &PyUnicode_Type); break; } From d24bed5ba0c60bbcc1662ae51071067799862fe0 Mon Sep 17 00:00:00 2001 From: Victor Stinner <vstinner@python.org> Date: Tue, 20 Feb 2024 14:35:41 +0100 Subject: [PATCH 370/507] gh-110850: PyTime_Time() return 0 on success (GH-115713) Thanks! --- Python/pytime.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/pytime.c b/Python/pytime.c index fb0ed85c541e68c..8b3c7128aae3bc3 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -1053,7 +1053,7 @@ PyTime_Time(PyTime_t *result) *result = 0; return -1; } - return 1; + return 0; } int From 1ff6c1416b0bb422f4847cd84fcb33662a2497ef Mon Sep 17 00:00:00 2001 From: Alexander Shadchin <shadchin@yandex-team.com> Date: Tue, 20 Feb 2024 17:09:46 +0300 Subject: [PATCH 371/507] Add missed `stream` argument (#111775) * Add missed `stream` argument * Add news --- Lib/importlib/resources/simple.py | 2 +- .../next/Library/2023-11-07-10-22-06.gh-issue-111775.IoVxfX.rst | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-07-10-22-06.gh-issue-111775.IoVxfX.rst diff --git a/Lib/importlib/resources/simple.py b/Lib/importlib/resources/simple.py index 7770c922c84fabe..96f117fec62c102 100644 --- a/Lib/importlib/resources/simple.py +++ b/Lib/importlib/resources/simple.py @@ -88,7 +88,7 @@ def is_dir(self): def open(self, mode='r', *args, **kwargs): stream = self.parent.reader.open_binary(self.name) if 'b' not in mode: - stream = io.TextIOWrapper(*args, **kwargs) + stream = io.TextIOWrapper(stream, *args, **kwargs) return stream def joinpath(self, name): diff --git a/Misc/NEWS.d/next/Library/2023-11-07-10-22-06.gh-issue-111775.IoVxfX.rst b/Misc/NEWS.d/next/Library/2023-11-07-10-22-06.gh-issue-111775.IoVxfX.rst new file mode 100644 index 000000000000000..2a3bdd640ea67de --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-07-10-22-06.gh-issue-111775.IoVxfX.rst @@ -0,0 +1,2 @@ +Fix :meth:`importlib.resources.simple.ResourceHandle.open` for text mode, +added missed ``stream`` argument. From e00960a74d66f95b7803f8e5546267a078fd065c Mon Sep 17 00:00:00 2001 From: Victor Stinner <vstinner@python.org> Date: Tue, 20 Feb 2024 15:53:40 +0100 Subject: [PATCH 372/507] gh-110850: Enhance PyTime C API tests (#115715) --- Modules/_testcapi/time.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Modules/_testcapi/time.c b/Modules/_testcapi/time.c index 57eb9135d300296..68f082bf3f3d88a 100644 --- a/Modules/_testcapi/time.c +++ b/Modules/_testcapi/time.c @@ -49,9 +49,11 @@ static PyObject* test_pytime_monotonic(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) { PyTime_t t; - if (PyTime_Monotonic(&t) < 0) { + int res = PyTime_Monotonic(&t); + if (res < 0) { return NULL; } + assert(res == 0); return pytime_as_float(t); } @@ -60,9 +62,11 @@ static PyObject* test_pytime_perf_counter(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) { PyTime_t t; - if (PyTime_PerfCounter(&t) < 0) { + int res = PyTime_PerfCounter(&t); + if (res < 0) { return NULL; } + assert(res == 0); return pytime_as_float(t); } @@ -71,10 +75,11 @@ static PyObject* test_pytime_time(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) { PyTime_t t; - if (PyTime_Time(&t) < 0) { - printf("ERR! %d\n", (int)t); + int res = PyTime_Time(&t); + if (res < 0) { return NULL; } + assert(res == 0); return pytime_as_float(t); } From e71468ba4f5fb2da0cefe9e923b01811cb53fb5f Mon Sep 17 00:00:00 2001 From: talcs <talh8787@gmail.com> Date: Tue, 20 Feb 2024 16:54:33 +0200 Subject: [PATCH 373/507] gh-112020: Document the meaning of empty bytes returned by socket.recv() (GH-112055) --- Doc/library/socket.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 4bfb0d8c2cfeacd..dccf78ef8c0128f 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -1605,8 +1605,9 @@ to sockets. Receive data from the socket. The return value is a bytes object representing the data received. The maximum amount of data to be received at once is specified - by *bufsize*. See the Unix manual page :manpage:`recv(2)` for the meaning of - the optional argument *flags*; it defaults to zero. + by *bufsize*. A returned empty bytes object indicates that the client has disconnected. + See the Unix manual page :manpage:`recv(2)` for the meaning of the optional argument + *flags*; it defaults to zero. .. note:: From 0749244d13412d7cb5b53d834f586f2198f5b9a6 Mon Sep 17 00:00:00 2001 From: Brett Simmers <swtaarrs@users.noreply.github.com> Date: Tue, 20 Feb 2024 06:57:48 -0800 Subject: [PATCH 374/507] gh-112175: Add `eval_breaker` to `PyThreadState` (#115194) This change adds an `eval_breaker` field to `PyThreadState`. The primary motivation is for performance in free-threaded builds: with thread-local eval breakers, we can stop a specific thread (e.g., for an async exception) without interrupting other threads. The source of truth for the global instrumentation version is stored in the `instrumentation_version` field in PyInterpreterState. Threads usually read the version from their local `eval_breaker`, where it continues to be colocated with the eval breaker bits. --- Include/cpython/pystate.h | 5 + Include/internal/pycore_ceval.h | 50 ++-- Include/internal/pycore_ceval_state.h | 11 +- Include/internal/pycore_gc.h | 2 +- Include/internal/pycore_runtime.h | 3 + ...-02-09-18-59-22.gh-issue-112175.qglugr.rst | 1 + Modules/signalmodule.c | 11 +- Python/brc.c | 2 +- Python/bytecodes.c | 7 +- Python/ceval.c | 2 +- Python/ceval_gil.c | 243 +++++++++++------- Python/ceval_macros.h | 2 +- Python/executor_cases.c.h | 2 +- Python/gc.c | 9 +- Python/gc_free_threading.c | 9 +- Python/generated_cases.c.h | 6 +- Python/instrumentation.c | 49 +++- Python/pylifecycle.c | 1 + Python/pystate.c | 16 +- 19 files changed, 262 insertions(+), 169 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-09-18-59-22.gh-issue-112175.qglugr.rst diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index b99450a8a8d0935..ac7ff83748dbfc8 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -68,6 +68,11 @@ struct _ts { PyThreadState *next; PyInterpreterState *interp; + /* The global instrumentation version in high bits, plus flags indicating + when to break out of the interpreter loop in lower bits. See details in + pycore_ceval.h. */ + uintptr_t eval_breaker; + struct { /* Has been initialized to a safe state. diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index b158fc9ff5ebc1b..bf77526cf75cc14 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -42,7 +42,7 @@ PyAPI_FUNC(int) _PyEval_MakePendingCalls(PyThreadState *); extern void _Py_FinishPendingCalls(PyThreadState *tstate); extern void _PyEval_InitState(PyInterpreterState *); -extern void _PyEval_SignalReceived(PyInterpreterState *interp); +extern void _PyEval_SignalReceived(void); // bitwise flags: #define _Py_PENDING_MAINTHREADONLY 1 @@ -55,7 +55,6 @@ PyAPI_FUNC(int) _PyEval_AddPendingCall( void *arg, int flags); -extern void _PyEval_SignalAsyncExc(PyInterpreterState *interp); #ifdef HAVE_FORK extern PyStatus _PyEval_ReInitThreads(PyThreadState *tstate); #endif @@ -200,40 +199,43 @@ int _PyEval_UnpackIterable(PyThreadState *tstate, PyObject *v, int argcnt, int a void _PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame *frame); -#define _PY_GIL_DROP_REQUEST_BIT 0 -#define _PY_SIGNALS_PENDING_BIT 1 -#define _PY_CALLS_TO_DO_BIT 2 -#define _PY_ASYNC_EXCEPTION_BIT 3 -#define _PY_GC_SCHEDULED_BIT 4 -#define _PY_EVAL_PLEASE_STOP_BIT 5 -#define _PY_EVAL_EXPLICIT_MERGE_BIT 6 +/* Bits that can be set in PyThreadState.eval_breaker */ +#define _PY_GIL_DROP_REQUEST_BIT (1U << 0) +#define _PY_SIGNALS_PENDING_BIT (1U << 1) +#define _PY_CALLS_TO_DO_BIT (1U << 2) +#define _PY_ASYNC_EXCEPTION_BIT (1U << 3) +#define _PY_GC_SCHEDULED_BIT (1U << 4) +#define _PY_EVAL_PLEASE_STOP_BIT (1U << 5) +#define _PY_EVAL_EXPLICIT_MERGE_BIT (1U << 6) /* Reserve a few bits for future use */ #define _PY_EVAL_EVENTS_BITS 8 #define _PY_EVAL_EVENTS_MASK ((1 << _PY_EVAL_EVENTS_BITS)-1) static inline void -_Py_set_eval_breaker_bit(PyInterpreterState *interp, uint32_t bit, uint32_t set) +_Py_set_eval_breaker_bit(PyThreadState *tstate, uintptr_t bit) { - assert(set == 0 || set == 1); - uintptr_t to_set = set << bit; - uintptr_t mask = ((uintptr_t)1) << bit; - uintptr_t old = _Py_atomic_load_uintptr(&interp->ceval.eval_breaker); - if ((old & mask) == to_set) { - return; - } - uintptr_t new; - do { - new = (old & ~mask) | to_set; - } while (!_Py_atomic_compare_exchange_uintptr(&interp->ceval.eval_breaker, &old, new)); + _Py_atomic_or_uintptr(&tstate->eval_breaker, bit); +} + +static inline void +_Py_unset_eval_breaker_bit(PyThreadState *tstate, uintptr_t bit) +{ + _Py_atomic_and_uintptr(&tstate->eval_breaker, ~bit); } -static inline bool -_Py_eval_breaker_bit_is_set(PyInterpreterState *interp, int32_t bit) +static inline int +_Py_eval_breaker_bit_is_set(PyThreadState *tstate, uintptr_t bit) { - return _Py_atomic_load_uintptr_relaxed(&interp->ceval.eval_breaker) & (((uintptr_t)1) << bit); + uintptr_t b = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker); + return (b & bit) != 0; } +// Free-threaded builds use these functions to set or unset a bit on all +// threads in the given interpreter. +void _Py_set_eval_breaker_bit_all(PyInterpreterState *interp, uintptr_t bit); +void _Py_unset_eval_breaker_bit_all(PyInterpreterState *interp, uintptr_t bit); + #ifdef __cplusplus } diff --git a/Include/internal/pycore_ceval_state.h b/Include/internal/pycore_ceval_state.h index 28738980eb49be8..b453328f15649eb 100644 --- a/Include/internal/pycore_ceval_state.h +++ b/Include/internal/pycore_ceval_state.h @@ -78,13 +78,10 @@ struct _ceval_runtime_state { struct _ceval_state { - /* This single variable consolidates all requests to break out of - * the fast path in the eval loop. - * It is by far the hottest field in this struct and - * should be placed at the beginning. */ - uintptr_t eval_breaker; - /* Avoid false sharing */ - int64_t padding[7]; + /* This variable holds the global instrumentation version. When a thread is + running, this value is overlaid onto PyThreadState.eval_breaker so that + changes in the instrumentation version will trigger the eval breaker. */ + uintptr_t instrumentation_version; int recursion_limit; struct _gil_runtime_state *gil; int own_gil; diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h index a98864f74313987..40414a868518bb6 100644 --- a/Include/internal/pycore_gc.h +++ b/Include/internal/pycore_gc.h @@ -286,7 +286,7 @@ extern PyObject *_PyGC_GetReferrers(PyInterpreterState *interp, PyObject *objs); // Functions to clear types free lists extern void _PyGC_ClearAllFreeLists(PyInterpreterState *interp); -extern void _Py_ScheduleGC(PyInterpreterState *interp); +extern void _Py_ScheduleGC(PyThreadState *tstate); extern void _Py_RunGC(PyThreadState *tstate); #ifdef __cplusplus diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index 7c705d1224f915b..0c9c59e85b2fcfc 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -191,7 +191,10 @@ typedef struct pyruntimestate { int64_t next_id; } interpreters; + /* Platform-specific identifier and PyThreadState, respectively, for the + main thread in the main interpreter. */ unsigned long main_thread; + PyThreadState *main_tstate; /* ---------- IMPORTANT --------------------------- The fields above this line are declared as early as diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-09-18-59-22.gh-issue-112175.qglugr.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-09-18-59-22.gh-issue-112175.qglugr.rst new file mode 100644 index 000000000000000..6d919134bf4d9c5 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-09-18-59-22.gh-issue-112175.qglugr.rst @@ -0,0 +1 @@ +Every ``PyThreadState`` now has its own ``eval_breaker``, allowing specific threads to be interrupted. diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c index 394a997b20c06de..652e69b0d28b21e 100644 --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -276,11 +276,7 @@ trip_signal(int sig_num) cleared in PyErr_CheckSignals() before .tripped. */ _Py_atomic_store_int(&is_tripped, 1); - /* Signals are always handled by the main interpreter */ - PyInterpreterState *interp = _PyInterpreterState_Main(); - - /* Notify ceval.c */ - _PyEval_SignalReceived(interp); + _PyEval_SignalReceived(); /* And then write to the wakeup fd *after* setting all the globals and doing the _PyEval_SignalReceived. We used to write to the wakeup fd @@ -303,6 +299,7 @@ trip_signal(int sig_num) int fd = wakeup.fd; if (fd != INVALID_FD) { + PyInterpreterState *interp = _PyInterpreterState_Main(); unsigned char byte = (unsigned char)sig_num; #ifdef MS_WINDOWS if (wakeup.use_send) { @@ -1770,8 +1767,8 @@ PyErr_CheckSignals(void) Python code to ensure signals are handled. Checking for the GC here allows long running native code to clean cycles created using the C-API even if it doesn't run the evaluation loop */ - if (_Py_eval_breaker_bit_is_set(tstate->interp, _PY_GC_SCHEDULED_BIT)) { - _Py_set_eval_breaker_bit(tstate->interp, _PY_GC_SCHEDULED_BIT, 0); + if (_Py_eval_breaker_bit_is_set(tstate, _PY_GC_SCHEDULED_BIT)) { + _Py_unset_eval_breaker_bit(tstate, _PY_GC_SCHEDULED_BIT); _Py_RunGC(tstate); } diff --git a/Python/brc.c b/Python/brc.c index f1fd57a2964cf55..b73c721e71aef6b 100644 --- a/Python/brc.c +++ b/Python/brc.c @@ -94,7 +94,7 @@ _Py_brc_queue_object(PyObject *ob) } // Notify owning thread - _Py_set_eval_breaker_bit(interp, _PY_EVAL_EXPLICIT_MERGE_BIT, 1); + _Py_set_eval_breaker_bit(&tstate->base, _PY_EVAL_EXPLICIT_MERGE_BIT); PyMutex_Unlock(&bucket->mutex); } diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 10bb1525fc18019..9d790a9d3e6577d 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -8,7 +8,6 @@ #include "Python.h" #include "pycore_abstract.h" // _PyIndex_Check() -#include "pycore_ceval.h" // _PyEval_SignalAsyncExc() #include "pycore_code.h" #include "pycore_emscripten_signal.h" // _Py_CHECK_EMSCRIPTEN_SIGNALS #include "pycore_function.h" @@ -146,7 +145,7 @@ dummy_func( TIER_ONE_ONLY assert(frame == tstate->current_frame); uintptr_t global_version = - _Py_atomic_load_uintptr_relaxed(&tstate->interp->ceval.eval_breaker) & + _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & ~_PY_EVAL_EVENTS_MASK; uintptr_t code_version = _PyFrame_GetCode(frame)->_co_instrumentation_version; assert((code_version & 255) == 0); @@ -168,14 +167,14 @@ dummy_func( DEOPT_IF(_Py_emscripten_signal_clock == 0); _Py_emscripten_signal_clock -= Py_EMSCRIPTEN_SIGNAL_HANDLING; #endif - uintptr_t eval_breaker = _Py_atomic_load_uintptr_relaxed(&tstate->interp->ceval.eval_breaker); + uintptr_t eval_breaker = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker); uintptr_t version = _PyFrame_GetCode(frame)->_co_instrumentation_version; assert((version & _PY_EVAL_EVENTS_MASK) == 0); DEOPT_IF(eval_breaker != version); } inst(INSTRUMENTED_RESUME, (--)) { - uintptr_t global_version = _Py_atomic_load_uintptr_relaxed(&tstate->interp->ceval.eval_breaker) & ~_PY_EVAL_EVENTS_MASK; + uintptr_t global_version = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & ~_PY_EVAL_EVENTS_MASK; uintptr_t code_version = _PyFrame_GetCode(frame)->_co_instrumentation_version; if (code_version != global_version) { if (_Py_Instrument(_PyFrame_GetCode(frame), tstate->interp)) { diff --git a/Python/ceval.c b/Python/ceval.c index 6f647cfdd53d832..596d5f449c06fa9 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -5,7 +5,7 @@ #include "Python.h" #include "pycore_abstract.h" // _PyIndex_Check() #include "pycore_call.h" // _PyObject_CallNoArgs() -#include "pycore_ceval.h" // _PyEval_SignalAsyncExc() +#include "pycore_ceval.h" #include "pycore_code.h" #include "pycore_emscripten_signal.h" // _Py_CHECK_EMSCRIPTEN_SIGNALS #include "pycore_function.h" diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c index deb9741291fca73..f5c44307a513f83 100644 --- a/Python/ceval_gil.c +++ b/Python/ceval_gil.c @@ -56,60 +56,52 @@ #define _Py_atomic_load_relaxed_int32(ATOMIC_VAL) _Py_atomic_load_relaxed(ATOMIC_VAL) #endif -/* bpo-40010: eval_breaker should be recomputed if there - is a pending signal: signal received by another thread which cannot - handle signals. - Similarly, we set CALLS_TO_DO and ASYNC_EXCEPTION to match the thread. -*/ +// Atomically copy the bits indicated by mask between two values. static inline void -update_eval_breaker_from_thread(PyInterpreterState *interp, PyThreadState *tstate) +copy_eval_breaker_bits(uintptr_t *from, uintptr_t *to, uintptr_t mask) { - if (tstate == NULL) { + uintptr_t from_bits = _Py_atomic_load_uintptr_relaxed(from) & mask; + uintptr_t old_value = _Py_atomic_load_uintptr_relaxed(to); + uintptr_t to_bits = old_value & mask; + if (from_bits == to_bits) { return; } - if (_Py_IsMainThread()) { - int32_t calls_to_do = _Py_atomic_load_int32_relaxed( - &_PyRuntime.ceval.pending_mainthread.calls_to_do); - if (calls_to_do) { - _Py_set_eval_breaker_bit(interp, _PY_CALLS_TO_DO_BIT, 1); - } - if (_Py_ThreadCanHandleSignals(interp)) { - if (_Py_atomic_load_int(&_PyRuntime.signals.is_tripped)) { - _Py_set_eval_breaker_bit(interp, _PY_SIGNALS_PENDING_BIT, 1); - } - } - } - if (tstate->async_exc != NULL) { - _Py_set_eval_breaker_bit(interp, _PY_ASYNC_EXCEPTION_BIT, 1); - } + uintptr_t new_value; + do { + new_value = (old_value & ~mask) | from_bits; + } while (!_Py_atomic_compare_exchange_uintptr(to, &old_value, new_value)); } +// When attaching a thread, set the global instrumentation version and +// _PY_CALLS_TO_DO_BIT from the current state of the interpreter. static inline void -SET_GIL_DROP_REQUEST(PyInterpreterState *interp) +update_eval_breaker_for_thread(PyInterpreterState *interp, PyThreadState *tstate) { - _Py_set_eval_breaker_bit(interp, _PY_GIL_DROP_REQUEST_BIT, 1); -} - - -static inline void -RESET_GIL_DROP_REQUEST(PyInterpreterState *interp) -{ - _Py_set_eval_breaker_bit(interp, _PY_GIL_DROP_REQUEST_BIT, 0); -} - - -static inline void -SIGNAL_PENDING_CALLS(PyInterpreterState *interp) -{ - _Py_set_eval_breaker_bit(interp, _PY_CALLS_TO_DO_BIT, 1); -} +#ifdef Py_GIL_DISABLED + // Free-threaded builds eagerly update the eval_breaker on *all* threads as + // needed, so this function doesn't apply. + return; +#endif + int32_t calls_to_do = _Py_atomic_load_int32_relaxed( + &interp->ceval.pending.calls_to_do); + if (calls_to_do) { + _Py_set_eval_breaker_bit(tstate, _PY_CALLS_TO_DO_BIT); + } + else if (_Py_IsMainThread()) { + calls_to_do = _Py_atomic_load_int32_relaxed( + &_PyRuntime.ceval.pending_mainthread.calls_to_do); + if (calls_to_do) { + _Py_set_eval_breaker_bit(tstate, _PY_CALLS_TO_DO_BIT); + } + } -static inline void -UNSIGNAL_PENDING_CALLS(PyInterpreterState *interp) -{ - _Py_set_eval_breaker_bit(interp, _PY_CALLS_TO_DO_BIT, 0); + // _PY_CALLS_TO_DO_BIT was derived from other state above, so the only bits + // we copy from our interpreter's state are the instrumentation version. + copy_eval_breaker_bits(&interp->ceval.instrumentation_version, + &tstate->eval_breaker, + ~_PY_EVAL_EVENTS_MASK); } /* @@ -254,13 +246,14 @@ drop_gil(PyInterpreterState *interp, PyThreadState *tstate) the GIL, and that's the only time we might delete the interpreter, so checking tstate first prevents the crash. See https://github.com/python/cpython/issues/104341. */ - if (tstate != NULL && _Py_eval_breaker_bit_is_set(interp, _PY_GIL_DROP_REQUEST_BIT)) { + if (tstate != NULL && + _Py_eval_breaker_bit_is_set(tstate, _PY_GIL_DROP_REQUEST_BIT)) { MUTEX_LOCK(gil->switch_mutex); /* Not switched yet => wait */ if (((PyThreadState*)_Py_atomic_load_ptr_relaxed(&gil->last_holder)) == tstate) { assert(_PyThreadState_CheckConsistency(tstate)); - RESET_GIL_DROP_REQUEST(tstate->interp); + _Py_unset_eval_breaker_bit(tstate, _PY_GIL_DROP_REQUEST_BIT); /* NOTE: if COND_WAIT does not atomically start waiting when releasing the mutex, another thread can run through, take the GIL and drop it again, and reset the condition @@ -321,6 +314,8 @@ take_gil(PyThreadState *tstate) _Py_atomic_load_int_relaxed(&gil->locked) && gil->switch_number == saved_switchnum) { + PyThreadState *holder_tstate = + (PyThreadState*)_Py_atomic_load_ptr_relaxed(&gil->last_holder); if (_PyThreadState_MustExit(tstate)) { MUTEX_UNLOCK(gil->mutex); // gh-96387: If the loop requested a drop request in a previous @@ -330,13 +325,13 @@ take_gil(PyThreadState *tstate) // may have to request again a drop request (iterate one more // time). if (drop_requested) { - RESET_GIL_DROP_REQUEST(interp); + _Py_unset_eval_breaker_bit(holder_tstate, _PY_GIL_DROP_REQUEST_BIT); } PyThread_exit_thread(); } assert(_PyThreadState_CheckConsistency(tstate)); - SET_GIL_DROP_REQUEST(interp); + _Py_set_eval_breaker_bit(holder_tstate, _PY_GIL_DROP_REQUEST_BIT); drop_requested = 1; } } @@ -369,13 +364,15 @@ take_gil(PyThreadState *tstate) in take_gil() while the main thread called wait_for_thread_shutdown() from Py_Finalize(). */ MUTEX_UNLOCK(gil->mutex); - drop_gil(interp, tstate); + /* Passing NULL to drop_gil() indicates that this thread is about to + terminate and will never hold the GIL again. */ + drop_gil(interp, NULL); PyThread_exit_thread(); } assert(_PyThreadState_CheckConsistency(tstate)); - RESET_GIL_DROP_REQUEST(interp); - update_eval_breaker_from_thread(interp, tstate); + _Py_unset_eval_breaker_bit(tstate, _PY_GIL_DROP_REQUEST_BIT); + update_eval_breaker_for_thread(interp, tstate); MUTEX_UNLOCK(gil->mutex); @@ -590,15 +587,6 @@ _PyEval_ReInitThreads(PyThreadState *tstate) } #endif -/* This function is used to signal that async exceptions are waiting to be - raised. */ - -void -_PyEval_SignalAsyncExc(PyInterpreterState *interp) -{ - _Py_set_eval_breaker_bit(interp, _PY_ASYNC_EXCEPTION_BIT, 1); -} - PyThreadState * PyEval_SaveThread(void) { @@ -646,11 +634,9 @@ PyEval_RestoreThread(PyThreadState *tstate) */ void -_PyEval_SignalReceived(PyInterpreterState *interp) +_PyEval_SignalReceived(void) { - if (_Py_ThreadCanHandleSignals(interp)) { - _Py_set_eval_breaker_bit(interp, _PY_SIGNALS_PENDING_BIT, 1); - } + _Py_set_eval_breaker_bit(_PyRuntime.main_tstate, _PY_SIGNALS_PENDING_BIT); } /* Push one item onto the queue while holding the lock. */ @@ -702,6 +688,26 @@ _pop_pending_call(struct _pending_calls *pending, } } +#ifndef Py_GIL_DISABLED +static void +signal_active_thread(PyInterpreterState *interp, uintptr_t bit) +{ + struct _gil_runtime_state *gil = interp->ceval.gil; + + // If a thread from the targeted interpreter is holding the GIL, signal + // that thread. Otherwise, the next thread to run from the targeted + // interpreter will have its bit set as part of taking the GIL. + MUTEX_LOCK(gil->mutex); + if (_Py_atomic_load_int_relaxed(&gil->locked)) { + PyThreadState *holder = (PyThreadState*)_Py_atomic_load_ptr_relaxed(&gil->last_holder); + if (holder->interp == interp) { + _Py_set_eval_breaker_bit(holder, bit); + } + } + MUTEX_UNLOCK(gil->mutex); +} +#endif + /* This implementation is thread-safe. It allows scheduling to be made from any thread, and even from an executing callback. @@ -711,10 +717,9 @@ int _PyEval_AddPendingCall(PyInterpreterState *interp, _Py_pending_call_func func, void *arg, int flags) { - assert(!(flags & _Py_PENDING_MAINTHREADONLY) - || _Py_IsMainInterpreter(interp)); struct _pending_calls *pending = &interp->ceval.pending; - if (flags & _Py_PENDING_MAINTHREADONLY) { + int main_only = (flags & _Py_PENDING_MAINTHREADONLY) != 0; + if (main_only) { /* The main thread only exists in the main interpreter. */ assert(_Py_IsMainInterpreter(interp)); pending = &_PyRuntime.ceval.pending_mainthread; @@ -724,8 +729,17 @@ _PyEval_AddPendingCall(PyInterpreterState *interp, int result = _push_pending_call(pending, func, arg, flags); PyMutex_Unlock(&pending->mutex); - /* signal main loop */ - SIGNAL_PENDING_CALLS(interp); + if (main_only) { + _Py_set_eval_breaker_bit(_PyRuntime.main_tstate, _PY_CALLS_TO_DO_BIT); + } + else { +#ifdef Py_GIL_DISABLED + _Py_set_eval_breaker_bit_all(interp, _PY_CALLS_TO_DO_BIT); +#else + signal_active_thread(interp, _PY_CALLS_TO_DO_BIT); +#endif + } + return result; } @@ -742,13 +756,13 @@ static int handle_signals(PyThreadState *tstate) { assert(_PyThreadState_CheckConsistency(tstate)); - _Py_set_eval_breaker_bit(tstate->interp, _PY_SIGNALS_PENDING_BIT, 0); + _Py_unset_eval_breaker_bit(tstate, _PY_SIGNALS_PENDING_BIT); if (!_Py_ThreadCanHandleSignals(tstate->interp)) { return 0; } if (_PyErr_CheckSignalsTstate(tstate) < 0) { /* On failure, re-schedule a call to handle_signals(). */ - _Py_set_eval_breaker_bit(tstate->interp, _PY_SIGNALS_PENDING_BIT, 1); + _Py_set_eval_breaker_bit(tstate, _PY_SIGNALS_PENDING_BIT); return -1; } return 0; @@ -783,9 +797,30 @@ _make_pending_calls(struct _pending_calls *pending) return 0; } +static void +signal_pending_calls(PyThreadState *tstate, PyInterpreterState *interp) +{ +#ifdef Py_GIL_DISABLED + _Py_set_eval_breaker_bit_all(interp, _PY_CALLS_TO_DO_BIT); +#else + _Py_set_eval_breaker_bit(tstate, _PY_CALLS_TO_DO_BIT); +#endif +} + +static void +unsignal_pending_calls(PyThreadState *tstate, PyInterpreterState *interp) +{ +#ifdef Py_GIL_DISABLED + _Py_unset_eval_breaker_bit_all(interp, _PY_CALLS_TO_DO_BIT); +#else + _Py_unset_eval_breaker_bit(tstate, _PY_CALLS_TO_DO_BIT); +#endif +} + static int -make_pending_calls(PyInterpreterState *interp) +make_pending_calls(PyThreadState *tstate) { + PyInterpreterState *interp = tstate->interp; struct _pending_calls *pending = &interp->ceval.pending; struct _pending_calls *pending_main = &_PyRuntime.ceval.pending_mainthread; @@ -811,12 +846,12 @@ make_pending_calls(PyInterpreterState *interp) /* unsignal before starting to call callbacks, so that any callback added in-between re-signals */ - UNSIGNAL_PENDING_CALLS(interp); + unsignal_pending_calls(tstate, interp); if (_make_pending_calls(pending) != 0) { pending->busy = 0; /* There might not be more calls to make, but we play it safe. */ - SIGNAL_PENDING_CALLS(interp); + signal_pending_calls(tstate, interp); return -1; } @@ -824,7 +859,7 @@ make_pending_calls(PyInterpreterState *interp) if (_make_pending_calls(pending_main) != 0) { pending->busy = 0; /* There might not be more calls to make, but we play it safe. */ - SIGNAL_PENDING_CALLS(interp); + signal_pending_calls(tstate, interp); return -1; } } @@ -833,13 +868,37 @@ make_pending_calls(PyInterpreterState *interp) return 0; } +void +_Py_set_eval_breaker_bit_all(PyInterpreterState *interp, uintptr_t bit) +{ + _PyRuntimeState *runtime = &_PyRuntime; + + HEAD_LOCK(runtime); + for (PyThreadState *tstate = interp->threads.head; tstate != NULL; tstate = tstate->next) { + _Py_set_eval_breaker_bit(tstate, bit); + } + HEAD_UNLOCK(runtime); +} + +void +_Py_unset_eval_breaker_bit_all(PyInterpreterState *interp, uintptr_t bit) +{ + _PyRuntimeState *runtime = &_PyRuntime; + + HEAD_LOCK(runtime); + for (PyThreadState *tstate = interp->threads.head; tstate != NULL; tstate = tstate->next) { + _Py_unset_eval_breaker_bit(tstate, bit); + } + HEAD_UNLOCK(runtime); +} + void _Py_FinishPendingCalls(PyThreadState *tstate) { assert(PyGILState_Check()); assert(_PyThreadState_CheckConsistency(tstate)); - if (make_pending_calls(tstate->interp) < 0) { + if (make_pending_calls(tstate) < 0) { PyObject *exc = _PyErr_GetRaisedException(tstate); PyErr_BadInternalCall(); _PyErr_ChainExceptions1(exc); @@ -862,7 +921,7 @@ _PyEval_MakePendingCalls(PyThreadState *tstate) } } - res = make_pending_calls(tstate->interp); + res = make_pending_calls(tstate); if (res != 0) { return res; } @@ -955,11 +1014,11 @@ _PyEval_InitState(PyInterpreterState *interp) int _Py_HandlePending(PyThreadState *tstate) { - PyInterpreterState *interp = tstate->interp; + uintptr_t breaker = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker); /* Stop-the-world */ - if (_Py_eval_breaker_bit_is_set(interp, _PY_EVAL_PLEASE_STOP_BIT)) { - _Py_set_eval_breaker_bit(interp, _PY_EVAL_PLEASE_STOP_BIT, 0); + if ((breaker & _PY_EVAL_PLEASE_STOP_BIT) != 0) { + _Py_unset_eval_breaker_bit(tstate, _PY_EVAL_PLEASE_STOP_BIT); _PyThreadState_Suspend(tstate); /* The attach blocks until the stop-the-world event is complete. */ @@ -967,35 +1026,35 @@ _Py_HandlePending(PyThreadState *tstate) } /* Pending signals */ - if (_Py_eval_breaker_bit_is_set(interp, _PY_SIGNALS_PENDING_BIT)) { + if ((breaker & _PY_SIGNALS_PENDING_BIT) != 0) { if (handle_signals(tstate) != 0) { return -1; } } /* Pending calls */ - if (_Py_eval_breaker_bit_is_set(interp, _PY_CALLS_TO_DO_BIT)) { - if (make_pending_calls(interp) != 0) { + if ((breaker & _PY_CALLS_TO_DO_BIT) != 0) { + if (make_pending_calls(tstate) != 0) { return -1; } } #ifdef Py_GIL_DISABLED /* Objects with refcounts to merge */ - if (_Py_eval_breaker_bit_is_set(interp, _PY_EVAL_EXPLICIT_MERGE_BIT)) { - _Py_set_eval_breaker_bit(interp, _PY_EVAL_EXPLICIT_MERGE_BIT, 0); + if ((breaker & _PY_EVAL_EXPLICIT_MERGE_BIT) != 0) { + _Py_unset_eval_breaker_bit(tstate, _PY_EVAL_EXPLICIT_MERGE_BIT); _Py_brc_merge_refcounts(tstate); } #endif /* GC scheduled to run */ - if (_Py_eval_breaker_bit_is_set(interp, _PY_GC_SCHEDULED_BIT)) { - _Py_set_eval_breaker_bit(interp, _PY_GC_SCHEDULED_BIT, 0); + if ((breaker & _PY_GC_SCHEDULED_BIT) != 0) { + _Py_unset_eval_breaker_bit(tstate, _PY_GC_SCHEDULED_BIT); _Py_RunGC(tstate); } /* GIL drop request */ - if (_Py_eval_breaker_bit_is_set(interp, _PY_GIL_DROP_REQUEST_BIT)) { + if ((breaker & _PY_GIL_DROP_REQUEST_BIT) != 0) { /* Give another thread a chance */ _PyThreadState_Detach(tstate); @@ -1005,11 +1064,10 @@ _Py_HandlePending(PyThreadState *tstate) } /* Check for asynchronous exception. */ - if (_Py_eval_breaker_bit_is_set(interp, _PY_ASYNC_EXCEPTION_BIT)) { - _Py_set_eval_breaker_bit(interp, _PY_ASYNC_EXCEPTION_BIT, 0); - if (tstate->async_exc != NULL) { - PyObject *exc = tstate->async_exc; - tstate->async_exc = NULL; + if ((breaker & _PY_ASYNC_EXCEPTION_BIT) != 0) { + _Py_unset_eval_breaker_bit(tstate, _PY_ASYNC_EXCEPTION_BIT); + PyObject *exc = _Py_atomic_exchange_ptr(&tstate->async_exc, NULL); + if (exc != NULL) { _PyErr_SetNone(tstate, exc); Py_DECREF(exc); return -1; @@ -1017,4 +1075,3 @@ _Py_HandlePending(PyThreadState *tstate) } return 0; } - diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h index f796b60335612cb..01a9b32229d8a52 100644 --- a/Python/ceval_macros.h +++ b/Python/ceval_macros.h @@ -124,7 +124,7 @@ #define CHECK_EVAL_BREAKER() \ _Py_CHECK_EMSCRIPTEN_SIGNALS_PERIODICALLY(); \ QSBR_QUIESCENT_STATE(tstate); \ - if (_Py_atomic_load_uintptr_relaxed(&tstate->interp->ceval.eval_breaker) & _PY_EVAL_EVENTS_MASK) { \ + if (_Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & _PY_EVAL_EVENTS_MASK) { \ if (_Py_HandlePending(tstate) != 0) { \ GOTO_ERROR(error); \ } \ diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 445f98b469e9783..2ca54b6fe9cd380 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -17,7 +17,7 @@ if (_Py_emscripten_signal_clock == 0) goto deoptimize; _Py_emscripten_signal_clock -= Py_EMSCRIPTEN_SIGNAL_HANDLING; #endif - uintptr_t eval_breaker = _Py_atomic_load_uintptr_relaxed(&tstate->interp->ceval.eval_breaker); + uintptr_t eval_breaker = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker); uintptr_t version = _PyFrame_GetCode(frame)->_co_instrumentation_version; assert((version & _PY_EVAL_EVENTS_MASK) == 0); if (eval_breaker != version) goto deoptimize; diff --git a/Python/gc.c b/Python/gc.c index c6831f4c74bcac8..8c2def1017bf2e3 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -1771,9 +1771,12 @@ PyObject_IS_GC(PyObject *obj) } void -_Py_ScheduleGC(PyInterpreterState *interp) +_Py_ScheduleGC(PyThreadState *tstate) { - _Py_set_eval_breaker_bit(interp, _PY_GC_SCHEDULED_BIT, 1); + if (!_Py_eval_breaker_bit_is_set(tstate, _PY_GC_SCHEDULED_BIT)) + { + _Py_set_eval_breaker_bit(tstate, _PY_GC_SCHEDULED_BIT); + } } void @@ -1794,7 +1797,7 @@ _PyObject_GC_Link(PyObject *op) !_Py_atomic_load_int_relaxed(&gcstate->collecting) && !_PyErr_Occurred(tstate)) { - _Py_ScheduleGC(tstate->interp); + _Py_ScheduleGC(tstate); } } diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index a758c99285a5390..2993ef4ac7818bc 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -981,7 +981,7 @@ record_allocation(PyThreadState *tstate) if (gc_should_collect(gcstate) && !_Py_atomic_load_int_relaxed(&gcstate->collecting)) { - _Py_ScheduleGC(tstate->interp); + _Py_ScheduleGC(tstate); } } } @@ -1564,9 +1564,12 @@ PyObject_IS_GC(PyObject *obj) } void -_Py_ScheduleGC(PyInterpreterState *interp) +_Py_ScheduleGC(PyThreadState *tstate) { - _Py_set_eval_breaker_bit(interp, _PY_GC_SCHEDULED_BIT, 1); + if (!_Py_eval_breaker_bit_is_set(tstate, _PY_GC_SCHEDULED_BIT)) + { + _Py_set_eval_breaker_bit(tstate, _PY_GC_SCHEDULED_BIT); + } } void diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 78991066974df33..01e67acdc5c0e5d 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -3139,7 +3139,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(INSTRUMENTED_RESUME); - uintptr_t global_version = _Py_atomic_load_uintptr_relaxed(&tstate->interp->ceval.eval_breaker) & ~_PY_EVAL_EVENTS_MASK; + uintptr_t global_version = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & ~_PY_EVAL_EVENTS_MASK; uintptr_t code_version = _PyFrame_GetCode(frame)->_co_instrumentation_version; if (code_version != global_version) { if (_Py_Instrument(_PyFrame_GetCode(frame), tstate->interp)) { @@ -4809,7 +4809,7 @@ TIER_ONE_ONLY assert(frame == tstate->current_frame); uintptr_t global_version = - _Py_atomic_load_uintptr_relaxed(&tstate->interp->ceval.eval_breaker) & + _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & ~_PY_EVAL_EVENTS_MASK; uintptr_t code_version = _PyFrame_GetCode(frame)->_co_instrumentation_version; assert((code_version & 255) == 0); @@ -4836,7 +4836,7 @@ DEOPT_IF(_Py_emscripten_signal_clock == 0, RESUME); _Py_emscripten_signal_clock -= Py_EMSCRIPTEN_SIGNAL_HANDLING; #endif - uintptr_t eval_breaker = _Py_atomic_load_uintptr_relaxed(&tstate->interp->ceval.eval_breaker); + uintptr_t eval_breaker = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker); uintptr_t version = _PyFrame_GetCode(frame)->_co_instrumentation_version; assert((version & _PY_EVAL_EVENTS_MASK) == 0); DEOPT_IF(eval_breaker != version, RESUME); diff --git a/Python/instrumentation.c b/Python/instrumentation.c index 533aece210202bb..878d19f0552bf59 100644 --- a/Python/instrumentation.c +++ b/Python/instrumentation.c @@ -891,18 +891,43 @@ static inline int most_significant_bit(uint8_t bits) { static uint32_t global_version(PyInterpreterState *interp) { - return interp->ceval.eval_breaker & ~_PY_EVAL_EVENTS_MASK; + return (uint32_t)_Py_atomic_load_uintptr_relaxed( + &interp->ceval.instrumentation_version); } +/* Atomically set the given version in the given location, without touching + anything in _PY_EVAL_EVENTS_MASK. */ static void -set_global_version(PyInterpreterState *interp, uint32_t version) +set_version_raw(uintptr_t *ptr, uint32_t version) { - assert((version & _PY_EVAL_EVENTS_MASK) == 0); - uintptr_t old = _Py_atomic_load_uintptr(&interp->ceval.eval_breaker); - intptr_t new; + uintptr_t old = _Py_atomic_load_uintptr_relaxed(ptr); + uintptr_t new; do { new = (old & _PY_EVAL_EVENTS_MASK) | version; - } while (!_Py_atomic_compare_exchange_uintptr(&interp->ceval.eval_breaker, &old, new)); + } while (!_Py_atomic_compare_exchange_uintptr(ptr, &old, new)); +} + +static void +set_global_version(PyThreadState *tstate, uint32_t version) +{ + assert((version & _PY_EVAL_EVENTS_MASK) == 0); + PyInterpreterState *interp = tstate->interp; + set_version_raw(&interp->ceval.instrumentation_version, version); + +#ifdef Py_GIL_DISABLED + // Set the version on all threads in free-threaded builds. + _PyRuntimeState *runtime = &_PyRuntime; + HEAD_LOCK(runtime); + for (tstate = interp->threads.head; tstate; + tstate = PyThreadState_Next(tstate)) { + set_version_raw(&tstate->eval_breaker, version); + }; + HEAD_UNLOCK(runtime); +#else + // Normal builds take the current version from instrumentation_version when + // attaching a thread, so we only have to set the current thread's version. + set_version_raw(&tstate->eval_breaker, version); +#endif } static bool @@ -1566,7 +1591,7 @@ _Py_Instrument(PyCodeObject *code, PyInterpreterState *interp) { if (is_version_up_to_date(code, interp)) { assert( - (interp->ceval.eval_breaker & ~_PY_EVAL_EVENTS_MASK) == 0 || + interp->ceval.instrumentation_version == 0 || instrumentation_cross_checks(interp, code) ); return 0; @@ -1778,7 +1803,8 @@ int _PyMonitoring_SetEvents(int tool_id, _PyMonitoringEventSet events) { assert(0 <= tool_id && tool_id < PY_MONITORING_TOOL_IDS); - PyInterpreterState *interp = _PyInterpreterState_GET(); + PyThreadState *tstate = _PyThreadState_GET(); + PyInterpreterState *interp = tstate->interp; assert(events < (1 << _PY_MONITORING_UNGROUPED_EVENTS)); if (check_tool(interp, tool_id)) { return -1; @@ -1793,7 +1819,7 @@ _PyMonitoring_SetEvents(int tool_id, _PyMonitoringEventSet events) PyErr_Format(PyExc_OverflowError, "events set too many times"); return -1; } - set_global_version(interp, new_version); + set_global_version(tstate, new_version); _Py_Executors_InvalidateAll(interp); return instrument_all_executing_code_objects(interp); } @@ -2122,7 +2148,8 @@ monitoring_restart_events_impl(PyObject *module) * last restart version > instrumented version for all code objects * last restart version < current version */ - PyInterpreterState *interp = _PyInterpreterState_GET(); + PyThreadState *tstate = _PyThreadState_GET(); + PyInterpreterState *interp = tstate->interp; uint32_t restart_version = global_version(interp) + MONITORING_VERSION_INCREMENT; uint32_t new_version = restart_version + MONITORING_VERSION_INCREMENT; if (new_version <= MONITORING_VERSION_INCREMENT) { @@ -2130,7 +2157,7 @@ monitoring_restart_events_impl(PyObject *module) return NULL; } interp->last_restart_version = restart_version; - set_global_version(interp, new_version); + set_global_version(tstate, new_version); if (instrument_all_executing_code_objects(interp)) { return NULL; } diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 7b537af8c87697a..656d82136d263b9 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -663,6 +663,7 @@ pycore_create_interpreter(_PyRuntimeState *runtime, if (tstate == NULL) { return _PyStatus_ERR("can't make first thread"); } + runtime->main_tstate = tstate; _PyThreadState_Bind(tstate); init_interp_create_gil(tstate, config.gil); diff --git a/Python/pystate.c b/Python/pystate.c index 3484beab7aaed49..4cd975a84d16455 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -794,9 +794,7 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate) Py_CLEAR(interp->audit_hooks); - // At this time, all the threads should be cleared so we don't need - // atomic operations for eval_breaker - interp->ceval.eval_breaker = 0; + interp->ceval.instrumentation_version = 0; for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) { interp->monitors.tools[i] = 0; @@ -1318,6 +1316,8 @@ init_threadstate(_PyThreadStateImpl *_tstate, assert(interp != NULL); tstate->interp = interp; + tstate->eval_breaker = + _Py_atomic_load_uintptr_relaxed(&interp->ceval.instrumentation_version); // next/prev are set in add_threadstate(). assert(tstate->next == NULL); @@ -2021,8 +2021,7 @@ park_detached_threads(struct _stoptheworld_state *stw) } } else if (state == _Py_THREAD_ATTACHED && t != stw->requester) { - // TODO: set this per-thread, rather than per-interpreter. - _Py_set_eval_breaker_bit(t->interp, _PY_EVAL_PLEASE_STOP_BIT, 1); + _Py_set_eval_breaker_bit(t, _PY_EVAL_PLEASE_STOP_BIT); } } stw->thread_countdown -= num_parked; @@ -2186,19 +2185,18 @@ PyThreadState_SetAsyncExc(unsigned long id, PyObject *exc) * deadlock, we need to release head_mutex before * the decref. */ - PyObject *old_exc = tstate->async_exc; - tstate->async_exc = Py_XNewRef(exc); + Py_XINCREF(exc); + PyObject *old_exc = _Py_atomic_exchange_ptr(&tstate->async_exc, exc); HEAD_UNLOCK(runtime); Py_XDECREF(old_exc); - _PyEval_SignalAsyncExc(tstate->interp); + _Py_set_eval_breaker_bit(tstate, _PY_ASYNC_EXCEPTION_BIT); return 1; } HEAD_UNLOCK(runtime); return 0; } - //--------------------------------- // API for the current thread state //--------------------------------- From 9af80ec83d1647a472331bd1333a7fa9108fe98e Mon Sep 17 00:00:00 2001 From: Victor Stinner <vstinner@python.org> Date: Tue, 20 Feb 2024 16:02:27 +0100 Subject: [PATCH 375/507] gh-110850: Replace _PyTime_t with PyTime_t (#115719) Run command: sed -i -e 's!\<_PyTime_t\>!PyTime_t!g' $(find -name "*.c" -o -name "*.h") --- Include/internal/pycore_import.h | 4 +- Include/internal/pycore_lock.h | 6 +- Include/internal/pycore_parking_lot.h | 4 +- Include/internal/pycore_pythread.h | 2 +- Include/internal/pycore_semaphore.h | 4 +- Include/internal/pycore_time.h | 74 +++---- Modules/_datetimemodule.c | 2 +- Modules/_lsprof.c | 26 +-- Modules/_queuemodule.c | 6 +- Modules/_ssl.c | 14 +- Modules/_testinternalcapi/pytime.c | 24 +- Modules/_testinternalcapi/test_lock.c | 4 +- Modules/_testsinglephase.c | 10 +- Modules/_threadmodule.c | 12 +- Modules/faulthandler.c | 4 +- Modules/posixmodule.c | 2 +- Modules/selectmodule.c | 12 +- Modules/signalmodule.c | 6 +- Modules/socketmodule.c | 24 +- Modules/socketmodule.h | 4 +- Modules/timemodule.c | 74 +++---- Python/gc.c | 2 +- Python/gc_free_threading.c | 2 +- Python/import.c | 4 +- Python/lock.c | 14 +- Python/parking_lot.c | 12 +- Python/pystate.c | 2 +- Python/pytime.c | 304 +++++++++++++------------- Python/thread.c | 6 +- Python/thread_nt.h | 6 +- Python/thread_pthread.h | 12 +- 31 files changed, 341 insertions(+), 341 deletions(-) diff --git a/Include/internal/pycore_import.h b/Include/internal/pycore_import.h index c84f87a831bf384..5f49b9aa6dfebfa 100644 --- a/Include/internal/pycore_import.h +++ b/Include/internal/pycore_import.h @@ -11,7 +11,7 @@ extern "C" { #include "pycore_lock.h" // PyMutex #include "pycore_hashtable.h" // _Py_hashtable_t -#include "pycore_time.h" // _PyTime_t +#include "pycore_time.h" // PyTime_t extern int _PyImport_IsInitialized(PyInterpreterState *); @@ -103,7 +103,7 @@ struct _import_state { /* diagnostic info in PyImport_ImportModuleLevelObject() */ struct { int import_level; - _PyTime_t accumulated; + PyTime_t accumulated; int header; } find_and_load; }; diff --git a/Include/internal/pycore_lock.h b/Include/internal/pycore_lock.h index 674a1d170fec101..1aaa6677932282d 100644 --- a/Include/internal/pycore_lock.h +++ b/Include/internal/pycore_lock.h @@ -13,7 +13,7 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif -#include "pycore_time.h" // _PyTime_t +#include "pycore_time.h" // PyTime_t // A mutex that occupies one byte. The lock can be zero initialized. @@ -113,7 +113,7 @@ typedef enum _PyLockFlags { // Lock a mutex with an optional timeout and additional options. See // _PyLockFlags for details. extern PyLockStatus -_PyMutex_LockTimed(PyMutex *m, _PyTime_t timeout_ns, _PyLockFlags flags); +_PyMutex_LockTimed(PyMutex *m, PyTime_t timeout_ns, _PyLockFlags flags); // Lock a mutex with aditional options. See _PyLockFlags for details. static inline void @@ -146,7 +146,7 @@ PyAPI_FUNC(void) PyEvent_Wait(PyEvent *evt); // Wait for the event to be set, or until the timeout expires. If the event is // already set, then this returns immediately. Returns 1 if the event was set, // and 0 if the timeout expired or thread was interrupted. -PyAPI_FUNC(int) PyEvent_WaitTimed(PyEvent *evt, _PyTime_t timeout_ns); +PyAPI_FUNC(int) PyEvent_WaitTimed(PyEvent *evt, PyTime_t timeout_ns); // _PyRawMutex implements a word-sized mutex that that does not depend on the diff --git a/Include/internal/pycore_parking_lot.h b/Include/internal/pycore_parking_lot.h index f444da730055e84..a192228970c6a30 100644 --- a/Include/internal/pycore_parking_lot.h +++ b/Include/internal/pycore_parking_lot.h @@ -18,7 +18,7 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif -#include "pycore_time.h" // _PyTime_t +#include "pycore_time.h" // PyTime_t enum { @@ -61,7 +61,7 @@ enum { // } PyAPI_FUNC(int) _PyParkingLot_Park(const void *address, const void *expected, - size_t address_size, _PyTime_t timeout_ns, + size_t address_size, PyTime_t timeout_ns, void *park_arg, int detach); // Callback for _PyParkingLot_Unpark: diff --git a/Include/internal/pycore_pythread.h b/Include/internal/pycore_pythread.h index 265299d7574838b..d017d4ff308aa8d 100644 --- a/Include/internal/pycore_pythread.h +++ b/Include/internal/pycore_pythread.h @@ -96,7 +96,7 @@ extern void _PyThread_AfterFork(struct _pythread_runtime_state *state); // unset: -1 seconds, in nanoseconds -#define PyThread_UNSET_TIMEOUT ((_PyTime_t)(-1 * 1000 * 1000 * 1000)) +#define PyThread_UNSET_TIMEOUT ((PyTime_t)(-1 * 1000 * 1000 * 1000)) // Exported for the _xxinterpchannels module. PyAPI_FUNC(int) PyThread_ParseTimeoutArg( diff --git a/Include/internal/pycore_semaphore.h b/Include/internal/pycore_semaphore.h index 4c37df7b39a48a3..e1963a6ea239e17 100644 --- a/Include/internal/pycore_semaphore.h +++ b/Include/internal/pycore_semaphore.h @@ -8,7 +8,7 @@ #endif #include "pycore_pythread.h" // _POSIX_SEMAPHORES -#include "pycore_time.h" // _PyTime_t +#include "pycore_time.h" // PyTime_t #ifdef MS_WINDOWS # define WIN32_LEAN_AND_MEAN @@ -48,7 +48,7 @@ typedef struct _PySemaphore { // If `detach` is true, then the thread will detach/release the GIL while // sleeping. PyAPI_FUNC(int) -_PySemaphore_Wait(_PySemaphore *sema, _PyTime_t timeout_ns, int detach); +_PySemaphore_Wait(_PySemaphore *sema, PyTime_t timeout_ns, int detach); // Wakes up a single thread waiting on sema. Note that _PySemaphore_Wakeup() // can be called before _PySemaphore_Wait(). diff --git a/Include/internal/pycore_time.h b/Include/internal/pycore_time.h index 1aad6ccea69ae35..abef52e3f9f0fc5 100644 --- a/Include/internal/pycore_time.h +++ b/Include/internal/pycore_time.h @@ -62,7 +62,7 @@ extern "C" { struct timeval; #endif -typedef PyTime_t _PyTime_t; +typedef PyTime_t PyTime_t; #define _SIZEOF_PYTIME_T 8 typedef enum { @@ -130,69 +130,69 @@ PyAPI_FUNC(int) _PyTime_ObjectToTimespec( // Create a timestamp from a number of seconds. // Export for '_socket' shared extension. -PyAPI_FUNC(_PyTime_t) _PyTime_FromSeconds(int seconds); +PyAPI_FUNC(PyTime_t) _PyTime_FromSeconds(int seconds); // Create a timestamp from a number of seconds in double. // Export for '_socket' shared extension. -PyAPI_FUNC(_PyTime_t) _PyTime_FromSecondsDouble(double seconds, _PyTime_round_t round); +PyAPI_FUNC(PyTime_t) _PyTime_FromSecondsDouble(double seconds, _PyTime_round_t round); // Macro to create a timestamp from a number of seconds, no integer overflow. // Only use the macro for small values, prefer _PyTime_FromSeconds(). #define _PYTIME_FROMSECONDS(seconds) \ - ((_PyTime_t)(seconds) * (1000 * 1000 * 1000)) + ((PyTime_t)(seconds) * (1000 * 1000 * 1000)) // Create a timestamp from a number of nanoseconds. // Export for '_testinternalcapi' shared extension. -PyAPI_FUNC(_PyTime_t) _PyTime_FromNanoseconds(_PyTime_t ns); +PyAPI_FUNC(PyTime_t) _PyTime_FromNanoseconds(PyTime_t ns); // Create a timestamp from a number of microseconds. // Clamp to [PyTime_MIN; PyTime_MAX] on overflow. -extern _PyTime_t _PyTime_FromMicrosecondsClamp(_PyTime_t us); +extern PyTime_t _PyTime_FromMicrosecondsClamp(PyTime_t us); // Create a timestamp from nanoseconds (Python int). // Export for '_lsprof' shared extension. -PyAPI_FUNC(int) _PyTime_FromNanosecondsObject(_PyTime_t *t, +PyAPI_FUNC(int) _PyTime_FromNanosecondsObject(PyTime_t *t, PyObject *obj); // Convert a number of seconds (Python float or int) to a timestamp. // Raise an exception and return -1 on error, return 0 on success. // Export for '_socket' shared extension. -PyAPI_FUNC(int) _PyTime_FromSecondsObject(_PyTime_t *t, +PyAPI_FUNC(int) _PyTime_FromSecondsObject(PyTime_t *t, PyObject *obj, _PyTime_round_t round); // Convert a number of milliseconds (Python float or int, 10^-3) to a timestamp. // Raise an exception and return -1 on error, return 0 on success. // Export for 'select' shared extension. -PyAPI_FUNC(int) _PyTime_FromMillisecondsObject(_PyTime_t *t, +PyAPI_FUNC(int) _PyTime_FromMillisecondsObject(PyTime_t *t, PyObject *obj, _PyTime_round_t round); // Convert timestamp to a number of milliseconds (10^-3 seconds). // Export for '_ssl' shared extension. -PyAPI_FUNC(_PyTime_t) _PyTime_AsMilliseconds(_PyTime_t t, +PyAPI_FUNC(PyTime_t) _PyTime_AsMilliseconds(PyTime_t t, _PyTime_round_t round); // Convert timestamp to a number of microseconds (10^-6 seconds). // Export for '_queue' shared extension. -PyAPI_FUNC(_PyTime_t) _PyTime_AsMicroseconds(_PyTime_t t, +PyAPI_FUNC(PyTime_t) _PyTime_AsMicroseconds(PyTime_t t, _PyTime_round_t round); #ifdef MS_WINDOWS // Convert timestamp to a number of 100 nanoseconds (10^-7 seconds). -extern _PyTime_t _PyTime_As100Nanoseconds(_PyTime_t t, +extern PyTime_t _PyTime_As100Nanoseconds(PyTime_t t, _PyTime_round_t round); #endif // Convert timestamp to a number of nanoseconds (10^-9 seconds) as a Python int // object. // Export for '_testinternalcapi' shared extension. -PyAPI_FUNC(PyObject*) _PyTime_AsNanosecondsObject(_PyTime_t t); +PyAPI_FUNC(PyObject*) _PyTime_AsNanosecondsObject(PyTime_t t); #ifndef MS_WINDOWS // Create a timestamp from a timeval structure. // Raise an exception and return -1 on overflow, return 0 on success. -extern int _PyTime_FromTimeval(_PyTime_t *tp, struct timeval *tv); +extern int _PyTime_FromTimeval(PyTime_t *tp, struct timeval *tv); #endif // Convert a timestamp to a timeval structure (microsecond resolution). @@ -200,14 +200,14 @@ extern int _PyTime_FromTimeval(_PyTime_t *tp, struct timeval *tv); // Raise an exception and return -1 if the conversion overflowed, // return 0 on success. // Export for 'select' shared extension. -PyAPI_FUNC(int) _PyTime_AsTimeval(_PyTime_t t, +PyAPI_FUNC(int) _PyTime_AsTimeval(PyTime_t t, struct timeval *tv, _PyTime_round_t round); // Similar to _PyTime_AsTimeval() but don't raise an exception on overflow. -// On overflow, clamp tv_sec to _PyTime_t min/max. +// On overflow, clamp tv_sec to PyTime_t min/max. // Export for 'select' shared extension. -PyAPI_FUNC(void) _PyTime_AsTimeval_clamp(_PyTime_t t, +PyAPI_FUNC(void) _PyTime_AsTimeval_clamp(PyTime_t t, struct timeval *tv, _PyTime_round_t round); @@ -219,7 +219,7 @@ PyAPI_FUNC(void) _PyTime_AsTimeval_clamp(_PyTime_t t, // return 0 on success. // Export for '_datetime' shared extension. PyAPI_FUNC(int) _PyTime_AsTimevalTime_t( - _PyTime_t t, + PyTime_t t, time_t *secs, int *us, _PyTime_round_t round); @@ -227,23 +227,23 @@ PyAPI_FUNC(int) _PyTime_AsTimevalTime_t( #if defined(HAVE_CLOCK_GETTIME) || defined(HAVE_KQUEUE) // Create a timestamp from a timespec structure. // Raise an exception and return -1 on overflow, return 0 on success. -extern int _PyTime_FromTimespec(_PyTime_t *tp, const struct timespec *ts); +extern int _PyTime_FromTimespec(PyTime_t *tp, const struct timespec *ts); // Convert a timestamp to a timespec structure (nanosecond resolution). // tv_nsec is always positive. // Raise an exception and return -1 on error, return 0 on success. // Export for '_testinternalcapi' shared extension. -PyAPI_FUNC(int) _PyTime_AsTimespec(_PyTime_t t, struct timespec *ts); +PyAPI_FUNC(int) _PyTime_AsTimespec(PyTime_t t, struct timespec *ts); // Similar to _PyTime_AsTimespec() but don't raise an exception on overflow. -// On overflow, clamp tv_sec to _PyTime_t min/max. +// On overflow, clamp tv_sec to PyTime_t min/max. // Export for '_testinternalcapi' shared extension. -PyAPI_FUNC(void) _PyTime_AsTimespec_clamp(_PyTime_t t, struct timespec *ts); +PyAPI_FUNC(void) _PyTime_AsTimespec_clamp(PyTime_t t, struct timespec *ts); #endif // Compute t1 + t2. Clamp to [PyTime_MIN; PyTime_MAX] on overflow. -extern _PyTime_t _PyTime_Add(_PyTime_t t1, _PyTime_t t2); +extern PyTime_t _PyTime_Add(PyTime_t t1, PyTime_t t2); // Structure used by time.get_clock_info() typedef struct { @@ -262,13 +262,13 @@ typedef struct { // Use _PyTime_GetSystemClockWithInfo or the public PyTime_Time() to check // for failure. // Export for '_random' shared extension. -PyAPI_FUNC(_PyTime_t) _PyTime_GetSystemClock(void); +PyAPI_FUNC(PyTime_t) _PyTime_GetSystemClock(void); // Get the current time from the system clock. // On success, set *t and *info (if not NULL), and return 0. // On error, raise an exception and return -1. extern int _PyTime_GetSystemClockWithInfo( - _PyTime_t *t, + PyTime_t *t, _Py_clock_info_t *info); // Get the time of a monotonic clock, i.e. a clock that cannot go backwards. @@ -283,7 +283,7 @@ extern int _PyTime_GetSystemClockWithInfo( // Use _PyTime_GetMonotonicClockWithInfo or the public PyTime_Monotonic() // to check for failure. // Export for '_random' shared extension. -PyAPI_FUNC(_PyTime_t) _PyTime_GetMonotonicClock(void); +PyAPI_FUNC(PyTime_t) _PyTime_GetMonotonicClock(void); // Get the time of a monotonic clock, i.e. a clock that cannot go backwards. // The clock is not affected by system clock updates. The reference point of @@ -295,7 +295,7 @@ PyAPI_FUNC(_PyTime_t) _PyTime_GetMonotonicClock(void); // Return 0 on success, raise an exception and return -1 on error. // Export for '_testsinglephase' shared extension. PyAPI_FUNC(int) _PyTime_GetMonotonicClockWithInfo( - _PyTime_t *t, + PyTime_t *t, _Py_clock_info_t *info); @@ -319,7 +319,7 @@ PyAPI_FUNC(int) _PyTime_gmtime(time_t t, struct tm *tm); // Use _PyTime_GetPerfCounterWithInfo() or the public PyTime_PerfCounter // to check for failure. // Export for '_lsprof' shared extension. -PyAPI_FUNC(_PyTime_t) _PyTime_GetPerfCounter(void); +PyAPI_FUNC(PyTime_t) _PyTime_GetPerfCounter(void); // Get the performance counter: clock with the highest available resolution to @@ -329,7 +329,7 @@ PyAPI_FUNC(_PyTime_t) _PyTime_GetPerfCounter(void); // // Return 0 on success, raise an exception and return -1 on error. extern int _PyTime_GetPerfCounterWithInfo( - _PyTime_t *t, + PyTime_t *t, _Py_clock_info_t *info); // Alias for backward compatibility @@ -343,19 +343,19 @@ extern int _PyTime_GetPerfCounterWithInfo( // Create a deadline. // Pseudo code: _PyTime_GetMonotonicClock() + timeout. // Export for '_ssl' shared extension. -PyAPI_FUNC(_PyTime_t) _PyDeadline_Init(_PyTime_t timeout); +PyAPI_FUNC(PyTime_t) _PyDeadline_Init(PyTime_t timeout); // Get remaining time from a deadline. // Pseudo code: deadline - _PyTime_GetMonotonicClock(). // Export for '_ssl' shared extension. -PyAPI_FUNC(_PyTime_t) _PyDeadline_Get(_PyTime_t deadline); +PyAPI_FUNC(PyTime_t) _PyDeadline_Get(PyTime_t deadline); // --- _PyTimeFraction ------------------------------------------------------- typedef struct { - _PyTime_t numer; - _PyTime_t denom; + PyTime_t numer; + PyTime_t denom; } _PyTimeFraction; // Set a fraction. @@ -363,13 +363,13 @@ typedef struct { // Return -1 if the fraction is invalid. extern int _PyTimeFraction_Set( _PyTimeFraction *frac, - _PyTime_t numer, - _PyTime_t denom); + PyTime_t numer, + PyTime_t denom); // Compute ticks * frac.numer / frac.denom. // Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow. -extern _PyTime_t _PyTimeFraction_Mul( - _PyTime_t ticks, +extern PyTime_t _PyTimeFraction_Mul( + PyTime_t ticks, const _PyTimeFraction *frac); // Compute a clock resolution: frac.numer / frac.denom / 1e9. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 014ccdd3f6effe4..b8bd70250aee299 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -5131,7 +5131,7 @@ datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp, static PyObject * datetime_best_possible(PyObject *cls, TM_FUNC f, PyObject *tzinfo) { - _PyTime_t ts = _PyTime_GetSystemClock(); + PyTime_t ts = _PyTime_GetSystemClock(); time_t secs; int us; diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c index 8f09204097529f4..eae4261f4953f50 100644 --- a/Modules/_lsprof.c +++ b/Modules/_lsprof.c @@ -17,8 +17,8 @@ struct _ProfilerEntry; /* represents a function called from another function */ typedef struct _ProfilerSubEntry { rotating_node_t header; - _PyTime_t tt; - _PyTime_t it; + PyTime_t tt; + PyTime_t it; long callcount; long recursivecallcount; long recursionLevel; @@ -28,8 +28,8 @@ typedef struct _ProfilerSubEntry { typedef struct _ProfilerEntry { rotating_node_t header; PyObject *userObj; /* PyCodeObject, or a descriptive str for builtins */ - _PyTime_t tt; /* total time in this entry */ - _PyTime_t it; /* inline time in this entry (not in subcalls) */ + PyTime_t tt; /* total time in this entry */ + PyTime_t it; /* inline time in this entry (not in subcalls) */ long callcount; /* how many times this was called */ long recursivecallcount; /* how many times called recursively */ long recursionLevel; @@ -37,8 +37,8 @@ typedef struct _ProfilerEntry { } ProfilerEntry; typedef struct _ProfilerContext { - _PyTime_t t0; - _PyTime_t subt; + PyTime_t t0; + PyTime_t subt; struct _ProfilerContext *previous; ProfilerEntry *ctxEntry; } ProfilerContext; @@ -84,7 +84,7 @@ _lsprof_get_state(PyObject *module) /*** External Timers ***/ -static _PyTime_t CallExternalTimer(ProfilerObject *pObj) +static PyTime_t CallExternalTimer(ProfilerObject *pObj) { PyObject *o = _PyObject_CallNoArgs(pObj->externalTimer); if (o == NULL) { @@ -92,7 +92,7 @@ static _PyTime_t CallExternalTimer(ProfilerObject *pObj) return 0; } - _PyTime_t result; + PyTime_t result; int err; if (pObj->externalTimerUnit > 0.0) { /* interpret the result as an integer that will be scaled @@ -101,7 +101,7 @@ static _PyTime_t CallExternalTimer(ProfilerObject *pObj) } else { /* interpret the result as a double measured in seconds. - As the profiler works with _PyTime_t internally + As the profiler works with PyTime_t internally we convert it to a large integer */ err = _PyTime_FromSecondsObject(&result, o, _PyTime_ROUND_FLOOR); } @@ -113,7 +113,7 @@ static _PyTime_t CallExternalTimer(ProfilerObject *pObj) return result; } -static inline _PyTime_t +static inline PyTime_t call_timer(ProfilerObject *pObj) { if (pObj->externalTimer != NULL) { @@ -311,8 +311,8 @@ initContext(ProfilerObject *pObj, ProfilerContext *self, ProfilerEntry *entry) static void Stop(ProfilerObject *pObj, ProfilerContext *self, ProfilerEntry *entry) { - _PyTime_t tt = call_timer(pObj) - self->t0; - _PyTime_t it = tt - self->subt; + PyTime_t tt = call_timer(pObj) - self->t0; + PyTime_t it = tt - self->subt; if (self->previous) self->previous->subt += tt; pObj->currentProfilerContext = self->previous; @@ -557,7 +557,7 @@ _lsprof_Profiler_getstats_impl(ProfilerObject *self, PyTypeObject *cls) return NULL; } if (!self->externalTimer || self->externalTimerUnit == 0.0) { - _PyTime_t onesec = _PyTime_FromSeconds(1); + PyTime_t onesec = _PyTime_FromSeconds(1); collect.factor = (double)1 / onesec; } else { diff --git a/Modules/_queuemodule.c b/Modules/_queuemodule.c index 18b24855c52ad61..5ef1cea24dce68c 100644 --- a/Modules/_queuemodule.c +++ b/Modules/_queuemodule.c @@ -6,7 +6,7 @@ #include "pycore_ceval.h" // Py_MakePendingCalls() #include "pycore_moduleobject.h" // _PyModule_GetState() #include "pycore_parking_lot.h" -#include "pycore_time.h" // _PyTime_t +#include "pycore_time.h" // PyTime_t #include <stdbool.h> #include <stddef.h> // offsetof() @@ -372,13 +372,13 @@ _queue_SimpleQueue_get_impl(simplequeueobject *self, PyTypeObject *cls, int block, PyObject *timeout_obj) /*[clinic end generated code: output=5c2cca914cd1e55b input=f7836c65e5839c51]*/ { - _PyTime_t endtime = 0; + PyTime_t endtime = 0; // XXX Use PyThread_ParseTimeoutArg(). if (block != 0 && !Py_IsNone(timeout_obj)) { /* With timeout */ - _PyTime_t timeout; + PyTime_t timeout; if (_PyTime_FromSecondsObject(&timeout, timeout_obj, _PyTime_ROUND_CEILING) < 0) { return NULL; diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 950ee3663080e1e..1bf7241042d39a9 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -369,7 +369,7 @@ class _ssl.SSLSession "PySSLSession *" "get_state_type(type)->PySSLSession_Type" #include "clinic/_ssl.c.h" -static int PySSL_select(PySocketSockObject *s, int writing, _PyTime_t timeout); +static int PySSL_select(PySocketSockObject *s, int writing, PyTime_t timeout); static int PySSL_set_owner(PySSLSocket *, PyObject *, void *); static int PySSL_set_session(PySSLSocket *, PyObject *, void *); @@ -963,7 +963,7 @@ _ssl__SSLSocket_do_handshake_impl(PySSLSocket *self) _PySSLError err; int sockstate, nonblocking; PySocketSockObject *sock = GET_SOCKET(self); - _PyTime_t timeout, deadline = 0; + PyTime_t timeout, deadline = 0; int has_timeout; if (sock) { @@ -2273,12 +2273,12 @@ PySSL_dealloc(PySSLSocket *self) */ static int -PySSL_select(PySocketSockObject *s, int writing, _PyTime_t timeout) +PySSL_select(PySocketSockObject *s, int writing, PyTime_t timeout) { int rc; #ifdef HAVE_POLL struct pollfd pollfd; - _PyTime_t ms; + PyTime_t ms; #else int nfds; fd_set fds; @@ -2357,7 +2357,7 @@ _ssl__SSLSocket_write_impl(PySSLSocket *self, Py_buffer *b) _PySSLError err; int nonblocking; PySocketSockObject *sock = GET_SOCKET(self); - _PyTime_t timeout, deadline = 0; + PyTime_t timeout, deadline = 0; int has_timeout; if (sock != NULL) { @@ -2495,7 +2495,7 @@ _ssl__SSLSocket_read_impl(PySSLSocket *self, Py_ssize_t len, _PySSLError err; int nonblocking; PySocketSockObject *sock = GET_SOCKET(self); - _PyTime_t timeout, deadline = 0; + PyTime_t timeout, deadline = 0; int has_timeout; if (!group_right_1 && len < 0) { @@ -2627,7 +2627,7 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self) int sockstate, nonblocking, ret; int zeros = 0; PySocketSockObject *sock = GET_SOCKET(self); - _PyTime_t timeout, deadline = 0; + PyTime_t timeout, deadline = 0; int has_timeout; if (sock != NULL) { diff --git a/Modules/_testinternalcapi/pytime.c b/Modules/_testinternalcapi/pytime.c index f0f758ea032df82..11a02413b8c114c 100644 --- a/Modules/_testinternalcapi/pytime.c +++ b/Modules/_testinternalcapi/pytime.c @@ -16,7 +16,7 @@ test_pytime_fromseconds(PyObject *self, PyObject *args) if (!PyArg_ParseTuple(args, "i", &seconds)) { return NULL; } - _PyTime_t ts = _PyTime_FromSeconds(seconds); + PyTime_t ts = _PyTime_FromSeconds(seconds); return _PyTime_AsNanosecondsObject(ts); } @@ -45,7 +45,7 @@ test_pytime_fromsecondsobject(PyObject *self, PyObject *args) if (check_time_rounding(round) < 0) { return NULL; } - _PyTime_t ts; + PyTime_t ts; if (_PyTime_FromSecondsObject(&ts, obj, round) == -1) { return NULL; } @@ -63,7 +63,7 @@ test_PyTime_AsTimeval(PyObject *self, PyObject *args) if (check_time_rounding(round) < 0) { return NULL; } - _PyTime_t t; + PyTime_t t; if (_PyTime_FromNanosecondsObject(&t, obj) < 0) { return NULL; } @@ -90,7 +90,7 @@ test_PyTime_AsTimeval_clamp(PyObject *self, PyObject *args) if (check_time_rounding(round) < 0) { return NULL; } - _PyTime_t t; + PyTime_t t; if (_PyTime_FromNanosecondsObject(&t, obj) < 0) { return NULL; } @@ -112,7 +112,7 @@ test_PyTime_AsTimespec(PyObject *self, PyObject *args) if (!PyArg_ParseTuple(args, "O", &obj)) { return NULL; } - _PyTime_t t; + PyTime_t t; if (_PyTime_FromNanosecondsObject(&t, obj) < 0) { return NULL; } @@ -130,7 +130,7 @@ test_PyTime_AsTimespec_clamp(PyObject *self, PyObject *args) if (!PyArg_ParseTuple(args, "O", &obj)) { return NULL; } - _PyTime_t t; + PyTime_t t; if (_PyTime_FromNanosecondsObject(&t, obj) < 0) { return NULL; } @@ -148,15 +148,15 @@ test_PyTime_AsMilliseconds(PyObject *self, PyObject *args) if (!PyArg_ParseTuple(args, "Oi", &obj, &round)) { return NULL; } - _PyTime_t t; + PyTime_t t; if (_PyTime_FromNanosecondsObject(&t, obj) < 0) { return NULL; } if (check_time_rounding(round) < 0) { return NULL; } - _PyTime_t ms = _PyTime_AsMilliseconds(t, round); - _PyTime_t ns = _PyTime_FromNanoseconds(ms); + PyTime_t ms = _PyTime_AsMilliseconds(t, round); + PyTime_t ns = _PyTime_FromNanoseconds(ms); return _PyTime_AsNanosecondsObject(ns); } @@ -168,15 +168,15 @@ test_PyTime_AsMicroseconds(PyObject *self, PyObject *args) if (!PyArg_ParseTuple(args, "Oi", &obj, &round)) { return NULL; } - _PyTime_t t; + PyTime_t t; if (_PyTime_FromNanosecondsObject(&t, obj) < 0) { return NULL; } if (check_time_rounding(round) < 0) { return NULL; } - _PyTime_t us = _PyTime_AsMicroseconds(t, round); - _PyTime_t ns = _PyTime_FromNanoseconds(us); + PyTime_t us = _PyTime_AsMicroseconds(t, round); + PyTime_t ns = _PyTime_FromNanoseconds(us); return _PyTime_AsNanosecondsObject(ns); } diff --git a/Modules/_testinternalcapi/test_lock.c b/Modules/_testinternalcapi/test_lock.c index 83081f73a72f648..9facbc5bccf9f93 100644 --- a/Modules/_testinternalcapi/test_lock.c +++ b/Modules/_testinternalcapi/test_lock.c @@ -289,7 +289,7 @@ _testinternalcapi_benchmark_locks_impl(PyObject *module, goto exit; } - _PyTime_t start = _PyTime_GetMonotonicClock(); + PyTime_t start = _PyTime_GetMonotonicClock(); for (Py_ssize_t i = 0; i < num_threads; i++) { thread_data[i].bench_data = &bench_data; @@ -306,7 +306,7 @@ _testinternalcapi_benchmark_locks_impl(PyObject *module, } Py_ssize_t total_iters = bench_data.total_iters; - _PyTime_t end = _PyTime_GetMonotonicClock(); + PyTime_t end = _PyTime_GetMonotonicClock(); // Return the total number of acquisitions and the number of acquisitions // for each thread. diff --git a/Modules/_testsinglephase.c b/Modules/_testsinglephase.c index c42a15a0eff4944..dccac2852a567a4 100644 --- a/Modules/_testsinglephase.c +++ b/Modules/_testsinglephase.c @@ -8,11 +8,11 @@ //#include <time.h> #include "Python.h" #include "pycore_namespace.h" // _PyNamespace_New() -#include "pycore_time.h" // _PyTime_t +#include "pycore_time.h" // PyTime_t typedef struct { - _PyTime_t initialized; + PyTime_t initialized; PyObject *error; PyObject *int_const; PyObject *str_const; @@ -67,15 +67,15 @@ clear_state(module_state *state) } static int -_set_initialized(_PyTime_t *initialized) +_set_initialized(PyTime_t *initialized) { /* We go strictly monotonic to ensure each time is unique. */ - _PyTime_t prev; + PyTime_t prev; if (_PyTime_GetMonotonicClockWithInfo(&prev, NULL) != 0) { return -1; } /* We do a busy sleep since the interval should be super short. */ - _PyTime_t t; + PyTime_t t; do { if (_PyTime_GetMonotonicClockWithInfo(&t, NULL) != 0) { return -1; diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index ec23fe849eb0316..226053437bc2cd9 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -235,14 +235,14 @@ lock_dealloc(lockobject *self) } static inline PyLockStatus -acquire_timed(PyThread_type_lock lock, _PyTime_t timeout) +acquire_timed(PyThread_type_lock lock, PyTime_t timeout) { return PyThread_acquire_lock_timed_with_retries(lock, timeout); } static int lock_acquire_parse_args(PyObject *args, PyObject *kwds, - _PyTime_t *timeout) + PyTime_t *timeout) { char *kwlist[] = {"blocking", "timeout", NULL}; int blocking = 1; @@ -253,7 +253,7 @@ lock_acquire_parse_args(PyObject *args, PyObject *kwds, // XXX Use PyThread_ParseTimeoutArg(). - const _PyTime_t unset_timeout = _PyTime_FromSeconds(-1); + const PyTime_t unset_timeout = _PyTime_FromSeconds(-1); *timeout = unset_timeout; if (timeout_obj @@ -274,7 +274,7 @@ lock_acquire_parse_args(PyObject *args, PyObject *kwds, if (!blocking) *timeout = 0; else if (*timeout != unset_timeout) { - _PyTime_t microseconds; + PyTime_t microseconds; microseconds = _PyTime_AsMicroseconds(*timeout, _PyTime_ROUND_TIMEOUT); if (microseconds > PY_TIMEOUT_MAX) { @@ -289,7 +289,7 @@ lock_acquire_parse_args(PyObject *args, PyObject *kwds, static PyObject * lock_PyThread_acquire_lock(lockobject *self, PyObject *args, PyObject *kwds) { - _PyTime_t timeout; + PyTime_t timeout; if (lock_acquire_parse_args(args, kwds, &timeout) < 0) return NULL; @@ -501,7 +501,7 @@ rlock_is_owned_by(rlockobject *self, PyThread_ident_t tid) static PyObject * rlock_acquire(rlockobject *self, PyObject *args, PyObject *kwds) { - _PyTime_t timeout; + PyTime_t timeout; PyThread_ident_t tid; PyLockStatus r = PY_LOCK_ACQUIRED; diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index 95d646c9c65b3c4..91255fc98885aec 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -623,7 +623,7 @@ cancel_dump_traceback_later(void) #define SEC_TO_US (1000 * 1000) static char* -format_timeout(_PyTime_t us) +format_timeout(PyTime_t us) { unsigned long sec, min, hour; char buffer[100]; @@ -656,7 +656,7 @@ faulthandler_dump_traceback_later(PyObject *self, { static char *kwlist[] = {"timeout", "repeat", "file", "exit", NULL}; PyObject *timeout_obj; - _PyTime_t timeout, timeout_us; + PyTime_t timeout, timeout_us; int repeat = 0; PyObject *file = NULL; int fd; diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 9d9c9bd76b7fff6..4165fb66cc10dd3 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -10410,7 +10410,7 @@ build_itimerspec(const struct itimerspec* curr_value) static PyObject * build_itimerspec_ns(const struct itimerspec* curr_value) { - _PyTime_t value, interval; + PyTime_t value, interval; if (_PyTime_FromTimespec(&value, &curr_value->it_value) < 0) { return NULL; } diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c index 1dbde3e9e6ca5df..57d55a5611f1215 100644 --- a/Modules/selectmodule.c +++ b/Modules/selectmodule.c @@ -15,7 +15,7 @@ #include "Python.h" #include "pycore_fileutils.h" // _Py_set_inheritable() #include "pycore_import.h" // _PyImport_GetModuleAttrString() -#include "pycore_time.h" // _PyTime_t +#include "pycore_time.h" // PyTime_t #include <stdbool.h> #include <stddef.h> // offsetof() @@ -297,7 +297,7 @@ select_select_impl(PyObject *module, PyObject *rlist, PyObject *wlist, struct timeval tv, *tvp; int imax, omax, emax, max; int n; - _PyTime_t timeout, deadline = 0; + PyTime_t timeout, deadline = 0; if (timeout_obj == Py_None) tvp = (struct timeval *)NULL; @@ -619,7 +619,7 @@ select_poll_poll_impl(pollObject *self, PyObject *timeout_obj) PyObject *result_list = NULL; int poll_result, i, j; PyObject *value = NULL, *num = NULL; - _PyTime_t timeout = -1, ms = -1, deadline = 0; + PyTime_t timeout = -1, ms = -1, deadline = 0; int async_err = 0; if (timeout_obj != Py_None) { @@ -946,7 +946,7 @@ select_devpoll_poll_impl(devpollObject *self, PyObject *timeout_obj) PyObject *result_list = NULL; int poll_result, i; PyObject *value, *num1, *num2; - _PyTime_t timeout, ms, deadline = 0; + PyTime_t timeout, ms, deadline = 0; if (self->fd_devpoll < 0) return devpoll_err_closed(); @@ -1559,7 +1559,7 @@ select_epoll_poll_impl(pyEpoll_Object *self, PyObject *timeout_obj, int nfds, i; PyObject *elist = NULL, *etuple = NULL; struct epoll_event *evs = NULL; - _PyTime_t timeout = -1, ms = -1, deadline = 0; + PyTime_t timeout = -1, ms = -1, deadline = 0; if (self->epfd < 0) return pyepoll_err_closed(); @@ -2242,7 +2242,7 @@ select_kqueue_control_impl(kqueue_queue_Object *self, PyObject *changelist, struct kevent *chl = NULL; struct timespec timeoutspec; struct timespec *ptimeoutspec; - _PyTime_t timeout, deadline = 0; + PyTime_t timeout, deadline = 0; _selectstate *state = _selectstate_by_type(Py_TYPE(self)); if (self->kqfd < 0) diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c index 652e69b0d28b21e..a968cb1b0aa49cd 100644 --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -173,7 +173,7 @@ timeval_from_double(PyObject *obj, struct timeval *tv) return 0; } - _PyTime_t t; + PyTime_t t; if (_PyTime_FromSecondsObject(&t, obj, _PyTime_ROUND_CEILING) < 0) { return -1; } @@ -1207,7 +1207,7 @@ signal_sigtimedwait_impl(PyObject *module, sigset_t sigset, PyObject *timeout_obj) /*[clinic end generated code: output=59c8971e8ae18a64 input=87fd39237cf0b7ba]*/ { - _PyTime_t timeout; + PyTime_t timeout; if (_PyTime_FromSecondsObject(&timeout, timeout_obj, _PyTime_ROUND_CEILING) < 0) return NULL; @@ -1217,7 +1217,7 @@ signal_sigtimedwait_impl(PyObject *module, sigset_t sigset, return NULL; } - _PyTime_t deadline = _PyDeadline_Init(timeout); + PyTime_t deadline = _PyDeadline_Init(timeout); siginfo_t si; do { diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 0a0e0e78656f76b..9f70dbe4a83c2fd 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -547,7 +547,7 @@ typedef struct _socket_state { PyObject *socket_gaierror; /* Default timeout for new sockets */ - _PyTime_t defaulttimeout; + PyTime_t defaulttimeout; #if defined(HAVE_ACCEPT) || defined(HAVE_ACCEPT4) #if defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) @@ -772,13 +772,13 @@ internal_setblocking(PySocketSockObject *s, int block) } static int -internal_select(PySocketSockObject *s, int writing, _PyTime_t interval, +internal_select(PySocketSockObject *s, int writing, PyTime_t interval, int connect) { int n; #ifdef HAVE_POLL struct pollfd pollfd; - _PyTime_t ms; + PyTime_t ms; #else fd_set fds, efds; struct timeval tv, *tvp; @@ -888,10 +888,10 @@ sock_call_ex(PySocketSockObject *s, void *data, int connect, int *err, - _PyTime_t timeout) + PyTime_t timeout) { int has_timeout = (timeout > 0); - _PyTime_t deadline = 0; + PyTime_t deadline = 0; int deadline_initialized = 0; int res; @@ -905,7 +905,7 @@ sock_call_ex(PySocketSockObject *s, runs asynchronously. */ if (has_timeout || connect) { if (has_timeout) { - _PyTime_t interval; + PyTime_t interval; if (deadline_initialized) { /* recompute the timeout */ @@ -3011,13 +3011,13 @@ Returns True if socket is in blocking mode, or False if it\n\ is in non-blocking mode."); static int -socket_parse_timeout(_PyTime_t *timeout, PyObject *timeout_obj) +socket_parse_timeout(PyTime_t *timeout, PyObject *timeout_obj) { #ifdef MS_WINDOWS struct timeval tv; #endif #ifndef HAVE_POLL - _PyTime_t ms; + PyTime_t ms; #endif int overflow = 0; @@ -3060,7 +3060,7 @@ socket_parse_timeout(_PyTime_t *timeout, PyObject *timeout_obj) static PyObject * sock_settimeout(PySocketSockObject *s, PyObject *arg) { - _PyTime_t timeout; + PyTime_t timeout; if (socket_parse_timeout(&timeout, arg) < 0) return NULL; @@ -4382,8 +4382,8 @@ sock_sendall(PySocketSockObject *s, PyObject *args) Py_buffer pbuf; struct sock_send ctx; int has_timeout = (s->sock_timeout > 0); - _PyTime_t timeout = s->sock_timeout; - _PyTime_t deadline = 0; + PyTime_t timeout = s->sock_timeout; + PyTime_t deadline = 0; int deadline_initialized = 0; PyObject *res = NULL; @@ -6931,7 +6931,7 @@ When the socket module is first imported, the default is None."); static PyObject * socket_setdefaulttimeout(PyObject *self, PyObject *arg) { - _PyTime_t timeout; + PyTime_t timeout; if (socket_parse_timeout(&timeout, arg) < 0) return NULL; diff --git a/Modules/socketmodule.h b/Modules/socketmodule.h index 47146a28e02c8f4..a7c592c83aa25f7 100644 --- a/Modules/socketmodule.h +++ b/Modules/socketmodule.h @@ -1,6 +1,6 @@ /* Socket module header file */ -#include "pycore_time.h" // _PyTime_t +#include "pycore_time.h" // PyTime_t /* Includes needed for the sockaddr_* symbols below */ #ifndef MS_WINDOWS @@ -324,7 +324,7 @@ typedef struct { PyObject *(*errorhandler)(void); /* Error handler; checks errno, returns NULL and sets a Python exception */ - _PyTime_t sock_timeout; /* Operation timeout in seconds; + PyTime_t sock_timeout; /* Operation timeout in seconds; 0.0 means non-blocking */ struct _socket_state *state; } PySocketSockObject; diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 2b0d3900dbddd63..16769b2405f7063 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -70,7 +70,7 @@ module time /* Forward declarations */ -static int pysleep(_PyTime_t timeout); +static int pysleep(PyTime_t timeout); typedef struct { @@ -95,7 +95,7 @@ get_time_state(PyObject *module) static PyObject* -_PyFloat_FromPyTime(_PyTime_t t) +_PyFloat_FromPyTime(PyTime_t t) { double d = _PyTime_AsSecondsDouble(t); return PyFloat_FromDouble(d); @@ -103,7 +103,7 @@ _PyFloat_FromPyTime(_PyTime_t t) static int -get_system_time(_PyTime_t *t) +get_system_time(PyTime_t *t) { // Avoid _PyTime_GetSystemClock() which silently ignores errors. return _PyTime_GetSystemClockWithInfo(t, NULL); @@ -113,7 +113,7 @@ get_system_time(_PyTime_t *t) static PyObject * time_time(PyObject *self, PyObject *unused) { - _PyTime_t t; + PyTime_t t; if (get_system_time(&t) < 0) { return NULL; } @@ -130,7 +130,7 @@ Fractions of a second may be present if the system clock provides them."); static PyObject * time_time_ns(PyObject *self, PyObject *unused) { - _PyTime_t t; + PyTime_t t; if (get_system_time(&t) < 0) { return NULL; } @@ -153,7 +153,7 @@ Return the current time in nanoseconds since the Epoch."); #endif static int -py_clock(time_module_state *state, _PyTime_t *tp, _Py_clock_info_t *info) +py_clock(time_module_state *state, PyTime_t *tp, _Py_clock_info_t *info) { _PyTimeFraction *base = &state->clock_base; @@ -171,7 +171,7 @@ py_clock(time_module_state *state, _PyTime_t *tp, _Py_clock_info_t *info) "or its value cannot be represented"); return -1; } - _PyTime_t ns = _PyTimeFraction_Mul(ticks, base); + PyTime_t ns = _PyTimeFraction_Mul(ticks, base); *tp = _PyTime_FromNanoseconds(ns); return 0; } @@ -262,7 +262,7 @@ time_clock_gettime_ns_impl(PyObject *module, clockid_t clk_id) return NULL; } - _PyTime_t t; + PyTime_t t; if (_PyTime_FromTimespec(&t, &ts) < 0) { return NULL; } @@ -276,7 +276,7 @@ time_clock_settime(PyObject *self, PyObject *args) { int clk_id; PyObject *obj; - _PyTime_t t; + PyTime_t t; struct timespec tp; int ret; @@ -307,7 +307,7 @@ time_clock_settime_ns(PyObject *self, PyObject *args) { int clk_id; PyObject *obj; - _PyTime_t t; + PyTime_t t; struct timespec ts; int ret; @@ -402,7 +402,7 @@ time_sleep(PyObject *self, PyObject *timeout_obj) return NULL; } - _PyTime_t timeout; + PyTime_t timeout; if (_PyTime_FromSecondsObject(&timeout, timeout_obj, _PyTime_ROUND_TIMEOUT)) return NULL; if (timeout < 0) { @@ -1156,7 +1156,7 @@ should not be relied on."); static int -get_monotonic(_PyTime_t *t) +get_monotonic(PyTime_t *t) { // Avoid _PyTime_GetMonotonicClock() which silently ignores errors. return _PyTime_GetMonotonicClockWithInfo(t, NULL); @@ -1166,7 +1166,7 @@ get_monotonic(_PyTime_t *t) static PyObject * time_monotonic(PyObject *self, PyObject *unused) { - _PyTime_t t; + PyTime_t t; if (get_monotonic(&t) < 0) { return NULL; } @@ -1181,7 +1181,7 @@ Monotonic clock, cannot go backward."); static PyObject * time_monotonic_ns(PyObject *self, PyObject *unused) { - _PyTime_t t; + PyTime_t t; if (get_monotonic(&t) < 0) { return NULL; } @@ -1195,7 +1195,7 @@ Monotonic clock, cannot go backward, as nanoseconds."); static int -get_perf_counter(_PyTime_t *t) +get_perf_counter(PyTime_t *t) { // Avoid _PyTime_GetPerfCounter() which silently ignores errors. return _PyTime_GetPerfCounterWithInfo(t, NULL); @@ -1205,7 +1205,7 @@ get_perf_counter(_PyTime_t *t) static PyObject * time_perf_counter(PyObject *self, PyObject *unused) { - _PyTime_t t; + PyTime_t t; if (get_perf_counter(&t) < 0) { return NULL; } @@ -1221,7 +1221,7 @@ Performance counter for benchmarking."); static PyObject * time_perf_counter_ns(PyObject *self, PyObject *unused) { - _PyTime_t t; + PyTime_t t; if (get_perf_counter(&t) < 0) { return NULL; } @@ -1236,7 +1236,7 @@ Performance counter for benchmarking as nanoseconds."); #ifdef HAVE_TIMES static int -process_time_times(time_module_state *state, _PyTime_t *tp, +process_time_times(time_module_state *state, PyTime_t *tp, _Py_clock_info_t *info) { _PyTimeFraction *base = &state->times_base; @@ -1253,7 +1253,7 @@ process_time_times(time_module_state *state, _PyTime_t *tp, info->adjustable = 0; } - _PyTime_t ns; + PyTime_t ns; ns = _PyTimeFraction_Mul(process.tms_utime, base); ns += _PyTimeFraction_Mul(process.tms_stime, base); *tp = _PyTime_FromNanoseconds(ns); @@ -1263,14 +1263,14 @@ process_time_times(time_module_state *state, _PyTime_t *tp, static int -py_process_time(time_module_state *state, _PyTime_t *tp, +py_process_time(time_module_state *state, PyTime_t *tp, _Py_clock_info_t *info) { #if defined(MS_WINDOWS) HANDLE process; FILETIME creation_time, exit_time, kernel_time, user_time; ULARGE_INTEGER large; - _PyTime_t ktime, utime, t; + PyTime_t ktime, utime, t; BOOL ok; process = GetCurrentProcess(); @@ -1343,7 +1343,7 @@ py_process_time(time_module_state *state, _PyTime_t *tp, struct rusage ru; if (getrusage(RUSAGE_SELF, &ru) == 0) { - _PyTime_t utime, stime; + PyTime_t utime, stime; if (info) { info->implementation = "getrusage(RUSAGE_SELF)"; @@ -1359,7 +1359,7 @@ py_process_time(time_module_state *state, _PyTime_t *tp, return -1; } - _PyTime_t total = utime + stime; + PyTime_t total = utime + stime; *tp = total; return 0; } @@ -1386,7 +1386,7 @@ static PyObject * time_process_time(PyObject *module, PyObject *unused) { time_module_state *state = get_time_state(module); - _PyTime_t t; + PyTime_t t; if (py_process_time(state, &t, NULL) < 0) { return NULL; } @@ -1402,7 +1402,7 @@ static PyObject * time_process_time_ns(PyObject *module, PyObject *unused) { time_module_state *state = get_time_state(module); - _PyTime_t t; + PyTime_t t; if (py_process_time(state, &t, NULL) < 0) { return NULL; } @@ -1419,12 +1419,12 @@ sum of the kernel and user-space CPU time."); #if defined(MS_WINDOWS) #define HAVE_THREAD_TIME static int -_PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) +_PyTime_GetThreadTimeWithInfo(PyTime_t *tp, _Py_clock_info_t *info) { HANDLE thread; FILETIME creation_time, exit_time, kernel_time, user_time; ULARGE_INTEGER large; - _PyTime_t ktime, utime, t; + PyTime_t ktime, utime, t; BOOL ok; thread = GetCurrentThread(); @@ -1459,7 +1459,7 @@ _PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) #elif defined(_AIX) #define HAVE_THREAD_TIME static int -_PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) +_PyTime_GetThreadTimeWithInfo(PyTime_t *tp, _Py_clock_info_t *info) { /* bpo-40192: On AIX, thread_cputime() is preferred: it has nanosecond resolution, whereas clock_gettime(CLOCK_THREAD_CPUTIME_ID) @@ -1483,7 +1483,7 @@ _PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) #elif defined(__sun) && defined(__SVR4) #define HAVE_THREAD_TIME static int -_PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) +_PyTime_GetThreadTimeWithInfo(PyTime_t *tp, _Py_clock_info_t *info) { /* bpo-35455: On Solaris, CLOCK_THREAD_CPUTIME_ID clock is not always available; use gethrvtime() to substitute this functionality. */ @@ -1504,7 +1504,7 @@ _PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) #if defined(__APPLE__) && defined(__has_attribute) && __has_attribute(availability) static int -_PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) +_PyTime_GetThreadTimeWithInfo(PyTime_t *tp, _Py_clock_info_t *info) __attribute__((availability(macos, introduced=10.12))) __attribute__((availability(ios, introduced=10.0))) __attribute__((availability(tvos, introduced=10.0))) @@ -1512,7 +1512,7 @@ _PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) #endif static int -_PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) +_PyTime_GetThreadTimeWithInfo(PyTime_t *tp, _Py_clock_info_t *info) { struct timespec ts; const clockid_t clk_id = CLOCK_THREAD_CPUTIME_ID; @@ -1554,7 +1554,7 @@ _PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) static PyObject * time_thread_time(PyObject *self, PyObject *unused) { - _PyTime_t t; + PyTime_t t; if (_PyTime_GetThreadTimeWithInfo(&t, NULL) < 0) { return NULL; } @@ -1569,7 +1569,7 @@ Thread time for profiling: sum of the kernel and user-space CPU time."); static PyObject * time_thread_time_ns(PyObject *self, PyObject *unused) { - _PyTime_t t; + PyTime_t t; if (_PyTime_GetThreadTimeWithInfo(&t, NULL) < 0) { return NULL; } @@ -1595,7 +1595,7 @@ time_get_clock_info(PyObject *module, PyObject *args) char *name; _Py_clock_info_t info; PyObject *obj = NULL, *dict, *ns; - _PyTime_t t; + PyTime_t t; if (!PyArg_ParseTuple(args, "s:get_clock_info", &name)) { return NULL; @@ -2174,7 +2174,7 @@ PyInit_time(void) // On error, raise an exception and return -1. // On success, return 0. static int -pysleep(_PyTime_t timeout) +pysleep(PyTime_t timeout) { assert(timeout >= 0); @@ -2186,7 +2186,7 @@ pysleep(_PyTime_t timeout) #else struct timeval timeout_tv; #endif - _PyTime_t deadline, monotonic; + PyTime_t deadline, monotonic; int err = 0; if (get_monotonic(&monotonic) < 0) { @@ -2255,7 +2255,7 @@ pysleep(_PyTime_t timeout) return 0; #else // MS_WINDOWS - _PyTime_t timeout_100ns = _PyTime_As100Nanoseconds(timeout, + PyTime_t timeout_100ns = _PyTime_As100Nanoseconds(timeout, _PyTime_ROUND_CEILING); // Maintain Windows Sleep() semantics for time.sleep(0) diff --git a/Python/gc.c b/Python/gc.c index 8c2def1017bf2e3..b665e4681b3c39a 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -1285,7 +1285,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) PyGC_Head unreachable; /* non-problematic unreachable trash */ PyGC_Head finalizers; /* objects with, & reachable from, __del__ */ PyGC_Head *gc; - _PyTime_t t1 = 0; /* initialize to prevent a compiler warning */ + PyTime_t t1 = 0; /* initialize to prevent a compiler warning */ GCState *gcstate = &tstate->interp->gc; // gc_collect_main() must not be called before _PyGC_Init diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 2993ef4ac7818bc..0f159f4a272be6d 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -1071,7 +1071,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) int i; Py_ssize_t m = 0; /* # objects collected */ Py_ssize_t n = 0; /* # unreachable objects that couldn't be collected */ - _PyTime_t t1 = 0; /* initialize to prevent a compiler warning */ + PyTime_t t1 = 0; /* initialize to prevent a compiler warning */ GCState *gcstate = &tstate->interp->gc; // gc_collect_main() must not be called before _PyGC_Init diff --git a/Python/import.c b/Python/import.c index 2fd0c08a6bb5aec..6512963d9f6bed9 100644 --- a/Python/import.c +++ b/Python/import.c @@ -2719,7 +2719,7 @@ import_find_and_load(PyThreadState *tstate, PyObject *abs_name) #define import_level FIND_AND_LOAD(interp).import_level #define accumulated FIND_AND_LOAD(interp).accumulated - _PyTime_t t1 = 0, accumulated_copy = accumulated; + PyTime_t t1 = 0, accumulated_copy = accumulated; PyObject *sys_path = PySys_GetObject("path"); PyObject *sys_meta_path = PySys_GetObject("meta_path"); @@ -2762,7 +2762,7 @@ import_find_and_load(PyThreadState *tstate, PyObject *abs_name) mod != NULL); if (import_time) { - _PyTime_t cum = _PyTime_GetPerfCounter() - t1; + PyTime_t cum = _PyTime_GetPerfCounter() - t1; import_level--; fprintf(stderr, "import time: %9ld | %10ld | %*s%s\n", diff --git a/Python/lock.c b/Python/lock.c index bf0143654bd6921..0c0d298f17d54ff 100644 --- a/Python/lock.c +++ b/Python/lock.c @@ -16,7 +16,7 @@ // If a thread waits on a lock for longer than TIME_TO_BE_FAIR_NS (1 ms), then // the unlocking thread directly hands off ownership of the lock. This avoids // starvation. -static const _PyTime_t TIME_TO_BE_FAIR_NS = 1000*1000; +static const PyTime_t TIME_TO_BE_FAIR_NS = 1000*1000; // Spin for a bit before parking the thread. This is only enabled for // `--disable-gil` builds because it is unlikely to be helpful if the GIL is @@ -30,7 +30,7 @@ static const int MAX_SPIN_COUNT = 0; struct mutex_entry { // The time after which the unlocking thread should hand off lock ownership // directly to the waiting thread. Written by the waiting thread. - _PyTime_t time_to_be_fair; + PyTime_t time_to_be_fair; // Set to 1 if the lock was handed off. Written by the unlocking thread. int handed_off; @@ -53,7 +53,7 @@ _PyMutex_LockSlow(PyMutex *m) } PyLockStatus -_PyMutex_LockTimed(PyMutex *m, _PyTime_t timeout, _PyLockFlags flags) +_PyMutex_LockTimed(PyMutex *m, PyTime_t timeout, _PyLockFlags flags) { uint8_t v = _Py_atomic_load_uint8_relaxed(&m->v); if ((v & _Py_LOCKED) == 0) { @@ -65,8 +65,8 @@ _PyMutex_LockTimed(PyMutex *m, _PyTime_t timeout, _PyLockFlags flags) return PY_LOCK_FAILURE; } - _PyTime_t now = _PyTime_GetMonotonicClock(); - _PyTime_t endtime = 0; + PyTime_t now = _PyTime_GetMonotonicClock(); + PyTime_t endtime = 0; if (timeout > 0) { endtime = _PyTime_Add(now, timeout); } @@ -142,7 +142,7 @@ mutex_unpark(PyMutex *m, struct mutex_entry *entry, int has_more_waiters) { uint8_t v = 0; if (entry) { - _PyTime_t now = _PyTime_GetMonotonicClock(); + PyTime_t now = _PyTime_GetMonotonicClock(); int should_be_fair = now > entry->time_to_be_fair; entry->handed_off = should_be_fair; @@ -274,7 +274,7 @@ PyEvent_Wait(PyEvent *evt) } int -PyEvent_WaitTimed(PyEvent *evt, _PyTime_t timeout_ns) +PyEvent_WaitTimed(PyEvent *evt, PyTime_t timeout_ns) { for (;;) { uint8_t v = _Py_atomic_load_uint8(&evt->v); diff --git a/Python/parking_lot.c b/Python/parking_lot.c index 8ba50fc1353ebdd..9a8a403a746914f 100644 --- a/Python/parking_lot.c +++ b/Python/parking_lot.c @@ -91,7 +91,7 @@ _PySemaphore_Destroy(_PySemaphore *sema) } static int -_PySemaphore_PlatformWait(_PySemaphore *sema, _PyTime_t timeout) +_PySemaphore_PlatformWait(_PySemaphore *sema, PyTime_t timeout) { int res; #if defined(MS_WINDOWS) @@ -119,13 +119,13 @@ _PySemaphore_PlatformWait(_PySemaphore *sema, _PyTime_t timeout) struct timespec ts; #if defined(CLOCK_MONOTONIC) && defined(HAVE_SEM_CLOCKWAIT) - _PyTime_t deadline = _PyTime_Add(_PyTime_GetMonotonicClock(), timeout); + PyTime_t deadline = _PyTime_Add(_PyTime_GetMonotonicClock(), timeout); _PyTime_AsTimespec_clamp(deadline, &ts); err = sem_clockwait(&sema->platform_sem, CLOCK_MONOTONIC, &ts); #else - _PyTime_t deadline = _PyTime_Add(_PyTime_GetSystemClock(), timeout); + PyTime_t deadline = _PyTime_Add(_PyTime_GetSystemClock(), timeout); _PyTime_AsTimespec_clamp(deadline, &ts); @@ -162,7 +162,7 @@ _PySemaphore_PlatformWait(_PySemaphore *sema, _PyTime_t timeout) _PyTime_AsTimespec_clamp(timeout, &ts); err = pthread_cond_timedwait_relative_np(&sema->cond, &sema->mutex, &ts); #else - _PyTime_t deadline = _PyTime_Add(_PyTime_GetSystemClock(), timeout); + PyTime_t deadline = _PyTime_Add(_PyTime_GetSystemClock(), timeout); _PyTime_AsTimespec_clamp(deadline, &ts); err = pthread_cond_timedwait(&sema->cond, &sema->mutex, &ts); @@ -188,7 +188,7 @@ _PySemaphore_PlatformWait(_PySemaphore *sema, _PyTime_t timeout) } int -_PySemaphore_Wait(_PySemaphore *sema, _PyTime_t timeout, int detach) +_PySemaphore_Wait(_PySemaphore *sema, PyTime_t timeout, int detach) { PyThreadState *tstate = NULL; if (detach) { @@ -283,7 +283,7 @@ atomic_memcmp(const void *addr, const void *expected, size_t addr_size) int _PyParkingLot_Park(const void *addr, const void *expected, size_t size, - _PyTime_t timeout_ns, void *park_arg, int detach) + PyTime_t timeout_ns, void *park_arg, int detach) { struct wait_entry wait = { .park_arg = park_arg, diff --git a/Python/pystate.c b/Python/pystate.c index 4cd975a84d16455..d1d66e23f78d68c 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2072,7 +2072,7 @@ stop_the_world(struct _stoptheworld_state *stw) break; } - _PyTime_t wait_ns = 1000*1000; // 1ms (arbitrary, may need tuning) + PyTime_t wait_ns = 1000*1000; // 1ms (arbitrary, may need tuning) if (PyEvent_WaitTimed(&stw->stop_event, wait_ns)) { assert(stw->thread_countdown == 0); break; diff --git a/Python/pytime.c b/Python/pytime.c index 8b3c7128aae3bc3..f29337eb5364090 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -1,5 +1,5 @@ #include "Python.h" -#include "pycore_time.h" // _PyTime_t +#include "pycore_time.h" // PyTime_t #include <time.h> // gmtime_r() #ifdef HAVE_SYS_TIME_H @@ -51,18 +51,18 @@ #endif #if PyTime_MIN + PyTime_MAX != -1 -# error "_PyTime_t is not a two's complement integer type" +# error "PyTime_t is not a two's complement integer type" #endif -static _PyTime_t -_PyTime_GCD(_PyTime_t x, _PyTime_t y) +static PyTime_t +_PyTime_GCD(PyTime_t x, PyTime_t y) { // Euclidean algorithm assert(x >= 1); assert(y >= 1); while (y != 0) { - _PyTime_t tmp = y; + PyTime_t tmp = y; y = x % y; x = tmp; } @@ -72,13 +72,13 @@ _PyTime_GCD(_PyTime_t x, _PyTime_t y) int -_PyTimeFraction_Set(_PyTimeFraction *frac, _PyTime_t numer, _PyTime_t denom) +_PyTimeFraction_Set(_PyTimeFraction *frac, PyTime_t numer, PyTime_t denom) { if (numer < 1 || denom < 1) { return -1; } - _PyTime_t gcd = _PyTime_GCD(numer, denom); + PyTime_t gcd = _PyTime_GCD(numer, denom); frac->numer = numer / gcd; frac->denom = denom / gcd; return 0; @@ -104,29 +104,29 @@ static void pytime_overflow(void) { PyErr_SetString(PyExc_OverflowError, - "timestamp too large to convert to C _PyTime_t"); + "timestamp too large to convert to C PyTime_t"); } -static inline _PyTime_t -pytime_from_nanoseconds(_PyTime_t t) +static inline PyTime_t +pytime_from_nanoseconds(PyTime_t t) { - // _PyTime_t is a number of nanoseconds + // PyTime_t is a number of nanoseconds return t; } -static inline _PyTime_t -pytime_as_nanoseconds(_PyTime_t t) +static inline PyTime_t +pytime_as_nanoseconds(PyTime_t t) { - // _PyTime_t is a number of nanoseconds: see pytime_from_nanoseconds() + // PyTime_t is a number of nanoseconds: see pytime_from_nanoseconds() return t; } // Compute t1 + t2. Clamp to [PyTime_MIN; PyTime_MAX] on overflow. static inline int -pytime_add(_PyTime_t *t1, _PyTime_t t2) +pytime_add(PyTime_t *t1, PyTime_t t2) { if (t2 > 0 && *t1 > PyTime_MAX - t2) { *t1 = PyTime_MAX; @@ -143,8 +143,8 @@ pytime_add(_PyTime_t *t1, _PyTime_t t2) } -_PyTime_t -_PyTime_Add(_PyTime_t t1, _PyTime_t t2) +PyTime_t +_PyTime_Add(PyTime_t t1, PyTime_t t2) { (void)pytime_add(&t1, t2); return t1; @@ -152,7 +152,7 @@ _PyTime_Add(_PyTime_t t1, _PyTime_t t2) static inline int -pytime_mul_check_overflow(_PyTime_t a, _PyTime_t b) +pytime_mul_check_overflow(PyTime_t a, PyTime_t b) { if (b != 0) { assert(b > 0); @@ -166,7 +166,7 @@ pytime_mul_check_overflow(_PyTime_t a, _PyTime_t b) // Compute t * k. Clamp to [PyTime_MIN; PyTime_MAX] on overflow. static inline int -pytime_mul(_PyTime_t *t, _PyTime_t k) +pytime_mul(PyTime_t *t, PyTime_t k) { assert(k >= 0); if (pytime_mul_check_overflow(*t, k)) { @@ -181,19 +181,19 @@ pytime_mul(_PyTime_t *t, _PyTime_t k) // Compute t * k. Clamp to [PyTime_MIN; PyTime_MAX] on overflow. -static inline _PyTime_t -_PyTime_Mul(_PyTime_t t, _PyTime_t k) +static inline PyTime_t +_PyTime_Mul(PyTime_t t, PyTime_t k) { (void)pytime_mul(&t, k); return t; } -_PyTime_t -_PyTimeFraction_Mul(_PyTime_t ticks, const _PyTimeFraction *frac) +PyTime_t +_PyTimeFraction_Mul(PyTime_t ticks, const _PyTimeFraction *frac) { - const _PyTime_t mul = frac->numer; - const _PyTime_t div = frac->denom; + const PyTime_t mul = frac->numer; + const PyTime_t div = frac->denom; if (div == 1) { // Fast-path taken by mach_absolute_time() with 1/1 time base. @@ -205,7 +205,7 @@ _PyTimeFraction_Mul(_PyTime_t ticks, const _PyTimeFraction *frac) (ticks * mul) / div == (ticks / div) * mul + (ticks % div) * mul / div */ - _PyTime_t intpart, remaining; + PyTime_t intpart, remaining; intpart = ticks / div; ticks %= div; remaining = _PyTime_Mul(ticks, mul) / div; @@ -247,17 +247,17 @@ _PyLong_FromTime_t(time_t t) } -// Convert _PyTime_t to time_t. +// Convert PyTime_t to time_t. // Return 0 on success. Return -1 and clamp the value on overflow. static int -_PyTime_AsTime_t(_PyTime_t t, time_t *t2) +_PyTime_AsTime_t(PyTime_t t, time_t *t2) { #if SIZEOF_TIME_T < _SIZEOF_PYTIME_T - if ((_PyTime_t)PY_TIME_T_MAX < t) { + if ((PyTime_t)PY_TIME_T_MAX < t) { *t2 = PY_TIME_T_MAX; return -1; } - if (t < (_PyTime_t)PY_TIME_T_MIN) { + if (t < (PyTime_t)PY_TIME_T_MIN) { *t2 = PY_TIME_T_MIN; return -1; } @@ -268,17 +268,17 @@ _PyTime_AsTime_t(_PyTime_t t, time_t *t2) #ifdef MS_WINDOWS -// Convert _PyTime_t to long. +// Convert PyTime_t to long. // Return 0 on success. Return -1 and clamp the value on overflow. static int -_PyTime_AsLong(_PyTime_t t, long *t2) +_PyTime_AsLong(PyTime_t t, long *t2) { #if SIZEOF_LONG < _SIZEOF_PYTIME_T - if ((_PyTime_t)LONG_MAX < t) { + if ((PyTime_t)LONG_MAX < t) { *t2 = LONG_MAX; return -1; } - if (t < (_PyTime_t)LONG_MIN) { + if (t < (PyTime_t)LONG_MIN) { *t2 = LONG_MIN; return -1; } @@ -453,16 +453,16 @@ _PyTime_ObjectToTimeval(PyObject *obj, time_t *sec, long *usec, } -_PyTime_t +PyTime_t _PyTime_FromSeconds(int seconds) { /* ensure that integer overflow cannot happen, int type should have 32 - bits, whereas _PyTime_t type has at least 64 bits (SEC_TO_NS takes 30 + bits, whereas PyTime_t type has at least 64 bits (SEC_TO_NS takes 30 bits). */ - static_assert(INT_MAX <= PyTime_MAX / SEC_TO_NS, "_PyTime_t overflow"); - static_assert(INT_MIN >= PyTime_MIN / SEC_TO_NS, "_PyTime_t underflow"); + static_assert(INT_MAX <= PyTime_MAX / SEC_TO_NS, "PyTime_t overflow"); + static_assert(INT_MIN >= PyTime_MIN / SEC_TO_NS, "PyTime_t underflow"); - _PyTime_t t = (_PyTime_t)seconds; + PyTime_t t = (PyTime_t)seconds; assert((t >= 0 && t <= PyTime_MAX / SEC_TO_NS) || (t < 0 && t >= PyTime_MIN / SEC_TO_NS)); t *= SEC_TO_NS; @@ -470,23 +470,23 @@ _PyTime_FromSeconds(int seconds) } -_PyTime_t -_PyTime_FromNanoseconds(_PyTime_t ns) +PyTime_t +_PyTime_FromNanoseconds(PyTime_t ns) { return pytime_from_nanoseconds(ns); } -_PyTime_t -_PyTime_FromMicrosecondsClamp(_PyTime_t us) +PyTime_t +_PyTime_FromMicrosecondsClamp(PyTime_t us) { - _PyTime_t ns = _PyTime_Mul(us, US_TO_NS); + PyTime_t ns = _PyTime_Mul(us, US_TO_NS); return pytime_from_nanoseconds(ns); } int -_PyTime_FromNanosecondsObject(_PyTime_t *tp, PyObject *obj) +_PyTime_FromNanosecondsObject(PyTime_t *tp, PyObject *obj) { if (!PyLong_Check(obj)) { @@ -495,8 +495,8 @@ _PyTime_FromNanosecondsObject(_PyTime_t *tp, PyObject *obj) return -1; } - static_assert(sizeof(long long) == sizeof(_PyTime_t), - "_PyTime_t is not long long"); + static_assert(sizeof(long long) == sizeof(PyTime_t), + "PyTime_t is not long long"); long long nsec = PyLong_AsLongLong(obj); if (nsec == -1 && PyErr_Occurred()) { if (PyErr_ExceptionMatches(PyExc_OverflowError)) { @@ -505,7 +505,7 @@ _PyTime_FromNanosecondsObject(_PyTime_t *tp, PyObject *obj) return -1; } - _PyTime_t t = (_PyTime_t)nsec; + PyTime_t t = (PyTime_t)nsec; *tp = pytime_from_nanoseconds(t); return 0; } @@ -513,13 +513,13 @@ _PyTime_FromNanosecondsObject(_PyTime_t *tp, PyObject *obj) #ifdef HAVE_CLOCK_GETTIME static int -pytime_fromtimespec(_PyTime_t *tp, const struct timespec *ts, int raise_exc) +pytime_fromtimespec(PyTime_t *tp, const struct timespec *ts, int raise_exc) { - _PyTime_t t, tv_nsec; + PyTime_t t, tv_nsec; - static_assert(sizeof(ts->tv_sec) <= sizeof(_PyTime_t), - "timespec.tv_sec is larger than _PyTime_t"); - t = (_PyTime_t)ts->tv_sec; + static_assert(sizeof(ts->tv_sec) <= sizeof(PyTime_t), + "timespec.tv_sec is larger than PyTime_t"); + t = (PyTime_t)ts->tv_sec; int res1 = pytime_mul(&t, SEC_TO_NS); @@ -536,7 +536,7 @@ pytime_fromtimespec(_PyTime_t *tp, const struct timespec *ts, int raise_exc) } int -_PyTime_FromTimespec(_PyTime_t *tp, const struct timespec *ts) +_PyTime_FromTimespec(PyTime_t *tp, const struct timespec *ts) { return pytime_fromtimespec(tp, ts, 1); } @@ -545,15 +545,15 @@ _PyTime_FromTimespec(_PyTime_t *tp, const struct timespec *ts) #ifndef MS_WINDOWS static int -pytime_fromtimeval(_PyTime_t *tp, struct timeval *tv, int raise_exc) +pytime_fromtimeval(PyTime_t *tp, struct timeval *tv, int raise_exc) { - static_assert(sizeof(tv->tv_sec) <= sizeof(_PyTime_t), - "timeval.tv_sec is larger than _PyTime_t"); - _PyTime_t t = (_PyTime_t)tv->tv_sec; + static_assert(sizeof(tv->tv_sec) <= sizeof(PyTime_t), + "timeval.tv_sec is larger than PyTime_t"); + PyTime_t t = (PyTime_t)tv->tv_sec; int res1 = pytime_mul(&t, SEC_TO_NS); - _PyTime_t usec = (_PyTime_t)tv->tv_usec * US_TO_NS; + PyTime_t usec = (PyTime_t)tv->tv_usec * US_TO_NS; int res2 = pytime_add(&t, usec); *tp = pytime_from_nanoseconds(t); @@ -567,7 +567,7 @@ pytime_fromtimeval(_PyTime_t *tp, struct timeval *tv, int raise_exc) int -_PyTime_FromTimeval(_PyTime_t *tp, struct timeval *tv) +_PyTime_FromTimeval(PyTime_t *tp, struct timeval *tv) { return pytime_fromtimeval(tp, tv, 1); } @@ -575,7 +575,7 @@ _PyTime_FromTimeval(_PyTime_t *tp, struct timeval *tv) static int -pytime_from_double(_PyTime_t *tp, double value, _PyTime_round_t round, +pytime_from_double(PyTime_t *tp, double value, _PyTime_round_t round, long unit_to_ns) { /* volatile avoids optimization changing how numbers are rounded */ @@ -591,7 +591,7 @@ pytime_from_double(_PyTime_t *tp, double value, _PyTime_round_t round, pytime_time_t_overflow(); return -1; } - _PyTime_t ns = (_PyTime_t)d; + PyTime_t ns = (PyTime_t)d; *tp = pytime_from_nanoseconds(ns); return 0; @@ -599,7 +599,7 @@ pytime_from_double(_PyTime_t *tp, double value, _PyTime_round_t round, static int -pytime_from_object(_PyTime_t *tp, PyObject *obj, _PyTime_round_t round, +pytime_from_object(PyTime_t *tp, PyObject *obj, _PyTime_round_t round, long unit_to_ns) { if (PyFloat_Check(obj)) { @@ -620,9 +620,9 @@ pytime_from_object(_PyTime_t *tp, PyObject *obj, _PyTime_round_t round, return -1; } - static_assert(sizeof(long long) <= sizeof(_PyTime_t), - "_PyTime_t is smaller than long long"); - _PyTime_t ns = (_PyTime_t)sec; + static_assert(sizeof(long long) <= sizeof(PyTime_t), + "PyTime_t is smaller than long long"); + PyTime_t ns = (PyTime_t)sec; if (pytime_mul(&ns, unit_to_ns) < 0) { pytime_overflow(); return -1; @@ -635,14 +635,14 @@ pytime_from_object(_PyTime_t *tp, PyObject *obj, _PyTime_round_t round, int -_PyTime_FromSecondsObject(_PyTime_t *tp, PyObject *obj, _PyTime_round_t round) +_PyTime_FromSecondsObject(PyTime_t *tp, PyObject *obj, _PyTime_round_t round) { return pytime_from_object(tp, obj, round, SEC_TO_NS); } int -_PyTime_FromMillisecondsObject(_PyTime_t *tp, PyObject *obj, _PyTime_round_t round) +_PyTime_FromMillisecondsObject(PyTime_t *tp, PyObject *obj, _PyTime_round_t round) { return pytime_from_object(tp, obj, round, MS_TO_NS); } @@ -658,7 +658,7 @@ PyTime_AsSecondsDouble(PyTime_t t) if (ns % SEC_TO_NS == 0) { /* Divide using integers to avoid rounding issues on the integer part. 1e-9 cannot be stored exactly in IEEE 64-bit. */ - _PyTime_t secs = ns / SEC_TO_NS; + PyTime_t secs = ns / SEC_TO_NS; d = (double)secs; } else { @@ -670,18 +670,18 @@ PyTime_AsSecondsDouble(PyTime_t t) PyObject * -_PyTime_AsNanosecondsObject(_PyTime_t t) +_PyTime_AsNanosecondsObject(PyTime_t t) { - _PyTime_t ns = pytime_as_nanoseconds(t); - static_assert(sizeof(long long) >= sizeof(_PyTime_t), - "_PyTime_t is larger than long long"); + PyTime_t ns = pytime_as_nanoseconds(t); + static_assert(sizeof(long long) >= sizeof(PyTime_t), + "PyTime_t is larger than long long"); return PyLong_FromLongLong((long long)ns); } -_PyTime_t +PyTime_t _PyTime_FromSecondsDouble(double seconds, _PyTime_round_t round) { - _PyTime_t tp; + PyTime_t tp; if(pytime_from_double(&tp, seconds, round, SEC_TO_NS) < 0) { return -1; } @@ -689,14 +689,14 @@ _PyTime_FromSecondsDouble(double seconds, _PyTime_round_t round) } -static _PyTime_t -pytime_divide_round_up(const _PyTime_t t, const _PyTime_t k) +static PyTime_t +pytime_divide_round_up(const PyTime_t t, const PyTime_t k) { assert(k > 1); if (t >= 0) { // Don't use (t + k - 1) / k to avoid integer overflow // if t is equal to PyTime_MAX - _PyTime_t q = t / k; + PyTime_t q = t / k; if (t % k) { q += 1; } @@ -705,7 +705,7 @@ pytime_divide_round_up(const _PyTime_t t, const _PyTime_t k) else { // Don't use (t - (k - 1)) / k to avoid integer overflow // if t is equals to PyTime_MIN. - _PyTime_t q = t / k; + PyTime_t q = t / k; if (t % k) { q -= 1; } @@ -714,15 +714,15 @@ pytime_divide_round_up(const _PyTime_t t, const _PyTime_t k) } -static _PyTime_t -pytime_divide(const _PyTime_t t, const _PyTime_t k, +static PyTime_t +pytime_divide(const PyTime_t t, const PyTime_t k, const _PyTime_round_t round) { assert(k > 1); if (round == _PyTime_ROUND_HALF_EVEN) { - _PyTime_t x = t / k; - _PyTime_t r = t % k; - _PyTime_t abs_r = Py_ABS(r); + PyTime_t x = t / k; + PyTime_t r = t % k; + PyTime_t abs_r = Py_ABS(r); if (abs_r > k / 2 || (abs_r == k / 2 && (Py_ABS(x) & 1))) { if (t >= 0) { x++; @@ -761,12 +761,12 @@ pytime_divide(const _PyTime_t t, const _PyTime_t k, // Return 0 on success. // Return -1 on underflow and store (PyTime_MIN, 0) in (pq, pr). static int -pytime_divmod(const _PyTime_t t, const _PyTime_t k, - _PyTime_t *pq, _PyTime_t *pr) +pytime_divmod(const PyTime_t t, const PyTime_t k, + PyTime_t *pq, PyTime_t *pr) { assert(k > 1); - _PyTime_t q = t / k; - _PyTime_t r = t % k; + PyTime_t q = t / k; + PyTime_t r = t % k; if (r < 0) { if (q == PyTime_MIN) { *pq = PyTime_MIN; @@ -785,39 +785,39 @@ pytime_divmod(const _PyTime_t t, const _PyTime_t k, #ifdef MS_WINDOWS -_PyTime_t -_PyTime_As100Nanoseconds(_PyTime_t t, _PyTime_round_t round) +PyTime_t +_PyTime_As100Nanoseconds(PyTime_t t, _PyTime_round_t round) { - _PyTime_t ns = pytime_as_nanoseconds(t); + PyTime_t ns = pytime_as_nanoseconds(t); return pytime_divide(ns, NS_TO_100NS, round); } #endif -_PyTime_t -_PyTime_AsMicroseconds(_PyTime_t t, _PyTime_round_t round) +PyTime_t +_PyTime_AsMicroseconds(PyTime_t t, _PyTime_round_t round) { - _PyTime_t ns = pytime_as_nanoseconds(t); + PyTime_t ns = pytime_as_nanoseconds(t); return pytime_divide(ns, NS_TO_US, round); } -_PyTime_t -_PyTime_AsMilliseconds(_PyTime_t t, _PyTime_round_t round) +PyTime_t +_PyTime_AsMilliseconds(PyTime_t t, _PyTime_round_t round) { - _PyTime_t ns = pytime_as_nanoseconds(t); + PyTime_t ns = pytime_as_nanoseconds(t); return pytime_divide(ns, NS_TO_MS, round); } static int -pytime_as_timeval(_PyTime_t t, _PyTime_t *ptv_sec, int *ptv_usec, +pytime_as_timeval(PyTime_t t, PyTime_t *ptv_sec, int *ptv_usec, _PyTime_round_t round) { - _PyTime_t ns = pytime_as_nanoseconds(t); - _PyTime_t us = pytime_divide(ns, US_TO_NS, round); + PyTime_t ns = pytime_as_nanoseconds(t); + PyTime_t us = pytime_divide(ns, US_TO_NS, round); - _PyTime_t tv_sec, tv_usec; + PyTime_t tv_sec, tv_usec; int res = pytime_divmod(us, SEC_TO_US, &tv_sec, &tv_usec); *ptv_sec = tv_sec; *ptv_usec = (int)tv_usec; @@ -826,10 +826,10 @@ pytime_as_timeval(_PyTime_t t, _PyTime_t *ptv_sec, int *ptv_usec, static int -pytime_as_timeval_struct(_PyTime_t t, struct timeval *tv, +pytime_as_timeval_struct(PyTime_t t, struct timeval *tv, _PyTime_round_t round, int raise_exc) { - _PyTime_t tv_sec; + PyTime_t tv_sec; int tv_usec; int res = pytime_as_timeval(t, &tv_sec, &tv_usec, round); int res2; @@ -853,24 +853,24 @@ pytime_as_timeval_struct(_PyTime_t t, struct timeval *tv, int -_PyTime_AsTimeval(_PyTime_t t, struct timeval *tv, _PyTime_round_t round) +_PyTime_AsTimeval(PyTime_t t, struct timeval *tv, _PyTime_round_t round) { return pytime_as_timeval_struct(t, tv, round, 1); } void -_PyTime_AsTimeval_clamp(_PyTime_t t, struct timeval *tv, _PyTime_round_t round) +_PyTime_AsTimeval_clamp(PyTime_t t, struct timeval *tv, _PyTime_round_t round) { (void)pytime_as_timeval_struct(t, tv, round, 0); } int -_PyTime_AsTimevalTime_t(_PyTime_t t, time_t *p_secs, int *us, +_PyTime_AsTimevalTime_t(PyTime_t t, time_t *p_secs, int *us, _PyTime_round_t round) { - _PyTime_t secs; + PyTime_t secs; if (pytime_as_timeval(t, &secs, us, round) < 0) { pytime_time_t_overflow(); return -1; @@ -886,10 +886,10 @@ _PyTime_AsTimevalTime_t(_PyTime_t t, time_t *p_secs, int *us, #if defined(HAVE_CLOCK_GETTIME) || defined(HAVE_KQUEUE) static int -pytime_as_timespec(_PyTime_t t, struct timespec *ts, int raise_exc) +pytime_as_timespec(PyTime_t t, struct timespec *ts, int raise_exc) { - _PyTime_t ns = pytime_as_nanoseconds(t); - _PyTime_t tv_sec, tv_nsec; + PyTime_t ns = pytime_as_nanoseconds(t); + PyTime_t tv_sec, tv_nsec; int res = pytime_divmod(ns, SEC_TO_NS, &tv_sec, &tv_nsec); int res2 = _PyTime_AsTime_t(tv_sec, &ts->tv_sec); @@ -906,13 +906,13 @@ pytime_as_timespec(_PyTime_t t, struct timespec *ts, int raise_exc) } void -_PyTime_AsTimespec_clamp(_PyTime_t t, struct timespec *ts) +_PyTime_AsTimespec_clamp(PyTime_t t, struct timespec *ts) { (void)pytime_as_timespec(t, ts, 0); } int -_PyTime_AsTimespec(_PyTime_t t, struct timespec *ts) +_PyTime_AsTimespec(PyTime_t t, struct timespec *ts) { return pytime_as_timespec(t, ts, 1); } @@ -921,7 +921,7 @@ _PyTime_AsTimespec(_PyTime_t t, struct timespec *ts) // N.B. If raise_exc=0, this may be called without the GIL. static int -py_get_system_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) +py_get_system_clock(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) { assert(info == NULL || raise_exc); @@ -935,7 +935,7 @@ py_get_system_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) /* 11,644,473,600,000,000,000: number of nanoseconds between the 1st january 1601 and the 1st january 1970 (369 years + 89 leap days). */ - _PyTime_t ns = large.QuadPart * 100 - 11644473600000000000; + PyTime_t ns = large.QuadPart * 100 - 11644473600000000000; *tp = pytime_from_nanoseconds(ns); if (info) { DWORD timeAdjustment, timeIncrement; @@ -1031,10 +1031,10 @@ py_get_system_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) } -_PyTime_t +PyTime_t _PyTime_GetSystemClock(void) { - _PyTime_t t; + PyTime_t t; if (py_get_system_clock(&t, NULL, 0) < 0) { // If clock_gettime(CLOCK_REALTIME) or gettimeofday() fails: // silently ignore the failure and return 0. @@ -1057,7 +1057,7 @@ PyTime_Time(PyTime_t *result) } int -_PyTime_GetSystemClockWithInfo(_PyTime_t *t, _Py_clock_info_t *info) +_PyTime_GetSystemClockWithInfo(PyTime_t *t, _Py_clock_info_t *info) { return py_get_system_clock(t, info, 1); } @@ -1073,13 +1073,13 @@ py_mach_timebase_info(_PyTimeFraction *base, int raise) (void)mach_timebase_info(&timebase); // Check that timebase.numer and timebase.denom can be casted to - // _PyTime_t. In practice, timebase uses uint32_t, so casting cannot + // PyTime_t. In practice, timebase uses uint32_t, so casting cannot // overflow. At the end, only make sure that the type is uint32_t - // (_PyTime_t is 64-bit long). - Py_BUILD_ASSERT(sizeof(timebase.numer) <= sizeof(_PyTime_t)); - Py_BUILD_ASSERT(sizeof(timebase.denom) <= sizeof(_PyTime_t)); - _PyTime_t numer = (_PyTime_t)timebase.numer; - _PyTime_t denom = (_PyTime_t)timebase.denom; + // (PyTime_t is 64-bit long). + Py_BUILD_ASSERT(sizeof(timebase.numer) <= sizeof(PyTime_t)); + Py_BUILD_ASSERT(sizeof(timebase.denom) <= sizeof(PyTime_t)); + PyTime_t numer = (PyTime_t)timebase.numer; + PyTime_t denom = (PyTime_t)timebase.denom; // Known time bases: // @@ -1100,21 +1100,21 @@ py_mach_timebase_info(_PyTimeFraction *base, int raise) // N.B. If raise_exc=0, this may be called without the GIL. static int -py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) +py_get_monotonic_clock(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) { assert(info == NULL || raise_exc); #if defined(MS_WINDOWS) ULONGLONG ticks = GetTickCount64(); - static_assert(sizeof(ticks) <= sizeof(_PyTime_t), - "ULONGLONG is larger than _PyTime_t"); - _PyTime_t t; + static_assert(sizeof(ticks) <= sizeof(PyTime_t), + "ULONGLONG is larger than PyTime_t"); + PyTime_t t; if (ticks <= (ULONGLONG)PyTime_MAX) { - t = (_PyTime_t)ticks; + t = (PyTime_t)ticks; } else { - // GetTickCount64() maximum is larger than _PyTime_t maximum: - // ULONGLONG is unsigned, whereas _PyTime_t is signed. + // GetTickCount64() maximum is larger than PyTime_t maximum: + // ULONGLONG is unsigned, whereas PyTime_t is signed. t = PyTime_MAX; } @@ -1159,9 +1159,9 @@ py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) uint64_t uticks = mach_absolute_time(); // unsigned => signed assert(uticks <= (uint64_t)PyTime_MAX); - _PyTime_t ticks = (_PyTime_t)uticks; + PyTime_t ticks = (PyTime_t)uticks; - _PyTime_t ns = _PyTimeFraction_Mul(ticks, &base); + PyTime_t ns = _PyTimeFraction_Mul(ticks, &base); *tp = pytime_from_nanoseconds(ns); #elif defined(__hpux) @@ -1223,10 +1223,10 @@ py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) } -_PyTime_t +PyTime_t _PyTime_GetMonotonicClock(void) { - _PyTime_t t; + PyTime_t t; if (py_get_monotonic_clock(&t, NULL, 0) < 0) { // If mach_timebase_info(), clock_gettime() or gethrtime() fails: // silently ignore the failure and return 0. @@ -1248,7 +1248,7 @@ PyTime_Monotonic(PyTime_t *result) int -_PyTime_GetMonotonicClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) +_PyTime_GetMonotonicClockWithInfo(PyTime_t *tp, _Py_clock_info_t *info) { return py_get_monotonic_clock(tp, info, 1); } @@ -1268,8 +1268,8 @@ py_win_perf_counter_frequency(_PyTimeFraction *base, int raise) // Since Windows XP, frequency cannot be zero. assert(frequency >= 1); - Py_BUILD_ASSERT(sizeof(_PyTime_t) == sizeof(frequency)); - _PyTime_t denom = (_PyTime_t)frequency; + Py_BUILD_ASSERT(sizeof(PyTime_t) == sizeof(frequency)); + PyTime_t denom = (PyTime_t)frequency; // Known QueryPerformanceFrequency() values: // @@ -1288,7 +1288,7 @@ py_win_perf_counter_frequency(_PyTimeFraction *base, int raise) // N.B. If raise_exc=0, this may be called without the GIL. static int -py_get_win_perf_counter(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) +py_get_win_perf_counter(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) { assert(info == NULL || raise_exc); @@ -1310,14 +1310,14 @@ py_get_win_perf_counter(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) QueryPerformanceCounter(&now); LONGLONG ticksll = now.QuadPart; - /* Make sure that casting LONGLONG to _PyTime_t cannot overflow, + /* Make sure that casting LONGLONG to PyTime_t cannot overflow, both types are signed */ - _PyTime_t ticks; + PyTime_t ticks; static_assert(sizeof(ticksll) <= sizeof(ticks), - "LONGLONG is larger than _PyTime_t"); - ticks = (_PyTime_t)ticksll; + "LONGLONG is larger than PyTime_t"); + ticks = (PyTime_t)ticksll; - _PyTime_t ns = _PyTimeFraction_Mul(ticks, &base); + PyTime_t ns = _PyTimeFraction_Mul(ticks, &base); *tp = pytime_from_nanoseconds(ns); return 0; } @@ -1325,7 +1325,7 @@ py_get_win_perf_counter(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) int -_PyTime_GetPerfCounterWithInfo(_PyTime_t *t, _Py_clock_info_t *info) +_PyTime_GetPerfCounterWithInfo(PyTime_t *t, _Py_clock_info_t *info) { #ifdef MS_WINDOWS return py_get_win_perf_counter(t, info, 1); @@ -1335,10 +1335,10 @@ _PyTime_GetPerfCounterWithInfo(_PyTime_t *t, _Py_clock_info_t *info) } -_PyTime_t +PyTime_t _PyTime_GetPerfCounter(void) { - _PyTime_t t; + PyTime_t t; int res; #ifdef MS_WINDOWS res = py_get_win_perf_counter(&t, NULL, 0); @@ -1440,17 +1440,17 @@ _PyTime_gmtime(time_t t, struct tm *tm) } -_PyTime_t -_PyDeadline_Init(_PyTime_t timeout) +PyTime_t +_PyDeadline_Init(PyTime_t timeout) { - _PyTime_t now = _PyTime_GetMonotonicClock(); + PyTime_t now = _PyTime_GetMonotonicClock(); return _PyTime_Add(now, timeout); } -_PyTime_t -_PyDeadline_Get(_PyTime_t deadline) +PyTime_t +_PyDeadline_Get(PyTime_t deadline) { - _PyTime_t now = _PyTime_GetMonotonicClock(); + PyTime_t now = _PyTime_GetMonotonicClock(); return deadline - now; } diff --git a/Python/thread.c b/Python/thread.c index fefae8391617f7d..cafcaa0b9acbb13 100644 --- a/Python/thread.c +++ b/Python/thread.c @@ -107,7 +107,7 @@ PyThread_ParseTimeoutArg(PyObject *arg, int blocking, PY_TIMEOUT_T *timeout_p) return -1; } - _PyTime_t timeout; + PyTime_t timeout; if (_PyTime_FromSecondsObject(&timeout, arg, _PyTime_ROUND_TIMEOUT) < 0) { return -1; } @@ -132,14 +132,14 @@ PyThread_acquire_lock_timed_with_retries(PyThread_type_lock lock, PY_TIMEOUT_T timeout) { PyThreadState *tstate = _PyThreadState_GET(); - _PyTime_t endtime = 0; + PyTime_t endtime = 0; if (timeout > 0) { endtime = _PyDeadline_Init(timeout); } PyLockStatus r; do { - _PyTime_t microseconds; + PyTime_t microseconds; microseconds = _PyTime_AsMicroseconds(timeout, _PyTime_ROUND_CEILING); /* first a simple non-blocking try without releasing the GIL */ diff --git a/Python/thread_nt.h b/Python/thread_nt.h index ad467e0e7840e7b..9ca2a55cae5eef8 100644 --- a/Python/thread_nt.h +++ b/Python/thread_nt.h @@ -76,10 +76,10 @@ EnterNonRecursiveMutex(PNRMUTEX mutex, DWORD milliseconds) } } else if (milliseconds != 0) { /* wait at least until the deadline */ - _PyTime_t nanoseconds = _PyTime_FromNanoseconds((_PyTime_t)milliseconds * 1000000); - _PyTime_t deadline = _PyTime_Add(_PyTime_GetPerfCounter(), nanoseconds); + PyTime_t nanoseconds = _PyTime_FromNanoseconds((PyTime_t)milliseconds * 1000000); + PyTime_t deadline = _PyTime_Add(_PyTime_GetPerfCounter(), nanoseconds); while (mutex->locked) { - _PyTime_t microseconds = _PyTime_AsMicroseconds(nanoseconds, + PyTime_t microseconds = _PyTime_AsMicroseconds(nanoseconds, _PyTime_ROUND_TIMEOUT); if (PyCOND_TIMEDWAIT(&mutex->cv, &mutex->cs, microseconds) < 0) { result = WAIT_FAILED; diff --git a/Python/thread_pthread.h b/Python/thread_pthread.h index 556e3de0b071f81..ce0af736e8f7e45 100644 --- a/Python/thread_pthread.h +++ b/Python/thread_pthread.h @@ -149,8 +149,8 @@ _PyThread_cond_init(PyCOND_T *cond) void _PyThread_cond_after(long long us, struct timespec *abs) { - _PyTime_t timeout = _PyTime_FromMicrosecondsClamp(us); - _PyTime_t t; + PyTime_t timeout = _PyTime_FromMicrosecondsClamp(us); + PyTime_t t; #ifdef CONDATTR_MONOTONIC if (condattr_monotonic) { t = _PyTime_GetMonotonicClock(); @@ -481,7 +481,7 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds, (void) error; /* silence unused-but-set-variable warning */ - _PyTime_t timeout; // relative timeout + PyTime_t timeout; // relative timeout if (microseconds >= 0) { // bpo-41710: PyThread_acquire_lock_timed() cannot report timeout // overflow to the caller, so clamp the timeout to @@ -501,11 +501,11 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds, struct timespec abs_timeout; // Local scope for deadline { - _PyTime_t deadline = _PyTime_Add(_PyTime_GetMonotonicClock(), timeout); + PyTime_t deadline = _PyTime_Add(_PyTime_GetMonotonicClock(), timeout); _PyTime_AsTimespec_clamp(deadline, &abs_timeout); } #else - _PyTime_t deadline = 0; + PyTime_t deadline = 0; if (timeout > 0 && !intr_flag) { deadline = _PyDeadline_Init(timeout); } @@ -517,7 +517,7 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds, status = fix_status(sem_clockwait(thelock, CLOCK_MONOTONIC, &abs_timeout)); #else - _PyTime_t abs_time = _PyTime_Add(_PyTime_GetSystemClock(), + PyTime_t abs_time = _PyTime_Add(_PyTime_GetSystemClock(), timeout); struct timespec ts; _PyTime_AsTimespec_clamp(abs_time, &ts); From c0b0c2f2015fb27db4306109b2b3781eb2057c2b Mon Sep 17 00:00:00 2001 From: Eugene Toder <eltoder@users.noreply.github.com> Date: Tue, 20 Feb 2024 10:14:34 -0500 Subject: [PATCH 376/507] gh-101860: Expose __name__ on property (GH-101876) Useful for introspection and consistent with functions and other descriptors. --- Doc/howto/descriptor.rst | 19 ++++- Lib/inspect.py | 5 +- Lib/pydoc.py | 5 +- Lib/test/test_inspect/inspect_fodder.py | 4 +- Lib/test/test_property.py | 53 ++++++++++++++ Lib/test/test_pydoc/test_pydoc.py | 23 ++++-- ...-02-13-11-36-50.gh-issue-101860.CKCMbC.rst | 1 + Objects/descrobject.c | 73 +++++++++++++++++-- 8 files changed, 158 insertions(+), 25 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-02-13-11-36-50.gh-issue-101860.CKCMbC.rst diff --git a/Doc/howto/descriptor.rst b/Doc/howto/descriptor.rst index e72386a4da4f8a4..51f9f4a6556e576 100644 --- a/Doc/howto/descriptor.rst +++ b/Doc/howto/descriptor.rst @@ -1004,31 +1004,42 @@ here is a pure Python equivalent: if doc is None and fget is not None: doc = fget.__doc__ self.__doc__ = doc - self._name = '' + self._name = None def __set_name__(self, owner, name): self._name = name + @property + def __name__(self): + return self._name if self._name is not None else self.fget.__name__ + + @__name__.setter + def __name__(self, value): + self._name = value + def __get__(self, obj, objtype=None): if obj is None: return self if self.fget is None: raise AttributeError( - f'property {self._name!r} of {type(obj).__name__!r} object has no getter' + f'property {self.__name__!r} of {type(obj).__name__!r} ' + 'object has no getter' ) return self.fget(obj) def __set__(self, obj, value): if self.fset is None: raise AttributeError( - f'property {self._name!r} of {type(obj).__name__!r} object has no setter' + f'property {self.__name__!r} of {type(obj).__name__!r} ' + 'object has no setter' ) self.fset(obj, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError( - f'property {self._name!r} of {type(obj).__name__!r} object has no deleter' + f'property {self.__name__!r} of {type(obj).__name__!r} ' + 'object has no deleter' ) self.fdel(obj) diff --git a/Lib/inspect.py b/Lib/inspect.py index 450093a8b4c1ee6..da504037ac282c9 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -834,9 +834,8 @@ def _finddoc(obj): cls = self.__class__ # Should be tested before isdatadescriptor(). elif isinstance(obj, property): - func = obj.fget - name = func.__name__ - cls = _findclass(func) + name = obj.__name__ + cls = _findclass(obj.fget) if cls is None or getattr(cls, name) is not obj: return None elif ismethoddescriptor(obj) or isdatadescriptor(obj): diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 9bb64feca8f93e8..d32fa8d05044177 100755 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -127,9 +127,8 @@ def _finddoc(obj): cls = self.__class__ # Should be tested before isdatadescriptor(). elif isinstance(obj, property): - func = obj.fget - name = func.__name__ - cls = _findclass(func) + name = obj.__name__ + cls = _findclass(obj.fget) if cls is None or getattr(cls, name) is not obj: return None elif inspect.ismethoddescriptor(obj) or inspect.isdatadescriptor(obj): diff --git a/Lib/test/test_inspect/inspect_fodder.py b/Lib/test/test_inspect/inspect_fodder.py index 60ba7aa78394e87..febd54c86fe1d19 100644 --- a/Lib/test/test_inspect/inspect_fodder.py +++ b/Lib/test/test_inspect/inspect_fodder.py @@ -68,9 +68,9 @@ class FesteringGob(MalodorousPervert, ParrotDroppings): def abuse(self, a, b, c): pass - @property - def contradiction(self): + def _getter(self): pass + contradiction = property(_getter) async def lobbest(grenade): pass diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index ad5ab5a87b5a663..408e64f53142db1 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -201,6 +201,59 @@ def test_gh_115618(self): self.assertIsNone(prop.fdel) self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10) + def test_property_name(self): + def getter(self): + return 42 + + def setter(self, value): + pass + + class A: + @property + def foo(self): + return 1 + + @foo.setter + def oof(self, value): + pass + + bar = property(getter) + baz = property(None, setter) + + self.assertEqual(A.foo.__name__, 'foo') + self.assertEqual(A.oof.__name__, 'oof') + self.assertEqual(A.bar.__name__, 'bar') + self.assertEqual(A.baz.__name__, 'baz') + + A.quux = property(getter) + self.assertEqual(A.quux.__name__, 'getter') + A.quux.__name__ = 'myquux' + self.assertEqual(A.quux.__name__, 'myquux') + self.assertEqual(A.bar.__name__, 'bar') # not affected + A.quux.__name__ = None + self.assertIsNone(A.quux.__name__) + + with self.assertRaisesRegex( + AttributeError, "'property' object has no attribute '__name__'" + ): + property(None, setter).__name__ + + with self.assertRaisesRegex( + AttributeError, "'property' object has no attribute '__name__'" + ): + property(1).__name__ + + class Err: + def __getattr__(self, attr): + raise RuntimeError('fail') + + p = property(Err()) + with self.assertRaisesRegex(RuntimeError, 'fail'): + p.__name__ + + p.__name__ = 'not_fail' + self.assertEqual(p.__name__, 'not_fail') + def test_property_set_name_incorrect_args(self): p = property() diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index d7a333a1103eaca..b07d9119e494017 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -1162,6 +1162,17 @@ def test_importfile(self): self.assertEqual(loaded_pydoc.__spec__, pydoc.__spec__) +class Rect: + @property + def area(self): + '''Area of the rect''' + return self.w * self.h + + +class Square(Rect): + area = property(lambda self: self.side**2) + + class TestDescriptions(unittest.TestCase): def test_module(self): @@ -1550,13 +1561,13 @@ def test_namedtuple_field_descriptor(self): @requires_docstrings def test_property(self): - class Rect: - @property - def area(self): - '''Area of the rect''' - return self.w * self.h - self.assertEqual(self._get_summary_lines(Rect.area), """\ +area + Area of the rect +""") + # inherits the docstring from Rect.area + self.assertEqual(self._get_summary_lines(Square.area), """\ +area Area of the rect """) self.assertIn(""" diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-02-13-11-36-50.gh-issue-101860.CKCMbC.rst b/Misc/NEWS.d/next/Core and Builtins/2023-02-13-11-36-50.gh-issue-101860.CKCMbC.rst new file mode 100644 index 000000000000000..5a2743534669737 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-02-13-11-36-50.gh-issue-101860.CKCMbC.rst @@ -0,0 +1 @@ +Expose ``__name__`` attribute on property. diff --git a/Objects/descrobject.c b/Objects/descrobject.c index c4cd51bdae45ab5..df546a090c28e45 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1519,22 +1519,34 @@ class property(object): self.__doc__ = doc except AttributeError: # read-only or dict-less class pass + self.__name = None + + def __set_name__(self, owner, name): + self.__name = name + + @property + def __name__(self): + return self.__name if self.__name is not None else self.fget.__name__ + + @__name__.setter + def __name__(self, value): + self.__name = value def __get__(self, inst, type=None): if inst is None: return self if self.__get is None: - raise AttributeError, "property has no getter" + raise AttributeError("property has no getter") return self.__get(inst) def __set__(self, inst, value): if self.__set is None: - raise AttributeError, "property has no setter" + raise AttributeError("property has no setter") return self.__set(inst, value) def __delete__(self, inst): if self.__del is None: - raise AttributeError, "property has no deleter" + raise AttributeError("property has no deleter") return self.__del(inst) */ @@ -1628,6 +1640,20 @@ property_dealloc(PyObject *self) Py_TYPE(self)->tp_free(self); } +static int +property_name(propertyobject *prop, PyObject **name) +{ + if (prop->prop_name != NULL) { + *name = Py_NewRef(prop->prop_name); + return 1; + } + if (prop->prop_get == NULL) { + *name = NULL; + return 0; + } + return PyObject_GetOptionalAttr(prop->prop_get, &_Py_ID(__name__), name); +} + static PyObject * property_descr_get(PyObject *self, PyObject *obj, PyObject *type) { @@ -1637,11 +1663,15 @@ property_descr_get(PyObject *self, PyObject *obj, PyObject *type) propertyobject *gs = (propertyobject *)self; if (gs->prop_get == NULL) { + PyObject *propname; + if (property_name(gs, &propname) < 0) { + return NULL; + } PyObject *qualname = PyType_GetQualName(Py_TYPE(obj)); - if (gs->prop_name != NULL && qualname != NULL) { + if (propname != NULL && qualname != NULL) { PyErr_Format(PyExc_AttributeError, "property %R of %R object has no getter", - gs->prop_name, + propname, qualname); } else if (qualname != NULL) { @@ -1652,6 +1682,7 @@ property_descr_get(PyObject *self, PyObject *obj, PyObject *type) PyErr_SetString(PyExc_AttributeError, "property has no getter"); } + Py_XDECREF(propname); Py_XDECREF(qualname); return NULL; } @@ -1673,16 +1704,20 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value) } if (func == NULL) { + PyObject *propname; + if (property_name(gs, &propname) < 0) { + return -1; + } PyObject *qualname = NULL; if (obj != NULL) { qualname = PyType_GetQualName(Py_TYPE(obj)); } - if (gs->prop_name != NULL && qualname != NULL) { + if (propname != NULL && qualname != NULL) { PyErr_Format(PyExc_AttributeError, value == NULL ? "property %R of %R object has no deleter" : "property %R of %R object has no setter", - gs->prop_name, + propname, qualname); } else if (qualname != NULL) { @@ -1698,6 +1733,7 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value) "property has no deleter" : "property has no setter"); } + Py_XDECREF(propname); Py_XDECREF(qualname); return -1; } @@ -1883,6 +1919,28 @@ property_init_impl(propertyobject *self, PyObject *fget, PyObject *fset, return 0; } +static PyObject * +property_get__name__(propertyobject *prop, void *Py_UNUSED(ignored)) +{ + PyObject *name; + if (property_name(prop, &name) < 0) { + return NULL; + } + if (name == NULL) { + PyErr_SetString(PyExc_AttributeError, + "'property' object has no attribute '__name__'"); + } + return name; +} + +static int +property_set__name__(propertyobject *prop, PyObject *value, + void *Py_UNUSED(ignored)) +{ + Py_XSETREF(prop->prop_name, Py_XNewRef(value)); + return 0; +} + static PyObject * property_get___isabstractmethod__(propertyobject *prop, void *closure) { @@ -1913,6 +1971,7 @@ property_get___isabstractmethod__(propertyobject *prop, void *closure) } static PyGetSetDef property_getsetlist[] = { + {"__name__", (getter)property_get__name__, (setter)property_set__name__}, {"__isabstractmethod__", (getter)property_get___isabstractmethod__, NULL, NULL, From cc82e33af978df793b83cefe4e25e07223a3a09e Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Tue, 20 Feb 2024 10:36:40 -0500 Subject: [PATCH 377/507] gh-115491: Keep some fields valid across allocations (free-threading) (#115573) This avoids filling the memory occupied by ob_tid, ob_ref_local, and ob_ref_shared with debug bytes (e.g., 0xDD) in mimalloc in the free-threaded build. --- Include/internal/mimalloc/mimalloc/types.h | 2 ++ Objects/mimalloc/alloc.c | 17 ++++++++++++---- Objects/mimalloc/init.c | 23 ++-------------------- Objects/mimalloc/page.c | 1 + Python/pystate.c | 15 ++++++++++++++ 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/Include/internal/mimalloc/mimalloc/types.h b/Include/internal/mimalloc/mimalloc/types.h index b8cae24507fc5e0..ed93e45062c2dbd 100644 --- a/Include/internal/mimalloc/mimalloc/types.h +++ b/Include/internal/mimalloc/mimalloc/types.h @@ -312,6 +312,7 @@ typedef struct mi_page_s { uint8_t is_committed : 1; // `true` if the page virtual memory is committed uint8_t is_zero_init : 1; // `true` if the page was initially zero initialized uint8_t tag : 4; // tag from the owning heap + uint8_t debug_offset; // number of bytes to preserve when filling freed or uninitialized memory // layout like this to optimize access in `mi_malloc` and `mi_free` uint16_t capacity; // number of blocks committed, must be the first field, see `segment.c:page_clear` @@ -553,6 +554,7 @@ struct mi_heap_s { mi_heap_t* next; // list of heaps per thread bool no_reclaim; // `true` if this heap should not reclaim abandoned pages uint8_t tag; // custom identifier for this heap + uint8_t debug_offset; // number of bytes to preserve when filling freed or uninitialized memory }; diff --git a/Objects/mimalloc/alloc.c b/Objects/mimalloc/alloc.c index f96c6f0b37f873b..b369a5ebcb23a76 100644 --- a/Objects/mimalloc/alloc.c +++ b/Objects/mimalloc/alloc.c @@ -26,6 +26,15 @@ terms of the MIT license. A copy of the license can be found in the file // Allocation // ------------------------------------------------------ +#if (MI_DEBUG>0) +static void mi_debug_fill(mi_page_t* page, mi_block_t* block, int c, size_t size) { + size_t offset = (size_t)page->debug_offset; + if (offset < size) { + memset((char*)block + offset, c, size - offset); + } +} +#endif + // Fast allocation in a page: just pop from the free list. // Fall back to generic allocation only if the list is empty. extern inline void* _mi_page_malloc(mi_heap_t* heap, mi_page_t* page, size_t size, bool zero) mi_attr_noexcept { @@ -65,7 +74,7 @@ extern inline void* _mi_page_malloc(mi_heap_t* heap, mi_page_t* page, size_t siz #if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN if (!zero && !mi_page_is_huge(page)) { - memset(block, MI_DEBUG_UNINIT, mi_page_usable_block_size(page)); + mi_debug_fill(page, block, MI_DEBUG_UNINIT, mi_page_usable_block_size(page)); } #elif (MI_SECURE!=0) if (!zero) { block->next = 0; } // don't leak internal data @@ -426,7 +435,7 @@ static mi_decl_noinline void _mi_free_block_mt(mi_page_t* page, mi_block_t* bloc #if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN // note: when tracking, cannot use mi_usable_size with multi-threading if (segment->kind != MI_SEGMENT_HUGE) { // not for huge segments as we just reset the content - memset(block, MI_DEBUG_FREED, mi_usable_size(block)); + mi_debug_fill(page, block, MI_DEBUG_FREED, mi_usable_size(block)); } #endif @@ -480,7 +489,7 @@ static inline void _mi_free_block(mi_page_t* page, bool local, mi_block_t* block mi_check_padding(page, block); #if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN if (!mi_page_is_huge(page)) { // huge page content may be already decommitted - memset(block, MI_DEBUG_FREED, mi_page_block_size(page)); + mi_debug_fill(page, block, MI_DEBUG_FREED, mi_page_block_size(page)); } #endif mi_block_set_next(page, block, page->local_free); @@ -575,7 +584,7 @@ void mi_free(void* p) mi_attr_noexcept mi_check_padding(page, block); mi_stat_free(page, block); #if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN - memset(block, MI_DEBUG_FREED, mi_page_block_size(page)); + mi_debug_fill(page, block, MI_DEBUG_FREED, mi_page_block_size(page)); #endif mi_track_free_size(p, mi_page_usable_size_of(page,block)); // faster then mi_usable_size as we already know the page and that p is unaligned mi_block_set_next(page, block, page->local_free); diff --git a/Objects/mimalloc/init.c b/Objects/mimalloc/init.c index 5897f0512f8ef9d..cb0ef6642803ccc 100644 --- a/Objects/mimalloc/init.c +++ b/Objects/mimalloc/init.c @@ -13,27 +13,7 @@ terms of the MIT license. A copy of the license can be found in the file // Empty page used to initialize the small free pages array -const mi_page_t _mi_page_empty = { - 0, false, false, false, 0, - 0, // capacity - 0, // reserved capacity - { 0 }, // flags - false, // is_zero - 0, // retire_expire - NULL, // free - 0, // used - 0, // xblock_size - NULL, // local_free - #if (MI_PADDING || MI_ENCODE_FREELIST) - { 0, 0 }, - #endif - MI_ATOMIC_VAR_INIT(0), // xthread_free - MI_ATOMIC_VAR_INIT(0), // xheap - NULL, NULL - #if MI_INTPTR_SIZE==8 - , { 0 } // padding - #endif -}; +const mi_page_t _mi_page_empty; #define MI_PAGE_EMPTY() ((mi_page_t*)&_mi_page_empty) @@ -122,6 +102,7 @@ mi_decl_cache_align const mi_heap_t _mi_heap_empty = { MI_BIN_FULL, 0, // page retired min/max NULL, // next false, + 0, 0 }; diff --git a/Objects/mimalloc/page.c b/Objects/mimalloc/page.c index 8f0ce920156e04f..63db893e49405c7 100644 --- a/Objects/mimalloc/page.c +++ b/Objects/mimalloc/page.c @@ -661,6 +661,7 @@ static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi // set fields mi_page_set_heap(page, heap); page->tag = heap->tag; + page->debug_offset = heap->debug_offset; page->xblock_size = (block_size < MI_HUGE_BLOCK_SIZE ? (uint32_t)block_size : MI_HUGE_BLOCK_SIZE); // initialize before _mi_segment_page_start size_t page_size; const void* page_start = _mi_segment_page_start(segment, page, &page_size); diff --git a/Python/pystate.c b/Python/pystate.c index d1d66e23f78d68c..1d8c09653d56296 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2845,9 +2845,24 @@ tstate_mimalloc_bind(PyThreadState *tstate) // pools to keep Python objects from different interpreters separate. tld->segments.abandoned = &tstate->interp->mimalloc.abandoned_pool; + // Don't fill in the first N bytes up to ob_type in debug builds. We may + // access ob_tid and the refcount fields in the dict and list lock-less + // accesses, so they must remain valid for a while after deallocation. + size_t base_offset = offsetof(PyObject, ob_type); + if (_PyMem_DebugEnabled()) { + // The debug allocator adds two words at the beginning of each block. + base_offset += 2 * sizeof(size_t); + } + size_t debug_offsets[_Py_MIMALLOC_HEAP_COUNT] = { + [_Py_MIMALLOC_HEAP_OBJECT] = base_offset, + [_Py_MIMALLOC_HEAP_GC] = base_offset, + [_Py_MIMALLOC_HEAP_GC_PRE] = base_offset + 2 * sizeof(PyObject *), + }; + // Initialize each heap for (uint8_t i = 0; i < _Py_MIMALLOC_HEAP_COUNT; i++) { _mi_heap_init_ex(&mts->heaps[i], tld, _mi_arena_id_none(), false, i); + mts->heaps[i].debug_offset = (uint8_t)debug_offsets[i]; } // By default, object allocations use _Py_MIMALLOC_HEAP_OBJECT. From 937d2821501de7adaa5ed8491eef4b7f3dc0940a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Tue, 20 Feb 2024 18:09:50 +0200 Subject: [PATCH 378/507] gh-115712: Support CSV dialects with delimiter=' ' and skipinitialspace=True (GH-115721) Restore support of such combination, disabled in gh-113796. csv.writer() now quotes empty fields if delimiter is a space and skipinitialspace is true and raises exception if quoting is not possible. --- Lib/test/test_csv.py | 67 ++++++++++++++++--- ...-02-20-16-42-54.gh-issue-115712.EXVMXw.rst | 4 ++ Modules/_csv.c | 36 ++++++++-- 3 files changed, 90 insertions(+), 17 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-20-16-42-54.gh-issue-115712.EXVMXw.rst diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py index 21a4cb586ff6658..5217f2a71ec0f96 100644 --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -64,8 +64,7 @@ def _test_arg_valid(self, ctor, arg): ctor(arg, delimiter='\t', skipinitialspace=True) ctor(arg, escapechar='\t', skipinitialspace=True) ctor(arg, quotechar='\t', skipinitialspace=True) - self.assertRaises(ValueError, ctor, arg, - delimiter=' ', skipinitialspace=True) + ctor(arg, delimiter=' ', skipinitialspace=True) self.assertRaises(ValueError, ctor, arg, escapechar=' ', skipinitialspace=True) self.assertRaises(ValueError, ctor, arg, @@ -192,9 +191,6 @@ def _write_error_test(self, exc, fields, **kwargs): def test_write_arg_valid(self): self._write_error_test(csv.Error, None) - self._write_test((), '') - self._write_test([None], '""') - self._write_error_test(csv.Error, [None], quoting = csv.QUOTE_NONE) # Check that exceptions are passed up the chain self._write_error_test(OSError, BadIterable()) class BadList: @@ -208,7 +204,6 @@ class BadItem: def __str__(self): raise OSError self._write_error_test(OSError, [BadItem()]) - def test_write_bigfield(self): # This exercises the buffer realloc functionality bigstring = 'X' * 50000 @@ -315,6 +310,49 @@ def test_writerows_with_none(self): fileobj.seek(0) self.assertEqual(fileobj.read(), 'a\r\n""\r\n') + + def test_write_empty_fields(self): + self._write_test((), '') + self._write_test([''], '""') + self._write_error_test(csv.Error, [''], quoting=csv.QUOTE_NONE) + self._write_test([''], '""', quoting=csv.QUOTE_STRINGS) + self._write_test([''], '""', quoting=csv.QUOTE_NOTNULL) + self._write_test([None], '""') + self._write_error_test(csv.Error, [None], quoting=csv.QUOTE_NONE) + self._write_error_test(csv.Error, [None], quoting=csv.QUOTE_STRINGS) + self._write_error_test(csv.Error, [None], quoting=csv.QUOTE_NOTNULL) + self._write_test(['', ''], ',') + self._write_test([None, None], ',') + + def test_write_empty_fields_space_delimiter(self): + self._write_test([''], '""', delimiter=' ', skipinitialspace=False) + self._write_test([''], '""', delimiter=' ', skipinitialspace=True) + self._write_test([None], '""', delimiter=' ', skipinitialspace=False) + self._write_test([None], '""', delimiter=' ', skipinitialspace=True) + + self._write_test(['', ''], ' ', delimiter=' ', skipinitialspace=False) + self._write_test(['', ''], '"" ""', delimiter=' ', skipinitialspace=True) + self._write_test([None, None], ' ', delimiter=' ', skipinitialspace=False) + self._write_test([None, None], '"" ""', delimiter=' ', skipinitialspace=True) + + self._write_test(['', ''], ' ', delimiter=' ', skipinitialspace=False, + quoting=csv.QUOTE_NONE) + self._write_error_test(csv.Error, ['', ''], + delimiter=' ', skipinitialspace=True, + quoting=csv.QUOTE_NONE) + for quoting in csv.QUOTE_STRINGS, csv.QUOTE_NOTNULL: + self._write_test(['', ''], '"" ""', delimiter=' ', skipinitialspace=False, + quoting=quoting) + self._write_test(['', ''], '"" ""', delimiter=' ', skipinitialspace=True, + quoting=quoting) + + for quoting in csv.QUOTE_NONE, csv.QUOTE_STRINGS, csv.QUOTE_NOTNULL: + self._write_test([None, None], ' ', delimiter=' ', skipinitialspace=False, + quoting=quoting) + self._write_error_test(csv.Error, [None, None], + delimiter=' ', skipinitialspace=True, + quoting=quoting) + def test_writerows_errors(self): with TemporaryFile("w+", encoding="utf-8", newline='') as fileobj: writer = csv.writer(fileobj) @@ -429,6 +467,14 @@ def test_read_skipinitialspace(self): [[None, None, None]], skipinitialspace=True, quoting=csv.QUOTE_STRINGS) + def test_read_space_delimiter(self): + self._read_test(['a b', ' a ', ' ', ''], + [['a', '', '', 'b'], ['', '', 'a', '', ''], ['', '', ''], []], + delimiter=' ', skipinitialspace=False) + self._read_test(['a b', ' a ', ' ', ''], + [['a', 'b'], ['a', ''], [''], []], + delimiter=' ', skipinitialspace=True) + def test_read_bigfield(self): # This exercises the buffer realloc functionality and field size # limits. @@ -555,10 +601,10 @@ class space(csv.excel): escapechar = "\\" with TemporaryFile("w+", encoding="utf-8") as fileobj: - fileobj.write("abc def\nc1ccccc1 benzene\n") + fileobj.write("abc def\nc1ccccc1 benzene\n") fileobj.seek(0) reader = csv.reader(fileobj, dialect=space()) - self.assertEqual(next(reader), ["abc", "def"]) + self.assertEqual(next(reader), ["abc", "", "", "def"]) self.assertEqual(next(reader), ["c1ccccc1", "benzene"]) def compare_dialect_123(self, expected, *writeargs, **kwwriteargs): @@ -1164,8 +1210,9 @@ class mydialect(csv.Dialect): self.assertRaises(csv.Error, create_invalid, field_name, 5) self.assertRaises(ValueError, create_invalid, field_name, "\n") self.assertRaises(ValueError, create_invalid, field_name, "\r") - self.assertRaises(ValueError, create_invalid, field_name, " ", - skipinitialspace=True) + if field_name != "delimiter": + self.assertRaises(ValueError, create_invalid, field_name, " ", + skipinitialspace=True) class TestSniffer(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2024-02-20-16-42-54.gh-issue-115712.EXVMXw.rst b/Misc/NEWS.d/next/Library/2024-02-20-16-42-54.gh-issue-115712.EXVMXw.rst new file mode 100644 index 000000000000000..8b19064dba779d2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-20-16-42-54.gh-issue-115712.EXVMXw.rst @@ -0,0 +1,4 @@ +Restore support of space delimiter with ``skipinitialspace=True`` in +:mod:`csv`. :func:`csv.writer()` now quotes empty fields if delimiter is a +space and skipinitialspace is true and raises exception if quoting is not +possible. diff --git a/Modules/_csv.c b/Modules/_csv.c index 3aa648b8e9cec44..8d0472885afd96d 100644 --- a/Modules/_csv.c +++ b/Modules/_csv.c @@ -332,9 +332,9 @@ dialect_check_quoting(int quoting) } static int -dialect_check_char(const char *name, Py_UCS4 c, DialectObj *dialect) +dialect_check_char(const char *name, Py_UCS4 c, DialectObj *dialect, bool allowspace) { - if (c == '\r' || c == '\n' || (dialect->skipinitialspace && c == ' ')) { + if (c == '\r' || c == '\n' || (c == ' ' && !allowspace)) { PyErr_Format(PyExc_ValueError, "bad %s value", name); return -1; } @@ -535,9 +535,11 @@ dialect_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) PyErr_SetString(PyExc_TypeError, "lineterminator must be set"); goto err; } - if (dialect_check_char("delimiter", self->delimiter, self) || - dialect_check_char("escapechar", self->escapechar, self) || - dialect_check_char("quotechar", self->quotechar, self) || + if (dialect_check_char("delimiter", self->delimiter, self, true) || + dialect_check_char("escapechar", self->escapechar, self, + !self->skipinitialspace) || + dialect_check_char("quotechar", self->quotechar, self, + !self->skipinitialspace) || dialect_check_chars("delimiter", "escapechar", self->delimiter, self->escapechar) || dialect_check_chars("delimiter", "quotechar", @@ -1221,6 +1223,7 @@ join_check_rec_size(WriterObj *self, Py_ssize_t rec_len) static int join_append(WriterObj *self, PyObject *field, int quoted) { + DialectObj *dialect = self->dialect; int field_kind = -1; const void *field_data = NULL; Py_ssize_t field_len = 0; @@ -1231,6 +1234,19 @@ join_append(WriterObj *self, PyObject *field, int quoted) field_data = PyUnicode_DATA(field); field_len = PyUnicode_GET_LENGTH(field); } + if (!field_len && dialect->delimiter == ' ' && dialect->skipinitialspace) { + if (dialect->quoting == QUOTE_NONE || + (field == NULL && + (dialect->quoting == QUOTE_STRINGS || + dialect->quoting == QUOTE_NOTNULL))) + { + PyErr_Format(self->error_obj, + "empty field must be quoted if delimiter is a space " + "and skipinitialspace is true"); + return 0; + } + quoted = 1; + } rec_len = join_append_data(self, field_kind, field_data, field_len, "ed, 0); if (rec_len < 0) @@ -1282,6 +1298,7 @@ csv_writerow(WriterObj *self, PyObject *seq) { DialectObj *dialect = self->dialect; PyObject *iter, *field, *line, *result; + bool null_field = false; iter = PyObject_GetIter(seq); if (iter == NULL) { @@ -1318,11 +1335,12 @@ csv_writerow(WriterObj *self, PyObject *seq) break; } + null_field = (field == Py_None); if (PyUnicode_Check(field)) { append_ok = join_append(self, field, quoted); Py_DECREF(field); } - else if (field == Py_None) { + else if (null_field) { append_ok = join_append(self, NULL, quoted); Py_DECREF(field); } @@ -1348,7 +1366,11 @@ csv_writerow(WriterObj *self, PyObject *seq) return NULL; if (self->num_fields > 0 && self->rec_len == 0) { - if (dialect->quoting == QUOTE_NONE) { + if (dialect->quoting == QUOTE_NONE || + (null_field && + (dialect->quoting == QUOTE_STRINGS || + dialect->quoting == QUOTE_NOTNULL))) + { PyErr_Format(self->error_obj, "single empty field record must be quoted"); return NULL; From e976baba99a5c243ff3a3b5ef2fd14608a398338 Mon Sep 17 00:00:00 2001 From: Eugene Toder <eltoder@users.noreply.github.com> Date: Tue, 20 Feb 2024 11:47:41 -0500 Subject: [PATCH 379/507] gh-86291: linecache: get module name from __spec__ if available (GH-22908) This allows getting source code for the __main__ module when a custom loader is used. --- Lib/linecache.py | 12 +++--- Lib/test/test_linecache.py | 38 +++++++++++++++++++ .../2020-12-15-22-30-49.bpo-42125.UGyseY.rst | 2 + 3 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-12-15-22-30-49.bpo-42125.UGyseY.rst diff --git a/Lib/linecache.py b/Lib/linecache.py index 329a14053458b7b..04c8f45a6c60ca2 100644 --- a/Lib/linecache.py +++ b/Lib/linecache.py @@ -166,13 +166,11 @@ def lazycache(filename, module_globals): return False # Try for a __loader__, if available if module_globals and '__name__' in module_globals: - name = module_globals['__name__'] - if (loader := module_globals.get('__loader__')) is None: - if spec := module_globals.get('__spec__'): - try: - loader = spec.loader - except AttributeError: - pass + spec = module_globals.get('__spec__') + name = getattr(spec, 'name', None) or module_globals['__name__'] + loader = getattr(spec, 'loader', None) + if loader is None: + loader = module_globals.get('__loader__') get_source = getattr(loader, 'get_source', None) if name and get_source: diff --git a/Lib/test/test_linecache.py b/Lib/test/test_linecache.py index 72dd40136cfdb24..e42df3d9496bc82 100644 --- a/Lib/test/test_linecache.py +++ b/Lib/test/test_linecache.py @@ -5,6 +5,7 @@ import os.path import tempfile import tokenize +from importlib.machinery import ModuleSpec from test import support from test.support import os_helper @@ -97,6 +98,16 @@ class BadUnicode_WithDeclaration(GetLineTestsBadData, unittest.TestCase): file_byte_string = b'# coding=utf-8\n\x80abc' +class FakeLoader: + def get_source(self, fullname): + return f'source for {fullname}' + + +class NoSourceLoader: + def get_source(self, fullname): + return None + + class LineCacheTests(unittest.TestCase): def test_getline(self): @@ -238,6 +249,33 @@ def raise_memoryerror(*args, **kwargs): self.assertEqual(lines3, []) self.assertEqual(linecache.getlines(FILENAME), lines) + def test_loader(self): + filename = 'scheme://path' + + for loader in (None, object(), NoSourceLoader()): + linecache.clearcache() + module_globals = {'__name__': 'a.b.c', '__loader__': loader} + self.assertEqual(linecache.getlines(filename, module_globals), []) + + linecache.clearcache() + module_globals = {'__name__': 'a.b.c', '__loader__': FakeLoader()} + self.assertEqual(linecache.getlines(filename, module_globals), + ['source for a.b.c\n']) + + for spec in (None, object(), ModuleSpec('', FakeLoader())): + linecache.clearcache() + module_globals = {'__name__': 'a.b.c', '__loader__': FakeLoader(), + '__spec__': spec} + self.assertEqual(linecache.getlines(filename, module_globals), + ['source for a.b.c\n']) + + linecache.clearcache() + spec = ModuleSpec('x.y.z', FakeLoader()) + module_globals = {'__name__': 'a.b.c', '__loader__': spec.loader, + '__spec__': spec} + self.assertEqual(linecache.getlines(filename, module_globals), + ['source for x.y.z\n']) + class LineCacheInvalidationTests(unittest.TestCase): def setUp(self): diff --git a/Misc/NEWS.d/next/Library/2020-12-15-22-30-49.bpo-42125.UGyseY.rst b/Misc/NEWS.d/next/Library/2020-12-15-22-30-49.bpo-42125.UGyseY.rst new file mode 100644 index 000000000000000..49d4462e2577022 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-12-15-22-30-49.bpo-42125.UGyseY.rst @@ -0,0 +1,2 @@ +linecache: get module name from ``__spec__`` if available. This allows getting +source code for the ``__main__`` module when a custom loader is used. From d207c7cd5a8c0d3e5f6c5eb947243e4afcd718b0 Mon Sep 17 00:00:00 2001 From: Victor Stinner <vstinner@python.org> Date: Tue, 20 Feb 2024 17:50:43 +0100 Subject: [PATCH 380/507] gh-110850: Cleanup pycore_time.h includes (#115724) <pycore_time.h> include is no longer needed to get the PyTime_t type in internal header files. This type is now provided by <Python.h> include. Add <pycore_time.h> includes to C files instead. --- Include/internal/pycore_import.h | 1 - Include/internal/pycore_lock.h | 2 -- Include/internal/pycore_parking_lot.h | 2 -- Include/internal/pycore_semaphore.h | 1 - Modules/_datetimemodule.c | 2 ++ Modules/_lsprof.c | 1 + Modules/_randommodule.c | 1 + Modules/_ssl.c | 1 + Modules/_testinternalcapi/test_lock.c | 3 ++- Modules/_threadmodule.c | 1 + Modules/faulthandler.c | 1 + Modules/posixmodule.c | 1 + Modules/signalmodule.c | 1 + Modules/socketmodule.c | 1 + Modules/socketmodule.h | 2 -- Modules/timemodule.c | 1 + Python/gc.c | 1 + Python/gc_free_threading.c | 1 + Python/import.c | 1 + Python/lock.c | 7 ++++--- Python/parking_lot.c | 9 +++++---- Python/thread_nt.h | 3 ++- Python/thread_pthread.h | 1 + 23 files changed, 28 insertions(+), 17 deletions(-) diff --git a/Include/internal/pycore_import.h b/Include/internal/pycore_import.h index 5f49b9aa6dfebfa..eb8a9a0db46c221 100644 --- a/Include/internal/pycore_import.h +++ b/Include/internal/pycore_import.h @@ -11,7 +11,6 @@ extern "C" { #include "pycore_lock.h" // PyMutex #include "pycore_hashtable.h" // _Py_hashtable_t -#include "pycore_time.h" // PyTime_t extern int _PyImport_IsInitialized(PyInterpreterState *); diff --git a/Include/internal/pycore_lock.h b/Include/internal/pycore_lock.h index 1aaa6677932282d..07bf3db16f3d3af 100644 --- a/Include/internal/pycore_lock.h +++ b/Include/internal/pycore_lock.h @@ -13,8 +13,6 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif -#include "pycore_time.h" // PyTime_t - // A mutex that occupies one byte. The lock can be zero initialized. // diff --git a/Include/internal/pycore_parking_lot.h b/Include/internal/pycore_parking_lot.h index a192228970c6a30..8c9260e2636fbc7 100644 --- a/Include/internal/pycore_parking_lot.h +++ b/Include/internal/pycore_parking_lot.h @@ -18,8 +18,6 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif -#include "pycore_time.h" // PyTime_t - enum { // The thread was unparked by another thread. diff --git a/Include/internal/pycore_semaphore.h b/Include/internal/pycore_semaphore.h index e1963a6ea239e17..ffcc6d80344d6ea 100644 --- a/Include/internal/pycore_semaphore.h +++ b/Include/internal/pycore_semaphore.h @@ -8,7 +8,6 @@ #endif #include "pycore_pythread.h" // _POSIX_SEMAPHORES -#include "pycore_time.h" // PyTime_t #ifdef MS_WINDOWS # define WIN32_LEAN_AND_MEAN diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index b8bd70250aee299..3ae95a8c9a87a74 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -14,6 +14,8 @@ #include "Python.h" #include "pycore_long.h" // _PyLong_GetOne() #include "pycore_object.h" // _PyObject_Init() +#include "pycore_time.h" // _PyTime_ObjectToTime_t() + #include "datetime.h" diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c index eae4261f4953f50..928baf034f62a39 100644 --- a/Modules/_lsprof.c +++ b/Modules/_lsprof.c @@ -6,6 +6,7 @@ #include "pycore_call.h" // _PyObject_CallNoArgs() #include "pycore_ceval.h" // _PyEval_SetProfile() #include "pycore_pystate.h" // _PyThreadState_GET() +#include "pycore_time.h" // _PyTime_FromNanosecondsObject() #include "rotatingtree.h" diff --git a/Modules/_randommodule.c b/Modules/_randommodule.c index 4463157d62248dd..62f1acaf8872961 100644 --- a/Modules/_randommodule.c +++ b/Modules/_randommodule.c @@ -75,6 +75,7 @@ #include "pycore_modsupport.h" // _PyArg_NoKeywords() #include "pycore_moduleobject.h" // _PyModule_GetState() #include "pycore_pylifecycle.h" // _PyOS_URandomNonblock() +#include "pycore_time.h" // _PyTime_GetSystemClock() #ifdef HAVE_UNISTD_H # include <unistd.h> // getpid() diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 1bf7241042d39a9..d00f407b569fb69 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -28,6 +28,7 @@ #include "Python.h" #include "pycore_fileutils.h" // _PyIsSelectable_fd() #include "pycore_pyerrors.h" // _PyErr_ChainExceptions1() +#include "pycore_time.h" // _PyDeadline_Init() #include "pycore_weakref.h" // _PyWeakref_GET_REF() /* Include symbols from _socket module */ diff --git a/Modules/_testinternalcapi/test_lock.c b/Modules/_testinternalcapi/test_lock.c index 9facbc5bccf9f93..724bbd0e8f0c9d0 100644 --- a/Modules/_testinternalcapi/test_lock.c +++ b/Modules/_testinternalcapi/test_lock.c @@ -1,8 +1,9 @@ // C Extension module to test pycore_lock.h API #include "parts.h" - #include "pycore_lock.h" +#include "pycore_time.h" // _PyTime_GetMonotonicClock() + #include "clinic/test_lock.c.h" #ifdef MS_WINDOWS diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 226053437bc2cd9..addaafb4f860398 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -9,6 +9,7 @@ #include "pycore_pylifecycle.h" #include "pycore_pystate.h" // _PyThreadState_SetCurrent() #include "pycore_sysmodule.h" // _PySys_GetAttr() +#include "pycore_time.h" // _PyTime_FromSeconds() #include "pycore_weakref.h" // _PyWeakref_GET_REF() #include <stdbool.h> diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index 91255fc98885aec..02e94a211914839 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -4,6 +4,7 @@ #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_signal.h" // Py_NSIG #include "pycore_sysmodule.h" // _PySys_GetAttr() +#include "pycore_time.h" // _PyTime_FromSecondsObject() #include "pycore_traceback.h" // _Py_DumpTracebackThreads #ifdef HAVE_UNISTD_H diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 4165fb66cc10dd3..fd70b38bddec79e 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -23,6 +23,7 @@ #include "pycore_pylifecycle.h" // _PyOS_URandom() #include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_signal.h" // Py_NSIG +#include "pycore_time.h" // _PyLong_FromTime_t() #ifdef HAVE_UNISTD_H # include <unistd.h> // symlink() diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c index a968cb1b0aa49cd..5804e30af1b4265 100644 --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -13,6 +13,7 @@ #include "pycore_pyerrors.h" // _PyErr_SetString() #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_signal.h" // _Py_RestoreSignals() +#include "pycore_time.h" // _PyTime_FromSecondsObject() #ifndef MS_WINDOWS # include "posixmodule.h" // _PyLong_FromUid() diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 9f70dbe4a83c2fd..298c0e29d0d9b8a 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -109,6 +109,7 @@ Local naming conventions: #include "pycore_capsule.h" // _PyCapsule_SetTraverse() #include "pycore_fileutils.h" // _Py_set_inheritable() #include "pycore_moduleobject.h" // _PyModule_GetState +#include "pycore_time.h" // _PyTime_AsMilliseconds() #ifdef _Py_MEMORY_SANITIZER # include <sanitizer/msan_interface.h> diff --git a/Modules/socketmodule.h b/Modules/socketmodule.h index a7c592c83aa25f7..09fd70f351f1d84 100644 --- a/Modules/socketmodule.h +++ b/Modules/socketmodule.h @@ -1,7 +1,5 @@ /* Socket module header file */ -#include "pycore_time.h" // PyTime_t - /* Includes needed for the sockaddr_* symbols below */ #ifndef MS_WINDOWS #ifdef __VMS diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 16769b2405f7063..73b9fc067af6fff 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -5,6 +5,7 @@ #include "pycore_moduleobject.h" // _PyModule_GetState() #include "pycore_namespace.h" // _PyNamespace_New() #include "pycore_runtime.h" // _Py_ID() +#include "pycore_time.h" // _PyTimeFraction #include <time.h> // clock() #ifdef HAVE_SYS_TIMES_H diff --git a/Python/gc.c b/Python/gc.c index b665e4681b3c39a..907f29baa3777aa 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -12,6 +12,7 @@ #include "pycore_object_alloc.h" // _PyObject_MallocWithType() #include "pycore_pyerrors.h" #include "pycore_pystate.h" // _PyThreadState_GET() +#include "pycore_time.h" // _PyTime_GetPerfCounter() #include "pycore_weakref.h" // _PyWeakref_ClearRef() #include "pydtrace.h" diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 0f159f4a272be6d..88c9c4ae5d77b92 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -11,6 +11,7 @@ #include "pycore_object_stack.h" #include "pycore_pyerrors.h" #include "pycore_pystate.h" // _PyThreadState_GET() +#include "pycore_time.h" // _PyTime_GetPerfCounter() #include "pycore_tstate.h" // _PyThreadStateImpl #include "pycore_weakref.h" // _PyWeakref_ClearRef() #include "pydtrace.h" diff --git a/Python/import.c b/Python/import.c index 6512963d9f6bed9..a8fed67755256e3 100644 --- a/Python/import.c +++ b/Python/import.c @@ -13,6 +13,7 @@ #include "pycore_pymem.h" // _PyMem_SetDefaultAllocator() #include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_sysmodule.h" // _PySys_Audit() +#include "pycore_time.h" // _PyTime_GetPerfCounter() #include "pycore_weakref.h" // _PyWeakref_GET_REF() #include "marshal.h" // PyMarshal_ReadObjectFromString() diff --git a/Python/lock.c b/Python/lock.c index 0c0d298f17d54ff..a4b044ecff0d70d 100644 --- a/Python/lock.c +++ b/Python/lock.c @@ -5,12 +5,13 @@ #include "pycore_lock.h" #include "pycore_parking_lot.h" #include "pycore_semaphore.h" +#include "pycore_time.h" // _PyTime_GetMonotonicClock() #ifdef MS_WINDOWS -#define WIN32_LEAN_AND_MEAN -#include <windows.h> // SwitchToThread() +# define WIN32_LEAN_AND_MEAN +# include <windows.h> // SwitchToThread() #elif defined(HAVE_SCHED_H) -#include <sched.h> // sched_yield() +# include <sched.h> // sched_yield() #endif // If a thread waits on a lock for longer than TIME_TO_BE_FAIR_NS (1 ms), then diff --git a/Python/parking_lot.c b/Python/parking_lot.c index 9a8a403a746914f..9bf8376e485ea49 100644 --- a/Python/parking_lot.c +++ b/Python/parking_lot.c @@ -1,11 +1,12 @@ #include "Python.h" #include "pycore_llist.h" -#include "pycore_lock.h" // _PyRawMutex +#include "pycore_lock.h" // _PyRawMutex #include "pycore_parking_lot.h" -#include "pycore_pyerrors.h" // _Py_FatalErrorFormat -#include "pycore_pystate.h" // _PyThreadState_GET -#include "pycore_semaphore.h" // _PySemaphore +#include "pycore_pyerrors.h" // _Py_FatalErrorFormat +#include "pycore_pystate.h" // _PyThreadState_GET +#include "pycore_semaphore.h" // _PySemaphore +#include "pycore_time.h" //_PyTime_GetMonotonicClock() #include <stdbool.h> diff --git a/Python/thread_nt.h b/Python/thread_nt.h index 9ca2a55cae5eef8..307352f592e70e9 100644 --- a/Python/thread_nt.h +++ b/Python/thread_nt.h @@ -1,4 +1,5 @@ -#include "pycore_interp.h" // _PyInterpreterState.threads.stacksize +#include "pycore_interp.h" // _PyInterpreterState.threads.stacksize +#include "pycore_time.h" // _PyTime_AsMicroseconds() /* This code implemented by Dag.Gruneau@elsa.preseco.comm.se */ /* Fast NonRecursiveMutex support by Yakov Markovitch, markovitch@iso.ru */ diff --git a/Python/thread_pthread.h b/Python/thread_pthread.h index ce0af736e8f7e45..9db6a4666f510c4 100644 --- a/Python/thread_pthread.h +++ b/Python/thread_pthread.h @@ -1,5 +1,6 @@ #include "pycore_interp.h" // _PyInterpreterState.threads.stacksize #include "pycore_pythread.h" // _POSIX_SEMAPHORES +#include "pycore_time.h" // _PyTime_FromMicrosecondsClamup() /* Posix threads interface */ From e3ad6ca56f9b49db0694f432a870f907a8039f79 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Tue, 20 Feb 2024 13:04:37 -0500 Subject: [PATCH 381/507] gh-115103: Implement delayed free mechanism for free-threaded builds (#115367) This adds `_PyMem_FreeDelayed()` and supporting functions. The `_PyMem_FreeDelayed()` function frees memory with the same allocator as `PyMem_Free()`, but after some delay to ensure that concurrent lock-free readers have finished. --- Include/internal/pycore_interp.h | 1 + Include/internal/pycore_pymem.h | 19 +++ Include/internal/pycore_pymem_init.h | 5 + Include/internal/pycore_runtime_init.h | 1 + Include/internal/pycore_tstate.h | 1 + Objects/obmalloc.c | 190 +++++++++++++++++++++++++ Python/pylifecycle.c | 3 + Python/pystate.c | 6 + 8 files changed, 226 insertions(+) diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 06eba665c80e930..6a00aafea73779a 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -231,6 +231,7 @@ struct _is { struct _Py_dict_state dict_state; struct _Py_exc_state exc_state; + struct _Py_mem_interp_free_queue mem_free_queue; struct ast_state ast; struct types_state types; diff --git a/Include/internal/pycore_pymem.h b/Include/internal/pycore_pymem.h index 1a72d07b50b738e..1aea91abc5d69f4 100644 --- a/Include/internal/pycore_pymem.h +++ b/Include/internal/pycore_pymem.h @@ -1,6 +1,7 @@ #ifndef Py_INTERNAL_PYMEM_H #define Py_INTERNAL_PYMEM_H +#include "pycore_llist.h" // struct llist_node #include "pycore_lock.h" // PyMutex #ifdef __cplusplus @@ -48,6 +49,11 @@ struct _pymem_allocators { PyObjectArenaAllocator obj_arena; }; +struct _Py_mem_interp_free_queue { + int has_work; // true if the queue is not empty + PyMutex mutex; // protects the queue + struct llist_node head; // queue of _mem_work_chunk items +}; /* Set the memory allocator of the specified domain to the default. Save the old allocator into *old_alloc if it's non-NULL. @@ -110,6 +116,19 @@ extern int _PyMem_SetupAllocators(PyMemAllocatorName allocator); /* Is the debug allocator enabled? */ extern int _PyMem_DebugEnabled(void); +// Enqueue a pointer to be freed possibly after some delay. +extern void _PyMem_FreeDelayed(void *ptr); + +// Periodically process delayed free requests. +extern void _PyMem_ProcessDelayed(PyThreadState *tstate); + +// Abandon all thread-local delayed free requests and push them to the +// interpreter's queue. +extern void _PyMem_AbandonDelayed(PyThreadState *tstate); + +// On interpreter shutdown, frees all delayed free requests. +extern void _PyMem_FiniDelayed(PyInterpreterState *interp); + #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_pymem_init.h b/Include/internal/pycore_pymem_init.h index 96c49ed7338d6d6..c593edc86d9952a 100644 --- a/Include/internal/pycore_pymem_init.h +++ b/Include/internal/pycore_pymem_init.h @@ -92,6 +92,11 @@ extern void _PyMem_ArenaFree(void *, void *, size_t); { NULL, _PyMem_ArenaAlloc, _PyMem_ArenaFree } +#define _Py_mem_free_queue_INIT(queue) \ + { \ + .head = LLIST_INIT(queue.head), \ + } + #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index be81604d653814c..d093047d4bc09da 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -176,6 +176,7 @@ extern PyTypeObject _PyExc_MemoryError; }, \ .dtoa = _dtoa_state_INIT(&(INTERP)), \ .dict_state = _dict_state_INIT, \ + .mem_free_queue = _Py_mem_free_queue_INIT(INTERP.mem_free_queue), \ .func_state = { \ .next_version = 1, \ }, \ diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h index d0f980ed49ee3e2..e268e6fbbb087b2 100644 --- a/Include/internal/pycore_tstate.h +++ b/Include/internal/pycore_tstate.h @@ -29,6 +29,7 @@ typedef struct _PyThreadStateImpl { PyThreadState base; struct _qsbr_thread_state *qsbr; // only used by free-threaded build + struct llist_node mem_free_queue; // delayed free queue #ifdef Py_GIL_DISABLED struct _gc_thread_state gc; diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index 6a12c3dca38b36d..9bf4eeb9c822a50 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -948,6 +948,196 @@ _PyMem_Strdup(const char *str) return copy; } +/***********************************************/ +/* Delayed freeing support for Py_GIL_DISABLED */ +/***********************************************/ + +// So that sizeof(struct _mem_work_chunk) is 4096 bytes on 64-bit platforms. +#define WORK_ITEMS_PER_CHUNK 254 + +// A pointer to be freed once the QSBR read sequence reaches qsbr_goal. +struct _mem_work_item { + void *ptr; + uint64_t qsbr_goal; +}; + +// A fixed-size buffer of pointers to be freed +struct _mem_work_chunk { + // Linked list node of chunks in queue + struct llist_node node; + + Py_ssize_t rd_idx; // index of next item to read + Py_ssize_t wr_idx; // index of next item to write + struct _mem_work_item array[WORK_ITEMS_PER_CHUNK]; +}; + +void +_PyMem_FreeDelayed(void *ptr) +{ +#ifndef Py_GIL_DISABLED + PyMem_Free(ptr); +#else + if (_PyRuntime.stoptheworld.world_stopped) { + // Free immediately if the world is stopped, including during + // interpreter shutdown. + PyMem_Free(ptr); + return; + } + + _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET(); + struct llist_node *head = &tstate->mem_free_queue; + + struct _mem_work_chunk *buf = NULL; + if (!llist_empty(head)) { + // Try to re-use the last buffer + buf = llist_data(head->prev, struct _mem_work_chunk, node); + if (buf->wr_idx == WORK_ITEMS_PER_CHUNK) { + // already full + buf = NULL; + } + } + + if (buf == NULL) { + buf = PyMem_Calloc(1, sizeof(*buf)); + if (buf != NULL) { + llist_insert_tail(head, &buf->node); + } + } + + if (buf == NULL) { + // failed to allocate a buffer, free immediately + _PyEval_StopTheWorld(tstate->base.interp); + PyMem_Free(ptr); + _PyEval_StartTheWorld(tstate->base.interp); + return; + } + + assert(buf != NULL && buf->wr_idx < WORK_ITEMS_PER_CHUNK); + uint64_t seq = _Py_qsbr_deferred_advance(tstate->qsbr); + buf->array[buf->wr_idx].ptr = ptr; + buf->array[buf->wr_idx].qsbr_goal = seq; + buf->wr_idx++; + + if (buf->wr_idx == WORK_ITEMS_PER_CHUNK) { + _PyMem_ProcessDelayed((PyThreadState *)tstate); + } +#endif +} + +static struct _mem_work_chunk * +work_queue_first(struct llist_node *head) +{ + return llist_data(head->next, struct _mem_work_chunk, node); +} + +static void +process_queue(struct llist_node *head, struct _qsbr_thread_state *qsbr, + bool keep_empty) +{ + while (!llist_empty(head)) { + struct _mem_work_chunk *buf = work_queue_first(head); + + while (buf->rd_idx < buf->wr_idx) { + struct _mem_work_item *item = &buf->array[buf->rd_idx]; + if (!_Py_qsbr_poll(qsbr, item->qsbr_goal)) { + return; + } + + PyMem_Free(item->ptr); + buf->rd_idx++; + } + + assert(buf->rd_idx == buf->wr_idx); + if (keep_empty && buf->node.next == head) { + // Keep the last buffer in the queue to reduce re-allocations + buf->rd_idx = buf->wr_idx = 0; + return; + } + + llist_remove(&buf->node); + PyMem_Free(buf); + } +} + +static void +process_interp_queue(struct _Py_mem_interp_free_queue *queue, + struct _qsbr_thread_state *qsbr) +{ + if (!_Py_atomic_load_int_relaxed(&queue->has_work)) { + return; + } + + // Try to acquire the lock, but don't block if it's already held. + if (_PyMutex_LockTimed(&queue->mutex, 0, 0) == PY_LOCK_ACQUIRED) { + process_queue(&queue->head, qsbr, false); + + int more_work = !llist_empty(&queue->head); + _Py_atomic_store_int_relaxed(&queue->has_work, more_work); + + PyMutex_Unlock(&queue->mutex); + } +} + +void +_PyMem_ProcessDelayed(PyThreadState *tstate) +{ + PyInterpreterState *interp = tstate->interp; + _PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate; + + // Process thread-local work + process_queue(&tstate_impl->mem_free_queue, tstate_impl->qsbr, true); + + // Process shared interpreter work + process_interp_queue(&interp->mem_free_queue, tstate_impl->qsbr); +} + +void +_PyMem_AbandonDelayed(PyThreadState *tstate) +{ + PyInterpreterState *interp = tstate->interp; + struct llist_node *queue = &((_PyThreadStateImpl *)tstate)->mem_free_queue; + + if (llist_empty(queue)) { + return; + } + + // Check if the queue contains one empty buffer + struct _mem_work_chunk *buf = work_queue_first(queue); + if (buf->rd_idx == buf->wr_idx) { + llist_remove(&buf->node); + PyMem_Free(buf); + assert(llist_empty(queue)); + return; + } + + // Merge the thread's work queue into the interpreter's work queue. + PyMutex_Lock(&interp->mem_free_queue.mutex); + llist_concat(&interp->mem_free_queue.head, queue); + _Py_atomic_store_int_relaxed(&interp->mem_free_queue.has_work, 1); + PyMutex_Unlock(&interp->mem_free_queue.mutex); + + assert(llist_empty(queue)); // the thread's queue is now empty +} + +void +_PyMem_FiniDelayed(PyInterpreterState *interp) +{ + struct llist_node *head = &interp->mem_free_queue.head; + while (!llist_empty(head)) { + struct _mem_work_chunk *buf = work_queue_first(head); + + while (buf->rd_idx < buf->wr_idx) { + // Free the remaining items immediately. There should be no other + // threads accessing the memory at this point during shutdown. + struct _mem_work_item *item = &buf->array[buf->rd_idx]; + PyMem_Free(item->ptr); + buf->rd_idx++; + } + + llist_remove(&buf->node); + PyMem_Free(buf); + } +} /**************************/ /* the "object" allocator */ diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 656d82136d263b9..04487345f7ec053 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1837,6 +1837,9 @@ finalize_interp_clear(PyThreadState *tstate) finalize_interp_types(tstate->interp); + /* Free any delayed free requests immediately */ + _PyMem_FiniDelayed(tstate->interp); + /* finalize_interp_types may allocate Python objects so we may need to abandon mimalloc segments again */ _PyThreadState_ClearMimallocHeaps(tstate); diff --git a/Python/pystate.c b/Python/pystate.c index 1d8c09653d56296..bb8e24c1dbe12fe 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -617,6 +617,7 @@ init_interpreter(PyInterpreterState *interp, #ifdef Py_GIL_DISABLED _Py_brc_init_state(interp); #endif + llist_init(&interp->mem_free_queue.head); for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) { interp->monitors.tools[i] = 0; } @@ -1353,6 +1354,7 @@ init_threadstate(_PyThreadStateImpl *_tstate, // Initialize biased reference counting inter-thread queue _Py_brc_init_thread(tstate); #endif + llist_init(&_tstate->mem_free_queue); if (interp->stoptheworld.requested || _PyRuntime.stoptheworld.requested) { // Start in the suspended state if there is an ongoing stop-the-world. @@ -1574,6 +1576,7 @@ PyThreadState_Clear(PyThreadState *tstate) // don't call _PyInterpreterState_SetNotRunningMain() yet. tstate->on_delete(tstate->on_delete_data); } + #ifdef Py_GIL_DISABLED // Each thread should clear own freelists in free-threading builds. struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); @@ -1583,6 +1586,9 @@ PyThreadState_Clear(PyThreadState *tstate) _Py_brc_remove_thread(tstate); #endif + // Merge our queue of pointers to be freed into the interpreter queue. + _PyMem_AbandonDelayed(tstate); + _PyThreadState_ClearMimallocHeaps(tstate); tstate->_status.cleared = 1; From 7a8c3ed43abb4b7a18e7a271aaee3ca69c0d83bc Mon Sep 17 00:00:00 2001 From: Ken Jin <kenjin@python.org> Date: Wed, 21 Feb 2024 02:47:05 +0800 Subject: [PATCH 382/507] gh-115735: Fix current executor NULL before _START_EXECUTOR (#115736) This fixes level 3 or higher lltrace debug output `--with-pydebug` runs. --- Python/ceval.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Python/ceval.c b/Python/ceval.c index 596d5f449c06fa9..b7a5d629c9466b7 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1008,7 +1008,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int uopcode = next_uop->opcode; DPRINTF(3, "%4d: uop %s, oparg %d, operand %" PRIu64 ", target %d, stack_level %d\n", - (int)(next_uop - current_executor->trace), + (int)(next_uop - (current_executor == NULL ? next_uop : current_executor->trace)), _PyUOpName(uopcode), next_uop->oparg, next_uop->operand, @@ -1030,7 +1030,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int { fprintf(stderr, "Unknown uop %d, oparg %d, operand %" PRIu64 " @ %d\n", next_uop[-1].opcode, next_uop[-1].oparg, next_uop[-1].operand, - (int)(next_uop - current_executor->trace - 1)); + (int)(next_uop - (current_executor == NULL ? next_uop : current_executor->trace) - 1)); Py_FatalError("Unknown uop"); } #else From 494739e1f71f8e290a2c869d4f40f4ea36a045c2 Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Tue, 20 Feb 2024 18:50:31 +0000 Subject: [PATCH 383/507] GH-115727: Temporary fix of confidence score test. (GH-115728) Temporary fix of confidence score test. --- Lib/test/test_capi/test_opt.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 3ba38c77710b2b3..d53d6a057aa872f 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -554,10 +554,9 @@ def testfunc(n): ex = get_first_executor(testfunc) self.assertIsNotNone(ex) ops = [opname for opname, _, _ in ex] - count = ops.count("_GUARD_IS_TRUE_POP") - # Because Each 'if' halves the score, the second branch is - # too much already. - self.assertEqual(count, 1) + #Since branch is 50/50 the trace could go either way. + count = ops.count("_GUARD_IS_TRUE_POP") + ops.count("_GUARD_IS_FALSE_POP") + self.assertLessEqual(count, 2) class TestUopsOptimization(unittest.TestCase): From 520403ed4cdf4890d63403c9cf01ac63233f5ef4 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Tue, 20 Feb 2024 15:18:44 -0500 Subject: [PATCH 384/507] gh-115733: Fix crash involving exhausted list iterator (#115740) * gh-115733: Fix crash involving exhausted iterator * Add blurb --- Lib/test/list_tests.py | 5 +++++ .../2024-02-20-18-49-02.gh-issue-115733.51Zb85.rst | 1 + Objects/listobject.c | 6 +++--- Python/bytecodes.c | 3 ++- Python/executor_cases.c.h | 1 + Python/generated_cases.c.h | 2 +- 6 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-20-18-49-02.gh-issue-115733.51Zb85.rst diff --git a/Lib/test/list_tests.py b/Lib/test/list_tests.py index d9ab21d4941cdb9..26118e14bb97e08 100644 --- a/Lib/test/list_tests.py +++ b/Lib/test/list_tests.py @@ -562,3 +562,8 @@ def test_exhausted_iterator(self): self.assertEqual(list(exhit), []) self.assertEqual(list(empit), [9]) self.assertEqual(a, self.type2test([1, 2, 3, 9])) + + # gh-115733: Crash when iterating over exhausted iterator + exhit = iter(self.type2test([1, 2, 3])) + for _ in exhit: + next(exhit, 1) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-20-18-49-02.gh-issue-115733.51Zb85.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-20-18-49-02.gh-issue-115733.51Zb85.rst new file mode 100644 index 000000000000000..5cbb292065b5daa --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-20-18-49-02.gh-issue-115733.51Zb85.rst @@ -0,0 +1 @@ +Fix crash when calling ``next()`` on exhausted list iterators. diff --git a/Objects/listobject.c b/Objects/listobject.c index eb466260318ec16..b07970298b8a000 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -3537,13 +3537,13 @@ listreviter_next(PyObject *self) { listreviterobject *it = (listreviterobject *)self; assert(it != NULL); - PyListObject *seq = it->it_seq; - assert(PyList_Check(seq)); - Py_ssize_t index = LOAD_SSIZE(it->it_index); if (index < 0) { return NULL; } + + PyListObject *seq = it->it_seq; + assert(PyList_Check(seq)); PyObject *item = list_get_item_ref(seq, index); if (item != NULL) { STORE_SSIZE(it->it_index, index - 1); diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 9d790a9d3e6577d..5835b80582b3bca 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2612,7 +2612,7 @@ dummy_func( assert(Py_TYPE(iter) == &PyListIter_Type); STAT_INC(FOR_ITER, hit); PyListObject *seq = it->it_seq; - if ((size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)) { + if (seq == NULL || (size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)) { it->it_index = -1; #ifndef Py_GIL_DISABLED if (seq != NULL) { @@ -2633,6 +2633,7 @@ dummy_func( _PyListIterObject *it = (_PyListIterObject *)iter; assert(Py_TYPE(iter) == &PyListIter_Type); PyListObject *seq = it->it_seq; + DEOPT_IF(seq == NULL); DEOPT_IF((size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)); } diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 2ca54b6fe9cd380..974555cbba9dd6e 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -2427,6 +2427,7 @@ _PyListIterObject *it = (_PyListIterObject *)iter; assert(Py_TYPE(iter) == &PyListIter_Type); PyListObject *seq = it->it_seq; + if (seq == NULL) goto deoptimize; if ((size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)) goto deoptimize; break; } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 01e67acdc5c0e5d..7f46bc8916c8d8c 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -2560,7 +2560,7 @@ assert(Py_TYPE(iter) == &PyListIter_Type); STAT_INC(FOR_ITER, hit); PyListObject *seq = it->it_seq; - if ((size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)) { + if (seq == NULL || (size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)) { it->it_index = -1; #ifndef Py_GIL_DISABLED if (seq != NULL) { From 142502ea8d26b17732009b6e981e630c342054f7 Mon Sep 17 00:00:00 2001 From: Guido van Rossum <guido@python.org> Date: Tue, 20 Feb 2024 12:24:35 -0800 Subject: [PATCH 385/507] Tier 2 cleanups and tweaks (#115534) * Rename `_testinternalcapi.get_{uop,counter}_optimizer` to `new_*_optimizer` * Use `_PyUOpName()` instead of` _PyOpcode_uop_name[]` * Add `target` to executor iterator items -- `list(ex)` now returns `(opcode, oparg, target, operand)` quadruples * Add executor methods `get_opcode()` and `get_oparg()` to get `vmdata.opcode`, `vmdata.oparg` * Define a helper for printing uops, and unify various places where they are printed * Add a hack to summarize_stats.py to fix legacy uop names (e.g. `POP_TOP` -> `_POP_TOP`) * Define helpers in `test_opt.py` for accessing the set or list of opnames of an executor --- Lib/test/test_capi/test_opt.py | 157 +++++++++++++++++-------------- Lib/test/test_monitoring.py | 2 +- Modules/_testinternalcapi.c | 8 +- Python/ceval.c | 61 +++++++----- Python/optimizer.c | 77 ++++++++++++--- Python/optimizer_analysis.c | 3 +- Python/specialize.c | 12 +-- Tools/scripts/summarize_stats.py | 4 + 8 files changed, 203 insertions(+), 121 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index d53d6a057aa872f..38c6fa4b47d0c92 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -32,16 +32,16 @@ def clear_executors(func): class TestOptimizerAPI(unittest.TestCase): - def test_get_counter_optimizer_dealloc(self): + def test_new_counter_optimizer_dealloc(self): # See gh-108727 def f(): - _testinternalcapi.get_counter_optimizer() + _testinternalcapi.new_counter_optimizer() f() def test_get_set_optimizer(self): old = _testinternalcapi.get_optimizer() - opt = _testinternalcapi.get_counter_optimizer() + opt = _testinternalcapi.new_counter_optimizer() try: _testinternalcapi.set_optimizer(opt) self.assertEqual(_testinternalcapi.get_optimizer(), opt) @@ -62,7 +62,7 @@ def loop(): loop = ns['loop'] for repeat in range(5): - opt = _testinternalcapi.get_counter_optimizer() + opt = _testinternalcapi.new_counter_optimizer() with temporary_optimizer(opt): self.assertEqual(opt.get_count(), 0) with clear_executors(loop): @@ -90,7 +90,7 @@ def long_loop(): """), ns, ns) long_loop = ns['long_loop'] - opt = _testinternalcapi.get_counter_optimizer() + opt = _testinternalcapi.new_counter_optimizer() with temporary_optimizer(opt): self.assertEqual(opt.get_count(), 0) long_loop() @@ -102,7 +102,7 @@ def testfunc(x): while i < x: i += 1 - opt = _testinternalcapi.get_counter_optimizer() + opt = _testinternalcapi.new_counter_optimizer() with temporary_optimizer(opt): testfunc(1000) code, replace_code = testfunc.__code__, testfunc.__code__.replace() @@ -123,11 +123,20 @@ def get_first_executor(func): return None +def iter_opnames(ex): + for item in ex: + yield item[0] + + +def get_opnames(ex): + return set(iter_opnames(ex)) + + class TestExecutorInvalidation(unittest.TestCase): def setUp(self): self.old = _testinternalcapi.get_optimizer() - self.opt = _testinternalcapi.get_counter_optimizer() + self.opt = _testinternalcapi.new_counter_optimizer() _testinternalcapi.set_optimizer(self.opt) def tearDown(self): @@ -176,7 +185,7 @@ def f(): pass """), ns, ns) f = ns['f'] - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): f() exe = get_first_executor(f) @@ -189,7 +198,7 @@ def test_sys__clear_internal_caches(self): def f(): for _ in range(1000): pass - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): f() exe = get_first_executor(f) @@ -208,13 +217,13 @@ def testfunc(x): while i < x: i += 1 - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): testfunc(1000) ex = get_first_executor(testfunc) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) self.assertIn("_SET_IP", uops) self.assertIn("_LOAD_FAST_0", uops) @@ -255,7 +264,7 @@ def many_vars(): """), ns, ns) many_vars = ns["many_vars"] - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): ex = get_first_executor(many_vars) self.assertIsNone(ex) @@ -263,7 +272,8 @@ def many_vars(): ex = get_first_executor(many_vars) self.assertIsNotNone(ex) - self.assertIn(("_LOAD_FAST", 259, 0), list(ex)) + self.assertTrue(any((opcode, oparg, operand) == ("_LOAD_FAST", 259, 0) + for opcode, oparg, _, operand in list(ex))) def test_unspecialized_unpack(self): # An example of an unspecialized opcode @@ -277,14 +287,14 @@ def testfunc(x): while i < x: i += 1 - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): testfunc(20) ex = get_first_executor(testfunc) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) self.assertIn("_UNPACK_SEQUENCE", uops) def test_pop_jump_if_false(self): @@ -293,13 +303,13 @@ def testfunc(n): while i < n: i += 1 - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): testfunc(20) ex = get_first_executor(testfunc) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) self.assertIn("_GUARD_IS_TRUE_POP", uops) def test_pop_jump_if_none(self): @@ -308,13 +318,13 @@ def testfunc(a): if x is None: x = 0 - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): testfunc(range(20)) ex = get_first_executor(testfunc) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) self.assertIn("_GUARD_IS_NOT_NONE_POP", uops) def test_pop_jump_if_not_none(self): @@ -324,13 +334,13 @@ def testfunc(a): if x is not None: x = 0 - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): testfunc(range(20)) ex = get_first_executor(testfunc) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) self.assertIn("_GUARD_IS_NONE_POP", uops) def test_pop_jump_if_true(self): @@ -339,13 +349,13 @@ def testfunc(n): while not i >= n: i += 1 - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): testfunc(20) ex = get_first_executor(testfunc) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) self.assertIn("_GUARD_IS_FALSE_POP", uops) def test_jump_backward(self): @@ -354,13 +364,13 @@ def testfunc(n): while i < n: i += 1 - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): testfunc(20) ex = get_first_executor(testfunc) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) self.assertIn("_JUMP_TO_TOP", uops) def test_jump_forward(self): @@ -374,13 +384,13 @@ def testfunc(n): a += 1 return a - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): testfunc(20) ex = get_first_executor(testfunc) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) # Since there is no JUMP_FORWARD instruction, # look for indirect evidence: the += operator self.assertIn("_BINARY_OP_ADD_INT", uops) @@ -392,7 +402,7 @@ def testfunc(n): total += i return total - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): total = testfunc(20) self.assertEqual(total, 190) @@ -401,7 +411,7 @@ def testfunc(n): self.assertIsNotNone(ex) # for i, (opname, oparg) in enumerate(ex): # print(f"{i:4d}: {opname:<20s} {oparg:3d}") - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) self.assertIn("_GUARD_NOT_EXHAUSTED_RANGE", uops) # Verification that the jump goes past END_FOR # is done by manual inspection of the output @@ -413,7 +423,7 @@ def testfunc(a): total += i return total - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): a = list(range(20)) total = testfunc(a) @@ -423,7 +433,7 @@ def testfunc(a): self.assertIsNotNone(ex) # for i, (opname, oparg) in enumerate(ex): # print(f"{i:4d}: {opname:<20s} {oparg:3d}") - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) self.assertIn("_GUARD_NOT_EXHAUSTED_LIST", uops) # Verification that the jump goes past END_FOR # is done by manual inspection of the output @@ -435,7 +445,7 @@ def testfunc(a): total += i return total - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): a = tuple(range(20)) total = testfunc(a) @@ -445,7 +455,7 @@ def testfunc(a): self.assertIsNotNone(ex) # for i, (opname, oparg) in enumerate(ex): # print(f"{i:4d}: {opname:<20s} {oparg:3d}") - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) self.assertIn("_GUARD_NOT_EXHAUSTED_TUPLE", uops) # Verification that the jump goes past END_FOR # is done by manual inspection of the output @@ -455,7 +465,7 @@ def testfunc(it): for x in it: pass - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): a = [1, 2, 3] it = iter(a) @@ -471,13 +481,13 @@ def dummy(x): for i in range(n): dummy(i) - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): testfunc(20) ex = get_first_executor(testfunc) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) self.assertIn("_PUSH_FRAME", uops) self.assertIn("_BINARY_OP_ADD_INT", uops) @@ -489,13 +499,13 @@ def testfunc(n): else: i = 1 - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): testfunc(20) ex = get_first_executor(testfunc) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) self.assertIn("_GUARD_IS_FALSE_POP", uops) def test_for_iter_tier_two(self): @@ -517,7 +527,7 @@ def testfunc(n, m): x += 1000*i + j return x - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): x = testfunc(10, 10) @@ -525,7 +535,7 @@ def testfunc(n, m): ex = get_first_executor(testfunc) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) self.assertIn("_FOR_ITER_TIER_TWO", uops) def test_confidence_score(self): @@ -546,14 +556,14 @@ def testfunc(n): bits += 1 return bits - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): x = testfunc(20) self.assertEqual(x, 40) ex = get_first_executor(testfunc) self.assertIsNotNone(ex) - ops = [opname for opname, _, _ in ex] + ops = list(iter_opnames(ex)) #Since branch is 50/50 the trace could go either way. count = ops.count("_GUARD_IS_TRUE_POP") + ops.count("_GUARD_IS_FALSE_POP") self.assertLessEqual(count, 2) @@ -562,7 +572,7 @@ class TestUopsOptimization(unittest.TestCase): def _run_with_optimizer(self, testfunc, arg): res = None - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() with temporary_optimizer(opt): res = testfunc(arg) @@ -582,8 +592,8 @@ def testfunc(loops): res, ex = self._run_with_optimizer(testfunc, 32) self.assertIsNotNone(ex) self.assertEqual(res, 63) - binop_count = [opname for opname, _, _ in ex if opname == "_BINARY_OP_ADD_INT"] - guard_both_int_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_INT"] + binop_count = [opname for opname in iter_opnames(ex) if opname == "_BINARY_OP_ADD_INT"] + guard_both_int_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_INT"] self.assertGreaterEqual(len(binop_count), 3) self.assertLessEqual(len(guard_both_int_count), 1) @@ -598,7 +608,7 @@ def testfunc(loops): num += 1 return a - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() res = None with temporary_optimizer(opt): res = testfunc(32) @@ -606,8 +616,8 @@ def testfunc(loops): ex = get_first_executor(testfunc) self.assertIsNotNone(ex) self.assertEqual(res, 124) - binop_count = [opname for opname, _, _ in ex if opname == "_BINARY_OP_ADD_INT"] - guard_both_int_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_INT"] + binop_count = [opname for opname in iter_opnames(ex) if opname == "_BINARY_OP_ADD_INT"] + guard_both_int_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_INT"] self.assertGreaterEqual(len(binop_count), 3) self.assertLessEqual(len(guard_both_int_count), 1) @@ -622,7 +632,7 @@ def testfunc(loops): num += 1 return x - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() res = None with temporary_optimizer(opt): res = testfunc(32) @@ -630,8 +640,8 @@ def testfunc(loops): ex = get_first_executor(testfunc) self.assertIsNotNone(ex) self.assertEqual(res, 124) - binop_count = [opname for opname, _, _ in ex if opname == "_BINARY_OP_ADD_INT"] - guard_both_int_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_INT"] + binop_count = [opname for opname in iter_opnames(ex) if opname == "_BINARY_OP_ADD_INT"] + guard_both_int_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_INT"] self.assertGreaterEqual(len(binop_count), 3) self.assertLessEqual(len(guard_both_int_count), 1) @@ -648,7 +658,7 @@ def testfunc(loops): res, ex = self._run_with_optimizer(testfunc, 64) self.assertIsNotNone(ex) - binop_count = [opname for opname, _, _ in ex if opname == "_BINARY_OP_ADD_INT"] + binop_count = [opname for opname in iter_opnames(ex) if opname == "_BINARY_OP_ADD_INT"] self.assertGreaterEqual(len(binop_count), 3) def test_call_py_exact_args(self): @@ -660,7 +670,7 @@ def dummy(x): res, ex = self._run_with_optimizer(testfunc, 32) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) self.assertIn("_PUSH_FRAME", uops) self.assertIn("_BINARY_OP_ADD_INT", uops) self.assertNotIn("_CHECK_PEP_523", uops) @@ -675,7 +685,7 @@ def testfunc(n): res, ex = self._run_with_optimizer(testfunc, 32) self.assertEqual(res, 62) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) self.assertNotIn("_GUARD_BOTH_INT", uops) def test_int_value_numbering(self): @@ -693,9 +703,9 @@ def testfunc(n): res, ex = self._run_with_optimizer(testfunc, 32) self.assertEqual(res, 4) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) self.assertIn("_GUARD_BOTH_INT", uops) - guard_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_INT"] + guard_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_INT"] self.assertEqual(len(guard_count), 1) def test_comprehension(self): @@ -706,7 +716,7 @@ def testfunc(n): res, ex = self._run_with_optimizer(testfunc, 32) self.assertEqual(res, list(range(32))) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) self.assertNotIn("_BINARY_OP_ADD_INT", uops) def test_call_py_exact_args_disappearing(self): @@ -717,7 +727,7 @@ def testfunc(n): for i in range(n): dummy(i) - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() # Trigger specialization testfunc(8) with temporary_optimizer(opt): @@ -752,18 +762,21 @@ def get_first_executor(func): pass return None + def get_opnames(ex): + return {item[0] for item in ex} + def testfunc(n): for i in range(n): x = range(i) return x - opt = _testinternalcapi.get_uop_optimizer() + opt = _testinternalcapi.new_uop_optimizer() _testinternalcapi.set_optimizer(opt) testfunc(64) ex = get_first_executor(testfunc) assert ex is not None - uops = {opname for opname, _, _ in ex} + uops = get_opnames(ex) assert "_LOAD_GLOBAL_BUILTINS" not in uops assert "_LOAD_CONST_INLINE_BORROW_WITH_NULL" in uops """)) @@ -779,8 +792,8 @@ def testfunc(n): res, ex = self._run_with_optimizer(testfunc, 32) self.assertAlmostEqual(res, 4.2) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - guard_both_float_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_FLOAT"] + uops = get_opnames(ex) + guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_FLOAT"] self.assertLessEqual(len(guard_both_float_count), 1) # TODO gh-115506: this assertion may change after propagating constants. # We'll also need to verify that propagation actually occurs. @@ -796,8 +809,8 @@ def testfunc(n): res, ex = self._run_with_optimizer(testfunc, 32) self.assertAlmostEqual(res, -2.2) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - guard_both_float_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_FLOAT"] + uops = get_opnames(ex) + guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_FLOAT"] self.assertLessEqual(len(guard_both_float_count), 1) # TODO gh-115506: this assertion may change after propagating constants. # We'll also need to verify that propagation actually occurs. @@ -813,8 +826,8 @@ def testfunc(n): res, ex = self._run_with_optimizer(testfunc, 32) self.assertAlmostEqual(res, 2 ** 32) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - guard_both_float_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_FLOAT"] + uops = get_opnames(ex) + guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_FLOAT"] self.assertLessEqual(len(guard_both_float_count), 1) # TODO gh-115506: this assertion may change after propagating constants. # We'll also need to verify that propagation actually occurs. @@ -833,8 +846,8 @@ def testfunc(n): res, ex = self._run_with_optimizer(testfunc, 32) self.assertTrue(res) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - guard_both_float_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_FLOAT"] + uops = get_opnames(ex) + guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_FLOAT"] self.assertLessEqual(len(guard_both_float_count), 1) self.assertIn("_COMPARE_OP_FLOAT", uops) @@ -851,8 +864,8 @@ def testfunc(n): res, ex = self._run_with_optimizer(testfunc, 32) self.assertTrue(res) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - guard_both_float_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_INT"] + uops = get_opnames(ex) + guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_INT"] self.assertLessEqual(len(guard_both_float_count), 1) self.assertIn("_COMPARE_OP_INT", uops) @@ -869,8 +882,8 @@ def testfunc(n): res, ex = self._run_with_optimizer(testfunc, 32) self.assertTrue(res) self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - guard_both_float_count = [opname for opname, _, _ in ex if opname == "_GUARD_BOTH_UNICODE"] + uops = get_opnames(ex) + guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_UNICODE"] self.assertLessEqual(len(guard_both_float_count), 1) self.assertIn("_COMPARE_OP_STR", uops) diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index 60b6326bfbad5e9..58aa4bca7534b1e 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -1799,7 +1799,7 @@ class TestOptimizer(MonitoringTestBase, unittest.TestCase): def setUp(self): import _testinternalcapi self.old_opt = _testinternalcapi.get_optimizer() - opt = _testinternalcapi.get_counter_optimizer() + opt = _testinternalcapi.new_counter_optimizer() _testinternalcapi.set_optimizer(opt) super(TestOptimizer, self).setUp() diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index bcc431a27001f25..0d23b1899f22e44 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -960,13 +960,13 @@ iframe_getlasti(PyObject *self, PyObject *frame) } static PyObject * -get_counter_optimizer(PyObject *self, PyObject *arg) +new_counter_optimizer(PyObject *self, PyObject *arg) { return PyUnstable_Optimizer_NewCounter(); } static PyObject * -get_uop_optimizer(PyObject *self, PyObject *arg) +new_uop_optimizer(PyObject *self, PyObject *arg) { return PyUnstable_Optimizer_NewUOpOptimizer(); } @@ -1711,8 +1711,8 @@ static PyMethodDef module_functions[] = { {"get_optimizer", get_optimizer, METH_NOARGS, NULL}, {"set_optimizer", set_optimizer, METH_O, NULL}, {"get_executor", _PyCFunction_CAST(get_executor), METH_FASTCALL, NULL}, - {"get_counter_optimizer", get_counter_optimizer, METH_NOARGS, NULL}, - {"get_uop_optimizer", get_uop_optimizer, METH_NOARGS, NULL}, + {"new_counter_optimizer", new_counter_optimizer, METH_NOARGS, NULL}, + {"new_uop_optimizer", new_uop_optimizer, METH_NOARGS, NULL}, {"add_executor_dependency", add_executor_dependency, METH_VARARGS, NULL}, {"invalidate_executors", invalidate_executors, METH_O, NULL}, {"pending_threadfunc", _PyCFunction_CAST(pending_threadfunc), diff --git a/Python/ceval.c b/Python/ceval.c index b7a5d629c9466b7..06c136aeb252c9a 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -649,7 +649,10 @@ static const _Py_CODEUNIT _Py_INTERPRETER_TRAMPOLINE_INSTRUCTIONS[] = { extern const struct _PyCode_DEF(8) _Py_InitCleanup; -extern const char *_PyUOpName(int index); +#ifdef Py_DEBUG +extern void _PyUOpPrint(const _PyUOpInstruction *uop); +#endif + /* Disable unused label warnings. They are handy for debugging, even if computed gotos aren't used. */ @@ -1006,14 +1009,14 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int assert(next_uop->opcode == _START_EXECUTOR || next_uop->opcode == _COLD_EXIT); for (;;) { uopcode = next_uop->opcode; - DPRINTF(3, - "%4d: uop %s, oparg %d, operand %" PRIu64 ", target %d, stack_level %d\n", - (int)(next_uop - (current_executor == NULL ? next_uop : current_executor->trace)), - _PyUOpName(uopcode), - next_uop->oparg, - next_uop->operand, - next_uop->target, +#ifdef Py_DEBUG + if (lltrace >= 3) { + printf("%4d uop: ", (int)(next_uop - (current_executor == NULL ? next_uop : current_executor->trace))); + _PyUOpPrint(next_uop); + printf(" stack_level=%d\n", (int)(stack_pointer - _PyFrame_Stackbase(frame))); + } +#endif next_uop++; OPT_STAT_INC(uops_executed); UOP_STAT_INC(uopcode, execution_count); @@ -1028,9 +1031,9 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int default: #ifdef Py_DEBUG { - fprintf(stderr, "Unknown uop %d, oparg %d, operand %" PRIu64 " @ %d\n", - next_uop[-1].opcode, next_uop[-1].oparg, next_uop[-1].operand, - (int)(next_uop - (current_executor == NULL ? next_uop : current_executor->trace) - 1)); + printf("Unknown uop: "); + _PyUOpPrint(&next_uop[-1]); + printf(" @ %d\n", (int)(next_uop - current_executor->trace - 1)); Py_FatalError("Unknown uop"); } #else @@ -1058,10 +1061,15 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int pop_1_error_tier_two: STACK_SHRINK(1); error_tier_two: - DPRINTF(2, "Error: [UOp %d (%s), oparg %d, operand %" PRIu64 ", target %d @ %d -> %s]\n", - uopcode, _PyUOpName(uopcode), next_uop[-1].oparg, next_uop[-1].operand, next_uop[-1].target, - (int)(next_uop - current_executor->trace - 1), - _PyOpcode_OpName[frame->instr_ptr->op.code]); +#ifdef Py_DEBUG + if (lltrace >= 2) { + printf("Error: [UOp "); + _PyUOpPrint(&next_uop[-1]); + printf(" @ %d -> %s]\n", + (int)(next_uop - current_executor->trace - 1), + _PyOpcode_OpName[frame->instr_ptr->op.code]); + } +#endif OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); frame->return_offset = 0; // Don't leave this random _PyFrame_SetStackPointer(frame, stack_pointer); @@ -1072,9 +1080,14 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int // Jump here from DEOPT_IF() deoptimize: next_instr = next_uop[-1].target + _PyCode_CODE(_PyFrame_GetCode(frame)); - DPRINTF(2, "DEOPT: [UOp %d (%s), oparg %d, operand %" PRIu64 ", target %d -> %s]\n", - uopcode, _PyUOpName(uopcode), next_uop[-1].oparg, next_uop[-1].operand, next_uop[-1].target, - _PyOpcode_OpName[next_instr->op.code]); +#ifdef Py_DEBUG + if (lltrace >= 2) { + printf("DEOPT: [UOp "); + _PyUOpPrint(&next_uop[-1]); + printf(" -> %s]\n", + _PyOpcode_OpName[frame->instr_ptr->op.code]); + } +#endif OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); UOP_STAT_INC(uopcode, miss); Py_DECREF(current_executor); @@ -1088,9 +1101,15 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int uint32_t exit_index = next_uop[-1].exit_index; assert(exit_index < current_executor->exit_count); _PyExitData *exit = ¤t_executor->exits[exit_index]; - DPRINTF(2, "SIDE EXIT: [UOp %d (%s), oparg %d, operand %" PRIu64 ", exit %u, temp %d, target %d -> %s]\n", - uopcode, _PyUOpName(uopcode), next_uop[-1].oparg, next_uop[-1].operand, exit_index, exit->temperature, - exit->target, _PyOpcode_OpName[_PyCode_CODE(_PyFrame_GetCode(frame))[exit->target].op.code]); +#ifdef Py_DEBUG + if (lltrace >= 2) { + printf("SIDE EXIT: [UOp "); + _PyUOpPrint(&next_uop[-1]); + printf(", exit %u, temp %d, target %d -> %s]\n", + exit_index, exit->temperature, exit->target, + _PyOpcode_OpName[frame->instr_ptr->op.code]); + } +#endif Py_INCREF(exit->executor); tstate->previous_executor = (PyObject *)current_executor; GOTO_TIER_TWO(exit->executor); diff --git a/Python/optimizer.c b/Python/optimizer.c index df8f0ed234b59d2..74708beea7a53d1 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -262,8 +262,22 @@ is_valid(PyObject *self, PyObject *Py_UNUSED(ignored)) return PyBool_FromLong(((_PyExecutorObject *)self)->vm_data.valid); } +static PyObject * +get_opcode(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + return PyLong_FromUnsignedLong(((_PyExecutorObject *)self)->vm_data.opcode); +} + +static PyObject * +get_oparg(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + return PyLong_FromUnsignedLong(((_PyExecutorObject *)self)->vm_data.oparg); +} + static PyMethodDef executor_methods[] = { { "is_valid", is_valid, METH_NOARGS, NULL }, + { "get_opcode", get_opcode, METH_NOARGS, NULL }, + { "get_oparg", get_oparg, METH_NOARGS, NULL }, { NULL, NULL }, }; @@ -282,9 +296,30 @@ uop_dealloc(_PyExecutorObject *self) { const char * _PyUOpName(int index) { + if (index < 0 || index > MAX_UOP_ID) { + return NULL; + } return _PyOpcode_uop_name[index]; } +#ifdef Py_DEBUG +void +_PyUOpPrint(const _PyUOpInstruction *uop) +{ + const char *name = _PyUOpName(uop->opcode); + if (name == NULL) { + printf("<uop %d>", uop->opcode); + } + else { + printf("%s", name); + } + printf(" (%d, target=%d, operand=%" PRIx64 ")", + uop->oparg, + uop->target, + (uint64_t)uop->operand); +} +#endif + static Py_ssize_t uop_len(_PyExecutorObject *self) { @@ -312,14 +347,21 @@ uop_item(_PyExecutorObject *self, Py_ssize_t index) Py_DECREF(oname); return NULL; } + PyObject *target = PyLong_FromUnsignedLong(self->trace[index].target); + if (oparg == NULL) { + Py_DECREF(oparg); + Py_DECREF(oname); + return NULL; + } PyObject *operand = PyLong_FromUnsignedLongLong(self->trace[index].operand); if (operand == NULL) { + Py_DECREF(target); Py_DECREF(oparg); Py_DECREF(oname); return NULL; } - PyObject *args[3] = { oname, oparg, operand }; - return _PyTuple_FromArraySteal(args, 3); + PyObject *args[4] = { oname, oparg, target, operand }; + return _PyTuple_FromArraySteal(args, 4); } PySequenceMethods uop_as_sequence = { @@ -390,19 +432,29 @@ BRANCH_TO_GUARD[4][2] = { #endif +// Beware: Macro arg order differs from struct member order +#ifdef Py_DEBUG #define ADD_TO_TRACE(OPCODE, OPARG, OPERAND, TARGET) \ - DPRINTF(2, \ - " ADD_TO_TRACE(%s, %d, %" PRIu64 ", %d)\n", \ - _PyUOpName(OPCODE), \ - (OPARG), \ - (uint64_t)(OPERAND), \ - TARGET); \ assert(trace_length < max_length); \ trace[trace_length].opcode = (OPCODE); \ trace[trace_length].oparg = (OPARG); \ + trace[trace_length].target = (TARGET); \ trace[trace_length].operand = (OPERAND); \ + if (lltrace >= 2) { \ + printf("%4d ADD_TO_TRACE: ", trace_length); \ + _PyUOpPrint(&trace[trace_length]); \ + printf("\n"); \ + } \ + trace_length++; +#else +#define ADD_TO_TRACE(OPCODE, OPARG, OPERAND, TARGET) \ + assert(trace_length < max_length); \ + trace[trace_length].opcode = (OPCODE); \ + trace[trace_length].oparg = (OPARG); \ trace[trace_length].target = (TARGET); \ + trace[trace_length].operand = (OPERAND); \ trace_length++; +#endif #define INSTR_IP(INSTR, CODE) \ ((uint32_t)((INSTR) - ((_Py_CODEUNIT *)(CODE)->co_code_adaptive))) @@ -890,12 +942,9 @@ make_executor_from_uops(_PyUOpInstruction *buffer, const _PyBloomFilter *depende if (lltrace >= 2) { printf("Optimized executor (length %d):\n", length); for (int i = 0; i < length; i++) { - printf("%4d %s(%d, %d, %" PRIu64 ")\n", - i, - _PyUOpName(executor->trace[i].opcode), - executor->trace[i].oparg, - executor->trace[i].target, - executor->trace[i].operand); + printf("%4d OPTIMIZED: ", i); + _PyUOpPrint(&executor->trace[i]); + printf("\n"); } } #endif diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index b104d2fa7baec95..e7fb1e38c0dfd72 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -44,6 +44,7 @@ #define MAX_ABSTRACT_FRAME_DEPTH (TRACE_STACK_SIZE + 2) #ifdef Py_DEBUG + extern const char *_PyUOpName(int index); static const char *const DEBUG_ENV = "PYTHON_OPT_DEBUG"; static inline int get_lltrace(void) { char *uop_debug = Py_GETENV(DEBUG_ENV); @@ -632,7 +633,7 @@ uop_redundancy_eliminator( _Py_UOpsSymType **stack_pointer = ctx->frame->stack_pointer; DPRINTF(3, "Abstract interpreting %s:%d ", - _PyOpcode_uop_name[opcode], + _PyUOpName(opcode), oparg); switch (opcode) { #include "tier2_redundancy_eliminator_cases.c.h" diff --git a/Python/specialize.c b/Python/specialize.c index 2256d79b387c565..871979d92298b6e 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -17,6 +17,7 @@ #include <stdlib.h> // rand() +extern const char *_PyUOpName(int index); /* For guidance on adding or extending families of instructions see * ./adaptive.md @@ -246,17 +247,12 @@ print_optimization_stats(FILE *out, OptimizationStats *stats) stats->optimizer_failure_reason_no_memory); const char* const* names; - for (int i = 0; i < 512; i++) { - if (i < 256) { - names = _PyOpcode_OpName; - } else { - names = _PyOpcode_uop_name; - } + for (int i = 0; i <= MAX_UOP_ID; i++) { if (stats->opcode[i].execution_count) { - fprintf(out, "uops[%s].execution_count : %" PRIu64 "\n", names[i], stats->opcode[i].execution_count); + fprintf(out, "uops[%s].execution_count : %" PRIu64 "\n", _PyUOpName(i), stats->opcode[i].execution_count); } if (stats->opcode[i].miss) { - fprintf(out, "uops[%s].specialization.miss : %" PRIu64 "\n", names[i], stats->opcode[i].miss); + fprintf(out, "uops[%s].specialization.miss : %" PRIu64 "\n", _PyUOpName(i), stats->opcode[i].miss); } } diff --git a/Tools/scripts/summarize_stats.py b/Tools/scripts/summarize_stats.py index 5bc39fceb4b2a11..6b60b59b3b0e797 100644 --- a/Tools/scripts/summarize_stats.py +++ b/Tools/scripts/summarize_stats.py @@ -102,6 +102,10 @@ def load_raw_data(input: Path) -> RawData: file=sys.stderr, ) continue + # Hack to handle older data files where some uops + # are missing an underscore prefix in their name + if key.startswith("uops[") and key[5:6] != "_": + key = "uops[_" + key[5:] stats[key.strip()] += int(value) stats["__nfiles__"] += 1 From e1fdc3c323bd605f92622b7ee18805885ff0bb4e Mon Sep 17 00:00:00 2001 From: Victor Westerhuis <viccie30@users.noreply.github.com> Date: Tue, 20 Feb 2024 22:08:15 +0100 Subject: [PATCH 386/507] gh-104061: Add socket.SO_BINDTOIFINDEX constant (GH-104062) Add socket.SO_BINDTOIFINDEX constant This socket option avoids a race condition between SO_BINDTODEVICE and network interface renaming. --- Doc/library/socket.rst | 5 +++++ .../Library/2023-05-01-22-28-57.gh-issue-104061.vxfBXf.rst | 1 + Modules/socketmodule.c | 3 +++ 3 files changed, 9 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-05-01-22-28-57.gh-issue-104061.vxfBXf.rst diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index dccf78ef8c0128f..3a931e25de91e5f 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -445,6 +445,11 @@ Constants Added ``IP_PKTINFO``, ``IP_UNBLOCK_SOURCE``, ``IP_BLOCK_SOURCE``, ``IP_ADD_SOURCE_MEMBERSHIP``, ``IP_DROP_SOURCE_MEMBERSHIP``. + .. versionchanged:: 3.13 + Added ``SO_BINDTOIFINDEX``. On Linux this constant can be used in the + same way that ``SO_BINDTODEVICE`` is used, but with the index of a + network interface instead of its name. + .. data:: AF_CAN PF_CAN SOL_CAN_* diff --git a/Misc/NEWS.d/next/Library/2023-05-01-22-28-57.gh-issue-104061.vxfBXf.rst b/Misc/NEWS.d/next/Library/2023-05-01-22-28-57.gh-issue-104061.vxfBXf.rst new file mode 100644 index 000000000000000..e15a811f9043520 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-05-01-22-28-57.gh-issue-104061.vxfBXf.rst @@ -0,0 +1 @@ +Add :data:`socket.SO_BINDTOIFINDEX` constant. diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 298c0e29d0d9b8a..836cf6c05b31965 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -7927,6 +7927,9 @@ socket_exec(PyObject *m) #ifdef SO_BINDTODEVICE ADD_INT_MACRO(m, SO_BINDTODEVICE); #endif +#ifdef SO_BINDTOIFINDEX + ADD_INT_MACRO(m, SO_BINDTOIFINDEX); +#endif #ifdef SO_PRIORITY ADD_INT_MACRO(m, SO_PRIORITY); #endif From 52d14775665a6fde518ee3da88a73f39b09d993f Mon Sep 17 00:00:00 2001 From: Victor Stinner <vstinner@python.org> Date: Tue, 20 Feb 2024 23:16:37 +0100 Subject: [PATCH 387/507] gh-110850: Rename internal PyTime C API functions (#115734) Rename functions: * _PyTime_GetSystemClock() => _PyTime_TimeUnchecked() * _PyTime_GetPerfCounter() => _PyTime_PerfCounterUnchecked() * _PyTime_GetMonotonicClock() => _PyTime_MonotonicUnchecked() * _PyTime_GetSystemClockWithInfo() => _PyTime_TimeWithInfo() * _PyTime_GetMonotonicClockWithInfo() => _PyTime_MonotonicWithInfo() * _PyTime_GetMonotonicClockWithInfo() => _PyTime_MonotonicWithInfo() Changes: * Remove "typedef PyTime_t PyTime_t;" which was "typedef PyTime_t _PyTime_t;" before a previous rename. * Update comments of "Unchecked" functions. * Remove invalid PyTime_Time() comment. --- Include/internal/pycore_time.h | 50 ++++++++++----------------- Modules/_datetimemodule.c | 2 +- Modules/_lsprof.c | 2 +- Modules/_randommodule.c | 6 ++-- Modules/_testinternalcapi/test_lock.c | 6 ++-- Modules/_testsinglephase.c | 4 +-- Modules/timemodule.c | 18 +++++----- Python/gc.c | 6 ++-- Python/gc_free_threading.c | 4 +-- Python/import.c | 6 ++-- Python/lock.c | 6 ++-- Python/parking_lot.c | 8 ++--- Python/pytime.c | 20 +++++------ Python/thread_nt.h | 4 +-- Python/thread_pthread.h | 8 ++--- Tools/c-analyzer/cpython/ignored.tsv | 1 + 16 files changed, 68 insertions(+), 83 deletions(-) diff --git a/Include/internal/pycore_time.h b/Include/internal/pycore_time.h index abef52e3f9f0fc5..9692bbc89711a55 100644 --- a/Include/internal/pycore_time.h +++ b/Include/internal/pycore_time.h @@ -62,7 +62,6 @@ extern "C" { struct timeval; #endif -typedef PyTime_t PyTime_t; #define _SIZEOF_PYTIME_T 8 typedef enum { @@ -253,37 +252,28 @@ typedef struct { double resolution; } _Py_clock_info_t; -// Get the current time from the system clock. -// -// If the internal clock fails, silently ignore the error and return 0. -// On integer overflow, silently ignore the overflow and clamp the clock to -// [_PyTime_MIN; _PyTime_MAX]. +// Similar to PyTime_Time() but silently ignore the error and return 0 if the +// internal clock fails. // -// Use _PyTime_GetSystemClockWithInfo or the public PyTime_Time() to check +// Use _PyTime_TimeWithInfo() or the public PyTime_Time() to check // for failure. // Export for '_random' shared extension. -PyAPI_FUNC(PyTime_t) _PyTime_GetSystemClock(void); +PyAPI_FUNC(PyTime_t) _PyTime_TimeUnchecked(void); // Get the current time from the system clock. // On success, set *t and *info (if not NULL), and return 0. // On error, raise an exception and return -1. -extern int _PyTime_GetSystemClockWithInfo( +extern int _PyTime_TimeWithInfo( PyTime_t *t, _Py_clock_info_t *info); -// Get the time of a monotonic clock, i.e. a clock that cannot go backwards. -// The clock is not affected by system clock updates. The reference point of -// the returned value is undefined, so that only the difference between the -// results of consecutive calls is valid. +// Similar to PyTime_Monotonic() but silently ignore the error and return 0 if +// the internal clock fails. // -// If the internal clock fails, silently ignore the error and return 0. -// On integer overflow, silently ignore the overflow and clamp the clock to -// [_PyTime_MIN; _PyTime_MAX]. -// -// Use _PyTime_GetMonotonicClockWithInfo or the public PyTime_Monotonic() +// Use _PyTime_MonotonicWithInfo() or the public PyTime_Monotonic() // to check for failure. // Export for '_random' shared extension. -PyAPI_FUNC(PyTime_t) _PyTime_GetMonotonicClock(void); +PyAPI_FUNC(PyTime_t) _PyTime_MonotonicUnchecked(void); // Get the time of a monotonic clock, i.e. a clock that cannot go backwards. // The clock is not affected by system clock updates. The reference point of @@ -294,7 +284,7 @@ PyAPI_FUNC(PyTime_t) _PyTime_GetMonotonicClock(void); // // Return 0 on success, raise an exception and return -1 on error. // Export for '_testsinglephase' shared extension. -PyAPI_FUNC(int) _PyTime_GetMonotonicClockWithInfo( +PyAPI_FUNC(int) _PyTime_MonotonicWithInfo( PyTime_t *t, _Py_clock_info_t *info); @@ -309,17 +299,13 @@ PyAPI_FUNC(int) _PyTime_localtime(time_t t, struct tm *tm); // Export for '_datetime' shared extension. PyAPI_FUNC(int) _PyTime_gmtime(time_t t, struct tm *tm); -// Get the performance counter: clock with the highest available resolution to -// measure a short duration. +// Similar to PyTime_PerfCounter() but silently ignore the error and return 0 +// if the internal clock fails. // -// If the internal clock fails, silently ignore the error and return 0. -// On integer overflow, silently ignore the overflow and clamp the clock to -// [_PyTime_MIN; _PyTime_MAX]. -// -// Use _PyTime_GetPerfCounterWithInfo() or the public PyTime_PerfCounter -// to check for failure. +// Use _PyTime_PerfCounterWithInfo() or the public PyTime_PerfCounter() to +// check for failure. // Export for '_lsprof' shared extension. -PyAPI_FUNC(PyTime_t) _PyTime_GetPerfCounter(void); +PyAPI_FUNC(PyTime_t) _PyTime_PerfCounterUnchecked(void); // Get the performance counter: clock with the highest available resolution to @@ -328,7 +314,7 @@ PyAPI_FUNC(PyTime_t) _PyTime_GetPerfCounter(void); // Fill info (if set) with information of the function used to get the time. // // Return 0 on success, raise an exception and return -1 on error. -extern int _PyTime_GetPerfCounterWithInfo( +extern int _PyTime_PerfCounterWithInfo( PyTime_t *t, _Py_clock_info_t *info); @@ -341,12 +327,12 @@ extern int _PyTime_GetPerfCounterWithInfo( // --- _PyDeadline ----------------------------------------------------------- // Create a deadline. -// Pseudo code: _PyTime_GetMonotonicClock() + timeout. +// Pseudo code: _PyTime_MonotonicUnchecked() + timeout. // Export for '_ssl' shared extension. PyAPI_FUNC(PyTime_t) _PyDeadline_Init(PyTime_t timeout); // Get remaining time from a deadline. -// Pseudo code: deadline - _PyTime_GetMonotonicClock(). +// Pseudo code: deadline - _PyTime_MonotonicUnchecked(). // Export for '_ssl' shared extension. PyAPI_FUNC(PyTime_t) _PyDeadline_Get(PyTime_t deadline); diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 3ae95a8c9a87a74..9fd59c34de6cefc 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -5133,7 +5133,7 @@ datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp, static PyObject * datetime_best_possible(PyObject *cls, TM_FUNC f, PyObject *tzinfo) { - PyTime_t ts = _PyTime_GetSystemClock(); + PyTime_t ts = _PyTime_TimeUnchecked(); time_t secs; int us; diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c index 928baf034f62a39..29a80c70c0db6ac 100644 --- a/Modules/_lsprof.c +++ b/Modules/_lsprof.c @@ -121,7 +121,7 @@ call_timer(ProfilerObject *pObj) return CallExternalTimer(pObj); } else { - return _PyTime_GetPerfCounter(); + return _PyTime_PerfCounterUnchecked(); } } diff --git a/Modules/_randommodule.c b/Modules/_randommodule.c index 62f1acaf8872961..920645b453536ab 100644 --- a/Modules/_randommodule.c +++ b/Modules/_randommodule.c @@ -75,7 +75,7 @@ #include "pycore_modsupport.h" // _PyArg_NoKeywords() #include "pycore_moduleobject.h" // _PyModule_GetState() #include "pycore_pylifecycle.h" // _PyOS_URandomNonblock() -#include "pycore_time.h" // _PyTime_GetSystemClock() +#include "pycore_time.h" // _PyTime_TimeUnchecked() #ifdef HAVE_UNISTD_H # include <unistd.h> // getpid() @@ -266,7 +266,7 @@ random_seed_time_pid(RandomObject *self) PyTime_t now; uint32_t key[5]; - now = _PyTime_GetSystemClock(); + now = _PyTime_TimeUnchecked(); key[0] = (uint32_t)(now & 0xffffffffU); key[1] = (uint32_t)(now >> 32); @@ -278,7 +278,7 @@ random_seed_time_pid(RandomObject *self) key[2] = 0; #endif - now = _PyTime_GetMonotonicClock(); + now = _PyTime_MonotonicUnchecked(); key[3] = (uint32_t)(now & 0xffffffffU); key[4] = (uint32_t)(now >> 32); diff --git a/Modules/_testinternalcapi/test_lock.c b/Modules/_testinternalcapi/test_lock.c index 724bbd0e8f0c9d0..1c5048170e9f2ea 100644 --- a/Modules/_testinternalcapi/test_lock.c +++ b/Modules/_testinternalcapi/test_lock.c @@ -2,7 +2,7 @@ #include "parts.h" #include "pycore_lock.h" -#include "pycore_time.h" // _PyTime_GetMonotonicClock() +#include "pycore_time.h" // _PyTime_MonotonicUnchecked() #include "clinic/test_lock.c.h" @@ -290,7 +290,7 @@ _testinternalcapi_benchmark_locks_impl(PyObject *module, goto exit; } - PyTime_t start = _PyTime_GetMonotonicClock(); + PyTime_t start = _PyTime_MonotonicUnchecked(); for (Py_ssize_t i = 0; i < num_threads; i++) { thread_data[i].bench_data = &bench_data; @@ -307,7 +307,7 @@ _testinternalcapi_benchmark_locks_impl(PyObject *module, } Py_ssize_t total_iters = bench_data.total_iters; - PyTime_t end = _PyTime_GetMonotonicClock(); + PyTime_t end = _PyTime_MonotonicUnchecked(); // Return the total number of acquisitions and the number of acquisitions // for each thread. diff --git a/Modules/_testsinglephase.c b/Modules/_testsinglephase.c index dccac2852a567a4..58d22e2d5dbe562 100644 --- a/Modules/_testsinglephase.c +++ b/Modules/_testsinglephase.c @@ -71,13 +71,13 @@ _set_initialized(PyTime_t *initialized) { /* We go strictly monotonic to ensure each time is unique. */ PyTime_t prev; - if (_PyTime_GetMonotonicClockWithInfo(&prev, NULL) != 0) { + if (_PyTime_MonotonicWithInfo(&prev, NULL) != 0) { return -1; } /* We do a busy sleep since the interval should be super short. */ PyTime_t t; do { - if (_PyTime_GetMonotonicClockWithInfo(&t, NULL) != 0) { + if (_PyTime_MonotonicWithInfo(&t, NULL) != 0) { return -1; } } while (t == prev); diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 73b9fc067af6fff..28dba903d2b9e87 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -106,8 +106,8 @@ _PyFloat_FromPyTime(PyTime_t t) static int get_system_time(PyTime_t *t) { - // Avoid _PyTime_GetSystemClock() which silently ignores errors. - return _PyTime_GetSystemClockWithInfo(t, NULL); + // Avoid _PyTime_TimeUnchecked() which silently ignores errors. + return _PyTime_TimeWithInfo(t, NULL); } @@ -1159,8 +1159,8 @@ should not be relied on."); static int get_monotonic(PyTime_t *t) { - // Avoid _PyTime_GetMonotonicClock() which silently ignores errors. - return _PyTime_GetMonotonicClockWithInfo(t, NULL); + // Avoid _PyTime_MonotonicUnchecked() which silently ignores errors. + return _PyTime_MonotonicWithInfo(t, NULL); } @@ -1198,8 +1198,8 @@ Monotonic clock, cannot go backward, as nanoseconds."); static int get_perf_counter(PyTime_t *t) { - // Avoid _PyTime_GetPerfCounter() which silently ignores errors. - return _PyTime_GetPerfCounterWithInfo(t, NULL); + // Avoid _PyTime_PerfCounterUnchecked() which silently ignores errors. + return _PyTime_PerfCounterWithInfo(t, NULL); } @@ -1615,17 +1615,17 @@ time_get_clock_info(PyObject *module, PyObject *args) #endif if (strcmp(name, "time") == 0) { - if (_PyTime_GetSystemClockWithInfo(&t, &info) < 0) { + if (_PyTime_TimeWithInfo(&t, &info) < 0) { return NULL; } } else if (strcmp(name, "monotonic") == 0) { - if (_PyTime_GetMonotonicClockWithInfo(&t, &info) < 0) { + if (_PyTime_MonotonicWithInfo(&t, &info) < 0) { return NULL; } } else if (strcmp(name, "perf_counter") == 0) { - if (_PyTime_GetPerfCounterWithInfo(&t, &info) < 0) { + if (_PyTime_PerfCounterWithInfo(&t, &info) < 0) { return NULL; } } diff --git a/Python/gc.c b/Python/gc.c index 907f29baa3777aa..a031897d235deaf 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -12,7 +12,7 @@ #include "pycore_object_alloc.h" // _PyObject_MallocWithType() #include "pycore_pyerrors.h" #include "pycore_pystate.h" // _PyThreadState_GET() -#include "pycore_time.h" // _PyTime_GetPerfCounter() +#include "pycore_time.h" // _PyTime_PerfCounterUnchecked() #include "pycore_weakref.h" // _PyWeakref_ClearRef() #include "pydtrace.h" @@ -1327,7 +1327,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) if (gcstate->debug & _PyGC_DEBUG_STATS) { PySys_WriteStderr("gc: collecting generation %d...\n", generation); show_stats_each_generations(gcstate); - t1 = _PyTime_GetPerfCounter(); + t1 = _PyTime_PerfCounterUnchecked(); } if (PyDTrace_GC_START_ENABLED()) { @@ -1428,7 +1428,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) debug_cycle("uncollectable", FROM_GC(gc)); } if (gcstate->debug & _PyGC_DEBUG_STATS) { - double d = _PyTime_AsSecondsDouble(_PyTime_GetPerfCounter() - t1); + double d = _PyTime_AsSecondsDouble(_PyTime_PerfCounterUnchecked() - t1); PySys_WriteStderr( "gc: done, %zd unreachable, %zd uncollectable, %.4fs elapsed\n", n+m, n, d); diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 88c9c4ae5d77b92..4d886ee369db11f 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -1108,7 +1108,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) if (gcstate->debug & _PyGC_DEBUG_STATS) { PySys_WriteStderr("gc: collecting generation %d...\n", generation); show_stats_each_generations(gcstate); - t1 = _PyTime_GetPerfCounter(); + t1 = _PyTime_PerfCounterUnchecked(); } if (PyDTrace_GC_START_ENABLED()) { @@ -1136,7 +1136,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) n = state.uncollectable; if (gcstate->debug & _PyGC_DEBUG_STATS) { - double d = _PyTime_AsSecondsDouble(_PyTime_GetPerfCounter() - t1); + double d = _PyTime_AsSecondsDouble(_PyTime_PerfCounterUnchecked() - t1); PySys_WriteStderr( "gc: done, %zd unreachable, %zd uncollectable, %.4fs elapsed\n", n+m, n, d); diff --git a/Python/import.c b/Python/import.c index a8fed67755256e3..dc92708c8b6ea0b 100644 --- a/Python/import.c +++ b/Python/import.c @@ -13,7 +13,7 @@ #include "pycore_pymem.h" // _PyMem_SetDefaultAllocator() #include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_sysmodule.h" // _PySys_Audit() -#include "pycore_time.h" // _PyTime_GetPerfCounter() +#include "pycore_time.h" // _PyTime_PerfCounterUnchecked() #include "pycore_weakref.h" // _PyWeakref_GET_REF() #include "marshal.h" // PyMarshal_ReadObjectFromString() @@ -2748,7 +2748,7 @@ import_find_and_load(PyThreadState *tstate, PyObject *abs_name) #undef header import_level++; - t1 = _PyTime_GetPerfCounter(); + t1 = _PyTime_PerfCounterUnchecked(); accumulated = 0; } @@ -2763,7 +2763,7 @@ import_find_and_load(PyThreadState *tstate, PyObject *abs_name) mod != NULL); if (import_time) { - PyTime_t cum = _PyTime_GetPerfCounter() - t1; + PyTime_t cum = _PyTime_PerfCounterUnchecked() - t1; import_level--; fprintf(stderr, "import time: %9ld | %10ld | %*s%s\n", diff --git a/Python/lock.c b/Python/lock.c index a4b044ecff0d70d..5fa8bf78da23808 100644 --- a/Python/lock.c +++ b/Python/lock.c @@ -5,7 +5,7 @@ #include "pycore_lock.h" #include "pycore_parking_lot.h" #include "pycore_semaphore.h" -#include "pycore_time.h" // _PyTime_GetMonotonicClock() +#include "pycore_time.h" // _PyTime_MonotonicUnchecked() #ifdef MS_WINDOWS # define WIN32_LEAN_AND_MEAN @@ -66,7 +66,7 @@ _PyMutex_LockTimed(PyMutex *m, PyTime_t timeout, _PyLockFlags flags) return PY_LOCK_FAILURE; } - PyTime_t now = _PyTime_GetMonotonicClock(); + PyTime_t now = _PyTime_MonotonicUnchecked(); PyTime_t endtime = 0; if (timeout > 0) { endtime = _PyTime_Add(now, timeout); @@ -143,7 +143,7 @@ mutex_unpark(PyMutex *m, struct mutex_entry *entry, int has_more_waiters) { uint8_t v = 0; if (entry) { - PyTime_t now = _PyTime_GetMonotonicClock(); + PyTime_t now = _PyTime_MonotonicUnchecked(); int should_be_fair = now > entry->time_to_be_fair; entry->handed_off = should_be_fair; diff --git a/Python/parking_lot.c b/Python/parking_lot.c index 9bf8376e485ea49..0a897f9952f648e 100644 --- a/Python/parking_lot.c +++ b/Python/parking_lot.c @@ -6,7 +6,7 @@ #include "pycore_pyerrors.h" // _Py_FatalErrorFormat #include "pycore_pystate.h" // _PyThreadState_GET #include "pycore_semaphore.h" // _PySemaphore -#include "pycore_time.h" //_PyTime_GetMonotonicClock() +#include "pycore_time.h" //_PyTime_MonotonicUnchecked() #include <stdbool.h> @@ -120,13 +120,13 @@ _PySemaphore_PlatformWait(_PySemaphore *sema, PyTime_t timeout) struct timespec ts; #if defined(CLOCK_MONOTONIC) && defined(HAVE_SEM_CLOCKWAIT) - PyTime_t deadline = _PyTime_Add(_PyTime_GetMonotonicClock(), timeout); + PyTime_t deadline = _PyTime_Add(_PyTime_MonotonicUnchecked(), timeout); _PyTime_AsTimespec_clamp(deadline, &ts); err = sem_clockwait(&sema->platform_sem, CLOCK_MONOTONIC, &ts); #else - PyTime_t deadline = _PyTime_Add(_PyTime_GetSystemClock(), timeout); + PyTime_t deadline = _PyTime_Add(_PyTime_TimeUnchecked(), timeout); _PyTime_AsTimespec_clamp(deadline, &ts); @@ -163,7 +163,7 @@ _PySemaphore_PlatformWait(_PySemaphore *sema, PyTime_t timeout) _PyTime_AsTimespec_clamp(timeout, &ts); err = pthread_cond_timedwait_relative_np(&sema->cond, &sema->mutex, &ts); #else - PyTime_t deadline = _PyTime_Add(_PyTime_GetSystemClock(), timeout); + PyTime_t deadline = _PyTime_Add(_PyTime_TimeUnchecked(), timeout); _PyTime_AsTimespec_clamp(deadline, &ts); err = pthread_cond_timedwait(&sema->cond, &sema->mutex, &ts); diff --git a/Python/pytime.c b/Python/pytime.c index f29337eb5364090..c3534d9a1ca44bc 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -1032,7 +1032,7 @@ py_get_system_clock(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) PyTime_t -_PyTime_GetSystemClock(void) +_PyTime_TimeUnchecked(void) { PyTime_t t; if (py_get_system_clock(&t, NULL, 0) < 0) { @@ -1048,8 +1048,6 @@ int PyTime_Time(PyTime_t *result) { if (py_get_system_clock(result, NULL, 1) < 0) { - // If clock_gettime(CLOCK_REALTIME) or gettimeofday() fails: - // silently ignore the failure and return 0. *result = 0; return -1; } @@ -1057,7 +1055,7 @@ PyTime_Time(PyTime_t *result) } int -_PyTime_GetSystemClockWithInfo(PyTime_t *t, _Py_clock_info_t *info) +_PyTime_TimeWithInfo(PyTime_t *t, _Py_clock_info_t *info) { return py_get_system_clock(t, info, 1); } @@ -1224,7 +1222,7 @@ py_get_monotonic_clock(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) PyTime_t -_PyTime_GetMonotonicClock(void) +_PyTime_MonotonicUnchecked(void) { PyTime_t t; if (py_get_monotonic_clock(&t, NULL, 0) < 0) { @@ -1248,7 +1246,7 @@ PyTime_Monotonic(PyTime_t *result) int -_PyTime_GetMonotonicClockWithInfo(PyTime_t *tp, _Py_clock_info_t *info) +_PyTime_MonotonicWithInfo(PyTime_t *tp, _Py_clock_info_t *info) { return py_get_monotonic_clock(tp, info, 1); } @@ -1325,18 +1323,18 @@ py_get_win_perf_counter(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) int -_PyTime_GetPerfCounterWithInfo(PyTime_t *t, _Py_clock_info_t *info) +_PyTime_PerfCounterWithInfo(PyTime_t *t, _Py_clock_info_t *info) { #ifdef MS_WINDOWS return py_get_win_perf_counter(t, info, 1); #else - return _PyTime_GetMonotonicClockWithInfo(t, info); + return _PyTime_MonotonicWithInfo(t, info); #endif } PyTime_t -_PyTime_GetPerfCounter(void) +_PyTime_PerfCounterUnchecked(void) { PyTime_t t; int res; @@ -1443,7 +1441,7 @@ _PyTime_gmtime(time_t t, struct tm *tm) PyTime_t _PyDeadline_Init(PyTime_t timeout) { - PyTime_t now = _PyTime_GetMonotonicClock(); + PyTime_t now = _PyTime_MonotonicUnchecked(); return _PyTime_Add(now, timeout); } @@ -1451,6 +1449,6 @@ _PyDeadline_Init(PyTime_t timeout) PyTime_t _PyDeadline_Get(PyTime_t deadline) { - PyTime_t now = _PyTime_GetMonotonicClock(); + PyTime_t now = _PyTime_MonotonicUnchecked(); return deadline - now; } diff --git a/Python/thread_nt.h b/Python/thread_nt.h index 307352f592e70e9..e7591600c6416a4 100644 --- a/Python/thread_nt.h +++ b/Python/thread_nt.h @@ -78,7 +78,7 @@ EnterNonRecursiveMutex(PNRMUTEX mutex, DWORD milliseconds) } else if (milliseconds != 0) { /* wait at least until the deadline */ PyTime_t nanoseconds = _PyTime_FromNanoseconds((PyTime_t)milliseconds * 1000000); - PyTime_t deadline = _PyTime_Add(_PyTime_GetPerfCounter(), nanoseconds); + PyTime_t deadline = _PyTime_Add(_PyTime_PerfCounterUnchecked(), nanoseconds); while (mutex->locked) { PyTime_t microseconds = _PyTime_AsMicroseconds(nanoseconds, _PyTime_ROUND_TIMEOUT); @@ -86,7 +86,7 @@ EnterNonRecursiveMutex(PNRMUTEX mutex, DWORD milliseconds) result = WAIT_FAILED; break; } - nanoseconds = deadline - _PyTime_GetPerfCounter(); + nanoseconds = deadline - _PyTime_PerfCounterUnchecked(); if (nanoseconds <= 0) { break; } diff --git a/Python/thread_pthread.h b/Python/thread_pthread.h index 9db6a4666f510c4..17f6ae7eb70553d 100644 --- a/Python/thread_pthread.h +++ b/Python/thread_pthread.h @@ -154,12 +154,12 @@ _PyThread_cond_after(long long us, struct timespec *abs) PyTime_t t; #ifdef CONDATTR_MONOTONIC if (condattr_monotonic) { - t = _PyTime_GetMonotonicClock(); + t = _PyTime_MonotonicUnchecked(); } else #endif { - t = _PyTime_GetSystemClock(); + t = _PyTime_TimeUnchecked(); } t = _PyTime_Add(t, timeout); _PyTime_AsTimespec_clamp(t, abs); @@ -502,7 +502,7 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds, struct timespec abs_timeout; // Local scope for deadline { - PyTime_t deadline = _PyTime_Add(_PyTime_GetMonotonicClock(), timeout); + PyTime_t deadline = _PyTime_Add(_PyTime_MonotonicUnchecked(), timeout); _PyTime_AsTimespec_clamp(deadline, &abs_timeout); } #else @@ -518,7 +518,7 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds, status = fix_status(sem_clockwait(thelock, CLOCK_MONOTONIC, &abs_timeout)); #else - PyTime_t abs_time = _PyTime_Add(_PyTime_GetSystemClock(), + PyTime_t abs_time = _PyTime_Add(_PyTime_TimeUnchecked(), timeout); struct timespec ts; _PyTime_AsTimespec_clamp(abs_time, &ts); diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index 94be33758495977..e9b81cf4c7d6531 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -361,6 +361,7 @@ Python/import.c - _PyImport_Inittab - Python/import.c - _PySys_ImplCacheTag - Python/intrinsics.c - _PyIntrinsics_UnaryFunctions - Python/intrinsics.c - _PyIntrinsics_BinaryFunctions - +Python/lock.c - TIME_TO_BE_FAIR_NS - Python/opcode_targets.h - opcode_targets - Python/perf_trampoline.c - _Py_perfmap_callbacks - Python/pyhash.c - PyHash_Func - From 145bc2d638370cb6d3da361c6dc05c5bc29f0d11 Mon Sep 17 00:00:00 2001 From: Victor Stinner <vstinner@python.org> Date: Wed, 21 Feb 2024 00:31:30 +0100 Subject: [PATCH 388/507] gh-110850: Use public PyTime functions (#115746) Replace private _PyTime functions with public PyTime functions. random_seed_time_pid() now reports errors to its caller. --- Include/internal/pycore_time.h | 1 - Modules/_datetimemodule.c | 6 ++++- Modules/_queuemodule.c | 2 +- Modules/_randommodule.c | 18 ++++++++----- Modules/_testinternalcapi/pytime.c | 4 +-- Modules/_testsinglephase.c | 9 +++---- Modules/_threadmodule.c | 2 +- Modules/selectmodule.c | 2 +- Modules/socketmodule.c | 4 +-- Modules/timemodule.c | 42 +++++++----------------------- Python/gc.c | 2 +- Python/gc_free_threading.c | 2 +- 12 files changed, 39 insertions(+), 55 deletions(-) diff --git a/Include/internal/pycore_time.h b/Include/internal/pycore_time.h index 9692bbc89711a55..682aee2170bdaef 100644 --- a/Include/internal/pycore_time.h +++ b/Include/internal/pycore_time.h @@ -321,7 +321,6 @@ extern int _PyTime_PerfCounterWithInfo( // Alias for backward compatibility #define _PyTime_MIN PyTime_MIN #define _PyTime_MAX PyTime_MAX -#define _PyTime_AsSecondsDouble PyTime_AsSecondsDouble // --- _PyDeadline ----------------------------------------------------------- diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 9fd59c34de6cefc..a626bda2ea9be9f 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -5133,7 +5133,11 @@ datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp, static PyObject * datetime_best_possible(PyObject *cls, TM_FUNC f, PyObject *tzinfo) { - PyTime_t ts = _PyTime_TimeUnchecked(); + PyTime_t ts; + if (PyTime_Time(&ts) < 0) { + return NULL; + } + time_t secs; int us; diff --git a/Modules/_queuemodule.c b/Modules/_queuemodule.c index 5ef1cea24dce68c..5db9b645849fcd8 100644 --- a/Modules/_queuemodule.c +++ b/Modules/_queuemodule.c @@ -6,7 +6,7 @@ #include "pycore_ceval.h" // Py_MakePendingCalls() #include "pycore_moduleobject.h" // _PyModule_GetState() #include "pycore_parking_lot.h" -#include "pycore_time.h" // PyTime_t +#include "pycore_time.h" // _PyTime_FromSecondsObject() #include <stdbool.h> #include <stddef.h> // offsetof() diff --git a/Modules/_randommodule.c b/Modules/_randommodule.c index 920645b453536ab..56b891dfe0f85f8 100644 --- a/Modules/_randommodule.c +++ b/Modules/_randommodule.c @@ -75,7 +75,6 @@ #include "pycore_modsupport.h" // _PyArg_NoKeywords() #include "pycore_moduleobject.h" // _PyModule_GetState() #include "pycore_pylifecycle.h" // _PyOS_URandomNonblock() -#include "pycore_time.h" // _PyTime_TimeUnchecked() #ifdef HAVE_UNISTD_H # include <unistd.h> // getpid() @@ -260,13 +259,15 @@ random_seed_urandom(RandomObject *self) return 0; } -static void +static int random_seed_time_pid(RandomObject *self) { PyTime_t now; - uint32_t key[5]; + if (PyTime_Time(&now) < 0) { + return -1; + } - now = _PyTime_TimeUnchecked(); + uint32_t key[5]; key[0] = (uint32_t)(now & 0xffffffffU); key[1] = (uint32_t)(now >> 32); @@ -278,11 +279,14 @@ random_seed_time_pid(RandomObject *self) key[2] = 0; #endif - now = _PyTime_MonotonicUnchecked(); + if (PyTime_Monotonic(&now) < 0) { + return -1; + } key[3] = (uint32_t)(now & 0xffffffffU); key[4] = (uint32_t)(now >> 32); init_by_array(self, key, Py_ARRAY_LENGTH(key)); + return 0; } static int @@ -300,7 +304,9 @@ random_seed(RandomObject *self, PyObject *arg) /* Reading system entropy failed, fall back on the worst entropy: use the current time and process identifier. */ - random_seed_time_pid(self); + if (random_seed_time_pid(self) < 0) { + return -1; + } } return 0; } diff --git a/Modules/_testinternalcapi/pytime.c b/Modules/_testinternalcapi/pytime.c index 11a02413b8c114c..2abe5c2b725713b 100644 --- a/Modules/_testinternalcapi/pytime.c +++ b/Modules/_testinternalcapi/pytime.c @@ -2,10 +2,10 @@ #include "parts.h" -#include "pycore_time.h" +#include "pycore_time.h" // _PyTime_FromSeconds() #ifdef MS_WINDOWS -# include <winsock2.h> // struct timeval +# include <winsock2.h> // struct timeval #endif diff --git a/Modules/_testsinglephase.c b/Modules/_testsinglephase.c index 58d22e2d5dbe562..092673a9ea43e11 100644 --- a/Modules/_testsinglephase.c +++ b/Modules/_testsinglephase.c @@ -8,7 +8,6 @@ //#include <time.h> #include "Python.h" #include "pycore_namespace.h" // _PyNamespace_New() -#include "pycore_time.h" // PyTime_t typedef struct { @@ -71,13 +70,13 @@ _set_initialized(PyTime_t *initialized) { /* We go strictly monotonic to ensure each time is unique. */ PyTime_t prev; - if (_PyTime_MonotonicWithInfo(&prev, NULL) != 0) { + if (PyTime_Monotonic(&prev) != 0) { return -1; } /* We do a busy sleep since the interval should be super short. */ PyTime_t t; do { - if (_PyTime_MonotonicWithInfo(&t, NULL) != 0) { + if (PyTime_Monotonic(&t) != 0) { return -1; } } while (t == prev); @@ -136,7 +135,7 @@ init_module(PyObject *module, module_state *state) return -1; } - double d = _PyTime_AsSecondsDouble(state->initialized); + double d = PyTime_AsSecondsDouble(state->initialized); if (PyModule_Add(module, "_module_initialized", PyFloat_FromDouble(d)) < 0) { return -1; } @@ -157,7 +156,7 @@ common_state_initialized(PyObject *self, PyObject *Py_UNUSED(ignored)) if (state == NULL) { Py_RETURN_NONE; } - double d = _PyTime_AsSecondsDouble(state->initialized); + double d = PyTime_AsSecondsDouble(state->initialized); return PyFloat_FromDouble(d); } diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index addaafb4f860398..25e100278790552 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1948,7 +1948,7 @@ thread_module_exec(PyObject *module) // TIMEOUT_MAX double timeout_max = (double)PY_TIMEOUT_MAX * 1e-6; - double time_max = _PyTime_AsSecondsDouble(_PyTime_MAX); + double time_max = PyTime_AsSecondsDouble(_PyTime_MAX); timeout_max = Py_MIN(timeout_max, time_max); // Round towards minus infinity timeout_max = floor(timeout_max); diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c index 57d55a5611f1215..f16173aafa7d3c8 100644 --- a/Modules/selectmodule.c +++ b/Modules/selectmodule.c @@ -15,7 +15,7 @@ #include "Python.h" #include "pycore_fileutils.h" // _Py_set_inheritable() #include "pycore_import.h" // _PyImport_GetModuleAttrString() -#include "pycore_time.h" // PyTime_t +#include "pycore_time.h" // _PyTime_FromSecondsObject() #include <stdbool.h> #include <stddef.h> // offsetof() diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 836cf6c05b31965..cd9a803648be712 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -3113,7 +3113,7 @@ sock_gettimeout(PySocketSockObject *s, PyObject *Py_UNUSED(ignored)) Py_RETURN_NONE; } else { - double seconds = _PyTime_AsSecondsDouble(s->sock_timeout); + double seconds = PyTime_AsSecondsDouble(s->sock_timeout); return PyFloat_FromDouble(seconds); } } @@ -6917,7 +6917,7 @@ socket_getdefaulttimeout(PyObject *self, PyObject *Py_UNUSED(ignored)) Py_RETURN_NONE; } else { - double seconds = _PyTime_AsSecondsDouble(state->defaulttimeout); + double seconds = PyTime_AsSecondsDouble(state->defaulttimeout); return PyFloat_FromDouble(seconds); } } diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 28dba903d2b9e87..ac96ed40a9bb278 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -98,24 +98,16 @@ get_time_state(PyObject *module) static PyObject* _PyFloat_FromPyTime(PyTime_t t) { - double d = _PyTime_AsSecondsDouble(t); + double d = PyTime_AsSecondsDouble(t); return PyFloat_FromDouble(d); } -static int -get_system_time(PyTime_t *t) -{ - // Avoid _PyTime_TimeUnchecked() which silently ignores errors. - return _PyTime_TimeWithInfo(t, NULL); -} - - static PyObject * time_time(PyObject *self, PyObject *unused) { PyTime_t t; - if (get_system_time(&t) < 0) { + if (PyTime_Time(&t) < 0) { return NULL; } return _PyFloat_FromPyTime(t); @@ -132,7 +124,7 @@ static PyObject * time_time_ns(PyObject *self, PyObject *unused) { PyTime_t t; - if (get_system_time(&t) < 0) { + if (PyTime_Time(&t) < 0) { return NULL; } return _PyTime_AsNanosecondsObject(t); @@ -1156,19 +1148,11 @@ should not be relied on."); #endif /* HAVE_WORKING_TZSET */ -static int -get_monotonic(PyTime_t *t) -{ - // Avoid _PyTime_MonotonicUnchecked() which silently ignores errors. - return _PyTime_MonotonicWithInfo(t, NULL); -} - - static PyObject * time_monotonic(PyObject *self, PyObject *unused) { PyTime_t t; - if (get_monotonic(&t) < 0) { + if (PyTime_Monotonic(&t) < 0) { return NULL; } return _PyFloat_FromPyTime(t); @@ -1183,7 +1167,7 @@ static PyObject * time_monotonic_ns(PyObject *self, PyObject *unused) { PyTime_t t; - if (get_monotonic(&t) < 0) { + if (PyTime_Monotonic(&t) < 0) { return NULL; } return _PyTime_AsNanosecondsObject(t); @@ -1195,19 +1179,11 @@ PyDoc_STRVAR(monotonic_ns_doc, Monotonic clock, cannot go backward, as nanoseconds."); -static int -get_perf_counter(PyTime_t *t) -{ - // Avoid _PyTime_PerfCounterUnchecked() which silently ignores errors. - return _PyTime_PerfCounterWithInfo(t, NULL); -} - - static PyObject * time_perf_counter(PyObject *self, PyObject *unused) { PyTime_t t; - if (get_perf_counter(&t) < 0) { + if (PyTime_PerfCounter(&t) < 0) { return NULL; } return _PyFloat_FromPyTime(t); @@ -1223,7 +1199,7 @@ static PyObject * time_perf_counter_ns(PyObject *self, PyObject *unused) { PyTime_t t; - if (get_perf_counter(&t) < 0) { + if (PyTime_PerfCounter(&t) < 0) { return NULL; } return _PyTime_AsNanosecondsObject(t); @@ -2190,7 +2166,7 @@ pysleep(PyTime_t timeout) PyTime_t deadline, monotonic; int err = 0; - if (get_monotonic(&monotonic) < 0) { + if (PyTime_Monotonic(&monotonic) < 0) { return -1; } deadline = monotonic + timeout; @@ -2243,7 +2219,7 @@ pysleep(PyTime_t timeout) } #ifndef HAVE_CLOCK_NANOSLEEP - if (get_monotonic(&monotonic) < 0) { + if (PyTime_Monotonic(&monotonic) < 0) { return -1; } timeout = deadline - monotonic; diff --git a/Python/gc.c b/Python/gc.c index a031897d235deaf..ea3b596d1713dfc 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -1428,7 +1428,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) debug_cycle("uncollectable", FROM_GC(gc)); } if (gcstate->debug & _PyGC_DEBUG_STATS) { - double d = _PyTime_AsSecondsDouble(_PyTime_PerfCounterUnchecked() - t1); + double d = PyTime_AsSecondsDouble(_PyTime_PerfCounterUnchecked() - t1); PySys_WriteStderr( "gc: done, %zd unreachable, %zd uncollectable, %.4fs elapsed\n", n+m, n, d); diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 4d886ee369db11f..14790899825de1a 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -1136,7 +1136,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) n = state.uncollectable; if (gcstate->debug & _PyGC_DEBUG_STATS) { - double d = _PyTime_AsSecondsDouble(_PyTime_PerfCounterUnchecked() - t1); + double d = PyTime_AsSecondsDouble(_PyTime_PerfCounterUnchecked() - t1); PySys_WriteStderr( "gc: done, %zd unreachable, %zd uncollectable, %.4fs elapsed\n", n+m, n, d); From 176df09adbb42bbb50febd02346c32782d39dc4d Mon Sep 17 00:00:00 2001 From: Dino Viehland <dinoviehland@meta.com> Date: Tue, 20 Feb 2024 16:40:37 -0800 Subject: [PATCH 389/507] gh-112075: Make PyDictKeysObject thread-safe (#114741) Adds locking for shared PyDictKeysObject's for dictionaries --- Include/cpython/pyatomic.h | 6 + Include/cpython/pyatomic_gcc.h | 8 + Include/cpython/pyatomic_msc.h | 24 +++ Include/cpython/pyatomic_std.h | 16 +- Include/internal/pycore_dict.h | 6 + Objects/dictobject.c | 290 ++++++++++++++++++++++----------- 6 files changed, 257 insertions(+), 93 deletions(-) diff --git a/Include/cpython/pyatomic.h b/Include/cpython/pyatomic.h index 737eed8b12dd817..c3e132d3877ca51 100644 --- a/Include/cpython/pyatomic.h +++ b/Include/cpython/pyatomic.h @@ -469,6 +469,9 @@ _Py_atomic_load_ptr_acquire(const void *obj); static inline void _Py_atomic_store_ptr_release(void *obj, void *value); +static inline void +_Py_atomic_store_ssize_release(Py_ssize_t *obj, Py_ssize_t value); + static inline void _Py_atomic_store_int_release(int *obj, int value); @@ -484,6 +487,9 @@ _Py_atomic_load_uint64_acquire(const uint64_t *obj); static inline uint32_t _Py_atomic_load_uint32_acquire(const uint32_t *obj); +static inline Py_ssize_t +_Py_atomic_load_ssize_acquire(const Py_ssize_t *obj); + // --- _Py_atomic_fence ------------------------------------------------------ diff --git a/Include/cpython/pyatomic_gcc.h b/Include/cpython/pyatomic_gcc.h index de23edfc6877d23..0b40f81bd8736de 100644 --- a/Include/cpython/pyatomic_gcc.h +++ b/Include/cpython/pyatomic_gcc.h @@ -500,6 +500,10 @@ static inline void _Py_atomic_store_int_release(int *obj, int value) { __atomic_store_n(obj, value, __ATOMIC_RELEASE); } +static inline void +_Py_atomic_store_ssize_release(Py_ssize_t *obj, Py_ssize_t value) +{ __atomic_store_n(obj, value, __ATOMIC_RELEASE); } + static inline int _Py_atomic_load_int_acquire(const int *obj) { return __atomic_load_n(obj, __ATOMIC_ACQUIRE); } @@ -516,6 +520,10 @@ static inline uint32_t _Py_atomic_load_uint32_acquire(const uint32_t *obj) { return __atomic_load_n(obj, __ATOMIC_ACQUIRE); } +static inline Py_ssize_t +_Py_atomic_load_ssize_acquire(const Py_ssize_t *obj) +{ return __atomic_load_n(obj, __ATOMIC_ACQUIRE); } + // --- _Py_atomic_fence ------------------------------------------------------ static inline void diff --git a/Include/cpython/pyatomic_msc.h b/Include/cpython/pyatomic_msc.h index 9809d9806d7b574..3205e253b285466 100644 --- a/Include/cpython/pyatomic_msc.h +++ b/Include/cpython/pyatomic_msc.h @@ -939,6 +939,18 @@ _Py_atomic_store_int_release(int *obj, int value) #endif } +static inline void +_Py_atomic_store_ssize_release(Py_ssize_t *obj, Py_ssize_t value) +{ +#if defined(_M_X64) || defined(_M_IX86) + *(Py_ssize_t volatile *)obj = value; +#elif defined(_M_ARM64) + __stlr64((unsigned __int64 volatile *)obj, (unsigned __int64)value); +#else +# error "no implementation of _Py_atomic_store_ssize_release" +#endif +} + static inline int _Py_atomic_load_int_acquire(const int *obj) { @@ -990,6 +1002,18 @@ _Py_atomic_load_uint32_acquire(const uint32_t *obj) #endif } +static inline Py_ssize_t +_Py_atomic_load_ssize_acquire(const Py_ssize_t *obj) +{ +#if defined(_M_X64) || defined(_M_IX86) + return *(Py_ssize_t volatile *)obj; +#elif defined(_M_ARM64) + return (Py_ssize_t)__ldar64((unsigned __int64 volatile *)obj); +#else +# error "no implementation of _Py_atomic_load_ssize_acquire" +#endif +} + // --- _Py_atomic_fence ------------------------------------------------------ static inline void diff --git a/Include/cpython/pyatomic_std.h b/Include/cpython/pyatomic_std.h index f5bd73a8a49e31b..f3970a45df24f91 100644 --- a/Include/cpython/pyatomic_std.h +++ b/Include/cpython/pyatomic_std.h @@ -879,6 +879,14 @@ _Py_atomic_store_int_release(int *obj, int value) memory_order_release); } +static inline void +_Py_atomic_store_ssize_release(Py_ssize_t *obj, Py_ssize_t value) +{ + _Py_USING_STD; + atomic_store_explicit((_Atomic(Py_ssize_t)*)obj, value, + memory_order_release); +} + static inline int _Py_atomic_load_int_acquire(const int *obj) { @@ -908,7 +916,13 @@ _Py_atomic_load_uint32_acquire(const uint32_t *obj) { _Py_USING_STD; return atomic_load_explicit((const _Atomic(uint32_t)*)obj, - memory_order_acquire); +} + +static inline Py_ssize_t +_Py_atomic_load_ssize_acquire(const Py_ssize_t *obj) +{ + _Py_USING_STD; + return atomic_load_explicit((const _Atomic(Py_ssize_t)*)obj, } diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index e5ef9a8607a83b5..bf7a2abcad38144 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -136,6 +136,11 @@ struct _dictkeysobject { /* Kind of keys */ uint8_t dk_kind; +#ifdef Py_GIL_DISABLED + /* Lock used to protect shared keys */ + PyMutex dk_mutex; +#endif + /* Version number -- Reset to 0 by any modification to keys */ uint32_t dk_version; @@ -145,6 +150,7 @@ struct _dictkeysobject { /* Number of used entries in dk_entries. */ Py_ssize_t dk_nentries; + /* Actual hash table of dk_size entries. It holds indices in dk_entries, or DKIX_EMPTY(-1) or DKIX_DUMMY(-2). diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 25ab21881f8f746..a07267169fdc20f 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -151,9 +151,45 @@ ASSERT_DICT_LOCKED(PyObject *op) } #define ASSERT_DICT_LOCKED(op) ASSERT_DICT_LOCKED(_Py_CAST(PyObject*, op)) -#else +#define LOCK_KEYS(keys) PyMutex_LockFlags(&keys->dk_mutex, _Py_LOCK_DONT_DETACH) +#define UNLOCK_KEYS(keys) PyMutex_Unlock(&keys->dk_mutex) + +#define ASSERT_KEYS_LOCKED(keys) assert(PyMutex_IsLocked(&keys->dk_mutex)) +#define LOAD_SHARED_KEY(key) _Py_atomic_load_ptr_acquire(&key) +#define STORE_SHARED_KEY(key, value) _Py_atomic_store_ptr_release(&key, value) +// Inc refs the keys object, giving the previous value +#define INCREF_KEYS(dk) _Py_atomic_add_ssize(&dk->dk_refcnt, 1) +// Dec refs the keys object, giving the previous value +#define DECREF_KEYS(dk) _Py_atomic_add_ssize(&dk->dk_refcnt, -1) +#define LOAD_KEYS_NENTIRES(keys) _Py_atomic_load_ssize_relaxed(&keys->dk_nentries) + +static inline void split_keys_entry_added(PyDictKeysObject *keys) +{ + ASSERT_KEYS_LOCKED(keys); + + // We increase before we decrease so we never get too small of a value + // when we're racing with reads + _Py_atomic_store_ssize_relaxed(&keys->dk_nentries, keys->dk_nentries + 1); + _Py_atomic_store_ssize_release(&keys->dk_usable, keys->dk_usable - 1); +} + +#else /* Py_GIL_DISABLED */ #define ASSERT_DICT_LOCKED(op) +#define LOCK_KEYS(keys) +#define UNLOCK_KEYS(keys) +#define ASSERT_KEYS_LOCKED(keys) +#define LOAD_SHARED_KEY(key) key +#define STORE_SHARED_KEY(key, value) key = value +#define INCREF_KEYS(dk) dk->dk_refcnt++ +#define DECREF_KEYS(dk) dk->dk_refcnt-- +#define LOAD_KEYS_NENTIRES(keys) keys->dk_nentries + +static inline void split_keys_entry_added(PyDictKeysObject *keys) +{ + keys->dk_usable--; + keys->dk_nentries++; +} #endif @@ -348,7 +384,7 @@ dictkeys_incref(PyDictKeysObject *dk) #ifdef Py_REF_DEBUG _Py_IncRefTotal(_PyInterpreterState_GET()); #endif - dk->dk_refcnt++; + INCREF_KEYS(dk); } static inline void @@ -361,7 +397,7 @@ dictkeys_decref(PyInterpreterState *interp, PyDictKeysObject *dk) #ifdef Py_REF_DEBUG _Py_DecRefTotal(_PyInterpreterState_GET()); #endif - if (--dk->dk_refcnt == 0) { + if (DECREF_KEYS(dk) == 1) { if (DK_IS_UNICODE(dk)) { PyDictUnicodeEntry *entries = DK_UNICODE_ENTRIES(dk); Py_ssize_t i, n; @@ -512,6 +548,9 @@ static PyDictKeysObject empty_keys_struct = { 0, /* dk_log2_size */ 0, /* dk_log2_index_bytes */ DICT_KEYS_UNICODE, /* dk_kind */ +#ifdef Py_GIL_DISABLED + {0}, /* dk_mutex */ +#endif 1, /* dk_version */ 0, /* dk_usable (immutable) */ 0, /* dk_nentries */ @@ -697,6 +736,9 @@ new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode) dk->dk_log2_size = log2_size; dk->dk_log2_index_bytes = log2_bytes; dk->dk_kind = unicode ? DICT_KEYS_UNICODE : DICT_KEYS_GENERAL; +#ifdef Py_GIL_DISABLED + dk->dk_mutex = (PyMutex){0}; +#endif dk->dk_nentries = 0; dk->dk_usable = usable; dk->dk_version = 0; @@ -785,7 +827,19 @@ new_dict(PyInterpreterState *interp, static inline size_t shared_keys_usable_size(PyDictKeysObject *keys) { +#ifdef Py_GIL_DISABLED + // dk_usable will decrease for each instance that is created and each + // value that is added. dk_nentries will increase for each value that + // is added. We want to always return the right value or larger. + // We therefore increase dk_nentries first and we decrease dk_usable + // second, and conversely here we read dk_usable first and dk_entries + // second (to avoid the case where we read entries before the increment + // and read usable after the decrement) + return (size_t)(_Py_atomic_load_ssize_acquire(&keys->dk_usable) + + _Py_atomic_load_ssize_acquire(&keys->dk_nentries)); +#else return (size_t)keys->dk_nentries + (size_t)keys->dk_usable; +#endif } /* Consumes a reference to the keys object */ @@ -1074,7 +1128,7 @@ _Py_dict_lookup(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **valu PyDictKeysObject *dk; DictKeysKind kind; Py_ssize_t ix; - + // TODO: Thread safety start: dk = mp->ma_keys; kind = dk->dk_kind; @@ -1190,8 +1244,7 @@ _PyDict_MaybeUntrack(PyObject *op) /* Internal function to find slot for an item from its hash when it is known that the key is not present in the dict. - - The dict must be combined. */ + */ static Py_ssize_t find_empty_slot(PyDictKeysObject *keys, Py_hash_t hash) { @@ -1215,9 +1268,11 @@ insertion_resize(PyInterpreterState *interp, PyDictObject *mp, int unicode) } static Py_ssize_t -insert_into_dictkeys(PyDictKeysObject *keys, PyObject *name) +insert_into_splitdictkeys(PyDictKeysObject *keys, PyObject *name) { assert(PyUnicode_CheckExact(name)); + ASSERT_KEYS_LOCKED(keys); + Py_hash_t hash = unicode_get_hash(name); if (hash == -1) { hash = PyUnicode_Type.tp_hash(name); @@ -1239,13 +1294,81 @@ insert_into_dictkeys(PyDictKeysObject *keys, PyObject *name) dictkeys_set_index(keys, hashpos, ix); assert(ep->me_key == NULL); ep->me_key = Py_NewRef(name); - keys->dk_usable--; - keys->dk_nentries++; + split_keys_entry_added(keys); } assert (ix < SHARED_KEYS_MAX_SIZE); return ix; } + +static inline int +insert_combined_dict(PyInterpreterState *interp, PyDictObject *mp, + Py_hash_t hash, PyObject *key, PyObject *value) +{ + if (mp->ma_keys->dk_usable <= 0) { + /* Need to resize. */ + if (insertion_resize(interp, mp, 1) < 0) { + return -1; + } + } + + Py_ssize_t hashpos = find_empty_slot(mp->ma_keys, hash); + dictkeys_set_index(mp->ma_keys, hashpos, mp->ma_keys->dk_nentries); + + if (DK_IS_UNICODE(mp->ma_keys)) { + PyDictUnicodeEntry *ep; + ep = &DK_UNICODE_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries]; + ep->me_key = key; + ep->me_value = value; + } + else { + PyDictKeyEntry *ep; + ep = &DK_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries]; + ep->me_key = key; + ep->me_hash = hash; + ep->me_value = value; + } + mp->ma_keys->dk_usable--; + mp->ma_keys->dk_nentries++; + assert(mp->ma_keys->dk_usable >= 0); + return 0; +} + +static int +insert_split_dict(PyInterpreterState *interp, PyDictObject *mp, + Py_hash_t hash, PyObject *key, PyObject *value) +{ + PyDictKeysObject *keys = mp->ma_keys; + LOCK_KEYS(keys); + if (keys->dk_usable <= 0) { + /* Need to resize. */ + UNLOCK_KEYS(keys); + int ins = insertion_resize(interp, mp, 1); + if (ins < 0) { + return -1; + } + assert(!_PyDict_HasSplitTable(mp)); + return insert_combined_dict(interp, mp, hash, key, value); + } + + Py_ssize_t hashpos = find_empty_slot(keys, hash); + dictkeys_set_index(keys, hashpos, keys->dk_nentries); + + PyDictUnicodeEntry *ep; + ep = &DK_UNICODE_ENTRIES(keys)[keys->dk_nentries]; + STORE_SHARED_KEY(ep->me_key, key); + + Py_ssize_t index = keys->dk_nentries; + _PyDictValues_AddToInsertionOrder(mp->ma_values, index); + assert (mp->ma_values->values[index] == NULL); + mp->ma_values->values[index] = value; + + split_keys_entry_added(keys); + assert(keys->dk_usable >= 0); + UNLOCK_KEYS(keys); + return 0; +} + /* Internal routine to insert a new item into the table. Used both by the internal resize routine and by the public insert routine. @@ -1278,41 +1401,19 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp, /* Insert into new slot. */ mp->ma_keys->dk_version = 0; assert(old_value == NULL); - if (mp->ma_keys->dk_usable <= 0) { - /* Need to resize. */ - if (insertion_resize(interp, mp, 1) < 0) - goto Fail; - } - - Py_ssize_t hashpos = find_empty_slot(mp->ma_keys, hash); - dictkeys_set_index(mp->ma_keys, hashpos, mp->ma_keys->dk_nentries); - if (DK_IS_UNICODE(mp->ma_keys)) { - PyDictUnicodeEntry *ep; - ep = &DK_UNICODE_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries]; - ep->me_key = key; - if (mp->ma_values) { - Py_ssize_t index = mp->ma_keys->dk_nentries; - _PyDictValues_AddToInsertionOrder(mp->ma_values, index); - assert (mp->ma_values->values[index] == NULL); - mp->ma_values->values[index] = value; - } - else { - ep->me_value = value; + if (!_PyDict_HasSplitTable(mp)) { + if (insert_combined_dict(interp, mp, hash, key, value) < 0) { + goto Fail; } } else { - PyDictKeyEntry *ep; - ep = &DK_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries]; - ep->me_key = key; - ep->me_hash = hash; - ep->me_value = value; + if (insert_split_dict(interp, mp, hash, key, value) < 0) + goto Fail; } + mp->ma_used++; mp->ma_version_tag = new_version; - mp->ma_keys->dk_usable--; - mp->ma_keys->dk_nentries++; - assert(mp->ma_keys->dk_usable >= 0); ASSERT_CONSISTENT(mp); return 0; } @@ -1482,7 +1583,8 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp, Py_ssize_t numentries = mp->ma_used; if (oldvalues != NULL) { - PyDictUnicodeEntry *oldentries = DK_UNICODE_ENTRIES(oldkeys); + LOCK_KEYS(oldkeys); + PyDictUnicodeEntry *oldentries = DK_UNICODE_ENTRIES(oldkeys); /* Convert split table into new combined table. * We must incref keys; we can transfer values. */ @@ -1512,6 +1614,7 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp, } build_indices_unicode(mp->ma_keys, newentries, numentries); } + UNLOCK_KEYS(oldkeys); dictkeys_decref(interp, oldkeys); mp->ma_values = NULL; free_values(oldvalues); @@ -2025,7 +2128,7 @@ delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix, mp->ma_used--; mp->ma_version_tag = new_version; - if (mp->ma_values) { + if (_PyDict_HasSplitTable(mp)) { assert(old_value == mp->ma_values->values[ix]); mp->ma_values->values[ix] = NULL; assert(ix < SHARED_KEYS_MAX_SIZE); @@ -2244,14 +2347,13 @@ _PyDict_Next(PyObject *op, Py_ssize_t *ppos, PyObject **pkey, mp = (PyDictObject *)op; i = *ppos; - if (mp->ma_values) { + if (_PyDict_HasSplitTable(mp)) { assert(mp->ma_used <= SHARED_KEYS_MAX_SIZE); if (i < 0 || i >= mp->ma_used) return 0; int index = get_index_from_order(mp, i); value = mp->ma_values->values[index]; - - key = DK_UNICODE_ENTRIES(mp->ma_keys)[index].me_key; + key = LOAD_SHARED_KEY(DK_UNICODE_ENTRIES(mp->ma_keys)[index].me_key); hash = unicode_get_hash(key); assert(value != NULL); } @@ -3143,7 +3245,7 @@ dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *othe dictkeys_decref(interp, mp->ma_keys); mp->ma_keys = keys; - if (mp->ma_values != NULL) { + if (_PyDict_HasSplitTable(mp)) { free_values(mp->ma_values); mp->ma_values = NULL; } @@ -3484,7 +3586,7 @@ dict_equal_lock_held(PyDictObject *a, PyDictObject *b) /* can't be equal if # of entries differ */ return 0; /* Same # of entries -- check all of 'em. Exit early on any diff. */ - for (i = 0; i < a->ma_keys->dk_nentries; i++) { + for (i = 0; i < LOAD_KEYS_NENTIRES(a->ma_keys); i++) { PyObject *key, *aval; Py_hash_t hash; if (DK_IS_UNICODE(a->ma_keys)) { @@ -3494,7 +3596,7 @@ dict_equal_lock_held(PyDictObject *a, PyDictObject *b) continue; } hash = unicode_get_hash(key); - if (a->ma_values) + if (_PyDict_HasSplitTable(a)) aval = a->ma_values->values[i]; else aval = ep->me_value; @@ -3697,42 +3799,31 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu interp, PyDict_EVENT_ADDED, mp, key, default_value); mp->ma_keys->dk_version = 0; value = default_value; - if (mp->ma_keys->dk_usable <= 0) { - if (insertion_resize(interp, mp, 1) < 0) { + + if (!_PyDict_HasSplitTable(mp)) { + if (insert_combined_dict(interp, mp, hash, Py_NewRef(key), Py_NewRef(value)) < 0) { + Py_DECREF(key); + Py_DECREF(value); if (result) { *result = NULL; } return -1; } } - Py_ssize_t hashpos = find_empty_slot(mp->ma_keys, hash); - dictkeys_set_index(mp->ma_keys, hashpos, mp->ma_keys->dk_nentries); - if (DK_IS_UNICODE(mp->ma_keys)) { - assert(PyUnicode_CheckExact(key)); - PyDictUnicodeEntry *ep = &DK_UNICODE_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries]; - ep->me_key = Py_NewRef(key); - if (_PyDict_HasSplitTable(mp)) { - Py_ssize_t index = (int)mp->ma_keys->dk_nentries; - assert(index < SHARED_KEYS_MAX_SIZE); - assert(mp->ma_values->values[index] == NULL); - mp->ma_values->values[index] = Py_NewRef(value); - _PyDictValues_AddToInsertionOrder(mp->ma_values, index); - } - else { - ep->me_value = Py_NewRef(value); - } - } else { - PyDictKeyEntry *ep = &DK_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries]; - ep->me_key = Py_NewRef(key); - ep->me_hash = hash; - ep->me_value = Py_NewRef(value); + if (insert_split_dict(interp, mp, hash, Py_NewRef(key), Py_NewRef(value)) < 0) { + Py_DECREF(key); + Py_DECREF(value); + if (result) { + *result = NULL; + } + return -1; + } } + MAINTAIN_TRACKING(mp, key, value); mp->ma_used++; mp->ma_version_tag = new_version; - mp->ma_keys->dk_usable--; - mp->ma_keys->dk_nentries++; assert(mp->ma_keys->dk_usable >= 0); ASSERT_CONSISTENT(mp); if (result) { @@ -3881,8 +3972,8 @@ dict_popitem_impl(PyDictObject *self) return NULL; } /* Convert split table to combined table */ - if (self->ma_keys->dk_kind == DICT_KEYS_SPLIT) { - if (dictresize(interp, self, DK_LOG_SIZE(self->ma_keys), 1)) { + if (_PyDict_HasSplitTable(self)) { + if (dictresize(interp, self, DK_LOG_SIZE(self->ma_keys), 1) < 0) { Py_DECREF(res); return NULL; } @@ -3949,7 +4040,7 @@ dict_traverse(PyObject *op, visitproc visit, void *arg) Py_ssize_t i, n = keys->dk_nentries; if (DK_IS_UNICODE(keys)) { - if (mp->ma_values != NULL) { + if (_PyDict_HasSplitTable(mp)) { for (i = 0; i < n; i++) { Py_VISIT(mp->ma_values->values[i]); } @@ -3986,7 +4077,7 @@ static Py_ssize_t sizeof_lock_held(PyDictObject *mp) { size_t res = _PyObject_SIZE(Py_TYPE(mp)); - if (mp->ma_values) { + if (_PyDict_HasSplitTable(mp)) { res += shared_keys_usable_size(mp->ma_keys) * sizeof(PyObject*); } /* If the dictionary is split, the keys portion is accounted-for @@ -4431,7 +4522,7 @@ dictiter_new(PyDictObject *dict, PyTypeObject *itertype) if (itertype == &PyDictRevIterKey_Type || itertype == &PyDictRevIterItem_Type || itertype == &PyDictRevIterValue_Type) { - if (dict->ma_values) { + if (_PyDict_HasSplitTable(dict)) { di->di_pos = dict->ma_used - 1; } else { @@ -4523,11 +4614,11 @@ dictiter_iternextkey_lock_held(PyDictObject *d, PyObject *self) i = di->di_pos; k = d->ma_keys; assert(i >= 0); - if (d->ma_values) { + if (_PyDict_HasSplitTable(d)) { if (i >= d->ma_used) goto fail; int index = get_index_from_order(d, i); - key = DK_UNICODE_ENTRIES(k)[index].me_key; + key = LOAD_SHARED_KEY(DK_UNICODE_ENTRIES(k)[index].me_key); assert(d->ma_values->values[index] != NULL); } else { @@ -4638,7 +4729,7 @@ dictiter_iternextvalue_lock_held(PyDictObject *d, PyObject *self) i = di->di_pos; assert(i >= 0); - if (d->ma_values) { + if (_PyDict_HasSplitTable(d)) { if (i >= d->ma_used) goto fail; int index = get_index_from_order(d, i); @@ -4752,11 +4843,11 @@ dictiter_iternextitem_lock_held(PyDictObject *d, PyObject *self) i = di->di_pos; assert(i >= 0); - if (d->ma_values) { + if (_PyDict_HasSplitTable(d)) { if (i >= d->ma_used) goto fail; int index = get_index_from_order(d, i); - key = DK_UNICODE_ENTRIES(d->ma_keys)[index].me_key; + key = LOAD_SHARED_KEY(DK_UNICODE_ENTRIES(d->ma_keys)[index].me_key); value = d->ma_values->values[index]; assert(value != NULL); } @@ -4897,9 +4988,9 @@ dictreviter_iter_PyDict_Next(PyDictObject *d, PyObject *self) if (i < 0) { goto fail; } - if (d->ma_values) { + if (_PyDict_HasSplitTable(d)) { int index = get_index_from_order(d, i); - key = DK_UNICODE_ENTRIES(k)[index].me_key; + key = LOAD_SHARED_KEY(DK_UNICODE_ENTRIES(k)[index].me_key); value = d->ma_values->values[index]; assert (value != NULL); } @@ -5912,9 +6003,20 @@ _PyObject_InitInlineValues(PyObject *obj, PyTypeObject *tp) assert(tp->tp_flags & Py_TPFLAGS_MANAGED_DICT); PyDictKeysObject *keys = CACHED_KEYS(tp); assert(keys != NULL); +#ifdef Py_GIL_DISABLED + Py_ssize_t usable = _Py_atomic_load_ssize_relaxed(&keys->dk_usable); + if (usable > 1) { + LOCK_KEYS(keys); + if (keys->dk_usable > 1) { + _Py_atomic_store_ssize(&keys->dk_usable, keys->dk_usable - 1); + } + UNLOCK_KEYS(keys); + } +#else if (keys->dk_usable > 1) { keys->dk_usable--; } +#endif size_t size = shared_keys_usable_size(keys); PyDictValues *values = new_values(size); if (values == NULL) { @@ -6045,22 +6147,26 @@ _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values, assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT); Py_ssize_t ix = DKIX_EMPTY; if (PyUnicode_CheckExact(name)) { - ix = insert_into_dictkeys(keys, name); - } - if (ix == DKIX_EMPTY) { + LOCK_KEYS(keys); + ix = insert_into_splitdictkeys(keys, name); #ifdef Py_STATS - if (PyUnicode_CheckExact(name)) { - if (shared_keys_usable_size(keys) == SHARED_KEYS_MAX_SIZE) { - OBJECT_STAT_INC(dict_materialized_too_big); + if (ix == DKIX_EMPTY) { + if (PyUnicode_CheckExact(name)) { + if (shared_keys_usable_size(keys) == SHARED_KEYS_MAX_SIZE) { + OBJECT_STAT_INC(dict_materialized_too_big); + } + else { + OBJECT_STAT_INC(dict_materialized_new_key); + } } else { - OBJECT_STAT_INC(dict_materialized_new_key); + OBJECT_STAT_INC(dict_materialized_str_subclass); } } - else { - OBJECT_STAT_INC(dict_materialized_str_subclass); - } #endif + UNLOCK_KEYS(keys); + } + if (ix == DKIX_EMPTY) { PyObject *dict = make_dict_from_instance_attributes( interp, keys, values); if (dict == NULL) { From 54071460d76cdb3c805c8669dea7e82c70c5880f Mon Sep 17 00:00:00 2001 From: Dino Viehland <dinoviehland@meta.com> Date: Tue, 20 Feb 2024 17:08:14 -0800 Subject: [PATCH 390/507] gh-112075: Accessing a single element should optimistically avoid locking (#115109) Makes accessing a single element thread safe and typically lock free --- Include/internal/pycore_dict.h | 4 +- Objects/clinic/dictobject.c.h | 17 +- Objects/dictobject.c | 650 +++++++++++++++++++++++++-------- 3 files changed, 496 insertions(+), 175 deletions(-) diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index bf7a2abcad38144..5a496d59ab7af79 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -211,6 +211,8 @@ static inline PyDictUnicodeEntry* DK_UNICODE_ENTRIES(PyDictKeysObject *dk) { #define DICT_WATCHER_MASK ((1 << DICT_MAX_WATCHERS) - 1) #define DICT_WATCHER_AND_MODIFICATION_MASK ((1 << (DICT_MAX_WATCHERS + DICT_WATCHED_MUTATION_BITS)) - 1) +#define DICT_VALUES_SIZE(values) ((uint8_t *)values)[-1] + #ifdef Py_GIL_DISABLED #define DICT_NEXT_VERSION(INTERP) \ (_Py_atomic_add_uint64(&(INTERP)->dict_state.global_version, DICT_VERSION_INCREMENT) + DICT_VERSION_INCREMENT) @@ -256,7 +258,7 @@ _PyDictValues_AddToInsertionOrder(PyDictValues *values, Py_ssize_t ix) assert(ix < SHARED_KEYS_MAX_SIZE); uint8_t *size_ptr = ((uint8_t *)values)-2; int size = *size_ptr; - assert(size+2 < ((uint8_t *)values)[-1]); + assert(size+2 < DICT_VALUES_SIZE(values)); size++; size_ptr[-size] = (uint8_t)ix; *size_ptr = size; diff --git a/Objects/clinic/dictobject.c.h b/Objects/clinic/dictobject.c.h index daaef211b1db494..fb46c4c64334f91 100644 --- a/Objects/clinic/dictobject.c.h +++ b/Objects/clinic/dictobject.c.h @@ -66,21 +66,6 @@ PyDoc_STRVAR(dict___contains____doc__, #define DICT___CONTAINS___METHODDEF \ {"__contains__", (PyCFunction)dict___contains__, METH_O|METH_COEXIST, dict___contains____doc__}, -static PyObject * -dict___contains___impl(PyDictObject *self, PyObject *key); - -static PyObject * -dict___contains__(PyDictObject *self, PyObject *key) -{ - PyObject *return_value = NULL; - - Py_BEGIN_CRITICAL_SECTION(self); - return_value = dict___contains___impl(self, key); - Py_END_CRITICAL_SECTION(); - - return return_value; -} - PyDoc_STRVAR(dict_get__doc__, "get($self, key, default=None, /)\n" "--\n" @@ -327,4 +312,4 @@ dict_values(PyDictObject *self, PyObject *Py_UNUSED(ignored)) { return dict_values_impl(self); } -/*[clinic end generated code: output=c8fda06bac5b05f3 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=f3dd5f3fb8122aef input=a9049054013a1b77]*/ diff --git a/Objects/dictobject.c b/Objects/dictobject.c index a07267169fdc20f..ed92998a23a7689 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -80,6 +80,8 @@ DK_ENTRIES(keys)[index] if index >= 0): Active upon key insertion. Dummy slots cannot be made Unused again else the probe sequence in case of collision would have no way to know they were once active. + In free-threaded builds dummy slots are not re-used to allow lock-free + lookups to proceed safely. 4. Pending. index >= 0, key != NULL, and value == NULL (split only) Not yet inserted in split-table. @@ -150,6 +152,29 @@ ASSERT_DICT_LOCKED(PyObject *op) _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op); } #define ASSERT_DICT_LOCKED(op) ASSERT_DICT_LOCKED(_Py_CAST(PyObject*, op)) +#define IS_DICT_SHARED(mp) _PyObject_GC_IS_SHARED(mp) +#define SET_DICT_SHARED(mp) _PyObject_GC_SET_SHARED(mp) +#define LOAD_INDEX(keys, size, idx) _Py_atomic_load_int##size##_relaxed(&((const int##size##_t*)keys->dk_indices)[idx]); +#define STORE_INDEX(keys, size, idx, value) _Py_atomic_store_int##size##_relaxed(&((int##size##_t*)keys->dk_indices)[idx], (int##size##_t)value); +#define ASSERT_OWNED_OR_SHARED(mp) \ + assert(_Py_IsOwnedByCurrentThread((PyObject *)mp) || IS_DICT_SHARED(mp)); + +static inline void +set_keys(PyDictObject *mp, PyDictKeysObject *keys) +{ + ASSERT_OWNED_OR_SHARED(mp); + _Py_atomic_store_ptr_release(&mp->ma_keys, keys); +} + +static inline void +set_values(PyDictObject *mp, PyDictValues *values) +{ + ASSERT_OWNED_OR_SHARED(mp); + _Py_atomic_store_ptr_release(&mp->ma_values, values); +} + +// Defined until we get QSBR +#define _PyMem_FreeQsbr PyMem_Free #define LOCK_KEYS(keys) PyMutex_LockFlags(&keys->dk_mutex, _Py_LOCK_DONT_DETACH) #define UNLOCK_KEYS(keys) PyMutex_Unlock(&keys->dk_mutex) @@ -184,6 +209,10 @@ static inline void split_keys_entry_added(PyDictKeysObject *keys) #define INCREF_KEYS(dk) dk->dk_refcnt++ #define DECREF_KEYS(dk) dk->dk_refcnt-- #define LOAD_KEYS_NENTIRES(keys) keys->dk_nentries +#define IS_DICT_SHARED(mp) (false) +#define SET_DICT_SHARED(mp) +#define LOAD_INDEX(keys, size, idx) ((const int##size##_t*)(keys->dk_indices))[idx] +#define STORE_INDEX(keys, size, idx, value) ((int##size##_t*)(keys->dk_indices))[idx] = (int##size##_t)value static inline void split_keys_entry_added(PyDictKeysObject *keys) { @@ -191,6 +220,18 @@ static inline void split_keys_entry_added(PyDictKeysObject *keys) keys->dk_nentries++; } +static inline void +set_keys(PyDictObject *mp, PyDictKeysObject *keys) +{ + mp->ma_keys = keys; +} + +static inline void +set_values(PyDictObject *mp, PyDictValues *values) +{ + mp->ma_values = values; +} + #endif #define PERTURB_SHIFT 5 @@ -292,10 +333,6 @@ static int dictresize(PyInterpreterState *interp, PyDictObject *mp, static PyObject* dict_iter(PyObject *dict); -static int -contains_lock_held(PyDictObject *mp, PyObject *key); -static int -contains_known_hash_lock_held(PyDictObject *mp, PyObject *key, Py_ssize_t hash); static int setitem_lock_held(PyDictObject *mp, PyObject *key, PyObject *value); static int @@ -366,7 +403,7 @@ _PyDict_DebugMallocStats(FILE *out) #define DK_MASK(dk) (DK_SIZE(dk)-1) -static void free_keys_object(PyDictKeysObject *keys); +static void free_keys_object(PyDictKeysObject *keys, bool use_qsbr); /* PyDictKeysObject has refcounts like PyObject does, so we have the following two functions to mirror what Py_INCREF() and Py_DECREF() do. @@ -388,7 +425,7 @@ dictkeys_incref(PyDictKeysObject *dk) } static inline void -dictkeys_decref(PyInterpreterState *interp, PyDictKeysObject *dk) +dictkeys_decref(PyInterpreterState *interp, PyDictKeysObject *dk, bool use_qsbr) { if (dk->dk_refcnt == _Py_IMMORTAL_REFCNT) { return; @@ -414,7 +451,7 @@ dictkeys_decref(PyInterpreterState *interp, PyDictKeysObject *dk) Py_XDECREF(entries[i].me_value); } } - free_keys_object(dk); + free_keys_object(dk, use_qsbr); } } @@ -426,22 +463,18 @@ dictkeys_get_index(const PyDictKeysObject *keys, Py_ssize_t i) Py_ssize_t ix; if (log2size < 8) { - const int8_t *indices = (const int8_t*)(keys->dk_indices); - ix = indices[i]; + ix = LOAD_INDEX(keys, 8, i); } else if (log2size < 16) { - const int16_t *indices = (const int16_t*)(keys->dk_indices); - ix = indices[i]; + ix = LOAD_INDEX(keys, 16, i); } #if SIZEOF_VOID_P > 4 else if (log2size >= 32) { - const int64_t *indices = (const int64_t*)(keys->dk_indices); - ix = indices[i]; + ix = LOAD_INDEX(keys, 64, i); } #endif else { - const int32_t *indices = (const int32_t*)(keys->dk_indices); - ix = indices[i]; + ix = LOAD_INDEX(keys, 32, i); } assert(ix >= DKIX_DUMMY); return ix; @@ -457,25 +490,21 @@ dictkeys_set_index(PyDictKeysObject *keys, Py_ssize_t i, Py_ssize_t ix) assert(keys->dk_version == 0); if (log2size < 8) { - int8_t *indices = (int8_t*)(keys->dk_indices); assert(ix <= 0x7f); - indices[i] = (char)ix; + STORE_INDEX(keys, 8, i, ix); } else if (log2size < 16) { - int16_t *indices = (int16_t*)(keys->dk_indices); assert(ix <= 0x7fff); - indices[i] = (int16_t)ix; + STORE_INDEX(keys, 16, i, ix); } #if SIZEOF_VOID_P > 4 else if (log2size >= 32) { - int64_t *indices = (int64_t*)(keys->dk_indices); - indices[i] = ix; + STORE_INDEX(keys, 64, i, ix); } #endif else { - int32_t *indices = (int32_t*)(keys->dk_indices); assert(ix <= 0x7fffffff); - indices[i] = (int32_t)ix; + STORE_INDEX(keys, 32, i, ix); } } @@ -748,8 +777,14 @@ new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode) } static void -free_keys_object(PyDictKeysObject *keys) +free_keys_object(PyDictKeysObject *keys, bool use_qsbr) { +#ifdef Py_GIL_DISABLED + if (use_qsbr) { + _PyMem_FreeQsbr(keys); + return; + } +#endif #ifdef WITH_FREELISTS struct _Py_dictkeys_freelist *freelist = get_dictkeys_freelist(); if (DK_LOG_SIZE(keys) == PyDict_LOG_MINSIZE @@ -781,9 +816,15 @@ new_values(size_t size) } static inline void -free_values(PyDictValues *values) +free_values(PyDictValues *values, bool use_qsbr) { - int prefix_size = ((uint8_t *)values)[-1]; + int prefix_size = DICT_VALUES_SIZE(values); +#ifdef Py_GIL_DISABLED + if (use_qsbr) { + _PyMem_FreeQsbr(((char *)values)-prefix_size); + return; + } +#endif PyMem_Free(((char *)values)-prefix_size); } @@ -809,9 +850,9 @@ new_dict(PyInterpreterState *interp, { mp = PyObject_GC_New(PyDictObject, &PyDict_Type); if (mp == NULL) { - dictkeys_decref(interp, keys); + dictkeys_decref(interp, keys, false); if (free_values_on_failure) { - free_values(values); + free_values(values, false); } return NULL; } @@ -849,7 +890,7 @@ new_dict_with_shared_keys(PyInterpreterState *interp, PyDictKeysObject *keys) size_t size = shared_keys_usable_size(keys); PyDictValues *values = new_values(size); if (values == NULL) { - dictkeys_decref(interp, keys); + dictkeys_decref(interp, keys, false); return PyErr_NoMemory(); } ((char *)values)[-2] = 0; @@ -1172,6 +1213,260 @@ _Py_dict_lookup(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **valu return ix; } +static inline void +ensure_shared_on_read(PyDictObject *mp) +{ +#ifdef Py_GIL_DISABLED + if (!_Py_IsOwnedByCurrentThread((PyObject *)mp) && !IS_DICT_SHARED(mp)) { + // The first time we access a dict from a non-owning thread we mark it + // as shared. This ensures that a concurrent resize operation will + // delay freeing the old keys or values using QSBR, which is necessary + // to safely allow concurrent reads without locking... + Py_BEGIN_CRITICAL_SECTION(mp); + if (!IS_DICT_SHARED(mp)) { + SET_DICT_SHARED(mp); + } + Py_END_CRITICAL_SECTION(); + } +#endif +} + +static inline void +ensure_shared_on_resize(PyDictObject *mp) +{ +#ifdef Py_GIL_DISABLED + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(mp); + + if (!_Py_IsOwnedByCurrentThread((PyObject *)mp) && !IS_DICT_SHARED(mp)) { + // We are writing to the dict from another thread that owns + // it and we haven't marked it as shared which will ensure + // that when we re-size ma_keys or ma_values that we will + // free using QSBR. We need to lock the dictionary to + // contend with writes from the owning thread, mark it as + // shared, and then we can continue with lock-free reads. + // Technically this is a little heavy handed, we could just + // free the individual old keys / old-values using qsbr + SET_DICT_SHARED(mp); + } +#endif +} + +#ifdef Py_GIL_DISABLED + +static inline Py_ALWAYS_INLINE +Py_ssize_t compare_unicode_generic_threadsafe(PyDictObject *mp, PyDictKeysObject *dk, + void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash) +{ + PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix]; + PyObject *startkey = _Py_atomic_load_ptr_relaxed(&ep->me_key); + assert(startkey == NULL || PyUnicode_CheckExact(ep->me_key)); + assert(!PyUnicode_CheckExact(key)); + + if (startkey != NULL) { + if (!_Py_TryIncref(&ep->me_key, startkey)) { + return DKIX_KEY_CHANGED; + } + + if (unicode_get_hash(startkey) == hash) { + int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); + Py_DECREF(startkey); + if (cmp < 0) { + return DKIX_ERROR; + } + if (dk == _Py_atomic_load_ptr_relaxed(&mp->ma_keys) && + startkey == _Py_atomic_load_ptr_relaxed(&ep->me_key)) { + return cmp; + } + else { + /* The dict was mutated, restart */ + return DKIX_KEY_CHANGED; + } + } + else { + Py_DECREF(startkey); + } + } + return 0; +} + +// Search non-Unicode key from Unicode table +static Py_ssize_t +unicodekeys_lookup_generic_threadsafe(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) +{ + return do_lookup(mp, dk, key, hash, compare_unicode_generic_threadsafe); +} + +static inline Py_ALWAYS_INLINE Py_ssize_t +compare_unicode_unicode_threadsafe(PyDictObject *mp, PyDictKeysObject *dk, + void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash) +{ + PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix]; + PyObject *startkey = _Py_atomic_load_ptr_relaxed(&ep->me_key); + assert(startkey == NULL || PyUnicode_CheckExact(startkey)); + if (startkey == key) { + return 1; + } + if (startkey != NULL) { + if (_Py_IsImmortal(startkey)) { + return unicode_get_hash(startkey) == hash && unicode_eq(startkey, key); + } + else { + if (!_Py_TryIncref(&ep->me_key, startkey)) { + return DKIX_KEY_CHANGED; + } + if (unicode_get_hash(startkey) == hash && unicode_eq(startkey, key)) { + Py_DECREF(startkey); + return 1; + } + Py_DECREF(startkey); + } + } + return 0; +} + +static Py_ssize_t _Py_HOT_FUNCTION +unicodekeys_lookup_unicode_threadsafe(PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) +{ + return do_lookup(NULL, dk, key, hash, compare_unicode_unicode_threadsafe); +} + +static inline Py_ALWAYS_INLINE +Py_ssize_t compare_generic_threadsafe(PyDictObject *mp, PyDictKeysObject *dk, + void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash) +{ + PyDictKeyEntry *ep = &((PyDictKeyEntry *)ep0)[ix]; + PyObject *startkey = _Py_atomic_load_ptr_relaxed(&ep->me_key); + if (startkey == key) { + return 1; + } + Py_ssize_t ep_hash = _Py_atomic_load_ssize_relaxed(&ep->me_hash); + if (ep_hash == hash) { + if (startkey == NULL || !_Py_TryIncref(&ep->me_key, startkey)) { + return DKIX_KEY_CHANGED; + } + int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); + Py_DECREF(startkey); + if (cmp < 0) { + return DKIX_ERROR; + } + if (dk == _Py_atomic_load_ptr_relaxed(&mp->ma_keys) && + startkey == _Py_atomic_load_ptr_relaxed(&ep->me_key)) { + return cmp; + } + else { + /* The dict was mutated, restart */ + return DKIX_KEY_CHANGED; + } + } + return 0; +} + +static Py_ssize_t +dictkeys_generic_lookup_threadsafe(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) +{ + return do_lookup(mp, dk, key, hash, compare_generic_threadsafe); +} + +static Py_ssize_t +_Py_dict_lookup_threadsafe(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr) +{ + PyDictKeysObject *dk; + DictKeysKind kind; + Py_ssize_t ix; + PyObject *value; + + ensure_shared_on_read(mp); + + dk = _Py_atomic_load_ptr(&mp->ma_keys); + kind = dk->dk_kind; + + if (kind != DICT_KEYS_GENERAL) { + if (PyUnicode_CheckExact(key)) { + ix = unicodekeys_lookup_unicode_threadsafe(dk, key, hash); + } + else { + ix = unicodekeys_lookup_generic_threadsafe(mp, dk, key, hash); + } + if (ix == DKIX_KEY_CHANGED) { + goto read_failed; + } + + if (ix >= 0) { + if (kind == DICT_KEYS_SPLIT) { + PyDictValues *values = _Py_atomic_load_ptr(&mp->ma_values); + if (values == NULL) + goto read_failed; + + uint8_t capacity = _Py_atomic_load_uint8_relaxed(&DICT_VALUES_SIZE(values)); + if (ix >= (Py_ssize_t)capacity) + goto read_failed; + + value = _Py_TryXGetRef(&values->values[ix]); + if (value == NULL) + goto read_failed; + + if (values != _Py_atomic_load_ptr(&mp->ma_values)) { + Py_DECREF(value); + goto read_failed; + } + } + else { + value = _Py_TryXGetRef(&DK_UNICODE_ENTRIES(dk)[ix].me_value); + if (value == NULL) { + goto read_failed; + } + + if (dk != _Py_atomic_load_ptr(&mp->ma_keys)) { + Py_DECREF(value); + goto read_failed; + } + } + } + else { + value = NULL; + } + } + else { + ix = dictkeys_generic_lookup_threadsafe(mp, dk, key, hash); + if (ix == DKIX_KEY_CHANGED) { + goto read_failed; + } + if (ix >= 0) { + value = _Py_TryXGetRef(&DK_ENTRIES(dk)[ix].me_value); + if (value == NULL) + goto read_failed; + + if (dk != _Py_atomic_load_ptr(&mp->ma_keys)) { + Py_DECREF(value); + goto read_failed; + } + } + else { + value = NULL; + } + } + + *value_addr = value; + return ix; + +read_failed: + // In addition to the normal races of the dict being modified the _Py_TryXGetRef + // can all fail if they don't yet have a shared ref count. That can happen here + // or in the *_lookup_* helper. In that case we need to take the lock to avoid + // mutation and do a normal incref which will make them shared. + Py_BEGIN_CRITICAL_SECTION(mp); + ix = _Py_dict_lookup(mp, key, hash, &value); + *value_addr = value; + if (value != NULL) { + assert(ix >= 0); + Py_INCREF(value); + } + Py_END_CRITICAL_SECTION(); + return ix; +} + +#endif + int _PyDict_HasOnlyStringKeys(PyObject *dict) { @@ -1242,6 +1537,16 @@ _PyDict_MaybeUntrack(PyObject *op) _PyObject_GC_UNTRACK(op); } +static inline int +is_unusable_slot(Py_ssize_t ix) +{ +#ifdef Py_GIL_DISABLED + return ix >= 0 || ix == DKIX_DUMMY; +#else + return ix >= 0; +#endif +} + /* Internal function to find slot for an item from its hash when it is known that the key is not present in the dict. */ @@ -1253,7 +1558,7 @@ find_empty_slot(PyDictKeysObject *keys, Py_hash_t hash) const size_t mask = DK_MASK(keys); size_t i = hash & mask; Py_ssize_t ix = dictkeys_get_index(keys, i); - for (size_t perturb = hash; ix >= 0;) { + for (size_t perturb = hash; is_unusable_slot(ix);) { perturb >>= PERTURB_SHIFT; i = (i*5 + perturb + 1) & mask; ix = dictkeys_get_index(keys, i); @@ -1450,7 +1755,7 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp, return -1; } -// Same to insertdict but specialized for ma_keys = Py_EMPTY_KEYS. +// Same as insertdict but specialized for ma_keys == Py_EMPTY_KEYS. // Consumes key and value references. static int insert_to_emptydict(PyInterpreterState *interp, PyDictObject *mp, @@ -1471,28 +1776,37 @@ insert_to_emptydict(PyInterpreterState *interp, PyDictObject *mp, return -1; } /* We don't decref Py_EMPTY_KEYS here because it is immortal. */ - mp->ma_keys = newkeys; - mp->ma_values = NULL; + assert(mp->ma_values == NULL); MAINTAIN_TRACKING(mp, key, value); size_t hashpos = (size_t)hash & (PyDict_MINSIZE-1); - dictkeys_set_index(mp->ma_keys, hashpos, 0); + dictkeys_set_index(newkeys, hashpos, 0); if (unicode) { - PyDictUnicodeEntry *ep = DK_UNICODE_ENTRIES(mp->ma_keys); + PyDictUnicodeEntry *ep = DK_UNICODE_ENTRIES(newkeys); ep->me_key = key; ep->me_value = value; } else { - PyDictKeyEntry *ep = DK_ENTRIES(mp->ma_keys); + PyDictKeyEntry *ep = DK_ENTRIES(newkeys); ep->me_key = key; ep->me_hash = hash; ep->me_value = value; } mp->ma_used++; mp->ma_version_tag = new_version; - mp->ma_keys->dk_usable--; - mp->ma_keys->dk_nentries++; + newkeys->dk_usable--; + newkeys->dk_nentries++; + // We store the keys last so no one can see them in a partially inconsistent + // state so that we don't need to switch the keys to being shared yet for + // the case where we're inserting from the non-owner thread. We don't use + // set_keys here because the transition from empty to non-empty is safe + // as the empty keys will never be freed. +#ifdef Py_GIL_DISABLED + _Py_atomic_store_ptr_release(&mp->ma_keys, newkeys); +#else + mp->ma_keys = newkeys; +#endif return 0; } @@ -1548,7 +1862,7 @@ static int dictresize(PyInterpreterState *interp, PyDictObject *mp, uint8_t log2_newsize, int unicode) { - PyDictKeysObject *oldkeys; + PyDictKeysObject *oldkeys, *newkeys; PyDictValues *oldvalues; ASSERT_DICT_LOCKED(mp); @@ -1566,19 +1880,19 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp, unicode = 0; } + ensure_shared_on_resize(mp); /* NOTE: Current odict checks mp->ma_keys to detect resize happen. * So we can't reuse oldkeys even if oldkeys->dk_size == newsize. * TODO: Try reusing oldkeys when reimplement odict. */ /* Allocate a new table. */ - mp->ma_keys = new_keys_object(interp, log2_newsize, unicode); - if (mp->ma_keys == NULL) { - mp->ma_keys = oldkeys; + newkeys = new_keys_object(interp, log2_newsize, unicode); + if (newkeys == NULL) { return -1; } // New table must be large enough. - assert(mp->ma_keys->dk_usable >= mp->ma_used); + assert(newkeys->dk_usable >= mp->ma_used); Py_ssize_t numentries = mp->ma_used; @@ -1588,9 +1902,9 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp, /* Convert split table into new combined table. * We must incref keys; we can transfer values. */ - if (mp->ma_keys->dk_kind == DICT_KEYS_GENERAL) { + if (newkeys->dk_kind == DICT_KEYS_GENERAL) { // split -> generic - PyDictKeyEntry *newentries = DK_ENTRIES(mp->ma_keys); + PyDictKeyEntry *newentries = DK_ENTRIES(newkeys); for (Py_ssize_t i = 0; i < numentries; i++) { int index = get_index_from_order(mp, i); @@ -1600,10 +1914,10 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp, newentries[i].me_hash = unicode_get_hash(ep->me_key); newentries[i].me_value = oldvalues->values[index]; } - build_indices_generic(mp->ma_keys, newentries, numentries); + build_indices_generic(newkeys, newentries, numentries); } else { // split -> combined unicode - PyDictUnicodeEntry *newentries = DK_UNICODE_ENTRIES(mp->ma_keys); + PyDictUnicodeEntry *newentries = DK_UNICODE_ENTRIES(newkeys); for (Py_ssize_t i = 0; i < numentries; i++) { int index = get_index_from_order(mp, i); @@ -1612,19 +1926,20 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp, newentries[i].me_key = Py_NewRef(ep->me_key); newentries[i].me_value = oldvalues->values[index]; } - build_indices_unicode(mp->ma_keys, newentries, numentries); + build_indices_unicode(newkeys, newentries, numentries); } UNLOCK_KEYS(oldkeys); - dictkeys_decref(interp, oldkeys); - mp->ma_values = NULL; - free_values(oldvalues); + set_keys(mp, newkeys); + dictkeys_decref(interp, oldkeys, IS_DICT_SHARED(mp)); + set_values(mp, NULL); + free_values(oldvalues, IS_DICT_SHARED(mp)); } else { // oldkeys is combined. if (oldkeys->dk_kind == DICT_KEYS_GENERAL) { // generic -> generic - assert(mp->ma_keys->dk_kind == DICT_KEYS_GENERAL); + assert(newkeys->dk_kind == DICT_KEYS_GENERAL); PyDictKeyEntry *oldentries = DK_ENTRIES(oldkeys); - PyDictKeyEntry *newentries = DK_ENTRIES(mp->ma_keys); + PyDictKeyEntry *newentries = DK_ENTRIES(newkeys); if (oldkeys->dk_nentries == numentries) { memcpy(newentries, oldentries, numentries * sizeof(PyDictKeyEntry)); } @@ -1636,12 +1951,12 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp, newentries[i] = *ep++; } } - build_indices_generic(mp->ma_keys, newentries, numentries); + build_indices_generic(newkeys, newentries, numentries); } else { // oldkeys is combined unicode PyDictUnicodeEntry *oldentries = DK_UNICODE_ENTRIES(oldkeys); if (unicode) { // combined unicode -> combined unicode - PyDictUnicodeEntry *newentries = DK_UNICODE_ENTRIES(mp->ma_keys); + PyDictUnicodeEntry *newentries = DK_UNICODE_ENTRIES(newkeys); if (oldkeys->dk_nentries == numentries && mp->ma_keys->dk_kind == DICT_KEYS_UNICODE) { memcpy(newentries, oldentries, numentries * sizeof(PyDictUnicodeEntry)); } @@ -1653,10 +1968,10 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp, newentries[i] = *ep++; } } - build_indices_unicode(mp->ma_keys, newentries, numentries); + build_indices_unicode(newkeys, newentries, numentries); } else { // combined unicode -> generic - PyDictKeyEntry *newentries = DK_ENTRIES(mp->ma_keys); + PyDictKeyEntry *newentries = DK_ENTRIES(newkeys); PyDictUnicodeEntry *ep = oldentries; for (Py_ssize_t i = 0; i < numentries; i++) { while (ep->me_value == NULL) @@ -1666,17 +1981,19 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp, newentries[i].me_value = ep->me_value; ep++; } - build_indices_generic(mp->ma_keys, newentries, numentries); + build_indices_generic(newkeys, newentries, numentries); } } + set_keys(mp, newkeys); + if (oldkeys != Py_EMPTY_KEYS) { #ifdef Py_REF_DEBUG _Py_DecRefTotal(_PyInterpreterState_GET()); #endif assert(oldkeys->dk_kind != DICT_KEYS_SPLIT); assert(oldkeys->dk_refcnt == 1); - free_keys_object(oldkeys); + free_keys_object(oldkeys, IS_DICT_SHARED(mp)); } } @@ -1798,9 +2115,13 @@ dict_getitem(PyObject *op, PyObject *key, const char *warnmsg) PyObject *value; Py_ssize_t ix; (void)ix; - PyObject *exc = _PyErr_GetRaisedException(tstate); +#ifdef Py_GIL_DISABLED + ix = _Py_dict_lookup_threadsafe(mp, key, hash, &value); + Py_XDECREF(value); +#else ix = _Py_dict_lookup(mp, key, hash, &value); +#endif /* Ignore any exception raised by the lookup */ PyObject *exc2 = _PyErr_Occurred(tstate); @@ -1856,11 +2177,47 @@ _PyDict_GetItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash) return NULL; } +#ifdef Py_GIL_DISABLED + ix = _Py_dict_lookup_threadsafe(mp, key, hash, &value); + Py_XDECREF(value); +#else ix = _Py_dict_lookup(mp, key, hash, &value); +#endif assert(ix >= 0 || value == NULL); return value; // borrowed reference } +/* Gets an item and provides a new reference if the value is present. + * Returns 1 if the key is present, 0 if the key is missing, and -1 if an + * exception occurred. +*/ +int +_PyDict_GetItemRef_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash, PyObject **result) +{ + PyDictObject*mp = (PyDictObject *)op; + + PyObject *value; +#ifdef Py_GIL_DISABLED + Py_ssize_t ix = _Py_dict_lookup_threadsafe(mp, key, hash, &value); +#else + Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &value); +#endif + assert(ix >= 0 || value == NULL); + if (ix == DKIX_ERROR) { + *result = NULL; + return -1; + } + if (value == NULL) { + *result = NULL; + return 0; // missing key + } +#ifdef Py_GIL_DISABLED + *result = value; +#else + *result = Py_NewRef(value); +#endif + return 1; // key is present +} int PyDict_GetItemRef(PyObject *op, PyObject *key, PyObject **result) @@ -1870,7 +2227,6 @@ PyDict_GetItemRef(PyObject *op, PyObject *key, PyObject **result) *result = NULL; return -1; } - PyDictObject*mp = (PyDictObject *)op; Py_hash_t hash; if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) @@ -1882,19 +2238,7 @@ PyDict_GetItemRef(PyObject *op, PyObject *key, PyObject **result) } } - PyObject *value; - Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &value); - assert(ix >= 0 || value == NULL); - if (ix == DKIX_ERROR) { - *result = NULL; - return -1; - } - if (value == NULL) { - *result = NULL; - return 0; // missing key - } - *result = Py_NewRef(value); - return 1; // key is present + return _PyDict_GetItemRef_KnownHash(op, key, hash, result); } @@ -1922,7 +2266,12 @@ PyDict_GetItemWithError(PyObject *op, PyObject *key) } } +#ifdef Py_GIL_DISABLED + ix = _Py_dict_lookup_threadsafe(mp, key, hash, &value); + Py_XDECREF(value); +#else ix = _Py_dict_lookup(mp, key, hash, &value); +#endif assert(ix >= 0 || value == NULL); return value; // borrowed reference } @@ -1963,7 +2312,6 @@ _PyDict_GetItemStringWithError(PyObject *v, const char *key) return rv; } - /* Fast version of global value lookup (LOAD_GLOBAL). * Lookup in globals, then builtins. * @@ -1977,6 +2325,7 @@ _PyDict_GetItemStringWithError(PyObject *v, const char *key) PyObject * _PyDict_LoadGlobal(PyDictObject *globals, PyDictObject *builtins, PyObject *key) { + // TODO: Thread safety Py_ssize_t ix; Py_hash_t hash; PyObject *value; @@ -2298,9 +2647,11 @@ clear_lock_held(PyObject *op) PyInterpreterState *interp = _PyInterpreterState_GET(); uint64_t new_version = _PyDict_NotifyEvent( interp, PyDict_EVENT_CLEARED, mp, NULL, NULL); - dictkeys_incref(Py_EMPTY_KEYS); - mp->ma_keys = Py_EMPTY_KEYS; - mp->ma_values = NULL; + // We don't inc ref empty keys because they're immortal + ensure_shared_on_resize(mp); + + set_keys(mp, Py_EMPTY_KEYS); + set_values(mp, NULL); mp->ma_used = 0; mp->ma_version_tag = new_version; /* ...then clear the keys and values */ @@ -2308,12 +2659,12 @@ clear_lock_held(PyObject *op) n = oldkeys->dk_nentries; for (i = 0; i < n; i++) Py_CLEAR(oldvalues->values[i]); - free_values(oldvalues); - dictkeys_decref(interp, oldkeys); + free_values(oldvalues, IS_DICT_SHARED(mp)); + dictkeys_decref(interp, oldkeys, false); } else { assert(oldkeys->dk_refcnt == 1); - dictkeys_decref(interp, oldkeys); + dictkeys_decref(interp, oldkeys, IS_DICT_SHARED(mp)); } ASSERT_CONSISTENT(mp); } @@ -2698,12 +3049,12 @@ dict_dealloc(PyObject *self) for (i = 0, n = mp->ma_keys->dk_nentries; i < n; i++) { Py_XDECREF(values->values[i]); } - free_values(values); - dictkeys_decref(interp, keys); + free_values(values, false); + dictkeys_decref(interp, keys, false); } else if (keys != NULL) { assert(keys->dk_refcnt == 1 || keys == Py_EMPTY_KEYS); - dictkeys_decref(interp, keys); + dictkeys_decref(interp, keys, false); } #ifdef WITH_FREELISTS struct _Py_dict_freelist *freelist = get_dict_freelist(); @@ -2823,7 +3174,7 @@ dict_length(PyObject *self) } static PyObject * -dict_subscript_lock_held(PyObject *self, PyObject *key) +dict_subscript(PyObject *self, PyObject *key) { PyDictObject *mp = (PyDictObject *)self; Py_ssize_t ix; @@ -2835,7 +3186,11 @@ dict_subscript_lock_held(PyObject *self, PyObject *key) if (hash == -1) return NULL; } +#ifdef Py_GIL_DISABLED + ix = _Py_dict_lookup_threadsafe(mp, key, hash, &value); +#else ix = _Py_dict_lookup(mp, key, hash, &value); +#endif if (ix == DKIX_ERROR) return NULL; if (ix == DKIX_EMPTY || value == NULL) { @@ -2855,17 +3210,11 @@ dict_subscript_lock_held(PyObject *self, PyObject *key) _PyErr_SetKeyError(key); return NULL; } +#ifdef Py_GIL_DISABLED + return value; +#else return Py_NewRef(value); -} - -static PyObject * -dict_subscript(PyObject *self, PyObject *key) -{ - PyObject *res; - Py_BEGIN_CRITICAL_SECTION(self); - res = dict_subscript_lock_held(self, key); - Py_END_CRITICAL_SECTION(); - return res; +#endif } static int @@ -3243,10 +3592,11 @@ dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *othe if (keys == NULL) return -1; - dictkeys_decref(interp, mp->ma_keys); + ensure_shared_on_resize(mp); + dictkeys_decref(interp, mp->ma_keys, IS_DICT_SHARED(mp)); mp->ma_keys = keys; if (_PyDict_HasSplitTable(mp)) { - free_values(mp->ma_values); + free_values(mp->ma_values, IS_DICT_SHARED(mp)); mp->ma_values = NULL; } @@ -3289,7 +3639,7 @@ dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *othe Py_NewRef(key), hash, Py_NewRef(value)); } else { - err = contains_known_hash_lock_held(mp, key, hash); + err = _PyDict_Contains_KnownHash((PyObject *)mp, key, hash); if (err == 0) { err = insertdict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value)); @@ -3372,7 +3722,7 @@ dict_merge(PyInterpreterState *interp, PyObject *a, PyObject *b, int override) for (key = PyIter_Next(iter); key; key = PyIter_Next(iter)) { if (override != 1) { - status = contains_lock_held(mp, key); + status = PyDict_Contains(a, key); if (status != 0) { if (status > 0) { if (override == 0) { @@ -3476,7 +3826,7 @@ copy_lock_held(PyObject *o) return PyErr_NoMemory(); split_copy = PyObject_GC_New(PyDictObject, &PyDict_Type); if (split_copy == NULL) { - free_values(newvalues); + free_values(newvalues, false); return NULL; } size_t prefix_size = ((uint8_t *)newvalues)[-1]; @@ -3670,7 +4020,6 @@ dict_richcompare(PyObject *v, PyObject *w, int op) /*[clinic input] @coexist -@critical_section dict.__contains__ key: object @@ -3680,25 +4029,17 @@ True if the dictionary has the specified key, else False. [clinic start generated code]*/ static PyObject * -dict___contains___impl(PyDictObject *self, PyObject *key) -/*[clinic end generated code: output=1b314e6da7687dae input=bc76ec9c157cb81b]*/ +dict___contains__(PyDictObject *self, PyObject *key) +/*[clinic end generated code: output=a3d03db709ed6e6b input=fe1cb42ad831e820]*/ { - register PyDictObject *mp = self; - Py_hash_t hash; - Py_ssize_t ix; - PyObject *value; - - if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) { - hash = PyObject_Hash(key); - if (hash == -1) - return NULL; - } - ix = _Py_dict_lookup(mp, key, hash, &value); - if (ix == DKIX_ERROR) + int contains = PyDict_Contains((PyObject *)self, key); + if (contains < 0) { return NULL; - if (ix == DKIX_EMPTY || value == NULL) - Py_RETURN_FALSE; - Py_RETURN_TRUE; + } + if (contains) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; } /*[clinic input] @@ -3725,13 +4066,24 @@ dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value) if (hash == -1) return NULL; } +#ifdef Py_GIL_DISABLED + ix = _Py_dict_lookup_threadsafe(self, key, hash, &val); +#else ix = _Py_dict_lookup(self, key, hash, &val); +#endif if (ix == DKIX_ERROR) return NULL; +#ifdef Py_GIL_DISABLED + if (ix == DKIX_EMPTY || val == NULL) { + val = Py_NewRef(default_value); + } + return val; +#else if (ix == DKIX_EMPTY || val == NULL) { val = default_value; } return Py_NewRef(val); +#endif } static int @@ -4183,49 +4535,19 @@ static PyMethodDef mapp_methods[] = { {NULL, NULL} /* sentinel */ }; -static int -contains_known_hash_lock_held(PyDictObject *mp, PyObject *key, Py_ssize_t hash) -{ - Py_ssize_t ix; - PyObject *value; - - ASSERT_DICT_LOCKED(mp); - - ix = _Py_dict_lookup(mp, key, hash, &value); - if (ix == DKIX_ERROR) - return -1; - return (ix != DKIX_EMPTY && value != NULL); -} - -static int -contains_lock_held(PyDictObject *mp, PyObject *key) +/* Return 1 if `key` is in dict `op`, 0 if not, and -1 on error. */ +int +PyDict_Contains(PyObject *op, PyObject *key) { Py_hash_t hash; - Py_ssize_t ix; - PyObject *value; - - ASSERT_DICT_LOCKED(mp); if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) { hash = PyObject_Hash(key); if (hash == -1) return -1; } - ix = _Py_dict_lookup(mp, key, hash, &value); - if (ix == DKIX_ERROR) - return -1; - return (ix != DKIX_EMPTY && value != NULL); -} -/* Return 1 if `key` is in dict `op`, 0 if not, and -1 on error. */ -int -PyDict_Contains(PyObject *op, PyObject *key) -{ - int res; - Py_BEGIN_CRITICAL_SECTION(op); - res = contains_lock_held((PyDictObject *)op, key); - Py_END_CRITICAL_SECTION(); - return res; + return _PyDict_Contains_KnownHash(op, key, hash); } int @@ -4248,10 +4570,20 @@ _PyDict_Contains_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash) PyObject *value; Py_ssize_t ix; +#ifdef Py_GIL_DISABLED + ix = _Py_dict_lookup_threadsafe(mp, key, hash, &value); +#else ix = _Py_dict_lookup(mp, key, hash, &value); +#endif if (ix == DKIX_ERROR) return -1; - return (ix != DKIX_EMPTY && value != NULL); + if (ix != DKIX_EMPTY && value != NULL) { +#ifdef Py_GIL_DISABLED + Py_DECREF(value); +#endif + return 1; + } + return 0; } int @@ -4967,7 +5299,7 @@ PyTypeObject PyDictIterItem_Type = { /* dictreviter */ static PyObject * -dictreviter_iter_PyDict_Next(PyDictObject *d, PyObject *self) +dictreviter_iter_lock_held(PyDictObject *d, PyObject *self) { dictiterobject *di = (dictiterobject *)self; @@ -5074,7 +5406,7 @@ dictreviter_iternext(PyObject *self) PyObject *value; Py_BEGIN_CRITICAL_SECTION(d); - value = dictreviter_iter_PyDict_Next(d, self); + value = dictreviter_iter_lock_held(d, self); Py_END_CRITICAL_SECTION(); return value; @@ -6124,6 +6456,8 @@ _PyObject_MakeInstanceAttributesFromDict(PyObject *obj, PyDictOrValues *dorv) { return false; } + ensure_shared_on_resize(dict); + assert(dict->ma_values); // We have an opportunity to do something *really* cool: dematerialize it! _PyDictKeys_DecRef(dict->ma_keys); @@ -6291,7 +6625,7 @@ _PyObject_FreeInstanceAttributes(PyObject *self) for (Py_ssize_t i = 0; i < keys->dk_nentries; i++) { Py_XDECREF(values->values[i]); } - free_values(values); + free_values(values, IS_DICT_SHARED((PyDictObject*)self)); } int @@ -6332,7 +6666,7 @@ PyObject_ClearManagedDict(PyObject *obj) Py_CLEAR(values->values[i]); } dorv_ptr->dict = NULL; - free_values(values); + free_values(values, IS_DICT_SHARED((PyDictObject*)obj)); } else { PyObject *dict = dorv_ptr->dict; @@ -6442,7 +6776,7 @@ void _PyDictKeys_DecRef(PyDictKeysObject *keys) { PyInterpreterState *interp = _PyInterpreterState_GET(); - dictkeys_decref(interp, keys); + dictkeys_decref(interp, keys, false); } uint32_t _PyDictKeys_GetVersionForCurrentState(PyInterpreterState *interp, From 259730bbb5b5381421d494330775022a1506efe4 Mon Sep 17 00:00:00 2001 From: Donghee Na <donghee.na@python.org> Date: Wed, 21 Feb 2024 10:38:09 +0900 Subject: [PATCH 391/507] gh-112087: Make list_{concat, repeat, inplace_repeat, ass_item) to be thread-safe (gh-115605) --- .../internal/pycore_pyatomic_ft_wrappers.h | 6 + Objects/listobject.c | 122 ++++++++++++------ 2 files changed, 88 insertions(+), 40 deletions(-) diff --git a/Include/internal/pycore_pyatomic_ft_wrappers.h b/Include/internal/pycore_pyatomic_ft_wrappers.h index cbbe90e009c8d29..e441600d54e1aac 100644 --- a/Include/internal/pycore_pyatomic_ft_wrappers.h +++ b/Include/internal/pycore_pyatomic_ft_wrappers.h @@ -23,11 +23,17 @@ extern "C" { #define FT_ATOMIC_LOAD_SSIZE(value) _Py_atomic_load_ssize(&value) #define FT_ATOMIC_LOAD_SSIZE_RELAXED(value) \ _Py_atomic_load_ssize_relaxed(&value) +#define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) \ + _Py_atomic_store_ptr_relaxed(&value, new_value) +#define FT_ATOMIC_STORE_PTR_RELEASE(value, new_value) \ + _Py_atomic_store_ptr_release(&value, new_value) #define FT_ATOMIC_STORE_SSIZE_RELAXED(value, new_value) \ _Py_atomic_store_ssize_relaxed(&value, new_value) #else #define FT_ATOMIC_LOAD_SSIZE(value) value #define FT_ATOMIC_LOAD_SSIZE_RELAXED(value) value +#define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) value = new_value +#define FT_ATOMIC_STORE_PTR_RELEASE(value, new_value) value = new_value #define FT_ATOMIC_STORE_SSIZE_RELAXED(value, new_value) value = new_value #endif diff --git a/Objects/listobject.c b/Objects/listobject.c index b07970298b8a000..2bb7d4ec3424510 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -3,6 +3,7 @@ #include "Python.h" #include "pycore_abstract.h" // _PyIndex_Check() #include "pycore_ceval.h" // _PyEval_GetBuiltin() +#include "pycore_pyatomic_ft_wrappers.h" #include "pycore_interp.h" // PyInterpreterState.list #include "pycore_list.h" // struct _Py_list_freelist, _PyListIterObject #include "pycore_long.h" // _PyLong_DigitCount @@ -20,14 +21,6 @@ class list "PyListObject *" "&PyList_Type" _Py_DECLARE_STR(list_err, "list index out of range"); -#ifdef Py_GIL_DISABLED -# define LOAD_SSIZE(value) _Py_atomic_load_ssize_relaxed(&value) -# define STORE_SSIZE(value, new_value) _Py_atomic_store_ssize_relaxed(&value, new_value) -#else -# define LOAD_SSIZE(value) value -# define STORE_SSIZE(value, new_value) value = new_value -#endif - #ifdef WITH_FREELISTS static struct _Py_list_freelist * get_list_freelist(void) @@ -563,20 +556,12 @@ PyList_GetSlice(PyObject *a, Py_ssize_t ilow, Py_ssize_t ihigh) } static PyObject * -list_concat(PyObject *aa, PyObject *bb) +list_concat_lock_held(PyListObject *a, PyListObject *b) { - PyListObject *a = (PyListObject *)aa; Py_ssize_t size; Py_ssize_t i; PyObject **src, **dest; PyListObject *np; - if (!PyList_Check(bb)) { - PyErr_Format(PyExc_TypeError, - "can only concatenate list (not \"%.200s\") to list", - Py_TYPE(bb)->tp_name); - return NULL; - } -#define b ((PyListObject *)bb) assert((size_t)Py_SIZE(a) + (size_t)Py_SIZE(b) < PY_SSIZE_T_MAX); size = Py_SIZE(a) + Py_SIZE(b); if (size == 0) { @@ -590,23 +575,39 @@ list_concat(PyObject *aa, PyObject *bb) dest = np->ob_item; for (i = 0; i < Py_SIZE(a); i++) { PyObject *v = src[i]; - dest[i] = Py_NewRef(v); + FT_ATOMIC_STORE_PTR_RELAXED(dest[i], Py_NewRef(v)); } src = b->ob_item; dest = np->ob_item + Py_SIZE(a); for (i = 0; i < Py_SIZE(b); i++) { PyObject *v = src[i]; - dest[i] = Py_NewRef(v); + FT_ATOMIC_STORE_PTR_RELAXED(dest[i], Py_NewRef(v)); } Py_SET_SIZE(np, size); return (PyObject *)np; -#undef b } static PyObject * -list_repeat(PyObject *aa, Py_ssize_t n) +list_concat(PyObject *aa, PyObject *bb) { + if (!PyList_Check(bb)) { + PyErr_Format(PyExc_TypeError, + "can only concatenate list (not \"%.200s\") to list", + Py_TYPE(bb)->tp_name); + return NULL; + } PyListObject *a = (PyListObject *)aa; + PyListObject *b = (PyListObject *)bb; + PyObject *ret; + Py_BEGIN_CRITICAL_SECTION2(a, b); + ret = list_concat_lock_held(a, b); + Py_END_CRITICAL_SECTION2(); + return ret; +} + +static PyObject * +list_repeat_lock_held(PyListObject *a, Py_ssize_t n) +{ const Py_ssize_t input_size = Py_SIZE(a); if (input_size == 0 || n <= 0) return PyList_New(0); @@ -626,7 +627,7 @@ list_repeat(PyObject *aa, Py_ssize_t n) _Py_RefcntAdd(elem, n); PyObject **dest_end = dest + output_size; while (dest < dest_end) { - *dest++ = elem; + FT_ATOMIC_STORE_PTR_RELAXED(*dest++, elem); } } else { @@ -634,7 +635,7 @@ list_repeat(PyObject *aa, Py_ssize_t n) PyObject **src_end = src + input_size; while (src < src_end) { _Py_RefcntAdd(*src, n); - *dest++ = *src++; + FT_ATOMIC_STORE_PTR_RELAXED(*dest++, *src++); } _Py_memory_repeat((char *)np->ob_item, sizeof(PyObject *)*output_size, @@ -645,6 +646,17 @@ list_repeat(PyObject *aa, Py_ssize_t n) return (PyObject *) np; } +static PyObject * +list_repeat(PyObject *aa, Py_ssize_t n) +{ + PyObject *ret; + PyListObject *a = (PyListObject *)aa; + Py_BEGIN_CRITICAL_SECTION(a); + ret = list_repeat_lock_held(a, n); + Py_END_CRITICAL_SECTION(); + return ret; +} + static void list_clear(PyListObject *a) { @@ -657,11 +669,12 @@ list_clear(PyListObject *a) this list, we make it empty first. */ Py_ssize_t i = Py_SIZE(a); Py_SET_SIZE(a, 0); - a->ob_item = NULL; + FT_ATOMIC_STORE_PTR_RELEASE(a->ob_item, NULL); a->allocated = 0; while (--i >= 0) { Py_XDECREF(items[i]); } + // TODO: Use QSBR technique, if the list is shared between threads, PyMem_Free(items); // Note that there is no guarantee that the list is actually empty @@ -798,9 +811,8 @@ PyList_SetSlice(PyObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) } static PyObject * -list_inplace_repeat(PyObject *_self, Py_ssize_t n) +list_inplace_repeat_lock_held(PyListObject *self, Py_ssize_t n) { - PyListObject *self = (PyListObject *)_self; Py_ssize_t input_size = PyList_GET_SIZE(self); if (input_size == 0 || n == 1) { return Py_NewRef(self); @@ -829,21 +841,51 @@ list_inplace_repeat(PyObject *_self, Py_ssize_t n) return Py_NewRef(self); } +static PyObject * +list_inplace_repeat(PyObject *_self, Py_ssize_t n) +{ + PyObject *ret; + PyListObject *self = (PyListObject *) _self; + Py_BEGIN_CRITICAL_SECTION(self); + ret = list_inplace_repeat_lock_held(self, n); + Py_END_CRITICAL_SECTION(); + return ret; +} + static int -list_ass_item(PyObject *aa, Py_ssize_t i, PyObject *v) +list_ass_item_lock_held(PyListObject *a, Py_ssize_t i, PyObject *v) { - PyListObject *a = (PyListObject *)aa; if (!valid_index(i, Py_SIZE(a))) { PyErr_SetString(PyExc_IndexError, "list assignment index out of range"); return -1; } - if (v == NULL) - return list_ass_slice(a, i, i+1, v); - Py_SETREF(a->ob_item[i], Py_NewRef(v)); + PyObject *tmp = a->ob_item[i]; + if (v == NULL) { + Py_ssize_t size = Py_SIZE(a); + for (Py_ssize_t idx = i; idx < size - 1; idx++) { + FT_ATOMIC_STORE_PTR_RELAXED(a->ob_item[idx], a->ob_item[idx + 1]); + } + Py_SET_SIZE(a, size - 1); + } + else { + FT_ATOMIC_STORE_PTR_RELEASE(a->ob_item[i], Py_NewRef(v)); + } + Py_DECREF(tmp); return 0; } +static int +list_ass_item(PyObject *aa, Py_ssize_t i, PyObject *v) +{ + int ret; + PyListObject *a = (PyListObject *)aa; + Py_BEGIN_CRITICAL_SECTION(a); + ret = list_ass_item_lock_held(a, i, v); + Py_END_CRITICAL_SECTION(); + return ret; +} + /*[clinic input] @critical_section list.insert @@ -2979,7 +3021,7 @@ list___sizeof___impl(PyListObject *self) /*[clinic end generated code: output=3417541f95f9a53e input=b8030a5d5ce8a187]*/ { size_t res = _PyObject_SIZE(Py_TYPE(self)); - Py_ssize_t allocated = LOAD_SSIZE(self->allocated); + Py_ssize_t allocated = FT_ATOMIC_LOAD_SSIZE_RELAXED(self->allocated); res += (size_t)allocated * sizeof(void*); return PyLong_FromSize_t(res); } @@ -3382,7 +3424,7 @@ static PyObject * listiter_next(PyObject *self) { _PyListIterObject *it = (_PyListIterObject *)self; - Py_ssize_t index = LOAD_SSIZE(it->it_index); + Py_ssize_t index = FT_ATOMIC_LOAD_SSIZE_RELAXED(it->it_index); if (index < 0) { return NULL; } @@ -3390,7 +3432,7 @@ listiter_next(PyObject *self) PyObject *item = list_get_item_ref(it->it_seq, index); if (item == NULL) { // out-of-bounds - STORE_SSIZE(it->it_index, -1); + FT_ATOMIC_STORE_SSIZE_RELAXED(it->it_index, -1); #ifndef Py_GIL_DISABLED PyListObject *seq = it->it_seq; it->it_seq = NULL; @@ -3398,7 +3440,7 @@ listiter_next(PyObject *self) #endif return NULL; } - STORE_SSIZE(it->it_index, index + 1); + FT_ATOMIC_STORE_SSIZE_RELAXED(it->it_index, index + 1); return item; } @@ -3407,7 +3449,7 @@ listiter_len(PyObject *self, PyObject *Py_UNUSED(ignored)) { assert(self != NULL); _PyListIterObject *it = (_PyListIterObject *)self; - Py_ssize_t index = LOAD_SSIZE(it->it_index); + Py_ssize_t index = FT_ATOMIC_LOAD_SSIZE_RELAXED(it->it_index); if (index >= 0) { Py_ssize_t len = PyList_GET_SIZE(it->it_seq) - index; if (len >= 0) @@ -3537,7 +3579,7 @@ listreviter_next(PyObject *self) { listreviterobject *it = (listreviterobject *)self; assert(it != NULL); - Py_ssize_t index = LOAD_SSIZE(it->it_index); + Py_ssize_t index = FT_ATOMIC_LOAD_SSIZE_RELAXED(it->it_index); if (index < 0) { return NULL; } @@ -3546,10 +3588,10 @@ listreviter_next(PyObject *self) assert(PyList_Check(seq)); PyObject *item = list_get_item_ref(seq, index); if (item != NULL) { - STORE_SSIZE(it->it_index, index - 1); + FT_ATOMIC_STORE_SSIZE_RELAXED(it->it_index, index - 1); return item; } - STORE_SSIZE(it->it_index, -1); + FT_ATOMIC_STORE_SSIZE_RELAXED(it->it_index, -1); #ifndef Py_GIL_DISABLED it->it_seq = NULL; Py_DECREF(seq); @@ -3561,7 +3603,7 @@ static PyObject * listreviter_len(PyObject *self, PyObject *Py_UNUSED(ignored)) { listreviterobject *it = (listreviterobject *)self; - Py_ssize_t index = LOAD_SSIZE(it->it_index); + Py_ssize_t index = FT_ATOMIC_LOAD_SSIZE_RELAXED(it->it_index); Py_ssize_t len = index + 1; if (it->it_seq == NULL || PyList_GET_SIZE(it->it_seq) < len) len = 0; From 1235e84276ab8eabca1348034453c614f0b6b543 Mon Sep 17 00:00:00 2001 From: Benjamin Peterson <benjamin@python.org> Date: Tue, 20 Feb 2024 17:40:48 -0800 Subject: [PATCH 392/507] Delete unused sym_clear_flag function. (#115744) --- Python/optimizer_analysis.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index e7fb1e38c0dfd72..5d2df9aca4e77f8 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -247,12 +247,6 @@ sym_set_flag(_Py_UOpsSymType *sym, int flag) sym->flags |= flag; } -static inline void -sym_clear_flag(_Py_UOpsSymType *sym, int flag) -{ - sym->flags &= (~flag); -} - static inline bool sym_has_flag(_Py_UOpsSymType *sym, int flag) { From 77430b6a329bb04ca884d08afefda25112372afa Mon Sep 17 00:00:00 2001 From: Victor Stinner <vstinner@python.org> Date: Wed, 21 Feb 2024 09:11:40 +0100 Subject: [PATCH 393/507] gh-110850: Replace private _PyTime_MAX with public PyTime_MAX (#115751) Remove references to the old names _PyTime_MIN and _PyTime_MAX, now that PyTime_MIN and PyTime_MAX are public. Replace also _PyTime_MIN with PyTime_MIN. --- Include/internal/pycore_time.h | 6 +----- Modules/_threadmodule.c | 2 +- Python/thread_pthread.h | 4 ++-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Include/internal/pycore_time.h b/Include/internal/pycore_time.h index 682aee2170bdaef..57ee55f14414a75 100644 --- a/Include/internal/pycore_time.h +++ b/Include/internal/pycore_time.h @@ -318,10 +318,6 @@ extern int _PyTime_PerfCounterWithInfo( PyTime_t *t, _Py_clock_info_t *info); -// Alias for backward compatibility -#define _PyTime_MIN PyTime_MIN -#define _PyTime_MAX PyTime_MAX - // --- _PyDeadline ----------------------------------------------------------- @@ -352,7 +348,7 @@ extern int _PyTimeFraction_Set( PyTime_t denom); // Compute ticks * frac.numer / frac.denom. -// Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow. +// Clamp to [PyTime_MIN; PyTime_MAX] on overflow. extern PyTime_t _PyTimeFraction_Mul( PyTime_t ticks, const _PyTimeFraction *frac); diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 25e100278790552..4c2185cc7ea1fd3 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1948,7 +1948,7 @@ thread_module_exec(PyObject *module) // TIMEOUT_MAX double timeout_max = (double)PY_TIMEOUT_MAX * 1e-6; - double time_max = PyTime_AsSecondsDouble(_PyTime_MAX); + double time_max = PyTime_AsSecondsDouble(PyTime_MAX); timeout_max = Py_MIN(timeout_max, time_max); // Round towards minus infinity timeout_max = floor(timeout_max); diff --git a/Python/thread_pthread.h b/Python/thread_pthread.h index 17f6ae7eb70553d..dee21f028ac48cd 100644 --- a/Python/thread_pthread.h +++ b/Python/thread_pthread.h @@ -486,9 +486,9 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds, if (microseconds >= 0) { // bpo-41710: PyThread_acquire_lock_timed() cannot report timeout // overflow to the caller, so clamp the timeout to - // [_PyTime_MIN, _PyTime_MAX]. + // [PyTime_MIN, PyTime_MAX]. // - // _PyTime_MAX nanoseconds is around 292.3 years. + // PyTime_MAX nanoseconds is around 292.3 years. // // _thread.Lock.acquire() and _thread.RLock.acquire() raise an // OverflowError if microseconds is greater than PY_TIMEOUT_MAX. From 10fc4675fdb14e19f2fdd15102c6533b9f71e992 Mon Sep 17 00:00:00 2001 From: Bogdan Romanyuk <65823030+wrongnull@users.noreply.github.com> Date: Wed, 21 Feb 2024 12:01:36 +0300 Subject: [PATCH 394/507] gh-115653: Document PyCode_GetFirstFree() (#115654) Correct the return type of the PyCode_GetNumFree() documentation. --- Doc/c-api/code.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/code.rst b/Doc/c-api/code.rst index 382cfbff864072c..f6fdd7574323c7e 100644 --- a/Doc/c-api/code.rst +++ b/Doc/c-api/code.rst @@ -30,9 +30,13 @@ bound into a function. Return true if *co* is a :ref:`code object <code-objects>`. This function always succeeds. -.. c:function:: int PyCode_GetNumFree(PyCodeObject *co) +.. c:function:: Py_ssize_t PyCode_GetNumFree(PyCodeObject *co) - Return the number of free variables in *co*. + Return the number of free variables in a code object. + +.. c:function:: int PyCode_GetFirstFree(PyCodeObject *co) + + Return the position of the first free variable in a code object. .. c:function:: PyCodeObject* PyUnstable_Code_New(int argcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, PyObject *qualname, int firstlineno, PyObject *linetable, PyObject *exceptiontable) From 074bbec9c4911da1d1155e56bd1693665800b814 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee <russell@keith-magee.com> Date: Wed, 21 Feb 2024 17:11:04 +0800 Subject: [PATCH 395/507] gh-115737: Correct libpython install name for macOS shared library builds. (gh-115750) --- Makefile.pre.in | 2 +- .../next/Build/2024-02-21-11-58-30.gh-issue-115737.dpNl2T.rst | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Build/2024-02-21-11-58-30.gh-issue-115737.dpNl2T.rst diff --git a/Makefile.pre.in b/Makefile.pre.in index 66c4266b2f8f97b..8130921b633e530 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -869,7 +869,7 @@ libpython3.so: libpython$(LDVERSION).so $(BLDSHARED) $(NO_AS_NEEDED) -o $@ -Wl,-h$@ $^ libpython$(LDVERSION).dylib: $(LIBRARY_OBJS) - $(CC) -dynamiclib $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(PYTHONFRAMEWORKINSTALLNAMEPREFIX)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ + $(CC) -dynamiclib $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ libpython$(VERSION).sl: $(LIBRARY_OBJS) diff --git a/Misc/NEWS.d/next/Build/2024-02-21-11-58-30.gh-issue-115737.dpNl2T.rst b/Misc/NEWS.d/next/Build/2024-02-21-11-58-30.gh-issue-115737.dpNl2T.rst new file mode 100644 index 000000000000000..112f65258dd84b0 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2024-02-21-11-58-30.gh-issue-115737.dpNl2T.rst @@ -0,0 +1,2 @@ +The install name for libPython is now correctly set for non-framework macOS +builds. From 69ab93082d14425aaac48b8393711c716575b132 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <44680962+15r10nk@users.noreply.github.com> Date: Wed, 21 Feb 2024 11:24:08 +0100 Subject: [PATCH 396/507] gh-112364: Correct unparsing of backslashes and quotes in ast.unparse (#115696) --- Lib/ast.py | 15 ++++++++------- Lib/test/test_unparse.py | 15 +++++++++++++++ ...2024-02-20-07-38-15.gh-issue-112364.EX7uGI.rst | 1 + 3 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-20-07-38-15.gh-issue-112364.EX7uGI.rst diff --git a/Lib/ast.py b/Lib/ast.py index 43703a8325cc5ec..b8c4ce6f919e6b4 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -1269,14 +1269,18 @@ def visit_JoinedStr(self, node): quote_type = quote_types[0] self.write(f"{quote_type}{value}{quote_type}") - def _write_fstring_inner(self, node, escape_newlines=False): + def _write_fstring_inner(self, node, is_format_spec=False): if isinstance(node, JoinedStr): # for both the f-string itself, and format_spec for value in node.values: - self._write_fstring_inner(value, escape_newlines=escape_newlines) + self._write_fstring_inner(value, is_format_spec=is_format_spec) elif isinstance(node, Constant) and isinstance(node.value, str): value = node.value.replace("{", "{{").replace("}", "}}") - if escape_newlines: + + if is_format_spec: + value = value.replace("\\", "\\\\") + value = value.replace("'", "\\'") + value = value.replace('"', '\\"') value = value.replace("\n", "\\n") self.write(value) elif isinstance(node, FormattedValue): @@ -1300,10 +1304,7 @@ def unparse_inner(inner): self.write(f"!{chr(node.conversion)}") if node.format_spec: self.write(":") - self._write_fstring_inner( - node.format_spec, - escape_newlines=True - ) + self._write_fstring_inner(node.format_spec, is_format_spec=True) def visit_Name(self, node): self.write(node.id) diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py index 6f698a8d8918154..77ce18cbf4cbfb5 100644 --- a/Lib/test/test_unparse.py +++ b/Lib/test/test_unparse.py @@ -649,6 +649,21 @@ def test_multiquote_joined_string(self): self.check_ast_roundtrip("""f'''""\"''\\'{"\\n\\"'"}''' """) self.check_ast_roundtrip("""f'''""\"''\\'{""\"\\n\\"'''""\" '''\\n'''}''' """) + def test_backslash_in_format_spec(self): + self.check_ast_roundtrip("""f"{x:\\ }" """) + self.check_ast_roundtrip("""f"{x:\\\\ }" """) + self.check_ast_roundtrip("""f"{x:\\\\\\ }" """) + self.check_ast_roundtrip("""f"{x:\\\\\\\\ }" """) + + def test_quote_in_format_spec(self): + self.check_ast_roundtrip("""f"{x:'}" """) + self.check_ast_roundtrip("""f"{x:\\'}" """) + self.check_ast_roundtrip("""f"{x:\\\\'}" """) + + self.check_ast_roundtrip("""f'\\'{x:"}' """) + self.check_ast_roundtrip("""f'\\'{x:\\"}' """) + self.check_ast_roundtrip("""f'\\'{x:\\\\"}' """) + class ManualASTCreationTestCase(unittest.TestCase): """Test that AST nodes created without a type_params field unparse correctly.""" diff --git a/Misc/NEWS.d/next/Library/2024-02-20-07-38-15.gh-issue-112364.EX7uGI.rst b/Misc/NEWS.d/next/Library/2024-02-20-07-38-15.gh-issue-112364.EX7uGI.rst new file mode 100644 index 000000000000000..6af71e60ec2a8ea --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-20-07-38-15.gh-issue-112364.EX7uGI.rst @@ -0,0 +1 @@ +Fixed :func:`ast.unparse` to handle format_spec with ``"``, ``'`` or ``\\``. Patched by Frank Hoffmann. From e4c34f04a197391576a692d77eaece1ea10abd87 Mon Sep 17 00:00:00 2001 From: Victor Stinner <vstinner@python.org> Date: Wed, 21 Feb 2024 11:46:00 +0100 Subject: [PATCH 397/507] gh-110850: Cleanup PyTime API: PyTime_t are nanoseconds (#115753) PyTime_t no longer uses an arbitrary unit, it's always a number of nanoseconds (64-bit signed integer). * Rename _PyTime_FromNanosecondsObject() to _PyTime_FromLong(). * Rename _PyTime_AsNanosecondsObject() to _PyTime_AsLong(). * Remove pytime_from_nanoseconds(). * Remove pytime_as_nanoseconds(). * Remove _PyTime_FromNanoseconds(). --- Include/internal/pycore_time.h | 13 ++--- Modules/_lsprof.c | 4 +- Modules/_testinternalcapi/pytime.c | 22 ++++----- Modules/timemodule.c | 33 ++++++------- Python/pytime.c | 77 +++++++++--------------------- Python/thread.c | 4 +- Python/thread_nt.h | 2 +- Python/thread_pthread.h | 2 +- 8 files changed, 57 insertions(+), 100 deletions(-) diff --git a/Include/internal/pycore_time.h b/Include/internal/pycore_time.h index 57ee55f14414a75..40b28e0ba221aee 100644 --- a/Include/internal/pycore_time.h +++ b/Include/internal/pycore_time.h @@ -140,17 +140,13 @@ PyAPI_FUNC(PyTime_t) _PyTime_FromSecondsDouble(double seconds, _PyTime_round_t r #define _PYTIME_FROMSECONDS(seconds) \ ((PyTime_t)(seconds) * (1000 * 1000 * 1000)) -// Create a timestamp from a number of nanoseconds. -// Export for '_testinternalcapi' shared extension. -PyAPI_FUNC(PyTime_t) _PyTime_FromNanoseconds(PyTime_t ns); - // Create a timestamp from a number of microseconds. // Clamp to [PyTime_MIN; PyTime_MAX] on overflow. extern PyTime_t _PyTime_FromMicrosecondsClamp(PyTime_t us); -// Create a timestamp from nanoseconds (Python int). +// Create a timestamp from a Python int object (number of nanoseconds). // Export for '_lsprof' shared extension. -PyAPI_FUNC(int) _PyTime_FromNanosecondsObject(PyTime_t *t, +PyAPI_FUNC(int) _PyTime_FromLong(PyTime_t *t, PyObject *obj); // Convert a number of seconds (Python float or int) to a timestamp. @@ -183,10 +179,9 @@ extern PyTime_t _PyTime_As100Nanoseconds(PyTime_t t, _PyTime_round_t round); #endif -// Convert timestamp to a number of nanoseconds (10^-9 seconds) as a Python int -// object. +// Convert a timestamp (number of nanoseconds) as a Python int object. // Export for '_testinternalcapi' shared extension. -PyAPI_FUNC(PyObject*) _PyTime_AsNanosecondsObject(PyTime_t t); +PyAPI_FUNC(PyObject*) _PyTime_AsLong(PyTime_t t); #ifndef MS_WINDOWS // Create a timestamp from a timeval structure. diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c index 29a80c70c0db6ac..f1cee7cb6f66bf8 100644 --- a/Modules/_lsprof.c +++ b/Modules/_lsprof.c @@ -6,7 +6,7 @@ #include "pycore_call.h" // _PyObject_CallNoArgs() #include "pycore_ceval.h" // _PyEval_SetProfile() #include "pycore_pystate.h" // _PyThreadState_GET() -#include "pycore_time.h" // _PyTime_FromNanosecondsObject() +#include "pycore_time.h" // _PyTime_FromLong() #include "rotatingtree.h" @@ -98,7 +98,7 @@ static PyTime_t CallExternalTimer(ProfilerObject *pObj) if (pObj->externalTimerUnit > 0.0) { /* interpret the result as an integer that will be scaled in profiler_getstats() */ - err = _PyTime_FromNanosecondsObject(&result, o); + err = _PyTime_FromLong(&result, o); } else { /* interpret the result as a double measured in seconds. diff --git a/Modules/_testinternalcapi/pytime.c b/Modules/_testinternalcapi/pytime.c index 2abe5c2b725713b..2b0a205d158a963 100644 --- a/Modules/_testinternalcapi/pytime.c +++ b/Modules/_testinternalcapi/pytime.c @@ -17,7 +17,7 @@ test_pytime_fromseconds(PyObject *self, PyObject *args) return NULL; } PyTime_t ts = _PyTime_FromSeconds(seconds); - return _PyTime_AsNanosecondsObject(ts); + return _PyTime_AsLong(ts); } static int @@ -49,7 +49,7 @@ test_pytime_fromsecondsobject(PyObject *self, PyObject *args) if (_PyTime_FromSecondsObject(&ts, obj, round) == -1) { return NULL; } - return _PyTime_AsNanosecondsObject(ts); + return _PyTime_AsLong(ts); } static PyObject * @@ -64,7 +64,7 @@ test_PyTime_AsTimeval(PyObject *self, PyObject *args) return NULL; } PyTime_t t; - if (_PyTime_FromNanosecondsObject(&t, obj) < 0) { + if (_PyTime_FromLong(&t, obj) < 0) { return NULL; } struct timeval tv; @@ -91,7 +91,7 @@ test_PyTime_AsTimeval_clamp(PyObject *self, PyObject *args) return NULL; } PyTime_t t; - if (_PyTime_FromNanosecondsObject(&t, obj) < 0) { + if (_PyTime_FromLong(&t, obj) < 0) { return NULL; } struct timeval tv; @@ -113,7 +113,7 @@ test_PyTime_AsTimespec(PyObject *self, PyObject *args) return NULL; } PyTime_t t; - if (_PyTime_FromNanosecondsObject(&t, obj) < 0) { + if (_PyTime_FromLong(&t, obj) < 0) { return NULL; } struct timespec ts; @@ -131,7 +131,7 @@ test_PyTime_AsTimespec_clamp(PyObject *self, PyObject *args) return NULL; } PyTime_t t; - if (_PyTime_FromNanosecondsObject(&t, obj) < 0) { + if (_PyTime_FromLong(&t, obj) < 0) { return NULL; } struct timespec ts; @@ -149,15 +149,14 @@ test_PyTime_AsMilliseconds(PyObject *self, PyObject *args) return NULL; } PyTime_t t; - if (_PyTime_FromNanosecondsObject(&t, obj) < 0) { + if (_PyTime_FromLong(&t, obj) < 0) { return NULL; } if (check_time_rounding(round) < 0) { return NULL; } PyTime_t ms = _PyTime_AsMilliseconds(t, round); - PyTime_t ns = _PyTime_FromNanoseconds(ms); - return _PyTime_AsNanosecondsObject(ns); + return _PyTime_AsLong(ms); } static PyObject * @@ -169,15 +168,14 @@ test_PyTime_AsMicroseconds(PyObject *self, PyObject *args) return NULL; } PyTime_t t; - if (_PyTime_FromNanosecondsObject(&t, obj) < 0) { + if (_PyTime_FromLong(&t, obj) < 0) { return NULL; } if (check_time_rounding(round) < 0) { return NULL; } PyTime_t us = _PyTime_AsMicroseconds(t, round); - PyTime_t ns = _PyTime_FromNanoseconds(us); - return _PyTime_AsNanosecondsObject(ns); + return _PyTime_AsLong(us); } static PyObject * diff --git a/Modules/timemodule.c b/Modules/timemodule.c index ac96ed40a9bb278..fc493bde599ce8d 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -127,7 +127,7 @@ time_time_ns(PyObject *self, PyObject *unused) if (PyTime_Time(&t) < 0) { return NULL; } - return _PyTime_AsNanosecondsObject(t); + return _PyTime_AsLong(t); } PyDoc_STRVAR(time_ns_doc, @@ -164,8 +164,7 @@ py_clock(time_module_state *state, PyTime_t *tp, _Py_clock_info_t *info) "or its value cannot be represented"); return -1; } - PyTime_t ns = _PyTimeFraction_Mul(ticks, base); - *tp = _PyTime_FromNanoseconds(ns); + *tp = _PyTimeFraction_Mul(ticks, base); return 0; } #endif /* HAVE_CLOCK */ @@ -259,7 +258,7 @@ time_clock_gettime_ns_impl(PyObject *module, clockid_t clk_id) if (_PyTime_FromTimespec(&t, &ts) < 0) { return NULL; } - return _PyTime_AsNanosecondsObject(t); + return _PyTime_AsLong(t); } #endif /* HAVE_CLOCK_GETTIME */ @@ -308,7 +307,7 @@ time_clock_settime_ns(PyObject *self, PyObject *args) return NULL; } - if (_PyTime_FromNanosecondsObject(&t, obj) < 0) { + if (_PyTime_FromLong(&t, obj) < 0) { return NULL; } if (_PyTime_AsTimespec(t, &ts) == -1) { @@ -1170,7 +1169,7 @@ time_monotonic_ns(PyObject *self, PyObject *unused) if (PyTime_Monotonic(&t) < 0) { return NULL; } - return _PyTime_AsNanosecondsObject(t); + return _PyTime_AsLong(t); } PyDoc_STRVAR(monotonic_ns_doc, @@ -1202,7 +1201,7 @@ time_perf_counter_ns(PyObject *self, PyObject *unused) if (PyTime_PerfCounter(&t) < 0) { return NULL; } - return _PyTime_AsNanosecondsObject(t); + return _PyTime_AsLong(t); } PyDoc_STRVAR(perf_counter_ns_doc, @@ -1233,7 +1232,7 @@ process_time_times(time_module_state *state, PyTime_t *tp, PyTime_t ns; ns = _PyTimeFraction_Mul(process.tms_utime, base); ns += _PyTimeFraction_Mul(process.tms_stime, base); - *tp = _PyTime_FromNanoseconds(ns); + *tp = ns; return 1; } #endif @@ -1247,7 +1246,7 @@ py_process_time(time_module_state *state, PyTime_t *tp, HANDLE process; FILETIME creation_time, exit_time, kernel_time, user_time; ULARGE_INTEGER large; - PyTime_t ktime, utime, t; + PyTime_t ktime, utime; BOOL ok; process = GetCurrentProcess(); @@ -1274,8 +1273,7 @@ py_process_time(time_module_state *state, PyTime_t *tp, utime = large.QuadPart; /* ktime and utime have a resolution of 100 nanoseconds */ - t = _PyTime_FromNanoseconds((ktime + utime) * 100); - *tp = t; + *tp = (ktime + utime) * 100; return 0; #else @@ -1383,7 +1381,7 @@ time_process_time_ns(PyObject *module, PyObject *unused) if (py_process_time(state, &t, NULL) < 0) { return NULL; } - return _PyTime_AsNanosecondsObject(t); + return _PyTime_AsLong(t); } PyDoc_STRVAR(process_time_ns_doc, @@ -1401,7 +1399,7 @@ _PyTime_GetThreadTimeWithInfo(PyTime_t *tp, _Py_clock_info_t *info) HANDLE thread; FILETIME creation_time, exit_time, kernel_time, user_time; ULARGE_INTEGER large; - PyTime_t ktime, utime, t; + PyTime_t ktime, utime; BOOL ok; thread = GetCurrentThread(); @@ -1428,8 +1426,7 @@ _PyTime_GetThreadTimeWithInfo(PyTime_t *tp, _Py_clock_info_t *info) utime = large.QuadPart; /* ktime and utime have a resolution of 100 nanoseconds */ - t = _PyTime_FromNanoseconds((ktime + utime) * 100); - *tp = t; + *tp = (ktime + utime) * 100; return 0; } @@ -1453,7 +1450,7 @@ _PyTime_GetThreadTimeWithInfo(PyTime_t *tp, _Py_clock_info_t *info) info->adjustable = 0; info->resolution = 1e-9; } - *tp = _PyTime_FromNanoseconds(tc.stime + tc.utime); + *tp = (tc.stime + tc.utime); return 0; } @@ -1470,7 +1467,7 @@ _PyTime_GetThreadTimeWithInfo(PyTime_t *tp, _Py_clock_info_t *info) info->monotonic = 1; info->adjustable = 0; } - *tp = _PyTime_FromNanoseconds(gethrvtime()); + *tp = gethrvtime(); return 0; } @@ -1550,7 +1547,7 @@ time_thread_time_ns(PyObject *self, PyObject *unused) if (_PyTime_GetThreadTimeWithInfo(&t, NULL) < 0) { return NULL; } - return _PyTime_AsNanosecondsObject(t); + return _PyTime_AsLong(t); } PyDoc_STRVAR(thread_time_ns_doc, diff --git a/Python/pytime.c b/Python/pytime.c index c3534d9a1ca44bc..90ef2eeb546f7fd 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -108,22 +108,6 @@ pytime_overflow(void) } -static inline PyTime_t -pytime_from_nanoseconds(PyTime_t t) -{ - // PyTime_t is a number of nanoseconds - return t; -} - - -static inline PyTime_t -pytime_as_nanoseconds(PyTime_t t) -{ - // PyTime_t is a number of nanoseconds: see pytime_from_nanoseconds() - return t; -} - - // Compute t1 + t2. Clamp to [PyTime_MIN; PyTime_MAX] on overflow. static inline int pytime_add(PyTime_t *t1, PyTime_t t2) @@ -271,7 +255,7 @@ _PyTime_AsTime_t(PyTime_t t, time_t *t2) // Convert PyTime_t to long. // Return 0 on success. Return -1 and clamp the value on overflow. static int -_PyTime_AsLong(PyTime_t t, long *t2) +_PyTime_AsCLong(PyTime_t t, long *t2) { #if SIZEOF_LONG < _SIZEOF_PYTIME_T if ((PyTime_t)LONG_MAX < t) { @@ -466,14 +450,7 @@ _PyTime_FromSeconds(int seconds) assert((t >= 0 && t <= PyTime_MAX / SEC_TO_NS) || (t < 0 && t >= PyTime_MIN / SEC_TO_NS)); t *= SEC_TO_NS; - return pytime_from_nanoseconds(t); -} - - -PyTime_t -_PyTime_FromNanoseconds(PyTime_t ns) -{ - return pytime_from_nanoseconds(ns); + return t; } @@ -481,14 +458,13 @@ PyTime_t _PyTime_FromMicrosecondsClamp(PyTime_t us) { PyTime_t ns = _PyTime_Mul(us, US_TO_NS); - return pytime_from_nanoseconds(ns); + return ns; } int -_PyTime_FromNanosecondsObject(PyTime_t *tp, PyObject *obj) +_PyTime_FromLong(PyTime_t *tp, PyObject *obj) { - if (!PyLong_Check(obj)) { PyErr_Format(PyExc_TypeError, "expect int, got %s", Py_TYPE(obj)->tp_name); @@ -506,7 +482,7 @@ _PyTime_FromNanosecondsObject(PyTime_t *tp, PyObject *obj) } PyTime_t t = (PyTime_t)nsec; - *tp = pytime_from_nanoseconds(t); + *tp = t; return 0; } @@ -526,7 +502,7 @@ pytime_fromtimespec(PyTime_t *tp, const struct timespec *ts, int raise_exc) tv_nsec = ts->tv_nsec; int res2 = pytime_add(&t, tv_nsec); - *tp = pytime_from_nanoseconds(t); + *tp = t; if (raise_exc && (res1 < 0 || res2 < 0)) { pytime_overflow(); @@ -556,7 +532,7 @@ pytime_fromtimeval(PyTime_t *tp, struct timeval *tv, int raise_exc) PyTime_t usec = (PyTime_t)tv->tv_usec * US_TO_NS; int res2 = pytime_add(&t, usec); - *tp = pytime_from_nanoseconds(t); + *tp = t; if (raise_exc && (res1 < 0 || res2 < 0)) { pytime_overflow(); @@ -593,7 +569,7 @@ pytime_from_double(PyTime_t *tp, double value, _PyTime_round_t round, } PyTime_t ns = (PyTime_t)d; - *tp = pytime_from_nanoseconds(ns); + *tp = ns; return 0; } @@ -628,7 +604,7 @@ pytime_from_object(PyTime_t *tp, PyObject *obj, _PyTime_round_t round, return -1; } - *tp = pytime_from_nanoseconds(ns); + *tp = ns; return 0; } } @@ -649,12 +625,11 @@ _PyTime_FromMillisecondsObject(PyTime_t *tp, PyObject *obj, _PyTime_round_t roun double -PyTime_AsSecondsDouble(PyTime_t t) +PyTime_AsSecondsDouble(PyTime_t ns) { /* volatile avoids optimization changing how numbers are rounded */ volatile double d; - PyTime_t ns = pytime_as_nanoseconds(t); if (ns % SEC_TO_NS == 0) { /* Divide using integers to avoid rounding issues on the integer part. 1e-9 cannot be stored exactly in IEEE 64-bit. */ @@ -670,9 +645,8 @@ PyTime_AsSecondsDouble(PyTime_t t) PyObject * -_PyTime_AsNanosecondsObject(PyTime_t t) +_PyTime_AsLong(PyTime_t ns) { - PyTime_t ns = pytime_as_nanoseconds(t); static_assert(sizeof(long long) >= sizeof(PyTime_t), "PyTime_t is larger than long long"); return PyLong_FromLongLong((long long)ns); @@ -786,35 +760,31 @@ pytime_divmod(const PyTime_t t, const PyTime_t k, #ifdef MS_WINDOWS PyTime_t -_PyTime_As100Nanoseconds(PyTime_t t, _PyTime_round_t round) +_PyTime_As100Nanoseconds(PyTime_t ns, _PyTime_round_t round) { - PyTime_t ns = pytime_as_nanoseconds(t); return pytime_divide(ns, NS_TO_100NS, round); } #endif PyTime_t -_PyTime_AsMicroseconds(PyTime_t t, _PyTime_round_t round) +_PyTime_AsMicroseconds(PyTime_t ns, _PyTime_round_t round) { - PyTime_t ns = pytime_as_nanoseconds(t); return pytime_divide(ns, NS_TO_US, round); } PyTime_t -_PyTime_AsMilliseconds(PyTime_t t, _PyTime_round_t round) +_PyTime_AsMilliseconds(PyTime_t ns, _PyTime_round_t round) { - PyTime_t ns = pytime_as_nanoseconds(t); return pytime_divide(ns, NS_TO_MS, round); } static int -pytime_as_timeval(PyTime_t t, PyTime_t *ptv_sec, int *ptv_usec, +pytime_as_timeval(PyTime_t ns, PyTime_t *ptv_sec, int *ptv_usec, _PyTime_round_t round) { - PyTime_t ns = pytime_as_nanoseconds(t); PyTime_t us = pytime_divide(ns, US_TO_NS, round); PyTime_t tv_sec, tv_usec; @@ -835,7 +805,7 @@ pytime_as_timeval_struct(PyTime_t t, struct timeval *tv, int res2; #ifdef MS_WINDOWS // On Windows, timeval.tv_sec type is long - res2 = _PyTime_AsLong(tv_sec, &tv->tv_sec); + res2 = _PyTime_AsCLong(tv_sec, &tv->tv_sec); #else res2 = _PyTime_AsTime_t(tv_sec, &tv->tv_sec); #endif @@ -886,9 +856,8 @@ _PyTime_AsTimevalTime_t(PyTime_t t, time_t *p_secs, int *us, #if defined(HAVE_CLOCK_GETTIME) || defined(HAVE_KQUEUE) static int -pytime_as_timespec(PyTime_t t, struct timespec *ts, int raise_exc) +pytime_as_timespec(PyTime_t ns, struct timespec *ts, int raise_exc) { - PyTime_t ns = pytime_as_nanoseconds(t); PyTime_t tv_sec, tv_nsec; int res = pytime_divmod(ns, SEC_TO_NS, &tv_sec, &tv_nsec); @@ -936,7 +905,7 @@ py_get_system_clock(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) the 1st january 1601 and the 1st january 1970 (369 years + 89 leap days). */ PyTime_t ns = large.QuadPart * 100 - 11644473600000000000; - *tp = pytime_from_nanoseconds(ns); + *tp = ns; if (info) { DWORD timeAdjustment, timeIncrement; BOOL isTimeAdjustmentDisabled, ok; @@ -1160,12 +1129,10 @@ py_get_monotonic_clock(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) PyTime_t ticks = (PyTime_t)uticks; PyTime_t ns = _PyTimeFraction_Mul(ticks, &base); - *tp = pytime_from_nanoseconds(ns); + *tp = ns; #elif defined(__hpux) - hrtime_t time; - - time = gethrtime(); + hrtime_t time = gethrtime(); if (time == -1) { if (raise_exc) { PyErr_SetFromErrno(PyExc_OSError); @@ -1173,7 +1140,7 @@ py_get_monotonic_clock(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) return -1; } - *tp = pytime_from_nanoseconds(time); + *tp = time; if (info) { info->implementation = "gethrtime()"; @@ -1316,7 +1283,7 @@ py_get_win_perf_counter(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) ticks = (PyTime_t)ticksll; PyTime_t ns = _PyTimeFraction_Mul(ticks, &base); - *tp = pytime_from_nanoseconds(ns); + *tp = ns; return 0; } #endif // MS_WINDOWS diff --git a/Python/thread.c b/Python/thread.c index cafcaa0b9acbb13..b31d1dc5e770eff 100644 --- a/Python/thread.c +++ b/Python/thread.c @@ -20,8 +20,8 @@ // Define PY_TIMEOUT_MAX constant. #ifdef _POSIX_THREADS - // PyThread_acquire_lock_timed() uses _PyTime_FromNanoseconds(us * 1000), - // convert microseconds to nanoseconds. + // PyThread_acquire_lock_timed() uses (us * 1000) to convert microseconds + // to nanoseconds. # define PY_TIMEOUT_MAX_VALUE (LLONG_MAX / 1000) #elif defined (NT_THREADS) // WaitForSingleObject() accepts timeout in milliseconds in the range diff --git a/Python/thread_nt.h b/Python/thread_nt.h index e7591600c6416a4..7922b2d7e848454 100644 --- a/Python/thread_nt.h +++ b/Python/thread_nt.h @@ -77,7 +77,7 @@ EnterNonRecursiveMutex(PNRMUTEX mutex, DWORD milliseconds) } } else if (milliseconds != 0) { /* wait at least until the deadline */ - PyTime_t nanoseconds = _PyTime_FromNanoseconds((PyTime_t)milliseconds * 1000000); + PyTime_t nanoseconds = (PyTime_t)milliseconds * (1000 * 1000); PyTime_t deadline = _PyTime_Add(_PyTime_PerfCounterUnchecked(), nanoseconds); while (mutex->locked) { PyTime_t microseconds = _PyTime_AsMicroseconds(nanoseconds, diff --git a/Python/thread_pthread.h b/Python/thread_pthread.h index dee21f028ac48cd..64cc60053e6cf7a 100644 --- a/Python/thread_pthread.h +++ b/Python/thread_pthread.h @@ -495,7 +495,7 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds, timeout = _PyTime_FromMicrosecondsClamp(microseconds); } else { - timeout = _PyTime_FromNanoseconds(-1); + timeout = -1; } #ifdef HAVE_SEM_CLOCKWAIT From fbd40ce46e7335a5dbaf48a3aa841be22d7302ba Mon Sep 17 00:00:00 2001 From: Sebastian Pipping <sebastian@pipping.org> Date: Wed, 21 Feb 2024 12:26:16 +0100 Subject: [PATCH 398/507] gh-115399: Document CVE-2023-52425 under "XML vulnerabilities" (GH-115400) Doc/library/xml.rst: Document CVE-2023-52425 under "XML vulnerabilities" --- Doc/library/xml.rst | 13 +++++++++++++ .../2024-02-14-20-17-04.gh-issue-115399.fb9a0R.rst | 1 + 2 files changed, 14 insertions(+) create mode 100644 Misc/NEWS.d/next/Documentation/2024-02-14-20-17-04.gh-issue-115399.fb9a0R.rst diff --git a/Doc/library/xml.rst b/Doc/library/xml.rst index 909022ea4ba6a4b..662cc459197e2ca 100644 --- a/Doc/library/xml.rst +++ b/Doc/library/xml.rst @@ -68,6 +68,7 @@ quadratic blowup **Vulnerable** (1) **Vulnerable** (1) **Vulnerable* external entity expansion Safe (5) Safe (2) Safe (3) Safe (5) Safe (4) `DTD`_ retrieval Safe (5) Safe Safe Safe (5) Safe decompression bomb Safe Safe Safe Safe **Vulnerable** +large tokens **Vulnerable** (6) **Vulnerable** (6) **Vulnerable** (6) **Vulnerable** (6) **Vulnerable** (6) ========================= ================== ================== ================== ================== ================== 1. Expat 2.4.1 and newer is not vulnerable to the "billion laughs" and @@ -81,6 +82,11 @@ decompression bomb Safe Safe Safe 4. :mod:`xmlrpc.client` doesn't expand external entities and omits them. 5. Since Python 3.7.1, external general entities are no longer processed by default. +6. Expat 2.6.0 and newer is not vulnerable to denial of service + through quadratic runtime caused by parsing large tokens. + Items still listed as vulnerable due to + potential reliance on system-provided libraries. Check + :const:`!pyexpat.EXPAT_VERSION`. billion laughs / exponential entity expansion @@ -114,6 +120,13 @@ decompression bomb files. For an attacker it can reduce the amount of transmitted data by three magnitudes or more. +large tokens + Expat needs to re-parse unfinished tokens; without the protection + introduced in Expat 2.6.0, this can lead to quadratic runtime that can + be used to cause denial of service in the application parsing XML. + The issue is known as + `CVE-2023-52425 <https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-52425>`_. + The documentation for `defusedxml`_ on PyPI has further information about all known attack vectors with examples and references. diff --git a/Misc/NEWS.d/next/Documentation/2024-02-14-20-17-04.gh-issue-115399.fb9a0R.rst b/Misc/NEWS.d/next/Documentation/2024-02-14-20-17-04.gh-issue-115399.fb9a0R.rst new file mode 100644 index 000000000000000..587aea802168bd6 --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2024-02-14-20-17-04.gh-issue-115399.fb9a0R.rst @@ -0,0 +1 @@ +Document CVE-2023-52425 of Expat <2.6.0 under "XML vulnerabilities". From b052fa381fa2ce6820332d56fb22cd7156529d24 Mon Sep 17 00:00:00 2001 From: Frank Dana <ferdnyc@gmail.com> Date: Wed, 21 Feb 2024 06:32:28 -0500 Subject: [PATCH 399/507] argparse: remove incoherent and redundant docstring for private method (GH-101591) Signed-off-by: FeRD (Frank Dana) <ferdnyc@gmail.com> --- Lib/argparse.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 6ef0bea5c6cd4aa..ea39d26687b9314 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -698,14 +698,6 @@ class ArgumentDefaultsHelpFormatter(HelpFormatter): """ def _get_help_string(self, action): - """ - Add the default value to the option help message. - - ArgumentDefaultsHelpFormatter and BooleanOptionalAction when it isn't - already present. This code will do that, detecting cornercases to - prevent duplicates or cases where it wouldn't make sense to the end - user. - """ help = action.help if help is None: help = '' From 4a9e6497c2cae40647589497e033b0b590a180ba Mon Sep 17 00:00:00 2001 From: Petr Viktorin <encukou@gmail.com> Date: Wed, 21 Feb 2024 13:54:57 +0100 Subject: [PATCH 400/507] gh-104090: Add exit code to multiprocessing ResourceTracker (GH-115410) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This builds on https://github.com/python/cpython/pull/106807, which adds a return code to ResourceTracker, to make future debugging easier. Testing this “in situ” proved difficult, since the global ResourceTracker is involved in test infrastructure. So, the tests here create a new instance and feed it fake data. --------- Co-authored-by: Yonatan Bitton <yonatan.bitton@perception-point.io> Co-authored-by: Yonatan Bitton <bityob@gmail.com> Co-authored-by: Antoine Pitrou <antoine@python.org> --- Lib/multiprocessing/resource_tracker.py | 38 ++++++++++++++++--- Lib/test/_test_multiprocessing.py | 35 ++++++++++++++++- Lib/test/test_concurrent_futures/test_init.py | 26 +++++++++++++ ...-07-16-15-02-47.gh-issue-104090.oMjNa9.rst | 2 + 4 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-07-16-15-02-47.gh-issue-104090.oMjNa9.rst diff --git a/Lib/multiprocessing/resource_tracker.py b/Lib/multiprocessing/resource_tracker.py index 8e41f461cc934e0..20ddd9c50e3d884 100644 --- a/Lib/multiprocessing/resource_tracker.py +++ b/Lib/multiprocessing/resource_tracker.py @@ -29,8 +29,12 @@ _HAVE_SIGMASK = hasattr(signal, 'pthread_sigmask') _IGNORED_SIGNALS = (signal.SIGINT, signal.SIGTERM) +def cleanup_noop(name): + raise RuntimeError('noop should never be registered or cleaned up') + _CLEANUP_FUNCS = { - 'noop': lambda: None, + 'noop': cleanup_noop, + 'dummy': lambda name: None, # Dummy resource used in tests } if os.name == 'posix': @@ -61,6 +65,7 @@ def __init__(self): self._lock = threading.RLock() self._fd = None self._pid = None + self._exitcode = None def _reentrant_call_error(self): # gh-109629: this happens if an explicit call to the ResourceTracker @@ -84,9 +89,16 @@ def _stop(self): os.close(self._fd) self._fd = None - os.waitpid(self._pid, 0) + _, status = os.waitpid(self._pid, 0) + self._pid = None + try: + self._exitcode = os.waitstatus_to_exitcode(status) + except ValueError: + # os.waitstatus_to_exitcode may raise an exception for invalid values + self._exitcode = None + def getfd(self): self.ensure_running() return self._fd @@ -119,6 +131,7 @@ def ensure_running(self): pass self._fd = None self._pid = None + self._exitcode = None warnings.warn('resource_tracker: process died unexpectedly, ' 'relaunching. Some resources might leak.') @@ -221,6 +234,8 @@ def main(fd): pass cache = {rtype: set() for rtype in _CLEANUP_FUNCS.keys()} + exit_code = 0 + try: # keep track of registered/unregistered resources with open(fd, 'rb') as f: @@ -242,6 +257,7 @@ def main(fd): else: raise RuntimeError('unrecognized command %r' % cmd) except Exception: + exit_code = 3 try: sys.excepthook(*sys.exc_info()) except: @@ -251,10 +267,17 @@ def main(fd): for rtype, rtype_cache in cache.items(): if rtype_cache: try: - warnings.warn( - f'resource_tracker: There appear to be {len(rtype_cache)} ' - f'leaked {rtype} objects to clean up at shutdown: {rtype_cache}' - ) + exit_code = 1 + if rtype == 'dummy': + # The test 'dummy' resource is expected to leak. + # We skip the warning (and *only* the warning) for it. + pass + else: + warnings.warn( + f'resource_tracker: There appear to be ' + f'{len(rtype_cache)} leaked {rtype} objects to ' + f'clean up at shutdown: {rtype_cache}' + ) except Exception: pass for name in rtype_cache: @@ -265,6 +288,9 @@ def main(fd): try: _CLEANUP_FUNCS[rtype](name) except Exception as e: + exit_code = 2 warnings.warn('resource_tracker: %r: %s' % (name, e)) finally: pass + + sys.exit(exit_code) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 94ce85cac754ae5..e4183eea959dd58 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -5609,8 +5609,9 @@ def create_and_register_resource(rtype): ''' for rtype in resource_tracker._CLEANUP_FUNCS: with self.subTest(rtype=rtype): - if rtype == "noop": + if rtype in ("noop", "dummy"): # Artefact resource type used by the resource_tracker + # or tests continue r, w = os.pipe() p = subprocess.Popen([sys.executable, @@ -5730,6 +5731,38 @@ def test_too_long_name_resource(self): with self.assertRaises(ValueError): resource_tracker.register(too_long_name_resource, rtype) + def _test_resource_tracker_leak_resources(self, cleanup): + # We use a separate instance for testing, since the main global + # _resource_tracker may be used to watch test infrastructure. + from multiprocessing.resource_tracker import ResourceTracker + tracker = ResourceTracker() + tracker.ensure_running() + self.assertTrue(tracker._check_alive()) + + self.assertIsNone(tracker._exitcode) + tracker.register('somename', 'dummy') + if cleanup: + tracker.unregister('somename', 'dummy') + expected_exit_code = 0 + else: + expected_exit_code = 1 + + self.assertTrue(tracker._check_alive()) + self.assertIsNone(tracker._exitcode) + tracker._stop() + self.assertEqual(tracker._exitcode, expected_exit_code) + + def test_resource_tracker_exit_code(self): + """ + Test the exit code of the resource tracker. + + If no leaked resources were found, exit code should be 0, otherwise 1 + """ + for cleanup in [True, False]: + with self.subTest(cleanup=cleanup): + self._test_resource_tracker_leak_resources( + cleanup=cleanup, + ) class TestSimpleQueue(unittest.TestCase): diff --git a/Lib/test/test_concurrent_futures/test_init.py b/Lib/test/test_concurrent_futures/test_init.py index ce01e0ff0f287a0..d79a6367701fb4b 100644 --- a/Lib/test/test_concurrent_futures/test_init.py +++ b/Lib/test/test_concurrent_futures/test_init.py @@ -3,6 +3,7 @@ import queue import time import unittest +import sys from concurrent.futures._base import BrokenExecutor from logging.handlers import QueueHandler @@ -109,6 +110,31 @@ def _assert_logged(self, msg): create_executor_tests(globals(), FailingInitializerMixin) +@unittest.skipIf(sys.platform == "win32", "Resource Tracker doesn't run on Windows") +class FailingInitializerResourcesTest(unittest.TestCase): + """ + Source: https://github.com/python/cpython/issues/104090 + """ + + def _test(self, test_class): + runner = unittest.TextTestRunner() + runner.run(test_class('test_initializer')) + + # GH-104090: + # Stop resource tracker manually now, so we can verify there are not leaked resources by checking + # the process exit code + from multiprocessing.resource_tracker import _resource_tracker + _resource_tracker._stop() + + self.assertEqual(_resource_tracker._exitcode, 0) + + def test_spawn(self): + self._test(ProcessPoolSpawnFailingInitializerTest) + + def test_forkserver(self): + self._test(ProcessPoolForkserverFailingInitializerTest) + + def setUpModule(): setup_module() diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-07-16-15-02-47.gh-issue-104090.oMjNa9.rst b/Misc/NEWS.d/next/Core and Builtins/2023-07-16-15-02-47.gh-issue-104090.oMjNa9.rst new file mode 100644 index 000000000000000..e581d291d047aee --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-07-16-15-02-47.gh-issue-104090.oMjNa9.rst @@ -0,0 +1,2 @@ +The multiprocessing resource tracker now exits with non-zero status code if a resource +leak was detected. It still exits with status code 0 otherwise. From 5f7df88821347c5f44fc4e2c691e83a60a6c6cd5 Mon Sep 17 00:00:00 2001 From: Daniel Mach <daniel.mach@suse.com> Date: Wed, 21 Feb 2024 14:58:04 +0100 Subject: [PATCH 401/507] gh-96310: Fix a traceback in argparse when all options in a mutually exclusive group are suppressed (GH-96311) Reproducer depends on terminal size - the traceback occurs when there's an option long enough so the usage line doesn't fit the terminal width. Option order is also important for reproducibility. Excluding empty groups (with all options suppressed) from inserts fixes the problem. --- Lib/argparse.py | 2 ++ Lib/test/test_argparse.py | 21 +++++++++++++++++++ ...2-08-26-15-50-53.gh-issue-96310.0NssDh.rst | 2 ++ 3 files changed, 25 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2022-08-26-15-50-53.gh-issue-96310.0NssDh.rst diff --git a/Lib/argparse.py b/Lib/argparse.py index ea39d26687b9314..f86658baf7f2bac 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -413,6 +413,8 @@ def _format_actions_usage(self, actions, groups): suppressed_actions_count += 1 exposed_actions_count = group_action_count - suppressed_actions_count + if not exposed_actions_count: + continue if not group.required: if start in inserts: diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 65fd9cf1a4a567a..617b1721f3dbb1c 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2864,6 +2864,27 @@ def test_help(self): ''' self.assertEqual(parser.format_help(), textwrap.dedent(expected)) + def test_help_subparser_all_mutually_exclusive_group_members_suppressed(self): + self.maxDiff = None + parser = ErrorRaisingArgumentParser(prog='PROG') + commands = parser.add_subparsers(title="commands", dest="command") + cmd_foo = commands.add_parser("foo") + group = cmd_foo.add_mutually_exclusive_group() + group.add_argument('--verbose', action='store_true', help=argparse.SUPPRESS) + group.add_argument('--quiet', action='store_true', help=argparse.SUPPRESS) + longopt = '--' + 'long'*32 + longmeta = 'LONG'*32 + cmd_foo.add_argument(longopt) + expected = f'''\ + usage: PROG foo [-h] + [{longopt} {longmeta}] + + options: + -h, --help show this help message and exit + {longopt} {longmeta} + ''' + self.assertEqual(cmd_foo.format_help(), textwrap.dedent(expected)) + def test_empty_group(self): # See issue 26952 parser = argparse.ArgumentParser() diff --git a/Misc/NEWS.d/next/Library/2022-08-26-15-50-53.gh-issue-96310.0NssDh.rst b/Misc/NEWS.d/next/Library/2022-08-26-15-50-53.gh-issue-96310.0NssDh.rst new file mode 100644 index 000000000000000..f8efb0002e104a4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-08-26-15-50-53.gh-issue-96310.0NssDh.rst @@ -0,0 +1,2 @@ +Fix a traceback in :mod:`argparse` when all options in a mutually exclusive +group are suppressed. From 87a65a5bd446a6fc74db651e56b04c332e33fa07 Mon Sep 17 00:00:00 2001 From: AN Long <aisk@users.noreply.github.com> Date: Thu, 22 Feb 2024 01:35:53 +0800 Subject: [PATCH 402/507] gh-115304: Add doc for initializing PyMutex as a global variable (#115305) --- Include/internal/pycore_lock.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Include/internal/pycore_lock.h b/Include/internal/pycore_lock.h index 07bf3db16f3d3af..c89159b55e130f6 100644 --- a/Include/internal/pycore_lock.h +++ b/Include/internal/pycore_lock.h @@ -26,6 +26,9 @@ extern "C" { // Typical initialization: // PyMutex m = (PyMutex){0}; // +// Or initialize as global variables: +// static PyMutex m; +// // Typical usage: // PyMutex_Lock(&m); // ... From 347acded845d07b70232cade8d576b6f0aaeb473 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Wed, 21 Feb 2024 13:00:08 -0500 Subject: [PATCH 403/507] gh-115491: Keep some fields valid across allocations in obmalloc (free-threading) (#115745) --- Objects/object.c | 12 ++++++------ Objects/obmalloc.c | 31 +++++++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index 23eab8288a41e8b..df14fe0c6fbfec1 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -299,13 +299,13 @@ _Py_DecRef(PyObject *o) #ifdef Py_GIL_DISABLED # ifdef Py_REF_DEBUG -static inline int -is_shared_refcnt_dead(Py_ssize_t shared) +static int +is_dead(PyObject *o) { # if SIZEOF_SIZE_T == 8 - return shared == (Py_ssize_t)0xDDDDDDDDDDDDDDDD; + return (uintptr_t)o->ob_type == 0xDDDDDDDDDDDDDDDD; # else - return shared == (Py_ssize_t)0xDDDDDDDD; + return (uintptr_t)o->ob_type == 0xDDDDDDDD; # endif } # endif @@ -335,8 +335,8 @@ _Py_DecRefSharedDebug(PyObject *o, const char *filename, int lineno) } #ifdef Py_REF_DEBUG - if ((_Py_REF_IS_MERGED(new_shared) && new_shared < 0) || - is_shared_refcnt_dead(shared)) + if ((new_shared < 0 && _Py_REF_IS_MERGED(new_shared)) || + (should_queue && is_dead(o))) { _Py_NegativeRefcount(filename, lineno, o); } diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index 9bf4eeb9c822a50..43427d4449eb1ad 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -2459,6 +2459,24 @@ write_size_t(void *p, size_t n) } } +static void +fill_mem_debug(debug_alloc_api_t *api, void *data, int c, size_t nbytes) +{ +#ifdef Py_GIL_DISABLED + if (api->api_id == 'o') { + // Don't overwrite the first few bytes of a PyObject allocation in the + // free-threaded build + _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET(); + size_t debug_offset = tstate->mimalloc.current_object_heap->debug_offset; + if (debug_offset < nbytes) { + memset((char *)data + debug_offset, c, nbytes - debug_offset); + } + return; + } +#endif + memset(data, c, nbytes); +} + /* Let S = sizeof(size_t). The debug malloc asks for 4 * S extra bytes and fills them with useful stuff, here calling the underlying malloc's result p: @@ -2535,7 +2553,7 @@ _PyMem_DebugRawAlloc(int use_calloc, void *ctx, size_t nbytes) memset(p + SST + 1, PYMEM_FORBIDDENBYTE, SST-1); if (nbytes > 0 && !use_calloc) { - memset(data, PYMEM_CLEANBYTE, nbytes); + fill_mem_debug(api, data, PYMEM_CLEANBYTE, nbytes); } /* at tail, write pad (SST bytes) and serialno (SST bytes) */ @@ -2583,8 +2601,9 @@ _PyMem_DebugRawFree(void *ctx, void *p) _PyMem_DebugCheckAddress(__func__, api->api_id, p); nbytes = read_size_t(q); - nbytes += PYMEM_DEBUG_EXTRA_BYTES; - memset(q, PYMEM_DEADBYTE, nbytes); + nbytes += PYMEM_DEBUG_EXTRA_BYTES - 2*SST; + memset(q, PYMEM_DEADBYTE, 2*SST); + fill_mem_debug(api, p, PYMEM_DEADBYTE, nbytes); api->alloc.free(api->alloc.ctx, q); } @@ -2604,7 +2623,6 @@ _PyMem_DebugRawRealloc(void *ctx, void *p, size_t nbytes) size_t total; /* 2 * SST + nbytes + 2 * SST */ size_t original_nbytes; #define ERASED_SIZE 64 - uint8_t save[2*ERASED_SIZE]; /* A copy of erased bytes. */ _PyMem_DebugCheckAddress(__func__, api->api_id, p); @@ -2621,9 +2639,11 @@ _PyMem_DebugRawRealloc(void *ctx, void *p, size_t nbytes) #ifdef PYMEM_DEBUG_SERIALNO size_t block_serialno = read_size_t(tail + SST); #endif +#ifndef Py_GIL_DISABLED /* Mark the header, the trailer, ERASED_SIZE bytes at the begin and ERASED_SIZE bytes at the end as dead and save the copy of erased bytes. */ + uint8_t save[2*ERASED_SIZE]; /* A copy of erased bytes. */ if (original_nbytes <= sizeof(save)) { memcpy(save, data, original_nbytes); memset(data - 2 * SST, PYMEM_DEADBYTE, @@ -2636,6 +2656,7 @@ _PyMem_DebugRawRealloc(void *ctx, void *p, size_t nbytes) memset(tail - ERASED_SIZE, PYMEM_DEADBYTE, ERASED_SIZE + PYMEM_DEBUG_EXTRA_BYTES - 2 * SST); } +#endif /* Resize and add decorations. */ r = (uint8_t *)api->alloc.realloc(api->alloc.ctx, head, total); @@ -2663,6 +2684,7 @@ _PyMem_DebugRawRealloc(void *ctx, void *p, size_t nbytes) write_size_t(tail + SST, block_serialno); #endif +#ifndef Py_GIL_DISABLED /* Restore saved bytes. */ if (original_nbytes <= sizeof(save)) { memcpy(data, save, Py_MIN(nbytes, original_nbytes)); @@ -2675,6 +2697,7 @@ _PyMem_DebugRawRealloc(void *ctx, void *p, size_t nbytes) Py_MIN(nbytes - i, ERASED_SIZE)); } } +#endif if (r == NULL) { return NULL; From 113687a8381d6dde179aeede607bcbca5c09d182 Mon Sep 17 00:00:00 2001 From: Gabriele Catania <gabriele.ctn@gmail.com> Date: Wed, 21 Feb 2024 21:09:06 +0000 Subject: [PATCH 404/507] gh-93205: When rotating logs with no namer specified, match whole extension (GH-93224) --- Lib/logging/handlers.py | 43 +++++++++++-------- Lib/test/test_logging.py | 37 ++++++++++++++++ ...2-05-25-17-49-04.gh-issue-93205.DjhFVR.rst | 1 + 3 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-05-25-17-49-04.gh-issue-93205.DjhFVR.rst diff --git a/Lib/logging/handlers.py b/Lib/logging/handlers.py index e7f1322e4ba3d9d..895f11c3392253e 100644 --- a/Lib/logging/handlers.py +++ b/Lib/logging/handlers.py @@ -369,16 +369,20 @@ def getFilesToDelete(self): dirName, baseName = os.path.split(self.baseFilename) fileNames = os.listdir(dirName) result = [] - # See bpo-44753: Don't use the extension when computing the prefix. - n, e = os.path.splitext(baseName) - prefix = n + '.' - plen = len(prefix) - for fileName in fileNames: - if self.namer is None: - # Our files will always start with baseName - if not fileName.startswith(baseName): - continue - else: + if self.namer is None: + prefix = baseName + '.' + plen = len(prefix) + for fileName in fileNames: + if fileName[:plen] == prefix: + suffix = fileName[plen:] + if self.extMatch.match(suffix): + result.append(os.path.join(dirName, fileName)) + else: + # See bpo-44753: Don't use the extension when computing the prefix. + n, e = os.path.splitext(baseName) + prefix = n + '.' + plen = len(prefix) + for fileName in fileNames: # Our files could be just about anything after custom naming, but # likely candidates are of the form # foo.log.DATETIME_SUFFIX or foo.DATETIME_SUFFIX.log @@ -386,15 +390,16 @@ def getFilesToDelete(self): len(fileName) > (plen + 1) and not fileName[plen+1].isdigit()): continue - if fileName[:plen] == prefix: - suffix = fileName[plen:] - # See bpo-45628: The date/time suffix could be anywhere in the - # filename - parts = suffix.split('.') - for part in parts: - if self.extMatch.match(part): - result.append(os.path.join(dirName, fileName)) - break + if fileName[:plen] == prefix: + suffix = fileName[plen:] + # See bpo-45628: The date/time suffix could be anywhere in the + # filename + + parts = suffix.split('.') + for part in parts: + if self.extMatch.match(part): + result.append(os.path.join(dirName, fileName)) + break if len(result) < self.backupCount: result = [] else: diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index cf09bad4c9187bc..d87142698efa8b3 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -6193,6 +6193,43 @@ def test_compute_files_to_delete(self): self.assertTrue(fn.startswith(prefix + '.') and fn[len(prefix) + 2].isdigit()) + def test_compute_files_to_delete_same_filename_different_extensions(self): + # See GH-93205 for background + wd = pathlib.Path(tempfile.mkdtemp(prefix='test_logging_')) + self.addCleanup(shutil.rmtree, wd) + times = [] + dt = datetime.datetime.now() + n_files = 10 + for _ in range(n_files): + times.append(dt.strftime('%Y-%m-%d_%H-%M-%S')) + dt += datetime.timedelta(seconds=5) + prefixes = ('a.log', 'a.log.b') + files = [] + rotators = [] + for i, prefix in enumerate(prefixes): + backupCount = i+1 + rotator = logging.handlers.TimedRotatingFileHandler(wd / prefix, when='s', + interval=5, + backupCount=backupCount, + delay=True) + rotators.append(rotator) + for t in times: + files.append('%s.%s' % (prefix, t)) + # Create empty files + for f in files: + (wd / f).touch() + # Now the checks that only the correct files are offered up for deletion + for i, prefix in enumerate(prefixes): + backupCount = i+1 + rotator = rotators[i] + candidates = rotator.getFilesToDelete() + self.assertEqual(len(candidates), n_files - backupCount) + matcher = re.compile(r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}(\.\w+)?$") + for c in candidates: + d, fn = os.path.split(c) + self.assertTrue(fn.startswith(prefix)) + suffix = fn[(len(prefix)+1):] + self.assertRegex(suffix, matcher) def secs(**kw): return datetime.timedelta(**kw) // datetime.timedelta(seconds=1) diff --git a/Misc/NEWS.d/next/Library/2022-05-25-17-49-04.gh-issue-93205.DjhFVR.rst b/Misc/NEWS.d/next/Library/2022-05-25-17-49-04.gh-issue-93205.DjhFVR.rst new file mode 100644 index 000000000000000..4a280b93d933478 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-05-25-17-49-04.gh-issue-93205.DjhFVR.rst @@ -0,0 +1 @@ +Fixed a bug in :class:`logging.handlers.TimedRotatingFileHandler` where multiple rotating handler instances pointing to files with the same name but different extensions would conflict and not delete the correct files. From 7f5e3f04f838686d65f1053a5e47f5d3faf0b228 Mon Sep 17 00:00:00 2001 From: Malcolm Smith <smith@chaquo.com> Date: Wed, 21 Feb 2024 23:18:57 +0000 Subject: [PATCH 405/507] gh-111225: Link extension modules against libpython on Android (#115780) Part of the work on PEP 738: Adding Android as a supported platform. * Rename the LIBPYTHON variable to MODULE_LDFLAGS, to more accurately reflect its purpose. * Edit makesetup to use MODULE_LDFLAGS when linking extension modules. * Edit the Makefile so that extension modules depend on libpython on Android and Cygwin. * Restore `-fPIC` on Android. It was removed several years ago with a note that the toolchain used it automatically, but this is no longer the case. Omitting it causes all linker commands to fail with an error like `relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol '_Py_FalseStruct'; recompile with -fPIC`. --- Makefile.pre.in | 4 ++-- .../2024-02-21-18-22-49.gh-issue-111225.Z8C3av.rst | 1 + Modules/makesetup | 14 +------------- configure | 13 ++++++++----- configure.ac | 12 +++++++----- 5 files changed, 19 insertions(+), 25 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2024-02-21-18-22-49.gh-issue-111225.Z8C3av.rst diff --git a/Makefile.pre.in b/Makefile.pre.in index 8130921b633e530..11d22d9a419ba78 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -41,7 +41,7 @@ AR= @AR@ READELF= @READELF@ SOABI= @SOABI@ LDVERSION= @LDVERSION@ -LIBPYTHON= @LIBPYTHON@ +MODULE_LDFLAGS=@MODULE_LDFLAGS@ GITVERSION= @GITVERSION@ GITTAG= @GITTAG@ GITBRANCH= @GITBRANCH@ @@ -2917,7 +2917,7 @@ Python/thread.o: @THREADHEADERS@ $(srcdir)/Python/condvar.h # force rebuild when header file or module build flavor (static/shared) is changed MODULE_DEPS_STATIC=Modules/config.c -MODULE_DEPS_SHARED=$(MODULE_DEPS_STATIC) $(EXPORTSYMS) +MODULE_DEPS_SHARED=@MODULE_DEPS_SHARED@ MODULE__CURSES_DEPS=$(srcdir)/Include/py_curses.h MODULE__CURSES_PANEL_DEPS=$(srcdir)/Include/py_curses.h diff --git a/Misc/NEWS.d/next/Build/2024-02-21-18-22-49.gh-issue-111225.Z8C3av.rst b/Misc/NEWS.d/next/Build/2024-02-21-18-22-49.gh-issue-111225.Z8C3av.rst new file mode 100644 index 000000000000000..8cdeba46ba23136 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2024-02-21-18-22-49.gh-issue-111225.Z8C3av.rst @@ -0,0 +1 @@ +Link extension modules against libpython on Android. diff --git a/Modules/makesetup b/Modules/makesetup index f000c9cd67310e3..d41b6640bb5186f 100755 --- a/Modules/makesetup +++ b/Modules/makesetup @@ -87,18 +87,6 @@ esac NL='\ ' -# Setup to link with extra libraries when making shared extensions. -# Currently, only Cygwin needs this baggage. -case `uname -s` in -CYGWIN*) if test $libdir = . - then - ExtraLibDir=. - else - ExtraLibDir='$(LIBPL)' - fi - ExtraLibs="-L$ExtraLibDir -lpython\$(LDVERSION)";; -esac - # Main loop for i in ${*-Setup} do @@ -286,7 +274,7 @@ sed -e 's/[ ]*#.*//' -e '/^[ ]*$/d' | ;; esac rule="$file: $objs" - rule="$rule; \$(BLDSHARED) $objs $libs $ExtraLibs -o $file" + rule="$rule; \$(BLDSHARED) $objs $libs \$(MODULE_LDFLAGS) -o $file" echo "$rule" >>$rulesf done done diff --git a/configure b/configure index ba2d49df7c65fee..a08fc1ca12e895d 100755 --- a/configure +++ b/configure @@ -834,7 +834,8 @@ LIBPL PY_ENABLE_SHARED PLATLIBDIR BINLIBDEST -LIBPYTHON +MODULE_LDFLAGS +MODULE_DEPS_SHARED EXT_SUFFIX ALT_SOABI SOABI @@ -7330,6 +7331,7 @@ printf "%s\n" "#define Py_ENABLE_SHARED 1" >>confdefs.h case $ac_sys_system in CYGWIN*) LDLIBRARY='libpython$(LDVERSION).dll.a' + BLDLIBRARY='-L. -lpython$(LDVERSION)' DLLLIBRARY='libpython$(LDVERSION).dll' ;; SunOS*) @@ -12789,7 +12791,6 @@ then then CCSHARED="-fPIC"; else CCSHARED="+z"; fi;; - Linux-android*) ;; Linux*|GNU*) CCSHARED="-fPIC";; Emscripten*|WASI*) if test "x$enable_wasm_dynamic_linking" = xyes @@ -23959,10 +23960,12 @@ printf "%s\n" "$LDVERSION" >&6; } # On Android and Cygwin the shared libraries must be linked with libpython. + +MODULE_DEPS_SHARED='$(MODULE_DEPS_STATIC) $(EXPORTSYMS)' +MODULE_LDFLAGS='' if test "$PY_ENABLE_SHARED" = "1" && ( test -n "$ANDROID_API_LEVEL" || test "$MACHDEP" = "cygwin"); then - LIBPYTHON="-lpython${VERSION}${ABIFLAGS}" -else - LIBPYTHON='' + MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(LDLIBRARY)" + MODULE_LDFLAGS="\$(BLDLIBRARY)" fi diff --git a/configure.ac b/configure.ac index b39af7422c4c7ca..ac26486144515be 100644 --- a/configure.ac +++ b/configure.ac @@ -1360,6 +1360,7 @@ if test $enable_shared = "yes"; then case $ac_sys_system in CYGWIN*) LDLIBRARY='libpython$(LDVERSION).dll.a' + BLDLIBRARY='-L. -lpython$(LDVERSION)' DLLLIBRARY='libpython$(LDVERSION).dll' ;; SunOS*) @@ -3333,7 +3334,6 @@ then then CCSHARED="-fPIC"; else CCSHARED="+z"; fi;; - Linux-android*) ;; Linux*|GNU*) CCSHARED="-fPIC";; Emscripten*|WASI*) AS_VAR_IF([enable_wasm_dynamic_linking], [yes], [ @@ -5888,11 +5888,13 @@ LDVERSION='$(VERSION)$(ABIFLAGS)' AC_MSG_RESULT([$LDVERSION]) # On Android and Cygwin the shared libraries must be linked with libpython. -AC_SUBST([LIBPYTHON]) +AC_SUBST([MODULE_DEPS_SHARED]) +AC_SUBST([MODULE_LDFLAGS]) +MODULE_DEPS_SHARED='$(MODULE_DEPS_STATIC) $(EXPORTSYMS)' +MODULE_LDFLAGS='' if test "$PY_ENABLE_SHARED" = "1" && ( test -n "$ANDROID_API_LEVEL" || test "$MACHDEP" = "cygwin"); then - LIBPYTHON="-lpython${VERSION}${ABIFLAGS}" -else - LIBPYTHON='' + MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(LDLIBRARY)" + MODULE_LDFLAGS="\$(BLDLIBRARY)" fi From fac99b8b0df209ca6546545193b1873672d536ca Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" <greg@krypto.org> Date: Wed, 21 Feb 2024 19:27:16 -0800 Subject: [PATCH 406/507] gh-111140: Improve PyLong_AsNativeBytes API doc example & improve the test (#115380) This expands the examples to cover both realistic use cases for the API. I noticed thing in the test that could be done better so I added those as well: We need to guarantee that all bytes of the result are overwritten and that too many are not written. Tests now pre-fills the result with data in order to ensure that. Co-authored-by: Steve Dower <steve.dower@microsoft.com> --- Doc/c-api/long.rst | 74 +++++++++++++++++++++++++-------- Lib/test/test_capi/test_long.py | 30 ++++++++++--- 2 files changed, 82 insertions(+), 22 deletions(-) diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index f24282e76a33d15..582f5c7bf05471c 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -358,46 +358,86 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. Copy the Python integer value to a native *buffer* of size *n_bytes*:: - int value; - Py_ssize_t bytes = PyLong_AsNativeBytes(v, &value, sizeof(value), -1); + int32_t value; + Py_ssize_t bytes = PyLong_AsNativeBits(pylong, &value, sizeof(value), -1); if (bytes < 0) { - // Error occurred + // A Python exception was set with the reason. return NULL; } else if (bytes <= (Py_ssize_t)sizeof(value)) { // Success! } else { - // Overflow occurred, but 'value' contains truncated value + // Overflow occurred, but 'value' contains the truncated + // lowest bits of pylong. } + The above example may look *similar* to + :c:func:`PyLong_As* <PyLong_AsSize_t>` + but instead fills in a specific caller defined type and never raises an + error about of the :class:`int` *pylong*'s value regardless of *n_bytes* + or the returned byte count. + + To get at the entire potentially big Python value, this can be used to + reserve enough space and copy it:: + + // Ask how much space we need. + Py_ssize_t expected = PyLong_AsNativeBits(pylong, NULL, 0, -1); + if (expected < 0) { + // A Python exception was set with the reason. + return NULL; + } + assert(expected != 0); // Impossible per the API definition. + uint8_t *bignum = malloc(expected); + if (!bignum) { + PyErr_SetString(PyExc_MemoryError, "bignum malloc failed."); + return NULL; + } + // Safely get the entire value. + Py_ssize_t bytes = PyLong_AsNativeBits(pylong, bignum, expected, -1); + if (bytes < 0) { // Exception set. + free(bignum); + return NULL; + } + else if (bytes > expected) { // Be safe, should not be possible. + PyErr_SetString(PyExc_RuntimeError, + "Unexpected bignum truncation after a size check."); + free(bignum); + return NULL; + } + // The expected success given the above pre-check. + // ... use bignum ... + free(bignum); + *endianness* may be passed ``-1`` for the native endian that CPython was compiled with, or ``0`` for big endian and ``1`` for little. - Return ``-1`` with an exception raised if *pylong* cannot be interpreted as + Returns ``-1`` with an exception raised if *pylong* cannot be interpreted as an integer. Otherwise, return the size of the buffer required to store the value. If this is equal to or less than *n_bytes*, the entire value was - copied. + copied. ``0`` will never be returned. - Unless an exception is raised, all *n_bytes* of the buffer will be written - with as much of the value as can fit. This allows the caller to ignore all - non-negative results if the intent is to match the typical behavior of a - C-style downcast. No exception is set for this case. + Unless an exception is raised, all *n_bytes* of the buffer will always be + written. In the case of truncation, as many of the lowest bits of the value + as could fit are written. This allows the caller to ignore all non-negative + results if the intent is to match the typical behavior of a C-style + downcast. No exception is set on truncation. - Values are always copied as two's-complement, and sufficient buffer will be + Values are always copied as two's-complement and sufficient buffer will be requested to include a sign bit. For example, this may cause an value that fits into 8 bytes when treated as unsigned to request 9 bytes, even though all eight bytes were copied into the buffer. What has been omitted is the - zero sign bit, which is redundant when the intention is to treat the value as - unsigned. + zero sign bit -- redundant if the caller's intention is to treat the value + as unsigned. - Passing zero to *n_bytes* will return the requested buffer size. + Passing zero to *n_bytes* will return the size of a buffer that would + be large enough to hold the value. This may be larger than technically + necessary, but not unreasonably so. .. note:: - When the value does not fit in the provided buffer, the requested size - returned from the function may be larger than necessary. Passing 0 to this - function is not an accurate way to determine the bit length of a value. + Passing *n_bytes=0* to this function is not an accurate way to determine + the bit length of a value. .. versionadded:: 3.13 diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index fc82cbfa66ea7aa..9f5ee507a8eb85e 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -438,7 +438,12 @@ def test_long_asnativebytes(self): if support.verbose: print(f"SIZEOF_SIZE={SZ}\n{MAX_SSIZE=:016X}\n{MAX_USIZE=:016X}") - # These tests check that the requested buffer size is correct + # These tests check that the requested buffer size is correct. + # This matches our current implementation: We only specify that the + # return value is a size *sufficient* to hold the result when queried + # using n_bytes=0. If our implementation changes, feel free to update + # the expectations here -- or loosen them to be range checks. + # (i.e. 0 *could* be stored in 1 byte and 512 in 2) for v, expect in [ (0, SZ), (512, SZ), @@ -453,12 +458,25 @@ def test_long_asnativebytes(self): (-(2**256-1), 33), ]: with self.subTest(f"sizeof-{v:X}"): - buffer = bytearray(1) + buffer = bytearray(b"\x5a") self.assertEqual(expect, asnativebytes(v, buffer, 0, -1), - "PyLong_AsNativeBytes(v, NULL, 0, -1)") + "PyLong_AsNativeBytes(v, <unknown>, 0, -1)") + self.assertEqual(buffer, b"\x5a", + "buffer overwritten when it should not have been") # Also check via the __index__ path self.assertEqual(expect, asnativebytes(Index(v), buffer, 0, -1), - "PyLong_AsNativeBytes(Index(v), NULL, 0, -1)") + "PyLong_AsNativeBytes(Index(v), <unknown>, 0, -1)") + self.assertEqual(buffer, b"\x5a", + "buffer overwritten when it should not have been") + + # Test that we populate n=2 bytes but do not overwrite more. + buffer = bytearray(b"\x99"*3) + self.assertEqual(2, asnativebytes(4, buffer, 2, 0), # BE + "PyLong_AsNativeBytes(v, <3 byte buffer>, 2, 0) // BE") + self.assertEqual(buffer, b"\x00\x04\x99") + self.assertEqual(2, asnativebytes(4, buffer, 2, 1), # LE + "PyLong_AsNativeBytes(v, <3 byte buffer>, 2, 1) // LE") + self.assertEqual(buffer, b"\x04\x00\x99") # We request as many bytes as `expect_be` contains, and always check # the result (both big and little endian). We check the return value @@ -510,7 +528,9 @@ def test_long_asnativebytes(self): ]: with self.subTest(f"{v:X}-{len(expect_be)}bytes"): n = len(expect_be) - buffer = bytearray(n) + # Fill the buffer with dummy data to ensure all bytes + # are overwritten. + buffer = bytearray(b"\xa5"*n) expect_le = expect_be[::-1] self.assertEqual(expect_n, asnativebytes(v, buffer, n, 0), From 7bc79371a62e8f45542cf5679ed35d0d29e94226 Mon Sep 17 00:00:00 2001 From: partev <petrosyan@gmail.com> Date: Thu, 22 Feb 2024 01:45:26 -0500 Subject: [PATCH 407/507] gh-115795: Doc: fix obsolete URL (#115749) --- Doc/using/windows.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/using/windows.rst b/Doc/using/windows.rst index 2a0e7b4b06f5865..bfcf1c6882f29d4 100644 --- a/Doc/using/windows.rst +++ b/Doc/using/windows.rst @@ -15,7 +15,7 @@ know about when using Python on Microsoft Windows. Unlike most Unix systems and services, Windows does not include a system supported installation of Python. To make Python available, the CPython team has compiled Windows installers (MSI packages) with every `release -<https://www.python.org/download/releases/>`_ for many years. These installers +<https://www.python.org/downloads/>`_ for many years. These installers are primarily intended to add a per-user installation of Python, with the core interpreter and library being used by a single user. The installer is also able to install for all users of a single machine, and a separate ZIP file is From baae73d7307214ee97abbe80aaa8c1c773a6f682 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Thu, 22 Feb 2024 10:04:15 +0100 Subject: [PATCH 408/507] gh-115765: Don't use deprecated AC_CHECK_TYPE macro in configure.ac (#115792) Instead use AC_CHECK_TYPES. --- configure | 8 ++++++++ configure.ac | 25 ++++++++----------------- pyconfig.h.in | 12 +++++++++--- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/configure b/configure index a08fc1ca12e895d..b910d05ca699c04 100755 --- a/configure +++ b/configure @@ -11496,12 +11496,16 @@ then : printf "%s\n" "#define HAVE_SSIZE_T 1" >>confdefs.h + fi ac_fn_c_check_type "$LINENO" "__uint128_t" "ac_cv_type___uint128_t" "$ac_includes_default" if test "x$ac_cv_type___uint128_t" = xyes then : +printf "%s\n" "#define HAVE___UINT128_T 1" >>confdefs.h + + printf "%s\n" "#define HAVE_GCC_UINT128_T 1" >>confdefs.h fi @@ -24961,6 +24965,7 @@ then : printf "%s\n" "#define HAVE_RL_COMPDISP_FUNC_T 1" >>confdefs.h + fi @@ -26783,6 +26788,9 @@ ac_fn_c_check_type "$LINENO" "socklen_t" "ac_cv_type_socklen_t" " if test "x$ac_cv_type_socklen_t" = xyes then : +printf "%s\n" "#define HAVE_SOCKLEN_T 1" >>confdefs.h + + else $as_nop printf "%s\n" "#define socklen_t int" >>confdefs.h diff --git a/configure.ac b/configure.ac index ac26486144515be..5b655e57ef315b5 100644 --- a/configure.ac +++ b/configure.ac @@ -2910,12 +2910,10 @@ AC_DEFINE_UNQUOTED([RETSIGTYPE],[void],[assume C89 semantics that RETSIGTYPE is AC_TYPE_SIZE_T AC_TYPE_UID_T -AC_CHECK_TYPE([ssize_t], - AC_DEFINE([HAVE_SSIZE_T], [1], - [Define if your compiler provides ssize_t]), [], []) -AC_CHECK_TYPE([__uint128_t], - AC_DEFINE([HAVE_GCC_UINT128_T], [1], - [Define if your compiler provides __uint128_t]), [], []) +AC_CHECK_TYPES([ssize_t]) +AC_CHECK_TYPES([__uint128_t], + [AC_DEFINE([HAVE_GCC_UINT128_T], [1], + [Define if your compiler provides __uint128_t])]) # Sizes and alignments of various common basic types # ANSI C requires sizeof(char) == 1, so no need to check it @@ -6153,11 +6151,7 @@ AS_VAR_IF([with_readline], [no], [ ]) # in readline as well as newer editline (April 2023) - AC_CHECK_TYPE([rl_compdisp_func_t], - [AC_DEFINE([HAVE_RL_COMPDISP_FUNC_T], [1], - [Define if readline supports rl_compdisp_func_t])], - [], - [readline_includes]) + AC_CHECK_TYPES([rl_compdisp_func_t], [], [], [readline_includes]) m4_undefine([readline_includes]) ])dnl WITH_SAVE_ENV() @@ -6555,12 +6549,9 @@ then LIBS="$LIBS -framework CoreFoundation" fi -AC_CHECK_TYPE( - [socklen_t], [], - [AC_DEFINE( - [socklen_t], [int], - [Define to `int' if <sys/socket.h> does not define.] - )], [ +AC_CHECK_TYPES([socklen_t], [], + [AC_DEFINE([socklen_t], [int], + [Define to 'int' if <sys/socket.h> does not define.])], [ #ifdef HAVE_SYS_TYPES_H #include <sys/types.h> #endif diff --git a/pyconfig.h.in b/pyconfig.h.in index 2b4bb1a2b528662..63a3437ebb3eac1 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -1009,7 +1009,7 @@ /* Define if you can turn off readline's signal handling. */ #undef HAVE_RL_CATCH_SIGNAL -/* Define if readline supports rl_compdisp_func_t */ +/* Define to 1 if the system has the type `rl_compdisp_func_t'. */ #undef HAVE_RL_COMPDISP_FUNC_T /* Define if you have readline 2.2 */ @@ -1195,13 +1195,16 @@ /* Define if you have the 'socketpair' function. */ #undef HAVE_SOCKETPAIR +/* Define to 1 if the system has the type `socklen_t'. */ +#undef HAVE_SOCKLEN_T + /* Define to 1 if you have the <spawn.h> header file. */ #undef HAVE_SPAWN_H /* Define to 1 if you have the `splice' function. */ #undef HAVE_SPLICE -/* Define if your compiler provides ssize_t */ +/* Define to 1 if the system has the type `ssize_t'. */ #undef HAVE_SSIZE_T /* Define to 1 if you have the `statvfs' function. */ @@ -1568,6 +1571,9 @@ /* Define to 1 if you have the `_getpty' function. */ #undef HAVE__GETPTY +/* Define to 1 if the system has the type `__uint128_t'. */ +#undef HAVE___UINT128_T + /* Define to 1 if `major', `minor', and `makedev' are declared in <mkdev.h>. */ #undef MAJOR_IN_MKDEV @@ -1956,7 +1962,7 @@ /* Define to `unsigned int' if <sys/types.h> does not define. */ #undef size_t -/* Define to `int' if <sys/socket.h> does not define. */ +/* Define to 'int' if <sys/socket.h> does not define. */ #undef socklen_t /* Define to `int' if <sys/types.h> doesn't define. */ From 8aa372edcd857d2fbec8da0cb993de15b4179308 Mon Sep 17 00:00:00 2001 From: Petr Viktorin <encukou@gmail.com> Date: Thu, 22 Feb 2024 12:39:45 +0100 Subject: [PATCH 409/507] gh-115714: Don't use CLOCK_PROCESS_CPUTIME_ID and times() on WASI (GH-115757) * gh-115714: Don't use CLOCK_PROCESS_CPUTIME_ID and times() on WASI * Add blurb --- ...024-02-22-12-10-18.gh-issue-115714.P2JsU1.rst | 4 ++++ Modules/timemodule.c | 16 +++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-22-12-10-18.gh-issue-115714.P2JsU1.rst diff --git a/Misc/NEWS.d/next/Library/2024-02-22-12-10-18.gh-issue-115714.P2JsU1.rst b/Misc/NEWS.d/next/Library/2024-02-22-12-10-18.gh-issue-115714.P2JsU1.rst new file mode 100644 index 000000000000000..fb626344c87fdb3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-22-12-10-18.gh-issue-115714.P2JsU1.rst @@ -0,0 +1,4 @@ +On WASI, the :mod:`time` module no longer get process time using ``times()`` +or ``CLOCK_PROCESS_CPUTIME_ID``, system API is that is unreliable and is +likely to be removed from WASI. The affected clock functions fall back to +calling ``clock()``. diff --git a/Modules/timemodule.c b/Modules/timemodule.c index fc493bde599ce8d..ed41ffd3662aa8c 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -76,7 +76,8 @@ static int pysleep(PyTime_t timeout); typedef struct { PyTypeObject *struct_time_type; -#ifdef HAVE_TIMES +// gh-115714: Don't use times() on WASI. +#if defined(HAVE_TIMES) && !defined(__wasi__) // times() clock frequency in hertz _PyTimeFraction times_base; #endif @@ -1210,7 +1211,8 @@ PyDoc_STRVAR(perf_counter_ns_doc, Performance counter for benchmarking as nanoseconds."); -#ifdef HAVE_TIMES +// gh-115714: Don't use times() on WASI. +#if defined(HAVE_TIMES) && !defined(__wasi__) static int process_time_times(time_module_state *state, PyTime_t *tp, _Py_clock_info_t *info) @@ -1278,8 +1280,10 @@ py_process_time(time_module_state *state, PyTime_t *tp, #else /* clock_gettime */ +// gh-115714: Don't use CLOCK_PROCESS_CPUTIME_ID on WASI. #if defined(HAVE_CLOCK_GETTIME) \ - && (defined(CLOCK_PROCESS_CPUTIME_ID) || defined(CLOCK_PROF)) + && (defined(CLOCK_PROCESS_CPUTIME_ID) || defined(CLOCK_PROF)) \ + && !defined(__wasi__) struct timespec ts; if (HAVE_CLOCK_GETTIME_RUNTIME) { @@ -1341,7 +1345,8 @@ py_process_time(time_module_state *state, PyTime_t *tp, #endif /* times() */ -#ifdef HAVE_TIMES +// gh-115714: Don't use times() on WASI. +#if defined(HAVE_TIMES) && !defined(__wasi__) int res = process_time_times(state, tp, info); if (res < 0) { return -1; @@ -2068,7 +2073,8 @@ time_exec(PyObject *module) } #endif -#ifdef HAVE_TIMES +// gh-115714: Don't use times() on WASI. +#if defined(HAVE_TIMES) && !defined(__wasi__) long ticks_per_second; if (_Py_GetTicksPerSecond(&ticks_per_second) < 0) { PyErr_SetString(PyExc_RuntimeError, From 96c1737591b45bc1c528df1103eb0e500751fefe Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Thu, 22 Feb 2024 12:36:44 +0000 Subject: [PATCH 410/507] gh-115796: fix exception table construction in _testinternalcapi.assemble_code_object (#115797) --- Lib/test/test_compiler_assemble.py | 41 ++++++++++++++++++- ...-02-22-00-17-06.gh-issue-115796.d4hpKy.rst | 2 + Python/flowgraph.c | 17 +++++--- 3 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Tests/2024-02-22-00-17-06.gh-issue-115796.d4hpKy.rst diff --git a/Lib/test/test_compiler_assemble.py b/Lib/test/test_compiler_assemble.py index 5696433e529d0a2..ab9f04dd63af202 100644 --- a/Lib/test/test_compiler_assemble.py +++ b/Lib/test/test_compiler_assemble.py @@ -1,3 +1,6 @@ +import dis +import io +import textwrap import types from test.support.bytecode_helper import AssemblerTestCase @@ -22,11 +25,13 @@ def complete_metadata(self, metadata, filename="myfile.py"): metadata.setdefault('filename', filename) return metadata - def assemble_test(self, insts, metadata, expected): + def insts_to_code_object(self, insts, metadata): metadata = self.complete_metadata(metadata) insts = self.complete_insts_info(insts) + return self.get_code_object(metadata['filename'], insts, metadata) - co = self.get_code_object(metadata['filename'], insts, metadata) + def assemble_test(self, insts, metadata, expected): + co = self.insts_to_code_object(insts, metadata) self.assertIsInstance(co, types.CodeType) expected_metadata = {} @@ -108,3 +113,35 @@ def inner(): expected = {(0,): 0, (1,): 1, (2,): 0, (120,): 0, (121,): 1} self.assemble_test(instructions, metadata, expected) + + + def test_exception_table(self): + metadata = { + 'filename' : 'exc.py', + 'name' : 'exc', + 'consts' : {2 : 0}, + } + + # code for "try: pass\n except: pass" + insts = [ + ('RESUME', 0), + ('SETUP_FINALLY', 3), + ('RETURN_CONST', 0), + ('SETUP_CLEANUP', 8), + ('PUSH_EXC_INFO', 0), + ('POP_TOP', 0), + ('POP_EXCEPT', 0), + ('RETURN_CONST', 0), + ('COPY', 3), + ('POP_EXCEPT', 0), + ('RERAISE', 1), + ] + co = self.insts_to_code_object(insts, metadata) + output = io.StringIO() + dis.dis(co, file=output) + exc_table = textwrap.dedent(""" + ExceptionTable: + L1 to L2 -> L2 [0] + L2 to L3 -> L3 [1] lasti + """) + self.assertTrue(output.getvalue().endswith(exc_table)) diff --git a/Misc/NEWS.d/next/Tests/2024-02-22-00-17-06.gh-issue-115796.d4hpKy.rst b/Misc/NEWS.d/next/Tests/2024-02-22-00-17-06.gh-issue-115796.d4hpKy.rst new file mode 100644 index 000000000000000..a40be74f73908e5 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-02-22-00-17-06.gh-issue-115796.d4hpKy.rst @@ -0,0 +1,2 @@ +Make '_testinternalcapi.assemble_code_object' construct the exception table +for the code object. diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 4d9ba9eceb86373..2f47e47bf9d29d0 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -665,12 +665,6 @@ translate_jump_labels_to_targets(basicblock *entryblock) return SUCCESS; } -int -_PyCfg_JumpLabelsToTargets(cfg_builder *g) -{ - return translate_jump_labels_to_targets(g->g_entryblock); -} - static int mark_except_handlers(basicblock *entryblock) { #ifndef NDEBUG @@ -2790,3 +2784,14 @@ _PyCfg_OptimizedCfgToInstructionSequence(cfg_builder *g, return SUCCESS; } + +/* This is used by _PyCompile_Assemble to fill in the jump and exception + * targets in a synthetic CFG (which is not the ouptut of the builtin compiler). + */ +int +_PyCfg_JumpLabelsToTargets(cfg_builder *g) +{ + RETURN_IF_ERROR(translate_jump_labels_to_targets(g->g_entryblock)); + RETURN_IF_ERROR(label_exception_targets(g->g_entryblock)); + return SUCCESS; +} From c6a47de70966e6899871245e800a0a9271e25b12 Mon Sep 17 00:00:00 2001 From: Seth Michael Larson <seth@python.org> Date: Thu, 22 Feb 2024 07:42:26 -0600 Subject: [PATCH 411/507] gh-115663: Remove 'regen-sbom' from the 'regen-all' target (#115790) --- .github/workflows/build.yml | 2 +- Makefile.pre.in | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 70db2a6250e8dab..20d1fad40ecafee 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -162,7 +162,7 @@ jobs: - name: Build CPython run: | make -j4 regen-all - make regen-stdlib-module-names + make regen-stdlib-module-names regen-sbom - name: Check for changes run: | git add -u diff --git a/Makefile.pre.in b/Makefile.pre.in index 11d22d9a419ba78..4c1a18602b2d0bb 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1627,10 +1627,10 @@ regen-unicodedata: regen-all: regen-cases regen-typeslots \ regen-token regen-ast regen-keyword regen-sre regen-frozen \ regen-pegen-metaparser regen-pegen regen-test-frozenmain \ - regen-test-levenshtein regen-global-objects regen-sbom regen-jit + regen-test-levenshtein regen-global-objects regen-jit @echo @echo "Note: make regen-stdlib-module-names, make regen-limited-abi, " - @echo "make regen-configure and make regen-unicodedata should be run manually" + @echo "make regen-configure, make regen-sbom, and make regen-unicodedata should be run manually" ############################################################################ # Special rules for object files From b348313e7a48811acacc293ac4b2c8b20b4c631b Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Thu, 22 Feb 2024 14:48:25 +0000 Subject: [PATCH 412/507] GH-115651: Convert `LOAD_MODULE_ATTR` into `LOAD_INLINE_CONST` when the module is itself a constant. (GH-115711) --- Python/bytecodes.c | 4 +- Python/executor_cases.c.h | 4 +- Python/generated_cases.c.h | 4 +- Python/optimizer_analysis.c | 68 +++++++----- .../tier2_redundancy_eliminator_bytecodes.c | 100 ++++++++++++------ Python/tier2_redundancy_eliminator_cases.c.h | 97 +++++++++++------ 6 files changed, 182 insertions(+), 95 deletions(-) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 5835b80582b3bca..7e2c9c4d6a6db46 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1932,11 +1932,11 @@ dummy_func( _LOAD_ATTR_INSTANCE_VALUE + unused/5; // Skip over rest of cache - op(_CHECK_ATTR_MODULE, (type_version/2, owner -- owner)) { + op(_CHECK_ATTR_MODULE, (dict_version/2, owner -- owner)) { DEOPT_IF(!PyModule_CheckExact(owner)); PyDictObject *dict = (PyDictObject *)((PyModuleObject *)owner)->md_dict; assert(dict != NULL); - DEOPT_IF(dict->ma_keys->dk_version != type_version); + DEOPT_IF(dict->ma_keys->dk_version != dict_version); } op(_LOAD_ATTR_MODULE, (index/1, owner -- attr, null if (oparg & 1))) { diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 974555cbba9dd6e..3054058cf44d31b 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1856,11 +1856,11 @@ case _CHECK_ATTR_MODULE: { PyObject *owner; owner = stack_pointer[-1]; - uint32_t type_version = (uint32_t)CURRENT_OPERAND(); + uint32_t dict_version = (uint32_t)CURRENT_OPERAND(); if (!PyModule_CheckExact(owner)) goto deoptimize; PyDictObject *dict = (PyDictObject *)((PyModuleObject *)owner)->md_dict; assert(dict != NULL); - if (dict->ma_keys->dk_version != type_version) goto deoptimize; + if (dict->ma_keys->dk_version != dict_version) goto deoptimize; break; } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 7f46bc8916c8d8c..87579134146c857 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -3712,11 +3712,11 @@ // _CHECK_ATTR_MODULE owner = stack_pointer[-1]; { - uint32_t type_version = read_u32(&this_instr[2].cache); + uint32_t dict_version = read_u32(&this_instr[2].cache); DEOPT_IF(!PyModule_CheckExact(owner), LOAD_ATTR); PyDictObject *dict = (PyDictObject *)((PyModuleObject *)owner)->md_dict; assert(dict != NULL); - DEOPT_IF(dict->ma_keys->dk_version != type_version, LOAD_ATTR); + DEOPT_IF(dict->ma_keys->dk_version != dict_version, LOAD_ATTR); } // _LOAD_ATTR_MODULE { diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 5d2df9aca4e77f8..68ef8254b494a40 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -271,6 +271,20 @@ sym_is_null(_Py_UOpsSymType *sym) return (sym->flags & (IS_NULL | NOT_NULL)) == IS_NULL; } +static inline bool +sym_is_const(_Py_UOpsSymType *sym) +{ + return (sym->flags & TRUE_CONST) != 0; +} + +static inline PyObject * +sym_get_const(_Py_UOpsSymType *sym) +{ + assert(sym_is_const(sym)); + assert(sym->const_val); + return sym->const_val; +} + static inline void sym_set_type(_Py_UOpsSymType *sym, PyTypeObject *tp) { @@ -336,18 +350,6 @@ sym_new_const(_Py_UOpsAbstractInterpContext *ctx, PyObject *const_val) return temp; } -static inline bool -is_const(_Py_UOpsSymType *sym) -{ - return sym->const_val != NULL; -} - -static inline PyObject * -get_const(_Py_UOpsSymType *sym) -{ - return sym->const_val; -} - static _Py_UOpsSymType* sym_new_null(_Py_UOpsAbstractInterpContext *ctx) { @@ -408,18 +410,21 @@ globals_watcher_callback(PyDict_WatchEvent event, PyObject* dict, return 0; } -static void -global_to_const(_PyUOpInstruction *inst, PyObject *obj) +static PyObject * +convert_global_to_const(_PyUOpInstruction *inst, PyObject *obj) { - assert(inst->opcode == _LOAD_GLOBAL_MODULE || inst->opcode == _LOAD_GLOBAL_BUILTINS); + assert(inst->opcode == _LOAD_GLOBAL_MODULE || inst->opcode == _LOAD_GLOBAL_BUILTINS || inst->opcode == _LOAD_ATTR_MODULE); assert(PyDict_CheckExact(obj)); PyDictObject *dict = (PyDictObject *)obj; assert(dict->ma_keys->dk_kind == DICT_KEYS_UNICODE); PyDictUnicodeEntry *entries = DK_UNICODE_ENTRIES(dict->ma_keys); assert(inst->operand <= UINT16_MAX); + if ((int)inst->operand >= dict->ma_keys->dk_nentries) { + return NULL; + } PyObject *res = entries[inst->operand].me_value; if (res == NULL) { - return; + return NULL; } if (_Py_IsImmortal(res)) { inst->opcode = (inst->oparg & 1) ? _LOAD_CONST_INLINE_BORROW_WITH_NULL : _LOAD_CONST_INLINE_BORROW; @@ -428,6 +433,7 @@ global_to_const(_PyUOpInstruction *inst, PyObject *obj) inst->opcode = (inst->oparg & 1) ? _LOAD_CONST_INLINE_WITH_NULL : _LOAD_CONST_INLINE; } inst->operand = (uint64_t)res; + return res; } static int @@ -524,12 +530,12 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, break; case _LOAD_GLOBAL_BUILTINS: if (globals_checked & builtins_checked & globals_watched & builtins_watched & 1) { - global_to_const(inst, builtins); + convert_global_to_const(inst, builtins); } break; case _LOAD_GLOBAL_MODULE: if (globals_checked & globals_watched & 1) { - global_to_const(inst, globals); + convert_global_to_const(inst, globals); } break; case _PUSH_FRAME: @@ -603,7 +609,8 @@ uop_redundancy_eliminator( PyCodeObject *co, _PyUOpInstruction *trace, int trace_len, - int curr_stacklen + int curr_stacklen, + _PyBloomFilter *dependencies ) { @@ -665,7 +672,7 @@ remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size) * could error. _CHECK_VALIDITY is needed if the previous * instruction could have escaped. */ int last_set_ip = -1; - bool may_have_escaped = false; + bool may_have_escaped = true; for (int pc = 0; pc < buffer_size; pc++) { int opcode = buffer[pc].opcode; switch (opcode) { @@ -691,6 +698,22 @@ remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size) } last_set_ip = pc; break; + case _POP_TOP: + { + _PyUOpInstruction *last = &buffer[pc-1]; + while (last->opcode == _NOP) { + last--; + } + if (last->opcode == _LOAD_CONST_INLINE || + last->opcode == _LOAD_CONST_INLINE_BORROW || + last->opcode == _LOAD_FAST || + last->opcode == _COPY + ) { + last->opcode = _NOP; + buffer[pc].opcode = NOP; + } + break; + } case _JUMP_TO_TOP: case _EXIT_TRACE: return; @@ -704,9 +727,6 @@ remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size) if (_PyUop_Flags[opcode] & HAS_ERROR_FLAG) { needs_ip = true; } - if (opcode == _PUSH_FRAME) { - needs_ip = true; - } if (needs_ip && last_set_ip >= 0) { if (buffer[last_set_ip].opcode == _CHECK_VALIDITY) { buffer[last_set_ip].opcode = _CHECK_VALIDITY_AND_SET_IP; @@ -791,7 +811,7 @@ _Py_uop_analyze_and_optimize( err = uop_redundancy_eliminator( (PyCodeObject *)frame->f_executable, buffer, - buffer_size, curr_stacklen); + buffer_size, curr_stacklen, dependencies); if (err == 0) { goto not_ready; diff --git a/Python/tier2_redundancy_eliminator_bytecodes.c b/Python/tier2_redundancy_eliminator_bytecodes.c index e9b556d16c37025..ff2b9a42272863c 100644 --- a/Python/tier2_redundancy_eliminator_bytecodes.c +++ b/Python/tier2_redundancy_eliminator_bytecodes.c @@ -1,6 +1,7 @@ #include "Python.h" #include "pycore_uops.h" #include "pycore_uop_ids.h" +#include "internal/pycore_moduleobject.h" #define op(name, ...) /* NAME is ignored */ @@ -87,11 +88,11 @@ dummy_func(void) { } op(_BINARY_OP_ADD_INT, (left, right -- res)) { - if (is_const(left) && is_const(right)) { - assert(PyLong_CheckExact(get_const(left))); - assert(PyLong_CheckExact(get_const(right))); - PyObject *temp = _PyLong_Add((PyLongObject *)get_const(left), - (PyLongObject *)get_const(right)); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyLong_CheckExact(sym_get_const(left))); + assert(PyLong_CheckExact(sym_get_const(right))); + PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(left), + (PyLongObject *)sym_get_const(right)); if (temp == NULL) { goto error; } @@ -105,11 +106,11 @@ dummy_func(void) { } op(_BINARY_OP_SUBTRACT_INT, (left, right -- res)) { - if (is_const(left) && is_const(right)) { - assert(PyLong_CheckExact(get_const(left))); - assert(PyLong_CheckExact(get_const(right))); - PyObject *temp = _PyLong_Subtract((PyLongObject *)get_const(left), - (PyLongObject *)get_const(right)); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyLong_CheckExact(sym_get_const(left))); + assert(PyLong_CheckExact(sym_get_const(right))); + PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(left), + (PyLongObject *)sym_get_const(right)); if (temp == NULL) { goto error; } @@ -123,11 +124,11 @@ dummy_func(void) { } op(_BINARY_OP_MULTIPLY_INT, (left, right -- res)) { - if (is_const(left) && is_const(right)) { - assert(PyLong_CheckExact(get_const(left))); - assert(PyLong_CheckExact(get_const(right))); - PyObject *temp = _PyLong_Multiply((PyLongObject *)get_const(left), - (PyLongObject *)get_const(right)); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyLong_CheckExact(sym_get_const(left))); + assert(PyLong_CheckExact(sym_get_const(right))); + PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(left), + (PyLongObject *)sym_get_const(right)); if (temp == NULL) { goto error; } @@ -141,12 +142,12 @@ dummy_func(void) { } op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { - if (is_const(left) && is_const(right)) { - assert(PyFloat_CheckExact(get_const(left))); - assert(PyFloat_CheckExact(get_const(right))); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyFloat_CheckExact(sym_get_const(left))); + assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(get_const(left)) + - PyFloat_AS_DOUBLE(get_const(right))); + PyFloat_AS_DOUBLE(sym_get_const(left)) + + PyFloat_AS_DOUBLE(sym_get_const(right))); if (temp == NULL) { goto error; } @@ -160,12 +161,12 @@ dummy_func(void) { } op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) { - if (is_const(left) && is_const(right)) { - assert(PyFloat_CheckExact(get_const(left))); - assert(PyFloat_CheckExact(get_const(right))); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyFloat_CheckExact(sym_get_const(left))); + assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(get_const(left)) - - PyFloat_AS_DOUBLE(get_const(right))); + PyFloat_AS_DOUBLE(sym_get_const(left)) - + PyFloat_AS_DOUBLE(sym_get_const(right))); if (temp == NULL) { goto error; } @@ -179,12 +180,12 @@ dummy_func(void) { } op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { - if (is_const(left) && is_const(right)) { - assert(PyFloat_CheckExact(get_const(left))); - assert(PyFloat_CheckExact(get_const(right))); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyFloat_CheckExact(sym_get_const(left))); + assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(get_const(left)) * - PyFloat_AS_DOUBLE(get_const(right))); + PyFloat_AS_DOUBLE(sym_get_const(left)) * + PyFloat_AS_DOUBLE(sym_get_const(right))); if (temp == NULL) { goto error; } @@ -237,10 +238,43 @@ dummy_func(void) { (void)owner; } + op(_CHECK_ATTR_MODULE, (dict_version/2, owner -- owner)) { + (void)dict_version; + if (sym_is_const(owner)) { + PyObject *cnst = sym_get_const(owner); + if (PyModule_CheckExact(cnst)) { + PyModuleObject *mod = (PyModuleObject *)cnst; + PyObject *dict = mod->md_dict; + uint64_t watched_mutations = get_mutations(dict); + if (watched_mutations < _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS) { + PyDict_Watch(GLOBALS_WATCHER_ID, dict); + _Py_BloomFilter_Add(dependencies, dict); + this_instr->opcode = _NOP; + } + } + } + } + op(_LOAD_ATTR_MODULE, (index/1, owner -- attr, null if (oparg & 1))) { - _LOAD_ATTR_NOT_NULL (void)index; - (void)owner; + OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); + attr = NULL; + if (this_instr[-1].opcode == _NOP) { + // Preceding _CHECK_ATTR_MODULE was removed: mod is const and dict is watched. + assert(sym_is_const(owner)); + PyModuleObject *mod = (PyModuleObject *)sym_get_const(owner); + assert(PyModule_CheckExact(mod)); + PyObject *dict = mod->md_dict; + PyObject *res = convert_global_to_const(this_instr, dict); + if (res != NULL) { + this_instr[-1].opcode = _POP_TOP; + OUT_OF_SPACE_IF_NULL(attr = sym_new_const(ctx, res)); + } + } + if (attr == NULL) { + /* No conversion made. We don't know what `attr` is. */ + OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); + } } op(_LOAD_ATTR_WITH_HINT, (hint/1, owner -- attr, null if (oparg & 1))) { @@ -347,4 +381,4 @@ dummy_func(void) { // END BYTECODES // -} \ No newline at end of file +} diff --git a/Python/tier2_redundancy_eliminator_cases.c.h b/Python/tier2_redundancy_eliminator_cases.c.h index f41fe328195b4dc..58c11b7a1c87e09 100644 --- a/Python/tier2_redundancy_eliminator_cases.c.h +++ b/Python/tier2_redundancy_eliminator_cases.c.h @@ -183,11 +183,11 @@ _Py_UOpsSymType *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (is_const(left) && is_const(right)) { - assert(PyLong_CheckExact(get_const(left))); - assert(PyLong_CheckExact(get_const(right))); - PyObject *temp = _PyLong_Multiply((PyLongObject *)get_const(left), - (PyLongObject *)get_const(right)); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyLong_CheckExact(sym_get_const(left))); + assert(PyLong_CheckExact(sym_get_const(right))); + PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(left), + (PyLongObject *)sym_get_const(right)); if (temp == NULL) { goto error; } @@ -209,11 +209,11 @@ _Py_UOpsSymType *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (is_const(left) && is_const(right)) { - assert(PyLong_CheckExact(get_const(left))); - assert(PyLong_CheckExact(get_const(right))); - PyObject *temp = _PyLong_Add((PyLongObject *)get_const(left), - (PyLongObject *)get_const(right)); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyLong_CheckExact(sym_get_const(left))); + assert(PyLong_CheckExact(sym_get_const(right))); + PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(left), + (PyLongObject *)sym_get_const(right)); if (temp == NULL) { goto error; } @@ -235,11 +235,11 @@ _Py_UOpsSymType *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (is_const(left) && is_const(right)) { - assert(PyLong_CheckExact(get_const(left))); - assert(PyLong_CheckExact(get_const(right))); - PyObject *temp = _PyLong_Subtract((PyLongObject *)get_const(left), - (PyLongObject *)get_const(right)); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyLong_CheckExact(sym_get_const(left))); + assert(PyLong_CheckExact(sym_get_const(right))); + PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(left), + (PyLongObject *)sym_get_const(right)); if (temp == NULL) { goto error; } @@ -275,12 +275,12 @@ _Py_UOpsSymType *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (is_const(left) && is_const(right)) { - assert(PyFloat_CheckExact(get_const(left))); - assert(PyFloat_CheckExact(get_const(right))); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyFloat_CheckExact(sym_get_const(left))); + assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(get_const(left)) * - PyFloat_AS_DOUBLE(get_const(right))); + PyFloat_AS_DOUBLE(sym_get_const(left)) * + PyFloat_AS_DOUBLE(sym_get_const(right))); if (temp == NULL) { goto error; } @@ -302,12 +302,12 @@ _Py_UOpsSymType *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (is_const(left) && is_const(right)) { - assert(PyFloat_CheckExact(get_const(left))); - assert(PyFloat_CheckExact(get_const(right))); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyFloat_CheckExact(sym_get_const(left))); + assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(get_const(left)) + - PyFloat_AS_DOUBLE(get_const(right))); + PyFloat_AS_DOUBLE(sym_get_const(left)) + + PyFloat_AS_DOUBLE(sym_get_const(right))); if (temp == NULL) { goto error; } @@ -329,12 +329,12 @@ _Py_UOpsSymType *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (is_const(left) && is_const(right)) { - assert(PyFloat_CheckExact(get_const(left))); - assert(PyFloat_CheckExact(get_const(right))); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyFloat_CheckExact(sym_get_const(left))); + assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(get_const(left)) - - PyFloat_AS_DOUBLE(get_const(right))); + PyFloat_AS_DOUBLE(sym_get_const(left)) - + PyFloat_AS_DOUBLE(sym_get_const(right))); if (temp == NULL) { goto error; } @@ -898,6 +898,23 @@ } case _CHECK_ATTR_MODULE: { + _Py_UOpsSymType *owner; + owner = stack_pointer[-1]; + uint32_t dict_version = (uint32_t)this_instr->operand; + (void)dict_version; + if (sym_is_const(owner)) { + PyObject *cnst = sym_get_const(owner); + if (PyModule_CheckExact(cnst)) { + PyModuleObject *mod = (PyModuleObject *)cnst; + PyObject *dict = mod->md_dict; + uint64_t watched_mutations = get_mutations(dict); + if (watched_mutations < _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS) { + PyDict_Watch(GLOBALS_WATCHER_ID, dict); + _Py_BloomFilter_Add(dependencies, dict); + this_instr->opcode = _NOP; + } + } + } break; } @@ -907,9 +924,25 @@ _Py_UOpsSymType *null = NULL; owner = stack_pointer[-1]; uint16_t index = (uint16_t)this_instr->operand; - _LOAD_ATTR_NOT_NULL (void)index; - (void)owner; + OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); + attr = NULL; + if (this_instr[-1].opcode == _NOP) { + // Preceding _CHECK_ATTR_MODULE was removed: mod is const and dict is watched. + assert(sym_is_const(owner)); + PyModuleObject *mod = (PyModuleObject *)sym_get_const(owner); + assert(PyModule_CheckExact(mod)); + PyObject *dict = mod->md_dict; + PyObject *res = convert_global_to_const(this_instr, dict); + if (res != NULL) { + this_instr[-1].opcode = _POP_TOP; + OUT_OF_SPACE_IF_NULL(attr = sym_new_const(ctx, res)); + } + } + if (attr == NULL) { + /* No conversion made. We don't know what `attr` is. */ + OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); + } stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = null; stack_pointer += (oparg & 1); From 465df8855e9fff771e119ea525344de48869ef1a Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Thu, 22 Feb 2024 20:57:12 +0300 Subject: [PATCH 413/507] gh-115827: Fix compile warning in `longobject.c` (#115828) Objects/longobject.c:1186:42: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] --- Objects/longobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index fe7829833343236..2d1c6ad788e281e 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -1183,7 +1183,7 @@ PyLong_AsNativeBytes(PyObject* vv, void* buffer, Py_ssize_t n, int endianness) for (Py_ssize_t i = sizeof(cv.b); i > 0; --i) { *b++ = cv.b[i - 1]; } - for (Py_ssize_t i = 0; i < n - sizeof(cv.b); ++i) { + for (Py_ssize_t i = 0; i < n - (int)sizeof(cv.b); ++i) { *b++ = fill; } } From 17dab2e572141a4295325bf7019301fece301fe1 Mon Sep 17 00:00:00 2001 From: Brandt Bucher <brandtbucher@microsoft.com> Date: Thu, 22 Feb 2024 10:22:23 -0800 Subject: [PATCH 414/507] GH-113464: Clean up JIT stencil generation (GH-115800) --- Tools/jit/_targets.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Tools/jit/_targets.py b/Tools/jit/_targets.py index 51b091eb2464131..6c1d440324c5052 100644 --- a/Tools/jit/_targets.py +++ b/Tools/jit/_targets.py @@ -45,8 +45,7 @@ class _Target(typing.Generic[_S, _R]): def _compute_digest(self, out: pathlib.Path) -> str: hasher = hashlib.sha256() hasher.update(self.triple.encode()) - hasher.update(self.alignment.to_bytes()) - hasher.update(self.prefix.encode()) + hasher.update(self.debug.to_bytes()) # These dependencies are also reflected in _JITSources in regen.targets: hasher.update(PYTHON_EXECUTOR_CASES_C_H.read_bytes()) hasher.update((out / "pyconfig.h").read_bytes()) @@ -119,6 +118,7 @@ async def _compile( "-O3", "-c", "-fno-asynchronous-unwind-tables", + "-fno-builtin", # SET_FUNCTION_ATTRIBUTE on 32-bit Windows debug builds: "-fno-jump-tables", # Position-independent code adds indirection to every load and jump: @@ -166,7 +166,7 @@ def build(self, out: pathlib.Path, *, comment: str = "") -> None: with jit_stencils.open("w") as file: file.write(digest) if comment: - file.write(f"// {comment}\n") + file.write(f"// {comment}\n\n") file.write("") for line in _writer.dump(stencil_groups): file.write(f"{line}\n") @@ -310,6 +310,8 @@ def _handle_section( flags = {flag["Name"] for flag in section["Attributes"]["Flags"]} name = section["Name"]["Value"] name = name.removeprefix(self.prefix) + if "Debug" in flags: + return if "SomeInstructions" in flags: value = _stencils.HoleValue.CODE stencil = group.code @@ -371,9 +373,6 @@ def _handle_relocation( addend = 0 case _: raise NotImplementedError(relocation) - # Turn Clang's weird __bzero calls into normal bzero calls: - if symbol == "__bzero": - symbol = "bzero" return _stencils.Hole(offset, kind, value, symbol, addend) From a3859422d15d98892fd53499916bd424f841404f Mon Sep 17 00:00:00 2001 From: NewUserHa <32261870+NewUserHa@users.noreply.github.com> Date: Fri, 23 Feb 2024 03:08:04 +0800 Subject: [PATCH 415/507] Update http.cookiejar document for cookie object attributes (GH-101885) --- Doc/library/http.cookiejar.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/library/http.cookiejar.rst b/Doc/library/http.cookiejar.rst index 12a6d768437ea54..2fe188be641c2d1 100644 --- a/Doc/library/http.cookiejar.rst +++ b/Doc/library/http.cookiejar.rst @@ -649,6 +649,11 @@ internal consistency, so you should know what you're doing if you do that. :const:`None`. +.. attribute:: Cookie.domain + + Cookie domain (a string). + + .. attribute:: Cookie.path Cookie path (a string, eg. ``'/acme/rocket_launchers'``). From 1002fbe12e0bd8c9a54bc5addbf5d94a5b35f91f Mon Sep 17 00:00:00 2001 From: Dino Viehland <dinoviehland@meta.com> Date: Thu, 22 Feb 2024 12:02:39 -0800 Subject: [PATCH 416/507] gh-112075: Iterating a dict shouldn't require locks (#115108) Makes iteration of a dict be lock free for the forward iteration case. --- Include/internal/pycore_dict.h | 1 + Objects/dictobject.c | 324 +++++++++++++++++++++++++++------ 2 files changed, 265 insertions(+), 60 deletions(-) diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index 5a496d59ab7af79..d1a0010b9a81dd7 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -212,6 +212,7 @@ static inline PyDictUnicodeEntry* DK_UNICODE_ENTRIES(PyDictKeysObject *dk) { #define DICT_WATCHER_AND_MODIFICATION_MASK ((1 << (DICT_MAX_WATCHERS + DICT_WATCHED_MUTATION_BITS)) - 1) #define DICT_VALUES_SIZE(values) ((uint8_t *)values)[-1] +#define DICT_VALUES_USED_SIZE(values) ((uint8_t *)values)[-2] #ifdef Py_GIL_DISABLED #define DICT_NEXT_VERSION(INTERP) \ diff --git a/Objects/dictobject.c b/Objects/dictobject.c index ed92998a23a7689..5ae4c3dbea23800 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -115,19 +115,20 @@ As a consequence of this, split keys have a maximum size of 16. #define PyDict_MINSIZE 8 #include "Python.h" -#include "pycore_bitutils.h" // _Py_bit_length -#include "pycore_call.h" // _PyObject_CallNoArgs() -#include "pycore_ceval.h" // _PyEval_GetBuiltin() -#include "pycore_code.h" // stats -#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION, Py_END_CRITICAL_SECTION -#include "pycore_dict.h" // export _PyDict_SizeOf() -#include "pycore_freelist.h" // _PyFreeListState_GET() -#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED() -#include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats() -#include "pycore_pyerrors.h" // _PyErr_GetRaisedException() -#include "pycore_pystate.h" // _PyThreadState_GET() -#include "pycore_setobject.h" // _PySet_NextEntry() -#include "stringlib/eq.h" // unicode_eq() +#include "pycore_bitutils.h" // _Py_bit_length +#include "pycore_call.h" // _PyObject_CallNoArgs() +#include "pycore_ceval.h" // _PyEval_GetBuiltin() +#include "pycore_code.h" // stats +#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION, Py_END_CRITICAL_SECTION +#include "pycore_dict.h" // export _PyDict_SizeOf() +#include "pycore_freelist.h" // _PyFreeListState_GET() +#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED() +#include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats() +#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_SSIZE_RELAXED +#include "pycore_pyerrors.h" // _PyErr_GetRaisedException() +#include "pycore_pystate.h" // _PyThreadState_GET() +#include "pycore_setobject.h" // _PySet_NextEntry() +#include "stringlib/eq.h" // unicode_eq() #include <stdbool.h> @@ -158,6 +159,14 @@ ASSERT_DICT_LOCKED(PyObject *op) #define STORE_INDEX(keys, size, idx, value) _Py_atomic_store_int##size##_relaxed(&((int##size##_t*)keys->dk_indices)[idx], (int##size##_t)value); #define ASSERT_OWNED_OR_SHARED(mp) \ assert(_Py_IsOwnedByCurrentThread((PyObject *)mp) || IS_DICT_SHARED(mp)); +#define LOAD_KEYS_NENTRIES(d) + +static inline Py_ssize_t +load_keys_nentries(PyDictObject *mp) +{ + PyDictKeysObject *keys = _Py_atomic_load_ptr(&mp->ma_keys); + return _Py_atomic_load_ssize(&keys->dk_nentries); +} static inline void set_keys(PyDictObject *mp, PyDictKeysObject *keys) @@ -232,6 +241,13 @@ set_values(PyDictObject *mp, PyDictValues *values) mp->ma_values = values; } +static inline Py_ssize_t +load_keys_nentries(PyDictObject *mp) +{ + return mp->ma_keys->dk_nentries; +} + + #endif #define PERTURB_SHIFT 5 @@ -4858,7 +4874,7 @@ dictiter_new(PyDictObject *dict, PyTypeObject *itertype) di->di_pos = dict->ma_used - 1; } else { - di->di_pos = dict->ma_keys->dk_nentries - 1; + di->di_pos = load_keys_nentries(dict) - 1; } } else { @@ -4925,6 +4941,14 @@ static PyMethodDef dictiter_methods[] = { {NULL, NULL} /* sentinel */ }; +#ifdef Py_GIL_DISABLED + +static int +dictiter_iternext_threadsafe(PyDictObject *d, PyObject *self, + PyObject **out_key, PyObject **out_value); + +#else /* Py_GIL_DISABLED */ + static PyObject* dictiter_iternextkey_lock_held(PyDictObject *d, PyObject *self) { @@ -4992,6 +5016,8 @@ dictiter_iternextkey_lock_held(PyDictObject *d, PyObject *self) return NULL; } +#endif /* Py_GIL_DISABLED */ + static PyObject* dictiter_iternextkey(PyObject *self) { @@ -5002,9 +5028,13 @@ dictiter_iternextkey(PyObject *self) return NULL; PyObject *value; - Py_BEGIN_CRITICAL_SECTION(d); +#ifdef Py_GIL_DISABLED + if (!dictiter_iternext_threadsafe(d, self, &value, NULL) == 0) { + value = NULL; + } +#else value = dictiter_iternextkey_lock_held(d, self); - Py_END_CRITICAL_SECTION(); +#endif return value; } @@ -5042,6 +5072,8 @@ PyTypeObject PyDictIterKey_Type = { 0, }; +#ifndef Py_GIL_DISABLED + static PyObject * dictiter_iternextvalue_lock_held(PyDictObject *d, PyObject *self) { @@ -5107,6 +5139,8 @@ dictiter_iternextvalue_lock_held(PyDictObject *d, PyObject *self) return NULL; } +#endif /* Py_GIL_DISABLED */ + static PyObject * dictiter_iternextvalue(PyObject *self) { @@ -5117,9 +5151,13 @@ dictiter_iternextvalue(PyObject *self) return NULL; PyObject *value; - Py_BEGIN_CRITICAL_SECTION(d); +#ifdef Py_GIL_DISABLED + if (!dictiter_iternext_threadsafe(d, self, NULL, &value) == 0) { + value = NULL; + } +#else value = dictiter_iternextvalue_lock_held(d, self); - Py_END_CRITICAL_SECTION(); +#endif return value; } @@ -5157,11 +5195,12 @@ PyTypeObject PyDictIterValue_Type = { 0, }; -static PyObject * -dictiter_iternextitem_lock_held(PyDictObject *d, PyObject *self) +static int +dictiter_iternextitem_lock_held(PyDictObject *d, PyObject *self, + PyObject **out_key, PyObject **out_value) { dictiterobject *di = (dictiterobject *)self; - PyObject *key, *value, *result; + PyObject *key, *value; Py_ssize_t i; assert (PyDict_Check(d)); @@ -5170,10 +5209,11 @@ dictiter_iternextitem_lock_held(PyDictObject *d, PyObject *self) PyErr_SetString(PyExc_RuntimeError, "dictionary changed size during iteration"); di->di_used = -1; /* Make this state sticky */ - return NULL; + return -1; } - i = di->di_pos; + i = FT_ATOMIC_LOAD_SSIZE_RELAXED(di->di_pos); + assert(i >= 0); if (_PyDict_HasSplitTable(d)) { if (i >= d->ma_used) @@ -5216,34 +5256,184 @@ dictiter_iternextitem_lock_held(PyDictObject *d, PyObject *self) } di->di_pos = i+1; di->len--; - result = di->di_result; - if (Py_REFCNT(result) == 1) { - PyObject *oldkey = PyTuple_GET_ITEM(result, 0); - PyObject *oldvalue = PyTuple_GET_ITEM(result, 1); - PyTuple_SET_ITEM(result, 0, Py_NewRef(key)); - PyTuple_SET_ITEM(result, 1, Py_NewRef(value)); - Py_INCREF(result); - Py_DECREF(oldkey); - Py_DECREF(oldvalue); - // bpo-42536: The GC may have untracked this result tuple. Since we're - // recycling it, make sure it's tracked again: - if (!_PyObject_GC_IS_TRACKED(result)) { - _PyObject_GC_TRACK(result); + if (out_key != NULL) { + *out_key = Py_NewRef(key); + } + if (out_value != NULL) { + *out_value = Py_NewRef(value); + } + return 0; + +fail: + di->di_dict = NULL; + Py_DECREF(d); + return -1; +} + +#ifdef Py_GIL_DISABLED + +// Grabs the key and/or value from the provided locations and if successful +// returns them with an increased reference count. If either one is unsucessful +// nothing is incref'd and returns -1. +static int +acquire_key_value(PyObject **key_loc, PyObject *value, PyObject **value_loc, + PyObject **out_key, PyObject **out_value) +{ + if (out_key) { + *out_key = _Py_TryXGetRef(key_loc); + if (*out_key == NULL) { + return -1; + } + } + + if (out_value) { + if (!_Py_TryIncref(value_loc, value)) { + if (out_key) { + Py_DECREF(*out_key); + } + return -1; + } + *out_value = value; + } + + return 0; +} + +static Py_ssize_t +load_values_used_size(PyDictValues *values) +{ + return (Py_ssize_t)_Py_atomic_load_uint8(&DICT_VALUES_USED_SIZE(values)); +} + +static int +dictiter_iternext_threadsafe(PyDictObject *d, PyObject *self, + PyObject **out_key, PyObject **out_value) +{ + dictiterobject *di = (dictiterobject *)self; + Py_ssize_t i; + PyDictKeysObject *k; + + assert (PyDict_Check(d)); + + if (di->di_used != _Py_atomic_load_ssize_relaxed(&d->ma_used)) { + PyErr_SetString(PyExc_RuntimeError, + "dictionary changed size during iteration"); + di->di_used = -1; /* Make this state sticky */ + return -1; + } + + ensure_shared_on_read(d); + + i = _Py_atomic_load_ssize_relaxed(&di->di_pos); + k = _Py_atomic_load_ptr_relaxed(&d->ma_keys); + assert(i >= 0); + if (_PyDict_HasSplitTable(d)) { + PyDictValues *values = _Py_atomic_load_ptr_relaxed(&d->ma_values); + if (values == NULL) { + goto concurrent_modification; + } + + Py_ssize_t used = load_values_used_size(values); + if (i >= used) { + goto fail; + } + + // We're racing against writes to the order from delete_index_from_values, but + // single threaded can suffer from concurrent modification to those as well and + // can have either duplicated or skipped attributes, so we strive to do no better + // here. + int index = get_index_from_order(d, i); + PyObject *value = _Py_atomic_load_ptr(&values->values[index]); + if (acquire_key_value(&DK_UNICODE_ENTRIES(k)[index].me_key, value, + &values->values[index], out_key, out_value) < 0) { + goto try_locked; } } else { - result = PyTuple_New(2); - if (result == NULL) - return NULL; - PyTuple_SET_ITEM(result, 0, Py_NewRef(key)); - PyTuple_SET_ITEM(result, 1, Py_NewRef(value)); + Py_ssize_t n = _Py_atomic_load_ssize_relaxed(&k->dk_nentries); + if (DK_IS_UNICODE(k)) { + PyDictUnicodeEntry *entry_ptr = &DK_UNICODE_ENTRIES(k)[i]; + PyObject *value; + while (i < n && + (value = _Py_atomic_load_ptr(&entry_ptr->me_value)) == NULL) { + entry_ptr++; + i++; + } + if (i >= n) + goto fail; + + if (acquire_key_value(&entry_ptr->me_key, value, + &entry_ptr->me_value, out_key, out_value) < 0) { + goto try_locked; + } + } + else { + PyDictKeyEntry *entry_ptr = &DK_ENTRIES(k)[i]; + PyObject *value; + while (i < n && + (value = _Py_atomic_load_ptr(&entry_ptr->me_value)) == NULL) { + entry_ptr++; + i++; + } + + if (i >= n) + goto fail; + + if (acquire_key_value(&entry_ptr->me_key, value, + &entry_ptr->me_value, out_key, out_value) < 0) { + goto try_locked; + } + } } - return result; + // We found an element (key), but did not expect it + Py_ssize_t len; + if ((len = _Py_atomic_load_ssize_relaxed(&di->len)) == 0) { + goto concurrent_modification; + } + + _Py_atomic_store_ssize_relaxed(&di->di_pos, i + 1); + _Py_atomic_store_ssize_relaxed(&di->len, len - 1); + return 0; + +concurrent_modification: + PyErr_SetString(PyExc_RuntimeError, + "dictionary keys changed during iteration"); fail: di->di_dict = NULL; Py_DECREF(d); - return NULL; + return -1; + + int res; +try_locked: + Py_BEGIN_CRITICAL_SECTION(d); + res = dictiter_iternextitem_lock_held(d, self, out_key, out_value); + Py_END_CRITICAL_SECTION(); + return res; +} + +#endif + +static bool +has_unique_reference(PyObject *op) +{ +#ifdef Py_GIL_DISABLED + return (_Py_IsOwnedByCurrentThread(op) && + op->ob_ref_local == 1 && + _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared) == 0); +#else + return Py_REFCNT(op) == 1; +#endif +} + +static bool +acquire_iter_result(PyObject *result) +{ + if (has_unique_reference(result)) { + Py_INCREF(result); + return true; + } + return false; } static PyObject * @@ -5255,11 +5445,37 @@ dictiter_iternextitem(PyObject *self) if (d == NULL) return NULL; - PyObject *item; - Py_BEGIN_CRITICAL_SECTION(d); - item = dictiter_iternextitem_lock_held(d, self); - Py_END_CRITICAL_SECTION(); - return item; + PyObject *key, *value; +#ifdef Py_GIL_DISABLED + if (dictiter_iternext_threadsafe(d, self, &key, &value) == 0) { +#else + if (dictiter_iternextitem_lock_held(d, self, &key, &value) == 0) { + +#endif + PyObject *result = di->di_result; + if (acquire_iter_result(result)) { + PyObject *oldkey = PyTuple_GET_ITEM(result, 0); + PyObject *oldvalue = PyTuple_GET_ITEM(result, 1); + PyTuple_SET_ITEM(result, 0, key); + PyTuple_SET_ITEM(result, 1, value); + Py_DECREF(oldkey); + Py_DECREF(oldvalue); + // bpo-42536: The GC may have untracked this result tuple. Since we're + // recycling it, make sure it's tracked again: + if (!_PyObject_GC_IS_TRACKED(result)) { + _PyObject_GC_TRACK(result); + } + } + else { + result = PyTuple_New(2); + if (result == NULL) + return NULL; + PyTuple_SET_ITEM(result, 0, key); + PyTuple_SET_ITEM(result, 1, value); + } + return result; + } + return NULL; } PyTypeObject PyDictIterItem_Type = { @@ -6423,18 +6639,6 @@ _PyObject_MakeDictFromInstanceAttributes(PyObject *obj, PyDictValues *values) return make_dict_from_instance_attributes(interp, keys, values); } -static bool -has_unique_reference(PyObject *op) -{ -#ifdef Py_GIL_DISABLED - return (_Py_IsOwnedByCurrentThread(op) && - op->ob_ref_local == 1 && - _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared) == 0); -#else - return Py_REFCNT(op) == 1; -#endif -} - // Return true if the dict was dematerialized, false otherwise. bool _PyObject_MakeInstanceAttributesFromDict(PyObject *obj, PyDictOrValues *dorv) From 4ee6bdfbaa792a3aa93c65c2022a89bd2d1e0894 Mon Sep 17 00:00:00 2001 From: Guido van Rossum <guido@python.org> Date: Thu, 22 Feb 2024 12:23:48 -0800 Subject: [PATCH 417/507] gh-115727: Reduce confidence even on 100% predicted jumps (#115748) The theory is that even if we saw a jump go in the same direction the last 16 times we got there, we shouldn't be overly confident that it's still going to go the same way in the future. This PR makes it so that in the extreme cases, the confidence is multiplied by 0.9 instead of remaining unchanged. For unpredictable jumps, there is no difference (still 0.5). For somewhat predictable jumps, we interpolate. --- Python/optimizer.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index 74708beea7a53d1..6b2ba3addefc95d 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -546,6 +546,8 @@ translate_bytecode_to_trace( uint32_t oparg = instr->op.arg; uint32_t extended = 0; + DPRINTF(3, "%d: %s(%d)\n", target, _PyOpcode_OpName[opcode], oparg); + if (opcode == ENTER_EXECUTOR) { assert(oparg < 256); _PyExecutorObject *executor = code->co_executors->executors[oparg]; @@ -593,21 +595,25 @@ translate_bytecode_to_trace( int counter = instr[1].cache; int bitcount = _Py_popcount32(counter); int jump_likely = bitcount > 8; + /* If bitcount is 8 (half the jumps were taken), adjust confidence by 50%. + If it's 16 or 0 (all or none were taken), adjust by 10% + (since the future is still somewhat uncertain). + For values in between, adjust proportionally. */ if (jump_likely) { - confidence = confidence * bitcount / 16; + confidence = confidence * (bitcount + 2) / 20; } else { - confidence = confidence * (16 - bitcount) / 16; + confidence = confidence * (18 - bitcount) / 20; } + uint32_t uopcode = BRANCH_TO_GUARD[opcode - POP_JUMP_IF_FALSE][jump_likely]; + DPRINTF(2, "%d: %s(%d): counter=%x, bitcount=%d, likely=%d, confidence=%d, uopcode=%s\n", + target, _PyOpcode_OpName[opcode], oparg, + counter, bitcount, jump_likely, confidence, _PyUOpName(uopcode)); if (confidence < CONFIDENCE_CUTOFF) { - DPRINTF(2, "Confidence too low (%d)\n", confidence); + DPRINTF(2, "Confidence too low (%d < %d)\n", confidence, CONFIDENCE_CUTOFF); OPT_STAT_INC(low_confidence); goto done; } - uint32_t uopcode = BRANCH_TO_GUARD[opcode - POP_JUMP_IF_FALSE][jump_likely]; - DPRINTF(2, "%s(%d): counter=%x, bitcount=%d, likely=%d, confidence=%d, uopcode=%s\n", - _PyOpcode_OpName[opcode], oparg, - counter, bitcount, jump_likely, confidence, _PyUOpName(uopcode)); _Py_CODEUNIT *next_instr = instr + 1 + _PyOpcode_Caches[_PyOpcode_Deopt[opcode]]; _Py_CODEUNIT *target_instr = next_instr + oparg; if (jump_likely) { From b48101864c724a7eab41a6878a836f38e54e04fb Mon Sep 17 00:00:00 2001 From: Ronald Oussoren <ronaldoussoren@mac.com> Date: Fri, 23 Feb 2024 03:15:39 +0100 Subject: [PATCH 418/507] gh-88516: show file proxy icon in IDLE editor windows on macOS (#112894) The platform standard on macOS is to show a proxy icon for open files in the titlebar of Windows. Make sure IDLE matches this behaviour. Don't use both the long and short names in the window title. The behaviour of other editors (such as Text Editor) is to show only the short name with the proxy icon. Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu> --- Lib/idlelib/editor.py | 11 ++++++++++- .../2023-12-09-11-04-26.gh-issue-88516.SIIvfs.rst | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/IDLE/2023-12-09-11-04-26.gh-issue-88516.SIIvfs.rst diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 8ee8eba64367a50..7bfa0932500d81c 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -1044,7 +1044,9 @@ def open_recent_file(fn_closure=file_name): def saved_change_hook(self): short = self.short_title() long = self.long_title() - if short and long: + if short and long and not macosx.isCocoaTk(): + # Don't use both values on macOS because + # that doesn't match platform conventions. title = short + " - " + long + _py_version elif short: title = short @@ -1059,6 +1061,13 @@ def saved_change_hook(self): self.top.wm_title(title) self.top.wm_iconname(icon) + if macosx.isCocoaTk(): + # Add a proxy icon to the window title + self.top.wm_attributes("-titlepath", long) + + # Maintain the modification status for the window + self.top.wm_attributes("-modified", not self.get_saved()) + def get_saved(self): return self.undo.get_saved() diff --git a/Misc/NEWS.d/next/IDLE/2023-12-09-11-04-26.gh-issue-88516.SIIvfs.rst b/Misc/NEWS.d/next/IDLE/2023-12-09-11-04-26.gh-issue-88516.SIIvfs.rst new file mode 100644 index 000000000000000..b6dea5029bf3531 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2023-12-09-11-04-26.gh-issue-88516.SIIvfs.rst @@ -0,0 +1,2 @@ +On macOS show a proxy icon in the title bar of editor windows to match +platform behaviour. From a494a3dd8e0e74861972698c37b14a4087a4688c Mon Sep 17 00:00:00 2001 From: Brett Simmers <swtaarrs@users.noreply.github.com> Date: Thu, 22 Feb 2024 19:14:17 -0800 Subject: [PATCH 419/507] gh-115836: Don't use hardcoded line numbers in test_monitoring (#115837) --- Lib/test/test_monitoring.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index 58aa4bca7534b1e..2fd822036bcff5e 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -35,6 +35,9 @@ def g1(): TEST_TOOL2 = 3 TEST_TOOL3 = 4 +def nth_line(func, offset): + return func.__code__.co_firstlineno + offset + class MonitoringBasicTest(unittest.TestCase): def test_has_objects(self): @@ -529,8 +532,8 @@ def test_lines_single(self): f1() sys.monitoring.set_events(TEST_TOOL, 0) sys.monitoring.register_callback(TEST_TOOL, E.LINE, None) - start = LineMonitoringTest.test_lines_single.__code__.co_firstlineno - self.assertEqual(events, [start+7, 16, start+8]) + start = nth_line(LineMonitoringTest.test_lines_single, 0) + self.assertEqual(events, [start+7, nth_line(f1, 1), start+8]) finally: sys.monitoring.set_events(TEST_TOOL, 0) sys.monitoring.register_callback(TEST_TOOL, E.LINE, None) @@ -547,8 +550,13 @@ def test_lines_loop(self): floop() sys.monitoring.set_events(TEST_TOOL, 0) sys.monitoring.register_callback(TEST_TOOL, E.LINE, None) - start = LineMonitoringTest.test_lines_loop.__code__.co_firstlineno - self.assertEqual(events, [start+7, 23, 24, 23, 24, 23, start+8]) + start = nth_line(LineMonitoringTest.test_lines_loop, 0) + floop_1 = nth_line(floop, 1) + floop_2 = nth_line(floop, 2) + self.assertEqual( + events, + [start+7, floop_1, floop_2, floop_1, floop_2, floop_1, start+8] + ) finally: sys.monitoring.set_events(TEST_TOOL, 0) sys.monitoring.register_callback(TEST_TOOL, E.LINE, None) @@ -569,8 +577,8 @@ def test_lines_two(self): sys.monitoring.set_events(TEST_TOOL, 0); sys.monitoring.set_events(TEST_TOOL2, 0) sys.monitoring.register_callback(TEST_TOOL, E.LINE, None) sys.monitoring.register_callback(TEST_TOOL2, E.LINE, None) - start = LineMonitoringTest.test_lines_two.__code__.co_firstlineno - expected = [start+10, 16, start+11] + start = nth_line(LineMonitoringTest.test_lines_two, 0) + expected = [start+10, nth_line(f1, 1), start+11] self.assertEqual(events, expected) self.assertEqual(events2, expected) finally: From a33ffe4785f90f68227ddf2ec3e06d5ceaf76cec Mon Sep 17 00:00:00 2001 From: Ken Jin <kenjin@python.org> Date: Fri, 23 Feb 2024 15:42:03 +0800 Subject: [PATCH 420/507] gh-114058: More robust method handling in redundancy eliminator (GH-115779) --- Python/optimizer_analysis.c | 1 + .../tier2_redundancy_eliminator_bytecodes.c | 21 +++++++++++ Python/tier2_redundancy_eliminator_cases.c.h | 35 ++++++++++--------- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 68ef8254b494a40..9503dcc74656cd8 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -315,6 +315,7 @@ sym_new_known_notnull(_Py_UOpsAbstractInterpContext *ctx) if (res == NULL) { return NULL; } + sym_set_flag(res, KNOWN); sym_set_flag(res, NOT_NULL); return res; } diff --git a/Python/tier2_redundancy_eliminator_bytecodes.c b/Python/tier2_redundancy_eliminator_bytecodes.c index ff2b9a42272863c..ef7b43d53539ce6 100644 --- a/Python/tier2_redundancy_eliminator_bytecodes.c +++ b/Python/tier2_redundancy_eliminator_bytecodes.c @@ -295,6 +295,27 @@ dummy_func(void) { (void)owner; } + op(_LOAD_ATTR_METHOD_WITH_VALUES, (descr/4, owner -- attr, self if (1))) { + OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + } + + op(_LOAD_ATTR_METHOD_NO_DICT, (descr/4, owner -- attr, self if (1))) { + OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + } + + op(_LOAD_ATTR_METHOD_LAZY_DICT, (descr/4, owner -- attr, self if (1))) { + OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + } + + op(_INIT_CALL_BOUND_METHOD_EXACT_ARGS, (callable, unused, unused[oparg] -- func, self, unused[oparg])) { + OUT_OF_SPACE_IF_NULL(func = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + } + + op(_CHECK_FUNCTION_EXACT_ARGS, (func_version/2, callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) { sym_set_type(callable, &PyFunction_Type); (void)self_or_null; diff --git a/Python/tier2_redundancy_eliminator_cases.c.h b/Python/tier2_redundancy_eliminator_cases.c.h index 58c11b7a1c87e09..ca9b5953d21012f 100644 --- a/Python/tier2_redundancy_eliminator_cases.c.h +++ b/Python/tier2_redundancy_eliminator_cases.c.h @@ -1300,12 +1300,13 @@ } case _LOAD_ATTR_METHOD_WITH_VALUES: { + _Py_UOpsSymType *owner; _Py_UOpsSymType *attr; _Py_UOpsSymType *self = NULL; - attr = sym_new_unknown(ctx); - if (attr == NULL) goto out_of_space; - self = sym_new_unknown(ctx); - if (self == NULL) goto out_of_space; + owner = stack_pointer[-1]; + PyObject *descr = (PyObject *)this_instr->operand; + OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); stack_pointer[-1] = attr; stack_pointer[0] = self; stack_pointer += 1; @@ -1313,12 +1314,13 @@ } case _LOAD_ATTR_METHOD_NO_DICT: { + _Py_UOpsSymType *owner; _Py_UOpsSymType *attr; _Py_UOpsSymType *self = NULL; - attr = sym_new_unknown(ctx); - if (attr == NULL) goto out_of_space; - self = sym_new_unknown(ctx); - if (self == NULL) goto out_of_space; + owner = stack_pointer[-1]; + PyObject *descr = (PyObject *)this_instr->operand; + OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); stack_pointer[-1] = attr; stack_pointer[0] = self; stack_pointer += 1; @@ -1346,12 +1348,13 @@ } case _LOAD_ATTR_METHOD_LAZY_DICT: { + _Py_UOpsSymType *owner; _Py_UOpsSymType *attr; _Py_UOpsSymType *self = NULL; - attr = sym_new_unknown(ctx); - if (attr == NULL) goto out_of_space; - self = sym_new_unknown(ctx); - if (self == NULL) goto out_of_space; + owner = stack_pointer[-1]; + PyObject *descr = (PyObject *)this_instr->operand; + OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); stack_pointer[-1] = attr; stack_pointer[0] = self; stack_pointer += 1; @@ -1373,12 +1376,12 @@ } case _INIT_CALL_BOUND_METHOD_EXACT_ARGS: { + _Py_UOpsSymType *callable; _Py_UOpsSymType *func; _Py_UOpsSymType *self; - func = sym_new_unknown(ctx); - if (func == NULL) goto out_of_space; - self = sym_new_unknown(ctx); - if (self == NULL) goto out_of_space; + callable = stack_pointer[-2 - oparg]; + OUT_OF_SPACE_IF_NULL(func = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); stack_pointer[-2 - oparg] = func; stack_pointer[-1 - oparg] = self; break; From e3f462c9a73d2a6e79aacce7f25aaac361c2d743 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Fri, 23 Feb 2024 12:00:07 +0300 Subject: [PATCH 421/507] Remove `ConverterKeywordDict` alias in `clinic.py` (#115843) --- Tools/clinic/clinic.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 5d2617b3bd579fc..7906e7c95d17ba1 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -4069,8 +4069,6 @@ def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> st # mapping from arguments to format unit *and* registers the # legacy C converter for that format unit. # -ConverterKeywordDict = dict[str, TypeSet | bool] - def r(format_unit: str, *, accept: TypeSet, @@ -4086,7 +4084,7 @@ def r(format_unit: str, # # also don't add the converter for 's' because # the metaclass for CConverter adds it for us. - kwargs: ConverterKeywordDict = {} + kwargs: dict[str, Any] = {} if accept != {str}: kwargs['accept'] = accept if zeroes: From 2e92ffd7fa89e3bd33ee2f31541d3dc53aaa2d12 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Fri, 23 Feb 2024 11:27:07 +0200 Subject: [PATCH 422/507] gh-90300: Reformat the Python CLI help output (GH-93415) --- Python/initconfig.c | 214 ++++++++++++++++++++++---------------------- 1 file changed, 106 insertions(+), 108 deletions(-) diff --git a/Python/initconfig.c b/Python/initconfig.c index a6d8c1761566179..74f28f3b39175b1 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -153,7 +153,8 @@ Options (and corresponding environment variables):\n\ .pyc extension; also PYTHONOPTIMIZE=x\n\ -OO : do -O changes and also discard docstrings; add .opt-2 before\n\ .pyc extension\n\ --P : don't prepend a potentially unsafe path to sys.path; also PYTHONSAFEPATH\n\ +-P : don't prepend a potentially unsafe path to sys.path; also\n\ + PYTHONSAFEPATH\n\ -q : don't print version and copyright messages on interactive startup\n\ -s : don't add user site directory to sys.path; also PYTHONNOUSERSITE\n\ -S : don't imply 'import site' on initialization\n\ @@ -169,9 +170,10 @@ Options (and corresponding environment variables):\n\ -X opt : set implementation-specific option\n\ --check-hash-based-pycs always|default|never:\n\ control how Python invalidates hash-based .pyc files\n\ ---help-env : print help about Python environment variables and exit\n\ ---help-xoptions : print help about implementation-specific -X options and exit\n\ ---help-all : print complete help information and exit\n\ +--help-env: print help about Python environment variables and exit\n\ +--help-xoptions: print help about implementation-specific -X options and exit\n\ +--help-all: print complete help information and exit\n\ +\n\ Arguments:\n\ file : program read from script file\n\ - : program read from stdin (default; interactive mode if a tty)\n\ @@ -180,73 +182,61 @@ arg ...: arguments passed to program in sys.argv[1:]\n\ static const char usage_xoptions[] = "\ The following implementation-specific options are available:\n\ -\n\ -X faulthandler: enable faulthandler\n\ -\n\ -X showrefcount: output the total reference count and number of used\n\ - memory blocks when the program finishes or after each statement in the\n\ - interactive interpreter. This only works on debug builds\n\ -\n\ + memory blocks when the program finishes or after each statement in\n\ + the interactive interpreter. This only works on debug builds\n\ -X tracemalloc: start tracing Python memory allocations using the\n\ - tracemalloc module. By default, only the most recent frame is stored in a\n\ - traceback of a trace. Use -X tracemalloc=NFRAME to start tracing with a\n\ - traceback limit of NFRAME frames\n\ -\n\ --X importtime: show how long each import takes. It shows module name,\n\ - cumulative time (including nested imports) and self time (excluding\n\ - nested imports). Note that its output may be broken in multi-threaded\n\ - application. Typical usage is python3 -X importtime -c 'import asyncio'\n\ -\n\ --X dev: enable CPython's \"development mode\", introducing additional runtime\n\ - checks which are too expensive to be enabled by default. Effect of the\n\ - developer mode:\n\ - * Add default warning filter, as -W default\n\ - * Install debug hooks on memory allocators: see the PyMem_SetupDebugHooks()\n\ - C function\n\ - * Enable the faulthandler module to dump the Python traceback on a crash\n\ - * Enable asyncio debug mode\n\ - * Set the dev_mode attribute of sys.flags to True\n\ - * io.IOBase destructor logs close() exceptions\n\ -\n\ --X utf8: enable UTF-8 mode for operating system interfaces, overriding the default\n\ - locale-aware mode. -X utf8=0 explicitly disables UTF-8 mode (even when it would\n\ - otherwise activate automatically)\n\ -\n\ --X pycache_prefix=PATH: enable writing .pyc files to a parallel tree rooted at the\n\ - given directory instead of to the code tree\n\ -\n\ + tracemalloc module. By default, only the most recent frame is stored\n\ + in a traceback of a trace. Use -X tracemalloc=NFRAME to start\n\ + tracing with a traceback limit of NFRAME frames\n\ +-X importtime: show how long each import takes. It shows module name,\n\ + cumulative time (including nested imports) and self time (excluding\n\ + nested imports). Note that its output may be broken in\n\ + multi-threaded application.\n\ + Typical usage is python3 -X importtime -c 'import asyncio'\n\ +-X dev : enable CPython's \"development mode\", introducing additional runtime\n\ + checks which are too expensive to be enabled by default. Effect of\n\ + the developer mode:\n\ + * Add default warning filter, as -W default\n\ + * Install debug hooks on memory allocators: see the\n\ + PyMem_SetupDebugHooks() C function\n\ + * Enable the faulthandler module to dump the Python traceback on\n\ + a crash\n\ + * Enable asyncio debug mode\n\ + * Set the dev_mode attribute of sys.flags to True\n\ + * io.IOBase destructor logs close() exceptions\n\ +-X utf8: enable UTF-8 mode for operating system interfaces, overriding the\n\ + default locale-aware mode. -X utf8=0 explicitly disables UTF-8 mode\n\ + (even when it would otherwise activate automatically)\n\ +-X pycache_prefix=PATH: enable writing .pyc files to a parallel tree rooted\n\ + at the given directory instead of to the code tree\n\ -X warn_default_encoding: enable opt-in EncodingWarning for 'encoding=None'\n\ -\n\ --X no_debug_ranges: disable the inclusion of the tables mapping extra location \n\ - information (end line, start column offset and end column offset) to every \n\ - instruction in code objects. This is useful when smaller code objects and pyc \n\ - files are desired as well as suppressing the extra visual location indicators \n\ - when the interpreter displays tracebacks.\n\ -\n\ --X perf: activate support for the Linux \"perf\" profiler by activating the \"perf\"\n\ - trampoline. When this option is activated, the Linux \"perf\" profiler will be \n\ - able to report Python calls. This option is only available on some platforms and will \n\ - do nothing if is not supported on the current system. The default value is \"off\".\n\ -\n\ +-X no_debug_ranges: disable the inclusion of the tables mapping extra location\n\ + information (end line, start column offset and end column offset) to\n\ + every instruction in code objects. This is useful when smaller code\n\ + objects and pyc files are desired as well as suppressing the extra\n\ + visual location indicators when the interpreter displays tracebacks.\n\ +-X perf: activate support for the Linux \"perf\" profiler by activating the\n\ + \"perf\" trampoline. When this option is activated, the Linux \"perf\"\n\ + profiler will be able to report Python calls. This option is only\n\ + available on some platforms and will do nothing if is not supported\n\ + on the current system. The default value is \"off\".\n\ -X frozen_modules=[on|off]: whether or not frozen modules should be used.\n\ - The default is \"on\" (or \"off\" if you are running a local build).\n\ -\n\ + The default is \"on\" (or \"off\" if you are running a local build).\n\ -X int_max_str_digits=number: limit the size of int<->str conversions.\n\ - This helps avoid denial of service attacks when parsing untrusted data.\n\ - The default is sys.int_info.default_max_str_digits. 0 disables.\n\ -\n\ + This helps avoid denial of service attacks when parsing untrusted\n\ + data. The default is sys.int_info.default_max_str_digits.\n\ + 0 disables.\n\ -X cpu_count=[n|default]: Override the return value of os.cpu_count(),\n\ - os.process_cpu_count(), and multiprocessing.cpu_count(). This can help users who need\n\ - to limit resources in a container." - + os.process_cpu_count(), and multiprocessing.cpu_count(). This can\n\ + help users who need to limit resources in a container." #ifdef Py_STATS "\n\ -\n\ -X pystats: Enable pystats collection at startup." #endif #ifdef Py_DEBUG "\n\ -\n\ -X presite=package.module: import this module before site.py is run." #endif ; @@ -254,65 +244,73 @@ The following implementation-specific options are available:\n\ /* Envvars that don't have equivalent command-line options are listed first */ static const char usage_envvars[] = "Environment variables that change behavior:\n" -"PYTHONSTARTUP: file executed on interactive startup (no default)\n" -"PYTHONPATH : '%lc'-separated list of directories prefixed to the\n" -" default module search path. The result is sys.path.\n" -"PYTHONHOME : alternate <prefix> directory (or <prefix>%lc<exec_prefix>).\n" -" The default module search path uses %s.\n" -"PYTHONPLATLIBDIR : override sys.platlibdir.\n" -"PYTHONCASEOK : ignore case in 'import' statements (Windows).\n" -"PYTHONUTF8: if set to 1, enable the UTF-8 mode.\n" +"PYTHONSTARTUP : file executed on interactive startup (no default)\n" +"PYTHONPATH : '%lc'-separated list of directories prefixed to the\n" +" default module search path. The result is sys.path.\n" +"PYTHONHOME : alternate <prefix> directory (or <prefix>%lc<exec_prefix>).\n" +" The default module search path uses %s.\n" +"PYTHONPLATLIBDIR: override sys.platlibdir.\n" +"PYTHONCASEOK : ignore case in 'import' statements (Windows).\n" +"PYTHONUTF8 : if set to 1, enable the UTF-8 mode.\n" "PYTHONIOENCODING: Encoding[:errors] used for stdin/stdout/stderr.\n" "PYTHONFAULTHANDLER: dump the Python traceback on fatal errors.\n" -"PYTHONHASHSEED: if this variable is set to 'random', a random value is used\n" -" to seed the hashes of str and bytes objects. It can also be set to an\n" -" integer in the range [0,4294967295] to get hash values with a\n" -" predictable seed.\n" +"PYTHONHASHSEED : if this variable is set to 'random', a random value is used\n" +" to seed the hashes of str and bytes objects. It can also be\n" +" set to an integer in the range [0,4294967295] to get hash\n" +" values with a predictable seed.\n" "PYTHONINTMAXSTRDIGITS: limits the maximum digit characters in an int value\n" -" when converting from a string and when converting an int back to a str.\n" -" A value of 0 disables the limit. Conversions to or from bases 2, 4, 8,\n" -" 16, and 32 are never limited.\n" -"PYTHONMALLOC: set the Python memory allocators and/or install debug hooks\n" -" on Python memory allocators. Use PYTHONMALLOC=debug to install debug\n" -" hooks.\n" +" when converting from a string and when converting an int\n" +" back to a str. A value of 0 disables the limit.\n" +" Conversions to or from bases 2, 4, 8, 16, and 32 are never\n" +" limited.\n" +"PYTHONMALLOC : set the Python memory allocators and/or install debug hooks\n" +" on Python memory allocators. Use PYTHONMALLOC=debug to\n" +" install debug hooks.\n" "PYTHONCOERCECLOCALE: if this variable is set to 0, it disables the locale\n" -" coercion behavior. Use PYTHONCOERCECLOCALE=warn to request display of\n" -" locale coercion and locale compatibility warnings on stderr.\n" +" coercion behavior. Use PYTHONCOERCECLOCALE=warn to request\n" +" display of locale coercion and locale compatibility warnings\n" +" on stderr.\n" "PYTHONBREAKPOINT: if this variable is set to 0, it disables the default\n" -" debugger. It can be set to the callable of your debugger of choice.\n" +" debugger. It can be set to the callable of your debugger of\n" +" choice.\n" "PYTHON_CPU_COUNT: Overrides the return value of os.process_cpu_count(),\n" -" os.cpu_count(), and multiprocessing.cpu_count() if set to a positive integer.\n" -"PYTHONDEVMODE: enable the development mode.\n" +" os.cpu_count(), and multiprocessing.cpu_count() if set to\n" +" a positive integer.\n" +"PYTHONDEVMODE : enable the development mode.\n" "PYTHONPYCACHEPREFIX: root directory for bytecode cache (pyc) files.\n" "PYTHONWARNDEFAULTENCODING: enable opt-in EncodingWarning for 'encoding=None'.\n" -"PYTHONNODEBUGRANGES: if this variable is set, it disables the inclusion of the \n" -" tables mapping extra location information (end line, start column offset \n" -" and end column offset) to every instruction in code objects. This is useful \n" -" when smaller code objects and pyc files are desired as well as suppressing the \n" -" extra visual location indicators when the interpreter displays tracebacks.\n" -"PYTHON_FROZEN_MODULES : if this variable is set, it determines whether or not \n" -" frozen modules should be used. The default is \"on\" (or \"off\" if you are \n" -" running a local build).\n" -"PYTHON_COLORS : If this variable is set to 1, the interpreter will" -" colorize various kinds of output. Setting it to 0 deactivates this behavior.\n" -"PYTHON_HISTORY : the location of a .python_history file.\n" -"These variables have equivalent command-line parameters (see --help for details):\n" -"PYTHONDEBUG : enable parser debug mode (-d)\n" -"PYTHONDONTWRITEBYTECODE : don't write .pyc files (-B)\n" -"PYTHONINSPECT : inspect interactively after running script (-i)\n" -"PYTHONINTMAXSTRDIGITS : limit max digit characters in an int value\n" -" (-X int_max_str_digits=number)\n" -"PYTHONNOUSERSITE : disable user site directory (-s)\n" -"PYTHONOPTIMIZE : enable level 1 optimizations (-O)\n" -"PYTHONSAFEPATH : don't prepend a potentially unsafe path to sys.path (-P)\n" -"PYTHONUNBUFFERED : disable stdout/stderr buffering (-u)\n" -"PYTHONVERBOSE : trace import statements (-v)\n" -"PYTHONWARNINGS=arg : warning control (-W arg)\n" +"PYTHONNODEBUGRANGES: if this variable is set, it disables the inclusion of\n" +" the tables mapping extra location information (end line,\n" +" start column offset and end column offset) to every\n" +" instruction in code objects. This is useful when smaller\n" +" code objects and pyc files are desired as well as\n" +" suppressing the extra visual location indicators when the\n" +" interpreter displays tracebacks.\n" +"PYTHON_FROZEN_MODULES: if this variable is set, it determines whether or not\n" +" frozen modules should be used. The default is \"on\" (or\n" +" \"off\" if you are running a local build).\n" +"PYTHON_COLORS : if this variable is set to 1, the interpreter will colorize\n" +" various kinds of output. Setting it to 0 deactivates\n" +" this behavior.\n" +"PYTHON_HISTORY : the location of a .python_history file.\n" +"\n" +"These variables have equivalent command-line options (see --help for details):\n" +"PYTHONDEBUG : enable parser debug mode (-d)\n" +"PYTHONDONTWRITEBYTECODE: don't write .pyc files (-B)\n" +"PYTHONINSPECT : inspect interactively after running script (-i)\n" +"PYTHONINTMAXSTRDIGITS: limit max digit characters in an int value\n" +" (-X int_max_str_digits=number)\n" +"PYTHONNOUSERSITE: disable user site directory (-s)\n" +"PYTHONOPTIMIZE : enable level 1 optimizations (-O)\n" +"PYTHONSAFEPATH : don't prepend a potentially unsafe path to sys.path.\n" +"PYTHONUNBUFFERED: disable stdout/stderr buffering (-u)\n" +"PYTHONVERBOSE : trace import statements (-v)\n" +"PYTHONWARNINGS=arg: warning control (-W arg)\n" #ifdef Py_STATS -"PYTHONSTATS : turns on statistics gathering\n" +"PYTHONSTATS : turns on statistics gathering\n" #endif #ifdef Py_DEBUG -"PYTHON_PRESITE=pkg.mod : import this module before site.py is run\n" +"PYTHON_PRESITE=pkg.mod: import this module before site.py is run\n" #endif ; @@ -2387,9 +2385,9 @@ static void config_complete_usage(const wchar_t* program) { config_usage(0, program); - puts("\n"); + putchar('\n'); config_envvars_usage(); - puts("\n"); + putchar('\n'); config_xoptions_usage(); } From e74cd0f9101d06045464ac3173ab73e0b78d175e Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Fri, 23 Feb 2024 10:52:06 +0100 Subject: [PATCH 423/507] gh-115806: Make configure output more readable (#115807) - make sure LDLIBRARY and HOSTRUNNER checks don't overlap - make the ipv6 library check less subtle --- configure | 19 ++++++++++--------- configure.ac | 10 +++++----- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/configure b/configure index b910d05ca699c04..fcf34f050861bea 100755 --- a/configure +++ b/configure @@ -7386,11 +7386,15 @@ else # shared is disabled ;; esac fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $LDLIBRARY" >&5 +printf "%s\n" "$LDLIBRARY" >&6; } if test "$cross_compiling" = yes; then RUNSHARED= fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking HOSTRUNNER" >&5 +printf %s "checking HOSTRUNNER... " >&6; } if test -z "$HOSTRUNNER" then @@ -7574,8 +7578,6 @@ fi esac fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking HOSTRUNNER" >&5 -printf %s "checking HOSTRUNNER... " >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $HOSTRUNNER" >&5 printf "%s\n" "$HOSTRUNNER" >&6; } @@ -7583,9 +7585,6 @@ if test -n "$HOSTRUNNER"; then PYTHON_FOR_BUILD="_PYTHON_HOSTRUNNER='$HOSTRUNNER' $PYTHON_FOR_BUILD" fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $LDLIBRARY" >&5 -printf "%s\n" "$LDLIBRARY" >&6; } - # LIBRARY_DEPS, LINK_PYTHON_OBJS and LINK_PYTHON_DEPS variable case $ac_sys_system/$ac_sys_emscripten_target in #( Emscripten/browser*) : @@ -16755,16 +16754,18 @@ printf "%s\n" "$ipv6type" >&6; } fi if test "$ipv6" = "yes" -a "$ipv6lib" != "none"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking ipv6 library" >&5 +printf %s "checking ipv6 library... " >&6; } if test -d $ipv6libdir -a -f $ipv6libdir/lib$ipv6lib.a; then LIBS="-L$ipv6libdir -l$ipv6lib $LIBS" - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: using lib$ipv6lib" >&5 -printf "%s\n" "$as_me: using lib$ipv6lib" >&6;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: lib$ipv6lib" >&5 +printf "%s\n" "lib$ipv6lib" >&6; } else if test "x$ipv6trylibc" = xyes then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: using libc" >&5 -printf "%s\n" "$as_me: using libc" >&6;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: libc" >&5 +printf "%s\n" "libc" >&6; } else $as_nop diff --git a/configure.ac b/configure.ac index 5b655e57ef315b5..f6df9b8bb41cc98 100644 --- a/configure.ac +++ b/configure.ac @@ -1415,11 +1415,13 @@ else # shared is disabled ;; esac fi +AC_MSG_RESULT([$LDLIBRARY]) if test "$cross_compiling" = yes; then RUNSHARED= fi +AC_MSG_CHECKING([HOSTRUNNER]) AC_ARG_VAR([HOSTRUNNER], [Program to run CPython for the host platform]) if test -z "$HOSTRUNNER" then @@ -1465,7 +1467,6 @@ then ) fi AC_SUBST([HOSTRUNNER]) -AC_MSG_CHECKING([HOSTRUNNER]) AC_MSG_RESULT([$HOSTRUNNER]) if test -n "$HOSTRUNNER"; then @@ -1473,8 +1474,6 @@ if test -n "$HOSTRUNNER"; then PYTHON_FOR_BUILD="_PYTHON_HOSTRUNNER='$HOSTRUNNER' $PYTHON_FOR_BUILD" fi -AC_MSG_RESULT([$LDLIBRARY]) - # LIBRARY_DEPS, LINK_PYTHON_OBJS and LINK_PYTHON_DEPS variable AS_CASE([$ac_sys_system/$ac_sys_emscripten_target], [Emscripten/browser*], [LIBRARY_DEPS='$(PY3LIBRARY) $(WASM_STDLIB) python.html python.worker.js'], @@ -4522,12 +4521,13 @@ yes fi if test "$ipv6" = "yes" -a "$ipv6lib" != "none"; then + AC_MSG_CHECKING([ipv6 library]) if test -d $ipv6libdir -a -f $ipv6libdir/lib$ipv6lib.a; then LIBS="-L$ipv6libdir -l$ipv6lib $LIBS" - AC_MSG_NOTICE([using lib$ipv6lib]) + AC_MSG_RESULT([lib$ipv6lib]) else AS_VAR_IF([ipv6trylibc], [yes], [ - AC_MSG_NOTICE([using libc]) + AC_MSG_RESULT([libc]) ], [ AC_MSG_ERROR([m4_normalize([ No $ipv6lib library found; cannot continue. From acd6f41ecf9987c21c3d238d5496b17857c05482 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Fri, 23 Feb 2024 13:35:27 +0200 Subject: [PATCH 424/507] gh-111789: Use PyDict_GetItemRef() in Python/compile.c (GH-112083) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Łukasz Langa <lukasz@langa.pl> --- Python/compile.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index d857239690e7b54..6b17f3bcaf22644 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -921,11 +921,10 @@ dict_add_o(PyObject *dict, PyObject *o) PyObject *v; Py_ssize_t arg; - v = PyDict_GetItemWithError(dict, o); + if (PyDict_GetItemRef(dict, o, &v) < 0) { + return ERROR; + } if (!v) { - if (PyErr_Occurred()) { - return ERROR; - } arg = PyDict_GET_SIZE(dict); v = PyLong_FromSsize_t(arg); if (!v) { @@ -935,10 +934,10 @@ dict_add_o(PyObject *dict, PyObject *o) Py_DECREF(v); return ERROR; } - Py_DECREF(v); } else arg = PyLong_AsLong(v); + Py_DECREF(v); return arg; } From 2ec50b4a66de6be418bfad058117249d4775df0f Mon Sep 17 00:00:00 2001 From: Ken Jin <kenjin@python.org> Date: Fri, 23 Feb 2024 23:41:10 +0800 Subject: [PATCH 425/507] gh-114058: Improve method information in redundancy eliminator (GH-115848) --- Python/tier2_redundancy_eliminator_bytecodes.c | 10 +++++++--- Python/tier2_redundancy_eliminator_cases.c.h | 10 +++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Python/tier2_redundancy_eliminator_bytecodes.c b/Python/tier2_redundancy_eliminator_bytecodes.c index ef7b43d53539ce6..b9afd3089e10778 100644 --- a/Python/tier2_redundancy_eliminator_bytecodes.c +++ b/Python/tier2_redundancy_eliminator_bytecodes.c @@ -296,21 +296,25 @@ dummy_func(void) { } op(_LOAD_ATTR_METHOD_WITH_VALUES, (descr/4, owner -- attr, self if (1))) { + (void)descr; OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); - OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + self = owner; } op(_LOAD_ATTR_METHOD_NO_DICT, (descr/4, owner -- attr, self if (1))) { + (void)descr; OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); - OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + self = owner; } op(_LOAD_ATTR_METHOD_LAZY_DICT, (descr/4, owner -- attr, self if (1))) { + (void)descr; OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); - OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + self = owner; } op(_INIT_CALL_BOUND_METHOD_EXACT_ARGS, (callable, unused, unused[oparg] -- func, self, unused[oparg])) { + (void)callable; OUT_OF_SPACE_IF_NULL(func = sym_new_known_notnull(ctx)); OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); } diff --git a/Python/tier2_redundancy_eliminator_cases.c.h b/Python/tier2_redundancy_eliminator_cases.c.h index ca9b5953d21012f..ca341e4dde5d93b 100644 --- a/Python/tier2_redundancy_eliminator_cases.c.h +++ b/Python/tier2_redundancy_eliminator_cases.c.h @@ -1305,8 +1305,9 @@ _Py_UOpsSymType *self = NULL; owner = stack_pointer[-1]; PyObject *descr = (PyObject *)this_instr->operand; + (void)descr; OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); - OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + self = owner; stack_pointer[-1] = attr; stack_pointer[0] = self; stack_pointer += 1; @@ -1319,8 +1320,9 @@ _Py_UOpsSymType *self = NULL; owner = stack_pointer[-1]; PyObject *descr = (PyObject *)this_instr->operand; + (void)descr; OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); - OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + self = owner; stack_pointer[-1] = attr; stack_pointer[0] = self; stack_pointer += 1; @@ -1353,8 +1355,9 @@ _Py_UOpsSymType *self = NULL; owner = stack_pointer[-1]; PyObject *descr = (PyObject *)this_instr->operand; + (void)descr; OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); - OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + self = owner; stack_pointer[-1] = attr; stack_pointer[0] = self; stack_pointer += 1; @@ -1380,6 +1383,7 @@ _Py_UOpsSymType *func; _Py_UOpsSymType *self; callable = stack_pointer[-2 - oparg]; + (void)callable; OUT_OF_SPACE_IF_NULL(func = sym_new_known_notnull(ctx)); OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); stack_pointer[-2 - oparg] = func; From 59057ce55a443f35bfd685c688071aebad7b3671 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado <Pablogsal@gmail.com> Date: Fri, 23 Feb 2024 17:13:45 +0100 Subject: [PATCH 426/507] gh-89480: Document the gdb helpers (GH-115657) Content adapted from https://devguide.python.org/development-tools/gdb/# and https://wiki.python.org/moin/DebuggingWithGdb. The original content on the Wiki page came from gdb debug help used by the Launchpad (https://launchpad.net/) team. Thanks to Anatoly Techtonik and user `rmf` for substantial improvements to the Wiki page. The history of the Devguide page follows (with log entries expanded for major content contributions): Hugo van Kemenade, Sat Dec 30 21:22:04 2023 +0200 Hugo van Kemenade, Fri Dec 8 12:04:32 2023 +0200 Erlend E. Aasland & Hugo van Kemenade, Tue Aug 8 22:05:34 2023 +0200 Satish Mishra, Sat Feb 11 13:54:57 2023 +0530 Hugo van Kemenade, Fri Dec 23 17:33:33 2022 +0200 Skip Montanaro, Hugo, Erlend, & Ezio, Fri Nov 4 05:04:23 2022 -0500 Add a GDB tips section to Advanced Tools (#977) Adam Turner, Wed Jun 15 21:19:23 2022 +0100 Adam Turner, Tue Jun 14 11:12:26 2022 +0100 Suriyaa, Fri Jun 8 19:39:23 2018 +0200 Jeff Allen, Tue Oct 24 18:12:53 2017 +0100 Jeff Allen, Fri Oct 13 13:43:43 2017 +0100 Mariatta, Wed Jan 4 09:14:55 2017 -0800 Carol Willing, Mon Sep 26 14:50:54 2016 -0700 Zachary Ware, Thu Jul 21 10:42:23 2016 -0500 Georg Brandl, Mon Nov 3 11:28:19 2014 +0100 Add instruction how to activate python-gdb.py Georg Brandl, Sun Mar 9 10:32:01 2014 +0100 Georg Brandl, Tue Apr 3 09:12:53 2012 +0200 Georg Brandl, Sat Mar 5 17:32:35 2011 +0100 Dave Malcolm, Fri Jan 21 12:34:09 2011 -0500 Add documentation on the gdb extension commands provided in libpython.py I adapted this from documentation I wrote for the Fedora wiki: https://fedoraproject.org/wiki/Features/EasierPythonDebugging#New_gdb_commands reformatting it as rst, and making other minor changes Brett Cannon, Thu Jan 20 15:16:52 2011 -0800 Dave Malcolm, Thu Jan 20 16:17:23 2011 -0500 Add some notes on the gdb pretty-printer hooks Antoine Pitrou, Thu Jan 20 21:17:49 2011 +0100 Give an example backtrace Antoine Pitrou, Thu Jan 20 21:03:06 2011 +0100 Expand explanations about gdb support Brett Cannon, Thu Jan 20 11:33:36 2011 -0800 Tweak the gdb support title to fit in better with the devguide. Brett Cannon, Mon Jan 17 21:12:54 2011 +0000 Short README on gdb support. Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com> Co-authored-by: anatoly techtonik <techtonik@gmail.com> Co-authored-by: Antoine Pitrou <solipsis@pitrou.net> Co-authored-by: Brett Cannon <brett@python.org> Co-authored-by: Carol Willing <carolcode@willingconsulting.com> Co-authored-by: Dave Malcolm <dmalcolm@redhat.com> Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com> Co-authored-by: Ezio Melotti <ezio.melotti@gmail.com> Co-authored-by: Georg Brandl <georg@python.org> Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Co-authored-by: Jeff Allen <ja.py@farowl.co.uk> Co-authored-by: Mariatta <Mariatta@users.noreply.github.com> Co-authored-by: Petr Viktorin <encukou@gmail.com> Co-authored-by: Satish Mishra <7506satish@gmail.com> Co-authored-by: Skip Montanaro <skip.montanaro@gmail.com> Co-authored-by: Suriyaa <isc.suriyaa@gmail.com> Co-authored-by: Zachary Ware <zachary.ware@gmail.com> --- Doc/howto/gdb_helpers.rst | 449 ++++++++++++++++++++++++++++++++++++++ Doc/howto/index.rst | 1 + 2 files changed, 450 insertions(+) create mode 100644 Doc/howto/gdb_helpers.rst diff --git a/Doc/howto/gdb_helpers.rst b/Doc/howto/gdb_helpers.rst new file mode 100644 index 000000000000000..53bbf7ddaa2ab96 --- /dev/null +++ b/Doc/howto/gdb_helpers.rst @@ -0,0 +1,449 @@ +.. _gdb: + +========================================================= +Debugging C API extensions and CPython Internals with GDB +========================================================= + +.. highlight:: none + +This document explains how the Python GDB extension, ``python-gdb.py``, can +be used with the GDB debugger to debug CPython extensions and the +CPython interpreter itself. + +When debugging low-level problems such as crashes or deadlocks, a low-level +debugger, such as GDB, is useful to diagnose and correct the issue. +By default, GDB (or any of its front-ends) doesn't support high-level +information specific to the CPython interpreter. + +The ``python-gdb.py`` extension adds CPython interpreter information to GDB. +The extension helps introspect the stack of currently executing Python functions. +Given a Python object represented by a :c:expr:`PyObject *` pointer, +the extension surfaces the type and value of the object. + +Developers who are working on CPython extensions or tinkering with parts +of CPython that are written in C can use this document to learn how to use the +``python-gdb.py`` extension with GDB. + +.. note:: + + This document assumes that you are familiar with the basics of GDB and the + CPython C API. It consolidates guidance from the + `devguide <https://devguide.python.org>`_ and the + `Python wiki <https://wiki.python.org/moin/DebuggingWithGdb>`_. + + +Prerequisites +============= + +You need to have: + +- GDB 7 or later. (For earlier versions of GDB, see ``Misc/gdbinit`` in the + sources of Python 3.11 or earlier.) +- GDB-compatible debugging information for Python and any extension you are + debugging. +- The ``python-gdb.py`` extension. + +The extension is built with Python, but might be distributed separately or +not at all. Below, we include tips for a few common systems as examples. +Note that even if the instructions match your system, they might be outdated. + + +Setup with Python built from source +----------------------------------- + +When you build CPython from source, debugging information should be available, +and the build should add a ``python-gdb.py`` file to the root directory of +your repository. + +To activate support, you must add the directory containing ``python-gdb.py`` +to GDB's "auto-load-safe-path". +If you haven't done this, recent versions of GDB will print out a warning +with instructions on how to do this. + +.. note:: + + If you do not see instructions for your version of GDB, put this in your + configuration file (``~/.gdbinit`` or ``~/.config/gdb/gdbinit``):: + + add-auto-load-safe-path /path/to/cpython + + You can also add multiple paths, separated by ``:``. + + +Setup for Python from a Linux distro +------------------------------------ + +Most Linux systems provide debug information for the system Python +in a package called ``python-debuginfo``, ``python-dbg`` or similar. +For example: + +- Fedora: + + .. code-block:: shell + + sudo dnf install gdb + sudo dnf debuginfo-install python3 + +- Ubuntu: + + .. code-block:: shell + + sudo apt install gdb python3-dbg + +On several recent Linux systems, GDB can download debugging symbols +automatically using *debuginfod*. +However, this will not install the ``python-gdb.py`` extension; +you generally do need to install the debug info package separately. + + +Using the Debug build and Development mode +========================================== + +For easier debugging, you might want to: + +- Use a :ref:`debug build <debug-build>` of Python. (When building from source, + use ``configure --with-pydebug``. On Linux distros, install and run a package + like ``python-debug`` or ``python-dbg``, if available.) +- Use the runtime :ref:`development mode <devmode>` (``-X dev``). + +Both enable extra assertions and disable some optimizations. +Sometimes this hides the bug you are trying to find, but in most cases they +make the process easier. + + +Using the ``python-gdb`` extension +================================== + +When the extension is loaded, it provides two main features: +pretty printers for Python values, and additional commands. + +Pretty-printers +--------------- + +This is what a GDB backtrace looks like (truncated) when this extension is +enabled:: + + #0 0x000000000041a6b1 in PyObject_Malloc (nbytes=Cannot access memory at address 0x7fffff7fefe8 + ) at Objects/obmalloc.c:748 + #1 0x000000000041b7c0 in _PyObject_DebugMallocApi (id=111 'o', nbytes=24) at Objects/obmalloc.c:1445 + #2 0x000000000041b717 in _PyObject_DebugMalloc (nbytes=24) at Objects/obmalloc.c:1412 + #3 0x000000000044060a in _PyUnicode_New (length=11) at Objects/unicodeobject.c:346 + #4 0x00000000004466aa in PyUnicodeUCS2_DecodeUTF8Stateful (s=0x5c2b8d "__lltrace__", size=11, errors=0x0, consumed= + 0x0) at Objects/unicodeobject.c:2531 + #5 0x0000000000446647 in PyUnicodeUCS2_DecodeUTF8 (s=0x5c2b8d "__lltrace__", size=11, errors=0x0) + at Objects/unicodeobject.c:2495 + #6 0x0000000000440d1b in PyUnicodeUCS2_FromStringAndSize (u=0x5c2b8d "__lltrace__", size=11) + at Objects/unicodeobject.c:551 + #7 0x0000000000440d94 in PyUnicodeUCS2_FromString (u=0x5c2b8d "__lltrace__") at Objects/unicodeobject.c:569 + #8 0x0000000000584abd in PyDict_GetItemString (v= + {'Yuck': <type at remote 0xad4730>, '__builtins__': <module at remote 0x7ffff7fd5ee8>, '__file__': 'Lib/test/crashers/nasty_eq_vs_dict.py', '__package__': None, 'y': <Yuck(i=0) at remote 0xaacd80>, 'dict': {0: 0, 1: 1, 2: 2, 3: 3}, '__cached__': None, '__name__': '__main__', 'z': <Yuck(i=0) at remote 0xaace60>, '__doc__': None}, key= + 0x5c2b8d "__lltrace__") at Objects/dictobject.c:2171 + +Notice how the dictionary argument to ``PyDict_GetItemString`` is displayed +as its ``repr()``, rather than an opaque ``PyObject *`` pointer. + +The extension works by supplying a custom printing routine for values of type +``PyObject *``. If you need to access lower-level details of an object, then +cast the value to a pointer of the appropriate type. For example:: + + (gdb) p globals + $1 = {'__builtins__': <module at remote 0x7ffff7fb1868>, '__name__': + '__main__', 'ctypes': <module at remote 0x7ffff7f14360>, '__doc__': None, + '__package__': None} + + (gdb) p *(PyDictObject*)globals + $2 = {ob_refcnt = 3, ob_type = 0x3dbdf85820, ma_fill = 5, ma_used = 5, + ma_mask = 7, ma_table = 0x63d0f8, ma_lookup = 0x3dbdc7ea70 + <lookdict_string>, ma_smalltable = {{me_hash = 7065186196740147912, + me_key = '__builtins__', me_value = <module at remote 0x7ffff7fb1868>}, + {me_hash = -368181376027291943, me_key = '__name__', + me_value ='__main__'}, {me_hash = 0, me_key = 0x0, me_value = 0x0}, + {me_hash = 0, me_key = 0x0, me_value = 0x0}, + {me_hash = -9177857982131165996, me_key = 'ctypes', + me_value = <module at remote 0x7ffff7f14360>}, + {me_hash = -8518757509529533123, me_key = '__doc__', me_value = None}, + {me_hash = 0, me_key = 0x0, me_value = 0x0}, { + me_hash = 6614918939584953775, me_key = '__package__', me_value = None}}} + +Note that the pretty-printers do not actually call ``repr()``. +For basic types, they try to match its result closely. + +An area that can be confusing is that the custom printer for some types look a +lot like GDB's built-in printer for standard types. For example, the +pretty-printer for a Python ``int`` (:c:expr:`PyLongObject *`) +gives a representation that is not distinguishable from one of a +regular machine-level integer:: + + (gdb) p some_machine_integer + $3 = 42 + + (gdb) p some_python_integer + $4 = 42 + +The internal structure can be revealed with a cast to :c:expr:`PyLongObject *`: + + (gdb) p *(PyLongObject*)some_python_integer + $5 = {ob_base = {ob_base = {ob_refcnt = 8, ob_type = 0x3dad39f5e0}, ob_size = 1}, + ob_digit = {42}} + +A similar confusion can arise with the ``str`` type, where the output looks a +lot like gdb's built-in printer for ``char *``:: + + (gdb) p ptr_to_python_str + $6 = '__builtins__' + +The pretty-printer for ``str`` instances defaults to using single-quotes (as +does Python's ``repr`` for strings) whereas the standard printer for ``char *`` +values uses double-quotes and contains a hexadecimal address:: + + (gdb) p ptr_to_char_star + $7 = 0x6d72c0 "hello world" + +Again, the implementation details can be revealed with a cast to +:c:expr:`PyUnicodeObject *`:: + + (gdb) p *(PyUnicodeObject*)$6 + $8 = {ob_base = {ob_refcnt = 33, ob_type = 0x3dad3a95a0}, length = 12, + str = 0x7ffff2128500, hash = 7065186196740147912, state = 1, defenc = 0x0} + +``py-list`` +----------- + + The extension adds a ``py-list`` command, which + lists the Python source code (if any) for the current frame in the selected + thread. The current line is marked with a ">":: + + (gdb) py-list + 901 if options.profile: + 902 options.profile = False + 903 profile_me() + 904 return + 905 + >906 u = UI() + 907 if not u.quit: + 908 try: + 909 gtk.main() + 910 except KeyboardInterrupt: + 911 # properly quit on a keyboard interrupt... + + Use ``py-list START`` to list at a different line number within the Python + source, and ``py-list START,END`` to list a specific range of lines within + the Python source. + +``py-up`` and ``py-down`` +------------------------- + + The ``py-up`` and ``py-down`` commands are analogous to GDB's regular ``up`` + and ``down`` commands, but try to move at the level of CPython frames, rather + than C frames. + + GDB is not always able to read the relevant frame information, depending on + the optimization level with which CPython was compiled. Internally, the + commands look for C frames that are executing the default frame evaluation + function (that is, the core bytecode interpreter loop within CPython) and + look up the value of the related ``PyFrameObject *``. + + They emit the frame number (at the C level) within the thread. + + For example:: + + (gdb) py-up + #37 Frame 0x9420b04, for file /usr/lib/python2.6/site-packages/ + gnome_sudoku/main.py, line 906, in start_game () + u = UI() + (gdb) py-up + #40 Frame 0x948e82c, for file /usr/lib/python2.6/site-packages/ + gnome_sudoku/gnome_sudoku.py, line 22, in start_game(main=<module at remote 0xb771b7f4>) + main.start_game() + (gdb) py-up + Unable to find an older python frame + + so we're at the top of the Python stack. + + The frame numbers correspond to those displayed by GDB's standard + ``backtrace`` command. + The command skips C frames which are not executing Python code. + + Going back down:: + + (gdb) py-down + #37 Frame 0x9420b04, for file /usr/lib/python2.6/site-packages/gnome_sudoku/main.py, line 906, in start_game () + u = UI() + (gdb) py-down + #34 (unable to read python frame information) + (gdb) py-down + #23 (unable to read python frame information) + (gdb) py-down + #19 (unable to read python frame information) + (gdb) py-down + #14 Frame 0x99262ac, for file /usr/lib/python2.6/site-packages/gnome_sudoku/game_selector.py, line 201, in run_swallowed_dialog (self=<NewOrSavedGameSelector(new_game_model=<gtk.ListStore at remote 0x98fab44>, puzzle=None, saved_games=[{'gsd.auto_fills': 0, 'tracking': {}, 'trackers': {}, 'notes': [], 'saved_at': 1270084485, 'game': '7 8 0 0 0 0 0 5 6 0 0 9 0 8 0 1 0 0 0 4 6 0 0 0 0 7 0 6 5 0 0 0 4 7 9 2 0 0 0 9 0 1 0 0 0 3 9 7 6 0 0 0 1 8 0 6 0 0 0 0 2 8 0 0 0 5 0 4 0 6 0 0 2 1 0 0 0 0 0 4 5\n7 8 0 0 0 0 0 5 6 0 0 9 0 8 0 1 0 0 0 4 6 0 0 0 0 7 0 6 5 1 8 3 4 7 9 2 0 0 0 9 0 1 0 0 0 3 9 7 6 0 0 0 1 8 0 6 0 0 0 0 2 8 0 0 0 5 0 4 0 6 0 0 2 1 0 0 0 0 0 4 5', 'gsd.impossible_hints': 0, 'timer.__absolute_start_time__': <float at remote 0x984b474>, 'gsd.hints': 0, 'timer.active_time': <float at remote 0x984b494>, 'timer.total_time': <float at remote 0x984b464>}], dialog=<gtk.Dialog at remote 0x98faaa4>, saved_game_model=<gtk.ListStore at remote 0x98fad24>, sudoku_maker=<SudokuMaker(terminated=False, played=[], batch_siz...(truncated) + swallower.run_dialog(self.dialog) + (gdb) py-down + #11 Frame 0x9aead74, for file /usr/lib/python2.6/site-packages/gnome_sudoku/dialog_swallower.py, line 48, in run_dialog (self=<SwappableArea(running=<gtk.Dialog at remote 0x98faaa4>, main_page=0) at remote 0x98fa6e4>, d=<gtk.Dialog at remote 0x98faaa4>) + gtk.main() + (gdb) py-down + #8 (unable to read python frame information) + (gdb) py-down + Unable to find a newer python frame + + and we're at the bottom of the Python stack. + + Note that in Python 3.12 and newer, the same C stack frame can be used for + multiple Python stack frames. This means that ``py-up`` and ``py-down`` + may move multiple Python frames at once. For example:: + + (gdb) py-up + #6 Frame 0x7ffff7fb62b0, for file /tmp/rec.py, line 5, in recursive_function (n=0) + time.sleep(5) + #6 Frame 0x7ffff7fb6240, for file /tmp/rec.py, line 7, in recursive_function (n=1) + recursive_function(n-1) + #6 Frame 0x7ffff7fb61d0, for file /tmp/rec.py, line 7, in recursive_function (n=2) + recursive_function(n-1) + #6 Frame 0x7ffff7fb6160, for file /tmp/rec.py, line 7, in recursive_function (n=3) + recursive_function(n-1) + #6 Frame 0x7ffff7fb60f0, for file /tmp/rec.py, line 7, in recursive_function (n=4) + recursive_function(n-1) + #6 Frame 0x7ffff7fb6080, for file /tmp/rec.py, line 7, in recursive_function (n=5) + recursive_function(n-1) + #6 Frame 0x7ffff7fb6020, for file /tmp/rec.py, line 9, in <module> () + recursive_function(5) + (gdb) py-up + Unable to find an older python frame + + +``py-bt`` +--------- + + The ``py-bt`` command attempts to display a Python-level backtrace of the + current thread. + + For example:: + + (gdb) py-bt + #8 (unable to read python frame information) + #11 Frame 0x9aead74, for file /usr/lib/python2.6/site-packages/gnome_sudoku/dialog_swallower.py, line 48, in run_dialog (self=<SwappableArea(running=<gtk.Dialog at remote 0x98faaa4>, main_page=0) at remote 0x98fa6e4>, d=<gtk.Dialog at remote 0x98faaa4>) + gtk.main() + #14 Frame 0x99262ac, for file /usr/lib/python2.6/site-packages/gnome_sudoku/game_selector.py, line 201, in run_swallowed_dialog (self=<NewOrSavedGameSelector(new_game_model=<gtk.ListStore at remote 0x98fab44>, puzzle=None, saved_games=[{'gsd.auto_fills': 0, 'tracking': {}, 'trackers': {}, 'notes': [], 'saved_at': 1270084485, 'game': '7 8 0 0 0 0 0 5 6 0 0 9 0 8 0 1 0 0 0 4 6 0 0 0 0 7 0 6 5 0 0 0 4 7 9 2 0 0 0 9 0 1 0 0 0 3 9 7 6 0 0 0 1 8 0 6 0 0 0 0 2 8 0 0 0 5 0 4 0 6 0 0 2 1 0 0 0 0 0 4 5\n7 8 0 0 0 0 0 5 6 0 0 9 0 8 0 1 0 0 0 4 6 0 0 0 0 7 0 6 5 1 8 3 4 7 9 2 0 0 0 9 0 1 0 0 0 3 9 7 6 0 0 0 1 8 0 6 0 0 0 0 2 8 0 0 0 5 0 4 0 6 0 0 2 1 0 0 0 0 0 4 5', 'gsd.impossible_hints': 0, 'timer.__absolute_start_time__': <float at remote 0x984b474>, 'gsd.hints': 0, 'timer.active_time': <float at remote 0x984b494>, 'timer.total_time': <float at remote 0x984b464>}], dialog=<gtk.Dialog at remote 0x98faaa4>, saved_game_model=<gtk.ListStore at remote 0x98fad24>, sudoku_maker=<SudokuMaker(terminated=False, played=[], batch_siz...(truncated) + swallower.run_dialog(self.dialog) + #19 (unable to read python frame information) + #23 (unable to read python frame information) + #34 (unable to read python frame information) + #37 Frame 0x9420b04, for file /usr/lib/python2.6/site-packages/gnome_sudoku/main.py, line 906, in start_game () + u = UI() + #40 Frame 0x948e82c, for file /usr/lib/python2.6/site-packages/gnome_sudoku/gnome_sudoku.py, line 22, in start_game (main=<module at remote 0xb771b7f4>) + main.start_game() + + The frame numbers correspond to those displayed by GDB's standard + ``backtrace`` command. + +``py-print`` +------------ + + The ``py-print`` command looks up a Python name and tries to print it. + It looks in locals within the current thread, then globals, then finally + builtins:: + + (gdb) py-print self + local 'self' = <SwappableArea(running=<gtk.Dialog at remote 0x98faaa4>, + main_page=0) at remote 0x98fa6e4> + (gdb) py-print __name__ + global '__name__' = 'gnome_sudoku.dialog_swallower' + (gdb) py-print len + builtin 'len' = <built-in function len> + (gdb) py-print scarlet_pimpernel + 'scarlet_pimpernel' not found + + If the current C frame corresponds to multiple Python frames, ``py-print`` + only considers the first one. + +``py-locals`` +------------- + + The ``py-locals`` command looks up all Python locals within the current + Python frame in the selected thread, and prints their representations:: + + (gdb) py-locals + self = <SwappableArea(running=<gtk.Dialog at remote 0x98faaa4>, + main_page=0) at remote 0x98fa6e4> + d = <gtk.Dialog at remote 0x98faaa4> + + If the current C frame corresponds to multiple Python frames, locals from + all of them will be shown:: + + (gdb) py-locals + Locals for recursive_function + n = 0 + Locals for recursive_function + n = 1 + Locals for recursive_function + n = 2 + Locals for recursive_function + n = 3 + Locals for recursive_function + n = 4 + Locals for recursive_function + n = 5 + Locals for <module> + + +Use with GDB commands +===================== + +The extension commands complement GDB's built-in commands. +For example, you can use a frame numbers shown by ``py-bt`` with the ``frame`` +command to go a specific frame within the selected thread, like this:: + + (gdb) py-bt + (output snipped) + #68 Frame 0xaa4560, for file Lib/test/regrtest.py, line 1548, in <module> () + main() + (gdb) frame 68 + #68 0x00000000004cd1e6 in PyEval_EvalFrameEx (f=Frame 0xaa4560, for file Lib/test/regrtest.py, line 1548, in <module> (), throwflag=0) at Python/ceval.c:2665 + 2665 x = call_function(&sp, oparg); + (gdb) py-list + 1543 # Run the tests in a context manager that temporary changes the CWD to a + 1544 # temporary and writable directory. If it's not possible to create or + 1545 # change the CWD, the original CWD will be used. The original CWD is + 1546 # available from test_support.SAVEDCWD. + 1547 with test_support.temp_cwd(TESTCWD, quiet=True): + >1548 main() + +The ``info threads`` command will give you a list of the threads within the +process, and you can use the ``thread`` command to select a different one:: + + (gdb) info threads + 105 Thread 0x7fffefa18710 (LWP 10260) sem_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86 + 104 Thread 0x7fffdf5fe710 (LWP 10259) sem_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86 + * 1 Thread 0x7ffff7fe2700 (LWP 10145) 0x00000038e46d73e3 in select () at ../sysdeps/unix/syscall-template.S:82 + +You can use ``thread apply all COMMAND`` or (``t a a COMMAND`` for short) to run +a command on all threads. With ``py-bt``, this lets you see what every +thread is doing at the Python level:: + + (gdb) t a a py-bt + + Thread 105 (Thread 0x7fffefa18710 (LWP 10260)): + #5 Frame 0x7fffd00019d0, for file /home/david/coding/python-svn/Lib/threading.py, line 155, in _acquire_restore (self=<_RLock(_Verbose__verbose=False, _RLock__owner=140737354016512, _RLock__block=<thread.lock at remote 0x858770>, _RLock__count=1) at remote 0xd7ff40>, count_owner=(1, 140737213728528), count=1, owner=140737213728528) + self.__block.acquire() + #8 Frame 0x7fffac001640, for file /home/david/coding/python-svn/Lib/threading.py, line 269, in wait (self=<_Condition(_Condition__lock=<_RLock(_Verbose__verbose=False, _RLock__owner=140737354016512, _RLock__block=<thread.lock at remote 0x858770>, _RLock__count=1) at remote 0xd7ff40>, acquire=<instancemethod at remote 0xd80260>, _is_owned=<instancemethod at remote 0xd80160>, _release_save=<instancemethod at remote 0xd803e0>, release=<instancemethod at remote 0xd802e0>, _acquire_restore=<instancemethod at remote 0xd7ee60>, _Verbose__verbose=False, _Condition__waiters=[]) at remote 0xd7fd10>, timeout=None, waiter=<thread.lock at remote 0x858a90>, saved_state=(1, 140737213728528)) + self._acquire_restore(saved_state) + #12 Frame 0x7fffb8001a10, for file /home/david/coding/python-svn/Lib/test/lock_tests.py, line 348, in f () + cond.wait() + #16 Frame 0x7fffb8001c40, for file /home/david/coding/python-svn/Lib/test/lock_tests.py, line 37, in task (tid=140737213728528) + f() + + Thread 104 (Thread 0x7fffdf5fe710 (LWP 10259)): + #5 Frame 0x7fffe4001580, for file /home/david/coding/python-svn/Lib/threading.py, line 155, in _acquire_restore (self=<_RLock(_Verbose__verbose=False, _RLock__owner=140737354016512, _RLock__block=<thread.lock at remote 0x858770>, _RLock__count=1) at remote 0xd7ff40>, count_owner=(1, 140736940992272), count=1, owner=140736940992272) + self.__block.acquire() + #8 Frame 0x7fffc8002090, for file /home/david/coding/python-svn/Lib/threading.py, line 269, in wait (self=<_Condition(_Condition__lock=<_RLock(_Verbose__verbose=False, _RLock__owner=140737354016512, _RLock__block=<thread.lock at remote 0x858770>, _RLock__count=1) at remote 0xd7ff40>, acquire=<instancemethod at remote 0xd80260>, _is_owned=<instancemethod at remote 0xd80160>, _release_save=<instancemethod at remote 0xd803e0>, release=<instancemethod at remote 0xd802e0>, _acquire_restore=<instancemethod at remote 0xd7ee60>, _Verbose__verbose=False, _Condition__waiters=[]) at remote 0xd7fd10>, timeout=None, waiter=<thread.lock at remote 0x858860>, saved_state=(1, 140736940992272)) + self._acquire_restore(saved_state) + #12 Frame 0x7fffac001c90, for file /home/david/coding/python-svn/Lib/test/lock_tests.py, line 348, in f () + cond.wait() + #16 Frame 0x7fffac0011c0, for file /home/david/coding/python-svn/Lib/test/lock_tests.py, line 37, in task (tid=140736940992272) + f() + + Thread 1 (Thread 0x7ffff7fe2700 (LWP 10145)): + #5 Frame 0xcb5380, for file /home/david/coding/python-svn/Lib/test/lock_tests.py, line 16, in _wait () + time.sleep(0.01) + #8 Frame 0x7fffd00024a0, for file /home/david/coding/python-svn/Lib/test/lock_tests.py, line 378, in _check_notify (self=<ConditionTests(_testMethodName='test_notify', _resultForDoCleanups=<TestResult(_original_stdout=<cStringIO.StringO at remote 0xc191e0>, skipped=[], _mirrorOutput=False, testsRun=39, buffer=False, _original_stderr=<file at remote 0x7ffff7fc6340>, _stdout_buffer=<cStringIO.StringO at remote 0xc9c7f8>, _stderr_buffer=<cStringIO.StringO at remote 0xc9c790>, _moduleSetUpFailed=False, expectedFailures=[], errors=[], _previousTestClass=<type at remote 0x928310>, unexpectedSuccesses=[], failures=[], shouldStop=False, failfast=False) at remote 0xc185a0>, _threads=(0,), _cleanups=[], _type_equality_funcs={<type at remote 0x7eba00>: <instancemethod at remote 0xd750e0>, <type at remote 0x7e7820>: <instancemethod at remote 0xd75160>, <type at remote 0x7e30e0>: <instancemethod at remote 0xd75060>, <type at remote 0x7e7d20>: <instancemethod at remote 0xd751e0>, <type at remote 0x7f19e0...(truncated) + _wait() diff --git a/Doc/howto/index.rst b/Doc/howto/index.rst index bb507953582639c..8b334555ab64633 100644 --- a/Doc/howto/index.rst +++ b/Doc/howto/index.rst @@ -16,6 +16,7 @@ Currently, the HOWTOs are: cporting.rst curses.rst descriptor.rst + gdb_helpers.rst enum.rst functional.rst logging.rst From e4561e050148f6dc347e8c7ba30c8125b5fc0e45 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Fri, 23 Feb 2024 19:31:57 +0200 Subject: [PATCH 427/507] gh-115778: Add `tierN` annotation for instruction definitions (#115815) This replaces the old `TIER_{ONE,TWO}_ONLY` macros. Note that `specialized` implies `tier1`. Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> --- ...-02-22-11-33-20.gh-issue-115778.jksd1D.rst | 1 + Python/bytecodes.c | 118 ++++++------------ Python/ceval_macros.h | 6 - Python/executor_cases.c.h | 13 -- Python/generated_cases.c.h | 35 ------ Tools/cases_generator/analyzer.py | 15 ++- .../cases_generator/interpreter_definition.md | 1 + Tools/cases_generator/lexer.py | 2 + .../opcode_metadata_generator.py | 2 +- .../tier2_abstract_generator.py | 2 +- Tools/cases_generator/tier2_generator.py | 2 +- Tools/cases_generator/uop_id_generator.py | 2 +- .../cases_generator/uop_metadata_generator.py | 4 +- 13 files changed, 56 insertions(+), 147 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-22-11-33-20.gh-issue-115778.jksd1D.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-22-11-33-20.gh-issue-115778.jksd1D.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-22-11-33-20.gh-issue-115778.jksd1D.rst new file mode 100644 index 000000000000000..023f6aa3026733e --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-22-11-33-20.gh-issue-115778.jksd1D.rst @@ -0,0 +1 @@ +Add ``tierN`` annotation for instruction definition in interpreter DSL. diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 7e2c9c4d6a6db46..e9e9425f826a2d7 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -141,8 +141,7 @@ dummy_func( RESUME_CHECK, }; - inst(RESUME, (--)) { - TIER_ONE_ONLY + tier1 inst(RESUME, (--)) { assert(frame == tstate->current_frame); uintptr_t global_version = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & @@ -268,8 +267,7 @@ dummy_func( macro(END_FOR) = POP_TOP; - inst(INSTRUMENTED_END_FOR, (receiver, value -- receiver)) { - TIER_ONE_ONLY + tier1 inst(INSTRUMENTED_END_FOR, (receiver, value -- receiver)) { /* Need to create a fake StopIteration error here, * to conform to PEP 380 */ if (PyGen_Check(receiver)) { @@ -286,8 +284,7 @@ dummy_func( Py_DECREF(receiver); } - inst(INSTRUMENTED_END_SEND, (receiver, value -- value)) { - TIER_ONE_ONLY + tier1 inst(INSTRUMENTED_END_SEND, (receiver, value -- value)) { if (PyGen_Check(receiver) || PyCoro_CheckExact(receiver)) { PyErr_SetObject(PyExc_StopIteration, value); if (monitor_stop_iteration(tstate, frame, this_instr)) { @@ -319,7 +316,6 @@ dummy_func( }; specializing op(_SPECIALIZE_TO_BOOL, (counter/1, value -- value)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -506,8 +502,7 @@ dummy_func( // 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 --)) { - TIER_ONE_ONLY + tier1 op(_BINARY_OP_INPLACE_ADD_UNICODE, (left, right --)) { assert(next_instr->op.code == STORE_FAST); PyObject **target_local = &GETLOCAL(next_instr->op.arg); DEOPT_IF(*target_local != left); @@ -545,7 +540,6 @@ dummy_func( }; specializing op(_SPECIALIZE_BINARY_SUBSCR, (counter/1, container, sub -- container, sub)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -693,7 +687,6 @@ dummy_func( }; specializing op(_SPECIALIZE_STORE_SUBSCR, (counter/1, container, sub -- container, sub)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -762,8 +755,7 @@ dummy_func( ERROR_IF(res == NULL, error); } - inst(RAISE_VARARGS, (args[oparg] -- )) { - TIER_ONE_ONLY + tier1 inst(RAISE_VARARGS, (args[oparg] -- )) { PyObject *cause = NULL, *exc = NULL; switch (oparg) { case 2: @@ -787,8 +779,7 @@ dummy_func( ERROR_IF(true, error); } - inst(INTERPRETER_EXIT, (retval --)) { - TIER_ONE_ONLY + tier1 inst(INTERPRETER_EXIT, (retval --)) { assert(frame == &entry_frame); assert(_PyFrame_IsIncomplete(frame)); /* Restore previous frame and return. */ @@ -980,7 +971,6 @@ dummy_func( }; specializing op(_SPECIALIZE_SEND, (counter/1, receiver, unused -- receiver, unused)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -1075,8 +1065,7 @@ dummy_func( goto resume_frame; } - inst(YIELD_VALUE, (retval -- unused)) { - TIER_ONE_ONLY + tier1 inst(YIELD_VALUE, (retval -- unused)) { // NOTE: It's important that YIELD_VALUE never raises an exception! // The compiler treats any exception raised here as a failed close() // or throw() call. @@ -1105,8 +1094,7 @@ dummy_func( Py_XSETREF(exc_info->exc_value, exc_value == Py_None ? NULL : exc_value); } - inst(RERAISE, (values[oparg], exc -- values[oparg])) { - TIER_ONE_ONLY + tier1 inst(RERAISE, (values[oparg], exc -- values[oparg])) { assert(oparg >= 0 && oparg <= 2); if (oparg) { PyObject *lasti = values[0]; @@ -1127,8 +1115,7 @@ dummy_func( goto exception_unwind; } - inst(END_ASYNC_FOR, (awaitable, exc -- )) { - TIER_ONE_ONLY + tier1 inst(END_ASYNC_FOR, (awaitable, exc -- )) { assert(exc && PyExceptionInstance_Check(exc)); if (PyErr_GivenExceptionMatches(exc, PyExc_StopAsyncIteration)) { DECREF_INPUTS(); @@ -1141,8 +1128,7 @@ dummy_func( } } - inst(CLEANUP_THROW, (sub_iter, last_sent_val, exc_value -- none, value)) { - TIER_ONE_ONLY + tier1 inst(CLEANUP_THROW, (sub_iter, last_sent_val, exc_value -- none, value)) { assert(throwflag); assert(exc_value && PyExceptionInstance_Check(exc_value)); if (PyErr_GivenExceptionMatches(exc_value, PyExc_StopIteration)) { @@ -1214,7 +1200,6 @@ dummy_func( }; specializing op(_SPECIALIZE_UNPACK_SEQUENCE, (counter/1, seq -- seq)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -1284,7 +1269,6 @@ dummy_func( }; specializing op(_SPECIALIZE_STORE_ATTR, (counter/1, owner -- owner)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); @@ -1403,7 +1387,6 @@ dummy_func( }; specializing op(_SPECIALIZE_LOAD_GLOBAL, (counter/1 -- )) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg>>1); @@ -1731,7 +1714,6 @@ dummy_func( }; specializing op(_SPECIALIZE_LOAD_SUPER_ATTR, (counter/1, global_super, class, unused -- global_super, class, unused)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION int load_method = oparg & 1; if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { @@ -1744,8 +1726,7 @@ dummy_func( #endif /* ENABLE_SPECIALIZATION */ } - op(_LOAD_SUPER_ATTR, (global_super, class, self -- attr, null if (oparg & 1))) { - TIER_ONE_ONLY + tier1 op(_LOAD_SUPER_ATTR, (global_super, class, self -- attr, null if (oparg & 1))) { if (opcode == INSTRUMENTED_LOAD_SUPER_ATTR) { PyObject *arg = oparg & 2 ? class : &_PyInstrumentation_MISSING; int err = _Py_call_instrumentation_2args( @@ -1847,7 +1828,6 @@ dummy_func( }; specializing op(_SPECIALIZE_LOAD_ATTR, (counter/1, owner -- owner)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg>>1); @@ -2172,7 +2152,6 @@ dummy_func( }; specializing op(_SPECIALIZE_COMPARE_OP, (counter/1, left, right -- left, right)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -2297,28 +2276,24 @@ dummy_func( b = res ? Py_True : Py_False; } - inst(IMPORT_NAME, (level, fromlist -- res)) { - TIER_ONE_ONLY + tier1 inst(IMPORT_NAME, (level, fromlist -- res)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); res = import_name(tstate, frame, name, fromlist, level); DECREF_INPUTS(); ERROR_IF(res == NULL, error); } - inst(IMPORT_FROM, (from -- from, res)) { - TIER_ONE_ONLY + tier1 inst(IMPORT_FROM, (from -- from, res)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); res = import_from(tstate, from, name); ERROR_IF(res == NULL, error); } - inst(JUMP_FORWARD, (--)) { - TIER_ONE_ONLY + tier1 inst(JUMP_FORWARD, (--)) { JUMPBY(oparg); } - inst(JUMP_BACKWARD, (unused/1 --)) { - TIER_ONE_ONLY + tier1 inst(JUMP_BACKWARD, (unused/1 --)) { CHECK_EVAL_BREAKER(); assert(oparg <= INSTR_OFFSET()); JUMPBY(-oparg); @@ -2373,8 +2348,7 @@ dummy_func( JUMP_BACKWARD_NO_INTERRUPT, }; - inst(ENTER_EXECUTOR, (--)) { - TIER_ONE_ONLY + tier1 inst(ENTER_EXECUTOR, (--)) { CHECK_EVAL_BREAKER(); PyCodeObject *code = _PyFrame_GetCode(frame); _PyExecutorObject *executor = code->co_executors->executors[oparg & 255]; @@ -2423,8 +2397,7 @@ dummy_func( macro(POP_JUMP_IF_NOT_NONE) = unused/1 + _IS_NONE + _POP_JUMP_IF_FALSE; - inst(JUMP_BACKWARD_NO_INTERRUPT, (--)) { - TIER_ONE_ONLY + tier1 inst(JUMP_BACKWARD_NO_INTERRUPT, (--)) { /* This bytecode is used in the `yield from` or `await` loop. * If there is an interrupt, we want it handled in the innermost * generator or coroutine, so we deliberately do not check it here. @@ -2520,7 +2493,6 @@ dummy_func( }; specializing op(_SPECIALIZE_FOR_ITER, (counter/1, iter -- iter)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -3018,7 +2990,6 @@ dummy_func( }; specializing op(_SPECIALIZE_CALL, (counter/1, callable, self_or_null, args[oparg] -- callable, self_or_null, args[oparg])) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -3480,8 +3451,7 @@ dummy_func( } // This is secretly a super-instruction - inst(CALL_LIST_APPEND, (unused/1, unused/2, callable, self, args[oparg] -- unused)) { - TIER_ONE_ONLY + tier1 inst(CALL_LIST_APPEND, (unused/1, unused/2, callable, self, args[oparg] -- unused)) { assert(oparg == 1); PyInterpreterState *interp = tstate->interp; DEOPT_IF(callable != interp->callable_cache.list_append); @@ -3819,8 +3789,7 @@ dummy_func( } } - inst(RETURN_GENERATOR, (--)) { - TIER_ONE_ONLY + tier1 inst(RETURN_GENERATOR, (--)) { assert(PyFunction_Check(frame->f_funcobj)); PyFunctionObject *func = (PyFunctionObject *)frame->f_funcobj; PyGenObject *gen = (PyGenObject *)_Py_MakeCoro(func); @@ -3886,7 +3855,6 @@ dummy_func( } specializing op(_SPECIALIZE_BINARY_OP, (counter/1, lhs, rhs -- lhs, rhs)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -3992,8 +3960,7 @@ dummy_func( INSTRUMENTED_JUMP(this_instr, next_instr + offset, PY_MONITORING_EVENT_BRANCH); } - inst(EXTENDED_ARG, ( -- )) { - TIER_ONE_ONLY + tier1 inst(EXTENDED_ARG, ( -- )) { assert(oparg); opcode = next_instr->op.code; oparg = oparg << 8 | next_instr->op.arg; @@ -4001,14 +3968,12 @@ dummy_func( DISPATCH_GOTO(); } - inst(CACHE, (--)) { - TIER_ONE_ONLY + tier1 inst(CACHE, (--)) { assert(0 && "Executing a cache."); Py_FatalError("Executing a cache."); } - inst(RESERVED, (--)) { - TIER_ONE_ONLY + tier1 inst(RESERVED, (--)) { assert(0 && "Executing RESERVED instruction."); Py_FatalError("Executing RESERVED instruction."); } @@ -4048,8 +4013,7 @@ dummy_func( CHECK_EVAL_BREAKER(); } - op(_SET_IP, (instr_ptr/4 --)) { - TIER_TWO_ONLY + tier2 op(_SET_IP, (instr_ptr/4 --)) { frame->instr_ptr = (_Py_CODEUNIT *)instr_ptr; } @@ -4062,45 +4026,37 @@ dummy_func( #endif } - op(_EXIT_TRACE, (--)) { - TIER_TWO_ONLY + tier2 op(_EXIT_TRACE, (--)) { EXIT_IF(1); } - op(_CHECK_VALIDITY, (--)) { - TIER_TWO_ONLY + tier2 op(_CHECK_VALIDITY, (--)) { DEOPT_IF(!current_executor->vm_data.valid); } - pure op(_LOAD_CONST_INLINE, (ptr/4 -- value)) { - TIER_TWO_ONLY + tier2 pure op(_LOAD_CONST_INLINE, (ptr/4 -- value)) { value = Py_NewRef(ptr); } - pure op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) { - TIER_TWO_ONLY + tier2 pure op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) { value = ptr; } - pure op(_LOAD_CONST_INLINE_WITH_NULL, (ptr/4 -- value, null)) { - TIER_TWO_ONLY + tier2 pure op(_LOAD_CONST_INLINE_WITH_NULL, (ptr/4 -- value, null)) { value = Py_NewRef(ptr); null = NULL; } - pure op(_LOAD_CONST_INLINE_BORROW_WITH_NULL, (ptr/4 -- value, null)) { - TIER_TWO_ONLY + tier2 pure op(_LOAD_CONST_INLINE_BORROW_WITH_NULL, (ptr/4 -- value, null)) { value = ptr; null = NULL; } - op(_CHECK_GLOBALS, (dict/4 -- )) { - TIER_TWO_ONLY + tier2 op(_CHECK_GLOBALS, (dict/4 -- )) { DEOPT_IF(GLOBALS() != dict); } - op(_CHECK_BUILTINS, (dict/4 -- )) { - TIER_TWO_ONLY + tier2 op(_CHECK_BUILTINS, (dict/4 -- )) { DEOPT_IF(BUILTINS() != dict); } @@ -4113,8 +4069,7 @@ dummy_func( /* Only used for handling cold side exits, should never appear in * a normal trace or as part of an instruction. */ - op(_COLD_EXIT, (--)) { - TIER_TWO_ONLY + tier2 op(_COLD_EXIT, (--)) { _PyExecutorObject *previous = (_PyExecutorObject *)tstate->previous_executor; _PyExitData *exit = &previous->exits[oparg]; exit->temperature++; @@ -4147,8 +4102,7 @@ dummy_func( GOTO_TIER_TWO(executor); } - op(_START_EXECUTOR, (executor/4 --)) { - TIER_TWO_ONLY + tier2 op(_START_EXECUTOR, (executor/4 --)) { Py_DECREF(tstate->previous_executor); tstate->previous_executor = NULL; #ifndef _Py_JIT @@ -4156,14 +4110,12 @@ dummy_func( #endif } - op(_FATAL_ERROR, (--)) { - TIER_TWO_ONLY + tier2 op(_FATAL_ERROR, (--)) { assert(0); Py_FatalError("Fatal error uop executed."); } - op(_CHECK_VALIDITY_AND_SET_IP, (instr_ptr/4 --)) { - TIER_TWO_ONLY + tier2 op(_CHECK_VALIDITY_AND_SET_IP, (instr_ptr/4 --)) { DEOPT_IF(!current_executor->vm_data.valid); frame->instr_ptr = (_Py_CODEUNIT *)instr_ptr; } diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h index 01a9b32229d8a52..085199416c15232 100644 --- a/Python/ceval_macros.h +++ b/Python/ceval_macros.h @@ -372,12 +372,6 @@ static inline void _Py_LeaveRecursiveCallPy(PyThreadState *tstate) { tstate->py_recursion_remaining++; } -/* Marker to specify tier 1 only instructions */ -#define TIER_ONE_ONLY - -/* Marker to specify tier 2 only instructions */ -#define TIER_TWO_ONLY - /* Implementation of "macros" that modify the instruction pointer, * stack pointer, or frame pointer. * These need to treated differently by tier 1 and 2. diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 3054058cf44d31b..0b1d75985e11952 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -3716,7 +3716,6 @@ case _SET_IP: { PyObject *instr_ptr = (PyObject *)CURRENT_OPERAND(); - TIER_TWO_ONLY frame->instr_ptr = (_Py_CODEUNIT *)instr_ptr; break; } @@ -3733,13 +3732,11 @@ } case _EXIT_TRACE: { - TIER_TWO_ONLY if (1) goto side_exit; break; } case _CHECK_VALIDITY: { - TIER_TWO_ONLY if (!current_executor->vm_data.valid) goto deoptimize; break; } @@ -3747,7 +3744,6 @@ case _LOAD_CONST_INLINE: { PyObject *value; PyObject *ptr = (PyObject *)CURRENT_OPERAND(); - TIER_TWO_ONLY value = Py_NewRef(ptr); stack_pointer[0] = value; stack_pointer += 1; @@ -3757,7 +3753,6 @@ case _LOAD_CONST_INLINE_BORROW: { PyObject *value; PyObject *ptr = (PyObject *)CURRENT_OPERAND(); - TIER_TWO_ONLY value = ptr; stack_pointer[0] = value; stack_pointer += 1; @@ -3768,7 +3763,6 @@ PyObject *value; PyObject *null; PyObject *ptr = (PyObject *)CURRENT_OPERAND(); - TIER_TWO_ONLY value = Py_NewRef(ptr); null = NULL; stack_pointer[0] = value; @@ -3781,7 +3775,6 @@ PyObject *value; PyObject *null; PyObject *ptr = (PyObject *)CURRENT_OPERAND(); - TIER_TWO_ONLY value = ptr; null = NULL; stack_pointer[0] = value; @@ -3792,14 +3785,12 @@ case _CHECK_GLOBALS: { PyObject *dict = (PyObject *)CURRENT_OPERAND(); - TIER_TWO_ONLY if (GLOBALS() != dict) goto deoptimize; break; } case _CHECK_BUILTINS: { PyObject *dict = (PyObject *)CURRENT_OPERAND(); - TIER_TWO_ONLY if (BUILTINS() != dict) goto deoptimize; break; } @@ -3815,7 +3806,6 @@ case _COLD_EXIT: { oparg = CURRENT_OPARG(); - TIER_TWO_ONLY _PyExecutorObject *previous = (_PyExecutorObject *)tstate->previous_executor; _PyExitData *exit = &previous->exits[oparg]; exit->temperature++; @@ -3851,7 +3841,6 @@ case _START_EXECUTOR: { PyObject *executor = (PyObject *)CURRENT_OPERAND(); - TIER_TWO_ONLY Py_DECREF(tstate->previous_executor); tstate->previous_executor = NULL; #ifndef _Py_JIT @@ -3861,7 +3850,6 @@ } case _FATAL_ERROR: { - TIER_TWO_ONLY assert(0); Py_FatalError("Fatal error uop executed."); break; @@ -3869,7 +3857,6 @@ case _CHECK_VALIDITY_AND_SET_IP: { PyObject *instr_ptr = (PyObject *)CURRENT_OPERAND(); - TIER_TWO_ONLY if (!current_executor->vm_data.valid) goto deoptimize; frame->instr_ptr = (_Py_CODEUNIT *)instr_ptr; break; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 87579134146c857..8b90e448a7b8fa2 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -112,7 +112,6 @@ lhs = stack_pointer[-2]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -242,7 +241,6 @@ /* Skip 1 cache entry */ // _BINARY_OP_INPLACE_ADD_UNICODE { - TIER_ONE_ONLY assert(next_instr->op.code == STORE_FAST); PyObject **target_local = &GETLOCAL(next_instr->op.arg); DEOPT_IF(*target_local != left, BINARY_OP); @@ -429,7 +427,6 @@ container = stack_pointer[-2]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -739,7 +736,6 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(CACHE); - TIER_ONE_ONLY assert(0 && "Executing a cache."); Py_FatalError("Executing a cache."); DISPATCH(); @@ -761,7 +757,6 @@ callable = stack_pointer[-2 - oparg]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -1477,7 +1472,6 @@ args = &stack_pointer[-oparg]; self = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - TIER_ONE_ONLY assert(oparg == 1); PyInterpreterState *interp = tstate->interp; DEOPT_IF(callable != interp->callable_cache.list_append, CALL); @@ -1953,7 +1947,6 @@ exc_value = stack_pointer[-1]; last_sent_val = stack_pointer[-2]; sub_iter = stack_pointer[-3]; - TIER_ONE_ONLY assert(throwflag); assert(exc_value && PyExceptionInstance_Check(exc_value)); if (PyErr_GivenExceptionMatches(exc_value, PyExc_StopIteration)) { @@ -1988,7 +1981,6 @@ left = stack_pointer[-2]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -2338,7 +2330,6 @@ PyObject *awaitable; exc = stack_pointer[-1]; awaitable = stack_pointer[-2]; - TIER_ONE_ONLY assert(exc && PyExceptionInstance_Check(exc)); if (PyErr_GivenExceptionMatches(exc, PyExc_StopAsyncIteration)) { Py_DECREF(awaitable); @@ -2383,7 +2374,6 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(ENTER_EXECUTOR); - TIER_ONE_ONLY CHECK_EVAL_BREAKER(); PyCodeObject *code = _PyFrame_GetCode(frame); _PyExecutorObject *executor = code->co_executors->executors[oparg & 255]; @@ -2418,7 +2408,6 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(EXTENDED_ARG); - TIER_ONE_ONLY assert(oparg); opcode = next_instr->op.code; oparg = oparg << 8 | next_instr->op.arg; @@ -2477,7 +2466,6 @@ iter = stack_pointer[-1]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -2867,7 +2855,6 @@ PyObject *from; PyObject *res; from = stack_pointer[-1]; - TIER_ONE_ONLY PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); res = import_from(tstate, from, name); if (res == NULL) goto error; @@ -2885,7 +2872,6 @@ PyObject *res; fromlist = stack_pointer[-1]; level = stack_pointer[-2]; - TIER_ONE_ONLY PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); res = import_name(tstate, frame, name, fromlist, level); Py_DECREF(level); @@ -2945,7 +2931,6 @@ PyObject *receiver; value = stack_pointer[-1]; receiver = stack_pointer[-2]; - TIER_ONE_ONLY /* Need to create a fake StopIteration error here, * to conform to PEP 380 */ if (PyGen_Check(receiver)) { @@ -2968,7 +2953,6 @@ PyObject *receiver; value = stack_pointer[-1]; receiver = stack_pointer[-2]; - TIER_ONE_ONLY if (PyGen_Check(receiver) || PyCoro_CheckExact(receiver)) { PyErr_SetObject(PyExc_StopIteration, value); if (monitor_stop_iteration(tstate, frame, this_instr)) { @@ -3248,7 +3232,6 @@ INSTRUCTION_STATS(INTERPRETER_EXIT); PyObject *retval; retval = stack_pointer[-1]; - TIER_ONE_ONLY assert(frame == &entry_frame); assert(_PyFrame_IsIncomplete(frame)); /* Restore previous frame and return. */ @@ -3281,7 +3264,6 @@ next_instr += 2; INSTRUCTION_STATS(JUMP_BACKWARD); /* Skip 1 cache entry */ - TIER_ONE_ONLY CHECK_EVAL_BREAKER(); assert(oparg <= INSTR_OFFSET()); JUMPBY(-oparg); @@ -3331,7 +3313,6 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(JUMP_BACKWARD_NO_INTERRUPT); - TIER_ONE_ONLY /* This bytecode is used in the `yield from` or `await` loop. * If there is an interrupt, we want it handled in the innermost * generator or coroutine, so we deliberately do not check it here. @@ -3345,7 +3326,6 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(JUMP_FORWARD); - TIER_ONE_ONLY JUMPBY(oparg); DISPATCH(); } @@ -3414,7 +3394,6 @@ owner = stack_pointer[-1]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg>>1); @@ -4109,7 +4088,6 @@ // _SPECIALIZE_LOAD_GLOBAL { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg>>1); @@ -4311,7 +4289,6 @@ global_super = stack_pointer[-3]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION int load_method = oparg & 1; if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { @@ -4326,7 +4303,6 @@ // _LOAD_SUPER_ATTR self = stack_pointer[-1]; { - TIER_ONE_ONLY if (opcode == INSTRUMENTED_LOAD_SUPER_ATTR) { PyObject *arg = oparg & 2 ? class : &_PyInstrumentation_MISSING; int err = _Py_call_instrumentation_2args( @@ -4737,7 +4713,6 @@ INSTRUCTION_STATS(RAISE_VARARGS); PyObject **args; args = &stack_pointer[-oparg]; - TIER_ONE_ONLY PyObject *cause = NULL, *exc = NULL; switch (oparg) { case 2: @@ -4769,7 +4744,6 @@ PyObject **values; exc = stack_pointer[-1]; values = &stack_pointer[-1 - oparg]; - TIER_ONE_ONLY assert(oparg >= 0 && oparg <= 2); if (oparg) { PyObject *lasti = values[0]; @@ -4794,7 +4768,6 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(RESERVED); - TIER_ONE_ONLY assert(0 && "Executing RESERVED instruction."); Py_FatalError("Executing RESERVED instruction."); DISPATCH(); @@ -4806,7 +4779,6 @@ INSTRUCTION_STATS(RESUME); PREDICTED(RESUME); _Py_CODEUNIT *this_instr = next_instr - 1; - TIER_ONE_ONLY assert(frame == tstate->current_frame); uintptr_t global_version = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & @@ -4884,7 +4856,6 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(RETURN_GENERATOR); - TIER_ONE_ONLY assert(PyFunction_Check(frame->f_funcobj)); PyFunctionObject *func = (PyFunctionObject *)frame->f_funcobj; PyGenObject *gen = (PyGenObject *)_Py_MakeCoro(func); @@ -4951,7 +4922,6 @@ receiver = stack_pointer[-2]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -5138,7 +5108,6 @@ owner = stack_pointer[-1]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); @@ -5430,7 +5399,6 @@ container = stack_pointer[-2]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -5532,7 +5500,6 @@ value = stack_pointer[-1]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -5741,7 +5708,6 @@ seq = stack_pointer[-1]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -5876,7 +5842,6 @@ INSTRUCTION_STATS(YIELD_VALUE); PyObject *retval; retval = stack_pointer[-1]; - TIER_ONE_ONLY // NOTE: It's important that YIELD_VALUE never raises an exception! // The compiler treats any exception raised here as a failed close() // or throw() call. diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index 49b8b426444d248..b0a15e6d87c2c6c 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -17,7 +17,6 @@ class Properties: needs_this: bool always_exits: bool stores_sp: bool - tier_one_only: bool uses_co_consts: bool uses_co_names: bool uses_locals: bool @@ -25,6 +24,7 @@ class Properties: side_exit: bool pure: bool passthrough: bool + tier: int | None = None oparg_and_1: bool = False const_oparg: int = -1 @@ -46,7 +46,6 @@ def from_list(properties: list["Properties"]) -> "Properties": needs_this=any(p.needs_this for p in properties), always_exits=any(p.always_exits for p in properties), stores_sp=any(p.stores_sp for p in properties), - tier_one_only=any(p.tier_one_only for p in properties), uses_co_consts=any(p.uses_co_consts for p in properties), uses_co_names=any(p.uses_co_names for p in properties), uses_locals=any(p.uses_locals for p in properties), @@ -68,7 +67,6 @@ def from_list(properties: list["Properties"]) -> "Properties": needs_this=False, always_exits=False, stores_sp=False, - tier_one_only=False, uses_co_consts=False, uses_co_names=False, uses_locals=False, @@ -312,6 +310,15 @@ def variable_used(node: parser.InstDef, name: str) -> bool: token.kind == "IDENTIFIER" and token.text == name for token in node.tokens ) +def tier_variable(node: parser.InstDef) -> int | None: + """Determine whether a tier variable is used in a node.""" + for token in node.tokens: + if token.kind == "ANNOTATION": + if token.text == "specializing": + return 1 + if re.fullmatch(r"tier\d", token.text): + return int(token.text[-1]) + return None def is_infallible(op: parser.InstDef) -> bool: return not ( @@ -498,7 +505,6 @@ def compute_properties(op: parser.InstDef) -> Properties: needs_this=variable_used(op, "this_instr"), always_exits=always_exits(op), stores_sp=variable_used(op, "SYNC_SP"), - tier_one_only=variable_used(op, "TIER_ONE_ONLY"), uses_co_consts=variable_used(op, "FRAME_CO_CONSTS"), uses_co_names=variable_used(op, "FRAME_CO_NAMES"), uses_locals=(variable_used(op, "GETLOCAL") or variable_used(op, "SETLOCAL")) @@ -506,6 +512,7 @@ def compute_properties(op: parser.InstDef) -> Properties: has_free=has_free, pure="pure" in op.annotations, passthrough=passthrough, + tier=tier_variable(op), ) diff --git a/Tools/cases_generator/interpreter_definition.md b/Tools/cases_generator/interpreter_definition.md index 9b5733562f77b40..889f58fc3e1a75d 100644 --- a/Tools/cases_generator/interpreter_definition.md +++ b/Tools/cases_generator/interpreter_definition.md @@ -168,6 +168,7 @@ list of annotations and their meanings are as follows: * `override`. For external use by other interpreter definitions to override the current instruction definition. * `pure`. This instruction has no side effects. +* 'tierN'. This instruction only used by tier N interpreter. ### Special functions/macros diff --git a/Tools/cases_generator/lexer.py b/Tools/cases_generator/lexer.py index 0077921e7d7fa12..13aee94f2b957c6 100644 --- a/Tools/cases_generator/lexer.py +++ b/Tools/cases_generator/lexer.py @@ -224,6 +224,8 @@ def choice(*opts: str) -> str: "pure", "split", "replicate", + "tier1", + "tier2", } __all__ = [] diff --git a/Tools/cases_generator/opcode_metadata_generator.py b/Tools/cases_generator/opcode_metadata_generator.py index 24fbea6cfcb77ac..ab597834a8892fa 100644 --- a/Tools/cases_generator/opcode_metadata_generator.py +++ b/Tools/cases_generator/opcode_metadata_generator.py @@ -284,7 +284,7 @@ def is_viable_expansion(inst: Instruction) -> bool: continue if "replaced" in part.annotations: continue - if part.properties.tier_one_only or not part.is_viable(): + if part.properties.tier == 1 or not part.is_viable(): return False return True diff --git a/Tools/cases_generator/tier2_abstract_generator.py b/Tools/cases_generator/tier2_abstract_generator.py index 47a862643d987e1..58c3110dc3facbf 100644 --- a/Tools/cases_generator/tier2_abstract_generator.py +++ b/Tools/cases_generator/tier2_abstract_generator.py @@ -176,7 +176,7 @@ def generate_abstract_interpreter( if uop.name in abstract.uops: override = abstract.uops[uop.name] validate_uop(override, uop) - if uop.properties.tier_one_only: + if uop.properties.tier == 1: continue if uop.replicates: continue diff --git a/Tools/cases_generator/tier2_generator.py b/Tools/cases_generator/tier2_generator.py index 6fbe5c355c90831..d8eed1078b09149 100644 --- a/Tools/cases_generator/tier2_generator.py +++ b/Tools/cases_generator/tier2_generator.py @@ -194,7 +194,7 @@ def generate_tier2( out = CWriter(outfile, 2, lines) out.emit("\n") for name, uop in analysis.uops.items(): - if uop.properties.tier_one_only: + if uop.properties.tier == 1: continue if uop.properties.oparg_and_1: out.emit(f"/* {uop.name} is split on (oparg & 1) */\n\n") diff --git a/Tools/cases_generator/uop_id_generator.py b/Tools/cases_generator/uop_id_generator.py index 907158f27959597..eb5e3f4a324735e 100644 --- a/Tools/cases_generator/uop_id_generator.py +++ b/Tools/cases_generator/uop_id_generator.py @@ -43,7 +43,7 @@ def generate_uop_ids( for name, uop in sorted(uops): if name in PRE_DEFINED: continue - if uop.properties.tier_one_only: + if uop.properties.tier == 1: continue if uop.implicitly_created and not distinct_namespace and not uop.replicated: out.emit(f"#define {name} {name[1:]}\n") diff --git a/Tools/cases_generator/uop_metadata_generator.py b/Tools/cases_generator/uop_metadata_generator.py index f85f1c6ce9c817a..72eed3041c55c9c 100644 --- a/Tools/cases_generator/uop_metadata_generator.py +++ b/Tools/cases_generator/uop_metadata_generator.py @@ -29,7 +29,7 @@ def generate_names_and_flags(analysis: Analysis, out: CWriter) -> None: out.emit("#ifdef NEED_OPCODE_METADATA\n") out.emit("const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {\n") for uop in analysis.uops.values(): - if uop.is_viable() and not uop.properties.tier_one_only: + if uop.is_viable() and uop.properties.tier != 1: out.emit(f"[{uop.name}] = {cflags(uop.properties)},\n") out.emit("};\n\n") @@ -41,7 +41,7 @@ def generate_names_and_flags(analysis: Analysis, out: CWriter) -> None: out.emit("};\n\n") out.emit("const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = {\n") for uop in sorted(analysis.uops.values(), key=lambda t: t.name): - if uop.is_viable() and not uop.properties.tier_one_only: + if uop.is_viable() and uop.properties.tier != 1: out.emit(f'[{uop.name}] = "{uop.name}",\n') out.emit("};\n") out.emit("#endif // NEED_OPCODE_METADATA\n\n") From 3d8fc06d4f8dc1e7be2455a7e89b37285fa89112 Mon Sep 17 00:00:00 2001 From: Ken Jin <kenjin@python.org> Date: Sat, 24 Feb 2024 02:43:52 +0800 Subject: [PATCH 428/507] gh-115859: Disable the tier 2 redundancy eliminator by default (GH-115860) --- Lib/test/test_capi/test_opt.py | 3 +++ Python/optimizer_analysis.c | 9 ++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 38c6fa4b47d0c92..25fc36dec93ddcb 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -4,6 +4,7 @@ import textwrap import unittest import gc +import os import _testinternalcapi @@ -568,6 +569,8 @@ def testfunc(n): count = ops.count("_GUARD_IS_TRUE_POP") + ops.count("_GUARD_IS_FALSE_POP") self.assertLessEqual(count, 2) + +@unittest.skipIf(os.getenv("PYTHONUOPSOPTIMIZE", default=0) == 0, "Needs uop optimizer to run.") class TestUopsOptimization(unittest.TestCase): def _run_with_optimizer(self, testfunc, arg): diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 9503dcc74656cd8..47bfc8cf1061d92 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -810,9 +810,12 @@ _Py_uop_analyze_and_optimize( peephole_opt(frame, buffer, buffer_size); - err = uop_redundancy_eliminator( - (PyCodeObject *)frame->f_executable, buffer, - buffer_size, curr_stacklen, dependencies); + char *uop_optimize = Py_GETENV("PYTHONUOPSOPTIMIZE"); + if (uop_optimize != NULL && *uop_optimize > '0') { + err = uop_redundancy_eliminator( + (PyCodeObject *)frame->f_executable, buffer, + buffer_size, curr_stacklen, dependencies); + } if (err == 0) { goto not_ready; From 462a2fc09d9e5f7cdd3a8f2faed73e5bc2c93349 Mon Sep 17 00:00:00 2001 From: Stanley <46876382+slateny@users.noreply.github.com> Date: Fri, 23 Feb 2024 10:57:08 -0800 Subject: [PATCH 429/507] gh-54358: Clarify data chunking in pyexpat (GH-31629) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Éric Araujo <merwok@netwok.org> --- Doc/library/pyexpat.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Doc/library/pyexpat.rst b/Doc/library/pyexpat.rst index 935e872480efda7..a6ae8fdaa4991ce 100644 --- a/Doc/library/pyexpat.rst +++ b/Doc/library/pyexpat.rst @@ -214,7 +214,8 @@ XMLParser Objects :meth:`CharacterDataHandler` callback whenever possible. This can improve performance substantially since Expat normally breaks character data into chunks at every line ending. This attribute is false by default, and may be changed at - any time. + any time. Note that when it is false, data that does not contain newlines + may be chunked too. .. attribute:: xmlparser.buffer_used @@ -372,7 +373,10 @@ otherwise stated. marked content, and ignorable whitespace. Applications which must distinguish these cases can use the :attr:`StartCdataSectionHandler`, :attr:`EndCdataSectionHandler`, and :attr:`ElementDeclHandler` callbacks to - collect the required information. + collect the required information. Note that the character data may be + chunked even if it is short and so you may receive more than one call to + :meth:`CharacterDataHandler`. Set the :attr:`buffer_text` instance attribute + to ``True`` to avoid that. .. method:: xmlparser.UnparsedEntityDeclHandler(entityName, base, systemId, publicId, notationName) From c688c0f130906ff7725a126fff143d1389884f89 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Fri, 23 Feb 2024 22:25:09 +0200 Subject: [PATCH 430/507] gh-67044: Always quote or escape \r and \n in csv.writer() (GH-115741) --- Lib/test/test_csv.py | 54 +++++++++++++------ ...4-02-20-22-02-34.gh-issue-67044.QF9_Ru.rst | 2 + Modules/_csv.c | 2 + 3 files changed, 43 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-20-22-02-34.gh-issue-67044.QF9_Ru.rst diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py index 5217f2a71ec0f96..d74ab7e016f78c0 100644 --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -265,9 +265,11 @@ def test_write_lineterminator(self): writer = csv.writer(sio, lineterminator=lineterminator) writer.writerow(['a', 'b']) writer.writerow([1, 2]) + writer.writerow(['\r', '\n']) self.assertEqual(sio.getvalue(), f'a,b{lineterminator}' - f'1,2{lineterminator}') + f'1,2{lineterminator}' + f'"\r","\n"{lineterminator}') def test_write_iterable(self): self._write_test(iter(['a', 1, 'p,q']), 'a,1,"p,q"') @@ -507,22 +509,44 @@ def test_read_linenum(self): self.assertEqual(r.line_num, 3) def test_roundtrip_quoteed_newlines(self): - with TemporaryFile("w+", encoding="utf-8", newline='') as fileobj: - writer = csv.writer(fileobj) - rows = [['a\nb','b'],['c','x\r\nd']] - writer.writerows(rows) - fileobj.seek(0) - for i, row in enumerate(csv.reader(fileobj)): - self.assertEqual(row, rows[i]) + rows = [ + ['\na', 'b\nc', 'd\n'], + ['\re', 'f\rg', 'h\r'], + ['\r\ni', 'j\r\nk', 'l\r\n'], + ['\n\rm', 'n\n\ro', 'p\n\r'], + ['\r\rq', 'r\r\rs', 't\r\r'], + ['\n\nu', 'v\n\nw', 'x\n\n'], + ] + for lineterminator in '\r\n', '\n', '\r': + with self.subTest(lineterminator=lineterminator): + with TemporaryFile("w+", encoding="utf-8", newline='') as fileobj: + writer = csv.writer(fileobj, lineterminator=lineterminator) + writer.writerows(rows) + fileobj.seek(0) + for i, row in enumerate(csv.reader(fileobj)): + self.assertEqual(row, rows[i]) def test_roundtrip_escaped_unquoted_newlines(self): - with TemporaryFile("w+", encoding="utf-8", newline='') as fileobj: - writer = csv.writer(fileobj,quoting=csv.QUOTE_NONE,escapechar="\\") - rows = [['a\nb','b'],['c','x\r\nd']] - writer.writerows(rows) - fileobj.seek(0) - for i, row in enumerate(csv.reader(fileobj,quoting=csv.QUOTE_NONE,escapechar="\\")): - self.assertEqual(row,rows[i]) + rows = [ + ['\na', 'b\nc', 'd\n'], + ['\re', 'f\rg', 'h\r'], + ['\r\ni', 'j\r\nk', 'l\r\n'], + ['\n\rm', 'n\n\ro', 'p\n\r'], + ['\r\rq', 'r\r\rs', 't\r\r'], + ['\n\nu', 'v\n\nw', 'x\n\n'], + ] + for lineterminator in '\r\n', '\n', '\r': + with self.subTest(lineterminator=lineterminator): + with TemporaryFile("w+", encoding="utf-8", newline='') as fileobj: + writer = csv.writer(fileobj, lineterminator=lineterminator, + quoting=csv.QUOTE_NONE, escapechar="\\") + writer.writerows(rows) + fileobj.seek(0) + for i, row in enumerate(csv.reader(fileobj, + quoting=csv.QUOTE_NONE, + escapechar="\\")): + self.assertEqual(row, rows[i]) + class TestDialectRegistry(unittest.TestCase): def test_registry_badargs(self): diff --git a/Misc/NEWS.d/next/Library/2024-02-20-22-02-34.gh-issue-67044.QF9_Ru.rst b/Misc/NEWS.d/next/Library/2024-02-20-22-02-34.gh-issue-67044.QF9_Ru.rst new file mode 100644 index 000000000000000..095e69b6cadab64 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-20-22-02-34.gh-issue-67044.QF9_Ru.rst @@ -0,0 +1,2 @@ +:func:`csv.writer` now always quotes or escapes ``'\r'`` and ``'\n'``, +regardless of *lineterminator* value. diff --git a/Modules/_csv.c b/Modules/_csv.c index 8d0472885afd96d..660c5455af764ef 100644 --- a/Modules/_csv.c +++ b/Modules/_csv.c @@ -1152,6 +1152,8 @@ join_append_data(WriterObj *self, int field_kind, const void *field_data, if (c == dialect->delimiter || c == dialect->escapechar || c == dialect->quotechar || + c == '\n' || + c == '\r' || PyUnicode_FindChar( dialect->lineterminator, c, 0, PyUnicode_GET_LENGTH(dialect->lineterminator), 1) >= 0) { From ef6074b352a95706f44a592ffe31baace690cc1c Mon Sep 17 00:00:00 2001 From: jmcb <joelsgp@protonmail.com> Date: Fri, 23 Feb 2024 22:55:47 +0000 Subject: [PATCH 431/507] Insert missing apostrophes in ctypes documentation (#115090) --- Doc/library/ctypes.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index 73779547b35a1f2..702955659a3bb97 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -200,7 +200,7 @@ calls). Python objects that can directly be used as parameters in these function calls. ``None`` is passed as a C ``NULL`` pointer, bytes objects and strings are passed as pointer to the memory block that contains their data (:c:expr:`char *` or -:c:expr:`wchar_t *`). Python integers are passed as the platforms default C +:c:expr:`wchar_t *`). Python integers are passed as the platform's default C :c:expr:`int` type, their value is masked to fit into the C type. Before we move on calling functions with other parameter types, we have to learn @@ -1445,7 +1445,7 @@ function exported by these libraries, and reacquired afterwards. All these classes can be instantiated by calling them with at least one argument, the pathname of the shared library. If you have an existing handle to an already loaded shared library, it can be passed as the ``handle`` named -parameter, otherwise the underlying platforms :c:func:`!dlopen` or +parameter, otherwise the underlying platform's :c:func:`!dlopen` or :c:func:`!LoadLibrary` function is used to load the library into the process, and to get a handle to it. @@ -1456,7 +1456,7 @@ configurable. The *use_errno* parameter, when set to true, enables a ctypes mechanism that allows accessing the system :data:`errno` error number in a safe way. -:mod:`ctypes` maintains a thread-local copy of the systems :data:`errno` +:mod:`ctypes` maintains a thread-local copy of the system's :data:`errno` variable; if you call foreign functions created with ``use_errno=True`` then the :data:`errno` value before the function call is swapped with the ctypes private copy, the same happens immediately after the function call. From 200271c61db44d90759f8a8934949aefd72d5724 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz <effigies@gmail.com> Date: Fri, 23 Feb 2024 19:02:16 -0500 Subject: [PATCH 432/507] gh-114763: Protect lazy loading modules from attribute access races (GH-114781) Setting the __class__ attribute of a lazy-loading module to ModuleType enables other threads to attempt to access attributes before the loading is complete. Now that is protected by a lock. --- Lib/importlib/util.py | 81 ++++++++++++------- Lib/test/test_importlib/test_lazy.py | 42 +++++++++- ...-01-30-23-28-29.gh-issue-114763.BRjKkg.rst | 3 + 3 files changed, 94 insertions(+), 32 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-30-23-28-29.gh-issue-114763.BRjKkg.rst diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index 3ad71d31c2f4384..ff4f12fb1af70cd 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -13,6 +13,7 @@ import _imp import sys +import threading import types @@ -171,36 +172,54 @@ class _LazyModule(types.ModuleType): def __getattribute__(self, attr): """Trigger the load of the module and return the attribute.""" - # All module metadata must be garnered from __spec__ in order to avoid - # using mutated values. - # Stop triggering this method. - self.__class__ = types.ModuleType - # Get the original name to make sure no object substitution occurred - # in sys.modules. - original_name = self.__spec__.name - # Figure out exactly what attributes were mutated between the creation - # of the module and now. - attrs_then = self.__spec__.loader_state['__dict__'] - attrs_now = self.__dict__ - attrs_updated = {} - for key, value in attrs_now.items(): - # Code that set the attribute may have kept a reference to the - # assigned object, making identity more important than equality. - if key not in attrs_then: - attrs_updated[key] = value - elif id(attrs_now[key]) != id(attrs_then[key]): - attrs_updated[key] = value - self.__spec__.loader.exec_module(self) - # If exec_module() was used directly there is no guarantee the module - # object was put into sys.modules. - if original_name in sys.modules: - if id(self) != id(sys.modules[original_name]): - raise ValueError(f"module object for {original_name!r} " - "substituted in sys.modules during a lazy " - "load") - # Update after loading since that's what would happen in an eager - # loading situation. - self.__dict__.update(attrs_updated) + __spec__ = object.__getattribute__(self, '__spec__') + loader_state = __spec__.loader_state + with loader_state['lock']: + # Only the first thread to get the lock should trigger the load + # and reset the module's class. The rest can now getattr(). + if object.__getattribute__(self, '__class__') is _LazyModule: + # The first thread comes here multiple times as it descends the + # call stack. The first time, it sets is_loading and triggers + # exec_module(), which will access module.__dict__, module.__name__, + # and/or module.__spec__, reentering this method. These accesses + # need to be allowed to proceed without triggering the load again. + if loader_state['is_loading'] and attr.startswith('__') and attr.endswith('__'): + return object.__getattribute__(self, attr) + loader_state['is_loading'] = True + + __dict__ = object.__getattribute__(self, '__dict__') + + # All module metadata must be gathered from __spec__ in order to avoid + # using mutated values. + # Get the original name to make sure no object substitution occurred + # in sys.modules. + original_name = __spec__.name + # Figure out exactly what attributes were mutated between the creation + # of the module and now. + attrs_then = loader_state['__dict__'] + attrs_now = __dict__ + attrs_updated = {} + for key, value in attrs_now.items(): + # Code that set an attribute may have kept a reference to the + # assigned object, making identity more important than equality. + if key not in attrs_then: + attrs_updated[key] = value + elif id(attrs_now[key]) != id(attrs_then[key]): + attrs_updated[key] = value + __spec__.loader.exec_module(self) + # If exec_module() was used directly there is no guarantee the module + # object was put into sys.modules. + if original_name in sys.modules: + if id(self) != id(sys.modules[original_name]): + raise ValueError(f"module object for {original_name!r} " + "substituted in sys.modules during a lazy " + "load") + # Update after loading since that's what would happen in an eager + # loading situation. + __dict__.update(attrs_updated) + # Finally, stop triggering this method. + self.__class__ = types.ModuleType + return getattr(self, attr) def __delattr__(self, attr): @@ -244,5 +263,7 @@ def exec_module(self, module): loader_state = {} loader_state['__dict__'] = module.__dict__.copy() loader_state['__class__'] = module.__class__ + loader_state['lock'] = threading.RLock() + loader_state['is_loading'] = False module.__spec__.loader_state = loader_state module.__class__ = _LazyModule diff --git a/Lib/test/test_importlib/test_lazy.py b/Lib/test/test_importlib/test_lazy.py index cc993f333e355af..38ab21907b58d9d 100644 --- a/Lib/test/test_importlib/test_lazy.py +++ b/Lib/test/test_importlib/test_lazy.py @@ -2,9 +2,12 @@ from importlib import abc from importlib import util import sys +import time +import threading import types import unittest +from test.support import threading_helper from test.test_importlib import util as test_util @@ -40,6 +43,7 @@ class TestingImporter(abc.MetaPathFinder, abc.Loader): module_name = 'lazy_loader_test' mutated_name = 'changed' loaded = None + load_count = 0 source_code = 'attr = 42; __name__ = {!r}'.format(mutated_name) def find_spec(self, name, path, target=None): @@ -48,8 +52,10 @@ def find_spec(self, name, path, target=None): return util.spec_from_loader(name, util.LazyLoader(self)) def exec_module(self, module): + time.sleep(0.01) # Simulate a slow load. exec(self.source_code, module.__dict__) self.loaded = module + self.load_count += 1 class LazyLoaderTests(unittest.TestCase): @@ -59,8 +65,9 @@ def test_init(self): # Classes that don't define exec_module() trigger TypeError. util.LazyLoader(object) - def new_module(self, source_code=None): - loader = TestingImporter() + def new_module(self, source_code=None, loader=None): + if loader is None: + loader = TestingImporter() if source_code is not None: loader.source_code = source_code spec = util.spec_from_loader(TestingImporter.module_name, @@ -140,6 +147,37 @@ def test_module_already_in_sys(self): # Force the load; just care that no exception is raised. module.__name__ + @threading_helper.requires_working_threading() + def test_module_load_race(self): + with test_util.uncache(TestingImporter.module_name): + loader = TestingImporter() + module = self.new_module(loader=loader) + self.assertEqual(loader.load_count, 0) + + class RaisingThread(threading.Thread): + exc = None + def run(self): + try: + super().run() + except Exception as exc: + self.exc = exc + + def access_module(): + return module.attr + + threads = [] + for _ in range(2): + threads.append(thread := RaisingThread(target=access_module)) + thread.start() + + # Races could cause errors + for thread in threads: + thread.join() + self.assertIsNone(thread.exc) + + # Or multiple load attempts + self.assertEqual(loader.load_count, 1) + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2024-01-30-23-28-29.gh-issue-114763.BRjKkg.rst b/Misc/NEWS.d/next/Library/2024-01-30-23-28-29.gh-issue-114763.BRjKkg.rst new file mode 100644 index 000000000000000..e8bdb83dde61fba --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-30-23-28-29.gh-issue-114763.BRjKkg.rst @@ -0,0 +1,3 @@ +Protect modules loaded with :class:`importlib.util.LazyLoader` from race +conditions when multiple threads try to access attributes before the loading +is complete. From 52517118685dd3cc35068af6bba80b650775a89b Mon Sep 17 00:00:00 2001 From: partev <petrosyan@gmail.com> Date: Sat, 24 Feb 2024 01:53:26 -0500 Subject: [PATCH 433/507] gh-115872: Doc: remove obsolete reference to MSI packages (#115873) --- Doc/using/windows.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/using/windows.rst b/Doc/using/windows.rst index bfcf1c6882f29d4..cc4db34b04d900b 100644 --- a/Doc/using/windows.rst +++ b/Doc/using/windows.rst @@ -14,7 +14,7 @@ know about when using Python on Microsoft Windows. Unlike most Unix systems and services, Windows does not include a system supported installation of Python. To make Python available, the CPython team -has compiled Windows installers (MSI packages) with every `release +has compiled Windows installers with every `release <https://www.python.org/downloads/>`_ for many years. These installers are primarily intended to add a per-user installation of Python, with the core interpreter and library being used by a single user. The installer is also From 53c5c17e0a97ee06e511c89f1ca6ceb38fd06246 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger <rhettinger@users.noreply.github.com> Date: Sat, 24 Feb 2024 12:03:11 -0600 Subject: [PATCH 434/507] gh-113202: Add whatsnew entry for the batched() strict option. (gh-115889) --- Doc/whatsnew/3.13.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 7c6a2af28758bee..40823587fb94177 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -313,6 +313,14 @@ ipaddress * Add the :attr:`ipaddress.IPv4Address.ipv6_mapped` property, which returns the IPv4-mapped IPv6 address. (Contributed by Charles Machalow in :gh:`109466`.) +itertools +--------- + +* Added a ``strict`` option to :func:`itertools.batched`. + This raises a :exc:`ValueError` if the final batch is shorter + than the specified batch size. + (Contributed by Raymond Hettinger in :gh:`113202`.) + marshal ------- From e3dedeae7abbeda0cb3f1d872ebbb914635d64f2 Mon Sep 17 00:00:00 2001 From: Barney Gale <barney.gale@gmail.com> Date: Sat, 24 Feb 2024 19:37:03 +0000 Subject: [PATCH 435/507] GH-114610: Fix `pathlib.PurePath.with_stem('')` handling of file extensions (#114612) Raise `ValueError` if `with_stem('')` is called on a path with a file extension. Paths may only have an empty stem if they also have an empty suffix. --- Lib/pathlib/_abc.py | 10 +++++++++- Lib/test/test_pathlib/test_pathlib_abc.py | 2 ++ .../2024-01-26-16-42-31.gh-issue-114610.S18Vuz.rst | 4 ++++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-26-16-42-31.gh-issue-114610.S18Vuz.rst diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 27c6b4e367a050f..44fea525b6cac74 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -313,7 +313,14 @@ def with_name(self, name): def with_stem(self, stem): """Return a new path with the stem changed.""" - return self.with_name(stem + self.suffix) + suffix = self.suffix + if not suffix: + return self.with_name(stem) + elif not stem: + # If the suffix is non-empty, we can't make the stem empty. + raise ValueError(f"{self!r} has a non-empty suffix") + else: + return self.with_name(stem + suffix) def with_suffix(self, suffix): """Return a new path with the file suffix changed. If the path @@ -324,6 +331,7 @@ def with_suffix(self, suffix): if not suffix: return self.with_name(stem) elif not stem: + # If the stem is empty, we can't make the suffix non-empty. raise ValueError(f"{self!r} has an empty name") elif suffix.startswith('.') and len(suffix) > 1: return self.with_name(stem + suffix) diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 1d30deca8f7a1bb..5bfb76f85c79094 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -957,6 +957,8 @@ def test_with_stem_empty(self): self.assertEqual(P('/').with_stem('d'), P('/d')) self.assertEqual(P('a/b').with_stem(''), P('a/')) self.assertEqual(P('a/b').with_stem('.'), P('a/.')) + self.assertRaises(ValueError, P('foo.gz').with_stem, '') + self.assertRaises(ValueError, P('/a/b/foo.gz').with_stem, '') def test_with_stem_seps(self): P = self.cls diff --git a/Misc/NEWS.d/next/Library/2024-01-26-16-42-31.gh-issue-114610.S18Vuz.rst b/Misc/NEWS.d/next/Library/2024-01-26-16-42-31.gh-issue-114610.S18Vuz.rst new file mode 100644 index 000000000000000..519aede72aaf29a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-26-16-42-31.gh-issue-114610.S18Vuz.rst @@ -0,0 +1,4 @@ +Fix bug where :meth:`pathlib.PurePath.with_stem` converted a non-empty path +suffix to a stem when given an empty *stem* argument. It now raises +:exc:`ValueError`, just like :meth:`pathlib.PurePath.with_suffix` does when +called on a path with an empty stem, given a non-empty *suffix* argument. From 948acd6ed856251dc5889cc34cf7a58210c4f9a9 Mon Sep 17 00:00:00 2001 From: Jay Ting <65202977+jayasting98@users.noreply.github.com> Date: Sun, 25 Feb 2024 07:34:45 +0800 Subject: [PATCH 436/507] gh-115323: Add meaningful error message for using bytearray.extend with str (#115332) Perform str check after TypeError is raised --------- Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu> --- Lib/test/test_bytes.py | 7 +++++++ .../2024-02-12-23-29-17.gh-issue-115323.3t6687.rst | 2 ++ Objects/bytearrayobject.c | 4 ++++ 3 files changed, 13 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-12-23-29-17.gh-issue-115323.3t6687.rst diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index a3804a945f2e3a4..71bb1e75c6affd7 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -1599,6 +1599,13 @@ def test_extend(self): a = bytearray(b'') a.extend([Indexable(ord('a'))]) self.assertEqual(a, b'a') + a = bytearray(b'abc') + self.assertRaisesRegex(TypeError, # Override for string. + "expected iterable of integers; got: 'str'", + a.extend, 'def') + self.assertRaisesRegex(TypeError, # But not for others. + "can't extend bytearray with float", + a.extend, 1.0) def test_remove(self): b = bytearray(b'hello') diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-12-23-29-17.gh-issue-115323.3t6687.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-12-23-29-17.gh-issue-115323.3t6687.rst new file mode 100644 index 000000000000000..171855608fbc6a2 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-12-23-29-17.gh-issue-115323.3t6687.rst @@ -0,0 +1,2 @@ +Make error message more meaningful for when :meth:`bytearray.extend` is +called with a :class:`str` object. diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index acc59b926448ca4..5e3b3affbc76c53 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1729,6 +1729,10 @@ bytearray_extend(PyByteArrayObject *self, PyObject *iterable_of_ints) while ((item = PyIter_Next(it)) != NULL) { if (! _getbytevalue(item, &value)) { + if (PyErr_ExceptionMatches(PyExc_TypeError) && PyUnicode_Check(iterable_of_ints)) { + PyErr_Format(PyExc_TypeError, + "expected iterable of integers; got: 'str'"); + } Py_DECREF(item); Py_DECREF(it); Py_DECREF(bytearray_obj); From f7455864f22369cc23bf3428624f310305cac999 Mon Sep 17 00:00:00 2001 From: Adorilson Bezerra <adorilson@gmail.com> Date: Sun, 25 Feb 2024 00:16:19 +0000 Subject: [PATCH 437/507] Erase some unnecessary quotes on data model doc (#113521) Thanks to Pedro Arthur Duarte (pedroarthur.jedi at gmail.com) for help with this bug. --- Doc/library/array.rst | 1 - Doc/reference/datamodel.rst | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Doc/library/array.rst b/Doc/library/array.rst index 043badf05ffc126..cdf21db8779fe8f 100644 --- a/Doc/library/array.rst +++ b/Doc/library/array.rst @@ -279,4 +279,3 @@ Examples:: `NumPy <https://numpy.org/>`_ The NumPy package defines another array type. - diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index afeb6596fbb9787..6438abbba10e378 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -34,7 +34,7 @@ represented by objects.) Every object has an identity, a type and a value. An object's *identity* never changes once it has been created; you may think of it as the object's address in -memory. The ':keyword:`is`' operator compares the identity of two objects; the +memory. The :keyword:`is` operator compares the identity of two objects; the :func:`id` function returns an integer representing its identity. .. impl-detail:: @@ -81,7 +81,7 @@ are still reachable. Note that the use of the implementation's tracing or debugging facilities may keep objects alive that would normally be collectable. Also note that catching -an exception with a ':keyword:`try`...\ :keyword:`except`' statement may keep +an exception with a :keyword:`try`...\ :keyword:`except` statement may keep objects alive. Some objects contain references to "external" resources such as open files or @@ -89,8 +89,8 @@ windows. It is understood that these resources are freed when the object is garbage-collected, but since garbage collection is not guaranteed to happen, such objects also provide an explicit way to release the external resource, usually a :meth:`!close` method. Programs are strongly recommended to explicitly -close such objects. The ':keyword:`try`...\ :keyword:`finally`' statement -and the ':keyword:`with`' statement provide convenient ways to do this. +close such objects. The :keyword:`try`...\ :keyword:`finally` statement +and the :keyword:`with` statement provide convenient ways to do this. .. index:: single: container From 5770006ffac2abd4f1c9fd33bf5015c9ef023576 Mon Sep 17 00:00:00 2001 From: Oh seungmin <tmdals179@gmail.com> Date: Sun, 25 Feb 2024 16:59:35 +0900 Subject: [PATCH 438/507] Add an example of of custom `__repr__` (#112761) Added to repr entry in Doc/library/functions.rst. --------- Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu> --- Doc/library/functions.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 27fce5aa0f1a636..a4852b922b65b3c 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -1569,6 +1569,16 @@ are always available. They are listed here in alphabetical order. If :func:`sys.displayhook` is not accessible, this function will raise :exc:`RuntimeError`. + This class has a custom representation that can be evaluated:: + + class Person: + def __init__(self, name, age): + self.name = name + self.age = age + + def __repr__(self): + return f"Person('{self.name}', {self.age})" + .. function:: reversed(seq) From 79811ededd160b6e8bcfbe4b0f9d5b4589280f19 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 25 Feb 2024 11:31:03 +0200 Subject: [PATCH 439/507] gh-115886: Handle embedded null characters in shared memory name (GH-115887) shm_open() and shm_unlink() now check for embedded null characters in the name and raise an error instead of silently truncating it. --- Lib/test/_test_multiprocessing.py | 17 ++++++++++++++++- ...24-02-24-18-48-14.gh-issue-115886.rgM6AF.rst | 2 ++ Modules/_multiprocessing/posixshmem.c | 15 +++++++++++++-- 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-24-18-48-14.gh-issue-115886.rgM6AF.rst diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index e4183eea959dd58..f70a693e641b4e7 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -3971,6 +3971,21 @@ def _new_shm_name(self, prefix): # test_multiprocessing_spawn, etc) in parallel. return prefix + str(os.getpid()) + def test_shared_memory_name_with_embedded_null(self): + name_tsmb = self._new_shm_name('test01_null') + sms = shared_memory.SharedMemory(name_tsmb, create=True, size=512) + self.addCleanup(sms.unlink) + with self.assertRaises(ValueError): + shared_memory.SharedMemory(name_tsmb + '\0a', create=False, size=512) + if shared_memory._USE_POSIX: + orig_name = sms._name + try: + sms._name = orig_name + '\0a' + with self.assertRaises(ValueError): + sms.unlink() + finally: + sms._name = orig_name + def test_shared_memory_basics(self): name_tsmb = self._new_shm_name('test01_tsmb') sms = shared_memory.SharedMemory(name_tsmb, create=True, size=512) @@ -4105,7 +4120,7 @@ def test_shared_memory_recreate(self): self.addCleanup(shm2.unlink) self.assertEqual(shm2._name, names[1]) - def test_invalid_shared_memory_cration(self): + def test_invalid_shared_memory_creation(self): # Test creating a shared memory segment with negative size with self.assertRaises(ValueError): sms_invalid = shared_memory.SharedMemory(create=True, size=-1) diff --git a/Misc/NEWS.d/next/Library/2024-02-24-18-48-14.gh-issue-115886.rgM6AF.rst b/Misc/NEWS.d/next/Library/2024-02-24-18-48-14.gh-issue-115886.rgM6AF.rst new file mode 100644 index 000000000000000..9688f713d5ba7b8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-24-18-48-14.gh-issue-115886.rgM6AF.rst @@ -0,0 +1,2 @@ +Fix silent truncation of the name with an embedded null character in +:class:`multiprocessing.shared_memory.SharedMemory`. diff --git a/Modules/_multiprocessing/posixshmem.c b/Modules/_multiprocessing/posixshmem.c index 425ce10075c1565..4ab15fa65736652 100644 --- a/Modules/_multiprocessing/posixshmem.c +++ b/Modules/_multiprocessing/posixshmem.c @@ -11,6 +11,7 @@ posixshmem - A Python extension that provides shm_open() and shm_unlink() #include <Python.h> +#include <string.h> // strlen() #include <errno.h> // EINTR #ifdef HAVE_SYS_MMAN_H # include <sys/mman.h> // shm_open(), shm_unlink() @@ -48,10 +49,15 @@ _posixshmem_shm_open_impl(PyObject *module, PyObject *path, int flags, { int fd; int async_err = 0; - const char *name = PyUnicode_AsUTF8AndSize(path, NULL); + Py_ssize_t name_size; + const char *name = PyUnicode_AsUTF8AndSize(path, &name_size); if (name == NULL) { return -1; } + if (strlen(name) != (size_t)name_size) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + return -1; + } do { Py_BEGIN_ALLOW_THREADS fd = shm_open(name, flags, mode); @@ -87,10 +93,15 @@ _posixshmem_shm_unlink_impl(PyObject *module, PyObject *path) { int rv; int async_err = 0; - const char *name = PyUnicode_AsUTF8AndSize(path, NULL); + Py_ssize_t name_size; + const char *name = PyUnicode_AsUTF8AndSize(path, &name_size); if (name == NULL) { return NULL; } + if (strlen(name) != (size_t)name_size) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + return NULL; + } do { Py_BEGIN_ALLOW_THREADS rv = shm_unlink(name); From a00b41b9e9257e3c779e3ef5d5d5f0cef9223a30 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 25 Feb 2024 11:45:56 +0200 Subject: [PATCH 440/507] gh-101100: Fix Sphinx warnings in `whatsnew/2.0.rst` (#112351) Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com> --- Doc/tools/.nitignore | 1 - Doc/whatsnew/2.0.rst | 105 ++++++++++++++++++++++--------------------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index eb45413d7cef787..344b0c9b679df00 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -79,7 +79,6 @@ Doc/reference/compound_stmts.rst Doc/reference/datamodel.rst Doc/tutorial/datastructures.rst Doc/using/windows.rst -Doc/whatsnew/2.0.rst Doc/whatsnew/2.1.rst Doc/whatsnew/2.4.rst Doc/whatsnew/2.5.rst diff --git a/Doc/whatsnew/2.0.rst b/Doc/whatsnew/2.0.rst index af8171487fbcfa2..1a949ec40358072 100644 --- a/Doc/whatsnew/2.0.rst +++ b/Doc/whatsnew/2.0.rst @@ -217,13 +217,13 @@ often use the ``codecs.lookup(encoding)`` function, which returns a was consumed. * *stream_reader* is a class that supports decoding input from a stream. - *stream_reader(file_obj)* returns an object that supports the :meth:`read`, - :meth:`readline`, and :meth:`readlines` methods. These methods will all + *stream_reader(file_obj)* returns an object that supports the :meth:`!read`, + :meth:`!readline`, and :meth:`!readlines` methods. These methods will all translate from the given encoding and return Unicode strings. * *stream_writer*, similarly, is a class that supports encoding output to a stream. *stream_writer(file_obj)* returns an object that supports the - :meth:`write` and :meth:`writelines` methods. These methods expect Unicode + :meth:`!write` and :meth:`!writelines` methods. These methods expect Unicode strings, translating them to the given encoding on output. For example, the following code writes a Unicode string into a file, encoding @@ -356,8 +356,8 @@ variable ``a`` by 2, equivalent to the slightly lengthier ``a = a + 2``. The full list of supported assignment operators is ``+=``, ``-=``, ``*=``, ``/=``, ``%=``, ``**=``, ``&=``, ``|=``, ``^=``, ``>>=``, and ``<<=``. Python classes can override the augmented assignment operators by defining methods -named :meth:`__iadd__`, :meth:`__isub__`, etc. For example, the following -:class:`Number` class stores a number and supports using += to create a new +named :meth:`!__iadd__`, :meth:`!__isub__`, etc. For example, the following +:class:`!Number` class stores a number and supports using += to create a new instance with an incremented value. .. The empty groups below prevent conversion to guillemets. @@ -374,7 +374,7 @@ instance with an incremented value. n += 3 print n.value -The :meth:`__iadd__` special method is called with the value of the increment, +The :meth:`!__iadd__` special method is called with the value of the increment, and should return a new instance with an appropriately modified value; this return value is bound as the new value of the variable on the left-hand side. @@ -390,10 +390,10 @@ String Methods ============== Until now string-manipulation functionality was in the :mod:`string` module, -which was usually a front-end for the :mod:`strop` module written in C. The -addition of Unicode posed a difficulty for the :mod:`strop` module, because the +which was usually a front-end for the :mod:`!strop` module written in C. The +addition of Unicode posed a difficulty for the :mod:`!strop` module, because the functions would all need to be rewritten in order to accept either 8-bit or -Unicode strings. For functions such as :func:`string.replace`, which takes 3 +Unicode strings. For functions such as :func:`!string.replace`, which takes 3 string arguments, that means eight possible permutations, and correspondingly complicated code. @@ -416,13 +416,13 @@ The old :mod:`string` module is still around for backwards compatibility, but it mostly acts as a front-end to the new string methods. Two methods which have no parallel in pre-2.0 versions, although they did exist -in JPython for quite some time, are :meth:`startswith` and :meth:`endswith`. +in JPython for quite some time, are :meth:`!startswith` and :meth:`!endswith`. ``s.startswith(t)`` is equivalent to ``s[:len(t)] == t``, while ``s.endswith(t)`` is equivalent to ``s[-len(t):] == t``. -One other method which deserves special mention is :meth:`join`. The -:meth:`join` method of a string receives one parameter, a sequence of strings, -and is equivalent to the :func:`string.join` function from the old :mod:`string` +One other method which deserves special mention is :meth:`!join`. The +:meth:`!join` method of a string receives one parameter, a sequence of strings, +and is equivalent to the :func:`!string.join` function from the old :mod:`string` module, with the arguments reversed. In other words, ``s.join(seq)`` is equivalent to the old ``string.join(seq, s)``. @@ -503,9 +503,9 @@ Minor Language Changes A new syntax makes it more convenient to call a given function with a tuple of arguments and/or a dictionary of keyword arguments. In Python 1.5 and earlier, -you'd use the :func:`apply` built-in function: ``apply(f, args, kw)`` calls the -function :func:`f` with the argument tuple *args* and the keyword arguments in -the dictionary *kw*. :func:`apply` is the same in 2.0, but thanks to a patch +you'd use the :func:`!apply` built-in function: ``apply(f, args, kw)`` calls the +function :func:`!f` with the argument tuple *args* and the keyword arguments in +the dictionary *kw*. :func:`!apply` is the same in 2.0, but thanks to a patch from Greg Ewing, ``f(*args, **kw)`` is a shorter and clearer way to achieve the same effect. This syntax is symmetrical with the syntax for defining functions:: @@ -518,7 +518,7 @@ functions:: The ``print`` statement can now have its output directed to a file-like object by following the ``print`` with ``>> file``, similar to the redirection operator in Unix shells. Previously you'd either have to use the -:meth:`write` method of the file-like object, which lacks the convenience and +:meth:`!write` method of the file-like object, which lacks the convenience and simplicity of ``print``, or you could assign a new value to ``sys.stdout`` and then restore the old value. For sending output to standard error, it's much easier to write this:: @@ -540,7 +540,7 @@ Previously there was no way to implement a class that overrode Python's built-in true if *obj* is present in the sequence *seq*; Python computes this by simply trying every index of the sequence until either *obj* is found or an :exc:`IndexError` is encountered. Moshe Zadka contributed a patch which adds a -:meth:`__contains__` magic method for providing a custom implementation for +:meth:`!__contains__` magic method for providing a custom implementation for :keyword:`!in`. Additionally, new built-in objects written in C can define what :keyword:`!in` means for them via a new slot in the sequence protocol. @@ -562,7 +562,7 @@ the python-dev mailing list for the discussion leading up to this implementation, and some useful relevant links. Note that comparisons can now also raise exceptions. In earlier versions of Python, a comparison operation such as ``cmp(a,b)`` would always produce an answer, even if a user-defined -:meth:`__cmp__` method encountered an error, since the resulting exception would +:meth:`!__cmp__` method encountered an error, since the resulting exception would simply be silently swallowed. .. Starting URL: @@ -607,7 +607,7 @@ seq1, seq2)`` is that :func:`map` pads the sequences with ``None`` if the sequences aren't all of the same length, while :func:`zip` truncates the returned list to the length of the shortest argument sequence. -The :func:`int` and :func:`long` functions now accept an optional "base" +The :func:`int` and :func:`!long` functions now accept an optional "base" parameter when the first argument is a string. ``int('123', 10)`` returns 123, while ``int('123', 16)`` returns 291. ``int(123, 16)`` raises a :exc:`TypeError` exception with the message "can't convert non-string with @@ -620,8 +620,8 @@ would be ``(2, 0, 1, 'beta', 1)``. *level* is a string such as ``"alpha"``, ``"beta"``, or ``"final"`` for a final release. Dictionaries have an odd new method, ``setdefault(key, default)``, which -behaves similarly to the existing :meth:`get` method. However, if the key is -missing, :meth:`setdefault` both returns the value of *default* as :meth:`get` +behaves similarly to the existing :meth:`!get` method. However, if the key is +missing, :meth:`!setdefault` both returns the value of *default* as :meth:`!get` would do, and also inserts it into the dictionary as the value for *key*. Thus, the following lines of code:: @@ -656,7 +656,7 @@ break. The change which will probably break the most code is tightening up the arguments accepted by some methods. Some methods would take multiple arguments and treat them as a tuple, particularly various list methods such as -:meth:`append` and :meth:`insert`. In earlier versions of Python, if ``L`` is +:meth:`!append` and :meth:`!insert`. In earlier versions of Python, if ``L`` is a list, ``L.append( 1,2 )`` appends the tuple ``(1,2)`` to the list. In Python 2.0 this causes a :exc:`TypeError` exception to be raised, with the message: 'append requires exactly 1 argument; 2 given'. The fix is to simply add an @@ -693,7 +693,7 @@ advantage of this fact will break in 2.0. Some work has been done to make integers and long integers a bit more interchangeable. In 1.5.2, large-file support was added for Solaris, to allow -reading files larger than 2 GiB; this made the :meth:`tell` method of file +reading files larger than 2 GiB; this made the :meth:`!tell` method of file objects return a long integer instead of a regular integer. Some code would subtract two file offsets and attempt to use the result to multiply a sequence or slice a string, but this raised a :exc:`TypeError`. In 2.0, long integers @@ -701,7 +701,7 @@ can be used to multiply or slice a sequence, and it'll behave as you'd intuitively expect it to; ``3L * 'abc'`` produces 'abcabcabc', and ``(0,1,2,3)[2L:4L]`` produces (2,3). Long integers can also be used in various contexts where previously only integers were accepted, such as in the -:meth:`seek` method of file objects, and in the formats supported by the ``%`` +:meth:`!seek` method of file objects, and in the formats supported by the ``%`` operator (``%d``, ``%i``, ``%x``, etc.). For example, ``"%d" % 2L**64`` will produce the string ``18446744073709551616``. @@ -715,7 +715,7 @@ digit. Taking the :func:`repr` of a float now uses a different formatting precision than :func:`str`. :func:`repr` uses ``%.17g`` format string for C's -:func:`sprintf`, while :func:`str` uses ``%.12g`` as before. The effect is that +:func:`!sprintf`, while :func:`str` uses ``%.12g`` as before. The effect is that :func:`repr` may occasionally show more decimal places than :func:`str`, for certain numbers. For example, the number 8.1 can't be represented exactly in binary, so ``repr(8.1)`` is ``'8.0999999999999996'``, while str(8.1) is @@ -723,7 +723,7 @@ binary, so ``repr(8.1)`` is ``'8.0999999999999996'``, while str(8.1) is The ``-X`` command-line option, which turned all standard exceptions into strings instead of classes, has been removed; the standard exceptions will now -always be classes. The :mod:`exceptions` module containing the standard +always be classes. The :mod:`!exceptions` module containing the standard exceptions was translated from Python to a built-in C module, written by Barry Warsaw and Fredrik Lundh. @@ -879,11 +879,11 @@ joins the basic set of Python documentation. XML Modules =========== -Python 1.5.2 included a simple XML parser in the form of the :mod:`xmllib` +Python 1.5.2 included a simple XML parser in the form of the :mod:`!xmllib` module, contributed by Sjoerd Mullender. Since 1.5.2's release, two different interfaces for processing XML have become common: SAX2 (version 2 of the Simple API for XML) provides an event-driven interface with some similarities to -:mod:`xmllib`, and the DOM (Document Object Model) provides a tree-based +:mod:`!xmllib`, and the DOM (Document Object Model) provides a tree-based interface, transforming an XML document into a tree of nodes that can be traversed and modified. Python 2.0 includes a SAX2 interface and a stripped-down DOM interface as part of the :mod:`xml` package. Here we will give a brief @@ -898,9 +898,9 @@ SAX2 Support SAX defines an event-driven interface for parsing XML. To use SAX, you must write a SAX handler class. Handler classes inherit from various classes provided by SAX, and override various methods that will then be called by the -XML parser. For example, the :meth:`startElement` and :meth:`endElement` +XML parser. For example, the :meth:`~xml.sax.handler.ContentHandler.startElement` and :meth:`~xml.sax.handler.ContentHandler.endElement` methods are called for every starting and end tag encountered by the parser, the -:meth:`characters` method is called for every chunk of character data, and so +:meth:`~xml.sax.handler.ContentHandler.characters` method is called for every chunk of character data, and so forth. The advantage of the event-driven approach is that the whole document doesn't @@ -940,8 +940,8 @@ DOM Support ----------- The Document Object Model is a tree-based representation for an XML document. A -top-level :class:`Document` instance is the root of the tree, and has a single -child which is the top-level :class:`Element` instance. This :class:`Element` +top-level :class:`!Document` instance is the root of the tree, and has a single +child which is the top-level :class:`!Element` instance. This :class:`!Element` has children nodes representing character data and any sub-elements, which may have further children of their own, and so forth. Using the DOM you can traverse the resulting tree any way you like, access element and attribute @@ -955,18 +955,18 @@ simply writing ``<tag1>``...\ ``</tag1>`` to a file. The DOM implementation included with Python lives in the :mod:`xml.dom.minidom` module. It's a lightweight implementation of the Level 1 DOM with support for -XML namespaces. The :func:`parse` and :func:`parseString` convenience +XML namespaces. The :func:`!parse` and :func:`!parseString` convenience functions are provided for generating a DOM tree:: from xml.dom import minidom doc = minidom.parse('hamlet.xml') -``doc`` is a :class:`Document` instance. :class:`Document`, like all the other -DOM classes such as :class:`Element` and :class:`Text`, is a subclass of the -:class:`Node` base class. All the nodes in a DOM tree therefore support certain -common methods, such as :meth:`toxml` which returns a string containing the XML +``doc`` is a :class:`!Document` instance. :class:`!Document`, like all the other +DOM classes such as :class:`!Element` and :class:`Text`, is a subclass of the +:class:`!Node` base class. All the nodes in a DOM tree therefore support certain +common methods, such as :meth:`!toxml` which returns a string containing the XML representation of the node and its children. Each class also has special -methods of its own; for example, :class:`Element` and :class:`Document` +methods of its own; for example, :class:`!Element` and :class:`!Document` instances have a method to find all child elements with a given tag name. Continuing from the previous 2-line example:: @@ -995,7 +995,7 @@ its children can be easily modified by deleting, adding, or removing nodes:: root.insertBefore( root.childNodes[0], root.childNodes[20] ) Again, I will refer you to the Python documentation for a complete listing of -the different :class:`Node` classes and their various methods. +the different :class:`!Node` classes and their various methods. Relationship to PyXML @@ -1020,7 +1020,7 @@ features in PyXML include: * The xmlproc validating parser, written by Lars Marius Garshol. -* The :mod:`sgmlop` parser accelerator module, written by Fredrik Lundh. +* The :mod:`!sgmlop` parser accelerator module, written by Fredrik Lundh. .. ====================================================================== @@ -1031,7 +1031,7 @@ Module changes Lots of improvements and bugfixes were made to Python's extensive standard library; some of the affected modules include :mod:`readline`, :mod:`ConfigParser <configparser>`, :mod:`!cgi`, :mod:`calendar`, :mod:`posix`, :mod:`readline`, -:mod:`xmllib`, :mod:`!aifc`, :mod:`!chunk`, :mod:`wave`, :mod:`random`, :mod:`shelve`, +:mod:`!xmllib`, :mod:`!aifc`, :mod:`!chunk`, :mod:`wave`, :mod:`random`, :mod:`shelve`, and :mod:`!nntplib`. Consult the CVS logs for the exact patch-by-patch details. Brian Gallew contributed OpenSSL support for the :mod:`socket` module. OpenSSL @@ -1044,11 +1044,12 @@ were also changed to support ``https://`` URLs, though no one has implemented FTP or SMTP over SSL. The :mod:`httplib <http>` module has been rewritten by Greg Stein to support HTTP/1.1. + Backward compatibility with the 1.5 version of :mod:`!httplib` is provided, though using HTTP/1.1 features such as pipelining will require rewriting code to use a different set of interfaces. -The :mod:`Tkinter` module now supports Tcl/Tk version 8.1, 8.2, or 8.3, and +The :mod:`!Tkinter` module now supports Tcl/Tk version 8.1, 8.2, or 8.3, and support for the older 7.x versions has been dropped. The Tkinter module now supports displaying Unicode strings in Tk widgets. Also, Fredrik Lundh contributed an optimization which makes operations like ``create_line`` and @@ -1083,11 +1084,11 @@ module. calling :func:`atexit.register` with the function to be called on exit. (Contributed by Skip Montanaro.) -* :mod:`codecs`, :mod:`encodings`, :mod:`unicodedata`: Added as part of the new +* :mod:`codecs`, :mod:`!encodings`, :mod:`unicodedata`: Added as part of the new Unicode support. -* :mod:`filecmp`: Supersedes the old :mod:`cmp`, :mod:`cmpcache` and - :mod:`dircmp` modules, which have now become deprecated. (Contributed by Gordon +* :mod:`filecmp`: Supersedes the old :mod:`!cmp`, :mod:`!cmpcache` and + :mod:`!dircmp` modules, which have now become deprecated. (Contributed by Gordon MacMillan and Moshe Zadka.) * :mod:`gettext`: This module provides internationalization (I18N) and @@ -1105,7 +1106,7 @@ module. be passed to functions that expect ordinary strings, such as the :mod:`re` module. (Contributed by Sam Rushing, with some extensions by A.M. Kuchling.) -* :mod:`pyexpat`: An interface to the Expat XML parser. (Contributed by Paul +* :mod:`!pyexpat`: An interface to the Expat XML parser. (Contributed by Paul Prescod.) * :mod:`robotparser <urllib.robotparser>`: Parse a :file:`robots.txt` file, which is used for writing @@ -1117,7 +1118,7 @@ module. * :mod:`tabnanny`: A module/script to check Python source code for ambiguous indentation. (Contributed by Tim Peters.) -* :mod:`UserString`: A base class useful for deriving objects that behave like +* :mod:`!UserString`: A base class useful for deriving objects that behave like strings. * :mod:`webbrowser`: A module that provides a platform independent way to launch @@ -1184,13 +1185,13 @@ Deleted and Deprecated Modules ============================== A few modules have been dropped because they're obsolete, or because there are -now better ways to do the same thing. The :mod:`stdwin` module is gone; it was +now better ways to do the same thing. The :mod:`!stdwin` module is gone; it was for a platform-independent windowing toolkit that's no longer developed. A number of modules have been moved to the :file:`lib-old` subdirectory: -:mod:`cmp`, :mod:`cmpcache`, :mod:`dircmp`, :mod:`dump`, :mod:`find`, -:mod:`grep`, :mod:`packmail`, :mod:`poly`, :mod:`util`, :mod:`whatsound`, -:mod:`zmod`. If you have code which relies on a module that's been moved to +:mod:`!cmp`, :mod:`!cmpcache`, :mod:`!dircmp`, :mod:`!dump`, :mod:`!find`, +:mod:`!grep`, :mod:`!packmail`, :mod:`!poly`, :mod:`!util`, :mod:`!whatsound`, +:mod:`!zmod`. If you have code which relies on a module that's been moved to :file:`lib-old`, you can simply add that directory to ``sys.path`` to get them back, but you're encouraged to update any code that uses these modules. From 6550b548138ed996e1098c4271f5b7df56f02ab8 Mon Sep 17 00:00:00 2001 From: Arjun <ccldarjun@icloud.com> Date: Sun, 25 Feb 2024 02:33:28 -0800 Subject: [PATCH 441/507] bpo-14322: added test case for invalid update to hmac (#26636) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Lib/test/test_hmac.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py index a39a2c45ebc2e28..1502fba9f3e8b8b 100644 --- a/Lib/test/test_hmac.py +++ b/Lib/test/test_hmac.py @@ -479,6 +479,14 @@ def test_exercise_all_methods(self): self.fail("Exception raised during normal usage of HMAC class.") +class UpdateTestCase(unittest.TestCase): + @hashlib_helper.requires_hashdigest('sha256') + def test_with_str_update(self): + with self.assertRaises(TypeError): + h = hmac.new(b"key", digestmod='sha256') + h.update("invalid update") + + class CopyTestCase(unittest.TestCase): @hashlib_helper.requires_hashdigest('sha256') From cb287d342139509e03a2dbe5ea2608627fd3a350 Mon Sep 17 00:00:00 2001 From: mauricelambert <50479118+mauricelambert@users.noreply.github.com> Date: Sun, 25 Feb 2024 11:55:57 +0000 Subject: [PATCH 442/507] gh-103417: Fix the scheduler example (GH-111497) Arguments to enterabs() are specified as Unix time. If the scheduler use the time.monotonic timer, the code will take decades to complete. --- Doc/library/sched.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/sched.rst b/Doc/library/sched.rst index 01bac5afd0b9b38..4c980dd97f9394f 100644 --- a/Doc/library/sched.rst +++ b/Doc/library/sched.rst @@ -36,7 +36,7 @@ scheduler: Example:: >>> import sched, time - >>> s = sched.scheduler(time.monotonic, time.sleep) + >>> s = sched.scheduler(time.time, time.sleep) >>> def print_time(a='default'): ... print("From print_time", time.time(), a) ... From a0a8d9ffe0ddb0f55aeb02801f48e722c2660ed3 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger <rhettinger@users.noreply.github.com> Date: Sun, 25 Feb 2024 06:32:14 -0600 Subject: [PATCH 443/507] gh-113479: Link to workaround for subtle issue with takewhile() (gh-115890) --- Doc/library/itertools.rst | 74 ++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 338a5f9615aae31..42e70404b306b0c 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -688,6 +688,14 @@ loops that truncate the stream. else: break + Note, the element that first fails the predicate condition is + consumed from the input iterator and there is no way to access it. + This could be an issue if an application wants to further consume the + input iterator after takewhile has been run to exhaustion. To work + around this problem, consider using `more-iterools before_and_after() + <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.before_and_after>`_ + instead. + .. function:: tee(iterable, n=2) @@ -1004,32 +1012,6 @@ which incur interpreter overhead. except exception: pass - def before_and_after(predicate, it): - """ Variant of takewhile() that allows complete - access to the remainder of the iterator. - - >>> it = iter('ABCdEfGhI') - >>> all_upper, remainder = before_and_after(str.isupper, it) - >>> ''.join(all_upper) - 'ABC' - >>> ''.join(remainder) # takewhile() would lose the 'd' - 'dEfGhI' - - Note that the true iterator must be fully consumed - before the remainder iterator can generate valid results. - """ - it = iter(it) - transition = [] - - def true_iterator(): - for elem in it: - if predicate(elem): - yield elem - else: - transition.append(elem) - return - - return true_iterator(), chain(transition, it) The following recipes have a more mathematical flavor: @@ -1543,13 +1525,6 @@ The following recipes have a more mathematical flavor: >>> list(odds) [1, 3, 5, 7, 9] - >>> it = iter('ABCdEfGhI') - >>> all_upper, remainder = before_and_after(str.isupper, it) - >>> ''.join(all_upper) - 'ABC' - >>> ''.join(remainder) - 'dEfGhI' - >>> list(subslices('ABCD')) ['A', 'AB', 'ABC', 'ABCD', 'B', 'BC', 'BCD', 'C', 'CD', 'D'] @@ -1640,6 +1615,32 @@ The following recipes have a more mathematical flavor: result.append(pool[-1-n]) return tuple(result) + def before_and_after(predicate, it): + """ Variant of takewhile() that allows complete + access to the remainder of the iterator. + + >>> it = iter('ABCdEfGhI') + >>> all_upper, remainder = before_and_after(str.isupper, it) + >>> ''.join(all_upper) + 'ABC' + >>> ''.join(remainder) # takewhile() would lose the 'd' + 'dEfGhI' + + Note that the true iterator must be fully consumed + before the remainder iterator can generate valid results. + """ + it = iter(it) + transition = [] + + def true_iterator(): + for elem in it: + if predicate(elem): + yield elem + else: + transition.append(elem) + return + + return true_iterator(), chain(transition, it) .. doctest:: :hide: @@ -1669,3 +1670,10 @@ The following recipes have a more mathematical flavor: >>> combos = list(combinations(iterable, r)) >>> all(nth_combination(iterable, r, i) == comb for i, comb in enumerate(combos)) True + + >>> it = iter('ABCdEfGhI') + >>> all_upper, remainder = before_and_after(str.isupper, it) + >>> ''.join(all_upper) + 'ABC' + >>> ''.join(remainder) + 'dEfGhI' From 9402ea63f7897ecda7036f667949ad17f4208e5b Mon Sep 17 00:00:00 2001 From: Laurie O <laurie_opperman@hotmail.com> Date: Mon, 26 Feb 2024 02:53:21 +1000 Subject: [PATCH 444/507] gh-96471: Correct docs for queue shutdown (#115838) --- Doc/library/queue.rst | 7 +++---- Lib/queue.py | 12 +++++------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Doc/library/queue.rst b/Doc/library/queue.rst index 1421fc2e552f0e3..f2a6dbf589fd872 100644 --- a/Doc/library/queue.rst +++ b/Doc/library/queue.rst @@ -187,11 +187,12 @@ fully processed by daemon consumer threads. processed (meaning that a :meth:`task_done` call was received for every item that had been :meth:`put` into the queue). + ``shutdown(immediate=True)`` calls :meth:`task_done` for each remaining item + in the queue. + Raises a :exc:`ValueError` if called more times than there were items placed in the queue. - Raises :exc:`ShutDown` if the queue has been shut down immediately. - .. method:: Queue.join() @@ -202,8 +203,6 @@ fully processed by daemon consumer threads. indicate that the item was retrieved and all work on it is complete. When the count of unfinished tasks drops to zero, :meth:`join` unblocks. - Raises :exc:`ShutDown` if the queue has been shut down immediately. - Example of how to wait for enqueued tasks to be completed:: diff --git a/Lib/queue.py b/Lib/queue.py index 467ff4fcecb1340..18fad8df08e4696 100644 --- a/Lib/queue.py +++ b/Lib/queue.py @@ -72,10 +72,11 @@ def task_done(self): have been processed (meaning that a task_done() call was received for every item that had been put() into the queue). + shutdown(immediate=True) calls task_done() for each remaining item in + the queue. + Raises a ValueError if called more times than there were items placed in the queue. - - Raises ShutDown if the queue has been shut down immediately. ''' with self.all_tasks_done: unfinished = self.unfinished_tasks - 1 @@ -93,8 +94,6 @@ def join(self): to indicate the item was retrieved and all work on it is complete. When the count of unfinished tasks drops to zero, join() unblocks. - - Raises ShutDown if the queue has been shut down immediately. ''' with self.all_tasks_done: while self.unfinished_tasks: @@ -227,18 +226,17 @@ def get_nowait(self): return self.get(block=False) def shutdown(self, immediate=False): - '''Shut-down the queue, making queue gets and puts raise. + '''Shut-down the queue, making queue gets and puts raise ShutDown. By default, gets will only raise once the queue is empty. Set 'immediate' to True to make gets raise immediately instead. All blocked callers of put() will be unblocked, and also get() - and join() if 'immediate'. The ShutDown exception is raised. + and join() if 'immediate'. ''' with self.mutex: self.is_shutdown = True if immediate: - n_items = self._qsize() while self._qsize(): self._get() if self.unfinished_tasks > 0: From c40b5b97fdac5099f670589b42c9038d7b23f2e5 Mon Sep 17 00:00:00 2001 From: Matan Perelman <matan1008@gmail.com> Date: Sun, 25 Feb 2024 19:17:54 +0200 Subject: [PATCH 445/507] bpo-31116: Add Z85 variant to base64 (GH-30598) Z85 specification: https://rfc.zeromq.org/spec/32/ --- Doc/library/base64.rst | 18 ++++ Doc/whatsnew/3.13.rst | 8 ++ Lib/base64.py | 29 ++++++- Lib/test/test_base64.py | 87 ++++++++++++++++++- .../2022-01-14-10-50-17.bpo-31116.0bduV9.rst | 1 + 5 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-01-14-10-50-17.bpo-31116.0bduV9.rst diff --git a/Doc/library/base64.rst b/Doc/library/base64.rst index d5b6af8c1928eff..e596893358f3fb3 100644 --- a/Doc/library/base64.rst +++ b/Doc/library/base64.rst @@ -244,6 +244,24 @@ The modern interface provides: .. versionadded:: 3.4 +.. function:: z85encode(s) + + Encode the :term:`bytes-like object` *s* using Z85 (as used in ZeroMQ) + and return the encoded :class:`bytes`. See `Z85 specification + <https://rfc.zeromq.org/spec/32/>`_ for more information. + + .. versionadded:: 3.13 + + +.. function:: z85decode(s) + + Decode the Z85-encoded :term:`bytes-like object` or ASCII string *s* and + return the decoded :class:`bytes`. See `Z85 specification + <https://rfc.zeromq.org/spec/32/>`_ for more information. + + .. versionadded:: 3.13 + + The legacy interface: .. function:: decode(input, output) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 40823587fb94177..a393a1df71a65ca 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -224,6 +224,14 @@ asyncio the buffer size. (Contributed by Jamie Phan in :gh:`115199`.) +base64 +--- + +* Add :func:`base64.z85encode` and :func:`base64.z85decode` functions which allow encoding + and decoding z85 data. + See `Z85 specification <https://rfc.zeromq.org/spec/32/>`_ for more information. + (Contributed by Matan Perelman in :gh:`75299`.) + copy ---- diff --git a/Lib/base64.py b/Lib/base64.py index e3e983b3064fe7b..25164d1a1df4fc8 100755 --- a/Lib/base64.py +++ b/Lib/base64.py @@ -18,7 +18,7 @@ 'b64encode', 'b64decode', 'b32encode', 'b32decode', 'b32hexencode', 'b32hexdecode', 'b16encode', 'b16decode', # Base85 and Ascii85 encodings - 'b85encode', 'b85decode', 'a85encode', 'a85decode', + 'b85encode', 'b85decode', 'a85encode', 'a85decode', 'z85encode', 'z85decode', # Standard Base64 encoding 'standard_b64encode', 'standard_b64decode', # Some common Base64 alternatives. As referenced by RFC 3458, see thread @@ -497,6 +497,33 @@ def b85decode(b): result = result[:-padding] return result +_z85alphabet = (b'0123456789abcdefghijklmnopqrstuvwxyz' + b'ABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#') +# Translating b85 valid but z85 invalid chars to b'\x00' is required +# to prevent them from being decoded as b85 valid chars. +_z85_b85_decode_diff = b';_`|~' +_z85_decode_translation = bytes.maketrans( + _z85alphabet + _z85_b85_decode_diff, + _b85alphabet + b'\x00' * len(_z85_b85_decode_diff) +) +_z85_encode_translation = bytes.maketrans(_b85alphabet, _z85alphabet) + +def z85encode(s): + """Encode bytes-like object b in z85 format and return a bytes object.""" + return b85encode(s).translate(_z85_encode_translation) + +def z85decode(s): + """Decode the z85-encoded bytes-like object or ASCII string b + + The result is returned as a bytes object. + """ + s = _bytes_from_decode_data(s) + s = s.translate(_z85_decode_translation) + try: + return b85decode(s) + except ValueError as e: + raise ValueError(e.args[0].replace('base85', 'z85')) from None + # Legacy interface. This code could be cleaned up since I don't believe # binascii has any line length limitations. It just doesn't seem worth it # though. The files should be opened in binary mode. diff --git a/Lib/test/test_base64.py b/Lib/test/test_base64.py index f6171d3ed4efd73..409c8c109e885f6 100644 --- a/Lib/test/test_base64.py +++ b/Lib/test/test_base64.py @@ -545,6 +545,40 @@ def test_b85encode(self): self.check_other_types(base64.b85encode, b"www.python.org", b'cXxL#aCvlSZ*DGca%T') + def test_z85encode(self): + eq = self.assertEqual + + tests = { + b'': b'', + 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""" + b"""oIFnTp/ga?r8($2sxO*itWpVyu$0IOwmYv=xLzi%y&a6dAb/]tBAI+J""" + b"""CZjQZE0{D[FpSr8GOteoH(41EJe-<UKDCY&L:dM3N3<zjOsMmzPRn9P""" + b"""Q[%@^ShV!$TGwUeU^7HuW6^uKXvGh.YUh4]Z})[9-kP:p:JqPF+*1CV""" + b"""^9Zp<!yAd4/Xb0k*$*&A&nJXQ<MkK!>&}x#)cTlf[Bu8v].4}L}1:^-""" + b"""@qDP""", + b"""abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ""" + b"""0123456789!@#0^&*();:<>,. []{}""": + b"""vpA.SwObN*x>?B1zeKohADlbxB-}$ND3R+ylQTvjm[uizoh55PpF:[^""" + b"""q=D:$s6eQefFLssg=mfIi5@cEbqrBJdKV-ciY]OSe*aw7DWL""", + b'no padding..': b'zF{UpvpS[.zF7NO', + b'zero compression\x00\x00\x00\x00': b'Ds.bnay/tbAb]JhB7]Mg00000', + b'zero compression\x00\x00\x00': b'Ds.bnay/tbAb]JhB7]Mg0000', + b"""Boundary:\x00\x00\x00\x00""": b"""lt}0:wmoI7iSGcW00""", + b'Space compr: ': b'q/DePwGUG3ze:IRarR^H', + b'\xff': b'@@', + b'\xff'*2: b'%nJ', + b'\xff'*3: b'%nS9', + b'\xff'*4: b'%nSc0', + } + + for data, res in tests.items(): + eq(base64.z85encode(data), res) + + self.check_other_types(base64.z85encode, b"www.python.org", + b'CxXl-AcVLsz/dgCA+t') + def test_a85decode(self): eq = self.assertEqual @@ -626,6 +660,41 @@ def test_b85decode(self): self.check_other_types(base64.b85decode, b'cXxL#aCvlSZ*DGca%T', b"www.python.org") + def test_z85decode(self): + eq = self.assertEqual + + tests = { + b'': b'', + b'CxXl-AcVLsz/dgCA+t': b'www.python.org', + b"""009c61o!#m2NH?C3>iWS5d]J*6CRx17-skh9337x""" + b"""ar.{NbQB=+c[cR@eg&FcfFLssg=mfIi5%2YjuU>)kTv.7l}6Nnnj=AD""" + b"""oIFnTp/ga?r8($2sxO*itWpVyu$0IOwmYv=xLzi%y&a6dAb/]tBAI+J""" + b"""CZjQZE0{D[FpSr8GOteoH(41EJe-<UKDCY&L:dM3N3<zjOsMmzPRn9P""" + b"""Q[%@^ShV!$TGwUeU^7HuW6^uKXvGh.YUh4]Z})[9-kP:p:JqPF+*1CV""" + b"""^9Zp<!yAd4/Xb0k*$*&A&nJXQ<MkK!>&}x#)cTlf[Bu8v].4}L}1:^-""" + b"""@qDP""": bytes(range(255)), + b"""vpA.SwObN*x>?B1zeKohADlbxB-}$ND3R+ylQTvjm[uizoh55PpF:[^""" + b"""q=D:$s6eQefFLssg=mfIi5@cEbqrBJdKV-ciY]OSe*aw7DWL""": + b"""abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ""" + b"""0123456789!@#0^&*();:<>,. []{}""", + b'zF{UpvpS[.zF7NO': b'no padding..', + b'Ds.bnay/tbAb]JhB7]Mg00000': b'zero compression\x00\x00\x00\x00', + b'Ds.bnay/tbAb]JhB7]Mg0000': b'zero compression\x00\x00\x00', + b"""lt}0:wmoI7iSGcW00""": b"""Boundary:\x00\x00\x00\x00""", + b'q/DePwGUG3ze:IRarR^H': b'Space compr: ', + b'@@': b'\xff', + b'%nJ': b'\xff'*2, + b'%nS9': b'\xff'*3, + b'%nSc0': b'\xff'*4, + } + + for data, res in tests.items(): + eq(base64.z85decode(data), res) + eq(base64.z85decode(data.decode("ascii")), res) + + self.check_other_types(base64.z85decode, b'CxXl-AcVLsz/dgCA+t', + b'www.python.org') + def test_a85_padding(self): eq = self.assertEqual @@ -707,6 +776,21 @@ def test_b85decode_errors(self): self.assertRaises(ValueError, base64.b85decode, b'|NsC') self.assertRaises(ValueError, base64.b85decode, b'|NsC1') + def test_z85decode_errors(self): + illegal = list(range(33)) + \ + list(b'"\',;_`|\\~') + \ + list(range(128, 256)) + for c in illegal: + with self.assertRaises(ValueError, msg=bytes([c])): + base64.z85decode(b'0000' + bytes([c])) + + # b'\xff\xff\xff\xff' encodes to b'%nSc0', the following will overflow: + self.assertRaises(ValueError, base64.z85decode, b'%') + self.assertRaises(ValueError, base64.z85decode, b'%n') + self.assertRaises(ValueError, base64.z85decode, b'%nS') + self.assertRaises(ValueError, base64.z85decode, b'%nSc') + self.assertRaises(ValueError, base64.z85decode, b'%nSc1') + def test_decode_nonascii_str(self): decode_funcs = (base64.b64decode, base64.standard_b64decode, @@ -714,7 +798,8 @@ def test_decode_nonascii_str(self): base64.b32decode, base64.b16decode, base64.b85decode, - base64.a85decode) + base64.a85decode, + base64.z85decode) for f in decode_funcs: self.assertRaises(ValueError, f, 'with non-ascii \xcb') diff --git a/Misc/NEWS.d/next/Library/2022-01-14-10-50-17.bpo-31116.0bduV9.rst b/Misc/NEWS.d/next/Library/2022-01-14-10-50-17.bpo-31116.0bduV9.rst new file mode 100644 index 000000000000000..d77a96b442bcbb7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-01-14-10-50-17.bpo-31116.0bduV9.rst @@ -0,0 +1 @@ +Add Z85 encoding to ``base64``. From 4827968af87d828554c3fadd97094c97798541b4 Mon Sep 17 00:00:00 2001 From: Malcolm Smith <smith@chaquo.com> Date: Sun, 25 Feb 2024 19:38:18 +0000 Subject: [PATCH 446/507] gh-71052: Enable test_concurrent_futures on platforms that lack multiprocessing (gh-115917) Enable test_concurrent_futures on platforms that support threading but not multiprocessing. --- Lib/multiprocessing/queues.py | 2 -- Lib/test/test___all__.py | 16 ---------------- Lib/test/test_concurrent_futures/__init__.py | 2 -- Lib/test/test_concurrent_futures/test_init.py | 7 +++++++ Lib/test/test_concurrent_futures/util.py | 8 +++++++- ...2024-02-25-15-58-28.gh-issue-71052.lxBjqY.rst | 2 ++ 6 files changed, 16 insertions(+), 21 deletions(-) create mode 100644 Misc/NEWS.d/next/Tests/2024-02-25-15-58-28.gh-issue-71052.lxBjqY.rst diff --git a/Lib/multiprocessing/queues.py b/Lib/multiprocessing/queues.py index 852ae87b2768612..925f043900004e7 100644 --- a/Lib/multiprocessing/queues.py +++ b/Lib/multiprocessing/queues.py @@ -20,8 +20,6 @@ from queue import Empty, Full -import _multiprocessing - from . import connection from . import context _ForkingPickler = context.reduction.ForkingPickler diff --git a/Lib/test/test___all__.py b/Lib/test/test___all__.py index c87cde4b3d1fab1..19dcbb207e914af 100644 --- a/Lib/test/test___all__.py +++ b/Lib/test/test___all__.py @@ -5,11 +5,6 @@ import sys import types -try: - import _multiprocessing -except ModuleNotFoundError: - _multiprocessing = None - if support.check_sanitizer(address=True, memory=True): SKIP_MODULES = frozenset(( @@ -36,17 +31,6 @@ class FailedImport(RuntimeError): class AllTest(unittest.TestCase): - def setUp(self): - # concurrent.futures uses a __getattr__ hook. Its __all__ triggers - # import of a submodule, which fails when _multiprocessing is not - # available. - if _multiprocessing is None: - sys.modules["_multiprocessing"] = types.ModuleType("_multiprocessing") - - def tearDown(self): - if _multiprocessing is None: - sys.modules.pop("_multiprocessing") - def check_all(self, modname): names = {} with warnings_helper.check_warnings( diff --git a/Lib/test/test_concurrent_futures/__init__.py b/Lib/test/test_concurrent_futures/__init__.py index 430fa93aa456a2d..17a2853173ba18a 100644 --- a/Lib/test/test_concurrent_futures/__init__.py +++ b/Lib/test/test_concurrent_futures/__init__.py @@ -3,8 +3,6 @@ from test import support from test.support import import_helper -# Skip tests if _multiprocessing wasn't built. -import_helper.import_module('_multiprocessing') if support.check_sanitizer(address=True, memory=True): # gh-90791: Skip the test because it is too slow when Python is built diff --git a/Lib/test/test_concurrent_futures/test_init.py b/Lib/test/test_concurrent_futures/test_init.py index d79a6367701fb4b..113a4d1c54be03b 100644 --- a/Lib/test/test_concurrent_futures/test_init.py +++ b/Lib/test/test_concurrent_futures/test_init.py @@ -5,6 +5,8 @@ import unittest import sys from concurrent.futures._base import BrokenExecutor +from concurrent.futures.process import _check_system_limits + from logging.handlers import QueueHandler from test import support @@ -117,6 +119,11 @@ class FailingInitializerResourcesTest(unittest.TestCase): """ def _test(self, test_class): + try: + _check_system_limits() + except NotImplementedError: + self.skipTest("ProcessPoolExecutor unavailable on this system") + runner = unittest.TextTestRunner() runner.run(test_class('test_initializer')) diff --git a/Lib/test/test_concurrent_futures/util.py b/Lib/test/test_concurrent_futures/util.py index dc48bec796b87fe..3e855031913042f 100644 --- a/Lib/test/test_concurrent_futures/util.py +++ b/Lib/test/test_concurrent_futures/util.py @@ -136,6 +136,12 @@ def strip_mixin(name): def setup_module(): - unittest.addModuleCleanup(multiprocessing.util._cleanup_tests) + try: + _check_system_limits() + except NotImplementedError: + pass + else: + unittest.addModuleCleanup(multiprocessing.util._cleanup_tests) + thread_info = threading_helper.threading_setup() unittest.addModuleCleanup(threading_helper.threading_cleanup, *thread_info) diff --git a/Misc/NEWS.d/next/Tests/2024-02-25-15-58-28.gh-issue-71052.lxBjqY.rst b/Misc/NEWS.d/next/Tests/2024-02-25-15-58-28.gh-issue-71052.lxBjqY.rst new file mode 100644 index 000000000000000..8bac68b5aea78ee --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-02-25-15-58-28.gh-issue-71052.lxBjqY.rst @@ -0,0 +1,2 @@ +Enable ``test_concurrent_futures`` on platforms that support threading but not +multiprocessing. From 84a275c4a2c8a22d198c6f227d538e6b27bbb029 Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Sun, 25 Feb 2024 14:02:18 -0600 Subject: [PATCH 447/507] gh-101100: Fix broken xrefs in fcntl module doc (#115691) * clean up fcntl module doc * simplify * a few changes, based on suggestion by CAM-Gerlach * nitpick ignore for a couple other C functions mentioned in the fcntl module doc * more changes, especially related to LOCK_* constants * :data: back to :const: * Apply suggestions from code review Co-authored-by: C.A.M. Gerlach <CAM.Gerlach@Gerlach.CAM> --------- Co-authored-by: C.A.M. Gerlach <CAM.Gerlach@Gerlach.CAM> --- Doc/conf.py | 2 ++ Doc/library/fcntl.rst | 41 ++++++++++++++++++++++++++--------------- Doc/tools/.nitignore | 1 - 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index 7c4817320a7de2c..f4c75c5758cb281 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -101,11 +101,13 @@ ('c:func', 'dlopen'), ('c:func', 'exec'), ('c:func', 'fcntl'), + ('c:func', 'flock'), ('c:func', 'fork'), ('c:func', 'free'), ('c:func', 'gettimeofday'), ('c:func', 'gmtime'), ('c:func', 'grantpt'), + ('c:func', 'ioctl'), ('c:func', 'localeconv'), ('c:func', 'localtime'), ('c:func', 'main'), diff --git a/Doc/library/fcntl.rst b/Doc/library/fcntl.rst index 13ad2dd7da5090a..b93d6ac7aab9565 100644 --- a/Doc/library/fcntl.rst +++ b/Doc/library/fcntl.rst @@ -13,10 +13,10 @@ ---------------- -This module performs file control and I/O control on file descriptors. It is an -interface to the :c:func:`fcntl` and :c:func:`ioctl` Unix routines. For a -complete description of these calls, see :manpage:`fcntl(2)` and -:manpage:`ioctl(2)` Unix manual pages. +This module performs file and I/O control on file descriptors. It is an +interface to the :c:func:`fcntl` and :c:func:`ioctl` Unix routines. +See the :manpage:`fcntl(2)` and :manpage:`ioctl(2)` Unix manual pages +for full details. .. availability:: Unix, not Emscripten, not WASI. @@ -101,7 +101,7 @@ The module defines the following functions: most likely to result in a segmentation violation or a more subtle data corruption. - If the :c:func:`fcntl` fails, an :exc:`OSError` is raised. + If the :c:func:`fcntl` call fails, an :exc:`OSError` is raised. .. audit-event:: fcntl.fcntl fd,cmd,arg fcntl.fcntl @@ -139,7 +139,7 @@ The module defines the following functions: buffer 1024 bytes long which is then passed to :func:`ioctl` and copied back into the supplied buffer. - If the :c:func:`ioctl` fails, an :exc:`OSError` exception is raised. + If the :c:func:`ioctl` call fails, an :exc:`OSError` exception is raised. An example:: @@ -164,7 +164,7 @@ The module defines the following functions: :manpage:`flock(2)` for details. (On some systems, this function is emulated using :c:func:`fcntl`.) - If the :c:func:`flock` fails, an :exc:`OSError` exception is raised. + If the :c:func:`flock` call fails, an :exc:`OSError` exception is raised. .. audit-event:: fcntl.flock fd,operation fcntl.flock @@ -176,17 +176,28 @@ The module defines the following functions: method are accepted as well) of the file to lock or unlock, and *cmd* is one of the following values: - * :const:`LOCK_UN` -- unlock - * :const:`LOCK_SH` -- acquire a shared lock - * :const:`LOCK_EX` -- acquire an exclusive lock + .. data:: LOCK_UN - When *cmd* is :const:`LOCK_SH` or :const:`LOCK_EX`, it can also be - bitwise ORed with :const:`LOCK_NB` to avoid blocking on lock acquisition. - If :const:`LOCK_NB` is used and the lock cannot be acquired, an + Release an existing lock. + + .. data:: LOCK_SH + + Acquire a shared lock. + + .. data:: LOCK_EX + + Acquire an exclusive lock. + + .. data:: LOCK_NB + + Bitwise OR with any of the other three ``LOCK_*`` constants to make + the request non-blocking. + + If :const:`!LOCK_NB` is used and the lock cannot be acquired, an :exc:`OSError` will be raised and the exception will have an *errno* - attribute set to :const:`EACCES` or :const:`EAGAIN` (depending on the + attribute set to :const:`~errno.EACCES` or :const:`~errno.EAGAIN` (depending on the operating system; for portability, check for both values). On at least some - systems, :const:`LOCK_EX` can only be used if the file descriptor refers to a + systems, :const:`!LOCK_EX` can only be used if the file descriptor refers to a file opened for writing. *len* is the number of bytes to lock, *start* is the byte offset at diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 344b0c9b679df00..18eac5ff4063120 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -29,7 +29,6 @@ Doc/library/email.parser.rst Doc/library/email.policy.rst Doc/library/exceptions.rst Doc/library/faulthandler.rst -Doc/library/fcntl.rst Doc/library/functools.rst Doc/library/http.cookiejar.rst Doc/library/http.server.rst From f082a05c67cc949ccd1a940ecf6721953bbdc34f Mon Sep 17 00:00:00 2001 From: Sergii K <gorsing444@gmail.com> Date: Sun, 25 Feb 2024 23:45:38 +0300 Subject: [PATCH 448/507] gh-115914: minor cleanup: simplify filename_obj assignment in PyRun_AnyFileExFlags (gh-115916) This simplifies the code: less lines, easier to read. Logically equivalent, as any compiler likely already determined. --- Python/pythonrun.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 5f305aa00e08b9c..f87c53fb28fbead 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -89,7 +89,7 @@ int PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit, PyCompilerFlags *flags) { - PyObject *filename_obj; + PyObject *filename_obj = NULL; if (filename != NULL) { filename_obj = PyUnicode_DecodeFSDefault(filename); if (filename_obj == NULL) { @@ -97,9 +97,6 @@ PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit, return -1; } } - else { - filename_obj = NULL; - } int res = _PyRun_AnyFileObject(fp, filename_obj, closeit, flags); Py_XDECREF(filename_obj); return res; From 8f5be78bce95deb338e2e1cf13a0a579b3b42dd2 Mon Sep 17 00:00:00 2001 From: Furkan Onder <furkanonder@protonmail.com> Date: Sun, 25 Feb 2024 23:55:19 +0300 Subject: [PATCH 449/507] gh-72249: Include the module name in the repr of partial object (GH-101910) Co-authored-by: Anilyka Barry <vgr255@live.ca> --- Lib/functools.py | 8 +++---- Lib/test/test_functools.py | 14 ++----------- ...3-02-14-17-19-59.gh-issue-72249.fv35wU.rst | 2 ++ Modules/_functoolsmodule.c | 21 +++++++++++++++++-- 4 files changed, 27 insertions(+), 18 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-02-14-17-19-59.gh-issue-72249.fv35wU.rst diff --git a/Lib/functools.py b/Lib/functools.py index 7045be551c8c491..601cb8e7c0b74b1 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -303,13 +303,13 @@ def __call__(self, /, *args, **keywords): @recursive_repr() def __repr__(self): - qualname = type(self).__qualname__ + cls = type(self) + qualname = cls.__qualname__ + module = cls.__module__ args = [repr(self.func)] args.extend(repr(x) for x in self.args) args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items()) - if type(self).__module__ == "functools": - return f"functools.{qualname}({', '.join(args)})" - return f"{qualname}({', '.join(args)})" + return f"{module}.{qualname}({', '.join(args)})" def __reduce__(self): return type(self), (self.func,), (self.func, self.args, diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 2c814d5e8888403..4eb322644fc541b 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -31,10 +31,6 @@ decimal = import_helper.import_fresh_module('decimal', fresh=['_decimal']) -_partial_types = [py_functools.partial] -if c_functools: - _partial_types.append(c_functools.partial) - @contextlib.contextmanager def replaced_module(name, replacement): @@ -207,10 +203,7 @@ def test_repr(self): kwargs = {'a': object(), 'b': object()} kwargs_reprs = ['a={a!r}, b={b!r}'.format_map(kwargs), 'b={b!r}, a={a!r}'.format_map(kwargs)] - if self.partial in _partial_types: - name = 'functools.partial' - else: - name = self.partial.__name__ + name = f"{self.partial.__module__}.{self.partial.__qualname__}" f = self.partial(capture) self.assertEqual(f'{name}({capture!r})', repr(f)) @@ -229,10 +222,7 @@ def test_repr(self): for kwargs_repr in kwargs_reprs]) def test_recursive_repr(self): - if self.partial in _partial_types: - name = 'functools.partial' - else: - name = self.partial.__name__ + name = f"{self.partial.__module__}.{self.partial.__qualname__}" f = self.partial(capture) f.__setstate__((f, (), {}, {})) diff --git a/Misc/NEWS.d/next/Library/2023-02-14-17-19-59.gh-issue-72249.fv35wU.rst b/Misc/NEWS.d/next/Library/2023-02-14-17-19-59.gh-issue-72249.fv35wU.rst new file mode 100644 index 000000000000000..10cc5a4e7528dd4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-02-14-17-19-59.gh-issue-72249.fv35wU.rst @@ -0,0 +1,2 @@ +:func:`functools.partial`s of :func:`repr` has been improved to include the +:term:`module` name. Patched by Furkan Onder and Anilyka Barry. diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 9ab847165dc0970..d2212d40550a3c7 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -365,6 +365,8 @@ partial_repr(partialobject *pto) { PyObject *result = NULL; PyObject *arglist; + PyObject *mod; + PyObject *name; Py_ssize_t i, n; PyObject *key, *value; int status; @@ -399,13 +401,28 @@ partial_repr(partialobject *pto) if (arglist == NULL) goto done; } - result = PyUnicode_FromFormat("%s(%R%U)", Py_TYPE(pto)->tp_name, - pto->fn, arglist); + + mod = _PyType_GetModuleName(Py_TYPE(pto)); + if (mod == NULL) { + goto error; + } + name = PyType_GetQualName(Py_TYPE(pto)); + if (name == NULL) { + Py_DECREF(mod); + goto error; + } + result = PyUnicode_FromFormat("%S.%S(%R%U)", mod, name, pto->fn, arglist); + Py_DECREF(mod); + Py_DECREF(name); Py_DECREF(arglist); done: Py_ReprLeave((PyObject *)pto); return result; + error: + Py_DECREF(arglist); + Py_ReprLeave((PyObject *)pto); + return NULL; } /* Pickle strategy: From 6a3236fe2e61673cf9f819534afbf14a18678408 Mon Sep 17 00:00:00 2001 From: bssyousefi <44493177+bssyousefi@users.noreply.github.com> Date: Sun, 25 Feb 2024 17:07:08 -0500 Subject: [PATCH 450/507] gh-115799: Add missing double-quote in docs (#115884) --- Doc/c-api/exceptions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index e6309ae7614d347..ba13fd1b9973e03 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -105,7 +105,7 @@ Printing and clearing parameters help format the warning message; they have the same meaning and values as in :c:func:`PyUnicode_FromFormat`. ``PyErr_WriteUnraisable(obj)`` is roughtly equivalent to - ``PyErr_FormatUnraisable("Exception ignored in: %R, obj)``. + ``PyErr_FormatUnraisable("Exception ignored in: %R", obj)``. If *format* is ``NULL``, only the traceback is printed. .. versionadded:: 3.13 From 6d34eb0e36d3a7edd9e7629f21da39b6a74b8f68 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger <rhettinger@users.noreply.github.com> Date: Sun, 25 Feb 2024 17:46:47 -0600 Subject: [PATCH 451/507] gh-115532: Add kernel density estimation to the statistics module (gh-115863) --- Doc/library/statistics.rst | 89 +++++----- Doc/whatsnew/3.13.rst | 8 + Lib/statistics.py | 168 +++++++++++++++++- Lib/test/test_statistics.py | 60 +++++++ ...-02-23-11-08-31.gh-issue-115532.zVd3gK.rst | 1 + 5 files changed, 285 insertions(+), 41 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-23-11-08-31.gh-issue-115532.zVd3gK.rst diff --git a/Doc/library/statistics.rst b/Doc/library/statistics.rst index 0417b3f38a98079..1785c6bcc212b74 100644 --- a/Doc/library/statistics.rst +++ b/Doc/library/statistics.rst @@ -76,6 +76,7 @@ or sample. :func:`fmean` Fast, floating point arithmetic mean, with optional weighting. :func:`geometric_mean` Geometric mean of data. :func:`harmonic_mean` Harmonic mean of data. +:func:`kde` Estimate the probability density distribution of the data. :func:`median` Median (middle value) of data. :func:`median_low` Low median of data. :func:`median_high` High median of data. @@ -259,6 +260,54 @@ However, for reading convenience, most of the examples show sorted sequences. .. versionchanged:: 3.10 Added support for *weights*. + +.. function:: kde(data, h, kernel='normal') + + `Kernel Density Estimation (KDE) + <https://www.itm-conferences.org/articles/itmconf/pdf/2018/08/itmconf_sam2018_00037.pdf>`_: + Create a continuous probability density function from discrete samples. + + The basic idea is to smooth the data using `a kernel function + <https://en.wikipedia.org/wiki/Kernel_(statistics)>`_. + to help draw inferences about a population from a sample. + + The degree of smoothing is controlled by the scaling parameter *h* + which is called the bandwidth. Smaller values emphasize local + features while larger values give smoother results. + + The *kernel* determines the relative weights of the sample data + points. Generally, the choice of kernel shape does not matter + as much as the more influential bandwidth smoothing parameter. + + Kernels that give some weight to every sample point include + *normal* or *gauss*, *logistic*, and *sigmoid*. + + Kernels that only give weight to sample points within the bandwidth + include *rectangular* or *uniform*, *triangular*, *parabolic* or + *epanechnikov*, *quartic* or *biweight*, *triweight*, and *cosine*. + + A :exc:`StatisticsError` will be raised if the *data* sequence is empty. + + `Wikipedia has an example + <https://en.wikipedia.org/wiki/Kernel_density_estimation#Example>`_ + where we can use :func:`kde` to generate and plot a probability + density function estimated from a small sample: + + .. doctest:: + + >>> sample = [-2.1, -1.3, -0.4, 1.9, 5.1, 6.2] + >>> f_hat = kde(sample, h=1.5) + >>> xarr = [i/100 for i in range(-750, 1100)] + >>> yarr = [f_hat(x) for x in xarr] + + The points in ``xarr`` and ``yarr`` can be used to make a PDF plot: + + .. image:: kde_example.png + :alt: Scatter plot of the estimated probability density function. + + .. versionadded:: 3.13 + + .. function:: median(data) Return the median (middle value) of numeric data, using the common "mean of @@ -1095,46 +1144,6 @@ The final prediction goes to the largest posterior. This is known as the 'female' -Kernel density estimation -************************* - -It is possible to estimate a continuous probability density function -from a fixed number of discrete samples. - -The basic idea is to smooth the data using `a kernel function such as a -normal distribution, triangular distribution, or uniform distribution -<https://en.wikipedia.org/wiki/Kernel_(statistics)#Kernel_functions_in_common_use>`_. -The degree of smoothing is controlled by a scaling parameter, ``h``, -which is called the *bandwidth*. - -.. testcode:: - - def kde_normal(sample, h): - "Create a continuous probability density function from a sample." - # Smooth the sample with a normal distribution kernel scaled by h. - kernel_h = NormalDist(0.0, h).pdf - n = len(sample) - def pdf(x): - return sum(kernel_h(x - x_i) for x_i in sample) / n - return pdf - -`Wikipedia has an example -<https://en.wikipedia.org/wiki/Kernel_density_estimation#Example>`_ -where we can use the ``kde_normal()`` recipe to generate and plot -a probability density function estimated from a small sample: - -.. doctest:: - - >>> sample = [-2.1, -1.3, -0.4, 1.9, 5.1, 6.2] - >>> f_hat = kde_normal(sample, h=1.5) - >>> xarr = [i/100 for i in range(-750, 1100)] - >>> yarr = [f_hat(x) for x in xarr] - -The points in ``xarr`` and ``yarr`` can be used to make a PDF plot: - -.. image:: kde_example.png - :alt: Scatter plot of the estimated probability density function. - .. # This modelines must appear within the last ten lines of the file. kate: indent-width 3; remove-trailing-space on; replace-tabs on; encoding utf-8; diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index a393a1df71a65ca..ca5a0e7515994fe 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -476,6 +476,14 @@ sqlite3 for filtering database objects to dump. (Contributed by Mariusz Felisiak in :gh:`91602`.) +statistics +---------- + +* Add :func:`statistics.kde` for kernel density estimation. + This makes it possible to estimate a continuous probability density function + from a fixed number of discrete samples. + (Contributed by Raymond Hettinger in :gh:`115863`.) + subprocess ---------- diff --git a/Lib/statistics.py b/Lib/statistics.py index 83aaedb04515e02..7924123c05b8c37 100644 --- a/Lib/statistics.py +++ b/Lib/statistics.py @@ -112,6 +112,7 @@ 'fmean', 'geometric_mean', 'harmonic_mean', + 'kde', 'linear_regression', 'mean', 'median', @@ -137,7 +138,7 @@ from itertools import count, groupby, repeat from bisect import bisect_left, bisect_right from math import hypot, sqrt, fabs, exp, erf, tau, log, fsum, sumprod -from math import isfinite, isinf +from math import isfinite, isinf, pi, cos, cosh from functools import reduce from operator import itemgetter from collections import Counter, namedtuple, defaultdict @@ -802,6 +803,171 @@ def multimode(data): return [value for value, count in counts.items() if count == maxcount] +def kde(data, h, kernel='normal'): + """Kernel Density Estimation: Create a continuous probability + density function from discrete samples. + + The basic idea is to smooth the data using a kernel function + to help draw inferences about a population from a sample. + + The degree of smoothing is controlled by the scaling parameter h + which is called the bandwidth. Smaller values emphasize local + features while larger values give smoother results. + + The kernel determines the relative weights of the sample data + points. Generally, the choice of kernel shape does not matter + as much as the more influential bandwidth smoothing parameter. + + Kernels that give some weight to every sample point: + + normal or gauss + logistic + sigmoid + + Kernels that only give weight to sample points within + the bandwidth: + + rectangular or uniform + triangular + parabolic or epanechnikov + quartic or biweight + triweight + cosine + + A StatisticsError will be raised if the data sequence is empty. + + Example + ------- + + Given a sample of six data points, construct a continuous + function that estimates the underlying probability density: + + >>> sample = [-2.1, -1.3, -0.4, 1.9, 5.1, 6.2] + >>> f_hat = kde(sample, h=1.5) + + Compute the area under the curve: + + >>> sum(f_hat(x) for x in range(-20, 20)) + 1.0 + + Plot the estimated probability density function at + evenly spaced points from -6 to 10: + + >>> for x in range(-6, 11): + ... density = f_hat(x) + ... plot = ' ' * int(density * 400) + 'x' + ... print(f'{x:2}: {density:.3f} {plot}') + ... + -6: 0.002 x + -5: 0.009 x + -4: 0.031 x + -3: 0.070 x + -2: 0.111 x + -1: 0.125 x + 0: 0.110 x + 1: 0.086 x + 2: 0.068 x + 3: 0.059 x + 4: 0.066 x + 5: 0.082 x + 6: 0.082 x + 7: 0.058 x + 8: 0.028 x + 9: 0.009 x + 10: 0.002 x + + References + ---------- + + Kernel density estimation and its application: + https://www.itm-conferences.org/articles/itmconf/pdf/2018/08/itmconf_sam2018_00037.pdf + + Kernel functions in common use: + https://en.wikipedia.org/wiki/Kernel_(statistics)#kernel_functions_in_common_use + + Interactive graphical demonstration and exploration: + https://demonstrations.wolfram.com/KernelDensityEstimation/ + + """ + + n = len(data) + if not n: + raise StatisticsError('Empty data sequence') + + if not isinstance(data[0], (int, float)): + raise TypeError('Data sequence must contain ints or floats') + + if h <= 0.0: + raise StatisticsError(f'Bandwidth h must be positive, not {h=!r}') + + match kernel: + + case 'normal' | 'gauss': + c = 1 / sqrt(2 * pi) + K = lambda t: c * exp(-1/2 * t * t) + support = None + + case 'logistic': + # 1.0 / (exp(t) + 2.0 + exp(-t)) + K = lambda t: 1/2 / (1.0 + cosh(t)) + support = None + + case 'sigmoid': + # (2/pi) / (exp(t) + exp(-t)) + c = 1 / pi + K = lambda t: c / cosh(t) + support = None + + case 'rectangular' | 'uniform': + K = lambda t: 1/2 + support = 1.0 + + case 'triangular': + K = lambda t: 1.0 - abs(t) + support = 1.0 + + case 'parabolic' | 'epanechnikov': + K = lambda t: 3/4 * (1.0 - t * t) + support = 1.0 + + case 'quartic' | 'biweight': + K = lambda t: 15/16 * (1.0 - t * t) ** 2 + support = 1.0 + + case 'triweight': + K = lambda t: 35/32 * (1.0 - t * t) ** 3 + support = 1.0 + + case 'cosine': + c1 = pi / 4 + c2 = pi / 2 + K = lambda t: c1 * cos(c2 * t) + support = 1.0 + + case _: + raise StatisticsError(f'Unknown kernel name: {kernel!r}') + + if support is None: + + def pdf(x): + return sum(K((x - x_i) / h) for x_i in data) / (n * h) + + else: + + sample = sorted(data) + bandwidth = h * support + + def pdf(x): + i = bisect_left(sample, x - bandwidth) + j = bisect_right(sample, x + bandwidth) + supported = sample[i : j] + return sum(K((x - x_i) / h) for x_i in supported) / (n * h) + + pdf.__doc__ = f'PDF estimate with {kernel=!r} and {h=!r}' + + return pdf + + # Notes on methods for computing quantiles # ---------------------------------------- # diff --git a/Lib/test/test_statistics.py b/Lib/test/test_statistics.py index bf2c254c9ee7d9a..1cf41638a7f01a3 100644 --- a/Lib/test/test_statistics.py +++ b/Lib/test/test_statistics.py @@ -2353,6 +2353,66 @@ def test_mixed_int_and_float(self): self.assertAlmostEqual(actual_mean, expected_mean, places=5) +class TestKDE(unittest.TestCase): + + def test_kde(self): + kde = statistics.kde + StatisticsError = statistics.StatisticsError + + kernels = ['normal', 'gauss', 'logistic', 'sigmoid', 'rectangular', + 'uniform', 'triangular', 'parabolic', 'epanechnikov', + 'quartic', 'biweight', 'triweight', 'cosine'] + + sample = [-2.1, -1.3, -0.4, 1.9, 5.1, 6.2] + + # The approximate integral of a PDF should be close to 1.0 + + def integrate(func, low, high, steps=10_000): + "Numeric approximation of a definite function integral." + dx = (high - low) / steps + midpoints = (low + (i + 1/2) * dx for i in range(steps)) + return sum(map(func, midpoints)) * dx + + for kernel in kernels: + with self.subTest(kernel=kernel): + f_hat = kde(sample, h=1.5, kernel=kernel) + area = integrate(f_hat, -20, 20) + self.assertAlmostEqual(area, 1.0, places=4) + + # Check error cases + + with self.assertRaises(StatisticsError): + kde([], h=1.0) # Empty dataset + with self.assertRaises(TypeError): + kde(['abc', 'def'], 1.5) # Non-numeric data + with self.assertRaises(TypeError): + kde(iter(sample), 1.5) # Data is not a sequence + with self.assertRaises(StatisticsError): + kde(sample, h=0.0) # Zero bandwidth + with self.assertRaises(StatisticsError): + kde(sample, h=0.0) # Negative bandwidth + with self.assertRaises(TypeError): + kde(sample, h='str') # Wrong bandwidth type + with self.assertRaises(StatisticsError): + kde(sample, h=1.0, kernel='bogus') # Invalid kernel + + # Test name and docstring of the generated function + + h = 1.5 + kernel = 'cosine' + f_hat = kde(sample, h, kernel) + self.assertEqual(f_hat.__name__, 'pdf') + self.assertIn(kernel, f_hat.__doc__) + self.assertIn(str(h), f_hat.__doc__) + + # Test closed interval for the support boundaries. + # In particular, 'uniform' should non-zero at the boundaries. + + f_hat = kde([0], 1.0, 'uniform') + self.assertEqual(f_hat(-1.0), 1/2) + self.assertEqual(f_hat(1.0), 1/2) + + class TestQuantiles(unittest.TestCase): def test_specific_cases(self): diff --git a/Misc/NEWS.d/next/Library/2024-02-23-11-08-31.gh-issue-115532.zVd3gK.rst b/Misc/NEWS.d/next/Library/2024-02-23-11-08-31.gh-issue-115532.zVd3gK.rst new file mode 100644 index 000000000000000..fb36c0b2a4fabd2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-23-11-08-31.gh-issue-115532.zVd3gK.rst @@ -0,0 +1 @@ +Add kernel density estimation to the statistics module. From 92ce41cce1694b755ba80ed870c3f1083617dd97 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" <greg@krypto.org> Date: Sun, 25 Feb 2024 16:02:56 -0800 Subject: [PATCH 452/507] gh-71052: fix test_concurrent_futures wasi regression. (#115923) Fix the WASI test_concurrent_futures regression from #115917. --- Lib/test/test_concurrent_futures/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_concurrent_futures/__init__.py b/Lib/test/test_concurrent_futures/__init__.py index 17a2853173ba18a..b38bd38d338f0ee 100644 --- a/Lib/test/test_concurrent_futures/__init__.py +++ b/Lib/test/test_concurrent_futures/__init__.py @@ -1,7 +1,11 @@ import os.path import unittest from test import support -from test.support import import_helper +from test.support import threading_helper + + +# Adjust if we ever have a platform with processes but not threads. +threading_helper.requires_working_threading(module=True) if support.check_sanitizer(address=True, memory=True): From e921f09c8a7eb2ab24e8a782b65531ee5afb291a Mon Sep 17 00:00:00 2001 From: Barney Gale <barney.gale@gmail.com> Date: Mon, 26 Feb 2024 00:19:03 +0000 Subject: [PATCH 453/507] GH-101112: Add "pattern language" section to pathlib docs (#114030) Explain the `full_match()` / `glob()` / `rglob()` pattern language in its own section. Move `rglob()` documentation under `glob()` and reduce duplicated text. --- Doc/library/pathlib.rst | 159 ++++++++++++++++++++++++++-------------- 1 file changed, 103 insertions(+), 56 deletions(-) diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index f94b6fb38056847..4b461a5d4a2949c 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -572,6 +572,9 @@ Pure paths provide the following methods and properties: >>> PurePath('/a/b/c.py').full_match('**/*.py') True + .. seealso:: + :ref:`pathlib-pattern-language` documentation. + As with other methods, case-sensitivity follows platform defaults:: >>> PurePosixPath('b.py').full_match('*.PY') @@ -991,11 +994,6 @@ call fails (for example because the path doesn't exist). [PosixPath('pathlib.py'), PosixPath('setup.py'), PosixPath('test_pathlib.py')] >>> sorted(Path('.').glob('*/*.py')) [PosixPath('docs/conf.py')] - - Patterns are the same as for :mod:`fnmatch`, with the addition of "``**``" - which means "this directory and all subdirectories, recursively". In other - words, it enables recursive globbing:: - >>> sorted(Path('.').glob('**/*.py')) [PosixPath('build/lib/pathlib.py'), PosixPath('docs/conf.py'), @@ -1003,13 +1001,8 @@ call fails (for example because the path doesn't exist). PosixPath('setup.py'), PosixPath('test_pathlib.py')] - .. note:: - Using the "``**``" pattern in large directory trees may consume - an inordinate amount of time. - - .. tip:: - Set *follow_symlinks* to ``True`` or ``False`` to improve performance - of recursive globbing. + .. seealso:: + :ref:`pathlib-pattern-language` documentation. This method calls :meth:`Path.is_dir` on the top-level directory and propagates any :exc:`OSError` exception that is raised. Subsequent @@ -1025,11 +1018,11 @@ call fails (for example because the path doesn't exist). wildcards. Set *follow_symlinks* to ``True`` to always follow symlinks, or ``False`` to treat all symlinks as files. - .. audit-event:: pathlib.Path.glob self,pattern pathlib.Path.glob + .. tip:: + Set *follow_symlinks* to ``True`` or ``False`` to improve performance + of recursive globbing. - .. versionchanged:: 3.11 - Return only directories if *pattern* ends with a pathname components - separator (:data:`~os.sep` or :data:`~os.altsep`). + .. audit-event:: pathlib.Path.glob self,pattern pathlib.Path.glob .. versionchanged:: 3.12 The *case_sensitive* parameter was added. @@ -1038,12 +1031,29 @@ call fails (for example because the path doesn't exist). The *follow_symlinks* parameter was added. .. versionchanged:: 3.13 - Return files and directories if *pattern* ends with "``**``". In - previous versions, only directories were returned. + The *pattern* parameter accepts a :term:`path-like object`. + + +.. method:: Path.rglob(pattern, *, case_sensitive=None, follow_symlinks=None) + + Glob the given relative *pattern* recursively. This is like calling + :func:`Path.glob` with "``**/``" added in front of the *pattern*. + + .. seealso:: + :ref:`pathlib-pattern-language` and :meth:`Path.glob` documentation. + + .. audit-event:: pathlib.Path.rglob self,pattern pathlib.Path.rglob + + .. versionchanged:: 3.12 + The *case_sensitive* parameter was added. + + .. versionchanged:: 3.13 + The *follow_symlinks* parameter was added. .. versionchanged:: 3.13 The *pattern* parameter accepts a :term:`path-like object`. + .. method:: Path.group(*, follow_symlinks=True) Return the name of the group owning the file. :exc:`KeyError` is raised @@ -1471,44 +1481,6 @@ call fails (for example because the path doesn't exist). strict mode, and no exception is raised in non-strict mode. In previous versions, :exc:`RuntimeError` is raised no matter the value of *strict*. -.. method:: Path.rglob(pattern, *, case_sensitive=None, follow_symlinks=None) - - Glob the given relative *pattern* recursively. This is like calling - :func:`Path.glob` with "``**/``" added in front of the *pattern*, where - *patterns* are the same as for :mod:`fnmatch`:: - - >>> sorted(Path().rglob("*.py")) - [PosixPath('build/lib/pathlib.py'), - PosixPath('docs/conf.py'), - PosixPath('pathlib.py'), - PosixPath('setup.py'), - PosixPath('test_pathlib.py')] - - By default, or when the *case_sensitive* keyword-only argument is set to - ``None``, this method matches paths using platform-specific casing rules: - typically, case-sensitive on POSIX, and case-insensitive on Windows. - Set *case_sensitive* to ``True`` or ``False`` to override this behaviour. - - By default, or when the *follow_symlinks* keyword-only argument is set to - ``None``, this method follows symlinks except when expanding "``**``" - wildcards. Set *follow_symlinks* to ``True`` to always follow symlinks, or - ``False`` to treat all symlinks as files. - - .. audit-event:: pathlib.Path.rglob self,pattern pathlib.Path.rglob - - .. versionchanged:: 3.11 - Return only directories if *pattern* ends with a pathname components - separator (:data:`~os.sep` or :data:`~os.altsep`). - - .. versionchanged:: 3.12 - The *case_sensitive* parameter was added. - - .. versionchanged:: 3.13 - The *follow_symlinks* parameter was added. - - .. versionchanged:: 3.13 - The *pattern* parameter accepts a :term:`path-like object`. - .. method:: Path.rmdir() Remove this directory. The directory must be empty. @@ -1639,6 +1611,81 @@ call fails (for example because the path doesn't exist). .. versionchanged:: 3.10 The *newline* parameter was added. + +.. _pathlib-pattern-language: + +Pattern language +---------------- + +The following wildcards are supported in patterns for +:meth:`~PurePath.full_match`, :meth:`~Path.glob` and :meth:`~Path.rglob`: + +``**`` (entire segment) + Matches any number of file or directory segments, including zero. +``*`` (entire segment) + Matches one file or directory segment. +``*`` (part of a segment) + Matches any number of non-separator characters, including zero. +``?`` + Matches one non-separator character. +``[seq]`` + Matches one character in *seq*. +``[!seq]`` + Matches one character not in *seq*. + +For a literal match, wrap the meta-characters in brackets. +For example, ``"[?]"`` matches the character ``"?"``. + +The "``**``" wildcard enables recursive globbing. A few examples: + +========================= =========================================== +Pattern Meaning +========================= =========================================== +"``**/*``" Any path with at least one segment. +"``**/*.py``" Any path with a final segment ending "``.py``". +"``assets/**``" Any path starting with "``assets/``". +"``assets/**/*``" Any path starting with "``assets/``", excluding "``assets/``" itself. +========================= =========================================== + +.. note:: + Globbing with the "``**``" wildcard visits every directory in the tree. + Large directory trees may take a long time to search. + +.. versionchanged:: 3.13 + Globbing with a pattern that ends with "``**``" returns both files and + directories. In previous versions, only directories were returned. + +In :meth:`Path.glob` and :meth:`~Path.rglob`, a trailing slash may be added to +the pattern to match only directories. + +.. versionchanged:: 3.11 + Globbing with a pattern that ends with a pathname components separator + (:data:`~os.sep` or :data:`~os.altsep`) returns only directories. + + +Comparison to the :mod:`glob` module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The patterns accepted and results generated by :meth:`Path.glob` and +:meth:`Path.rglob` differ slightly from those by the :mod:`glob` module: + +1. Files beginning with a dot are not special in pathlib. This is + like passing ``include_hidden=True`` to :func:`glob.glob`. +2. "``**``" pattern components are always recursive in pathlib. This is like + passing ``recursive=True`` to :func:`glob.glob`. +3. "``**``" pattern components do not follow symlinks by default in pathlib. + This behaviour has no equivalent in :func:`glob.glob`, but you can pass + ``follow_symlinks=True`` to :meth:`Path.glob` for compatible behaviour. +4. Like all :class:`PurePath` and :class:`Path` objects, the values returned + from :meth:`Path.glob` and :meth:`Path.rglob` don't include trailing + slashes. +5. The values returned from pathlib's ``path.glob()`` and ``path.rglob()`` + include the *path* as a prefix, unlike the results of + ``glob.glob(root_dir=path)``. +6. ``bytes``-based paths and :ref:`paths relative to directory descriptors + <dir_fd>` are not supported by pathlib. + + Correspondence to tools in the :mod:`os` module ----------------------------------------------- From bee7bb3310b356e99e3a0f75f23efbc97f1b0a24 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee <russell@keith-magee.com> Date: Mon, 26 Feb 2024 09:21:10 +0800 Subject: [PATCH 454/507] gh-114099: Add configure and Makefile targets to support iOS compilation. (GH-115390) --- .gitignore | 1 + Makefile.pre.in | 56 ++- ...-02-13-14-52-59.gh-issue-114099.zjXsQr.rst | 2 + Misc/platform_triplet.c | 15 + config.sub | 5 +- configure | 243 ++++++++++--- configure.ac | 225 +++++++++--- iOS/README.rst | 321 ++++++++++++++++++ iOS/Resources/Info.plist.in | 34 ++ iOS/Resources/bin/arm64-apple-ios-ar | 2 + iOS/Resources/bin/arm64-apple-ios-clang | 2 + iOS/Resources/bin/arm64-apple-ios-cpp | 2 + .../bin/arm64-apple-ios-simulator-ar | 2 + .../bin/arm64-apple-ios-simulator-clang | 2 + .../bin/arm64-apple-ios-simulator-cpp | 2 + .../bin/x86_64-apple-ios-simulator-ar | 2 + .../bin/x86_64-apple-ios-simulator-clang | 2 + .../bin/x86_64-apple-ios-simulator-cpp | 2 + iOS/Resources/dylib-Info-template.plist | 26 ++ iOS/Resources/pyconfig.h | 7 + 20 files changed, 849 insertions(+), 104 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2024-02-13-14-52-59.gh-issue-114099.zjXsQr.rst create mode 100644 iOS/README.rst create mode 100644 iOS/Resources/Info.plist.in create mode 100755 iOS/Resources/bin/arm64-apple-ios-ar create mode 100755 iOS/Resources/bin/arm64-apple-ios-clang create mode 100755 iOS/Resources/bin/arm64-apple-ios-cpp create mode 100755 iOS/Resources/bin/arm64-apple-ios-simulator-ar create mode 100755 iOS/Resources/bin/arm64-apple-ios-simulator-clang create mode 100755 iOS/Resources/bin/arm64-apple-ios-simulator-cpp create mode 100755 iOS/Resources/bin/x86_64-apple-ios-simulator-ar create mode 100755 iOS/Resources/bin/x86_64-apple-ios-simulator-clang create mode 100755 iOS/Resources/bin/x86_64-apple-ios-simulator-cpp create mode 100644 iOS/Resources/dylib-Info-template.plist create mode 100644 iOS/Resources/pyconfig.h diff --git a/.gitignore b/.gitignore index 6ed7197e3ab6269..2194b393aa48211 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,7 @@ Lib/test/data/* /_bootstrap_python /Makefile /Makefile.pre +iOS/Resources/Info.plist Mac/Makefile Mac/PythonLauncher/Info.plist Mac/PythonLauncher/Makefile diff --git a/Makefile.pre.in b/Makefile.pre.in index 4c1a18602b2d0bb..8893da816e93364 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -912,6 +912,21 @@ $(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK): \ $(LN) -fsn Versions/Current/$(PYTHONFRAMEWORK) $(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK) $(LN) -fsn Versions/Current/Resources $(PYTHONFRAMEWORKDIR)/Resources +# This rule is for iOS, which requires an annoyingly just slighly different +# format for frameworks to macOS. It *doesn't* use a versioned framework, and +# the Info.plist must be in the root of the framework. +$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK): \ + $(LIBRARY) \ + $(RESSRCDIR)/Info.plist + $(INSTALL) -d -m $(DIRMODE) $(PYTHONFRAMEWORKDIR) + $(CC) -o $(LDLIBRARY) $(PY_CORE_LDFLAGS) -dynamiclib \ + -all_load $(LIBRARY) \ + -install_name $(PYTHONFRAMEWORKINSTALLNAMEPREFIX)/$(PYTHONFRAMEWORK) \ + -compatibility_version $(VERSION) \ + -current_version $(VERSION) \ + -framework CoreFoundation $(LIBS); + $(INSTALL_DATA) $(RESSRCDIR)/Info.plist $(PYTHONFRAMEWORKDIR)/Info.plist + # This rule builds the Cygwin Python DLL and import library if configured # for a shared core library; otherwise, this rule is a noop. $(DLLLIBRARY) libpython$(LDVERSION).dll.a: $(LIBRARY_OBJS) @@ -2607,10 +2622,11 @@ frameworkinstall: install # only have to cater for the structural bits of the framework. .PHONY: frameworkinstallframework -frameworkinstallframework: frameworkinstallstructure install frameworkinstallmaclib +frameworkinstallframework: @FRAMEWORKINSTALLFIRST@ install frameworkinstallmaclib -.PHONY: frameworkinstallstructure -frameworkinstallstructure: $(LDLIBRARY) +# macOS uses a versioned frameworks structure that includes a full install +.PHONY: frameworkinstallversionedstructure +frameworkinstallversionedstructure: $(LDLIBRARY) @if test "$(PYTHONFRAMEWORKDIR)" = no-framework; then \ echo Not configured with --enable-framework; \ exit 1; \ @@ -2631,6 +2647,27 @@ frameworkinstallstructure: $(LDLIBRARY) $(LN) -fsn Versions/Current/Resources $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Resources $(INSTALL_SHARED) $(LDLIBRARY) $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/$(LDLIBRARY) +# iOS/tvOS/watchOS uses a non-versioned framework with Info.plist in the +# framework root, no .lproj data, and only stub compilation assistance binaries +.PHONY: frameworkinstallunversionedstructure +frameworkinstallunversionedstructure: $(LDLIBRARY) + @if test "$(PYTHONFRAMEWORKDIR)" = no-framework; then \ + echo Not configured with --enable-framework; \ + exit 1; \ + else true; \ + fi + if test -d $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include; then \ + echo "Clearing stale header symlink directory"; \ + rm -rf $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include; \ + fi + $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR) + sed 's/%VERSION%/'"`$(RUNSHARED) $(PYTHON_FOR_BUILD) -c 'import platform; print(platform.python_version())'`"'/g' < $(RESSRCDIR)/Info.plist > $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Info.plist + $(INSTALL_SHARED) $(LDLIBRARY) $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/$(LDLIBRARY) + $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(BINDIR) + for file in $(srcdir)/$(RESSRCDIR)/bin/* ; do \ + $(INSTALL) -m $(EXEMODE) $$file $(DESTDIR)$(BINDIR); \ + done + # This installs Mac/Lib into the framework # Install a number of symlinks to keep software that expects a normal unix # install (which includes python-config) happy. @@ -2671,6 +2708,19 @@ frameworkaltinstallunixtools: frameworkinstallextras: cd Mac && $(MAKE) installextras DESTDIR="$(DESTDIR)" +# On iOS, bin/lib can't live inside the framework; include needs to be called +# "Headers", but *must* be in the framework, and *not* include the `python3.X` +# subdirectory. The install has put these folders in the same folder as +# Python.framework; Move the headers to their final framework-compatible home. +.PHONY: frameworkinstallmobileheaders +frameworkinstallmobileheaders: + if test -d $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers; then \ + echo "Removing old framework headers"; \ + rm -rf $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers; \ + fi + mv "$(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include/python$(VERSION)" "$(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers" + $(LN) -fs "../$(PYTHONFRAMEWORKDIR)/Headers" "$(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include/python$(VERSION)" + # Build the toplevel Makefile Makefile.pre: $(srcdir)/Makefile.pre.in config.status CONFIG_FILES=Makefile.pre CONFIG_HEADERS= ./config.status diff --git a/Misc/NEWS.d/next/Build/2024-02-13-14-52-59.gh-issue-114099.zjXsQr.rst b/Misc/NEWS.d/next/Build/2024-02-13-14-52-59.gh-issue-114099.zjXsQr.rst new file mode 100644 index 000000000000000..e2858bd71d28cb2 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2024-02-13-14-52-59.gh-issue-114099.zjXsQr.rst @@ -0,0 +1,2 @@ +Makefile targets were added to support compiling an iOS-compatible framework +build. diff --git a/Misc/platform_triplet.c b/Misc/platform_triplet.c index 3307260544e8a6d..0b912e332510a6b 100644 --- a/Misc/platform_triplet.c +++ b/Misc/platform_triplet.c @@ -233,7 +233,22 @@ PLATFORM_TRIPLET=i386-gnu # error unknown platform triplet # endif #elif defined(__APPLE__) +# include "TargetConditionals.h" +# if TARGET_OS_IOS +# if TARGET_OS_SIMULATOR +# if __x86_64__ +PLATFORM_TRIPLET=x86_64-iphonesimulator +# else +PLATFORM_TRIPLET=arm64-iphonesimulator +# endif +# else +PLATFORM_TRIPLET=arm64-iphoneos +# endif +# elif TARGET_OS_OSX PLATFORM_TRIPLET=darwin +# else +# error unknown Apple platform +# endif #elif defined(__VXWORKS__) PLATFORM_TRIPLET=vxworks #elif defined(__wasm32__) diff --git a/config.sub b/config.sub index 2c6a07ab3c34eab..1bb6a05dc11026c 100755 --- a/config.sub +++ b/config.sub @@ -4,6 +4,7 @@ # shellcheck disable=SC2006,SC2268 # see below for rationale +# Patched 2024-02-03 to include support for arm64_32 and iOS/tvOS/watchOS simulators timestamp='2024-01-01' # This file is free software; you can redistribute it and/or modify it @@ -1127,7 +1128,7 @@ case $cpu-$vendor in xscale-* | xscalee[bl]-*) cpu=`echo "$cpu" | sed 's/^xscale/arm/'` ;; - arm64-* | aarch64le-*) + arm64-* | aarch64le-* | arm64_32-*) cpu=aarch64 ;; @@ -1866,6 +1867,8 @@ case $kernel-$os-$obj in ;; *-eabi*- | *-gnueabi*-) ;; + ios*-simulator- | tvos*-simulator- | watchos*-simulator- ) + ;; none--*) # None (no kernel, i.e. freestanding / bare metal), # can be paired with an machine code file format diff --git a/configure b/configure index fcf34f050861bea..b565cdbfae1327a 100755 --- a/configure +++ b/configure @@ -970,6 +970,7 @@ LDFLAGS CFLAGS CC HAS_XCRUN +IOS_DEPLOYMENT_TARGET EXPORT_MACOSX_DEPLOYMENT_TARGET CONFIGURE_MACOSX_DEPLOYMENT_TARGET _PYTHON_HOST_PLATFORM @@ -4029,6 +4030,9 @@ then *-*-cygwin*) ac_sys_system=Cygwin ;; + *-apple-ios*) + ac_sys_system=iOS + ;; *-*-vxworks*) ac_sys_system=VxWorks ;; @@ -4194,10 +4198,18 @@ then : enableval=$enable_framework; case $enableval in yes) + if test "$ac_sys_system" = "iOS"; then + as_fn_error $? "iOS builds must provide an explicit path for --enable-framework" "$LINENO" 5 + fi + enableval=/Library/Frameworks esac case $enableval in no) + if test "$ac_sys_system" = "iOS"; then + as_fn_error $? "iOS builds must use --enable-framework=<install path>" "$LINENO" 5 + fi + PYTHONFRAMEWORK= PYTHONFRAMEWORKDIR=no-framework PYTHONFRAMEWORKPREFIX= @@ -4221,11 +4233,11 @@ then : *) PYTHONFRAMEWORKPREFIX="${enableval}" PYTHONFRAMEWORKINSTALLDIR=$PYTHONFRAMEWORKPREFIX/$PYTHONFRAMEWORKDIR - FRAMEWORKINSTALLFIRST="frameworkinstallstructure" - FRAMEWORKALTINSTALLFIRST="frameworkinstallstructure " case $ac_sys_system in #( Darwin) : + FRAMEWORKINSTALLFIRST="frameworkinstallversionedstructure" + FRAMEWORKALTINSTALLFIRST="frameworkinstallversionedstructure " FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" FRAMEWORKPYTHONW="frameworkpythonw" @@ -4287,6 +4299,21 @@ then : ac_config_files="$ac_config_files Mac/Resources/app/Info.plist" + ;; + iOS) : + FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" + FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " + FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" + FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" + FRAMEWORKPYTHONW= + INSTALLTARGETS="libinstall inclinstall sharedinstall" + + prefix=$PYTHONFRAMEWORKPREFIX + PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" + RESSRCDIR=iOS/Resources + + ac_config_files="$ac_config_files iOS/Resources/Info.plist" + ;; *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 @@ -4296,6 +4323,10 @@ then : else $as_nop + if test "$ac_sys_system" = "iOS"; then + as_fn_error $? "iOS builds must use --enable-framework=<install path>" "$LINENO" 5 + fi + PYTHONFRAMEWORK= PYTHONFRAMEWORKDIR=no-framework PYTHONFRAMEWORKPREFIX= @@ -4353,6 +4384,23 @@ if test "$cross_compiling" = yes; then *-*-cygwin*) _host_ident= ;; + *-apple-ios*) + _host_os=`echo $host | cut -d '-' -f3` + _host_device=`echo $host | cut -d '-' -f4` + _host_device=${_host_device:=os} + + IOS_DEPLOYMENT_TARGET=${_host_os:3} + IOS_DEPLOYMENT_TARGET=${IOS_DEPLOYMENT_TARGET:=12.0} + + case "$host_cpu" in + aarch64) + _host_ident=${IOS_DEPLOYMENT_TARGET}-arm64-iphone${_host_device} + ;; + *) + _host_ident=${IOS_DEPLOYMENT_TARGET}-$host_cpu-iphone${_host_device} + ;; + esac + ;; *-*-vxworks*) _host_ident=$host_cpu ;; @@ -4431,6 +4479,9 @@ printf "%s\n" "#define _BSD_SOURCE 1" >>confdefs.h define_xopen_source=no;; Darwin/[12][0-9].*) define_xopen_source=no;; + # On iOS, defining _POSIX_C_SOURCE also disables platform specific features. + iOS/*) + define_xopen_source=no;; # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from # defining NI_NUMERICHOST. QNX/6.3.2) @@ -4525,6 +4576,17 @@ case $host in #( ;; esac +case $ac_sys_system in #( + iOS) : + + as_fn_append CFLAGS " -mios-version-min=${IOS_DEPLOYMENT_TARGET}" + as_fn_append LDFLAGS " -mios-version-min=${IOS_DEPLOYMENT_TARGET}" + + ;; #( + *) : + ;; +esac + if test "$ac_sys_system" = "Darwin" then # Extract the first word of "xcrun", so it can be a program name with args. @@ -6787,6 +6849,8 @@ printf %s "checking for multiarch... " >&6; } case $ac_sys_system in #( Darwin*) : MULTIARCH="" ;; #( + iOS) : + MULTIARCH="" ;; #( FreeBSD*) : MULTIARCH="" ;; #( *) : @@ -6807,6 +6871,8 @@ fi printf "%s\n" "$MULTIARCH" >&6; } case $ac_sys_system in #( + iOS) : + SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2` ;; #( *) : SOABI_PLATFORM=$PLATFORM_TRIPLET ;; @@ -6852,6 +6918,10 @@ case $host/$ac_cv_cc_name in #( PY_SUPPORT_TIER=3 ;; #( x86_64-*-freebsd*/clang) : PY_SUPPORT_TIER=3 ;; #( + aarch64-apple-ios*-simulator/clang) : + PY_SUPPORT_TIER=3 ;; #( + aarch64-apple-ios*/clang) : + PY_SUPPORT_TIER=3 ;; #( *) : PY_SUPPORT_TIER=0 ;; @@ -7307,12 +7377,15 @@ printf %s "checking LDLIBRARY... " >&6; } # will find it with a -framework option). For this reason there is an # extra variable BLDLIBRARY against which Python and the extension # modules are linked, BLDLIBRARY. This is normally the same as -# LDLIBRARY, but empty for MacOSX framework builds. +# LDLIBRARY, but empty for MacOSX framework builds. iOS does the same, +# but uses a non-versioned framework layout. if test "$enable_framework" then case $ac_sys_system in Darwin) LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; + iOS) + LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5;; esac @@ -7371,6 +7444,9 @@ printf "%s\n" "#define Py_ENABLE_SHARED 1" >>confdefs.h BLDLIBRARY='-L. -lpython$(LDVERSION)' RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} ;; + iOS) + LDLIBRARY='libpython$(LDVERSION).dylib' + ;; AIX*) LDLIBRARY='libpython$(LDVERSION).so' RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} @@ -12624,6 +12700,7 @@ if test -z "$SHLIB_SUFFIX"; then esac ;; CYGWIN*) SHLIB_SUFFIX=.dll;; + iOS) SHLIB_SUFFIX=.dylib;; *) SHLIB_SUFFIX=.so;; esac fi @@ -12706,6 +12783,11 @@ then BLDSHARED="$LDSHARED" fi ;; + iOS/*) + LDSHARED='$(CC) -dynamiclib -F . -framework Python' + LDCXXSHARED='$(CXX) -dynamiclib -F . -framework Python' + BLDSHARED="$LDSHARED" + ;; Emscripten|WASI) LDSHARED='$(CC) -shared' LDCXXSHARED='$(CXX) -shared';; @@ -12834,30 +12916,34 @@ then Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; # -u libsys_s pulls in all symbols in libsys - Darwin/*) + Darwin/*|iOS/*) LINKFORSHARED="$extra_undefs -framework CoreFoundation" # Issue #18075: the default maximum stack size (8MBytes) is too # small for the default recursion limit. Increase the stack size # to ensure that tests don't crash - stack_size="1000000" # 16 MB - if test "$with_ubsan" = "yes" - then - # Undefined behavior sanitizer requires an even deeper stack - stack_size="4000000" # 64 MB - fi - - LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" + stack_size="1000000" # 16 MB + if test "$with_ubsan" = "yes" + then + # Undefined behavior sanitizer requires an even deeper stack + stack_size="4000000" # 64 MB + fi printf "%s\n" "#define THREAD_STACK_SIZE 0x$stack_size" >>confdefs.h - if test "$enable_framework" - then - LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' + if test $ac_sys_system = "Darwin"; then + LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" + + if test "$enable_framework"; then + LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' + fi + LINKFORSHARED="$LINKFORSHARED" + elif test $ac_sys_system = "iOS"; then + LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' fi - LINKFORSHARED="$LINKFORSHARED";; + ;; OpenUNIX*|UnixWare*) LINKFORSHARED="-Wl,-Bexport";; SCO_SV*) LINKFORSHARED="-Wl,-Bexport";; ReliantUNIX*) LINKFORSHARED="-W1 -Blargedynsym";; @@ -14246,6 +14332,10 @@ then : ctypes_malloc_closure=yes ;; #( + iOS) : + + ctypes_malloc_closure=yes + ;; #( sunos5) : as_fn_append LIBFFI_LIBS " -mimpure-text" ;; #( @@ -17499,12 +17589,6 @@ if test "x$ac_cv_func_getegid" = xyes then : printf "%s\n" "#define HAVE_GETEGID 1" >>confdefs.h -fi -ac_fn_c_check_func "$LINENO" "getentropy" "ac_cv_func_getentropy" -if test "x$ac_cv_func_getentropy" = xyes -then : - printf "%s\n" "#define HAVE_GETENTROPY 1" >>confdefs.h - fi ac_fn_c_check_func "$LINENO" "geteuid" "ac_cv_func_geteuid" if test "x$ac_cv_func_geteuid" = xyes @@ -17547,12 +17631,6 @@ if test "x$ac_cv_func_getgrouplist" = xyes then : printf "%s\n" "#define HAVE_GETGROUPLIST 1" >>confdefs.h -fi -ac_fn_c_check_func "$LINENO" "getgroups" "ac_cv_func_getgroups" -if test "x$ac_cv_func_getgroups" = xyes -then : - printf "%s\n" "#define HAVE_GETGROUPS 1" >>confdefs.h - fi ac_fn_c_check_func "$LINENO" "gethostname" "ac_cv_func_gethostname" if test "x$ac_cv_func_gethostname" = xyes @@ -18279,12 +18357,6 @@ if test "x$ac_cv_func_sysconf" = xyes then : printf "%s\n" "#define HAVE_SYSCONF 1" >>confdefs.h -fi -ac_fn_c_check_func "$LINENO" "system" "ac_cv_func_system" -if test "x$ac_cv_func_system" = xyes -then : - printf "%s\n" "#define HAVE_SYSTEM 1" >>confdefs.h - fi ac_fn_c_check_func "$LINENO" "tcgetpgrp" "ac_cv_func_tcgetpgrp" if test "x$ac_cv_func_tcgetpgrp" = xyes @@ -18463,6 +18535,32 @@ fi fi +# iOS defines some system methods that can be linked (so they are +# found by configure), but either raise a compilation error (because the +# header definition prevents usage - autoconf doesn't use the headers), or +# raise an error if used at runtime. Force these symbols off. +if test "$ac_sys_system" != "iOS" ; then + ac_fn_c_check_func "$LINENO" "getentropy" "ac_cv_func_getentropy" +if test "x$ac_cv_func_getentropy" = xyes +then : + printf "%s\n" "#define HAVE_GETENTROPY 1" >>confdefs.h + +fi +ac_fn_c_check_func "$LINENO" "getgroups" "ac_cv_func_getgroups" +if test "x$ac_cv_func_getgroups" = xyes +then : + printf "%s\n" "#define HAVE_GETGROUPS 1" >>confdefs.h + +fi +ac_fn_c_check_func "$LINENO" "system" "ac_cv_func_system" +if test "x$ac_cv_func_system" = xyes +then : + printf "%s\n" "#define HAVE_SYSTEM 1" >>confdefs.h + +fi + +fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC options needed to detect all undeclared functions" >&5 printf %s "checking for $CC options needed to detect all undeclared functions... " >&6; } if test ${ac_cv_c_undeclared_builtin_options+y} @@ -21758,6 +21856,10 @@ fi done +# On iOS, clock_settime can be linked (so it is found by +# configure), but it raises a runtime error if used because apps can't change +# the clock. Force the symbol off. +if test "$ac_sys_system" != "iOS" ; then for ac_func in clock_settime do : @@ -21768,7 +21870,7 @@ then : else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for clock_settime in -lrt" >&5 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for clock_settime in -lrt" >&5 printf %s "checking for clock_settime in -lrt... " >&6; } if test ${ac_cv_lib_rt_clock_settime+y} then : @@ -21806,7 +21908,7 @@ printf "%s\n" "$ac_cv_lib_rt_clock_settime" >&6; } if test "x$ac_cv_lib_rt_clock_settime" = xyes then : - printf "%s\n" "#define HAVE_CLOCK_SETTIME 1" >>confdefs.h + printf "%s\n" "#define HAVE_CLOCK_SETTIME 1" >>confdefs.h fi @@ -21815,6 +21917,7 @@ fi fi done +fi for ac_func in clock_nanosleep @@ -23963,16 +24066,23 @@ LDVERSION='$(VERSION)$(ABIFLAGS)' { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $LDVERSION" >&5 printf "%s\n" "$LDVERSION" >&6; } -# On Android and Cygwin the shared libraries must be linked with libpython. +# Configure the flags and dependencies used when compiling shared modules MODULE_DEPS_SHARED='$(MODULE_DEPS_STATIC) $(EXPORTSYMS)' MODULE_LDFLAGS='' + +# On Android and Cygwin the shared libraries must be linked with libpython. if test "$PY_ENABLE_SHARED" = "1" && ( test -n "$ANDROID_API_LEVEL" || test "$MACHDEP" = "cygwin"); then MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(LDLIBRARY)" MODULE_LDFLAGS="\$(BLDLIBRARY)" fi +# On iOS the shared libraries must be linked with the Python framework +if test "$ac_sys_system" == "iOS"; then + MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)" +fi + BINLIBDEST='$(LIBDIR)/python$(VERSION)' @@ -26702,24 +26812,28 @@ CPPFLAGS=$ac_save_cppflags { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for device files" >&5 printf "%s\n" "$as_me: checking for device files" >&6;} -if test "x$cross_compiling" = xyes; then - if test "${ac_cv_file__dev_ptmx+set}" != set; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 +if test "$ac_sys_system" = "iOS" ; then + ac_cv_file__dev_ptmx=no + ac_cv_file__dev_ptc=no +else + if test "x$cross_compiling" = xyes; then + if test "${ac_cv_file__dev_ptmx+set}" != set; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 printf %s "checking for /dev/ptmx... " >&6; } - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not set" >&5 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not set" >&5 printf "%s\n" "not set" >&6; } - as_fn_error $? "set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 - fi - if test "${ac_cv_file__dev_ptc+set}" != set; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 + as_fn_error $? "set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 + fi + if test "${ac_cv_file__dev_ptc+set}" != set; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 printf %s "checking for /dev/ptc... " >&6; } - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not set" >&5 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not set" >&5 printf "%s\n" "not set" >&6; } - as_fn_error $? "set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 + as_fn_error $? "set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 + fi fi -fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 printf %s "checking for /dev/ptmx... " >&6; } if test ${ac_cv_file__dev_ptmx+y} then : @@ -26740,12 +26854,12 @@ then : fi -if test "x$ac_cv_file__dev_ptmx" = xyes; then + if test "x$ac_cv_file__dev_ptmx" = xyes; then printf "%s\n" "#define HAVE_DEV_PTMX 1" >>confdefs.h -fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 + fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 printf %s "checking for /dev/ptc... " >&6; } if test ${ac_cv_file__dev_ptc+y} then : @@ -26766,10 +26880,11 @@ then : fi -if test "x$ac_cv_file__dev_ptc" = xyes; then + if test "x$ac_cv_file__dev_ptc" = xyes; then printf "%s\n" "#define HAVE_DEV_PTC 1" >>confdefs.h + fi fi if test $ac_sys_system = Darwin @@ -28155,6 +28270,27 @@ case $ac_sys_system in #( ;; #( Darwin) : ;; #( + iOS) : + + + + py_cv_module__curses=n/a + py_cv_module__curses_panel=n/a + py_cv_module__gdbm=n/a + py_cv_module__multiprocessing=n/a + py_cv_module__posixshmem=n/a + py_cv_module__posixsubprocess=n/a + py_cv_module__scproxy=n/a + py_cv_module__tkinter=n/a + py_cv_module_grp=n/a + py_cv_module_nis=n/a + py_cv_module_readline=n/a + py_cv_module_pwd=n/a + py_cv_module_spwd=n/a + py_cv_module_syslog=n/a + py_cv_module_=n/a + + ;; #( CYGWIN*) : @@ -31766,6 +31902,7 @@ do "Mac/PythonLauncher/Makefile") CONFIG_FILES="$CONFIG_FILES Mac/PythonLauncher/Makefile" ;; "Mac/Resources/framework/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/framework/Info.plist" ;; "Mac/Resources/app/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/app/Info.plist" ;; + "iOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES iOS/Resources/Info.plist" ;; "Makefile.pre") CONFIG_FILES="$CONFIG_FILES Makefile.pre" ;; "Misc/python.pc") CONFIG_FILES="$CONFIG_FILES Misc/python.pc" ;; "Misc/python-embed.pc") CONFIG_FILES="$CONFIG_FILES Misc/python-embed.pc" ;; diff --git a/configure.ac b/configure.ac index f6df9b8bb41cc98..0694ef06364f82c 100644 --- a/configure.ac +++ b/configure.ac @@ -327,6 +327,9 @@ then *-*-cygwin*) ac_sys_system=Cygwin ;; + *-apple-ios*) + ac_sys_system=iOS + ;; *-*-vxworks*) ac_sys_system=VxWorks ;; @@ -484,10 +487,18 @@ AC_ARG_ENABLE([framework], [ case $enableval in yes) + if test "$ac_sys_system" = "iOS"; then + AC_MSG_ERROR([iOS builds must provide an explicit path for --enable-framework]) + fi + enableval=/Library/Frameworks esac case $enableval in no) + if test "$ac_sys_system" = "iOS"; then + AC_MSG_ERROR([iOS builds must use --enable-framework=<install path>]) + fi + PYTHONFRAMEWORK= PYTHONFRAMEWORKDIR=no-framework PYTHONFRAMEWORKPREFIX= @@ -511,11 +522,11 @@ AC_ARG_ENABLE([framework], *) PYTHONFRAMEWORKPREFIX="${enableval}" PYTHONFRAMEWORKINSTALLDIR=$PYTHONFRAMEWORKPREFIX/$PYTHONFRAMEWORKDIR - FRAMEWORKINSTALLFIRST="frameworkinstallstructure" - FRAMEWORKALTINSTALLFIRST="frameworkinstallstructure " case $ac_sys_system in #( Darwin) : + FRAMEWORKINSTALLFIRST="frameworkinstallversionedstructure" + FRAMEWORKALTINSTALLFIRST="frameworkinstallversionedstructure " FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" FRAMEWORKPYTHONW="frameworkpythonw" @@ -574,12 +585,30 @@ AC_ARG_ENABLE([framework], AC_CONFIG_FILES([Mac/Resources/framework/Info.plist]) AC_CONFIG_FILES([Mac/Resources/app/Info.plist]) ;; + iOS) : + FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" + FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " + FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" + FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" + FRAMEWORKPYTHONW= + INSTALLTARGETS="libinstall inclinstall sharedinstall" + + prefix=$PYTHONFRAMEWORKPREFIX + PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" + RESSRCDIR=iOS/Resources + + AC_CONFIG_FILES([iOS/Resources/Info.plist]) + ;; *) AC_MSG_ERROR([Unknown platform for framework build]) ;; esac esac ],[ + if test "$ac_sys_system" = "iOS"; then + AC_MSG_ERROR([iOS builds must use --enable-framework=<install path>]) + fi + PYTHONFRAMEWORK= PYTHONFRAMEWORKDIR=no-framework PYTHONFRAMEWORKPREFIX= @@ -634,6 +663,24 @@ if test "$cross_compiling" = yes; then *-*-cygwin*) _host_ident= ;; + *-apple-ios*) + _host_os=`echo $host | cut -d '-' -f3` + _host_device=`echo $host | cut -d '-' -f4` + _host_device=${_host_device:=os} + + dnl IOS_DEPLOYMENT_TARGET is the minimum supported iOS version + IOS_DEPLOYMENT_TARGET=${_host_os:3} + IOS_DEPLOYMENT_TARGET=${IOS_DEPLOYMENT_TARGET:=12.0} + + case "$host_cpu" in + aarch64) + _host_ident=${IOS_DEPLOYMENT_TARGET}-arm64-iphone${_host_device} + ;; + *) + _host_ident=${IOS_DEPLOYMENT_TARGET}-$host_cpu-iphone${_host_device} + ;; + esac + ;; *-*-vxworks*) _host_ident=$host_cpu ;; @@ -711,6 +758,9 @@ case $ac_sys_system/$ac_sys_release in define_xopen_source=no;; Darwin/@<:@[12]@:>@@<:@0-9@:>@.*) define_xopen_source=no;; + # On iOS, defining _POSIX_C_SOURCE also disables platform specific features. + iOS/*) + define_xopen_source=no;; # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from # defining NI_NUMERICHOST. QNX/6.3.2) @@ -801,6 +851,15 @@ AS_CASE([$host], ], ) +dnl Add the compiler flag for the iOS minimum supported OS version. +AS_CASE([$ac_sys_system], + [iOS], [ + AS_VAR_APPEND([CFLAGS], [" -mios-version-min=${IOS_DEPLOYMENT_TARGET}"]) + AS_VAR_APPEND([LDFLAGS], [" -mios-version-min=${IOS_DEPLOYMENT_TARGET}"]) + AC_SUBST([IOS_DEPLOYMENT_TARGET]) + ], +) + if test "$ac_sys_system" = "Darwin" then dnl look for SDKROOT @@ -967,6 +1026,7 @@ dnl platforms. AC_MSG_CHECKING([for multiarch]) AS_CASE([$ac_sys_system], [Darwin*], [MULTIARCH=""], + [iOS], [MULTIARCH=""], [FreeBSD*], [MULTIARCH=""], [MULTIARCH=$($CC --print-multiarch 2>/dev/null)] ) @@ -988,6 +1048,7 @@ dnl will have multiple sysconfig modules (one for each CPU architecture), but dnl use a single "fat" binary at runtime. SOABI_PLATFORM is the component of dnl the PLATFORM_TRIPLET that will be used in binary module extensions. AS_CASE([$ac_sys_system], + [iOS], [SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2`], [SOABI_PLATFORM=$PLATFORM_TRIPLET] ) @@ -1019,6 +1080,8 @@ AS_CASE([$host/$ac_cv_cc_name], [powerpc64le-*-linux-gnu/clang], [PY_SUPPORT_TIER=3], dnl Linux on PPC64 little endian, glibc, clang [s390x-*-linux-gnu/gcc], [PY_SUPPORT_TIER=3], dnl Linux on 64bit s390x (big endian), glibc, gcc [x86_64-*-freebsd*/clang], [PY_SUPPORT_TIER=3], dnl FreeBSD on AMD64 + [aarch64-apple-ios*-simulator/clang], [PY_SUPPORT_TIER=3], dnl iOS Simulator on arm64 + [aarch64-apple-ios*/clang], [PY_SUPPORT_TIER=3], dnl iOS on ARM64 [PY_SUPPORT_TIER=0] ) @@ -1337,12 +1400,15 @@ AC_MSG_CHECKING([LDLIBRARY]) # will find it with a -framework option). For this reason there is an # extra variable BLDLIBRARY against which Python and the extension # modules are linked, BLDLIBRARY. This is normally the same as -# LDLIBRARY, but empty for MacOSX framework builds. +# LDLIBRARY, but empty for MacOSX framework builds. iOS does the same, +# but uses a non-versioned framework layout. if test "$enable_framework" then case $ac_sys_system in Darwin) LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; + iOS) + LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; *) AC_MSG_ERROR([Unknown platform for framework build]);; esac @@ -1400,6 +1466,9 @@ if test $enable_shared = "yes"; then BLDLIBRARY='-L. -lpython$(LDVERSION)' RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} ;; + iOS) + LDLIBRARY='libpython$(LDVERSION).dylib' + ;; AIX*) LDLIBRARY='libpython$(LDVERSION).so' RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} @@ -3167,6 +3236,7 @@ if test -z "$SHLIB_SUFFIX"; then esac ;; CYGWIN*) SHLIB_SUFFIX=.dll;; + iOS) SHLIB_SUFFIX=.dylib;; *) SHLIB_SUFFIX=.so;; esac fi @@ -3247,6 +3317,11 @@ then BLDSHARED="$LDSHARED" fi ;; + iOS/*) + LDSHARED='$(CC) -dynamiclib -F . -framework Python' + LDCXXSHARED='$(CXX) -dynamiclib -F . -framework Python' + BLDSHARED="$LDSHARED" + ;; Emscripten|WASI) LDSHARED='$(CC) -shared' LDCXXSHARED='$(CXX) -shared';; @@ -3366,30 +3441,34 @@ then Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; # -u libsys_s pulls in all symbols in libsys - Darwin/*) + Darwin/*|iOS/*) LINKFORSHARED="$extra_undefs -framework CoreFoundation" # Issue #18075: the default maximum stack size (8MBytes) is too # small for the default recursion limit. Increase the stack size # to ensure that tests don't crash - stack_size="1000000" # 16 MB - if test "$with_ubsan" = "yes" - then - # Undefined behavior sanitizer requires an even deeper stack - stack_size="4000000" # 64 MB - fi + stack_size="1000000" # 16 MB + if test "$with_ubsan" = "yes" + then + # Undefined behavior sanitizer requires an even deeper stack + stack_size="4000000" # 64 MB + fi - LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" + AC_DEFINE_UNQUOTED([THREAD_STACK_SIZE], + [0x$stack_size], + [Custom thread stack size depending on chosen sanitizer runtimes.]) - AC_DEFINE_UNQUOTED([THREAD_STACK_SIZE], - [0x$stack_size], - [Custom thread stack size depending on chosen sanitizer runtimes.]) + if test $ac_sys_system = "Darwin"; then + LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" - if test "$enable_framework" - then - LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' + if test "$enable_framework"; then + LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' + fi + LINKFORSHARED="$LINKFORSHARED" + elif test $ac_sys_system = "iOS"; then + LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' fi - LINKFORSHARED="$LINKFORSHARED";; + ;; OpenUNIX*|UnixWare*) LINKFORSHARED="-Wl,-Bexport";; SCO_SV*) LINKFORSHARED="-Wl,-Bexport";; ReliantUNIX*) LINKFORSHARED="-W1 -Blargedynsym";; @@ -3763,6 +3842,9 @@ AS_VAR_IF([have_libffi], [yes], [ dnl when do we need USING_APPLE_OS_LIBFFI? ctypes_malloc_closure=yes ], + [iOS], [ + ctypes_malloc_closure=yes + ], [sunos5], [AS_VAR_APPEND([LIBFFI_LIBS], [" -mimpure-text"])] ) AS_VAR_IF([ctypes_malloc_closure], [yes], [ @@ -4829,8 +4911,8 @@ AC_CHECK_FUNCS([ \ copy_file_range ctermid dup dup3 execv explicit_bzero explicit_memset \ faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \ fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \ - gai_strerror getegid getentropy geteuid getgid getgrent getgrgid getgrgid_r \ - getgrnam_r getgrouplist getgroups gethostname getitimer getloadavg getlogin \ + gai_strerror getegid geteuid getgid getgrent getgrgid getgrgid_r \ + getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin \ getpeername getpgid getpid getppid getpriority _getpty \ getpwent getpwnam_r getpwuid getpwuid_r getresgid getresuid getrusage getsid getspent \ getspnam getuid getwd grantpt if_nameindex initgroups kill killpg lchown linkat \ @@ -4847,7 +4929,7 @@ AC_CHECK_FUNCS([ \ setresuid setreuid setsid setuid setvbuf shutdown sigaction sigaltstack \ sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \ sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \ - sysconf system tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ + sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ tmpnam tmpnam_r truncate ttyname umask uname unlinkat unlockpt utimensat utimes vfork \ wait wait3 wait4 waitid waitpid wcscoll wcsftime wcsxfrm wmemcmp writev \ ]) @@ -4859,6 +4941,14 @@ if test "$MACHDEP" != linux; then AC_CHECK_FUNCS([lchmod]) fi +# iOS defines some system methods that can be linked (so they are +# found by configure), but either raise a compilation error (because the +# header definition prevents usage - autoconf doesn't use the headers), or +# raise an error if used at runtime. Force these symbols off. +if test "$ac_sys_system" != "iOS" ; then + AC_CHECK_FUNCS([getentropy getgroups system]) +fi + AC_CHECK_DECL([dirfd], [AC_DEFINE([HAVE_DIRFD], [1], [Define if you have the 'dirfd' function or macro.])], @@ -5159,11 +5249,16 @@ AC_CHECK_FUNCS([clock_getres], [], [ ]) ]) -AC_CHECK_FUNCS([clock_settime], [], [ - AC_CHECK_LIB([rt], [clock_settime], [ - AC_DEFINE([HAVE_CLOCK_SETTIME], [1]) - ]) -]) +# On iOS, clock_settime can be linked (so it is found by +# configure), but it raises a runtime error if used because apps can't change +# the clock. Force the symbol off. +if test "$ac_sys_system" != "iOS" ; then + AC_CHECK_FUNCS([clock_settime], [], [ + AC_CHECK_LIB([rt], [clock_settime], [ + AC_DEFINE([HAVE_CLOCK_SETTIME], [1]) + ]) + ]) +fi AC_CHECK_FUNCS([clock_nanosleep], [], [ AC_CHECK_LIB([rt], [clock_nanosleep], [ @@ -5885,16 +5980,23 @@ AC_MSG_CHECKING([LDVERSION]) LDVERSION='$(VERSION)$(ABIFLAGS)' AC_MSG_RESULT([$LDVERSION]) -# On Android and Cygwin the shared libraries must be linked with libpython. +# Configure the flags and dependencies used when compiling shared modules AC_SUBST([MODULE_DEPS_SHARED]) AC_SUBST([MODULE_LDFLAGS]) MODULE_DEPS_SHARED='$(MODULE_DEPS_STATIC) $(EXPORTSYMS)' MODULE_LDFLAGS='' + +# On Android and Cygwin the shared libraries must be linked with libpython. if test "$PY_ENABLE_SHARED" = "1" && ( test -n "$ANDROID_API_LEVEL" || test "$MACHDEP" = "cygwin"); then MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(LDLIBRARY)" MODULE_LDFLAGS="\$(BLDLIBRARY)" fi +# On iOS the shared libraries must be linked with the Python framework +if test "$ac_sys_system" == "iOS"; then + MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)" +fi + AC_SUBST([BINLIBDEST]) BINLIBDEST='$(LIBDIR)/python$(VERSION)' @@ -6520,28 +6622,35 @@ CPPFLAGS=$ac_save_cppflags AC_MSG_NOTICE([checking for device files]) dnl NOTE: Inform user how to proceed with files when cross compiling. -if test "x$cross_compiling" = xyes; then - if test "${ac_cv_file__dev_ptmx+set}" != set; then - AC_MSG_CHECKING([for /dev/ptmx]) - AC_MSG_RESULT([not set]) - AC_MSG_ERROR([set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling]) - fi - if test "${ac_cv_file__dev_ptc+set}" != set; then - AC_MSG_CHECKING([for /dev/ptc]) - AC_MSG_RESULT([not set]) - AC_MSG_ERROR([set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling]) +dnl iOS cross-compile builds are predictable; they won't ever +dnl have /dev/ptmx or /dev/ptc, so we can set them explicitly. +if test "$ac_sys_system" = "iOS" ; then + ac_cv_file__dev_ptmx=no + ac_cv_file__dev_ptc=no +else + if test "x$cross_compiling" = xyes; then + if test "${ac_cv_file__dev_ptmx+set}" != set; then + AC_MSG_CHECKING([for /dev/ptmx]) + AC_MSG_RESULT([not set]) + AC_MSG_ERROR([set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling]) + fi + if test "${ac_cv_file__dev_ptc+set}" != set; then + AC_MSG_CHECKING([for /dev/ptc]) + AC_MSG_RESULT([not set]) + AC_MSG_ERROR([set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling]) + fi fi -fi -AC_CHECK_FILE([/dev/ptmx], [], []) -if test "x$ac_cv_file__dev_ptmx" = xyes; then - AC_DEFINE([HAVE_DEV_PTMX], [1], - [Define to 1 if you have the /dev/ptmx device file.]) -fi -AC_CHECK_FILE([/dev/ptc], [], []) -if test "x$ac_cv_file__dev_ptc" = xyes; then - AC_DEFINE([HAVE_DEV_PTC], [1], - [Define to 1 if you have the /dev/ptc device file.]) + AC_CHECK_FILE([/dev/ptmx], [], []) + if test "x$ac_cv_file__dev_ptmx" = xyes; then + AC_DEFINE([HAVE_DEV_PTMX], [1], + [Define to 1 if you have the /dev/ptmx device file.]) + fi + AC_CHECK_FILE([/dev/ptc], [], []) + if test "x$ac_cv_file__dev_ptc" = xyes; then + AC_DEFINE([HAVE_DEV_PTC], [1], + [Define to 1 if you have the /dev/ptc device file.]) + fi fi if test $ac_sys_system = Darwin @@ -7181,6 +7290,28 @@ AS_CASE([$ac_sys_system], [VxWorks*], [PY_STDLIB_MOD_SET_NA([_scproxy], [termios], [grp])], dnl The _scproxy module is available on macOS [Darwin], [], + [iOS], [ + dnl subprocess and multiprocessing are not supported (no fork syscall). + dnl curses and tkinter user interface are not available. + dnl gdbm and nis aren't available + dnl Stub implementations are provided for pwd, grp etc APIs + PY_STDLIB_MOD_SET_NA( + [_curses], + [_curses_panel], + [_gdbm], + [_multiprocessing], + [_posixshmem], + [_posixsubprocess], + [_scproxy], + [_tkinter], + [grp], + [nis], + [readline], + [pwd], + [spwd], + [syslog], + ) + ], [CYGWIN*], [PY_STDLIB_MOD_SET_NA([_scproxy])], [QNX*], [PY_STDLIB_MOD_SET_NA([_scproxy])], [FreeBSD*], [PY_STDLIB_MOD_SET_NA([_scproxy])], diff --git a/iOS/README.rst b/iOS/README.rst new file mode 100644 index 000000000000000..1043b5ecebbc0c7 --- /dev/null +++ b/iOS/README.rst @@ -0,0 +1,321 @@ +==================== +Python on iOS README +==================== + +:Authors: + Russell Keith-Magee (2023-11) + +This document provides a quick overview of some iOS specific features in the +Python distribution. + +These instructions are only needed if you're planning to compile Python for iOS +yourself. Most users should *not* need to do this. If you're looking to +experiment with writing an iOS app in Python, tools such as `BeeWare's Briefcase +<https://briefcase.readthedocs.io>`__ and `Kivy's Buildozer +<https://buildozer.readthedocs.io>`__ will provide a much more approachable +user experience. + +Compilers for building on iOS +============================= + +Building for iOS requires the use of Apple's Xcode tooling. It is strongly +recommended that you use the most recent stable release of Xcode. This will +require the use of the most (or second-most) recently released macOS version, +as Apple does not maintain Xcode for older macOS versions. The Xcode Command +Line Tools are not sufficient for iOS development; you need a *full* Xcode +install. + +If you want to run your code on the iOS simulator, you'll also need to install +an iOS Simulator Platform. You should be prompted to select an iOS Simulator +Platform when you first run Xcode. Alternatively, you can add an iOS Simulator +Platform by selecting an open the Platforms tab of the Xcode Settings panel. + +iOS specific arguments to configure +=================================== + +* ``--enable-framework=DIR`` + + This argument specifies the location where the Python.framework will be + installed. This argument is required for all iOS builds; a directory *must* + be specified. + +* ``--with-framework-name=NAME`` + + Specify the name for the Python framework; defaults to ``Python``. + +Building Python on iOS +====================== + +ABIs and Architectures +---------------------- + +iOS apps can be deployed on physical devices, and on the iOS simulator. Although +the API used on these devices is identical, the ABI is different - you need to +link against different libraries for an iOS device build (``iphoneos``) or an +iOS simulator build (``iphonesimulator``). + +Apple uses the ``XCframework`` format to allow specifying a single dependency +that supports multiple ABIs. An ``XCframework`` is a wrapper around multiple +ABI-specific frameworks that share a common API. + +iOS can also support different CPU architectures within each ABI. At present, +there is only a single supported architecture on physical devices - ARM64. +However, the *simulator* supports 2 architectures - ARM64 (for running on Apple +Silicon machines), and x86_64 (for running on older Intel-based machines). + +To support multiple CPU architectures on a single platform, Apple uses a "fat +binary" format - a single physical file that contains support for multiple +architectures. It is possible to compile and use a "thin" single architecture +version of a binary for testing purposes; however, the "thin" binary will not be +portable to machines using other architectures. + +Building a single-architecture framework +---------------------------------------- + +The Python build system will create a ``Python.framework`` that supports a +*single* ABI with a *single* architecture. Unlike macOS, iOS does not allow a +framework to contain non-library content, so the iOS build will produce a +``bin`` and ``lib`` folder in the same output folder as ``Python.framework``. +The ``lib`` folder will be needed at runtime to support the Python library. + +If you want to use Python in a real iOS project, you need to produce multiple +``Python.framework`` builds, one for each ABI and architecture. iOS builds of +Python *must* be constructed as framework builds. To support this, you must +provide the ``--enable-framework`` flag when configuring the build. The build +also requires the use of cross-compilation. The minimal commands for building +Python for the ARM64 iOS simulator will look something like:: + + $ export PATH="`pwd`/iOS/Resources/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" + $ ./configure \ + AR=arm64-apple-ios-simulator-ar \ + CC=arm64-apple-ios-simulator-clang \ + CPP=arm64-apple-ios-simulator-cpp \ + CXX=arm64-apple-ios-simulator-clang \ + --enable-framework=/path/to/install \ + --host=arm64-apple-ios-simulator \ + --build=arm64-apple-darwin \ + --with-build-python=/path/to/python.exe + $ make + $ make install + +In this invocation: + +* ``iOS/Resources/bin`` has been added to the path, providing some shims for the + compilers and linkers needed by the build. Xcode requires the use of ``xcrun`` + to invoke compiler tooling. However, if ``xcrun`` is pre-evaluated and the + result passed to ``configure``, these results can embed user- and + version-specific paths into the sysconfig data, which limits the portability + of the compiled Python. Alternatively, if ``xcrun`` is used *as* the compiler, + it requires that compiler variables like ``CC`` include spaces, which can + cause significant problems with many C configuration systems which assume that + ``CC`` will be a single executable. + + To work around this problem, the ``iOS/Resources/bin`` folder contains some + wrapper scripts that present as simple compilers and linkers, but wrap + underlying calls to ``xcrun``. This allows configure to use a ``CC`` + definition without spaces, and without user- or version-specific paths, while + retaining the ability to adapt to the local Xcode install. These scripts are + included in the ``bin`` directory of an iOS install. + + These scripts will, by default, use the currently active Xcode installation. + If you want to use a different Xcode installation, you can use + ``xcode-select`` to set a new default Xcode globally, or you can use the + ``DEVELOPER_DIR`` environment variable to specify an Xcode install. The + scripts will use the default ``iphoneos``/``iphonesimulator`` SDK version for + the select Xcode install; if you want to use a different SDK, you can set the + ``IOS_SDK_VERSION`` environment variable. (e.g, setting + ``IOS_SDK_VERSION=17.1`` would cause the scripts to use the ``iphoneos17.1`` + and ``iphonesimulator17.1`` SDKs, regardless of the Xcode default.) + + The path has also been cleared of any user customizations. A common source of + bugs is for tools like Homebrew to accidentally leak macOS binaries into an iOS + build. Resetting the path to a known "bare bones" value is the easiest way to + avoid these problems. + +* ``/path/to/install`` is the location where the final ``Python.framework`` will + be output. + +* ``--host`` is the architecture and ABI that you want to build, in GNU compiler + triple format. This will be one of: + + - ``arm64-apple-ios`` for ARM64 iOS devices. + - ``arm64-apple-ios-simulator`` for the iOS simulator running on Apple + Silicon devices. + - ``x86_64-apple-ios-simulator`` for the iOS simulator running on Intel + devices. + +* ``--build`` is the GNU compiler triple for the machine that will be running + the compiler. This is one of: + + - ``arm64-apple-darwin`` for Apple Silicon devices. + - ``x86_64-apple-darwin`` for Intel devices. + +* ``/path/to/python.exe`` is the path to a Python binary on the machine that + will be running the compiler. This is needed because the Python compilation + process involves running some Python code. On a normal desktop build of + Python, you can compile a python interpreter and then use that interpreter to + run Python code. However, the binaries produced for iOS won't run on macOS, so + you need to provide an external Python interpreter. This interpreter must be + the same version as the Python that is being compiled. To be completely safe, + this should be the *exact* same commit hash. However, the longer a Python + release has been stable, the more likely it is that this constraint can be + relaxed - the same micro version will often be sufficient. + +For a full CPython build, you also need to specify the paths to iOS builds of +the binary libraries that CPython depends on (XZ, BZip2, LibFFI and OpenSSL). +This can be done by defining the ``LIBLZMA_CFLAGS``, ``LIBLZMA_LIBS``, +``BZIP2_CFLAGS``, ``BZIP2_LIBS``, ``LIBFFI_CFLAGS``, and ``LIBFFI_LIBS`` +environment variables, and the ``--with-openssl`` configure option. Versions of +these libraries pre-compiled for iOS can be found in `this repository +<https://github.com/beeware/cpython-apple-source-deps/releases>`__. + +By default, Python will be compiled with an iOS deployment target (i.e., the +minimum supported iOS version) of 12.0. To specify a different deployment +target, provide the version number as part of the ``--host`` argument - for +example, ``--host=arm64-apple-ios15.4-simulator`` would compile an ARM64 +simulator build with a deployment target of 15.4. + +Merge thin frameworks into fat frameworks +----------------------------------------- + +Once you've built a ``Python.framework`` for each ABI and and architecture, you +must produce a "fat" framework for each ABI that contains all the architectures +for that ABI. + +The ``iphoneos`` build only needs to support a single architecture, so it can be +used without modification. + +If you only want to support a single simulator architecture, (e.g., only support +ARM64 simulators), you can use a single architecture ``Python.framework`` build. +However, if you want to create ``Python.xcframework`` that supports *all* +architectures, you'll need to merge the ``iphonesimulator`` builds for ARM64 and +x86_64 into a single "fat" framework. + +The "fat" framework can be constructed by performing a directory merge of the +content of the two "thin" ``Python.framework`` directories, plus the ``bin`` and +``lib`` folders for each thin framework. When performing this merge: + +* The pure Python standard library content is identical for each architecture, + except for a handful of platform-specific files (such as the ``sysconfig`` + module). Ensure that the "fat" framework has the union of all standard library + files. + +* Any binary files in the standard library, plus the main + ``libPython3.X.dylib``, can be merged using the ``lipo`` tool, provide by + Xcode:: + + $ lipo -create -output module.dylib path/to/x86_64/module.dylib path/to/arm64/module.dylib + +* The header files will be indentical on both architectures, except for + ``pyconfig.h``. Copy all the headers from one platform (say, arm64), rename + ``pyconfig.h`` to ``pyconfig-arm64.h``, and copy the ``pyconfig.h`` for the + other architecture into the merged header folder as ``pyconfig-x86_64.h``. + Then copy the ``iOS/Resources/pyconfig.h`` file from the CPython sources into + the merged headers folder. This will allow the two Python architectures to + share a common ``pyconfig.h`` header file. + +At this point, you should have 2 Python.framework folders - one for ``iphoneos``, +and one for ``iphonesimulator`` that is a merge of x86+64 and ARM64 content. + +Merge frameworks into an XCframework +------------------------------------ + +Now that we have 2 (potentially fat) ABI-specific frameworks, we can merge those +frameworks into a single ``XCframework``. + +The initial skeleton of an ``XCframework`` is built using:: + + xcodebuild -create-xcframework -output Python.xcframework -framework path/to/iphoneos/Python.framework -framework path/to/iphonesimulator/Python.framework + +Then, copy the ``bin`` and ``lib`` folders into the architecture-specific slices of +the XCframework:: + + cp path/to/iphoneos/bin Python.xcframework/ios-arm64 + cp path/to/iphoneos/lib Python.xcframework/ios-arm64 + + cp path/to/iphonesimulator/bin Python.xcframework/ios-arm64_x86-64-simulator + cp path/to/iphonesimulator/lib Python.xcframework/ios-arm64_x86-64-simulator + +Note that the name of the architecture-specific slice for the simulator will +depend on the CPU architecture that you build. + +Then, add symbolic links to "common" platform names for each slice:: + + ln -si ios-arm64 Python.xcframework/iphoneos + ln -si ios-arm64_x86-64-simulator Python.xcframework/iphonesimulator + +You now have a Python.xcframework that can be used in a project. + +Testing Python on iOS +===================== + +The ``iOS/testbed`` folder that contains an Xcode project that is able to run +the iOS test suite. This project converts the Python test suite into a single +test case in Xcode's XCTest framework. The single XCTest passes if the test +suite passes. + +To run the test suite, configure a Python build for an iOS simulator (i.e., +``--host=arm64-apple-ios-simulator`` or ``--host=x86_64-apple-ios-simulator`` +), setting the framework location to the testbed project:: + + --enable-framework="./iOS/testbed/Python.xcframework/ios-arm64_x86_64-simulator" + +Then run ``make all install testiOS``. This will build an iOS framework for your +chosen architecture, install the Python iOS framework into the testbed project, +and run the test suite on an "iPhone SE (3rd generation)" simulator. + +While the test suite is running, Xcode does not display any console output. +After showing some Xcode build commands, the console output will print ``Testing +started``, and then appear to stop. It will remain in this state until the test +suite completes. On a 2022 M1 MacBook Pro, the test suite takes approximately 12 +minutes to run; a couple of extra minutes is required to boot and prepare the +iOS simulator. + +On success, the test suite will exit and report successful completion of the +test suite. No output of the Python test suite will be displayed. + +On failure, the output of the Python test suite *will* be displayed. This will +show the details of the tests that failed. + +Debugging test failures +----------------------- + +The easiest way to diagnose a single test failure is to open the testbed project +in Xcode and run the tests from there using the "Product > Test" menu item. + +Running specific tests +^^^^^^^^^^^^^^^^^^^^^^ + +As the test suite is being executed on an iOS simulator, it is not possible to +pass in command line arguments to configure test suite operation. To work around +this limitation, the arguments that would normally be passed as command line +arguments are configured as a static string at the start of the XCTest method +``- (void)testPython`` in ``iOSTestbedTests.m``. To pass an argument to the test +suite, add a a string to the ``argv`` defintion. These arguments will be passed +to the test suite as if they had been passed to ``python -m test`` at the +command line. + +Disabling automated breakpoints +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +By default, Xcode will inserts an automatic breakpoint whenever a signal is +raised. The Python test suite raises many of these signals as part of normal +operation; unless you are trying to diagnose an issue with signals, the +automatic breakpoints can be inconvenient. However, they can be disabled by +creating a symbolic breakpoint that is triggered at the start of the test run. + +Select "Debug > Breakpoints > Create Symbolic Breakpoint" from the Xcode menu, and +populate the new brewpoint with the following details: + +* **Name**: IgnoreSignals +* **Symbol**: UIApplicationMain +* **Action**: Add debugger commands for: + - ``process handle SIGINT -n true -p true -s false`` + - ``process handle SIGUSR1 -n true -p true -s false`` + - ``process handle SIGUSR2 -n true -p true -s false`` + - ``process handle SIGXFSZ -n true -p true -s false`` +* Check the "Automatically continue after evaluating" box. + +All other details can be left blank. When the process executes the +``UIApplicationMain`` entry point, the breakpoint will trigger, run the debugger +commands to disable the automatic breakpoints, and automatically resume. diff --git a/iOS/Resources/Info.plist.in b/iOS/Resources/Info.plist.in new file mode 100644 index 000000000000000..3ecdc894f0a285e --- /dev/null +++ b/iOS/Resources/Info.plist.in @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd"> +<plist version="0.9"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>en</string> + <key>CFBundleExecutable</key> + <string>Python</string> + <key>CFBundleGetInfoString</key> + <string>Python Runtime and Library</string> + <key>CFBundleIdentifier</key> + <string>@PYTHONFRAMEWORKIDENTIFIER@</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>Python</string> + <key>CFBundlePackageType</key> + <string>FMWK</string> + <key>CFBundleShortVersionString</key> + <string>%VERSION%</string> + <key>CFBundleLongVersionString</key> + <string>%VERSION%, (c) 2001-2024 Python Software Foundation.</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1</string> + <key>CFBundleSupportedPlatforms</key> + <array> + <string>iPhoneOS</string> + </array> + <key>MinimumOSVersion</key> + <string>@IOS_DEPLOYMENT_TARGET@</string> +</dict> +</plist> diff --git a/iOS/Resources/bin/arm64-apple-ios-ar b/iOS/Resources/bin/arm64-apple-ios-ar new file mode 100755 index 000000000000000..8122332b9c1de04 --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-ar @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} ar $@ diff --git a/iOS/Resources/bin/arm64-apple-ios-clang b/iOS/Resources/bin/arm64-apple-ios-clang new file mode 100755 index 000000000000000..4d525751eba798c --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-clang @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios $@ diff --git a/iOS/Resources/bin/arm64-apple-ios-cpp b/iOS/Resources/bin/arm64-apple-ios-cpp new file mode 100755 index 000000000000000..891bb25bb4318c0 --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-cpp @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios -E $@ diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-ar b/iOS/Resources/bin/arm64-apple-ios-simulator-ar new file mode 100755 index 000000000000000..74ed3bc6df1c2bf --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-simulator-ar @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar $@ diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-clang b/iOS/Resources/bin/arm64-apple-ios-simulator-clang new file mode 100755 index 000000000000000..32574cad2844415 --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios-simulator $@ diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-cpp b/iOS/Resources/bin/arm64-apple-ios-simulator-cpp new file mode 100755 index 000000000000000..6aaf6fbe188c326 --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-simulator-cpp @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios-simulator -E $@ diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-ar b/iOS/Resources/bin/x86_64-apple-ios-simulator-ar new file mode 100755 index 000000000000000..74ed3bc6df1c2bf --- /dev/null +++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-ar @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar $@ diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-clang b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang new file mode 100755 index 000000000000000..bcbe91f6061e160 --- /dev/null +++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios-simulator $@ diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp b/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp new file mode 100755 index 000000000000000..e6a42d9b85dec7d --- /dev/null +++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios-simulator -E $@ diff --git a/iOS/Resources/dylib-Info-template.plist b/iOS/Resources/dylib-Info-template.plist new file mode 100644 index 000000000000000..f652e272f71c88a --- /dev/null +++ b/iOS/Resources/dylib-Info-template.plist @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>en</string> + <key>CFBundleExecutable</key> + <string></string> + <key>CFBundleIdentifier</key> + <string></string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleSupportedPlatforms</key> + <array> + <string>iPhoneOS</string> + </array> + <key>MinimumOSVersion</key> + <string>12.0</string> + <key>CFBundleVersion</key> + <string>1</string> +</dict> +</plist> diff --git a/iOS/Resources/pyconfig.h b/iOS/Resources/pyconfig.h new file mode 100644 index 000000000000000..4acff2c60516377 --- /dev/null +++ b/iOS/Resources/pyconfig.h @@ -0,0 +1,7 @@ +#ifdef __arm64__ +#include "pyconfig-arm64.h" +#endif + +#ifdef __x86_64__ +#include "pyconfig-x86_64.h" +#endif From de0b4f95cbfe1f868514029289597204074c05c8 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy <tjreedy@udel.edu> Date: Sun, 25 Feb 2024 22:50:49 -0500 Subject: [PATCH 455/507] gh-115921: Change 'equation' to 'expression' in random.rst (#115927) In uniform function entry. --- Doc/library/random.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/library/random.rst b/Doc/library/random.rst index d0ced2416c9578d..8fbce18c56f17c9 100644 --- a/Doc/library/random.rst +++ b/Doc/library/random.rst @@ -301,7 +301,8 @@ be found in any statistics text. ``a <= b`` and ``b <= N <= a`` for ``b < a``. The end-point value ``b`` may or may not be included in the range - depending on floating-point rounding in the equation ``a + (b-a) * random()``. + depending on floating-point rounding in the expression + ``a + (b-a) * random()``. .. function:: triangular(low, high, mode) From 8e8ab75d97f51c2850eb8cd711010662d5f1d360 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 26 Feb 2024 08:49:04 +0200 Subject: [PATCH 456/507] gh-101100: Fix Sphinx warnings in `whatsnew/2.1.rst` (#112357) Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu> Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com> --- Doc/tools/.nitignore | 1 - Doc/whatsnew/2.1.rst | 74 ++++++++++++++++++++++---------------------- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 18eac5ff4063120..5fbc24c6ee65a51 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -78,7 +78,6 @@ Doc/reference/compound_stmts.rst Doc/reference/datamodel.rst Doc/tutorial/datastructures.rst Doc/using/windows.rst -Doc/whatsnew/2.1.rst Doc/whatsnew/2.4.rst Doc/whatsnew/2.5.rst Doc/whatsnew/2.6.rst diff --git a/Doc/whatsnew/2.1.rst b/Doc/whatsnew/2.1.rst index 6d2d3cc02b87689..b4002f06e92adc2 100644 --- a/Doc/whatsnew/2.1.rst +++ b/Doc/whatsnew/2.1.rst @@ -48,7 +48,7 @@ nested recursive function definition doesn't work:: return g(value-1) + 1 ... -The function :func:`g` will always raise a :exc:`NameError` exception, because +The function :func:`!g` will always raise a :exc:`NameError` exception, because the binding of the name ``g`` isn't in either its local namespace or in the module-level namespace. This isn't much of a problem in practice (how often do you recursively define interior functions like this?), but this also made using @@ -104,7 +104,7 @@ To make the preceding explanation a bit clearer, here's an example:: Line 4 containing the ``exec`` statement is a syntax error, since ``exec`` would define a new local variable named ``x`` whose value should -be accessed by :func:`g`. +be accessed by :func:`!g`. This shouldn't be much of a limitation, since ``exec`` is rarely used in most Python code (and when it is used, it's often a sign of a poor design @@ -161,7 +161,7 @@ PEP 207: Rich Comparisons In earlier versions, Python's support for implementing comparisons on user-defined classes and extension types was quite simple. Classes could implement a -:meth:`__cmp__` method that was given two instances of a class, and could only +:meth:`!__cmp__` method that was given two instances of a class, and could only return 0 if they were equal or +1 or -1 if they weren't; the method couldn't raise an exception or return anything other than a Boolean value. Users of Numeric Python often found this model too weak and restrictive, because in the @@ -175,21 +175,21 @@ In Python 2.1, rich comparisons were added in order to support this need. Python classes can now individually overload each of the ``<``, ``<=``, ``>``, ``>=``, ``==``, and ``!=`` operations. The new magic method names are: -+-----------+----------------+ -| Operation | Method name | -+===========+================+ -| ``<`` | :meth:`__lt__` | -+-----------+----------------+ -| ``<=`` | :meth:`__le__` | -+-----------+----------------+ -| ``>`` | :meth:`__gt__` | -+-----------+----------------+ -| ``>=`` | :meth:`__ge__` | -+-----------+----------------+ -| ``==`` | :meth:`__eq__` | -+-----------+----------------+ -| ``!=`` | :meth:`__ne__` | -+-----------+----------------+ ++-----------+------------------------+ +| Operation | Method name | ++===========+========================+ +| ``<`` | :meth:`~object.__lt__` | ++-----------+------------------------+ +| ``<=`` | :meth:`~object.__le__` | ++-----------+------------------------+ +| ``>`` | :meth:`~object.__gt__` | ++-----------+------------------------+ +| ``>=`` | :meth:`~object.__ge__` | ++-----------+------------------------+ +| ``==`` | :meth:`~object.__eq__` | ++-----------+------------------------+ +| ``!=`` | :meth:`~object.__ne__` | ++-----------+------------------------+ (The magic methods are named after the corresponding Fortran operators ``.LT.``. ``.LE.``, &c. Numeric programmers are almost certainly quite familiar with @@ -208,7 +208,7 @@ The built-in ``cmp(A,B)`` function can use the rich comparison machinery, and now accepts an optional argument specifying which comparison operation to use; this is given as one of the strings ``"<"``, ``"<="``, ``">"``, ``">="``, ``"=="``, or ``"!="``. If called without the optional third argument, -:func:`cmp` will only return -1, 0, or +1 as in previous versions of Python; +:func:`!cmp` will only return -1, 0, or +1 as in previous versions of Python; otherwise it will call the appropriate method and can return any Python object. There are also corresponding changes of interest to C programmers; there's a new @@ -245,7 +245,7 @@ out warnings that you don't want to be displayed. Third-party modules can also use this framework to deprecate old features that they no longer wish to support. -For example, in Python 2.1 the :mod:`regex` module is deprecated, so importing +For example, in Python 2.1 the :mod:`!regex` module is deprecated, so importing it causes a warning to be printed:: >>> import regex @@ -262,7 +262,7 @@ can be used to specify a particular warning category. Filters can be added to disable certain warnings; a regular expression pattern can be applied to the message or to the module name in order to suppress a -warning. For example, you may have a program that uses the :mod:`regex` module +warning. For example, you may have a program that uses the :mod:`!regex` module and not want to spare the time to convert it to use the :mod:`re` module right now. The warning can be suppressed by calling :: @@ -274,7 +274,7 @@ now. The warning can be suppressed by calling :: This adds a filter that will apply only to warnings of the class :class:`DeprecationWarning` triggered in the :mod:`__main__` module, and applies -a regular expression to only match the message about the :mod:`regex` module +a regular expression to only match the message about the :mod:`!regex` module being deprecated, and will cause such warnings to be ignored. Warnings can also be printed only once, printed every time the offending code is executed, or turned into exceptions that will cause the program to stop (unless the @@ -368,7 +368,7 @@ dictionary:: This version works for simple things such as integers, but it has a side effect; the ``_cache`` dictionary holds a reference to the return values, so they'll never be deallocated until the Python process exits and cleans up. This isn't -very noticeable for integers, but if :func:`f` returns an object, or a data +very noticeable for integers, but if :func:`!f` returns an object, or a data structure that takes up a lot of memory, this can be a problem. Weak references provide a way to implement a cache that won't keep objects alive @@ -379,7 +379,7 @@ created by calling ``wr = weakref.ref(obj)``. The object being referred to is returned by calling the weak reference as if it were a function: ``wr()``. It will return the referenced object, or ``None`` if the object no longer exists. -This makes it possible to write a :func:`memoize` function whose cache doesn't +This makes it possible to write a :func:`!memoize` function whose cache doesn't keep objects alive, by storing weak references in the cache. :: _cache = {} @@ -402,7 +402,7 @@ weak references --- an object referenced only by proxy objects is deallocated -- but instead of requiring an explicit call to retrieve the object, the proxy transparently forwards all operations to the object as long as the object still exists. If the object is deallocated, attempting to use a proxy will cause a -:exc:`weakref.ReferenceError` exception to be raised. :: +:exc:`!weakref.ReferenceError` exception to be raised. :: proxy = weakref.proxy(obj) proxy.attr # Equivalent to obj.attr @@ -446,7 +446,7 @@ The dictionary containing attributes can be accessed as the function's :attr:`~object.__dict__`. Unlike the :attr:`~object.__dict__` attribute of class instances, in functions you can actually assign a new dictionary to :attr:`~object.__dict__`, though the new value is restricted to a regular Python dictionary; you *can't* be -tricky and set it to a :class:`UserDict` instance, or any other random object +tricky and set it to a :class:`!UserDict` instance, or any other random object that behaves like a mapping. @@ -584,11 +584,11 @@ available from the Distutils SIG at https://www.python.org/community/sigs/curren New and Improved Modules ======================== -* Ka-Ping Yee contributed two new modules: :mod:`inspect.py`, a module for - getting information about live Python code, and :mod:`pydoc.py`, a module for +* Ka-Ping Yee contributed two new modules: :mod:`!inspect.py`, a module for + getting information about live Python code, and :mod:`!pydoc.py`, a module for interactively converting docstrings to HTML or text. As a bonus, :file:`Tools/scripts/pydoc`, which is now automatically installed, uses - :mod:`pydoc.py` to display documentation given a Python module, package, or + :mod:`!pydoc.py` to display documentation given a Python module, package, or class name. For example, ``pydoc xml.dom`` displays the following:: Python Library Documentation: package xml.dom in xml @@ -617,7 +617,7 @@ New and Improved Modules Kent Beck's Smalltalk testing framework. See https://pyunit.sourceforge.net/ for more information about PyUnit. -* The :mod:`difflib` module contains a class, :class:`SequenceMatcher`, which +* The :mod:`difflib` module contains a class, :class:`~difflib.SequenceMatcher`, which compares two sequences and computes the changes required to transform one sequence into the other. For example, this module can be used to write a tool similar to the Unix :program:`diff` program, and in fact the sample program @@ -633,7 +633,7 @@ New and Improved Modules 2.1 includes an updated version of the :mod:`xml` package. Some of the noteworthy changes include support for Expat 1.2 and later versions, the ability for Expat parsers to handle files in any encoding supported by Python, and - various bugfixes for SAX, DOM, and the :mod:`minidom` module. + various bugfixes for SAX, DOM, and the :mod:`!minidom` module. * Ping also contributed another hook for handling uncaught exceptions. :func:`sys.excepthook` can be set to a callable object. When an exception isn't @@ -643,8 +643,8 @@ New and Improved Modules printing an extended traceback that not only lists the stack frames, but also lists the function arguments and the local variables for each frame. -* Various functions in the :mod:`time` module, such as :func:`asctime` and - :func:`localtime`, require a floating point argument containing the time in +* Various functions in the :mod:`time` module, such as :func:`~time.asctime` and + :func:`~time.localtime`, require a floating point argument containing the time in seconds since the epoch. The most common use of these functions is to work with the current time, so the floating point argument has been made optional; when a value isn't provided, the current time will be used. For example, log file @@ -724,10 +724,10 @@ of the more notable changes are: a discussion in comp.lang.python. A new module and method for file objects was also added, contributed by Jeff - Epler. The new method, :meth:`xreadlines`, is similar to the existing - :func:`xrange` built-in. :func:`xreadlines` returns an opaque sequence object + Epler. The new method, :meth:`!xreadlines`, is similar to the existing + :func:`!xrange` built-in. :func:`!xreadlines` returns an opaque sequence object that only supports being iterated over, reading a line on every iteration but - not reading the entire file into memory as the existing :meth:`readlines` method + not reading the entire file into memory as the existing :meth:`!readlines` method does. You'd use it like this:: for line in sys.stdin.xreadlines(): @@ -737,7 +737,7 @@ of the more notable changes are: For a fuller discussion of the line I/O changes, see the python-dev summary for January 1--15, 2001 at https://mail.python.org/pipermail/python-dev/2001-January/. -* A new method, :meth:`popitem`, was added to dictionaries to enable +* A new method, :meth:`~dict.popitem`, was added to dictionaries to enable destructively iterating through the contents of a dictionary; this can be faster for large dictionaries because there's no need to construct a list containing all the keys or values. ``D.popitem()`` removes a random ``(key, value)`` pair From 7a3518e43aa50ea57fd35863da831052749b6115 Mon Sep 17 00:00:00 2001 From: Alex Waygood <Alex.Waygood@Gmail.com> Date: Mon, 26 Feb 2024 09:22:09 +0000 Subject: [PATCH 457/507] gh-115881: Ensure `ast.parse()` parses conditional context managers even with low `feature_version` passed (#115920) --- Grammar/python.gram | 2 +- Lib/test/test_ast.py | 12 ++++-------- Lib/test/test_type_comments.py | 2 +- .../2024-02-25-19-20-05.gh-issue-115881.ro_Kuw.rst | 4 ++++ Parser/parser.c | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-25-19-20-05.gh-issue-115881.ro_Kuw.rst diff --git a/Grammar/python.gram b/Grammar/python.gram index 174b4dbb6f78427..797c195a0a91ba7 100644 --- a/Grammar/python.gram +++ b/Grammar/python.gram @@ -393,7 +393,7 @@ for_stmt[stmt_ty]: with_stmt[stmt_ty]: | invalid_with_stmt_indent | 'with' '(' a[asdl_withitem_seq*]=','.with_item+ ','? ')' ':' tc=[TYPE_COMMENT] b=block { - CHECK_VERSION(stmt_ty, 9, "Parenthesized context managers are", _PyAST_With(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA)) } + _PyAST_With(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA) } | 'with' a[asdl_withitem_seq*]=','.with_item+ ':' tc=[TYPE_COMMENT] b=block { _PyAST_With(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA) } | 'async' 'with' '(' a[asdl_withitem_seq*]=','.with_item+ ','? ')' ':' b=block { diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 3789ac22e3899cf..d49c149da42592a 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -1045,19 +1045,15 @@ def test_positional_only_feature_version(self): with self.assertRaises(SyntaxError): ast.parse('lambda x=1, /: ...', feature_version=(3, 7)) - def test_parenthesized_with_feature_version(self): - ast.parse('with (CtxManager() as example): ...', feature_version=(3, 10)) - # While advertised as a feature in Python 3.10, this was allowed starting 3.9 - ast.parse('with (CtxManager() as example): ...', feature_version=(3, 9)) - with self.assertRaises(SyntaxError): - ast.parse('with (CtxManager() as example): ...', feature_version=(3, 8)) - ast.parse('with CtxManager() as example: ...', feature_version=(3, 8)) - def test_assignment_expression_feature_version(self): ast.parse('(x := 0)', feature_version=(3, 8)) with self.assertRaises(SyntaxError): ast.parse('(x := 0)', feature_version=(3, 7)) + def test_conditional_context_managers_parse_with_low_feature_version(self): + # regression test for gh-115881 + ast.parse('with (x() if y else z()): ...', feature_version=(3, 8)) + def test_exception_groups_feature_version(self): code = dedent(''' try: ... diff --git a/Lib/test/test_type_comments.py b/Lib/test/test_type_comments.py index 5a911da56f8f8a1..ee8939f62d082cd 100644 --- a/Lib/test/test_type_comments.py +++ b/Lib/test/test_type_comments.py @@ -309,7 +309,7 @@ def test_withstmt(self): self.assertEqual(tree.body[0].type_comment, None) def test_parenthesized_withstmt(self): - for tree in self.parse_all(parenthesized_withstmt, minver=9): + for tree in self.parse_all(parenthesized_withstmt): self.assertEqual(tree.body[0].type_comment, "int") self.assertEqual(tree.body[1].type_comment, "int") tree = self.classic_parse(parenthesized_withstmt) diff --git a/Misc/NEWS.d/next/Library/2024-02-25-19-20-05.gh-issue-115881.ro_Kuw.rst b/Misc/NEWS.d/next/Library/2024-02-25-19-20-05.gh-issue-115881.ro_Kuw.rst new file mode 100644 index 000000000000000..99bccb265ff80cd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-25-19-20-05.gh-issue-115881.ro_Kuw.rst @@ -0,0 +1,4 @@ +Fix issue where :func:`ast.parse` would incorrectly flag conditional context +managers (such as ``with (x() if y else z()): ...``) as invalid syntax if +``feature_version=(3, 8)`` was passed. This reverts changes to the +grammar made as part of gh-94949. diff --git a/Parser/parser.c b/Parser/parser.c index 779b18e9650e9f5..f1170c261974520 100644 --- a/Parser/parser.c +++ b/Parser/parser.c @@ -6603,7 +6603,7 @@ with_stmt_rule(Parser *p) UNUSED(_end_lineno); // Only used by EXTRA macro int _end_col_offset = _token->end_col_offset; UNUSED(_end_col_offset); // Only used by EXTRA macro - _res = CHECK_VERSION ( stmt_ty , 9 , "Parenthesized context managers are" , _PyAST_With ( a , b , NEW_TYPE_COMMENT ( p , tc ) , EXTRA ) ); + _res = _PyAST_With ( a , b , NEW_TYPE_COMMENT ( p , tc ) , EXTRA ); if (_res == NULL && PyErr_Occurred()) { p->error_indicator = 1; p->level--; From 915d7dd090387b52f62bdc2f572413bc87297cee Mon Sep 17 00:00:00 2001 From: Antti Haapala <antti.haapala@interjektio.fi> Date: Mon, 26 Feb 2024 11:22:54 +0200 Subject: [PATCH 458/507] gh-115091: Remove a left-over sentence that refers to Py_OptimizeFlag from ctypes documentation (GH-115092) Remove a left-over sentence that refers to Py_OptimizeFlag Remove a left-over sentence that refers to an example that was present in Python 3.10 and was using ``Py_OptimizeFlag``. --- Doc/library/ctypes.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index 702955659a3bb97..eed18201e3ede05 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -1117,10 +1117,6 @@ api:: >>> print(hex(version.value)) 0x30c00a0 -If the interpreter would have been started with :option:`-O`, the sample would -have printed ``c_long(1)``, or ``c_long(2)`` if :option:`-OO` would have been -specified. - An extended example which also demonstrates the use of pointers accesses the :c:data:`PyImport_FrozenModules` pointer exported by Python. From 37f5d06b1bf830048c09ed967bb2cda945d56541 Mon Sep 17 00:00:00 2001 From: Phil Elson <pelson.pub@gmail.com> Date: Mon, 26 Feb 2024 10:53:20 +0100 Subject: [PATCH 459/507] Doc: Clarify the return type of Event.wait when timeout is used (GH-104168) --- Doc/library/threading.rst | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index 5fbf9379b8202c0..cb511471579b694 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -987,18 +987,15 @@ method. The :meth:`~Event.wait` method blocks until the flag is true. .. method:: wait(timeout=None) - Block until the internal flag is true. If the internal flag is true on - entry, return immediately. Otherwise, block until another thread calls - :meth:`.set` to set the flag to true, or until the optional timeout occurs. + Block as long as the internal flag is false and the timeout, if given, + has not expired. The return value represents the + reason that this blocking method returned; ``True`` if returning because + the internal flag is set to true, or ``False`` if a timeout is given and + the the internal flag did not become true within the given wait time. When the timeout argument is present and not ``None``, it should be a - floating point number specifying a timeout for the operation in seconds - (or fractions thereof). - - This method returns ``True`` if and only if the internal flag has been set to - true, either before the wait call or after the wait starts, so it will - always return ``True`` except if a timeout is given and the operation - times out. + floating point number specifying a timeout for the operation in seconds, + or fractions thereof. .. versionchanged:: 3.1 Previously, the method always returned ``None``. From b7383b8b71d49c761480ae9a8b2111644310e61d Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Mon, 26 Feb 2024 15:32:27 +0300 Subject: [PATCH 460/507] gh-115931: Fix `SyntaxWarning`s in `test_unparse` (#115935) --- Lib/test/test_unparse.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py index 77ce18cbf4cbfb5..106704ba8c9c2dd 100644 --- a/Lib/test/test_unparse.py +++ b/Lib/test/test_unparse.py @@ -650,9 +650,18 @@ def test_multiquote_joined_string(self): self.check_ast_roundtrip("""f'''""\"''\\'{""\"\\n\\"'''""\" '''\\n'''}''' """) def test_backslash_in_format_spec(self): - self.check_ast_roundtrip("""f"{x:\\ }" """) + import re + msg = re.escape("invalid escape sequence '\\ '") + with self.assertWarnsRegex(SyntaxWarning, msg): + self.check_ast_roundtrip("""f"{x:\\ }" """) + self.check_ast_roundtrip("""f"{x:\\n}" """) + self.check_ast_roundtrip("""f"{x:\\\\ }" """) - self.check_ast_roundtrip("""f"{x:\\\\\\ }" """) + + with self.assertWarnsRegex(SyntaxWarning, msg): + self.check_ast_roundtrip("""f"{x:\\\\\\ }" """) + self.check_ast_roundtrip("""f"{x:\\\\\\n}" """) + self.check_ast_roundtrip("""f"{x:\\\\\\\\ }" """) def test_quote_in_format_spec(self): From 015b97d19a24a169cc3c0939119e1228791e4253 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado <Pablogsal@gmail.com> Date: Mon, 26 Feb 2024 13:57:09 +0100 Subject: [PATCH 461/507] gh-115823: Calculate correctly error locations when dealing with implicit encodings (#115824) --- Lib/test/test_exceptions.py | 1 + ...-02-22-16-17-53.gh-issue-115823.c1TreJ.rst | 3 +++ Parser/pegen_errors.c | 20 +++++++++---------- 3 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-22-16-17-53.gh-issue-115823.c1TreJ.rst diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index c7e76414ff07154..c5eff8ad8ccca17 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -301,6 +301,7 @@ def baz(): { 6 0="""''', 5, 13) + check('b"fooжжж"'.encode(), 1, 1, 1, 10) # Errors thrown by symtable.c check('x = [(yield i) for i in range(3)]', 1, 7) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-22-16-17-53.gh-issue-115823.c1TreJ.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-22-16-17-53.gh-issue-115823.c1TreJ.rst new file mode 100644 index 000000000000000..8cda4c9343d4d70 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-22-16-17-53.gh-issue-115823.c1TreJ.rst @@ -0,0 +1,3 @@ +Properly calculate error ranges in the parser when raising +:exc:`SyntaxError` exceptions caused by invalid byte sequences. Patch by +Pablo Galindo diff --git a/Parser/pegen_errors.c b/Parser/pegen_errors.c index e15673d02dd3b05..e8f11a67e50fa0a 100644 --- a/Parser/pegen_errors.c +++ b/Parser/pegen_errors.c @@ -369,20 +369,18 @@ _PyPegen_raise_error_known_location(Parser *p, PyObject *errtype, Py_ssize_t col_number = col_offset; Py_ssize_t end_col_number = end_col_offset; - if (p->tok->encoding != NULL) { - col_number = _PyPegen_byte_offset_to_character_offset(error_line, col_offset); - if (col_number < 0) { + col_number = _PyPegen_byte_offset_to_character_offset(error_line, col_offset); + if (col_number < 0) { + goto error; + } + + if (end_col_offset > 0) { + end_col_number = _PyPegen_byte_offset_to_character_offset(error_line, end_col_offset); + if (end_col_number < 0) { goto error; } - if (end_col_number > 0) { - Py_ssize_t end_col_offset = _PyPegen_byte_offset_to_character_offset(error_line, end_col_number); - if (end_col_offset < 0) { - goto error; - } else { - end_col_number = end_col_offset; - } - } } + tmp = Py_BuildValue("(OnnNnn)", p->tok->filename, lineno, col_number, error_line, end_lineno, end_col_number); if (!tmp) { goto error; From 07824995a056b2894adac69813444307835cc245 Mon Sep 17 00:00:00 2001 From: Michael Droettboom <mdboom@gmail.com> Date: Mon, 26 Feb 2024 10:18:30 -0500 Subject: [PATCH 462/507] gh-113706: Update comment about long int representation (#113707) --- Include/cpython/longintrepr.h | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/Include/cpython/longintrepr.h b/Include/cpython/longintrepr.h index f5ccbb704e8bb8e..f037c7bb90deda5 100644 --- a/Include/cpython/longintrepr.h +++ b/Include/cpython/longintrepr.h @@ -62,21 +62,32 @@ typedef long stwodigits; /* signed variant of twodigits */ #define PyLong_MASK ((digit)(PyLong_BASE - 1)) /* Long integer representation. + + Long integers are made up of a number of 30- or 15-bit digits, depending on + the platform. The number of digits (ndigits) is stored in the high bits of + the lv_tag field (lvtag >> _PyLong_NON_SIZE_BITS). + The absolute value of a number is equal to - SUM(for i=0 through abs(ob_size)-1) ob_digit[i] * 2**(SHIFT*i) - Negative numbers are represented with ob_size < 0; - zero is represented by ob_size == 0. - In a normalized number, ob_digit[abs(ob_size)-1] (the most significant + SUM(for i=0 through ndigits-1) ob_digit[i] * 2**(PyLong_SHIFT*i) + + The sign of the value is stored in the lower 2 bits of lv_tag. + + - 0: Positive + - 1: Zero + - 2: Negative + + The third lowest bit of lv_tag is reserved for an immortality flag, but is + not currently used. + + In a normalized number, ob_digit[ndigits-1] (the most significant digit) is never zero. Also, in all cases, for all valid i, - 0 <= ob_digit[i] <= MASK. + 0 <= ob_digit[i] <= PyLong_MASK. + The allocation function takes care of allocating extra memory - so that ob_digit[0] ... ob_digit[abs(ob_size)-1] are actually available. + so that ob_digit[0] ... ob_digit[ndigits-1] are actually available. We always allocate memory for at least one digit, so accessing ob_digit[0] - is always safe. However, in the case ob_size == 0, the contents of + is always safe. However, in the case ndigits == 0, the contents of ob_digit[0] may be undefined. - - CAUTION: Generic code manipulating subtypes of PyVarObject has to - aware that ints abuse ob_size's sign bit. */ typedef struct _PyLongValue { From 5a832922130908994d313b56a3345ff410a0e11a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" <jaraco@jaraco.com> Date: Mon, 26 Feb 2024 11:11:38 -0500 Subject: [PATCH 463/507] Add Jason as an owner of configparser to coordinate backport concerns. (#115885) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- .github/CODEOWNERS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5dbfbbb8ebaf7e9..7e20294e90e50d2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -249,3 +249,7 @@ Lib/test/test_interpreters/ @ericsnowcurrently # SBOM /Misc/sbom.spdx.json @sethmlarson /Tools/build/generate_sbom.py @sethmlarson + +# Config Parser +Lib/configparser.py @jaraco +Lib/test/test_configparser.py @jaraco From 7259480957e10359cc5ab8786f32f197c88e274c Mon Sep 17 00:00:00 2001 From: Brandt Bucher <brandtbucher@microsoft.com> Date: Mon, 26 Feb 2024 08:32:44 -0800 Subject: [PATCH 464/507] GH-115802: JIT "small" code for macOS and Linux (GH-115826) --- Python/jit.c | 119 ++++++++++++++++++++++++++++++++++++------ Tools/jit/_schema.py | 10 ++++ Tools/jit/_targets.py | 68 +++++++++++++++++++----- 3 files changed, 168 insertions(+), 29 deletions(-) diff --git a/Python/jit.c b/Python/jit.c index 839414bd8106778..ac2c60ed925a264 100644 --- a/Python/jit.c +++ b/Python/jit.c @@ -47,18 +47,18 @@ jit_error(const char *message) PyErr_Format(PyExc_RuntimeWarning, "JIT %s (%d)", message, hint); } -static char * +static unsigned char * jit_alloc(size_t size) { assert(size); assert(size % get_page_size() == 0); #ifdef MS_WINDOWS int flags = MEM_COMMIT | MEM_RESERVE; - char *memory = VirtualAlloc(NULL, size, flags, PAGE_READWRITE); + unsigned char *memory = VirtualAlloc(NULL, size, flags, PAGE_READWRITE); int failed = memory == NULL; #else int flags = MAP_ANONYMOUS | MAP_PRIVATE; - char *memory = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, -1, 0); + unsigned char *memory = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, -1, 0); int failed = memory == MAP_FAILED; #endif if (failed) { @@ -69,7 +69,7 @@ jit_alloc(size_t size) } static int -jit_free(char *memory, size_t size) +jit_free(unsigned char *memory, size_t size) { assert(size); assert(size % get_page_size() == 0); @@ -86,7 +86,7 @@ jit_free(char *memory, size_t size) } static int -mark_executable(char *memory, size_t size) +mark_executable(unsigned char *memory, size_t size) { if (size == 0) { return 0; @@ -113,7 +113,7 @@ mark_executable(char *memory, size_t size) } static int -mark_readable(char *memory, size_t size) +mark_readable(unsigned char *memory, size_t size) { if (size == 0) { return 0; @@ -169,18 +169,20 @@ set_bits(uint32_t *loc, uint8_t loc_start, uint64_t value, uint8_t value_start, // Fill all of stencil's holes in the memory pointed to by base, using the // values in patches. static void -patch(char *base, const Stencil *stencil, uint64_t *patches) +patch(unsigned char *base, const Stencil *stencil, uint64_t *patches) { for (uint64_t i = 0; i < stencil->holes_size; i++) { const Hole *hole = &stencil->holes[i]; - void *location = base + hole->offset; + unsigned char *location = base + hole->offset; uint64_t value = patches[hole->value] + (uint64_t)hole->symbol + hole->addend; + uint8_t *loc8 = (uint8_t *)location; uint32_t *loc32 = (uint32_t *)location; uint64_t *loc64 = (uint64_t *)location; // LLD is a great reference for performing relocations... just keep in // mind that Tools/jit/build.py does filtering and preprocessing for us! // Here's a good place to start for each platform: // - aarch64-apple-darwin: + // - https://github.com/llvm/llvm-project/blob/main/lld/MachO/Arch/ARM64.cpp // - https://github.com/llvm/llvm-project/blob/main/lld/MachO/Arch/ARM64Common.cpp // - https://github.com/llvm/llvm-project/blob/main/lld/MachO/Arch/ARM64Common.h // - aarch64-unknown-linux-gnu: @@ -208,6 +210,47 @@ patch(char *base, const Stencil *stencil, uint64_t *patches) // 64-bit absolute address. *loc64 = value; continue; + case HoleKind_R_X86_64_GOTPCRELX: + case HoleKind_R_X86_64_REX_GOTPCRELX: + case HoleKind_X86_64_RELOC_GOT: + case HoleKind_X86_64_RELOC_GOT_LOAD: { + // 32-bit relative address. + // Try to relax the GOT load into an immediate value: + uint64_t relaxed = *(uint64_t *)(value + 4) - 4; + if ((int64_t)relaxed - (int64_t)location >= -(1LL << 31) && + (int64_t)relaxed - (int64_t)location + 1 < (1LL << 31)) + { + if (loc8[-2] == 0x8B) { + // mov reg, dword ptr [rip + AAA] -> lea reg, [rip + XXX] + loc8[-2] = 0x8D; + value = relaxed; + } + else if (loc8[-2] == 0xFF && loc8[-1] == 0x15) { + // call qword ptr [rip + AAA] -> nop; call XXX + loc8[-2] = 0x90; + loc8[-1] = 0xE8; + value = relaxed; + } + else if (loc8[-2] == 0xFF && loc8[-1] == 0x25) { + // jmp qword ptr [rip + AAA] -> nop; jmp XXX + loc8[-2] = 0x90; + loc8[-1] = 0xE9; + value = relaxed; + } + } + } + // Fall through... + case HoleKind_R_X86_64_GOTPCREL: + case HoleKind_R_X86_64_PC32: + case HoleKind_X86_64_RELOC_SIGNED: + case HoleKind_X86_64_RELOC_BRANCH: + // 32-bit relative address. + value -= (uint64_t)location; + // Check that we're not out of range of 32 signed bits: + assert((int64_t)value >= -(1LL << 31)); + assert((int64_t)value < (1LL << 31)); + loc32[0] = (uint32_t)value; + continue; case HoleKind_R_AARCH64_CALL26: case HoleKind_R_AARCH64_JUMP26: // 28-bit relative branch. @@ -249,10 +292,53 @@ patch(char *base, const Stencil *stencil, uint64_t *patches) set_bits(loc32, 5, value, 48, 16); continue; case HoleKind_ARM64_RELOC_GOT_LOAD_PAGE21: + case HoleKind_R_AARCH64_ADR_GOT_PAGE: // 21-bit count of pages between this page and an absolute address's // page... I know, I know, it's weird. Pairs nicely with // ARM64_RELOC_GOT_LOAD_PAGEOFF12 (below). assert(IS_AARCH64_ADRP(*loc32)); + // Try to relax the pair of GOT loads into an immediate value: + const Hole *next_hole = &stencil->holes[i + 1]; + if (i + 1 < stencil->holes_size && + (next_hole->kind == HoleKind_ARM64_RELOC_GOT_LOAD_PAGEOFF12 || + next_hole->kind == HoleKind_R_AARCH64_LD64_GOT_LO12_NC) && + next_hole->offset == hole->offset + 4 && + next_hole->symbol == hole->symbol && + next_hole->addend == hole->addend && + next_hole->value == hole->value) + { + unsigned char rd = get_bits(loc32[0], 0, 5); + assert(IS_AARCH64_LDR_OR_STR(loc32[1])); + unsigned char rt = get_bits(loc32[1], 0, 5); + unsigned char rn = get_bits(loc32[1], 5, 5); + assert(rd == rn && rn == rt); + uint64_t relaxed = *(uint64_t *)value; + if (relaxed < (1UL << 16)) { + // adrp reg, AAA; ldr reg, [reg + BBB] -> movz reg, XXX; nop + loc32[0] = 0xD2800000 | (get_bits(relaxed, 0, 16) << 5) | rd; + loc32[1] = 0xD503201F; + i++; + continue; + } + if (relaxed < (1ULL << 32)) { + // adrp reg, AAA; ldr reg, [reg + BBB] -> movz reg, XXX; movk reg, YYY + loc32[0] = 0xD2800000 | (get_bits(relaxed, 0, 16) << 5) | rd; + loc32[1] = 0xF2A00000 | (get_bits(relaxed, 16, 16) << 5) | rd; + i++; + continue; + } + relaxed = (uint64_t)value - (uint64_t)location; + if ((relaxed & 0x3) == 0 && + (int64_t)relaxed >= -(1L << 19) && + (int64_t)relaxed < (1L << 19)) + { + // adrp reg, AAA; ldr reg, [reg + BBB] -> ldr x0, XXX; nop + loc32[0] = 0x58000000 | (get_bits(relaxed, 2, 19) << 5) | rd; + loc32[1] = 0xD503201F; + i++; + continue; + } + } // Number of pages between this page and the value's page: value = (value >> 12) - ((uint64_t)location >> 12); // Check that we're not out of range of 21 signed bits: @@ -264,6 +350,7 @@ patch(char *base, const Stencil *stencil, uint64_t *patches) set_bits(loc32, 5, value, 2, 19); continue; case HoleKind_ARM64_RELOC_GOT_LOAD_PAGEOFF12: + case HoleKind_R_AARCH64_LD64_GOT_LO12_NC: // 12-bit low part of an absolute address. Pairs nicely with // ARM64_RELOC_GOT_LOAD_PAGE21 (above). assert(IS_AARCH64_LDR_OR_STR(*loc32) || IS_AARCH64_ADD_OR_SUB(*loc32)); @@ -285,7 +372,7 @@ patch(char *base, const Stencil *stencil, uint64_t *patches) } static void -copy_and_patch(char *base, const Stencil *stencil, uint64_t *patches) +copy_and_patch(unsigned char *base, const Stencil *stencil, uint64_t *patches) { memcpy(base, stencil->body, stencil->body_size); patch(base, stencil, patches); @@ -294,8 +381,8 @@ copy_and_patch(char *base, const Stencil *stencil, uint64_t *patches) static void emit(const StencilGroup *group, uint64_t patches[]) { - copy_and_patch((char *)patches[HoleValue_CODE], &group->code, patches); - copy_and_patch((char *)patches[HoleValue_DATA], &group->data, patches); + copy_and_patch((unsigned char *)patches[HoleValue_DATA], &group->data, patches); + copy_and_patch((unsigned char *)patches[HoleValue_CODE], &group->code, patches); } // Compiles executor in-place. Don't forget to call _PyJIT_Free later! @@ -316,14 +403,14 @@ _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction *trace, size assert((page_size & (page_size - 1)) == 0); code_size += page_size - (code_size & (page_size - 1)); data_size += page_size - (data_size & (page_size - 1)); - char *memory = jit_alloc(code_size + data_size); + unsigned char *memory = jit_alloc(code_size + data_size); if (memory == NULL) { return -1; } // Loop again to emit the code: - char *code = memory; - char *data = memory + code_size; - char *top = code; + unsigned char *code = memory; + unsigned char *data = memory + code_size; + unsigned char *top = code; if (trace[0].opcode == _START_EXECUTOR) { // Don't want to execute this more than once: top += stencil_groups[_START_EXECUTOR].code.body_size; @@ -360,7 +447,7 @@ _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction *trace, size void _PyJIT_Free(_PyExecutorObject *executor) { - char *memory = (char *)executor->jit_code; + unsigned char *memory = (unsigned char *)executor->jit_code; size_t size = executor->jit_size; if (memory) { executor->jit_code = NULL; diff --git a/Tools/jit/_schema.py b/Tools/jit/_schema.py index 8eeb78e6cd69eee..975ca650a13c1a8 100644 --- a/Tools/jit/_schema.py +++ b/Tools/jit/_schema.py @@ -8,13 +8,23 @@ "IMAGE_REL_AMD64_ADDR64", "IMAGE_REL_I386_DIR32", "R_AARCH64_ABS64", + "R_AARCH64_ADR_GOT_PAGE", "R_AARCH64_CALL26", "R_AARCH64_JUMP26", + "R_AARCH64_LD64_GOT_LO12_NC", "R_AARCH64_MOVW_UABS_G0_NC", "R_AARCH64_MOVW_UABS_G1_NC", "R_AARCH64_MOVW_UABS_G2_NC", "R_AARCH64_MOVW_UABS_G3", "R_X86_64_64", + "R_X86_64_GOTPCREL", + "R_X86_64_GOTPCRELX", + "R_X86_64_PC32", + "R_X86_64_REX_GOTPCRELX", + "X86_64_RELOC_BRANCH", + "X86_64_RELOC_GOT", + "X86_64_RELOC_GOT_LOAD", + "X86_64_RELOC_SIGNED", "X86_64_RELOC_UNSIGNED", ] diff --git a/Tools/jit/_targets.py b/Tools/jit/_targets.py index 6c1d440324c5052..06dc4e7acc6c91c 100644 --- a/Tools/jit/_targets.py +++ b/Tools/jit/_targets.py @@ -37,6 +37,7 @@ class _Target(typing.Generic[_S, _R]): triple: str _: dataclasses.KW_ONLY alignment: int = 1 + args: typing.Sequence[str] = () prefix: str = "" debug: bool = False force: bool = False @@ -121,21 +122,14 @@ async def _compile( "-fno-builtin", # SET_FUNCTION_ATTRIBUTE on 32-bit Windows debug builds: "-fno-jump-tables", - # Position-independent code adds indirection to every load and jump: - "-fno-pic", + "-fno-plt", # Don't make calls to weird stack-smashing canaries: "-fno-stack-protector", - # We have three options for code model: - # - "small": the default, assumes that code and data reside in the - # lowest 2GB of memory (128MB on aarch64) - # - "medium": assumes that code resides in the lowest 2GB of memory, - # and makes no assumptions about data (not available on aarch64) - # - "large": makes no assumptions about either code or data - "-mcmodel=large", "-o", f"{o}", "-std=c11", f"{c}", + *self.args, ] await _llvm.run("clang", args, echo=self.verbose) return await self._parse(o) @@ -284,7 +278,23 @@ def _handle_section( def _handle_relocation( self, base: int, relocation: _schema.ELFRelocation, raw: bytes ) -> _stencils.Hole: + symbol: str | None match relocation: + case { + "Addend": addend, + "Offset": offset, + "Symbol": {"Value": s}, + "Type": { + "Value": "R_AARCH64_ADR_GOT_PAGE" + | "R_AARCH64_LD64_GOT_LO12_NC" + | "R_X86_64_GOTPCREL" + | "R_X86_64_GOTPCRELX" + | "R_X86_64_REX_GOTPCRELX" as kind + }, + }: + offset += base + s = s.removeprefix(self.prefix) + value, symbol = _stencils.HoleValue.GOT, s case { "Addend": addend, "Offset": offset, @@ -358,6 +368,34 @@ def _handle_relocation( s = s.removeprefix(self.prefix) value, symbol = _stencils.HoleValue.GOT, s addend = 0 + case { + "Offset": offset, + "Symbol": {"Value": s}, + "Type": {"Value": "X86_64_RELOC_GOT" | "X86_64_RELOC_GOT_LOAD" as kind}, + }: + offset += base + s = s.removeprefix(self.prefix) + value, symbol = _stencils.HoleValue.GOT, s + addend = ( + int.from_bytes(raw[offset : offset + 4], "little", signed=True) - 4 + ) + case { + "Offset": offset, + "Section": {"Value": s}, + "Type": {"Value": "X86_64_RELOC_SIGNED" as kind}, + } | { + "Offset": offset, + "Symbol": {"Value": s}, + "Type": { + "Value": "X86_64_RELOC_BRANCH" | "X86_64_RELOC_SIGNED" as kind + }, + }: + offset += base + s = s.removeprefix(self.prefix) + value, symbol = _stencils.symbol_to_value(s) + addend = ( + int.from_bytes(raw[offset : offset + 4], "little", signed=True) - 4 + ) case { "Offset": offset, "Section": {"Value": s}, @@ -379,15 +417,19 @@ def _handle_relocation( def get_target(host: str) -> _COFF | _ELF | _MachO: """Build a _Target for the given host "triple" and options.""" if re.fullmatch(r"aarch64-apple-darwin.*", host): - return _MachO(host, alignment=8, prefix="_") + args = ["-mcmodel=large"] + return _MachO(host, alignment=8, args=args, prefix="_") if re.fullmatch(r"aarch64-.*-linux-gnu", host): - return _ELF(host, alignment=8) + args = ["-mcmodel=large"] + return _ELF(host, alignment=8, args=args) if re.fullmatch(r"i686-pc-windows-msvc", host): - return _COFF(host, prefix="_") + args = ["-mcmodel=large"] + return _COFF(host, args=args, prefix="_") if re.fullmatch(r"x86_64-apple-darwin.*", host): return _MachO(host, prefix="_") if re.fullmatch(r"x86_64-pc-windows-msvc", host): - return _COFF(host) + args = ["-mcmodel=large"] + return _COFF(host, args=args) if re.fullmatch(r"x86_64-.*-linux-gnu", host): return _ELF(host) raise ValueError(host) From c0fdfba7ff981c55ac13325e4dddaf382601b246 Mon Sep 17 00:00:00 2001 From: Guido van Rossum <guido@python.org> Date: Mon, 26 Feb 2024 08:42:53 -0800 Subject: [PATCH 465/507] Rename tier 2 redundancy eliminator to optimizer (#115888) The original name is just too much of a mouthful. --- .gitattributes | 2 +- .github/CODEOWNERS | 2 +- .github/workflows/jit.yml | 4 ++-- Lib/test/test_generated_cases.py | 4 ++-- Makefile.pre.in | 10 +++++----- PCbuild/regen.targets | 6 +++--- Python/optimizer_analysis.c | 8 ++++---- ...cy_eliminator_bytecodes.c => optimizer_bytecodes.c} | 0 ...ndancy_eliminator_cases.c.h => optimizer_cases.c.h} | 4 ++-- Tools/c-analyzer/cpython/_parser.py | 4 ++-- Tools/cases_generator/README.md | 6 +++--- ...r2_abstract_generator.py => optimizer_generator.py} | 10 +++++----- 12 files changed, 30 insertions(+), 30 deletions(-) rename Python/{tier2_redundancy_eliminator_bytecodes.c => optimizer_bytecodes.c} (100%) rename Python/{tier2_redundancy_eliminator_cases.c.h => optimizer_cases.c.h} (99%) rename Tools/cases_generator/{tier2_abstract_generator.py => optimizer_generator.py} (94%) diff --git a/.gitattributes b/.gitattributes index 159cd83ff7407d6..4f82cea7480cfe8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -95,7 +95,7 @@ Programs/test_frozenmain.h generated Python/Python-ast.c generated Python/executor_cases.c.h generated Python/generated_cases.c.h generated -Python/tier2_redundancy_eliminator_cases.c.h generated +Python/optimizer_cases.c.h generated Python/opcode_targets.h generated Python/stdlib_module_names.h generated Tools/peg_generator/pegen/grammar_parser.py generated diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7e20294e90e50d2..1d0bce111320ff6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -38,7 +38,7 @@ Python/ast_opt.c @isidentical Python/bytecodes.c @markshannon @gvanrossum Python/optimizer*.c @markshannon @gvanrossum Python/optimizer_analysis.c @Fidget-Spinner -Python/tier2_redundancy_eliminator_bytecodes.c @Fidget-Spinner +Python/optimizer_bytecodes.c @Fidget-Spinner Lib/test/test_patma.py @brandtbucher Lib/test/test_type_*.py @JelleZijlstra Lib/test/test_capi/test_misc.py @markshannon @gvanrossum diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml index 69c7b45376a411f..21d4603b8679ea8 100644 --- a/.github/workflows/jit.yml +++ b/.github/workflows/jit.yml @@ -5,13 +5,13 @@ on: - '**jit**' - 'Python/bytecodes.c' - 'Python/optimizer*.c' - - 'Python/tier2_redundancy_eliminator_bytecodes.c' + - 'Python/optimizer_bytecodes.c' push: paths: - '**jit**' - 'Python/bytecodes.c' - 'Python/optimizer*.c' - - 'Python/tier2_redundancy_eliminator_bytecodes.c' + - 'Python/optimizer_bytecodes.c' workflow_dispatch: concurrency: diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 0d2ccd558d436d5..6a5586822c24913 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -33,7 +33,7 @@ def skip_if_different_mount_drives(): import parser from stack import Stack import tier1_generator - import tier2_abstract_generator + import optimizer_generator def handle_stderr(): @@ -841,7 +841,7 @@ def run_cases_test(self, input: str, input2: str, expected: str): temp_input.flush() with handle_stderr(): - tier2_abstract_generator.generate_tier2_abstract_from_files( + optimizer_generator.generate_tier2_abstract_from_files( [self.temp_input_filename, self.temp_input2_filename], self.temp_output_filename ) diff --git a/Makefile.pre.in b/Makefile.pre.in index 8893da816e93364..e4c64aea80d22c3 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1889,9 +1889,9 @@ regen-cases: -o $(srcdir)/Python/generated_cases.c.h.new $(srcdir)/Python/bytecodes.c $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/tier2_generator.py \ -o $(srcdir)/Python/executor_cases.c.h.new $(srcdir)/Python/bytecodes.c - $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/tier2_abstract_generator.py \ - -o $(srcdir)/Python/tier2_redundancy_eliminator_cases.c.h.new \ - $(srcdir)/Python/tier2_redundancy_eliminator_bytecodes.c \ + $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/optimizer_generator.py \ + -o $(srcdir)/Python/optimizer_cases.c.h.new \ + $(srcdir)/Python/optimizer_bytecodes.c \ $(srcdir)/Python/bytecodes.c $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/opcode_metadata_generator.py \ -o $(srcdir)/Include/internal/pycore_opcode_metadata.h.new $(srcdir)/Python/bytecodes.c @@ -1904,7 +1904,7 @@ regen-cases: $(UPDATE_FILE) $(srcdir)/Include/internal/pycore_opcode_metadata.h $(srcdir)/Include/internal/pycore_opcode_metadata.h.new $(UPDATE_FILE) $(srcdir)/Include/internal/pycore_uop_metadata.h $(srcdir)/Include/internal/pycore_uop_metadata.h.new $(UPDATE_FILE) $(srcdir)/Python/executor_cases.c.h $(srcdir)/Python/executor_cases.c.h.new - $(UPDATE_FILE) $(srcdir)/Python/tier2_redundancy_eliminator_cases.c.h $(srcdir)/Python/tier2_redundancy_eliminator_cases.c.h.new + $(UPDATE_FILE) $(srcdir)/Python/optimizer_cases.c.h $(srcdir)/Python/optimizer_cases.c.h.new $(UPDATE_FILE) $(srcdir)/Lib/_opcode_metadata.py $(srcdir)/Lib/_opcode_metadata.py.new Python/compile.o: $(srcdir)/Include/internal/pycore_opcode_metadata.h @@ -1927,7 +1927,7 @@ Python/optimizer.o: \ Python/optimizer_analysis.o: \ $(srcdir)/Include/internal/pycore_opcode_metadata.h \ $(srcdir)/Include/internal/pycore_optimizer.h \ - $(srcdir)/Python/tier2_redundancy_eliminator_cases.c.h + $(srcdir)/Python/optimizer_cases.c.h Python/frozen.o: $(FROZEN_FILES_OUT) diff --git a/PCbuild/regen.targets b/PCbuild/regen.targets index 8f31803dbb752a6..f36387231a0d3a9 100644 --- a/PCbuild/regen.targets +++ b/PCbuild/regen.targets @@ -31,8 +31,8 @@ <!-- Taken from _Target._compute_digest in Tools\jit\_targets.py: --> <_JITSources Include="$(PySourcePath)Python\executor_cases.c.h;$(GeneratedPyConfigDir)pyconfig.h;$(PySourcePath)Tools\jit\**"/> <_JITOutputs Include="$(GeneratedPyConfigDir)jit_stencils.h"/> - <_CasesSources Include="$(PySourcePath)Python\bytecodes.c;$(PySourcePath)Python\tier2_redundancy_eliminator_bytecodes.c;"/> - <_CasesOutputs Include="$(PySourcePath)Python\generated_cases.c.h;$(PySourcePath)Include\opcode_ids.h;$(PySourcePath)Include\internal\pycore_uop_ids.h;$(PySourcePath)Python\opcode_targets.h;$(PySourcePath)Include\internal\pycore_opcode_metadata.h;$(PySourcePath)Include\internal\pycore_uop_metadata.h;$(PySourcePath)Python\tier2_redundancy_eliminator_cases.c.h;$(PySourcePath)Lib\_opcode_metadata.py"/> + <_CasesSources Include="$(PySourcePath)Python\bytecodes.c;$(PySourcePath)Python\optimizer_bytecodes.c;"/> + <_CasesOutputs Include="$(PySourcePath)Python\generated_cases.c.h;$(PySourcePath)Include\opcode_ids.h;$(PySourcePath)Include\internal\pycore_uop_ids.h;$(PySourcePath)Python\opcode_targets.h;$(PySourcePath)Include\internal\pycore_opcode_metadata.h;$(PySourcePath)Include\internal\pycore_uop_metadata.h;$(PySourcePath)Python\optimizer_cases.c.h;$(PySourcePath)Lib\_opcode_metadata.py"/> </ItemGroup> <Target Name="_TouchRegenSources" Condition="$(ForceRegen) == 'true'"> @@ -98,7 +98,7 @@ WorkingDirectory="$(PySourcePath)" /> <Exec Command="$(PythonForBuild) $(PySourcePath)Tools\cases_generator\tier2_generator.py $(PySourcePath)Python\bytecodes.c" WorkingDirectory="$(PySourcePath)" /> - <Exec Command="$(PythonForBuild) $(PySourcePath)Tools\cases_generator\tier2_abstract_generator.py $(PySourcePath)Python\tier2_redundancy_eliminator_bytecodes.c $(PySourcePath)Python\bytecodes.c" + <Exec Command="$(PythonForBuild) $(PySourcePath)Tools\cases_generator\optimizer_generator.py $(PySourcePath)Python\optimizer_bytecodes.c $(PySourcePath)Python\bytecodes.c" WorkingDirectory="$(PySourcePath)" /> <Exec Command="$(PythonForBuild) $(PySourcePath)Tools\cases_generator\opcode_metadata_generator.py $(PySourcePath)Python\bytecodes.c" WorkingDirectory="$(PySourcePath)" /> diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 47bfc8cf1061d92..1a9e82903ffbc00 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -1,5 +1,5 @@ /* - * This file contains the support code for CPython's uops redundancy eliminator. + * This file contains the support code for CPython's uops optimizer. * It also performs some simple optimizations. * It performs a traditional data-flow analysis[1] over the trace of uops. * Using the information gained, it chooses to emit, or skip certain instructions @@ -606,7 +606,7 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, /* 1 for success, 0 for not ready, cannot error at the moment. */ static int -uop_redundancy_eliminator( +optimize_uops( PyCodeObject *co, _PyUOpInstruction *trace, int trace_len, @@ -638,7 +638,7 @@ uop_redundancy_eliminator( _PyUOpName(opcode), oparg); switch (opcode) { -#include "tier2_redundancy_eliminator_cases.c.h" +#include "optimizer_cases.c.h" default: DPRINTF(1, "Unknown opcode in abstract interpreter\n"); @@ -812,7 +812,7 @@ _Py_uop_analyze_and_optimize( char *uop_optimize = Py_GETENV("PYTHONUOPSOPTIMIZE"); if (uop_optimize != NULL && *uop_optimize > '0') { - err = uop_redundancy_eliminator( + err = optimize_uops( (PyCodeObject *)frame->f_executable, buffer, buffer_size, curr_stacklen, dependencies); } diff --git a/Python/tier2_redundancy_eliminator_bytecodes.c b/Python/optimizer_bytecodes.c similarity index 100% rename from Python/tier2_redundancy_eliminator_bytecodes.c rename to Python/optimizer_bytecodes.c diff --git a/Python/tier2_redundancy_eliminator_cases.c.h b/Python/optimizer_cases.c.h similarity index 99% rename from Python/tier2_redundancy_eliminator_cases.c.h rename to Python/optimizer_cases.c.h index ca341e4dde5d93b..e8146dccbdfcd18 100644 --- a/Python/tier2_redundancy_eliminator_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -1,6 +1,6 @@ -// This file is generated by Tools/cases_generator/tier2_abstract_generator.py +// This file is generated by Tools/cases_generator/optimizer_generator.py // from: -// Python/tier2_redundancy_eliminator_bytecodes.c +// Python/optimizer_bytecodes.c // Do not edit! case _NOP: { diff --git a/Tools/c-analyzer/cpython/_parser.py b/Tools/c-analyzer/cpython/_parser.py index 8482bf849aaacd3..12010f0e9c05491 100644 --- a/Tools/c-analyzer/cpython/_parser.py +++ b/Tools/c-analyzer/cpython/_parser.py @@ -83,11 +83,11 @@ def clean_lines(text): Python/frozen_modules/*.h Python/generated_cases.c.h Python/executor_cases.c.h -Python/tier2_redundancy_eliminator_cases.c.h +Python/optimizer_cases.c.h # not actually source Python/bytecodes.c -Python/tier2_redundancy_eliminator_bytecodes.c +Python/optimizer_bytecodes.c # mimalloc Objects/mimalloc/*.c diff --git a/Tools/cases_generator/README.md b/Tools/cases_generator/README.md index d35a868b42ea9ec..fb512c4646b851a 100644 --- a/Tools/cases_generator/README.md +++ b/Tools/cases_generator/README.md @@ -13,9 +13,9 @@ What's currently here: - `parser.py` helper for interactions with `parsing.py` - `tierN_generator.py`: a couple of driver scripts to read `Python/bytecodes.c` and write `Python/generated_cases.c.h` (and several other files) -- `tier2_abstract_generator.py`: reads `Python/bytecodes.c` and - `Python/tier2_redundancy_eliminator_bytecodes.c` and writes - `Python/tier2_redundancy_eliminator_cases.c.h` +- `optimizer_generator.py`: reads `Python/bytecodes.c` and + `Python/optimizer_bytecodes.c` and writes + `Python/optimizer_cases.c.h` - `stack.py`: code to handle generalized stack effects - `cwriter.py`: code which understands tokens and how to format C code; main class: `CWriter` diff --git a/Tools/cases_generator/tier2_abstract_generator.py b/Tools/cases_generator/optimizer_generator.py similarity index 94% rename from Tools/cases_generator/tier2_abstract_generator.py rename to Tools/cases_generator/optimizer_generator.py index 58c3110dc3facbf..aa3f4ecb11d58ba 100644 --- a/Tools/cases_generator/tier2_abstract_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -1,6 +1,6 @@ -"""Generate the cases for the tier 2 redundancy eliminator/abstract interpreter. -Reads the instruction definitions from bytecodes.c. and tier2_redundancy_eliminator.bytecodes.c -Writes the cases to tier2_redundancy_eliminator_cases.c.h, which is #included in Python/optimizer_analysis.c. +"""Generate the cases for the tier 2 optimizer. +Reads the instruction definitions from bytecodes.c and optimizer_bytecodes.c +Writes the cases to optimizer_cases.c.h, which is #included in Python/optimizer_analysis.c. """ import argparse @@ -30,8 +30,8 @@ from lexer import Token from stack import StackOffset, Stack, SizeMismatch, UNUSED -DEFAULT_OUTPUT = ROOT / "Python/tier2_redundancy_eliminator_cases.c.h" -DEFAULT_ABSTRACT_INPUT = ROOT / "Python/tier2_redundancy_eliminator_bytecodes.c" +DEFAULT_OUTPUT = ROOT / "Python/optimizer_cases.c.h" +DEFAULT_ABSTRACT_INPUT = ROOT / "Python/optimizer_bytecodes.c" def validate_uop(override: Uop, uop: Uop) -> None: From 96c10c648565c7406d5606099dbbb937310c26dc Mon Sep 17 00:00:00 2001 From: Yuriy Chernyshov <thegeorg@yandex-team.com> Date: Mon, 26 Feb 2024 18:21:55 +0100 Subject: [PATCH 466/507] gh-115882: Reference Unknwn.h for ctypes on Windows (GH-115350) This allows the module to be compiled with WIN32_LEAN_AND_MEAN enabled --- .../next/Build/2024-02-24-12-50-43.gh-issue-115350.naQA6y.rst | 1 + Modules/_ctypes/ctypes.h | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 Misc/NEWS.d/next/Build/2024-02-24-12-50-43.gh-issue-115350.naQA6y.rst diff --git a/Misc/NEWS.d/next/Build/2024-02-24-12-50-43.gh-issue-115350.naQA6y.rst b/Misc/NEWS.d/next/Build/2024-02-24-12-50-43.gh-issue-115350.naQA6y.rst new file mode 100644 index 000000000000000..5492a804255838c --- /dev/null +++ b/Misc/NEWS.d/next/Build/2024-02-24-12-50-43.gh-issue-115350.naQA6y.rst @@ -0,0 +1 @@ +Fix building ctypes module with -DWIN32_LEAN_AND_MEAN defined diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h index 1989723f6f3dbb1..02f48a9ed55843c 100644 --- a/Modules/_ctypes/ctypes.h +++ b/Modules/_ctypes/ctypes.h @@ -32,6 +32,10 @@ #endif #endif +#ifdef MS_WIN32 +#include <Unknwn.h> // for IUnknown interface +#endif + typedef struct { PyTypeObject *DictRemover_Type; PyTypeObject *PyCArg_Type; From b05afdd5ec325bdb4cc89bb3be177ed577bea41f Mon Sep 17 00:00:00 2001 From: Michael Droettboom <mdboom@gmail.com> Date: Mon, 26 Feb 2024 12:51:47 -0500 Subject: [PATCH 467/507] gh-115168: Add pystats counter for invalidated executors (GH-115169) --- Include/cpython/optimizer.h | 4 ++-- Include/cpython/pystats.h | 1 + Modules/_testinternalcapi.c | 2 +- Python/instrumentation.c | 6 +++--- Python/optimizer.c | 10 ++++++++-- Python/optimizer_analysis.c | 2 +- Python/pylifecycle.c | 4 ++-- Python/pystate.c | 2 +- Python/specialize.c | 1 + Python/sysmodule.c | 2 +- Tools/scripts/summarize_stats.py | 11 ++++++++++- 11 files changed, 31 insertions(+), 14 deletions(-) diff --git a/Include/cpython/optimizer.h b/Include/cpython/optimizer.h index fe54d1ddfe61293..8fc9fb62aebdb41 100644 --- a/Include/cpython/optimizer.h +++ b/Include/cpython/optimizer.h @@ -100,8 +100,8 @@ void _Py_ExecutorClear(_PyExecutorObject *); void _Py_BloomFilter_Init(_PyBloomFilter *); void _Py_BloomFilter_Add(_PyBloomFilter *bloom, void *obj); PyAPI_FUNC(void) _Py_Executor_DependsOn(_PyExecutorObject *executor, void *obj); -PyAPI_FUNC(void) _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj); -extern void _Py_Executors_InvalidateAll(PyInterpreterState *interp); +PyAPI_FUNC(void) _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is_invalidation); +extern void _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation); /* For testing */ PyAPI_FUNC(PyObject *)PyUnstable_Optimizer_NewCounter(void); diff --git a/Include/cpython/pystats.h b/Include/cpython/pystats.h index db9aaedec950e45..887fbbedf88502b 100644 --- a/Include/cpython/pystats.h +++ b/Include/cpython/pystats.h @@ -115,6 +115,7 @@ typedef struct _optimization_stats { uint64_t inner_loop; uint64_t recursive_call; uint64_t low_confidence; + uint64_t executors_invalidated; UOpStats opcode[512]; uint64_t unsupported_opcode[256]; uint64_t trace_length_hist[_Py_UOP_HIST_SIZE]; diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 0d23b1899f22e44..5b714ca3f3dc33e 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -1035,7 +1035,7 @@ static PyObject * invalidate_executors(PyObject *self, PyObject *obj) { PyInterpreterState *interp = PyInterpreterState_Get(); - _Py_Executors_InvalidateDependency(interp, obj); + _Py_Executors_InvalidateDependency(interp, obj, 1); Py_RETURN_NONE; } diff --git a/Python/instrumentation.c b/Python/instrumentation.c index 878d19f0552bf59..6f1bc2e0a107df5 100644 --- a/Python/instrumentation.c +++ b/Python/instrumentation.c @@ -1599,7 +1599,7 @@ _Py_Instrument(PyCodeObject *code, PyInterpreterState *interp) if (code->co_executors != NULL) { _PyCode_Clear_Executors(code); } - _Py_Executors_InvalidateDependency(interp, code); + _Py_Executors_InvalidateDependency(interp, code, 1); int code_len = (int)Py_SIZE(code); /* Exit early to avoid creating instrumentation * data for potential statically allocated code @@ -1820,7 +1820,7 @@ _PyMonitoring_SetEvents(int tool_id, _PyMonitoringEventSet events) return -1; } set_global_version(tstate, new_version); - _Py_Executors_InvalidateAll(interp); + _Py_Executors_InvalidateAll(interp, 1); return instrument_all_executing_code_objects(interp); } @@ -1850,7 +1850,7 @@ _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEvent /* Force instrumentation update */ code->_co_instrumentation_version -= MONITORING_VERSION_INCREMENT; } - _Py_Executors_InvalidateDependency(interp, code); + _Py_Executors_InvalidateDependency(interp, code, 1); if (_Py_Instrument(code, interp)) { return -1; } diff --git a/Python/optimizer.c b/Python/optimizer.c index 6b2ba3addefc95d..c04ee17ee2171d6 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1348,7 +1348,7 @@ _Py_Executor_DependsOn(_PyExecutorObject *executor, void *obj) * May cause other executors to be invalidated as well */ void -_Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj) +_Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is_invalidation) { _PyBloomFilter obj_filter; _Py_BloomFilter_Init(&obj_filter); @@ -1360,6 +1360,9 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj) _PyExecutorObject *next = exec->vm_data.links.next; if (bloom_filter_may_contain(&exec->vm_data.bloom, &obj_filter)) { _Py_ExecutorClear(exec); + if (is_invalidation) { + OPT_STAT_INC(executors_invalidated); + } } exec = next; } @@ -1367,7 +1370,7 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj) /* Invalidate all executors */ void -_Py_Executors_InvalidateAll(PyInterpreterState *interp) +_Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation) { while (interp->executor_list_head) { _PyExecutorObject *executor = interp->executor_list_head; @@ -1378,5 +1381,8 @@ _Py_Executors_InvalidateAll(PyInterpreterState *interp) else { _Py_ExecutorClear(executor); } + if (is_invalidation) { + OPT_STAT_INC(executors_invalidated); + } } } diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 1a9e82903ffbc00..e9751291e2fd5ae 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -405,7 +405,7 @@ globals_watcher_callback(PyDict_WatchEvent event, PyObject* dict, { RARE_EVENT_STAT_INC(watched_globals_modification); assert(get_mutations(dict) < _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS); - _Py_Executors_InvalidateDependency(_PyInterpreterState_GET(), dict); + _Py_Executors_InvalidateDependency(_PyInterpreterState_GET(), dict, 1); increment_mutations(dict); PyDict_Unwatch(GLOBALS_WATCHER_ID, dict); return 0; diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 04487345f7ec053..3a2c0a450ac9d97 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -612,7 +612,7 @@ builtins_dict_watcher(PyDict_WatchEvent event, PyObject *dict, PyObject *key, Py { PyInterpreterState *interp = _PyInterpreterState_GET(); if (interp->rare_events.builtin_dict < _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS) { - _Py_Executors_InvalidateAll(interp); + _Py_Executors_InvalidateAll(interp, 1); } RARE_EVENT_INTERP_INC(interp, builtin_dict); return 0; @@ -1628,7 +1628,7 @@ finalize_modules(PyThreadState *tstate) PyInterpreterState *interp = tstate->interp; // Invalidate all executors and turn off tier 2 optimizer - _Py_Executors_InvalidateAll(interp); + _Py_Executors_InvalidateAll(interp, 0); _PyOptimizerObject *old = _Py_SetOptimizer(interp, NULL); Py_XDECREF(old); diff --git a/Python/pystate.c b/Python/pystate.c index bb8e24c1dbe12fe..a80c1b7fb9c8665 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2666,7 +2666,7 @@ _PyInterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, return; } if (eval_frame != NULL) { - _Py_Executors_InvalidateAll(interp); + _Py_Executors_InvalidateAll(interp, 1); } RARE_EVENT_INC(set_eval_frame_func); interp->eval_frame = eval_frame; diff --git a/Python/specialize.c b/Python/specialize.c index 871979d92298b6e..f83d8a9ceb0282d 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -236,6 +236,7 @@ print_optimization_stats(FILE *out, OptimizationStats *stats) fprintf(out, "Optimization inner loop: %" PRIu64 "\n", stats->inner_loop); fprintf(out, "Optimization recursive call: %" PRIu64 "\n", stats->recursive_call); fprintf(out, "Optimization low confidence: %" PRIu64 "\n", stats->low_confidence); + fprintf(out, "Executors invalidated: %" PRIu64 "\n", stats->executors_invalidated); print_histogram(out, "Trace length", stats->trace_length_hist); print_histogram(out, "Trace run length", stats->trace_run_length_hist); diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 69b6d886ccc3e90..1bfd031fdd26b21 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2138,7 +2138,7 @@ sys__clear_internal_caches_impl(PyObject *module) /*[clinic end generated code: output=0ee128670a4966d6 input=253e741ca744f6e8]*/ { PyInterpreterState *interp = _PyInterpreterState_GET(); - _Py_Executors_InvalidateAll(interp); + _Py_Executors_InvalidateAll(interp, 0); PyType_ClearCache(); Py_RETURN_NONE; } diff --git a/Tools/scripts/summarize_stats.py b/Tools/scripts/summarize_stats.py index 6b60b59b3b0e797..2925e096f4d95ef 100644 --- a/Tools/scripts/summarize_stats.py +++ b/Tools/scripts/summarize_stats.py @@ -451,6 +451,7 @@ def get_optimization_stats(self) -> dict[str, tuple[int, int | None]]: inner_loop = self._data["Optimization inner loop"] recursive_call = self._data["Optimization recursive call"] low_confidence = self._data["Optimization low confidence"] + executors_invalidated = self._data["Executors invalidated"] return { Doc( @@ -493,11 +494,19 @@ def get_optimization_stats(self) -> dict[str, tuple[int, int | None]]: "A trace is abandoned because the likelihood of the jump to top being taken " "is too low.", ): (low_confidence, attempts), + Doc( + "Executors invalidated", + "The number of executors that were invalidated due to watched " + "dictionary changes.", + ): (executors_invalidated, created), Doc("Traces executed", "The number of traces that were executed"): ( executed, None, ), - Doc("Uops executed", "The total number of uops (micro-operations) that were executed"): ( + Doc( + "Uops executed", + "The total number of uops (micro-operations) that were executed", + ): ( uops, executed, ), From 68c79d21fa791d7418a858b7aa4604880e988a02 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Mon, 26 Feb 2024 20:07:41 +0200 Subject: [PATCH 468/507] gh-112006: Fix inspect.unwrap() for types where __wrapped__ is a data descriptor (GH-115540) This also fixes inspect.Signature.from_callable() for builtins classmethod() and staticmethod(). --- Lib/inspect.py | 10 ++---- Lib/test/test_inspect/test_inspect.py | 32 +++++++++++++++---- ...-02-15-23-42-54.gh-issue-112006.4wxcK-.rst | 3 ++ 3 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-15-23-42-54.gh-issue-112006.4wxcK-.rst diff --git a/Lib/inspect.py b/Lib/inspect.py index da504037ac282c9..9191d4761b2c768 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -762,18 +762,14 @@ def unwrap(func, *, stop=None): :exc:`ValueError` is raised if a cycle is encountered. """ - if stop is None: - def _is_wrapper(f): - return hasattr(f, '__wrapped__') - else: - def _is_wrapper(f): - return hasattr(f, '__wrapped__') and not stop(f) f = func # remember the original func for error reporting # Memoise by id to tolerate non-hashable objects, but store objects to # ensure they aren't destroyed, which would allow their IDs to be reused. memo = {id(f): f} recursion_limit = sys.getrecursionlimit() - while _is_wrapper(func): + while not isinstance(func, type) and hasattr(func, '__wrapped__'): + if stop is not None and stop(func): + break func = func.__wrapped__ id_func = id(func) if (id_func in memo) or (len(memo) >= recursion_limit): diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index c5a6de5993fad49..9dc37852ec205a6 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -3137,6 +3137,10 @@ def m1d(*args, **kwargs): int)) def test_signature_on_classmethod(self): + self.assertEqual(self.signature(classmethod), + ((('function', ..., ..., "positional_only"),), + ...)) + class Test: @classmethod def foo(cls, arg1, *, arg2=1): @@ -3155,6 +3159,10 @@ def foo(cls, arg1, *, arg2=1): ...)) def test_signature_on_staticmethod(self): + self.assertEqual(self.signature(staticmethod), + ((('function', ..., ..., "positional_only"),), + ...)) + class Test: @staticmethod def foo(cls, *, arg): @@ -3678,16 +3686,20 @@ class Bar(Spam, Foo): ((('a', ..., ..., "positional_or_keyword"),), ...)) - class Wrapped: - pass - Wrapped.__wrapped__ = lambda a: None - self.assertEqual(self.signature(Wrapped), + def test_signature_on_wrapper(self): + class Wrapper: + def __call__(self, b): + pass + wrapper = Wrapper() + wrapper.__wrapped__ = lambda a: None + self.assertEqual(self.signature(wrapper), ((('a', ..., ..., "positional_or_keyword"),), ...)) # wrapper loop: - Wrapped.__wrapped__ = Wrapped + wrapper = Wrapper() + wrapper.__wrapped__ = wrapper with self.assertRaisesRegex(ValueError, 'wrapper loop'): - self.signature(Wrapped) + self.signature(wrapper) def test_signature_on_lambdas(self): self.assertEqual(self.signature((lambda a=10: a)), @@ -4999,6 +5011,14 @@ def test_recursion_limit(self): with self.assertRaisesRegex(ValueError, 'wrapper loop'): inspect.unwrap(obj) + def test_wrapped_descriptor(self): + self.assertIs(inspect.unwrap(NTimesUnwrappable), NTimesUnwrappable) + self.assertIs(inspect.unwrap(staticmethod), staticmethod) + self.assertIs(inspect.unwrap(classmethod), classmethod) + self.assertIs(inspect.unwrap(staticmethod(classmethod)), classmethod) + self.assertIs(inspect.unwrap(classmethod(staticmethod)), staticmethod) + + class TestMain(unittest.TestCase): def test_only_source(self): module = importlib.import_module('unittest') diff --git a/Misc/NEWS.d/next/Library/2024-02-15-23-42-54.gh-issue-112006.4wxcK-.rst b/Misc/NEWS.d/next/Library/2024-02-15-23-42-54.gh-issue-112006.4wxcK-.rst new file mode 100644 index 000000000000000..32af2bd24e54f2c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-15-23-42-54.gh-issue-112006.4wxcK-.rst @@ -0,0 +1,3 @@ +Fix :func:`inspect.unwrap` for types with the ``__wrapper__`` data +descriptor. Fix :meth:`inspect.Signature.from_callable` for builtins +:func:`classmethod` and :func:`staticmethod`. From 72cff8d8e5a476d3406efb0491452bf9d6b02feb Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Mon, 26 Feb 2024 20:29:49 +0200 Subject: [PATCH 469/507] gh-113942: Show functions implemented as builtin methods (GH-115306) Pydoc no longer skips global functions implemented as builtin methods, such as MethodDescriptorType and WrapperDescriptorType. --- Lib/pydoc.py | 12 ++++++------ Lib/test/test_pydoc/pydocfodder.py | 4 ++++ Lib/test/test_pydoc/test_pydoc.py | 12 ++++++++++++ .../2024-02-11-20-12-39.gh-issue-113942.i72sMJ.rst | 2 ++ 4 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-11-20-12-39.gh-issue-113942.i72sMJ.rst diff --git a/Lib/pydoc.py b/Lib/pydoc.py index d32fa8d05044177..b0193b4a85164a7 100755 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -855,9 +855,9 @@ def docmodule(self, object, name=None, mod=None, *ignored): cdict[key] = cdict[base] = modname + '.html#' + key funcs, fdict = [], {} for key, value in inspect.getmembers(object, inspect.isroutine): - # if __all__ exists, believe it. Otherwise use old heuristic. - if (all is not None or - inspect.isbuiltin(value) or inspect.getmodule(value) is object): + # if __all__ exists, believe it. Otherwise use a heuristic. + if (all is not None + or (inspect.getmodule(value) or object) is object): if visiblename(key, all, object): funcs.append((key, value)) fdict[key] = '#-' + key @@ -1299,9 +1299,9 @@ def docmodule(self, object, name=None, mod=None, *ignored): classes.append((key, value)) funcs = [] for key, value in inspect.getmembers(object, inspect.isroutine): - # if __all__ exists, believe it. Otherwise use old heuristic. - if (all is not None or - inspect.isbuiltin(value) or inspect.getmodule(value) is object): + # if __all__ exists, believe it. Otherwise use a heuristic. + if (all is not None + or (inspect.getmodule(value) or object) is object): if visiblename(key, all, object): funcs.append((key, value)) data = [] diff --git a/Lib/test/test_pydoc/pydocfodder.py b/Lib/test/test_pydoc/pydocfodder.py index 27037e048db8198..3cc2d5bd57fe5b3 100644 --- a/Lib/test/test_pydoc/pydocfodder.py +++ b/Lib/test/test_pydoc/pydocfodder.py @@ -81,6 +81,8 @@ def B_classmethod(cls, x): A_method_ref = A().A_method A_method_alias = A.A_method B_method_alias = B_method + count = list.count # same name + list_count = list.count __repr__ = object.__repr__ # same name object_repr = object.__repr__ get = {}.get # same name @@ -180,5 +182,7 @@ def __call__(self, inst): B_method2 = B.B_method count = list.count # same name list_count = list.count +__repr__ = object.__repr__ # same name +object_repr = object.__repr__ get = {}.get # same name dict_get = {}.get diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index b07d9119e494017..9d40234ed01697f 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -1686,6 +1686,8 @@ def test_text_doc_routines_in_class(self, cls=pydocfodder.B): self.assertIn(' | global_func(x, y) from test.test_pydoc.pydocfodder', lines) self.assertIn(' | global_func_alias = global_func(x, y)', lines) self.assertIn(' | global_func2_alias = global_func2(x, y) from test.test_pydoc.pydocfodder', lines) + self.assertIn(' | count(self, value, /) from builtins.list', lines) + self.assertIn(' | list_count = count(self, value, /)', lines) self.assertIn(' | __repr__(self, /) from builtins.object', lines) self.assertIn(' | object_repr = __repr__(self, /)', lines) @@ -1714,6 +1716,8 @@ def test_html_doc_routines_in_class(self, cls=pydocfodder.B): self.assertIn('global_func(x, y) from test.test_pydoc.pydocfodder', lines) self.assertIn('global_func_alias = global_func(x, y)', lines) self.assertIn('global_func2_alias = global_func2(x, y) from test.test_pydoc.pydocfodder', lines) + self.assertIn('count(self, value, /) from builtins.list', lines) + self.assertIn('list_count = count(self, value, /)', lines) self.assertIn('__repr__(self, /) from builtins.object', lines) self.assertIn('object_repr = __repr__(self, /)', lines) @@ -1757,6 +1761,10 @@ def test_text_doc_routines_in_module(self): # unbound methods self.assertIn(' B_method(self)', lines) self.assertIn(' B_method2 = B_method(self)', lines) + self.assertIn(' count(self, value, /) unbound builtins.list method', lines) + self.assertIn(' list_count = count(self, value, /) unbound builtins.list method', lines) + self.assertIn(' __repr__(self, /) unbound builtins.object method', lines) + self.assertIn(' object_repr = __repr__(self, /) unbound builtins.object method', lines) def test_html_doc_routines_in_module(self): doc = pydoc.HTMLDoc() @@ -1782,6 +1790,10 @@ def test_html_doc_routines_in_module(self): # unbound methods self.assertIn(' B_method(self)', lines) self.assertIn(' B_method2 = B_method(self)', lines) + self.assertIn(' count(self, value, /) unbound builtins.list method', lines) + self.assertIn(' list_count = count(self, value, /) unbound builtins.list method', lines) + self.assertIn(' __repr__(self, /) unbound builtins.object method', lines) + self.assertIn(' object_repr = __repr__(self, /) unbound builtins.object method', lines) @unittest.skipIf( diff --git a/Misc/NEWS.d/next/Library/2024-02-11-20-12-39.gh-issue-113942.i72sMJ.rst b/Misc/NEWS.d/next/Library/2024-02-11-20-12-39.gh-issue-113942.i72sMJ.rst new file mode 100644 index 000000000000000..2da43a43b787982 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-11-20-12-39.gh-issue-113942.i72sMJ.rst @@ -0,0 +1,2 @@ +:mod:`pydoc` no longer skips global functions implemented as builtin methods, +such as :class:`~type.MethodDescriptorType` and :class:`~type.WrapperDescriptorType`. From 37a13b941394a1337576c67bff35d4a44a1157e2 Mon Sep 17 00:00:00 2001 From: Steve Dower <steve.dower@python.org> Date: Mon, 26 Feb 2024 19:14:14 +0000 Subject: [PATCH 470/507] gh-115582: Make default PC/pyconfig.h work for free-threaded builds with manual /DPy_GIL_DISABLED (GH-115850) --- .../Windows/2024-02-23-11-43-43.gh-issue-115582.sk1XPi.rst | 3 +++ PC/pyconfig.h.in | 7 ++++++- PCbuild/pythoncore.vcxproj | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2024-02-23-11-43-43.gh-issue-115582.sk1XPi.rst diff --git a/Misc/NEWS.d/next/Windows/2024-02-23-11-43-43.gh-issue-115582.sk1XPi.rst b/Misc/NEWS.d/next/Windows/2024-02-23-11-43-43.gh-issue-115582.sk1XPi.rst new file mode 100644 index 000000000000000..f2e82bf6a3e0285 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-02-23-11-43-43.gh-issue-115582.sk1XPi.rst @@ -0,0 +1,3 @@ +Building extensions intended for free-threaded builds of CPython now require +compiling with ``/DPy_GIL_DISABLED`` manually when using a regular install. This +is expected to change in future releases. diff --git a/PC/pyconfig.h.in b/PC/pyconfig.h.in index 8bbf877a5bb5edc..d72d6282c2806f1 100644 --- a/PC/pyconfig.h.in +++ b/PC/pyconfig.h.in @@ -95,7 +95,12 @@ WIN32 is still required for the locale module. #endif /* Py_BUILD_CORE || Py_BUILD_CORE_BUILTIN || Py_BUILD_CORE_MODULE */ /* Define to 1 if you want to disable the GIL */ -#undef Py_GIL_DISABLED +/* Uncomment the definition for free-threaded builds, or define it manually + * when compiling extension modules. Note that we test with #ifdef, so + * defining as 0 will still disable the GIL. */ +#ifndef Py_GIL_DISABLED +/* #define Py_GIL_DISABLED 1 */ +#endif /* Compiler specific defines */ diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index c7b698f0e17a396..ef21f85107bc32d 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -677,7 +677,7 @@ <OldPyConfigH Condition="Exists('$(IntDir)pyconfig.h')">$([System.IO.File]::ReadAllText('$(IntDir)pyconfig.h'))</OldPyConfigH> </PropertyGroup> <PropertyGroup Condition="$(DisableGil) == 'true'"> - <PyConfigHText>$(PyConfigHText.Replace('#undef Py_GIL_DISABLED', '#define Py_GIL_DISABLED 1'))</PyConfigHText> + <PyConfigHText>$(PyConfigHText.Replace('/* #define Py_GIL_DISABLED 1 */', '#define Py_GIL_DISABLED 1'))</PyConfigHText> </PropertyGroup> <Message Text="Updating pyconfig.h" Condition="$(PyConfigHText.TrimEnd()) != $(OldPyConfigH.TrimEnd())" /> <WriteLinesToFile File="$(IntDir)pyconfig.h" From da382aaf52d761a037874bee27bb5db69906df9e Mon Sep 17 00:00:00 2001 From: Emmanuel Arias <eamanu@yaerobi.com> Date: Mon, 26 Feb 2024 16:20:39 -0300 Subject: [PATCH 471/507] gh-77956: Add the words 'default' and 'version' help text localizable (GH-12711) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: paul.j3 Co-authored-by: Jérémie Detrey <jdetrey@users.noreply.github.com> --- Lib/argparse.py | 6 ++++-- .../next/Library/2019-04-06-23-50-59.bpo-33775.0yhMDc.rst | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-04-06-23-50-59.bpo-33775.0yhMDc.rst diff --git a/Lib/argparse.py b/Lib/argparse.py index f86658baf7f2bac..c16f538ae1535a1 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -708,7 +708,7 @@ def _get_help_string(self, action): if action.default is not SUPPRESS: defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] if action.option_strings or action.nargs in defaulting_nargs: - help += ' (default: %(default)s)' + help += _(' (default: %(default)s)') return help @@ -1159,8 +1159,10 @@ def __init__(self, version=None, dest=SUPPRESS, default=SUPPRESS, - help="show program's version number and exit", + help=None, deprecated=False): + if help is None: + help = _("show program's version number and exit") super(_VersionAction, self).__init__( option_strings=option_strings, dest=dest, diff --git a/Misc/NEWS.d/next/Library/2019-04-06-23-50-59.bpo-33775.0yhMDc.rst b/Misc/NEWS.d/next/Library/2019-04-06-23-50-59.bpo-33775.0yhMDc.rst new file mode 100644 index 000000000000000..2a663ac7940dcb2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-04-06-23-50-59.bpo-33775.0yhMDc.rst @@ -0,0 +1 @@ +Add 'default' and 'version' help text for localization in argparse. From de2a73dc4649b110351fce789de0abb14c460b97 Mon Sep 17 00:00:00 2001 From: Kien Dang <kiend@pm.me> Date: Tue, 27 Feb 2024 03:04:44 +0700 Subject: [PATCH 472/507] bpo-45101: Add consistency in usage message IO between 2 versions of python-config (GH-28162) On --help output to stdout. On error output to stderr. --- .../Tools-Demos/2021-09-05-02-47-48.bpo-45101.60Zqmt.rst | 1 + Misc/python-config.in | 3 ++- Misc/python-config.sh.in | 7 ++++++- 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Tools-Demos/2021-09-05-02-47-48.bpo-45101.60Zqmt.rst diff --git a/Misc/NEWS.d/next/Tools-Demos/2021-09-05-02-47-48.bpo-45101.60Zqmt.rst b/Misc/NEWS.d/next/Tools-Demos/2021-09-05-02-47-48.bpo-45101.60Zqmt.rst new file mode 100644 index 000000000000000..48a09da78229156 --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2021-09-05-02-47-48.bpo-45101.60Zqmt.rst @@ -0,0 +1 @@ +Add consistency in usage message IO between 2 versions of python-config. diff --git a/Misc/python-config.in b/Misc/python-config.in index 81c3316e334a485..dd5d161ab2286f1 100644 --- a/Misc/python-config.in +++ b/Misc/python-config.in @@ -13,7 +13,8 @@ valid_opts = ['prefix', 'exec-prefix', 'includes', 'libs', 'cflags', def exit_with_usage(code=1): print("Usage: {0} [{1}]".format( - sys.argv[0], '|'.join('--'+opt for opt in valid_opts)), file=sys.stderr) + sys.argv[0], '|'.join('--'+opt for opt in valid_opts)), + file=sys.stdout if code == 0 else sys.stderr) sys.exit(code) try: diff --git a/Misc/python-config.sh.in b/Misc/python-config.sh.in index 2602fe24c0402e1..eb02223ddcd2c30 100644 --- a/Misc/python-config.sh.in +++ b/Misc/python-config.sh.in @@ -4,7 +4,12 @@ exit_with_usage () { - echo "Usage: $0 --prefix|--exec-prefix|--includes|--libs|--cflags|--ldflags|--extension-suffix|--help|--abiflags|--configdir|--embed" + local USAGE="Usage: $0 --prefix|--exec-prefix|--includes|--libs|--cflags|--ldflags|--extension-suffix|--help|--abiflags|--configdir|--embed" + if [[ "$1" -eq 0 ]]; then + echo "$USAGE" + else + echo "$USAGE" >&2 + fi exit $1 } From 6087315926fb185847a52559af063cc7d337d978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Detrey?= <jdetrey@users.noreply.github.com> Date: Mon, 26 Feb 2024 23:05:01 +0100 Subject: [PATCH 473/507] bpo-44865: Fix yet one missing translations in argparse (GH-27668) --- Lib/argparse.py | 3 ++- .../next/Library/2021-08-24-20-47-37.bpo-44865.c3BhZS.rst | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2021-08-24-20-47-37.bpo-44865.c3BhZS.rst diff --git a/Lib/argparse.py b/Lib/argparse.py index c16f538ae1535a1..4200dd5e334ad5c 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -223,7 +223,8 @@ def format_help(self): # add the heading if the section was non-empty if self.heading is not SUPPRESS and self.heading is not None: current_indent = self.formatter._current_indent - heading = '%*s%s:\n' % (current_indent, '', self.heading) + heading_text = _('%(heading)s:') % dict(heading=self.heading) + heading = '%*s%s\n' % (current_indent, '', heading_text) else: heading = '' diff --git a/Misc/NEWS.d/next/Library/2021-08-24-20-47-37.bpo-44865.c3BhZS.rst b/Misc/NEWS.d/next/Library/2021-08-24-20-47-37.bpo-44865.c3BhZS.rst new file mode 100644 index 000000000000000..ecdb26cdd6edd60 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-08-24-20-47-37.bpo-44865.c3BhZS.rst @@ -0,0 +1 @@ +Add missing call to localization function in :mod:`argparse`. From af5f9d682c20c951b90e3c020eeccac386c9bbb0 Mon Sep 17 00:00:00 2001 From: Petr Viktorin <encukou@gmail.com> Date: Tue, 27 Feb 2024 09:51:17 +0100 Subject: [PATCH 474/507] gh-115720: Show number of leaks in huntrleaks progress reports (GH-115726) Instead of showing a dot for each iteration, show: - '.' for zero (on negative) leaks - number of leaks for 1-9 - 'X' if there are more leaks This allows more rapid iteration: when bisecting, I don't need to wait for the final report to see if the test still leaks. Also, show the full result if there are any non-zero entries. This shows negative entries, for the unfortunate cases where a reference is created and cleaned up in different runs. Test *failure* is still determined by the existing heuristic. --- Lib/test/libregrtest/refleak.py | 47 ++++++++++++++----- Lib/test/test_regrtest.py | 4 +- ...-02-20-15-47-41.gh-issue-115720.w8i8UG.rst | 2 + 3 files changed, 39 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/Tests/2024-02-20-15-47-41.gh-issue-115720.w8i8UG.rst diff --git a/Lib/test/libregrtest/refleak.py b/Lib/test/libregrtest/refleak.py index 71a70af6882d16f..f582c0d3e7ff134 100644 --- a/Lib/test/libregrtest/refleak.py +++ b/Lib/test/libregrtest/refleak.py @@ -88,9 +88,12 @@ def get_pooled_int(value): rc_before = alloc_before = fd_before = interned_before = 0 if not quiet: - print("beginning", repcount, "repetitions", file=sys.stderr) - print(("1234567890"*(repcount//10 + 1))[:repcount], file=sys.stderr, - flush=True) + print("beginning", repcount, "repetitions. Showing number of leaks " + "(. for 0 or less, X for 10 or more)", + file=sys.stderr) + numbers = ("1234567890"*(repcount//10 + 1))[:repcount] + numbers = numbers[:warmups] + ':' + numbers[warmups:] + print(numbers, file=sys.stderr, flush=True) results = None dash_R_cleanup(fs, ps, pic, zdc, abcs) @@ -116,13 +119,27 @@ def get_pooled_int(value): rc_after = gettotalrefcount() - interned_after * 2 fd_after = fd_count() - if not quiet: - print('.', end='', file=sys.stderr, flush=True) - rc_deltas[i] = get_pooled_int(rc_after - rc_before) alloc_deltas[i] = get_pooled_int(alloc_after - alloc_before) fd_deltas[i] = get_pooled_int(fd_after - fd_before) + if not quiet: + # use max, not sum, so total_leaks is one of the pooled ints + total_leaks = max(rc_deltas[i], alloc_deltas[i], fd_deltas[i]) + if total_leaks <= 0: + symbol = '.' + elif total_leaks < 10: + symbol = ( + '.', '1', '2', '3', '4', '5', '6', '7', '8', '9', + )[total_leaks] + else: + symbol = 'X' + if i == warmups: + print(' ', end='', file=sys.stderr, flush=True) + print(symbol, end='', file=sys.stderr, flush=True) + del total_leaks + del symbol + alloc_before = alloc_after rc_before = rc_after fd_before = fd_after @@ -158,14 +175,20 @@ def check_fd_deltas(deltas): ]: # ignore warmup runs deltas = deltas[warmups:] - if checker(deltas): + failing = checker(deltas) + suspicious = any(deltas) + if failing or suspicious: msg = '%s leaked %s %s, sum=%s' % ( test_name, deltas, item_name, sum(deltas)) - print(msg, file=sys.stderr, flush=True) - with open(filename, "a", encoding="utf-8") as refrep: - print(msg, file=refrep) - refrep.flush() - failed = True + print(msg, end='', file=sys.stderr) + if failing: + print(file=sys.stderr, flush=True) + with open(filename, "a", encoding="utf-8") as refrep: + print(msg, file=refrep) + refrep.flush() + failed = True + else: + print(' (this is fine)', file=sys.stderr, flush=True) return (failed, results) diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py index b80e0524593fc7d..7e1eaa7d6a515ec 100644 --- a/Lib/test/test_regrtest.py +++ b/Lib/test/test_regrtest.py @@ -1171,8 +1171,8 @@ def check_leak(self, code, what, *, run_workers=False): stderr=subprocess.STDOUT) self.check_executed_tests(output, [test], failed=test, stats=1) - line = 'beginning 6 repetitions\n123456\n......\n' - self.check_line(output, re.escape(line)) + line = r'beginning 6 repetitions. .*\n123:456\n[.0-9X]{3} 111\n' + self.check_line(output, line) line2 = '%s leaked [1, 1, 1] %s, sum=3\n' % (test, what) self.assertIn(line2, output) diff --git a/Misc/NEWS.d/next/Tests/2024-02-20-15-47-41.gh-issue-115720.w8i8UG.rst b/Misc/NEWS.d/next/Tests/2024-02-20-15-47-41.gh-issue-115720.w8i8UG.rst new file mode 100644 index 000000000000000..a03ee11d974251c --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-02-20-15-47-41.gh-issue-115720.w8i8UG.rst @@ -0,0 +1,2 @@ +Leak tests (``-R``, ``--huntrleaks``) now show a summary of the number of +leaks found in each iteration. From 10fbcd6c5dc25bfe14e02fd93ef93498a393860c Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Tue, 27 Feb 2024 10:51:26 +0000 Subject: [PATCH 475/507] GH-115816: Make tier2 optimizer symbols testable, and add a few tests. (GH-115953) --- Include/internal/pycore_optimizer.h | 85 +++++ Lib/test/test_generated_cases.py | 4 +- Lib/test/test_optimizer.py | 7 + Makefile.pre.in | 1 + Modules/_testinternalcapi.c | 3 +- PCbuild/_freeze_module.vcxproj | 1 + PCbuild/_freeze_module.vcxproj.filters | 3 + PCbuild/pythoncore.vcxproj | 1 + PCbuild/pythoncore.vcxproj.filters | 3 + Python/optimizer_analysis.c | 343 +---------------- Python/optimizer_bytecodes.c | 170 ++++----- Python/optimizer_cases.c.h | 366 +++++++++---------- Python/optimizer_symbols.c | 332 +++++++++++++++++ Tools/c-analyzer/cpython/ignored.tsv | 2 - Tools/cases_generator/optimizer_generator.py | 6 +- 15 files changed, 720 insertions(+), 607 deletions(-) create mode 100644 Python/optimizer_symbols.c diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index eee71c700d4904a..267cbf1265c420a 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -9,6 +9,7 @@ extern "C" { #endif #include "pycore_uop_ids.h" +#include <stdbool.h> // This is the length of the trace we project initially. #define UOP_MAX_TRACE_LENGTH 512 @@ -25,6 +26,90 @@ extern PyTypeObject _PyDefaultOptimizer_Type; extern PyTypeObject _PyUOpExecutor_Type; extern PyTypeObject _PyUOpOptimizer_Type; +/* Symbols */ + +struct _Py_UOpsSymType { + int flags; + PyTypeObject *typ; + // constant propagated value (might be NULL) + PyObject *const_val; +}; + +// Holds locals, stack, locals, stack ... co_consts (in that order) +#define MAX_ABSTRACT_INTERP_SIZE 4096 + +#define OVERALLOCATE_FACTOR 5 + +#define TY_ARENA_SIZE (UOP_MAX_TRACE_LENGTH * OVERALLOCATE_FACTOR) + +// Need extras for root frame and for overflow frame (see TRACE_STACK_PUSH()) +#define MAX_ABSTRACT_FRAME_DEPTH (TRACE_STACK_SIZE + 2) + +typedef struct _Py_UOpsSymType _Py_UOpsSymType; + +struct _Py_UOpsAbstractFrame { + // Max stacklen + int stack_len; + int locals_len; + + _Py_UOpsSymType **stack_pointer; + _Py_UOpsSymType **stack; + _Py_UOpsSymType **locals; +}; + +typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame; + +typedef struct ty_arena { + int ty_curr_number; + int ty_max_number; + _Py_UOpsSymType arena[TY_ARENA_SIZE]; +} ty_arena; + +struct _Py_UOpsAbstractInterpContext { + PyObject_HEAD + // The current "executing" frame. + _Py_UOpsAbstractFrame *frame; + _Py_UOpsAbstractFrame frames[MAX_ABSTRACT_FRAME_DEPTH]; + int curr_frame_depth; + + // Arena for the symbolic types. + ty_arena t_arena; + + _Py_UOpsSymType **n_consumed; + _Py_UOpsSymType **limit; + _Py_UOpsSymType *locals_and_stack[MAX_ABSTRACT_INTERP_SIZE]; +}; + +typedef struct _Py_UOpsAbstractInterpContext _Py_UOpsAbstractInterpContext; + +extern bool _Py_uop_sym_is_null(_Py_UOpsSymType *sym); +extern bool _Py_uop_sym_is_not_null(_Py_UOpsSymType *sym); +extern bool _Py_uop_sym_is_const(_Py_UOpsSymType *sym); +extern PyObject *_Py_uop_sym_get_const(_Py_UOpsSymType *sym); +extern _Py_UOpsSymType *_Py_uop_sym_new_unknown(_Py_UOpsAbstractInterpContext *ctx); +extern _Py_UOpsSymType *_Py_uop_sym_new_not_null(_Py_UOpsAbstractInterpContext *ctx); +extern _Py_UOpsSymType *_Py_uop_sym_new_type( + _Py_UOpsAbstractInterpContext *ctx, PyTypeObject *typ); +extern _Py_UOpsSymType *_Py_uop_sym_new_const(_Py_UOpsAbstractInterpContext *ctx, PyObject *const_val); +extern _Py_UOpsSymType *_Py_uop_sym_new_null(_Py_UOpsAbstractInterpContext *ctx); +extern bool _Py_uop_sym_matches_type(_Py_UOpsSymType *sym, PyTypeObject *typ); +extern void _Py_uop_sym_set_null(_Py_UOpsSymType *sym); +extern void _Py_uop_sym_set_type(_Py_UOpsSymType *sym, PyTypeObject *tp); + +extern int _Py_uop_abstractcontext_init(_Py_UOpsAbstractInterpContext *ctx); +extern void _Py_uop_abstractcontext_fini(_Py_UOpsAbstractInterpContext *ctx); + +extern _Py_UOpsAbstractFrame *_Py_uop_ctx_frame_new( + _Py_UOpsAbstractInterpContext *ctx, + PyCodeObject *co, + _Py_UOpsSymType **localsplus_start, + int n_locals_already_filled, + int curr_stackentries); +extern int _Py_uop_ctx_frame_pop(_Py_UOpsAbstractInterpContext *ctx); + +PyAPI_FUNC(PyObject *) +_Py_uop_symbols_test(PyObject *self, PyObject *ignored); + #ifdef __cplusplus } #endif diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 6a5586822c24913..601775817043fe1 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -900,7 +900,7 @@ def test_overridden_abstract_args(self): case OP2: { _Py_UOpsSymType *out; - out = sym_new_unknown(ctx); + out = _Py_uop_sym_new_unknown(ctx); if (out == NULL) goto out_of_space; stack_pointer[-1] = out; break; @@ -925,7 +925,7 @@ def test_no_overridden_case(self): output = """ case OP: { _Py_UOpsSymType *out; - out = sym_new_unknown(ctx); + out = _Py_uop_sym_new_unknown(ctx); if (out == NULL) goto out_of_space; stack_pointer[-1] = out; break; diff --git a/Lib/test/test_optimizer.py b/Lib/test/test_optimizer.py index dfea8be3c6956f3..899a45073173347 100644 --- a/Lib/test/test_optimizer.py +++ b/Lib/test/test_optimizer.py @@ -77,5 +77,12 @@ def func(x=0): _testinternalcapi.get_rare_event_counters()["func_modification"] ) + +class TestOptimizerSymbols(unittest.TestCase): + + def test_optimizer_symbols(self): + _testinternalcapi.uop_symbols_test() + + if __name__ == "__main__": unittest.main() diff --git a/Makefile.pre.in b/Makefile.pre.in index e4c64aea80d22c3..7533a49b4392f04 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -446,6 +446,7 @@ PYTHON_OBJS= \ Python/object_stack.o \ Python/optimizer.o \ Python/optimizer_analysis.o \ + Python/optimizer_symbols.o \ Python/parking_lot.o \ Python/pathconfig.o \ Python/preconfig.o \ diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 5b714ca3f3dc33e..9c6970d48ae6f7e 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -24,6 +24,7 @@ #include "pycore_interp.h" // _PyInterpreterState_GetConfigCopy() #include "pycore_long.h" // _PyLong_Sign() #include "pycore_object.h" // _PyObject_IsFreed() +#include "pycore_optimizer.h" // _Py_UOpsSymType, etc. #include "pycore_pathconfig.h" // _PyPathConfig_ClearGlobal() #include "pycore_pyerrors.h" // _PyErr_ChainExceptions1() #include "pycore_pystate.h" // _PyThreadState_GET() @@ -1677,7 +1678,6 @@ get_py_thread_id(PyObject *self, PyObject *Py_UNUSED(ignored)) } #endif - static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, @@ -1747,6 +1747,7 @@ static PyMethodDef module_functions[] = { #ifdef Py_GIL_DISABLED {"py_thread_id", get_py_thread_id, METH_NOARGS}, #endif + {"uop_symbols_test", _Py_uop_symbols_test, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index 00ad3e2472af04c..3a8a417a6bf47a0 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -235,6 +235,7 @@ <ClCompile Include="..\Python\object_stack.c" /> <ClCompile Include="..\Python\optimizer.c" /> <ClCompile Include="..\Python\optimizer_analysis.c" /> + <ClCompile Include="..\Python\optimizer_symbols.c" /> <ClCompile Include="..\Python\parking_lot.c" /> <ClCompile Include="..\Python\pathconfig.c" /> <ClCompile Include="..\Python\perf_trampoline.c" /> diff --git a/PCbuild/_freeze_module.vcxproj.filters b/PCbuild/_freeze_module.vcxproj.filters index aea5f730607658e..5b34440af9322b5 100644 --- a/PCbuild/_freeze_module.vcxproj.filters +++ b/PCbuild/_freeze_module.vcxproj.filters @@ -310,6 +310,9 @@ <ClCompile Include="..\Python\optimizer_analysis.c"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="..\Python\optimizer_symbols.c"> + <Filter>Python</Filter> + </ClCompile> <ClCompile Include="..\Parser\parser.c"> <Filter>Source Files</Filter> </ClCompile> diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index ef21f85107bc32d..88a4a7c95643098 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -601,6 +601,7 @@ <ClCompile Include="..\Python\object_stack.c" /> <ClCompile Include="..\Python\optimizer.c" /> <ClCompile Include="..\Python\optimizer_analysis.c" /> + <ClCompile Include="..\Python\optimizer_symbols.c" /> <ClCompile Include="..\Python\parking_lot.c" /> <ClCompile Include="..\Python\pathconfig.c" /> <ClCompile Include="..\Python\perf_trampoline.c" /> diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index ffe93dc787a8b8d..27bd1121663398f 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -1382,6 +1382,9 @@ <ClCompile Include="..\Python\optimizer_analysis.c"> <Filter>Python</Filter> </ClCompile> + <ClCompile Include="..\Python\optimizer_symbols.c"> + <Filter>Python</Filter> + </ClCompile> <ClCompile Include="..\Python\parking_lot.c"> <Filter>Python</Filter> </ClCompile> diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index e9751291e2fd5ae..6b2aec8385824d0 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -33,16 +33,6 @@ #include <stdint.h> #include <stddef.h> -// Holds locals, stack, locals, stack ... co_consts (in that order) -#define MAX_ABSTRACT_INTERP_SIZE 4096 - -#define OVERALLOCATE_FACTOR 5 - -#define TY_ARENA_SIZE (UOP_MAX_TRACE_LENGTH * OVERALLOCATE_FACTOR) - -// Need extras for root frame and for overflow frame (see TRACE_STACK_PUSH()) -#define MAX_ABSTRACT_FRAME_DEPTH (TRACE_STACK_SIZE + 2) - #ifdef Py_DEBUG extern const char *_PyUOpName(int index); static const char *const DEBUG_ENV = "PYTHON_OPT_DEBUG"; @@ -61,318 +51,6 @@ #endif -// Flags for below. -#define KNOWN 1 << 0 -#define TRUE_CONST 1 << 1 -#define IS_NULL 1 << 2 -#define NOT_NULL 1 << 3 - -typedef struct { - int flags; - PyTypeObject *typ; - // constant propagated value (might be NULL) - PyObject *const_val; -} _Py_UOpsSymType; - - -typedef struct _Py_UOpsAbstractFrame { - // Max stacklen - int stack_len; - int locals_len; - - _Py_UOpsSymType **stack_pointer; - _Py_UOpsSymType **stack; - _Py_UOpsSymType **locals; -} _Py_UOpsAbstractFrame; - - -typedef struct ty_arena { - int ty_curr_number; - int ty_max_number; - _Py_UOpsSymType arena[TY_ARENA_SIZE]; -} ty_arena; - -// Tier 2 types meta interpreter -typedef struct _Py_UOpsAbstractInterpContext { - PyObject_HEAD - // The current "executing" frame. - _Py_UOpsAbstractFrame *frame; - _Py_UOpsAbstractFrame frames[MAX_ABSTRACT_FRAME_DEPTH]; - int curr_frame_depth; - - // Arena for the symbolic types. - ty_arena t_arena; - - _Py_UOpsSymType **n_consumed; - _Py_UOpsSymType **limit; - _Py_UOpsSymType *locals_and_stack[MAX_ABSTRACT_INTERP_SIZE]; -} _Py_UOpsAbstractInterpContext; - -static inline _Py_UOpsSymType* sym_new_unknown(_Py_UOpsAbstractInterpContext *ctx); - -// 0 on success, -1 on error. -static _Py_UOpsAbstractFrame * -ctx_frame_new( - _Py_UOpsAbstractInterpContext *ctx, - PyCodeObject *co, - _Py_UOpsSymType **localsplus_start, - int n_locals_already_filled, - int curr_stackentries -) -{ - assert(ctx->curr_frame_depth < MAX_ABSTRACT_FRAME_DEPTH); - _Py_UOpsAbstractFrame *frame = &ctx->frames[ctx->curr_frame_depth]; - - frame->stack_len = co->co_stacksize; - frame->locals_len = co->co_nlocalsplus; - - frame->locals = localsplus_start; - frame->stack = frame->locals + co->co_nlocalsplus; - frame->stack_pointer = frame->stack + curr_stackentries; - ctx->n_consumed = localsplus_start + (co->co_nlocalsplus + co->co_stacksize); - if (ctx->n_consumed >= ctx->limit) { - return NULL; - } - - - // Initialize with the initial state of all local variables - for (int i = n_locals_already_filled; i < co->co_nlocalsplus; i++) { - _Py_UOpsSymType *local = sym_new_unknown(ctx); - if (local == NULL) { - return NULL; - } - frame->locals[i] = local; - } - - - // Initialize the stack as well - for (int i = 0; i < curr_stackentries; i++) { - _Py_UOpsSymType *stackvar = sym_new_unknown(ctx); - if (stackvar == NULL) { - return NULL; - } - frame->stack[i] = stackvar; - } - - return frame; -} - -static void -abstractcontext_fini(_Py_UOpsAbstractInterpContext *ctx) -{ - if (ctx == NULL) { - return; - } - ctx->curr_frame_depth = 0; - int tys = ctx->t_arena.ty_curr_number; - for (int i = 0; i < tys; i++) { - Py_CLEAR(ctx->t_arena.arena[i].const_val); - } -} - -static int -abstractcontext_init( - _Py_UOpsAbstractInterpContext *ctx, - PyCodeObject *co, - int curr_stacklen, - int ir_entries -) -{ - ctx->limit = ctx->locals_and_stack + MAX_ABSTRACT_INTERP_SIZE; - ctx->n_consumed = ctx->locals_and_stack; -#ifdef Py_DEBUG // Aids debugging a little. There should never be NULL in the abstract interpreter. - for (int i = 0 ; i < MAX_ABSTRACT_INTERP_SIZE; i++) { - ctx->locals_and_stack[i] = NULL; - } -#endif - - // Setup the arena for sym expressions. - ctx->t_arena.ty_curr_number = 0; - ctx->t_arena.ty_max_number = TY_ARENA_SIZE; - - // Frame setup - ctx->curr_frame_depth = 0; - _Py_UOpsAbstractFrame *frame = ctx_frame_new(ctx, co, ctx->n_consumed, 0, curr_stacklen); - if (frame == NULL) { - return -1; - } - ctx->curr_frame_depth++; - ctx->frame = frame; - return 0; -} - - -static int -ctx_frame_pop( - _Py_UOpsAbstractInterpContext *ctx -) -{ - _Py_UOpsAbstractFrame *frame = ctx->frame; - - ctx->n_consumed = frame->locals; - ctx->curr_frame_depth--; - assert(ctx->curr_frame_depth >= 1); - ctx->frame = &ctx->frames[ctx->curr_frame_depth - 1]; - - return 0; -} - - -// Takes a borrowed reference to const_val, turns that into a strong reference. -static _Py_UOpsSymType* -sym_new(_Py_UOpsAbstractInterpContext *ctx, - PyObject *const_val) -{ - _Py_UOpsSymType *self = &ctx->t_arena.arena[ctx->t_arena.ty_curr_number]; - if (ctx->t_arena.ty_curr_number >= ctx->t_arena.ty_max_number) { - OPT_STAT_INC(optimizer_failure_reason_no_memory); - DPRINTF(1, "out of space for symbolic expression type\n"); - return NULL; - } - ctx->t_arena.ty_curr_number++; - self->const_val = NULL; - self->typ = NULL; - self->flags = 0; - - if (const_val != NULL) { - self->const_val = Py_NewRef(const_val); - } - - return self; -} - -static inline void -sym_set_flag(_Py_UOpsSymType *sym, int flag) -{ - sym->flags |= flag; -} - -static inline bool -sym_has_flag(_Py_UOpsSymType *sym, int flag) -{ - return (sym->flags & flag) != 0; -} - -static inline bool -sym_is_known(_Py_UOpsSymType *sym) -{ - return sym_has_flag(sym, KNOWN); -} - -static inline bool -sym_is_not_null(_Py_UOpsSymType *sym) -{ - return (sym->flags & (IS_NULL | NOT_NULL)) == NOT_NULL; -} - -static inline bool -sym_is_null(_Py_UOpsSymType *sym) -{ - return (sym->flags & (IS_NULL | NOT_NULL)) == IS_NULL; -} - -static inline bool -sym_is_const(_Py_UOpsSymType *sym) -{ - return (sym->flags & TRUE_CONST) != 0; -} - -static inline PyObject * -sym_get_const(_Py_UOpsSymType *sym) -{ - assert(sym_is_const(sym)); - assert(sym->const_val); - return sym->const_val; -} - -static inline void -sym_set_type(_Py_UOpsSymType *sym, PyTypeObject *tp) -{ - assert(PyType_Check(tp)); - sym->typ = tp; - sym_set_flag(sym, KNOWN); - sym_set_flag(sym, NOT_NULL); -} - -static inline void -sym_set_null(_Py_UOpsSymType *sym) -{ - sym_set_flag(sym, IS_NULL); - sym_set_flag(sym, KNOWN); -} - - -static inline _Py_UOpsSymType* -sym_new_unknown(_Py_UOpsAbstractInterpContext *ctx) -{ - return sym_new(ctx,NULL); -} - -static inline _Py_UOpsSymType* -sym_new_known_notnull(_Py_UOpsAbstractInterpContext *ctx) -{ - _Py_UOpsSymType *res = sym_new_unknown(ctx); - if (res == NULL) { - return NULL; - } - sym_set_flag(res, KNOWN); - sym_set_flag(res, NOT_NULL); - return res; -} - -static inline _Py_UOpsSymType* -sym_new_known_type(_Py_UOpsAbstractInterpContext *ctx, - PyTypeObject *typ) -{ - _Py_UOpsSymType *res = sym_new(ctx,NULL); - if (res == NULL) { - return NULL; - } - sym_set_type(res, typ); - return res; -} - -// Takes a borrowed reference to const_val. -static inline _Py_UOpsSymType* -sym_new_const(_Py_UOpsAbstractInterpContext *ctx, PyObject *const_val) -{ - assert(const_val != NULL); - _Py_UOpsSymType *temp = sym_new( - ctx, - const_val - ); - if (temp == NULL) { - return NULL; - } - sym_set_type(temp, Py_TYPE(const_val)); - sym_set_flag(temp, TRUE_CONST); - sym_set_flag(temp, KNOWN); - sym_set_flag(temp, NOT_NULL); - return temp; -} - -static _Py_UOpsSymType* -sym_new_null(_Py_UOpsAbstractInterpContext *ctx) -{ - _Py_UOpsSymType *null_sym = sym_new_unknown(ctx); - if (null_sym == NULL) { - return NULL; - } - sym_set_null(null_sym); - return null_sym; -} - - -static inline bool -sym_matches_type(_Py_UOpsSymType *sym, PyTypeObject *typ) -{ - assert(typ == NULL || PyType_Check(typ)); - if (!sym_has_flag(sym, KNOWN)) { - return false; - } - return sym->typ == typ; -} - static inline bool op_is_end(uint32_t opcode) @@ -599,8 +277,8 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, #define _LOAD_ATTR_NOT_NULL \ do { \ - OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); \ - OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); \ + OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_not_null(ctx)); \ + OUT_OF_SPACE_IF_NULL(null = _Py_uop_sym_new_null(ctx)); \ } while (0); @@ -618,12 +296,15 @@ optimize_uops( _Py_UOpsAbstractInterpContext context; _Py_UOpsAbstractInterpContext *ctx = &context; - if (abstractcontext_init( - ctx, - co, curr_stacklen, - trace_len) < 0) { + if (_Py_uop_abstractcontext_init(ctx) < 0) { goto out_of_space; } + _Py_UOpsAbstractFrame *frame = _Py_uop_ctx_frame_new(ctx, co, ctx->n_consumed, 0, curr_stacklen); + if (frame == NULL) { + return -1; + } + ctx->curr_frame_depth++; + ctx->frame = frame; for (_PyUOpInstruction *this_instr = trace; this_instr < trace + trace_len && !op_is_end(this_instr->opcode); @@ -650,17 +331,17 @@ optimize_uops( assert(STACK_LEVEL() >= 0); } - abstractcontext_fini(ctx); + _Py_uop_abstractcontext_fini(ctx); return 1; out_of_space: DPRINTF(1, "Out of space in abstract interpreter\n"); - abstractcontext_fini(ctx); + _Py_uop_abstractcontext_fini(ctx); return 0; error: DPRINTF(1, "Encountered error in abstract interpreter\n"); - abstractcontext_fini(ctx); + _Py_uop_abstractcontext_fini(ctx); return 0; } diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index b9afd3089e10778..e6e41f8968eaf36 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -33,7 +33,7 @@ dummy_func(void) { op(_LOAD_FAST_CHECK, (-- value)) { value = GETLOCAL(oparg); // We guarantee this will error - just bail and don't optimize it. - if (sym_is_null(value)) { + if (_Py_uop_sym_is_null(value)) { goto out_of_space; } } @@ -45,7 +45,7 @@ dummy_func(void) { op(_LOAD_FAST_AND_CLEAR, (-- value)) { value = GETLOCAL(oparg); _Py_UOpsSymType *temp; - OUT_OF_SPACE_IF_NULL(temp = sym_new_null(ctx)); + OUT_OF_SPACE_IF_NULL(temp = _Py_uop_sym_new_null(ctx)); GETLOCAL(oparg) = temp; } @@ -54,147 +54,147 @@ dummy_func(void) { } op(_PUSH_NULL, (-- res)) { - res = sym_new_null(ctx); + res = _Py_uop_sym_new_null(ctx); if (res == NULL) { goto out_of_space; }; } op(_GUARD_BOTH_INT, (left, right -- left, right)) { - if (sym_matches_type(left, &PyLong_Type) && - sym_matches_type(right, &PyLong_Type)) { + if (_Py_uop_sym_matches_type(left, &PyLong_Type) && + _Py_uop_sym_matches_type(right, &PyLong_Type)) { REPLACE_OP(this_instr, _NOP, 0, 0); } - sym_set_type(left, &PyLong_Type); - sym_set_type(right, &PyLong_Type); + _Py_uop_sym_set_type(left, &PyLong_Type); + _Py_uop_sym_set_type(right, &PyLong_Type); } op(_GUARD_BOTH_FLOAT, (left, right -- left, right)) { - if (sym_matches_type(left, &PyFloat_Type) && - sym_matches_type(right, &PyFloat_Type)) { + if (_Py_uop_sym_matches_type(left, &PyFloat_Type) && + _Py_uop_sym_matches_type(right, &PyFloat_Type)) { REPLACE_OP(this_instr, _NOP, 0 ,0); } - sym_set_type(left, &PyFloat_Type); - sym_set_type(right, &PyFloat_Type); + _Py_uop_sym_set_type(left, &PyFloat_Type); + _Py_uop_sym_set_type(right, &PyFloat_Type); } op(_GUARD_BOTH_UNICODE, (left, right -- left, right)) { - if (sym_matches_type(left, &PyUnicode_Type) && - sym_matches_type(right, &PyUnicode_Type)) { + if (_Py_uop_sym_matches_type(left, &PyUnicode_Type) && + _Py_uop_sym_matches_type(right, &PyUnicode_Type)) { REPLACE_OP(this_instr, _NOP, 0 ,0); } - sym_set_type(left, &PyUnicode_Type); - sym_set_type(right, &PyUnicode_Type); + _Py_uop_sym_set_type(left, &PyUnicode_Type); + _Py_uop_sym_set_type(right, &PyUnicode_Type); } op(_BINARY_OP_ADD_INT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right)) { - assert(PyLong_CheckExact(sym_get_const(left))); - assert(PyLong_CheckExact(sym_get_const(right))); - PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(left), - (PyLongObject *)sym_get_const(right)); + if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { + assert(PyLong_CheckExact(_Py_uop_sym_get_const(left))); + assert(PyLong_CheckExact(_Py_uop_sym_get_const(right))); + PyObject *temp = _PyLong_Add((PyLongObject *)_Py_uop_sym_get_const(left), + (PyLongObject *)_Py_uop_sym_get_const(right)); if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_const(ctx, temp)); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyLong_Type)); } } op(_BINARY_OP_SUBTRACT_INT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right)) { - assert(PyLong_CheckExact(sym_get_const(left))); - assert(PyLong_CheckExact(sym_get_const(right))); - PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(left), - (PyLongObject *)sym_get_const(right)); + if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { + assert(PyLong_CheckExact(_Py_uop_sym_get_const(left))); + assert(PyLong_CheckExact(_Py_uop_sym_get_const(right))); + PyObject *temp = _PyLong_Subtract((PyLongObject *)_Py_uop_sym_get_const(left), + (PyLongObject *)_Py_uop_sym_get_const(right)); if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_const(ctx, temp)); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyLong_Type)); } } op(_BINARY_OP_MULTIPLY_INT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right)) { - assert(PyLong_CheckExact(sym_get_const(left))); - assert(PyLong_CheckExact(sym_get_const(right))); - PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(left), - (PyLongObject *)sym_get_const(right)); + if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { + assert(PyLong_CheckExact(_Py_uop_sym_get_const(left))); + assert(PyLong_CheckExact(_Py_uop_sym_get_const(right))); + PyObject *temp = _PyLong_Multiply((PyLongObject *)_Py_uop_sym_get_const(left), + (PyLongObject *)_Py_uop_sym_get_const(right)); if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_const(ctx, temp)); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyLong_Type)); } } op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right)) { - assert(PyFloat_CheckExact(sym_get_const(left))); - assert(PyFloat_CheckExact(sym_get_const(right))); + if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { + assert(PyFloat_CheckExact(_Py_uop_sym_get_const(left))); + assert(PyFloat_CheckExact(_Py_uop_sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(sym_get_const(left)) + - PyFloat_AS_DOUBLE(sym_get_const(right))); + PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(left)) + + PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(right))); if (temp == NULL) { goto error; } - res = sym_new_const(ctx, temp); + res = _Py_uop_sym_new_const(ctx, temp); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyFloat_Type)); } } op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right)) { - assert(PyFloat_CheckExact(sym_get_const(left))); - assert(PyFloat_CheckExact(sym_get_const(right))); + if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { + assert(PyFloat_CheckExact(_Py_uop_sym_get_const(left))); + assert(PyFloat_CheckExact(_Py_uop_sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(sym_get_const(left)) - - PyFloat_AS_DOUBLE(sym_get_const(right))); + PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(left)) - + PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(right))); if (temp == NULL) { goto error; } - res = sym_new_const(ctx, temp); + res = _Py_uop_sym_new_const(ctx, temp); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyFloat_Type)); } } op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right)) { - assert(PyFloat_CheckExact(sym_get_const(left))); - assert(PyFloat_CheckExact(sym_get_const(right))); + if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { + assert(PyFloat_CheckExact(_Py_uop_sym_get_const(left))); + assert(PyFloat_CheckExact(_Py_uop_sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(sym_get_const(left)) * - PyFloat_AS_DOUBLE(sym_get_const(right))); + PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(left)) * + PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(right))); if (temp == NULL) { goto error; } - res = sym_new_const(ctx, temp); + res = _Py_uop_sym_new_const(ctx, temp); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyFloat_Type)); } } @@ -205,21 +205,21 @@ dummy_func(void) { } op(_LOAD_CONST_INLINE, (ptr/4 -- value)) { - OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(value = _Py_uop_sym_new_const(ctx, ptr)); } op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) { - OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(value = _Py_uop_sym_new_const(ctx, ptr)); } op(_LOAD_CONST_INLINE_WITH_NULL, (ptr/4 -- value, null)) { - OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); - OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); + OUT_OF_SPACE_IF_NULL(value = _Py_uop_sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(null = _Py_uop_sym_new_null(ctx)); } op(_LOAD_CONST_INLINE_BORROW_WITH_NULL, (ptr/4 -- value, null)) { - OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); - OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); + OUT_OF_SPACE_IF_NULL(value = _Py_uop_sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(null = _Py_uop_sym_new_null(ctx)); } @@ -240,8 +240,8 @@ dummy_func(void) { op(_CHECK_ATTR_MODULE, (dict_version/2, owner -- owner)) { (void)dict_version; - if (sym_is_const(owner)) { - PyObject *cnst = sym_get_const(owner); + if (_Py_uop_sym_is_const(owner)) { + PyObject *cnst = _Py_uop_sym_get_const(owner); if (PyModule_CheckExact(cnst)) { PyModuleObject *mod = (PyModuleObject *)cnst; PyObject *dict = mod->md_dict; @@ -257,23 +257,23 @@ dummy_func(void) { op(_LOAD_ATTR_MODULE, (index/1, owner -- attr, null if (oparg & 1))) { (void)index; - OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); + OUT_OF_SPACE_IF_NULL(null = _Py_uop_sym_new_null(ctx)); attr = NULL; if (this_instr[-1].opcode == _NOP) { // Preceding _CHECK_ATTR_MODULE was removed: mod is const and dict is watched. - assert(sym_is_const(owner)); - PyModuleObject *mod = (PyModuleObject *)sym_get_const(owner); + assert(_Py_uop_sym_is_const(owner)); + PyModuleObject *mod = (PyModuleObject *)_Py_uop_sym_get_const(owner); assert(PyModule_CheckExact(mod)); PyObject *dict = mod->md_dict; PyObject *res = convert_global_to_const(this_instr, dict); if (res != NULL) { this_instr[-1].opcode = _POP_TOP; - OUT_OF_SPACE_IF_NULL(attr = sym_new_const(ctx, res)); + OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_const(ctx, res)); } } if (attr == NULL) { /* No conversion made. We don't know what `attr` is. */ - OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_not_null(ctx)); } } @@ -297,38 +297,38 @@ dummy_func(void) { op(_LOAD_ATTR_METHOD_WITH_VALUES, (descr/4, owner -- attr, self if (1))) { (void)descr; - OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_not_null(ctx)); self = owner; } op(_LOAD_ATTR_METHOD_NO_DICT, (descr/4, owner -- attr, self if (1))) { (void)descr; - OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_not_null(ctx)); self = owner; } op(_LOAD_ATTR_METHOD_LAZY_DICT, (descr/4, owner -- attr, self if (1))) { (void)descr; - OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_not_null(ctx)); self = owner; } op(_INIT_CALL_BOUND_METHOD_EXACT_ARGS, (callable, unused, unused[oparg] -- func, self, unused[oparg])) { (void)callable; - OUT_OF_SPACE_IF_NULL(func = sym_new_known_notnull(ctx)); - OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(func = _Py_uop_sym_new_not_null(ctx)); + OUT_OF_SPACE_IF_NULL(self = _Py_uop_sym_new_not_null(ctx)); } op(_CHECK_FUNCTION_EXACT_ARGS, (func_version/2, callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) { - sym_set_type(callable, &PyFunction_Type); + _Py_uop_sym_set_type(callable, &PyFunction_Type); (void)self_or_null; (void)func_version; } op(_CHECK_CALL_BOUND_METHOD_EXACT_ARGS, (callable, null, unused[oparg] -- callable, null, unused[oparg])) { - sym_set_null(null); - sym_set_type(callable, &PyMethod_Type); + _Py_uop_sym_set_null(null); + _Py_uop_sym_set_type(callable, &PyMethod_Type); } op(_INIT_CALL_PY_EXACT_ARGS, (callable, self_or_null, args[oparg] -- new_frame: _Py_UOpsAbstractFrame *)) { @@ -344,7 +344,7 @@ dummy_func(void) { assert(self_or_null != NULL); assert(args != NULL); - if (sym_is_not_null(self_or_null)) { + if (_Py_uop_sym_is_not_null(self_or_null)) { // Bound method fiddling, same as _INIT_CALL_PY_EXACT_ARGS in VM args--; argcount++; @@ -355,18 +355,18 @@ dummy_func(void) { // Can determine statically, so we interleave the new locals // and make the current stack the new locals. // This also sets up for true call inlining. - if (sym_is_known(self_or_null)) { + if (_Py_uop_sym_is_null(self_or_null) || _Py_uop_sym_is_not_null(self_or_null)) { localsplus_start = args; n_locals_already_filled = argcount; } OUT_OF_SPACE_IF_NULL(new_frame = - ctx_frame_new(ctx, co, localsplus_start, n_locals_already_filled, 0)); + _Py_uop_ctx_frame_new(ctx, co, localsplus_start, n_locals_already_filled, 0)); } op(_POP_FRAME, (retval -- res)) { SYNC_SP(); ctx->frame->stack_pointer = stack_pointer; - ctx_frame_pop(ctx); + _Py_uop_ctx_frame_pop(ctx); stack_pointer = ctx->frame->stack_pointer; res = retval; } @@ -383,7 +383,7 @@ dummy_func(void) { /* This has to be done manually */ (void)seq; for (int i = 0; i < oparg; i++) { - OUT_OF_SPACE_IF_NULL(values[i] = sym_new_unknown(ctx)); + OUT_OF_SPACE_IF_NULL(values[i] = _Py_uop_sym_new_unknown(ctx)); } } @@ -392,12 +392,12 @@ dummy_func(void) { (void)seq; int totalargs = (oparg & 0xFF) + (oparg >> 8) + 1; for (int i = 0; i < totalargs; i++) { - OUT_OF_SPACE_IF_NULL(values[i] = sym_new_unknown(ctx)); + OUT_OF_SPACE_IF_NULL(values[i] = _Py_uop_sym_new_unknown(ctx)); } } op(_ITER_NEXT_RANGE, (iter -- iter, next)) { - OUT_OF_SPACE_IF_NULL(next = sym_new_known_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(next = _Py_uop_sym_new_type(ctx, &PyLong_Type)); (void)iter; } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index e8146dccbdfcd18..305158847da6fc0 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -17,7 +17,7 @@ _Py_UOpsSymType *value; value = GETLOCAL(oparg); // We guarantee this will error - just bail and don't optimize it. - if (sym_is_null(value)) { + if (_Py_uop_sym_is_null(value)) { goto out_of_space; } stack_pointer[0] = value; @@ -37,7 +37,7 @@ _Py_UOpsSymType *value; value = GETLOCAL(oparg); _Py_UOpsSymType *temp; - OUT_OF_SPACE_IF_NULL(temp = sym_new_null(ctx)); + OUT_OF_SPACE_IF_NULL(temp = _Py_uop_sym_new_null(ctx)); GETLOCAL(oparg) = temp; stack_pointer[0] = value; stack_pointer += 1; @@ -69,7 +69,7 @@ case _PUSH_NULL: { _Py_UOpsSymType *res; - res = sym_new_null(ctx); + res = _Py_uop_sym_new_null(ctx); if (res == NULL) { goto out_of_space; }; @@ -80,7 +80,7 @@ case _END_SEND: { _Py_UOpsSymType *value; - value = sym_new_unknown(ctx); + value = _Py_uop_sym_new_unknown(ctx); if (value == NULL) goto out_of_space; stack_pointer[-2] = value; stack_pointer += -1; @@ -89,7 +89,7 @@ case _UNARY_NEGATIVE: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -97,7 +97,7 @@ case _UNARY_NOT: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -105,7 +105,7 @@ case _TO_BOOL: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -117,7 +117,7 @@ case _TO_BOOL_INT: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -125,7 +125,7 @@ case _TO_BOOL_LIST: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -133,7 +133,7 @@ case _TO_BOOL_NONE: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -141,7 +141,7 @@ case _TO_BOOL_STR: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -149,7 +149,7 @@ case _TO_BOOL_ALWAYS_TRUE: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -157,7 +157,7 @@ case _UNARY_INVERT: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -168,12 +168,12 @@ _Py_UOpsSymType *left; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_matches_type(left, &PyLong_Type) && - sym_matches_type(right, &PyLong_Type)) { + if (_Py_uop_sym_matches_type(left, &PyLong_Type) && + _Py_uop_sym_matches_type(right, &PyLong_Type)) { REPLACE_OP(this_instr, _NOP, 0, 0); } - sym_set_type(left, &PyLong_Type); - sym_set_type(right, &PyLong_Type); + _Py_uop_sym_set_type(left, &PyLong_Type); + _Py_uop_sym_set_type(right, &PyLong_Type); break; } @@ -183,20 +183,20 @@ _Py_UOpsSymType *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right)) { - assert(PyLong_CheckExact(sym_get_const(left))); - assert(PyLong_CheckExact(sym_get_const(right))); - PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(left), - (PyLongObject *)sym_get_const(right)); + if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { + assert(PyLong_CheckExact(_Py_uop_sym_get_const(left))); + assert(PyLong_CheckExact(_Py_uop_sym_get_const(right))); + PyObject *temp = _PyLong_Multiply((PyLongObject *)_Py_uop_sym_get_const(left), + (PyLongObject *)_Py_uop_sym_get_const(right)); if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_const(ctx, temp)); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyLong_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -209,20 +209,20 @@ _Py_UOpsSymType *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right)) { - assert(PyLong_CheckExact(sym_get_const(left))); - assert(PyLong_CheckExact(sym_get_const(right))); - PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(left), - (PyLongObject *)sym_get_const(right)); + if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { + assert(PyLong_CheckExact(_Py_uop_sym_get_const(left))); + assert(PyLong_CheckExact(_Py_uop_sym_get_const(right))); + PyObject *temp = _PyLong_Add((PyLongObject *)_Py_uop_sym_get_const(left), + (PyLongObject *)_Py_uop_sym_get_const(right)); if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_const(ctx, temp)); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyLong_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -235,20 +235,20 @@ _Py_UOpsSymType *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right)) { - assert(PyLong_CheckExact(sym_get_const(left))); - assert(PyLong_CheckExact(sym_get_const(right))); - PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(left), - (PyLongObject *)sym_get_const(right)); + if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { + assert(PyLong_CheckExact(_Py_uop_sym_get_const(left))); + assert(PyLong_CheckExact(_Py_uop_sym_get_const(right))); + PyObject *temp = _PyLong_Subtract((PyLongObject *)_Py_uop_sym_get_const(left), + (PyLongObject *)_Py_uop_sym_get_const(right)); if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_const(ctx, temp)); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyLong_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -260,12 +260,12 @@ _Py_UOpsSymType *left; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_matches_type(left, &PyFloat_Type) && - sym_matches_type(right, &PyFloat_Type)) { + if (_Py_uop_sym_matches_type(left, &PyFloat_Type) && + _Py_uop_sym_matches_type(right, &PyFloat_Type)) { REPLACE_OP(this_instr, _NOP, 0 ,0); } - sym_set_type(left, &PyFloat_Type); - sym_set_type(right, &PyFloat_Type); + _Py_uop_sym_set_type(left, &PyFloat_Type); + _Py_uop_sym_set_type(right, &PyFloat_Type); break; } @@ -275,21 +275,21 @@ _Py_UOpsSymType *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right)) { - assert(PyFloat_CheckExact(sym_get_const(left))); - assert(PyFloat_CheckExact(sym_get_const(right))); + if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { + assert(PyFloat_CheckExact(_Py_uop_sym_get_const(left))); + assert(PyFloat_CheckExact(_Py_uop_sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(sym_get_const(left)) * - PyFloat_AS_DOUBLE(sym_get_const(right))); + PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(left)) * + PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(right))); if (temp == NULL) { goto error; } - res = sym_new_const(ctx, temp); + res = _Py_uop_sym_new_const(ctx, temp); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyFloat_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -302,21 +302,21 @@ _Py_UOpsSymType *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right)) { - assert(PyFloat_CheckExact(sym_get_const(left))); - assert(PyFloat_CheckExact(sym_get_const(right))); + if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { + assert(PyFloat_CheckExact(_Py_uop_sym_get_const(left))); + assert(PyFloat_CheckExact(_Py_uop_sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(sym_get_const(left)) + - PyFloat_AS_DOUBLE(sym_get_const(right))); + PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(left)) + + PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(right))); if (temp == NULL) { goto error; } - res = sym_new_const(ctx, temp); + res = _Py_uop_sym_new_const(ctx, temp); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyFloat_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -329,21 +329,21 @@ _Py_UOpsSymType *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right)) { - assert(PyFloat_CheckExact(sym_get_const(left))); - assert(PyFloat_CheckExact(sym_get_const(right))); + if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { + assert(PyFloat_CheckExact(_Py_uop_sym_get_const(left))); + assert(PyFloat_CheckExact(_Py_uop_sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(sym_get_const(left)) - - PyFloat_AS_DOUBLE(sym_get_const(right))); + PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(left)) - + PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(right))); if (temp == NULL) { goto error; } - res = sym_new_const(ctx, temp); + res = _Py_uop_sym_new_const(ctx, temp); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyFloat_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -355,18 +355,18 @@ _Py_UOpsSymType *left; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_matches_type(left, &PyUnicode_Type) && - sym_matches_type(right, &PyUnicode_Type)) { + if (_Py_uop_sym_matches_type(left, &PyUnicode_Type) && + _Py_uop_sym_matches_type(right, &PyUnicode_Type)) { REPLACE_OP(this_instr, _NOP, 0 ,0); } - sym_set_type(left, &PyUnicode_Type); - sym_set_type(right, &PyUnicode_Type); + _Py_uop_sym_set_type(left, &PyUnicode_Type); + _Py_uop_sym_set_type(right, &PyUnicode_Type); break; } case _BINARY_OP_ADD_UNICODE: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -375,7 +375,7 @@ case _BINARY_SUBSCR: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -384,7 +384,7 @@ case _BINARY_SLICE: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-3] = res; stack_pointer += -2; @@ -398,7 +398,7 @@ case _BINARY_SUBSCR_LIST_INT: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -407,7 +407,7 @@ case _BINARY_SUBSCR_STR_INT: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -416,7 +416,7 @@ case _BINARY_SUBSCR_TUPLE_INT: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -425,7 +425,7 @@ case _BINARY_SUBSCR_DICT: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -466,7 +466,7 @@ case _CALL_INTRINSIC_1: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -474,7 +474,7 @@ case _CALL_INTRINSIC_2: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -487,7 +487,7 @@ retval = stack_pointer[-1]; stack_pointer += -1; ctx->frame->stack_pointer = stack_pointer; - ctx_frame_pop(ctx); + _Py_uop_ctx_frame_pop(ctx); stack_pointer = ctx->frame->stack_pointer; res = retval; stack_pointer[0] = res; @@ -501,7 +501,7 @@ case _GET_AITER: { _Py_UOpsSymType *iter; - iter = sym_new_unknown(ctx); + iter = _Py_uop_sym_new_unknown(ctx); if (iter == NULL) goto out_of_space; stack_pointer[-1] = iter; break; @@ -509,7 +509,7 @@ case _GET_ANEXT: { _Py_UOpsSymType *awaitable; - awaitable = sym_new_unknown(ctx); + awaitable = _Py_uop_sym_new_unknown(ctx); if (awaitable == NULL) goto out_of_space; stack_pointer[0] = awaitable; stack_pointer += 1; @@ -518,7 +518,7 @@ case _GET_AWAITABLE: { _Py_UOpsSymType *iter; - iter = sym_new_unknown(ctx); + iter = _Py_uop_sym_new_unknown(ctx); if (iter == NULL) goto out_of_space; stack_pointer[-1] = iter; break; @@ -537,7 +537,7 @@ case _LOAD_ASSERTION_ERROR: { _Py_UOpsSymType *value; - value = sym_new_unknown(ctx); + value = _Py_uop_sym_new_unknown(ctx); if (value == NULL) goto out_of_space; stack_pointer[0] = value; stack_pointer += 1; @@ -546,7 +546,7 @@ case _LOAD_BUILD_CLASS: { _Py_UOpsSymType *bc; - bc = sym_new_unknown(ctx); + bc = _Py_uop_sym_new_unknown(ctx); if (bc == NULL) goto out_of_space; stack_pointer[0] = bc; stack_pointer += 1; @@ -570,7 +570,7 @@ /* This has to be done manually */ (void)seq; for (int i = 0; i < oparg; i++) { - OUT_OF_SPACE_IF_NULL(values[i] = sym_new_unknown(ctx)); + OUT_OF_SPACE_IF_NULL(values[i] = _Py_uop_sym_new_unknown(ctx)); } stack_pointer += -1 + oparg; break; @@ -580,7 +580,7 @@ _Py_UOpsSymType **values; values = &stack_pointer[-1]; for (int _i = oparg; --_i >= 0;) { - values[_i] = sym_new_unknown(ctx); + values[_i] = _Py_uop_sym_new_unknown(ctx); if (values[_i] == NULL) goto out_of_space; } stack_pointer += -1 + oparg; @@ -591,7 +591,7 @@ _Py_UOpsSymType **values; values = &stack_pointer[-1]; for (int _i = oparg; --_i >= 0;) { - values[_i] = sym_new_unknown(ctx); + values[_i] = _Py_uop_sym_new_unknown(ctx); if (values[_i] == NULL) goto out_of_space; } stack_pointer += -1 + oparg; @@ -602,7 +602,7 @@ _Py_UOpsSymType **values; values = &stack_pointer[-1]; for (int _i = oparg; --_i >= 0;) { - values[_i] = sym_new_unknown(ctx); + values[_i] = _Py_uop_sym_new_unknown(ctx); if (values[_i] == NULL) goto out_of_space; } stack_pointer += -1 + oparg; @@ -618,7 +618,7 @@ (void)seq; int totalargs = (oparg & 0xFF) + (oparg >> 8) + 1; for (int i = 0; i < totalargs; i++) { - OUT_OF_SPACE_IF_NULL(values[i] = sym_new_unknown(ctx)); + OUT_OF_SPACE_IF_NULL(values[i] = _Py_uop_sym_new_unknown(ctx)); } stack_pointer += (oparg >> 8) + (oparg & 0xFF); break; @@ -645,7 +645,7 @@ case _LOAD_LOCALS: { _Py_UOpsSymType *locals; - locals = sym_new_unknown(ctx); + locals = _Py_uop_sym_new_unknown(ctx); if (locals == NULL) goto out_of_space; stack_pointer[0] = locals; stack_pointer += 1; @@ -654,7 +654,7 @@ case _LOAD_FROM_DICT_OR_GLOBALS: { _Py_UOpsSymType *v; - v = sym_new_unknown(ctx); + v = _Py_uop_sym_new_unknown(ctx); if (v == NULL) goto out_of_space; stack_pointer[-1] = v; break; @@ -662,7 +662,7 @@ case _LOAD_NAME: { _Py_UOpsSymType *v; - v = sym_new_unknown(ctx); + v = _Py_uop_sym_new_unknown(ctx); if (v == NULL) goto out_of_space; stack_pointer[0] = v; stack_pointer += 1; @@ -672,9 +672,9 @@ case _LOAD_GLOBAL: { _Py_UOpsSymType *res; _Py_UOpsSymType *null = NULL; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; - null = sym_new_null(ctx); + null = _Py_uop_sym_new_null(ctx); if (null == NULL) goto out_of_space; stack_pointer[0] = res; if (oparg & 1) stack_pointer[1] = null; @@ -693,9 +693,9 @@ case _LOAD_GLOBAL_MODULE: { _Py_UOpsSymType *res; _Py_UOpsSymType *null = NULL; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; - null = sym_new_null(ctx); + null = _Py_uop_sym_new_null(ctx); if (null == NULL) goto out_of_space; stack_pointer[0] = res; if (oparg & 1) stack_pointer[1] = null; @@ -706,9 +706,9 @@ case _LOAD_GLOBAL_BUILTINS: { _Py_UOpsSymType *res; _Py_UOpsSymType *null = NULL; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; - null = sym_new_null(ctx); + null = _Py_uop_sym_new_null(ctx); if (null == NULL) goto out_of_space; stack_pointer[0] = res; if (oparg & 1) stack_pointer[1] = null; @@ -730,7 +730,7 @@ case _LOAD_FROM_DICT_OR_DEREF: { _Py_UOpsSymType *value; - value = sym_new_unknown(ctx); + value = _Py_uop_sym_new_unknown(ctx); if (value == NULL) goto out_of_space; stack_pointer[-1] = value; break; @@ -738,7 +738,7 @@ case _LOAD_DEREF: { _Py_UOpsSymType *value; - value = sym_new_unknown(ctx); + value = _Py_uop_sym_new_unknown(ctx); if (value == NULL) goto out_of_space; stack_pointer[0] = value; stack_pointer += 1; @@ -756,7 +756,7 @@ case _BUILD_STRING: { _Py_UOpsSymType *str; - str = sym_new_unknown(ctx); + str = _Py_uop_sym_new_unknown(ctx); if (str == NULL) goto out_of_space; stack_pointer[-oparg] = str; stack_pointer += 1 - oparg; @@ -765,7 +765,7 @@ case _BUILD_TUPLE: { _Py_UOpsSymType *tup; - tup = sym_new_unknown(ctx); + tup = _Py_uop_sym_new_unknown(ctx); if (tup == NULL) goto out_of_space; stack_pointer[-oparg] = tup; stack_pointer += 1 - oparg; @@ -774,7 +774,7 @@ case _BUILD_LIST: { _Py_UOpsSymType *list; - list = sym_new_unknown(ctx); + list = _Py_uop_sym_new_unknown(ctx); if (list == NULL) goto out_of_space; stack_pointer[-oparg] = list; stack_pointer += 1 - oparg; @@ -793,7 +793,7 @@ case _BUILD_SET: { _Py_UOpsSymType *set; - set = sym_new_unknown(ctx); + set = _Py_uop_sym_new_unknown(ctx); if (set == NULL) goto out_of_space; stack_pointer[-oparg] = set; stack_pointer += 1 - oparg; @@ -802,7 +802,7 @@ case _BUILD_MAP: { _Py_UOpsSymType *map; - map = sym_new_unknown(ctx); + map = _Py_uop_sym_new_unknown(ctx); if (map == NULL) goto out_of_space; stack_pointer[-oparg*2] = map; stack_pointer += 1 - oparg*2; @@ -815,7 +815,7 @@ case _BUILD_CONST_KEY_MAP: { _Py_UOpsSymType *map; - map = sym_new_unknown(ctx); + map = _Py_uop_sym_new_unknown(ctx); if (map == NULL) goto out_of_space; stack_pointer[-1 - oparg] = map; stack_pointer += -oparg; @@ -841,7 +841,7 @@ case _LOAD_SUPER_ATTR_ATTR: { _Py_UOpsSymType *attr; - attr = sym_new_unknown(ctx); + attr = _Py_uop_sym_new_unknown(ctx); if (attr == NULL) goto out_of_space; stack_pointer[-3] = attr; stack_pointer += -2; @@ -851,9 +851,9 @@ case _LOAD_SUPER_ATTR_METHOD: { _Py_UOpsSymType *attr; _Py_UOpsSymType *self_or_null; - attr = sym_new_unknown(ctx); + attr = _Py_uop_sym_new_unknown(ctx); if (attr == NULL) goto out_of_space; - self_or_null = sym_new_unknown(ctx); + self_or_null = _Py_uop_sym_new_unknown(ctx); if (self_or_null == NULL) goto out_of_space; stack_pointer[-3] = attr; stack_pointer[-2] = self_or_null; @@ -864,9 +864,9 @@ case _LOAD_ATTR: { _Py_UOpsSymType *attr; _Py_UOpsSymType *self_or_null = NULL; - attr = sym_new_unknown(ctx); + attr = _Py_uop_sym_new_unknown(ctx); if (attr == NULL) goto out_of_space; - self_or_null = sym_new_unknown(ctx); + self_or_null = _Py_uop_sym_new_unknown(ctx); if (self_or_null == NULL) goto out_of_space; stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = self_or_null; @@ -902,8 +902,8 @@ owner = stack_pointer[-1]; uint32_t dict_version = (uint32_t)this_instr->operand; (void)dict_version; - if (sym_is_const(owner)) { - PyObject *cnst = sym_get_const(owner); + if (_Py_uop_sym_is_const(owner)) { + PyObject *cnst = _Py_uop_sym_get_const(owner); if (PyModule_CheckExact(cnst)) { PyModuleObject *mod = (PyModuleObject *)cnst; PyObject *dict = mod->md_dict; @@ -925,23 +925,23 @@ owner = stack_pointer[-1]; uint16_t index = (uint16_t)this_instr->operand; (void)index; - OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); + OUT_OF_SPACE_IF_NULL(null = _Py_uop_sym_new_null(ctx)); attr = NULL; if (this_instr[-1].opcode == _NOP) { // Preceding _CHECK_ATTR_MODULE was removed: mod is const and dict is watched. - assert(sym_is_const(owner)); - PyModuleObject *mod = (PyModuleObject *)sym_get_const(owner); + assert(_Py_uop_sym_is_const(owner)); + PyModuleObject *mod = (PyModuleObject *)_Py_uop_sym_get_const(owner); assert(PyModule_CheckExact(mod)); PyObject *dict = mod->md_dict; PyObject *res = convert_global_to_const(this_instr, dict); if (res != NULL) { this_instr[-1].opcode = _POP_TOP; - OUT_OF_SPACE_IF_NULL(attr = sym_new_const(ctx, res)); + OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_const(ctx, res)); } } if (attr == NULL) { /* No conversion made. We don't know what `attr` is. */ - OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_not_null(ctx)); } stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = null; @@ -1024,7 +1024,7 @@ case _COMPARE_OP: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -1033,7 +1033,7 @@ case _COMPARE_OP_FLOAT: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -1042,7 +1042,7 @@ case _COMPARE_OP_INT: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -1051,7 +1051,7 @@ case _COMPARE_OP_STR: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -1060,7 +1060,7 @@ case _IS_OP: { _Py_UOpsSymType *b; - b = sym_new_unknown(ctx); + b = _Py_uop_sym_new_unknown(ctx); if (b == NULL) goto out_of_space; stack_pointer[-2] = b; stack_pointer += -1; @@ -1069,7 +1069,7 @@ case _CONTAINS_OP: { _Py_UOpsSymType *b; - b = sym_new_unknown(ctx); + b = _Py_uop_sym_new_unknown(ctx); if (b == NULL) goto out_of_space; stack_pointer[-2] = b; stack_pointer += -1; @@ -1079,9 +1079,9 @@ case _CHECK_EG_MATCH: { _Py_UOpsSymType *rest; _Py_UOpsSymType *match; - rest = sym_new_unknown(ctx); + rest = _Py_uop_sym_new_unknown(ctx); if (rest == NULL) goto out_of_space; - match = sym_new_unknown(ctx); + match = _Py_uop_sym_new_unknown(ctx); if (match == NULL) goto out_of_space; stack_pointer[-2] = rest; stack_pointer[-1] = match; @@ -1090,7 +1090,7 @@ case _CHECK_EXC_MATCH: { _Py_UOpsSymType *b; - b = sym_new_unknown(ctx); + b = _Py_uop_sym_new_unknown(ctx); if (b == NULL) goto out_of_space; stack_pointer[-1] = b; break; @@ -1102,7 +1102,7 @@ case _IS_NONE: { _Py_UOpsSymType *b; - b = sym_new_unknown(ctx); + b = _Py_uop_sym_new_unknown(ctx); if (b == NULL) goto out_of_space; stack_pointer[-1] = b; break; @@ -1110,7 +1110,7 @@ case _GET_LEN: { _Py_UOpsSymType *len_o; - len_o = sym_new_unknown(ctx); + len_o = _Py_uop_sym_new_unknown(ctx); if (len_o == NULL) goto out_of_space; stack_pointer[0] = len_o; stack_pointer += 1; @@ -1119,7 +1119,7 @@ case _MATCH_CLASS: { _Py_UOpsSymType *attrs; - attrs = sym_new_unknown(ctx); + attrs = _Py_uop_sym_new_unknown(ctx); if (attrs == NULL) goto out_of_space; stack_pointer[-3] = attrs; stack_pointer += -2; @@ -1128,7 +1128,7 @@ case _MATCH_MAPPING: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[0] = res; stack_pointer += 1; @@ -1137,7 +1137,7 @@ case _MATCH_SEQUENCE: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[0] = res; stack_pointer += 1; @@ -1146,7 +1146,7 @@ case _MATCH_KEYS: { _Py_UOpsSymType *values_or_none; - values_or_none = sym_new_unknown(ctx); + values_or_none = _Py_uop_sym_new_unknown(ctx); if (values_or_none == NULL) goto out_of_space; stack_pointer[0] = values_or_none; stack_pointer += 1; @@ -1155,7 +1155,7 @@ case _GET_ITER: { _Py_UOpsSymType *iter; - iter = sym_new_unknown(ctx); + iter = _Py_uop_sym_new_unknown(ctx); if (iter == NULL) goto out_of_space; stack_pointer[-1] = iter; break; @@ -1163,7 +1163,7 @@ case _GET_YIELD_FROM_ITER: { _Py_UOpsSymType *iter; - iter = sym_new_unknown(ctx); + iter = _Py_uop_sym_new_unknown(ctx); if (iter == NULL) goto out_of_space; stack_pointer[-1] = iter; break; @@ -1173,7 +1173,7 @@ case _FOR_ITER_TIER_TWO: { _Py_UOpsSymType *next; - next = sym_new_unknown(ctx); + next = _Py_uop_sym_new_unknown(ctx); if (next == NULL) goto out_of_space; stack_pointer[0] = next; stack_pointer += 1; @@ -1194,7 +1194,7 @@ case _ITER_NEXT_LIST: { _Py_UOpsSymType *next; - next = sym_new_unknown(ctx); + next = _Py_uop_sym_new_unknown(ctx); if (next == NULL) goto out_of_space; stack_pointer[0] = next; stack_pointer += 1; @@ -1213,7 +1213,7 @@ case _ITER_NEXT_TUPLE: { _Py_UOpsSymType *next; - next = sym_new_unknown(ctx); + next = _Py_uop_sym_new_unknown(ctx); if (next == NULL) goto out_of_space; stack_pointer[0] = next; stack_pointer += 1; @@ -1234,7 +1234,7 @@ _Py_UOpsSymType *iter; _Py_UOpsSymType *next; iter = stack_pointer[-1]; - OUT_OF_SPACE_IF_NULL(next = sym_new_known_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(next = _Py_uop_sym_new_type(ctx, &PyLong_Type)); (void)iter; stack_pointer[0] = next; stack_pointer += 1; @@ -1246,9 +1246,9 @@ case _BEFORE_ASYNC_WITH: { _Py_UOpsSymType *exit; _Py_UOpsSymType *res; - exit = sym_new_unknown(ctx); + exit = _Py_uop_sym_new_unknown(ctx); if (exit == NULL) goto out_of_space; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = exit; stack_pointer[0] = res; @@ -1259,9 +1259,9 @@ case _BEFORE_WITH: { _Py_UOpsSymType *exit; _Py_UOpsSymType *res; - exit = sym_new_unknown(ctx); + exit = _Py_uop_sym_new_unknown(ctx); if (exit == NULL) goto out_of_space; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = exit; stack_pointer[0] = res; @@ -1271,7 +1271,7 @@ case _WITH_EXCEPT_START: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[0] = res; stack_pointer += 1; @@ -1281,9 +1281,9 @@ case _PUSH_EXC_INFO: { _Py_UOpsSymType *prev_exc; _Py_UOpsSymType *new_exc; - prev_exc = sym_new_unknown(ctx); + prev_exc = _Py_uop_sym_new_unknown(ctx); if (prev_exc == NULL) goto out_of_space; - new_exc = sym_new_unknown(ctx); + new_exc = _Py_uop_sym_new_unknown(ctx); if (new_exc == NULL) goto out_of_space; stack_pointer[-1] = prev_exc; stack_pointer[0] = new_exc; @@ -1306,7 +1306,7 @@ owner = stack_pointer[-1]; PyObject *descr = (PyObject *)this_instr->operand; (void)descr; - OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_not_null(ctx)); self = owner; stack_pointer[-1] = attr; stack_pointer[0] = self; @@ -1321,7 +1321,7 @@ owner = stack_pointer[-1]; PyObject *descr = (PyObject *)this_instr->operand; (void)descr; - OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_not_null(ctx)); self = owner; stack_pointer[-1] = attr; stack_pointer[0] = self; @@ -1331,7 +1331,7 @@ case _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: { _Py_UOpsSymType *attr; - attr = sym_new_unknown(ctx); + attr = _Py_uop_sym_new_unknown(ctx); if (attr == NULL) goto out_of_space; stack_pointer[-1] = attr; break; @@ -1339,7 +1339,7 @@ case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: { _Py_UOpsSymType *attr; - attr = sym_new_unknown(ctx); + attr = _Py_uop_sym_new_unknown(ctx); if (attr == NULL) goto out_of_space; stack_pointer[-1] = attr; break; @@ -1356,7 +1356,7 @@ owner = stack_pointer[-1]; PyObject *descr = (PyObject *)this_instr->operand; (void)descr; - OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_not_null(ctx)); self = owner; stack_pointer[-1] = attr; stack_pointer[0] = self; @@ -1373,8 +1373,8 @@ _Py_UOpsSymType *callable; null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - sym_set_null(null); - sym_set_type(callable, &PyMethod_Type); + _Py_uop_sym_set_null(null); + _Py_uop_sym_set_type(callable, &PyMethod_Type); break; } @@ -1384,8 +1384,8 @@ _Py_UOpsSymType *self; callable = stack_pointer[-2 - oparg]; (void)callable; - OUT_OF_SPACE_IF_NULL(func = sym_new_known_notnull(ctx)); - OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(func = _Py_uop_sym_new_not_null(ctx)); + OUT_OF_SPACE_IF_NULL(self = _Py_uop_sym_new_not_null(ctx)); stack_pointer[-2 - oparg] = func; stack_pointer[-1 - oparg] = self; break; @@ -1401,7 +1401,7 @@ self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; uint32_t func_version = (uint32_t)this_instr->operand; - sym_set_type(callable, &PyFunction_Type); + _Py_uop_sym_set_type(callable, &PyFunction_Type); (void)self_or_null; (void)func_version; break; @@ -1428,7 +1428,7 @@ PyCodeObject *co = (PyCodeObject *)func->func_code; assert(self_or_null != NULL); assert(args != NULL); - if (sym_is_not_null(self_or_null)) { + if (_Py_uop_sym_is_not_null(self_or_null)) { // Bound method fiddling, same as _INIT_CALL_PY_EXACT_ARGS in VM args--; argcount++; @@ -1438,12 +1438,12 @@ // Can determine statically, so we interleave the new locals // and make the current stack the new locals. // This also sets up for true call inlining. - if (sym_is_known(self_or_null)) { + if (_Py_uop_sym_is_null(self_or_null) || _Py_uop_sym_is_not_null(self_or_null)) { localsplus_start = args; n_locals_already_filled = argcount; } OUT_OF_SPACE_IF_NULL(new_frame = - ctx_frame_new(ctx, co, localsplus_start, n_locals_already_filled, 0)); + _Py_uop_ctx_frame_new(ctx, co, localsplus_start, n_locals_already_filled, 0)); stack_pointer[-2 - oparg] = (_Py_UOpsSymType *)new_frame; stack_pointer += -1 - oparg; break; @@ -1464,7 +1464,7 @@ case _CALL_TYPE_1: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1473,7 +1473,7 @@ case _CALL_STR_1: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1482,7 +1482,7 @@ case _CALL_TUPLE_1: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1498,7 +1498,7 @@ case _CALL_BUILTIN_CLASS: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1507,7 +1507,7 @@ case _CALL_BUILTIN_O: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1516,7 +1516,7 @@ case _CALL_BUILTIN_FAST: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1525,7 +1525,7 @@ case _CALL_BUILTIN_FAST_WITH_KEYWORDS: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1534,7 +1534,7 @@ case _CALL_LEN: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1543,7 +1543,7 @@ case _CALL_ISINSTANCE: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1552,7 +1552,7 @@ case _CALL_METHOD_DESCRIPTOR_O: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1561,7 +1561,7 @@ case _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1570,7 +1570,7 @@ case _CALL_METHOD_DESCRIPTOR_NOARGS: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1579,7 +1579,7 @@ case _CALL_METHOD_DESCRIPTOR_FAST: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1596,7 +1596,7 @@ case _MAKE_FUNCTION: { _Py_UOpsSymType *func; - func = sym_new_unknown(ctx); + func = _Py_uop_sym_new_unknown(ctx); if (func == NULL) goto out_of_space; stack_pointer[-1] = func; break; @@ -1604,7 +1604,7 @@ case _SET_FUNCTION_ATTRIBUTE: { _Py_UOpsSymType *func; - func = sym_new_unknown(ctx); + func = _Py_uop_sym_new_unknown(ctx); if (func == NULL) goto out_of_space; stack_pointer[-2] = func; stack_pointer += -1; @@ -1613,7 +1613,7 @@ case _BUILD_SLICE: { _Py_UOpsSymType *slice; - slice = sym_new_unknown(ctx); + slice = _Py_uop_sym_new_unknown(ctx); if (slice == NULL) goto out_of_space; stack_pointer[-2 - ((oparg == 3) ? 1 : 0)] = slice; stack_pointer += -1 - ((oparg == 3) ? 1 : 0); @@ -1622,7 +1622,7 @@ case _CONVERT_VALUE: { _Py_UOpsSymType *result; - result = sym_new_unknown(ctx); + result = _Py_uop_sym_new_unknown(ctx); if (result == NULL) goto out_of_space; stack_pointer[-1] = result; break; @@ -1630,7 +1630,7 @@ case _FORMAT_SIMPLE: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -1638,7 +1638,7 @@ case _FORMAT_WITH_SPEC: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -1658,7 +1658,7 @@ case _BINARY_OP: { _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -1732,7 +1732,7 @@ case _LOAD_CONST_INLINE: { _Py_UOpsSymType *value; PyObject *ptr = (PyObject *)this_instr->operand; - OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(value = _Py_uop_sym_new_const(ctx, ptr)); stack_pointer[0] = value; stack_pointer += 1; break; @@ -1741,7 +1741,7 @@ case _LOAD_CONST_INLINE_BORROW: { _Py_UOpsSymType *value; PyObject *ptr = (PyObject *)this_instr->operand; - OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(value = _Py_uop_sym_new_const(ctx, ptr)); stack_pointer[0] = value; stack_pointer += 1; break; @@ -1751,8 +1751,8 @@ _Py_UOpsSymType *value; _Py_UOpsSymType *null; PyObject *ptr = (PyObject *)this_instr->operand; - OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); - OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); + OUT_OF_SPACE_IF_NULL(value = _Py_uop_sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(null = _Py_uop_sym_new_null(ctx)); stack_pointer[0] = value; stack_pointer[1] = null; stack_pointer += 2; @@ -1763,8 +1763,8 @@ _Py_UOpsSymType *value; _Py_UOpsSymType *null; PyObject *ptr = (PyObject *)this_instr->operand; - OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); - OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); + OUT_OF_SPACE_IF_NULL(value = _Py_uop_sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(null = _Py_uop_sym_new_null(ctx)); stack_pointer[0] = value; stack_pointer[1] = null; stack_pointer += 2; diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c new file mode 100644 index 000000000000000..1b7c88cf785fd26 --- /dev/null +++ b/Python/optimizer_symbols.c @@ -0,0 +1,332 @@ + +#include "Python.h" + +#include "cpython/optimizer.h" +#include "pycore_code.h" +#include "pycore_frame.h" +#include "pycore_optimizer.h" + +#include <stdbool.h> +#include <stdint.h> +#include <stddef.h> + +// Flags for below. +#define KNOWN 1 << 0 +#define TRUE_CONST 1 << 1 +#define IS_NULL 1 << 2 +#define NOT_NULL 1 << 3 + +#ifdef Py_DEBUG +static inline int get_lltrace(void) { + char *uop_debug = Py_GETENV("PYTHON_OPT_DEBUG"); + int lltrace = 0; + if (uop_debug != NULL && *uop_debug >= '0') { + lltrace = *uop_debug - '0'; // TODO: Parse an int and all that + } + return lltrace; +} +#define DPRINTF(level, ...) \ + if (get_lltrace() >= (level)) { printf(__VA_ARGS__); } +#else +#define DPRINTF(level, ...) +#endif + +// Takes a borrowed reference to const_val, turns that into a strong reference. +static _Py_UOpsSymType* +sym_new(_Py_UOpsAbstractInterpContext *ctx, PyObject *const_val) +{ + _Py_UOpsSymType *self = &ctx->t_arena.arena[ctx->t_arena.ty_curr_number]; + if (ctx->t_arena.ty_curr_number >= ctx->t_arena.ty_max_number) { + OPT_STAT_INC(optimizer_failure_reason_no_memory); + DPRINTF(1, "out of space for symbolic expression type\n"); + return NULL; + } + ctx->t_arena.ty_curr_number++; + self->const_val = NULL; + self->typ = NULL; + self->flags = 0; + + if (const_val != NULL) { + self->const_val = Py_NewRef(const_val); + } + + return self; +} + +static inline void +sym_set_flag(_Py_UOpsSymType *sym, int flag) +{ + sym->flags |= flag; +} + +static inline bool +sym_has_flag(_Py_UOpsSymType *sym, int flag) +{ + return (sym->flags & flag) != 0; +} + +bool +_Py_uop_sym_is_not_null(_Py_UOpsSymType *sym) +{ + return (sym->flags & (IS_NULL | NOT_NULL)) == NOT_NULL; +} + +bool +_Py_uop_sym_is_null(_Py_UOpsSymType *sym) +{ + return (sym->flags & (IS_NULL | NOT_NULL)) == IS_NULL; +} + +bool +_Py_uop_sym_is_const(_Py_UOpsSymType *sym) +{ + return (sym->flags & TRUE_CONST) != 0; +} + +PyObject * +_Py_uop_sym_get_const(_Py_UOpsSymType *sym) +{ + assert(_Py_uop_sym_is_const(sym)); + assert(sym->const_val); + return sym->const_val; +} + +void +_Py_uop_sym_set_type(_Py_UOpsSymType *sym, PyTypeObject *tp) +{ + assert(PyType_Check(tp)); + sym->typ = tp; + sym_set_flag(sym, KNOWN); + sym_set_flag(sym, NOT_NULL); +} + +void +_Py_uop_sym_set_null(_Py_UOpsSymType *sym) +{ + sym_set_flag(sym, IS_NULL); + sym_set_flag(sym, KNOWN); +} + + +_Py_UOpsSymType * +_Py_uop_sym_new_unknown(_Py_UOpsAbstractInterpContext *ctx) +{ + return sym_new(ctx,NULL); +} + +_Py_UOpsSymType * +_Py_uop_sym_new_not_null(_Py_UOpsAbstractInterpContext *ctx) +{ + _Py_UOpsSymType *res = _Py_uop_sym_new_unknown(ctx); + if (res == NULL) { + return NULL; + } + sym_set_flag(res, KNOWN); + sym_set_flag(res, NOT_NULL); + return res; +} + +_Py_UOpsSymType * +_Py_uop_sym_new_type(_Py_UOpsAbstractInterpContext *ctx, + PyTypeObject *typ) +{ + _Py_UOpsSymType *res = sym_new(ctx,NULL); + if (res == NULL) { + return NULL; + } + _Py_uop_sym_set_type(res, typ); + return res; +} + +// Takes a borrowed reference to const_val. +_Py_UOpsSymType* +_Py_uop_sym_new_const(_Py_UOpsAbstractInterpContext *ctx, PyObject *const_val) +{ + assert(const_val != NULL); + _Py_UOpsSymType *temp = sym_new( + ctx, + const_val + ); + if (temp == NULL) { + return NULL; + } + _Py_uop_sym_set_type(temp, Py_TYPE(const_val)); + sym_set_flag(temp, TRUE_CONST); + sym_set_flag(temp, KNOWN); + sym_set_flag(temp, NOT_NULL); + return temp; +} + +_Py_UOpsSymType* +_Py_uop_sym_new_null(_Py_UOpsAbstractInterpContext *ctx) +{ + _Py_UOpsSymType *null_sym = _Py_uop_sym_new_unknown(ctx); + if (null_sym == NULL) { + return NULL; + } + _Py_uop_sym_set_null(null_sym); + return null_sym; +} + +bool +_Py_uop_sym_matches_type(_Py_UOpsSymType *sym, PyTypeObject *typ) +{ + assert(typ == NULL || PyType_Check(typ)); + if (!sym_has_flag(sym, KNOWN)) { + return false; + } + return sym->typ == typ; +} + +// 0 on success, -1 on error. +_Py_UOpsAbstractFrame * +_Py_uop_ctx_frame_new( + _Py_UOpsAbstractInterpContext *ctx, + PyCodeObject *co, + _Py_UOpsSymType **localsplus_start, + int n_locals_already_filled, + int curr_stackentries +) +{ + assert(ctx->curr_frame_depth < MAX_ABSTRACT_FRAME_DEPTH); + _Py_UOpsAbstractFrame *frame = &ctx->frames[ctx->curr_frame_depth]; + + frame->stack_len = co->co_stacksize; + frame->locals_len = co->co_nlocalsplus; + + frame->locals = localsplus_start; + frame->stack = frame->locals + co->co_nlocalsplus; + frame->stack_pointer = frame->stack + curr_stackentries; + ctx->n_consumed = localsplus_start + (co->co_nlocalsplus + co->co_stacksize); + if (ctx->n_consumed >= ctx->limit) { + return NULL; + } + + + // Initialize with the initial state of all local variables + for (int i = n_locals_already_filled; i < co->co_nlocalsplus; i++) { + _Py_UOpsSymType *local = _Py_uop_sym_new_unknown(ctx); + if (local == NULL) { + return NULL; + } + frame->locals[i] = local; + } + + + // Initialize the stack as well + for (int i = 0; i < curr_stackentries; i++) { + _Py_UOpsSymType *stackvar = _Py_uop_sym_new_unknown(ctx); + if (stackvar == NULL) { + return NULL; + } + frame->stack[i] = stackvar; + } + + return frame; +} + +void +_Py_uop_abstractcontext_fini(_Py_UOpsAbstractInterpContext *ctx) +{ + if (ctx == NULL) { + return; + } + ctx->curr_frame_depth = 0; + int tys = ctx->t_arena.ty_curr_number; + for (int i = 0; i < tys; i++) { + Py_CLEAR(ctx->t_arena.arena[i].const_val); + } +} + +int +_Py_uop_abstractcontext_init( + _Py_UOpsAbstractInterpContext *ctx +) +{ + ctx->limit = ctx->locals_and_stack + MAX_ABSTRACT_INTERP_SIZE; + ctx->n_consumed = ctx->locals_and_stack; +#ifdef Py_DEBUG // Aids debugging a little. There should never be NULL in the abstract interpreter. + for (int i = 0 ; i < MAX_ABSTRACT_INTERP_SIZE; i++) { + ctx->locals_and_stack[i] = NULL; + } +#endif + + // Setup the arena for sym expressions. + ctx->t_arena.ty_curr_number = 0; + ctx->t_arena.ty_max_number = TY_ARENA_SIZE; + + // Frame setup + ctx->curr_frame_depth = 0; + + return 0; +} + +int +_Py_uop_ctx_frame_pop( + _Py_UOpsAbstractInterpContext *ctx +) +{ + _Py_UOpsAbstractFrame *frame = ctx->frame; + + ctx->n_consumed = frame->locals; + ctx->curr_frame_depth--; + assert(ctx->curr_frame_depth >= 1); + ctx->frame = &ctx->frames[ctx->curr_frame_depth - 1]; + + return 0; +} + +#define TEST_PREDICATE(PRED, MSG) \ +do { \ + if (!(PRED)) { \ + PyErr_SetString( \ + PyExc_AssertionError, \ + (MSG)); \ + goto fail; \ + } \ +} while (0) + +/* +static _Py_UOpsSymType * +make_bottom(_Py_UOpsAbstractInterpContext *ctx) +{ + _Py_UOpsSymType *sym = _Py_uop_sym_new_unknown(ctx); + _Py_uop_sym_set_null(sym); + _Py_uop_sym_set_type(sym, &PyLong_Type); + return sym; +}*/ + +PyObject * +_Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) +{ + _Py_UOpsAbstractInterpContext context; + _Py_UOpsAbstractInterpContext *ctx = &context; + _Py_uop_abstractcontext_init(ctx); + + _Py_UOpsSymType *top = _Py_uop_sym_new_unknown(ctx); + if (top == NULL) { + return NULL; + } + TEST_PREDICATE(!_Py_uop_sym_is_null(top), "unknown is NULL"); + TEST_PREDICATE(!_Py_uop_sym_is_not_null(top), "unknown is not NULL"); + TEST_PREDICATE(!_Py_uop_sym_is_const(top), "unknown is a constant"); + // TEST_PREDICATE(_Py_uop_sym_get_const(top) == NULL, "unknown as constant is not NULL"); + + // _Py_UOpsSymType *bottom = make_bottom(ctx); + // TEST_PREDICATE(_Py_uop_sym_is_null(bottom), "bottom is NULL is not true"); + // TEST_PREDICATE(_Py_uop_sym_is_not_null(bottom), "bottom is not NULL is not true"); + // TEST_PREDICATE(_Py_uop_sym_is_const(bottom), "bottom is a constant is not true"); + + _Py_UOpsSymType *int_type = _Py_uop_sym_new_type(ctx, &PyLong_Type); + TEST_PREDICATE(_Py_uop_sym_matches_type(int_type, &PyLong_Type), "inconsistent type"); + _Py_uop_sym_set_type(int_type, &PyLong_Type); + TEST_PREDICATE(_Py_uop_sym_matches_type(int_type, &PyLong_Type), "inconsistent type"); + _Py_uop_sym_set_type(int_type, &PyFloat_Type); + // TEST_PREDICATE(_Py_uop_sym_matches_type(int_type, &PyLong_Type), "bottom doesn't match int"); + + _Py_uop_abstractcontext_fini(ctx); + Py_RETURN_NONE; +fail: + _Py_uop_abstractcontext_fini(ctx); + return NULL; +} diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index e9b81cf4c7d6531..0f212ecb528ac4e 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -740,6 +740,4 @@ Modules/expat/xmlrole.c - error - ## other Modules/_io/_iomodule.c - _PyIO_Module - Modules/_sqlite/module.c - _sqlite3module - -Python/optimizer_analysis.c - _Py_UOpsAbstractFrame_Type - -Python/optimizer_analysis.c - _Py_UOpsAbstractInterpContext_Type - Modules/clinic/md5module.c.h _md5_md5 _keywords - diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index aa3f4ecb11d58ba..f110a74e3ad3514 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -87,14 +87,14 @@ def emit_default(out: CWriter, uop: Uop) -> None: if var.name != "unused" and not var.peek: if var.is_array(): out.emit(f"for (int _i = {var.size}; --_i >= 0;) {{\n") - out.emit(f"{var.name}[_i] = sym_new_unknown(ctx);\n") + out.emit(f"{var.name}[_i] = _Py_uop_sym_new_unknown(ctx);\n") out.emit(f"if ({var.name}[_i] == NULL) goto out_of_space;\n") out.emit("}\n") elif var.name == "null": - out.emit(f"{var.name} = sym_new_null(ctx);\n") + out.emit(f"{var.name} = _Py_uop_sym_new_null(ctx);\n") out.emit(f"if ({var.name} == NULL) goto out_of_space;\n") else: - out.emit(f"{var.name} = sym_new_unknown(ctx);\n") + out.emit(f"{var.name} = _Py_uop_sym_new_unknown(ctx);\n") out.emit(f"if ({var.name} == NULL) goto out_of_space;\n") From 6ecfcfe8946cf701c6c7018e30ae54d8b7a7ac2a Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Tue, 27 Feb 2024 13:25:02 +0000 Subject: [PATCH 476/507] GH-115816: Assorted naming and formatting changes to improve maintainability. (GH-115987) * Rename _Py_UOpsAbstractInterpContext to _Py_UOpsContext and _Py_UOpsSymType to _Py_UopsSymbol. * #define shortened form of _Py_uop_... names for improved readability. --- Include/internal/pycore_optimizer.h | 73 ++- Lib/test/test_generated_cases.py | 12 +- Modules/_testinternalcapi.c | 2 +- Python/optimizer_analysis.c | 25 +- Python/optimizer_bytecodes.c | 213 ++++---- Python/optimizer_cases.c.h | 538 +++++++++---------- Python/optimizer_symbols.c | 105 ++-- Tools/cases_generator/optimizer_generator.py | 6 +- 8 files changed, 498 insertions(+), 476 deletions(-) diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 267cbf1265c420a..425bd693fac53da 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -28,7 +28,7 @@ extern PyTypeObject _PyUOpOptimizer_Type; /* Symbols */ -struct _Py_UOpsSymType { +struct _Py_UopsSymbol { int flags; PyTypeObject *typ; // constant propagated value (might be NULL) @@ -38,23 +38,21 @@ struct _Py_UOpsSymType { // Holds locals, stack, locals, stack ... co_consts (in that order) #define MAX_ABSTRACT_INTERP_SIZE 4096 -#define OVERALLOCATE_FACTOR 5 - -#define TY_ARENA_SIZE (UOP_MAX_TRACE_LENGTH * OVERALLOCATE_FACTOR) +#define TY_ARENA_SIZE (UOP_MAX_TRACE_LENGTH * 5) // Need extras for root frame and for overflow frame (see TRACE_STACK_PUSH()) #define MAX_ABSTRACT_FRAME_DEPTH (TRACE_STACK_SIZE + 2) -typedef struct _Py_UOpsSymType _Py_UOpsSymType; +typedef struct _Py_UopsSymbol _Py_UopsSymbol; struct _Py_UOpsAbstractFrame { // Max stacklen int stack_len; int locals_len; - _Py_UOpsSymType **stack_pointer; - _Py_UOpsSymType **stack; - _Py_UOpsSymType **locals; + _Py_UopsSymbol **stack_pointer; + _Py_UopsSymbol **stack; + _Py_UopsSymbol **locals; }; typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame; @@ -62,10 +60,10 @@ typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame; typedef struct ty_arena { int ty_curr_number; int ty_max_number; - _Py_UOpsSymType arena[TY_ARENA_SIZE]; + _Py_UopsSymbol arena[TY_ARENA_SIZE]; } ty_arena; -struct _Py_UOpsAbstractInterpContext { +struct _Py_UOpsContext { PyObject_HEAD // The current "executing" frame. _Py_UOpsAbstractFrame *frame; @@ -75,40 +73,39 @@ struct _Py_UOpsAbstractInterpContext { // Arena for the symbolic types. ty_arena t_arena; - _Py_UOpsSymType **n_consumed; - _Py_UOpsSymType **limit; - _Py_UOpsSymType *locals_and_stack[MAX_ABSTRACT_INTERP_SIZE]; + _Py_UopsSymbol **n_consumed; + _Py_UopsSymbol **limit; + _Py_UopsSymbol *locals_and_stack[MAX_ABSTRACT_INTERP_SIZE]; }; -typedef struct _Py_UOpsAbstractInterpContext _Py_UOpsAbstractInterpContext; - -extern bool _Py_uop_sym_is_null(_Py_UOpsSymType *sym); -extern bool _Py_uop_sym_is_not_null(_Py_UOpsSymType *sym); -extern bool _Py_uop_sym_is_const(_Py_UOpsSymType *sym); -extern PyObject *_Py_uop_sym_get_const(_Py_UOpsSymType *sym); -extern _Py_UOpsSymType *_Py_uop_sym_new_unknown(_Py_UOpsAbstractInterpContext *ctx); -extern _Py_UOpsSymType *_Py_uop_sym_new_not_null(_Py_UOpsAbstractInterpContext *ctx); -extern _Py_UOpsSymType *_Py_uop_sym_new_type( - _Py_UOpsAbstractInterpContext *ctx, PyTypeObject *typ); -extern _Py_UOpsSymType *_Py_uop_sym_new_const(_Py_UOpsAbstractInterpContext *ctx, PyObject *const_val); -extern _Py_UOpsSymType *_Py_uop_sym_new_null(_Py_UOpsAbstractInterpContext *ctx); -extern bool _Py_uop_sym_matches_type(_Py_UOpsSymType *sym, PyTypeObject *typ); -extern void _Py_uop_sym_set_null(_Py_UOpsSymType *sym); -extern void _Py_uop_sym_set_type(_Py_UOpsSymType *sym, PyTypeObject *tp); - -extern int _Py_uop_abstractcontext_init(_Py_UOpsAbstractInterpContext *ctx); -extern void _Py_uop_abstractcontext_fini(_Py_UOpsAbstractInterpContext *ctx); - -extern _Py_UOpsAbstractFrame *_Py_uop_ctx_frame_new( - _Py_UOpsAbstractInterpContext *ctx, +typedef struct _Py_UOpsContext _Py_UOpsContext; + +extern bool _Py_uop_sym_is_null(_Py_UopsSymbol *sym); +extern bool _Py_uop_sym_is_not_null(_Py_UopsSymbol *sym); +extern bool _Py_uop_sym_is_const(_Py_UopsSymbol *sym); +extern PyObject *_Py_uop_sym_get_const(_Py_UopsSymbol *sym); +extern _Py_UopsSymbol *_Py_uop_sym_new_unknown(_Py_UOpsContext *ctx); +extern _Py_UopsSymbol *_Py_uop_sym_new_not_null(_Py_UOpsContext *ctx); +extern _Py_UopsSymbol *_Py_uop_sym_new_type( + _Py_UOpsContext *ctx, PyTypeObject *typ); +extern _Py_UopsSymbol *_Py_uop_sym_new_const(_Py_UOpsContext *ctx, PyObject *const_val); +extern _Py_UopsSymbol *_Py_uop_sym_new_null(_Py_UOpsContext *ctx); +extern bool _Py_uop_sym_matches_type(_Py_UopsSymbol *sym, PyTypeObject *typ); +extern void _Py_uop_sym_set_null(_Py_UopsSymbol *sym); +extern void _Py_uop_sym_set_type(_Py_UopsSymbol *sym, PyTypeObject *tp); + +extern int _Py_uop_abstractcontext_init(_Py_UOpsContext *ctx); +extern void _Py_uop_abstractcontext_fini(_Py_UOpsContext *ctx); + +extern _Py_UOpsAbstractFrame *_Py_uop_frame_new( + _Py_UOpsContext *ctx, PyCodeObject *co, - _Py_UOpsSymType **localsplus_start, + _Py_UopsSymbol **localsplus_start, int n_locals_already_filled, int curr_stackentries); -extern int _Py_uop_ctx_frame_pop(_Py_UOpsAbstractInterpContext *ctx); +extern int _Py_uop_frame_pop(_Py_UOpsContext *ctx); -PyAPI_FUNC(PyObject *) -_Py_uop_symbols_test(PyObject *self, PyObject *ignored); +PyAPI_FUNC(PyObject *) _Py_uop_symbols_test(PyObject *self, PyObject *ignored); #ifdef __cplusplus } diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 601775817043fe1..18bf8ab29148c47 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -890,8 +890,8 @@ def test_overridden_abstract_args(self): """ output = """ case OP: { - _Py_UOpsSymType *arg1; - _Py_UOpsSymType *out; + _Py_UopsSymbol *arg1; + _Py_UopsSymbol *out; arg1 = stack_pointer[-1]; eggs(); stack_pointer[-1] = out; @@ -899,7 +899,7 @@ def test_overridden_abstract_args(self): } case OP2: { - _Py_UOpsSymType *out; + _Py_UopsSymbol *out; out = _Py_uop_sym_new_unknown(ctx); if (out == NULL) goto out_of_space; stack_pointer[-1] = out; @@ -924,7 +924,7 @@ def test_no_overridden_case(self): """ output = """ case OP: { - _Py_UOpsSymType *out; + _Py_UopsSymbol *out; out = _Py_uop_sym_new_unknown(ctx); if (out == NULL) goto out_of_space; stack_pointer[-1] = out; @@ -932,8 +932,8 @@ def test_no_overridden_case(self): } case OP2: { - _Py_UOpsSymType *arg1; - _Py_UOpsSymType *out; + _Py_UopsSymbol *arg1; + _Py_UopsSymbol *out; arg1 = stack_pointer[-1]; stack_pointer[-1] = out; break; diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 9c6970d48ae6f7e..db8817418950b92 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -24,7 +24,7 @@ #include "pycore_interp.h" // _PyInterpreterState_GetConfigCopy() #include "pycore_long.h" // _PyLong_Sign() #include "pycore_object.h" // _PyObject_IsFreed() -#include "pycore_optimizer.h" // _Py_UOpsSymType, etc. +#include "pycore_optimizer.h" // _Py_UopsSymbol, etc. #include "pycore_pathconfig.h" // _PyPathConfig_ClearGlobal() #include "pycore_pyerrors.h" // _PyErr_ChainExceptions1() #include "pycore_pystate.h" // _PyThreadState_GET() diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 6b2aec8385824d0..b29a00c941e996a 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -282,6 +282,23 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, } while (0); +/* Shortened forms for convenience, used in optimizer_bytecodes.c */ +#define sym_is_not_null _Py_uop_sym_is_not_null +#define sym_is_const _Py_uop_sym_is_const +#define sym_get_const _Py_uop_sym_get_const +#define sym_new_unknown _Py_uop_sym_new_unknown +#define sym_new_not_null _Py_uop_sym_new_not_null +#define sym_new_type _Py_uop_sym_new_type +#define sym_is_null _Py_uop_sym_is_null +#define sym_new_const _Py_uop_sym_new_const +#define sym_new_null _Py_uop_sym_new_null +#define sym_matches_type _Py_uop_sym_matches_type +#define sym_set_null _Py_uop_sym_set_null +#define sym_set_type _Py_uop_sym_set_type +#define frame_new _Py_uop_frame_new +#define frame_pop _Py_uop_frame_pop + + /* 1 for success, 0 for not ready, cannot error at the moment. */ static int optimize_uops( @@ -293,13 +310,13 @@ optimize_uops( ) { - _Py_UOpsAbstractInterpContext context; - _Py_UOpsAbstractInterpContext *ctx = &context; + _Py_UOpsContext context; + _Py_UOpsContext *ctx = &context; if (_Py_uop_abstractcontext_init(ctx) < 0) { goto out_of_space; } - _Py_UOpsAbstractFrame *frame = _Py_uop_ctx_frame_new(ctx, co, ctx->n_consumed, 0, curr_stacklen); + _Py_UOpsAbstractFrame *frame = _Py_uop_frame_new(ctx, co, ctx->n_consumed, 0, curr_stacklen); if (frame == NULL) { return -1; } @@ -313,7 +330,7 @@ optimize_uops( int oparg = this_instr->oparg; uint32_t opcode = this_instr->opcode; - _Py_UOpsSymType **stack_pointer = ctx->frame->stack_pointer; + _Py_UopsSymbol **stack_pointer = ctx->frame->stack_pointer; DPRINTF(3, "Abstract interpreting %s:%d ", _PyUOpName(opcode), diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index e6e41f8968eaf36..68737389c66b679 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -1,29 +1,46 @@ #include "Python.h" +#include "pycore_optimizer.h" #include "pycore_uops.h" #include "pycore_uop_ids.h" #include "internal/pycore_moduleobject.h" #define op(name, ...) /* NAME is ignored */ -typedef struct _Py_UOpsSymType _Py_UOpsSymType; -typedef struct _Py_UOpsAbstractInterpContext _Py_UOpsAbstractInterpContext; +typedef struct _Py_UopsSymbol _Py_UopsSymbol; +typedef struct _Py_UOpsContext _Py_UOpsContext; typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame; +/* Shortened forms for convenience */ +#define sym_is_not_null _Py_uop_sym_is_not_null +#define sym_is_const _Py_uop_sym_is_const +#define sym_get_const _Py_uop_sym_get_const +#define sym_new_unknown _Py_uop_sym_new_unknown +#define sym_new_not_null _Py_uop_sym_new_not_null +#define sym_new_type _Py_uop_sym_new_type +#define sym_is_null _Py_uop_sym_is_null +#define sym_new_const _Py_uop_sym_new_const +#define sym_new_null _Py_uop_sym_new_null +#define sym_matches_type _Py_uop_sym_matches_type +#define sym_set_null _Py_uop_sym_set_null +#define sym_set_type _Py_uop_sym_set_type +#define frame_new _Py_uop_frame_new +#define frame_pop _Py_uop_frame_pop + static int dummy_func(void) { PyCodeObject *code; int oparg; - _Py_UOpsSymType *flag; - _Py_UOpsSymType *left; - _Py_UOpsSymType *right; - _Py_UOpsSymType *value; - _Py_UOpsSymType *res; - _Py_UOpsSymType *iter; - _Py_UOpsSymType *top; - _Py_UOpsSymType *bottom; + _Py_UopsSymbol *flag; + _Py_UopsSymbol *left; + _Py_UopsSymbol *right; + _Py_UopsSymbol *value; + _Py_UopsSymbol *res; + _Py_UopsSymbol *iter; + _Py_UopsSymbol *top; + _Py_UopsSymbol *bottom; _Py_UOpsAbstractFrame *frame; - _Py_UOpsAbstractInterpContext *ctx; + _Py_UOpsContext *ctx; _PyUOpInstruction *this_instr; _PyBloomFilter *dependencies; int modified; @@ -33,7 +50,7 @@ dummy_func(void) { op(_LOAD_FAST_CHECK, (-- value)) { value = GETLOCAL(oparg); // We guarantee this will error - just bail and don't optimize it. - if (_Py_uop_sym_is_null(value)) { + if (sym_is_null(value)) { goto out_of_space; } } @@ -44,8 +61,8 @@ dummy_func(void) { op(_LOAD_FAST_AND_CLEAR, (-- value)) { value = GETLOCAL(oparg); - _Py_UOpsSymType *temp; - OUT_OF_SPACE_IF_NULL(temp = _Py_uop_sym_new_null(ctx)); + _Py_UopsSymbol *temp; + OUT_OF_SPACE_IF_NULL(temp = sym_new_null(ctx)); GETLOCAL(oparg) = temp; } @@ -54,147 +71,147 @@ dummy_func(void) { } op(_PUSH_NULL, (-- res)) { - res = _Py_uop_sym_new_null(ctx); + res = sym_new_null(ctx); if (res == NULL) { goto out_of_space; }; } op(_GUARD_BOTH_INT, (left, right -- left, right)) { - if (_Py_uop_sym_matches_type(left, &PyLong_Type) && - _Py_uop_sym_matches_type(right, &PyLong_Type)) { + if (sym_matches_type(left, &PyLong_Type) && + sym_matches_type(right, &PyLong_Type)) { REPLACE_OP(this_instr, _NOP, 0, 0); } - _Py_uop_sym_set_type(left, &PyLong_Type); - _Py_uop_sym_set_type(right, &PyLong_Type); + sym_set_type(left, &PyLong_Type); + sym_set_type(right, &PyLong_Type); } op(_GUARD_BOTH_FLOAT, (left, right -- left, right)) { - if (_Py_uop_sym_matches_type(left, &PyFloat_Type) && - _Py_uop_sym_matches_type(right, &PyFloat_Type)) { + if (sym_matches_type(left, &PyFloat_Type) && + sym_matches_type(right, &PyFloat_Type)) { REPLACE_OP(this_instr, _NOP, 0 ,0); } - _Py_uop_sym_set_type(left, &PyFloat_Type); - _Py_uop_sym_set_type(right, &PyFloat_Type); + sym_set_type(left, &PyFloat_Type); + sym_set_type(right, &PyFloat_Type); } op(_GUARD_BOTH_UNICODE, (left, right -- left, right)) { - if (_Py_uop_sym_matches_type(left, &PyUnicode_Type) && - _Py_uop_sym_matches_type(right, &PyUnicode_Type)) { + if (sym_matches_type(left, &PyUnicode_Type) && + sym_matches_type(right, &PyUnicode_Type)) { REPLACE_OP(this_instr, _NOP, 0 ,0); } - _Py_uop_sym_set_type(left, &PyUnicode_Type); - _Py_uop_sym_set_type(right, &PyUnicode_Type); + sym_set_type(left, &PyUnicode_Type); + sym_set_type(right, &PyUnicode_Type); } op(_BINARY_OP_ADD_INT, (left, right -- res)) { - if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { - assert(PyLong_CheckExact(_Py_uop_sym_get_const(left))); - assert(PyLong_CheckExact(_Py_uop_sym_get_const(right))); - PyObject *temp = _PyLong_Add((PyLongObject *)_Py_uop_sym_get_const(left), - (PyLongObject *)_Py_uop_sym_get_const(right)); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyLong_CheckExact(sym_get_const(left))); + assert(PyLong_CheckExact(sym_get_const(right))); + PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(left), + (PyLongObject *)sym_get_const(right)); if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_const(ctx, temp)); + OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } else { - OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyLong_Type)); } } op(_BINARY_OP_SUBTRACT_INT, (left, right -- res)) { - if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { - assert(PyLong_CheckExact(_Py_uop_sym_get_const(left))); - assert(PyLong_CheckExact(_Py_uop_sym_get_const(right))); - PyObject *temp = _PyLong_Subtract((PyLongObject *)_Py_uop_sym_get_const(left), - (PyLongObject *)_Py_uop_sym_get_const(right)); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyLong_CheckExact(sym_get_const(left))); + assert(PyLong_CheckExact(sym_get_const(right))); + PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(left), + (PyLongObject *)sym_get_const(right)); if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_const(ctx, temp)); + OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } else { - OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyLong_Type)); } } op(_BINARY_OP_MULTIPLY_INT, (left, right -- res)) { - if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { - assert(PyLong_CheckExact(_Py_uop_sym_get_const(left))); - assert(PyLong_CheckExact(_Py_uop_sym_get_const(right))); - PyObject *temp = _PyLong_Multiply((PyLongObject *)_Py_uop_sym_get_const(left), - (PyLongObject *)_Py_uop_sym_get_const(right)); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyLong_CheckExact(sym_get_const(left))); + assert(PyLong_CheckExact(sym_get_const(right))); + PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(left), + (PyLongObject *)sym_get_const(right)); if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_const(ctx, temp)); + OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } else { - OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyLong_Type)); } } op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { - if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { - assert(PyFloat_CheckExact(_Py_uop_sym_get_const(left))); - assert(PyFloat_CheckExact(_Py_uop_sym_get_const(right))); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyFloat_CheckExact(sym_get_const(left))); + assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(left)) + - PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(right))); + PyFloat_AS_DOUBLE(sym_get_const(left)) + + PyFloat_AS_DOUBLE(sym_get_const(right))); if (temp == NULL) { goto error; } - res = _Py_uop_sym_new_const(ctx, temp); + res = sym_new_const(ctx, temp); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } else { - OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyFloat_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyFloat_Type)); } } op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) { - if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { - assert(PyFloat_CheckExact(_Py_uop_sym_get_const(left))); - assert(PyFloat_CheckExact(_Py_uop_sym_get_const(right))); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyFloat_CheckExact(sym_get_const(left))); + assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(left)) - - PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(right))); + PyFloat_AS_DOUBLE(sym_get_const(left)) - + PyFloat_AS_DOUBLE(sym_get_const(right))); if (temp == NULL) { goto error; } - res = _Py_uop_sym_new_const(ctx, temp); + res = sym_new_const(ctx, temp); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } else { - OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyFloat_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyFloat_Type)); } } op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { - if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { - assert(PyFloat_CheckExact(_Py_uop_sym_get_const(left))); - assert(PyFloat_CheckExact(_Py_uop_sym_get_const(right))); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyFloat_CheckExact(sym_get_const(left))); + assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(left)) * - PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(right))); + PyFloat_AS_DOUBLE(sym_get_const(left)) * + PyFloat_AS_DOUBLE(sym_get_const(right))); if (temp == NULL) { goto error; } - res = _Py_uop_sym_new_const(ctx, temp); + res = sym_new_const(ctx, temp); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } else { - OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyFloat_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyFloat_Type)); } } @@ -205,21 +222,21 @@ dummy_func(void) { } op(_LOAD_CONST_INLINE, (ptr/4 -- value)) { - OUT_OF_SPACE_IF_NULL(value = _Py_uop_sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); } op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) { - OUT_OF_SPACE_IF_NULL(value = _Py_uop_sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); } op(_LOAD_CONST_INLINE_WITH_NULL, (ptr/4 -- value, null)) { - OUT_OF_SPACE_IF_NULL(value = _Py_uop_sym_new_const(ctx, ptr)); - OUT_OF_SPACE_IF_NULL(null = _Py_uop_sym_new_null(ctx)); + OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); } op(_LOAD_CONST_INLINE_BORROW_WITH_NULL, (ptr/4 -- value, null)) { - OUT_OF_SPACE_IF_NULL(value = _Py_uop_sym_new_const(ctx, ptr)); - OUT_OF_SPACE_IF_NULL(null = _Py_uop_sym_new_null(ctx)); + OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); } @@ -240,8 +257,8 @@ dummy_func(void) { op(_CHECK_ATTR_MODULE, (dict_version/2, owner -- owner)) { (void)dict_version; - if (_Py_uop_sym_is_const(owner)) { - PyObject *cnst = _Py_uop_sym_get_const(owner); + if (sym_is_const(owner)) { + PyObject *cnst = sym_get_const(owner); if (PyModule_CheckExact(cnst)) { PyModuleObject *mod = (PyModuleObject *)cnst; PyObject *dict = mod->md_dict; @@ -257,23 +274,23 @@ dummy_func(void) { op(_LOAD_ATTR_MODULE, (index/1, owner -- attr, null if (oparg & 1))) { (void)index; - OUT_OF_SPACE_IF_NULL(null = _Py_uop_sym_new_null(ctx)); + OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); attr = NULL; if (this_instr[-1].opcode == _NOP) { // Preceding _CHECK_ATTR_MODULE was removed: mod is const and dict is watched. - assert(_Py_uop_sym_is_const(owner)); - PyModuleObject *mod = (PyModuleObject *)_Py_uop_sym_get_const(owner); + assert(sym_is_const(owner)); + PyModuleObject *mod = (PyModuleObject *)sym_get_const(owner); assert(PyModule_CheckExact(mod)); PyObject *dict = mod->md_dict; PyObject *res = convert_global_to_const(this_instr, dict); if (res != NULL) { this_instr[-1].opcode = _POP_TOP; - OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_const(ctx, res)); + OUT_OF_SPACE_IF_NULL(attr = sym_new_const(ctx, res)); } } if (attr == NULL) { /* No conversion made. We don't know what `attr` is. */ - OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_not_null(ctx)); + OUT_OF_SPACE_IF_NULL(attr = sym_new_not_null(ctx)); } } @@ -297,38 +314,38 @@ dummy_func(void) { op(_LOAD_ATTR_METHOD_WITH_VALUES, (descr/4, owner -- attr, self if (1))) { (void)descr; - OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_not_null(ctx)); + OUT_OF_SPACE_IF_NULL(attr = sym_new_not_null(ctx)); self = owner; } op(_LOAD_ATTR_METHOD_NO_DICT, (descr/4, owner -- attr, self if (1))) { (void)descr; - OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_not_null(ctx)); + OUT_OF_SPACE_IF_NULL(attr = sym_new_not_null(ctx)); self = owner; } op(_LOAD_ATTR_METHOD_LAZY_DICT, (descr/4, owner -- attr, self if (1))) { (void)descr; - OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_not_null(ctx)); + OUT_OF_SPACE_IF_NULL(attr = sym_new_not_null(ctx)); self = owner; } op(_INIT_CALL_BOUND_METHOD_EXACT_ARGS, (callable, unused, unused[oparg] -- func, self, unused[oparg])) { (void)callable; - OUT_OF_SPACE_IF_NULL(func = _Py_uop_sym_new_not_null(ctx)); - OUT_OF_SPACE_IF_NULL(self = _Py_uop_sym_new_not_null(ctx)); + OUT_OF_SPACE_IF_NULL(func = sym_new_not_null(ctx)); + OUT_OF_SPACE_IF_NULL(self = sym_new_not_null(ctx)); } op(_CHECK_FUNCTION_EXACT_ARGS, (func_version/2, callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) { - _Py_uop_sym_set_type(callable, &PyFunction_Type); + sym_set_type(callable, &PyFunction_Type); (void)self_or_null; (void)func_version; } op(_CHECK_CALL_BOUND_METHOD_EXACT_ARGS, (callable, null, unused[oparg] -- callable, null, unused[oparg])) { - _Py_uop_sym_set_null(null); - _Py_uop_sym_set_type(callable, &PyMethod_Type); + sym_set_null(null); + sym_set_type(callable, &PyMethod_Type); } op(_INIT_CALL_PY_EXACT_ARGS, (callable, self_or_null, args[oparg] -- new_frame: _Py_UOpsAbstractFrame *)) { @@ -344,29 +361,29 @@ dummy_func(void) { assert(self_or_null != NULL); assert(args != NULL); - if (_Py_uop_sym_is_not_null(self_or_null)) { + if (sym_is_not_null(self_or_null)) { // Bound method fiddling, same as _INIT_CALL_PY_EXACT_ARGS in VM args--; argcount++; } - _Py_UOpsSymType **localsplus_start = ctx->n_consumed; + _Py_UopsSymbol **localsplus_start = ctx->n_consumed; int n_locals_already_filled = 0; // Can determine statically, so we interleave the new locals // and make the current stack the new locals. // This also sets up for true call inlining. - if (_Py_uop_sym_is_null(self_or_null) || _Py_uop_sym_is_not_null(self_or_null)) { + if (sym_is_null(self_or_null) || sym_is_not_null(self_or_null)) { localsplus_start = args; n_locals_already_filled = argcount; } OUT_OF_SPACE_IF_NULL(new_frame = - _Py_uop_ctx_frame_new(ctx, co, localsplus_start, n_locals_already_filled, 0)); + frame_new(ctx, co, localsplus_start, n_locals_already_filled, 0)); } op(_POP_FRAME, (retval -- res)) { SYNC_SP(); ctx->frame->stack_pointer = stack_pointer; - _Py_uop_ctx_frame_pop(ctx); + frame_pop(ctx); stack_pointer = ctx->frame->stack_pointer; res = retval; } @@ -383,7 +400,7 @@ dummy_func(void) { /* This has to be done manually */ (void)seq; for (int i = 0; i < oparg; i++) { - OUT_OF_SPACE_IF_NULL(values[i] = _Py_uop_sym_new_unknown(ctx)); + OUT_OF_SPACE_IF_NULL(values[i] = sym_new_unknown(ctx)); } } @@ -392,12 +409,12 @@ dummy_func(void) { (void)seq; int totalargs = (oparg & 0xFF) + (oparg >> 8) + 1; for (int i = 0; i < totalargs; i++) { - OUT_OF_SPACE_IF_NULL(values[i] = _Py_uop_sym_new_unknown(ctx)); + OUT_OF_SPACE_IF_NULL(values[i] = sym_new_unknown(ctx)); } } op(_ITER_NEXT_RANGE, (iter -- iter, next)) { - OUT_OF_SPACE_IF_NULL(next = _Py_uop_sym_new_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(next = sym_new_type(ctx, &PyLong_Type)); (void)iter; } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 305158847da6fc0..9e99c8395a405b5 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -14,10 +14,10 @@ /* _INSTRUMENTED_RESUME is not a viable micro-op for tier 2 */ case _LOAD_FAST_CHECK: { - _Py_UOpsSymType *value; + _Py_UopsSymbol *value; value = GETLOCAL(oparg); // We guarantee this will error - just bail and don't optimize it. - if (_Py_uop_sym_is_null(value)) { + if (sym_is_null(value)) { goto out_of_space; } stack_pointer[0] = value; @@ -26,7 +26,7 @@ } case _LOAD_FAST: { - _Py_UOpsSymType *value; + _Py_UopsSymbol *value; value = GETLOCAL(oparg); stack_pointer[0] = value; stack_pointer += 1; @@ -34,10 +34,10 @@ } case _LOAD_FAST_AND_CLEAR: { - _Py_UOpsSymType *value; + _Py_UopsSymbol *value; value = GETLOCAL(oparg); - _Py_UOpsSymType *temp; - OUT_OF_SPACE_IF_NULL(temp = _Py_uop_sym_new_null(ctx)); + _Py_UopsSymbol *temp; + OUT_OF_SPACE_IF_NULL(temp = sym_new_null(ctx)); GETLOCAL(oparg) = temp; stack_pointer[0] = value; stack_pointer += 1; @@ -45,7 +45,7 @@ } case _LOAD_CONST: { - _Py_UOpsSymType *value; + _Py_UopsSymbol *value; // There should be no LOAD_CONST. It should be all // replaced by peephole_opt. Py_UNREACHABLE(); @@ -55,7 +55,7 @@ } case _STORE_FAST: { - _Py_UOpsSymType *value; + _Py_UopsSymbol *value; value = stack_pointer[-1]; GETLOCAL(oparg) = value; stack_pointer += -1; @@ -68,8 +68,8 @@ } case _PUSH_NULL: { - _Py_UOpsSymType *res; - res = _Py_uop_sym_new_null(ctx); + _Py_UopsSymbol *res; + res = sym_new_null(ctx); if (res == NULL) { goto out_of_space; }; @@ -79,7 +79,7 @@ } case _END_SEND: { - _Py_UOpsSymType *value; + _Py_UopsSymbol *value; value = _Py_uop_sym_new_unknown(ctx); if (value == NULL) goto out_of_space; stack_pointer[-2] = value; @@ -88,7 +88,7 @@ } case _UNARY_NEGATIVE: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; @@ -96,7 +96,7 @@ } case _UNARY_NOT: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; @@ -104,7 +104,7 @@ } case _TO_BOOL: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; @@ -116,7 +116,7 @@ } case _TO_BOOL_INT: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; @@ -124,7 +124,7 @@ } case _TO_BOOL_LIST: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; @@ -132,7 +132,7 @@ } case _TO_BOOL_NONE: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; @@ -140,7 +140,7 @@ } case _TO_BOOL_STR: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; @@ -148,7 +148,7 @@ } case _TO_BOOL_ALWAYS_TRUE: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; @@ -156,7 +156,7 @@ } case _UNARY_INVERT: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; @@ -164,39 +164,39 @@ } case _GUARD_BOTH_INT: { - _Py_UOpsSymType *right; - _Py_UOpsSymType *left; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (_Py_uop_sym_matches_type(left, &PyLong_Type) && - _Py_uop_sym_matches_type(right, &PyLong_Type)) { + if (sym_matches_type(left, &PyLong_Type) && + sym_matches_type(right, &PyLong_Type)) { REPLACE_OP(this_instr, _NOP, 0, 0); } - _Py_uop_sym_set_type(left, &PyLong_Type); - _Py_uop_sym_set_type(right, &PyLong_Type); + sym_set_type(left, &PyLong_Type); + sym_set_type(right, &PyLong_Type); break; } case _BINARY_OP_MULTIPLY_INT: { - _Py_UOpsSymType *right; - _Py_UOpsSymType *left; - _Py_UOpsSymType *res; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { - assert(PyLong_CheckExact(_Py_uop_sym_get_const(left))); - assert(PyLong_CheckExact(_Py_uop_sym_get_const(right))); - PyObject *temp = _PyLong_Multiply((PyLongObject *)_Py_uop_sym_get_const(left), - (PyLongObject *)_Py_uop_sym_get_const(right)); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyLong_CheckExact(sym_get_const(left))); + assert(PyLong_CheckExact(sym_get_const(right))); + PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(left), + (PyLongObject *)sym_get_const(right)); if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_const(ctx, temp)); + OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } else { - OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyLong_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -204,25 +204,25 @@ } case _BINARY_OP_ADD_INT: { - _Py_UOpsSymType *right; - _Py_UOpsSymType *left; - _Py_UOpsSymType *res; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { - assert(PyLong_CheckExact(_Py_uop_sym_get_const(left))); - assert(PyLong_CheckExact(_Py_uop_sym_get_const(right))); - PyObject *temp = _PyLong_Add((PyLongObject *)_Py_uop_sym_get_const(left), - (PyLongObject *)_Py_uop_sym_get_const(right)); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyLong_CheckExact(sym_get_const(left))); + assert(PyLong_CheckExact(sym_get_const(right))); + PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(left), + (PyLongObject *)sym_get_const(right)); if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_const(ctx, temp)); + OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } else { - OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyLong_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -230,25 +230,25 @@ } case _BINARY_OP_SUBTRACT_INT: { - _Py_UOpsSymType *right; - _Py_UOpsSymType *left; - _Py_UOpsSymType *res; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { - assert(PyLong_CheckExact(_Py_uop_sym_get_const(left))); - assert(PyLong_CheckExact(_Py_uop_sym_get_const(right))); - PyObject *temp = _PyLong_Subtract((PyLongObject *)_Py_uop_sym_get_const(left), - (PyLongObject *)_Py_uop_sym_get_const(right)); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyLong_CheckExact(sym_get_const(left))); + assert(PyLong_CheckExact(sym_get_const(right))); + PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(left), + (PyLongObject *)sym_get_const(right)); if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_const(ctx, temp)); + OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } else { - OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyLong_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -256,40 +256,40 @@ } case _GUARD_BOTH_FLOAT: { - _Py_UOpsSymType *right; - _Py_UOpsSymType *left; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (_Py_uop_sym_matches_type(left, &PyFloat_Type) && - _Py_uop_sym_matches_type(right, &PyFloat_Type)) { + if (sym_matches_type(left, &PyFloat_Type) && + sym_matches_type(right, &PyFloat_Type)) { REPLACE_OP(this_instr, _NOP, 0 ,0); } - _Py_uop_sym_set_type(left, &PyFloat_Type); - _Py_uop_sym_set_type(right, &PyFloat_Type); + sym_set_type(left, &PyFloat_Type); + sym_set_type(right, &PyFloat_Type); break; } case _BINARY_OP_MULTIPLY_FLOAT: { - _Py_UOpsSymType *right; - _Py_UOpsSymType *left; - _Py_UOpsSymType *res; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { - assert(PyFloat_CheckExact(_Py_uop_sym_get_const(left))); - assert(PyFloat_CheckExact(_Py_uop_sym_get_const(right))); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyFloat_CheckExact(sym_get_const(left))); + assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(left)) * - PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(right))); + PyFloat_AS_DOUBLE(sym_get_const(left)) * + PyFloat_AS_DOUBLE(sym_get_const(right))); if (temp == NULL) { goto error; } - res = _Py_uop_sym_new_const(ctx, temp); + res = sym_new_const(ctx, temp); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } else { - OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyFloat_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyFloat_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -297,26 +297,26 @@ } case _BINARY_OP_ADD_FLOAT: { - _Py_UOpsSymType *right; - _Py_UOpsSymType *left; - _Py_UOpsSymType *res; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { - assert(PyFloat_CheckExact(_Py_uop_sym_get_const(left))); - assert(PyFloat_CheckExact(_Py_uop_sym_get_const(right))); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyFloat_CheckExact(sym_get_const(left))); + assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(left)) + - PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(right))); + PyFloat_AS_DOUBLE(sym_get_const(left)) + + PyFloat_AS_DOUBLE(sym_get_const(right))); if (temp == NULL) { goto error; } - res = _Py_uop_sym_new_const(ctx, temp); + res = sym_new_const(ctx, temp); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } else { - OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyFloat_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyFloat_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -324,26 +324,26 @@ } case _BINARY_OP_SUBTRACT_FLOAT: { - _Py_UOpsSymType *right; - _Py_UOpsSymType *left; - _Py_UOpsSymType *res; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (_Py_uop_sym_is_const(left) && _Py_uop_sym_is_const(right)) { - assert(PyFloat_CheckExact(_Py_uop_sym_get_const(left))); - assert(PyFloat_CheckExact(_Py_uop_sym_get_const(right))); + if (sym_is_const(left) && sym_is_const(right)) { + assert(PyFloat_CheckExact(sym_get_const(left))); + assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(left)) - - PyFloat_AS_DOUBLE(_Py_uop_sym_get_const(right))); + PyFloat_AS_DOUBLE(sym_get_const(left)) - + PyFloat_AS_DOUBLE(sym_get_const(right))); if (temp == NULL) { goto error; } - res = _Py_uop_sym_new_const(ctx, temp); + res = sym_new_const(ctx, temp); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } else { - OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_type(ctx, &PyFloat_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyFloat_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -351,21 +351,21 @@ } case _GUARD_BOTH_UNICODE: { - _Py_UOpsSymType *right; - _Py_UOpsSymType *left; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (_Py_uop_sym_matches_type(left, &PyUnicode_Type) && - _Py_uop_sym_matches_type(right, &PyUnicode_Type)) { + if (sym_matches_type(left, &PyUnicode_Type) && + sym_matches_type(right, &PyUnicode_Type)) { REPLACE_OP(this_instr, _NOP, 0 ,0); } - _Py_uop_sym_set_type(left, &PyUnicode_Type); - _Py_uop_sym_set_type(right, &PyUnicode_Type); + sym_set_type(left, &PyUnicode_Type); + sym_set_type(right, &PyUnicode_Type); break; } case _BINARY_OP_ADD_UNICODE: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; @@ -374,7 +374,7 @@ } case _BINARY_SUBSCR: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; @@ -383,7 +383,7 @@ } case _BINARY_SLICE: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-3] = res; @@ -397,7 +397,7 @@ } case _BINARY_SUBSCR_LIST_INT: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; @@ -406,7 +406,7 @@ } case _BINARY_SUBSCR_STR_INT: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; @@ -415,7 +415,7 @@ } case _BINARY_SUBSCR_TUPLE_INT: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; @@ -424,7 +424,7 @@ } case _BINARY_SUBSCR_DICT: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; @@ -465,7 +465,7 @@ } case _CALL_INTRINSIC_1: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; @@ -473,7 +473,7 @@ } case _CALL_INTRINSIC_2: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; @@ -482,12 +482,12 @@ } case _POP_FRAME: { - _Py_UOpsSymType *retval; - _Py_UOpsSymType *res; + _Py_UopsSymbol *retval; + _Py_UopsSymbol *res; retval = stack_pointer[-1]; stack_pointer += -1; ctx->frame->stack_pointer = stack_pointer; - _Py_uop_ctx_frame_pop(ctx); + frame_pop(ctx); stack_pointer = ctx->frame->stack_pointer; res = retval; stack_pointer[0] = res; @@ -500,7 +500,7 @@ /* _INSTRUMENTED_RETURN_CONST is not a viable micro-op for tier 2 */ case _GET_AITER: { - _Py_UOpsSymType *iter; + _Py_UopsSymbol *iter; iter = _Py_uop_sym_new_unknown(ctx); if (iter == NULL) goto out_of_space; stack_pointer[-1] = iter; @@ -508,7 +508,7 @@ } case _GET_ANEXT: { - _Py_UOpsSymType *awaitable; + _Py_UopsSymbol *awaitable; awaitable = _Py_uop_sym_new_unknown(ctx); if (awaitable == NULL) goto out_of_space; stack_pointer[0] = awaitable; @@ -517,7 +517,7 @@ } case _GET_AWAITABLE: { - _Py_UOpsSymType *iter; + _Py_UopsSymbol *iter; iter = _Py_uop_sym_new_unknown(ctx); if (iter == NULL) goto out_of_space; stack_pointer[-1] = iter; @@ -536,7 +536,7 @@ } case _LOAD_ASSERTION_ERROR: { - _Py_UOpsSymType *value; + _Py_UopsSymbol *value; value = _Py_uop_sym_new_unknown(ctx); if (value == NULL) goto out_of_space; stack_pointer[0] = value; @@ -545,7 +545,7 @@ } case _LOAD_BUILD_CLASS: { - _Py_UOpsSymType *bc; + _Py_UopsSymbol *bc; bc = _Py_uop_sym_new_unknown(ctx); if (bc == NULL) goto out_of_space; stack_pointer[0] = bc; @@ -563,21 +563,21 @@ } case _UNPACK_SEQUENCE: { - _Py_UOpsSymType *seq; - _Py_UOpsSymType **values; + _Py_UopsSymbol *seq; + _Py_UopsSymbol **values; seq = stack_pointer[-1]; values = &stack_pointer[-1]; /* This has to be done manually */ (void)seq; for (int i = 0; i < oparg; i++) { - OUT_OF_SPACE_IF_NULL(values[i] = _Py_uop_sym_new_unknown(ctx)); + OUT_OF_SPACE_IF_NULL(values[i] = sym_new_unknown(ctx)); } stack_pointer += -1 + oparg; break; } case _UNPACK_SEQUENCE_TWO_TUPLE: { - _Py_UOpsSymType **values; + _Py_UopsSymbol **values; values = &stack_pointer[-1]; for (int _i = oparg; --_i >= 0;) { values[_i] = _Py_uop_sym_new_unknown(ctx); @@ -588,7 +588,7 @@ } case _UNPACK_SEQUENCE_TUPLE: { - _Py_UOpsSymType **values; + _Py_UopsSymbol **values; values = &stack_pointer[-1]; for (int _i = oparg; --_i >= 0;) { values[_i] = _Py_uop_sym_new_unknown(ctx); @@ -599,7 +599,7 @@ } case _UNPACK_SEQUENCE_LIST: { - _Py_UOpsSymType **values; + _Py_UopsSymbol **values; values = &stack_pointer[-1]; for (int _i = oparg; --_i >= 0;) { values[_i] = _Py_uop_sym_new_unknown(ctx); @@ -610,15 +610,15 @@ } case _UNPACK_EX: { - _Py_UOpsSymType *seq; - _Py_UOpsSymType **values; + _Py_UopsSymbol *seq; + _Py_UopsSymbol **values; seq = stack_pointer[-1]; values = &stack_pointer[-1]; /* This has to be done manually */ (void)seq; int totalargs = (oparg & 0xFF) + (oparg >> 8) + 1; for (int i = 0; i < totalargs; i++) { - OUT_OF_SPACE_IF_NULL(values[i] = _Py_uop_sym_new_unknown(ctx)); + OUT_OF_SPACE_IF_NULL(values[i] = sym_new_unknown(ctx)); } stack_pointer += (oparg >> 8) + (oparg & 0xFF); break; @@ -644,7 +644,7 @@ } case _LOAD_LOCALS: { - _Py_UOpsSymType *locals; + _Py_UopsSymbol *locals; locals = _Py_uop_sym_new_unknown(ctx); if (locals == NULL) goto out_of_space; stack_pointer[0] = locals; @@ -653,7 +653,7 @@ } case _LOAD_FROM_DICT_OR_GLOBALS: { - _Py_UOpsSymType *v; + _Py_UopsSymbol *v; v = _Py_uop_sym_new_unknown(ctx); if (v == NULL) goto out_of_space; stack_pointer[-1] = v; @@ -661,7 +661,7 @@ } case _LOAD_NAME: { - _Py_UOpsSymType *v; + _Py_UopsSymbol *v; v = _Py_uop_sym_new_unknown(ctx); if (v == NULL) goto out_of_space; stack_pointer[0] = v; @@ -670,8 +670,8 @@ } case _LOAD_GLOBAL: { - _Py_UOpsSymType *res; - _Py_UOpsSymType *null = NULL; + _Py_UopsSymbol *res; + _Py_UopsSymbol *null = NULL; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; null = _Py_uop_sym_new_null(ctx); @@ -691,8 +691,8 @@ } case _LOAD_GLOBAL_MODULE: { - _Py_UOpsSymType *res; - _Py_UOpsSymType *null = NULL; + _Py_UopsSymbol *res; + _Py_UopsSymbol *null = NULL; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; null = _Py_uop_sym_new_null(ctx); @@ -704,8 +704,8 @@ } case _LOAD_GLOBAL_BUILTINS: { - _Py_UOpsSymType *res; - _Py_UOpsSymType *null = NULL; + _Py_UopsSymbol *res; + _Py_UopsSymbol *null = NULL; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; null = _Py_uop_sym_new_null(ctx); @@ -729,7 +729,7 @@ } case _LOAD_FROM_DICT_OR_DEREF: { - _Py_UOpsSymType *value; + _Py_UopsSymbol *value; value = _Py_uop_sym_new_unknown(ctx); if (value == NULL) goto out_of_space; stack_pointer[-1] = value; @@ -737,7 +737,7 @@ } case _LOAD_DEREF: { - _Py_UOpsSymType *value; + _Py_UopsSymbol *value; value = _Py_uop_sym_new_unknown(ctx); if (value == NULL) goto out_of_space; stack_pointer[0] = value; @@ -755,7 +755,7 @@ } case _BUILD_STRING: { - _Py_UOpsSymType *str; + _Py_UopsSymbol *str; str = _Py_uop_sym_new_unknown(ctx); if (str == NULL) goto out_of_space; stack_pointer[-oparg] = str; @@ -764,7 +764,7 @@ } case _BUILD_TUPLE: { - _Py_UOpsSymType *tup; + _Py_UopsSymbol *tup; tup = _Py_uop_sym_new_unknown(ctx); if (tup == NULL) goto out_of_space; stack_pointer[-oparg] = tup; @@ -773,7 +773,7 @@ } case _BUILD_LIST: { - _Py_UOpsSymType *list; + _Py_UopsSymbol *list; list = _Py_uop_sym_new_unknown(ctx); if (list == NULL) goto out_of_space; stack_pointer[-oparg] = list; @@ -792,7 +792,7 @@ } case _BUILD_SET: { - _Py_UOpsSymType *set; + _Py_UopsSymbol *set; set = _Py_uop_sym_new_unknown(ctx); if (set == NULL) goto out_of_space; stack_pointer[-oparg] = set; @@ -801,7 +801,7 @@ } case _BUILD_MAP: { - _Py_UOpsSymType *map; + _Py_UopsSymbol *map; map = _Py_uop_sym_new_unknown(ctx); if (map == NULL) goto out_of_space; stack_pointer[-oparg*2] = map; @@ -814,7 +814,7 @@ } case _BUILD_CONST_KEY_MAP: { - _Py_UOpsSymType *map; + _Py_UopsSymbol *map; map = _Py_uop_sym_new_unknown(ctx); if (map == NULL) goto out_of_space; stack_pointer[-1 - oparg] = map; @@ -840,7 +840,7 @@ /* _INSTRUMENTED_LOAD_SUPER_ATTR is not a viable micro-op for tier 2 */ case _LOAD_SUPER_ATTR_ATTR: { - _Py_UOpsSymType *attr; + _Py_UopsSymbol *attr; attr = _Py_uop_sym_new_unknown(ctx); if (attr == NULL) goto out_of_space; stack_pointer[-3] = attr; @@ -849,8 +849,8 @@ } case _LOAD_SUPER_ATTR_METHOD: { - _Py_UOpsSymType *attr; - _Py_UOpsSymType *self_or_null; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *self_or_null; attr = _Py_uop_sym_new_unknown(ctx); if (attr == NULL) goto out_of_space; self_or_null = _Py_uop_sym_new_unknown(ctx); @@ -862,8 +862,8 @@ } case _LOAD_ATTR: { - _Py_UOpsSymType *attr; - _Py_UOpsSymType *self_or_null = NULL; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *self_or_null = NULL; attr = _Py_uop_sym_new_unknown(ctx); if (attr == NULL) goto out_of_space; self_or_null = _Py_uop_sym_new_unknown(ctx); @@ -883,9 +883,9 @@ } case _LOAD_ATTR_INSTANCE_VALUE: { - _Py_UOpsSymType *owner; - _Py_UOpsSymType *attr; - _Py_UOpsSymType *null = NULL; + _Py_UopsSymbol *owner; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *null = NULL; owner = stack_pointer[-1]; uint16_t index = (uint16_t)this_instr->operand; _LOAD_ATTR_NOT_NULL @@ -898,12 +898,12 @@ } case _CHECK_ATTR_MODULE: { - _Py_UOpsSymType *owner; + _Py_UopsSymbol *owner; owner = stack_pointer[-1]; uint32_t dict_version = (uint32_t)this_instr->operand; (void)dict_version; - if (_Py_uop_sym_is_const(owner)) { - PyObject *cnst = _Py_uop_sym_get_const(owner); + if (sym_is_const(owner)) { + PyObject *cnst = sym_get_const(owner); if (PyModule_CheckExact(cnst)) { PyModuleObject *mod = (PyModuleObject *)cnst; PyObject *dict = mod->md_dict; @@ -919,29 +919,29 @@ } case _LOAD_ATTR_MODULE: { - _Py_UOpsSymType *owner; - _Py_UOpsSymType *attr; - _Py_UOpsSymType *null = NULL; + _Py_UopsSymbol *owner; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *null = NULL; owner = stack_pointer[-1]; uint16_t index = (uint16_t)this_instr->operand; (void)index; - OUT_OF_SPACE_IF_NULL(null = _Py_uop_sym_new_null(ctx)); + OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); attr = NULL; if (this_instr[-1].opcode == _NOP) { // Preceding _CHECK_ATTR_MODULE was removed: mod is const and dict is watched. - assert(_Py_uop_sym_is_const(owner)); - PyModuleObject *mod = (PyModuleObject *)_Py_uop_sym_get_const(owner); + assert(sym_is_const(owner)); + PyModuleObject *mod = (PyModuleObject *)sym_get_const(owner); assert(PyModule_CheckExact(mod)); PyObject *dict = mod->md_dict; PyObject *res = convert_global_to_const(this_instr, dict); if (res != NULL) { this_instr[-1].opcode = _POP_TOP; - OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_const(ctx, res)); + OUT_OF_SPACE_IF_NULL(attr = sym_new_const(ctx, res)); } } if (attr == NULL) { /* No conversion made. We don't know what `attr` is. */ - OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_not_null(ctx)); + OUT_OF_SPACE_IF_NULL(attr = sym_new_not_null(ctx)); } stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = null; @@ -954,9 +954,9 @@ } case _LOAD_ATTR_WITH_HINT: { - _Py_UOpsSymType *owner; - _Py_UOpsSymType *attr; - _Py_UOpsSymType *null = NULL; + _Py_UopsSymbol *owner; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *null = NULL; owner = stack_pointer[-1]; uint16_t hint = (uint16_t)this_instr->operand; _LOAD_ATTR_NOT_NULL @@ -969,9 +969,9 @@ } case _LOAD_ATTR_SLOT: { - _Py_UOpsSymType *owner; - _Py_UOpsSymType *attr; - _Py_UOpsSymType *null = NULL; + _Py_UopsSymbol *owner; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *null = NULL; owner = stack_pointer[-1]; uint16_t index = (uint16_t)this_instr->operand; _LOAD_ATTR_NOT_NULL @@ -988,9 +988,9 @@ } case _LOAD_ATTR_CLASS: { - _Py_UOpsSymType *owner; - _Py_UOpsSymType *attr; - _Py_UOpsSymType *null = NULL; + _Py_UopsSymbol *owner; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *null = NULL; owner = stack_pointer[-1]; PyObject *descr = (PyObject *)this_instr->operand; _LOAD_ATTR_NOT_NULL @@ -1023,7 +1023,7 @@ } case _COMPARE_OP: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; @@ -1032,7 +1032,7 @@ } case _COMPARE_OP_FLOAT: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; @@ -1041,7 +1041,7 @@ } case _COMPARE_OP_INT: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; @@ -1050,7 +1050,7 @@ } case _COMPARE_OP_STR: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; @@ -1059,7 +1059,7 @@ } case _IS_OP: { - _Py_UOpsSymType *b; + _Py_UopsSymbol *b; b = _Py_uop_sym_new_unknown(ctx); if (b == NULL) goto out_of_space; stack_pointer[-2] = b; @@ -1068,7 +1068,7 @@ } case _CONTAINS_OP: { - _Py_UOpsSymType *b; + _Py_UopsSymbol *b; b = _Py_uop_sym_new_unknown(ctx); if (b == NULL) goto out_of_space; stack_pointer[-2] = b; @@ -1077,8 +1077,8 @@ } case _CHECK_EG_MATCH: { - _Py_UOpsSymType *rest; - _Py_UOpsSymType *match; + _Py_UopsSymbol *rest; + _Py_UopsSymbol *match; rest = _Py_uop_sym_new_unknown(ctx); if (rest == NULL) goto out_of_space; match = _Py_uop_sym_new_unknown(ctx); @@ -1089,7 +1089,7 @@ } case _CHECK_EXC_MATCH: { - _Py_UOpsSymType *b; + _Py_UopsSymbol *b; b = _Py_uop_sym_new_unknown(ctx); if (b == NULL) goto out_of_space; stack_pointer[-1] = b; @@ -1101,7 +1101,7 @@ /* _POP_JUMP_IF_TRUE is not a viable micro-op for tier 2 */ case _IS_NONE: { - _Py_UOpsSymType *b; + _Py_UopsSymbol *b; b = _Py_uop_sym_new_unknown(ctx); if (b == NULL) goto out_of_space; stack_pointer[-1] = b; @@ -1109,7 +1109,7 @@ } case _GET_LEN: { - _Py_UOpsSymType *len_o; + _Py_UopsSymbol *len_o; len_o = _Py_uop_sym_new_unknown(ctx); if (len_o == NULL) goto out_of_space; stack_pointer[0] = len_o; @@ -1118,7 +1118,7 @@ } case _MATCH_CLASS: { - _Py_UOpsSymType *attrs; + _Py_UopsSymbol *attrs; attrs = _Py_uop_sym_new_unknown(ctx); if (attrs == NULL) goto out_of_space; stack_pointer[-3] = attrs; @@ -1127,7 +1127,7 @@ } case _MATCH_MAPPING: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[0] = res; @@ -1136,7 +1136,7 @@ } case _MATCH_SEQUENCE: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[0] = res; @@ -1145,7 +1145,7 @@ } case _MATCH_KEYS: { - _Py_UOpsSymType *values_or_none; + _Py_UopsSymbol *values_or_none; values_or_none = _Py_uop_sym_new_unknown(ctx); if (values_or_none == NULL) goto out_of_space; stack_pointer[0] = values_or_none; @@ -1154,7 +1154,7 @@ } case _GET_ITER: { - _Py_UOpsSymType *iter; + _Py_UopsSymbol *iter; iter = _Py_uop_sym_new_unknown(ctx); if (iter == NULL) goto out_of_space; stack_pointer[-1] = iter; @@ -1162,7 +1162,7 @@ } case _GET_YIELD_FROM_ITER: { - _Py_UOpsSymType *iter; + _Py_UopsSymbol *iter; iter = _Py_uop_sym_new_unknown(ctx); if (iter == NULL) goto out_of_space; stack_pointer[-1] = iter; @@ -1172,7 +1172,7 @@ /* _FOR_ITER is not a viable micro-op for tier 2 */ case _FOR_ITER_TIER_TWO: { - _Py_UOpsSymType *next; + _Py_UopsSymbol *next; next = _Py_uop_sym_new_unknown(ctx); if (next == NULL) goto out_of_space; stack_pointer[0] = next; @@ -1193,7 +1193,7 @@ } case _ITER_NEXT_LIST: { - _Py_UOpsSymType *next; + _Py_UopsSymbol *next; next = _Py_uop_sym_new_unknown(ctx); if (next == NULL) goto out_of_space; stack_pointer[0] = next; @@ -1212,7 +1212,7 @@ } case _ITER_NEXT_TUPLE: { - _Py_UOpsSymType *next; + _Py_UopsSymbol *next; next = _Py_uop_sym_new_unknown(ctx); if (next == NULL) goto out_of_space; stack_pointer[0] = next; @@ -1231,10 +1231,10 @@ } case _ITER_NEXT_RANGE: { - _Py_UOpsSymType *iter; - _Py_UOpsSymType *next; + _Py_UopsSymbol *iter; + _Py_UopsSymbol *next; iter = stack_pointer[-1]; - OUT_OF_SPACE_IF_NULL(next = _Py_uop_sym_new_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(next = sym_new_type(ctx, &PyLong_Type)); (void)iter; stack_pointer[0] = next; stack_pointer += 1; @@ -1244,8 +1244,8 @@ /* _FOR_ITER_GEN is not a viable micro-op for tier 2 */ case _BEFORE_ASYNC_WITH: { - _Py_UOpsSymType *exit; - _Py_UOpsSymType *res; + _Py_UopsSymbol *exit; + _Py_UopsSymbol *res; exit = _Py_uop_sym_new_unknown(ctx); if (exit == NULL) goto out_of_space; res = _Py_uop_sym_new_unknown(ctx); @@ -1257,8 +1257,8 @@ } case _BEFORE_WITH: { - _Py_UOpsSymType *exit; - _Py_UOpsSymType *res; + _Py_UopsSymbol *exit; + _Py_UopsSymbol *res; exit = _Py_uop_sym_new_unknown(ctx); if (exit == NULL) goto out_of_space; res = _Py_uop_sym_new_unknown(ctx); @@ -1270,7 +1270,7 @@ } case _WITH_EXCEPT_START: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[0] = res; @@ -1279,8 +1279,8 @@ } case _PUSH_EXC_INFO: { - _Py_UOpsSymType *prev_exc; - _Py_UOpsSymType *new_exc; + _Py_UopsSymbol *prev_exc; + _Py_UopsSymbol *new_exc; prev_exc = _Py_uop_sym_new_unknown(ctx); if (prev_exc == NULL) goto out_of_space; new_exc = _Py_uop_sym_new_unknown(ctx); @@ -1300,13 +1300,13 @@ } case _LOAD_ATTR_METHOD_WITH_VALUES: { - _Py_UOpsSymType *owner; - _Py_UOpsSymType *attr; - _Py_UOpsSymType *self = NULL; + _Py_UopsSymbol *owner; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *self = NULL; owner = stack_pointer[-1]; PyObject *descr = (PyObject *)this_instr->operand; (void)descr; - OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_not_null(ctx)); + OUT_OF_SPACE_IF_NULL(attr = sym_new_not_null(ctx)); self = owner; stack_pointer[-1] = attr; stack_pointer[0] = self; @@ -1315,13 +1315,13 @@ } case _LOAD_ATTR_METHOD_NO_DICT: { - _Py_UOpsSymType *owner; - _Py_UOpsSymType *attr; - _Py_UOpsSymType *self = NULL; + _Py_UopsSymbol *owner; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *self = NULL; owner = stack_pointer[-1]; PyObject *descr = (PyObject *)this_instr->operand; (void)descr; - OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_not_null(ctx)); + OUT_OF_SPACE_IF_NULL(attr = sym_new_not_null(ctx)); self = owner; stack_pointer[-1] = attr; stack_pointer[0] = self; @@ -1330,7 +1330,7 @@ } case _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: { - _Py_UOpsSymType *attr; + _Py_UopsSymbol *attr; attr = _Py_uop_sym_new_unknown(ctx); if (attr == NULL) goto out_of_space; stack_pointer[-1] = attr; @@ -1338,7 +1338,7 @@ } case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: { - _Py_UOpsSymType *attr; + _Py_UopsSymbol *attr; attr = _Py_uop_sym_new_unknown(ctx); if (attr == NULL) goto out_of_space; stack_pointer[-1] = attr; @@ -1350,13 +1350,13 @@ } case _LOAD_ATTR_METHOD_LAZY_DICT: { - _Py_UOpsSymType *owner; - _Py_UOpsSymType *attr; - _Py_UOpsSymType *self = NULL; + _Py_UopsSymbol *owner; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *self = NULL; owner = stack_pointer[-1]; PyObject *descr = (PyObject *)this_instr->operand; (void)descr; - OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_not_null(ctx)); + OUT_OF_SPACE_IF_NULL(attr = sym_new_not_null(ctx)); self = owner; stack_pointer[-1] = attr; stack_pointer[0] = self; @@ -1369,23 +1369,23 @@ /* _CALL is not a viable micro-op for tier 2 */ case _CHECK_CALL_BOUND_METHOD_EXACT_ARGS: { - _Py_UOpsSymType *null; - _Py_UOpsSymType *callable; + _Py_UopsSymbol *null; + _Py_UopsSymbol *callable; null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - _Py_uop_sym_set_null(null); - _Py_uop_sym_set_type(callable, &PyMethod_Type); + sym_set_null(null); + sym_set_type(callable, &PyMethod_Type); break; } case _INIT_CALL_BOUND_METHOD_EXACT_ARGS: { - _Py_UOpsSymType *callable; - _Py_UOpsSymType *func; - _Py_UOpsSymType *self; + _Py_UopsSymbol *callable; + _Py_UopsSymbol *func; + _Py_UopsSymbol *self; callable = stack_pointer[-2 - oparg]; (void)callable; - OUT_OF_SPACE_IF_NULL(func = _Py_uop_sym_new_not_null(ctx)); - OUT_OF_SPACE_IF_NULL(self = _Py_uop_sym_new_not_null(ctx)); + OUT_OF_SPACE_IF_NULL(func = sym_new_not_null(ctx)); + OUT_OF_SPACE_IF_NULL(self = sym_new_not_null(ctx)); stack_pointer[-2 - oparg] = func; stack_pointer[-1 - oparg] = self; break; @@ -1396,12 +1396,12 @@ } case _CHECK_FUNCTION_EXACT_ARGS: { - _Py_UOpsSymType *self_or_null; - _Py_UOpsSymType *callable; + _Py_UopsSymbol *self_or_null; + _Py_UopsSymbol *callable; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; uint32_t func_version = (uint32_t)this_instr->operand; - _Py_uop_sym_set_type(callable, &PyFunction_Type); + sym_set_type(callable, &PyFunction_Type); (void)self_or_null; (void)func_version; break; @@ -1412,9 +1412,9 @@ } case _INIT_CALL_PY_EXACT_ARGS: { - _Py_UOpsSymType **args; - _Py_UOpsSymType *self_or_null; - _Py_UOpsSymType *callable; + _Py_UopsSymbol **args; + _Py_UopsSymbol *self_or_null; + _Py_UopsSymbol *callable; _Py_UOpsAbstractFrame *new_frame; args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; @@ -1428,23 +1428,23 @@ PyCodeObject *co = (PyCodeObject *)func->func_code; assert(self_or_null != NULL); assert(args != NULL); - if (_Py_uop_sym_is_not_null(self_or_null)) { + if (sym_is_not_null(self_or_null)) { // Bound method fiddling, same as _INIT_CALL_PY_EXACT_ARGS in VM args--; argcount++; } - _Py_UOpsSymType **localsplus_start = ctx->n_consumed; + _Py_UopsSymbol **localsplus_start = ctx->n_consumed; int n_locals_already_filled = 0; // Can determine statically, so we interleave the new locals // and make the current stack the new locals. // This also sets up for true call inlining. - if (_Py_uop_sym_is_null(self_or_null) || _Py_uop_sym_is_not_null(self_or_null)) { + if (sym_is_null(self_or_null) || sym_is_not_null(self_or_null)) { localsplus_start = args; n_locals_already_filled = argcount; } OUT_OF_SPACE_IF_NULL(new_frame = - _Py_uop_ctx_frame_new(ctx, co, localsplus_start, n_locals_already_filled, 0)); - stack_pointer[-2 - oparg] = (_Py_UOpsSymType *)new_frame; + frame_new(ctx, co, localsplus_start, n_locals_already_filled, 0)); + stack_pointer[-2 - oparg] = (_Py_UopsSymbol *)new_frame; stack_pointer += -1 - oparg; break; } @@ -1463,7 +1463,7 @@ /* _CALL_PY_WITH_DEFAULTS is not a viable micro-op for tier 2 */ case _CALL_TYPE_1: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; @@ -1472,7 +1472,7 @@ } case _CALL_STR_1: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; @@ -1481,7 +1481,7 @@ } case _CALL_TUPLE_1: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; @@ -1497,7 +1497,7 @@ } case _CALL_BUILTIN_CLASS: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; @@ -1506,7 +1506,7 @@ } case _CALL_BUILTIN_O: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; @@ -1515,7 +1515,7 @@ } case _CALL_BUILTIN_FAST: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; @@ -1524,7 +1524,7 @@ } case _CALL_BUILTIN_FAST_WITH_KEYWORDS: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; @@ -1533,7 +1533,7 @@ } case _CALL_LEN: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; @@ -1542,7 +1542,7 @@ } case _CALL_ISINSTANCE: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; @@ -1551,7 +1551,7 @@ } case _CALL_METHOD_DESCRIPTOR_O: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; @@ -1560,7 +1560,7 @@ } case _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; @@ -1569,7 +1569,7 @@ } case _CALL_METHOD_DESCRIPTOR_NOARGS: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; @@ -1578,7 +1578,7 @@ } case _CALL_METHOD_DESCRIPTOR_FAST: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; @@ -1595,7 +1595,7 @@ /* _CALL_FUNCTION_EX is not a viable micro-op for tier 2 */ case _MAKE_FUNCTION: { - _Py_UOpsSymType *func; + _Py_UopsSymbol *func; func = _Py_uop_sym_new_unknown(ctx); if (func == NULL) goto out_of_space; stack_pointer[-1] = func; @@ -1603,7 +1603,7 @@ } case _SET_FUNCTION_ATTRIBUTE: { - _Py_UOpsSymType *func; + _Py_UopsSymbol *func; func = _Py_uop_sym_new_unknown(ctx); if (func == NULL) goto out_of_space; stack_pointer[-2] = func; @@ -1612,7 +1612,7 @@ } case _BUILD_SLICE: { - _Py_UOpsSymType *slice; + _Py_UopsSymbol *slice; slice = _Py_uop_sym_new_unknown(ctx); if (slice == NULL) goto out_of_space; stack_pointer[-2 - ((oparg == 3) ? 1 : 0)] = slice; @@ -1621,7 +1621,7 @@ } case _CONVERT_VALUE: { - _Py_UOpsSymType *result; + _Py_UopsSymbol *result; result = _Py_uop_sym_new_unknown(ctx); if (result == NULL) goto out_of_space; stack_pointer[-1] = result; @@ -1629,7 +1629,7 @@ } case _FORMAT_SIMPLE: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; @@ -1637,7 +1637,7 @@ } case _FORMAT_WITH_SPEC: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; @@ -1646,8 +1646,8 @@ } case _COPY: { - _Py_UOpsSymType *bottom; - _Py_UOpsSymType *top; + _Py_UopsSymbol *bottom; + _Py_UopsSymbol *top; bottom = stack_pointer[-1 - (oparg-1)]; assert(oparg > 0); top = bottom; @@ -1657,7 +1657,7 @@ } case _BINARY_OP: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; @@ -1666,8 +1666,8 @@ } case _SWAP: { - _Py_UOpsSymType *top; - _Py_UOpsSymType *bottom; + _Py_UopsSymbol *top; + _Py_UopsSymbol *bottom; top = stack_pointer[-1]; bottom = stack_pointer[-2 - (oparg-2)]; stack_pointer[-2 - (oparg-2)] = top; @@ -1730,29 +1730,29 @@ } case _LOAD_CONST_INLINE: { - _Py_UOpsSymType *value; + _Py_UopsSymbol *value; PyObject *ptr = (PyObject *)this_instr->operand; - OUT_OF_SPACE_IF_NULL(value = _Py_uop_sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); stack_pointer[0] = value; stack_pointer += 1; break; } case _LOAD_CONST_INLINE_BORROW: { - _Py_UOpsSymType *value; + _Py_UopsSymbol *value; PyObject *ptr = (PyObject *)this_instr->operand; - OUT_OF_SPACE_IF_NULL(value = _Py_uop_sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); stack_pointer[0] = value; stack_pointer += 1; break; } case _LOAD_CONST_INLINE_WITH_NULL: { - _Py_UOpsSymType *value; - _Py_UOpsSymType *null; + _Py_UopsSymbol *value; + _Py_UopsSymbol *null; PyObject *ptr = (PyObject *)this_instr->operand; - OUT_OF_SPACE_IF_NULL(value = _Py_uop_sym_new_const(ctx, ptr)); - OUT_OF_SPACE_IF_NULL(null = _Py_uop_sym_new_null(ctx)); + OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); stack_pointer[0] = value; stack_pointer[1] = null; stack_pointer += 2; @@ -1760,11 +1760,11 @@ } case _LOAD_CONST_INLINE_BORROW_WITH_NULL: { - _Py_UOpsSymType *value; - _Py_UOpsSymType *null; + _Py_UopsSymbol *value; + _Py_UopsSymbol *null; PyObject *ptr = (PyObject *)this_instr->operand; - OUT_OF_SPACE_IF_NULL(value = _Py_uop_sym_new_const(ctx, ptr)); - OUT_OF_SPACE_IF_NULL(null = _Py_uop_sym_new_null(ctx)); + OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); + OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); stack_pointer[0] = value; stack_pointer[1] = null; stack_pointer += 2; diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 1b7c88cf785fd26..794d73733f85a72 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -32,10 +32,10 @@ static inline int get_lltrace(void) { #endif // Takes a borrowed reference to const_val, turns that into a strong reference. -static _Py_UOpsSymType* -sym_new(_Py_UOpsAbstractInterpContext *ctx, PyObject *const_val) +static _Py_UopsSymbol * +sym_new(_Py_UOpsContext *ctx, PyObject *const_val) { - _Py_UOpsSymType *self = &ctx->t_arena.arena[ctx->t_arena.ty_curr_number]; + _Py_UopsSymbol *self = &ctx->t_arena.arena[ctx->t_arena.ty_curr_number]; if (ctx->t_arena.ty_curr_number >= ctx->t_arena.ty_max_number) { OPT_STAT_INC(optimizer_failure_reason_no_memory); DPRINTF(1, "out of space for symbolic expression type\n"); @@ -54,37 +54,37 @@ sym_new(_Py_UOpsAbstractInterpContext *ctx, PyObject *const_val) } static inline void -sym_set_flag(_Py_UOpsSymType *sym, int flag) +sym_set_flag(_Py_UopsSymbol *sym, int flag) { sym->flags |= flag; } static inline bool -sym_has_flag(_Py_UOpsSymType *sym, int flag) +sym_has_flag(_Py_UopsSymbol *sym, int flag) { return (sym->flags & flag) != 0; } bool -_Py_uop_sym_is_not_null(_Py_UOpsSymType *sym) +_Py_uop_sym_is_not_null(_Py_UopsSymbol *sym) { return (sym->flags & (IS_NULL | NOT_NULL)) == NOT_NULL; } bool -_Py_uop_sym_is_null(_Py_UOpsSymType *sym) +_Py_uop_sym_is_null(_Py_UopsSymbol *sym) { return (sym->flags & (IS_NULL | NOT_NULL)) == IS_NULL; } bool -_Py_uop_sym_is_const(_Py_UOpsSymType *sym) +_Py_uop_sym_is_const(_Py_UopsSymbol *sym) { return (sym->flags & TRUE_CONST) != 0; } PyObject * -_Py_uop_sym_get_const(_Py_UOpsSymType *sym) +_Py_uop_sym_get_const(_Py_UopsSymbol *sym) { assert(_Py_uop_sym_is_const(sym)); assert(sym->const_val); @@ -92,7 +92,7 @@ _Py_uop_sym_get_const(_Py_UOpsSymType *sym) } void -_Py_uop_sym_set_type(_Py_UOpsSymType *sym, PyTypeObject *tp) +_Py_uop_sym_set_type(_Py_UopsSymbol *sym, PyTypeObject *tp) { assert(PyType_Check(tp)); sym->typ = tp; @@ -101,23 +101,23 @@ _Py_uop_sym_set_type(_Py_UOpsSymType *sym, PyTypeObject *tp) } void -_Py_uop_sym_set_null(_Py_UOpsSymType *sym) +_Py_uop_sym_set_null(_Py_UopsSymbol *sym) { sym_set_flag(sym, IS_NULL); sym_set_flag(sym, KNOWN); } -_Py_UOpsSymType * -_Py_uop_sym_new_unknown(_Py_UOpsAbstractInterpContext *ctx) +_Py_UopsSymbol * +_Py_uop_sym_new_unknown(_Py_UOpsContext *ctx) { - return sym_new(ctx,NULL); + return sym_new(ctx, NULL); } -_Py_UOpsSymType * -_Py_uop_sym_new_not_null(_Py_UOpsAbstractInterpContext *ctx) +_Py_UopsSymbol * +_Py_uop_sym_new_not_null(_Py_UOpsContext *ctx) { - _Py_UOpsSymType *res = _Py_uop_sym_new_unknown(ctx); + _Py_UopsSymbol *res = _Py_uop_sym_new_unknown(ctx); if (res == NULL) { return NULL; } @@ -126,11 +126,11 @@ _Py_uop_sym_new_not_null(_Py_UOpsAbstractInterpContext *ctx) return res; } -_Py_UOpsSymType * -_Py_uop_sym_new_type(_Py_UOpsAbstractInterpContext *ctx, +_Py_UopsSymbol * +_Py_uop_sym_new_type(_Py_UOpsContext *ctx, PyTypeObject *typ) { - _Py_UOpsSymType *res = sym_new(ctx,NULL); + _Py_UopsSymbol *res = sym_new(ctx,NULL); if (res == NULL) { return NULL; } @@ -139,14 +139,11 @@ _Py_uop_sym_new_type(_Py_UOpsAbstractInterpContext *ctx, } // Takes a borrowed reference to const_val. -_Py_UOpsSymType* -_Py_uop_sym_new_const(_Py_UOpsAbstractInterpContext *ctx, PyObject *const_val) +_Py_UopsSymbol * +_Py_uop_sym_new_const(_Py_UOpsContext *ctx, PyObject *const_val) { assert(const_val != NULL); - _Py_UOpsSymType *temp = sym_new( - ctx, - const_val - ); + _Py_UopsSymbol *temp = sym_new(ctx, const_val); if (temp == NULL) { return NULL; } @@ -157,10 +154,10 @@ _Py_uop_sym_new_const(_Py_UOpsAbstractInterpContext *ctx, PyObject *const_val) return temp; } -_Py_UOpsSymType* -_Py_uop_sym_new_null(_Py_UOpsAbstractInterpContext *ctx) +_Py_UopsSymbol * +_Py_uop_sym_new_null(_Py_UOpsContext *ctx) { - _Py_UOpsSymType *null_sym = _Py_uop_sym_new_unknown(ctx); + _Py_UopsSymbol *null_sym = _Py_uop_sym_new_unknown(ctx); if (null_sym == NULL) { return NULL; } @@ -169,7 +166,7 @@ _Py_uop_sym_new_null(_Py_UOpsAbstractInterpContext *ctx) } bool -_Py_uop_sym_matches_type(_Py_UOpsSymType *sym, PyTypeObject *typ) +_Py_uop_sym_matches_type(_Py_UopsSymbol *sym, PyTypeObject *typ) { assert(typ == NULL || PyType_Check(typ)); if (!sym_has_flag(sym, KNOWN)) { @@ -180,13 +177,12 @@ _Py_uop_sym_matches_type(_Py_UOpsSymType *sym, PyTypeObject *typ) // 0 on success, -1 on error. _Py_UOpsAbstractFrame * -_Py_uop_ctx_frame_new( - _Py_UOpsAbstractInterpContext *ctx, +_Py_uop_frame_new( + _Py_UOpsContext *ctx, PyCodeObject *co, - _Py_UOpsSymType **localsplus_start, + _Py_UopsSymbol **localsplus_start, int n_locals_already_filled, - int curr_stackentries -) + int curr_stackentries) { assert(ctx->curr_frame_depth < MAX_ABSTRACT_FRAME_DEPTH); _Py_UOpsAbstractFrame *frame = &ctx->frames[ctx->curr_frame_depth]; @@ -205,7 +201,7 @@ _Py_uop_ctx_frame_new( // Initialize with the initial state of all local variables for (int i = n_locals_already_filled; i < co->co_nlocalsplus; i++) { - _Py_UOpsSymType *local = _Py_uop_sym_new_unknown(ctx); + _Py_UopsSymbol *local = _Py_uop_sym_new_unknown(ctx); if (local == NULL) { return NULL; } @@ -215,7 +211,7 @@ _Py_uop_ctx_frame_new( // Initialize the stack as well for (int i = 0; i < curr_stackentries; i++) { - _Py_UOpsSymType *stackvar = _Py_uop_sym_new_unknown(ctx); + _Py_UopsSymbol *stackvar = _Py_uop_sym_new_unknown(ctx); if (stackvar == NULL) { return NULL; } @@ -226,7 +222,7 @@ _Py_uop_ctx_frame_new( } void -_Py_uop_abstractcontext_fini(_Py_UOpsAbstractInterpContext *ctx) +_Py_uop_abstractcontext_fini(_Py_UOpsContext *ctx) { if (ctx == NULL) { return; @@ -239,9 +235,7 @@ _Py_uop_abstractcontext_fini(_Py_UOpsAbstractInterpContext *ctx) } int -_Py_uop_abstractcontext_init( - _Py_UOpsAbstractInterpContext *ctx -) +_Py_uop_abstractcontext_init(_Py_UOpsContext *ctx) { ctx->limit = ctx->locals_and_stack + MAX_ABSTRACT_INTERP_SIZE; ctx->n_consumed = ctx->locals_and_stack; @@ -262,12 +256,9 @@ _Py_uop_abstractcontext_init( } int -_Py_uop_ctx_frame_pop( - _Py_UOpsAbstractInterpContext *ctx -) +_Py_uop_frame_pop(_Py_UOpsContext *ctx) { _Py_UOpsAbstractFrame *frame = ctx->frame; - ctx->n_consumed = frame->locals; ctx->curr_frame_depth--; assert(ctx->curr_frame_depth >= 1); @@ -287,10 +278,10 @@ do { \ } while (0) /* -static _Py_UOpsSymType * -make_bottom(_Py_UOpsAbstractInterpContext *ctx) +static _Py_UopsSymbol * +make_contradiction(_Py_UOpsContext *ctx) { - _Py_UOpsSymType *sym = _Py_uop_sym_new_unknown(ctx); + _Py_UopsSymbol *sym = _Py_uop_sym_new_unknown(ctx); _Py_uop_sym_set_null(sym); _Py_uop_sym_set_type(sym, &PyLong_Type); return sym; @@ -299,11 +290,11 @@ make_bottom(_Py_UOpsAbstractInterpContext *ctx) PyObject * _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) { - _Py_UOpsAbstractInterpContext context; - _Py_UOpsAbstractInterpContext *ctx = &context; + _Py_UOpsContext context; + _Py_UOpsContext *ctx = &context; _Py_uop_abstractcontext_init(ctx); - _Py_UOpsSymType *top = _Py_uop_sym_new_unknown(ctx); + _Py_UopsSymbol *top = _Py_uop_sym_new_unknown(ctx); if (top == NULL) { return NULL; } @@ -312,17 +303,17 @@ _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) TEST_PREDICATE(!_Py_uop_sym_is_const(top), "unknown is a constant"); // TEST_PREDICATE(_Py_uop_sym_get_const(top) == NULL, "unknown as constant is not NULL"); - // _Py_UOpsSymType *bottom = make_bottom(ctx); - // TEST_PREDICATE(_Py_uop_sym_is_null(bottom), "bottom is NULL is not true"); - // TEST_PREDICATE(_Py_uop_sym_is_not_null(bottom), "bottom is not NULL is not true"); - // TEST_PREDICATE(_Py_uop_sym_is_const(bottom), "bottom is a constant is not true"); + // _Py_UopsSymbol *contradiction = make_contradiction(ctx); + // TEST_PREDICATE(_Py_uop_sym_is_null(contradiction), "contradiction is NULL is not true"); + // TEST_PREDICATE(_Py_uop_sym_is_not_null(contradiction), "contradiction is not NULL is not true"); + // TEST_PREDICATE(_Py_uop_sym_is_const(contradiction), "contradiction is a constant is not true"); - _Py_UOpsSymType *int_type = _Py_uop_sym_new_type(ctx, &PyLong_Type); + _Py_UopsSymbol *int_type = _Py_uop_sym_new_type(ctx, &PyLong_Type); TEST_PREDICATE(_Py_uop_sym_matches_type(int_type, &PyLong_Type), "inconsistent type"); _Py_uop_sym_set_type(int_type, &PyLong_Type); TEST_PREDICATE(_Py_uop_sym_matches_type(int_type, &PyLong_Type), "inconsistent type"); _Py_uop_sym_set_type(int_type, &PyFloat_Type); - // TEST_PREDICATE(_Py_uop_sym_matches_type(int_type, &PyLong_Type), "bottom doesn't match int"); + // TEST_PREDICATE(_Py_uop_sym_matches_type(int_type, &PyLong_Type), "(int and float) doesn't match int"); _Py_uop_abstractcontext_fini(ctx); Py_RETURN_NONE; diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index f110a74e3ad3514..e933fee8f4242b8 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -41,10 +41,10 @@ def validate_uop(override: Uop, uop: Uop) -> None: def type_name(var: StackItem) -> str: if var.is_array(): - return f"_Py_UOpsSymType **" + return f"_Py_UopsSymbol **" if var.type: return var.type - return f"_Py_UOpsSymType *" + return f"_Py_UopsSymbol *" def declare_variables(uop: Uop, out: CWriter, skip_inputs: bool) -> None: @@ -148,7 +148,7 @@ def write_uop( if not var.peek or is_override: out.emit(stack.push(var)) out.start_line() - stack.flush(out, cast_type="_Py_UOpsSymType *") + stack.flush(out, cast_type="_Py_UopsSymbol *") except SizeMismatch as ex: raise analysis_error(ex.args[0], uop.body[0]) From 3a72fc36f93d40048371b789e32eefc97b6ade63 Mon Sep 17 00:00:00 2001 From: Tahoma Software <bacon@tahoma.com> Date: Tue, 27 Feb 2024 08:33:05 -0500 Subject: [PATCH 477/507] gh-115315: Update time.rst to include microseconds field (%f) in chart (#115316) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/library/time.rst | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Doc/library/time.rst b/Doc/library/time.rst index 2782a961363666b..88c15f19bedf8c8 100644 --- a/Doc/library/time.rst +++ b/Doc/library/time.rst @@ -433,6 +433,10 @@ Functions | ``%d`` | Day of the month as a decimal number [01,31]. | | | | | | +-----------+------------------------------------------------+-------+ + | ``%f`` | Microseconds as a decimal number | \(1) | + | | [000000,999999]. | | + | | | | + +-----------+------------------------------------------------+-------+ | ``%H`` | Hour (24-hour clock) as a decimal number | | | | [00,23]. | | +-----------+------------------------------------------------+-------+ @@ -448,13 +452,13 @@ Functions | ``%M`` | Minute as a decimal number [00,59]. | | | | | | +-----------+------------------------------------------------+-------+ - | ``%p`` | Locale's equivalent of either AM or PM. | \(1) | + | ``%p`` | Locale's equivalent of either AM or PM. | \(2) | | | | | +-----------+------------------------------------------------+-------+ - | ``%S`` | Second as a decimal number [00,61]. | \(2) | + | ``%S`` | Second as a decimal number [00,61]. | \(3) | | | | | +-----------+------------------------------------------------+-------+ - | ``%U`` | Week number of the year (Sunday as the first | \(3) | + | ``%U`` | Week number of the year (Sunday as the first | \(4) | | | day of the week) as a decimal number [00,53]. | | | | All days in a new year preceding the first | | | | Sunday are considered to be in week 0. | | @@ -465,7 +469,7 @@ Functions | ``%w`` | Weekday as a decimal number [0(Sunday),6]. | | | | | | +-----------+------------------------------------------------+-------+ - | ``%W`` | Week number of the year (Monday as the first | \(3) | + | ``%W`` | Week number of the year (Monday as the first | \(4) | | | day of the week) as a decimal number [00,53]. | | | | All days in a new year preceding the first | | | | Monday are considered to be in week 0. | | @@ -500,17 +504,23 @@ Functions Notes: (1) + The ``%f`` format directive only applies to :func:`strptime`, + not to :func:`strftime`. However, see also :meth:`datetime.datetime.strptime` and + :meth:`datetime.datetime.strftime` where the ``%f`` format directive + :ref:`applies to microseconds <format-codes>`. + + (2) When used with the :func:`strptime` function, the ``%p`` directive only affects the output hour field if the ``%I`` directive is used to parse the hour. .. _leap-second: - (2) + (3) The range really is ``0`` to ``61``; value ``60`` is valid in timestamps representing `leap seconds`_ and value ``61`` is supported for historical reasons. - (3) + (4) When used with the :func:`strptime` function, ``%U`` and ``%W`` are only used in calculations when the day of the week and the year are specified. From 686ec17f506cddd0b14a8aad5849c15ffc20ed46 Mon Sep 17 00:00:00 2001 From: Miguel Brito <5544985+miguendes@users.noreply.github.com> Date: Tue, 27 Feb 2024 14:57:59 +0000 Subject: [PATCH 478/507] bpo-43952: Fix multiprocessing Listener authkey bug (GH-25845) Listener.accept() no longer hangs when authkey is an empty bytes object. --- Lib/multiprocessing/connection.py | 3 ++- Lib/test/_test_multiprocessing.py | 19 +++++++++++++++++++ .../2021-05-03-11-04-12.bpo-43952.Me7fJe.rst | 2 ++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2021-05-03-11-04-12.bpo-43952.Me7fJe.rst diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py index 58d697fdecacc05..b7e1e132172d020 100644 --- a/Lib/multiprocessing/connection.py +++ b/Lib/multiprocessing/connection.py @@ -476,8 +476,9 @@ def accept(self): ''' if self._listener is None: raise OSError('listener is closed') + c = self._listener.accept() - if self._authkey: + if self._authkey is not None: deliver_challenge(c, self._authkey) answer_challenge(c, self._authkey) return c diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index f70a693e641b4e7..058537bab5af260 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -3504,6 +3504,25 @@ def test_context(self): if self.TYPE == 'processes': self.assertRaises(OSError, l.accept) + def test_empty_authkey(self): + # bpo-43952: allow empty bytes as authkey + def handler(*args): + raise RuntimeError('Connection took too long...') + + def run(addr, authkey): + client = self.connection.Client(addr, authkey=authkey) + client.send(1729) + + key = b"" + + with self.connection.Listener(authkey=key) as listener: + threading.Thread(target=run, args=(listener.address, key)).start() + with listener.accept() as d: + self.assertEqual(d.recv(), 1729) + + if self.TYPE == 'processes': + self.assertRaises(OSError, listener.accept) + @unittest.skipUnless(util.abstract_sockets_supported, "test needs abstract socket support") def test_abstract_socket(self): diff --git a/Misc/NEWS.d/next/Library/2021-05-03-11-04-12.bpo-43952.Me7fJe.rst b/Misc/NEWS.d/next/Library/2021-05-03-11-04-12.bpo-43952.Me7fJe.rst new file mode 100644 index 000000000000000..e164619e44a3019 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-05-03-11-04-12.bpo-43952.Me7fJe.rst @@ -0,0 +1,2 @@ +Fix :meth:`multiprocessing.connection.Listener.accept()` to accept empty bytes +as authkey. Not accepting empty bytes as key causes it to hang indefinitely. From a355f60b032306651ca27bc53bbb82eb5106ff71 Mon Sep 17 00:00:00 2001 From: "Pierre Ossman (ThinLinc team)" <ossman@cendio.se> Date: Wed, 28 Feb 2024 02:27:44 +0100 Subject: [PATCH 479/507] gh-114914: Avoid keeping dead StreamWriter alive (#115661) In some cases we might cause a StreamWriter to stay alive even when the application has dropped all references to it. This prevents us from doing automatical cleanup, and complaining that the StreamWriter wasn't properly closed. Fortunately, the extra reference was never actually used for anything so we can just drop it. --- Lib/asyncio/streams.py | 14 +++-------- Lib/test/test_asyncio/test_streams.py | 25 +++++++++++++++++++ ...-02-19-15-52-30.gh-issue-114914.M5-1d8.rst | 2 ++ 3 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-19-15-52-30.gh-issue-114914.M5-1d8.rst diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py index df58b7a799a5add..3fe52dbac25c916 100644 --- a/Lib/asyncio/streams.py +++ b/Lib/asyncio/streams.py @@ -201,7 +201,6 @@ def __init__(self, stream_reader, client_connected_cb=None, loop=None): # is established. self._strong_reader = stream_reader self._reject_connection = False - self._stream_writer = None self._task = None self._transport = None self._client_connected_cb = client_connected_cb @@ -214,10 +213,8 @@ def _stream_reader(self): return None return self._stream_reader_wr() - def _replace_writer(self, writer): + def _replace_transport(self, transport): loop = self._loop - transport = writer.transport - self._stream_writer = writer self._transport = transport self._over_ssl = transport.get_extra_info('sslcontext') is not None @@ -239,11 +236,8 @@ def connection_made(self, transport): reader.set_transport(transport) self._over_ssl = transport.get_extra_info('sslcontext') is not None if self._client_connected_cb is not None: - self._stream_writer = StreamWriter(transport, self, - reader, - self._loop) - res = self._client_connected_cb(reader, - self._stream_writer) + writer = StreamWriter(transport, self, reader, self._loop) + res = self._client_connected_cb(reader, writer) if coroutines.iscoroutine(res): def callback(task): if task.cancelled(): @@ -405,7 +399,7 @@ async def start_tls(self, sslcontext, *, ssl_handshake_timeout=ssl_handshake_timeout, ssl_shutdown_timeout=ssl_shutdown_timeout) self._transport = new_transport - protocol._replace_writer(self) + protocol._replace_transport(new_transport) def __del__(self, warnings=warnings): if not self._transport.is_closing(): diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index 210990593adfa9b..bf123ebf9bd158f 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -1130,6 +1130,31 @@ async def inner(httpd): self.assertEqual(messages, []) + def test_unclosed_server_resource_warnings(self): + async def inner(rd, wr): + fut.set_result(True) + with self.assertWarns(ResourceWarning) as cm: + del wr + gc.collect() + self.assertEqual(len(cm.warnings), 1) + self.assertTrue(str(cm.warnings[0].message).startswith("unclosed <StreamWriter")) + + async def outer(): + srv = await asyncio.start_server(inner, socket_helper.HOSTv4, 0) + async with srv: + addr = srv.sockets[0].getsockname() + with socket.create_connection(addr): + # Give the loop some time to notice the connection + await fut + + messages = [] + self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx)) + + fut = self.loop.create_future() + self.loop.run_until_complete(outer()) + + self.assertEqual(messages, []) + def _basetest_unhandled_exceptions(self, handle_echo): port = socket_helper.find_unused_port() diff --git a/Misc/NEWS.d/next/Library/2024-02-19-15-52-30.gh-issue-114914.M5-1d8.rst b/Misc/NEWS.d/next/Library/2024-02-19-15-52-30.gh-issue-114914.M5-1d8.rst new file mode 100644 index 000000000000000..f7d392c8bceea3c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-19-15-52-30.gh-issue-114914.M5-1d8.rst @@ -0,0 +1,2 @@ +Fix an issue where an abandoned :class:`StreamWriter` would not be garbage +collected. From 5a1559d9493dd298a08c4be32b52295aa3eb89e5 Mon Sep 17 00:00:00 2001 From: "Pierre Ossman (ThinLinc team)" <ossman@cendio.se> Date: Wed, 28 Feb 2024 02:39:08 +0100 Subject: [PATCH 480/507] gh-112997: Don't log arguments in asyncio unless debugging (#115667) Nothing else in Python generally logs the contents of variables, so this can be very unexpected for developers and could leak sensitive information in to terminals and log files. --- Lib/asyncio/events.py | 6 +++-- Lib/asyncio/format_helpers.py | 22 +++++++++++------ Lib/test/test_asyncio/test_events.py | 24 ++++++++++++++++--- ...-02-19-16-53-48.gh-issue-112997.sYBXRZ.rst | 2 ++ 4 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-19-16-53-48.gh-issue-112997.sYBXRZ.rst diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py index 072a99fee123c39..680749325025db3 100644 --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -54,7 +54,8 @@ def _repr_info(self): info.append('cancelled') if self._callback is not None: info.append(format_helpers._format_callback_source( - self._callback, self._args)) + self._callback, self._args, + debug=self._loop.get_debug())) if self._source_traceback: frame = self._source_traceback[-1] info.append(f'created at {frame[0]}:{frame[1]}') @@ -90,7 +91,8 @@ def _run(self): raise except BaseException as exc: cb = format_helpers._format_callback_source( - self._callback, self._args) + self._callback, self._args, + debug=self._loop.get_debug()) msg = f'Exception in callback {cb}' context = { 'message': msg, diff --git a/Lib/asyncio/format_helpers.py b/Lib/asyncio/format_helpers.py index 27d11fd4fa9553e..93737b7708a67b6 100644 --- a/Lib/asyncio/format_helpers.py +++ b/Lib/asyncio/format_helpers.py @@ -19,19 +19,26 @@ def _get_function_source(func): return None -def _format_callback_source(func, args): - func_repr = _format_callback(func, args, None) +def _format_callback_source(func, args, *, debug=False): + func_repr = _format_callback(func, args, None, debug=debug) source = _get_function_source(func) if source: func_repr += f' at {source[0]}:{source[1]}' return func_repr -def _format_args_and_kwargs(args, kwargs): +def _format_args_and_kwargs(args, kwargs, *, debug=False): """Format function arguments and keyword arguments. Special case for a single parameter: ('hello',) is formatted as ('hello'). + + Note that this function only returns argument details when + debug=True is specified, as arguments may contain sensitive + information. """ + if not debug: + return '()' + # use reprlib to limit the length of the output items = [] if args: @@ -41,10 +48,11 @@ def _format_args_and_kwargs(args, kwargs): return '({})'.format(', '.join(items)) -def _format_callback(func, args, kwargs, suffix=''): +def _format_callback(func, args, kwargs, *, debug=False, suffix=''): if isinstance(func, functools.partial): - suffix = _format_args_and_kwargs(args, kwargs) + suffix - return _format_callback(func.func, func.args, func.keywords, suffix) + suffix = _format_args_and_kwargs(args, kwargs, debug=debug) + suffix + return _format_callback(func.func, func.args, func.keywords, + debug=debug, suffix=suffix) if hasattr(func, '__qualname__') and func.__qualname__: func_repr = func.__qualname__ @@ -53,7 +61,7 @@ def _format_callback(func, args, kwargs, suffix=''): else: func_repr = repr(func) - func_repr += _format_args_and_kwargs(args, kwargs) + func_repr += _format_args_and_kwargs(args, kwargs, debug=debug) if suffix: func_repr += suffix return func_repr diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index c92c88bd5b2429c..5b9c871e1d1b5a0 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -2250,7 +2250,7 @@ def test_handle_repr(self): h = asyncio.Handle(noop, (1, 2), self.loop) filename, lineno = test_utils.get_function_source(noop) self.assertEqual(repr(h), - '<Handle noop(1, 2) at %s:%s>' + '<Handle noop() at %s:%s>' % (filename, lineno)) # cancelled handle @@ -2268,14 +2268,14 @@ def test_handle_repr(self): # partial function cb = functools.partial(noop, 1, 2) h = asyncio.Handle(cb, (3,), self.loop) - regex = (r'^<Handle noop\(1, 2\)\(3\) at %s:%s>$' + regex = (r'^<Handle noop\(\)\(\) at %s:%s>$' % (re.escape(filename), lineno)) self.assertRegex(repr(h), regex) # partial function with keyword args cb = functools.partial(noop, x=1) h = asyncio.Handle(cb, (2, 3), self.loop) - regex = (r'^<Handle noop\(x=1\)\(2, 3\) at %s:%s>$' + regex = (r'^<Handle noop\(\)\(\) at %s:%s>$' % (re.escape(filename), lineno)) self.assertRegex(repr(h), regex) @@ -2316,6 +2316,24 @@ def test_handle_repr_debug(self): '<Handle cancelled noop(1, 2) at %s:%s created at %s:%s>' % (filename, lineno, create_filename, create_lineno)) + # partial function + cb = functools.partial(noop, 1, 2) + create_lineno = sys._getframe().f_lineno + 1 + h = asyncio.Handle(cb, (3,), self.loop) + regex = (r'^<Handle noop\(1, 2\)\(3\) at %s:%s created at %s:%s>$' + % (re.escape(filename), lineno, + re.escape(create_filename), create_lineno)) + self.assertRegex(repr(h), regex) + + # partial function with keyword args + cb = functools.partial(noop, x=1) + create_lineno = sys._getframe().f_lineno + 1 + h = asyncio.Handle(cb, (2, 3), self.loop) + regex = (r'^<Handle noop\(x=1\)\(2, 3\) at %s:%s created at %s:%s>$' + % (re.escape(filename), lineno, + re.escape(create_filename), create_lineno)) + self.assertRegex(repr(h), regex) + def test_handle_source_traceback(self): loop = asyncio.get_event_loop_policy().new_event_loop() loop.set_debug(True) diff --git a/Misc/NEWS.d/next/Library/2024-02-19-16-53-48.gh-issue-112997.sYBXRZ.rst b/Misc/NEWS.d/next/Library/2024-02-19-16-53-48.gh-issue-112997.sYBXRZ.rst new file mode 100644 index 000000000000000..4f97b2d6085ba0a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-19-16-53-48.gh-issue-112997.sYBXRZ.rst @@ -0,0 +1,2 @@ +Stop logging potentially sensitive callback arguments in :mod:`asyncio` +unless debug mode is active. From ed4dfd8825b49e16a0fcb9e67baf1b58bb8d438f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra <jelle.zijlstra@gmail.com> Date: Tue, 27 Feb 2024 18:13:03 -0800 Subject: [PATCH 481/507] gh-105858: Improve AST node constructors (#105880) Demonstration: >>> ast.FunctionDef.__annotations__ {'name': <class 'str'>, 'args': <class 'ast.arguments'>, 'body': list[ast.stmt], 'decorator_list': list[ast.expr], 'returns': ast.expr | None, 'type_comment': str | None, 'type_params': list[ast.type_param]} >>> ast.FunctionDef() <stdin>:1: DeprecationWarning: FunctionDef.__init__ missing 1 required positional argument: 'name'. This will become an error in Python 3.15. <stdin>:1: DeprecationWarning: FunctionDef.__init__ missing 1 required positional argument: 'args'. This will become an error in Python 3.15. <ast.FunctionDef object at 0x101959460> >>> node = ast.FunctionDef(name="foo", args=ast.arguments()) >>> node.decorator_list [] >>> ast.FunctionDef(whatever="you want", name="x", args=ast.arguments()) <stdin>:1: DeprecationWarning: FunctionDef.__init__ got an unexpected keyword argument 'whatever'. Support for arbitrary keyword arguments is deprecated and will be removed in Python 3.15. <ast.FunctionDef object at 0x1019581f0> --- Doc/library/ast.rst | 25 +- Doc/whatsnew/3.13.rst | 15 + .../pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + .../internal/pycore_runtime_init_generated.h | 1 + .../internal/pycore_unicodeobject_generated.h | 3 + Lib/test/test_ast.py | 101 +- ...-06-16-21-29-06.gh-issue-105858.Q7h0EV.rst | 8 + Parser/asdl_c.py | 238 +- Python/Python-ast.c | 4333 ++++++++++++++++- 10 files changed, 4676 insertions(+), 50 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-06-16-21-29-06.gh-issue-105858.Q7h0EV.rst diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index c943c2f498173e8..d629393f023c8a0 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -103,20 +103,15 @@ Node classes For example, to create and populate an :class:`ast.UnaryOp` node, you could use :: - node = ast.UnaryOp() - node.op = ast.USub() - node.operand = ast.Constant() - node.operand.value = 5 - node.operand.lineno = 0 - node.operand.col_offset = 0 - node.lineno = 0 - node.col_offset = 0 - - or the more compact :: - node = ast.UnaryOp(ast.USub(), ast.Constant(5, lineno=0, col_offset=0), lineno=0, col_offset=0) + If a field that is optional in the grammar is omitted from the constructor, + it defaults to ``None``. If a list field is omitted, it defaults to the empty + list. If any other field is omitted, a :exc:`DeprecationWarning` is raised + and the AST node will not have this field. In Python 3.15, this condition will + raise an error. + .. versionchanged:: 3.8 Class :class:`ast.Constant` is now used for all constants. @@ -140,6 +135,14 @@ Node classes In the meantime, instantiating them will return an instance of a different class. +.. deprecated-removed:: 3.13 3.15 + + Previous versions of Python allowed the creation of AST nodes that were missing + required fields. Similarly, AST node constructors allowed arbitrary keyword + arguments that were set as attributes of the AST node, even if they did not + match any of the fields of the AST node. This behavior is deprecated and will + be removed in Python 3.15. + .. note:: The descriptions of the specific node classes displayed here were initially adapted from the fantastic `Green Tree diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index ca5a0e7515994fe..3a277d7ce1585fc 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -206,6 +206,21 @@ array ast --- +* The constructors of node types in the :mod:`ast` module are now stricter + in the arguments they accept, and have more intuitive behaviour when + arguments are omitted. + + If an optional field on an AST node is not included as an argument when + constructing an instance, the field will now be set to ``None``. Similarly, + if a list field is omitted, that field will now be set to an empty list. + (Previously, in both cases, the attribute would be missing on the newly + constructed AST node instance.) + + If other arguments are omitted, a :exc:`DeprecationWarning` is emitted. + This will cause an exception in Python 3.15. Similarly, passing a keyword + argument that does not map to a field on the AST node is now deprecated, + and will raise an exception in Python 3.15. + * :func:`ast.parse` now accepts an optional argument ``optimize`` which is passed on to the :func:`compile` built-in. This makes it possible to obtain an optimized ``AST``. diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 3253b5271a9b7c8..e8b62bd0dcd3691 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -753,6 +753,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_check_retval_)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_dealloc_warn)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_feature_version)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_field_types)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_fields_)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_finalizing)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_find_and_load)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 8780f7ef9491651..b41f5c8952021e4 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -242,6 +242,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(_check_retval_) STRUCT_FOR_ID(_dealloc_warn) STRUCT_FOR_ID(_feature_version) + STRUCT_FOR_ID(_field_types) STRUCT_FOR_ID(_fields_) STRUCT_FOR_ID(_finalizing) STRUCT_FOR_ID(_find_and_load) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index a9d514856dab1ff..016eae02a2103d1 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -751,6 +751,7 @@ extern "C" { INIT_ID(_check_retval_), \ INIT_ID(_dealloc_warn), \ INIT_ID(_feature_version), \ + INIT_ID(_field_types), \ INIT_ID(_fields_), \ INIT_ID(_finalizing), \ INIT_ID(_find_and_load), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index f3b064e2a2cb258..64c4cf8c0770562 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -567,6 +567,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(_feature_version); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(_field_types); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(_fields_); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index d49c149da42592a..7cecf319e3638f0 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -525,17 +525,38 @@ def test_field_attr_existence(self): if name == 'Index': continue if self._is_ast_node(name, item): - x = item() + x = self._construct_ast_class(item) if isinstance(x, ast.AST): self.assertIs(type(x._fields), tuple) + def _construct_ast_class(self, cls): + kwargs = {} + for name, typ in cls.__annotations__.items(): + if typ is str: + kwargs[name] = 'capybara' + elif typ is int: + kwargs[name] = 42 + elif typ is object: + kwargs[name] = b'capybara' + elif isinstance(typ, type) and issubclass(typ, ast.AST): + kwargs[name] = self._construct_ast_class(typ) + return cls(**kwargs) + def test_arguments(self): x = ast.arguments() self.assertEqual(x._fields, ('posonlyargs', 'args', 'vararg', 'kwonlyargs', 'kw_defaults', 'kwarg', 'defaults')) - - with self.assertRaises(AttributeError): - x.args + self.assertEqual(x.__annotations__, { + 'posonlyargs': list[ast.arg], + 'args': list[ast.arg], + 'vararg': ast.arg | None, + 'kwonlyargs': list[ast.arg], + 'kw_defaults': list[ast.expr], + 'kwarg': ast.arg | None, + 'defaults': list[ast.expr], + }) + + self.assertEqual(x.args, []) self.assertIsNone(x.vararg) x = ast.arguments(*range(1, 8)) @@ -551,7 +572,7 @@ def test_field_attr_writable_deprecated(self): self.assertEqual(x._fields, 666) def test_field_attr_writable(self): - x = ast.Constant() + x = ast.Constant(1) # We can assign to _fields x._fields = 666 self.assertEqual(x._fields, 666) @@ -611,15 +632,22 @@ def test_classattrs_deprecated(self): self.assertEqual([str(w.message) for w in wlog], [ 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + "Constant.__init__ missing 1 required positional argument: 'value'. This will become " + 'an error in Python 3.15.', 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + "Constant.__init__ missing 1 required positional argument: 'value'. This will become " + 'an error in Python 3.15.', 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + "Constant.__init__ got an unexpected keyword argument 'foo'. Support for " + 'arbitrary keyword arguments is deprecated and will be removed in Python ' + '3.15.', 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', @@ -636,7 +664,8 @@ def test_classattrs_deprecated(self): ]) def test_classattrs(self): - x = ast.Constant() + with self.assertWarns(DeprecationWarning): + x = ast.Constant() self.assertEqual(x._fields, ('value', 'kind')) with self.assertRaises(AttributeError): @@ -651,7 +680,7 @@ def test_classattrs(self): with self.assertRaises(AttributeError): x.foobar - x = ast.Constant(lineno=2) + x = ast.Constant(lineno=2, value=3) self.assertEqual(x.lineno, 2) x = ast.Constant(42, lineno=0) @@ -662,8 +691,9 @@ def test_classattrs(self): self.assertRaises(TypeError, ast.Constant, 1, None, 2) self.assertRaises(TypeError, ast.Constant, 1, None, 2, lineno=0) - # Arbitrary keyword arguments are supported - self.assertEqual(ast.Constant(1, foo='bar').foo, 'bar') + # Arbitrary keyword arguments are supported (but deprecated) + with self.assertWarns(DeprecationWarning): + self.assertEqual(ast.Constant(1, foo='bar').foo, 'bar') with self.assertRaisesRegex(TypeError, "Constant got multiple values for argument 'value'"): ast.Constant(1, value=2) @@ -815,11 +845,11 @@ def test_isinstance(self): assertBytesDeprecated(self.assertNotIsInstance, Constant('42'), Bytes) assertNameConstantDeprecated(self.assertNotIsInstance, Constant(42), NameConstant) assertEllipsisDeprecated(self.assertNotIsInstance, Constant(42), Ellipsis) - assertNumDeprecated(self.assertNotIsInstance, Constant(), Num) - assertStrDeprecated(self.assertNotIsInstance, Constant(), Str) - assertBytesDeprecated(self.assertNotIsInstance, Constant(), Bytes) - assertNameConstantDeprecated(self.assertNotIsInstance, Constant(), NameConstant) - assertEllipsisDeprecated(self.assertNotIsInstance, Constant(), Ellipsis) + assertNumDeprecated(self.assertNotIsInstance, Constant(None), Num) + assertStrDeprecated(self.assertNotIsInstance, Constant(None), Str) + assertBytesDeprecated(self.assertNotIsInstance, Constant(None), Bytes) + assertNameConstantDeprecated(self.assertNotIsInstance, Constant(1), NameConstant) + assertEllipsisDeprecated(self.assertNotIsInstance, Constant(None), Ellipsis) class S(str): pass with assertStrDeprecated(): @@ -888,8 +918,9 @@ def test_module(self): self.assertEqual(x.body, body) def test_nodeclasses(self): - # Zero arguments constructor explicitly allowed - x = ast.BinOp() + # Zero arguments constructor explicitly allowed (but deprecated) + with self.assertWarns(DeprecationWarning): + x = ast.BinOp() self.assertEqual(x._fields, ('left', 'op', 'right')) # Random attribute allowed too @@ -927,8 +958,9 @@ def test_nodeclasses(self): self.assertEqual(x.right, 3) self.assertEqual(x.lineno, 0) - # Random kwargs also allowed - x = ast.BinOp(1, 2, 3, foobarbaz=42) + # Random kwargs also allowed (but deprecated) + with self.assertWarns(DeprecationWarning): + x = ast.BinOp(1, 2, 3, foobarbaz=42) self.assertEqual(x.foobarbaz, 42) def test_no_fields(self): @@ -941,8 +973,9 @@ def test_pickling(self): for protocol in range(pickle.HIGHEST_PROTOCOL + 1): for ast in (compile(i, "?", "exec", 0x400) for i in exec_tests): - ast2 = pickle.loads(pickle.dumps(ast, protocol)) - self.assertEqual(to_tuple(ast2), to_tuple(ast)) + with self.subTest(ast=ast, protocol=protocol): + ast2 = pickle.loads(pickle.dumps(ast, protocol)) + self.assertEqual(to_tuple(ast2), to_tuple(ast)) def test_invalid_sum(self): pos = dict(lineno=2, col_offset=3) @@ -1310,8 +1343,9 @@ def test_copy_location(self): 'lineno=1, col_offset=4, end_lineno=1, end_col_offset=5), lineno=1, ' 'col_offset=0, end_lineno=1, end_col_offset=5))' ) - src = ast.Call(col_offset=1, lineno=1, end_lineno=1, end_col_offset=1) - new = ast.copy_location(src, ast.Call(col_offset=None, lineno=None)) + func = ast.Name('spam', ast.Load()) + src = ast.Call(col_offset=1, lineno=1, end_lineno=1, end_col_offset=1, func=func) + new = ast.copy_location(src, ast.Call(col_offset=None, lineno=None, func=func)) self.assertIsNone(new.end_lineno) self.assertIsNone(new.end_col_offset) self.assertEqual(new.lineno, 1) @@ -1570,15 +1604,15 @@ def test_level_as_none(self): self.assertIn('sleep', ns) def test_recursion_direct(self): - e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0) + e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) e.operand = e with self.assertRaises(RecursionError): with support.infinite_recursion(): compile(ast.Expression(e), "<test>", "eval") def test_recursion_indirect(self): - e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0) - f = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0) + e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) + f = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) e.operand = f f.operand = e with self.assertRaises(RecursionError): @@ -2866,6 +2900,23 @@ def visit_Call(self, node: ast.Call): self.assertASTTransformation(PrintToLog, code, expected) +class ASTConstructorTests(unittest.TestCase): + """Test the autogenerated constructors for AST nodes.""" + + def test_FunctionDef(self): + args = ast.arguments() + self.assertEqual(args.args, []) + self.assertEqual(args.posonlyargs, []) + with self.assertWarnsRegex(DeprecationWarning, + r"FunctionDef\.__init__ missing 1 required positional argument: 'name'"): + node = ast.FunctionDef(args=args) + self.assertFalse(hasattr(node, "name")) + self.assertEqual(node.decorator_list, []) + node = ast.FunctionDef(name='foo', args=args) + self.assertEqual(node.name, 'foo') + self.assertEqual(node.decorator_list, []) + + @support.cpython_only class ModuleStateTests(unittest.TestCase): # bpo-41194, bpo-41261, bpo-41631: The _ast module uses a global state. diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-06-16-21-29-06.gh-issue-105858.Q7h0EV.rst b/Misc/NEWS.d/next/Core and Builtins/2023-06-16-21-29-06.gh-issue-105858.Q7h0EV.rst new file mode 100644 index 000000000000000..9338f662374c9ab --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-06-16-21-29-06.gh-issue-105858.Q7h0EV.rst @@ -0,0 +1,8 @@ +Improve the constructors for :mod:`ast` nodes. Arguments of list types now +default to an empty list if omitted, and optional fields default to ``None``. +AST nodes now have an +``__annotations__`` attribute with the expected types of their attributes. +Passing unrecognized extra arguments to AST nodes is deprecated and will +become an error in Python 3.15. Omitting a required argument to an AST node +is deprecated and will become an error in Python 3.15. Patch by Jelle +Zijlstra. diff --git a/Parser/asdl_c.py b/Parser/asdl_c.py index ce92672bf00776a..865fd76acf697d8 100755 --- a/Parser/asdl_c.py +++ b/Parser/asdl_c.py @@ -15,6 +15,13 @@ MAX_COL = 80 AUTOGEN_MESSAGE = "// File automatically generated by {}.\n\n" +builtin_type_to_c_type = { + "identifier": "PyUnicode_Type", + "string": "PyUnicode_Type", + "int": "PyLong_Type", + "constant": "PyBaseObject_Type", +} + def get_c_type(name): """Return a string for the C name of the type. @@ -764,6 +771,67 @@ def visitConstructor(self, cons, name): self.emit("};",0) +class AnnotationsVisitor(PickleVisitor): + def visitModule(self, mod): + self.file.write(textwrap.dedent(''' + static int + add_ast_annotations(struct ast_state *state) + { + bool cond; + ''')) + for dfn in mod.dfns: + self.visit(dfn) + self.file.write(textwrap.dedent(''' + return 1; + } + ''')) + + def visitProduct(self, prod, name): + self.emit_annotations(name, prod.fields) + + def visitSum(self, sum, name): + for t in sum.types: + self.visitConstructor(t, name) + + def visitConstructor(self, cons, name): + self.emit_annotations(cons.name, cons.fields) + + def emit_annotations(self, name, fields): + self.emit(f"PyObject *{name}_annotations = PyDict_New();", 1) + self.emit(f"if (!{name}_annotations) return 0;", 1) + for field in fields: + self.emit("{", 1) + if field.type in builtin_type_to_c_type: + self.emit(f"PyObject *type = (PyObject *)&{builtin_type_to_c_type[field.type]};", 2) + else: + self.emit(f"PyObject *type = state->{field.type}_type;", 2) + if field.opt: + self.emit("type = _Py_union_type_or(type, Py_None);", 2) + self.emit("cond = type != NULL;", 2) + self.emit_annotations_error(name, 2) + elif field.seq: + self.emit("type = Py_GenericAlias((PyObject *)&PyList_Type, type);", 2) + self.emit("cond = type != NULL;", 2) + self.emit_annotations_error(name, 2) + else: + self.emit("Py_INCREF(type);", 2) + self.emit(f"cond = PyDict_SetItemString({name}_annotations, \"{field.name}\", type) == 0;", 2) + self.emit("Py_DECREF(type);", 2) + self.emit_annotations_error(name, 2) + self.emit("}", 1) + self.emit(f'cond = PyObject_SetAttrString(state->{name}_type, "_field_types", {name}_annotations) == 0;', 1) + self.emit_annotations_error(name, 1) + self.emit(f'cond = PyObject_SetAttrString(state->{name}_type, "__annotations__", {name}_annotations) == 0;', 1) + self.emit_annotations_error(name, 1) + self.emit(f"Py_DECREF({name}_annotations);", 1) + + def emit_annotations_error(self, name, depth): + self.emit("if (!cond) {", depth) + self.emit(f"Py_DECREF({name}_annotations);", depth + 1) + self.emit("return 0;", depth + 1) + self.emit("}", depth) + + class PyTypesVisitor(PickleVisitor): def visitModule(self, mod): @@ -812,7 +880,7 @@ def visitModule(self, mod): Py_ssize_t i, numfields = 0; int res = -1; - PyObject *key, *value, *fields; + PyObject *key, *value, *fields, *remaining_fields = NULL; if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), state->_fields, &fields) < 0) { goto cleanup; } @@ -821,6 +889,13 @@ def visitModule(self, mod): if (numfields == -1) { goto cleanup; } + remaining_fields = PySet_New(fields); + } + else { + remaining_fields = PySet_New(NULL); + } + if (remaining_fields == NULL) { + goto cleanup; } res = 0; /* if no error occurs, this stays 0 to the end */ @@ -840,6 +915,11 @@ def visitModule(self, mod): goto cleanup; } res = PyObject_SetAttr(self, name, PyTuple_GET_ITEM(args, i)); + if (PySet_Discard(remaining_fields, name) < 0) { + res = -1; + Py_DECREF(name); + goto cleanup; + } Py_DECREF(name); if (res < 0) { goto cleanup; @@ -852,13 +932,14 @@ def visitModule(self, mod): if (contains == -1) { res = -1; goto cleanup; - } else if (contains == 1) { - Py_ssize_t p = PySequence_Index(fields, key); + } + else if (contains == 1) { + int p = PySet_Discard(remaining_fields, key); if (p == -1) { res = -1; goto cleanup; } - if (p < PyTuple_GET_SIZE(args)) { + if (p == 0) { PyErr_Format(PyExc_TypeError, "%.400s got multiple values for argument '%U'", Py_TYPE(self)->tp_name, key); @@ -866,15 +947,91 @@ def visitModule(self, mod): goto cleanup; } } + else if ( + PyUnicode_CompareWithASCIIString(key, "lineno") != 0 && + PyUnicode_CompareWithASCIIString(key, "col_offset") != 0 && + PyUnicode_CompareWithASCIIString(key, "end_lineno") != 0 && + PyUnicode_CompareWithASCIIString(key, "end_col_offset") != 0 + ) { + if (PyErr_WarnFormat( + PyExc_DeprecationWarning, 1, + "%.400s.__init__ got an unexpected keyword argument '%U'. " + "Support for arbitrary keyword arguments is deprecated " + "and will be removed in Python 3.15.", + Py_TYPE(self)->tp_name, key + ) < 0) { + res = -1; + goto cleanup; + } + } res = PyObject_SetAttr(self, key, value); if (res < 0) { goto cleanup; } } } + Py_ssize_t size = PySet_Size(remaining_fields); + PyObject *field_types = NULL, *remaining_list = NULL; + if (size > 0) { + if (!PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), &_Py_ID(_field_types), + &field_types)) { + res = -1; + goto cleanup; + } + remaining_list = PySequence_List(remaining_fields); + if (!remaining_list) { + goto set_remaining_cleanup; + } + for (Py_ssize_t i = 0; i < size; i++) { + PyObject *name = PyList_GET_ITEM(remaining_list, i); + PyObject *type = PyDict_GetItemWithError(field_types, name); + if (!type) { + if (!PyErr_Occurred()) { + PyErr_SetObject(PyExc_KeyError, name); + } + goto set_remaining_cleanup; + } + if (_PyUnion_Check(type)) { + // optional field + // do nothing, we'll have set a None default on the class + } + else if (Py_IS_TYPE(type, &Py_GenericAliasType)) { + // list field + PyObject *empty = PyList_New(0); + if (!empty) { + goto set_remaining_cleanup; + } + res = PyObject_SetAttr(self, name, empty); + Py_DECREF(empty); + if (res < 0) { + goto set_remaining_cleanup; + } + } + else { + // simple field (e.g., identifier) + if (PyErr_WarnFormat( + PyExc_DeprecationWarning, 1, + "%.400s.__init__ missing 1 required positional argument: '%U'. " + "This will become an error in Python 3.15.", + Py_TYPE(self)->tp_name, name + ) < 0) { + res = -1; + goto cleanup; + } + } + } + Py_DECREF(remaining_list); + Py_DECREF(field_types); + } cleanup: Py_XDECREF(fields); + Py_XDECREF(remaining_fields); return res; + set_remaining_cleanup: + Py_XDECREF(remaining_list); + Py_XDECREF(field_types); + res = -1; + goto cleanup; } /* Pickling support */ @@ -886,14 +1043,75 @@ def visitModule(self, mod): return NULL; } - PyObject *dict; + PyObject *dict = NULL, *fields = NULL, *remaining_fields = NULL, + *remaining_dict = NULL, *positional_args = NULL; if (PyObject_GetOptionalAttr(self, state->__dict__, &dict) < 0) { return NULL; } + PyObject *result = NULL; if (dict) { - return Py_BuildValue("O()N", Py_TYPE(self), dict); + // Serialize the fields as positional args if possible, because if we + // serialize them as a dict, during unpickling they are set only *after* + // the object is constructed, which will now trigger a DeprecationWarning + // if the AST type has required fields. + if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), state->_fields, &fields) < 0) { + goto cleanup; + } + if (fields) { + Py_ssize_t numfields = PySequence_Size(fields); + if (numfields == -1) { + Py_DECREF(dict); + goto cleanup; + } + remaining_dict = PyDict_Copy(dict); + Py_DECREF(dict); + if (!remaining_dict) { + goto cleanup; + } + positional_args = PyList_New(0); + if (!positional_args) { + goto cleanup; + } + for (Py_ssize_t i = 0; i < numfields; i++) { + PyObject *name = PySequence_GetItem(fields, i); + if (!name) { + goto cleanup; + } + PyObject *value = PyDict_GetItemWithError(remaining_dict, name); + if (!value) { + if (PyErr_Occurred()) { + goto cleanup; + } + break; + } + if (PyList_Append(positional_args, value) < 0) { + goto cleanup; + } + if (PyDict_DelItem(remaining_dict, name) < 0) { + goto cleanup; + } + Py_DECREF(name); + } + PyObject *args_tuple = PyList_AsTuple(positional_args); + if (!args_tuple) { + goto cleanup; + } + result = Py_BuildValue("ONO", Py_TYPE(self), args_tuple, + remaining_dict); + } + else { + result = Py_BuildValue("O()N", Py_TYPE(self), dict); + } + } + else { + result = Py_BuildValue("O()", Py_TYPE(self)); } - return Py_BuildValue("O()", Py_TYPE(self)); +cleanup: + Py_XDECREF(fields); + Py_XDECREF(remaining_fields); + Py_XDECREF(remaining_dict); + Py_XDECREF(positional_args); + return result; } static PyMemberDef ast_type_members[] = { @@ -1117,6 +1335,9 @@ def visitModule(self, mod): for dfn in mod.dfns: self.visit(dfn) self.file.write(textwrap.dedent(''' + if (!add_ast_annotations(state)) { + return -1; + } return 0; } ''')) @@ -1534,6 +1755,8 @@ def generate_module_def(mod, metadata, f, internal_h): #include "pycore_lock.h" // _PyOnceFlag #include "pycore_interp.h" // _PyInterpreterState.ast #include "pycore_pystate.h" // _PyInterpreterState_GET() + #include "pycore_unionobject.h" // _Py_union_type_or + #include "structmember.h" #include <stddef.h> struct validator { @@ -1651,6 +1874,7 @@ def write_source(mod, metadata, f, internal_h_file): v = ChainOfVisitors( SequenceConstructorVisitor(f), PyTypesDeclareVisitor(f), + AnnotationsVisitor(f), PyTypesVisitor(f), Obj2ModPrototypeVisitor(f), FunctionVisitor(f), diff --git a/Python/Python-ast.c b/Python/Python-ast.c index d77e986ba067a3e..46387493214829f 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -7,6 +7,8 @@ #include "pycore_lock.h" // _PyOnceFlag #include "pycore_interp.h" // _PyInterpreterState.ast #include "pycore_pystate.h" // _PyInterpreterState_GET() +#include "pycore_unionobject.h" // _Py_union_type_or +#include "structmember.h" #include <stddef.h> struct validator { @@ -816,6 +818,4170 @@ static const char * const TypeVarTuple_fields[]={ }; +static int +add_ast_annotations(struct ast_state *state) +{ + bool cond; + PyObject *Module_annotations = PyDict_New(); + if (!Module_annotations) return 0; + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Module_annotations); + return 0; + } + cond = PyDict_SetItemString(Module_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Module_annotations); + return 0; + } + } + { + PyObject *type = state->type_ignore_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Module_annotations); + return 0; + } + cond = PyDict_SetItemString(Module_annotations, "type_ignores", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Module_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Module_type, "_field_types", + Module_annotations) == 0; + if (!cond) { + Py_DECREF(Module_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Module_type, "__annotations__", + Module_annotations) == 0; + if (!cond) { + Py_DECREF(Module_annotations); + return 0; + } + Py_DECREF(Module_annotations); + PyObject *Interactive_annotations = PyDict_New(); + if (!Interactive_annotations) return 0; + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Interactive_annotations); + return 0; + } + cond = PyDict_SetItemString(Interactive_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Interactive_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Interactive_type, "_field_types", + Interactive_annotations) == 0; + if (!cond) { + Py_DECREF(Interactive_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Interactive_type, "__annotations__", + Interactive_annotations) == 0; + if (!cond) { + Py_DECREF(Interactive_annotations); + return 0; + } + Py_DECREF(Interactive_annotations); + PyObject *Expression_annotations = PyDict_New(); + if (!Expression_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Expression_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Expression_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Expression_type, "_field_types", + Expression_annotations) == 0; + if (!cond) { + Py_DECREF(Expression_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Expression_type, "__annotations__", + Expression_annotations) == 0; + if (!cond) { + Py_DECREF(Expression_annotations); + return 0; + } + Py_DECREF(Expression_annotations); + PyObject *FunctionType_annotations = PyDict_New(); + if (!FunctionType_annotations) return 0; + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(FunctionType_annotations); + return 0; + } + cond = PyDict_SetItemString(FunctionType_annotations, "argtypes", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FunctionType_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(FunctionType_annotations, "returns", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FunctionType_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->FunctionType_type, "_field_types", + FunctionType_annotations) == 0; + if (!cond) { + Py_DECREF(FunctionType_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->FunctionType_type, "__annotations__", + FunctionType_annotations) == 0; + if (!cond) { + Py_DECREF(FunctionType_annotations); + return 0; + } + Py_DECREF(FunctionType_annotations); + PyObject *FunctionDef_annotations = PyDict_New(); + if (!FunctionDef_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(FunctionDef_annotations, "name", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->arguments_type; + Py_INCREF(type); + cond = PyDict_SetItemString(FunctionDef_annotations, "args", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(FunctionDef_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(FunctionDef_annotations, "decorator_list", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(FunctionDef_annotations, "returns", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(FunctionDef_annotations, "type_comment", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->type_param_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(FunctionDef_annotations, "type_params", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->FunctionDef_type, "_field_types", + FunctionDef_annotations) == 0; + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->FunctionDef_type, "__annotations__", + FunctionDef_annotations) == 0; + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + Py_DECREF(FunctionDef_annotations); + PyObject *AsyncFunctionDef_annotations = PyDict_New(); + if (!AsyncFunctionDef_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(AsyncFunctionDef_annotations, "name", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->arguments_type; + Py_INCREF(type); + cond = PyDict_SetItemString(AsyncFunctionDef_annotations, "args", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncFunctionDef_annotations, "body", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncFunctionDef_annotations, + "decorator_list", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncFunctionDef_annotations, "returns", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncFunctionDef_annotations, + "type_comment", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->type_param_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncFunctionDef_annotations, + "type_params", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->AsyncFunctionDef_type, "_field_types", + AsyncFunctionDef_annotations) == 0; + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->AsyncFunctionDef_type, + "__annotations__", + AsyncFunctionDef_annotations) == 0; + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + Py_DECREF(AsyncFunctionDef_annotations); + PyObject *ClassDef_annotations = PyDict_New(); + if (!ClassDef_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(ClassDef_annotations, "name", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + cond = PyDict_SetItemString(ClassDef_annotations, "bases", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + } + { + PyObject *type = state->keyword_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + cond = PyDict_SetItemString(ClassDef_annotations, "keywords", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + cond = PyDict_SetItemString(ClassDef_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + cond = PyDict_SetItemString(ClassDef_annotations, "decorator_list", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + } + { + PyObject *type = state->type_param_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + cond = PyDict_SetItemString(ClassDef_annotations, "type_params", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->ClassDef_type, "_field_types", + ClassDef_annotations) == 0; + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->ClassDef_type, "__annotations__", + ClassDef_annotations) == 0; + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + Py_DECREF(ClassDef_annotations); + PyObject *Return_annotations = PyDict_New(); + if (!Return_annotations) return 0; + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Return_annotations); + return 0; + } + cond = PyDict_SetItemString(Return_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Return_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Return_type, "_field_types", + Return_annotations) == 0; + if (!cond) { + Py_DECREF(Return_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Return_type, "__annotations__", + Return_annotations) == 0; + if (!cond) { + Py_DECREF(Return_annotations); + return 0; + } + Py_DECREF(Return_annotations); + PyObject *Delete_annotations = PyDict_New(); + if (!Delete_annotations) return 0; + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Delete_annotations); + return 0; + } + cond = PyDict_SetItemString(Delete_annotations, "targets", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Delete_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Delete_type, "_field_types", + Delete_annotations) == 0; + if (!cond) { + Py_DECREF(Delete_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Delete_type, "__annotations__", + Delete_annotations) == 0; + if (!cond) { + Py_DECREF(Delete_annotations); + return 0; + } + Py_DECREF(Delete_annotations); + PyObject *Assign_annotations = PyDict_New(); + if (!Assign_annotations) return 0; + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Assign_annotations); + return 0; + } + cond = PyDict_SetItemString(Assign_annotations, "targets", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Assign_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Assign_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Assign_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Assign_annotations); + return 0; + } + cond = PyDict_SetItemString(Assign_annotations, "type_comment", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Assign_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Assign_type, "_field_types", + Assign_annotations) == 0; + if (!cond) { + Py_DECREF(Assign_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Assign_type, "__annotations__", + Assign_annotations) == 0; + if (!cond) { + Py_DECREF(Assign_annotations); + return 0; + } + Py_DECREF(Assign_annotations); + PyObject *TypeAlias_annotations = PyDict_New(); + if (!TypeAlias_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(TypeAlias_annotations, "name", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TypeAlias_annotations); + return 0; + } + } + { + PyObject *type = state->type_param_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(TypeAlias_annotations); + return 0; + } + cond = PyDict_SetItemString(TypeAlias_annotations, "type_params", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TypeAlias_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(TypeAlias_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TypeAlias_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->TypeAlias_type, "_field_types", + TypeAlias_annotations) == 0; + if (!cond) { + Py_DECREF(TypeAlias_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->TypeAlias_type, "__annotations__", + TypeAlias_annotations) == 0; + if (!cond) { + Py_DECREF(TypeAlias_annotations); + return 0; + } + Py_DECREF(TypeAlias_annotations); + PyObject *AugAssign_annotations = PyDict_New(); + if (!AugAssign_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(AugAssign_annotations, "target", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AugAssign_annotations); + return 0; + } + } + { + PyObject *type = state->operator_type; + Py_INCREF(type); + cond = PyDict_SetItemString(AugAssign_annotations, "op", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AugAssign_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(AugAssign_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AugAssign_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->AugAssign_type, "_field_types", + AugAssign_annotations) == 0; + if (!cond) { + Py_DECREF(AugAssign_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->AugAssign_type, "__annotations__", + AugAssign_annotations) == 0; + if (!cond) { + Py_DECREF(AugAssign_annotations); + return 0; + } + Py_DECREF(AugAssign_annotations); + PyObject *AnnAssign_annotations = PyDict_New(); + if (!AnnAssign_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(AnnAssign_annotations, "target", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AnnAssign_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(AnnAssign_annotations, "annotation", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AnnAssign_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(AnnAssign_annotations); + return 0; + } + cond = PyDict_SetItemString(AnnAssign_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AnnAssign_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyLong_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(AnnAssign_annotations, "simple", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AnnAssign_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->AnnAssign_type, "_field_types", + AnnAssign_annotations) == 0; + if (!cond) { + Py_DECREF(AnnAssign_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->AnnAssign_type, "__annotations__", + AnnAssign_annotations) == 0; + if (!cond) { + Py_DECREF(AnnAssign_annotations); + return 0; + } + Py_DECREF(AnnAssign_annotations); + PyObject *For_annotations = PyDict_New(); + if (!For_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(For_annotations, "target", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(For_annotations, "iter", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + cond = PyDict_SetItemString(For_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + cond = PyDict_SetItemString(For_annotations, "orelse", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + cond = PyDict_SetItemString(For_annotations, "type_comment", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->For_type, "_field_types", + For_annotations) == 0; + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->For_type, "__annotations__", + For_annotations) == 0; + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + Py_DECREF(For_annotations); + PyObject *AsyncFor_annotations = PyDict_New(); + if (!AsyncFor_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(AsyncFor_annotations, "target", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(AsyncFor_annotations, "iter", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncFor_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncFor_annotations, "orelse", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncFor_annotations, "type_comment", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->AsyncFor_type, "_field_types", + AsyncFor_annotations) == 0; + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->AsyncFor_type, "__annotations__", + AsyncFor_annotations) == 0; + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + Py_DECREF(AsyncFor_annotations); + PyObject *While_annotations = PyDict_New(); + if (!While_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(While_annotations, "test", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(While_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(While_annotations); + return 0; + } + cond = PyDict_SetItemString(While_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(While_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(While_annotations); + return 0; + } + cond = PyDict_SetItemString(While_annotations, "orelse", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(While_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->While_type, "_field_types", + While_annotations) == 0; + if (!cond) { + Py_DECREF(While_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->While_type, "__annotations__", + While_annotations) == 0; + if (!cond) { + Py_DECREF(While_annotations); + return 0; + } + Py_DECREF(While_annotations); + PyObject *If_annotations = PyDict_New(); + if (!If_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(If_annotations, "test", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(If_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(If_annotations); + return 0; + } + cond = PyDict_SetItemString(If_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(If_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(If_annotations); + return 0; + } + cond = PyDict_SetItemString(If_annotations, "orelse", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(If_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->If_type, "_field_types", + If_annotations) == 0; + if (!cond) { + Py_DECREF(If_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->If_type, "__annotations__", + If_annotations) == 0; + if (!cond) { + Py_DECREF(If_annotations); + return 0; + } + Py_DECREF(If_annotations); + PyObject *With_annotations = PyDict_New(); + if (!With_annotations) return 0; + { + PyObject *type = state->withitem_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(With_annotations); + return 0; + } + cond = PyDict_SetItemString(With_annotations, "items", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(With_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(With_annotations); + return 0; + } + cond = PyDict_SetItemString(With_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(With_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(With_annotations); + return 0; + } + cond = PyDict_SetItemString(With_annotations, "type_comment", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(With_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->With_type, "_field_types", + With_annotations) == 0; + if (!cond) { + Py_DECREF(With_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->With_type, "__annotations__", + With_annotations) == 0; + if (!cond) { + Py_DECREF(With_annotations); + return 0; + } + Py_DECREF(With_annotations); + PyObject *AsyncWith_annotations = PyDict_New(); + if (!AsyncWith_annotations) return 0; + { + PyObject *type = state->withitem_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncWith_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncWith_annotations, "items", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncWith_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncWith_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncWith_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncWith_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncWith_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncWith_annotations, "type_comment", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncWith_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->AsyncWith_type, "_field_types", + AsyncWith_annotations) == 0; + if (!cond) { + Py_DECREF(AsyncWith_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->AsyncWith_type, "__annotations__", + AsyncWith_annotations) == 0; + if (!cond) { + Py_DECREF(AsyncWith_annotations); + return 0; + } + Py_DECREF(AsyncWith_annotations); + PyObject *Match_annotations = PyDict_New(); + if (!Match_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Match_annotations, "subject", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Match_annotations); + return 0; + } + } + { + PyObject *type = state->match_case_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Match_annotations); + return 0; + } + cond = PyDict_SetItemString(Match_annotations, "cases", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Match_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Match_type, "_field_types", + Match_annotations) == 0; + if (!cond) { + Py_DECREF(Match_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Match_type, "__annotations__", + Match_annotations) == 0; + if (!cond) { + Py_DECREF(Match_annotations); + return 0; + } + Py_DECREF(Match_annotations); + PyObject *Raise_annotations = PyDict_New(); + if (!Raise_annotations) return 0; + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Raise_annotations); + return 0; + } + cond = PyDict_SetItemString(Raise_annotations, "exc", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Raise_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Raise_annotations); + return 0; + } + cond = PyDict_SetItemString(Raise_annotations, "cause", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Raise_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Raise_type, "_field_types", + Raise_annotations) == 0; + if (!cond) { + Py_DECREF(Raise_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Raise_type, "__annotations__", + Raise_annotations) == 0; + if (!cond) { + Py_DECREF(Raise_annotations); + return 0; + } + Py_DECREF(Raise_annotations); + PyObject *Try_annotations = PyDict_New(); + if (!Try_annotations) return 0; + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + cond = PyDict_SetItemString(Try_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + } + { + PyObject *type = state->excepthandler_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + cond = PyDict_SetItemString(Try_annotations, "handlers", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + cond = PyDict_SetItemString(Try_annotations, "orelse", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + cond = PyDict_SetItemString(Try_annotations, "finalbody", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Try_type, "_field_types", + Try_annotations) == 0; + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Try_type, "__annotations__", + Try_annotations) == 0; + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + Py_DECREF(Try_annotations); + PyObject *TryStar_annotations = PyDict_New(); + if (!TryStar_annotations) return 0; + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + cond = PyDict_SetItemString(TryStar_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + } + { + PyObject *type = state->excepthandler_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + cond = PyDict_SetItemString(TryStar_annotations, "handlers", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + cond = PyDict_SetItemString(TryStar_annotations, "orelse", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + cond = PyDict_SetItemString(TryStar_annotations, "finalbody", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->TryStar_type, "_field_types", + TryStar_annotations) == 0; + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->TryStar_type, "__annotations__", + TryStar_annotations) == 0; + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + Py_DECREF(TryStar_annotations); + PyObject *Assert_annotations = PyDict_New(); + if (!Assert_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Assert_annotations, "test", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Assert_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Assert_annotations); + return 0; + } + cond = PyDict_SetItemString(Assert_annotations, "msg", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Assert_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Assert_type, "_field_types", + Assert_annotations) == 0; + if (!cond) { + Py_DECREF(Assert_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Assert_type, "__annotations__", + Assert_annotations) == 0; + if (!cond) { + Py_DECREF(Assert_annotations); + return 0; + } + Py_DECREF(Assert_annotations); + PyObject *Import_annotations = PyDict_New(); + if (!Import_annotations) return 0; + { + PyObject *type = state->alias_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Import_annotations); + return 0; + } + cond = PyDict_SetItemString(Import_annotations, "names", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Import_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Import_type, "_field_types", + Import_annotations) == 0; + if (!cond) { + Py_DECREF(Import_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Import_type, "__annotations__", + Import_annotations) == 0; + if (!cond) { + Py_DECREF(Import_annotations); + return 0; + } + Py_DECREF(Import_annotations); + PyObject *ImportFrom_annotations = PyDict_New(); + if (!ImportFrom_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(ImportFrom_annotations); + return 0; + } + cond = PyDict_SetItemString(ImportFrom_annotations, "module", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ImportFrom_annotations); + return 0; + } + } + { + PyObject *type = state->alias_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(ImportFrom_annotations); + return 0; + } + cond = PyDict_SetItemString(ImportFrom_annotations, "names", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ImportFrom_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyLong_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(ImportFrom_annotations); + return 0; + } + cond = PyDict_SetItemString(ImportFrom_annotations, "level", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ImportFrom_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->ImportFrom_type, "_field_types", + ImportFrom_annotations) == 0; + if (!cond) { + Py_DECREF(ImportFrom_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->ImportFrom_type, "__annotations__", + ImportFrom_annotations) == 0; + if (!cond) { + Py_DECREF(ImportFrom_annotations); + return 0; + } + Py_DECREF(ImportFrom_annotations); + PyObject *Global_annotations = PyDict_New(); + if (!Global_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Global_annotations); + return 0; + } + cond = PyDict_SetItemString(Global_annotations, "names", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Global_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Global_type, "_field_types", + Global_annotations) == 0; + if (!cond) { + Py_DECREF(Global_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Global_type, "__annotations__", + Global_annotations) == 0; + if (!cond) { + Py_DECREF(Global_annotations); + return 0; + } + Py_DECREF(Global_annotations); + PyObject *Nonlocal_annotations = PyDict_New(); + if (!Nonlocal_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Nonlocal_annotations); + return 0; + } + cond = PyDict_SetItemString(Nonlocal_annotations, "names", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Nonlocal_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Nonlocal_type, "_field_types", + Nonlocal_annotations) == 0; + if (!cond) { + Py_DECREF(Nonlocal_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Nonlocal_type, "__annotations__", + Nonlocal_annotations) == 0; + if (!cond) { + Py_DECREF(Nonlocal_annotations); + return 0; + } + Py_DECREF(Nonlocal_annotations); + PyObject *Expr_annotations = PyDict_New(); + if (!Expr_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Expr_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Expr_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Expr_type, "_field_types", + Expr_annotations) == 0; + if (!cond) { + Py_DECREF(Expr_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Expr_type, "__annotations__", + Expr_annotations) == 0; + if (!cond) { + Py_DECREF(Expr_annotations); + return 0; + } + Py_DECREF(Expr_annotations); + PyObject *Pass_annotations = PyDict_New(); + if (!Pass_annotations) return 0; + cond = PyObject_SetAttrString(state->Pass_type, "_field_types", + Pass_annotations) == 0; + if (!cond) { + Py_DECREF(Pass_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Pass_type, "__annotations__", + Pass_annotations) == 0; + if (!cond) { + Py_DECREF(Pass_annotations); + return 0; + } + Py_DECREF(Pass_annotations); + PyObject *Break_annotations = PyDict_New(); + if (!Break_annotations) return 0; + cond = PyObject_SetAttrString(state->Break_type, "_field_types", + Break_annotations) == 0; + if (!cond) { + Py_DECREF(Break_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Break_type, "__annotations__", + Break_annotations) == 0; + if (!cond) { + Py_DECREF(Break_annotations); + return 0; + } + Py_DECREF(Break_annotations); + PyObject *Continue_annotations = PyDict_New(); + if (!Continue_annotations) return 0; + cond = PyObject_SetAttrString(state->Continue_type, "_field_types", + Continue_annotations) == 0; + if (!cond) { + Py_DECREF(Continue_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Continue_type, "__annotations__", + Continue_annotations) == 0; + if (!cond) { + Py_DECREF(Continue_annotations); + return 0; + } + Py_DECREF(Continue_annotations); + PyObject *BoolOp_annotations = PyDict_New(); + if (!BoolOp_annotations) return 0; + { + PyObject *type = state->boolop_type; + Py_INCREF(type); + cond = PyDict_SetItemString(BoolOp_annotations, "op", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(BoolOp_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(BoolOp_annotations); + return 0; + } + cond = PyDict_SetItemString(BoolOp_annotations, "values", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(BoolOp_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->BoolOp_type, "_field_types", + BoolOp_annotations) == 0; + if (!cond) { + Py_DECREF(BoolOp_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->BoolOp_type, "__annotations__", + BoolOp_annotations) == 0; + if (!cond) { + Py_DECREF(BoolOp_annotations); + return 0; + } + Py_DECREF(BoolOp_annotations); + PyObject *NamedExpr_annotations = PyDict_New(); + if (!NamedExpr_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(NamedExpr_annotations, "target", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(NamedExpr_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(NamedExpr_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(NamedExpr_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->NamedExpr_type, "_field_types", + NamedExpr_annotations) == 0; + if (!cond) { + Py_DECREF(NamedExpr_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->NamedExpr_type, "__annotations__", + NamedExpr_annotations) == 0; + if (!cond) { + Py_DECREF(NamedExpr_annotations); + return 0; + } + Py_DECREF(NamedExpr_annotations); + PyObject *BinOp_annotations = PyDict_New(); + if (!BinOp_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(BinOp_annotations, "left", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(BinOp_annotations); + return 0; + } + } + { + PyObject *type = state->operator_type; + Py_INCREF(type); + cond = PyDict_SetItemString(BinOp_annotations, "op", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(BinOp_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(BinOp_annotations, "right", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(BinOp_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->BinOp_type, "_field_types", + BinOp_annotations) == 0; + if (!cond) { + Py_DECREF(BinOp_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->BinOp_type, "__annotations__", + BinOp_annotations) == 0; + if (!cond) { + Py_DECREF(BinOp_annotations); + return 0; + } + Py_DECREF(BinOp_annotations); + PyObject *UnaryOp_annotations = PyDict_New(); + if (!UnaryOp_annotations) return 0; + { + PyObject *type = state->unaryop_type; + Py_INCREF(type); + cond = PyDict_SetItemString(UnaryOp_annotations, "op", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(UnaryOp_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(UnaryOp_annotations, "operand", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(UnaryOp_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->UnaryOp_type, "_field_types", + UnaryOp_annotations) == 0; + if (!cond) { + Py_DECREF(UnaryOp_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->UnaryOp_type, "__annotations__", + UnaryOp_annotations) == 0; + if (!cond) { + Py_DECREF(UnaryOp_annotations); + return 0; + } + Py_DECREF(UnaryOp_annotations); + PyObject *Lambda_annotations = PyDict_New(); + if (!Lambda_annotations) return 0; + { + PyObject *type = state->arguments_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Lambda_annotations, "args", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Lambda_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Lambda_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Lambda_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Lambda_type, "_field_types", + Lambda_annotations) == 0; + if (!cond) { + Py_DECREF(Lambda_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Lambda_type, "__annotations__", + Lambda_annotations) == 0; + if (!cond) { + Py_DECREF(Lambda_annotations); + return 0; + } + Py_DECREF(Lambda_annotations); + PyObject *IfExp_annotations = PyDict_New(); + if (!IfExp_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(IfExp_annotations, "test", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(IfExp_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(IfExp_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(IfExp_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(IfExp_annotations, "orelse", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(IfExp_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->IfExp_type, "_field_types", + IfExp_annotations) == 0; + if (!cond) { + Py_DECREF(IfExp_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->IfExp_type, "__annotations__", + IfExp_annotations) == 0; + if (!cond) { + Py_DECREF(IfExp_annotations); + return 0; + } + Py_DECREF(IfExp_annotations); + PyObject *Dict_annotations = PyDict_New(); + if (!Dict_annotations) return 0; + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Dict_annotations); + return 0; + } + cond = PyDict_SetItemString(Dict_annotations, "keys", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Dict_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Dict_annotations); + return 0; + } + cond = PyDict_SetItemString(Dict_annotations, "values", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Dict_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Dict_type, "_field_types", + Dict_annotations) == 0; + if (!cond) { + Py_DECREF(Dict_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Dict_type, "__annotations__", + Dict_annotations) == 0; + if (!cond) { + Py_DECREF(Dict_annotations); + return 0; + } + Py_DECREF(Dict_annotations); + PyObject *Set_annotations = PyDict_New(); + if (!Set_annotations) return 0; + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Set_annotations); + return 0; + } + cond = PyDict_SetItemString(Set_annotations, "elts", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Set_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Set_type, "_field_types", + Set_annotations) == 0; + if (!cond) { + Py_DECREF(Set_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Set_type, "__annotations__", + Set_annotations) == 0; + if (!cond) { + Py_DECREF(Set_annotations); + return 0; + } + Py_DECREF(Set_annotations); + PyObject *ListComp_annotations = PyDict_New(); + if (!ListComp_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(ListComp_annotations, "elt", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ListComp_annotations); + return 0; + } + } + { + PyObject *type = state->comprehension_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(ListComp_annotations); + return 0; + } + cond = PyDict_SetItemString(ListComp_annotations, "generators", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ListComp_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->ListComp_type, "_field_types", + ListComp_annotations) == 0; + if (!cond) { + Py_DECREF(ListComp_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->ListComp_type, "__annotations__", + ListComp_annotations) == 0; + if (!cond) { + Py_DECREF(ListComp_annotations); + return 0; + } + Py_DECREF(ListComp_annotations); + PyObject *SetComp_annotations = PyDict_New(); + if (!SetComp_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(SetComp_annotations, "elt", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(SetComp_annotations); + return 0; + } + } + { + PyObject *type = state->comprehension_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(SetComp_annotations); + return 0; + } + cond = PyDict_SetItemString(SetComp_annotations, "generators", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(SetComp_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->SetComp_type, "_field_types", + SetComp_annotations) == 0; + if (!cond) { + Py_DECREF(SetComp_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->SetComp_type, "__annotations__", + SetComp_annotations) == 0; + if (!cond) { + Py_DECREF(SetComp_annotations); + return 0; + } + Py_DECREF(SetComp_annotations); + PyObject *DictComp_annotations = PyDict_New(); + if (!DictComp_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(DictComp_annotations, "key", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(DictComp_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(DictComp_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(DictComp_annotations); + return 0; + } + } + { + PyObject *type = state->comprehension_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(DictComp_annotations); + return 0; + } + cond = PyDict_SetItemString(DictComp_annotations, "generators", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(DictComp_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->DictComp_type, "_field_types", + DictComp_annotations) == 0; + if (!cond) { + Py_DECREF(DictComp_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->DictComp_type, "__annotations__", + DictComp_annotations) == 0; + if (!cond) { + Py_DECREF(DictComp_annotations); + return 0; + } + Py_DECREF(DictComp_annotations); + PyObject *GeneratorExp_annotations = PyDict_New(); + if (!GeneratorExp_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(GeneratorExp_annotations, "elt", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(GeneratorExp_annotations); + return 0; + } + } + { + PyObject *type = state->comprehension_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(GeneratorExp_annotations); + return 0; + } + cond = PyDict_SetItemString(GeneratorExp_annotations, "generators", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(GeneratorExp_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->GeneratorExp_type, "_field_types", + GeneratorExp_annotations) == 0; + if (!cond) { + Py_DECREF(GeneratorExp_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->GeneratorExp_type, "__annotations__", + GeneratorExp_annotations) == 0; + if (!cond) { + Py_DECREF(GeneratorExp_annotations); + return 0; + } + Py_DECREF(GeneratorExp_annotations); + PyObject *Await_annotations = PyDict_New(); + if (!Await_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Await_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Await_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Await_type, "_field_types", + Await_annotations) == 0; + if (!cond) { + Py_DECREF(Await_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Await_type, "__annotations__", + Await_annotations) == 0; + if (!cond) { + Py_DECREF(Await_annotations); + return 0; + } + Py_DECREF(Await_annotations); + PyObject *Yield_annotations = PyDict_New(); + if (!Yield_annotations) return 0; + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Yield_annotations); + return 0; + } + cond = PyDict_SetItemString(Yield_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Yield_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Yield_type, "_field_types", + Yield_annotations) == 0; + if (!cond) { + Py_DECREF(Yield_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Yield_type, "__annotations__", + Yield_annotations) == 0; + if (!cond) { + Py_DECREF(Yield_annotations); + return 0; + } + Py_DECREF(Yield_annotations); + PyObject *YieldFrom_annotations = PyDict_New(); + if (!YieldFrom_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(YieldFrom_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(YieldFrom_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->YieldFrom_type, "_field_types", + YieldFrom_annotations) == 0; + if (!cond) { + Py_DECREF(YieldFrom_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->YieldFrom_type, "__annotations__", + YieldFrom_annotations) == 0; + if (!cond) { + Py_DECREF(YieldFrom_annotations); + return 0; + } + Py_DECREF(YieldFrom_annotations); + PyObject *Compare_annotations = PyDict_New(); + if (!Compare_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Compare_annotations, "left", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Compare_annotations); + return 0; + } + } + { + PyObject *type = state->cmpop_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Compare_annotations); + return 0; + } + cond = PyDict_SetItemString(Compare_annotations, "ops", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Compare_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Compare_annotations); + return 0; + } + cond = PyDict_SetItemString(Compare_annotations, "comparators", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Compare_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Compare_type, "_field_types", + Compare_annotations) == 0; + if (!cond) { + Py_DECREF(Compare_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Compare_type, "__annotations__", + Compare_annotations) == 0; + if (!cond) { + Py_DECREF(Compare_annotations); + return 0; + } + Py_DECREF(Compare_annotations); + PyObject *Call_annotations = PyDict_New(); + if (!Call_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Call_annotations, "func", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Call_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Call_annotations); + return 0; + } + cond = PyDict_SetItemString(Call_annotations, "args", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Call_annotations); + return 0; + } + } + { + PyObject *type = state->keyword_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Call_annotations); + return 0; + } + cond = PyDict_SetItemString(Call_annotations, "keywords", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Call_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Call_type, "_field_types", + Call_annotations) == 0; + if (!cond) { + Py_DECREF(Call_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Call_type, "__annotations__", + Call_annotations) == 0; + if (!cond) { + Py_DECREF(Call_annotations); + return 0; + } + Py_DECREF(Call_annotations); + PyObject *FormattedValue_annotations = PyDict_New(); + if (!FormattedValue_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(FormattedValue_annotations, "value", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FormattedValue_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyLong_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(FormattedValue_annotations, "conversion", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FormattedValue_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(FormattedValue_annotations); + return 0; + } + cond = PyDict_SetItemString(FormattedValue_annotations, "format_spec", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FormattedValue_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->FormattedValue_type, "_field_types", + FormattedValue_annotations) == 0; + if (!cond) { + Py_DECREF(FormattedValue_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->FormattedValue_type, + "__annotations__", + FormattedValue_annotations) == 0; + if (!cond) { + Py_DECREF(FormattedValue_annotations); + return 0; + } + Py_DECREF(FormattedValue_annotations); + PyObject *JoinedStr_annotations = PyDict_New(); + if (!JoinedStr_annotations) return 0; + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(JoinedStr_annotations); + return 0; + } + cond = PyDict_SetItemString(JoinedStr_annotations, "values", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(JoinedStr_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->JoinedStr_type, "_field_types", + JoinedStr_annotations) == 0; + if (!cond) { + Py_DECREF(JoinedStr_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->JoinedStr_type, "__annotations__", + JoinedStr_annotations) == 0; + if (!cond) { + Py_DECREF(JoinedStr_annotations); + return 0; + } + Py_DECREF(JoinedStr_annotations); + PyObject *Constant_annotations = PyDict_New(); + if (!Constant_annotations) return 0; + { + PyObject *type = (PyObject *)&PyBaseObject_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(Constant_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Constant_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Constant_annotations); + return 0; + } + cond = PyDict_SetItemString(Constant_annotations, "kind", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Constant_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Constant_type, "_field_types", + Constant_annotations) == 0; + if (!cond) { + Py_DECREF(Constant_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Constant_type, "__annotations__", + Constant_annotations) == 0; + if (!cond) { + Py_DECREF(Constant_annotations); + return 0; + } + Py_DECREF(Constant_annotations); + PyObject *Attribute_annotations = PyDict_New(); + if (!Attribute_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Attribute_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Attribute_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(Attribute_annotations, "attr", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Attribute_annotations); + return 0; + } + } + { + PyObject *type = state->expr_context_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Attribute_annotations, "ctx", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Attribute_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Attribute_type, "_field_types", + Attribute_annotations) == 0; + if (!cond) { + Py_DECREF(Attribute_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Attribute_type, "__annotations__", + Attribute_annotations) == 0; + if (!cond) { + Py_DECREF(Attribute_annotations); + return 0; + } + Py_DECREF(Attribute_annotations); + PyObject *Subscript_annotations = PyDict_New(); + if (!Subscript_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Subscript_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Subscript_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Subscript_annotations, "slice", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Subscript_annotations); + return 0; + } + } + { + PyObject *type = state->expr_context_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Subscript_annotations, "ctx", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Subscript_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Subscript_type, "_field_types", + Subscript_annotations) == 0; + if (!cond) { + Py_DECREF(Subscript_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Subscript_type, "__annotations__", + Subscript_annotations) == 0; + if (!cond) { + Py_DECREF(Subscript_annotations); + return 0; + } + Py_DECREF(Subscript_annotations); + PyObject *Starred_annotations = PyDict_New(); + if (!Starred_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Starred_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Starred_annotations); + return 0; + } + } + { + PyObject *type = state->expr_context_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Starred_annotations, "ctx", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Starred_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Starred_type, "_field_types", + Starred_annotations) == 0; + if (!cond) { + Py_DECREF(Starred_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Starred_type, "__annotations__", + Starred_annotations) == 0; + if (!cond) { + Py_DECREF(Starred_annotations); + return 0; + } + Py_DECREF(Starred_annotations); + PyObject *Name_annotations = PyDict_New(); + if (!Name_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(Name_annotations, "id", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Name_annotations); + return 0; + } + } + { + PyObject *type = state->expr_context_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Name_annotations, "ctx", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Name_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Name_type, "_field_types", + Name_annotations) == 0; + if (!cond) { + Py_DECREF(Name_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Name_type, "__annotations__", + Name_annotations) == 0; + if (!cond) { + Py_DECREF(Name_annotations); + return 0; + } + Py_DECREF(Name_annotations); + PyObject *List_annotations = PyDict_New(); + if (!List_annotations) return 0; + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(List_annotations); + return 0; + } + cond = PyDict_SetItemString(List_annotations, "elts", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(List_annotations); + return 0; + } + } + { + PyObject *type = state->expr_context_type; + Py_INCREF(type); + cond = PyDict_SetItemString(List_annotations, "ctx", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(List_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->List_type, "_field_types", + List_annotations) == 0; + if (!cond) { + Py_DECREF(List_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->List_type, "__annotations__", + List_annotations) == 0; + if (!cond) { + Py_DECREF(List_annotations); + return 0; + } + Py_DECREF(List_annotations); + PyObject *Tuple_annotations = PyDict_New(); + if (!Tuple_annotations) return 0; + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Tuple_annotations); + return 0; + } + cond = PyDict_SetItemString(Tuple_annotations, "elts", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Tuple_annotations); + return 0; + } + } + { + PyObject *type = state->expr_context_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Tuple_annotations, "ctx", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Tuple_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Tuple_type, "_field_types", + Tuple_annotations) == 0; + if (!cond) { + Py_DECREF(Tuple_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Tuple_type, "__annotations__", + Tuple_annotations) == 0; + if (!cond) { + Py_DECREF(Tuple_annotations); + return 0; + } + Py_DECREF(Tuple_annotations); + PyObject *Slice_annotations = PyDict_New(); + if (!Slice_annotations) return 0; + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Slice_annotations); + return 0; + } + cond = PyDict_SetItemString(Slice_annotations, "lower", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Slice_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Slice_annotations); + return 0; + } + cond = PyDict_SetItemString(Slice_annotations, "upper", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Slice_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Slice_annotations); + return 0; + } + cond = PyDict_SetItemString(Slice_annotations, "step", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Slice_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Slice_type, "_field_types", + Slice_annotations) == 0; + if (!cond) { + Py_DECREF(Slice_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Slice_type, "__annotations__", + Slice_annotations) == 0; + if (!cond) { + Py_DECREF(Slice_annotations); + return 0; + } + Py_DECREF(Slice_annotations); + PyObject *Load_annotations = PyDict_New(); + if (!Load_annotations) return 0; + cond = PyObject_SetAttrString(state->Load_type, "_field_types", + Load_annotations) == 0; + if (!cond) { + Py_DECREF(Load_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Load_type, "__annotations__", + Load_annotations) == 0; + if (!cond) { + Py_DECREF(Load_annotations); + return 0; + } + Py_DECREF(Load_annotations); + PyObject *Store_annotations = PyDict_New(); + if (!Store_annotations) return 0; + cond = PyObject_SetAttrString(state->Store_type, "_field_types", + Store_annotations) == 0; + if (!cond) { + Py_DECREF(Store_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Store_type, "__annotations__", + Store_annotations) == 0; + if (!cond) { + Py_DECREF(Store_annotations); + return 0; + } + Py_DECREF(Store_annotations); + PyObject *Del_annotations = PyDict_New(); + if (!Del_annotations) return 0; + cond = PyObject_SetAttrString(state->Del_type, "_field_types", + Del_annotations) == 0; + if (!cond) { + Py_DECREF(Del_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Del_type, "__annotations__", + Del_annotations) == 0; + if (!cond) { + Py_DECREF(Del_annotations); + return 0; + } + Py_DECREF(Del_annotations); + PyObject *And_annotations = PyDict_New(); + if (!And_annotations) return 0; + cond = PyObject_SetAttrString(state->And_type, "_field_types", + And_annotations) == 0; + if (!cond) { + Py_DECREF(And_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->And_type, "__annotations__", + And_annotations) == 0; + if (!cond) { + Py_DECREF(And_annotations); + return 0; + } + Py_DECREF(And_annotations); + PyObject *Or_annotations = PyDict_New(); + if (!Or_annotations) return 0; + cond = PyObject_SetAttrString(state->Or_type, "_field_types", + Or_annotations) == 0; + if (!cond) { + Py_DECREF(Or_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Or_type, "__annotations__", + Or_annotations) == 0; + if (!cond) { + Py_DECREF(Or_annotations); + return 0; + } + Py_DECREF(Or_annotations); + PyObject *Add_annotations = PyDict_New(); + if (!Add_annotations) return 0; + cond = PyObject_SetAttrString(state->Add_type, "_field_types", + Add_annotations) == 0; + if (!cond) { + Py_DECREF(Add_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Add_type, "__annotations__", + Add_annotations) == 0; + if (!cond) { + Py_DECREF(Add_annotations); + return 0; + } + Py_DECREF(Add_annotations); + PyObject *Sub_annotations = PyDict_New(); + if (!Sub_annotations) return 0; + cond = PyObject_SetAttrString(state->Sub_type, "_field_types", + Sub_annotations) == 0; + if (!cond) { + Py_DECREF(Sub_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Sub_type, "__annotations__", + Sub_annotations) == 0; + if (!cond) { + Py_DECREF(Sub_annotations); + return 0; + } + Py_DECREF(Sub_annotations); + PyObject *Mult_annotations = PyDict_New(); + if (!Mult_annotations) return 0; + cond = PyObject_SetAttrString(state->Mult_type, "_field_types", + Mult_annotations) == 0; + if (!cond) { + Py_DECREF(Mult_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Mult_type, "__annotations__", + Mult_annotations) == 0; + if (!cond) { + Py_DECREF(Mult_annotations); + return 0; + } + Py_DECREF(Mult_annotations); + PyObject *MatMult_annotations = PyDict_New(); + if (!MatMult_annotations) return 0; + cond = PyObject_SetAttrString(state->MatMult_type, "_field_types", + MatMult_annotations) == 0; + if (!cond) { + Py_DECREF(MatMult_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->MatMult_type, "__annotations__", + MatMult_annotations) == 0; + if (!cond) { + Py_DECREF(MatMult_annotations); + return 0; + } + Py_DECREF(MatMult_annotations); + PyObject *Div_annotations = PyDict_New(); + if (!Div_annotations) return 0; + cond = PyObject_SetAttrString(state->Div_type, "_field_types", + Div_annotations) == 0; + if (!cond) { + Py_DECREF(Div_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Div_type, "__annotations__", + Div_annotations) == 0; + if (!cond) { + Py_DECREF(Div_annotations); + return 0; + } + Py_DECREF(Div_annotations); + PyObject *Mod_annotations = PyDict_New(); + if (!Mod_annotations) return 0; + cond = PyObject_SetAttrString(state->Mod_type, "_field_types", + Mod_annotations) == 0; + if (!cond) { + Py_DECREF(Mod_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Mod_type, "__annotations__", + Mod_annotations) == 0; + if (!cond) { + Py_DECREF(Mod_annotations); + return 0; + } + Py_DECREF(Mod_annotations); + PyObject *Pow_annotations = PyDict_New(); + if (!Pow_annotations) return 0; + cond = PyObject_SetAttrString(state->Pow_type, "_field_types", + Pow_annotations) == 0; + if (!cond) { + Py_DECREF(Pow_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Pow_type, "__annotations__", + Pow_annotations) == 0; + if (!cond) { + Py_DECREF(Pow_annotations); + return 0; + } + Py_DECREF(Pow_annotations); + PyObject *LShift_annotations = PyDict_New(); + if (!LShift_annotations) return 0; + cond = PyObject_SetAttrString(state->LShift_type, "_field_types", + LShift_annotations) == 0; + if (!cond) { + Py_DECREF(LShift_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->LShift_type, "__annotations__", + LShift_annotations) == 0; + if (!cond) { + Py_DECREF(LShift_annotations); + return 0; + } + Py_DECREF(LShift_annotations); + PyObject *RShift_annotations = PyDict_New(); + if (!RShift_annotations) return 0; + cond = PyObject_SetAttrString(state->RShift_type, "_field_types", + RShift_annotations) == 0; + if (!cond) { + Py_DECREF(RShift_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->RShift_type, "__annotations__", + RShift_annotations) == 0; + if (!cond) { + Py_DECREF(RShift_annotations); + return 0; + } + Py_DECREF(RShift_annotations); + PyObject *BitOr_annotations = PyDict_New(); + if (!BitOr_annotations) return 0; + cond = PyObject_SetAttrString(state->BitOr_type, "_field_types", + BitOr_annotations) == 0; + if (!cond) { + Py_DECREF(BitOr_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->BitOr_type, "__annotations__", + BitOr_annotations) == 0; + if (!cond) { + Py_DECREF(BitOr_annotations); + return 0; + } + Py_DECREF(BitOr_annotations); + PyObject *BitXor_annotations = PyDict_New(); + if (!BitXor_annotations) return 0; + cond = PyObject_SetAttrString(state->BitXor_type, "_field_types", + BitXor_annotations) == 0; + if (!cond) { + Py_DECREF(BitXor_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->BitXor_type, "__annotations__", + BitXor_annotations) == 0; + if (!cond) { + Py_DECREF(BitXor_annotations); + return 0; + } + Py_DECREF(BitXor_annotations); + PyObject *BitAnd_annotations = PyDict_New(); + if (!BitAnd_annotations) return 0; + cond = PyObject_SetAttrString(state->BitAnd_type, "_field_types", + BitAnd_annotations) == 0; + if (!cond) { + Py_DECREF(BitAnd_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->BitAnd_type, "__annotations__", + BitAnd_annotations) == 0; + if (!cond) { + Py_DECREF(BitAnd_annotations); + return 0; + } + Py_DECREF(BitAnd_annotations); + PyObject *FloorDiv_annotations = PyDict_New(); + if (!FloorDiv_annotations) return 0; + cond = PyObject_SetAttrString(state->FloorDiv_type, "_field_types", + FloorDiv_annotations) == 0; + if (!cond) { + Py_DECREF(FloorDiv_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->FloorDiv_type, "__annotations__", + FloorDiv_annotations) == 0; + if (!cond) { + Py_DECREF(FloorDiv_annotations); + return 0; + } + Py_DECREF(FloorDiv_annotations); + PyObject *Invert_annotations = PyDict_New(); + if (!Invert_annotations) return 0; + cond = PyObject_SetAttrString(state->Invert_type, "_field_types", + Invert_annotations) == 0; + if (!cond) { + Py_DECREF(Invert_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Invert_type, "__annotations__", + Invert_annotations) == 0; + if (!cond) { + Py_DECREF(Invert_annotations); + return 0; + } + Py_DECREF(Invert_annotations); + PyObject *Not_annotations = PyDict_New(); + if (!Not_annotations) return 0; + cond = PyObject_SetAttrString(state->Not_type, "_field_types", + Not_annotations) == 0; + if (!cond) { + Py_DECREF(Not_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Not_type, "__annotations__", + Not_annotations) == 0; + if (!cond) { + Py_DECREF(Not_annotations); + return 0; + } + Py_DECREF(Not_annotations); + PyObject *UAdd_annotations = PyDict_New(); + if (!UAdd_annotations) return 0; + cond = PyObject_SetAttrString(state->UAdd_type, "_field_types", + UAdd_annotations) == 0; + if (!cond) { + Py_DECREF(UAdd_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->UAdd_type, "__annotations__", + UAdd_annotations) == 0; + if (!cond) { + Py_DECREF(UAdd_annotations); + return 0; + } + Py_DECREF(UAdd_annotations); + PyObject *USub_annotations = PyDict_New(); + if (!USub_annotations) return 0; + cond = PyObject_SetAttrString(state->USub_type, "_field_types", + USub_annotations) == 0; + if (!cond) { + Py_DECREF(USub_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->USub_type, "__annotations__", + USub_annotations) == 0; + if (!cond) { + Py_DECREF(USub_annotations); + return 0; + } + Py_DECREF(USub_annotations); + PyObject *Eq_annotations = PyDict_New(); + if (!Eq_annotations) return 0; + cond = PyObject_SetAttrString(state->Eq_type, "_field_types", + Eq_annotations) == 0; + if (!cond) { + Py_DECREF(Eq_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Eq_type, "__annotations__", + Eq_annotations) == 0; + if (!cond) { + Py_DECREF(Eq_annotations); + return 0; + } + Py_DECREF(Eq_annotations); + PyObject *NotEq_annotations = PyDict_New(); + if (!NotEq_annotations) return 0; + cond = PyObject_SetAttrString(state->NotEq_type, "_field_types", + NotEq_annotations) == 0; + if (!cond) { + Py_DECREF(NotEq_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->NotEq_type, "__annotations__", + NotEq_annotations) == 0; + if (!cond) { + Py_DECREF(NotEq_annotations); + return 0; + } + Py_DECREF(NotEq_annotations); + PyObject *Lt_annotations = PyDict_New(); + if (!Lt_annotations) return 0; + cond = PyObject_SetAttrString(state->Lt_type, "_field_types", + Lt_annotations) == 0; + if (!cond) { + Py_DECREF(Lt_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Lt_type, "__annotations__", + Lt_annotations) == 0; + if (!cond) { + Py_DECREF(Lt_annotations); + return 0; + } + Py_DECREF(Lt_annotations); + PyObject *LtE_annotations = PyDict_New(); + if (!LtE_annotations) return 0; + cond = PyObject_SetAttrString(state->LtE_type, "_field_types", + LtE_annotations) == 0; + if (!cond) { + Py_DECREF(LtE_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->LtE_type, "__annotations__", + LtE_annotations) == 0; + if (!cond) { + Py_DECREF(LtE_annotations); + return 0; + } + Py_DECREF(LtE_annotations); + PyObject *Gt_annotations = PyDict_New(); + if (!Gt_annotations) return 0; + cond = PyObject_SetAttrString(state->Gt_type, "_field_types", + Gt_annotations) == 0; + if (!cond) { + Py_DECREF(Gt_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Gt_type, "__annotations__", + Gt_annotations) == 0; + if (!cond) { + Py_DECREF(Gt_annotations); + return 0; + } + Py_DECREF(Gt_annotations); + PyObject *GtE_annotations = PyDict_New(); + if (!GtE_annotations) return 0; + cond = PyObject_SetAttrString(state->GtE_type, "_field_types", + GtE_annotations) == 0; + if (!cond) { + Py_DECREF(GtE_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->GtE_type, "__annotations__", + GtE_annotations) == 0; + if (!cond) { + Py_DECREF(GtE_annotations); + return 0; + } + Py_DECREF(GtE_annotations); + PyObject *Is_annotations = PyDict_New(); + if (!Is_annotations) return 0; + cond = PyObject_SetAttrString(state->Is_type, "_field_types", + Is_annotations) == 0; + if (!cond) { + Py_DECREF(Is_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Is_type, "__annotations__", + Is_annotations) == 0; + if (!cond) { + Py_DECREF(Is_annotations); + return 0; + } + Py_DECREF(Is_annotations); + PyObject *IsNot_annotations = PyDict_New(); + if (!IsNot_annotations) return 0; + cond = PyObject_SetAttrString(state->IsNot_type, "_field_types", + IsNot_annotations) == 0; + if (!cond) { + Py_DECREF(IsNot_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->IsNot_type, "__annotations__", + IsNot_annotations) == 0; + if (!cond) { + Py_DECREF(IsNot_annotations); + return 0; + } + Py_DECREF(IsNot_annotations); + PyObject *In_annotations = PyDict_New(); + if (!In_annotations) return 0; + cond = PyObject_SetAttrString(state->In_type, "_field_types", + In_annotations) == 0; + if (!cond) { + Py_DECREF(In_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->In_type, "__annotations__", + In_annotations) == 0; + if (!cond) { + Py_DECREF(In_annotations); + return 0; + } + Py_DECREF(In_annotations); + PyObject *NotIn_annotations = PyDict_New(); + if (!NotIn_annotations) return 0; + cond = PyObject_SetAttrString(state->NotIn_type, "_field_types", + NotIn_annotations) == 0; + if (!cond) { + Py_DECREF(NotIn_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->NotIn_type, "__annotations__", + NotIn_annotations) == 0; + if (!cond) { + Py_DECREF(NotIn_annotations); + return 0; + } + Py_DECREF(NotIn_annotations); + PyObject *comprehension_annotations = PyDict_New(); + if (!comprehension_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(comprehension_annotations, "target", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(comprehension_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(comprehension_annotations, "iter", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(comprehension_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(comprehension_annotations); + return 0; + } + cond = PyDict_SetItemString(comprehension_annotations, "ifs", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(comprehension_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyLong_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(comprehension_annotations, "is_async", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(comprehension_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->comprehension_type, "_field_types", + comprehension_annotations) == 0; + if (!cond) { + Py_DECREF(comprehension_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->comprehension_type, "__annotations__", + comprehension_annotations) == 0; + if (!cond) { + Py_DECREF(comprehension_annotations); + return 0; + } + Py_DECREF(comprehension_annotations); + PyObject *ExceptHandler_annotations = PyDict_New(); + if (!ExceptHandler_annotations) return 0; + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(ExceptHandler_annotations); + return 0; + } + cond = PyDict_SetItemString(ExceptHandler_annotations, "type", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ExceptHandler_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(ExceptHandler_annotations); + return 0; + } + cond = PyDict_SetItemString(ExceptHandler_annotations, "name", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ExceptHandler_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(ExceptHandler_annotations); + return 0; + } + cond = PyDict_SetItemString(ExceptHandler_annotations, "body", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ExceptHandler_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->ExceptHandler_type, "_field_types", + ExceptHandler_annotations) == 0; + if (!cond) { + Py_DECREF(ExceptHandler_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->ExceptHandler_type, "__annotations__", + ExceptHandler_annotations) == 0; + if (!cond) { + Py_DECREF(ExceptHandler_annotations); + return 0; + } + Py_DECREF(ExceptHandler_annotations); + PyObject *arguments_annotations = PyDict_New(); + if (!arguments_annotations) return 0; + { + PyObject *type = state->arg_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + cond = PyDict_SetItemString(arguments_annotations, "posonlyargs", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + } + { + PyObject *type = state->arg_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + cond = PyDict_SetItemString(arguments_annotations, "args", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + } + { + PyObject *type = state->arg_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + cond = PyDict_SetItemString(arguments_annotations, "vararg", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + } + { + PyObject *type = state->arg_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + cond = PyDict_SetItemString(arguments_annotations, "kwonlyargs", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + cond = PyDict_SetItemString(arguments_annotations, "kw_defaults", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + } + { + PyObject *type = state->arg_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + cond = PyDict_SetItemString(arguments_annotations, "kwarg", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + cond = PyDict_SetItemString(arguments_annotations, "defaults", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->arguments_type, "_field_types", + arguments_annotations) == 0; + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->arguments_type, "__annotations__", + arguments_annotations) == 0; + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + Py_DECREF(arguments_annotations); + PyObject *arg_annotations = PyDict_New(); + if (!arg_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(arg_annotations, "arg", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arg_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(arg_annotations); + return 0; + } + cond = PyDict_SetItemString(arg_annotations, "annotation", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arg_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(arg_annotations); + return 0; + } + cond = PyDict_SetItemString(arg_annotations, "type_comment", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arg_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->arg_type, "_field_types", + arg_annotations) == 0; + if (!cond) { + Py_DECREF(arg_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->arg_type, "__annotations__", + arg_annotations) == 0; + if (!cond) { + Py_DECREF(arg_annotations); + return 0; + } + Py_DECREF(arg_annotations); + PyObject *keyword_annotations = PyDict_New(); + if (!keyword_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(keyword_annotations); + return 0; + } + cond = PyDict_SetItemString(keyword_annotations, "arg", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(keyword_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(keyword_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(keyword_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->keyword_type, "_field_types", + keyword_annotations) == 0; + if (!cond) { + Py_DECREF(keyword_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->keyword_type, "__annotations__", + keyword_annotations) == 0; + if (!cond) { + Py_DECREF(keyword_annotations); + return 0; + } + Py_DECREF(keyword_annotations); + PyObject *alias_annotations = PyDict_New(); + if (!alias_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(alias_annotations, "name", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(alias_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(alias_annotations); + return 0; + } + cond = PyDict_SetItemString(alias_annotations, "asname", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(alias_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->alias_type, "_field_types", + alias_annotations) == 0; + if (!cond) { + Py_DECREF(alias_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->alias_type, "__annotations__", + alias_annotations) == 0; + if (!cond) { + Py_DECREF(alias_annotations); + return 0; + } + Py_DECREF(alias_annotations); + PyObject *withitem_annotations = PyDict_New(); + if (!withitem_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(withitem_annotations, "context_expr", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(withitem_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(withitem_annotations); + return 0; + } + cond = PyDict_SetItemString(withitem_annotations, "optional_vars", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(withitem_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->withitem_type, "_field_types", + withitem_annotations) == 0; + if (!cond) { + Py_DECREF(withitem_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->withitem_type, "__annotations__", + withitem_annotations) == 0; + if (!cond) { + Py_DECREF(withitem_annotations); + return 0; + } + Py_DECREF(withitem_annotations); + PyObject *match_case_annotations = PyDict_New(); + if (!match_case_annotations) return 0; + { + PyObject *type = state->pattern_type; + Py_INCREF(type); + cond = PyDict_SetItemString(match_case_annotations, "pattern", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(match_case_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(match_case_annotations); + return 0; + } + cond = PyDict_SetItemString(match_case_annotations, "guard", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(match_case_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(match_case_annotations); + return 0; + } + cond = PyDict_SetItemString(match_case_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(match_case_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->match_case_type, "_field_types", + match_case_annotations) == 0; + if (!cond) { + Py_DECREF(match_case_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->match_case_type, "__annotations__", + match_case_annotations) == 0; + if (!cond) { + Py_DECREF(match_case_annotations); + return 0; + } + Py_DECREF(match_case_annotations); + PyObject *MatchValue_annotations = PyDict_New(); + if (!MatchValue_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(MatchValue_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchValue_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->MatchValue_type, "_field_types", + MatchValue_annotations) == 0; + if (!cond) { + Py_DECREF(MatchValue_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->MatchValue_type, "__annotations__", + MatchValue_annotations) == 0; + if (!cond) { + Py_DECREF(MatchValue_annotations); + return 0; + } + Py_DECREF(MatchValue_annotations); + PyObject *MatchSingleton_annotations = PyDict_New(); + if (!MatchSingleton_annotations) return 0; + { + PyObject *type = (PyObject *)&PyBaseObject_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(MatchSingleton_annotations, "value", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchSingleton_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->MatchSingleton_type, "_field_types", + MatchSingleton_annotations) == 0; + if (!cond) { + Py_DECREF(MatchSingleton_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->MatchSingleton_type, + "__annotations__", + MatchSingleton_annotations) == 0; + if (!cond) { + Py_DECREF(MatchSingleton_annotations); + return 0; + } + Py_DECREF(MatchSingleton_annotations); + PyObject *MatchSequence_annotations = PyDict_New(); + if (!MatchSequence_annotations) return 0; + { + PyObject *type = state->pattern_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchSequence_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchSequence_annotations, "patterns", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchSequence_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->MatchSequence_type, "_field_types", + MatchSequence_annotations) == 0; + if (!cond) { + Py_DECREF(MatchSequence_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->MatchSequence_type, "__annotations__", + MatchSequence_annotations) == 0; + if (!cond) { + Py_DECREF(MatchSequence_annotations); + return 0; + } + Py_DECREF(MatchSequence_annotations); + PyObject *MatchMapping_annotations = PyDict_New(); + if (!MatchMapping_annotations) return 0; + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchMapping_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchMapping_annotations, "keys", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchMapping_annotations); + return 0; + } + } + { + PyObject *type = state->pattern_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchMapping_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchMapping_annotations, "patterns", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchMapping_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchMapping_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchMapping_annotations, "rest", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchMapping_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->MatchMapping_type, "_field_types", + MatchMapping_annotations) == 0; + if (!cond) { + Py_DECREF(MatchMapping_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->MatchMapping_type, "__annotations__", + MatchMapping_annotations) == 0; + if (!cond) { + Py_DECREF(MatchMapping_annotations); + return 0; + } + Py_DECREF(MatchMapping_annotations); + PyObject *MatchClass_annotations = PyDict_New(); + if (!MatchClass_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(MatchClass_annotations, "cls", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchClass_annotations); + return 0; + } + } + { + PyObject *type = state->pattern_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchClass_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchClass_annotations, "patterns", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchClass_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchClass_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchClass_annotations, "kwd_attrs", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchClass_annotations); + return 0; + } + } + { + PyObject *type = state->pattern_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchClass_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchClass_annotations, "kwd_patterns", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchClass_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->MatchClass_type, "_field_types", + MatchClass_annotations) == 0; + if (!cond) { + Py_DECREF(MatchClass_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->MatchClass_type, "__annotations__", + MatchClass_annotations) == 0; + if (!cond) { + Py_DECREF(MatchClass_annotations); + return 0; + } + Py_DECREF(MatchClass_annotations); + PyObject *MatchStar_annotations = PyDict_New(); + if (!MatchStar_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchStar_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchStar_annotations, "name", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchStar_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->MatchStar_type, "_field_types", + MatchStar_annotations) == 0; + if (!cond) { + Py_DECREF(MatchStar_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->MatchStar_type, "__annotations__", + MatchStar_annotations) == 0; + if (!cond) { + Py_DECREF(MatchStar_annotations); + return 0; + } + Py_DECREF(MatchStar_annotations); + PyObject *MatchAs_annotations = PyDict_New(); + if (!MatchAs_annotations) return 0; + { + PyObject *type = state->pattern_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchAs_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchAs_annotations, "pattern", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchAs_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchAs_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchAs_annotations, "name", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchAs_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->MatchAs_type, "_field_types", + MatchAs_annotations) == 0; + if (!cond) { + Py_DECREF(MatchAs_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->MatchAs_type, "__annotations__", + MatchAs_annotations) == 0; + if (!cond) { + Py_DECREF(MatchAs_annotations); + return 0; + } + Py_DECREF(MatchAs_annotations); + PyObject *MatchOr_annotations = PyDict_New(); + if (!MatchOr_annotations) return 0; + { + PyObject *type = state->pattern_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchOr_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchOr_annotations, "patterns", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchOr_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->MatchOr_type, "_field_types", + MatchOr_annotations) == 0; + if (!cond) { + Py_DECREF(MatchOr_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->MatchOr_type, "__annotations__", + MatchOr_annotations) == 0; + if (!cond) { + Py_DECREF(MatchOr_annotations); + return 0; + } + Py_DECREF(MatchOr_annotations); + PyObject *TypeIgnore_annotations = PyDict_New(); + if (!TypeIgnore_annotations) return 0; + { + PyObject *type = (PyObject *)&PyLong_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(TypeIgnore_annotations, "lineno", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TypeIgnore_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(TypeIgnore_annotations, "tag", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TypeIgnore_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->TypeIgnore_type, "_field_types", + TypeIgnore_annotations) == 0; + if (!cond) { + Py_DECREF(TypeIgnore_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->TypeIgnore_type, "__annotations__", + TypeIgnore_annotations) == 0; + if (!cond) { + Py_DECREF(TypeIgnore_annotations); + return 0; + } + Py_DECREF(TypeIgnore_annotations); + PyObject *TypeVar_annotations = PyDict_New(); + if (!TypeVar_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(TypeVar_annotations, "name", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TypeVar_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(TypeVar_annotations); + return 0; + } + cond = PyDict_SetItemString(TypeVar_annotations, "bound", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TypeVar_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->TypeVar_type, "_field_types", + TypeVar_annotations) == 0; + if (!cond) { + Py_DECREF(TypeVar_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->TypeVar_type, "__annotations__", + TypeVar_annotations) == 0; + if (!cond) { + Py_DECREF(TypeVar_annotations); + return 0; + } + Py_DECREF(TypeVar_annotations); + PyObject *ParamSpec_annotations = PyDict_New(); + if (!ParamSpec_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(ParamSpec_annotations, "name", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ParamSpec_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->ParamSpec_type, "_field_types", + ParamSpec_annotations) == 0; + if (!cond) { + Py_DECREF(ParamSpec_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->ParamSpec_type, "__annotations__", + ParamSpec_annotations) == 0; + if (!cond) { + Py_DECREF(ParamSpec_annotations); + return 0; + } + Py_DECREF(ParamSpec_annotations); + PyObject *TypeVarTuple_annotations = PyDict_New(); + if (!TypeVarTuple_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(TypeVarTuple_annotations, "name", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TypeVarTuple_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->TypeVarTuple_type, "_field_types", + TypeVarTuple_annotations) == 0; + if (!cond) { + Py_DECREF(TypeVarTuple_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->TypeVarTuple_type, "__annotations__", + TypeVarTuple_annotations) == 0; + if (!cond) { + Py_DECREF(TypeVarTuple_annotations); + return 0; + } + Py_DECREF(TypeVarTuple_annotations); + + return 1; +} + + typedef struct { PyObject_HEAD @@ -860,7 +5026,7 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) Py_ssize_t i, numfields = 0; int res = -1; - PyObject *key, *value, *fields; + PyObject *key, *value, *fields, *remaining_fields = NULL; if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), state->_fields, &fields) < 0) { goto cleanup; } @@ -869,6 +5035,13 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) if (numfields == -1) { goto cleanup; } + remaining_fields = PySet_New(fields); + } + else { + remaining_fields = PySet_New(NULL); + } + if (remaining_fields == NULL) { + goto cleanup; } res = 0; /* if no error occurs, this stays 0 to the end */ @@ -888,6 +5061,11 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) goto cleanup; } res = PyObject_SetAttr(self, name, PyTuple_GET_ITEM(args, i)); + if (PySet_Discard(remaining_fields, name) < 0) { + res = -1; + Py_DECREF(name); + goto cleanup; + } Py_DECREF(name); if (res < 0) { goto cleanup; @@ -900,13 +5078,14 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) if (contains == -1) { res = -1; goto cleanup; - } else if (contains == 1) { - Py_ssize_t p = PySequence_Index(fields, key); + } + else if (contains == 1) { + int p = PySet_Discard(remaining_fields, key); if (p == -1) { res = -1; goto cleanup; } - if (p < PyTuple_GET_SIZE(args)) { + if (p == 0) { PyErr_Format(PyExc_TypeError, "%.400s got multiple values for argument '%U'", Py_TYPE(self)->tp_name, key); @@ -914,15 +5093,91 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) goto cleanup; } } + else if ( + PyUnicode_CompareWithASCIIString(key, "lineno") != 0 && + PyUnicode_CompareWithASCIIString(key, "col_offset") != 0 && + PyUnicode_CompareWithASCIIString(key, "end_lineno") != 0 && + PyUnicode_CompareWithASCIIString(key, "end_col_offset") != 0 + ) { + if (PyErr_WarnFormat( + PyExc_DeprecationWarning, 1, + "%.400s.__init__ got an unexpected keyword argument '%U'. " + "Support for arbitrary keyword arguments is deprecated " + "and will be removed in Python 3.15.", + Py_TYPE(self)->tp_name, key + ) < 0) { + res = -1; + goto cleanup; + } + } res = PyObject_SetAttr(self, key, value); if (res < 0) { goto cleanup; } } } + Py_ssize_t size = PySet_Size(remaining_fields); + PyObject *field_types = NULL, *remaining_list = NULL; + if (size > 0) { + if (!PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), &_Py_ID(_field_types), + &field_types)) { + res = -1; + goto cleanup; + } + remaining_list = PySequence_List(remaining_fields); + if (!remaining_list) { + goto set_remaining_cleanup; + } + for (Py_ssize_t i = 0; i < size; i++) { + PyObject *name = PyList_GET_ITEM(remaining_list, i); + PyObject *type = PyDict_GetItemWithError(field_types, name); + if (!type) { + if (!PyErr_Occurred()) { + PyErr_SetObject(PyExc_KeyError, name); + } + goto set_remaining_cleanup; + } + if (_PyUnion_Check(type)) { + // optional field + // do nothing, we'll have set a None default on the class + } + else if (Py_IS_TYPE(type, &Py_GenericAliasType)) { + // list field + PyObject *empty = PyList_New(0); + if (!empty) { + goto set_remaining_cleanup; + } + res = PyObject_SetAttr(self, name, empty); + Py_DECREF(empty); + if (res < 0) { + goto set_remaining_cleanup; + } + } + else { + // simple field (e.g., identifier) + if (PyErr_WarnFormat( + PyExc_DeprecationWarning, 1, + "%.400s.__init__ missing 1 required positional argument: '%U'. " + "This will become an error in Python 3.15.", + Py_TYPE(self)->tp_name, name + ) < 0) { + res = -1; + goto cleanup; + } + } + } + Py_DECREF(remaining_list); + Py_DECREF(field_types); + } cleanup: Py_XDECREF(fields); + Py_XDECREF(remaining_fields); return res; + set_remaining_cleanup: + Py_XDECREF(remaining_list); + Py_XDECREF(field_types); + res = -1; + goto cleanup; } /* Pickling support */ @@ -934,14 +5189,75 @@ ast_type_reduce(PyObject *self, PyObject *unused) return NULL; } - PyObject *dict; + PyObject *dict = NULL, *fields = NULL, *remaining_fields = NULL, + *remaining_dict = NULL, *positional_args = NULL; if (PyObject_GetOptionalAttr(self, state->__dict__, &dict) < 0) { return NULL; } + PyObject *result = NULL; if (dict) { - return Py_BuildValue("O()N", Py_TYPE(self), dict); + // Serialize the fields as positional args if possible, because if we + // serialize them as a dict, during unpickling they are set only *after* + // the object is constructed, which will now trigger a DeprecationWarning + // if the AST type has required fields. + if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), state->_fields, &fields) < 0) { + goto cleanup; + } + if (fields) { + Py_ssize_t numfields = PySequence_Size(fields); + if (numfields == -1) { + Py_DECREF(dict); + goto cleanup; + } + remaining_dict = PyDict_Copy(dict); + Py_DECREF(dict); + if (!remaining_dict) { + goto cleanup; + } + positional_args = PyList_New(0); + if (!positional_args) { + goto cleanup; + } + for (Py_ssize_t i = 0; i < numfields; i++) { + PyObject *name = PySequence_GetItem(fields, i); + if (!name) { + goto cleanup; + } + PyObject *value = PyDict_GetItemWithError(remaining_dict, name); + if (!value) { + if (PyErr_Occurred()) { + goto cleanup; + } + break; + } + if (PyList_Append(positional_args, value) < 0) { + goto cleanup; + } + if (PyDict_DelItem(remaining_dict, name) < 0) { + goto cleanup; + } + Py_DECREF(name); + } + PyObject *args_tuple = PyList_AsTuple(positional_args); + if (!args_tuple) { + goto cleanup; + } + result = Py_BuildValue("ONO", Py_TYPE(self), args_tuple, + remaining_dict); + } + else { + result = Py_BuildValue("O()N", Py_TYPE(self), dict); + } + } + else { + result = Py_BuildValue("O()", Py_TYPE(self)); } - return Py_BuildValue("O()", Py_TYPE(self)); +cleanup: + Py_XDECREF(fields); + Py_XDECREF(remaining_fields); + Py_XDECREF(remaining_dict); + Py_XDECREF(positional_args); + return result; } static PyMemberDef ast_type_members[] = { @@ -1939,6 +6255,9 @@ init_types(struct ast_state *state) "TypeVarTuple(identifier name)"); if (!state->TypeVarTuple_type) return -1; + if (!add_ast_annotations(state)) { + return -1; + } return 0; } From 02beb9f0208d22fd8bd893e6e6ec813f7e51b235 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra <jelle.zijlstra@gmail.com> Date: Tue, 27 Feb 2024 21:35:25 -0800 Subject: [PATCH 482/507] gh-116026: Try disabling rebuilds of dependents in Homebrew (#116027) --- .github/workflows/reusable-macos.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/reusable-macos.yml b/.github/workflows/reusable-macos.yml index ba62d9568c6b80a..65b73cd791c75c7 100644 --- a/.github/workflows/reusable-macos.yml +++ b/.github/workflows/reusable-macos.yml @@ -17,6 +17,7 @@ jobs: HOMEBREW_NO_ANALYTICS: 1 HOMEBREW_NO_AUTO_UPDATE: 1 HOMEBREW_NO_INSTALL_CLEANUP: 1 + HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1 PYTHONSTRICTEXTENSIONBUILD: 1 strategy: fail-fast: false From e72576c48b8be1e4f22c2f387f9769efa073c5be Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Wed, 28 Feb 2024 09:51:08 +0200 Subject: [PATCH 483/507] gh-115961: Improve tests for compressed file-like objects (GH-115963) * Increase coverage for compressed file-like objects initialized with a file name, an open file object, a file object opened by file descriptor, and a file-like object without name and mode attributes (io.BytesIO) * Increase coverage for name, fileno(), mode, readable(), writable(), seekable() in different modes and states * No longer skip tests with bytes names * Test objects implementing the path protocol, not just pathlib.Path. --- Lib/test/test_bz2.py | 138 ++++++++++++++++++++-- Lib/test/test_gzip.py | 183 +++++++++++++++++++++++++++-- Lib/test/test_lzma.py | 123 ++++++++++++++++--- Lib/test/test_tarfile.py | 21 +++- Lib/test/test_zipfile/test_core.py | 56 +++++++-- 5 files changed, 476 insertions(+), 45 deletions(-) diff --git a/Lib/test/test_bz2.py b/Lib/test/test_bz2.py index 1f0b9adc3698b4a..772f0eacce28f52 100644 --- a/Lib/test/test_bz2.py +++ b/Lib/test/test_bz2.py @@ -3,19 +3,19 @@ import array import unittest +import io from io import BytesIO, DEFAULT_BUFFER_SIZE import os import pickle import glob import tempfile -import pathlib import random import shutil import subprocess import threading from test.support import import_helper from test.support import threading_helper -from test.support.os_helper import unlink +from test.support.os_helper import unlink, FakePath import _compression import sys @@ -537,12 +537,136 @@ def testMultiStreamOrdering(self): with BZ2File(self.filename) as bz2f: self.assertEqual(bz2f.read(), data1 + data2) + def testOpenFilename(self): + with BZ2File(self.filename, "wb") as f: + f.write(b'content') + self.assertIsInstance(f.fileno(), int) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), False) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + + with BZ2File(self.filename, "ab") as f: + f.write(b'appendix') + self.assertIsInstance(f.fileno(), int) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), False) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + + with BZ2File(self.filename, 'rb') as f: + self.assertEqual(f.read(), b'contentappendix') + self.assertIsInstance(f.fileno(), int) + self.assertIs(f.readable(), True) + self.assertIs(f.writable(), False) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + with self.assertRaises(ValueError): + f.fileno() + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + + def testOpenFileWithName(self): + with open(self.filename, 'wb') as raw: + with BZ2File(raw, 'wb') as f: + f.write(b'content') + self.assertEqual(f.fileno(), raw.fileno()) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), False) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + + with open(self.filename, 'ab') as raw: + with BZ2File(raw, 'ab') as f: + f.write(b'appendix') + self.assertEqual(f.fileno(), raw.fileno()) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), False) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + + with open(self.filename, 'rb') as raw: + with BZ2File(raw, 'rb') as f: + self.assertEqual(f.read(), b'contentappendix') + self.assertEqual(f.fileno(), raw.fileno()) + self.assertIs(f.readable(), True) + self.assertIs(f.writable(), False) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + with self.assertRaises(ValueError): + f.fileno() + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + + def testOpenFileWithoutName(self): + bio = BytesIO() + with BZ2File(bio, 'wb') as f: + f.write(b'content') + self.assertRaises(io.UnsupportedOperation, f.fileno) + self.assertRaises(ValueError, f.fileno) + + with BZ2File(bio, 'ab') as f: + f.write(b'appendix') + self.assertRaises(io.UnsupportedOperation, f.fileno) + self.assertRaises(ValueError, f.fileno) + + bio.seek(0) + with BZ2File(bio, 'rb') as f: + self.assertEqual(f.read(), b'contentappendix') + self.assertRaises(io.UnsupportedOperation, f.fileno) + with self.assertRaises(ValueError): + f.fileno() + + def testOpenFileWithIntName(self): + fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC) + with open(fd, 'wb') as raw: + with BZ2File(raw, 'wb') as f: + f.write(b'content') + self.assertEqual(f.fileno(), raw.fileno()) + self.assertRaises(ValueError, f.fileno) + + fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT | os.O_APPEND) + with open(fd, 'ab') as raw: + with BZ2File(raw, 'ab') as f: + f.write(b'appendix') + self.assertEqual(f.fileno(), raw.fileno()) + self.assertRaises(ValueError, f.fileno) + + fd = os.open(self.filename, os.O_RDONLY) + with open(fd, 'rb') as raw: + with BZ2File(raw, 'rb') as f: + self.assertEqual(f.read(), b'contentappendix') + self.assertEqual(f.fileno(), raw.fileno()) + with self.assertRaises(ValueError): + f.fileno() + def testOpenBytesFilename(self): str_filename = self.filename - try: - bytes_filename = str_filename.encode("ascii") - except UnicodeEncodeError: - self.skipTest("Temporary file name needs to be ASCII") + bytes_filename = os.fsencode(str_filename) with BZ2File(bytes_filename, "wb") as f: f.write(self.DATA) with BZ2File(bytes_filename, "rb") as f: @@ -552,7 +676,7 @@ def testOpenBytesFilename(self): self.assertEqual(f.read(), self.DATA) def testOpenPathLikeFilename(self): - filename = pathlib.Path(self.filename) + filename = FakePath(self.filename) with BZ2File(filename, "wb") as f: f.write(self.DATA) with BZ2File(filename, "rb") as f: diff --git a/Lib/test/test_gzip.py b/Lib/test/test_gzip.py index 128f933787a3f67..d220c7d06e50c97 100644 --- a/Lib/test/test_gzip.py +++ b/Lib/test/test_gzip.py @@ -5,7 +5,6 @@ import functools import io import os -import pathlib import struct import sys import unittest @@ -79,16 +78,18 @@ def test_write(self): f.close() def test_write_read_with_pathlike_file(self): - filename = pathlib.Path(self.filename) + filename = os_helper.FakePath(self.filename) with gzip.GzipFile(filename, 'w') as f: f.write(data1 * 50) self.assertIsInstance(f.name, str) + self.assertEqual(f.name, self.filename) with gzip.GzipFile(filename, 'a') as f: f.write(data1) with gzip.GzipFile(filename) as f: d = f.read() self.assertEqual(d, data1 * 51) self.assertIsInstance(f.name, str) + self.assertEqual(f.name, self.filename) # The following test_write_xy methods test that write accepts # the corresponding bytes-like object type as input @@ -472,13 +473,118 @@ def test_textio_readlines(self): with io.TextIOWrapper(f, encoding="ascii") as t: self.assertEqual(t.readlines(), lines) + def test_fileobj_with_name(self): + with open(self.filename, "xb") as raw: + with gzip.GzipFile(fileobj=raw, mode="x") as f: + f.write(b'one') + self.assertEqual(f.name, raw.name) + self.assertEqual(f.fileno(), raw.fileno()) + self.assertEqual(f.mode, gzip.WRITE) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertEqual(f.name, raw.name) + self.assertRaises(AttributeError, f.fileno) + self.assertEqual(f.mode, gzip.WRITE) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), True) + + with open(self.filename, "wb") as raw: + with gzip.GzipFile(fileobj=raw, mode="w") as f: + f.write(b'two') + self.assertEqual(f.name, raw.name) + self.assertEqual(f.fileno(), raw.fileno()) + self.assertEqual(f.mode, gzip.WRITE) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertEqual(f.name, raw.name) + self.assertRaises(AttributeError, f.fileno) + self.assertEqual(f.mode, gzip.WRITE) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), True) + + with open(self.filename, "ab") as raw: + with gzip.GzipFile(fileobj=raw, mode="a") as f: + f.write(b'three') + self.assertEqual(f.name, raw.name) + self.assertEqual(f.fileno(), raw.fileno()) + self.assertEqual(f.mode, gzip.WRITE) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertEqual(f.name, raw.name) + self.assertRaises(AttributeError, f.fileno) + self.assertEqual(f.mode, gzip.WRITE) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), True) + + with open(self.filename, "rb") as raw: + with gzip.GzipFile(fileobj=raw, mode="r") as f: + self.assertEqual(f.read(), b'twothree') + self.assertEqual(f.name, raw.name) + self.assertEqual(f.fileno(), raw.fileno()) + self.assertEqual(f.mode, gzip.READ) + self.assertIs(f.readable(), True) + self.assertIs(f.writable(), False) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertEqual(f.name, raw.name) + self.assertRaises(AttributeError, f.fileno) + self.assertEqual(f.mode, gzip.READ) + self.assertIs(f.readable(), True) + self.assertIs(f.writable(), False) + self.assertIs(f.seekable(), True) + def test_fileobj_from_fdopen(self): # Issue #13781: Opening a GzipFile for writing fails when using a # fileobj created with os.fdopen(). - fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT) - with os.fdopen(fd, "wb") as f: - with gzip.GzipFile(fileobj=f, mode="w") as g: - pass + fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT | os.O_EXCL) + with os.fdopen(fd, "xb") as raw: + with gzip.GzipFile(fileobj=raw, mode="x") as f: + f.write(b'one') + self.assertEqual(f.name, '') + self.assertEqual(f.fileno(), raw.fileno()) + self.assertIs(f.closed, True) + self.assertEqual(f.name, '') + self.assertRaises(AttributeError, f.fileno) + + fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC) + with os.fdopen(fd, "wb") as raw: + with gzip.GzipFile(fileobj=raw, mode="w") as f: + f.write(b'two') + self.assertEqual(f.name, '') + self.assertEqual(f.fileno(), raw.fileno()) + self.assertEqual(f.name, '') + self.assertRaises(AttributeError, f.fileno) + + fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT | os.O_APPEND) + with os.fdopen(fd, "ab") as raw: + with gzip.GzipFile(fileobj=raw, mode="a") as f: + f.write(b'three') + self.assertEqual(f.name, '') + self.assertEqual(f.fileno(), raw.fileno()) + self.assertEqual(f.name, '') + self.assertRaises(AttributeError, f.fileno) + + fd = os.open(self.filename, os.O_RDONLY) + with os.fdopen(fd, "rb") as raw: + with gzip.GzipFile(fileobj=raw, mode="r") as f: + self.assertEqual(f.read(), b'twothree') + self.assertEqual(f.name, '') + self.assertEqual(f.fileno(), raw.fileno()) + self.assertEqual(f.name, '') + self.assertRaises(AttributeError, f.fileno) def test_fileobj_mode(self): gzip.GzipFile(self.filename, "wb").close() @@ -508,17 +614,69 @@ def test_fileobj_mode(self): def test_bytes_filename(self): str_filename = self.filename - try: - bytes_filename = str_filename.encode("ascii") - except UnicodeEncodeError: - self.skipTest("Temporary file name needs to be ASCII") + bytes_filename = os.fsencode(str_filename) with gzip.GzipFile(bytes_filename, "wb") as f: f.write(data1 * 50) + self.assertEqual(f.name, bytes_filename) with gzip.GzipFile(bytes_filename, "rb") as f: self.assertEqual(f.read(), data1 * 50) + self.assertEqual(f.name, bytes_filename) # Sanity check that we are actually operating on the right file. with gzip.GzipFile(str_filename, "rb") as f: self.assertEqual(f.read(), data1 * 50) + self.assertEqual(f.name, str_filename) + + def test_fileobj_without_name(self): + bio = io.BytesIO() + with gzip.GzipFile(fileobj=bio, mode='wb') as f: + f.write(data1 * 50) + self.assertEqual(f.name, '') + self.assertRaises(io.UnsupportedOperation, f.fileno) + self.assertEqual(f.mode, gzip.WRITE) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertEqual(f.name, '') + self.assertRaises(AttributeError, f.fileno) + self.assertEqual(f.mode, gzip.WRITE) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), True) + + bio.seek(0) + with gzip.GzipFile(fileobj=bio, mode='rb') as f: + self.assertEqual(f.read(), data1 * 50) + self.assertEqual(f.name, '') + self.assertRaises(io.UnsupportedOperation, f.fileno) + self.assertEqual(f.mode, gzip.READ) + self.assertIs(f.readable(), True) + self.assertIs(f.writable(), False) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertEqual(f.name, '') + self.assertRaises(AttributeError, f.fileno) + self.assertEqual(f.mode, gzip.READ) + self.assertIs(f.readable(), True) + self.assertIs(f.writable(), False) + self.assertIs(f.seekable(), True) + + def test_fileobj_and_filename(self): + filename2 = self.filename + 'new' + with (open(self.filename, 'wb') as fileobj, + gzip.GzipFile(fileobj=fileobj, filename=filename2, mode='wb') as f): + f.write(data1 * 50) + self.assertEqual(f.name, filename2) + with (open(self.filename, 'rb') as fileobj, + gzip.GzipFile(fileobj=fileobj, filename=filename2, mode='rb') as f): + self.assertEqual(f.read(), data1 * 50) + self.assertEqual(f.name, filename2) + # Sanity check that we are actually operating on the right file. + with gzip.GzipFile(self.filename, 'rb') as f: + self.assertEqual(f.read(), data1 * 50) + self.assertEqual(f.name, self.filename) def test_decompress_limited(self): """Decompressed data buffering should be limited""" @@ -707,13 +865,16 @@ def test_binary_modes(self): self.assertEqual(file_data, uncompressed) def test_pathlike_file(self): - filename = pathlib.Path(self.filename) + filename = os_helper.FakePath(self.filename) with gzip.open(filename, "wb") as f: f.write(data1 * 50) + self.assertEqual(f.name, self.filename) with gzip.open(filename, "ab") as f: f.write(data1) + self.assertEqual(f.name, self.filename) with gzip.open(filename) as f: self.assertEqual(f.read(), data1 * 51) + self.assertEqual(f.name, self.filename) def test_implicit_binary_modes(self): # Test implicit binary modes (no "b" or "t" in mode string). diff --git a/Lib/test/test_lzma.py b/Lib/test/test_lzma.py index 65e6488c5d7b106..db290e139327e02 100644 --- a/Lib/test/test_lzma.py +++ b/Lib/test/test_lzma.py @@ -2,7 +2,6 @@ import array from io import BytesIO, UnsupportedOperation, DEFAULT_BUFFER_SIZE import os -import pathlib import pickle import random import sys @@ -12,7 +11,7 @@ from test.support import _4G, bigmemtest from test.support.import_helper import import_module from test.support.os_helper import ( - TESTFN, unlink + TESTFN, unlink, FakePath ) lzma = import_module("lzma") @@ -548,7 +547,7 @@ def test_init(self): pass def test_init_with_PathLike_filename(self): - filename = pathlib.Path(TESTFN) + filename = FakePath(TESTFN) with TempFile(filename, COMPRESSED_XZ): with LZMAFile(filename) as f: self.assertEqual(f.read(), INPUT) @@ -585,11 +584,10 @@ def test_init_with_x_mode(self): self.addCleanup(unlink, TESTFN) for mode in ("x", "xb"): unlink(TESTFN) - with LZMAFile(TESTFN, mode): + with LZMAFile(TESTFN, mode) as f: pass with self.assertRaises(FileExistsError): - with LZMAFile(TESTFN, mode): - pass + LZMAFile(TESTFN, mode) def test_init_bad_mode(self): with self.assertRaises(ValueError): @@ -867,17 +865,59 @@ def test_read_from_file(self): with LZMAFile(TESTFN) as f: self.assertEqual(f.read(), INPUT) self.assertEqual(f.read(), b"") + self.assertIsInstance(f.fileno(), int) + self.assertIs(f.readable(), True) + self.assertIs(f.writable(), False) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) def test_read_from_file_with_bytes_filename(self): - try: - bytes_filename = TESTFN.encode("ascii") - except UnicodeEncodeError: - self.skipTest("Temporary file name needs to be ASCII") + bytes_filename = os.fsencode(TESTFN) with TempFile(TESTFN, COMPRESSED_XZ): with LZMAFile(bytes_filename) as f: self.assertEqual(f.read(), INPUT) self.assertEqual(f.read(), b"") + def test_read_from_fileobj(self): + with TempFile(TESTFN, COMPRESSED_XZ): + with open(TESTFN, 'rb') as raw: + with LZMAFile(raw) as f: + self.assertEqual(f.read(), INPUT) + self.assertEqual(f.read(), b"") + self.assertEqual(f.fileno(), raw.fileno()) + self.assertIs(f.readable(), True) + self.assertIs(f.writable(), False) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + + def test_read_from_fileobj_with_int_name(self): + with TempFile(TESTFN, COMPRESSED_XZ): + fd = os.open(TESTFN, os.O_RDONLY) + with open(fd, 'rb') as raw: + with LZMAFile(raw) as f: + self.assertEqual(f.read(), INPUT) + self.assertEqual(f.read(), b"") + self.assertEqual(f.fileno(), raw.fileno()) + self.assertIs(f.readable(), True) + self.assertIs(f.writable(), False) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + def test_read_incomplete(self): with LZMAFile(BytesIO(COMPRESSED_XZ[:128])) as f: self.assertRaises(EOFError, f.read) @@ -1051,6 +1091,17 @@ def test_write_to_file(self): try: with LZMAFile(TESTFN, "w") as f: f.write(INPUT) + self.assertIsInstance(f.fileno(), int) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), False) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + expected = lzma.compress(INPUT) with open(TESTFN, "rb") as f: self.assertEqual(f.read(), expected) @@ -1058,10 +1109,7 @@ def test_write_to_file(self): unlink(TESTFN) def test_write_to_file_with_bytes_filename(self): - try: - bytes_filename = TESTFN.encode("ascii") - except UnicodeEncodeError: - self.skipTest("Temporary file name needs to be ASCII") + bytes_filename = os.fsencode(TESTFN) try: with LZMAFile(bytes_filename, "w") as f: f.write(INPUT) @@ -1071,6 +1119,51 @@ def test_write_to_file_with_bytes_filename(self): finally: unlink(TESTFN) + def test_write_to_fileobj(self): + try: + with open(TESTFN, "wb") as raw: + with LZMAFile(raw, "w") as f: + f.write(INPUT) + self.assertEqual(f.fileno(), raw.fileno()) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), False) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + + expected = lzma.compress(INPUT) + with open(TESTFN, "rb") as f: + self.assertEqual(f.read(), expected) + finally: + unlink(TESTFN) + + def test_write_to_fileobj_with_int_name(self): + try: + fd = os.open(TESTFN, os.O_WRONLY | os.O_CREAT | os.O_TRUNC) + with open(fd, 'wb') as raw: + with LZMAFile(raw, "w") as f: + f.write(INPUT) + self.assertEqual(f.fileno(), raw.fileno()) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), False) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + + expected = lzma.compress(INPUT) + with open(TESTFN, "rb") as f: + self.assertEqual(f.read(), expected) + finally: + unlink(TESTFN) + def test_write_append_to_file(self): part1 = INPUT[:1024] part2 = INPUT[1024:1536] @@ -1276,7 +1369,7 @@ def test_filename(self): self.assertEqual(f.read(), INPUT * 2) def test_with_pathlike_filename(self): - filename = pathlib.Path(TESTFN) + filename = FakePath(TESTFN) with TempFile(filename): with lzma.open(filename, "wb") as f: f.write(INPUT) diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 51f070e96047a6e..a047780fdd7e17c 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -507,14 +507,32 @@ def test_length_zero_header(self): with tarfile.open(support.findfile('recursion.tar', subdir='archivetestdata')): pass - def test_extractfile_name(self): + def test_extractfile_attrs(self): # gh-74468: TarFile.name must name a file, not a parent archive. file = self.tar.getmember('ustar/regtype') with self.tar.extractfile(file) as fobj: self.assertEqual(fobj.name, 'ustar/regtype') + self.assertRaises(AttributeError, fobj.fileno) + self.assertIs(fobj.readable(), True) + self.assertIs(fobj.writable(), False) + if self.is_stream: + self.assertRaises(AttributeError, fobj.seekable) + else: + self.assertIs(fobj.seekable(), True) + self.assertIs(fobj.closed, False) + self.assertIs(fobj.closed, True) + self.assertEqual(fobj.name, 'ustar/regtype') + self.assertRaises(AttributeError, fobj.fileno) + self.assertIs(fobj.readable(), True) + self.assertIs(fobj.writable(), False) + if self.is_stream: + self.assertRaises(AttributeError, fobj.seekable) + else: + self.assertIs(fobj.seekable(), True) class MiscReadTestBase(CommonReadTest): + is_stream = False def requires_name_attribute(self): pass @@ -807,6 +825,7 @@ def requires_name_attribute(self): class StreamReadTest(CommonReadTest, unittest.TestCase): prefix="r|" + is_stream = True def test_read_through(self): # Issue #11224: A poorly designed _FileInFile.read() method diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index 087fa8d65cc3368..7d89f753448dd75 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -4,7 +4,6 @@ import io import itertools import os -import pathlib import posixpath import struct import subprocess @@ -25,7 +24,7 @@ captured_stdout, captured_stderr, requires_subprocess ) from test.support.os_helper import ( - TESTFN, unlink, rmtree, temp_dir, temp_cwd, fd_count + TESTFN, unlink, rmtree, temp_dir, temp_cwd, fd_count, FakePath ) @@ -160,7 +159,7 @@ def test_open(self): self.zip_open_test(f, self.compression) def test_open_with_pathlike(self): - path = pathlib.Path(TESTFN2) + path = FakePath(TESTFN2) self.zip_open_test(path, self.compression) with zipfile.ZipFile(path, "r", self.compression) as zipfp: self.assertIsInstance(zipfp.filename, str) @@ -447,6 +446,27 @@ def write(self, data): self.assertEqual(zipfp.read('file1'), b'data1') self.assertEqual(zipfp.read('file2'), b'data2') + def test_zipextfile_attrs(self): + fname = "somefile.txt" + with zipfile.ZipFile(TESTFN2, mode="w") as zipfp: + zipfp.writestr(fname, "bogus") + + with zipfile.ZipFile(TESTFN2, mode="r") as zipfp: + with zipfp.open(fname) as fid: + self.assertEqual(fid.name, fname) + self.assertRaises(io.UnsupportedOperation, fid.fileno) + self.assertEqual(fid.mode, 'r') + self.assertIs(fid.readable(), True) + self.assertIs(fid.writable(), False) + self.assertIs(fid.seekable(), True) + self.assertIs(fid.closed, False) + self.assertIs(fid.closed, True) + self.assertEqual(fid.name, fname) + self.assertEqual(fid.mode, 'r') + self.assertRaises(io.UnsupportedOperation, fid.fileno) + self.assertRaises(ValueError, fid.readable) + self.assertIs(fid.writable(), False) + self.assertRaises(ValueError, fid.seekable) def tearDown(self): unlink(TESTFN) @@ -578,17 +598,16 @@ def test_write_default_name(self): def test_io_on_closed_zipextfile(self): fname = "somefile.txt" - with zipfile.ZipFile(TESTFN2, mode="w") as zipfp: + with zipfile.ZipFile(TESTFN2, mode="w", compression=self.compression) as zipfp: zipfp.writestr(fname, "bogus") with zipfile.ZipFile(TESTFN2, mode="r") as zipfp: with zipfp.open(fname) as fid: fid.close() + self.assertIs(fid.closed, True) self.assertRaises(ValueError, fid.read) self.assertRaises(ValueError, fid.seek, 0) self.assertRaises(ValueError, fid.tell) - self.assertRaises(ValueError, fid.readable) - self.assertRaises(ValueError, fid.seekable) def test_write_to_readonly(self): """Check that trying to call write() on a readonly ZipFile object @@ -1285,6 +1304,21 @@ def test_issue44439(self): self.assertEqual(data.write(q), LENGTH) self.assertEqual(zip.getinfo('data').file_size, LENGTH) + def test_zipwritefile_attrs(self): + fname = "somefile.txt" + with zipfile.ZipFile(TESTFN2, mode="w", compression=self.compression) as zipfp: + with zipfp.open(fname, 'w') as fid: + self.assertRaises(io.UnsupportedOperation, fid.fileno) + self.assertIs(fid.readable(), False) + self.assertIs(fid.writable(), True) + self.assertIs(fid.seekable(), False) + self.assertIs(fid.closed, False) + self.assertIs(fid.closed, True) + self.assertRaises(io.UnsupportedOperation, fid.fileno) + self.assertIs(fid.readable(), False) + self.assertIs(fid.writable(), True) + self.assertIs(fid.seekable(), False) + class StoredWriterTests(AbstractWriterTests, unittest.TestCase): compression = zipfile.ZIP_STORED @@ -1487,7 +1521,7 @@ def test_write_pathlike(self): fp.write("print(42)\n") with TemporaryFile() as t, zipfile.PyZipFile(t, "w") as zipfp: - zipfp.writepy(pathlib.Path(TESTFN2) / "mod1.py") + zipfp.writepy(FakePath(os.path.join(TESTFN2, "mod1.py"))) names = zipfp.namelist() self.assertCompiledIn('mod1.py', names) finally: @@ -1545,7 +1579,7 @@ def test_extract_with_target(self): def test_extract_with_target_pathlike(self): with temp_dir() as extdir: - self._test_extract_with_target(pathlib.Path(extdir)) + self._test_extract_with_target(FakePath(extdir)) def test_extract_all(self): with temp_cwd(): @@ -1580,7 +1614,7 @@ def test_extract_all_with_target(self): def test_extract_all_with_target_pathlike(self): with temp_dir() as extdir: - self._test_extract_all_with_target(pathlib.Path(extdir)) + self._test_extract_all_with_target(FakePath(extdir)) def check_file(self, filename, content): self.assertTrue(os.path.isfile(filename)) @@ -1893,7 +1927,7 @@ def test_is_zip_erroneous_file(self): fp.write("this is not a legal zip file\n") self.assertFalse(zipfile.is_zipfile(TESTFN)) # - passing a path-like object - self.assertFalse(zipfile.is_zipfile(pathlib.Path(TESTFN))) + self.assertFalse(zipfile.is_zipfile(FakePath(TESTFN))) # - passing a file object with open(TESTFN, "rb") as fp: self.assertFalse(zipfile.is_zipfile(fp)) @@ -3013,7 +3047,7 @@ def test_from_file(self): self.assertEqual(zi.file_size, os.path.getsize(__file__)) def test_from_file_pathlike(self): - zi = zipfile.ZipInfo.from_file(pathlib.Path(__file__)) + zi = zipfile.ZipInfo.from_file(FakePath(__file__)) self.assertEqual(posixpath.basename(zi.filename), 'test_core.py') self.assertFalse(zi.is_dir()) self.assertEqual(zi.file_size, os.path.getsize(__file__)) From d53560deb2c9ae12147201003fe63b266654ee21 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra <jelle.zijlstra@gmail.com> Date: Wed, 28 Feb 2024 01:56:40 -0800 Subject: [PATCH 484/507] gh-105858: Expose some union-related objects as internal APIs (GH-116025) We now use these in the AST parsing code after gh-105880. A few comparable types (e.g., NoneType) are already exposed as internal APIs. --- Include/internal/pycore_unionobject.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Include/internal/pycore_unionobject.h b/Include/internal/pycore_unionobject.h index 87264635b6e1cfe..6ece7134cdeca03 100644 --- a/Include/internal/pycore_unionobject.h +++ b/Include/internal/pycore_unionobject.h @@ -8,9 +8,11 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif -extern PyTypeObject _PyUnion_Type; +// For extensions created by test_peg_generator +PyAPI_DATA(PyTypeObject) _PyUnion_Type; +PyAPI_FUNC(PyObject *) _Py_union_type_or(PyObject *, PyObject *); + #define _PyUnion_Check(op) Py_IS_TYPE((op), &_PyUnion_Type) -extern PyObject *_Py_union_type_or(PyObject *, PyObject *); #define _PyGenericAlias_Check(op) PyObject_TypeCheck((op), &Py_GenericAliasType) extern PyObject *_Py_subs_parameters(PyObject *, PyObject *, PyObject *, PyObject *); From 1752b51012269eaa35f7a28f162d18479a4f72aa Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado <Pablogsal@gmail.com> Date: Wed, 28 Feb 2024 10:17:34 +0000 Subject: [PATCH 485/507] gh-115773: Add tests to exercise the _Py_DebugOffsets structure (#115774) --- Include/internal/pycore_runtime.h | 77 +-- Include/internal/pycore_runtime_init.h | 5 + Lib/test/test_external_inspection.py | 84 +++ Modules/Setup | 1 + Modules/Setup.stdlib.in | 1 + Modules/_testexternalinspection.c | 629 ++++++++++++++++++++ Tools/build/generate_stdlib_module_names.py | 1 + configure | 50 ++ configure.ac | 3 +- pyconfig.h.in | 3 + 10 files changed, 818 insertions(+), 36 deletions(-) create mode 100644 Lib/test/test_external_inspection.py create mode 100644 Modules/_testexternalinspection.c diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index 0c9c59e85b2fcfc..dc6f6f100f7a922 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -55,74 +55,81 @@ typedef struct _Py_DebugOffsets { uint64_t version; // Runtime state offset; struct _runtime_state { - off_t finalizing; - off_t interpreters_head; + uint64_t finalizing; + uint64_t interpreters_head; } runtime_state; // Interpreter state offset; struct _interpreter_state { - off_t next; - off_t threads_head; - off_t gc; - off_t imports_modules; - off_t sysdict; - off_t builtins; - off_t ceval_gil; - off_t gil_runtime_state_locked; - off_t gil_runtime_state_holder; + uint64_t next; + uint64_t threads_head; + uint64_t gc; + uint64_t imports_modules; + uint64_t sysdict; + uint64_t builtins; + uint64_t ceval_gil; + uint64_t gil_runtime_state_locked; + uint64_t gil_runtime_state_holder; } interpreter_state; // Thread state offset; struct _thread_state{ - off_t prev; - off_t next; - off_t interp; - off_t current_frame; - off_t thread_id; - off_t native_thread_id; + uint64_t prev; + uint64_t next; + uint64_t interp; + uint64_t current_frame; + uint64_t thread_id; + uint64_t native_thread_id; } thread_state; // InterpreterFrame offset; struct _interpreter_frame { - off_t previous; - off_t executable; - off_t instr_ptr; - off_t localsplus; - off_t owner; + uint64_t previous; + uint64_t executable; + uint64_t instr_ptr; + uint64_t localsplus; + uint64_t owner; } interpreter_frame; // CFrame offset; struct _cframe { - off_t current_frame; - off_t previous; + uint64_t current_frame; + uint64_t previous; } cframe; // Code object offset; struct _code_object { - off_t filename; - off_t name; - off_t linetable; - off_t firstlineno; - off_t argcount; - off_t localsplusnames; - off_t localspluskinds; - off_t co_code_adaptive; + uint64_t filename; + uint64_t name; + uint64_t linetable; + uint64_t firstlineno; + uint64_t argcount; + uint64_t localsplusnames; + uint64_t localspluskinds; + uint64_t co_code_adaptive; } code_object; // PyObject offset; struct _pyobject { - off_t ob_type; + uint64_t ob_type; } pyobject; // PyTypeObject object offset; struct _type_object { - off_t tp_name; + uint64_t tp_name; } type_object; // PyTuple object offset; struct _tuple_object { - off_t ob_item; + uint64_t ob_item; } tuple_object; + + // Unicode object offset; + struct _unicode_object { + uint64_t state; + uint64_t length; + size_t asciiobject_size; + } unicode_object; } _Py_DebugOffsets; /* Full Python runtime state */ diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index d093047d4bc09da..cc47b9a82e28792 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -83,6 +83,11 @@ extern PyTypeObject _PyExc_MemoryError; .tuple_object = { \ .ob_item = offsetof(PyTupleObject, ob_item), \ }, \ + .unicode_object = { \ + .state = offsetof(PyUnicodeObject, _base._base.state), \ + .length = offsetof(PyUnicodeObject, _base._base.length), \ + .asciiobject_size = sizeof(PyASCIIObject), \ + }, \ }, \ .allocators = { \ .standard = _pymem_allocators_standard_INIT(runtime), \ diff --git a/Lib/test/test_external_inspection.py b/Lib/test/test_external_inspection.py new file mode 100644 index 000000000000000..86c07de507e39cb --- /dev/null +++ b/Lib/test/test_external_inspection.py @@ -0,0 +1,84 @@ +import unittest +import os +import textwrap +import importlib +import sys +from test.support import os_helper, SHORT_TIMEOUT +from test.support.script_helper import make_script + +import subprocess + +PROCESS_VM_READV_SUPPORTED = False + +try: + from _testexternalinspection import PROCESS_VM_READV_SUPPORTED + from _testexternalinspection import get_stack_trace +except ImportError: + unittest.skip("Test only runs when _testexternalinspection is available") + +def _make_test_script(script_dir, script_basename, source): + to_return = make_script(script_dir, script_basename, source) + importlib.invalidate_caches() + return to_return + +class TestGetStackTrace(unittest.TestCase): + + @unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux", "Test only runs on Linux and MacOS") + @unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, "Test only runs on Linux with process_vm_readv support") + def test_remote_stack_trace(self): + # Spawn a process with some realistic Python code + script = textwrap.dedent("""\ + import time, sys, os + def bar(): + for x in range(100): + if x == 50: + baz() + def baz(): + foo() + + def foo(): + fifo = sys.argv[1] + with open(sys.argv[1], "w") as fifo: + fifo.write("ready") + time.sleep(1000) + + bar() + """) + stack_trace = None + with os_helper.temp_dir() as work_dir: + script_dir = os.path.join(work_dir, "script_pkg") + os.mkdir(script_dir) + fifo = f"{work_dir}/the_fifo" + os.mkfifo(fifo) + script_name = _make_test_script(script_dir, 'script', script) + try: + p = subprocess.Popen([sys.executable, script_name, str(fifo)]) + with open(fifo, "r") as fifo_file: + response = fifo_file.read() + self.assertEqual(response, "ready") + stack_trace = get_stack_trace(p.pid) + except PermissionError: + self.skipTest("Insufficient permissions to read the stack trace") + finally: + os.remove(fifo) + p.kill() + p.terminate() + p.wait(timeout=SHORT_TIMEOUT) + + + expected_stack_trace = [ + 'foo', + 'baz', + 'bar', + '<module>' + ] + self.assertEqual(stack_trace, expected_stack_trace) + + @unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux", "Test only runs on Linux and MacOS") + @unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, "Test only runs on Linux with process_vm_readv support") + def test_self_trace(self): + stack_trace = get_stack_trace(os.getpid()) + self.assertEqual(stack_trace[0], "test_self_trace") + +if __name__ == "__main__": + unittest.main() diff --git a/Modules/Setup b/Modules/Setup index 8ad9a5aebbfcaad..cd1cf24c25d4064 100644 --- a/Modules/Setup +++ b/Modules/Setup @@ -285,6 +285,7 @@ PYTHONPATH=$(COREPYTHONPATH) #_testcapi _testcapimodule.c #_testimportmultiple _testimportmultiple.c #_testmultiphase _testmultiphase.c +#_testexternalinspection _testexternalinspection.c #_testsinglephase _testsinglephase.c # --- diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index e98775a48087653..73b082691a3fd49 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -171,6 +171,7 @@ @MODULE__TESTIMPORTMULTIPLE_TRUE@_testimportmultiple _testimportmultiple.c @MODULE__TESTMULTIPHASE_TRUE@_testmultiphase _testmultiphase.c @MODULE__TESTMULTIPHASE_TRUE@_testsinglephase _testsinglephase.c +@MODULE__TESTEXTERNALINSPECTION_TRUE@_testexternalinspection _testexternalinspection.c @MODULE__CTYPES_TEST_TRUE@_ctypes_test _ctypes/_ctypes_test.c # Limited API template modules; must be built as shared modules. diff --git a/Modules/_testexternalinspection.c b/Modules/_testexternalinspection.c new file mode 100644 index 000000000000000..19ee3b8dd1428d6 --- /dev/null +++ b/Modules/_testexternalinspection.c @@ -0,0 +1,629 @@ +#define _GNU_SOURCE + +#ifdef __linux__ +# include <elf.h> +# include <sys/uio.h> +# if INTPTR_MAX == INT64_MAX +# define Elf_Ehdr Elf64_Ehdr +# define Elf_Shdr Elf64_Shdr +# define Elf_Phdr Elf64_Phdr +# else +# define Elf_Ehdr Elf32_Ehdr +# define Elf_Shdr Elf32_Shdr +# define Elf_Phdr Elf32_Phdr +# endif +# include <sys/mman.h> +#endif + +#ifdef __APPLE__ +# include <libproc.h> +# include <mach-o/fat.h> +# include <mach-o/loader.h> +# include <mach-o/nlist.h> +# include <mach/mach.h> +# include <mach/mach_vm.h> +# include <mach/machine.h> +# include <sys/mman.h> +# include <sys/proc.h> +# include <sys/sysctl.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif +#include "Python.h" +#include <internal/pycore_runtime.h> + +#ifndef HAVE_PROCESS_VM_READV +# define HAVE_PROCESS_VM_READV 0 +#endif + +#ifdef __APPLE__ +static void* +analyze_macho64(mach_port_t proc_ref, void* base, void* map) +{ + struct mach_header_64* hdr = (struct mach_header_64*)map; + int ncmds = hdr->ncmds; + + int cmd_cnt = 0; + struct segment_command_64* cmd = map + sizeof(struct mach_header_64); + + mach_vm_size_t size = 0; + mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t); + mach_vm_address_t address = (mach_vm_address_t)base; + vm_region_basic_info_data_64_t region_info; + mach_port_t object_name; + + for (int i = 0; cmd_cnt < 2 && i < ncmds; i++) { + if (cmd->cmd == LC_SEGMENT_64 && strcmp(cmd->segname, "__DATA") == 0) { + while (cmd->filesize != size) { + address += size; + if (mach_vm_region( + proc_ref, + &address, + &size, + VM_REGION_BASIC_INFO_64, + (vm_region_info_t)®ion_info, // cppcheck-suppress [uninitvar] + &count, + &object_name) + != KERN_SUCCESS) + { + PyErr_SetString(PyExc_RuntimeError, "Cannot get any more VM maps.\n"); + return NULL; + } + } + base = (void*)address - cmd->vmaddr; + + int nsects = cmd->nsects; + struct section_64* sec = + (struct section_64*)((void*)cmd + sizeof(struct segment_command_64)); + for (int j = 0; j < nsects; j++) { + if (strcmp(sec[j].sectname, "PyRuntime") == 0) { + return base + sec[j].addr; + } + } + cmd_cnt++; + } + + cmd = (struct segment_command_64*)((void*)cmd + cmd->cmdsize); + } + return NULL; +} + +static void* +analyze_macho(char* path, void* base, mach_vm_size_t size, mach_port_t proc_ref) +{ + int fd = open(path, O_RDONLY); + if (fd == -1) { + PyErr_Format(PyExc_RuntimeError, "Cannot open binary %s\n", path); + return NULL; + } + + struct stat fs; + if (fstat(fd, &fs) == -1) { + PyErr_Format(PyExc_RuntimeError, "Cannot get size of binary %s\n", path); + close(fd); + return NULL; + } + + void* map = mmap(0, fs.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (map == MAP_FAILED) { + PyErr_Format(PyExc_RuntimeError, "Cannot map binary %s\n", path); + close(fd); + return NULL; + } + + void* result = NULL; + + struct mach_header_64* hdr = (struct mach_header_64*)map; + switch (hdr->magic) { + case MH_MAGIC: + case MH_CIGAM: + case FAT_MAGIC: + case FAT_CIGAM: + PyErr_SetString(PyExc_RuntimeError, "32-bit Mach-O binaries are not supported"); + break; + case MH_MAGIC_64: + case MH_CIGAM_64: + result = analyze_macho64(proc_ref, base, map); + break; + default: + PyErr_SetString(PyExc_RuntimeError, "Unknown Mach-O magic"); + break; + } + + munmap(map, fs.st_size); + if (close(fd) != 0) { + PyErr_SetFromErrno(PyExc_OSError); + } + return result; +} + +static mach_port_t +pid_to_task(pid_t pid) +{ + mach_port_t task; + kern_return_t result; + + result = task_for_pid(mach_task_self(), pid, &task); + if (result != KERN_SUCCESS) { + PyErr_Format(PyExc_PermissionError, "Cannot get task for PID %d", pid); + return 0; + } + return task; +} + +static void* +get_py_runtime_macos(pid_t pid) +{ + mach_vm_address_t address = 0; + mach_vm_size_t size = 0; + mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t); + vm_region_basic_info_data_64_t region_info; + mach_port_t object_name; + + mach_port_t proc_ref = pid_to_task(pid); + if (proc_ref == 0) { + PyErr_SetString(PyExc_PermissionError, "Cannot get task for PID"); + return NULL; + } + + int match_found = 0; + char map_filename[MAXPATHLEN + 1]; + void* result_address = NULL; + while (mach_vm_region( + proc_ref, + &address, + &size, + VM_REGION_BASIC_INFO_64, + (vm_region_info_t)®ion_info, + &count, + &object_name) + == KERN_SUCCESS) + { + int path_len = proc_regionfilename(pid, address, map_filename, MAXPATHLEN); + if (path_len == 0) { + address += size; + continue; + } + + char* filename = strrchr(map_filename, '/'); + if (filename != NULL) { + filename++; // Move past the '/' + } else { + filename = map_filename; // No path, use the whole string + } + + // Check if the filename starts with "python" or "libpython" + if (!match_found && strncmp(filename, "python", 6) == 0) { + match_found = 1; + result_address = analyze_macho(map_filename, (void*)address, size, proc_ref); + } + if (strncmp(filename, "libpython", 9) == 0) { + match_found = 1; + result_address = analyze_macho(map_filename, (void*)address, size, proc_ref); + break; + } + + address += size; + } + return result_address; +} +#endif + +#ifdef __linux__ +void* +find_python_map_start_address(pid_t pid, char* result_filename) +{ + char maps_file_path[64]; + sprintf(maps_file_path, "/proc/%d/maps", pid); + + FILE* maps_file = fopen(maps_file_path, "r"); + if (maps_file == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + int match_found = 0; + + char line[256]; + char map_filename[PATH_MAX]; + void* result_address = 0; + while (fgets(line, sizeof(line), maps_file) != NULL) { + unsigned long start_address = 0; + sscanf(line, "%lx-%*x %*s %*s %*s %*s %s", &start_address, map_filename); + char* filename = strrchr(map_filename, '/'); + if (filename != NULL) { + filename++; // Move past the '/' + } else { + filename = map_filename; // No path, use the whole string + } + + // Check if the filename starts with "python" or "libpython" + if (!match_found && strncmp(filename, "python", 6) == 0) { + match_found = 1; + result_address = (void*)start_address; + strcpy(result_filename, map_filename); + } + if (strncmp(filename, "libpython", 9) == 0) { + match_found = 1; + result_address = (void*)start_address; + strcpy(result_filename, map_filename); + break; + } + } + + fclose(maps_file); + + if (!match_found) { + map_filename[0] = '\0'; + } + + return result_address; +} + +void* +get_py_runtime_linux(pid_t pid) +{ + char elf_file[256]; + void* start_address = (void*)find_python_map_start_address(pid, elf_file); + + if (start_address == 0) { + PyErr_SetString(PyExc_RuntimeError, "No memory map associated with python or libpython found"); + return NULL; + } + + void* result = NULL; + void* file_memory = NULL; + + int fd = open(elf_file, O_RDONLY); + if (fd < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto exit; + } + + struct stat file_stats; + if (fstat(fd, &file_stats) != 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto exit; + } + + file_memory = mmap(NULL, file_stats.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (file_memory == MAP_FAILED) { + PyErr_SetFromErrno(PyExc_OSError); + goto exit; + } + + Elf_Ehdr* elf_header = (Elf_Ehdr*)file_memory; + + Elf_Shdr* section_header_table = (Elf_Shdr*)(file_memory + elf_header->e_shoff); + + Elf_Shdr* shstrtab_section = §ion_header_table[elf_header->e_shstrndx]; + char* shstrtab = (char*)(file_memory + shstrtab_section->sh_offset); + + Elf_Shdr* py_runtime_section = NULL; + for (int i = 0; i < elf_header->e_shnum; i++) { + if (strcmp(".PyRuntime", shstrtab + section_header_table[i].sh_name) == 0) { + py_runtime_section = §ion_header_table[i]; + break; + } + } + + Elf_Phdr* program_header_table = (Elf_Phdr*)(file_memory + elf_header->e_phoff); + // Find the first PT_LOAD segment + Elf_Phdr* first_load_segment = NULL; + for (int i = 0; i < elf_header->e_phnum; i++) { + if (program_header_table[i].p_type == PT_LOAD) { + first_load_segment = &program_header_table[i]; + break; + } + } + + if (py_runtime_section != NULL && first_load_segment != NULL) { + uintptr_t elf_load_addr = first_load_segment->p_vaddr + - (first_load_segment->p_vaddr % first_load_segment->p_align); + result = start_address + py_runtime_section->sh_addr - elf_load_addr; + } + +exit: + if (close(fd) != 0) { + PyErr_SetFromErrno(PyExc_OSError); + } + if (file_memory != NULL) { + munmap(file_memory, file_stats.st_size); + } + return result; +} +#endif + +ssize_t +read_memory(pid_t pid, void* remote_address, size_t len, void* dst) +{ + ssize_t total_bytes_read = 0; +#ifdef __linux__ + struct iovec local[1]; + struct iovec remote[1]; + ssize_t result = 0; + ssize_t read = 0; + + do { + local[0].iov_base = dst + result; + local[0].iov_len = len - result; + remote[0].iov_base = (void*)(remote_address + result); + remote[0].iov_len = len - result; + + read = process_vm_readv(pid, local, 1, remote, 1, 0); + if (read < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + + result += read; + } while ((size_t)read != local[0].iov_len); + total_bytes_read = result; +#elif defined(__APPLE__) + ssize_t result = -1; + kern_return_t kr = mach_vm_read_overwrite( + pid_to_task(pid), + (mach_vm_address_t)remote_address, + len, + (mach_vm_address_t)dst, + (mach_vm_size_t*)&result); + + if (kr != KERN_SUCCESS) { + switch (kr) { + case KERN_PROTECTION_FAILURE: + PyErr_SetString(PyExc_PermissionError, "Not enough permissions to read memory"); + break; + case KERN_INVALID_ARGUMENT: + PyErr_SetString(PyExc_PermissionError, "Invalid argument to mach_vm_read_overwrite"); + break; + default: + PyErr_SetString(PyExc_RuntimeError, "Unknown error reading memory"); + } + return -1; + } + total_bytes_read = len; +#else + return -1; +#endif + return total_bytes_read; +} + +int +read_string(pid_t pid, _Py_DebugOffsets* debug_offsets, void* address, char* buffer, Py_ssize_t size) +{ + Py_ssize_t len; + ssize_t bytes_read = + read_memory(pid, address + debug_offsets->unicode_object.length, sizeof(Py_ssize_t), &len); + if (bytes_read == -1) { + return -1; + } + if (len >= size) { + PyErr_SetString(PyExc_RuntimeError, "Buffer too small"); + return -1; + } + size_t offset = debug_offsets->unicode_object.asciiobject_size; + bytes_read = read_memory(pid, address + offset, len, buffer); + if (bytes_read == -1) { + return -1; + } + buffer[len] = '\0'; + return 0; +} + +void* +get_py_runtime(pid_t pid) +{ +#if defined(__linux__) + return get_py_runtime_linux(pid); +#elif defined(__APPLE__) + return get_py_runtime_macos(pid); +#else + return NULL; +#endif +} + +static int +parse_code_object( + int pid, + PyObject* result, + struct _Py_DebugOffsets* offsets, + void* address, + void** previous_frame) +{ + void* address_of_function_name; + read_memory( + pid, + (void*)(address + offsets->code_object.name), + sizeof(void*), + &address_of_function_name); + + if (address_of_function_name == NULL) { + PyErr_SetString(PyExc_RuntimeError, "No function name found"); + return -1; + } + + char function_name[256]; + if (read_string(pid, offsets, address_of_function_name, function_name, sizeof(function_name)) != 0) { + return -1; + } + + PyObject* py_function_name = PyUnicode_FromString(function_name); + if (py_function_name == NULL) { + return -1; + } + + if (PyList_Append(result, py_function_name) == -1) { + Py_DECREF(py_function_name); + return -1; + } + Py_DECREF(py_function_name); + + return 0; +} + +static int +parse_frame_object( + int pid, + PyObject* result, + struct _Py_DebugOffsets* offsets, + void* address, + void** previous_frame) +{ + ssize_t bytes_read = read_memory( + pid, + (void*)(address + offsets->interpreter_frame.previous), + sizeof(void*), + previous_frame); + if (bytes_read == -1) { + return -1; + } + + char owner; + bytes_read = + read_memory(pid, (void*)(address + offsets->interpreter_frame.owner), sizeof(char), &owner); + if (bytes_read < 0) { + return -1; + } + + if (owner == FRAME_OWNED_BY_CSTACK) { + return 0; + } + + void* address_of_code_object; + bytes_read = read_memory( + pid, + (void*)(address + offsets->interpreter_frame.executable), + sizeof(void*), + &address_of_code_object); + if (bytes_read == -1) { + return -1; + } + + if (address_of_code_object == NULL) { + return 0; + } + return parse_code_object(pid, result, offsets, address_of_code_object, previous_frame); +} + +static PyObject* +get_stack_trace(PyObject* self, PyObject* args) +{ +#if (!defined(__linux__) && !defined(__APPLE__)) || (defined(__linux__) && !HAVE_PROCESS_VM_READV) + PyErr_SetString(PyExc_RuntimeError, "get_stack_trace is not supported on this platform"); + return NULL; +#endif + int pid; + + if (!PyArg_ParseTuple(args, "i", &pid)) { + return NULL; + } + + void* runtime_start_address = get_py_runtime(pid); + if (runtime_start_address == NULL) { + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_RuntimeError, "Failed to get .PyRuntime address"); + } + return NULL; + } + size_t size = sizeof(struct _Py_DebugOffsets); + struct _Py_DebugOffsets local_debug_offsets; + + ssize_t bytes_read = read_memory(pid, runtime_start_address, size, &local_debug_offsets); + if (bytes_read == -1) { + return NULL; + } + off_t thread_state_list_head = local_debug_offsets.runtime_state.interpreters_head; + + void* address_of_interpreter_state; + bytes_read = read_memory( + pid, + (void*)(runtime_start_address + thread_state_list_head), + sizeof(void*), + &address_of_interpreter_state); + if (bytes_read == -1) { + return NULL; + } + + if (address_of_interpreter_state == NULL) { + PyErr_SetString(PyExc_RuntimeError, "No interpreter state found"); + return NULL; + } + + void* address_of_thread; + bytes_read = read_memory( + pid, + (void*)(address_of_interpreter_state + local_debug_offsets.interpreter_state.threads_head), + sizeof(void*), + &address_of_thread); + if (bytes_read == -1) { + return NULL; + } + + PyObject* result = PyList_New(0); + if (result == NULL) { + return NULL; + } + + // No Python frames are available for us (can happen at tear-down). + if (address_of_thread != NULL) { + void* address_of_current_frame; + (void)read_memory( + pid, + (void*)(address_of_thread + local_debug_offsets.thread_state.current_frame), + sizeof(void*), + &address_of_current_frame); + while (address_of_current_frame != NULL) { + if (parse_frame_object( + pid, + result, + &local_debug_offsets, + address_of_current_frame, + &address_of_current_frame) + < 0) + { + Py_DECREF(result); + return NULL; + } + } + } + + return result; +} + +static PyMethodDef methods[] = { + {"get_stack_trace", get_stack_trace, METH_VARARGS, "Get the Python stack from a given PID"}, + {NULL, NULL, 0, NULL}, +}; + +static struct PyModuleDef module = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "_testexternalinspection", + .m_size = -1, + .m_methods = methods, +}; + +PyMODINIT_FUNC +PyInit__testexternalinspection(void) +{ + PyObject* mod = PyModule_Create(&module); + int rc = PyModule_AddIntConstant(mod, "PROCESS_VM_READV_SUPPORTED", HAVE_PROCESS_VM_READV); + if (rc < 0) { + Py_DECREF(mod); + return NULL; + } + return mod; +} diff --git a/Tools/build/generate_stdlib_module_names.py b/Tools/build/generate_stdlib_module_names.py index 5dce4e042d1eb44..588dfda50658be3 100644 --- a/Tools/build/generate_stdlib_module_names.py +++ b/Tools/build/generate_stdlib_module_names.py @@ -34,6 +34,7 @@ '_testinternalcapi', '_testmultiphase', '_testsinglephase', + '_testexternalinspection', '_xxsubinterpreters', '_xxinterpchannels', '_xxinterpqueues', diff --git a/configure b/configure index b565cdbfae1327a..c204c9eb499559f 100755 --- a/configure +++ b/configure @@ -661,6 +661,8 @@ MODULE__XXTESTFUZZ_FALSE MODULE__XXTESTFUZZ_TRUE MODULE_XXSUBTYPE_FALSE MODULE_XXSUBTYPE_TRUE +MODULE__TESTEXTERNALINSPECTION_FALSE +MODULE__TESTEXTERNALINSPECTION_TRUE MODULE__TESTMULTIPHASE_FALSE MODULE__TESTMULTIPHASE_TRUE MODULE__TESTIMPORTMULTIPLE_FALSE @@ -17997,6 +17999,12 @@ if test "x$ac_cv_func_preadv2" = xyes then : printf "%s\n" "#define HAVE_PREADV2 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "process_vm_readv" "ac_cv_func_process_vm_readv" +if test "x$ac_cv_func_process_vm_readv" = xyes +then : + printf "%s\n" "#define HAVE_PROCESS_VM_READV 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "pthread_cond_timedwait_relative_np" "ac_cv_func_pthread_cond_timedwait_relative_np" if test "x$ac_cv_func_pthread_cond_timedwait_relative_np" = xyes @@ -30688,6 +30696,44 @@ fi printf "%s\n" "$py_cv_module__testmultiphase" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module _testexternalinspection" >&5 +printf %s "checking for stdlib extension module _testexternalinspection... " >&6; } + if test "$py_cv_module__testexternalinspection" != "n/a" +then : + + if test "$TEST_MODULES" = yes +then : + if true +then : + py_cv_module__testexternalinspection=yes +else $as_nop + py_cv_module__testexternalinspection=missing +fi +else $as_nop + py_cv_module__testexternalinspection=disabled +fi + +fi + as_fn_append MODULE_BLOCK "MODULE__TESTEXTERNALINSPECTION_STATE=$py_cv_module__testexternalinspection$as_nl" + if test "x$py_cv_module__testexternalinspection" = xyes +then : + + + + +fi + if test "$py_cv_module__testexternalinspection" = yes; then + MODULE__TESTEXTERNALINSPECTION_TRUE= + MODULE__TESTEXTERNALINSPECTION_FALSE='#' +else + MODULE__TESTEXTERNALINSPECTION_TRUE='#' + MODULE__TESTEXTERNALINSPECTION_FALSE= +fi + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $py_cv_module__testexternalinspection" >&5 +printf "%s\n" "$py_cv_module__testexternalinspection" >&6; } + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module xxsubtype" >&5 printf %s "checking for stdlib extension module xxsubtype... " >&6; } if test "$py_cv_module_xxsubtype" != "n/a" @@ -31300,6 +31346,10 @@ if test -z "${MODULE__TESTMULTIPHASE_TRUE}" && test -z "${MODULE__TESTMULTIPHASE as_fn_error $? "conditional \"MODULE__TESTMULTIPHASE\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi +if test -z "${MODULE__TESTEXTERNALINSPECTION_TRUE}" && test -z "${MODULE__TESTEXTERNALINSPECTION_FALSE}"; then + as_fn_error $? "conditional \"MODULE__TESTEXTERNALINSPECTION\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi if test -z "${MODULE_XXSUBTYPE_TRUE}" && test -z "${MODULE_XXSUBTYPE_FALSE}"; then as_fn_error $? "conditional \"MODULE_XXSUBTYPE\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 diff --git a/configure.ac b/configure.ac index 0694ef06364f82c..702e588357789cd 100644 --- a/configure.ac +++ b/configure.ac @@ -4920,7 +4920,7 @@ AC_CHECK_FUNCS([ \ mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ pipe2 plock poll posix_fadvise posix_fallocate posix_openpt posix_spawn posix_spawnp \ posix_spawn_file_actions_addclosefrom_np \ - pread preadv preadv2 pthread_cond_timedwait_relative_np pthread_condattr_setclock pthread_init \ + pread preadv preadv2 process_vm_readv pthread_cond_timedwait_relative_np pthread_condattr_setclock pthread_init \ pthread_kill ptsname ptsname_r pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \ sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \ @@ -7581,6 +7581,7 @@ PY_STDLIB_MOD([_testinternalcapi], [test "$TEST_MODULES" = yes]) PY_STDLIB_MOD([_testbuffer], [test "$TEST_MODULES" = yes]) PY_STDLIB_MOD([_testimportmultiple], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes]) PY_STDLIB_MOD([_testmultiphase], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes]) +PY_STDLIB_MOD([_testexternalinspection], [test "$TEST_MODULES" = yes]) PY_STDLIB_MOD([xxsubtype], [test "$TEST_MODULES" = yes]) PY_STDLIB_MOD([_xxtestfuzz], [test "$TEST_MODULES" = yes]) PY_STDLIB_MOD([_ctypes_test], diff --git a/pyconfig.h.in b/pyconfig.h.in index 63a3437ebb3eac1..b93cac9d23c796a 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -933,6 +933,9 @@ /* Define to 1 if you have the <process.h> header file. */ #undef HAVE_PROCESS_H +/* Define to 1 if you have the `process_vm_readv' function. */ +#undef HAVE_PROCESS_VM_READV + /* Define if your compiler supports function prototype */ #undef HAVE_PROTOTYPES From 3b63d0769f49171f53e9cecc686fa01a383bd4b1 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Wed, 28 Feb 2024 13:04:23 +0200 Subject: [PATCH 486/507] gh-116030: test_unparse: Add ``ctx`` argument to ``ast.Name`` calls (#116031) --- Lib/test/test_unparse.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py index 106704ba8c9c2dd..bb15f64c59dbd13 100644 --- a/Lib/test/test_unparse.py +++ b/Lib/test/test_unparse.py @@ -370,13 +370,13 @@ def test_slices(self): self.check_ast_roundtrip("a[i:j, k]") def test_invalid_raise(self): - self.check_invalid(ast.Raise(exc=None, cause=ast.Name(id="X"))) + self.check_invalid(ast.Raise(exc=None, cause=ast.Name(id="X", ctx=ast.Load()))) def test_invalid_fstring_value(self): self.check_invalid( ast.JoinedStr( values=[ - ast.Name(id="test"), + ast.Name(id="test", ctx=ast.Load()), ast.Constant(value="test") ] ) @@ -718,7 +718,7 @@ def test_function_with_type_params_and_bound(self): body=[ast.Pass()], decorator_list=[], returns=None, - type_params=[ast.TypeVar("T", bound=ast.Name("int"))], + type_params=[ast.TypeVar("T", bound=ast.Name("int", ctx=ast.Load()))], ) ast.fix_missing_locations(node) self.assertEqual(ast.unparse(node), "def f[T: int]():\n pass") From 7acf1fb5a70776429bd99e741d69471eb2d1c1bb Mon Sep 17 00:00:00 2001 From: Petr Viktorin <encukou@gmail.com> Date: Wed, 28 Feb 2024 12:53:48 +0100 Subject: [PATCH 487/507] gh-114911: Add CPUStopwatch test helper (GH-114912) A few of our tests measure the time of CPU-bound operation, mainly to avoid quadratic or worse behaviour. Add a helper to ignore GC and time spent in other processes. --- Lib/test/support/__init__.py | 40 +++++++++++++++++++++++++ Lib/test/test_int.py | 58 ++++++++++++++++-------------------- Lib/test/test_re.py | 19 ++++++------ 3 files changed, 75 insertions(+), 42 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 1d03ec0f5bd12be..401b2ce1fe213cb 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -2381,6 +2381,46 @@ def sleeping_retry(timeout, err_msg=None, /, delay = min(delay * 2, max_delay) +class CPUStopwatch: + """Context manager to roughly time a CPU-bound operation. + + Disables GC. Uses CPU time if it can (i.e. excludes sleeps & time of + other processes). + + N.B.: + - This *includes* time spent in other threads. + - Some systems only have a coarse resolution; check + stopwatch.clock_info.rseolution if. + + Usage: + + with ProcessStopwatch() as stopwatch: + ... + elapsed = stopwatch.seconds + resolution = stopwatch.clock_info.resolution + """ + def __enter__(self): + get_time = time.process_time + clock_info = time.get_clock_info('process_time') + if get_time() <= 0: # some platforms like WASM lack process_time() + get_time = time.monotonic + clock_info = time.get_clock_info('monotonic') + self.context = disable_gc() + self.context.__enter__() + self.get_time = get_time + self.clock_info = clock_info + self.start_time = get_time() + return self + + def __exit__(self, *exc): + try: + end_time = self.get_time() + finally: + result = self.context.__exit__(*exc) + self.seconds = end_time - self.start_time + return result + + @contextlib.contextmanager def adjust_int_max_str_digits(max_digits): """Temporarily change the integer string conversion length limit.""" diff --git a/Lib/test/test_int.py b/Lib/test/test_int.py index 0bf55facad9fedb..47fc50a0e203497 100644 --- a/Lib/test/test_int.py +++ b/Lib/test/test_int.py @@ -664,84 +664,78 @@ def test_denial_of_service_prevented_int_to_str(self): """Regression test: ensure we fail before performing O(N**2) work.""" maxdigits = sys.get_int_max_str_digits() assert maxdigits < 50_000, maxdigits # A test prerequisite. - get_time = time.process_time - if get_time() <= 0: # some platforms like WASM lack process_time() - get_time = time.monotonic huge_int = int(f'0x{"c"*65_000}', base=16) # 78268 decimal digits. digits = 78_268 - with support.adjust_int_max_str_digits(digits): - start = get_time() + with ( + support.adjust_int_max_str_digits(digits), + support.CPUStopwatch() as sw_convert): huge_decimal = str(huge_int) - seconds_to_convert = get_time() - start self.assertEqual(len(huge_decimal), digits) # Ensuring that we chose a slow enough conversion to measure. # It takes 0.1 seconds on a Zen based cloud VM in an opt build. # Some OSes have a low res 1/64s timer, skip if hard to measure. - if seconds_to_convert < 1/64: + if sw_convert.seconds < sw_convert.clock_info.resolution * 2: raise unittest.SkipTest('"slow" conversion took only ' - f'{seconds_to_convert} seconds.') + f'{sw_convert.seconds} seconds.') # We test with the limit almost at the size needed to check performance. # The performant limit check is slightly fuzzy, give it a some room. with support.adjust_int_max_str_digits(int(.995 * digits)): - with self.assertRaises(ValueError) as err: - start = get_time() + with ( + self.assertRaises(ValueError) as err, + support.CPUStopwatch() as sw_fail_huge): str(huge_int) - seconds_to_fail_huge = get_time() - start self.assertIn('conversion', str(err.exception)) - self.assertLessEqual(seconds_to_fail_huge, seconds_to_convert/2) + self.assertLessEqual(sw_fail_huge.seconds, sw_convert.seconds/2) # Now we test that a conversion that would take 30x as long also fails # in a similarly fast fashion. extra_huge_int = int(f'0x{"c"*500_000}', base=16) # 602060 digits. - with self.assertRaises(ValueError) as err: - start = get_time() + with ( + self.assertRaises(ValueError) as err, + support.CPUStopwatch() as sw_fail_extra_huge): # If not limited, 8 seconds said Zen based cloud VM. str(extra_huge_int) - seconds_to_fail_extra_huge = get_time() - start self.assertIn('conversion', str(err.exception)) - self.assertLess(seconds_to_fail_extra_huge, seconds_to_convert/2) + self.assertLess(sw_fail_extra_huge.seconds, sw_convert.seconds/2) def test_denial_of_service_prevented_str_to_int(self): """Regression test: ensure we fail before performing O(N**2) work.""" maxdigits = sys.get_int_max_str_digits() assert maxdigits < 100_000, maxdigits # A test prerequisite. - get_time = time.process_time - if get_time() <= 0: # some platforms like WASM lack process_time() - get_time = time.monotonic digits = 133700 huge = '8'*digits - with support.adjust_int_max_str_digits(digits): - start = get_time() + with ( + support.adjust_int_max_str_digits(digits), + support.CPUStopwatch() as sw_convert): int(huge) - seconds_to_convert = get_time() - start # Ensuring that we chose a slow enough conversion to measure. # It takes 0.1 seconds on a Zen based cloud VM in an opt build. # Some OSes have a low res 1/64s timer, skip if hard to measure. - if seconds_to_convert < 1/64: + if sw_convert.seconds < sw_convert.clock_info.resolution * 2: raise unittest.SkipTest('"slow" conversion took only ' - f'{seconds_to_convert} seconds.') + f'{sw_convert.seconds} seconds.') with support.adjust_int_max_str_digits(digits - 1): - with self.assertRaises(ValueError) as err: - start = get_time() + with ( + self.assertRaises(ValueError) as err, + support.CPUStopwatch() as sw_fail_huge): int(huge) - seconds_to_fail_huge = get_time() - start self.assertIn('conversion', str(err.exception)) - self.assertLessEqual(seconds_to_fail_huge, seconds_to_convert/2) + self.assertLessEqual(sw_fail_huge.seconds, sw_convert.seconds/2) # Now we test that a conversion that would take 30x as long also fails # in a similarly fast fashion. extra_huge = '7'*1_200_000 - with self.assertRaises(ValueError) as err: - start = get_time() + with ( + self.assertRaises(ValueError) as err, + support.CPUStopwatch() as sw_fail_extra_huge): # If not limited, 8 seconds in the Zen based cloud VM. int(extra_huge) - seconds_to_fail_extra_huge = get_time() - start self.assertIn('conversion', str(err.exception)) - self.assertLessEqual(seconds_to_fail_extra_huge, seconds_to_convert/2) + self.assertLessEqual(sw_fail_extra_huge.seconds, sw_convert.seconds/2) def test_power_of_two_bases_unlimited(self): """The limit does not apply to power of 2 bases.""" diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py index 993a7d6e264a1f4..b1ac22c28cf7c13 100644 --- a/Lib/test/test_re.py +++ b/Lib/test/test_re.py @@ -1,7 +1,7 @@ from test.support import (gc_collect, bigmemtest, _2G, cpython_only, captured_stdout, check_disallow_instantiation, is_emscripten, is_wasi, - warnings_helper, SHORT_TIMEOUT) + warnings_helper, SHORT_TIMEOUT, CPUStopwatch) import locale import re import string @@ -2284,17 +2284,16 @@ def test_bug_40736(self): def test_search_anchor_at_beginning(self): s = 'x'*10**7 - start = time.perf_counter() - for p in r'\Ay', r'^y': - self.assertIsNone(re.search(p, s)) - self.assertEqual(re.split(p, s), [s]) - self.assertEqual(re.findall(p, s), []) - self.assertEqual(list(re.finditer(p, s)), []) - self.assertEqual(re.sub(p, '', s), s) - t = time.perf_counter() - start + with CPUStopwatch() as stopwatch: + for p in r'\Ay', r'^y': + self.assertIsNone(re.search(p, s)) + self.assertEqual(re.split(p, s), [s]) + self.assertEqual(re.findall(p, s), []) + self.assertEqual(list(re.finditer(p, s)), []) + self.assertEqual(re.sub(p, '', s), s) # Without optimization it takes 1 second on my computer. # With optimization -- 0.0003 seconds. - self.assertLess(t, 0.1) + self.assertLess(stopwatch.seconds, 0.1) def test_possessive_quantifiers(self): """Test Possessive Quantifiers From a71e32ce8e183023fc1ee401c22ebe35e4832f09 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Wed, 28 Feb 2024 14:03:50 +0100 Subject: [PATCH 488/507] gh-78612: Mark up eval() using param list (#115212) Also mention that the 'expression' parameter can be a string. --- Doc/library/functions.rst | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index a4852b922b65b3c..e598ef423de4972 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -526,9 +526,20 @@ are always available. They are listed here in alphabetical order. .. function:: eval(expression, globals=None, locals=None) - The arguments are a string and optional globals and locals. If provided, - *globals* must be a dictionary. If provided, *locals* can be any mapping - object. + :param expression: + A Python expression. + :type expression: :class:`str` | :ref:`code object <code-objects>` + + :param globals: + The global namespace (default: ``None``). + :type globals: :class:`dict` | ``None`` + + :param locals: + The local namespace (default: ``None``). + :type locals: :term:`mapping` | ``None`` + + :returns: The result of the evaluated expression. + :raises: Syntax errors are reported as exceptions. The *expression* argument is parsed and evaluated as a Python expression (technically speaking, a condition list) using the *globals* and *locals* @@ -545,8 +556,7 @@ are always available. They are listed here in alphabetical order. :term:`nested scopes <nested scope>` (non-locals) in the enclosing environment. - The return value is the result of - the evaluated expression. Syntax errors are reported as exceptions. Example: + Example: >>> x = 1 >>> eval('x+1') From 449c6da2bdc5c6aa5e096aa550a4ba377b85db46 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Wed, 28 Feb 2024 14:35:41 +0100 Subject: [PATCH 489/507] gh-115765: Don't use deprecated AC_EGREP_* macros in configure.ac (#116016) Rewrite using AX_CHECK_DEFINE and AC_CHECK_TYPES. --- aclocal.m4 | 74 +++++++++++ configure | 360 +++++++++++++++++++++++++++++++++++--------------- configure.ac | 95 +++++-------- pyconfig.h.in | 5 +- 4 files changed, 363 insertions(+), 171 deletions(-) diff --git a/aclocal.m4 b/aclocal.m4 index 09ae5d1aa8a608e..832aec19f48f178 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -150,6 +150,80 @@ AS_VAR_IF(CACHEVAR,yes, AS_VAR_POPDEF([CACHEVAR])dnl ])dnl AX_CHECK_COMPILE_FLAGS +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_check_define.html +# =========================================================================== +# +# SYNOPSIS +# +# AC_CHECK_DEFINE([symbol], [ACTION-IF-FOUND], [ACTION-IF-NOT]) +# AX_CHECK_DEFINE([includes],[symbol], [ACTION-IF-FOUND], [ACTION-IF-NOT]) +# +# DESCRIPTION +# +# Complements AC_CHECK_FUNC but it does not check for a function but for a +# define to exist. Consider a usage like: +# +# AC_CHECK_DEFINE(__STRICT_ANSI__, CFLAGS="$CFLAGS -D_XOPEN_SOURCE=500") +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de> +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 11 + +AU_ALIAS([AC_CHECK_DEFINED], [AC_CHECK_DEFINE]) +AC_DEFUN([AC_CHECK_DEFINE],[ +AS_VAR_PUSHDEF([ac_var],[ac_cv_defined_$1])dnl +AC_CACHE_CHECK([for $1 defined], ac_var, +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[ + #ifdef $1 + int ok; + (void)ok; + #else + choke me + #endif +]])],[AS_VAR_SET(ac_var, yes)],[AS_VAR_SET(ac_var, no)])) +AS_IF([test AS_VAR_GET(ac_var) != "no"], [$2], [$3])dnl +AS_VAR_POPDEF([ac_var])dnl +]) + +AU_ALIAS([AX_CHECK_DEFINED], [AX_CHECK_DEFINE]) +AC_DEFUN([AX_CHECK_DEFINE],[ +AS_VAR_PUSHDEF([ac_var],[ac_cv_defined_$2_$1])dnl +AC_CACHE_CHECK([for $2 defined in $1], ac_var, +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <$1>]], [[ + #ifdef $2 + int ok; + (void)ok; + #else + choke me + #endif +]])],[AS_VAR_SET(ac_var, yes)],[AS_VAR_SET(ac_var, no)])) +AS_IF([test AS_VAR_GET(ac_var) != "no"], [$3], [$4])dnl +AS_VAR_POPDEF([ac_var])dnl +]) + +AC_DEFUN([AX_CHECK_FUNC], +[AS_VAR_PUSHDEF([ac_var], [ac_cv_func_$2])dnl +AC_CACHE_CHECK([for $2], ac_var, +dnl AC_LANG_FUNC_LINK_TRY +[AC_LINK_IFELSE([AC_LANG_PROGRAM([$1 + #undef $2 + char $2 ();],[ + char (*f) () = $2; + return f != $2; ])], + [AS_VAR_SET(ac_var, yes)], + [AS_VAR_SET(ac_var, no)])]) +AS_IF([test AS_VAR_GET(ac_var) = yes], [$3], [$4])dnl +AS_VAR_POPDEF([ac_var])dnl +])# AC_CHECK_FUNC + # =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_check_openssl.html # =========================================================================== diff --git a/configure b/configure index c204c9eb499559f..f431c5dd15ec4a0 100755 --- a/configure +++ b/configure @@ -11297,42 +11297,22 @@ then : fi -# checks for typedefs - -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for clock_t in time.h" >&5 -printf %s "checking for clock_t in time.h... " >&6; } -if test ${ac_cv_clock_t_time_h+y} -then : - printf %s "(cached) " >&6 -else $as_nop - - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <time.h> - -_ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "clock_t" >/dev/null 2>&1 +# Check for clock_t in time.h. +ac_fn_c_check_type "$LINENO" "clock_t" "ac_cv_type_clock_t" "#include <time.h> +" +if test "x$ac_cv_type_clock_t" = xyes then : - ac_cv_clock_t_time_h=yes -else $as_nop - ac_cv_clock_t_time_h=no -fi -rm -rf conftest* +printf "%s\n" "#define HAVE_CLOCK_T 1" >>confdefs.h -fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_clock_t_time_h" >&5 -printf "%s\n" "$ac_cv_clock_t_time_h" >&6; } -if test "x$ac_cv_clock_t_time_h" = xno -then : +else $as_nop printf "%s\n" "#define clock_t long" >>confdefs.h - fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for makedev" >&5 printf %s "checking for makedev... " >&6; } if test ${ac_cv_func_makedev+y} @@ -11534,6 +11514,7 @@ printf "%s\n" "#define size_t unsigned int" >>confdefs.h fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for uid_t in sys/types.h" >&5 printf %s "checking for uid_t in sys/types.h... " >&6; } if test ${ac_cv_type_uid_t+y} @@ -16184,24 +16165,47 @@ else # (e.g. gnu pth with pthread emulation) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for _POSIX_THREADS in unistd.h" >&5 printf %s "checking for _POSIX_THREADS in unistd.h... " >&6; } - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for _POSIX_THREADS defined in unistd.h" >&5 +printf %s "checking for _POSIX_THREADS defined in unistd.h... " >&6; } +if test ${ac_cv_defined__POSIX_THREADS_unistd_h+y} +then : + printf %s "(cached) " >&6 +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ #include <unistd.h> -#ifdef _POSIX_THREADS -yes -#endif +int +main (void) +{ + #ifdef _POSIX_THREADS + int ok; + (void)ok; + #else + choke me + #endif + + ; + return 0; +} _ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "yes" >/dev/null 2>&1 +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_defined__POSIX_THREADS_unistd_h=yes +else $as_nop + ac_cv_defined__POSIX_THREADS_unistd_h=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_defined__POSIX_THREADS_unistd_h" >&5 +printf "%s\n" "$ac_cv_defined__POSIX_THREADS_unistd_h" >&6; } +if test $ac_cv_defined__POSIX_THREADS_unistd_h != "no" then : unistd_defines_pthreads=yes else $as_nop unistd_defines_pthreads=no fi -rm -rf conftest* - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $unistd_defines_pthreads" >&5 printf "%s\n" "$unistd_defines_pthreads" >&6; } @@ -16708,59 +16712,131 @@ printf %s "checking ipv6 stack type... " >&6; } do case $i in inria) - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for IPV6_INRIA_VERSION defined in netinet/in.h" >&5 +printf %s "checking for IPV6_INRIA_VERSION defined in netinet/in.h... " >&6; } +if test ${ac_cv_defined_IPV6_INRIA_VERSION_netinet_in_h+y} +then : + printf %s "(cached) " >&6 +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ #include <netinet/in.h> -#ifdef IPV6_INRIA_VERSION -yes -#endif +int +main (void) +{ + + #ifdef IPV6_INRIA_VERSION + int ok; + (void)ok; + #else + choke me + #endif + + ; + return 0; +} _ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "yes" >/dev/null 2>&1 +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_defined_IPV6_INRIA_VERSION_netinet_in_h=yes +else $as_nop + ac_cv_defined_IPV6_INRIA_VERSION_netinet_in_h=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_defined_IPV6_INRIA_VERSION_netinet_in_h" >&5 +printf "%s\n" "$ac_cv_defined_IPV6_INRIA_VERSION_netinet_in_h" >&6; } +if test $ac_cv_defined_IPV6_INRIA_VERSION_netinet_in_h != "no" then : ipv6type=$i fi -rm -rf conftest* - ;; kame) - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for __KAME__ defined in netinet/in.h" >&5 +printf %s "checking for __KAME__ defined in netinet/in.h... " >&6; } +if test ${ac_cv_defined___KAME___netinet_in_h+y} +then : + printf %s "(cached) " >&6 +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ #include <netinet/in.h> -#ifdef __KAME__ -yes -#endif +int +main (void) +{ + + #ifdef __KAME__ + int ok; + (void)ok; + #else + choke me + #endif + + ; + return 0; +} _ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "yes" >/dev/null 2>&1 +if ac_fn_c_try_compile "$LINENO" then : - ipv6type=$i; - ipv6lib=inet6 - ipv6libdir=/usr/local/v6/lib - ipv6trylibc=yes + ac_cv_defined___KAME___netinet_in_h=yes +else $as_nop + ac_cv_defined___KAME___netinet_in_h=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_defined___KAME___netinet_in_h" >&5 +printf "%s\n" "$ac_cv_defined___KAME___netinet_in_h" >&6; } +if test $ac_cv_defined___KAME___netinet_in_h != "no" +then : + ipv6type=$i + ipv6lib=inet6 + ipv6libdir=/usr/local/v6/lib + ipv6trylibc=yes fi -rm -rf conftest* - ;; linux-glibc) - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for __GLIBC__ defined in features.h" >&5 +printf %s "checking for __GLIBC__ defined in features.h... " >&6; } +if test ${ac_cv_defined___GLIBC___features_h+y} +then : + printf %s "(cached) " >&6 +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ #include <features.h> -#if defined(__GLIBC__) && ((__GLIBC__ == 2 && __GLIBC_MINOR__ >= 1) || (__GLIBC__ > 2)) -yes -#endif +int +main (void) +{ + + #ifdef __GLIBC__ + int ok; + (void)ok; + #else + choke me + #endif + + ; + return 0; +} _ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "yes" >/dev/null 2>&1 +if ac_fn_c_try_compile "$LINENO" then : - ipv6type=$i; - ipv6trylibc=yes + ac_cv_defined___GLIBC___features_h=yes +else $as_nop + ac_cv_defined___GLIBC___features_h=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_defined___GLIBC___features_h" >&5 +printf "%s\n" "$ac_cv_defined___GLIBC___features_h" >&6; } +if test $ac_cv_defined___GLIBC___features_h != "no" +then : + ipv6type=$i + ipv6trylibc=yes fi -rm -rf conftest* - ;; linux-inet6) if test -d /usr/inet6; then @@ -16779,62 +16855,134 @@ rm -rf conftest* fi ;; toshiba) - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for _TOSHIBA_INET6 defined in sys/param.h" >&5 +printf %s "checking for _TOSHIBA_INET6 defined in sys/param.h... " >&6; } +if test ${ac_cv_defined__TOSHIBA_INET6_sys_param_h+y} +then : + printf %s "(cached) " >&6 +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ #include <sys/param.h> -#ifdef _TOSHIBA_INET6 -yes -#endif +int +main (void) +{ + + #ifdef _TOSHIBA_INET6 + int ok; + (void)ok; + #else + choke me + #endif + + ; + return 0; +} _ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "yes" >/dev/null 2>&1 +if ac_fn_c_try_compile "$LINENO" then : - ipv6type=$i; - ipv6lib=inet6; - ipv6libdir=/usr/local/v6/lib + ac_cv_defined__TOSHIBA_INET6_sys_param_h=yes +else $as_nop + ac_cv_defined__TOSHIBA_INET6_sys_param_h=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_defined__TOSHIBA_INET6_sys_param_h" >&5 +printf "%s\n" "$ac_cv_defined__TOSHIBA_INET6_sys_param_h" >&6; } +if test $ac_cv_defined__TOSHIBA_INET6_sys_param_h != "no" +then : + ipv6type=$i + ipv6lib=inet6 + ipv6libdir=/usr/local/v6/lib fi -rm -rf conftest* - ;; v6d) - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for __V6D__ defined in /usr/local/v6/include/sys/v6config.h" >&5 +printf %s "checking for __V6D__ defined in /usr/local/v6/include/sys/v6config.h... " >&6; } +if test ${ac_cv_defined___V6D____usr_local_v6_include_sys_v6config_h+y} +then : + printf %s "(cached) " >&6 +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ #include </usr/local/v6/include/sys/v6config.h> -#ifdef __V6D__ -yes -#endif +int +main (void) +{ + + #ifdef __V6D__ + int ok; + (void)ok; + #else + choke me + #endif + + ; + return 0; +} _ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "yes" >/dev/null 2>&1 +if ac_fn_c_try_compile "$LINENO" then : - ipv6type=$i; - ipv6lib=v6; - ipv6libdir=/usr/local/v6/lib; - BASECFLAGS="-I/usr/local/v6/include $BASECFLAGS" + ac_cv_defined___V6D____usr_local_v6_include_sys_v6config_h=yes +else $as_nop + ac_cv_defined___V6D____usr_local_v6_include_sys_v6config_h=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_defined___V6D____usr_local_v6_include_sys_v6config_h" >&5 +printf "%s\n" "$ac_cv_defined___V6D____usr_local_v6_include_sys_v6config_h" >&6; } +if test $ac_cv_defined___V6D____usr_local_v6_include_sys_v6config_h != "no" +then : + ipv6type=$i + ipv6lib=v6 + ipv6libdir=/usr/local/v6/lib + BASECFLAGS="-I/usr/local/v6/include $BASECFLAGS" fi -rm -rf conftest* - ;; zeta) - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for _ZETA_MINAMI_INET6 defined in sys/param.h" >&5 +printf %s "checking for _ZETA_MINAMI_INET6 defined in sys/param.h... " >&6; } +if test ${ac_cv_defined__ZETA_MINAMI_INET6_sys_param_h+y} +then : + printf %s "(cached) " >&6 +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ #include <sys/param.h> -#ifdef _ZETA_MINAMI_INET6 -yes -#endif +int +main (void) +{ + + #ifdef _ZETA_MINAMI_INET6 + int ok; + (void)ok; + #else + choke me + #endif + + ; + return 0; +} _ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "yes" >/dev/null 2>&1 +if ac_fn_c_try_compile "$LINENO" then : - ipv6type=$i; - ipv6lib=inet6; - ipv6libdir=/usr/local/v6/lib + ac_cv_defined__ZETA_MINAMI_INET6_sys_param_h=yes +else $as_nop + ac_cv_defined__ZETA_MINAMI_INET6_sys_param_h=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_defined__ZETA_MINAMI_INET6_sys_param_h" >&5 +printf "%s\n" "$ac_cv_defined__ZETA_MINAMI_INET6_sys_param_h" >&6; } +if test $ac_cv_defined__ZETA_MINAMI_INET6_sys_param_h != "no" +then : + ipv6type=$i + ipv6lib=inet6 + ipv6libdir=/usr/local/v6/lib fi -rm -rf conftest* - ;; esac if test "$ipv6type" != "unknown"; then diff --git a/configure.ac b/configure.ac index 702e588357789cd..a5abb06a8294139 100644 --- a/configure.ac +++ b/configure.ac @@ -2895,15 +2895,11 @@ AC_CHECK_HEADERS( #endif ]) -# checks for typedefs -AC_CACHE_CHECK([for clock_t in time.h], [ac_cv_clock_t_time_h], [ - AC_EGREP_HEADER([clock_t], [time.h], [ac_cv_clock_t_time_h=yes], [ac_cv_clock_t_time_h=no]) -]) -dnl checks for "no" -AS_VAR_IF([ac_cv_clock_t_time_h], [no], [ - AC_DEFINE([clock_t], [long], - [Define to 'long' if <time.h> doesn't define.]) -]) +# Check for clock_t in time.h. +AC_CHECK_TYPES([clock_t], [], + [AC_DEFINE([clock_t], [long], + [Define to 'long' if <time.h> does not define clock_t.])], + [@%:@include <time.h>]) AC_CACHE_CHECK([for makedev], [ac_cv_func_makedev], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ @@ -4331,13 +4327,9 @@ else # define _POSIX_THREADS in unistd.h. Some apparently don't # (e.g. gnu pth with pthread emulation) AC_MSG_CHECKING([for _POSIX_THREADS in unistd.h]) - AC_EGREP_CPP([yes], - [ -#include <unistd.h> -#ifdef _POSIX_THREADS -yes -#endif - ], unistd_defines_pthreads=yes, unistd_defines_pthreads=no) + AX_CHECK_DEFINE([unistd.h], [_POSIX_THREADS], + [unistd_defines_pthreads=yes], + [unistd_defines_pthreads=no]) AC_MSG_RESULT([$unistd_defines_pthreads]) AC_DEFINE([_REENTRANT]) @@ -4517,34 +4509,21 @@ if test "$ipv6" = yes -a "$cross_compiling" = no; then case $i in inria) dnl http://www.kame.net/ - AC_EGREP_CPP([yes], [ -#include <netinet/in.h> -#ifdef IPV6_INRIA_VERSION -yes -@%:@endif], - [ipv6type=$i]) + AX_CHECK_DEFINE([netinet/in.h], [IPV6_INRIA_VERSION], [ipv6type=$i]) ;; kame) dnl http://www.kame.net/ - AC_EGREP_CPP([yes], [ -#include <netinet/in.h> -#ifdef __KAME__ -yes -@%:@endif], - [ipv6type=$i; - ipv6lib=inet6 - ipv6libdir=/usr/local/v6/lib - ipv6trylibc=yes]) + AX_CHECK_DEFINE([netinet/in.h], [__KAME__], + [ipv6type=$i + ipv6lib=inet6 + ipv6libdir=/usr/local/v6/lib + ipv6trylibc=yes]) ;; linux-glibc) - dnl http://www.v6.linux.or.jp/ - AC_EGREP_CPP([yes], [ -#include <features.h> -#if defined(__GLIBC__) && ((__GLIBC__ == 2 && __GLIBC_MINOR__ >= 1) || (__GLIBC__ > 2)) -yes -@%:@endif], - [ipv6type=$i; - ipv6trylibc=yes]) + dnl Advanced IPv6 support was added to glibc 2.1 in 1999. + AX_CHECK_DEFINE([features.h], [__GLIBC__], + [ipv6type=$i + ipv6trylibc=yes]) ;; linux-inet6) dnl http://www.v6.linux.or.jp/ @@ -4564,35 +4543,23 @@ yes fi ;; toshiba) - AC_EGREP_CPP([yes], [ -#include <sys/param.h> -#ifdef _TOSHIBA_INET6 -yes -@%:@endif], - [ipv6type=$i; - ipv6lib=inet6; - ipv6libdir=/usr/local/v6/lib]) + AX_CHECK_DEFINE([sys/param.h], [_TOSHIBA_INET6], + [ipv6type=$i + ipv6lib=inet6 + ipv6libdir=/usr/local/v6/lib]) ;; v6d) - AC_EGREP_CPP([yes], [ -#include </usr/local/v6/include/sys/v6config.h> -#ifdef __V6D__ -yes -@%:@endif], - [ipv6type=$i; - ipv6lib=v6; - ipv6libdir=/usr/local/v6/lib; - BASECFLAGS="-I/usr/local/v6/include $BASECFLAGS"]) + AX_CHECK_DEFINE([/usr/local/v6/include/sys/v6config.h], [__V6D__], + [ipv6type=$i + ipv6lib=v6 + ipv6libdir=/usr/local/v6/lib + BASECFLAGS="-I/usr/local/v6/include $BASECFLAGS"]) ;; zeta) - AC_EGREP_CPP([yes], [ -#include <sys/param.h> -#ifdef _ZETA_MINAMI_INET6 -yes -@%:@endif], - [ipv6type=$i; - ipv6lib=inet6; - ipv6libdir=/usr/local/v6/lib]) + AX_CHECK_DEFINE([sys/param.h], [_ZETA_MINAMI_INET6], + [ipv6type=$i + ipv6lib=inet6 + ipv6libdir=/usr/local/v6/lib]) ;; esac if test "$ipv6type" != "unknown"; then diff --git a/pyconfig.h.in b/pyconfig.h.in index b93cac9d23c796a..e28baef51d57373 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -157,6 +157,9 @@ /* Define to 1 if you have the `clock_settime' function. */ #undef HAVE_CLOCK_SETTIME +/* Define to 1 if the system has the type `clock_t'. */ +#undef HAVE_CLOCK_T + /* Define to 1 if you have the `closefrom' function. */ #undef HAVE_CLOSEFROM @@ -1941,7 +1944,7 @@ /* Define on FreeBSD to activate all library features */ #undef __BSD_VISIBLE -/* Define to 'long' if <time.h> doesn't define. */ +/* Define to 'long' if <time.h> does not define clock_t. */ #undef clock_t /* Define to empty if `const' does not conform to ANSI C. */ From 647053fed182066d3b8c934fb0bf52ee48ff3911 Mon Sep 17 00:00:00 2001 From: Jan Max Meyer <jmm@phorward.de> Date: Wed, 28 Feb 2024 14:54:12 +0100 Subject: [PATCH 490/507] doc: Use super() in subclassed JSONEncoder examples (GH-115565) Replace calls to `json.JSONEncoder.default(self, obj)` by `super().default(obj)` within the examples of the documentation. --- Doc/library/json.rst | 4 ++-- Lib/json/encoder.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/json.rst b/Doc/library/json.rst index 0ce4b697145cb3e..c82ff9dc325b4c8 100644 --- a/Doc/library/json.rst +++ b/Doc/library/json.rst @@ -106,7 +106,7 @@ Extending :class:`JSONEncoder`:: ... if isinstance(obj, complex): ... return [obj.real, obj.imag] ... # Let the base class default method raise the TypeError - ... return json.JSONEncoder.default(self, obj) + ... return super().default(obj) ... >>> json.dumps(2 + 1j, cls=ComplexEncoder) '[2.0, 1.0]' @@ -504,7 +504,7 @@ Encoders and Decoders else: return list(iterable) # Let the base class default method raise the TypeError - return json.JSONEncoder.default(self, o) + return super().default(o) .. method:: encode(o) diff --git a/Lib/json/encoder.py b/Lib/json/encoder.py index 45f547741885a86..597849eca0524a3 100644 --- a/Lib/json/encoder.py +++ b/Lib/json/encoder.py @@ -174,7 +174,7 @@ def default(self, o): else: return list(iterable) # Let the base class default method raise the TypeError - return JSONEncoder.default(self, o) + return super().default(o) """ raise TypeError(f'Object of type {o.__class__.__name__} ' From 9578288a3e5a7f42d1f3bec139c0c85b87775c90 Mon Sep 17 00:00:00 2001 From: Steve Dower <steve.dower@python.org> Date: Wed, 28 Feb 2024 13:58:25 +0000 Subject: [PATCH 491/507] gh-116012: Preserve GetLastError() across calls to TlsGetValue on Windows (GH-116014) --- .../2024-02-27-23-21-55.gh-issue-116012.B9_IwM.rst | 1 + Python/pystate.c | 9 --------- Python/thread_nt.h | 7 ++++++- 3 files changed, 7 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2024-02-27-23-21-55.gh-issue-116012.B9_IwM.rst diff --git a/Misc/NEWS.d/next/Windows/2024-02-27-23-21-55.gh-issue-116012.B9_IwM.rst b/Misc/NEWS.d/next/Windows/2024-02-27-23-21-55.gh-issue-116012.B9_IwM.rst new file mode 100644 index 000000000000000..a55e5b1c7b566d3 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-02-27-23-21-55.gh-issue-116012.B9_IwM.rst @@ -0,0 +1 @@ +Ensure the value of ``GetLastError()`` is preserved across GIL operations. diff --git a/Python/pystate.c b/Python/pystate.c index a80c1b7fb9c8665..a370fff857af852 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2528,16 +2528,7 @@ PyGILState_Check(void) return 0; } -#ifdef MS_WINDOWS - int err = GetLastError(); -#endif - PyThreadState *tcur = gilstate_tss_get(runtime); - -#ifdef MS_WINDOWS - SetLastError(err); -#endif - return (tstate == tcur); } diff --git a/Python/thread_nt.h b/Python/thread_nt.h index 7922b2d7e848454..9dca833ff203ca9 100644 --- a/Python/thread_nt.h +++ b/Python/thread_nt.h @@ -513,5 +513,10 @@ void * PyThread_tss_get(Py_tss_t *key) { assert(key != NULL); - return TlsGetValue(key->_key); + int err = GetLastError(); + void *r = TlsGetValue(key->_key); + if (r || !GetLastError()) { + SetLastError(err); + } + return r; } From 0a61e237009bf6b833e13ac635299ee063377699 Mon Sep 17 00:00:00 2001 From: Tian Gao <gaogaotiantian@hotmail.com> Date: Wed, 28 Feb 2024 07:21:42 -0800 Subject: [PATCH 492/507] gh-107674: Improve performance of `sys.settrace` (GH-114986) --- ...-02-04-07-45-29.gh-issue-107674.q8mCmi.rst | 1 + Python/bytecodes.c | 33 ++++++++++--------- Python/ceval.c | 28 +++++++++------- Python/ceval_macros.h | 16 +++++---- Python/executor_cases.c.h | 2 +- Python/generated_cases.c.h | 33 ++++++++++--------- Python/instrumentation.c | 4 +-- 7 files changed, 64 insertions(+), 53 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-04-07-45-29.gh-issue-107674.q8mCmi.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-04-07-45-29.gh-issue-107674.q8mCmi.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-04-07-45-29.gh-issue-107674.q8mCmi.rst new file mode 100644 index 000000000000000..f9b96788bfad940 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-04-07-45-29.gh-issue-107674.q8mCmi.rst @@ -0,0 +1 @@ +Improved the performance of :func:`sys.settrace` significantly diff --git a/Python/bytecodes.c b/Python/bytecodes.c index e9e9425f826a2d7..565379afc4b5a70 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -143,22 +143,23 @@ dummy_func( tier1 inst(RESUME, (--)) { assert(frame == tstate->current_frame); - uintptr_t global_version = - _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & - ~_PY_EVAL_EVENTS_MASK; - uintptr_t code_version = _PyFrame_GetCode(frame)->_co_instrumentation_version; - assert((code_version & 255) == 0); - if (code_version != global_version) { - int err = _Py_Instrument(_PyFrame_GetCode(frame), tstate->interp); - ERROR_IF(err, error); - next_instr = this_instr; - } - else { - if ((oparg & RESUME_OPARG_LOCATION_MASK) < RESUME_AFTER_YIELD_FROM) { - CHECK_EVAL_BREAKER(); + if (tstate->tracing == 0) { + uintptr_t global_version = + _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & + ~_PY_EVAL_EVENTS_MASK; + uintptr_t code_version = _PyFrame_GetCode(frame)->_co_instrumentation_version; + assert((code_version & 255) == 0); + if (code_version != global_version) { + int err = _Py_Instrument(_PyFrame_GetCode(frame), tstate->interp); + ERROR_IF(err, error); + next_instr = this_instr; + DISPATCH(); } - this_instr->op.code = RESUME_CHECK; } + if ((oparg & RESUME_OPARG_LOCATION_MASK) < RESUME_AFTER_YIELD_FROM) { + CHECK_EVAL_BREAKER(); + } + this_instr->op.code = RESUME_CHECK; } inst(RESUME_CHECK, (--)) { @@ -169,13 +170,13 @@ dummy_func( uintptr_t eval_breaker = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker); uintptr_t version = _PyFrame_GetCode(frame)->_co_instrumentation_version; assert((version & _PY_EVAL_EVENTS_MASK) == 0); - DEOPT_IF(eval_breaker != version); + DEOPT_IF(eval_breaker != version && tstate->tracing == 0); } inst(INSTRUMENTED_RESUME, (--)) { uintptr_t global_version = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & ~_PY_EVAL_EVENTS_MASK; uintptr_t code_version = _PyFrame_GetCode(frame)->_co_instrumentation_version; - if (code_version != global_version) { + if (code_version != global_version && tstate->tracing == 0) { if (_Py_Instrument(_PyFrame_GetCode(frame), tstate->interp)) { GOTO_ERROR(error); } diff --git a/Python/ceval.c b/Python/ceval.c index 06c136aeb252c9a..41e9310938d826e 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -800,17 +800,23 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int { _Py_CODEUNIT *prev = frame->instr_ptr; _Py_CODEUNIT *here = frame->instr_ptr = next_instr; - _PyFrame_SetStackPointer(frame, stack_pointer); - int original_opcode = _Py_call_instrumentation_line( - tstate, frame, here, prev); - stack_pointer = _PyFrame_GetStackPointer(frame); - if (original_opcode < 0) { - next_instr = here+1; - goto error; - } - next_instr = frame->instr_ptr; - if (next_instr != here) { - DISPATCH(); + int original_opcode = 0; + if (tstate->tracing) { + PyCodeObject *code = _PyFrame_GetCode(frame); + original_opcode = code->_co_monitoring->lines[(int)(here - _PyCode_CODE(code))].original_opcode; + } else { + _PyFrame_SetStackPointer(frame, stack_pointer); + original_opcode = _Py_call_instrumentation_line( + tstate, frame, here, prev); + stack_pointer = _PyFrame_GetStackPointer(frame); + if (original_opcode < 0) { + next_instr = here+1; + goto error; + } + next_instr = frame->instr_ptr; + if (next_instr != here) { + DISPATCH(); + } } if (_PyOpcode_Caches[original_opcode]) { _PyBinaryOpCache *cache = (_PyBinaryOpCache *)(next_instr+1); diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h index 085199416c15232..6ad41950ea3b7e9 100644 --- a/Python/ceval_macros.h +++ b/Python/ceval_macros.h @@ -339,12 +339,16 @@ do { \ // for an exception handler, displaying the traceback, and so on #define INSTRUMENTED_JUMP(src, dest, event) \ do { \ - _PyFrame_SetStackPointer(frame, stack_pointer); \ - next_instr = _Py_call_instrumentation_jump(tstate, event, frame, src, dest); \ - stack_pointer = _PyFrame_GetStackPointer(frame); \ - if (next_instr == NULL) { \ - next_instr = (dest)+1; \ - goto error; \ + if (tstate->tracing) {\ + next_instr = dest; \ + } else { \ + _PyFrame_SetStackPointer(frame, stack_pointer); \ + next_instr = _Py_call_instrumentation_jump(tstate, event, frame, src, dest); \ + stack_pointer = _PyFrame_GetStackPointer(frame); \ + if (next_instr == NULL) { \ + next_instr = (dest)+1; \ + goto error; \ + } \ } \ } while (0); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 0b1d75985e11952..20fab8f4c61eb5c 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -20,7 +20,7 @@ uintptr_t eval_breaker = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker); uintptr_t version = _PyFrame_GetCode(frame)->_co_instrumentation_version; assert((version & _PY_EVAL_EVENTS_MASK) == 0); - if (eval_breaker != version) goto deoptimize; + if (eval_breaker != version && tstate->tracing == 0) goto deoptimize; break; } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 8b90e448a7b8fa2..bb26dac0e2be203 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -3125,7 +3125,7 @@ INSTRUCTION_STATS(INSTRUMENTED_RESUME); uintptr_t global_version = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & ~_PY_EVAL_EVENTS_MASK; uintptr_t code_version = _PyFrame_GetCode(frame)->_co_instrumentation_version; - if (code_version != global_version) { + if (code_version != global_version && tstate->tracing == 0) { if (_Py_Instrument(_PyFrame_GetCode(frame), tstate->interp)) { GOTO_ERROR(error); } @@ -4780,22 +4780,23 @@ PREDICTED(RESUME); _Py_CODEUNIT *this_instr = next_instr - 1; assert(frame == tstate->current_frame); - uintptr_t global_version = - _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & - ~_PY_EVAL_EVENTS_MASK; - uintptr_t code_version = _PyFrame_GetCode(frame)->_co_instrumentation_version; - assert((code_version & 255) == 0); - if (code_version != global_version) { - int err = _Py_Instrument(_PyFrame_GetCode(frame), tstate->interp); - if (err) goto error; - next_instr = this_instr; - } - else { - if ((oparg & RESUME_OPARG_LOCATION_MASK) < RESUME_AFTER_YIELD_FROM) { - CHECK_EVAL_BREAKER(); + if (tstate->tracing == 0) { + uintptr_t global_version = + _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & + ~_PY_EVAL_EVENTS_MASK; + uintptr_t code_version = _PyFrame_GetCode(frame)->_co_instrumentation_version; + assert((code_version & 255) == 0); + if (code_version != global_version) { + int err = _Py_Instrument(_PyFrame_GetCode(frame), tstate->interp); + if (err) goto error; + next_instr = this_instr; + DISPATCH(); } - this_instr->op.code = RESUME_CHECK; } + if ((oparg & RESUME_OPARG_LOCATION_MASK) < RESUME_AFTER_YIELD_FROM) { + CHECK_EVAL_BREAKER(); + } + this_instr->op.code = RESUME_CHECK; DISPATCH(); } @@ -4811,7 +4812,7 @@ uintptr_t eval_breaker = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker); uintptr_t version = _PyFrame_GetCode(frame)->_co_instrumentation_version; assert((version & _PY_EVAL_EVENTS_MASK) == 0); - DEOPT_IF(eval_breaker != version, RESUME); + DEOPT_IF(eval_breaker != version && tstate->tracing == 0, RESUME); DISPATCH(); } diff --git a/Python/instrumentation.c b/Python/instrumentation.c index 6f1bc2e0a107df5..4b7d8b5a5875044 100644 --- a/Python/instrumentation.c +++ b/Python/instrumentation.c @@ -1156,15 +1156,13 @@ int _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame, _Py_CODEUNIT *instr, _Py_CODEUNIT *prev) { PyCodeObject *code = _PyFrame_GetCode(frame); + assert(tstate->tracing == 0); assert(is_version_up_to_date(code, tstate->interp)); assert(instrumentation_cross_checks(tstate->interp, code)); int i = (int)(instr - _PyCode_CODE(code)); _PyCoMonitoringData *monitoring = code->_co_monitoring; _PyCoLineInstrumentationData *line_data = &monitoring->lines[i]; - if (tstate->tracing) { - goto done; - } PyInterpreterState *interp = tstate->interp; int8_t line_delta = line_data->line_delta; int line = compute_line(code, i, line_delta); From f58f8cef7445ea04a69ba3e2848fffdb6b72df91 Mon Sep 17 00:00:00 2001 From: Donghee Na <donghee.na@python.org> Date: Thu, 29 Feb 2024 02:51:59 +0900 Subject: [PATCH 493/507] gh-112075: Remove compiler warning from apple clang (gh-115855) --- Objects/dictobject.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 5ae4c3dbea23800..58fe973bc7a0361 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -5029,7 +5029,7 @@ dictiter_iternextkey(PyObject *self) PyObject *value; #ifdef Py_GIL_DISABLED - if (!dictiter_iternext_threadsafe(d, self, &value, NULL) == 0) { + if (dictiter_iternext_threadsafe(d, self, &value, NULL) < 0) { value = NULL; } #else @@ -5152,7 +5152,7 @@ dictiter_iternextvalue(PyObject *self) PyObject *value; #ifdef Py_GIL_DISABLED - if (!dictiter_iternext_threadsafe(d, self, NULL, &value) == 0) { + if (dictiter_iternext_threadsafe(d, self, NULL, &value) < 0) { value = NULL; } #else From e2a3e4b7488aff6fdc704a0f258bc315e96c1d6e Mon Sep 17 00:00:00 2001 From: Guido van Rossum <guido@python.org> Date: Wed, 28 Feb 2024 09:55:56 -0800 Subject: [PATCH 494/507] gh-115816: Improve internal symbols API in optimizer (#116028) - Any `sym_set_...` call that attempts to set conflicting information cause the symbol to become `bottom` (contradiction). - All `sym_is...` and similar calls return false or NULL for `bottom`. - Everything's tested. - The tests still pass with `PYTHONUOPSOPTIMIZE=1`. --- Include/internal/pycore_optimizer.h | 12 +- Python/optimizer_analysis.c | 2 + Python/optimizer_bytecodes.c | 2 + Python/optimizer_symbols.c | 242 +++++++++++++++++++++------- 4 files changed, 191 insertions(+), 67 deletions(-) diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 425bd693fac53da..265eae4e290c38f 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -27,12 +27,12 @@ extern PyTypeObject _PyUOpExecutor_Type; extern PyTypeObject _PyUOpOptimizer_Type; /* Symbols */ +/* See explanation in optimizer_symbols.c */ struct _Py_UopsSymbol { - int flags; - PyTypeObject *typ; - // constant propagated value (might be NULL) - PyObject *const_val; + int flags; // 0 bits: Top; 2 or more bits: Bottom + PyTypeObject *typ; // Borrowed reference + PyObject *const_val; // Owned reference (!) }; // Holds locals, stack, locals, stack ... co_consts (in that order) @@ -92,7 +92,9 @@ extern _Py_UopsSymbol *_Py_uop_sym_new_const(_Py_UOpsContext *ctx, PyObject *con extern _Py_UopsSymbol *_Py_uop_sym_new_null(_Py_UOpsContext *ctx); extern bool _Py_uop_sym_matches_type(_Py_UopsSymbol *sym, PyTypeObject *typ); extern void _Py_uop_sym_set_null(_Py_UopsSymbol *sym); -extern void _Py_uop_sym_set_type(_Py_UopsSymbol *sym, PyTypeObject *tp); +extern void _Py_uop_sym_set_non_null(_Py_UopsSymbol *sym); +extern void _Py_uop_sym_set_type(_Py_UopsSymbol *sym, PyTypeObject *typ); +extern void _Py_uop_sym_set_const(_Py_UopsSymbol *sym, PyObject *const_val); extern int _Py_uop_abstractcontext_init(_Py_UOpsContext *ctx); extern void _Py_uop_abstractcontext_fini(_Py_UOpsContext *ctx); diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index b29a00c941e996a..8e408ffbb1c2b5f 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -294,7 +294,9 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, #define sym_new_null _Py_uop_sym_new_null #define sym_matches_type _Py_uop_sym_matches_type #define sym_set_null _Py_uop_sym_set_null +#define sym_set_non_null _Py_uop_sym_set_non_null #define sym_set_type _Py_uop_sym_set_type +#define sym_set_const _Py_uop_sym_set_const #define frame_new _Py_uop_frame_new #define frame_pop _Py_uop_frame_pop diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 68737389c66b679..b65e90bf980e5a1 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -22,7 +22,9 @@ typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame; #define sym_new_null _Py_uop_sym_new_null #define sym_matches_type _Py_uop_sym_matches_type #define sym_set_null _Py_uop_sym_set_null +#define sym_set_non_null _Py_uop_sym_set_non_null #define sym_set_type _Py_uop_sym_set_type +#define sym_set_const _Py_uop_sym_set_const #define frame_new _Py_uop_frame_new #define frame_pop _Py_uop_frame_pop diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 794d73733f85a72..158ee67d19f50e2 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -10,11 +10,26 @@ #include <stdint.h> #include <stddef.h> +/* Symbols + ======= + + See the diagram at + https://github.com/faster-cpython/ideas/blob/main/3.13/redundancy_eliminator.md + + We represent the nodes in the diagram as follows + (the flag bits are only defined in optimizer_symbols.c): + - Top: no flag bits, typ and const_val are NULL. + - NULL: IS_NULL flag set, type and const_val NULL. + - Not NULL: NOT_NULL flag set, type and const_val NULL. + - None/not None: not used. (None could be represented as any other constant.) + - Known type: NOT_NULL flag set and typ set; const_val is NULL. + - Known constant: NOT_NULL flag set, type set, const_val set. + - Bottom: IS_NULL and NOT_NULL flags set, type and const_val NULL. + */ + // Flags for below. -#define KNOWN 1 << 0 -#define TRUE_CONST 1 << 1 -#define IS_NULL 1 << 2 -#define NOT_NULL 1 << 3 +#define IS_NULL 1 << 0 +#define NOT_NULL 1 << 1 #ifdef Py_DEBUG static inline int get_lltrace(void) { @@ -31,9 +46,8 @@ static inline int get_lltrace(void) { #define DPRINTF(level, ...) #endif -// Takes a borrowed reference to const_val, turns that into a strong reference. static _Py_UopsSymbol * -sym_new(_Py_UOpsContext *ctx, PyObject *const_val) +sym_new(_Py_UOpsContext *ctx) { _Py_UopsSymbol *self = &ctx->t_arena.arena[ctx->t_arena.ty_curr_number]; if (ctx->t_arena.ty_curr_number >= ctx->t_arena.ty_max_number) { @@ -42,13 +56,9 @@ sym_new(_Py_UOpsContext *ctx, PyObject *const_val) return NULL; } ctx->t_arena.ty_curr_number++; - self->const_val = NULL; - self->typ = NULL; self->flags = 0; - - if (const_val != NULL) { - self->const_val = Py_NewRef(const_val); - } + self->typ = NULL; + self->const_val = NULL; return self; } @@ -59,59 +69,111 @@ sym_set_flag(_Py_UopsSymbol *sym, int flag) sym->flags |= flag; } +static inline void +sym_set_bottom(_Py_UopsSymbol *sym) +{ + sym_set_flag(sym, IS_NULL | NOT_NULL); + sym->typ = NULL; + Py_CLEAR(sym->const_val); +} + static inline bool -sym_has_flag(_Py_UopsSymbol *sym, int flag) +_Py_uop_sym_is_bottom(_Py_UopsSymbol *sym) { - return (sym->flags & flag) != 0; + if ((sym->flags & IS_NULL) && (sym->flags & NOT_NULL)) { + assert(sym->flags == (IS_NULL | NOT_NULL)); + assert(sym->typ == NULL); + assert(sym->const_val == NULL); + return true; + } + return false; } bool _Py_uop_sym_is_not_null(_Py_UopsSymbol *sym) { - return (sym->flags & (IS_NULL | NOT_NULL)) == NOT_NULL; + return sym->flags == NOT_NULL; } bool _Py_uop_sym_is_null(_Py_UopsSymbol *sym) { - return (sym->flags & (IS_NULL | NOT_NULL)) == IS_NULL; + return sym->flags == IS_NULL; } bool _Py_uop_sym_is_const(_Py_UopsSymbol *sym) { - return (sym->flags & TRUE_CONST) != 0; + return sym->const_val != NULL; } PyObject * _Py_uop_sym_get_const(_Py_UopsSymbol *sym) { - assert(_Py_uop_sym_is_const(sym)); - assert(sym->const_val); return sym->const_val; } void -_Py_uop_sym_set_type(_Py_UopsSymbol *sym, PyTypeObject *tp) +_Py_uop_sym_set_type(_Py_UopsSymbol *sym, PyTypeObject *typ) { - assert(PyType_Check(tp)); - sym->typ = tp; - sym_set_flag(sym, KNOWN); + assert(typ != NULL && PyType_Check(typ)); + if (sym->flags & IS_NULL) { + sym_set_bottom(sym); + return; + } + if (sym->typ != NULL) { + if (sym->typ != typ) { + sym_set_bottom(sym); + } + return; + } + sym_set_flag(sym, NOT_NULL); + sym->typ = typ; +} + +void +_Py_uop_sym_set_const(_Py_UopsSymbol *sym, PyObject *const_val) +{ + assert(const_val != NULL); + if (sym->flags & IS_NULL) { + sym_set_bottom(sym); + return; + } + PyTypeObject *typ = Py_TYPE(const_val); + if (sym->typ != NULL && sym->typ != typ) { + sym_set_bottom(sym); + return; + } + if (sym->const_val != NULL) { + if (sym->const_val != const_val) { + // TODO: What if they're equal? + sym_set_bottom(sym); + } + return; + } sym_set_flag(sym, NOT_NULL); + sym->typ = typ; + sym->const_val = Py_NewRef(const_val); } + void _Py_uop_sym_set_null(_Py_UopsSymbol *sym) { sym_set_flag(sym, IS_NULL); - sym_set_flag(sym, KNOWN); +} + +void +_Py_uop_sym_set_non_null(_Py_UopsSymbol *sym) +{ + sym_set_flag(sym, NOT_NULL); } _Py_UopsSymbol * _Py_uop_sym_new_unknown(_Py_UOpsContext *ctx) { - return sym_new(ctx, NULL); + return sym_new(ctx); } _Py_UopsSymbol * @@ -121,16 +183,14 @@ _Py_uop_sym_new_not_null(_Py_UOpsContext *ctx) if (res == NULL) { return NULL; } - sym_set_flag(res, KNOWN); sym_set_flag(res, NOT_NULL); return res; } _Py_UopsSymbol * -_Py_uop_sym_new_type(_Py_UOpsContext *ctx, - PyTypeObject *typ) +_Py_uop_sym_new_type(_Py_UOpsContext *ctx, PyTypeObject *typ) { - _Py_UopsSymbol *res = sym_new(ctx,NULL); + _Py_UopsSymbol *res = sym_new(ctx); if (res == NULL) { return NULL; } @@ -138,20 +198,17 @@ _Py_uop_sym_new_type(_Py_UOpsContext *ctx, return res; } -// Takes a borrowed reference to const_val. +// Adds a new reference to const_val, owned by the symbol. _Py_UopsSymbol * _Py_uop_sym_new_const(_Py_UOpsContext *ctx, PyObject *const_val) { assert(const_val != NULL); - _Py_UopsSymbol *temp = sym_new(ctx, const_val); - if (temp == NULL) { + _Py_UopsSymbol *res = sym_new(ctx); + if (res == NULL) { return NULL; } - _Py_uop_sym_set_type(temp, Py_TYPE(const_val)); - sym_set_flag(temp, TRUE_CONST); - sym_set_flag(temp, KNOWN); - sym_set_flag(temp, NOT_NULL); - return temp; + _Py_uop_sym_set_const(res, const_val); + return res; } _Py_UopsSymbol * @@ -168,8 +225,8 @@ _Py_uop_sym_new_null(_Py_UOpsContext *ctx) bool _Py_uop_sym_matches_type(_Py_UopsSymbol *sym, PyTypeObject *typ) { - assert(typ == NULL || PyType_Check(typ)); - if (!sym_has_flag(sym, KNOWN)) { + assert(typ != NULL && PyType_Check(typ)); + if (_Py_uop_sym_is_bottom(sym)) { return false; } return sym->typ == typ; @@ -277,15 +334,14 @@ do { \ } \ } while (0) -/* static _Py_UopsSymbol * -make_contradiction(_Py_UOpsContext *ctx) +make_bottom(_Py_UOpsContext *ctx) { _Py_UopsSymbol *sym = _Py_uop_sym_new_unknown(ctx); _Py_uop_sym_set_null(sym); - _Py_uop_sym_set_type(sym, &PyLong_Type); + _Py_uop_sym_set_non_null(sym); return sym; -}*/ +} PyObject * _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) @@ -293,31 +349,93 @@ _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) _Py_UOpsContext context; _Py_UOpsContext *ctx = &context; _Py_uop_abstractcontext_init(ctx); + PyObject *val_42 = NULL; + PyObject *val_43 = NULL; - _Py_UopsSymbol *top = _Py_uop_sym_new_unknown(ctx); - if (top == NULL) { - return NULL; + // Use a single 'sym' variable so copy-pasting tests is easier. + _Py_UopsSymbol *sym = _Py_uop_sym_new_unknown(ctx); + if (sym == NULL) { + goto fail; } - TEST_PREDICATE(!_Py_uop_sym_is_null(top), "unknown is NULL"); - TEST_PREDICATE(!_Py_uop_sym_is_not_null(top), "unknown is not NULL"); - TEST_PREDICATE(!_Py_uop_sym_is_const(top), "unknown is a constant"); - // TEST_PREDICATE(_Py_uop_sym_get_const(top) == NULL, "unknown as constant is not NULL"); - - // _Py_UopsSymbol *contradiction = make_contradiction(ctx); - // TEST_PREDICATE(_Py_uop_sym_is_null(contradiction), "contradiction is NULL is not true"); - // TEST_PREDICATE(_Py_uop_sym_is_not_null(contradiction), "contradiction is not NULL is not true"); - // TEST_PREDICATE(_Py_uop_sym_is_const(contradiction), "contradiction is a constant is not true"); - - _Py_UopsSymbol *int_type = _Py_uop_sym_new_type(ctx, &PyLong_Type); - TEST_PREDICATE(_Py_uop_sym_matches_type(int_type, &PyLong_Type), "inconsistent type"); - _Py_uop_sym_set_type(int_type, &PyLong_Type); - TEST_PREDICATE(_Py_uop_sym_matches_type(int_type, &PyLong_Type), "inconsistent type"); - _Py_uop_sym_set_type(int_type, &PyFloat_Type); - // TEST_PREDICATE(_Py_uop_sym_matches_type(int_type, &PyLong_Type), "(int and float) doesn't match int"); + TEST_PREDICATE(!_Py_uop_sym_is_null(sym), "top is NULL"); + TEST_PREDICATE(!_Py_uop_sym_is_not_null(sym), "top is not NULL"); + TEST_PREDICATE(!_Py_uop_sym_matches_type(sym, &PyLong_Type), "top matches a type"); + TEST_PREDICATE(!_Py_uop_sym_is_const(sym), "top is a constant"); + TEST_PREDICATE(_Py_uop_sym_get_const(sym) == NULL, "top as constant is not NULL"); + TEST_PREDICATE(!_Py_uop_sym_is_bottom(sym), "top is bottom"); + + sym = make_bottom(ctx); + if (sym == NULL) { + goto fail; + } + TEST_PREDICATE(!_Py_uop_sym_is_null(sym), "bottom is NULL is not false"); + TEST_PREDICATE(!_Py_uop_sym_is_not_null(sym), "bottom is not NULL is not false"); + TEST_PREDICATE(!_Py_uop_sym_matches_type(sym, &PyLong_Type), "bottom matches a type"); + TEST_PREDICATE(!_Py_uop_sym_is_const(sym), "bottom is a constant is not false"); + TEST_PREDICATE(_Py_uop_sym_get_const(sym) == NULL, "bottom as constant is not NULL"); + TEST_PREDICATE(_Py_uop_sym_is_bottom(sym), "bottom isn't bottom"); + + sym = _Py_uop_sym_new_type(ctx, &PyLong_Type); + if (sym == NULL) { + goto fail; + } + TEST_PREDICATE(!_Py_uop_sym_is_null(sym), "int is NULL"); + TEST_PREDICATE(_Py_uop_sym_is_not_null(sym), "int isn't not NULL"); + TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyLong_Type), "int isn't int"); + TEST_PREDICATE(!_Py_uop_sym_matches_type(sym, &PyFloat_Type), "int matches float"); + TEST_PREDICATE(!_Py_uop_sym_is_const(sym), "int is a constant"); + TEST_PREDICATE(_Py_uop_sym_get_const(sym) == NULL, "int as constant is not NULL"); + + _Py_uop_sym_set_type(sym, &PyLong_Type); // Should be a no-op + TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyLong_Type), "(int and int) isn't int"); + + _Py_uop_sym_set_type(sym, &PyFloat_Type); // Should make it bottom + TEST_PREDICATE(_Py_uop_sym_is_bottom(sym), "(int and float) isn't bottom"); + + val_42 = PyLong_FromLong(42); + assert(val_42 != NULL); + assert(_Py_IsImmortal(val_42)); + + val_43 = PyLong_FromLong(43); + assert(val_43 != NULL); + assert(_Py_IsImmortal(val_43)); + + sym = _Py_uop_sym_new_type(ctx, &PyLong_Type); + if (sym == NULL) { + goto fail; + } + _Py_uop_sym_set_const(sym, val_42); + TEST_PREDICATE(!_Py_uop_sym_is_null(sym), "42 is NULL"); + TEST_PREDICATE(_Py_uop_sym_is_not_null(sym), "42 isn't not NULL"); + TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyLong_Type), "42 isn't an int"); + TEST_PREDICATE(!_Py_uop_sym_matches_type(sym, &PyFloat_Type), "42 matches float"); + TEST_PREDICATE(_Py_uop_sym_is_const(sym), "42 is not a constant"); + TEST_PREDICATE(_Py_uop_sym_get_const(sym) != NULL, "42 as constant is NULL"); + TEST_PREDICATE(_Py_uop_sym_get_const(sym) == val_42, "42 as constant isn't 42"); + + _Py_uop_sym_set_type(sym, &PyLong_Type); // Should be a no-op + TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyLong_Type), "(42 and 42) isn't an int"); + TEST_PREDICATE(_Py_uop_sym_get_const(sym) == val_42, "(42 and 42) as constant isn't 42"); + + _Py_uop_sym_set_type(sym, &PyFloat_Type); // Should make it bottom + TEST_PREDICATE(_Py_uop_sym_is_bottom(sym), "(42 and float) isn't bottom"); + + sym = _Py_uop_sym_new_type(ctx, &PyLong_Type); + if (sym == NULL) { + goto fail; + } + _Py_uop_sym_set_const(sym, val_42); + _Py_uop_sym_set_const(sym, val_43); // Should make it bottom + TEST_PREDICATE(_Py_uop_sym_is_bottom(sym), "(42 and 43) isn't bottom"); _Py_uop_abstractcontext_fini(ctx); + Py_DECREF(val_42); + Py_DECREF(val_43); Py_RETURN_NONE; + fail: _Py_uop_abstractcontext_fini(ctx); + Py_XDECREF(val_42); + Py_XDECREF(val_43); return NULL; } From 6c1c94dc517b77afcebb25436a4b7b0d13b6eb4d Mon Sep 17 00:00:00 2001 From: Kerim Kabirov <39376984+Privat33r-dev@users.noreply.github.com> Date: Wed, 28 Feb 2024 20:43:05 +0100 Subject: [PATCH 495/507] GH-115986 Reorder pprint docs and amend some references (#116019) Introduce a new subsubsection, 'Functions', for module level functions, and place it before the PrettyPrinter class reference. Also: - Fix pprint.pprint() references so they properly link to the module level function. - Add links to sys.stdout. --- Doc/library/pprint.rst | 181 +++++++++++++++++++++-------------------- 1 file changed, 92 insertions(+), 89 deletions(-) diff --git a/Doc/library/pprint.rst b/Doc/library/pprint.rst index e883acd67d6c72f..2a2eb098646364d 100644 --- a/Doc/library/pprint.rst +++ b/Doc/library/pprint.rst @@ -31,7 +31,93 @@ Dictionaries are sorted by key before the display is computed. .. versionchanged:: 3.10 Added support for pretty-printing :class:`dataclasses.dataclass`. -The :mod:`pprint` module defines one class: +.. _pprint-functions: + +Functions +--------- + +.. function:: pp(object, *args, sort_dicts=False, **kwargs) + + Prints the formatted representation of *object* followed by a newline. + If *sort_dicts* is false (the default), dictionaries will be displayed with + their keys in insertion order, otherwise the dict keys will be sorted. + *args* and *kwargs* will be passed to :func:`~pprint.pprint` as formatting + parameters. + + .. versionadded:: 3.8 + + +.. function:: pprint(object, stream=None, indent=1, width=80, depth=None, *, \ + compact=False, sort_dicts=True, underscore_numbers=False) + + Prints the formatted representation of *object* on *stream*, followed by a + newline. If *stream* is ``None``, :data:`sys.stdout` is used. This may be used + in the interactive interpreter instead of the :func:`print` function for + inspecting values (you can even reassign ``print = pprint.pprint`` for use + within a scope). + + The configuration parameters *stream*, *indent*, *width*, *depth*, + *compact*, *sort_dicts* and *underscore_numbers* are passed to the + :class:`PrettyPrinter` constructor and their meanings are as + described in its documentation above. + + >>> import pprint + >>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni'] + >>> stuff.insert(0, stuff) + >>> pprint.pprint(stuff) + [<Recursion on list with id=...>, + 'spam', + 'eggs', + 'lumberjack', + 'knights', + 'ni'] + +.. function:: pformat(object, indent=1, width=80, depth=None, *, \ + compact=False, sort_dicts=True, underscore_numbers=False) + + Return the formatted representation of *object* as a string. *indent*, + *width*, *depth*, *compact*, *sort_dicts* and *underscore_numbers* are + passed to the :class:`PrettyPrinter` constructor as formatting parameters + and their meanings are as described in its documentation above. + + +.. function:: isreadable(object) + + .. index:: pair: built-in function; eval + + Determine if the formatted representation of *object* is "readable", or can be + used to reconstruct the value using :func:`eval`. This always returns ``False`` + for recursive objects. + + >>> pprint.isreadable(stuff) + False + + +.. function:: isrecursive(object) + + Determine if *object* requires a recursive representation. This function is + subject to the same limitations as noted in :func:`saferepr` below and may raise an + :exc:`RecursionError` if it fails to detect a recursive object. + + +.. function:: saferepr(object) + + Return a string representation of *object*, protected against recursion in + some common data structures, namely instances of :class:`dict`, :class:`list` + and :class:`tuple` or subclasses whose ``__repr__`` has not been overridden. If the + representation of object exposes a recursive entry, the recursive reference + will be represented as ``<Recursion on typename with id=number>``. The + representation is not otherwise formatted. + + >>> pprint.saferepr(stuff) + "[<Recursion on list with id=...>, 'spam', 'eggs', 'lumberjack', 'knights', 'ni']" + +.. _prettyprinter-objects: + +PrettyPrinter Objects +--------------------- + +This module defines one class: .. First the implementation class: @@ -44,9 +130,9 @@ The :mod:`pprint` module defines one class: Construct a :class:`PrettyPrinter` instance. This constructor understands several keyword parameters. - *stream* (default ``sys.stdout``) is a :term:`file-like object` to + *stream* (default :data:`!sys.stdout`) is a :term:`file-like object` to which the output will be written by calling its :meth:`!write` method. - If both *stream* and ``sys.stdout`` are ``None``, then + If both *stream* and :data:`!sys.stdout` are ``None``, then :meth:`~PrettyPrinter.pprint` silently returns. Other values configure the manner in which nesting of complex data @@ -87,7 +173,7 @@ The :mod:`pprint` module defines one class: Added the *underscore_numbers* parameter. .. versionchanged:: 3.11 - No longer attempts to write to ``sys.stdout`` if it is ``None``. + No longer attempts to write to :data:`!sys.stdout` if it is ``None``. >>> import pprint >>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni'] @@ -112,89 +198,6 @@ The :mod:`pprint` module defines one class: >>> pp.pprint(tup) ('spam', ('eggs', ('lumberjack', ('knights', ('ni', ('dead', (...))))))) -.. function:: pformat(object, indent=1, width=80, depth=None, *, \ - compact=False, sort_dicts=True, underscore_numbers=False) - - Return the formatted representation of *object* as a string. *indent*, - *width*, *depth*, *compact*, *sort_dicts* and *underscore_numbers* are - passed to the :class:`PrettyPrinter` constructor as formatting parameters - and their meanings are as described in its documentation above. - - -.. function:: pp(object, *args, sort_dicts=False, **kwargs) - - Prints the formatted representation of *object* followed by a newline. - If *sort_dicts* is false (the default), dictionaries will be displayed with - their keys in insertion order, otherwise the dict keys will be sorted. - *args* and *kwargs* will be passed to :func:`pprint` as formatting - parameters. - - .. versionadded:: 3.8 - - -.. function:: pprint(object, stream=None, indent=1, width=80, depth=None, *, \ - compact=False, sort_dicts=True, underscore_numbers=False) - - Prints the formatted representation of *object* on *stream*, followed by a - newline. If *stream* is ``None``, ``sys.stdout`` is used. This may be used - in the interactive interpreter instead of the :func:`print` function for - inspecting values (you can even reassign ``print = pprint.pprint`` for use - within a scope). - - The configuration parameters *stream*, *indent*, *width*, *depth*, - *compact*, *sort_dicts* and *underscore_numbers* are passed to the - :class:`PrettyPrinter` constructor and their meanings are as - described in its documentation above. - - >>> import pprint - >>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni'] - >>> stuff.insert(0, stuff) - >>> pprint.pprint(stuff) - [<Recursion on list with id=...>, - 'spam', - 'eggs', - 'lumberjack', - 'knights', - 'ni'] - -.. function:: isreadable(object) - - .. index:: pair: built-in function; eval - - Determine if the formatted representation of *object* is "readable", or can be - used to reconstruct the value using :func:`eval`. This always returns ``False`` - for recursive objects. - - >>> pprint.isreadable(stuff) - False - - -.. function:: isrecursive(object) - - Determine if *object* requires a recursive representation. This function is - subject to the same limitations as noted in :func:`saferepr` below and may raise an - :exc:`RecursionError` if it fails to detect a recursive object. - - -One more support function is also defined: - -.. function:: saferepr(object) - - Return a string representation of *object*, protected against recursion in - some common data structures, namely instances of :class:`dict`, :class:`list` - and :class:`tuple` or subclasses whose ``__repr__`` has not been overridden. If the - representation of object exposes a recursive entry, the recursive reference - will be represented as ``<Recursion on typename with id=number>``. The - representation is not otherwise formatted. - - >>> pprint.saferepr(stuff) - "[<Recursion on list with id=...>, 'spam', 'eggs', 'lumberjack', 'knights', 'ni']" - - -.. _prettyprinter-objects: - -PrettyPrinter Objects ---------------------- :class:`PrettyPrinter` instances have the following methods: @@ -258,7 +261,7 @@ are converted to strings. The default implementation uses the internals of the Example ------- -To demonstrate several uses of the :func:`pprint` function and its parameters, +To demonstrate several uses of the :func:`~pprint.pprint` function and its parameters, let's fetch information about a project from `PyPI <https://pypi.org>`_:: >>> import json @@ -267,7 +270,7 @@ let's fetch information about a project from `PyPI <https://pypi.org>`_:: >>> with urlopen('https://pypi.org/pypi/sampleproject/json') as resp: ... project_info = json.load(resp)['info'] -In its basic form, :func:`pprint` shows the whole object:: +In its basic form, :func:`~pprint.pprint` shows the whole object:: >>> pprint.pprint(project_info) {'author': 'The Python Packaging Authority', From c43b26d02eaa103756c250e8d36829d388c5f3be Mon Sep 17 00:00:00 2001 From: Weii Wang <weii.wang@canonical.com> Date: Thu, 29 Feb 2024 04:15:52 +0800 Subject: [PATCH 496/507] gh-115197: Stop resolving host in urllib.request proxy bypass (GH-115210) Use of a proxy is intended to defer DNS for the hosts to the proxy itself, rather than a potential for information leak of the host doing DNS resolution itself for any reason. Proxy bypass lists are strictly name based. Most implementations of proxy support agree. --- Lib/test/test_urllib2.py | 29 ++++++- Lib/urllib/request.py | 77 +++++++++---------- ...-02-09-19-41-48.gh-issue-115197.20wkWH.rst | 2 + 3 files changed, 64 insertions(+), 44 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-09-19-41-48.gh-issue-115197.20wkWH.rst diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py index fa528a675892b5e..739c15df13de210 100644 --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -15,10 +15,11 @@ import subprocess import urllib.request -# The proxy bypass method imported below has logic specific to the OSX -# proxy config data structure but is testable on all platforms. +# The proxy bypass method imported below has logic specific to the +# corresponding system but is testable on all platforms. from urllib.request import (Request, OpenerDirector, HTTPBasicAuthHandler, HTTPPasswordMgrWithPriorAuth, _parse_proxy, + _proxy_bypass_winreg_override, _proxy_bypass_macosx_sysconf, AbstractDigestAuthHandler) from urllib.parse import urlparse @@ -1485,6 +1486,30 @@ def test_proxy_https_proxy_authorization(self): self.assertEqual(req.host, "proxy.example.com:3128") self.assertEqual(req.get_header("Proxy-authorization"), "FooBar") + @unittest.skipUnless(os.name == "nt", "only relevant for Windows") + def test_winreg_proxy_bypass(self): + proxy_override = "www.example.com;*.example.net; 192.168.0.1" + proxy_bypass = _proxy_bypass_winreg_override + for host in ("www.example.com", "www.example.net", "192.168.0.1"): + self.assertTrue(proxy_bypass(host, proxy_override), + "expected bypass of %s to be true" % host) + + for host in ("example.com", "www.example.org", "example.net", + "192.168.0.2"): + self.assertFalse(proxy_bypass(host, proxy_override), + "expected bypass of %s to be False" % host) + + # check intranet address bypass + proxy_override = "example.com; <local>" + self.assertTrue(proxy_bypass("example.com", proxy_override), + "expected bypass of %s to be true" % host) + self.assertFalse(proxy_bypass("example.net", proxy_override), + "expected bypass of %s to be False" % host) + for host in ("test", "localhost"): + self.assertTrue(proxy_bypass(host, proxy_override), + "expect <local> to bypass intranet address '%s'" + % host) + @unittest.skipUnless(sys.platform == 'darwin', "only relevant for OSX") def test_osx_proxy_bypass(self): bypass = { diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py index bca594420f6d9d2..d22af6618d80f16 100644 --- a/Lib/urllib/request.py +++ b/Lib/urllib/request.py @@ -2563,6 +2563,7 @@ def _proxy_bypass_macosx_sysconf(host, proxy_settings): } """ from fnmatch import fnmatch + from ipaddress import AddressValueError, IPv4Address hostonly, port = _splitport(host) @@ -2579,20 +2580,17 @@ def ip2num(ipAddr): return True hostIP = None + try: + hostIP = int(IPv4Address(hostonly)) + except AddressValueError: + pass for value in proxy_settings.get('exceptions', ()): # Items in the list are strings like these: *.local, 169.254/16 if not value: continue m = re.match(r"(\d+(?:\.\d+)*)(/\d+)?", value) - if m is not None: - if hostIP is None: - try: - hostIP = socket.gethostbyname(hostonly) - hostIP = ip2num(hostIP) - except OSError: - continue - + if m is not None and hostIP is not None: base = ip2num(m.group(1)) mask = m.group(2) if mask is None: @@ -2615,6 +2613,31 @@ def ip2num(ipAddr): return False +# Same as _proxy_bypass_macosx_sysconf, testable on all platforms +def _proxy_bypass_winreg_override(host, override): + """Return True if the host should bypass the proxy server. + + The proxy override list is obtained from the Windows + Internet settings proxy override registry value. + + An example of a proxy override value is: + "www.example.com;*.example.net; 192.168.0.1" + """ + from fnmatch import fnmatch + + host, _ = _splitport(host) + proxy_override = override.split(';') + for test in proxy_override: + test = test.strip() + # "<local>" should bypass the proxy server for all intranet addresses + if test == '<local>': + if '.' not in host: + return True + elif fnmatch(host, test): + return True + return False + + if sys.platform == 'darwin': from _scproxy import _get_proxy_settings, _get_proxies @@ -2713,7 +2736,7 @@ def proxy_bypass_registry(host): import winreg except ImportError: # Std modules, so should be around - but you never know! - return 0 + return False try: internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows\CurrentVersion\Internet Settings') @@ -2723,40 +2746,10 @@ def proxy_bypass_registry(host): 'ProxyOverride')[0]) # ^^^^ Returned as Unicode but problems if not converted to ASCII except OSError: - return 0 + return False if not proxyEnable or not proxyOverride: - return 0 - # try to make a host list from name and IP address. - rawHost, port = _splitport(host) - host = [rawHost] - try: - addr = socket.gethostbyname(rawHost) - if addr != rawHost: - host.append(addr) - except OSError: - pass - try: - fqdn = socket.getfqdn(rawHost) - if fqdn != rawHost: - host.append(fqdn) - except OSError: - pass - # make a check value list from the registry entry: replace the - # '<local>' string by the localhost entry and the corresponding - # canonical entry. - proxyOverride = proxyOverride.split(';') - # now check if we match one of the registry values. - for test in proxyOverride: - if test == '<local>': - if '.' not in rawHost: - return 1 - test = test.replace(".", r"\.") # mask dots - test = test.replace("*", r".*") # change glob sequence - test = test.replace("?", r".") # change glob char - for val in host: - if re.match(test, val, re.I): - return 1 - return 0 + return False + return _proxy_bypass_winreg_override(host, proxyOverride) def proxy_bypass(host): """Return True, if host should be bypassed. diff --git a/Misc/NEWS.d/next/Library/2024-02-09-19-41-48.gh-issue-115197.20wkWH.rst b/Misc/NEWS.d/next/Library/2024-02-09-19-41-48.gh-issue-115197.20wkWH.rst new file mode 100644 index 000000000000000..e6ca3cc525d74a1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-09-19-41-48.gh-issue-115197.20wkWH.rst @@ -0,0 +1,2 @@ +``urllib.request`` no longer resolves the hostname before checking it +against the system's proxy bypass list on macOS and Windows. From df5212df6c6f08308c68de4b3ed8a1b51ac6334b Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Wed, 28 Feb 2024 15:37:59 -0500 Subject: [PATCH 497/507] gh-112529: Simplify PyObject_GC_IsTracked and PyObject_GC_IsFinalized (#114732) --- Modules/clinic/gcmodule.c.h | 40 ++++++++++++++++++++++++++++++++++++- Modules/gcmodule.c | 29 ++++++++++----------------- Python/gc_free_threading.c | 10 ++-------- 3 files changed, 51 insertions(+), 28 deletions(-) diff --git a/Modules/clinic/gcmodule.c.h b/Modules/clinic/gcmodule.c.h index d50d170589a2cd5..9fff4da616ba003 100644 --- a/Modules/clinic/gcmodule.c.h +++ b/Modules/clinic/gcmodule.c.h @@ -469,6 +469,25 @@ PyDoc_STRVAR(gc_is_tracked__doc__, #define GC_IS_TRACKED_METHODDEF \ {"is_tracked", (PyCFunction)gc_is_tracked, METH_O, gc_is_tracked__doc__}, +static int +gc_is_tracked_impl(PyObject *module, PyObject *obj); + +static PyObject * +gc_is_tracked(PyObject *module, PyObject *obj) +{ + PyObject *return_value = NULL; + int _return_value; + + _return_value = gc_is_tracked_impl(module, obj); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyBool_FromLong((long)_return_value); + +exit: + return return_value; +} + PyDoc_STRVAR(gc_is_finalized__doc__, "is_finalized($module, obj, /)\n" "--\n" @@ -478,6 +497,25 @@ PyDoc_STRVAR(gc_is_finalized__doc__, #define GC_IS_FINALIZED_METHODDEF \ {"is_finalized", (PyCFunction)gc_is_finalized, METH_O, gc_is_finalized__doc__}, +static int +gc_is_finalized_impl(PyObject *module, PyObject *obj); + +static PyObject * +gc_is_finalized(PyObject *module, PyObject *obj) +{ + PyObject *return_value = NULL; + int _return_value; + + _return_value = gc_is_finalized_impl(module, obj); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyBool_FromLong((long)_return_value); + +exit: + return return_value; +} + PyDoc_STRVAR(gc_freeze__doc__, "freeze($module, /)\n" "--\n" @@ -547,4 +585,4 @@ gc_get_freeze_count(PyObject *module, PyObject *Py_UNUSED(ignored)) exit: return return_value; } -/*[clinic end generated code: output=258f92524c1141fc input=a9049054013a1b77]*/ +/*[clinic end generated code: output=0a7e91917adcb937 input=a9049054013a1b77]*/ diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 961165e16a0fee9..9807d2e7d48a364 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -383,7 +383,7 @@ gc_get_stats_impl(PyObject *module) /*[clinic input] -gc.is_tracked +gc.is_tracked -> bool obj: object / @@ -393,21 +393,15 @@ Returns true if the object is tracked by the garbage collector. Simple atomic objects will return false. [clinic start generated code]*/ -static PyObject * -gc_is_tracked(PyObject *module, PyObject *obj) -/*[clinic end generated code: output=14f0103423b28e31 input=d83057f170ea2723]*/ +static int +gc_is_tracked_impl(PyObject *module, PyObject *obj) +/*[clinic end generated code: output=91c8d086b7f47a33 input=423b98ec680c3126]*/ { - PyObject *result; - - if (_PyObject_IS_GC(obj) && _PyObject_GC_IS_TRACKED(obj)) - result = Py_True; - else - result = Py_False; - return Py_NewRef(result); + return PyObject_GC_IsTracked(obj); } /*[clinic input] -gc.is_finalized +gc.is_finalized -> bool obj: object / @@ -415,14 +409,11 @@ gc.is_finalized Returns true if the object has been already finalized by the GC. [clinic start generated code]*/ -static PyObject * -gc_is_finalized(PyObject *module, PyObject *obj) -/*[clinic end generated code: output=e1516ac119a918ed input=201d0c58f69ae390]*/ +static int +gc_is_finalized_impl(PyObject *module, PyObject *obj) +/*[clinic end generated code: output=401ff5d6fc660429 input=ca4d111c8f8c4e3a]*/ { - if (_PyObject_IS_GC(obj) && _PyGC_FINALIZED(obj)) { - Py_RETURN_TRUE; - } - Py_RETURN_FALSE; + return PyObject_GC_IsFinalized(obj); } /*[clinic input] diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 14790899825de1a..d4fb50106093ee6 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -1693,19 +1693,13 @@ PyObject_GC_Del(void *op) int PyObject_GC_IsTracked(PyObject* obj) { - if (_PyObject_IS_GC(obj) && _PyObject_GC_IS_TRACKED(obj)) { - return 1; - } - return 0; + return _PyObject_GC_IS_TRACKED(obj); } int PyObject_GC_IsFinalized(PyObject *obj) { - if (_PyObject_IS_GC(obj) && _PyGC_FINALIZED(obj)) { - return 1; - } - return 0; + return _PyGC_FINALIZED(obj); } struct custom_visitor_args { From 75c6c05fea212330f4b0259602ffae1b2cb91be3 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Wed, 28 Feb 2024 15:50:09 -0500 Subject: [PATCH 498/507] gh-115891: Fix debug byte filling in free-threaded build (#116018) The previous code had two bugs. First, the debug offset in the mimalloc heap includes the two pymalloc debug words, but the pointer passed to fill_mem_debug does not include them. Second, the current object heap is correct source for allocations, but not deallocations. --- Objects/obmalloc.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index 43427d4449eb1ad..b2a2286ef22b662 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -2460,14 +2460,23 @@ write_size_t(void *p, size_t n) } static void -fill_mem_debug(debug_alloc_api_t *api, void *data, int c, size_t nbytes) +fill_mem_debug(debug_alloc_api_t *api, void *data, int c, size_t nbytes, + bool is_alloc) { #ifdef Py_GIL_DISABLED if (api->api_id == 'o') { // Don't overwrite the first few bytes of a PyObject allocation in the // free-threaded build _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET(); - size_t debug_offset = tstate->mimalloc.current_object_heap->debug_offset; + size_t debug_offset; + if (is_alloc) { + debug_offset = tstate->mimalloc.current_object_heap->debug_offset; + } + else { + char *alloc = (char *)data - 2*SST; // start of the allocation + debug_offset = _mi_ptr_page(alloc)->debug_offset; + } + debug_offset -= 2*SST; // account for pymalloc extra bytes if (debug_offset < nbytes) { memset((char *)data + debug_offset, c, nbytes - debug_offset); } @@ -2553,7 +2562,7 @@ _PyMem_DebugRawAlloc(int use_calloc, void *ctx, size_t nbytes) memset(p + SST + 1, PYMEM_FORBIDDENBYTE, SST-1); if (nbytes > 0 && !use_calloc) { - fill_mem_debug(api, data, PYMEM_CLEANBYTE, nbytes); + fill_mem_debug(api, data, PYMEM_CLEANBYTE, nbytes, true); } /* at tail, write pad (SST bytes) and serialno (SST bytes) */ @@ -2603,7 +2612,7 @@ _PyMem_DebugRawFree(void *ctx, void *p) nbytes = read_size_t(q); nbytes += PYMEM_DEBUG_EXTRA_BYTES - 2*SST; memset(q, PYMEM_DEADBYTE, 2*SST); - fill_mem_debug(api, p, PYMEM_DEADBYTE, nbytes); + fill_mem_debug(api, p, PYMEM_DEADBYTE, nbytes, false); api->alloc.free(api->alloc.ctx, q); } From 3409bc29c9f06051c28ae0791155e3aebd76ff2d Mon Sep 17 00:00:00 2001 From: Guido van Rossum <guido@python.org> Date: Wed, 28 Feb 2024 14:38:01 -0800 Subject: [PATCH 499/507] gh-115859: Re-enable T2 optimizer pass by default (#116062) This undoes the *temporary* default disabling of the T2 optimizer pass in gh-115860. - Add a new test that reproduces Brandt's example from gh-115859; it indeed crashes before gh-116028 with PYTHONUOPSOPTIMIZE=1 - Re-enable the optimizer pass in T2, stop checking PYTHONUOPSOPTIMIZE - Rename the env var to disable T2 entirely to PYTHON_UOPS_OPTIMIZE (must be explicitly set to 0 to disable) - Fix skipIf conditions on tests in test_opt.py accordingly - Export sym_is_bottom() (for debugging) - Fix various things in the `_BINARY_OP_` specializations in the abstract interpreter: - DECREF(temp) - out-of-space check after sym_new_const() - add sym_matches_type() checks, so even if we somehow reach a binary op with symbolic constants of the wrong type on the stack we won't trigger the type assert --- Include/internal/pycore_optimizer.h | 2 ++ Lib/test/test_capi/test_opt.py | 21 +++++++++++++- Python/optimizer.c | 4 +-- Python/optimizer_analysis.c | 10 +++---- Python/optimizer_bytecodes.c | 43 +++++++++++++++++++++++------ Python/optimizer_cases.c.h | 42 ++++++++++++++++++++++------ Python/optimizer_symbols.c | 2 +- 7 files changed, 96 insertions(+), 28 deletions(-) diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 265eae4e290c38f..614850468ec1d38 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -95,6 +95,8 @@ extern void _Py_uop_sym_set_null(_Py_UopsSymbol *sym); extern void _Py_uop_sym_set_non_null(_Py_UopsSymbol *sym); extern void _Py_uop_sym_set_type(_Py_UopsSymbol *sym, PyTypeObject *typ); extern void _Py_uop_sym_set_const(_Py_UopsSymbol *sym, PyObject *const_val); +extern bool _Py_uop_sym_is_bottom(_Py_UopsSymbol *sym); + extern int _Py_uop_abstractcontext_init(_Py_UOpsContext *ctx); extern void _Py_uop_abstractcontext_fini(_Py_UOpsContext *ctx); diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 25fc36dec93ddcb..e1aef21b2c7644a 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -210,6 +210,8 @@ def f(): exe = get_first_executor(f) self.assertIsNone(exe) + +@unittest.skipIf(os.getenv("PYTHON_UOPS_OPTIMIZE") == "0", "Needs uop optimizer to run.") class TestUops(unittest.TestCase): def test_basic_loop(self): @@ -570,7 +572,7 @@ def testfunc(n): self.assertLessEqual(count, 2) -@unittest.skipIf(os.getenv("PYTHONUOPSOPTIMIZE", default=0) == 0, "Needs uop optimizer to run.") +@unittest.skipIf(os.getenv("PYTHON_UOPS_OPTIMIZE") == "0", "Needs uop optimizer to run.") class TestUopsOptimization(unittest.TestCase): def _run_with_optimizer(self, testfunc, arg): @@ -890,5 +892,22 @@ def testfunc(n): self.assertLessEqual(len(guard_both_float_count), 1) self.assertIn("_COMPARE_OP_STR", uops) + def test_type_inconsistency(self): + def testfunc(n): + for i in range(n): + x = _test_global + _test_global + # Must be a real global else it won't be optimized to _LOAD_CONST_INLINE + global _test_global + _test_global = 0 + _, ex = self._run_with_optimizer(testfunc, 16) + self.assertIsNone(ex) + _test_global = 1.2 + _, ex = self._run_with_optimizer(testfunc, 16) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertIn("_GUARD_BOTH_INT", uops) + self.assertIn("_BINARY_OP_ADD_INT", uops) + + if __name__ == "__main__": unittest.main() diff --git a/Python/optimizer.c b/Python/optimizer.c index c04ee17ee2171d6..acd6d52c4a885f2 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1008,8 +1008,8 @@ uop_optimize( return err; } OPT_STAT_INC(traces_created); - char *uop_optimize = Py_GETENV("PYTHONUOPSOPTIMIZE"); - if (uop_optimize == NULL || *uop_optimize > '0') { + char *env_var = Py_GETENV("PYTHON_UOPS_OPTIMIZE"); + if (env_var == NULL || *env_var == '\0' || *env_var > '0') { err = _Py_uop_analyze_and_optimize(frame, buffer, UOP_MAX_TRACE_LENGTH, curr_stackentries, &dependencies); diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 8e408ffbb1c2b5f..2a7ef4ec919eebe 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -297,6 +297,7 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, #define sym_set_non_null _Py_uop_sym_set_non_null #define sym_set_type _Py_uop_sym_set_type #define sym_set_const _Py_uop_sym_set_const +#define sym_is_bottom _Py_uop_sym_is_bottom #define frame_new _Py_uop_frame_new #define frame_pop _Py_uop_frame_pop @@ -510,12 +511,9 @@ _Py_uop_analyze_and_optimize( peephole_opt(frame, buffer, buffer_size); - char *uop_optimize = Py_GETENV("PYTHONUOPSOPTIMIZE"); - if (uop_optimize != NULL && *uop_optimize > '0') { - err = optimize_uops( - (PyCodeObject *)frame->f_executable, buffer, - buffer_size, curr_stacklen, dependencies); - } + err = optimize_uops( + (PyCodeObject *)frame->f_executable, buffer, + buffer_size, curr_stacklen, dependencies); if (err == 0) { goto not_ready; diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index b65e90bf980e5a1..928c22da16b9998 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -25,6 +25,7 @@ typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame; #define sym_set_non_null _Py_uop_sym_set_non_null #define sym_set_type _Py_uop_sym_set_type #define sym_set_const _Py_uop_sym_set_const +#define sym_is_bottom _Py_uop_sym_is_bottom #define frame_new _Py_uop_frame_new #define frame_pop _Py_uop_frame_pop @@ -107,7 +108,9 @@ dummy_func(void) { } op(_BINARY_OP_ADD_INT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) + { assert(PyLong_CheckExact(sym_get_const(left))); assert(PyLong_CheckExact(sym_get_const(right))); PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(left), @@ -115,7 +118,9 @@ dummy_func(void) { if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } @@ -125,7 +130,9 @@ dummy_func(void) { } op(_BINARY_OP_SUBTRACT_INT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) + { assert(PyLong_CheckExact(sym_get_const(left))); assert(PyLong_CheckExact(sym_get_const(right))); PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(left), @@ -133,7 +140,9 @@ dummy_func(void) { if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } @@ -143,7 +152,9 @@ dummy_func(void) { } op(_BINARY_OP_MULTIPLY_INT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) + { assert(PyLong_CheckExact(sym_get_const(left))); assert(PyLong_CheckExact(sym_get_const(right))); PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(left), @@ -151,7 +162,9 @@ dummy_func(void) { if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } @@ -161,7 +174,9 @@ dummy_func(void) { } op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) + { assert(PyFloat_CheckExact(sym_get_const(left))); assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( @@ -171,6 +186,8 @@ dummy_func(void) { goto error; } res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } @@ -180,7 +197,9 @@ dummy_func(void) { } op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) + { assert(PyFloat_CheckExact(sym_get_const(left))); assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( @@ -190,6 +209,8 @@ dummy_func(void) { goto error; } res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } @@ -199,7 +220,9 @@ dummy_func(void) { } op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) + { assert(PyFloat_CheckExact(sym_get_const(left))); assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( @@ -209,6 +232,8 @@ dummy_func(void) { goto error; } res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 9e99c8395a405b5..9b387c078502456 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -183,7 +183,9 @@ _Py_UopsSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) + { assert(PyLong_CheckExact(sym_get_const(left))); assert(PyLong_CheckExact(sym_get_const(right))); PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(left), @@ -191,7 +193,9 @@ if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } @@ -209,7 +213,9 @@ _Py_UopsSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) + { assert(PyLong_CheckExact(sym_get_const(left))); assert(PyLong_CheckExact(sym_get_const(right))); PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(left), @@ -217,7 +223,9 @@ if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } @@ -235,7 +243,9 @@ _Py_UopsSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) + { assert(PyLong_CheckExact(sym_get_const(left))); assert(PyLong_CheckExact(sym_get_const(right))); PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(left), @@ -243,7 +253,9 @@ if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } @@ -275,7 +287,9 @@ _Py_UopsSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) + { assert(PyFloat_CheckExact(sym_get_const(left))); assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( @@ -285,6 +299,8 @@ goto error; } res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } @@ -302,7 +318,9 @@ _Py_UopsSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) + { assert(PyFloat_CheckExact(sym_get_const(left))); assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( @@ -312,6 +330,8 @@ goto error; } res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } @@ -329,7 +349,9 @@ _Py_UopsSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) + { assert(PyFloat_CheckExact(sym_get_const(left))); assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( @@ -339,6 +361,8 @@ goto error; } res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 158ee67d19f50e2..a529cc2f5cf2150 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -77,7 +77,7 @@ sym_set_bottom(_Py_UopsSymbol *sym) Py_CLEAR(sym->const_val); } -static inline bool +bool _Py_uop_sym_is_bottom(_Py_UopsSymbol *sym) { if ((sym->flags & IS_NULL) && (sym->flags & NOT_NULL)) { From 81c79961d2ee27dec90dbc0b72dfca7a5b27de7a Mon Sep 17 00:00:00 2001 From: Dino Viehland <dinoviehland@meta.com> Date: Wed, 28 Feb 2024 14:53:19 -0800 Subject: [PATCH 500/507] gh-112075: Use relaxed stores for places where we may race with when reading lock-free (#115786) --- Objects/dictobject.c | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 58fe973bc7a0361..5016e255f70ef98 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -250,6 +250,14 @@ load_keys_nentries(PyDictObject *mp) #endif +#define STORE_KEY(ep, key) FT_ATOMIC_STORE_PTR_RELEASE(ep->me_key, key) +#define STORE_VALUE(ep, value) FT_ATOMIC_STORE_PTR_RELEASE(ep->me_value, value) +#define STORE_SPLIT_VALUE(mp, idx, value) FT_ATOMIC_STORE_PTR_RELEASE(mp->ma_values->values[idx], value) +#define STORE_HASH(ep, hash) FT_ATOMIC_STORE_SSIZE_RELAXED(ep->me_hash, hash) +#define STORE_KEYS_USABLE(keys, usable) FT_ATOMIC_STORE_SSIZE_RELAXED(keys->dk_usable, usable) +#define STORE_KEYS_NENTRIES(keys, nentries) FT_ATOMIC_STORE_SSIZE_RELAXED(keys->dk_nentries, nentries) +#define STORE_USED(mp, used) FT_ATOMIC_STORE_SSIZE_RELAXED(mp->ma_used, used) + #define PERTURB_SHIFT 5 /* @@ -1621,7 +1629,6 @@ insert_into_splitdictkeys(PyDictKeysObject *keys, PyObject *name) return ix; } - static inline int insert_combined_dict(PyInterpreterState *interp, PyDictObject *mp, Py_hash_t hash, PyObject *key, PyObject *value) @@ -1639,18 +1646,18 @@ insert_combined_dict(PyInterpreterState *interp, PyDictObject *mp, if (DK_IS_UNICODE(mp->ma_keys)) { PyDictUnicodeEntry *ep; ep = &DK_UNICODE_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries]; - ep->me_key = key; - ep->me_value = value; + STORE_KEY(ep, key); + STORE_VALUE(ep, value); } else { PyDictKeyEntry *ep; ep = &DK_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries]; - ep->me_key = key; - ep->me_hash = hash; - ep->me_value = value; + STORE_KEY(ep, key); + STORE_VALUE(ep, value); + STORE_HASH(ep, hash); } - mp->ma_keys->dk_usable--; - mp->ma_keys->dk_nentries++; + STORE_KEYS_USABLE(mp->ma_keys, mp->ma_keys->dk_usable - 1); + STORE_KEYS_NENTRIES(mp->ma_keys, mp->ma_keys->dk_nentries + 1); assert(mp->ma_keys->dk_usable >= 0); return 0; } @@ -1682,7 +1689,7 @@ insert_split_dict(PyInterpreterState *interp, PyDictObject *mp, Py_ssize_t index = keys->dk_nentries; _PyDictValues_AddToInsertionOrder(mp->ma_values, index); assert (mp->ma_values->values[index] == NULL); - mp->ma_values->values[index] = value; + STORE_SPLIT_VALUE(mp, index, value); split_keys_entry_added(keys); assert(keys->dk_usable >= 0); @@ -2013,8 +2020,8 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp, } } - mp->ma_keys->dk_usable -= numentries; - mp->ma_keys->dk_nentries = numentries; + STORE_KEYS_USABLE(mp->ma_keys, mp->ma_keys->dk_usable - numentries); + STORE_KEYS_NENTRIES(mp->ma_keys, numentries); ASSERT_CONSISTENT(mp); return 0; } @@ -2507,15 +2514,15 @@ delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix, if (DK_IS_UNICODE(mp->ma_keys)) { PyDictUnicodeEntry *ep = &DK_UNICODE_ENTRIES(mp->ma_keys)[ix]; old_key = ep->me_key; - ep->me_key = NULL; - ep->me_value = NULL; + STORE_KEY(ep, NULL); + STORE_VALUE(ep, NULL); } else { PyDictKeyEntry *ep = &DK_ENTRIES(mp->ma_keys)[ix]; old_key = ep->me_key; - ep->me_key = NULL; - ep->me_value = NULL; - ep->me_hash = 0; + STORE_KEY(ep, NULL); + STORE_VALUE(ep, NULL); + STORE_HASH(ep, 0); } Py_DECREF(old_key); } @@ -4393,8 +4400,8 @@ dict_popitem_impl(PyDictObject *self) PyTuple_SET_ITEM(res, 0, key); PyTuple_SET_ITEM(res, 1, value); /* We can't dk_usable++ since there is DKIX_DUMMY in indices */ - self->ma_keys->dk_nentries = i; - self->ma_used--; + STORE_KEYS_NENTRIES(self->ma_keys, i); + STORE_USED(self, self->ma_used - 1); self->ma_version_tag = new_version; ASSERT_CONSISTENT(self); return res; From 67c19e57b5c928278ebd191a545979ce786f06b3 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger <rhettinger@users.noreply.github.com> Date: Wed, 28 Feb 2024 17:04:56 -0600 Subject: [PATCH 501/507] Improve all_equal() recipe (gh-116081) Replace conjuction of next() calls with simpler len()/take() logic. Add key function. --- Doc/library/itertools.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 42e70404b306b0c..4e731fefe8908d3 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -863,10 +863,9 @@ which incur interpreter overhead. "Given a predicate that returns True or False, count the True results." return sum(map(pred, iterable)) - def all_equal(iterable): + def all_equal(iterable, key=None): "Returns True if all the elements are equal to each other." - g = groupby(iterable) - return next(g, True) and not next(g, False) + return len(take(2, groupby(iterable, key))) <= 1 def first_true(iterable, default=False, pred=None): """Returns the first true value in the iterable. @@ -1225,6 +1224,8 @@ The following recipes have a more mathematical flavor: >>> [all_equal(s) for s in ('', 'A', 'AAAA', 'AAAB', 'AAABA')] [True, True, True, False, False] + >>> [all_equal(s, key=str.casefold) for s in ('', 'A', 'AaAa', 'AAAB', 'AAABA')] + [True, True, True, False, False] >>> quantify(range(99), lambda x: x%2==0) 50 From e80abd57a82ea1beae0a82423d45c6eb8c5c5c74 Mon Sep 17 00:00:00 2001 From: Eric Snow <ericsnowcurrently@gmail.com> Date: Wed, 28 Feb 2024 16:08:08 -0700 Subject: [PATCH 502/507] gh-76785: Update test.support.interpreters to Align With PEP 734 (gh-115566) This brings the code under test.support.interpreters, and the corresponding extension modules, in line with recent updates to PEP 734. (Note: PEP 734 has not been accepted at this time. However, we are using an internal copy of the implementation in the test suite to exercise the existing subinterpreters feature.) --- Lib/test/support/interpreters/__init__.py | 51 +++- Lib/test/support/interpreters/queues.py | 74 ++++- Lib/test/test_interpreters/test_api.py | 291 +++++++++++++++---- Lib/test/test_interpreters/test_channels.py | 4 +- Lib/test/test_interpreters/test_lifecycle.py | 2 +- Lib/test/test_interpreters/test_queues.py | 151 +++++++--- Lib/test/test_interpreters/utils.py | 19 +- Lib/test/test_sys.py | 4 +- Lib/test/test_threading.py | 2 +- Modules/_xxinterpqueuesmodule.c | 125 +++++--- Modules/_xxsubinterpretersmodule.c | 58 ++++ 11 files changed, 624 insertions(+), 157 deletions(-) diff --git a/Lib/test/support/interpreters/__init__.py b/Lib/test/support/interpreters/__init__.py index 15a908e9663593f..d02ffbae1113c0a 100644 --- a/Lib/test/support/interpreters/__init__.py +++ b/Lib/test/support/interpreters/__init__.py @@ -6,7 +6,7 @@ # aliases: from _xxsubinterpreters import ( - InterpreterError, InterpreterNotFoundError, + InterpreterError, InterpreterNotFoundError, NotShareableError, is_shareable, ) @@ -14,7 +14,8 @@ __all__ = [ 'get_current', 'get_main', 'create', 'list_all', 'is_shareable', 'Interpreter', - 'InterpreterError', 'InterpreterNotFoundError', 'ExecFailure', + 'InterpreterError', 'InterpreterNotFoundError', 'ExecutionFailed', + 'NotShareableError', 'create_queue', 'Queue', 'QueueEmpty', 'QueueFull', ] @@ -42,7 +43,11 @@ def __getattr__(name): {formatted} """.strip() -class ExecFailure(RuntimeError): +class ExecutionFailed(RuntimeError): + """An unhandled exception happened during execution. + + This is raised from Interpreter.exec() and Interpreter.call(). + """ def __init__(self, excinfo): msg = excinfo.formatted @@ -157,7 +162,7 @@ def prepare_main(self, ns=None, /, **kwargs): ns = dict(ns, **kwargs) if ns is not None else kwargs _interpreters.set___main___attrs(self._id, ns) - def exec_sync(self, code, /): + def exec(self, code, /): """Run the given source code in the interpreter. This is essentially the same as calling the builtin "exec" @@ -166,10 +171,10 @@ def exec_sync(self, code, /): There is no return value. - If the code raises an unhandled exception then an ExecFailure - is raised, which summarizes the unhandled exception. The actual - exception is discarded because objects cannot be shared between - interpreters. + If the code raises an unhandled exception then an ExecutionFailed + exception is raised, which summarizes the unhandled exception. + The actual exception is discarded because objects cannot be + shared between interpreters. This blocks the current Python thread until done. During that time, the previous interpreter is allowed to run @@ -177,11 +182,35 @@ def exec_sync(self, code, /): """ excinfo = _interpreters.exec(self._id, code) if excinfo is not None: - raise ExecFailure(excinfo) + raise ExecutionFailed(excinfo) + + def call(self, callable, /): + """Call the object in the interpreter with given args/kwargs. + + Only functions that take no arguments and have no closure + are supported. - def run(self, code, /): + The return value is discarded. + + If the callable raises an exception then the error display + (including full traceback) is send back between the interpreters + and an ExecutionFailed exception is raised, much like what + happens with Interpreter.exec(). + """ + # XXX Support args and kwargs. + # XXX Support arbitrary callables. + # XXX Support returning the return value (e.g. via pickle). + excinfo = _interpreters.call(self._id, callable) + if excinfo is not None: + raise ExecutionFailed(excinfo) + + def call_in_thread(self, callable, /): + """Return a new thread that calls the object in the interpreter. + + The return value and any raised exception are discarded. + """ def task(): - self.exec_sync(code) + self.call(callable) t = threading.Thread(target=task) t.start() return t diff --git a/Lib/test/support/interpreters/queues.py b/Lib/test/support/interpreters/queues.py index aead0c40ca9667e..2cc616be337a501 100644 --- a/Lib/test/support/interpreters/queues.py +++ b/Lib/test/support/interpreters/queues.py @@ -1,5 +1,6 @@ """Cross-interpreter Queues High Level Module.""" +import pickle import queue import time import weakref @@ -31,20 +32,26 @@ class QueueFull(_queues.QueueFull, queue.Full): """ -def create(maxsize=0): +_SHARED_ONLY = 0 +_PICKLED = 1 + +def create(maxsize=0, *, syncobj=False): """Return a new cross-interpreter queue. The queue may be used to pass data safely between interpreters. + + "syncobj" sets the default for Queue.put() + and Queue.put_nowait(). """ - qid = _queues.create(maxsize) - return Queue(qid) + fmt = _SHARED_ONLY if syncobj else _PICKLED + qid = _queues.create(maxsize, fmt) + return Queue(qid, _fmt=fmt) def list_all(): """Return a list of all open queues.""" - return [Queue(qid) - for qid in _queues.list_all()] - + return [Queue(qid, _fmt=fmt) + for qid, fmt in _queues.list_all()] _known_queues = weakref.WeakValueDictionary() @@ -52,17 +59,20 @@ def list_all(): class Queue: """A cross-interpreter queue.""" - def __new__(cls, id, /): + def __new__(cls, id, /, *, _fmt=None): # There is only one instance for any given ID. if isinstance(id, int): id = int(id) else: raise TypeError(f'id must be an int, got {id!r}') + if _fmt is None: + _fmt = _queues.get_default_fmt(id) try: self = _known_queues[id] except KeyError: self = super().__new__(cls) self._id = id + self._fmt = _fmt _known_queues[id] = self _queues.bind(id) return self @@ -105,20 +115,50 @@ def qsize(self): return _queues.get_count(self._id) def put(self, obj, timeout=None, *, + syncobj=None, _delay=10 / 1000, # 10 milliseconds ): """Add the object to the queue. This blocks while the queue is full. + + If "syncobj" is None (the default) then it uses the + queue's default, set with create_queue().. + + If "syncobj" is false then all objects are supported, + at the expense of worse performance. + + If "syncobj" is true then the object must be "shareable". + Examples of "shareable" objects include the builtin singletons, + str, and memoryview. One benefit is that such objects are + passed through the queue efficiently. + + The key difference, though, is conceptual: the corresponding + object returned from Queue.get() will be strictly equivalent + to the given obj. In other words, the two objects will be + effectively indistinguishable from each other, even if the + object is mutable. The received object may actually be the + same object, or a copy (immutable values only), or a proxy. + Regardless, the received object should be treated as though + the original has been shared directly, whether or not it + actually is. That's a slightly different and stronger promise + than just (initial) equality, which is all "syncobj=False" + can promise. """ + if syncobj is None: + fmt = self._fmt + else: + fmt = _SHARED_ONLY if syncobj else _PICKLED if timeout is not None: timeout = int(timeout) if timeout < 0: raise ValueError(f'timeout value must be non-negative') end = time.time() + timeout + if fmt is _PICKLED: + obj = pickle.dumps(obj) while True: try: - _queues.put(self._id, obj) + _queues.put(self._id, obj, fmt) except _queues.QueueFull as exc: if timeout is not None and time.time() >= end: exc.__class__ = QueueFull @@ -127,9 +167,15 @@ def put(self, obj, timeout=None, *, else: break - def put_nowait(self, obj): + def put_nowait(self, obj, *, syncobj=None): + if syncobj is None: + fmt = self._fmt + else: + fmt = _SHARED_ONLY if syncobj else _PICKLED + if fmt is _PICKLED: + obj = pickle.dumps(obj) try: - return _queues.put(self._id, obj) + _queues.put(self._id, obj, fmt) except _queues.QueueFull as exc: exc.__class__ = QueueFull raise # re-raise @@ -148,12 +194,18 @@ def get(self, timeout=None, *, end = time.time() + timeout while True: try: - return _queues.get(self._id) + obj, fmt = _queues.get(self._id) except _queues.QueueEmpty as exc: if timeout is not None and time.time() >= end: exc.__class__ = QueueEmpty raise # re-raise time.sleep(_delay) + else: + break + if fmt == _PICKLED: + obj = pickle.loads(obj) + else: + assert fmt == _SHARED_ONLY return obj def get_nowait(self): diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index aefd326977095f9..363143fa810f35d 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -280,7 +280,7 @@ def test_subinterpreter(self): def test_finished(self): r, w = self.pipe() interp = interpreters.create() - interp.exec_sync(f"""if True: + interp.exec(f"""if True: import os os.write({w}, b'x') """) @@ -312,7 +312,7 @@ def test_with_only_background_threads(self): FINISHED = b'F' interp = interpreters.create() - interp.exec_sync(f"""if True: + interp.exec(f"""if True: import os import threading @@ -326,7 +326,7 @@ def task(): self.assertFalse(interp.is_running()) os.write(w_thread, DONE) - interp.exec_sync('t.join()') + interp.exec('t.join()') self.assertEqual(os.read(r_interp, 1), FINISHED) @@ -393,7 +393,7 @@ def test_from_sibling(self): interp2 = interpreters.create() self.assertEqual(set(interpreters.list_all()), {main, interp1, interp2}) - interp1.exec_sync(dedent(f""" + interp1.exec(dedent(f""" from test.support import interpreters interp2 = interpreters.Interpreter({interp2.id}) interp2.close() @@ -427,7 +427,7 @@ def test_subthreads_still_running(self): FINISHED = b'F' interp = interpreters.create() - interp.exec_sync(f"""if True: + interp.exec(f"""if True: import os import threading import time @@ -503,27 +503,27 @@ def test_not_shareable(self): interp.prepare_main(spam={'spam': 'eggs', 'foo': 'bar'}) # Make sure neither was actually bound. - with self.assertRaises(interpreters.ExecFailure): - interp.exec_sync('print(foo)') - with self.assertRaises(interpreters.ExecFailure): - interp.exec_sync('print(spam)') + with self.assertRaises(interpreters.ExecutionFailed): + interp.exec('print(foo)') + with self.assertRaises(interpreters.ExecutionFailed): + interp.exec('print(spam)') -class TestInterpreterExecSync(TestBase): +class TestInterpreterExec(TestBase): def test_success(self): interp = interpreters.create() script, file = _captured_script('print("it worked!", end="")') with file: - interp.exec_sync(script) + interp.exec(script) out = file.read() self.assertEqual(out, 'it worked!') def test_failure(self): interp = interpreters.create() - with self.assertRaises(interpreters.ExecFailure): - interp.exec_sync('raise Exception') + with self.assertRaises(interpreters.ExecutionFailed): + interp.exec('raise Exception') def test_display_preserved_exception(self): tempdir = self.temp_dir() @@ -542,21 +542,21 @@ def script(): spam.eggs() interp = interpreters.create() - interp.exec_sync(script) + interp.exec(script) """) stdout, stderr = self.assert_python_failure(scriptfile) self.maxDiff = None - interpmod_line, = (l for l in stderr.splitlines() if ' exec_sync' in l) - # File "{interpreters.__file__}", line 179, in exec_sync + interpmod_line, = (l for l in stderr.splitlines() if ' exec' in l) + # File "{interpreters.__file__}", line 179, in exec self.assertEqual(stderr, dedent(f"""\ Traceback (most recent call last): File "{scriptfile}", line 9, in <module> - interp.exec_sync(script) - ~~~~~~~~~~~~~~~~^^^^^^^^ + interp.exec(script) + ~~~~~~~~~~~^^^^^^^^ {interpmod_line.strip()} - raise ExecFailure(excinfo) - test.support.interpreters.ExecFailure: RuntimeError: uh-oh! + raise ExecutionFailed(excinfo) + test.support.interpreters.ExecutionFailed: RuntimeError: uh-oh! Uncaught in the interpreter: @@ -578,7 +578,7 @@ def test_in_thread(self): script, file = _captured_script('print("it worked!", end="")') with file: def f(): - interp.exec_sync(script) + interp.exec(script) t = threading.Thread(target=f) t.start() @@ -604,7 +604,7 @@ def test_fork(self): with open('{file.name}', 'w', encoding='utf-8') as out: out.write('{expected}') """) - interp.exec_sync(script) + interp.exec(script) file.seek(0) content = file.read() @@ -615,17 +615,17 @@ def test_already_running(self): interp = interpreters.create() with _running(interp): with self.assertRaises(RuntimeError): - interp.exec_sync('print("spam")') + interp.exec('print("spam")') def test_bad_script(self): interp = interpreters.create() with self.assertRaises(TypeError): - interp.exec_sync(10) + interp.exec(10) def test_bytes_for_script(self): interp = interpreters.create() with self.assertRaises(TypeError): - interp.exec_sync(b'print("spam")') + interp.exec(b'print("spam")') def test_with_background_threads_still_running(self): r_interp, w_interp = self.pipe() @@ -636,7 +636,7 @@ def test_with_background_threads_still_running(self): FINISHED = b'F' interp = interpreters.create() - interp.exec_sync(f"""if True: + interp.exec(f"""if True: import os import threading @@ -648,46 +648,229 @@ def task(): t.start() os.write({w_interp}, {RAN!r}) """) - interp.exec_sync(f"""if True: + interp.exec(f"""if True: os.write({w_interp}, {RAN!r}) """) os.write(w_thread, DONE) - interp.exec_sync('t.join()') + interp.exec('t.join()') self.assertEqual(os.read(r_interp, 1), RAN) self.assertEqual(os.read(r_interp, 1), RAN) self.assertEqual(os.read(r_interp, 1), FINISHED) # test_xxsubinterpreters covers the remaining - # Interpreter.exec_sync() behavior. + # Interpreter.exec() behavior. -class TestInterpreterRun(TestBase): - - def test_success(self): - interp = interpreters.create() - script, file = _captured_script('print("it worked!", end="")') - with file: - t = interp.run(script) +def call_func_noop(): + pass + + +def call_func_return_shareable(): + return (1, None) + + +def call_func_return_not_shareable(): + return [1, 2, 3] + + +def call_func_failure(): + raise Exception('spam!') + + +def call_func_ident(value): + return value + + +def get_call_func_closure(value): + def call_func_closure(): + return value + return call_func_closure + + +class Spam: + + @staticmethod + def noop(): + pass + + @classmethod + def from_values(cls, *values): + return cls(values) + + def __init__(self, value): + self.value = value + + def __call__(self, *args, **kwargs): + return (self.value, args, kwargs) + + def __eq__(self, other): + if not isinstance(other, Spam): + return NotImplemented + return self.value == other.value + + def run(self, *args, **kwargs): + return (self.value, args, kwargs) + + +def call_func_complex(op, /, value=None, *args, exc=None, **kwargs): + if exc is not None: + raise exc + if op == '': + raise ValueError('missing op') + elif op == 'ident': + if args or kwargs: + raise Exception((args, kwargs)) + return value + elif op == 'full-ident': + return (value, args, kwargs) + elif op == 'globals': + if value is not None or args or kwargs: + raise Exception((value, args, kwargs)) + return __name__ + elif op == 'interpid': + if value is not None or args or kwargs: + raise Exception((value, args, kwargs)) + return interpreters.get_current().id + elif op == 'closure': + if args or kwargs: + raise Exception((args, kwargs)) + return get_call_func_closure(value) + elif op == 'custom': + if args or kwargs: + raise Exception((args, kwargs)) + return Spam(value) + elif op == 'custom-inner': + if args or kwargs: + raise Exception((args, kwargs)) + class Eggs(Spam): + pass + return Eggs(value) + elif not isinstance(op, str): + raise TypeError(op) + else: + raise NotImplementedError(op) + + +class TestInterpreterCall(TestBase): + + # signature + # - blank + # - args + # - kwargs + # - args, kwargs + # return + # - nothing (None) + # - simple + # - closure + # - custom + # ops: + # - do nothing + # - fail + # - echo + # - do complex, relative to interpreter + # scope + # - global func + # - local closure + # - returned closure + # - callable type instance + # - type + # - classmethod + # - staticmethod + # - instance method + # exception + # - builtin + # - custom + # - preserves info (e.g. SyntaxError) + # - matching error display + + def test_call(self): + interp = interpreters.create() + + for i, (callable, args, kwargs) in enumerate([ + (call_func_noop, (), {}), + (call_func_return_shareable, (), {}), + (call_func_return_not_shareable, (), {}), + (Spam.noop, (), {}), + ]): + with self.subTest(f'success case #{i+1}'): + res = interp.call(callable) + self.assertIs(res, None) + + for i, (callable, args, kwargs) in enumerate([ + (call_func_ident, ('spamspamspam',), {}), + (get_call_func_closure, (42,), {}), + (get_call_func_closure(42), (), {}), + (Spam.from_values, (), {}), + (Spam.from_values, (1, 2, 3), {}), + (Spam, ('???'), {}), + (Spam(101), (), {}), + (Spam(10101).run, (), {}), + (call_func_complex, ('ident', 'spam'), {}), + (call_func_complex, ('full-ident', 'spam'), {}), + (call_func_complex, ('full-ident', 'spam', 'ham'), {'eggs': '!!!'}), + (call_func_complex, ('globals',), {}), + (call_func_complex, ('interpid',), {}), + (call_func_complex, ('closure',), {'value': '~~~'}), + (call_func_complex, ('custom', 'spam!'), {}), + (call_func_complex, ('custom-inner', 'eggs!'), {}), + (call_func_complex, ('???',), {'exc': ValueError('spam')}), + ]): + with self.subTest(f'invalid case #{i+1}'): + with self.assertRaises(Exception): + if args or kwargs: + raise Exception((args, kwargs)) + interp.call(callable) + + with self.assertRaises(interpreters.ExecutionFailed): + interp.call(call_func_failure) + + def test_call_in_thread(self): + interp = interpreters.create() + + for i, (callable, args, kwargs) in enumerate([ + (call_func_noop, (), {}), + (call_func_return_shareable, (), {}), + (call_func_return_not_shareable, (), {}), + (Spam.noop, (), {}), + ]): + with self.subTest(f'success case #{i+1}'): + with self.captured_thread_exception() as ctx: + t = interp.call_in_thread(callable) + t.join() + self.assertIsNone(ctx.caught) + + for i, (callable, args, kwargs) in enumerate([ + (call_func_ident, ('spamspamspam',), {}), + (get_call_func_closure, (42,), {}), + (get_call_func_closure(42), (), {}), + (Spam.from_values, (), {}), + (Spam.from_values, (1, 2, 3), {}), + (Spam, ('???'), {}), + (Spam(101), (), {}), + (Spam(10101).run, (), {}), + (call_func_complex, ('ident', 'spam'), {}), + (call_func_complex, ('full-ident', 'spam'), {}), + (call_func_complex, ('full-ident', 'spam', 'ham'), {'eggs': '!!!'}), + (call_func_complex, ('globals',), {}), + (call_func_complex, ('interpid',), {}), + (call_func_complex, ('closure',), {'value': '~~~'}), + (call_func_complex, ('custom', 'spam!'), {}), + (call_func_complex, ('custom-inner', 'eggs!'), {}), + (call_func_complex, ('???',), {'exc': ValueError('spam')}), + ]): + with self.subTest(f'invalid case #{i+1}'): + if args or kwargs: + continue + with self.captured_thread_exception() as ctx: + t = interp.call_in_thread(callable) + t.join() + self.assertIsNotNone(ctx.caught) + + with self.captured_thread_exception() as ctx: + t = interp.call_in_thread(call_func_failure) t.join() - out = file.read() - - self.assertEqual(out, 'it worked!') - - def test_failure(self): - caught = False - def excepthook(args): - nonlocal caught - caught = True - threading.excepthook = excepthook - try: - interp = interpreters.create() - t = interp.run('raise Exception') - t.join() - - self.assertTrue(caught) - except BaseException: - threading.excepthook = threading.__excepthook__ + self.assertIsNotNone(ctx.caught) class TestIsShareable(TestBase): diff --git a/Lib/test/test_interpreters/test_channels.py b/Lib/test/test_interpreters/test_channels.py index 3c3e18832d41683..07e503837bcf75b 100644 --- a/Lib/test/test_interpreters/test_channels.py +++ b/Lib/test/test_interpreters/test_channels.py @@ -120,7 +120,7 @@ def test_send_recv_main(self): def test_send_recv_same_interpreter(self): interp = interpreters.create() - interp.exec_sync(dedent(""" + interp.exec(dedent(""" from test.support.interpreters import channels r, s = channels.create() orig = b'spam' @@ -193,7 +193,7 @@ def test_send_recv_nowait_main_with_default(self): def test_send_recv_nowait_same_interpreter(self): interp = interpreters.create() - interp.exec_sync(dedent(""" + interp.exec(dedent(""" from test.support.interpreters import channels r, s = channels.create() orig = b'spam' diff --git a/Lib/test/test_interpreters/test_lifecycle.py b/Lib/test/test_interpreters/test_lifecycle.py index c2917d839904f91..67b6f439c3191fc 100644 --- a/Lib/test/test_interpreters/test_lifecycle.py +++ b/Lib/test/test_interpreters/test_lifecycle.py @@ -124,7 +124,7 @@ def test_sys_path_0(self): orig = sys.path[0] interp = interpreters.create() - interp.exec_sync(f"""if True: + interp.exec(f"""if True: import json import sys print(json.dumps({{ diff --git a/Lib/test/test_interpreters/test_queues.py b/Lib/test/test_interpreters/test_queues.py index 2a8ca99c1f6e3f3..65b5435fb00b04b 100644 --- a/Lib/test/test_interpreters/test_queues.py +++ b/Lib/test/test_interpreters/test_queues.py @@ -51,20 +51,20 @@ def test_shareable(self): queue1 = queues.create() interp = interpreters.create() - interp.exec_sync(dedent(f""" + interp.exec(dedent(f""" from test.support.interpreters import queues queue1 = queues.Queue({queue1.id}) """)); with self.subTest('same interpreter'): queue2 = queues.create() - queue1.put(queue2) + queue1.put(queue2, syncobj=True) queue3 = queue1.get() self.assertIs(queue3, queue2) with self.subTest('from current interpreter'): queue4 = queues.create() - queue1.put(queue4) + queue1.put(queue4, syncobj=True) out = _run_output(interp, dedent(""" queue4 = queue1.get() print(queue4.id) @@ -75,7 +75,7 @@ def test_shareable(self): with self.subTest('from subinterpreter'): out = _run_output(interp, dedent(""" queue5 = queues.create() - queue1.put(queue5) + queue1.put(queue5, syncobj=True) print(queue5.id) """)) qid = int(out) @@ -118,7 +118,7 @@ class TestQueueOps(TestBase): def test_empty(self): queue = queues.create() before = queue.empty() - queue.put(None) + queue.put(None, syncobj=True) during = queue.empty() queue.get() after = queue.empty() @@ -133,7 +133,7 @@ def test_full(self): queue = queues.create(3) for _ in range(3): actual.append(queue.full()) - queue.put(None) + queue.put(None, syncobj=True) actual.append(queue.full()) for _ in range(3): queue.get() @@ -147,16 +147,16 @@ def test_qsize(self): queue = queues.create() for _ in range(3): actual.append(queue.qsize()) - queue.put(None) + queue.put(None, syncobj=True) actual.append(queue.qsize()) queue.get() actual.append(queue.qsize()) - queue.put(None) + queue.put(None, syncobj=True) actual.append(queue.qsize()) for _ in range(3): queue.get() actual.append(queue.qsize()) - queue.put(None) + queue.put(None, syncobj=True) actual.append(queue.qsize()) queue.get() actual.append(queue.qsize()) @@ -165,30 +165,81 @@ def test_qsize(self): def test_put_get_main(self): expected = list(range(20)) - queue = queues.create() - for i in range(20): - queue.put(i) - actual = [queue.get() for _ in range(20)] + for syncobj in (True, False): + kwds = dict(syncobj=syncobj) + with self.subTest(f'syncobj={syncobj}'): + queue = queues.create() + for i in range(20): + queue.put(i, **kwds) + actual = [queue.get() for _ in range(20)] - self.assertEqual(actual, expected) + self.assertEqual(actual, expected) def test_put_timeout(self): - queue = queues.create(2) - queue.put(None) - queue.put(None) - with self.assertRaises(queues.QueueFull): - queue.put(None, timeout=0.1) - queue.get() - queue.put(None) + for syncobj in (True, False): + kwds = dict(syncobj=syncobj) + with self.subTest(f'syncobj={syncobj}'): + queue = queues.create(2) + queue.put(None, **kwds) + queue.put(None, **kwds) + with self.assertRaises(queues.QueueFull): + queue.put(None, timeout=0.1, **kwds) + queue.get() + queue.put(None, **kwds) def test_put_nowait(self): - queue = queues.create(2) - queue.put_nowait(None) - queue.put_nowait(None) - with self.assertRaises(queues.QueueFull): - queue.put_nowait(None) - queue.get() - queue.put_nowait(None) + for syncobj in (True, False): + kwds = dict(syncobj=syncobj) + with self.subTest(f'syncobj={syncobj}'): + queue = queues.create(2) + queue.put_nowait(None, **kwds) + queue.put_nowait(None, **kwds) + with self.assertRaises(queues.QueueFull): + queue.put_nowait(None, **kwds) + queue.get() + queue.put_nowait(None, **kwds) + + def test_put_syncobj(self): + for obj in [ + None, + True, + 10, + 'spam', + b'spam', + (0, 'a'), + ]: + with self.subTest(repr(obj)): + queue = queues.create() + queue.put(obj, syncobj=True) + obj2 = queue.get() + self.assertEqual(obj2, obj) + + for obj in [ + [1, 2, 3], + {'a': 13, 'b': 17}, + ]: + with self.subTest(repr(obj)): + queue = queues.create() + with self.assertRaises(interpreters.NotShareableError): + queue.put(obj, syncobj=True) + + def test_put_not_syncobj(self): + for obj in [ + None, + True, + 10, + 'spam', + b'spam', + (0, 'a'), + # not shareable + [1, 2, 3], + {'a': 13, 'b': 17}, + ]: + with self.subTest(repr(obj)): + queue = queues.create() + queue.put(obj, syncobj=False) + obj2 = queue.get() + self.assertEqual(obj2, obj) def test_get_timeout(self): queue = queues.create() @@ -200,13 +251,41 @@ def test_get_nowait(self): with self.assertRaises(queues.QueueEmpty): queue.get_nowait() + def test_put_get_default_syncobj(self): + expected = list(range(20)) + queue = queues.create(syncobj=True) + for i in range(20): + queue.put(i) + actual = [queue.get() for _ in range(20)] + + self.assertEqual(actual, expected) + + obj = [1, 2, 3] # lists are not shareable + with self.assertRaises(interpreters.NotShareableError): + queue.put(obj) + + def test_put_get_default_not_syncobj(self): + expected = list(range(20)) + queue = queues.create(syncobj=False) + for i in range(20): + queue.put(i) + actual = [queue.get() for _ in range(20)] + + self.assertEqual(actual, expected) + + obj = [1, 2, 3] # lists are not shareable + queue.put(obj) + obj2 = queue.get() + self.assertEqual(obj, obj2) + self.assertIsNot(obj, obj2) + def test_put_get_same_interpreter(self): interp = interpreters.create() - interp.exec_sync(dedent(""" + interp.exec(dedent(""" from test.support.interpreters import queues queue = queues.create() orig = b'spam' - queue.put(orig) + queue.put(orig, syncobj=True) obj = queue.get() assert obj == orig, 'expected: obj == orig' assert obj is not orig, 'expected: obj is not orig' @@ -219,7 +298,7 @@ def test_put_get_different_interpreters(self): self.assertEqual(len(queues.list_all()), 2) obj1 = b'spam' - queue1.put(obj1) + queue1.put(obj1, syncobj=True) out = _run_output( interp, @@ -236,7 +315,7 @@ def test_put_get_different_interpreters(self): obj2 = b'eggs' print(id(obj2)) assert queue2.qsize() == 0, 'expected: queue2.qsize() == 0' - queue2.put(obj2) + queue2.put(obj2, syncobj=True) assert queue2.qsize() == 1, 'expected: queue2.qsize() == 1' """)) self.assertEqual(len(queues.list_all()), 2) @@ -258,8 +337,8 @@ def test_put_cleared_with_subinterpreter(self): queue = queues.Queue({queue.id}) obj1 = b'spam' obj2 = b'eggs' - queue.put(obj1) - queue.put(obj2) + queue.put(obj1, syncobj=True) + queue.put(obj2, syncobj=True) """)) self.assertEqual(queue.qsize(), 2) @@ -281,12 +360,12 @@ def f(): break except queues.QueueEmpty: continue - queue2.put(obj) + queue2.put(obj, syncobj=True) t = threading.Thread(target=f) t.start() orig = b'spam' - queue1.put(orig) + queue1.put(orig, syncobj=True) obj = queue2.get() t.join() diff --git a/Lib/test/test_interpreters/utils.py b/Lib/test/test_interpreters/utils.py index 3a37ed09dd89434..973d05d4f96dcb8 100644 --- a/Lib/test/test_interpreters/utils.py +++ b/Lib/test/test_interpreters/utils.py @@ -4,8 +4,9 @@ import subprocess import sys import tempfile -import threading from textwrap import dedent +import threading +import types import unittest from test import support @@ -41,7 +42,7 @@ def _run_output(interp, request, init=None): with rpipe: if init: interp.prepare_main(init) - interp.exec_sync(script) + interp.exec(script) return rpipe.read() @@ -49,7 +50,7 @@ def _run_output(interp, request, init=None): def _running(interp): r, w = os.pipe() def run(): - interp.exec_sync(dedent(f""" + interp.exec(dedent(f""" # wait for "signal" with open({r}) as rpipe: rpipe.read() @@ -84,6 +85,18 @@ def temp_dir(self): self.addCleanup(lambda: os_helper.rmtree(tempdir)) return tempdir + @contextlib.contextmanager + def captured_thread_exception(self): + ctx = types.SimpleNamespace(caught=None) + def excepthook(args): + ctx.caught = args + orig_excepthook = threading.excepthook + threading.excepthook = excepthook + try: + yield ctx + finally: + threading.excepthook = orig_excepthook + def make_script(self, filename, dirname=None, text=None): if text: text = dedent(text) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 71671a5a984256d..38dcabd84d8170a 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -729,7 +729,7 @@ def test_subinterp_intern_dynamically_allocated(self): self.assertIs(t, s) interp = interpreters.create() - interp.exec_sync(textwrap.dedent(f''' + interp.exec(textwrap.dedent(f''' import sys t = sys.intern({s!r}) assert id(t) != {id(s)}, (id(t), {id(s)}) @@ -744,7 +744,7 @@ def test_subinterp_intern_statically_allocated(self): t = sys.intern(s) interp = interpreters.create() - interp.exec_sync(textwrap.dedent(f''' + interp.exec(textwrap.dedent(f''' import sys t = sys.intern({s!r}) assert id(t) == {id(t)}, (id(t), {id(t)}) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 1ab223b81e939e5..3b5c37c948c8c34 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1478,7 +1478,7 @@ def test_threads_join_with_no_main(self): DONE = b'D' interp = interpreters.create() - interp.exec_sync(f"""if True: + interp.exec(f"""if True: import os import threading import time diff --git a/Modules/_xxinterpqueuesmodule.c b/Modules/_xxinterpqueuesmodule.c index 7d8c67f49fefb85..715bb766cac624e 100644 --- a/Modules/_xxinterpqueuesmodule.c +++ b/Modules/_xxinterpqueuesmodule.c @@ -294,6 +294,8 @@ handle_queue_error(int err, PyObject *mod, int64_t qid) case ERR_QUEUES_ALLOC: PyErr_NoMemory(); break; + case -1: + return -1; default: state = get_module_state(mod); assert(state->QueueError != NULL); @@ -320,14 +322,17 @@ struct _queueitem; typedef struct _queueitem { _PyCrossInterpreterData *data; + int fmt; struct _queueitem *next; } _queueitem; static void -_queueitem_init(_queueitem *item, _PyCrossInterpreterData *data) +_queueitem_init(_queueitem *item, + _PyCrossInterpreterData *data, int fmt) { *item = (_queueitem){ .data = data, + .fmt = fmt, }; } @@ -344,14 +349,14 @@ _queueitem_clear(_queueitem *item) } static _queueitem * -_queueitem_new(_PyCrossInterpreterData *data) +_queueitem_new(_PyCrossInterpreterData *data, int fmt) { _queueitem *item = GLOBAL_MALLOC(_queueitem); if (item == NULL) { PyErr_NoMemory(); return NULL; } - _queueitem_init(item, data); + _queueitem_init(item, data, fmt); return item; } @@ -373,9 +378,11 @@ _queueitem_free_all(_queueitem *item) } static void -_queueitem_popped(_queueitem *item, _PyCrossInterpreterData **p_data) +_queueitem_popped(_queueitem *item, + _PyCrossInterpreterData **p_data, int *p_fmt) { *p_data = item->data; + *p_fmt = item->fmt; // We clear them here, so they won't be released in _queueitem_clear(). item->data = NULL; _queueitem_free(item); @@ -393,10 +400,11 @@ typedef struct _queue { _queueitem *first; _queueitem *last; } items; + int fmt; } _queue; static int -_queue_init(_queue *queue, Py_ssize_t maxsize) +_queue_init(_queue *queue, Py_ssize_t maxsize, int fmt) { PyThread_type_lock mutex = PyThread_allocate_lock(); if (mutex == NULL) { @@ -408,6 +416,7 @@ _queue_init(_queue *queue, Py_ssize_t maxsize) .items = { .maxsize = maxsize, }, + .fmt = fmt, }; return 0; } @@ -486,7 +495,7 @@ _queue_unlock(_queue *queue) } static int -_queue_add(_queue *queue, _PyCrossInterpreterData *data) +_queue_add(_queue *queue, _PyCrossInterpreterData *data, int fmt) { int err = _queue_lock(queue); if (err < 0) { @@ -502,7 +511,7 @@ _queue_add(_queue *queue, _PyCrossInterpreterData *data) return ERR_QUEUE_FULL; } - _queueitem *item = _queueitem_new(data); + _queueitem *item = _queueitem_new(data, fmt); if (item == NULL) { _queue_unlock(queue); return -1; @@ -522,7 +531,8 @@ _queue_add(_queue *queue, _PyCrossInterpreterData *data) } static int -_queue_next(_queue *queue, _PyCrossInterpreterData **p_data) +_queue_next(_queue *queue, + _PyCrossInterpreterData **p_data, int *p_fmt) { int err = _queue_lock(queue); if (err < 0) { @@ -541,7 +551,7 @@ _queue_next(_queue *queue, _PyCrossInterpreterData **p_data) } queue->items.count -= 1; - _queueitem_popped(item, p_data); + _queueitem_popped(item, p_data, p_fmt); _queue_unlock(queue); return 0; @@ -843,18 +853,26 @@ _queues_decref(_queues *queues, int64_t qid) PyThread_release_lock(queues->mutex); } -static int64_t * +struct queue_id_and_fmt { + int64_t id; + int fmt; +}; + +static struct queue_id_and_fmt * _queues_list_all(_queues *queues, int64_t *count) { - int64_t *qids = NULL; + struct queue_id_and_fmt *qids = NULL; PyThread_acquire_lock(queues->mutex, WAIT_LOCK); - int64_t *ids = PyMem_NEW(int64_t, (Py_ssize_t)(queues->count)); + struct queue_id_and_fmt *ids = PyMem_NEW(struct queue_id_and_fmt, + (Py_ssize_t)(queues->count)); if (ids == NULL) { goto done; } _queueref *ref = queues->head; for (int64_t i=0; ref != NULL; ref = ref->next, i++) { - ids[i] = ref->qid; + ids[i].id = ref->qid; + assert(ref->queue != NULL); + ids[i].fmt = ref->queue->fmt; } *count = queues->count; @@ -890,13 +908,13 @@ _queue_free(_queue *queue) // Create a new queue. static int64_t -queue_create(_queues *queues, Py_ssize_t maxsize) +queue_create(_queues *queues, Py_ssize_t maxsize, int fmt) { _queue *queue = GLOBAL_MALLOC(_queue); if (queue == NULL) { return ERR_QUEUE_ALLOC; } - int err = _queue_init(queue, maxsize); + int err = _queue_init(queue, maxsize, fmt); if (err < 0) { GLOBAL_FREE(queue); return (int64_t)err; @@ -925,7 +943,7 @@ queue_destroy(_queues *queues, int64_t qid) // Push an object onto the queue. static int -queue_put(_queues *queues, int64_t qid, PyObject *obj) +queue_put(_queues *queues, int64_t qid, PyObject *obj, int fmt) { // Look up the queue. _queue *queue = NULL; @@ -948,7 +966,7 @@ queue_put(_queues *queues, int64_t qid, PyObject *obj) } // Add the data to the queue. - int res = _queue_add(queue, data); + int res = _queue_add(queue, data, fmt); _queue_unmark_waiter(queue, queues->mutex); if (res != 0) { // We may chain an exception here: @@ -963,7 +981,7 @@ queue_put(_queues *queues, int64_t qid, PyObject *obj) // Pop the next object off the queue. Fail if empty. // XXX Support a "wait" mutex? static int -queue_get(_queues *queues, int64_t qid, PyObject **res) +queue_get(_queues *queues, int64_t qid, PyObject **res, int *p_fmt) { int err; *res = NULL; @@ -979,7 +997,7 @@ queue_get(_queues *queues, int64_t qid, PyObject **res) // Pop off the next item from the queue. _PyCrossInterpreterData *data = NULL; - err = _queue_next(queue, &data); + err = _queue_next(queue, &data, p_fmt); _queue_unmark_waiter(queue, queues->mutex); if (err != 0) { return err; @@ -1267,14 +1285,15 @@ qidarg_converter(PyObject *arg, void *ptr) static PyObject * queuesmod_create(PyObject *self, PyObject *args, PyObject *kwds) { - static char *kwlist[] = {"maxsize", NULL}; - Py_ssize_t maxsize = -1; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|n:create", kwlist, - &maxsize)) { + static char *kwlist[] = {"maxsize", "fmt", NULL}; + Py_ssize_t maxsize; + int fmt; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ni:create", kwlist, + &maxsize, &fmt)) { return NULL; } - int64_t qid = queue_create(&_globals.queues, maxsize); + int64_t qid = queue_create(&_globals.queues, maxsize, fmt); if (qid < 0) { (void)handle_queue_error((int)qid, self, qid); return NULL; @@ -1329,7 +1348,7 @@ static PyObject * queuesmod_list_all(PyObject *self, PyObject *Py_UNUSED(ignored)) { int64_t count = 0; - int64_t *qids = _queues_list_all(&_globals.queues, &count); + struct queue_id_and_fmt *qids = _queues_list_all(&_globals.queues, &count); if (qids == NULL) { if (count == 0) { return PyList_New(0); @@ -1340,14 +1359,14 @@ queuesmod_list_all(PyObject *self, PyObject *Py_UNUSED(ignored)) if (ids == NULL) { goto finally; } - int64_t *cur = qids; + struct queue_id_and_fmt *cur = qids; for (int64_t i=0; i < count; cur++, i++) { - PyObject *qidobj = PyLong_FromLongLong(*cur); - if (qidobj == NULL) { + PyObject *item = Py_BuildValue("Li", cur->id, cur->fmt); + if (item == NULL) { Py_SETREF(ids, NULL); break; } - PyList_SET_ITEM(ids, (Py_ssize_t)i, qidobj); + PyList_SET_ITEM(ids, (Py_ssize_t)i, item); } finally: @@ -1363,17 +1382,18 @@ Return the list of IDs for all queues."); static PyObject * queuesmod_put(PyObject *self, PyObject *args, PyObject *kwds) { - static char *kwlist[] = {"qid", "obj", NULL}; + static char *kwlist[] = {"qid", "obj", "fmt", NULL}; qidarg_converter_data qidarg; PyObject *obj; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&O:put", kwlist, - qidarg_converter, &qidarg, &obj)) { + int fmt; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&Oi:put", kwlist, + qidarg_converter, &qidarg, &obj, &fmt)) { return NULL; } int64_t qid = qidarg.id; /* Queue up the object. */ - int err = queue_put(&_globals.queues, qid, obj); + int err = queue_put(&_globals.queues, qid, obj, fmt); if (handle_queue_error(err, self, qid)) { return NULL; } @@ -1382,7 +1402,7 @@ queuesmod_put(PyObject *self, PyObject *args, PyObject *kwds) } PyDoc_STRVAR(queuesmod_put_doc, -"put(qid, obj)\n\ +"put(qid, obj, sharedonly=False)\n\ \n\ Add the object's data to the queue."); @@ -1399,7 +1419,8 @@ queuesmod_get(PyObject *self, PyObject *args, PyObject *kwds) int64_t qid = qidarg.id; PyObject *obj = NULL; - int err = queue_get(&_globals.queues, qid, &obj); + int fmt; + int err = queue_get(&_globals.queues, qid, &obj, &fmt); if (err == ERR_QUEUE_EMPTY && dflt != NULL) { assert(obj == NULL); obj = Py_NewRef(dflt); @@ -1407,7 +1428,10 @@ queuesmod_get(PyObject *self, PyObject *args, PyObject *kwds) else if (handle_queue_error(err, self, qid)) { return NULL; } - return obj; + + PyObject *res = Py_BuildValue("Oi", obj, fmt); + Py_DECREF(obj); + return res; } PyDoc_STRVAR(queuesmod_get_doc, @@ -1499,6 +1523,33 @@ PyDoc_STRVAR(queuesmod_get_maxsize_doc, \n\ Return the maximum number of items in the queue."); +static PyObject * +queuesmod_get_default_fmt(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"qid", NULL}; + qidarg_converter_data qidarg; + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "O&:get_default_fmt", kwlist, + qidarg_converter, &qidarg)) { + return NULL; + } + int64_t qid = qidarg.id; + + _queue *queue = NULL; + int err = _queues_lookup(&_globals.queues, qid, &queue); + if (handle_queue_error(err, self, qid)) { + return NULL; + } + int fmt = queue->fmt; + _queue_unmark_waiter(queue, _globals.queues.mutex); + return PyLong_FromLong(fmt); +} + +PyDoc_STRVAR(queuesmod_get_default_fmt_doc, +"get_default_fmt(qid)\n\ +\n\ +Return the default format to use for the queue."); + static PyObject * queuesmod_is_full(PyObject *self, PyObject *args, PyObject *kwds) { @@ -1593,6 +1644,8 @@ static PyMethodDef module_functions[] = { METH_VARARGS | METH_KEYWORDS, queuesmod_release_doc}, {"get_maxsize", _PyCFunction_CAST(queuesmod_get_maxsize), METH_VARARGS | METH_KEYWORDS, queuesmod_get_maxsize_doc}, + {"get_default_fmt", _PyCFunction_CAST(queuesmod_get_default_fmt), + METH_VARARGS | METH_KEYWORDS, queuesmod_get_default_fmt_doc}, {"is_full", _PyCFunction_CAST(queuesmod_is_full), METH_VARARGS | METH_KEYWORDS, queuesmod_is_full_doc}, {"get_count", _PyCFunction_CAST(queuesmod_get_count), diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index b4004d165078f71..28c2f9c08bc0daf 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -902,6 +902,56 @@ The code/function must not take any arguments or be a closure\n\ If a function is provided, its code object is used and all its state\n\ is ignored, including its __globals__ dict."); +static PyObject * +interp_call(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"id", "callable", "args", "kwargs", NULL}; + PyObject *id, *callable; + PyObject *args_obj = NULL; + PyObject *kwargs_obj = NULL; + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "OO|OO:" MODULE_NAME_STR ".call", kwlist, + &id, &callable, &args_obj, &kwargs_obj)) { + return NULL; + } + + if (args_obj != NULL) { + PyErr_SetString(PyExc_ValueError, "got unexpected args"); + return NULL; + } + if (kwargs_obj != NULL) { + PyErr_SetString(PyExc_ValueError, "got unexpected kwargs"); + return NULL; + } + + PyObject *code = (PyObject *)convert_code_arg(callable, MODULE_NAME_STR ".call", + "argument 2", "a function"); + if (code == NULL) { + return NULL; + } + + PyObject *excinfo = NULL; + int res = _interp_exec(self, id, code, NULL, &excinfo); + Py_DECREF(code); + if (res < 0) { + assert((excinfo == NULL) != (PyErr_Occurred() == NULL)); + return excinfo; + } + Py_RETURN_NONE; +} + +PyDoc_STRVAR(call_doc, +"call(id, callable, args=None, kwargs=None)\n\ +\n\ +Call the provided object in the identified interpreter.\n\ +Pass the given args and kwargs, if possible.\n\ +\n\ +\"callable\" may be a plain function with no free vars that takes\n\ +no arguments.\n\ +\n\ +The function's code object is used and all its state\n\ +is ignored, including its __globals__ dict."); + static PyObject * interp_run_string(PyObject *self, PyObject *args, PyObject *kwds) { @@ -1085,6 +1135,8 @@ static PyMethodDef module_functions[] = { METH_VARARGS | METH_KEYWORDS, is_running_doc}, {"exec", _PyCFunction_CAST(interp_exec), METH_VARARGS | METH_KEYWORDS, exec_doc}, + {"call", _PyCFunction_CAST(interp_call), + METH_VARARGS | METH_KEYWORDS, call_doc}, {"run_string", _PyCFunction_CAST(interp_run_string), METH_VARARGS | METH_KEYWORDS, run_string_doc}, {"run_func", _PyCFunction_CAST(interp_run_func), @@ -1113,6 +1165,7 @@ The 'interpreters' module provides a more convenient interface."); static int module_exec(PyObject *mod) { + PyInterpreterState *interp = PyInterpreterState_Get(); module_state *state = get_module_state(mod); // exceptions @@ -1122,6 +1175,11 @@ module_exec(PyObject *mod) if (PyModule_AddType(mod, (PyTypeObject *)PyExc_InterpreterNotFoundError) < 0) { goto error; } + PyObject *PyExc_NotShareableError = \ + _PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError; + if (PyModule_AddType(mod, (PyTypeObject *)PyExc_NotShareableError) < 0) { + goto error; + } if (register_memoryview_xid(mod, &state->XIBufferViewType) < 0) { goto error; From f484a2a7486d0b4c7c11901f6c668eb23b74e81f Mon Sep 17 00:00:00 2001 From: Raymond Hettinger <rhettinger@users.noreply.github.com> Date: Wed, 28 Feb 2024 17:11:05 -0600 Subject: [PATCH 503/507] Update an out-of-date example in the itertools recipe intro (gh-116082) --- Doc/library/itertools.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 4e731fefe8908d3..c26f6c89b4920a1 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -778,7 +778,7 @@ The primary purpose of the itertools recipes is educational. The recipes show various ways of thinking about individual tools — for example, that ``chain.from_iterable`` is related to the concept of flattening. The recipes also give ideas about ways that the tools can be combined — for example, how -``compress()`` and ``range()`` can work together. The recipes also show patterns +``starmap()`` and ``repeat()`` can work together. The recipes also show patterns for using itertools with the :mod:`operator` and :mod:`collections` modules as well as with the built-in itertools such as ``map()``, ``filter()``, ``reversed()``, and ``enumerate()``. From 4d1d35b906010c6db15f54443a9701c20af1db2d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Thu, 29 Feb 2024 00:16:01 +0100 Subject: [PATCH 504/507] gh-116075: Skip test_external_inspection on qemu in JIT CI (#116076) --- .github/workflows/jit.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml index 21d4603b8679ea8..43d0b2c1b4016ca 100644 --- a/.github/workflows/jit.yml +++ b/.github/workflows/jit.yml @@ -70,13 +70,13 @@ jobs: runner: ubuntu-latest compiler: gcc # These fail because of emulation, not because of the JIT: - exclude: test_unix_events test_init test_process_pool test_shutdown test_multiprocessing_fork test_cmd_line test_faulthandler test_os test_perf_profiler test_posix test_signal test_socket test_subprocess test_threading test_venv + exclude: test_unix_events test_init test_process_pool test_shutdown test_multiprocessing_fork test_cmd_line test_faulthandler test_os test_perf_profiler test_posix test_signal test_socket test_subprocess test_threading test_venv test_external_inspection - target: aarch64-unknown-linux-gnu/clang architecture: aarch64 runner: ubuntu-latest compiler: clang # These fail because of emulation, not because of the JIT: - exclude: test_unix_events test_init test_process_pool test_shutdown test_multiprocessing_fork test_cmd_line test_faulthandler test_os test_perf_profiler test_posix test_signal test_socket test_subprocess test_threading test_venv + exclude: test_unix_events test_init test_process_pool test_shutdown test_multiprocessing_fork test_cmd_line test_faulthandler test_os test_perf_profiler test_posix test_signal test_socket test_subprocess test_threading test_venv test_external_inspection env: CC: ${{ matrix.compiler }} steps: From 3ea78fd5bc93fc339ef743e6a5dfde35f04d972e Mon Sep 17 00:00:00 2001 From: Ethan Furman <ethan@stoneleaf.us> Date: Wed, 28 Feb 2024 15:17:49 -0800 Subject: [PATCH 505/507] gh-115821: [Enum] better error message for calling super().__new__() (GH-116063) docs now state to not call super().__new__ if super().__new__ is called, a better error message is now used --- Doc/library/enum.rst | 3 +++ Lib/enum.py | 5 +++++ Lib/test/test_enum.py | 13 ++++++++++++- .../2024-02-28-12-14-31.gh-issue-115821.YO2vKA.rst | 2 ++ 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-28-12-14-31.gh-issue-115821.YO2vKA.rst diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index 30d80ce8d488ccb..6e7de004cd52a1a 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -400,6 +400,9 @@ Data Types results in the call ``int('1a', 16)`` and a value of ``17`` for the member. + ..note:: When writing a custom ``__new__``, do not use ``super().__new__`` -- + call the appropriate ``__new__`` instead. + .. method:: Enum.__repr__(self) Returns the string used for *repr()* calls. By default, returns the diff --git a/Lib/enum.py b/Lib/enum.py index d10b99615981ba2..22963cca4466f2d 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -547,7 +547,10 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k classdict['_inverted_'] = None try: exc = None + classdict['_%s__in_progress' % cls] = True enum_class = super().__new__(metacls, cls, bases, classdict, **kwds) + classdict['_%s__in_progress' % cls] = False + delattr(enum_class, '_%s__in_progress' % cls) except Exception as e: # since 3.12 the line "Error calling __set_name__ on '_proto_member' instance ..." # is tacked on to the error instead of raising a RuntimeError @@ -1155,6 +1158,8 @@ def __new__(cls, value): # still not found -- verify that members exist, in-case somebody got here mistakenly # (such as via super when trying to override __new__) if not cls._member_map_: + if getattr(cls, '_%s__in_progress' % cls.__name__, False): + raise TypeError('do not use `super().__new__; call the appropriate __new__ directly') from None raise TypeError("%r has no members defined" % cls) # # still not found -- try _missing_ hook diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index cf3e042de1a4b4f..27f8bbaf952afca 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -447,7 +447,7 @@ def spam(cls): def test_bad_new_super(self): with self.assertRaisesRegex( TypeError, - 'has no members defined', + 'do not use .super...__new__;', ): class BadSuper(self.enum_type): def __new__(cls, value): @@ -3409,6 +3409,17 @@ def __new__(cls, int_value, *value_aliases): self.assertIs(Types(2), Types.NetList) self.assertIs(Types('nl'), Types.NetList) + def test_no_members(self): + with self.assertRaisesRegex( + TypeError, + 'has no members', + ): + Enum(7) + with self.assertRaisesRegex( + TypeError, + 'has no members', + ): + Flag(7) class TestOrder(unittest.TestCase): "test usage of the `_order_` attribute" diff --git a/Misc/NEWS.d/next/Library/2024-02-28-12-14-31.gh-issue-115821.YO2vKA.rst b/Misc/NEWS.d/next/Library/2024-02-28-12-14-31.gh-issue-115821.YO2vKA.rst new file mode 100644 index 000000000000000..7512a09a37cd469 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-28-12-14-31.gh-issue-115821.YO2vKA.rst @@ -0,0 +1,2 @@ +[Enum] Improve error message when calling super().__new__() in custom +__new__. From 479ac5ce8a311c9a5830b96e972478867fcbce61 Mon Sep 17 00:00:00 2001 From: Guido van Rossum <guido@python.org> Date: Wed, 28 Feb 2024 15:56:58 -0800 Subject: [PATCH 506/507] gh-115859: Fix test_type_inconsistency() when run multiple times (#116079) This should fix the refleaks bots. (See https://github.com/python/cpython/pull/116062#issuecomment-1970038174 .) --- Lib/test/test_capi/test_opt.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index e1aef21b2c7644a..a43726f05a448d3 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -893,9 +893,13 @@ def testfunc(n): self.assertIn("_COMPARE_OP_STR", uops) def test_type_inconsistency(self): - def testfunc(n): - for i in range(n): - x = _test_global + _test_global + ns = {} + exec(textwrap.dedent(""" + def testfunc(n): + for i in range(n): + x = _test_global + _test_global + """), globals(), ns) + testfunc = ns['testfunc'] # Must be a real global else it won't be optimized to _LOAD_CONST_INLINE global _test_global _test_global = 0 From 86e5e063aba76a7f4fc58f7d06b17b0a4730fd8e Mon Sep 17 00:00:00 2001 From: Guido van Rossum <guido@python.org> Date: Wed, 28 Feb 2024 16:05:53 -0800 Subject: [PATCH 507/507] gh-115816: Generate calls to sym_new_const() etc. without _Py_uop prefix (#116077) This was left behind by GH-115987. Basically a lot of diffs like this: ``` - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); ``` --- Lib/test/test_generated_cases.py | 4 +- Python/optimizer_cases.c.h | 196 +++++++++---------- Tools/cases_generator/optimizer_generator.py | 8 +- 3 files changed, 104 insertions(+), 104 deletions(-) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 18bf8ab29148c47..6fcb5d58dd7f348 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -900,7 +900,7 @@ def test_overridden_abstract_args(self): case OP2: { _Py_UopsSymbol *out; - out = _Py_uop_sym_new_unknown(ctx); + out = sym_new_unknown(ctx); if (out == NULL) goto out_of_space; stack_pointer[-1] = out; break; @@ -925,7 +925,7 @@ def test_no_overridden_case(self): output = """ case OP: { _Py_UopsSymbol *out; - out = _Py_uop_sym_new_unknown(ctx); + out = sym_new_unknown(ctx); if (out == NULL) goto out_of_space; stack_pointer[-1] = out; break; diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 9b387c078502456..b38c03bed4b4d07 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -80,7 +80,7 @@ case _END_SEND: { _Py_UopsSymbol *value; - value = _Py_uop_sym_new_unknown(ctx); + value = sym_new_unknown(ctx); if (value == NULL) goto out_of_space; stack_pointer[-2] = value; stack_pointer += -1; @@ -89,7 +89,7 @@ case _UNARY_NEGATIVE: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -97,7 +97,7 @@ case _UNARY_NOT: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -105,7 +105,7 @@ case _TO_BOOL: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -117,7 +117,7 @@ case _TO_BOOL_INT: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -125,7 +125,7 @@ case _TO_BOOL_LIST: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -133,7 +133,7 @@ case _TO_BOOL_NONE: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -141,7 +141,7 @@ case _TO_BOOL_STR: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -149,7 +149,7 @@ case _TO_BOOL_ALWAYS_TRUE: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -157,7 +157,7 @@ case _UNARY_INVERT: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -390,7 +390,7 @@ case _BINARY_OP_ADD_UNICODE: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -399,7 +399,7 @@ case _BINARY_SUBSCR: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -408,7 +408,7 @@ case _BINARY_SLICE: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-3] = res; stack_pointer += -2; @@ -422,7 +422,7 @@ case _BINARY_SUBSCR_LIST_INT: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -431,7 +431,7 @@ case _BINARY_SUBSCR_STR_INT: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -440,7 +440,7 @@ case _BINARY_SUBSCR_TUPLE_INT: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -449,7 +449,7 @@ case _BINARY_SUBSCR_DICT: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -490,7 +490,7 @@ case _CALL_INTRINSIC_1: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -498,7 +498,7 @@ case _CALL_INTRINSIC_2: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -525,7 +525,7 @@ case _GET_AITER: { _Py_UopsSymbol *iter; - iter = _Py_uop_sym_new_unknown(ctx); + iter = sym_new_unknown(ctx); if (iter == NULL) goto out_of_space; stack_pointer[-1] = iter; break; @@ -533,7 +533,7 @@ case _GET_ANEXT: { _Py_UopsSymbol *awaitable; - awaitable = _Py_uop_sym_new_unknown(ctx); + awaitable = sym_new_unknown(ctx); if (awaitable == NULL) goto out_of_space; stack_pointer[0] = awaitable; stack_pointer += 1; @@ -542,7 +542,7 @@ case _GET_AWAITABLE: { _Py_UopsSymbol *iter; - iter = _Py_uop_sym_new_unknown(ctx); + iter = sym_new_unknown(ctx); if (iter == NULL) goto out_of_space; stack_pointer[-1] = iter; break; @@ -561,7 +561,7 @@ case _LOAD_ASSERTION_ERROR: { _Py_UopsSymbol *value; - value = _Py_uop_sym_new_unknown(ctx); + value = sym_new_unknown(ctx); if (value == NULL) goto out_of_space; stack_pointer[0] = value; stack_pointer += 1; @@ -570,7 +570,7 @@ case _LOAD_BUILD_CLASS: { _Py_UopsSymbol *bc; - bc = _Py_uop_sym_new_unknown(ctx); + bc = sym_new_unknown(ctx); if (bc == NULL) goto out_of_space; stack_pointer[0] = bc; stack_pointer += 1; @@ -604,7 +604,7 @@ _Py_UopsSymbol **values; values = &stack_pointer[-1]; for (int _i = oparg; --_i >= 0;) { - values[_i] = _Py_uop_sym_new_unknown(ctx); + values[_i] = sym_new_unknown(ctx); if (values[_i] == NULL) goto out_of_space; } stack_pointer += -1 + oparg; @@ -615,7 +615,7 @@ _Py_UopsSymbol **values; values = &stack_pointer[-1]; for (int _i = oparg; --_i >= 0;) { - values[_i] = _Py_uop_sym_new_unknown(ctx); + values[_i] = sym_new_unknown(ctx); if (values[_i] == NULL) goto out_of_space; } stack_pointer += -1 + oparg; @@ -626,7 +626,7 @@ _Py_UopsSymbol **values; values = &stack_pointer[-1]; for (int _i = oparg; --_i >= 0;) { - values[_i] = _Py_uop_sym_new_unknown(ctx); + values[_i] = sym_new_unknown(ctx); if (values[_i] == NULL) goto out_of_space; } stack_pointer += -1 + oparg; @@ -669,7 +669,7 @@ case _LOAD_LOCALS: { _Py_UopsSymbol *locals; - locals = _Py_uop_sym_new_unknown(ctx); + locals = sym_new_unknown(ctx); if (locals == NULL) goto out_of_space; stack_pointer[0] = locals; stack_pointer += 1; @@ -678,7 +678,7 @@ case _LOAD_FROM_DICT_OR_GLOBALS: { _Py_UopsSymbol *v; - v = _Py_uop_sym_new_unknown(ctx); + v = sym_new_unknown(ctx); if (v == NULL) goto out_of_space; stack_pointer[-1] = v; break; @@ -686,7 +686,7 @@ case _LOAD_NAME: { _Py_UopsSymbol *v; - v = _Py_uop_sym_new_unknown(ctx); + v = sym_new_unknown(ctx); if (v == NULL) goto out_of_space; stack_pointer[0] = v; stack_pointer += 1; @@ -696,9 +696,9 @@ case _LOAD_GLOBAL: { _Py_UopsSymbol *res; _Py_UopsSymbol *null = NULL; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; - null = _Py_uop_sym_new_null(ctx); + null = sym_new_null(ctx); if (null == NULL) goto out_of_space; stack_pointer[0] = res; if (oparg & 1) stack_pointer[1] = null; @@ -717,9 +717,9 @@ case _LOAD_GLOBAL_MODULE: { _Py_UopsSymbol *res; _Py_UopsSymbol *null = NULL; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; - null = _Py_uop_sym_new_null(ctx); + null = sym_new_null(ctx); if (null == NULL) goto out_of_space; stack_pointer[0] = res; if (oparg & 1) stack_pointer[1] = null; @@ -730,9 +730,9 @@ case _LOAD_GLOBAL_BUILTINS: { _Py_UopsSymbol *res; _Py_UopsSymbol *null = NULL; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; - null = _Py_uop_sym_new_null(ctx); + null = sym_new_null(ctx); if (null == NULL) goto out_of_space; stack_pointer[0] = res; if (oparg & 1) stack_pointer[1] = null; @@ -754,7 +754,7 @@ case _LOAD_FROM_DICT_OR_DEREF: { _Py_UopsSymbol *value; - value = _Py_uop_sym_new_unknown(ctx); + value = sym_new_unknown(ctx); if (value == NULL) goto out_of_space; stack_pointer[-1] = value; break; @@ -762,7 +762,7 @@ case _LOAD_DEREF: { _Py_UopsSymbol *value; - value = _Py_uop_sym_new_unknown(ctx); + value = sym_new_unknown(ctx); if (value == NULL) goto out_of_space; stack_pointer[0] = value; stack_pointer += 1; @@ -780,7 +780,7 @@ case _BUILD_STRING: { _Py_UopsSymbol *str; - str = _Py_uop_sym_new_unknown(ctx); + str = sym_new_unknown(ctx); if (str == NULL) goto out_of_space; stack_pointer[-oparg] = str; stack_pointer += 1 - oparg; @@ -789,7 +789,7 @@ case _BUILD_TUPLE: { _Py_UopsSymbol *tup; - tup = _Py_uop_sym_new_unknown(ctx); + tup = sym_new_unknown(ctx); if (tup == NULL) goto out_of_space; stack_pointer[-oparg] = tup; stack_pointer += 1 - oparg; @@ -798,7 +798,7 @@ case _BUILD_LIST: { _Py_UopsSymbol *list; - list = _Py_uop_sym_new_unknown(ctx); + list = sym_new_unknown(ctx); if (list == NULL) goto out_of_space; stack_pointer[-oparg] = list; stack_pointer += 1 - oparg; @@ -817,7 +817,7 @@ case _BUILD_SET: { _Py_UopsSymbol *set; - set = _Py_uop_sym_new_unknown(ctx); + set = sym_new_unknown(ctx); if (set == NULL) goto out_of_space; stack_pointer[-oparg] = set; stack_pointer += 1 - oparg; @@ -826,7 +826,7 @@ case _BUILD_MAP: { _Py_UopsSymbol *map; - map = _Py_uop_sym_new_unknown(ctx); + map = sym_new_unknown(ctx); if (map == NULL) goto out_of_space; stack_pointer[-oparg*2] = map; stack_pointer += 1 - oparg*2; @@ -839,7 +839,7 @@ case _BUILD_CONST_KEY_MAP: { _Py_UopsSymbol *map; - map = _Py_uop_sym_new_unknown(ctx); + map = sym_new_unknown(ctx); if (map == NULL) goto out_of_space; stack_pointer[-1 - oparg] = map; stack_pointer += -oparg; @@ -865,7 +865,7 @@ case _LOAD_SUPER_ATTR_ATTR: { _Py_UopsSymbol *attr; - attr = _Py_uop_sym_new_unknown(ctx); + attr = sym_new_unknown(ctx); if (attr == NULL) goto out_of_space; stack_pointer[-3] = attr; stack_pointer += -2; @@ -875,9 +875,9 @@ case _LOAD_SUPER_ATTR_METHOD: { _Py_UopsSymbol *attr; _Py_UopsSymbol *self_or_null; - attr = _Py_uop_sym_new_unknown(ctx); + attr = sym_new_unknown(ctx); if (attr == NULL) goto out_of_space; - self_or_null = _Py_uop_sym_new_unknown(ctx); + self_or_null = sym_new_unknown(ctx); if (self_or_null == NULL) goto out_of_space; stack_pointer[-3] = attr; stack_pointer[-2] = self_or_null; @@ -888,9 +888,9 @@ case _LOAD_ATTR: { _Py_UopsSymbol *attr; _Py_UopsSymbol *self_or_null = NULL; - attr = _Py_uop_sym_new_unknown(ctx); + attr = sym_new_unknown(ctx); if (attr == NULL) goto out_of_space; - self_or_null = _Py_uop_sym_new_unknown(ctx); + self_or_null = sym_new_unknown(ctx); if (self_or_null == NULL) goto out_of_space; stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = self_or_null; @@ -1048,7 +1048,7 @@ case _COMPARE_OP: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -1057,7 +1057,7 @@ case _COMPARE_OP_FLOAT: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -1066,7 +1066,7 @@ case _COMPARE_OP_INT: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -1075,7 +1075,7 @@ case _COMPARE_OP_STR: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -1084,7 +1084,7 @@ case _IS_OP: { _Py_UopsSymbol *b; - b = _Py_uop_sym_new_unknown(ctx); + b = sym_new_unknown(ctx); if (b == NULL) goto out_of_space; stack_pointer[-2] = b; stack_pointer += -1; @@ -1093,7 +1093,7 @@ case _CONTAINS_OP: { _Py_UopsSymbol *b; - b = _Py_uop_sym_new_unknown(ctx); + b = sym_new_unknown(ctx); if (b == NULL) goto out_of_space; stack_pointer[-2] = b; stack_pointer += -1; @@ -1103,9 +1103,9 @@ case _CHECK_EG_MATCH: { _Py_UopsSymbol *rest; _Py_UopsSymbol *match; - rest = _Py_uop_sym_new_unknown(ctx); + rest = sym_new_unknown(ctx); if (rest == NULL) goto out_of_space; - match = _Py_uop_sym_new_unknown(ctx); + match = sym_new_unknown(ctx); if (match == NULL) goto out_of_space; stack_pointer[-2] = rest; stack_pointer[-1] = match; @@ -1114,7 +1114,7 @@ case _CHECK_EXC_MATCH: { _Py_UopsSymbol *b; - b = _Py_uop_sym_new_unknown(ctx); + b = sym_new_unknown(ctx); if (b == NULL) goto out_of_space; stack_pointer[-1] = b; break; @@ -1126,7 +1126,7 @@ case _IS_NONE: { _Py_UopsSymbol *b; - b = _Py_uop_sym_new_unknown(ctx); + b = sym_new_unknown(ctx); if (b == NULL) goto out_of_space; stack_pointer[-1] = b; break; @@ -1134,7 +1134,7 @@ case _GET_LEN: { _Py_UopsSymbol *len_o; - len_o = _Py_uop_sym_new_unknown(ctx); + len_o = sym_new_unknown(ctx); if (len_o == NULL) goto out_of_space; stack_pointer[0] = len_o; stack_pointer += 1; @@ -1143,7 +1143,7 @@ case _MATCH_CLASS: { _Py_UopsSymbol *attrs; - attrs = _Py_uop_sym_new_unknown(ctx); + attrs = sym_new_unknown(ctx); if (attrs == NULL) goto out_of_space; stack_pointer[-3] = attrs; stack_pointer += -2; @@ -1152,7 +1152,7 @@ case _MATCH_MAPPING: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[0] = res; stack_pointer += 1; @@ -1161,7 +1161,7 @@ case _MATCH_SEQUENCE: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[0] = res; stack_pointer += 1; @@ -1170,7 +1170,7 @@ case _MATCH_KEYS: { _Py_UopsSymbol *values_or_none; - values_or_none = _Py_uop_sym_new_unknown(ctx); + values_or_none = sym_new_unknown(ctx); if (values_or_none == NULL) goto out_of_space; stack_pointer[0] = values_or_none; stack_pointer += 1; @@ -1179,7 +1179,7 @@ case _GET_ITER: { _Py_UopsSymbol *iter; - iter = _Py_uop_sym_new_unknown(ctx); + iter = sym_new_unknown(ctx); if (iter == NULL) goto out_of_space; stack_pointer[-1] = iter; break; @@ -1187,7 +1187,7 @@ case _GET_YIELD_FROM_ITER: { _Py_UopsSymbol *iter; - iter = _Py_uop_sym_new_unknown(ctx); + iter = sym_new_unknown(ctx); if (iter == NULL) goto out_of_space; stack_pointer[-1] = iter; break; @@ -1197,7 +1197,7 @@ case _FOR_ITER_TIER_TWO: { _Py_UopsSymbol *next; - next = _Py_uop_sym_new_unknown(ctx); + next = sym_new_unknown(ctx); if (next == NULL) goto out_of_space; stack_pointer[0] = next; stack_pointer += 1; @@ -1218,7 +1218,7 @@ case _ITER_NEXT_LIST: { _Py_UopsSymbol *next; - next = _Py_uop_sym_new_unknown(ctx); + next = sym_new_unknown(ctx); if (next == NULL) goto out_of_space; stack_pointer[0] = next; stack_pointer += 1; @@ -1237,7 +1237,7 @@ case _ITER_NEXT_TUPLE: { _Py_UopsSymbol *next; - next = _Py_uop_sym_new_unknown(ctx); + next = sym_new_unknown(ctx); if (next == NULL) goto out_of_space; stack_pointer[0] = next; stack_pointer += 1; @@ -1270,9 +1270,9 @@ case _BEFORE_ASYNC_WITH: { _Py_UopsSymbol *exit; _Py_UopsSymbol *res; - exit = _Py_uop_sym_new_unknown(ctx); + exit = sym_new_unknown(ctx); if (exit == NULL) goto out_of_space; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = exit; stack_pointer[0] = res; @@ -1283,9 +1283,9 @@ case _BEFORE_WITH: { _Py_UopsSymbol *exit; _Py_UopsSymbol *res; - exit = _Py_uop_sym_new_unknown(ctx); + exit = sym_new_unknown(ctx); if (exit == NULL) goto out_of_space; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = exit; stack_pointer[0] = res; @@ -1295,7 +1295,7 @@ case _WITH_EXCEPT_START: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[0] = res; stack_pointer += 1; @@ -1305,9 +1305,9 @@ case _PUSH_EXC_INFO: { _Py_UopsSymbol *prev_exc; _Py_UopsSymbol *new_exc; - prev_exc = _Py_uop_sym_new_unknown(ctx); + prev_exc = sym_new_unknown(ctx); if (prev_exc == NULL) goto out_of_space; - new_exc = _Py_uop_sym_new_unknown(ctx); + new_exc = sym_new_unknown(ctx); if (new_exc == NULL) goto out_of_space; stack_pointer[-1] = prev_exc; stack_pointer[0] = new_exc; @@ -1355,7 +1355,7 @@ case _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: { _Py_UopsSymbol *attr; - attr = _Py_uop_sym_new_unknown(ctx); + attr = sym_new_unknown(ctx); if (attr == NULL) goto out_of_space; stack_pointer[-1] = attr; break; @@ -1363,7 +1363,7 @@ case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: { _Py_UopsSymbol *attr; - attr = _Py_uop_sym_new_unknown(ctx); + attr = sym_new_unknown(ctx); if (attr == NULL) goto out_of_space; stack_pointer[-1] = attr; break; @@ -1488,7 +1488,7 @@ case _CALL_TYPE_1: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1497,7 +1497,7 @@ case _CALL_STR_1: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1506,7 +1506,7 @@ case _CALL_TUPLE_1: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1522,7 +1522,7 @@ case _CALL_BUILTIN_CLASS: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1531,7 +1531,7 @@ case _CALL_BUILTIN_O: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1540,7 +1540,7 @@ case _CALL_BUILTIN_FAST: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1549,7 +1549,7 @@ case _CALL_BUILTIN_FAST_WITH_KEYWORDS: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1558,7 +1558,7 @@ case _CALL_LEN: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1567,7 +1567,7 @@ case _CALL_ISINSTANCE: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1576,7 +1576,7 @@ case _CALL_METHOD_DESCRIPTOR_O: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1585,7 +1585,7 @@ case _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1594,7 +1594,7 @@ case _CALL_METHOD_DESCRIPTOR_NOARGS: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1603,7 +1603,7 @@ case _CALL_METHOD_DESCRIPTOR_FAST: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1620,7 +1620,7 @@ case _MAKE_FUNCTION: { _Py_UopsSymbol *func; - func = _Py_uop_sym_new_unknown(ctx); + func = sym_new_unknown(ctx); if (func == NULL) goto out_of_space; stack_pointer[-1] = func; break; @@ -1628,7 +1628,7 @@ case _SET_FUNCTION_ATTRIBUTE: { _Py_UopsSymbol *func; - func = _Py_uop_sym_new_unknown(ctx); + func = sym_new_unknown(ctx); if (func == NULL) goto out_of_space; stack_pointer[-2] = func; stack_pointer += -1; @@ -1637,7 +1637,7 @@ case _BUILD_SLICE: { _Py_UopsSymbol *slice; - slice = _Py_uop_sym_new_unknown(ctx); + slice = sym_new_unknown(ctx); if (slice == NULL) goto out_of_space; stack_pointer[-2 - ((oparg == 3) ? 1 : 0)] = slice; stack_pointer += -1 - ((oparg == 3) ? 1 : 0); @@ -1646,7 +1646,7 @@ case _CONVERT_VALUE: { _Py_UopsSymbol *result; - result = _Py_uop_sym_new_unknown(ctx); + result = sym_new_unknown(ctx); if (result == NULL) goto out_of_space; stack_pointer[-1] = result; break; @@ -1654,7 +1654,7 @@ case _FORMAT_SIMPLE: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; @@ -1662,7 +1662,7 @@ case _FORMAT_WITH_SPEC: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -1682,7 +1682,7 @@ case _BINARY_OP: { _Py_UopsSymbol *res; - res = _Py_uop_sym_new_unknown(ctx); + res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index e933fee8f4242b8..d3ce4c8a25f5b85 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -28,7 +28,7 @@ from cwriter import CWriter from typing import TextIO, Iterator from lexer import Token -from stack import StackOffset, Stack, SizeMismatch, UNUSED +from stack import Stack, SizeMismatch, UNUSED DEFAULT_OUTPUT = ROOT / "Python/optimizer_cases.c.h" DEFAULT_ABSTRACT_INPUT = ROOT / "Python/optimizer_bytecodes.c" @@ -87,14 +87,14 @@ def emit_default(out: CWriter, uop: Uop) -> None: if var.name != "unused" and not var.peek: if var.is_array(): out.emit(f"for (int _i = {var.size}; --_i >= 0;) {{\n") - out.emit(f"{var.name}[_i] = _Py_uop_sym_new_unknown(ctx);\n") + out.emit(f"{var.name}[_i] = sym_new_unknown(ctx);\n") out.emit(f"if ({var.name}[_i] == NULL) goto out_of_space;\n") out.emit("}\n") elif var.name == "null": - out.emit(f"{var.name} = _Py_uop_sym_new_null(ctx);\n") + out.emit(f"{var.name} = sym_new_null(ctx);\n") out.emit(f"if ({var.name} == NULL) goto out_of_space;\n") else: - out.emit(f"{var.name} = _Py_uop_sym_new_unknown(ctx);\n") + out.emit(f"{var.name} = sym_new_unknown(ctx);\n") out.emit(f"if ({var.name} == NULL) goto out_of_space;\n")