github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/integration_test/spectest/spectest.go (about) 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 // Set when type == "module" || "register" 31 Name string `json:"name,omitempty"` 32 33 // Set when type == "module" || "assert_uninstantiable" || "assert_malformed" 34 Filename string `json:"filename,omitempty"` 35 36 // Set when type == "register" 37 As string `json:"as,omitempty"` 38 39 // Set when type == "assert_return" || "action" 40 Action commandAction `json:"action,omitempty"` 41 Exps []commandActionVal `json:"expected"` 42 43 // Set when type == "assert_malformed" 44 ModuleType string `json:"module_type"` 45 46 // Set when type == "assert_trap" 47 Text string `json:"text"` 48 } 49 50 commandAction struct { 51 ActionType string `json:"type"` 52 Args []commandActionVal `json:"args"` 53 54 // Set when ActionType == "invoke" 55 Field string `json:"field,omitempty"` 56 Module string `json:"module,omitempty"` 57 } 58 59 commandActionVal struct { 60 ValType string `json:"type"` 61 // LaneType is not empty if ValueType == "v128" 62 LaneType laneType `json:"lane_type"` 63 Value interface{} `json:"value"` 64 } 65 ) 66 67 // laneType is a type of each lane of vector value. 68 // 69 // See https://github.com/WebAssembly/wabt/blob/main/docs/wast2json.md#const 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 // In wazero, externref is opaque pointer, so "0" is considered as null. 111 // So in order to treat "externref 0" in spectest non nullref, we increment the value. 112 v = fmt.Sprintf("%d", original+1) 113 } 114 case "funcref": 115 // All the in and out funcref params are null in spectest (cannot represent non-null as it depends on runtime impl). 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 // TODO: 152 case "assert_trap": 153 msg += fmt.Sprintf(", args: %v, error text: %s", c.Action.Args, c.Text) 154 case "assert_invalid": 155 // TODO: 156 case "assert_exhaustion": 157 // TODO: 158 case "assert_unlinkable": 159 // TODO: 160 case "assert_uninstantiable": 161 // TODO: 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 // Note: nan:canonical, nan:arithmetic only appears on the expected values. 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 // In wazero, externref is opaque pointer, so "0" is considered as null. 272 // So in order to treat "externref 0" in spectest non nullref, we increment the value. 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 // expectedError returns the expected runtime error when the command type equals assert_trap 284 // which expects engines to emit the errors corresponding command.Text field. 285 func (c command) expectedError() (err error) { 286 if c.CommandType != "assert_trap" { 287 panic("unreachable") 288 } 289 switch c.Text { 290 case "expected shared memory": 291 err = wasmruntime.ErrRuntimeExpectedSharedMemory 292 case "out of bounds memory access": 293 err = wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess 294 case "indirect call type mismatch", "indirect call": 295 err = wasmruntime.ErrRuntimeIndirectCallTypeMismatch 296 case "undefined element", "undefined", "out of bounds table access": 297 err = wasmruntime.ErrRuntimeInvalidTableAccess 298 case "integer overflow": 299 err = wasmruntime.ErrRuntimeIntegerOverflow 300 case "invalid conversion to integer": 301 err = wasmruntime.ErrRuntimeInvalidConversionToInteger 302 case "integer divide by zero": 303 err = wasmruntime.ErrRuntimeIntegerDivideByZero 304 case "unaligned atomic": 305 err = wasmruntime.ErrRuntimeUnalignedAtomic 306 case "unreachable": 307 err = wasmruntime.ErrRuntimeUnreachable 308 default: 309 if strings.HasPrefix(c.Text, "uninitialized") { 310 err = wasmruntime.ErrRuntimeInvalidTableAccess 311 } 312 } 313 return 314 } 315 316 // spectestWasm was generated by the following: 317 // 318 // cd testdata; wat2wasm --debug-names spectest.wat 319 // 320 // This module is required by some test cases, and must be instantiated before running cases. 321 // See https://github.com/WebAssembly/spec/blob/1c5e5d178bd75c79b7a12881c529098beaee2a05/test/core/imports.wast 322 // See https://github.com/WebAssembly/spec/blob/1c5e5d178bd75c79b7a12881c529098beaee2a05/interpreter/script/js.ml#L33-L50 323 // 324 //go:embed testdata/spectest.wasm 325 var spectestWasm []byte 326 327 // Run runs all the test inside the testDataFS file system where all the cases are described 328 // via JSON files created from wast2json. 329 func Run(t *testing.T, testDataFS embed.FS, ctx context.Context, config wazero.RuntimeConfig) { 330 files, err := testDataFS.ReadDir("testdata") 331 require.NoError(t, err) 332 333 caseNames := make([]string, 0, len(files)) 334 for _, f := range files { 335 filename := f.Name() 336 if strings.HasSuffix(filename, ".json") { 337 caseNames = append(caseNames, strings.TrimSuffix(filename, ".json")) 338 } 339 } 340 341 // If the go:embed path resolution was wrong, this fails. 342 // https://github.com/tetratelabs/wazero/issues/247 343 require.True(t, len(caseNames) > 0, "len(caseNames)=%d (not greater than zero)", len(caseNames)) 344 345 for _, f := range caseNames { 346 RunCase(t, testDataFS, f, ctx, config, -1, 0, math.MaxInt) 347 } 348 } 349 350 // RunCase runs the test case described by the given spectest file name (without .wast!) in the testDataFS file system. 351 // lineBegin and lineEnd are the line numbers to run. If lineBegin == 0 and lineEnd == math.MaxInt, all the lines are run. 352 // 353 // For example, if you want to run memory_grow.wast:66 to 70, you can do: 354 // 355 // RunCase(t, testDataFS, "memory_grow", ctx, config, mandatoryLine, 66, 70) 356 // 357 // where mandatoryLine is the line number which can be run regardless of the lineBegin and lineEnd. It is useful when 358 // we only want to run specific command while running "module" command to instantiate a module. If you don't need it, 359 // just pass -1. 360 func RunCase(t *testing.T, testDataFS embed.FS, f string, ctx context.Context, config wazero.RuntimeConfig, mandatoryLine, lineBegin, lineEnd int) { 361 raw, err := testDataFS.ReadFile(testdataPath(f + ".json")) 362 require.NoError(t, err) 363 364 var base testbase 365 require.NoError(t, json.Unmarshal(raw, &base)) 366 367 wastName := basename(base.SourceFile) 368 369 t.Run(wastName, func(t *testing.T) { 370 r := wazero.NewRuntimeWithConfig(ctx, config) 371 defer func() { 372 require.NoError(t, r.Close(ctx)) 373 }() 374 375 _, err := r.InstantiateWithConfig(ctx, spectestWasm, wazero.NewModuleConfig()) 376 require.NoError(t, err) 377 378 modules := make(map[string]api.Module) 379 var lastInstantiatedModule api.Module 380 for i := 0; i < len(base.Commands); i++ { 381 c := &base.Commands[i] 382 line := c.Line 383 if mandatoryLine > -1 && c.Line == mandatoryLine { 384 } else if line < lineBegin || line > lineEnd { 385 continue 386 } 387 t.Run(fmt.Sprintf("%s/line:%d", c.CommandType, c.Line), func(t *testing.T) { 388 msg := fmt.Sprintf("%s:%d %s", wastName, c.Line, c.CommandType) 389 switch c.CommandType { 390 case "module": 391 buf, err := testDataFS.ReadFile(testdataPath(c.Filename)) 392 require.NoError(t, err, msg) 393 394 var registeredName string 395 if next := i + 1; next < len(base.Commands) && base.Commands[next].CommandType == "register" { 396 registeredName = base.Commands[next].As 397 i++ // Skip the entire "register" command. 398 } 399 mod, err := r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig().WithName(registeredName)) 400 require.NoError(t, err, msg) 401 if c.Name != "" { 402 modules[c.Name] = mod 403 } 404 lastInstantiatedModule = mod 405 case "assert_return", "action": 406 m := lastInstantiatedModule 407 if c.Action.Module != "" { 408 m = modules[c.Action.Module] 409 } 410 switch c.Action.ActionType { 411 case "invoke": 412 args, exps := c.getAssertReturnArgsExps() 413 msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args) 414 if c.Action.Module != "" { 415 msg += " in module " + c.Action.Module 416 } 417 fn := m.ExportedFunction(c.Action.Field) 418 results, err := fn.Call(ctx, args...) 419 require.NoError(t, err, msg) 420 require.Equal(t, len(exps), len(results), msg) 421 laneTypes := map[int]string{} 422 for i, expV := range c.Exps { 423 if expV.ValType == "v128" { 424 laneTypes[i] = expV.LaneType 425 } 426 } 427 matched, valuesMsg := valuesEq(results, exps, fn.Definition().ResultTypes(), laneTypes) 428 require.True(t, matched, msg+"\n"+valuesMsg) 429 case "get": 430 _, exps := c.getAssertReturnArgsExps() 431 require.Equal(t, 1, len(exps)) 432 msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args) 433 if c.Action.Module != "" { 434 msg += " in module " + c.Action.Module 435 } 436 global := m.ExportedGlobal(c.Action.Field) 437 require.NotNil(t, global) 438 require.Equal(t, exps[0], global.Get(), msg) 439 default: 440 t.Fatalf("unsupported action type type: %v", c) 441 } 442 case "assert_malformed": 443 if c.ModuleType != "text" { 444 // We don't support direct loading of wast yet. 445 buf, err := testDataFS.ReadFile(testdataPath(c.Filename)) 446 require.NoError(t, err, msg) 447 _, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig()) 448 require.Error(t, err, msg) 449 } 450 case "assert_trap": 451 m := lastInstantiatedModule 452 if c.Action.Module != "" { 453 m = modules[c.Action.Module] 454 } 455 switch c.Action.ActionType { 456 case "invoke": 457 args := c.getAssertReturnArgs() 458 msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args) 459 if c.Action.Module != "" { 460 msg += " in module " + c.Action.Module 461 } 462 _, err := m.ExportedFunction(c.Action.Field).Call(ctx, args...) 463 require.ErrorIs(t, err, c.expectedError(), msg) 464 default: 465 t.Fatalf("unsupported action type type: %v", c) 466 } 467 case "assert_invalid": 468 if c.ModuleType == "text" { 469 // We don't support direct loading of wast yet. 470 t.Skip() 471 } 472 buf, err := testDataFS.ReadFile(testdataPath(c.Filename)) 473 require.NoError(t, err, msg) 474 _, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig()) 475 require.Error(t, err, msg) 476 case "assert_exhaustion": 477 switch c.Action.ActionType { 478 case "invoke": 479 args := c.getAssertReturnArgs() 480 msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args) 481 if c.Action.Module != "" { 482 msg += " in module " + c.Action.Module 483 } 484 _, err := lastInstantiatedModule.ExportedFunction(c.Action.Field).Call(ctx, args...) 485 require.ErrorIs(t, err, wasmruntime.ErrRuntimeStackOverflow, msg) 486 default: 487 t.Fatalf("unsupported action type type: %v", c) 488 } 489 case "assert_unlinkable": 490 if c.ModuleType == "text" { 491 // We don't support direct loading of wast yet. 492 t.Skip() 493 } 494 buf, err := testDataFS.ReadFile(testdataPath(c.Filename)) 495 require.NoError(t, err, msg) 496 _, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig()) 497 require.Error(t, err, msg) 498 case "assert_uninstantiable": 499 buf, err := testDataFS.ReadFile(testdataPath(c.Filename)) 500 require.NoError(t, err, msg) 501 _, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig()) 502 if c.Text == "out of bounds table access" { 503 // This is not actually an instantiation error, but assert_trap in the original wast, but wast2json translates it to assert_uninstantiable. 504 // Anyway, this spectest case expects the error due to active element offset ouf of bounds 505 // "after" instantiation while retaining function instances used for elements. 506 // https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/linking.wast#L264-L274 507 // 508 // In practice, such a module instance can be used for invoking functions without any issue. In addition, we have to 509 // retain functions after the expected "instantiation" failure, so in wazero we choose to not raise error in that case. 510 require.NoError(t, err, msg) 511 } else { 512 require.Error(t, err, msg) 513 } 514 default: 515 t.Fatalf("unsupported command type: %s", c) 516 } 517 }) 518 } 519 }) 520 } 521 522 // basename avoids filepath.Base to ensure a forward slash is used even in Windows. 523 // See https://pkg.go.dev/embed#hdr-Directives 524 func basename(path string) string { 525 lastSlash := strings.LastIndexByte(path, '/') 526 return path[lastSlash+1:] 527 } 528 529 // testdataPath avoids filepath.Join to ensure a forward slash is used even in Windows. 530 // See https://pkg.go.dev/embed#hdr-Directives 531 func testdataPath(filename string) string { 532 return fmt.Sprintf("testdata/%s", filename) 533 } 534 535 // valuesEq returns true if all the actual result matches exps which are all expressed as uint64. 536 // - actual,exps: comparison target values which are all represented as uint64, meaning that if valTypes = [V128,I32], then 537 // we have actual/exp = [(lower-64bit of the first V128), (higher-64bit of the first V128), I32]. 538 // - valTypes holds the wasm.ValueType(s) of the original values in Wasm. 539 // - laneTypes maps the index of valueTypes to laneType if valueTypes[i] == wasm.ValueTypeV128. 540 // 541 // Also, if matched == false this returns non-empty valuesMsg which can be used to augment the test failure message. 542 func valuesEq(actual, exps []uint64, valTypes []wasm.ValueType, laneTypes map[int]laneType) (matched bool, valuesMsg string) { 543 matched = true 544 545 var msgExpValuesStrs, msgActualValuesStrs []string 546 var uint64RepPos int // the index to actual and exps slice. 547 for i, tp := range valTypes { 548 switch tp { 549 case wasm.ValueTypeI32: 550 msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%d", uint32(exps[uint64RepPos]))) 551 msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%d", uint32(actual[uint64RepPos]))) 552 matched = matched && uint32(exps[uint64RepPos]) == uint32(actual[uint64RepPos]) 553 uint64RepPos++ 554 case wasm.ValueTypeI64, wasm.ValueTypeExternref, wasm.ValueTypeFuncref: 555 msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%d", exps[uint64RepPos])) 556 msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%d", actual[uint64RepPos])) 557 matched = matched && exps[uint64RepPos] == actual[uint64RepPos] 558 uint64RepPos++ 559 case wasm.ValueTypeF32: 560 a := math.Float32frombits(uint32(actual[uint64RepPos])) 561 e := math.Float32frombits(uint32(exps[uint64RepPos])) 562 msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%f", e)) 563 msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%f", a)) 564 matched = matched && f32Equal(e, a) 565 uint64RepPos++ 566 case wasm.ValueTypeF64: 567 e := math.Float64frombits(exps[uint64RepPos]) 568 a := math.Float64frombits(actual[uint64RepPos]) 569 msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%f", e)) 570 msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%f", a)) 571 matched = matched && f64Equal(e, a) 572 uint64RepPos++ 573 case wasm.ValueTypeV128: 574 actualLo, actualHi := actual[uint64RepPos], actual[uint64RepPos+1] 575 expLo, expHi := exps[uint64RepPos], exps[uint64RepPos+1] 576 switch laneTypes[i] { 577 case laneTypeI8: 578 msgExpValuesStrs = append(msgExpValuesStrs, 579 fmt.Sprintf("i8x16(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)", 580 byte(expLo), byte(expLo>>8), byte(expLo>>16), byte(expLo>>24), 581 byte(expLo>>32), byte(expLo>>40), byte(expLo>>48), byte(expLo>>56), 582 byte(expHi), byte(expHi>>8), byte(expHi>>16), byte(expHi>>24), 583 byte(expHi>>32), byte(expHi>>40), byte(expHi>>48), byte(expHi>>56), 584 ), 585 ) 586 msgActualValuesStrs = append(msgActualValuesStrs, 587 fmt.Sprintf("i8x16(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)", 588 byte(actualLo), byte(actualLo>>8), byte(actualLo>>16), byte(actualLo>>24), 589 byte(actualLo>>32), byte(actualLo>>40), byte(actualLo>>48), byte(actualLo>>56), 590 byte(actualHi), byte(actualHi>>8), byte(actualHi>>16), byte(actualHi>>24), 591 byte(actualHi>>32), byte(actualHi>>40), byte(actualHi>>48), byte(actualHi>>56), 592 ), 593 ) 594 matched = matched && (expLo == actualLo) && (expHi == actualHi) 595 case laneTypeI16: 596 msgExpValuesStrs = append(msgExpValuesStrs, 597 fmt.Sprintf("i16x8(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)", 598 uint16(expLo), uint16(expLo>>16), uint16(expLo>>32), uint16(expLo>>48), 599 uint16(expHi), uint16(expHi>>16), uint16(expHi>>32), uint16(expHi>>48), 600 ), 601 ) 602 msgActualValuesStrs = append(msgActualValuesStrs, 603 fmt.Sprintf("i16x8(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)", 604 uint16(actualLo), uint16(actualLo>>16), uint16(actualLo>>32), uint16(actualLo>>48), 605 uint16(actualHi), uint16(actualHi>>16), uint16(actualHi>>32), uint16(actualHi>>48), 606 ), 607 ) 608 matched = matched && (expLo == actualLo) && (expHi == actualHi) 609 case laneTypeI32: 610 msgExpValuesStrs = append(msgExpValuesStrs, 611 fmt.Sprintf("i32x4(%#x, %#x, %#x, %#x)", uint32(expLo), uint32(expLo>>32), uint32(expHi), uint32(expHi>>32)), 612 ) 613 msgActualValuesStrs = append(msgActualValuesStrs, 614 fmt.Sprintf("i32x4(%#x, %#x, %#x, %#x)", uint32(actualLo), uint32(actualLo>>32), uint32(actualHi), uint32(actualHi>>32)), 615 ) 616 matched = matched && (expLo == actualLo) && (expHi == actualHi) 617 case laneTypeI64: 618 msgExpValuesStrs = append(msgExpValuesStrs, 619 fmt.Sprintf("i64x2(%#x, %#x)", expLo, expHi), 620 ) 621 msgActualValuesStrs = append(msgActualValuesStrs, 622 fmt.Sprintf("i64x2(%#x, %#x)", actualLo, actualHi), 623 ) 624 matched = matched && (expLo == actualLo) && (expHi == actualHi) 625 case laneTypeF32: 626 msgExpValuesStrs = append(msgExpValuesStrs, 627 fmt.Sprintf("f32x4(%f, %f, %f, %f)", 628 math.Float32frombits(uint32(expLo)), math.Float32frombits(uint32(expLo>>32)), 629 math.Float32frombits(uint32(expHi)), math.Float32frombits(uint32(expHi>>32)), 630 ), 631 ) 632 msgActualValuesStrs = append(msgActualValuesStrs, 633 fmt.Sprintf("f32x4(%f, %f, %f, %f)", 634 math.Float32frombits(uint32(actualLo)), math.Float32frombits(uint32(actualLo>>32)), 635 math.Float32frombits(uint32(actualHi)), math.Float32frombits(uint32(actualHi>>32)), 636 ), 637 ) 638 matched = matched && 639 f32Equal(math.Float32frombits(uint32(expLo)), math.Float32frombits(uint32(actualLo))) && 640 f32Equal(math.Float32frombits(uint32(expLo>>32)), math.Float32frombits(uint32(actualLo>>32))) && 641 f32Equal(math.Float32frombits(uint32(expHi)), math.Float32frombits(uint32(actualHi))) && 642 f32Equal(math.Float32frombits(uint32(expHi>>32)), math.Float32frombits(uint32(actualHi>>32))) 643 case laneTypeF64: 644 msgExpValuesStrs = append(msgExpValuesStrs, 645 fmt.Sprintf("f64x2(%f, %f)", math.Float64frombits(expLo), math.Float64frombits(expHi)), 646 ) 647 msgActualValuesStrs = append(msgActualValuesStrs, 648 fmt.Sprintf("f64x2(%f, %f)", math.Float64frombits(actualLo), math.Float64frombits(actualHi)), 649 ) 650 matched = matched && 651 f64Equal(math.Float64frombits(expLo), math.Float64frombits(actualLo)) && 652 f64Equal(math.Float64frombits(expHi), math.Float64frombits(actualHi)) 653 default: 654 panic("BUG") 655 } 656 uint64RepPos += 2 657 default: 658 panic("BUG") 659 } 660 } 661 662 if !matched { 663 valuesMsg = fmt.Sprintf("\thave [%s]\n\twant [%s]", 664 strings.Join(msgActualValuesStrs, ", "), 665 strings.Join(msgExpValuesStrs, ", ")) 666 } 667 return 668 } 669 670 func f32Equal(expected, actual float32) (matched bool) { 671 if expBit := math.Float32bits(expected); expBit == moremath.F32CanonicalNaNBits { 672 matched = math.Float32bits(actual)&moremath.F32CanonicalNaNBitsMask == moremath.F32CanonicalNaNBits 673 } else if expBit == moremath.F32ArithmeticNaNBits { 674 b := math.Float32bits(actual) 675 matched = b&moremath.F32ExponentMask == moremath.F32ExponentMask && // Indicates that exponent part equals of NaN. 676 b&moremath.F32ArithmeticNaNPayloadMSB == moremath.F32ArithmeticNaNPayloadMSB 677 } else if math.IsNaN(float64(expected)) { // NaN cannot be compared with themselves, so we have to use IsNaN 678 matched = math.IsNaN(float64(actual)) 679 } else { 680 // Compare the bit patterns directly, rather than == on float32 since in Go, -0 and 0 equals, 681 // but in the Wasm spec, they are treated as different. 682 matched = math.Float32bits(expected) == math.Float32bits(actual) 683 } 684 return 685 } 686 687 func f64Equal(expected, actual float64) (matched bool) { 688 if expBit := math.Float64bits(expected); expBit == moremath.F64CanonicalNaNBits { 689 matched = math.Float64bits(actual)&moremath.F64CanonicalNaNBitsMask == moremath.F64CanonicalNaNBits 690 } else if expBit == moremath.F64ArithmeticNaNBits { 691 b := math.Float64bits(actual) 692 matched = b&moremath.F64ExponentMask == moremath.F64ExponentMask && // Indicates that exponent part equals of NaN. 693 b&moremath.F64ArithmeticNaNPayloadMSB == moremath.F64ArithmeticNaNPayloadMSB 694 } else if math.IsNaN(expected) { // NaN cannot be compared with themselves, so we have to use IsNaN 695 matched = math.IsNaN(actual) 696 } else { 697 // Compare the bit patterns directly, rather than == on float64 since in Go, -0 and 0 equals, 698 // but in the Wasm spec, they are treated as different. 699 matched = math.Float64bits(expected) == math.Float64bits(actual) 700 } 701 return 702 }