FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

bpo-47215: Add undocumented, unstable FrameStack API for use by greenlets and similar libraries. by markshannon · Pull Request #32303 · python/cpython · GitHub

/ cpython Public
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .c  (2) .h  (2) All 2 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
29 changes: 23 additions & 6 deletions Include/cpython/pystate.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,18 @@ typedef struct _err_stackitem {

typedef struct _stack_chunk {
struct _stack_chunk *previous;
size_t size;
size_t top;
int size_in_bytes;
int free;
PyObject * data[1]; /* Variable sized */
} _PyStackChunk;

typedef struct _frame_stack {
_PyStackChunk *current_chunk;
PyObject **limit;
int free;
int chunk_size;
} _PyFrameStack;

struct _ts {
/* See Python/ceval.c for comments explaining most fields */

Expand Down Expand Up @@ -178,10 +185,7 @@ struct _ts {
uint64_t id;

PyTraceInfo trace_info;

_PyStackChunk *datastack_chunk;
PyObject **datastack_top;
PyObject **datastack_limit;
_PyFrameStack frame_stack;
/* XXX signal handlers should also be here */

/* The following fields are here to avoid allocation during init.
Expand Down Expand Up @@ -367,3 +371,16 @@ typedef int (*crossinterpdatafunc)(PyObject *, _PyCrossInterpreterData *);

PyAPI_FUNC(int) _PyCrossInterpreterData_RegisterClass(PyTypeObject *, crossinterpdatafunc);
PyAPI_FUNC(crossinterpdatafunc) _PyCrossInterpreterData_Lookup(PyObject *);

/* UNSTABLE API for stackful coroutines.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

What about hiding this behind an ifdef, it would technically be an api break, but would make the intention clearer:

#ifdef _PYTHON_C_UNSTABLE_FRAMESTACK_API
...
#endif

* It it the responsibility of the caller to manage the memory for the _PyFrameStack struct.
* The memory for the actual frame stack will be managed by the VM.
* All functions need the GIL to be held.
*/

/* Initialize fs with given chunk size */
PyAPI_FUNC(void) _PyFrameStack_Init(_PyFrameStack *fs, int chunk_size);
/* Swap the frame stack of the current thread with fs */
PyAPI_FUNC(void) _PyFrameStack_Swap(_PyFrameStack *fs);
/* Free any allocated memory chunks for fs. */
PyAPI_FUNC(void) _PyFrameStack_Clear(_PyFrameStack *fs);
14 changes: 7 additions & 7 deletions Include/internal/pycore_frame.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,11 @@ static inline bool
_PyThreadState_HasStackSpace(PyThreadState *tstate, int size)
{
assert(
(tstate->datastack_top == NULL && tstate->datastack_limit == NULL)
(tstate->frame_stack.limit == NULL && tstate->frame_stack.free <= 0)
||
(tstate->datastack_top != NULL && tstate->datastack_limit != NULL)
(tstate->frame_stack.limit != NULL && tstate->frame_stack.free >= 0)
);
return tstate->datastack_top != NULL &&
size < tstate->datastack_limit - tstate->datastack_top;
return size < tstate->frame_stack.free;
}

extern _PyInterpreterFrame *
Expand All @@ -215,9 +214,10 @@ _PyFrame_PushUnchecked(PyThreadState *tstate, PyFunctionObject *func)
{
CALL_STAT_INC(frames_pushed);
PyCodeObject *code = (PyCodeObject *)func->func_code;
_PyInterpreterFrame *new_frame = (_PyInterpreterFrame *)tstate->datastack_top;
tstate->datastack_top += code->co_framesize;
assert(tstate->datastack_top < tstate->datastack_limit);
PyObject **top = tstate->frame_stack.limit - tstate->frame_stack.free;
_PyInterpreterFrame *new_frame = (_PyInterpreterFrame *)top;
tstate->frame_stack.free -= code->co_framesize;
assert(top < tstate->frame_stack.limit);
_PyFrame_InitializeSpecials(new_frame, func, NULL, code);
return new_frame;
}
Expand Down
2 changes: 1 addition & 1 deletion Python/ceval.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -5756,7 +5756,7 @@ _PyEvalFrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame * frame)
// Make sure that this is, indeed, the top frame. We can't check this in
// _PyThreadState_PopFrame, since f_code is already cleared at that point:
assert((PyObject **)frame + frame->f_code->co_framesize ==
tstate->datastack_top);
tstate->frame_stack.limit - tstate->frame_stack.free);
tstate->recursion_remaining--;
assert(frame->frame_obj == NULL || frame->frame_obj->f_frame == frame);
assert(frame->owner == FRAME_OWNED_BY_THREAD);
Expand Down
138 changes: 99 additions & 39 deletions Python/pystate.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -736,8 +736,8 @@ allocate_chunk(int size_in_bytes, _PyStackChunk* previous)
return NULL;
}
res->previous = previous;
res->size = size_in_bytes;
res->top = 0;
res->size_in_bytes = size_in_bytes;
res->free = 0;
return res;
}

