Source file src/simd/archsimd/internal/simd_test/helpers_test.go

     1  // Copyright 2025 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 simd_test
     8  
     9  import (
    10  	"fmt"
    11  	"math"
    12  	"reflect"
    13  	"simd/archsimd/internal/test_helpers"
    14  	"testing"
    15  	"unsafe"
    16  )
    17  
    18  type signed interface {
    19  	~int | ~int8 | ~int16 | ~int32 | ~int64
    20  }
    21  
    22  type unsigned interface {
    23  	~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
    24  }
    25  
    26  type integer interface {
    27  	~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
    28  }
    29  
    30  type float interface {
    31  	~float32 | ~float64
    32  }
    33  
    34  type number interface {
    35  	~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64
    36  }
    37  
    38  func checkSlices[T number](t *testing.T, got, want []T) bool {
    39  	t.Helper()
    40  	return test_helpers.CheckSlicesLogInput[T](t, got, want, 0.0, nil)
    41  }
    42  
    43  func checkSlicesLogInput[T number](t *testing.T, got, want []T, flakiness float64, logInput func()) bool {
    44  	t.Helper()
    45  	return test_helpers.CheckSlicesLogInput[T](t, got, want, flakiness, logInput)
    46  }
    47  
    48  // sliceOf returns a slice n T's, with each
    49  // element of the slice initialized to its
    50  // index + 1.
    51  func sliceOf[T number](n int) []T {
    52  	s := make([]T, n)
    53  	for i := 0; i < n; i++ {
    54  		s[i] = T(i + 1)
    55  	}
    56  	return s
    57  }
    58  
    59  func toVect[T signed](b []bool) []T {
    60  	s := make([]T, len(b))
    61  	for i := range b {
    62  		if b[i] {
    63  			s[i] = -1
    64  		}
    65  	}
    66  	return s
    67  }
    68  
    69  // s64 converts a slice of some integer type into a slice of int64
    70  func s64[T number](s []T) []int64 {
    71  	var is any = s
    72  	if r, ok := is.([]int64); ok {
    73  		return r
    74  	}
    75  	r := make([]int64, len(s))
    76  	for i := range s {
    77  		r[i] = int64(s[i])
    78  	}
    79  	return r
    80  }
    81  
    82  // Do implements slice part testing.  It repeatedly calls
    83  // body on smaller and smaller slices and an output slice
    84  // for the result, then compares the result to its own
    85  // calculation of what the result should be.
    86  func Do[T number](t *testing.T, n int, body func(a, c []T)) {
    87  	a := sliceOf[T](n)
    88  	b := sliceOf[T](n)
    89  
    90  	for i := n; i >= 0; i-- {
    91  		c := make([]T, n, n)
    92  		body(a[:i], c)
    93  		checkSlices(t, c, b)
    94  		if i > 0 {
    95  			b[i-1] = T(0)
    96  		}
    97  	}
    98  }
    99  
   100  // map3 returns a function that returns the slice of the results of applying
   101  // input parameter elem to the respective elements of its 3 slice inputs.
   102  func map3[T, U any](elem func(x, y, z T) U) func(x, y, z []T) []U {
   103  	return func(x, y, z []T) []U {
   104  		s := make([]U, len(x))
   105  		for i := range s {
   106  			s[i] = elem(x[i], y[i], z[i])
   107  		}
   108  		return s
   109  	}
   110  }
   111  
   112  // map2 returns a function that returns the slice of the results of applying
   113  // input parameter elem to the respective elements of its 2 slice inputs.
   114  func map2[T, U any](elem func(x, y T) U) func(x, y []T) []U {
   115  	return func(x, y []T) []U {
   116  		s := make([]U, len(x))
   117  		for i := range s {
   118  			s[i] = elem(x[i], y[i])
   119  		}
   120  		return s
   121  	}
   122  }
   123  
   124  // map1 returns a function that returns the slice of the results of applying
   125  // input parameter elem to the respective elements of its single slice input.
   126  func map1[T, U any](elem func(x T) U) func(x []T) []U {
   127  	return func(x []T) []U {
   128  		s := make([]U, len(x))
   129  		for i := range s {
   130  			s[i] = elem(x[i])
   131  		}
   132  		return s
   133  	}
   134  }
   135  
   136  // map1n returns a function that returns the slice of the results of applying
   137  // input parameter elem to the respective elements of its single slice input,
   138  // extended (with zero values) or truncated to length n.
   139  func map1n[T, U any](elem func(x T) U, n int) func(x []T) []U {
   140  	return func(x []T) []U {
   141  		s := make([]U, n)
   142  		for i := range min(len(x), n) {
   143  			s[i] = elem(x[i])
   144  		}
   145  		return s
   146  	}
   147  }
   148  
   149  // mapCompare returns a function that returns the slice of the results of applying
   150  // comparison function elem to the respective elements of its two slice inputs,
   151  // and returns -1 if the comparison is true, 0 otherwise.
   152  func mapCompare[T number](elem func(x, y T) bool) func(x, y []T) []int64 {
   153  	return func(x, y []T) []int64 {
   154  		s := make([]int64, len(x))
   155  		for i := range s {
   156  			if elem(x[i], y[i]) {
   157  				s[i] = -1
   158  			}
   159  		}
   160  		return s
   161  	}
   162  }
   163  
   164  // nOf returns a slice of length n whose elements are taken
   165  // from input slice s.
   166  func nOf[T any](n int, s []T) []T {
   167  	if len(s) >= n {
   168  		return s
   169  	}
   170  	r := make([]T, n)
   171  	for i := range r {
   172  		r[i] = s[i%len(s)]
   173  	}
   174  	return r
   175  }
   176  
   177  // grouped2 takes a function that applies to a 128-bit group and returns a
   178  // function that applies to arbitrary length vectors.
   179  func grouped2[T any](fg func(xg, yg []T) (zg []T)) func(x, y []T) []T {
   180  	return func(x, y []T) []T {
   181  		z := make([]T, len(x))
   182  		groupElems := 128 / int(8*unsafe.Sizeof(*new(T)))
   183  		for i := 0; i < len(z); i += groupElems {
   184  			zg := fg(x[i:][:groupElems], y[i:][:groupElems])
   185  			if len(zg) != groupElems {
   186  				panic(fmt.Sprintf("got %d elements, want %d", len(zg), groupElems))
   187  			}
   188  			copy(z[i:], zg)
   189  		}
   190  		return z
   191  	}
   192  }
   193  
   194  const (
   195  	PN22  = 1.0 / 1024 / 1024 / 4
   196  	PN24  = 1.0 / 1024 / 1024 / 16
   197  	PN53  = PN24 * PN24 / 32
   198  	F0    = float32(1.0 + 513*PN22/2)
   199  	F1    = float32(1.0 + 511*PN22*8)
   200  	Aeasy = float32(2046 * PN53)
   201  	Ahard = float32(2047 * PN53) // 2047 provokes a 2-rounding in 64-bit FMA rounded to 32-bit
   202  )
   203  
   204  var zero = 0.0
   205  var nzero = -zero
   206  var inf = 1 / zero
   207  var ninf = -1 / zero
   208  var nan = math.NaN()
   209  var snan32 = math.Float32frombits(0x7f800001)
   210  var snan64 = math.Float64frombits(0x7ff0000000000001)
   211  
   212  // N controls how large the test vectors are
   213  const N = 144
   214  
   215  var float32s = nOf(N, []float32{float32(inf), float32(ninf), 1, float32(nan), snan32, -float32(nan), -snan32, float32(zero), 2, float32(nan), float32(zero), 3, float32(-zero), float32(1.0 / zero), float32(-1.0 / zero), 1.0 / 2, 1.0 / 4, 1.0 / 8, 1.0 / 1000, 1.0 / 1000000, 1, -1, 0, 2, -2, 3, -3, math.MaxFloat32, 1 / math.MaxFloat32, 10, -10, 100, 20, -20, 300, -300, -4000, -80, -160, -3200, -64, -4, -8, -16, -32, -64})
   216  var float64s = nOf(N, []float64{inf, ninf, nan, snan64, -nan, -snan64, zero, -zero, 1 / zero, -1 / zero, 0.0001, 0.0000001, 1, -1, 0, 2, -2, 3, -3, math.MaxFloat64, 1.0 / math.MaxFloat64, 10, -10, 100, 20, -20, 300, -300, -4000, -80, -16, -32, -64})
   217  
   218  var int32s = nOf(N, []int32{1, -1, 0, 2, 4, 8, 1024, 0xffffff, -0xffffff, 0x55555, 0x77777, 0xccccc, -0x55555, -0x77777, -0xccccc, -4, -8, -16, -32, -64})
   219  var uint32s = nOf(N, []uint32{1, 0, 2, 4, 8, 1024, 0xffffff, ^uint32(0xffffff), 0x55555, 0x77777, 0xccccc, ^uint32(0x55555), ^uint32(0x77777), ^uint32(0xccccc)})
   220  
   221  var int64s = nOf(N, []int64{1, -1, 0, 2, 4, 8, 1024, 0xffffff, -0xffffff, 0x55555, 0x77777, 0xccccc, -0x55555, -0x77777, -0xccccc, -4, -8, -16, -32, -64})
   222  var uint64s = nOf(N, []uint64{1, 0, 2, 4, 8, 1024, 0xffffff, ^uint64(0xffffff), 0x55555, 0x77777, 0xccccc, ^uint64(0x55555), ^uint64(0x77777), ^uint64(0xccccc)})
   223  
   224  var int16s = nOf(N, []int16{1, -1, 0, 2, 4, 8, 1024, 3, 5, 7, 11, 13, 3000, 5555, 7777, 11111, 32767, 32766, -32767, -32768, -11111, -4, -8, -16, -32, -64})
   225  var uint16s = nOf(N, []uint16{1, 0, 2, 4, 8, 1024, 3, 5, 7, 11, 13, 3000, 5555, 7777, 11111, 32767, 32766, 32768, 65535, 45678, 56789})
   226  
   227  var int8s = nOf(N, []int8{0, 1, 2, 3, 5, 7, 11, 22, 33, 55, 77, 121, 127, -1, -2, -3, -5, -7, -11, -77, -121, -127, -128, 4, 8, 16, 32, 64, -4, -8, -16, -32, -64})
   228  var uint8s = nOf(N, []uint8{0, 1, 2, 3, 5, 7, 11, 22, 33, 55, 77, 121, 127, 128, 255, 233, 211, 177, 144, 4, 8, 16, 32, 64})
   229  
   230  var bools = nOf(N, []bool{
   231  	true, false, true, true, false, false, true, true, true, false, false, false, true, true, true, true, false, false, false, false})
   232  
   233  func forSlice[T number](t *testing.T, s []T, n int, f func(a []T) bool) {
   234  	t.Helper()
   235  	for i := 0; i < len(s)-n; i++ {
   236  		if !f(s[i : i+n]) {
   237  			return
   238  		}
   239  	}
   240  }
   241  
   242  func forSlicePair[T number](t *testing.T, s []T, n int, f func(a, b []T) bool) {
   243  	t.Helper()
   244  	for i := 0; i < len(s)-n; i++ {
   245  		for j := 0; j < len(s)-n; j++ {
   246  			if !f(s[i:i+n], s[j:j+n]) {
   247  				return
   248  			}
   249  		}
   250  	}
   251  }
   252  
   253  func forSliceTriple[T number](t *testing.T, s []T, n int, f func(a, b, c []T) bool) {
   254  	t.Helper()
   255  	for i := 0; i < len(s)-n; i += 3 {
   256  		for j := 0; j < len(s)-n; j += 3 {
   257  			for k := 0; k < len(s)-n; k += 3 {
   258  				if !f(s[i:i+n], s[j:j+n], s[k:k+n]) {
   259  					return
   260  				}
   261  			}
   262  		}
   263  	}
   264  }
   265  
   266  func forSliceMixed[D, S number](t *testing.T, d []D, s []S, n int, f func(a []D, b []S) bool) {
   267  	t.Helper()
   268  	for i := 0; i < len(d)-n; i++ {
   269  		for j := 0; j < len(s)-n; j++ {
   270  			if !f(d[i:i+n], s[j:j+n]) {
   271  				return
   272  			}
   273  		}
   274  	}
   275  }
   276  
   277  func forSlicePairMasked[T number](t *testing.T, s []T, n int, f func(a, b []T, m []bool) bool) {
   278  	t.Helper()
   279  	m := bools
   280  	// Step slice pair masked forward much more quickly, otherwise it is slooooow
   281  	for i := 0; i < len(s)-n; i += 3 {
   282  		for j := 0; j < len(s)-n; j += 3 {
   283  			for k := 0; k < len(m)-n; k += 3 {
   284  				if !f(s[i:i+n], s[j:j+n], m[k:k+n]) {
   285  					return
   286  				}
   287  			}
   288  		}
   289  	}
   290  }
   291  
   292  //go:noinline
   293  func hideConst[T number](x T) T {
   294  	return x
   295  }
   296  
   297  func testStorePartRV[T number, V any](t *testing.T, name string, n int, val V, storePart func(v V, s []T) int) {
   298  	t.Helper()
   299  	// empty slice
   300  	{
   301  		s := make([]T, 0)
   302  		rv := storePart(val, s)
   303  		if rv != 0 {
   304  			t.Errorf("%s: StorePart on empty slice returned %d, expected 0", name, rv)
   305  		}
   306  	}
   307  
   308  	// single-element slice
   309  	{
   310  		s := make([]T, 1)
   311  		rv := storePart(val, s)
   312  		if rv != 1 {
   313  			t.Errorf("%s: StorePart on 1-element slice returned %d, expected 1", name, rv)
   314  		}
   315  	}
   316  
   317  	// longer-than-vector slice
   318  	{
   319  		s := make([]T, n+5)
   320  		rv := storePart(val, s)
   321  		if rv != n {
   322  			t.Errorf("%s: StorePart on %d-element slice (longer than %d) returned %d, expected %d", name, n+5, n, rv, n)
   323  		}
   324  	}
   325  }
   326  
   327  type HasLenAndStorePart[T number] interface {
   328  	StorePart(s []T) int
   329  	Len() int
   330  }
   331  
   332  func testStorePartReturnValue[T number, V HasLenAndStorePart[T]](t *testing.T) {
   333  	t.Helper()
   334  	var v V
   335  	n := v.Len()
   336  	name := reflect.TypeOf(v).Name()
   337  	// empty slice
   338  	{
   339  		s := make([]T, 0)
   340  		rv := v.StorePart(s)
   341  		if rv != 0 {
   342  			t.Errorf("%s: StorePart on empty slice returned %d, expected 0", name, rv)
   343  		}
   344  	}
   345  
   346  	// single-element slice
   347  	{
   348  		s := make([]T, 1)
   349  		rv := v.StorePart(s)
   350  		if rv != 1 {
   351  			t.Errorf("%s: StorePart on 1-element slice returned %d, expected 1", name, rv)
   352  		}
   353  	}
   354  
   355  	// longer-than-vector slice
   356  	{
   357  		s := make([]T, n+5)
   358  		rv := v.StorePart(s)
   359  		if rv != n {
   360  			t.Errorf("%s: StorePart on %d-element slice (longer than %d) returned %d, expected %d", name, n+5, n, rv, n)
   361  		}
   362  	}
   363  	t.Logf("tested %s", name)
   364  }
   365  

View as plain text