package interpreter

import (
	"context"
	"encoding/binary"
	"errors"
	"fmt"
	"math"
	"math/bits"
	"slices"
	"sync"
	"unsafe"

	"github.com/tetratelabs/wazero/api"
	"github.com/tetratelabs/wazero/experimental"
	"github.com/tetratelabs/wazero/internal/expctxkeys"
	"github.com/tetratelabs/wazero/internal/filecache"
	"github.com/tetratelabs/wazero/internal/internalapi"
	"github.com/tetratelabs/wazero/internal/moremath"
	"github.com/tetratelabs/wazero/internal/wasm"
	"github.com/tetratelabs/wazero/internal/wasmdebug"
	"github.com/tetratelabs/wazero/internal/wasmruntime"
)

// callStackCeiling is the maximum WebAssembly call frame stack height. This allows wazero to raise
// wasm.ErrCallStackOverflow instead of overflowing the Go runtime.
//
// The default value should suffice for most use cases. Those wishing to change this can via `go build -ldflags`.
var callStackCeiling = 2000

type compiledFunctionWithCount struct {
	funcs    []compiledFunction
	refCount int
}

// engine is an interpreter implementation of wasm.Engine
type engine struct {
	enabledFeatures   api.CoreFeatures
	compiledFunctions map[wasm.ModuleID]*compiledFunctionWithCount // guarded by mutex.
	mux               sync.Mutex
}

func NewEngine(_ context.Context, enabledFeatures api.CoreFeatures, _ filecache.Cache) wasm.Engine {
	return &engine{
		enabledFeatures:   enabledFeatures,
		compiledFunctions: map[wasm.ModuleID]*compiledFunctionWithCount{},
	}
}

// Close implements the same method as documented on wasm.Engine.
func (e *engine) Close() (err error) {
	e.mux.Lock()
	defer e.mux.Unlock()
	clear(e.compiledFunctions)
	return
}

// CompiledModuleCount implements the same method as documented on wasm.Engine.
func (e *engine) CompiledModuleCount() uint32 {
	e.mux.Lock()
	defer e.mux.Unlock()
	return uint32(len(e.compiledFunctions))
}

// DeleteCompiledModule implements the same method as documented on wasm.Engine.
func (e *engine) DeleteCompiledModule(m *wasm.Module) {
	e.deleteCompiledFunctions(m)
}

func (e *engine) deleteCompiledFunctions(module *wasm.Module) {
	e.mux.Lock()
	defer e.mux.Unlock()
	cf, ok := e.compiledFunctions[module.ID]
	if !ok {
		return
	}
	cf.refCount--
	if cf.refCount > 0 {
		return
	}
	delete(e.compiledFunctions, module.ID)
}

func (e *engine) addCompiledFunctions(module *wasm.Module, fs []compiledFunction) {
	e.mux.Lock()
	defer e.mux.Unlock()
	if c, ok := e.compiledFunctions[module.ID]; ok {
		c.refCount++
		return
	}
	e.compiledFunctions[module.ID] = &compiledFunctionWithCount{funcs: fs, refCount: 1}
}

func (e *engine) getCompiledFunctions(module *wasm.Module, increaseRefCount bool) (fs []compiledFunction, ok bool) {
	e.mux.Lock()
	defer e.mux.Unlock()
	cf, ok := e.compiledFunctions[module.ID]
	if ok {
		fs = cf.funcs
		if increaseRefCount {
			cf.refCount++
		}
	}
	return
}

// moduleEngine implements wasm.ModuleEngine
type moduleEngine struct {
	// codes are the compiled functions in a module instances.
	// The index is module instance-scoped.
	functions []function

	// parentEngine holds *engine from which this module engine is created from.
	parentEngine *engine
}

// GetGlobalValue implements the same method as documented on wasm.ModuleEngine.
func (e *moduleEngine) GetGlobalValue(wasm.Index) (lo, hi uint64) {
	panic("BUG: GetGlobalValue should never be called on interpreter mode")
}

// SetGlobalValue implements the same method as documented on wasm.ModuleEngine.
func (e *moduleEngine) SetGlobalValue(idx wasm.Index, lo, hi uint64) {
	panic("BUG: SetGlobalValue should never be called on interpreter mode")
}

// OwnsGlobals implements the same method as documented on wasm.ModuleEngine.
func (e *moduleEngine) OwnsGlobals() bool { return false }

// MemoryGrown implements wasm.ModuleEngine.
func (e *moduleEngine) MemoryGrown() {}

// restorable is implemented by panic values that can restore callEngine state.
// Both *snapshot (snapshotter API) and *thrownException (exception handling)
// implement this interface.
type restorable interface {
	// canRestore unwinds ce.frames to callerFrameCount and checks whether a
	// handler exists at that depth. If no handler is found, the caller
	// re-panics and the next outer callWithUnwind unwinds further.
	canRestore(ce *callEngine, callerFrameCount int) bool
	// doRestore restores the callEngine state to the given stack frame depth.
	doRestore(ce *callEngine, callerFrameCount int)
}

// thrownException is the panic value for wasm exception propagation.
type thrownException struct {
	exception *wasm.Exception
	// Fields populated by canRestore for doRestore.
	clause *exceptionTableCatchClause
	values []uint64
}

func (t *thrownException) canRestore(ce *callEngine, callerFrameCount int) bool {
	ce.frames = ce.frames[:callerFrameCount]
	frame := ce.frames[callerFrameCount-1]
	t.clause, t.values = searchExceptionTable(t.exception, frame)
	return t.clause != nil
}

func (t *thrownException) doRestore(ce *callEngine, callerFrameCount int) {
	frame := ce.frames[callerFrameCount-1]
	ce.applyExceptionHandler(frame, t.clause, t.values)
	t.clause, t.values = nil, nil
}

// callEngine holds context per moduleEngine.Call, and shared across all the
// function calls originating from the same moduleEngine.Call execution.
//
// This implements api.Function.
type callEngine struct {
	internalapi.WazeroOnlyType

	// stack contains the operands.
	// Note that all the values are represented as uint64.
	stack []uint64

	// frames are the function call stack.
	frames []*callFrame

	// f is the initial function for this call engine.
	f *function

	// stackiterator for Listeners to walk frames and stack.
	stackIterator stackIterator
}

// matchCatchClause checks whether a single catch clause matches the given exception.
// Returns whether it matched and the values to push onto the stack.
func matchCatchClause(kind byte, clauseTag *wasm.TagInstance, exn *wasm.Exception) (matched bool, values []uint64) {
	switch kind {
	case wasm.CatchKindCatch:
		if exn.Tag == clauseTag {
			return true, slices.Clone(exn.Params)
		}
	case wasm.CatchKindCatchRef:
		if exn.Tag == clauseTag {
			values = slices.Clone(exn.Params)
			values = append(values, uint64(uintptr(unsafe.Pointer(exn))))
			return true, values
		}
	case wasm.CatchKindCatchAll:
		return true, nil
	case wasm.CatchKindCatchAllRef:
		return true, []uint64{uint64(uintptr(unsafe.Pointer(exn)))}
	}
	return false, nil
}

// searchExceptionTable searches the compiled function's static exception table
// for a handler matching the given exception at the current PC. Returns the
// matched clause and catch values, or nil if no handler matches. Searches
// backwards so inner try_tables (which have higher indices) are checked first.
// This function is pure — it does not modify callEngine state.
func searchExceptionTable(exn *wasm.Exception, frame *callFrame) (*exceptionTableCatchClause, []uint64) {
	table := frame.f.parent.exceptionTable
	pc := frame.pc
	for i := len(table) - 1; i >= 0; i-- {
		entry := &table[i]
		if pc < entry.startPC || pc >= entry.endPC {
			continue
		}
		for j := range entry.clauses {
			clause := &entry.clauses[j]
			var clauseTag *wasm.TagInstance
			if clause.kind == wasm.CatchKindCatch || clause.kind == wasm.CatchKindCatchRef {
				clauseTag = frame.f.moduleInstance.Tags[clause.tagIndex]
			}
			matched, values := matchCatchClause(clause.kind, clauseTag, exn)
			if matched {
				return clause, values
			}
		}
	}
	return nil, nil
}

// applyExceptionHandler applies a matched exception table clause to the callEngine state.
func (ce *callEngine) applyExceptionHandler(frame *callFrame, clause *exceptionTableCatchClause, values []uint64) {
	ce.stack = ce.stack[:frame.base-frame.f.funcType.ParamNumInUint64+clause.targetStackDepth]
	ce.stack = append(ce.stack, values...)
	frame.pc = clause.targetPC
}

// callWithUnwind calls the target function with support for stack unwinding
// (exception handling and snapshot restores). Returns true if the frame was
// unwound (caller should refresh frame/body/bodyLen and continue the loop).
// Returns false on normal return (caller should do frame.pc++).
func (ce *callEngine) callWithUnwind(ctx context.Context, m *wasm.ModuleInstance, tf *function) bool {
	// Short-circuit: skip defer/recover overhead when neither exception
	// handlers nor the snapshotter are active for the calling frame.
	frame := ce.frames[len(ce.frames)-1]
	if len(frame.f.parent.exceptionTable) == 0 && ctx.Value(expctxkeys.EnableSnapshotterKey{}) == nil {
		ce.callFunction(ctx, m, tf)
		return false
	}

	callerFrameCount := len(ce.frames)
	caught := false
	func() {
		defer func() {
			if r := recover(); r != nil {
				if v, ok := r.(restorable); ok && v.canRestore(ce, callerFrameCount) {
					v.doRestore(ce, callerFrameCount)
					caught = true
					return
				}
				panic(r)
			}
		}()
		ce.callFunction(ctx, m, tf)
	}()
	return caught
}

func (e *moduleEngine) newCallEngine(compiled *function) *callEngine {
	return &callEngine{f: compiled}
}

func (ce *callEngine) pushValue(v uint64) {
	ce.stack = append(ce.stack, v)
}

func (ce *callEngine) pushValues(v []uint64) {
	ce.stack = append(ce.stack, v...)
}

func (ce *callEngine) popValue() (v uint64) {
	// No need to check stack bound
	// as we can assume that all the operations
	// are valid thanks to validateFunction
	// at module validation phase
	// and interpreterir translation
	// before compilation.
	stackTopIndex := len(ce.stack) - 1
	v = ce.stack[stackTopIndex]
	ce.stack = ce.stack[:stackTopIndex]
	return
}

func (ce *callEngine) popValues(v []uint64) {
	stackTopIndex := len(ce.stack) - len(v)
	copy(v, ce.stack[stackTopIndex:])
	ce.stack = ce.stack[:stackTopIndex]
}

// peekValues peeks api.ValueType values from the stack and returns them.
func (ce *callEngine) peekValues(count int) []uint64 {
	if count == 0 {
		return nil
	}
	stackLen := len(ce.stack)
	return ce.stack[stackLen-count : stackLen]
}

func (ce *callEngine) drop(raw uint64) {
	r := inclusiveRangeFromU64(raw)
	if r.Start == -1 {
		return
	} else if r.Start == 0 {
		ce.stack = ce.stack[:int32(len(ce.stack))-1-r.End]
	} else {
		newStack := ce.stack[:int32(len(ce.stack))-1-r.End]
		newStack = append(newStack, ce.stack[int32(len(ce.stack))-r.Start:]...)
		ce.stack = newStack
	}
}

func (ce *callEngine) pushFrame(frame *callFrame) {
	if callStackCeiling <= len(ce.frames) {
		panic(wasmruntime.ErrRuntimeStackOverflow)
	}
	ce.frames = append(ce.frames, frame)
}

func (ce *callEngine) popFrame() (frame *callFrame) {
	// No need to check stack bound as we can assume that all the operations are valid thanks to validateFunction at
	// module validation phase and interpreterir translation before compilation.
	oneLess := len(ce.frames) - 1
	frame = ce.frames[oneLess]
	ce.frames = ce.frames[:oneLess]
	return
}

type callFrame struct {
	// pc is the program counter representing the current position in code.body.
	pc uint64
	// f is the compiled function used in this function frame.
	f *function
	// base index in the frame of this function, used to detect the count of
	// values on the stack.
	base int
}

type compiledFunction struct {
	source              *wasm.Module
	body                []unionOperation
	exceptionTable      []exceptionTableEntry
	listener            experimental.FunctionListener
	offsetsInWasmBinary []uint64
	hostFn              interface{}
	ensureTermination   bool
	index               wasm.Index
}

type function struct {
	funcType       *wasm.FunctionType
	moduleInstance *wasm.ModuleInstance
	typeID         wasm.FunctionTypeID
	parent         *compiledFunction
}

// functionFromUintptr resurrects the original *function from the given uintptr
// which comes from either funcref table or OpcodeRefFunc instruction.
func functionFromUintptr(ptr uintptr) *function {
	// Wraps ptrs as the double pointer in order to avoid the unsafe access as detected by race detector.
	//
	// For example, if we have (*function)(unsafe.Pointer(ptr)) instead, then the race detector's "checkptr"
	// subroutine wanrs as "checkptr: pointer arithmetic result points to invalid allocation"
	// https://github.com/golang/go/blob/go1.24.0/src/runtime/checkptr.go#L69
	var wrapped *uintptr = &ptr
	return *(**function)(unsafe.Pointer(wrapped))
}

type snapshot struct {
	stack  []uint64
	frames []*callFrame
	pc     uint64

	ret []uint64

	ce *callEngine
}

// Snapshot implements the same method as documented on experimental.Snapshotter.
func (ce *callEngine) Snapshot() experimental.Snapshot {
	return &snapshot{
		stack:  slices.Clone(ce.stack),
		frames: slices.Clone(ce.frames),
		ce:     ce,
	}
}

// Restore implements the same method as documented on experimental.Snapshot.
func (s *snapshot) Restore(ret []uint64) {
	s.ret = ret
	panic(s)
}

func (s *snapshot) canRestore(ce *callEngine, _ int) bool {
	return s.ce == ce
}

func (s *snapshot) doRestore(_ *callEngine, _ int) {
	ce := s.ce

	ce.stack = s.stack
	ce.frames = s.frames
	ce.frames[len(ce.frames)-1].pc = s.pc

	copy(ce.stack[len(ce.stack)-len(s.ret):], s.ret)
}

// Error implements the same method on error.
func (s *snapshot) Error() string {
	return "unhandled snapshot restore, this generally indicates restore was called from a different " +
		"exported function invocation than snapshot"
}

// stackIterator implements experimental.StackIterator.
type stackIterator struct {
	stack   []uint64
	frames  []*callFrame
	started bool
	fn      *function
	pc      uint64
}

func (si *stackIterator) reset(stack []uint64, frames []*callFrame, f *function) {
	si.fn = f
	si.pc = 0
	si.stack = stack
	si.frames = frames
	si.started = false
}

func (si *stackIterator) clear() {
	si.stack = nil
	si.frames = nil
	si.started = false
	si.fn = nil
}

// Next implements the same method as documented on experimental.StackIterator.
func (si *stackIterator) Next() bool {
	if !si.started {
		si.started = true
		return true
	}

	if len(si.frames) == 0 {
		return false
	}

	frame := si.frames[len(si.frames)-1]
	si.stack = si.stack[:frame.base]
	si.fn = frame.f
	si.pc = frame.pc
	si.frames = si.frames[:len(si.frames)-1]
	return true
}

// Function implements the same method as documented on
// experimental.StackIterator.
func (si *stackIterator) Function() experimental.InternalFunction {
	return internalFunction{si.fn}
}

// ProgramCounter implements the same method as documented on
// experimental.StackIterator.
func (si *stackIterator) ProgramCounter() experimental.ProgramCounter {
	return experimental.ProgramCounter(si.pc)
}

// internalFunction implements experimental.InternalFunction.
type internalFunction struct{ *function }

// Definition implements the same method as documented on
// experimental.InternalFunction.
func (f internalFunction) Definition() api.FunctionDefinition {
	return f.definition()
}

// SourceOffsetForPC implements the same method as documented on
// experimental.InternalFunction.
func (f internalFunction) SourceOffsetForPC(pc experimental.ProgramCounter) uint64 {
	offsetsMap := f.parent.offsetsInWasmBinary
	if uint64(pc) < uint64(len(offsetsMap)) {
		return offsetsMap[pc]
	}
	return 0
}

// interpreter mode doesn't maintain call frames in the stack, so pass the zero size to the IR.
const callFrameStackSize = 0

// CompileModule implements the same method as documented on wasm.Engine.
func (e *engine) CompileModule(_ context.Context, module *wasm.Module, listeners []experimental.FunctionListener, ensureTermination bool) error {
	if _, ok := e.getCompiledFunctions(module, true); ok { // cache hit!
		return nil
	}

	funcs := make([]compiledFunction, len(module.FunctionSection))
	irCompiler, err := newCompiler(e.enabledFeatures, callFrameStackSize, module, ensureTermination)
	if err != nil {
		return err
	}
	imported := module.ImportFunctionCount
	for i := range module.CodeSection {
		var lsn experimental.FunctionListener
		if i < len(listeners) {
			lsn = listeners[i]
		}

		compiled := &funcs[i]
		// If this is the host function, there's nothing to do as the runtime representation of
		// host function in interpreter is its Go function itself as opposed to Wasm functions,
		// which need to be compiled down to
		if codeSeg := &module.CodeSection[i]; codeSeg.GoFunc != nil {
			compiled.hostFn = codeSeg.GoFunc
		} else {
			ir, err := irCompiler.Next()
			if err != nil {
				return err
			}
			err = e.lowerIR(ir, compiled)
			if err != nil {
				def := module.FunctionDefinition(uint32(i) + module.ImportFunctionCount)
				return fmt.Errorf("failed to lower func[%s] to interpreterir: %w", def.DebugName(), err)
			}
		}
		compiled.source = module
		compiled.ensureTermination = ensureTermination
		compiled.listener = lsn
		compiled.index = imported + uint32(i)
	}
	e.addCompiledFunctions(module, funcs)
	return nil
}

// NewModuleEngine implements the same method as documented on wasm.Engine.
func (e *engine) NewModuleEngine(module *wasm.Module, instance *wasm.ModuleInstance) (wasm.ModuleEngine, error) {
	me := &moduleEngine{
		parentEngine: e,
		functions:    make([]function, len(module.FunctionSection)+int(module.ImportFunctionCount)),
	}

	codes, ok := e.getCompiledFunctions(module, false)
	if !ok {
		return nil, errors.New("source module must be compiled before instantiation")
	}

	for i := range codes {
		c := &codes[i]
		offset := i + int(module.ImportFunctionCount)
		typeIndex := module.FunctionSection[i]
		me.functions[offset] = function{
			moduleInstance: instance,
			typeID:         instance.TypeIDs[typeIndex],
			funcType:       &module.TypeSection[typeIndex],
			parent:         c,
		}
	}
	return me, nil
}

// lowerIR lowers the interpreterir operations to engine friendly struct.
func (e *engine) lowerIR(ir *compilationResult, ret *compiledFunction) error {
	// Copy the body from the result.
	ret.body = slices.Clone(ir.Operations)
	// Also copy the offsets if necessary.
	if offsets := ir.IROperationSourceOffsetsInWasmBinary; len(offsets) > 0 {
		ret.offsetsInWasmBinary = slices.Clone(offsets)
	}

	labelAddressResolutions := [labelKindNum][]uint64{}

	// First, we iterate all labels, and resolve the address.
	for i := range ret.body {
		op := &ret.body[i]
		switch op.Kind {
		case operationKindLabel:
			label := label(op.U1)
			address := uint64(i)

			kind, fid := label.Kind(), label.FrameID()
			frameToAddresses := labelAddressResolutions[label.Kind()]
			// Expand the slice if necessary.
			if diff := fid - len(frameToAddresses) + 1; diff > 0 {
				frameToAddresses = append(frameToAddresses, make([]uint64, diff)...)
			}
			frameToAddresses[fid] = address
			labelAddressResolutions[kind] = frameToAddresses
		}
	}

	// Then resolve the label as the index to the body.
	for i := range ret.body {
		op := &ret.body[i]
		switch op.Kind {
		case operationKindBr:
			e.setLabelAddress(&op.U1, label(op.U1), labelAddressResolutions)
		case operationKindBrIf:
			e.setLabelAddress(&op.U1, label(op.U1), labelAddressResolutions)
			e.setLabelAddress(&op.U2, label(op.U2), labelAddressResolutions)
		case operationKindBrTable:
			for j := 0; j < len(op.Us); j += 2 {
				target := op.Us[j]
				e.setLabelAddress(&op.Us[j], label(target), labelAddressResolutions)
			}
		case operationKindTailCallReturnCallIndirect:
			e.setLabelAddress(&op.Us[1], label(op.Us[1]), labelAddressResolutions)
		case operationKindBrOnNull:
			e.setLabelAddress(&op.U1, label(op.U1), labelAddressResolutions)
			e.setLabelAddress(&op.U2, label(op.U2), labelAddressResolutions)
		case operationKindBrOnNonNull:
			e.setLabelAddress(&op.U1, label(op.U1), labelAddressResolutions)
			e.setLabelAddress(&op.U2, label(op.U2), labelAddressResolutions)
		case operationKindReturnCallRef:
			e.setLabelAddress(&op.Us[1], label(op.Us[1]), labelAddressResolutions)
		}
	}

	// Resolve exception table entries (translate labels to PC).
	if len(ir.PendingExceptionTable) > 0 {
		ret.exceptionTable = make([]exceptionTableEntry, len(ir.PendingExceptionTable))
		for i, pe := range ir.PendingExceptionTable {
			contLabel := newLabel(labelKindContinuation, pe.continuationFrameID)
			var endPC uint64
			e.setLabelAddress(&endPC, contLabel, labelAddressResolutions)

			clauses := make([]exceptionTableCatchClause, len(pe.clauses))
			for j, clause := range pe.clauses {
				var targetPC uint64
				e.setLabelAddress(&targetPC, clause.targetLabel, labelAddressResolutions)
				clauses[j] = exceptionTableCatchClause{
					kind:             clause.kind,
					tagIndex:         clause.tagIndex,
					targetPC:         targetPC,
					targetStackDepth: clause.targetStackDepth,
				}
			}

			ret.exceptionTable[i] = exceptionTableEntry{
				startPC: uint64(pe.startOpIndex),
				endPC:   endPC,
				clauses: clauses,
			}
		}
	}

	return nil
}

func (e *engine) setLabelAddress(op *uint64, label label, labelAddressResolutions [labelKindNum][]uint64) {
	if label.IsReturnTarget() {
		// Jmp to the end of the possible binary.
		*op = math.MaxUint64
	} else {
		*op = labelAddressResolutions[label.Kind()][label.FrameID()]
	}
}

// ResolveImportedFunction implements wasm.ModuleEngine.
func (e *moduleEngine) ResolveImportedFunction(index, descFunc, indexInImportedModule wasm.Index, importedModuleEngine wasm.ModuleEngine) {
	imported := importedModuleEngine.(*moduleEngine)
	e.functions[index] = imported.functions[indexInImportedModule]
}

// ResolveImportedMemory implements wasm.ModuleEngine.
func (e *moduleEngine) ResolveImportedMemory(wasm.ModuleEngine) {}

// DoneInstantiation implements wasm.ModuleEngine.
func (e *moduleEngine) DoneInstantiation() {}

// FunctionInstanceReference implements the same method as documented on wasm.ModuleEngine.
func (e *moduleEngine) FunctionInstanceReference(funcIndex wasm.Index) wasm.Reference {
	return uintptr(unsafe.Pointer(&e.functions[funcIndex]))
}

// NewFunction implements the same method as documented on wasm.ModuleEngine.
func (e *moduleEngine) NewFunction(index wasm.Index) (ce api.Function) {
	// Note: The input parameters are pre-validated, so a compiled function is only absent on close. Updates to
	// code on close aren't locked, neither is this read.
	compiled := &e.functions[index]
	return e.newCallEngine(compiled)
}

// LookupFunction implements the same method as documented on wasm.ModuleEngine.
func (e *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.FunctionTypeID, tableOffset wasm.Index) (*wasm.ModuleInstance, wasm.Index) {
	if tableOffset >= uint32(len(t.References)) {
		panic(wasmruntime.ErrRuntimeInvalidTableAccess)
	}
	rawPtr := t.References[tableOffset]
	if rawPtr == 0 {
		panic(wasmruntime.ErrRuntimeInvalidTableAccess)
	}

	tf := functionFromUintptr(rawPtr)
	if tf.typeID != typeId {
		panic(wasmruntime.ErrRuntimeIndirectCallTypeMismatch)
	}
	return tf.moduleInstance, tf.parent.index
}

// Definition implements the same method as documented on api.Function.
func (ce *callEngine) Definition() api.FunctionDefinition {
	return ce.f.definition()
}

func (f *function) definition() api.FunctionDefinition {
	compiled := f.parent
	return compiled.source.FunctionDefinition(compiled.index)
}

// Call implements the same method as documented on api.Function.
func (ce *callEngine) Call(ctx context.Context, params ...uint64) (results []uint64, err error) {
	ft := ce.f.funcType
	if n := ft.ParamNumInUint64; n != len(params) {
		return nil, fmt.Errorf("expected %d params, but passed %d", n, len(params))
	}
	return ce.call(ctx, params, nil)
}

// CallWithStack implements the same method as documented on api.Function.
func (ce *callEngine) CallWithStack(ctx context.Context, stack []uint64) error {
	params, results, err := wasm.SplitCallStack(ce.f.funcType, stack)
	if err != nil {
		return err
	}
	_, err = ce.call(ctx, params, results)
	return err
}

