package wasm

import (
	"bytes"
	"fmt"
	"testing"

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

func TestModule_ValidateFunction_validateFunctionWithMaxStackValues(t *testing.T) {
	const max = 100
	const valuesNum = max + 1

	// Compile a function which has max+1 const instructions.
	var body []byte
	for i := 0; i < valuesNum; i++ {
		body = append(body, OpcodeI32Const, 1)
	}

	// Drop all the consts so that if the max is higher, this function body would be sound.
	for i := 0; i < valuesNum; i++ {
		body = append(body, OpcodeDrop)
	}

	// Plus all functions must end with End opcode.
	body = append(body, OpcodeEnd)

	m := &Module{
		TypeSection:     []FunctionType{v_v},
		FunctionSection: []Index{0},
		CodeSection:     []Code{{Body: body}},
	}

	t.Run("not exceed", func(t *testing.T) {
		err := m.validateFunctionWithMaxStackValues(&stacks{}, api.CoreFeaturesV1,
			0, []Index{0}, nil, nil, nil, nil, max+1, nil, bytes.NewReader(nil))
		require.NoError(t, err)
	})
	t.Run("exceed", func(t *testing.T) {
		err := m.validateFunctionWithMaxStackValues(&stacks{}, api.CoreFeaturesV1,
			0, []Index{0}, nil, nil, nil, nil, max, nil, bytes.NewReader(nil))
		require.Error(t, err)
		expMsg := fmt.Sprintf("function may have %d stack values, which exceeds limit %d", valuesNum, max)
		require.Equal(t, expMsg, err.Error())
	})
}

func TestModule_ValidateFunction_SignExtensionOps(t *testing.T) {
	tests := []struct {
		input                Opcode
		expectedErrOnDisable string
	}{
		{
			input:                OpcodeI32Extend8S,
			expectedErrOnDisable: "i32.extend8_s invalid as feature \"sign-extension-ops\" is disabled",
		},
		{
			input:                OpcodeI32Extend16S,
			expectedErrOnDisable: "i32.extend16_s invalid as feature \"sign-extension-ops\" is disabled",
		},
		{
			input:                OpcodeI64Extend8S,
			expectedErrOnDisable: "i64.extend8_s invalid as feature \"sign-extension-ops\" is disabled",
		},
		{
			input:                OpcodeI64Extend16S,
			expectedErrOnDisable: "i64.extend16_s invalid as feature \"sign-extension-ops\" is disabled",
		},
		{
			input:                OpcodeI64Extend32S,
			expectedErrOnDisable: "i64.extend32_s invalid as feature \"sign-extension-ops\" is disabled",
		},
	}

	for _, tt := range tests {
		tc := tt
		t.Run(InstructionName(tc.input), func(t *testing.T) {
			t.Run("disabled", func(t *testing.T) {
				m := &Module{
					TypeSection:     []FunctionType{v_v},
					FunctionSection: []Index{0},
					CodeSection:     []Code{{Body: []byte{tc.input}}},
				}
				err := m.validateFunction(&stacks{}, api.CoreFeaturesV1,
					0, []Index{0}, nil, nil, nil, nil, nil,
					bytes.NewReader(nil))
				require.EqualError(t, err, tc.expectedErrOnDisable)
			})
			t.Run("enabled", func(t *testing.T) {
				is32bit := tc.input == OpcodeI32Extend8S || tc.input == OpcodeI32Extend16S
				var body []byte
				if is32bit {
					body = append(body, OpcodeI32Const)
				} else {
					body = append(body, OpcodeI64Const)
				}
				body = append(body, tc.input, 123, OpcodeDrop, OpcodeEnd)
				m := &Module{
					TypeSection:     []FunctionType{v_v},
					FunctionSection: []Index{0},
					CodeSection:     []Code{{Body: body}},
				}
				err := m.validateFunction(&stacks{}, api.CoreFeatureSignExtensionOps,
					0, []Index{0}, nil, nil, nil, nil,
					nil, bytes.NewReader(nil))
				require.NoError(t, err)
			})
		})
	}
}

func TestModule_ValidateFunction_NonTrappingFloatToIntConversion(t *testing.T) {
	tests := []struct {
		input                Opcode
		expectedErrOnDisable string
	}{
		{
			input:                OpcodeMiscI32TruncSatF32S,
			expectedErrOnDisable: "i32.trunc_sat_f32_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
		},
		{
			input:                OpcodeMiscI32TruncSatF32U,
			expectedErrOnDisable: "i32.trunc_sat_f32_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
		},
		{
			input:                OpcodeMiscI32TruncSatF64S,
			expectedErrOnDisable: "i32.trunc_sat_f64_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
		},
		{
			input:                OpcodeMiscI32TruncSatF64U,
			expectedErrOnDisable: "i32.trunc_sat_f64_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
		},
		{
			input:                OpcodeMiscI64TruncSatF32S,
			expectedErrOnDisable: "i64.trunc_sat_f32_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
		},
		{
			input:                OpcodeMiscI64TruncSatF32U,
			expectedErrOnDisable: "i64.trunc_sat_f32_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
		},
		{
			input:                OpcodeMiscI64TruncSatF64S,
			expectedErrOnDisable: "i64.trunc_sat_f64_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
		},
		{
			input:                OpcodeMiscI64TruncSatF64U,
			expectedErrOnDisable: "i64.trunc_sat_f64_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
		},
	}

	for _, tt := range tests {
		tc := tt
		t.Run(InstructionName(tc.input), func(t *testing.T) {
			t.Run("disabled", func(t *testing.T) {
				m := &Module{
					TypeSection:     []FunctionType{v_v},
					FunctionSection: []Index{0},
					CodeSection:     []Code{{Body: []byte{OpcodeMiscPrefix, tc.input}}},
				}
				err := m.validateFunction(&stacks{}, api.CoreFeaturesV1,
					0, []Index{0}, nil, nil, nil, nil, nil, bytes.NewReader(nil))
				require.EqualError(t, err, tc.expectedErrOnDisable)
			})
			t.Run("enabled", func(t *testing.T) {
				var body []byte
				switch tc.input {
				case OpcodeMiscI32TruncSatF32S, OpcodeMiscI32TruncSatF32U, OpcodeMiscI64TruncSatF32S, OpcodeMiscI64TruncSatF32U:
					body = []byte{OpcodeF32Const, 1, 2, 3, 4}
				case OpcodeMiscI32TruncSatF64S, OpcodeMiscI32TruncSatF64U, OpcodeMiscI64TruncSatF64S, OpcodeMiscI64TruncSatF64U:
					body = []byte{OpcodeF64Const, 1, 2, 3, 4, 5, 6, 7, 8}
				}
				body = append(body, OpcodeMiscPrefix, tc.input, OpcodeDrop, OpcodeEnd)

				m := &Module{
					TypeSection:     []FunctionType{v_v},
					FunctionSection: []Index{0},
					CodeSection:     []Code{{Body: body}},
				}
				err := m.validateFunction(&stacks{}, api.CoreFeatureNonTrappingFloatToIntConversion,
					0, []Index{0}, nil, nil, nil, nil, nil, bytes.NewReader(nil))
				require.NoError(t, err)
			})
		})
	}
}

// TestModule_ValidateFunction_MultiValue only tests what can't yet be detected during compilation. These examples are
// from test/core/if.wast from the commit that added "multi-value" support.
//
// See https://github.com/WebAssembly/spec/commit/484180ba3d9d7638ba1cb400b699ffede796927c
func TestModule_ValidateFunction_MultiValue(t *testing.T) {
	tests := []struct {
		name                 string
		module               *Module
		expectedErrOnDisable string
	}{
		{
			name: "block with function type",
			module: &Module{
				TypeSection:     []FunctionType{v_f64f64},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeBlock, 0, // (block (result f64 f64)
					OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0x10, 0x40, // (f64.const 4)
					OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0x14, 0x40, // (f64.const 5)
					OpcodeBr, 0,
					OpcodeF64Add,
					OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0x18, 0x40, // (f64.const 6)
					OpcodeEnd,
					OpcodeEnd,
				}}},
			},
			expectedErrOnDisable: "read block: block with function type return invalid as feature \"multi-value\" is disabled",
		},
		{
			name: "if with function type", // a.k.a. "param"
			module: &Module{
				TypeSection:     []FunctionType{i32_i32}, // (func (param i32) (result i32)
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, // (i32.const 1)
					OpcodeLocalGet, 0, OpcodeIf, 0, // (if (param i32) (result i32) (local.get 0)
					OpcodeI32Const, 2, OpcodeI32Add, // (then (i32.const 2) (i32.add))
					OpcodeElse, OpcodeI32Const, 0x7e, OpcodeI32Add, // (else (i32.const -2) (i32.add))
					OpcodeEnd, // )
					OpcodeEnd, // )
				}}},
			},
			expectedErrOnDisable: "read block: block with function type return invalid as feature \"multi-value\" is disabled",
		},
		{
			name: "if with function type - br", // a.k.a. "params-break"
			module: &Module{
				TypeSection: []FunctionType{
					i32_i32,    // (func (param i32) (result i32)
					i32i32_i32, // (if (param i32 i32) (result i32)
				},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, // (i32.const 1)
					OpcodeI32Const, 2, // (i32.const 2)
					OpcodeLocalGet, 0, OpcodeIf, 1, // (if (param i32) (result i32) (local.get 0)
					OpcodeI32Add, OpcodeBr, 0, // (then (i32.add) (br 0))
					OpcodeElse, OpcodeI32Sub, OpcodeBr, 0, // (else (i32.sub) (br 0))
					OpcodeEnd, // )
					OpcodeEnd, // )
				}}},
			},
			expectedErrOnDisable: "read block: block with function type return invalid as feature \"multi-value\" is disabled",
		},
	}

	for _, tt := range tests {
		tc := tt
		t.Run(tc.name, func(t *testing.T) {
			t.Run("disabled", func(t *testing.T) {
				err := tc.module.validateFunction(&stacks{}, api.CoreFeaturesV1,
					0, []Index{0}, nil, nil, nil, nil, nil, bytes.NewReader(nil))
				require.EqualError(t, err, tc.expectedErrOnDisable)
			})
			t.Run("enabled", func(t *testing.T) {
				err := tc.module.validateFunction(&stacks{}, api.CoreFeatureMultiValue,
					0, []Index{0}, nil, nil, nil, nil, nil, bytes.NewReader(nil))
				require.NoError(t, err)
			})
		})
	}
}

func TestModule_ValidateFunction_BulkMemoryOperations(t *testing.T) {
	t.Run("ok", func(t *testing.T) {
		for _, op := range []OpcodeMisc{
			OpcodeMiscMemoryInit, OpcodeMiscDataDrop, OpcodeMiscMemoryCopy,
			OpcodeMiscMemoryFill, OpcodeMiscTableInit, OpcodeMiscElemDrop, OpcodeMiscTableCopy,
		} {
			t.Run(MiscInstructionName(op), func(t *testing.T) {
				var body []byte
				if op != OpcodeMiscDataDrop && op != OpcodeMiscElemDrop {
					body = append(body, OpcodeI32Const, 1, OpcodeI32Const, 2, OpcodeI32Const, 3)
				}

				body = append(body, OpcodeMiscPrefix, op)
				if op != OpcodeMiscDataDrop && op != OpcodeMiscMemoryFill && op != OpcodeMiscElemDrop {
					body = append(body, 0, 0)
				} else {
					body = append(body, 0)
				}

				body = append(body, OpcodeEnd)

				c := uint32(0)
				m := &Module{
					TypeSection:      []FunctionType{v_v},
					FunctionSection:  []Index{0},
					CodeSection:      []Code{{Body: body}},
					DataSection:      []DataSegment{{}},
					ElementSection:   []ElementSegment{{}},
					DataCountSection: &c,
				}
				err := m.validateFunction(&stacks{}, api.CoreFeatureBulkMemoryOperations,
					0, []Index{0}, nil, &Memory{}, []Table{{}, {}}, nil, nil, bytes.NewReader(nil))
				require.NoError(t, err)
			})
		}
	})
	t.Run("errors", func(t *testing.T) {
		tests := []struct {
			body                []byte
			dataSection         []DataSegment
			elementSection      []ElementSegment
			dataCountSectionNil bool
			memory              *Memory
			tables              []Table
			flag                api.CoreFeatures
			expectedErr         string
		}{
			// memory.init
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      nil,
				expectedErr: "memory must exist for memory.init",
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit},
				flag:        api.CoreFeaturesV1,
				expectedErr: `memory.init invalid as feature "bulk-memory-operations" is disabled`,
			},
			{
				body:                []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit},
				flag:                api.CoreFeatureBulkMemoryOperations,
				dataCountSectionNil: true,
				expectedErr:         `memory must exist for memory.init`,
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				expectedErr: "failed to read data segment index for memory.init: EOF",
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit, 100 /* data section out of range */},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				dataSection: []DataSegment{{}},
				expectedErr: "index 100 out of range of data section(len=1)",
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				dataSection: []DataSegment{{}},
				expectedErr: "failed to read memory index for memory.init: EOF",
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0, 1},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				dataSection: []DataSegment{{}},
				expectedErr: "memory.init reserved byte must be zero encoded with 1 byte",
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0, 0},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				dataSection: []DataSegment{{}},
				expectedErr: "cannot pop the operand for memory.init: i32 missing",
			},
			{
				body:        []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0, 0},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				dataSection: []DataSegment{{}},
				expectedErr: "cannot pop the operand for memory.init: i32 missing",
			},
			{
				body:        []byte{OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0, 0},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				dataSection: []DataSegment{{}},
				expectedErr: "cannot pop the operand for memory.init: i32 missing",
			},
			// data.drop
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscDataDrop},
				flag:        api.CoreFeaturesV1,
				expectedErr: `data.drop invalid as feature "bulk-memory-operations" is disabled`,
			},
			{
				body:                []byte{OpcodeMiscPrefix, OpcodeMiscDataDrop},
				dataCountSectionNil: true,
				memory:              &Memory{},
				flag:                api.CoreFeatureBulkMemoryOperations,
				expectedErr:         `data.drop requires data count section`,
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscDataDrop},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				expectedErr: "failed to read data segment index for data.drop: EOF",
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscDataDrop, 100 /* data section out of range */},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				dataSection: []DataSegment{{}},
				expectedErr: "index 100 out of range of data section(len=1)",
			},
			// memory.copy
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      nil,
				expectedErr: "memory must exist for memory.copy",
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy},
				flag:        api.CoreFeaturesV1,
				expectedErr: `memory.copy invalid as feature "bulk-memory-operations" is disabled`,
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				expectedErr: `failed to read memory index for memory.copy: EOF`,
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				expectedErr: "failed to read memory index for memory.copy: EOF",
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0, 1},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				expectedErr: "memory.copy reserved byte must be zero encoded with 1 byte",
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0, 0},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				expectedErr: "cannot pop the operand for memory.copy: i32 missing",
			},
			{
				body:        []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0, 0},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				expectedErr: "cannot pop the operand for memory.copy: i32 missing",
			},
			{
				body:        []byte{OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0, 0},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				expectedErr: "cannot pop the operand for memory.copy: i32 missing",
			},
			// memory.fill
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      nil,
				expectedErr: "memory must exist for memory.fill",
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill},
				flag:        api.CoreFeaturesV1,
				expectedErr: `memory.fill invalid as feature "bulk-memory-operations" is disabled`,
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				expectedErr: `failed to read memory index for memory.fill: EOF`,
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill, 1},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				expectedErr: `memory.fill reserved byte must be zero encoded with 1 byte`,
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill, 0},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				expectedErr: "cannot pop the operand for memory.fill: i32 missing",
			},
			{
				body:        []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryFill, 0},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				expectedErr: "cannot pop the operand for memory.fill: i32 missing",
			},
			{
				body:        []byte{OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryFill, 0},
				flag:        api.CoreFeatureBulkMemoryOperations,
				memory:      &Memory{},
				expectedErr: "cannot pop the operand for memory.fill: i32 missing",
			},
			// table.init
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscTableInit},
				flag:        api.CoreFeaturesV1,
				tables:      []Table{{}},
				expectedErr: `table.init invalid as feature "bulk-memory-operations" is disabled`,
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscTableInit},
				flag:        api.CoreFeatureBulkMemoryOperations,
				tables:      []Table{{}},
				expectedErr: "failed to read element segment index for table.init: EOF",
			},
			{
				body:           []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 100 /* data section out of range */},
				flag:           api.CoreFeatureBulkMemoryOperations,
				tables:         []Table{{}},
				elementSection: []ElementSegment{{}},
				expectedErr:    "index 100 out of range of element section(len=1)",
			},
			{
				body:           []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 0},
				flag:           api.CoreFeatureBulkMemoryOperations,
				tables:         []Table{{}},
				elementSection: []ElementSegment{{}},
				expectedErr:    "failed to read source table index for table.init: EOF",
			},
			{
				body:           []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 10},
				flag:           api.CoreFeatureBulkMemoryOperations,
				tables:         []Table{{}},
				elementSection: []ElementSegment{{}},
				expectedErr:    "source table index must be zero for table.init as feature \"reference-types\" is disabled",
			},
			{
				body:           []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 10},
				flag:           api.CoreFeatureBulkMemoryOperations | api.CoreFeatureReferenceTypes,
				tables:         []Table{{}},
				elementSection: []ElementSegment{{}},
				expectedErr:    "table of index 10 not found",
			},
			{
				body:           []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 1},
				flag:           api.CoreFeatureBulkMemoryOperations | api.CoreFeatureReferenceTypes,
				tables:         []Table{{}, {Type: RefTypeExternref}},
				elementSection: []ElementSegment{{Type: RefTypeFuncref}},
				expectedErr:    "type mismatch for table.init: element type funcref does not match table type externref",
			},
			{
				body:           []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 0},
				flag:           api.CoreFeatureBulkMemoryOperations,
				tables:         []Table{{}},
				elementSection: []ElementSegment{{}},
				expectedErr:    "cannot pop the operand for table.init: i32 missing",
			},
			{
				body:           []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 0},
				flag:           api.CoreFeatureBulkMemoryOperations,
				tables:         []Table{{}},
				elementSection: []ElementSegment{{}},
				expectedErr:    "cannot pop the operand for table.init: i32 missing",
			},
			{
				body:           []byte{OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 0},
				flag:           api.CoreFeatureBulkMemoryOperations,
				tables:         []Table{{}},
				elementSection: []ElementSegment{{}},
				expectedErr:    "cannot pop the operand for table.init: i32 missing",
			},
			// elem.drop
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscElemDrop},
				flag:        api.CoreFeaturesV1,
				tables:      []Table{{}},
				expectedErr: `elem.drop invalid as feature "bulk-memory-operations" is disabled`,
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscElemDrop},
				flag:        api.CoreFeatureBulkMemoryOperations,
				tables:      []Table{{}},
				expectedErr: "failed to read element segment index for elem.drop: EOF",
			},
			{
				body:           []byte{OpcodeMiscPrefix, OpcodeMiscElemDrop, 100 /* element section out of range */},
				flag:           api.CoreFeatureBulkMemoryOperations,
				tables:         []Table{{}},
				elementSection: []ElementSegment{{}},
				expectedErr:    "index 100 out of range of element section(len=1)",
			},
			// table.copy
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy},
				flag:        api.CoreFeaturesV1,
				tables:      []Table{{}},
				expectedErr: `table.copy invalid as feature "bulk-memory-operations" is disabled`,
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy},
				flag:        api.CoreFeatureBulkMemoryOperations,
				tables:      []Table{{}},
				expectedErr: `failed to read destination table index for table.copy: EOF`,
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 10},
				flag:        api.CoreFeatureBulkMemoryOperations,
				tables:      []Table{{}},
				expectedErr: "destination table index must be zero for table.copy as feature \"reference-types\" is disabled",
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 3},
				flag:        api.CoreFeatureBulkMemoryOperations | api.CoreFeatureReferenceTypes,
				tables:      []Table{{}, {}},
				expectedErr: "table of index 3 not found",
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 3},
				flag:        api.CoreFeatureBulkMemoryOperations | api.CoreFeatureReferenceTypes,
				tables:      []Table{{}, {}, {}, {}},
				expectedErr: "failed to read source table index for table.copy: EOF",
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 0, 3},
				flag:        api.CoreFeatureBulkMemoryOperations, // Multiple tables require api.CoreFeatureReferenceTypes.
				tables:      []Table{{}, {}, {}, {}},
				expectedErr: "source table index must be zero for table.copy as feature \"reference-types\" is disabled",
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 3, 1},
				flag:        api.CoreFeatureBulkMemoryOperations | api.CoreFeatureReferenceTypes,
				tables:      []Table{{}, {Type: RefTypeFuncref}, {}, {Type: RefTypeExternref}},
				expectedErr: "table type mismatch for table.copy: funcref (src) != externref (dst)",
			},
			{
				body:        []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 0, 0},
				flag:        api.CoreFeatureBulkMemoryOperations,
				tables:      []Table{{}},
				expectedErr: "cannot pop the operand for table.copy: i32 missing",
			},
			{
				body:        []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscTableCopy, 0, 0},
				flag:        api.CoreFeatureBulkMemoryOperations,
				tables:      []Table{{}},
				expectedErr: "cannot pop the operand for table.copy: i32 missing",
			},
			{
				body:        []byte{OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscTableCopy, 0, 0},
				flag:        api.CoreFeatureBulkMemoryOperations,
				tables:      []Table{{}},
				expectedErr: "cannot pop the operand for table.copy: i32 missing",
			},
		}

		for _, tt := range tests {
			tc := tt
			t.Run(tc.expectedErr, func(t *testing.T) {
				m := &Module{
					TypeSection:     []FunctionType{v_v},
					FunctionSection: []Index{0},
					CodeSection:     []Code{{Body: tc.body}},
					ElementSection:  tc.elementSection,
					DataSection:     tc.dataSection,
				}
				if !tc.dataCountSectionNil {
					c := uint32(0)
					m.DataCountSection = &c
				}
				err := m.validateFunction(&stacks{}, tc.flag, 0, []Index{0}, nil, tc.memory, tc.tables, nil, nil, bytes.NewReader(nil))
				require.EqualError(t, err, tc.expectedErr)
			})
		}
	})
}

