wa-lang.org/wazero@v1.0.2/internal/integration_test/engine/adhoc_test.go (about)

     1  package adhoc
     2  
     3  import (
     4  	"context"
     5  	_ "embed"
     6  	"math"
     7  	"strconv"
     8  	"testing"
     9  	"unsafe"
    10  
    11  	"wa-lang.org/wazero"
    12  	"wa-lang.org/wazero/api"
    13  	"wa-lang.org/wazero/internal/platform"
    14  	"wa-lang.org/wazero/internal/testing/require"
    15  	"wa-lang.org/wazero/internal/wasm"
    16  	"wa-lang.org/wazero/internal/wasm/binary"
    17  	"wa-lang.org/wazero/sys"
    18  )
    19  
    20  // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
    21  var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
    22  
    23  const (
    24  	i32, i64 = wasm.ValueTypeI32, wasm.ValueTypeI64
    25  )
    26  
    27  var memoryCapacityPages = uint32(2)
    28  
    29  var moduleConfig = wazero.NewModuleConfig()
    30  
    31  var tests = map[string]func(t *testing.T, r wazero.Runtime){
    32  	"huge stack":                                        testHugeStack,
    33  	"unreachable":                                       testUnreachable,
    34  	"recursive entry":                                   testRecursiveEntry,
    35  	"host func memory":                                  testHostFuncMemory,
    36  	"host function with context parameter":              testHostFunctionContextParameter,
    37  	"host function with nested context":                 testNestedGoContext,
    38  	"host function with numeric parameter":              testHostFunctionNumericParameter,
    39  	"close module with in-flight calls":                 testCloseInFlight,
    40  	"multiple instantiation from same source":           testMultipleInstantiation,
    41  	"exported function that grows memory":               testMemOps,
    42  	"import functions with reference type in signature": testReftypeImports,
    43  	"overflow integer addition":                         testOverflow,
    44  	"un-signed extend global":                           testGlobalExtend,
    45  }
    46  
    47  func TestEngineCompiler(t *testing.T) {
    48  	if !platform.CompilerSupported() {
    49  		t.Skip()
    50  	}
    51  	runAllTests(t, tests, wazero.NewRuntimeConfigCompiler())
    52  }
    53  
    54  func TestEngineInterpreter(t *testing.T) {
    55  	runAllTests(t, tests, wazero.NewRuntimeConfigInterpreter())
    56  }
    57  
    58  func runAllTests(t *testing.T, tests map[string]func(t *testing.T, r wazero.Runtime), config wazero.RuntimeConfig) {
    59  	for name, testf := range tests {
    60  		name := name   // pin
    61  		testf := testf // pin
    62  		t.Run(name, func(t *testing.T) {
    63  			t.Parallel()
    64  			testf(t, wazero.NewRuntimeWithConfig(testCtx, config))
    65  		})
    66  	}
    67  }
    68  
    69  var (
    70  	//go:embed testdata/unreachable.wasm
    71  	unreachableWasm []byte
    72  	//go:embed testdata/recursive.wasm
    73  	recursiveWasm []byte
    74  	//go:embed testdata/host_memory.wasm
    75  	hostMemoryWasm []byte
    76  	//go:embed testdata/hugestack.wasm
    77  	hugestackWasm []byte
    78  	//go:embed testdata/memory.wasm
    79  	memoryWasm []byte
    80  	//go:embed testdata/reftype_imports.wasm
    81  	reftypeImportsWasm []byte
    82  	//go:embed testdata/overflow.wasm
    83  	overflowWasm []byte
    84  	//go:embed testdata/global_extend.wasm
    85  	globalExtendWasm []byte
    86  )
    87  
    88  func testReftypeImports(t *testing.T, r wazero.Runtime) {
    89  	type dog struct {
    90  		name string
    91  	}
    92  
    93  	hostObj := &dog{name: "hello"}
    94  	host, err := r.NewHostModuleBuilder("host").
    95  		NewFunctionBuilder().
    96  		WithFunc(func(ctx context.Context, externrefFromRefNull uintptr) uintptr {
    97  			require.Zero(t, externrefFromRefNull)
    98  			return uintptr(unsafe.Pointer(hostObj))
    99  		}).
   100  		Export("externref").
   101  		Instantiate(testCtx, r)
   102  	require.NoError(t, err)
   103  	defer host.Close(testCtx)
   104  
   105  	module, err := r.InstantiateModuleFromBinary(testCtx, reftypeImportsWasm)
   106  	require.NoError(t, err)
   107  	defer module.Close(testCtx)
   108  
   109  	actual, err := module.ExportedFunction("get_externref_by_host").Call(testCtx)
   110  	require.NoError(t, err)
   111  
   112  	// Verifies that the returned raw uintptr is the same as the one for the host object.
   113  	require.Equal(t, uintptr(unsafe.Pointer(hostObj)), uintptr(actual[0]))
   114  }
   115  
   116  func testHugeStack(t *testing.T, r wazero.Runtime) {
   117  	module, err := r.InstantiateModuleFromBinary(testCtx, hugestackWasm)
   118  	require.NoError(t, err)
   119  	defer module.Close(testCtx)
   120  
   121  	fn := module.ExportedFunction("main")
   122  	require.NotNil(t, fn)
   123  
   124  	res, err := fn.Call(testCtx, 0, 0, 0, 0, 0, 0) // params ignored by wasm
   125  	require.NoError(t, err)
   126  
   127  	const resultNumInUint64 = 180
   128  	require.Equal(t, resultNumInUint64, len(res))
   129  	for i := uint64(1); i <= resultNumInUint64; i++ {
   130  		require.Equal(t, i, res[i-1])
   131  	}
   132  }
   133  
   134  // testOverflow ensures that adding one into the maximum integer results in the
   135  // minimum one. See #636.
   136  func testOverflow(t *testing.T, r wazero.Runtime) {
   137  	module, err := r.InstantiateModuleFromBinary(testCtx, overflowWasm)
   138  	require.NoError(t, err)
   139  	defer module.Close(testCtx)
   140  
   141  	for _, name := range []string{"i32", "i64"} {
   142  		i32 := module.ExportedFunction(name)
   143  		require.NotNil(t, i32)
   144  
   145  		res, err := i32.Call(testCtx)
   146  		require.NoError(t, err)
   147  
   148  		require.Equal(t, uint64(1), res[0])
   149  	}
   150  }
   151  
   152  // testGlobalExtend ensures that un-signed extension of i32 globals must be zero extended. See #656.
   153  func testGlobalExtend(t *testing.T, r wazero.Runtime) {
   154  	module, err := r.InstantiateModuleFromBinary(testCtx, globalExtendWasm)
   155  	require.NoError(t, err)
   156  	defer module.Close(testCtx)
   157  
   158  	extend := module.ExportedFunction("extend")
   159  	require.NotNil(t, extend)
   160  
   161  	res, err := extend.Call(testCtx)
   162  	require.NoError(t, err)
   163  
   164  	require.Equal(t, uint64(0xffff_ffff), res[0])
   165  }
   166  
   167  func testUnreachable(t *testing.T, r wazero.Runtime) {
   168  	callUnreachable := func() {
   169  		panic("panic in host function")
   170  	}
   171  
   172  	_, err := r.NewHostModuleBuilder("host").
   173  		NewFunctionBuilder().WithFunc(callUnreachable).Export("cause_unreachable").
   174  		Instantiate(testCtx, r)
   175  	require.NoError(t, err)
   176  
   177  	module, err := r.InstantiateModuleFromBinary(testCtx, unreachableWasm)
   178  	require.NoError(t, err)
   179  	defer module.Close(testCtx)
   180  
   181  	_, err = module.ExportedFunction("main").Call(testCtx)
   182  	exp := `panic in host function (recovered by wazero)
   183  wasm stack trace:
   184  	host.cause_unreachable()
   185  	.two()
   186  	.one()
   187  	.main()`
   188  	require.Equal(t, exp, err.Error())
   189  }
   190  
   191  func testRecursiveEntry(t *testing.T, r wazero.Runtime) {
   192  	hostfunc := func(ctx context.Context, mod api.Module) {
   193  		_, err := mod.ExportedFunction("called_by_host_func").Call(testCtx)
   194  		require.NoError(t, err)
   195  	}
   196  
   197  	_, err := r.NewHostModuleBuilder("env").
   198  		NewFunctionBuilder().WithFunc(hostfunc).Export("host_func").
   199  		Instantiate(testCtx, r)
   200  	require.NoError(t, err)
   201  
   202  	module, err := r.InstantiateModuleFromBinary(testCtx, recursiveWasm)
   203  	require.NoError(t, err)
   204  	defer module.Close(testCtx)
   205  
   206  	_, err = module.ExportedFunction("main").Call(testCtx, 1)
   207  	require.NoError(t, err)
   208  }
   209  
   210  // testHostFuncMemory ensures that host functions can see the callers' memory
   211  func testHostFuncMemory(t *testing.T, r wazero.Runtime) {
   212  	var memory *wasm.MemoryInstance
   213  	storeInt := func(ctx context.Context, m api.Module, offset uint32, val uint64) uint32 {
   214  		if !m.Memory().WriteUint64Le(ctx, offset, val) {
   215  			return 1
   216  		}
   217  		// sneak a reference to the memory, so we can check it later
   218  		memory = m.Memory().(*wasm.MemoryInstance)
   219  		return 0
   220  	}
   221  
   222  	host, err := r.NewHostModuleBuilder("").
   223  		NewFunctionBuilder().WithFunc(storeInt).Export("store_int").
   224  		Instantiate(testCtx, r)
   225  	require.NoError(t, err)
   226  	defer host.Close(testCtx)
   227  
   228  	module, err := r.InstantiateModuleFromBinary(testCtx, hostMemoryWasm)
   229  	require.NoError(t, err)
   230  	defer module.Close(testCtx)
   231  
   232  	// Call store_int and ensure it didn't return an error code.
   233  	fn := module.ExportedFunction("store_int")
   234  	results, err := fn.Call(testCtx, 1, math.MaxUint64)
   235  	require.NoError(t, err)
   236  	require.Equal(t, uint64(0), results[0])
   237  
   238  	// Since offset=1 and val=math.MaxUint64, we expect to have written exactly 8 bytes, with all bits set, at index 1.
   239  	require.Equal(t, []byte{0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0}, memory.Buffer[0:10])
   240  }
   241  
   242  // testNestedGoContext ensures context is updated when a function calls another.
   243  func testNestedGoContext(t *testing.T, r wazero.Runtime) {
   244  	nestedCtx := context.WithValue(context.Background(), struct{}{}, "nested")
   245  
   246  	importedName := t.Name() + "-imported"
   247  	importingName := t.Name() + "-importing"
   248  
   249  	var importing api.Module
   250  
   251  	imported, err := r.NewHostModuleBuilder(importedName).
   252  		NewFunctionBuilder().
   253  		WithFunc(func(ctx context.Context, p uint32) uint32 {
   254  			// We expect the initial context, testCtx, to be overwritten by "outer" when it called this.
   255  			require.Equal(t, nestedCtx, ctx)
   256  			return p + 1
   257  		}).
   258  		Export("inner").
   259  		NewFunctionBuilder().
   260  		WithFunc(func(ctx context.Context, module api.Module, p uint32) uint32 {
   261  			require.Equal(t, testCtx, ctx)
   262  			results, err := module.ExportedFunction("inner").Call(nestedCtx, uint64(p))
   263  			require.NoError(t, err)
   264  			return uint32(results[0]) + 1
   265  		}).
   266  		Export("outer").
   267  		Instantiate(testCtx, r)
   268  	require.NoError(t, err)
   269  	defer imported.Close(testCtx)
   270  
   271  	// Instantiate a module that uses Wasm code to call the host function.
   272  	importing, err = r.InstantiateModuleFromBinary(testCtx, callOuterInnerWasm(t, importedName, importingName))
   273  	require.NoError(t, err)
   274  	defer importing.Close(testCtx)
   275  
   276  	input := uint64(math.MaxUint32 - 2) // We expect two calls where each increment by one.
   277  	results, err := importing.ExportedFunction("call->outer").Call(testCtx, input)
   278  	require.NoError(t, err)
   279  	require.Equal(t, uint64(math.MaxUint32), results[0])
   280  }
   281  
   282  // testHostFunctionContextParameter ensures arg0 is optionally a context.
   283  func testHostFunctionContextParameter(t *testing.T, r wazero.Runtime) {
   284  	importedName := t.Name() + "-imported"
   285  	importingName := t.Name() + "-importing"
   286  
   287  	var importing api.Module
   288  	fns := map[string]interface{}{
   289  		"ctx": func(ctx context.Context, p uint32) uint32 {
   290  			require.Equal(t, testCtx, ctx)
   291  			return p + 1
   292  		},
   293  		"ctx mod": func(ctx context.Context, module api.Module, p uint32) uint32 {
   294  			require.Equal(t, importing, module)
   295  			return p + 1
   296  		},
   297  	}
   298  
   299  	for test := range fns {
   300  		t.Run(test, func(t *testing.T) {
   301  			imported, err := r.NewHostModuleBuilder(importedName).
   302  				NewFunctionBuilder().WithFunc(fns[test]).Export("return_input").
   303  				Instantiate(testCtx, r)
   304  			require.NoError(t, err)
   305  			defer imported.Close(testCtx)
   306  
   307  			// Instantiate a module that uses Wasm code to call the host function.
   308  			importing, err = r.InstantiateModuleFromBinary(testCtx,
   309  				callReturnImportWasm(t, importedName, importingName, i32))
   310  			require.NoError(t, err)
   311  			defer importing.Close(testCtx)
   312  
   313  			results, err := importing.ExportedFunction("call_return_input").Call(testCtx, math.MaxUint32-1)
   314  			require.NoError(t, err)
   315  			require.Equal(t, uint64(math.MaxUint32), results[0])
   316  		})
   317  	}
   318  }
   319  
   320  // testHostFunctionNumericParameter ensures numeric parameters aren't corrupted
   321  func testHostFunctionNumericParameter(t *testing.T, r wazero.Runtime) {
   322  	importedName := t.Name() + "-imported"
   323  	importingName := t.Name() + "-importing"
   324  
   325  	fns := map[string]interface{}{
   326  		"i32": func(ctx context.Context, p uint32) uint32 {
   327  			return p + 1
   328  		},
   329  		"i64": func(ctx context.Context, p uint64) uint64 {
   330  			return p + 1
   331  		},
   332  		"f32": func(ctx context.Context, p float32) float32 {
   333  			return p + 1
   334  		},
   335  		"f64": func(ctx context.Context, p float64) float64 {
   336  			return p + 1
   337  		},
   338  	}
   339  
   340  	for _, test := range []struct {
   341  		name            string
   342  		vt              wasm.ValueType
   343  		input, expected uint64
   344  	}{
   345  		{
   346  			name:     "i32",
   347  			vt:       i32,
   348  			input:    math.MaxUint32 - 1,
   349  			expected: math.MaxUint32,
   350  		},
   351  		{
   352  			name:     "i64",
   353  			vt:       i64,
   354  			input:    math.MaxUint64 - 1,
   355  			expected: math.MaxUint64,
   356  		},
   357  		{
   358  			name:     "f32",
   359  			vt:       wasm.ValueTypeF32,
   360  			input:    api.EncodeF32(math.MaxFloat32 - 1),
   361  			expected: api.EncodeF32(math.MaxFloat32),
   362  		},
   363  		{
   364  			name:     "f64",
   365  			vt:       wasm.ValueTypeF64,
   366  			input:    api.EncodeF64(math.MaxFloat64 - 1),
   367  			expected: api.EncodeF64(math.MaxFloat64),
   368  		},
   369  	} {
   370  		t.Run(test.name, func(t *testing.T) {
   371  			imported, err := r.NewHostModuleBuilder(importedName).
   372  				NewFunctionBuilder().WithFunc(fns[test.name]).Export("return_input").
   373  				Instantiate(testCtx, r)
   374  			require.NoError(t, err)
   375  			defer imported.Close(testCtx)
   376  
   377  			// Instantiate a module that uses Wasm code to call the host function.
   378  			importing, err := r.InstantiateModuleFromBinary(testCtx,
   379  				callReturnImportWasm(t, importedName, importingName, test.vt))
   380  			require.NoError(t, err)
   381  			defer importing.Close(testCtx)
   382  
   383  			results, err := importing.ExportedFunction("call_return_input").Call(testCtx, test.input)
   384  			require.NoError(t, err)
   385  			require.Equal(t, test.expected, results[0])
   386  		})
   387  	}
   388  }
   389  
   390  func callReturnImportWasm(t *testing.T, importedModule, importingModule string, vt wasm.ValueType) []byte {
   391  	// test an imported function by re-exporting it
   392  	module := &wasm.Module{
   393  		TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{vt}, Results: []wasm.ValueType{vt}}},
   394  		// (import "%[2]s" "return_input" (func $return_input (param i32) (result i32)))
   395  		ImportSection: []*wasm.Import{
   396  			{Module: importedModule, Name: "return_input", Type: wasm.ExternTypeFunc, DescFunc: 0},
   397  		},
   398  		FunctionSection: []wasm.Index{0},
   399  		ExportSection: []*wasm.Export{
   400  			// (export "return_input" (func $return_input))
   401  			{Name: "return_input", Type: wasm.ExternTypeFunc, Index: 0},
   402  			// (export "call_return_input" (func $call_return_input))
   403  			{Name: "call_return_input", Type: wasm.ExternTypeFunc, Index: 1},
   404  		},
   405  		// (func $call_return_input (param i32) (result i32) local.get 0 call $return_input)
   406  		CodeSection: []*wasm.Code{
   407  			{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 0, wasm.OpcodeEnd}},
   408  		},
   409  		NameSection: &wasm.NameSection{
   410  			ModuleName: importingModule,
   411  			FunctionNames: wasm.NameMap{
   412  				{Index: 0, Name: "return_input"},
   413  				{Index: 1, Name: "call_return_input"},
   414  			},
   415  		},
   416  	}
   417  	require.NoError(t, module.Validate(api.CoreFeaturesV2))
   418  	return binary.EncodeModule(module)
   419  }
   420  
   421  func callOuterInnerWasm(t *testing.T, importedModule, importingModule string) []byte {
   422  	module := &wasm.Module{
   423  		TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}}},
   424  		// (import "%[2]s" "outer" (func $outer (param i32) (result i32)))
   425  		// (import "%[2]s" "inner" (func $inner (param i32) (result i32)))
   426  		ImportSection: []*wasm.Import{
   427  			{Module: importedModule, Name: "outer", Type: wasm.ExternTypeFunc, DescFunc: 0},
   428  			{Module: importedModule, Name: "inner", Type: wasm.ExternTypeFunc, DescFunc: 0},
   429  		},
   430  		FunctionSection: []wasm.Index{0, 0},
   431  		ExportSection: []*wasm.Export{
   432  			// (export "call->outer" (func $call_outer))
   433  			{Name: "call->outer", Type: wasm.ExternTypeFunc, Index: 2},
   434  			// 	(export "inner" (func $call_inner))
   435  			{Name: "inner", Type: wasm.ExternTypeFunc, Index: 3},
   436  		},
   437  		CodeSection: []*wasm.Code{
   438  			// (func $call_outer (param i32) (result i32) local.get 0 call $outer)
   439  			{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 0, wasm.OpcodeEnd}},
   440  			// (func $call_inner (param i32) (result i32) local.get 0 call $inner)
   441  			{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 1, wasm.OpcodeEnd}},
   442  		},
   443  		NameSection: &wasm.NameSection{
   444  			ModuleName: importingModule,
   445  			FunctionNames: wasm.NameMap{
   446  				{Index: 0, Name: "outer"},
   447  				{Index: 1, Name: "inner"},
   448  				{Index: 2, Name: "call_outer"},
   449  				{Index: 3, Name: "call_inner"},
   450  			},
   451  		},
   452  	}
   453  	require.NoError(t, module.Validate(api.CoreFeaturesV2))
   454  	return binary.EncodeModule(module)
   455  }
   456  
   457  func testCloseInFlight(t *testing.T, r wazero.Runtime) {
   458  	tests := []struct {
   459  		name, function                        string
   460  		closeImporting, closeImported         uint32
   461  		closeImportingCode, closeImportedCode bool
   462  	}{
   463  		{ // e.g. WASI proc_exit or AssemblyScript abort handler.
   464  			name:           "importing",
   465  			function:       "call_return_input",
   466  			closeImporting: 1,
   467  		},
   468  		// TODO: A module that re-exports a function (ex "return_input") can call it after it is closed!
   469  		{ // e.g. A function that stops the runtime.
   470  			name:           "both",
   471  			function:       "call_return_input",
   472  			closeImporting: 1,
   473  			closeImported:  2,
   474  		},
   475  		{ // e.g. WASI proc_exit or AssemblyScript abort handler.
   476  			name:              "importing",
   477  			function:          "call_return_input",
   478  			closeImporting:    1,
   479  			closeImportedCode: true,
   480  		},
   481  		{ // e.g. WASI proc_exit or AssemblyScript abort handler.
   482  			name:               "importing",
   483  			function:           "call_return_input",
   484  			closeImporting:     1,
   485  			closeImportedCode:  true,
   486  			closeImportingCode: true,
   487  		},
   488  		// TODO: A module that re-exports a function (ex "return_input") can call it after it is closed!
   489  		{ // e.g. A function that stops the runtime.
   490  			name:               "both",
   491  			function:           "call_return_input",
   492  			closeImporting:     1,
   493  			closeImported:      2,
   494  			closeImportingCode: true,
   495  		},
   496  	}
   497  	for _, tt := range tests {
   498  		tc := tt
   499  
   500  		t.Run(tc.name, func(t *testing.T) {
   501  			var importingCode, importedCode wazero.CompiledModule
   502  			var imported, importing api.Module
   503  			var err error
   504  			closeAndReturn := func(ctx context.Context, x uint32) uint32 {
   505  				if tc.closeImporting != 0 {
   506  					require.NoError(t, importing.CloseWithExitCode(ctx, tc.closeImporting))
   507  				}
   508  				if tc.closeImported != 0 {
   509  					require.NoError(t, imported.CloseWithExitCode(ctx, tc.closeImported))
   510  				}
   511  				if tc.closeImportedCode {
   512  					importedCode.Close(testCtx)
   513  				}
   514  				if tc.closeImportingCode {
   515  					importingCode.Close(testCtx)
   516  				}
   517  				return x
   518  			}
   519  
   520  			// Create the host module, which exports the function that closes the importing module.
   521  			importedCode, err = r.NewHostModuleBuilder(t.Name() + "-imported").
   522  				NewFunctionBuilder().WithFunc(closeAndReturn).Export("return_input").
   523  				Compile(testCtx)
   524  			require.NoError(t, err)
   525  
   526  			imported, err = r.InstantiateModule(testCtx, importedCode, moduleConfig)
   527  			require.NoError(t, err)
   528  			defer imported.Close(testCtx)
   529  
   530  			// Import that module.
   531  			binary := callReturnImportWasm(t, imported.Name(), t.Name()+"-importing", i32)
   532  			importingCode, err = r.CompileModule(testCtx, binary)
   533  			require.NoError(t, err)
   534  
   535  			importing, err = r.InstantiateModule(testCtx, importingCode, moduleConfig)
   536  			require.NoError(t, err)
   537  			defer importing.Close(testCtx)
   538  
   539  			var expectedErr error
   540  			if tc.closeImported != 0 && tc.closeImporting != 0 {
   541  				// When both modules are closed, importing is the better one to choose in the error message.
   542  				expectedErr = sys.NewExitError(importing.Name(), tc.closeImporting)
   543  			} else if tc.closeImported != 0 {
   544  				expectedErr = sys.NewExitError(imported.Name(), tc.closeImported)
   545  			} else if tc.closeImporting != 0 {
   546  				expectedErr = sys.NewExitError(importing.Name(), tc.closeImporting)
   547  			} else {
   548  				t.Fatal("invalid test case")
   549  			}
   550  
   551  			// Functions that return after being closed should have an exit error.
   552  			_, err = importing.ExportedFunction(tc.function).Call(testCtx, 5)
   553  			require.Equal(t, expectedErr, err)
   554  		})
   555  	}
   556  }
   557  
   558  func testMemOps(t *testing.T, r wazero.Runtime) {
   559  	// Instantiate a module that manages its memory
   560  	mod, err := r.InstantiateModuleFromBinary(testCtx, memoryWasm)
   561  	require.NoError(t, err)
   562  	defer mod.Close(testCtx)
   563  
   564  	// Check the export worked
   565  	require.Equal(t, mod.Memory(), mod.ExportedMemory("memory"))
   566  	memory := mod.Memory()
   567  
   568  	sizeFn, storeFn, growFn := mod.ExportedFunction("size"), mod.ExportedFunction("store"), mod.ExportedFunction("grow")
   569  
   570  	// Check the size command worked
   571  	results, err := sizeFn.Call(testCtx)
   572  	require.NoError(t, err)
   573  	require.Zero(t, results[0])
   574  	require.Zero(t, memory.Size(testCtx))
   575  
   576  	// Any offset should be out of bounds error even when it is less than memory capacity(=memoryCapacityPages).
   577  	_, err = storeFn.Call(testCtx, wasm.MemoryPagesToBytesNum(memoryCapacityPages)-8)
   578  	require.Error(t, err) // Out of bounds error.
   579  
   580  	// Try to grow the memory by one page
   581  	results, err = growFn.Call(testCtx, 1)
   582  	require.NoError(t, err)
   583  	require.Zero(t, results[0]) // should succeed and return the old size in pages.
   584  
   585  	// Any offset larger than the current size should be out of bounds error even when it is less than memory capacity.
   586  	_, err = storeFn.Call(testCtx, wasm.MemoryPagesToBytesNum(memoryCapacityPages)-8)
   587  	require.Error(t, err) // Out of bounds error.
   588  
   589  	// Check the size command works!
   590  	results, err = sizeFn.Call(testCtx)
   591  	require.NoError(t, err)
   592  	require.Equal(t, uint64(1), results[0])               // 1 page
   593  	require.Equal(t, uint32(65536), memory.Size(testCtx)) // 64KB
   594  
   595  	// Grow again so that the memory size matches memory capacity.
   596  	results, err = growFn.Call(testCtx, 1)
   597  	require.NoError(t, err)
   598  	require.Equal(t, uint64(1), results[0])
   599  
   600  	// Verify the size matches cap.
   601  	results, err = sizeFn.Call(testCtx)
   602  	require.NoError(t, err)
   603  	require.Equal(t, uint64(memoryCapacityPages), results[0])
   604  
   605  	// Now the store instruction at the memory capcity bound should succeed.
   606  	_, err = storeFn.Call(testCtx, wasm.MemoryPagesToBytesNum(memoryCapacityPages)-8) // i64.store needs 8 bytes from offset.
   607  	require.NoError(t, err)
   608  }
   609  
   610  func testMultipleInstantiation(t *testing.T, r wazero.Runtime) {
   611  	bin := binary.EncodeModule(&wasm.Module{
   612  		TypeSection:     []*wasm.FunctionType{{}},
   613  		FunctionSection: []wasm.Index{0},
   614  		MemorySection:   &wasm.Memory{Min: 1, Cap: 1, Max: 1, IsMaxEncoded: true},
   615  		CodeSection: []*wasm.Code{{
   616  			Body: []byte{
   617  				wasm.OpcodeI32Const, 1, // i32.const 1    ;; memory offset
   618  				wasm.OpcodeI64Const, 0xe8, 0x7, // i64.const 1000 ;; expected value
   619  				wasm.OpcodeI64Store, 0x3, 0x0, // i64.store
   620  				wasm.OpcodeEnd,
   621  			},
   622  		}},
   623  		ExportSection: []*wasm.Export{{Name: "store"}},
   624  	})
   625  	compiled, err := r.CompileModule(testCtx, bin)
   626  	require.NoError(t, err)
   627  	defer compiled.Close(testCtx)
   628  
   629  	// Instantiate multiple modules with the same source (*CompiledModule).
   630  	for i := 0; i < 100; i++ {
   631  		module, err := r.InstantiateModule(testCtx, compiled, wazero.NewModuleConfig().WithName(strconv.Itoa(i)))
   632  		require.NoError(t, err)
   633  		defer module.Close(testCtx)
   634  
   635  		// Ensure that compilation cache doesn't cause race on memory instance.
   636  		before, ok := module.Memory().ReadUint64Le(testCtx, 1)
   637  		require.True(t, ok)
   638  		// Value must be zero as the memory must not be affected by the previously instantiated modules.
   639  		require.Zero(t, before)
   640  
   641  		f := module.ExportedFunction("store")
   642  		require.NotNil(t, f)
   643  
   644  		_, err = f.Call(testCtx)
   645  		require.NoError(t, err)
   646  
   647  		// After the call, the value must be set properly.
   648  		after, ok := module.Memory().ReadUint64Le(testCtx, 1)
   649  		require.True(t, ok)
   650  		require.Equal(t, uint64(1000), after)
   651  	}
   652  }