Skip to content

Commit 904b7ee

Browse files
Merge remote-tracking branch 'upstream/main' into tailcall_for_windows
2 parents c811b8a + c357d69 commit 904b7ee

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1600
-941
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,7 @@ jobs:
621621
- build_wasi
622622
- build_windows
623623
- build_windows_msi
624+
- cross-build-linux
624625
- test_hypothesis
625626
- build_asan
626627
- build_tsan

.github/workflows/tail-call.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ jobs:
4343
- aarch64-apple-darwin/clang
4444
- x86_64-unknown-linux-gnu/gcc
4545
- aarch64-unknown-linux-gnu/gcc
46+
- free-threading
4647
llvm:
4748
- 19
4849
include:
@@ -67,6 +68,9 @@ jobs:
6768
- target: aarch64-unknown-linux-gnu/gcc
6869
architecture: aarch64
6970
runner: ubuntu-22.04-arm
71+
- target: free-threading
72+
architecture: x86_64
73+
runner: ubuntu-24.04
7074
steps:
7175
- uses: actions/checkout@v4
7276
with:
@@ -115,11 +119,20 @@ jobs:
115119
./python.exe -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3
116120
117121
- name: Native Linux (debug)
118-
if: runner.os == 'Linux'
122+
if: runner.os == 'Linux' && matrix.target != 'free-threading'
119123
run: |
120124
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }}
121125
export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH"
122126
CC=clang-19 ./configure --with-tail-call-interp --with-pydebug
123127
make all --jobs 4
124128
./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3
125129
130+
- name: Native Linux with free-threading (release)
131+
if: matrix.target == 'free-threading'
132+
run: |
133+
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }}
134+
export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH"
135+
CC=clang-19 ./configure --with-tail-call-interp --disable-gil
136+
make all --jobs 4
137+
./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3
138+

Doc/c-api/typeobj.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1830,7 +1830,7 @@ and :c:data:`PyType_Type` effectively act as defaults.)
18301830
dictionary, so it is may be more efficient to call :c:func:`PyObject_GetAttr`
18311831
when accessing an attribute on the object.
18321832

1833-
It is an error to set both the :c:macro:`Py_TPFLAGS_MANAGED_WEAKREF` bit and
1833+
It is an error to set both the :c:macro:`Py_TPFLAGS_MANAGED_DICT` bit and
18341834
:c:member:`~PyTypeObject.tp_dictoffset`.
18351835

18361836
**Inheritance:**

Doc/library/traceback.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,11 @@ Module-Level Functions
257257

258258
.. versionadded:: 3.5
259259

260+
.. versionchanged:: 3.14
261+
This function previously returned a generator that would walk the stack
262+
when first iterated over. The generator returned now is the state of the
263+
stack when ``walk_stack`` is called.
264+
260265
.. function:: walk_tb(tb)
261266

262267
Walk a traceback following :attr:`~traceback.tb_next` yielding the frame and

Doc/using/configure.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,9 @@ also be used to improve performance.
575575
.. versionchanged:: 3.12
576576
Use ThinLTO as the default optimization policy on Clang if the compiler accepts the flag.
577577

578+
.. versionchanged:: next
579+
Revert to using full LTO as the default optimization policy on Clang.
580+
578581
.. option:: --enable-bolt
579582

