Skip to content

simd: architecture and vector-size agnostic SIMD intrinsics under a GOEXPERIMENT #78902

Open

Description

@dr2chase

Proposal Details

This is the first version of the "second level" SIMD api described in #73787; the intent is to provide a portable interface to SIMD vectors that is not tied to a particular architecture or vector length. On architectures where the instructions are supported, the API provides high performance, on those where it is not, it at least provides portability and a competent emulation. The API will also automatically pick the "best" (largest vector length, latest technology) when run on different variants of a different architecture (that is, it will auto-select between AVX, AVX2, and AVX512, depending on what is available).

In this version, the supported vector methods are those in the intersection of the wasm SIMD API and the current amd64 SIMD API; this is a small but still useful set of operations. Starting small also simplifies the emulation for those platforms that do not yet support SIMD, and also increases the chance that newly implemented SIMD support for some other architecture will easily be compatible.

API overview

The vector type names are all the signed, unsigned, and float types, with first letter capitalized, and a plural suffix, for example Int8s or Float64s. Within a given execution of a program all the vectors have the same length in bits, so they may be punned with As<OtherTypeSize>s, for example x.AsFloat64s().

Masks are supported as an abstraction (because all the architectures implement them differently), with a bit width to indicate the size of compatible vector elements, and all non-mask types support the corresponding .Masked(mask) and .Merged(valueIfFalse, mask) methods. Masks can also be manipulated with simple booleans, currently And and Or. On architectures/variants supporting single operation+mask instructions, it is intended that x.Operation(y).Masked(m) pattern-match into that single instruction.

It is possible to convert an architecture-agnostic simd type into a specific architecture's simd type, and back again. This isn't in the prototype yet, the proposed methods names for this are ToArch() any and ToSimd() any followed by a type case that includes the desired types.
For example:

