package interpreter

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"math"
	"strings"

	"github.com/tetratelabs/wazero/api"
	"github.com/tetratelabs/wazero/internal/leb128"
	"github.com/tetratelabs/wazero/internal/wasm"
)

type controlFrameKind byte

const (
	controlFrameKindBlockWithContinuationLabel controlFrameKind = iota
	controlFrameKindBlockWithoutContinuationLabel
	controlFrameKindFunction
	controlFrameKindLoop
	controlFrameKindIfWithElse
	controlFrameKindIfWithoutElse
	controlFrameKindTryTable
)

type (
	controlFrame struct {
		frameID uint32
		// originalStackLenWithoutParam holds the number of values on the stack
		// when Start executing this control frame minus params for the block.
		originalStackLenWithoutParam int
		// originalStackLenWithoutParamUint64 is almost the same as originalStackLenWithoutParam
		// except that it holds the number of values on the stack in uint64.
		originalStackLenWithoutParamUint64 int
		blockType                          *wasm.FunctionType
		kind                               controlFrameKind
	}
	controlFrames struct{ frames []controlFrame }
)

func (c *controlFrame) ensureContinuation() {
	// Make sure that if the frame is block and doesn't have continuation,
	// change the Kind so we can emit the continuation block
	// later when we reach the End instruction of this frame.
	if c.kind == controlFrameKindBlockWithoutContinuationLabel {
		c.kind = controlFrameKindBlockWithContinuationLabel
	}
}

func (c *controlFrame) asLabel() label {
	switch c.kind {
	case controlFrameKindBlockWithContinuationLabel,
		controlFrameKindBlockWithoutContinuationLabel:
		return newLabel(labelKindContinuation, c.frameID)
	case controlFrameKindLoop:
		return newLabel(labelKindHeader, c.frameID)
	case controlFrameKindFunction:
		return newLabel(labelKindReturn, 0)
	case controlFrameKindIfWithElse,
		controlFrameKindIfWithoutElse,
		controlFrameKindTryTable:
		return newLabel(labelKindContinuation, c.frameID)
	}
	panic(fmt.Sprintf("unreachable: a bug in interpreterir implementation: %v", c.kind))
}

func (c *controlFrames) functionFrame() *controlFrame {
	// No need to check stack bound
	// as we can assume that all the operations
	// are valid thanks to validateFunction
	// at module validation phase.
	return &c.frames[0]
}

func (c *controlFrames) get(n int) *controlFrame {
	// No need to check stack bound
	// as we can assume that all the operations
	// are valid thanks to validateFunction
	// at module validation phase.
	return &c.frames[len(c.frames)-n-1]
}

func (c *controlFrames) top() *controlFrame {
	// No need to check stack bound
	// as we can assume that all the operations
	// are valid thanks to validateFunction
	// at module validation phase.
	return &c.frames[len(c.frames)-1]
}

func (c *controlFrames) empty() bool {
	return len(c.frames) == 0
}

func (c *controlFrames) pop() (frame *controlFrame) {
	// No need to check stack bound
	// as we can assume that all the operations
	// are valid thanks to validateFunction
	// at module validation phase.
	frame = c.top()
	c.frames = c.frames[:len(c.frames)-1]
	return
}

func (c *controlFrames) push(frame controlFrame) {
	c.frames = append(c.frames, frame)
}

func (c *compiler) initializeStack() {
	// Reuse the existing slice.
	c.localIndexToStackHeightInUint64 = c.localIndexToStackHeightInUint64[:0]
	var current int
	for _, lt := range c.sig.Params {
		c.localIndexToStackHeightInUint64 = append(c.localIndexToStackHeightInUint64, current)
		if lt == wasm.ValueTypeV128 {
			current++
		}
		current++
	}

	if c.callFrameStackSizeInUint64 > 0 {
		// We reserve the stack slots for result values below the return call frame slots.
		if diff := c.sig.ResultNumInUint64 - c.sig.ParamNumInUint64; diff > 0 {
			current += diff
		}
	}

	// Non-func param locals Start after the return call frame.
	current += c.callFrameStackSizeInUint64

	for _, lt := range c.localTypes {
		c.localIndexToStackHeightInUint64 = append(c.localIndexToStackHeightInUint64, current)
		if lt == wasm.ValueTypeV128 {
			current++
		}
		current++
	}

	// Push function arguments.
	for _, t := range c.sig.Params {
		c.stackPush(wasmValueTypeTounsignedType(t))
	}

	if c.callFrameStackSizeInUint64 > 0 {
		// Reserve the stack slots for results.
		for i := 0; i < c.sig.ResultNumInUint64-c.sig.ParamNumInUint64; i++ {
			c.stackPush(unsignedTypeI64)
		}

		// Reserve the stack slots for call frame.
		for i := 0; i < c.callFrameStackSizeInUint64; i++ {
			c.stackPush(unsignedTypeI64)
		}
	}
}

// compiler is in charge of lowering raw Wasm function body to get compilationResult.
// This is created per *wasm.Module and reused for all functions in it to reduce memory allocations.
type compiler struct {
	module                     *wasm.Module
	enabledFeatures            api.CoreFeatures
	callFrameStackSizeInUint64 int
	stack                      []unsignedType
	// stackLenInUint64 is the length of the stack in uint64.
	stackLenInUint64 int
	currentFrameID   uint32
	controlFrames    controlFrames
	unreachableState struct {
		on    bool
		depth int
	}
	pc, currentOpPC uint64
	result          compilationResult

	// body holds the code for the function's body where Wasm instructions are stored.
	body []byte
	// sig is the function type of the target function.
	sig *wasm.FunctionType
	// localTypes holds the target function locals' value types except function params.
	localTypes []wasm.ValueType
	// localIndexToStackHeightInUint64 maps the local index (starting with function params) to the stack height
	// where the local is places. This is the necessary mapping for functions who contain vector type locals.
	localIndexToStackHeightInUint64 []int

	// types hold all the function types in the module where the targe function exists.
	types []wasm.FunctionType
	// funcs holds the type indexes for all declared functions in the module where the target function exists.
	funcs []uint32
	// globals holds the global types for all declared globals in the module where the target function exists.
	globals []wasm.GlobalType
	// tags holds the type indexes for all declared tags in the module where the target function exists.
	tags []uint32

	// needSourceOffset is true if this module requires DWARF based stack trace.
	needSourceOffset bool
	// bodyOffsetInCodeSection is the offset of the body of this function in the original Wasm binary's code section.
	bodyOffsetInCodeSection uint64

	ensureTermination bool
	// Pre-allocated bytes.Reader to be used in various places.
	br             *bytes.Reader
	funcTypeToSigs funcTypeToIRSignatures

	next int
}

//lint:ignore U1000 for debugging only.
func (c *compiler) stackDump() string {
	strs := make([]string, 0, len(c.stack))
	for _, s := range c.stack {
		strs = append(strs, s.String())
	}
	return "[" + strings.Join(strs, ", ") + "]"
}

func (c *compiler) markUnreachable() {
	c.unreachableState.on = true
}

func (c *compiler) resetUnreachable() {
	c.unreachableState.on = false
}

// memoryType is the type of memory in a compiled module.
type memoryType byte

const (
	// memoryTypeNone indicates there is no memory.
	memoryTypeNone memoryType = iota
	// memoryTypeStandard indicates there is a non-shared memory.
	memoryTypeStandard
	// memoryTypeShared indicates there is a shared memory.
	memoryTypeShared
)

type compilationResult struct {
	// Operations holds interpreterir operations compiled from Wasm instructions in a Wasm function.
	Operations []unionOperation

	// IROperationSourceOffsetsInWasmBinary is index-correlated with Operation and maps each operation to the corresponding source instruction's
	// offset in the original WebAssembly binary.
	// Non nil only when the given Wasm module has the DWARF section.
	IROperationSourceOffsetsInWasmBinary []uint64

	// LabelCallers maps label to the number of callers to that label.
	// Here "callers" means that the call-sites which jumps to the label with br, br_if or br_table
	// instructions.
	//
	// Note: zero possible and allowed in wasm. e.g.
	//
	//	(block
	//	  (br 0)
	//	  (block i32.const 1111)
	//	)
	//
	// This example the label corresponding to `(block i32.const 1111)` is never be reached at runtime because `br 0` exits the function before we reach there
	LabelCallers map[label]uint32
	// UsesMemory is true if this function might use memory.
	UsesMemory bool
	// PendingExceptionTable holds unresolved exception table entries, built during
	// compilation. Labels are resolved to final PCs in lowerIR.
	PendingExceptionTable []pendingExceptionTableEntry

	// The following fields are per-module values, not per-function.

	// Globals holds all the declarations of globals in the module from which this function is compiled.
	Globals []wasm.GlobalType
	// Functions holds all the declarations of function in the module from which this function is compiled, including itself.
	Functions []wasm.Index
	// Types holds all the types in the module from which this function is compiled.
	Types []wasm.FunctionType
	// Memory indicates the type of memory of the module.
	Memory memoryType
	// HasTable is true if the module from which this function is compiled has table declaration.
	HasTable bool
	// HasDataInstances is true if the module has data instances which might be used by memory.init or data.drop instructions.
	HasDataInstances bool
	// HasDataInstances is true if the module has element instances which might be used by table.init or elem.drop instructions.
	HasElementInstances bool
}

// newCompiler returns the new *compiler for the given parameters.
// Use compiler.Next function to get compilation result per function.
func newCompiler(enabledFeatures api.CoreFeatures, callFrameStackSizeInUint64 int, module *wasm.Module, ensureTermination bool) (*compiler, error) {
	functions, globals, mem, tables, tags, err := module.AllDeclarations()
	if err != nil {
		return nil, err
	}

	hasTable, hasDataInstances, hasElementInstances := len(tables) > 0,
		len(module.DataSection) > 0, len(module.ElementSection) > 0

	var mt memoryType
	switch {
	case mem == nil:
		mt = memoryTypeNone
	case mem.IsShared:
		mt = memoryTypeShared
	default:
		mt = memoryTypeStandard
	}

	types := module.TypeSection

	c := &compiler{
		module:                     module,
		enabledFeatures:            enabledFeatures,
		controlFrames:              controlFrames{},
		callFrameStackSizeInUint64: callFrameStackSizeInUint64,
		result: compilationResult{
			Globals:             globals,
			Functions:           functions,
			Types:               types,
			Memory:              mt,
			HasTable:            hasTable,
			HasDataInstances:    hasDataInstances,
			HasElementInstances: hasElementInstances,
			LabelCallers:        map[label]uint32{},
		},
		globals:           globals,
		funcs:             functions,
		tags:              tags,
		types:             types,
		ensureTermination: ensureTermination,
		br:                bytes.NewReader(nil),
		funcTypeToSigs: funcTypeToIRSignatures{
			indirectCalls: make([]*signature, len(types)),
			directCalls:   make([]*signature, len(types)),
			callRefCalls:  make([]*signature, len(types)),
			wasmTypes:     types,
		},
		needSourceOffset: module.DWARFLines != nil,
	}
	return c, nil
}

// Next returns the next compilationResult for this compiler.
func (c *compiler) Next() (*compilationResult, error) {
	funcIndex := c.next
	code := &c.module.CodeSection[funcIndex]
	sig := &c.types[c.module.FunctionSection[funcIndex]]

	// Reset the previous result.
	c.result.Operations = c.result.Operations[:0]
	c.result.IROperationSourceOffsetsInWasmBinary = c.result.IROperationSourceOffsetsInWasmBinary[:0]
	c.result.UsesMemory = false
	c.result.PendingExceptionTable = c.result.PendingExceptionTable[:0]
	// Clears the existing entries in LabelCallers.
	for frameID := uint32(0); frameID <= c.currentFrameID; frameID++ {
		for k := labelKind(0); k < labelKindNum; k++ {
			delete(c.result.LabelCallers, newLabel(k, frameID))
		}
	}
	// Reset the previous states.
	c.pc = 0
	c.currentOpPC = 0
	c.currentFrameID = 0
	c.stackLenInUint64 = 0
	c.unreachableState.on, c.unreachableState.depth = false, 0

	if err := c.compile(sig, code.Body, code.LocalTypes, code.BodyOffsetInCodeSection); err != nil {
		return nil, err
	}
	c.next++
	return &c.result, nil
}

// Compile lowers given function instance into interpreterir operations
// so that the resulting operations can be consumed by the interpreter
// or the compiler compilation engine.
func (c *compiler) compile(sig *wasm.FunctionType, body []byte, localTypes []wasm.ValueType, bodyOffsetInCodeSection uint64) error {
	// Set function specific fields.
	c.body = body
	c.localTypes = localTypes
	c.sig = sig
	c.bodyOffsetInCodeSection = bodyOffsetInCodeSection

	// Reuses the underlying slices.
	c.stack = c.stack[:0]
	c.controlFrames.frames = c.controlFrames.frames[:0]

	c.initializeStack()

	// Emit const expressions for locals.
	// Note that here we don't take function arguments
	// into account, meaning that callers must push
	// arguments before entering into the function body.
	for _, t := range c.localTypes {
		c.emitDefaultValue(t)
	}

	// Insert the function control frame.
	c.controlFrames.push(controlFrame{
		frameID:   c.nextFrameID(),
		blockType: c.sig,
		kind:      controlFrameKindFunction,
	})

	// Now, enter the function body.
	for !c.controlFrames.empty() && c.pc < uint64(len(c.body)) {
		if err := c.handleInstruction(); err != nil {
			return fmt.Errorf("handling instruction: %w", err)
		}
	}
	return nil
}

