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

     1  package compiler
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"runtime"
     9  	"testing"
    10  	"unsafe"
    11  
    12  	"wa-lang.org/wazero/api"
    13  	"wa-lang.org/wazero/experimental"
    14  	"wa-lang.org/wazero/experimental/logging"
    15  	"wa-lang.org/wazero/internal/platform"
    16  	"wa-lang.org/wazero/internal/testing/enginetest"
    17  	"wa-lang.org/wazero/internal/testing/require"
    18  	"wa-lang.org/wazero/internal/wasm"
    19  )
    20  
    21  // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
    22  var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
    23  
    24  var (
    25  	// et is used for tests defined in the enginetest package.
    26  	et              = &engineTester{}
    27  	functionLog     bytes.Buffer
    28  	listenerFactory = logging.NewLoggingListenerFactory(&functionLog)
    29  )
    30  
    31  // engineTester implements enginetest.EngineTester.
    32  type engineTester struct{}
    33  
    34  // IsCompiler implements the same method as documented on enginetest.EngineTester.
    35  func (e *engineTester) IsCompiler() bool {
    36  	return true
    37  }
    38  
    39  // ListenerFactory implements the same method as documented on enginetest.EngineTester.
    40  func (e *engineTester) ListenerFactory() experimental.FunctionListenerFactory {
    41  	return listenerFactory
    42  }
    43  
    44  // NewEngine implements the same method as documented on enginetest.EngineTester.
    45  func (e *engineTester) NewEngine(enabledFeatures api.CoreFeatures) wasm.Engine {
    46  	return newEngine(context.Background(), enabledFeatures)
    47  }
    48  
    49  // CompiledFunctionPointerValue implements the same method as documented on enginetest.EngineTester.
    50  func (e engineTester) CompiledFunctionPointerValue(me wasm.ModuleEngine, funcIndex wasm.Index) uint64 {
    51  	internal := me.(*moduleEngine)
    52  	return uint64(uintptr(unsafe.Pointer(internal.functions[funcIndex])))
    53  }
    54  
    55  func TestCompiler_Engine_NewModuleEngine(t *testing.T) {
    56  	requireSupportedOSArch(t)
    57  	enginetest.RunTestEngine_NewModuleEngine(t, et)
    58  }
    59  
    60  func TestCompiler_Engine_InitializeFuncrefGlobals(t *testing.T) {
    61  	enginetest.RunTestEngine_InitializeFuncrefGlobals(t, et)
    62  }
    63  
    64  func TestCompiler_ModuleEngine_LookupFunction(t *testing.T) {
    65  	enginetest.RunTestModuleEngine_LookupFunction(t, et)
    66  }
    67  
    68  func TestCompiler_ModuleEngine_Call(t *testing.T) {
    69  	defer functionLog.Reset()
    70  	requireSupportedOSArch(t)
    71  	enginetest.RunTestModuleEngine_Call(t, et)
    72  	require.Equal(t, `
    73  --> .$0(1,2)
    74  <-- (1,2)
    75  `, "\n"+functionLog.String())
    76  }
    77  
    78  func TestCompiler_ModuleEngine_Call_HostFn(t *testing.T) {
    79  	defer functionLog.Reset()
    80  	requireSupportedOSArch(t)
    81  	enginetest.RunTestModuleEngine_Call_HostFn(t, et)
    82  }
    83  
    84  func TestCompiler_ModuleEngine_Call_Errors(t *testing.T) {
    85  	defer functionLog.Reset()
    86  	requireSupportedOSArch(t)
    87  	enginetest.RunTestModuleEngine_Call_Errors(t, et)
    88  
    89  	// TODO: Currently, the listener doesn't get notified on errors as they are
    90  	// implemented with panic. This means the end hooks aren't make resulting
    91  	// in dangling logs like this:
    92  	//	==> host.host_div_by(4294967295)
    93  	// instead of seeing a return like
    94  	//	<== DivByZero
    95  	require.Equal(t, `
    96  --> imported.div_by.wasm(1)
    97  <-- (1)
    98  --> imported.div_by.wasm(1)
    99  <-- (1)
   100  --> imported.div_by.wasm(0)
   101  --> imported.div_by.wasm(1)
   102  <-- (1)
   103  --> imported.call->div_by.go(4294967295)
   104  	==> host.div_by.go(4294967295)
   105  --> imported.call->div_by.go(1)
   106  	==> host.div_by.go(1)
   107  	<== (1)
   108  <-- (1)
   109  --> importing.call_import->call->div_by.go(0)
   110  	--> imported.call->div_by.go(0)
   111  		==> host.div_by.go(0)
   112  --> importing.call_import->call->div_by.go(1)
   113  	--> imported.call->div_by.go(1)
   114  		==> host.div_by.go(1)
   115  		<== (1)
   116  	<-- (1)
   117  <-- (1)
   118  --> importing.call_import->call->div_by.go(4294967295)
   119  	--> imported.call->div_by.go(4294967295)
   120  		==> host.div_by.go(4294967295)
   121  --> importing.call_import->call->div_by.go(1)
   122  	--> imported.call->div_by.go(1)
   123  		==> host.div_by.go(1)
   124  		<== (1)
   125  	<-- (1)
   126  <-- (1)
   127  --> importing.call_import->call->div_by.go(0)
   128  	--> imported.call->div_by.go(0)
   129  		==> host.div_by.go(0)
   130  --> importing.call_import->call->div_by.go(1)
   131  	--> imported.call->div_by.go(1)
   132  		==> host.div_by.go(1)
   133  		<== (1)
   134  	<-- (1)
   135  <-- (1)
   136  `, "\n"+functionLog.String())
   137  }
   138  
   139  func TestCompiler_ModuleEngine_Memory(t *testing.T) {
   140  	requireSupportedOSArch(t)
   141  	enginetest.RunTestModuleEngine_Memory(t, et)
   142  }
   143  
   144  // requireSupportedOSArch is duplicated also in the platform package to ensure no cyclic dependency.
   145  func requireSupportedOSArch(t *testing.T) {
   146  	if !platform.CompilerSupported() {
   147  		t.Skip()
   148  	}
   149  }
   150  
   151  type fakeFinalizer map[*code]func(*code)
   152  
   153  func (f fakeFinalizer) setFinalizer(obj interface{}, finalizer interface{}) {
   154  	cf := obj.(*code)
   155  	if _, ok := f[cf]; ok { // easier than adding a field for testing.T
   156  		panic(fmt.Sprintf("BUG: %v already had its finalizer set", cf))
   157  	}
   158  	f[cf] = finalizer.(func(*code))
   159  }
   160  
   161  func TestCompiler_CompileModule(t *testing.T) {
   162  	t.Run("ok", func(t *testing.T) {
   163  		e := et.NewEngine(api.CoreFeaturesV1).(*engine)
   164  		ff := fakeFinalizer{}
   165  		e.setFinalizer = ff.setFinalizer
   166  
   167  		okModule := &wasm.Module{
   168  			TypeSection:     []*wasm.FunctionType{{}},
   169  			FunctionSection: []wasm.Index{0, 0, 0, 0},
   170  			CodeSection: []*wasm.Code{
   171  				{Body: []byte{wasm.OpcodeEnd}},
   172  				{Body: []byte{wasm.OpcodeEnd}},
   173  				{Body: []byte{wasm.OpcodeEnd}},
   174  				{Body: []byte{wasm.OpcodeEnd}},
   175  			},
   176  			ID: wasm.ModuleID{},
   177  		}
   178  
   179  		err := e.CompileModule(testCtx, okModule, nil)
   180  		require.NoError(t, err)
   181  
   182  		// Compiling same module shouldn't be compiled again, but instead should be cached.
   183  		err = e.CompileModule(testCtx, okModule, nil)
   184  		require.NoError(t, err)
   185  
   186  		compiled, ok := e.codes[okModule.ID]
   187  		require.True(t, ok)
   188  		require.Equal(t, len(okModule.FunctionSection), len(compiled))
   189  
   190  		// Pretend the finalizer executed, by invoking them one-by-one.
   191  		for k, v := range ff {
   192  			v(k)
   193  		}
   194  	})
   195  
   196  	t.Run("fail", func(t *testing.T) {
   197  		errModule := &wasm.Module{
   198  			TypeSection:     []*wasm.FunctionType{{}},
   199  			FunctionSection: []wasm.Index{0, 0, 0},
   200  			CodeSection: []*wasm.Code{
   201  				{Body: []byte{wasm.OpcodeEnd}},
   202  				{Body: []byte{wasm.OpcodeEnd}},
   203  				{Body: []byte{wasm.OpcodeCall}}, // Call instruction without immediate for call target index is invalid and should fail to compile.
   204  			},
   205  			ID: wasm.ModuleID{},
   206  		}
   207  		errModule.BuildFunctionDefinitions()
   208  
   209  		e := et.NewEngine(api.CoreFeaturesV1).(*engine)
   210  		err := e.CompileModule(testCtx, errModule, nil)
   211  		require.EqualError(t, err, "failed to lower func[.$2] to wazeroir: handling instruction: apply stack failed for call: reading immediates: EOF")
   212  
   213  		// On the compilation failure, the compiled functions must not be cached.
   214  		_, ok := e.codes[errModule.ID]
   215  		require.False(t, ok)
   216  	})
   217  }
   218  
   219  // TestCompiler_Releasecode_Panic tests that an unexpected panic has some identifying information in it.
   220  func TestCompiler_Releasecode_Panic(t *testing.T) {
   221  	captured := require.CapturePanic(func() {
   222  		releaseCode(&code{
   223  			indexInModule: 2,
   224  			sourceModule:  &wasm.Module{NameSection: &wasm.NameSection{ModuleName: t.Name()}},
   225  			codeSegment:   []byte{wasm.OpcodeEnd}, // never compiled means it was never mapped.
   226  		})
   227  	})
   228  	require.Contains(t, captured.Error(), fmt.Sprintf("compiler: failed to munmap code segment for %[1]s.function[2]", t.Name()))
   229  }
   230  
   231  // Ensures that value stack and call-frame stack are allocated on heap which
   232  // allows us to safely access to their data region from native code.
   233  // See comments on initialStackSize and initialCallFrameStackSize.
   234  func TestCompiler_SliceAllocatedOnHeap(t *testing.T) {
   235  	enabledFeatures := api.CoreFeaturesV1
   236  	e := newEngine(context.Background(), enabledFeatures)
   237  	s, ns := wasm.NewStore(enabledFeatures, e)
   238  
   239  	const hostModuleName = "env"
   240  	const hostFnName = "grow_and_shrink_goroutine_stack"
   241  	hm, err := wasm.NewHostModule(hostModuleName, map[string]interface{}{hostFnName: func() {
   242  		// This function aggressively grow the goroutine stack by recursively
   243  		// calling the function many times.
   244  		callNum := 1000
   245  		var growGoroutineStack func()
   246  		growGoroutineStack = func() {
   247  			if callNum != 0 {
   248  				callNum--
   249  				growGoroutineStack()
   250  			}
   251  		}
   252  		growGoroutineStack()
   253  
   254  		// Trigger relocation of goroutine stack because at this point we have the majority of
   255  		// goroutine stack unused after recursive call.
   256  		runtime.GC()
   257  	}}, nil, enabledFeatures)
   258  	require.NoError(t, err)
   259  
   260  	err = s.Engine.CompileModule(testCtx, hm, nil)
   261  	require.NoError(t, err)
   262  
   263  	_, err = s.Instantiate(testCtx, ns, hm, hostModuleName, nil)
   264  	require.NoError(t, err)
   265  
   266  	const stackCorruption = "value_stack_corruption"
   267  	const callStackCorruption = "call_stack_corruption"
   268  	const expectedReturnValue = 0x1
   269  	m := &wasm.Module{
   270  		TypeSection: []*wasm.FunctionType{
   271  			{Params: []wasm.ValueType{}, Results: []wasm.ValueType{wasm.ValueTypeI32}, ResultNumInUint64: 1},
   272  			{Params: []wasm.ValueType{}, Results: []wasm.ValueType{}},
   273  		},
   274  		FunctionSection: []wasm.Index{
   275  			wasm.Index(0),
   276  			wasm.Index(0),
   277  			wasm.Index(0),
   278  		},
   279  		CodeSection: []*wasm.Code{
   280  			{
   281  				// value_stack_corruption
   282  				Body: []byte{
   283  					wasm.OpcodeCall, 0, // Call host function to shrink Goroutine stack
   284  					// We expect this value is returned, but if the stack is allocated on
   285  					// goroutine stack, we write this expected value into the old-location of
   286  					// stack.
   287  					wasm.OpcodeI32Const, expectedReturnValue,
   288  					wasm.OpcodeEnd,
   289  				},
   290  			},
   291  			{
   292  				// call_stack_corruption
   293  				Body: []byte{
   294  					wasm.OpcodeCall, 3, // Call the wasm function below.
   295  					// At this point, call stack's memory looks like [call_stack_corruption, index3]
   296  					// With this function call it should end up [call_stack_corruption, host func]
   297  					// but if the call-frame stack is allocated on goroutine stack, we exit the native code
   298  					// with  [call_stack_corruption, index3] (old call frame stack) with HostCall status code,
   299  					// and end up trying to call index3 as a host function which results in nil pointer exception.
   300  					wasm.OpcodeCall, 0,
   301  					wasm.OpcodeI32Const, expectedReturnValue,
   302  					wasm.OpcodeEnd,
   303  				},
   304  			},
   305  			{Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}},
   306  		},
   307  		ImportSection: []*wasm.Import{{Module: hostModuleName, Name: hostFnName, DescFunc: 1}},
   308  		ExportSection: []*wasm.Export{
   309  			{Type: wasm.ExternTypeFunc, Index: 1, Name: stackCorruption},
   310  			{Type: wasm.ExternTypeFunc, Index: 2, Name: callStackCorruption},
   311  		},
   312  		ID: wasm.ModuleID{1},
   313  	}
   314  	m.BuildFunctionDefinitions()
   315  
   316  	err = s.Engine.CompileModule(testCtx, m, nil)
   317  	require.NoError(t, err)
   318  
   319  	mi, err := s.Instantiate(testCtx, ns, m, t.Name(), nil)
   320  	require.NoError(t, err)
   321  
   322  	for _, fnName := range []string{stackCorruption, callStackCorruption} {
   323  		fnName := fnName
   324  		t.Run(fnName, func(t *testing.T) {
   325  			ret, err := mi.ExportedFunction(fnName).Call(testCtx)
   326  			require.NoError(t, err)
   327  
   328  			require.Equal(t, uint32(expectedReturnValue), uint32(ret[0]))
   329  		})
   330  	}
   331  }
   332  
   333  func TestCallEngine_builtinFunctionTableGrow(t *testing.T) {
   334  	ce := &callEngine{
   335  		stack: []uint64{
   336  			0xff, // pseudo-ref
   337  			1,    // num
   338  			// Table Index = 0 (lower 32-bits), but the higher bits (32-63) are all sets,
   339  			// which happens if the previous value on that stack location was 64-bit wide.
   340  			0xffffffff << 32,
   341  		},
   342  		stackContext: stackContext{stackPointer: 3},
   343  	}
   344  
   345  	table := &wasm.TableInstance{References: []wasm.Reference{}, Min: 10}
   346  	ce.builtinFunctionTableGrow(context.Background(), []*wasm.TableInstance{table})
   347  
   348  	require.Equal(t, 1, len(table.References))
   349  	require.Equal(t, uintptr(0xff), table.References[0])
   350  }
   351  
   352  func ptrAsUint64(f *function) uint64 {
   353  	return uint64(uintptr(unsafe.Pointer(f)))
   354  }
   355  
   356  func TestCallEngine_deferredOnCall(t *testing.T) {
   357  	f1 := &function{source: &wasm.FunctionInstance{
   358  		Definition: newMockFunctionDefinition("1"),
   359  		Type:       &wasm.FunctionType{ParamNumInUint64: 2},
   360  	}}
   361  	f2 := &function{source: &wasm.FunctionInstance{
   362  		Definition: newMockFunctionDefinition("2"),
   363  		Type:       &wasm.FunctionType{ParamNumInUint64: 2, ResultNumInUint64: 3},
   364  	}}
   365  	f3 := &function{source: &wasm.FunctionInstance{
   366  		Definition: newMockFunctionDefinition("3"),
   367  		Type:       &wasm.FunctionType{ResultNumInUint64: 1},
   368  	}}
   369  
   370  	ce := &callEngine{
   371  		stack: []uint64{
   372  			0xff, 0xff, // dummy argument for f1
   373  			0, 0, 0, 0,
   374  			0xcc, 0xcc, // local variable for f1.
   375  			// <----- stack base point of f2 (top) == index 8.
   376  			0xaa, 0xaa, 0xdeadbeaf, // dummy argument for f2 (0xaa, 0xaa) and the reserved slot for result 0xdeadbeaf)
   377  			0, 0, ptrAsUint64(f1), 0, // callFrame
   378  			0xcc, 0xcc, 0xcc, // local variable for f2.
   379  			// <----- stack base point of f3 (top) == index 18
   380  			0xdeadbeaf,                    // the reserved slot for result 0xdeadbeaf) from f3.
   381  			0, 8 << 3, ptrAsUint64(f2), 0, // callFrame
   382  		},
   383  		stackContext: stackContext{
   384  			stackBasePointerInBytes: 18 << 3, // currently executed function (f3)'s base pointer.
   385  			stackPointer:            0xff,    // dummy supposed to be reset to zero.
   386  		},
   387  		moduleContext: moduleContext{
   388  			fn:                    f3, // currently executed function (f3)!
   389  			moduleInstanceAddress: 0xdeafbeaf,
   390  		},
   391  	}
   392  
   393  	beforeRecoverStack := ce.stack
   394  
   395  	err := ce.deferredOnCall(errors.New("some error"))
   396  	require.EqualError(t, err, `some error (recovered by wazero)
   397  wasm stack trace:
   398  	3()
   399  	2()
   400  	1()`)
   401  
   402  	// After recover, the state of callEngine must be reset except that the underlying slices must be intact
   403  	// for the subsequent calls to avoid additional allocations on each call.
   404  	require.Equal(t, uint64(0), ce.stackBasePointerInBytes)
   405  	require.Equal(t, uint64(0), ce.stackPointer)
   406  	require.Equal(t, uintptr(0), ce.moduleInstanceAddress)
   407  	require.Equal(t, beforeRecoverStack, ce.stack)
   408  
   409  	// Keep f1, f2, and f3 alive until we reach here, as we access these functions from the uint64 raw pointers in the stack.
   410  	// In practice, they are guaranteed to be alive as they are held by moduleContext.
   411  	runtime.KeepAlive(f1)
   412  	runtime.KeepAlive(f2)
   413  	runtime.KeepAlive(f3)
   414  }
   415  
   416  func newMockFunctionDefinition(name string) api.FunctionDefinition {
   417  	return &mockFunctionDefinition{debugName: name, FunctionDefinition: &wasm.FunctionDefinition{}}
   418  }
   419  
   420  type mockFunctionDefinition struct {
   421  	debugName string
   422  	*wasm.FunctionDefinition
   423  }
   424  
   425  // DebugName implements the same method as documented on api.FunctionDefinition.
   426  func (f *mockFunctionDefinition) DebugName() string {
   427  	return f.debugName
   428  }
   429  
   430  // ParamTypes implements api.FunctionDefinition ParamTypes.
   431  func (f *mockFunctionDefinition) ParamTypes() []wasm.ValueType {
   432  	return []wasm.ValueType{}
   433  }
   434  
   435  // ResultTypes implements api.FunctionDefinition ResultTypes.
   436  func (f *mockFunctionDefinition) ResultTypes() []wasm.ValueType {
   437  	return []wasm.ValueType{}
   438  }
   439  
   440  func TestCallEngine_initializeStack(t *testing.T) {
   441  	const i32 = wasm.ValueTypeI32
   442  	const stackSize = 10
   443  	const initialVal = ^uint64(0)
   444  	tests := []struct {
   445  		name            string
   446  		funcType        *wasm.FunctionType
   447  		args            []uint64
   448  		expStackPointer uint64
   449  		expStack        [stackSize]uint64
   450  	}{
   451  		{
   452  			name:            "no param/result",
   453  			funcType:        &wasm.FunctionType{},
   454  			expStackPointer: callFrameDataSizeInUint64,
   455  			expStack: [stackSize]uint64{
   456  				0, 0, 0, // zeroed call frame
   457  				initialVal, initialVal, initialVal, initialVal, initialVal, initialVal, initialVal,
   458  			},
   459  		},
   460  		{
   461  			name: "no result",
   462  			funcType: &wasm.FunctionType{
   463  				Params:           []wasm.ValueType{i32, i32},
   464  				ParamNumInUint64: 2,
   465  			},
   466  			args:            []uint64{0xdeadbeaf, 0xdeadbeaf},
   467  			expStackPointer: callFrameDataSizeInUint64 + 2,
   468  			expStack: [stackSize]uint64{
   469  				0xdeadbeaf, 0xdeadbeaf, // arguments
   470  				0, 0, 0, // zeroed call frame
   471  				initialVal, initialVal, initialVal, initialVal, initialVal,
   472  			},
   473  		},
   474  		{
   475  			name: "no param",
   476  			funcType: &wasm.FunctionType{
   477  				Results:           []wasm.ValueType{i32, i32, i32},
   478  				ResultNumInUint64: 3,
   479  			},
   480  			expStackPointer: callFrameDataSizeInUint64 + 3,
   481  			expStack: [stackSize]uint64{
   482  				initialVal, initialVal, initialVal, // reserved slots for results
   483  				0, 0, 0, // zeroed call frame
   484  				initialVal, initialVal, initialVal, initialVal,
   485  			},
   486  		},
   487  		{
   488  			name: "params > results",
   489  			funcType: &wasm.FunctionType{
   490  				Params:            []wasm.ValueType{i32, i32, i32, i32, i32},
   491  				ParamNumInUint64:  5,
   492  				Results:           []wasm.ValueType{i32, i32, i32},
   493  				ResultNumInUint64: 3,
   494  			},
   495  			args:            []uint64{0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf},
   496  			expStackPointer: callFrameDataSizeInUint64 + 5,
   497  			expStack: [stackSize]uint64{
   498  				0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf,
   499  				0, 0, 0, // zeroed call frame
   500  				initialVal, initialVal,
   501  			},
   502  		},
   503  		{
   504  			name: "params == results",
   505  			funcType: &wasm.FunctionType{
   506  				Params:            []wasm.ValueType{i32, i32, i32, i32, i32},
   507  				ParamNumInUint64:  5,
   508  				Results:           []wasm.ValueType{i32, i32, i32, i32, i32},
   509  				ResultNumInUint64: 5,
   510  			},
   511  			args:            []uint64{0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf},
   512  			expStackPointer: callFrameDataSizeInUint64 + 5,
   513  			expStack: [stackSize]uint64{
   514  				0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf,
   515  				0, 0, 0, // zeroed call frame
   516  				initialVal, initialVal,
   517  			},
   518  		},
   519  		{
   520  			name: "params < results",
   521  			funcType: &wasm.FunctionType{
   522  				Params:            []wasm.ValueType{i32, i32, i32},
   523  				ParamNumInUint64:  3,
   524  				Results:           []wasm.ValueType{i32, i32, i32, i32, i32},
   525  				ResultNumInUint64: 5,
   526  			},
   527  			args:            []uint64{0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf},
   528  			expStackPointer: callFrameDataSizeInUint64 + 5,
   529  			expStack: [stackSize]uint64{
   530  				0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf,
   531  				initialVal, initialVal, // reserved for results
   532  				0, 0, 0, // zeroed call frame
   533  				initialVal, initialVal,
   534  			},
   535  		},
   536  	}
   537  
   538  	for _, tc := range tests {
   539  		tc := tc
   540  		t.Run(tc.name, func(t *testing.T) {
   541  			initialStack := make([]uint64, stackSize)
   542  			for i := range initialStack {
   543  				initialStack[i] = initialVal
   544  			}
   545  			ce := &callEngine{stack: initialStack}
   546  			ce.initializeStack(tc.funcType, tc.args)
   547  			require.Equal(t, tc.expStackPointer, ce.stackPointer)
   548  			require.Equal(t, tc.expStack[:], ce.stack)
   549  		})
   550  	}
   551  }
   552  
   553  func Test_callFrameOffset(t *testing.T) {
   554  	require.Equal(t, 1, callFrameOffset(&wasm.FunctionType{ParamNumInUint64: 0, ResultNumInUint64: 1}))
   555  	require.Equal(t, 10, callFrameOffset(&wasm.FunctionType{ParamNumInUint64: 5, ResultNumInUint64: 10}))
   556  	require.Equal(t, 100, callFrameOffset(&wasm.FunctionType{ParamNumInUint64: 50, ResultNumInUint64: 100}))
   557  	require.Equal(t, 1, callFrameOffset(&wasm.FunctionType{ParamNumInUint64: 1, ResultNumInUint64: 0}))
   558  	require.Equal(t, 10, callFrameOffset(&wasm.FunctionType{ParamNumInUint64: 10, ResultNumInUint64: 5}))
   559  	require.Equal(t, 100, callFrameOffset(&wasm.FunctionType{ParamNumInUint64: 100, ResultNumInUint64: 50}))
   560  }
   561  
   562  func TestCallEngine_builtinFunctionFunctionListenerBefore(t *testing.T) {
   563  	nextContext, currentContext, prevContext := context.Background(), context.Background(), context.Background()
   564  
   565  	f := &function{
   566  		source: &wasm.FunctionInstance{
   567  			Definition: newMockFunctionDefinition("1"),
   568  			Type:       &wasm.FunctionType{ParamNumInUint64: 3},
   569  		},
   570  		parent: &code{
   571  			listener: mockListener{
   572  				before: func(ctx context.Context, def api.FunctionDefinition, paramValues []uint64) context.Context {
   573  					require.Equal(t, currentContext, ctx)
   574  					require.Equal(t, []uint64{2, 3, 4}, paramValues)
   575  					return nextContext
   576  				},
   577  			},
   578  		},
   579  	}
   580  	ce := &callEngine{
   581  		ctx: currentContext, stack: []uint64{0, 1, 2, 3, 4, 5},
   582  		stackContext: stackContext{stackBasePointerInBytes: 16},
   583  		contextStack: &contextStack{self: prevContext},
   584  	}
   585  	ce.builtinFunctionFunctionListenerBefore(ce.ctx, f)
   586  
   587  	// Contexts must be stacked.
   588  	require.Equal(t, currentContext, ce.contextStack.self)
   589  	require.Equal(t, prevContext, ce.contextStack.prev.self)
   590  }
   591  
   592  func TestCallEngine_builtinFunctionFunctionListenerAfter(t *testing.T) {
   593  	currentContext, prevContext := context.Background(), context.Background()
   594  	f := &function{
   595  		source: &wasm.FunctionInstance{
   596  			Definition: newMockFunctionDefinition("1"),
   597  			Type:       &wasm.FunctionType{ResultNumInUint64: 1},
   598  		},
   599  		parent: &code{
   600  			listener: mockListener{
   601  				after: func(ctx context.Context, def api.FunctionDefinition, err error, resultValues []uint64) {
   602  					require.Equal(t, currentContext, ctx)
   603  					require.Equal(t, []uint64{5}, resultValues)
   604  				},
   605  			},
   606  		},
   607  	}
   608  
   609  	ce := &callEngine{
   610  		ctx: currentContext, stack: []uint64{0, 1, 2, 3, 4, 5},
   611  		stackContext: stackContext{stackBasePointerInBytes: 40},
   612  		contextStack: &contextStack{self: prevContext},
   613  	}
   614  	ce.builtinFunctionFunctionListenerAfter(ce.ctx, f)
   615  
   616  	// Contexts must be popped.
   617  	require.Nil(t, ce.contextStack)
   618  	require.Equal(t, prevContext, ce.ctx)
   619  }
   620  
   621  type mockListener struct {
   622  	before func(ctx context.Context, def api.FunctionDefinition, paramValues []uint64) context.Context
   623  	after  func(ctx context.Context, def api.FunctionDefinition, err error, resultValues []uint64)
   624  }
   625  
   626  func (m mockListener) Before(ctx context.Context, def api.FunctionDefinition, paramValues []uint64) context.Context {
   627  	return m.before(ctx, def, paramValues)
   628  }
   629  
   630  func (m mockListener) After(ctx context.Context, def api.FunctionDefinition, err error, resultValues []uint64) {
   631  	m.after(ctx, def, err, resultValues)
   632  }