Skip to content

Commit 9c2dabb

Browse files
Anselm KruisAnselm Kruis
authored andcommitted
Stackless issue python#198: Improve the soft-switchable extension functions
Better handling of exceptions / NULL result values. Move the macros to manipulate the try-stackless floag to the API. Now it is possible to convert existing extension functions to stackless, without using internal API. Coming soon: test cases and documentation
1 parent 65a02d1 commit 9c2dabb

File tree

5 files changed

+156
-49
lines changed

5 files changed

+156
-49
lines changed

Stackless/core/cframeobject.c

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,8 @@ execute_soft_switchable_func(PyFrameObject *f, int exc, PyObject *retval)
436436
PyObject *ob1, *ob2, *ob3;
437437
PyStacklessFunctionDeclarationObject *ssfd =
438438
(PyStacklessFunctionDeclarationObject*)cf->any2;
439-
if (retval == NULL) {
439+
if (retval == NULL && !PyErr_Occurred()) {
440+
PyErr_SetString(PyExc_SystemError, "retval is NULL, but no error occurred");
440441
SLP_STORE_NEXT_FRAME(ts, cf->f_back);
441442
return NULL;
442443
}
@@ -452,7 +453,7 @@ execute_soft_switchable_func(PyFrameObject *f, int exc, PyObject *retval)
452453
Py_XINCREF(ob3);
453454
STACKLESS_PROPOSE_ALL(ts);
454455
/* use Py_SETREF, because we own a ref to retval. */
455-
Py_SETREF(retval, ssfd->sfunc(retval, &cf->i, &cf->ob1, &cf->ob2, &cf->ob3, &cf->n, &cf->any1));
456+
Py_XSETREF(retval, ssfd->sfunc(retval, &cf->i, &cf->ob1, &cf->ob2, &cf->ob3, &cf->n, &cf->any1));
456457
STACKLESS_RETRACT();
457458
STACKLESS_ASSERT();
458459
Py_XDECREF(ob1);
@@ -484,18 +485,29 @@ PyStackless_CallFunction(PyStacklessFunctionDeclarationObject *ssfd, PyObject *a
484485
{
485486
STACKLESS_GETARG();
486487
PyThreadState *ts = PyThreadState_GET();
488+
PyObject *et, *ev, *tb;
487489

488490
assert(ssfd);
489491
assert(ts->st.main != NULL);
490492

493+
if (arg == NULL) {
494+
if (!PyErr_Occurred()) {
495+
PyErr_SetString(PyExc_SystemError, "No error occurred, but arg is NULL");
496+
return NULL;
497+
}
498+
PyErr_Fetch(&et, &ev, &tb);
499+
}
500+
491501
if (stackless) {
492502
/* Only soft-switch, if the caller can handle Py_UnwindToken.
493503
* If called from Python-code, this is always the case,
494504
* but if you call this method from a C-function, you don't know. */
495505
PyCFrameObject *cf;
496506
cf = slp_cframe_new(execute_soft_switchable_func, 1);
497-
if (cf == NULL)
507+
if (cf == NULL) {
508+
_PyErr_ChainExceptions(et, ev, tb);
498509
return NULL;
510+
}
499511
cf->any2 = ssfd;
500512

501513
Py_XINCREF(ob1);
@@ -510,9 +522,9 @@ PyStackless_CallFunction(PyStacklessFunctionDeclarationObject *ssfd, PyObject *a
510522
cf->any1 = any;
511523
SLP_STORE_NEXT_FRAME(ts, (PyFrameObject *) cf);
512524
Py_DECREF(cf);
525+
Py_XINCREF(arg);
513526
if (arg == NULL)
514-
arg = Py_None;
515-
Py_INCREF(arg);
527+
PyErr_Restore(et, ev, tb);
516528
return STACKLESS_PACK(ts, arg);
517529
} else {
518530
/*
@@ -521,23 +533,23 @@ PyStackless_CallFunction(PyStacklessFunctionDeclarationObject *ssfd, PyObject *a
521533
PyObject *retval, *saved_ob1, *saved_ob2, *saved_ob3;
522534
long step = 0;
523535

524-
if (arg == NULL)
525-
arg = Py_None;
526-
Py_INCREF(arg);
536+
Py_XINCREF(arg);
527537

528538
saved_ob1 = ob1;
529539
saved_ob2 = ob2;
530540
saved_ob3 = ob3;
531541
Py_XINCREF(saved_ob1);
532542
Py_XINCREF(saved_ob2);
533543
Py_XINCREF(saved_ob3);
544+
if (arg == NULL)
545+
PyErr_Restore(et, ev, tb);
534546
retval = ssfd->sfunc(arg, &step, &ob1, &ob2, &ob3, &n, &any);
535547
STACKLESS_ASSERT();
536548
assert(!STACKLESS_UNWINDING(retval));
537549
Py_XDECREF(saved_ob1);
538550
Py_XDECREF(saved_ob2);
539551
Py_XDECREF(saved_ob3);
540-
Py_DECREF(arg);
552+
Py_XDECREF(arg);
541553
return retval;
542554
}
543555
}

Stackless/core/stackless_impl.h

Lines changed: 15 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -423,32 +423,19 @@ PyTaskletObject * slp_get_watchdog(PyThreadState *ts, int interrupt);
423423

424424
/* macros for setting/resetting the stackless flag */
425425

426+
#ifdef _PyStackless_TRY_STACKLESS
427+
#undef _PyStackless_TRY_STACKLESS
428+
#endif
429+
#define _PyStackless_TRY_STACKLESS (_PyRuntime.st.try_stackless)
430+
426431
#define STACKLESS_POSSIBLE(tstate) \
427432
((tstate)->interp->st.enable_softswitch && (tstate)->st.unwinding_retval == NULL)
428433

429-
#define STACKLESS_GETARG() \
430-
int stackless = (assert(SLP_CURRENT_FRAME_IS_VALID(PyThreadState_GET())), \
431-
stackless = (_PyRuntime.st.try_stackless), \
432-
(_PyRuntime.st.try_stackless) = 0, \
433-
stackless)
434-
435-
#define STACKLESS_PROMOTE(func) \
436-
(stackless ? (_PyRuntime.st.try_stackless) = \
437-
Py_TYPE(func)->tp_flags & Py_TPFLAGS_HAVE_STACKLESS_CALL : 0)
438-
439-
#define STACKLESS_PROMOTE_FLAG(flag) \
440-
(stackless ? (_PyRuntime.st.try_stackless) = (flag) : 0)
441-
442-
#define STACKLESS_PROMOTE_METHOD(obj, meth) do { \
443-
if ((Py_TYPE(obj)->tp_flags & Py_TPFLAGS_HAVE_STACKLESS_EXTENSION) && \
444-
Py_TYPE(obj)->tp_as_mapping) \
445-
(_PyRuntime.st.try_stackless) = stackless && Py_TYPE(obj)->tp_as_mapping->slpflags.meth; \
446-
} while (0)
447-
448-
#define STACKLESS_PROMOTE_WRAPPER(descr) \
449-
((_PyRuntime.st.try_stackless) = stackless && (descr)->d_slpmask)
450-
451-
#define STACKLESS_PROMOTE_ALL() ((void)((_PyRuntime.st.try_stackless) = stackless, NULL))
434+
#ifdef STACKLESS__GETARG_ASSERT
435+
#undef STACKLESS__GETARG_ASSERT
436+
#endif
437+
#define STACKLESS__GETARG_ASSERT \
438+
assert(SLP_CURRENT_FRAME_IS_VALID(PyThreadState_GET()))
452439

453440
#define STACKLESS_PROPOSE(tstate, func) {int stackless = STACKLESS_POSSIBLE(tstate); \
454441
STACKLESS_PROMOTE(func);}
@@ -459,11 +446,7 @@ PyTaskletObject * slp_get_watchdog(PyThreadState *ts, int interrupt);
459446
#define STACKLESS_PROPOSE_METHOD(tstate, obj, meth) {int stackless = STACKLESS_POSSIBLE(tstate); \
460447
STACKLESS_PROMOTE_METHOD(obj, meth);}
461448

462-
#define STACKLESS_PROPOSE_ALL(tstate) (_PyRuntime.st.try_stackless) = STACKLESS_POSSIBLE(tstate)
463-
464-
#define STACKLESS_RETRACT() (_PyRuntime.st.try_stackless) = 0;
465-
466-
#define STACKLESS_ASSERT() assert(!(_PyRuntime.st.try_stackless))
449+
#define STACKLESS_PROPOSE_ALL(tstate) _PyStackless_TRY_STACKLESS = STACKLESS_POSSIBLE(tstate)
467450

468451
/* this is just a tag to denote which methods are stackless */
469452

@@ -477,7 +460,7 @@ PyTaskletObject * slp_get_watchdog(PyThreadState *ts, int interrupt);
477460
/*
478461
479462
How this works:
480-
There is one global variable (_PyRuntime.st.try_stackless) which is used
463+
There is one global variable _PyStackless_TRY_STACKLESS which is used
481464
like an implicit parameter. Since we don't have a real parameter,
482465
the flag is copied into the local variable "stackless" and cleared.
483466
This is done by the STACKLESS_GETARG() macro, which should be added to
@@ -493,7 +476,7 @@ PyTaskletObject * slp_get_watchdog(PyThreadState *ts, int interrupt);
493476
494477
STACKLESS_GETARG()
495478
496-
move the (_PyRuntime.st.try_stackless) flag into the local variable "stackless".
479+
move the _PyStackless_TRY_STACKLESS flag into the local variable "stackless".
497480
498481
STACKLESS_PROMOTE_ALL()
499482
@@ -506,7 +489,7 @@ PyTaskletObject * slp_get_watchdog(PyThreadState *ts, int interrupt);
506489
507490
if stackless was set and the function's type has set
508491
Py_TPFLAGS_HAVE_STACKLESS_CALL, then this flag will be
509-
put back into (_PyRuntime.st.try_stackless), and we expect that the
492+
put back into _PyStackless_TRY_STACKLESS, and we expect that the
510493
function handles it correctly.
511494
512495
STACKLESS_PROMOTE_FLAG(flag)
@@ -526,7 +509,7 @@ PyTaskletObject * slp_get_watchdog(PyThreadState *ts, int interrupt);
526509
527510
STACKLESS_ASSERT()
528511
529-
make sure that (_PyRuntime.st.try_stackless) was cleared. This debug feature
512+
make sure that _PyStackless_TRY_STACKLESS was cleared. This debug feature
530513
tries to ensure that no unexpected nonrecursive call can happen.
531514
532515
Some functions which are known to be stackless by nature
@@ -787,18 +770,10 @@ long slp_parse_thread_id(PyObject *thread_id, unsigned long *id);
787770
#define SLP_PEEK_NEXT_FRAME(tstate) \
788771
((tstate)->frame)
789772

790-
#define STACKLESS_GETARG() int stackless = 0
791-
#define STACKLESS_PROMOTE(func) stackless = 0
792-
#define STACKLESS_PROMOTE_FLAG(flag) stackless = 0
793-
#define STACKLESS_PROMOTE_METHOD(obj, meth) stackless = 0
794-
#define STACKLESS_PROMOTE_WRAPPER(descr) stackless = 0
795-
#define STACKLESS_PROMOTE_ALL() stackless = 0
796773
#define STACKLESS_PROPOSE(tstate, func) assert(1)
797774
#define STACKLESS_PROPOSE_FLAG(tstate, flag) assert(1)
798775
#define STACKLESS_PROPOSE_ALL(tstate) assert(1)
799776
#define STACKLESS_PROPOSE_METHOD(tstate, obj, meth) assert(1)
800-
#define STACKLESS_RETRACT() assert(1)
801-
#define STACKLESS_ASSERT() assert(1)
802777

803778
#define STACKLESS_RETVAL(tstate, obj) (obj)
804779
#define STACKLESS_ASSERT_UNWINDING_VALUE_IS_NOT(tstate, obj, val) assert(1)

Stackless/core/stackless_structs.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,18 @@ PyAPI_DATA(PyTypeObject) PyTasklet_Type;
288288
PyAPI_DATA(PyTypeObject) PyChannel_Type;
289289
#define PyChannel_Check(op) PyObject_TypeCheck(op, &PyChannel_Type)
290290
#define PyChannel_CheckExact(op) (Py_TYPE(op) == &PyChannel_Type)
291+
292+
/******************************************************
293+
Macros for the stackless protocol
294+
******************************************************/
295+
296+
#ifndef _PyStackless_TRY_STACKLESS
297+
PyAPI_DATA(int * const) _PyStackless__TryStacklessPtr;
298+
#define _PyStackless_TRY_STACKLESS (*_PyStackless__TryStacklessPtr)
299+
#endif
300+
#ifndef STACKLESS__GETARG_ASSERT
301+
#define STACKLESS__GETARG_ASSERT ((void)0)
302+
#endif
291303

292304
#endif /* #ifdef STACKLESS */
293305

Stackless/core/stackless_util.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#ifdef STACKLESS
44
#include "stackless_impl.h"
55

6+
int * const _PyStackless__TryStacklessPtr = &_PyStackless_TRY_STACKLESS;
7+
68
/* Initialize the Stackless runtime state */
79
void
810
slp_initialize(struct _stackless_runtime_state * state) {

Stackless/stackless_api.h

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,111 @@ PyAPI_FUNC(PyObject *) PyStackless_CallFunction(
390390
PyAPI_FUNC(int) PyStackless_InitFunctionDeclaration(
391391
PyStacklessFunctionDeclarationObject *sfd, PyObject *module, PyModuleDef *module_def);
392392

393+
/*
394+
395+
Macros for the "stackless protocol"
396+
===================================
397+
398+
How to does Stackless Python decide, if a function may return an unwind-token.
399+
400+
There is one global variable _PyStackless_TRY_STACKLESS which is used
401+
like an implicit parameter. Since we don't have a real parameter,
402+
the flag is copied into the local variable "stackless" and cleared.
403+
This is done by the STACKLESS_GETARG() macro, which should be added to
404+
the top of the function's declarations.
405+
The idea is to keep the chances to introduce error to the minimum.
406+
A function can safely do some tests and return before calling
407+
anything, since the flag is in a local variable.
408+
Depending on context, this flag is propagated to other called
409+
functions. They *must* obey the protocol. To make this sure,
410+
the STACKLESS_ASSERT() macro has to be called after every such call.
411+
412+
Many internal functions have been patched to support this protocol.
413+
414+
STACKLESS_GETARG()
415+
416+
Move the _PyStackless_TRY_STACKLESS flag into the local variable "stackless".
417+
418+
STACKLESS_PROMOTE_ALL()
419+
420+
is used for cases where we know that the called function will take
421+
care of our object, and we need no test. For example, PyObject_Call
422+
and all other Py{Object,Function,CFunction}_*Call* functions use
423+
STACKLESS_PROMOTE_xxx, itself, so we don't need to check further.
424+
425+
STACKLESS_PROMOTE(func)
426+
427+
If stackless was set and the function's type has set
428+
Py_TPFLAGS_HAVE_STACKLESS_CALL, then this flag will be
429+
put back into _PyStackless_TRY_STACKLESS, and we expect that the
430+
function handles it correctly.
431+
432+
STACKLESS_PROMOTE_FLAG(flag)
433+
434+
is used for special cases, like PyCFunction objects. PyCFunction_Type
435+
says that it supports a stackless call, but the final action depends
436+
on the METH_STACKLESS flag in the object to be called. Therefore,
437+
PyCFunction_Call uses PROMOTE_FLAG(flags & METH_STACKLESS) to
438+
take care of PyCFunctions which don't care about it.
439+
440+
Another example is the "next" method of iterators. To support this,
441+
the wrapperobject's type has the Py_TPFLAGS_HAVE_STACKLESS_CALL
442+
flag set, but wrapper_call then examines the wrapper descriptors
443+
flags if PyWrapperFlag_STACKLESS is set. "next" has it set.
444+
It also checks whether Py_TPFLAGS_HAVE_STACKLESS_CALL is set
445+
for the iterator's type.
446+
447+
STACKLESS_ASSERT()
448+
449+
Make sure that _PyStackless_TRY_STACKLESS was cleared. This debug feature
450+
tries to ensure that no unexpected nonrecursive call can happen.
451+
452+
STACKLESS_RETRACT()
453+
454+
Reset _PyStackless_TRY_STACKLESS. Rarely needed.
455+
456+
*/
457+
458+
#define STACKLESS_GETARG() \
459+
int stackless = (STACKLESS__GETARG_ASSERT, \
460+
stackless = _PyStackless_TRY_STACKLESS, \
461+
_PyStackless_TRY_STACKLESS = 0, \
462+
stackless)
463+
464+
#define STACKLESS_PROMOTE(func) \
465+
(stackless ? _PyStackless_TRY_STACKLESS = \
466+
Py_TYPE(func)->tp_flags & Py_TPFLAGS_HAVE_STACKLESS_CALL : 0)
467+
468+
#define STACKLESS_PROMOTE_FLAG(flag) \
469+
(stackless ? _PyStackless_TRY_STACKLESS = (flag) : 0)
470+
471+
#define STACKLESS_PROMOTE_METHOD(obj, meth) do { \
472+
if ((Py_TYPE(obj)->tp_flags & Py_TPFLAGS_HAVE_STACKLESS_EXTENSION) && \
473+
Py_TYPE(obj)->tp_as_mapping) \
474+
_PyStackless_TRY_STACKLESS = stackless && Py_TYPE(obj)->tp_as_mapping->slpflags.meth; \
475+
} while (0)
476+
477+
#define STACKLESS_PROMOTE_WRAPPER(descr) \
478+
(_PyStackless_TRY_STACKLESS = stackless && (descr)->d_slpmask)
479+
480+
#define STACKLESS_PROMOTE_ALL() ((void)(_PyStackless_TRY_STACKLESS = stackless, NULL))
481+
482+
#define STACKLESS_RETRACT() (_PyStackless_TRY_STACKLESS = 0)
483+
484+
#define STACKLESS_ASSERT() assert(!_PyStackless_TRY_STACKLESS)
485+
486+
#else /* STACKLESS */
487+
/* turn the stackless flag macros into dummies */
488+
#define STACKLESS_GETARG() int stackless = 0
489+
#define STACKLESS_PROMOTE(func) stackless = 0
490+
#define STACKLESS_PROMOTE_FLAG(flag) stackless = 0
491+
#define STACKLESS_PROMOTE_METHOD(obj, meth) stackless = 0
492+
#define STACKLESS_PROMOTE_WRAPPER(descr) stackless = 0
493+
#define STACKLESS_PROMOTE_ALL() stackless = 0
494+
#define STACKLESS_RETRACT() assert(1)
495+
#define STACKLESS_ASSERT() assert(1)
496+
#endif
497+
#ifdef STACKLESS
393498

394499
/******************************************************
395500
@@ -476,6 +581,7 @@ PyAPI_FUNC(PyObject *) PyStackless_CallMethod_Main(PyObject *o, char *name,
476581
PyAPI_FUNC(PyObject *) PyStackless_CallCMethod_Main(
477582
PyMethodDef *meth, PyObject *self, char *format, ...);
478583

584+
479585
#endif /* STACKLESS */
480586

481587
#ifdef __cplusplus

0 commit comments

Comments
 (0)