func (ce *callEngine) call(ctx context.Context, params, results []uint64) (_ []uint64, err error) {
	m := ce.f.moduleInstance
	if ce.f.parent.ensureTermination {
		select {
		case <-ctx.Done():
			// If the provided context is already done, close the call context
			// and return the error.
			m.CloseWithCtxErr(ctx)
			return nil, m.FailIfClosed()
		default:
		}
	}

	if ctx.Value(expctxkeys.EnableSnapshotterKey{}) != nil {
		ctx = context.WithValue(ctx, expctxkeys.SnapshotterKey{}, ce)
	}

	defer func() {
		// If the module closed during the call, and the call didn't err for another reason, set an ExitError.
		if err == nil {
			err = m.FailIfClosed()
		}
		// TODO: ^^ Will not fail if the function was imported from a closed module.

		if v := recover(); v != nil {
			err = ce.recoverOnCall(ctx, m, v)
		}
	}()

	ce.pushValues(params)

	if ce.f.parent.ensureTermination {
		done := m.CloseModuleOnCanceledOrTimeout(ctx)
		defer done()
	}

	ce.callFunction(ctx, m, ce.f)

	// This returns a safe copy of the results, instead of a slice view. If we
	// returned a re-slice, the caller could accidentally or purposefully
	// corrupt the stack of subsequent calls.
	ft := ce.f.funcType
	if results == nil && ft.ResultNumInUint64 > 0 {
		results = make([]uint64, ft.ResultNumInUint64)
	}
	ce.popValues(results)
	return results, nil
}

// functionListenerInvocation captures arguments needed to perform function
// listener invocations when unwinding the call stack.
type functionListenerInvocation struct {
	experimental.FunctionListener
	def api.FunctionDefinition
}

// recoverOnCall takes the recovered value `recoverOnCall`, and wraps it
// with the call frame stack traces. Also, reset the state of callEngine
// so that it can be used for the subsequent calls.
func (ce *callEngine) recoverOnCall(ctx context.Context, m *wasm.ModuleInstance, v interface{}) (err error) {
	if s, ok := v.(*snapshot); ok {
		// A snapshot that wasn't handled was created by a different call engine possibly from a nested wasm invocation,
		// let it propagate up to be handled by the caller.
		panic(s)
	}

	// If an exception reached the top level without being caught, convert it to an uncaught exception error.
	if _, ok := v.(*thrownException); ok {
		v = wasmruntime.ErrRuntimeUncaughtException
	}

	builder := wasmdebug.NewErrorBuilder()
	frameCount := len(ce.frames)
	functionListeners := make([]functionListenerInvocation, 0, 16)

	if frameCount > wasmdebug.MaxFrames {
		frameCount = wasmdebug.MaxFrames
	}
	for i := 0; i < frameCount; i++ {
		frame := ce.popFrame()
		f := frame.f
		def := f.definition()
		var sources []string
		if parent := frame.f.parent; parent.body != nil && len(parent.offsetsInWasmBinary) > 0 {
			sources = parent.source.DWARFLines.Line(parent.offsetsInWasmBinary[frame.pc])
		}
		builder.AddFrame(def.DebugName(), def.ParamTypes(), def.ResultTypes(), sources)
		if f.parent.listener != nil {
			functionListeners = append(functionListeners, functionListenerInvocation{
				FunctionListener: f.parent.listener,
				def:              f.definition(),
			})
		}
	}

	err = builder.FromRecovered(v)
	for i := range functionListeners {
		functionListeners[i].Abort(ctx, m, functionListeners[i].def, err)
	}

	// Allows the reuse of CallEngine.
	ce.stack, ce.frames = ce.stack[:0], ce.frames[:0]
	return
}

func (ce *callEngine) callFunction(ctx context.Context, m *wasm.ModuleInstance, f *function) {
	if f.parent.hostFn != nil {
		ce.callGoFuncWithStack(ctx, m, f)
	} else if lsn := f.parent.listener; lsn != nil {
		ce.callNativeFuncWithListener(ctx, m, f, lsn)
	} else {
		ce.callNativeFunc(ctx, m, f)
	}
}

func (ce *callEngine) callGoFunc(ctx context.Context, m *wasm.ModuleInstance, f *function, stack []uint64) {
	typ := f.funcType
	lsn := f.parent.listener
	if lsn != nil {
		params := stack[:typ.ParamNumInUint64]
		ce.stackIterator.reset(ce.stack, ce.frames, f)
		lsn.Before(ctx, m, f.definition(), params, &ce.stackIterator)
		ce.stackIterator.clear()
	}
	frame := &callFrame{f: f, base: len(ce.stack)}
	ce.pushFrame(frame)

	fn := f.parent.hostFn
	switch fn := fn.(type) {
	case api.GoModuleFunction:
		fn.Call(ctx, m, stack)
	case api.GoFunction:
		fn.Call(ctx, stack)
	}

	ce.popFrame()
	if lsn != nil {
		// TODO: This doesn't get the error due to use of panic to propagate them.
		results := stack[:typ.ResultNumInUint64]
		lsn.After(ctx, m, f.definition(), results)
	}
}

