Second Draft: Coroutines

Richard (Rikki) Andrew Cattermole richard at cattermole.co.nz
Sat Jan 25 13:41:24 UTC 2025


On 25/01/2025 11:38 PM, Sebastiaan Koppe wrote:
> On Friday, 24 January 2025 at 23:22:19 UTC, Richard (Rikki) Andrew 
> Cattermole wrote:
>> On 25/01/2025 9:56 AM, Sebastiaan Koppe wrote:
>>> On Thursday, 23 January 2025 at 23:09:42 UTC, Richard (Rikki) Andrew 
>>> Cattermole wrote:
>>>>
>>>> On 24/01/2025 10:17 AM, Sebastiaan Koppe wrote:
>>>>> On Thursday, 23 January 2025 at 20:37:59 UTC, Richard (Rikki) 
>>>>> Andrew Cattermole wrote:
>>>>> You don't need to describe how scheduling works, just the mechanism 
>>>>> by which a scheduler gets notified when a coroutine is ready for 
>>>>> resumption.
>>>>>
>>>>> Rust has a Waker, C++ has the await_suspend function, etc.
>>>>
>>>> Are you wanting this snippet?
>>>
>>> No, not specifically. I am requesting the DIP to clarify the 
>>> mechanism by which a scheduler is notified when a coroutine is ready 
>>> for resumption, not the specific scheduling itself.
>>
>> If it doesn't work for you, do it a different way. The language has no 
>> inbuilt knowledge of any of these types. It determines everything that 
>> it needs from the operator overload and the core.attributes attributes.
> 
> Well, then the DIP needs to be more explicit that the compiler is merely 
> doing the code transformation, that the created a coroutine frame needs 
> to be driven completely by library code, and that the types that are 
> awaited on are opaque to the compiler and simply passed along to library 
> code.

It is in there.

"The language feature must not require a specific library to be used 
with it."

But, you want it to be stated again some place, will do.

I am very happy that we have got this resolved.

>>> It seems that in your design coroutines are only able to wait for 
>>> other coroutines. This means you need to model async operations as 
>>> coroutines in order to suspend on them.
>>
>> It should be coroutines, but I left out the filtering for the type 
>> that the ``await`` statement will accept. It'll chuck whatever you 
>> want into the sumtype value. Its your job to filter it. If you want to 
>> support other types and behaviors go for it!
> 
> The name `GenericCoroutine` suggests there is type erasure, but if the 
> library driving the coroutine can work with the direct types that are 
> awaited on, that would work.
> 
> As an optimisation possibility it would be good if the coroutine frame 
> could have some storage space for async operations, which would allow us 
> to eliminate some heap allocations. The easiest way to support that is 
> by having the compiler call a predefined function on the object in the 
> await expression (say `getAwaiter`), whose returned object would be 
> stored in the coroutine frame. This offers quite a bit of flexibility 
> for library authors without putting any burden on the user.

My main concern is it'll result in stack memory escaping.

We may want to limit that with an attribute, but that is an open problem 
that isn't going to limit us for the time being.

> In the Fiber support in my Sender/Receiver library there is only one 
> single allocation per yield point. Would be good if we can get at least 
> as few allocations.

The best way to handle that is one allocation (at CT) for the descriptor 
that you can instantiate coroutines form.

Then one big allocation for all the different structs involved.

Could use a free list to optimize that a bit.

Some interesting possibilities here for someone that cares.

>> This is the C# approach.
>>
>> https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/ 
>> language-specification/expressions#1298-await-expressions
> 
>  From that link:
> 
> "The operand of an await_expression is called the task. It represents an 
> asynchronous operation that may or may not be complete at the time the 
> await_expression is evaluated. The purpose of the await operator is to 
> suspend execution of the enclosing async function until the awaited task 
> is complete, and then obtain its outcome."
> 
> Note that `task` is a way better name than `Future`.

It can be, I intentionally tried to conflate a promise and a coroutine 
into a single object.

There is a bunch of fairly standard names for this stuff, whatever I 
picked people would have opinions on and since its my stuff, I can 
disregard them. PhobosV3 would need to be argued about.

> And:
> 
> "The task of an await_expression is required to be awaitable.
> An expression t is awaitable if one of the following holds:
> [...]
> - t has an accessible instance or extension method called GetAwaiter
> [...]
> The purpose of the GetAwaiter method is to obtain an awaiter for the task.
> [...]
> The purpose of the INotifyCompletion.OnCompleted method is to sign up a 
> “continuation” to the task; i.e., a delegate (of type System.Action) 
> that will be invoked once the task is complete."
> 
> You see? It defines the mechanism by which to resume an awaitable.
> 
>> In saying all that, I find the dependency approach to be very 
>> intuitive, and I was able to implement it purely off of first 
>> principles. Whereas the other approaches including C++ is still after 
>> much reading not in my mental model.
> 
> It is hard for me to see if there are any shortcomings at this point.
> Is there an dmd implemention I could try to integrate with?

There is no language implementation currently, only my library (which 
hasn't made it to branch tables just yet, and I'll wait for language 
support before hand).

Sadly it is not priority to implement this year even if it is accepted, 
stuff like escape analysis is up this year.

>> > For one it allows the object you are awaiting
>> > on to control the continuation directly.
>>
>> If you want that for your library to look at the ``waitingOn`` 
>> variable for control over scheduling, go for it! Nothing in the DIP 
>> currently should stop you from doing that.
>>
>> [...]
>>
>> The language defines none of this _on purpose_.
> 
> As mentioned above, this needs to be made more clear in the DIP.
> 
> One possible challenge with this flexibility is whether it isn't too 
> flexible. It is not uncommon to have multiple eventloops in a program, 
> potentionally coming from distinct libraries. Without a common mechanism 
> to resume awaitables from each it might result in incompatibility galore.

I fear the opposite, that any attempted typed vtable to merge 
implementations is going to have minimal use and for all intents and 
purposes they will each be too specialized for their use case to make it 
worth adding.

Consider vibe.d a lot of projects are derived from it, and they use its 
abstractions.

In PhobosV3 is meant to take on the role of a correct event based 
library, so the hope is it'll be a root. Most likely there would also be 
one focused on speed.

Do you really want the correct-but-slower design to be used as part of 
the fast-with-assumptions implementation?



More information about the dip.development mailing list