package wasm

import (
	"bytes"
	"errors"
	"fmt"
	"maps"
	"slices"
	"strconv"
	"strings"

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

// The wazero specific limitation described at RATIONALE.md.
const maximumValuesOnStack = 1 << 27

// validateFunction validates the instruction sequence of a function.
// following the specification https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#instructions%E2%91%A2.
//
// * idx is the index in the FunctionSection
// * functions are the function index, which is prefixed by imports. The value is the TypeSection index.
// * globals are the global index, which is prefixed by imports.
// * memory is the potentially imported memory and can be nil.
// * table is the potentially imported table and can be nil.
// * declaredFunctionIndexes is the set of function indexes declared by declarative element segments which can be acceed by OpcodeRefFunc instruction.
//
// Returns an error if the instruction sequence is not valid,
// or potentially it can exceed the maximum number of values on the stack.
func (m *Module) validateFunction(sts *stacks, enabledFeatures api.CoreFeatures, idx Index, functions []Index,
	globals []GlobalType, memory *Memory, tables []Table, tags []Index, declaredFunctionIndexes map[Index]struct{}, br *bytes.Reader,
) error {
	return m.validateFunctionWithMaxStackValues(sts, enabledFeatures, idx, functions, globals, memory, tables, tags, maximumValuesOnStack, declaredFunctionIndexes, br)
}

func readMemArg(pc uint64, body []byte) (align, offset uint32, read uint64, err error) {
	align, num, err := leb128.LoadUint32(body[pc:])
	if err != nil {
		err = fmt.Errorf("read memory align: %v", err)
		return
	}
	if align >= 32 {
		// Prevent 1<<align uint32 overflow.
		err = fmt.Errorf("invalid memory alignment")
		return
	}
	read += num

	offset, num, err = leb128.LoadUint32(body[pc+num:])
	if err != nil {
		err = fmt.Errorf("read memory offset: %v", err)
		return
	}

	read += num
	return align, offset, read, nil
}

// validateFunctionWithMaxStackValues is like validateFunction, but allows overriding maxStackValues for testing.
//
// * stacks is to track the state of Wasm value and control frame stacks at anypoint of execution, and reused to reduce allocation.
// * maxStackValues is the maximum height of values stack which the target is allowed to reach.
func (m *Module) validateFunctionWithMaxStackValues(
	sts *stacks,
	enabledFeatures api.CoreFeatures,
	idx Index,
	functions []Index,
	globals []GlobalType,
	memory *Memory,
	tables []Table,
	tags []Index,
	maxStackValues int,
	declaredFunctionIndexes map[Index]struct{},
	br *bytes.Reader,
) error {
	functionType := &m.TypeSection[m.FunctionSection[idx]]
	code := &m.CodeSection[idx]
	body := code.Body
	localTypes := code.LocalTypes

	sts.reset(functionType)
	valueTypeStack := &sts.vs
	// We start with the outermost control block which is for function return if the code branches into it.
	controlBlockStack := &sts.cs

	// Now start walking through all the instructions in the body while tracking
	// control blocks and value types to check the validity of all instructions.
	for pc := uint64(0); pc < uint64(len(body)); pc++ {
		op := body[pc]
		if false {
			var instName string
			if op == OpcodeMiscPrefix {
				instName = MiscInstructionName(body[pc+1])
			} else if op == OpcodeVecPrefix {
				instName = VectorInstructionName(body[pc+1])
			} else if op == OpcodeAtomicPrefix {
				instName = AtomicInstructionName(body[pc+1])
			} else {
				instName = InstructionName(op)
			}
			fmt.Printf("handling %s, stack=%v, blocks: %v\n", instName, valueTypeStack.stack, controlBlockStack)
		}

		if len(controlBlockStack.stack) == 0 {
			return fmt.Errorf("unexpected end of function at pc=%#x", pc)
		}

		if OpcodeI32Load <= op && op <= OpcodeI64Store32 {
			if memory == nil {
				return fmt.Errorf("memory must exist for %s", InstructionName(op))
			}
			pc++
			align, _, read, err := readMemArg(pc, body)
			if err != nil {
				return err
			}
			pc += read - 1
			switch op {
			case OpcodeI32Load:
				if 1<<align > 32/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeF32Load:
				if 1<<align > 32/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeF32)
			case OpcodeI32Store:
				if 1<<align > 32/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
			case OpcodeF32Store:
				if 1<<align > 32/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
			case OpcodeI64Load:
				if 1<<align > 64/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeF64Load:
				if 1<<align > 64/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeF64)
			case OpcodeI64Store:
				if 1<<align > 64/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
			case OpcodeF64Store:
				if 1<<align > 64/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
			case OpcodeI32Load8S:
				if 1<<align > 1 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeI32Load8U:
				if 1<<align > 1 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeI64Load8S, OpcodeI64Load8U:
				if 1<<align > 1 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeI32Store8:
				if 1<<align > 1 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
			case OpcodeI64Store8:
				if 1<<align > 1 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
			case OpcodeI32Load16S, OpcodeI32Load16U:
				if 1<<align > 16/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeI64Load16S, OpcodeI64Load16U:
				if 1<<align > 16/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeI32Store16:
				if 1<<align > 16/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
			case OpcodeI64Store16:
				if 1<<align > 16/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
			case OpcodeI64Load32S, OpcodeI64Load32U:
				if 1<<align > 32/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeI64Store32:
				if 1<<align > 32/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
			}
		} else if OpcodeMemorySize <= op && op <= OpcodeMemoryGrow {
			if memory == nil {
				return fmt.Errorf("memory must exist for %s", InstructionName(op))
			}
			pc++
			val, num, err := leb128.LoadUint32(body[pc:])
			if err != nil {
				return fmt.Errorf("read immediate: %v", err)
			}
			if val != 0 || num != 1 {
				return fmt.Errorf("memory instruction reserved bytes not zero with 1 byte")
			}
			switch Opcode(op) {
			case OpcodeMemoryGrow:
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeMemorySize:
				valueTypeStack.push(ValueTypeI32)
			}
			pc += num - 1
		} else if OpcodeI32Const <= op && op <= OpcodeF64Const {
			pc++
			switch Opcode(op) {
			case OpcodeI32Const:
				_, num, err := leb128.LoadInt32(body[pc:])
				if err != nil {
					return fmt.Errorf("read i32 immediate: %s", err)
				}
				pc += num - 1
				valueTypeStack.push(ValueTypeI32)
			case OpcodeI64Const:
				_, num, err := leb128.LoadInt64(body[pc:])
				if err != nil {
					return fmt.Errorf("read i64 immediate: %v", err)
				}
				valueTypeStack.push(ValueTypeI64)
				pc += num - 1
			case OpcodeF32Const:
				valueTypeStack.push(ValueTypeF32)
				pc += 3
			case OpcodeF64Const:
				valueTypeStack.push(ValueTypeF64)
				pc += 7
			}
		} else if OpcodeLocalGet <= op && op <= OpcodeGlobalSet {
			pc++
			index, num, err := leb128.LoadUint32(body[pc:])
			if err != nil {
				return fmt.Errorf("read immediate: %v", err)
			}
			pc += num - 1
			switch op {
			case OpcodeLocalGet:
				inputLen := uint32(len(functionType.Params))
				if l := uint32(len(localTypes)) + inputLen; index >= l {
					return fmt.Errorf("invalid local index for %s %d >= %d(=len(locals)+len(parameters))",
						OpcodeLocalGetName, index, l)
				}
				if err := sts.requireLocalInit(index, inputLen, localTypes); err != nil {
					return err
				}
				if index < inputLen {
					valueTypeStack.push(functionType.Params[index])
				} else {
					valueTypeStack.push(localTypes[index-inputLen])
				}
			case OpcodeLocalSet:
				inputLen := uint32(len(functionType.Params))
				if l := uint32(len(localTypes)) + inputLen; index >= l {
					return fmt.Errorf("invalid local index for %s %d >= %d(=len(locals)+len(parameters))",
						OpcodeLocalSetName, index, l)
				}
				var expType ValueType
				if index < inputLen {
					expType = functionType.Params[index]
				} else {
					expType = localTypes[index-inputLen]
				}
				if err := valueTypeStack.popAndVerifyType(expType); err != nil {
					return err
				}
				sts.markLocalInit(index, inputLen, localTypes)
			case OpcodeLocalTee:
				inputLen := uint32(len(functionType.Params))
				if l := uint32(len(localTypes)) + inputLen; index >= l {
					return fmt.Errorf("invalid local index for %s %d >= %d(=len(locals)+len(parameters))",
						OpcodeLocalTeeName, index, l)
				}
				var expType ValueType
				if index < inputLen {
					expType = functionType.Params[index]
				} else {
					expType = localTypes[index-inputLen]
				}
				if err := valueTypeStack.popAndVerifyType(expType); err != nil {
					return err
				}
				valueTypeStack.push(expType)
				sts.markLocalInit(index, inputLen, localTypes)
			case OpcodeGlobalGet:
				if index >= uint32(len(globals)) {
					return fmt.Errorf("invalid index for %s", OpcodeGlobalGetName)
				}
				valueTypeStack.push(globals[index].ValType)
			case OpcodeGlobalSet:
				if index >= uint32(len(globals)) {
					return fmt.Errorf("invalid global index")
				} else if !globals[index].Mutable {
					return fmt.Errorf("%s when not mutable", OpcodeGlobalSetName)
				} else if err := valueTypeStack.popAndVerifyType(
					globals[index].ValType); err != nil {
					return err
				}
			}
		} else if op == OpcodeBr {
			pc++
			index, num, err := leb128.LoadUint32(body[pc:])
			if err != nil {
				return fmt.Errorf("read immediate: %v", err)
			} else if int(index) >= len(controlBlockStack.stack) {
				return fmt.Errorf("invalid %s operation: index out of range", OpcodeBrName)
			}
			pc += num - 1
			// Check type soundness.
			target := &controlBlockStack.stack[len(controlBlockStack.stack)-int(index)-1]
			var targetResultType []ValueType
			if target.op == OpcodeLoop {
				targetResultType = target.blockType.Params
			} else {
				targetResultType = target.blockType.Results
			}
			if err = valueTypeStack.popResults(op, targetResultType, false); err != nil {
				return err
			}
			// br instruction is stack-polymorphic.
			valueTypeStack.unreachable()
		} else if op == OpcodeBrIf {
			pc++
			index, num, err := leb128.LoadUint32(body[pc:])
			if err != nil {
				return fmt.Errorf("read immediate: %v", err)
			} else if int(index) >= len(controlBlockStack.stack) {
				return fmt.Errorf(
					"invalid ln param given for %s: index=%d with %d for the current label stack length",
					OpcodeBrIfName, index, len(controlBlockStack.stack))
			}
			pc += num - 1
			if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
				return fmt.Errorf("cannot pop the required operand for %s", OpcodeBrIfName)
			}
			// Check type soundness.
			target := &controlBlockStack.stack[len(controlBlockStack.stack)-int(index)-1]
			var targetResultType []ValueType
			if target.op == OpcodeLoop {
				targetResultType = target.blockType.Params
			} else {
				targetResultType = target.blockType.Results
			}
			if err := valueTypeStack.popResults(op, targetResultType, false); err != nil {
				return err
			}
			// Push back the result
			for _, t := range targetResultType {
				valueTypeStack.push(t)
			}
		} else if op == OpcodeBrTable {
			pc++
			br.Reset(body[pc:])
			nl, num, err := leb128.DecodeUint32(br)
			if err != nil {
				return fmt.Errorf("read immediate: %w", err)
			}

			sts.ls = sts.ls[:0]
			for i := uint32(0); i < nl; i++ {
				l, n, err := leb128.DecodeUint32(br)
				if err != nil {
					return fmt.Errorf("read immediate: %w", err)
				}
				num += n
				sts.ls = append(sts.ls, l)
			}
			ln, n, err := leb128.DecodeUint32(br)
			if err != nil {
				return fmt.Errorf("read immediate: %w", err)
			} else if int(ln) >= len(controlBlockStack.stack) {
				return fmt.Errorf(
					"invalid ln param given for %s: ln=%d with %d for the current label stack length",
					OpcodeBrTableName, ln, len(controlBlockStack.stack))
			}
			pc += n + num - 1
			// Check type soundness.
			if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
				return fmt.Errorf("cannot pop the required operand for %s", OpcodeBrTableName)
			}
			lnLabel := &controlBlockStack.stack[len(controlBlockStack.stack)-1-int(ln)]
			var defaultLabelType []ValueType
			// Below, we might modify the slice in case of unreachable. Therefore,
			// we have to copy the content of block result types, otherwise the original
			// function type might result in invalid value types if the block is the outermost label
			// which equals the function's type.
			if lnLabel.op != OpcodeLoop { // Loop operation doesn't require results since the continuation is the beginning of the loop.
				defaultLabelType = slices.Clone(lnLabel.blockType.Results)
			} else {
				defaultLabelType = slices.Clone(lnLabel.blockType.Params)
			}

			if enabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes) {
				// As of reference-types proposal, br_table on unreachable state
				// can choose unknown types for expected parameter types for each label.
				// https://github.com/WebAssembly/reference-types/pull/116
				for i := range defaultLabelType {
					index := len(defaultLabelType) - 1 - i
					exp := defaultLabelType[index]
					actual, err := valueTypeStack.pop()
					if err != nil {
						return err
					}
					if actual == valueTypeUnknown {
						defaultLabelType[index] = valueTypeUnknown
					} else if !isRefSubtypeOf(actual, exp) {
						return typeMismatchError(true, OpcodeBrTableName, actual, exp, i)
					}
				}
			} else {
				if err = valueTypeStack.popResults(op, defaultLabelType, false); err != nil {
					return err
				}
			}

			for _, l := range sts.ls {
				if int(l) >= len(controlBlockStack.stack) {
					return fmt.Errorf("invalid l param given for %s", OpcodeBrTableName)
				}
				label := &controlBlockStack.stack[len(controlBlockStack.stack)-1-int(l)]
				var tableLabelType []ValueType
				if label.op != OpcodeLoop {
					tableLabelType = label.blockType.Results
				} else {
					tableLabelType = label.blockType.Params
				}
				if len(defaultLabelType) != len(tableLabelType) {
					return fmt.Errorf("inconsistent block type length for %s at %d; %v (ln=%d) != %v (l=%d)", OpcodeBrTableName, l, defaultLabelType, ln, tableLabelType, l)
				}
				for i := range defaultLabelType {
					if defaultLabelType[i] != valueTypeUnknown && !areRefTypesCompatible(defaultLabelType[i], tableLabelType[i]) {
						return fmt.Errorf("inconsistent block type for %s at %d", OpcodeBrTableName, l)
					}
				}
			}

			// br_table instruction is stack-polymorphic.
			valueTypeStack.unreachable()
		} else if op == OpcodeThrow {
			if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesExceptionHandling); err != nil {
				return fmt.Errorf("%s invalid as %v", OpcodeThrowName, err)
			}
			pc++
			tagIndex, num, err := leb128.LoadUint32(body[pc:])
			if err != nil {
				return fmt.Errorf("read immediate: %v", err)
			}
			pc += num - 1
			if tagIndex >= uint32(len(tags)) {
				return fmt.Errorf("invalid tag index for %s: %d", OpcodeThrowName, tagIndex)
			}
			tagType := &m.TypeSection[tags[tagIndex]]
			// Pop values matching the tag's params in reverse order.
			for i := len(tagType.Params) - 1; i >= 0; i-- {
				if err := valueTypeStack.popAndVerifyType(tagType.Params[i]); err != nil {
					return fmt.Errorf("type mismatch on %s operation: %v", OpcodeThrowName, err)
				}
			}
			// throw is stack-polymorphic (never returns).
			valueTypeStack.unreachable()
		} else if op == OpcodeThrowRef {
			if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesExceptionHandling); err != nil {
				return fmt.Errorf("%s invalid as %v", OpcodeThrowRefName, err)
			}
			if err := valueTypeStack.popAndVerifyType(ValueTypeExnref); err != nil {
				return fmt.Errorf("type mismatch on %s: %v", OpcodeThrowRefName, err)
			}
			// throw_ref is stack-polymorphic (never returns).
			valueTypeStack.unreachable()
		} else if op == OpcodeTryTable {
			if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesExceptionHandling); err != nil {
				return fmt.Errorf("%s invalid as %v", OpcodeTryTableName, err)
			}
			br.Reset(body[pc+1:])
			bt, num, err := DecodeBlockType(m.TypeSection, br, enabledFeatures)
			if err != nil {
				return fmt.Errorf("read block: %w", err)
			}
			pc += num

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

			// Validate each catch clause.
			for i := uint32(0); i < catchCount; i++ {
				pc++
				catchKind := body[pc]
				switch catchKind {
				case CatchKindCatch, CatchKindCatchRef:
					pc++
					tagIdx, tagNum, err := leb128.LoadUint32(body[pc:])
					if err != nil {
						return fmt.Errorf("read catch tag index: %v", err)
					}
					pc += tagNum - 1
					if tagIdx >= uint32(len(tags)) {
						return fmt.Errorf("invalid tag index in catch clause: %d", tagIdx)
					}
					tagType := &m.TypeSection[tags[tagIdx]]
					pc++
					labelIdx, labelNum, err := leb128.LoadUint32(body[pc:])
					if err != nil {
						return fmt.Errorf("read catch label index: %v", err)
					}
					pc += labelNum - 1
					if int(labelIdx) >= len(controlBlockStack.stack) {
						return fmt.Errorf("invalid label index in catch clause: %d", labelIdx)
					}
					// Validate that the target label can accept the catch values.
					target := &controlBlockStack.stack[len(controlBlockStack.stack)-int(labelIdx)-1]
					var expectedTypes []ValueType
					if target.op == OpcodeLoop {
						expectedTypes = target.blockType.Params
					} else {
						expectedTypes = target.blockType.Results
					}
					var catchTypes []ValueType
					catchTypes = append(catchTypes, tagType.Params...)
					if catchKind == CatchKindCatchRef {
						catchTypes = append(catchTypes, ValueTypeExnref.AsNonNullable())
					}
					if len(catchTypes) != len(expectedTypes) {
						return fmt.Errorf("catch clause type mismatch: catch delivers %d values but label expects %d", len(catchTypes), len(expectedTypes))
					}
					for j := range catchTypes {
						if !isRefSubtypeOf(catchTypes[j], expectedTypes[j]) {
							return fmt.Errorf("catch clause type mismatch at index %d: %v is not a subtype of %v", j, catchTypes[j], expectedTypes[j])
						}
					}
				case CatchKindCatchAll, CatchKindCatchAllRef:
					pc++
					labelIdx, labelNum, err := leb128.LoadUint32(body[pc:])
					if err != nil {
						return fmt.Errorf("read catch_all label index: %v", err)
					}
					pc += labelNum - 1
					if int(labelIdx) >= len(controlBlockStack.stack) {
						return fmt.Errorf("invalid label index in catch_all clause: %d", labelIdx)
					}
					target := &controlBlockStack.stack[len(controlBlockStack.stack)-int(labelIdx)-1]
					var expectedTypes []ValueType
					if target.op == OpcodeLoop {
						expectedTypes = target.blockType.Params
					} else {
						expectedTypes = target.blockType.Results
					}
					var catchTypes []ValueType
					if catchKind == CatchKindCatchAllRef {
						catchTypes = append(catchTypes, ValueTypeExnref.AsNonNullable())
					}
					if len(catchTypes) != len(expectedTypes) {
						return fmt.Errorf("catch_all clause type mismatch: catch delivers %d values but label expects %d", len(catchTypes), len(expectedTypes))
					}
					for j := range catchTypes {
						if !isRefSubtypeOf(catchTypes[j], expectedTypes[j]) {
							return fmt.Errorf("catch_all clause type mismatch at index %d", j)
						}
					}
				default:
					return fmt.Errorf("invalid catch kind: %#x", catchKind)
				}
			}

			controlBlockStack.push(pc, 0, 0, bt, 0, op)
			if err = valueTypeStack.popParams(op, bt.Params, false); err != nil {
				return err
			}
			for _, p := range bt.Params {
				valueTypeStack.push(p)
			}
			valueTypeStack.pushStackLimit(len(bt.Params))
		} else if op == OpcodeCall || op == OpcodeTailCallReturnCall {
			pc++
			index, num, err := leb128.LoadUint32(body[pc:])
			if err != nil {
				return fmt.Errorf("read immediate: %v", err)
			}
			pc += num - 1
			if int(index) >= len(functions) {
				return fmt.Errorf("invalid function index")
			}

			var opcodeName string
			if op == OpcodeCall {
				opcodeName = OpcodeCallName
			} else {
				opcodeName = OpcodeTailCallReturnCallName
			}

			funcType := &m.TypeSection[functions[index]]
			if err := sts.validateCallSignature(opcodeName, funcType); err != nil {
				return err
			}
			if op == OpcodeTailCallReturnCall {
				if err := sts.validateReturnCall(OpcodeTailCallReturnCallName, enabledFeatures, funcType, functionType); err != nil {
					return err
				}
				// behaves as a jump.
				valueTypeStack.unreachable()
			}
		} else if op == OpcodeCallIndirect || op == OpcodeTailCallReturnCallIndirect {
			pc++
			typeIndex, num, err := leb128.LoadUint32(body[pc:])
			if err != nil {
				return fmt.Errorf("read immediate: %v", err)
			}
			pc += num

			var opcodeName string
			if op == OpcodeCallIndirect {
				opcodeName = OpcodeCallIndirectName
			} else {
				opcodeName = OpcodeTailCallReturnCallIndirectName
			}

			if int(typeIndex) >= len(m.TypeSection) {
				return fmt.Errorf("invalid type index at %s: %d", opcodeName, typeIndex)
			}

			tableIndex, num, err := leb128.LoadUint32(body[pc:])
			if err != nil {
				return fmt.Errorf("read table index: %v", err)
			}
			pc += num - 1
			if tableIndex != 0 {
				if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
					return fmt.Errorf("table index must be zero but was %d: %w", tableIndex, err)
				}
			}

			if tableIndex >= uint32(len(tables)) {
				return fmt.Errorf("unknown table index: %d", tableIndex)
			}

			table := tables[tableIndex]
			if table.Type != RefTypeFuncref {
				return fmt.Errorf("table is not funcref type but was %s for %s", RefTypeName(table.Type), opcodeName)
			}

			if err = valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
				return fmt.Errorf("cannot pop the offset in table for %s", opcodeName)
			}
			funcType := &m.TypeSection[typeIndex]
			if err := sts.validateCallSignature(opcodeName, funcType); err != nil {
				return err
			}
			if op == OpcodeTailCallReturnCallIndirect {
				if err := sts.validateReturnCall(OpcodeTailCallReturnCallIndirectName, enabledFeatures, funcType, functionType); err != nil {
					return err
				}
				// behaves as a jump.
				valueTypeStack.unreachable()
			}
		} else if op == OpcodeCallRef || op == OpcodeReturnCallRef {
			if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesTypedFunctionReferences); err != nil {
				return fmt.Errorf("%s invalid as %v", instructionNames[op], err)
			}
			pc++
			typeIndex, num, err := leb128.LoadUint32(body[pc:])
			if err != nil {
				return fmt.Errorf("read immediate: %v", err)
			}
			pc += num - 1
			if int(typeIndex) >= len(m.TypeSection) {
				return fmt.Errorf("invalid type index at %s: %d", instructionNames[op], typeIndex)
			}
			// Pop the funcref operand (nullable concrete ref to the type).
			if err := valueTypeStack.popAndVerifyType(ValueTypeConcreteRef(typeIndex, true)); err != nil {
				return fmt.Errorf("type mismatch on %s operation: %v", instructionNames[op], err)
			}
			funcType := &m.TypeSection[typeIndex]
			if err := sts.validateCallSignature(instructionNames[op], funcType); err != nil {
				return err
			}
			if op == OpcodeReturnCallRef {
				if err := sts.validateReturnCall(OpcodeReturnCallRefName, enabledFeatures, funcType, functionType); err != nil {
					return err
				}
				// behaves as a jump.
				valueTypeStack.unreachable()
			}
		} else if OpcodeI32Eqz <= op && op <= OpcodeI64Extend32S {
			switch op {
			case OpcodeI32Eqz:
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI32EqzName, err)
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeI32Eq, OpcodeI32Ne, OpcodeI32LtS,
				OpcodeI32LtU, OpcodeI32GtS, OpcodeI32GtU, OpcodeI32LeS,
				OpcodeI32LeU, OpcodeI32GeS, OpcodeI32GeU:
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return fmt.Errorf("cannot pop the 1st i32 operand for %s: %v", InstructionName(op), err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return fmt.Errorf("cannot pop the 2nd i32 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeI64Eqz:
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI64EqzName, err)
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeI64Eq, OpcodeI64Ne, OpcodeI64LtS,
				OpcodeI64LtU, OpcodeI64GtS, OpcodeI64GtU,
				OpcodeI64LeS, OpcodeI64LeU, OpcodeI64GeS, OpcodeI64GeU:
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return fmt.Errorf("cannot pop the 1st i64 operand for %s: %v", InstructionName(op), err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return fmt.Errorf("cannot pop the 2nd i64 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeF32Eq, OpcodeF32Ne, OpcodeF32Lt, OpcodeF32Gt, OpcodeF32Le, OpcodeF32Ge:
				if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
					return fmt.Errorf("cannot pop the 1st f32 operand for %s: %v", InstructionName(op), err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
					return fmt.Errorf("cannot pop the 2nd f32 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeF64Eq, OpcodeF64Ne, OpcodeF64Lt, OpcodeF64Gt, OpcodeF64Le, OpcodeF64Ge:
				if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
					return fmt.Errorf("cannot pop the 1st f64 operand for %s: %v", InstructionName(op), err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
					return fmt.Errorf("cannot pop the 2nd f64 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeI32Clz, OpcodeI32Ctz, OpcodeI32Popcnt:
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return fmt.Errorf("cannot pop the i32 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeI32Add, OpcodeI32Sub, OpcodeI32Mul, OpcodeI32DivS,
				OpcodeI32DivU, OpcodeI32RemS, OpcodeI32RemU, OpcodeI32And,
				OpcodeI32Or, OpcodeI32Xor, OpcodeI32Shl, OpcodeI32ShrS,
				OpcodeI32ShrU, OpcodeI32Rotl, OpcodeI32Rotr:
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return fmt.Errorf("cannot pop the 1st operand for %s: %v", InstructionName(op), err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return fmt.Errorf("cannot pop the 2nd operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeI64Clz, OpcodeI64Ctz, OpcodeI64Popcnt:
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return fmt.Errorf("cannot pop the i64 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeI64Add, OpcodeI64Sub, OpcodeI64Mul, OpcodeI64DivS,
				OpcodeI64DivU, OpcodeI64RemS, OpcodeI64RemU, OpcodeI64And,
				OpcodeI64Or, OpcodeI64Xor, OpcodeI64Shl, OpcodeI64ShrS,
				OpcodeI64ShrU, OpcodeI64Rotl, OpcodeI64Rotr:
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return fmt.Errorf("cannot pop the 1st i64 operand for %s: %v", InstructionName(op), err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return fmt.Errorf("cannot pop the 2nd i64 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeF32Abs, OpcodeF32Neg, OpcodeF32Ceil,
				OpcodeF32Floor, OpcodeF32Trunc, OpcodeF32Nearest,
				OpcodeF32Sqrt:
				if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
					return fmt.Errorf("cannot pop the 1st f32 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeF32)
			case OpcodeF32Add, OpcodeF32Sub, OpcodeF32Mul,
				OpcodeF32Div, OpcodeF32Min, OpcodeF32Max,
				OpcodeF32Copysign:
				if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
					return fmt.Errorf("cannot pop the 1st f32 operand for %s: %v", InstructionName(op), err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
					return fmt.Errorf("cannot pop the 2nd f32 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeF32)
			case OpcodeF64Abs, OpcodeF64Neg, OpcodeF64Ceil,
				OpcodeF64Floor, OpcodeF64Trunc, OpcodeF64Nearest,
				OpcodeF64Sqrt:
				if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
					return fmt.Errorf("cannot pop the 1st f64 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeF64)
			case OpcodeF64Add, OpcodeF64Sub, OpcodeF64Mul,
				OpcodeF64Div, OpcodeF64Min, OpcodeF64Max,
				OpcodeF64Copysign:
				if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
					return fmt.Errorf("cannot pop the 1st f64 operand for %s: %v", InstructionName(op), err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
					return fmt.Errorf("cannot pop the 2nd f64 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeF64)
			case OpcodeI32WrapI64:
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI32WrapI64Name, err)
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeI32TruncF32S, OpcodeI32TruncF32U:
				if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
					return fmt.Errorf("cannot pop the f32 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeI32TruncF64S, OpcodeI32TruncF64U:
				if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
					return fmt.Errorf("cannot pop the f64 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeI64ExtendI32S, OpcodeI64ExtendI32U:
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return fmt.Errorf("cannot pop the i32 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeI64TruncF32S, OpcodeI64TruncF32U:
				if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
					return fmt.Errorf("cannot pop the f32 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeI64TruncF64S, OpcodeI64TruncF64U:
				if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
					return fmt.Errorf("cannot pop the f64 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeF32ConvertI32S, OpcodeF32ConvertI32U:
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return fmt.Errorf("cannot pop the i32 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeF32)
			case OpcodeF32ConvertI64S, OpcodeF32ConvertI64U:
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return fmt.Errorf("cannot pop the i64 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeF32)
			case OpcodeF32DemoteF64:
				if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeF32DemoteF64Name, err)
				}
				valueTypeStack.push(ValueTypeF32)
			case OpcodeF64ConvertI32S, OpcodeF64ConvertI32U:
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return fmt.Errorf("cannot pop the i32 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeF64)
			case OpcodeF64ConvertI64S, OpcodeF64ConvertI64U:
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return fmt.Errorf("cannot pop the i64 operand for %s: %v", InstructionName(op), err)
				}
				valueTypeStack.push(ValueTypeF64)
			case OpcodeF64PromoteF32:
				if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeF64PromoteF32Name, err)
				}
				valueTypeStack.push(ValueTypeF64)
			case OpcodeI32ReinterpretF32:
				if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI32ReinterpretF32Name, err)
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeI64ReinterpretF64:
				if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI64ReinterpretF64Name, err)
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeF32ReinterpretI32:
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeF32ReinterpretI32Name, err)
				}
				valueTypeStack.push(ValueTypeF32)
			case OpcodeF64ReinterpretI64:
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeF64ReinterpretI64Name, err)
				}
				valueTypeStack.push(ValueTypeF64)
			case OpcodeI32Extend8S, OpcodeI32Extend16S:
				if err := enabledFeatures.RequireEnabled(api.CoreFeatureSignExtensionOps); err != nil {
					return fmt.Errorf("%s invalid as %v", instructionNames[op], err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", instructionNames[op], err)
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeI64Extend8S, OpcodeI64Extend16S, OpcodeI64Extend32S:
				if err := enabledFeatures.RequireEnabled(api.CoreFeatureSignExtensionOps); err != nil {
					return fmt.Errorf("%s invalid as %v", instructionNames[op], err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", instructionNames[op], err)
				}
				valueTypeStack.push(ValueTypeI64)
			default:
				return fmt.Errorf("invalid numeric instruction 0x%x", op)
			}
		} else if op >= OpcodeRefNull && op <= OpcodeRefFunc {
			if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
				return fmt.Errorf("%s invalid as %v", instructionNames[op], err)
			}
			switch op {
			case OpcodeRefNull:
				pc++
				switch reftype := body[pc]; ValueType(reftype) {
				case ValueTypeExternref:
					valueTypeStack.push(ValueTypeExternref)
				case ValueTypeFuncref:
					valueTypeStack.push(ValueTypeFuncref)
				case ValueTypeExnref:
					valueTypeStack.push(ValueTypeExnref)
				default:
					// Concrete type index encoded as LEB128 u32.
					if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesTypedFunctionReferences); err != nil {
						return fmt.Errorf("ref.null with concrete type invalid as %v", err)
					}
					br.Reset(body[pc:])
					typeIdx, num, err := leb128.DecodeUint32(br)
					if err != nil {
						return fmt.Errorf("unknown type for ref.null: 0x%x", reftype)
					}
					if int(typeIdx) >= len(m.TypeSection) {
						return fmt.Errorf("unknown type for ref.null: type index %d out of range", typeIdx)
					}
					pc += uint64(num) - 1
					valueTypeStack.push(ValueTypeConcreteRef(typeIdx, true))
				}
			case OpcodeRefIsNull:
				tp, err := valueTypeStack.pop()
				if err != nil {
					return fmt.Errorf("cannot pop the operand for ref.is_null: %v", err)
				} else if !isReferenceValueType(tp) && tp != valueTypeUnknown {
					return fmt.Errorf("type mismatch: expected reference type but was %s", ValueTypeName(tp))
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeRefFunc:
				pc++
				index, num, err := leb128.LoadUint32(body[pc:])
				if err != nil {
					return fmt.Errorf("failed to read function index for ref.func: %v", err)
				}
				if _, ok := declaredFunctionIndexes[index]; !ok {
					return fmt.Errorf("undeclared function index %d for ref.func", index)
				}
				pc += num - 1
				if enabledFeatures.IsEnabled(experimental.CoreFeaturesTypedFunctionReferences) {
					valueTypeStack.push(ValueTypeConcreteRef(functions[index], false))
				} else {
					valueTypeStack.push(ValueTypeFuncref)
				}
			}
		} else if op == OpcodeRefAsNonNull {
			if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesTypedFunctionReferences); err != nil {
				return fmt.Errorf("%s invalid as %v", OpcodeRefAsNonNullName, err)
			}
			tp, err := valueTypeStack.pop()
			if err != nil {
				return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeRefAsNonNullName, err)
			} else if !isReferenceValueType(tp) && tp != valueTypeUnknown {
				return fmt.Errorf("type mismatch: expected reference type but was %s for %s", ValueTypeName(tp), OpcodeRefAsNonNullName)
			}
			if tp == valueTypeUnknown {
				valueTypeStack.push(tp)
			} else {
				valueTypeStack.push(tp.AsNonNullable())
			}
		} else if op == OpcodeBrOnNull {
			if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesTypedFunctionReferences); err != nil {
				return fmt.Errorf("%s invalid as %v", OpcodeBrOnNullName, err)
			}
			pc++
			index, num, err := leb128.LoadUint32(body[pc:])
			if err != nil {
				return fmt.Errorf("read immediate: %v", err)
			} else if int(index) >= len(controlBlockStack.stack) {
				return fmt.Errorf("invalid %s operation: index out of range", OpcodeBrOnNullName)
			}
			pc += num - 1
			// Pop the reference operand.
			tp, err := valueTypeStack.pop()
			if err != nil {
				return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeBrOnNullName, err)
			} else if !isReferenceValueType(tp) && tp != valueTypeUnknown {
				return fmt.Errorf("type mismatch: expected reference type but was %s for %s", ValueTypeName(tp), OpcodeBrOnNullName)
			}
			// If null, branch with the label's expected values (without the ref).
			target := &controlBlockStack.stack[len(controlBlockStack.stack)-int(index)-1]
			var targetResultType []ValueType
			if target.op == OpcodeLoop {
				targetResultType = target.blockType.Params
			} else {
				targetResultType = target.blockType.Results
			}
			if err = valueTypeStack.popResults(op, targetResultType, false); err != nil {
				return err
			}
			// Push back the label results (they stay on the stack for the fall-through path).
			for _, t := range targetResultType {
				valueTypeStack.push(t)
			}
			// On the fall-through path, the non-null ref is on the stack.
			if tp != valueTypeUnknown {
				valueTypeStack.push(tp.AsNonNullable())
			} else {
				valueTypeStack.push(tp)
			}
		} else if op == OpcodeBrOnNonNull {
			if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesTypedFunctionReferences); err != nil {
				return fmt.Errorf("%s invalid as %v", OpcodeBrOnNonNullName, err)
			}
			pc++
			index, num, err := leb128.LoadUint32(body[pc:])
			if err != nil {
				return fmt.Errorf("read immediate: %v", err)
			} else if int(index) >= len(controlBlockStack.stack) {
				return fmt.Errorf("invalid %s operation: index out of range", OpcodeBrOnNonNullName)
			}
			pc += num - 1
			// Pop the reference operand.
			tp, err := valueTypeStack.pop()
			if err != nil {
				return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeBrOnNonNullName, err)
			} else if !isReferenceValueType(tp) && tp != valueTypeUnknown {
				return fmt.Errorf("type mismatch: expected reference type but was %s for %s", ValueTypeName(tp), OpcodeBrOnNonNullName)
			}
			// If non-null, branch to label with the label's expected values.
			// The non-null ref is the LAST value delivered to the label.
			target := &controlBlockStack.stack[len(controlBlockStack.stack)-int(index)-1]
			var targetResultType []ValueType
			if target.op == OpcodeLoop {
				targetResultType = target.blockType.Params
			} else {
				targetResultType = target.blockType.Results
			}
			// The branch delivers: [label types..., non-null ref].
			// So the last type in the target must match the non-null ref.
			if len(targetResultType) == 0 {
				return fmt.Errorf("type mismatch on %s: label has no results but needs a ref type", OpcodeBrOnNonNullName)
			}
			nonNullTp := tp
			if nonNullTp != valueTypeUnknown {
				nonNullTp = tp.AsNonNullable()
			}
			lastTarget := targetResultType[len(targetResultType)-1]
			if nonNullTp != valueTypeUnknown && !isRefSubtypeOf(nonNullTp, lastTarget) {
				return fmt.Errorf("type mismatch on %s: ref type %s is not a subtype of label's last result %s",
					OpcodeBrOnNonNullName, ValueTypeName(nonNullTp), ValueTypeName(lastTarget))
			}
			// Pop and verify the remaining label types (all except the last which is the ref).
			remaining := targetResultType[:len(targetResultType)-1]
			if err = valueTypeStack.popResults(op, remaining, false); err != nil {
				return err
			}
			// Push back the remaining label results for the fall-through path.
			for _, t := range remaining {
				valueTypeStack.push(t)
			}
			// On the fall-through (null) path, nothing extra is pushed.
		} else if op == OpcodeTableGet || op == OpcodeTableSet {
			if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
				return fmt.Errorf("%s is invalid as %v", InstructionName(op), err)
			}
			pc++
			tableIndex, num, err := leb128.LoadUint32(body[pc:])
			if err != nil {
				return fmt.Errorf("read immediate: %v", err)
			}
			if tableIndex >= uint32(len(tables)) {
				return fmt.Errorf("table of index %d not found", tableIndex)
			}

			refType := tables[tableIndex].Type
			if op == OpcodeTableGet {
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return fmt.Errorf("cannot pop the operand for table.get: %v", err)
				}
				valueTypeStack.push(refType)
			} else {
				if err := valueTypeStack.popAndVerifyType(refType); err != nil {
					return fmt.Errorf("cannot pop the operand for table.set: %v", err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return fmt.Errorf("cannot pop the operand for table.set: %v", err)
				}
			}
			pc += num - 1
		} else if op == OpcodeMiscPrefix {
			pc++
			// A misc opcode is encoded as an unsigned variable 32-bit integer.
			miscOp32, num, err := leb128.LoadUint32(body[pc:])
			if err != nil {
				return fmt.Errorf("failed to read misc opcode: %v", err)
			}
			pc += num - 1
			miscOpcode := byte(miscOp32)
			// If the misc opcode is beyond byte range, it is highly likely this is an invalid binary, or
			// it is due to the new opcode from a new proposal. In the latter case, we have to
			// change the alias type of OpcodeMisc (which is currently byte) to uint32.
			if uint32(byte(miscOp32)) != miscOp32 {
				return fmt.Errorf("invalid misc opcode: %#x", miscOp32)
			}
			if miscOpcode >= OpcodeMiscI32TruncSatF32S && miscOpcode <= OpcodeMiscI64TruncSatF64U {
				if err := enabledFeatures.RequireEnabled(api.CoreFeatureNonTrappingFloatToIntConversion); err != nil {
					return fmt.Errorf("%s invalid as %v", miscInstructionNames[miscOpcode], err)
				}
				var inType, outType ValueType
				switch miscOpcode {
				case OpcodeMiscI32TruncSatF32S, OpcodeMiscI32TruncSatF32U:
					inType, outType = ValueTypeF32, ValueTypeI32
				case OpcodeMiscI32TruncSatF64S, OpcodeMiscI32TruncSatF64U:
					inType, outType = ValueTypeF64, ValueTypeI32
				case OpcodeMiscI64TruncSatF32S, OpcodeMiscI64TruncSatF32U:
					inType, outType = ValueTypeF32, ValueTypeI64
				case OpcodeMiscI64TruncSatF64S, OpcodeMiscI64TruncSatF64U:
					inType, outType = ValueTypeF64, ValueTypeI64
				}
				if err := valueTypeStack.popAndVerifyType(inType); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", miscInstructionNames[miscOpcode], err)
				}
				valueTypeStack.push(outType)
			} else if miscOpcode >= OpcodeMiscMemoryInit && miscOpcode <= OpcodeMiscTableCopy {
				if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil {
					return fmt.Errorf("%s invalid as %v", miscInstructionNames[miscOpcode], err)
				}
				var params []ValueType
				// Handle opcodes added in bulk-memory-operations/WebAssembly 2.0.
				switch miscOpcode {
				case OpcodeMiscDataDrop:
					if m.DataCountSection == nil {
						return fmt.Errorf("%s requires data count section", MiscInstructionName(miscOpcode))
					}

					// We need to read the index to the data section.
					pc++
					index, num, err := leb128.LoadUint32(body[pc:])
					if err != nil {
						return fmt.Errorf("failed to read data segment index for %s: %v", MiscInstructionName(miscOpcode), err)
					}
					if int(index) >= len(m.DataSection) {
						return fmt.Errorf("index %d out of range of data section(len=%d)", index, len(m.DataSection))
					}
					pc += num - 1
				case OpcodeMiscMemoryInit, OpcodeMiscMemoryCopy, OpcodeMiscMemoryFill:
					if memory == nil {
						return fmt.Errorf("memory must exist for %s", MiscInstructionName(miscOpcode))
					}
					params = []ValueType{ValueTypeI32, ValueTypeI32, ValueTypeI32}

					if miscOpcode == OpcodeMiscMemoryInit {
						if m.DataCountSection == nil {
							return fmt.Errorf("%s requires data count section", MiscInstructionName(miscOpcode))
						}

						// We need to read the index to the data section.
						pc++
						index, num, err := leb128.LoadUint32(body[pc:])
						if err != nil {
							return fmt.Errorf("failed to read data segment index for %s: %v", MiscInstructionName(miscOpcode), err)
						}
						if int(index) >= len(m.DataSection) {
							return fmt.Errorf("index %d out of range of data section(len=%d)", index, len(m.DataSection))
						}
						pc += num - 1
					}

					pc++
					val, num, err := leb128.LoadUint32(body[pc:])
					if err != nil {
						return fmt.Errorf("failed to read memory index for %s: %v", MiscInstructionName(miscOpcode), err)
					}
					if val != 0 || num != 1 {
						return fmt.Errorf("%s reserved byte must be zero encoded with 1 byte", MiscInstructionName(miscOpcode))
					}
					if miscOpcode == OpcodeMiscMemoryCopy {
						pc++
						// memory.copy needs two memory index which are reserved as zero.
						val, num, err := leb128.LoadUint32(body[pc:])
						if err != nil {
							return fmt.Errorf("failed to read memory index for %s: %v", MiscInstructionName(miscOpcode), err)
						}
						if val != 0 || num != 1 {
							return fmt.Errorf("%s reserved byte must be zero encoded with 1 byte", MiscInstructionName(miscOpcode))
						}
					}

				case OpcodeMiscTableInit:
					params = []ValueType{ValueTypeI32, ValueTypeI32, ValueTypeI32}
					pc++
					elementIndex, num, err := leb128.LoadUint32(body[pc:])
					if err != nil {
						return fmt.Errorf("failed to read element segment index for %s: %v", MiscInstructionName(miscOpcode), err)
					}
					if int(elementIndex) >= len(m.ElementSection) {
						return fmt.Errorf("index %d out of range of element section(len=%d)", elementIndex, len(m.ElementSection))
					}
					pc += num

					tableIndex, num, err := leb128.LoadUint32(body[pc:])
					if err != nil {
						return fmt.Errorf("failed to read source table index for %s: %v", MiscInstructionName(miscOpcode), err)
					}
					if tableIndex != 0 {
						if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
							return fmt.Errorf("source table index must be zero for %s as %v", MiscInstructionName(miscOpcode), err)
						}
					}
					if tableIndex >= uint32(len(tables)) {
						return fmt.Errorf("table of index %d not found", tableIndex)
					}

					if !isRefSubtypeOf(m.ElementSection[elementIndex].Type, tables[tableIndex].Type) {
						return fmt.Errorf("type mismatch for table.init: element type %s does not match table type %s",
							RefTypeName(m.ElementSection[elementIndex].Type),
							RefTypeName(tables[tableIndex].Type),
						)
					}
					pc += num - 1
				case OpcodeMiscElemDrop:
					pc++
					elementIndex, num, err := leb128.LoadUint32(body[pc:])
					if err != nil {
						return fmt.Errorf("failed to read element segment index for %s: %v", MiscInstructionName(miscOpcode), err)
					} else if int(elementIndex) >= len(m.ElementSection) {
						return fmt.Errorf("index %d out of range of element section(len=%d)", elementIndex, len(m.ElementSection))
					}
					pc += num - 1
				case OpcodeMiscTableCopy:
					params = []ValueType{ValueTypeI32, ValueTypeI32, ValueTypeI32}
					pc++

					dstTableIndex, num, err := leb128.LoadUint32(body[pc:])
					if err != nil {
						return fmt.Errorf("failed to read destination table index for %s: %v", MiscInstructionName(miscOpcode), err)
					}
					if dstTableIndex != 0 {
						if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
							return fmt.Errorf("destination table index must be zero for %s as %v", MiscInstructionName(miscOpcode), err)
						}
					}
					if dstTableIndex >= uint32(len(tables)) {
						return fmt.Errorf("table of index %d not found", dstTableIndex)
					}
					pc += num

					srcTableIndex, num, err := leb128.LoadUint32(body[pc:])
					if err != nil {
						return fmt.Errorf("failed to read source table index for %s: %v", MiscInstructionName(miscOpcode), err)
					}
					if srcTableIndex != 0 {
						if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
							return fmt.Errorf("source table index must be zero for %s as %v", MiscInstructionName(miscOpcode), err)
						}
					}
					if srcTableIndex >= uint32(len(tables)) {
						return fmt.Errorf("table of index %d not found", srcTableIndex)
					}

					if !isRefSubtypeOf(tables[srcTableIndex].Type, tables[dstTableIndex].Type) {
						return fmt.Errorf("table type mismatch for table.copy: %s (src) != %s (dst)",
							RefTypeName(tables[srcTableIndex].Type), RefTypeName(tables[dstTableIndex].Type))
					}

					pc += num - 1
				}
				for _, p := range params {
					if err := valueTypeStack.popAndVerifyType(p); err != nil {
						return fmt.Errorf("cannot pop the operand for %s: %v", miscInstructionNames[miscOpcode], err)
					}
				}
			} else if miscOpcode >= OpcodeMiscTableGrow && miscOpcode <= OpcodeMiscTableFill {
				if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
					return fmt.Errorf("%s invalid as %v", miscInstructionNames[miscOpcode], err)
				}

				pc++
				tableIndex, num, err := leb128.LoadUint32(body[pc:])
				if err != nil {
					return fmt.Errorf("failed to read table index for %s: %v", MiscInstructionName(miscOpcode), err)
				}
				if tableIndex >= uint32(len(tables)) {
					return fmt.Errorf("table of index %d not found", tableIndex)
				}
				pc += num - 1

				var params, results []ValueType
				reftype := tables[tableIndex].Type
				if miscOpcode == OpcodeMiscTableGrow {
					params = []ValueType{ValueTypeI32, reftype}
					results = []ValueType{ValueTypeI32}
				} else if miscOpcode == OpcodeMiscTableSize {
					results = []ValueType{ValueTypeI32}
				} else if miscOpcode == OpcodeMiscTableFill {
					params = []ValueType{ValueTypeI32, reftype, ValueTypeI32}
				}

				for _, p := range params {
					if err := valueTypeStack.popAndVerifyType(p); err != nil {
						return fmt.Errorf("cannot pop the operand for %s: %v", miscInstructionNames[miscOpcode], err)
					}
				}
				for _, r := range results {
					valueTypeStack.push(r)
				}
			} else {
				return fmt.Errorf("unknown misc opcode %#x", miscOpcode)
			}
		} else if op == OpcodeVecPrefix {
			pc++
			// Vector instructions come with two bytes where the first byte is always OpcodeVecPrefix,
			// and the second byte determines the actual instruction.
			vecOpcode := body[pc]
			if err := enabledFeatures.RequireEnabled(api.CoreFeatureSIMD); err != nil {
				return fmt.Errorf("%s invalid as %v", vectorInstructionName[vecOpcode], err)
			}

			switch vecOpcode {
			case OpcodeVecV128Const:
				// Read 128-bit = 16 bytes constants
				if int(pc+16) >= len(body) {
					return fmt.Errorf("cannot read constant vector value for %s", vectorInstructionName[vecOpcode])
				}
				pc += 16
				valueTypeStack.push(ValueTypeV128)
			case OpcodeVecV128AnyTrue, OpcodeVecI8x16AllTrue, OpcodeVecI16x8AllTrue, OpcodeVecI32x4AllTrue, OpcodeVecI64x2AllTrue,
				OpcodeVecI8x16BitMask, OpcodeVecI16x8BitMask, OpcodeVecI32x4BitMask, OpcodeVecI64x2BitMask:
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeVecV128Load, OpcodeVecV128Load8x8s, OpcodeVecV128Load8x8u, OpcodeVecV128Load16x4s, OpcodeVecV128Load16x4u,
				OpcodeVecV128Load32x2s, OpcodeVecV128Load32x2u, OpcodeVecV128Load8Splat, OpcodeVecV128Load16Splat,
				OpcodeVecV128Load32Splat, OpcodeVecV128Load64Splat,
				OpcodeVecV128Load32zero, OpcodeVecV128Load64zero:
				if memory == nil {
					return fmt.Errorf("memory must exist for %s", VectorInstructionName(vecOpcode))
				}
				pc++
				align, _, read, err := readMemArg(pc, body)
				if err != nil {
					return err
				}
				pc += read - 1
				var maxAlign uint32
				switch vecOpcode {
				case OpcodeVecV128Load:
					maxAlign = 128 / 8
				case OpcodeVecV128Load8x8s, OpcodeVecV128Load8x8u, OpcodeVecV128Load16x4s, OpcodeVecV128Load16x4u,
					OpcodeVecV128Load32x2s, OpcodeVecV128Load32x2u:
					maxAlign = 64 / 8
				case OpcodeVecV128Load8Splat:
					maxAlign = 1
				case OpcodeVecV128Load16Splat:
					maxAlign = 16 / 8
				case OpcodeVecV128Load32Splat:
					maxAlign = 32 / 8
				case OpcodeVecV128Load64Splat:
					maxAlign = 64 / 8
				case OpcodeVecV128Load32zero:
					maxAlign = 32 / 8
				case OpcodeVecV128Load64zero:
					maxAlign = 64 / 8
				}

				if 1<<align > maxAlign {
					return fmt.Errorf("invalid memory alignment %d for %s", align, VectorInstructionName(vecOpcode))
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", VectorInstructionName(vecOpcode), err)
				}
				valueTypeStack.push(ValueTypeV128)
			case OpcodeVecV128Store:
				if memory == nil {
					return fmt.Errorf("memory must exist for %s", VectorInstructionName(vecOpcode))
				}
				pc++
				align, _, read, err := readMemArg(pc, body)
				if err != nil {
					return err
				}
				pc += read - 1
				if 1<<align > 128/8 {
					return fmt.Errorf("invalid memory alignment %d for %s", align, OpcodeVecV128StoreName)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeVecV128StoreName, err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeVecV128StoreName, err)
				}
			case OpcodeVecV128Load8Lane, OpcodeVecV128Load16Lane, OpcodeVecV128Load32Lane, OpcodeVecV128Load64Lane:
				if memory == nil {
					return fmt.Errorf("memory must exist for %s", VectorInstructionName(vecOpcode))
				}
				attr := vecLoadLanes[vecOpcode]
				pc++
				align, _, read, err := readMemArg(pc, body)
				if err != nil {
					return err
				}
				if 1<<align > attr.alignMax {
					return fmt.Errorf("invalid memory alignment %d for %s", align, vectorInstructionName[vecOpcode])
				}
				pc += read
				if pc >= uint64(len(body)) {
					return fmt.Errorf("lane for %s not found", OpcodeVecV128Load64LaneName)
				}
				lane := body[pc]
				if lane >= attr.laneCeil {
					return fmt.Errorf("invalid lane index %d >= %d for %s", lane, attr.laneCeil, vectorInstructionName[vecOpcode])
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				valueTypeStack.push(ValueTypeV128)
			case OpcodeVecV128Store8Lane, OpcodeVecV128Store16Lane, OpcodeVecV128Store32Lane, OpcodeVecV128Store64Lane:
				if memory == nil {
					return fmt.Errorf("memory must exist for %s", VectorInstructionName(vecOpcode))
				}
				attr := vecStoreLanes[vecOpcode]
				pc++
				align, _, read, err := readMemArg(pc, body)
				if err != nil {
					return err
				}
				if 1<<align > attr.alignMax {
					return fmt.Errorf("invalid memory alignment %d for %s", align, vectorInstructionName[vecOpcode])
				}
				pc += read
				if pc >= uint64(len(body)) {
					return fmt.Errorf("lane for %s not found", vectorInstructionName[vecOpcode])
				}
				lane := body[pc]
				if lane >= attr.laneCeil {
					return fmt.Errorf("invalid lane index %d >= %d for %s", lane, attr.laneCeil, vectorInstructionName[vecOpcode])
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
			case OpcodeVecI8x16ExtractLaneS,
				OpcodeVecI8x16ExtractLaneU,
				OpcodeVecI16x8ExtractLaneS,
				OpcodeVecI16x8ExtractLaneU,
				OpcodeVecI32x4ExtractLane,
				OpcodeVecI64x2ExtractLane,
				OpcodeVecF32x4ExtractLane,
				OpcodeVecF64x2ExtractLane:
				pc++
				if pc >= uint64(len(body)) {
					return fmt.Errorf("lane for %s not found", vectorInstructionName[vecOpcode])
				}
				attr := vecExtractLanes[vecOpcode]
				lane := body[pc]
				if lane >= attr.laneCeil {
					return fmt.Errorf("invalid lane index %d >= %d for %s", lane, attr.laneCeil, vectorInstructionName[vecOpcode])
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				valueTypeStack.push(attr.resultType)
			case OpcodeVecI8x16ReplaceLane, OpcodeVecI16x8ReplaceLane, OpcodeVecI32x4ReplaceLane,
				OpcodeVecI64x2ReplaceLane, OpcodeVecF32x4ReplaceLane, OpcodeVecF64x2ReplaceLane:
				pc++
				if pc >= uint64(len(body)) {
					return fmt.Errorf("lane for %s not found", vectorInstructionName[vecOpcode])
				}
				attr := vecReplaceLanes[vecOpcode]
				lane := body[pc]
				if lane >= attr.laneCeil {
					return fmt.Errorf("invalid lane index %d >= %d for %s", lane, attr.laneCeil, vectorInstructionName[vecOpcode])
				}
				if err := valueTypeStack.popAndVerifyType(attr.paramType); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				valueTypeStack.push(ValueTypeV128)
			case OpcodeVecI8x16Splat, OpcodeVecI16x8Splat, OpcodeVecI32x4Splat,
				OpcodeVecI64x2Splat, OpcodeVecF32x4Splat, OpcodeVecF64x2Splat:
				tp := vecSplatValueTypes[vecOpcode]
				if err := valueTypeStack.popAndVerifyType(tp); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				valueTypeStack.push(ValueTypeV128)
			case OpcodeVecI8x16Swizzle, OpcodeVecV128And, OpcodeVecV128Or, OpcodeVecV128Xor, OpcodeVecV128AndNot:
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				valueTypeStack.push(ValueTypeV128)
			case OpcodeVecV128Bitselect:
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				valueTypeStack.push(ValueTypeV128)
			case OpcodeVecV128Not:
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				valueTypeStack.push(ValueTypeV128)
			case OpcodeVecV128i8x16Shuffle:
				pc++
				if pc+15 >= uint64(len(body)) {
					return fmt.Errorf("16 lane indexes for %s not found", vectorInstructionName[vecOpcode])
				}
				lanes := body[pc : pc+16]
				for i, l := range lanes {
					if l >= 32 {
						return fmt.Errorf("invalid lane index[%d] %d >= %d for %s", i, l, 32, vectorInstructionName[vecOpcode])
					}
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				valueTypeStack.push(ValueTypeV128)
				pc += 15
			case OpcodeVecI8x16Shl, OpcodeVecI8x16ShrS, OpcodeVecI8x16ShrU,
				OpcodeVecI16x8Shl, OpcodeVecI16x8ShrS, OpcodeVecI16x8ShrU,
				OpcodeVecI32x4Shl, OpcodeVecI32x4ShrS, OpcodeVecI32x4ShrU,
				OpcodeVecI64x2Shl, OpcodeVecI64x2ShrS, OpcodeVecI64x2ShrU:
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				valueTypeStack.push(ValueTypeV128)
			case OpcodeVecI8x16Eq, OpcodeVecI8x16Ne, OpcodeVecI8x16LtS, OpcodeVecI8x16LtU, OpcodeVecI8x16GtS,
				OpcodeVecI8x16GtU, OpcodeVecI8x16LeS, OpcodeVecI8x16LeU, OpcodeVecI8x16GeS, OpcodeVecI8x16GeU,
				OpcodeVecI16x8Eq, OpcodeVecI16x8Ne, OpcodeVecI16x8LtS, OpcodeVecI16x8LtU, OpcodeVecI16x8GtS,
				OpcodeVecI16x8GtU, OpcodeVecI16x8LeS, OpcodeVecI16x8LeU, OpcodeVecI16x8GeS, OpcodeVecI16x8GeU,
				OpcodeVecI32x4Eq, OpcodeVecI32x4Ne, OpcodeVecI32x4LtS, OpcodeVecI32x4LtU, OpcodeVecI32x4GtS,
				OpcodeVecI32x4GtU, OpcodeVecI32x4LeS, OpcodeVecI32x4LeU, OpcodeVecI32x4GeS, OpcodeVecI32x4GeU,
				OpcodeVecI64x2Eq, OpcodeVecI64x2Ne, OpcodeVecI64x2LtS, OpcodeVecI64x2GtS, OpcodeVecI64x2LeS,
				OpcodeVecI64x2GeS, OpcodeVecF32x4Eq, OpcodeVecF32x4Ne, OpcodeVecF32x4Lt, OpcodeVecF32x4Gt,
				OpcodeVecF32x4Le, OpcodeVecF32x4Ge, OpcodeVecF64x2Eq, OpcodeVecF64x2Ne, OpcodeVecF64x2Lt,
				OpcodeVecF64x2Gt, OpcodeVecF64x2Le, OpcodeVecF64x2Ge,
				OpcodeVecI32x4DotI16x8S,
				OpcodeVecI8x16NarrowI16x8S, OpcodeVecI8x16NarrowI16x8U, OpcodeVecI16x8NarrowI32x4S, OpcodeVecI16x8NarrowI32x4U:
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				valueTypeStack.push(ValueTypeV128)
			case OpcodeVecI8x16Neg, OpcodeVecI16x8Neg, OpcodeVecI32x4Neg, OpcodeVecI64x2Neg, OpcodeVecF32x4Neg, OpcodeVecF64x2Neg,
				OpcodeVecF32x4Sqrt, OpcodeVecF64x2Sqrt,
				OpcodeVecI8x16Abs, OpcodeVecI8x16Popcnt, OpcodeVecI16x8Abs, OpcodeVecI32x4Abs, OpcodeVecI64x2Abs,
				OpcodeVecF32x4Abs, OpcodeVecF64x2Abs,
				OpcodeVecF32x4Ceil, OpcodeVecF32x4Floor, OpcodeVecF32x4Trunc, OpcodeVecF32x4Nearest,
				OpcodeVecF64x2Ceil, OpcodeVecF64x2Floor, OpcodeVecF64x2Trunc, OpcodeVecF64x2Nearest,
				OpcodeVecI16x8ExtendLowI8x16S, OpcodeVecI16x8ExtendHighI8x16S, OpcodeVecI16x8ExtendLowI8x16U, OpcodeVecI16x8ExtendHighI8x16U,
				OpcodeVecI32x4ExtendLowI16x8S, OpcodeVecI32x4ExtendHighI16x8S, OpcodeVecI32x4ExtendLowI16x8U, OpcodeVecI32x4ExtendHighI16x8U,
				OpcodeVecI64x2ExtendLowI32x4S, OpcodeVecI64x2ExtendHighI32x4S, OpcodeVecI64x2ExtendLowI32x4U, OpcodeVecI64x2ExtendHighI32x4U,
				OpcodeVecI16x8ExtaddPairwiseI8x16S, OpcodeVecI16x8ExtaddPairwiseI8x16U,
				OpcodeVecI32x4ExtaddPairwiseI16x8S, OpcodeVecI32x4ExtaddPairwiseI16x8U,
				OpcodeVecF64x2PromoteLowF32x4Zero, OpcodeVecF32x4DemoteF64x2Zero,
				OpcodeVecF32x4ConvertI32x4S, OpcodeVecF32x4ConvertI32x4U,
				OpcodeVecF64x2ConvertLowI32x4S, OpcodeVecF64x2ConvertLowI32x4U,
				OpcodeVecI32x4TruncSatF32x4S, OpcodeVecI32x4TruncSatF32x4U, OpcodeVecI32x4TruncSatF64x2SZero, OpcodeVecI32x4TruncSatF64x2UZero:
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				valueTypeStack.push(ValueTypeV128)

			case OpcodeVecI8x16Add, OpcodeVecI8x16AddSatS, OpcodeVecI8x16AddSatU, OpcodeVecI8x16Sub, OpcodeVecI8x16SubSatS, OpcodeVecI8x16SubSatU,
				OpcodeVecI16x8Add, OpcodeVecI16x8AddSatS, OpcodeVecI16x8AddSatU, OpcodeVecI16x8Sub, OpcodeVecI16x8SubSatS, OpcodeVecI16x8SubSatU, OpcodeVecI16x8Mul,
				OpcodeVecI32x4Add, OpcodeVecI32x4Sub, OpcodeVecI32x4Mul,
				OpcodeVecI64x2Add, OpcodeVecI64x2Sub, OpcodeVecI64x2Mul,
				OpcodeVecF32x4Add, OpcodeVecF32x4Sub, OpcodeVecF32x4Mul, OpcodeVecF32x4Div,
				OpcodeVecF64x2Add, OpcodeVecF64x2Sub, OpcodeVecF64x2Mul, OpcodeVecF64x2Div,
				OpcodeVecI8x16MinS, OpcodeVecI8x16MinU, OpcodeVecI8x16MaxS, OpcodeVecI8x16MaxU,
				OpcodeVecI8x16AvgrU,
				OpcodeVecI16x8MinS, OpcodeVecI16x8MinU, OpcodeVecI16x8MaxS, OpcodeVecI16x8MaxU,
				OpcodeVecI16x8AvgrU,
				OpcodeVecI32x4MinS, OpcodeVecI32x4MinU, OpcodeVecI32x4MaxS, OpcodeVecI32x4MaxU,
				OpcodeVecF32x4Min, OpcodeVecF32x4Max, OpcodeVecF64x2Min, OpcodeVecF64x2Max,
				OpcodeVecF32x4Pmin, OpcodeVecF32x4Pmax, OpcodeVecF64x2Pmin, OpcodeVecF64x2Pmax,
				OpcodeVecI16x8Q15mulrSatS,
				OpcodeVecI16x8ExtMulLowI8x16S, OpcodeVecI16x8ExtMulHighI8x16S, OpcodeVecI16x8ExtMulLowI8x16U, OpcodeVecI16x8ExtMulHighI8x16U,
				OpcodeVecI32x4ExtMulLowI16x8S, OpcodeVecI32x4ExtMulHighI16x8S, OpcodeVecI32x4ExtMulLowI16x8U, OpcodeVecI32x4ExtMulHighI16x8U,
				OpcodeVecI64x2ExtMulLowI32x4S, OpcodeVecI64x2ExtMulHighI32x4S, OpcodeVecI64x2ExtMulLowI32x4U, OpcodeVecI64x2ExtMulHighI32x4U:
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
				}
				valueTypeStack.push(ValueTypeV128)
			default:
				return fmt.Errorf("unknown SIMD instruction %s", vectorInstructionName[vecOpcode])
			}
		} else if op == OpcodeBlock {
			br.Reset(body[pc+1:])
			bt, num, err := DecodeBlockType(m.TypeSection, br, enabledFeatures)
			if err != nil {
				return fmt.Errorf("read block: %w", err)
			}
			controlBlockStack.push(pc, 0, 0, bt, num, 0)
			controlBlockStack.stack[len(controlBlockStack.stack)-1].savedInitLocals = maps.Clone(sts.initLocals)
			if err = valueTypeStack.popParams(op, bt.Params, false); err != nil {
				return err
			}
			// Plus we have to push any block params again.
			for _, p := range bt.Params {
				valueTypeStack.push(p)
			}
			valueTypeStack.pushStackLimit(len(bt.Params))
			pc += num
		} else if op == OpcodeAtomicPrefix {
			pc++
			// Atomic instructions come with two bytes where the first byte is always OpcodeAtomicPrefix,
			// and the second byte determines the actual instruction.
			atomicOpcode := body[pc]
			if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesThreads); err != nil {
				return fmt.Errorf("%s invalid as %v", atomicInstructionName[atomicOpcode], err)
			}
			pc++

			if atomicOpcode == OpcodeAtomicFence {
				// No memory requirement and no arguments or return, however the immediate byte value must be 0.
				imm := body[pc]
				if imm != 0x0 {
					return fmt.Errorf("invalid immediate value for %s", AtomicInstructionName(atomicOpcode))
				}
				continue
			}

			// All atomic operations except fence (checked above) require memory
			if memory == nil {
				return fmt.Errorf("memory must exist for %s", AtomicInstructionName(atomicOpcode))
			}
			align, _, read, err := readMemArg(pc, body)
			if err != nil {
				return err
			}
			pc += read - 1
			switch atomicOpcode {
			case OpcodeAtomicMemoryNotify:
				if 1<<align > 32/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeAtomicMemoryWait32:
				if 1<<align > 32/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeAtomicMemoryWait64:
				if 1<<align > 64/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeAtomicI32Load:
				if 1<<align > 32/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeAtomicI64Load:
				if 1<<align > 64/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeAtomicI32Load8U:
				if 1<<align != 1 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeAtomicI32Load16U:
				if 1<<align != 16/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeAtomicI64Load8U:
				if 1<<align != 1 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeAtomicI64Load16U:
				if 1<<align > 16/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeAtomicI64Load32U:
				if 1<<align > 32/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeAtomicI32Store:
				if 1<<align > 32/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
			case OpcodeAtomicI64Store:
				if 1<<align > 64/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
			case OpcodeAtomicI32Store8:
				if 1<<align > 1 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
			case OpcodeAtomicI32Store16:
				if 1<<align > 16/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
			case OpcodeAtomicI64Store8:
				if 1<<align > 1 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
			case OpcodeAtomicI64Store16:
				if 1<<align > 16/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
			case OpcodeAtomicI64Store32:
				if 1<<align > 32/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
			case OpcodeAtomicI32RmwAdd, OpcodeAtomicI32RmwSub, OpcodeAtomicI32RmwAnd, OpcodeAtomicI32RmwOr, OpcodeAtomicI32RmwXor, OpcodeAtomicI32RmwXchg:
				if 1<<align > 32/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeAtomicI32Rmw8AddU, OpcodeAtomicI32Rmw8SubU, OpcodeAtomicI32Rmw8AndU, OpcodeAtomicI32Rmw8OrU, OpcodeAtomicI32Rmw8XorU, OpcodeAtomicI32Rmw8XchgU:
				if 1<<align > 1 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeAtomicI32Rmw16AddU, OpcodeAtomicI32Rmw16SubU, OpcodeAtomicI32Rmw16AndU, OpcodeAtomicI32Rmw16OrU, OpcodeAtomicI32Rmw16XorU, OpcodeAtomicI32Rmw16XchgU:
				if 1<<align > 16/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeAtomicI64RmwAdd, OpcodeAtomicI64RmwSub, OpcodeAtomicI64RmwAnd, OpcodeAtomicI64RmwOr, OpcodeAtomicI64RmwXor, OpcodeAtomicI64RmwXchg:
				if 1<<align > 64/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeAtomicI64Rmw8AddU, OpcodeAtomicI64Rmw8SubU, OpcodeAtomicI64Rmw8AndU, OpcodeAtomicI64Rmw8OrU, OpcodeAtomicI64Rmw8XorU, OpcodeAtomicI64Rmw8XchgU:
				if 1<<align > 1 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeAtomicI64Rmw16AddU, OpcodeAtomicI64Rmw16SubU, OpcodeAtomicI64Rmw16AndU, OpcodeAtomicI64Rmw16OrU, OpcodeAtomicI64Rmw16XorU, OpcodeAtomicI64Rmw16XchgU:
				if 1<<align > 16/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeAtomicI64Rmw32AddU, OpcodeAtomicI64Rmw32SubU, OpcodeAtomicI64Rmw32AndU, OpcodeAtomicI64Rmw32OrU, OpcodeAtomicI64Rmw32XorU, OpcodeAtomicI64Rmw32XchgU:
				if 1<<align > 32/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeAtomicI32RmwCmpxchg:
				if 1<<align > 32/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeAtomicI32Rmw8CmpxchgU:
				if 1<<align > 1 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeAtomicI32Rmw16CmpxchgU:
				if 1<<align > 16/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI32)
			case OpcodeAtomicI64RmwCmpxchg:
				if 1<<align > 64/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeAtomicI64Rmw8CmpxchgU:
				if 1<<align > 1 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeAtomicI64Rmw16CmpxchgU:
				if 1<<align > 16/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI64)
			case OpcodeAtomicI64Rmw32CmpxchgU:
				if 1<<align > 32/8 {
					return fmt.Errorf("invalid memory alignment")
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
					return err
				}
				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
					return err
				}
				valueTypeStack.push(ValueTypeI64)
			default:
				return fmt.Errorf("invalid atomic opcode: 0x%x", atomicOpcode)
			}
		} else if op == OpcodeLoop {
			br.Reset(body[pc+1:])
			bt, num, err := DecodeBlockType(m.TypeSection, br, enabledFeatures)
			if err != nil {
				return fmt.Errorf("read block: %w", err)
			}
			controlBlockStack.push(pc, 0, 0, bt, num, op)
			controlBlockStack.stack[len(controlBlockStack.stack)-1].savedInitLocals = maps.Clone(sts.initLocals)
			if err = valueTypeStack.popParams(op, bt.Params, false); err != nil {
				return err
			}
			// Plus we have to push any block params again.
			for _, p := range bt.Params {
				valueTypeStack.push(p)
			}
			valueTypeStack.pushStackLimit(len(bt.Params))
			pc += num
		} else if op == OpcodeIf {
			br.Reset(body[pc+1:])
			bt, num, err := DecodeBlockType(m.TypeSection, br, enabledFeatures)
			if err != nil {
				return fmt.Errorf("read block: %w", err)
			}
			controlBlockStack.push(pc, 0, 0, bt, num, op)
			controlBlockStack.stack[len(controlBlockStack.stack)-1].savedInitLocals = maps.Clone(sts.initLocals)
			if err = valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
				return fmt.Errorf("cannot pop the operand for 'if': %v", err)
			}
			if err = valueTypeStack.popParams(op, bt.Params, false); err != nil {
				return err
			}
			// Plus we have to push any block params again.
			for _, p := range bt.Params {
				valueTypeStack.push(p)
			}
			valueTypeStack.pushStackLimit(len(bt.Params))
			pc += num
		} else if op == OpcodeElse {
			bl := &controlBlockStack.stack[len(controlBlockStack.stack)-1]
			if bl.op != OpcodeIf {
				return fmt.Errorf("else instruction must be used in if block: %#x", pc)
			}
			bl.op = OpcodeElse
			bl.elseAt = pc
			// Check the type soundness of the instructions *before* entering this else Op.
			if err := valueTypeStack.popResults(OpcodeIf, bl.blockType.Results, true); err != nil {
				return err
			}
			// Restore init locals to the state at if-entry for the else branch.
			sts.initLocals = maps.Clone(bl.savedInitLocals)
			// Before entering instructions inside else, we pop all the values pushed by then block.
			valueTypeStack.resetAtStackLimit()
			// Plus we have to push any block params again.
			for _, p := range bl.blockType.Params {
				valueTypeStack.push(p)
			}
		} else if op == OpcodeEnd {
			bl := controlBlockStack.pop()
			bl.endAt = pc
			// Restore init locals to the state at block entry (skip for outermost function block).
			if len(controlBlockStack.stack) > 0 {
				sts.initLocals = bl.savedInitLocals
			}

			// OpcodeEnd can end a block or the function itself. Check to see what it is:

			ifMissingElse := bl.op == OpcodeIf && bl.elseAt <= bl.startAt
			if ifMissingElse {
				// If this is the end of block without else, the number of block's results and params must be same.
				// Otherwise, the value stack would result in the inconsistent state at runtime.
				if !slices.Equal(bl.blockType.Results, bl.blockType.Params) {
					return typeCountError(false, OpcodeElseName, bl.blockType.Params, bl.blockType.Results)
				}
				// -1 skips else, to handle if block without else properly.
				bl.elseAt = bl.endAt - 1
			}

			// Determine the block context
			ctx := "" // the outer-most block: the function return
			if bl.op == OpcodeIf && !ifMissingElse && bl.elseAt > 0 {
				ctx = OpcodeElseName
			} else if bl.op != 0 {
				ctx = InstructionName(bl.op)
			}

			// Check return types match
			if err := valueTypeStack.requireStackValues(false, ctx, bl.blockType.Results, true); err != nil {
				return err
			}

			// Put the result types at the end after resetting at the stack limit
			// since we might have Any type between the limit and the current top.
			valueTypeStack.resetAtStackLimit()
			for _, exp := range bl.blockType.Results {
				valueTypeStack.push(exp)
			}
			// We exit if/loop/block, so reset the constraints on the stack manipulation
			// on values previously pushed by outer blocks.
			valueTypeStack.popStackLimit()
		} else if op == OpcodeReturn {
			// Same formatting as OpcodeEnd on the outer-most block
			if err := valueTypeStack.requireStackValues(false, "", functionType.Results, false); err != nil {
				return err
			}
			// return instruction is stack-polymorphic.
			valueTypeStack.unreachable()
		} else if op == OpcodeDrop {
			_, err := valueTypeStack.pop()
			if err != nil {
				return fmt.Errorf("invalid drop: %v", err)
			}
		} else if op == OpcodeSelect || op == OpcodeTypedSelect {
			if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
				return fmt.Errorf("type mismatch on 3rd select operand: %v", err)
			}
			v1, err := valueTypeStack.pop()
			if err != nil {
				return fmt.Errorf("invalid select: %v", err)
			}
			v2, err := valueTypeStack.pop()
			if err != nil {
				return fmt.Errorf("invalid select: %v", err)
			}

			if op == OpcodeTypedSelect {
				if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
					return fmt.Errorf("%s is invalid as %w", InstructionName(op), err)
				}
				pc++
				if numTypeImmeidates := body[pc]; numTypeImmeidates != 1 {
					return fmt.Errorf("too many type immediates for %s", InstructionName(op))
				}
				pc++
				b := body[pc]
				switch b {
				case RefPrefixNullable, RefPrefixNonNullable:
					br.Reset(body[pc+1:])
					ht, num, err := leb128.DecodeInt33AsInt64(br)
					if err != nil {
						return fmt.Errorf("read heap type for %s: %v", OpcodeTypedSelectName, err)
					}
					pc += uint64(num)
					switch ht {
					case HeapTypeFunc:
						// ok
					case HeapTypeExtern:
						// ok
					case HeapTypeExn:
						// ok
					default:
						if ht < 0 {
							return fmt.Errorf("invalid heap type for %s: %d", OpcodeTypedSelectName, ht)
						}
						if int(ht) >= len(m.TypeSection) {
							return fmt.Errorf("unknown type for %s: type index %d out of range", OpcodeTypedSelectName, ht)
						}
					}
				default:
					tp := ValueType(b)
					if tp != ValueTypeI32 && tp != ValueTypeI64 && tp != ValueTypeF32 && tp != ValueTypeF64 &&
						tp != ValueTypeExternref && tp != ValueTypeFuncref && tp != ValueTypeV128 {
						return fmt.Errorf("invalid type %s for %s", ValueTypeName(tp), OpcodeTypedSelectName)
					}
				}
			} else if isReferenceValueType(v1) || isReferenceValueType(v2) {
				return fmt.Errorf("reference types cannot be used for non typed select instruction")
			}

			if v1 != valueTypeUnknown && v2 != valueTypeUnknown && !areRefTypesCompatible(v1, v2) {
				return fmt.Errorf("type mismatch on 1st and 2nd select operands")
			}
			if v1 == valueTypeUnknown {
				valueTypeStack.push(v2)
			} else if v2 == valueTypeUnknown {
				valueTypeStack.push(v1)
			} else if isRefSubtypeOf(v1, v2) {
				valueTypeStack.push(v2)
			} else {
				valueTypeStack.push(v1)
			}
		} else if op == OpcodeUnreachable {
			// unreachable instruction is stack-polymorphic.
			valueTypeStack.unreachable()
		} else if op == OpcodeNop {
		} else if enabledFeatures.IsEnabled(experimental.CoreFeaturesExceptionHandling) &&
			(op == OpcodeLegacyTry || op == OpcodeLegacyCatch || op == OpcodeLegacyRethrow ||
				op == OpcodeLegacyDelegate || op == OpcodeLegacyCatchAll) {
			return fmt.Errorf("legacy exception handling instruction 0x%x not supported; recompile with wasm-opt --translate-to-exnref", op)
		} else {
			return fmt.Errorf("invalid instruction 0x%x", op)
		}
	}

	if len(controlBlockStack.stack) > 0 {
		return fmt.Errorf("ill-nested block exists")
	}
	if valueTypeStack.maximumStackPointer > maxStackValues {
		return fmt.Errorf("function may have %d stack values, which exceeds limit %d", valueTypeStack.maximumStackPointer, maxStackValues)
	}
	return nil
}

