Skip to content

Commit 24a77d8

Browse files
committed
Use per-threadstate allocated memory chunks for local variables. Dumb and slow implementation.
1 parent 431e1cb commit 24a77d8

File tree

6 files changed

+141
-68
lines changed

6 files changed

+141
-68
lines changed

Include/cpython/frameobject.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ PyAPI_FUNC(PyFrameObject *) PyFrame_New(PyThreadState *, PyCodeObject *,
6363

6464
/* only internal use */
6565
PyFrameObject*
66-
_PyFrame_New_NoTrack(PyThreadState *, PyFrameConstructor *, PyObject *);
66+
_PyFrame_New_NoTrack(PyThreadState *, PyFrameConstructor *, PyObject *, PyObject **);
6767

6868

6969
/* The rest of the interface is specific for frame objects */
@@ -78,3 +78,5 @@ PyAPI_FUNC(void) PyFrame_FastToLocals(PyFrameObject *);
7878
PyAPI_FUNC(void) _PyFrame_DebugMallocStats(FILE *out);
7979

8080
PyAPI_FUNC(PyFrameObject *) PyFrame_GetBack(PyFrameObject *frame);
81+
82+
int _PyFrame_MakeCopyOfLocals(PyFrameObject *f);

Include/internal/pycore_pystate.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ PyAPI_FUNC(int) _PyState_AddModule(
147147

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

150+
PyObject **_PyThreadState_PushLocals(PyThreadState *, int size);
151+
void _PyThreadState_PopLocals(PyThreadState *, PyObject **);
152+
150153
#ifdef __cplusplus
151154
}
152155
#endif

Lib/test/test_sys.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,11 +1274,7 @@ class C(object): pass
12741274
# frame
12751275
import inspect
12761276
x = inspect.currentframe()
1277-
ncells = len(x.f_code.co_cellvars)
1278-
nfrees = len(x.f_code.co_freevars)
1279-
localsplus = x.f_code.co_stacksize + x.f_code.co_nlocals +\
1280-
ncells + nfrees
1281-
check(x, vsize('8P3i3c' + localsplus*'P'))
1277+
check(x, size('8P3i4cP'))
12821278
# function
12831279
def func(): pass
12841280
check(func, size('14P'))

Objects/frameobject.c

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -593,25 +593,23 @@ frame_dealloc(PyFrameObject *f)
593593
PyCodeObject *co = f->f_code;
594594

595595
/* Kill all local variables */
596-
for (int i = 0; i < co->co_nlocalsplus; i++) {
597-
Py_CLEAR(f->f_localsptr[i]);
598-
}
599-
600-
/* Free items on stack */
601-
for (int i = 0; i < f->f_stackdepth; i++) {
602-
Py_XDECREF(f->f_valuestack[i]);
596+
if (f->f_own_locals_memory) {
597+
for (int i = 0; i < co->co_nlocalsplus; i++) {
598+
Py_CLEAR(f->f_localsptr[i]);
599+
}
600+
/* Free items on stack */
601+
for (int i = 0; i < f->f_stackdepth; i++) {
602+
Py_XDECREF(f->f_valuestack[i]);
603+
}
604+
PyMem_Free(f->f_localsptr);
605+
f->f_own_locals_memory = 0;
603606
}
604607
f->f_stackdepth = 0;
605608
Py_XDECREF(f->f_back);
606609
Py_DECREF(f->f_builtins);
607610
Py_DECREF(f->f_globals);
608611
Py_CLEAR(f->f_locals);
609612
Py_CLEAR(f->f_trace);
610-
if (f->f_own_locals_memory) {
611-
PyMem_Free(f->f_localsptr);
612-
f->f_localsptr = NULL;
613-
f->f_own_locals_memory = 0;
614-
}
615613
struct _Py_frame_state *state = get_frame_state();
616614
#ifdef Py_DEBUG
617615
// frame_dealloc() must not be called after _PyFrame_Fini()
@@ -709,10 +707,9 @@ static PyObject *
709707
frame_sizeof(PyFrameObject *f, PyObject *Py_UNUSED(ignored))
710708
{
711709
Py_ssize_t res;
712-
PyCodeObject *code = f->f_code;
713-
/* subtract one as it is already included in PyFrameObject */
714710
res = sizeof(PyFrameObject);
715711
if (f->f_own_locals_memory) {
712+
PyCodeObject *code = f->f_code;
716713
res += (code->co_nlocalsplus+code->co_stacksize) * sizeof(PyObject *);
717714
}
718715
return PyLong_FromSsize_t(res);
@@ -777,19 +774,32 @@ PyTypeObject PyFrame_Type = {
777774
_Py_IDENTIFIER(__builtins__);
778775

779776
static inline PyFrameObject*
780-
frame_alloc(PyCodeObject *code)
777+
frame_alloc(PyCodeObject *code, PyObject **localsarray)
781778
{
779+
int owns;
782780
PyFrameObject *f;
783-
PyObject **locals = PyMem_Malloc(sizeof(PyObject *)*(code->co_nlocalsplus+code->co_stacksize));
784-
if (locals == NULL) {
785-
PyErr_NoMemory();
786-
return NULL;
781+
if (localsarray == NULL) {
782+
localsarray = PyMem_Malloc(sizeof(PyObject *)*(code->co_nlocalsplus+code->co_stacksize));
783+
if (localsarray == NULL) {
784+
PyErr_NoMemory();
785+
return NULL;
786+
}
787+
for (Py_ssize_t i=0; i < code->co_nlocalsplus; i++) {
788+
localsarray[i] = NULL;
789+
}
790+
owns = 1;
791+
}
792+
else {
793+
owns = 0;
787794
}
788795
struct _Py_frame_state *state = get_frame_state();
789796
if (state->free_list == NULL)
790797
{
791798
f = PyObject_GC_New(PyFrameObject, &PyFrame_Type);
792799
if (f == NULL) {
800+
if (owns) {
801+
PyMem_Free(localsarray);
802+
}
793803
return NULL;
794804
}
795805
}
@@ -804,26 +814,44 @@ frame_alloc(PyCodeObject *code)
804814
state->free_list = state->free_list->f_back;
805815
_Py_NewReference((PyObject *)f);
806816
}
807-
f->f_localsptr = locals;
808-
f->f_own_locals_memory = 1;
817+
f->f_localsptr = localsarray;
818+
f->f_own_locals_memory = owns;
809819
f->f_valuestack = f->f_localsptr + code->co_nlocalsplus;
810-
for (Py_ssize_t i=0; i < code->co_nlocalsplus; i++) {
811-
f->f_localsptr[i] = NULL;
812-
}
813820
return f;
814821
}
815822

823+
int
824+
_PyFrame_MakeCopyOfLocals(PyFrameObject *f)
825+
{
826+
if (f->f_own_locals_memory) {
827+
return 0;
828+
}
829+
PyObject **copy = PyMem_Malloc(sizeof(PyObject *)*(f->f_code->co_nlocalsplus+f->f_code->co_stacksize));
830+
if (copy == NULL) {
831+
PyErr_NoMemory();
832+
return -1;
833+
}
834+
for (int i = 0; i < f->f_code->co_nlocalsplus+f->f_stackdepth; i++) {
835+
PyObject *o = f->f_localsptr[i];
836+
Py_XINCREF(o);
837+
copy[i] = o;
838+
}
839+
f->f_own_locals_memory = 1;
840+
f->f_localsptr = copy;
841+
f->f_valuestack = f->f_localsptr + f->f_code->co_nlocalsplus;
842+
return 0;
843+
}
816844

817845
PyFrameObject* _Py_HOT_FUNCTION
818-
_PyFrame_New_NoTrack(PyThreadState *tstate, PyFrameConstructor *con, PyObject *locals)
846+
_PyFrame_New_NoTrack(PyThreadState *tstate, PyFrameConstructor *con, PyObject *locals, PyObject **localsarray)
819847
{
820848
assert(con != NULL);
821849
assert(con->fc_globals != NULL);
822850
assert(con->fc_builtins != NULL);
823851
assert(con->fc_code != NULL);
824852
assert(locals == NULL || PyMapping_Check(locals));
825853

826-
PyFrameObject *f = frame_alloc((PyCodeObject *)con->fc_code);
854+
PyFrameObject *f = frame_alloc((PyCodeObject *)con->fc_code, localsarray);
827855
if (f == NULL) {
828856
return NULL;
829857
}
@@ -865,7 +893,7 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
865893
.fc_kwdefaults = NULL,
866894
.fc_closure = NULL
867895
};
868-
PyFrameObject *f = _PyFrame_New_NoTrack(tstate, &desc, locals);
896+
PyFrameObject *f = _PyFrame_New_NoTrack(tstate, &desc, locals, NULL);
869897
if (f) {
870898
_PyObject_GC_TRACK(f);
871899
}

Python/ceval.c

Lines changed: 54 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4771,10 +4771,6 @@ positional_only_passed_as_keyword(PyThreadState *tstate, PyCodeObject *co,
47714771

47724772
}
47734773

4774-
/* Exception table parsing code.
4775-
* See Objects/exception_table_notes.txt for details.
4776-
*/
4777-
47784774
static inline unsigned char *
47794775
parse_varint(unsigned char *p, int *result) {
47804776
int val = p[0] & 63;
@@ -4862,25 +4858,14 @@ get_exception_handler(PyCodeObject *code, int index, int *level, int *handler, i
48624858
return 0;
48634859
}
48644860

4865-
PyFrameObject *
4866-
_PyEval_MakeFrameVector(PyThreadState *tstate,
4867-
PyFrameConstructor *con, PyObject *locals,
4868-
PyObject *const *args, Py_ssize_t argcount,
4869-
PyObject *kwnames)
4861+
static int
4862+
initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
4863+
PyObject **fastlocals, PyObject *const *args,
4864+
Py_ssize_t argcount, PyObject *kwnames)
48704865
{
4871-
assert(is_tstate_valid(tstate));
4872-
48734866
PyCodeObject *co = (PyCodeObject*)con->fc_code;
4874-
assert(con->fc_defaults == NULL || PyTuple_CheckExact(con->fc_defaults));
48754867
const Py_ssize_t total_args = co->co_argcount + co->co_kwonlyargcount;
4876-
4877-
/* Create the frame */
4878-
PyFrameObject *f = _PyFrame_New_NoTrack(tstate, con, locals);
4879-
if (f == NULL) {
4880-
return NULL;
4881-
}
4882-
PyObject **fastlocals = f->f_localsptr;
4883-
PyObject **freevars = f->f_localsptr + co->co_nlocals;
4868+
PyObject **freevars = fastlocals + co->co_nlocals;
48844869

48854870
/* Create a dictionary for keyword parameters (**kwags) */
48864871
PyObject *kwdict;
@@ -5086,25 +5071,33 @@ _PyEval_MakeFrameVector(PyThreadState *tstate,
50865071
freevars[PyTuple_GET_SIZE(co->co_cellvars) + i] = o;
50875072
}
50885073

5089-
return f;
5074+
return 0;
50905075

50915076
fail: /* Jump here from prelude on failure */
5077+
return -1;
50925078

5093-
/* decref'ing the frame can cause __del__ methods to get invoked,
5094-
which can call back into Python. While we're done with the
5095-
current Python frame (f), the associated C stack is still in use,
5096-
so recursion_depth must be boosted for the duration.
5097-
*/
5098-
if (Py_REFCNT(f) > 1) {
5099-
Py_DECREF(f);
5100-
_PyObject_GC_TRACK(f);
5079+
}
5080+
5081+
5082+
PyFrameObject *
5083+
_PyEval_MakeFrameVector(PyThreadState *tstate,
5084+
PyFrameConstructor *con, PyObject *locals,
5085+
PyObject *const *args, Py_ssize_t argcount,
5086+
PyObject *kwnames, PyObject** localsarray)
5087+
{
5088+
assert(is_tstate_valid(tstate));
5089+
assert(con->fc_defaults == NULL || PyTuple_CheckExact(con->fc_defaults));
5090+
5091+
/* Create the frame */
5092+
PyFrameObject *f = _PyFrame_New_NoTrack(tstate, con, locals, localsarray);
5093+
if (f == NULL) {
5094+
return NULL;
51015095
}
5102-
else {
5103-
++tstate->recursion_depth;
5096+
if (initialize_locals(tstate, con, f->f_localsptr, args, argcount, kwnames)) {
51045097
Py_DECREF(f);
5105-
--tstate->recursion_depth;
5098+
return NULL;
51065099
}
5107-
return NULL;
5100+
return f;
51085101
}
51095102

51105103
static PyObject *
@@ -5142,15 +5135,31 @@ _PyEval_Vector(PyThreadState *tstate, PyFrameConstructor *con,
51425135
PyObject* const* args, size_t argcount,
51435136
PyObject *kwnames)
51445137
{
5138+
PyObject **localsarray;
5139+
PyCodeObject *code = (PyCodeObject *)con->fc_code;
5140+
int is_coro = code->co_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR);
5141+
if (is_coro) {
5142+
localsarray = NULL;
5143+
}
5144+
else {
5145+
localsarray = _PyThreadState_PushLocals(tstate, code->co_nlocalsplus + code->co_stacksize);
5146+
if (localsarray == NULL) {
5147+
return NULL;
5148+
}
5149+
}
51455150
PyFrameObject *f = _PyEval_MakeFrameVector(
5146-
tstate, con, locals, args, argcount, kwnames);
5151+
tstate, con, locals, args, argcount, kwnames, localsarray);
51475152
if (f == NULL) {
5153+
if (!is_coro) {
5154+
_PyThreadState_PopLocals(tstate, localsarray);
5155+
}
51485156
return NULL;
51495157
}
5150-
if (((PyCodeObject *)con->fc_code)->co_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) {
5158+
if (is_coro) {
51515159
return make_coro(con, f);
51525160
}
51535161
PyObject *retval = _PyEval_EvalFrame(tstate, f, 0);
5162+
assert(f->f_stackdepth == 0);
51545163

51555164
/* decref'ing the frame can cause __del__ methods to get invoked,
51565165
which can call back into Python. While we're done with the
@@ -5160,12 +5169,22 @@ _PyEval_Vector(PyThreadState *tstate, PyFrameConstructor *con,
51605169
if (Py_REFCNT(f) > 1) {
51615170
Py_DECREF(f);
51625171
_PyObject_GC_TRACK(f);
5172+
if (_PyFrame_MakeCopyOfLocals(f)) {
5173+
Py_XDECREF(retval);
5174+
return NULL;
5175+
}
51635176
}
51645177
else {
51655178
++tstate->recursion_depth;
51665179
Py_DECREF(f);
51675180
--tstate->recursion_depth;
51685181
}
5182+
assert (!is_coro);
5183+
5184+
for (int i = 0; i < code->co_nlocalsplus; i++) {
5185+
Py_XDECREF(localsarray[i]);
5186+
}
5187+
_PyThreadState_PopLocals(tstate, localsarray);
51695188
return retval;
51705189
}
51715190

Python/pystate.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1969,6 +1969,31 @@ _Py_GetConfig(void)
19691969
return _PyInterpreterState_GetConfig(tstate->interp);
19701970
}
19711971

1972+
/* Dumbest possible (and very inefficient) implementation */
1973+
1974+
PyObject **
1975+
_PyThreadState_PushLocals(PyThreadState *tstate, int size)
1976+
{
1977+
(void)tstate;
1978+
PyObject **res = PyMem_Malloc(sizeof(PyObject **)*size);
1979+
if (res == NULL) {
1980+
PyErr_NoMemory();
1981+
return NULL;
1982+
}
1983+
for (Py_ssize_t i=0; i < size; i++) {
1984+
res[i] = NULL;
1985+
}
1986+
return res;
1987+
}
1988+
1989+
void
1990+
_PyThreadState_PopLocals(PyThreadState *tstate, PyObject **locals)
1991+
{
1992+
(void)tstate;
1993+
PyMem_Free(locals);
1994+
}
1995+
1996+
19721997
#ifdef __cplusplus
19731998
}
19741999
#endif

0 commit comments

Comments
 (0)