var (
	f32, f64, i32, i64, v128, externref = ValueTypeF32, ValueTypeF64, ValueTypeI32, ValueTypeI64, ValueTypeV128, ValueTypeExternref
	f32i32_v                            = initFt([]ValueType{f32, i32}, nil)
	f64f32_i64                          = initFt([]ValueType{f64, f32}, []ValueType{i64})
	f64i32_v128i64                      = initFt([]ValueType{f64, i32}, []ValueType{v128, i64})
	i32_i32                             = initFt([]ValueType{i32}, []ValueType{i32})
	i32f64_v                            = initFt([]ValueType{i32, f64}, nil)
	i32i32_i32                          = initFt([]ValueType{i32, i32}, []ValueType{i32})
	i32_v                               = initFt([]ValueType{i32}, nil)
	v_v                                 = FunctionType{}
	v_f32                               = initFt(nil, []ValueType{f32})
	v_f32f32                            = initFt(nil, []ValueType{f32, f32})
	v_f64i32                            = initFt(nil, []ValueType{f64, i32})
	v_f64f64                            = initFt(nil, []ValueType{f64, f64})
	v_i32                               = initFt(nil, []ValueType{i32})
	v_i32i32                            = initFt(nil, []ValueType{i32, i32})
	v_i32i64                            = initFt(nil, []ValueType{i32, i64})
	v_i64i64                            = initFt(nil, []ValueType{i64, i64})
)

func initFt(params, results []ValueType) FunctionType {
	ft := FunctionType{Params: params, Results: results}
	ft.CacheNumInUint64()
	return ft
}