var vecExtractLanes = [...]struct {
	laneCeil   byte
	resultType ValueType
}{
	OpcodeVecI8x16ExtractLaneS: {laneCeil: 16, resultType: ValueTypeI32},
	OpcodeVecI8x16ExtractLaneU: {laneCeil: 16, resultType: ValueTypeI32},
	OpcodeVecI16x8ExtractLaneS: {laneCeil: 8, resultType: ValueTypeI32},
	OpcodeVecI16x8ExtractLaneU: {laneCeil: 8, resultType: ValueTypeI32},
	OpcodeVecI32x4ExtractLane:  {laneCeil: 4, resultType: ValueTypeI32},
	OpcodeVecI64x2ExtractLane:  {laneCeil: 2, resultType: ValueTypeI64},
	OpcodeVecF32x4ExtractLane:  {laneCeil: 4, resultType: ValueTypeF32},
	OpcodeVecF64x2ExtractLane:  {laneCeil: 2, resultType: ValueTypeF64},
}

var vecReplaceLanes = [...]struct {
	laneCeil  byte
	paramType ValueType
}{
	OpcodeVecI8x16ReplaceLane: {laneCeil: 16, paramType: ValueTypeI32},
	OpcodeVecI16x8ReplaceLane: {laneCeil: 8, paramType: ValueTypeI32},
	OpcodeVecI32x4ReplaceLane: {laneCeil: 4, paramType: ValueTypeI32},
	OpcodeVecI64x2ReplaceLane: {laneCeil: 2, paramType: ValueTypeI64},
	OpcodeVecF32x4ReplaceLane: {laneCeil: 4, paramType: ValueTypeF32},
	OpcodeVecF64x2ReplaceLane: {laneCeil: 2, paramType: ValueTypeF64},
}

