From a373f4e4f06846e4ad687009cdc1a81ef210cea2 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Mon, 13 Oct 2025 09:00:40 +0200 Subject: [PATCH 1/7] GH-139914: On HPPA, the stack grows up Adapted from a patch for Python 3.14 submitted to the Debian BTS by John https://bugs.debian.org/1105111#20 Co-authored-by: John David Anglin --- Include/internal/pycore_ceval.h | 8 +++++ ...-10-13-13-54-19.gh-issue-139914.M-y_3E.rst | 1 + Python/ceval.c | 32 +++++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-10-13-13-54-19.gh-issue-139914.M-y_3E.rst diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index 102a378f8f08bc..329ed065da9ca5 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -217,7 +217,11 @@ extern void _PyEval_DeactivateOpCache(void); static inline int _Py_MakeRecCheck(PyThreadState *tstate) { uintptr_t here_addr = _Py_get_machine_stack_pointer(); _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; +#ifdef __hppa__ + return here_addr > _tstate->c_stack_soft_limit; +#else return here_addr < _tstate->c_stack_soft_limit; +#endif } // Export for '_json' shared extension, used via _Py_EnterRecursiveCall() @@ -249,7 +253,11 @@ static inline int _Py_ReachedRecursionLimit(PyThreadState *tstate) { uintptr_t here_addr = _Py_get_machine_stack_pointer(); _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; assert(_tstate->c_stack_hard_limit != 0); +#ifdef __hppa__ + return here_addr >= _tstate->c_stack_soft_limit; +#else return here_addr <= _tstate->c_stack_soft_limit; +#endif } static inline void _Py_LeaveRecursiveCall(void) { diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-13-13-54-19.gh-issue-139914.M-y_3E.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-13-13-54-19.gh-issue-139914.M-y_3E.rst new file mode 100644 index 00000000000000..7529108d5d4772 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-13-13-54-19.gh-issue-139914.M-y_3E.rst @@ -0,0 +1 @@ +Restore support for HP PA-RISC, which has an upwards-growing stack. diff --git a/Python/ceval.c b/Python/ceval.c index f48f412fab8335..1f64bf13c54232 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -347,13 +347,21 @@ _Py_ReachedRecursionLimitWithMargin(PyThreadState *tstate, int margin_count) { uintptr_t here_addr = _Py_get_machine_stack_pointer(); _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; +#ifdef __hppa__ + if (here_addr <= _tstate->c_stack_soft_limit - margin_count * _PyOS_STACK_MARGIN_BYTES) { +#else if (here_addr > _tstate->c_stack_soft_limit + margin_count * _PyOS_STACK_MARGIN_BYTES) { +#endif return 0; } if (_tstate->c_stack_hard_limit == 0) { _Py_InitializeRecursionLimits(tstate); } +#ifdef __hppa__ + return here_addr > _tstate->c_stack_soft_limit - margin_count * _PyOS_STACK_MARGIN_BYTES; +#else return here_addr <= _tstate->c_stack_soft_limit + margin_count * _PyOS_STACK_MARGIN_BYTES; +#endif } void @@ -361,7 +369,11 @@ _Py_EnterRecursiveCallUnchecked(PyThreadState *tstate) { uintptr_t here_addr = _Py_get_machine_stack_pointer(); _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; +#ifdef __hppa__ + if (here_addr > _tstate->c_stack_hard_limit) { +#else if (here_addr < _tstate->c_stack_hard_limit) { +#endif Py_FatalError("Unchecked stack overflow."); } } @@ -491,12 +503,22 @@ _Py_InitializeRecursionLimits(PyThreadState *tstate) #ifdef _Py_THREAD_SANITIZER // Thread sanitizer crashes if we use more than half the stack. uintptr_t stacksize = top - base; +# ifdef __hppa__ + top -= stacksize/2; +# else base += stacksize/2; +# endif #endif _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; +#ifdef __hppa__ + _tstate->c_stack_top = base; + _tstate->c_stack_hard_limit = top - _PyOS_STACK_MARGIN_BYTES; + _tstate->c_stack_soft_limit = top - _PyOS_STACK_MARGIN_BYTES * 2; +#else _tstate->c_stack_top = top; _tstate->c_stack_hard_limit = base + _PyOS_STACK_MARGIN_BYTES; _tstate->c_stack_soft_limit = base + _PyOS_STACK_MARGIN_BYTES * 2; +#endif } /* The function _Py_EnterRecursiveCallTstate() only calls _Py_CheckRecursiveCall() @@ -508,9 +530,15 @@ _Py_CheckRecursiveCall(PyThreadState *tstate, const char *where) uintptr_t here_addr = _Py_get_machine_stack_pointer(); assert(_tstate->c_stack_soft_limit != 0); assert(_tstate->c_stack_hard_limit != 0); +#ifdef __hppa__ + if (here_addr > _tstate->c_stack_hard_limit) { + /* Overflowing while handling an overflow. Give up. */ + int kbytes_used = (int)(here_addr - _tstate->c_stack_top)/1024; +#else if (here_addr < _tstate->c_stack_hard_limit) { /* Overflowing while handling an overflow. Give up. */ int kbytes_used = (int)(_tstate->c_stack_top - here_addr)/1024; +#endif char buffer[80]; snprintf(buffer, 80, "Unrecoverable stack overflow (used %d kB)%s", kbytes_used, where); Py_FatalError(buffer); @@ -519,7 +547,11 @@ _Py_CheckRecursiveCall(PyThreadState *tstate, const char *where) return 0; } else { +#ifdef __hppa__ + int kbytes_used = (int)(here_addr - _tstate->c_stack_top)/1024; +#else int kbytes_used = (int)(_tstate->c_stack_top - here_addr)/1024; +#endif tstate->recursion_headroom++; _PyErr_Format(tstate, PyExc_RecursionError, "Stack overflow (used %d kB)%s", From c0c9000f8bd5e1316fc9d1c4f46b5556f42a8be1 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Tue, 14 Oct 2025 17:59:25 +0200 Subject: [PATCH 2/7] Define _Py_STACK_GROWS_DOWN to declare the stack direction --- Include/internal/pycore_ceval.h | 18 ++++++++---- Python/ceval.c | 50 ++++++++++++++++----------------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index 329ed065da9ca5..e0b1f099abd613 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -16,6 +16,12 @@ extern "C" { #include "pycore_stats.h" // EVAL_CALL_STAT_INC() #include "pycore_typedefs.h" // _PyInterpreterFrame +/* HP PA-RISC has a stack that goes up */ +#ifdef __hppa__ +# define _Py_STACK_GROWS_DOWN 0 +#else +# define _Py_STACK_GROWS_DOWN 1 +#endif /* Forward declarations */ struct _ceval_runtime_state; @@ -217,10 +223,10 @@ extern void _PyEval_DeactivateOpCache(void); static inline int _Py_MakeRecCheck(PyThreadState *tstate) { uintptr_t here_addr = _Py_get_machine_stack_pointer(); _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; -#ifdef __hppa__ - return here_addr > _tstate->c_stack_soft_limit; -#else +#if _Py_STACK_GROWS_DOWN return here_addr < _tstate->c_stack_soft_limit; +#else + return here_addr > _tstate->c_stack_soft_limit; #endif } @@ -253,10 +259,10 @@ static inline int _Py_ReachedRecursionLimit(PyThreadState *tstate) { uintptr_t here_addr = _Py_get_machine_stack_pointer(); _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; assert(_tstate->c_stack_hard_limit != 0); -#ifdef __hppa__ - return here_addr >= _tstate->c_stack_soft_limit; -#else +#if _Py_STACK_GROWS_DOWN return here_addr <= _tstate->c_stack_soft_limit; +#else + return here_addr >= _tstate->c_stack_soft_limit; #endif } diff --git a/Python/ceval.c b/Python/ceval.c index 1f64bf13c54232..f562ae67bb9e21 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -347,20 +347,20 @@ _Py_ReachedRecursionLimitWithMargin(PyThreadState *tstate, int margin_count) { uintptr_t here_addr = _Py_get_machine_stack_pointer(); _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; -#ifdef __hppa__ - if (here_addr <= _tstate->c_stack_soft_limit - margin_count * _PyOS_STACK_MARGIN_BYTES) { -#else +#if _Py_STACK_GROWS_DOWN if (here_addr > _tstate->c_stack_soft_limit + margin_count * _PyOS_STACK_MARGIN_BYTES) { +#else + if (here_addr <= _tstate->c_stack_soft_limit - margin_count * _PyOS_STACK_MARGIN_BYTES) { #endif return 0; } if (_tstate->c_stack_hard_limit == 0) { _Py_InitializeRecursionLimits(tstate); } -#ifdef __hppa__ - return here_addr > _tstate->c_stack_soft_limit - margin_count * _PyOS_STACK_MARGIN_BYTES; -#else +#if _Py_STACK_GROWS_DOWN return here_addr <= _tstate->c_stack_soft_limit + margin_count * _PyOS_STACK_MARGIN_BYTES; +#else + return here_addr > _tstate->c_stack_soft_limit - margin_count * _PyOS_STACK_MARGIN_BYTES; #endif } @@ -369,10 +369,10 @@ _Py_EnterRecursiveCallUnchecked(PyThreadState *tstate) { uintptr_t here_addr = _Py_get_machine_stack_pointer(); _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; -#ifdef __hppa__ - if (here_addr > _tstate->c_stack_hard_limit) { -#else +#if _Py_STACK_GROWS_DOWN if (here_addr < _tstate->c_stack_hard_limit) { +#else + if (here_addr > _tstate->c_stack_hard_limit) { #endif Py_FatalError("Unchecked stack overflow."); } @@ -503,21 +503,21 @@ _Py_InitializeRecursionLimits(PyThreadState *tstate) #ifdef _Py_THREAD_SANITIZER // Thread sanitizer crashes if we use more than half the stack. uintptr_t stacksize = top - base; -# ifdef __hppa__ - top -= stacksize/2; -# else +# if _Py_STACK_GROWS_DOWN base += stacksize/2; +# else + top -= stacksize/2; # endif #endif _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; -#ifdef __hppa__ - _tstate->c_stack_top = base; - _tstate->c_stack_hard_limit = top - _PyOS_STACK_MARGIN_BYTES; - _tstate->c_stack_soft_limit = top - _PyOS_STACK_MARGIN_BYTES * 2; -#else +#if _Py_STACK_GROWS_DOWN _tstate->c_stack_top = top; _tstate->c_stack_hard_limit = base + _PyOS_STACK_MARGIN_BYTES; _tstate->c_stack_soft_limit = base + _PyOS_STACK_MARGIN_BYTES * 2; +#else + _tstate->c_stack_top = base; + _tstate->c_stack_hard_limit = top - _PyOS_STACK_MARGIN_BYTES; + _tstate->c_stack_soft_limit = top - _PyOS_STACK_MARGIN_BYTES * 2; #endif } @@ -530,14 +530,14 @@ _Py_CheckRecursiveCall(PyThreadState *tstate, const char *where) uintptr_t here_addr = _Py_get_machine_stack_pointer(); assert(_tstate->c_stack_soft_limit != 0); assert(_tstate->c_stack_hard_limit != 0); -#ifdef __hppa__ - if (here_addr > _tstate->c_stack_hard_limit) { - /* Overflowing while handling an overflow. Give up. */ - int kbytes_used = (int)(here_addr - _tstate->c_stack_top)/1024; -#else +#if _Py_STACK_GROWS_DOWN if (here_addr < _tstate->c_stack_hard_limit) { /* Overflowing while handling an overflow. Give up. */ int kbytes_used = (int)(_tstate->c_stack_top - here_addr)/1024; +#else + if (here_addr > _tstate->c_stack_hard_limit) { + /* Overflowing while handling an overflow. Give up. */ + int kbytes_used = (int)(here_addr - _tstate->c_stack_top)/1024; #endif char buffer[80]; snprintf(buffer, 80, "Unrecoverable stack overflow (used %d kB)%s", kbytes_used, where); @@ -547,10 +547,10 @@ _Py_CheckRecursiveCall(PyThreadState *tstate, const char *where) return 0; } else { -#ifdef __hppa__ - int kbytes_used = (int)(here_addr - _tstate->c_stack_top)/1024; -#else +#if _Py_STACK_GROWS_DOWN int kbytes_used = (int)(_tstate->c_stack_top - here_addr)/1024; +#else + int kbytes_used = (int)(here_addr - _tstate->c_stack_top)/1024; #endif tstate->recursion_headroom++; _PyErr_Format(tstate, PyExc_RecursionError, From 7fec9395f7ef5cc0fbc0bfabe23af02fc716851f Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 17 Oct 2025 14:14:16 +0200 Subject: [PATCH 3/7] Use autoconf for _Py_STACK_GROWS_DOWN --- Include/internal/pycore_ceval.h | 6 ------ Include/pyport.h | 6 ++++++ configure | 13 +++++++++++++ configure.ac | 8 ++++++++ pyconfig.h.in | 3 +++ 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index e0b1f099abd613..8f2768e3184c77 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -16,12 +16,6 @@ extern "C" { #include "pycore_stats.h" // EVAL_CALL_STAT_INC() #include "pycore_typedefs.h" // _PyInterpreterFrame -/* HP PA-RISC has a stack that goes up */ -#ifdef __hppa__ -# define _Py_STACK_GROWS_DOWN 0 -#else -# define _Py_STACK_GROWS_DOWN 1 -#endif /* Forward declarations */ struct _ceval_runtime_state; diff --git a/Include/pyport.h b/Include/pyport.h index 62db8d07701d1d..189ec607aec499 100644 --- a/Include/pyport.h +++ b/Include/pyport.h @@ -682,4 +682,10 @@ extern "C" { #endif +// Assume the stack grows down unless specified otherwise +#ifndef _Py_STACK_GROWS_DOWN +# define _Py_STACK_GROWS_DOWN 1 +#endif + + #endif /* Py_PYPORT_H */ diff --git a/configure b/configure index 211f84399064a0..37a3068725bfba 100755 --- a/configure +++ b/configure @@ -965,6 +965,7 @@ LDLIBRARY LIBRARY BUILDEXEEXT NO_AS_NEEDED +_Py_STACK_GROWS_DOWN MULTIARCH_CPPFLAGS PLATFORM_TRIPLET MULTIARCH @@ -7211,6 +7212,18 @@ if test x$MULTIARCH != x; then fi +# Guess C stack direction +case $host in #( + hppa-*) : + _Py_STACK_GROWS_DOWN=0 ;; #( + *) : + _Py_STACK_GROWS_DOWN=1 ;; +esac + +printf "%s\n" "#define _Py_STACK_GROWS_DOWN $_Py_STACK_GROWS_DOWN" >>confdefs.h + + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for PEP 11 support tier" >&5 printf %s "checking for PEP 11 support tier... " >&6; } case $host/$ac_cv_cc_name in #( diff --git a/configure.ac b/configure.ac index 35bf153a8987b2..eab6c02a110800 100644 --- a/configure.ac +++ b/configure.ac @@ -1202,6 +1202,14 @@ if test x$MULTIARCH != x; then fi AC_SUBST([MULTIARCH_CPPFLAGS]) +# Guess C stack direction +AS_CASE([$host], + [hppa-*], [_Py_STACK_GROWS_DOWN=0], + [_Py_STACK_GROWS_DOWN=1]) +AC_DEFINE_UNQUOTED([_Py_STACK_GROWS_DOWN], [$_Py_STACK_GROWS_DOWN], + [Define to 1 if the machine stack grows down (default); 0 if it grows up.]) +AC_SUBST([_Py_STACK_GROWS_DOWN]) + dnl Support tiers according to https://peps.python.org/pep-0011/ dnl dnl NOTE: Windows support tiers are defined in PC/pyconfig.h. diff --git a/pyconfig.h.in b/pyconfig.h.in index 72870411bc086a..3919315466e0f3 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -2026,6 +2026,9 @@ /* HACL* library can compile SIMD256 implementations */ #undef _Py_HACL_CAN_COMPILE_VEC256 +/* Define to 1 if the machine stack grows down (default); 0 if it grows up. */ +#undef _Py_STACK_GROWS_DOWN + /* Define if you want to use tail-calling interpreters in CPython. */ #undef _Py_TAIL_CALL_INTERP From a096813f3a701c78b72dda2282fc3b7579639fea Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 17 Oct 2025 14:17:30 +0200 Subject: [PATCH 4/7] Expose _Py_STACK_GROWS_DOWN to tests --- Modules/_testcapimodule.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 4e73be20e1b709..e451b656c82ea7 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3324,6 +3324,10 @@ _testcapi_exec(PyObject *m) PyModule_AddObject(m, "INT64_MAX", PyLong_FromInt64(INT64_MAX)); PyModule_AddObject(m, "UINT64_MAX", PyLong_FromUInt64(UINT64_MAX)); + if (PyModule_AddIntMacro(m, _Py_STACK_GROWS_DOWN)) { + return -1; + } + if (PyModule_AddIntMacro(m, Py_single_input)) { return -1; } From c9f0215561b463088289fc2e72fe1bbd183f7338 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sun, 19 Oct 2025 23:12:05 +0200 Subject: [PATCH 5/7] Include hppa64-* and hppaX.Y-* in the autoconf check --- configure | 2 +- configure.ac | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 37a3068725bfba..50d6d4ccdd99c7 100755 --- a/configure +++ b/configure @@ -7214,7 +7214,7 @@ fi # Guess C stack direction case $host in #( - hppa-*) : + hppa*) : _Py_STACK_GROWS_DOWN=0 ;; #( *) : _Py_STACK_GROWS_DOWN=1 ;; diff --git a/configure.ac b/configure.ac index eab6c02a110800..6452c2faf8146e 100644 --- a/configure.ac +++ b/configure.ac @@ -1204,7 +1204,7 @@ AC_SUBST([MULTIARCH_CPPFLAGS]) # Guess C stack direction AS_CASE([$host], - [hppa-*], [_Py_STACK_GROWS_DOWN=0], + [hppa*], [_Py_STACK_GROWS_DOWN=0], [_Py_STACK_GROWS_DOWN=1]) AC_DEFINE_UNQUOTED([_Py_STACK_GROWS_DOWN], [$_Py_STACK_GROWS_DOWN], [Define to 1 if the machine stack grows down (default); 0 if it grows up.]) From 4434b2410e31e8a45e1f28cb9d4af8d8a78c728c Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Mon, 20 Oct 2025 09:14:48 +0200 Subject: [PATCH 6/7] Support a stack that grows up in test_call --- Lib/test/test_call.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 31e58e825be422..c60c34b2371cd3 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -1048,9 +1048,12 @@ def get_sp(): this_sp = _testinternalcapi.get_stack_pointer() lower_sp = _testcapi.pyobject_vectorcall(get_sp, (), ()) - self.assertLess(lower_sp, this_sp) + if _testcapi._Py_STACK_GROWS_DOWN: + self.assertLess(lower_sp, this_sp) + else: + self.assertGreater(lower_sp, this_sp) # Add an (arbitrary) extra 25% for safety - safe_margin = (this_sp - lower_sp) * 5 / 4 + safe_margin = abs(this_sp - lower_sp) * 5 / 4 self.assertLess(safe_margin, _testinternalcapi.get_stack_margin()) @skip_on_s390x From b217e3f9f8fa61d7446bc84237897f5fadeb0db1 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Tue, 21 Oct 2025 22:36:26 +0200 Subject: [PATCH 7/7] Found another stack computation --- Include/internal/pycore_pystate.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index ea3dfbd2eef9c1..7f1b0b7523b20d 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -326,7 +326,11 @@ _Py_RecursionLimit_GetMargin(PyThreadState *tstate) _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; assert(_tstate->c_stack_hard_limit != 0); intptr_t here_addr = _Py_get_machine_stack_pointer(); +#if _Py_STACK_GROWS_DOWN return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, here_addr - (intptr_t)_tstate->c_stack_soft_limit, _PyOS_STACK_MARGIN_SHIFT); +#else + return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, (intptr_t)_tstate->c_stack_soft_limit - here_addr, _PyOS_STACK_MARGIN_SHIFT); +#endif } #ifdef __cplusplus