// TestModule_ValidateFunction_MultiValue_TypeMismatch are "type mismatch" tests when "multi-value" was merged.
//
// See https://github.com/WebAssembly/spec/commit/484180ba3d9d7638ba1cb400b699ffede796927c
func TestModule_ValidateFunction_MultiValue_TypeMismatch(t *testing.T) {
	tests := []struct {
		name            string
		module          *Module
		expectedErr     string
		enabledFeatures api.CoreFeatures
	}{
		// test/core/func.wast

		{
			name: `func.wast - type-empty-f64-i32`,
			module: &Module{
				TypeSection:     []FunctionType{v_f64i32},
				FunctionSection: []Index{0},
				CodeSection:     []Code{{Body: []byte{OpcodeEnd}}},
			},
			expectedErr: `not enough results
	have ()
	want (f64, i32)`,
		},
		{
			name: `func.wast - type-value-void-vs-nums`,
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection:     []Code{{Body: []byte{OpcodeNop, OpcodeEnd}}},
			},
			expectedErr: `not enough results
	have ()
	want (i32, i32)`,
		},
		{
			name: `func.wast - type-value-nums-vs-void`,
			module: &Module{
				TypeSection:     []FunctionType{v_v},
				FunctionSection: []Index{0},
				CodeSection:     []Code{{Body: []byte{OpcodeI32Const, 0, OpcodeI64Const, 0, OpcodeEnd}}},
			},
			expectedErr: `too many results
	have (i32, i64)
	want ()`,
		},
		{
			name: `func.wast - type-value-num-vs-nums - v_f32f32 -> f32`,
			module: &Module{
				TypeSection:     []FunctionType{v_f32f32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results
	have (f32)
	want (f32, f32)`,
		},
		{
			name: `func.wast - type-value-num-vs-nums - v_f32 -> f32f32`,
			module: &Module{
				TypeSection:     []FunctionType{v_f32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeF32Const, 0, 0, 0, 0, OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0) (f32.const 0)
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `too many results
	have (f32, f32)
	want (f32)`,
		},
		{
			name: `func.wast - type-return-last-empty-vs-nums`,
			module: &Module{
				TypeSection:     []FunctionType{v_f32f32},
				FunctionSection: []Index{0},
				CodeSection:     []Code{{Body: []byte{OpcodeReturn, OpcodeEnd}}},
			},
			expectedErr: `not enough results
	have ()
	want (f32, f32)`,
		},
		{
			name: `func.wast - type-return-last-void-vs-nums`,
			module: &Module{
				TypeSection:     []FunctionType{v_i32i64},
				FunctionSection: []Index{0},
				CodeSection:     []Code{{Body: []byte{OpcodeNop, OpcodeReturn, OpcodeEnd}}}, // (return (nop))
			},
			expectedErr: `not enough results
	have ()
	want (i32, i64)`,
		},
		{
			name: `func.wast - type-return-last-num-vs-nums`,
			module: &Module{
				TypeSection:     []FunctionType{v_i64i64},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI64Const, 0, OpcodeReturn, // (return (i64.const 0))
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results
	have (i64)
	want (i64, i64)`,
		},
		{
			name: `func.wast - type-return-empty-vs-nums`,
			// This should err because (return) precedes the values expected in the signature (i32i32):
			//	(module (func $type-return-empty-vs-nums (result i32 i32)
			//	  (return) (i32.const 1) (i32.const 2)
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeReturn, OpcodeI32Const, 1, OpcodeI32Const, 2,
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results
	have ()
	want (i32, i32)`,
		},
		{
			name: `func.wast - type-return-partial-vs-nums`,
			// This should err because (return) precedes one of the values expected in the signature (i32i32):
			//	(module (func $type-return-partial-vs-nums (result i32 i32)
			//	  (i32.const 1) (return) (i32.const 2)
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeReturn, OpcodeI32Const, 2,
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results
	have (i32)
	want (i32, i32)`,
		},
		{
			name: `func.wast - type-return-void-vs-nums`,
			// This should err because (return) is empty due to nop, but the signature requires i32i32:
			//	(module (func $type-return-void-vs-nums (result i32 i32)
			//	  (return (nop)) (i32.const 1)
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeNop, OpcodeReturn, // (return (nop))
					OpcodeI32Const, 1, // (i32.const 1)
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results
	have ()
	want (i32, i32)`,
		},

		{
			name: `func.wast - type-return-num-vs-nums`,
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI64Const, 1, OpcodeReturn, // (return (i64.const 1))
					OpcodeI32Const, 1, OpcodeI32Const, 2, // (i32.const 1) (i32.const 2)
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results
	have (i64)
	want (i32, i32)`,
		},
		{
			name: `func.wast - type-return-first-num-vs-nums`,
			// This should err because the return block doesn't return enough values.
			//	(module (func $type-return-first-num-vs-nums (result i32 i32)
			//	  (return (i32.const 1)) (return (i32.const 1) (i32.const 2))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI64Const, 1, OpcodeReturn, // (return (i64.const 1))
					OpcodeI32Const, 1, OpcodeI32Const, 2, OpcodeReturn, // (return (i32.const 1) (i32.const 2))
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results
	have (i64)
	want (i32, i32)`,
		},
		{
			name: `func.wast - type-break-last-num-vs-nums`,
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 0, OpcodeBr, 0, // (br 0 (i32.const 0))
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in br block
	have (i32)
	want (i32, i32)`,
		},
		{
			name: `func.wast - type-break-void-vs-nums`,
			// This should err because (br 0) returns no values, but its enclosing function requires two:
			//	(module (func $type-break-void-vs-nums (result i32 i32)
			//	  (br 0) (i32.const 1) (i32.const 2)
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeBr, 0, // (br 0)
					OpcodeI32Const, 1, OpcodeI32Const, 2, // (i32.const 1) (i32.const 2)
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in br block
	have ()
	want (i32, i32)`,
		},
		{
			name: `func.wast - type-break-num-vs-nums`,
			// This should err because (br 0) returns one value, but its enclosing function requires two:
			//	(module (func $type-break-num-vs-nums (result i32 i32)
			//	  (br 0 (i32.const 1)) (i32.const 1) (i32.const 2)
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeBr, 0, // (br 0 (i32.const 1))
					OpcodeI32Const, 1, OpcodeI32Const, 2, // (i32.const 1) (i32.const 2)
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in br block
	have (i32)
	want (i32, i32)`,
		},
		{
			name: `func.wast - type-break-nested-empty-vs-nums`,
			// This should err because (br 1) doesn't return values, but its enclosing function does:
			//	(module (func $type-break-nested-empty-vs-nums (result i32 i32)
			//	  (block (br 1)) (br 0 (i32.const 1) (i32.const 2))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeBlock, 0x40, OpcodeBr, 0x01, OpcodeEnd, // (block (br 1))
					OpcodeI32Const, 1, OpcodeI32Const, 2, OpcodeBr, 0, // (br 0 (i32.const 1) (i32.const 2))
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in br block
	have ()
	want (i32, i32)`,
		},
		{
			name: `func.wast - type-break-nested-void-vs-nums`,
			// This should err because nop returns the empty type, but the enclosing function returns i32i32:
			//	(module (func $type-break-nested-void-vs-nums (result i32 i32)
			//	  (block (br 1 (nop))) (br 0 (i32.const 1) (i32.const 2))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeBlock, 0x40, OpcodeNop, OpcodeBr, 0x01, OpcodeEnd, // (block (br 1 (nop)))
					OpcodeI32Const, 1, OpcodeI32Const, 2, OpcodeBr, 0, // (br 0 (i32.const 1) (i32.const 2))
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in br block
	have ()
	want (i32, i32)`,
		},
		{
			name: `func.wast - type-break-nested-num-vs-nums`,
			// This should err because the block signature is v_i32, but the enclosing function is v_i32i32:
			//	(module (func $type-break-nested-num-vs-nums (result i32 i32)
			//	  (block (result i32) (br 1 (i32.const 1))) (br 0 (i32.const 1) (i32.const 2))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeBlock, 0x7f, OpcodeI32Const, 1, OpcodeBr, 1, OpcodeEnd, // (block (result i32) (br 1 (i32.const 1)))
					OpcodeI32Const, 1, OpcodeI32Const, 2, OpcodeBr, 0, // (br 0 (i32.const 1) (i32.const 2))
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in br block
	have (i32)
	want (i32, i32)`,
		},

		// test/core/if.wast
		{
			name: `if.wast - wrong signature for if type use`,
			// This should err because (br 0) returns no values, but its enclosing function requires two:
			//  (module
			//    (type $sig (func))
			//    (func (i32.const 1) (if (type $sig) (i32.const 0) (then)))
			//  )
			module: &Module{
				TypeSection:     []FunctionType{v_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, // (i32.const 1)
					OpcodeI32Const, 0, OpcodeIf, 0, // (if (type $sig) (i32.const 0)
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `too many results
	have (i32)
	want ()`,
		},
		{
			name: `if.wast - type-then-value-nums-vs-void`,
			// This should err because (if) without a type use returns no values, but its (then) returns two:
			//	(module (func $type-then-value-nums-vs-void
			//	  (if (i32.const 1) (then (i32.const 1) (i32.const 2)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x40, // (if (i32.const 1)
					OpcodeI32Const, 1, OpcodeI32Const, 2, // (then (i32.const 1) (i32.const 2))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `too many results in if block
	have (i32, i32)
	want ()`,
		},
		{
			name: `if.wast - type-then-value-nums-vs-void-else`,
			// This should err because (if) without a type use returns no values, but its (then) returns two:
			//	(module (func $type-then-value-nums-vs-void-else
			//	  (if (i32.const 1) (then (i32.const 1) (i32.const 2)) (else))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x40, // (if (i32.const 1)
					OpcodeI32Const, 1, OpcodeI32Const, 2, // (then (i32.const 1) (i32.const 2))
					OpcodeElse, // (else)
					OpcodeEnd,  // if
					OpcodeEnd,  // func
				}}},
			},
			expectedErr: `too many results in if block
	have (i32, i32)
	want ()`,
		},
		{
			name: `if.wast - type-else-value-nums-vs-void`,
			// This should err because (if) without a type use returns no values, but its (else) returns two:
			//	(module (func $type-else-value-nums-vs-void
			//	  (if (i32.const 1) (then) (else (i32.const 1) (i32.const 2)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x40, // (if (i32.const 1) (then)
					OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 2, // (else (i32.const 1) (i32.const 2))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `too many results in else block
	have (i32, i32)
	want ()`,
		},
		{
			name: `if.wast - type-both-value-nums-vs-void`,
			// This should err because (if) without a type use returns no values, each branch returns two:
			//	(module (func $type-both-value-nums-vs-void
			//	  (if (i32.const 1) (then (i32.const 1) (i32.const 2)) (else (i32.const 2) (i32.const 1)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x40, // (if (i32.const 1)
					OpcodeI32Const, 1, OpcodeI32Const, 2, // (then (i32.const 1) (i32.const 2))
					OpcodeElse, OpcodeI32Const, 2, OpcodeI32Const, 1, // (else (i32.const 2) (i32.const 1))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `too many results in if block
	have (i32, i32)
	want ()`,
		},
		{
			name: `if.wast - type-then-value-empty-vs-nums`,
			// This should err because the if branch is empty, but its type use requires two i32s:
			//	(module (func $type-then-value-empty-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1) (then) (else (i32.const 0) (i32.const 2)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (result i32 i32) (i32.const 1) (then)
					OpcodeElse, OpcodeI32Const, 0, OpcodeI32Const, 2, // (else (i32.const 0) (i32.const 2)))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in if block
	have ()
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-else-value-empty-vs-nums`,
			// This should err because the else branch is empty, but its type use requires two i32s:
			//	(module (func $type-else-value-empty-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1) (then (i32.const 0) (i32.const 1)) (else))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (result i32 i32) (i32.const 1)
					OpcodeI32Const, 0, OpcodeI32Const, 2, // (then (i32.const 0) (i32.const 1))
					OpcodeElse, // (else)
					OpcodeEnd,  // if
					OpcodeEnd,  // func
				}}},
			},
			expectedErr: `not enough results in else block
	have ()
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-both-value-empty-vs-nums`,
			// This should err because the both branches are empty, but the if type use requires two i32s:
			//	(module (func $type-both-value-empty-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1) (then) (else))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (result i32 i32) (i32.const 1) (then)
					OpcodeElse, // (else)
					OpcodeEnd,  // if
					OpcodeEnd,  // func
				}}},
			},
			expectedErr: `not enough results in if block
	have ()
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-no-else-vs-nums`,
			// This should err because the else branch is missing, but its type use requires two i32s:
			//	(module (func $type-no-else-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1) (then (i32.const 1) (i32.const 1)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (result i32 i32) (i32.const 1)
					OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in else block
	have ()
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-then-value-void-vs-nums`,
			// This should err because the then branch evaluates to empty, but its type use requires two i32s:
			//	(module (func $type-then-value-void-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1) (then (nop)) (else (i32.const 0) (i32.const 0)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (result i32 i32) (i32.const 1)
					OpcodeNop,                                        // (then (nop))
					OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in if block
	have ()
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-then-value-void-vs-nums`,
			// This should err because the else branch evaluates to empty, but its type use requires two i32s:
			//	(module (func $type-else-value-void-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1) (then (i32.const 0) (i32.const 0)) (else (nop)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeI32Const, 0, OpcodeI32Const, 0, // (then (i32.const 0) (i32.const 0))
					OpcodeElse, OpcodeNop, // (else (nop))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in else block
	have ()
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-both-value-void-vs-nums`,
			// This should err because the if branch evaluates to empty, but its type use requires two i32s:
			//	(module (func $type-both-value-void-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1) (then (nop)) (else (nop)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeNop,             // (then (nop))
					OpcodeElse, OpcodeNop, // (else (nop))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in if block
	have ()
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-then-value-num-vs-nums`,
			// This should err because the if branch returns one value, but its type use requires two:
			//	(module (func $type-then-value-num-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1) (then (i32.const 1)) (else (i32.const 1) (i32.const 1)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeI32Const, 1, // (then (i32.const 1))
					OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1)))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in if block
	have (i32)
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-else-value-num-vs-nums`,
			// This should err because the else branch returns one value, but its type use requires two:
			//	(module (func $type-else-value-num-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1) (then (i32.const 1) (i32.const 1)) (else (i32.const 1)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
					OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1)))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in else block
	have (i32)
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-both-value-num-vs-nums`,
			// This should err because the if branch returns one value, but its type use requires two:
			//	(module (func $type-both-value-num-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1) (then (i32.const 1)) (else (i32.const 1)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeI32Const, 1, // (then (i32.const 1))
					OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1)))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in if block
	have (i32)
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-then-value-partial-vs-nums`,
			// This should err because the if branch returns one value, but its type use requires two:
			//	(module (func $type-then-value-partial-vs-nums (result i32 i32)
			//	  (i32.const 0)
			//	  (if (result i32 i32) (i32.const 1) (then (i32.const 1)) (else (i32.const 1) (i32.const 1)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 0, // (i32.const 0) - NOTE: this is outside the (if)
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeI32Const, 1, // (then (i32.const 1))
					OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in if block
	have (i32)
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-else-value-partial-vs-nums`,
			// This should err because the else branch returns one value, but its type use requires two:
			//	(module (func $type-else-value-partial-vs-nums (result i32 i32)
			//	  (i32.const 0)
			//	  (if (result i32 i32) (i32.const 1) (then (i32.const 1) (i32.const 1)) (else (i32.const 1)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 0, // (i32.const 0) - NOTE: this is outside the (if)
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
					OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in else block
	have (i32)
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-both-value-partial-vs-nums`,
			// This should err because the if branch returns one value, but its type use requires two:
			//  (module (func $type-both-value-partial-vs-nums (result i32 i32)
			//    (i32.const 0)
			//    (if (result i32 i32) (i32.const 1) (then (i32.const 1)) (else (i32.const 1)))
			//  ))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 0, // (i32.const 0) - NOTE: this is outside the (if)
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeI32Const, 1, // (then (i32.const 1))
					OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in if block
	have (i32)
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-then-value-nums-vs-num`,
			// This should err because the if branch returns two values, but its type use requires one:
			//	(module (func $type-then-value-nums-vs-num (result i32)
			//	  (if (result i32) (i32.const 1) (then (i32.const 1) (i32.const 1)) (else (i32.const 1)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32) (i32.const 1)
					OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
					OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `too many results in if block
	have (i32, i32)
	want (i32)`,
		},
		{
			name: `if.wast - type-else-value-nums-vs-num`,
			// This should err because the else branch returns two values, but its type use requires one:
			//	(module (func $type-else-value-nums-vs-num (result i32)
			//	  (if (result i32) (i32.const 1) (then (i32.const 1)) (else (i32.const 1) (i32.const 1)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32) (i32.const 1)
					OpcodeI32Const, 1, // (then (i32.const 1))
					OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `too many results in else block
	have (i32, i32)
	want (i32)`,
		},
		{
			name: `if.wast - type-both-value-nums-vs-num`,
			// This should err because the if branch returns two values, but its type use requires one:
			//	(module (func $type-both-value-nums-vs-num (result i32)
			//	  (if (result i32) (i32.const 1) (then (i32.const 1) (i32.const 1)) (else (i32.const 1) (i32.const 1)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32) (i32.const 1)
					OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
					OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `too many results in if block
	have (i32, i32)
	want (i32)`,
		},
		{
			name: `if.wast - type-both-different-value-nums-vs-nums`,
			// This should err because the if branch returns three values, but its type use requires two:
			//	(module (func $type-both-different-value-nums-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1) (then (i32.const 1) (i32.const 1) (i32.const 1)) (else (i32.const 1)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeI32Const, 1, OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1) (i32.const 1))
					OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `too many results in if block
	have (i32, i32, i32)
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-then-break-last-void-vs-nums`,
			// This should err because the branch in the if returns no values, but its type use requires two:
			//	(module (func $type-then-break-last-void-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1) (then (br 0)) (else (i32.const 1) (i32.const 1)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeBr, 0, // (then (br 0))
					OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1)))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in br block
	have ()
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-else-break-last-void-vs-nums`,
			// This should err because the branch in the else returns no values, but its type use requires two:
			//	(module (func $type-else-break-last-void-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1) (then (i32.const 1) (i32.const 1)) (else (br 0)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
					OpcodeElse, OpcodeBr, 0, // (else (br 0))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in br block
	have ()
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-then-break-empty-vs-nums`,
			// This should err because the branch in the if returns no values, but its type use requires two:
			//	(module (func $type-then-break-empty-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1)
			//	    (then (br 0) (i32.const 1) (i32.const 1))
			//	    (else (i32.const 1) (i32.const 1))
			//	  )
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeBr, 0, OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (br 0) (i32.const 1) (i32.const 1))
					// ^^ NOTE: consts are outside the br block
					OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in br block
	have ()
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-else-break-empty-vs-nums`,
			// This should err because the branch in the else returns no values, but its type use requires two:
			//	(module (func $type-else-break-empty-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1)
			//	    (then (i32.const 1) (i32.const 1))
			//	    (else (br 0) (i32.const 1) (i32.const 1))
			//	  )
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
					OpcodeElse, OpcodeBr, 0, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (br 0) (i32.const 1) (i32.const 1))
					// ^^ NOTE: consts are outside the br block
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in br block
	have ()
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-then-break-void-vs-nums`,
			// This should err because the branch in the if evaluates to no values, but its type use requires two:
			//	(module (func $type-then-break-void-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1)
			//	    (then (br 0 (nop)) (i32.const 1) (i32.const 1))
			//	    (else (i32.const 1) (i32.const 1))
			//	  )
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeNop, OpcodeBr, 0, OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (br 0 (nop)) (i32.const 1) (i32.const 1))
					// ^^ NOTE: consts are outside the br block
					OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in br block
	have ()
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-else-break-void-vs-nums`,
			// This should err because the branch in the else evaluates to no values, but its type use requires two:
			//	(module (func $type-else-break-void-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1)
			//	    (then (i32.const 1) (i32.const 1))
			//	    (else (br 0 (nop)) (i32.const 1) (i32.const 1))
			//	  )
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
					OpcodeElse, OpcodeNop, OpcodeBr, 0, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (br 0 (nop)) (i32.const 1) (i32.const 1))
					// ^^ NOTE: consts are outside the br block
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in br block
	have ()
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-then-break-num-vs-nums`,
			// This should err because the branch in the if evaluates to one value, but its type use requires two:
			//	(module (func $type-then-break-num-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1)
			//	    (then (br 0 (i64.const 1)) (i32.const 1) (i32.const 1))
			//	    (else (i32.const 1) (i32.const 1))
			//	  )
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeI64Const, 1, OpcodeBr, 0, OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (br 0 (i64.const 1)) (i32.const 1) (i32.const 1))
					// ^^ NOTE: only one (incorrect) const is inside the br block
					OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in br block
	have (i64)
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-else-break-num-vs-nums`,
			// This should err because the branch in the else evaluates to one value, but its type use requires two:
			//	(module (func $type-else-break-num-vs-nums (result i32 i32)
			//	  (if (result i32 i32) (i32.const 1)
			//	    (then (i32.const 1) (i32.const 1))
			//	    (else (br 0 (i64.const 1)) (i32.const 1) (i32.const 1))
			//	  )
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
					OpcodeElse, OpcodeI64Const, 1, OpcodeBr, 0, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (br 0 (i64.const 1)) (i32.const 1) (i32.const 1))
					// ^^ NOTE: only one (incorrect) const is inside the br block
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in br block
	have (i64)
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-then-break-partial-vs-nums`,
			// This should err because the branch in the if evaluates to one value, but its type use requires two:
			//	(module (func $type-then-break-partial-vs-nums (result i32 i32)
			//	  (i32.const 1)
			//	  (if (result i32 i32) (i32.const 1)
			//	    (then (br 0 (i64.const 1)) (i32.const 1))
			//	    (else (i32.const 1))
			//	  )
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, // (i32.const 1)
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeI64Const, 1, OpcodeBr, 0, OpcodeI32Const, 1, // (then (br 0 (i64.const 1)) (i32.const 1))
					// ^^ NOTE: only one (incorrect) const is inside the br block
					OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in br block
	have (i64)
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-else-break-partial-vs-nums`,
			// This should err because the branch in the if evaluates to one value, but its type use requires two:
			//	(module (func $type-else-break-partial-vs-nums (result i32 i32)
			//	  (i32.const 1)
			//	  (if (result i32 i32) (i32.const 1)
			//	    (then (i32.const 1))
			//	    (else (br 0 (i64.const 1)) (i32.const 1))
			//	  )
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, // (i32.const 1)
					OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
					OpcodeI32Const, 1, // (then (i32.const 1))
					OpcodeElse, OpcodeI64Const, 1, OpcodeBr, 0, OpcodeI32Const, 1, // (else (br 0 (i64.const 1)) (i32.const 1))
					// ^^ NOTE: only one (incorrect) const is inside the br block
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in if block
	have (i32)
	want (i32, i32)`,
		},
		{
			name: `if.wast - type-param-void-vs-num`,
			// This should err because the stack has no values, but the if type use requires two:
			//	(module (func $type-param-void-vs-num
			//	  (if (param i32) (i32.const 1) (then (drop)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, i32_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param i32) (i32.const 1)
					OpcodeDrop, // (then (drop)))
					OpcodeEnd,  // if
					OpcodeEnd,  // func
				}}},
			},
			expectedErr: `not enough params for if block
	have ()
	want (i32)`,
		},
		{
			name: `if.wast - type-param-void-vs-nums`,
			// This should err because the stack has no values, but the if type use requires two:
			//	(module (func $type-param-void-vs-nums
			//	  (if (param i32 f64) (i32.const 1) (then (drop) (drop)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, i32f64_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param i32 f64) (i32.const 1)
					OpcodeI32Const, 1, OpcodeDrop, // (then (drop) (drop))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough params for if block
	have ()
	want (i32, f64)`,
		},
		{
			name: `if.wast - type-param-num-vs-num`,
			// This should err because the stack has a different value that what the if type use requires:
			//	(module (func $type-param-num-vs-num
			//	  (f32.const 0) (if (param i32) (i32.const 1) (then (drop)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, i32_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
					OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param i32) (i32.const 1)
					OpcodeDrop, // (then (drop))
					OpcodeEnd,  // if
					OpcodeEnd,  // func
				}}},
			},
			expectedErr: "cannot use f32 in if block as param[0] type i32",
		},
		{
			name: `if.wast - type-param-num-vs-nums`,
			// This should err because the stack has one value, but the if type use requires two:
			//	(module (func $type-param-num-vs-nums
			//	  (f32.const 0) (if (param f32 i32) (i32.const 1) (then (drop) (drop)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, f32i32_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
					OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param f32 i32) (i32.const 1)
					OpcodeDrop, OpcodeDrop, // (then (drop) (drop))
					OpcodeEnd, // if
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough params for if block
	have (f32)
	want (f32, i32)`,
		},
		{
			name: `if.wast - type-param-nested-void-vs-num`,
			// This should err because the stack has no values, but the if type use requires one:
			//	(module (func $type-param-nested-void-vs-num
			//	  (block (if (param i32) (i32.const 1) (then (drop))))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, i32_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeBlock, 0x40, // (block
					OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param i32) (i32.const 1)
					OpcodeDrop, // (then (drop))
					OpcodeEnd,  // block
					OpcodeEnd,  // if
					OpcodeEnd,  // func
				}}},
			},
			expectedErr: `not enough params for if block
	have ()
	want (i32)`,
		},
		{
			name: `if.wast - type-param-void-vs-nums`,
			// This should err because the stack has no values, but the if type use requires two:
			//	(module (func $type-param-void-vs-nums
			//	  (block (if (param i32 f64) (i32.const 1) (then (drop) (drop))))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, i32f64_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeBlock, 0x40, // (block
					OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param i32 f64) (i32.const 1)
					OpcodeDrop, // (then (drop) (drop))
					OpcodeEnd,  // block
					OpcodeEnd,  // if
					OpcodeEnd,  // func
				}}},
			},
			expectedErr: `not enough params for if block
	have ()
	want (i32, f64)`,
		},
		{
			name: `if.wast - type-param-num-vs-num`,
			// This should err because the stack has a different values than required by the if type use:
			//	(module (func $type-param-num-vs-num
			//	  (block (f32.const 0) (if (param i32) (i32.const 1) (then (drop))))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, i32_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeBlock, 0x40, // (block
					OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
					OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param i32) (i32.const 1)
					OpcodeDrop, // (then (drop))
					OpcodeEnd,  // block
					OpcodeEnd,  // if
					OpcodeEnd,  // func
				}}},
			},
			expectedErr: "cannot use f32 in if block as param[0] type i32",
		},
		{
			name: `if.wast - type-param-num-vs-nums`,
			// This should err because the stack has one value, but the if type use requires two:
			//	(module (func $type-param-num-vs-nums
			//	  (block (f32.const 0) (if (param f32 i32) (i32.const 1) (then (drop) (drop))))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, f32i32_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeBlock, 0x40, // (block
					OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
					OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param f32 i32) (i32.const 1)
					OpcodeDrop, // (then (drop) (drop))
					OpcodeEnd,  // block
					OpcodeEnd,  // if
					OpcodeEnd,  // func
				}}},
			},
			expectedErr: `not enough params for if block
	have (f32)
	want (f32, i32)`,
		},

		// test/core/loop.wast
		{
			name: `loop.wast - wrong signature for loop type use`,
			// This should err because the loop type use returns no values, but its block returns one:
			//  (module
			//    (type $sig (func))
			//    (func (loop (type $sig) (i32.const 0)))
			//  )
			module: &Module{
				TypeSection:     []FunctionType{v_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeLoop, 0, OpcodeI32Const, 0, // (loop (type $sig) (i32.const 0))
					OpcodeEnd, // loop
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `too many results in loop block
	have (i32)
	want ()`,
		},
		{
			name: `loop.wast - type-value-nums-vs-void`,
			// This should err because the empty block type requires no values, but the loop returns two:
			//  (module (func $type-value-nums-vs-void
			//    (loop (i32.const 1) (i32.const 2))
			//  ))
			module: &Module{
				TypeSection:     []FunctionType{v_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeLoop, 0x40, OpcodeI32Const, 1, OpcodeI32Const, 2, // (loop (i32.const 1) (i32.const 2))
					OpcodeEnd, // loop
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `too many results in loop block
	have (i32, i32)
	want ()`,
		},
		{
			name: `loop.wast - type-value-empty-vs-nums`,
			// This should err because the loop type use returns two values, but the block returns none:
			//	(module (func $type-value-empty-vs-nums (result i32 i32)
			//	  (loop (result i32 i32))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeLoop, 0x0, // (loop (result i32 i32)) - matches existing func type
					OpcodeEnd, // loop
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in loop block
	have ()
	want (i32, i32)`,
		},
		{
			name: `loop.wast - type-value-void-vs-nums`,
			// This should err because the loop type use returns two values, but the block returns none:
			//	(module (func $type-value-void-vs-nums (result i32 i32)
			//	  (loop (result i32 i32) (nop))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeLoop, 0x0, // (loop (result i32 i32) - matches existing func type
					OpcodeNop, // (nop)
					OpcodeEnd, // loop
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in loop block
	have ()
	want (i32, i32)`,
		},
		{
			name: `loop.wast - type-value-num-vs-nums`,
			// This should err because the loop type use returns two values, but the block returns one:
			//	(module (func $type-value-num-vs-nums (result i32 i32)
			//	  (loop (result i32 i32) (i32.const 0))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeLoop, 0x0, // (loop (result i32 i32) - matches existing func type
					OpcodeI32Const, 0, // (i32.const 0)
					OpcodeEnd, // loop
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in loop block
	have (i32)
	want (i32, i32)`,
		},
		{
			name: `loop.wast - type-value-partial-vs-nums`,
			// This should err because the loop type use returns two values, but the block returns one:
			//	(module (func $type-value-partial-vs-nums (result i32 i32)
			//	  (i32.const 1) (loop (result i32 i32) (i32.const 2))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeI32Const, 1, // (i32.const 1) - NOTE: outside the loop!
					OpcodeLoop, 0x0, // (loop (result i32 i32) - matches existing func type
					OpcodeI32Const, 2, // (i32.const 2)
					OpcodeEnd, // loop
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough results in loop block
	have (i32)
	want (i32, i32)`,
		},
		{
			name: `loop.wast - type-value-nums-vs-num`,
			// This should err because the loop type use returns one value, but the block returns two:
			//	(module (func $type-value-nums-vs-num (result i32)
			//	  (loop (result i32) (i32.const 1) (i32.const 2))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_i32},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeLoop, 0x0, // (loop (result i32) - matches existing func type
					OpcodeI32Const, 1, OpcodeI32Const, 2, // (i32.const 1) (i32.const 2))
					OpcodeEnd, // loop
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `too many results in loop block
	have (i32, i32)
	want (i32)`,
		},
		{
			name: `loop.wast - type-param-void-vs-num`,
			// This should err because the loop type use requires one param, but the stack has none:
			//	(module (func $type-param-void-vs-num
			//	  (loop (param i32) (drop))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, i32_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeLoop, 0x1, // (loop (param i32)
					OpcodeDrop, // (drop)
					OpcodeEnd,  // loop
					OpcodeEnd,  // func
				}}},
			},
			expectedErr: `not enough params for loop block
	have ()
	want (i32)`,
		},
		{
			name: `loop.wast - type-param-void-vs-nums`,
			// This should err because the loop type use requires two params, but the stack has none:
			//	(module (func $type-param-void-vs-nums
			//	  (loop (param i32 f64) (drop) (drop))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, i32f64_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeLoop, 0x1, // (loop (param i32 f64)
					OpcodeDrop, // (drop)
					OpcodeDrop, // (drop)
					OpcodeEnd,  // loop
					OpcodeEnd,  // func
				}}},
			},
			expectedErr: `not enough params for loop block
	have ()
	want (i32, f64)`,
		},
		{
			name: `loop.wast - type-param-num-vs-num`,
			// This should err because the loop type use requires a different param type than what's on the stack:
			//	(module (func $type-param-num-vs-num
			//	  (f32.const 0) (loop (param i32) (drop))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, i32_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
					OpcodeLoop, 0x1, // (loop (param i32)
					OpcodeDrop, // (drop)
					OpcodeEnd,  // loop
					OpcodeEnd,  // func
				}}},
			},
			expectedErr: "cannot use f32 in loop block as param[0] type i32",
		},
		{
			name: `loop.wast - type-param-num-vs-num`,
			// This should err because the loop type use requires a more parameters than what's on the stack:
			//	(module (func $type-param-num-vs-nums
			//	  (f32.const 0) (loop (param f32 i32) (drop) (drop))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, f32i32_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
					OpcodeLoop, 0x1, // (loop (param f32 i32)
					OpcodeDrop, OpcodeDrop, // (drop) (drop)
					OpcodeEnd, // loop
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough params for loop block
	have (f32)
	want (f32, i32)`,
		},
		{
			name: `loop.wast - type-param-nested-void-vs-num`,
			// This should err because the loop type use requires a more parameters than what's on the stack:
			//	(module (func $type-param-nested-void-vs-num
			//	  (block (loop (param i32) (drop)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, i32_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeBlock, 0x40, // (block
					OpcodeLoop, 0x1, // (loop (param i32)
					OpcodeDrop, // (drop)
					OpcodeEnd,  // loop
					OpcodeEnd,  // block
					OpcodeEnd,  // func
				}}},
			},
			expectedErr: `not enough params for loop block
	have ()
	want (i32)`,
		},
		{
			name: `loop.wast - type-param-void-vs-nums`,
			// This should err because the loop type use requires a more parameters than what's on the stack:
			//	(module (func $type-param-void-vs-nums
			//	  (block (loop (param i32 f64) (drop) (drop)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, i32f64_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeBlock, 0x40, // (block
					OpcodeLoop, 0x1, // (loop (param i32 f64)
					OpcodeDrop, OpcodeDrop, // (drop) (drop)
					OpcodeEnd, // loop
					OpcodeEnd, // block
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough params for loop block
	have ()
	want (i32, f64)`,
		},
		{
			name: `loop.wast - type-param-void-vs-nums`,
			// This should err because the loop type use requires a different param type than what's on the stack:
			//	(module (func $type-param-num-vs-num
			//	  (block (f32.const 0) (loop (param i32) (drop)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, i32_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeBlock, 0x40, // (block
					OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
					OpcodeLoop, 0x1, // (loop (param i32)
					OpcodeDrop, // (drop)
					OpcodeEnd,  // loop
					OpcodeEnd,  // block
					OpcodeEnd,  // func
				}}},
			},
			expectedErr: "cannot use f32 in loop block as param[0] type i32",
		},
		{
			name: `loop.wast - type-param-void-vs-nums`,
			// This should err because the loop type use requires a more parameters than what's on the stack:
			//	(module (func $type-param-num-vs-nums
			//	  (block (f32.const 0) (loop (param f32 i32) (drop) (drop)))
			//	))
			module: &Module{
				TypeSection:     []FunctionType{v_v, f32i32_v},
				FunctionSection: []Index{0},
				CodeSection: []Code{{Body: []byte{
					OpcodeBlock, 0x40, // (block
					OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
					OpcodeLoop, 0x1, // (loop (param f32 i32)
					OpcodeDrop, OpcodeDrop, // (drop) (drop)
					OpcodeEnd, // loop
					OpcodeEnd, // block
					OpcodeEnd, // func
				}}},
			},
			expectedErr: `not enough params for loop block
	have (f32)
	want (f32, i32)`,
		},
	}

	for _, tt := range tests {
		tc := tt

		t.Run(tc.name, func(t *testing.T) {
			err := tc.module.validateFunction(&stacks{}, api.CoreFeatureMultiValue,
				0, []Index{0}, nil, nil, nil, nil, nil, bytes.NewReader(nil))
			require.EqualError(t, err, tc.expectedErr)
		})
	}
}