// Translate the current Wasm instruction to interpreterir's operations,
// and emit the results into c.results.
func (c *compiler) handleInstruction() error {
	op := c.body[c.pc]
	c.currentOpPC = c.pc
	if false {
		var instName string
		if op == wasm.OpcodeVecPrefix {
			instName = wasm.VectorInstructionName(c.body[c.pc+1])
		} else if op == wasm.OpcodeAtomicPrefix {
			instName = wasm.AtomicInstructionName(c.body[c.pc+1])
		} else if op == wasm.OpcodeMiscPrefix {
			instName = wasm.MiscInstructionName(c.body[c.pc+1])
		} else {
			instName = wasm.InstructionName(op)
		}
		fmt.Printf("handling %s, unreachable_state(on=%v,depth=%d), stack=%v\n",
			instName, c.unreachableState.on, c.unreachableState.depth, c.stack,
		)
	}

	var peekValueType unsignedType
	if len(c.stack) > 0 {
		peekValueType = c.stackPeek()
	}

	// Modify the stack according the current instruction.
	// Note that some instructions will read "index" in
	// applyToStack and advance c.pc inside the function.
	index, err := c.applyToStack(op)
	if err != nil {
		return fmt.Errorf("apply stack failed for %s: %w", wasm.InstructionName(op), err)
	}
	// Now we handle each instruction, and
	// emit the corresponding interpreterir operations to the results.
operatorSwitch:
	switch op {
	case wasm.OpcodeUnreachable:
		c.emit(newOperationUnreachable())
		c.markUnreachable()
	case wasm.OpcodeNop:
		// Nop is noop!
	case wasm.OpcodeBlock:
		c.br.Reset(c.body[c.pc+1:])
		bt, num, err := wasm.DecodeBlockType(c.types, c.br, c.enabledFeatures)
		if err != nil {
			return fmt.Errorf("reading block type for block instruction: %w", err)
		}
		c.pc += num

		if c.unreachableState.on {
			// If it is currently in unreachable,
			// just remove the entire block.
			c.unreachableState.depth++
			break operatorSwitch
		}

		// Create a new frame -- entering this block.
		frame := controlFrame{
			frameID:                            c.nextFrameID(),
			originalStackLenWithoutParam:       len(c.stack) - len(bt.Params),
			originalStackLenWithoutParamUint64: c.stackLenInUint64 - bt.ParamNumInUint64,
			kind:                               controlFrameKindBlockWithoutContinuationLabel,
			blockType:                          bt,
		}
		c.controlFrames.push(frame)

	case wasm.OpcodeLoop:
		c.br.Reset(c.body[c.pc+1:])
		bt, num, err := wasm.DecodeBlockType(c.types, c.br, c.enabledFeatures)
		if err != nil {
			return fmt.Errorf("reading block type for loop instruction: %w", err)
		}
		c.pc += num

		if c.unreachableState.on {
			// If it is currently in unreachable,
			// just remove the entire block.
			c.unreachableState.depth++
			break operatorSwitch
		}

		// Create a new frame -- entering loop.
		frame := controlFrame{
			frameID:                            c.nextFrameID(),
			originalStackLenWithoutParam:       len(c.stack) - len(bt.Params),
			originalStackLenWithoutParamUint64: c.stackLenInUint64 - bt.ParamNumInUint64,
			kind:                               controlFrameKindLoop,
			blockType:                          bt,
		}
		c.controlFrames.push(frame)

		// Prep labels for inside and the continuation of this loop.
		loopLabel := newLabel(labelKindHeader, frame.frameID)
		c.result.LabelCallers[loopLabel]++

		// Emit the branch operation to enter inside the loop.
		c.emit(newOperationBr(loopLabel))
		c.emit(newOperationLabel(loopLabel))

		// Insert the exit code check on the loop header, which is the only necessary point in the function body
		// to prevent infinite loop.
		//
		// Note that this is a little aggressive: this checks the exit code regardless the loop header is actually
		// the loop. In other words, this checks even when no br/br_if/br_table instructions jumping to this loop
		// exist. However, in reality, that shouldn't be an issue since such "noop" loop header will highly likely be
		// optimized out by almost all guest language compilers which have the control flow optimization passes.
		if c.ensureTermination {
			c.emit(newOperationBuiltinFunctionCheckExitCode())
		}
	case wasm.OpcodeIf:
		c.br.Reset(c.body[c.pc+1:])
		bt, num, err := wasm.DecodeBlockType(c.types, c.br, c.enabledFeatures)
		if err != nil {
			return fmt.Errorf("reading block type for if instruction: %w", err)
		}
		c.pc += num

		if c.unreachableState.on {
			// If it is currently in unreachable,
			// just remove the entire block.
			c.unreachableState.depth++
			break operatorSwitch
		}

		// Create a new frame -- entering if.
		frame := controlFrame{
			frameID:                            c.nextFrameID(),
			originalStackLenWithoutParam:       len(c.stack) - len(bt.Params),
			originalStackLenWithoutParamUint64: c.stackLenInUint64 - bt.ParamNumInUint64,
			// Note this will be set to controlFrameKindIfWithElse
			// when else opcode found later.
			kind:      controlFrameKindIfWithoutElse,
			blockType: bt,
		}
		c.controlFrames.push(frame)

		// Prep labels for if and else of this if.
		thenLabel := newLabel(labelKindHeader, frame.frameID)
		elseLabel := newLabel(labelKindElse, frame.frameID)
		c.result.LabelCallers[thenLabel]++
		c.result.LabelCallers[elseLabel]++

		// Emit the branch operation to enter the then block.
		c.emit(newOperationBrIf(thenLabel, elseLabel, nopinclusiveRange))
		c.emit(newOperationLabel(thenLabel))
	case wasm.OpcodeElse:
		frame := c.controlFrames.top()
		if c.unreachableState.on && c.unreachableState.depth > 0 {
			// If it is currently in unreachable, and the nested if,
			// just remove the entire else block.
			break operatorSwitch
		} else if c.unreachableState.on {
			// If it is currently in unreachable, and the non-nested if,
			// reset the stack so we can correctly handle the else block.
			top := c.controlFrames.top()
			c.stackSwitchAt(top)
			top.kind = controlFrameKindIfWithElse

			// Re-push the parameters to the if block so that else block can use them.
			for _, t := range frame.blockType.Params {
				c.stackPush(wasmValueTypeTounsignedType(t))
			}

			// We are no longer unreachable in else frame,
			// so emit the correct label, and reset the unreachable state.
			elseLabel := newLabel(labelKindElse, frame.frameID)
			c.resetUnreachable()
			c.emit(
				newOperationLabel(elseLabel),
			)
			break operatorSwitch
		}

		// Change the Kind of this If block, indicating that
		// the if has else block.
		frame.kind = controlFrameKindIfWithElse

		// We need to reset the stack so that
		// the values pushed inside the then block
		// do not affect the else block.
		dropOp := newOperationDrop(c.getFrameDropRange(frame, false))

		// Reset the stack manipulated by the then block, and re-push the block param types to the stack.

		c.stackSwitchAt(frame)
		for _, t := range frame.blockType.Params {
			c.stackPush(wasmValueTypeTounsignedType(t))
		}

		// Prep labels for else and the continuation of this if block.
		elseLabel := newLabel(labelKindElse, frame.frameID)
		continuationLabel := newLabel(labelKindContinuation, frame.frameID)
		c.result.LabelCallers[continuationLabel]++

		// Emit the instructions for exiting the if loop,
		// and then the initiation of else block.
		c.emit(dropOp)
		// Jump to the continuation of this block.
		c.emit(newOperationBr(continuationLabel))
		// Initiate the else block.
		c.emit(newOperationLabel(elseLabel))
	case wasm.OpcodeEnd:
		if c.unreachableState.on && c.unreachableState.depth > 0 {
			c.unreachableState.depth--
			break operatorSwitch
		} else if c.unreachableState.on {
			c.resetUnreachable()

			frame := c.controlFrames.pop()
			if c.controlFrames.empty() {
				return nil
			}

			c.stackSwitchAt(frame)
			for _, t := range frame.blockType.Results {
				c.stackPush(wasmValueTypeTounsignedType(t))
			}

			continuationLabel := newLabel(labelKindContinuation, frame.frameID)
			if frame.kind == controlFrameKindIfWithoutElse {
				// Emit the else label.
				elseLabel := newLabel(labelKindElse, frame.frameID)
				c.result.LabelCallers[continuationLabel]++
				c.emit(newOperationLabel(elseLabel))
				c.emit(newOperationBr(continuationLabel))
				c.emit(newOperationLabel(continuationLabel))
			} else {
				c.emit(
					newOperationLabel(continuationLabel),
				)
			}

			break operatorSwitch
		}

		frame := c.controlFrames.pop()

		// We need to reset the stack so that
		// the values pushed inside the block.
		dropOp := newOperationDrop(c.getFrameDropRange(frame, true))
		c.stackSwitchAt(frame)

		// Push the result types onto the stack.
		for _, t := range frame.blockType.Results {
			c.stackPush(wasmValueTypeTounsignedType(t))
		}

		// Emit the instructions according to the Kind of the current control frame.
		switch frame.kind {
		case controlFrameKindFunction:
			if !c.controlFrames.empty() {
				// Should never happen. If so, there's a bug in the translation.
				panic("bug: found more function control frames")
			}
			// Return from function.
			c.emit(dropOp)
			c.emit(newOperationBr(newLabel(labelKindReturn, 0)))
		case controlFrameKindIfWithoutElse:
			// This case we have to emit "empty" else label.
			elseLabel := newLabel(labelKindElse, frame.frameID)
			continuationLabel := newLabel(labelKindContinuation, frame.frameID)
			c.result.LabelCallers[continuationLabel] += 2
			c.emit(dropOp)
			c.emit(newOperationBr(continuationLabel))
			// Emit the else which soon branches into the continuation.
			c.emit(newOperationLabel(elseLabel))
			c.emit(newOperationBr(continuationLabel))
			// Initiate the continuation.
			c.emit(newOperationLabel(continuationLabel))
		case controlFrameKindBlockWithContinuationLabel,
			controlFrameKindIfWithElse,
			controlFrameKindTryTable:
			continuationLabel := newLabel(labelKindContinuation, frame.frameID)
			c.result.LabelCallers[continuationLabel]++
			c.emit(dropOp)
			c.emit(newOperationBr(continuationLabel))
			c.emit(newOperationLabel(continuationLabel))
		case controlFrameKindLoop, controlFrameKindBlockWithoutContinuationLabel:
			c.emit(
				dropOp,
			)
		default:
			// Should never happen. If so, there's a bug in the translation.
			panic(fmt.Errorf("bug: invalid control frame Kind: 0x%x", frame.kind))
		}

	case wasm.OpcodeBr:
		targetIndex, n, err := leb128.LoadUint32(c.body[c.pc+1:])
		if err != nil {
			return fmt.Errorf("read the target for br_if: %w", err)
		}
		c.pc += n

		if c.unreachableState.on {
			// If it is currently in unreachable, br is no-op.
			break operatorSwitch
		}

		targetFrame := c.controlFrames.get(int(targetIndex))
		targetFrame.ensureContinuation()
		dropOp := newOperationDrop(c.getFrameDropRange(targetFrame, false))
		targetID := targetFrame.asLabel()
		c.result.LabelCallers[targetID]++
		c.emit(dropOp)
		c.emit(newOperationBr(targetID))
		// Br operation is stack-polymorphic, and mark the state as unreachable.
		// That means subsequent instructions in the current control frame are "unreachable"
		// and can be safely removed.
		c.markUnreachable()
	case wasm.OpcodeBrIf:
		targetIndex, n, err := leb128.LoadUint32(c.body[c.pc+1:])
		if err != nil {
			return fmt.Errorf("read the target for br_if: %w", err)
		}
		c.pc += n

		if c.unreachableState.on {
			// If it is currently in unreachable, br-if is no-op.
			break operatorSwitch
		}

		targetFrame := c.controlFrames.get(int(targetIndex))
		targetFrame.ensureContinuation()
		drop := c.getFrameDropRange(targetFrame, false)
		target := targetFrame.asLabel()
		c.result.LabelCallers[target]++

		continuationLabel := newLabel(labelKindHeader, c.nextFrameID())
		c.result.LabelCallers[continuationLabel]++
		c.emit(newOperationBrIf(target, continuationLabel, drop))
		// Start emitting else block operations.
		c.emit(newOperationLabel(continuationLabel))
	case wasm.OpcodeBrTable:
		c.br.Reset(c.body[c.pc+1:])
		r := c.br
		numTargets, n, err := leb128.DecodeUint32(r)
		if err != nil {
			return fmt.Errorf("error reading number of targets in br_table: %w", err)
		}
		c.pc += n

		if c.unreachableState.on {
			// If it is currently in unreachable, br_table is no-op.
			// But before proceeding to the next instruction, we must advance the pc
			// according to the number of br_table targets.
			for i := uint32(0); i <= numTargets; i++ { // inclusive as we also need to read the index of default target.
				_, n, err := leb128.DecodeUint32(r)
				if err != nil {
					return fmt.Errorf("error reading target %d in br_table: %w", i, err)
				}
				c.pc += n
			}
			break operatorSwitch
		}

		// Read the branch targets.
		s := numTargets * 2
		targetLabels := make([]uint64, 2+s) // (label, inclusiveRange) * (default+numTargets)
		for i := uint32(0); i < s; i += 2 {
			l, n, err := leb128.DecodeUint32(r)
			if err != nil {
				return fmt.Errorf("error reading target %d in br_table: %w", i, err)
			}
			c.pc += n
			targetFrame := c.controlFrames.get(int(l))
			targetFrame.ensureContinuation()
			drop := c.getFrameDropRange(targetFrame, false)
			targetLabel := targetFrame.asLabel()
			targetLabels[i] = uint64(targetLabel)
			targetLabels[i+1] = drop.AsU64()
			c.result.LabelCallers[targetLabel]++
		}

		// Prep default target control frame.
		l, n, err := leb128.DecodeUint32(r)
		if err != nil {
			return fmt.Errorf("error reading default target of br_table: %w", err)
		}
		c.pc += n
		defaultTargetFrame := c.controlFrames.get(int(l))
		defaultTargetFrame.ensureContinuation()
		defaultTargetDrop := c.getFrameDropRange(defaultTargetFrame, false)
		defaultLabel := defaultTargetFrame.asLabel()
		c.result.LabelCallers[defaultLabel]++
		targetLabels[s] = uint64(defaultLabel)
		targetLabels[s+1] = defaultTargetDrop.AsU64()
		c.emit(newOperationBrTable(targetLabels))

		// br_table operation is stack-polymorphic, and mark the state as unreachable.
		// That means subsequent instructions in the current control frame are "unreachable"
		// and can be safely removed.
		c.markUnreachable()
	case wasm.OpcodeReturn:
		functionFrame := c.controlFrames.functionFrame()
		dropOp := newOperationDrop(c.getFrameDropRange(functionFrame, false))

		// Cleanup the stack and then jmp to function frame's continuation (meaning return).
		c.emit(dropOp)
		c.emit(newOperationBr(functionFrame.asLabel()))

		// Return operation is stack-polymorphic, and mark the state as unreachable.
		// That means subsequent instructions in the current control frame are "unreachable"
		// and can be safely removed.
		c.markUnreachable()
	case wasm.OpcodeThrow:
		if c.unreachableState.on {
			break operatorSwitch
		}
		// Pop the tag's param values from the stack.
		if index < uint32(len(c.tags)) {
			tagType := &c.types[c.tags[index]]
			for i := len(tagType.Params) - 1; i >= 0; i-- {
				c.stackPop()
			}
		}
		c.emit(newOperationThrow(index))
		c.markUnreachable()
	case wasm.OpcodeThrowRef:
		if c.unreachableState.on {
			break operatorSwitch
		}
		// Pop the exnref from the stack.
		c.stackPop()
		c.emit(newOperationThrowRef())
		c.markUnreachable()
	case wasm.OpcodeTryTable:
		c.br.Reset(c.body[c.pc+1:])
		bt, num, err := wasm.DecodeBlockType(c.types, c.br, c.enabledFeatures)
		if err != nil {
			return fmt.Errorf("reading block type for try_table instruction: %w", err)
		}
		c.pc += num

		if c.unreachableState.on {
			c.unreachableState.depth++
			// Still need to skip the catch clause bytes.
			c.pc++
			catchCount, catchNum, err := leb128.LoadUint32(c.body[c.pc:])
			if err != nil {
				return fmt.Errorf("reading catch count for try_table: %w", err)
			}
			c.pc += catchNum - 1
			for i := uint32(0); i < catchCount; i++ {
				if _, _, _, err := c.parseCatchClause(); err != nil {
					return err
				}
			}
			break operatorSwitch
		}

		// Read catch clause count.
		c.pc++
		catchCount, catchNum, err := leb128.LoadUint32(c.body[c.pc:])
		if err != nil {
			return fmt.Errorf("reading catch count for try_table: %w", err)
		}
		c.pc += catchNum - 1

		// Parse catch clauses.
		var pendingClauses []pendingCatchClause
		for i := uint32(0); i < catchCount; i++ {
			kind, tagIdx, labelIdx, err := c.parseCatchClause()
			if err != nil {
				return err
			}
			// Resolve the label from the control frame stack.
			targetFrame := c.controlFrames.get(int(labelIdx))
			targetFrame.ensureContinuation()
			targetLabel := targetFrame.asLabel()
			c.result.LabelCallers[targetLabel]++
			pendingClauses = append(pendingClauses, pendingCatchClause{
				kind:             kind,
				tagIndex:         tagIdx,
				targetLabel:      targetLabel,
				targetStackDepth: targetFrame.originalStackLenWithoutParamUint64,
			})
		}

		// Create a control frame for the try_table block.
		frameID := c.nextFrameID()
		c.result.PendingExceptionTable = append(c.result.PendingExceptionTable, pendingExceptionTableEntry{
			startOpIndex:        len(c.result.Operations),
			continuationFrameID: frameID,
			clauses:             pendingClauses,
		})
		frame := controlFrame{
			frameID:                            frameID,
			originalStackLenWithoutParam:       len(c.stack) - len(bt.Params),
			originalStackLenWithoutParamUint64: c.stackLenInUint64 - bt.ParamNumInUint64,
			kind:                               controlFrameKindTryTable,
			blockType:                          bt,
		}
		c.controlFrames.push(frame)
	case wasm.OpcodeCall:
		c.emit(
			newOperationCall(index),
		)
	case wasm.OpcodeCallIndirect:
		typeIndex := index
		tableIndex, n, err := leb128.LoadUint32(c.body[c.pc+1:])
		if err != nil {
			return fmt.Errorf("read target for br_table: %w", err)
		}
		c.pc += n
		c.emit(
			newOperationCallIndirect(typeIndex, tableIndex),
		)

	case wasm.OpcodeDrop:
		r := inclusiveRange{Start: 0, End: 0}
		if peekValueType == unsignedTypeV128 {
			// inclusiveRange is the range in uint64 representation, so dropping a vector value on top
			// should be translated as drop [0..1] inclusively.
			r.End++
		}
		c.emit(newOperationDrop(r))
	case wasm.OpcodeSelect:
		// If it is on the unreachable state, ignore the instruction.
		if c.unreachableState.on {
			break operatorSwitch
		}
		isTargetVector := c.stackPeek() == unsignedTypeV128
		c.emit(
			newOperationSelect(isTargetVector),
		)
	case wasm.OpcodeTypedSelect:
		// Skips two bytes: vector size fixed to 1, and the value type for select.
		c.pc += 2
		// If it is on the unreachable state, ignore the instruction.
		if c.unreachableState.on {
			break operatorSwitch
		}
		// Typed select is semantically equivalent to select at runtime.
		isTargetVector := c.stackPeek() == unsignedTypeV128
		c.emit(
			newOperationSelect(isTargetVector),
		)
	case wasm.OpcodeLocalGet:
		depth := c.localDepth(index)
		if isVector := c.localType(index) == wasm.ValueTypeV128; !isVector {
			c.emit(
				// -1 because we already manipulated the stack before
				// called localDepth ^^.
				newOperationPick(depth-1, isVector),
			)
		} else {
			c.emit(
				// -2 because we already manipulated the stack before
				// called localDepth ^^.
				newOperationPick(depth-2, isVector),
			)
		}
	case wasm.OpcodeLocalSet:
		depth := c.localDepth(index)

		isVector := c.localType(index) == wasm.ValueTypeV128
		if isVector {
			c.emit(
				// +2 because we already popped the operands for this operation from the c.stack before
				// called localDepth ^^,
				newOperationSet(depth+2, isVector),
			)
		} else {
			c.emit(
				// +1 because we already popped the operands for this operation from the c.stack before
				// called localDepth ^^,
				newOperationSet(depth+1, isVector),
			)
		}
	case wasm.OpcodeLocalTee:
		depth := c.localDepth(index)
		isVector := c.localType(index) == wasm.ValueTypeV128
		if isVector {
			c.emit(newOperationPick(1, isVector))
			c.emit(newOperationSet(depth+2, isVector))
		} else {
			c.emit(
				newOperationPick(0, isVector))
			c.emit(newOperationSet(depth+1, isVector))
		}
	case wasm.OpcodeGlobalGet:
		c.emit(
			newOperationGlobalGet(index),
		)
	case wasm.OpcodeGlobalSet:
		c.emit(
			newOperationGlobalSet(index),
		)
	case wasm.OpcodeI32Load:
		imm, err := c.readMemoryArg(wasm.OpcodeI32LoadName)
		if err != nil {
			return err
		}
		c.emit(newOperationLoad(unsignedTypeI32, imm))
	case wasm.OpcodeI64Load:
		imm, err := c.readMemoryArg(wasm.OpcodeI64LoadName)
		if err != nil {
			return err
		}
		c.emit(newOperationLoad(unsignedTypeI64, imm))
	case wasm.OpcodeF32Load:
		imm, err := c.readMemoryArg(wasm.OpcodeF32LoadName)
		if err != nil {
			return err
		}
		c.emit(newOperationLoad(unsignedTypeF32, imm))
	case wasm.OpcodeF64Load:
		imm, err := c.readMemoryArg(wasm.OpcodeF64LoadName)
		if err != nil {
			return err
		}
		c.emit(newOperationLoad(unsignedTypeF64, imm))
	case wasm.OpcodeI32Load8S:
		imm, err := c.readMemoryArg(wasm.OpcodeI32Load8SName)
		if err != nil {
			return err
		}
		c.emit(newOperationLoad8(signedInt32, imm))
	case wasm.OpcodeI32Load8U:
		imm, err := c.readMemoryArg(wasm.OpcodeI32Load8UName)
		if err != nil {
			return err
		}
		c.emit(newOperationLoad8(signedUint32, imm))
	case wasm.OpcodeI32Load16S:
		imm, err := c.readMemoryArg(wasm.OpcodeI32Load16SName)
		if err != nil {
			return err
		}
		c.emit(newOperationLoad16(signedInt32, imm))
	case wasm.OpcodeI32Load16U:
		imm, err := c.readMemoryArg(wasm.OpcodeI32Load16UName)
		if err != nil {
			return err
		}
		c.emit(newOperationLoad16(signedUint32, imm))
	case wasm.OpcodeI64Load8S:
		imm, err := c.readMemoryArg(wasm.OpcodeI64Load8SName)
		if err != nil {
			return err
		}
		c.emit(newOperationLoad8(signedInt64, imm))
	case wasm.OpcodeI64Load8U:
		imm, err := c.readMemoryArg(wasm.OpcodeI64Load8UName)
		if err != nil {
			return err
		}
		c.emit(newOperationLoad8(signedUint64, imm))
	case wasm.OpcodeI64Load16S:
		imm, err := c.readMemoryArg(wasm.OpcodeI64Load16SName)
		if err != nil {
			return err
		}
		c.emit(newOperationLoad16(signedInt64, imm))
	case wasm.OpcodeI64Load16U:
		imm, err := c.readMemoryArg(wasm.OpcodeI64Load16UName)
		if err != nil {
			return err
		}
		c.emit(newOperationLoad16(signedUint64, imm))
	case wasm.OpcodeI64Load32S:
		imm, err := c.readMemoryArg(wasm.OpcodeI64Load32SName)
		if err != nil {
			return err
		}
		c.emit(newOperationLoad32(true, imm))
	case wasm.OpcodeI64Load32U:
		imm, err := c.readMemoryArg(wasm.OpcodeI64Load32UName)
		if err != nil {
			return err
		}
		c.emit(newOperationLoad32(false, imm))
	case wasm.OpcodeI32Store:
		imm, err := c.readMemoryArg(wasm.OpcodeI32StoreName)
		if err != nil {
			return err
		}
		c.emit(
			newOperationStore(unsignedTypeI32, imm),
		)
	case wasm.OpcodeI64Store:
		imm, err := c.readMemoryArg(wasm.OpcodeI64StoreName)
		if err != nil {
			return err
		}
		c.emit(
			newOperationStore(unsignedTypeI64, imm),
		)
	case wasm.OpcodeF32Store:
		imm, err := c.readMemoryArg(wasm.OpcodeF32StoreName)
		if err != nil {
			return err
		}
		c.emit(
			newOperationStore(unsignedTypeF32, imm),
		)
	case wasm.OpcodeF64Store:
		imm, err := c.readMemoryArg(wasm.OpcodeF64StoreName)
		if err != nil {
			return err
		}
		c.emit(
			newOperationStore(unsignedTypeF64, imm),
		)
	case wasm.OpcodeI32Store8:
		imm, err := c.readMemoryArg(wasm.OpcodeI32Store8Name)
		if err != nil {
			return err
		}
		c.emit(
			newOperationStore8(imm),
		)
	case wasm.OpcodeI32Store16:
		imm, err := c.readMemoryArg(wasm.OpcodeI32Store16Name)
		if err != nil {
			return err
		}
		c.emit(
			newOperationStore16(imm),
		)
	case wasm.OpcodeI64Store8:
		imm, err := c.readMemoryArg(wasm.OpcodeI64Store8Name)
		if err != nil {
			return err
		}
		c.emit(
			newOperationStore8(imm),
		)
	case wasm.OpcodeI64Store16:
		imm, err := c.readMemoryArg(wasm.OpcodeI64Store16Name)
		if err != nil {
			return err
		}
		c.emit(
			newOperationStore16(imm),
		)
	case wasm.OpcodeI64Store32:
		imm, err := c.readMemoryArg(wasm.OpcodeI64Store32Name)
		if err != nil {
			return err
		}
		c.emit(
			newOperationStore32(imm),
		)
	case wasm.OpcodeMemorySize:
		c.result.UsesMemory = true
		c.pc++ // Skip the reserved one byte.
		c.emit(
			newOperationMemorySize(),
		)
	case wasm.OpcodeMemoryGrow:
		c.result.UsesMemory = true
		c.pc++ // Skip the reserved one byte.
		c.emit(
			newOperationMemoryGrow(),
		)
	case wasm.OpcodeI32Const:
		val, num, err := leb128.LoadInt32(c.body[c.pc+1:])
		if err != nil {
			return fmt.Errorf("reading i32.const value: %v", err)
		}
		c.pc += num
		c.emit(
			newOperationConstI32(uint32(val)),
		)
	case wasm.OpcodeI64Const:
		val, num, err := leb128.LoadInt64(c.body[c.pc+1:])
		if err != nil {
			return fmt.Errorf("reading i64.const value: %v", err)
		}
		c.pc += num
		c.emit(
			newOperationConstI64(uint64(val)),
		)
	case wasm.OpcodeF32Const:
		v := math.Float32frombits(binary.LittleEndian.Uint32(c.body[c.pc+1:]))
		c.pc += 4
		c.emit(
			newOperationConstF32(v),
		)
	case wasm.OpcodeF64Const:
		v := math.Float64frombits(binary.LittleEndian.Uint64(c.body[c.pc+1:]))
		c.pc += 8
		c.emit(
			newOperationConstF64(v),
		)
	case wasm.OpcodeI32Eqz:
		c.emit(
			newOperationEqz(unsignedInt32),
		)
	case wasm.OpcodeI32Eq:
		c.emit(
			newOperationEq(unsignedTypeI32),
		)
	case wasm.OpcodeI32Ne:
		c.emit(
			newOperationNe(unsignedTypeI32),
		)
	case wasm.OpcodeI32LtS:
		c.emit(
			newOperationLt(signedTypeInt32),
		)
	case wasm.OpcodeI32LtU:
		c.emit(
			newOperationLt(signedTypeUint32),
		)
	case wasm.OpcodeI32GtS:
		c.emit(
			newOperationGt(signedTypeInt32),
		)
	case wasm.OpcodeI32GtU:
		c.emit(
			newOperationGt(signedTypeUint32),
		)
	case wasm.OpcodeI32LeS:
		c.emit(
			newOperationLe(signedTypeInt32),
		)
	case wasm.OpcodeI32LeU:
		c.emit(
			newOperationLe(signedTypeUint32),
		)
	case wasm.OpcodeI32GeS:
		c.emit(
			newOperationGe(signedTypeInt32),
		)
	case wasm.OpcodeI32GeU:
		c.emit(
			newOperationGe(signedTypeUint32),
		)
	case wasm.OpcodeI64Eqz:
		c.emit(
			newOperationEqz(unsignedInt64),
		)
	case wasm.OpcodeI64Eq:
		c.emit(
			newOperationEq(unsignedTypeI64),
		)
	case wasm.OpcodeI64Ne:
		c.emit(
			newOperationNe(unsignedTypeI64),
		)
	case wasm.OpcodeI64LtS:
		c.emit(
			newOperationLt(signedTypeInt64),
		)
	case wasm.OpcodeI64LtU:
		c.emit(
			newOperationLt(signedTypeUint64),
		)
	case wasm.OpcodeI64GtS:
		c.emit(
			newOperationGt(signedTypeInt64),
		)
	case wasm.OpcodeI64GtU:
		c.emit(
			newOperationGt(signedTypeUint64),
		)
	case wasm.OpcodeI64LeS:
		c.emit(
			newOperationLe(signedTypeInt64),
		)
	case wasm.OpcodeI64LeU:
		c.emit(
			newOperationLe(signedTypeUint64),
		)
	case wasm.OpcodeI64GeS:
		c.emit(
			newOperationGe(signedTypeInt64),
		)
	case wasm.OpcodeI64GeU:
		c.emit(
			newOperationGe(signedTypeUint64),
		)
	case wasm.OpcodeF32Eq:
		c.emit(
			newOperationEq(unsignedTypeF32),
		)
	case wasm.OpcodeF32Ne:
		c.emit(
			newOperationNe(unsignedTypeF32),
		)
	case wasm.OpcodeF32Lt:
		c.emit(
			newOperationLt(signedTypeFloat32),
		)
	case wasm.OpcodeF32Gt:
		c.emit(
			newOperationGt(signedTypeFloat32),
		)
	case wasm.OpcodeF32Le:
		c.emit(
			newOperationLe(signedTypeFloat32),
		)
	case wasm.OpcodeF32Ge:
		c.emit(
			newOperationGe(signedTypeFloat32),
		)
	case wasm.OpcodeF64Eq:
		c.emit(
			newOperationEq(unsignedTypeF64),
		)
	case wasm.OpcodeF64Ne:
		c.emit(
			newOperationNe(unsignedTypeF64),
		)
	case wasm.OpcodeF64Lt:
		c.emit(
			newOperationLt(signedTypeFloat64),
		)
	case wasm.OpcodeF64Gt:
		c.emit(
			newOperationGt(signedTypeFloat64),
		)
	case wasm.OpcodeF64Le:
		c.emit(
			newOperationLe(signedTypeFloat64),
		)
	case wasm.OpcodeF64Ge:
		c.emit(
			newOperationGe(signedTypeFloat64),
		)
	case wasm.OpcodeI32Clz:
		c.emit(
			newOperationClz(unsignedInt32),
		)
	case wasm.OpcodeI32Ctz:
		c.emit(
			newOperationCtz(unsignedInt32),
		)
	case wasm.OpcodeI32Popcnt:
		c.emit(
			newOperationPopcnt(unsignedInt32),
		)
	case wasm.OpcodeI32Add:
		c.emit(
			newOperationAdd(unsignedTypeI32),
		)
	case wasm.OpcodeI32Sub:
		c.emit(
			newOperationSub(unsignedTypeI32),
		)
	case wasm.OpcodeI32Mul:
		c.emit(
			newOperationMul(unsignedTypeI32),
		)
	case wasm.OpcodeI32DivS:
		c.emit(
			newOperationDiv(signedTypeInt32),
		)
	case wasm.OpcodeI32DivU:
		c.emit(
			newOperationDiv(signedTypeUint32),
		)
	case wasm.OpcodeI32RemS:
		c.emit(
			newOperationRem(signedInt32),
		)
	case wasm.OpcodeI32RemU:
		c.emit(
			newOperationRem(signedUint32),
		)
	case wasm.OpcodeI32And:
		c.emit(
			newOperationAnd(unsignedInt32),
		)
	case wasm.OpcodeI32Or:
		c.emit(
			newOperationOr(unsignedInt32),
		)
	case wasm.OpcodeI32Xor:
		c.emit(
			newOperationXor(unsignedInt64),
		)
	case wasm.OpcodeI32Shl:
		c.emit(
			newOperationShl(unsignedInt32),
		)
	case wasm.OpcodeI32ShrS:
		c.emit(
			newOperationShr(signedInt32),
		)
	case wasm.OpcodeI32ShrU:
		c.emit(
			newOperationShr(signedUint32),
		)
	case wasm.OpcodeI32Rotl:
		c.emit(
			newOperationRotl(unsignedInt32),
		)
	case wasm.OpcodeI32Rotr:
		c.emit(
			newOperationRotr(unsignedInt32),
		)
	case wasm.OpcodeI64Clz:
		c.emit(
			newOperationClz(unsignedInt64),
		)
	case wasm.OpcodeI64Ctz:
		c.emit(
			newOperationCtz(unsignedInt64),
		)
	case wasm.OpcodeI64Popcnt:
		c.emit(
			newOperationPopcnt(unsignedInt64),
		)
	case wasm.OpcodeI64Add:
		c.emit(
			newOperationAdd(unsignedTypeI64),
		)
	case wasm.OpcodeI64Sub:
		c.emit(
			newOperationSub(unsignedTypeI64),
		)
	case wasm.OpcodeI64Mul:
		c.emit(
			newOperationMul(unsignedTypeI64),
		)
	case wasm.OpcodeI64DivS:
		c.emit(
			newOperationDiv(signedTypeInt64),
		)
	case wasm.OpcodeI64DivU:
		c.emit(
			newOperationDiv(signedTypeUint64),
		)
	case wasm.OpcodeI64RemS:
		c.emit(
			newOperationRem(signedInt64),
		)
	case wasm.OpcodeI64RemU:
		c.emit(
			newOperationRem(signedUint64),
		)
	case wasm.OpcodeI64And:
		c.emit(
			newOperationAnd(unsignedInt64),
		)
	case wasm.OpcodeI64Or:
		c.emit(
			newOperationOr(unsignedInt64),
		)
	case wasm.OpcodeI64Xor:
		c.emit(
			newOperationXor(unsignedInt64),
		)
	case wasm.OpcodeI64Shl:
		c.emit(
			newOperationShl(unsignedInt64),
		)
	case wasm.OpcodeI64ShrS:
		c.emit(
			newOperationShr(signedInt64),
		)
	case wasm.OpcodeI64ShrU:
		c.emit(
			newOperationShr(signedUint64),
		)
	case wasm.OpcodeI64Rotl:
		c.emit(
			newOperationRotl(unsignedInt64),
		)
	case wasm.OpcodeI64Rotr:
		c.emit(
			newOperationRotr(unsignedInt64),
		)
	case wasm.OpcodeF32Abs:
		c.emit(
			newOperationAbs(f32),
		)
	case wasm.OpcodeF32Neg:
		c.emit(
			newOperationNeg(f32),
		)
	case wasm.OpcodeF32Ceil:
		c.emit(
			newOperationCeil(f32),
		)
	case wasm.OpcodeF32Floor:
		c.emit(
			newOperationFloor(f32),
		)
	case wasm.OpcodeF32Trunc:
		c.emit(
			newOperationTrunc(f32),
		)
	case wasm.OpcodeF32Nearest:
		c.emit(
			newOperationNearest(f32),
		)
	case wasm.OpcodeF32Sqrt:
		c.emit(
			newOperationSqrt(f32),
		)
	case wasm.OpcodeF32Add:
		c.emit(
			newOperationAdd(unsignedTypeF32),
		)
	case wasm.OpcodeF32Sub:
		c.emit(
			newOperationSub(unsignedTypeF32),
		)
	case wasm.OpcodeF32Mul:
		c.emit(
			newOperationMul(unsignedTypeF32),
		)
	case wasm.OpcodeF32Div:
		c.emit(
			newOperationDiv(signedTypeFloat32),
		)
	case wasm.OpcodeF32Min:
		c.emit(
			newOperationMin(f32),
		)
	case wasm.OpcodeF32Max:
		c.emit(
			newOperationMax(f32),
		)
	case wasm.OpcodeF32Copysign:
		c.emit(
			newOperationCopysign(f32),
		)
	case wasm.OpcodeF64Abs:
		c.emit(
			newOperationAbs(f64),
		)
	case wasm.OpcodeF64Neg:
		c.emit(
			newOperationNeg(f64),
		)
	case wasm.OpcodeF64Ceil:
		c.emit(
			newOperationCeil(f64),
		)
	case wasm.OpcodeF64Floor:
		c.emit(
			newOperationFloor(f64),
		)
	case wasm.OpcodeF64Trunc:
		c.emit(
			newOperationTrunc(f64),
		)
	case wasm.OpcodeF64Nearest:
		c.emit(
			newOperationNearest(f64),
		)
	case wasm.OpcodeF64Sqrt:
		c.emit(
			newOperationSqrt(f64),
		)
	case wasm.OpcodeF64Add:
		c.emit(
			newOperationAdd(unsignedTypeF64),
		)
	case wasm.OpcodeF64Sub:
		c.emit(
			newOperationSub(unsignedTypeF64),
		)
	case wasm.OpcodeF64Mul:
		c.emit(
			newOperationMul(unsignedTypeF64),
		)
	case wasm.OpcodeF64Div:
		c.emit(
			newOperationDiv(signedTypeFloat64),
		)
	case wasm.OpcodeF64Min:
		c.emit(
			newOperationMin(f64),
		)
	case wasm.OpcodeF64Max:
		c.emit(
			newOperationMax(f64),
		)
	case wasm.OpcodeF64Copysign:
		c.emit(
			newOperationCopysign(f64),
		)
	case wasm.OpcodeI32WrapI64:
		c.emit(
			newOperationI32WrapFromI64(),
		)
	case wasm.OpcodeI32TruncF32S:
		c.emit(
			newOperationITruncFromF(f32, signedInt32, false),
		)
	case wasm.OpcodeI32TruncF32U:
		c.emit(
			newOperationITruncFromF(f32, signedUint32, false),
		)
	case wasm.OpcodeI32TruncF64S:
		c.emit(
			newOperationITruncFromF(f64, signedInt32, false),
		)
	case wasm.OpcodeI32TruncF64U:
		c.emit(
			newOperationITruncFromF(f64, signedUint32, false),
		)
	case wasm.OpcodeI64ExtendI32S:
		c.emit(
			newOperationExtend(true),
		)
	case wasm.OpcodeI64ExtendI32U:
		c.emit(
			newOperationExtend(false),
		)
	case wasm.OpcodeI64TruncF32S:
		c.emit(
			newOperationITruncFromF(f32, signedInt64, false),
		)
	case wasm.OpcodeI64TruncF32U:
		c.emit(
			newOperationITruncFromF(f32, signedUint64, false),
		)
	case wasm.OpcodeI64TruncF64S:
		c.emit(
			newOperationITruncFromF(f64, signedInt64, false),
		)
	case wasm.OpcodeI64TruncF64U:
		c.emit(
			newOperationITruncFromF(f64, signedUint64, false),
		)
	case wasm.OpcodeF32ConvertI32S:
		c.emit(
			newOperationFConvertFromI(signedInt32, f32),
		)
	case wasm.OpcodeF32ConvertI32U:
		c.emit(
			newOperationFConvertFromI(signedUint32, f32),
		)
	case wasm.OpcodeF32ConvertI64S:
		c.emit(
			newOperationFConvertFromI(signedInt64, f32),
		)
	case wasm.OpcodeF32ConvertI64U:
		c.emit(
			newOperationFConvertFromI(signedUint64, f32),
		)
	case wasm.OpcodeF32DemoteF64:
		c.emit(
			newOperationF32DemoteFromF64(),
		)
	case wasm.OpcodeF64ConvertI32S:
		c.emit(
			newOperationFConvertFromI(signedInt32, f64),
		)
	case wasm.OpcodeF64ConvertI32U:
		c.emit(
			newOperationFConvertFromI(signedUint32, f64),
		)
	case wasm.OpcodeF64ConvertI64S:
		c.emit(
			newOperationFConvertFromI(signedInt64, f64),
		)
	case wasm.OpcodeF64ConvertI64U:
		c.emit(
			newOperationFConvertFromI(signedUint64, f64),
		)
	case wasm.OpcodeF64PromoteF32:
		c.emit(
			newOperationF64PromoteFromF32(),
		)
	case wasm.OpcodeI32ReinterpretF32:
		c.emit(
			newOperationI32ReinterpretFromF32(),
		)
	case wasm.OpcodeI64ReinterpretF64:
		c.emit(
			newOperationI64ReinterpretFromF64(),
		)
	case wasm.OpcodeF32ReinterpretI32:
		c.emit(
			newOperationF32ReinterpretFromI32(),
		)
	case wasm.OpcodeF64ReinterpretI64:
		c.emit(
			newOperationF64ReinterpretFromI64(),
		)
	case wasm.OpcodeI32Extend8S:
		c.emit(
			newOperationSignExtend32From8(),
		)
	case wasm.OpcodeI32Extend16S:
		c.emit(
			newOperationSignExtend32From16(),
		)
	case wasm.OpcodeI64Extend8S:
		c.emit(
			newOperationSignExtend64From8(),
		)
	case wasm.OpcodeI64Extend16S:
		c.emit(
			newOperationSignExtend64From16(),
		)
	case wasm.OpcodeI64Extend32S:
		c.emit(
			newOperationSignExtend64From32(),
		)
	case wasm.OpcodeRefFunc:
		c.pc++
		index, num, err := leb128.LoadUint32(c.body[c.pc:])
		if err != nil {
			return fmt.Errorf("failed to read function index for ref.func: %v", err)
		}
		c.pc += num - 1
		c.emit(
			newOperationRefFunc(index),
		)
	case wasm.OpcodeRefNull:
		c.pc++
		switch reftype := c.body[c.pc]; wasm.ValueType(reftype) {
		case wasm.ValueTypeFuncref, wasm.ValueTypeExternref, wasm.ValueTypeExnref:
			// Abstract ref types are a single byte; already skipped.
		default:
			// Concrete type index encoded as LEB128; skip it.
			_, num, err := leb128.LoadUint32(c.body[c.pc:])
			if err != nil {
				return fmt.Errorf("failed to read type index for ref.null: %v", err)
			}
			c.pc += num - 1
		}
		c.emit(
			newOperationConstI64(0),
		)
	case wasm.OpcodeRefIsNull:
		// Simply compare the opaque pointer (i64) with zero.
		c.emit(
			newOperationEqz(unsignedInt64),
		)
	case wasm.OpcodeTableGet:
		c.pc++
		tableIndex, num, err := leb128.LoadUint32(c.body[c.pc:])
		if err != nil {
			return fmt.Errorf("failed to read function index for table.get: %v", err)
		}
		c.pc += num - 1
		c.emit(
			newOperationTableGet(tableIndex),
		)
	case wasm.OpcodeTableSet:
		c.pc++
		tableIndex, num, err := leb128.LoadUint32(c.body[c.pc:])
		if err != nil {
			return fmt.Errorf("failed to read function index for table.set: %v", err)
		}
		c.pc += num - 1
		c.emit(
			newOperationTableSet(tableIndex),
		)
	case wasm.OpcodeMiscPrefix:
		c.pc++
		// A misc opcode is encoded as an unsigned variable 32-bit integer.
		miscOp, num, err := leb128.LoadUint32(c.body[c.pc:])
		if err != nil {
			return fmt.Errorf("failed to read misc opcode: %v", err)
		}
		c.pc += num - 1
		switch byte(miscOp) {
		case wasm.OpcodeMiscI32TruncSatF32S:
			c.emit(
				newOperationITruncFromF(f32, signedInt32, true),
			)
		case wasm.OpcodeMiscI32TruncSatF32U:
			c.emit(
				newOperationITruncFromF(f32, signedUint32, true),
			)
		case wasm.OpcodeMiscI32TruncSatF64S:
			c.emit(
				newOperationITruncFromF(f64, signedInt32, true),
			)
		case wasm.OpcodeMiscI32TruncSatF64U:
			c.emit(
				newOperationITruncFromF(f64, signedUint32, true),
			)
		case wasm.OpcodeMiscI64TruncSatF32S:
			c.emit(
				newOperationITruncFromF(f32, signedInt64, true),
			)
		case wasm.OpcodeMiscI64TruncSatF32U:
			c.emit(
				newOperationITruncFromF(f32, signedUint64, true),
			)
		case wasm.OpcodeMiscI64TruncSatF64S:
			c.emit(
				newOperationITruncFromF(f64, signedInt64, true),
			)
		case wasm.OpcodeMiscI64TruncSatF64U:
			c.emit(
				newOperationITruncFromF(f64, signedUint64, true),
			)
		case wasm.OpcodeMiscMemoryInit:
			c.result.UsesMemory = true
			dataIndex, num, err := leb128.LoadUint32(c.body[c.pc+1:])
			if err != nil {
				return fmt.Errorf("reading i32.const value: %v", err)
			}
			c.pc += num + 1 // +1 to skip the memory index which is fixed to zero.
			c.emit(
				newOperationMemoryInit(dataIndex),
			)
		case wasm.OpcodeMiscDataDrop:
			dataIndex, num, err := leb128.LoadUint32(c.body[c.pc+1:])
			if err != nil {
				return fmt.Errorf("reading i32.const value: %v", err)
			}
			c.pc += num
			c.emit(
				newOperationDataDrop(dataIndex),
			)
		case wasm.OpcodeMiscMemoryCopy:
			c.result.UsesMemory = true
			c.pc += 2 // +2 to skip two memory indexes which are fixed to zero.
			c.emit(
				newOperationMemoryCopy(),
			)
		case wasm.OpcodeMiscMemoryFill:
			c.result.UsesMemory = true
			c.pc += 1 // +1 to skip the memory index which is fixed to zero.
			c.emit(
				newOperationMemoryFill(),
			)
		case wasm.OpcodeMiscTableInit:
			elemIndex, num, err := leb128.LoadUint32(c.body[c.pc+1:])
			if err != nil {
				return fmt.Errorf("reading i32.const value: %v", err)
			}
			c.pc += num
			// Read table index which is fixed to zero currently.
			tableIndex, num, err := leb128.LoadUint32(c.body[c.pc+1:])
			if err != nil {
				return fmt.Errorf("reading i32.const value: %v", err)
			}
			c.pc += num
			c.emit(
				newOperationTableInit(elemIndex, tableIndex),
			)
		case wasm.OpcodeMiscElemDrop:
			elemIndex, num, err := leb128.LoadUint32(c.body[c.pc+1:])
			if err != nil {
				return fmt.Errorf("reading i32.const value: %v", err)
			}
			c.pc += num
			c.emit(
				newOperationElemDrop(elemIndex),
			)
		case wasm.OpcodeMiscTableCopy:
			// Read the source table inde.g.
			dst, num, err := leb128.LoadUint32(c.body[c.pc+1:])
			if err != nil {
				return fmt.Errorf("reading i32.const value: %v", err)
			}
			c.pc += num
			// Read the destination table inde.g.
			src, num, err := leb128.LoadUint32(c.body[c.pc+1:])
			if err != nil {
				return fmt.Errorf("reading i32.const value: %v", err)
			}
			c.pc += num
			c.emit(
				newOperationTableCopy(src, dst),
			)
		case wasm.OpcodeMiscTableGrow:
			// Read the source table inde.g.
			tableIndex, num, err := leb128.LoadUint32(c.body[c.pc+1:])
			if err != nil {
				return fmt.Errorf("reading i32.const value: %v", err)
			}
			c.pc += num
			c.emit(
				newOperationTableGrow(tableIndex),
			)
		case wasm.OpcodeMiscTableSize:
			// Read the source table inde.g.
			tableIndex, num, err := leb128.LoadUint32(c.body[c.pc+1:])
			if err != nil {
				return fmt.Errorf("reading i32.const value: %v", err)
			}
			c.pc += num
			c.emit(
				newOperationTableSize(tableIndex),
			)
		case wasm.OpcodeMiscTableFill:
			// Read the source table index.
			tableIndex, num, err := leb128.LoadUint32(c.body[c.pc+1:])
			if err != nil {
				return fmt.Errorf("reading i32.const value: %v", err)
			}
			c.pc += num
			c.emit(
				newOperationTableFill(tableIndex),
			)
		default:
			return fmt.Errorf("unsupported misc instruction in interpreterir: 0x%x", op)
		}
	case wasm.OpcodeVecPrefix:
		c.pc++
		switch vecOp := c.body[c.pc]; vecOp {
		case wasm.OpcodeVecV128Const:
			c.pc++
			lo := binary.LittleEndian.Uint64(c.body[c.pc : c.pc+8])
			c.pc += 8
			hi := binary.LittleEndian.Uint64(c.body[c.pc : c.pc+8])
			c.emit(
				newOperationV128Const(lo, hi),
			)
			c.pc += 7
		case wasm.OpcodeVecV128Load:
			arg, err := c.readMemoryArg(wasm.OpcodeI32LoadName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationV128Load(v128LoadType128, arg),
			)
		case wasm.OpcodeVecV128Load8x8s:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load8x8SName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationV128Load(v128LoadType8x8s, arg),
			)
		case wasm.OpcodeVecV128Load8x8u:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load8x8UName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationV128Load(v128LoadType8x8u, arg),
			)
		case wasm.OpcodeVecV128Load16x4s:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load16x4SName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationV128Load(v128LoadType16x4s, arg),
			)
		case wasm.OpcodeVecV128Load16x4u:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load16x4UName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationV128Load(v128LoadType16x4u, arg),
			)
		case wasm.OpcodeVecV128Load32x2s:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load32x2SName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationV128Load(v128LoadType32x2s, arg),
			)
		case wasm.OpcodeVecV128Load32x2u:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load32x2UName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationV128Load(v128LoadType32x2u, arg),
			)
		case wasm.OpcodeVecV128Load8Splat:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load8SplatName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationV128Load(v128LoadType8Splat, arg),
			)
		case wasm.OpcodeVecV128Load16Splat:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load16SplatName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationV128Load(v128LoadType16Splat, arg),
			)
		case wasm.OpcodeVecV128Load32Splat:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load32SplatName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationV128Load(v128LoadType32Splat, arg),
			)
		case wasm.OpcodeVecV128Load64Splat:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load64SplatName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationV128Load(v128LoadType64Splat, arg),
			)
		case wasm.OpcodeVecV128Load32zero:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load32zeroName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationV128Load(v128LoadType32zero, arg),
			)
		case wasm.OpcodeVecV128Load64zero:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load64zeroName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationV128Load(v128LoadType64zero, arg),
			)
		case wasm.OpcodeVecV128Load8Lane:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load8LaneName)
			if err != nil {
				return err
			}
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128LoadLane(laneIndex, 8, arg),
			)
		case wasm.OpcodeVecV128Load16Lane:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load16LaneName)
			if err != nil {
				return err
			}
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128LoadLane(laneIndex, 16, arg),
			)
		case wasm.OpcodeVecV128Load32Lane:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load32LaneName)
			if err != nil {
				return err
			}
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128LoadLane(laneIndex, 32, arg),
			)
		case wasm.OpcodeVecV128Load64Lane:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load64LaneName)
			if err != nil {
				return err
			}
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128LoadLane(laneIndex, 64, arg),
			)
		case wasm.OpcodeVecV128Store:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128StoreName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationV128Store(arg),
			)
		case wasm.OpcodeVecV128Store8Lane:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Store8LaneName)
			if err != nil {
				return err
			}
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128StoreLane(laneIndex, 8, arg),
			)
		case wasm.OpcodeVecV128Store16Lane:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Store16LaneName)
			if err != nil {
				return err
			}
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128StoreLane(laneIndex, 16, arg),
			)
		case wasm.OpcodeVecV128Store32Lane:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Store32LaneName)
			if err != nil {
				return err
			}
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128StoreLane(laneIndex, 32, arg),
			)
		case wasm.OpcodeVecV128Store64Lane:
			arg, err := c.readMemoryArg(wasm.OpcodeVecV128Store64LaneName)
			if err != nil {
				return err
			}
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128StoreLane(laneIndex, 64, arg),
			)
		case wasm.OpcodeVecI8x16ExtractLaneS:
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128ExtractLane(laneIndex, true, shapeI8x16),
			)
		case wasm.OpcodeVecI8x16ExtractLaneU:
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128ExtractLane(laneIndex, false, shapeI8x16),
			)
		case wasm.OpcodeVecI16x8ExtractLaneS:
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128ExtractLane(laneIndex, true, shapeI16x8),
			)
		case wasm.OpcodeVecI16x8ExtractLaneU:
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128ExtractLane(laneIndex, false, shapeI16x8),
			)
		case wasm.OpcodeVecI32x4ExtractLane:
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128ExtractLane(laneIndex, false, shapeI32x4),
			)
		case wasm.OpcodeVecI64x2ExtractLane:
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128ExtractLane(laneIndex, false, shapeI64x2),
			)
		case wasm.OpcodeVecF32x4ExtractLane:
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128ExtractLane(laneIndex, false, shapeF32x4),
			)
		case wasm.OpcodeVecF64x2ExtractLane:
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128ExtractLane(laneIndex, false, shapeF64x2),
			)
		case wasm.OpcodeVecI8x16ReplaceLane:
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128ReplaceLane(laneIndex, shapeI8x16),
			)
		case wasm.OpcodeVecI16x8ReplaceLane:
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128ReplaceLane(laneIndex, shapeI16x8),
			)
		case wasm.OpcodeVecI32x4ReplaceLane:
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128ReplaceLane(laneIndex, shapeI32x4),
			)
		case wasm.OpcodeVecI64x2ReplaceLane:
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128ReplaceLane(laneIndex, shapeI64x2),
			)
		case wasm.OpcodeVecF32x4ReplaceLane:
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128ReplaceLane(laneIndex, shapeF32x4),
			)
		case wasm.OpcodeVecF64x2ReplaceLane:
			c.pc++
			laneIndex := c.body[c.pc]
			c.emit(
				newOperationV128ReplaceLane(laneIndex, shapeF64x2),
			)
		case wasm.OpcodeVecI8x16Splat:
			c.emit(
				newOperationV128Splat(shapeI8x16),
			)
		case wasm.OpcodeVecI16x8Splat:
			c.emit(
				newOperationV128Splat(shapeI16x8),
			)
		case wasm.OpcodeVecI32x4Splat:
			c.emit(
				newOperationV128Splat(shapeI32x4),
			)
		case wasm.OpcodeVecI64x2Splat:
			c.emit(
				newOperationV128Splat(shapeI64x2),
			)
		case wasm.OpcodeVecF32x4Splat:
			c.emit(
				newOperationV128Splat(shapeF32x4),
			)
		case wasm.OpcodeVecF64x2Splat:
			c.emit(
				newOperationV128Splat(shapeF64x2),
			)
		case wasm.OpcodeVecI8x16Swizzle:
			c.emit(
				newOperationV128Swizzle(),
			)
		case wasm.OpcodeVecV128i8x16Shuffle:
			c.pc++
			lanes := make([]uint64, 16)
			for i := uint64(0); i < 16; i++ {
				lanes[i] = uint64(c.body[c.pc+i])
			}
			op := newOperationV128Shuffle(lanes)
			c.emit(op)
			c.pc += 15
		case wasm.OpcodeVecV128AnyTrue:
			c.emit(
				newOperationV128AnyTrue(),
			)
		case wasm.OpcodeVecI8x16AllTrue:
			c.emit(
				newOperationV128AllTrue(shapeI8x16),
			)
		case wasm.OpcodeVecI16x8AllTrue:
			c.emit(
				newOperationV128AllTrue(shapeI16x8),
			)
		case wasm.OpcodeVecI32x4AllTrue:
			c.emit(
				newOperationV128AllTrue(shapeI32x4),
			)
		case wasm.OpcodeVecI64x2AllTrue:
			c.emit(
				newOperationV128AllTrue(shapeI64x2),
			)
		case wasm.OpcodeVecI8x16BitMask:
			c.emit(
				newOperationV128BitMask(shapeI8x16),
			)
		case wasm.OpcodeVecI16x8BitMask:
			c.emit(
				newOperationV128BitMask(shapeI16x8),
			)
		case wasm.OpcodeVecI32x4BitMask:
			c.emit(
				newOperationV128BitMask(shapeI32x4),
			)
		case wasm.OpcodeVecI64x2BitMask:
			c.emit(
				newOperationV128BitMask(shapeI64x2),
			)
		case wasm.OpcodeVecV128And:
			c.emit(
				newOperationV128And(),
			)
		case wasm.OpcodeVecV128Not:
			c.emit(
				newOperationV128Not(),
			)
		case wasm.OpcodeVecV128Or:
			c.emit(
				newOperationV128Or(),
			)
		case wasm.OpcodeVecV128Xor:
			c.emit(
				newOperationV128Xor(),
			)
		case wasm.OpcodeVecV128Bitselect:
			c.emit(
				newOperationV128Bitselect(),
			)
		case wasm.OpcodeVecV128AndNot:
			c.emit(
				newOperationV128AndNot(),
			)
		case wasm.OpcodeVecI8x16Shl:
			c.emit(
				newOperationV128Shl(shapeI8x16),
			)
		case wasm.OpcodeVecI8x16ShrS:
			c.emit(
				newOperationV128Shr(shapeI8x16, true),
			)
		case wasm.OpcodeVecI8x16ShrU:
			c.emit(
				newOperationV128Shr(shapeI8x16, false),
			)
		case wasm.OpcodeVecI16x8Shl:
			c.emit(
				newOperationV128Shl(shapeI16x8),
			)
		case wasm.OpcodeVecI16x8ShrS:
			c.emit(
				newOperationV128Shr(shapeI16x8, true),
			)
		case wasm.OpcodeVecI16x8ShrU:
			c.emit(
				newOperationV128Shr(shapeI16x8, false),
			)
		case wasm.OpcodeVecI32x4Shl:
			c.emit(
				newOperationV128Shl(shapeI32x4),
			)
		case wasm.OpcodeVecI32x4ShrS:
			c.emit(
				newOperationV128Shr(shapeI32x4, true),
			)
		case wasm.OpcodeVecI32x4ShrU:
			c.emit(
				newOperationV128Shr(shapeI32x4, false),
			)
		case wasm.OpcodeVecI64x2Shl:
			c.emit(
				newOperationV128Shl(shapeI64x2),
			)
		case wasm.OpcodeVecI64x2ShrS:
			c.emit(
				newOperationV128Shr(shapeI64x2, true),
			)
		case wasm.OpcodeVecI64x2ShrU:
			c.emit(
				newOperationV128Shr(shapeI64x2, false),
			)
		case wasm.OpcodeVecI8x16Eq:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI8x16Eq),
			)
		case wasm.OpcodeVecI8x16Ne:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI8x16Ne),
			)
		case wasm.OpcodeVecI8x16LtS:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI8x16LtS),
			)
		case wasm.OpcodeVecI8x16LtU:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI8x16LtU),
			)
		case wasm.OpcodeVecI8x16GtS:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI8x16GtS),
			)
		case wasm.OpcodeVecI8x16GtU:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI8x16GtU),
			)
		case wasm.OpcodeVecI8x16LeS:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI8x16LeS),
			)
		case wasm.OpcodeVecI8x16LeU:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI8x16LeU),
			)
		case wasm.OpcodeVecI8x16GeS:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI8x16GeS),
			)
		case wasm.OpcodeVecI8x16GeU:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI8x16GeU),
			)
		case wasm.OpcodeVecI16x8Eq:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI16x8Eq),
			)
		case wasm.OpcodeVecI16x8Ne:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI16x8Ne),
			)
		case wasm.OpcodeVecI16x8LtS:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI16x8LtS),
			)
		case wasm.OpcodeVecI16x8LtU:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI16x8LtU),
			)
		case wasm.OpcodeVecI16x8GtS:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI16x8GtS),
			)
		case wasm.OpcodeVecI16x8GtU:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI16x8GtU),
			)
		case wasm.OpcodeVecI16x8LeS:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI16x8LeS),
			)
		case wasm.OpcodeVecI16x8LeU:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI16x8LeU),
			)
		case wasm.OpcodeVecI16x8GeS:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI16x8GeS),
			)
		case wasm.OpcodeVecI16x8GeU:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI16x8GeU),
			)
		case wasm.OpcodeVecI32x4Eq:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI32x4Eq),
			)
		case wasm.OpcodeVecI32x4Ne:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI32x4Ne),
			)
		case wasm.OpcodeVecI32x4LtS:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI32x4LtS),
			)
		case wasm.OpcodeVecI32x4LtU:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI32x4LtU),
			)
		case wasm.OpcodeVecI32x4GtS:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI32x4GtS),
			)
		case wasm.OpcodeVecI32x4GtU:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI32x4GtU),
			)
		case wasm.OpcodeVecI32x4LeS:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI32x4LeS),
			)
		case wasm.OpcodeVecI32x4LeU:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI32x4LeU),
			)
		case wasm.OpcodeVecI32x4GeS:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI32x4GeS),
			)
		case wasm.OpcodeVecI32x4GeU:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI32x4GeU),
			)
		case wasm.OpcodeVecI64x2Eq:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI64x2Eq),
			)
		case wasm.OpcodeVecI64x2Ne:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI64x2Ne),
			)
		case wasm.OpcodeVecI64x2LtS:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI64x2LtS),
			)
		case wasm.OpcodeVecI64x2GtS:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI64x2GtS),
			)
		case wasm.OpcodeVecI64x2LeS:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI64x2LeS),
			)
		case wasm.OpcodeVecI64x2GeS:
			c.emit(
				newOperationV128Cmp(v128CmpTypeI64x2GeS),
			)
		case wasm.OpcodeVecF32x4Eq:
			c.emit(
				newOperationV128Cmp(v128CmpTypeF32x4Eq),
			)
		case wasm.OpcodeVecF32x4Ne:
			c.emit(
				newOperationV128Cmp(v128CmpTypeF32x4Ne),
			)
		case wasm.OpcodeVecF32x4Lt:
			c.emit(
				newOperationV128Cmp(v128CmpTypeF32x4Lt),
			)
		case wasm.OpcodeVecF32x4Gt:
			c.emit(
				newOperationV128Cmp(v128CmpTypeF32x4Gt),
			)
		case wasm.OpcodeVecF32x4Le:
			c.emit(
				newOperationV128Cmp(v128CmpTypeF32x4Le),
			)
		case wasm.OpcodeVecF32x4Ge:
			c.emit(
				newOperationV128Cmp(v128CmpTypeF32x4Ge),
			)
		case wasm.OpcodeVecF64x2Eq:
			c.emit(
				newOperationV128Cmp(v128CmpTypeF64x2Eq),
			)
		case wasm.OpcodeVecF64x2Ne:
			c.emit(
				newOperationV128Cmp(v128CmpTypeF64x2Ne),
			)
		case wasm.OpcodeVecF64x2Lt:
			c.emit(
				newOperationV128Cmp(v128CmpTypeF64x2Lt),
			)
		case wasm.OpcodeVecF64x2Gt:
			c.emit(
				newOperationV128Cmp(v128CmpTypeF64x2Gt),
			)
		case wasm.OpcodeVecF64x2Le:
			c.emit(
				newOperationV128Cmp(v128CmpTypeF64x2Le),
			)
		case wasm.OpcodeVecF64x2Ge:
			c.emit(
				newOperationV128Cmp(v128CmpTypeF64x2Ge),
			)
		case wasm.OpcodeVecI8x16Neg:
			c.emit(
				newOperationV128Neg(shapeI8x16),
			)
		case wasm.OpcodeVecI16x8Neg:
			c.emit(
				newOperationV128Neg(shapeI16x8),
			)
		case wasm.OpcodeVecI32x4Neg:
			c.emit(
				newOperationV128Neg(shapeI32x4),
			)
		case wasm.OpcodeVecI64x2Neg:
			c.emit(
				newOperationV128Neg(shapeI64x2),
			)
		case wasm.OpcodeVecF32x4Neg:
			c.emit(
				newOperationV128Neg(shapeF32x4),
			)
		case wasm.OpcodeVecF64x2Neg:
			c.emit(
				newOperationV128Neg(shapeF64x2),
			)
		case wasm.OpcodeVecI8x16Add:
			c.emit(
				newOperationV128Add(shapeI8x16),
			)
		case wasm.OpcodeVecI16x8Add:
			c.emit(
				newOperationV128Add(shapeI16x8),
			)
		case wasm.OpcodeVecI32x4Add:
			c.emit(
				newOperationV128Add(shapeI32x4),
			)
		case wasm.OpcodeVecI64x2Add:
			c.emit(
				newOperationV128Add(shapeI64x2),
			)
		case wasm.OpcodeVecF32x4Add:
			c.emit(
				newOperationV128Add(shapeF32x4),
			)
		case wasm.OpcodeVecF64x2Add:
			c.emit(
				newOperationV128Add(shapeF64x2),
			)
		case wasm.OpcodeVecI8x16Sub:
			c.emit(
				newOperationV128Sub(shapeI8x16),
			)
		case wasm.OpcodeVecI16x8Sub:
			c.emit(
				newOperationV128Sub(shapeI16x8),
			)
		case wasm.OpcodeVecI32x4Sub:
			c.emit(
				newOperationV128Sub(shapeI32x4),
			)
		case wasm.OpcodeVecI64x2Sub:
			c.emit(
				newOperationV128Sub(shapeI64x2),
			)
		case wasm.OpcodeVecF32x4Sub:
			c.emit(
				newOperationV128Sub(shapeF32x4),
			)
		case wasm.OpcodeVecF64x2Sub:
			c.emit(
				newOperationV128Sub(shapeF64x2),
			)
		case wasm.OpcodeVecI8x16AddSatS:
			c.emit(
				newOperationV128AddSat(shapeI8x16, true),
			)
		case wasm.OpcodeVecI8x16AddSatU:
			c.emit(
				newOperationV128AddSat(shapeI8x16, false),
			)
		case wasm.OpcodeVecI16x8AddSatS:
			c.emit(
				newOperationV128AddSat(shapeI16x8, true),
			)
		case wasm.OpcodeVecI16x8AddSatU:
			c.emit(
				newOperationV128AddSat(shapeI16x8, false),
			)
		case wasm.OpcodeVecI8x16SubSatS:
			c.emit(
				newOperationV128SubSat(shapeI8x16, true),
			)
		case wasm.OpcodeVecI8x16SubSatU:
			c.emit(
				newOperationV128SubSat(shapeI8x16, false),
			)
		case wasm.OpcodeVecI16x8SubSatS:
			c.emit(
				newOperationV128SubSat(shapeI16x8, true),
			)
		case wasm.OpcodeVecI16x8SubSatU:
			c.emit(
				newOperationV128SubSat(shapeI16x8, false),
			)
		case wasm.OpcodeVecI16x8Mul:
			c.emit(
				newOperationV128Mul(shapeI16x8),
			)
		case wasm.OpcodeVecI32x4Mul:
			c.emit(
				newOperationV128Mul(shapeI32x4),
			)
		case wasm.OpcodeVecI64x2Mul:
			c.emit(
				newOperationV128Mul(shapeI64x2),
			)
		case wasm.OpcodeVecF32x4Mul:
			c.emit(
				newOperationV128Mul(shapeF32x4),
			)
		case wasm.OpcodeVecF64x2Mul:
			c.emit(
				newOperationV128Mul(shapeF64x2),
			)
		case wasm.OpcodeVecF32x4Sqrt:
			c.emit(
				newOperationV128Sqrt(shapeF32x4),
			)
		case wasm.OpcodeVecF64x2Sqrt:
			c.emit(
				newOperationV128Sqrt(shapeF64x2),
			)
		case wasm.OpcodeVecF32x4Div:
			c.emit(
				newOperationV128Div(shapeF32x4),
			)
		case wasm.OpcodeVecF64x2Div:
			c.emit(
				newOperationV128Div(shapeF64x2),
			)
		case wasm.OpcodeVecI8x16Abs:
			c.emit(
				newOperationV128Abs(shapeI8x16),
			)
		case wasm.OpcodeVecI8x16Popcnt:
			c.emit(
				newOperationV128Popcnt(shapeI8x16),
			)
		case wasm.OpcodeVecI16x8Abs:
			c.emit(
				newOperationV128Abs(shapeI16x8),
			)
		case wasm.OpcodeVecI32x4Abs:
			c.emit(
				newOperationV128Abs(shapeI32x4),
			)
		case wasm.OpcodeVecI64x2Abs:
			c.emit(
				newOperationV128Abs(shapeI64x2),
			)
		case wasm.OpcodeVecF32x4Abs:
			c.emit(
				newOperationV128Abs(shapeF32x4),
			)
		case wasm.OpcodeVecF64x2Abs:
			c.emit(
				newOperationV128Abs(shapeF64x2),
			)
		case wasm.OpcodeVecI8x16MinS:
			c.emit(
				newOperationV128Min(shapeI8x16, true),
			)
		case wasm.OpcodeVecI8x16MinU:
			c.emit(
				newOperationV128Min(shapeI8x16, false),
			)
		case wasm.OpcodeVecI8x16MaxS:
			c.emit(
				newOperationV128Max(shapeI8x16, true),
			)
		case wasm.OpcodeVecI8x16MaxU:
			c.emit(
				newOperationV128Max(shapeI8x16, false),
			)
		case wasm.OpcodeVecI8x16AvgrU:
			c.emit(
				newOperationV128AvgrU(shapeI8x16),
			)
		case wasm.OpcodeVecI16x8MinS:
			c.emit(
				newOperationV128Min(shapeI16x8, true),
			)
		case wasm.OpcodeVecI16x8MinU:
			c.emit(
				newOperationV128Min(shapeI16x8, false),
			)
		case wasm.OpcodeVecI16x8MaxS:
			c.emit(
				newOperationV128Max(shapeI16x8, true),
			)
		case wasm.OpcodeVecI16x8MaxU:
			c.emit(
				newOperationV128Max(shapeI16x8, false),
			)
		case wasm.OpcodeVecI16x8AvgrU:
			c.emit(
				newOperationV128AvgrU(shapeI16x8),
			)
		case wasm.OpcodeVecI32x4MinS:
			c.emit(
				newOperationV128Min(shapeI32x4, true),
			)
		case wasm.OpcodeVecI32x4MinU:
			c.emit(
				newOperationV128Min(shapeI32x4, false),
			)
		case wasm.OpcodeVecI32x4MaxS:
			c.emit(
				newOperationV128Max(shapeI32x4, true),
			)
		case wasm.OpcodeVecI32x4MaxU:
			c.emit(
				newOperationV128Max(shapeI32x4, false),
			)
		case wasm.OpcodeVecF32x4Min:
			c.emit(
				newOperationV128Min(shapeF32x4, false),
			)
		case wasm.OpcodeVecF32x4Max:
			c.emit(
				newOperationV128Max(shapeF32x4, false),
			)
		case wasm.OpcodeVecF64x2Min:
			c.emit(
				newOperationV128Min(shapeF64x2, false),
			)
		case wasm.OpcodeVecF64x2Max:
			c.emit(
				newOperationV128Max(shapeF64x2, false),
			)
		case wasm.OpcodeVecF32x4Pmin:
			c.emit(
				newOperationV128Pmin(shapeF32x4),
			)
		case wasm.OpcodeVecF32x4Pmax:
			c.emit(
				newOperationV128Pmax(shapeF32x4),
			)
		case wasm.OpcodeVecF64x2Pmin:
			c.emit(
				newOperationV128Pmin(shapeF64x2),
			)
		case wasm.OpcodeVecF64x2Pmax:
			c.emit(
				newOperationV128Pmax(shapeF64x2),
			)
		case wasm.OpcodeVecF32x4Ceil:
			c.emit(
				newOperationV128Ceil(shapeF32x4),
			)
		case wasm.OpcodeVecF32x4Floor:
			c.emit(
				newOperationV128Floor(shapeF32x4),
			)
		case wasm.OpcodeVecF32x4Trunc:
			c.emit(
				newOperationV128Trunc(shapeF32x4),
			)
		case wasm.OpcodeVecF32x4Nearest:
			c.emit(
				newOperationV128Nearest(shapeF32x4),
			)
		case wasm.OpcodeVecF64x2Ceil:
			c.emit(
				newOperationV128Ceil(shapeF64x2),
			)
		case wasm.OpcodeVecF64x2Floor:
			c.emit(
				newOperationV128Floor(shapeF64x2),
			)
		case wasm.OpcodeVecF64x2Trunc:
			c.emit(
				newOperationV128Trunc(shapeF64x2),
			)
		case wasm.OpcodeVecF64x2Nearest:
			c.emit(
				newOperationV128Nearest(shapeF64x2),
			)
		case wasm.OpcodeVecI16x8ExtendLowI8x16S:
			c.emit(
				newOperationV128Extend(shapeI8x16, true, true),
			)
		case wasm.OpcodeVecI16x8ExtendHighI8x16S:
			c.emit(
				newOperationV128Extend(shapeI8x16, true, false),
			)
		case wasm.OpcodeVecI16x8ExtendLowI8x16U:
			c.emit(
				newOperationV128Extend(shapeI8x16, false, true),
			)
		case wasm.OpcodeVecI16x8ExtendHighI8x16U:
			c.emit(
				newOperationV128Extend(shapeI8x16, false, false),
			)
		case wasm.OpcodeVecI32x4ExtendLowI16x8S:
			c.emit(
				newOperationV128Extend(shapeI16x8, true, true),
			)
		case wasm.OpcodeVecI32x4ExtendHighI16x8S:
			c.emit(
				newOperationV128Extend(shapeI16x8, true, false),
			)
		case wasm.OpcodeVecI32x4ExtendLowI16x8U:
			c.emit(
				newOperationV128Extend(shapeI16x8, false, true),
			)
		case wasm.OpcodeVecI32x4ExtendHighI16x8U:
			c.emit(
				newOperationV128Extend(shapeI16x8, false, false),
			)
		case wasm.OpcodeVecI64x2ExtendLowI32x4S:
			c.emit(
				newOperationV128Extend(shapeI32x4, true, true),
			)
		case wasm.OpcodeVecI64x2ExtendHighI32x4S:
			c.emit(
				newOperationV128Extend(shapeI32x4, true, false),
			)
		case wasm.OpcodeVecI64x2ExtendLowI32x4U:
			c.emit(
				newOperationV128Extend(shapeI32x4, false, true),
			)
		case wasm.OpcodeVecI64x2ExtendHighI32x4U:
			c.emit(
				newOperationV128Extend(shapeI32x4, false, false),
			)
		case wasm.OpcodeVecI16x8Q15mulrSatS:
			c.emit(
				newOperationV128Q15mulrSatS(),
			)
		case wasm.OpcodeVecI16x8ExtMulLowI8x16S:
			c.emit(
				newOperationV128ExtMul(shapeI8x16, true, true),
			)
		case wasm.OpcodeVecI16x8ExtMulHighI8x16S:
			c.emit(
				newOperationV128ExtMul(shapeI8x16, true, false),
			)
		case wasm.OpcodeVecI16x8ExtMulLowI8x16U:
			c.emit(
				newOperationV128ExtMul(shapeI8x16, false, true),
			)
		case wasm.OpcodeVecI16x8ExtMulHighI8x16U:
			c.emit(
				newOperationV128ExtMul(shapeI8x16, false, false),
			)
		case wasm.OpcodeVecI32x4ExtMulLowI16x8S:
			c.emit(
				newOperationV128ExtMul(shapeI16x8, true, true),
			)
		case wasm.OpcodeVecI32x4ExtMulHighI16x8S:
			c.emit(
				newOperationV128ExtMul(shapeI16x8, true, false),
			)
		case wasm.OpcodeVecI32x4ExtMulLowI16x8U:
			c.emit(
				newOperationV128ExtMul(shapeI16x8, false, true),
			)
		case wasm.OpcodeVecI32x4ExtMulHighI16x8U:
			c.emit(
				newOperationV128ExtMul(shapeI16x8, false, false),
			)
		case wasm.OpcodeVecI64x2ExtMulLowI32x4S:
			c.emit(
				newOperationV128ExtMul(shapeI32x4, true, true),
			)
		case wasm.OpcodeVecI64x2ExtMulHighI32x4S:
			c.emit(
				newOperationV128ExtMul(shapeI32x4, true, false),
			)
		case wasm.OpcodeVecI64x2ExtMulLowI32x4U:
			c.emit(
				newOperationV128ExtMul(shapeI32x4, false, true),
			)
		case wasm.OpcodeVecI64x2ExtMulHighI32x4U:
			c.emit(
				newOperationV128ExtMul(shapeI32x4, false, false),
			)
		case wasm.OpcodeVecI16x8ExtaddPairwiseI8x16S:
			c.emit(
				newOperationV128ExtAddPairwise(shapeI8x16, true),
			)
		case wasm.OpcodeVecI16x8ExtaddPairwiseI8x16U:
			c.emit(
				newOperationV128ExtAddPairwise(shapeI8x16, false),
			)
		case wasm.OpcodeVecI32x4ExtaddPairwiseI16x8S:
			c.emit(
				newOperationV128ExtAddPairwise(shapeI16x8, true),
			)
		case wasm.OpcodeVecI32x4ExtaddPairwiseI16x8U:
			c.emit(
				newOperationV128ExtAddPairwise(shapeI16x8, false),
			)
		case wasm.OpcodeVecF64x2PromoteLowF32x4Zero:
			c.emit(
				newOperationV128FloatPromote(),
			)
		case wasm.OpcodeVecF32x4DemoteF64x2Zero:
			c.emit(
				newOperationV128FloatDemote(),
			)
		case wasm.OpcodeVecF32x4ConvertI32x4S:
			c.emit(
				newOperationV128FConvertFromI(shapeF32x4, true),
			)
		case wasm.OpcodeVecF32x4ConvertI32x4U:
			c.emit(
				newOperationV128FConvertFromI(shapeF32x4, false),
			)
		case wasm.OpcodeVecF64x2ConvertLowI32x4S:
			c.emit(
				newOperationV128FConvertFromI(shapeF64x2, true),
			)
		case wasm.OpcodeVecF64x2ConvertLowI32x4U:
			c.emit(
				newOperationV128FConvertFromI(shapeF64x2, false),
			)
		case wasm.OpcodeVecI32x4DotI16x8S:
			c.emit(
				newOperationV128Dot(),
			)
		case wasm.OpcodeVecI8x16NarrowI16x8S:
			c.emit(
				newOperationV128Narrow(shapeI16x8, true),
			)
		case wasm.OpcodeVecI8x16NarrowI16x8U:
			c.emit(
				newOperationV128Narrow(shapeI16x8, false),
			)
		case wasm.OpcodeVecI16x8NarrowI32x4S:
			c.emit(
				newOperationV128Narrow(shapeI32x4, true),
			)
		case wasm.OpcodeVecI16x8NarrowI32x4U:
			c.emit(
				newOperationV128Narrow(shapeI32x4, false),
			)
		case wasm.OpcodeVecI32x4TruncSatF32x4S:
			c.emit(
				newOperationV128ITruncSatFromF(shapeF32x4, true),
			)
		case wasm.OpcodeVecI32x4TruncSatF32x4U:
			c.emit(
				newOperationV128ITruncSatFromF(shapeF32x4, false),
			)
		case wasm.OpcodeVecI32x4TruncSatF64x2SZero:
			c.emit(
				newOperationV128ITruncSatFromF(shapeF64x2, true),
			)
		case wasm.OpcodeVecI32x4TruncSatF64x2UZero:
			c.emit(
				newOperationV128ITruncSatFromF(shapeF64x2, false),
			)
		default:
			return fmt.Errorf("unsupported vector instruction in interpreterir: %s", wasm.VectorInstructionName(vecOp))
		}
	case wasm.OpcodeAtomicPrefix:
		c.pc++
		atomicOp := c.body[c.pc]
		switch atomicOp {
		case wasm.OpcodeAtomicMemoryWait32:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicMemoryWait32Name)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicMemoryWait(unsignedTypeI32, imm),
			)
		case wasm.OpcodeAtomicMemoryWait64:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicMemoryWait64Name)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicMemoryWait(unsignedTypeI64, imm),
			)
		case wasm.OpcodeAtomicMemoryNotify:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicMemoryNotifyName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicMemoryNotify(imm),
			)
		case wasm.OpcodeAtomicFence:
			// Skip immediate value
			c.pc++
			_ = c.body[c.pc]
			c.emit(
				newOperationAtomicFence(),
			)
		case wasm.OpcodeAtomicI32Load:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32LoadName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicLoad(unsignedTypeI32, imm),
			)
		case wasm.OpcodeAtomicI64Load:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64LoadName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicLoad(unsignedTypeI64, imm),
			)
		case wasm.OpcodeAtomicI32Load8U:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Load8UName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicLoad8(unsignedTypeI32, imm),
			)
		case wasm.OpcodeAtomicI32Load16U:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Load16UName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicLoad16(unsignedTypeI32, imm),
			)
		case wasm.OpcodeAtomicI64Load8U:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Load8UName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicLoad8(unsignedTypeI64, imm),
			)
		case wasm.OpcodeAtomicI64Load16U:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Load16UName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicLoad16(unsignedTypeI64, imm),
			)
		case wasm.OpcodeAtomicI64Load32U:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Load32UName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicLoad(unsignedTypeI32, imm),
			)
		case wasm.OpcodeAtomicI32Store:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32StoreName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicStore(unsignedTypeI32, imm),
			)
		case wasm.OpcodeAtomicI32Store8:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Store8Name)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicStore8(unsignedTypeI32, imm),
			)
		case wasm.OpcodeAtomicI32Store16:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Store16Name)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicStore16(unsignedTypeI32, imm),
			)
		case wasm.OpcodeAtomicI64Store:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64StoreName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicStore(unsignedTypeI64, imm),
			)
		case wasm.OpcodeAtomicI64Store8:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Store8Name)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicStore8(unsignedTypeI64, imm),
			)
		case wasm.OpcodeAtomicI64Store16:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Store16Name)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicStore16(unsignedTypeI64, imm),
			)
		case wasm.OpcodeAtomicI64Store32:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Store32Name)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicStore(unsignedTypeI32, imm),
			)
		case wasm.OpcodeAtomicI32RmwAdd:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32RmwAddName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpAdd),
			)
		case wasm.OpcodeAtomicI64RmwAdd:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64RmwAddName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW(unsignedTypeI64, imm, atomicArithmeticOpAdd),
			)
		case wasm.OpcodeAtomicI32Rmw8AddU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw8AddUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW8(unsignedTypeI32, imm, atomicArithmeticOpAdd),
			)
		case wasm.OpcodeAtomicI64Rmw8AddU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw8AddUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW8(unsignedTypeI64, imm, atomicArithmeticOpAdd),
			)
		case wasm.OpcodeAtomicI32Rmw16AddU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw16AddUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW16(unsignedTypeI32, imm, atomicArithmeticOpAdd),
			)
		case wasm.OpcodeAtomicI64Rmw16AddU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw16AddUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW16(unsignedTypeI64, imm, atomicArithmeticOpAdd),
			)
		case wasm.OpcodeAtomicI64Rmw32AddU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw32AddUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpAdd),
			)
		case wasm.OpcodeAtomicI32RmwSub:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32RmwSubName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpSub),
			)
		case wasm.OpcodeAtomicI64RmwSub:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64RmwSubName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW(unsignedTypeI64, imm, atomicArithmeticOpSub),
			)
		case wasm.OpcodeAtomicI32Rmw8SubU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw8SubUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW8(unsignedTypeI32, imm, atomicArithmeticOpSub),
			)
		case wasm.OpcodeAtomicI64Rmw8SubU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw8SubUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW8(unsignedTypeI64, imm, atomicArithmeticOpSub),
			)
		case wasm.OpcodeAtomicI32Rmw16SubU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw16SubUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW16(unsignedTypeI32, imm, atomicArithmeticOpSub),
			)
		case wasm.OpcodeAtomicI64Rmw16SubU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw16SubUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW16(unsignedTypeI64, imm, atomicArithmeticOpSub),
			)
		case wasm.OpcodeAtomicI64Rmw32SubU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw32SubUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpSub),
			)
		case wasm.OpcodeAtomicI32RmwAnd:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32RmwAndName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpAnd),
			)
		case wasm.OpcodeAtomicI64RmwAnd:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64RmwAndName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW(unsignedTypeI64, imm, atomicArithmeticOpAnd),
			)
		case wasm.OpcodeAtomicI32Rmw8AndU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw8AndUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW8(unsignedTypeI32, imm, atomicArithmeticOpAnd),
			)
		case wasm.OpcodeAtomicI64Rmw8AndU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw8AndUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW8(unsignedTypeI64, imm, atomicArithmeticOpAnd),
			)
		case wasm.OpcodeAtomicI32Rmw16AndU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw16AndUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW16(unsignedTypeI32, imm, atomicArithmeticOpAnd),
			)
		case wasm.OpcodeAtomicI64Rmw16AndU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw16AndUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW16(unsignedTypeI64, imm, atomicArithmeticOpAnd),
			)
		case wasm.OpcodeAtomicI64Rmw32AndU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw32AndUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpAnd),
			)
		case wasm.OpcodeAtomicI32RmwOr:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32RmwOrName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpOr),
			)
		case wasm.OpcodeAtomicI64RmwOr:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64RmwOrName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW(unsignedTypeI64, imm, atomicArithmeticOpOr),
			)
		case wasm.OpcodeAtomicI32Rmw8OrU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw8OrUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW8(unsignedTypeI32, imm, atomicArithmeticOpOr),
			)
		case wasm.OpcodeAtomicI64Rmw8OrU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw8OrUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW8(unsignedTypeI64, imm, atomicArithmeticOpOr),
			)
		case wasm.OpcodeAtomicI32Rmw16OrU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw16OrUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW16(unsignedTypeI32, imm, atomicArithmeticOpOr),
			)
		case wasm.OpcodeAtomicI64Rmw16OrU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw16OrUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW16(unsignedTypeI64, imm, atomicArithmeticOpOr),
			)
		case wasm.OpcodeAtomicI64Rmw32OrU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw32OrUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpOr),
			)
		case wasm.OpcodeAtomicI32RmwXor:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32RmwXorName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpXor),
			)
		case wasm.OpcodeAtomicI64RmwXor:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64RmwXorName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW(unsignedTypeI64, imm, atomicArithmeticOpXor),
			)
		case wasm.OpcodeAtomicI32Rmw8XorU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw8XorUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW8(unsignedTypeI32, imm, atomicArithmeticOpXor),
			)
		case wasm.OpcodeAtomicI64Rmw8XorU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw8XorUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW8(unsignedTypeI64, imm, atomicArithmeticOpXor),
			)
		case wasm.OpcodeAtomicI32Rmw16XorU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw16XorUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW16(unsignedTypeI32, imm, atomicArithmeticOpXor),
			)
		case wasm.OpcodeAtomicI64Rmw16XorU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw16XorUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW16(unsignedTypeI64, imm, atomicArithmeticOpXor),
			)
		case wasm.OpcodeAtomicI64Rmw32XorU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw32XorUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpXor),
			)
		case wasm.OpcodeAtomicI32RmwXchg:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32RmwXchgName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpNop),
			)
		case wasm.OpcodeAtomicI64RmwXchg:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64RmwXchgName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW(unsignedTypeI64, imm, atomicArithmeticOpNop),
			)
		case wasm.OpcodeAtomicI32Rmw8XchgU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw8XchgUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW8(unsignedTypeI32, imm, atomicArithmeticOpNop),
			)
		case wasm.OpcodeAtomicI64Rmw8XchgU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw8XchgUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW8(unsignedTypeI64, imm, atomicArithmeticOpNop),
			)
		case wasm.OpcodeAtomicI32Rmw16XchgU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw16XchgUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW16(unsignedTypeI32, imm, atomicArithmeticOpNop),
			)
		case wasm.OpcodeAtomicI64Rmw16XchgU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw16XchgUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW16(unsignedTypeI64, imm, atomicArithmeticOpNop),
			)
		case wasm.OpcodeAtomicI64Rmw32XchgU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw32XchgUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpNop),
			)
		case wasm.OpcodeAtomicI32RmwCmpxchg:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32RmwCmpxchgName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMWCmpxchg(unsignedTypeI32, imm),
			)
		case wasm.OpcodeAtomicI64RmwCmpxchg:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64RmwCmpxchgName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMWCmpxchg(unsignedTypeI64, imm),
			)
		case wasm.OpcodeAtomicI32Rmw8CmpxchgU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw8CmpxchgUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW8Cmpxchg(unsignedTypeI32, imm),
			)
		case wasm.OpcodeAtomicI64Rmw8CmpxchgU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw8CmpxchgUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW8Cmpxchg(unsignedTypeI64, imm),
			)
		case wasm.OpcodeAtomicI32Rmw16CmpxchgU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw16CmpxchgUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW16Cmpxchg(unsignedTypeI32, imm),
			)
		case wasm.OpcodeAtomicI64Rmw16CmpxchgU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw16CmpxchgUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMW16Cmpxchg(unsignedTypeI64, imm),
			)
		case wasm.OpcodeAtomicI64Rmw32CmpxchgU:
			imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw32CmpxchgUName)
			if err != nil {
				return err
			}
			c.emit(
				newOperationAtomicRMWCmpxchg(unsignedTypeI32, imm),
			)
		default:
			return fmt.Errorf("unsupported atomic instruction in interpreterir: %s", wasm.AtomicInstructionName(atomicOp))
		}

	case wasm.OpcodeTailCallReturnCall:
		fdef := c.module.FunctionDefinition(index)
		functionFrame := c.controlFrames.functionFrame()
		// Currently we do not support imported functions, we treat them as regular calls.
		// For details, see internal/engine/RATIONALE.md
		if _, _, isImport := fdef.Import(); isImport {
			c.emit(newOperationCall(index))
			dropOp := newOperationDrop(c.getFrameDropRange(functionFrame, false))

			// Cleanup the stack and then jmp to function frame's continuation (meaning return).
			c.emit(dropOp)
			c.emit(newOperationBr(functionFrame.asLabel()))
		} else {
			c.emit(newOperationTailCallReturnCall(index))
		}

		// Return operation is stack-polymorphic, and mark the state as unreachable.
		// That means subsequent instructions in the current control frame are "unreachable"
		// and can be safely removed.
		c.markUnreachable()

	case wasm.OpcodeTailCallReturnCallIndirect:
		typeIndex := index
		tableIndex, n, err := leb128.LoadUint32(c.body[c.pc+1:])
		if err != nil {
			return fmt.Errorf("read target for br_table: %w", err)
		}
		c.pc += n

		functionFrame := c.controlFrames.functionFrame()
		dropRange := c.getFrameDropRange(functionFrame, false)
		c.emit(newOperationTailCallReturnCallIndirect(typeIndex, tableIndex, dropRange, functionFrame.asLabel()))

		// Return operation is stack-polymorphic, and mark the state as unreachable.
		// That means subsequent instructions in the current control frame are "unreachable"
		// and can be safely removed.
		c.markUnreachable()

	case wasm.OpcodeCallRef:
		c.emit(newOperationCallRef(index))

	case wasm.OpcodeReturnCallRef:
		functionFrame := c.controlFrames.functionFrame()
		dropRange := c.getFrameDropRange(functionFrame, false)
		c.emit(newOperationReturnCallRef(index, dropRange, functionFrame.asLabel()))
		c.markUnreachable()

	case wasm.OpcodeRefAsNonNull:
		c.emit(newOperationRefAsNonNull())

	case wasm.OpcodeBrOnNull:
		targetIndex, n, err := leb128.LoadUint32(c.body[c.pc+1:])
		if err != nil {
			return fmt.Errorf("read the target for br_on_null: %w", err)
		}
		c.pc += n

		if c.unreachableState.on {
			break operatorSwitch
		}

		targetFrame := c.controlFrames.get(int(targetIndex))
		targetFrame.ensureContinuation()
		drop := c.getFrameDropRange(targetFrame, false)
		target := targetFrame.asLabel()
		c.result.LabelCallers[target]++

		continuationLabel := newLabel(labelKindHeader, c.nextFrameID())
		c.result.LabelCallers[continuationLabel]++
		c.emit(newOperationBrOnNull(target, continuationLabel, drop))
		c.emit(newOperationLabel(continuationLabel))
		// On fall-through (non-null), the ref is pushed back at runtime.
		c.stackPush(unsignedTypeI64)

	case wasm.OpcodeBrOnNonNull:
		targetIndex, n, err := leb128.LoadUint32(c.body[c.pc+1:])
		if err != nil {
			return fmt.Errorf("read the target for br_on_non_null: %w", err)
		}
		c.pc += n

		if c.unreachableState.on {
			break operatorSwitch
		}

		targetFrame := c.controlFrames.get(int(targetIndex))
		targetFrame.ensureContinuation()
		drop := c.getFrameDropRange(targetFrame, false)
		target := targetFrame.asLabel()
		c.result.LabelCallers[target]++

		continuationLabel := newLabel(labelKindHeader, c.nextFrameID())
		c.result.LabelCallers[continuationLabel]++
		c.emit(newOperationBrOnNonNull(target, continuationLabel, drop))
		c.emit(newOperationLabel(continuationLabel))

	default:
		return fmt.Errorf("unsupported instruction in interpreterir: 0x%x", op)
	}

	// Move the program counter to point to the next instruction.
	c.pc++
	return nil
}

