Comment by mlugg

Comment by mlugg 2 days ago

6 replies

We don't know whether or not we'll have stackless coroutines; it's possible that we hit design problems we didn't foresee. However, at this moment, the general consensus is that we are interested in pursuing stackless coroutines.

While Andrew has the final say, as Loris points out, we always work to reach a consensus internally. The article lists this an an implementation that will probably exist, because we agree that it probably will; nobody is promising it, because we also agree that it isn't guaranteed.

Also, bear in mind that even if stackless coroutines don't make it into Zig, you can always use a single-threaded blocking implementation of `Io`, so you need not be negatively affected by any potential downsides to fibers either way.

This new `Io` approach has made it strictly more likely than it previously was that stackless coroutines become a part of Zig's final design.

comex a day ago

But how will that actually work? Your stackless coroutines proposal talks about explicit primitives for defining a coroutine. But what about a function that's not designed for any particular implementation strategy - it just takes an Io and passes it on to some other functions? Will the compiler have a way to compile it as either sync or async, like apparently it did before? It would have to, if you want to avoid function colors. But your proposal doesn't explain anything about that.

Disclaimer: I'm not actually a Zig user, but I am very interested in the design space.

  • mlugg a day ago

    Right, the proposal doesn't discuss the implementation details -- I do apologise if that made it seem a little hand-wavey. I opted not to discuss them there, because they're similar-ish to the way we lowered stackless async in its stage1 implementation, and hence not massively interesting to discuss.

    The idea is that, yes, the compiler will infer whether or not a function is async (in the stackless async sense) based on whether it has any "suspension point", where a suspension point is either: * Usage of `@asyncSuspend` * A call to another async function

    Calls through function pointers (where we typically wouldn't know what we're calling, and hence don't know whether or not it's async!) are handled by a new language feature which has already been accepted; see a comment I left a moment ago [1] for details on that.

    If the compiler infers a function to be async, it will lower it differently; with each suspension point becoming a boundary where any stack-local state is saved to the async frame, as well as an integer indicating where we are in the function, and we jump to different code to be resumed once it finishes. The details of this depend on specifics of the proposal (which I'm planning to change soon) and sometimes melt my brain a little, so I'll leave them unexplained for now, but can probably elaborate on them in the issue thread at some point.

    Of course, this analysis of whether a function is async is a little bit awkward, because it is a whole-program analysis; a change in a leaf function in a little file in a random helper module could introduce asynchronocity which propagates all the way up to your `pub fn main`. As such, we'll probably have different strategies for this inference in the compiler depending on the release mode:

    * In Debug mode, it may be a reasonable strategy to just assume that (almost) all functions are asynchronous (it's safe to lower a synchronous function as asynchronous, just not vice versa). The overhead introduced by the async lowering will probably be fairly minimal in the context of a Debug build, and this will speed up build times by allowing functions to be sent straight to the code generator (like they are today) without having to wait for other functions to be analyzed (and without potentially having to codegen again later if we "guessed wrong").

    * In Release[Fast,Small,Safe] mode, we might hold back code generation until we know for sure, based on the parts of the call graph we have analyzed, whether or not a function is async. Vtables might be a bit of a problem here, since we don't know for sure that a vtable call is not async until we've finished literally all semantic analysis. Perhaps we'll make a guess about whether such functions are async and re-do codegen later if that guess was wrong. Or, in the worst case... perhaps we'll literally just defer all codegen until semantic analysis completes! After all, it's a release build, so you're going to be waiting a while for optimizations anyway; you won't mind an extra couple of seconds on delayed codegen.

    [1]: https://news.ycombinator.com/item?id=44549131

    • Icathian a day ago

      > a change in a leaf function in a little file in a random helper module could introduce asynchronocity which propagates all the way up to your `pub fn main`

      If this doesn't make the argument that Zig has certainly not defeated function coloring, I don't know what would.

      The fact that this change to how my program runs is hidden away in the compiler instead of somewhere visible is not an improvement.

      • kristoff_it a day ago

        > If this doesn't make the argument that Zig has certainly not defeated function coloring, I don't know what would.

        if you're opting into stackless coroutines then yeah you're opting into their viral nature, but the point is that you don't have to. as the application author your dependencies won't opt you forcefully in using stackless coroutines (or any singular execution model), which is currently the case with other languages.

        this is what it means to defeat function coloring.

        • hitekker 18 hours ago

          Small marketing suggestion: maybe "limit function coloring" instead of "defeat function coloring". I like Zig's approach so far, but clearer terms would help avoid pointless arguments and disappointments that a certain proglang supremacist community loves.

    • SkiFire13 a day ago

      > it's safe to lower a synchronous function as asynchronous

      Is it though? I believe this would be an issue if you want to pass that function as a function pointer to an FFI function, in which case it must be sync.