| // 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 x and y elementwise. | |
| // 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. | |
| // | |
| // ### Vector widths and scalable execution | |
| // | |
| // Fixed vector widths are represented by [Width128], [Width256], and [Width512]. | |
| // Scalable vectors are represented by [WidthScalable]. At the specification level, | |
| // scalable vector width is symbolic. To execute scalable operations in the spec | |
| // (for example, as an oracle or via simdref), the concrete bit width must be | |
| // configured via [SetScalableWidth] (up to 2048 bits, matching the architectural | |
| // maximum of ARM SVE); executing without setting the width panics. | |
| // | |
| // Calls to [SetScalableWidth] return a restore function and must follow a | |
| // strict set/restore sequence. Concurrency or nested sets panic. | |
| // | |
| // ## Documentation conventions | |
| // | |
| // Look at other functions in this package and try to follow their example. | |
| // | |
| // Be sure to document behavior in corner cases such as overflow, rounding, or | |
| // unusual floating-point values. The default assumed oveflow behavior for ints | |
| // and uints is that they wrap like in Go. Any deviation must be documented. If | |
| // rounding is possible, document the type of rounding (e.g., rounding toward | |
| // +/-∞, rounding toward 0, rounding ties to even, rounding ties away from 0). | |
| // | |
| // When feasible, doc comments should include a mathematical statement of the | |
| // operation. This should balance precision and clarity. We follow various | |
| // conventions for this: | |
| // | |
| // - Vectors are written in a succinct variant of Go slice syntax. For example, | |
| // to refer to element i of vector x, use "x[i]". To construct a whole vector, | |
| // use syntax like "{x[0]+y[0], x[1]+y[1], ...}". | |
| // | |
| // - Expressions can be quantified over all elements of a vector without | |
| // explicitly saying so. E.g., "z[i] = x[i] + y[i]" implicitly "loops" over all | |
| // indexes of z. | |
| // | |
| // - Operators like +, *, etc have infinite precision, and assignment to a | |
| // fixed-width type behaves like a Go cast (wrapping for int/uint, rounding for | |
| // float). When this matters, it should generally be clarified in a comment. For | |
| // example, the intermediate step of dot-product is written "w[i] = x[i] * y[i] | |
| // // Double width" to clarify the precision. | |
| // | |
| // - Saturating operations are common and are written like "sat(expr)", where | |
| // "sat" saturates the result to the appropriate range, rather than wrapping it. | |
| package spec |