# Go Perf Compiler Intrinsics

> Guides Go compiler intrinsics and microarchitecture tuning — the math/bits functions (OnesCount/popcount, LeadingZeros, TrailingZeros, RotateLeft, Mul64) the compiler lowers to single CPU instructions like POPCNT, the GOAMD64 v1-v4 and GOARM64 levels, the Go 1.17+ register calling convention, atomic intrinsics, and interface devirtualization. Fires on "popcount in Go", "use hardware instructions", "GOAMD64 levels", "reduce call overhead". Routes SIMD to go-perf-simd, PGO devirtualization to go-perf-pgo.

- Skill: `ctoth/go-perf-compiler-intrinsics` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add ctoth/go-perf-compiler-intrinsics`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ctoth/go-perf-compiler-intrinsics/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: ctoth (https://skillmd.com/u/ctoth)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ctoth/go-perf-compiler-intrinsics

---


# Go Compiler Intrinsics & Microarchitecture Tuning

An **intrinsic** is a stdlib function the compiler recognizes and replaces with inline machine
instructions instead of a real call. `math/bits` says so: "Functions in this package may be implemented
directly by the compiler, for better performance. For those functions the code in this package will not
be used. Which functions are implemented by the compiler depends on the architecture and the Go
release" ([math/bits](https://pkg.go.dev/math/bits)). The authoritative list lives in the compiler at
**`src/cmd/compile/internal/ssagen/intrinsics.go`** — every claim here is checked against it (Go 1.26).

This skill is for the **proven-hot inner loop, after the algorithm is right** — reach for it last, per
**`go-perf-methodology`**. Wider data-parallel vectorization → **`go-perf-simd`**; profile-guided
devirtualization → **`go-perf-pgo`**. All Go below builds clean under `gofmt`/`go vet` on Go 1.26.

---

## 1. `math/bits` — One Function, One Instruction

`math/bits` "implements bit counting and manipulation functions for the predeclared unsigned integer
types" ([math/bits](https://pkg.go.dev/math/bits)). On amd64/arm64 the hot ones lower to a single
instruction — no call, no loop. Confirmed in `intrinsics.go`:

| `math/bits` function | SSA op | Typical amd64 instr | Source (`intrinsics.go`) |
|---|---|---|---|
| `OnesCount{,8,16,32,64}` (popcount) | `OpPopCount*` | `POPCNT` | L1202-1256 |
| `TrailingZeros{,8,16,32,64}` | `OpCtz*` | `TZCNT`/`BSF` | L934-985 |
| `LeadingZeros{,8,16,32,64}` | via `Len` | `LZCNT`/`BSR` | L1057 ("trivially calls `Len`") |
| `Len{,8,16,32,64}` | `OpBitLen*` | `BSR`+adjust | L1010-1055 |
| `RotateLeft{,8,16,32,64}` | `OpRotateLeft*` | `ROL` | L1083-1103 |
| `Reverse{,8,16,32,64}` | `OpBitRev*` | `RBIT` (arm64) | L1058-1081 |
| `ReverseBytes{,16,32,64}` | `OpBswap*` | `BSWAP` | L986-1008 |

So the rule: **never hand-roll these.** A shift-and-mask loop to count set bits is slower and less
readable than `bits.OnesCount64(x)`, one `POPCNT`. Likewise `bits.TrailingZeros64` for "lowest set
bit," `bits.Len64(x)` for `⌊log2 x⌋+1`, and `bits.RotateLeft64(x, k)` for a rotate (`x<<k | x>>(64-k)`
is *not* a rotate — UB-adjacent for `k==0`/`k>=64` and may not pattern-match). Bit-math correctness →
**`go-naming-and-style`**.

---

## 2. Wide Arithmetic — `Mul64`, `Add64`, `Sub64`, `Div64`

The full-width and carry-aware primitives are intrinsics too, giving the CPU's native 128-bit result
that plain Go operators discard:

- `bits.Mul64(x, y) (hi, lo uint64)` → `OpMul64uhilo`, one `MULQ` for the full 128-bit product (L1258);
  plain `x*y` keeps only the low 64 bits.
- `bits.Add64(x, y, carry)` → `OpAdd64carry` (`ADC`, L1264); `bits.Sub64` → `OpSub64borrow` (`SBB`,
  L1271) — these chain into multi-word (bignum) add/subtract without branching on the carry.
- `bits.Div64(hi, lo, y)` → a `DIVQ`-backed op that "check[s] for divide-by-zero/overflow and panic[s]"
  (L1277, amd64).

`math/big` itself multiplies this way — `intrinsics.go` aliases `math/big.mulWW` to `bits.Mul64`
(L1348). For 128-bit hashing, checksums, or fixed-point scaling these beat manual hi/lo splitting.

---

## 3. GOAMD64 v1–v4 — Raising the Instruction Floor

The compiler emits an intrinsic *only if the target is guaranteed to have the instruction.* `GOAMD64`
(Go 1.18+) picks the microarchitecture floor ([MinimumRequirements](https://go.dev/wiki/MinimumRequirements)):

| Level | Adds over previous (selected) | Default? |
|---|---|---|
| `v1` | baseline; every 64-bit x86 CPU | **yes** |
| `v2` | `POPCNT`, `SSE3`, `SSE4.1/4.2`, `SSSE3`, `CMPXCHG16B` | |
| `v3` | `AVX`, `AVX2`, `BMI1`, `BMI2`, `LZCNT`, `MOVBE`, `FMA`, `F16C` | |
| `v4` | `AVX512F/BW/CD/DQ/VL` | |

The concrete payoff is visible in the source. `bits.OnesCount64` at the **default `v1`** does *not*
emit a bare `POPCNT` — because a baseline x86-64 CPU might lack it, the compiler emits a **runtime
feature check** branching on `X86HasPOPCNT`, falling back to the pure-Go loop (`intrinsics.go` L1107-1135).
At **`GOAMD64=v2` or higher** the same call compiles to a single unconditional `POPCNT` (`if cfg.goamd64 >= 2`,
L1107). `math.FMA` is the same story keyed to `v3` (L786). So for a fleet you know is Haswell-or-newer,
`GOAMD64=v3 go build` removes those runtime checks *and* raises the autovectorization/BMI floor for the
whole binary — a free, deployment-wide win when you control the hardware. The cost: the binary
**SIGILLs on older CPUs**, so only set it when the deployment target is known.

---

## 4. GOARM64 / GOARM / GORISCV64 — the Non-amd64 Equivalents

Same idea, different knobs ([MinimumRequirements](https://go.dev/wiki/MinimumRequirements)):

- **`GOARM64`** (Go 1.23+): `v8.{0-9}` / `v9.{0-5}`, default **`v8.0`**, plus options `,lse` and
  `,crypto`. arm64 intrinsifies the same `math/bits` set unconditionally (`RBIT`/`CLZ` are in the base
  ISA), and `,lse` lets atomics (§6) use single-instruction `LDADD`/`SWP`/`CAS` instead of a
  load-linked/store-conditional loop — gated on `cfg.goarm64.LSE` (`intrinsics.go` L403).
- **`GORISCV64`** (`rv64g` baseline): the `Zbb` extension (`goriscv64 >= 22`) unlocks the direct
  `POPCNT`/`CTZ` lowering, else a feature-check fallback (`intrinsics.go` L1170, L220). **`GOARM`**:
  `5`/`6`/`7` (soft float / VFPv1 / VFPv3) for 32-bit ARM.

---

## 5. The Register-Based Calling Convention (Go 1.17+)

Before Go 1.17 every argument and result went on the stack. Since 1.17 they go in **registers**: the
change "has shown about a 5% performance improvement in Go programs and reduction in binary sizes of
around 2% for amd64 platforms" ([Go 1.17](https://go.dev/blog/go1.17)). Why it matters for this skill:

- **Small functions are cheap now.** A leaf helper taking a few words no longer pays stack
  stores/loads on entry/exit, so the call overhead a register ABI removes is most of what a tiny
  function cost. This is *why* the intrinsics above are worth so little to inline-by-hand — the call
  they replace is already nearly free, and inlining (**`go-perf-inlining`**) erases what remains.
- **Don't reason about call cost with a pre-1.17 mental model.** Stack-spilling args on every call is
  no longer the default; budget call overhead as a handful of register moves, not a frame setup.
  Hand-written assembly must opt in via `//go:registerparams`
  ([Compiler Directives](https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives)). The conclusion:
  small, focused functions are the right default *for performance too*.

