4 ms·
> I’m actually not sure if Zig’s async design even uses hardware call/return pairs Zig no longer has async in the language (and hasn't for quite some time). Th
by messe 1y ago
> I’m actually not sure if Zig’s async design even uses hardware call/return pairs
Zig no longer has async in the language (and hasn't for quite some time). The OP implemented task switching in user-space.
- loeg 1y agoEven so. You're talking about storing and loading at least ~16 8-byte registers, including the instruction pointer which is essentially a jump. Even to L1 that takes some time; more than a simple function call (jump + pushed return address).
- lukaslalinsky 1y agoOnly stack and instruction pointer are explicitly restored. The rest is handled by the compiler, instead of depending on the C calling convention, it can avoid having things in registers during yield. See this for more details on how stackful coroutines can be made much faster: https://photonlibos.github.io/blog/stackful-coroutine-made-fast https://photonlibos.github.io/blog/stackful-coroutine-made-f...
- messe 1y ago> The rest is handled by the compiler, instead of depending on the C calling convention, it can avoid having things in registers during yield. Yep, the frame pointer as well if you're using it. This is exactly how its implemented in user-space in Zig's WIP std.Io branch green-threading implementation: https://github.com/ziglang/zig/blob/ce704963037fed60a30fd9d40114d0c899feee66/lib/std/Io/IoUring.zig#L607-L783 https://github.com/ziglang/zig/blob/ce704963037fed60a30fd9d4... On ARM64, only fp, sp and pc are explicitly restored; and on x86_64 only rbp, rsp, and rip. For everything else, the compiler is just informed that the registers will be clobbered by the call, so it can optimize allocation to avoid having to save/restore them from the stack when it can.
- flimflamm 1y agoIs this just buttering the cost of switches by crippling the optimization options compiler have?
- lukaslalinsky 1y agoIf this was done the classical C way, you would always have to stack-save a number of registers, even if they are not really needed. The only difference here is that the compiler will do the save for you, in whatever way fits the context best. Sometimes it will stack-save, sometimes it will decide to use a different option. It's always strictly better than explicitly saving/restoring N registers unaware of the context. Keep in mind, that in Zig, the compiler always knows the entire code base. It does not work on object/function boundaries. That leads to better optimizations.
- hawk_ 1y agoThis is amazing to me that you can do this in Zig code directly as opposed to messing with the compiler.
- messe 1y agoTo be fair, this can be done in GNU C as well. Like the Zig implementation, you'd still have to use inline assembly.
- hawk_ 1y ago> If this was done the classical C way, you would always have to stack-save a number of registers I see, so you're saying that GCC can be coaxed into gathering only the relevant registers to stack and unstack not blindly do all of them?
- messe 1y agoYes, you write inline assembly that saves the frame pointer, stack pointer, and instruction pointer to the stack, and list every other register as a clobber. GCC will know which ones its using at the call-site (assuming the function gets inlined; this is more likely in Zig due to its single unit of compilation model), and save those to the stack. If it doesn't get inlined, it'll be treated as any other C function and only save the ones needed to be preserved by the target ABI.
- ori_b 1y agoWhich, with store forwarding, can be shockingly cheap. You may not actually be hitting L1, and if you are, you're probably not hitting it synchronously. https://easyperf.net/blog/2018/03/09/Store-forwarding https://easyperf.net/blog/2018/03/09/Store-forwarding and, section 15.10 of https://www.agner.org/optimize/microarchitecture.pdf https://www.agner.org/optimize/microarchitecture.pdf
- loeg 1y agoAre you talking about context switching every handful of cycles? This is going to be extremely inefficient even with store forwarding.
- ori_b 1y agoSure, and so is calling a function every handful of cycles. That's a big part of why compilers inline. Either you're context switching often enough that store forwarding helps, or you're not spending a lot of time context switching. Either way, I would expect that you aren't waiting on L1: you put the write into a queue and move on.