//go:build amd64
...
import (
	"simd"
	"simd/archsimd"
)
func sum(x simd.Float32s) float32 {
	switch a := x.ToArch().(type) {
	case archsimd.Float32x8:
		a = a.AddPairsGrouped(a)
		a = a.AddPairsGrouped(a)
		return a.GetLo().GetElem(0) + a.GetHi().GetElem(0)
	case archsimd.Float32x16:
	...
	case archsimd.Float32x4:
	...
	}

There is a candidate CL; the actual API appears in simd/mocks.go. It is generated automatically by simd/archsimd/_gen/midway/ from an intersection of the SIMD APIs for wasm and amd64 (and other architectures, in the future). The portable API will be expanded over time by various architecture-specific APIs with emulations to fill in the intersection.

As an example, "Int8s" supports the following functions and methods

LoadInt8Slice, LoadInt8SlicePart, Abs, Add, AddSaturated, And, AndNot,
AsFloat32s, AsFloat64s, AsInt16s, AsInt32s, AsInt64s, AsUint16s, AsUint32s,
AsUint64s, AsUint8s, Equal, Greater, Len, Masked, Max, Merge, Min, Neg, Not,
Or, StoreSlice, StoreSlicePart, String, Sub, SubSaturated, ToMask, Xor

and Float32s supports these functions and methods

LoadFloat32Slice, LoadFloat32SlicePart, Add, AsFloat64s, AsInt16s, AsInt32s,
AsInt64s, AsInt8s, AsUint16s, AsUint32s, AsUint64s, AsUint8s, ConvertToInt32,
Div, Equal, Greater, GreaterEqual, Len, Less, LessEqual, Masked, Max, Merge,
Min, Mul, MulAdd, NotEqual, Sqrt, StoreSlice, StoreSlicePart, String, Sub, 

Implementation technique

Code that depends on "simd" is rewritten into multiple versions, and functions and methods that internally depend on simd but do not export that dependence in their signatures will be turned into dispatch functions/methods. Within code compiled for a particular variant, references to other simd-dependent code will be rewritten to reference the same variant; the intent is to both avoid internal dispatch and minimize code cloning. This rewrite also occurs early in the compiler to maximize the chance of inlining (for performance, of course). The rewriting generates names with "@" characters in them, so they cannot be referred to from source code (this may have some effect on tools processing export data or symbols in object files, e.g., debuggers).

Activity

  1. added this to the Proposal milestone on Apr 22, 2026
  2. ianlancetaylor commented on Apr 22, 2026

    @ianlancetaylor
    Contributor

    Since the length is not known when writing the program, I think it would help to show a couple of examples of how to write code that actually uses these new types and functions/methods. Thanks.

  3. added
    LibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a tool
    on Apr 22, 2026
  4. balasanjay commented on Apr 22, 2026

    @balasanjay
    Contributor

    (copying over my comment from dr2chase/midway#1 and closing that)

    Let's say I wanted to write an algorithm specifically using simd.Uint32x8 (for cryptographic reasons, or hash-table sizing reasons (e.g. I'm storing 8 uint32 hash codes in a single vector)).

    I would want that to compile to archsimd.Uint32x8 natively for platforms supporting 256-bit operations, but I would want it to also generate an implementation using a pair of archsimd.Uint32x4 values representing lo and hi elements in my logical 8-vector.

    (Actually, for that matter, I may even want it to compile it into a variant that is implemented using 8 uint32s that work without any archsimd support at all).

    Is the intention for midway/simd package to support use-cases like that (fixed lane size but generic over underlying hardware lane size), or is the use-case limited to code that is "generic" over the number of lanes.
    I believe highway supports this via FixedTag<T, kCount> (and what is currently implemented is equivalent to ScalableTag).

  5. dr2chase commented on Apr 22, 2026

    @dr2chase
    ContributorAuthor

    @ianlancetaylor
    The canonical example is inner product:

    func ip(x, y []float32) float32 {
    	var a simd.Float32s
    	var i int
    	for i = 0; i < len(x)-a.Len()+1; i += a.Len() {
    		u := simd.LoadFloat32Slice(x[i : i+a.Len()])
    		v := simd.LoadFloat32Slice(y[i : i+a.Len()])
    		a = a.Add(u.Mul(v))
    	}
    	if i < len(x) {
    		a = a.Add(simd.LoadFloat32SlicePart(x[i:]).
    			Mul(simd.LoadFloat32SlicePart(y[i:])))
    	}
    	return sum(a)
    }
    

    "Sum" is implemented two different ways, chosen by build tag:

    //go:build !amd64
    package main
    import (
    	"simd"
    )
    
    func sum(x simd.Float32s) float32 {
    	s := make([]float32, x.Len())
    	x.StoreSlice(s)
    	var r float32
    	for _, e := range s {
    		r += e
    	}
    	return r
    }
    

    or

    //go:build amd64
    package main
    import (
    	"simd"
    	"simd/archsimd"
    )
    
    func sum(x simd.Float32s) float32 {
    	switch a := x.ToArch().(type) { 
    	case archsimd.Float32x8:
    		a = a.AddPairsGrouped(a)
    		a = a.AddPairsGrouped(a)
    		return a.GetLo().GetElem(0) + a.GetHi().GetElem(0)
    	case archsimd.Float32x16:
    		s := make([]float32, a.Len())
    		a.StoreSlice(s)
    		var r float32
    		for _, e := range s {
    			r += e
    		}
    		return r
    	case archsimd.Float32x4:
    		s := make([]float32, a.Len())
    		a.StoreSlice(s)
    		var r float32
    		for _, e := range s {
    			r += e
    		}
    		return r
    	}
    	panic("not a known type")
    }
    
  6. dr2chase commented on Apr 22, 2026

    @dr2chase
    ContributorAuthor

    @balasanjay The use case is code that is not sensitive to the vector width. You can however inquire about the length of a simd.Uint32s and chose different code based on that, and also use build tags (in the case of sum above) to specialize by architecture. The one thing that won't work is converting archsimd.Uint32x8 directly into simd.Uint32s if you happen to be running on AVX512, since in that case simd.Uint32s is actually archsimd.Uint32x16.

    This gets more annoying given the scalable SIMD architectures (SVE, RVV) that in theory and sometimes in practice can have more than 256 bits -- code generation to 256-bit vector operations in that case potentially requires a mask applied to all otherwise-larger operations. (Last I checked, real-world SVE was 128, 256, and a 512-bit Japanese supercomputer. There are several RISCV implementations with 512-bit RVV.)

  7. balasanjay commented on Apr 24, 2026

    @balasanjay
    Contributor

    @dr2chase Hmm, I see.

    I understand the usecase for simd.Uint32s. As you say, it allows you to write portable code that is not sensitive to the vector width. It will compile variations for architectures with archsimd.Uint32x4 support, archsimd.Uint32x8 support, etc.

    I think there is also a usecase for simd.Uint32x8. It would allow you to write portable code for dealing with specifically 8 uint32s. It would of course compile a variation for an architecture with archsimd.Uint32x8 support (trivially), but it would also compile a variation for an architecture with just archsimd.Uint32x4 support (by emulating a simd.Uint32x8 as two archsimd.Uint32x4). It would even compile a variation for a scalar architecture (by emulating a simd.Uint32x8 as eight uint32s).

    Consider the program I wrote to test out archsimd many months ago: link. Note: this predated the package rename, so simd in that code should be read as archsimd (confusing, I admit). That function is written using archsimd.Uint8x32 because that expresses the computation required by the spec for this encryption algorithm, but I don't want to write separate AVX512, AVX2, SSE, and scalar version of that algorithm. I think using build tags and/or inquiring about the length of a simd.Uint32s are strategies for me writing all those variants. I'd like the compiler to just generate those variants for me. Maybe you're saying that that is out-of-scope for the initial version of the portable simd package.

    (ignoring mildly tricky AES situation later in that file) That file does have 1 tricky aspect which really does seem to require AVX: [the Wide AES](https://github.com/balasanjay/aegis/blob/ae41f298cff5b5a615127c11ab2ac4979384466d/internal/impl/aegis128x2.go#L247). But even that could be emulated with the non-vector AES instructions. Or in the worst case, with a fallback to a software AES implementation. I could buy that a vector AES function specifically should be left out of the portable `simd` package and put elsewhere. But certainly all the simple functions (`Xor`, `And`, `LoadFromSlice`, etc) should be available in the portable package, without resorting to writing `archsimd` code directly.
  8. aclements commented on Apr 29, 2026

    @aclements
    Member

    @balasanjay , thanks for the example. Certainly for now this is out of scope. In particular, emulating fixed widths that are wider than the hardware supports would be quite a lot of work (doable, certainly, but not something we're going to tackle at first).

  9. aclements commented on Apr 29, 2026

    @aclements
    Member

    Pasting our discussion on possible approaches for going between simd and archsimd:

    // Option 1: Force a type switch. Totally portable, no "unspeakable types",
    // but the caller is going to need build tags anyway to speak the type switch.
    
    func (x Int8s) ToArch() any
    
    // What if we pass Int8x16 on an AVX-512 machine? Panic? Extend? Broadcast?
    func Int8sFromArch(any) Int8s
    
    
    // Option 2: Build-tag methods. Concrete, but annoying for docs. Lots of API
    // that portable users don't care about.
    
    //go:build amd64 || wasm
    func (x Int8s) ToInt8x16() (archsimd.Int8x16, bool)
    //go:build amd64
    func (x Int8s) ToInt8x32() (archsimd.Int8x32, bool)
    //go:build arm64 || riscv
    func (x Int8s) ToInt8s() (archsimd.Int8s, bool) // A little weird!
    
    // Option 2b: Use generics to fold a bunch of the above together.
    func Int8sFromArch[T archsimd.Int8x16|archsimd.Int8x32|...](T) Int8s
    func (x Int8s) ToArch[T archsimd.Int8x16|archsimd.Int8x32|...]() (T, bool)
  10. aclements commented on Apr 29, 2026

    @aclements
    Member

    To clarify @dr2chase 's sum examples, these show a reducing sum operations, not a vector-wise sum. The current prototype doesn't directly support any reduction operations because no hardware directly supports this. Reduction operations are useful enough we would probably want to add them, and we'd do that by adding emulations to archsimd so they wind up in the intersection of operations exposed in the simd package.

  11. aclements commented on Apr 29, 2026

    @aclements
    Member

    @dr2chase , could you post a complete API? It can be a go doc or a pointer to a CL if there's already a good summary somewhere (maybe mocks.go is that?)

  12. dr2chase commented on Apr 30, 2026

    @dr2chase
    ContributorAuthor

    Here is most of the API, at today's version: https://go.dev/cl/c/go/+/763100/16/src/simd/mocks.go

    This is automatically generated from existing architecture-specific APIS. There may be some operations that we wish were there, and if so, say so, but the proposed mechanism for obtaining more operations is to add emulations to the architecture-specific APIs, which then increases the size of their intersection.

    The missing part of the API is the conversions between simd and archsimd. I have a minor preference for a combination of option 1 and 2b; I haven't completely explored the FromArch method/function, but adapted the "sum" code to use the two different options. The answer to

    // What if we pass Int8x16 on an AVX-512 machine? Panic? Extend? Broadcast?
    func Int8sFromArch(any) Int8s
    

    is "panic", if we don't like panic then it needs to return (Int8s, bool) and it might be slower, too.

    As noted above, sum exists because we don't have a ReduceAdd method in the existing archsimd APIs, so it is also not in their intersection. I wrote it like this because it was an easy example of dropping from architecture-agnostic down to architecture-specific code, and I could compile it and run it and compare its behavior. All the type-wrangling vanishes after optimization; because the code is specifically recompiled for each particular vector width, all the type predicates become constants.

    Option 1:

    func sum(x simd.Float32s) float32 {
    	switch a := x.ToArch().(type) {
    	case archsimd.Float32x8:
    		a = a.AddPairsGrouped(a)
    		a = a.AddPairsGrouped(a)
    		return a.GetLo().GetElem(0) + a.GetHi().GetElem(0)
    	case archsimd.Float32x16:
    		b := a.GetLo().Add(a.GetHi())
    		b = b.AddPairsGrouped(b)
    		b = b.AddPairsGrouped(b)
    		return b.GetLo().GetElem(0) + b.GetHi().GetElem(0)
    	}
    	s := make([]float32, x.Len())
    	x.StoreSlice(s)
    	var r float32
    	for _, e := range s {
    		r += e
    	}
    	return r
    }
    

    Option 2b (which required enabling generic methods and then provoked a crash in type-checking, so not currently my favorite):

    func sumMaybe(x simd.Float32s) float32 {
    	if a, ok := x.ToArchMaybe[archsimd.Float32x8](); ok {
    		a = a.AddPairsGrouped(a)
    		a = a.AddPairsGrouped(a)
    		return a.GetLo().GetElem(0) + a.GetHi().GetElem(0)
    	}
    	if b, ok := x.ToArchMaybe[archsimd.Float32x16](); ok {
    		a := b.GetLo().Add(b.GetHi())
    		a = a.AddPairsGrouped(a)
    		a = a.AddPairsGrouped(a)
    		return a.GetLo().GetElem(0) + a.GetHi().GetElem(0)
    	}
    	s := make([]float32, x.Len())
    	x.StoreSlice(s)
    	var r float32
    	for _, e := range s {
    		r += e
    	}
    	return r
    }
    
  13. dr2chase commented on May 6, 2026

    @dr2chase
    ContributorAuthor

    I took a stab at rearranging the proposal into a more digestible form (yes an LLM was involved, I had to proofread carefully but the formatting is nice and the organization looks helpful).


    Experimental size-and-architecture agnostic SIMD Reference

    This document provides a comprehensive functional summary of all type declarations and SIMD operations currently defined in the proposed "simd" package, which is mostly the contents of mocks.go . Some adjustments are underway to align this with Austin's recent proposed changes to the amd64 SIMD API; the goal is to track that.

    The abstract API in mocks.go represents the computed intersection of SIMD methods supported across all target SIMD architectures (such as amd64 and wasm) and vector widths. The current version is missing a few methods that can be supplied with straightforward emulation, and these are noted, but may not be complete by the 1.27 freeze.


    Vector & Mask Types Reference

    The API defines specific vector types and corresponding boolean mask types, categorized by element width and representation:

    Element Type Bit Width Vector Type Mask Type
    Signed Integer 8-bit Int8s Mask8s
    16-bit Int16s Mask16s
    32-bit Int32s Mask32s
    64-bit Int64s Mask64s
    Unsigned Integer 8-bit Uint8s Mask8s
    16-bit Uint16s Mask16s
    32-bit Uint32s Mask32s
    64-bit Uint64s Mask64s
    Floating-Point 32-bit Float32s Mask32s
    64-bit Float64s Mask64s

    1. Global & Utility Operations

    Global Capacity Queries

    • VectorSize() int Returns the size (in bits) of the SIMD vectors supported by the current host CPU for this execution.

    Intrinsic Utilities

    • x.Len() int Returns the number of elements in the vector (available on all vector types).
    • x.String() string Returns a string representation of the vector or mask (available on all vector and mask types).

    2. Memory Operations (Loads & Stores)

    These operations load data from standard slices into SIMD registers or store SIMD register values back to memory slices.

    Slice Loads

    • Load<Type>Slice(s []elem) <Type> Loads a full slice into a vector register.
      • Example: LoadInt8Slice([]int8) Int8s
    • Load<Type>SlicePart(s []elem) <Type> Loads a partial slice into a vector register (e.g., when slice length is less than the register width).
      • Example: LoadInt8SlicePart([]int8) Int8s
    • LoadMask<Size>Slice(s []elem) Mask<Size>s Loads slice element values into a boolean mask vector.
      • Example: LoadMask8Slice([]int8) Mask8s
    • LoadMask<Size>SlicePart(s []elem) Mask<Size>s Loads a partial slice element values into a boolean mask vector.
      • Example: LoadMask8SlicePart([]int8) Mask8s

    Vector Stores

    • x.StoreSlice(s []elem) Stores vector elements back into the destination slice s.
      • Example: (x Int8s) StoreSlice(s []int8)
    • x.StoreSlicePart(s []elem) Stores a partial vector back into the destination slice s.
      • Example: (x Int8s) StoreSlicePart(s []int8)

    [!NOTE] Boolean mask types (Mask8s, Mask16s, Mask32s, Mask64s) support load operations, but do not support direct StoreSlice* operations. To store a mask, it must first be converted to an integer vector (see Casts & Conversions).


    3. Arithmetic Operations

    Basic Arithmetic

    • x.Add(y <Type>) <Type> Returns the element-wise sum ($x + y$). Supported by all vector types.
    • x.Sub(y <Type>) <Type> Returns the element-wise difference ($x - y$). Supported by all vector types.
    • x.Mul(y <Type>) <Type> Returns the element-wise product ($x \times y$).
      • Supported by: Int16s, Int32s, Uint16s, Uint32s, Float32s, Float64s.
      • Unsupported by: Int8s, Uint8s, Int64s, Uint64s (currently).
    • x.Div(y <Type>) <Type> Returns the element-wise quotient ($x / y$).
      • Supported by: Float32s, Float64s.
    • x.MulAdd(y <Type>, z <Type>) <Type> Fused Multiply-Add: Returns $(x \times y) + z$ element-wise.
      • Supported by: Float32s, Float64s.

    Unary & Sign Arithmetic

    • x.Neg() <Type> Returns the element-wise negation of x.
      • Supported by: Signed integer (Int8s, Int16s, Int32s, Int64s)
      • To be implemented: Floating-point (Float32s, Float64s via Sub from zero).
    • x.Abs() <Type> Returns the element-wise absolute value.
      • Supported by: Signed integers Int8s, Int16s, Int32s.
      • Unsupported by: Int64s.
    • x.Sqrt() <Type> Returns the element-wise square root.
      • Supported by: Float32s, Float64s.

    Saturated Arithmetic

    Saturated operations clamp values to the minimum/maximum values representing the integer width instead of wrapping around.

    • x.AddSaturated(y <Type>) <Type> Returns saturated sum of $x$ and $y$.
      • Supported by: 8-bit and 16-bit integer types (Int8s, Int16s, Uint8s, Uint16s).
    • x.SubSaturated(y <Type>) <Type> Returns saturated difference of $x$ and $y$.
      • Supported by: 8-bit and 16-bit integer types (Int8s, Int16s, Uint8s, Uint16s).

    Reduction & Blending Arithmetic

    • x.Min(y <Type>) <Type> Returns the element-wise minimum of $x$ and $y$.
      • Supported by: All types except 64-bit integers (Int64s, Uint64s).
    • x.Max(y <Type>) <Type> Returns the element-wise maximum of $x$ and $y$.
      • Supported by: All types except 64-bit integers (Int64s, Uint64s).
    • x.Average(y <Type>) <Type> Returns the element-wise average of $x$ and $y$, rounding half up: $\lfloor(x + y + 1) / 2\rfloor$.
      • Supported by: Uint8s, Uint16s.

    4. Bitwise & Logical Operations

    Bitwise operations are performed on a bit-by-bit level across entire vector registers.

    • x.And(y <Type>) <Type> Bitwise AND ($x \ \&amp; \ y$).
      • Supported by: All integer types (Int8s, Int16s, Int32s, Int64s, Uint8s, Uint16s, Uint32s, Uint64s) and mask types (Mask8s, Mask16s, Mask32s, Mask64s).
    • x.Or(y <Type>) <Type> Bitwise OR ($x \ | \ y$).
      • Supported by: All integer types and mask types.
    • x.Xor(y <Type>) <Type> Bitwise XOR ($x \ \land \ y$).
      • Supported by: All integer types.
      • To be implemented: mask types
    • x.AndNot(y <Type>) <Type> Bitwise AND-NOT ($x \ \&amp; \ \sim y$).
      • Supported by: All integer types.
    • x.Not() <Type> Bitwise NOT ($\sim x$).
      • Supported by: All integer types.
      • To be implemented: mask types

    5. Shift Operations

    Shift operations shift all elements inside the vector left or right by a uniform bit count specified as a uint64.

    • x.ShiftAllLeft(y uint64) <Type> Shifts all elements left by $y$ bits.
      • Supported by: Int16s, Int32s, Int64s, Uint16s, Uint32s, Uint64s.
      • Unsupported by: 8-bit types (Int8s, Uint8s).
    • x.ShiftAllRight(y uint64) <Type> Shifts all elements right by $y$ bits (arithmetic shift for signed integers, logical shift for unsigned).
      • Supported by: Int16s, Int32s, Uint16s, Uint32s, Uint64s.
      • Unsupported by: 8-bit types (Int8s, Uint8s) and 64-bit signed integers (Int64s).

    6. Comparisons & Masking

    Comparison operations evaluate conditions element-wise, returning boolean mask vectors of matching layouts.

    Comparisons

    • x.Equal(y <Type>) <Mask> Returns a mask indicating where elements are equal ($x == y$). Supported by all vector types.
    • x.Greater(y <Type>) <Mask> Returns a mask indicating where $x &gt; y$.
      • Supported by: All signed integer types (Int8s, Int16s, Int32s, Int64s) and floating-point types (Float32s, Float64s).
    • x.GreaterEqual(y <Type>) <Mask> Returns a mask indicating where $x \ge y$.
      • Supported by: Float32s, Float64s.
    • x.Less(y <Type>) <Mask> Returns a mask indicating where $x &lt; y$.
      • Supported by: Float32s, Float64s.
    • x.LessEqual(y <Type>) <Mask> Returns a mask indicating where $x \le y$.
      • Supported by: Float32s, Float64s.
    • x.NotEqual(y <Type>) <Mask> Returns a mask indicating where $x \ne y$.
      • Supported by: Float32s, Float64s.
    • Missing comparisons are to-be-implemented using operand reordering and/or complement.

    Element Blending & Selection (Masking)

    • x.Masked(mask <Mask>) <Type> Element-wise masking: returns a new vector containing elements from $x$ where mask is true, and zero elsewhere.
      • Supported by: All vector types.
    • x.IfElse(mask <Mask>, y <Type>) <Type> Element-wise blending: returns a new vector containing elements from $x$ where mask is false, and elements from $y$ where mask is true.
      • Supported by: All vector types.

    7. Casts & Conversions

    Casts and conversions transform vector layouts either by bitwise reinterpretation or by numerical conversion of elements.

    Bitwise Reinterpretation (No-op Casts)

    To avoid a large matrix of methods, bitwise reinterpretation is factored into puns to same-sized Uint vectors, puns from same-sized Uint vectors, and size conversions of Uint vectors. This API would have 90 reinterpretation methods if it were unfactored, but on AMD64 where there are 3 fixed wvector widths, there would be 270 reinterpretation methods. The names here may change based on updates to the AMD64 SIMD API; whatever is chosen, these will agree, but the division into 3 kinds of reinterpration is not expected to change.

    • x.ToBits() Uint<W>s Returns the bits of $x$ reinterpreted as a vector of Uints of the same width $W$.
      Supported by: All signed integer types (Int8s, Int16s, Int32s, Int64s) and floating-point types (Float32s, Float64s).
    • x.BitsToFloats() Float<W>s Returns the bits of Uint<W>s x reinterpreted as a Float<W>s.
    • x.BitsToInts() Ints<W>s Returns the bits of Uint<W>s x reinterpreted as an Int<W>s.
    • Reshaping methods for Uint<W>s vectors:
      • x.ReshapeToUint8s()
      • x.ReshapeToUint16s()
      • x.ReshapeToUint32s()
      • x,ReshapeToUint64s()
    • TBD: the no-op conversions -- Uints-to-Uints w/o changing size may be desirable because it slightly simplifies machine-generation of SIMD code. It may be that this is only interesting to actual SIMD implementers.

    Value Conversions (Element-wise Value Casts)

    Numerically converts element values from one primitive representation to another. Expanding this portion of the API is desirable but it will require emulation work on various architectures because there is great variety in how expanding and contracting vector elemens are handled.

    • (x Int32s) ConvertToFloat32() Float32s Converts integer elements to float32 values.
    • (x Float32s) ConvertToInt32() Int32s Converts float32 elements to int32 values.

    Mask Conversions

    Note: signedness of the int types that interconvert with masks is TBD in the amd64 SIMD spec, and this will follow that if it changes to unsigned.

    • x.ToMask() <Mask> Converts a signed integer vector x to its boolean mask equivalent.
      • Supported by: Int8s ($\rightarrow$ Mask8s), Int16s ($\rightarrow$ Mask16s), Int32s ($\rightarrow$ Mask32s), Int64s ($\rightarrow$ Mask64s).
    • m.To<IntegerType>() <IntegerType> Converts a mask m back to its signed integer vector equivalent (with all bits set to 1 for true, 0 for false).
      • Example: (x Mask8s) ToInt8s() Int8s

    8. Translation to/from architecture specific types

    • x.ToArch() any Converts x, an architecture agnostic SIMD vector, into an any that can be type-asserted or type-switched into the platform-specific simd/archsimd vector type that implements x.
    • <SimdType>FromArch[T archSimd<SimdType>](x T) <SimdType> Converts x, an architecture-specific SIMD value, into <SimdType>, an architecture-agnostic SIMD vector. The exact definition of archSimd<SimdType> varies across architectures, as an example, for Float32 vectors on amd64 it is
      type archSimdFloat32s interface {
        archsimd.Float32x4 | archsimd.Float32x8 | archsimd.Float32x16
      }
    

    and

    func Float32sFromArch[T archSimdFloat32s](x T) Float32s
    
  14. aclements commented on May 6, 2026

    @aclements
    Member

    Proposal review discussion:

    • No broad objections. A few questions about details.
    • <SimdType>FromArch[T archSimd<SimdType>](x T) <SimdType> - Does this panic if T isn't the expected lane count?
    • Is, say, Int32s always the same length in a given run of a program? Yes.
    • Load<Type>SlicePart(s []elem) <Type> - We're a little concerned that this doesn't return the actual number of elements loaded. Yes, you can get that from min(len(s), x.Len()), but that's a bit cumbersome and it would be easy to accidentally just use x.Len() in a loop.
    • This is in a way a slight extension of the Go type system because these types do not have a compile-time size. They're very similar to type parameters in that way (e.g., unsafe.Sizeof(typeParam) is a non-constant). Part of the reason we want to do this as an experiment for a release is to iron out bad tools interactions.
  15. 43 remaining items

  16. gopherbot commented on Aug 21, 2026

    @gopherbot
    Contributor

    Change https://go.dev/cl/819140 mentions this issue: simd, simd/archsimd: correct package documentation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    LibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a toolProposalProposal-Accepted

    Type

    No type

    Projects

    • Status
      Accepted

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions