Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 72 additions & 85 deletions .github/workflows/tail-call.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
name: Tail calling interpreter
on:
pull_request:
paths:
paths: &paths
- '.github/workflows/tail-call.yml'
- 'Python/bytecodes.c'
- 'Python/ceval.c'
- 'Python/ceval_macros.h'
- 'Python/generated_cases.c.h'
push:
paths:
- '.github/workflows/tail-call.yml'
- 'Python/bytecodes.c'
- 'Python/ceval.c'
- 'Python/ceval_macros.h'
- 'Python/generated_cases.c.h'
paths: *paths
workflow_dispatch:

permissions:
Expand All @@ -25,117 +20,109 @@ concurrency:

env:
FORCE_COLOR: 1
LLVM_VERSION: 21

jobs:
tail-call:
windows:
name: ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 90
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
target:
# Un-comment as we add support for more platforms for tail-calling interpreters.
# - i686-pc-windows-msvc/msvc
- x86_64-pc-windows-msvc/msvc
- free-threading-msvc
# - aarch64-pc-windows-msvc/msvc
- x86_64-apple-darwin/clang
- aarch64-apple-darwin/clang
- x86_64-unknown-linux-gnu/gcc
- aarch64-unknown-linux-gnu/gcc
- free-threading
llvm:
- 20
include:
# - target: i686-pc-windows-msvc/msvc
# architecture: Win32
# runner: windows-2022
- target: x86_64-pc-windows-msvc/msvc
architecture: x64
runner: windows-2025-vs2026
- target: free-threading-msvc
build_flags: ""
run_tests: true
- target: x86_64-pc-windows-msvc/msvc-free-threading
architecture: x64
runner: windows-2025-vs2026
# - target: aarch64-pc-windows-msvc/msvc
# architecture: ARM64
# runner: windows-2022
- target: x86_64-apple-darwin/clang
architecture: x86_64
runner: macos-15-intel
- target: aarch64-apple-darwin/clang
architecture: aarch64
runner: macos-14
- target: x86_64-unknown-linux-gnu/gcc
architecture: x86_64
runner: ubuntu-24.04
- target: aarch64-unknown-linux-gnu/gcc
architecture: aarch64
runner: ubuntu-24.04-arm
- target: free-threading
architecture: x86_64
runner: ubuntu-24.04
build_flags: --disable-gil
run_tests: false
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: actions/setup-python@v6
with:
python-version: '3.11'

- name: Native Windows MSVC (release)
if: runner.os == 'Windows' && matrix.architecture != 'ARM64' && matrix.target != 'free-threading-msvc'
- name: Build
shell: pwsh
run: |
$env:PlatformToolset = "v145"
./PCbuild/build.bat --tail-call-interp -c Release -p ${{ matrix.architecture }}
./PCbuild/rt.bat -p ${{ matrix.architecture }} -q --multiprocess 0 --timeout 4500 --verbose2 --verbose3

# No tests:
- name: Native Windows MSVC with free-threading (release)
if: matrix.target == 'free-threading-msvc'
shell: pwsh
run: |
$env:PlatformToolset = "v145"
./PCbuild/build.bat --tail-call-interp --disable-gil -c Release -p ${{ matrix.architecture }}

# No tests (yet):
- name: Emulated Windows Clang (release)
if: runner.os == 'Windows' && matrix.architecture == 'ARM64'
./PCbuild/build.bat --tail-call-interp ${{ matrix.build_flags }} -c Release -p ${{ matrix.architecture }}
- name: Test
if: matrix.run_tests
shell: pwsh
run: |
choco install llvm --allow-downgrade --no-progress --version ${{ matrix.llvm }}.1.0
$env:PlatformToolset = "clangcl"
$env:LLVMToolsVersion = "${{ matrix.llvm }}.1.0"
$env:LLVMInstallDir = "C:\Program Files\LLVM"
./PCbuild/build.bat --tail-call-interp -p ${{ matrix.architecture }}
./PCbuild/rt.bat -p ${{ matrix.architecture }} -q --multiprocess 0 --timeout 4500 --verbose2 --verbose3

- name: Native macOS (release)
if: runner.os == 'macOS'
macos:
name: ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-apple-darwin/clang
runner: macos-15-intel
- target: aarch64-apple-darwin/clang
runner: macos-14
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install dependencies
run: |
brew update
brew install llvm@${{ matrix.llvm }}
brew install llvm@${{ env.LLVM_VERSION }}
- name: Build
run: |
export SDKROOT="$(xcrun --show-sdk-path)"
export PATH="/usr/local/opt/llvm@${{ matrix.llvm }}/bin:$PATH"
export PATH="/opt/homebrew/opt/llvm@${{ matrix.llvm }}/bin:$PATH"
CC=clang-20 ./configure --with-tail-call-interp
export PATH="/usr/local/opt/llvm@${{ env.LLVM_VERSION }}/bin:$PATH"
export PATH="/opt/homebrew/opt/llvm@${{ env.LLVM_VERSION }}/bin:$PATH"
CC=clang-${{ env.LLVM_VERSION }} ./configure --with-tail-call-interp
make all --jobs 4
- name: Test
run: |
./python.exe -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3