func TestModule_funcValidation_CallIndirect(t *testing.T) {
	t.Run("ok", func(t *testing.T) {
		m := &Module{
			TypeSection:     []FunctionType{v_v},
			FunctionSection: []Index{0},
			CodeSection: []Code{{Body: []byte{
				OpcodeI32Const, 1,
				OpcodeCallIndirect, 0, 0,
				OpcodeEnd,
			}}},
		}
		err := m.validateFunction(&stacks{}, api.CoreFeatureReferenceTypes,
			0, []Index{0}, nil, &Memory{}, []Table{{Type: RefTypeFuncref}}, nil, nil, bytes.NewReader(nil))
		require.NoError(t, err)
	})
	t.Run("non zero table index", func(t *testing.T) {
		m := &Module{
			TypeSection:     []FunctionType{v_v},
			FunctionSection: []Index{0},
			CodeSection: []Code{{Body: []byte{
				OpcodeI32Const, 1,
				OpcodeCallIndirect, 0, 100,
				OpcodeEnd,
			}}},
		}
		t.Run("disabled", func(t *testing.T) {
			err := m.validateFunction(&stacks{}, api.CoreFeaturesV1,
				0, []Index{0}, nil, &Memory{}, []Table{{}, {}}, nil, nil, bytes.NewReader(nil))
			require.EqualError(t, err, "table index must be zero but was 100: feature \"reference-types\" is disabled")
		})
		t.Run("enabled but out of range", func(t *testing.T) {
			err := m.validateFunction(&stacks{}, api.CoreFeatureReferenceTypes,
				0, []Index{0}, nil, &Memory{}, []Table{{}, {}}, nil, nil, bytes.NewReader(nil))
			require.EqualError(t, err, "unknown table index: 100")
		})
	})
	t.Run("non funcref table", func(t *testing.T) {
		m := &Module{
			TypeSection:     []FunctionType{v_v},
			FunctionSection: []Index{0},
			CodeSection: []Code{{Body: []byte{
				OpcodeI32Const, 1,
				OpcodeCallIndirect, 0, 0,
				OpcodeEnd,
			}}},
		}
		err := m.validateFunction(&stacks{}, api.CoreFeatureReferenceTypes,
			0, []Index{0}, nil, &Memory{}, []Table{{Type: RefTypeExternref}}, nil, nil, bytes.NewReader(nil))
		require.EqualError(t, err, "table is not funcref type but was externref for call_indirect")
	})
}

func TestModule_funcValidation_RefTypes(t *testing.T) {
	tests := []struct {
		name                    string
		body                    []byte
		flag                    api.CoreFeatures
		declaredFunctionIndexes map[Index]struct{}
		expectedErr             string
	}{
		{
			name: "ref.null (funcref)",
			flag: api.CoreFeatureReferenceTypes,
			body: []byte{
				OpcodeRefNull, ValueTypeFuncref.Kind(),
				OpcodeDrop, OpcodeEnd,
			},
		},
		{
			name: "ref.null (externref)",
			flag: api.CoreFeatureReferenceTypes,
			body: []byte{
				OpcodeRefNull, ValueTypeExternref.Kind(),
				OpcodeDrop, OpcodeEnd,
			},
		},
		{
			name: "ref.null - disabled",
			flag: api.CoreFeaturesV1,
			body: []byte{
				OpcodeRefNull, ValueTypeFuncref.Kind(),
				OpcodeDrop, OpcodeEnd,
			},
			expectedErr: "ref.null invalid as feature \"reference-types\" is disabled",
		},
		{
			name: "ref.is_null",
			flag: api.CoreFeatureReferenceTypes,
			body: []byte{
				OpcodeRefNull, ValueTypeFuncref.Kind(),
				OpcodeRefIsNull,
				OpcodeDrop, OpcodeEnd,
			},
		},
		{
			name: "ref.is_null - disabled",
			flag: api.CoreFeaturesV1,
			body: []byte{
				OpcodeRefIsNull,
				OpcodeDrop, OpcodeEnd,
			},
			expectedErr: `ref.is_null invalid as feature "reference-types" is disabled`,
		},
		{
			name:                    "ref.func",
			flag:                    api.CoreFeatureReferenceTypes,
			declaredFunctionIndexes: map[uint32]struct{}{0: {}},
			body: []byte{
				OpcodeRefFunc, 0,
				OpcodeDrop, OpcodeEnd,
			},
		},
		{
			name:                    "ref.func - undeclared function index",
			flag:                    api.CoreFeatureReferenceTypes,
			declaredFunctionIndexes: map[uint32]struct{}{0: {}},
			body: []byte{
				OpcodeRefFunc, 100,
				OpcodeDrop, OpcodeEnd,
			},
			expectedErr: `undeclared function index 100 for ref.func`,
		},
		{
			name:                    "ref.func",
			flag:                    api.CoreFeaturesV1,
			declaredFunctionIndexes: map[uint32]struct{}{0: {}},
			body: []byte{
				OpcodeRefFunc, 0,
				OpcodeDrop, OpcodeEnd,
			},
			expectedErr: "ref.func invalid as feature \"reference-types\" is disabled",
		},
	}

	for _, tt := range tests {
		tc := tt
		t.Run(tc.name, func(t *testing.T) {
			m := &Module{
				TypeSection:     []FunctionType{v_v},
				FunctionSection: []Index{0},
				CodeSection:     []Code{{Body: tc.body}},
			}
			err := m.validateFunction(&stacks{}, tc.flag,
				0, []Index{0}, nil, nil, nil, nil, tc.declaredFunctionIndexes, bytes.NewReader(nil))
			if tc.expectedErr != "" {
				require.EqualError(t, err, tc.expectedErr)
			} else {
				require.NoError(t, err)
			}
		})
	}
}

func TestModule_funcValidation_TableGrowSizeFill(t *testing.T) {
	tables := []Table{{Type: RefTypeFuncref}, {Type: RefTypeExternref}}
	tests := []struct {
		name        string
		body        []byte
		flag        api.CoreFeatures
		expectedErr string
	}{
		{
			name: "table.grow (funcref)",
			body: []byte{
				OpcodeRefNull, RefTypeFuncref.Kind(),
				OpcodeI32Const, 1, // number of elements
				OpcodeMiscPrefix, OpcodeMiscTableGrow,
				0, // Table Index.
				OpcodeDrop,
				OpcodeEnd,
			},
			flag: api.CoreFeatureReferenceTypes,
		},
		{
			name: "table.grow (funcref) - type mismatch",
			body: []byte{
				OpcodeRefNull, RefTypeFuncref.Kind(),
				OpcodeI32Const, 1, // number of elements
				OpcodeMiscPrefix, OpcodeMiscTableGrow,
				1, // Table of externref type -> mismatch.
				OpcodeEnd,
			},
			flag:        api.CoreFeatureReferenceTypes,
			expectedErr: `cannot pop the operand for table.grow: type mismatch: expected externref, but was funcref`,
		},
		{
			name: "table.grow (externref)",
			body: []byte{
				OpcodeRefNull, RefTypeExternref.Kind(),
				OpcodeI32Const, 1, // number of elements
				OpcodeMiscPrefix, OpcodeMiscTableGrow,
				1, // Table Index.
				OpcodeDrop,
				OpcodeEnd,
			},
			flag: api.CoreFeatureReferenceTypes,
		},
		{
			name: "table.grow (externref) type mismatch",
			body: []byte{
				OpcodeRefNull, RefTypeExternref.Kind(),
				OpcodeI32Const, 1, // number of elements
				OpcodeMiscPrefix, OpcodeMiscTableGrow,
				0, // Table of funcref type -> mismatch.
				OpcodeEnd,
			},
			flag:        api.CoreFeatureReferenceTypes,
			expectedErr: `cannot pop the operand for table.grow: type mismatch: expected funcref, but was externref`,
		},
		{
			name: "table.grow - table not found",
			body: []byte{
				OpcodeRefNull, RefTypeFuncref.Kind(),
				OpcodeI32Const, 1, // number of elements
				OpcodeMiscPrefix, OpcodeMiscTableGrow,
				10, // Table Index.
				OpcodeEnd,
			},
			flag:        api.CoreFeatureReferenceTypes,
			expectedErr: `table of index 10 not found`,
		},
		{
			name: "table.size - table not found",
			body: []byte{
				OpcodeMiscPrefix, OpcodeMiscTableSize,
				10, // Table Index.
				OpcodeEnd,
			},
			flag:        api.CoreFeatureReferenceTypes,
			expectedErr: `table of index 10 not found`,
		},
		{
			name: "table.size",
			body: []byte{
				OpcodeMiscPrefix, OpcodeMiscTableSize,
				1, // Table Index.
				OpcodeDrop,
				OpcodeEnd,
			},
			flag: api.CoreFeatureReferenceTypes,
		},
		{
			name: "table.fill (funcref)",
			body: []byte{
				OpcodeI32Const, 1, // offset
				OpcodeRefNull, RefTypeFuncref.Kind(),
				OpcodeI32Const, 1, // number of elements
				OpcodeMiscPrefix, OpcodeMiscTableFill,
				0, // Table Index.
				OpcodeEnd,
			},
			flag: api.CoreFeatureReferenceTypes,
		},
		{
			name: "table.fill (funcref) - type mismatch",
			body: []byte{
				OpcodeI32Const, 1, // offset
				OpcodeRefNull, RefTypeFuncref.Kind(),
				OpcodeI32Const, 1, // number of elements
				OpcodeMiscPrefix, OpcodeMiscTableFill,
				1, // Table of externref type -> mismatch.
				OpcodeEnd,
			},
			flag:        api.CoreFeatureReferenceTypes,
			expectedErr: `cannot pop the operand for table.fill: type mismatch: expected externref, but was funcref`,
		},
		{
			name: "table.fill (externref)",
			body: []byte{
				OpcodeI32Const, 1, // offset
				OpcodeRefNull, RefTypeExternref.Kind(),
				OpcodeI32Const, 1, // number of elements
				OpcodeMiscPrefix, OpcodeMiscTableFill,
				1, // Table Index.
				OpcodeEnd,
			},
			flag: api.CoreFeatureReferenceTypes,
		},
		{
			name: "table.fill (externref) - type mismatch",
			body: []byte{
				OpcodeI32Const, 1, // offset
				OpcodeRefNull, RefTypeExternref.Kind(),
				OpcodeI32Const, 1, // number of elements
				OpcodeMiscPrefix, OpcodeMiscTableFill,
				0, // Table of funcref type -> mismatch.
				OpcodeEnd,
			},
			flag:        api.CoreFeatureReferenceTypes,
			expectedErr: `cannot pop the operand for table.fill: type mismatch: expected funcref, but was externref`,
		},
		{
			name: "table.fill - table not found",
			body: []byte{
				OpcodeMiscPrefix, OpcodeMiscTableFill,
				10, // Table Index.
				OpcodeEnd,
			},
			flag:        api.CoreFeatureReferenceTypes,
			expectedErr: `table of index 10 not found`,
		},
	}

	for _, tt := range tests {
		tc := tt
		t.Run(tc.name, func(t *testing.T) {
			m := &Module{
				TypeSection:     []FunctionType{v_v},
				FunctionSection: []Index{0},
				CodeSection:     []Code{{Body: tc.body}},
			}
			err := m.validateFunction(&stacks{}, tc.flag,
				0, []Index{0}, nil, nil, tables, nil, nil, bytes.NewReader(nil))
			if tc.expectedErr != "" {
				require.EqualError(t, err, tc.expectedErr)
			} else {
				require.NoError(t, err)
			}
		})
	}
}

func TestModule_funcValidation_TableGetSet(t *testing.T) {
	tables := []Table{{Type: RefTypeFuncref}, {Type: RefTypeExternref}}
	tests := []struct {
		name        string
		body        []byte
		flag        api.CoreFeatures
		expectedErr string
	}{
		{
			name: "table.get (funcref)",
			body: []byte{
				OpcodeI32Const, 0,
				OpcodeTableGet, 0,
				OpcodeRefIsNull,
				OpcodeDrop,
				OpcodeEnd,
			},
			flag: api.CoreFeatureReferenceTypes,
		},
		{
			name: "table.get (externref)",
			body: []byte{
				OpcodeI32Const, 0,
				OpcodeTableGet, 1,
				OpcodeRefIsNull,
				OpcodeDrop,
				OpcodeEnd,
			},
			flag: api.CoreFeatureReferenceTypes,
		},
		{
			name: "table.get (disabled)",
			body: []byte{
				OpcodeI32Const, 0,
				OpcodeTableGet, 0,
				OpcodeDrop,
				OpcodeEnd,
			},
			flag:        api.CoreFeaturesV1,
			expectedErr: `table.get is invalid as feature "reference-types" is disabled`,
		},
		{
			name: "table.set (funcref)",
			body: []byte{
				OpcodeI32Const, 0,
				OpcodeRefNull, ValueTypeFuncref.Kind(),
				OpcodeTableSet, 0,
				OpcodeEnd,
			},
			flag: api.CoreFeatureReferenceTypes,
		},
		{
			name: "table.set type mismatch (src=funcref, dst=externref)",
			body: []byte{
				OpcodeI32Const, 0,
				OpcodeRefNull, ValueTypeFuncref.Kind(),
				OpcodeTableSet, 1,
				OpcodeEnd,
			},
			flag:        api.CoreFeatureReferenceTypes,
			expectedErr: `cannot pop the operand for table.set: type mismatch: expected externref, but was funcref`,
		},
		{
			name: "table.set (externref)",
			body: []byte{
				OpcodeI32Const, 0,
				OpcodeRefNull, ValueTypeExternref.Kind(),
				OpcodeTableSet, 1,
				OpcodeEnd,
			},
			flag: api.CoreFeatureReferenceTypes,
		},
		{
			name: "table.set type mismatch (src=externref, dst=funcref)",
			body: []byte{
				OpcodeI32Const, 0,
				OpcodeRefNull, ValueTypeExternref.Kind(),
				OpcodeTableSet, 0,
				OpcodeEnd,
			},
			flag:        api.CoreFeatureReferenceTypes,
			expectedErr: `cannot pop the operand for table.set: type mismatch: expected funcref, but was externref`,
		},
		{
			name: "table.set (disabled)",
			body: []byte{
				OpcodeTableSet, 1,
				OpcodeEnd,
			},
			flag:        api.CoreFeaturesV1,
			expectedErr: `table.set is invalid as feature "reference-types" is disabled`,
		},
	}

	for _, tt := range tests {
		tc := tt
		t.Run(tc.name, func(t *testing.T) {
			m := &Module{
				TypeSection:     []FunctionType{v_v},
				FunctionSection: []Index{0},
				CodeSection:     []Code{{Body: tc.body}},
			}
			err := m.validateFunction(&stacks{}, tc.flag,
				0, []Index{0}, nil, nil, tables, nil, nil, bytes.NewReader(nil))
			if tc.expectedErr != "" {
				require.EqualError(t, err, tc.expectedErr)
			} else {
				require.NoError(t, err)
			}
		})
	}
}

