Splittable and unsplittable functions

Posted on Aug 13, 2024

As I described in Preemption in Go: an introduction, function prologues contain a stack bound check, which triggers a stack growth on failure. The stack guard can be poisoned with a specific value by the runtime to force a goroutine to enter the stack growth algorithm, which can detect the poisoning and give the runtime a chance to preempt the goroutine at a safe-point.

However, there are ways to avoid preemption when needed. One of them is to use proc pinning, as sync.Pool does, but that’s a bit heavy.

Another way is to use the nosplit annotation:

//go:nosplit
//go:noinline
func LoadAcquintptr(ptr *uintptr) uintptr {
	return *ptr
}

Here, LoadAcquintptr has a noinline annotation to prevent inlining. This is to avoid memory reordering that could happen if the load of the pointer was bundled with the code that uses the loaded value. But as it cannot be inlined, it remains a function call with a prologue. The nosplit annotation tells the compiler to leave the function without a stack bound check, which means it won’t be synchronously preemptable, and it won’t grow the stack. This is needed here to ensure atomicity and consistency.

All fields requesting personally identifiable information (PII) are optional. Any personal data you provide will be kept private and is used solely for moderation purposes. Your information is stored securely on a server in Switzerland and will not be used directly, shared, or disclosed to third parties. By submitting your personal data, you consent to its indefinite storage under these terms. Should you wish to have your data removed or have any privacy concerns, please contact me using the information provided in the 'About' section.