2

I can currently think of two ways of implementing a coroutine:

  1. Whenever a coroutine is started, instead of storing local variables on stack, get a piece of memory from heap and use it to store local variables. This way, local variables are not destroyed, and any called function can return to this function later in time. But in this case, any called function which is not a coroutine has to run on main call stack. I don't know if this could cause any problem. Can someone confirm it!
  2. Whenever a coroutine is started, allocate a bigger amount of memory than required to that coroutine. This would act like some kind of custom call stack for that coroutine. In this case, all the sub-functions called by this would be stored together with this coroutine. But this implementation may require too much of redundant memory.

I think these two are popularly known as stackless and stackful coroutines.

What are other theoretically possible ways of implementing a coroutine? What are advantages and disadvantages of them? Which languages use which implementations?

Sourav Kannantha B
  • 2,860
  • 1
  • 11
  • 35

1 Answers1

1

Your option (1) is indeed a stackless coroutine, and this is how it's implemented in Kotlin, and usually Javascript (async/await), for example. This is how you do it when you don't necessarily have the low-level control of the call stack that other methods require. Languages that use this strategy require suspendable functions to be marked in some way. That's called the "red/blue function problem". See: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/

Languages that do have low-level control of the call stack can implement coroutines in various ways, but none of them look like your option (2). They generally involve copying data into and out of the call stack when required (like Java project Loom) or using a completely different data structure in place of the call stack (like early Go implementations). In these languages, coroutine-callable functions don't usually need special markings.

Matt Timmermans
  • 53,709
  • 3
  • 46
  • 87
  • So, languages that do have low-level control would copy the entire stack in and out of the main-stack??? Isn't this too heavy on performance? I will be seeing about early Go's implementation. – Sourav Kannantha B Mar 16 '22 at 13:12
  • 1
    Not the entire stack. Just the part between the coroutine entry point and the position where it has to be suspended. And they they copy back only pieces of it, on demand. It can be made to perform much better than option 1. – Matt Timmermans Mar 16 '22 at 13:13
  • 'It can be made to perform much better than option 1'. That's quite interesting, can I get some reference for further reading? – Sourav Kannantha B Mar 16 '22 at 13:20
  • 1
    Details are difficult to find, unfortunately. I think your google searches for "project loom implementation" will do as well as mine. – Matt Timmermans Mar 16 '22 at 13:28
  • What about 'C++' implementation? It doesn't need to mark a function as red/blue. But it has keywords like `co_await`, `co_yield`, `co_return`. – Sourav Kannantha B Mar 16 '22 at 13:34
  • 1
    Any function that uses one of those `co_` keywords might have to suspend, and is implemented with option (1). That's the *internal* mark. It must also have a return type that can capture the heap-allocated state, like `task`. That marks a function externally as "suspendable", even though (in typical C++ fashion), you're free to implement it in a different way. C++, unfortunately, cannot use implementations that move around call stack data, because it lets you pass and store pointers to stack-allocated objects. – Matt Timmermans Mar 16 '22 at 14:10