func TestModule_funcValidation_Select_error(t *testing.T) {
	tests := []struct {
		name        string
		body        []byte
		flag        api.CoreFeatures
		expectedErr string
	}{
		{
			name: "typed_select (disabled)",
			body: []byte{
				OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeI32Const, 0,
				OpcodeTypedSelect, 1, ValueTypeI32.Kind(), // immediate vector's size must be one
				OpcodeDrop,
				OpcodeEnd,
			},
			flag:        api.CoreFeaturesV1,
			expectedErr: "typed_select is invalid as feature \"reference-types\" is disabled",
		},
		{
			name: "typed_select (too many immediate types)",
			body: []byte{
				OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeI32Const, 0,
				OpcodeTypedSelect, 2, // immediate vector's size must be one
			},
			flag:        api.CoreFeatureReferenceTypes,
			expectedErr: `too many type immediates for typed_select`,
		},
		{
			name: "typed_select (immediate type not found)",
			body: []byte{
				OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeI32Const, 0,
				OpcodeTypedSelect, 1, 0,
				OpcodeEnd,
			},
			flag:        api.CoreFeatureReferenceTypes,
			expectedErr: `invalid type unknown for typed_select`,
		},
	}

	for _, tt := range tests {
		tc := tt
		t.Run(tc.name, func(t *testing.T) {
			m := &Module{
				TypeSection:     []FunctionType{v_v},
				FunctionSection: []Index{0},
				CodeSection:     []Code{{Body: tc.body}},
			}
			err := m.validateFunction(&stacks{}, tc.flag,
				0, []Index{0}, nil, nil, nil, nil, nil, bytes.NewReader(nil))
			require.EqualError(t, err, tc.expectedErr)
		})
	}
}

