| # The Go SIMD code generators |
| |
| This directory contains the code generators for Go's SIMD support, along with |
| the libraries and debugging tools they are built from. It is a separate module |
| from the main Go tree. `main.go` is the driver that runs all the generators, or |
| they can be run individually. |
| |
| We generate for four **targets**: amd64, arm64 (NEON), arm64 SVE, and wasm. SVE |
| is not a GOARCH — it is an arm64 feature with its own generated files, its own |
| scalable vector types, and its own simdgen invocation. It behaves like a |
| separate target throughout, so "four targets" always means these four. |
| |
| ## The packages we generate |
| |
| The Go SIMD API lives under `$GOROOT/src/simd`: |
| |
| | Path | What it is | |
| |---|---| |
| | `simd/` | Portable package. Length-agnostic vector types only (`Int8s`, `Float32s`, `Mask8s`, …). Works on every platform. | |
| | `simd/archsimd/` | Platform package. Fixed-width types (`Int8x16` … `Float64x8`) plus scalable types on SVE. Contents vary by GOARCH. | |
| | `simd/internal/spec/` | The executable specification. Buildable Go, but not meant to be called directly. | |
| | `simd/internal/simdref/` | Reference implementation of the API, generated from spec by `refgen`. | |
| | `simd/internal/bridge/` | Glue between `simd` and `archsimd`. Generated by `midway`, not user-visible. | |
| |
| ## The "one meaning" rule |
| |
| Both `simd` and `archsimd` follow a single rule, which is the hardest-won |
| property of the whole project: |
| |
| > **One name means one thing.** A name need not exist on every platform or in |
| > both packages, but wherever it does exist it must denote the same operation, |
| > with the same signature and the same semantics. |
| |
| A method must mean one thing *across all types* within a category, where the |
| only two categories are vectors and masks. Furthermore, its signature should |
| vary only in minor ways that depend on the receiver (e.g., it may be sensitive |
| to the number of lanes). `simd` and `archsimd` even have exactly the same |
| scalable vector types. On fixed-width architectures, the types differ, but |
| method names, signatures, and meanings still match. |
| |
| Method names shared *between categories* should at least be logically related. |
| For example, `Mask8x16.ToBits()` yields a `uint16` bitmap while |
| `Float32x4.ToBits()` reinterprets the bits as `Uint32x4`. Different shapes, |
| plainly related meaning, both correct. |
| |
| One caveat is that implementations of a method may differ in minor, |
| hardware-dependent ways, such as floating point rounding and treatment of |
| out-of-range conversions. The allowed wiggle room is not yet well-defined. |
| |
| Historically the rule was maintained by |
| [`apisum`](https://github.com/aclements/go-misc/tree/master/simdapi/cmd/apisum), |
| which audits after the fact. `spec` is the same idea in reverse — consistent by |
| construction rather than by observation — and it doubles as documentation and a |
| behavioral oracle. |
| |
| ## The intersection corollary |
| |
| The `simd` package API is the intersection of the `archsimd` package APIs across |
| all architectures, after generalizing fixed-width to scalable width, plus a few |
| methods for querying emulation behavior. This is only possible because of the |
| "one meaning" rule. |
| |
| Another consequence of this: if we want to add an operation to `simd`, we do so |
| by ensuring it's implemented in `archsimd` across all architectures, clearly |
| documenting where it's an emulation in the `archsimd` package. |
| |
| The key constraint of the `simd` surface is that every operation must be either |
| supported natively or efficient to emulate on every architecture. In general, we |
| follow the Highway project's lead on what operations can be universally |
| efficient. |
| |
| ## The generators |
| |
| | Directory | Role | |
| |---|---| |
| | `simdgen/` | The big one. Unification-based. Produces the amd64/arm64/SVE API *and* the compiler backend. | |
| | `tmplgen/` | Template expansion for slice ops, compares, mask merges, conversions, and testing boilerplate. | |
| | `wasmgen/` | The wasm API. | |
| | `midway/` | Computes the intersection of methods across architectures to produce the portable `simd` package's declarations. | |
| | `specgen/` | Interprets `internal/spec` into a concrete API description. | |
| | `specgen/specexpr/` | Constraint solver over vector shapes. | |
| | `unify/` | The unification engine simdgen is built on. | |
| | `gentools/` | General shared file-writing utility: `-w`, `-diff`, `-txtar`, `-goroot`. Used by every generator. | |
| | `sgutil/` | Assorted helpers specific to these generators: path resolution, ordered maps, natural sort. | |
| | `cmd/refgen/` | Generates `internal/simdref` from spec. | |
| | `cmd/specls/` | Not a generator: prints the API that spec expands to. The main debugging tool for spec. | |
| |
| The best way to see what files in the simd packages are generated and by what |
| tools is `grep -r '^// Code generated by'`. In addition, simdgen generates the |
| compiler's `ssa/_gen/simd*ops.go`, `simd*.rules`, and |
| `ssagen/simd*intrinsics.go` files. |
| |
| ## How simdgen works |
| |
| simdgen unifies four inputs, decodes the unified results into `Operation` |
| values, performs several canonicalization and update steps, then runs several |
| emitters over the final set of `Operation`s. |
| |
| | Input | Content | |
| |---|---| |
| | Platform loader (`xed.go`, `arm64/`, `sve/`) | Machine instructions parsed from ISA tables into `simdgen` defs | |
| | `types.yaml` | Vocabulary of legal types as `simdgen` defs | |
| | `ops/*/categories.yaml` | Platform-independent API defs: name, doc, commutativity | |
| | `ops/*/go_GOARCH.yaml` | Glue between the ISA defs and Go API: `go:` (often a regexp), `asm:` pattern, operand patterns, and a long tail of quirk fields | |
| |
| Two structural facts that are easy to miss: |
| |
| - `!import ops/*/categories.yaml` produces a **single global sum**. The |
| `ops/AddSub/`, `ops/Moves/` directory structure is cosmetic. Nothing scopes a |
| directory's category entries to that directory's arch entries; everything |
| cross-joins with everything. |
| |
| - `unify.Def` treats any absent field as `Top`, so **there is no schema**. Any |
| invented or misspelled key is silently accepted and silently inert. Several |
| keys in the checked-in YAML are dead weight for exactly this reason. |
| |
| The external instruction data is not checked in. Use `fetch-xed.sh` and |
| `fetch-arm64.sh`, or point `-xedPath` / `-arm64Path` (or `$XEDPATH` / |
| `$ARM64_ISA_PATH`) at existing copies. |
| |
| ## What spec and specgen are |
| |
| `internal/spec` describes operations as generic Go functions: |
| |
| ```go |
| // Add adds corresponding elements of two vectors. |
| // |
| // z[i] = x[i] + y[i] |
| func Add[E Nums, W Width](x, y Vec[E, W]) (z Vec[E, W]) { |
| return map2[E, W, E, W](x, y, func(x, y E) E { return x + y }) |
| } |
| ``` |
| |
| `specgen.Load(dir string, opts *LoadOptions)` returns `([]*specgen.Func, |
| error)`. Each `Func` is a **fully instantiated, concrete** API signature. |
| `Recv`, `In`, and `Out` are `specgen.Arg` values — a parameter *name* plus a |
| `specexpr.Type` — so a `Func` carries argument names as well as types, which |
| matters to anything comparing signatures. There is also `Name` and `Doc`. Read |
| `simd/internal/spec/doc.go` for the `//specgen:name`, `//specgen:require`, and |
| `{{.var}}` doc-template mechanisms. |
| |
| Two properties are worth knowing before you build anything on specgen: |
| |
| 1. **`Func.Name` is already the API name.** `//specgen:name` is resolved inside |
| specgen, so consumers see `ToBits` and `ReshapeToUint32s`, never the |
| underlying spec function names. Matching a concrete API declaration to a spec |
| function is a plain `(receiver type name, method name)` lookup. No mapping |
| table is needed anywhere. |
| |
| 2. **`Func.Decl()` already composes doc + signature**, so anything that emits or |
| compares declarations should build on it rather than reimplementing it. |
| |
| ## Working in this tree |
| |
| The `_gen` module requires at least the Go version listed in its `go.mod`. A |
| working Go dev tree or an installed Go system at that version is required. |
| |
| Every generator supports `-w` (write into the tree), `-diff` (compare against |
| the tree and print unified diffs), and `-txtar` (dump generated files to stdout; |
| the default). **Use `-diff` constantly.** The generated files are checked in, so |
| byte-for-byte comparison against them is available as a regression test for any |
| change you make — including refactors that are supposed to change nothing. |
| |
| Be sure to include changes to generated outputs in the same commit that changes |
| the generator. This makes it easy to review the effects of the generator change. |
| |
| Useful one-liners: |
| |
| ```sh |
| go run ./cmd/specls # the API spec currently expands to |
| go run ./cmd/specls -f Add # just one spec function |
| go run ./cmd/specls -f Add -trace # why a spec function expands the way it does |
| go run ./cmd/refgen -diff # is internal/simdref current? |
| go run . -tools tmplgen -diff # run one generator via the driver |
| ``` |
| |
| ## Reading order for someone new |
| |
| Working on spec? Check out: |
| |
| 1. `simd/internal/spec/doc.go` — the spec language: `Vec`, `Width`, |
| `//specgen:name`, `//specgen:require`, doc templates. |
| 2. `simd/internal/spec/math.go`, `loadstore.go`, `masks.go` — worked examples. |
| 3. `specgen/api.go` — `Func`, `Signature()`, `Decl()`, `SpecFunc()`. |
| 4. `specgen/specexpr/solver.go` — the shape constraint language, documented in |
| the package comment. |
| 5. `cmd/refgen/main.go` — a simple consumer of `specgen.Load`, and a model for |
| how to walk `[]*Func`. |
| |
| Working on simdgen? Check out: |
| |
| 1. `simdgen/ops/AddSub/*.yaml` — unifier inputs for core math operations. |
| 2. `simdgen/godefs.go` and `simdgen/types/operation.go` — the `Operation` model. |