agentsclimarketplace

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

Install
npx -y skills add ctoth/golang-skills-plugin --skill go-perf-bounds-check-elimination

Assembled 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_bce build 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++ and for i := range b are 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 free b[i]/b[i+1] — the prover can't bound i below 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 for unsafe to skip a check the flag shows is already gone (try go-perf-simd if 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-intrinsicsmath/bits and microarchitecture levels for hot numeric code.

Marketplace (go-* correctness):

  • go-slices-and-maps — slice-is-a-view, aliasing, 3-index slices, len/cap semantics.
  • go-strings-bytes-runes[]byte/string indexing 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/

Keep looking

Skills are one crate of 326,834. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.