Go

What is bounds check elimination (BCE), how do you see it, and how can you help the compiler?

Question 390HardGo 1.22 to 1.25

Go is memory safe: every s[i] gets a runtime check that panics if i is out of range. The SSA backend's prove pass removes checks it can prove redundant. In tight loops (codecs, hashing, parsers), the remaining checks cost compare-and-branch instructions, add code size, and can prevent optimizations such as combining adjacent byte loads into one wide load (Go's compiler does not auto-vectorize, so this is about scalar code quality).

go build -gcflags='-d=ssa/check_bce/debug=1' ./...
# ./enc.go:12:9: Found IsInBounds
// 4 bounds checks: the compiler can't assume len(b) >= 4
func load32Slow(b []byte) uint32 {
    return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
}

// 1 bounds check: the early hint proves the rest safe
func load32(b []byte) uint32 {
    _ = b[3] // bounds check hint
    return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
}

// Loop patterns the prover understands
func sum(s []int) int {
    total := 0
    for i := range s { // i in [0, len(s)): no check on s[i]
        total += s[i]
    }
    return total
}

func addInto(dst, src []int) {
    dst = dst[:len(src)] // one check here; then dst[i] and src[i] are free
    for i := range src {
        dst[i] += src[i]
    }
}

This is exactly what encoding/binary.LittleEndian.Uint32 does. Gotchas: only bother after a profile shows the loop is hot; reslicing tricks can make code less readable; -B disables all bounds checks and should never ship. Iterating with for i := range s is both idiomatic and BCE-friendly.

More on Performance, Profiling & Testing

All 38 Performance, Profiling & Testing questions