Gate Go 1.27 SIMD experiments before adding intrinsics

John Burns

A package import can be a more important compatibility boundary than the first vector operation inside it. Go 1.27 ships experimental simd and simd/archsimd packages, but they are not part of an ordinary build. Code that imports either package needs GOEXPERIMENT=simd at build time. If that requirement is left as tribal knowledge, a developer can add an intrinsic locally, CI can build a different program, and a release job can discover the gap only after the source has reached a broader branch.

The useful first gate is deliberately small. Obtain the exact Go toolchain, verify its archive, prove that the experiment’s own package tests are selected with GOEXPERIMENT=simd, and prove that the same package pattern is unavailable without it. This does not benchmark an application or establish that a particular CPU instruction is faster. It establishes the toolchain boundary that a project must make explicit before it accepts architecture-specific code.

The validation described here used Go 1.27.2 on Linux amd64. The official archive SHA-256 check passed. With GOEXPERIMENT=simd, go test simd/archsimd/... passed the public package and its internal SIMD test package. Without the environment variable, the same command exited 1 with matched no packages and no packages to test. That negative result is useful: it shows that a normal Go 1.27 command does not silently opt in to the experiment.

Decide whether simd or archsimd fits the change

Start with the smallest API surface that meets the algorithm’s needs. The portable simd package is intended for operations that can work across supported vector implementations and emulation. It avoids putting a fixed vector width in the source type. That is the safer starting point for an operation that must run on a mixture of processors.

simd/archsimd is different. It exposes architecture-specific vector types and operations. On amd64, for example, it provides types such as Float32x4 and runtime feature checks through archsimd.X86. The package documentation calls it experimental, hardware-specific, and outside the Go 1 compatibility promise. Do not expose those vector types in a public API, store them in a long-lived aggregate, or assume that compiling on an amd64 builder means every deployment CPU supports the same extension.

Use archsimd only when the operation genuinely needs a capability outside the portable interface or when measurement supports the added complexity. A feature check is not decorative. The Go project documents checks such as archsimd.X86.AVX2() and AVX512() because executing an unsupported instruction can terminate a process with an illegal-instruction fault. Keep a scalar or otherwise safe path for machines that do not meet the required capability.

The experiment is also a release-management decision. Go labels these packages experimental, so names, semantics, and generated code can change outside the normal compatibility guarantee. Pin the Go release in CI, keep the experiment narrow, and make an upgrade test part of the change rather than assuming a future toolchain will preserve every intrinsic call.

Verify the toolchain before running it

Use an official release archive and compare it with the checksum published on the Go download page. Do this before extracting or executing the archive. The command below is for Linux amd64 and Go 1.27.2; select the matching archive and checksum when testing another platform or patch release.

work=$(mktemp -d)
cd "$work"

curl --fail --location --remote-name \
  https://go.dev/dl/go1.27.2.linux-amd64.tar.gz
printf '%s  %s\n' \
  'ecbadb99091a3f46e31f5f934b068b1864eafa7995211b39eaddf76996045fe5' \
  go1.27.2.linux-amd64.tar.gz | sha256sum -c -
tar -xzf go1.27.2.linux-amd64.tar.gz
./go/bin/go version

mktemp -d keeps the test archive and extracted toolchain out of an application checkout. It also makes the executable path explicit. An unqualified go command can resolve to a system package, a version manager shim, or a previous CI installation. That ambiguity defeats a test meant to establish a new language or experiment boundary.

A successful run printed:

go1.27.2.linux-amd64.tar.gz: OK
go version go1.27.2 linux/amd64

Keep the archive checksum, exact version line, and test transcript with the upgrade evidence. They identify the binary that selected the experimental packages. They do not prove anything about a different Go patch version or a different CPU architecture.

Make experiment selection a required check

The Go project’s own archsimd test packages provide a compact selection test. Run the package pattern with the environment variable attached to the command, not exported invisibly in a developer shell profile:

GOEXPERIMENT=simd ./go/bin/go test simd/archsimd/...

In the validation run, the decisive lines were:

ok  	simd/archsimd	0.002s
ok  	simd/archsimd/internal/simd_test	4.746s
?   	simd/archsimd/internal/test_helpers	[no test files]

This is a toolchain smoke test, not an application benchmark. Its value is that the command exercises the compiler’s experiment selection and the standard-library test coverage shipped with that exact release. Keep it separate from a repository’s normal go test ./... command so an operator can identify whether a failure belongs to the experiment boundary or to application code.

Now run the same pattern without the environment variable and require a nonzero result:

set +e
./go/bin/go test simd/archsimd/...
status=$?
set -e

if [ "$status" -eq 0 ]; then
  printf '%s\n' 'unexpected: SIMD packages were selected without GOEXPERIMENT=simd' >&2
  exit 1
fi

On the tested Go 1.27.2 toolchain, this produced exit status 1 and the following output:

go: warning: "simd/archsimd/..." matched no packages
no packages to test

Treat that output as an expected boundary, not as an error to suppress globally. A project that adds an import from simd or simd/archsimd should make the environment requirement visible in its CI job, container build, and developer instructions. If one build path sets GOEXPERIMENT=simd and another does not, they are not compiling the same supported source contract.

Add a narrow repository gate

After the toolchain smoke test works, add a focused package test around the planned algorithm. Keep the first fixture small: known input slices, an expected scalar result, and a scalar fallback that is checked on every supported machine. Do not start with a broad rewrite of a parser, codec, or cryptographic routine. Vectorized code can have tail-handling, alignment, aliasing, and feature-detection mistakes that a general build will not reveal.

A shell gate can make the prerequisite explicit before the project suite starts:

#!/usr/bin/env sh
set -eu

: "${GOEXPERIMENT:?set GOEXPERIMENT=simd for this SIMD test job}"
case ",$GOEXPERIMENT," in
  *,simd,*) ;;
  *) printf '%s\n' 'GOEXPERIMENT must include simd' >&2; exit 2 ;;
esac

go version
go test ./internal/vectorcheck

This example intentionally does not set the variable itself. A job that silently adds it can mask a release configuration that omitted it. Set GOEXPERIMENT=simd in the named CI step or its reviewed build definition, then let the script reject an incorrectly configured invocation. Use a dedicated job name such as go-simd-experiment so a failure does not look like a generic unit-test regression.

For archsimd, test the feature-gated path and the fallback separately. The feature-gated test should verify that the vector operation gives the same result as a scalar reference for representative lengths, including a length smaller than one vector and a length with a tail. The fallback test should run without assuming AVX2 or AVX-512 is present. Avoid asserting instruction names in a functional test; inspect generated code only when performance work requires it, and retain correctness tests as the release gate.

Finally, keep the scope honest. Passing the standard-library SIMD tests proves that Go 1.27.2 selected its experiment on this Linux amd64 lab. It does not prove a speedup in an application, validate another processor’s feature set, or guarantee compatibility with the next Go release. The point of the gate is to make those next questions visible before experimental intrinsics become an untracked dependency.

Sources