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  }