Skip to content

Commit 7a0b713

Browse files
committed
gh-139653: Add PyUnstable_ThreadState_SetStack()
Add PyUnstable_ThreadState_SetStack() and PyUnstable_ThreadState_ResetStack() functions to set the stack base address and stack size of a Python thread state.
1 parent 6f3dae0 commit 7a0b713

File tree

7 files changed

+132
-35
lines changed

7 files changed

+132
-35
lines changed

Doc/c-api/init.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,6 +1353,29 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
13531353
.. versionadded:: 3.11
13541354
13551355
1356+
.. c:function:: void PyUnstable_ThreadState_SetStack(PyThreadState *tstate, void *stack_start_addr, size_t stack_size)
1357+
1358+
Set the stack start address and stack size of a Python thread state.
1359+
1360+
*stack_size* must be greater than ``0``.
1361+
1362+
.. seealso::
1363+
The :c:func:`PyUnstable_ThreadState_ResetStack` function.
1364+
1365+
.. versionadded:: next
1366+
1367+
1368+
.. c:function:: void PyUnstable_ThreadState_ResetStack(PyThreadState *tstate)
1369+
1370+
Reset the stack start address and stack size of a Python thread state to
1371+
the operating system defaults.
1372+
1373+
.. seealso::
1374+
The :c:func:`PyUnstable_ThreadState_SetStack` function.
1375+
1376+
.. versionadded:: next
1377+
1378+
13561379
.. c:function:: PyInterpreterState* PyInterpreterState_Get(void)
13571380
13581381
Get the current interpreter.

Include/cpython/pystate.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,16 @@ PyAPI_FUNC(int) PyGILState_Check(void);
252252
*/
253253
PyAPI_FUNC(PyObject*) _PyThread_CurrentFrames(void);
254254

255+
// Set the stack start address and stack size of a Python thread state
256+
PyAPI_FUNC(void) PyUnstable_ThreadState_SetStack(
257+
PyThreadState *tstate,
258+
void *stack_start_addr, // Stack start address
259+
size_t stack_size); // Stack size (in bytes)
260+
261+
// Reset the stack start address and stack size of a Python thread state
262+
PyAPI_FUNC(void) PyUnstable_ThreadState_ResetStack(
263+
PyThreadState *tstate);
264+
255265
/* Routines for advanced debuggers, requested by David Beazley.
256266
Don't use unless you know what you are doing! */
257267
PyAPI_FUNC(PyInterpreterState *) PyInterpreterState_Main(void);

Include/internal/pycore_ceval.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,6 @@ static inline void _Py_LeaveRecursiveCallTstate(PyThreadState *tstate) {
243243
(void)tstate;
244244
}
245245

246-
PyAPI_FUNC(void) _Py_InitializeRecursionLimits(PyThreadState *tstate);
247-
248246
static inline int _Py_ReachedRecursionLimit(PyThreadState *tstate) {
249247
uintptr_t here_addr = _Py_get_machine_stack_pointer();
250248
_PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate;

Modules/_testinternalcapi.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2418,6 +2418,39 @@ set_vectorcall_nop(PyObject *self, PyObject *func)
24182418
Py_RETURN_NONE;
24192419
}
24202420

