diff --git a/Modules/cPickle.c b/Modules/cPickle.c index c90b2492d38343..931557e95a2f36 100644 --- a/Modules/cPickle.c +++ b/Modules/cPickle.c @@ -2,7 +2,6 @@ #include "cStringIO.h" #include "structmember.h" #include "core/stackless_impl.h" -#include "platf/slp_platformselect.h" PyDoc_STRVAR(cPickle_module_documentation, "C implementation and optimization of the Python pickle module."); @@ -2571,9 +2570,9 @@ save(Picklerobject *self, PyObject *args, int pers_save) int res = -1; int tmp; -#ifdef STACKLESS +#if defined STACKLESS /* but we save the stack after a fixed watermark */ - if (CSTACK_SAVE_NOW(PyThreadState_GET(), self)) { + if (slp_cstack_save_now(PyThreadState_GET())) { res = slp_safe_pickling((int(*)(PyObject *, PyObject *, int))&save, (PyObject *)self, args, pers_save); goto finally; diff --git a/PCbuild/pythoncore.vcproj b/PCbuild/pythoncore.vcproj index 146d89baac291f..0afc880e286c36 100644 --- a/PCbuild/pythoncore.vcproj +++ b/PCbuild/pythoncore.vcproj @@ -16,6 +16,9 @@ /> + + @@ -105,6 +111,9 @@ + @@ -181,6 +190,9 @@ + @@ -258,6 +270,9 @@ + @@ -336,6 +351,9 @@ + @@ -410,6 +428,9 @@ + @@ -486,6 +507,9 @@ + @@ -560,6 +584,9 @@ + @@ -655,11 +682,11 @@ > - - @@ -1954,155 +1977,135 @@ > - - - - - - + + + + + + + + - - - - - - - - diff --git a/Python/ceval.c b/Python/ceval.c index f0addb5d3ee4b3..3f98a4b2f984ad 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -17,7 +17,6 @@ #include "opcode.h" #include "structmember.h" #include "core/stackless_impl.h" -#include "platf/slp_platformselect.h" /* for stack saving */ #include @@ -906,7 +905,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) return NULL; #ifdef STACKLESS - if (CSTACK_SAVE_NOW(tstate, f)) + if (slp_cstack_save_now(tstate)) return slp_eval_frame_newstack(f, throwflag, retval); /* push frame */ diff --git a/Stackless/core/slp_transfer.c b/Stackless/core/slp_transfer.c index 2fc8b189f029b1..efac1a1152b330 100644 --- a/Stackless/core/slp_transfer.c +++ b/Stackless/core/slp_transfer.c @@ -80,35 +80,6 @@ extern int slp_switch(void); #endif -/* a write only variable used to prevent overly optimisation */ -intptr_t *global_goobledigoobs; -static int -climb_stack_and_transfer(PyCStackObject **cstprev, PyCStackObject *cst, - PyTaskletObject *prev) -{ - /* - * there are cases where we have been initialized - * in some deep stack recursion, but later on we - * need to switch from a higher stacklevel, and the - * needed stack size becomes *negative* :-)) - */ - PyThreadState *ts = PyThreadState_GET(); - intptr_t probe; - register ptrdiff_t needed = &probe - ts->st.cstack_base; - /* in rare cases, the need might have vanished due to the recursion */ - register intptr_t *goobledigoobs; - if (needed > 0) { - goobledigoobs = alloca(needed * sizeof(intptr_t)); - if (goobledigoobs == NULL) - return -1; - /* hinder the compiler to optimise away - goobledigoobs and the alloca call. - This happens with gcc 4.7.x and -O2 */ - global_goobledigoobs = goobledigoobs; - } - return slp_transfer(cstprev, cst, prev); -} - /* This function returns -1 on error, 1 if a switch occurred and 0 * if only a stack save was performed */ @@ -122,9 +93,7 @@ slp_transfer(PyCStackObject **cstprev, PyCStackObject *cst, /* since we change the stack we must assure that the protocol was met */ STACKLESS_ASSERT(); - if ((intptr_t *) &ts > ts->st.cstack_base) - return climb_stack_and_transfer(cstprev, cst, prev); - if (cst == NULL || cst->ob_size == 0) + if (cst == NULL) cst = ts->st.initial_stub; if (cst != NULL) { if (cst->tstate != ts) { @@ -145,6 +114,10 @@ slp_transfer(PyCStackObject **cstprev, PyCStackObject *cst, if (cstprev && *cstprev == cst && cst->ob_refcnt == 1) cst = NULL; } + /* allocate a fresh + if (!slp_cstack_new(cstprev, NULL, t)) + return NULL; + _cstprev = cstprev; _cst = cst; _prev = prev; diff --git a/Stackless/core/stackless_impl.h b/Stackless/core/stackless_impl.h index 62fbaa6ab4fa7c..2e39ed8f2c5f39 100644 --- a/Stackless/core/stackless_impl.h +++ b/Stackless/core/stackless_impl.h @@ -36,23 +36,7 @@ extern "C" { PyAPI_DATA(int) slp_enable_softswitch; PyAPI_DATA(int) slp_in_psyco; PyAPI_DATA(int) slp_try_stackless; -PyAPI_DATA(PyCStackObject *) slp_cstack_chain; - -PyAPI_FUNC(PyCStackObject *) slp_cstack_new(PyCStackObject **cst, - intptr_t *stackref, - PyTaskletObject *task); -PyAPI_FUNC(size_t) slp_cstack_save(PyCStackObject *cstprev); -PyAPI_FUNC(void) slp_cstack_restore(PyCStackObject *cst); - -PyAPI_FUNC(int) slp_transfer(PyCStackObject **cstprev, PyCStackObject *cst, - PyTaskletObject *prev); - -#ifdef Py_DEBUG -PyAPI_FUNC(int) slp_transfer_return(PyCStackObject *cst); -#else -#define slp_transfer_return(cst) \ - slp_transfer(NULL, (cst), NULL) -#endif +PyAPI_DATA(PyTaskletObject *) slp_tasklet_chain; PyAPI_FUNC(int) _PyStackless_InitTypes(void); PyAPI_FUNC(void) _PyStackless_Init(void); diff --git a/Stackless/core/stackless_structs.h b/Stackless/core/stackless_structs.h index e4c31505d680e3..8bf5325a9e48d4 100644 --- a/Stackless/core/stackless_structs.h +++ b/Stackless/core/stackless_structs.h @@ -5,10 +5,7 @@ extern "C" { #endif -/* platform specific constants (mainly SEH stuff to store )*/ -#include "platf/slp_platformselect.h" - - +#include "stackless_tealet.h" /*** important structures: tasklet ***/ @@ -62,7 +59,7 @@ extern "C" { ***************************************************************************/ -typedef struct _tasklet_flags { +typedef struct PyTaskletObject_flags { int blocked: 2; unsigned int atomic: 1; unsigned int ignore_nesting: 1; @@ -73,46 +70,25 @@ typedef struct _tasklet_flags { } PyTaskletFlagStruc; -typedef struct _tasklet { +typedef struct PyTaskletObject { PyObject_HEAD - struct _tasklet *next; - struct _tasklet *prev; + struct PyTaskletObject *next; + struct PyTaskletObject *prev; union { struct _frame *frame; struct _cframe *cframe; } f; PyObject *tempval; /* bits stuff */ - struct _tasklet_flags flags; + struct PyTaskletObject_flags flags; int recursion_depth; - struct _cstack *cstate; + int nesting_level; + PyThreadState *tstate; + struct tealet_t *cstate; PyObject *def_globals; PyObject *tsk_weakreflist; } PyTaskletObject; - -/*** important structures: cstack ***/ - -typedef struct _cstack { - PyObject_VAR_HEAD - struct _cstack *next; - struct _cstack *prev; -#ifdef have_long_long - long_long serial; -#else - long serial; -#endif - struct _tasklet *task; - int nesting_level; - PyThreadState *tstate; -#ifdef _SEH32 - DWORD exception_list; /* SEH handler on Win32 */ -#endif - intptr_t *startaddr; - intptr_t stack[1]; -} PyCStackObject; - - /*** important structures: bomb ***/ typedef struct _bomb { @@ -156,8 +132,8 @@ typedef struct _channel_flags { typedef struct _channel { PyObject_HEAD /* make sure that these fit tasklet's next/prev */ - struct _tasklet *head; - struct _tasklet *tail; + struct PyTaskletObject *head; + struct PyTaskletObject *tail; int balance; struct _channel_flags flags; PyObject *chan_weakreflist; @@ -204,9 +180,6 @@ typedef struct _slpmodule { PyAPI_DATA(PyTypeObject) PyCFrame_Type; #define PyCFrame_Check(op) ((op)->ob_type == &PyCFrame_Type) -PyAPI_DATA(PyTypeObject) PyCStack_Type; -#define PyCStack_Check(op) ((op)->ob_type == &PyCStack_Type) - PyAPI_DATA(PyTypeObject) PyBomb_Type; #define PyBomb_Check(op) ((op)->ob_type == &PyBomb_Type) diff --git a/Stackless/core/stackless_tealet.c b/Stackless/core/stackless_tealet.c new file mode 100644 index 00000000000000..a1e1ddd5e33d29 --- /dev/null +++ b/Stackless/core/stackless_tealet.c @@ -0,0 +1,366 @@ + +#include "stackless_tealet.h" + +#include +#include "frameobject.h" +#include "stackless_impl.h" + +PyTealet_data *slp_tealet_list = NULL; + +static void +slp_tealet_link(tealet_t *t, PyTaskletObject *tasklet) +{ + PyTealet_data *td = TEALET_EXTRA(t, PyTealet_data); + if (slp_tealet_list) { + td->next = slp_tealet_list; + td->prev = td->next->prev; + td->next->prev = td; + td->prev->next = td; + } else + td->next = td->prev = td; + slp_tealet_list = td; + td->tasklet = tasklet; +} + +static void +slp_tealet_unlink(tealet_t *t) +{ + PyTealet_data *td = TEALET_EXTRA(t, PyTealet_data); + if (td->next != td) { + slp_tealet_list = td->next; + td->next->prev = td->prev; + td->prev->next = td->next; + } else + slp_tealet_list = NULL; +} + +/**************************************************************** + *Implement copyable tealet stubs by using a trampoline + */ +struct slp_stub_arg +{ + tealet_t *current; + tealet_run_t run; + void *runarg; +}; + +static tealet_t *slp_stub_main(tealet_t *current, void *arg) +{ + void *myarg = 0; + /* the caller is in arg, return right back to him */ + tealet_switch((tealet_t*)arg, &myarg); + /* now we are back, myarg should contain the arg to the run function. + * We were possibly duplicated, so can't trust the original function args. + */ + { + struct slp_stub_arg sarg = *(struct slp_stub_arg*)myarg; + tealet_free(sarg.current, myarg); + return (sarg.run)(sarg.current, sarg.runarg); + } +} + +/* create a stub and return it */ +tealet_t *slp_stub_new(tealet_t *t, size_t extrasize) +{ + void *arg = (void*)tealet_current(t); + return tealet_new(t, slp_stub_main, &arg, extrasize); +} + +/* find the stack far point if a stub were created here */ +void * slp_stub_far(tealet_t *dummy1, size_t dummy2) +{ + void *arg = 0; + return tealet_new_far(dummy1, NULL, &arg, dummy2); +} + + +/* run a stub */ +int slp_stub_run(tealet_t *stub, tealet_run_t run, void **parg) +{ + int result; + void *myarg; + + /* we cannot pass arguments to a different tealet on the stack */ + struct slp_stub_arg *psarg = (struct slp_stub_arg*)tealet_malloc(stub, sizeof(struct slp_stub_arg)); + if (!psarg) + return TEALET_ERR_MEM; + psarg->current = stub; + psarg->run = run; + psarg->runarg = parg ? *parg : NULL; + myarg = (void*)psarg; + result = tealet_switch(stub, &myarg); + if (result) { + /* failure */ + tealet_free(stub, psarg); + return result; + } + /* pass back the arg value from the switch */ + if (parg) + *parg = myarg; + return 0; +} + +/* end of generic tealet stub code */ + +/* translate tealet errors into python errors */ +static int +slp_tealet_error(int err) +{ + assert(err); + if (err == TEALET_ERR_MEM) + PyErr_NoMemory(); + assert(err == TEALET_ERR_DEFUNCT); + PyErr_SetString(PyExc_RuntimeError, "Tealet corrupt"); + return -1; +} + +/* the current mechanism is based on the generic callable stubs + * above. This is useful, because it allows us to call arbitrary + * callbacks, e.g. when doing stack spilling. + */ +int +slp_make_initial_stub(PyThreadState *ts) +{ + if (ts->st.tealet_main == NULL) { + tealet_alloc_t ta = { + (tealet_malloc_t)&PyMem_Malloc, + (tealet_free_t)&PyMem_Free, + 0}; + ts->st.tealet_main = tealet_initialize(&ta, sizeof(PyTealet_data)); + if (!ts->st.tealet_main) + goto err; + + } + if (ts->st.initial_stub) { + /* recreate stub, if it was deeper on the stack, otherwise, we just leave it */ + /* note, this approach needs to be subject to tuning. Maybe the absolute position + * of the stub makes no difference. But for stack spilling, a "high" stub makes + * perfect sense. + */ + void *a; + void *b; + a = tealet_get_far(ts->st.initial_stub); + b = slp_stub_far(ts->st.tealet_main, 0); + if (tealet_stack_diff(a, b) < 0) { + /* new stub will be higher up, recreate it */ + tealet_delete(ts->st.initial_stub); + ts->st.initial_stub = NULL; + } + } + + if (!ts->st.initial_stub) { + ts->st.initial_stub = slp_stub_new(ts->st.tealet_main, sizeof(PyTealet_data)); + if (!ts->st.initial_stub) + goto err; +#if 0 /* just to verify predicted pos */ + { + void *c + c = slp_stub_far(ts->st.tealet_main, 0); + c = c; + } +#endif + } + return 0; +err: + PyErr_NoMemory(); + return -1; +} + +/* clean up tealet state for this thread */ +void slp_tealet_cleanup(PyThreadState *ts) +{ + slp_destroy_initial_stub(ts); + if (ts->st.tealet_main) { + /* we may be calling Py_Exit from a tasklet or otherwise + * from a sub-tealet. Therefore we can't make sanity checks + * like testing that we are the main tealet. Accept that we + * may leak suspended tealets in this case + */ + if (TEALET_CURRENT_IS_MAIN(ts->st.tealet_main)) + tealet_finalize(ts->st.tealet_main); + ts->st.tealet_main = NULL; + } +} + +void +slp_destroy_initial_stub(PyThreadState *ts) +{ + if (ts->st.initial_stub) + tealet_delete(ts->st.initial_stub); + ts->st.initial_stub = NULL; +} + +/* The function that runs tasklet loop in a tealet */ +static tealet_t *tasklet_stub_func(tealet_t *me, void *arg) +{ + PyThreadState *ts = PyThreadState_GET(); + PyFrameObject *f = ts->frame; + PyObject *result; + ts->frame = NULL; + ts->st.nesting_level = 0; + Py_CLEAR(ts->st.del_post_switch); + result = slp_run_tasklet(f); + + /* this tealet is returning, which means that main is returning. Switch back + * to the main tealet. The result is passed to the target + */ + tealet_exit(ts->st.tealet_main, (void*)result, TEALET_EXIT_DEFAULT); + /* this should never fail */ + assert(0); + return NULL; +} + +/* Running a function in the top level stub. If NULL is provided, + * use the tasklet evaluation loop + */ + +/* Running a function in the top level stub */ +int +slp_run_initial_stub(PyThreadState *ts, tealet_run_t func, void **arg) +{ + tealet_t *stub; + int result; + stub = tealet_duplicate(ts->st.initial_stub, sizeof(PyTealet_data)); + if (!stub) { + PyErr_NoMemory(); + return -1; + } + result = slp_stub_run(stub, func, arg); + if (result) + return slp_tealet_error(result); + return 0; +} + +/* call the top level loop as a means of starting a new such loop, hardswitching out + * of a tasklet. This invocation does not expect a return value + */ +int +slp_run_stub_from_worker(PyThreadState *ts) +{ + return slp_run_initial_stub(ts, &tasklet_stub_func, NULL); +} + +/* hard switching of tasklets */ + +int +slp_transfer(PyThreadState *ts, tealet_t *cst, PyTaskletObject *prev) +{ + int result; + int nesting_level; + tealet_t *current; + + assert(prev->cstate == NULL); + nesting_level = prev->tstate->st.nesting_level; + prev->nesting_level = nesting_level; + + /* mark the old tasklet as having cstate, and link it in */ + current = tealet_current(ts->st.tealet_main); + prev->cstate = current; + slp_tealet_link(current, prev); + + if (cst) { + /* make sure we are not trying to jump between threads */ + assert(TEALET_RELATED(ts->st.tealet_main, cst)); + result = tealet_switch(cst, NULL); + } else { + assert(TEALET_RELATED(ts->st.tealet_main, ts->st.initial_stub)); + result = slp_run_stub_from_worker(ts); + } + + /* we are back, or failed, no cstate in tasklet */ + slp_tealet_unlink(current); + prev->cstate = NULL; + prev->tstate->st.nesting_level = nesting_level; + if (!result) { + if (ts->st.del_post_switch) { + PyObject *tmp; + TASKLET_CLAIMVAL(ts->st.current, &tmp); + Py_CLEAR(ts->st.del_post_switch); + TASKLET_SETVAL_OWN(ts->st.current, tmp); + } + return 0; + } + if (result == TEALET_ERR_MEM) + PyErr_NoMemory(); + else + PyErr_SetString(PyExc_RuntimeError, "Invalid tasklet"); + return -1; +} + +int +slp_transfer_return(tealet_t *cst) +{ + int result = tealet_exit(cst, NULL, TEALET_EXIT_DEFAULT); + if (result) { + /* emergency switch back to main tealet */ + PyThreadState *ts = PyThreadState_GET(); + PyErr_SetString(PyExc_RuntimeError, "Invalid tealet"); + tealet_exit(ts->st.tealet_main, NULL, TEALET_EXIT_DEFAULT); + /* emergency!. Transfer to the _real_ main */ + tealet_exit(TEALET_MAIN(ts->st.tealet_main), NULL, TEALET_EXIT_DEFAULT); + } + assert(0); /* never returns */ + return 0; +} + +int +slp_transfer_with_exc(PyThreadState *ts, tealet_t *cst, PyTaskletObject *prev) +{ + int tracing = ts->tracing; + int use_tracing = ts->use_tracing; + + Py_tracefunc c_profilefunc = ts->c_profilefunc; + Py_tracefunc c_tracefunc = ts->c_tracefunc; + PyObject *c_profileobj = ts->c_profileobj; + PyObject *c_traceobj = ts->c_traceobj; + + PyObject *exc_type = ts->exc_type; + PyObject *exc_value = ts->exc_value; + PyObject *exc_traceback = ts->exc_traceback; + int ret; + + ts->exc_type = ts->exc_value = ts->exc_traceback = NULL; + ts->c_profilefunc = ts->c_tracefunc = NULL; + ts->c_profileobj = ts->c_traceobj = NULL; + ts->use_tracing = ts->tracing = 0; + + /* note that trace/profile are set without ref */ + Py_XINCREF(c_profileobj); + Py_XINCREF(c_traceobj); + + ret = slp_transfer(ts, cst, prev); + + ts->tracing = tracing; + ts->use_tracing = use_tracing; + + ts->c_profilefunc = c_profilefunc; + ts->c_tracefunc = c_tracefunc; + ts->c_profileobj = c_profileobj; + ts->c_traceobj = c_traceobj; + Py_XDECREF(c_profileobj); + Py_XDECREF(c_traceobj); + + ts->exc_type = exc_type; + ts->exc_value = exc_value; + ts->exc_traceback = exc_traceback; + return ret; +} + +#ifndef CSTACK_WATERMARK +#define CSTACK_WATERMARK 16384 +#endif +int slp_cstack_save_now(PyThreadState *ts) +{ + void *a, *b; + ptrdiff_t diff; + if (!ts->st.initial_stub) + return 0; + assert(ts->st.initial_stub); + a = tealet_get_far(ts->st.initial_stub); + b = slp_stub_far(NULL, 0); + diff = tealet_stack_diff(a, b); +#if 0 /* enable this for testing, a low threshold */ + return diff > 1000; +#endif + return diff > CSTACK_WATERMARK; +} \ No newline at end of file diff --git a/Stackless/core/stackless_tealet.h b/Stackless/core/stackless_tealet.h new file mode 100644 index 00000000000000..1baf871ba5fc64 --- /dev/null +++ b/Stackless/core/stackless_tealet.h @@ -0,0 +1,33 @@ +/* interface to the stack switching routines used by stackless */ +#ifndef _STACKLESS_TEALET_H +#define _STACKLESS_TEALET_H + +#include +#include + +/* a list of tealets. Currently these are suspended tealets with their + * suspended tasklets indicated. + */ +typedef struct PyTealet_data { + struct PyTealet_data *prev; + struct PyTealet_data *next; + struct PyTaskletObject *tasklet; +} PyTealet_data; + +PyTealet_data *slp_tealet_list; /* head of the tealet list */ + +int slp_make_initial_stub(PyThreadState *ts); +void slp_destroy_initial_stub(PyThreadState *ts); +int slp_run_initial_stub(PyThreadState *ts, tealet_run_t func, void **arg); +int slp_run_stub_from_worker(PyThreadState *ts); +void slp_tealet_cleanup(PyThreadState *ts); + +/* hard switching of tasklets */ +int slp_transfer(PyThreadState *ts, tealet_t *cst, struct PyTaskletObject *prev); +int slp_transfer_with_exc(PyThreadState *ts, tealet_t *cst, struct PyTaskletObject *prev); +int slp_transfer_return(tealet_t *cst); + +/* stack spilling */ +int slp_cstack_save_now(PyThreadState *ts); + +#endif \ No newline at end of file diff --git a/Stackless/core/stackless_tstate.h b/Stackless/core/stackless_tstate.h index c0fc2f12c249e7..6eb3c14c48fbac 100644 --- a/Stackless/core/stackless_tstate.h +++ b/Stackless/core/stackless_tstate.h @@ -1,29 +1,12 @@ /*** addition to tstate ***/ typedef struct _sts { - /* the blueprint for new stacks */ - struct _cstack *initial_stub; - /* "serial" is incremented each time we create a new stub. - * (enter "stackless" from the outside) - * and "serial_last_jump" indicates to which stub the current - * stack belongs. This is used to find out if a stack switch - * is required when the main tasklet exits - */ -#ifdef have_long_long - long_long serial; - long_long serial_last_jump; -#else - long serial; - long serial_last_jump; -#endif - /* the base address for hijacking stacks. XXX deprecating */ - intptr_t *cstack_base; - /* stack overflow check and init flag */ - intptr_t *cstack_root; + struct tealet_t *initial_stub; /* stub for creating new tealets */ + struct tealet_t *tealet_main; /* the current "main" tealet */ /* main tasklet */ - struct _tasklet *main; + struct PyTaskletObject *main; /* runnable tasklets */ - struct _tasklet *current; + struct PyTaskletObject *current; int runcount; /* scheduling */ @@ -53,10 +36,7 @@ typedef struct _sts { /* these macros go into pystate.c */ #define __STACKLESS_PYSTATE_NEW \ tstate->st.initial_stub = NULL; \ - tstate->st.serial = 0; \ - tstate->st.serial_last_jump = 0; \ - tstate->st.cstack_base = NULL; \ - tstate->st.cstack_root = NULL; \ + tstate->st.tealet_main = NULL; \ tstate->st.ticker = 0; \ tstate->st.interval = 0; \ tstate->st.interrupt = NULL; \ @@ -79,12 +59,13 @@ typedef struct _sts { struct _ts; /* Forward */ void slp_kill_tasks_with_stacks(struct _ts *tstate); +void slp_tealet_cleanup(struct _ts *tstate); #define __STACKLESS_PYSTATE_CLEAR \ slp_kill_tasks_with_stacks(tstate); \ - Py_CLEAR(tstate->st.initial_stub); \ Py_CLEAR(tstate->st.del_post_switch); \ - Py_CLEAR(tstate->st.interrupted); + Py_CLEAR(tstate->st.interrupted); \ + slp_tealet_cleanup(tstate); #ifdef WITH_THREAD diff --git a/Stackless/core/stacklesseval.c b/Stackless/core/stacklesseval.c index fe7a77959ebb41..ec330be2574fac 100644 --- a/Stackless/core/stacklesseval.c +++ b/Stackless/core/stacklesseval.c @@ -8,8 +8,7 @@ #include "stackless_impl.h" #include "pickling/prickelpit.h" -/* platform specific constants */ -#include "platf/slp_platformselect.h" +#include "stackless_tealet.h" /* Stackless extension for ceval.c */ @@ -37,8 +36,8 @@ int slp_in_psyco = 0; */ int slp_try_stackless = 0; -/* the list of all stacks of all threads */ -struct _cstack *slp_cstack_chain = NULL; +/* the list of all tasklets of all threads */ +PyTaskletObject *slp_tasklet_chain = NULL; /****************************************************** @@ -47,232 +46,11 @@ struct _cstack *slp_cstack_chain = NULL; ******************************************************/ -static PyCStackObject *cstack_cache[CSTACK_SLOTS] = { NULL }; -static int cstack_cachecount = 0; /* this function will get called by PyStacklessEval_Fini */ -static void slp_cstack_cacheclear(void) -{ - int i; - PyCStackObject *stack; - - for (i=0; i < CSTACK_SLOTS; i++) { - while (cstack_cache[i] != NULL) { - stack = cstack_cache[i]; - cstack_cache[i] = (PyCStackObject *) stack->startaddr; - PyObject_Del(stack); - } - } - cstack_cachecount = 0; -} - -static void -cstack_dealloc(PyCStackObject *cst) -{ - slp_cstack_chain = cst; - SLP_CHAIN_REMOVE(PyCStackObject, &slp_cstack_chain, cst, next, - prev); - if (cst->ob_size >= CSTACK_SLOTS) { - PyObject_Del(cst); - } - else { - if (cstack_cachecount >= CSTACK_MAXCACHE) - slp_cstack_cacheclear(); - cst->startaddr = (intptr_t *) cstack_cache[cst->ob_size]; - cstack_cache[cst->ob_size] = cst; - ++cstack_cachecount; - } -} - - -PyCStackObject * -slp_cstack_new(PyCStackObject **cst, intptr_t *stackref, PyTaskletObject *task) -{ - PyThreadState *ts = PyThreadState_GET(); - intptr_t *stackbase = ts->st.cstack_base; - ptrdiff_t size = stackbase - stackref; - - assert(size >= 0); - - if (*cst != NULL) { - if ((*cst)->task == task) - (*cst)->task = NULL; - Py_DECREF(*cst); - } - if (size < CSTACK_SLOTS && ((*cst) = cstack_cache[size])) { - /* take stack from cache */ - cstack_cache[size] = (PyCStackObject *) (*cst)->startaddr; - --cstack_cachecount; - _Py_NewReference((PyObject *)(*cst)); - } - else - *cst = PyObject_NewVar(PyCStackObject, &PyCStack_Type, size); - if (*cst == NULL) return NULL; - - (*cst)->startaddr = stackbase; - (*cst)->next = (*cst)->prev = NULL; - SLP_CHAIN_INSERT(PyCStackObject, &slp_cstack_chain, *cst, next, prev); - (*cst)->serial = ts->st.serial_last_jump; - (*cst)->task = task; - (*cst)->tstate = ts; - (*cst)->nesting_level = ts->st.nesting_level; -#ifdef _SEH32 - //save the SEH handler - (*cst)->exception_list = (DWORD) - __readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList)); -#endif - return *cst; -} - -size_t -slp_cstack_save(PyCStackObject *cstprev) -{ - size_t stsizeb = (cstprev)->ob_size * sizeof(intptr_t); - - memcpy((cstprev)->stack, (cstprev)->startaddr - - (cstprev)->ob_size, stsizeb); -#ifdef _SEH32 - //save the SEH handler - cstprev->exception_list = (DWORD) - __readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList)); -#endif - return stsizeb; -} - -void -#ifdef _SEH32 -#pragma warning(disable:4733) /* disable warning about modifying FS[0] */ -#endif -slp_cstack_restore(PyCStackObject *cst) -{ - cst->tstate->st.nesting_level = cst->nesting_level; - /* mark task as no longer responsible for cstack instance */ - cst->task = NULL; - memcpy(cst->startaddr - cst->ob_size, &cst->stack, - (cst->ob_size) * sizeof(intptr_t)); -#ifdef _SEH32 - //restore the SEH handler - __writefsdword(FIELD_OFFSET(NT_TIB, ExceptionList), (DWORD)(cst->exception_list)); - #pragma warning(default:4733) -#endif -} - - -static char cstack_doc[] = -"A CStack object serves to save the stack slice which is involved\n\ -during a recursive Python call. It will also be used for pickling\n\ -of program state. This structure is highly platform dependant.\n\ -Note: For inspection, str() can dump it as a string.\ -"; - -#if SIZEOF_VOIDP == SIZEOF_INT -#define T_ADDR T_UINT -#else -#define T_ADDR T_ULONG -#endif - -static PyMemberDef cstack_members[] = { - {"size", T_INT, offsetof(PyCStackObject, ob_size), READONLY}, - {"next", T_OBJECT, offsetof(PyCStackObject, next), READONLY}, - {"prev", T_OBJECT, offsetof(PyCStackObject, prev), READONLY}, - {"task", T_OBJECT, offsetof(PyCStackObject, task), READONLY}, - {"startaddr", T_ADDR, offsetof(PyCStackObject, startaddr), READONLY}, - {0} -}; -/* simple string interface for inspection */ -static PyObject * -cstack_str(PyObject *o) -{ - PyCStackObject *cst = (PyCStackObject*)o; - return PyString_FromStringAndSize((char*)&cst->stack, - cst->ob_size*sizeof(cst->stack[0])); -} - -PyTypeObject PyCStack_Type = { - PyObject_HEAD_INIT(&PyType_Type) - 0, - "stackless.cstack", - sizeof(PyCStackObject), - sizeof(PyObject *), - (destructor)cstack_dealloc, /* 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 */ - 0, /* tp_hash */ - 0, /* tp_call */ - (reprfunc)cstack_str, /* tp_str */ - PyObject_GenericGetAttr, /* tp_getattro */ - PyObject_GenericSetAttr, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ - cstack_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - 0, /* tp_methods */ - cstack_members, /* tp_members */ -}; - - -static int -make_initial_stub(void) -{ - PyThreadState *ts = PyThreadState_GET(); - int result; - - if (ts->st.initial_stub != NULL) { - Py_DECREF(ts->st.initial_stub); - ts->st.initial_stub = NULL; - } - ts->st.serial_last_jump = ++ts->st.serial; - result = slp_transfer(&ts->st.initial_stub, NULL, NULL); - if (result < 0) - return result; - /* - * from here, we always arrive with a compatible cstack - * that also can be used by main, if it is running - * in soft-switching mode. - * To insure that, it was necessary to re-create the - * initial stub for *every* run of a new main. - * This will vanish with greenlet-like stack management. - */ - - return result; -} - -static PyObject * -climb_stack_and_eval_frame(PyFrameObject *f) -{ - /* - * a similar case to climb_stack_and_transfer, - * but here we need to incorporate a gap in the - * stack into main and keep this gap on the stack. - * This way, initial_stub is always valid to be - * used to return to the main c stack. - */ - PyThreadState *ts = PyThreadState_GET(); - intptr_t probe; - ptrdiff_t needed = &probe - ts->st.cstack_base; - /* in rare cases, the need might have vanished due to the recursion */ - intptr_t *goobledigoobs; - if (needed > 0) { - goobledigoobs = alloca(needed * sizeof(intptr_t)); - if (goobledigoobs == NULL) - return NULL; - } - return slp_eval_frame(f); -} PyObject * @@ -280,42 +58,19 @@ slp_eval_frame(PyFrameObject *f) { PyThreadState *ts = PyThreadState_GET(); PyFrameObject *fprev = f->f_back; - intptr_t * stackref; if (fprev == NULL && ts->st.main == NULL) { - int returning; - /* this is the initial frame, so mark the stack base */ - - /* - * careful, this caused me a major headache. - * it is *not* sufficient to just check for fprev == NULL. - * Reason: (observed with wxPython): - * A toplevel frame is run as a tasklet. When its frame - * is deallocated (in tasklet_end), a Python object - * with a __del__ method is destroyed. This __del__ - * will run as a toplevel frame, with f_back == NULL! + /* this is the initial frame. From here we must + * run the evaluation loop on a separate tealet, so that + * all such tealets are equivalent and can jump back to main + * when they exit. */ - - stackref = STACK_REFPLUS + (intptr_t *) &f; - if (ts->st.cstack_base == NULL) - ts->st.cstack_base = stackref - CSTACK_GOODGAP; - if (stackref > ts->st.cstack_base) - return climb_stack_and_eval_frame(f); - - returning = make_initial_stub(); - if (returning < 0) + PyObject *result; + if (slp_make_initial_stub(ts)) return NULL; - /* returning will be 1 if we "switched back" to this stub, and 0 - * if this is the original call that just created the stub. - * If the stub is being reused, the argument, i.e. the frame, - * is in ts->frame - */ - if (returning == 1) { - f = ts->frame; - ts->frame = NULL; - } return slp_run_tasklet(f); } + Py_INCREF(Py_None); return slp_frame_dispatch(f, fprev, 0, Py_None); } @@ -327,32 +82,29 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts) /* a loop to kill tasklets on the local thread */ while (1) { - PyCStackObject *csfirst = slp_cstack_chain, *cs; + PyTealet_data *tdfirst = slp_tealet_list, *td; PyTaskletObject *t; - if (csfirst == NULL) + if (tdfirst == NULL) break; - for (cs = csfirst; ; cs = cs->next) { - if (count && cs == csfirst) { + for (td = tdfirst; ; td = td->next) { + if (count && td == tdfirst) { /* nothing found */ return; } ++count; - if (cs->task == NULL) + if (td->tasklet == NULL) continue; /* can't kill tasklets from other threads here */ - if (cs->tstate != ts) - continue; - /* were we asked to kill tasklet on our thread? */ - if (target_ts != NULL && cs->tstate != target_ts) + if (td->tasklet->tstate != ts) continue; /* Not killable, another thread's frameless main? */ - if (slp_get_frame(cs->task) == NULL) + if (slp_get_frame(td->tasklet) == NULL) continue; break; } count = 0; - t = cs->task; + t = td->tasklet; Py_INCREF(t); /* cs->task is a borrowed ref */ /* We need to ensure that the tasklet 't' is in the scheduler @@ -365,7 +117,7 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts) * killed, they will be implicitly placed before this one, * leaving it to run next. */ - if (!t->flags.blocked && t != cs->tstate->st.current) { + if (!t->flags.blocked && t != t->tstate->st.current) { PyTaskletObject *tmp; PyTaskletObject **chain; /* unlink from runnable queue if it wasn't previously remove()'d */ @@ -373,12 +125,12 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts) assert(t->prev); chain = &t; SLP_CHAIN_REMOVE(PyTaskletObject, chain, tmp, next, prev); - ts->st.runcount--; t = tmp; + ts->st.runcount--; } else Py_INCREF(t); /* a new reference for the runnable queue */ /* insert into the 'current' chain without modifying 'current' */ - tmp = cs->tstate->st.current; + tmp = t->tstate->st.current; chain = &tmp; SLP_CHAIN_INSERT(PyTaskletObject, chain, t, next, prev); ts->st.runcount++; @@ -387,10 +139,6 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts) PyTasklet_Kill(t); PyErr_Clear(); - if (t->cstate != NULL) { - /* ensure a valid tstate */ - t->cstate->tstate = slp_initial_tstate; - } Py_DECREF(t); } @@ -400,18 +148,24 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts) * exit this function */ { - PyCStackObject *csfirst = slp_cstack_chain, *cs; + PyTealet_data *tdfirst = slp_tealet_list, *td; PyTaskletObject *t; - if (csfirst == NULL) + if (tdfirst == NULL) return; count = 0; - for (cs = csfirst; ; cs = cs->next) { - if (count && cs == csfirst) { + for (td = tdfirst; ; td = td->next) { + if (count && td == tdfirst) { return; } + if (td->tasklet == NULL) + continue; + t = td->tasklet; + if (t->tstate == ts) + continue; /* ignore this thread's tasklets */ + if (target_ts && t->tstate != target_ts) + continue; /* want a specific thread */ count++; - t = cs->task; Py_INCREF(t); /* cs->task is a borrowed ref */ PyTasklet_Kill(t); PyErr_Clear(); @@ -423,6 +177,9 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts) void PyStackless_kill_tasks_with_stacks(int allthreads) { PyThreadState *ts = PyThreadState_Get(); + int init = 0; + if (slp_tealet_list == NULL) + return; if (ts->st.main == NULL) { if (initialize_main_and_current()) { @@ -431,118 +188,72 @@ void PyStackless_kill_tasks_with_stacks(int allthreads) Py_XDECREF(s); return; } + init = 1; } slp_kill_tasks_with_stacks(allthreads ? NULL : ts); + + if (init) { + PyTaskletObject *m = ts->st.main, *c; + ts->st.main = NULL; + c = slp_current_remove(); + Py_XDECREF(m); + Py_XDECREF(c); + } } /* cstack spilling for recursive calls */ - -static PyObject * -eval_frame_callback(PyFrameObject *f, int exc, PyObject *retval) +typedef struct eval_args { - PyThreadState *ts = PyThreadState_GET(); - PyTaskletObject *cur = ts->st.current; - PyCStackObject *cst; - PyCFrameObject *cf = (PyCFrameObject *) f; - intptr_t *saved_base; - - //make sure we don't try softswitching out of this callstack - ts->st.nesting_level = cf->n + 1; - ts->frame = f->f_back; - - //this tasklet now runs in this tstate. - cst = cur->cstate; //The calling cstate - cur->cstate = ts->st.initial_stub; - Py_INCREF(cur->cstate); - - /* We must base our new stack from here, because otherwise we might find - * ourselves in an infinite loop of stack spilling. - */ - saved_base = ts->st.cstack_root; - ts->st.cstack_root = STACK_REFPLUS + (intptr_t *) &f; - - /* pull in the right retval and tempval from the arguments */ - Py_DECREF(retval); - retval = cf->ob1; - cf->ob1 = NULL; - TASKLET_SETVAL_OWN(cur, cf->ob2); - cf->ob2 = NULL; + PyThreadState *ts; + tealet_t *return_to; + PyFrameObject *f; + int exc; + PyObject *retval; +} eval_args; - retval = PyEval_EvalFrameEx_slp(ts->frame, exc, retval); - ts->st.cstack_root = saved_base; - - /* store retval back into the cstate object */ - if (retval == NULL) - retval = slp_curexc_to_bomb(); - if (retval == NULL) - goto fatal; - cf->ob1 = retval; - - /* jump back */ - Py_DECREF(cur->cstate); - cur->cstate = cst; - slp_transfer_return(cst); - /* should never come here */ -fatal: - Py_DECREF(cf); /* since the caller won't do it */ - return NULL; +static tealet_t * +eval_frame_callback(tealet_t *current, void *arg) +{ + eval_args *args = (eval_args*)arg; + PyThreadState *ts = args->ts; + + /*make sure we don't try softswitching out of this callstack + /* TODO: find a way to keep this tasklet pickleable, but not softswitchable */ + ts->st.nesting_level++; + + /* perform the call */ + args->retval = PyEval_EvalFrameEx_slp(args->f, args->exc, args->retval); + ts->st.nesting_level--; + return args->return_to; } PyObject * slp_eval_frame_newstack(PyFrameObject *f, int exc, PyObject *retval) { PyThreadState *ts = PyThreadState_GET(); - PyTaskletObject *cur = ts->st.current; - PyCFrameObject *cf = NULL; - PyCStackObject *cst; - - if (cur == NULL || PyErr_Occurred()) { - /* Bypass this during early initialization or if we have a pending - * exception, such as the one set via gen_close(). Doing the stack - * magic here will clear that exception. - */ - intptr_t *old = ts->st.cstack_root; - ts->st.cstack_root = STACK_REFPLUS + (intptr_t *) &f; - retval = PyEval_EvalFrameEx_slp(f,exc, retval); - ts->st.cstack_root = old; - return retval; - } - if (ts->st.cstack_root == NULL) { - /* this is a toplevel call. Store the root of stack spilling */ - ts->st.cstack_root = STACK_REFPLUS + (intptr_t *) &f; - retval = PyEval_EvalFrameEx_slp(f, exc, retval); - /* and reset it. We may reenter stackless at a completely different - * depth later - */ - ts->st.cstack_root = NULL; - return retval; + eval_args *args = PyMem_MALLOC(sizeof(eval_args)); + int result; + void *argp; + if (!args) { + PyErr_NoMemory(); + return NULL; } - - ts->frame = f; - cf = slp_cframe_new(eval_frame_callback, 1); - if (cf == NULL) + args->ts = ts; + args->return_to = tealet_current(ts->st.tealet_main); + args->f = f; + args->exc = exc; + args->retval = retval; + + argp = (void*)args; + result = slp_run_initial_stub(ts, eval_frame_callback, &argp); + if (result) { + Py_XDECREF(retval); + PyMem_FREE(args); return NULL; - cf->n = ts->st.nesting_level; - cf->ob1 = retval; - /* store the tmpval here so that it won't get clobbered - * by slp_run_tasklet() - */ - TASKLET_CLAIMVAL(cur, &(cf->ob2)); - ts->frame = (PyFrameObject *) cf; - cst = cur->cstate; - cur->cstate = NULL; - if (slp_transfer(&cur->cstate, NULL, cur) < 0) - goto finally; /* fatal */ - Py_XDECREF(cur->cstate); - - retval = cf->ob1; - cf->ob1 = NULL; - if (PyBomb_Check(retval)) - retval = slp_bomb_explode(retval); -finally: - Py_DECREF(cf); - cur->cstate = cst; + } + retval = args->retval; + PyMem_FREE(args); return retval; } @@ -817,7 +528,6 @@ slp_frame_dispatch_top(PyObject *retval) void slp_stacklesseval_fini(void) { - slp_cstack_cacheclear(); } #endif /* STACKLESS */ diff --git a/Stackless/module/channelobject.c b/Stackless/module/channelobject.c index 17e3c9c796e2c8..e2541becc90a76 100644 --- a/Stackless/module/channelobject.c +++ b/Stackless/module/channelobject.c @@ -450,7 +450,7 @@ generic_channel_action(PyChannelObject *self, PyObject *arg, int dir, int stackl PyTaskletObject *source = ts->st.current; PyTaskletObject *target = self->head; int cando = dir > 0 ? self->balance < 0 : self->balance > 0; - int interthread = cando ? target->cstate->tstate != ts : 0; + int interthread = cando ? target->tstate != ts : 0; PyObject *tmpval, *retval; int fail; @@ -493,7 +493,7 @@ generic_channel_cando(PyThreadState *ts, PyObject **result, PyChannelObject *sel /* swap data and perform necessary scheduling */ switchto = target = slp_channel_remove(self, NULL, NULL, &next); - interthread = target->cstate->tstate != ts; + interthread = target->tstate != ts; /* exchange data */ TASKLET_SWAPVAL(source, target); diff --git a/Stackless/module/scheduling.c b/Stackless/module/scheduling.c index a74474f2908b94..9e8b71d96f96e7 100644 --- a/Stackless/module/scheduling.c +++ b/Stackless/module/scheduling.c @@ -2,6 +2,7 @@ #ifdef STACKLESS #include "core/stackless_impl.h" +#include "core/stackless_tealet.h" #ifdef WITH_THREAD #include "pythread.h" @@ -330,51 +331,6 @@ PyTypeObject PyBomb_Type = { slp_schedule_hook_func *_slp_schedule_fasthook; PyObject *_slp_schedule_hook; -static int -transfer_with_exc(PyCStackObject **cstprev, PyCStackObject *cst, PyTaskletObject *prev) -{ - PyThreadState *ts = PyThreadState_GET(); - - int tracing = ts->tracing; - int use_tracing = ts->use_tracing; - - Py_tracefunc c_profilefunc = ts->c_profilefunc; - Py_tracefunc c_tracefunc = ts->c_tracefunc; - PyObject *c_profileobj = ts->c_profileobj; - PyObject *c_traceobj = ts->c_traceobj; - - PyObject *exc_type = ts->exc_type; - PyObject *exc_value = ts->exc_value; - PyObject *exc_traceback = ts->exc_traceback; - int ret; - - ts->exc_type = ts->exc_value = ts->exc_traceback = NULL; - ts->c_profilefunc = ts->c_tracefunc = NULL; - ts->c_profileobj = ts->c_traceobj = NULL; - ts->use_tracing = ts->tracing = 0; - - /* note that trace/profile are set without ref */ - Py_XINCREF(c_profileobj); - Py_XINCREF(c_traceobj); - - ret = slp_transfer(cstprev, cst, prev); - - ts->tracing = tracing; - ts->use_tracing = use_tracing; - - ts->c_profilefunc = c_profilefunc; - ts->c_tracefunc = c_tracefunc; - ts->c_profileobj = c_profileobj; - ts->c_traceobj = c_traceobj; - Py_XDECREF(c_profileobj); - Py_XDECREF(c_traceobj); - - ts->exc_type = exc_type; - ts->exc_value = exc_value; - ts->exc_traceback = exc_traceback; - return ret; -} - /* scheduler monitoring */ int @@ -525,17 +481,6 @@ jump_soft_to_hard(PyFrameObject *f, int exc, PyObject *retval) /* combined soft/hard switching */ -int -slp_ensure_linkage(PyTaskletObject *t) -{ - if (t->cstate->task == t) - return 0; - if (!slp_cstack_new(&t->cstate, t->cstate->tstate->st.cstack_base, t)) - return -1; - t->cstate->nesting_level = 0; - return 0; -} - /* check whether a different thread can be run */ @@ -749,7 +694,7 @@ schedule_task_interthread(PyObject **result, int stackless, int *did_switch) { - PyThreadState *nts = next->cstate->tstate; + PyThreadState *nts = next->tstate; int fail; /* get myself ready, since the previous task is going to continue on the @@ -846,8 +791,7 @@ slp_schedule_task(PyObject **result, PyTaskletObject *prev, PyTaskletObject *nex return schedule_task_block(result, prev, stackless, did_switch); #ifdef WITH_THREAD - /* note that next->cstate is undefined if it is ourself */ - if (next->cstate != NULL && next->cstate->tstate != ts) { + if (next->tstate != ts) { return schedule_task_interthread(result, prev, next, stackless, did_switch); } #endif @@ -891,10 +835,8 @@ static int slp_schedule_task_prepared(PyThreadState *ts, PyObject **result, PyTaskletObject *prev, PyTaskletObject *next, int stackless, int *did_switch) { - PyCStackObject **cstprev; - PyObject *retval; - int (*transfer)(PyCStackObject **, PyCStackObject *, PyTaskletObject *); + int (*transfer)(PyThreadState *, tealet_t *, PyTaskletObject *); /* remove the no-soft-irq flag from the runflags */ int no_soft_irq = ts->st.runflags & PY_WATCHDOG_NO_SOFT_IRQ; @@ -920,18 +862,16 @@ slp_schedule_task_prepared(PyThreadState *ts, PyObject **result, PyTaskletObject if (!stackless || ts->st.nesting_level != 0) goto hard_switching; - /* start of soft switching code */ + /* We never soft switch out of the main tasklet. Otherwise, we might end up soft switching + * back to the mani tasklet from another stack, but that screws up with assumptions + * of the caller. e.g. the main tasklet might make callbacks back into the program, but + * it really shouln't do that from a different stack. For example, the main stack might + * have structured exception handlers installed. + */ + if (prev == ts->st.main) + goto hard_switching; - if (prev->cstate != ts->st.initial_stub) { - Py_DECREF(prev->cstate); - prev->cstate = ts->st.initial_stub; - Py_INCREF(prev->cstate); - } - if (ts != slp_initial_tstate) { - /* ensure to get all tasklets into the other thread's chain */ - if (slp_ensure_linkage(prev) || slp_ensure_linkage(next)) - return -1; - } + /* start of soft switching code */ /* handle exception */ if (ts->exc_type == Py_None) { @@ -973,9 +913,8 @@ slp_schedule_task_prepared(PyThreadState *ts, PyObject **result, PyTaskletObject ts->c_traceobj = ts->c_profileobj = NULL; ts->tracing = ts->use_tracing = 0; } - assert(next->cstate != NULL); - if (next->cstate->nesting_level != 0) { + if (next->cstate) { /* can soft switch out of this tasklet, but the target tasklet * was in a hard switched state, so we need a helper frame to * jump to the destination stack @@ -1021,9 +960,8 @@ slp_schedule_task_prepared(PyThreadState *ts, PyObject **result, PyTaskletObject /* since we change the stack we must assure that the protocol was met */ STACKLESS_ASSERT(); - /* note: nesting_level is handled in cstack_new */ - cstprev = &prev->cstate; - + /* note: nesting_level is handled in slp_transfer */ + ts->st.current = next; if (ts->exc_type == Py_None) { @@ -1036,11 +974,11 @@ slp_schedule_task_prepared(PyThreadState *ts, PyObject **result, PyTaskletObject ++ts->st.nesting_level; if (ts->exc_type != NULL || ts->use_tracing || ts->tracing) - transfer = transfer_with_exc; + transfer = slp_transfer_with_exc; else transfer = slp_transfer; - if (transfer(cstprev, next->cstate, prev) >= 0) { + if (transfer(ts, next->cstate, prev) >= 0) { --ts->st.nesting_level; TASKLET_CLAIMVAL(prev, &retval); if (PyBomb_Check(retval)) @@ -1086,7 +1024,9 @@ initialize_main_and_current(void) &PyTasklet_Type, noargs, NULL); Py_DECREF(noargs); if (task == NULL) return -1; - assert(task->cstate != NULL); + task->cstate = NULL; + task->tstate = ts; + task->nesting_level = 0; ts->st.main = task; Py_INCREF(task); slp_current_insert(task); @@ -1213,17 +1153,6 @@ tasklet_end(PyObject *retval) */ TASKLET_SETVAL(task, retval); - if (ismain) { - /* - * Because of soft switching, we may find ourself in the top level of a stack that was created - * using another stub (another entry point into stackless). If so, we need a final return to - * the original stub if necessary. (Meanwhile, task->cstate may be an old nesting state and not - * the original stub, so we take the stub from the tstate) - */ - if (ts->st.serial_last_jump != ts->st.initial_stub->serial) - slp_transfer_return(ts->st.initial_stub); - } - /* remove from runnables */ slp_current_remove(); diff --git a/Stackless/module/stacklessmodule.c b/Stackless/module/stacklessmodule.c index 7d2ea7dd5cd672..9f9bd563fc20d9 100644 --- a/Stackless/module/stacklessmodule.c +++ b/Stackless/module/stacklessmodule.c @@ -6,7 +6,6 @@ #include "core/stackless_impl.h" #define IMPLEMENT_STACKLESSMODULE -#include "platf/slp_platformselect.h" #include "core/cframeobject.h" #include "taskletobject.h" #include "channelobject.h" @@ -659,6 +658,9 @@ This can be used to measure the execution time of 1.000.000 switches."); #define STACK_MAX_USEFUL 64000 #define STACK_MAX_USESTR "64000" +#ifdef MS_WINDOWS +#define alloca _alloca +#endif static PyObject * @@ -705,13 +707,12 @@ PyObject * test_outside(PyObject *self) { PyThreadState *ts = PyThreadState_GET(); - PyTaskletObject *stmain = ts->st.main; - PyCStackObject *cst = ts->st.initial_stub; + PyTaskletObject *stcurrent, *stmain = ts->st.main; + tealet_t *cst = ts->st.initial_stub; PyFrameObject *f = ts->frame; int recursion_depth = ts->recursion_depth; int nesting_level = ts->st.nesting_level; PyObject *ret = Py_None; - int jump = ts->st.serial_last_jump; Py_INCREF(ret); ts->st.main = NULL; @@ -719,6 +720,7 @@ test_outside(PyObject *self) ts->st.nesting_level = 0; ts->frame = NULL; ts->recursion_depth = 0; + stcurrent = ts->st.current; slp_current_remove(); while (ts->st.runcount > 0) { Py_DECREF(ret); @@ -728,13 +730,12 @@ test_outside(PyObject *self) } } ts->st.main = stmain; - Py_XDECREF(ts->st.initial_stub); + slp_destroy_initial_stub(ts); ts->st.initial_stub = cst; ts->frame = f; - slp_current_insert(stmain); + slp_current_insert(stcurrent); ts->recursion_depth = recursion_depth; ts->st.nesting_level = nesting_level; - ts->st.serial_last_jump = jump; return ret; } @@ -978,7 +979,6 @@ PyStackless_CallMethod_Main(PyObject *o, char *name, char *format, ...) return NULL; } } else { - /* direct call, no method lookup */ func = o; Py_INCREF(func); } @@ -1396,19 +1396,21 @@ static PyObject * slpmodule_getuncollectables(PySlpModuleObject *mod, void *context) { PyObject *lis = PyList_New(0); - PyCStackObject *cst = slp_cstack_chain; - + PyTealet_data *data = slp_tealet_list; + if (lis == NULL) return NULL; - do { - if (cst->task != NULL) { - if (PyList_Append(lis, (PyObject *) cst->task)) { - Py_DECREF(lis); - return NULL; + if (data) { + do { + if (data->tasklet) { + if (PyList_Append(lis, (PyObject*)data->tasklet)) { + Py_DECREF(lis); + return NULL; + } } - } - cst = cst->next; - } while (cst != slp_cstack_chain); + data = data->next; + } while (data != slp_tealet_list); + } return lis; } @@ -1604,7 +1606,6 @@ _PyStackless_Init(void) INSERT("slpmodule", PySlpModule_TypePtr); INSERT("cframe", &PyCFrame_Type); - INSERT("cstack", &PyCStack_Type); INSERT("bomb", &PyBomb_Type); INSERT("tasklet", &PyTasklet_Type); INSERT("channel", &PyChannel_Type); diff --git a/Stackless/module/taskletobject.c b/Stackless/module/taskletobject.c index 9b9dcff0fabe8a..13c739c732388c 100644 --- a/Stackless/module/taskletobject.c +++ b/Stackless/module/taskletobject.c @@ -13,7 +13,7 @@ void slp_current_insert(PyTaskletObject *task) { - PyThreadState *ts = task->cstate->tstate; + PyThreadState *ts = task->tstate; PyTaskletObject **chain = &ts->st.current; SLP_CHAIN_INSERT(PyTaskletObject, chain, task, next, prev); @@ -23,7 +23,7 @@ slp_current_insert(PyTaskletObject *task) void slp_current_insert_after(PyTaskletObject *task) { - PyThreadState *ts = task->cstate->tstate; + PyThreadState *ts = task->tstate; PyTaskletObject *hold = ts->st.current; PyTaskletObject **chain = &ts->st.current; @@ -36,7 +36,7 @@ slp_current_insert_after(PyTaskletObject *task) void slp_current_uninsert(PyTaskletObject *task) { - PyThreadState *ts = task->cstate->tstate; + PyThreadState *ts = task->tstate; PyTaskletObject *hold = ts->st.current; PyTaskletObject **chain = &ts->st.current; @@ -74,7 +74,7 @@ slp_current_unremove(PyTaskletObject* task) static int tasklet_has_c_stack(PyTaskletObject *t) { - return t->f.frame && t->cstate && t->cstate->nesting_level != 0 ; + return t->f.frame && t->cstate; } static int @@ -93,7 +93,6 @@ tasklet_traverse(PyTaskletObject *t, visitproc visit, void *arg) Py_VISIT(f); } Py_VISIT(t->tempval); - Py_VISIT(t->cstate); return 0; } @@ -117,11 +116,6 @@ tasklet_clear(PyTaskletObject *t) { tasklet_clear_frames(t); TASKLET_SETVAL(t, Py_None); /* always non-zero */ - - /* unlink task from cstate */ - if (t->cstate != NULL && t->cstate->task == t) - t->cstate->task = NULL; - Py_CLEAR(t->cstate); } /* @@ -140,7 +134,7 @@ kill_finally (PyObject *ob) { PyThreadState *ts = PyThreadState_GET(); PyTaskletObject *self = (PyTaskletObject *) ob; - int is_mine = ts == self->cstate->tstate; + int is_mine = ts == self->tstate; /* this could happen if we have a refcount bug, so catch it here. assert(self != ts->st.current); @@ -182,34 +176,18 @@ tasklet_dealloc(PyTaskletObject *t) tasklet_clear_frames(t); if (t->tsk_weakreflist != NULL) PyObject_ClearWeakRefs((PyObject *)t); - if (t->cstate != NULL) { - assert(t->cstate->task != t || t->cstate->ob_size == 0); - Py_DECREF(t->cstate); - } Py_DECREF(t->tempval); Py_XDECREF(t->def_globals); t->ob_type->tp_free((PyObject*)t); } -static PyTaskletObject * -PyTasklet_New_M(PyTypeObject *type, PyObject *func) -{ - char *fmt = "(O)"; - - if (func == NULL) fmt = NULL; - return (PyTaskletObject *) PyStackless_CallMethod_Main( - (PyObject*)type, NULL, fmt, func); -} - PyTaskletObject * PyTasklet_New(PyTypeObject *type, PyObject *func) { PyThreadState *ts = PyThreadState_GET(); PyTaskletObject *t; - /* we always need a cstate, so be sure to initialize */ - if (ts->st.initial_stub == NULL) return PyTasklet_New_M(type, func); if (func != NULL && !PyCallable_Check(func)) TYPE_ERROR("tasklet function must be a callable", NULL); if (type == NULL) type = &PyTasklet_Type; @@ -223,19 +201,13 @@ PyTasklet_New(PyTypeObject *type, PyObject *func) if (func == NULL) func = Py_None; Py_INCREF(func); - t->tempval = func; + t->tempval = func; t->tsk_weakreflist = NULL; - Py_INCREF(ts->st.initial_stub); - t->cstate = ts->st.initial_stub; + t->cstate = 0; + t->tstate = ts; + t->nesting_level = 0; t->def_globals = PyEval_GetGlobals(); Py_XINCREF(t->def_globals); - if (ts != slp_initial_tstate) { - /* make sure to kill tasklets with their thread */ - if (slp_ensure_linkage(t)) { - Py_DECREF(t); - return NULL; - } - } } return t; } @@ -324,12 +296,11 @@ tasklet_reduce(PyTaskletObject * t) f = f->f_back; } if (PyList_Reverse(lis)) goto err_exit; - assert(t->cstate != NULL); tup = Py_BuildValue("(O()(" TASKLET_TUPLEFMT "))", t->ob_type, t->flags, t->tempval, - t->cstate->nesting_level, + t->nesting_level, lis ); err_exit: @@ -516,14 +487,14 @@ static TASKLET_INSERT_HEAD(impl_tasklet_insert) if (task->next == NULL) { if (task->f.frame == NULL && task != ts->st.current) RUNTIME_ERROR("You cannot run an unbound(dead) tasklet", -1); - if (task->cstate->tstate->st.main == NULL) + if (task->tstate->st.main == NULL) RUNTIME_ERROR("Target thread isn't initialized", -1); Py_INCREF(task); slp_current_insert(task); /* The tasklet may belong to a different thread, and that thread may * be blocked, waiting for something to do! */ - slp_thread_unblock(task->cstate->tstate); + slp_thread_unblock(task->tstate); } return 0; } @@ -584,7 +555,7 @@ static TASKLET_RUN_HEAD(impl_tasklet_run) if (ts->st.main == NULL) return PyTasklet_Run_M(task); inserted = task->next == NULL; - if (ts == task->cstate->tstate) { + if (ts == task->tstate) { /* same thread behaviour. Insert at the end of the queue and then * switch to that task. Notice that this behaviour upsets FIFO * order @@ -592,7 +563,7 @@ static TASKLET_RUN_HEAD(impl_tasklet_run) fail = impl_tasklet_insert(task); } else { /* interthread. */ - PyThreadState *rts = task->cstate->tstate; + PyThreadState *rts = task->tstate; PyTaskletObject *current = rts->st.current; if (rts->st.thread.is_idle) { /* remote thread is blocked, or unblocked and hasn't got the GIL yet. @@ -741,20 +712,13 @@ tasklet_set_ignore_nesting(PyObject *self, PyObject *flag) static int bind_tasklet_to_frame(PyTaskletObject *task, PyFrameObject *frame) { - PyThreadState *ts = task->cstate->tstate; - + PyThreadState *ts = PyThreadState_GET(); + if (task->f.frame != NULL) RUNTIME_ERROR("tasklet is already bound to a frame", -1); task->f.frame = frame; - if (task->cstate != ts->st.initial_stub) { - PyCStackObject *hold = task->cstate; - task->cstate = ts->st.initial_stub; - Py_INCREF(task->cstate); - Py_DECREF(hold); - if (ts != slp_initial_tstate) - if (slp_ensure_linkage(task)) - return -1; - } + task->tstate = ts; + task->nesting_level = 0; return 0; /* note: We expect that f_back is NULL, or will be adjusted immediately */ } @@ -861,7 +825,7 @@ static TASKLET_THROW_HEAD(impl_tasklet_throw) * f.frame is null for the running tasklet and a dead tasklet * A new tasklet has a CFrame */ - if (self->f.frame == NULL && self != self->cstate->tstate->st.current) { + if (self->f.frame == NULL && self != self->tstate->st.current) { /* however, allow tasklet exit errors for already dead tasklets */ if (PyObject_IsSubclass(((PyBombObject*)bomb)->curexc_type, PyExc_TaskletExit)) { Py_DECREF(bomb); @@ -1176,8 +1140,7 @@ tasklet_get_recursion_depth(PyTaskletObject *task) { PyThreadState *ts; - assert(task->cstate != NULL); - ts = task->cstate->tstate; + ts = task->tstate; return PyInt_FromLong(ts->st.current == task ? ts->recursion_depth : task->recursion_depth); } @@ -1187,8 +1150,7 @@ PyTasklet_GetRecursionDepth(PyTaskletObject *task) { PyThreadState *ts; - assert(task->cstate != NULL); - ts = task->cstate->tstate; + ts = task->tstate; return ts->st.current == task ? ts->recursion_depth : task->recursion_depth; } @@ -1199,11 +1161,10 @@ tasklet_get_nesting_level(PyTaskletObject *task) { PyThreadState *ts; - assert(task->cstate != NULL); - ts = task->cstate->tstate; + ts = task->tstate; return PyInt_FromLong( ts->st.current == task ? ts->st.nesting_level - : task->cstate->nesting_level); + : task->nesting_level); } int @@ -1211,10 +1172,9 @@ PyTasklet_GetNestingLevel(PyTaskletObject *task) { PyThreadState *ts; - assert(task->cstate != NULL); - ts = task->cstate->tstate; + ts = task->tstate; return ts->st.current == task ? ts->st.nesting_level - : task->cstate->nesting_level; + : task->nesting_level; } @@ -1264,11 +1224,10 @@ tasklet_restorable(PyTaskletObject *task) { PyThreadState *ts; - assert(task->cstate != NULL); - ts = task->cstate->tstate; + ts = task->tstate; return PyBool_FromLong( 0 == (ts->st.current == task ? ts->st.nesting_level - : task->cstate->nesting_level) ); + : task->nesting_level) ); } int @@ -1276,10 +1235,9 @@ PyTasklet_Restorable(PyTaskletObject *task) { PyThreadState *ts; - assert(task->cstate != NULL); - ts = task->cstate->tstate; + ts = task->tstate; return 0 == (ts->st.current == task ? ts->st.nesting_level - : task->cstate->nesting_level); + : task->nesting_level); } static PyObject * @@ -1322,14 +1280,10 @@ tasklet_get_prev(PyTaskletObject *task) static PyObject * tasklet_thread_id(PyTaskletObject *task) { - return PyInt_FromLong(task->cstate->tstate->thread_id); + return PyInt_FromLong(task->tstate->thread_id); } static PyMemberDef tasklet_members[] = { - {"cstate", T_OBJECT, offsetof(PyTaskletObject, cstate), READONLY, - PyDoc_STR("the C stack object associated with the tasklet.\n\ - Every tasklet has a cstate, even if it is a trivial one.\n\ - Please see the cstate doc and the stackless documentation.")}, {"tempval", T_OBJECT, offsetof(PyTaskletObject, tempval), 0}, /* blocked, slicing_lock, atomic and such are treated by tp_getset */ {0} diff --git a/Stackless/pickling/prickelpit.c b/Stackless/pickling/prickelpit.c index ff3f08a42a3a92..878093625c920d 100644 --- a/Stackless/pickling/prickelpit.c +++ b/Stackless/pickling/prickelpit.c @@ -7,9 +7,6 @@ #include "pickling/prickelpit.h" #include "module/channelobject.h" -/* platform specific constants */ -#include "platf/slp_platformselect.h" - /****************************************************** type template and support for pickle helper types diff --git a/Stackless/pickling/safe_pickle.c b/Stackless/pickling/safe_pickle.c index 964d2282ff1a31..ff02112c7cbd71 100644 --- a/Stackless/pickling/safe_pickle.c +++ b/Stackless/pickling/safe_pickle.c @@ -1,134 +1,59 @@ #include "Python.h" #ifdef STACKLESS -#include "compile.h" - -#include "core/stackless_impl.h" -#include "platf/slp_platformselect.h" +#include "core/stackless_tealet.h" /* safe pickling */ -static int(*cPickle_save)(PyObject *, PyObject *, int) = NULL; - -static PyObject * -pickle_callback(PyFrameObject *f, int exc, PyObject *retval) +typedef struct pickle_args { - PyThreadState *ts = PyThreadState_GET(); - PyTaskletObject *cur = ts->st.current; - PyCStackObject *cst; - PyCFrameObject *cf = (PyCFrameObject *) f; - intptr_t *saved_base; - - /* store and update thread state */ - ts->st.nesting_level = 1; /* always hard-switch from this one now */ - /* swap the cstates, hold on to the return one */ - cst = cur->cstate; - cur->cstate = ts->st.initial_stub; - Py_INCREF(cur->cstate); - - /* We must base our new stack from here, because oterwise we might find - * ourselves in an infinite loop of stack spilling. - */ - saved_base = ts->st.cstack_root; - ts->st.cstack_root = STACK_REFPLUS + (intptr_t *) &f; - Py_DECREF(retval); - cf->i = cPickle_save(cf->ob1, cf->ob2, cf->n); - ts->st.cstack_root = saved_base; - - /* jump back. No decref, frame contains result. */ - Py_DECREF(cur->cstate); - cur->cstate = cst; - ts->frame = cf->f_back; - slp_transfer_return(cst); - /* never come here */ - return NULL; + PyThreadState *ts; + tealet_t *return_to; + int (*save)(PyObject *, PyObject *, int); + PyObject *self; + PyObject *args; + int pers_save; + int result; +} pickle_args; + + +static tealet_t * +pickle_callback(tealet_t *current, void *arg) +{ + pickle_args *args = (pickle_args*)arg; + args->ts->st.nesting_level++; /* always hard-switch from this one now */ + args->result = (*args->save)(args->self, args->args, args->pers_save); + args->ts->st.nesting_level--; + return args->return_to; } -static int pickle_M(PyObject *self, PyObject *args, int pers_save); - int slp_safe_pickling(int(*save)(PyObject *, PyObject *, int), PyObject *self, PyObject *args, int pers_save) { PyThreadState *ts = PyThreadState_GET(); - PyTaskletObject *cur = ts->st.current; - int ret = -1; - PyCFrameObject *cf = NULL; - PyCStackObject *cst; - - if (ts->st.cstack_root == NULL) { - /* mark the stack spilling base */ - ts->st.cstack_root = STACK_REFPLUS + (intptr_t *) &save; - ret = (*save)(self, args, pers_save); - ts->st.cstack_root = NULL; - return ret; + pickle_args *pargs = PyMem_MALLOC(sizeof(pickle_args)); + void *ppargs; + int result; + if (!pargs) { + PyErr_NoMemory(); + return -1; } - - cPickle_save = save; - - if (ts->st.main == NULL) - return pickle_M(self, args, pers_save); - - cf = slp_cframe_new(pickle_callback, 1); - if (cf == NULL) - goto finally; - Py_INCREF(self); - cf->ob1 = self; - Py_INCREF(args); - cf->ob2 = args; - cf->n = pers_save; - ts->frame = (PyFrameObject *) cf; - cst = cur->cstate; - cur->cstate = NULL; - if (slp_transfer(&cur->cstate, NULL, cur) < 0) - return -1; /* fatal */ - Py_XDECREF(cur->cstate); - cur->cstate = cst; - ret = cf->i; -finally: - Py_XDECREF(cf); - return ret; -} - -/* safe unpickling is not needed */ - - -/* - * the following stuff is only needed in the rare case that we are - * run without any initialisation. In this case, we don't save stack - * but use slp_eval_frame, which initializes everything. - */ - -static PyObject *_self, *_args; -static int _pers_save; - -static PyObject * -pickle_runmain(PyFrameObject *f, int exc, PyObject *retval) -{ - PyThreadState *ts = PyThreadState_GET(); - Py_XDECREF(retval); - ts->frame = f->f_back; - Py_DECREF(f); - return PyInt_FromLong(cPickle_save(_self, _args, _pers_save)); + pargs->ts = ts; + pargs->return_to = tealet_current(ts->st.tealet_main); + pargs->save = save; + pargs->self = self; + pargs->args = args; + pargs->pers_save = pers_save; + + ppargs = (void*)pargs; + result = slp_run_initial_stub(ts, pickle_callback, &ppargs); + if (result == 0) + result = pargs->result; + PyMem_FREE(pargs); + return result; } -static int -pickle_M(PyObject *self, PyObject *args, int pers_save) -{ - PyThreadState *ts = PyThreadState_GET(); - PyCFrameObject *cf = slp_cframe_new(pickle_runmain, 0); - int ret; - intptr_t *old_root; - - if (cf == NULL) return -1; - _self = self; - _args = args; - _pers_save = pers_save; - old_root = ts->st.cstack_root; - ts->st.cstack_root = STACK_REFPLUS + (intptr_t *) &self; - ret = slp_int_wrapper(slp_eval_frame((PyFrameObject *)cf)); - ts->st.cstack_root = old_root; - return ret; -} -#endif +/* safe unpickling is not needed */ +#endif \ No newline at end of file diff --git a/Stackless/platf/switch_amd64_unix_gas.s b/Stackless/platf/switch_amd64_unix_gas.s deleted file mode 100644 index 6f61106572728b..00000000000000 --- a/Stackless/platf/switch_amd64_unix_gas.s +++ /dev/null @@ -1,56 +0,0 @@ -# NOTE: This is not yet safe to use. Checked in for the same of reference. -# -# (int) slp_switch (void); - - .text - .type slp_switch, @function - # This next line is required to the C code can find and - # link against this function. - .global slp_switch -slp_switch: - pushq %rbp - pushq %r15 - pushq %r14 - pushq %r13 - pushq %r12 - pushq %rbx - - # Disabled for now, which should ideally give us identical - # behaviour to the inline C version. Can add this when we - # are ready for it. - #subq $8, %rsp - #stmxcsr (%rsp) - - movq %rsp, %rdi - - call slp_save_state # diff = slp_save_state([?]stackref) - - cmp $-1, %rax # if (diff == -1) - je .exit # return -1; - - cmp $1, %rax # if (diff == 1) - je .no_restore # return 0; - -.restore: - add %rax, %rsp # Adjust the stack pointer for the state we are restoring. - - call slp_restore_state # slp_restore_state() - -.no_restore: - xor %rax, %rax # Switch successful (whether we restored or not). - -.exit: - #ldmxcsr (%rsp) - #addq $8, %rsp - - popq %rbx - popq %r12 - popq %r13 - popq %r14 - popq %r15 - # rbp gets popped by the leave statement - - leave - ret -.LFE11: - .size slp_switch, .-slp_switch diff --git a/Stackless/tealet/platf_slp/mkswitch_stack.py b/Stackless/tealet/platf_slp/mkswitch_stack.py new file mode 100644 index 00000000000000..5a047ef7f2998e --- /dev/null +++ b/Stackless/tealet/platf_slp/mkswitch_stack.py @@ -0,0 +1,71 @@ +""" + mkswitch_stack.py + + Purpose: + Generate an include file from the platform dependant + include files mentioned in slp_platformselect.h . + + The existing slp_switch implementations are calling + the macros slp_save_state and slp_restore_state. + Now I want to support real stack switching, that is, + the stack is not modified in place, but we jump to + a different stack, without copying anything. + This costs a lot of memory and should be used for + a few high-speed tasklets, only. + + In order to keep things simple, I'm not special-casing + the support macroes, but use a different macro set. + The machine code is the same, therefore the implementation + can be generated from the existing include files. + + We generate a new include file called slp_switch_stack.h . +""" + +def parse_platformselect(): + fin_name = "slp_platformselect.h" + fin = file(fin_name) + fout_name = "slp_switch_stack.h" + fout = file(fout_name, "w") + import sys + print>>fout, "/* this file is generated by mkswitch_stack.py, don't edit */" + print>>fout + for line in fin: + tokens = line.split() + if not tokens: continue + tok = tokens[0] + if tok == "#endif": + print>>fout, line + break # done + if tok in ("#if", "#elif"): + print>>fout, line + elif tok == "#include": + finc_name = tokens[1][1:-1] + txt = parse_switch(finc_name) + print>>fout, txt + +edits = ( + ("slp_switch", "slp_switch_stack"), + ("SLP_SAVE_STATE", "SLP_STACK_BEGIN"), + ("SLP_RESTORE_STATE", "SLP_STACK_END"), +) + +def parse_switch(fname): + f = file(fname) + res = [] + for line in f: + if line.strip() == "static int": + res.append(line) + break + for line in f: + res.append(line) + if line.rstrip() == "}": + break + # end of procedure. + # now substitute + s = "".join(res) + for txt, repl in edits: + s = s.replace(txt, repl) + return s + +if __name__ == "__main__": + parse_platformselect() \ No newline at end of file diff --git a/Stackless/tealet/platf_slp/slp_fallback.h b/Stackless/tealet/platf_slp/slp_fallback.h new file mode 100644 index 00000000000000..ef4687f3df025b --- /dev/null +++ b/Stackless/tealet/platf_slp/slp_fallback.h @@ -0,0 +1,67 @@ +/* slp_fallback.h + * Implements a fallback mechanism where the stacless python switching code be used. + * Stackless python switching code uses global variables to communicate and thus is not + * thread safe. We do not attempt to provide a lock for those global variables here, it + * is up to the user in each case to ensure that switching only occurs on one thread at + * a time. + */ + #include + + +/* rename the slp_switch function that the slp headers will define */ +#define slp_switch fallback_slp_switch + +/* sttic vars to pass inforamtion around. This is what makes this non-threadsafe */ +typedef void *(*save_restore_t)(void*, void*); +static save_restore_t fallback_save_state=NULL, fallback_restore_state=NULL; +static void *fallback_extra=NULL; +static void *fallback_newstack = NULL; + +/* call the provided save_satate method with its arguments. + * compute a proper stack delta and store the result for later + */ +#define SLP_SAVE_STATE(stackref, stsizediff) do {\ + intptr_t stsizeb; \ + void *newstack; \ + stackref += STACK_MAGIC; \ + newstack = fallback_save_state(stackref, fallback_extra); \ + fallback_newstack = newstack; /* store this for restore_state */ \ + if ((intptr_t)newstack & 1) \ + return (int)newstack; \ + /* compute the delta expected by old switching code */ \ + stsizediff = ((intptr_t*) newstack - (intptr_t*)stackref) * sizeof(intptr_t); \ +} while (0) + +/* call the restore_state using the stored data */ +#define SLP_RESTORE_STATE() do { \ + fallback_restore_state(fallback_newstack, fallback_extra); \ +} while(0) + +#define SLP_EVAL +#include "slp_platformselect.h" + +/* This is a wrapper that takes care of setting the appropriate globals */ +#undef slp_switch +static void *slp_switch(save_restore_t save_state, + save_restore_t restore_state, + void *extra) +{ + /* need to store the restore information on the stack */ + void *result; + fallback_save_state = save_state; + fallback_restore_state = restore_state; + fallback_extra = extra; + + fallback_slp_switch(); + result = fallback_newstack; + + /* clearing them again is prudent */ + fallback_save_state = fallback_restore_state = NULL; + fallback_extra = fallback_newstack = NULL; + + /* successful switch was indicated by save_state returning an even result */ + if (! ((intptr_t)result & 1)) + result = NULL; + /* otherwise it is 1 or -1 */ + return result; +} \ No newline at end of file diff --git a/Stackless/platf/slp_platformselect.h b/Stackless/tealet/platf_slp/slp_platformselect.h similarity index 96% rename from Stackless/platf/slp_platformselect.h rename to Stackless/tealet/platf_slp/slp_platformselect.h index 59ad0ef371404e..90b46370c6e463 100644 --- a/Stackless/platf/slp_platformselect.h +++ b/Stackless/tealet/platf_slp/slp_platformselect.h @@ -2,9 +2,9 @@ * Platform Selection for Stackless Python */ -#if defined(MS_WIN32) && !defined(MS_WIN64) && defined(_M_IX86) +#if defined(_M_IX86) #include "switch_x86_msvc.h" /* MS Visual Studio on X86 */ -#elif defined(MS_WIN64) && defined(_M_X64) +#elif defined(_M_X64) #include "switch_x64_msvc.h" /* MS Visual Studio on X64 */ #elif defined(__GNUC__) && defined(__i386__) #include "switch_x86_unix.h" /* gcc on X86 */ @@ -28,6 +28,8 @@ #include "switch_mips_unix.h" /* MIPS */ #elif defined(SN_TARGET_PS3) #include "switch_ps3_SNTools.h" /* Sony PS3 */ +#else +#error "platform unsupported by stackless python" #endif /* default definitions if not defined in above files */ diff --git a/Stackless/tealet/platf_slp/slp_switch_stack.h b/Stackless/tealet/platf_slp/slp_switch_stack.h new file mode 100644 index 00000000000000..4eb03f379a6f2f --- /dev/null +++ b/Stackless/tealet/platf_slp/slp_switch_stack.h @@ -0,0 +1,279 @@ +/* this file is generated by mkswitch_stack.py, don't edit */ + +#if defined(MS_WIN32) && !defined(MS_WIN64) && defined(_M_IX86) + +static int +slp_switch_stack(void) +{ + register int *stackref, stsizediff; + __asm mov stackref, esp; + /* modify EBX, ESI and EDI in order to get them preserved */ + __asm mov ebx, ebx; + __asm xchg esi, edi; + { + SLP_STACK_BEGIN(stackref, stsizediff); + __asm { + mov eax, stsizediff + add esp, eax + add ebp, eax + } + SLP_STACK_END(); + return 0; + } +#pragma warning(default:4731) +} + +#elif defined(MS_WIN64) && defined(_M_X64) + + +#elif defined(__GNUC__) && defined(__i386__) + +static int +slp_switch_stack(void) +{ + register int *stackref, stsizediff; +#if STACKLESS_FRHACK + __asm__ volatile ("" : : : "esi", "edi"); +#else + __asm__ volatile ("" : : : "ebx", "esi", "edi"); +#endif + __asm__ ("movl %%esp, %0" : "=g" (stackref)); + { + SLP_STACK_BEGIN(stackref, stsizediff); + __asm__ volatile ( + "addl %0, %%esp\n" + "addl %0, %%ebp\n" + : + : "r" (stsizediff) + ); + SLP_STACK_END(); + return 0; + } +#if STACKLESS_FRHACK + __asm__ volatile ("" : : : "esi", "edi"); +#else + __asm__ volatile ("" : : : "ebx", "esi", "edi"); +#endif +} + +#elif defined(__GNUC__) && defined(__amd64__) + +static int +slp_switch_stack(void) +{ + register long *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ ("movq %%rsp, %0" : "=g" (stackref)); + { + SLP_STACK_BEGIN(stackref, stsizediff); + __asm__ volatile ( + "addq %0, %%rsp\n" + "addq %0, %%rbp\n" + : + : "r" (stsizediff) + ); + SLP_STACK_END(); + return 0; + } + __asm__ volatile ("" : : : REGS_TO_SAVE); +} + +#elif defined(__GNUC__) && defined(__PPC__) && defined(__linux__) + +static int +slp_switch_stack(void) +{ + register int *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ ("mr %0, 1" : "=g" (stackref) : ); + { + SLP_STACK_BEGIN(stackref, stsizediff); + __asm__ volatile ( + "mr 11, %0\n" + "add 1, 1, 11\n" + "add 30, 30, 11\n" + : /* no outputs */ + : "g" (stsizediff) + : "11" + ); + SLP_STACK_END(); + return 0; + } + __asm__ volatile ("" : : : REGS_TO_SAVE); +} + +#elif defined(__GNUC__) && defined(__ppc__) && defined(__APPLE__) + +static int +slp_switch_stack(void) +{ + static int x = 0; + register intptr_t *stackref; + register int stsizediff; + __asm__ volatile ( + "; asm block 1\n" + : /* no outputs */ + : "r" (x) + : REGS_TO_SAVE + ); + __asm__ ("; asm block 2\n\tmr %0, r1" : "=g" (stackref) : ); + { + SLP_STACK_BEGIN(stackref, stsizediff); + __asm__ volatile ( + "; asm block 3\n" + "\tmr r11, %0\n" + "\tadd r1, r1, r11\n" + "\tadd r30, r30, r11\n" + : /* no outputs */ + : "g" (stsizediff) + : "r11" + ); + SLP_STACK_END(); + return 0; + } +} + +#elif defined(__GNUC__) && defined(sparc) && defined(sun) + +static int +slp_switch_stack(void) +{ + register int *stackref, stsizediff; + + /* Put the stack pointer into stackref */ + + /* Sparc special: at first, flush register windows + */ + __asm__ volatile ( + "ta %1\n\t" + "mov %%sp, %0" + : "=r" (stackref) : "i" (ST_FLUSH_WINDOWS)); + + { /* You shalt put SLP_STACK_BEGIN into a local block */ + + SLP_STACK_BEGIN(stackref, stsizediff); + + /* Increment stack and frame pointer by stsizediff */ + + /* Sparc special: at first load new return address. + This cannot be done later, because the stack + might be overwritten again just after SLP_STACK_END + has finished. BTW: All other registers (l0-l7 and i0-i5) + might be clobbered too. + */ + __asm__ volatile ( + "ld [%0+60], %%i7\n\t" + "add %1, %%sp, %%sp\n\t" + "add %1, %%fp, %%fp" + : : "r" (_cst->stack), "r" (stsizediff) + : "%l0", "%l1", "%l2", "%l3", "%l4", "%l5", "%l6", "%l7", + "%i0", "%i1", "%i2", "%i3", "%i4", "%i5"); + + SLP_STACK_END(); + + /* Run far away as fast as possible, don't look back at the sins. + * The LORD rained down burning sulfur on Sodom and Gomorra ... + */ + + /* Sparc special: Must make it *very* clear to the CPU that + it shouldn't look back into the register windows + */ + __asm__ volatile ( "ta %0" : : "i" (ST_CLEAN_WINDOWS)); + return 0; + } +} + +#elif defined(__GNUC__) && defined(__s390__) && defined(__linux__) + +static int +slp_switch_stack(void) +{ + register int *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ ("lr %0, 15" : "=g" (stackref) : ); + { + SLP_STACK_BEGIN(stackref, stsizediff); + __asm__ volatile ( + "ar 15, %0" + : /* no outputs */ + : "g" (stsizediff) + ); + SLP_STACK_END(); + return 0; + } + __asm__ volatile ("" : : : REGS_TO_SAVE); +} + +#elif defined(__GNUC__) && defined(__s390x__) && defined(__linux__) + +static int +slp_switch_stack(void) +{ + register int *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ ("lr %0, 15" : "=g" (stackref) : ); + { + SLP_STACK_BEGIN(stackref, stsizediff); + __asm__ volatile ( + "ar 15, %0" + : /* no outputs */ + : "g" (stsizediff) + ); + SLP_STACK_END(); + return 0; + } + __asm__ volatile ("" : : : REGS_TO_SAVE); +} + +#elif defined(__GNUC__) && defined(__arm__) && defined(__thumb__) + + +#elif defined(__GNUC__) && defined(__arm32__) + +static int +slp_switch_stack(void) +{ + register int *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ ("mov %0,sp" : "=g" (stackref)); + { + SLP_STACK_BEGIN(stackref, stsizediff); + __asm__ volatile ( + "add sp,sp,%0\n" + "add fp,fp,%0\n" + : + : "r" (stsizediff) + ); + SLP_STACK_END(); + return 0; + } + __asm__ volatile ("" : : : REGS_TO_SAVE); +} + +#elif defined(__GNUC__) && defined(__mips__) && defined(__linux__) + +static int +slp_switch_stack(void) +{ + register int *stackref, stsizediff; + /* __asm__ __volatile__ ("" : : : REGS_TO_SAVE); */ + __asm__ ("move %0, $29" : "=r" (stackref) : ); + { + SLP_STACK_BEGIN(stackref, stsizediff); + __asm__ __volatile__ ( +#ifdef __mips64 + "daddu $29, %0\n" +#else + "addu $29, %0\n" +#endif + : /* no outputs */ + : "r" (stsizediff) + ); + SLP_STACK_END(); + } + /* __asm__ __volatile__ ("" : : : REGS_TO_SAVE); */ + return 0; +} + +#endif + diff --git a/Stackless/platf/switch_amd64_unix.h b/Stackless/tealet/platf_slp/switch_amd64_unix.h similarity index 100% rename from Stackless/platf/switch_amd64_unix.h rename to Stackless/tealet/platf_slp/switch_amd64_unix.h diff --git a/Stackless/platf/switch_arm32_gcc.h b/Stackless/tealet/platf_slp/switch_arm32_gcc.h similarity index 100% rename from Stackless/platf/switch_arm32_gcc.h rename to Stackless/tealet/platf_slp/switch_arm32_gcc.h diff --git a/Stackless/platf/switch_arm_thumb_gas.s b/Stackless/tealet/platf_slp/switch_arm_thumb_gas.s similarity index 100% rename from Stackless/platf/switch_arm_thumb_gas.s rename to Stackless/tealet/platf_slp/switch_arm_thumb_gas.s diff --git a/Stackless/platf/switch_arm_thumb_gcc.h b/Stackless/tealet/platf_slp/switch_arm_thumb_gcc.h similarity index 100% rename from Stackless/platf/switch_arm_thumb_gcc.h rename to Stackless/tealet/platf_slp/switch_arm_thumb_gcc.h diff --git a/Stackless/platf/switch_mips_unix.h b/Stackless/tealet/platf_slp/switch_mips_unix.h similarity index 100% rename from Stackless/platf/switch_mips_unix.h rename to Stackless/tealet/platf_slp/switch_mips_unix.h diff --git a/Stackless/platf/switch_ppc_macosx.h b/Stackless/tealet/platf_slp/switch_ppc_macosx.h similarity index 100% rename from Stackless/platf/switch_ppc_macosx.h rename to Stackless/tealet/platf_slp/switch_ppc_macosx.h diff --git a/Stackless/platf/switch_ppc_unix.h b/Stackless/tealet/platf_slp/switch_ppc_unix.h similarity index 99% rename from Stackless/platf/switch_ppc_unix.h rename to Stackless/tealet/platf_slp/switch_ppc_unix.h index ccc893b2c2022b..f0bf07845f1dcf 100644 --- a/Stackless/platf/switch_ppc_unix.h +++ b/Stackless/tealet/platf_slp/switch_ppc_unix.h @@ -65,7 +65,7 @@ slp_switch(void) * further self-processing support */ -/* +/* * if you want to add self-inspection tools, place them * here. See the x86_msvc for the necessary defines. * These features are highly experimental und not diff --git a/Stackless/platf/switch_ps3_SNTools.h b/Stackless/tealet/platf_slp/switch_ps3_SNTools.h similarity index 100% rename from Stackless/platf/switch_ps3_SNTools.h rename to Stackless/tealet/platf_slp/switch_ps3_SNTools.h diff --git a/Stackless/platf/switch_ps3_SNTools.s b/Stackless/tealet/platf_slp/switch_ps3_SNTools.s similarity index 100% rename from Stackless/platf/switch_ps3_SNTools.s rename to Stackless/tealet/platf_slp/switch_ps3_SNTools.s diff --git a/Stackless/platf/switch_s390_unix.h b/Stackless/tealet/platf_slp/switch_s390_unix.h similarity index 100% rename from Stackless/platf/switch_s390_unix.h rename to Stackless/tealet/platf_slp/switch_s390_unix.h diff --git a/Stackless/platf/switch_sparc_sun_gcc.h b/Stackless/tealet/platf_slp/switch_sparc_sun_gcc.h similarity index 100% rename from Stackless/platf/switch_sparc_sun_gcc.h rename to Stackless/tealet/platf_slp/switch_sparc_sun_gcc.h diff --git a/Stackless/platf/switch_x64_masm.asm b/Stackless/tealet/platf_slp/switch_x64_masm.asm similarity index 100% rename from Stackless/platf/switch_x64_masm.asm rename to Stackless/tealet/platf_slp/switch_x64_masm.asm diff --git a/Stackless/platf/switch_x64_msvc.h b/Stackless/tealet/platf_slp/switch_x64_msvc.h similarity index 100% rename from Stackless/platf/switch_x64_msvc.h rename to Stackless/tealet/platf_slp/switch_x64_msvc.h diff --git a/Stackless/platf/switch_x86_msvc.h b/Stackless/tealet/platf_slp/switch_x86_msvc.h similarity index 100% rename from Stackless/platf/switch_x86_msvc.h rename to Stackless/tealet/platf_slp/switch_x86_msvc.h diff --git a/Stackless/platf/switch_x86_unix.h b/Stackless/tealet/platf_slp/switch_x86_unix.h similarity index 93% rename from Stackless/platf/switch_x86_unix.h rename to Stackless/tealet/platf_slp/switch_x86_unix.h index 6668e659bb5ff3..c95362c0acbea5 100644 --- a/Stackless/platf/switch_x86_unix.h +++ b/Stackless/tealet/platf_slp/switch_x86_unix.h @@ -34,7 +34,7 @@ slp_switch(void) #if STACKLESS_FRHACK __asm__ volatile ("" : : : "esi", "edi"); #else - __asm__ volatile ("" : : : "ebx", "esi", "edi"); + __asm__ volatile ("" : : : "ebx", "esi", "edi", "ebp"); #endif __asm__ ("movl %%esp, %0" : "=g" (stackref)); { @@ -51,7 +51,7 @@ slp_switch(void) #if STACKLESS_FRHACK __asm__ volatile ("" : : : "esi", "edi"); #else - __asm__ volatile ("" : : : "ebx", "esi", "edi"); + __asm__ volatile ("" : : : "ebx", "esi", "edi", "ebp"); #endif } diff --git a/Stackless/tealet/slp_platformselect.h b/Stackless/tealet/slp_platformselect.h new file mode 100644 index 00000000000000..e86580628f9e7d --- /dev/null +++ b/Stackless/tealet/slp_platformselect.h @@ -0,0 +1,32 @@ + +/* define USE_SLP_FALLBACK here if you want to test the SLP_FALLBACK mechanism */ +#define ENABLE_SLP_FALLBACK +/* #define USE_SLP_FALLBACK */ + +#if defined USE_SLP_FALLBACK && !defined ENABLE_SLP_FALLBACK +#define ENABLE_SLP_FALLBACK +#endif + +#ifndef USE_SLP_FALLBACK +#if defined(_M_IX86) +#include "switch_x86_msvc.h" /* MS Visual Studio on X86 */ +#elif defined(_M_X64) +#include "switch_x64_msvc.h" /* MS Visual Studio on X64 */ +#elif defined(__GNUC__) && defined(__amd64__) +#include "switch_x86_64_gcc.h" /* gcc on amd64 */ +#elif defined(__GNUC__) && defined(__i386__) +#include "switch_x86_gcc.h" /* gcc on X86 */ +#else +#ifdef ENABLE_SLP_FALLBACK +#define USE_SLP_FALLBACK +#else +#error "Unsupported platform!" +#endif +#endif +#endif + +#ifdef USE_SLP_FALLBACK +/* hope this is standard C */ +#pragma message("fallback to stackless platform support. Switching is not thread-safe") +#include "platf_slp/slp_fallback.h" +#endif diff --git a/Stackless/tealet/switch_x64_msvc.asm b/Stackless/tealet/switch_x64_msvc.asm new file mode 100644 index 00000000000000..a9ebd40409703e --- /dev/null +++ b/Stackless/tealet/switch_x64_msvc.asm @@ -0,0 +1,103 @@ +; +; stack switching code for MASM on x64 +; Kristjan Valur Jonsson, apr 2011 +; + +include macamd64.inc + +pop_reg MACRO reg + pop reg +ENDM + +load_xmm128 macro Reg, Offset + movdqa Reg, Offset[rsp] +endm + +.code + +;arguments save_state, restore_state, extra are passed in rcx, rdx, r8 respectively +;slp_switch PROC FRAME +NESTED_ENTRY slp_switch, _TEXT$00 + ; save all registers that the x64 ABI specifies as non-volatile. + ; This includes some mmx registers. May not always be necessary, + ; unless our application is doing 3D, but better safe than sorry. + alloc_stack 168; 10 * 16 bytes, plus 8 bytes to make stack 16 byte aligned + save_xmm128 xmm15, 144 + save_xmm128 xmm14, 128 + save_xmm128 xmm13, 112 + save_xmm128 xmm12, 96 + save_xmm128 xmm11, 80 + save_xmm128 xmm10, 64 + save_xmm128 xmm9, 48 + save_xmm128 xmm8, 32 + save_xmm128 xmm7, 16 + save_xmm128 xmm6, 0 + + push_reg r15 + push_reg r14 + push_reg r13 + push_reg r12 + + push_reg rbp + push_reg rbx + push_reg rdi + push_reg rsi + + sub rsp, 20h ;allocate shadow stack space for the arguments (must be multiple of 16) + .allocstack 20h +.endprolog + + ;save argments in nonvolatile registers + mov r12, rcx ;save_state + mov r13, rdx + mov r14, r8 + + ; load stack base that we are saving minus the callee argument + ; shadow stack. We don't want that clobbered + lea rcx, [rsp+20h] + mov rdx, r14 + call r12 ;pass stackpointer, return new stack pointer in eax + + ; an odd value means that we don't restore, could be + ; an error (e.g. -1) or indication that we only want + ; to save state (1). return that value to the caller. + test rax, 1 + jnz exit + + ;actual stack switch (and re-allocating the shadow stack): + lea rsp, [rax-20h] + + mov rcx, rax ;pass new stack pointer + mov rdx, r14 + call r13 + ;return the rax +EXIT: + + add rsp, 20h + pop_reg rsi + pop_reg rdi + pop_reg rbx + pop_reg rbp + + pop_reg r12 + pop_reg r13 + pop_reg r14 + pop_reg r15 + + load_xmm128 xmm15, 144 + load_xmm128 xmm14, 128 + load_xmm128 xmm13, 112 + load_xmm128 xmm12, 96 + load_xmm128 xmm11, 80 + load_xmm128 xmm10, 64 + load_xmm128 xmm9, 48 + load_xmm128 xmm8, 32 + load_xmm128 xmm7, 16 + load_xmm128 xmm6, 0 + add rsp, 168 + ret + +NESTED_END slp_switch, _TEXT$00 +;slp_switch ENDP + +END \ No newline at end of file diff --git a/Stackless/tealet/switch_x64_msvc.h b/Stackless/tealet/switch_x64_msvc.h new file mode 100644 index 00000000000000..1c0617121956b8 --- /dev/null +++ b/Stackless/tealet/switch_x64_msvc.h @@ -0,0 +1,7 @@ +/* The actual stack saving function, which just stores the stack, + * this declared in an .asm file + */ +extern void *slp_switch(void *(*save_state)(void*, void*), + void *(*restore_state)(void*, void*), + void *extra); + diff --git a/Stackless/tealet/switch_x86_64_gcc.h b/Stackless/tealet/switch_x86_64_gcc.h new file mode 100644 index 00000000000000..c240c1c261ee9b --- /dev/null +++ b/Stackless/tealet/switch_x86_64_gcc.h @@ -0,0 +1,66 @@ + +static void *slp_switch(void *(*save_state)(void*, void*), + void *(*restore_state)(void*, void*), + void *extra) +{ + void *result; + __asm__ volatile ( + "pushq %%rbp\n" + "pushq %%rbx\n" /* push the registers that may contain */ + "pushq %%rsi\n" /* some value that is meant to be saved */ + "pushq %%rdi\n" + "pushq %%rcx\n" + "pushq %%rdx\n" + "pushq %%r8\n" + "pushq %%r9\n" + "pushq %%r10\n" + "pushq %%r11\n" + "pushq %%r12\n" + "pushq %%r13\n" + "pushq %%r14\n" + "pushq %%r15\n" + + "movq %%rax, %%r12\n" /* save 'restore_state' for later */ + "movq %%rsi, %%r13\n" /* save 'extra' for later */ + + /* arg 2: extra */ + "movq %%rsp, %%rdi\n" /* arg 1: current (old) stack pointer */ + "call *%%rcx\n" /* call save_state() */ + + "testb $1, %%al\n" /* skip the rest if the return value is odd */ + "jnz 0f\n" + + "movq %%rax, %%rsp\n" /* change the stack pointer */ + + /* From now on, the stack pointer is modified, but the content of the + stack is not restored yet. It contains only garbage here. */ + + "movq %%r13, %%rsi\n" /* arg 2: extra */ + "movq %%rax, %%rdi\n" /* arg 1: current (new) stack pointer */ + "call *%%r12\n" /* call restore_state() */ + + /* The stack's content is now restored. */ + + "0:\n" + "popq %%r15\n" + "popq %%r14\n" + "popq %%r13\n" + "popq %%r12\n" + "popq %%r11\n" + "popq %%r10\n" + "popq %%r9\n" + "popq %%r8\n" + "popq %%rdx\n" + "popq %%rcx\n" + "popq %%rdi\n" + "popq %%rsi\n" + "popq %%rbx\n" + "popq %%rbp\n" + + : "=a"(result) /* output variables */ + : "a"(restore_state), /* input variables */ + "c"(save_state), + "S"(extra) + ); + return result; +} diff --git a/Stackless/tealet/switch_x86_gcc.h b/Stackless/tealet/switch_x86_gcc.h new file mode 100644 index 00000000000000..8621dcc584922f --- /dev/null +++ b/Stackless/tealet/switch_x86_gcc.h @@ -0,0 +1,53 @@ + +static void *slp_switch(void *(*save_state)(void*, void*), + void *(*restore_state)(void*, void*), + void *extra) +{ + void *result; + __asm__ volatile ( + "pushl %%ebp\n" + "pushl %%ebx\n" /* push the registers that may contain */ + "pushl %%esi\n" /* some value that is meant to be saved */ + "pushl %%edi\n" + "pushl %%ecx\n" + "pushl %%edx\n" + + "movl %%eax, %%esi\n" /* save 'restore_state' for later */ + "movl %%edx, %%edi\n" /* save 'extra' for later */ + + "movl %%esp, %%eax\n" + + "pushl %%edx\n" /* arg 2: extra */ + "pushl %%eax\n" /* arg 1: current (old) stack pointer */ + "call *%%ecx\n" /* call save_state() */ + + "testl $1, %%eax\n" /* skip the rest if the return value is odd */ + "jnz 0f\n" + + "movl %%eax, %%esp\n" /* change the stack pointer */ + + /* From now on, the stack pointer is modified, but the content of the + stack is not restored yet. It contains only garbage here. */ + + "pushl %%edi\n" /* arg 2: extra */ + "pushl %%eax\n" /* arg 1: current (new) stack pointer */ + "call *%%esi\n" /* call restore_state() */ + + /* The stack's content is now restored. */ + + "0:\n" + "addl $8, %%esp\n" + "popl %%edx\n" + "popl %%ecx\n" + "popl %%edi\n" + "popl %%esi\n" + "popl %%ebx\n" + "popl %%ebp\n" + + : "=a"(result) /* output variables */ + : "a"(restore_state), /* input variables */ + "c"(save_state), + "d"(extra) + ); + return result; +} diff --git a/Stackless/tealet/switch_x86_msvc.asm b/Stackless/tealet/switch_x86_msvc.asm new file mode 100644 index 00000000000000..628643e3975aec --- /dev/null +++ b/Stackless/tealet/switch_x86_msvc.asm @@ -0,0 +1,44 @@ + +.386 +.model flat, c + +.code + +slp_switch_raw PROC save_state:DWORD, restore_state:DWORD, extra:DWORD + + ;save registers. EAX ECX and EDX are available for function use and thus + ;do not have to be stored. + push ebx + push esi + push edi + push ebp + + mov esi, restore_state ; /* save 'restore_state' for later */ + mov edi, extra ; /* save 'extra' for later */ + + mov eax, esp + + push edi ; /* arg 2: extra */ + push eax ; /* arg 1: current (old) stack pointer */ + mov ecx, save_state + call ecx ; /* call save_state() */ + + test eax, 1; /* skip the restore if the return value is odd */ + jnz exit + + mov esp, eax; /* change the stack pointer */ + + push edi ; /* arg 2: extra */ + push eax ; /* arg 1: current (new) stack pointer */ + call esi ; /* call restore_state() */ + +exit: + add esp, 8 + pop ebp + pop edi + pop esi + pop ebx + ret +slp_switch_raw ENDP + +end \ No newline at end of file diff --git a/Stackless/tealet/switch_x86_msvc.h b/Stackless/tealet/switch_x86_msvc.h new file mode 100644 index 00000000000000..bd219271f8e281 --- /dev/null +++ b/Stackless/tealet/switch_x86_msvc.h @@ -0,0 +1,26 @@ +/* The actual stack saving function, which just stores the stack, + * this declared in an .asm file + */ +extern void *slp_switch_raw(void *(*save_state)(void*, void*), + void *(*restore_state)(void*, void*), + void *extra); + +#define WIN32_LEAN_AND_MEAN +#include + +/* Store any other runtime information on the local stack */ +#pragma optimize("", off) /* so that autos are stored on the stack */ +#pragma warning(disable:4733) /* disable warning about modifying FS[0] */ + +static void *slp_switch(void *(*save_state)(void*, void*), + void *(*restore_state)(void*, void*), + void *extra) +{ + /* store the structured exception state for this stack */ + DWORD seh_state = __readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList)); + void * result = slp_switch_raw(save_state, restore_state, extra); + __writefsdword(FIELD_OFFSET(NT_TIB, ExceptionList), seh_state); + return result; +} +#pragma warning(default:4733) /* disable warning about modifying FS[0] */ +#pragma optimize("", on) diff --git a/Stackless/tealet/tealet.c b/Stackless/tealet/tealet.c new file mode 100644 index 00000000000000..9744fa65639d47 --- /dev/null +++ b/Stackless/tealet/tealet.c @@ -0,0 +1,785 @@ +/********** A minimal coroutine package for C ********** + * By Armin Rigo + * Documentation: see the source code of the greenlet package from + * + * http://codespeak.net/svn/greenlet/trunk/c/_greenlet.c + */ + +#include "tealet.h" + +#include +#include +#include + +/************************************************************ + * platform specific code + */ + +/* The default stack direction is downwards, 0, but platforms + * can redefine it to upwards growing, 1. + * Since we support both architectures with descending and + * ascending stacks, we use the terms "near" and "far" + * to describe stack boundaries. In a typical architecture + * with descending stacks, "near" corresponds to a low + * address and "far" to a high address. + */ +#define STACK_DIRECTION 0 + +#include "slp_platformselect.h" + +#if STACK_DIRECTION == 0 +#define STACK_FAR_MAIN ((char*) -1) /* for stack_far */ +#define STACK_LE(a, b) ((a) <= (b)) /* to compare stack position */ +#define STACK_SUB(a, b) ((a) - (b)) /* to subtract stack pointers */ +#else +#define STACK_FAR_MAIN ((char*) 1) /* for stack_far */ +#define STACK_LE(a, b) ((b) <= (a)) /* to compare stack position */ +#define STACK_SUB(a, b) ((b) - (a)) /* to subtract stack pointers */ +#endif + +/************************************************************/ + +/* #define DEBUG_DUMP */ + +#ifdef DEBUG_DUMP +#include +#endif + +/************************************************************ + * Structures for maintaining copies of the C stack. + */ + +/* a chunk represents a single segment of saved stack */ +typedef struct tealet_chunk_t +{ + struct tealet_chunk_t *next; /* additional chunks */ + char *stack_near; /* near stack address */ + size_t size; /* amount of data saved */ + char data[1]; /* the data follows here */ +} tealet_chunk_t; + +/* The main stack structure, contains the initial chunk and a link to further + * segments. Stacks can be shared by different tealets, hence the reference + * count. They can also be linked into a list of partiallty unsaved + * stacks, that are saved only on demand. + */ +typedef struct tealet_stack_t +{ + int refcount; /* controls lifetime */ + struct tealet_stack_t **prev; /* previous 'next' pointer */ + struct tealet_stack_t *next; /* next unsaved stack */ + char *stack_far; /* the far boundary of this stack */ + size_t saved; /* total amount of memory saved in all chunks */ + struct tealet_chunk_t chunk; /* the initial chunk */ +} tealet_stack_t; + + +/* the actual tealet structure as used internally + * The main tealet will have stack_far set to the end of memory. + * "stack" is zero for a running tealet, otherwise it points + * to the saved stack, or is -1 if the sate is invalid. + * In addition, stack_far is set to NULL value to indicate + * that a tealet is exiting. + */ +typedef struct tealet_sub_t { + tealet_t base; /* the public part of the tealet */ + char *stack_far; /* the "far" end of the stack or NULL when exiting */ + tealet_stack_t *stack; /* saved stack or 0 if active or -1 if invalid*/ +#ifndef debug + int id; /* number of this tealet */ +#endif +} tealet_sub_t; + +/* a structure incorporating extra data */ +typedef struct tealet_nonmain_t { + tealet_sub_t base; + double _extra[1]; /* start of any extra data */ +} tealet_nonmain_t; + +/* The main tealet has additional fields for housekeeping */ +typedef struct tealet_main_t { + tealet_sub_t base; + void *g_user; /* user data pointer for main */ + tealet_sub_t *g_current; + tealet_sub_t *g_target; /* Temporary store when switching */ + void *g_arg; /* argument passed around when switching */ + tealet_alloc_t g_alloc; /* the allocation context used */ + tealet_stack_t *g_prev; /* previously active unsaved stacks */ +#ifndef NDEBUG + int g_tealets; /* number of active tealets excluding main */ + int g_counter; /* total number of tealets */ +#endif + double _extra[1]; /* start of any extra data */ +} tealet_main_t; + +#define TEALET_IS_MAIN_STACK(t) (((tealet_sub_t *)(t))->stack_far == STACK_FAR_MAIN) +#define TEALET_GET_MAIN(t) ((tealet_main_t *)(((tealet_t *)(t))->main)) + +/************************************************************/ + +int (*_tealet_switchstack)(tealet_main_t*); +int (*_tealet_initialstub)(tealet_main_t*, tealet_run_t run, void*); + +/************************************************************ + * helpers to call the malloc functions provided by the user + */ +static void *tealet_int_malloc(tealet_main_t *main, size_t size) +{ + return main->g_alloc.malloc_p(size, main->g_alloc.context); +} +static void tealet_int_free(tealet_main_t *main, void *ptr) +{ + main->g_alloc.free_p(ptr, main->g_alloc.context); +} + +/************************************************************* + * actual stack management routines. Copying, growing + * restoring, duplicating, deleting + */ +static tealet_stack_t *tealet_stack_new(tealet_main_t *main, + char *stack_near, char *stack_far, size_t size) +{ + size_t tsize; + tealet_stack_t *s; + + tsize = offsetof(tealet_stack_t, chunk.data[0]) + size; + s = (tealet_stack_t*)tealet_int_malloc(main, tsize); + if (!s) + return NULL; + s->refcount = 1; + s->prev = NULL; + s->stack_far = stack_far; + s->saved = size; + + s->chunk.next = NULL; + s->chunk.stack_near = stack_near; + s->chunk.size = size; +#if STACK_DIRECTION == 0 + memcpy(&s->chunk.data[0], stack_near, size); +#else + memcpy(&s->chunk.data[0], stack_near-size, size); +#endif + return s; +} + +static int tealet_stack_grow(tealet_main_t *main, + tealet_stack_t *stack, size_t size) +{ + tealet_chunk_t *chunk; + size_t tsize, diff; + assert(size > stack->saved); + + diff = size - stack->saved; + tsize = offsetof(tealet_chunk_t, data[0]) + diff; + chunk = (tealet_chunk_t*)tealet_int_malloc(main, tsize); + if (!chunk) + return -1; +#if STACK_DIRECTION == 0 + chunk->stack_near = stack->chunk.stack_near + stack->saved; + memcpy(&chunk->data[0], chunk->stack_near, diff); +#else + chunk->stack_near = stack->chunk.stack_near - stack->saved; + memcpy(&chunk->data[0], chunk->stack_near - diff, diff); +#endif + chunk->size = diff; + chunk->next = stack->chunk.next; + stack->chunk.next = chunk; + stack->saved = size; + return 0; +} + +static void tealet_stack_restore(tealet_stack_t *stack) +{ + tealet_chunk_t *chunk = &stack->chunk; + do { +#if STACK_DIRECTION == 0 + memcpy(chunk->stack_near, &chunk->data[0], chunk->size); +#else + memcpy(chunk->stack_near - chunk->size, &chunk->data[0], chunk->size); +#endif + chunk = chunk->next; + } while(chunk); +} + +static tealet_stack_t *tealet_stack_dup(tealet_stack_t *stack) +{ + stack->refcount += 1; + return stack; +} + +static void tealet_stack_link(tealet_stack_t *stack, tealet_stack_t **head) +{ + assert(stack->prev == NULL); + assert(*head != stack); + if (*head) + assert((*head)->prev == head); + stack->next = *head; + if (stack->next) + stack->next->prev = &stack->next; + stack->prev = head; + *head = stack; +} + +static void tealet_stack_unlink(tealet_stack_t *stack) +{ + tealet_stack_t *next = stack->next; + assert(stack->prev); + assert(*stack->prev == stack); + if (next) + assert(next->prev == &stack->next); + + if (next) + next->prev = stack->prev; + *stack->prev = next; + stack->prev = NULL; +} + +static void tealet_stack_decref(tealet_main_t *main, tealet_stack_t *stack) +{ + tealet_chunk_t *chunk; + if (stack == NULL || --stack->refcount > 0) + return; + if (stack->prev) + tealet_stack_unlink(stack); + + chunk = stack->chunk.next; + tealet_int_free(main, (void*)stack); + while(chunk) { + tealet_chunk_t *next = chunk->next; + tealet_int_free(main, (void*)chunk); + chunk = next; + } +} + +static void tealet_stack_defunct(tealet_main_t *main, tealet_stack_t *stack) +{ + /* stack couldn't be grown. Release any extra chunks and mark stack as defunct */ + tealet_chunk_t *chunk; + chunk = stack->chunk.next; + stack->chunk.next = NULL; + stack->saved = (size_t)-1; + while(chunk) { + tealet_chunk_t *next = chunk->next; + tealet_int_free(main, (void*)chunk); + chunk = next; + } +} + + +/*************************************************************** + * utility functions for allocating and growing stacks + */ + +/* save a new stack, at least up to "saveto" */ +static tealet_stack_t *tealet_stack_saveto(tealet_main_t *main, + char *stack_near, char *stack_far, char *saveto, int *full) +{ + ptrdiff_t size; + if (STACK_LE(stack_far, saveto)) { + saveto = stack_far; + *full = 1; + } else + *full = 0; + assert(saveto != STACK_FAR_MAIN); /* can't save all of memory */ + size = STACK_SUB(saveto, stack_near); + if (size < 0) + size = 0; + return tealet_stack_new(main, (char*) stack_near, stack_far, size); +} + +static int tealet_stack_growto(tealet_main_t *main, tealet_stack_t *stack, char* saveto, + int *full, int fail_ok) +{ + /* Save more of g's stack into the heap -- at least up to 'saveto' + + g->stack_stop |________| + | | + | __ stop ......... + | | ==> : : + |________| :_______: + | | | | + | | | | + g->stack_start | | |_______| g->stack_copy + + */ + ptrdiff_t size, saved=(ptrdiff_t)stack->saved; + int fail; + + /* We shouldn't be completely saved already */ + if (stack->stack_far != STACK_FAR_MAIN) + assert(STACK_SUB(stack->stack_far, stack->chunk.stack_near) > saved); + + /* truncate the "stop" */ + if (STACK_LE(stack->stack_far, saveto)) { + saveto = stack->stack_far; + *full = 1; + } else + *full = 0; + + /* total saved size expected after this */ + assert(saveto != STACK_FAR_MAIN); /* can't save them all */ + size = STACK_SUB(saveto, stack->chunk.stack_near); + if (size <= saved) + return 0; /* nothing to do */ + + fail = tealet_stack_grow(main, stack, size); + if (fail == 0) + return 0; + + if (fail_ok) + return fail; /* caller can deal with failures */ + /* we cannot fail. Mark this stack as defunct and continue */ + tealet_stack_defunct(main, stack); + *full = 1; + return 0; +} + +/* Grow a list og stacks to a certain limit. Unlink those that + * become fully saved. + */ +static int tealet_stack_grow_list(tealet_main_t *main, tealet_stack_t *list, + char *saveto, tealet_stack_t *target, int fail_ok) +{ + while (list) { + int fail; + int full; + if (list == target) { + /* this is the stack we are switching to. We should stop here + * since previous stacks are already fully saved wrt. this. + * also, if this stack is not shared, it need not be saved + */ + if (list->refcount > 1) { + /* saving because the target stack is shared. If failure cannot + * be handled, the target will be marked invalid on error. But + * the caller of this function will have already checked that + * and will complete the switch despite such a flag. Only + * subsequent uses of this stack will fail. + */ + fail = tealet_stack_growto(main, list, saveto, &full, fail_ok); + if (fail) + return fail; + if (fail_ok) + assert(full); /* we saved it entirely */ + } + tealet_stack_unlink(list); + return 0; + } + + fail = tealet_stack_growto(main, list, saveto, &full, fail_ok); + if (fail) + return fail; + if (full) + tealet_stack_unlink(list); + list = list->next; + } + return 0; +} + +/********************************************************************* + * the save and restore callbacks. These implement all the stack + * save and restore logic using previously defined functions + */ + +/* main->g_target contains the tealet we are switching to: + * target->stack_far is the limit to which we must save the old stack + * target->stack can be NULL, indicating that the target stack + * needs not be restored. + */ +static void *tealet_save_state(void *old_stack_pointer, void *main) +{ + /* must free all the C stack up to target->stack_stop */ + tealet_main_t *g_main = (tealet_main_t *)main; + tealet_sub_t *g_target = g_main->g_target; + tealet_sub_t *g_current = g_main->g_current; + char* target_stop = g_target->stack_far; + int exiting, fail, fail_ok; + assert(target_stop != NULL); /* target is't exiting */ + assert(g_current != g_target); + + exiting = g_current->stack_far == NULL; + fail_ok = !exiting; + + /* save and unlink older stacks on demand */ + /* when coming from main, there should be no list of unsaved stacks */ + if (TEALET_IS_MAIN_STACK(g_main->g_current)) { + assert(!exiting); + assert(g_main->g_prev == NULL); + } + fail = tealet_stack_grow_list(g_main, g_main->g_prev, target_stop, g_target->stack, fail_ok); + if (fail) + return (void*) -1; + /* when returning to main, there should now be no list of unsaved stacks */ + if (TEALET_IS_MAIN_STACK(g_main->g_target)) + assert(g_main->g_prev == NULL); + + if (exiting) { + /* tealet is exiting. We don't save its stack. */ + assert(!TEALET_IS_MAIN_STACK(g_current)); + if (g_current->stack == NULL) { + /* auto-delete the tealet */ +#ifndef NDEBUG + g_main->g_tealets--; +#endif + tealet_int_free(g_main, g_current); + } else { + /* -1 means do-not-delete */ + assert(g_current->stack == (tealet_stack_t*)-1); + g_current->stack = NULL; + } + } else { + /* save the initial stack chunk */ + int full; + tealet_stack_t *stack = tealet_stack_saveto(g_main, (char*) old_stack_pointer, + g_current->stack_far, target_stop, &full); + if (!stack) { + if (fail_ok) + return (void*) -1; + assert(!TEALET_IS_MAIN_STACK(g_current)); + g_current->stack = (tealet_stack_t *)-1; /* signal invalid stack */ + } else { + g_current->stack = stack; + /* if it is partially saved, link it in to previous stacks */ + if (TEALET_IS_MAIN_STACK(g_current)) + assert(!full); /* always link in the main tealet's stack */ + if (!full) + tealet_stack_link(stack, &g_main->g_prev); + } + } + + if (g_target->stack == NULL) + return (void *) 1; /* don't restore */ + + /* return the stack pointer, it was saved here */ + return g_target->stack->chunk.stack_near; +} + +static void *tealet_restore_state(void *new_stack_pointer, void *main) +{ + tealet_main_t *g_main = (tealet_main_t *)main; + tealet_sub_t *g = g_main->g_target; + + /* Restore the heap copy back into the C stack */ + assert(g->stack != NULL); + tealet_stack_restore(g->stack); + tealet_stack_decref(g_main, g->stack); + g->stack = NULL; + return NULL; +} + +static int tealet_switchstack(tealet_main_t *g_main) +{ + /* note: we can't pass g_target simply as an argument here, because + of the mix between different call stacks: after slp_switch() it + might end up with a different value. But g_main is safe, because + it should have always the same value before and after the switch. */ + void *res; + assert(g_main->g_target); + assert(g_main->g_target != g_main->g_current); + /* if the target saved stack is invalid (due to a failure to save it + * during the exit of another tealet), we detect this here and + * report an error + * return value is: + * 0 = successful switch + * 1 = successful save only + * -1 = error, couldn't save state + * -2 = error, target tealet corrupt + */ + if (g_main->g_target->stack == (tealet_stack_t*)-1) + return -2; + { + /* make sure that optimizers, e.g. gcc -O2, won't assume that + * g_main->g_target stays unchanged across the switch and optimize it + * into a register + */ + tealet_sub_t * volatile *ptarget = &g_main->g_target; + res = slp_switch(tealet_save_state, tealet_restore_state, g_main); + g_main->g_target = *ptarget; + } + if ((int)res >= 0) + g_main->g_current = g_main->g_target; + g_main->g_target = NULL; + return (int)res; +} + +/* We are initializing and switching to a new stub, + * in order to immediately start a new tealet's execution. + * stack_far is the far end of this stack and must be + * far enough that local variables in this function get saved. + * A stack variable in the calling function is sufficient. + */ +static int tealet_initialstub(tealet_main_t *g_main, tealet_run_t run, void *stack_far) +{ + int result; + tealet_sub_t *g = g_main->g_current; + tealet_sub_t *g_target = g_main->g_target; + assert(g_target->stack == NULL); /* it is fresh */ + + assert(run); + g_target->stack_far = (char *)stack_far; + result = _tealet_switchstack(g_main); + if (result < 0) { + /* couldn't allocate stack */ + g_main->g_current = g; + return result; + } + if (result == 1) { + /* We successfully saved the source state (our caller) and initialized + * the current target without restoring state. We are the new tealet. + */ + g = g_main->g_current; + assert(g == g_target); + assert(g->stack == NULL); /* running */ + + #ifdef DEBUG_DUMP + printf("starting %p\n", g); + #endif + g_target = (tealet_sub_t *)(run((tealet_t *)g, g_main->g_arg)); + #ifdef DEBUG_DUMP + printf("ending %p -> %p\n", g, g_target); + #endif + if (tealet_exit((tealet_t*)g_target, NULL, TEALET_EXIT_DEFAULT)) + tealet_exit((tealet_t*)g_main, NULL, TEALET_EXIT_DEFAULT); /* failsafe */ + assert(!"This point should not be reached"); + } else { + /* this is a switch back into the calling tealet */ + assert(result == 0); + } + return 0; +} + +static tealet_sub_t *tealet_alloc(tealet_main_t *g_main, tealet_alloc_t *alloc, size_t extrasize) +{ + tealet_sub_t *g; + size_t basesize = g_main == NULL ? offsetof(tealet_main_t, _extra) : offsetof(tealet_nonmain_t, _extra); + size_t size = basesize + extrasize; + if (g_main != NULL) + alloc = &g_main->g_alloc; + g = (tealet_sub_t*) alloc->malloc_p(size, alloc->context); + if (g == NULL) + return NULL; + if (g_main == NULL) + g_main = (tealet_main_t *)g; + g->base.main = (tealet_t *)g_main; + if (extrasize) + g->base.extra = (void*)((char*)g + basesize); + else + g->base.extra = NULL; + g->stack = NULL; + g->stack_far = NULL; +#ifndef NDEBUG + g->id = g_main->g_counter++; +#endif + return g; +} + + +/************************************************************/ + +tealet_t *tealet_initialize(tealet_alloc_t *alloc, size_t extrasize) +{ + tealet_sub_t *g; + tealet_main_t *g_main; + g = tealet_alloc(NULL, alloc, extrasize); + if (g == NULL) + return NULL; + g_main = (tealet_main_t *)g; + g->stack = NULL; + g->stack_far = STACK_FAR_MAIN; + g_main->g_user = NULL; + g_main->g_current = g; + g_main->g_target = NULL; + g_main->g_arg = NULL; + g_main->g_alloc = *alloc; + g_main->g_prev = NULL; +#ifndef NDEBUG + g_main->g_tealets = 0; + g_main->g_counter = 0; +#endif + assert(TEALET_IS_MAIN_STACK(g_main)); + /* set up the following field with an indirection, which is needed + to prevent any inlining */ + _tealet_initialstub = tealet_initialstub; + _tealet_switchstack = tealet_switchstack; + return (tealet_t *)g_main; +} + +void tealet_finalize(tealet_t *tealet) +{ + tealet_main_t *g_main = TEALET_GET_MAIN(tealet); + assert(TEALET_IS_MAIN_STACK(g_main)); + assert(g_main->g_current == (tealet_sub_t *)g_main); + tealet_int_free(g_main, g_main); +} + +void *tealet_malloc(tealet_t *tealet, size_t s) +{ + tealet_main_t *g_main = TEALET_GET_MAIN(tealet); + return tealet_int_malloc(g_main, s); +} + +void tealet_free(tealet_t *tealet, void *p) +{ + tealet_main_t *g_main = TEALET_GET_MAIN(tealet); + tealet_int_free(g_main, p); +} + +tealet_t *tealet_new(tealet_t *tealet, tealet_run_t run, void **parg, size_t extrasize) +{ + tealet_sub_t *result; /* store this until we return */ + int fail; + tealet_main_t *g_main = TEALET_GET_MAIN(tealet); + assert(TEALET_IS_MAIN_STACK(g_main)); + assert(!g_main->g_target); + result = tealet_alloc(g_main, NULL, extrasize); + if (result == NULL) + return NULL; /* Could not allocate */ + g_main->g_target = result; + g_main->g_arg = parg ? *parg : NULL; +#ifndef NDEBUG + g_main->g_tealets ++; +#endif + fail = _tealet_initialstub(g_main, run, (void*)&result); + if (fail) { + /* could not save stack */ + tealet_int_free(g_main, result); + g_main->g_target = NULL; +#ifndef NDEBUG + g_main->g_tealets --; +#endif + return NULL; + } + if (parg) + *parg = g_main->g_arg; + return (tealet_t*)result; +} + +int tealet_switch(tealet_t *stub, void **parg) +{ + tealet_sub_t *g_target = (tealet_sub_t *)stub; + tealet_main_t *g_main = TEALET_GET_MAIN(g_target); + int result; + if (g_target == g_main->g_current) + return 0; /* switch to self */ +#ifdef DEBUG_DUMP + printf("switch %p -> %p\n", g_main->g_current, g_target); +#endif + g_main->g_target = g_target; + g_main->g_arg = parg ? *parg : NULL; + result = _tealet_switchstack(g_main); + if (parg) + *parg = g_main->g_arg; +#ifdef DEBUG_DUMP + printf("done switch, res=%d, now in %p\n", result, g_main->g_current); +#endif + return result; +} + +int tealet_exit(tealet_t *target, void *arg, int flags) +{ + tealet_sub_t *g_target = (tealet_sub_t *)target; + tealet_main_t *g_main = TEALET_GET_MAIN(g_target); + tealet_sub_t *g_current = g_main->g_current; + char *stack_far = g_target->stack_far; + int result; + assert(g_current != (tealet_sub_t*)g_main); /* mustn't exit main */ + if (g_target == g_current) + return -2; /* invalid tealet */ + + g_current->stack_far = NULL; /* signal exit */ + assert (g_current->stack == NULL); + if (flags & TEALET_EXIT_NODELETE) + g_current->stack = (tealet_stack_t*) -1; /* signal do-not-delete */ + g_main->g_target = g_target; + g_main->g_arg = arg; + result = _tealet_switchstack(g_main); + assert(result < 0); /* only return here if there was failure */ + g_target->stack_far = stack_far; + g_current->stack = NULL; + return result; +} + +tealet_t *tealet_duplicate(tealet_t *tealet, size_t extrasize) +{ + tealet_sub_t *g_tealet = (tealet_sub_t *)tealet; + tealet_main_t *g_main = TEALET_GET_MAIN(g_tealet); + tealet_sub_t *g_copy; + /* can't dup the current or the main tealet */ + assert(g_tealet != g_main->g_current && g_tealet != (tealet_sub_t*)g_main); + g_copy = tealet_alloc(g_main, NULL, extrasize); + if (g_copy == NULL) + return NULL; +#ifndef NDEBUG + g_main->g_tealets++; +#endif + /* copy the relevant bits. extra data is not copied since we don't + * know how large it was in the source + */ + g_copy->stack_far = g_tealet->stack_far; + g_copy->stack = tealet_stack_dup(g_tealet->stack); + return (tealet_t*)g_copy; +} + +void tealet_delete(tealet_t *target) +{ + tealet_sub_t *g_target = (tealet_sub_t *)target; + tealet_main_t *g_main = TEALET_GET_MAIN(g_target); + assert(!TEALET_IS_MAIN(target)); + tealet_stack_decref(g_main, g_target->stack); + tealet_int_free(g_main, g_target); +#ifndef NDEBUG + g_main->g_tealets--; +#endif +} + +tealet_t *tealet_current(tealet_t *tealet) +{ + tealet_main_t *g_main = TEALET_GET_MAIN(tealet); + return (tealet_t *)g_main->g_current; +} + +void **tealet_main_userpointer(tealet_t *tealet) +{ + tealet_main_t *g_main = TEALET_GET_MAIN(tealet); + return &g_main->g_user; +} + +int tealet_status(tealet_t *_tealet) +{ + tealet_sub_t *tealet = (tealet_sub_t *)_tealet; + if (tealet->stack_far == NULL) + return TEALET_STATUS_EXITED; + if (tealet->stack == (tealet_stack_t*)-1) + return TEALET_STATUS_DEFUNCT; + return TEALET_STATUS_ACTIVE; +} + +#ifndef NDEBUG +int tealet_get_count(tealet_t *tealet) +{ + return TEALET_GET_MAIN(tealet)->g_tealets; +} +#endif + +ptrdiff_t tealet_stack_diff(void *a, void *b) +{ + return STACK_SUB((ptrdiff_t)a, (ptrdiff_t)(b)); +} + +void *tealet_get_far(tealet_t *_tealet) +{ + tealet_sub_t *tealet = (tealet_sub_t *)_tealet; + return tealet->stack_far; +} + +void *tealet_new_far(tealet_t *d1, tealet_run_t d2, void **d3, size_t d4) +{ + tealet_sub_t *result; + void *r; + (void)d1; + (void)d2; + (void)d3; + (void)d4; + /* avoid compiler warnings about returning tmp addr */ + r = (void*)&result; + return r; +} diff --git a/Stackless/tealet/tealet.h b/Stackless/tealet/tealet.h new file mode 100644 index 00000000000000..beeebcfec3d411 --- /dev/null +++ b/Stackless/tealet/tealet.h @@ -0,0 +1,227 @@ +/********** A minimal coroutine package for C **********/ +#ifndef _TEALET_H_ +#define _TEALET_H_ + +#include + +#ifdef WIN32 +#if defined TEALET_EXPORTS +#define TEALET_API __declspec(dllexport) +#elif defined TEALET_IMPORTS +#define TEALET_API __declspec(dllimport) +#else +#define TEALET_API +#endif +#else /* win32 */ +#define TEALET_API +#endif + + +/* A structure to define the memory allocation api used. + * the functions have C89 semantics and take an additional "context" + * pointer that they can use as they please + */ +typedef void*(*tealet_malloc_t)(size_t size, void *context); +typedef void*(*tealet_free_t)(void *ptr, void *context); +typedef struct tealet_alloc_t { + tealet_malloc_t malloc_p; + tealet_free_t free_p; + void *context; +} tealet_alloc_t; + +/* use the following macro to initialize a tealet_alloc_t + * structure with stdlib malloc functions, for convenience, e.g.: + * tealet_alloc_t stdalloc = TEALET_MALLOC; + */ +#define TEALET_MALLOC {\ + (tealet_malloc_t)&malloc, \ + (tealet_free_t)&free, \ + 0 \ +} + + +/* The user-visible tealet structure. If an "extrasize" is provided when + * the tealet is created, "extra" points to a block of that size, otherwise + * it is initialized to NULL + */ +typedef struct tealet_t { + struct tealet_t *main; /* pointer to the main tealet */ + void *extra; + /* private fields follow */ +} tealet_t; + +/* The "run" function of a tealet. It is called with the + * current tealet and the argument provided to its start function + */ +typedef tealet_t *(*tealet_run_t)(tealet_t *current, void *arg); + + +/* error codes. API functions that return int return a negative value + * to signal an error. + * Those that return tealet_t pointers return NULL to signal a memory + * error. + */ +#define TEALET_ERR_MEM -1 /* memory allocation failed */ +#define TEALET_ERR_DEFUNCT -2 /* the target tealet is corrupt */ + + +/* Initialize and return the main tealet. The main tealet contains the whole + * "normal" execution of the program; it starts when the program starts and + * ends when the program ends. This function and tealet_finalize() should + * be called together from the same (main) function which calls the rest of + * the program. It is fine to nest several uses of initialize/finalize, + * or to call them in multiple threads in case of multithreaded programs, + * as long as you don't try to switch between tealets created with a + * different main tealet. + */ +TEALET_API +tealet_t *tealet_initialize(tealet_alloc_t *alloc, size_t extrasize); + +/* Tear down the main tealet. Call e.g. after a thread finishes (including + * all its tealets). + */ +TEALET_API +void tealet_finalize(tealet_t *tealet); + +/* access to the tealet's allocator. This can be useful to use + * e.g. when passing data between tealets but such data cannot + * reside on the stack (except during the initial call to tealet_new) + */ +TEALET_API +void *tealet_malloc(tealet_t *tealet, size_t s); +TEALET_API +void tealet_free(tealet_t *tealet, void *p); + +/* Allocate a new tealet 'g', and call 'run(g, *arg)' in it. + * The return value of run() must be the next tealet in which to continue + * execution, which must be a different one, like for example the main tealet. + * When 'run(g)' returns, the tealet 'g' is freed. + * The return value is the new tealet, or NULL if memory allocation failed. + * Note that this tealet may have been alread freed should run(g) have + * returned by the time this function returns. + * On return, *arg contains the arg value passed in to the switch + * causing this return. + * 'arg' can be NULL, in which case NULL is passed to run and no result + * argument is passed. + * If 'extrasize' is non-zero, extra data will be allocated and the tealet's + * 'extra' member points to it, otherwise it is set to NULL + */ +TEALET_API +tealet_t *tealet_new(tealet_t *tealet, tealet_run_t run, void **parg, size_t extrasize); + +/* Switch to another tealet. Execution continues there. The tealet + * passed in must not have been freed yet and must descend from + * the same main tealet as the current one. In multithreaded applications, + * it must also belong to the current thread (otherwise, segfaults). + * if 'arg' is non-NULL, the argument passed in *arg will be provided + * to the other tealet when it returns from its tealet_new() or + * tealet_switch(). + * On return, *arg contains whatever *arg was + * provided when switching back here. + * Take care to not have *arg point to stack allocated data because + * such data may be overwritten when the context switches. + */ +TEALET_API +int tealet_switch(tealet_t *target, void **parg); + +/* Exit the current tealet. Similar to tealet_switch except that it only + * ever returns if the target tealet is defunct. + * It also allows passing of an *arg to the target tealet, plus allows + * control over whether the tealet is automatically deleted or not. + * Returning with 'p' from the tealet's run funciton is equivalent to calling + * tealet_exit(p, NULL, 1). + * This function can be used as an emergency measure to return to the + * main tealet if tealet_switch() fails due to inability to save the stack. + * Note that exiting to the main tealet is always guaranteed to work. + */ +#define TEALET_EXIT_DEFAULT 0 +#define TEALET_EXIT_NODELETE 1 +TEALET_API +int tealet_exit(tealet_t *target, void *arg, int flags); + +/* Duplicate a tealet. The active tealet is duplicated + * along with its stack contents. + * This can be used, for example, to create "stubs" that can be duplicated + * and re-used to run with different arguments. + * Use this with care, because initially the duplicate will share the same + * stack data as the original. This includes any local variables, even the + * "current" argument passed to the original "run" function which may + * be obsolete by the time the duplicate is run. Use the argument passing + * mechanism to provide the copy with fresh data. + */ +TEALET_API +tealet_t *tealet_duplicate(tealet_t *tealet, size_t extrasize); + +/* Deallocate a tealet. Use this to delete a tealet that has exited + * with tealet_exit() with 'TEALET_EXIT_NODELETE', or defunct tealets. + * Active tealet can also be + * deleted, such as stubs that are no longer in use, but take care + * because any local resources in such tealets won't be freed. + */ +TEALET_API +void tealet_delete(tealet_t *target); + +/* Return the current tealet, i.e. the one in which the caller of this + * function currently is. "tealet" can be any tealet derived from the + * main tealet. + */ +TEALET_API +tealet_t *tealet_current(tealet_t *tealet); + +/* Get the address of a the tealet's main user pointer, a single + * void pointer associated with the main tealet. Use this to + * associate a (void*)value with the main tealet. + */ +TEALET_API +void **tealet_main_userpointer(tealet_t *tealet); + +/* functions for stack arithmetic. Useful when deciding + * if you want to start a newa tealet, or when doing stack + * spilling + */ + +/* subtract two stack positions, taking into account if the + * local stack grows up or down in memory. + * The result is positive if 'b' is 'deeper' on the stack + * than 'a' + */ +TEALET_API +ptrdiff_t tealet_stack_diff(void *a, void *b); + +/* Get a tealet's "far" position on the stack. This is an + * indicator of its creation position on the stack. The main + * tealet extends until the beginning of stack + */ +TEALET_API +void *tealet_get_far(tealet_t *_tealet); + +/* this is used to get the "far address _if_ a tealet were initialized here + * The arguments must match the real tealet_new() but are dummies. + */ +TEALET_API +void *tealet_new_far(tealet_t *dummy1, tealet_run_t dummy2, void **dummy3, size_t dummy4); + +/* get a tealet's status */ +#define TEALET_STATUS_ACTIVE 0 +#define TEALET_STATUS_EXITED 1 +#define TEALET_STATUS_DEFUNCT -2 +TEALET_API +int tealet_status(tealet_t *tealet); + +#ifndef NDEBUG +TEALET_API +int tealet_get_count(tealet_t *t); +#endif + +/* Convenience macros */ +#define TEALET_MAIN(t) ((t)->main) +#define TEALET_IS_MAIN(t) ((t) == TEALET_MAIN(t)) +#define TEALET_CURRENT_IS_MAIN(t) (tealet_current(t) == TEALET_MAIN(t)) + +/* see if two tealets share the same MAIN, and can therefore be switched between */ +#define TEALET_RELATED(t1, t2) (TEALET_MAIN(t1) == TEALET_MAIN(t2)) + +/* convenience access to a typecast extra pointer */ +#define TEALET_EXTRA(t, tp) ((tp*)((t)->extra)) + +#endif /* _TEALET_H_ */ diff --git a/Stackless/tealet/tealet.sln b/Stackless/tealet/tealet.sln new file mode 100644 index 00000000000000..7958176dfa8edd --- /dev/null +++ b/Stackless/tealet/tealet.sln @@ -0,0 +1,36 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tealet", "tealet.vcxproj", "{4D8AC602-C828-4EDC-B65A-9A439810F157}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test.vcxproj", "{E03E9A66-978B-43DE-82D6-FE0F6367368F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4D8AC602-C828-4EDC-B65A-9A439810F157}.Debug|Win32.ActiveCfg = Debug|Win32 + {4D8AC602-C828-4EDC-B65A-9A439810F157}.Debug|Win32.Build.0 = Debug|Win32 + {4D8AC602-C828-4EDC-B65A-9A439810F157}.Debug|x64.ActiveCfg = Debug|x64 + {4D8AC602-C828-4EDC-B65A-9A439810F157}.Debug|x64.Build.0 = Debug|x64 + {4D8AC602-C828-4EDC-B65A-9A439810F157}.Release|Win32.ActiveCfg = Release|Win32 + {4D8AC602-C828-4EDC-B65A-9A439810F157}.Release|Win32.Build.0 = Release|Win32 + {4D8AC602-C828-4EDC-B65A-9A439810F157}.Release|x64.ActiveCfg = Release|x64 + {4D8AC602-C828-4EDC-B65A-9A439810F157}.Release|x64.Build.0 = Release|x64 + {E03E9A66-978B-43DE-82D6-FE0F6367368F}.Debug|Win32.ActiveCfg = Debug|Win32 + {E03E9A66-978B-43DE-82D6-FE0F6367368F}.Debug|Win32.Build.0 = Debug|Win32 + {E03E9A66-978B-43DE-82D6-FE0F6367368F}.Debug|x64.ActiveCfg = Debug|x64 + {E03E9A66-978B-43DE-82D6-FE0F6367368F}.Debug|x64.Build.0 = Debug|x64 + {E03E9A66-978B-43DE-82D6-FE0F6367368F}.Release|Win32.ActiveCfg = Release|Win32 + {E03E9A66-978B-43DE-82D6-FE0F6367368F}.Release|Win32.Build.0 = Release|Win32 + {E03E9A66-978B-43DE-82D6-FE0F6367368F}.Release|x64.ActiveCfg = Release|x64 + {E03E9A66-978B-43DE-82D6-FE0F6367368F}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Stackless/tealet/tealet.vcproj b/Stackless/tealet/tealet.vcproj new file mode 100644 index 00000000000000..c2dca5bb195ba2 --- /dev/null +++ b/Stackless/tealet/tealet.vcproj @@ -0,0 +1,454 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stackless/tealet/tealet.vcxproj b/Stackless/tealet/tealet.vcxproj new file mode 100644 index 00000000000000..9be8abf16525d3 --- /dev/null +++ b/Stackless/tealet/tealet.vcxproj @@ -0,0 +1,215 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {4D8AC602-C828-4EDC-B65A-9A439810F157} + tealet + Win32Proj + + + + DynamicLibrary + Unicode + true + + + DynamicLibrary + Unicode + + + DynamicLibrary + Unicode + true + + + DynamicLibrary + Unicode + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + true + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;TEALET_EXPORTS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + EditAndContinue + + + true + Windows + MachineX86 + + + + + X64 + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;TEALET_EXPORTS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + ProgramDatabase + + + true + Windows + MachineX64 + + + + + MaxSpeed + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;TEALET_EXPORTS;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + + + false + true + Windows + true + true + MachineX86 + + + + + X64 + + + MaxSpeed + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;TEALET_EXPORTS;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + + + true + Windows + true + true + MachineX64 + + + + + + + + + + + + + + + + + true + Assembling + ml64 /nologo /c /Zi /Fo "$(IntDir)%(Filename) .obj" "%(FullPath)" + + $(IntDir)%(Filename) .obj;%(Outputs) + true + Assembling + ml64 /nologo /c /Zi /Fo "$(IntDir)%(Filename) .obj" "%(FullPath)" + + $(IntDir)%(Filename) .obj;%(Outputs) + + + Assembling + ml -c -Zi "-Fl$(IntDir)%(Filename).lst" "-Fo$(IntDir)%(Filename).obj" "%(FullPath)" + + $(IntDir)%(Filename).obj;%(Outputs) + true + ml -c -Zi "-Fl$(IntDir)%(Filename).lst" "-Fo$(IntDir)%(Filename).obj" "%(FullPath)" + + $(IntDir)%(Filename).obj;%(Outputs) + Assembling + ml -c -Zi "-Fl$(IntDir)%(Filename).lst" "-Fo$(IntDir)%(Filename).obj" "%(FullPath)" + + $(IntDir)%(Filename).obj;%(Outputs) + true + + + + + + \ No newline at end of file diff --git a/Stackless/tealet/tealetvs2008.sln b/Stackless/tealet/tealetvs2008.sln new file mode 100644 index 00000000000000..eb422edf285782 --- /dev/null +++ b/Stackless/tealet/tealetvs2008.sln @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tealet", "tealet.vcproj", "{4D8AC602-C828-4EDC-B65A-9A439810F157}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test.vcproj", "{E03E9A66-978B-43DE-82D6-FE0F6367368F}" + ProjectSection(ProjectDependencies) = postProject + {4D8AC602-C828-4EDC-B65A-9A439810F157} = {4D8AC602-C828-4EDC-B65A-9A439810F157} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4D8AC602-C828-4EDC-B65A-9A439810F157}.Debug|Win32.ActiveCfg = Debug|Win32 + {4D8AC602-C828-4EDC-B65A-9A439810F157}.Debug|Win32.Build.0 = Debug|Win32 + {4D8AC602-C828-4EDC-B65A-9A439810F157}.Debug|x64.ActiveCfg = Debug|x64 + {4D8AC602-C828-4EDC-B65A-9A439810F157}.Debug|x64.Build.0 = Debug|x64 + {4D8AC602-C828-4EDC-B65A-9A439810F157}.Release|Win32.ActiveCfg = Release|Win32 + {4D8AC602-C828-4EDC-B65A-9A439810F157}.Release|Win32.Build.0 = Release|Win32 + {4D8AC602-C828-4EDC-B65A-9A439810F157}.Release|x64.ActiveCfg = Release|x64 + {4D8AC602-C828-4EDC-B65A-9A439810F157}.Release|x64.Build.0 = Release|x64 + {E03E9A66-978B-43DE-82D6-FE0F6367368F}.Debug|Win32.ActiveCfg = Debug|Win32 + {E03E9A66-978B-43DE-82D6-FE0F6367368F}.Debug|Win32.Build.0 = Debug|Win32 + {E03E9A66-978B-43DE-82D6-FE0F6367368F}.Debug|x64.ActiveCfg = Debug|x64 + {E03E9A66-978B-43DE-82D6-FE0F6367368F}.Debug|x64.Build.0 = Debug|x64 + {E03E9A66-978B-43DE-82D6-FE0F6367368F}.Release|Win32.ActiveCfg = Release|Win32 + {E03E9A66-978B-43DE-82D6-FE0F6367368F}.Release|Win32.Build.0 = Release|Win32 + {E03E9A66-978B-43DE-82D6-FE0F6367368F}.Release|x64.ActiveCfg = Release|x64 + {E03E9A66-978B-43DE-82D6-FE0F6367368F}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Stackless/tealet/test.vcproj b/Stackless/tealet/test.vcproj new file mode 100644 index 00000000000000..3cf8e75d77f51c --- /dev/null +++ b/Stackless/tealet/test.vcproj @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stackless/tealet/test.vcxproj b/Stackless/tealet/test.vcxproj new file mode 100644 index 00000000000000..95aedadedaa844 --- /dev/null +++ b/Stackless/tealet/test.vcxproj @@ -0,0 +1,184 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {E03E9A66-978B-43DE-82D6-FE0F6367368F} + test + Win32Proj + + + + Application + Unicode + true + + + Application + Unicode + + + Application + Unicode + true + + + Application + Unicode + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + true + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + WIN32;_DEBUG;_CONSOLE;TEALET_IMPORTS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + EditAndContinue + + + true + Console + MachineX86 + + + + + X64 + + + Disabled + WIN32;_DEBUG;_CONSOLE;TEALET_IMPORTS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + ProgramDatabase + + + true + Console + MachineX64 + + + + + MaxSpeed + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + + + true + Console + true + true + MachineX86 + + + + + X64 + + + MaxSpeed + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + + + true + Console + true + true + MachineX64 + + + + + + + + {4d8ac602-c828-4edc-b65a-9a439810f157} + false + + + + + + + \ No newline at end of file diff --git a/Stackless/tealet/tests.c b/Stackless/tealet/tests.c new file mode 100644 index 00000000000000..ff9c51774e8160 --- /dev/null +++ b/Stackless/tealet/tests.c @@ -0,0 +1,621 @@ +#include +#include +#include +#include "tealet.h" + +static int status = 0; +static tealet_t *g_main = NULL; +static tealet_t *the_stub = NULL; +static int newmode = 0; + + +static tealet_alloc_t talloc = TEALET_MALLOC; +void init_test() { + assert(g_main == NULL); + g_main = tealet_initialize(&talloc); + assert(tealet_current(g_main) == g_main); + status = 0; +} +void fini_test() { + assert(g_main != NULL); + assert(tealet_current(g_main) == g_main); + if (the_stub) + tealet_delete(the_stub); + the_stub = NULL; +#ifndef NDEBUG + assert(tealet_get_count(g_main) == 0); +#endif + tealet_finalize(g_main); + g_main = NULL; +} + +/**************************************************************** + *Implement copyable stubs by using a trampoline + */ +struct mystub_arg +{ + tealet_t *current; + tealet_run_t run; + void *runarg; +}; +static tealet_t *mystub_main(tealet_t *current, void *arg) +{ + void *myarg = 0; + /* the caller is in arg, return right back to him */ + tealet_switch((tealet_t*)arg, &myarg); + /* now we are back, myarg should contain the arg to the run function. + * We were possibly duplicated, so can't trust the original function args. + */ + { + struct mystub_arg sarg = *(struct mystub_arg*)myarg; + tealet_free(sarg.current, myarg); + return (sarg.run)(sarg.current, sarg.runarg); + } +} + +/* create a stub and return it */ +tealet_t *mystub_new(tealet_t *t) { + void *arg = (void*)tealet_current(t); + return tealet_new(t, mystub_main, &arg); +} + +/* run a stub */ +int mystub_run(tealet_t *stub, tealet_run_t run, void **parg) +{ + int result; + void *myarg; + /* we cannot pass arguments to a different tealet on the stack */ + struct mystub_arg *psarg = (struct mystub_arg*)tealet_malloc(stub, sizeof(struct mystub_arg)); + if (!psarg) + return TEALET_ERR_MEM; + psarg->current = stub; + psarg->run = run; + psarg->runarg = parg ? *parg : NULL; + myarg = (void*)psarg; + result = tealet_switch(stub, &myarg); + if (result) { + /* failure */ + tealet_free(stub, psarg); + return result; + } + /* pass back the arg value from the switch */ + if (parg) + *parg = myarg; + return 0; +} + + + +/**************************************/ + +/* create a tealet or stub low on the stack */ +static tealet_t * tealet_new_descend(tealet_t *t, int level, tealet_run_t run, void **parg) +{ + int boo[10]; + boo[9] = 0; + if (level > 0) + return tealet_new_descend(t, level-1, run, parg); + if (run) + return tealet_new(t, run, parg); + else + return mystub_new(t); +} + +/*************************************** + * methods for creating tealets in different ways + */ + +static tealet_t * tealet_new_rnd(tealet_t* t, tealet_run_t run, void **parg) +{ + return tealet_new_descend(t, rand() % 20, run, parg); +} + +static tealet_t * stub_new(tealet_t *t, tealet_run_t run, void **parg) +{ + tealet_t *stub = tealet_new_descend(t, rand() % 20, NULL, NULL); + int res; + if (stub == NULL) + return NULL; + if (run) + res = mystub_run(stub, run, parg); + else + res = 0; + if (res) { + tealet_delete(stub); + assert(res == TEALET_ERR_MEM); + return NULL; + } + return stub; +} + +static tealet_t * stub_new2(tealet_t *t, tealet_run_t run, void **parg) +{ + tealet_t *dup, *stub; + int res; + stub = tealet_new_descend(t, rand() % 20, NULL, NULL); + if (stub == NULL) + return NULL; + dup = tealet_duplicate(stub); + if (stub == NULL) { + tealet_delete(stub); + return NULL; + } + if (run) + res = mystub_run(dup, run, parg); + else + res = 0; + tealet_delete(stub); + if (res) { + tealet_delete(dup); + assert(res == TEALET_ERR_MEM); + return NULL; + } + return dup; +} + +static tealet_t * stub_new3(tealet_t *t, tealet_run_t run, void **parg) +{ + tealet_t *dup; + int res; + if ((rand()%10) == 0) + if (the_stub != NULL) { + tealet_delete(the_stub); + the_stub = NULL; + } + if (the_stub == NULL) + the_stub = tealet_new_descend(t, rand() % 20, NULL, NULL); + if (the_stub == NULL) + return NULL; + dup = tealet_duplicate(the_stub); + if (dup == NULL) + return NULL; + if (run) { + res = mystub_run(dup, run, parg); + if (res) { + tealet_delete(dup); + assert(res == TEALET_ERR_MEM); + return NULL; + } + } + return dup; +} + + +typedef tealet_t* (*t_new)(tealet_t *, tealet_run_t, void**); +static t_new newarray[] = {tealet_new_rnd, stub_new, stub_new2, stub_new3}; + +static t_new get_new(){ + if (newmode >= 0) + return newarray[newmode]; + return newarray[rand() % (sizeof(newarray)/sizeof(*newarray))]; +} +#define tealet_new get_new() + +/************************************************************/ + +void test_main_current(void) +{ + init_test(); + fini_test(); +} + +/************************************************************/ + +tealet_t *test_simple_run(tealet_t *t1, void *arg) +{ + assert(t1 != g_main); + status = 1; + return g_main; +} + +void test_simple(void) +{ + init_test(); + tealet_new(g_main, test_simple_run, NULL); + assert(status == 1); + fini_test(); +} + +/*************************************************************/ + +tealet_t *test_status_run(tealet_t *t1, void *arg) +{ + assert(t1 == tealet_current(t1)); + assert(!TEALET_IS_MAIN(t1)); + assert(tealet_status(t1) == TEALET_STATUS_ACTIVE); + return g_main; +} + +void test_status(void) +{ + tealet_t *stub1; + init_test(); + + assert(tealet_status(g_main) == TEALET_STATUS_ACTIVE); + assert(TEALET_IS_MAIN(g_main)); + + stub1 = tealet_new(g_main, NULL, NULL); + assert(tealet_status(stub1) == TEALET_STATUS_ACTIVE); + assert(!TEALET_IS_MAIN(stub1)); + mystub_run(stub1, test_status_run, NULL); + + fini_test(); +} + + +/*************************************************************/ +tealet_t *test_exit_run(tealet_t *t1, void *arg) +{ + int result; + assert(t1 != g_main); + status += 1; + result = tealet_exit(g_main, NULL, (int)arg); + assert(0); + return (tealet_t*)-1; +} + +void test_exit(void) +{ + tealet_t *stub1, *stub2; + int result; + void *arg; + init_test(); + stub1 = tealet_new(g_main, NULL, NULL); + stub2 = tealet_duplicate(stub1); + arg = (void*)TEALET_EXIT_NODELETE; + result = mystub_run(stub1, test_exit_run, &arg); + assert(result == 0); + assert(status == 1); + assert(tealet_status(stub1) == TEALET_STATUS_EXITED); + tealet_delete(stub1); + arg = (void*)TEALET_EXIT_DEFAULT; + result = mystub_run(stub2, test_exit_run, &arg); + assert(status == 2); + fini_test(); +} + + +/************************************************************/ + +static tealet_t *glob_t1; +static tealet_t *glob_t2; + +tealet_t *test_switch_2(tealet_t *t2, void *arg) +{ + assert(t2 != g_main); + assert(t2 != glob_t1); + glob_t2 = t2; + assert(status == 1); + status = 2; + assert(tealet_current(g_main) == t2); + tealet_switch(glob_t1, NULL); + assert(status == 3); + status = 4; + assert(tealet_current(g_main) == t2); + tealet_switch(glob_t1, NULL); + assert(status == 5); + status = 6; + assert(t2 == glob_t2); + assert(tealet_current(g_main) == t2); + tealet_switch(t2, NULL); + assert(status == 6); + status = 7; + assert(tealet_current(g_main) == t2); + return g_main; +} + +tealet_t *test_switch_1(tealet_t *t1, void *arg) +{ + assert(t1 != g_main); + glob_t1 = t1; + assert(status == 0); + status = 1; + assert(tealet_current(g_main) == t1); + tealet_new(g_main, test_switch_2, NULL); + assert(status == 2); + status = 3; + assert(tealet_current(g_main) == t1); + tealet_switch(glob_t2, NULL); + assert(status == 4); + status = 5; + assert(tealet_current(g_main) == t1); + return glob_t2; +} + +void test_switch(void) +{ + init_test(); + tealet_new(g_main, test_switch_1, NULL); + assert(status == 7); + fini_test(); +} + +/************************************************************/ + +/* 1 is high on the stack. We then create 2 lower on the stack */ +/* the execution is : m 1 m 2 1 m 2 m */ +tealet_t *test_switch_new_1(tealet_t *t1, void *arg) +{ + tealet_t *caller = (tealet_t*)arg; + tealet_t *stub; + /* switch back to the creator */ + tealet_switch(caller, NULL); + /* now we want to trample the stack */ + stub = tealet_new_descend(t1, 50, NULL, NULL); + tealet_delete(stub); + /* and back to main */ + return g_main; +} + +tealet_t *test_switch_new_2(tealet_t *t2, void *arg) { + tealet_t *target = (tealet_t*)arg; + /* switch to tealet 1 to trample the stack*/ + target->data = (void*)t2; + tealet_switch(target, NULL); + + /* and then return to main */ + return g_main; +} + +void test_switch_new(void) +{ + tealet_t *tealet1, *tealet2; + void *arg; + init_test(); + arg = (void *)tealet_current(g_main); + tealet1 = tealet_new(g_main, test_switch_new_1, &arg); + /* the tealet is now running */ + arg = (void*)tealet1; + tealet2 = tealet_new_descend(g_main, 4, test_switch_new_2, &arg); + assert(tealet_status(tealet2) == TEALET_STATUS_ACTIVE); + tealet_switch(tealet2, NULL); + /* tealet should be dead now */ + fini_test(); +} + +/************************************************************/ + +/* test argument passing with switch and exit */ +tealet_t *test_arg_1(tealet_t *t1, void *arg) +{ + void *myarg; + tealet_t *peer = (tealet_t*)arg; + myarg = (void*)1; + tealet_switch(peer, &myarg); + assert(myarg == (void*)2); + myarg = (void*)3; + tealet_exit(peer, myarg, TEALET_EXIT_DEFAULT); + return NULL; +} + +void test_arg(void) +{ + void *myarg; + tealet_t *t1; + init_test(); + myarg = (void*)g_main; + t1 = tealet_new(g_main, test_arg_1, &myarg); + assert(myarg == (void*)1); + myarg = (void*)2; + tealet_switch(t1, &myarg); + assert(myarg == (void*)3); + fini_test(); +} + +/************************************************************/ + +#define ARRAYSIZE 127 +#define MAX_STATUS 50000 + +static tealet_t *tealetarray[ARRAYSIZE] = {NULL}; +static int got_index; + +tealet_t *random_new_tealet(tealet_t*, void *arg); + +static void random_run(int index) +{ + int i, prevstatus; + void *arg; + tealet_t *cur = tealet_current(g_main); + assert(tealetarray[index] == cur); + do + { + i = rand() % (ARRAYSIZE + 1); + status += 1; + if (i == ARRAYSIZE) + break; + prevstatus = status; + got_index = i; + if (tealetarray[i] == NULL) + { + if (status >= MAX_STATUS) + break; + arg = (void*)i; + tealet_new(g_main, random_new_tealet, &arg); + } + else + { + tealet_switch(tealetarray[i], NULL); + } + assert(status >= prevstatus); + assert(tealet_current(g_main) == cur); + assert(tealetarray[index] == cur); + assert(got_index == index); + } + while (status < MAX_STATUS); +} + +tealet_t *random_new_tealet(tealet_t* cur, void *arg) +{ + int i = got_index; + assert(tealet_current(g_main) == cur); + assert(i == (int)(arg)); + assert(i > 0 && i < ARRAYSIZE); + assert(tealetarray[i] == NULL); + tealetarray[i] = cur; + random_run(i); + tealetarray[i] = NULL; + + i = rand() % ARRAYSIZE; + if (tealetarray[i] == NULL) + { + assert(tealetarray[0] != NULL); + i = 0; + } + got_index = i; + return tealetarray[i]; +} + +void test_random(void) +{ + int i; + init_test(); + for( i=0; i 0 && index < ARRAYSIZE); + assert(tealetarray[index] == NULL); + tealetarray[index] = cur; + random2_run(index); + tealetarray[index] = NULL; + return tealetarray[0]; /* switch to main */ +} +void random2_new(int index) { + void *arg = (void*)index; + tealet_new(g_main, random2_tealet, &arg); +} + +int random2_descend(int index, int level) { + int target; + if (level > 0) + return random2_descend(index, level-1); + + /* find target */ + target = rand() % ARRAYSIZE; + if (status