* Function call
* The coroutine creates its frame (I believe this maps to "future creation".)
* Function return (the injected immediate suspend)
* (sometime later) Put the work on the event loop
* Function call (suspend resume)
* Function executes until a suspend point
Eager execution has no injected immediate suspend, so it looks like this:
* Function call
* The coroutine creates its frame
* The function executes until a suspend point (function return)
That's it. The coroutine is responsible for making sure that it gets resumed appropriately if it suspends. Await doesn't do anything with an event loop; it just suspends and then puts the suspended coroutine handle in the target coroutine's frame with an AtomicRMW. If it turns out the target coroutine already completed, then it grabs the result, destroys the target coroutine, and cancels suspending (which is a jmp instruction). Otherwise the suspend completes and the target coroutine will resume the suspended one when it completes.
> If you have a program that only uses async & await and no concurrency primitives, the code in your program will execute in a defined, statically known, linear order.
This is true with eager execution as well. You can see some examples here [2].
One more side note, how eager execution was valuable to me in the self-hosted compiler:
pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) !void {
fn_val.base.ref();
defer fn_val.base.deref(comp);
// ...
}
At the callsite we make an async call to renderToLlvm, passing in a ref-counted fn_val. If the body didn't execute immediately, the callsite might deref fn_val and destroy it before renderToLlvm gets a chance to add a reference, but because of eager execution, the ref is guaranteed.[1]: http://llvm.org/docs/Coroutines.html
[2]: https://github.com/ziglang/zig/blob/363f4facea7fac2d6cfeab9d...