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 }