func (c *compiler) nextFrameID() (id uint32) {
	id = c.currentFrameID + 1
	c.currentFrameID++
	return
}

func (c *compiler) applyToStack(opcode wasm.Opcode) (index uint32, err error) {
	switch opcode {
	case
		// These are the opcodes that is coupled with "index"　immediate
		// and it DOES affect the signature of opcode.
		wasm.OpcodeCall,
		wasm.OpcodeCallIndirect,
		wasm.OpcodeLocalGet,
		wasm.OpcodeLocalSet,
		wasm.OpcodeLocalTee,
		wasm.OpcodeGlobalGet,
		wasm.OpcodeGlobalSet,
		// tail-call proposal
		wasm.OpcodeTailCallReturnCall,
		wasm.OpcodeTailCallReturnCallIndirect,
		// exception handling - throw reads tag index
		wasm.OpcodeThrow,
		// typed function references
		wasm.OpcodeCallRef,
		wasm.OpcodeReturnCallRef:
		// Assumes that we are at the opcode now so skip it before read immediates.
		v, num, err := leb128.LoadUint32(c.body[c.pc+1:])
		if err != nil {
			return 0, fmt.Errorf("reading immediates: %w", err)
		}
		c.pc += num
		index = v
	default:
		// Note that other opcodes are free of index
		// as it doesn't affect the signature of opt code.
		// In other words, the "index" argument of wasmOpcodeSignature
		// is ignored there.
	}

	if c.unreachableState.on {
		return 0, nil
	}

	// Retrieve the signature of the opcode.
	s, err := c.wasmOpcodeSignature(opcode, index)
	if err != nil {
		return 0, err
	}

	// Manipulate the stack according to the signature.
	// Note that the following algorithm assumes that
	// the unknown type is unique in the signature,
	// and is determined by the actual type on the stack.
	// The determined type is stored in this typeParam.
	var typeParam unsignedType
	var typeParamFound bool
	for i := range s.in {
		want := s.in[len(s.in)-1-i]
		actual := c.stackPop()
		if want == unsignedTypeUnknown && typeParamFound {
			want = typeParam
		} else if want == unsignedTypeUnknown {
			want = actual
			typeParam = want
			typeParamFound = true
		}
		if want != actual {
			return 0, fmt.Errorf("input signature mismatch: want %s but have %s", want, actual)
		}
	}

	for _, target := range s.out {
		if target == unsignedTypeUnknown && !typeParamFound {
			return 0, fmt.Errorf("cannot determine type of unknown result")
		} else if target == unsignedTypeUnknown {
			c.stackPush(typeParam)
		} else {
			c.stackPush(target)
		}
	}

	return index, nil
}

