summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Shannon <mark@hotpy.org>2021-11-29 12:34:59 +0000
committerGitHub <noreply@github.com>2021-11-29 12:34:59 +0000
commit60929576e40038ec71d896230f69e4411c82be4b (patch)
tree34dc24d0a73ef0205514202820d66c152260dc22
parent7431448b817d3bf87f71661cf8f3d537807ab2e2 (diff)
downloadcpython-git-60929576e40038ec71d896230f69e4411c82be4b.tar.gz
bpo-45786: Allocate space for frame in frame object. (GH-29729)
-rw-r--r--Include/cpython/frameobject.h6
-rw-r--r--Include/internal/pycore_frame.h7
-rw-r--r--Include/internal/pycore_gc.h1
-rw-r--r--Include/internal/pycore_interp.h14
-rw-r--r--Lib/test/test_exceptions.py6
-rw-r--r--Lib/test/test_sys.py7
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2021-11-23-15-25-00.bpo-45786.UdEciD.rst2
-rw-r--r--Modules/gcmodule.c1
-rw-r--r--Objects/frameobject.c141
-rw-r--r--Objects/genobject.c22
-rw-r--r--Python/ceval.c12
-rw-r--r--Python/frame.c38
12 files changed, 76 insertions, 181 deletions
diff --git a/Include/cpython/frameobject.h b/Include/cpython/frameobject.h
index e4cfac518b..67f98a7642 100644
--- a/Include/cpython/frameobject.h
+++ b/Include/cpython/frameobject.h
@@ -12,7 +12,9 @@ struct _frame {
int f_lineno; /* Current line number. Only valid if non-zero */
char f_trace_lines; /* Emit per-line trace events? */
char f_trace_opcodes; /* Emit per-opcode trace events? */
- char f_own_locals_memory; /* This frame owns the memory for the locals */
+ char f_owns_frame; /* This frame owns the frame */
+ /* The frame data, if this frame object owns the frame */
+ PyObject *_f_frame_data[1];
};
/* Standard object interface */
@@ -26,7 +28,7 @@ PyAPI_FUNC(PyFrameObject *) PyFrame_New(PyThreadState *, PyCodeObject *,
/* only internal use */
PyFrameObject*
-_PyFrame_New_NoTrack(struct _interpreter_frame *, int);
+_PyFrame_New_NoTrack(PyCodeObject *code);
/* The rest of the interface is specific for frame objects */
diff --git a/Include/internal/pycore_frame.h b/Include/internal/pycore_frame.h
index e36241f4a6..0015de8f8f 100644
--- a/Include/internal/pycore_frame.h
+++ b/Include/internal/pycore_frame.h
@@ -69,8 +69,7 @@ static inline void _PyFrame_StackPush(InterpreterFrame *f, PyObject *value) {
#define FRAME_SPECIALS_SIZE ((sizeof(InterpreterFrame)-1)/sizeof(PyObject *))
-InterpreterFrame *
-_PyInterpreterFrame_HeapAlloc(PyFunctionObject *func, PyObject *locals);
+InterpreterFrame *_PyFrame_Copy(InterpreterFrame *frame);
static inline void
_PyFrame_InitializeSpecials(
@@ -139,8 +138,8 @@ _PyFrame_GetFrameObject(InterpreterFrame *frame)
* take should be set to 1 for heap allocated
* frames like the ones in generators and coroutines.
*/
-int
-_PyFrame_Clear(InterpreterFrame * frame, int take);
+void
+_PyFrame_Clear(InterpreterFrame * frame);
int
_PyFrame_Traverse(InterpreterFrame *frame, visitproc visit, void *arg);
diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h
index 45e85b54c2..129d53921d 100644
--- a/Include/internal/pycore_gc.h
+++ b/Include/internal/pycore_gc.h
@@ -167,7 +167,6 @@ extern Py_ssize_t _PyGC_CollectNoFail(PyThreadState *tstate);
// Functions to clear types free lists
-extern void _PyFrame_ClearFreeList(PyInterpreterState *interp);
extern void _PyTuple_ClearFreeList(PyInterpreterState *interp);
extern void _PyFloat_ClearFreeList(PyInterpreterState *interp);
extern void _PyList_ClearFreeList(PyInterpreterState *interp);
diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h
index 18f2143200..f52ee59410 100644
--- a/Include/internal/pycore_interp.h
+++ b/Include/internal/pycore_interp.h
@@ -93,7 +93,6 @@ struct _Py_unicode_state {
# define PyTuple_MAXFREELIST 1
# define PyList_MAXFREELIST 0
# define PyDict_MAXFREELIST 0
-# define PyFrame_MAXFREELIST 0
# define _PyAsyncGen_MAXFREELIST 0
# define PyContext_MAXFREELIST 0
#endif
@@ -158,18 +157,6 @@ struct _Py_dict_state {
#endif
};
-#ifndef PyFrame_MAXFREELIST
-# define PyFrame_MAXFREELIST 200
-#endif
-
-struct _Py_frame_state {
-#if PyFrame_MAXFREELIST > 0
- PyFrameObject *free_list;
- /* number of frames currently in free_list */
- int numfree;
-#endif
-};
-
#ifndef _PyAsyncGen_MAXFREELIST
# define _PyAsyncGen_MAXFREELIST 80
#endif
@@ -332,7 +319,6 @@ struct _is {
struct _Py_tuple_state tuple;
struct _Py_list_state list;
struct _Py_dict_state dict_state;
- struct _Py_frame_state frame;
struct _Py_async_gen_state async_gen;
struct _Py_context_state context;
struct _Py_exc_state exc_state;
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index 7f087d085a..c6660043c8 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -209,7 +209,7 @@ class ExceptionTests(unittest.TestCase):
src = src.decode(encoding, 'replace')
line = src.split('\n')[lineno-1]
self.assertIn(line, cm.exception.text)
-
+
def test_error_offset_continuation_characters(self):
check = self.check
check('"\\\n"(1 for c in I,\\\n\\', 2, 2)
@@ -1342,9 +1342,7 @@ class ExceptionTests(unittest.TestCase):
"""
with SuppressCrashReport():
rc, out, err = script_helper.assert_python_failure("-c", code)
- self.assertIn(b'Fatal Python error: _PyErr_NormalizeException: '
- b'Cannot recover from MemoryErrors while '
- b'normalizing exceptions.', err)
+ self.assertIn(b'MemoryError', err)
@cpython_only
def test_MemoryError(self):
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index b0688e1e60..db8d008208 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -1320,9 +1320,10 @@ class SizeofTest(unittest.TestCase):
# sys.floatinfo
check(sys.float_info, vsize('') + self.P * len(sys.float_info))
# frame
- import inspect
- x = inspect.currentframe()
- check(x, size('3Pi3c'))
+ def func():
+ return sys._getframe()
+ x = func()
+ check(x, size('3Pi3c8P2iciP'))
# function
def func(): pass
check(func, size('14Pi'))
diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-11-23-15-25-00.bpo-45786.UdEciD.rst b/Misc/NEWS.d/next/Core and Builtins/2021-11-23-15-25-00.bpo-45786.UdEciD.rst
new file mode 100644
index 0000000000..c88f1165a0
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2021-11-23-15-25-00.bpo-45786.UdEciD.rst
@@ -0,0 +1,2 @@
+Allocate space for the interpreter frame in the frame object, to avoid an
+additional allocation when the frame object outlives the frame activation.
diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c
index 7d1a45bcae..b505676636 100644
--- a/Modules/gcmodule.c
+++ b/Modules/gcmodule.c
@@ -1038,7 +1038,6 @@ delete_garbage(PyThreadState *tstate, GCState *gcstate,
static void
clear_freelists(PyInterpreterState *interp)
{
- _PyFrame_ClearFreeList(interp);
_PyTuple_ClearFreeList(interp);
_PyFloat_ClearFreeList(interp);
_PyList_ClearFreeList(interp);
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index 2a283b3113..926a32a510 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -610,7 +610,7 @@ static PyGetSetDef frame_getsetlist[] = {
f_back next item on free list, or NULL
*/
-static void _Py_HOT_FUNCTION
+static void
frame_dealloc(PyFrameObject *f)
{
if (_PyObject_GC_IS_TRACKED(f)) {
@@ -621,9 +621,10 @@ frame_dealloc(PyFrameObject *f)
PyCodeObject *co = NULL;
/* Kill all local variables including specials, if we own them */
- if (f->f_own_locals_memory) {
- f->f_own_locals_memory = 0;
- InterpreterFrame *frame = f->f_frame;
+ if (f->f_owns_frame) {
+ f->f_owns_frame = 0;
+ assert(f->f_frame == (InterpreterFrame *)f->_f_frame_data);
+ InterpreterFrame *frame = (InterpreterFrame *)f->_f_frame_data;
/* Don't clear code object until the end */
co = frame->f_code;
frame->f_code = NULL;
@@ -633,27 +634,10 @@ frame_dealloc(PyFrameObject *f)
for (int i = 0; i < frame->stacktop; i++) {
Py_CLEAR(locals[i]);
}
- PyMem_Free(frame);
}
Py_CLEAR(f->f_back);
Py_CLEAR(f->f_trace);
-#if PyFrame_MAXFREELIST > 0
- struct _Py_frame_state *state = get_frame_state();
-#ifdef Py_DEBUG
- // frame_dealloc() must not be called after _PyFrame_Fini()
- assert(state->numfree != -1);
-#endif
- if (state->numfree < PyFrame_MAXFREELIST) {
- ++state->numfree;
- f->f_back = state->free_list;
- state->free_list = f;
- }
- else
-#endif
- {
- PyObject_GC_Del(f);
- }
-
+ PyObject_GC_Del(f);
Py_XDECREF(co);
Py_TRASHCAN_END;
}
@@ -663,7 +647,7 @@ frame_traverse(PyFrameObject *f, visitproc visit, void *arg)
{
Py_VISIT(f->f_back);
Py_VISIT(f->f_trace);
- if (f->f_own_locals_memory == 0) {
+ if (f->f_owns_frame == 0) {
return 0;
}
assert(f->f_frame->frame_obj == NULL);
@@ -715,11 +699,9 @@ static PyObject *
frame_sizeof(PyFrameObject *f, PyObject *Py_UNUSED(ignored))
{
Py_ssize_t res;
- res = sizeof(PyFrameObject);
- if (f->f_own_locals_memory) {
- PyCodeObject *code = f->f_frame->f_code;
- res += (code->co_nlocalsplus+code->co_stacksize) * sizeof(PyObject *);
- }
+ res = offsetof(PyFrameObject, _f_frame_data) + offsetof(InterpreterFrame, localsplus);
+ PyCodeObject *code = f->f_frame->f_code;
+ res += (code->co_nlocalsplus+code->co_stacksize) * sizeof(PyObject *);
return PyLong_FromSsize_t(res);
}
@@ -747,7 +729,8 @@ static PyMethodDef frame_methods[] = {
PyTypeObject PyFrame_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"frame",
- sizeof(PyFrameObject),
+ offsetof(PyFrameObject, _f_frame_data) +
+ offsetof(InterpreterFrame, localsplus),
sizeof(PyObject *),
(destructor)frame_dealloc, /* tp_dealloc */
0, /* tp_vectorcall_offset */
@@ -781,67 +764,21 @@ PyTypeObject PyFrame_Type = {
_Py_IDENTIFIER(__builtins__);
-static InterpreterFrame *
-allocate_heap_frame(PyFunctionObject *func, PyObject *locals)
+static void
+init_frame(InterpreterFrame *frame, PyFunctionObject *func, PyObject *locals)
{
PyCodeObject *code = (PyCodeObject *)func->func_code;
- int size = code->co_nlocalsplus+code->co_stacksize + FRAME_SPECIALS_SIZE;
- InterpreterFrame *frame = (InterpreterFrame *)PyMem_Malloc(sizeof(PyObject *)*size);
- if (frame == NULL) {
- PyErr_NoMemory();
- return NULL;
- }
_PyFrame_InitializeSpecials(frame, func, locals, code->co_nlocalsplus);
for (Py_ssize_t i = 0; i < code->co_nlocalsplus; i++) {
frame->localsplus[i] = NULL;
}
- return frame;
}
-static inline PyFrameObject*
-frame_alloc(InterpreterFrame *frame, int owns)
-{
- PyFrameObject *f;
-#if PyFrame_MAXFREELIST > 0
- struct _Py_frame_state *state = get_frame_state();
- if (state->free_list == NULL)
-#endif
- {
- f = PyObject_GC_New(PyFrameObject, &PyFrame_Type);
- if (f == NULL) {
- if (owns) {
- Py_XDECREF(frame->f_code);
- Py_XDECREF(frame->f_builtins);
- Py_XDECREF(frame->f_globals);
- Py_XDECREF(frame->f_locals);
- PyMem_Free(frame);
- }
- return NULL;
- }
- }
-#if PyFrame_MAXFREELIST > 0
- else
- {
-#ifdef Py_DEBUG
- // frame_alloc() must not be called after _PyFrame_Fini()
- assert(state->numfree != -1);
-#endif
- assert(state->numfree > 0);
- --state->numfree;
- f = state->free_list;
- state->free_list = state->free_list->f_back;
- _Py_NewReference((PyObject *)f);
- }
-#endif
- f->f_frame = frame;
- f->f_own_locals_memory = owns;
- return f;
-}
-
-PyFrameObject* _Py_HOT_FUNCTION
-_PyFrame_New_NoTrack(InterpreterFrame *frame, int owns)
+PyFrameObject*
+_PyFrame_New_NoTrack(PyCodeObject *code)
{
- PyFrameObject *f = frame_alloc(frame, owns);
+ int slots = code->co_nlocalsplus + code->co_stacksize;
+ PyFrameObject *f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type, slots);
if (f == NULL) {
return NULL;
}
@@ -876,15 +813,16 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
if (func == NULL) {
return NULL;
}
- InterpreterFrame *frame = allocate_heap_frame(func, locals);
- Py_DECREF(func);
- if (frame == NULL) {
+ PyFrameObject *f = _PyFrame_New_NoTrack(code);
+ if (f == NULL) {
+ Py_DECREF(func);
return NULL;
}
- PyFrameObject *f = _PyFrame_New_NoTrack(frame, 1);
- if (f) {
- _PyObject_GC_TRACK(f);
- }
+ init_frame((InterpreterFrame *)f->_f_frame_data, func, locals);
+ f->f_frame = (InterpreterFrame *)f->_f_frame_data;
+ f->f_owns_frame = 1;
+ Py_DECREF(func);
+ _PyObject_GC_TRACK(f);
return f;
}
@@ -1087,42 +1025,15 @@ PyFrame_LocalsToFast(PyFrameObject *f, int clear)
_PyFrame_LocalsToFast(f->f_frame, clear);
}
-/* Clear out the free list */
-void
-_PyFrame_ClearFreeList(PyInterpreterState *interp)
-{
-#if PyFrame_MAXFREELIST > 0
- struct _Py_frame_state *state = &interp->frame;
- while (state->free_list != NULL) {
- PyFrameObject *f = state->free_list;
- state->free_list = state->free_list->f_back;
- PyObject_GC_Del(f);
- --state->numfree;
- }
- assert(state->numfree == 0);
-#endif
-}
-
void
_PyFrame_Fini(PyInterpreterState *interp)
{
- _PyFrame_ClearFreeList(interp);
-#if defined(Py_DEBUG) && PyFrame_MAXFREELIST > 0
- struct _Py_frame_state *state = &interp->frame;
- state->numfree = -1;
-#endif
}
/* Print summary info about the state of the optimized allocator */
void
_PyFrame_DebugMallocStats(FILE *out)
{
-#if PyFrame_MAXFREELIST > 0
- struct _Py_frame_state *state = get_frame_state();
- _PyDebugAllocatorStats(out,
- "free PyFrameObject",
- state->numfree, sizeof(PyFrameObject));
-#endif
}
diff --git a/Objects/genobject.c b/Objects/genobject.c
index 24d5f3579c..04d98a2b4e 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -38,7 +38,7 @@ gen_traverse(PyGenObject *gen, visitproc visit, void *arg)
Py_VISIT(gen->gi_qualname);
InterpreterFrame *frame = gen->gi_xframe;
if (frame != NULL) {
- assert(frame->frame_obj == NULL || frame->frame_obj->f_own_locals_memory == 0);
+ assert(frame->frame_obj == NULL || frame->frame_obj->f_owns_frame == 0);
int err = _PyFrame_Traverse(frame, visit, arg);
if (err) {
return err;
@@ -136,7 +136,8 @@ gen_dealloc(PyGenObject *gen)
gen->gi_xframe = NULL;
frame->generator = NULL;
frame->previous = NULL;
- _PyFrame_Clear(frame, 1);
+ _PyFrame_Clear(frame);
+ PyMem_Free(frame);
}
if (((PyCodeObject *)gen->gi_code)->co_flags & CO_COROUTINE) {
Py_CLEAR(((PyCoroObject *)gen)->cr_origin);
@@ -254,7 +255,8 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
frame->generator = NULL;
gen->gi_xframe = NULL;
- _PyFrame_Clear(frame, 1);
+ _PyFrame_Clear(frame);
+ PyMem_Free(frame);
*presult = result;
return result ? PYGEN_RETURN : PYGEN_ERROR;
}
@@ -845,7 +847,8 @@ make_gen(PyTypeObject *type, PyFunctionObject *func, InterpreterFrame *frame)
PyGenObject *gen = PyObject_GC_New(PyGenObject, type);
if (gen == NULL) {
assert(frame->frame_obj == NULL);
- _PyFrame_Clear(frame, 1);
+ _PyFrame_Clear(frame);
+ PyMem_Free(frame);
return NULL;
}
gen->gi_xframe = frame;
@@ -929,10 +932,15 @@ gen_new_with_qualname(PyTypeObject *type, PyFrameObject *f,
/* Take ownership of the frame */
assert(f->f_frame->frame_obj == NULL);
- assert(f->f_own_locals_memory);
- gen->gi_xframe = f->f_frame;
+ assert(f->f_owns_frame);
+ gen->gi_xframe = _PyFrame_Copy((InterpreterFrame *)f->_f_frame_data);
+ if (gen->gi_xframe == NULL) {
+ Py_DECREF(f);
+ Py_DECREF(gen);
+ return NULL;
+ }
gen->gi_xframe->frame_obj = f;
- f->f_own_locals_memory = 0;
+ f->f_owns_frame = 0;
gen->gi_xframe->generator = (PyObject *) gen;
assert(PyObject_GC_IsTracked((PyObject *)f));
diff --git a/Python/ceval.c b/Python/ceval.c
index 9beb1a4368..0427361a03 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -5694,7 +5694,8 @@ make_coro_frame(PyThreadState *tstate,
}
assert(frame->frame_obj == NULL);
if (initialize_locals(tstate, func, frame->localsplus, args, argcount, kwnames)) {
- _PyFrame_Clear(frame, 1);
+ _PyFrame_Clear(frame);
+ PyMem_Free(frame);
return NULL;
}
return frame;
@@ -5750,7 +5751,7 @@ _PyEvalFramePushAndInit(PyThreadState *tstate, PyFunctionObject *func,
localsarray[i] = NULL;
}
if (initialize_locals(tstate, func, localsarray, args, argcount, kwnames)) {
- _PyFrame_Clear(frame, 0);
+ _PyFrame_Clear(frame);
return NULL;
}
return frame;
@@ -5773,11 +5774,8 @@ static int
_PyEvalFrameClearAndPop(PyThreadState *tstate, InterpreterFrame * frame)
{
--tstate->recursion_remaining;
- assert(frame->frame_obj == NULL || frame->frame_obj->f_own_locals_memory == 0);
- if (_PyFrame_Clear(frame, 0)) {
- ++tstate->recursion_remaining;
- return -1;
- }
+ assert(frame->frame_obj == NULL || frame->frame_obj->f_owns_frame == 0);
+ _PyFrame_Clear(frame);
++tstate->recursion_remaining;
_PyThreadState_PopFrame(tstate, frame);
return 0;
diff --git a/Python/frame.c b/Python/frame.c
index 79b0f77a06..21f2ced959 100644
--- a/Python/frame.c
+++ b/Python/frame.c
@@ -27,22 +27,24 @@ _PyFrame_MakeAndSetFrameObject(InterpreterFrame *frame)
assert(frame->frame_obj == NULL);
PyObject *error_type, *error_value, *error_traceback;
PyErr_Fetch(&error_type, &error_value, &error_traceback);
- PyFrameObject *f = _PyFrame_New_NoTrack(frame, 0);
+
+ PyFrameObject *f = _PyFrame_New_NoTrack(frame->f_code);
if (f == NULL) {
Py_XDECREF(error_type);
Py_XDECREF(error_value);
Py_XDECREF(error_traceback);
}
else {
+ f->f_owns_frame = 0;
+ f->f_frame = frame;
+ frame->frame_obj = f;
PyErr_Restore(error_type, error_value, error_traceback);
}
- frame->frame_obj = f;
return f;
}
-
-static InterpreterFrame *
-copy_frame_to_heap(InterpreterFrame *frame)
+InterpreterFrame *
+_PyFrame_Copy(InterpreterFrame *frame)
{
assert(frame->stacktop >= frame->f_code->co_nlocalsplus);
Py_ssize_t size = ((char*)&frame->localsplus[frame->stacktop]) - (char *)frame;
@@ -68,10 +70,11 @@ clear_specials(InterpreterFrame *frame)
static void
take_ownership(PyFrameObject *f, InterpreterFrame *frame)
{
- assert(f->f_own_locals_memory == 0);
- assert(frame->frame_obj == NULL);
-
- f->f_own_locals_memory = 1;
+ assert(f->f_owns_frame == 0);
+ Py_ssize_t size = ((char*)&frame->localsplus[frame->stacktop]) - (char *)frame;
+ memcpy((InterpreterFrame *)f->_f_frame_data, frame, size);
+ frame = (InterpreterFrame *)f->_f_frame_data;
+ f->f_owns_frame = 1;
f->f_frame = frame;
assert(f->f_back == NULL);
if (frame->previous != NULL) {
@@ -82,7 +85,6 @@ take_ownership(PyFrameObject *f, InterpreterFrame *frame)
assert(PyErr_ExceptionMatches(PyExc_MemoryError));
/* Nothing we can do about it */
PyErr_Clear();
- _PyErr_WriteUnraisableMsg("Out of memory lazily allocating frame->f_back", NULL);
}
else {
f->f_back = (PyFrameObject *)Py_NewRef(back);
@@ -94,8 +96,8 @@ take_ownership(PyFrameObject *f, InterpreterFrame *frame)
}
}
-int
-_PyFrame_Clear(InterpreterFrame * frame, int take)
+void
+_PyFrame_Clear(InterpreterFrame * frame)
{
/* It is the responsibility of the owning generator/coroutine
* to have cleared the generator pointer */
@@ -104,15 +106,9 @@ _PyFrame_Clear(InterpreterFrame * frame, int take)
PyFrameObject *f = frame->frame_obj;
frame->frame_obj = NULL;
if (Py_REFCNT(f) > 1) {
- if (!take) {
- frame = copy_frame_to_heap(frame);
- if (frame == NULL) {
- return -1;
- }
- }
take_ownership(f, frame);
Py_DECREF(f);
- return 0;
+ return;
}
Py_DECREF(f);
}
@@ -121,8 +117,4 @@ _PyFrame_Clear(InterpreterFrame * frame, int take)
Py_XDECREF(frame->localsplus[i]);
}
clear_specials(frame);
- if (take) {
- PyMem_Free(frame);
- }
- return 0;
}