Search code examples
gox86-64goroutinecalling-conventioncontext-switch

Why does Golang only save %rsp, %rip and %rbp(no other callee-saved registers) when switch context?


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?


Solution

  • 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.