While compilers should be conservative when targeting WebAssembly Core features, runtimes should be lenient as otherwise people need to constantly turn on all features. Currently, most examples have to turn on 2.0 features because compilers such as AssemblyScript and TinyGo use them by default. This matches the policy with the reality, and should make first time use easier. This top-levels an internal type as `api.CoreFeatures` and defaults to 2.0 as opposed to 1.0, our previous default. This is less cluttered than the excess of `WithXXX` methods we had prior to implementing all planned WebAssembly Core Specification 1.0 features. Finally, this backfills rationale as flat config types were a distinct decision even if feature set selection muddied the topic. Signed-off-by: Adrian Cole <adrian@tetrate.io>
3543 lines
126 KiB
Go
3543 lines
126 KiB
Go
package wasm
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/tetratelabs/wazero/api"
|
|
"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(api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, max+1, nil)
|
|
require.NoError(t, err)
|
|
})
|
|
t.Run("exceed", func(t *testing.T) {
|
|
err := m.validateFunctionWithMaxStackValues(api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, max, 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(api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, 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(api.CoreFeatureSignExtensionOps, 0, []Index{0}, nil, nil, nil, 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(api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, 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(api.CoreFeatureNonTrappingFloatToIntConversion, 0, []Index{0}, nil, nil, nil, 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(api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, nil)
|
|
require.EqualError(t, err, tc.expectedErrOnDisable)
|
|
})
|
|
t.Run("enabled", func(t *testing.T) {
|
|
err := tc.module.validateFunction(api.CoreFeatureMultiValue, 0, []Index{0}, nil, nil, nil, 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(api.CoreFeatureBulkMemoryOperations, 0, []Index{0}, nil, &Memory{}, []*Table{{}, {}}, 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(tc.flag, 0, []Index{0}, nil, tc.memory, tc.tables, nil)
|
|
require.EqualError(t, err, tc.expectedErr)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
var (
|
|
f32, f64, i32, i64, externref = ValueTypeF32, ValueTypeF64, ValueTypeI32, ValueTypeI64, ValueTypeExternref
|
|
f32i32_v = &FunctionType{Params: []ValueType{f32, i32}}
|
|
i32_i32 = &FunctionType{Params: []ValueType{i32}, Results: []ValueType{i32}}
|
|
i32f64_v = &FunctionType{Params: []ValueType{i32, f64}}
|
|
i32i32_i32 = &FunctionType{Params: []ValueType{i32, i32}, Results: []ValueType{i32}}
|
|
i32_v = &FunctionType{Params: []ValueType{i32}}
|
|
v_v = &FunctionType{}
|
|
v_f32 = &FunctionType{Results: []ValueType{f32}}
|
|
v_f32f32 = &FunctionType{Results: []ValueType{f32, f32}}
|
|
v_f64i32 = &FunctionType{Results: []ValueType{f64, i32}}
|
|
v_f64f64 = &FunctionType{Results: []ValueType{f64, f64}}
|
|
v_i32 = &FunctionType{Results: []ValueType{i32}}
|
|
v_i32i32 = &FunctionType{Results: []ValueType{i32, i32}}
|
|
v_i32i64 = &FunctionType{Results: []ValueType{i32, i64}}
|
|
v_i64i64 = &FunctionType{Results: []ValueType{i64, i64}}
|
|
)
|
|
|
|
// TestModule_ValidateFunction_TypeMismatchSpecTests 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(api.CoreFeatureMultiValue, 0, []Index{0}, nil, nil, nil, 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(api.CoreFeatureReferenceTypes, 0, []Index{0}, nil, &Memory{}, []*Table{{Type: RefTypeFuncref}}, 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(api.CoreFeaturesV1, 0, []Index{0}, nil, &Memory{}, []*Table{{}, {}}, 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(api.CoreFeatureReferenceTypes, 0, []Index{0}, nil, &Memory{}, []*Table{{}, {}}, 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(api.CoreFeatureReferenceTypes, 0, []Index{0}, nil, &Memory{}, []*Table{{Type: RefTypeExternref}}, 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,
|
|
OpcodeDrop, OpcodeEnd,
|
|
},
|
|
},
|
|
{
|
|
name: "ref.null (externref)",
|
|
flag: api.CoreFeatureReferenceTypes,
|
|
body: []byte{
|
|
OpcodeRefNull, ValueTypeExternref,
|
|
OpcodeDrop, OpcodeEnd,
|
|
},
|
|
},
|
|
{
|
|
name: "ref.null - disabled",
|
|
flag: api.CoreFeaturesV1,
|
|
body: []byte{
|
|
OpcodeRefNull, ValueTypeFuncref,
|
|
OpcodeDrop, OpcodeEnd,
|
|
},
|
|
expectedErr: "ref.null invalid as feature \"reference-types\" is disabled",
|
|
},
|
|
{
|
|
name: "ref.is_null",
|
|
flag: api.CoreFeatureReferenceTypes,
|
|
body: []byte{
|
|
OpcodeRefNull, ValueTypeFuncref,
|
|
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(tc.flag, 0, []Index{0}, nil, nil, nil, tc.declaredFunctionIndexes)
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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(tc.flag, 0, []Index{0}, nil, nil, tables, 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,
|
|
OpcodeTableSet, 0,
|
|
OpcodeEnd,
|
|
},
|
|
flag: api.CoreFeatureReferenceTypes,
|
|
},
|
|
{
|
|
name: "table.set type mismatch (src=funcref, dst=externref)",
|
|
body: []byte{
|
|
OpcodeI32Const, 0,
|
|
OpcodeRefNull, ValueTypeFuncref,
|
|
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,
|
|
OpcodeTableSet, 1,
|
|
OpcodeEnd,
|
|
},
|
|
flag: api.CoreFeatureReferenceTypes,
|
|
},
|
|
{
|
|
name: "table.set type mismatch (src=externref, dst=funcref)",
|
|
body: []byte{
|
|
OpcodeI32Const, 0,
|
|
OpcodeRefNull, ValueTypeExternref,
|
|
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(tc.flag, 0, []Index{0}, nil, nil, tables, 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, // 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(tc.flag, 0, []Index{0}, nil, nil, nil, 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(api.CoreFeatureSIMD, 0, []Index{0}, nil, &Memory{}, nil, 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(tc.flag, 0, []Index{0}, nil, &Memory{}, nil, 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, expected := range types {
|
|
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(api.CoreFeaturesV2, 0, nil, nil, nil, nil, 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(api.CoreFeatureMultiValue, 0, []Index{0}, nil, nil, nil, nil)
|
|
if tc.expErr != "" {
|
|
require.EqualError(t, err, tc.expErr)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|