Some notes on Go's runtime.KeepAlive() function

May 25, 2018

I was recently reading go101's "Type-Unsafe Pointers" (via) and ran across a usage of an interesting new runtime package function, runtime.KeepAlive(). I was initially puzzled by how it was used, and then me being me I had to poke into how it worked.

What runtime.KeepAlive() does is that it keeps a variable 'alive', which means that it (and what it refers to) will not be garbage collected and any finalizers it has won't be run. The documentation has an example of its use. My initial confusion was why the use of runtime.KeepAlive() was so late in the code; I had sort of expected it to be used early, like finalizers are set, but then I realized what it is really doing. In short, runtime.KeepAlive() is using the variable. A variable is obviously alive right up to the end of its last use, so if you use a variable late, Go must keep it alive all the way there.

At one level, there's nothing magical about runtime.KeepAlive; any use of the variable would do to keep it alive. At another level there is an important bit of magic about runtime.KeepAlive, which is that Go guarantees that this use of your variable will not be cleverly optimized away because the compiler can see that nothing actually really depends on your 'use'. There are various other ways of using a variable, but even reasonably clever ones are vulnerable to compiler optimization and aggressively clever ones have the downside that they may accidentally defeat Go's reasonably clever escape analysis, forcing what would otherwise be a local stack variable to be allocated on the heap instead.

The other special magic trick in runtime.KeepAlive() is in how it's implemented, which is that it doesn't actually do anything. In particular, it doesn't make a function call. Instead, much like unsafe.Pointer, it's a compiler builtin, set up in ssa.go. When your code uses runtime.KeepAlive(), the Go compiler just sets up a OpKeepAlive SSA thing and then the rest of the compiler knows that this is a use of the variable and keeps it alive through to that point.

(Reading this ssa.go initialization function was interesting. Unsurprisingly, it turns out that there are a number of nominal package function calls that are mapped directly to instructions that will be placed inline in your code, like math.Sqrt. Some of these are platform-dependent, including a bunch of math.bits.)

That runtime.KeepAlive is special magic has one direct consequence, which is that you can't take its address. If you try, Go will report:

./tst.go:20:22: cannot take the address of runtime.KeepAlive

I don't know if Go will too-cleverly optimize away a function that only exists to call runtime.KeepAlive, but hopefully you're never going to need to call runtime.KeepAlive indirectly.

PS: Although it's tempting to say that one should never need to call runtime.KeepAlive on a stack allocated local variable (including arguments) because the stack isn't cleaned up until the function returns, I think that this is a dangerous assumption. The compiler could be sufficiently clever to either reuse a stack slot for two different variables with non-overlapping lifetimes or simply tell garbage collection that it's done with something (for example by overwriting the pointer to the object with nil).

Written on 25 May 2018.
« There's real reasons for Linux to replace ifconfig, netstat, et al
Most modern web spiders are parasites »

Page tools: View Source, Add Comment.
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Fri May 25 22:43:42 2018
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.