Description
Dead stores are writes to variables that are not followed by corresponding reads. While dead store elimination is mostly a desirable optimization, some of the time it is essential for program correctness that dead store elimination (or the unreachable code elimination that it sometimes implies) does not happen.
The two examples on my mind are:
-
Secure zeroization of sensitive data (also known as cleansing, wiping, etc.), where the store (to computer memory) itself is essential, and it should not legitimately be followed by a read.
-
Benchmarking, where the problem is the elimination of code that is meant to be benchmarked
In C these problems might be solved by using memset_s, volatile types or inline assembly tricks.
In current Go user code, convoluted efforts to prevent dead store elimination for wiping memory and to prevent dead code elimination for benchmarking ultimately rely only on the lack of optimizations in current Go implementations, instead of on formal correctness, which this proposal aims to enable in addition to simplification of user code.
The new built-in "formallyRead" is meant to return nothing and either
-
takes a pointer or an arbitrary number of pointers
-
takes a slice or an arbitrary number of slices
-
if package "unsafe" is imported it additionally can take an unsafe.Pointer and a uintptr (to indicate base and size of a memory object)
The pointer arguments to formallyRead indicate objects that must be considered as having formally been read at the point of the formallyRead invocation. Slice arguments indicate the same for the slice's underlying array in the range up to len(slice).
The following minimal example is meant to show the effects of formallyRead :
package main
func main() {
...
var key [32]byte
// Do cryptographic operations with the key
...
for i := range key {
key[i] = 0
}
// Pretend to read the key so the compiler would not optimize out the preceding loop.
formallyRead(&key) // or formallyRead(key[:])
...
}
The following example showcases the cutting down of benchmark code that formallyRead would enable, because use of sink package level variables is no longer necessary:
var GlobalF float64
func BenchmarkAcos(b *testing.B) {
x := 0.0
for i := 0; i < b.N; i++ {
x = Acos(.5)
}
GlobalF = x
}
turns into:
func BenchmarkAcos(b *testing.B) {
for i := 0; i < b.N; i++ {
x := Acos(.5)
formallyRead(&x)
}
}
See also:
Regarding benchmarking:
Line 3109 in 80f9d32
https://groups.google.com/forum/#!topic/golang-dev/eCPwwvqs2Cg
A more ambitious and ambiguous proposal regarding the zeroization use case:
Discusses some ugly hacks that are done in the name of zeroization: