Source file src/simd/internal/test_helpers/emulation.go

     1  // Copyright 2026 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build goexperiment.simd
     6  
     7  package test_helpers
     8  
     9  import (
    10  	"math"
    11  	"math/bits"
    12  	"testing"
    13  	"unsafe"
    14  )
    15  
    16  func ReduceSum[S number](s []S) S {
    17  	var x S
    18  	for _, v := range s {
    19  		x += v
    20  	}
    21  	return x
    22  }
    23  
    24  // The rounding references compute in float64, which is exact for float32
    25  // inputs too.
    26  
    27  func Round[S float](x S) S { return S(math.RoundToEven(float64(x))) }
    28  func Ceil[S float](x S) S  { return S(math.Ceil(float64(x))) }
    29  func Floor[S float](x S) S { return S(math.Floor(float64(x))) }
    30  func Trunc[S float](x S) S { return S(math.Trunc(float64(x))) }
    31  
    32  func Neg[S number](x S) S {
    33  	return -x
    34  }
    35  
    36  func Abs[S, S2 number](x S) S2 {
    37  	if x == 0 {
    38  		return 0 // this is zero not -0 if S is float
    39  	}
    40  	if x < 0 {
    41  		return S2(-x)
    42  	}
    43  	return S2(x)
    44  }
    45  
    46  func Add[S number](x, y S) S {
    47  	return x + y
    48  }
    49  
    50  func Sub[S number](x, y S) S {
    51  	return x - y
    52  }
    53  
    54  func Mul[S number](x, y S) S {
    55  	return x * y
    56  }
    57  
    58  func Div[S float](x, y S) S {
    59  	return x / y
    60  }
    61  
    62  func And[S integer](x, y S) S {
    63  	return x & y
    64  }
    65  
    66  func Or[S integer](x, y S) S {
    67  	return x | y
    68  }
    69  
    70  func Xor[S integer](x, y S) S {
    71  	return x ^ y
    72  }
    73  
    74  func AndNot[S integer](x, y S) S {
    75  	return x &^ y
    76  }
    77  
    78  func Xnor[S integer](x, y S) S {
    79  	return ^(x ^ y)
    80  }
    81  
    82  func Not[S integer](x S) S {
    83  	return ^x
    84  }
    85  
    86  func Max[S number](x, y S) S {
    87  	return max(x, y)
    88  }
    89  
    90  func Min[S number](x, y S) S {
    91  	return min(x, y)
    92  }
    93  
    94  func ShiftLeft[S integer](x S, s uint64) S {
    95  	return x << s
    96  }
    97  
    98  func ShiftRight[S integer](x S, s uint64) S {
    99  	return x >> s
   100  }
   101  
   102  func Equal[S number](x, y S) bool {
   103  	return x == y
   104  }
   105  
   106  func NotEqual[S number](x, y S) bool {
   107  	return x != y
   108  }
   109  
   110  func Less[S number](x, y S) bool {
   111  	return x < y
   112  }
   113  
   114  func LessEqual[S number](x, y S) bool {
   115  	return x <= y
   116  }
   117  
   118  func Greater[S number](x, y S) bool {
   119  	return x > y
   120  }
   121  
   122  func GreaterEqual[S number](x, y S) bool {
   123  	return x >= y
   124  }
   125  
   126  func RotateLeft[S integer](x S, dist uint64) S {
   127  	size := uint64(unsafe.Sizeof(x)) * 8
   128  	mask := (uint64(1) << size) - 1
   129  	dist = dist & (size - 1)
   130  	if dist == 0 {
   131  		return x
   132  	}
   133  	return (x << dist) | S((mask&uint64(x))>>(size-dist))
   134  }
   135  
   136  func RotateRight[S integer](x S, dist uint64) S {
   137  	size := uint64(unsafe.Sizeof(x)) * 8
   138  	mask := (uint64(1) << size) - 1
   139  	dist = dist & (size - 1)
   140  	if dist == 0 {
   141  		return x
   142  	}
   143  	return S((mask&uint64(x))>>dist) | (x << (size - dist))
   144  }
   145  
   146  func OnesCount[T integer](x T) T {
   147  	size := uint64(unsafe.Sizeof(x)) * 8
   148  	return T(bits.OnesCount64(uint64(x) & ((1 << size) - 1)))
   149  }
   150  
   151  func TrailingZeros[T integer](x T) T {
   152  	size := int(unsafe.Sizeof(x)) * 8
   153  	tz := bits.TrailingZeros64(uint64(x))
   154  	if tz > size {
   155  		tz = size
   156  	}
   157  	return T(tz)
   158  }
   159  
   160  // Vec is any vector of any number (int, uint, or float)
   161  type Vec[E number] interface {
   162  	Store(s []E)
   163  	Len() int
   164  }
   165  
   166  // Mask is any mask
   167  type Mask interface {
   168  	String() string
   169  }
   170  
   171  // IVec is a vector of signed integers, that will also have a ToMask() method.
   172  type IVec[E signed, M Mask] interface {
   173  	Store(s []E)
   174  	Len() int
   175  	ToMask() M
   176  }
   177  
   178  // The Test-XX-2-YY-[e] functions test various unary and binary vector operations.
   179  // V = 1 vector, VV = 2 vectors, VR = vector and rotation.
   180  // S = scalar.
   181  // M = mask.
   182  // A trailing "e" means that the emulation function is elementwise, or scalar.
   183  
   184  // TestV2S tests the simd Vector-To-Scalar method f against the expected behavior generated by want
   185  func TestV2S[S number, V Vec[S]](t *testing.T, slice []S, load func([]S) V, f func(_ V) S, want func([]S) S) {
   186  	var v V
   187  	n := v.Len()
   188  	t.Helper()
   189  	forSlice(t, slice, n, func(x []S) bool {
   190  		t.Helper()
   191  		a := load(x)
   192  		g := f(a)
   193  		w := want(x)
   194  		return CheckScalarsLogInput(t, g, w, 0.000001, func() { t.Helper(); t.Logf("x=%v", x) })
   195  	})
   196  }
   197  
   198  // TestV2V tests the simd Vector-To-Vector method f against the expected behavior generated by want
   199  func TestV2V[S number, V Vec[S]](t *testing.T, slice []S, load func([]S) V, f func(_ V) V, want func([]S) []S) {
   200  	var v V
   201  	n := v.Len()
   202  	t.Helper()
   203  	forSlice(t, slice, n, func(x []S) bool {
   204  		t.Helper()
   205  		a := load(x)
   206  		g := make([]S, n)
   207  		f(a).Store(g)
   208  		w := want(x)
   209  		return CheckSlicesLogInput(t, g, w, 0.000001, func() { t.Helper(); t.Logf("x=%v", x) })
   210  	})
   211  }
   212  
   213  // TestV2Ve tests the simd unary Vector-To-Vector method f against the expected behavior generated by per-Element function want.
   214  //
   215  // V2 may have a different number of elements than V, as for a conversion
   216  // that changes the element size and keeps the vector width: element i of the
   217  // result is want(x[i]) for i < min(x.Len(), result.Len()), and zero above
   218  // that.
   219  func TestV2Ve[S, S2 number, V Vec[S], V2 Vec[S2]](t *testing.T, slice []S, load func([]S) V, f func(_ V) V2, want func(S) S2) {
   220  	var v V
   221  	var v2 V2
   222  	n, n2 := v.Len(), v2.Len()
   223  	t.Helper()
   224  	forSlice(t, slice, n, func(x []S) bool {
   225  		t.Helper()
   226  		a := load(x)
   227  		g := make([]S2, n2)
   228  		f(a).Store(g)
   229  		w := ImpliedDo(n2, func(i int) S2 {
   230  			if i < n {
   231  				return want(x[i])
   232  			}
   233  			return 0
   234  		})
   235  		return CheckSlicesLogInput(t, g, w, 0.000001, func() { t.Helper(); t.Logf("x=%v", x) })
   236  	})
   237  }
   238  
   239  // TestVR2Ve tests the simd binary (Vector,Rotation)-To-Vector method f against the expected behavior generated by per-Element function want
   240  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) {
   241  	var v V
   242  	n := v.Len()
   243  	t.Helper()
   244  	for _, shift := range shifts {
   245  		forSlice(t, slice, n, func(x []S) bool {
   246  			t.Helper()
   247  			a := load(x)
   248  			g := make([]S, n)
   249  			f(a, shift).Store(g)
   250  			w := ImpliedDo(n, func(i int) S { return want(x[i], shift) })
   251  			return CheckSlicesLogInput(t, g, w, 0.000001, func() { t.Helper(); t.Logf("x=%v, dist=%v", x, shift) })
   252  		})
   253  	}
   254  }
   255  
   256  // TestVV2V tests the simd binary (vector,vector)-to-vector method f against the expected behavior generated by want
   257  func TestVV2V[S number, V Vec[S]](t *testing.T, slice []S, load func([]S) V, f func(_, _ V) V, want func(_, _ []S) []S) {
   258  	var v V
   259  	n := v.Len()
   260  	t.Helper()
   261  	forSlicePair(t, slice, n, func(x, y []S) bool {
   262  		t.Helper()
   263  		a := load(x)
   264  		b := load(y)
   265  		g := make([]S, n)
   266  		f(a, b).Store(g)
   267  		w := want(x, y)
   268  		return CheckSlicesLogInput(t, g, w, 0.000001, func() { t.Helper(); t.Logf("x=%v, y=%v", x, y) })
   269  	})
   270  }
   271  
   272  // TestVV2Ve tests the simd binary (Vector,Vector)-To-Vector method f against the expected behavior generated by per-Element function want
   273  func TestVV2Ve[S number, V Vec[S]](t *testing.T, slice []S, load func([]S) V, f func(_, _ V) V, want func(_, _ S) S) {
   274  	var v V
   275  	n := v.Len()
   276  	t.Helper()
   277  	forSlicePair(t, slice, n, func(x, y []S) bool {
   278  		t.Helper()
   279  		a := load(x)
   280  		b := load(y)
   281  		g := make([]S, n)
   282  		f(a, b).Store(g)
   283  		w := ImpliedDo(n, func(i int) S { return want(x[i], y[i]) })
   284  		return CheckSlicesLogInput(t, g, w, 0.000001, func() { t.Helper(); t.Logf("x=%v, y=%v", x, y) })
   285  	})
   286  }
   287  
   288  // TestVV2Me tests the simd comparison method f against the expected behavior generated by per-Element function want
   289  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) {
   290  	var v V
   291  	n := v.Len()
   292  	t.Helper()
   293  	forSlicePair(t, slice, n, func(x, y []S) bool {
   294  		t.Helper()
   295  		a := load(x)
   296  		b := load(y)
   297  		g := f(a, b).String()
   298  		w := ImpliedBools(n, func(i int) bool { return want(x[i], y[i]) })
   299  		return CheckStringsLogInput(t, g, w, 0.000001, func() { t.Helper(); t.Logf("x=%v, y=%v", x, y) })
   300  	})
   301  }
   302  
   303  // TestM2Me tests the simd mask unary method f against the expected behavior generated by per-Element function want
   304  func TestM2Me[S signed, M Mask, V IVec[S, M]](t *testing.T, load func([]S) V, f func(_ M) M, want func(_ S) S) {
   305  	var v V
   306  	n := v.Len()
   307  	t.Helper()
   308  	b2s := func(b bool) S {
   309  		if b {
   310  			return -1
   311  		}
   312  		return 0
   313  	}
   314  
   315  	bload := func(s []bool) V {
   316  		sl := make([]S, n)
   317  		for i := range sl {
   318  			sl[i] = b2s(s[i])
   319  		}
   320  		return load(sl)
   321  	}
   322  	forBoolSlice(t, Bools(), n, func(x []bool) bool {
   323  		t.Helper()
   324  		a := bload(x)
   325  		g := f(a.ToMask()).String()
   326  		w := ImpliedBools(n, func(i int) bool { return 0 != want(b2s(x[i])) })
   327  		return CheckStringsLogInput(t, g, w, 0.000001, func() { t.Helper(); t.Logf("x=%v", x) })
   328  	})
   329  }
   330  
   331  // TestMM2Me tests the simd mask binary method f against the expected behavior generated by per-Element function want
   332  func TestMM2Me[S signed, M Mask, V IVec[S, M]](t *testing.T, load func([]S) V, f func(_, _ M) M, want func(_, _ S) S) {
   333  	var v V
   334  	n := v.Len()
   335  	t.Helper()
   336  	b2s := func(b bool) S {
   337  		if b {
   338  			return -1
   339  		}
   340  		return 0
   341  	}
   342  
   343  	bload := func(s []bool) V {
   344  		sl := make([]S, n)
   345  		for i := range sl {
   346  			sl[i] = b2s(s[i])
   347  		}
   348  		return load(sl)
   349  	}
   350  	forBoolSlicePair(t, Bools(), n, func(x, y []bool) bool {
   351  		t.Helper()
   352  		a := bload(x)
   353  		b := bload(y)
   354  		g := f(a.ToMask(), b.ToMask()).String()
   355  		w := ImpliedBools(n, func(i int) bool { return 0 != want(b2s(x[i]), b2s(y[i])) })
   356  		return CheckStringsLogInput(t, g, w, 0.000001, func() { t.Helper(); t.Logf("x=%v, y=%v", x, y) })
   357  	})
   358  }
   359  

View as plain text