Midway: SIMD Source-to-Source Rewriter
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.
Overview
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 on midway.MaxVectorSize(). The build tag is updated to !midway so 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 abstract simd types are replaced with concrete archsimd types (e.g., simd.Int32s becomes archsimd.Int32x4 for 128-bit, archsimd.Int32x8 for 256-bit).
Installation
go install github.com/dr2chase/midway/cmd/midway
Usage
Run 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/midway
Flags
arch: 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 the archsimd package path (default: "simd").
-midway string: Package path for midway helpers (default: "github.com/dr2chase/midway/midway").
Example
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,256
Output:
-
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 using archsimd.Int32x8.
Development
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.
Known issues
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.