2421+
static void
2422+
check_threadstate_set_stack(PyThreadState *tstate, void *start, size_t size)
2423+
{
2424+
PyUnstable_ThreadState_SetStack(tstate, start, size);
2425+
2426+
_PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
2427+
assert(ts->c_stack_hard_limit == (uintptr_t)start);
2428+
assert(ts->c_stack_top == (uintptr_t)start + size);
2429+
assert(ts->c_stack_soft_limit >= ts->c_stack_hard_limit);
2430+
assert(ts->c_stack_soft_limit < ts->c_stack_top);
2431+
}
2432+
2433+
2434+
static PyObject *
2435+
test_threadstate_set_stack(PyObject *self, PyObject *Py_UNUSED(args))
2436+
{
2437+
PyThreadState *tstate = PyThreadState_GET();
2438+
2439+
// Test a size smaller than _PyOS_STACK_MARGIN_BYTES
2440+
size_t size = 123;
2441+
assert(size < _PyOS_STACK_MARGIN_BYTES);
2442+
void *start = (void*)(_Py_get_machine_stack_pointer() - size);
2443+
check_threadstate_set_stack(tstate, start, size);
2444+
2445+
// Test a larger size
2446+
size = 7654321;
2447+
start = (void*)(_Py_get_machine_stack_pointer() - size);
2448+
check_threadstate_set_stack(tstate, start, size);
2449+
2450+
PyUnstable_ThreadState_ResetStack(tstate);
2451+
Py_RETURN_NONE;
2452+
}
2453+
24212454
static PyMethodDef module_functions[] = {
24222455
{"get_configs", get_configs, METH_NOARGS},
24232456
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
@@ -2527,6 +2560,7 @@ static PyMethodDef module_functions[] = {
25272560
#endif
25282561
{"simple_pending_call", simple_pending_call, METH_O},
25292562
{"set_vectorcall_nop", set_vectorcall_nop, METH_O},
2563+
{"test_threadstate_set_stack", test_threadstate_set_stack, METH_NOARGS},
25302564
{NULL, NULL} /* sentinel */
25312565
};
25322566

Python/ceval.c

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ _Py_ReachedRecursionLimitWithMargin(PyThreadState *tstate, int margin_count)
351351
return 0;
352352
}
353353
if (_tstate->c_stack_hard_limit == 0) {
354-
_Py_InitializeRecursionLimits(tstate);
354+
PyUnstable_ThreadState_ResetStack(tstate);
355355
}
356356
return here_addr <= _tstate->c_stack_soft_limit + margin_count * _PyOS_STACK_MARGIN_BYTES;
357357
}
@@ -440,34 +440,68 @@ int pthread_attr_destroy(pthread_attr_t *a)
440440

441441