func TestModule_funcValidation_SIMD(t *testing.T) {
	addV128Const := func(in []byte) []byte {
		return append(in, OpcodeVecPrefix,
			OpcodeVecV128Const,
			1, 1, 1, 1, 1, 1, 1, 1,
			1, 1, 1, 1, 1, 1, 1, 1)
	}
	vv2v := func(vec OpcodeVec) (ret []byte) {
		ret = addV128Const(ret)
		ret = addV128Const(ret)
		return append(ret,
			OpcodeVecPrefix,
			vec,
			OpcodeDrop,
			OpcodeEnd,
		)
	}
	vvv2v := func(vec OpcodeVec) (ret []byte) {
		ret = addV128Const(ret)
		ret = addV128Const(ret)
		ret = addV128Const(ret)
		return append(ret,
			OpcodeVecPrefix,
			vec,
			OpcodeDrop,
			OpcodeEnd,
		)
	}

	v2v := func(vec OpcodeVec) (ret []byte) {
		ret = addV128Const(ret)
		return append(ret,
			OpcodeVecPrefix,
			vec,
			OpcodeDrop,
			OpcodeEnd,
		)
	}

	vi2v := func(vec OpcodeVec) (ret []byte) {
		ret = addV128Const(ret)
		return append(ret,
			OpcodeI32Const, 1,
			OpcodeVecPrefix,
			vec,
			OpcodeDrop,
			OpcodeEnd,
		)
	}

	load := func(vec OpcodeVec, offset, align uint32) (ret []byte) {
		ret = []byte{
			OpcodeI32Const, 1,
			OpcodeVecPrefix,
			vec,
		}

		ret = append(ret, leb128.EncodeUint32(align)...)
		ret = append(ret, leb128.EncodeUint32(offset)...)
		ret = append(ret,
			OpcodeDrop,
			OpcodeEnd,
		)
		return
	}

	loadLane := func(vec OpcodeVec, offset, align uint32, lane byte) (ret []byte) {
		ret = addV128Const([]byte{OpcodeI32Const, 1})
		ret = append(ret,
			OpcodeVecPrefix,
			vec,
		)

		ret = append(ret, leb128.EncodeUint32(align)...)
		ret = append(ret, leb128.EncodeUint32(offset)...)
		ret = append(ret,
			lane,
			OpcodeDrop,
			OpcodeEnd,
		)
		return
	}

	storeLane := func(vec OpcodeVec, offset, align uint32, lane byte) (ret []byte) {
		ret = addV128Const([]byte{OpcodeI32Const, 1})
		ret = append(ret,
			OpcodeVecPrefix,
			vec,
		)
		ret = append(ret, leb128.EncodeUint32(align)...)
		ret = append(ret, leb128.EncodeUint32(offset)...)
		ret = append(ret,
			lane,
			OpcodeEnd,
		)
		return
	}

	extractLane := func(vec OpcodeVec, lane byte) (ret []byte) {
		ret = addV128Const(ret)
		ret = append(ret,
			OpcodeVecPrefix,
			vec,
			lane,
			OpcodeDrop,
			OpcodeEnd,
		)
		return
	}

	replaceLane := func(vec OpcodeVec, lane byte) (ret []byte) {
		ret = addV128Const(ret)

		switch vec {
		case OpcodeVecI8x16ReplaceLane, OpcodeVecI16x8ReplaceLane, OpcodeVecI32x4ReplaceLane:
			ret = append(ret, OpcodeI32Const, 0)
		case OpcodeVecI64x2ReplaceLane:
			ret = append(ret, OpcodeI64Const, 0)
		case OpcodeVecF32x4ReplaceLane:
			ret = append(ret, OpcodeF32Const, 0, 0, 0, 0)
		case OpcodeVecF64x2ReplaceLane:
			ret = append(ret, OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0, 0)
		}

		ret = append(ret,
			OpcodeVecPrefix,
			vec,
			lane,
			OpcodeDrop,
			OpcodeEnd,
		)
		return
	}

	splat := func(vec OpcodeVec) (ret []byte) {
		switch vec {
		case OpcodeVecI8x16Splat, OpcodeVecI16x8Splat, OpcodeVecI32x4Splat:
			ret = append(ret, OpcodeI32Const, 0, 0, 0, 0)
		case OpcodeVecI64x2Splat:
			ret = append(ret, OpcodeI64Const, 0, 0, 0, 0, 0, 0, 0, 0)
		case OpcodeVecF32x4Splat:
			ret = append(ret, OpcodeF32Const, 0, 0, 0, 0)
		case OpcodeVecF64x2Splat:
			ret = append(ret, OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0, 0)
		}

		ret = append(ret,
			OpcodeVecPrefix,
			vec,
			OpcodeDrop,
			OpcodeEnd,
		)
		return
	}

	tests := []struct {
		name        string
		body        []byte
		expectedErr string
	}{
		{
			name: "v128.const",
			body: []byte{
				OpcodeVecPrefix,
				OpcodeVecV128Const,
				1, 1, 1, 1, 1, 1, 1, 1,
				1, 1, 1, 1, 1, 1, 1, 1,
				OpcodeDrop,
				OpcodeEnd,
			},
		},
		{name: OpcodeVecI8x16AddName, body: vv2v(OpcodeVecI8x16Add)},
		{name: OpcodeVecI16x8AddName, body: vv2v(OpcodeVecI16x8Add)},
		{name: OpcodeVecI32x4AddName, body: vv2v(OpcodeVecI32x4Add)},
		{name: OpcodeVecI64x2AddName, body: vv2v(OpcodeVecI64x2Add)},
		{name: OpcodeVecI8x16SubName, body: vv2v(OpcodeVecI8x16Sub)},
		{name: OpcodeVecI16x8SubName, body: vv2v(OpcodeVecI16x8Sub)},
		{name: OpcodeVecI32x4SubName, body: vv2v(OpcodeVecI32x4Sub)},
		{name: OpcodeVecI64x2SubName, body: vv2v(OpcodeVecI64x2Sub)},
		{name: OpcodeVecV128AnyTrueName, body: v2v(OpcodeVecV128AnyTrue)},
		{name: OpcodeVecI8x16AllTrueName, body: v2v(OpcodeVecI8x16AllTrue)},
		{name: OpcodeVecI16x8AllTrueName, body: v2v(OpcodeVecI16x8AllTrue)},
		{name: OpcodeVecI32x4AllTrueName, body: v2v(OpcodeVecI32x4AllTrue)},
		{name: OpcodeVecI64x2AllTrueName, body: v2v(OpcodeVecI64x2AllTrue)},
		{name: OpcodeVecI8x16BitMaskName, body: v2v(OpcodeVecI8x16BitMask)},
		{name: OpcodeVecI16x8BitMaskName, body: v2v(OpcodeVecI16x8BitMask)},
		{name: OpcodeVecI32x4BitMaskName, body: v2v(OpcodeVecI32x4BitMask)},
		{name: OpcodeVecI64x2BitMaskName, body: v2v(OpcodeVecI64x2BitMask)},
		{name: OpcodeVecV128LoadName, body: load(OpcodeVecV128Load, 0, 0)},
		{name: OpcodeVecV128LoadName + "/align=4", body: load(OpcodeVecV128Load, 0, 4)},
		{name: OpcodeVecV128Load8x8SName, body: load(OpcodeVecV128Load8x8s, 1, 0)},
		{name: OpcodeVecV128Load8x8SName + "/align=1", body: load(OpcodeVecV128Load8x8s, 0, 1)},
		{name: OpcodeVecV128Load8x8UName, body: load(OpcodeVecV128Load8x8u, 0, 0)},
		{name: OpcodeVecV128Load8x8UName + "/align=1", body: load(OpcodeVecV128Load8x8u, 0, 1)},
		{name: OpcodeVecV128Load16x4SName, body: load(OpcodeVecV128Load16x4s, 1, 0)},
		{name: OpcodeVecV128Load16x4SName + "/align=2", body: load(OpcodeVecV128Load16x4s, 0, 2)},
		{name: OpcodeVecV128Load16x4UName, body: load(OpcodeVecV128Load16x4u, 0, 0)},
		{name: OpcodeVecV128Load16x4UName + "/align=2", body: load(OpcodeVecV128Load16x4u, 0, 2)},
		{name: OpcodeVecV128Load32x2SName, body: load(OpcodeVecV128Load32x2s, 1, 0)},
		{name: OpcodeVecV128Load32x2SName + "/align=3", body: load(OpcodeVecV128Load32x2s, 0, 3)},
		{name: OpcodeVecV128Load32x2UName, body: load(OpcodeVecV128Load32x2u, 0, 0)},
		{name: OpcodeVecV128Load32x2UName + "/align=3", body: load(OpcodeVecV128Load32x2u, 0, 3)},
		{name: OpcodeVecV128Load8SplatName, body: load(OpcodeVecV128Load8Splat, 2, 0)},
		{name: OpcodeVecV128Load16SplatName, body: load(OpcodeVecV128Load16Splat, 0, 1)},
		{name: OpcodeVecV128Load32SplatName, body: load(OpcodeVecV128Load32Splat, 3, 2)},
		{name: OpcodeVecV128Load64SplatName, body: load(OpcodeVecV128Load64Splat, 0, 3)},
		{name: OpcodeVecV128Load32zeroName, body: load(OpcodeVecV128Load32zero, 0, 2)},
		{name: OpcodeVecV128Load64zeroName, body: load(OpcodeVecV128Load64zero, 5, 3)},
		{name: OpcodeVecV128Load8LaneName, body: loadLane(OpcodeVecV128Load8Lane, 5, 0, 10)},
		{name: OpcodeVecV128Load16LaneName, body: loadLane(OpcodeVecV128Load16Lane, 100, 1, 7)},
		{name: OpcodeVecV128Load32LaneName, body: loadLane(OpcodeVecV128Load32Lane, 0, 2, 3)},
		{name: OpcodeVecV128Load64LaneName, body: loadLane(OpcodeVecV128Load64Lane, 0, 3, 1)},
		{
			name: OpcodeVecV128StoreName, body: []byte{
				OpcodeI32Const,
				1, 1, 1, 1,
				OpcodeVecPrefix,
				OpcodeVecV128Const,
				1, 1, 1, 1, 1, 1, 1, 1,
				1, 1, 1, 1, 1, 1, 1, 1,
				OpcodeVecPrefix,
				OpcodeVecV128Store,
				4,  // alignment
				10, // offset
				OpcodeEnd,
			},
		},
		{name: OpcodeVecV128Store8LaneName, body: storeLane(OpcodeVecV128Store8Lane, 0, 0, 0)},
		{name: OpcodeVecV128Store8LaneName + "/lane=15", body: storeLane(OpcodeVecV128Store8Lane, 100, 0, 15)},
		{name: OpcodeVecV128Store16LaneName, body: storeLane(OpcodeVecV128Store16Lane, 0, 0, 0)},
		{name: OpcodeVecV128Store16LaneName + "/lane=7/align=1", body: storeLane(OpcodeVecV128Store16Lane, 100, 1, 7)},
		{name: OpcodeVecV128Store32LaneName, body: storeLane(OpcodeVecV128Store32Lane, 0, 0, 0)},
		{name: OpcodeVecV128Store32LaneName + "/lane=3/align=2", body: storeLane(OpcodeVecV128Store32Lane, 100, 2, 3)},
		{name: OpcodeVecV128Store64LaneName, body: storeLane(OpcodeVecV128Store64Lane, 0, 0, 0)},
		{name: OpcodeVecV128Store64LaneName + "/lane=1/align=3", body: storeLane(OpcodeVecV128Store64Lane, 50, 3, 1)},
		{name: OpcodeVecI8x16ExtractLaneSName, body: extractLane(OpcodeVecI8x16ExtractLaneS, 0)},
		{name: OpcodeVecI8x16ExtractLaneSName + "/lane=15", body: extractLane(OpcodeVecI8x16ExtractLaneS, 15)},
		{name: OpcodeVecI8x16ExtractLaneUName, body: extractLane(OpcodeVecI8x16ExtractLaneU, 0)},
		{name: OpcodeVecI8x16ExtractLaneUName + "/lane=15", body: extractLane(OpcodeVecI8x16ExtractLaneU, 15)},
		{name: OpcodeVecI16x8ExtractLaneSName, body: extractLane(OpcodeVecI16x8ExtractLaneS, 0)},
		{name: OpcodeVecI16x8ExtractLaneSName + "/lane=7", body: extractLane(OpcodeVecI16x8ExtractLaneS, 7)},
		{name: OpcodeVecI16x8ExtractLaneUName, body: extractLane(OpcodeVecI16x8ExtractLaneU, 0)},
		{name: OpcodeVecI16x8ExtractLaneUName + "/lane=8", body: extractLane(OpcodeVecI16x8ExtractLaneU, 7)},
		{name: OpcodeVecI32x4ExtractLaneName, body: extractLane(OpcodeVecI32x4ExtractLane, 0)},
		{name: OpcodeVecI32x4ExtractLaneName + "/lane=3", body: extractLane(OpcodeVecI32x4ExtractLane, 3)},
		{name: OpcodeVecI64x2ExtractLaneName, body: extractLane(OpcodeVecI64x2ExtractLane, 0)},
		{name: OpcodeVecI64x2ExtractLaneName + "/lane=1", body: extractLane(OpcodeVecI64x2ExtractLane, 1)},
		{name: OpcodeVecF32x4ExtractLaneName, body: extractLane(OpcodeVecF32x4ExtractLane, 0)},
		{name: OpcodeVecF32x4ExtractLaneName + "/lane=3", body: extractLane(OpcodeVecF32x4ExtractLane, 3)},
		{name: OpcodeVecF64x2ExtractLaneName, body: extractLane(OpcodeVecF64x2ExtractLane, 0)},
		{name: OpcodeVecF64x2ExtractLaneName + "/lane=1", body: extractLane(OpcodeVecF64x2ExtractLane, 1)},
		{name: OpcodeVecI8x16ReplaceLaneName, body: replaceLane(OpcodeVecI8x16ReplaceLane, 0)},
		{name: OpcodeVecI8x16ReplaceLaneName + "/lane=15", body: replaceLane(OpcodeVecI8x16ReplaceLane, 15)},
		{name: OpcodeVecI16x8ReplaceLaneName, body: replaceLane(OpcodeVecI16x8ReplaceLane, 0)},
		{name: OpcodeVecI16x8ReplaceLaneName + "/lane=7", body: replaceLane(OpcodeVecI16x8ReplaceLane, 7)},
		{name: OpcodeVecI32x4ReplaceLaneName, body: replaceLane(OpcodeVecI32x4ReplaceLane, 0)},
		{name: OpcodeVecI32x4ReplaceLaneName + "/lane=3", body: replaceLane(OpcodeVecI32x4ReplaceLane, 3)},
		{name: OpcodeVecI64x2ReplaceLaneName, body: replaceLane(OpcodeVecI64x2ReplaceLane, 0)},
		{name: OpcodeVecI64x2ReplaceLaneName + "/lane=1", body: replaceLane(OpcodeVecI64x2ReplaceLane, 1)},
		{name: OpcodeVecF32x4ReplaceLaneName, body: replaceLane(OpcodeVecF32x4ReplaceLane, 0)},
		{name: OpcodeVecF32x4ReplaceLaneName + "/lane=3", body: replaceLane(OpcodeVecF32x4ReplaceLane, 3)},
		{name: OpcodeVecF64x2ReplaceLaneName, body: replaceLane(OpcodeVecF64x2ReplaceLane, 0)},
		{name: OpcodeVecF64x2ReplaceLaneName + "/lane=1", body: replaceLane(OpcodeVecF64x2ReplaceLane, 1)},
		{name: OpcodeVecI8x16SplatName, body: splat(OpcodeVecI8x16Splat)},
		{name: OpcodeVecI16x8SplatName, body: splat(OpcodeVecI16x8Splat)},
		{name: OpcodeVecI32x4SplatName, body: splat(OpcodeVecI32x4Splat)},
		{name: OpcodeVecI64x2SplatName, body: splat(OpcodeVecI64x2Splat)},
		{name: OpcodeVecF32x4SplatName, body: splat(OpcodeVecF32x4Splat)},
		{name: OpcodeVecF64x2SplatName, body: splat(OpcodeVecF64x2Splat)},
		{name: OpcodeVecI8x16SwizzleName, body: vv2v(OpcodeVecI8x16Swizzle)},
		{
			name: OpcodeVecV128i8x16ShuffleName, body: []byte{
				OpcodeVecPrefix,
				OpcodeVecV128Const,
				1, 1, 1, 1, 1, 1, 1, 1,
				1, 1, 1, 1, 1, 1, 1, 1,
				OpcodeVecPrefix,
				OpcodeVecV128Const,
				1, 1, 1, 1, 1, 1, 1, 1,
				1, 1, 1, 1, 1, 1, 1, 1,
				OpcodeVecPrefix,
				OpcodeVecV128i8x16Shuffle,
				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
				OpcodeDrop,
				OpcodeEnd,
			},
		},
		{name: OpcodeVecV128NotName, body: v2v(OpcodeVecV128Not)},
		{name: OpcodeVecV128AndName, body: vv2v(OpcodeVecV128And)},
		{name: OpcodeVecV128AndNotName, body: vv2v(OpcodeVecV128AndNot)},
		{name: OpcodeVecV128OrName, body: vv2v(OpcodeVecV128Or)},
		{name: OpcodeVecV128XorName, body: vv2v(OpcodeVecV128Xor)},
		{name: OpcodeVecV128BitselectName, body: vvv2v(OpcodeVecV128Bitselect)},
		{name: OpcodeVecI8x16ShlName, body: vi2v(OpcodeVecI8x16Shl)},
		{name: OpcodeVecI8x16ShrSName, body: vi2v(OpcodeVecI8x16ShrS)},
		{name: OpcodeVecI8x16ShrUName, body: vi2v(OpcodeVecI8x16ShrU)},
		{name: OpcodeVecI16x8ShlName, body: vi2v(OpcodeVecI16x8Shl)},
		{name: OpcodeVecI16x8ShrSName, body: vi2v(OpcodeVecI16x8ShrS)},
		{name: OpcodeVecI16x8ShrUName, body: vi2v(OpcodeVecI16x8ShrU)},
		{name: OpcodeVecI32x4ShlName, body: vi2v(OpcodeVecI32x4Shl)},
		{name: OpcodeVecI32x4ShrSName, body: vi2v(OpcodeVecI32x4ShrS)},
		{name: OpcodeVecI32x4ShrUName, body: vi2v(OpcodeVecI32x4ShrU)},
		{name: OpcodeVecI64x2ShlName, body: vi2v(OpcodeVecI64x2Shl)},
		{name: OpcodeVecI64x2ShrSName, body: vi2v(OpcodeVecI64x2ShrS)},
		{name: OpcodeVecI64x2ShrUName, body: vi2v(OpcodeVecI64x2ShrU)},
		{name: OpcodeVecI8x16EqName, body: vv2v(OpcodeVecI8x16Eq)},
		{name: OpcodeVecI8x16NeName, body: vv2v(OpcodeVecI8x16Ne)},
		{name: OpcodeVecI8x16LtSName, body: vv2v(OpcodeVecI8x16LtS)},
		{name: OpcodeVecI8x16LtUName, body: vv2v(OpcodeVecI8x16LtU)},
		{name: OpcodeVecI8x16GtSName, body: vv2v(OpcodeVecI8x16GtS)},
		{name: OpcodeVecI8x16GtUName, body: vv2v(OpcodeVecI8x16GtU)},
		{name: OpcodeVecI8x16LeSName, body: vv2v(OpcodeVecI8x16LeS)},
		{name: OpcodeVecI8x16LeUName, body: vv2v(OpcodeVecI8x16LeU)},
		{name: OpcodeVecI8x16GeSName, body: vv2v(OpcodeVecI8x16GeS)},
		{name: OpcodeVecI8x16GeUName, body: vv2v(OpcodeVecI8x16GeU)},
		{name: OpcodeVecI16x8EqName, body: vv2v(OpcodeVecI16x8Eq)},
		{name: OpcodeVecI16x8NeName, body: vv2v(OpcodeVecI16x8Ne)},
		{name: OpcodeVecI16x8LtSName, body: vv2v(OpcodeVecI16x8LtS)},
		{name: OpcodeVecI16x8LtUName, body: vv2v(OpcodeVecI16x8LtU)},
		{name: OpcodeVecI16x8GtSName, body: vv2v(OpcodeVecI16x8GtS)},
		{name: OpcodeVecI16x8GtUName, body: vv2v(OpcodeVecI16x8GtU)},
		{name: OpcodeVecI16x8LeSName, body: vv2v(OpcodeVecI16x8LeS)},
		{name: OpcodeVecI16x8LeUName, body: vv2v(OpcodeVecI16x8LeU)},
		{name: OpcodeVecI16x8GeSName, body: vv2v(OpcodeVecI16x8GeS)},
		{name: OpcodeVecI16x8GeUName, body: vv2v(OpcodeVecI16x8GeU)},
		{name: OpcodeVecI32x4EqName, body: vv2v(OpcodeVecI32x4Eq)},
		{name: OpcodeVecI32x4NeName, body: vv2v(OpcodeVecI32x4Ne)},
		{name: OpcodeVecI32x4LtSName, body: vv2v(OpcodeVecI32x4LtS)},
		{name: OpcodeVecI32x4LtUName, body: vv2v(OpcodeVecI32x4LtU)},
		{name: OpcodeVecI32x4GtSName, body: vv2v(OpcodeVecI32x4GtS)},
		{name: OpcodeVecI32x4GtUName, body: vv2v(OpcodeVecI32x4GtU)},
		{name: OpcodeVecI32x4LeSName, body: vv2v(OpcodeVecI32x4LeS)},
		{name: OpcodeVecI32x4LeUName, body: vv2v(OpcodeVecI32x4LeU)},
		{name: OpcodeVecI32x4GeSName, body: vv2v(OpcodeVecI32x4GeS)},
		{name: OpcodeVecI32x4GeUName, body: vv2v(OpcodeVecI32x4GeU)},
		{name: OpcodeVecI64x2EqName, body: vv2v(OpcodeVecI64x2Eq)},
		{name: OpcodeVecI64x2NeName, body: vv2v(OpcodeVecI64x2Ne)},
		{name: OpcodeVecI64x2LtSName, body: vv2v(OpcodeVecI64x2LtS)},
		{name: OpcodeVecI64x2GtSName, body: vv2v(OpcodeVecI64x2GtS)},
		{name: OpcodeVecI64x2LeSName, body: vv2v(OpcodeVecI64x2LeS)},
		{name: OpcodeVecI64x2GeSName, body: vv2v(OpcodeVecI64x2GeS)},
		{name: OpcodeVecF32x4EqName, body: vv2v(OpcodeVecF32x4Eq)},
		{name: OpcodeVecF32x4NeName, body: vv2v(OpcodeVecF32x4Ne)},
		{name: OpcodeVecF32x4LtName, body: vv2v(OpcodeVecF32x4Lt)},
		{name: OpcodeVecF32x4GtName, body: vv2v(OpcodeVecF32x4Gt)},
		{name: OpcodeVecF32x4LeName, body: vv2v(OpcodeVecF32x4Le)},
		{name: OpcodeVecF32x4GeName, body: vv2v(OpcodeVecF32x4Ge)},
		{name: OpcodeVecF64x2EqName, body: vv2v(OpcodeVecF64x2Eq)},
		{name: OpcodeVecF64x2NeName, body: vv2v(OpcodeVecF64x2Ne)},
		{name: OpcodeVecF64x2LtName, body: vv2v(OpcodeVecF64x2Lt)},
		{name: OpcodeVecF64x2GtName, body: vv2v(OpcodeVecF64x2Gt)},
		{name: OpcodeVecF64x2LeName, body: vv2v(OpcodeVecF64x2Le)},
		{name: OpcodeVecF64x2GeName, body: vv2v(OpcodeVecF64x2Ge)},
		{name: OpcodeVecI8x16AddName, body: vv2v(OpcodeVecI8x16Add)},
		{name: OpcodeVecI8x16AddSatSName, body: vv2v(OpcodeVecI8x16AddSatS)},
		{name: OpcodeVecI8x16AddSatUName, body: vv2v(OpcodeVecI8x16AddSatU)},
		{name: OpcodeVecI8x16SubName, body: vv2v(OpcodeVecI8x16Sub)},
		{name: OpcodeVecI8x16SubSatSName, body: vv2v(OpcodeVecI8x16SubSatS)},
		{name: OpcodeVecI8x16SubSatUName, body: vv2v(OpcodeVecI8x16SubSatU)},
		{name: OpcodeVecI16x8AddName, body: vv2v(OpcodeVecI16x8Add)},
		{name: OpcodeVecI16x8AddSatSName, body: vv2v(OpcodeVecI16x8AddSatS)},
		{name: OpcodeVecI16x8AddSatUName, body: vv2v(OpcodeVecI16x8AddSatU)},
		{name: OpcodeVecI16x8SubName, body: vv2v(OpcodeVecI16x8Sub)},
		{name: OpcodeVecI16x8SubSatSName, body: vv2v(OpcodeVecI16x8SubSatS)},
		{name: OpcodeVecI16x8SubSatUName, body: vv2v(OpcodeVecI16x8SubSatU)},
		{name: OpcodeVecI16x8MulName, body: vv2v(OpcodeVecI16x8Mul)},
		{name: OpcodeVecI32x4AddName, body: vv2v(OpcodeVecI32x4Add)},
		{name: OpcodeVecI32x4SubName, body: vv2v(OpcodeVecI32x4Sub)},
		{name: OpcodeVecI32x4MulName, body: vv2v(OpcodeVecI32x4Mul)},
		{name: OpcodeVecI64x2AddName, body: vv2v(OpcodeVecI64x2Add)},
		{name: OpcodeVecI64x2SubName, body: vv2v(OpcodeVecI64x2Sub)},
		{name: OpcodeVecI64x2MulName, body: vv2v(OpcodeVecI64x2Mul)},
		{name: OpcodeVecF32x4AddName, body: vv2v(OpcodeVecF32x4Add)},
		{name: OpcodeVecF32x4SubName, body: vv2v(OpcodeVecF32x4Sub)},
		{name: OpcodeVecF32x4MulName, body: vv2v(OpcodeVecF32x4Mul)},
		{name: OpcodeVecF32x4DivName, body: vv2v(OpcodeVecF32x4Div)},
		{name: OpcodeVecF64x2AddName, body: vv2v(OpcodeVecF64x2Add)},
		{name: OpcodeVecF64x2SubName, body: vv2v(OpcodeVecF64x2Sub)},
		{name: OpcodeVecF64x2MulName, body: vv2v(OpcodeVecF64x2Mul)},
		{name: OpcodeVecF64x2DivName, body: vv2v(OpcodeVecF64x2Div)},
		{name: OpcodeVecI8x16NegName, body: v2v(OpcodeVecI8x16Neg)},
		{name: OpcodeVecI16x8NegName, body: v2v(OpcodeVecI16x8Neg)},
		{name: OpcodeVecI32x4NegName, body: v2v(OpcodeVecI32x4Neg)},
		{name: OpcodeVecI64x2NegName, body: v2v(OpcodeVecI64x2Neg)},
		{name: OpcodeVecF32x4NegName, body: v2v(OpcodeVecF32x4Neg)},
		{name: OpcodeVecF64x2NegName, body: v2v(OpcodeVecF64x2Neg)},
		{name: OpcodeVecF32x4SqrtName, body: v2v(OpcodeVecF32x4Sqrt)},
		{name: OpcodeVecF64x2SqrtName, body: v2v(OpcodeVecF64x2Sqrt)},
		{name: OpcodeVecI8x16MinSName, body: vv2v(OpcodeVecI8x16MinS)},
		{name: OpcodeVecI8x16MinUName, body: vv2v(OpcodeVecI8x16MinU)},
		{name: OpcodeVecI8x16MaxSName, body: vv2v(OpcodeVecI8x16MaxS)},
		{name: OpcodeVecI8x16MaxUName, body: vv2v(OpcodeVecI8x16MaxU)},
		{name: OpcodeVecI8x16AvgrUName, body: vv2v(OpcodeVecI8x16AvgrU)},
		{name: OpcodeVecI8x16AbsName, body: v2v(OpcodeVecI8x16Abs)},
		{name: OpcodeVecI8x16PopcntName, body: v2v(OpcodeVecI8x16Popcnt)},
		{name: OpcodeVecI16x8MinSName, body: vv2v(OpcodeVecI16x8MinS)},
		{name: OpcodeVecI16x8MinUName, body: vv2v(OpcodeVecI16x8MinU)},
		{name: OpcodeVecI16x8MaxSName, body: vv2v(OpcodeVecI16x8MaxS)},
		{name: OpcodeVecI16x8MaxUName, body: vv2v(OpcodeVecI16x8MaxU)},
		{name: OpcodeVecI16x8AvgrUName, body: vv2v(OpcodeVecI16x8AvgrU)},
		{name: OpcodeVecI16x8AbsName, body: v2v(OpcodeVecI16x8Abs)},
		{name: OpcodeVecI32x4MinSName, body: vv2v(OpcodeVecI32x4MinS)},
		{name: OpcodeVecI32x4MinUName, body: vv2v(OpcodeVecI32x4MinU)},
		{name: OpcodeVecI32x4MaxSName, body: vv2v(OpcodeVecI32x4MaxS)},
		{name: OpcodeVecI32x4MaxUName, body: vv2v(OpcodeVecI32x4MaxU)},
		{name: OpcodeVecI32x4AbsName, body: v2v(OpcodeVecI32x4Abs)},
		{name: OpcodeVecI64x2AbsName, body: v2v(OpcodeVecI64x2Abs)},
		{name: OpcodeVecF32x4AbsName, body: v2v(OpcodeVecF32x4Abs)},
		{name: OpcodeVecF64x2AbsName, body: v2v(OpcodeVecF64x2Abs)},
		{name: OpcodeVecF32x4MinName, body: vv2v(OpcodeVecF32x4Min)},
		{name: OpcodeVecF32x4MaxName, body: vv2v(OpcodeVecF32x4Max)},
		{name: OpcodeVecF64x2MinName, body: vv2v(OpcodeVecF64x2Min)},
		{name: OpcodeVecF64x2MaxName, body: vv2v(OpcodeVecF64x2Max)},
		{name: OpcodeVecF32x4CeilName, body: v2v(OpcodeVecF32x4Ceil)},
		{name: OpcodeVecF32x4FloorName, body: v2v(OpcodeVecF32x4Floor)},
		{name: OpcodeVecF32x4TruncName, body: v2v(OpcodeVecF32x4Trunc)},
		{name: OpcodeVecF32x4NearestName, body: v2v(OpcodeVecF32x4Nearest)},
		{name: OpcodeVecF64x2CeilName, body: v2v(OpcodeVecF64x2Ceil)},
		{name: OpcodeVecF64x2FloorName, body: v2v(OpcodeVecF64x2Floor)},
		{name: OpcodeVecF64x2TruncName, body: v2v(OpcodeVecF64x2Trunc)},
		{name: OpcodeVecF64x2NearestName, body: v2v(OpcodeVecF64x2Nearest)},
		{name: OpcodeVecF32x4MinName, body: vv2v(OpcodeVecF32x4Pmin)},
		{name: OpcodeVecF32x4MaxName, body: vv2v(OpcodeVecF32x4Pmax)},
		{name: OpcodeVecF64x2MinName, body: vv2v(OpcodeVecF64x2Pmin)},
		{name: OpcodeVecF64x2MaxName, body: vv2v(OpcodeVecF64x2Pmax)},
		{name: OpcodeVecI16x8ExtendLowI8x16SName, body: v2v(OpcodeVecI16x8ExtendLowI8x16S)},
		{name: OpcodeVecI16x8ExtendHighI8x16SName, body: v2v(OpcodeVecI16x8ExtendHighI8x16S)},
		{name: OpcodeVecI16x8ExtendLowI8x16UName, body: v2v(OpcodeVecI16x8ExtendLowI8x16U)},
		{name: OpcodeVecI16x8ExtendHighI8x16UName, body: v2v(OpcodeVecI16x8ExtendHighI8x16U)},
		{name: OpcodeVecI32x4ExtendLowI16x8SName, body: v2v(OpcodeVecI32x4ExtendLowI16x8S)},
		{name: OpcodeVecI32x4ExtendHighI16x8SName, body: v2v(OpcodeVecI32x4ExtendHighI16x8S)},
		{name: OpcodeVecI32x4ExtendLowI16x8UName, body: v2v(OpcodeVecI32x4ExtendLowI16x8U)},
		{name: OpcodeVecI32x4ExtendHighI16x8UName, body: v2v(OpcodeVecI32x4ExtendHighI16x8U)},
		{name: OpcodeVecI64x2ExtendLowI32x4SName, body: v2v(OpcodeVecI64x2ExtendLowI32x4S)},
		{name: OpcodeVecI64x2ExtendHighI32x4SName, body: v2v(OpcodeVecI64x2ExtendHighI32x4S)},
		{name: OpcodeVecI64x2ExtendLowI32x4UName, body: v2v(OpcodeVecI64x2ExtendLowI32x4U)},
		{name: OpcodeVecI64x2ExtendHighI32x4UName, body: v2v(OpcodeVecI64x2ExtendHighI32x4U)},
		{name: OpcodeVecI16x8Q15mulrSatSName, body: vv2v(OpcodeVecI16x8Q15mulrSatS)},
		{name: OpcodeVecI16x8ExtMulLowI8x16SName, body: vv2v(OpcodeVecI16x8ExtMulLowI8x16S)},
		{name: OpcodeVecI16x8ExtMulHighI8x16SName, body: vv2v(OpcodeVecI16x8ExtMulHighI8x16S)},
		{name: OpcodeVecI16x8ExtMulLowI8x16UName, body: vv2v(OpcodeVecI16x8ExtMulLowI8x16U)},
		{name: OpcodeVecI16x8ExtMulHighI8x16UName, body: vv2v(OpcodeVecI16x8ExtMulHighI8x16U)},
		{name: OpcodeVecI32x4ExtMulLowI16x8SName, body: vv2v(OpcodeVecI32x4ExtMulLowI16x8S)},
		{name: OpcodeVecI32x4ExtMulHighI16x8SName, body: vv2v(OpcodeVecI32x4ExtMulHighI16x8S)},
		{name: OpcodeVecI32x4ExtMulLowI16x8UName, body: vv2v(OpcodeVecI32x4ExtMulLowI16x8U)},
		{name: OpcodeVecI32x4ExtMulHighI16x8UName, body: vv2v(OpcodeVecI32x4ExtMulHighI16x8U)},
		{name: OpcodeVecI64x2ExtMulLowI32x4SName, body: vv2v(OpcodeVecI64x2ExtMulLowI32x4S)},
		{name: OpcodeVecI64x2ExtMulHighI32x4SName, body: vv2v(OpcodeVecI64x2ExtMulHighI32x4S)},
		{name: OpcodeVecI64x2ExtMulLowI32x4UName, body: vv2v(OpcodeVecI64x2ExtMulLowI32x4U)},
		{name: OpcodeVecI64x2ExtMulHighI32x4UName, body: vv2v(OpcodeVecI64x2ExtMulHighI32x4U)},
		{name: OpcodeVecI16x8ExtaddPairwiseI8x16SName, body: v2v(OpcodeVecI16x8ExtaddPairwiseI8x16S)},
		{name: OpcodeVecI16x8ExtaddPairwiseI8x16UName, body: v2v(OpcodeVecI16x8ExtaddPairwiseI8x16U)},
		{name: OpcodeVecI32x4ExtaddPairwiseI16x8SName, body: v2v(OpcodeVecI32x4ExtaddPairwiseI16x8S)},
		{name: OpcodeVecI32x4ExtaddPairwiseI16x8UName, body: v2v(OpcodeVecI32x4ExtaddPairwiseI16x8U)},
		{name: OpcodeVecF64x2PromoteLowF32x4ZeroName, body: v2v(OpcodeVecF64x2PromoteLowF32x4Zero)},
		{name: OpcodeVecF32x4DemoteF64x2ZeroName, body: v2v(OpcodeVecF32x4DemoteF64x2Zero)},
		{name: OpcodeVecF32x4ConvertI32x4SName, body: v2v(OpcodeVecF32x4ConvertI32x4S)},
		{name: OpcodeVecF32x4ConvertI32x4UName, body: v2v(OpcodeVecF32x4ConvertI32x4U)},
		{name: OpcodeVecF64x2ConvertLowI32x4SName, body: v2v(OpcodeVecF64x2ConvertLowI32x4S)},
		{name: OpcodeVecF64x2ConvertLowI32x4UName, body: v2v(OpcodeVecF64x2ConvertLowI32x4U)},
		{name: OpcodeVecI32x4DotI16x8SName, body: vv2v(OpcodeVecI32x4DotI16x8S)},
		{name: OpcodeVecI8x16NarrowI16x8SName, body: vv2v(OpcodeVecI8x16NarrowI16x8S)},
		{name: OpcodeVecI8x16NarrowI16x8UName, body: vv2v(OpcodeVecI8x16NarrowI16x8U)},
		{name: OpcodeVecI16x8NarrowI32x4SName, body: vv2v(OpcodeVecI16x8NarrowI32x4S)},
		{name: OpcodeVecI16x8NarrowI32x4UName, body: vv2v(OpcodeVecI16x8NarrowI32x4U)},
		{name: OpcodeVecI32x4TruncSatF32x4SName, body: v2v(OpcodeVecI32x4TruncSatF32x4S)},
		{name: OpcodeVecI32x4TruncSatF32x4UName, body: v2v(OpcodeVecI32x4TruncSatF32x4U)},
		{name: OpcodeVecI32x4TruncSatF64x2SZeroName, body: v2v(OpcodeVecI32x4TruncSatF64x2SZero)},
		{name: OpcodeVecI32x4TruncSatF64x2UZeroName, body: v2v(OpcodeVecI32x4TruncSatF64x2UZero)},
	}

	for _, tt := range tests {
		tc := tt
		t.Run(tc.name, func(t *testing.T) {
			m := &Module{
				TypeSection:     []FunctionType{v_v},
				FunctionSection: []Index{0},
				CodeSection:     []Code{{Body: tc.body}},
			}
			err := m.validateFunction(&stacks{}, api.CoreFeatureSIMD,
				0, []Index{0}, nil, &Memory{}, nil, nil, nil, bytes.NewReader(nil))
			require.NoError(t, err)
		})
	}
}

