It's large and very detailed, but 99% of time a simple “async
function is a function with a call frame not on the stack” is sufficient.
At least if your goal is rough understanding of how is the result of the procedure returned on an assembly level.
Start with the normal code with a stack, stack pointer and, importantly, a frame pointer. How can we turn that function into a stoppable and resumable coroutine?
Well… it's easy, at least conceptually: just rip out the stack frame and put it somewhere… not on stack, just in some region… and call that region “future”. Bam: done, now you have function that can be called later, stoped and resumed. In addition to our “future” (former “function stack frame”) you only need pointer to the “currently executing instruction” beside that “future” (or, perhaps, as a slot in that future).
When you call await
– “currently executing instruction” is recorded and execution is transferred to “executor”, when executor wants to resume the execution – it could just jump on that address.
Of course to actually have usable async
you need many more details, you need some way of notifying the executor about the need to “wake” some coroutine (otherwise your only hope is to wake them as much as you can to see if, maybe, they would do some useful work… not too much useful if your goal is working program and not a replacement for space heater), you need some low-level API that would allow you to call the OS (some OSes have the required mechanisms but most only have a handful of useful syscalls, mostly networking-related which means in practice async
is much less useful than people think) and so on (there are lots of details, read the article if you want to know about them).
But the core idea is just that simple: “rip out” the call frame of the function from stack, put it in the “future” and add “currently executing instruction” slot to it.
P.S. I just wish they would make coroutines without all that async
machinery available on stable, at some point. Because most OSes are not asyncronyous pretending that everything around you is asyncronous just adds complications without many real benefits… but alas, async
is currently hot buzzword, while coroutines were “hot” decades ago. That's why we got async
first and much more useful (and simpler!) coroutines mechanism without all that extra baggage is still unstable.