github.com/tetratelabs/wazero@v1.7.1/internal/engine/wazevo/engine.go (about)

     1  package wazevo
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"errors"
     7  	"fmt"
     8  	"runtime"
     9  	"sort"
    10  	"sync"
    11  	"unsafe"
    12  
    13  	"github.com/tetratelabs/wazero/api"
    14  	"github.com/tetratelabs/wazero/experimental"
    15  	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
    16  	"github.com/tetratelabs/wazero/internal/engine/wazevo/frontend"
    17  	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
    18  	"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
    19  	"github.com/tetratelabs/wazero/internal/filecache"
    20  	"github.com/tetratelabs/wazero/internal/platform"
    21  	"github.com/tetratelabs/wazero/internal/version"
    22  	"github.com/tetratelabs/wazero/internal/wasm"
    23  )
    24  
    25  type (
    26  	// engine implements wasm.Engine.
    27  	engine struct {
    28  		wazeroVersion   string
    29  		fileCache       filecache.Cache
    30  		compiledModules map[wasm.ModuleID]*compiledModule
    31  		// sortedCompiledModules is a list of compiled modules sorted by the initial address of the executable.
    32  		sortedCompiledModules []*compiledModule
    33  		mux                   sync.RWMutex
    34  		// rels is a list of relocations to be resolved. This is reused for each compilation to avoid allocation.
    35  		rels []backend.RelocationInfo
    36  		// refToBinaryOffset is reused for each compilation to avoid allocation.
    37  		refToBinaryOffset map[ssa.FuncRef]int
    38  		// sharedFunctions is compiled functions shared by all modules.
    39  		sharedFunctions *sharedFunctions
    40  		// setFinalizer defaults to runtime.SetFinalizer, but overridable for tests.
    41  		setFinalizer func(obj interface{}, finalizer interface{})
    42  
    43  		// The followings are reused for compiling shared functions.
    44  		machine backend.Machine
    45  		be      backend.Compiler
    46  	}
    47  
    48  	sharedFunctions struct {
    49  		// memoryGrowExecutable is a compiled trampoline executable for memory.grow builtin function.
    50  		memoryGrowExecutable []byte
    51  		// checkModuleExitCode is a compiled trampoline executable for checking module instance exit code. This
    52  		// is used when ensureTermination is true.
    53  		checkModuleExitCode []byte
    54  		// stackGrowExecutable is a compiled executable for growing stack builtin function.
    55  		stackGrowExecutable []byte
    56  		// tableGrowExecutable is a compiled trampoline executable for table.grow builtin function.
    57  		tableGrowExecutable []byte
    58  		// refFuncExecutable is a compiled trampoline executable for ref.func builtin function.
    59  		refFuncExecutable []byte
    60  		// memoryWait32Executable is a compiled trampoline executable for memory.wait32 builtin function
    61  		memoryWait32Executable []byte
    62  		// memoryWait64Executable is a compiled trampoline executable for memory.wait64 builtin function
    63  		memoryWait64Executable []byte
    64  		// memoryNotifyExecutable is a compiled trampoline executable for memory.notify builtin function
    65  		memoryNotifyExecutable    []byte
    66  		listenerBeforeTrampolines map[*wasm.FunctionType][]byte
    67  		listenerAfterTrampolines  map[*wasm.FunctionType][]byte
    68  	}
    69  
    70  	// compiledModule is a compiled variant of a wasm.Module and ready to be used for instantiation.
    71  	compiledModule struct {
    72  		*executables
    73  		// functionOffsets maps a local function index to the offset in the executable.
    74  		functionOffsets           []int
    75  		parent                    *engine
    76  		module                    *wasm.Module
    77  		ensureTermination         bool
    78  		listeners                 []experimental.FunctionListener
    79  		listenerBeforeTrampolines []*byte
    80  		listenerAfterTrampolines  []*byte
    81  
    82  		// The followings are only available for non host modules.
    83  
    84  		offsets         wazevoapi.ModuleContextOffsetData
    85  		sharedFunctions *sharedFunctions
    86  		sourceMap       sourceMap
    87  	}
    88  
    89  	executables struct {
    90  		executable     []byte
    91  		entryPreambles [][]byte
    92  	}
    93  )
    94  
    95  // sourceMap is a mapping from the offset of the executable to the offset of the original wasm binary.
    96  type sourceMap struct {
    97  	// executableOffsets is a sorted list of offsets of the executable. This is index-correlated with wasmBinaryOffsets,
    98  	// in other words executableOffsets[i] is the offset of the executable which corresponds to the offset of a Wasm
    99  	// binary pointed by wasmBinaryOffsets[i].
   100  	executableOffsets []uintptr
   101  	// wasmBinaryOffsets is the counterpart of executableOffsets.
   102  	wasmBinaryOffsets []uint64
   103  }
   104  
   105  var _ wasm.Engine = (*engine)(nil)
   106  
   107  // NewEngine returns the implementation of wasm.Engine.
   108  func NewEngine(ctx context.Context, _ api.CoreFeatures, fc filecache.Cache) wasm.Engine {
   109  	machine := newMachine()
   110  	be := backend.NewCompiler(ctx, machine, ssa.NewBuilder())
   111  	e := &engine{
   112  		compiledModules: make(map[wasm.ModuleID]*compiledModule), refToBinaryOffset: make(map[ssa.FuncRef]int),
   113  		setFinalizer:  runtime.SetFinalizer,
   114  		machine:       machine,
   115  		be:            be,
   116  		fileCache:     fc,
   117  		wazeroVersion: version.GetWazeroVersion(),
   118  	}
   119  	e.compileSharedFunctions()
   120  	return e
   121  }
   122  
   123  // CompileModule implements wasm.Engine.
   124  func (e *engine) CompileModule(ctx context.Context, module *wasm.Module, listeners []experimental.FunctionListener, ensureTermination bool) (err error) {
   125  	if wazevoapi.PerfMapEnabled {
   126  		wazevoapi.PerfMap.Lock()
   127  		defer wazevoapi.PerfMap.Unlock()
   128  	}
   129  
   130  	if _, ok, err := e.getCompiledModule(module, listeners, ensureTermination); ok { // cache hit!
   131  		return nil
   132  	} else if err != nil {
   133  		return err
   134  	}
   135  
   136  	if wazevoapi.DeterministicCompilationVerifierEnabled {
   137  		ctx = wazevoapi.NewDeterministicCompilationVerifierContext(ctx, len(module.CodeSection))
   138  	}
   139  	cm, err := e.compileModule(ctx, module, listeners, ensureTermination)
   140  	if err != nil {
   141  		return err
   142  	}
   143  	if err = e.addCompiledModule(module, cm); err != nil {
   144  		return err
   145  	}
   146  
   147  	if wazevoapi.DeterministicCompilationVerifierEnabled {
   148  		for i := 0; i < wazevoapi.DeterministicCompilationVerifyingIter; i++ {
   149  			_, err := e.compileModule(ctx, module, listeners, ensureTermination)
   150  			if err != nil {
   151  				return err
   152  			}
   153  		}
   154  	}
   155  
   156  	if len(listeners) > 0 {
   157  		cm.listeners = listeners
   158  		cm.listenerBeforeTrampolines = make([]*byte, len(module.TypeSection))
   159  		cm.listenerAfterTrampolines = make([]*byte, len(module.TypeSection))
   160  		for i := range module.TypeSection {
   161  			typ := &module.TypeSection[i]
   162  			before, after := e.getListenerTrampolineForType(typ)
   163  			cm.listenerBeforeTrampolines[i] = before
   164  			cm.listenerAfterTrampolines[i] = after
   165  		}
   166  	}
   167  	return nil
   168  }
   169  
   170  func (exec *executables) compileEntryPreambles(m *wasm.Module, machine backend.Machine, be backend.Compiler) {
   171  	exec.entryPreambles = make([][]byte, len(m.TypeSection))
   172  	for i := range m.TypeSection {
   173  		typ := &m.TypeSection[i]
   174  		sig := frontend.SignatureForWasmFunctionType(typ)
   175  		be.Init()
   176  		buf := machine.CompileEntryPreamble(&sig)
   177  		executable := mmapExecutable(buf)
   178  		exec.entryPreambles[i] = executable
   179  
   180  		if wazevoapi.PerfMapEnabled {
   181  			wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(&executable[0])),
   182  				uint64(len(executable)), fmt.Sprintf("entry_preamble::type=%s", typ.String()))
   183  		}
   184  	}
   185  }
   186  
   187  func (e *engine) compileModule(ctx context.Context, module *wasm.Module, listeners []experimental.FunctionListener, ensureTermination bool) (*compiledModule, error) {
   188  	withListener := len(listeners) > 0
   189  	e.rels = e.rels[:0]
   190  	cm := &compiledModule{
   191  		offsets: wazevoapi.NewModuleContextOffsetData(module, withListener), parent: e, module: module,
   192  		ensureTermination: ensureTermination,
   193  		executables:       &executables{},
   194  	}
   195  
   196  	if module.IsHostModule {
   197  		return e.compileHostModule(ctx, module, listeners)
   198  	}
   199  
   200  	importedFns, localFns := int(module.ImportFunctionCount), len(module.FunctionSection)
   201  	if localFns == 0 {
   202  		return cm, nil
   203  	}
   204  
   205  	if wazevoapi.DeterministicCompilationVerifierEnabled {
   206  		// The compilation must be deterministic regardless of the order of functions being compiled.
   207  		wazevoapi.DeterministicCompilationVerifierRandomizeIndexes(ctx)
   208  	}
   209  
   210  	needSourceInfo := module.DWARFLines != nil
   211  
   212  	// Creates new compiler instances which are reused for each function.
   213  	ssaBuilder := ssa.NewBuilder()
   214  	fe := frontend.NewFrontendCompiler(module, ssaBuilder, &cm.offsets, ensureTermination, withListener, needSourceInfo)
   215  	machine := newMachine()
   216  	be := backend.NewCompiler(ctx, machine, ssaBuilder)
   217  
   218  	cm.executables.compileEntryPreambles(module, machine, be)
   219  
   220  	totalSize := 0 // Total binary size of the executable.
   221  	cm.functionOffsets = make([]int, localFns)
   222  	bodies := make([][]byte, localFns)
   223  
   224  	// Trampoline relocation related variables.
   225  	trampolineInterval, callTrampolineIslandSize, err := machine.CallTrampolineIslandInfo(localFns)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  	needCallTrampoline := callTrampolineIslandSize > 0
   230  	var callTrampolineIslandOffsets []int // Holds the offsets of trampoline islands.
   231  
   232  	for i := range module.CodeSection {
   233  		if wazevoapi.DeterministicCompilationVerifierEnabled {
   234  			i = wazevoapi.DeterministicCompilationVerifierGetRandomizedLocalFunctionIndex(ctx, i)
   235  		}
   236  
   237  		fidx := wasm.Index(i + importedFns)
   238  
   239  		if wazevoapi.NeedFunctionNameInContext {
   240  			def := module.FunctionDefinition(fidx)
   241  			name := def.DebugName()
   242  			if len(def.ExportNames()) > 0 {
   243  				name = def.ExportNames()[0]
   244  			}
   245  			ctx = wazevoapi.SetCurrentFunctionName(ctx, i, fmt.Sprintf("[%d/%d]%s", i, len(module.CodeSection)-1, name))
   246  		}
   247  
   248  		needListener := len(listeners) > 0 && listeners[i] != nil
   249  		body, rels, err := e.compileLocalWasmFunction(ctx, module, wasm.Index(i), fe, ssaBuilder, be, needListener)
   250  		if err != nil {
   251  			return nil, fmt.Errorf("compile function %d/%d: %v", i, len(module.CodeSection)-1, err)
   252  		}
   253  
   254  		// Align 16-bytes boundary.
   255  		totalSize = (totalSize + 15) &^ 15
   256  		cm.functionOffsets[i] = totalSize
   257  
   258  		if needSourceInfo {
   259  			// At the beginning of the function, we add the offset of the function body so that
   260  			// we can resolve the source location of the call site of before listener call.
   261  			cm.sourceMap.executableOffsets = append(cm.sourceMap.executableOffsets, uintptr(totalSize))
   262  			cm.sourceMap.wasmBinaryOffsets = append(cm.sourceMap.wasmBinaryOffsets, module.CodeSection[i].BodyOffsetInCodeSection)
   263  
   264  			for _, info := range be.SourceOffsetInfo() {
   265  				cm.sourceMap.executableOffsets = append(cm.sourceMap.executableOffsets, uintptr(totalSize)+uintptr(info.ExecutableOffset))
   266  				cm.sourceMap.wasmBinaryOffsets = append(cm.sourceMap.wasmBinaryOffsets, uint64(info.SourceOffset))
   267  			}
   268  		}
   269  
   270  		fref := frontend.FunctionIndexToFuncRef(fidx)
   271  		e.refToBinaryOffset[fref] = totalSize
   272  
   273  		// At this point, relocation offsets are relative to the start of the function body,
   274  		// so we adjust it to the start of the executable.
   275  		for _, r := range rels {
   276  			r.Offset += int64(totalSize)
   277  			e.rels = append(e.rels, r)
   278  		}
   279  
   280  		bodies[i] = body
   281  		totalSize += len(body)
   282  		if wazevoapi.PrintMachineCodeHexPerFunction {
   283  			fmt.Printf("[[[machine code for %s]]]\n%s\n\n", wazevoapi.GetCurrentFunctionName(ctx), hex.EncodeToString(body))
   284  		}
   285  
   286  		if needCallTrampoline {
   287  			// If the total size exceeds the trampoline interval, we need to add a trampoline island.
   288  			if totalSize/trampolineInterval > len(callTrampolineIslandOffsets) {
   289  				callTrampolineIslandOffsets = append(callTrampolineIslandOffsets, totalSize)
   290  				totalSize += callTrampolineIslandSize
   291  			}
   292  		}
   293  	}
   294  
   295  	// Allocate executable memory and then copy the generated machine code.
   296  	executable, err := platform.MmapCodeSegment(totalSize)
   297  	if err != nil {
   298  		panic(err)
   299  	}
   300  	cm.executable = executable
   301  
   302  	for i, b := range bodies {
   303  		offset := cm.functionOffsets[i]
   304  		copy(executable[offset:], b)
   305  	}
   306  
   307  	if wazevoapi.PerfMapEnabled {
   308  		wazevoapi.PerfMap.Flush(uintptr(unsafe.Pointer(&executable[0])), cm.functionOffsets)
   309  	}
   310  
   311  	if needSourceInfo {
   312  		for i := range cm.sourceMap.executableOffsets {
   313  			cm.sourceMap.executableOffsets[i] += uintptr(unsafe.Pointer(&cm.executable[0]))
   314  		}
   315  	}
   316  
   317  	// Resolve relocations for local function calls.
   318  	if len(e.rels) > 0 {
   319  		machine.ResolveRelocations(e.refToBinaryOffset, executable, e.rels, callTrampolineIslandOffsets)
   320  	}
   321  
   322  	if runtime.GOARCH == "arm64" {
   323  		// On arm64, we cannot give all of rwx at the same time, so we change it to exec.
   324  		if err = platform.MprotectRX(executable); err != nil {
   325  			return nil, err
   326  		}
   327  	}
   328  	cm.sharedFunctions = e.sharedFunctions
   329  	e.setFinalizer(cm.executables, executablesFinalizer)
   330  	return cm, nil
   331  }
   332  
   333  func (e *engine) compileLocalWasmFunction(
   334  	ctx context.Context,
   335  	module *wasm.Module,
   336  	localFunctionIndex wasm.Index,
   337  	fe *frontend.Compiler,
   338  	ssaBuilder ssa.Builder,
   339  	be backend.Compiler,
   340  	needListener bool,
   341  ) (body []byte, rels []backend.RelocationInfo, err error) {
   342  	typIndex := module.FunctionSection[localFunctionIndex]
   343  	typ := &module.TypeSection[typIndex]
   344  	codeSeg := &module.CodeSection[localFunctionIndex]
   345  
   346  	// Initializes both frontend and backend compilers.
   347  	fe.Init(localFunctionIndex, typIndex, typ, codeSeg.LocalTypes, codeSeg.Body, needListener, codeSeg.BodyOffsetInCodeSection)
   348  	be.Init()
   349  
   350  	// Lower Wasm to SSA.
   351  	fe.LowerToSSA()
   352  	if wazevoapi.PrintSSA && wazevoapi.PrintEnabledIndex(ctx) {
   353  		fmt.Printf("[[[SSA for %s]]]%s\n", wazevoapi.GetCurrentFunctionName(ctx), ssaBuilder.Format())
   354  	}
   355  
   356  	if wazevoapi.DeterministicCompilationVerifierEnabled {
   357  		wazevoapi.VerifyOrSetDeterministicCompilationContextValue(ctx, "SSA", ssaBuilder.Format())
   358  	}
   359  
   360  	// Run SSA-level optimization passes.
   361  	ssaBuilder.RunPasses()
   362  
   363  	if wazevoapi.PrintOptimizedSSA && wazevoapi.PrintEnabledIndex(ctx) {
   364  		fmt.Printf("[[[Optimized SSA for %s]]]%s\n", wazevoapi.GetCurrentFunctionName(ctx), ssaBuilder.Format())
   365  	}
   366  
   367  	if wazevoapi.DeterministicCompilationVerifierEnabled {
   368  		wazevoapi.VerifyOrSetDeterministicCompilationContextValue(ctx, "Optimized SSA", ssaBuilder.Format())
   369  	}
   370  
   371  	// Now our ssaBuilder contains the necessary information to further lower them to
   372  	// machine code.
   373  	original, rels, err := be.Compile(ctx)
   374  	if err != nil {
   375  		return nil, nil, fmt.Errorf("ssa->machine code: %v", err)
   376  	}
   377  
   378  	// TODO: optimize as zero copy.
   379  	copied := make([]byte, len(original))
   380  	copy(copied, original)
   381  	return copied, rels, nil
   382  }
   383  
   384  func (e *engine) compileHostModule(ctx context.Context, module *wasm.Module, listeners []experimental.FunctionListener) (*compiledModule, error) {
   385  	machine := newMachine()
   386  	be := backend.NewCompiler(ctx, machine, ssa.NewBuilder())
   387  
   388  	num := len(module.CodeSection)
   389  	cm := &compiledModule{module: module, listeners: listeners, executables: &executables{}}
   390  	cm.functionOffsets = make([]int, num)
   391  	totalSize := 0 // Total binary size of the executable.
   392  	bodies := make([][]byte, num)
   393  	var sig ssa.Signature
   394  	for i := range module.CodeSection {
   395  		totalSize = (totalSize + 15) &^ 15
   396  		cm.functionOffsets[i] = totalSize
   397  
   398  		typIndex := module.FunctionSection[i]
   399  		typ := &module.TypeSection[typIndex]
   400  
   401  		// We can relax until the index fits together in ExitCode as we do in wazevoapi.ExitCodeCallGoModuleFunctionWithIndex.
   402  		// However, 1 << 16 should be large enough for a real use case.
   403  		const hostFunctionNumMaximum = 1 << 16
   404  		if i >= hostFunctionNumMaximum {
   405  			return nil, fmt.Errorf("too many host functions (maximum %d)", hostFunctionNumMaximum)
   406  		}
   407  
   408  		sig.ID = ssa.SignatureID(typIndex) // This is important since we reuse the `machine` which caches the ABI based on the SignatureID.
   409  		sig.Params = append(sig.Params[:0],
   410  			ssa.TypeI64, // First argument must be exec context.
   411  			ssa.TypeI64, // The second argument is the moduleContextOpaque of this host module.
   412  		)
   413  		for _, t := range typ.Params {
   414  			sig.Params = append(sig.Params, frontend.WasmTypeToSSAType(t))
   415  		}
   416  
   417  		sig.Results = sig.Results[:0]
   418  		for _, t := range typ.Results {
   419  			sig.Results = append(sig.Results, frontend.WasmTypeToSSAType(t))
   420  		}
   421  
   422  		c := &module.CodeSection[i]
   423  		if c.GoFunc == nil {
   424  			panic("BUG: GoFunc must be set for host module")
   425  		}
   426  
   427  		withListener := len(listeners) > 0 && listeners[i] != nil
   428  		var exitCode wazevoapi.ExitCode
   429  		fn := c.GoFunc
   430  		switch fn.(type) {
   431  		case api.GoModuleFunction:
   432  			exitCode = wazevoapi.ExitCodeCallGoModuleFunctionWithIndex(i, withListener)
   433  		case api.GoFunction:
   434  			exitCode = wazevoapi.ExitCodeCallGoFunctionWithIndex(i, withListener)
   435  		}
   436  
   437  		be.Init()
   438  		machine.CompileGoFunctionTrampoline(exitCode, &sig, true)
   439  		if err := be.Finalize(ctx); err != nil {
   440  			return nil, err
   441  		}
   442  		body := be.Buf()
   443  
   444  		if wazevoapi.PerfMapEnabled {
   445  			name := module.FunctionDefinition(wasm.Index(i)).DebugName()
   446  			wazevoapi.PerfMap.AddModuleEntry(i,
   447  				int64(totalSize),
   448  				uint64(len(body)),
   449  				fmt.Sprintf("trampoline:%s", name))
   450  		}
   451  
   452  		// TODO: optimize as zero copy.
   453  		copied := make([]byte, len(body))
   454  		copy(copied, body)
   455  		bodies[i] = copied
   456  		totalSize += len(body)
   457  	}
   458  
   459  	if totalSize == 0 {
   460  		// Empty module.
   461  		return cm, nil
   462  	}
   463  
   464  	// Allocate executable memory and then copy the generated machine code.
   465  	executable, err := platform.MmapCodeSegment(totalSize)
   466  	if err != nil {
   467  		panic(err)
   468  	}
   469  	cm.executable = executable
   470  
   471  	for i, b := range bodies {
   472  		offset := cm.functionOffsets[i]
   473  		copy(executable[offset:], b)
   474  	}
   475  
   476  	if wazevoapi.PerfMapEnabled {
   477  		wazevoapi.PerfMap.Flush(uintptr(unsafe.Pointer(&executable[0])), cm.functionOffsets)
   478  	}
   479  
   480  	if runtime.GOARCH == "arm64" {
   481  		// On arm64, we cannot give all of rwx at the same time, so we change it to exec.
   482  		if err = platform.MprotectRX(executable); err != nil {
   483  			return nil, err
   484  		}
   485  	}
   486  	e.setFinalizer(cm.executables, executablesFinalizer)
   487  	return cm, nil
   488  }
   489  
   490  // Close implements wasm.Engine.
   491  func (e *engine) Close() (err error) {
   492  	e.mux.Lock()
   493  	defer e.mux.Unlock()
   494  	e.sortedCompiledModules = nil
   495  	e.compiledModules = nil
   496  	e.sharedFunctions = nil
   497  	return nil
   498  }
   499  
   500  // CompiledModuleCount implements wasm.Engine.
   501  func (e *engine) CompiledModuleCount() uint32 {
   502  	e.mux.RLock()
   503  	defer e.mux.RUnlock()
   504  	return uint32(len(e.compiledModules))
   505  }
   506  
   507  // DeleteCompiledModule implements wasm.Engine.
   508  func (e *engine) DeleteCompiledModule(m *wasm.Module) {
   509  	e.mux.Lock()
   510  	defer e.mux.Unlock()
   511  	cm, ok := e.compiledModules[m.ID]
   512  	if ok {
   513  		if len(cm.executable) > 0 {
   514  			e.deleteCompiledModuleFromSortedList(cm)
   515  		}
   516  		delete(e.compiledModules, m.ID)
   517  	}
   518  }
   519  
   520  func (e *engine) addCompiledModuleToSortedList(cm *compiledModule) {
   521  	ptr := uintptr(unsafe.Pointer(&cm.executable[0]))
   522  
   523  	index := sort.Search(len(e.sortedCompiledModules), func(i int) bool {
   524  		return uintptr(unsafe.Pointer(&e.sortedCompiledModules[i].executable[0])) >= ptr
   525  	})
   526  	e.sortedCompiledModules = append(e.sortedCompiledModules, nil)
   527  	copy(e.sortedCompiledModules[index+1:], e.sortedCompiledModules[index:])
   528  	e.sortedCompiledModules[index] = cm
   529  }
   530  
   531  func (e *engine) deleteCompiledModuleFromSortedList(cm *compiledModule) {
   532  	ptr := uintptr(unsafe.Pointer(&cm.executable[0]))
   533  
   534  	index := sort.Search(len(e.sortedCompiledModules), func(i int) bool {
   535  		return uintptr(unsafe.Pointer(&e.sortedCompiledModules[i].executable[0])) >= ptr
   536  	})
   537  	if index >= len(e.sortedCompiledModules) {
   538  		return
   539  	}
   540  	copy(e.sortedCompiledModules[index:], e.sortedCompiledModules[index+1:])
   541  	e.sortedCompiledModules = e.sortedCompiledModules[:len(e.sortedCompiledModules)-1]
   542  }
   543  
   544  func (e *engine) compiledModuleOfAddr(addr uintptr) *compiledModule {
   545  	e.mux.RLock()
   546  	defer e.mux.RUnlock()
   547  
   548  	index := sort.Search(len(e.sortedCompiledModules), func(i int) bool {
   549  		return uintptr(unsafe.Pointer(&e.sortedCompiledModules[i].executable[0])) > addr
   550  	})
   551  	index -= 1
   552  	if index < 0 {
   553  		return nil
   554  	}
   555  	candidate := e.sortedCompiledModules[index]
   556  	if checkAddrInBytes(addr, candidate.executable) {
   557  		// If a module is already deleted, the found module may have been wrong.
   558  		return candidate
   559  	}
   560  	return nil
   561  }
   562  
   563  func checkAddrInBytes(addr uintptr, b []byte) bool {
   564  	return uintptr(unsafe.Pointer(&b[0])) <= addr && addr <= uintptr(unsafe.Pointer(&b[len(b)-1]))
   565  }
   566  
   567  // NewModuleEngine implements wasm.Engine.
   568  func (e *engine) NewModuleEngine(m *wasm.Module, mi *wasm.ModuleInstance) (wasm.ModuleEngine, error) {
   569  	me := &moduleEngine{}
   570  
   571  	// Note: imported functions are resolved in moduleEngine.ResolveImportedFunction.
   572  	me.importedFunctions = make([]importedFunction, m.ImportFunctionCount)
   573  
   574  	compiled, ok := e.compiledModules[m.ID]
   575  	if !ok {
   576  		return nil, errors.New("source module must be compiled before instantiation")
   577  	}
   578  	me.parent = compiled
   579  	me.module = mi
   580  	me.listeners = compiled.listeners
   581  
   582  	if m.IsHostModule {
   583  		me.opaque = buildHostModuleOpaque(m, compiled.listeners)
   584  		me.opaquePtr = &me.opaque[0]
   585  	} else {
   586  		if size := compiled.offsets.TotalSize; size != 0 {
   587  			opaque := newAlignedOpaque(size)
   588  			me.opaque = opaque
   589  			me.opaquePtr = &opaque[0]
   590  		}
   591  	}
   592  	return me, nil
   593  }
   594  
   595  func (e *engine) compileSharedFunctions() {
   596  	e.sharedFunctions = &sharedFunctions{
   597  		listenerBeforeTrampolines: make(map[*wasm.FunctionType][]byte),
   598  		listenerAfterTrampolines:  make(map[*wasm.FunctionType][]byte),
   599  	}
   600  
   601  	e.be.Init()
   602  	{
   603  		src := e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeGrowMemory, &ssa.Signature{
   604  			Params:  []ssa.Type{ssa.TypeI64 /* exec context */, ssa.TypeI32},
   605  			Results: []ssa.Type{ssa.TypeI32},
   606  		}, false)
   607  		e.sharedFunctions.memoryGrowExecutable = mmapExecutable(src)
   608  		if wazevoapi.PerfMapEnabled {
   609  			exe := e.sharedFunctions.memoryGrowExecutable
   610  			wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(&exe[0])), uint64(len(exe)), "memory_grow_trampoline")
   611  		}
   612  	}
   613  
   614  	e.be.Init()
   615  	{
   616  		src := e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeTableGrow, &ssa.Signature{
   617  			Params:  []ssa.Type{ssa.TypeI64 /* exec context */, ssa.TypeI32 /* table index */, ssa.TypeI32 /* num */, ssa.TypeI64 /* ref */},
   618  			Results: []ssa.Type{ssa.TypeI32},
   619  		}, false)
   620  		e.sharedFunctions.tableGrowExecutable = mmapExecutable(src)
   621  		if wazevoapi.PerfMapEnabled {
   622  			exe := e.sharedFunctions.tableGrowExecutable
   623  			wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(&exe[0])), uint64(len(exe)), "table_grow_trampoline")
   624  		}
   625  	}
   626  
   627  	e.be.Init()
   628  	{
   629  		src := e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeCheckModuleExitCode, &ssa.Signature{
   630  			Params:  []ssa.Type{ssa.TypeI32 /* exec context */},
   631  			Results: []ssa.Type{ssa.TypeI32},
   632  		}, false)
   633  		e.sharedFunctions.checkModuleExitCode = mmapExecutable(src)
   634  		if wazevoapi.PerfMapEnabled {
   635  			exe := e.sharedFunctions.checkModuleExitCode
   636  			wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(&exe[0])), uint64(len(exe)), "check_module_exit_code_trampoline")
   637  		}
   638  	}
   639  
   640  	e.be.Init()
   641  	{
   642  		src := e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeRefFunc, &ssa.Signature{
   643  			Params:  []ssa.Type{ssa.TypeI64 /* exec context */, ssa.TypeI32 /* function index */},
   644  			Results: []ssa.Type{ssa.TypeI64}, // returns the function reference.
   645  		}, false)
   646  		e.sharedFunctions.refFuncExecutable = mmapExecutable(src)
   647  		if wazevoapi.PerfMapEnabled {
   648  			exe := e.sharedFunctions.refFuncExecutable
   649  			wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(&exe[0])), uint64(len(exe)), "ref_func_trampoline")
   650  		}
   651  	}
   652  
   653  	e.be.Init()
   654  	{
   655  		src := e.machine.CompileStackGrowCallSequence()
   656  		e.sharedFunctions.stackGrowExecutable = mmapExecutable(src)
   657  		if wazevoapi.PerfMapEnabled {
   658  			exe := e.sharedFunctions.stackGrowExecutable
   659  			wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(&exe[0])), uint64(len(exe)), "stack_grow_trampoline")
   660  		}
   661  	}
   662  
   663  	e.be.Init()
   664  	{
   665  		src := e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeMemoryWait32, &ssa.Signature{
   666  			// exec context, timeout, expected, addr
   667  			Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeI32, ssa.TypeI64},
   668  			// Returns the status.
   669  			Results: []ssa.Type{ssa.TypeI32},
   670  		}, false)
   671  		e.sharedFunctions.memoryWait32Executable = mmapExecutable(src)
   672  		if wazevoapi.PerfMapEnabled {
   673  			exe := e.sharedFunctions.memoryWait32Executable
   674  			wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(&exe[0])), uint64(len(exe)), "memory_wait32_trampoline")
   675  		}
   676  	}
   677  
   678  	e.be.Init()
   679  	{
   680  		src := e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeMemoryWait64, &ssa.Signature{
   681  			// exec context, timeout, expected, addr
   682  			Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeI64, ssa.TypeI64},
   683  			// Returns the status.
   684  			Results: []ssa.Type{ssa.TypeI32},
   685  		}, false)
   686  		e.sharedFunctions.memoryWait64Executable = mmapExecutable(src)
   687  		if wazevoapi.PerfMapEnabled {
   688  			exe := e.sharedFunctions.memoryWait64Executable
   689  			wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(&exe[0])), uint64(len(exe)), "memory_wait64_trampoline")
   690  		}
   691  	}
   692  
   693  	e.be.Init()
   694  	{
   695  		src := e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeMemoryNotify, &ssa.Signature{
   696  			// exec context, count, addr
   697  			Params: []ssa.Type{ssa.TypeI64, ssa.TypeI32, ssa.TypeI64},
   698  			// Returns the number notified.
   699  			Results: []ssa.Type{ssa.TypeI32},
   700  		}, false)
   701  		e.sharedFunctions.memoryNotifyExecutable = mmapExecutable(src)
   702  		if wazevoapi.PerfMapEnabled {
   703  			exe := e.sharedFunctions.memoryNotifyExecutable
   704  			wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(&exe[0])), uint64(len(exe)), "memory_notify_trampoline")
   705  		}
   706  	}
   707  
   708  	e.setFinalizer(e.sharedFunctions, sharedFunctionsFinalizer)
   709  }
   710  
   711  func sharedFunctionsFinalizer(sf *sharedFunctions) {
   712  	if err := platform.MunmapCodeSegment(sf.memoryGrowExecutable); err != nil {
   713  		panic(err)
   714  	}
   715  	if err := platform.MunmapCodeSegment(sf.checkModuleExitCode); err != nil {
   716  		panic(err)
   717  	}
   718  	if err := platform.MunmapCodeSegment(sf.stackGrowExecutable); err != nil {
   719  		panic(err)
   720  	}
   721  	if err := platform.MunmapCodeSegment(sf.tableGrowExecutable); err != nil {
   722  		panic(err)
   723  	}
   724  	if err := platform.MunmapCodeSegment(sf.refFuncExecutable); err != nil {
   725  		panic(err)
   726  	}
   727  	if err := platform.MunmapCodeSegment(sf.memoryWait32Executable); err != nil {
   728  		panic(err)
   729  	}
   730  	if err := platform.MunmapCodeSegment(sf.memoryWait64Executable); err != nil {
   731  		panic(err)
   732  	}
   733  	if err := platform.MunmapCodeSegment(sf.memoryNotifyExecutable); err != nil {
   734  		panic(err)
   735  	}
   736  	for _, f := range sf.listenerBeforeTrampolines {
   737  		if err := platform.MunmapCodeSegment(f); err != nil {
   738  			panic(err)
   739  		}
   740  	}
   741  	for _, f := range sf.listenerAfterTrampolines {
   742  		if err := platform.MunmapCodeSegment(f); err != nil {
   743  			panic(err)
   744  		}
   745  	}
   746  
   747  	sf.memoryGrowExecutable = nil
   748  	sf.checkModuleExitCode = nil
   749  	sf.stackGrowExecutable = nil
   750  	sf.tableGrowExecutable = nil
   751  	sf.refFuncExecutable = nil
   752  	sf.memoryWait32Executable = nil
   753  	sf.memoryWait64Executable = nil
   754  	sf.memoryNotifyExecutable = nil
   755  	sf.listenerBeforeTrampolines = nil
   756  	sf.listenerAfterTrampolines = nil
   757  }
   758  
   759  func executablesFinalizer(exec *executables) {
   760  	if len(exec.executable) > 0 {
   761  		if err := platform.MunmapCodeSegment(exec.executable); err != nil {
   762  			panic(err)
   763  		}
   764  	}
   765  	exec.executable = nil
   766  
   767  	for _, f := range exec.entryPreambles {
   768  		if err := platform.MunmapCodeSegment(f); err != nil {
   769  			panic(err)
   770  		}
   771  	}
   772  	exec.entryPreambles = nil
   773  }
   774  
   775  func mmapExecutable(src []byte) []byte {
   776  	executable, err := platform.MmapCodeSegment(len(src))
   777  	if err != nil {
   778  		panic(err)
   779  	}
   780  
   781  	copy(executable, src)
   782  
   783  	if runtime.GOARCH == "arm64" {
   784  		// On arm64, we cannot give all of rwx at the same time, so we change it to exec.
   785  		if err = platform.MprotectRX(executable); err != nil {
   786  			panic(err)
   787  		}
   788  	}
   789  	return executable
   790  }
   791  
   792  func (cm *compiledModule) functionIndexOf(addr uintptr) wasm.Index {
   793  	addr -= uintptr(unsafe.Pointer(&cm.executable[0]))
   794  	offset := cm.functionOffsets
   795  	index := sort.Search(len(offset), func(i int) bool {
   796  		return offset[i] > int(addr)
   797  	})
   798  	index--
   799  	if index < 0 {
   800  		panic("BUG")
   801  	}
   802  	return wasm.Index(index)
   803  }
   804  
   805  func (e *engine) getListenerTrampolineForType(functionType *wasm.FunctionType) (before, after *byte) {
   806  	e.mux.Lock()
   807  	defer e.mux.Unlock()
   808  
   809  	beforeBuf, ok := e.sharedFunctions.listenerBeforeTrampolines[functionType]
   810  	afterBuf := e.sharedFunctions.listenerAfterTrampolines[functionType]
   811  	if ok {
   812  		return &beforeBuf[0], &afterBuf[0]
   813  	}
   814  
   815  	beforeSig, afterSig := frontend.SignatureForListener(functionType)
   816  
   817  	e.be.Init()
   818  	buf := e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeCallListenerBefore, beforeSig, false)
   819  	beforeBuf = mmapExecutable(buf)
   820  
   821  	e.be.Init()
   822  	buf = e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeCallListenerAfter, afterSig, false)
   823  	afterBuf = mmapExecutable(buf)
   824  
   825  	e.sharedFunctions.listenerBeforeTrampolines[functionType] = beforeBuf
   826  	e.sharedFunctions.listenerAfterTrampolines[functionType] = afterBuf
   827  	return &beforeBuf[0], &afterBuf[0]
   828  }
   829  
   830  func (cm *compiledModule) getSourceOffset(pc uintptr) uint64 {
   831  	offsets := cm.sourceMap.executableOffsets
   832  	if len(offsets) == 0 {
   833  		return 0
   834  	}
   835  
   836  	index := sort.Search(len(offsets), func(i int) bool {
   837  		return offsets[i] >= pc
   838  	})
   839  
   840  	index--
   841  	if index < 0 {
   842  		return 0
   843  	}
   844  	return cm.sourceMap.wasmBinaryOffsets[index]
   845  }