442442
void
443-
_Py_InitializeRecursionLimits(PyThreadState *tstate)
443+
PyUnstable_ThreadState_SetStack(PyThreadState *tstate,
444+
void *stack_start_addr, size_t stack_size)
445+
{
446+
assert(stack_size > 0);
447+
448+
_PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
449+
ts->c_stack_hard_limit = (uintptr_t)stack_start_addr;
450+
ts->c_stack_top = (uintptr_t)stack_start_addr + stack_size;
451+
452+
uintptr_t soft_limit = ts->c_stack_hard_limit;
453+
if (stack_size >= _PyOS_STACK_MARGIN_BYTES) {
454+
#ifdef _Py_THREAD_SANITIZER
455+
// Thread sanitizer crashes if we use a bit more than half the stack.
456+
soft_limit += (stack_size / 2);
457+
#else
458+
soft_limit += _PyOS_STACK_MARGIN_BYTES;
459+
#endif
460+
}
461+
ts->c_stack_soft_limit = soft_limit;
462+
463+
// Sanity checks
464+
assert(ts->c_stack_hard_limit <= ts->c_stack_soft_limit);
465+
assert(ts->c_stack_soft_limit < ts->c_stack_top);
466+
467+
// Test the stack pointer
468+
#ifndef NDEBUG
469+
uintptr_t here_addr = _Py_get_machine_stack_pointer();
470+
#endif
471+
assert(ts->c_stack_soft_limit < here_addr);
472+
assert(here_addr < ts->c_stack_top);
473+
}
474+
475+
476+
void
477+
PyUnstable_ThreadState_ResetStack(PyThreadState *tstate)
444478
{
445-
_PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate;
446479
#ifdef WIN32
447480
ULONG_PTR low, high;
448481
GetCurrentThreadStackLimits(&low, &high);
449-
_tstate->c_stack_top = (uintptr_t)high;
482+
450483
ULONG guarantee = 0;
451484
SetThreadStackGuarantee(&guarantee);
452-
_tstate->c_stack_hard_limit = ((uintptr_t)low) + guarantee + _PyOS_STACK_MARGIN_BYTES;
453-
_tstate->c_stack_soft_limit = _tstate->c_stack_hard_limit + _PyOS_STACK_MARGIN_BYTES;
485+
486+
uintptr_t start = (uintptr_t)low + guarantee + _PyOS_STACK_MARGIN_BYTES;
487+
size_t size = (uintptr_t)high - start;
488+
PyUnstable_ThreadState_SetStack(tstate, (void*)start, size);
489+
454490
#elif defined(__APPLE__)
455491
pthread_t this_thread = pthread_self();
456-
void *stack_addr = pthread_get_stackaddr_np(this_thread); // top of the stack
457-
size_t stack_size = pthread_get_stacksize_np(this_thread);
458-
_tstate->c_stack_top = (uintptr_t)stack_addr;
459-
_tstate->c_stack_hard_limit = _tstate->c_stack_top - stack_size;
460-
_tstate->c_stack_soft_limit = _tstate->c_stack_hard_limit + _PyOS_STACK_MARGIN_BYTES;
492+
void *top = pthread_get_stackaddr_np(this_thread); // top of the stack
493+
size_t size = pthread_get_stacksize_np(this_thread);
494+
PyUnstable_ThreadState_SetStack(tstate, (char*)top - size, size);
495+
461496
#else
462-
uintptr_t here_addr = _Py_get_machine_stack_pointer();
463-
/// XXX musl supports HAVE_PTHRED_GETATTR_NP, but the resulting stack size
464-
/// (on alpine at least) is much smaller than expected and imposes undue limits
465-
/// compared to the old stack size estimation. (We assume musl is not glibc.)
497+
// XXX musl supports HAVE_PTHRED_GETATTR_NP, but the resulting stack size
498+
// (on alpine at least) is much smaller than expected and imposes undue limits
499+
// compared to the old stack size estimation. (We assume musl is not glibc.)
466500
# if defined(HAVE_PTHREAD_GETATTR_NP) && !defined(_AIX) && \
467501
!defined(__NetBSD__) && (defined(__GLIBC__) || !defined(__linux__))
468-
size_t stack_size, guard_size;
469-
void *stack_addr;
470502
pthread_attr_t attr;
503+
size_t guard_size, stack_size;
504+
void *stack_addr;
471505
int err = pthread_getattr_np(pthread_self(), &attr);
472506
if (err == 0) {
473507
err = pthread_attr_getguardsize(&attr, &guard_size);
@@ -476,25 +510,23 @@ _Py_InitializeRecursionLimits(PyThreadState *tstate)
476510
}
477511
if (err == 0) {
478512
uintptr_t base = ((uintptr_t)stack_addr) + guard_size;
479-
_tstate->c_stack_top = base + stack_size;
480-
#ifdef _Py_THREAD_SANITIZER
481-
// Thread sanitizer crashes if we use a bit more than half the stack.
482-
_tstate->c_stack_soft_limit = base + (stack_size / 2);
483-
#else
484-
_tstate->c_stack_soft_limit = base + _PyOS_STACK_MARGIN_BYTES * 2;
485-
#endif
486-
_tstate->c_stack_hard_limit = base + _PyOS_STACK_MARGIN_BYTES;
487-
assert(_tstate->c_stack_soft_limit < here_addr);
488-
assert(here_addr < _tstate->c_stack_top);
489-
return;
513+
uintptr_t start = base + _PyOS_STACK_MARGIN_BYTES;
514+
size_t pystack_size = (base + stack_size) - start;
515+
PyUnstable_ThreadState_SetStack(tstate, (void*)start, pystack_size);
490516
}
517+
else
491518
# endif
492-
_tstate->c_stack_top = _Py_SIZE_ROUND_UP(here_addr, 4096);
493-
_tstate->c_stack_soft_limit = _tstate->c_stack_top - Py_C_STACK_SIZE;
494-
_tstate->c_stack_hard_limit = _tstate->c_stack_top - (Py_C_STACK_SIZE + _PyOS_STACK_MARGIN_BYTES);
519+
{
520+
uintptr_t here_addr = _Py_get_machine_stack_pointer();
521+
uintptr_t top = _Py_SIZE_ROUND_UP(here_addr, 4096);
522+
uintptr_t start = top - (Py_C_STACK_SIZE + _PyOS_STACK_MARGIN_BYTES);
523+
size_t pystack_size = top - start;
524+
PyUnstable_ThreadState_SetStack(tstate, (void*)start, pystack_size);
525+
}
495526
#endif
496527
}
497528

529+
498530
/* The function _Py_EnterRecursiveCallTstate() only calls _Py_CheckRecursiveCall()
499531
if the recursion_depth reaches recursion_limit. */
500532
int

Python/pylifecycle.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -868,7 +868,7 @@ pycore_interp_init(PyThreadState *tstate)
868868
{
869869
_PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate;
870870
if (_tstate->c_stack_hard_limit == 0) {
871-
_Py_InitializeRecursionLimits(tstate);
871+
PyUnstable_ThreadState_ResetStack(tstate);
872872
}
873873
PyInterpreterState *interp = tstate->interp;
874874
PyStatus status;

Python/pystate.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2093,7 +2093,7 @@ _PyThreadState_Attach(PyThreadState *tstate)
20932093
}
20942094
_PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate;
20952095
if (_tstate->c_stack_hard_limit == 0) {
2096-
_Py_InitializeRecursionLimits(tstate);
2096+
PyUnstable_ThreadState_ResetStack(tstate);
20972097
}
20982098

20992099
while (1) {

0 commit comments

Comments
 (0)