func TestModule_funcValidation_SIMD_error(t *testing.T) {
	type testCase struct {
		name        string
		body        []byte
		flag        api.CoreFeatures
		expectedErr string
	}

	tests := []testCase{
		{
			name: "simd disabled",
			body: []byte{
				OpcodeVecPrefix,
				OpcodeVecF32x4Abs,
			},
			flag:        api.CoreFeaturesV1,
			expectedErr: "f32x4.abs invalid as feature \"simd\" is disabled",
		},
		{
			name: "v128.const immediate",
			body: []byte{
				OpcodeVecPrefix,
				OpcodeVecV128Const,
				1, 1, 1, 1, 1, 1, 1, 1,
				1, 1, 1, 1, 1, 1, 1,
			},
			flag:        api.CoreFeatureSIMD,
			expectedErr: "cannot read constant vector value for v128.const",
		},
		{
			name: "i32x4.add operand",
			body: []byte{
				OpcodeVecPrefix,
				OpcodeVecV128Const,
				1, 1, 1, 1, 1, 1, 1, 1,
				1, 1, 1, 1, 1, 1, 1, 1,
				OpcodeVecPrefix,
				OpcodeVecI32x4Add,
				OpcodeDrop,
				OpcodeEnd,
			},
			flag:        api.CoreFeatureSIMD,
			expectedErr: "cannot pop the operand for i32x4.add: v128 missing",
		},
		{
			name: "i64x2.add operand",
			body: []byte{
				OpcodeVecPrefix,
				OpcodeVecV128Const,
				1, 1, 1, 1, 1, 1, 1, 1,
				1, 1, 1, 1, 1, 1, 1, 1,
				OpcodeVecPrefix,
				OpcodeVecI64x2Add,
				OpcodeDrop,
				OpcodeEnd,
			},
			flag:        api.CoreFeatureSIMD,
			expectedErr: "cannot pop the operand for i64x2.add: v128 missing",
		},
		{
			name: "shuffle lane index not found",
			flag: api.CoreFeatureSIMD,
			body: []byte{
				OpcodeVecPrefix,
				OpcodeVecV128i8x16Shuffle,
			},
			expectedErr: "16 lane indexes for v128.shuffle not found",
		},
		{
			name: "shuffle lane index not found",
			flag: api.CoreFeatureSIMD,
			body: []byte{
				OpcodeVecPrefix,
				OpcodeVecV128i8x16Shuffle,
				0xff, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0,
			},
			expectedErr: "invalid lane index[0] 255 >= 32 for v128.shuffle",
		},
	}

	addExtractOrReplaceLaneOutOfIndexCase := func(op OpcodeVec, lane, laneCeil byte) {
		n := VectorInstructionName(op)
		tests = append(tests, testCase{
			name: n + "/lane index out of range",
			flag: api.CoreFeatureSIMD,
			body: []byte{
				OpcodeVecPrefix, op, lane,
			},
			expectedErr: fmt.Sprintf("invalid lane index %d >= %d for %s", lane, laneCeil, n),
		})
	}

	addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI8x16ExtractLaneS, 16, 16)
	addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI8x16ExtractLaneU, 20, 16)
	addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI16x8ExtractLaneS, 8, 8)
	addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI16x8ExtractLaneU, 8, 8)
	addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI32x4ExtractLane, 4, 4)
	addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecF32x4ExtractLane, 4, 4)
	addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI64x2ExtractLane, 2, 2)
	addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecF64x2ExtractLane, 2, 2)
	addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI8x16ReplaceLane, 16, 16)
	addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI16x8ReplaceLane, 8, 8)
	addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI32x4ReplaceLane, 4, 4)
	addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI64x2ReplaceLane, 2, 2)
	addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecF32x4ReplaceLane, 10, 4)
	addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecF64x2ReplaceLane, 3, 2)

	addStoreOrLoadLaneOutOfIndexCase := func(op OpcodeVec, lane, laneCeil byte) {
		n := VectorInstructionName(op)
		tests = append(tests, testCase{
			name: n + "/lane index out of range",
			flag: api.CoreFeatureSIMD,
			body: []byte{
				OpcodeVecPrefix, op,
				0, 0, // align and offset.
				lane,
			},
			expectedErr: fmt.Sprintf("invalid lane index %d >= %d for %s", lane, laneCeil, n),
		})
	}

	addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Load8Lane, 16, 16)
	addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Load16Lane, 8, 8)
	addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Load32Lane, 4, 4)
	addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Load64Lane, 2, 2)
	addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Store8Lane, 16, 16)
	addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Store16Lane, 8, 8)
	addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Store32Lane, 4, 4)
	addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Store64Lane, 2, 2)

	for _, tt := range tests {
		tc := tt
		t.Run(tc.name, func(t *testing.T) {
			m := &Module{
				TypeSection:     []FunctionType{v_v},
				FunctionSection: []Index{0},
				CodeSection:     []Code{{Body: tc.body}},
			}
			err := m.validateFunction(&stacks{}, tc.flag,
				0, []Index{0}, nil, &Memory{}, nil, nil, nil, bytes.NewReader(nil))
			require.EqualError(t, err, tc.expectedErr)
		})
	}
}

func TestDecodeBlockType(t *testing.T) {
	t.Run("primitive", func(t *testing.T) {
		for _, tc := range []struct {
			name                 string
			in                   byte
			exp                  ValueType
			expResultNumInUint64 int
		}{
			{name: "nil", in: 0x40},
			{name: "i32", in: 0x7f, exp: ValueTypeI32, expResultNumInUint64: 1},
			{name: "i64", in: 0x7e, exp: ValueTypeI64, expResultNumInUint64: 1},
			{name: "f32", in: 0x7d, exp: ValueTypeF32, expResultNumInUint64: 1},
			{name: "f64", in: 0x7c, exp: ValueTypeF64, expResultNumInUint64: 1},
			{name: "v128", in: 0x7b, exp: ValueTypeV128, expResultNumInUint64: 2},
			{name: "funcref", in: 0x70, exp: ValueTypeFuncref, expResultNumInUint64: 1},
			{name: "externref", in: 0x6f, exp: ValueTypeExternref, expResultNumInUint64: 1},
		} {
			tc := tc
			t.Run(tc.name, func(t *testing.T) {
				actual, read, err := DecodeBlockType(nil, bytes.NewReader([]byte{tc.in}), api.CoreFeaturesV2)
				require.NoError(t, err)
				require.Equal(t, uint64(1), read)
				require.Equal(t, 0, len(actual.Params))
				require.Equal(t, tc.expResultNumInUint64, actual.ResultNumInUint64)
				require.Equal(t, 0, actual.ParamNumInUint64)
				if tc.exp == 0 {
					require.Equal(t, 0, len(actual.Results))
				} else {
					require.Equal(t, 1, len(actual.Results))
					require.Equal(t, tc.exp, actual.Results[0])
				}
			})
		}
	})
	t.Run("function type", func(t *testing.T) {
		types := []FunctionType{
			{},
			{Params: []ValueType{ValueTypeI32}},
			{Results: []ValueType{ValueTypeI32}},
			{Params: []ValueType{ValueTypeF32, ValueTypeV128}, Results: []ValueType{ValueTypeI32}},
			{Params: []ValueType{ValueTypeF32, ValueTypeV128}, Results: []ValueType{ValueTypeI32, ValueTypeF32, ValueTypeV128}},
		}
		for index := range types {
			expected := &types[index]
			actual, read, err := DecodeBlockType(types, bytes.NewReader([]byte{byte(index)}), api.CoreFeatureMultiValue)
			require.NoError(t, err)
			require.Equal(t, uint64(1), read)
			require.Equal(t, expected, actual)
		}
	})
}

// TestFuncValidation_UnreachableBrTable_NotModifyTypes ensures that we do not modify the
// original function type during the function validation with the presence of unreachable br_table
// targeting the function return label.
func TestFuncValidation_UnreachableBrTable_NotModifyTypes(t *testing.T) {
	funcType := FunctionType{Results: []ValueType{i32, i64}, Params: []ValueType{i32}}
	copiedFuncType := FunctionType{
		Params:  make([]ValueType, len(funcType.Params)),
		Results: make([]ValueType, len(funcType.Results)),
	}

	copy(copiedFuncType.Results, funcType.Results)
	copy(copiedFuncType.Params, funcType.Params)

	for _, tc := range []struct {
		name string
		m    *Module
	}{
		{
			name: "on function return",
			m: &Module{
				TypeSection:     []FunctionType{funcType},
				FunctionSection: []Index{0},
				CodeSection: []Code{
					{Body: []byte{
						OpcodeUnreachable,
						// Having br_table in unreachable state.
						OpcodeI32Const, 1,
						// Setting the destination as labels of index 0 which
						// is the function return.
						OpcodeBrTable, 2, 0, 0, 0,
						OpcodeEnd,
					}},
				},
			},
		},
		{
			name: "on loop return",
			m: &Module{
				TypeSection:     []FunctionType{funcType},
				FunctionSection: []Index{0},
				CodeSection: []Code{
					{Body: []byte{
						OpcodeUnreachable,
						OpcodeLoop, 0, // indicates that loop has funcType as its block type
						OpcodeUnreachable,
						// Having br_table in unreachable state.
						OpcodeI32Const, 1,
						// Setting the destination as labels of index 0 which
						// is the loop return.
						OpcodeBrTable, 2, 0, 0, 0,
						OpcodeEnd, // End of loop
						OpcodeEnd,
					}},
				},
			},
		},
	} {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			err := tc.m.validateFunction(&stacks{}, api.CoreFeaturesV2,
				0, nil, nil, nil, nil, nil, nil, bytes.NewReader(nil))
			require.NoError(t, err)

			// Ensures that funcType has remained intact.
			require.Equal(t, copiedFuncType, funcType)
		})
	}
}

func TestModule_funcValidation_loopWithParams(t *testing.T) {
	tests := []struct {
		name   string
		body   []byte
		expErr string
	}{
		{
			name: "br",
			body: []byte{
				OpcodeI32Const, 1,
				OpcodeLoop, 1, // loop (param i32)
				OpcodeBr, 0,
				OpcodeEnd,
				OpcodeUnreachable,
				OpcodeEnd,
			},
		},
		{
			name: "br_if",
			body: []byte{
				OpcodeI32Const, 1,
				OpcodeLoop, 1, // loop (param i32)
				OpcodeI32Const, 1, // operand for br_if
				OpcodeBrIf, 0,
				OpcodeUnreachable,
				OpcodeEnd,
				OpcodeUnreachable,
				OpcodeEnd,
			},
		},
		{
			name: "br_table",
			body: []byte{
				OpcodeI32Const, 1,
				OpcodeLoop, 1, // loop (param i32)
				OpcodeI32Const, 4, // Operand for br_table.
				OpcodeBrTable, 2, 0, 0, 0, 0, // Jump into the loop header anyway.
				OpcodeEnd,
				OpcodeUnreachable,
				OpcodeEnd,
			},
		},
		{
			name: "br_table - nested",
			body: []byte{
				OpcodeI32Const, 1,
				OpcodeLoop, 1, // loop (param i32)
				OpcodeLoop, 1, // loop (param i32)
				OpcodeI32Const, 4, // Operand for br_table.
				OpcodeBrTable, 2, 0, 1, 0, // Jump into the loop header anyway.
				OpcodeEnd,
				OpcodeEnd,
				OpcodeUnreachable,
				OpcodeEnd,
			},
		},
		{
			name: "br / mismatch",
			body: []byte{
				OpcodeI32Const, 1,
				OpcodeLoop, 1, // loop (param i32)
				OpcodeDrop,
				OpcodeBr, 0, // trying to jump the loop head after dropping the value which causes the type mismatch.
				OpcodeEnd,
				OpcodeUnreachable,
				OpcodeEnd,
			},
			expErr: `not enough results in br block
	have ()
	want (i32)`,
		},
		{
			name: "br_if / mismatch",
			body: []byte{
				OpcodeI32Const, 1,
				OpcodeLoop, 1, // loop (param i32)
				// Use up the param for the br_if, therefore at the time of jumping, we don't have any param to the header.
				OpcodeBrIf, 0, // trying to jump the loop head after dropping the value which causes the type mismatch.
				OpcodeUnreachable,
				OpcodeEnd,
				OpcodeUnreachable,
				OpcodeEnd,
			},
			expErr: `not enough results in br_if block
	have ()
	want (i32)`,
		},
		{
			name: "br_table",
			body: []byte{
				OpcodeI32Const, 1,
				OpcodeLoop, 1, // loop (param i32)
				// Use up the param for the br_table, therefore at the time of jumping, we don't have any param to the header.
				OpcodeBrTable, 2, 0, 0, 0, // Jump into the loop header anyway.
				OpcodeEnd,
				OpcodeUnreachable,
				OpcodeEnd,
			},
			expErr: `not enough results in br_table block
	have ()
	want (i32)`,
		},
	}

	for _, tt := range tests {
		tc := tt
		t.Run(tc.name, func(t *testing.T) {
			m := &Module{
				TypeSection: []FunctionType{
					v_i32,
					i32_v,
				},
				FunctionSection: []Index{0},
				CodeSection:     []Code{{Body: tc.body}},
			}
			err := m.validateFunction(&stacks{}, api.CoreFeatureMultiValue,
				0, []Index{0}, nil, nil, nil, nil, nil, bytes.NewReader(nil))
			if tc.expErr != "" {
				require.EqualError(t, err, tc.expErr)
			} else {
				require.NoError(t, err)
			}
		})
	}
}

// TestFunctionValidation_redundantEnd is found in th validation fuzzing #879.
func TestFunctionValidation_redundantEnd(t *testing.T) {
	m := &Module{
		TypeSection:     []FunctionType{{}},
		FunctionSection: []Index{0},
		CodeSection:     []Code{{Body: []byte{OpcodeEnd, OpcodeEnd}}},
	}
	err := m.validateFunction(&stacks{}, api.CoreFeaturesV2,
		0, nil, nil, nil, nil, nil, nil, bytes.NewReader(nil))
	require.EqualError(t, err, "unexpected end of function at pc=0x1")
}

// TestFunctionValidation_redundantEnd is found in th validation fuzzing.
func TestFunctionValidation_redundantElse(t *testing.T) {
	for _, tc := range []struct {
		body   []byte
		expErr string
	}{
		{
			body:   []byte{OpcodeEnd, OpcodeElse},
			expErr: "unexpected end of function at pc=0x1",
		},
		{
			body:   []byte{OpcodeElse, OpcodeEnd},
			expErr: "else instruction must be used in if block: 0x0",
		},
		{
			body:   []byte{OpcodeBlock, 0, OpcodeElse, OpcodeEnd},
			expErr: "else instruction must be used in if block: 0x2",
		},
		{
			body: []byte{
				OpcodeI32Const, 0,
				OpcodeIf, 0, OpcodeElse, OpcodeElse, OpcodeEnd, OpcodeEnd,
			},
			expErr: "else instruction must be used in if block: 0x5",
		},
	} {
		t.Run(tc.expErr, func(t *testing.T) {
			m := &Module{TypeSection: []FunctionType{{}}, FunctionSection: []Index{0}, CodeSection: []Code{{Body: tc.body}}}
			err := m.validateFunction(&stacks{}, api.CoreFeaturesV2,
				0, nil, nil, nil, nil, nil, nil, bytes.NewReader(nil))
			require.EqualError(t, err, tc.expErr)
		})
	}
}

