module_engine.go

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