func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance, f *function) {
	frame := &callFrame{f: f, base: len(ce.stack)}
	moduleInst := f.moduleInstance
	functions := moduleInst.Engine.(*moduleEngine).functions
	memoryInst := moduleInst.MemoryInstance
	globals := moduleInst.Globals
	tables := moduleInst.Tables
	typeIDs := moduleInst.TypeIDs
	dataInstances := moduleInst.DataInstances
	elementInstances := moduleInst.ElementInstances
	ce.pushFrame(frame)
	body := frame.f.parent.body
	bodyLen := uint64(len(body))
	for frame.pc < bodyLen {
		op := &body[frame.pc]
		// TODO: add description of each operation/case
		// on, for example, how many args are used,
		// how the stack is modified, etc.
		switch op.Kind {
		case operationKindBuiltinFunctionCheckExitCode:
			if err := m.FailIfClosed(); err != nil {
				panic(err)
			}
			frame.pc++
		case operationKindUnreachable:
			panic(wasmruntime.ErrRuntimeUnreachable)
		case operationKindBr:
			frame.pc = op.U1
		case operationKindBrIf:
			if ce.popValue() > 0 {
				ce.drop(op.U3)
				frame.pc = op.U1
			} else {
				frame.pc = op.U2
			}
		case operationKindBrTable:
			v := ce.popValue()
			defaultAt := uint64(len(op.Us))/2 - 1
			if v > defaultAt {
				v = defaultAt
			}
			v *= 2
			ce.drop(op.Us[v+1])
			frame.pc = op.Us[v]
		case operationKindCall:
			frameUnwound := ce.callWithUnwind(ctx, f.moduleInstance, &functions[op.U1])
			if frameUnwound {
				frame = ce.frames[len(ce.frames)-1]
				body = frame.f.parent.body
				bodyLen = uint64(len(body))
				continue
			}
			frame.pc++
		case operationKindCallIndirect:
			offset := ce.popValue()
			table := tables[op.U2]
			tf := ce.functionForOffset(table, offset, typeIDs[op.U1])

			frameUnwound := ce.callWithUnwind(ctx, f.moduleInstance, tf)
			if frameUnwound {
				frame = ce.frames[len(ce.frames)-1]
				body = frame.f.parent.body
				bodyLen = uint64(len(body))
				continue
			}
			frame.pc++
		case operationKindDrop:
			ce.drop(op.U1)
			frame.pc++
		case operationKindSelect:
			c := ce.popValue()
			if op.B3 { // Target is vector.
				x2Hi, x2Lo := ce.popValue(), ce.popValue()
				if c == 0 {
					_, _ = ce.popValue(), ce.popValue() // discard the x1's lo and hi bits.
					ce.pushValue(x2Lo)
					ce.pushValue(x2Hi)
				}
			} else {
				v2 := ce.popValue()
				if c == 0 {
					_ = ce.popValue()
					ce.pushValue(v2)
				}
			}
			frame.pc++
		case operationKindPick:
			index := len(ce.stack) - 1 - int(op.U1)
			ce.pushValue(ce.stack[index])
			if op.B3 { // V128 value target.
				ce.pushValue(ce.stack[index+1])
			}
			frame.pc++
		case operationKindSet:
			if op.B3 { // V128 value target.
				lowIndex := len(ce.stack) - 1 - int(op.U1)
				highIndex := lowIndex + 1
				hi, lo := ce.popValue(), ce.popValue()
				ce.stack[lowIndex], ce.stack[highIndex] = lo, hi
			} else {
				index := len(ce.stack) - 1 - int(op.U1)
				ce.stack[index] = ce.popValue()
			}
			frame.pc++
		case operationKindGlobalGet:
			g := globals[op.U1]
			ce.pushValue(g.Val)
			if g.Type.ValType == wasm.ValueTypeV128 {
				ce.pushValue(g.ValHi)
			}
			frame.pc++
		case operationKindGlobalSet:
			g := globals[op.U1]
			if g.Type.ValType == wasm.ValueTypeV128 {
				g.ValHi = ce.popValue()
			}
			g.Val = ce.popValue()
			frame.pc++
		case operationKindLoad:
			offset := ce.popMemoryOffset(op)
			switch unsignedType(op.B1) {
			case unsignedTypeI32, unsignedTypeF32:
				if val, ok := memoryInst.ReadUint32Le(offset); !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				} else {
					ce.pushValue(uint64(val))
				}
			case unsignedTypeI64, unsignedTypeF64:
				if val, ok := memoryInst.ReadUint64Le(offset); !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				} else {
					ce.pushValue(val)
				}
			}
			frame.pc++
		case operationKindLoad8:
			val, ok := memoryInst.ReadByte(ce.popMemoryOffset(op))
			if !ok {
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}

			switch signedInt(op.B1) {
			case signedInt32:
				ce.pushValue(uint64(uint32(int8(val))))
			case signedInt64:
				ce.pushValue(uint64(int8(val)))
			case signedUint32, signedUint64:
				ce.pushValue(uint64(val))
			}
			frame.pc++
		case operationKindLoad16:

			val, ok := memoryInst.ReadUint16Le(ce.popMemoryOffset(op))
			if !ok {
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}

			switch signedInt(op.B1) {
			case signedInt32:
				ce.pushValue(uint64(uint32(int16(val))))
			case signedInt64:
				ce.pushValue(uint64(int16(val)))
			case signedUint32, signedUint64:
				ce.pushValue(uint64(val))
			}
			frame.pc++
		case operationKindLoad32:
			val, ok := memoryInst.ReadUint32Le(ce.popMemoryOffset(op))
			if !ok {
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}

			if op.B1 == 1 { // Signed
				ce.pushValue(uint64(int32(val)))
			} else {
				ce.pushValue(uint64(val))
			}
			frame.pc++
		case operationKindStore:
			val := ce.popValue()
			offset := ce.popMemoryOffset(op)
			switch unsignedType(op.B1) {
			case unsignedTypeI32, unsignedTypeF32:
				if !memoryInst.WriteUint32Le(offset, uint32(val)) {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
			case unsignedTypeI64, unsignedTypeF64:
				if !memoryInst.WriteUint64Le(offset, val) {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
			}
			frame.pc++
		case operationKindStore8:
			val := byte(ce.popValue())
			offset := ce.popMemoryOffset(op)
			if !memoryInst.WriteByte(offset, val) {
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}
			frame.pc++
		case operationKindStore16:
			val := uint16(ce.popValue())
			offset := ce.popMemoryOffset(op)
			if !memoryInst.WriteUint16Le(offset, val) {
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}
			frame.pc++
		case operationKindStore32:
			val := uint32(ce.popValue())
			offset := ce.popMemoryOffset(op)
			if !memoryInst.WriteUint32Le(offset, val) {
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}
			frame.pc++
		case operationKindMemorySize:
			ce.pushValue(uint64(memoryInst.Pages()))
			frame.pc++
		case operationKindMemoryGrow:
			n := ce.popValue()
			if res, ok := memoryInst.Grow(uint32(n)); !ok {
				ce.pushValue(uint64(0xffffffff)) // = -1 in signed 32-bit integer.
			} else {
				ce.pushValue(uint64(res))
			}
			frame.pc++
		case operationKindConstI32, operationKindConstI64,
			operationKindConstF32, operationKindConstF64:
			ce.pushValue(op.U1)
			frame.pc++
		case operationKindEq:
			var b bool
			switch unsignedType(op.B1) {
			case unsignedTypeI32:
				v2, v1 := ce.popValue(), ce.popValue()
				b = uint32(v1) == uint32(v2)
			case unsignedTypeI64:
				v2, v1 := ce.popValue(), ce.popValue()
				b = v1 == v2
			case unsignedTypeF32:
				v2, v1 := ce.popValue(), ce.popValue()
				b = math.Float32frombits(uint32(v2)) == math.Float32frombits(uint32(v1))
			case unsignedTypeF64:
				v2, v1 := ce.popValue(), ce.popValue()
				b = math.Float64frombits(v2) == math.Float64frombits(v1)
			}
			if b {
				ce.pushValue(1)
			} else {
				ce.pushValue(0)
			}
			frame.pc++
		case operationKindNe:
			var b bool
			switch unsignedType(op.B1) {
			case unsignedTypeI32:
				v2, v1 := ce.popValue(), ce.popValue()
				b = uint32(v1) != uint32(v2)
			case unsignedTypeI64:
				v2, v1 := ce.popValue(), ce.popValue()
				b = v1 != v2
			case unsignedTypeF32:
				v2, v1 := ce.popValue(), ce.popValue()
				b = math.Float32frombits(uint32(v2)) != math.Float32frombits(uint32(v1))
			case unsignedTypeF64:
				v2, v1 := ce.popValue(), ce.popValue()
				b = math.Float64frombits(v2) != math.Float64frombits(v1)
			}
			if b {
				ce.pushValue(1)
			} else {
				ce.pushValue(0)
			}
			frame.pc++
		case operationKindEqz:
			v := ce.popValue()
			if unsignedInt(op.B1) == unsignedInt32 {
				v = uint64(uint32(v))
			}
			if v == 0 {
				ce.pushValue(1)
			} else {
				ce.pushValue(0)
			}
			frame.pc++
		case operationKindLt:
			v2 := ce.popValue()
			v1 := ce.popValue()
			var b bool
			switch signedType(op.B1) {
			case signedTypeInt32:
				b = int32(v1) < int32(v2)
			case signedTypeInt64:
				b = int64(v1) < int64(v2)
			case signedTypeUint32:
				b = uint32(v1) < uint32(v2)
			case signedTypeUint64:
				b = v1 < v2
			case signedTypeFloat32:
				b = math.Float32frombits(uint32(v1)) < math.Float32frombits(uint32(v2))
			case signedTypeFloat64:
				b = math.Float64frombits(v1) < math.Float64frombits(v2)
			}
			if b {
				ce.pushValue(1)
			} else {
				ce.pushValue(0)
			}
			frame.pc++
		case operationKindGt:
			v2 := ce.popValue()
			v1 := ce.popValue()
			var b bool
			switch signedType(op.B1) {
			case signedTypeInt32:
				b = int32(v1) > int32(v2)
			case signedTypeInt64:
				b = int64(v1) > int64(v2)
			case signedTypeUint32:
				b = uint32(v1) > uint32(v2)
			case signedTypeUint64:
				b = v1 > v2
			case signedTypeFloat32:
				b = math.Float32frombits(uint32(v1)) > math.Float32frombits(uint32(v2))
			case signedTypeFloat64:
				b = math.Float64frombits(v1) > math.Float64frombits(v2)
			}
			if b {
				ce.pushValue(1)
			} else {
				ce.pushValue(0)
			}
			frame.pc++
		case operationKindLe:
			v2 := ce.popValue()
			v1 := ce.popValue()
			var b bool
			switch signedType(op.B1) {
			case signedTypeInt32:
				b = int32(v1) <= int32(v2)
			case signedTypeInt64:
				b = int64(v1) <= int64(v2)
			case signedTypeUint32:
				b = uint32(v1) <= uint32(v2)
			case signedTypeUint64:
				b = v1 <= v2
			case signedTypeFloat32:
				b = math.Float32frombits(uint32(v1)) <= math.Float32frombits(uint32(v2))
			case signedTypeFloat64:
				b = math.Float64frombits(v1) <= math.Float64frombits(v2)
			}
			if b {
				ce.pushValue(1)
			} else {
				ce.pushValue(0)
			}
			frame.pc++
		case operationKindGe:
			v2 := ce.popValue()
			v1 := ce.popValue()
			var b bool
			switch signedType(op.B1) {
			case signedTypeInt32:
				b = int32(v1) >= int32(v2)
			case signedTypeInt64:
				b = int64(v1) >= int64(v2)
			case signedTypeUint32:
				b = uint32(v1) >= uint32(v2)
			case signedTypeUint64:
				b = v1 >= v2
			case signedTypeFloat32:
				b = math.Float32frombits(uint32(v1)) >= math.Float32frombits(uint32(v2))
			case signedTypeFloat64:
				b = math.Float64frombits(v1) >= math.Float64frombits(v2)
			}
			if b {
				ce.pushValue(1)
			} else {
				ce.pushValue(0)
			}
			frame.pc++
		case operationKindAdd:
			v2 := ce.popValue()
			v1 := ce.popValue()
			switch unsignedType(op.B1) {
			case unsignedTypeI32:
				v := uint32(v1) + uint32(v2)
				ce.pushValue(uint64(v))
			case unsignedTypeI64:
				ce.pushValue(v1 + v2)
			case unsignedTypeF32:
				ce.pushValue(addFloat32bits(uint32(v1), uint32(v2)))
			case unsignedTypeF64:
				v := math.Float64frombits(v1) + math.Float64frombits(v2)
				ce.pushValue(math.Float64bits(v))
			}
			frame.pc++
		case operationKindSub:
			v2 := ce.popValue()
			v1 := ce.popValue()
			switch unsignedType(op.B1) {
			case unsignedTypeI32:
				ce.pushValue(uint64(uint32(v1) - uint32(v2)))
			case unsignedTypeI64:
				ce.pushValue(v1 - v2)
			case unsignedTypeF32:
				ce.pushValue(subFloat32bits(uint32(v1), uint32(v2)))
			case unsignedTypeF64:
				v := math.Float64frombits(v1) - math.Float64frombits(v2)
				ce.pushValue(math.Float64bits(v))
			}
			frame.pc++
		case operationKindMul:
			v2 := ce.popValue()
			v1 := ce.popValue()
			switch unsignedType(op.B1) {
			case unsignedTypeI32:
				ce.pushValue(uint64(uint32(v1) * uint32(v2)))
			case unsignedTypeI64:
				ce.pushValue(v1 * v2)
			case unsignedTypeF32:
				ce.pushValue(mulFloat32bits(uint32(v1), uint32(v2)))
			case unsignedTypeF64:
				v := math.Float64frombits(v2) * math.Float64frombits(v1)
				ce.pushValue(math.Float64bits(v))
			}
			frame.pc++
		case operationKindClz:
			v := ce.popValue()
			if op.B1 == 0 {
				// unsignedInt32
				ce.pushValue(uint64(bits.LeadingZeros32(uint32(v))))
			} else {
				// unsignedInt64
				ce.pushValue(uint64(bits.LeadingZeros64(v)))
			}
			frame.pc++
		case operationKindCtz:
			v := ce.popValue()
			if op.B1 == 0 {
				// unsignedInt32
				ce.pushValue(uint64(bits.TrailingZeros32(uint32(v))))
			} else {
				// unsignedInt64
				ce.pushValue(uint64(bits.TrailingZeros64(v)))
			}
			frame.pc++
		case operationKindPopcnt:
			v := ce.popValue()
			if op.B1 == 0 {
				// unsignedInt32
				ce.pushValue(uint64(bits.OnesCount32(uint32(v))))
			} else {
				// unsignedInt64
				ce.pushValue(uint64(bits.OnesCount64(v)))
			}
			frame.pc++
		case operationKindDiv:
			// If an integer, check we won't divide by zero.
			t := signedType(op.B1)
			v2, v1 := ce.popValue(), ce.popValue()
			switch t {
			case signedTypeFloat32, signedTypeFloat64: // not integers
			case signedTypeInt32, signedTypeUint32:
				if uint32(v2) == 0 {
					panic(wasmruntime.ErrRuntimeIntegerDivideByZero)
				}
			default:
				if v2 == 0 {
					panic(wasmruntime.ErrRuntimeIntegerDivideByZero)
				}
			}

			switch t {
			case signedTypeInt32:
				d := int32(v2)
				n := int32(v1)
				if n == math.MinInt32 && d == -1 {
					panic(wasmruntime.ErrRuntimeIntegerOverflow)
				}
				ce.pushValue(uint64(uint32(n / d)))
			case signedTypeInt64:
				d := int64(v2)
				n := int64(v1)
				if n == math.MinInt64 && d == -1 {
					panic(wasmruntime.ErrRuntimeIntegerOverflow)
				}
				ce.pushValue(uint64(n / d))
			case signedTypeUint32:
				d := uint32(v2)
				n := uint32(v1)
				ce.pushValue(uint64(n / d))
			case signedTypeUint64:
				d := v2
				n := v1
				ce.pushValue(n / d)
			case signedTypeFloat32:
				ce.pushValue(divFloat32bits(uint32(v1), uint32(v2)))
			case signedTypeFloat64:
				ce.pushValue(math.Float64bits(math.Float64frombits(v1) / math.Float64frombits(v2)))
			}
			frame.pc++
		case operationKindRem:
			v2, v1 := ce.popValue(), ce.popValue()
			switch signedInt(op.B1) {
			case signedInt32, signedUint32:
				if uint32(v2) == 0 {
					panic(wasmruntime.ErrRuntimeIntegerDivideByZero)
				}
			default:
				if v2 == 0 {
					panic(wasmruntime.ErrRuntimeIntegerDivideByZero)
				}
			}
			switch signedInt(op.B1) {
			case signedInt32:
				d := int32(v2)
				n := int32(v1)
				ce.pushValue(uint64(uint32(n % d)))
			case signedInt64:
				d := int64(v2)
				n := int64(v1)
				ce.pushValue(uint64(n % d))
			case signedUint32:
				d := uint32(v2)
				n := uint32(v1)
				ce.pushValue(uint64(n % d))
			case signedUint64:
				d := v2
				n := v1
				ce.pushValue(n % d)
			}
			frame.pc++
		case operationKindAnd:
			v2 := ce.popValue()
			v1 := ce.popValue()
			if op.B1 == 0 {
				// unsignedInt32
				ce.pushValue(uint64(uint32(v2) & uint32(v1)))
			} else {
				// unsignedInt64
				ce.pushValue(uint64(v2 & v1))
			}
			frame.pc++
		case operationKindOr:
			v2 := ce.popValue()
			v1 := ce.popValue()
			if op.B1 == 0 {
				// unsignedInt32
				ce.pushValue(uint64(uint32(v2) | uint32(v1)))
			} else {
				// unsignedInt64
				ce.pushValue(uint64(v2 | v1))
			}
			frame.pc++
		case operationKindXor:
			v2 := ce.popValue()
			v1 := ce.popValue()
			if op.B1 == 0 {
				// unsignedInt32
				ce.pushValue(uint64(uint32(v2) ^ uint32(v1)))
			} else {
				// unsignedInt64
				ce.pushValue(uint64(v2 ^ v1))
			}
			frame.pc++
		case operationKindShl:
			v2 := ce.popValue()
			v1 := ce.popValue()
			if op.B1 == 0 {
				// unsignedInt32
				ce.pushValue(uint64(uint32(v1) << (uint32(v2) % 32)))
			} else {
				// unsignedInt64
				ce.pushValue(v1 << (v2 % 64))
			}
			frame.pc++
		case operationKindShr:
			v2 := ce.popValue()
			v1 := ce.popValue()
			switch signedInt(op.B1) {
			case signedInt32:
				ce.pushValue(uint64(uint32(int32(v1) >> (uint32(v2) % 32))))
			case signedInt64:
				ce.pushValue(uint64(int64(v1) >> (v2 % 64)))
			case signedUint32:
				ce.pushValue(uint64(uint32(v1) >> (uint32(v2) % 32)))
			case signedUint64:
				ce.pushValue(v1 >> (v2 % 64))
			}
			frame.pc++
		case operationKindRotl:
			v2 := ce.popValue()
			v1 := ce.popValue()
			if op.B1 == 0 {
				// unsignedInt32
				ce.pushValue(uint64(bits.RotateLeft32(uint32(v1), int(v2))))
			} else {
				// unsignedInt64
				ce.pushValue(uint64(bits.RotateLeft64(v1, int(v2))))
			}
			frame.pc++
		case operationKindRotr:
			v2 := ce.popValue()
			v1 := ce.popValue()
			if op.B1 == 0 {
				// unsignedInt32
				ce.pushValue(uint64(bits.RotateLeft32(uint32(v1), -int(v2))))
			} else {
				// unsignedInt64
				ce.pushValue(uint64(bits.RotateLeft64(v1, -int(v2))))
			}
			frame.pc++
		case operationKindAbs:
			if op.B1 == 0 {
				// float32
				const mask uint32 = 1 << 31
				ce.pushValue(uint64(uint32(ce.popValue()) &^ mask))
			} else {
				// float64
				const mask uint64 = 1 << 63
				ce.pushValue(ce.popValue() &^ mask)
			}
			frame.pc++
		case operationKindNeg:
			if op.B1 == 0 {
				// float32
				v := -math.Float32frombits(uint32(ce.popValue()))
				ce.pushValue(uint64(math.Float32bits(v)))
			} else {
				// float64
				v := -math.Float64frombits(ce.popValue())
				ce.pushValue(math.Float64bits(v))
			}
			frame.pc++
		case operationKindCeil:
			if op.B1 == 0 {
				// float32
				v := moremath.WasmCompatCeilF32(math.Float32frombits(uint32(ce.popValue())))
				ce.pushValue(uint64(math.Float32bits(v)))
			} else {
				// float64
				v := moremath.WasmCompatCeilF64(math.Float64frombits(ce.popValue()))
				ce.pushValue(math.Float64bits(v))
			}
			frame.pc++
		case operationKindFloor:
			if op.B1 == 0 {
				// float32
				v := moremath.WasmCompatFloorF32(math.Float32frombits(uint32(ce.popValue())))
				ce.pushValue(uint64(math.Float32bits(v)))
			} else {
				// float64
				v := moremath.WasmCompatFloorF64(math.Float64frombits(ce.popValue()))
				ce.pushValue(math.Float64bits(v))
			}
			frame.pc++
		case operationKindTrunc:
			if op.B1 == 0 {
				// float32
				v := moremath.WasmCompatTruncF32(math.Float32frombits(uint32(ce.popValue())))
				ce.pushValue(uint64(math.Float32bits(v)))
			} else {
				// float64
				v := moremath.WasmCompatTruncF64(math.Float64frombits(ce.popValue()))
				ce.pushValue(math.Float64bits(v))
			}
			frame.pc++
		case operationKindNearest:
			if op.B1 == 0 {
				// float32
				f := math.Float32frombits(uint32(ce.popValue()))
				ce.pushValue(uint64(math.Float32bits(moremath.WasmCompatNearestF32(f))))
			} else {
				// float64
				f := math.Float64frombits(ce.popValue())
				ce.pushValue(math.Float64bits(moremath.WasmCompatNearestF64(f)))
			}
			frame.pc++
		case operationKindSqrt:
			if op.B1 == 0 {
				// float32
				v := math.Sqrt(float64(math.Float32frombits(uint32(ce.popValue()))))
				ce.pushValue(uint64(math.Float32bits(float32(v))))
			} else {
				// float64
				v := math.Sqrt(math.Float64frombits(ce.popValue()))
				ce.pushValue(math.Float64bits(v))
			}
			frame.pc++
		case operationKindMin:
			if op.B1 == 0 {
				// float32
				ce.pushValue(wasmCompatMin32bits(uint32(ce.popValue()), uint32(ce.popValue())))
			} else {
				v2 := math.Float64frombits(ce.popValue())
				v1 := math.Float64frombits(ce.popValue())
				ce.pushValue(math.Float64bits(moremath.WasmCompatMin64(v1, v2)))
			}
			frame.pc++
		case operationKindMax:
			if op.B1 == 0 {
				ce.pushValue(wasmCompatMax32bits(uint32(ce.popValue()), uint32(ce.popValue())))
			} else {
				// float64
				v2 := math.Float64frombits(ce.popValue())
				v1 := math.Float64frombits(ce.popValue())
				ce.pushValue(math.Float64bits(moremath.WasmCompatMax64(v1, v2)))
			}
			frame.pc++
		case operationKindCopysign:
			if op.B1 == 0 {
				// float32
				v2 := uint32(ce.popValue())
				v1 := uint32(ce.popValue())
				const signbit = 1 << 31
				ce.pushValue(uint64(v1&^signbit | v2&signbit))
			} else {
				// float64
				v2 := ce.popValue()
				v1 := ce.popValue()
				const signbit = 1 << 63
				ce.pushValue(v1&^signbit | v2&signbit)
			}
			frame.pc++
		case operationKindI32WrapFromI64:
			ce.pushValue(uint64(uint32(ce.popValue())))
			frame.pc++
		case operationKindITruncFromF:
			if op.B1 == 0 {
				// float32
				switch signedInt(op.B2) {
				case signedInt32:
					v := math.Trunc(float64(math.Float32frombits(uint32(ce.popValue()))))
					if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
						if op.B3 {
							// non-trapping conversion must cast nan to zero.
							v = 0
						} else {
							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
						}
					} else if v < math.MinInt32 || v > math.MaxInt32 {
						if op.B3 {
							// non-trapping conversion must "saturate" the value for overflowing sources.
							if v < 0 {
								v = math.MinInt32
							} else {
								v = math.MaxInt32
							}
						} else {
							panic(wasmruntime.ErrRuntimeIntegerOverflow)
						}
					}
					ce.pushValue(uint64(uint32(int32(v))))
				case signedInt64:
					v := math.Trunc(float64(math.Float32frombits(uint32(ce.popValue()))))
					res := int64(v)
					if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
						if op.B3 {
							// non-trapping conversion must cast nan to zero.
							res = 0
						} else {
							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
						}
					} else if v < math.MinInt64 || v >= math.MaxInt64 {
						// Note: math.MaxInt64 is rounded up to math.MaxInt64+1 in 64-bit float representation,
						// and that's why we use '>=' not '>' to check overflow.
						if op.B3 {
							// non-trapping conversion must "saturate" the value for overflowing sources.
							if v < 0 {
								res = math.MinInt64
							} else {
								res = math.MaxInt64
							}
						} else {
							panic(wasmruntime.ErrRuntimeIntegerOverflow)
						}
					}
					ce.pushValue(uint64(res))
				case signedUint32:
					v := math.Trunc(float64(math.Float32frombits(uint32(ce.popValue()))))
					if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
						if op.B3 {
							// non-trapping conversion must cast nan to zero.
							v = 0
						} else {
							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
						}
					} else if v < 0 || v > math.MaxUint32 {
						if op.B3 {
							// non-trapping conversion must "saturate" the value for overflowing source.
							if v < 0 {
								v = 0
							} else {
								v = math.MaxUint32
							}
						} else {
							panic(wasmruntime.ErrRuntimeIntegerOverflow)
						}
					}
					ce.pushValue(uint64(uint32(v)))
				case signedUint64:
					v := math.Trunc(float64(math.Float32frombits(uint32(ce.popValue()))))
					res := uint64(v)
					if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
						if op.B3 {
							// non-trapping conversion must cast nan to zero.
							res = 0
						} else {
							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
						}
					} else if v < 0 || v >= math.MaxUint64 {
						// Note: math.MaxUint64 is rounded up to math.MaxUint64+1 in 64-bit float representation,
						// and that's why we use '>=' not '>' to check overflow.
						if op.B3 {
							// non-trapping conversion must "saturate" the value for overflowing source.
							if v < 0 {
								res = 0
							} else {
								res = math.MaxUint64
							}
						} else {
							panic(wasmruntime.ErrRuntimeIntegerOverflow)
						}
					}
					ce.pushValue(res)
				}
			} else {
				// float64
				switch signedInt(op.B2) {
				case signedInt32:
					v := math.Trunc(math.Float64frombits(ce.popValue()))
					if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
						if op.B3 {
							// non-trapping conversion must cast nan to zero.
							v = 0
						} else {
							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
						}
					} else if v < math.MinInt32 || v > math.MaxInt32 {
						if op.B3 {
							// non-trapping conversion must "saturate" the value for overflowing source.
							if v < 0 {
								v = math.MinInt32
							} else {
								v = math.MaxInt32
							}
						} else {
							panic(wasmruntime.ErrRuntimeIntegerOverflow)
						}
					}
					ce.pushValue(uint64(uint32(int32(v))))
				case signedInt64:
					v := math.Trunc(math.Float64frombits(ce.popValue()))
					res := int64(v)
					if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
						if op.B3 {
							// non-trapping conversion must cast nan to zero.
							res = 0
						} else {
							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
						}
					} else if v < math.MinInt64 || v >= math.MaxInt64 {
						// Note: math.MaxInt64 is rounded up to math.MaxInt64+1 in 64-bit float representation,
						// and that's why we use '>=' not '>' to check overflow.
						if op.B3 {
							// non-trapping conversion must "saturate" the value for overflowing source.
							if v < 0 {
								res = math.MinInt64
							} else {
								res = math.MaxInt64
							}
						} else {
							panic(wasmruntime.ErrRuntimeIntegerOverflow)
						}
					}
					ce.pushValue(uint64(res))
				case signedUint32:
					v := math.Trunc(math.Float64frombits(ce.popValue()))
					if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
						if op.B3 {
							// non-trapping conversion must cast nan to zero.
							v = 0
						} else {
							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
						}
					} else if v < 0 || v > math.MaxUint32 {
						if op.B3 {
							// non-trapping conversion must "saturate" the value for overflowing source.
							if v < 0 {
								v = 0
							} else {
								v = math.MaxUint32
							}
						} else {
							panic(wasmruntime.ErrRuntimeIntegerOverflow)
						}
					}
					ce.pushValue(uint64(uint32(v)))
				case signedUint64:
					v := math.Trunc(math.Float64frombits(ce.popValue()))
					res := uint64(v)
					if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
						if op.B3 {
							// non-trapping conversion must cast nan to zero.
							res = 0
						} else {
							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
						}
					} else if v < 0 || v >= math.MaxUint64 {
						// Note: math.MaxUint64 is rounded up to math.MaxUint64+1 in 64-bit float representation,
						// and that's why we use '>=' not '>' to check overflow.
						if op.B3 {
							// non-trapping conversion must "saturate" the value for overflowing source.
							if v < 0 {
								res = 0
							} else {
								res = math.MaxUint64
							}
						} else {
							panic(wasmruntime.ErrRuntimeIntegerOverflow)
						}
					}
					ce.pushValue(res)
				}
			}
			frame.pc++
		case operationKindFConvertFromI:
			switch signedInt(op.B1) {
			case signedInt32:
				if op.B2 == 0 {
					// float32
					v := float32(int32(ce.popValue()))
					ce.pushValue(uint64(math.Float32bits(v)))
				} else {
					// float64
					v := float64(int32(ce.popValue()))
					ce.pushValue(math.Float64bits(v))
				}
			case signedInt64:
				if op.B2 == 0 {
					// float32
					v := float32(int64(ce.popValue()))
					ce.pushValue(uint64(math.Float32bits(v)))
				} else {
					// float64
					v := float64(int64(ce.popValue()))
					ce.pushValue(math.Float64bits(v))
				}
			case signedUint32:
				if op.B2 == 0 {
					// float32
					v := float32(uint32(ce.popValue()))
					ce.pushValue(uint64(math.Float32bits(v)))
				} else {
					// float64
					v := float64(uint32(ce.popValue()))
					ce.pushValue(math.Float64bits(v))
				}
			case signedUint64:
				if op.B2 == 0 {
					// float32
					v := float32(ce.popValue())
					ce.pushValue(uint64(math.Float32bits(v)))
				} else {
					// float64
					v := float64(ce.popValue())
					ce.pushValue(math.Float64bits(v))
				}
			}
			frame.pc++
		case operationKindF32DemoteFromF64:
			v := float32(math.Float64frombits(ce.popValue()))
			ce.pushValue(uint64(math.Float32bits(v)))
			frame.pc++
		case operationKindF64PromoteFromF32:
			v := float64(math.Float32frombits(uint32(ce.popValue())))
			ce.pushValue(math.Float64bits(v))
			frame.pc++
		case operationKindExtend:
			if op.B1 == 1 {
				// Signed.
				v := int64(int32(ce.popValue()))
				ce.pushValue(uint64(v))
			} else {
				v := uint64(uint32(ce.popValue()))
				ce.pushValue(v)
			}
			frame.pc++
		case operationKindSignExtend32From8:
			v := uint32(int8(ce.popValue()))
			ce.pushValue(uint64(v))
			frame.pc++
		case operationKindSignExtend32From16:
			v := uint32(int16(ce.popValue()))
			ce.pushValue(uint64(v))
			frame.pc++
		case operationKindSignExtend64From8:
			v := int64(int8(ce.popValue()))
			ce.pushValue(uint64(v))
			frame.pc++
		case operationKindSignExtend64From16:
			v := int64(int16(ce.popValue()))
			ce.pushValue(uint64(v))
			frame.pc++
		case operationKindSignExtend64From32:
			v := int64(int32(ce.popValue()))
			ce.pushValue(uint64(v))
			frame.pc++
		case operationKindMemoryInit:
			dataInstance := dataInstances[op.U1]
			copySize := ce.popValue()
			inDataOffset := ce.popValue()
			inMemoryOffset := ce.popValue()
			if inDataOffset+copySize > uint64(len(dataInstance)) ||
				inMemoryOffset+copySize > uint64(len(memoryInst.Buffer)) {
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			} else if copySize != 0 {
				copy(memoryInst.Buffer[inMemoryOffset:inMemoryOffset+copySize], dataInstance[inDataOffset:])
			}
			frame.pc++
		case operationKindDataDrop:
			dataInstances[op.U1] = nil
			frame.pc++
		case operationKindMemoryCopy:
			memLen := uint64(len(memoryInst.Buffer))
			copySize := ce.popValue()
			sourceOffset := ce.popValue()
			destinationOffset := ce.popValue()
			if sourceOffset+copySize > memLen || destinationOffset+copySize > memLen {
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			} else if copySize != 0 {
				copy(memoryInst.Buffer[destinationOffset:],
					memoryInst.Buffer[sourceOffset:sourceOffset+copySize])
			}
			frame.pc++
		case operationKindMemoryFill:
			fillSize := ce.popValue()
			value := byte(ce.popValue())
			offset := ce.popValue()
			if fillSize+offset > uint64(len(memoryInst.Buffer)) {
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			} else if fillSize != 0 {
				// Uses the copy trick for faster filling the buffer with the value.
				// https://github.com/golang/go/blob/go1.24.0/src/bytes/bytes.go#L664-L673
				buf := memoryInst.Buffer[offset : offset+fillSize]
				if value == 0 {
					clear(buf)
				} else {
					buf[0] = value
					for i := 1; i < len(buf); {
						chunk := min(i, 8192)
						i += copy(buf[i:], buf[:chunk])
					}
				}
			}
			frame.pc++
		case operationKindTableInit:
			elementInstance := elementInstances[op.U1]
			copySize := ce.popValue()
			inElementOffset := ce.popValue()
			inTableOffset := ce.popValue()
			table := tables[op.U2]
			if inElementOffset+copySize > uint64(len(elementInstance)) ||
				inTableOffset+copySize > uint64(len(table.References)) {
				panic(wasmruntime.ErrRuntimeInvalidTableAccess)
			} else if copySize != 0 {
				copy(table.References[inTableOffset:inTableOffset+copySize], elementInstance[inElementOffset:])
			}
			frame.pc++
		case operationKindElemDrop:
			elementInstances[op.U1] = nil
			frame.pc++
		case operationKindTableCopy:
			srcTable, dstTable := tables[op.U1].References, tables[op.U2].References
			copySize := ce.popValue()
			sourceOffset := ce.popValue()
			destinationOffset := ce.popValue()
			if sourceOffset+copySize > uint64(len(srcTable)) || destinationOffset+copySize > uint64(len(dstTable)) {
				panic(wasmruntime.ErrRuntimeInvalidTableAccess)
			} else if copySize != 0 {
				copy(dstTable[destinationOffset:], srcTable[sourceOffset:sourceOffset+copySize])
			}
			frame.pc++
		case operationKindRefFunc:
			ce.pushValue(uint64(uintptr(unsafe.Pointer(&functions[op.U1]))))
			frame.pc++
		case operationKindTableGet:
			table := tables[op.U1]

			offset := ce.popValue()
			if offset >= uint64(len(table.References)) {
				panic(wasmruntime.ErrRuntimeInvalidTableAccess)
			}

			ce.pushValue(uint64(table.References[offset]))
			frame.pc++
		case operationKindTableSet:
			table := tables[op.U1]
			ref := ce.popValue()

			offset := ce.popValue()
			if offset >= uint64(len(table.References)) {
				panic(wasmruntime.ErrRuntimeInvalidTableAccess)
			}

			table.References[offset] = uintptr(ref) // externrefs are opaque uint64.
			frame.pc++
		case operationKindTableSize:
			table := tables[op.U1]
			ce.pushValue(uint64(len(table.References)))
			frame.pc++
		case operationKindTableGrow:
			table := tables[op.U1]
			num, ref := ce.popValue(), ce.popValue()
			ret := table.Grow(uint32(num), uintptr(ref))
			ce.pushValue(uint64(ret))
			frame.pc++
		case operationKindTableFill:
			table := tables[op.U1]
			num := ce.popValue()
			ref := uintptr(ce.popValue())
			offset := ce.popValue()
			if num+offset > uint64(len(table.References)) {
				panic(wasmruntime.ErrRuntimeInvalidTableAccess)
			} else if num > 0 {
				// Uses the copy trick for faster filling the region with the value.
				// https://github.com/golang/go/blob/go1.24.0/src/slices/slices.go#L514-L517
				targetRegion := table.References[offset : offset+num]
				targetRegion[0] = ref
				for i := 1; i < len(targetRegion); i *= 2 {
					copy(targetRegion[i:], targetRegion[:i])
				}
			}
			frame.pc++
		case operationKindV128Const:
			lo, hi := op.U1, op.U2
			ce.pushValue(lo)
			ce.pushValue(hi)
			frame.pc++
		case operationKindV128Add:
			yHigh, yLow := ce.popValue(), ce.popValue()
			xHigh, xLow := ce.popValue(), ce.popValue()
			switch op.B1 {
			case shapeI8x16:
				ce.pushValue(
					uint64(uint8(xLow>>8)+uint8(yLow>>8))<<8 | uint64(uint8(xLow)+uint8(yLow)) |
						uint64(uint8(xLow>>24)+uint8(yLow>>24))<<24 | uint64(uint8(xLow>>16)+uint8(yLow>>16))<<16 |
						uint64(uint8(xLow>>40)+uint8(yLow>>40))<<40 | uint64(uint8(xLow>>32)+uint8(yLow>>32))<<32 |
						uint64(uint8(xLow>>56)+uint8(yLow>>56))<<56 | uint64(uint8(xLow>>48)+uint8(yLow>>48))<<48,
				)
				ce.pushValue(
					uint64(uint8(xHigh>>8)+uint8(yHigh>>8))<<8 | uint64(uint8(xHigh)+uint8(yHigh)) |
						uint64(uint8(xHigh>>24)+uint8(yHigh>>24))<<24 | uint64(uint8(xHigh>>16)+uint8(yHigh>>16))<<16 |
						uint64(uint8(xHigh>>40)+uint8(yHigh>>40))<<40 | uint64(uint8(xHigh>>32)+uint8(yHigh>>32))<<32 |
						uint64(uint8(xHigh>>56)+uint8(yHigh>>56))<<56 | uint64(uint8(xHigh>>48)+uint8(yHigh>>48))<<48,
				)
			case shapeI16x8:
				ce.pushValue(
					uint64(uint16(xLow>>16+yLow>>16))<<16 | uint64(uint16(xLow)+uint16(yLow)) |
						uint64(uint16(xLow>>48+yLow>>48))<<48 | uint64(uint16(xLow>>32+yLow>>32))<<32,
				)
				ce.pushValue(
					uint64(uint16(xHigh>>16)+uint16(yHigh>>16))<<16 | uint64(uint16(xHigh)+uint16(yHigh)) |
						uint64(uint16(xHigh>>48)+uint16(yHigh>>48))<<48 | uint64(uint16(xHigh>>32)+uint16(yHigh>>32))<<32,
				)
			case shapeI32x4:
				ce.pushValue(uint64(uint32(xLow>>32)+uint32(yLow>>32))<<32 | uint64(uint32(xLow)+uint32(yLow)))
				ce.pushValue(uint64(uint32(xHigh>>32)+uint32(yHigh>>32))<<32 | uint64(uint32(xHigh)+uint32(yHigh)))
			case shapeI64x2:
				ce.pushValue(xLow + yLow)
				ce.pushValue(xHigh + yHigh)
			case shapeF32x4:
				ce.pushValue(
					addFloat32bits(uint32(xLow), uint32(yLow)) | addFloat32bits(uint32(xLow>>32), uint32(yLow>>32))<<32,
				)
				ce.pushValue(
					addFloat32bits(uint32(xHigh), uint32(yHigh)) | addFloat32bits(uint32(xHigh>>32), uint32(yHigh>>32))<<32,
				)
			case shapeF64x2:
				ce.pushValue(math.Float64bits(math.Float64frombits(xLow) + math.Float64frombits(yLow)))
				ce.pushValue(math.Float64bits(math.Float64frombits(xHigh) + math.Float64frombits(yHigh)))
			}
			frame.pc++
		case operationKindV128Sub:
			yHigh, yLow := ce.popValue(), ce.popValue()
			xHigh, xLow := ce.popValue(), ce.popValue()
			switch op.B1 {
			case shapeI8x16:
				ce.pushValue(
					uint64(uint8(xLow>>8)-uint8(yLow>>8))<<8 | uint64(uint8(xLow)-uint8(yLow)) |
						uint64(uint8(xLow>>24)-uint8(yLow>>24))<<24 | uint64(uint8(xLow>>16)-uint8(yLow>>16))<<16 |
						uint64(uint8(xLow>>40)-uint8(yLow>>40))<<40 | uint64(uint8(xLow>>32)-uint8(yLow>>32))<<32 |
						uint64(uint8(xLow>>56)-uint8(yLow>>56))<<56 | uint64(uint8(xLow>>48)-uint8(yLow>>48))<<48,
				)
				ce.pushValue(
					uint64(uint8(xHigh>>8)-uint8(yHigh>>8))<<8 | uint64(uint8(xHigh)-uint8(yHigh)) |
						uint64(uint8(xHigh>>24)-uint8(yHigh>>24))<<24 | uint64(uint8(xHigh>>16)-uint8(yHigh>>16))<<16 |
						uint64(uint8(xHigh>>40)-uint8(yHigh>>40))<<40 | uint64(uint8(xHigh>>32)-uint8(yHigh>>32))<<32 |
						uint64(uint8(xHigh>>56)-uint8(yHigh>>56))<<56 | uint64(uint8(xHigh>>48)-uint8(yHigh>>48))<<48,
				)
			case shapeI16x8:
				ce.pushValue(
					uint64(uint16(xLow>>16)-uint16(yLow>>16))<<16 | uint64(uint16(xLow)-uint16(yLow)) |
						uint64(uint16(xLow>>48)-uint16(yLow>>48))<<48 | uint64(uint16(xLow>>32)-uint16(yLow>>32))<<32,
				)
				ce.pushValue(
					uint64(uint16(xHigh>>16)-uint16(yHigh>>16))<<16 | uint64(uint16(xHigh)-uint16(yHigh)) |
						uint64(uint16(xHigh>>48)-uint16(yHigh>>48))<<48 | uint64(uint16(xHigh>>32)-uint16(yHigh>>32))<<32,
				)
			case shapeI32x4:
				ce.pushValue(uint64(uint32(xLow>>32-yLow>>32))<<32 | uint64(uint32(xLow)-uint32(yLow)))
				ce.pushValue(uint64(uint32(xHigh>>32-yHigh>>32))<<32 | uint64(uint32(xHigh)-uint32(yHigh)))
			case shapeI64x2:
				ce.pushValue(xLow - yLow)
				ce.pushValue(xHigh - yHigh)
			case shapeF32x4:
				ce.pushValue(
					subFloat32bits(uint32(xLow), uint32(yLow)) | subFloat32bits(uint32(xLow>>32), uint32(yLow>>32))<<32,
				)
				ce.pushValue(
					subFloat32bits(uint32(xHigh), uint32(yHigh)) | subFloat32bits(uint32(xHigh>>32), uint32(yHigh>>32))<<32,
				)
			case shapeF64x2:
				ce.pushValue(math.Float64bits(math.Float64frombits(xLow) - math.Float64frombits(yLow)))
				ce.pushValue(math.Float64bits(math.Float64frombits(xHigh) - math.Float64frombits(yHigh)))
			}
			frame.pc++
		case operationKindV128Load:
			offset := ce.popMemoryOffset(op)
			switch op.B1 {
			case v128LoadType128:
				lo, ok := memoryInst.ReadUint64Le(offset)
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				ce.pushValue(lo)
				hi, ok := memoryInst.ReadUint64Le(offset + 8)
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				ce.pushValue(hi)
			case v128LoadType8x8s:
				data, ok := memoryInst.Read(offset, 8)
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				ce.pushValue(
					uint64(uint16(int8(data[3])))<<48 | uint64(uint16(int8(data[2])))<<32 | uint64(uint16(int8(data[1])))<<16 | uint64(uint16(int8(data[0]))),
				)
				ce.pushValue(
					uint64(uint16(int8(data[7])))<<48 | uint64(uint16(int8(data[6])))<<32 | uint64(uint16(int8(data[5])))<<16 | uint64(uint16(int8(data[4]))),
				)
			case v128LoadType8x8u:
				data, ok := memoryInst.Read(offset, 8)
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				ce.pushValue(
					uint64(data[3])<<48 | uint64(data[2])<<32 | uint64(data[1])<<16 | uint64(data[0]),
				)
				ce.pushValue(
					uint64(data[7])<<48 | uint64(data[6])<<32 | uint64(data[5])<<16 | uint64(data[4]),
				)
			case v128LoadType16x4s:
				data, ok := memoryInst.Read(offset, 8)
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				ce.pushValue(
					uint64(int16(binary.LittleEndian.Uint16(data[2:])))<<32 |
						uint64(uint32(int16(binary.LittleEndian.Uint16(data)))),
				)
				ce.pushValue(
					uint64(uint32(int16(binary.LittleEndian.Uint16(data[6:]))))<<32 |
						uint64(uint32(int16(binary.LittleEndian.Uint16(data[4:])))),
				)
			case v128LoadType16x4u:
				data, ok := memoryInst.Read(offset, 8)
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				ce.pushValue(
					uint64(binary.LittleEndian.Uint16(data[2:]))<<32 | uint64(binary.LittleEndian.Uint16(data)),
				)
				ce.pushValue(
					uint64(binary.LittleEndian.Uint16(data[6:]))<<32 | uint64(binary.LittleEndian.Uint16(data[4:])),
				)
			case v128LoadType32x2s:
				data, ok := memoryInst.Read(offset, 8)
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				ce.pushValue(uint64(int32(binary.LittleEndian.Uint32(data))))
				ce.pushValue(uint64(int32(binary.LittleEndian.Uint32(data[4:]))))
			case v128LoadType32x2u:
				data, ok := memoryInst.Read(offset, 8)
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				ce.pushValue(uint64(binary.LittleEndian.Uint32(data)))
				ce.pushValue(uint64(binary.LittleEndian.Uint32(data[4:])))
			case v128LoadType8Splat:
				v, ok := memoryInst.ReadByte(offset)
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				v8 := uint64(v)<<56 | uint64(v)<<48 | uint64(v)<<40 | uint64(v)<<32 |
					uint64(v)<<24 | uint64(v)<<16 | uint64(v)<<8 | uint64(v)
				ce.pushValue(v8)
				ce.pushValue(v8)
			case v128LoadType16Splat:
				v, ok := memoryInst.ReadUint16Le(offset)
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				v4 := uint64(v)<<48 | uint64(v)<<32 | uint64(v)<<16 | uint64(v)
				ce.pushValue(v4)
				ce.pushValue(v4)
			case v128LoadType32Splat:
				v, ok := memoryInst.ReadUint32Le(offset)
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				vv := uint64(v)<<32 | uint64(v)
				ce.pushValue(vv)
				ce.pushValue(vv)
			case v128LoadType64Splat:
				lo, ok := memoryInst.ReadUint64Le(offset)
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				ce.pushValue(lo)
				ce.pushValue(lo)
			case v128LoadType32zero:
				lo, ok := memoryInst.ReadUint32Le(offset)
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				ce.pushValue(uint64(lo))
				ce.pushValue(0)
			case v128LoadType64zero:
				lo, ok := memoryInst.ReadUint64Le(offset)
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				ce.pushValue(lo)
				ce.pushValue(0)
			}
			frame.pc++
		case operationKindV128LoadLane:
			hi, lo := ce.popValue(), ce.popValue()
			offset := ce.popMemoryOffset(op)
			switch op.B1 {
			case 8:
				b, ok := memoryInst.ReadByte(offset)
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				if op.B2 < 8 {
					s := op.B2 << 3
					lo = (lo & ^(0xff << s)) | uint64(b)<<s
				} else {
					s := (op.B2 - 8) << 3
					hi = (hi & ^(0xff << s)) | uint64(b)<<s
				}
			case 16:
				b, ok := memoryInst.ReadUint16Le(offset)
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				if op.B2 < 4 {
					s := op.B2 << 4
					lo = (lo & ^(0xff_ff << s)) | uint64(b)<<s
				} else {
					s := (op.B2 - 4) << 4
					hi = (hi & ^(0xff_ff << s)) | uint64(b)<<s
				}
			case 32:
				b, ok := memoryInst.ReadUint32Le(offset)
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				if op.B2 < 2 {
					s := op.B2 << 5
					lo = (lo & ^(0xff_ff_ff_ff << s)) | uint64(b)<<s
				} else {
					s := (op.B2 - 2) << 5
					hi = (hi & ^(0xff_ff_ff_ff << s)) | uint64(b)<<s
				}
			case 64:
				b, ok := memoryInst.ReadUint64Le(offset)
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				if op.B2 == 0 {
					lo = b
				} else {
					hi = b
				}
			}
			ce.pushValue(lo)
			ce.pushValue(hi)
			frame.pc++
		case operationKindV128Store:
			hi, lo := ce.popValue(), ce.popValue()
			offset := ce.popMemoryOffset(op)
			// Write the upper bytes first to trigger an early error if the memory access is out of bounds.
			// Otherwise, the lower bytes might be written to memory, but the upper bytes might not.
			if uint64(offset)+8 > math.MaxUint32 {
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}
			if ok := memoryInst.WriteUint64Le(offset+8, hi); !ok {
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}
			if ok := memoryInst.WriteUint64Le(offset, lo); !ok {
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}
			frame.pc++
		case operationKindV128StoreLane:
			hi, lo := ce.popValue(), ce.popValue()
			offset := ce.popMemoryOffset(op)
			var ok bool
			switch op.B1 {
			case 8:
				if op.B2 < 8 {
					ok = memoryInst.WriteByte(offset, byte(lo>>(op.B2*8)))
				} else {
					ok = memoryInst.WriteByte(offset, byte(hi>>((op.B2-8)*8)))
				}
			case 16:
				if op.B2 < 4 {
					ok = memoryInst.WriteUint16Le(offset, uint16(lo>>(op.B2*16)))
				} else {
					ok = memoryInst.WriteUint16Le(offset, uint16(hi>>((op.B2-4)*16)))
				}
			case 32:
				if op.B2 < 2 {
					ok = memoryInst.WriteUint32Le(offset, uint32(lo>>(op.B2*32)))
				} else {
					ok = memoryInst.WriteUint32Le(offset, uint32(hi>>((op.B2-2)*32)))
				}
			case 64:
				if op.B2 == 0 {
					ok = memoryInst.WriteUint64Le(offset, lo)
				} else {
					ok = memoryInst.WriteUint64Le(offset, hi)
				}
			}
			if !ok {
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}
			frame.pc++
		case operationKindV128ReplaceLane:
			v := ce.popValue()
			hi, lo := ce.popValue(), ce.popValue()
			switch op.B1 {
			case shapeI8x16:
				if op.B2 < 8 {
					s := op.B2 << 3
					lo = (lo & ^(0xff << s)) | uint64(byte(v))<<s
				} else {
					s := (op.B2 - 8) << 3
					hi = (hi & ^(0xff << s)) | uint64(byte(v))<<s
				}
			case shapeI16x8:
				if op.B2 < 4 {
					s := op.B2 << 4
					lo = (lo & ^(0xff_ff << s)) | uint64(uint16(v))<<s
				} else {
					s := (op.B2 - 4) << 4
					hi = (hi & ^(0xff_ff << s)) | uint64(uint16(v))<<s
				}
			case shapeI32x4, shapeF32x4:
				if op.B2 < 2 {
					s := op.B2 << 5
					lo = (lo & ^(0xff_ff_ff_ff << s)) | uint64(uint32(v))<<s
				} else {
					s := (op.B2 - 2) << 5
					hi = (hi & ^(0xff_ff_ff_ff << s)) | uint64(uint32(v))<<s
				}
			case shapeI64x2, shapeF64x2:
				if op.B2 == 0 {
					lo = v
				} else {
					hi = v
				}
			}
			ce.pushValue(lo)
			ce.pushValue(hi)
			frame.pc++
		case operationKindV128ExtractLane:
			hi, lo := ce.popValue(), ce.popValue()
			var v uint64
			switch op.B1 {
			case shapeI8x16:
				var u8 byte
				if op.B2 < 8 {
					u8 = byte(lo >> (op.B2 * 8))
				} else {
					u8 = byte(hi >> ((op.B2 - 8) * 8))
				}
				if op.B3 {
					// sign-extend.
					v = uint64(uint32(int8(u8)))
				} else {
					v = uint64(u8)
				}
			case shapeI16x8:
				var u16 uint16
				if op.B2 < 4 {
					u16 = uint16(lo >> (op.B2 * 16))
				} else {
					u16 = uint16(hi >> ((op.B2 - 4) * 16))
				}
				if op.B3 {
					// sign-extend.
					v = uint64(uint32(int16(u16)))
				} else {
					v = uint64(u16)
				}
			case shapeI32x4, shapeF32x4:
				if op.B2 < 2 {
					v = uint64(uint32(lo >> (op.B2 * 32)))
				} else {
					v = uint64(uint32(hi >> ((op.B2 - 2) * 32)))
				}
			case shapeI64x2, shapeF64x2:
				if op.B2 == 0 {
					v = lo
				} else {
					v = hi
				}
			}
			ce.pushValue(v)
			frame.pc++
		case operationKindV128Splat:
			v := ce.popValue()
			var hi, lo uint64
			switch op.B1 {
			case shapeI8x16:
				v8 := uint64(byte(v))<<56 | uint64(byte(v))<<48 | uint64(byte(v))<<40 | uint64(byte(v))<<32 |
					uint64(byte(v))<<24 | uint64(byte(v))<<16 | uint64(byte(v))<<8 | uint64(byte(v))
				hi, lo = v8, v8
			case shapeI16x8:
				v4 := uint64(uint16(v))<<48 | uint64(uint16(v))<<32 | uint64(uint16(v))<<16 | uint64(uint16(v))
				hi, lo = v4, v4
			case shapeI32x4, shapeF32x4:
				v2 := uint64(uint32(v))<<32 | uint64(uint32(v))
				lo, hi = v2, v2
			case shapeI64x2, shapeF64x2:
				lo, hi = v, v
			}
			ce.pushValue(lo)
			ce.pushValue(hi)
			frame.pc++
		case operationKindV128Swizzle:
			idxHi, idxLo := ce.popValue(), ce.popValue()
			baseHi, baseLo := ce.popValue(), ce.popValue()
			var newVal [16]byte
			for i := 0; i < 16; i++ {
				var id byte
				if i < 8 {
					id = byte(idxLo >> (i * 8))
				} else {
					id = byte(idxHi >> ((i - 8) * 8))
				}
				if id < 8 {
					newVal[i] = byte(baseLo >> (id * 8))
				} else if id < 16 {
					newVal[i] = byte(baseHi >> ((id - 8) * 8))
				}
			}
			ce.pushValue(binary.LittleEndian.Uint64(newVal[:8]))
			ce.pushValue(binary.LittleEndian.Uint64(newVal[8:]))
			frame.pc++
		case operationKindV128Shuffle:
			xHi, xLo, yHi, yLo := ce.popValue(), ce.popValue(), ce.popValue(), ce.popValue()
			var newVal [16]byte
			for i, l := range op.Us {
				if l < 8 {
					newVal[i] = byte(yLo >> (l * 8))
				} else if l < 16 {
					newVal[i] = byte(yHi >> ((l - 8) * 8))
				} else if l < 24 {
					newVal[i] = byte(xLo >> ((l - 16) * 8))
				} else if l < 32 {
					newVal[i] = byte(xHi >> ((l - 24) * 8))
				}
			}
			ce.pushValue(binary.LittleEndian.Uint64(newVal[:8]))
			ce.pushValue(binary.LittleEndian.Uint64(newVal[8:]))
			frame.pc++
		case operationKindV128AnyTrue:
			hi, lo := ce.popValue(), ce.popValue()
			if hi != 0 || lo != 0 {
				ce.pushValue(1)
			} else {
				ce.pushValue(0)
			}
			frame.pc++
		case operationKindV128AllTrue:
			hi, lo := ce.popValue(), ce.popValue()
			var ret bool
			switch op.B1 {
			case shapeI8x16:
				ret = (uint8(lo) != 0) && (uint8(lo>>8) != 0) && (uint8(lo>>16) != 0) && (uint8(lo>>24) != 0) &&
					(uint8(lo>>32) != 0) && (uint8(lo>>40) != 0) && (uint8(lo>>48) != 0) && (uint8(lo>>56) != 0) &&
					(uint8(hi) != 0) && (uint8(hi>>8) != 0) && (uint8(hi>>16) != 0) && (uint8(hi>>24) != 0) &&
					(uint8(hi>>32) != 0) && (uint8(hi>>40) != 0) && (uint8(hi>>48) != 0) && (uint8(hi>>56) != 0)
			case shapeI16x8:
				ret = (uint16(lo) != 0) && (uint16(lo>>16) != 0) && (uint16(lo>>32) != 0) && (uint16(lo>>48) != 0) &&
					(uint16(hi) != 0) && (uint16(hi>>16) != 0) && (uint16(hi>>32) != 0) && (uint16(hi>>48) != 0)
			case shapeI32x4:
				ret = (uint32(lo) != 0) && (uint32(lo>>32) != 0) &&
					(uint32(hi) != 0) && (uint32(hi>>32) != 0)
			case shapeI64x2:
				ret = (lo != 0) &&
					(hi != 0)
			}
			if ret {
				ce.pushValue(1)
			} else {
				ce.pushValue(0)
			}
			frame.pc++
		case operationKindV128BitMask:
			// https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md#bitmask-extraction
			hi, lo := ce.popValue(), ce.popValue()
			var res uint64
			switch op.B1 {
			case shapeI8x16:
				for i := 0; i < 8; i++ {
					if int8(lo>>(i*8)) < 0 {
						res |= 1 << i
					}
				}
				for i := 0; i < 8; i++ {
					if int8(hi>>(i*8)) < 0 {
						res |= 1 << (i + 8)
					}
				}
			case shapeI16x8:
				for i := 0; i < 4; i++ {
					if int16(lo>>(i*16)) < 0 {
						res |= 1 << i
					}
				}
				for i := 0; i < 4; i++ {
					if int16(hi>>(i*16)) < 0 {
						res |= 1 << (i + 4)
					}
				}
			case shapeI32x4:
				for i := 0; i < 2; i++ {
					if int32(lo>>(i*32)) < 0 {
						res |= 1 << i
					}
				}
				for i := 0; i < 2; i++ {
					if int32(hi>>(i*32)) < 0 {
						res |= 1 << (i + 2)
					}
				}
			case shapeI64x2:
				if int64(lo) < 0 {
					res |= 0b01
				}
				if int(hi) < 0 {
					res |= 0b10
				}
			}
			ce.pushValue(res)
			frame.pc++
		case operationKindV128And:
			x2Hi, x2Lo := ce.popValue(), ce.popValue()
			x1Hi, x1Lo := ce.popValue(), ce.popValue()
			ce.pushValue(x1Lo & x2Lo)
			ce.pushValue(x1Hi & x2Hi)
			frame.pc++
		case operationKindV128Not:
			hi, lo := ce.popValue(), ce.popValue()
			ce.pushValue(^lo)
			ce.pushValue(^hi)
			frame.pc++
		case operationKindV128Or:
			x2Hi, x2Lo := ce.popValue(), ce.popValue()
			x1Hi, x1Lo := ce.popValue(), ce.popValue()
			ce.pushValue(x1Lo | x2Lo)
			ce.pushValue(x1Hi | x2Hi)
			frame.pc++
		case operationKindV128Xor:
			x2Hi, x2Lo := ce.popValue(), ce.popValue()
			x1Hi, x1Lo := ce.popValue(), ce.popValue()
			ce.pushValue(x1Lo ^ x2Lo)
			ce.pushValue(x1Hi ^ x2Hi)
			frame.pc++
		case operationKindV128Bitselect:
			// https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md#bitwise-select
			cHi, cLo := ce.popValue(), ce.popValue()
			x2Hi, x2Lo := ce.popValue(), ce.popValue()
			x1Hi, x1Lo := ce.popValue(), ce.popValue()
			// v128.or(v128.and(v1, c), v128.and(v2, v128.not(c)))
			ce.pushValue((x1Lo & cLo) | (x2Lo & (^cLo)))
			ce.pushValue((x1Hi & cHi) | (x2Hi & (^cHi)))
			frame.pc++
		case operationKindV128AndNot:
			x2Hi, x2Lo := ce.popValue(), ce.popValue()
			x1Hi, x1Lo := ce.popValue(), ce.popValue()
			ce.pushValue(x1Lo & (^x2Lo))
			ce.pushValue(x1Hi & (^x2Hi))
			frame.pc++
		case operationKindV128Shl:
			s := ce.popValue()
			hi, lo := ce.popValue(), ce.popValue()
			switch op.B1 {
			case shapeI8x16:
				s = s % 8
				lo = uint64(uint8(lo<<s)) |
					uint64(uint8((lo>>8)<<s))<<8 |
					uint64(uint8((lo>>16)<<s))<<16 |
					uint64(uint8((lo>>24)<<s))<<24 |
					uint64(uint8((lo>>32)<<s))<<32 |
					uint64(uint8((lo>>40)<<s))<<40 |
					uint64(uint8((lo>>48)<<s))<<48 |
					uint64(uint8((lo>>56)<<s))<<56
				hi = uint64(uint8(hi<<s)) |
					uint64(uint8((hi>>8)<<s))<<8 |
					uint64(uint8((hi>>16)<<s))<<16 |
					uint64(uint8((hi>>24)<<s))<<24 |
					uint64(uint8((hi>>32)<<s))<<32 |
					uint64(uint8((hi>>40)<<s))<<40 |
					uint64(uint8((hi>>48)<<s))<<48 |
					uint64(uint8((hi>>56)<<s))<<56
			case shapeI16x8:
				s = s % 16
				lo = uint64(uint16(lo<<s)) |
					uint64(uint16((lo>>16)<<s))<<16 |
					uint64(uint16((lo>>32)<<s))<<32 |
					uint64(uint16((lo>>48)<<s))<<48
				hi = uint64(uint16(hi<<s)) |
					uint64(uint16((hi>>16)<<s))<<16 |
					uint64(uint16((hi>>32)<<s))<<32 |
					uint64(uint16((hi>>48)<<s))<<48
			case shapeI32x4:
				s = s % 32
				lo = uint64(uint32(lo<<s)) | uint64(uint32((lo>>32)<<s))<<32
				hi = uint64(uint32(hi<<s)) | uint64(uint32((hi>>32)<<s))<<32
			case shapeI64x2:
				s = s % 64
				lo = lo << s
				hi = hi << s
			}
			ce.pushValue(lo)
			ce.pushValue(hi)
			frame.pc++
		case operationKindV128Shr:
			s := ce.popValue()
			hi, lo := ce.popValue(), ce.popValue()
			switch op.B1 {
			case shapeI8x16:
				s = s % 8
				if op.B3 { // signed
					lo = uint64(uint8(int8(lo)>>s)) |
						uint64(uint8(int8(lo>>8)>>s))<<8 |
						uint64(uint8(int8(lo>>16)>>s))<<16 |
						uint64(uint8(int8(lo>>24)>>s))<<24 |
						uint64(uint8(int8(lo>>32)>>s))<<32 |
						uint64(uint8(int8(lo>>40)>>s))<<40 |
						uint64(uint8(int8(lo>>48)>>s))<<48 |
						uint64(uint8(int8(lo>>56)>>s))<<56
					hi = uint64(uint8(int8(hi)>>s)) |
						uint64(uint8(int8(hi>>8)>>s))<<8 |
						uint64(uint8(int8(hi>>16)>>s))<<16 |
						uint64(uint8(int8(hi>>24)>>s))<<24 |
						uint64(uint8(int8(hi>>32)>>s))<<32 |
						uint64(uint8(int8(hi>>40)>>s))<<40 |
						uint64(uint8(int8(hi>>48)>>s))<<48 |
						uint64(uint8(int8(hi>>56)>>s))<<56
				} else {
					lo = uint64(uint8(lo)>>s) |
						uint64(uint8(lo>>8)>>s)<<8 |
						uint64(uint8(lo>>16)>>s)<<16 |
						uint64(uint8(lo>>24)>>s)<<24 |
						uint64(uint8(lo>>32)>>s)<<32 |
						uint64(uint8(lo>>40)>>s)<<40 |
						uint64(uint8(lo>>48)>>s)<<48 |
						uint64(uint8(lo>>56)>>s)<<56
					hi = uint64(uint8(hi)>>s) |
						uint64(uint8(hi>>8)>>s)<<8 |
						uint64(uint8(hi>>16)>>s)<<16 |
						uint64(uint8(hi>>24)>>s)<<24 |
						uint64(uint8(hi>>32)>>s)<<32 |
						uint64(uint8(hi>>40)>>s)<<40 |
						uint64(uint8(hi>>48)>>s)<<48 |
						uint64(uint8(hi>>56)>>s)<<56
				}
			case shapeI16x8:
				s = s % 16
				if op.B3 { // signed
					lo = uint64(uint16(int16(lo)>>s)) |
						uint64(uint16(int16(lo>>16)>>s))<<16 |
						uint64(uint16(int16(lo>>32)>>s))<<32 |
						uint64(uint16(int16(lo>>48)>>s))<<48
					hi = uint64(uint16(int16(hi)>>s)) |
						uint64(uint16(int16(hi>>16)>>s))<<16 |
						uint64(uint16(int16(hi>>32)>>s))<<32 |
						uint64(uint16(int16(hi>>48)>>s))<<48
				} else {
					lo = uint64(uint16(lo)>>s) |
						uint64(uint16(lo>>16)>>s)<<16 |
						uint64(uint16(lo>>32)>>s)<<32 |
						uint64(uint16(lo>>48)>>s)<<48
					hi = uint64(uint16(hi)>>s) |
						uint64(uint16(hi>>16)>>s)<<16 |
						uint64(uint16(hi>>32)>>s)<<32 |
						uint64(uint16(hi>>48)>>s)<<48
				}
			case shapeI32x4:
				s = s % 32
				if op.B3 {
					lo = uint64(uint32(int32(lo)>>s)) | uint64(uint32(int32(lo>>32)>>s))<<32
					hi = uint64(uint32(int32(hi)>>s)) | uint64(uint32(int32(hi>>32)>>s))<<32
				} else {
					lo = uint64(uint32(lo)>>s) | uint64(uint32(lo>>32)>>s)<<32
					hi = uint64(uint32(hi)>>s) | uint64(uint32(hi>>32)>>s)<<32
				}
			case shapeI64x2:
				s = s % 64
				if op.B3 { // signed
					lo = uint64(int64(lo) >> s)
					hi = uint64(int64(hi) >> s)
				} else {
					lo = lo >> s
					hi = hi >> s
				}

			}
			ce.pushValue(lo)
			ce.pushValue(hi)
			frame.pc++
		case operationKindV128Cmp:
			x2Hi, x2Lo := ce.popValue(), ce.popValue()
			x1Hi, x1Lo := ce.popValue(), ce.popValue()
			var result []bool
			switch op.B1 {
			case v128CmpTypeI8x16Eq:
				result = []bool{
					byte(x1Lo>>0) == byte(x2Lo>>0), byte(x1Lo>>8) == byte(x2Lo>>8),
					byte(x1Lo>>16) == byte(x2Lo>>16), byte(x1Lo>>24) == byte(x2Lo>>24),
					byte(x1Lo>>32) == byte(x2Lo>>32), byte(x1Lo>>40) == byte(x2Lo>>40),
					byte(x1Lo>>48) == byte(x2Lo>>48), byte(x1Lo>>56) == byte(x2Lo>>56),
					byte(x1Hi>>0) == byte(x2Hi>>0), byte(x1Hi>>8) == byte(x2Hi>>8),
					byte(x1Hi>>16) == byte(x2Hi>>16), byte(x1Hi>>24) == byte(x2Hi>>24),
					byte(x1Hi>>32) == byte(x2Hi>>32), byte(x1Hi>>40) == byte(x2Hi>>40),
					byte(x1Hi>>48) == byte(x2Hi>>48), byte(x1Hi>>56) == byte(x2Hi>>56),
				}
			case v128CmpTypeI8x16Ne:
				result = []bool{
					byte(x1Lo>>0) != byte(x2Lo>>0), byte(x1Lo>>8) != byte(x2Lo>>8),
					byte(x1Lo>>16) != byte(x2Lo>>16), byte(x1Lo>>24) != byte(x2Lo>>24),
					byte(x1Lo>>32) != byte(x2Lo>>32), byte(x1Lo>>40) != byte(x2Lo>>40),
					byte(x1Lo>>48) != byte(x2Lo>>48), byte(x1Lo>>56) != byte(x2Lo>>56),
					byte(x1Hi>>0) != byte(x2Hi>>0), byte(x1Hi>>8) != byte(x2Hi>>8),
					byte(x1Hi>>16) != byte(x2Hi>>16), byte(x1Hi>>24) != byte(x2Hi>>24),
					byte(x1Hi>>32) != byte(x2Hi>>32), byte(x1Hi>>40) != byte(x2Hi>>40),
					byte(x1Hi>>48) != byte(x2Hi>>48), byte(x1Hi>>56) != byte(x2Hi>>56),
				}
			case v128CmpTypeI8x16LtS:
				result = []bool{
					int8(x1Lo>>0) < int8(x2Lo>>0), int8(x1Lo>>8) < int8(x2Lo>>8),
					int8(x1Lo>>16) < int8(x2Lo>>16), int8(x1Lo>>24) < int8(x2Lo>>24),
					int8(x1Lo>>32) < int8(x2Lo>>32), int8(x1Lo>>40) < int8(x2Lo>>40),
					int8(x1Lo>>48) < int8(x2Lo>>48), int8(x1Lo>>56) < int8(x2Lo>>56),
					int8(x1Hi>>0) < int8(x2Hi>>0), int8(x1Hi>>8) < int8(x2Hi>>8),
					int8(x1Hi>>16) < int8(x2Hi>>16), int8(x1Hi>>24) < int8(x2Hi>>24),
					int8(x1Hi>>32) < int8(x2Hi>>32), int8(x1Hi>>40) < int8(x2Hi>>40),
					int8(x1Hi>>48) < int8(x2Hi>>48), int8(x1Hi>>56) < int8(x2Hi>>56),
				}
			case v128CmpTypeI8x16LtU:
				result = []bool{
					byte(x1Lo>>0) < byte(x2Lo>>0), byte(x1Lo>>8) < byte(x2Lo>>8),
					byte(x1Lo>>16) < byte(x2Lo>>16), byte(x1Lo>>24) < byte(x2Lo>>24),
					byte(x1Lo>>32) < byte(x2Lo>>32), byte(x1Lo>>40) < byte(x2Lo>>40),
					byte(x1Lo>>48) < byte(x2Lo>>48), byte(x1Lo>>56) < byte(x2Lo>>56),
					byte(x1Hi>>0) < byte(x2Hi>>0), byte(x1Hi>>8) < byte(x2Hi>>8),
					byte(x1Hi>>16) < byte(x2Hi>>16), byte(x1Hi>>24) < byte(x2Hi>>24),
					byte(x1Hi>>32) < byte(x2Hi>>32), byte(x1Hi>>40) < byte(x2Hi>>40),
					byte(x1Hi>>48) < byte(x2Hi>>48), byte(x1Hi>>56) < byte(x2Hi>>56),
				}
			case v128CmpTypeI8x16GtS:
				result = []bool{
					int8(x1Lo>>0) > int8(x2Lo>>0), int8(x1Lo>>8) > int8(x2Lo>>8),
					int8(x1Lo>>16) > int8(x2Lo>>16), int8(x1Lo>>24) > int8(x2Lo>>24),
					int8(x1Lo>>32) > int8(x2Lo>>32), int8(x1Lo>>40) > int8(x2Lo>>40),
					int8(x1Lo>>48) > int8(x2Lo>>48), int8(x1Lo>>56) > int8(x2Lo>>56),
					int8(x1Hi>>0) > int8(x2Hi>>0), int8(x1Hi>>8) > int8(x2Hi>>8),
					int8(x1Hi>>16) > int8(x2Hi>>16), int8(x1Hi>>24) > int8(x2Hi>>24),
					int8(x1Hi>>32) > int8(x2Hi>>32), int8(x1Hi>>40) > int8(x2Hi>>40),
					int8(x1Hi>>48) > int8(x2Hi>>48), int8(x1Hi>>56) > int8(x2Hi>>56),
				}
			case v128CmpTypeI8x16GtU:
				result = []bool{
					byte(x1Lo>>0) > byte(x2Lo>>0), byte(x1Lo>>8) > byte(x2Lo>>8),
					byte(x1Lo>>16) > byte(x2Lo>>16), byte(x1Lo>>24) > byte(x2Lo>>24),
					byte(x1Lo>>32) > byte(x2Lo>>32), byte(x1Lo>>40) > byte(x2Lo>>40),
					byte(x1Lo>>48) > byte(x2Lo>>48), byte(x1Lo>>56) > byte(x2Lo>>56),
					byte(x1Hi>>0) > byte(x2Hi>>0), byte(x1Hi>>8) > byte(x2Hi>>8),
					byte(x1Hi>>16) > byte(x2Hi>>16), byte(x1Hi>>24) > byte(x2Hi>>24),
					byte(x1Hi>>32) > byte(x2Hi>>32), byte(x1Hi>>40) > byte(x2Hi>>40),
					byte(x1Hi>>48) > byte(x2Hi>>48), byte(x1Hi>>56) > byte(x2Hi>>56),
				}
			case v128CmpTypeI8x16LeS:
				result = []bool{
					int8(x1Lo>>0) <= int8(x2Lo>>0), int8(x1Lo>>8) <= int8(x2Lo>>8),
					int8(x1Lo>>16) <= int8(x2Lo>>16), int8(x1Lo>>24) <= int8(x2Lo>>24),
					int8(x1Lo>>32) <= int8(x2Lo>>32), int8(x1Lo>>40) <= int8(x2Lo>>40),
					int8(x1Lo>>48) <= int8(x2Lo>>48), int8(x1Lo>>56) <= int8(x2Lo>>56),
					int8(x1Hi>>0) <= int8(x2Hi>>0), int8(x1Hi>>8) <= int8(x2Hi>>8),
					int8(x1Hi>>16) <= int8(x2Hi>>16), int8(x1Hi>>24) <= int8(x2Hi>>24),
					int8(x1Hi>>32) <= int8(x2Hi>>32), int8(x1Hi>>40) <= int8(x2Hi>>40),
					int8(x1Hi>>48) <= int8(x2Hi>>48), int8(x1Hi>>56) <= int8(x2Hi>>56),
				}
			case v128CmpTypeI8x16LeU:
				result = []bool{
					byte(x1Lo>>0) <= byte(x2Lo>>0), byte(x1Lo>>8) <= byte(x2Lo>>8),
					byte(x1Lo>>16) <= byte(x2Lo>>16), byte(x1Lo>>24) <= byte(x2Lo>>24),
					byte(x1Lo>>32) <= byte(x2Lo>>32), byte(x1Lo>>40) <= byte(x2Lo>>40),
					byte(x1Lo>>48) <= byte(x2Lo>>48), byte(x1Lo>>56) <= byte(x2Lo>>56),
					byte(x1Hi>>0) <= byte(x2Hi>>0), byte(x1Hi>>8) <= byte(x2Hi>>8),
					byte(x1Hi>>16) <= byte(x2Hi>>16), byte(x1Hi>>24) <= byte(x2Hi>>24),
					byte(x1Hi>>32) <= byte(x2Hi>>32), byte(x1Hi>>40) <= byte(x2Hi>>40),
					byte(x1Hi>>48) <= byte(x2Hi>>48), byte(x1Hi>>56) <= byte(x2Hi>>56),
				}
			case v128CmpTypeI8x16GeS:
				result = []bool{
					int8(x1Lo>>0) >= int8(x2Lo>>0), int8(x1Lo>>8) >= int8(x2Lo>>8),
					int8(x1Lo>>16) >= int8(x2Lo>>16), int8(x1Lo>>24) >= int8(x2Lo>>24),
					int8(x1Lo>>32) >= int8(x2Lo>>32), int8(x1Lo>>40) >= int8(x2Lo>>40),
					int8(x1Lo>>48) >= int8(x2Lo>>48), int8(x1Lo>>56) >= int8(x2Lo>>56),
					int8(x1Hi>>0) >= int8(x2Hi>>0), int8(x1Hi>>8) >= int8(x2Hi>>8),
					int8(x1Hi>>16) >= int8(x2Hi>>16), int8(x1Hi>>24) >= int8(x2Hi>>24),
					int8(x1Hi>>32) >= int8(x2Hi>>32), int8(x1Hi>>40) >= int8(x2Hi>>40),
					int8(x1Hi>>48) >= int8(x2Hi>>48), int8(x1Hi>>56) >= int8(x2Hi>>56),
				}
			case v128CmpTypeI8x16GeU:
				result = []bool{
					byte(x1Lo>>0) >= byte(x2Lo>>0), byte(x1Lo>>8) >= byte(x2Lo>>8),
					byte(x1Lo>>16) >= byte(x2Lo>>16), byte(x1Lo>>24) >= byte(x2Lo>>24),
					byte(x1Lo>>32) >= byte(x2Lo>>32), byte(x1Lo>>40) >= byte(x2Lo>>40),
					byte(x1Lo>>48) >= byte(x2Lo>>48), byte(x1Lo>>56) >= byte(x2Lo>>56),
					byte(x1Hi>>0) >= byte(x2Hi>>0), byte(x1Hi>>8) >= byte(x2Hi>>8),
					byte(x1Hi>>16) >= byte(x2Hi>>16), byte(x1Hi>>24) >= byte(x2Hi>>24),
					byte(x1Hi>>32) >= byte(x2Hi>>32), byte(x1Hi>>40) >= byte(x2Hi>>40),
					byte(x1Hi>>48) >= byte(x2Hi>>48), byte(x1Hi>>56) >= byte(x2Hi>>56),
				}
			case v128CmpTypeI16x8Eq:
				result = []bool{
					uint16(x1Lo>>0) == uint16(x2Lo>>0), uint16(x1Lo>>16) == uint16(x2Lo>>16),
					uint16(x1Lo>>32) == uint16(x2Lo>>32), uint16(x1Lo>>48) == uint16(x2Lo>>48),
					uint16(x1Hi>>0) == uint16(x2Hi>>0), uint16(x1Hi>>16) == uint16(x2Hi>>16),
					uint16(x1Hi>>32) == uint16(x2Hi>>32), uint16(x1Hi>>48) == uint16(x2Hi>>48),
				}
			case v128CmpTypeI16x8Ne:
				result = []bool{
					uint16(x1Lo>>0) != uint16(x2Lo>>0), uint16(x1Lo>>16) != uint16(x2Lo>>16),
					uint16(x1Lo>>32) != uint16(x2Lo>>32), uint16(x1Lo>>48) != uint16(x2Lo>>48),
					uint16(x1Hi>>0) != uint16(x2Hi>>0), uint16(x1Hi>>16) != uint16(x2Hi>>16),
					uint16(x1Hi>>32) != uint16(x2Hi>>32), uint16(x1Hi>>48) != uint16(x2Hi>>48),
				}
			case v128CmpTypeI16x8LtS:
				result = []bool{
					int16(x1Lo>>0) < int16(x2Lo>>0), int16(x1Lo>>16) < int16(x2Lo>>16),
					int16(x1Lo>>32) < int16(x2Lo>>32), int16(x1Lo>>48) < int16(x2Lo>>48),
					int16(x1Hi>>0) < int16(x2Hi>>0), int16(x1Hi>>16) < int16(x2Hi>>16),
					int16(x1Hi>>32) < int16(x2Hi>>32), int16(x1Hi>>48) < int16(x2Hi>>48),
				}
			case v128CmpTypeI16x8LtU:
				result = []bool{
					uint16(x1Lo>>0) < uint16(x2Lo>>0), uint16(x1Lo>>16) < uint16(x2Lo>>16),
					uint16(x1Lo>>32) < uint16(x2Lo>>32), uint16(x1Lo>>48) < uint16(x2Lo>>48),
					uint16(x1Hi>>0) < uint16(x2Hi>>0), uint16(x1Hi>>16) < uint16(x2Hi>>16),
					uint16(x1Hi>>32) < uint16(x2Hi>>32), uint16(x1Hi>>48) < uint16(x2Hi>>48),
				}
			case v128CmpTypeI16x8GtS:
				result = []bool{
					int16(x1Lo>>0) > int16(x2Lo>>0), int16(x1Lo>>16) > int16(x2Lo>>16),
					int16(x1Lo>>32) > int16(x2Lo>>32), int16(x1Lo>>48) > int16(x2Lo>>48),
					int16(x1Hi>>0) > int16(x2Hi>>0), int16(x1Hi>>16) > int16(x2Hi>>16),
					int16(x1Hi>>32) > int16(x2Hi>>32), int16(x1Hi>>48) > int16(x2Hi>>48),
				}
			case v128CmpTypeI16x8GtU:
				result = []bool{
					uint16(x1Lo>>0) > uint16(x2Lo>>0), uint16(x1Lo>>16) > uint16(x2Lo>>16),
					uint16(x1Lo>>32) > uint16(x2Lo>>32), uint16(x1Lo>>48) > uint16(x2Lo>>48),
					uint16(x1Hi>>0) > uint16(x2Hi>>0), uint16(x1Hi>>16) > uint16(x2Hi>>16),
					uint16(x1Hi>>32) > uint16(x2Hi>>32), uint16(x1Hi>>48) > uint16(x2Hi>>48),
				}
			case v128CmpTypeI16x8LeS:
				result = []bool{
					int16(x1Lo>>0) <= int16(x2Lo>>0), int16(x1Lo>>16) <= int16(x2Lo>>16),
					int16(x1Lo>>32) <= int16(x2Lo>>32), int16(x1Lo>>48) <= int16(x2Lo>>48),
					int16(x1Hi>>0) <= int16(x2Hi>>0), int16(x1Hi>>16) <= int16(x2Hi>>16),
					int16(x1Hi>>32) <= int16(x2Hi>>32), int16(x1Hi>>48) <= int16(x2Hi>>48),
				}
			case v128CmpTypeI16x8LeU:
				result = []bool{
					uint16(x1Lo>>0) <= uint16(x2Lo>>0), uint16(x1Lo>>16) <= uint16(x2Lo>>16),
					uint16(x1Lo>>32) <= uint16(x2Lo>>32), uint16(x1Lo>>48) <= uint16(x2Lo>>48),
					uint16(x1Hi>>0) <= uint16(x2Hi>>0), uint16(x1Hi>>16) <= uint16(x2Hi>>16),
					uint16(x1Hi>>32) <= uint16(x2Hi>>32), uint16(x1Hi>>48) <= uint16(x2Hi>>48),
				}
			case v128CmpTypeI16x8GeS:
				result = []bool{
					int16(x1Lo>>0) >= int16(x2Lo>>0), int16(x1Lo>>16) >= int16(x2Lo>>16),
					int16(x1Lo>>32) >= int16(x2Lo>>32), int16(x1Lo>>48) >= int16(x2Lo>>48),
					int16(x1Hi>>0) >= int16(x2Hi>>0), int16(x1Hi>>16) >= int16(x2Hi>>16),
					int16(x1Hi>>32) >= int16(x2Hi>>32), int16(x1Hi>>48) >= int16(x2Hi>>48),
				}
			case v128CmpTypeI16x8GeU:
				result = []bool{
					uint16(x1Lo>>0) >= uint16(x2Lo>>0), uint16(x1Lo>>16) >= uint16(x2Lo>>16),
					uint16(x1Lo>>32) >= uint16(x2Lo>>32), uint16(x1Lo>>48) >= uint16(x2Lo>>48),
					uint16(x1Hi>>0) >= uint16(x2Hi>>0), uint16(x1Hi>>16) >= uint16(x2Hi>>16),
					uint16(x1Hi>>32) >= uint16(x2Hi>>32), uint16(x1Hi>>48) >= uint16(x2Hi>>48),
				}
			case v128CmpTypeI32x4Eq:
				result = []bool{
					uint32(x1Lo>>0) == uint32(x2Lo>>0), uint32(x1Lo>>32) == uint32(x2Lo>>32),
					uint32(x1Hi>>0) == uint32(x2Hi>>0), uint32(x1Hi>>32) == uint32(x2Hi>>32),
				}
			case v128CmpTypeI32x4Ne:
				result = []bool{
					uint32(x1Lo>>0) != uint32(x2Lo>>0), uint32(x1Lo>>32) != uint32(x2Lo>>32),
					uint32(x1Hi>>0) != uint32(x2Hi>>0), uint32(x1Hi>>32) != uint32(x2Hi>>32),
				}
			case v128CmpTypeI32x4LtS:
				result = []bool{
					int32(x1Lo>>0) < int32(x2Lo>>0), int32(x1Lo>>32) < int32(x2Lo>>32),
					int32(x1Hi>>0) < int32(x2Hi>>0), int32(x1Hi>>32) < int32(x2Hi>>32),
				}
			case v128CmpTypeI32x4LtU:
				result = []bool{
					uint32(x1Lo>>0) < uint32(x2Lo>>0), uint32(x1Lo>>32) < uint32(x2Lo>>32),
					uint32(x1Hi>>0) < uint32(x2Hi>>0), uint32(x1Hi>>32) < uint32(x2Hi>>32),
				}
			case v128CmpTypeI32x4GtS:
				result = []bool{
					int32(x1Lo>>0) > int32(x2Lo>>0), int32(x1Lo>>32) > int32(x2Lo>>32),
					int32(x1Hi>>0) > int32(x2Hi>>0), int32(x1Hi>>32) > int32(x2Hi>>32),
				}
			case v128CmpTypeI32x4GtU:
				result = []bool{
					uint32(x1Lo>>0) > uint32(x2Lo>>0), uint32(x1Lo>>32) > uint32(x2Lo>>32),
					uint32(x1Hi>>0) > uint32(x2Hi>>0), uint32(x1Hi>>32) > uint32(x2Hi>>32),
				}
			case v128CmpTypeI32x4LeS:
				result = []bool{
					int32(x1Lo>>0) <= int32(x2Lo>>0), int32(x1Lo>>32) <= int32(x2Lo>>32),
					int32(x1Hi>>0) <= int32(x2Hi>>0), int32(x1Hi>>32) <= int32(x2Hi>>32),
				}
			case v128CmpTypeI32x4LeU:
				result = []bool{
					uint32(x1Lo>>0) <= uint32(x2Lo>>0), uint32(x1Lo>>32) <= uint32(x2Lo>>32),
					uint32(x1Hi>>0) <= uint32(x2Hi>>0), uint32(x1Hi>>32) <= uint32(x2Hi>>32),
				}
			case v128CmpTypeI32x4GeS:
				result = []bool{
					int32(x1Lo>>0) >= int32(x2Lo>>0), int32(x1Lo>>32) >= int32(x2Lo>>32),
					int32(x1Hi>>0) >= int32(x2Hi>>0), int32(x1Hi>>32) >= int32(x2Hi>>32),
				}
			case v128CmpTypeI32x4GeU:
				result = []bool{
					uint32(x1Lo>>0) >= uint32(x2Lo>>0), uint32(x1Lo>>32) >= uint32(x2Lo>>32),
					uint32(x1Hi>>0) >= uint32(x2Hi>>0), uint32(x1Hi>>32) >= uint32(x2Hi>>32),
				}
			case v128CmpTypeI64x2Eq:
				result = []bool{x1Lo == x2Lo, x1Hi == x2Hi}
			case v128CmpTypeI64x2Ne:
				result = []bool{x1Lo != x2Lo, x1Hi != x2Hi}
			case v128CmpTypeI64x2LtS:
				result = []bool{int64(x1Lo) < int64(x2Lo), int64(x1Hi) < int64(x2Hi)}
			case v128CmpTypeI64x2GtS:
				result = []bool{int64(x1Lo) > int64(x2Lo), int64(x1Hi) > int64(x2Hi)}
			case v128CmpTypeI64x2LeS:
				result = []bool{int64(x1Lo) <= int64(x2Lo), int64(x1Hi) <= int64(x2Hi)}
			case v128CmpTypeI64x2GeS:
				result = []bool{int64(x1Lo) >= int64(x2Lo), int64(x1Hi) >= int64(x2Hi)}
			case v128CmpTypeF32x4Eq:
				result = []bool{
					math.Float32frombits(uint32(x1Lo>>0)) == math.Float32frombits(uint32(x2Lo>>0)),
					math.Float32frombits(uint32(x1Lo>>32)) == math.Float32frombits(uint32(x2Lo>>32)),
					math.Float32frombits(uint32(x1Hi>>0)) == math.Float32frombits(uint32(x2Hi>>0)),
					math.Float32frombits(uint32(x1Hi>>32)) == math.Float32frombits(uint32(x2Hi>>32)),
				}
			case v128CmpTypeF32x4Ne:
				result = []bool{
					math.Float32frombits(uint32(x1Lo>>0)) != math.Float32frombits(uint32(x2Lo>>0)),
					math.Float32frombits(uint32(x1Lo>>32)) != math.Float32frombits(uint32(x2Lo>>32)),
					math.Float32frombits(uint32(x1Hi>>0)) != math.Float32frombits(uint32(x2Hi>>0)),
					math.Float32frombits(uint32(x1Hi>>32)) != math.Float32frombits(uint32(x2Hi>>32)),
				}
			case v128CmpTypeF32x4Lt:
				result = []bool{
					math.Float32frombits(uint32(x1Lo>>0)) < math.Float32frombits(uint32(x2Lo>>0)),
					math.Float32frombits(uint32(x1Lo>>32)) < math.Float32frombits(uint32(x2Lo>>32)),
					math.Float32frombits(uint32(x1Hi>>0)) < math.Float32frombits(uint32(x2Hi>>0)),
					math.Float32frombits(uint32(x1Hi>>32)) < math.Float32frombits(uint32(x2Hi>>32)),
				}
			case v128CmpTypeF32x4Gt:
				result = []bool{
					math.Float32frombits(uint32(x1Lo>>0)) > math.Float32frombits(uint32(x2Lo>>0)),
					math.Float32frombits(uint32(x1Lo>>32)) > math.Float32frombits(uint32(x2Lo>>32)),
					math.Float32frombits(uint32(x1Hi>>0)) > math.Float32frombits(uint32(x2Hi>>0)),
					math.Float32frombits(uint32(x1Hi>>32)) > math.Float32frombits(uint32(x2Hi>>32)),
				}
			case v128CmpTypeF32x4Le:
				result = []bool{
					math.Float32frombits(uint32(x1Lo>>0)) <= math.Float32frombits(uint32(x2Lo>>0)),
					math.Float32frombits(uint32(x1Lo>>32)) <= math.Float32frombits(uint32(x2Lo>>32)),
					math.Float32frombits(uint32(x1Hi>>0)) <= math.Float32frombits(uint32(x2Hi>>0)),
					math.Float32frombits(uint32(x1Hi>>32)) <= math.Float32frombits(uint32(x2Hi>>32)),
				}
			case v128CmpTypeF32x4Ge:
				result = []bool{
					math.Float32frombits(uint32(x1Lo>>0)) >= math.Float32frombits(uint32(x2Lo>>0)),
					math.Float32frombits(uint32(x1Lo>>32)) >= math.Float32frombits(uint32(x2Lo>>32)),
					math.Float32frombits(uint32(x1Hi>>0)) >= math.Float32frombits(uint32(x2Hi>>0)),
					math.Float32frombits(uint32(x1Hi>>32)) >= math.Float32frombits(uint32(x2Hi>>32)),
				}
			case v128CmpTypeF64x2Eq:
				result = []bool{
					math.Float64frombits(x1Lo) == math.Float64frombits(x2Lo),
					math.Float64frombits(x1Hi) == math.Float64frombits(x2Hi),
				}
			case v128CmpTypeF64x2Ne:
				result = []bool{
					math.Float64frombits(x1Lo) != math.Float64frombits(x2Lo),
					math.Float64frombits(x1Hi) != math.Float64frombits(x2Hi),
				}
			case v128CmpTypeF64x2Lt:
				result = []bool{
					math.Float64frombits(x1Lo) < math.Float64frombits(x2Lo),
					math.Float64frombits(x1Hi) < math.Float64frombits(x2Hi),
				}
			case v128CmpTypeF64x2Gt:
				result = []bool{
					math.Float64frombits(x1Lo) > math.Float64frombits(x2Lo),
					math.Float64frombits(x1Hi) > math.Float64frombits(x2Hi),
				}
			case v128CmpTypeF64x2Le:
				result = []bool{
					math.Float64frombits(x1Lo) <= math.Float64frombits(x2Lo),
					math.Float64frombits(x1Hi) <= math.Float64frombits(x2Hi),
				}
			case v128CmpTypeF64x2Ge:
				result = []bool{
					math.Float64frombits(x1Lo) >= math.Float64frombits(x2Lo),
					math.Float64frombits(x1Hi) >= math.Float64frombits(x2Hi),
				}
			}

			var retLo, retHi uint64
			laneNum := len(result)
			switch laneNum {
			case 16:
				for i, b := range result {
					if b {
						if i < 8 {
							retLo |= 0xff << (i * 8)
						} else {
							retHi |= 0xff << ((i - 8) * 8)
						}
					}
				}
			case 8:
				for i, b := range result {
					if b {
						if i < 4 {
							retLo |= 0xffff << (i * 16)
						} else {
							retHi |= 0xffff << ((i - 4) * 16)
						}
					}
				}
			case 4:
				for i, b := range result {
					if b {
						if i < 2 {
							retLo |= 0xffff_ffff << (i * 32)
						} else {
							retHi |= 0xffff_ffff << ((i - 2) * 32)
						}
					}
				}
			case 2:
				if result[0] {
					retLo = ^uint64(0)
				}
				if result[1] {
					retHi = ^uint64(0)
				}
			}

			ce.pushValue(retLo)
			ce.pushValue(retHi)
			frame.pc++
		case operationKindV128AddSat:
			x2hi, x2Lo := ce.popValue(), ce.popValue()
			x1hi, x1Lo := ce.popValue(), ce.popValue()

			var retLo, retHi uint64

			// Lane-wise addition while saturating the overflowing values.
			// https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md#saturating-integer-addition
			switch op.B1 {
			case shapeI8x16:
				for i := 0; i < 16; i++ {
					var v, w byte
					if i < 8 {
						v, w = byte(x1Lo>>(i*8)), byte(x2Lo>>(i*8))
					} else {
						v, w = byte(x1hi>>((i-8)*8)), byte(x2hi>>((i-8)*8))
					}

					var uv uint64
					if op.B3 { // signed
						if subbed := int64(int8(v)) + int64(int8(w)); subbed < math.MinInt8 {
							uv = uint64(byte(0x80))
						} else if subbed > math.MaxInt8 {
							uv = uint64(byte(0x7f))
						} else {
							uv = uint64(byte(int8(subbed)))
						}
					} else {
						if subbed := int64(v) + int64(w); subbed < 0 {
							uv = uint64(byte(0))
						} else if subbed > math.MaxUint8 {
							uv = uint64(byte(0xff))
						} else {
							uv = uint64(byte(subbed))
						}
					}

					if i < 8 { // first 8 lanes are on lower 64bits.
						retLo |= uv << (i * 8)
					} else {
						retHi |= uv << ((i - 8) * 8)
					}
				}
			case shapeI16x8:
				for i := 0; i < 8; i++ {
					var v, w uint16
					if i < 4 {
						v, w = uint16(x1Lo>>(i*16)), uint16(x2Lo>>(i*16))
					} else {
						v, w = uint16(x1hi>>((i-4)*16)), uint16(x2hi>>((i-4)*16))
					}

					var uv uint64
					if op.B3 { // signed
						if added := int64(int16(v)) + int64(int16(w)); added < math.MinInt16 {
							uv = uint64(uint16(0x8000))
						} else if added > math.MaxInt16 {
							uv = uint64(uint16(0x7fff))
						} else {
							uv = uint64(uint16(int16(added)))
						}
					} else {
						if added := int64(v) + int64(w); added < 0 {
							uv = uint64(uint16(0))
						} else if added > math.MaxUint16 {
							uv = uint64(uint16(0xffff))
						} else {
							uv = uint64(uint16(added))
						}
					}

					if i < 4 { // first 4 lanes are on lower 64bits.
						retLo |= uv << (i * 16)
					} else {
						retHi |= uv << ((i - 4) * 16)
					}
				}
			}

			ce.pushValue(retLo)
			ce.pushValue(retHi)
			frame.pc++
		case operationKindV128SubSat:
			x2hi, x2Lo := ce.popValue(), ce.popValue()
			x1hi, x1Lo := ce.popValue(), ce.popValue()

			var retLo, retHi uint64

			// Lane-wise subtraction while saturating the overflowing values.
			// https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md#saturating-integer-subtraction
			switch op.B1 {
			case shapeI8x16:
				for i := 0; i < 16; i++ {
					var v, w byte
					if i < 8 {
						v, w = byte(x1Lo>>(i*8)), byte(x2Lo>>(i*8))
					} else {
						v, w = byte(x1hi>>((i-8)*8)), byte(x2hi>>((i-8)*8))
					}

					var uv uint64
					if op.B3 { // signed
						if subbed := int64(int8(v)) - int64(int8(w)); subbed < math.MinInt8 {
							uv = uint64(byte(0x80))
						} else if subbed > math.MaxInt8 {
							uv = uint64(byte(0x7f))
						} else {
							uv = uint64(byte(int8(subbed)))
						}
					} else {
						if subbed := int64(v) - int64(w); subbed < 0 {
							uv = uint64(byte(0))
						} else if subbed > math.MaxUint8 {
							uv = uint64(byte(0xff))
						} else {
							uv = uint64(byte(subbed))
						}
					}

					if i < 8 {
						retLo |= uv << (i * 8)
					} else {
						retHi |= uv << ((i - 8) * 8)
					}
				}
			case shapeI16x8:
				for i := 0; i < 8; i++ {
					var v, w uint16
					if i < 4 {
						v, w = uint16(x1Lo>>(i*16)), uint16(x2Lo>>(i*16))
					} else {
						v, w = uint16(x1hi>>((i-4)*16)), uint16(x2hi>>((i-4)*16))
					}

					var uv uint64
					if op.B3 { // signed
						if subbed := int64(int16(v)) - int64(int16(w)); subbed < math.MinInt16 {
							uv = uint64(uint16(0x8000))
						} else if subbed > math.MaxInt16 {
							uv = uint64(uint16(0x7fff))
						} else {
							uv = uint64(uint16(int16(subbed)))
						}
					} else {
						if subbed := int64(v) - int64(w); subbed < 0 {
							uv = uint64(uint16(0))
						} else if subbed > math.MaxUint16 {
							uv = uint64(uint16(0xffff))
						} else {
							uv = uint64(uint16(subbed))
						}
					}

					if i < 4 {
						retLo |= uv << (i * 16)
					} else {
						retHi |= uv << ((i - 4) * 16)
					}
				}
			}

			ce.pushValue(retLo)
			ce.pushValue(retHi)
			frame.pc++
		case operationKindV128Mul:
			x2hi, x2lo := ce.popValue(), ce.popValue()
			x1hi, x1lo := ce.popValue(), ce.popValue()
			var retLo, retHi uint64
			switch op.B1 {
			case shapeI16x8:
				retHi = uint64(uint16(x1hi)*uint16(x2hi)) | (uint64(uint16(x1hi>>16)*uint16(x2hi>>16)) << 16) |
					(uint64(uint16(x1hi>>32)*uint16(x2hi>>32)) << 32) | (uint64(uint16(x1hi>>48)*uint16(x2hi>>48)) << 48)
				retLo = uint64(uint16(x1lo)*uint16(x2lo)) | (uint64(uint16(x1lo>>16)*uint16(x2lo>>16)) << 16) |
					(uint64(uint16(x1lo>>32)*uint16(x2lo>>32)) << 32) | (uint64(uint16(x1lo>>48)*uint16(x2lo>>48)) << 48)
			case shapeI32x4:
				retHi = uint64(uint32(x1hi)*uint32(x2hi)) | (uint64(uint32(x1hi>>32)*uint32(x2hi>>32)) << 32)
				retLo = uint64(uint32(x1lo)*uint32(x2lo)) | (uint64(uint32(x1lo>>32)*uint32(x2lo>>32)) << 32)
			case shapeI64x2:
				retHi = x1hi * x2hi
				retLo = x1lo * x2lo
			case shapeF32x4:
				retHi = mulFloat32bits(uint32(x1hi), uint32(x2hi)) | mulFloat32bits(uint32(x1hi>>32), uint32(x2hi>>32))<<32
				retLo = mulFloat32bits(uint32(x1lo), uint32(x2lo)) | mulFloat32bits(uint32(x1lo>>32), uint32(x2lo>>32))<<32
			case shapeF64x2:
				retHi = math.Float64bits(math.Float64frombits(x1hi) * math.Float64frombits(x2hi))
				retLo = math.Float64bits(math.Float64frombits(x1lo) * math.Float64frombits(x2lo))
			}
			ce.pushValue(retLo)
			ce.pushValue(retHi)
			frame.pc++
		case operationKindV128Div:
			x2hi, x2lo := ce.popValue(), ce.popValue()
			x1hi, x1lo := ce.popValue(), ce.popValue()
			var retLo, retHi uint64
			if op.B1 == shapeF64x2 {
				retHi = math.Float64bits(math.Float64frombits(x1hi) / math.Float64frombits(x2hi))
				retLo = math.Float64bits(math.Float64frombits(x1lo) / math.Float64frombits(x2lo))
			} else {
				retHi = divFloat32bits(uint32(x1hi), uint32(x2hi)) | divFloat32bits(uint32(x1hi>>32), uint32(x2hi>>32))<<32
				retLo = divFloat32bits(uint32(x1lo), uint32(x2lo)) | divFloat32bits(uint32(x1lo>>32), uint32(x2lo>>32))<<32
			}
			ce.pushValue(retLo)
			ce.pushValue(retHi)
			frame.pc++
		case operationKindV128Neg:
			hi, lo := ce.popValue(), ce.popValue()
			switch op.B1 {
			case shapeI8x16:
				lo = uint64(-byte(lo)) | (uint64(-byte(lo>>8)) << 8) |
					(uint64(-byte(lo>>16)) << 16) | (uint64(-byte(lo>>24)) << 24) |
					(uint64(-byte(lo>>32)) << 32) | (uint64(-byte(lo>>40)) << 40) |
					(uint64(-byte(lo>>48)) << 48) | (uint64(-byte(lo>>56)) << 56)
				hi = uint64(-byte(hi)) | (uint64(-byte(hi>>8)) << 8) |
					(uint64(-byte(hi>>16)) << 16) | (uint64(-byte(hi>>24)) << 24) |
					(uint64(-byte(hi>>32)) << 32) | (uint64(-byte(hi>>40)) << 40) |
					(uint64(-byte(hi>>48)) << 48) | (uint64(-byte(hi>>56)) << 56)
			case shapeI16x8:
				hi = uint64(-uint16(hi)) | (uint64(-uint16(hi>>16)) << 16) |
					(uint64(-uint16(hi>>32)) << 32) | (uint64(-uint16(hi>>48)) << 48)
				lo = uint64(-uint16(lo)) | (uint64(-uint16(lo>>16)) << 16) |
					(uint64(-uint16(lo>>32)) << 32) | (uint64(-uint16(lo>>48)) << 48)
			case shapeI32x4:
				hi = uint64(-uint32(hi)) | (uint64(-uint32(hi>>32)) << 32)
				lo = uint64(-uint32(lo)) | (uint64(-uint32(lo>>32)) << 32)
			case shapeI64x2:
				hi = -hi
				lo = -lo
			case shapeF32x4:
				hi = uint64(math.Float32bits(-math.Float32frombits(uint32(hi)))) |
					(uint64(math.Float32bits(-math.Float32frombits(uint32(hi>>32)))) << 32)
				lo = uint64(math.Float32bits(-math.Float32frombits(uint32(lo)))) |
					(uint64(math.Float32bits(-math.Float32frombits(uint32(lo>>32)))) << 32)
			case shapeF64x2:
				hi = math.Float64bits(-math.Float64frombits(hi))
				lo = math.Float64bits(-math.Float64frombits(lo))
			}
			ce.pushValue(lo)
			ce.pushValue(hi)
			frame.pc++
		case operationKindV128Sqrt:
			hi, lo := ce.popValue(), ce.popValue()
			if op.B1 == shapeF64x2 {
				hi = math.Float64bits(math.Sqrt(math.Float64frombits(hi)))
				lo = math.Float64bits(math.Sqrt(math.Float64frombits(lo)))
			} else {
				hi = uint64(math.Float32bits(float32(math.Sqrt(float64(math.Float32frombits(uint32(hi))))))) |
					(uint64(math.Float32bits(float32(math.Sqrt(float64(math.Float32frombits(uint32(hi>>32))))))) << 32)
				lo = uint64(math.Float32bits(float32(math.Sqrt(float64(math.Float32frombits(uint32(lo))))))) |
					(uint64(math.Float32bits(float32(math.Sqrt(float64(math.Float32frombits(uint32(lo>>32))))))) << 32)
			}
			ce.pushValue(lo)
			ce.pushValue(hi)
			frame.pc++
		case operationKindV128Abs:
			hi, lo := ce.popValue(), ce.popValue()
			switch op.B1 {
			case shapeI8x16:
				lo = uint64(i8Abs(byte(lo))) | (uint64(i8Abs(byte(lo>>8))) << 8) |
					(uint64(i8Abs(byte(lo>>16))) << 16) | (uint64(i8Abs(byte(lo>>24))) << 24) |
					(uint64(i8Abs(byte(lo>>32))) << 32) | (uint64(i8Abs(byte(lo>>40))) << 40) |
					(uint64(i8Abs(byte(lo>>48))) << 48) | (uint64(i8Abs(byte(lo>>56))) << 56)
				hi = uint64(i8Abs(byte(hi))) | (uint64(i8Abs(byte(hi>>8))) << 8) |
					(uint64(i8Abs(byte(hi>>16))) << 16) | (uint64(i8Abs(byte(hi>>24))) << 24) |
					(uint64(i8Abs(byte(hi>>32))) << 32) | (uint64(i8Abs(byte(hi>>40))) << 40) |
					(uint64(i8Abs(byte(hi>>48))) << 48) | (uint64(i8Abs(byte(hi>>56))) << 56)
			case shapeI16x8:
				hi = uint64(i16Abs(uint16(hi))) | (uint64(i16Abs(uint16(hi>>16))) << 16) |
					(uint64(i16Abs(uint16(hi>>32))) << 32) | (uint64(i16Abs(uint16(hi>>48))) << 48)
				lo = uint64(i16Abs(uint16(lo))) | (uint64(i16Abs(uint16(lo>>16))) << 16) |
					(uint64(i16Abs(uint16(lo>>32))) << 32) | (uint64(i16Abs(uint16(lo>>48))) << 48)
			case shapeI32x4:
				hi = uint64(i32Abs(uint32(hi))) | (uint64(i32Abs(uint32(hi>>32))) << 32)
				lo = uint64(i32Abs(uint32(lo))) | (uint64(i32Abs(uint32(lo>>32))) << 32)
			case shapeI64x2:
				if int64(hi) < 0 {
					hi = -hi
				}
				if int64(lo) < 0 {
					lo = -lo
				}
			case shapeF32x4:
				hi = hi &^ (1<<31 | 1<<63)
				lo = lo &^ (1<<31 | 1<<63)
			case shapeF64x2:
				hi = hi &^ (1 << 63)
				lo = lo &^ (1 << 63)
			}
			ce.pushValue(lo)
			ce.pushValue(hi)
			frame.pc++
		case operationKindV128Popcnt:
			hi, lo := ce.popValue(), ce.popValue()
			var retLo, retHi uint64
			for i := 0; i < 16; i++ {
				var v byte
				if i < 8 {
					v = byte(lo >> (i * 8))
				} else {
					v = byte(hi >> ((i - 8) * 8))
				}

				var cnt uint64
				for i := 0; i < 8; i++ {
					if (v>>i)&0b1 != 0 {
						cnt++
					}
				}

				if i < 8 {
					retLo |= cnt << (i * 8)
				} else {
					retHi |= cnt << ((i - 8) * 8)
				}
			}
			ce.pushValue(retLo)
			ce.pushValue(retHi)
			frame.pc++
		case operationKindV128Min:
			x2hi, x2lo := ce.popValue(), ce.popValue()
			x1hi, x1lo := ce.popValue(), ce.popValue()
			var retLo, retHi uint64
			switch op.B1 {
			case shapeI8x16:
				if op.B3 { // signed
					retLo = uint64(i8MinS(uint8(x1lo>>8), uint8(x2lo>>8)))<<8 | uint64(i8MinS(uint8(x1lo), uint8(x2lo))) |
						uint64(i8MinS(uint8(x1lo>>24), uint8(x2lo>>24)))<<24 | uint64(i8MinS(uint8(x1lo>>16), uint8(x2lo>>16)))<<16 |
						uint64(i8MinS(uint8(x1lo>>40), uint8(x2lo>>40)))<<40 | uint64(i8MinS(uint8(x1lo>>32), uint8(x2lo>>32)))<<32 |
						uint64(i8MinS(uint8(x1lo>>56), uint8(x2lo>>56)))<<56 | uint64(i8MinS(uint8(x1lo>>48), uint8(x2lo>>48)))<<48
					retHi = uint64(i8MinS(uint8(x1hi>>8), uint8(x2hi>>8)))<<8 | uint64(i8MinS(uint8(x1hi), uint8(x2hi))) |
						uint64(i8MinS(uint8(x1hi>>24), uint8(x2hi>>24)))<<24 | uint64(i8MinS(uint8(x1hi>>16), uint8(x2hi>>16)))<<16 |
						uint64(i8MinS(uint8(x1hi>>40), uint8(x2hi>>40)))<<40 | uint64(i8MinS(uint8(x1hi>>32), uint8(x2hi>>32)))<<32 |
						uint64(i8MinS(uint8(x1hi>>56), uint8(x2hi>>56)))<<56 | uint64(i8MinS(uint8(x1hi>>48), uint8(x2hi>>48)))<<48
				} else {
					retLo = uint64(i8MinU(uint8(x1lo>>8), uint8(x2lo>>8)))<<8 | uint64(i8MinU(uint8(x1lo), uint8(x2lo))) |
						uint64(i8MinU(uint8(x1lo>>24), uint8(x2lo>>24)))<<24 | uint64(i8MinU(uint8(x1lo>>16), uint8(x2lo>>16)))<<16 |
						uint64(i8MinU(uint8(x1lo>>40), uint8(x2lo>>40)))<<40 | uint64(i8MinU(uint8(x1lo>>32), uint8(x2lo>>32)))<<32 |
						uint64(i8MinU(uint8(x1lo>>56), uint8(x2lo>>56)))<<56 | uint64(i8MinU(uint8(x1lo>>48), uint8(x2lo>>48)))<<48
					retHi = uint64(i8MinU(uint8(x1hi>>8), uint8(x2hi>>8)))<<8 | uint64(i8MinU(uint8(x1hi), uint8(x2hi))) |
						uint64(i8MinU(uint8(x1hi>>24), uint8(x2hi>>24)))<<24 | uint64(i8MinU(uint8(x1hi>>16), uint8(x2hi>>16)))<<16 |
						uint64(i8MinU(uint8(x1hi>>40), uint8(x2hi>>40)))<<40 | uint64(i8MinU(uint8(x1hi>>32), uint8(x2hi>>32)))<<32 |
						uint64(i8MinU(uint8(x1hi>>56), uint8(x2hi>>56)))<<56 | uint64(i8MinU(uint8(x1hi>>48), uint8(x2hi>>48)))<<48
				}
			case shapeI16x8:
				if op.B3 { // signed
					retLo = uint64(i16MinS(uint16(x1lo), uint16(x2lo))) |
						uint64(i16MinS(uint16(x1lo>>16), uint16(x2lo>>16)))<<16 |
						uint64(i16MinS(uint16(x1lo>>32), uint16(x2lo>>32)))<<32 |
						uint64(i16MinS(uint16(x1lo>>48), uint16(x2lo>>48)))<<48
					retHi = uint64(i16MinS(uint16(x1hi), uint16(x2hi))) |
						uint64(i16MinS(uint16(x1hi>>16), uint16(x2hi>>16)))<<16 |
						uint64(i16MinS(uint16(x1hi>>32), uint16(x2hi>>32)))<<32 |
						uint64(i16MinS(uint16(x1hi>>48), uint16(x2hi>>48)))<<48
				} else {
					retLo = uint64(i16MinU(uint16(x1lo), uint16(x2lo))) |
						uint64(i16MinU(uint16(x1lo>>16), uint16(x2lo>>16)))<<16 |
						uint64(i16MinU(uint16(x1lo>>32), uint16(x2lo>>32)))<<32 |
						uint64(i16MinU(uint16(x1lo>>48), uint16(x2lo>>48)))<<48
					retHi = uint64(i16MinU(uint16(x1hi), uint16(x2hi))) |
						uint64(i16MinU(uint16(x1hi>>16), uint16(x2hi>>16)))<<16 |
						uint64(i16MinU(uint16(x1hi>>32), uint16(x2hi>>32)))<<32 |
						uint64(i16MinU(uint16(x1hi>>48), uint16(x2hi>>48)))<<48
				}
			case shapeI32x4:
				if op.B3 { // signed
					retLo = uint64(i32MinS(uint32(x1lo), uint32(x2lo))) |
						uint64(i32MinS(uint32(x1lo>>32), uint32(x2lo>>32)))<<32
					retHi = uint64(i32MinS(uint32(x1hi), uint32(x2hi))) |
						uint64(i32MinS(uint32(x1hi>>32), uint32(x2hi>>32)))<<32
				} else {
					retLo = uint64(i32MinU(uint32(x1lo), uint32(x2lo))) |
						uint64(i32MinU(uint32(x1lo>>32), uint32(x2lo>>32)))<<32
					retHi = uint64(i32MinU(uint32(x1hi), uint32(x2hi))) |
						uint64(i32MinU(uint32(x1hi>>32), uint32(x2hi>>32)))<<32
				}
			case shapeF32x4:
				retHi = wasmCompatMin32bits(uint32(x1hi), uint32(x2hi)) |
					wasmCompatMin32bits(uint32(x1hi>>32), uint32(x2hi>>32))<<32
				retLo = wasmCompatMin32bits(uint32(x1lo), uint32(x2lo)) |
					wasmCompatMin32bits(uint32(x1lo>>32), uint32(x2lo>>32))<<32
			case shapeF64x2:
				retHi = math.Float64bits(moremath.WasmCompatMin64(
					math.Float64frombits(x1hi),
					math.Float64frombits(x2hi),
				))
				retLo = math.Float64bits(moremath.WasmCompatMin64(
					math.Float64frombits(x1lo),
					math.Float64frombits(x2lo),
				))
			}
			ce.pushValue(retLo)
			ce.pushValue(retHi)
			frame.pc++
		case operationKindV128Max:
			x2hi, x2lo := ce.popValue(), ce.popValue()
			x1hi, x1lo := ce.popValue(), ce.popValue()
			var retLo, retHi uint64
			switch op.B1 {
			case shapeI8x16:
				if op.B3 { // signed
					retLo = uint64(i8MaxS(uint8(x1lo>>8), uint8(x2lo>>8)))<<8 | uint64(i8MaxS(uint8(x1lo), uint8(x2lo))) |
						uint64(i8MaxS(uint8(x1lo>>24), uint8(x2lo>>24)))<<24 | uint64(i8MaxS(uint8(x1lo>>16), uint8(x2lo>>16)))<<16 |
						uint64(i8MaxS(uint8(x1lo>>40), uint8(x2lo>>40)))<<40 | uint64(i8MaxS(uint8(x1lo>>32), uint8(x2lo>>32)))<<32 |
						uint64(i8MaxS(uint8(x1lo>>56), uint8(x2lo>>56)))<<56 | uint64(i8MaxS(uint8(x1lo>>48), uint8(x2lo>>48)))<<48
					retHi = uint64(i8MaxS(uint8(x1hi>>8), uint8(x2hi>>8)))<<8 | uint64(i8MaxS(uint8(x1hi), uint8(x2hi))) |
						uint64(i8MaxS(uint8(x1hi>>24), uint8(x2hi>>24)))<<24 | uint64(i8MaxS(uint8(x1hi>>16), uint8(x2hi>>16)))<<16 |
						uint64(i8MaxS(uint8(x1hi>>40), uint8(x2hi>>40)))<<40 | uint64(i8MaxS(uint8(x1hi>>32), uint8(x2hi>>32)))<<32 |
						uint64(i8MaxS(uint8(x1hi>>56), uint8(x2hi>>56)))<<56 | uint64(i8MaxS(uint8(x1hi>>48), uint8(x2hi>>48)))<<48
				} else {
					retLo = uint64(i8MaxU(uint8(x1lo>>8), uint8(x2lo>>8)))<<8 | uint64(i8MaxU(uint8(x1lo), uint8(x2lo))) |
						uint64(i8MaxU(uint8(x1lo>>24), uint8(x2lo>>24)))<<24 | uint64(i8MaxU(uint8(x1lo>>16), uint8(x2lo>>16)))<<16 |
						uint64(i8MaxU(uint8(x1lo>>40), uint8(x2lo>>40)))<<40 | uint64(i8MaxU(uint8(x1lo>>32), uint8(x2lo>>32)))<<32 |
						uint64(i8MaxU(uint8(x1lo>>56), uint8(x2lo>>56)))<<56 | uint64(i8MaxU(uint8(x1lo>>48), uint8(x2lo>>48)))<<48
					retHi = uint64(i8MaxU(uint8(x1hi>>8), uint8(x2hi>>8)))<<8 | uint64(i8MaxU(uint8(x1hi), uint8(x2hi))) |
						uint64(i8MaxU(uint8(x1hi>>24), uint8(x2hi>>24)))<<24 | uint64(i8MaxU(uint8(x1hi>>16), uint8(x2hi>>16)))<<16 |
						uint64(i8MaxU(uint8(x1hi>>40), uint8(x2hi>>40)))<<40 | uint64(i8MaxU(uint8(x1hi>>32), uint8(x2hi>>32)))<<32 |
						uint64(i8MaxU(uint8(x1hi>>56), uint8(x2hi>>56)))<<56 | uint64(i8MaxU(uint8(x1hi>>48), uint8(x2hi>>48)))<<48
				}
			case shapeI16x8:
				if op.B3 { // signed
					retLo = uint64(i16MaxS(uint16(x1lo), uint16(x2lo))) |
						uint64(i16MaxS(uint16(x1lo>>16), uint16(x2lo>>16)))<<16 |
						uint64(i16MaxS(uint16(x1lo>>32), uint16(x2lo>>32)))<<32 |
						uint64(i16MaxS(uint16(x1lo>>48), uint16(x2lo>>48)))<<48
					retHi = uint64(i16MaxS(uint16(x1hi), uint16(x2hi))) |
						uint64(i16MaxS(uint16(x1hi>>16), uint16(x2hi>>16)))<<16 |
						uint64(i16MaxS(uint16(x1hi>>32), uint16(x2hi>>32)))<<32 |
						uint64(i16MaxS(uint16(x1hi>>48), uint16(x2hi>>48)))<<48
				} else {
					retLo = uint64(i16MaxU(uint16(x1lo), uint16(x2lo))) |
						uint64(i16MaxU(uint16(x1lo>>16), uint16(x2lo>>16)))<<16 |
						uint64(i16MaxU(uint16(x1lo>>32), uint16(x2lo>>32)))<<32 |
						uint64(i16MaxU(uint16(x1lo>>48), uint16(x2lo>>48)))<<48
					retHi = uint64(i16MaxU(uint16(x1hi), uint16(x2hi))) |
						uint64(i16MaxU(uint16(x1hi>>16), uint16(x2hi>>16)))<<16 |
						uint64(i16MaxU(uint16(x1hi>>32), uint16(x2hi>>32)))<<32 |
						uint64(i16MaxU(uint16(x1hi>>48), uint16(x2hi>>48)))<<48
				}
			case shapeI32x4:
				if op.B3 { // signed
					retLo = uint64(i32MaxS(uint32(x1lo), uint32(x2lo))) |
						uint64(i32MaxS(uint32(x1lo>>32), uint32(x2lo>>32)))<<32
					retHi = uint64(i32MaxS(uint32(x1hi), uint32(x2hi))) |
						uint64(i32MaxS(uint32(x1hi>>32), uint32(x2hi>>32)))<<32
				} else {
					retLo = uint64(i32MaxU(uint32(x1lo), uint32(x2lo))) |
						uint64(i32MaxU(uint32(x1lo>>32), uint32(x2lo>>32)))<<32
					retHi = uint64(i32MaxU(uint32(x1hi), uint32(x2hi))) |
						uint64(i32MaxU(uint32(x1hi>>32), uint32(x2hi>>32)))<<32
				}
			case shapeF32x4:
				retHi = wasmCompatMax32bits(uint32(x1hi), uint32(x2hi)) |
					wasmCompatMax32bits(uint32(x1hi>>32), uint32(x2hi>>32))<<32
				retLo = wasmCompatMax32bits(uint32(x1lo), uint32(x2lo)) |
					wasmCompatMax32bits(uint32(x1lo>>32), uint32(x2lo>>32))<<32
			case shapeF64x2:
				retHi = math.Float64bits(moremath.WasmCompatMax64(
					math.Float64frombits(x1hi),
					math.Float64frombits(x2hi),
				))
				retLo = math.Float64bits(moremath.WasmCompatMax64(
					math.Float64frombits(x1lo),
					math.Float64frombits(x2lo),
				))
			}
			ce.pushValue(retLo)
			ce.pushValue(retHi)
			frame.pc++
		case operationKindV128AvgrU:
			x2hi, x2lo := ce.popValue(), ce.popValue()
			x1hi, x1lo := ce.popValue(), ce.popValue()
			var retLo, retHi uint64
			switch op.B1 {
			case shapeI8x16:
				retLo = uint64(i8RoundingAverage(uint8(x1lo>>8), uint8(x2lo>>8)))<<8 | uint64(i8RoundingAverage(uint8(x1lo), uint8(x2lo))) |
					uint64(i8RoundingAverage(uint8(x1lo>>24), uint8(x2lo>>24)))<<24 | uint64(i8RoundingAverage(uint8(x1lo>>16), uint8(x2lo>>16)))<<16 |
					uint64(i8RoundingAverage(uint8(x1lo>>40), uint8(x2lo>>40)))<<40 | uint64(i8RoundingAverage(uint8(x1lo>>32), uint8(x2lo>>32)))<<32 |
					uint64(i8RoundingAverage(uint8(x1lo>>56), uint8(x2lo>>56)))<<56 | uint64(i8RoundingAverage(uint8(x1lo>>48), uint8(x2lo>>48)))<<48
				retHi = uint64(i8RoundingAverage(uint8(x1hi>>8), uint8(x2hi>>8)))<<8 | uint64(i8RoundingAverage(uint8(x1hi), uint8(x2hi))) |
					uint64(i8RoundingAverage(uint8(x1hi>>24), uint8(x2hi>>24)))<<24 | uint64(i8RoundingAverage(uint8(x1hi>>16), uint8(x2hi>>16)))<<16 |
					uint64(i8RoundingAverage(uint8(x1hi>>40), uint8(x2hi>>40)))<<40 | uint64(i8RoundingAverage(uint8(x1hi>>32), uint8(x2hi>>32)))<<32 |
					uint64(i8RoundingAverage(uint8(x1hi>>56), uint8(x2hi>>56)))<<56 | uint64(i8RoundingAverage(uint8(x1hi>>48), uint8(x2hi>>48)))<<48
			case shapeI16x8:
				retLo = uint64(i16RoundingAverage(uint16(x1lo), uint16(x2lo))) |
					uint64(i16RoundingAverage(uint16(x1lo>>16), uint16(x2lo>>16)))<<16 |
					uint64(i16RoundingAverage(uint16(x1lo>>32), uint16(x2lo>>32)))<<32 |
					uint64(i16RoundingAverage(uint16(x1lo>>48), uint16(x2lo>>48)))<<48
				retHi = uint64(i16RoundingAverage(uint16(x1hi), uint16(x2hi))) |
					uint64(i16RoundingAverage(uint16(x1hi>>16), uint16(x2hi>>16)))<<16 |
					uint64(i16RoundingAverage(uint16(x1hi>>32), uint16(x2hi>>32)))<<32 |
					uint64(i16RoundingAverage(uint16(x1hi>>48), uint16(x2hi>>48)))<<48
			}
			ce.pushValue(retLo)
			ce.pushValue(retHi)
			frame.pc++
		case operationKindV128Pmin:
			x2hi, x2lo := ce.popValue(), ce.popValue()
			x1hi, x1lo := ce.popValue(), ce.popValue()
			var retLo, retHi uint64
			if op.B1 == shapeF32x4 {
				if flt32(math.Float32frombits(uint32(x2lo)), math.Float32frombits(uint32(x1lo))) {
					retLo = x2lo & 0x00000000_ffffffff
				} else {
					retLo = x1lo & 0x00000000_ffffffff
				}
				if flt32(math.Float32frombits(uint32(x2lo>>32)), math.Float32frombits(uint32(x1lo>>32))) {
					retLo |= x2lo & 0xffffffff_00000000
				} else {
					retLo |= x1lo & 0xffffffff_00000000
				}
				if flt32(math.Float32frombits(uint32(x2hi)), math.Float32frombits(uint32(x1hi))) {
					retHi = x2hi & 0x00000000_ffffffff
				} else {
					retHi = x1hi & 0x00000000_ffffffff
				}
				if flt32(math.Float32frombits(uint32(x2hi>>32)), math.Float32frombits(uint32(x1hi>>32))) {
					retHi |= x2hi & 0xffffffff_00000000
				} else {
					retHi |= x1hi & 0xffffffff_00000000
				}
			} else {
				if flt64(math.Float64frombits(x2lo), math.Float64frombits(x1lo)) {
					retLo = x2lo
				} else {
					retLo = x1lo
				}
				if flt64(math.Float64frombits(x2hi), math.Float64frombits(x1hi)) {
					retHi = x2hi
				} else {
					retHi = x1hi
				}
			}
			ce.pushValue(retLo)
			ce.pushValue(retHi)
			frame.pc++
		case operationKindV128Pmax:
			x2hi, x2lo := ce.popValue(), ce.popValue()
			x1hi, x1lo := ce.popValue(), ce.popValue()
			var retLo, retHi uint64
			if op.B1 == shapeF32x4 {
				if flt32(math.Float32frombits(uint32(x1lo)), math.Float32frombits(uint32(x2lo))) {
					retLo = x2lo & 0x00000000_ffffffff
				} else {
					retLo = x1lo & 0x00000000_ffffffff
				}
				if flt32(math.Float32frombits(uint32(x1lo>>32)), math.Float32frombits(uint32(x2lo>>32))) {
					retLo |= x2lo & 0xffffffff_00000000
				} else {
					retLo |= x1lo & 0xffffffff_00000000
				}
				if flt32(math.Float32frombits(uint32(x1hi)), math.Float32frombits(uint32(x2hi))) {
					retHi = x2hi & 0x00000000_ffffffff
				} else {
					retHi = x1hi & 0x00000000_ffffffff
				}
				if flt32(math.Float32frombits(uint32(x1hi>>32)), math.Float32frombits(uint32(x2hi>>32))) {
					retHi |= x2hi & 0xffffffff_00000000
				} else {
					retHi |= x1hi & 0xffffffff_00000000
				}
			} else {
				if flt64(math.Float64frombits(x1lo), math.Float64frombits(x2lo)) {
					retLo = x2lo
				} else {
					retLo = x1lo
				}
				if flt64(math.Float64frombits(x1hi), math.Float64frombits(x2hi)) {
					retHi = x2hi
				} else {
					retHi = x1hi
				}
			}
			ce.pushValue(retLo)
			ce.pushValue(retHi)
			frame.pc++
		case operationKindV128Ceil:
			hi, lo := ce.popValue(), ce.popValue()
			if op.B1 == shapeF32x4 {
				lo = uint64(math.Float32bits(moremath.WasmCompatCeilF32(math.Float32frombits(uint32(lo))))) |
					(uint64(math.Float32bits(moremath.WasmCompatCeilF32(math.Float32frombits(uint32(lo>>32))))) << 32)
				hi = uint64(math.Float32bits(moremath.WasmCompatCeilF32(math.Float32frombits(uint32(hi))))) |
					(uint64(math.Float32bits(moremath.WasmCompatCeilF32(math.Float32frombits(uint32(hi>>32))))) << 32)
			} else {
				lo = math.Float64bits(moremath.WasmCompatCeilF64(math.Float64frombits(lo)))
				hi = math.Float64bits(moremath.WasmCompatCeilF64(math.Float64frombits(hi)))
			}
			ce.pushValue(lo)
			ce.pushValue(hi)
			frame.pc++
		case operationKindV128Floor:
			hi, lo := ce.popValue(), ce.popValue()
			if op.B1 == shapeF32x4 {
				lo = uint64(math.Float32bits(moremath.WasmCompatFloorF32(math.Float32frombits(uint32(lo))))) |
					(uint64(math.Float32bits(moremath.WasmCompatFloorF32(math.Float32frombits(uint32(lo>>32))))) << 32)
				hi = uint64(math.Float32bits(moremath.WasmCompatFloorF32(math.Float32frombits(uint32(hi))))) |
					(uint64(math.Float32bits(moremath.WasmCompatFloorF32(math.Float32frombits(uint32(hi>>32))))) << 32)
			} else {
				lo = math.Float64bits(moremath.WasmCompatFloorF64(math.Float64frombits(lo)))
				hi = math.Float64bits(moremath.WasmCompatFloorF64(math.Float64frombits(hi)))
			}
			ce.pushValue(lo)
			ce.pushValue(hi)
			frame.pc++
		case operationKindV128Trunc:
			hi, lo := ce.popValue(), ce.popValue()
			if op.B1 == shapeF32x4 {
				lo = uint64(math.Float32bits(moremath.WasmCompatTruncF32(math.Float32frombits(uint32(lo))))) |
					(uint64(math.Float32bits(moremath.WasmCompatTruncF32(math.Float32frombits(uint32(lo>>32))))) << 32)
				hi = uint64(math.Float32bits(moremath.WasmCompatTruncF32(math.Float32frombits(uint32(hi))))) |
					(uint64(math.Float32bits(moremath.WasmCompatTruncF32(math.Float32frombits(uint32(hi>>32))))) << 32)
			} else {
				lo = math.Float64bits(moremath.WasmCompatTruncF64(math.Float64frombits(lo)))
				hi = math.Float64bits(moremath.WasmCompatTruncF64(math.Float64frombits(hi)))
			}
			ce.pushValue(lo)
			ce.pushValue(hi)
			frame.pc++
		case operationKindV128Nearest:
			hi, lo := ce.popValue(), ce.popValue()
			if op.B1 == shapeF32x4 {
				lo = uint64(math.Float32bits(moremath.WasmCompatNearestF32(math.Float32frombits(uint32(lo))))) |
					(uint64(math.Float32bits(moremath.WasmCompatNearestF32(math.Float32frombits(uint32(lo>>32))))) << 32)
				hi = uint64(math.Float32bits(moremath.WasmCompatNearestF32(math.Float32frombits(uint32(hi))))) |
					(uint64(math.Float32bits(moremath.WasmCompatNearestF32(math.Float32frombits(uint32(hi>>32))))) << 32)
			} else {
				lo = math.Float64bits(moremath.WasmCompatNearestF64(math.Float64frombits(lo)))
				hi = math.Float64bits(moremath.WasmCompatNearestF64(math.Float64frombits(hi)))
			}
			ce.pushValue(lo)
			ce.pushValue(hi)
			frame.pc++
		case operationKindV128Extend:
			hi, lo := ce.popValue(), ce.popValue()
			var origin uint64
			if op.B3 { // use lower 64 bits
				origin = lo
			} else {
				origin = hi
			}

			signed := op.B2 == 1

			var retHi, retLo uint64
			switch op.B1 {
			case shapeI8x16:
				for i := 0; i < 8; i++ {
					v8 := byte(origin >> (i * 8))

					var v16 uint16
					if signed {
						v16 = uint16(int8(v8))
					} else {
						v16 = uint16(v8)
					}

					if i < 4 {
						retLo |= uint64(v16) << (i * 16)
					} else {
						retHi |= uint64(v16) << ((i - 4) * 16)
					}
				}
			case shapeI16x8:
				for i := 0; i < 4; i++ {
					v16 := uint16(origin >> (i * 16))

					var v32 uint32
					if signed {
						v32 = uint32(int16(v16))
					} else {
						v32 = uint32(v16)
					}

					if i < 2 {
						retLo |= uint64(v32) << (i * 32)
					} else {
						retHi |= uint64(v32) << ((i - 2) * 32)
					}
				}
			case shapeI32x4:
				v32Lo := uint32(origin)
				v32Hi := uint32(origin >> 32)
				if signed {
					retLo = uint64(int32(v32Lo))
					retHi = uint64(int32(v32Hi))
				} else {
					retLo = uint64(v32Lo)
					retHi = uint64(v32Hi)
				}
			}
			ce.pushValue(retLo)
			ce.pushValue(retHi)
			frame.pc++
		case operationKindV128ExtMul:
			x2Hi, x2Lo := ce.popValue(), ce.popValue()
			x1Hi, x1Lo := ce.popValue(), ce.popValue()
			var x1, x2 uint64
			if op.B3 { // use lower 64 bits
				x1, x2 = x1Lo, x2Lo
			} else {
				x1, x2 = x1Hi, x2Hi
			}

			signed := op.B2 == 1

			var retLo, retHi uint64
			switch op.B1 {
			case shapeI8x16:
				for i := 0; i < 8; i++ {
					v1, v2 := byte(x1>>(i*8)), byte(x2>>(i*8))

					var v16 uint16
					if signed {
						v16 = uint16(int16(int8(v1)) * int16(int8(v2)))
					} else {
						v16 = uint16(v1) * uint16(v2)
					}

					if i < 4 {
						retLo |= uint64(v16) << (i * 16)
					} else {
						retHi |= uint64(v16) << ((i - 4) * 16)
					}
				}
			case shapeI16x8:
				for i := 0; i < 4; i++ {
					v1, v2 := uint16(x1>>(i*16)), uint16(x2>>(i*16))

					var v32 uint32
					if signed {
						v32 = uint32(int32(int16(v1)) * int32(int16(v2)))
					} else {
						v32 = uint32(v1) * uint32(v2)
					}

					if i < 2 {
						retLo |= uint64(v32) << (i * 32)
					} else {
						retHi |= uint64(v32) << ((i - 2) * 32)
					}
				}
			case shapeI32x4:
				v1Lo, v2Lo := uint32(x1), uint32(x2)
				v1Hi, v2Hi := uint32(x1>>32), uint32(x2>>32)
				if signed {
					retLo = uint64(int64(int32(v1Lo)) * int64(int32(v2Lo)))
					retHi = uint64(int64(int32(v1Hi)) * int64(int32(v2Hi)))
				} else {
					retLo = uint64(v1Lo) * uint64(v2Lo)
					retHi = uint64(v1Hi) * uint64(v2Hi)
				}
			}

			ce.pushValue(retLo)
			ce.pushValue(retHi)
			frame.pc++
		case operationKindV128Q15mulrSatS:
			x2hi, x2Lo := ce.popValue(), ce.popValue()
			x1hi, x1Lo := ce.popValue(), ce.popValue()
			var retLo, retHi uint64
			for i := 0; i < 8; i++ {
				var v, w int16
				if i < 4 {
					v, w = int16(uint16(x1Lo>>(i*16))), int16(uint16(x2Lo>>(i*16)))
				} else {
					v, w = int16(uint16(x1hi>>((i-4)*16))), int16(uint16(x2hi>>((i-4)*16)))
				}

				var uv uint64
				// https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md#saturating-integer-q-format-rounding-multiplication
				if calc := ((int32(v) * int32(w)) + 0x4000) >> 15; calc < math.MinInt16 {
					uv = uint64(uint16(0x8000))
				} else if calc > math.MaxInt16 {
					uv = uint64(uint16(0x7fff))
				} else {
					uv = uint64(uint16(int16(calc)))
				}

				if i < 4 {
					retLo |= uv << (i * 16)
				} else {
					retHi |= uv << ((i - 4) * 16)
				}
			}

			ce.pushValue(retLo)
			ce.pushValue(retHi)
			frame.pc++
		case operationKindV128ExtAddPairwise:
			hi, lo := ce.popValue(), ce.popValue()

			signed := op.B3

			var retLo, retHi uint64
			switch op.B1 {
			case shapeI8x16:
				for i := 0; i < 8; i++ {
					var v1, v2 byte
					if i < 4 {
						v1, v2 = byte(lo>>((i*2)*8)), byte(lo>>((i*2+1)*8))
					} else {
						v1, v2 = byte(hi>>(((i-4)*2)*8)), byte(hi>>(((i-4)*2+1)*8))
					}

					var v16 uint16
					if signed {
						v16 = uint16(int16(int8(v1)) + int16(int8(v2)))
					} else {
						v16 = uint16(v1) + uint16(v2)
					}

					if i < 4 {
						retLo |= uint64(v16) << (i * 16)
					} else {
						retHi |= uint64(v16) << ((i - 4) * 16)
					}
				}
			case shapeI16x8:
				for i := 0; i < 4; i++ {
					var v1, v2 uint16
					if i < 2 {
						v1, v2 = uint16(lo>>((i*2)*16)), uint16(lo>>((i*2+1)*16))
					} else {
						v1, v2 = uint16(hi>>(((i-2)*2)*16)), uint16(hi>>(((i-2)*2+1)*16))
					}

					var v32 uint32
					if signed {
						v32 = uint32(int32(int16(v1)) + int32(int16(v2)))
					} else {
						v32 = uint32(v1) + uint32(v2)
					}

					if i < 2 {
						retLo |= uint64(v32) << (i * 32)
					} else {
						retHi |= uint64(v32) << ((i - 2) * 32)
					}
				}
			}
			ce.pushValue(retLo)
			ce.pushValue(retHi)
			frame.pc++
		case operationKindV128FloatPromote:
			_, toPromote := ce.popValue(), ce.popValue()
			ce.pushValue(math.Float64bits(float64(math.Float32frombits(uint32(toPromote)))))
			ce.pushValue(math.Float64bits(float64(math.Float32frombits(uint32(toPromote >> 32)))))
			frame.pc++
		case operationKindV128FloatDemote:
			hi, lo := ce.popValue(), ce.popValue()
			ce.pushValue(
				uint64(math.Float32bits(float32(math.Float64frombits(lo)))) |
					(uint64(math.Float32bits(float32(math.Float64frombits(hi)))) << 32),
			)
			ce.pushValue(0)
			frame.pc++
		case operationKindV128FConvertFromI:
			hi, lo := ce.popValue(), ce.popValue()
			v1, v2, v3, v4 := uint32(lo), uint32(lo>>32), uint32(hi), uint32(hi>>32)
			signed := op.B3

			var retLo, retHi uint64
			switch op.B1 { // Destination shape.
			case shapeF32x4: // f32x4 from signed/unsigned i32x4
				if signed {
					retLo = uint64(math.Float32bits(float32(int32(v1)))) |
						(uint64(math.Float32bits(float32(int32(v2)))) << 32)
					retHi = uint64(math.Float32bits(float32(int32(v3)))) |
						(uint64(math.Float32bits(float32(int32(v4)))) << 32)
				} else {
					retLo = uint64(math.Float32bits(float32(v1))) |
						(uint64(math.Float32bits(float32(v2))) << 32)
					retHi = uint64(math.Float32bits(float32(v3))) |
						(uint64(math.Float32bits(float32(v4))) << 32)
				}
			case shapeF64x2: // f64x2 from signed/unsigned i32x4
				if signed {
					retLo, retHi = math.Float64bits(float64(int32(v1))), math.Float64bits(float64(int32(v2)))
				} else {
					retLo, retHi = math.Float64bits(float64(v1)), math.Float64bits(float64(v2))
				}
			}

			ce.pushValue(retLo)
			ce.pushValue(retHi)
			frame.pc++
		case operationKindV128Narrow:
			x2Hi, x2Lo := ce.popValue(), ce.popValue()
			x1Hi, x1Lo := ce.popValue(), ce.popValue()
			signed := op.B3

			var retLo, retHi uint64
			switch op.B1 {
			case shapeI16x8: // signed/unsigned i16x8 to i8x16
				for i := 0; i < 8; i++ {
					var v16 uint16
					if i < 4 {
						v16 = uint16(x1Lo >> (i * 16))
					} else {
						v16 = uint16(x1Hi >> ((i - 4) * 16))
					}

					var v byte
					if signed {
						if s := int16(v16); s > math.MaxInt8 {
							v = math.MaxInt8
						} else if s < math.MinInt8 {
							s = math.MinInt8
							v = byte(s)
						} else {
							v = byte(v16)
						}
					} else {
						if s := int16(v16); s > math.MaxUint8 {
							v = math.MaxUint8
						} else if s < 0 {
							v = 0
						} else {
							v = byte(v16)
						}
					}
					retLo |= uint64(v) << (i * 8)
				}
				for i := 0; i < 8; i++ {
					var v16 uint16
					if i < 4 {
						v16 = uint16(x2Lo >> (i * 16))
					} else {
						v16 = uint16(x2Hi >> ((i - 4) * 16))
					}

					var v byte
					if signed {
						if s := int16(v16); s > math.MaxInt8 {
							v = math.MaxInt8
						} else if s < math.MinInt8 {
							s = math.MinInt8
							v = byte(s)
						} else {
							v = byte(v16)
						}
					} else {
						if s := int16(v16); s > math.MaxUint8 {
							v = math.MaxUint8
						} else if s < 0 {
							v = 0
						} else {
							v = byte(v16)
						}
					}
					retHi |= uint64(v) << (i * 8)
				}
			case shapeI32x4: // signed/unsigned i32x4 to i16x8
				for i := 0; i < 4; i++ {
					var v32 uint32
					if i < 2 {
						v32 = uint32(x1Lo >> (i * 32))
					} else {
						v32 = uint32(x1Hi >> ((i - 2) * 32))
					}

					var v uint16
					if signed {
						if s := int32(v32); s > math.MaxInt16 {
							v = math.MaxInt16
						} else if s < math.MinInt16 {
							s = math.MinInt16
							v = uint16(s)
						} else {
							v = uint16(v32)
						}
					} else {
						if s := int32(v32); s > math.MaxUint16 {
							v = math.MaxUint16
						} else if s < 0 {
							v = 0
						} else {
							v = uint16(v32)
						}
					}
					retLo |= uint64(v) << (i * 16)
				}

				for i := 0; i < 4; i++ {
					var v32 uint32
					if i < 2 {
						v32 = uint32(x2Lo >> (i * 32))
					} else {
						v32 = uint32(x2Hi >> ((i - 2) * 32))
					}

					var v uint16
					if signed {
						if s := int32(v32); s > math.MaxInt16 {
							v = math.MaxInt16
						} else if s < math.MinInt16 {
							s = math.MinInt16
							v = uint16(s)
						} else {
							v = uint16(v32)
						}
					} else {
						if s := int32(v32); s > math.MaxUint16 {
							v = math.MaxUint16
						} else if s < 0 {
							v = 0
						} else {
							v = uint16(v32)
						}
					}
					retHi |= uint64(v) << (i * 16)
				}
			}
			ce.pushValue(retLo)
			ce.pushValue(retHi)
			frame.pc++
		case operationKindV128Dot:
			x2Hi, x2Lo := ce.popValue(), ce.popValue()
			x1Hi, x1Lo := ce.popValue(), ce.popValue()
			lo, hi := v128Dot(x1Hi, x1Lo, x2Hi, x2Lo)
			ce.pushValue(lo)
			ce.pushValue(hi)
			frame.pc++
		case operationKindV128ITruncSatFromF:
			hi, lo := ce.popValue(), ce.popValue()
			signed := op.B3
			var retLo, retHi uint64

			switch op.B1 {
			case shapeF32x4: // f32x4 to i32x4
				for i, f64 := range [4]float64{
					math.Trunc(float64(math.Float32frombits(uint32(lo)))),
					math.Trunc(float64(math.Float32frombits(uint32(lo >> 32)))),
					math.Trunc(float64(math.Float32frombits(uint32(hi)))),
					math.Trunc(float64(math.Float32frombits(uint32(hi >> 32)))),
				} {

					var v uint32
					if math.IsNaN(f64) {
						v = 0
					} else if signed {
						if f64 < math.MinInt32 {
							f64 = math.MinInt32
						} else if f64 > math.MaxInt32 {
							f64 = math.MaxInt32
						}
						v = uint32(int32(f64))
					} else {
						if f64 < 0 {
							f64 = 0
						} else if f64 > math.MaxUint32 {
							f64 = math.MaxUint32
						}
						v = uint32(f64)
					}

					if i < 2 {
						retLo |= uint64(v) << (i * 32)
					} else {
						retHi |= uint64(v) << ((i - 2) * 32)
					}
				}

			case shapeF64x2: // f64x2 to i32x4
				for i, f := range [2]float64{
					math.Trunc(math.Float64frombits(lo)),
					math.Trunc(math.Float64frombits(hi)),
				} {
					var v uint32
					if math.IsNaN(f) {
						v = 0
					} else if signed {
						if f < math.MinInt32 {
							f = math.MinInt32
						} else if f > math.MaxInt32 {
							f = math.MaxInt32
						}
						v = uint32(int32(f))
					} else {
						if f < 0 {
							f = 0
						} else if f > math.MaxUint32 {
							f = math.MaxUint32
						}
						v = uint32(f)
					}

					retLo |= uint64(v) << (i * 32)
				}
			}

			ce.pushValue(retLo)
			ce.pushValue(retHi)
			frame.pc++
		case operationKindAtomicMemoryWait:
			timeout := int64(ce.popValue())
			exp := ce.popValue()
			offset := ce.popMemoryOffset(op)
			// Runtime instead of validation error because the spec intends to allow binaries to include
			// such instructions as long as they are not executed.
			if !memoryInst.Shared {
				panic(wasmruntime.ErrRuntimeExpectedSharedMemory)
			}

			switch unsignedType(op.B1) {
			case unsignedTypeI32:
				if offset%4 != 0 {
					panic(wasmruntime.ErrRuntimeUnalignedAtomic)
				}
				if int(offset) > len(memoryInst.Buffer)-4 {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				ce.pushValue(memoryInst.Wait32(offset, uint32(exp), timeout, func(mem *wasm.MemoryInstance, offset uint32) uint32 {
					mem.Mux.Lock()
					defer mem.Mux.Unlock()
					value, _ := mem.ReadUint32Le(offset)
					return value
				}))
			case unsignedTypeI64:
				if offset%8 != 0 {
					panic(wasmruntime.ErrRuntimeUnalignedAtomic)
				}
				if int(offset) > len(memoryInst.Buffer)-8 {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				ce.pushValue(memoryInst.Wait64(offset, exp, timeout, func(mem *wasm.MemoryInstance, offset uint32) uint64 {
					mem.Mux.Lock()
					defer mem.Mux.Unlock()
					value, _ := mem.ReadUint64Le(offset)
					return value
				}))
			}
			frame.pc++
		case operationKindAtomicMemoryNotify:
			count := ce.popValue()
			offset := ce.popMemoryOffset(op)
			if offset%4 != 0 {
				panic(wasmruntime.ErrRuntimeUnalignedAtomic)
			}
			// Just a bounds check
			if offset >= memoryInst.Size() {
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}
			res := memoryInst.Notify(offset, uint32(count))
			ce.pushValue(uint64(res))
			frame.pc++
		case operationKindAtomicFence:
			// Memory not required for fence only
			if memoryInst != nil {
				// An empty critical section can be used as a synchronization primitive, which is what
				// fence is. Probably, there are no spectests or defined behavior to confirm this yet.
				memoryInst.Mux.Lock()
				memoryInst.Mux.Unlock() //nolint:staticcheck
			}
			frame.pc++
		case operationKindAtomicLoad:
			offset := ce.popMemoryOffset(op)
			switch unsignedType(op.B1) {
			case unsignedTypeI32:
				if offset%4 != 0 {
					panic(wasmruntime.ErrRuntimeUnalignedAtomic)
				}
				memoryInst.Mux.Lock()
				val, ok := memoryInst.ReadUint32Le(offset)
				memoryInst.Mux.Unlock()
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				ce.pushValue(uint64(val))
			case unsignedTypeI64:
				if offset%8 != 0 {
					panic(wasmruntime.ErrRuntimeUnalignedAtomic)
				}
				memoryInst.Mux.Lock()
				val, ok := memoryInst.ReadUint64Le(offset)
				memoryInst.Mux.Unlock()
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				ce.pushValue(val)
			}
			frame.pc++
		case operationKindAtomicLoad8:
			offset := ce.popMemoryOffset(op)
			memoryInst.Mux.Lock()
			val, ok := memoryInst.ReadByte(offset)
			memoryInst.Mux.Unlock()
			if !ok {
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}
			ce.pushValue(uint64(val))
			frame.pc++
		case operationKindAtomicLoad16:
			offset := ce.popMemoryOffset(op)
			if offset%2 != 0 {
				panic(wasmruntime.ErrRuntimeUnalignedAtomic)
			}
			memoryInst.Mux.Lock()
			val, ok := memoryInst.ReadUint16Le(offset)
			memoryInst.Mux.Unlock()
			if !ok {
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}
			ce.pushValue(uint64(val))
			frame.pc++
		case operationKindAtomicStore:
			val := ce.popValue()
			offset := ce.popMemoryOffset(op)
			switch unsignedType(op.B1) {
			case unsignedTypeI32:
				if offset%4 != 0 {
					panic(wasmruntime.ErrRuntimeUnalignedAtomic)
				}
				memoryInst.Mux.Lock()
				ok := memoryInst.WriteUint32Le(offset, uint32(val))
				memoryInst.Mux.Unlock()
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
			case unsignedTypeI64:
				if offset%8 != 0 {
					panic(wasmruntime.ErrRuntimeUnalignedAtomic)
				}
				memoryInst.Mux.Lock()
				ok := memoryInst.WriteUint64Le(offset, val)
				memoryInst.Mux.Unlock()
				if !ok {
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
			}
			frame.pc++
		case operationKindAtomicStore8:
			val := byte(ce.popValue())
			offset := ce.popMemoryOffset(op)
			memoryInst.Mux.Lock()
			ok := memoryInst.WriteByte(offset, val)
			memoryInst.Mux.Unlock()
			if !ok {
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}
			frame.pc++
		case operationKindAtomicStore16:
			val := uint16(ce.popValue())
			offset := ce.popMemoryOffset(op)
			if offset%2 != 0 {
				panic(wasmruntime.ErrRuntimeUnalignedAtomic)
			}
			memoryInst.Mux.Lock()
			ok := memoryInst.WriteUint16Le(offset, val)
			memoryInst.Mux.Unlock()
			if !ok {
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}
			frame.pc++
		case operationKindAtomicRMW:
			val := ce.popValue()
			offset := ce.popMemoryOffset(op)
			switch unsignedType(op.B1) {
			case unsignedTypeI32:
				if offset%4 != 0 {
					panic(wasmruntime.ErrRuntimeUnalignedAtomic)
				}
				memoryInst.Mux.Lock()
				old, ok := memoryInst.ReadUint32Le(offset)
				if !ok {
					memoryInst.Mux.Unlock()
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				var newVal uint32
				switch atomicArithmeticOp(op.B2) {
				case atomicArithmeticOpAdd:
					newVal = old + uint32(val)
				case atomicArithmeticOpSub:
					newVal = old - uint32(val)
				case atomicArithmeticOpAnd:
					newVal = old & uint32(val)
				case atomicArithmeticOpOr:
					newVal = old | uint32(val)
				case atomicArithmeticOpXor:
					newVal = old ^ uint32(val)
				case atomicArithmeticOpNop:
					newVal = uint32(val)
				}
				memoryInst.WriteUint32Le(offset, newVal)
				memoryInst.Mux.Unlock()
				ce.pushValue(uint64(old))
			case unsignedTypeI64:
				if offset%8 != 0 {
					panic(wasmruntime.ErrRuntimeUnalignedAtomic)
				}
				memoryInst.Mux.Lock()
				old, ok := memoryInst.ReadUint64Le(offset)
				if !ok {
					memoryInst.Mux.Unlock()
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				var newVal uint64
				switch atomicArithmeticOp(op.B2) {
				case atomicArithmeticOpAdd:
					newVal = old + val
				case atomicArithmeticOpSub:
					newVal = old - val
				case atomicArithmeticOpAnd:
					newVal = old & val
				case atomicArithmeticOpOr:
					newVal = old | val
				case atomicArithmeticOpXor:
					newVal = old ^ val
				case atomicArithmeticOpNop:
					newVal = val
				}
				memoryInst.WriteUint64Le(offset, newVal)
				memoryInst.Mux.Unlock()
				ce.pushValue(old)
			}
			frame.pc++
		case operationKindAtomicRMW8:
			val := ce.popValue()
			offset := ce.popMemoryOffset(op)
			memoryInst.Mux.Lock()
			old, ok := memoryInst.ReadByte(offset)
			if !ok {
				memoryInst.Mux.Unlock()
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}
			arg := byte(val)
			var newVal byte
			switch atomicArithmeticOp(op.B2) {
			case atomicArithmeticOpAdd:
				newVal = old + arg
			case atomicArithmeticOpSub:
				newVal = old - arg
			case atomicArithmeticOpAnd:
				newVal = old & arg
			case atomicArithmeticOpOr:
				newVal = old | arg
			case atomicArithmeticOpXor:
				newVal = old ^ arg
			case atomicArithmeticOpNop:
				newVal = arg
			}
			memoryInst.WriteByte(offset, newVal)
			memoryInst.Mux.Unlock()
			ce.pushValue(uint64(old))
			frame.pc++
		case operationKindAtomicRMW16:
			val := ce.popValue()
			offset := ce.popMemoryOffset(op)
			if offset%2 != 0 {
				panic(wasmruntime.ErrRuntimeUnalignedAtomic)
			}
			memoryInst.Mux.Lock()
			old, ok := memoryInst.ReadUint16Le(offset)
			if !ok {
				memoryInst.Mux.Unlock()
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}
			arg := uint16(val)
			var newVal uint16
			switch atomicArithmeticOp(op.B2) {
			case atomicArithmeticOpAdd:
				newVal = old + arg
			case atomicArithmeticOpSub:
				newVal = old - arg
			case atomicArithmeticOpAnd:
				newVal = old & arg
			case atomicArithmeticOpOr:
				newVal = old | arg
			case atomicArithmeticOpXor:
				newVal = old ^ arg
			case atomicArithmeticOpNop:
				newVal = arg
			}
			memoryInst.WriteUint16Le(offset, newVal)
			memoryInst.Mux.Unlock()
			ce.pushValue(uint64(old))
			frame.pc++
		case operationKindAtomicRMWCmpxchg:
			rep := ce.popValue()
			exp := ce.popValue()
			offset := ce.popMemoryOffset(op)
			switch unsignedType(op.B1) {
			case unsignedTypeI32:
				if offset%4 != 0 {
					panic(wasmruntime.ErrRuntimeUnalignedAtomic)
				}
				memoryInst.Mux.Lock()
				old, ok := memoryInst.ReadUint32Le(offset)
				if !ok {
					memoryInst.Mux.Unlock()
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				if old == uint32(exp) {
					memoryInst.WriteUint32Le(offset, uint32(rep))
				}
				memoryInst.Mux.Unlock()
				ce.pushValue(uint64(old))
			case unsignedTypeI64:
				if offset%8 != 0 {
					panic(wasmruntime.ErrRuntimeUnalignedAtomic)
				}
				memoryInst.Mux.Lock()
				old, ok := memoryInst.ReadUint64Le(offset)
				if !ok {
					memoryInst.Mux.Unlock()
					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
				}
				if old == exp {
					memoryInst.WriteUint64Le(offset, rep)
				}
				memoryInst.Mux.Unlock()
				ce.pushValue(old)
			}
			frame.pc++
		case operationKindAtomicRMW8Cmpxchg:
			rep := byte(ce.popValue())
			exp := byte(ce.popValue())
			offset := ce.popMemoryOffset(op)
			memoryInst.Mux.Lock()
			old, ok := memoryInst.ReadByte(offset)
			if !ok {
				memoryInst.Mux.Unlock()
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}
			if old == exp {
				memoryInst.WriteByte(offset, rep)
			}
			memoryInst.Mux.Unlock()
			ce.pushValue(uint64(old))
			frame.pc++
		case operationKindAtomicRMW16Cmpxchg:
			rep := uint16(ce.popValue())
			exp := uint16(ce.popValue())
			offset := ce.popMemoryOffset(op)
			if offset%2 != 0 {
				panic(wasmruntime.ErrRuntimeUnalignedAtomic)
			}
			memoryInst.Mux.Lock()
			old, ok := memoryInst.ReadUint16Le(offset)
			if !ok {
				memoryInst.Mux.Unlock()
				panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
			}
			if old == exp {
				memoryInst.WriteUint16Le(offset, rep)
			}
			memoryInst.Mux.Unlock()
			ce.pushValue(uint64(old))
			frame.pc++
		case operationKindThrow:
			tagIndex := uint32(op.U1)
			tag := moduleInst.Tags[tagIndex]
			paramCount := len(tag.Type.Params)
			params := make([]uint64, paramCount)
			for i := paramCount - 1; i >= 0; i-- {
				params[i] = ce.popValue()
			}
			exn := &wasm.Exception{Tag: tag, Params: params}
			if clause, values := searchExceptionTable(exn, frame); clause != nil {
				ce.applyExceptionHandler(frame, clause, values)
				continue
			}
			panic(&thrownException{exception: exn})

		case operationKindThrowRef:
			v := ce.popValue()
			if v == 0 {
				panic(wasmruntime.ErrRuntimeNullReference) // throw_ref on null exnref traps
			}
			// Read the Exception pointer directly from the uint64 value to avoid
			// conversion from uintptr into unsafe.Pointer, which triggers checkptr.
			exn := *(**wasm.Exception)(unsafe.Pointer(&v))
			if clause, values := searchExceptionTable(exn, frame); clause != nil {
				ce.applyExceptionHandler(frame, clause, values)
				continue
			}
			panic(&thrownException{exception: exn})

		case operationKindTailCallReturnCall:
			f := &functions[op.U1]
			ce.dropForTailCall(frame, f)
			body, bodyLen = ce.resetPc(frame, f)

		case operationKindTailCallReturnCallIndirect:
			offset := ce.popValue()
			table := tables[op.U2]
			tf := ce.functionForOffset(table, offset, typeIDs[op.U1])

			// We are allowing proper tail calls only across functions that belong to the same
			// module; for indirect calls, we have to enforce it at run-time.
			// For details, see internal/engine/RATIONALE.md
			if tf.moduleInstance != f.moduleInstance {
				// Revert to a normal call.
				frameUnwound := ce.callWithUnwind(ctx, f.moduleInstance, tf)
				if frameUnwound {
					frame = ce.frames[len(ce.frames)-1]
					body = frame.f.parent.body
					bodyLen = uint64(len(body))
					continue
				}
				// Return
				ce.drop(op.Us[0])
				// Jump to the function frame (return)
				frame.pc = op.Us[1]
				continue
			}

			ce.dropForTailCall(frame, tf)
			body, bodyLen = ce.resetPc(frame, tf)

		case operationKindCallRef:
			ref := ce.popValue()
			if ref == 0 {
				panic(wasmruntime.ErrRuntimeNullReference)
			}
			tf := functionFromUintptr(uintptr(ref))

			frameUnwound := ce.callWithUnwind(ctx, f.moduleInstance, tf)
			if frameUnwound {
				frame = ce.frames[len(ce.frames)-1]
				body = frame.f.parent.body
				bodyLen = uint64(len(body))
				continue
			}
			frame.pc++

		case operationKindReturnCallRef:
			ref := ce.popValue()
			if ref == 0 {
				panic(wasmruntime.ErrRuntimeNullReference)
			}
			tf := functionFromUintptr(uintptr(ref))

			if tf.moduleInstance != f.moduleInstance {
				frameUnwound := ce.callWithUnwind(ctx, f.moduleInstance, tf)
				if frameUnwound {
					frame = ce.frames[len(ce.frames)-1]
					body = frame.f.parent.body
					bodyLen = uint64(len(body))
					continue
				}
				ce.drop(op.Us[0])
				frame.pc = op.Us[1]
				continue
			}

			ce.dropForTailCall(frame, tf)
			body, bodyLen = ce.resetPc(frame, tf)

		case operationKindRefAsNonNull:
			ref := ce.popValue()
			if ref == 0 {
				panic(wasmruntime.ErrRuntimeNullReference)
			}
			ce.pushValue(ref)
			frame.pc++

		case operationKindBrOnNull:
			ref := ce.popValue()
			if ref == 0 {
				ce.drop(op.U3)
				frame.pc = op.U1
			} else {
				ce.pushValue(ref)
				frame.pc = op.U2
			}

		case operationKindBrOnNonNull:
			ref := ce.popValue()
			if ref != 0 {
				ce.drop(op.U3)
				ce.pushValue(ref)
				frame.pc = op.U1
			} else {
				frame.pc = op.U2
			}

		default:
			frame.pc++
		}
	}
	ce.popFrame()
}

func (ce *callEngine) dropForTailCall(frame *callFrame, f *function) {
	base := frame.base - frame.f.funcType.ParamNumInUint64
	paramCount := f.funcType.ParamNumInUint64
	ce.stack = append(ce.stack[:base], ce.stack[len(ce.stack)-paramCount:]...)
}

func (ce *callEngine) resetPc(frame *callFrame, f *function) (body []unionOperation, bodyLen uint64) {
	// The compiler is currently allowing proper tail call only across functions
	// that belong to the same module; thus, we can overwrite the frame in-place.
	// For details, see internal/engine/RATIONALE.md
	frame.f = f
	frame.base = len(ce.stack)
	frame.pc = 0
	body = frame.f.parent.body
	bodyLen = uint64(len(body))
	return body, bodyLen
}

func (ce *callEngine) functionForOffset(table *wasm.TableInstance, offset uint64, expectedTypeID wasm.FunctionTypeID) *function {
	if offset >= uint64(len(table.References)) {
		panic(wasmruntime.ErrRuntimeInvalidTableAccess)
	}
	rawPtr := table.References[offset]
	if rawPtr == 0 {
		panic(wasmruntime.ErrRuntimeInvalidTableAccess)
	}

	tf := functionFromUintptr(rawPtr)
	if tf.typeID != expectedTypeID {
		panic(wasmruntime.ErrRuntimeIndirectCallTypeMismatch)
	}
	return tf
}

func wasmCompatMax32bits(v1, v2 uint32) uint64 {
	return uint64(math.Float32bits(moremath.WasmCompatMax32(
		math.Float32frombits(v1),
		math.Float32frombits(v2),
	)))
}

func wasmCompatMin32bits(v1, v2 uint32) uint64 {
	return uint64(math.Float32bits(moremath.WasmCompatMin32(
		math.Float32frombits(v1),
		math.Float32frombits(v2),
	)))
}

func addFloat32bits(v1, v2 uint32) uint64 {
	return uint64(math.Float32bits(math.Float32frombits(v1) + math.Float32frombits(v2)))
}

func subFloat32bits(v1, v2 uint32) uint64 {
	return uint64(math.Float32bits(math.Float32frombits(v1) - math.Float32frombits(v2)))
}

func mulFloat32bits(v1, v2 uint32) uint64 {
	return uint64(math.Float32bits(math.Float32frombits(v1) * math.Float32frombits(v2)))
}

func divFloat32bits(v1, v2 uint32) uint64 {
	return uint64(math.Float32bits(math.Float32frombits(v1) / math.Float32frombits(v2)))
}

// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#xref-exec-numerics-op-flt-mathrm-flt-n-z-1-z-2
func flt32(z1, z2 float32) bool {
	if z1 != z1 || z2 != z2 {
		return false
	} else if z1 == z2 {
		return false
	} else if math.IsInf(float64(z1), 1) {
		return false
	} else if math.IsInf(float64(z1), -1) {
		return true
	} else if math.IsInf(float64(z2), 1) {
		return true
	} else if math.IsInf(float64(z2), -1) {
		return false
	}
	return z1 < z2
}

// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#xref-exec-numerics-op-flt-mathrm-flt-n-z-1-z-2
func flt64(z1, z2 float64) bool {
	if z1 != z1 || z2 != z2 {
		return false
	} else if z1 == z2 {
		return false
	} else if math.IsInf(z1, 1) {
		return false
	} else if math.IsInf(z1, -1) {
		return true
	} else if math.IsInf(z2, 1) {
		return true
	} else if math.IsInf(z2, -1) {
		return false
	}
	return z1 < z2
}

func i8RoundingAverage(v1, v2 byte) byte {
	// https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md#lane-wise-integer-rounding-average
	return byte((uint16(v1) + uint16(v2) + uint16(1)) / 2)
}

func i16RoundingAverage(v1, v2 uint16) uint16 {
	// https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md#lane-wise-integer-rounding-average
	return uint16((uint32(v1) + uint32(v2) + 1) / 2)
}

func i8Abs(v byte) byte {
	if i := int8(v); i < 0 {
		return byte(-i)
	} else {
		return byte(i)
	}
}

func i8MaxU(v1, v2 byte) byte {
	if v1 < v2 {
		return v2
	} else {
		return v1
	}
}

func i8MinU(v1, v2 byte) byte {
	if v1 > v2 {
		return v2
	} else {
		return v1
	}
}

func i8MaxS(v1, v2 byte) byte {
	if int8(v1) < int8(v2) {
		return v2
	} else {
		return v1
	}
}

func i8MinS(v1, v2 byte) byte {
	if int8(v1) > int8(v2) {
		return v2
	} else {
		return v1
	}
}

func i16MaxU(v1, v2 uint16) uint16 {
	if v1 < v2 {
		return v2
	} else {
		return v1
	}
}

func i16MinU(v1, v2 uint16) uint16 {
	if v1 > v2 {
		return v2
	} else {
		return v1
	}
}

func i16MaxS(v1, v2 uint16) uint16 {
	if int16(v1) < int16(v2) {
		return v2
	} else {
		return v1
	}
}

func i16MinS(v1, v2 uint16) uint16 {
	if int16(v1) > int16(v2) {
		return v2
	} else {
		return v1
	}
}

func i32MaxU(v1, v2 uint32) uint32 {
	if v1 < v2 {
		return v2
	} else {
		return v1
	}
}

func i32MinU(v1, v2 uint32) uint32 {
	if v1 > v2 {
		return v2
	} else {
		return v1
	}
}

func i32MaxS(v1, v2 uint32) uint32 {
	if int32(v1) < int32(v2) {
		return v2
	} else {
		return v1
	}
}

func i32MinS(v1, v2 uint32) uint32 {
	if int32(v1) > int32(v2) {
		return v2
	} else {
		return v1
	}
}

func i16Abs(v uint16) uint16 {
	if i := int16(v); i < 0 {
		return uint16(-i)
	} else {
		return uint16(i)
	}
}

func i32Abs(v uint32) uint32 {
	if i := int32(v); i < 0 {
		return uint32(-i)
	} else {
		return uint32(i)
	}
}

func (ce *callEngine) callNativeFuncWithListener(ctx context.Context, m *wasm.ModuleInstance, f *function, fnl experimental.FunctionListener) context.Context {
	def, typ := f.definition(), f.funcType

	ce.stackIterator.reset(ce.stack, ce.frames, f)
	fnl.Before(ctx, m, def, ce.peekValues(typ.ParamNumInUint64), &ce.stackIterator)
	ce.stackIterator.clear()
	ce.callNativeFunc(ctx, m, f)
	fnl.After(ctx, m, def, ce.peekValues(typ.ResultNumInUint64))
	return ctx
}

// popMemoryOffset takes a memory offset off the stack for use in load and store instructions.
// As the top of stack value is 64-bit, this ensures it is in range before returning it.
func (ce *callEngine) popMemoryOffset(op *unionOperation) uint32 {
	// Memory addresses are i32; mask to 32 bits to ignore any
	// garbage in the upper bits of the uint64 stack slot.
	offset := op.U2 + uint64(uint32(ce.popValue()))
	if offset > math.MaxUint32 {
		panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
	}
	return uint32(offset)
}

func (ce *callEngine) callGoFuncWithStack(ctx context.Context, m *wasm.ModuleInstance, f *function) {
	typ := f.funcType
	paramLen := typ.ParamNumInUint64
	resultLen := typ.ResultNumInUint64
	stackLen := paramLen

	// In the interpreter engine, ce.stack may only have capacity to store
	// parameters. Grow when there are more results than parameters.
	if growLen := resultLen - paramLen; growLen > 0 {
		ce.stack = append(ce.stack, make([]uint64, growLen)...)
		stackLen += growLen
	}

	// Pass the stack elements to the go function.
	stack := ce.stack[len(ce.stack)-stackLen:]
	ce.callGoFunc(ctx, m, f, stack)

	// Shrink the stack when there were more parameters than results.
	if shrinkLen := paramLen - resultLen; shrinkLen > 0 {
		ce.stack = ce.stack[0 : len(ce.stack)-shrinkLen]
	}
}

// v128Dot performs a dot product of two 64-bit vectors.
// Note: for some reason (which I suspect is due to a bug in Go compiler's regalloc),
// inlining this function causes a bug which happens **only when** we run with -race AND arm64 AND Go 1.22.
func v128Dot(x1Hi, x1Lo, x2Hi, x2Lo uint64) (uint64, uint64) {
	r1 := int32(int16(x1Lo>>0)) * int32(int16(x2Lo>>0))
	r2 := int32(int16(x1Lo>>16)) * int32(int16(x2Lo>>16))
	r3 := int32(int16(x1Lo>>32)) * int32(int16(x2Lo>>32))
	r4 := int32(int16(x1Lo>>48)) * int32(int16(x2Lo>>48))
	r5 := int32(int16(x1Hi>>0)) * int32(int16(x2Hi>>0))
	r6 := int32(int16(x1Hi>>16)) * int32(int16(x2Hi>>16))
	r7 := int32(int16(x1Hi>>32)) * int32(int16(x2Hi>>32))
	r8 := int32(int16(x1Hi>>48)) * int32(int16(x2Hi>>48))
	return uint64(uint32(r1+r2)) | (uint64(uint32(r3+r4)) << 32), uint64(uint32(r5+r6)) | (uint64(uint32(r7+r8)) << 32)
}
