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