midway is a CLI tool that automatically generates vector-size-specific
SIMD code from Go written to use a "scalable SIMD" API, provided in
midway/simd/mocks.go. This is a prototype for both an API and for
a compiler transformation, and a step towards a more general solution
for writing SIMD code in Go across architectures and cpu variants with a
variety of vector widths. The intent is that the generated code for
different vector widths (for example, AVX and AVX-512, or ARM SVE's
scalable vector lengths) will all be present in a single binary that
chooses the right implementation at runtime. The tool is not yet
general purpose; it is specialized for the API provided in
midway/simd/mocks.go, which are amd64-specific.
The tool operates on Go files marked with //go:build midway. It performs two main transformations:
- Dispatcher Generation: Rewrites the original file in a new
..._simd_<arch>.go, replacing functions dependent on SIMD types with a switch statement that calls the appropriate architecture-specific implementation based onmidway.MaxVectorSize(). The build tag is updated to!midwayso it compiles as standard Go (and the originals, tagged "midway", are ignored). - Specialization: Generates specialized implementation files (e.g.,
..._simd128_<arch>.go,..._simd256_<arch>.go) where abstractsimdtypes are replaced with concretearchsimdtypes (e.g.,simd.Int32sbecomesarchsimd.Int32x4for 128-bit,archsimd.Int32x8for 256-bit).
go install github.com/dr2chase/midway/cmd/midwayRun the tool on/in a directory containing midway-tagged files:
midway [-dir <directory>] [other options]
go mod tidy # fill in entry for github.com/dr2chase/midway/midwayarch: Comma-separated lists of architectures to rewrite for. Default is"amd64".-dir string: Directory to process (default: current directory".").-a2s string: Comma-separated list mapping architectures to lists of vector sizes (e.g.,"amd64:128,256,512;wasm:128"). Default is"amd64:128,256,512;wasm:128".-prefix string: Prefix for thearchsimdpackage path (default:"simd").-midway string: Package path for midway helpers (default:"github.com/dr2chase/midway/midway").
Input (example.go):
//go:build midway
package example
import "github.com/dr2chase/midway/simd"
func Add(a, b simd.Int32s) simd.Int32s {
return a // Implementation
}Command:
midway -dir . -arch amd64 -sizes amd64:128,256Output:
-
Refactored
example_simd_amd64.go(Dispatcher)://go:build !midway package example import "github.com/dr2chase/midway/midway" func Add(a, b simd.Int32s) simd.Int32s { switch midway.MaxVectorSize() { case 256: return Add_simd256(a, b) case 128: return Add_simd128(a, b) default: panic("unsupported vector size") } }
-
Generated
example_simd128_amd64.go://go:build !midway package example import ( "simd/archsimd" "github.com/dr2chase/midway/midway" ) func Add_simd128(a, b archsimd.Int32x4) archsimd.Int32x4 { midway.Assert128() return a }
-
Generated
example_simd256_amd64.go: Similar to 128-bit, but usingarchsimd.Int32x8.
The project includes test data in testdata/simple
(hand-validated, won't run) and testdata/ip (runs, for GOARCH=amd64
and GOEXPERIMENT=simd) demonstrating various usage patterns,
including:
- Dependent types and aliases.
- Dependent struct fields.
- Global variables.
- Generic functions (instantiated with SIMD types).
- Midway files that are architecture-build tagged.
Top-level initialized SIMD variables probably don't work yet.
Mocks.go is autogenerated from signatures in go1.26/simd/archsimd and there may be unanticipated semantic mismatches.
It would be better to combine the dispatch and specialized code all into a single file; there are no conflicts, and doing otherwise is just messy.