Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Commit bb9dc7a

Browse files
author
Anselm Kruis
authored
Stackless issue #188: enable soft switching for sub-iterators, (async) generators and coroutines
Enable soft switching for iterators/coroutines called by "yield from". Improve the finalisation of a soft switched YIELD_FROM instruction. Enable stackless calls of the following methods: - generator.send() (generator.__next__() was already stackless); - coroutine.send(); - coroutine_wrapper.__next__() and coroutine_wrapper.send(); - async_generator_asend.__next__() and async_generator_asend.send().
1 parent f24223f commit bb9dc7a

File tree

7 files changed

+269
-16
lines changed

7 files changed

+269
-16
lines changed

Objects/genobject.c

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,30 @@ gen_dealloc(PyGenObject *gen)
159159
* was not faster, but considerably slower than this solution.
160160
*/
161161

162-
static PyObject* gen_iternext_callback(PyFrameObject *f, int exc, PyObject *result);
162+
/* Like gen_send_ex, additionally pass ob3 into the callback function. */
163+
static PyObject *
164+
gen_send_ex2(PyGenObject *gen, PyObject *arg, int exc, int closing, PyObject* ob3);
165+
166+
/* callback for (async) generators and coroutines. */
167+
static PyObject *
168+
gen_iternext_callback(PyFrameObject *f, int exc, PyObject *result);
169+
170+
/* Additional callback-code for async generators. */
171+
static PyObject *
172+
async_gen_asend_send_end(PyAsyncGenObject *gen, PyObject *asend, PyObject *result);
163173
#endif
164174

175+
165176
static PyObject *
166177
gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
167178
{
168179
#ifdef STACKLESS
180+
return gen_send_ex2(gen, arg, exc, closing, NULL);
181+
}
182+
183+
static PyObject *
184+
gen_send_ex2(PyGenObject *gen, PyObject *arg, int exc, int closing, PyObject * ob3)
185+
{
169186
STACKLESS_GETARG();
170187
PyFrameObject *stopframe;
171188
#endif
@@ -249,6 +266,7 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
249266
assert(f->f_back->f_back == NULL);
250267
assert(((PyCFrameObject *)f->f_back)->ob1 == NULL);
251268
assert(((PyCFrameObject *)f->f_back)->ob2 == NULL);
269+
assert(((PyCFrameObject *)f->f_back)->ob3 == NULL);
252270

253271
if (f->f_lasti != -1)
254272
#else
@@ -295,8 +313,10 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
295313

296314
Py_INCREF(gen);
297315
Py_XINCREF(arg);
316+
Py_XINCREF(ob3);
298317
((PyCFrameObject *) f->f_back)->ob1 = (PyObject *) gen;
299318
((PyCFrameObject *) f->f_back)->ob2 = arg;
319+
((PyCFrameObject *) f->f_back)->ob3 = ob3;
300320

301321
if (exc)
302322
result = NULL;
@@ -326,12 +346,14 @@ gen_iternext_callback(PyFrameObject *f, int exc, PyObject *result)
326346
PyCFrameObject *cf = (PyCFrameObject *) f;
327347
PyGenObject *gen = (PyGenObject *) cf->ob1;
328348
PyObject *arg = cf->ob2;
349+
PyObject *ob3 = cf->ob3;
329350

330351
/* We hold references to things in the cframe, if we release it
331352
before we clear the references, they get incorrectly and
332353
prematurely freed. */
333354
cf->ob1 = NULL;
334355
cf->ob2 = NULL;
356+
cf->ob3 = NULL;
335357

336358
f = gen->gi_frame;
337359
/* Check, that this cframe belongs to gen */
@@ -400,8 +422,13 @@ gen_iternext_callback(PyFrameObject *f, int exc, PyObject *result)
400422
Py_DECREF(f);
401423
}
402424
#ifdef STACKLESS
425+
if (ob3 && PyAsyncGen_CheckExact(gen)) {
426+
result = async_gen_asend_send_end((PyAsyncGenObject *)gen, ob3, result);
427+
}
428+
403429
Py_DECREF(gen);
404430
Py_XDECREF(arg);
431+
Py_XDECREF(ob3);
405432
#endif
406433
return result;
407434
}
@@ -839,7 +866,7 @@ static PyMemberDef gen_memberlist[] = {
839866
};
840867

