Skip to content

Commit 73c49d5

Browse files
committed
Use chunked stack, allows larger stack when needed with reduced memory use most of the time.
1 parent 2d1a9db commit 73c49d5

File tree

3 files changed

+66
-35
lines changed

3 files changed

+66
-35
lines changed

Include/cpython/pystate.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ typedef struct _err_stackitem {
5757

5858
} _PyErr_StackItem;
5959

60+
typedef struct _stack_chunk {
61+
struct _stack_chunk *previous;
62+
size_t size;
63+
size_t top;
64+
PyObject * data[1]; /* Variable sized */
65+
} _PyStackChunk;
6066

6167
// The PyThreadState typedef is in Include/pystate.h.
6268
struct _ts {
@@ -149,10 +155,9 @@ struct _ts {
149155

150156
CFrame root_cframe;
151157

152-
PyObject **datastack_base;
158+
_PyStackChunk *datastack_chunk;
153159
PyObject **datastack_top;
154-
PyObject **datastack_soft_limit;
155-
PyObject **datastack_hard_limit;
160+
PyObject **datastack_limit;
156161
/* XXX signal handlers should also be here */
157162

158163
};

Include/internal/pycore_pystate.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ PyAPI_FUNC(int) _PyState_AddModule(
147147

148148
PyAPI_FUNC(int) _PyOS_InterruptOccurred(PyThreadState *tstate);
149149

150-
PyObject **_PyThreadState_PushLocals(PyThreadState *, int size);
150+
PyObject **_PyThreadState_PushLocals(PyThreadState *, size_t size);
151151
void _PyThreadState_PopLocals(PyThreadState *, PyObject **);
152152

153153
#ifdef __cplusplus

Python/pystate.c

Lines changed: 57 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -607,12 +607,22 @@ PyInterpreterState_GetDict(PyInterpreterState *interp)
607607
return interp->dict;
608608
}
609609

610-
/* Size of data stack
611-
* Experimentally this can be set as low as 12k and have all the tests
612-
* pass (64bit linux). */
613-
#define DATA_STACK_SIZE (62*1024)
614-
/* Additional stack space for error recovery */
615-
#define DATA_STACK_HEADROOM (2*1024)
610+
/* Minimum size of data stack chunk */
611+
#define DATA_STACK_CHUNK_SIZE (16*1024)
612+
613+
static _PyStackChunk*
614+
allocate_chunk(int size_in_bytes, _PyStackChunk* previous)
615+
{
616+
assert(size_in_bytes % sizeof(PyObject **) == 0);
617+
_PyStackChunk *res = _PyObject_VirtualAlloc(size_in_bytes);
618+
if (res == NULL) {
619+
return NULL;
620+
}
621+
res->previous = previous;
622+
res->size = size_in_bytes;
623+
res->top = 0;
624+
return res;
625+
}
616626

617627
static PyThreadState *
618628
new_threadstate(PyInterpreterState *interp, int init)
@@ -665,15 +675,14 @@ new_threadstate(PyInterpreterState *interp, int init)
665675

666676
tstate->context = NULL;
667677
tstate->context_ver = 1;
668-
size_t total_size = (DATA_STACK_SIZE+DATA_STACK_HEADROOM);
669-
tstate->datastack_base = _PyObject_VirtualAlloc(sizeof(PyObject *)*total_size);
670-
if (tstate->datastack_base == NULL) {
678+
tstate->datastack_chunk = allocate_chunk(DATA_STACK_CHUNK_SIZE, NULL);
679+
if (tstate->datastack_chunk == NULL) {
671680
PyMem_RawFree(tstate);
672681
return NULL;
673682
}
674-
tstate->datastack_top = tstate->datastack_base;
675-
tstate->datastack_hard_limit = tstate->datastack_base + total_size;
676-
tstate->datastack_soft_limit = tstate->datastack_base + DATA_STACK_SIZE;
683+
/* If top points to entry 0, then _PyThreadState_PopLocals willl try to pop this chunk */
684+
tstate->datastack_top = &tstate->datastack_chunk->data[1];
685+
tstate->datastack_limit = (PyObject **)(((char *)tstate->datastack_chunk) + DATA_STACK_CHUNK_SIZE);
677686

678687
if (init) {
679688
_PyThreadState_Init(tstate);
@@ -932,7 +941,7 @@ _PyThreadState_Delete(PyThreadState *tstate, int check_current)
932941
}
933942
}
934943
tstate_delete_common(tstate, gilstate);
935-
_PyObject_VirtualFree(tstate->datastack_base, sizeof(PyObject *)*DATA_STACK_SIZE);
944+
_PyObject_VirtualFree(tstate->datastack_chunk, tstate->datastack_chunk->size);
936945
PyMem_RawFree(tstate);
937946
}
938947

