1 package spectest
2
3 import (
4 "context"
5 "embed"
6 "encoding/json"
7 "fmt"
8 "math"
9 "strconv"
10 "strings"
11 "testing"
12
13 "github.com/tetratelabs/wazero"
14 "github.com/tetratelabs/wazero/api"
15 "github.com/tetratelabs/wazero/internal/moremath"
16 "github.com/tetratelabs/wazero/internal/testing/require"
17 "github.com/tetratelabs/wazero/internal/wasm"
18 "github.com/tetratelabs/wazero/internal/wasmruntime"
19 )
20
21 type (
22 testbase struct {
23 SourceFile string `json:"source_filename"`
24 Commands []command `json:"commands"`
25 }
26 command struct {
27 CommandType string `json:"type"`
28 Line int `json:"line"`
29
30
31 Name string `json:"name,omitempty"`
32
33
34 Filename string `json:"filename,omitempty"`
35
36
37 As string `json:"as,omitempty"`
38
39
40 Action commandAction `json:"action,omitempty"`
41 Exps []commandActionVal `json:"expected"`
42
43
44 ModuleType string `json:"module_type"`
45
46
47 Text string `json:"text"`
48 }
49
50 commandAction struct {
51 ActionType string `json:"type"`
52 Args []commandActionVal `json:"args"`
53
54
55 Field string `json:"field,omitempty"`
56 Module string `json:"module,omitempty"`
57 }
58
59 commandActionVal struct {
60 ValType string `json:"type"`
61
62 LaneType laneType `json:"lane_type"`
63 Value interface{} `json:"value"`
64 }
65 )
66
67
68
69
70 type laneType = string
71
72 const (
73 laneTypeI8 laneType = "i8"
74 laneTypeI16 laneType = "i16"
75 laneTypeI32 laneType = "i32"
76 laneTypeI64 laneType = "i64"
77 laneTypeF32 laneType = "f32"
78 laneTypeF64 laneType = "f64"
79 )
80
81 func (c commandActionVal) String() string {
82 var v string
83 valTypeStr := c.ValType
84 switch c.ValType {
85 case "i32":
86 v = c.Value.(string)
87 case "f32":
88 str := c.Value.(string)
89 if strings.Contains(str, "nan") {
90 v = str
91 } else {
92 ret, _ := strconv.ParseUint(str, 10, 32)
93 v = fmt.Sprintf("%f", math.Float32frombits(uint32(ret)))
94 }
95 case "i64":
96 v = c.Value.(string)
97 case "f64":
98 str := c.Value.(string)
99 if strings.Contains(str, "nan") {
100 v = str
101 } else {
102 ret, _ := strconv.ParseUint(str, 10, 64)
103 v = fmt.Sprintf("%f", math.Float64frombits(ret))
104 }
105 case "externref":
106 if c.Value == "null" {
107 v = "null"
108 } else {
109 original, _ := strconv.ParseUint(c.Value.(string), 10, 64)
110
111
112 v = fmt.Sprintf("%d", original+1)
113 }
114 case "funcref":
115
116 v = "null"
117 case "v128":
118 simdValues, ok := c.Value.([]interface{})
119 if !ok {
120 panic("BUG")
121 }
122 var strs []string
123 for _, v := range simdValues {
124 strs = append(strs, v.(string))
125 }
126 v = strings.Join(strs, ",")
127 valTypeStr = fmt.Sprintf("v128[lane=%s]", c.LaneType)
128 }
129 return fmt.Sprintf("{type: %s, value: %v}", valTypeStr, v)
130 }
131
132 func (c command) String() string {
133 msg := fmt.Sprintf("line: %d, type: %s", c.Line, c.CommandType)
134 switch c.CommandType {
135 case "register":
136 msg += fmt.Sprintf(", name: %s, as: %s", c.Name, c.As)
137 case "module":
138 if c.Name != "" {
139 msg += fmt.Sprintf(", name: %s, filename: %s", c.Name, c.Filename)
140 } else {
141 msg += fmt.Sprintf(", filename: %s", c.Filename)
142 }
143 case "assert_return", "action":
144 msg += fmt.Sprintf(", action type: %s", c.Action.ActionType)
145 if c.Action.Module != "" {
146 msg += fmt.Sprintf(", module: %s", c.Action.Module)
147 }
148 msg += fmt.Sprintf(", field: %s", c.Action.Field)
149 msg += fmt.Sprintf(", args: %v, expected: %v", c.Action.Args, c.Exps)
150 case "assert_malformed":
151
152 case "assert_trap":
153 msg += fmt.Sprintf(", args: %v, error text: %s", c.Action.Args, c.Text)
154 case "assert_invalid":
155
156 case "assert_exhaustion":
157
158 case "assert_unlinkable":
159
160 case "assert_uninstantiable":
161
162 }
163 return "{" + msg + "}"
164 }
165
166 func (c command) getAssertReturnArgs() []uint64 {
167 var args []uint64
168 for _, arg := range c.Action.Args {
169 args = append(args, arg.toUint64s()...)
170 }
171 return args
172 }
173
174 func (c command) getAssertReturnArgsExps() (args []uint64, exps []uint64) {
175 for _, arg := range c.Action.Args {
176 args = append(args, arg.toUint64s()...)
177 }
178 for _, exp := range c.Exps {
179 exps = append(exps, exp.toUint64s()...)
180 }
181 return
182 }
183
184 func (c commandActionVal) toUint64s() (ret []uint64) {
185 if c.ValType == "v128" {
186 strValues, ok := c.Value.([]interface{})
187 if !ok {
188 panic("BUG")
189 }
190 var width, valNum int
191 switch c.LaneType {
192 case "i8":
193 width, valNum = 8, 16
194 case "i16":
195 width, valNum = 16, 8
196 case "i32":
197 width, valNum = 32, 4
198 case "i64":
199 width, valNum = 64, 2
200 case "f32":
201 width, valNum = 32, 4
202 case "f64":
203 width, valNum = 64, 2
204 default:
205 panic("BUG")
206 }
207 lo, hi := buildLaneUint64(strValues, width, valNum)
208 return []uint64{lo, hi}
209 } else {
210 return []uint64{c.toUint64()}
211 }
212 }
213
214 func buildLaneUint64(raw []interface{}, width, valNum int) (lo, hi uint64) {
215 for i := 0; i < valNum; i++ {
216 str := raw[i].(string)
217
218 var v uint64
219 var err error
220 if strings.Contains(str, "nan") {
221 v = getNaNBits(str, width == 32)
222 } else {
223 v, err = strconv.ParseUint(str, 10, width)
224 if err != nil {
225 panic(err)
226 }
227 }
228
229 if half := valNum / 2; i < half {
230 lo |= v << (i * width)
231 } else {
232 hi |= v << ((i - half) * width)
233 }
234 }
235 return
236 }
237
238 func getNaNBits(strValue string, is32bit bool) (ret uint64) {
239
240 if is32bit {
241 switch strValue {
242 case "nan:canonical":
243 ret = uint64(moremath.F32CanonicalNaNBits)
244 case "nan:arithmetic":
245 ret = uint64(moremath.F32ArithmeticNaNBits)
246 default:
247 panic("BUG")
248 }
249 } else {
250 switch strValue {
251 case "nan:canonical":
252 ret = moremath.F64CanonicalNaNBits
253 case "nan:arithmetic":
254 ret = moremath.F64ArithmeticNaNBits
255 default:
256 panic("BUG")
257 }
258 }
259 return
260 }
261
262 func (c commandActionVal) toUint64() (ret uint64) {
263 strValue := c.Value.(string)
264 if strings.Contains(strValue, "nan") {
265 ret = getNaNBits(strValue, c.ValType == "f32")
266 } else if c.ValType == "externref" {
267 if c.Value == "null" {
268 ret = 0
269 } else {
270 original, _ := strconv.ParseUint(strValue, 10, 64)
271
272
273 ret = original + 1
274 }
275 } else if strings.Contains(c.ValType, "32") {
276 ret, _ = strconv.ParseUint(strValue, 10, 32)
277 } else {
278 ret, _ = strconv.ParseUint(strValue, 10, 64)
279 }
280 return
281 }
282
283
284
285 func (c command) expectedError() (err error) {
286 if c.CommandType != "assert_trap" {
287 panic("unreachable")
288 }
289 switch c.Text {
290 case "out of bounds memory access":
291 err = wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess
292 case "indirect call type mismatch", "indirect call":
293 err = wasmruntime.ErrRuntimeIndirectCallTypeMismatch
294 case "undefined element", "undefined", "out of bounds table access":
295 err = wasmruntime.ErrRuntimeInvalidTableAccess
296 case "integer overflow":
297 err = wasmruntime.ErrRuntimeIntegerOverflow
298 case "invalid conversion to integer":
299 err = wasmruntime.ErrRuntimeInvalidConversionToInteger
300 case "integer divide by zero":
301 err = wasmruntime.ErrRuntimeIntegerDivideByZero
302 case "unreachable":
303 err = wasmruntime.ErrRuntimeUnreachable
304 default:
305 if strings.HasPrefix(c.Text, "uninitialized") {
306 err = wasmruntime.ErrRuntimeInvalidTableAccess
307 }
308 }
309 return
310 }
311
312
313
314
315
316
317
318
319
320
321 var spectestWasm []byte
322
323
324
325 func Run(t *testing.T, testDataFS embed.FS, ctx context.Context, config wazero.RuntimeConfig) {
326 files, err := testDataFS.ReadDir("testdata")
327 require.NoError(t, err)
328
329 caseNames := make([]string, 0, len(files))
330 for _, f := range files {
331 filename := f.Name()
332 if strings.HasSuffix(filename, ".json") {
333 caseNames = append(caseNames, strings.TrimSuffix(filename, ".json"))
334 }
335 }
336
337
338
339 require.True(t, len(caseNames) > 1, "len(caseNames)=%d (not greater than one)", len(caseNames))
340
341 for _, f := range caseNames {
342 RunCase(t, testDataFS, f, ctx, config, -1, 0, math.MaxInt)
343 }
344 }
345
346
347
348
349
350
351
352
353
354
355
356 func RunCase(t *testing.T, testDataFS embed.FS, f string, ctx context.Context, config wazero.RuntimeConfig, mandatoryLine, lineBegin, lineEnd int) {
357 raw, err := testDataFS.ReadFile(testdataPath(f + ".json"))
358 require.NoError(t, err)
359
360 var base testbase
361 require.NoError(t, json.Unmarshal(raw, &base))
362
363 wastName := basename(base.SourceFile)
364
365 t.Run(wastName, func(t *testing.T) {
366 r := wazero.NewRuntimeWithConfig(ctx, config)
367 defer func() {
368 require.NoError(t, r.Close(ctx))
369 }()
370
371 _, err := r.InstantiateWithConfig(ctx, spectestWasm, wazero.NewModuleConfig())
372 require.NoError(t, err)
373
374 modules := make(map[string]api.Module)
375 var lastInstantiatedModule api.Module
376 for i := 0; i < len(base.Commands); i++ {
377 c := &base.Commands[i]
378 line := c.Line
379 if mandatoryLine > -1 && c.Line == mandatoryLine {
380 } else if line < lineBegin || line > lineEnd {
381 continue
382 }
383 t.Run(fmt.Sprintf("%s/line:%d", c.CommandType, c.Line), func(t *testing.T) {
384 msg := fmt.Sprintf("%s:%d %s", wastName, c.Line, c.CommandType)
385 switch c.CommandType {
386 case "module":
387 buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
388 require.NoError(t, err, msg)
389
390 var registeredName string
391 if next := i + 1; next < len(base.Commands) && base.Commands[next].CommandType == "register" {
392 registeredName = base.Commands[next].As
393 i++
394 }
395 mod, err := r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig().WithName(registeredName))
396 require.NoError(t, err, msg)
397 if c.Name != "" {
398 modules[c.Name] = mod
399 }
400 lastInstantiatedModule = mod
401 case "assert_return", "action":
402 m := lastInstantiatedModule
403 if c.Action.Module != "" {
404 m = modules[c.Action.Module]
405 }
406 switch c.Action.ActionType {
407 case "invoke":
408 args, exps := c.getAssertReturnArgsExps()
409 msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
410 if c.Action.Module != "" {
411 msg += " in module " + c.Action.Module
412 }
413 fn := m.ExportedFunction(c.Action.Field)
414 results, err := fn.Call(ctx, args...)
415 require.NoError(t, err, msg)
416 require.Equal(t, len(exps), len(results), msg)
417 laneTypes := map[int]string{}
418 for i, expV := range c.Exps {
419 if expV.ValType == "v128" {
420 laneTypes[i] = expV.LaneType
421 }
422 }
423 matched, valuesMsg := valuesEq(results, exps, fn.Definition().ResultTypes(), laneTypes)
424 require.True(t, matched, msg+"\n"+valuesMsg)
425 case "get":
426 _, exps := c.getAssertReturnArgsExps()
427 require.Equal(t, 1, len(exps))
428 msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
429 if c.Action.Module != "" {
430 msg += " in module " + c.Action.Module
431 }
432 global := m.ExportedGlobal(c.Action.Field)
433 require.NotNil(t, global)
434 require.Equal(t, exps[0], global.Get(), msg)
435 default:
436 t.Fatalf("unsupported action type type: %v", c)
437 }
438 case "assert_malformed":
439 if c.ModuleType != "text" {
440
441 buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
442 require.NoError(t, err, msg)
443 _, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig())
444 require.Error(t, err, msg)
445 }
446 case "assert_trap":
447 m := lastInstantiatedModule
448 if c.Action.Module != "" {
449 m = modules[c.Action.Module]
450 }
451 switch c.Action.ActionType {
452 case "invoke":
453 args := c.getAssertReturnArgs()
454 msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
455 if c.Action.Module != "" {
456 msg += " in module " + c.Action.Module
457 }
458 _, err := m.ExportedFunction(c.Action.Field).Call(ctx, args...)
459 require.ErrorIs(t, err, c.expectedError(), msg)
460 default:
461 t.Fatalf("unsupported action type type: %v", c)
462 }
463 case "assert_invalid":
464 if c.ModuleType == "text" {
465
466 t.Skip()
467 }
468 buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
469 require.NoError(t, err, msg)
470 _, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig())
471 require.Error(t, err, msg)
472 case "assert_exhaustion":
473 switch c.Action.ActionType {
474 case "invoke":
475 args := c.getAssertReturnArgs()
476 msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
477 if c.Action.Module != "" {
478 msg += " in module " + c.Action.Module
479 }
480 _, err := lastInstantiatedModule.ExportedFunction(c.Action.Field).Call(ctx, args...)
481 require.ErrorIs(t, err, wasmruntime.ErrRuntimeStackOverflow, msg)
482 default:
483 t.Fatalf("unsupported action type type: %v", c)
484 }
485 case "assert_unlinkable":
486 if c.ModuleType == "text" {
487
488 t.Skip()
489 }
490 buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
491 require.NoError(t, err, msg)
492 _, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig())
493 require.Error(t, err, msg)
494 case "assert_uninstantiable":
495 buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
496 require.NoError(t, err, msg)
497 _, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig())
498 if c.Text == "out of bounds table access" {
499
500
501
502
503
504
505
506 require.NoError(t, err, msg)
507 } else {
508 require.Error(t, err, msg)
509 }
510 default:
511 t.Fatalf("unsupported command type: %s", c)
512 }
513 })
514 }
515 })
516 }
517
518
519
520 func basename(path string) string {
521 lastSlash := strings.LastIndexByte(path, '/')
522 return path[lastSlash+1:]
523 }
524
525
526
527 func testdataPath(filename string) string {
528 return fmt.Sprintf("testdata/%s", filename)
529 }
530
531
532
533
534
535
536
537
538 func valuesEq(actual, exps []uint64, valTypes []wasm.ValueType, laneTypes map[int]laneType) (matched bool, valuesMsg string) {
539 matched = true
540
541 var msgExpValuesStrs, msgActualValuesStrs []string
542 var uint64RepPos int
543 for i, tp := range valTypes {
544 switch tp {
545 case wasm.ValueTypeI32:
546 msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%d", uint32(exps[uint64RepPos])))
547 msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%d", uint32(actual[uint64RepPos])))
548 matched = matched && uint32(exps[uint64RepPos]) == uint32(actual[uint64RepPos])
549 uint64RepPos++
550 case wasm.ValueTypeI64, wasm.ValueTypeExternref, wasm.ValueTypeFuncref:
551 msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%d", exps[uint64RepPos]))
552 msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%d", actual[uint64RepPos]))
553 matched = matched && exps[uint64RepPos] == actual[uint64RepPos]
554 uint64RepPos++
555 case wasm.ValueTypeF32:
556 a := math.Float32frombits(uint32(actual[uint64RepPos]))
557 e := math.Float32frombits(uint32(exps[uint64RepPos]))
558 msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%f", e))
559 msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%f", a))
560 matched = matched && f32Equal(e, a)
561 uint64RepPos++
562 case wasm.ValueTypeF64:
563 e := math.Float64frombits(exps[uint64RepPos])
564 a := math.Float64frombits(actual[uint64RepPos])
565 msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%f", e))
566 msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%f", a))
567 matched = matched && f64Equal(e, a)
568 uint64RepPos++
569 case wasm.ValueTypeV128:
570 actualLo, actualHi := actual[uint64RepPos], actual[uint64RepPos+1]
571 expLo, expHi := exps[uint64RepPos], exps[uint64RepPos+1]
572 switch laneTypes[i] {
573 case laneTypeI8:
574 msgExpValuesStrs = append(msgExpValuesStrs,
575 fmt.Sprintf("i8x16(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)",
576 byte(expLo), byte(expLo>>8), byte(expLo>>16), byte(expLo>>24),
577 byte(expLo>>32), byte(expLo>>40), byte(expLo>>48), byte(expLo>>56),
578 byte(expHi), byte(expHi>>8), byte(expHi>>16), byte(expHi>>24),
579 byte(expHi>>32), byte(expHi>>40), byte(expHi>>48), byte(expHi>>56),
580 ),
581 )
582 msgActualValuesStrs = append(msgActualValuesStrs,
583 fmt.Sprintf("i8x16(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)",
584 byte(actualLo), byte(actualLo>>8), byte(actualLo>>16), byte(actualLo>>24),
585 byte(actualLo>>32), byte(actualLo>>40), byte(actualLo>>48), byte(actualLo>>56),
586 byte(actualHi), byte(actualHi>>8), byte(actualHi>>16), byte(actualHi>>24),
587 byte(actualHi>>32), byte(actualHi>>40), byte(actualHi>>48), byte(actualHi>>56),
588 ),
589 )
590 matched = matched && (expLo == actualLo) && (expHi == actualHi)
591 case laneTypeI16:
592 msgExpValuesStrs = append(msgExpValuesStrs,
593 fmt.Sprintf("i16x8(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)",
594 uint16(expLo), uint16(expLo>>16), uint16(expLo>>32), uint16(expLo>>48),
595 uint16(expHi), uint16(expHi>>16), uint16(expHi>>32), uint16(expHi>>48),
596 ),
597 )
598 msgActualValuesStrs = append(msgActualValuesStrs,
599 fmt.Sprintf("i16x8(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)",
600 uint16(actualLo), uint16(actualLo>>16), uint16(actualLo>>32), uint16(actualLo>>48),
601 uint16(actualHi), uint16(actualHi>>16), uint16(actualHi>>32), uint16(actualHi>>48),
602 ),
603 )
604 matched = matched && (expLo == actualLo) && (expHi == actualHi)
605 case laneTypeI32:
606 msgExpValuesStrs = append(msgExpValuesStrs,
607 fmt.Sprintf("i32x4(%#x, %#x, %#x, %#x)", uint32(expLo), uint32(expLo>>32), uint32(expHi), uint32(expHi>>32)),
608 )
609 msgActualValuesStrs = append(msgActualValuesStrs,
610 fmt.Sprintf("i32x4(%#x, %#x, %#x, %#x)", uint32(actualLo), uint32(actualLo>>32), uint32(actualHi), uint32(actualHi>>32)),
611 )
612 matched = matched && (expLo == actualLo) && (expHi == actualHi)
613 case laneTypeI64:
614 msgExpValuesStrs = append(msgExpValuesStrs,
615 fmt.Sprintf("i64x2(%#x, %#x)", expLo, expHi),
616 )
617 msgActualValuesStrs = append(msgActualValuesStrs,
618 fmt.Sprintf("i64x2(%#x, %#x)", actualLo, actualHi),
619 )
620 matched = matched && (expLo == actualLo) && (expHi == actualHi)
621 case laneTypeF32:
622 msgExpValuesStrs = append(msgExpValuesStrs,
623 fmt.Sprintf("f32x4(%f, %f, %f, %f)",
624 math.Float32frombits(uint32(expLo)), math.Float32frombits(uint32(expLo>>32)),
625 math.Float32frombits(uint32(expHi)), math.Float32frombits(uint32(expHi>>32)),
626 ),
627 )
628 msgActualValuesStrs = append(msgActualValuesStrs,
629 fmt.Sprintf("f32x4(%f, %f, %f, %f)",
630 math.Float32frombits(uint32(actualLo)), math.Float32frombits(uint32(actualLo>>32)),
631 math.Float32frombits(uint32(actualHi)), math.Float32frombits(uint32(actualHi>>32)),
632 ),
633 )
634 matched = matched &&
635 f32Equal(math.Float32frombits(uint32(expLo)), math.Float32frombits(uint32(actualLo))) &&
636 f32Equal(math.Float32frombits(uint32(expLo>>32)), math.Float32frombits(uint32(actualLo>>32))) &&
637 f32Equal(math.Float32frombits(uint32(expHi)), math.Float32frombits(uint32(actualHi))) &&
638 f32Equal(math.Float32frombits(uint32(expHi>>32)), math.Float32frombits(uint32(actualHi>>32)))
639 case laneTypeF64:
640 msgExpValuesStrs = append(msgExpValuesStrs,
641 fmt.Sprintf("f64x2(%f, %f)", math.Float64frombits(expLo), math.Float64frombits(expHi)),
642 )
643 msgActualValuesStrs = append(msgActualValuesStrs,
644 fmt.Sprintf("f64x2(%f, %f)", math.Float64frombits(actualLo), math.Float64frombits(actualHi)),
645 )
646 matched = matched &&
647 f64Equal(math.Float64frombits(expLo), math.Float64frombits(actualLo)) &&
648 f64Equal(math.Float64frombits(expHi), math.Float64frombits(actualHi))
649 default:
650 panic("BUG")
651 }
652 uint64RepPos += 2
653 default:
654 panic("BUG")
655 }
656 }
657
658 if !matched {
659 valuesMsg = fmt.Sprintf("\thave [%s]\n\twant [%s]",
660 strings.Join(msgActualValuesStrs, ", "),
661 strings.Join(msgExpValuesStrs, ", "))
662 }
663 return
664 }
665
666 func f32Equal(expected, actual float32) (matched bool) {
667 if expBit := math.Float32bits(expected); expBit == moremath.F32CanonicalNaNBits {
668 matched = math.Float32bits(actual)&moremath.F32CanonicalNaNBitsMask == moremath.F32CanonicalNaNBits
669 } else if expBit == moremath.F32ArithmeticNaNBits {
670 b := math.Float32bits(actual)
671 matched = b&moremath.F32ExponentMask == moremath.F32ExponentMask &&
672 b&moremath.F32ArithmeticNaNPayloadMSB == moremath.F32ArithmeticNaNPayloadMSB
673 } else if math.IsNaN(float64(expected)) {
674 matched = math.IsNaN(float64(actual))
675 } else {
676
677
678 matched = math.Float32bits(expected) == math.Float32bits(actual)
679 }
680 return
681 }
682
683 func f64Equal(expected, actual float64) (matched bool) {
684 if expBit := math.Float64bits(expected); expBit == moremath.F64CanonicalNaNBits {
685 matched = math.Float64bits(actual)&moremath.F64CanonicalNaNBitsMask == moremath.F64CanonicalNaNBits
686 } else if expBit == moremath.F64ArithmeticNaNBits {
687 b := math.Float64bits(actual)
688 matched = b&moremath.F64ExponentMask == moremath.F64ExponentMask &&
689 b&moremath.F64ArithmeticNaNPayloadMSB == moremath.F64ArithmeticNaNPayloadMSB
690 } else if math.IsNaN(expected) {
691 matched = math.IsNaN(actual)
692 } else {
693
694
695 matched = math.Float64bits(expected) == math.Float64bits(actual)
696 }
697 return
698 }
699
View as plain text