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