func (c *compiler) stackPeek() (ret unsignedType) {
	ret = c.stack[len(c.stack)-1]
	return
}

func (c *compiler) stackSwitchAt(frame *controlFrame) {
	c.stack = c.stack[:frame.originalStackLenWithoutParam]
	c.stackLenInUint64 = frame.originalStackLenWithoutParamUint64
}

func (c *compiler) stackPop() (ret unsignedType) {
	// No need to check stack bound
	// as we can assume that all the operations
	// are valid thanks to validateFunction
	// at module validation phase.
	ret = c.stack[len(c.stack)-1]
	c.stack = c.stack[:len(c.stack)-1]
	c.stackLenInUint64 -= 1 + int(unsignedTypeV128&ret>>2)
	return
}

func (c *compiler) stackPush(ts unsignedType) {
	c.stack = append(c.stack, ts)
	c.stackLenInUint64 += 1 + int(unsignedTypeV128&ts>>2)
}

// emit adds the operations into the result.
func (c *compiler) emit(op unionOperation) {
	if !c.unreachableState.on {
		switch op.Kind {
		case operationKindDrop:
			// If the drop range is nil,
			// we could remove such operations.
			// That happens when drop operation is unnecessary.
			// i.e. when there's no need to adjust stack before jmp.
			if int64(op.U1) == -1 {
				return
			}
		}
		c.result.Operations = append(c.result.Operations, op)
		if c.needSourceOffset {
			c.result.IROperationSourceOffsetsInWasmBinary = append(c.result.IROperationSourceOffsetsInWasmBinary,
				c.currentOpPC+c.bodyOffsetInCodeSection)
		}
	}
}

