wa-lang.org/wazero@v1.0.2/internal/testing/enginetest/enginetest.go (about)

     1  // Package enginetest contains tests common to any wasm.Engine implementation. Defining these as top-level
     2  // functions is less burden than copy/pasting the implementations, while still allowing test caching to operate.
     3  //
     4  // In simplest case, dispatch:
     5  //
     6  //	func TestModuleEngine_Call(t *testing.T) {
     7  //		enginetest.RunTestModuleEngine_Call(t, NewEngine)
     8  //	}
     9  //
    10  // Some tests using the Compiler Engine may need to guard as they use compiled features:
    11  //
    12  //	func TestModuleEngine_Call(t *testing.T) {
    13  //		requireSupportedOSArch(t)
    14  //		enginetest.RunTestModuleEngine_Call(t, NewEngine)
    15  //	}
    16  //
    17  // Note: These tests intentionally avoid using wasm.Store as it is important to know both the dependencies and
    18  // the capabilities at the wasm.Engine abstraction.
    19  package enginetest
    20  
    21  import (
    22  	"context"
    23  	"errors"
    24  	"math"
    25  	"testing"
    26  
    27  	"wa-lang.org/wazero/api"
    28  	"wa-lang.org/wazero/experimental"
    29  	"wa-lang.org/wazero/internal/testing/require"
    30  	"wa-lang.org/wazero/internal/u64"
    31  	"wa-lang.org/wazero/internal/wasm"
    32  	"wa-lang.org/wazero/internal/wasmruntime"
    33  )
    34  
    35  const (
    36  	i32, i64 = wasm.ValueTypeI32, wasm.ValueTypeI64
    37  )
    38  
    39  var (
    40  	// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
    41  	testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
    42  	// v_v is a nullary function type (void -> void)
    43  	v_v = &wasm.FunctionType{}
    44  )
    45  
    46  type EngineTester interface {
    47  	// IsCompiler returns true if this engine is a compiler.
    48  	IsCompiler() bool
    49  
    50  	NewEngine(enabledFeatures api.CoreFeatures) wasm.Engine
    51  
    52  	ListenerFactory() experimental.FunctionListenerFactory
    53  
    54  	// CompiledFunctionPointerValue returns the opaque compiledFunction's pointer for the `funcIndex`.
    55  	CompiledFunctionPointerValue(tme wasm.ModuleEngine, funcIndex wasm.Index) uint64
    56  }
    57  
    58  func RunTestEngine_NewModuleEngine(t *testing.T, et EngineTester) {
    59  	e := et.NewEngine(api.CoreFeaturesV1)
    60  
    61  	t.Run("error before instantiation", func(t *testing.T) {
    62  		_, err := e.NewModuleEngine("mymod", &wasm.Module{}, nil, nil)
    63  		require.EqualError(t, err, "source module for mymod must be compiled before instantiation")
    64  	})
    65  
    66  	t.Run("sets module name", func(t *testing.T) {
    67  		m := &wasm.Module{}
    68  		err := e.CompileModule(testCtx, m, nil)
    69  		require.NoError(t, err)
    70  		me, err := e.NewModuleEngine(t.Name(), m, nil, nil)
    71  		require.NoError(t, err)
    72  		require.Equal(t, t.Name(), me.Name())
    73  	})
    74  }
    75  
    76  func RunTestEngine_InitializeFuncrefGlobals(t *testing.T, et EngineTester) {
    77  	e := et.NewEngine(api.CoreFeaturesV2)
    78  
    79  	i64 := i64
    80  	m := &wasm.Module{
    81  		TypeSection:     []*wasm.FunctionType{{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64}}},
    82  		FunctionSection: []wasm.Index{0, 0, 0},
    83  		CodeSection: []*wasm.Code{
    84  			{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeEnd}, LocalTypes: []wasm.ValueType{i64}},
    85  			{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeEnd}, LocalTypes: []wasm.ValueType{i64}},
    86  			{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeEnd}, LocalTypes: []wasm.ValueType{i64}},
    87  		},
    88  	}
    89  	m.BuildFunctionDefinitions()
    90  	err := e.CompileModule(testCtx, m, nil)
    91  	require.NoError(t, err)
    92  
    93  	// To use the function, we first need to add it to a module.
    94  	instance := &wasm.ModuleInstance{Name: t.Name(), TypeIDs: []wasm.FunctionTypeID{0}}
    95  	fns := instance.BuildFunctions(m)
    96  	me, err := e.NewModuleEngine(t.Name(), m, nil, fns)
    97  	require.NoError(t, err)
    98  
    99  	nullRefVal := wasm.GlobalInstanceNullFuncRefValue
   100  	globals := []*wasm.GlobalInstance{
   101  		{Val: 10, Type: &wasm.GlobalType{ValType: i32}},
   102  		{Val: uint64(nullRefVal), Type: &wasm.GlobalType{ValType: wasm.ValueTypeFuncref}},
   103  		{Val: uint64(2), Type: &wasm.GlobalType{ValType: wasm.ValueTypeFuncref}},
   104  		{Val: uint64(1), Type: &wasm.GlobalType{ValType: wasm.ValueTypeFuncref}},
   105  		{Val: uint64(0), Type: &wasm.GlobalType{ValType: wasm.ValueTypeFuncref}},
   106  	}
   107  	me.InitializeFuncrefGlobals(globals)
   108  
   109  	// Non-funcref values must be intact.
   110  	require.Equal(t, uint64(10), globals[0].Val)
   111  	// The second global had wasm.GlobalInstanceNullFuncRefValue, so that value must be translated as null reference (uint64(0)).
   112  	require.Zero(t, globals[1].Val)
   113  	// Non GlobalInstanceNullFuncRefValue valued globals must result in having the valid compiled function's pointers.
   114  	require.Equal(t, et.CompiledFunctionPointerValue(me, 2), globals[2].Val)
   115  	require.Equal(t, et.CompiledFunctionPointerValue(me, 1), globals[3].Val)
   116  	require.Equal(t, et.CompiledFunctionPointerValue(me, 0), globals[4].Val)
   117  }
   118  
   119  func RunTestModuleEngine_Call(t *testing.T, et EngineTester) {
   120  	e := et.NewEngine(api.CoreFeaturesV2)
   121  
   122  	// Define a basic function which defines two parameters and two results.
   123  	// This is used to test results when incorrect arity is used.
   124  	m := &wasm.Module{
   125  		TypeSection: []*wasm.FunctionType{
   126  			{
   127  				Params:            []wasm.ValueType{i64, i64},
   128  				Results:           []wasm.ValueType{i64, i64},
   129  				ParamNumInUint64:  2,
   130  				ResultNumInUint64: 2,
   131  			},
   132  		},
   133  		FunctionSection: []wasm.Index{0},
   134  		CodeSection: []*wasm.Code{
   135  			{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeLocalGet, 1, wasm.OpcodeEnd}},
   136  		},
   137  	}
   138  
   139  	m.BuildFunctionDefinitions()
   140  	listeners := buildListeners(et.ListenerFactory(), m)
   141  	err := e.CompileModule(testCtx, m, listeners)
   142  	require.NoError(t, err)
   143  
   144  	// To use the function, we first need to add it to a module.
   145  	module := &wasm.ModuleInstance{Name: t.Name(), TypeIDs: []wasm.FunctionTypeID{0}}
   146  	module.Functions = module.BuildFunctions(m)
   147  
   148  	// Compile the module
   149  	me, err := e.NewModuleEngine(module.Name, m, nil, module.Functions)
   150  	require.NoError(t, err)
   151  	linkModuleToEngine(module, me)
   152  
   153  	// Ensure the base case doesn't fail: A single parameter should work as that matches the function signature.
   154  	fn := module.Functions[0]
   155  
   156  	ce, err := me.NewCallEngine(module.CallCtx, fn)
   157  	require.NoError(t, err)
   158  
   159  	results, err := ce.Call(testCtx, module.CallCtx, []uint64{1, 2})
   160  	require.NoError(t, err)
   161  	require.Equal(t, []uint64{1, 2}, results)
   162  
   163  	t.Run("errs when not enough parameters", func(t *testing.T) {
   164  		ce, err := me.NewCallEngine(module.CallCtx, fn)
   165  		require.NoError(t, err)
   166  
   167  		_, err = ce.Call(testCtx, module.CallCtx, nil)
   168  		require.EqualError(t, err, "expected 2 params, but passed 0")
   169  	})
   170  
   171  	t.Run("errs when too many parameters", func(t *testing.T) {
   172  		ce, err := me.NewCallEngine(module.CallCtx, fn)
   173  		require.NoError(t, err)
   174  
   175  		_, err = ce.Call(testCtx, module.CallCtx, []uint64{1, 2, 3})
   176  		require.EqualError(t, err, "expected 2 params, but passed 3")
   177  	})
   178  }
   179  
   180  func RunTestModuleEngine_LookupFunction(t *testing.T, et EngineTester) {
   181  	e := et.NewEngine(api.CoreFeaturesV1)
   182  
   183  	mod := &wasm.Module{
   184  		TypeSection:     []*wasm.FunctionType{{}, {Params: []wasm.ValueType{wasm.ValueTypeV128}}},
   185  		FunctionSection: []wasm.Index{0, 0, 0},
   186  		CodeSection: []*wasm.Code{
   187  			{
   188  				Body: []byte{wasm.OpcodeEnd},
   189  			}, {Body: []byte{wasm.OpcodeEnd}}, {Body: []byte{wasm.OpcodeEnd}},
   190  		},
   191  	}
   192  
   193  	mod.BuildFunctionDefinitions()
   194  	err := e.CompileModule(testCtx, mod, nil)
   195  	require.NoError(t, err)
   196  	m := &wasm.ModuleInstance{TypeIDs: []wasm.FunctionTypeID{0, 1}}
   197  	m.Tables = []*wasm.TableInstance{
   198  		{Min: 2, References: make([]wasm.Reference, 2), Type: wasm.RefTypeFuncref},
   199  		{Min: 2, References: make([]wasm.Reference, 2), Type: wasm.RefTypeExternref},
   200  		{Min: 10, References: make([]wasm.Reference, 10), Type: wasm.RefTypeFuncref},
   201  	}
   202  	m.Functions = m.BuildFunctions(mod)
   203  
   204  	me, err := e.NewModuleEngine(m.Name, mod, nil, m.Functions)
   205  	require.NoError(t, err)
   206  	linkModuleToEngine(m, me)
   207  
   208  	t.Run("null reference", func(t *testing.T) {
   209  		_, err := me.LookupFunction(m.Tables[0], m.TypeIDs[0], 0) // offset 0 is not initialized yet.
   210  		require.Equal(t, wasmruntime.ErrRuntimeInvalidTableAccess, err)
   211  		_, err = me.LookupFunction(m.Tables[0], m.TypeIDs[0], 1) // offset 1 is not initialized yet.
   212  		require.Equal(t, wasmruntime.ErrRuntimeInvalidTableAccess, err)
   213  	})
   214  
   215  	m.Tables[0].References[0] = me.FunctionInstanceReference(2)
   216  	m.Tables[0].References[1] = me.FunctionInstanceReference(0)
   217  
   218  	t.Run("initialized", func(t *testing.T) {
   219  		index, err := me.LookupFunction(m.Tables[0], m.TypeIDs[0], 0) // offset 0 is now initialized.
   220  		require.NoError(t, err)
   221  		require.Equal(t, wasm.Index(2), index)
   222  		index, err = me.LookupFunction(m.Tables[0], m.TypeIDs[0], 1) // offset 1 is now initialized.
   223  		require.NoError(t, err)
   224  		require.Equal(t, wasm.Index(0), index)
   225  	})
   226  
   227  	t.Run("out of range", func(t *testing.T) {
   228  		_, err := me.LookupFunction(m.Tables[0], m.TypeIDs[0], 100 /* out of range */)
   229  		require.Equal(t, wasmruntime.ErrRuntimeInvalidTableAccess, err)
   230  	})
   231  
   232  	t.Run("access to externref table", func(t *testing.T) {
   233  		_, err := me.LookupFunction(m.Tables[1], /* table[1] has externref type. */
   234  			m.TypeIDs[0], 0)
   235  		require.Equal(t, wasmruntime.ErrRuntimeInvalidTableAccess, err)
   236  	})
   237  
   238  	t.Run("access to externref table", func(t *testing.T) {
   239  		_, err := me.LookupFunction(m.Tables[0], /* type mismatch */
   240  			m.TypeIDs[1], 0)
   241  		require.Equal(t, wasmruntime.ErrRuntimeIndirectCallTypeMismatch, err)
   242  	})
   243  
   244  	m.Tables[2].References[0] = me.FunctionInstanceReference(1)
   245  	m.Tables[2].References[5] = me.FunctionInstanceReference(2)
   246  	t.Run("initialized - tables[2]", func(t *testing.T) {
   247  		index, err := me.LookupFunction(m.Tables[2], m.TypeIDs[0], 0)
   248  		require.NoError(t, err)
   249  		require.Equal(t, wasm.Index(1), index)
   250  		index, err = me.LookupFunction(m.Tables[2], m.TypeIDs[0], 5)
   251  		require.NoError(t, err)
   252  		require.Equal(t, wasm.Index(2), index)
   253  	})
   254  }
   255  
   256  func runTestModuleEngine_Call_HostFn_Mem(t *testing.T, et EngineTester, readMem *wasm.Code) {
   257  	e := et.NewEngine(api.CoreFeaturesV1)
   258  	_, importing, done := setupCallMemTests(t, e, readMem, et.ListenerFactory())
   259  	defer done()
   260  
   261  	importingMemoryVal := uint64(6)
   262  	importing.Memory = &wasm.MemoryInstance{Buffer: u64.LeBytes(importingMemoryVal), Min: 1, Cap: 1, Max: 1}
   263  
   264  	tests := []struct {
   265  		name     string
   266  		fn       *wasm.FunctionInstance
   267  		expected uint64
   268  	}{
   269  		{
   270  			name:     callImportReadMemName,
   271  			fn:       importing.Exports[callImportReadMemName].Function,
   272  			expected: importingMemoryVal,
   273  		},
   274  		{
   275  			name:     callImportCallReadMemName,
   276  			fn:       importing.Exports[callImportCallReadMemName].Function,
   277  			expected: importingMemoryVal,
   278  		},
   279  	}
   280  	for _, tt := range tests {
   281  		tc := tt
   282  
   283  		t.Run(tc.name, func(t *testing.T) {
   284  			ce, err := tc.fn.Module.Engine.NewCallEngine(tc.fn.Module.CallCtx, tc.fn)
   285  			require.NoError(t, err)
   286  
   287  			results, err := ce.Call(testCtx, importing.CallCtx, nil)
   288  			require.NoError(t, err)
   289  			require.Equal(t, tc.expected, results[0])
   290  		})
   291  	}
   292  }
   293  
   294  func RunTestModuleEngine_Call_HostFn(t *testing.T, et EngineTester) {
   295  	t.Run("wasm", func(t *testing.T) {
   296  		runTestModuleEngine_Call_HostFn(t, et, hostDivByWasm)
   297  		runTestModuleEngine_Call_HostFn_Mem(t, et, hostReadMemWasm)
   298  	})
   299  	t.Run("go", func(t *testing.T) {
   300  		runTestModuleEngine_Call_HostFn(t, et, hostDivByGo)
   301  		runTestModuleEngine_Call_HostFn_Mem(t, et, hostReadMemGo)
   302  	})
   303  }
   304  
   305  func runTestModuleEngine_Call_HostFn(t *testing.T, et EngineTester, hostDivBy *wasm.Code) {
   306  	e := et.NewEngine(api.CoreFeaturesV1)
   307  
   308  	_, imported, importing, done := setupCallTests(t, e, hostDivBy, et.ListenerFactory())
   309  	defer done()
   310  
   311  	// Ensure the base case doesn't fail: A single parameter should work as that matches the function signature.
   312  	tests := []struct {
   313  		name   string
   314  		module *wasm.CallContext
   315  		fn     *wasm.FunctionInstance
   316  	}{
   317  		{
   318  			name:   divByWasmName,
   319  			module: imported.CallCtx,
   320  			fn:     imported.Exports[divByWasmName].Function,
   321  		},
   322  		{
   323  			name:   callDivByGoName,
   324  			module: imported.CallCtx,
   325  			fn:     imported.Exports[callDivByGoName].Function,
   326  		},
   327  		{
   328  			name:   callImportCallDivByGoName,
   329  			module: importing.CallCtx,
   330  			fn:     importing.Exports[callImportCallDivByGoName].Function,
   331  		},
   332  	}
   333  	for _, tt := range tests {
   334  		tc := tt
   335  
   336  		t.Run(tc.name, func(t *testing.T) {
   337  			m := tc.module
   338  			f := tc.fn
   339  
   340  			ce, err := f.Module.Engine.NewCallEngine(m, f)
   341  			require.NoError(t, err)
   342  
   343  			results, err := ce.Call(testCtx, m, []uint64{1})
   344  			require.NoError(t, err)
   345  			require.Equal(t, uint64(1), results[0])
   346  
   347  			results2, err := ce.Call(testCtx, m, []uint64{1})
   348  			require.NoError(t, err)
   349  			require.Equal(t, results, results2)
   350  
   351  			// Ensure the result slices are unique
   352  			results[0] = 255
   353  			require.Equal(t, uint64(1), results2[0])
   354  		})
   355  	}
   356  }
   357  
   358  func RunTestModuleEngine_Call_Errors(t *testing.T, et EngineTester) {
   359  	e := et.NewEngine(api.CoreFeaturesV1)
   360  
   361  	_, imported, importing, done := setupCallTests(t, e, hostDivByGo, et.ListenerFactory())
   362  	defer done()
   363  
   364  	tests := []struct {
   365  		name        string
   366  		module      *wasm.CallContext
   367  		fn          *wasm.FunctionInstance
   368  		input       []uint64
   369  		expectedErr string
   370  	}{
   371  		{
   372  			name:        "wasm function not enough parameters",
   373  			input:       []uint64{},
   374  			module:      imported.CallCtx,
   375  			fn:          imported.Exports[divByWasmName].Function,
   376  			expectedErr: `expected 1 params, but passed 0`,
   377  		},
   378  		{
   379  			name:        "wasm function too many parameters",
   380  			input:       []uint64{1, 2},
   381  			module:      imported.CallCtx,
   382  			fn:          imported.Exports[divByWasmName].Function,
   383  			expectedErr: `expected 1 params, but passed 2`,
   384  		},
   385  		{
   386  			name:   "wasm function panics with wasmruntime.Error",
   387  			input:  []uint64{0},
   388  			module: imported.CallCtx,
   389  			fn:     imported.Exports[divByWasmName].Function,
   390  			expectedErr: `wasm error: integer divide by zero
   391  wasm stack trace:
   392  	imported.div_by.wasm(i32) i32`,
   393  		},
   394  		{
   395  			name:   "wasm calls host function that panics",
   396  			input:  []uint64{math.MaxUint32},
   397  			module: imported.CallCtx,
   398  			fn:     imported.Exports[callDivByGoName].Function,
   399  			expectedErr: `host-function panic (recovered by wazero)
   400  wasm stack trace:
   401  	host.div_by.go(i32) i32
   402  	imported.call->div_by.go(i32) i32`,
   403  		},
   404  		{
   405  			name:   "wasm calls imported wasm that calls host function panics with runtime.Error",
   406  			input:  []uint64{0},
   407  			module: importing.CallCtx,
   408  			fn:     importing.Exports[callImportCallDivByGoName].Function,
   409  			expectedErr: `runtime error: integer divide by zero (recovered by wazero)
   410  wasm stack trace:
   411  	host.div_by.go(i32) i32
   412  	imported.call->div_by.go(i32) i32
   413  	importing.call_import->call->div_by.go(i32) i32`,
   414  		},
   415  		{
   416  			name:   "wasm calls imported wasm that calls host function that panics",
   417  			input:  []uint64{math.MaxUint32},
   418  			module: importing.CallCtx,
   419  			fn:     importing.Exports[callImportCallDivByGoName].Function,
   420  			expectedErr: `host-function panic (recovered by wazero)
   421  wasm stack trace:
   422  	host.div_by.go(i32) i32
   423  	imported.call->div_by.go(i32) i32
   424  	importing.call_import->call->div_by.go(i32) i32`,
   425  		},
   426  		{
   427  			name:   "wasm calls imported wasm calls host function panics with runtime.Error",
   428  			input:  []uint64{0},
   429  			module: importing.CallCtx,
   430  			fn:     importing.Exports[callImportCallDivByGoName].Function,
   431  			expectedErr: `runtime error: integer divide by zero (recovered by wazero)
   432  wasm stack trace:
   433  	host.div_by.go(i32) i32
   434  	imported.call->div_by.go(i32) i32
   435  	importing.call_import->call->div_by.go(i32) i32`,
   436  		},
   437  	}
   438  	for _, tt := range tests {
   439  		tc := tt
   440  		t.Run(tc.name, func(t *testing.T) {
   441  			m := tc.module
   442  			f := tc.fn
   443  
   444  			ce, err := f.Module.Engine.NewCallEngine(m, f)
   445  			require.NoError(t, err)
   446  
   447  			_, err = ce.Call(testCtx, m, tc.input)
   448  			require.EqualError(t, err, tc.expectedErr)
   449  
   450  			// Ensure the module still works
   451  			results, err := ce.Call(testCtx, m, []uint64{1})
   452  			require.NoError(t, err)
   453  			require.Equal(t, uint64(1), results[0])
   454  		})
   455  	}
   456  }
   457  
   458  // RunTestModuleEngine_Memory shows that the byte slice returned from api.Memory Read is not a copy, rather a re-slice
   459  // of the underlying memory. This allows both host and Wasm to see each other's writes, unless one side changes the
   460  // capacity of the slice.
   461  //
   462  // Known cases that change the slice capacity:
   463  // * Host code calls append on a byte slice returned by api.Memory Read
   464  // * Wasm code calls wasm.OpcodeMemoryGrowName and this changes the capacity (by default, it will).
   465  func RunTestModuleEngine_Memory(t *testing.T, et EngineTester) {
   466  	e := et.NewEngine(api.CoreFeaturesV2)
   467  
   468  	wasmPhrase := "Well, that'll be the day when you say goodbye."
   469  	wasmPhraseSize := uint32(len(wasmPhrase))
   470  
   471  	// Define a basic function which defines one parameter. This is used to test results when incorrect arity is used.
   472  	one := uint32(1)
   473  	m := &wasm.Module{
   474  		TypeSection:     []*wasm.FunctionType{{Params: []api.ValueType{api.ValueTypeI32}, ParamNumInUint64: 1}, v_v},
   475  		FunctionSection: []wasm.Index{0, 1},
   476  		MemorySection:   &wasm.Memory{Min: 1, Cap: 1, Max: 2},
   477  		DataSection: []*wasm.DataSegment{
   478  			{
   479  				OffsetExpression: nil, // passive
   480  				Init:             []byte(wasmPhrase),
   481  			},
   482  		},
   483  		DataCountSection: &one,
   484  		CodeSection: []*wasm.Code{
   485  			{Body: []byte{ // "grow"
   486  				wasm.OpcodeLocalGet, 0, // how many pages to grow (param)
   487  				wasm.OpcodeMemoryGrow, 0, // memory index zero
   488  				wasm.OpcodeDrop, // drop the previous page count (or -1 if grow failed)
   489  				wasm.OpcodeEnd,
   490  			}},
   491  			{Body: []byte{ // "init"
   492  				wasm.OpcodeI32Const, 0, // target offset
   493  				wasm.OpcodeI32Const, 0, // source offset
   494  				wasm.OpcodeI32Const, byte(wasmPhraseSize), // len
   495  				wasm.OpcodeMiscPrefix, wasm.OpcodeMiscMemoryInit, 0, 0, // segment 0, memory 0
   496  				wasm.OpcodeEnd,
   497  			}},
   498  		},
   499  		ExportSection: []*wasm.Export{
   500  			{Name: "grow", Type: wasm.ExternTypeFunc, Index: 0},
   501  			{Name: "init", Type: wasm.ExternTypeFunc, Index: 1},
   502  		},
   503  	}
   504  	m.BuildFunctionDefinitions()
   505  	listeners := buildListeners(et.ListenerFactory(), m)
   506  
   507  	err := e.CompileModule(testCtx, m, listeners)
   508  	require.NoError(t, err)
   509  
   510  	// Assign memory to the module instance
   511  	module := &wasm.ModuleInstance{
   512  		Name:          t.Name(),
   513  		Memory:        wasm.NewMemoryInstance(m.MemorySection),
   514  		DataInstances: []wasm.DataInstance{m.DataSection[0].Init},
   515  		TypeIDs:       []wasm.FunctionTypeID{0, 1},
   516  	}
   517  	var memory api.Memory = module.Memory
   518  
   519  	// To use functions, we need to instantiate them (associate them with a ModuleInstance).
   520  	module.Functions = module.BuildFunctions(m)
   521  	module.BuildExports(m.ExportSection)
   522  	grow, init := module.Functions[0], module.Functions[1]
   523  
   524  	// Compile the module
   525  	me, err := e.NewModuleEngine(module.Name, m, nil, module.Functions)
   526  	require.NoError(t, err)
   527  	linkModuleToEngine(module, me)
   528  
   529  	buf, ok := memory.Read(testCtx, 0, wasmPhraseSize)
   530  	require.True(t, ok)
   531  	require.Equal(t, make([]byte, wasmPhraseSize), buf)
   532  
   533  	// Initialize the memory using Wasm. This copies the test phrase.
   534  	initCallEngine, err := me.NewCallEngine(module.CallCtx, init)
   535  	require.NoError(t, err)
   536  	_, err = initCallEngine.Call(testCtx, module.CallCtx, nil)
   537  	require.NoError(t, err)
   538  
   539  	// We expect the same []byte read earlier to now include the phrase in wasm.
   540  	require.Equal(t, wasmPhrase, string(buf))
   541  
   542  	hostPhrase := "Goodbye, cruel world. I'm off to join the circus." // Intentionally slightly longer.
   543  	hostPhraseSize := uint32(len(hostPhrase))
   544  
   545  	// Copy over the buffer, which should stop at the current length.
   546  	copy(buf, hostPhrase)
   547  	require.Equal(t, "Goodbye, cruel world. I'm off to join the circ", string(buf))
   548  
   549  	// The underlying memory should be updated. This proves that Memory.Read returns a re-slice, not a copy, and that
   550  	// programs can rely on this (for example, to update shared state in Wasm and view that in Go and visa versa).
   551  	buf2, ok := memory.Read(testCtx, 0, wasmPhraseSize)
   552  	require.True(t, ok)
   553  	require.Equal(t, buf, buf2)
   554  
   555  	// Now, append to the buffer we got from Wasm. As this changes capacity, it should result in a new byte slice.
   556  	buf = append(buf, 'u', 's', '.')
   557  	require.Equal(t, hostPhrase, string(buf))
   558  
   559  	// To prove the above, we re-read the memory and should not see the appended bytes (rather zeros instead).
   560  	buf2, ok = memory.Read(testCtx, 0, hostPhraseSize)
   561  	require.True(t, ok)
   562  	hostPhraseTruncated := "Goodbye, cruel world. I'm off to join the circ" + string([]byte{0, 0, 0})
   563  	require.Equal(t, hostPhraseTruncated, string(buf2))
   564  
   565  	// Now, we need to prove the other direction, that when Wasm changes the capacity, the host's buffer is unaffected.
   566  	growCallEngine, err := me.NewCallEngine(module.CallCtx, grow)
   567  	require.NoError(t, err)
   568  	_, err = growCallEngine.Call(testCtx, module.CallCtx, []uint64{1})
   569  	require.NoError(t, err)
   570  
   571  	// The host buffer should still contain the same bytes as before grow
   572  	require.Equal(t, hostPhraseTruncated, string(buf2))
   573  
   574  	// Re-initialize the memory in wasm, which overwrites the region.
   575  	initCallEngine2, err := me.NewCallEngine(module.CallCtx, init)
   576  	require.NoError(t, err)
   577  	_, err = initCallEngine2.Call(testCtx, module.CallCtx, nil)
   578  	require.NoError(t, err)
   579  
   580  	// The host was not affected because it is a different slice due to "memory.grow" affecting the underlying memory.
   581  	require.Equal(t, hostPhraseTruncated, string(buf2))
   582  }
   583  
   584  const (
   585  	divByWasmName             = "div_by.wasm"
   586  	divByGoName               = "div_by.go"
   587  	callDivByGoName           = "call->" + divByGoName
   588  	callImportCallDivByGoName = "call_import->" + callDivByGoName
   589  )
   590  
   591  func divByGo(d uint32) uint32 {
   592  	if d == math.MaxUint32 {
   593  		panic(errors.New("host-function panic"))
   594  	}
   595  	return 1 / d // go panics if d == 0
   596  }
   597  
   598  var hostDivByGo = wasm.MustParseGoReflectFuncCode(divByGo)
   599  
   600  // (func (export "div_by.wasm") (param i32) (result i32) (i32.div_u (i32.const 1) (local.get 0)))
   601  var (
   602  	divByWasm     = []byte{wasm.OpcodeI32Const, 1, wasm.OpcodeLocalGet, 0, wasm.OpcodeI32DivU, wasm.OpcodeEnd}
   603  	hostDivByWasm = &wasm.Code{IsHostFunction: true, Body: divByWasm}
   604  )
   605  
   606  const (
   607  	readMemName               = "read_mem"
   608  	callReadMemName           = "call->read_mem"
   609  	callImportReadMemName     = "call_import->read_mem"
   610  	callImportCallReadMemName = "call_import->call->read_mem"
   611  )
   612  
   613  func readMemGo(ctx context.Context, m api.Module) uint64 {
   614  	ret, ok := m.Memory().ReadUint64Le(ctx, 0)
   615  	if !ok {
   616  		panic("couldn't read memory")
   617  	}
   618  	return ret
   619  }
   620  
   621  var hostReadMemGo = wasm.MustParseGoReflectFuncCode(readMemGo)
   622  
   623  // (func (export "wasm_read_mem") (result i64) i32.const 0 i64.load)
   624  var (
   625  	readMemWasm     = []byte{wasm.OpcodeI32Const, 0, wasm.OpcodeI64Load, 0x3, 0x0, wasm.OpcodeEnd}
   626  	hostReadMemWasm = &wasm.Code{IsHostFunction: true, Body: readMemWasm}
   627  )
   628  
   629  func setupCallTests(t *testing.T, e wasm.Engine, divBy *wasm.Code, fnlf experimental.FunctionListenerFactory) (*wasm.ModuleInstance, *wasm.ModuleInstance, *wasm.ModuleInstance, func()) {
   630  	ft := &wasm.FunctionType{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1}
   631  
   632  	divByName := divByWasmName
   633  	if divBy.GoFunc != nil {
   634  		divByName = divByGoName
   635  	}
   636  	hostModule := &wasm.Module{
   637  		TypeSection:     []*wasm.FunctionType{ft},
   638  		FunctionSection: []wasm.Index{0},
   639  		CodeSection:     []*wasm.Code{divBy},
   640  		ExportSection:   []*wasm.Export{{Name: divByGoName, Type: wasm.ExternTypeFunc, Index: 0}},
   641  		NameSection: &wasm.NameSection{
   642  			ModuleName:    "host",
   643  			FunctionNames: wasm.NameMap{{Index: wasm.Index(0), Name: divByName}},
   644  		},
   645  		ID: wasm.ModuleID{0},
   646  	}
   647  	hostModule.BuildFunctionDefinitions()
   648  	lns := buildListeners(fnlf, hostModule)
   649  	err := e.CompileModule(testCtx, hostModule, lns)
   650  	require.NoError(t, err)
   651  	host := &wasm.ModuleInstance{Name: hostModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}}
   652  	host.Functions = host.BuildFunctions(hostModule)
   653  	host.BuildExports(hostModule.ExportSection)
   654  	hostFn := host.Exports[divByGoName].Function
   655  
   656  	hostME, err := e.NewModuleEngine(host.Name, hostModule, nil, host.Functions)
   657  	require.NoError(t, err)
   658  	linkModuleToEngine(host, hostME)
   659  
   660  	importedModule := &wasm.Module{
   661  		ImportSection:   []*wasm.Import{{}},
   662  		TypeSection:     []*wasm.FunctionType{ft},
   663  		FunctionSection: []wasm.Index{0, 0},
   664  		CodeSection: []*wasm.Code{
   665  			{Body: divByWasm},
   666  			{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, byte(0), // Calling imported host function ^.
   667  				wasm.OpcodeEnd}},
   668  		},
   669  		ExportSection: []*wasm.Export{
   670  			{Name: divByWasmName, Type: wasm.ExternTypeFunc, Index: 1},
   671  			{Name: callDivByGoName, Type: wasm.ExternTypeFunc, Index: 2},
   672  		},
   673  		NameSection: &wasm.NameSection{
   674  			ModuleName: "imported",
   675  			FunctionNames: wasm.NameMap{
   676  				{Index: wasm.Index(1), Name: divByWasmName},
   677  				{Index: wasm.Index(2), Name: callDivByGoName},
   678  			},
   679  		},
   680  		ID: wasm.ModuleID{1},
   681  	}
   682  	importedModule.BuildFunctionDefinitions()
   683  	lns = buildListeners(fnlf, importedModule)
   684  	err = e.CompileModule(testCtx, importedModule, lns)
   685  	require.NoError(t, err)
   686  
   687  	imported := &wasm.ModuleInstance{Name: importedModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}}
   688  	importedFunctions := imported.BuildFunctions(importedModule)
   689  	imported.Functions = append([]*wasm.FunctionInstance{hostFn}, importedFunctions...)
   690  	imported.BuildExports(importedModule.ExportSection)
   691  	callHostFn := imported.Exports[callDivByGoName].Function
   692  
   693  	// Compile the imported module
   694  	importedMe, err := e.NewModuleEngine(imported.Name, importedModule, []*wasm.FunctionInstance{hostFn}, importedFunctions)
   695  	require.NoError(t, err)
   696  	linkModuleToEngine(imported, importedMe)
   697  
   698  	// To test stack traces, call the same function from another module
   699  	importingModule := &wasm.Module{
   700  		TypeSection:     []*wasm.FunctionType{ft},
   701  		ImportSection:   []*wasm.Import{{}},
   702  		FunctionSection: []wasm.Index{0},
   703  		CodeSection: []*wasm.Code{
   704  			{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 0 /* only one imported function */, wasm.OpcodeEnd}},
   705  		},
   706  		ExportSection: []*wasm.Export{
   707  			{Name: callImportCallDivByGoName, Type: wasm.ExternTypeFunc, Index: 1},
   708  		},
   709  		NameSection: &wasm.NameSection{
   710  			ModuleName:    "importing",
   711  			FunctionNames: wasm.NameMap{{Index: wasm.Index(1), Name: callImportCallDivByGoName}},
   712  		},
   713  		ID: wasm.ModuleID{2},
   714  	}
   715  	importingModule.BuildFunctionDefinitions()
   716  	lns = buildListeners(fnlf, importingModule)
   717  	err = e.CompileModule(testCtx, importingModule, lns)
   718  	require.NoError(t, err)
   719  
   720  	// Add the exported function.
   721  	importing := &wasm.ModuleInstance{Name: importingModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}}
   722  	importingFunctions := importing.BuildFunctions(importingModule)
   723  	importing.Functions = append([]*wasm.FunctionInstance{callHostFn}, importingFunctions...)
   724  	importing.BuildExports(importingModule.ExportSection)
   725  
   726  	// Compile the importing module
   727  	importingMe, err := e.NewModuleEngine(importing.Name, importingModule, []*wasm.FunctionInstance{callHostFn}, importingFunctions)
   728  	require.NoError(t, err)
   729  	linkModuleToEngine(importing, importingMe)
   730  
   731  	return host, imported, importing, func() {
   732  		e.DeleteCompiledModule(hostModule)
   733  		e.DeleteCompiledModule(importedModule)
   734  		e.DeleteCompiledModule(importingModule)
   735  	}
   736  }
   737  
   738  func setupCallMemTests(t *testing.T, e wasm.Engine, readMem *wasm.Code, fnlf experimental.FunctionListenerFactory) (*wasm.ModuleInstance, *wasm.ModuleInstance, func()) {
   739  	ft := &wasm.FunctionType{Results: []wasm.ValueType{i64}, ResultNumInUint64: 1}
   740  
   741  	callReadMem := &wasm.Code{ // shows indirect calls still use the same memory
   742  		IsHostFunction: true,
   743  		Body: []byte{
   744  			wasm.OpcodeCall, 1,
   745  			// On the return from the another host function,
   746  			// we should still be able to access the memory.
   747  			wasm.OpcodeI32Const, 0,
   748  			wasm.OpcodeI32Load, 0x2, 0x0,
   749  			wasm.OpcodeEnd,
   750  		},
   751  	}
   752  	hostModule := &wasm.Module{
   753  		TypeSection:     []*wasm.FunctionType{ft},
   754  		FunctionSection: []wasm.Index{0, 0},
   755  		CodeSection:     []*wasm.Code{callReadMem, readMem},
   756  		ExportSection: []*wasm.Export{
   757  			{Name: callReadMemName, Type: wasm.ExternTypeFunc, Index: 0},
   758  			{Name: readMemName, Type: wasm.ExternTypeFunc, Index: 1},
   759  		},
   760  		NameSection: &wasm.NameSection{
   761  			ModuleName:    "host",
   762  			FunctionNames: wasm.NameMap{{Index: 0, Name: readMemName}, {Index: 1, Name: callReadMemName}},
   763  		},
   764  		ID: wasm.ModuleID{0},
   765  	}
   766  	hostModule.BuildFunctionDefinitions()
   767  	err := e.CompileModule(testCtx, hostModule, nil)
   768  	require.NoError(t, err)
   769  	host := &wasm.ModuleInstance{Name: hostModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}}
   770  	host.Functions = host.BuildFunctions(hostModule)
   771  	host.BuildExports(hostModule.ExportSection)
   772  	readMemFn := host.Exports[readMemName].Function
   773  	callReadMemFn := host.Exports[callReadMemName].Function
   774  
   775  	hostME, err := e.NewModuleEngine(host.Name, hostModule, nil, host.Functions)
   776  	require.NoError(t, err)
   777  	linkModuleToEngine(host, hostME)
   778  
   779  	importingModule := &wasm.Module{
   780  		TypeSection: []*wasm.FunctionType{ft},
   781  		ImportSection: []*wasm.Import{
   782  			// Placeholder for two import functions from `importedModule`.
   783  			{Type: wasm.ExternTypeFunc, DescFunc: 0},
   784  			{Type: wasm.ExternTypeFunc, DescFunc: 0},
   785  		},
   786  		FunctionSection: []wasm.Index{0, 0},
   787  		ExportSection: []*wasm.Export{
   788  			{Name: callImportReadMemName, Type: wasm.ExternTypeFunc, Index: 2},
   789  			{Name: callImportCallReadMemName, Type: wasm.ExternTypeFunc, Index: 3},
   790  		},
   791  		CodeSection: []*wasm.Code{
   792  			{Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Calling the index 0 = callReadMemFn.
   793  			{Body: []byte{wasm.OpcodeCall, 1, wasm.OpcodeEnd}}, // Calling the index 1 = readMemFn.
   794  		},
   795  		NameSection: &wasm.NameSection{
   796  			ModuleName: "importing",
   797  			FunctionNames: wasm.NameMap{
   798  				{Index: 2, Name: callImportReadMemName},
   799  				{Index: 3, Name: callImportCallReadMemName},
   800  			},
   801  		},
   802  		// Indicates that this module has a memory so that compilers are able to assembe memory-related initialization.
   803  		MemorySection: &wasm.Memory{Min: 1},
   804  		ID:            wasm.ModuleID{1},
   805  	}
   806  	importingModule.BuildFunctionDefinitions()
   807  	err = e.CompileModule(testCtx, importingModule, nil)
   808  	require.NoError(t, err)
   809  
   810  	// Add the exported function.
   811  	importing := &wasm.ModuleInstance{Name: importingModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}}
   812  	importingFunctions := importing.BuildFunctions(importingModule)
   813  	// Note: adds imported functions readMemFn and callReadMemFn at index 0 and 1.
   814  	importing.Functions = append([]*wasm.FunctionInstance{callReadMemFn, readMemFn}, importingFunctions...)
   815  	importing.BuildExports(importingModule.ExportSection)
   816  
   817  	// Compile the importing module
   818  	importingMe, err := e.NewModuleEngine(importing.Name, importingModule, []*wasm.FunctionInstance{readMemFn, callReadMemFn}, importingFunctions)
   819  	require.NoError(t, err)
   820  	linkModuleToEngine(importing, importingMe)
   821  
   822  	return host, importing, func() {
   823  		e.DeleteCompiledModule(hostModule)
   824  		e.DeleteCompiledModule(importingModule)
   825  	}
   826  }
   827  
   828  // linkModuleToEngine assigns fields that wasm.Store would on instantiation. These includes fields both interpreter and
   829  // Compiler needs as well as fields only needed by Compiler.
   830  //
   831  // Note: This sets fields that are not needed in the interpreter, but are required by code compiled by Compiler. If a new
   832  // test here passes in the interpreter and segmentation faults in Compiler, check for a new field offset or a change in Compiler
   833  // (e.g. compiler.TestVerifyOffsetValue). It is possible for all other tests to pass as that field is implicitly set by
   834  // wasm.Store: store isn't used here for unit test precision.
   835  func linkModuleToEngine(module *wasm.ModuleInstance, me wasm.ModuleEngine) {
   836  	module.Engine = me // for Compiler, links the module to the module-engine compiled from it (moduleInstanceEngineOffset).
   837  	// callEngineModuleContextModuleInstanceAddressOffset
   838  	module.CallCtx = wasm.NewCallContext(nil, module, nil)
   839  }
   840  
   841  func buildListeners(factory experimental.FunctionListenerFactory, m *wasm.Module) []experimental.FunctionListener {
   842  	if factory == nil || len(m.FunctionSection) == 0 {
   843  		return nil
   844  	}
   845  	listeners := make([]experimental.FunctionListener, len(m.FunctionSection))
   846  	importCount := m.ImportFuncCount()
   847  	for i := 0; i < len(listeners); i++ {
   848  		listeners[i] = factory.NewListener(m.FunctionDefinitionSection[uint32(i)+importCount])
   849  	}
   850  	return listeners
   851  }