Expand Down Expand Up @@ -798,9 +798,7 @@ init_threadstate(PyThreadState *tstate,
tstate->exc_info = &tstate->exc_state;

tstate->cframe = &tstate->root_cframe;
tstate->datastack_chunk = NULL;
tstate->datastack_top = NULL;
tstate->datastack_limit = NULL;
_PyFrameStack_Init(&tstate->frame_stack, DATA_STACK_CHUNK_SIZE);

tstate->_initialized = 1;
}
Expand Down Expand Up @@ -1093,13 +1091,7 @@ tstate_delete_common(PyThreadState *tstate,
{
PyThread_tss_set(&gilstate->autoTSSkey, NULL);
}
_PyStackChunk *chunk = tstate->datastack_chunk;
tstate->datastack_chunk = NULL;
while (chunk != NULL) {
_PyStackChunk *prev = chunk->previous;
_PyObject_VirtualFree(chunk, chunk->size);
chunk = prev;
}
_PyFrameStack_Clear(&tstate->frame_stack);
}

static void
Expand Down Expand Up @@ -2167,27 +2159,62 @@ _Py_GetConfig(void)
#define MINIMUM_OVERHEAD 1000

static PyObject **
push_chunk(PyThreadState *tstate, int size)
stack_chunk_limit(_PyStackChunk *chunk)
{
return (PyObject **)(((char *)chunk) + chunk->size_in_bytes);
}

static bool
framechunk_is_consistent(_PyStackChunk *chunk)
{
bool ok = chunk->size_in_bytes > MINIMUM_OVERHEAD;
int size = stack_chunk_limit(chunk) - &chunk->data[0];
ok &= chunk->free >= 0;
ok &= chunk->free < size;
return ok;
}

static bool
framestack_is_consistent(_PyFrameStack *fs)
{
bool ok = fs->free >= 0;
ok &= fs->free < fs->chunk_size;
if (fs->current_chunk) {
ok &= (char *)fs->limit == ((char *)fs->current_chunk) + fs->current_chunk->size_in_bytes;
}
_PyStackChunk *chunk = fs->current_chunk;
while (chunk) {
ok &= framechunk_is_consistent(chunk);
chunk = chunk->previous;
}
return ok;
}