---

## 6. Atomic Intrinsics

`sync/atomic`'s `Load*`, `Store*`, `Swap*`, `CompareAndSwap*`, `Add*`, and `And*`/`Or*` are intrinsics:
`intrinsics.go` aliases each to an `internal/runtime/atomic` op (L1299-1345) lowering to a
`LOCK`-prefixed instruction on amd64 (or `LDADD`/`CAS` on arm64 with `,lse`, §4) — no call. Two notes:

- They are **disabled under `-race`** ("Note: these are disabled by flag_race", L1298), so a race build
  measures differently from a production build — don't benchmark atomics with `-race` on.
- `atomic.Int64`/`atomic.Pointer[T]` *methods* compile down to these same ops, so the typed API costs
  nothing over the raw functions.

This skill owns only that atomics *are* intrinsics (no call overhead). **Atomic-vs-mutex cost** →
**`go-perf-atomics-vs-locks`**; **memory-ordering correctness** → **`go-race-and-memory-model`**.

---

## 7. Devirtualization — Direct Calls from Interface Values

An interface method call is an indirect call through the itable; it also **blocks inlining** because
the compiler can't see the callee. When the concrete type is provable at compile time, the compiler
does **static devirtualization**: "Devirtualization converts indirect calls on interface values whose
type can be determined statically into direct calls to the concrete method (which often enables
inlining of the call)" ([PGO blog](https://go.dev/blog/pgo)).

