github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/engine/wazevo/call_engine.go (about)

     1  package wazevo
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"reflect"
     8  	"runtime"
     9  	"sync/atomic"
    10  	"unsafe"
    11  
    12  	"github.com/tetratelabs/wazero/api"
    13  	"github.com/tetratelabs/wazero/experimental"
    14  	"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
    15  	"github.com/tetratelabs/wazero/internal/expctxkeys"
    16  	"github.com/tetratelabs/wazero/internal/internalapi"
    17  	"github.com/tetratelabs/wazero/internal/wasm"
    18  	"github.com/tetratelabs/wazero/internal/wasmdebug"
    19  	"github.com/tetratelabs/wazero/internal/wasmruntime"
    20  )
    21  
    22  type (
    23  	// callEngine implements api.Function.
    24  	callEngine struct {
    25  		internalapi.WazeroOnly
    26  		stack []byte
    27  		// stackTop is the pointer to the *aligned* top of the stack. This must be updated
    28  		// whenever the stack is changed. This is passed to the assembly function
    29  		// at the very beginning of api.Function Call/CallWithStack.
    30  		stackTop uintptr
    31  		// executable is the pointer to the executable code for this function.
    32  		executable         *byte
    33  		preambleExecutable *byte
    34  		// parent is the *moduleEngine from which this callEngine is created.
    35  		parent *moduleEngine
    36  		// indexInModule is the index of the function in the module.
    37  		indexInModule wasm.Index
    38  		// sizeOfParamResultSlice is the size of the parameter/result slice.
    39  		sizeOfParamResultSlice int
    40  		requiredParams         int
    41  		// execCtx holds various information to be read/written by assembly functions.
    42  		execCtx executionContext
    43  		// execCtxPtr holds the pointer to the executionContext which doesn't change after callEngine is created.
    44  		execCtxPtr        uintptr
    45  		numberOfResults   int
    46  		stackIteratorImpl stackIterator
    47  	}
    48  
    49  	// executionContext is the struct to be read/written by assembly functions.
    50  	executionContext struct {
    51  		// exitCode holds the wazevoapi.ExitCode describing the state of the function execution.
    52  		exitCode wazevoapi.ExitCode
    53  		// callerModuleContextPtr holds the moduleContextOpaque for Go function calls.
    54  		callerModuleContextPtr *byte
    55  		// originalFramePointer holds the original frame pointer of the caller of the assembly function.
    56  		originalFramePointer uintptr
    57  		// originalStackPointer holds the original stack pointer of the caller of the assembly function.
    58  		originalStackPointer uintptr
    59  		// goReturnAddress holds the return address to go back to the caller of the assembly function.
    60  		goReturnAddress uintptr
    61  		// stackBottomPtr holds the pointer to the bottom of the stack.
    62  		stackBottomPtr *byte
    63  		// goCallReturnAddress holds the return address to go back to the caller of the Go function.
    64  		goCallReturnAddress *byte
    65  		// stackPointerBeforeGoCall holds the stack pointer before calling a Go function.
    66  		stackPointerBeforeGoCall *uint64
    67  		// stackGrowRequiredSize holds the required size of stack grow.
    68  		stackGrowRequiredSize uintptr
    69  		// memoryGrowTrampolineAddress holds the address of memory grow trampoline function.
    70  		memoryGrowTrampolineAddress *byte
    71  		// stackGrowCallTrampolineAddress holds the address of stack grow trampoline function.
    72  		stackGrowCallTrampolineAddress *byte
    73  		// checkModuleExitCodeTrampolineAddress holds the address of check-module-exit-code function.
    74  		checkModuleExitCodeTrampolineAddress *byte
    75  		// savedRegisters is the opaque spaces for save/restore registers.
    76  		// We want to align 16 bytes for each register, so we use [64][2]uint64.
    77  		savedRegisters [64][2]uint64
    78  		// goFunctionCallCalleeModuleContextOpaque is the pointer to the target Go function's moduleContextOpaque.
    79  		goFunctionCallCalleeModuleContextOpaque uintptr
    80  		// tableGrowTrampolineAddress holds the address of table grow trampoline function.
    81  		tableGrowTrampolineAddress *byte
    82  		// refFuncTrampolineAddress holds the address of ref-func trampoline function.
    83  		refFuncTrampolineAddress *byte
    84  		// memmoveAddress holds the address of memmove function implemented by Go runtime. See memmove.go.
    85  		memmoveAddress uintptr
    86  		// framePointerBeforeGoCall holds the frame pointer before calling a Go function. Note: only used in amd64.
    87  		framePointerBeforeGoCall uintptr
    88  		// memoryWait32TrampolineAddress holds the address of memory_wait32 trampoline function.
    89  		memoryWait32TrampolineAddress *byte
    90  		// memoryWait32TrampolineAddress holds the address of memory_wait64 trampoline function.
    91  		memoryWait64TrampolineAddress *byte
    92  		// memoryNotifyTrampolineAddress holds the address of the memory_notify trampoline function.
    93  		memoryNotifyTrampolineAddress *byte
    94  	}
    95  )
    96  
    97  func (c *callEngine) requiredInitialStackSize() int {
    98  	const initialStackSizeDefault = 10240
    99  	stackSize := initialStackSizeDefault
   100  	paramResultInBytes := c.sizeOfParamResultSlice * 8 * 2 // * 8 because uint64 is 8 bytes, and *2 because we need both separated param/result slots.
   101  	required := paramResultInBytes + 32 + 16               // 32 is enough to accommodate the call frame info, and 16 exists just in case when []byte is not aligned to 16 bytes.
   102  	if required > stackSize {
   103  		stackSize = required
   104  	}
   105  	return stackSize
   106  }
   107  
   108  func (c *callEngine) init() {
   109  	stackSize := c.requiredInitialStackSize()
   110  	if wazevoapi.StackGuardCheckEnabled {
   111  		stackSize += wazevoapi.StackGuardCheckGuardPageSize
   112  	}
   113  	c.stack = make([]byte, stackSize)
   114  	c.stackTop = alignedStackTop(c.stack)
   115  	if wazevoapi.StackGuardCheckEnabled {
   116  		c.execCtx.stackBottomPtr = &c.stack[wazevoapi.StackGuardCheckGuardPageSize]
   117  	} else {
   118  		c.execCtx.stackBottomPtr = &c.stack[0]
   119  	}
   120  	c.execCtxPtr = uintptr(unsafe.Pointer(&c.execCtx))
   121  }
   122  
   123  // alignedStackTop returns 16-bytes aligned stack top of given stack.
   124  // 16 bytes should be good for all platform (arm64/amd64).
   125  func alignedStackTop(s []byte) uintptr {
   126  	stackAddr := uintptr(unsafe.Pointer(&s[len(s)-1]))
   127  	return stackAddr - (stackAddr & (16 - 1))
   128  }
   129  
   130  // Definition implements api.Function.
   131  func (c *callEngine) Definition() api.FunctionDefinition {
   132  	return c.parent.module.Source.FunctionDefinition(c.indexInModule)
   133  }
   134  
   135  // Call implements api.Function.
   136  func (c *callEngine) Call(ctx context.Context, params ...uint64) ([]uint64, error) {
   137  	if c.requiredParams != len(params) {
   138  		return nil, fmt.Errorf("expected %d params, but passed %d", c.requiredParams, len(params))
   139  	}
   140  	paramResultSlice := make([]uint64, c.sizeOfParamResultSlice)
   141  	copy(paramResultSlice, params)
   142  	if err := c.callWithStack(ctx, paramResultSlice); err != nil {
   143  		return nil, err
   144  	}
   145  	return paramResultSlice[:c.numberOfResults], nil
   146  }
   147  
   148  func (c *callEngine) addFrame(builder wasmdebug.ErrorBuilder, addr uintptr) (def api.FunctionDefinition, listener experimental.FunctionListener) {
   149  	eng := c.parent.parent.parent
   150  	cm := eng.compiledModuleOfAddr(addr)
   151  	if cm == nil {
   152  		// This case, the module might have been closed and deleted from the engine.
   153  		// We fall back to searching the imported modules that can be referenced from this callEngine.
   154  
   155  		// First, we check itself.
   156  		if checkAddrInBytes(addr, c.parent.parent.executable) {
   157  			cm = c.parent.parent
   158  		} else {
   159  			// Otherwise, search all imported modules. TODO: maybe recursive, but not sure it's useful in practice.
   160  			p := c.parent
   161  			for i := range p.importedFunctions {
   162  				candidate := p.importedFunctions[i].me.parent
   163  				if checkAddrInBytes(addr, candidate.executable) {
   164  					cm = candidate
   165  					break
   166  				}
   167  			}
   168  		}
   169  	}
   170  
   171  	if cm != nil {
   172  		index := cm.functionIndexOf(addr)
   173  		def = cm.module.FunctionDefinition(cm.module.ImportFunctionCount + index)
   174  		var sources []string
   175  		if dw := cm.module.DWARFLines; dw != nil {
   176  			sourceOffset := cm.getSourceOffset(addr)
   177  			sources = dw.Line(sourceOffset)
   178  		}
   179  		builder.AddFrame(def.DebugName(), def.ParamTypes(), def.ResultTypes(), sources)
   180  		if len(cm.listeners) > 0 {
   181  			listener = cm.listeners[index]
   182  		}
   183  	}
   184  	return
   185  }
   186  
   187  // CallWithStack implements api.Function.
   188  func (c *callEngine) CallWithStack(ctx context.Context, paramResultStack []uint64) (err error) {
   189  	if c.sizeOfParamResultSlice > len(paramResultStack) {
   190  		return fmt.Errorf("need %d params, but stack size is %d", c.sizeOfParamResultSlice, len(paramResultStack))
   191  	}
   192  	return c.callWithStack(ctx, paramResultStack)
   193  }
   194  
   195  // CallWithStack implements api.Function.
   196  func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint64) (err error) {
   197  	snapshotEnabled := ctx.Value(expctxkeys.EnableSnapshotterKey{}) != nil
   198  	if snapshotEnabled {
   199  		ctx = context.WithValue(ctx, expctxkeys.SnapshotterKey{}, c)
   200  	}
   201  
   202  	if wazevoapi.StackGuardCheckEnabled {
   203  		defer func() {
   204  			wazevoapi.CheckStackGuardPage(c.stack)
   205  		}()
   206  	}
   207  
   208  	p := c.parent
   209  	ensureTermination := p.parent.ensureTermination
   210  	m := p.module
   211  	if ensureTermination {
   212  		select {
   213  		case <-ctx.Done():
   214  			// If the provided context is already done, close the module and return the error.
   215  			m.CloseWithCtxErr(ctx)
   216  			return m.FailIfClosed()
   217  		default:
   218  		}
   219  	}
   220  
   221  	var paramResultPtr *uint64
   222  	if len(paramResultStack) > 0 {
   223  		paramResultPtr = &paramResultStack[0]
   224  	}
   225  	defer func() {
   226  		r := recover()
   227  		if s, ok := r.(*snapshot); ok {
   228  			// A snapshot that wasn't handled was created by a different call engine possibly from a nested wasm invocation,
   229  			// let it propagate up to be handled by the caller.
   230  			panic(s)
   231  		}
   232  		if r != nil {
   233  			type listenerForAbort struct {
   234  				def api.FunctionDefinition
   235  				lsn experimental.FunctionListener
   236  			}
   237  
   238  			var listeners []listenerForAbort
   239  			builder := wasmdebug.NewErrorBuilder()
   240  			def, lsn := c.addFrame(builder, uintptr(unsafe.Pointer(c.execCtx.goCallReturnAddress)))
   241  			if lsn != nil {
   242  				listeners = append(listeners, listenerForAbort{def, lsn})
   243  			}
   244  			returnAddrs := unwindStack(
   245  				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)),
   246  				c.execCtx.framePointerBeforeGoCall,
   247  				c.stackTop,
   248  				nil,
   249  			)
   250  			for _, retAddr := range returnAddrs[:len(returnAddrs)-1] { // the last return addr is the trampoline, so we skip it.
   251  				def, lsn = c.addFrame(builder, retAddr)
   252  				if lsn != nil {
   253  					listeners = append(listeners, listenerForAbort{def, lsn})
   254  				}
   255  			}
   256  			err = builder.FromRecovered(r)
   257  
   258  			for _, lsn := range listeners {
   259  				lsn.lsn.Abort(ctx, m, lsn.def, err)
   260  			}
   261  		} else {
   262  			if err != wasmruntime.ErrRuntimeStackOverflow { // Stackoverflow case shouldn't be panic (to avoid extreme stack unwinding).
   263  				err = c.parent.module.FailIfClosed()
   264  			}
   265  		}
   266  
   267  		if err != nil {
   268  			// Ensures that we can reuse this callEngine even after an error.
   269  			c.execCtx.exitCode = wazevoapi.ExitCodeOK
   270  		}
   271  	}()
   272  
   273  	if ensureTermination {
   274  		done := m.CloseModuleOnCanceledOrTimeout(ctx)
   275  		defer done()
   276  	}
   277  
   278  	if c.stackTop&(16-1) != 0 {
   279  		panic("BUG: stack must be aligned to 16 bytes")
   280  	}
   281  	entrypoint(c.preambleExecutable, c.executable, c.execCtxPtr, c.parent.opaquePtr, paramResultPtr, c.stackTop)
   282  	for {
   283  		switch ec := c.execCtx.exitCode; ec & wazevoapi.ExitCodeMask {
   284  		case wazevoapi.ExitCodeOK:
   285  			return nil
   286  		case wazevoapi.ExitCodeGrowStack:
   287  			oldsp := uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall))
   288  			oldTop := c.stackTop
   289  			oldStack := c.stack
   290  			var newsp, newfp uintptr
   291  			if wazevoapi.StackGuardCheckEnabled {
   292  				newsp, newfp, err = c.growStackWithGuarded()
   293  			} else {
   294  				newsp, newfp, err = c.growStack()
   295  			}
   296  			if err != nil {
   297  				return err
   298  			}
   299  			adjustClonedStack(oldsp, oldTop, newsp, newfp, c.stackTop)
   300  			// Old stack must be alive until the new stack is adjusted.
   301  			runtime.KeepAlive(oldStack)
   302  			c.execCtx.exitCode = wazevoapi.ExitCodeOK
   303  			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, newsp, newfp)
   304  		case wazevoapi.ExitCodeGrowMemory:
   305  			mod := c.callerModuleInstance()
   306  			mem := mod.MemoryInstance
   307  			s := goCallStackView(c.execCtx.stackPointerBeforeGoCall)
   308  			argRes := &s[0]
   309  			if res, ok := mem.Grow(uint32(*argRes)); !ok {
   310  				*argRes = uint64(0xffffffff) // = -1 in signed 32-bit integer.
   311  			} else {
   312  				*argRes = uint64(res)
   313  				calleeOpaque := opaqueViewFromPtr(uintptr(unsafe.Pointer(c.execCtx.callerModuleContextPtr)))
   314  				if mod.Source.MemorySection != nil { // Local memory.
   315  					putLocalMemory(calleeOpaque, 8 /* local memory begins at 8 */, mem)
   316  				} else {
   317  					// Imported memory's owner at offset 16 of the callerModuleContextPtr.
   318  					opaquePtr := uintptr(binary.LittleEndian.Uint64(calleeOpaque[16:]))
   319  					importedMemOwner := opaqueViewFromPtr(opaquePtr)
   320  					putLocalMemory(importedMemOwner, 8 /* local memory begins at 8 */, mem)
   321  				}
   322  			}
   323  			c.execCtx.exitCode = wazevoapi.ExitCodeOK
   324  			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
   325  		case wazevoapi.ExitCodeTableGrow:
   326  			mod := c.callerModuleInstance()
   327  			s := goCallStackView(c.execCtx.stackPointerBeforeGoCall)
   328  			tableIndex, num, ref := uint32(s[0]), uint32(s[1]), uintptr(s[2])
   329  			table := mod.Tables[tableIndex]
   330  			s[0] = uint64(uint32(int32(table.Grow(num, ref))))
   331  			c.execCtx.exitCode = wazevoapi.ExitCodeOK
   332  			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
   333  				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
   334  		case wazevoapi.ExitCodeCallGoFunction:
   335  			index := wazevoapi.GoFunctionIndexFromExitCode(ec)
   336  			f := hostModuleGoFuncFromOpaque[api.GoFunction](index, c.execCtx.goFunctionCallCalleeModuleContextOpaque)
   337  			func() {
   338  				if snapshotEnabled {
   339  					defer snapshotRecoverFn(c)
   340  				}
   341  				f.Call(ctx, goCallStackView(c.execCtx.stackPointerBeforeGoCall))
   342  			}()
   343  			// Back to the native code.
   344  			c.execCtx.exitCode = wazevoapi.ExitCodeOK
   345  			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
   346  				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
   347  		case wazevoapi.ExitCodeCallGoFunctionWithListener:
   348  			index := wazevoapi.GoFunctionIndexFromExitCode(ec)
   349  			f := hostModuleGoFuncFromOpaque[api.GoFunction](index, c.execCtx.goFunctionCallCalleeModuleContextOpaque)
   350  			listeners := hostModuleListenersSliceFromOpaque(c.execCtx.goFunctionCallCalleeModuleContextOpaque)
   351  			s := goCallStackView(c.execCtx.stackPointerBeforeGoCall)
   352  			// Call Listener.Before.
   353  			callerModule := c.callerModuleInstance()
   354  			listener := listeners[index]
   355  			hostModule := hostModuleFromOpaque(c.execCtx.goFunctionCallCalleeModuleContextOpaque)
   356  			def := hostModule.FunctionDefinition(wasm.Index(index))
   357  			listener.Before(ctx, callerModule, def, s, c.stackIterator(true))
   358  			// Call into the Go function.
   359  			func() {
   360  				if snapshotEnabled {
   361  					defer snapshotRecoverFn(c)
   362  				}
   363  				f.Call(ctx, s)
   364  			}()
   365  			// Call Listener.After.
   366  			listener.After(ctx, callerModule, def, s)
   367  			// Back to the native code.
   368  			c.execCtx.exitCode = wazevoapi.ExitCodeOK
   369  			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
   370  				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
   371  		case wazevoapi.ExitCodeCallGoModuleFunction:
   372  			index := wazevoapi.GoFunctionIndexFromExitCode(ec)
   373  			f := hostModuleGoFuncFromOpaque[api.GoModuleFunction](index, c.execCtx.goFunctionCallCalleeModuleContextOpaque)
   374  			mod := c.callerModuleInstance()
   375  			func() {
   376  				if snapshotEnabled {
   377  					defer snapshotRecoverFn(c)
   378  				}
   379  				f.Call(ctx, mod, goCallStackView(c.execCtx.stackPointerBeforeGoCall))
   380  			}()
   381  			// Back to the native code.
   382  			c.execCtx.exitCode = wazevoapi.ExitCodeOK
   383  			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
   384  				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
   385  		case wazevoapi.ExitCodeCallGoModuleFunctionWithListener:
   386  			index := wazevoapi.GoFunctionIndexFromExitCode(ec)
   387  			f := hostModuleGoFuncFromOpaque[api.GoModuleFunction](index, c.execCtx.goFunctionCallCalleeModuleContextOpaque)
   388  			listeners := hostModuleListenersSliceFromOpaque(c.execCtx.goFunctionCallCalleeModuleContextOpaque)
   389  			s := goCallStackView(c.execCtx.stackPointerBeforeGoCall)
   390  			// Call Listener.Before.
   391  			callerModule := c.callerModuleInstance()
   392  			listener := listeners[index]
   393  			hostModule := hostModuleFromOpaque(c.execCtx.goFunctionCallCalleeModuleContextOpaque)
   394  			def := hostModule.FunctionDefinition(wasm.Index(index))
   395  			listener.Before(ctx, callerModule, def, s, c.stackIterator(true))
   396  			// Call into the Go function.
   397  			func() {
   398  				if snapshotEnabled {
   399  					defer snapshotRecoverFn(c)
   400  				}
   401  				f.Call(ctx, callerModule, s)
   402  			}()
   403  			// Call Listener.After.
   404  			listener.After(ctx, callerModule, def, s)
   405  			// Back to the native code.
   406  			c.execCtx.exitCode = wazevoapi.ExitCodeOK
   407  			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
   408  				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
   409  		case wazevoapi.ExitCodeCallListenerBefore:
   410  			stack := goCallStackView(c.execCtx.stackPointerBeforeGoCall)
   411  			index := wasm.Index(stack[0])
   412  			mod := c.callerModuleInstance()
   413  			listener := mod.Engine.(*moduleEngine).listeners[index]
   414  			def := mod.Source.FunctionDefinition(index + mod.Source.ImportFunctionCount)
   415  			listener.Before(ctx, mod, def, stack[1:], c.stackIterator(false))
   416  			c.execCtx.exitCode = wazevoapi.ExitCodeOK
   417  			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
   418  				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
   419  		case wazevoapi.ExitCodeCallListenerAfter:
   420  			stack := goCallStackView(c.execCtx.stackPointerBeforeGoCall)
   421  			index := wasm.Index(stack[0])
   422  			mod := c.callerModuleInstance()
   423  			listener := mod.Engine.(*moduleEngine).listeners[index]
   424  			def := mod.Source.FunctionDefinition(index + mod.Source.ImportFunctionCount)
   425  			listener.After(ctx, mod, def, stack[1:])
   426  			c.execCtx.exitCode = wazevoapi.ExitCodeOK
   427  			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
   428  				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
   429  		case wazevoapi.ExitCodeCheckModuleExitCode:
   430  			// Note: this operation must be done in Go, not native code. The reason is that
   431  			// native code cannot be preempted and that means it can block forever if there are not
   432  			// enough OS threads (which we don't have control over).
   433  			if err := m.FailIfClosed(); err != nil {
   434  				panic(err)
   435  			}
   436  			c.execCtx.exitCode = wazevoapi.ExitCodeOK
   437  			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
   438  				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
   439  		case wazevoapi.ExitCodeRefFunc:
   440  			mod := c.callerModuleInstance()
   441  			s := goCallStackView(c.execCtx.stackPointerBeforeGoCall)
   442  			funcIndex := wasm.Index(s[0])
   443  			ref := mod.Engine.FunctionInstanceReference(funcIndex)
   444  			s[0] = uint64(ref)
   445  			c.execCtx.exitCode = wazevoapi.ExitCodeOK
   446  			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
   447  				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
   448  		case wazevoapi.ExitCodeMemoryWait32:
   449  			mod := c.callerModuleInstance()
   450  			mem := mod.MemoryInstance
   451  			if !mem.Shared {
   452  				panic(wasmruntime.ErrRuntimeExpectedSharedMemory)
   453  			}
   454  
   455  			s := goCallStackView(c.execCtx.stackPointerBeforeGoCall)
   456  			timeout, exp, addr := int64(s[0]), uint32(s[1]), uintptr(s[2])
   457  			base := uintptr(unsafe.Pointer(&mem.Buffer[0]))
   458  
   459  			offset := uint32(addr - base)
   460  			res := mem.Wait32(offset, exp, timeout, func(mem *wasm.MemoryInstance, offset uint32) uint32 {
   461  				addr := unsafe.Add(unsafe.Pointer(&mem.Buffer[0]), offset)
   462  				return atomic.LoadUint32((*uint32)(addr))
   463  			})
   464  			s[0] = res
   465  			c.execCtx.exitCode = wazevoapi.ExitCodeOK
   466  			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
   467  				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
   468  		case wazevoapi.ExitCodeMemoryWait64:
   469  			mod := c.callerModuleInstance()
   470  			mem := mod.MemoryInstance
   471  			if !mem.Shared {
   472  				panic(wasmruntime.ErrRuntimeExpectedSharedMemory)
   473  			}
   474  
   475  			s := goCallStackView(c.execCtx.stackPointerBeforeGoCall)
   476  			timeout, exp, addr := int64(s[0]), uint64(s[1]), uintptr(s[2])
   477  			base := uintptr(unsafe.Pointer(&mem.Buffer[0]))
   478  
   479  			offset := uint32(addr - base)
   480  			res := mem.Wait64(offset, exp, timeout, func(mem *wasm.MemoryInstance, offset uint32) uint64 {
   481  				addr := unsafe.Add(unsafe.Pointer(&mem.Buffer[0]), offset)
   482  				return atomic.LoadUint64((*uint64)(addr))
   483  			})
   484  			s[0] = uint64(res)
   485  			c.execCtx.exitCode = wazevoapi.ExitCodeOK
   486  			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
   487  				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
   488  		case wazevoapi.ExitCodeMemoryNotify:
   489  			mod := c.callerModuleInstance()
   490  			mem := mod.MemoryInstance
   491  
   492  			s := goCallStackView(c.execCtx.stackPointerBeforeGoCall)
   493  			count, addr := uint32(s[0]), s[1]
   494  			offset := uint32(uintptr(addr) - uintptr(unsafe.Pointer(&mem.Buffer[0])))
   495  			res := mem.Notify(offset, count)
   496  			s[0] = uint64(res)
   497  			c.execCtx.exitCode = wazevoapi.ExitCodeOK
   498  			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
   499  				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
   500  		case wazevoapi.ExitCodeUnreachable:
   501  			panic(wasmruntime.ErrRuntimeUnreachable)
   502  		case wazevoapi.ExitCodeMemoryOutOfBounds:
   503  			panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
   504  		case wazevoapi.ExitCodeTableOutOfBounds:
   505  			panic(wasmruntime.ErrRuntimeInvalidTableAccess)
   506  		case wazevoapi.ExitCodeIndirectCallNullPointer:
   507  			panic(wasmruntime.ErrRuntimeInvalidTableAccess)
   508  		case wazevoapi.ExitCodeIndirectCallTypeMismatch:
   509  			panic(wasmruntime.ErrRuntimeIndirectCallTypeMismatch)
   510  		case wazevoapi.ExitCodeIntegerOverflow:
   511  			panic(wasmruntime.ErrRuntimeIntegerOverflow)
   512  		case wazevoapi.ExitCodeIntegerDivisionByZero:
   513  			panic(wasmruntime.ErrRuntimeIntegerDivideByZero)
   514  		case wazevoapi.ExitCodeInvalidConversionToInteger:
   515  			panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
   516  		case wazevoapi.ExitCodeUnalignedAtomic:
   517  			panic(wasmruntime.ErrRuntimeUnalignedAtomic)
   518  		default:
   519  			panic("BUG")
   520  		}
   521  	}
   522  }
   523  
   524  func (c *callEngine) callerModuleInstance() *wasm.ModuleInstance {
   525  	return moduleInstanceFromOpaquePtr(c.execCtx.callerModuleContextPtr)
   526  }
   527  
   528  func opaqueViewFromPtr(ptr uintptr) []byte {
   529  	var opaque []byte
   530  	sh := (*reflect.SliceHeader)(unsafe.Pointer(&opaque))
   531  	sh.Data = ptr
   532  	setSliceLimits(sh, 24, 24)
   533  	return opaque
   534  }
   535  
   536  const callStackCeiling = uintptr(50000000) // in uint64 (8 bytes) == 400000000 bytes in total == 400mb.
   537  
   538  func (c *callEngine) growStackWithGuarded() (newSP uintptr, newFP uintptr, err error) {
   539  	if wazevoapi.StackGuardCheckEnabled {
   540  		wazevoapi.CheckStackGuardPage(c.stack)
   541  	}
   542  	newSP, newFP, err = c.growStack()
   543  	if err != nil {
   544  		return
   545  	}
   546  	if wazevoapi.StackGuardCheckEnabled {
   547  		c.execCtx.stackBottomPtr = &c.stack[wazevoapi.StackGuardCheckGuardPageSize]
   548  	}
   549  	return
   550  }
   551  
   552  // growStack grows the stack, and returns the new stack pointer.
   553  func (c *callEngine) growStack() (newSP, newFP uintptr, err error) {
   554  	currentLen := uintptr(len(c.stack))
   555  	if callStackCeiling < currentLen {
   556  		err = wasmruntime.ErrRuntimeStackOverflow
   557  		return
   558  	}
   559  
   560  	newLen := 2*currentLen + c.execCtx.stackGrowRequiredSize + 16 // Stack might be aligned to 16 bytes, so add 16 bytes just in case.
   561  	newSP, newFP, c.stackTop, c.stack = c.cloneStack(newLen)
   562  	c.execCtx.stackBottomPtr = &c.stack[0]
   563  	return
   564  }
   565  
   566  func (c *callEngine) cloneStack(l uintptr) (newSP, newFP, newTop uintptr, newStack []byte) {
   567  	newStack = make([]byte, l)
   568  
   569  	relSp := c.stackTop - uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall))
   570  	relFp := c.stackTop - c.execCtx.framePointerBeforeGoCall
   571  
   572  	// Copy the existing contents in the previous Go-allocated stack into the new one.
   573  	var prevStackAligned, newStackAligned []byte
   574  	{
   575  		sh := (*reflect.SliceHeader)(unsafe.Pointer(&prevStackAligned))
   576  		sh.Data = c.stackTop - relSp
   577  		setSliceLimits(sh, relSp, relSp)
   578  	}
   579  	newTop = alignedStackTop(newStack)
   580  	{
   581  		newSP = newTop - relSp
   582  		newFP = newTop - relFp
   583  		sh := (*reflect.SliceHeader)(unsafe.Pointer(&newStackAligned))
   584  		sh.Data = newSP
   585  		setSliceLimits(sh, relSp, relSp)
   586  	}
   587  	copy(newStackAligned, prevStackAligned)
   588  	return
   589  }
   590  
   591  func (c *callEngine) stackIterator(onHostCall bool) experimental.StackIterator {
   592  	c.stackIteratorImpl.reset(c, onHostCall)
   593  	return &c.stackIteratorImpl
   594  }
   595  
   596  // stackIterator implements experimental.StackIterator.
   597  type stackIterator struct {
   598  	retAddrs      []uintptr
   599  	retAddrCursor int
   600  	eng           *engine
   601  	pc            uint64
   602  
   603  	currentDef *wasm.FunctionDefinition
   604  }
   605  
   606  func (si *stackIterator) reset(c *callEngine, onHostCall bool) {
   607  	if onHostCall {
   608  		si.retAddrs = append(si.retAddrs[:0], uintptr(unsafe.Pointer(c.execCtx.goCallReturnAddress)))
   609  	} else {
   610  		si.retAddrs = si.retAddrs[:0]
   611  	}
   612  	si.retAddrs = unwindStack(uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall, c.stackTop, si.retAddrs)
   613  	si.retAddrs = si.retAddrs[:len(si.retAddrs)-1] // the last return addr is the trampoline, so we skip it.
   614  	si.retAddrCursor = 0
   615  	si.eng = c.parent.parent.parent
   616  }
   617  
   618  // Next implements the same method as documented on experimental.StackIterator.
   619  func (si *stackIterator) Next() bool {
   620  	if si.retAddrCursor >= len(si.retAddrs) {
   621  		return false
   622  	}
   623  
   624  	addr := si.retAddrs[si.retAddrCursor]
   625  	cm := si.eng.compiledModuleOfAddr(addr)
   626  	if cm != nil {
   627  		index := cm.functionIndexOf(addr)
   628  		def := cm.module.FunctionDefinition(cm.module.ImportFunctionCount + index)
   629  		si.currentDef = def
   630  		si.retAddrCursor++
   631  		si.pc = uint64(addr)
   632  		return true
   633  	}
   634  	return false
   635  }
   636  
   637  // ProgramCounter implements the same method as documented on experimental.StackIterator.
   638  func (si *stackIterator) ProgramCounter() experimental.ProgramCounter {
   639  	return experimental.ProgramCounter(si.pc)
   640  }
   641  
   642  // Function implements the same method as documented on experimental.StackIterator.
   643  func (si *stackIterator) Function() experimental.InternalFunction {
   644  	return si
   645  }
   646  
   647  // Definition implements the same method as documented on experimental.InternalFunction.
   648  func (si *stackIterator) Definition() api.FunctionDefinition {
   649  	return si.currentDef
   650  }
   651  
   652  // SourceOffsetForPC implements the same method as documented on experimental.InternalFunction.
   653  func (si *stackIterator) SourceOffsetForPC(pc experimental.ProgramCounter) uint64 {
   654  	upc := uintptr(pc)
   655  	cm := si.eng.compiledModuleOfAddr(upc)
   656  	return cm.getSourceOffset(upc)
   657  }
   658  
   659  // snapshot implements experimental.Snapshot
   660  type snapshot struct {
   661  	sp, fp, top    uintptr
   662  	returnAddress  *byte
   663  	stack          []byte
   664  	savedRegisters [64][2]uint64
   665  	ret            []uint64
   666  	c              *callEngine
   667  }
   668  
   669  // Snapshot implements the same method as documented on experimental.Snapshotter.
   670  func (c *callEngine) Snapshot() experimental.Snapshot {
   671  	returnAddress := c.execCtx.goCallReturnAddress
   672  	oldTop, oldSp := c.stackTop, uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall))
   673  	newSP, newFP, newTop, newStack := c.cloneStack(uintptr(len(c.stack)) + 16)
   674  	adjustClonedStack(oldSp, oldTop, newSP, newFP, newTop)
   675  	return &snapshot{
   676  		sp:             newSP,
   677  		fp:             newFP,
   678  		top:            newTop,
   679  		savedRegisters: c.execCtx.savedRegisters,
   680  		returnAddress:  returnAddress,
   681  		stack:          newStack,
   682  		c:              c,
   683  	}
   684  }
   685  
   686  // Restore implements the same method as documented on experimental.Snapshot.
   687  func (s *snapshot) Restore(ret []uint64) {
   688  	s.ret = ret
   689  	panic(s)
   690  }
   691  
   692  func (s *snapshot) doRestore() {
   693  	spp := *(**uint64)(unsafe.Pointer(&s.sp))
   694  	view := goCallStackView(spp)
   695  	copy(view, s.ret)
   696  
   697  	c := s.c
   698  	c.stack = s.stack
   699  	c.stackTop = s.top
   700  	ec := &c.execCtx
   701  	ec.stackBottomPtr = &c.stack[0]
   702  	ec.stackPointerBeforeGoCall = spp
   703  	ec.framePointerBeforeGoCall = s.fp
   704  	ec.goCallReturnAddress = s.returnAddress
   705  	ec.savedRegisters = s.savedRegisters
   706  }
   707  
   708  // Error implements the same method on error.
   709  func (s *snapshot) Error() string {
   710  	return "unhandled snapshot restore, this generally indicates restore was called from a different " +
   711  		"exported function invocation than snapshot"
   712  }
   713  
   714  func snapshotRecoverFn(c *callEngine) {
   715  	if r := recover(); r != nil {
   716  		if s, ok := r.(*snapshot); ok && s.c == c {
   717  			s.doRestore()
   718  		} else {
   719  			panic(r)
   720  		}
   721  	}
   722  }