var vecStoreLanes = [...]struct {
	alignMax uint32
	laneCeil byte
}{
	OpcodeVecV128Store64Lane: {alignMax: 64 / 8, laneCeil: 128 / 64},
	OpcodeVecV128Store32Lane: {alignMax: 32 / 8, laneCeil: 128 / 32},
	OpcodeVecV128Store16Lane: {alignMax: 16 / 8, laneCeil: 128 / 16},
	OpcodeVecV128Store8Lane:  {alignMax: 1, laneCeil: 128 / 8},
}

var vecLoadLanes = [...]struct {
	alignMax uint32
	laneCeil byte
}{
	OpcodeVecV128Load64Lane: {alignMax: 64 / 8, laneCeil: 128 / 64},
	OpcodeVecV128Load32Lane: {alignMax: 32 / 8, laneCeil: 128 / 32},
	OpcodeVecV128Load16Lane: {alignMax: 16 / 8, laneCeil: 128 / 16},
	OpcodeVecV128Load8Lane:  {alignMax: 1, laneCeil: 128 / 8},
}

var vecSplatValueTypes = [...]ValueType{
	OpcodeVecI8x16Splat: ValueTypeI32,
	OpcodeVecI16x8Splat: ValueTypeI32,
	OpcodeVecI32x4Splat: ValueTypeI32,
	OpcodeVecI64x2Splat: ValueTypeI64,
	OpcodeVecF32x4Splat: ValueTypeF32,
	OpcodeVecF64x2Splat: ValueTypeF64,
}

