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  }