1
2
3
4
5
6
7 package simd_test
8
9 import (
10 "math"
11 "runtime"
12 "simd/archsimd"
13 "testing"
14 )
15
16 func TestFloatMinMax32_128(t *testing.T) {
17 testFloatMinMax32(t, 4, floatMinMax32x4)
18 }
19
20 func TestFloatMinMax64_128(t *testing.T) {
21 testFloatMinMax64(t, 2, floatMinMax64x2)
22 }
23
24 func testFloatMinMax32(t *testing.T, lanes int, run func([]float32, []float32, [4][]float32, bool, bool)) {
25 t.Helper()
26 fromBits := func(x uint64) float32 { return math.Float32frombits(uint32(x)) }
27 toBits := func(x float32) uint64 { return uint64(math.Float32bits(x)) }
28 testFloatMinMax(t, lanes, floatMinMax32Cases(), fromBits, toBits, run, floatMinMaxPlatformConfig())
29 }
30
31 func testFloatMinMax64(t *testing.T, lanes int, run func([]float64, []float64, [4][]float64, bool, bool)) {
32 t.Helper()
33 testFloatMinMax(t, lanes, floatMinMax64Cases(), math.Float64frombits, math.Float64bits, run, floatMinMaxPlatformConfig())
34 }
35
36
37
38
39 type floatMinMaxCase struct {
40 name string
41 x, y uint64
42 wantAMD64, wantARM64 [4]uint64
43 }
44
45 func floatMinMax32Cases() []floatMinMaxCase {
46 const (
47 one = 0x3f800000
48 two = 0x40000000
49 negZero = 0x80000000
50 qnan1 = 0x7fc00001
51 qnan2 = 0x7fc00002
52 snan = 0x7f800001
53 inf = 0x7f800000
54 negInf = 0xff800000
55 )
56 return []floatMinMaxCase{
57 {"NaN-number", qnan1, one, [4]uint64{one, qnan1, one, qnan1}, [4]uint64{qnan1, qnan1, qnan1, qnan1}},
58 {"number-NaN", one, qnan1, [4]uint64{qnan1, one, qnan1, one}, [4]uint64{qnan1, qnan1, qnan1, qnan1}},
59 {"NaN-payloads", qnan1, qnan2, [4]uint64{qnan2, qnan1, qnan2, qnan1}, [4]uint64{qnan1, qnan2, qnan1, qnan2}},
60 {"sNaN-number", snan, one, [4]uint64{one, snan, one, snan}, [4]uint64{qnan1, qnan1, qnan1, qnan1}},
61 {"number-sNaN", one, snan, [4]uint64{snan, one, snan, one}, [4]uint64{qnan1, qnan1, qnan1, qnan1}},
62 {"negative-zero", negZero, 0, [4]uint64{0, negZero, 0, negZero}, [4]uint64{negZero, negZero, 0, 0}},
63 {"positive-zero", 0, negZero, [4]uint64{negZero, 0, negZero, 0}, [4]uint64{negZero, negZero, 0, 0}},
64 {"finite", two, one, [4]uint64{one, one, two, two}, [4]uint64{one, one, two, two}},
65 {"infinities", inf, negInf, [4]uint64{negInf, negInf, inf, inf}, [4]uint64{negInf, negInf, inf, inf}},
66 }
67 }
68
69 func floatMinMax64Cases() []floatMinMaxCase {
70 const (
71 one = 0x3ff0000000000000
72 two = 0x4000000000000000
73 negZero = 0x8000000000000000
74 qnan1 = 0x7ff8000000000001
75 qnan2 = 0x7ff8000000000002
76 snan = 0x7ff0000000000001
77 inf = 0x7ff0000000000000
78 negInf = 0xfff0000000000000
79 )
80 return []floatMinMaxCase{
81 {"NaN-number", qnan1, one, [4]uint64{one, qnan1, one, qnan1}, [4]uint64{qnan1, qnan1, qnan1, qnan1}},
82 {"number-NaN", one, qnan1, [4]uint64{qnan1, one, qnan1, one}, [4]uint64{qnan1, qnan1, qnan1, qnan1}},
83 {"NaN-payloads", qnan1, qnan2, [4]uint64{qnan2, qnan1, qnan2, qnan1}, [4]uint64{qnan1, qnan2, qnan1, qnan2}},
84 {"sNaN-number", snan, one, [4]uint64{one, snan, one, snan}, [4]uint64{qnan1, qnan1, qnan1, qnan1}},
85 {"number-sNaN", one, snan, [4]uint64{snan, one, snan, one}, [4]uint64{qnan1, qnan1, qnan1, qnan1}},
86 {"negative-zero", negZero, 0, [4]uint64{0, negZero, 0, negZero}, [4]uint64{negZero, negZero, 0, 0}},
87 {"positive-zero", 0, negZero, [4]uint64{negZero, 0, negZero, 0}, [4]uint64{negZero, negZero, 0, 0}},
88 {"finite", two, one, [4]uint64{one, one, two, two}, [4]uint64{one, one, two, two}},
89 {"infinities", inf, negInf, [4]uint64{negInf, negInf, inf, inf}, [4]uint64{negInf, negInf, inf, inf}},
90 }
91 }
92
93 type floatMinMaxConfig struct {
94 allowMasked bool
95 allowAnyNaN bool
96 }
97
98 func testFloatMinMax[T float](t *testing.T, lanes int, cases []floatMinMaxCase,
99 fromBits func(uint64) T, toBits func(T) uint64,
100 run func([]T, []T, [4][]T, bool, bool), config floatMinMaxConfig) {
101 t.Helper()
102 for _, form := range []struct {
103 name string
104 reverse, masked bool
105 }{
106 {"forward", false, false},
107 {"reverse", true, false},
108 {"masked-forward", false, true},
109 {"masked-reverse", true, true},
110 } {
111 t.Run(form.name, func(t *testing.T) {
112 if form.masked && !config.allowMasked {
113 t.Skip("requires AVX512")
114 }
115 for _, c := range cases {
116 t.Run(c.name, func(t *testing.T) {
117 wantResults := c.wantARM64
118 if runtime.GOARCH == "amd64" {
119 wantResults = c.wantAMD64
120 }
121 x, y := make([]T, lanes), make([]T, lanes)
122 var out [4][]T
123 for i := range out {
124 out[i] = make([]T, lanes)
125 }
126 for i := range x {
127 x[i], y[i] = fromBits(c.x), fromBits(c.y)
128 }
129 run(x, y, out, form.reverse, form.masked)
130 for j, name := range []string{"Min(x,y)", "Min(y,x)", "Max(x,y)", "Max(y,x)"} {
131 for i, v := range out[j] {
132 want := wantResults[j]
133 if form.masked && i%2 != 0 {
134 want = 0
135 }
136 if config.allowAnyNaN && math.IsNaN(float64(fromBits(want))) {
137 if !math.IsNaN(float64(v)) {
138 t.Errorf("%s lane %d: got %#x, want NaN", name, i, toBits(v))
139 }
140 continue
141 }
142 if got := toBits(v); got != want {
143 t.Errorf("%s lane %d: got %#x, want %#x", name, i, got, want)
144 }
145 }
146 }
147 })
148 }
149 })
150 }
151 }
152
153
154
155
156 func floatMinMax32x4(a, b []float32, out [4][]float32, reverse, masked bool) {
157 x, y := archsimd.LoadFloat32x4(a), archsimd.LoadFloat32x4(b)
158 var minXY, minYX, maxXY, maxYX archsimd.Float32x4
159 if masked {
160 mask := floatMinMaxMask32x4()
161 if reverse {
162 minYX, minXY = y.Min(x).Masked(mask), x.Min(y).Masked(mask)
163 maxYX, maxXY = y.Max(x).Masked(mask), x.Max(y).Masked(mask)
164 } else {
165 minXY, minYX = x.Min(y).Masked(mask), y.Min(x).Masked(mask)
166 maxXY, maxYX = x.Max(y).Masked(mask), y.Max(x).Masked(mask)
167 }
168 } else if reverse {
169 minYX, minXY = y.Min(x), x.Min(y)
170 maxYX, maxXY = y.Max(x), x.Max(y)
171 } else {
172 minXY, minYX = x.Min(y), y.Min(x)
173 maxXY, maxYX = x.Max(y), y.Max(x)
174 }
175 minXY.Store(out[0])
176 minYX.Store(out[1])
177 maxXY.Store(out[2])
178 maxYX.Store(out[3])
179 }
180
181
182 func floatMinMax64x2(a, b []float64, out [4][]float64, reverse, masked bool) {
183 x, y := archsimd.LoadFloat64x2(a), archsimd.LoadFloat64x2(b)
184 var minXY, minYX, maxXY, maxYX archsimd.Float64x2
185 if masked {
186 mask := floatMinMaxMask64x2()
187 if reverse {
188 minYX, minXY = y.Min(x).Masked(mask), x.Min(y).Masked(mask)
189 maxYX, maxXY = y.Max(x).Masked(mask), x.Max(y).Masked(mask)
190 } else {
191 minXY, minYX = x.Min(y).Masked(mask), y.Min(x).Masked(mask)
192 maxXY, maxYX = x.Max(y).Masked(mask), y.Max(x).Masked(mask)
193 }
194 } else if reverse {
195 minYX, minXY = y.Min(x), x.Min(y)
196 maxYX, maxXY = y.Max(x), x.Max(y)
197 } else {
198 minXY, minYX = x.Min(y), y.Min(x)
199 maxXY, maxYX = x.Max(y), y.Max(x)
200 }
201 minXY.Store(out[0])
202 minYX.Store(out[1])
203 maxXY.Store(out[2])
204 maxYX.Store(out[3])
205 }
206
View as plain text