- name: Native Linux (debug)
if: runner.os == 'Linux' && matrix.target != 'free-threading'
linux:
name: ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu/gcc
runner: ubuntu-24.04
configure_flags: --with-pydebug
- target: x86_64-unknown-linux-gnu/gcc-free-threading
runner: ubuntu-24.04
configure_flags: --disable-gil
- target: aarch64-unknown-linux-gnu/gcc
runner: ubuntu-24.04-arm
configure_flags: --with-pydebug
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Build
run: |
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }}
export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH"
CC=clang-20 ./configure --with-tail-call-interp --with-pydebug
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ env.LLVM_VERSION }}
export PATH="$(llvm-config-${{ env.LLVM_VERSION }} --bindir):$PATH"
CC=clang-${{ env.LLVM_VERSION }} ./configure --with-tail-call-interp ${{ matrix.configure_flags }}
make all --jobs 4
./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3

- name: Native Linux with free-threading (release)
if: matrix.target == 'free-threading'
- name: Test
run: |
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }}
export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH"
CC=clang-20 ./configure --with-tail-call-interp --disable-gil
make all --jobs 4
./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3
8 changes: 8 additions & 0 deletions Lib/compileall.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,14 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
stripdir = os.fspath(stripdir) if stripdir is not None else None
name = os.path.basename(fullname)

# Without a cache_tag, we can only create legacy .pyc files. None of our
# callers seem to expect this, so the best we can do is fail without raising
if not legacy and sys.implementation.cache_tag is None:
if not quiet:
print("No cache tag is available to generate .pyc path for",
repr(fullname))
return False

dfile = None