type stacks struct {
	vs valueTypeStack
	cs controlBlockStack
	// ls is the label slice that is reused for each br_table instruction.
	ls []uint32
	// initLocals tracks which non-nullable ref locals have been initialized.
	initLocals map[uint32]struct{}
}

func (sts *stacks) reset(functionType *FunctionType) {
	// Reset valueStack for reuse.
	sts.vs.stack = sts.vs.stack[:0]
	sts.vs.stackLimits = sts.vs.stackLimits[:0]
	sts.vs.maximumStackPointer = 0
	sts.cs.stack = sts.cs.stack[:0]
	sts.cs.stack = append(sts.cs.stack, controlBlock{blockType: functionType})
	sts.ls = sts.ls[:0]
	clear(sts.initLocals)
	if sts.initLocals == nil {
		sts.initLocals = make(map[uint32]struct{})
	}
}

func (sts *stacks) validateCallSignature(opName string, funcType *FunctionType) error {
	for i := len(funcType.Params) - 1; i >= 0; i-- {
		if err := sts.vs.popAndVerifyType(funcType.Params[i]); err != nil {
			return fmt.Errorf("type mismatch on %s operation param type: %v", opName, err)
		}
	}
	for _, exp := range funcType.Results {
		sts.vs.push(exp)
	}
	return nil
}

