diff options
Diffstat (limited to 'bubbob/statesaver.c')
-rw-r--r-- | bubbob/statesaver.c | 610 |
1 files changed, 610 insertions, 0 deletions
diff --git a/bubbob/statesaver.c b/bubbob/statesaver.c new file mode 100644 index 0000000..5baf033 --- /dev/null +++ b/bubbob/statesaver.c @@ -0,0 +1,610 @@ +/** High-performance deep copy. + + This one can copy running generators and their frames! + + statesaver.copy(x) -> recursive copy of x + + You have precise control over what is copied and what should be shared. + By default, *only* common built-in types are copied. Unrecognized + object types are shared. The copied built-in types are: + + - tuple + - list + - dict + - functions, for possibly mutable func_defaults (func_globals is shared) + - methods, for im_self and im_func (im_class is shared) + - running or stopped generators (yeah!) + - sequence iterators + + Old-style class instances are only copied if they have an + inst_build() method, which is called with no argument and must + return a new instance whose __dict__ is not filled (it will be + filled by the copying mecanisms). Suggested implementation: + + def inst_build(self): + return new.instance(self.__class__) + + New-style class instances are not supported (i.e. always shared). +**/ + +#include <Python.h> +#include <compile.h> +#include <frameobject.h> +#include <eval.h> + + +static PyObject* copyrec(PyObject* o); /* forward */ + +static PyObject* empty_iterator; + + +static PyObject* genbuild(PyObject* g) +{ + PyObject* x; + PyFrameObject* f; + PyCodeObject* co; + PyObject** dummy; + int i, res, ncells, nfrees; + + x = PyObject_GetAttrString(g, "gi_running"); + if (x == NULL) + return NULL; + res = PyObject_IsTrue(x); + Py_DECREF(x); + if (res < 0) + return NULL; + if (res) { + PyErr_SetString(PyExc_ValueError, "generator is running"); + return NULL; + } + + x = PyObject_GetAttrString(g, "gi_frame"); + if (x == NULL) + return NULL; + if (!PyFrame_Check(x)) { + if (x == Py_None) { + /* Python 2.5 only: exhausted generators have g.gi_frame == None */ + Py_DECREF(x); + Py_INCREF(empty_iterator); + return empty_iterator; + } + PyErr_SetString(PyExc_TypeError, "g.gi_frame must be a frame object"); + goto error; + } + f = (PyFrameObject*) x; + co = f->f_code; + + if (!(co->co_flags & CO_GENERATOR)) { + PyErr_SetString(PyExc_ValueError, "the frame is not from a generator"); + goto error; + } + if (f->f_stacktop == NULL) { + Py_DECREF(f); + Py_INCREF(g); /* exhausted -- can return 'g' itself */ + return g; + } + ncells = PyTuple_GET_SIZE(co->co_cellvars); + nfrees = PyTuple_GET_SIZE(co->co_freevars); + if (nfrees || ncells) { + PyErr_SetString(PyExc_ValueError, "generator has cell or free vars"); + goto error; + } + + if (co->co_argcount == 0) + dummy = NULL; + else + { + dummy = (PyObject**) malloc(co->co_argcount * sizeof(PyObject*)); + if (dummy == NULL) + { + PyErr_NoMemory(); + goto error; + } + for (i=0; i<co->co_argcount; i++) + dummy[i] = Py_None; + } + x = PyEval_EvalCodeEx(co, f->f_globals, f->f_locals, + dummy, co->co_argcount, NULL, 0, + NULL, 0, NULL); + if (dummy) + free(dummy); + Py_DECREF(f); + return x; + + error: + Py_DECREF(x); + return NULL; +} + +static int gencopy(PyObject* g2, PyObject* g) +{ + PyObject* x; + PyFrameObject* f = NULL; + PyFrameObject* f2 = NULL; + PyCodeObject* co; + int i, res; + + if (g != g2) + { + if (g2->ob_type != g->ob_type) + { + if (g2 == empty_iterator) + return 0; + PyErr_SetString(PyExc_TypeError, "type mismatch"); + return -1; + } + + x = PyObject_GetAttrString(g, "gi_frame"); + if (x == NULL) + return -1; + if (!PyFrame_Check(x)) { + PyErr_SetString(PyExc_TypeError, "g.gi_frame must be a frame object"); + Py_DECREF(x); + goto error; + } + f = (PyFrameObject*) x; + co = f->f_code; + + x = PyObject_GetAttrString(g2, "gi_frame"); + if (x == NULL) + return -1; + if (!PyFrame_Check(x)) { + PyErr_SetString(PyExc_TypeError, "returned gi_frame"); + Py_DECREF(x); + goto error; + } + f2 = (PyFrameObject*) x; + + if (f2->f_code != co) { + PyErr_SetString(PyExc_TypeError, "generator code mismatch"); + goto error; + } + + if (f2->f_stacktop != NULL) + while (f2->f_stacktop != f2->f_localsplus) + { + f2->f_stacktop--; + Py_XDECREF(*f2->f_stacktop); + } + + res = f->f_stacktop - f->f_localsplus; + f2->f_lasti = f->f_lasti; + f2->f_iblock = f->f_iblock; + memcpy(f2->f_blockstack, f->f_blockstack, sizeof(PyTryBlock)*f->f_iblock); + f2->f_stacktop = f2->f_localsplus; + for (i=0; i<res; i++) + { + x = f->f_localsplus[i]; + if (x != NULL) + x = copyrec(x); + *f2->f_stacktop++ = x; + } + } + return 0; + + error: + Py_XDECREF(f); + Py_XDECREF(f2); + return -1; +} + + +typedef struct { + PyObject_HEAD + long it_index; + PyObject *it_seq; /* Set to NULL when iterator is exhausted */ +} seqiterobject; +static PyObject* seqiterbuild(PyObject* o) +{ + seqiterobject* iter = (seqiterobject*) o; + if (iter->it_seq == NULL) + { + Py_INCREF(iter); /* exhausted */ + return (PyObject*) iter; + } + else + return PySeqIter_New(iter->it_seq); +} +static int seqitercopy(PyObject* o2, PyObject* o) +{ + PyObject* x; + seqiterobject* iter = (seqiterobject*) o; + seqiterobject* iter2 = (seqiterobject*) o2; + + iter2->it_index = iter->it_index; + if (iter->it_seq != NULL) + { + x = copyrec(iter->it_seq); + Py_XDECREF(iter2->it_seq); + iter2->it_seq = x; + } + return 0; +} + +#if PY_VERSION_HEX >= 0x02030000 /* 2.3 */ +/* pff */ +typedef struct { + PyObject_HEAD + long it_index; + PyListObject *it_seq; /* Set to NULL when iterator is exhausted */ +} listiterobject; +static PyTypeObject* PyListIter_TypePtr; +static PyObject* listiterbuild(PyObject* o) +{ + listiterobject* iter = (listiterobject*) o; + if (iter->it_seq == NULL) + { + Py_INCREF(iter); /* exhausted */ + return (PyObject*) iter; + } + else + return PyList_Type.tp_iter((PyObject*) iter->it_seq); +} +static int listitercopy(PyObject* o2, PyObject* o) +{ + PyObject* x; + listiterobject* iter = (listiterobject*) o; + listiterobject* iter2 = (listiterobject*) o2; + + iter2->it_index = iter->it_index; + if (iter->it_seq != NULL) + { + x = copyrec((PyObject*) iter->it_seq); + Py_XDECREF(iter2->it_seq); + iter2->it_seq = (PyListObject*) x; + } + return 0; +} + +typedef struct { + PyObject_HEAD + long it_index; + PyTupleObject *it_seq; /* Set to NULL when iterator is exhausted */ +} tupleiterobject; +static PyTypeObject* PyTupleIter_TypePtr; +static PyObject* tupleiterbuild(PyObject* o) +{ + tupleiterobject* iter = (tupleiterobject*) o; + if (iter->it_seq == NULL) + { + Py_INCREF(iter); /* exhausted */ + return (PyObject*) iter; + } + else + return PyTuple_Type.tp_iter((PyObject*) iter->it_seq); +} +static int tupleitercopy(PyObject* o2, PyObject* o) +{ + PyObject* x; + tupleiterobject* iter = (tupleiterobject*) o; + tupleiterobject* iter2 = (tupleiterobject*) o2; + + iter2->it_index = iter->it_index; + if (iter->it_seq != NULL) + { + x = copyrec((PyObject*) iter->it_seq); + Py_XDECREF(iter2->it_seq); + iter2->it_seq = (PyTupleObject*) x; + } + return 0; +} +#endif /* PY_VERSION_HEX >= 0x02030000 */ + + +/* HACKS HACKS HACKS */ + +typedef struct { + PyObject_HEAD + PyObject* o; +} KeyObject; + +#define KEYS_BY_BLOCK 1024 + +struct key_block { + KeyObject keys[KEYS_BY_BLOCK]; + struct key_block* next; +}; + +static long key_hash(KeyObject* k) +{ + return (long)(k->o); +} + +static PyObject* key_richcmp(KeyObject* k1, KeyObject* k2, int op) +{ + PyObject* r; + assert(op == 2 /*PyCmp_EQ*/ ); + r = k1->o == k2->o ? Py_True : Py_False; + Py_INCREF(r); + return r; +} + +static PyTypeObject keytype = { + PyObject_HEAD_INIT(NULL) + 0, + "key", + sizeof(KeyObject), + 0, + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + (hashfunc)key_hash, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + (richcmpfunc)key_richcmp, /* tp_richcompare */ +}; + + +/* global state */ +static PyObject* ss_memo; +static struct key_block* ss_block; +static int ss_next_in_block; +static PyObject *ss_error, *ss_errinst, *ss_errtb; + +static PyObject* str_inst_build; +static PyTypeObject* GeneratorType; + +/* never returns NULL, and never returns with a Python exception set! */ +static PyObject* copyrec(PyObject* o) +{ + PyTypeObject* t; + PyObject* n; + PyObject* key; + KeyObject* fkey; + + if (o == Py_None || o->ob_type == &PyInt_Type || + o->ob_type == &PyString_Type || o->ob_type == &PyFloat_Type || + o == empty_iterator) + { + Py_INCREF(o); + return o; + } + if (ss_next_in_block < 0) + { + struct key_block* b = (struct key_block*) malloc(sizeof(struct key_block)); + if (!b) { PyErr_NoMemory(); goto fail1; } + b->next = ss_block; + ss_block = b; + ss_next_in_block = KEYS_BY_BLOCK - 1; + } + fkey = ss_block->keys + ss_next_in_block; + fkey->ob_refcnt = 1; + fkey->ob_type = &keytype; + fkey->o = o; + key = (PyObject*) fkey; + n = PyDict_GetItem(ss_memo, key); + if (n) + { + Py_INCREF(n); + return n; + } + ss_next_in_block--; + Py_INCREF(o); /* reference stored in 'fkey->o' */ + t = o->ob_type; + if (t == &PyTuple_Type) + { + int i, count = PyTuple_GET_SIZE(o); + n = PyTuple_New(count); + if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail; + for (i=0; i<count; i++) + PyTuple_SET_ITEM(n, i, copyrec(PyTuple_GET_ITEM(o, i))); + return n; + } + if (t == &PyList_Type) + { + int i, count = PyList_GET_SIZE(o); + n = PyList_New(count); + if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail; + for (i=0; i<count; i++) + PyList_SET_ITEM(n, i, copyrec(PyList_GET_ITEM(o, i))); + return n; + } + if (t == &PyDict_Type) + { + int i = 0; + PyObject* dictkey; + PyObject* dictvalue; + n = PyDict_New(); + if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail; + while (PyDict_Next(o, &i, &dictkey, &dictvalue)) + if (PyDict_SetItem(n, copyrec(dictkey), copyrec(dictvalue))) + goto fail; + return n; + } + if (t == &PyInstance_Type) + { + int i = 0; + PyObject* dictkey; + PyObject* dictvalue; + PyObject* dsrc; + PyObject* ddest; + PyObject* inst_build = PyObject_GetAttr(o, str_inst_build); + if (inst_build == NULL) + { + PyErr_Clear(); + goto unmodified; + } + n = PyObject_CallObject(inst_build, NULL); + Py_DECREF(inst_build); + if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail; + dsrc = ((PyInstanceObject*) o)->in_dict; + ddest = ((PyInstanceObject*) n)->in_dict; + while (PyDict_Next(dsrc, &i, &dictkey, &dictvalue)) + if (PyDict_SetItem(ddest, copyrec(dictkey), copyrec(dictvalue))) + goto fail; + return n; + } + if (t == &PyFunction_Type) + { + int i, count; + PyObject* tsrc = PyFunction_GET_DEFAULTS(o); + PyObject* tdest; + if (!tsrc) goto unmodified; + count = PyTuple_GET_SIZE(tsrc); + if (count == 0) goto unmodified; + n = PyFunction_New(PyFunction_GET_CODE(o), PyFunction_GET_GLOBALS(o)); + if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail; + tdest = PyTuple_New(count); + if (!tdest) goto fail; + for (i=0; i<count; i++) + PyTuple_SET_ITEM(tdest, i, copyrec(PyTuple_GET_ITEM(tsrc, i))); + i = PyFunction_SetDefaults(n, tdest); + Py_DECREF(tdest); + if (i) goto fail; + return n; + } + if (t == &PyMethod_Type) + { + PyObject* x; + n = PyMethod_New(PyMethod_GET_FUNCTION(o), + PyMethod_GET_SELF(o), + PyMethod_GET_CLASS(o)); + if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail; + x = copyrec(PyMethod_GET_FUNCTION(n)); + Py_DECREF(PyMethod_GET_FUNCTION(n)); + PyMethod_GET_FUNCTION(n) = x; + x = copyrec(PyMethod_GET_SELF(n)); + Py_DECREF(PyMethod_GET_SELF(n)); + PyMethod_GET_SELF(n) = x; + return n; + } + if (t == GeneratorType) + { + n = genbuild(o); + if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail; + if (gencopy(n, o)) goto fail; + return n; + } + if (t == &PySeqIter_Type) + { + n = seqiterbuild(o); + if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail; + if (seqitercopy(n, o)) goto fail; + return n; + } + #if PY_VERSION_HEX >= 0x02030000 /* 2.3 */ + if (t == PyListIter_TypePtr) + { + n = listiterbuild(o); + if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail; + if (listitercopy(n, o)) goto fail; + return n; + } + if (t == PyTupleIter_TypePtr) + { + n = tupleiterbuild(o); + if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail; + if (tupleitercopy(n, o)) goto fail; + return n; + } + #endif + + ss_next_in_block++; + return o; /* reference no longer stored in 'fkey->o' */ + + unmodified: + PyDict_SetItem(ss_memo, key, o); + Py_INCREF(o); + return o; + + fail1: + n = NULL; + fail: + Py_INCREF(o); + Py_XDECREF(n); + if (ss_error == NULL) + PyErr_Fetch(&ss_error, &ss_errinst, &ss_errtb); + else + PyErr_Clear(); + return o; +} + +static PyObject* sscopy(PyObject* self, PyObject* o) +{ + PyObject* n; + ss_memo = PyDict_New(); + if (!ss_memo) + return NULL; + + ss_block = NULL; + ss_next_in_block = -1; + ss_error = NULL; + ss_errinst = NULL; + ss_errtb = NULL; + n = copyrec(o); + Py_DECREF(ss_memo); + while (ss_block) + { + int i; + struct key_block* b = ss_block; + ss_block = b->next; + for (i=ss_next_in_block+1; i<KEYS_BY_BLOCK; i++) + Py_DECREF(b->keys[i].o); + free(b); + ss_next_in_block = -1; + } + if (ss_error && !PyErr_Occurred()) + PyErr_Restore(ss_error, ss_errinst, ss_errtb); + else + { + Py_XDECREF(ss_error); + Py_XDECREF(ss_errinst); + Py_XDECREF(ss_errtb); + } + if (PyErr_Occurred()) + { + Py_DECREF(n); + n = NULL; + } + return n; +} + + +static PyMethodDef StateSaverMethods[] = { + {"copy", sscopy, METH_O}, + {NULL, NULL} /* Sentinel */ +}; + +void initstatesaver(void) +{ + PyObject* m; + PyObject* x, *y; + m = Py_InitModule("statesaver", StateSaverMethods); + if (m == NULL) + return; + keytype.ob_type = &PyType_Type; + str_inst_build = PyString_InternFromString("inst_build"); + + m = PyImport_ImportModule("types"); + if (!m) return; + GeneratorType = (PyTypeObject*) PyObject_GetAttrString(m, "GeneratorType"); + if (!GeneratorType) return; + + x = PyTuple_New(0); + if (!x) return; + empty_iterator = PyObject_GetIter(x); + Py_DECREF(x); + if (!empty_iterator) return; + PyTupleIter_TypePtr = empty_iterator->ob_type; + + x = PyList_New(0); + if (!x) return; + y = PyList_Type.tp_iter(x); + if (y) PyListIter_TypePtr = y->ob_type; + Py_XDECREF(y); + Py_DECREF(x); + if (!y) return; +} |