3 ms·
Gor Nishanov's analysis is way too narrow to be used as general language design advice. Windows API fibers are also way too specific of an implementation to gen
by Rusky 3y ago
Gor Nishanov's analysis is way too narrow to be used as general language design advice. Windows API fibers are also way too specific of an implementation to generalize.
Call stacks are just a data structure like any other. That they happen to be managed by the compiler to store things like local variables doesn't change this. Consider that there are algorithmic ways to convert from function calls to this data structure (defunctionalization) and back (refunctionaliztion), and the correspondence between recursive programs and iterative programs using an explicit stack.
The question here is, for any given stack, what requirements does it place on the memory layout and allocation for that stack? Gor's analysis covers a few of these, but not everyone has the same requirements and not every implementation style makes the same tradeoffs.
And you can mix and match these styles within a single programming language! Rust async functions and C++ coroutines both still store much of their local state on the native C stack- they only use their state machines to store local state (including the state machines of their callees) that lives across suspension points. This is what enables Rust to support APIs like spawn_blocking or block_on- you can run different parts of a program using different styles of stacks.
- zozbot234 3y ago> Call stacks are just a data structure like any other. Call stacks are a part of the system API/ABI; your fancy ersatz stacks not so much. This is kind of a key consideration in the above analysis. The whole point of having Windows API support for fibers was to try and obviate that basic pain point, but ultimately this has been shown not to be very successful. > Rust async functions and C++ coroutines ... only use their state machines to store local state ... that lives across suspension points. Yes and this is briefly mentioned in that analysis as a key advantage of stackless coroutines compared to fibers. If you might call a function that requires 500k of stack, a fiber has to provide for that amount of stack space to be available; a stackless coroutine doesn't need to store the 500k of data, provided that it doesn't live across await points.
- Rusky 3y agoFibers can make that exact same optimization by using a separate stack (as a stackless coroutine uses a separate state machine) alongside the C stack. This can be done either through stack switching, or by treating the fiber stack more like an ordinary object without a special reserved ABI register. The reason Windows fibers failed is not because they were stackful, but because they tried to use only one stack at a time.
- zozbot234 3y agoThis would seem to be comparable to segmented stacks - which turn out to be fiddly enough that Golang got rid of them. And they're said to be even less feasible in languages that don't use GC and can't adjust pointers to account for moved data.
- Rusky 3y agoSegmented vs contiguous stacks is a completely orthogonal design choice here. The key is merely that this stack is separate from the C stack but accessible simultaneously. As long as you have that, it can be segmented, or it can be contiguous and movable for growth if you have a GC and/or forbid pointers to locals, or it can be perfectly sized if you do a whole-program analysis on stack usage, or it can be large and non-moving like the C stack.