580583
Enable usage of the `BOLT post-link binary optimizer

Doc/whatsnew/3.14.rst

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ Improved error messages
165165
error message prints the received number of values in more cases than before.
166166
(Contributed by Tushar Sadhwani in :gh:`122239`.)
167167

168-
.. code-block:: pycon
168+
.. code-block:: python
169169
170170
>>> x, y, z = 1, 2, 3, 4
171171
Traceback (most recent call last):
@@ -175,6 +175,17 @@ Improved error messages
175175
ValueError: too many values to unpack (expected 3, got 4)
176176
177177
178+
* When incorrectly closed strings are detected, the error message suggests
179+
that the string may be intended to be part of the string. (Contributed by
180+
Pablo Galindo in :gh:`88535`.)
181+
182+
.. code-block:: python
183+
184+
>>> "The interesting object "The important object" is very important"
185+
Traceback (most recent call last):
186+
SyntaxError: invalid syntax. Is this intended to be part of the string?
187+
188+
178189
.. _whatsnew314-pep741:
179190

180191
PEP 741: Python Configuration C API
@@ -1268,6 +1279,10 @@ Build changes
12681279
* GNU Autoconf 2.72 is now required to generate :file:`configure`.
12691280
(Contributed by Erlend Aasland in :gh:`115765`.)
12701281

1282+
* CPython now uses Full LTO as the default link time optimization policy
1283+
on Clang. This reverts an earlier change in CPython 3.12.
1284+
(Contributed by Ken Jin in :gh:`130049`.)
1285+
12711286
.. _whatsnew314-pep761:
12721287

12731288
PEP 761: Discontinuation of PGP signatures

Grammar/python.gram

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,6 +1178,9 @@ invalid_type_param:
11781178
}
11791179

11801180
invalid_expression:
1181+
| STRING a=(!STRING expression_without_invalid)+ STRING {
1182+
RAISE_SYNTAX_ERROR_KNOWN_RANGE( PyPegen_first_item(a, expr_ty), PyPegen_last_item(a, expr_ty),
1183+
"invalid syntax. Is this intended to be part of the string?") }
11811184
# !(NAME STRING) is not matched so we don't show this error with some invalid string prefixes like: kf"dsfsdf"
11821185
# Soft keywords need to also be ignored because they can be parsed as NAME NAME
11831186
| !(NAME STRING | SOFT_KEYWORD) a=disjunction b=expression_without_invalid {

Lib/sysconfig/__init__.py

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -666,34 +666,34 @@ def get_platform():
666666

667667
# Set for cross builds explicitly
668668
if "_PYTHON_HOST_PLATFORM" in os.environ:
669-
return os.environ["_PYTHON_HOST_PLATFORM"]
670-
671-
# Try to distinguish various flavours of Unix
672-
osname, host, release, version, machine = os.uname()
673-
674-
# Convert the OS name to lowercase, remove '/' characters, and translate
675-
# spaces (for "Power Macintosh")
676-
osname = osname.lower().replace('/', '')
677-
machine = machine.replace(' ', '_')
678-
machine = machine.replace('/', '-')
679-
680-
if osname[:5] == "linux":
681-
if sys.platform == "android":
682-
osname = "android"
683-
release = get_config_var("ANDROID_API_LEVEL")
684-
685-
# Wheel tags use the ABI names from Android's own tools.
686-
machine = {
687-
"x86_64": "x86_64",
688-
"i686": "x86",
689-
"aarch64": "arm64_v8a",
690-
"armv7l": "armeabi_v7a",
691-
}[machine]
692-
else:
693-
# At least on Linux/Intel, 'machine' is the processor --
694-
# i386, etc.
695-
# XXX what about Alpha, SPARC, etc?
696-
return f"{osname}-{machine}"
669+
osname, _, machine = os.environ["_PYTHON_HOST_PLATFORM"].partition('-')
670+
release = None
671+
else:
672+
# Try to distinguish various flavours of Unix
673+
osname, host, release, version, machine = os.uname()
674+
675+
# Convert the OS name to lowercase, remove '/' characters, and translate
676+
# spaces (for "Power Macintosh")
677+
osname = osname.lower().replace('/', '')
678+
machine = machine.replace(' ', '_')
679+
machine = machine.replace('/', '-')
680+
681+
if osname == "android" or sys.platform == "android":
682+
osname = "android"
683+
release = get_config_var("ANDROID_API_LEVEL")
684+
685+
# Wheel tags use the ABI names from Android's own tools.
686+
machine = {
687+
"x86_64": "x86_64",
688+
"i686": "x86",
689+
"aarch64": "arm64_v8a",
690+
"armv7l": "armeabi_v7a",
691+
}[machine]
692+
elif osname == "linux":
693+
# At least on Linux/Intel, 'machine' is the processor --
694+
# i386, etc.
695+
# XXX what about Alpha, SPARC, etc?
696+
return f"{osname}-{machine}"
697697
elif osname[:5] == "sunos":
698698
if release[0] >= "5": # SunOS 5 == Solaris 2
699699
osname = "solaris"
@@ -725,7 +725,7 @@ def get_platform():
725725
get_config_vars(),
726726
osname, release, machine)
727727

728-
return f"{osname}-{release}-{machine}"
728+
return '-'.join(map(str, filter(None, (osname, release, machine))))
729729

730730

731731
def get_python_version():

Lib/test/test_ast/test_ast.py

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3239,46 +3239,6 @@ def test_folding_tuple(self):
32393239

32403240
self.assert_ast(code, non_optimized_target, optimized_target)
32413241

3242-
def test_folding_comparator(self):
3243-
code = "1 %s %s1%s"
3244-
operators = [("in", ast.In()), ("not in", ast.NotIn())]
3245-
braces = [
3246-
("[", "]", ast.List, (1,)),
3247-
("{", "}", ast.Set, frozenset({1})),
3248-
]
3249-
for left, right, non_optimized_comparator, optimized_comparator in braces:
3250-
for op, node in operators:
3251-
non_optimized_target = self.wrap_expr(ast.Compare(
3252-
left=ast.Constant(1), ops=[node],
3253-
comparators=[non_optimized_comparator(elts=[ast.Constant(1)])]
3254-
))
3255-
optimized_target = self.wrap_expr(ast.Compare(
3256-
left=ast.Constant(1), ops=[node],
3257-
comparators=[ast.Constant(value=optimized_comparator)]
3258-
))
3259-
self.assert_ast(code % (op, left, right), non_optimized_target, optimized_target)
3260-
3261-
def test_folding_iter(self):
3262-
code = "for _ in %s1%s: pass"
3263-
braces = [
3264-
("[", "]", ast.List, (1,)),
3265-
("{", "}", ast.Set, frozenset({1})),
3266-
]
3267-
3268-
for left, right, ast_cls, optimized_iter in braces:
3269-
non_optimized_target = self.wrap_statement(ast.For(
3270-
target=ast.Name(id="_", ctx=ast.Store()),
3271-
iter=ast_cls(elts=[ast.Constant(1)]),
3272-
body=[ast.Pass()]
3273-
))
3274-
optimized_target = self.wrap_statement(ast.For(
3275-
target=ast.Name(id="_", ctx=ast.Store()),
3276-
iter=ast.Constant(value=optimized_iter),
3277-
body=[ast.Pass()]
3278-
))
3279-
3280-
self.assert_ast(code % (left, right), non_optimized_target, optimized_target)
3281-
32823242
def test_folding_type_param_in_function_def(self):
32833243
code = "def foo[%s = 1 + 1](): pass"
32843244

Lib/test/test_build_details.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import json
2+
import os
3+
import sys
4+
import sysconfig
5+
import string
6+
import unittest
7+
8+
from test.support import is_android, is_apple_mobile, is_emscripten, is_wasi
9+
10+
11+
class FormatTestsBase:
12+
@property
13+
def contents(self):
14+
"""Install details file contents. Should be overriden by subclasses."""
15+
raise NotImplementedError
16+
17+
@property
18+
def data(self):
19+
"""Parsed install details file data, as a Python object."""
20+
return json.loads(self.contents)
21+
22+
def key(self, name):
23+
"""Helper to fetch subsection entries.
24+
25+
It takes the entry name, allowing the usage of a dot to separate the
26+
different subsection names (eg. specifying 'a.b.c' as the key will
27+
return the value of self.data['a']['b']['c']).
28+
"""
29+
value = self.data
30+
for part in name.split('.'):
31+
value = value[part]
32+
return value
33+
34+
def test_parse(self):
35+
self.data
36+
37+
def test_top_level_container(self):
38+
self.assertIsInstance(self.data, dict)
39+
for key, value in self.data.items():
40+
with self.subTest(key=key):
41+
if key in ('schema_version', 'base_prefix', 'base_interpreter', 'platform'):
42+
self.assertIsInstance(value, str)
43+
elif key in ('language', 'implementation', 'abi', 'suffixes', 'libpython', 'c_api', 'arbitrary_data'):
44+
self.assertIsInstance(value, dict)
45+
46+
def test_base_prefix(self):
47+
self.assertIsInstance(self.key('base_prefix'), str)
48+
49+
def test_base_interpreter(self):
50+
"""Test the base_interpreter entry.
51+
52+
The generic test wants the key to be missing. If your implementation
53+
provides a value for it, you should override this test.
54+
"""
55+
with self.assertRaises(KeyError):
56+
self.key('base_interpreter')
57+
58+
def test_platform(self):
59+
self.assertEqual(self.key('platform'), sysconfig.get_platform())
60+
61+
def test_language_version(self):
62+
allowed_characters = string.digits + string.ascii_letters + '.'
63+
value = self.key('language.version')
64+
65+
self.assertLessEqual(set(value), set(allowed_characters))
66+
self.assertTrue(sys.version.startswith(value))
67+
68+
def test_language_version_info(self):
69+
value = self.key('language.version_info')
70+
71+
self.assertEqual(len(value), sys.version_info.n_fields)
72+
for part_name, part_value in value.items():
73+
with self.subTest(part=part_name):
74+
self.assertEqual(part_value, getattr(sys.version_info, part_name))
75+
76+
def test_implementation(self):
77+
for key, value in self.key('implementation').items():
78+
with self.subTest(part=key):
79+
if key == 'version':
80+
self.assertEqual(len(value), len(sys.implementation.version))
81+
for part_name, part_value in value.items():
82+
self.assertEqual(getattr(sys.implementation.version, part_name), part_value)
83+
else:
84+
self.assertEqual(getattr(sys.implementation, key), value)
85+
86+
87+
needs_installed_python = unittest.skipIf(
88+
sysconfig.is_python_build(),
89+
'This test can only run in an installed Python',
90+
)
91+
92+
93+
@unittest.skipIf(os.name != 'posix', 'Feature only implemented on POSIX right now')
94+
@unittest.skipIf(is_wasi or is_emscripten, 'Feature not available on WebAssembly builds')
95+
class CPythonBuildDetailsTests(unittest.TestCase, FormatTestsBase):
96+
"""Test CPython's install details file implementation."""
97+
98+
@property
99+
def location(self):
100+
if sysconfig.is_python_build():
101+
projectdir = sysconfig.get_config_var('projectbase')
102+
with open(os.path.join(projectdir, 'pybuilddir.txt')) as f:
103+
dirname = os.path.join(projectdir, f.read())
104+
else:
105+
dirname = sysconfig.get_path('stdlib')
106+
return os.path.join(dirname, 'build-details.json')
107+
108+
@property
109+
def contents(self):
110+
with open(self.location, 'r') as f:
111+
return f.read()
112+
113+
@needs_installed_python
114+
def test_location(self):
115+
self.assertTrue(os.path.isfile(self.location))
116+
117+
# Override generic format tests with tests for our specific implemenation.
118+
119+
@needs_installed_python
120+
@unittest.skipIf(is_android or is_apple_mobile, 'Android and iOS run tests via a custom testbed method that changes sys.executable')
121+
def test_base_interpreter(self):
122+
value = self.key('base_interpreter')
123+
124+
self.assertEqual(os.path.realpath(value), os.path.realpath(sys.executable))
125+
126+
127+
if __name__ == '__main__':
128+
unittest.main()

0 commit comments

Comments
 (0)