// Emit const expression with default values of the given type.
func (c *compiler) emitDefaultValue(t wasm.ValueType) {
	switch t {
	case wasm.ValueTypeI32:
		c.stackPush(unsignedTypeI32)
		c.emit(newOperationConstI32(0))
	case wasm.ValueTypeI64, wasm.ValueTypeExternref, wasm.ValueTypeFuncref, wasm.ValueTypeExnref:
		c.stackPush(unsignedTypeI64)
		c.emit(newOperationConstI64(0))
	case wasm.ValueTypeF32:
		c.stackPush(unsignedTypeF32)
		c.emit(newOperationConstF32(0))
	case wasm.ValueTypeF64:
		c.stackPush(unsignedTypeF64)
		c.emit(newOperationConstF64(0))
	case wasm.ValueTypeV128:
		c.stackPush(unsignedTypeV128)
		c.emit(newOperationV128Const(0, 0))
	default:
		// Concrete ref types (ref $t) have variable bit patterns.
		if t.IsRef() {
			c.stackPush(unsignedTypeI64)
			c.emit(newOperationConstI64(0))
		} else {
			panic(fmt.Sprintf("bug: unsupported value type for default value: 0x%x", t))
		}
	}
}

// Returns the "depth" (starting from top of the stack)
// of the n-th local.
func (c *compiler) localDepth(index wasm.Index) int {
	height := c.localIndexToStackHeightInUint64[index]
	return c.stackLenInUint64 - 1 - height
}