841868
static PyMethodDef gen_methods[] = {
842-
{"send",(PyCFunction)_PyGen_Send, METH_O, send_doc},
869+
{"send",(PyCFunction)_PyGen_Send, METH_O | METH_STACKLESS, send_doc},
843870
{"throw",(PyCFunction)gen_throw, METH_VARARGS, throw_doc},
844871
{"close",(PyCFunction)gen_close, METH_NOARGS, close_doc},
845872
{NULL, NULL} /* Sentinel */
@@ -1092,7 +1119,7 @@ PyDoc_STRVAR(coro_close_doc,
10921119
"close() -> raise GeneratorExit inside coroutine.");
10931120

10941121
static PyMethodDef coro_methods[] = {
1095-
{"send",(PyCFunction)_PyGen_Send, METH_O, coro_send_doc},
1122+
{"send",(PyCFunction)_PyGen_Send, METH_O | METH_STACKLESS, coro_send_doc},
10961123
{"throw",(PyCFunction)gen_throw, METH_VARARGS, coro_throw_doc},
10971124
{"close",(PyCFunction)gen_close, METH_NOARGS, coro_close_doc},
10981125
{NULL, NULL} /* Sentinel */
@@ -1197,7 +1224,7 @@ coro_wrapper_traverse(PyCoroWrapper *cw, visitproc visit, void *arg)
11971224
}
11981225

11991226
static PyMethodDef coro_wrapper_methods[] = {
1200-
{"send",(PyCFunction)coro_wrapper_send, METH_O, coro_send_doc},
1227+
{"send",(PyCFunction)coro_wrapper_send, METH_O | METH_STACKLESS, coro_send_doc},
12011228
{"throw",(PyCFunction)coro_wrapper_throw, METH_VARARGS, coro_throw_doc},
12021229
{"close",(PyCFunction)coro_wrapper_close, METH_NOARGS, coro_close_doc},
12031230
{NULL, NULL} /* Sentinel */
@@ -1245,6 +1272,8 @@ PyTypeObject _PyCoroWrapper_Type = {
12451272
0, /* tp_free */
12461273
};
12471274

1275+
STACKLESS_DECLARE_METHOD(&_PyCoroWrapper_Type, tp_iternext);
1276+
12481277
static PyObject *
12491278
compute_cr_origin(int origin_depth)
12501279
{
@@ -1666,6 +1695,7 @@ async_gen_asend_traverse(PyAsyncGenASend *o, visitproc visit, void *arg)
16661695
static PyObject *
16671696
async_gen_asend_send(PyAsyncGenASend *o, PyObject *arg)
16681697
{
1698+
STACKLESS_GETARG();
16691699
PyObject *result;
16701700

16711701
if (o->ags_state == AWAITABLE_STATE_CLOSED) {
@@ -1680,6 +1710,16 @@ async_gen_asend_send(PyAsyncGenASend *o, PyObject *arg)
16801710
o->ags_state = AWAITABLE_STATE_ITER;
16811711
}
16821712

1713+
#ifdef STACKLESS
1714+
if (stackless) {
1715+
STACKLESS_PROMOTE_ALL();
1716+
result = gen_send_ex2((PyGenObject*)o->ags_gen, arg, 0, 0, (PyObject *)o);
1717+
STACKLESS_ASSERT();
1718+
if (STACKLESS_UNWINDING(result))
1719+
return result;
1720+
}
1721+
else
1722+
#endif
16831723
result = gen_send_ex((PyGenObject*)o->ags_gen, arg, 0, 0);
16841724
result = async_gen_unwrap_value(o->ags_gen, result);
16851725

@@ -1690,6 +1730,22 @@ async_gen_asend_send(PyAsyncGenASend *o, PyObject *arg)
16901730
return result;
16911731
}
16921732

1733+
#ifdef STACKLESS
1734+
static PyObject *
1735+
async_gen_asend_send_end(PyAsyncGenObject *gen, PyObject *asend, PyObject *result)
1736+
{
1737+
PyAsyncGenASend *o = (PyAsyncGenASend *) asend;
1738+
assert(o->ags_gen == gen);
1739+
1740+
result = async_gen_unwrap_value(o->ags_gen, result);
1741+
1742+
if (result == NULL) {
1743+
o->ags_state = AWAITABLE_STATE_CLOSED;
1744+
}
1745+
1746+
return result;
1747+
}
1748+
#endif
16931749

16941750
static PyObject *
16951751
async_gen_asend_iternext(PyAsyncGenASend *o)
@@ -1728,7 +1784,7 @@ async_gen_asend_close(PyAsyncGenASend *o, PyObject *args)
17281784

17291785

17301786
static PyMethodDef async_gen_asend_methods[] = {
1731-
{"send", (PyCFunction)async_gen_asend_send, METH_O, send_doc},
1787+
{"send", (PyCFunction)async_gen_asend_send, METH_O | METH_STACKLESS, send_doc},
17321788
{"throw", (PyCFunction)async_gen_asend_throw, METH_VARARGS, throw_doc},
17331789
{"close", (PyCFunction)async_gen_asend_close, METH_NOARGS, close_doc},
17341790
{NULL, NULL} /* Sentinel */
@@ -1784,6 +1840,8 @@ PyTypeObject _PyAsyncGenASend_Type = {
17841840
0, /* tp_new */
17851841
};
17861842

1843+
STACKLESS_DECLARE_METHOD(&_PyAsyncGenASend_Type, tp_iternext);
1844+
17871845

17881846
static PyObject *
17891847
async_gen_asend_new(PyAsyncGenObject *gen, PyObject *sendval)

Python/ceval.c

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,7 +1100,35 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
11001100
/*f->f_execute = PyEval_EvalFrame_value;
11011101
PREDICT(WITH_CLEANUP_FINISH); */
11021102
}
1103-
} else
1103+
}
1104+
else if (f->f_execute == slp_eval_frame_yield_from) {
1105+
/* finalise the YIELD_FROM operation */
1106+
PyObject *receiver = TOP();
1107+
if (retval == NULL) {
1108+
int err;
1109+
if (tstate->c_tracefunc != NULL
1110+
&& PyErr_ExceptionMatches(PyExc_StopIteration))
1111+
call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj, tstate, f);
1112+
err = _PyGen_FetchStopIterationValue(&retval);
1113+
if (err < 0) {
1114+
retval = NULL;
1115+
}
1116+
else {
1117+
Py_DECREF(receiver);
1118+
SET_TOP(retval);
1119+
}
1120+
}
1121+
else {
1122+
/* receiver remains on stack, retval is value to be yielded */
1123+
f->f_stacktop = stack_pointer;
1124+
why = WHY_YIELD;
1125+
/* and repeat... */
1126+
assert(f->f_lasti >= (int)sizeof(_Py_CODEUNIT));
1127+
f->f_lasti -= sizeof(_Py_CODEUNIT);
1128+
goto fast_yield;
1129+
}
1130+
}
1131+
else
11041132
Py_FatalError("invalid frame function");
11051133
f->f_execute = slp_eval_frame_value;
11061134
}
@@ -1972,15 +2000,29 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
19722000
PyObject *receiver = TOP();
19732001
int err;
19742002
if (PyGen_CheckExact(receiver) || PyCoro_CheckExact(receiver)) {
2003+
STACKLESS_PROPOSE_ALL(tstate);
19752004
retval = _PyGen_Send((PyGenObject *)receiver, v);
2005+
STACKLESS_ASSERT();
19762006
} else {
19772007
_Py_IDENTIFIER(send);
1978-
if (v == Py_None)
2008+
if (v == Py_None) {
2009+
STACKLESS_PROPOSE_METHOD(tstate, receiver, tp_iternext);
19792010
retval = Py_TYPE(receiver)->tp_iternext(receiver);
1980-
else
2011+
STACKLESS_ASSERT();
2012+
}
2013+
else {
2014+
STACKLESS_PROPOSE_ALL(tstate);
19812015
retval = _PyObject_CallMethodIdObjArgs(receiver, &PyId_send, v, NULL);
2016+
STACKLESS_ASSERT();
2017+
}
19822018
}
19832019
Py_DECREF(v);
2020+
#ifdef STACKLESS
2021+
if (STACKLESS_UNWINDING(retval)) {
2022+
HANDLE_UNWINDING(slp_eval_frame_yield_from, 0, retval);
2023+
receiver = TOP();
2024+
}
2025+
#endif
19842026
if (retval == NULL) {
19852027
PyObject *val;
19862028
if (tstate->c_tracefunc != NULL
@@ -3831,6 +3873,21 @@ slp_eval_frame_with_cleanup(PyFrameObject *f, int throwflag, PyObject *retval)
38313873
return r;
38323874
}
38333875

3876+
PyObject *
3877+
slp_eval_frame_yield_from(PyFrameObject *f, int throwflag, PyObject *retval)
3878+
{
3879+
PyObject *r;
3880+
/*
3881+
* this function is identical to PyEval_EvalFrame_value.
3882+
* it serves as a marker whether we are inside of a
3883+
* WITH_CLEANUP operation.
3884+
* NOTE / XXX: see above.
3885+
*/
3886+
SLP_DO_NOT_OPTIMIZE_AWAY((char *)5);
3887+
r = slp_eval_frame_value(f, throwflag, retval);
3888+
return r;
3889+
}
3890+
38343891
static PyObject *
38353892
run_frame_dispatch(PyFrameObject *f, int exc, PyObject *retval)
38363893
{
@@ -3948,7 +4005,8 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
39484005
f->f_execute != slp_eval_frame_noval &&
39494006
f->f_execute != slp_eval_frame_iter &&
39504007
f->f_execute != slp_eval_frame_setup_with &&
3951-
f->f_execute != slp_eval_frame_with_cleanup) {
4008+
f->f_execute != slp_eval_frame_with_cleanup &&
4009+
f->f_execute != slp_eval_frame_yield_from) {
39524010
PyErr_BadInternalCall();
39534011
return NULL;
39544012
}

Stackless/changelog.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ What's New in Stackless 3.X.X?
99

1010
*Release date: 20XX-XX-XX*
1111

12+
- https://github.com/stackless-dev/stackless/issues/188
13+
Enable stackless calls of sub-iterators and coroutines (technically: the
14+
YIELD_FROM opcode) and of the following methods, if soft-switching is
15+
enabled:
16+
- generator.send() (generator.__next__() was already stackless);
17+
- coroutine.send();
18+
- coroutine_wrapper.__next__() and coroutine_wrapper.send();
19+
- async_generator_asend.__next__() and async_generator_asend.send().
20+
With this change it is possible to soft switch into/from the compound
21+
statements "async for" and "async with".
22+
1223
- https://github.com/stackless-dev/stackless/issues/200
1324
Fix a bug in the C-API functions PyTasklet_Run_nr() and
1425
PyTasklet_Switch_nr(). Under exotic conditions the functions could

Stackless/core/stackless_impl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ PyObject * slp_eval_frame_noval(struct _frame *f, int throwflag, PyObject *retv
378378
PyObject * slp_eval_frame_iter(struct _frame *f, int throwflag, PyObject *retval);
379379
PyObject * slp_eval_frame_setup_with(struct _frame *f, int throwflag, PyObject *retval);
380380
PyObject * slp_eval_frame_with_cleanup(struct _frame *f, int throwflag, PyObject *retval);
381+
PyObject * slp_eval_frame_yield_from(struct _frame *f, int throwflag, PyObject *retval);
381382
/* other eval_frame functions from module/scheduling.c */
382383
PyObject * slp_restore_tracing(PyFrameObject *f, int exc, PyObject *retval);
383384
/* other eval_frame functions from Objects/typeobject.c */

Stackless/core/stackless_methods.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ typedef struct {
1414
#define MFLAG_OFS_IND(meth) MFLAG_OFS(meth) + MFLAG_IND
1515

1616
static _stackless_method _stackless_methtable[] = {
17-
/* from methodobject.c */
18-
{&PyCFunction_Type, MFLAG_OFS(tp_call)},
17+
/* from classobject.c */
18+
{&PyMethod_Type, MFLAG_OFS(tp_call)},
1919
/* from descrobject.c */
2020
{&PyMethodDescr_Type, MFLAG_OFS(tp_call)},
2121
{&PyClassMethodDescr_Type, MFLAG_OFS(tp_call)},
@@ -24,10 +24,12 @@ static _stackless_method _stackless_methtable[] = {
2424
{&PyFunction_Type, MFLAG_OFS(tp_call)},
2525
/* from genobject.c */
2626
{&PyGen_Type, MFLAG_OFS(tp_iternext)},
27+
{&_PyCoroWrapper_Type, MFLAG_OFS(tp_iternext)},
28+
{&_PyAsyncGenASend_Type, MFLAG_OFS(tp_iternext)},
29+
/* from methodobject.c */
30+
{&PyCFunction_Type, MFLAG_OFS(tp_call)},
2731
/* from typeobject.c */
2832
{&PyType_Type, MFLAG_OFS(tp_call)},
29-
/* from classobject.c */
30-
{&PyMethod_Type, MFLAG_OFS(tp_call)},
3133
/* from channelobject.c */
3234
{&PyChannel_Type, MFLAG_OFS(tp_iternext)},
3335
{0, 0} /* sentinel */

Stackless/pickling/prickelpit.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,7 @@ SLP_DEF_INVALID_EXEC(eval_frame_noval)
839839
SLP_DEF_INVALID_EXEC(eval_frame_iter)
840840
SLP_DEF_INVALID_EXEC(eval_frame_setup_with)
841841
SLP_DEF_INVALID_EXEC(eval_frame_with_cleanup)
842+
SLP_DEF_INVALID_EXEC(eval_frame_yield_from)
842843
SLP_DEF_INVALID_EXEC(slp_channel_seq_callback)
843844
SLP_DEF_INVALID_EXEC(slp_restore_tracing)
844845
SLP_DEF_INVALID_EXEC(slp_tp_init_callback)
@@ -1202,6 +1203,8 @@ static int init_frametype(PyObject * mod)
12021203
slp_eval_frame_setup_with, SLP_REF_INVALID_EXEC(eval_frame_setup_with))
12031204
|| slp_register_execute(&PyFrame_Type, "eval_frame_with_cleanup",
12041205
slp_eval_frame_with_cleanup, SLP_REF_INVALID_EXEC(eval_frame_with_cleanup))
1206+
|| slp_register_execute(&PyFrame_Type, "eval_frame_yield_from",
1207+
slp_eval_frame_yield_from, SLP_REF_INVALID_EXEC(eval_frame_yield_from))
12051208
|| slp_register_execute(&PyCFrame_Type, "channel_seq_callback",
12061209
slp_channel_seq_callback, SLP_REF_INVALID_EXEC(slp_channel_seq_callback))
12071210
|| slp_register_execute(&PyCFrame_Type, "slp_restore_tracing",

0 commit comments

Comments
 (0)