func (sts *stacks) validateReturnCall(
	opName string,
	enabledFeatures api.CoreFeatures,
	calleeFuncType, callerFuncType *FunctionType,
) error {
	if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesTailCall); err != nil {
		return fmt.Errorf("%s invalid as %v", opName, err)
	}
	if len(calleeFuncType.Results) != len(callerFuncType.Results) {
		return fmt.Errorf("type mismatch on %s: caller returns %d values but callee returns %d",
			opName, len(callerFuncType.Results), len(calleeFuncType.Results))
	}
	return sts.vs.requireStackValues(false, "", callerFuncType.Results, false)
}

type controlBlockStack struct {
	stack []controlBlock
}

func (sts *stacks) requireLocalInit(index, inputLen uint32, localTypes []ValueType) error {
	if index >= inputLen {
		lt := localTypes[index-inputLen]
		if lt.IsRef() && !lt.IsNullable() {
			if _, ok := sts.initLocals[index]; !ok {
				return fmt.Errorf("uninitialized local %d", index)
			}
		}
	}
	return nil
}

func (sts *stacks) markLocalInit(index, inputLen uint32, localTypes []ValueType) {
	if index >= inputLen {
		lt := localTypes[index-inputLen]
		if lt.IsRef() && !lt.IsNullable() {
			sts.initLocals[index] = struct{}{}
		}
	}
}

