github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/engine/wazevo/module_engine.go (about) 1 package wazevo 2 3 import ( 4 "encoding/binary" 5 "unsafe" 6 7 "github.com/bananabytelabs/wazero/api" 8 "github.com/bananabytelabs/wazero/experimental" 9 "github.com/bananabytelabs/wazero/internal/engine/wazevo/wazevoapi" 10 "github.com/bananabytelabs/wazero/internal/wasm" 11 "github.com/bananabytelabs/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 putLocalMemory(opaque []byte, offset wazevoapi.Offset, mem *wasm.MemoryInstance) { 78 s := uint64(len(mem.Buffer)) 79 var b uint64 80 if len(mem.Buffer) > 0 { 81 b = uint64(uintptr(unsafe.Pointer(&mem.Buffer[0]))) 82 } 83 binary.LittleEndian.PutUint64(opaque[offset:], b) 84 binary.LittleEndian.PutUint64(opaque[offset+8:], s) 85 } 86 87 func (m *moduleEngine) setupOpaque() { 88 inst := m.module 89 offsets := &m.parent.offsets 90 opaque := m.opaque 91 92 binary.LittleEndian.PutUint64(opaque[offsets.ModuleInstanceOffset:], 93 uint64(uintptr(unsafe.Pointer(m.module))), 94 ) 95 96 if lm := offsets.LocalMemoryBegin; lm >= 0 { 97 putLocalMemory(opaque, lm, inst.MemoryInstance) 98 } 99 100 // Note: imported memory is resolved in ResolveImportedFunction. 101 102 // Note: imported functions are resolved in ResolveImportedFunction. 103 104 if globalOffset := offsets.GlobalsBegin; globalOffset >= 0 { 105 for i, g := range inst.Globals { 106 if i < int(inst.Source.ImportGlobalCount) { 107 importedME := g.Me.(*moduleEngine) 108 offset := importedME.parent.offsets.GlobalInstanceOffset(g.Index) 109 importedMEOpaque := importedME.opaque 110 binary.LittleEndian.PutUint64(opaque[globalOffset:], 111 uint64(uintptr(unsafe.Pointer(&importedMEOpaque[offset])))) 112 } else { 113 binary.LittleEndian.PutUint64(opaque[globalOffset:], g.Val) 114 binary.LittleEndian.PutUint64(opaque[globalOffset+8:], g.ValHi) 115 } 116 globalOffset += 16 117 } 118 } 119 120 if tableOffset := offsets.TablesBegin; tableOffset >= 0 { 121 // First we write the first element's address of typeIDs. 122 if len(inst.TypeIDs) > 0 { 123 binary.LittleEndian.PutUint64(opaque[offsets.TypeIDs1stElement:], uint64(uintptr(unsafe.Pointer(&inst.TypeIDs[0])))) 124 } 125 126 // Then we write the table addresses. 127 for _, table := range inst.Tables { 128 binary.LittleEndian.PutUint64(opaque[tableOffset:], uint64(uintptr(unsafe.Pointer(table)))) 129 tableOffset += 8 130 } 131 } 132 133 if beforeListenerOffset := offsets.BeforeListenerTrampolines1stElement; beforeListenerOffset >= 0 { 134 binary.LittleEndian.PutUint64(opaque[beforeListenerOffset:], uint64(uintptr(unsafe.Pointer(&m.parent.listenerBeforeTrampolines[0])))) 135 } 136 if afterListenerOffset := offsets.AfterListenerTrampolines1stElement; afterListenerOffset >= 0 { 137 binary.LittleEndian.PutUint64(opaque[afterListenerOffset:], uint64(uintptr(unsafe.Pointer(&m.parent.listenerAfterTrampolines[0])))) 138 } 139 if len(inst.DataInstances) > 0 { 140 binary.LittleEndian.PutUint64(opaque[offsets.DataInstances1stElement:], uint64(uintptr(unsafe.Pointer(&inst.DataInstances[0])))) 141 } 142 if len(inst.ElementInstances) > 0 { 143 binary.LittleEndian.PutUint64(opaque[offsets.ElementInstances1stElement:], uint64(uintptr(unsafe.Pointer(&inst.ElementInstances[0])))) 144 } 145 } 146 147 // NewFunction implements wasm.ModuleEngine. 148 func (m *moduleEngine) NewFunction(index wasm.Index) api.Function { 149 if wazevoapi.PrintMachineCodeHexPerFunctionDisassemblable { 150 panic("When PrintMachineCodeHexPerFunctionDisassemblable enabled, functions must not be called") 151 } 152 153 localIndex := index 154 if importedFnCount := m.module.Source.ImportFunctionCount; index < importedFnCount { 155 imported := &m.importedFunctions[index] 156 return imported.me.NewFunction(imported.indexInModule) 157 } else { 158 localIndex -= importedFnCount 159 } 160 161 src := m.module.Source 162 typIndex := src.FunctionSection[localIndex] 163 typ := src.TypeSection[typIndex] 164 sizeOfParamResultSlice := typ.ResultNumInUint64 165 if ps := typ.ParamNumInUint64; ps > sizeOfParamResultSlice { 166 sizeOfParamResultSlice = ps 167 } 168 p := m.parent 169 offset := p.functionOffsets[localIndex] 170 171 ce := &callEngine{ 172 indexInModule: index, 173 executable: &p.executable[offset], 174 parent: m, 175 preambleExecutable: &m.parent.entryPreambles[typIndex][0], 176 sizeOfParamResultSlice: sizeOfParamResultSlice, 177 requiredParams: typ.ParamNumInUint64, 178 numberOfResults: typ.ResultNumInUint64, 179 } 180 181 ce.execCtx.memoryGrowTrampolineAddress = &m.parent.sharedFunctions.memoryGrowExecutable[0] 182 ce.execCtx.stackGrowCallTrampolineAddress = &m.parent.sharedFunctions.stackGrowExecutable[0] 183 ce.execCtx.checkModuleExitCodeTrampolineAddress = &m.parent.sharedFunctions.checkModuleExitCode[0] 184 ce.execCtx.tableGrowTrampolineAddress = &m.parent.sharedFunctions.tableGrowExecutable[0] 185 ce.execCtx.refFuncTrampolineAddress = &m.parent.sharedFunctions.refFuncExecutable[0] 186 ce.execCtx.memmoveAddress = memmovPtr 187 ce.init() 188 return ce 189 } 190 191 // GetGlobalValue implements the same method as documented on wasm.ModuleEngine. 192 func (m *moduleEngine) GetGlobalValue(i wasm.Index) (lo, hi uint64) { 193 offset := m.parent.offsets.GlobalInstanceOffset(i) 194 buf := m.opaque[offset:] 195 if i < m.module.Source.ImportGlobalCount { 196 panic("GetGlobalValue should not be called for imported globals") 197 } 198 return binary.LittleEndian.Uint64(buf), binary.LittleEndian.Uint64(buf[8:]) 199 } 200 201 // OwnsGlobals implements the same method as documented on wasm.ModuleEngine. 202 func (m *moduleEngine) OwnsGlobals() bool { return true } 203 204 // ResolveImportedFunction implements wasm.ModuleEngine. 205 func (m *moduleEngine) ResolveImportedFunction(index, indexInImportedModule wasm.Index, importedModuleEngine wasm.ModuleEngine) { 206 executableOffset, moduleCtxOffset, typeIDOffset := m.parent.offsets.ImportedFunctionOffset(index) 207 importedME := importedModuleEngine.(*moduleEngine) 208 209 if int(indexInImportedModule) >= len(importedME.importedFunctions) { 210 indexInImportedModule -= wasm.Index(len(importedME.importedFunctions)) 211 } else { 212 imported := &importedME.importedFunctions[indexInImportedModule] 213 m.ResolveImportedFunction(index, imported.indexInModule, imported.me) 214 return // Recursively resolve the imported function. 215 } 216 217 offset := importedME.parent.functionOffsets[indexInImportedModule] 218 typeID := getTypeIDOf(indexInImportedModule, importedME.module) 219 executable := &importedME.parent.executable[offset] 220 // Write functionInstance. 221 binary.LittleEndian.PutUint64(m.opaque[executableOffset:], uint64(uintptr(unsafe.Pointer(executable)))) 222 binary.LittleEndian.PutUint64(m.opaque[moduleCtxOffset:], uint64(uintptr(unsafe.Pointer(importedME.opaquePtr)))) 223 binary.LittleEndian.PutUint64(m.opaque[typeIDOffset:], uint64(typeID)) 224 225 // Write importedFunction so that it can be used by NewFunction. 226 m.importedFunctions[index] = importedFunction{me: importedME, indexInModule: indexInImportedModule} 227 } 228 229 func getTypeIDOf(funcIndex wasm.Index, m *wasm.ModuleInstance) wasm.FunctionTypeID { 230 source := m.Source 231 232 var typeIndex wasm.Index 233 if funcIndex >= source.ImportFunctionCount { 234 funcIndex -= source.ImportFunctionCount 235 typeIndex = source.FunctionSection[funcIndex] 236 } else { 237 var cnt wasm.Index 238 for i := range source.ImportSection { 239 if source.ImportSection[i].Type == wasm.ExternTypeFunc { 240 if cnt == funcIndex { 241 typeIndex = source.ImportSection[i].DescFunc 242 break 243 } 244 cnt++ 245 } 246 } 247 } 248 return m.TypeIDs[typeIndex] 249 } 250 251 // ResolveImportedMemory implements wasm.ModuleEngine. 252 func (m *moduleEngine) ResolveImportedMemory(importedModuleEngine wasm.ModuleEngine) { 253 importedME := importedModuleEngine.(*moduleEngine) 254 inst := importedME.module 255 256 var memInstPtr uint64 257 var memOwnerOpaquePtr uint64 258 if offs := importedME.parent.offsets; offs.ImportedMemoryBegin >= 0 { 259 offset := offs.ImportedMemoryBegin 260 memInstPtr = binary.LittleEndian.Uint64(importedME.opaque[offset:]) 261 memOwnerOpaquePtr = binary.LittleEndian.Uint64(importedME.opaque[offset+8:]) 262 } else { 263 memInstPtr = uint64(uintptr(unsafe.Pointer(inst.MemoryInstance))) 264 memOwnerOpaquePtr = uint64(uintptr(unsafe.Pointer(importedME.opaquePtr))) 265 } 266 offset := m.parent.offsets.ImportedMemoryBegin 267 binary.LittleEndian.PutUint64(m.opaque[offset:], memInstPtr) 268 binary.LittleEndian.PutUint64(m.opaque[offset+8:], memOwnerOpaquePtr) 269 } 270 271 // DoneInstantiation implements wasm.ModuleEngine. 272 func (m *moduleEngine) DoneInstantiation() { 273 if !m.module.Source.IsHostModule { 274 m.setupOpaque() 275 } 276 } 277 278 // FunctionInstanceReference implements wasm.ModuleEngine. 279 func (m *moduleEngine) FunctionInstanceReference(funcIndex wasm.Index) wasm.Reference { 280 if funcIndex < m.module.Source.ImportFunctionCount { 281 begin, _, _ := m.parent.offsets.ImportedFunctionOffset(funcIndex) 282 return uintptr(unsafe.Pointer(&m.opaque[begin])) 283 } 284 localIndex := funcIndex - m.module.Source.ImportFunctionCount 285 p := m.parent 286 executable := &p.executable[p.functionOffsets[localIndex]] 287 typeID := m.module.TypeIDs[m.module.Source.FunctionSection[localIndex]] 288 289 lf := &functionInstance{ 290 executable: executable, 291 moduleContextOpaquePtr: m.opaquePtr, 292 typeID: typeID, 293 indexInModule: funcIndex, 294 } 295 m.localFunctionInstances = append(m.localFunctionInstances, lf) 296 return uintptr(unsafe.Pointer(lf)) 297 } 298 299 // LookupFunction implements wasm.ModuleEngine. 300 func (m *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.FunctionTypeID, tableOffset wasm.Index) (*wasm.ModuleInstance, wasm.Index) { 301 if tableOffset >= uint32(len(t.References)) || t.Type != wasm.RefTypeFuncref { 302 panic(wasmruntime.ErrRuntimeInvalidTableAccess) 303 } 304 rawPtr := t.References[tableOffset] 305 if rawPtr == 0 { 306 panic(wasmruntime.ErrRuntimeInvalidTableAccess) 307 } 308 309 tf := functionFromUintptr(rawPtr) 310 if tf.typeID != typeId { 311 panic(wasmruntime.ErrRuntimeIndirectCallTypeMismatch) 312 } 313 return moduleInstanceFromOpaquePtr(tf.moduleContextOpaquePtr), tf.indexInModule 314 } 315 316 // functionFromUintptr resurrects the original *function from the given uintptr 317 // which comes from either funcref table or OpcodeRefFunc instruction. 318 func functionFromUintptr(ptr uintptr) *functionInstance { 319 // Wraps ptrs as the double pointer in order to avoid the unsafe access as detected by race detector. 320 // 321 // For example, if we have (*function)(unsafe.Pointer(ptr)) instead, then the race detector's "checkptr" 322 // subroutine wanrs as "checkptr: pointer arithmetic result points to invalid allocation" 323 // https://github.com/golang/go/blob/1ce7fcf139417d618c2730010ede2afb41664211/src/runtime/checkptr.go#L69 324 var wrapped *uintptr = &ptr 325 return *(**functionInstance)(unsafe.Pointer(wrapped)) 326 } 327 328 func moduleInstanceFromOpaquePtr(ptr *byte) *wasm.ModuleInstance { 329 return *(**wasm.ModuleInstance)(unsafe.Pointer(ptr)) 330 }