@@ -1985,28 +1994,34 @@ _Py_GetConfig(void)
19851994
return _PyInterpreterState_GetConfig(tstate->interp);
19861995
}
19871996

1997+
#define MINIMUM_OVERHEAD 1000
1998+
19881999
PyObject **
1989-
_PyThreadState_PushLocals(PyThreadState *tstate, int size)
2000+
_PyThreadState_PushLocals(PyThreadState *tstate, size_t size)
19902001
{
19912002
PyObject **res = tstate->datastack_top;
19922003
PyObject **top = res + size;
1993-
if (top >= tstate->datastack_soft_limit) {
1994-
if (top >= tstate->datastack_hard_limit) {
1995-
if (tstate->recursion_headroom) {
1996-
Py_FatalError("Cannot recover from data-stack overflow.");
1997-
}
1998-
else {
1999-
Py_FatalError("Rapid data-stack overflow.");
2000-
}
2004+
if (top >= tstate->datastack_limit) {
2005+
size_t allocate_size = DATA_STACK_CHUNK_SIZE;
2006+
while (allocate_size < sizeof(PyObject*)*(size + MINIMUM_OVERHEAD)) {
2007+
allocate_size *= 2;
20012008
}
2002-
tstate->recursion_headroom++;
2003-
_PyErr_Format(tstate, PyExc_RecursionError,
2004-
"data stack overflow");
2005-
tstate->recursion_headroom--;
2006-
return NULL;
2009+
_PyStackChunk *new = allocate_chunk(allocate_size, tstate->datastack_chunk);
2010+
if (new == NULL) {
2011+
_PyErr_SetString(tstate, PyExc_MemoryError, "Out of memory");
2012+
return NULL;
2013+
}
2014+
printf("Pushing chunk\n");
2015+
tstate->datastack_chunk->top = tstate->datastack_top - &tstate->datastack_chunk->data[0];
2016+
tstate->datastack_chunk = new;
2017+
tstate->datastack_limit = (PyObject **)(((char *)new) + allocate_size);
2018+
res = &new->data[0];
2019+
tstate->datastack_top = res + size;
2020+
}
2021+
else {
2022+
tstate->datastack_top = top;
20072023
}
2008-
tstate->datastack_top = top;
2009-
for (Py_ssize_t i=0; i < size; i++) {
2024+
for (size_t i=0; i < size; i++) {
20102025
res[i] = NULL;
20112026
}
20122027
return res;
@@ -2015,8 +2030,19 @@ _PyThreadState_PushLocals(PyThreadState *tstate, int size)
20152030
void
20162031
_PyThreadState_PopLocals(PyThreadState *tstate, PyObject **locals)
20172032
{
2018-
assert(tstate->datastack_top >= locals);
2019-
tstate->datastack_top = locals;
2033+
if (locals == &tstate->datastack_chunk->data[0]) {
2034+
printf("Popping chunk\n");
2035+
_PyStackChunk *chunk = tstate->datastack_chunk;
2036+
_PyStackChunk *previous = chunk->previous;
2037+
tstate->datastack_top = &previous->data[previous->top];
2038+
tstate->datastack_chunk = previous;
2039+
_PyObject_VirtualFree(chunk, chunk->size);
2040+
tstate->datastack_limit = (PyObject **)(((char *)tstate->datastack_chunk) + DATA_STACK_CHUNK_SIZE);
2041+
}
2042+
else {
2043+
assert(tstate->datastack_top >= locals);
2044+
tstate->datastack_top = locals;
2045+
}
20202046
}
20212047

20222048

0 commit comments

Comments
 (0)