func (s *controlBlockStack) pop() *controlBlock {
	tail := len(s.stack) - 1
	ret := &s.stack[tail]
	s.stack = s.stack[:tail]
	return ret
}

func (s *controlBlockStack) push(startAt, elseAt, endAt uint64, blockType *FunctionType, blockTypeBytes uint64, op Opcode) {
	s.stack = append(s.stack, controlBlock{
		startAt:        startAt,
		elseAt:         elseAt,
		endAt:          endAt,
		blockType:      blockType,
		blockTypeBytes: blockTypeBytes,
		op:             op,
	})
}

type valueTypeStack struct {
	stack               []ValueType
	stackLimits         []int
	maximumStackPointer int
	// requireStackValuesTmp is used in requireStackValues function to reduce the allocation.
	requireStackValuesTmp []ValueType
}

// Only used in the analyzeFunction below.
const valueTypeUnknown = ValueType(0xFF)

func (s *valueTypeStack) tryPop() (vt ValueType, limit int, ok bool) {
	if len(s.stackLimits) > 0 {
		limit = s.stackLimits[len(s.stackLimits)-1]
	}
	stackLen := len(s.stack)
	if stackLen <= limit {
		return
	} else if stackLen == limit+1 && s.stack[limit] == valueTypeUnknown {
		vt = valueTypeUnknown
		ok = true
		return
	} else {
		vt = s.stack[stackLen-1]
		s.stack = s.stack[:stackLen-1]
		ok = true
		return
	}
}

