wa-lang.org/wazero@v1.0.2/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  	"wa-lang.org/wazero/api"
    14  	"wa-lang.org/wazero/internal/leb128"
    15  	"wa-lang.org/wazero/internal/moremath"
    16  	"wa-lang.org/wazero/internal/sys"
    17  	"wa-lang.org/wazero/internal/testing/require"
    18  	"wa-lang.org/wazero/internal/u64"
    19  	"wa-lang.org/wazero/internal/wasm"
    20  	binaryformat "wa-lang.org/wazero/internal/wasm/binary"
    21  	"wa-lang.org/wazero/internal/wasmruntime"
    22  )
    23  
    24  // TODO: complete porting this to wazero API
    25  type (
    26  	testbase struct {
    27  		SourceFile string    `json:"source_filename"`
    28  		Commands   []command `json:"commands"`
    29  	}
    30  	command struct {
    31  		CommandType string `json:"type"`
    32  		Line        int    `json:"line"`
    33  
    34  		// Set when type == "module" || "register"
    35  		Name string `json:"name,omitempty"`
    36  
    37  		// Set when type == "module" || "assert_uninstantiable" || "assert_malformed"
    38  		Filename string `json:"filename,omitempty"`
    39  
    40  		// Set when type == "register"
    41  		As string `json:"as,omitempty"`
    42  
    43  		// Set when type == "assert_return" || "action"
    44  		Action commandAction      `json:"action,omitempty"`
    45  		Exps   []commandActionVal `json:"expected"`
    46  
    47  		// Set when type == "assert_malformed"
    48  		ModuleType string `json:"module_type"`
    49  
    50  		// Set when type == "assert_trap"
    51  		Text string `json:"text"`
    52  	}
    53  
    54  	commandAction struct {
    55  		ActionType string             `json:"type"`
    56  		Args       []commandActionVal `json:"args"`
    57  
    58  		// Set when ActionType == "invoke"
    59  		Field  string `json:"field,omitempty"`
    60  		Module string `json:"module,omitempty"`
    61  	}
    62  
    63  	commandActionVal struct {
    64  		ValType string `json:"type"`
    65  		// LaneType is not empty if ValueType == "v128"
    66  		LaneType laneType    `json:"lane_type"`
    67  		Value    interface{} `json:"value"`
    68  	}
    69  )
    70  
    71  // laneType is a type of each lane of vector value.
    72  //
    73  // See https://github.com/WebAssembly/wabt/blob/main/docs/wast2json.md#const
    74  type laneType = string
    75  
    76  const (
    77  	laneTypeI8  laneType = "i8"
    78  	laneTypeI16 laneType = "i16"
    79  	laneTypeI32 laneType = "i32"
    80  	laneTypeI64 laneType = "i64"
    81  	laneTypeF32 laneType = "f32"
    82  	laneTypeF64 laneType = "f64"
    83  )
    84  
    85  func (c commandActionVal) String() string {
    86  	var v string
    87  	valTypeStr := c.ValType
    88  	switch c.ValType {
    89  	case "i32":
    90  		v = c.Value.(string)
    91  	case "f32":
    92  		str := c.Value.(string)
    93  		if strings.Contains(str, "nan") {
    94  			v = str
    95  		} else {
    96  			ret, _ := strconv.ParseUint(str, 10, 32)
    97  			v = fmt.Sprintf("%f", math.Float32frombits(uint32(ret)))
    98  		}
    99  	case "i64":
   100  		v = c.Value.(string)
   101  	case "f64":
   102  		str := c.Value.(string)
   103  		if strings.Contains(str, "nan") {
   104  			v = str
   105  		} else {
   106  			ret, _ := strconv.ParseUint(str, 10, 64)
   107  			v = fmt.Sprintf("%f", math.Float64frombits(ret))
   108  		}
   109  	case "externref":
   110  		if c.Value == "null" {
   111  			v = "null"
   112  		} else {
   113  			original, _ := strconv.ParseUint(c.Value.(string), 10, 64)
   114  			// In wazero, externref is opaque pointer, so "0" is considered as null.
   115  			// So in order to treat "externref 0" in spectest non nullref, we increment the value.
   116  			v = fmt.Sprintf("%d", original+1)
   117  		}
   118  	case "funcref":
   119  		// All the in and out funcref params are null in spectest (cannot represent non-null as it depends on runtime impl).
   120  		v = "null"
   121  	case "v128":
   122  		simdValues, ok := c.Value.([]interface{})
   123  		if !ok {
   124  			panic("BUG")
   125  		}
   126  		var strs []string
   127  		for _, v := range simdValues {
   128  			strs = append(strs, v.(string))
   129  		}
   130  		v = strings.Join(strs, ",")
   131  		valTypeStr = fmt.Sprintf("v128[lane=%s]", c.LaneType)
   132  	}
   133  	return fmt.Sprintf("{type: %s, value: %v}", valTypeStr, v)
   134  }
   135  
   136  func (c command) String() string {
   137  	msg := fmt.Sprintf("line: %d, type: %s", c.Line, c.CommandType)
   138  	switch c.CommandType {
   139  	case "register":
   140  		msg += fmt.Sprintf(", name: %s, as: %s", c.Name, c.As)
   141  	case "module":
   142  		if c.Name != "" {
   143  			msg += fmt.Sprintf(", name: %s, filename: %s", c.Name, c.Filename)
   144  		} else {
   145  			msg += fmt.Sprintf(", filename: %s", c.Filename)
   146  		}
   147  	case "assert_return", "action":
   148  		msg += fmt.Sprintf(", action type: %s", c.Action.ActionType)
   149  		if c.Action.Module != "" {
   150  			msg += fmt.Sprintf(", module: %s", c.Action.Module)
   151  		}
   152  		msg += fmt.Sprintf(", field: %s", c.Action.Field)
   153  		msg += fmt.Sprintf(", args: %v, expected: %v", c.Action.Args, c.Exps)
   154  	case "assert_malformed":
   155  		// TODO:
   156  	case "assert_trap":
   157  		msg += fmt.Sprintf(", args: %v, error text:  %s", c.Action.Args, c.Text)
   158  	case "assert_invalid":
   159  		// TODO:
   160  	case "assert_exhaustion":
   161  		// TODO:
   162  	case "assert_unlinkable":
   163  		// TODO:
   164  	case "assert_uninstantiable":
   165  		// TODO:
   166  	}
   167  	return "{" + msg + "}"
   168  }
   169  
   170  func (c command) getAssertReturnArgs() []uint64 {
   171  	var args []uint64
   172  	for _, arg := range c.Action.Args {
   173  		args = append(args, arg.toUint64s()...)
   174  	}
   175  	return args
   176  }
   177  
   178  func (c command) getAssertReturnArgsExps() (args []uint64, exps []uint64) {
   179  	for _, arg := range c.Action.Args {
   180  		args = append(args, arg.toUint64s()...)
   181  	}
   182  	for _, exp := range c.Exps {
   183  		exps = append(exps, exp.toUint64s()...)
   184  	}
   185  	return
   186  }
   187  
   188  func (c commandActionVal) toUint64s() (ret []uint64) {
   189  	if c.ValType == "v128" {
   190  		strValues, ok := c.Value.([]interface{})
   191  		if !ok {
   192  			panic("BUG")
   193  		}
   194  		var width, valNum int
   195  		switch c.LaneType {
   196  		case "i8":
   197  			width, valNum = 8, 16
   198  		case "i16":
   199  			width, valNum = 16, 8
   200  		case "i32":
   201  			width, valNum = 32, 4
   202  		case "i64":
   203  			width, valNum = 64, 2
   204  		case "f32":
   205  			width, valNum = 32, 4
   206  		case "f64":
   207  			width, valNum = 64, 2
   208  		default:
   209  			panic("BUG")
   210  		}
   211  		lo, hi := buildLaneUint64(strValues, width, valNum)
   212  		return []uint64{lo, hi}
   213  	} else {
   214  		return []uint64{c.toUint64()}
   215  	}
   216  }
   217  
   218  func buildLaneUint64(raw []interface{}, width, valNum int) (lo, hi uint64) {
   219  	for i := 0; i < valNum; i++ {
   220  		str := raw[i].(string)
   221  
   222  		var v uint64
   223  		var err error
   224  		if strings.Contains(str, "nan") {
   225  			v = getNaNBits(str, width == 32)
   226  		} else {
   227  			v, err = strconv.ParseUint(str, 10, width)
   228  			if err != nil {
   229  				panic(err)
   230  			}
   231  		}
   232  
   233  		if half := valNum / 2; i < half {
   234  			lo |= v << (i * width)
   235  		} else {
   236  			hi |= v << ((i - half) * width)
   237  		}
   238  	}
   239  	return
   240  }
   241  
   242  func getNaNBits(strValue string, is32bit bool) (ret uint64) {
   243  	// Note: nan:canonical, nan:arithmetic only appears on the expected values.
   244  	if is32bit {
   245  		switch strValue {
   246  		case "nan:canonical":
   247  			ret = uint64(moremath.F32CanonicalNaNBits)
   248  		case "nan:arithmetic":
   249  			ret = uint64(moremath.F32ArithmeticNaNBits)
   250  		default:
   251  			panic("BUG")
   252  		}
   253  	} else {
   254  		switch strValue {
   255  		case "nan:canonical":
   256  			ret = moremath.F64CanonicalNaNBits
   257  		case "nan:arithmetic":
   258  			ret = moremath.F64ArithmeticNaNBits
   259  		default:
   260  			panic("BUG")
   261  		}
   262  	}
   263  	return
   264  }
   265  
   266  func (c commandActionVal) toUint64() (ret uint64) {
   267  	strValue := c.Value.(string)
   268  	if strings.Contains(strValue, "nan") {
   269  		ret = getNaNBits(strValue, c.ValType == "f32")
   270  	} else if c.ValType == "externref" {
   271  		if c.Value == "null" {
   272  			ret = 0
   273  		} else {
   274  			original, _ := strconv.ParseUint(strValue, 10, 64)
   275  			// In wazero, externref is opaque pointer, so "0" is considered as null.
   276  			// So in order to treat "externref 0" in spectest non nullref, we increment the value.
   277  			ret = original + 1
   278  		}
   279  	} else if strings.Contains(c.ValType, "32") {
   280  		ret, _ = strconv.ParseUint(strValue, 10, 32)
   281  	} else {
   282  		ret, _ = strconv.ParseUint(strValue, 10, 64)
   283  	}
   284  	return
   285  }
   286  
   287  // expectedError returns the expected runtime error when the command type equals assert_trap
   288  // which expects engines to emit the errors corresponding command.Text field.
   289  func (c command) expectedError() (err error) {
   290  	if c.CommandType != "assert_trap" {
   291  		panic("unreachable")
   292  	}
   293  	switch c.Text {
   294  	case "out of bounds memory access":
   295  		err = wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess
   296  	case "indirect call type mismatch", "indirect call":
   297  		err = wasmruntime.ErrRuntimeIndirectCallTypeMismatch
   298  	case "undefined element", "undefined", "out of bounds table access":
   299  		err = wasmruntime.ErrRuntimeInvalidTableAccess
   300  	case "integer overflow":
   301  		err = wasmruntime.ErrRuntimeIntegerOverflow
   302  	case "invalid conversion to integer":
   303  		err = wasmruntime.ErrRuntimeInvalidConversionToInteger
   304  	case "integer divide by zero":
   305  		err = wasmruntime.ErrRuntimeIntegerDivideByZero
   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  //go:embed testdata/spectest.wasm
   321  var spectestWasm []byte
   322  
   323  // addSpectestModule adds a module that drops inputs and returns globals as 666 per the default test harness.
   324  //
   325  // See https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/imports.wast
   326  // See https://github.com/WebAssembly/spec/blob/wg-1.0/interpreter/script/js.ml#L13-L25
   327  func addSpectestModule(t *testing.T, ctx context.Context, s *wasm.Store, ns *wasm.Namespace, enabledFeatures api.CoreFeatures) {
   328  	mod, err := binaryformat.DecodeModule(spectestWasm, api.CoreFeaturesV2, wasm.MemoryLimitPages, false)
   329  	require.NoError(t, err)
   330  
   331  	// (global (export "global_i32") i32 (i32.const 666))
   332  	mod.GlobalSection = append(mod.GlobalSection, &wasm.Global{
   333  		Type: &wasm.GlobalType{ValType: wasm.ValueTypeI32},
   334  		Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(666)},
   335  	})
   336  	mod.ExportSection = append(mod.ExportSection, &wasm.Export{Name: "global_i32", Index: 0, Type: wasm.ExternTypeGlobal})
   337  
   338  	// (global (export "global_i64") i64 (i32.const 666))
   339  	mod.GlobalSection = append(mod.GlobalSection, &wasm.Global{
   340  		Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64},
   341  		Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt32(666)},
   342  	})
   343  	mod.ExportSection = append(mod.ExportSection, &wasm.Export{Name: "global_i64", Index: 1, Type: wasm.ExternTypeGlobal})
   344  
   345  	// (global (export "global_f32") f32 (f32.const 666))
   346  	mod.GlobalSection = append(mod.GlobalSection, &wasm.Global{
   347  		Type: &wasm.GlobalType{ValType: wasm.ValueTypeF32},
   348  		Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF32Const, Data: u64.LeBytes(api.EncodeF32(666))},
   349  	})
   350  	mod.ExportSection = append(mod.ExportSection, &wasm.Export{Name: "global_f32", Index: 2, Type: wasm.ExternTypeGlobal})
   351  
   352  	// (global (export "global_f64") f64 (f64.const 666))
   353  	mod.GlobalSection = append(mod.GlobalSection, &wasm.Global{
   354  		Type: &wasm.GlobalType{ValType: wasm.ValueTypeF64},
   355  		Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF64Const, Data: u64.LeBytes(api.EncodeF64(666))},
   356  	})
   357  	mod.ExportSection = append(mod.ExportSection, &wasm.Export{Name: "global_f64", Index: 3, Type: wasm.ExternTypeGlobal})
   358  
   359  	//  (table (export "table") 10 20 funcref)
   360  	tableLimitMax := uint32(20)
   361  	mod.TableSection = []*wasm.Table{{Min: 10, Max: &tableLimitMax, Type: wasm.RefTypeFuncref}}
   362  	mod.ExportSection = append(mod.ExportSection, &wasm.Export{Name: "table", Index: 0, Type: wasm.ExternTypeTable})
   363  
   364  	maybeSetMemoryCap(mod)
   365  	mod.BuildFunctionDefinitions()
   366  
   367  	err = mod.Validate(enabledFeatures)
   368  	require.NoError(t, err)
   369  
   370  	err = s.Engine.CompileModule(ctx, mod, nil)
   371  	require.NoError(t, err)
   372  
   373  	_, err = s.Instantiate(ctx, ns, mod, mod.NameSection.ModuleName, sys.DefaultContext(nil))
   374  	require.NoError(t, err)
   375  }
   376  
   377  // maybeSetMemoryCap assigns wasm.Memory Cap to Min, which is what wazero.CompileModule would do.
   378  func maybeSetMemoryCap(mod *wasm.Module) {
   379  	if mem := mod.MemorySection; mem != nil {
   380  		mem.Cap = mem.Min
   381  	}
   382  }
   383  
   384  // Run runs all the test inside the testDataFS file system where all the cases are described
   385  // via JSON files created from wast2json.
   386  func Run(t *testing.T, testDataFS embed.FS, ctx context.Context, newEngine func(context.Context, api.CoreFeatures) wasm.Engine, enabledFeatures api.CoreFeatures) {
   387  	files, err := testDataFS.ReadDir("testdata")
   388  	require.NoError(t, err)
   389  
   390  	jsonfiles := make([]string, 0, len(files))
   391  	for _, f := range files {
   392  		filename := f.Name()
   393  		if strings.HasSuffix(filename, ".json") {
   394  			jsonfiles = append(jsonfiles, testdataPath(filename))
   395  		}
   396  	}
   397  
   398  	// If the go:embed path resolution was wrong, this fails.
   399  	// https://github.com/tetratelabs/wazero/issues/247
   400  	require.True(t, len(jsonfiles) > 1, "len(jsonfiles)=%d (not greater than one)", len(jsonfiles))
   401  
   402  	for _, f := range jsonfiles {
   403  		raw, err := testDataFS.ReadFile(f)
   404  		require.NoError(t, err)
   405  
   406  		var base testbase
   407  		require.NoError(t, json.Unmarshal(raw, &base))
   408  
   409  		wastName := basename(base.SourceFile)
   410  
   411  		t.Run(wastName, func(t *testing.T) {
   412  			s, ns := wasm.NewStore(enabledFeatures, newEngine(ctx, enabledFeatures))
   413  			addSpectestModule(t, ctx, s, ns, enabledFeatures)
   414  
   415  			var lastInstantiatedModuleName string
   416  			for _, c := range base.Commands {
   417  				t.Run(fmt.Sprintf("%s/line:%d", c.CommandType, c.Line), func(t *testing.T) {
   418  					msg := fmt.Sprintf("%s:%d %s", wastName, c.Line, c.CommandType)
   419  					switch c.CommandType {
   420  					case "module":
   421  						buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
   422  						require.NoError(t, err, msg)
   423  						mod, err := binaryformat.DecodeModule(buf, enabledFeatures, wasm.MemoryLimitPages, false)
   424  						require.NoError(t, err, msg)
   425  						require.NoError(t, mod.Validate(enabledFeatures))
   426  						mod.AssignModuleID(buf)
   427  
   428  						moduleName := c.Name
   429  						if moduleName == "" {
   430  							// Use the file name as the name.
   431  							moduleName = c.Filename
   432  						}
   433  
   434  						maybeSetMemoryCap(mod)
   435  						mod.BuildFunctionDefinitions()
   436  						err = s.Engine.CompileModule(ctx, mod, nil)
   437  						require.NoError(t, err, msg)
   438  
   439  						_, err = s.Instantiate(ctx, ns, mod, moduleName, nil)
   440  						lastInstantiatedModuleName = moduleName
   441  						require.NoError(t, err)
   442  					case "register":
   443  						src := c.Name
   444  						if src == "" {
   445  							src = lastInstantiatedModuleName
   446  						}
   447  						ns.AliasModule(src, c.As)
   448  						lastInstantiatedModuleName = c.As
   449  					case "assert_return", "action":
   450  						moduleName := lastInstantiatedModuleName
   451  						if c.Action.Module != "" {
   452  							moduleName = c.Action.Module
   453  						}
   454  						switch c.Action.ActionType {
   455  						case "invoke":
   456  							args, exps := c.getAssertReturnArgsExps()
   457  							msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
   458  							if c.Action.Module != "" {
   459  								msg += " in module " + c.Action.Module
   460  							}
   461  							vals, types, err := callFunction(ns, ctx, moduleName, c.Action.Field, args...)
   462  							require.NoError(t, err, msg)
   463  							require.Equal(t, len(exps), len(vals), msg)
   464  							laneTypes := map[int]string{}
   465  							for i, expV := range c.Exps {
   466  								if expV.ValType == "v128" {
   467  									laneTypes[i] = expV.LaneType
   468  								}
   469  							}
   470  							matched, valuesMsg := valuesEq(vals, exps, types, laneTypes)
   471  							require.True(t, matched, msg+"\n"+valuesMsg)
   472  						case "get":
   473  							_, exps := c.getAssertReturnArgsExps()
   474  							require.Equal(t, 1, len(exps))
   475  							msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
   476  							if c.Action.Module != "" {
   477  								msg += " in module " + c.Action.Module
   478  							}
   479  							module := ns.Module(moduleName)
   480  							require.NotNil(t, module)
   481  							global := module.ExportedGlobal(c.Action.Field)
   482  							require.NotNil(t, global)
   483  							var expType wasm.ValueType
   484  							switch c.Exps[0].ValType {
   485  							case "i32":
   486  								expType = wasm.ValueTypeI32
   487  							case "i64":
   488  								expType = wasm.ValueTypeI64
   489  							case "f32":
   490  								expType = wasm.ValueTypeF32
   491  							case "f64":
   492  								expType = wasm.ValueTypeF64
   493  							}
   494  							require.Equal(t, expType, global.Type(), msg)
   495  							require.Equal(t, exps[0], global.Get(ctx), msg)
   496  						default:
   497  							t.Fatalf("unsupported action type type: %v", c)
   498  						}
   499  					case "assert_malformed":
   500  						if c.ModuleType != "text" {
   501  							// We don't support direct loading of wast yet.
   502  							buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
   503  							require.NoError(t, err, msg)
   504  							requireInstantiationError(t, ctx, s, ns, buf, msg)
   505  						}
   506  					case "assert_trap":
   507  						moduleName := lastInstantiatedModuleName
   508  						if c.Action.Module != "" {
   509  							moduleName = c.Action.Module
   510  						}
   511  						switch c.Action.ActionType {
   512  						case "invoke":
   513  							args := c.getAssertReturnArgs()
   514  							msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
   515  							if c.Action.Module != "" {
   516  								msg += " in module " + c.Action.Module
   517  							}
   518  							_, _, err := callFunction(ns, ctx, moduleName, c.Action.Field, args...)
   519  							require.ErrorIs(t, err, c.expectedError(), msg)
   520  						default:
   521  							t.Fatalf("unsupported action type type: %v", c)
   522  						}
   523  					case "assert_invalid":
   524  						if c.ModuleType == "text" {
   525  							// We don't support direct loading of wast yet.
   526  							t.Skip()
   527  						}
   528  						buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
   529  						require.NoError(t, err, msg)
   530  						requireInstantiationError(t, ctx, s, ns, buf, msg)
   531  					case "assert_exhaustion":
   532  						moduleName := lastInstantiatedModuleName
   533  						switch c.Action.ActionType {
   534  						case "invoke":
   535  							args := c.getAssertReturnArgs()
   536  							msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
   537  							if c.Action.Module != "" {
   538  								msg += " in module " + c.Action.Module
   539  							}
   540  							_, _, err := callFunction(ns, ctx, moduleName, c.Action.Field, args...)
   541  							require.ErrorIs(t, err, wasmruntime.ErrRuntimeStackOverflow, msg)
   542  						default:
   543  							t.Fatalf("unsupported action type type: %v", c)
   544  						}
   545  					case "assert_unlinkable":
   546  						if c.ModuleType == "text" {
   547  							// We don't support direct loading of wast yet.
   548  							t.Skip()
   549  						}
   550  						buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
   551  						require.NoError(t, err, msg)
   552  						requireInstantiationError(t, ctx, s, ns, buf, msg)
   553  					case "assert_uninstantiable":
   554  						buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
   555  						require.NoError(t, err, msg)
   556  						if c.Text == "out of bounds table access" {
   557  							// This is not actually an instantiation error, but assert_trap in the original wast, but wast2json translates it to assert_uninstantiable.
   558  							// Anyway, this spectest case expects the error due to active element offset ouf of bounds
   559  							// "after" instantiation while retaining function instances used for elements.
   560  							// https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/linking.wast#L264-L274
   561  							//
   562  							// In practice, such a module instance can be used for invoking functions without any issue. In addition, we have to
   563  							// retain functions after the expected "instantiation" failure, so in wazero we choose to not raise error in that case.
   564  							mod, err := binaryformat.DecodeModule(buf, s.EnabledFeatures, wasm.MemoryLimitPages, false)
   565  							require.NoError(t, err, msg)
   566  
   567  							err = mod.Validate(s.EnabledFeatures)
   568  							require.NoError(t, err, msg)
   569  
   570  							mod.AssignModuleID(buf)
   571  
   572  							maybeSetMemoryCap(mod)
   573  							mod.BuildFunctionDefinitions()
   574  							err = s.Engine.CompileModule(ctx, mod, nil)
   575  							require.NoError(t, err, msg)
   576  
   577  							_, err = s.Instantiate(ctx, ns, mod, t.Name(), nil)
   578  							require.NoError(t, err, msg)
   579  						} else {
   580  							requireInstantiationError(t, ctx, s, ns, buf, msg)
   581  						}
   582  
   583  					default:
   584  						t.Fatalf("unsupported command type: %s", c)
   585  					}
   586  				})
   587  			}
   588  		})
   589  	}
   590  }
   591  
   592  func requireInstantiationError(t *testing.T, ctx context.Context, s *wasm.Store, ns *wasm.Namespace, buf []byte, msg string) {
   593  	mod, err := binaryformat.DecodeModule(buf, s.EnabledFeatures, wasm.MemoryLimitPages, false)
   594  	if err != nil {
   595  		return
   596  	}
   597  
   598  	err = mod.Validate(s.EnabledFeatures)
   599  	if err != nil {
   600  		return
   601  	}
   602  
   603  	mod.AssignModuleID(buf)
   604  
   605  	maybeSetMemoryCap(mod)
   606  	mod.BuildFunctionDefinitions()
   607  	err = s.Engine.CompileModule(ctx, mod, nil)
   608  	if err != nil {
   609  		return
   610  	}
   611  
   612  	_, err = s.Instantiate(ctx, ns, mod, t.Name(), nil)
   613  	require.Error(t, err, msg)
   614  }
   615  
   616  // basename avoids filepath.Base to ensure a forward slash is used even in Windows.
   617  // See https://pkg.go.dev/embed#hdr-Directives
   618  func basename(path string) string {
   619  	lastSlash := strings.LastIndexByte(path, '/')
   620  	return path[lastSlash+1:]
   621  }
   622  
   623  // testdataPath avoids filepath.Join to ensure a forward slash is used even in Windows.
   624  // See https://pkg.go.dev/embed#hdr-Directives
   625  func testdataPath(filename string) string {
   626  	return fmt.Sprintf("testdata/%s", filename)
   627  }
   628  
   629  // valuesEq returns true if all the actual result matches exps which are all expressed as uint64.
   630  //   - actual,exps: comparison target values which are all represented as uint64, meaning that if valTypes = [V128,I32], then
   631  //     we have actual/exp = [(lower-64bit of the first V128), (higher-64bit of the first V128), I32].
   632  //   - valTypes holds the wasm.ValueType(s) of the original values in Wasm.
   633  //   - laneTypes maps the index of valueTypes to laneType if valueTypes[i] == wasm.ValueTypeV128.
   634  //
   635  // Also, if matched == false this returns non-empty valuesMsg which can be used to augment the test failure message.
   636  func valuesEq(actual, exps []uint64, valTypes []wasm.ValueType, laneTypes map[int]laneType) (matched bool, valuesMsg string) {
   637  	matched = true
   638  
   639  	var msgExpValuesStrs, msgActualValuesStrs []string
   640  	var uint64RepPos int // the index to actual and exps slice.
   641  	for i, tp := range valTypes {
   642  		switch tp {
   643  		case wasm.ValueTypeI32:
   644  			msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%d", uint32(exps[uint64RepPos])))
   645  			msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%d", uint32(actual[uint64RepPos])))
   646  			matched = matched && uint32(exps[uint64RepPos]) == uint32(actual[uint64RepPos])
   647  			uint64RepPos++
   648  		case wasm.ValueTypeI64, wasm.ValueTypeExternref, wasm.ValueTypeFuncref:
   649  			msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%d", exps[uint64RepPos]))
   650  			msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%d", actual[uint64RepPos]))
   651  			matched = matched && exps[uint64RepPos] == actual[uint64RepPos]
   652  			uint64RepPos++
   653  		case wasm.ValueTypeF32:
   654  			a := math.Float32frombits(uint32(actual[uint64RepPos]))
   655  			e := math.Float32frombits(uint32(exps[uint64RepPos]))
   656  			msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%f", e))
   657  			msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%f", a))
   658  			matched = matched && f32Equal(e, a)
   659  			uint64RepPos++
   660  		case wasm.ValueTypeF64:
   661  			e := math.Float64frombits(exps[uint64RepPos])
   662  			a := math.Float64frombits(actual[uint64RepPos])
   663  			msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%f", e))
   664  			msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%f", a))
   665  			matched = matched && f64Equal(e, a)
   666  			uint64RepPos++
   667  		case wasm.ValueTypeV128:
   668  			actualLo, actualHi := actual[uint64RepPos], actual[uint64RepPos+1]
   669  			expLo, expHi := exps[uint64RepPos], exps[uint64RepPos+1]
   670  			switch laneTypes[i] {
   671  			case laneTypeI8:
   672  				msgExpValuesStrs = append(msgExpValuesStrs,
   673  					fmt.Sprintf("i8x16(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)",
   674  						byte(expLo), byte(expLo>>8), byte(expLo>>16), byte(expLo>>24),
   675  						byte(expLo>>32), byte(expLo>>40), byte(expLo>>48), byte(expLo>>56),
   676  						byte(expHi), byte(expHi>>8), byte(expHi>>16), byte(expHi>>24),
   677  						byte(expHi>>32), byte(expHi>>40), byte(expHi>>48), byte(expHi>>56),
   678  					),
   679  				)
   680  				msgActualValuesStrs = append(msgActualValuesStrs,
   681  					fmt.Sprintf("i8x16(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)",
   682  						byte(actualLo), byte(actualLo>>8), byte(actualLo>>16), byte(actualLo>>24),
   683  						byte(actualLo>>32), byte(actualLo>>40), byte(actualLo>>48), byte(actualLo>>56),
   684  						byte(actualHi), byte(actualHi>>8), byte(actualHi>>16), byte(actualHi>>24),
   685  						byte(actualHi>>32), byte(actualHi>>40), byte(actualHi>>48), byte(actualHi>>56),
   686  					),
   687  				)
   688  				matched = matched && (expLo == actualLo) && (expHi == actualHi)
   689  			case laneTypeI16:
   690  				msgExpValuesStrs = append(msgExpValuesStrs,
   691  					fmt.Sprintf("i16x8(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)",
   692  						uint16(expLo), uint16(expLo>>16), uint16(expLo>>32), uint16(expLo>>48),
   693  						uint16(expHi), uint16(expHi>>16), uint16(expHi>>32), uint16(expHi>>48),
   694  					),
   695  				)
   696  				msgActualValuesStrs = append(msgActualValuesStrs,
   697  					fmt.Sprintf("i16x8(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)",
   698  						uint16(actualLo), uint16(actualLo>>16), uint16(actualLo>>32), uint16(actualLo>>48),
   699  						uint16(actualHi), uint16(actualHi>>16), uint16(actualHi>>32), uint16(actualHi>>48),
   700  					),
   701  				)
   702  				matched = matched && (expLo == actualLo) && (expHi == actualHi)
   703  			case laneTypeI32:
   704  				msgExpValuesStrs = append(msgExpValuesStrs,
   705  					fmt.Sprintf("i32x4(%#x, %#x, %#x, %#x)", uint32(expLo), uint32(expLo>>32), uint32(expHi), uint32(expHi>>32)),
   706  				)
   707  				msgActualValuesStrs = append(msgActualValuesStrs,
   708  					fmt.Sprintf("i32x4(%#x, %#x, %#x, %#x)", uint32(actualLo), uint32(actualLo>>32), uint32(actualHi), uint32(actualHi>>32)),
   709  				)
   710  				matched = matched && (expLo == actualLo) && (expHi == actualHi)
   711  			case laneTypeI64:
   712  				msgExpValuesStrs = append(msgExpValuesStrs,
   713  					fmt.Sprintf("i64x2(%#x, %#x)", expLo, expHi),
   714  				)
   715  				msgActualValuesStrs = append(msgActualValuesStrs,
   716  					fmt.Sprintf("i64x2(%#x, %#x)", actualLo, actualHi),
   717  				)
   718  				matched = matched && (expLo == actualLo) && (expHi == actualHi)
   719  			case laneTypeF32:
   720  				msgExpValuesStrs = append(msgExpValuesStrs,
   721  					fmt.Sprintf("f32x4(%f, %f, %f, %f)",
   722  						math.Float32frombits(uint32(expLo)), math.Float32frombits(uint32(expLo>>32)),
   723  						math.Float32frombits(uint32(expHi)), math.Float32frombits(uint32(expHi>>32)),
   724  					),
   725  				)
   726  				msgActualValuesStrs = append(msgActualValuesStrs,
   727  					fmt.Sprintf("f32x4(%f, %f, %f, %f)",
   728  						math.Float32frombits(uint32(actualLo)), math.Float32frombits(uint32(actualLo>>32)),
   729  						math.Float32frombits(uint32(actualHi)), math.Float32frombits(uint32(actualHi>>32)),
   730  					),
   731  				)
   732  				matched = matched &&
   733  					f32Equal(math.Float32frombits(uint32(expLo)), math.Float32frombits(uint32(actualLo))) &&
   734  					f32Equal(math.Float32frombits(uint32(expLo>>32)), math.Float32frombits(uint32(actualLo>>32))) &&
   735  					f32Equal(math.Float32frombits(uint32(expHi)), math.Float32frombits(uint32(actualHi))) &&
   736  					f32Equal(math.Float32frombits(uint32(expHi>>32)), math.Float32frombits(uint32(actualHi>>32)))
   737  			case laneTypeF64:
   738  				msgExpValuesStrs = append(msgExpValuesStrs,
   739  					fmt.Sprintf("f64x2(%f, %f)", math.Float64frombits(expLo), math.Float64frombits(expHi)),
   740  				)
   741  				msgActualValuesStrs = append(msgActualValuesStrs,
   742  					fmt.Sprintf("f64x2(%f, %f)", math.Float64frombits(actualLo), math.Float64frombits(actualHi)),
   743  				)
   744  				matched = matched &&
   745  					f64Equal(math.Float64frombits(expLo), math.Float64frombits(actualLo)) &&
   746  					f64Equal(math.Float64frombits(expHi), math.Float64frombits(actualHi))
   747  			default:
   748  				panic("BUG")
   749  			}
   750  			uint64RepPos += 2
   751  		default:
   752  			panic("BUG")
   753  		}
   754  	}
   755  
   756  	if !matched {
   757  		valuesMsg = fmt.Sprintf("\thave [%s]\n\twant [%s]",
   758  			strings.Join(msgActualValuesStrs, ", "),
   759  			strings.Join(msgExpValuesStrs, ", "))
   760  	}
   761  	return
   762  }
   763  
   764  func f32Equal(expected, actual float32) (matched bool) {
   765  	if expBit := math.Float32bits(expected); expBit == moremath.F32CanonicalNaNBits {
   766  		matched = math.Float32bits(actual)&moremath.F32CanonicalNaNBitsMask == moremath.F32CanonicalNaNBits
   767  	} else if expBit == moremath.F32ArithmeticNaNBits {
   768  		b := math.Float32bits(actual)
   769  		matched = b&moremath.F32ExponentMask == moremath.F32ExponentMask && // Indicates that exponent part equals of NaN.
   770  			b&moremath.F32ArithmeticNaNPayloadMSB == moremath.F32ArithmeticNaNPayloadMSB
   771  	} else if math.IsNaN(float64(expected)) { // NaN cannot be compared with themselves, so we have to use IsNaN
   772  		matched = math.IsNaN(float64(actual))
   773  	} else {
   774  		// Compare the bit patterns directly, rather than == on float32 since in Go, -0 and 0 equals,
   775  		// but in the Wasm spec, they are treated as different.
   776  		matched = math.Float32bits(expected) == math.Float32bits(actual)
   777  	}
   778  	return
   779  }
   780  
   781  func f64Equal(expected, actual float64) (matched bool) {
   782  	if expBit := math.Float64bits(expected); expBit == moremath.F64CanonicalNaNBits {
   783  		matched = math.Float64bits(actual)&moremath.F64CanonicalNaNBitsMask == moremath.F64CanonicalNaNBits
   784  	} else if expBit == moremath.F64ArithmeticNaNBits {
   785  		b := math.Float64bits(actual)
   786  		matched = b&moremath.F64ExponentMask == moremath.F64ExponentMask && // Indicates that exponent part equals of NaN.
   787  			b&moremath.F64ArithmeticNaNPayloadMSB == moremath.F64ArithmeticNaNPayloadMSB
   788  	} else if math.IsNaN(expected) { // NaN cannot be compared with themselves, so we have to use IsNaN
   789  		matched = math.IsNaN(actual)
   790  	} else {
   791  		// Compare the bit patterns directly, rather than == on float64 since in Go, -0 and 0 equals,
   792  		// but in the Wasm spec, they are treated as different.
   793  		matched = math.Float64bits(expected) == math.Float64bits(actual)
   794  	}
   795  	return
   796  }
   797  
   798  // callFunction is inlined here as the spectest needs to validate the signature was correct
   799  // TODO: This is likely already covered with unit tests!
   800  func callFunction(ns *wasm.Namespace, ctx context.Context, moduleName, funcName string, params ...uint64) ([]uint64, []wasm.ValueType, error) {
   801  	fn := ns.Module(moduleName).ExportedFunction(funcName)
   802  	results, err := fn.Call(ctx, params...)
   803  	return results, fn.Definition().ResultTypes(), err
   804  }