Go 1.26 gave Go an architecture-specific archsimd package. Go 1.27 adds an experimental, fully portable simd package behind GOEXPERIMENT=simd, loosely modeled on Highway for C++. Until now, the only way to reach vector instructions from Go was writing Go assembly — worth it only for the hottest kernels, which meant plenty of software left most of the CPU idle.
The obstruction is not whether a CPU has SIMD, but that SIMD platforms disagree about nearly everything:
- Width — one fixed size on wasm, PowerPC and s390x (128 bits); several fixed sizes on amd64 (128/256/512) and loong64; run-time-only sizes on arm64 SVE (128–2048) and RISC-V V (128–65536).
- Masking — no masks at all on wasm, AVX, AVX2 and NEON (masking is vector bitmasks and boolean ops); dedicated mask registers with one bit per element on AVX512 and RVV; SVE allocates one bit per vector byte.
- Operations — each architecture has its own rearrangement primitives, different crypto instructions, and gaps in the basics: wasm has no vector comparisons for 64-bit integers.
So the portable package is defined by subtraction:
- Fixed-size vectors leave the type system entirely. Types are plural primitives —
simd.Uint8s,simd.Float32s— andLen()is a runtime value. - Only operations in the intersection of all platforms are exposed; holes are filled with emulation in
archsimd, and platforms with no SIMD emulate everything, so code always runs. - Emulation is added where it costs two or three instructions (scalar shift via vector shift; unsigned compare as signed compare plus two XORs), or where the operation matters more than the instruction — carryless multiply is emulated with input-independent run time because crypto depends on it.
- Instead of exposing a primitive like “add pairs”, the plan is to expose sum reduction, which also insulates users from vector-length dependence.
ReduceSumlands next release. - An escape hatch exists:
ToArch()/FromArchplus build tags let one function go platform-specific, with a shared emulation fallback. The post buildsOnesCountthat way.
The compiler work is the interesting half. simd is simultaneously a package, an internal implementation package, and an AST rewrite in the front end: functions, variables and types mentioning simd types are cloned into size-specialized copies against bridge types, with an @simdNNN suffix (128/256/512, or 0 for emulation). Functions that use simd internally but not in their signature become wrappers that switch once on the detected SIMD level. The overhead is hoisted as high as necessary to keep dispatch out of SIMD computation — but no higher, so a gratuitous var _ simd.Uint64s in a benchmark is the documented way to hoist it out of a loop.
GODEBUG=simd=0|128|256|512 forces a level, and the +NNN variants run with unsupported features so you can exercise those paths on your own machine (Raspberry Pi’s NEON without PMULL, Apple Silicon’s amd64 emulation with AVX2 but no VPCLMULQDQ).
The restraint is deliberate: methods are withheld because riscv64, ppc64, s390x and loong64 all have SIMD extensions coming. Go 1.28 targets SVE support, more operations, and “feature variants” so a platform missing one instruction doesn’t fall all the way back to emulation.