github.com/tetratelabs/wazero@v1.2.1/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 "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  // spectestWasm was generated by the following:
   313  //
   314  //	cd testdata; wat2wasm --debug-names spectest.wat
   315  //
   316  // This module is required by some test cases, and instantiated before running cases.
   317  // See https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/imports.wast
   318  // See https://github.com/WebAssembly/spec/blob/wg-1.0/interpreter/script/js.ml#L13-L25
   319  //
   320  //go:embed testdata/spectest.wasm
   321  var spectestWasm []byte
   322  
   323  // Run runs all the test inside the testDataFS file system where all the cases are described
   324  // via JSON files created from wast2json.
   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  	jsonfiles := make([]string, 0, len(files))
   330  	for _, f := range files {
   331  		filename := f.Name()
   332  		if strings.HasSuffix(filename, ".json") {
   333  			jsonfiles = append(jsonfiles, testdataPath(filename))
   334  		}
   335  	}
   336  
   337  	// If the go:embed path resolution was wrong, this fails.
   338  	// https://github.com/tetratelabs/wazero/issues/247
   339  	require.True(t, len(jsonfiles) > 1, "len(jsonfiles)=%d (not greater than one)", len(jsonfiles))
   340  
   341  	for _, f := range jsonfiles {
   342  		raw, err := testDataFS.ReadFile(f)
   343  		require.NoError(t, err)
   344  
   345  		var base testbase
   346  		require.NoError(t, json.Unmarshal(raw, &base))
   347  
   348  		wastName := basename(base.SourceFile)
   349  
   350  		t.Run(wastName, func(t *testing.T) {
   351  			r := wazero.NewRuntimeWithConfig(ctx, config)
   352  			defer func() {
   353  				require.NoError(t, r.Close(ctx))
   354  			}()
   355  
   356  			_, err := r.InstantiateWithConfig(ctx, spectestWasm, wazero.NewModuleConfig())
   357  			require.NoError(t, err)
   358  
   359  			modules := make(map[string]api.Module)
   360  			var lastInstantiatedModule api.Module
   361  			for i := 0; i < len(base.Commands); i++ {
   362  				c := &base.Commands[i]
   363  				t.Run(fmt.Sprintf("%s/line:%d", c.CommandType, c.Line), func(t *testing.T) {
   364  					msg := fmt.Sprintf("%s:%d %s", wastName, c.Line, c.CommandType)
   365  					switch c.CommandType {
   366  					case "module":
   367  						buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
   368  						require.NoError(t, err, msg)
   369  
   370  						var registeredName string
   371  						if next := i + 1; next < len(base.Commands) && base.Commands[next].CommandType == "register" {
   372  							registeredName = base.Commands[next].As
   373  							i++ // Skip the entire "register" command.
   374  						}
   375  						mod, err := r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig().WithName(registeredName))
   376  						require.NoError(t, err, msg)
   377  						if c.Name != "" {
   378  							modules[c.Name] = mod
   379  						}
   380  						lastInstantiatedModule = mod
   381  					case "assert_return", "action":
   382  						m := lastInstantiatedModule
   383  						if c.Action.Module != "" {
   384  							m = modules[c.Action.Module]
   385  						}
   386  						switch c.Action.ActionType {
   387  						case "invoke":
   388  							args, exps := c.getAssertReturnArgsExps()
   389  							msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
   390  							if c.Action.Module != "" {
   391  								msg += " in module " + c.Action.Module
   392  							}
   393  							fn := m.ExportedFunction(c.Action.Field)
   394  							results, err := fn.Call(ctx, args...)
   395  							require.NoError(t, err, msg)
   396  							require.Equal(t, len(exps), len(results), msg)
   397  							laneTypes := map[int]string{}
   398  							for i, expV := range c.Exps {
   399  								if expV.ValType == "v128" {
   400  									laneTypes[i] = expV.LaneType
   401  								}
   402  							}
   403  							matched, valuesMsg := valuesEq(results, exps, fn.Definition().ResultTypes(), laneTypes)
   404  							require.True(t, matched, msg+"\n"+valuesMsg)
   405  						case "get":
   406  							_, exps := c.getAssertReturnArgsExps()
   407  							require.Equal(t, 1, len(exps))
   408  							msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
   409  							if c.Action.Module != "" {
   410  								msg += " in module " + c.Action.Module
   411  							}
   412  							global := m.ExportedGlobal(c.Action.Field)
   413  							require.NotNil(t, global)
   414  							require.Equal(t, exps[0], global.Get(), msg)
   415  						default:
   416  							t.Fatalf("unsupported action type type: %v", c)
   417  						}
   418  					case "assert_malformed":
   419  						if c.ModuleType != "text" {
   420  							// We don't support direct loading of wast yet.
   421  							buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
   422  							require.NoError(t, err, msg)
   423  							_, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig())
   424  							require.Error(t, err, msg)
   425  						}
   426  					case "assert_trap":
   427  						m := lastInstantiatedModule
   428  						if c.Action.Module != "" {
   429  							m = modules[c.Action.Module]
   430  						}
   431  						switch c.Action.ActionType {
   432  						case "invoke":
   433  							args := c.getAssertReturnArgs()
   434  							msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
   435  							if c.Action.Module != "" {
   436  								msg += " in module " + c.Action.Module
   437  							}
   438  							_, err := m.ExportedFunction(c.Action.Field).Call(ctx, args...)
   439  							require.ErrorIs(t, err, c.expectedError(), msg)
   440  						default:
   441  							t.Fatalf("unsupported action type type: %v", c)
   442  						}
   443  					case "assert_invalid":
   444  						if c.ModuleType == "text" {
   445  							// We don't support direct loading of wast yet.
   446  							t.Skip()
   447  						}
   448  						buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
   449  						require.NoError(t, err, msg)
   450  						_, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig())
   451  						require.Error(t, err, msg)
   452  					case "assert_exhaustion":
   453  						switch c.Action.ActionType {
   454  						case "invoke":
   455  							args := c.getAssertReturnArgs()
   456  							msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
   457  							if c.Action.Module != "" {
   458  								msg += " in module " + c.Action.Module
   459  							}
   460  							_, err := lastInstantiatedModule.ExportedFunction(c.Action.Field).Call(ctx, args...)
   461  							require.ErrorIs(t, err, wasmruntime.ErrRuntimeStackOverflow, msg)
   462  						default:
   463  							t.Fatalf("unsupported action type type: %v", c)
   464  						}
   465  					case "assert_unlinkable":
   466  						if c.ModuleType == "text" {
   467  							// We don't support direct loading of wast yet.
   468  							t.Skip()
   469  						}
   470  						buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
   471  						require.NoError(t, err, msg)
   472  						_, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig())
   473  						require.Error(t, err, msg)
   474  					case "assert_uninstantiable":
   475  						buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
   476  						require.NoError(t, err, msg)
   477  						_, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig())
   478  						if c.Text == "out of bounds table access" {
   479  							// This is not actually an instantiation error, but assert_trap in the original wast, but wast2json translates it to assert_uninstantiable.
   480  							// Anyway, this spectest case expects the error due to active element offset ouf of bounds
   481  							// "after" instantiation while retaining function instances used for elements.
   482  							// https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/linking.wast#L264-L274
   483  							//
   484  							// In practice, such a module instance can be used for invoking functions without any issue. In addition, we have to
   485  							// retain functions after the expected "instantiation" failure, so in wazero we choose to not raise error in that case.
   486  							require.NoError(t, err, msg)
   487  						} else {
   488  							require.Error(t, err, msg)
   489  						}
   490  					default:
   491  						t.Fatalf("unsupported command type: %s", c)
   492  					}
   493  				})
   494  			}
   495  		})
   496  	}
   497  }
   498  
   499  // basename avoids filepath.Base to ensure a forward slash is used even in Windows.
   500  // See https://pkg.go.dev/embed#hdr-Directives
   501  func basename(path string) string {
   502  	lastSlash := strings.LastIndexByte(path, '/')
   503  	return path[lastSlash+1:]
   504  }
   505  
   506  // testdataPath avoids filepath.Join to ensure a forward slash is used even in Windows.
   507  // See https://pkg.go.dev/embed#hdr-Directives
   508  func testdataPath(filename string) string {
   509  	return fmt.Sprintf("testdata/%s", filename)
   510  }
   511  
   512  // valuesEq returns true if all the actual result matches exps which are all expressed as uint64.
   513  //   - actual,exps: comparison target values which are all represented as uint64, meaning that if valTypes = [V128,I32], then
   514  //     we have actual/exp = [(lower-64bit of the first V128), (higher-64bit of the first V128), I32].
   515  //   - valTypes holds the wasm.ValueType(s) of the original values in Wasm.
   516  //   - laneTypes maps the index of valueTypes to laneType if valueTypes[i] == wasm.ValueTypeV128.
   517  //
   518  // Also, if matched == false this returns non-empty valuesMsg which can be used to augment the test failure message.
   519  func valuesEq(actual, exps []uint64, valTypes []wasm.ValueType, laneTypes map[int]laneType) (matched bool, valuesMsg string) {
   520  	matched = true
   521  
   522  	var msgExpValuesStrs, msgActualValuesStrs []string
   523  	var uint64RepPos int // the index to actual and exps slice.
   524  	for i, tp := range valTypes {
   525  		switch tp {
   526  		case wasm.ValueTypeI32:
   527  			msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%d", uint32(exps[uint64RepPos])))
   528  			msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%d", uint32(actual[uint64RepPos])))
   529  			matched = matched && uint32(exps[uint64RepPos]) == uint32(actual[uint64RepPos])
   530  			uint64RepPos++
   531  		case wasm.ValueTypeI64, wasm.ValueTypeExternref, wasm.ValueTypeFuncref:
   532  			msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%d", exps[uint64RepPos]))
   533  			msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%d", actual[uint64RepPos]))
   534  			matched = matched && exps[uint64RepPos] == actual[uint64RepPos]
   535  			uint64RepPos++
   536  		case wasm.ValueTypeF32:
   537  			a := math.Float32frombits(uint32(actual[uint64RepPos]))
   538  			e := math.Float32frombits(uint32(exps[uint64RepPos]))
   539  			msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%f", e))
   540  			msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%f", a))
   541  			matched = matched && f32Equal(e, a)
   542  			uint64RepPos++
   543  		case wasm.ValueTypeF64:
   544  			e := math.Float64frombits(exps[uint64RepPos])
   545  			a := math.Float64frombits(actual[uint64RepPos])
   546  			msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%f", e))
   547  			msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%f", a))
   548  			matched = matched && f64Equal(e, a)
   549  			uint64RepPos++
   550  		case wasm.ValueTypeV128:
   551  			actualLo, actualHi := actual[uint64RepPos], actual[uint64RepPos+1]
   552  			expLo, expHi := exps[uint64RepPos], exps[uint64RepPos+1]
   553  			switch laneTypes[i] {
   554  			case laneTypeI8:
   555  				msgExpValuesStrs = append(msgExpValuesStrs,
   556  					fmt.Sprintf("i8x16(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)",
   557  						byte(expLo), byte(expLo>>8), byte(expLo>>16), byte(expLo>>24),
   558  						byte(expLo>>32), byte(expLo>>40), byte(expLo>>48), byte(expLo>>56),
   559  						byte(expHi), byte(expHi>>8), byte(expHi>>16), byte(expHi>>24),
   560  						byte(expHi>>32), byte(expHi>>40), byte(expHi>>48), byte(expHi>>56),
   561  					),
   562  				)
   563  				msgActualValuesStrs = append(msgActualValuesStrs,
   564  					fmt.Sprintf("i8x16(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)",
   565  						byte(actualLo), byte(actualLo>>8), byte(actualLo>>16), byte(actualLo>>24),
   566  						byte(actualLo>>32), byte(actualLo>>40), byte(actualLo>>48), byte(actualLo>>56),
   567  						byte(actualHi), byte(actualHi>>8), byte(actualHi>>16), byte(actualHi>>24),
   568  						byte(actualHi>>32), byte(actualHi>>40), byte(actualHi>>48), byte(actualHi>>56),
   569  					),
   570  				)
   571  				matched = matched && (expLo == actualLo) && (expHi == actualHi)
   572  			case laneTypeI16:
   573  				msgExpValuesStrs = append(msgExpValuesStrs,
   574  					fmt.Sprintf("i16x8(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)",
   575  						uint16(expLo), uint16(expLo>>16), uint16(expLo>>32), uint16(expLo>>48),
   576  						uint16(expHi), uint16(expHi>>16), uint16(expHi>>32), uint16(expHi>>48),
   577  					),
   578  				)
   579  				msgActualValuesStrs = append(msgActualValuesStrs,
   580  					fmt.Sprintf("i16x8(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)",
   581  						uint16(actualLo), uint16(actualLo>>16), uint16(actualLo>>32), uint16(actualLo>>48),
   582  						uint16(actualHi), uint16(actualHi>>16), uint16(actualHi>>32), uint16(actualHi>>48),
   583  					),
   584  				)
   585  				matched = matched && (expLo == actualLo) && (expHi == actualHi)
   586  			case laneTypeI32:
   587  				msgExpValuesStrs = append(msgExpValuesStrs,
   588  					fmt.Sprintf("i32x4(%#x, %#x, %#x, %#x)", uint32(expLo), uint32(expLo>>32), uint32(expHi), uint32(expHi>>32)),
   589  				)
   590  				msgActualValuesStrs = append(msgActualValuesStrs,
   591  					fmt.Sprintf("i32x4(%#x, %#x, %#x, %#x)", uint32(actualLo), uint32(actualLo>>32), uint32(actualHi), uint32(actualHi>>32)),
   592  				)
   593  				matched = matched && (expLo == actualLo) && (expHi == actualHi)
   594  			case laneTypeI64:
   595  				msgExpValuesStrs = append(msgExpValuesStrs,
   596  					fmt.Sprintf("i64x2(%#x, %#x)", expLo, expHi),
   597  				)
   598  				msgActualValuesStrs = append(msgActualValuesStrs,
   599  					fmt.Sprintf("i64x2(%#x, %#x)", actualLo, actualHi),
   600  				)
   601  				matched = matched && (expLo == actualLo) && (expHi == actualHi)
   602  			case laneTypeF32:
   603  				msgExpValuesStrs = append(msgExpValuesStrs,
   604  					fmt.Sprintf("f32x4(%f, %f, %f, %f)",
   605  						math.Float32frombits(uint32(expLo)), math.Float32frombits(uint32(expLo>>32)),
   606  						math.Float32frombits(uint32(expHi)), math.Float32frombits(uint32(expHi>>32)),
   607  					),
   608  				)
   609  				msgActualValuesStrs = append(msgActualValuesStrs,
   610  					fmt.Sprintf("f32x4(%f, %f, %f, %f)",
   611  						math.Float32frombits(uint32(actualLo)), math.Float32frombits(uint32(actualLo>>32)),
   612  						math.Float32frombits(uint32(actualHi)), math.Float32frombits(uint32(actualHi>>32)),
   613  					),
   614  				)
   615  				matched = matched &&
   616  					f32Equal(math.Float32frombits(uint32(expLo)), math.Float32frombits(uint32(actualLo))) &&
   617  					f32Equal(math.Float32frombits(uint32(expLo>>32)), math.Float32frombits(uint32(actualLo>>32))) &&
   618  					f32Equal(math.Float32frombits(uint32(expHi)), math.Float32frombits(uint32(actualHi))) &&
   619  					f32Equal(math.Float32frombits(uint32(expHi>>32)), math.Float32frombits(uint32(actualHi>>32)))
   620  			case laneTypeF64:
   621  				msgExpValuesStrs = append(msgExpValuesStrs,
   622  					fmt.Sprintf("f64x2(%f, %f)", math.Float64frombits(expLo), math.Float64frombits(expHi)),
   623  				)
   624  				msgActualValuesStrs = append(msgActualValuesStrs,
   625  					fmt.Sprintf("f64x2(%f, %f)", math.Float64frombits(actualLo), math.Float64frombits(actualHi)),
   626  				)
   627  				matched = matched &&
   628  					f64Equal(math.Float64frombits(expLo), math.Float64frombits(actualLo)) &&
   629  					f64Equal(math.Float64frombits(expHi), math.Float64frombits(actualHi))
   630  			default:
   631  				panic("BUG")
   632  			}
   633  			uint64RepPos += 2
   634  		default:
   635  			panic("BUG")
   636  		}
   637  	}
   638  
   639  	if !matched {
   640  		valuesMsg = fmt.Sprintf("\thave [%s]\n\twant [%s]",
   641  			strings.Join(msgActualValuesStrs, ", "),
   642  			strings.Join(msgExpValuesStrs, ", "))
   643  	}
   644  	return
   645  }
   646  
   647  func f32Equal(expected, actual float32) (matched bool) {
   648  	if expBit := math.Float32bits(expected); expBit == moremath.F32CanonicalNaNBits {
   649  		matched = math.Float32bits(actual)&moremath.F32CanonicalNaNBitsMask == moremath.F32CanonicalNaNBits
   650  	} else if expBit == moremath.F32ArithmeticNaNBits {
   651  		b := math.Float32bits(actual)
   652  		matched = b&moremath.F32ExponentMask == moremath.F32ExponentMask && // Indicates that exponent part equals of NaN.
   653  			b&moremath.F32ArithmeticNaNPayloadMSB == moremath.F32ArithmeticNaNPayloadMSB
   654  	} else if math.IsNaN(float64(expected)) { // NaN cannot be compared with themselves, so we have to use IsNaN
   655  		matched = math.IsNaN(float64(actual))
   656  	} else {
   657  		// Compare the bit patterns directly, rather than == on float32 since in Go, -0 and 0 equals,
   658  		// but in the Wasm spec, they are treated as different.
   659  		matched = math.Float32bits(expected) == math.Float32bits(actual)
   660  	}
   661  	return
   662  }
   663  
   664  func f64Equal(expected, actual float64) (matched bool) {
   665  	if expBit := math.Float64bits(expected); expBit == moremath.F64CanonicalNaNBits {
   666  		matched = math.Float64bits(actual)&moremath.F64CanonicalNaNBitsMask == moremath.F64CanonicalNaNBits
   667  	} else if expBit == moremath.F64ArithmeticNaNBits {
   668  		b := math.Float64bits(actual)
   669  		matched = b&moremath.F64ExponentMask == moremath.F64ExponentMask && // Indicates that exponent part equals of NaN.
   670  			b&moremath.F64ArithmeticNaNPayloadMSB == moremath.F64ArithmeticNaNPayloadMSB
   671  	} else if math.IsNaN(expected) { // NaN cannot be compared with themselves, so we have to use IsNaN
   672  		matched = math.IsNaN(actual)
   673  	} else {
   674  		// Compare the bit patterns directly, rather than == on float64 since in Go, -0 and 0 equals,
   675  		// but in the Wasm spec, they are treated as different.
   676  		matched = math.Float64bits(expected) == math.Float64bits(actual)
   677  	}
   678  	return
   679  }