github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/engine/wazevo/module_engine.go (about) 1 package wazevo 2 3 import ( 4 "encoding/binary" 5 "unsafe" 6 7 "github.com/tetratelabs/wazero/api" 8 "github.com/tetratelabs/wazero/experimental" 9 "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" 10 "github.com/tetratelabs/wazero/internal/wasm" 11 "github.com/tetratelabs/wazero/internal/wasmruntime" 12 ) 13 14 type ( 15 // moduleEngine implements wasm.ModuleEngine. 16 moduleEngine struct { 17 // opaquePtr equals &opaque[0]. 18 opaquePtr *byte 19 parent *compiledModule 20 module *wasm.ModuleInstance 21 opaque moduleContextOpaque 22 localFunctionInstances []*functionInstance 23 importedFunctions []importedFunction 24 listeners []experimental.FunctionListener 25 } 26 27 functionInstance struct { 28 executable *byte 29 moduleContextOpaquePtr *byte 30 typeID wasm.FunctionTypeID 31 indexInModule wasm.Index 32 } 33 34 importedFunction struct { 35 me *moduleEngine 36 indexInModule wasm.Index 37 } 38 39 // moduleContextOpaque is the opaque byte slice of Module instance specific contents whose size 40 // is only Wasm-compile-time known, hence dynamic. Its contents are basically the pointers to the module instance, 41 // specific objects as well as functions. This is sometimes called "VMContext" in other Wasm runtimes. 42 // 43 // Internally, the buffer is structured as follows: 44 // 45 // type moduleContextOpaque struct { 46 // moduleInstance *wasm.ModuleInstance 47 // localMemoryBufferPtr *byte (optional) 48 // localMemoryLength uint64 (optional) 49 // importedMemoryInstance *wasm.MemoryInstance (optional) 50 // importedMemoryOwnerOpaqueCtx *byte (optional) 51 // importedFunctions [# of importedFunctions]functionInstance 52 // importedGlobals []ImportedGlobal (optional) 53 // localGlobals []Global (optional) 54 // typeIDsBegin &wasm.ModuleInstance.TypeIDs[0] (optional) 55 // tables []*wasm.TableInstance (optional) 56 // beforeListenerTrampolines1stElement **byte (optional) 57 // afterListenerTrampolines1stElement **byte (optional) 58 // dataInstances1stElement []wasm.DataInstance (optional) 59 // elementInstances1stElement []wasm.ElementInstance (optional) 60 // } 61 // 62 // type ImportedGlobal struct { 63 // *Global 64 // _ uint64 // padding 65 // } 66 // 67 // type Global struct { 68 // Val, ValHi uint64 69 // } 70 // 71 // See wazevoapi.NewModuleContextOffsetData for the details of the offsets. 72 // 73 // Note that for host modules, the structure is entirely different. See buildHostModuleOpaque. 74 moduleContextOpaque []byte 75 ) 76 77 func newAlignedOpaque(size int) moduleContextOpaque { 78 // Check if the size is a multiple of 16. 79 if size%16 != 0 { 80 panic("size must be a multiple of 16") 81 } 82 buf := make([]byte, size+16) 83 // Align the buffer to 16 bytes. 84 rem := uintptr(unsafe.Pointer(&buf[0])) % 16 85 buf = buf[16-rem:] 86 return buf 87 } 88 89 func putLocalMemory(opaque []byte, offset wazevoapi.Offset, mem *wasm.MemoryInstance) { 90 s := uint64(len(mem.Buffer)) 91 var b uint64 92 if len(mem.Buffer) > 0 { 93 b = uint64(uintptr(unsafe.Pointer(&mem.Buffer[0]))) 94 } 95 binary.LittleEndian.PutUint64(opaque[offset:], b) 96 binary.LittleEndian.PutUint64(opaque[offset+8:], s) 97 } 98 99 func (m *moduleEngine) setupOpaque() { 100 inst := m.module 101 offsets := &m.parent.offsets 102 opaque := m.opaque 103 104 binary.LittleEndian.PutUint64(opaque[offsets.ModuleInstanceOffset:], 105 uint64(uintptr(unsafe.Pointer(m.module))), 106 ) 107 108 if lm := offsets.LocalMemoryBegin; lm >= 0 { 109 putLocalMemory(opaque, lm, inst.MemoryInstance) 110 } 111 112 // Note: imported memory is resolved in ResolveImportedFunction. 113 114 // Note: imported functions are resolved in ResolveImportedFunction. 115 116 if globalOffset := offsets.GlobalsBegin; globalOffset >= 0 { 117 for i, g := range inst.Globals { 118 if i < int(inst.Source.ImportGlobalCount) { 119 importedME := g.Me.(*moduleEngine) 120 offset := importedME.parent.offsets.GlobalInstanceOffset(g.Index) 121 importedMEOpaque := importedME.opaque 122 binary.LittleEndian.PutUint64(opaque[globalOffset:], 123 uint64(uintptr(unsafe.Pointer(&importedMEOpaque[offset])))) 124 } else { 125 binary.LittleEndian.PutUint64(opaque[globalOffset:], g.Val) 126 binary.LittleEndian.PutUint64(opaque[globalOffset+8:], g.ValHi) 127 } 128 globalOffset += 16 129 } 130 } 131 132 if tableOffset := offsets.TablesBegin; tableOffset >= 0 { 133 // First we write the first element's address of typeIDs. 134 if len(inst.TypeIDs) > 0 { 135 binary.LittleEndian.PutUint64(opaque[offsets.TypeIDs1stElement:], uint64(uintptr(unsafe.Pointer(&inst.TypeIDs[0])))) 136 } 137 138 // Then we write the table addresses. 139 for _, table := range inst.Tables { 140 binary.LittleEndian.PutUint64(opaque[tableOffset:], uint64(uintptr(unsafe.Pointer(table)))) 141 tableOffset += 8 142 } 143 } 144 145 if beforeListenerOffset := offsets.BeforeListenerTrampolines1stElement; beforeListenerOffset >= 0 { 146 binary.LittleEndian.PutUint64(opaque[beforeListenerOffset:], uint64(uintptr(unsafe.Pointer(&m.parent.listenerBeforeTrampolines[0])))) 147 } 148 if afterListenerOffset := offsets.AfterListenerTrampolines1stElement; afterListenerOffset >= 0 { 149 binary.LittleEndian.PutUint64(opaque[afterListenerOffset:], uint64(uintptr(unsafe.Pointer(&m.parent.listenerAfterTrampolines[0])))) 150 } 151 if len(inst.DataInstances) > 0 { 152 binary.LittleEndian.PutUint64(opaque[offsets.DataInstances1stElement:], uint64(uintptr(unsafe.Pointer(&inst.DataInstances[0])))) 153 } 154 if len(inst.ElementInstances) > 0 { 155 binary.LittleEndian.PutUint64(opaque[offsets.ElementInstances1stElement:], uint64(uintptr(unsafe.Pointer(&inst.ElementInstances[0])))) 156 } 157 } 158 159 // NewFunction implements wasm.ModuleEngine. 160 func (m *moduleEngine) NewFunction(index wasm.Index) api.Function { 161 if wazevoapi.PrintMachineCodeHexPerFunctionDisassemblable { 162 panic("When PrintMachineCodeHexPerFunctionDisassemblable enabled, functions must not be called") 163 } 164 165 localIndex := index 166 if importedFnCount := m.module.Source.ImportFunctionCount; index < importedFnCount { 167 imported := &m.importedFunctions[index] 168 return imported.me.NewFunction(imported.indexInModule) 169 } else { 170 localIndex -= importedFnCount 171 } 172 173 src := m.module.Source 174 typIndex := src.FunctionSection[localIndex] 175 typ := src.TypeSection[typIndex] 176 sizeOfParamResultSlice := typ.ResultNumInUint64 177 if ps := typ.ParamNumInUint64; ps > sizeOfParamResultSlice { 178 sizeOfParamResultSlice = ps 179 } 180 p := m.parent 181 offset := p.functionOffsets[localIndex] 182 183 ce := &callEngine{ 184 indexInModule: index, 185 executable: &p.executable[offset], 186 parent: m, 187 preambleExecutable: &m.parent.entryPreambles[typIndex][0], 188 sizeOfParamResultSlice: sizeOfParamResultSlice, 189 requiredParams: typ.ParamNumInUint64, 190 numberOfResults: typ.ResultNumInUint64, 191 } 192 193 ce.execCtx.memoryGrowTrampolineAddress = &m.parent.sharedFunctions.memoryGrowExecutable[0] 194 ce.execCtx.stackGrowCallTrampolineAddress = &m.parent.sharedFunctions.stackGrowExecutable[0] 195 ce.execCtx.checkModuleExitCodeTrampolineAddress = &m.parent.sharedFunctions.checkModuleExitCode[0] 196 ce.execCtx.tableGrowTrampolineAddress = &m.parent.sharedFunctions.tableGrowExecutable[0] 197 ce.execCtx.refFuncTrampolineAddress = &m.parent.sharedFunctions.refFuncExecutable[0] 198 ce.execCtx.memoryWait32TrampolineAddress = &m.parent.sharedFunctions.memoryWait32Executable[0] 199 ce.execCtx.memoryWait64TrampolineAddress = &m.parent.sharedFunctions.memoryWait64Executable[0] 200 ce.execCtx.memoryNotifyTrampolineAddress = &m.parent.sharedFunctions.memoryNotifyExecutable[0] 201 ce.execCtx.memmoveAddress = memmovPtr 202 ce.init() 203 return ce 204 } 205 206 // GetGlobalValue implements the same method as documented on wasm.ModuleEngine. 207 func (m *moduleEngine) GetGlobalValue(i wasm.Index) (lo, hi uint64) { 208 offset := m.parent.offsets.GlobalInstanceOffset(i) 209 buf := m.opaque[offset:] 210 if i < m.module.Source.ImportGlobalCount { 211 panic("GetGlobalValue should not be called for imported globals") 212 } 213 return binary.LittleEndian.Uint64(buf), binary.LittleEndian.Uint64(buf[8:]) 214 } 215 216 // SetGlobalValue implements the same method as documented on wasm.ModuleEngine. 217 func (m *moduleEngine) SetGlobalValue(i wasm.Index, lo, hi uint64) { 218 offset := m.parent.offsets.GlobalInstanceOffset(i) 219 buf := m.opaque[offset:] 220 if i < m.module.Source.ImportGlobalCount { 221 panic("GetGlobalValue should not be called for imported globals") 222 } 223 binary.LittleEndian.PutUint64(buf, lo) 224 binary.LittleEndian.PutUint64(buf[8:], hi) 225 } 226 227 // OwnsGlobals implements the same method as documented on wasm.ModuleEngine. 228 func (m *moduleEngine) OwnsGlobals() bool { return true } 229 230 // ResolveImportedFunction implements wasm.ModuleEngine. 231 func (m *moduleEngine) ResolveImportedFunction(index, indexInImportedModule wasm.Index, importedModuleEngine wasm.ModuleEngine) { 232 executableOffset, moduleCtxOffset, typeIDOffset := m.parent.offsets.ImportedFunctionOffset(index) 233 importedME := importedModuleEngine.(*moduleEngine) 234 235 if int(indexInImportedModule) >= len(importedME.importedFunctions) { 236 indexInImportedModule -= wasm.Index(len(importedME.importedFunctions)) 237 } else { 238 imported := &importedME.importedFunctions[indexInImportedModule] 239 m.ResolveImportedFunction(index, imported.indexInModule, imported.me) 240 return // Recursively resolve the imported function. 241 } 242 243 offset := importedME.parent.functionOffsets[indexInImportedModule] 244 typeID := getTypeIDOf(indexInImportedModule, importedME.module) 245 executable := &importedME.parent.executable[offset] 246 // Write functionInstance. 247 binary.LittleEndian.PutUint64(m.opaque[executableOffset:], uint64(uintptr(unsafe.Pointer(executable)))) 248 binary.LittleEndian.PutUint64(m.opaque[moduleCtxOffset:], uint64(uintptr(unsafe.Pointer(importedME.opaquePtr)))) 249 binary.LittleEndian.PutUint64(m.opaque[typeIDOffset:], uint64(typeID)) 250 251 // Write importedFunction so that it can be used by NewFunction. 252 m.importedFunctions[index] = importedFunction{me: importedME, indexInModule: indexInImportedModule} 253 } 254 255 func getTypeIDOf(funcIndex wasm.Index, m *wasm.ModuleInstance) wasm.FunctionTypeID { 256 source := m.Source 257 258 var typeIndex wasm.Index 259 if funcIndex >= source.ImportFunctionCount { 260 funcIndex -= source.ImportFunctionCount 261 typeIndex = source.FunctionSection[funcIndex] 262 } else { 263 var cnt wasm.Index 264 for i := range source.ImportSection { 265 if source.ImportSection[i].Type == wasm.ExternTypeFunc { 266 if cnt == funcIndex { 267 typeIndex = source.ImportSection[i].DescFunc 268 break 269 } 270 cnt++ 271 } 272 } 273 } 274 return m.TypeIDs[typeIndex] 275 } 276 277 // ResolveImportedMemory implements wasm.ModuleEngine. 278 func (m *moduleEngine) ResolveImportedMemory(importedModuleEngine wasm.ModuleEngine) { 279 importedME := importedModuleEngine.(*moduleEngine) 280 inst := importedME.module 281 282 var memInstPtr uint64 283 var memOwnerOpaquePtr uint64 284 if offs := importedME.parent.offsets; offs.ImportedMemoryBegin >= 0 { 285 offset := offs.ImportedMemoryBegin 286 memInstPtr = binary.LittleEndian.Uint64(importedME.opaque[offset:]) 287 memOwnerOpaquePtr = binary.LittleEndian.Uint64(importedME.opaque[offset+8:]) 288 } else { 289 memInstPtr = uint64(uintptr(unsafe.Pointer(inst.MemoryInstance))) 290 memOwnerOpaquePtr = uint64(uintptr(unsafe.Pointer(importedME.opaquePtr))) 291 } 292 offset := m.parent.offsets.ImportedMemoryBegin 293 binary.LittleEndian.PutUint64(m.opaque[offset:], memInstPtr) 294 binary.LittleEndian.PutUint64(m.opaque[offset+8:], memOwnerOpaquePtr) 295 } 296 297 // DoneInstantiation implements wasm.ModuleEngine. 298 func (m *moduleEngine) DoneInstantiation() { 299 if !m.module.Source.IsHostModule { 300 m.setupOpaque() 301 } 302 } 303 304 // FunctionInstanceReference implements wasm.ModuleEngine. 305 func (m *moduleEngine) FunctionInstanceReference(funcIndex wasm.Index) wasm.Reference { 306 if funcIndex < m.module.Source.ImportFunctionCount { 307 begin, _, _ := m.parent.offsets.ImportedFunctionOffset(funcIndex) 308 return uintptr(unsafe.Pointer(&m.opaque[begin])) 309 } 310 localIndex := funcIndex - m.module.Source.ImportFunctionCount 311 p := m.parent 312 executable := &p.executable[p.functionOffsets[localIndex]] 313 typeID := m.module.TypeIDs[m.module.Source.FunctionSection[localIndex]] 314 315 lf := &functionInstance{ 316 executable: executable, 317 moduleContextOpaquePtr: m.opaquePtr, 318 typeID: typeID, 319 indexInModule: funcIndex, 320 } 321 m.localFunctionInstances = append(m.localFunctionInstances, lf) 322 return uintptr(unsafe.Pointer(lf)) 323 } 324 325 // LookupFunction implements wasm.ModuleEngine. 326 func (m *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.FunctionTypeID, tableOffset wasm.Index) (*wasm.ModuleInstance, wasm.Index) { 327 if tableOffset >= uint32(len(t.References)) || t.Type != wasm.RefTypeFuncref { 328 panic(wasmruntime.ErrRuntimeInvalidTableAccess) 329 } 330 rawPtr := t.References[tableOffset] 331 if rawPtr == 0 { 332 panic(wasmruntime.ErrRuntimeInvalidTableAccess) 333 } 334 335 tf := wazevoapi.PtrFromUintptr[functionInstance](rawPtr) 336 if tf.typeID != typeId { 337 panic(wasmruntime.ErrRuntimeIndirectCallTypeMismatch) 338 } 339 return moduleInstanceFromOpaquePtr(tf.moduleContextOpaquePtr), tf.indexInModule 340 } 341 342 func moduleInstanceFromOpaquePtr(ptr *byte) *wasm.ModuleInstance { 343 return *(**wasm.ModuleInstance)(unsafe.Pointer(ptr)) 344 }