func (s *valueTypeStack) pop() (ValueType, error) {
	if vt, limit, ok := s.tryPop(); ok {
		return vt, nil
	} else {
		return 0, fmt.Errorf("invalid operation: trying to pop at %d with limit %d", len(s.stack), limit)
	}
}

// popAndVerifyType returns an error if the stack value is unexpected.
func (s *valueTypeStack) popAndVerifyType(expected ValueType) error {
	have, _, ok := s.tryPop()
	if !ok {
		return fmt.Errorf("%s missing", ValueTypeName(expected))
	}
	if have != valueTypeUnknown && expected != valueTypeUnknown && !isRefSubtypeOf(have, expected) {
		return fmt.Errorf("type mismatch: expected %s, but was %s", ValueTypeName(expected), ValueTypeName(have))
	}
	return nil
}

func (s *valueTypeStack) push(v ValueType) {
	s.stack = append(s.stack, v)
	if sp := len(s.stack); sp > s.maximumStackPointer {
		s.maximumStackPointer = sp
	}
}

func (s *valueTypeStack) unreachable() {
	s.resetAtStackLimit()
	s.stack = append(s.stack, valueTypeUnknown)
}

func (s *valueTypeStack) resetAtStackLimit() {
	if len(s.stackLimits) != 0 {
		s.stack = s.stack[:s.stackLimits[len(s.stackLimits)-1]]
	} else {
		s.stack = s.stack[:0]
	}
}

