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

     1  package wazevo
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  	"testing"
     8  	"unsafe"
     9  
    10  	"github.com/wasilibs/wazerox/internal/platform"
    11  	"github.com/wasilibs/wazerox/internal/testing/require"
    12  	"github.com/wasilibs/wazerox/internal/wasm"
    13  )
    14  
    15  func Test_sharedFunctionsFinalizer(t *testing.T) {
    16  	sf := &sharedFunctions{}
    17  
    18  	b1, err := platform.MmapCodeSegment(100)
    19  	require.NoError(t, err)
    20  	b2, err := platform.MmapCodeSegment(100)
    21  	require.NoError(t, err)
    22  	b3, err := platform.MmapCodeSegment(100)
    23  	require.NoError(t, err)
    24  	b6, err := platform.MmapCodeSegment(100)
    25  	require.NoError(t, err)
    26  	b7, err := platform.MmapCodeSegment(100)
    27  	require.NoError(t, err)
    28  
    29  	sf.memoryGrowExecutable = b1
    30  	sf.stackGrowExecutable = b2
    31  	sf.checkModuleExitCode = b3
    32  	sf.tableGrowExecutable = b6
    33  	sf.refFuncExecutable = b7
    34  
    35  	sharedFunctionsFinalizer(sf)
    36  	require.Nil(t, sf.memoryGrowExecutable)
    37  	require.Nil(t, sf.stackGrowExecutable)
    38  	require.Nil(t, sf.checkModuleExitCode)
    39  	require.Nil(t, sf.tableGrowExecutable)
    40  	require.Nil(t, sf.refFuncExecutable)
    41  }
    42  
    43  func Test_executablesFinalizer(t *testing.T) {
    44  	b, err := platform.MmapCodeSegment(100)
    45  	require.NoError(t, err)
    46  
    47  	exec := &executables{}
    48  	exec.executable = b
    49  	executablesFinalizer(exec)
    50  	require.Nil(t, exec.executable)
    51  }
    52  
    53  type fakeFinalizer map[*executables]func(module *executables)
    54  
    55  func (f fakeFinalizer) setFinalizer(obj interface{}, finalizer interface{}) {
    56  	cf := obj.(*executables)
    57  	if _, ok := f[cf]; ok { // easier than adding a field for testing.T
    58  		panic(fmt.Sprintf("BUG: %v already had its finalizer set", cf))
    59  	}
    60  	f[cf] = finalizer.(func(*executables))
    61  }
    62  
    63  func TestEngine_CompileModule(t *testing.T) {
    64  	ctx := context.Background()
    65  	e := NewEngine(ctx, 0, nil).(*engine)
    66  	ff := fakeFinalizer{}
    67  	e.setFinalizer = ff.setFinalizer
    68  
    69  	okModule := &wasm.Module{
    70  		TypeSection:     []wasm.FunctionType{{}},
    71  		FunctionSection: []wasm.Index{0, 0, 0, 0},
    72  		CodeSection: []wasm.Code{
    73  			{Body: []byte{wasm.OpcodeEnd}},
    74  			{Body: []byte{wasm.OpcodeEnd}},
    75  			{Body: []byte{wasm.OpcodeEnd}},
    76  			{Body: []byte{wasm.OpcodeEnd}},
    77  		},
    78  		ID: wasm.ModuleID{},
    79  	}
    80  
    81  	err := e.CompileModule(ctx, okModule, nil, false)
    82  	require.NoError(t, err)
    83  
    84  	// Compiling same module shouldn't be compiled again, but instead should be cached.
    85  	err = e.CompileModule(ctx, okModule, nil, false)
    86  	require.NoError(t, err)
    87  
    88  	// Pretend the finalizer executed, by invoking them one-by-one.
    89  	for k, v := range ff {
    90  		v(k)
    91  	}
    92  }
    93  
    94  func TestEngine_sortedCompiledModules(t *testing.T) {
    95  	getCM := func(addr uintptr) *compiledModule {
    96  		var buf []byte
    97  		{
    98  			// TODO: use unsafe.Slice after floor version is set to Go 1.20.
    99  			hdr := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
   100  			hdr.Data = addr
   101  			hdr.Len = 1
   102  			hdr.Cap = 1
   103  		}
   104  		cm := &compiledModule{executables: &executables{executable: buf}}
   105  		return cm
   106  	}
   107  
   108  	requireEqualExisting := func(t *testing.T, e *engine, expected []uintptr) {
   109  		actual := make([]uintptr, 0)
   110  		for _, cm := range e.sortedCompiledModules {
   111  			actual = append(actual, uintptr(unsafe.Pointer(&cm.executable[0])))
   112  		}
   113  		require.Equal(t, expected, actual)
   114  	}
   115  
   116  	m1 := getCM(1)
   117  	m100 := getCM(100)
   118  	m5 := getCM(5)
   119  	m10 := getCM(10)
   120  
   121  	t.Run("add", func(t *testing.T) {
   122  		e := &engine{}
   123  		e.addCompiledModuleToSortedList(m1)
   124  		e.addCompiledModuleToSortedList(m100)
   125  		e.addCompiledModuleToSortedList(m5)
   126  		e.addCompiledModuleToSortedList(m10)
   127  		requireEqualExisting(t, e, []uintptr{1, 5, 10, 100})
   128  	})
   129  	t.Run("delete", func(t *testing.T) {
   130  		e := &engine{}
   131  		e.addCompiledModuleToSortedList(m1)
   132  		e.addCompiledModuleToSortedList(m100)
   133  		e.addCompiledModuleToSortedList(m5)
   134  		e.addCompiledModuleToSortedList(m10)
   135  		e.deleteCompiledModuleFromSortedList(m100)
   136  		require.Equal(t, 3, len(e.sortedCompiledModules))
   137  		requireEqualExisting(t, e, []uintptr{1, 5, 10})
   138  		e.deleteCompiledModuleFromSortedList(m1)
   139  		requireEqualExisting(t, e, []uintptr{5, 10})
   140  		e.deleteCompiledModuleFromSortedList(m10)
   141  		requireEqualExisting(t, e, []uintptr{5})
   142  		e.deleteCompiledModuleFromSortedList(m5)
   143  		requireEqualExisting(t, e, []uintptr{})
   144  	})
   145  
   146  	t.Run("OfAddr", func(t *testing.T) {
   147  		e := &engine{}
   148  		e.addCompiledModuleToSortedList(m1)
   149  		e.addCompiledModuleToSortedList(m100)
   150  		e.addCompiledModuleToSortedList(m5)
   151  		e.addCompiledModuleToSortedList(m10)
   152  
   153  		require.Equal(t, nil, e.compiledModuleOfAddr(0))
   154  		require.Equal(t, unsafe.Pointer(m1), unsafe.Pointer(e.compiledModuleOfAddr(1)))
   155  		require.Equal(t, unsafe.Pointer(m1), unsafe.Pointer(e.compiledModuleOfAddr(4)))
   156  		require.Equal(t, unsafe.Pointer(m5), unsafe.Pointer(e.compiledModuleOfAddr(5)))
   157  		require.Equal(t, unsafe.Pointer(m5), unsafe.Pointer(e.compiledModuleOfAddr(9)))
   158  		require.Equal(t, unsafe.Pointer(m10), unsafe.Pointer(e.compiledModuleOfAddr(10)))
   159  		require.Equal(t, unsafe.Pointer(m10), unsafe.Pointer(e.compiledModuleOfAddr(11)))
   160  		require.Equal(t, unsafe.Pointer(m10), unsafe.Pointer(e.compiledModuleOfAddr(12)))
   161  		require.Equal(t, unsafe.Pointer(m10), unsafe.Pointer(e.compiledModuleOfAddr(50)))
   162  		require.Equal(t, unsafe.Pointer(m10), unsafe.Pointer(e.compiledModuleOfAddr(99)))
   163  		require.Equal(t, unsafe.Pointer(m100), unsafe.Pointer(e.compiledModuleOfAddr(100)))
   164  		require.Equal(t, unsafe.Pointer(m100), unsafe.Pointer(e.compiledModuleOfAddr(10000)))
   165  	})
   166  }
   167  
   168  func TestCompiledModule_functionIndexOf(t *testing.T) {
   169  	const executableAddr = 0xaaaa
   170  	var executable []byte
   171  	{
   172  		// TODO: use unsafe.Slice after floor version is set to Go 1.20.
   173  		hdr := (*reflect.SliceHeader)(unsafe.Pointer(&executable))
   174  		hdr.Data = executableAddr
   175  		hdr.Len = 0xffff
   176  		hdr.Cap = 0xffff
   177  	}
   178  
   179  	cm := &compiledModule{
   180  		executables:     &executables{executable: executable},
   181  		functionOffsets: []int{0, 500, 1000, 2000},
   182  	}
   183  	require.Equal(t, wasm.Index(0), cm.functionIndexOf(executableAddr))
   184  	require.Equal(t, wasm.Index(0), cm.functionIndexOf(executableAddr+499))
   185  	require.Equal(t, wasm.Index(1), cm.functionIndexOf(executableAddr+500))
   186  	require.Equal(t, wasm.Index(1), cm.functionIndexOf(executableAddr+999))
   187  	require.Equal(t, wasm.Index(2), cm.functionIndexOf(executableAddr+1000))
   188  	require.Equal(t, wasm.Index(2), cm.functionIndexOf(executableAddr+1500))
   189  	require.Equal(t, wasm.Index(2), cm.functionIndexOf(executableAddr+1999))
   190  	require.Equal(t, wasm.Index(3), cm.functionIndexOf(executableAddr+2000))
   191  }