Skip to content

Commit 895e837

Browse files
gh-144549: Fix tail calling interpreter on Windows for FT (GH-144550)
1 parent 28fb13c commit 895e837

File tree

8 files changed

+62
-86
lines changed

8 files changed

+62
-86
lines changed

.github/workflows/tail-call.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ jobs:
3838
# Un-comment as we add support for more platforms for tail-calling interpreters.
3939
# - i686-pc-windows-msvc/msvc
4040
- x86_64-pc-windows-msvc/msvc
41+
- free-threading-msvc
4142
# - aarch64-pc-windows-msvc/msvc
4243
- x86_64-apple-darwin/clang
4344
- aarch64-apple-darwin/clang
@@ -53,6 +54,9 @@ jobs:
5354
- target: x86_64-pc-windows-msvc/msvc
5455
architecture: x64
5556
runner: windows-2025-vs2026
57+
- target: free-threading-msvc
58+
architecture: x64
59+
runner: windows-2025-vs2026
5660
# - target: aarch64-pc-windows-msvc/msvc
5761
# architecture: ARM64
5862
# runner: windows-2022
@@ -80,13 +84,21 @@ jobs:
8084
python-version: '3.11'
8185

8286
- name: Native Windows MSVC (release)
83-
if: runner.os == 'Windows' && matrix.architecture != 'ARM64'
87+
if: runner.os == 'Windows' && matrix.architecture != 'ARM64' && matrix.target != 'free-threading-msvc'
8488
shell: pwsh
8589
run: |
8690
$env:PlatformToolset = "v145"
8791
./PCbuild/build.bat --tail-call-interp -c Release -p ${{ matrix.architecture }}
8892
./PCbuild/rt.bat -p ${{ matrix.architecture }} -q --multiprocess 0 --timeout 4500 --verbose2 --verbose3
8993
94+
# No tests:
95+
- name: Native Windows MSVC with free-threading (release)
96+
if: matrix.target == 'free-threading-msvc'
97+
shell: pwsh
98+
run: |
99+
$env:PlatformToolset = "v145"
100+
./PCbuild/build.bat --tail-call-interp --disable-gil -c Release -p ${{ matrix.architecture }}
101+
90102
# No tests (yet):
91103
- name: Emulated Windows Clang (release)
92104
if: runner.os == 'Windows' && matrix.architecture == 'ARM64'

Include/internal/pycore_ceval.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,11 @@ _Py_assert_within_stack_bounds(
474474
_PyInterpreterFrame *frame, _PyStackRef *stack_pointer,
475475
const char *filename, int lineno);
476476

477+
PyAPI_FUNC(_PyStackRef)
478+
_Py_LoadAttr_StackRefSteal(
479+
PyThreadState *tstate, _PyStackRef owner,
480+
PyObject *name, _PyStackRef *self_or_null);
481+
477482
// Like PyMapping_GetOptionalItem, but returns the PyObject* instead of taking
478483
// it as an out parameter. This helps MSVC's escape analysis when used with
479484
// tail calling.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix building the tail calling interpreter on Visual Studio 2026 with free-threading.

Modules/_testinternalcapi/test_cases.c.h

Lines changed: 3 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/bytecodes.c

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2364,31 +2364,9 @@ dummy_func(
23642364
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1);
23652365
if (oparg & 1) {
23662366
/* Designed to work in tandem with CALL, pushes two values. */
2367-
_PyCStackRef method;
2368-
_PyThreadState_PushCStackRef(tstate, &method);
2369-
int is_meth = _PyObject_GetMethodStackRef(tstate, PyStackRef_AsPyObjectBorrow(owner), name, &method.ref);
2370-
if (is_meth) {
2371-
/* We can bypass temporary bound method object.
2372-
meth is unbound method and obj is self.
2373-
meth | self | arg1 | ... | argN
2374-
*/
2375-
assert(!PyStackRef_IsNull(method.ref)); // No errors on this branch
2376-
self_or_null[0] = owner; // Transfer ownership
2377-
DEAD(owner);
2378-
attr = _PyThreadState_PopCStackRefSteal(tstate, &method);
2379-
}
2380-
else {
2381-
/* meth is not an unbound method (but a regular attr, or
2382-
something was returned by a descriptor protocol). Set
2383-
the second element of the stack to NULL, to signal
2384-
CALL that it's not a method call.
2385-
meth | NULL | arg1 | ... | argN
2386-
*/
2387-
PyStackRef_CLOSE(owner);
2388-
self_or_null[0] = PyStackRef_NULL;
2389-
attr = _PyThreadState_PopCStackRefSteal(tstate, &method);
2390-
ERROR_IF(PyStackRef_IsNull(attr));
2391-
}
2367+
attr = _Py_LoadAttr_StackRefSteal(tstate, owner, name, self_or_null);
2368+
DEAD(owner);
2369+
ERROR_IF(PyStackRef_IsNull(attr));
23922370
}
23932371
else {
23942372
/* Classic, pushes one value. */

Python/ceval.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,34 @@ _Py_BuildMap_StackRefSteal(
10071007
return res;
10081008
}
10091009

1010+
_PyStackRef
1011+
_Py_LoadAttr_StackRefSteal(
1012+
PyThreadState *tstate, _PyStackRef owner,
1013+
PyObject *name, _PyStackRef *self_or_null)
1014+
{
1015+
_PyCStackRef method;
1016+
_PyThreadState_PushCStackRef(tstate, &method);
1017+
int is_meth = _PyObject_GetMethodStackRef(tstate, PyStackRef_AsPyObjectBorrow(owner), name, &method.ref);
1018+
if (is_meth) {
1019+
/* We can bypass temporary bound method object.
1020+
meth is unbound method and obj is self.
1021+
meth | self | arg1 | ... | argN
1022+
*/
1023+
assert(!PyStackRef_IsNull(method.ref)); // No errors on this branch
1024+
self_or_null[0] = owner; // Transfer ownership
1025+
return _PyThreadState_PopCStackRefSteal(tstate, &method);
1026+
}
1027+
/* meth is not an unbound method (but a regular attr, or
1028+
something was returned by a descriptor protocol). Set
1029+
the second element of the stack to NULL, to signal
1030+
CALL that it's not a method call.
1031+
meth | NULL | arg1 | ... | argN
1032+
*/
1033+
PyStackRef_CLOSE(owner);
1034+
self_or_null[0] = PyStackRef_NULL;
1035+
return _PyThreadState_PopCStackRefSteal(tstate, &method);
1036+
}
1037+
10101038
#ifdef Py_DEBUG
10111039
void
10121040
_Py_assert_within_stack_bounds(

Python/executor_cases.c.h

Lines changed: 6 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/generated_cases.c.h

Lines changed: 3 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)