func (c *compiler) localType(index wasm.Index) (t wasm.ValueType) {
	if params := uint32(len(c.sig.Params)); index < params {
		t = c.sig.Params[index]
	} else {
		t = c.localTypes[index-params]
	}
	return
}

// getFrameDropRange returns the range (starting from top of the stack) that spans across the (uint64) stack. The range is
// supposed to be dropped from the stack when the given frame exists or branch into it.
//
// * frame is the control frame which the call-site is trying to branch into or exit.
// * isEnd true if the call-site is handling wasm.OpcodeEnd.
func (c *compiler) getFrameDropRange(frame *controlFrame, isEnd bool) inclusiveRange {
	var start int
	if !isEnd && frame.kind == controlFrameKindLoop {
		// If this is not End and the call-site is trying to branch into the Loop control frame,
		// we have to Start executing from the beginning of the loop block.
		// Therefore, we have to pass the inputs to the frame.
		start = frame.blockType.ParamNumInUint64
	} else {
		start = frame.blockType.ResultNumInUint64
	}
	end := c.stackLenInUint64 - 1 - frame.originalStackLenWithoutParamUint64
	if start <= end {
		return inclusiveRange{Start: int32(start), End: int32(end)}
	} else {
		return nopinclusiveRange
	}
}

func (c *compiler) readMemoryArg(tag string) (memoryArg, error) {
	c.result.UsesMemory = true
	alignment, num, err := leb128.LoadUint32(c.body[c.pc+1:])
	if err != nil {
		return memoryArg{}, fmt.Errorf("reading alignment for %s: %w", tag, err)
	}
	c.pc += num
	offset, num, err := leb128.LoadUint32(c.body[c.pc+1:])
	if err != nil {
		return memoryArg{}, fmt.Errorf("reading offset for %s: %w", tag, err)
	}
	c.pc += num
	return memoryArg{Offset: offset, Alignment: alignment}, nil
}

// parseCatchClause parses a single catch clause from the bytecode at c.pc,
// advancing c.pc past the clause. Returns the kind, tag index (0 for catch_all
// variants), and label index.
func (c *compiler) parseCatchClause() (kind byte, tagIdx, labelIdx uint32, err error) {
	var n uint64
	c.pc++
	kind = c.body[c.pc]
	switch kind {
	case wasm.CatchKindCatch, wasm.CatchKindCatchRef:
		c.pc++
		tagIdx, n, err = leb128.LoadUint32(c.body[c.pc:])
		if err != nil {
			err = fmt.Errorf("reading catch tag index: %w", err)
			return
		}
		c.pc += n - 1
	}
	c.pc++
	labelIdx, n, err = leb128.LoadUint32(c.body[c.pc:])
	if err != nil {
		err = fmt.Errorf("reading catch label index: %w", err)
		return
	}
	c.pc += n - 1
	return
}
