github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/engine/compiler/engine_test.go (about)

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