Help us shape the future of Go! Take the 2026 Go Developer Survey

Source file src/simd/internal/spec/variation_test.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  package spec
     6  
     7  import (
     8  	"math"
     9  	"testing"
    10  )
    11  
    12  func f32(bits uint32) float32 { return math.Float32frombits(bits) }
    13  func f64(bits uint64) float64 { return math.Float64frombits(bits) }
    14  
    15  // Interesting float32 values.
    16  var (
    17  	negZero32  = f32(0x80000000)
    18  	minSub32   = f32(0x00000001) // Smallest positive subnormal
    19  	maxSub32   = f32(0x007fffff) // Largest subnormal
    20  	minNorm32  = f32(0x00800000) // Smallest positive normal
    21  	inf32      = f32(0x7f800000)
    22  	qnan32     = f32(0x7fc00000) // Quiet NaN
    23  	qnanPay32  = f32(0x7fc00123) // Quiet NaN with a payload
    24  	snan32     = f32(0x7f800001) // Signaling NaN
    25  	negQnan32  = f32(0xffc00000) // Quiet NaN with the sign bit set
    26  	maxFloat32 = float32(math.MaxFloat32)
    27  )
    28  
    29  // Interesting float64 values.
    30  var (
    31  	negZero64 = f64(0x8000000000000000)
    32  	minSub64  = f64(0x0000000000000001)
    33  	maxSub64  = f64(0x000fffffffffffff)
    34  	minNorm64 = f64(0x0010000000000000)
    35  	inf64     = math.Inf(1)
    36  	qnan64    = f64(0x7ff8000000000000)
    37  	qnanPay64 = f64(0x7ff8000000000123)
    38  	snan64    = f64(0x7ff0000000000001)
    39  	negQnan64 = f64(0xfff8000000000000)
    40  )
    41  
    42  // allPlatforms is every concrete platform, plus PlatformAny.
    43  func allPlatforms() []Platform {
    44  	var ps []Platform
    45  	for p := range numPlatforms {
    46  		ps = append(ps, p)
    47  	}
    48  	return append(ps, PlatformAny)
    49  }
    50  
    51  // checkPermits checks that, on plat, v permits every value in yes and none of
    52  // the values in no.
    53  func checkPermits[E EltOrMask](t *testing.T, v Variation[E], plat Platform, yes, no []E) {
    54  	t.Helper()
    55  	for _, x := range yes {
    56  		if !v.Permits(x, plat) {
    57  			t.Errorf("%v on %v: %v not permitted, want permitted", v, plat, x)
    58  		}
    59  	}
    60  	for _, x := range no {
    61  		if v.Permits(x, plat) {
    62  			t.Errorf("%v on %v: %v permitted, want not permitted", v, plat, x)
    63  		}
    64  	}
    65  }
    66  
    67  // checkPermitsAll is checkPermits on every platform.
    68  func checkPermitsAll[E EltOrMask](t *testing.T, v Variation[E], yes, no []E) {
    69  	t.Helper()
    70  	for _, p := range allPlatforms() {
    71  		checkPermits(t, v, p, yes, no)
    72  	}
    73  }
    74  
    75  func checkCanon[E EltOrMask](t *testing.T, v Variation[E], want E) {
    76  	t.Helper()
    77  	if got := v.Canon(); !equalExact(got, want) {
    78  		t.Errorf("%v: Canon() = %v, want %v", v, got, want)
    79  	}
    80  }
    81  
    82  func checkString[E EltOrMask](t *testing.T, v Variation[E], want string) {
    83  	t.Helper()
    84  	if got := v.String(); got != want {
    85  		t.Errorf("String() = %q, want %q", got, want)
    86  	}
    87  }
    88  
    89  func mustPanic(t *testing.T, name string, f func()) {
    90  	t.Helper()
    91  	defer func() {
    92  		if recover() == nil {
    93  			t.Errorf("%s did not panic", name)
    94  		}
    95  	}()
    96  	f()
    97  }
    98  
    99  type equalExactCase[E EltOrMask] struct {
   100  	a, b E
   101  	want bool
   102  }
   103  
   104  func checkEqualExact[E EltOrMask](t *testing.T, cases []equalExactCase[E]) {
   105  	t.Helper()
   106  	for _, c := range cases {
   107  		// equalExact must be symmetric.
   108  		if got := equalExact(c.a, c.b); got != c.want {
   109  			t.Errorf("equalExact(%v, %v) = %v, want %v", c.a, c.b, got, c.want)
   110  		}
   111  		if got := equalExact(c.b, c.a); got != c.want {
   112  			t.Errorf("equalExact(%v, %v) = %v, want %v", c.b, c.a, got, c.want)
   113  		}
   114  	}
   115  }
   116  
   117  func TestEqualExact(t *testing.T) {
   118  	checkEqualExact(t, []equalExactCase[int8]{
   119  		{-1, -1, true},
   120  		{-1, 1, false},
   121  		{math.MinInt8, math.MinInt8, true},
   122  		{math.MinInt8, math.MaxInt8, false},
   123  	})
   124  	checkEqualExact(t, []equalExactCase[uint64]{
   125  		{math.MaxUint64, math.MaxUint64, true},
   126  		{math.MaxUint64, math.MaxUint64 - 1, false},
   127  	})
   128  	checkEqualExact(t, []equalExactCase[Mask32]{
   129  		{^Mask32(0), ^Mask32(0), true},
   130  		{0, 0, true},
   131  		{0, ^Mask32(0), false},
   132  	})
   133  
   134  	checkEqualExact(t, []equalExactCase[float32]{
   135  		{1, 1, true},
   136  		{1, math.Nextafter32(1, 2), false},
   137  		// Signed zeroes compare equal with ==, but are not identical.
   138  		{0, negZero32, false},
   139  		{negZero32, negZero32, true},
   140  		// Subnormals are distinct from zero and from each other.
   141  		{minSub32, minSub32, true},
   142  		{minSub32, 0, false},
   143  		{maxSub32, minNorm32, false},
   144  		{inf32, inf32, true},
   145  		{inf32, -inf32, false},
   146  		{inf32, maxFloat32, false},
   147  		// All NaNs are considered equal, regardless of payload, quiet vs.
   148  		// signaling, or sign, even though NaN != NaN.
   149  		{qnan32, qnan32, true},
   150  		{qnan32, qnanPay32, true},
   151  		{qnan32, snan32, true},
   152  		{qnan32, negQnan32, true},
   153  		{snan32, negQnan32, true},
   154  		{qnan32, 0, false},
   155  		{qnan32, inf32, false},
   156  		{snan32, inf32, false}, // These differ in only the lowest bit.
   157  	})
   158  
   159  	checkEqualExact(t, []equalExactCase[float64]{
   160  		{1, 1, true},
   161  		{1, math.Nextafter(1, 2), false},
   162  		{0, negZero64, false},
   163  		{negZero64, negZero64, true},
   164  		{minSub64, minSub64, true},
   165  		{minSub64, 0, false},
   166  		{maxSub64, minNorm64, false},
   167  		{inf64, inf64, true},
   168  		{inf64, -inf64, false},
   169  		{inf64, math.MaxFloat64, false},
   170  		{qnan64, qnan64, true},
   171  		{qnan64, qnanPay64, true},
   172  		{qnan64, snan64, true},
   173  		{qnan64, negQnan64, true},
   174  		{snan64, negQnan64, true},
   175  		{qnan64, 0, false},
   176  		{qnan64, inf64, false},
   177  		{snan64, inf64, false},
   178  	})
   179  }
   180  
   181  func TestExact(t *testing.T) {
   182  	v := exact[int32](5)
   183  	checkCanon(t, v, 5)
   184  	checkString(t, v, "5")
   185  	checkPermitsAll(t, v, []int32{5}, []int32{4, 6, 0})
   186  
   187  	// exact compares like equalExact.
   188  	checkPermitsAll(t, exact(qnan32), []float32{qnan32, snan32, negQnan32}, []float32{0, inf32})
   189  	checkPermitsAll(t, exact(float32(0)), []float32{0}, []float32{negZero32})
   190  
   191  	// The zero Variation is the exact zero value.
   192  	var zero Variation[int16]
   193  	checkCanon(t, zero, 0)
   194  	checkPermitsAll(t, zero, []int16{0}, []int16{1, -1})
   195  }
   196  
   197  func TestPerPlatform(t *testing.T) {
   198  	v := perPlatform(map[Platform]int32{
   199  		PlatformARM64: 2,
   200  		PlatformWASM:  3,
   201  	})
   202  	// The canonical value is the value for the smallest present platform.
   203  	checkCanon(t, v, 2)
   204  	checkString(t, v, "{arm64:2 wasm:3}")
   205  
   206  	checkPermits(t, v, PlatformARM64, []int32{2}, []int32{3, 0})
   207  	checkPermits(t, v, PlatformWASM, []int32{3}, []int32{2, 0})
   208  	// A platform without a value permits nothing.
   209  	checkPermits(t, v, PlatformAMD64, nil, []int32{2, 3, 0})
   210  	// PlatformAny permits any platform's value.
   211  	checkPermits(t, v, PlatformAny, []int32{2, 3}, []int32{0})
   212  	// Unknown platforms permit nothing.
   213  	checkPermits(t, v, numPlatforms, nil, []int32{2, 3})
   214  	checkPermits(t, v, Platform(-2), nil, []int32{2, 3})
   215  
   216  	// Values compare like equalExact.
   217  	vf := perPlatform(map[Platform]float32{
   218  		PlatformAMD64: qnan32,
   219  		PlatformARM64: 0,
   220  	})
   221  	checkPermits(t, vf, PlatformAMD64, []float32{snan32}, []float32{0})
   222  	checkPermits(t, vf, PlatformARM64, []float32{0}, []float32{negZero32, qnan32})
   223  
   224  	mustPanic(t, "perPlatform(empty)", func() { perPlatform(map[Platform]int32{}) })
   225  	mustPanic(t, "perPlatform(no recognized platforms)", func() {
   226  		perPlatform(map[Platform]int32{PlatformAny: 1})
   227  	})
   228  }
   229  
   230  func TestAnyOf(t *testing.T) {
   231  	// A single value is just exact.
   232  	v1 := anyOf[int32](7)
   233  	checkCanon(t, v1, 7)
   234  	checkString(t, v1, "7")
   235  	checkPermitsAll(t, v1, []int32{7}, []int32{6, 8})
   236  
   237  	v := anyOf[int32](1, 2, 3)
   238  	// The canonical value is the first value.
   239  	checkCanon(t, v, 1)
   240  	checkString(t, v, "{1|2|3}")
   241  	checkPermitsAll(t, v, []int32{1, 2, 3}, []int32{0, 4})
   242  
   243  	// Values compare like equalExact.
   244  	checkPermitsAll(t, anyOf(float32(0), qnan32), []float32{0, snan32}, []float32{negZero32, 1})
   245  
   246  	mustPanic(t, "anyOf()", func() { anyOf[int32]() })
   247  }
   248  
   249  func TestUndef(t *testing.T) {
   250  	v := undef[int8]()
   251  	checkCanon(t, v, 0)
   252  	checkString(t, v, "undef")
   253  	checkPermitsAll(t, v, []int8{0, 1, -1, math.MinInt8, math.MaxInt8}, nil)
   254  
   255  	checkPermitsAll(t, undef[float32](), []float32{0, negZero32, inf32, -inf32, qnan32, snan32}, nil)
   256  }
   257  
   258  func TestULPs(t *testing.T) {
   259  	t.Run("float32", func(t *testing.T) {
   260  		// up returns the float32 n representable values above x (below if n is
   261  		// negative), assuming no sign change.
   262  		up := func(x float32, n int32) float32 {
   263  			return f32(uint32(int32(math.Float32bits(x)) + n))
   264  		}
   265  
   266  		// ulps(x, 0) is exact.
   267  		v0 := ulps(float32(1), 0)
   268  		checkString(t, v0, "1")
   269  		checkPermitsAll(t, v0, []float32{1}, []float32{up(1, 1), up(1, -1)})
   270  
   271  		v := ulps(float32(1), 2)
   272  		checkCanon(t, v, 1)
   273  		checkString(t, v, "1±2ulp")
   274  		// Distance is in representable values, not magnitude: the ULP below
   275  		// 1 is half the ULP above 1. The platform is ignored.
   276  		checkPermitsAll(t, v,
   277  			[]float32{1, up(1, 1), up(1, 2), up(1, -1), up(1, -2)},
   278  			[]float32{up(1, 3), up(1, -3), -1, 0})
   279  
   280  		// Crossing a binade boundary: the largest float32 below 2 is 1 ULP
   281  		// from 2.
   282  		checkPermitsAll(t, ulps(float32(2), 1), []float32{up(2, -1)}, []float32{up(2, -2)})
   283  
   284  		// Crossing the subnormal/normal boundary, and within subnormals.
   285  		checkPermitsAll(t, ulps(minNorm32, 1), []float32{maxSub32}, []float32{up(maxSub32, -1)})
   286  		checkPermitsAll(t, ulps(minSub32, 1), []float32{0, up(minSub32, 1)}, []float32{up(minSub32, 2)})
   287  
   288  		// The sign must match, even across zero: ulps(+0, 1) permits the
   289  		// smallest positive subnormal, but not -0 or the smallest negative
   290  		// subnormal.
   291  		checkPermitsAll(t, ulps(float32(0), 1), []float32{0, minSub32}, []float32{negZero32, -minSub32})
   292  		checkPermitsAll(t, ulps(minSub32, 1), nil, []float32{negZero32, -minSub32})
   293  		checkPermitsAll(t, ulps(negZero32, 1), []float32{negZero32, -minSub32}, []float32{0, minSub32})
   294  
   295  		// Negative values.
   296  		checkPermitsAll(t, ulps(float32(-1), 1),
   297  			[]float32{-1, up(-1, 1), up(-1, -1)},
   298  			[]float32{up(-1, 2), 1})
   299  
   300  		// Infinities must match exactly, even though +Inf's bit pattern is
   301  		// adjacent to MaxFloat32's.
   302  		checkPermitsAll(t, ulps(maxFloat32, 1), []float32{up(maxFloat32, -1)}, []float32{inf32})
   303  		checkPermitsAll(t, ulps(inf32, 1), []float32{inf32}, []float32{maxFloat32, -inf32, qnan32})
   304  		checkPermitsAll(t, ulps(-inf32, 1), []float32{-inf32}, []float32{-maxFloat32, inf32})
   305  
   306  		// NaN permits any NaN, and nothing else, and no finite value permits
   307  		// NaN. (snan32 differs from +Inf in only the lowest bit.)
   308  		checkPermitsAll(t, ulps(qnan32, 1), []float32{qnan32, snan32, negQnan32}, []float32{0, inf32})
   309  		checkPermitsAll(t, ulps(float32(1), 1<<30), nil, []float32{qnan32, snan32, negQnan32})
   310  		checkPermitsAll(t, ulps(maxFloat32, 1<<30), nil, []float32{snan32})
   311  
   312  		// Large distances: 1 and 2 are 1<<23 representable values apart.
   313  		checkPermitsAll(t, ulps(float32(1), 1<<23), []float32{2}, []float32{up(2, 1)})
   314  
   315  		// Very large distances. The bit patterns of values with opposite signs
   316  		// are at least 1<<31 apart, so this is where the sign check matters.
   317  		// Also, the distance must not be truncated to 32 bits.
   318  		checkPermitsAll(t, ulps(minSub32, 1<<31), []float32{maxFloat32}, []float32{negZero32, -minSub32})
   319  		checkPermitsAll(t, ulps(float32(1), 1<<32), []float32{0, 2, maxFloat32}, []float32{-1, inf32, qnan32})
   320  		checkPermitsAll(t, ulps(float32(1), math.MaxUint64), []float32{0, minSub32, maxFloat32}, []float32{negZero32, -1, inf32, qnan32})
   321  	})
   322  
   323  	t.Run("float64", func(t *testing.T) {
   324  		up := func(x float64, n int64) float64 {
   325  			return f64(uint64(int64(math.Float64bits(x)) + n))
   326  		}
   327  
   328  		checkPermitsAll(t, ulps(1.0, 0), []float64{1}, []float64{up(1, 1)})
   329  
   330  		v := ulps(1.0, 2)
   331  		checkCanon(t, v, 1)
   332  		checkString(t, v, "1±2ulp")
   333  		checkPermitsAll(t, v,
   334  			[]float64{1, up(1, 1), up(1, 2), up(1, -1), up(1, -2)},
   335  			[]float64{up(1, 3), up(1, -3), -1, 0})
   336  
   337  		checkPermitsAll(t, ulps(2.0, 1), []float64{up(2, -1)}, []float64{up(2, -2)})
   338  
   339  		checkPermitsAll(t, ulps(minNorm64, 1), []float64{maxSub64}, []float64{up(maxSub64, -1)})
   340  		checkPermitsAll(t, ulps(minSub64, 1), []float64{0, up(minSub64, 1)}, []float64{up(minSub64, 2)})
   341  
   342  		checkPermitsAll(t, ulps(0.0, 1), []float64{0, minSub64}, []float64{negZero64, -minSub64})
   343  		checkPermitsAll(t, ulps(negZero64, 1), []float64{negZero64, -minSub64}, []float64{0, minSub64})
   344  
   345  		checkPermitsAll(t, ulps(-1.0, 1),
   346  			[]float64{-1, up(-1, 1), up(-1, -1)},
   347  			[]float64{up(-1, 2), 1})
   348  
   349  		checkPermitsAll(t, ulps(math.MaxFloat64, 1), []float64{up(math.MaxFloat64, -1)}, []float64{inf64})
   350  		checkPermitsAll(t, ulps(inf64, 1), []float64{inf64}, []float64{math.MaxFloat64, -inf64, qnan64})
   351  		checkPermitsAll(t, ulps(-inf64, 1), []float64{-inf64}, []float64{-math.MaxFloat64, inf64})
   352  
   353  		checkPermitsAll(t, ulps(qnan64, 1), []float64{qnan64, snan64, negQnan64}, []float64{0, inf64})
   354  		checkPermitsAll(t, ulps(1.0, 1<<30), nil, []float64{qnan64, snan64, negQnan64})
   355  		checkPermitsAll(t, ulps(math.MaxFloat64, 1<<30), nil, []float64{snan64})
   356  
   357  		// 1 and 2 are 1<<52 representable values apart.
   358  		checkPermitsAll(t, ulps(1.0, 1<<52), []float64{2}, []float64{up(2, 1)})
   359  
   360  		// The bit patterns of values with opposite signs are at least 1<<63
   361  		// apart, so this is where the sign check matters.
   362  		checkPermitsAll(t, ulps(minSub64, 1<<63), []float64{math.MaxFloat64}, []float64{negZero64, -minSub64})
   363  		checkPermitsAll(t, ulps(1.0, math.MaxUint64), []float64{0, minSub64, math.MaxFloat64}, []float64{negZero64, -1, inf64, qnan64})
   364  	})
   365  }
   366  
   367  func TestPerPlatformDefault(t *testing.T) {
   368  	v := perPlatformDefault[int32](1, map[Platform]int32{PlatformWASM: 2})
   369  	for p := range numPlatforms {
   370  		want := int32(1)
   371  		if p == PlatformWASM {
   372  			want = 2
   373  		}
   374  		if !v.Permits(want, p) || v.Permits(3-want, p) {
   375  			t.Errorf("on %v: want only %d to be permitted", p, want)
   376  		}
   377  	}
   378  	if !v.Permits(1, PlatformAny) || !v.Permits(2, PlatformAny) || v.Permits(3, PlatformAny) {
   379  		t.Errorf("on %v: want only 1 and 2 to be permitted", PlatformAny)
   380  	}
   381  	if v.Canon() != 1 {
   382  		t.Errorf("Canon() = %d, want 1", v.Canon())
   383  	}
   384  }
   385  

View as plain text