if ddir is not None:
Expand Down
2 changes: 2 additions & 0 deletions Lib/ensurepip/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ def _bootstrap(*, root=None, upgrade=False, user=False,
args += ["--user"]
if verbosity:
args += ["-" + "v" * verbosity]
if sys.implementation.cache_tag is None:
args += ["--no-compile"]

return _run_pip([*args, "pip"], [os.fsdecode(tmp_wheel_path)])

Expand Down
4 changes: 3 additions & 1 deletion Lib/py_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,10 @@ def main():
else:
filenames = args.filenames
for filename in filenames:
cfilename = (None if sys.implementation.cache_tag
else f"{filename.rpartition('.')[0]}.pyc")
try:
compile(filename, doraise=True)
compile(filename, cfilename, doraise=True)
except PyCompileError as error:
if args.quiet:
parser.exit(1)
Expand Down
22 changes: 17 additions & 5 deletions Lib/test/support/import_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import importlib.machinery
import importlib.util
import os
import py_compile
import shutil
import sys
import textwrap
Expand Down Expand Up @@ -49,20 +50,31 @@ def forget(modname):
# combinations of PEP 3147/488 and legacy pyc files.
unlink(source + 'c')
for opt in ('', 1, 2):
unlink(importlib.util.cache_from_source(source, optimization=opt))
try:
unlink(importlib.util.cache_from_source(source, optimization=opt))
except NotImplementedError:
pass


def make_legacy_pyc(source):
def make_legacy_pyc(source, allow_compile=False):
"""Move a PEP 3147/488 pyc file to its legacy pyc location.

:param source: The file system path to the source file. The source file
does not need to exist, however the PEP 3147/488 pyc file must exist.
does not need to exist, however the PEP 3147/488 pyc file must exist or
allow_compile must be set.
:param allow_compile: If True, uses py_compile to create a .pyc if it does
not exist. This should be passed as True if cache_tag may be None.
:return: The file system path to the legacy pyc file.
"""
pyc_file = importlib.util.cache_from_source(source)
assert source.endswith('.py')
legacy_pyc = source + 'c'
shutil.move(pyc_file, legacy_pyc)
try:
pyc_file = importlib.util.cache_from_source(source)
shutil.move(pyc_file, legacy_pyc)
except (FileNotFoundError, NotImplementedError):
if not allow_compile:
raise
py_compile.compile(source, legacy_pyc, doraise=True)
return legacy_pyc


Expand Down
4 changes: 1 addition & 3 deletions Lib/test/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import io
import operator
import os
import py_compile
import shutil
import stat
import sys
Expand Down Expand Up @@ -7162,9 +7161,8 @@ def make_script(self, dirname, basename, *, compiled=False):
script_name = script_helper.make_script(dirname, basename, self.source)
if not compiled:
return script_name
py_compile.compile(script_name, doraise=True)
pyc_file = import_helper.make_legacy_pyc(script_name, allow_compile=True)
os.remove(script_name)
pyc_file = import_helper.make_legacy_pyc(script_name)
return pyc_file

def make_zip_script(self, script_name, name_in_zip=None):
Expand Down
5 changes: 4 additions & 1 deletion Lib/test/test_capi/test_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,10 @@ def check_executecode_pathnames(self, execute_code_func, object=False):
self.check_executecodemodule(execute_code_func, NULL, pathname)

# Test NULL pathname and non-NULL cpathname
pyc_filename = importlib.util.cache_from_source(__file__)
try:
pyc_filename = importlib.util.cache_from_source(__file__)
except NotImplementedError:
return
py_filename = importlib.util.source_from_cache(pyc_filename)
origin = self.check_executecodemodule(execute_code_func, NULL, pyc_filename)
if not object:
Expand Down
21 changes: 9 additions & 12 deletions Lib/test/test_cmd_line_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,8 @@ def test_script_abspath(self):
def test_script_compiled(self):
with os_helper.temp_dir() as script_dir:
script_name = _make_test_script(script_dir, 'script')
py_compile.compile(script_name, doraise=True)
pyc_file = import_helper.make_legacy_pyc(script_name, allow_compile=True)
os.remove(script_name)
pyc_file = import_helper.make_legacy_pyc(script_name)
self._check_script(pyc_file, pyc_file,
pyc_file, script_dir, None,
importlib.machinery.SourcelessFileLoader)
Expand All @@ -257,9 +256,8 @@ def test_directory(self):
def test_directory_compiled(self):
with os_helper.temp_dir() as script_dir:
script_name = _make_test_script(script_dir, '__main__')
py_compile.compile(script_name, doraise=True)
pyc_file = import_helper.make_legacy_pyc(script_name, allow_compile=True)
os.remove(script_name)
pyc_file = import_helper.make_legacy_pyc(script_name)
self._check_script(script_dir, pyc_file, script_dir,
script_dir, '',
importlib.machinery.SourcelessFileLoader)
Expand All @@ -279,8 +277,8 @@ def test_zipfile(self):
def test_zipfile_compiled_timestamp(self):
with os_helper.temp_dir() as script_dir:
script_name = _make_test_script(script_dir, '__main__')
compiled_name = py_compile.compile(
script_name, doraise=True,
compiled_name = script_name + 'c'
py_compile.compile(script_name, compiled_name, doraise=True,
invalidation_mode=py_compile.PycInvalidationMode.TIMESTAMP)
zip_name, run_name = make_zip_script(script_dir, 'test_zip', compiled_name)
self._check_script(zip_name, run_name, zip_name, zip_name, '',
Expand All @@ -289,8 +287,8 @@ def test_zipfile_compiled_timestamp(self):
def test_zipfile_compiled_checked_hash(self):
with os_helper.temp_dir() as script_dir:
script_name = _make_test_script(script_dir, '__main__')
compiled_name = py_compile.compile(
script_name, doraise=True,
compiled_name = script_name + 'c'
py_compile.compile(script_name, compiled_name, doraise=True,
invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH)
zip_name, run_name = make_zip_script(script_dir, 'test_zip', compiled_name)
self._check_script(zip_name, run_name, zip_name, zip_name, '',
Expand All @@ -299,8 +297,8 @@ def test_zipfile_compiled_checked_hash(self):
def test_zipfile_compiled_unchecked_hash(self):
with os_helper.temp_dir() as script_dir:
script_name = _make_test_script(script_dir, '__main__')
compiled_name = py_compile.compile(
script_name, doraise=True,
compiled_name = script_name + 'c'
py_compile.compile(script_name, compiled_name, doraise=True,
invalidation_mode=py_compile.PycInvalidationMode.UNCHECKED_HASH)
zip_name, run_name = make_zip_script(script_dir, 'test_zip', compiled_name)
self._check_script(zip_name, run_name, zip_name, zip_name, '',
Expand Down Expand Up @@ -353,9 +351,8 @@ def test_package_compiled(self):
pkg_dir = os.path.join(script_dir, 'test_pkg')
make_pkg(pkg_dir)
script_name = _make_test_script(pkg_dir, '__main__')
compiled_name = py_compile.compile(script_name, doraise=True)
pyc_file = import_helper.make_legacy_pyc(script_name, allow_compile=True)
os.remove(script_name)
pyc_file = import_helper.make_legacy_pyc(script_name)
self._check_script(["-m", "test_pkg"], pyc_file,
pyc_file, script_dir, 'test_pkg',
importlib.machinery.SourcelessFileLoader,
Expand Down
4 changes: 4 additions & 0 deletions Lib/test/test_compileall.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
from test.support.os_helper import FakePath


if sys.implementation.cache_tag is None:
raise unittest.SkipTest('requires sys.implementation.cache_tag is not None')


def get_pyc(script, opt):
if not opt:
# Replace None and 0 with ''
Expand Down
Loading
Loading