func (s *valueTypeStack) popStackLimit() {
	if len(s.stackLimits) != 0 {
		s.stackLimits = s.stackLimits[:len(s.stackLimits)-1]
	}
}

// pushStackLimit pushes the control frame's bottom of the stack.
func (s *valueTypeStack) pushStackLimit(params int) {
	limit := len(s.stack) - params
	s.stackLimits = append(s.stackLimits, limit)
}

func (s *valueTypeStack) popParams(oc Opcode, want []ValueType, checkAboveLimit bool) error {
	return s.requireStackValues(true, InstructionName(oc), want, checkAboveLimit)
}

func (s *valueTypeStack) popResults(oc Opcode, want []ValueType, checkAboveLimit bool) error {
	return s.requireStackValues(false, InstructionName(oc), want, checkAboveLimit)
}

func (s *valueTypeStack) requireStackValues(
	isParam bool,
	context string,
	want []ValueType,
	checkAboveLimit bool,
) error {
	limit := 0
	if len(s.stackLimits) > 0 {
		limit = s.stackLimits[len(s.stackLimits)-1]
	}
	// Iterate backwards as we are comparing the desired slice against stack value types.
	countWanted := len(want)

	// First, check if there are enough values on the stack.
	s.requireStackValuesTmp = s.requireStackValuesTmp[:0]
	for i := countWanted - 1; i >= 0; i-- {
		popped, _, ok := s.tryPop()
		if !ok {
			if len(s.requireStackValuesTmp) > len(want) {
				return typeCountError(isParam, context, s.requireStackValuesTmp, want)
			}
			return typeCountError(isParam, context, s.requireStackValuesTmp, want)
		}
		s.requireStackValuesTmp = append(s.requireStackValuesTmp, popped)
	}

	// Now, check if there are too many values.
	if checkAboveLimit {
		if !(limit == len(s.stack) || (limit+1 == len(s.stack) && s.stack[limit] == valueTypeUnknown)) {
			return typeCountError(isParam, context, append(s.stack, want...), want)
		}
	}

	// Finally, check the types of the values:
	for i, v := range s.requireStackValuesTmp {
		nextWant := want[countWanted-i-1] // have is in reverse order (stack)
		if v != valueTypeUnknown && nextWant != valueTypeUnknown && !isRefSubtypeOf(v, nextWant) {
			return typeMismatchError(isParam, context, v, nextWant, i)
		}
	}
	return nil
}

// typeMismatchError returns an error similar to go compiler's error on type mismatch.
func typeMismatchError(isParam bool, context string, have ValueType, want ValueType, i int) error {
	var ret strings.Builder
	ret.WriteString("cannot use ")
	ret.WriteString(ValueTypeName(have))
	if context != "" {
		ret.WriteString(" in ")
		ret.WriteString(context)
		ret.WriteString(" block")
	}
	if isParam {
		ret.WriteString(" as param")
	} else {
		ret.WriteString(" as result")
	}
	ret.WriteString("[")
	ret.WriteString(strconv.Itoa(i))
	ret.WriteString("] type ")
	ret.WriteString(ValueTypeName(want))
	return errors.New(ret.String())
}

// typeCountError returns an error similar to go compiler's error on type count mismatch.
func typeCountError(isParam bool, context string, have []ValueType, want []ValueType) error {
	var ret strings.Builder
	if len(have) > len(want) {
		ret.WriteString("too many ")
	} else {
		ret.WriteString("not enough ")
	}
	if isParam {
		ret.WriteString("params")
	} else {
		ret.WriteString("results")
	}
	if context != "" {
		if isParam {
			ret.WriteString(" for ")
		} else {
			ret.WriteString(" in ")
		}
		ret.WriteString(context)
		ret.WriteString(" block")
	}
	ret.WriteString("\n\thave (")
	writeValueTypes(have, &ret)
	ret.WriteString(")\n\twant (")
	writeValueTypes(want, &ret)
	ret.WriteByte(')')
	return errors.New(ret.String())
}

func writeValueTypes(vts []ValueType, ret *strings.Builder) {
	switch len(vts) {
	case 0:
	case 1:
		ret.WriteString(ValueTypeName(vts[0]))
	default:
		ret.WriteString(ValueTypeName(vts[0]))
		for _, vt := range vts[1:] {
			ret.WriteString(", ")
			ret.WriteString(ValueTypeName(vt))
		}
	}
}

func (s *valueTypeStack) String() string {
	var typeStrs, limits []string
	for _, v := range s.stack {
		var str string
		if v == valueTypeUnknown {
			str = "unknown"
		} else {
			str = ValueTypeName(v)
		}
		typeStrs = append(typeStrs, str)
	}
	for _, d := range s.stackLimits {
		limits = append(limits, fmt.Sprintf("%d", d))
	}
	return fmt.Sprintf("{stack: [%s], limits: [%s]}",
		strings.Join(typeStrs, ", "), strings.Join(limits, ","))
}

type controlBlock struct {
	startAt, elseAt, endAt uint64
	blockType              *FunctionType
	blockTypeBytes         uint64
	// op is zero when the outermost block
	op Opcode
	// savedInitLocals is the set of initialized locals at the time this block was entered.
	savedInitLocals map[uint32]struct{}
}

// DecodeBlockType decodes the type index from a positive 33-bit signed integer. Negative numbers indicate up to one
// WebAssembly 1.0 (20191205) compatible result type. Positive numbers are decoded when `enabledFeatures` include
// CoreFeatureMultiValue and include an index in the Module.TypeSection.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-blocktype
// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/multi-value/Overview.md
func DecodeBlockType(types []FunctionType, r *bytes.Reader, enabledFeatures api.CoreFeatures) (*FunctionType, uint64, error) {
	raw, num, err := leb128.DecodeInt33AsInt64(r)
	if err != nil {
		return nil, 0, fmt.Errorf("decode int33: %w", err)
	}

	var ret *FunctionType
	switch raw {
	case -64: // 0x40 in original byte = nil
		ret = blockType_v_v
	case -1: // 0x7f in original byte = i32
		ret = blockType_v_i32
	case -2: // 0x7e in original byte = i64
		ret = blockType_v_i64
	case -3: // 0x7d in original byte = f32
		ret = blockType_v_f32
	case -4: // 0x7c in original byte = f64
		ret = blockType_v_f64
	case -5: // 0x7b in original byte = v128
		ret = blockType_v_v128
	case -16: // 0x70 in original byte = funcref
		ret = blockType_v_funcref
	case -17: // 0x6f in original byte = externref
		ret = blockType_v_externref
	case -23: // 0x69 in original byte = exnref
		ret = blockType_v_exnref
	case -29: // 0x63 = ref null (nullable)
		ht, htNum, err := leb128.DecodeInt33AsInt64(r)
		if err != nil {
			return nil, 0, fmt.Errorf("read ref heap type in block: %w", err)
		}
		num += htNum
		switch ht {
		case HeapTypeExn:
			ret = blockType_v_exnref
		case HeapTypeFunc:
			ret = blockType_v_funcref
		case HeapTypeExtern:
			ret = blockType_v_externref
		default:
			if ht < 0 {
				return nil, 0, fmt.Errorf("unknown abstract heap type in block: %d", ht)
			}
			if int64(len(types)) <= ht {
				return nil, 0, fmt.Errorf("unknown type")
			}
			vt := ValueTypeConcreteRef(uint32(ht), true)
			ret = &FunctionType{Results: []ValueType{vt}, ResultNumInUint64: 1}
		}
	case -28: // 0x64 = ref (non-nullable)
		ht, htNum, err := leb128.DecodeInt33AsInt64(r)
		if err != nil {
			return nil, 0, fmt.Errorf("read ref heap type in block: %w", err)
		}
		num += htNum
		switch ht {
		case HeapTypeExn:
			ret = &FunctionType{Results: []ValueType{ValueTypeExnref.AsNonNullable()}, ResultNumInUint64: 1}
		case HeapTypeFunc:
			ret = &FunctionType{Results: []ValueType{ValueTypeFuncref.AsNonNullable()}, ResultNumInUint64: 1}
		case HeapTypeExtern:
			ret = &FunctionType{Results: []ValueType{ValueTypeExternref.AsNonNullable()}, ResultNumInUint64: 1}
		default:
			if ht < 0 {
				return nil, 0, fmt.Errorf("unknown abstract heap type in block: %d", ht)
			}
			if int64(len(types)) <= ht {
				return nil, 0, fmt.Errorf("unknown type")
			}
			vt := ValueTypeConcreteRef(uint32(ht), false)
			ret = &FunctionType{Results: []ValueType{vt}, ResultNumInUint64: 1}
		}
	default:
		if err = enabledFeatures.RequireEnabled(api.CoreFeatureMultiValue); err != nil {
			return nil, num, fmt.Errorf("block with function type return invalid as %v", err)
		}
		if raw < 0 || (raw >= int64(len(types))) {
			return nil, 0, fmt.Errorf("type index out of range: %d", raw)
		}
		ret = &types[raw]
	}
	return ret, num, err
}

// These block types are defined as globals in order to avoid allocations in DecodeBlockType.
var (
	blockType_v_v         = &FunctionType{}
	blockType_v_i32       = &FunctionType{Results: []ValueType{ValueTypeI32}, ResultNumInUint64: 1}
	blockType_v_i64       = &FunctionType{Results: []ValueType{ValueTypeI64}, ResultNumInUint64: 1}
	blockType_v_f32       = &FunctionType{Results: []ValueType{ValueTypeF32}, ResultNumInUint64: 1}
	blockType_v_f64       = &FunctionType{Results: []ValueType{ValueTypeF64}, ResultNumInUint64: 1}
	blockType_v_v128      = &FunctionType{Results: []ValueType{ValueTypeV128}, ResultNumInUint64: 2}
	blockType_v_funcref   = &FunctionType{Results: []ValueType{ValueTypeFuncref}, ResultNumInUint64: 1}
	blockType_v_externref = &FunctionType{Results: []ValueType{ValueTypeExternref}, ResultNumInUint64: 1}
	blockType_v_exnref    = &FunctionType{Results: []ValueType{ValueTypeExnref}, ResultNumInUint64: 1}
)

// SplitCallStack returns the input stack resliced to the count of params and
// results, or errors if it isn't long enough for either.
func SplitCallStack(ft *FunctionType, stack []uint64) (params []uint64, results []uint64, err error) {
	stackLen := len(stack)
	if n := ft.ParamNumInUint64; n > stackLen {
		return nil, nil, fmt.Errorf("need %d params, but stack size is %d", n, stackLen)
	} else if n > 0 {
		params = stack[:n]
	}
	if n := ft.ResultNumInUint64; n > stackLen {
		return nil, nil, fmt.Errorf("need %d results, but stack size is %d", n, stackLen)
	} else if n > 0 {
		results = stack[:n]
	}
	return
}