static PyObject **
push_chunk(_PyFrameStack *frame_stack, int size)
{
int allocate_size = DATA_STACK_CHUNK_SIZE;
assert(framestack_is_consistent(frame_stack));
int allocate_size = frame_stack->chunk_size;
while (allocate_size < (int)sizeof(PyObject*)*(size + MINIMUM_OVERHEAD)) {
allocate_size *= 2;
}
_PyStackChunk *new = allocate_chunk(allocate_size, tstate->datastack_chunk);
_PyStackChunk *new = allocate_chunk(allocate_size, frame_stack->current_chunk);
if (new == NULL) {
return NULL;
}
if (tstate->datastack_chunk) {
tstate->datastack_chunk->top = tstate->datastack_top -
&tstate->datastack_chunk->data[0];
if (frame_stack->current_chunk) {
frame_stack->current_chunk->free = frame_stack->free;
}
tstate->datastack_chunk = new;
tstate->datastack_limit = (PyObject **)(((char *)new) + allocate_size);
frame_stack->current_chunk = new;
frame_stack->limit = stack_chunk_limit(new);
// When new is the "root" chunk (i.e. new->previous == NULL), we can keep
// _PyThreadState_PopFrame from freeing it later by "skipping" over the
// first element:
PyObject **res = &new->data[new->previous == NULL];
tstate->datastack_top = res + size;
PyObject **top = res + size;
frame_stack->free = frame_stack->limit - top;
assert(framestack_is_consistent(frame_stack));
return res;
}

Expand All @@ -2196,35 +2223,68 @@ _PyThreadState_PushFrame(PyThreadState *tstate, size_t size)
{
assert(size < INT_MAX/sizeof(PyObject *));
if (_PyThreadState_HasStackSpace(tstate, (int)size)) {
_PyInterpreterFrame *res = (_PyInterpreterFrame *)tstate->datastack_top;
tstate->datastack_top += size;
_PyInterpreterFrame *res = (_PyInterpreterFrame *)(tstate->frame_stack.limit - tstate->frame_stack.free);
tstate->frame_stack.free -= size;
return res;
}
return (_PyInterpreterFrame *)push_chunk(tstate, (int)size);
return (_PyInterpreterFrame *)push_chunk(&tstate->frame_stack, (int)size);
}

void
pop_chunk(PyThreadState *tstate)
{
_PyStackChunk *chunk = tstate->frame_stack.current_chunk;
_PyStackChunk *previous = chunk->previous;
// push_chunk ensures that the root chunk is never popped:
assert(previous);
tstate->frame_stack.free = previous->free;
tstate->frame_stack.current_chunk = previous;
tstate->frame_stack.limit = (PyObject **)(((char *)previous) + previous->size_in_bytes);
_PyObject_VirtualFree(chunk, chunk->size_in_bytes);
}

void
_PyThreadState_PopFrame(PyThreadState *tstate, _PyInterpreterFrame * frame)
{
assert(tstate->datastack_chunk);
assert(framestack_is_consistent(&tstate->frame_stack));
assert(tstate->frame_stack.current_chunk);
PyObject **base = (PyObject **)frame;
if (base == &tstate->datastack_chunk->data[0]) {
_PyStackChunk *chunk = tstate->datastack_chunk;
_PyStackChunk *previous = chunk->previous;
// push_chunk ensures that the root chunk is never popped:
assert(previous);
tstate->datastack_top = &previous->data[previous->top];
tstate->datastack_chunk = previous;
_PyObject_VirtualFree(chunk, chunk->size);
tstate->datastack_limit = (PyObject **)(((char *)previous) + previous->size);
}
else {
assert(tstate->datastack_top);
assert(tstate->datastack_top >= base);
tstate->datastack_top = base;
if (base != &tstate->frame_stack.current_chunk->data[0]) {
assert(tstate->frame_stack.limit);
tstate->frame_stack.free = tstate->frame_stack.limit - base;
assert(tstate->frame_stack.free > 0);
return;
}
pop_chunk(tstate);
}

void _PyFrameStack_Init(_PyFrameStack *fs, int chunk_size)
{
fs->chunk_size = chunk_size;
fs->current_chunk = NULL;
fs->free = 0;
fs->limit = NULL;
}

void _PyFrameStack_Swap(_PyFrameStack *fs)
{
PyThreadState *tstate = _PyThreadState_GET();
_PyFrameStack temp = *fs;
*fs = tstate->frame_stack;
tstate->frame_stack = temp;
}

void _PyFrameStack_Clear(_PyFrameStack *fs)
{
_PyStackChunk *chunk = fs->current_chunk;
fs->current_chunk = NULL;
fs->free = 0;
while (chunk != NULL) {
_PyStackChunk *previous = chunk->previous;
_PyObject_VirtualFree(chunk, chunk->size_in_bytes);
chunk = previous;
}
}

#ifdef __cplusplus
}
Expand Down

Back | FazBrowse Home | New Git URL