Skip to content

Commit f24223f

Browse files
author
Anselm Kruis
authored
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.
1 parent 3629160 commit f24223f

File tree

10 files changed

+500
-104
lines changed

10 files changed

+500
-104
lines changed

Doc/c-api/stackless.rst

Lines changed: 164 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,37 @@
33
|SLP| C-API
44
===========
55

6+
.. note::
7+
8+
Some switching functions have a variant with the
9+
same name, but ending on "_nr". These are non-recursive
10+
versions with the same functionality, but they might
11+
avoid a hard stack switch.
12+
Their return value is ternary, and they require the
13+
caller to return to its frame, properly.
14+
All three different cases must be treated.
15+
16+
Ternary return from an integer function:
17+
18+
===== ============= ===============================
19+
value meaning action
20+
===== ============= ===============================
21+
-1 failure return NULL
22+
1 soft switched return :c:data:`Py_UnwindToken`
23+
0 hard switched return :c:data:`Py_None`
24+
===== ============= ===============================
25+
26+
Ternary return from a PyObject * function:
27+
28+
============== ============= ===============================
29+
value meaning action
30+
============== ============= ===============================
31+
NULL failure return NULL
32+
Py_UnwindToken soft switched return :c:data:`Py_UnwindToken`
33+
other hard switched return value
34+
============== ============= ===============================
35+
36+
637
|SLP| provides the following C functions.
738

839
Tasklets
@@ -262,8 +293,8 @@ Channels
262293
263294
Gets the balance for *self*. See :attr:`channel.balance`.
264295
265-
stackless module
266-
----------------
296+
Module :py:mod:`stackless`
297+
--------------------------
267298
268299
.. c:function:: PyObject *PyStackless_Schedule(PyObject *retval, int remove)
269300
@@ -354,7 +385,8 @@ Soft-switchable extension functions
354385
355386
A soft switchable extension function or method is a function or method defined
356387
by an extension module written in C. In contrast to an normal C-function you
357-
can soft-switch tasklets while this function executes. At the C-language level
388+
can soft-switch tasklets while this function executes. Soft-switchable functions
389+
obey the stackless-protocol. At the C-language level
358390
such a function or method is made from 3 C-definitions:
359391
360392
1. A declaration object of type :c:type:`PyStacklessFunctionDeclaration_Type`.
@@ -378,10 +410,10 @@ Typedef ``slp_softswitchablefunc``::
378410
379411
.. c:type:: PyStacklessFunctionDeclarationObject
380412
381-
This subtype of :c:type:`PyObject` represents a Stackless soft switchable
382-
extension function declaration object.
413+
This subtype of :c:type:`PyObject` represents a Stackless soft switchable
414+
extension function declaration object.
383415
384-
Here is the structure definition::
416+
Here is the structure definition::
385417
386418
typedef struct {
387419
PyObject_HEAD
@@ -390,42 +422,145 @@ Typedef ``slp_softswitchablefunc``::
390422
const char * module_name;
391423
} PyStacklessFunctionDeclarationObject;
392424
393-
.. c:member:: slp_softswitchablefunc PyStacklessFunctionDeclarationObject.sfunc
425+
.. c:member:: slp_softswitchablefunc PyStacklessFunctionDeclarationObject.sfunc
394426
395-
Pointer to implementation function.
427+
Pointer to implementation function.
396428
397-
.. c:member:: const char * PyStacklessFunctionDeclarationObject.name
429+
.. c:member:: const char * PyStacklessFunctionDeclarationObject.name
398430
399-
Name of the function.
431+
Name of the function.
400432
401-
.. c:member:: const char * PyStacklessFunctionDeclarationObject.module_name
433+
.. c:member:: const char * PyStacklessFunctionDeclarationObject.module_name
402434
403-
Name of the containing module.
435+
Name of the containing module.
404436
405437
.. c:var:: PyTypeObject PyStacklessFunctionDeclaration_Type
406438
407-
This instance of :c:type:`PyTypeObject` represents the Stackless
408-
soft switchable extension function declaration type.
439+
This instance of :c:type:`PyTypeObject` represents the Stackless
440+
soft switchable extension function declaration type.
409441
410442
.. c:function:: int PyStacklessFunctionDeclarationType_CheckExact(PyObject *p)
411443
412-
Return true if *p* is a PyStacklessFunctionDeclarationObject object, but
413-
not an instance of a subtype of this type.
444+
Return true if *p* is a PyStacklessFunctionDeclarationObject object, but
445+
not an instance of a subtype of this type.
414446
415447
.. c:function:: PyObject* PyStackless_CallFunction(PyStacklessFunctionDeclarationObject *sfd, PyObject *arg, PyObject *ob1, PyObject *ob2, PyObject *ob3, long n, void *any)
416448
417-
Invoke the soft switchable extension, which is represented by *sfd*.
418-
Pass *arg* as initial value for argument *retval* and *ob1*, *ob2*, *ob3*,
419-
*n* and *any* as general purpose in-out-arguments.
449+
Invoke the soft switchable extension, which is represented by *sfd*.
450+
Pass *arg* as initial value for argument *retval* and *ob1*, *ob2*, *ob3*,
451+
*n* and *any* as general purpose in-out-arguments.
420452
421-
Return the result of the function call or :c:data:`Py_UnwindToken`.
453+
Return the result of the function call or :c:data:`Py_UnwindToken`.
422454
423455
.. c:function:: int PyStackless_InitFunctionDeclaration(PyStacklessFunctionDeclarationObject *sfd, PyObject *module, PyModuleDef *module_def)
424456
425-
Initialize the fields :c:member:`PyStacklessFunctionDeclarationObject.name` and
426-
:c:member:`PyStacklessFunctionDeclarationObject.module_name` of *sfd*.
457+
Initialize the fields :c:member:`PyStacklessFunctionDeclarationObject.name` and
458+
:c:member:`PyStacklessFunctionDeclarationObject.module_name` of *sfd*.
459+
460+
Within the body of a soft switchable extension function (or any other C-function, that obyes the stackless-protocol)
461+
you need the following macros.
462+
463+
Macros for the "stackless-protocol"
464+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
465+
466+
How to does Stackless Python decide, if a function may return an unwind-token?
467+
There is one global variable "_PyStackless_TRY_STACKLESS"[#]_ which is used
468+
like an implicit parameter. Since we don't have a real parameter,
469+
the flag is copied into the local variable "stackless" and cleared.
470+
This is done by the STACKLESS_GETARG() macro, which should be added to
471+
the top of the function's declarations.
472+
473+
The idea is to keep the chances to introduce error to the minimum.
474+
A function can safely do some tests and return before calling
475+
anything, since the flag is in a local variable.
476+
Depending on context, this flag is propagated to other called
477+
functions. They *must* obey the protocol. To make this sure,
478+
the STACKLESS_ASSERT() macro has to be called after every such call.
479+
480+
Many internal functions have been patched to support this protocol.
481+
Their first action is a direct or indirect call of the macro
482+
:c:func:`STACKLESS_GETARG`.
483+
484+
.. c:function:: STACKLESS_GETARG()
485+
486+
Define the local variable ``int stackless`` and move the global
487+
"_PyStackless_TRY_STACKLESS" flag into the local variable "stackless".
488+
After a call to :c:func:`STACKLESS_GETARG` the value of
489+
"_PyStackless_TRY_STACKLESS" is 0.
490+
491+
.. c:function:: STACKLESS_PROMOTE_ALL()
492+
493+
All STACKLESS_PROMOTE_xxx macros are used to propagate the stackless-flag
494+
from the local variable "stackless" to the global variable
495+
"_PyStackless_TRY_STACKLESS". The macro :c:func:`STACKLESS_PROMOTE_ALL` does
496+
this unconditionally. It is used for cases where we know that the called
497+
function will take care of our object, and we need no test. For example,
498+
:c:func:`PyObject_Call` and all other Py{Object,Function,CFunction}_*Call*
499+
functions use STACKLESS_PROMOTE_xxx itself, so we don't need to check further.
500+
501+
.. c:function:: STACKLESS_PROMOTE_FLAG(flag)
502+
503+
This macro is the most general conditional variant.
504+
If the local variable "stackless" was set, it sets the global
505+
variable "_PyStackless_TRY_STACKLESS" to *flag* and returns *flag*.
506+
Otherwise the macro returns 0. It is used for special cases,
507+
like PyCFunction objects. PyCFunction_Type
508+
says that it supports a stackless call, but the final action depends
509+
on the METH_STACKLESS flag in the object to be called. Therefore,
510+
PyCFunction_Call uses ``STACKLESS_PROMOTE_FLAG(flags & METH_STACKLESS)`` to
511+
take care of PyCFunctions which don't care about it.
427512
428-
debugging and monitoring functions
513+
Another example is the "next" method of iterators. To support this,
514+
the wrapperobject's type has the Py_TPFLAGS_HAVE_STACKLESS_CALL
515+
flag set, but wrapper_call then examines the wrapper descriptors
516+
flags if PyWrapperFlag_STACKLESS is set. "next" has it set.
517+
It also checks whether Py_TPFLAGS_HAVE_STACKLESS_CALL is set
518+
for the iterator's type.
519+
520+
.. c:function:: STACKLESS_PROMOTE_METHOD(obj, slot_name)
521+
522+
If the local variable "stackless" was set and if the type method for the
523+
slot *slot_name* of the type of object *obj* obeys the stackless-protocol,
524+
then _PyStackless_TRY_STACKLESS is set to 1, and we
525+
expect that the function handles it correctly.
526+
527+
.. c:function:: STACKLESS_PROMOTE(obj)
528+
529+
A special optimized variant of ``STACKLESS_PROMOTE_METHOD(`` *obj* ``, tp_call)``.
530+
531+
.. c:function:: STACKLESS_ASSERT()
532+
533+
In debug builds this macro asserts that _PyStackless_TRY_STACKLESS was cleared.
534+
This debug feature tries to ensure that no unexpected nonrecursive call can happen.
535+
In release builds this macro does nothing.
536+
537+
.. c:function:: STACKLESS_RETRACT()
538+
539+
Set the global variable "_PyStackless_TRY_STACKLESS" unconditionally to 0.
540+
Rarely used.
541+
542+
Examples
543+
~~~~~~~~
544+
545+
The Stackless test-module :py:mod:`_teststackless` contains the following
546+
example for a soft switchable function.
547+
To call it use
548+
``PyStackless_CallFunction(&demo_soft_switchable_declaration, result, NULL, NULL, NULL, action, NULL)``.
549+
550+
.. include:: ../../Stackless/module/_teststackless.c
551+
:code: c
552+
:encoding: utf-8
553+
:start-after: /*DO-NOT-REMOVE-OR-MODIFY-THIS-MARKER:ssf-example-start*/
554+
:end-before: /*DO-NOT-REMOVE-OR-MODIFY-THIS-MARKER:ssf-example-end*/
555+
556+
Another, more realistic example is :py:const:`_asyncio._task_step_impl_stackless`, defined in
557+
"Modules/_asynciomodules.c".
558+
559+
560+
.. [#] Actually "_PyStackless_TRY_STACKLESS" is a macro that expands to a C L-value. As long as
561+
|CPY| uses the GIL, this L-value is a global variable.
562+
563+
Debugging and monitoring Functions
429564
----------------------------------
430565
431566
.. c:function:: int PyStackless_SetChannelCallback(PyObject *callable)
@@ -460,6 +595,12 @@ Stack unwinding
460595
461596
A singleton that indicates C-stack unwinding
462597
598+
.. note::
599+
600+
:c:data:`Py_UnwindToken` is *never* inc/decref'ed. Use the
601+
macro :c:func:`STACKLESS_UNWINDING` to test for
602+
Py_UnwindToken.
603+
463604
.. c:function:: int STACKLESS_UNWINDING(obj)
464605
465606
Return 1, if *obj* is :c:data:`Py_UnwindToken` and 0 otherwise.

Stackless/changelog.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ What's New in Stackless 3.X.X?
2727
Previously they contained a reference to a non existing variable.
2828

2929
- https://github.com/stackless-dev/stackless/issues/178
30+
- https://github.com/stackless-dev/stackless/issues/198
3031
There is a new, provisional API to write soft switchable functions in C.
32+
You can also use this API to convert existing extension functions to
33+
the stackless-protocol.
3134

3235
- https://github.com/stackless-dev/stackless/issues/149
3336
The Stackless version is now "3.7".

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=NULL, *ev=NULL, *tb=NULL;
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
}

0 commit comments

Comments
 (0)