Skip to content

Commit 14c66bd

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 7094f09 commit 14c66bd

File tree

8 files changed

+171
-7
lines changed

8 files changed

+171
-7
lines changed

Doc/c-api/init.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,6 +1353,32 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
13531353
.. versionadded:: 3.11
13541354
13551355
1356+
.. c:function:: int 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+
On success, return ``0``.
1363+
On failure, set an exception and return ``-1``.
1364+
1365+
.. seealso::
1366+
The :c:func:`PyUnstable_ThreadState_ResetStack` function.
1367+
1368+
.. versionadded:: next
1369+
1370+
1371+
.. c:function:: void PyUnstable_ThreadState_ResetStack(PyThreadState *tstate)
1372+
1373+
Reset the stack start address and stack size of a Python thread state to
1374+
the operating system defaults.
1375+
1376+
.. seealso::
1377+
The :c:func:`PyUnstable_ThreadState_SetStack` function.
1378+
1379+
.. versionadded:: next
1380+
1381+
13561382
.. c:function:: PyInterpreterState* PyInterpreterState_Get(void)
13571383
13581384
Get the current interpreter.

Doc/whatsnew/3.14.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2994,6 +2994,11 @@ New features in the C API
29942994
as arguments to C API functions.
29952995
(Contributed by Sam Gross in :gh:`133164`.)
29962996

2997+
* Add :c:func:`PyUnstable_ThreadState_SetStack` and
2998+
:c:func:`PyUnstable_ThreadState_ResetStack` functions to set the stack base
2999+
address and stack size of a Python thread state.
3000+
(Contributed by Victor Stinner in :gh:`139653`.)
3001+
29973002

