Go perf bounds check elimination
Skill ctoth/golang-skills-plugin/plugins/performant-golang/skills/go-perf-bounds-check-elimination
Guides bounds-check elimination (BCE) in Go — the compiler checks every slice/array index and the SSA pass removes the ones it proves redundant. Covers seeing surviving checks with go build -gcflags="-d=ssa/check_bce/debug=1", prover-friendly idioms (the _ = b[n-1] / b = b[:n] hint, range loops, constant vs variable offsets), the inlining link, and why -B is a footgun not a fix. Fires on "eliminate bounds checks", "why is this loop slow", "bounds check in hot loop", "optimize this numeric loop". Routes should-I to go-perf-methodology, slices to go-perf-slices.From its SKILL.md
npx -y skills add ctoth/golang-skills-plugin --skill go-perf-bounds-check-eliminationAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
9.0 KB, ~2.4k tokens by cl100k_base, as published. Nobody here has run it
Go Bounds-Check Elimination
Go is memory-safe: every index b[i] and most slice expressions b[:n] carry an implicit runtime
check that panics on an out-of-range access. The SSA compiler runs a bounds-check elimination (BCE)
pass that proves many of those checks redundant and deletes them. This skill owns making that
pass visible and writing loops it can satisfy.
Decide whether to bother first. BCE wins are small — each check is one well-predicted
compare-and-branch, worth chasing only in a numeric/byte loop a profile has already proven hot. The
"should I optimize this at all" judgment routes to go-perf-methodology; slice growth/aliasing
to go-perf-slices and go-slices-and-maps.
All Go below builds clean under gofmt/go vet; the check_bce output shown was produced on Go
1.26 (go version go1.26.0).
1. Seeing the Remaining Checks
The compiler's SSA passes accept debug flags via -gcflags. The BCE pass reports every check it
could not eliminate:
go build -gcflags="-d=ssa/check_bce/debug=1" .
It prints one line per surviving check — silence means a fully eliminated expression:
./bce.go:4:3: Found IsInBounds # an index check b[i] that was NOT eliminated
./bce.go:26:7: Found IsSliceInBounds # a slice-expression check b[:n] that survived
IsInBounds is the index check (b[i]); IsSliceInBounds is the slice-expression check
(b[:n], b[lo:hi]). The flag applies to the package you build; add all= to instrument
dependencies. This output is the only trustworthy evidence — never claim a loop is
bounds-check-free without it. (To see the compare+branch a check costs, dump assembly with
go build -gcflags=-S and find the branch into panicIndex.)
2. What the Prover Already Does for Free
The most important measured fact: the canonical loop needs no help. Both of these emit zero
Found IsInBounds lines on Go 1.26 — the bound from the loop condition flows to the index:
func sumIndex(b []byte) int {
n := 0
for i := 0; i < len(b); i++ {
n += int(b[i]) // i < len(b) proven by the loop guard → check eliminated
}
return n
}
func sumRange(b []byte) int {
n := 0
for _, v := range b { // range never indexes past len → no check at all
n += int(v)
}
return n
}
First rule: don't fight a problem you don't have. Write the obvious
for i := 0; i < len(b); i++ { b[i] } or for i := range b { b[i] }, run the flag, and only reach
for an idiom if a check survives. Adding _ = b[len(b)-1] to these loops changes nothing — they were
already clean.
3. The Hint Idiom — and Exactly How Far It Reaches
Hints matter when you index several positions from one base outside a counted loop. Three writes
to b[i], b[i+1], b[i+2] produce three surviving checks:
func triple(b []byte, i int) {
b[i] = 1 // Found IsInBounds
b[i+1] = 2 // Found IsInBounds
b[i+2] = 3 // Found IsInBounds
}
A single proof up front collapses them. Two forms work — measured to one check total, with the indexed writes free:
func tripleAssertHigh(b []byte) {
_ = b[2] // one IsInBounds here; establishes len(b) > 2
b[0] = 1 // free
b[1] = 2 // free
b[2] = 3 // free
}
func tripleConstSlice(b []byte) {
b = b[:3] // one IsSliceInBounds here
b[0] = 1 // free
b[1] = 2 // free
b[2] = 3 // free
}
The critical, measured caveat: the constant-index trick is what makes that work. With a
variable base it does not fully transfer — because the prover cannot establish i >= 0, so
i and i+1 are not bounded below even after asserting b[i+2]:
func tripleVar(b []byte, i int) {
_ = b[i+2] // Found IsInBounds (the assert itself)
b[i] = 1 // Found IsInBounds — STILL checked (i could be negative)
b[i+1] = 2 // Found IsInBounds — STILL checked
b[i+2] = 3 // free — identical to the asserted expression
}
Asserting b[i+2] only frees the identical expression b[i+2]. The hint is reliable for
constant offsets from a base; for variable offsets, verify with the flag, and prefer
restructuring to a counted loop the prover already handles (§2).
4. Two Slices, One Index
Copying a into b by index checks b[i] every iteration — the range proves a[i] but says
nothing about b:
func copyLoop(a, b []int) {
for i := range a {
b[i] = a[i] // Found IsInBounds on b[i]; a[i] is the range element, free
}
}
Cut b to a's length once before the loop — trading a per-iteration index check for a single
slice check, so the loop body goes clean (measured: one IsSliceInBounds, zero IsInBounds):
func copyLoopBCE(a, b []int) {
b = b[:len(a)] // one IsSliceInBounds; len(b) >= len(a) now known in the loop
for i := range a {
b[i] = a[i] // free
}
}
(For a plain copy use copy(b, a); this pattern is for real per-element work on two slices.)
5. How BCE Interacts with Inlining
A bound proven in a caller reaches an index in a callee only if the callee is inlined — across a
real call boundary the compiler re-checks, since it can't assume the caller's invariant. A tiny
helper func at(b []byte, i int) byte { return b[i] } carries its own check at every call site;
inlining it lets the surrounding loop's proof eliminate it. So inlining is frequently the
precondition for elimination. The budget and //go:noinline are owned by go-perf-inlining —
check there first if a check survives only because a helper didn't inline.
6. Why -B Is a Footgun, Not a Fix
-gcflags=-B disables bounds checking globally — build with it and all Found IsInBounds output
disappears because the checks are simply gone. That is not BCE; it removes the safety net. A program
built with -B will read or write out of bounds silently instead of panicking, turning a clean
panic into memory corruption or a security hole. Use -B for exactly one thing: measuring the
ceiling — build both ways, benchmark, and see how much the surviving checks cost. If the gap is
small (it usually is), leave the checks in. Never ship -B. The same caution applies to unsafe to
dodge a check the compiler would have elided anyway — prove it survives with the flag first.
7. Don't
- Don't claim a loop is bounds-check-free without the flag. Run the
check_bcebuild and read the output — confidence is not evidence. - Don't add hints to a canonical counted/range loop.
for i := 0; i < len(b); i++andfor i := range bare already eliminated on 1.26;_ = b[n-1]there is noise (and a panic on the empty slice). - Don't assume the constant-index hint transfers to variable offsets.
_ = b[i+2]does not freeb[i]/b[i+1]— the prover can't boundibelow zero (§3). - Don't ship
-gcflags=-B— it deletes the safety check, not the redundancy; use it only to measure the ceiling (§6), and don't reach forunsafeto skip a check the flag shows is already gone (trygo-perf-simdif a real win remains). - Don't reach for BCE before a profile proves the loop hot. The win is one compare+branch per
check; route the "is it worth it" to
go-perf-methodology. - Don't re-slice inside the loop — it re-introduces a check every iteration; slice once up front (§4).
8. Routing to Related Skills
This plugin (go-perf-* depth):
go-perf-methodology— the should-I-optimize-this-at-all gate; BCE is a last-mile tweak.go-perf-inlining— why a check survives across a call boundary; inlining as BCE's precondition.go-perf-slices— slice growth, reuse, prealloc; re-slicing cost.go-perf-simd— when scalar BCE isn't enough and the loop wants vectorization.go-perf-compiler-intrinsics—math/bitsand microarchitecture levels for hot numeric code.
Marketplace (go-* correctness):
go-slices-and-maps— slice-is-a-view, aliasing, 3-index slices,len/capsemantics.go-strings-bytes-runes—[]byte/stringindexing correctness.go-performance— the measure-first overview this plugin deepens.
9. Reference Files
Wrong/right BCE anti-patterns from LLM-generated Go, each with measured check_bce output and
citations:
${CLAUDE_SKILL_DIR}/references/common-mistakes.md
Source provenance for every claim in this skill:
${CLAUDE_SKILL_DIR}/references/sources.yaml
What ships with it: 2 files
11.7 KB alongside SKILL.md
references/
- common-mistakes.md7.1 KB
- sources.yaml4.5 KB