RFC: LLVM Coroutine Representation, Round 2

Hi Antoine:

- As the caller of a coroutine, how do I know whether a coroutine
  reached its cleanup point or not? This seems essential for
  implementing iterators and looping on them, yet @llvm.coro.done only
  works if a particular suspend point was designated as "final" (which
  doesn't sound possible to do in the general case, i.e. halting

Whether a suspend point is final or not is a property of the a suspend point.
The second parameter of the coro.suspend intrinsic is a Boolean constant that
indicates whether it is final suspend point or not. The frontend decides which
suspend is final (if any).

For example, for C++ generator that looks like this:

   genenerator<int> f(int n) {
      yield 1;
      yield 2;

Compiler synthesizes additional initial and final suspend points, the latter
is marked as final in LLVM IR. So, the actual coroutine looks like:

   genenerator<int> f(int n) {
      yield 1;
      yield 2;
      <final-suspend> // marked as final suspend

When you pull from a generator, you resume the coroutine and then check, whether
coro.done is true or false. If false, coroutine is not at final
suspend and the user
of a generator can consume the current value. If it is at final suspend point
there is no value to consume, thus, you know that you are at the end
of the sequence.

If you have a coroutine that looks like this:

   genenerator<int> g(int n) {
      for (int i = 0;:wink: yield ++i;
      <final-suspend> // marked as final suspend

It will never reach the final suspend and thus coro.done will never evaluate
to true. Thus, coroutine will produce an infinite sequence of values.

- Is the promise type restricted to atomic LLVM types, or can I use
  @llvm.coro.promise.p0i8 (together with a bitcast) if the promise is an
  arbitrary complex structure?

A promise could be an arbitrary type. It does not have to be atomic.
A promise for a generator probably will only contain the current value or the
pointer to the current value depending if it is cheap or expensive to copy.

A promise for an asynchronous coroutine would probably contain a room to store
an eventual value produced by that task and, possibly, an atomic flag you can
set while coroutine is running to indicate that you would like it to cancel and
a reference count tracking number of users holding the reference to a coroutine
so that it won't go away until the number of users drops down to zero.

- Is the promise allocated/copied inside the coroutine frame? If not,
  how does the sharing work even if the coroutine is passed to different

Yes. Coroutine promise, if specified, will be always part of the coroutine frame
and will be placed at deterministic location. So that coro.promise and
coro.from.promise intrinsics can work.


I don't think this answers my question. Say I have (Python notation):

def coroutine(n):
    for i in range(n):
        yield i

def consumer(n):
    for val in coroutine(n):
        # do something with val

How does consumer() know the coroutine has exited after the n'th
iteration? It can't call @coro.done() as there is a single suspend