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 }