| // Copyright 2026 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| // Package spec describes all possible operations in the SIMD API. | |
| // | |
| // The SIMD spec describes the function and method signatures, documentation | |
| // comments, and behavior (written as a reference Go implementation) of all | |
| // possible Go SIMD APIs. The archsimd and simd packages are subsets of this | |
| // specified API. This approach enforces that "one name means one thing" across | |
| // all platforms and both packages. | |
| // | |
| // The spec is written as buildable and executable Go code, but isn't meant to | |
| // be called directly. Rather, the specgen package interprets this spec package | |
| // into a full API description, which can then be fed into other generators for | |
| // the actual SIMD packages. The executable part of the spec serves to precisely | |
| // specify the semantics of operations, and is intended for conformance testing. | |
| // | |
| // To see the spec-generated API and debug issues with it, use [cmd/specls]. | |
| // | |
| // ## Basic operation specifications | |
| // | |
| // Spec operations are written in a stylized form that makes heavy use of type | |
| // parameters so a single function can describe an operation generalized across | |
| // many vector types. This is in contrast with the public SIMD API, where every | |
| // function and method operates on concrete types. The specgen generator bridges | |
| // this gap, instantiating a single parameterized spec function into many | |
| // concrete methods. | |
| // | |
| // Consider a simple example, the Add operation: | |
| // | |
| // // Add adds corresponding elements of two vectors. | |
| // func Add[E Nums, W Width](x, y Vec[E, W]) (z Vec[E, W]) { | |
| // ... | |
| // } | |
| // | |
| // All spec operations are written as functions, but if the first parameter has | |
| // type Vec, then they specify a method of a vector type. Since Add's first | |
| // parameter (x) is a Vec, this describes a method on vector types. | |
| // | |
| // The "E Nums" type parameter controls the allowed element types of the three | |
| // vector types. Here, it can be any numeric type of any size (uint8, float64, | |
| // etc). The "W Width" type parameter controls the total bit width of the three | |
| // vector types. The types that implement Width stand in for 128, 256, or 512 | |
| // bits, or "scalable", which can represent any power of two >= 128. The number | |
| // of lanes of a vector is derived from the element size and the total vector | |
| // width. | |
| // | |
| // The Add spec expands to all possible types that satisfy the E and W type | |
| // parameters, which are in turn translated to types in the public API: | |
| // | |
| // func (Int8x16) Add(Int8x16) Int8x16 | |
| // func (Int8x32) Add(Int8x32) Int8x32 | |
| // func (Int8x64) Add(Int8x64) Int8x64 | |
| // func (Int8s) Add(Int8s) Int8s | |
| // ... | |
| // func (Float64x8) Add(Float64x8) Float64x8 | |
| // func (Float64s) Add(Float64s) Float64s | |
| // | |
| // ## Spec constraints | |
| // | |
| // For many operations, all possible combinations of their type parameters are | |
| // valid, but some need to express constraints between type parameters that | |
| // can't easily be described in the Go type system. For these, we support a | |
| // `//specgen:requires` directive. Consider DotProductPairs: | |
| // | |
| // // DotProductPairs computes the dot product of x and y. | |
| // // | |
| // //specgen:require z={xB}{xN*2}x{xL/2} | |
| // func DotProductPairs[E Nums, W Width, zE Nums](x, y Vec[E, W]) (z Vec[zE, W]) { | |
| // ... | |
| // } | |
| // | |
| // The require expression refers to the types of each parameter and result by | |
| // name. For Vec (and Array) arguments, each parameter and result also get | |
| // several related variables: | |
| // | |
| // v = The whole vector type (e.g., Uint32x8) | |
| // vE = The element type (e.g., uint32) | |
| // vB = The base type (e.g., uint) | |
| // vN = The base type size (e.g., 32) | |
| // vL = The number of lanes in the vector or elements in the array (e.g., 8) | |
| // vW = The total bit width of the vector or array (e.g., 256) | |
| // | |
| // The full syntax for constraints is described in the [specgen/specexpr] | |
| // package, but they often describe vector shapes like those in the | |
| // DotProductPairs example. The form of these is: | |
| // | |
| // - BaseNxL, which describes a vector with L elements of type BaseN; | |
| // - BaseNwW, which describes a vector of total width W; or | |
| // - BaseNs, which describes a scalable vector of BaseN elements. | |
| // | |
| // Base, N, L, or W can be either a literal or an expression in {}'s. For | |
| // example, Uint32x{vL} or {zB}{zN}w128. | |
| // | |
| // For DotProductPairs, the constraint "z={vB}{vN*2}x{vL/2}" says the result z | |
| // must have the same base type as v, but z's element type must be twice as | |
| // wide, and z must have half the number of lanes of v. | |
| // | |
| // The DotProductPairs constraint could also have been written in any of the | |
| // following equivalent ways: | |
| // | |
| // z={vB}{vN*2}w{vW} Constrain the total vector width | |
| // zB=vB zN=vN*2 zL=vL/2 Constrain each component separately | |
| // zE={vB}{vN*2} zW=vW Constrain the element type and width separately | |
| // | |
| // ### Width rounding | |
| // | |
| // Some operations would naturally result in an impossible vector size. For | |
| // example, converting a Float64x2 vector to float32s would naturally result in | |
| // Float32x2, which is only 64 bits and thus smaller than the smallest vector | |
| // size. For operations like this, we use "width rounding". | |
| // | |
| // Width rounding is applied specifically by the vector expression form BaseNxL. | |
| // If NxL would be < 128, the result rounds L up to 128/N. | |
| // | |
| // ## Name and doc templates | |
| // | |
| // For some operations, the API name or documentation depends on type | |
| // parameters. For these we use Go's text/template system where constraint | |
| // variables can be referenced in double curly braces, like {{.vE}}. | |
| // For doc comments, template actions can be included directly in the doc comment | |
| // (including conditionals like {{if lt .zL .xL}}). For names, we use a | |
| // `//specgen:name` directive, such as | |
| // | |
| // //specgen:name Load{{.z}} | |
| // func LoadZ[E Elt, W Width](s []E) (z Vec[E, W]) { | |
| // | |
| // In this case, the spec function itself can be named anything (as long as it's | |
| // exported), and the API name is generated from the directive. For example, | |
| // when LoadZ is instantiated on uint32 and Width128, the API name generated | |
| // from the template will be LoadUint32x4. When interpolating element types into | |
| // identifiers, the title filter can be used: ConvertTo{{.zE | title}}. | |
| // This is particularly useful for constructor functions and conversion | |
| // functions where types must appear in the name, such as LoadZ. | |
| // | |
| // ## The spec type system | |
| // | |
| // This package defines a set of types that translate to API types. We saw the | |
| // [Vec] type above, which translates to a concrete BNxL (or BNs) vector type in | |
| // the API, where L is determined from E and W. | |
| // | |
| // Masks are also represented using the [Vec] type, but with an element type | |
| // from the [MaskElt] interface, such as Mask8, Mask16, etc. The generator | |
| // translates these to Mask types in the API. Internal to the spec package, | |
| // these are like a wide mask, where only 0 and ^0 are legal values for these | |
| // elements. | |
| // | |
| // Similar to [Vec], there an [Array[E,W]] type that translates into a [L]E Go | |
| // array type in the API. | |
| // | |
| // The type [UintN] stands for a uint type whose bit width is determined by spec | |
| // constraints. | |
| // | |
| // Pointer and slice types translate directly to the API. | |
| package spec |