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

     1  package wazevo
     2  
     3  import (
     4  	"encoding/binary"
     5  	"runtime"
     6  	"strconv"
     7  	"testing"
     8  	"unsafe"
     9  
    10  	"github.com/wasilibs/wazerox/internal/engine/wazevo/wazevoapi"
    11  	"github.com/wasilibs/wazerox/internal/testing/require"
    12  	"github.com/wasilibs/wazerox/internal/wasm"
    13  )
    14  
    15  func TestModuleEngine_setupOpaque(t *testing.T) {
    16  	for i, tc := range []struct {
    17  		offset wazevoapi.ModuleContextOffsetData
    18  		m      *wasm.ModuleInstance
    19  	}{
    20  		{
    21  			offset: wazevoapi.ModuleContextOffsetData{
    22  				LocalMemoryBegin:                    10,
    23  				ImportedMemoryBegin:                 -1,
    24  				ImportedFunctionsBegin:              -1,
    25  				TablesBegin:                         -1,
    26  				BeforeListenerTrampolines1stElement: -1,
    27  				AfterListenerTrampolines1stElement:  -1,
    28  				GlobalsBegin:                        -1,
    29  			},
    30  			m: &wasm.ModuleInstance{MemoryInstance: &wasm.MemoryInstance{
    31  				Buffer: make([]byte, 0xff),
    32  			}},
    33  		},
    34  		{
    35  			offset: wazevoapi.ModuleContextOffsetData{
    36  				LocalMemoryBegin:                    -1,
    37  				ImportedMemoryBegin:                 30,
    38  				GlobalsBegin:                        -1,
    39  				TablesBegin:                         -1,
    40  				BeforeListenerTrampolines1stElement: -1,
    41  				AfterListenerTrampolines1stElement:  -1,
    42  				ImportedFunctionsBegin:              -1,
    43  			},
    44  			m: &wasm.ModuleInstance{MemoryInstance: &wasm.MemoryInstance{
    45  				Buffer: make([]byte, 0xff),
    46  			}},
    47  		},
    48  		{
    49  			offset: wazevoapi.ModuleContextOffsetData{
    50  				LocalMemoryBegin:                    -1,
    51  				ImportedMemoryBegin:                 -1,
    52  				ImportedFunctionsBegin:              -1,
    53  				GlobalsBegin:                        30,
    54  				TablesBegin:                         100,
    55  				BeforeListenerTrampolines1stElement: 200,
    56  				AfterListenerTrampolines1stElement:  208,
    57  			},
    58  			m: &wasm.ModuleInstance{
    59  				Globals: []*wasm.GlobalInstance{{}, {}, {}, {}, {}, {}},
    60  				Tables:  []*wasm.TableInstance{{}, {}, {}},
    61  				TypeIDs: make([]wasm.FunctionTypeID, 50),
    62  			},
    63  		},
    64  	} {
    65  		t.Run(strconv.Itoa(i), func(t *testing.T) {
    66  			tc.offset.TotalSize = 1000 // arbitrary large number to ensure we don't panic.
    67  			m := &moduleEngine{
    68  				parent: &compiledModule{
    69  					offsets:                   tc.offset,
    70  					listenerBeforeTrampolines: make([]*byte, 100),
    71  					listenerAfterTrampolines:  make([]*byte, 200),
    72  				},
    73  				module: tc.m,
    74  				opaque: make([]byte, tc.offset.TotalSize),
    75  			}
    76  			m.setupOpaque()
    77  
    78  			if tc.offset.LocalMemoryBegin >= 0 {
    79  				actualPtr := uintptr(binary.LittleEndian.Uint64(m.opaque[tc.offset.LocalMemoryBegin:]))
    80  				expPtr := uintptr(unsafe.Pointer(&tc.m.MemoryInstance.Buffer[0]))
    81  				require.Equal(t, expPtr, actualPtr)
    82  				actualLen := int(binary.LittleEndian.Uint64(m.opaque[tc.offset.LocalMemoryBegin+8:]))
    83  				expLen := len(tc.m.MemoryInstance.Buffer)
    84  				require.Equal(t, expLen, actualLen)
    85  			}
    86  			if tc.offset.ImportedMemoryBegin >= 0 {
    87  				imported := &moduleEngine{
    88  					opaque: []byte{1, 2, 3}, module: &wasm.ModuleInstance{MemoryInstance: tc.m.MemoryInstance},
    89  					parent: &compiledModule{offsets: wazevoapi.ModuleContextOffsetData{ImportedMemoryBegin: -1}},
    90  				}
    91  				imported.opaquePtr = &imported.opaque[0]
    92  				m.ResolveImportedMemory(imported)
    93  
    94  				actualPtr := uintptr(binary.LittleEndian.Uint64(m.opaque[tc.offset.ImportedMemoryBegin:]))
    95  				expPtr := uintptr(unsafe.Pointer(tc.m.MemoryInstance))
    96  				require.Equal(t, expPtr, actualPtr)
    97  
    98  				actualOpaquePtr := uintptr(binary.LittleEndian.Uint64(m.opaque[tc.offset.ImportedMemoryBegin+8:]))
    99  				require.Equal(t, uintptr(unsafe.Pointer(imported.opaquePtr)), actualOpaquePtr)
   100  				runtime.KeepAlive(imported)
   101  			}
   102  			if tc.offset.GlobalsBegin >= 0 {
   103  				for i, g := range tc.m.Globals {
   104  					actualPtr := uintptr(binary.LittleEndian.Uint64(m.opaque[int(tc.offset.GlobalsBegin)+8*i:]))
   105  					expPtr := uintptr(unsafe.Pointer(g))
   106  					require.Equal(t, expPtr, actualPtr)
   107  				}
   108  			}
   109  			if tc.offset.TablesBegin >= 0 {
   110  				typeIDsPtr := uintptr(binary.LittleEndian.Uint64(m.opaque[int(tc.offset.TypeIDs1stElement):]))
   111  				expPtr := uintptr(unsafe.Pointer(&tc.m.TypeIDs[0]))
   112  				require.Equal(t, expPtr, typeIDsPtr)
   113  
   114  				for i, table := range tc.m.Tables {
   115  					actualPtr := uintptr(binary.LittleEndian.Uint64(m.opaque[int(tc.offset.TablesBegin)+8*i:]))
   116  					expPtr := uintptr(unsafe.Pointer(table))
   117  					require.Equal(t, expPtr, actualPtr)
   118  				}
   119  			}
   120  			if tc.offset.BeforeListenerTrampolines1stElement >= 0 {
   121  				actualPtr := uintptr(binary.LittleEndian.Uint64(m.opaque[int(tc.offset.BeforeListenerTrampolines1stElement):]))
   122  				expPtr := uintptr(unsafe.Pointer(&m.parent.listenerBeforeTrampolines[0]))
   123  				require.Equal(t, expPtr, actualPtr)
   124  			}
   125  			if tc.offset.AfterListenerTrampolines1stElement >= 0 {
   126  				actualPtr := uintptr(binary.LittleEndian.Uint64(m.opaque[int(tc.offset.AfterListenerTrampolines1stElement):]))
   127  				expPtr := uintptr(unsafe.Pointer(&m.parent.listenerAfterTrampolines[0]))
   128  				require.Equal(t, expPtr, actualPtr)
   129  			}
   130  		})
   131  	}
   132  }
   133  
   134  func TestModuleEngine_ResolveImportedFunction(t *testing.T) {
   135  	const begin = 5000
   136  	m := &moduleEngine{
   137  		opaque:            make([]byte, 10000),
   138  		importedFunctions: make([]importedFunction, 4),
   139  		parent: &compiledModule{offsets: wazevoapi.ModuleContextOffsetData{
   140  			ImportedFunctionsBegin: begin,
   141  		}},
   142  	}
   143  
   144  	var op1, op2 byte = 0xaa, 0xbb
   145  	im1 := &moduleEngine{
   146  		opaquePtr: &op1,
   147  		parent: &compiledModule{
   148  			executables:     &executables{executable: make([]byte, 1000)},
   149  			functionOffsets: []int{1, 5, 10},
   150  		},
   151  		module: &wasm.ModuleInstance{
   152  			TypeIDs: []wasm.FunctionTypeID{0, 0, 0, 0, 111, 222, 333},
   153  			Source:  &wasm.Module{FunctionSection: []wasm.Index{4, 5, 6}},
   154  		},
   155  	}
   156  	im2 := &moduleEngine{
   157  		opaquePtr: &op2,
   158  		parent: &compiledModule{
   159  			executables:     &executables{executable: make([]byte, 1000)},
   160  			functionOffsets: []int{50, 4},
   161  		},
   162  		module: &wasm.ModuleInstance{
   163  			TypeIDs: []wasm.FunctionTypeID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 999},
   164  			Source:  &wasm.Module{FunctionSection: []wasm.Index{10}},
   165  		},
   166  	}
   167  
   168  	m.ResolveImportedFunction(0, 0, im1)
   169  	m.ResolveImportedFunction(1, 0, im2)
   170  	m.ResolveImportedFunction(2, 2, im1)
   171  	m.ResolveImportedFunction(3, 1, im1)
   172  
   173  	for i, tc := range []struct {
   174  		index      int
   175  		op         *byte
   176  		executable *byte
   177  		expTypeID  wasm.FunctionTypeID
   178  	}{
   179  		{index: 0, op: &op1, executable: &im1.parent.executable[1], expTypeID: 111},
   180  		{index: 1, op: &op2, executable: &im2.parent.executable[50], expTypeID: 999},
   181  		{index: 2, op: &op1, executable: &im1.parent.executable[10], expTypeID: 333},
   182  		{index: 3, op: &op1, executable: &im1.parent.executable[5], expTypeID: 222},
   183  	} {
   184  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   185  			buf := m.opaque[begin+wazevoapi.FunctionInstanceSize*tc.index:]
   186  			actualExecutable := binary.LittleEndian.Uint64(buf)
   187  			actualOpaquePtr := binary.LittleEndian.Uint64(buf[8:])
   188  			actualTypeID := binary.LittleEndian.Uint64(buf[16:])
   189  			expExecutable := uint64(uintptr(unsafe.Pointer(tc.executable)))
   190  			expOpaquePtr := uint64(uintptr(unsafe.Pointer(tc.op)))
   191  			require.Equal(t, expExecutable, actualExecutable)
   192  			require.Equal(t, expOpaquePtr, actualOpaquePtr)
   193  			require.Equal(t, uint64(tc.expTypeID), actualTypeID)
   194  		})
   195  	}
   196  }
   197  
   198  func TestModuleEngine_ResolveImportedFunction_recursive(t *testing.T) {
   199  	const begin = 5000
   200  	m := &moduleEngine{
   201  		opaque:            make([]byte, 10000),
   202  		importedFunctions: make([]importedFunction, 4),
   203  		parent: &compiledModule{offsets: wazevoapi.ModuleContextOffsetData{
   204  			ImportedFunctionsBegin: begin,
   205  		}},
   206  	}
   207  
   208  	var importingOp, importedOp byte = 0xaa, 0xbb
   209  	imported := &moduleEngine{
   210  		opaquePtr: &importedOp,
   211  		parent: &compiledModule{
   212  			executables:     &executables{executable: make([]byte, 50)},
   213  			functionOffsets: []int{10},
   214  		},
   215  		module: &wasm.ModuleInstance{
   216  			TypeIDs: []wasm.FunctionTypeID{111},
   217  			Source:  &wasm.Module{FunctionSection: []wasm.Index{0}},
   218  		},
   219  	}
   220  	importing := &moduleEngine{
   221  		opaquePtr: &importingOp,
   222  		parent: &compiledModule{
   223  			executables:     &executables{executable: make([]byte, 1000)},
   224  			functionOffsets: []int{500},
   225  		},
   226  		importedFunctions: []importedFunction{{me: imported, indexInModule: 0}},
   227  		module: &wasm.ModuleInstance{
   228  			TypeIDs: []wasm.FunctionTypeID{0, 222, 0},
   229  			Source:  &wasm.Module{FunctionSection: []wasm.Index{1}},
   230  		},
   231  	}
   232  
   233  	m.ResolveImportedFunction(0, 0, importing)
   234  	m.ResolveImportedFunction(1, 1, importing)
   235  
   236  	for i, tc := range []struct {
   237  		index      int
   238  		op         *byte
   239  		executable *byte
   240  		expTypeID  wasm.FunctionTypeID
   241  	}{
   242  		{index: 0, op: &importedOp, executable: &imported.parent.executable[10], expTypeID: 111},
   243  		{index: 1, op: &importingOp, executable: &importing.parent.executable[500], expTypeID: 222},
   244  	} {
   245  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   246  			buf := m.opaque[begin+wazevoapi.FunctionInstanceSize*tc.index:]
   247  			actualExecutable := binary.LittleEndian.Uint64(buf)
   248  			actualOpaquePtr := binary.LittleEndian.Uint64(buf[8:])
   249  			actualTypeID := binary.LittleEndian.Uint64(buf[16:])
   250  			expExecutable := uint64(uintptr(unsafe.Pointer(tc.executable)))
   251  			expOpaquePtr := uint64(uintptr(unsafe.Pointer(tc.op)))
   252  			require.Equal(t, expExecutable, actualExecutable)
   253  			require.Equal(t, expOpaquePtr, actualOpaquePtr)
   254  			require.Equal(t, uint64(tc.expTypeID), actualTypeID)
   255  		})
   256  	}
   257  }
   258  
   259  func TestModuleEngine_ResolveImportedMemory_reexported(t *testing.T) {
   260  	m := &moduleEngine{
   261  		parent: &compiledModule{offsets: wazevoapi.ModuleContextOffsetData{
   262  			ImportedMemoryBegin: 50,
   263  		}},
   264  		opaque: make([]byte, 100),
   265  	}
   266  
   267  	importedME := &moduleEngine{
   268  		parent: &compiledModule{offsets: wazevoapi.ModuleContextOffsetData{
   269  			ImportedMemoryBegin: 1000,
   270  		}},
   271  		opaque: make([]byte, 2000),
   272  	}
   273  	binary.LittleEndian.PutUint64(importedME.opaque[1000:], 0x1234567890abcdef)
   274  	binary.LittleEndian.PutUint64(importedME.opaque[1000+8:], 0xabcdef1234567890)
   275  
   276  	m.ResolveImportedMemory(importedME)
   277  	require.Equal(t, uint64(0x1234567890abcdef), binary.LittleEndian.Uint64(m.opaque[50:]))
   278  	require.Equal(t, uint64(0xabcdef1234567890), binary.LittleEndian.Uint64(m.opaque[50+8:]))
   279  }
   280  
   281  func Test_functionInstance_offsets(t *testing.T) {
   282  	var fi functionInstance
   283  	require.Equal(t, wazevoapi.FunctionInstanceSize, int(unsafe.Sizeof(fi)))
   284  	require.Equal(t, wazevoapi.FunctionInstanceExecutableOffset, int(unsafe.Offsetof(fi.executable)))
   285  	require.Equal(t, wazevoapi.FunctionInstanceModuleContextOpaquePtrOffset, int(unsafe.Offsetof(fi.moduleContextOpaquePtr)))
   286  	require.Equal(t, wazevoapi.FunctionInstanceTypeIDOffset, int(unsafe.Offsetof(fi.typeID)))
   287  
   288  	m := wazevoapi.ModuleContextOffsetData{ImportedFunctionsBegin: 100}
   289  	ptr, moduleCtx, typeID := m.ImportedFunctionOffset(10)
   290  	require.Equal(t, 100+10*wazevoapi.FunctionInstanceSize, int(ptr))
   291  	require.Equal(t, moduleCtx, ptr+8)
   292  	require.Equal(t, typeID, ptr+16)
   293  }
   294  
   295  func Test_getTypeIDOf(t *testing.T) {
   296  	m := &wasm.ModuleInstance{
   297  		TypeIDs: []wasm.FunctionTypeID{111, 222, 333, 444},
   298  		Source: &wasm.Module{
   299  			ImportFunctionCount: 1,
   300  			ImportSection: []wasm.Import{
   301  				{Type: wasm.ExternTypeMemory},
   302  				{Type: wasm.ExternTypeTable},
   303  				{Type: wasm.ExternTypeFunc, DescFunc: 3},
   304  			},
   305  			FunctionSection: []wasm.Index{2, 1, 0},
   306  		},
   307  	}
   308  
   309  	require.Equal(t, wasm.FunctionTypeID(444), getTypeIDOf(0, m))
   310  	require.Equal(t, wasm.FunctionTypeID(333), getTypeIDOf(1, m))
   311  	require.Equal(t, wasm.FunctionTypeID(222), getTypeIDOf(2, m))
   312  	require.Equal(t, wasm.FunctionTypeID(111), getTypeIDOf(3, m))
   313  }