| // 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. |
| |
| //go:build goexperiment.simd |
| |
| package test_helpers |
| |
| import ( |
| "math/bits" |
| "testing" |
| "unsafe" |
| ) |
| |
| func ReduceSum[S number](s []S) S { |
| var x S |
| for _, v := range s { |
| x += v |
| } |
| return x |
| } |
| |
| func Neg[S number](x S) S { |
| return -x |
| } |
| |
| func Abs[S number](x S) S { |
| if x == 0 { |
| return 0 // this is zero not -0 if S is float |
| } |
| if x < 0 { |
| return -x |
| } |
| return x |
| } |
| |
| func Add[S number](x, y S) S { |
| return x + y |
| } |
| |
| func Sub[S number](x, y S) S { |
| return x - y |
| } |
| |
| func Mul[S number](x, y S) S { |
| return x * y |
| } |
| |
| func Div[S float](x, y S) S { |
| return x / y |
| } |
| |
| func And[S integer](x, y S) S { |
| return x & y |
| } |
| |
| func Or[S integer](x, y S) S { |
| return x | y |
| } |
| |
| func Xor[S integer](x, y S) S { |
| return x ^ y |
| } |
| |
| func AndNot[S integer](x, y S) S { |
| return x &^ y |
| } |
| |
| func Max[S number](x, y S) S { |
| return max(x, y) |
| } |
| |
| func Min[S number](x, y S) S { |
| return min(x, y) |
| } |
| |
| func ShiftLeft[S integer](x S, s uint64) S { |
| return x << s |
| } |
| |
| func ShiftRight[S integer](x S, s uint64) S { |
| return x >> s |
| } |
| |
| func Equal[S number](x, y S) bool { |
| return x == y |
| } |
| |
| func NotEqual[S number](x, y S) bool { |
| return x != y |
| } |
| |
| func Less[S number](x, y S) bool { |
| return x < y |
| } |
| |
| func LessEqual[S number](x, y S) bool { |
| return x <= y |
| } |
| |
| func Greater[S number](x, y S) bool { |
| return x > y |
| } |
| |
| func GreaterEqual[S number](x, y S) bool { |
| return x >= y |
| } |
| |
| func RotateLeft[S integer](x S, dist uint64) S { |
| size := uint64(unsafe.Sizeof(x)) * 8 |
| mask := (uint64(1) << size) - 1 |
| dist = dist & (size - 1) |
| if dist == 0 { |
| return x |
| } |
| return (x << dist) | S((mask&uint64(x))>>(size-dist)) |
| } |
| |
| func RotateRight[S integer](x S, dist uint64) S { |
| size := uint64(unsafe.Sizeof(x)) * 8 |
| mask := (uint64(1) << size) - 1 |
| dist = dist & (size - 1) |
| if dist == 0 { |
| return x |
| } |
| return S((mask&uint64(x))>>dist) | (x << (size - dist)) |
| } |
| |
| func OnesCount[T integer](x T) T { |
| size := uint64(unsafe.Sizeof(x)) * 8 |
| return T(bits.OnesCount64(uint64(x) & ((1 << size) - 1))) |
| } |
| |
| func TrailingZeros[T integer](x T) T { |
| size := int(unsafe.Sizeof(x)) * 8 |
| tz := bits.TrailingZeros64(uint64(x)) |
| if tz > size { |
| tz = size |
| } |
| return T(tz) |
| } |
| |
| type Vec[E number] interface { |
| Store(s []E) |
| Len() int |
| } |
| |
| type Mask interface { |
| String() string |
| } |
| |
| // The Test-XX-2-YY-[e] functions test various unary and binary vector operations. |
| // V = 1 vector, VV = 2 vectors, VR = vector and rotation |
| // S = scalar. |
| // A trailing "e" means that the emulation function is elementwise, or scalar. |
| |
| // TestV2S tests the simd Vector-To-Scalar method f against the expected behavior generated by want |
| func TestV2S[S number, V Vec[S]](t *testing.T, slice []S, load func([]S) V, f func(_ V) S, want func([]S) S) { |
| var v V |
| n := v.Len() |
| t.Helper() |
| forSlice(t, slice, n, func(x []S) bool { |
| t.Helper() |
| a := load(x) |
| g := f(a) |
| w := want(x) |
| return CheckScalarsLogInput(t, g, w, 0.000001, func() { t.Helper(); t.Logf("x=%v", x) }) |
| }) |
| } |
| |
| // TestV2V tests the simd Vector-To-Vector method f against the expected behavior generated by want |
| func TestV2V[S number, V Vec[S]](t *testing.T, slice []S, load func([]S) V, f func(_ V) V, want func([]S) []S) { |
| var v V |
| n := v.Len() |
| t.Helper() |
| forSlice(t, slice, n, func(x []S) bool { |
| t.Helper() |
| a := load(x) |
| g := make([]S, n) |
| f(a).Store(g) |
| w := want(x) |
| return CheckSlicesLogInput(t, g, w, 0.000001, func() { t.Helper(); t.Logf("x=%v", x) }) |
| }) |
| } |
| |
| // TestV2Ve tests the simd unary Vector-To-Vector method f against the expected behavior generated by per-Element function want |
| func TestV2Ve[S number, V Vec[S]](t *testing.T, slice []S, load func([]S) V, f func(_ V) V, want func(S) S) { |
| var v V |
| n := v.Len() |
| t.Helper() |
| forSlice(t, slice, n, func(x []S) bool { |
| t.Helper() |
| a := load(x) |
| g := make([]S, n) |
| f(a).Store(g) |
| w := ImpliedDo(n, func(i int) S { return want(x[i]) }) |
| return CheckSlicesLogInput(t, g, w, 0.000001, func() { t.Helper(); t.Logf("x=%v", x) }) |
| }) |
| } |
| |
| // TestVR2Ve tests the simd binary (Vector,Rotation)-To-Vector method f against the expected behavior generated by per-Element function want |
| func TestVR2Ve[S number, V Vec[S]](t *testing.T, slice []S, shifts []uint64, load func([]S) V, f func(V, uint64) V, want func(S, uint64) S) { |
| var v V |
| n := v.Len() |
| t.Helper() |
| for _, shift := range shifts { |
| forSlice(t, slice, n, func(x []S) bool { |
| t.Helper() |
| a := load(x) |
| g := make([]S, n) |
| f(a, shift).Store(g) |
| w := ImpliedDo(n, func(i int) S { return want(x[i], shift) }) |
| return CheckSlicesLogInput(t, g, w, 0.000001, func() { t.Helper(); t.Logf("x=%v, dist=%v", x, shift) }) |
| }) |
| } |
| } |
| |
| // TestVV2V tests the simd binary (vector,vector)-to-vector method f against the expected behavior generated by want |
| func TestVV2V[S number, V Vec[S]](t *testing.T, slice []S, load func([]S) V, f func(_, _ V) V, want func(_, _ []S) []S) { |
| var v V |
| n := v.Len() |
| t.Helper() |
| forSlicePair(t, slice, n, func(x, y []S) bool { |
| t.Helper() |
| a := load(x) |
| b := load(y) |
| g := make([]S, n) |
| f(a, b).Store(g) |
| w := want(x, y) |
| return CheckSlicesLogInput(t, g, w, 0.000001, func() { t.Helper(); t.Logf("x=%v, y=%v", x, y) }) |
| }) |
| } |
| |
| // TestVV2Ve tests the simd binary (Vector,Vector)-To-Vector method f against the expected behavior generated by per-Element function want |
| func TestVV2Ve[S number, V Vec[S]](t *testing.T, slice []S, load func([]S) V, f func(_, _ V) V, want func(_, _ S) S) { |
| var v V |
| n := v.Len() |
| t.Helper() |
| forSlicePair(t, slice, n, func(x, y []S) bool { |
| t.Helper() |
| a := load(x) |
| b := load(y) |
| g := make([]S, n) |
| f(a, b).Store(g) |
| w := ImpliedDo(n, func(i int) S { return want(x[i], y[i]) }) |
| return CheckSlicesLogInput(t, g, w, 0.000001, func() { t.Helper(); t.Logf("x=%v, y=%v", x, y) }) |
| }) |
| } |
| |
| // TestVV2Me tests the simd comparison method f against the expected behavior generated by bool want |
| func TestVV2Me[S number, V Vec[S], M Mask](t *testing.T, slice []S, load func([]S) V, f func(_, _ V) M, want func(_, _ S) bool) { |
| var v V |
| n := v.Len() |
| t.Helper() |
| forSlicePair(t, slice, n, func(x, y []S) bool { |
| t.Helper() |
| a := load(x) |
| b := load(y) |
| g := f(a, b).String() |
| w := ImpliedBools(n, func(i int) bool { return want(x[i], y[i]) }) |
| return CheckStringsLogInput(t, g, w, 0.000001, func() { t.Helper(); t.Logf("x=%v, y=%v", x, y) }) |
| }) |
| } |