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}