What the *profile* adds is the probabilistic case: PGO "can replace `r.Read(b)` with" a type guard
(`if f, ok := r.(*os.File); ok { f.Read(b) } else { r.Read(b) }`) when profiling shows one concrete
type dominates — "Inlining and devirtualization are the two PGO-driven optimizations available in Go
1.21" ([PGO blog](https://go.dev/blog/pgo)). PGO collection and `default.pgo` lifecycle →
**`go-perf-pgo`**. Takeaway: a hot interface call you can make concrete is cheaper than one left
indirect.

---

## 8. When to Reach for These — and When Not

- **Reach for `math/bits`/wide-arithmetic intrinsics whenever the operation fits**, hot path or not —
  *more* readable than the hand-rolled equivalent and never slower; "use the fast thing" has no clarity
  cost here.
- **Reach for a `GOAMD64`/`GOARM64` level bump only when you control the deployment hardware** and a
  profile shows the code matters — a reversible, fleet-wide build flag.
- **Do NOT** hand-write assembly where an intrinsic exists (§1 covers most bit ops); verify the lowering
  with `go build -gcflags=-S` (look for `POPCNT`/`TZCNT`/`ROL`, not a `CALL`).
- **Do NOT** raise `GOAMD64` speculatively — a `v3` binary `SIGILL`s on a `v1` host; default `v1` is the
  safe floor. And **measure first** (**`go-perf-methodology`**): a `POPCNT` on a cold path moves nothing.

---

## 9. Don't

- **Don't hand-roll popcount/clz/ctz/rotate.** `bits.OnesCount64`, `bits.LeadingZeros64`,
  `bits.TrailingZeros64`, `bits.RotateLeft64` lower to single instructions ([math/bits](https://pkg.go.dev/math/bits); `intrinsics.go`).
- **Don't write `x<<k | x>>(64-k)` for a rotate** — it misbehaves at `k==0`/`k>=64` and may not match;
  use `bits.RotateLeft64`.
- **Don't assume `bits.OnesCount64` is a bare `POPCNT` at the default `GOAMD64=v1`** — it is a
  feature-checked branch until `v2`+ (`intrinsics.go` L1107).
- **Don't set `GOAMD64=v3`/`v4` for binaries that may run on unknown CPUs** — they crash with an illegal
  instruction on older hardware ([MinimumRequirements](https://go.dev/wiki/MinimumRequirements)).
- **Don't reason about call cost with a pre-1.17 stack-ABI model** — args/results are in registers now,
  ~5% faster calls ([Go 1.17](https://go.dev/blog/go1.17)).
- **Don't benchmark atomics under `-race`** — atomic intrinsics are disabled in race builds
  (`intrinsics.go` L1298).
- **Don't keep a hot call interface-typed when the concrete type is knowable** — it blocks
  devirtualization and inlining ([PGO blog](https://go.dev/blog/pgo)).
- **Don't reach here before the algorithm and profile justify it** (**`go-perf-methodology`**).

---

## 10. Routing to Related Skills

**This plugin (`go-perf-*` depth):**

- `go-perf-methodology` — the measure-first gate; intrinsics are a last, proven-hot-loop lever.
- `go-perf-inlining` — mid-stack inlining and the budget; why small register-ABI calls are cheap to inline.
- `go-perf-simd` — wide data-parallel vectorization (the experimental `simd` package, Plan 9 assembly).
- `go-perf-pgo` — collecting `default.pgo`; PGO-driven (probabilistic) devirtualization.
- `go-perf-bounds-check-elimination` — the other "single-instruction loop" lever, paired with §1.
- `go-perf-atomics-vs-locks` — the *cost* tradeoff of the atomic intrinsics in §6.

**Marketplace (`go-*` correctness):**

- `go-naming-and-style` / `go-performance` — bit-twiddling correctness and idiom.
- `go-race-and-memory-model` — happens-before/ordering correctness of atomics.
- `go-version-feature-map` — version floors: register ABI 1.17, GOAMD64 1.18, GOARM64 1.23.

---

## 11. Reference Files

Intrinsics/microarchitecture anti-patterns in LLM-generated Go, with wrong/right contrast and citations:

[references/common-mistakes.md](references/common-mistakes.md)

Source provenance for every claim in this skill:

[references/sources.yaml](references/sources.yaml)

