Skip to content

Implement "yield from" #366

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
pfalcon opened this issue Mar 23, 2014 · 10 comments
Closed

Implement "yield from" #366

pfalcon opened this issue Mar 23, 2014 · 10 comments

Comments

@pfalcon
Copy link
Contributor

pfalcon commented Mar 23, 2014

"yield from" is important part of modern Python asynchronous concurrency framework which is being established (asyncio module of py3.4 and friends). But as it's pretty new feature, it's not immediately clear how it should be implemented (especially efficiently). This ticket is open to collected pointers and ideas regarding that.

@pfalcon
Copy link
Contributor Author

pfalcon commented Mar 23, 2014

Original http://legacy.python.org/dev/peps/pep-0380/ had proposed implementation: http://bugs.python.org/issue11682 , which kinda even supported some optimization.

But that's not what ended up being in 3.3 & 3.4 mainline, supposedly because that code (optimizations it tried?) had some issues.

Implementations from PEP and mainline are pretty different, up to expecting different parameters on the stack.

@dpgeorge
Copy link
Member

uPy currently compiles "yield from", but the byte code is not implemented. Looking at Python's source in ceval.c (yes, cheating, I know!), YIELD_FROM does not look that crazy difficult.

@KeithJRome
Copy link

Many of the optimizations are going to only be relevant in a saturated multi-core environment (work-stealing queues, etc) and might not be of benefit to the lean execution model of typical microcontroller code. Perhaps the general notion of using a LIFO task queue instead of FIFO in order to improve cache hits and memory locality (are those even relevant on these processors?), but aside from that I'm not sure it's worth chasing many of the other optimizations.

@pfalcon
Copy link
Contributor Author

pfalcon commented Mar 23, 2014

Well, maybe if you wrote a whole Python interpreter from scratch, it's not crazy difficult for you ;-). But I don't understand all details of it (some of exceptions/generator patches were written in intuitive manner - I just tried ideas how it might work, and if testcases work the same as CPy and vm.c doesn't trigger asserts, that's it).

So, re: YIELD_FROM. dis.py tells me: "TypeError: don't know how to disassemble generator objects", so, I can't even verify that uPy bytecode matches CPy's. And looking at YIELD_FROM in ceval.c what I see is that it pops a value for .send() from stack, then decrements IP to point to same YIELD_FROM opcode. So, next iteration will expect to pop a value again, but where's this new value is pushed?

And don't forget that large deal of "yield from" magic lives in genobject.c, and there're lots of unclear stuff. For example, gen_yf() function (check if a generator currently yields from another gen) is called only for .throw() and .close(), but not for send(). And then close/throw recursively descends thru gen_yf() chain (not even using a loop), and then .send() apparently doesn't even try to walk frames, but bytecode-interpretes thru them, so it's not only messy, it's also awfully non-optimal in CPy.

@pfalcon
Copy link
Contributor Author

pfalcon commented Mar 23, 2014

Also, genobject.c:gen_send_ex() visibly pushed sent value on gen's value stack, but what we do is just set TOS, w/o push. Don't ask me why - I wrote above how that code was developed - it just works ;-).

Actually, thinking about it now, yield'ed value is on TOS, and naturally would need to be popped from it. Then sent value should be pushed on it. So, what we do is pop/push optimization - instead of this sequence, we just take/put value on TOS.

Ok, but how that would map to YIELD_FROM? Should we not pop value just the same? Or would we need to give up on pop/push opt altogether?

@pfalcon
Copy link
Contributor Author

pfalcon commented Mar 23, 2014

Also, intuitively, yield-from chain of generators is not really a chain, it's stack. But even PEP's code treat as a chain and O(n)-walks it (using C recursion again).

Instead, "top-level" gen should point to the deepest gen, that one - to its parent, etc. The problem - is such "toplevel" gen really exists? The matter, while gens seemingly form chain/stack, they still remain individually accessible, so one could .send() value in the middle of stack. So, is it the case that gen used in yield from is marked as "running" for the duration of yield from, so attempt to access it by any other means (e.g. .send()) will lead to RuntimeError?

Btw, we don't track running status at all, and apparently would need to, at least to assert correctness.

@pfalcon
Copy link
Contributor Author

pfalcon commented Mar 24, 2014

Ok, I have rough implementation which does basic working.

@pfalcon
Copy link
Contributor Author

pfalcon commented Mar 26, 2014

I believe I have failry complete implementation now at https://github.com/pfalcon/micropython/commits/pfalcon . As with #368, most cumbersome if not say annoying was implementing .close() . yield from establishes its own adhoc rules for passing upstream and downstream values and for downstream exceptions. However, it piggybacks on standard exception propagation for upstream exceptions. That already breaks consistency and complicates reasoning/implementation (I have a hypothesis now that PEP380's optimizing impl was not accepted because it doesn't account for this asymmetry). But with .close() and GeneratorExit, it again establishes different exception propagation rules, so it all gets pretty messy. (In this regard I still hope that my code is more clear than CPython's).

All in all, I still going to read PEP380 dozen more times, looking for lawyer-style tricks (reading PEP380, I immediately remember old subheading of Python's language manual: "for language lawyers" - PEP380 is truly written in that style, full of formalisms and pretty weak on real rationale for implementation choices).

@pfalcon
Copy link
Contributor Author

pfalcon commented Mar 30, 2014

As of now, yield from is implemented for both generators and other types.

@pfalcon pfalcon closed this as completed Mar 30, 2014
@dpgeorge
Copy link
Member

Great work.

tannewt added a commit to tannewt/circuitpython that referenced this issue Oct 25, 2017
…ython#366)

atmel-samd: Use our own CDC output cache because the internal
cache is only used when the memory isn't aligned even if we're
going to change the memory immediately after.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants