Skip to content

Conversation

vstinner
Copy link
Member

@vstinner vstinner commented Oct 6, 2025

Add PyUnstable_ThreadState_SetStack() and
PyUnstable_ThreadState_ResetStack() functions to set the stack base address and stack size of a Python thread state.


📚 Documentation preview 📚: https://cpython-previews--139668.org.readthedocs.build/

Add PyUnstable_ThreadState_SetStack() and
PyUnstable_ThreadState_ResetStack() functions to set the stack base
address and stack size of a Python thread state.
@encukou
Copy link
Member

encukou commented Oct 6, 2025

This generally matches what I came up with :)

The start argument might be confusing, as the place usage grows towards.
Why not use bottom & top as in the internals?

@vstinner
Copy link
Member Author

vstinner commented Oct 6, 2025

This generally matches what I came up with :)

Good!

The start argument might be confusing, as the place usage grows towards. Why not use bottom & top as in the internals?

APIs like make_fcontext() or sigaltstack() use void *stack_start_address and size_t stack_size.

pthread_attr_setstack() and pthread_attr_getstack() also use void *stack_start_address and size_t stack_size.

.. versionadded:: next
.. c:function:: void PyUnstable_ThreadState_ResetStack(PyThreadState *tstate)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure of this name. Maybe PyUnstable_ThreadState_InitStack() would be more explicit?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reset is better, "init" implies you should only call it once.

assert(ts->c_stack_soft_limit < ts->c_stack_top);

// Test the stack pointer
#if !defined(NDEBUG) && !defined(__wasi__)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These assertions fail on WASI but I don't have time right now to investigate why.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be possible to call PyUnstable_ThreadState_SetStack just before a stack switch, when the stack pointer is still in the old stack?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as you don't make any C-API calls between the two operations, the order in which you stack switch and call PyUnstable_ThreadState_SetStack shouldn't matter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, unless we specifically say that PyUnstable_ThreadState_SetStack() should only be called after the stack switch, we'll need to remove these asserts.

@vstinner
Copy link
Member Author

vstinner commented Oct 6, 2025

cc @markshannon @zooba

@encukou
Copy link
Member

encukou commented Oct 7, 2025

On systems like musl that just set top to the current stack pointer, PyUnstable_ThreadState_ResetStack has two issues:

  • calling it “often” will essentially prevent stack protection
  • returning from a function that called it will get you past the top of the stack, which should be fine but isn't really tested.

Would it be better to save the results of the initial _Py_InitializeRecursionLimits on the thread state, and have PyUnstable_ThreadState_ResetStack return to that?

Copy link
Member

@markshannon markshannon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API looks good, but it can fail in a couple of ways.

assert(stack_size > 0);

_PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
ts->c_stack_hard_limit = (uintptr_t)stack_start_addr;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect. You need to leave _PyOS_STACK_MARGIN_BYTES space.

Don't worry, everyone seems to get this wrong. If #139294 ever gets reviewed and merged, it should prevent that happening.


void
_Py_InitializeRecursionLimits(PyThreadState *tstate)
PyUnstable_ThreadState_ResetStack(PyThreadState *tstate)
Copy link
Member

@markshannon markshannon Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also won't work. We need to record the top of the stack when initially getting the stack limits, and use that instead of _Py_get_machine_stack_pointer() otherwise, we could continually lower the stack limits, so that they become invalid.

@bedevere-app
Copy link

bedevere-app bot commented Oct 7, 2025

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

void *stack_start_addr, size_t stack_size)
{
if (stack_size == 0) {
PyErr_SetString(PyExc_ValueError, "stack_size must be greater than 0");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? A stack size of zero is no less valid than a stack size of one. Anything less than 2 * _PyOS_STACK_MARGIN_BYTES + enough_stack_to_do_something_with is useless, and enough_stack_to_do_something_with is hard to define.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants