Skip to content

Commit e3001b5

Browse files
author
Anselm Kruis
committed
Stackless issue python#198: Improve the soft-switchable extension functions
Clean up the API header, add tests and documentation.
1 parent 9c2dabb commit e3001b5

File tree

8 files changed

+367
-87
lines changed

8 files changed

+367
-87
lines changed

Doc/c-api/stackless.rst

Lines changed: 161 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,142 @@ 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)
427502
428-
debugging and monitoring functions
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.
512+
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+
557+
.. [#] Actually "_PyStackless_TRY_STACKLESS" is a macro that expands to a C L-value. As long as
558+
|CPY| uses the GIL, this L-value is a global variable.
559+
560+
Debugging and monitoring Functions
429561
----------------------------------
430562
431563
.. c:function:: int PyStackless_SetChannelCallback(PyObject *callable)
@@ -460,6 +592,12 @@ Stack unwinding
460592
461593
A singleton that indicates C-stack unwinding
462594
595+
.. note::
596+
597+
:c:data:`Py_UnwindToken` is *never* inc/decref'ed. Use the
598+
macro :c:func:`STACKLESS_UNWINDING` to test for
599+
Py_UnwindToken.
600+
463601
.. c:function:: int STACKLESS_UNWINDING(obj)
464602
465603
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
@@ -19,7 +19,10 @@ What's New in Stackless 3.X.X?
1919
Previously they contained a reference to a non existing variable.
2020

2121
- https://github.com/stackless-dev/stackless/issues/178
22+
- https://github.com/stackless-dev/stackless/issues/198
2223
There is a new, provisional API to write soft switchable functions in C.
24+
You can also use this API to convert existing extension functions to
25+
the stackless-protocol.
2326

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

Stackless/core/stackless_impl.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,12 @@ PyTaskletObject * slp_get_watchdog(PyThreadState *ts, int interrupt);
437437
#define STACKLESS__GETARG_ASSERT \
438438
assert(SLP_CURRENT_FRAME_IS_VALID(PyThreadState_GET()))
439439

440+
/* descr must be of type PyWrapperDescrObject, but this type is undocumented.
441+
* Therefore this macro is in stackless_impl.h and not in stackless_api.h
442+
*/
443+
#define STACKLESS_PROMOTE_WRAPPER(descr) \
444+
STACKLESS_PROMOTE_FLAG((descr)->d_slpmask)
445+
440446
#define STACKLESS_PROPOSE(tstate, func) {int stackless = STACKLESS_POSSIBLE(tstate); \
441447
STACKLESS_PROMOTE(func);}
442448

Stackless/core/stackless_structs.h

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -288,18 +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
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
303303

304304
#endif /* #ifdef STACKLESS */
305305

0 commit comments

Comments
 (0)