https://github.com/python/cpython/commit/895e83727d6d686ab7dd5494f3ccd52b0726502e
commit: 895e83727d6d686ab7dd5494f3ccd52b0726502e
branch: main
author: Ken Jin <[email protected]>
committer: Fidget-Spinner <[email protected]>
date: 2026-02-06T19:20:28Z
summary:
gh-144549: Fix tail calling interpreter on Windows for FT (GH-144550)
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-02-06-17-59-47.gh-issue-144549.5BhPlY.rst
M .github/workflows/tail-call.yml
M Include/internal/pycore_ceval.h
M Modules/_testinternalcapi/test_cases.c.h
M Python/bytecodes.c
M Python/ceval.c
M Python/executor_cases.c.h
M Python/generated_cases.c.h
diff --git a/.github/workflows/tail-call.yml b/.github/workflows/tail-call.yml
index 853d149d20640c..a47e532d396bc0 100644
--- a/.github/workflows/tail-call.yml
+++ b/.github/workflows/tail-call.yml
@@ -38,6 +38,7 @@ jobs:
# 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
@@ -53,6 +54,9 @@ jobs:
- target: x86_64-pc-windows-msvc/msvc
architecture: x64
runner: windows-2025-vs2026
+ - target: free-threading-msvc
+ architecture: x64
+ runner: windows-2025-vs2026
# - target: aarch64-pc-windows-msvc/msvc
# architecture: ARM64
# runner: windows-2022
@@ -80,13 +84,21 @@ jobs:
python-version: '3.11'
- name: Native Windows MSVC (release)
- if: runner.os == 'Windows' && matrix.architecture != 'ARM64'
+ if: runner.os == 'Windows' && matrix.architecture != 'ARM64' &&
matrix.target != 'free-threading-msvc'
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'
diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h
index f6bdba3e9916c0..e9f1f65e53cec1 100644
--- a/Include/internal/pycore_ceval.h
+++ b/Include/internal/pycore_ceval.h
@@ -474,6 +474,11 @@ _Py_assert_within_stack_bounds(
_PyInterpreterFrame *frame, _PyStackRef *stack_pointer,
const char *filename, int lineno);
+PyAPI_FUNC(_PyStackRef)
+_Py_LoadAttr_StackRefSteal(
+ PyThreadState *tstate, _PyStackRef owner,
+ PyObject *name, _PyStackRef *self_or_null);
+
// Like PyMapping_GetOptionalItem, but returns the PyObject* instead of taking
// it as an out parameter. This helps MSVC's escape analysis when used with
// tail calling.
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-06-17-59-47.gh-issue-144549.5BhPlY.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-06-17-59-47.gh-issue-144549.5BhPlY.rst
new file mode 100644
index 00000000000000..9679e2cf6af426
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-06-17-59-47.gh-issue-144549.5BhPlY.rst
@@ -0,0 +1 @@
+Fix building the tail calling interpreter on Visual Studio 2026 with
free-threading.
diff --git a/Modules/_testinternalcapi/test_cases.c.h
b/Modules/_testinternalcapi/test_cases.c.h
index 2a73a554eda2cc..dda3bc53dc5e0d 100644
--- a/Modules/_testinternalcapi/test_cases.c.h
+++ b/Modules/_testinternalcapi/test_cases.c.h
@@ -7899,28 +7899,11 @@
self_or_null = &stack_pointer[0];
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1);
if (oparg & 1) {
- _PyCStackRef method;
_PyFrame_SetStackPointer(frame, stack_pointer);
- _PyThreadState_PushCStackRef(tstate, &method);
- int is_meth = _PyObject_GetMethodStackRef(tstate,
PyStackRef_AsPyObjectBorrow(owner), name, &method.ref);
+ attr = _Py_LoadAttr_StackRefSteal(tstate, owner, name,
self_or_null);
stack_pointer = _PyFrame_GetStackPointer(frame);
- if (is_meth) {
- assert(!PyStackRef_IsNull(method.ref));
- self_or_null[0] = owner;
- attr = _PyThreadState_PopCStackRefSteal(tstate,
&method);
- }
- else {
- stack_pointer += -1;
- ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyStackRef_CLOSE(owner);
- stack_pointer = _PyFrame_GetStackPointer(frame);
- self_or_null[0] = PyStackRef_NULL;
- attr = _PyThreadState_PopCStackRefSteal(tstate,
&method);
- if (PyStackRef_IsNull(attr)) {
- JUMP_TO_LABEL(error);
- }
- stack_pointer += 1;
+ if (PyStackRef_IsNull(attr)) {
+ JUMP_TO_LABEL(pop_1_error);
}
}
else {
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index 818b4fbc3801c0..bd22599aef725d 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -2364,31 +2364,9 @@ dummy_func(
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1);
if (oparg & 1) {
/* Designed to work in tandem with CALL, pushes two values. */
- _PyCStackRef method;
- _PyThreadState_PushCStackRef(tstate, &method);
- int is_meth = _PyObject_GetMethodStackRef(tstate,
PyStackRef_AsPyObjectBorrow(owner), name, &method.ref);
- if (is_meth) {
- /* We can bypass temporary bound method object.
- meth is unbound method and obj is self.
- meth | self | arg1 | ... | argN
- */
- assert(!PyStackRef_IsNull(method.ref)); // No errors on
this branch
- self_or_null[0] = owner; // Transfer ownership
- DEAD(owner);
- attr = _PyThreadState_PopCStackRefSteal(tstate, &method);
- }
- else {
- /* meth is not an unbound method (but a regular attr, or
- 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.
- meth | NULL | arg1 | ... | argN
- */
- PyStackRef_CLOSE(owner);
- self_or_null[0] = PyStackRef_NULL;
- attr = _PyThreadState_PopCStackRefSteal(tstate, &method);
- ERROR_IF(PyStackRef_IsNull(attr));
- }
+ attr = _Py_LoadAttr_StackRefSteal(tstate, owner, name,
self_or_null);
+ DEAD(owner);
+ ERROR_IF(PyStackRef_IsNull(attr));
}
else {
/* Classic, pushes one value. */
diff --git a/Python/ceval.c b/Python/ceval.c
index 590b315ab65c2c..61644d35b5e473 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -1007,6 +1007,34 @@ _Py_BuildMap_StackRefSteal(
return res;
}
+_PyStackRef
+_Py_LoadAttr_StackRefSteal(
+ PyThreadState *tstate, _PyStackRef owner,
+ PyObject *name, _PyStackRef *self_or_null)
+{
+ _PyCStackRef method;
+ _PyThreadState_PushCStackRef(tstate, &method);
+ int is_meth = _PyObject_GetMethodStackRef(tstate,
PyStackRef_AsPyObjectBorrow(owner), name, &method.ref);
+ if (is_meth) {
+ /* We can bypass temporary bound method object.
+ meth is unbound method and obj is self.
+ meth | self | arg1 | ... | argN
+ */
+ assert(!PyStackRef_IsNull(method.ref)); // No errors on this branch
+ self_or_null[0] = owner; // Transfer ownership
+ return _PyThreadState_PopCStackRefSteal(tstate, &method);
+ }
+ /* meth is not an unbound method (but a regular attr, or
+ 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.
+ meth | NULL | arg1 | ... | argN
+ */
+ PyStackRef_CLOSE(owner);
+ self_or_null[0] = PyStackRef_NULL;
+ return _PyThreadState_PopCStackRefSteal(tstate, &method);
+}
+
#ifdef Py_DEBUG
void
_Py_assert_within_stack_bounds(
diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h
index a98ec2200485d2..f8de66cbce3a9f 100644
--- a/Python/executor_cases.c.h
+++ b/Python/executor_cases.c.h
@@ -8670,32 +8670,18 @@
self_or_null = &stack_pointer[1];
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1);
if (oparg & 1) {
- _PyCStackRef method;
stack_pointer[0] = owner;
stack_pointer += 1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
- _PyThreadState_PushCStackRef(tstate, &method);
- int is_meth = _PyObject_GetMethodStackRef(tstate,
PyStackRef_AsPyObjectBorrow(owner), name, &method.ref);
+ attr = _Py_LoadAttr_StackRefSteal(tstate, owner, name,
self_or_null);
stack_pointer = _PyFrame_GetStackPointer(frame);
- if (is_meth) {
- assert(!PyStackRef_IsNull(method.ref));
- self_or_null[0] = owner;
- attr = _PyThreadState_PopCStackRefSteal(tstate, &method);
- }
- else {
- stack_pointer += -1;
+ if (PyStackRef_IsNull(attr)) {
+ stack_pointer[-1] = attr;
+ stack_pointer += (oparg&1);
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyStackRef_CLOSE(owner);
- stack_pointer = _PyFrame_GetStackPointer(frame);
- self_or_null[0] = PyStackRef_NULL;
- attr = _PyThreadState_PopCStackRefSteal(tstate, &method);
- if (PyStackRef_IsNull(attr)) {
- SET_CURRENT_CACHED_VALUES(0);
- JUMP_TO_ERROR();
- }
- stack_pointer += 1;
+ SET_CURRENT_CACHED_VALUES(0);
+ JUMP_TO_ERROR();
}
}
else {
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index fc1144a88d70cc..4cc9d9e03a545d 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -7898,28 +7898,11 @@
self_or_null = &stack_pointer[0];
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1);
if (oparg & 1) {
- _PyCStackRef method;
_PyFrame_SetStackPointer(frame, stack_pointer);
- _PyThreadState_PushCStackRef(tstate, &method);
- int is_meth = _PyObject_GetMethodStackRef(tstate,
PyStackRef_AsPyObjectBorrow(owner), name, &method.ref);
+ attr = _Py_LoadAttr_StackRefSteal(tstate, owner, name,
self_or_null);
stack_pointer = _PyFrame_GetStackPointer(frame);
- if (is_meth) {
- assert(!PyStackRef_IsNull(method.ref));
- self_or_null[0] = owner;
- attr = _PyThreadState_PopCStackRefSteal(tstate,
&method);
- }
- else {
- stack_pointer += -1;
- ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyStackRef_CLOSE(owner);
- stack_pointer = _PyFrame_GetStackPointer(frame);
- self_or_null[0] = PyStackRef_NULL;
- attr = _PyThreadState_PopCStackRefSteal(tstate,
&method);
- if (PyStackRef_IsNull(attr)) {
- JUMP_TO_LABEL(error);
- }
- stack_pointer += 1;
+ if (PyStackRef_IsNull(attr)) {
+ JUMP_TO_LABEL(pop_1_error);
}
}
else {
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]