func Test_SplitCallStack(t *testing.T) {
	oneToEight := []uint64{1, 2, 3, 4, 5, 6, 7, 8}

	tests := []struct {
		name                                   string
		ft                                     *FunctionType
		stack, expectedParams, expectedResults []uint64
		expectedErr                            string
	}{
		{
			name:            "v_v",
			ft:              &v_v,
			stack:           oneToEight,
			expectedParams:  nil,
			expectedResults: nil,
		},
		{
			name:            "v_v - stack nil",
			ft:              &v_v,
			expectedParams:  nil,
			expectedResults: nil,
		},
		{
			name:            "v_i32",
			ft:              &v_i32,
			stack:           oneToEight,
			expectedParams:  nil,
			expectedResults: []uint64{1},
		},
		{
			name:            "f32i32_v",
			ft:              &f32i32_v,
			stack:           oneToEight,
			expectedParams:  []uint64{1, 2},
			expectedResults: nil,
		},
		{
			name:            "f64f32_i64",
			ft:              &f64f32_i64,
			stack:           oneToEight,
			expectedParams:  []uint64{1, 2},
			expectedResults: []uint64{1},
		},
		{
			name:            "f64i32_v128i64",
			ft:              &f64i32_v128i64,
			stack:           oneToEight,
			expectedParams:  []uint64{1, 2},
			expectedResults: []uint64{1, 2, 3},
		},
		{
			name:        "not enough room for params",
			ft:          &f64i32_v128i64,
			stack:       oneToEight[0:1],
			expectedErr: "need 2 params, but stack size is 1",
		},
		{
			name:        "not enough room for results",
			ft:          &f64i32_v128i64,
			stack:       oneToEight[0:2],
			expectedErr: "need 3 results, but stack size is 2",
		},
	}

	for _, tt := range tests {
		tc := tt

		t.Run(tc.name, func(t *testing.T) {
			params, results, err := SplitCallStack(tc.ft, tc.stack)
			if tc.expectedErr != "" {
				require.EqualError(t, err, tc.expectedErr)
			} else {
				require.Equal(t, tc.expectedParams, params)
				require.Equal(t, tc.expectedResults, results)
			}
		})
	}
}

func TestModule_funcValidation_Atomic(t *testing.T) {
	t.Run("valid bytecode", func(t *testing.T) {
		tests := []struct {
			name               string
			body               []byte
			noDropBeforeReturn bool
		}{
			{
				name: "i32.atomic.load8_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI32Load8U, 0x0, 0x8, // alignment=2^0, offset=8
				},
			},
			{
				name: "i32.atomic.load16_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI32Load16U, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i32.atomic.load",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI32Load, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.load8_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI64Load8U, 0x0, 0x8, // alignment=2^0, offset=8
				},
			},
			{
				name: "i64.atomic.load16_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI64Load16U, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i64.atomic.load32_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI64Load32U, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.load",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI64Load, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i32.atomic.store8",
				body: []byte{
					OpcodeI32Const, 0x1,
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI32Store8, 0x0, 0x8, // alignment=2^0, offset=8
				},
				noDropBeforeReturn: true,
			},
			{
				name: "i32.atomic.store16",
				body: []byte{
					OpcodeI32Const, 0x1,
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI32Store16, 0x1, 0x8, // alignment=2^1, offset=8
				},
				noDropBeforeReturn: true,
			},
			{
				name: "i32.atomic.store",
				body: []byte{
					OpcodeI32Const, 0x1,
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI32Store, 0x2, 0x8, // alignment=2^2, offset=8
				},
				noDropBeforeReturn: true,
			},
			{
				name: "i64.atomic.store8",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Store8, 0x0, 0x8, // alignment=2^0, offset=8
				},
				noDropBeforeReturn: true,
			},
			{
				name: "i64.atomic.store16",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Store16, 0x1, 0x8, // alignment=2^1, offset=8
				},
				noDropBeforeReturn: true,
			},
			{
				name: "i64.atomic.store32",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Store32, 0x2, 0x8, // alignment=2^2, offset=8
				},
				noDropBeforeReturn: true,
			},
			{
				name: "i64.atomic.store",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Store, 0x3, 0x8, // alignment=2^3, offset=8
				},
				noDropBeforeReturn: true,
			},
			{
				name: "i32.atomic.rmw8.add_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8AddU, 0x0, 0x8, // alignment=2^0, offset=8
				},
			},
			{
				name: "i32.atomic.rmw16.add_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16AddU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i32.atomic.rmw.add",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32RmwAdd, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw8.add_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8AddU, 0x0, 0x8, // alignment=2^0, offset=8
				},
			},
			{
				name: "i64.atomic.rmw16.add_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16AddU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i64.atomic.rmw32.add_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32AddU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw.add",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64RmwAdd, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i32.atomic.rmw8.sub_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8SubU, 0x0, 0x8, // alignment=2^0, offset=8
				},
			},
			{
				name: "i32.atomic.rmw16.sub_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16SubU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i32.atomic.rmw.sub",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32RmwSub, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw8.sub_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8SubU, 0x0, 0x8, // alignment=2^0, offset=8
				},
			},
			{
				name: "i64.atomic.rmw16.sub_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16SubU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i64.atomic.rmw32.sub_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32SubU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw.sub",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64RmwSub, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i32.atomic.rmw8.and_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8AndU, 0x0, 0x8, // alignment=2^0, offset=8
				},
			},
			{
				name: "i32.atomic.rmw16.and_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16AndU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i32.atomic.rmw.and",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32RmwAnd, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw8.and_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8AndU, 0x0, 0x8, // alignment=2^0, offset=8
				},
			},
			{
				name: "i64.atomic.rmw16.and_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16AndU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i64.atomic.rmw32.and_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32AndU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw.and",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64RmwAnd, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i32.atomic.rmw8.or",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8OrU, 0x0, 0x8, // alignment=2^0, offset=8
				},
			},
			{
				name: "i32.atomic.rmw16.or_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16OrU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i32.atomic.rmw.or",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32RmwOr, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw8.or_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8OrU, 0x0, 0x8, // alignment=2^0, offset=8
				},
			},
			{
				name: "i64.atomic.rmw16.or_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16OrU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i64.atomic.rmw32.or_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32OrU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw.or",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64RmwOr, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i32.atomic.rmw8.xor_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8XorU, 0x0, 0x8, // alignment=2^0, offset=8
				},
			},
			{
				name: "i32.atomic.rmw16.xor_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16XorU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i32.atomic.rmw.xor",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32RmwXor, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw8.xor_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8XorU, 0x0, 0x8, // alignment=2^0, offset=8
				},
			},
			{
				name: "i64.atomic.rmw16.xor_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16XorU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i64.atomic.rmw32.xor_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32XorU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw.xor",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64RmwXor, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i32.atomic.rmw8.xchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8XchgU, 0x0, 0x8, // alignment=2^0, offset=8
				},
			},
			{
				name: "i32.atomic.rmw16.xchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16XchgU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i32.atomic.rmw.xchg",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32RmwXchg, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw8.xchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8XchgU, 0x0, 0x8, // alignment=2^0, offset=8
				},
			},
			{
				name: "i64.atomic.rmw16.xchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16XchgU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i64.atomic.rmw32.xchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32XchgU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw.xchg",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64RmwXchg, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i32.atomic.rmw8.cmpxchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeI32Const, 0x2,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8CmpxchgU, 0x0, 0x8, // alignment=2^0, offset=8
				},
			},
			{
				name: "i32.atomic.rmw16.cmpxchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeI32Const, 0x2,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16CmpxchgU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i32.atomic.rmw.cmpxchg",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeI32Const, 0x2,
					OpcodeAtomicPrefix, OpcodeAtomicI32RmwCmpxchg, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw8.xchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeI64Const, 0x2,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8CmpxchgU, 0x0, 0x8, // alignment=2^0, offset=8
				},
			},
			{
				name: "i64.atomic.rmw16.cmpxchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeI64Const, 0x2,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16CmpxchgU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i64.atomic.rmw32.cmpxchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32CmpxchgU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw.cmpxchg",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeI64Const, 0x2,
					OpcodeAtomicPrefix, OpcodeAtomicI64RmwCmpxchg, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "memory.atomic.wait32",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeI64Const, 0x2,
					OpcodeAtomicPrefix, OpcodeAtomicMemoryWait32, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "memory.atomic.wait64",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeI64Const, 0x2,
					OpcodeAtomicPrefix, OpcodeAtomicMemoryWait64, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "memory.atomic.notify",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicMemoryNotify, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "memory.atomic.fence",
				body: []byte{
					OpcodeAtomicPrefix, OpcodeAtomicFence, 0x0,
				},
				noDropBeforeReturn: true,
			},
		}

		for _, tt := range tests {
			tc := tt
			t.Run(tc.name, func(t *testing.T) {
				body := append([]byte{}, tc.body...)
				if !tt.noDropBeforeReturn {
					body = append(body, OpcodeDrop)
				}
				body = append(body, OpcodeEnd)
				m := &Module{
					TypeSection:     []FunctionType{v_v},
					FunctionSection: []Index{0},
					CodeSection:     []Code{{Body: body}},
				}

				t.Run("with memory", func(t *testing.T) {
					err := m.validateFunction(&stacks{}, experimental.CoreFeaturesThreads,
						0, []Index{0}, nil, &Memory{}, []Table{}, nil, nil, bytes.NewReader(nil))
					require.NoError(t, err)
				})

				t.Run("without memory", func(t *testing.T) {
					err := m.validateFunction(&stacks{}, experimental.CoreFeaturesThreads,
						0, []Index{0}, nil, nil, []Table{}, nil, nil, bytes.NewReader(nil))
					// Only fence doesn't require memory
					if tc.name == "memory.atomic.fence" {
						require.NoError(t, err)
					} else {
						require.Error(t, err, fmt.Sprintf("memory must exist for %s", tc.name))
					}
				})
			})
		}
	})

	t.Run("atomic.fence bad immediate", func(t *testing.T) {
		body := []byte{
			OpcodeAtomicPrefix, OpcodeAtomicFence, 0x1,
			OpcodeEnd,
		}
		m := &Module{
			TypeSection:     []FunctionType{v_v},
			FunctionSection: []Index{0},
			CodeSection:     []Code{{Body: body}},
		}
		err := m.validateFunction(&stacks{}, experimental.CoreFeaturesThreads,
			0, []Index{0}, nil, &Memory{}, []Table{}, nil, nil, bytes.NewReader(nil))
		require.Error(t, err, "invalid immediate value for atomic.fence")
	})

	t.Run("bad alignment", func(t *testing.T) {
		tests := []struct {
			name               string
			body               []byte
			noDropBeforeReturn bool
		}{
			{
				name: "i32.atomic.load8_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI32Load8U, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i32.atomic.load16_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI32Load16U, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i32.atomic.load",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI32Load, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i64.atomic.load8_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI64Load8U, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i64.atomic.load16_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI64Load16U, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.load32_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI64Load32U, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i64.atomic.load",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI64Load, 0x4, 0x8, // alignment=2^4, offset=8
				},
			},
			{
				name: "i32.atomic.store8",
				body: []byte{
					OpcodeI32Const, 0x1,
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI32Store8, 0x1, 0x8, // alignment=2^1, offset=8
				},
				noDropBeforeReturn: true,
			},
			{
				name: "i32.atomic.store16",
				body: []byte{
					OpcodeI32Const, 0x1,
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI32Store16, 0x2, 0x8, // alignment=2^2, offset=8
				},
				noDropBeforeReturn: true,
			},
			{
				name: "i32.atomic.store",
				body: []byte{
					OpcodeI32Const, 0x1,
					OpcodeI32Const, 0x0,
					OpcodeAtomicPrefix, OpcodeAtomicI32Store, 0x3, 0x8, // alignment=2^3, offset=8
				},
				noDropBeforeReturn: true,
			},
			{
				name: "i64.atomic.store8",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Store8, 0x1, 0x8, // alignment=2^1, offset=8
				},
				noDropBeforeReturn: true,
			},
			{
				name: "i64.atomic.store16",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Store16, 0x2, 0x8, // alignment=2^2, offset=8
				},
				noDropBeforeReturn: true,
			},
			{
				name: "i64.atomic.store32",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Store32, 0x3, 0x8, // alignment=2^3, offset=8
				},
				noDropBeforeReturn: true,
			},
			{
				name: "i64.atomic.store",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Store, 0x4, 0x8, // alignment=2^4, offset=8
				},
				noDropBeforeReturn: true,
			},
			{
				name: "i32.atomic.rmw8.add_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8AddU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i32.atomic.rmw16.add_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16AddU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i32.atomic.rmw.add",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32RmwAdd, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i64.atomic.rmw8.add_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8AddU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i64.atomic.rmw16.add_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16AddU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw32.add_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32AddU, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i64.atomic.rmw.add",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64RmwAdd, 0x4, 0x8, // alignment=2^4, offset=8
				},
			},
			{
				name: "i32.atomic.rmw8.sub_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8SubU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i32.atomic.rmw16.sub_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16SubU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i32.atomic.rmw.sub",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32RmwSub, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i64.atomic.rmw8.sub_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8SubU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i64.atomic.rmw16.sub_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16SubU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw32.sub_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32SubU, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i64.atomic.rmw.sub",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64RmwSub, 0x4, 0x8, // alignment=2^4, offset=8
				},
			},
			{
				name: "i32.atomic.rmw8.and_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8AndU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i32.atomic.rmw16.and_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16AndU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i32.atomic.rmw.and",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32RmwAnd, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i64.atomic.rmw8.and_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8AndU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i64.atomic.rmw16.and_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16AndU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw32.and_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32AndU, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i64.atomic.rmw.and",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64RmwAnd, 0x4, 0x8, // alignment=2^4, offset=8
				},
			},
			{
				name: "i32.atomic.rmw8.or",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8OrU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i32.atomic.rmw16.or_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16OrU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i32.atomic.rmw.or",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32RmwOr, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i64.atomic.rmw8.or_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8OrU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i64.atomic.rmw16.or_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16OrU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw32.or_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32OrU, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i64.atomic.rmw.or",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64RmwOr, 0x4, 0x8, // alignment=2^4, offset=8
				},
			},
			{
				name: "i32.atomic.rmw8.xor_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8XorU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i32.atomic.rmw16.xor_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16XorU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i32.atomic.rmw.xor",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32RmwXor, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i64.atomic.rmw8.xor_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8XorU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i64.atomic.rmw16.xor_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16XorU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw32.xor_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32XorU, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i64.atomic.rmw.xor",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64RmwXor, 0x4, 0x8, // alignment=2^4, offset=8
				},
			},
			{
				name: "i32.atomic.rmw8.xchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8XchgU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i32.atomic.rmw16.xchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16XchgU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i32.atomic.rmw.xchg",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI32RmwXchg, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i64.atomic.rmw8.xchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8XchgU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i64.atomic.rmw16.xchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16XchgU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw32.xchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32XchgU, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i64.atomic.rmw.xchg",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64RmwXchg, 0x4, 0x8, // alignment=2^4, offset=8
				},
			},
			{
				name: "i32.atomic.rmw8.cmpxchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeI32Const, 0x2,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8CmpxchgU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i32.atomic.rmw16.cmpxchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeI32Const, 0x2,
					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16CmpxchgU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i32.atomic.rmw.cmpxchg",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeI32Const, 0x2,
					OpcodeAtomicPrefix, OpcodeAtomicI32RmwCmpxchg, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i64.atomic.rmw8.xchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeI64Const, 0x2,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8CmpxchgU, 0x1, 0x8, // alignment=2^1, offset=8
				},
			},
			{
				name: "i64.atomic.rmw16.cmpxchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeI64Const, 0x2,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16CmpxchgU, 0x2, 0x8, // alignment=2^2, offset=8
				},
			},
			{
				name: "i64.atomic.rmw32.cmpxchg_u",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeI64Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32CmpxchgU, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "i64.atomic.rmw.cmpxchg",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeI64Const, 0x2,
					OpcodeAtomicPrefix, OpcodeAtomicI64RmwCmpxchg, 0x4, 0x8, // alignment=2^4, offset=8
				},
			},
			{
				name: "memory.atomic.wait32",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeI64Const, 0x2,
					OpcodeAtomicPrefix, OpcodeAtomicMemoryWait32, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
			{
				name: "memory.atomic.wait64",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI64Const, 0x1,
					OpcodeI64Const, 0x2,
					OpcodeAtomicPrefix, OpcodeAtomicMemoryWait64, 0x4, 0x8, // alignment=2^4, offset=8
				},
			},
			{
				name: "memory.atomic.notify",
				body: []byte{
					OpcodeI32Const, 0x0,
					OpcodeI32Const, 0x1,
					OpcodeAtomicPrefix, OpcodeAtomicMemoryNotify, 0x3, 0x8, // alignment=2^3, offset=8
				},
			},
		}

		for _, tt := range tests {
			tc := tt
			t.Run(tc.name, func(t *testing.T) {
				body := append([]byte{}, tc.body...)
				if !tt.noDropBeforeReturn {
					body = append(body, OpcodeDrop)
				}
				body = append(body, OpcodeEnd)
				m := &Module{
					TypeSection:     []FunctionType{v_v},
					FunctionSection: []Index{0},
					CodeSection:     []Code{{Body: body}},
				}
				err := m.validateFunction(&stacks{}, experimental.CoreFeaturesThreads,
					0, []Index{0}, nil, &Memory{}, []Table{}, nil, nil, bytes.NewReader(nil))
				require.Error(t, err, "invalid memory alignment")
			})
		}
	})
}

func TestValidation_LegacyExceptionHandlingOpcodes(t *testing.T) {
	for _, tc := range []struct {
		opcode byte
		name   string
	}{
		{OpcodeLegacyTry, "try"},
		{OpcodeLegacyCatch, "catch"},
		{OpcodeLegacyRethrow, "rethrow"},
		{OpcodeLegacyDelegate, "delegate"},
		{OpcodeLegacyCatchAll, "catch_all"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			m := &Module{
				TypeSection:     []FunctionType{v_v},
				FunctionSection: []Index{0},
				CodeSection:     []Code{{Body: []byte{tc.opcode, OpcodeEnd}}},
			}
			t.Run("with EH enabled", func(t *testing.T) {
				err := m.validateFunction(&stacks{}, api.CoreFeaturesV2|experimental.CoreFeaturesExceptionHandling,
					0, []Index{0}, nil, nil, nil, nil, nil, bytes.NewReader(nil))
				require.Error(t, err)
				require.Contains(t, err.Error(), "legacy exception handling instruction")
				require.Contains(t, err.Error(), "wasm-opt --translate-to-exnref")
			})
			t.Run("without EH enabled", func(t *testing.T) {
				err := m.validateFunction(&stacks{}, api.CoreFeaturesV2,
					0, []Index{0}, nil, nil, nil, nil, nil, bytes.NewReader(nil))
				require.Error(t, err)
				require.Contains(t, err.Error(), "invalid instruction")
			})
		})
	}
}
