| // Copyright 2025 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. |
| |
| //go:build goexperiment.simd |
| |
| package simd_test |
| |
| import ( |
| "math" |
| "math/bits" |
| "unsafe" |
| ) |
| |
| func rotl[T unsigned](x T, dist uint64) T { |
| size := uint64(unsafe.Sizeof(x)) * 8 |
| dist = dist & (size - 1) |
| if dist == 0 { |
| return x |
| } |
| return (x << dist) | (x >> (size - dist)) |
| } |
| |
| func rotr[T unsigned](x T, dist uint64) T { |
| size := uint64(unsafe.Sizeof(x)) * 8 |
| dist = dist & (size - 1) |
| if dist == 0 { |
| return x |
| } |
| return (x >> dist) | (x << (size - dist)) |
| } |
| |
| // rotlOfSlice returns a slice simulation of a left rotate |
| // of a specified distance. |
| func rotlOfSlice[T unsigned](dist uint64) func(x []T) []T { |
| return map1[T](func(x T) T { return rotl(x, dist) }) |
| } |
| |
| // rotrOfSlice returns a slice simulation of a right rotate |
| // of a specified distance. |
| func rotrOfSlice[T unsigned](dist uint64) func(x []T) []T { |
| return map1[T](func(x T) T { return rotr(x, dist) }) |
| } |
| |
| func curry2[T, U, V any](f func(T, U) V, y U) func(x T) V { |
| return func(x T) V { return f(x, y) } |
| } |
| |
| func curry1[T, U, V any](f func(T, U) V, x T) func(y U) V { |
| return func(y U) V { return f(x, y) } |
| } |
| |
| func less[T number](x, y T) bool { |
| return x < y |
| } |
| func lessEqual[T number](x, y T) bool { |
| return x <= y |
| } |
| func greater[T number](x, y T) bool { |
| return x > y |
| } |
| func greaterEqual[T number](x, y T) bool { |
| return x >= y |
| } |
| func equal[T number](x, y T) bool { |
| return x == y |
| } |
| func notEqual[T number](x, y T) bool { |
| return x != y |
| } |
| |
| func isNaN[T float](x T) bool { |
| return x != x |
| } |
| |
| func abs[T number](x T) T { |
| // TODO this will need a non-standard FP-equality test. |
| if x == 0 { // true if x is -0. |
| return 0 // this is not a negative zero |
| } |
| if x < 0 { |
| return -x |
| } |
| return x |
| } |
| |
| func neg[T number](x T) T { |
| return -x |
| } |
| |
| func onesCount[T integer](x T) T { |
| size := uint64(unsafe.Sizeof(x)) * 8 |
| return T(bits.OnesCount64(uint64(x) & ((1 << size) - 1))) |
| } |
| |
| func ceil[T float](x T) T { |
| return T(math.Ceil(float64(x))) |
| } |
| func floor[T float](x T) T { |
| return T(math.Floor(float64(x))) |
| } |
| func not[T integer](x T) T { |
| return ^x |
| } |
| func round[T float](x T) T { |
| return T(math.RoundToEven(float64(x))) |
| } |
| func sqrt[T float](x T) T { |
| return T(math.Sqrt(float64(x))) |
| } |
| func trunc[T float](x T) T { |
| return T(math.Trunc(float64(x))) |
| } |
| |
| func add[T number](x, y T) T { |
| return x + y |
| } |
| |
| func sub[T number](x, y T) T { |
| return x - y |
| } |
| |
| func max_[T number](x, y T) T { // "max" lands in infinite recursion |
| return max(x, y) |
| } |
| |
| func min_[T number](x, y T) T { // "min" lands in infinite recursion |
| return min(x, y) |
| } |
| |
| // Also mulLow for integers |
| func mul[T number](x, y T) T { |
| return x * y |
| } |
| |
| func div[T number](x, y T) T { |
| return x / y |
| } |
| |
| func and[T integer](x, y T) T { |
| return x & y |
| } |
| |
| func andNotI[T integer](x, y T) T { |
| return x & ^y // order corrected to match expectations |
| } |
| |
| func orI[T integer](x, y T) T { |
| return x | y |
| } |
| |
| func orNotI[T integer](x, y T) T { |
| return x | ^y |
| } |
| |
| func xorI[T integer](x, y T) T { |
| return x ^ y |
| } |
| |
| func ima[T integer](x, y, z T) T { |
| return x*y + z |
| } |
| |
| func fma[T float](x, y, z T) T { |
| return T(math.FMA(float64(x), float64(y), float64(z))) |
| } |
| |
| func toUint8[T number](x T) uint8 { |
| return uint8(x) |
| } |
| |
| func toUint16[T number](x T) uint16 { |
| return uint16(x) |
| } |
| |
| func toUint64[T number](x T) uint64 { |
| return uint64(x) |
| } |
| |
| func toUint32[T number](x T) uint32 { |
| return uint32(x) |
| } |
| |
| func toInt8[T number](x T) int8 { |
| return int8(x) |
| } |
| |
| func toInt16[T number](x T) int16 { |
| return int16(x) |
| } |
| |
| func toInt32[T number](x T) int32 { |
| return int32(x) |
| } |
| |
| func toInt64[T number](x T) int64 { |
| return int64(x) |
| } |
| |
| func toFloat32[T number](x T) float32 { |
| return float32(x) |
| } |
| |
| func toFloat64[T number](x T) float64 { |
| return float64(x) |
| } |
| |
| // X86 specific behavior for conversion from float to int32. |
| // If the value cannot be represented as int32, it returns -0x80000000. |
| func floatToInt32_x86[T float](x T) int32 { |
| switch y := (any(x)).(type) { |
| case float32: |
| if y != y || y < math.MinInt32 || |
| y >= math.MaxInt32 { // float32(MaxInt32) == 0x80000000, actually overflows |
| return -0x80000000 |
| } |
| case float64: |
| if y != y || y < math.MinInt32 || |
| y > math.MaxInt32 { // float64(MaxInt32) is exact, no overflow |
| return -0x80000000 |
| } |
| } |
| return int32(x) |
| } |
| |
| // X86 specific behavior for conversion from float to int64. |
| // If the value cannot be represented as int64, it returns -0x80000000_00000000. |
| func floatToInt64_x86[T float](x T) int64 { |
| switch y := (any(x)).(type) { |
| case float32: |
| if y != y || y < math.MinInt64 || |
| y >= math.MaxInt64 { // float32(MaxInt64) == 0x80000000_00000000, actually overflows |
| return -0x80000000_00000000 |
| } |
| case float64: |
| if y != y || y < math.MinInt64 || |
| y >= math.MaxInt64 { // float64(MaxInt64) == 0x80000000_00000000, also overflows |
| return -0x80000000_00000000 |
| } |
| } |
| return int64(x) |
| } |
| |
| // X86 specific behavior for conversion from float to uint32. |
| // If the value cannot be represented as uint32, it returns 1<<32 - 1. |
| func floatToUint32_x86[T float](x T) uint32 { |
| switch y := (any(x)).(type) { |
| case float32: |
| if y < 0 || y > math.MaxUint32 || y != y { |
| return 1<<32 - 1 |
| } |
| case float64: |
| if y < 0 || y > math.MaxUint32 || y != y { |
| return 1<<32 - 1 |
| } |
| } |
| return uint32(x) |
| } |
| |
| // X86 specific behavior for conversion from float to uint64. |
| // If the value cannot be represented as uint64, it returns 1<<64 - 1. |
| func floatToUint64_x86[T float](x T) uint64 { |
| switch y := (any(x)).(type) { |
| case float32: |
| if y < 0 || y > math.MaxUint64 || y != y { |
| return 1<<64 - 1 |
| } |
| case float64: |
| if y < 0 || y > math.MaxUint64 || y != y { |
| return 1<<64 - 1 |
| } |
| } |
| return uint64(x) |
| } |
| |
| func ceilResidueForPrecision[T float](i int) func(T) T { |
| f := 1.0 |
| for i > 0 { |
| f *= 2 |
| i-- |
| } |
| return func(x T) T { |
| y := float64(x) |
| if math.IsInf(float64(x*T(f)), 0) { |
| return 0 |
| } |
| // TODO sort out the rounding issues when T === float32 |
| return T(y - math.Ceil(y*f)/f) |
| } |
| } |
| |
| // Slice versions of all these elementwise operations |
| |
| func addSlice[T number](x, y []T) []T { |
| return map2[T](add)(x, y) |
| } |
| |
| func subSlice[T number](x, y []T) []T { |
| return map2[T](sub)(x, y) |
| } |
| |
| func maxSlice[T number](x, y []T) []T { |
| return map2[T](max_)(x, y) |
| } |
| |
| func minSlice[T number](x, y []T) []T { |
| return map2[T](min_)(x, y) |
| } |
| |
| // mulLow for integers |
| func mulSlice[T number](x, y []T) []T { |
| return map2[T](mul)(x, y) |
| } |
| |
| func divSlice[T number](x, y []T) []T { |
| return map2[T](div)(x, y) |
| } |
| |
| func andSlice[T integer](x, y []T) []T { |
| return map2[T](and)(x, y) |
| } |
| |
| func andNotSlice[T integer](x, y []T) []T { |
| return map2[T](andNotI)(x, y) |
| } |
| |
| func orSlice[T integer](x, y []T) []T { |
| return map2[T](orI)(x, y) |
| } |
| |
| func orNotSlice[T integer](x, y []T) []T { |
| return map2[T](orNotI)(x, y) |
| } |
| |
| func xorSlice[T integer](x, y []T) []T { |
| return map2[T](xorI)(x, y) |
| } |
| |
| func lessSlice[T number](x, y []T) []int64 { |
| return mapCompare[T](less)(x, y) |
| } |
| |
| func lessEqualSlice[T number](x, y []T) []int64 { |
| return mapCompare[T](lessEqual)(x, y) |
| } |
| |
| func greaterSlice[T number](x, y []T) []int64 { |
| return mapCompare[T](greater)(x, y) |
| } |
| |
| func greaterEqualSlice[T number](x, y []T) []int64 { |
| return mapCompare[T](greaterEqual)(x, y) |
| } |
| |
| func equalSlice[T number](x, y []T) []int64 { |
| return mapCompare[T](equal)(x, y) |
| } |
| |
| func notEqualSlice[T number](x, y []T) []int64 { |
| return mapCompare[T](notEqual)(x, y) |
| } |
| |
| func isNaNSlice[T float](x []T) []int64 { |
| return map1[T](func(x T) int64 { |
| if isNaN(x) { |
| return -1 |
| } |
| return 0 |
| })(x) |
| } |
| |
| func ceilSlice[T float](x []T) []T { |
| return map1[T](ceil)(x) |
| } |
| |
| func floorSlice[T float](x []T) []T { |
| return map1[T](floor)(x) |
| } |
| |
| func notSlice[T integer](x []T) []T { |
| return map1[T](not)(x) |
| } |
| |
| func roundSlice[T float](x []T) []T { |
| return map1[T](round)(x) |
| } |
| |
| // lanewiseSlice is the common helper for interleave, deinterleave, and transpose |
| // simulations. It handles lane computation, allocation, and iteration. |
| // laneBits is the lane size in bits (128 for NEON/x86 128-bit, 0 for whole-input/SVE). |
| // hi selects the half-lane offset (offHalf = 0 or half, for interleave hi/lo). |
| // odd selects the single-element offset (offOne = 0 or 1, for deinterleave/transpose odd/even). |
| // body receives (out, x, y, base, i, half, offHalf, offOne) for each pair within each lane |
| // and performs the operation-specific element assignment. |
| func lanewiseSlice[T number](laneBits int, hi bool, odd bool, body func(out, x, y []T, base, i, half, offHalf, offOne int)) func(x, y []T) []T { |
| return func(x, y []T) []T { |
| lane := laneBits / (8 * int(unsafe.Sizeof(x[0]))) |
| if lane == 0 || lane > len(x) { |
| lane = len(x) |
| } |
| half := lane / 2 |
| offHalf := 0 |
| if hi { |
| offHalf = half |
| } |
| offOne := 0 |
| if odd { |
| offOne = 1 |
| } |
| out := make([]T, len(x)) |
| for base := 0; base < len(x); base += lane { |
| for i := 0; i < half; i++ { |
| body(out, x, y, base, i, half, offHalf, offOne) |
| } |
| } |
| return out |
| } |
| } |
| |
| func interleaveSlice[T number](laneBits int, hi bool) func(x, y []T) []T { |
| return lanewiseSlice(laneBits, hi, false, func(out, x, y []T, base, i, half, offHalf, _ int) { |
| out[base+2*i] = x[base+offHalf+i] |
| out[base+2*i+1] = y[base+offHalf+i] |
| }) |
| } |
| |
| func deinterleaveSlice[T number](laneBits int, odd bool) func(x, y []T) []T { |
| return lanewiseSlice(laneBits, false, odd, func(out, x, y []T, base, i, half, _, offOne int) { |
| out[base+i] = x[base+2*i+offOne] |
| out[base+half+i] = y[base+2*i+offOne] |
| }) |
| } |
| |
| func transposeSlice[T number](laneBits int, odd bool) func(x, y []T) []T { |
| return lanewiseSlice(laneBits, false, odd, func(out, x, y []T, base, i, half, _, offOne int) { |
| out[base+2*i] = x[base+2*i+offOne] |
| out[base+2*i+1] = y[base+2*i+offOne] |
| }) |
| } |
| |
| func sqrtSlice[T float](x []T) []T { |
| return map1[T](sqrt)(x) |
| } |
| |
| func truncSlice[T float](x []T) []T { |
| return map1[T](trunc)(x) |
| } |
| |
| func imaSlice[T integer](x, y, z []T) []T { |
| return map3[T](ima)(x, y, z) |
| } |
| |
| func fmaSlice[T float](x, y, z []T) []T { |
| return map3[T](fma)(x, y, z) |
| } |
| |
| // reduceSlice reduces x using fn as the combining operation. |
| func reduceSlice[T number](x []T, fn func(a, b T) T) T { |
| acc := x[0] |
| for _, v := range x[1:] { |
| acc = fn(acc, v) |
| } |
| return acc |
| } |
| |
| func satToInt8[T integer](x T) int8 { |
| var m int8 = -128 |
| var M int8 = 127 |
| if T(M) < T(m) { // expecting T being a larger type |
| panic("bad input type") |
| } |
| if x < T(m) { |
| return m |
| } |
| if x > T(M) { |
| return M |
| } |
| return int8(x) |
| } |
| |
| func satToUint8[T integer](x T) uint8 { |
| var M uint8 = 255 |
| if T(M) < 0 { // expecting T being a larger type |
| panic("bad input type") |
| } |
| if x < 0 { |
| return 0 |
| } |
| if x > T(M) { |
| return M |
| } |
| return uint8(x) |
| } |
| |
| func satToInt16[T integer](x T) int16 { |
| var m int16 = -32768 |
| var M int16 = 32767 |
| if T(M) < T(m) { // expecting T being a larger type |
| panic("bad input type") |
| } |
| if x < T(m) { |
| return m |
| } |
| if x > T(M) { |
| return M |
| } |
| return int16(x) |
| } |
| |
| func satToUint16[T integer](x T) uint16 { |
| var M uint16 = 65535 |
| if T(M) < 0 { // expecting T being a larger type |
| panic("bad input type") |
| } |
| if x < 0 { |
| return 0 |
| } |
| if x > T(M) { |
| return M |
| } |
| return uint16(x) |
| } |
| |
| func satToInt32[T integer](x T) int32 { |
| var m int32 = -1 << 31 |
| var M int32 = 1<<31 - 1 |
| if T(M) < T(m) { // expecting T being a larger type |
| panic("bad input type") |
| } |
| if x < T(m) { |
| return m |
| } |
| if x > T(M) { |
| return M |
| } |
| return int32(x) |
| } |
| |
| func satToUint32[T integer](x T) uint32 { |
| var M uint32 = 1<<32 - 1 |
| if T(M) < 0 { // expecting T being a larger type |
| panic("bad input type") |
| } |
| if x < 0 { |
| return 0 |
| } |
| if x > T(M) { |
| return M |
| } |
| return uint32(x) |
| } |
| |
| // shiftAmount extracts the signed shift amount from the least significant byte of s. |
| // ARM64 SSHL/USHL use only bits [7:0] of the shift amount element, sign-extended. |
| func shiftAmount[T integer](s T) int8 { |
| return int8(uint8(s)) |
| } |
| |
| // shiftBy shifts x by signed amount: positive = left, negative = right. |
| func shiftBy[T integer](x T, amt int8) T { |
| a := int(amt) |
| if a > 0 { |
| return x << uint(a) |
| } |
| if a < 0 { |
| return x >> uint(-a) |
| } |
| return x |
| } |
| |
| // shiftSaturatingSigned shifts x by signed amount with signed saturation on overflow. |
| func shiftSaturatingSigned[T signed](x T, amt int8) T { |
| a := int(amt) |
| if a > 0 { |
| r := x << uint(a) |
| if r>>uint(a) != x { // overflow |
| bits := uint(unsafe.Sizeof(x)) * 8 |
| if x >= 0 { |
| return ^T(0) ^ (T(1) << (bits - 1)) // MaxSigned |
| } |
| return T(1) << (bits - 1) // MinSigned |
| } |
| return r |
| } |
| if a < 0 { |
| return x >> uint(-a) |
| } |
| return x |
| } |
| |
| // shiftSaturatingUnsigned shifts x by signed amount with unsigned saturation on overflow. |
| func shiftSaturatingUnsigned[T unsigned](x T, amt int8) T { |
| a := int(amt) |
| if a > 0 { |
| r := x << uint(a) |
| if r>>uint(a) != x { // overflow |
| return ^T(0) // MaxUnsigned |
| } |
| return r |
| } |
| if a < 0 { |
| return x >> uint(-a) |
| } |
| return x |
| } |
| |
| // Slice versions for shift operations |
| |
| // shiftSlice applies shiftBy element-wise using same-type slices. |
| func shiftSlice[T integer](x, y []T) []T { |
| return map2(func(a, b T) T { return shiftBy(a, shiftAmount(b)) })(x, y) |
| } |
| |
| // shiftMixedSlice applies shiftBy element-wise using mixed-type slices (unsigned data, signed amounts). |
| func shiftMixedSlice[D integer, S integer](x []D, y []S) []D { |
| r := make([]D, len(x)) |
| for i := range r { |
| r[i] = shiftBy(x[i], shiftAmount(y[i])) |
| } |
| return r |
| } |
| |
| // shiftSaturatingSignedSlice applies saturating shift element-wise (same-type). |
| func shiftSaturatingSignedSlice[T signed](x, y []T) []T { |
| return map2(func(a, b T) T { return shiftSaturatingSigned(a, shiftAmount(b)) })(x, y) |
| } |
| |
| // shiftSaturatingUnsignedSlice applies saturating shift element-wise (mixed-type). |
| func shiftSaturatingUnsignedSlice[D unsigned, S integer](x []D, y []S) []D { |
| r := make([]D, len(x)) |
| for i := range r { |
| r[i] = shiftSaturatingUnsigned(x[i], shiftAmount(y[i])) |
| } |
| return r |
| } |
| |
| // Slice versions for const shift operations (same constant amount for all elements) |
| |
| // shiftLeftByConstSlice shifts all elements left by constant amount. |
| func shiftLeftByConstSlice[T integer](x []T, amt uint64) []T { |
| return map1(func(a T) T { return a << amt })(x) |
| } |
| |
| // shiftRightByConstSlice shifts all elements right by constant amount. |
| // Signed types use arithmetic shift, unsigned types use logical shift. |
| func shiftRightByConstSlice[T integer](x []T, amt uint64) []T { |
| return map1(func(a T) T { return a >> amt })(x) |
| } |
| |
| // shiftLeftSaturatingByConstSlice shifts all elements left by constant amount with signed saturation. |
| func shiftLeftSaturatingByConstSlice[T signed](x []T, amt uint64) []T { |
| return map1(func(a T) T { return shiftSaturatingSigned(a, int8(amt)) })(x) |
| } |
| |
| // shiftLeftSaturatingUByConstSlice shifts all elements left by constant amount with unsigned saturation. |
| func shiftLeftSaturatingUByConstSlice[T unsigned](x []T, amt uint64) []T { |
| return map1(func(a T) T { return shiftSaturatingUnsigned(a, int8(amt)) })(x) |
| } |
| |
| // shiftAllLeftSlice shifts all elements left by the same amount. |
| func shiftAllLeftSlice[T integer](x []T, amt uint64) []T { |
| return map1(func(a T) T { return a << amt })(x) |
| } |
| |
| // shiftAllRightSlice shifts all elements right by the same amount. |
| // Signed types use arithmetic shift, unsigned types use logical shift. |
| func shiftAllRightSlice[T integer](x []T, amt uint64) []T { |
| return map1(func(a T) T { return a >> amt })(x) |
| } |
| |
| // ARM64-specific float-to-int conversion saturation helpers. |
| // ARM64 uses IEEE 754 saturation: out-of-range values clamp to min/max of the target type. |
| // NaN converts to 0. Negative values convert to 0 for unsigned types. |
| |
| func floatToInt32_arm64[T float](x T) int32 { |
| if x != x { // NaN |
| return 0 |
| } |
| if x >= math.MaxInt32 { |
| return math.MaxInt32 |
| } |
| if x < math.MinInt32 { |
| return math.MinInt32 |
| } |
| return int32(x) |
| } |
| |
| func floatToInt64_arm64[T float](x T) int64 { |
| if x != x { // NaN |
| return 0 |
| } |
| if x >= math.MaxInt64 { |
| return math.MaxInt64 |
| } |
| if x < math.MinInt64 { |
| return math.MinInt64 |
| } |
| return int64(x) |
| } |
| |
| func floatToUint32_arm64[T float](x T) uint32 { |
| if x != x || x < 0 { // NaN or negative |
| return 0 |
| } |
| if x >= math.MaxUint32 { |
| return math.MaxUint32 |
| } |
| return uint32(x) |
| } |
| |
| func floatToUint64_arm64[T float](x T) uint64 { |
| if x != x || x < 0 { // NaN or negative |
| return 0 |
| } |
| if x >= math.MaxUint64 { |
| return math.MaxUint64 |
| } |
| return uint64(x) |
| } |