実験的なsimd/archsimdパッケージがGo 1.27でAMD64向けAPIを改定しつつarm64(Neon)とWebAssemblyの128ビットSIMDに対応した
Experimental simd/archsimd package
概要
Go 1.26でGOEXPERIMENT=simdによって導入された実験的なsimd/archsimdパッケージが、Go 1.27で対応アーキテクチャを拡大した。従来のAMD64向けAPIを改定した上で、arm64の128ビット「Neon」SIMDとWebAssemblyの128ビットSIMDへの対応が追加された。いずれもビルド時にGOEXPERIMENT=simd環境変数を設定することで有効になる、まだ安定版とはみなされていない実験的なAPIである。
導入経緯
このパッケージは、アセンブリを書かずにGoからSIMD命令へアクセスする手段を提供する提案(golang/go#73787)に基づく。手書きアセンブリでのSIMD利用には「書くのが難しい」「非同期プリエンプションを妨げる」「小さなカーネルのインライン化を妨げる」という課題があり、それを解決するために提案された。設計方針として、命令に近い低レベルのアーキテクチャ固有APIと、将来追加予定の高レベルな移植可能APIの「二層アプローチ」が採用されている。本Itemが対象とするのは前者の低レベルAPIである。
議論タイムライン
- 2025-08-20:
dev.simdブランチでAMD64向け低レベルSIMDパッケージの試験実装が公開された。 - 2025-12-16: Go 1.26 RC1で利用可能になり、パッケージ名が
simdからsimd/archsimdに変更された。 - 2026-01-15: Go 1.26 RC2でいくつかのAPI変更を伴うバグ修正が行われた。
- 2026-02-11: Go 1.26でRC2からの軽微なバグ修正を経て正式にリリースされた。
- 2026-06-23: Go 1.27 RC1で、ポータブルな
simdパッケージ(golang/go#78902)の実験的サポート、AMD64向けarchsimd APIの更新、Wasm/ARM64向けarchsimdサポートが追加された。AMD64向けAPIには破壊的変更が含まれる。
議論のハイライト
- 名前空間の設計を巡る議論: 型ごとの名前(
Uint32x4など)にするか、レーン幅ごとの汎用型(Vec128など)にするかで議論があった。提案者は型安全性を重視し、型ごとの明示的な名前を採用する方針を維持した。 - 命令名 vs 意味のある名前: Ian Lance Taylorらは、Goのアセンブラで採用した「アーキテクチャ横断で共通の命令名を使う」方針が専門家にとってはむしろ分かりにくいと指摘し、命令名をそのまま使うべきだという意見を述べた。一方で提案者は、コードの「書き手」より「読み手」を重視し、意味のある名前を維持する方針を説明した。
- 移植性への懸念: 低レベルAPIを単一パッケージにまとめる「best-effort portability」の方針に対し、
syscallパッケージがOS別パッケージに分割された経緯を引き合いに、アーキテクチャ別パッケージに分けるべきという反論があった。 - CPU機能検出:
golang.org/x/sys/cpuとの重複を指摘する声があったが、コンパイラが値を定数として扱えるようにするため、simd/archsimdパッケージ内に専用の機能検出関数(X86.AVX()など)を持たせる設計となった。 - 実装が進むにつれ、
GetLo/GetHi、IsZero(VPTEST)、ClearAVXUpperBits(VZEROUPPER)などのAPI追加や、構造体スピルに関するコンパイラのバグ修正が活発に行われた。 - 2026-06-23時点のissue更新では、Go 1.27でAMD64 API改定に加え、arm64・Wasm対応が追加されたことと、AMD64側に破壊的なAPI変更が含まれることが明記されている。
使用例
Before
package main
import "fmt"
func addInt32(x, y []int32) []int32 {
out := make([]int32, len(x))
for i := range x {
out[i] = x[i] + y[i]
}
return out
}
func main() {
x := []int32{1, 2, 3, 4}
y := []int32{10, 20, 30, 40}
fmt.Println(addInt32(x, y))
}
After
//go:build goexperiment.simd
package main
import (
"fmt"
"simd/archsimd"
)
func main() {
if !archsimd.X86.AVX() {
fmt.Println("AVX unavailable")
return
}
x := archsimd.LoadInt32x4([]int32{1, 2, 3, 4})
y := archsimd.LoadInt32x4([]int32{10, 20, 30, 40})
sum := x.Add(y)
out := make([]int32, sum.Len())
sum.Store(out)
fmt.Println(out)
}
移行時の注意
simd/archsimdはAMD64向けにAPIの破壊的変更を含んでいるため、Go 1.26のGOEXPERIMENT=simdビルドで既にこのパッケージを使っていたコードは、Go 1.27へ更新する際にコンパイルが通らなくなる可能性がある。また、APIは依然として実験的であり、Go 1の互換性保証の対象外である点に変わりはない。
実装解説
Go 1.27時点のソースツリー(src/simd/archsimd)を確認すると、アーキテクチャごとに生成方法が異なる。AMD64向けの型・演算・CPU機能検出はsimdgen(types_amd64.go、cpu.go)で、arm64向けも同じsimdgen(arm64向けデータソースから、types_arm64.go)で生成されている一方、WebAssembly向けは別ツールのwasmgen(types_wasm.go)で生成されている(generate.goにあるgo:generate行に両方のツールが列挙されている)。
CPU機能検出はアーキテクチャごとに分離されている。AMD64向けのX86Features(cpu.go)はAVX/AVX2/AVX512系列など多数のフィーチャーフラグをinternal/cpu経由で提供するのに対し、arm64向けのARM64Features(cpu_other.go)は現時点でPMULLのみを公開しており、Neonの基本演算自体はCPU機能チェックなしに使える設計になっている(例えばops_arm64.goのInt8x16.AddはCPU Feature: NEONとコメントされているのみで、対応するHas関数は存在しない)。
AMD64のAPI改定と見られる部分として、各ベクタ型に低レベルの配列ベースのLoadXArray/StoreArray(コンパイラ組み込み、例: types_amd64.goのLoadFloat32x4Array/StoreArray)と、それをラップしてスライスを受け取るLoadX/Store(パニックするラッパー、slice_gen_amd64.goのLoadInt32x4/Store)の2段構成になっている。この分離により、配列ポインタを介した低レベルの組み込み命令と、範囲チェック付きの利用しやすいスライスAPIが明確に分かれている。