In general user-thread context switch implementations(like setjmp/longjmp
and the function return
way), we save and restore callee-saved registers, but golang only save and restore %rsp
, %rip
and %rbp
in gobuf.
Take x86_64 as example, golang save the goroutine context with runtime.gosave and restore goroutine context with runtime.gogo.
So why does golang do it in this way?
Apparently GoLang still uses an inefficient calling convention where the only call-preserved (aka non-volatile) registers are RSP and RBP.
A call to runtime.gosave
looks to the compiler like any other function call (i.e. it eventually returns after doing some stuff, and doesn't modify anything above its own stack frame). Like any other function call, the caller has to assume it destroys all the call-clobbered (volatile) registers (everything except RSP and RBP). Thus any values it wants to survive the call have to be spilled to stack slots (or other memory location where they belong).
For the same reason, C setjmp
only has to save the call-preserved registers. And kernel context-switch functions are the same.
This 2017 google groups post says that's how its calling convention / ABI works, and from the linked code it looks like that still hasn't been improved.
Go's calling convention also inefficiently passes all args on the stack, unlike the x86-64 System V ABI which passes the first 6 integer args (and first 8 FP) in registers.