29983003
Limited C API changes
29993004
---------------------

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(int) 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_tstate.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ typedef struct _PyThreadStateImpl {
3737
uintptr_t c_stack_soft_limit;
3838
uintptr_t c_stack_hard_limit;
3939

40+
// PyUnstable_ThreadState_ResetStack() values
41+
uintptr_t c_stack_init_base;
42+
uintptr_t c_stack_init_top;
43+
4044
PyObject *asyncio_running_loop; // Strong reference
4145
PyObject *asyncio_running_task; // Strong reference
4246

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add :c:func:`PyUnstable_ThreadState_SetStack` and
2+
:c:func:`PyUnstable_ThreadState_ResetStack` functions to set the stack base
3+
address and stack size of a Python thread state. Patch by Victor Stinner.

Modules/_testinternalcapi.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2418,6 +2418,55 @@ 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+
assert(PyUnstable_ThreadState_SetStack(tstate, start, size) == 0);
2425+
assert(!PyErr_Occurred());
2426+
2427+
_PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
2428+
assert(ts->c_stack_hard_limit == (uintptr_t)start + _PyOS_STACK_MARGIN_BYTES);
2429+
assert(ts->c_stack_top == (uintptr_t)start + size);
2430+
assert(ts->c_stack_soft_limit >= ts->c_stack_hard_limit);
2431+
assert(ts->c_stack_soft_limit < ts->c_stack_top);
2432+
}
2433+
2434+
2435+
static PyObject *
2436+
test_threadstate_set_stack(PyObject *self, PyObject *Py_UNUSED(args))
2437+
{
2438+
PyThreadState *tstate = PyThreadState_GET();
2439+
_PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
2440+
assert(!PyErr_Occurred());
2441+
2442+
uintptr_t init_base = ts->c_stack_init_base;
2443+
size_t init_top = ts->c_stack_init_top;
2444+
2445+
// Test the minimum stack size
2446+
size_t size = _PyOS_STACK_MARGIN_BYTES * 3;
2447+
void *start = (void*)(_Py_get_machine_stack_pointer() - size);
2448+
check_threadstate_set_stack(tstate, start, size);
2449+
2450+
// Test a larger size
2451+
size = 7654321;
2452+
start = (void*)(_Py_get_machine_stack_pointer() - size);
2453+
check_threadstate_set_stack(tstate, start, size);
2454+
2455+
// Test invalid size (too small)
2456+
size = 5;
2457+
start = (void*)(_Py_get_machine_stack_pointer() - size);
2458+
assert(PyUnstable_ThreadState_SetStack(tstate, start, size) == -1);
2459+
assert(PyErr_ExceptionMatches(PyExc_ValueError));
2460+
PyErr_Clear();
2461+
2462+
// Test PyUnstable_ThreadState_ResetStack()
2463+
PyUnstable_ThreadState_ResetStack(tstate);
2464+
assert(ts->c_stack_init_base == init_base);
2465+
assert(ts->c_stack_init_top == init_top);
2466+
2467+
Py_RETURN_NONE;
2468+
}
2469+
24212470
static PyMethodDef module_functions[] = {
24222471
{"get_configs", get_configs, METH_NOARGS},
24232472
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
@@ -2527,6 +2576,7 @@ static PyMethodDef module_functions[] = {
25272576
#endif
25282577
{"simple_pending_call", simple_pending_call, METH_O},
25292578
{"set_vectorcall_nop", set_vectorcall_nop, METH_O},
2579+
{"test_threadstate_set_stack", test_threadstate_set_stack, METH_NOARGS},
25302580
{NULL, NULL} /* sentinel */
25312581
};
25322582

Python/ceval.c

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ int pthread_attr_destroy(pthread_attr_t *a)
439439
#endif
440440

441441
static void
442-
hardware_stack_limits(uintptr_t *top, uintptr_t *base)
442+
hardware_stack_limits(uintptr_t *base, uintptr_t *top)
443443
{
444444
#ifdef WIN32
445445
ULONG_PTR low, high;
@@ -482,23 +482,86 @@ hardware_stack_limits(uintptr_t *top, uintptr_t *base)
482482
#endif
483483
}
484484

485-
void
486-
_Py_InitializeRecursionLimits(PyThreadState *tstate)
485+
static void
486+
tstate_set_stack(PyThreadState *tstate,
487+
uintptr_t base, uintptr_t top)
487488
{
488-
uintptr_t top;
489-
uintptr_t base;
490-
hardware_stack_limits(&top, &base);
489+
assert(base < top);
490+
assert((top - base) >= (_PyOS_STACK_MARGIN_BYTES * 3));
491+
491492
#ifdef _Py_THREAD_SANITIZER
492493
// Thread sanitizer crashes if we use more than half the stack.
493494
uintptr_t stacksize = top - base;
494-
base += stacksize/2;
495+
base += stacksize / 2;
495496
#endif
496497
_PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate;
497498
_tstate->c_stack_top = top;
498499
_tstate->c_stack_hard_limit = base + _PyOS_STACK_MARGIN_BYTES;
499500
_tstate->c_stack_soft_limit = base + _PyOS_STACK_MARGIN_BYTES * 2;
501+
502+
#ifndef NDEBUG
503+
// Sanity checks
504+
_PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
505+
assert(ts->c_stack_hard_limit <= ts->c_stack_soft_limit);
506+
assert(ts->c_stack_soft_limit < ts->c_stack_top);
507+
#endif
508+
}
509+
510+
511+
void
512+
_Py_InitializeRecursionLimits(PyThreadState *tstate)
513+
{
514+
uintptr_t base, top;
515+
hardware_stack_limits(&base, &top);
516+
assert(top != 0);
517+
518+
tstate_set_stack(tstate, base, top);
519+
_PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
520+
ts->c_stack_init_base = base;
521+
ts->c_stack_init_top = top;
522+
523+
// Test the stack pointer
524+
#if !defined(NDEBUG) && !defined(__wasi__)
525+
uintptr_t here_addr = _Py_get_machine_stack_pointer();
526+
assert(ts->c_stack_soft_limit < here_addr);
527+
assert(here_addr < ts->c_stack_top);
528+
#endif
529+
}
530+
531+
532+
int
533+
PyUnstable_ThreadState_SetStack(PyThreadState *tstate,
534+
void *stack_start_addr, size_t stack_size)
535+
{
536+
if (stack_size < (_PyOS_STACK_MARGIN_BYTES * 3)) {
537+
PyErr_Format(PyExc_ValueError,
538+
"stack_size must be at least %zu bytes",
539+
_PyOS_STACK_MARGIN_BYTES * 3);
540+
return -1;
541+
}
542+
543+
uintptr_t base = (uintptr_t)stack_start_addr;
544+
uintptr_t top = base + stack_size;
545+
tstate_set_stack(tstate, base, top);
546+
return 0;
500547
}
501548

549+
550+
void
551+
PyUnstable_ThreadState_ResetStack(PyThreadState *tstate)
552+
{
553+
_PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
554+
if (ts->c_stack_init_top != 0) {
555+
tstate_set_stack(tstate,
556+
ts->c_stack_init_base,
557+
ts->c_stack_init_top);
558+
return;
559+
}
560+
561+
_Py_InitializeRecursionLimits(tstate);
562+
}
563+
564+
502565
/* The function _Py_EnterRecursiveCallTstate() only calls _Py_CheckRecursiveCall()
503566
if the recursion_depth reaches recursion_limit. */
504567
int

Python/pystate.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1490,6 +1490,9 @@ init_threadstate(_PyThreadStateImpl *_tstate,
14901490
_tstate->c_stack_top = 0;
14911491
_tstate->c_stack_hard_limit = 0;
14921492

1493+
_tstate->c_stack_init_base = 0;
1494+
_tstate->c_stack_init_top = 0;
1495+
14931496
_tstate->asyncio_running_loop = NULL;
14941497
_tstate->asyncio_running_task = NULL;
14951498

0 commit comments

Comments
 (0)