github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/engine/wazevo/module_engine.go (about)

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