Files
wazero-wazero/internal/wasm/func_validation_test.go
T
Edoardo Vacchi bfb20e0ba7 feat: exception handling spec (#2489)
Add experimental support to the Exception Handling spec.

Exception handling adds structured error propagation to WebAssembly
through `tags` (typed exception signatures), `throw/throw_ref` (raising
exceptions), and `try_table` (catching exceptions with typed `catch`
clauses). A new value type, `exnref`, represents a reference to a caught
exception.

Excluding tests and spec suites, the entire feature amounts to a little
less than 2000 lines of code.

Feature flag: `experimental.CoreFeaturesExceptionHandling`


## What's the use for this?

In the spirit of always having a real use case for the specs we
implement, I did check this implementation against the one large Wasm
C++ codebase I am aware about, i.e.
https://github.com/klippa-app/go-pdfium, and I verified that the entire
suite, compiled with `emcc+wasm-opt` does work; incidentally, much of
the existing support code, based on Emscripten's longjmp/setjmp, can be
dropped (thx @jerbob92 for the help!)

Broadly speaking, I don't know how much EH-enabled Wasm code you will
find in the wild. For instance, code compiled using Clang's C++ backend
does not really throw new-style exceptions, unless you compile WASI-SDK
with some flags, or you build with `emcc` and translate old-style
exceptions using `wasm-opt` (see exceptions_test.go)

In short, this spec should be really seen as a step towards supporting
the GC spec. The design decisions (esp. regarding the test suite) follow
from this reasoning.


## Spec Suite

`wast2json` **does not** support the EH suite, but `wasm-tools` does,
albeit with a tiny quirk in the way negative numbers are rendered. In
this PR _I am not_ moving the other suites to `wasm-tools` though.

The spec test suite passes entirely, except for two tests that are
intentionally skipped.

These tests assume **a very small subset** of typed references is
implemented; specifically, the distinction between nullable and
non-nullable function references (`(ref null $t)` vs `(ref $t)`). I have
decided to just flatten all non-nullable refs to nullable refs at decode
time, so we cannot detect the type mismatch the spec expects in these
cases.

Passing the **full suite is actually straightforward** because it only
requires introducing a non-nullable variant for each ref type: strictly
speaking the 2 failing tests are only for related nullable vs
non-nullable **func refs** -- so I can special-case these and/or add
non-nullable types for each known value; however, we will **need** to
rethink the representation of Values for the GC proposal anyway (because
this will introduce custom type indices, and these won't fit our
currently byte-sized `ValueType` enum).

The subtype-checking functions (`isRefSubtypeOf`,
`isStrictRefSubtypeOf`) are kept as abstraction points but currently
they are just equalities.

Nevertheless, as proven by the CPP example and
https://github.com/klippa-app/go-pdfium this PR provides a starting
point that is already useful (even if it is for limited cases).


## Interpreter

Exception handling in the interpreter:

**Within a frame**, `try_table` blocks are compiled into a static
exception table with PC ranges. When `throw` or `throw_ref` executes,
`searchExceptionTable` scans backwards (inner handlers first) for a
catch clause whose PC range covers the current instruction. If a match
is found, `applyExceptionHandler` adjusts the operand stack depth and
jumps to the catch target PC directly.

**Across frames**, if no handler is found in the current frame,
`panic(&thrownException{...})` propagates up the Go call stack. Each
cross-function call goes through `callWithUnwind()`, which uses
`defer/recover` to intercept the panic. Its `canRestore` method unwinds
`ce.frames` to the caller's depth and searches that frame's exception
table. If a handler is found, `doRestore` applies it and
`callWithUnwind` returns `true` (frame unwound -- caller refreshes
locals and continues). If not, the panic re-propagates to the next outer
`callWithUnwind`.

Note on the implementation: this mechanism is unified with the existing
`snapshot/restore` API through the `restorable` interface: both
`*thrownException` and `*snapshot` implement `canRestore`/`doRestore`,
so `callWithUnwind` handles both with a single defer/recover path. The
short-circuit check (`len(exceptionTable) == 0 && no snapshotter`) skips
the defer/recover overhead entirely for frames that don't need it.


## Compiler

The compiler could not reuse the snapshot/restore mechanism directly,
but it follows a similar pattern. One cool thing is that exception
handling could be implemented entirely as an SSA-level lowering with new
exit codes; there is no backend-specific code.

Throwing uses a two-phase ABI:

1. `ExitCodeThrowAlloc`: exits to the Go runtime to allocate an
`Exception` struct on the heap and writes a pointer to its params array
into the execution context. Then we return control to compiled code,
which stores the tag parameters directly into the struct.
2. `ExitCodeThrow`: exits to the Go runtime again to search for a
matching handler, restore the stack checkpoint, and branch to the catch
target.

The idea is that we NEED to allocate the parameter slice dynamically, so
we delegate to the Go runtime. Incidentally, this also allows us to
unify `throw` and `throwRef`; in the latter case, we skip the `alloc`
phase, and we exit straight to `ExitCodeThrow`.

### Trampolines

The compiler uses four trampolines to return to host to manage exception
state:

| Trampoline | Exit Code | Purpose |
|---|---|---|
| **ThrowAlloc** | `ExitCodeThrowAlloc` | Allocates an `Exception`
struct on the Go heap, writes `&exn.Params[0]` into `execCtx` so
compiled code can store tag parameters directly. |
| **Throw** | `ExitCodeThrow` | Searches `tryHandlers` for a matching
catch clause, restores the stack checkpoint on match, sets
`caughtExceptionClauseIdx` for the compiled dispatch. Used for both
`throw` (after ThrowAlloc) and `throw_ref` (directly). |
| **TryTableEnter** | `ExitCodeTryTableEnter` | Clones the current stack
as a checkpoint, pushes a `tryHandler` with the catch clause table and
saved module instance. Sets `caughtExceptionClauseIdx = -1` (no
exception). |
| **TryTableLeave** | `ExitCodeTryTableLeave` | Pops the most recent
`tryHandler`. Emitted at `try_table` block ends, and before any branch
or return that exits a `try_table` scope. |

### Lowering

As mentioned earlier, this entirely resolved at the SSA-level. In
particular:

- **Entry**: when a `try_table` is encountered, the compiler emits a
call to the TryTableEnter trampoline. Control returns to compiled code,
which reads `caughtExceptionClauseIdx` from the execution context — if
it's -1 (no exception), execution continues into the `try_table` body.

- **Normal exit**: when execution reaches the `End` opcode of the
`try_table` block, a TryTableLeave trampoline call is emitted.

However, every TryTableEnter must have a matching TryTableLeave on every
control flow path that doesn't throw; so we also "TryTableLeaves" also
for the following:

- **Early exit (branch)**: a `br`, `br_if`, or `br_table` can jump to a
label outside the `try_table`, skipping its `End`.
`emitTryTableLeaves(depth)` walks the control frame stack and emits one
TryTableLeave for each `try_table` frame the branch crosses. For
`br_if`, this happens in a trampoline basic block that only executes on
the taken path.

- **Early exit (return)**: `return`, `return_call`, and
`return_call_indirect` call `emitTryTableLeaves` with the full control
stack depth, popping all active handlers before leaving the frame.

- **Throw**: on `throw`/`throw_ref`, compiled code exits to the dispatch
loop via ExitCodeThrow. `doHandleException` searches `tryHandlers`
innermost-to-outermost. On match, it restores the cloned stack
checkpoint, sets `caughtExceptionClauseIdx`, and re-enters compiled code
at the handler's return address. The compiled dispatch branches to the
matching catch clause's handler block.


### Caveats

- As described above, we are skipping 2 spec tests (will be addressed
when we implement typed references)
- Because of that, I am _not_ enabling by default the fuzzer: I would
have to special-case for typed references and/or TODOs I have left
behind; I did run the fuzzer with some special-casing while debugging
the issue with pdfium (#2488) and did not find issues (the fuzzer did go
frequently OOM though); in fact, the pdfium issue was actually unrelated
(!)

---------

Signed-off-by: Edoardo Vacchi <evacchi@users.noreply.github.com>
2026-04-26 09:12:51 -07:00

4907 lines
162 KiB
Go

package wasm
import (
"bytes"
"fmt"
"testing"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/testing/require"
)
func TestModule_ValidateFunction_validateFunctionWithMaxStackValues(t *testing.T) {
const max = 100
const valuesNum = max + 1
// Compile a function which has max+1 const instructions.
var body []byte
for i := 0; i < valuesNum; i++ {
body = append(body, OpcodeI32Const, 1)
}
// Drop all the consts so that if the max is higher, this function body would be sound.
for i := 0; i < valuesNum; i++ {
body = append(body, OpcodeDrop)
}
// Plus all functions must end with End opcode.
body = append(body, OpcodeEnd)
m := &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: body}},
}
t.Run("not exceed", func(t *testing.T) {
err := m.validateFunctionWithMaxStackValues(&stacks{}, api.CoreFeaturesV1,
0, []Index{0}, nil, nil, nil, nil, max+1, nil, bytes.NewReader(nil))
require.NoError(t, err)
})
t.Run("exceed", func(t *testing.T) {
err := m.validateFunctionWithMaxStackValues(&stacks{}, api.CoreFeaturesV1,
0, []Index{0}, nil, nil, nil, nil, max, nil, bytes.NewReader(nil))
require.Error(t, err)
expMsg := fmt.Sprintf("function may have %d stack values, which exceeds limit %d", valuesNum, max)
require.Equal(t, expMsg, err.Error())
})
}
func TestModule_ValidateFunction_SignExtensionOps(t *testing.T) {
tests := []struct {
input Opcode
expectedErrOnDisable string
}{
{
input: OpcodeI32Extend8S,
expectedErrOnDisable: "i32.extend8_s invalid as feature \"sign-extension-ops\" is disabled",
},
{
input: OpcodeI32Extend16S,
expectedErrOnDisable: "i32.extend16_s invalid as feature \"sign-extension-ops\" is disabled",
},
{
input: OpcodeI64Extend8S,
expectedErrOnDisable: "i64.extend8_s invalid as feature \"sign-extension-ops\" is disabled",
},
{
input: OpcodeI64Extend16S,
expectedErrOnDisable: "i64.extend16_s invalid as feature \"sign-extension-ops\" is disabled",
},
{
input: OpcodeI64Extend32S,
expectedErrOnDisable: "i64.extend32_s invalid as feature \"sign-extension-ops\" is disabled",
},
}
for _, tt := range tests {
tc := tt
t.Run(InstructionName(tc.input), func(t *testing.T) {
t.Run("disabled", func(t *testing.T) {
m := &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{tc.input}}},
}
err := m.validateFunction(&stacks{}, api.CoreFeaturesV1,
0, []Index{0}, nil, nil, nil, nil, nil,
bytes.NewReader(nil))
require.EqualError(t, err, tc.expectedErrOnDisable)
})
t.Run("enabled", func(t *testing.T) {
is32bit := tc.input == OpcodeI32Extend8S || tc.input == OpcodeI32Extend16S
var body []byte
if is32bit {
body = append(body, OpcodeI32Const)
} else {
body = append(body, OpcodeI64Const)
}
body = append(body, tc.input, 123, OpcodeDrop, OpcodeEnd)
m := &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: body}},
}
err := m.validateFunction(&stacks{}, api.CoreFeatureSignExtensionOps,
0, []Index{0}, nil, nil, nil, nil,
nil, bytes.NewReader(nil))
require.NoError(t, err)
})
})
}
}
func TestModule_ValidateFunction_NonTrappingFloatToIntConversion(t *testing.T) {
tests := []struct {
input Opcode
expectedErrOnDisable string
}{
{
input: OpcodeMiscI32TruncSatF32S,
expectedErrOnDisable: "i32.trunc_sat_f32_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
},
{
input: OpcodeMiscI32TruncSatF32U,
expectedErrOnDisable: "i32.trunc_sat_f32_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
},
{
input: OpcodeMiscI32TruncSatF64S,
expectedErrOnDisable: "i32.trunc_sat_f64_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
},
{
input: OpcodeMiscI32TruncSatF64U,
expectedErrOnDisable: "i32.trunc_sat_f64_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
},
{
input: OpcodeMiscI64TruncSatF32S,
expectedErrOnDisable: "i64.trunc_sat_f32_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
},
{
input: OpcodeMiscI64TruncSatF32U,
expectedErrOnDisable: "i64.trunc_sat_f32_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
},
{
input: OpcodeMiscI64TruncSatF64S,
expectedErrOnDisable: "i64.trunc_sat_f64_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
},
{
input: OpcodeMiscI64TruncSatF64U,
expectedErrOnDisable: "i64.trunc_sat_f64_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
},
}
for _, tt := range tests {
tc := tt
t.Run(InstructionName(tc.input), func(t *testing.T) {
t.Run("disabled", func(t *testing.T) {
m := &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{OpcodeMiscPrefix, tc.input}}},
}
err := m.validateFunction(&stacks{}, api.CoreFeaturesV1,
0, []Index{0}, nil, nil, nil, nil, nil, bytes.NewReader(nil))
require.EqualError(t, err, tc.expectedErrOnDisable)
})
t.Run("enabled", func(t *testing.T) {
var body []byte
switch tc.input {
case OpcodeMiscI32TruncSatF32S, OpcodeMiscI32TruncSatF32U, OpcodeMiscI64TruncSatF32S, OpcodeMiscI64TruncSatF32U:
body = []byte{OpcodeF32Const, 1, 2, 3, 4}
case OpcodeMiscI32TruncSatF64S, OpcodeMiscI32TruncSatF64U, OpcodeMiscI64TruncSatF64S, OpcodeMiscI64TruncSatF64U:
body = []byte{OpcodeF64Const, 1, 2, 3, 4, 5, 6, 7, 8}
}
body = append(body, OpcodeMiscPrefix, tc.input, OpcodeDrop, OpcodeEnd)
m := &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: body}},
}
err := m.validateFunction(&stacks{}, api.CoreFeatureNonTrappingFloatToIntConversion,
0, []Index{0}, nil, nil, nil, nil, nil, bytes.NewReader(nil))
require.NoError(t, err)
})
})
}
}
// TestModule_ValidateFunction_MultiValue only tests what can't yet be detected during compilation. These examples are
// from test/core/if.wast from the commit that added "multi-value" support.
//
// See https://github.com/WebAssembly/spec/commit/484180ba3d9d7638ba1cb400b699ffede796927c
func TestModule_ValidateFunction_MultiValue(t *testing.T) {
tests := []struct {
name string
module *Module
expectedErrOnDisable string
}{
{
name: "block with function type",
module: &Module{
TypeSection: []FunctionType{v_f64f64},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeBlock, 0, // (block (result f64 f64)
OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0x10, 0x40, // (f64.const 4)
OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0x14, 0x40, // (f64.const 5)
OpcodeBr, 0,
OpcodeF64Add,
OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0x18, 0x40, // (f64.const 6)
OpcodeEnd,
OpcodeEnd,
}}},
},
expectedErrOnDisable: "read block: block with function type return invalid as feature \"multi-value\" is disabled",
},
{
name: "if with function type", // a.k.a. "param"
module: &Module{
TypeSection: []FunctionType{i32_i32}, // (func (param i32) (result i32)
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, // (i32.const 1)
OpcodeLocalGet, 0, OpcodeIf, 0, // (if (param i32) (result i32) (local.get 0)
OpcodeI32Const, 2, OpcodeI32Add, // (then (i32.const 2) (i32.add))
OpcodeElse, OpcodeI32Const, 0x7e, OpcodeI32Add, // (else (i32.const -2) (i32.add))
OpcodeEnd, // )
OpcodeEnd, // )
}}},
},
expectedErrOnDisable: "read block: block with function type return invalid as feature \"multi-value\" is disabled",
},
{
name: "if with function type - br", // a.k.a. "params-break"
module: &Module{
TypeSection: []FunctionType{
i32_i32, // (func (param i32) (result i32)
i32i32_i32, // (if (param i32 i32) (result i32)
},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, // (i32.const 1)
OpcodeI32Const, 2, // (i32.const 2)
OpcodeLocalGet, 0, OpcodeIf, 1, // (if (param i32) (result i32) (local.get 0)
OpcodeI32Add, OpcodeBr, 0, // (then (i32.add) (br 0))
OpcodeElse, OpcodeI32Sub, OpcodeBr, 0, // (else (i32.sub) (br 0))
OpcodeEnd, // )
OpcodeEnd, // )
}}},
},
expectedErrOnDisable: "read block: block with function type return invalid as feature \"multi-value\" is disabled",
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
t.Run("disabled", func(t *testing.T) {
err := tc.module.validateFunction(&stacks{}, api.CoreFeaturesV1,
0, []Index{0}, nil, nil, nil, nil, nil, bytes.NewReader(nil))
require.EqualError(t, err, tc.expectedErrOnDisable)
})
t.Run("enabled", func(t *testing.T) {
err := tc.module.validateFunction(&stacks{}, api.CoreFeatureMultiValue,
0, []Index{0}, nil, nil, nil, nil, nil, bytes.NewReader(nil))
require.NoError(t, err)
})
})
}
}
func TestModule_ValidateFunction_BulkMemoryOperations(t *testing.T) {
t.Run("ok", func(t *testing.T) {
for _, op := range []OpcodeMisc{
OpcodeMiscMemoryInit, OpcodeMiscDataDrop, OpcodeMiscMemoryCopy,
OpcodeMiscMemoryFill, OpcodeMiscTableInit, OpcodeMiscElemDrop, OpcodeMiscTableCopy,
} {
t.Run(MiscInstructionName(op), func(t *testing.T) {
var body []byte
if op != OpcodeMiscDataDrop && op != OpcodeMiscElemDrop {
body = append(body, OpcodeI32Const, 1, OpcodeI32Const, 2, OpcodeI32Const, 3)
}
body = append(body, OpcodeMiscPrefix, op)
if op != OpcodeMiscDataDrop && op != OpcodeMiscMemoryFill && op != OpcodeMiscElemDrop {
body = append(body, 0, 0)
} else {
body = append(body, 0)
}
body = append(body, OpcodeEnd)
c := uint32(0)
m := &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: body}},
DataSection: []DataSegment{{}},
ElementSection: []ElementSegment{{}},
DataCountSection: &c,
}
err := m.validateFunction(&stacks{}, api.CoreFeatureBulkMemoryOperations,
0, []Index{0}, nil, &Memory{}, []Table{{}, {}}, nil, nil, bytes.NewReader(nil))
require.NoError(t, err)
})
}
})
t.Run("errors", func(t *testing.T) {
tests := []struct {
body []byte
dataSection []DataSegment
elementSection []ElementSegment
dataCountSectionNil bool
memory *Memory
tables []Table
flag api.CoreFeatures
expectedErr string
}{
// memory.init
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit},
flag: api.CoreFeatureBulkMemoryOperations,
memory: nil,
expectedErr: "memory must exist for memory.init",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit},
flag: api.CoreFeaturesV1,
expectedErr: `memory.init invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit},
flag: api.CoreFeatureBulkMemoryOperations,
dataCountSectionNil: true,
expectedErr: `memory must exist for memory.init`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "failed to read data segment index for memory.init: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit, 100 /* data section out of range */},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []DataSegment{{}},
expectedErr: "index 100 out of range of data section(len=1)",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []DataSegment{{}},
expectedErr: "failed to read memory index for memory.init: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0, 1},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []DataSegment{{}},
expectedErr: "memory.init reserved byte must be zero encoded with 1 byte",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []DataSegment{{}},
expectedErr: "cannot pop the operand for memory.init: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []DataSegment{{}},
expectedErr: "cannot pop the operand for memory.init: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []DataSegment{{}},
expectedErr: "cannot pop the operand for memory.init: i32 missing",
},
// data.drop
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscDataDrop},
flag: api.CoreFeaturesV1,
expectedErr: `data.drop invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscDataDrop},
dataCountSectionNil: true,
memory: &Memory{},
flag: api.CoreFeatureBulkMemoryOperations,
expectedErr: `data.drop requires data count section`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscDataDrop},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "failed to read data segment index for data.drop: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscDataDrop, 100 /* data section out of range */},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []DataSegment{{}},
expectedErr: "index 100 out of range of data section(len=1)",
},
// memory.copy
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy},
flag: api.CoreFeatureBulkMemoryOperations,
memory: nil,
expectedErr: "memory must exist for memory.copy",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy},
flag: api.CoreFeaturesV1,
expectedErr: `memory.copy invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: `failed to read memory index for memory.copy: EOF`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "failed to read memory index for memory.copy: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0, 1},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "memory.copy reserved byte must be zero encoded with 1 byte",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "cannot pop the operand for memory.copy: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "cannot pop the operand for memory.copy: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "cannot pop the operand for memory.copy: i32 missing",
},
// memory.fill
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill},
flag: api.CoreFeatureBulkMemoryOperations,
memory: nil,
expectedErr: "memory must exist for memory.fill",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill},
flag: api.CoreFeaturesV1,
expectedErr: `memory.fill invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: `failed to read memory index for memory.fill: EOF`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill, 1},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: `memory.fill reserved byte must be zero encoded with 1 byte`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "cannot pop the operand for memory.fill: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryFill, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "cannot pop the operand for memory.fill: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryFill, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "cannot pop the operand for memory.fill: i32 missing",
},
// table.init
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit},
flag: api.CoreFeaturesV1,
tables: []Table{{}},
expectedErr: `table.init invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []Table{{}},
expectedErr: "failed to read element segment index for table.init: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 100 /* data section out of range */},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []Table{{}},
elementSection: []ElementSegment{{}},
expectedErr: "index 100 out of range of element section(len=1)",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 0},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []Table{{}},
elementSection: []ElementSegment{{}},
expectedErr: "failed to read source table index for table.init: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 10},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []Table{{}},
elementSection: []ElementSegment{{}},
expectedErr: "source table index must be zero for table.init as feature \"reference-types\" is disabled",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 10},
flag: api.CoreFeatureBulkMemoryOperations | api.CoreFeatureReferenceTypes,
tables: []Table{{}},
elementSection: []ElementSegment{{}},
expectedErr: "table of index 10 not found",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 1},
flag: api.CoreFeatureBulkMemoryOperations | api.CoreFeatureReferenceTypes,
tables: []Table{{}, {Type: RefTypeExternref}},
elementSection: []ElementSegment{{Type: RefTypeFuncref}},
expectedErr: "type mismatch for table.init: element type funcref does not match table type externref",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []Table{{}},
elementSection: []ElementSegment{{}},
expectedErr: "cannot pop the operand for table.init: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []Table{{}},
elementSection: []ElementSegment{{}},
expectedErr: "cannot pop the operand for table.init: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []Table{{}},
elementSection: []ElementSegment{{}},
expectedErr: "cannot pop the operand for table.init: i32 missing",
},
// elem.drop
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscElemDrop},
flag: api.CoreFeaturesV1,
tables: []Table{{}},
expectedErr: `elem.drop invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscElemDrop},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []Table{{}},
expectedErr: "failed to read element segment index for elem.drop: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscElemDrop, 100 /* element section out of range */},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []Table{{}},
elementSection: []ElementSegment{{}},
expectedErr: "index 100 out of range of element section(len=1)",
},
// table.copy
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy},
flag: api.CoreFeaturesV1,
tables: []Table{{}},
expectedErr: `table.copy invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []Table{{}},
expectedErr: `failed to read destination table index for table.copy: EOF`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 10},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []Table{{}},
expectedErr: "destination table index must be zero for table.copy as feature \"reference-types\" is disabled",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 3},
flag: api.CoreFeatureBulkMemoryOperations | api.CoreFeatureReferenceTypes,
tables: []Table{{}, {}},
expectedErr: "table of index 3 not found",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 3},
flag: api.CoreFeatureBulkMemoryOperations | api.CoreFeatureReferenceTypes,
tables: []Table{{}, {}, {}, {}},
expectedErr: "failed to read source table index for table.copy: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 0, 3},
flag: api.CoreFeatureBulkMemoryOperations, // Multiple tables require api.CoreFeatureReferenceTypes.
tables: []Table{{}, {}, {}, {}},
expectedErr: "source table index must be zero for table.copy as feature \"reference-types\" is disabled",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 3, 1},
flag: api.CoreFeatureBulkMemoryOperations | api.CoreFeatureReferenceTypes,
tables: []Table{{}, {Type: RefTypeFuncref}, {}, {Type: RefTypeExternref}},
expectedErr: "table type mismatch for table.copy: funcref (src) != externref (dst)",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []Table{{}},
expectedErr: "cannot pop the operand for table.copy: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscTableCopy, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []Table{{}},
expectedErr: "cannot pop the operand for table.copy: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscTableCopy, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []Table{{}},
expectedErr: "cannot pop the operand for table.copy: i32 missing",
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.expectedErr, func(t *testing.T) {
m := &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: tc.body}},
ElementSection: tc.elementSection,
DataSection: tc.dataSection,
}
if !tc.dataCountSectionNil {
c := uint32(0)
m.DataCountSection = &c
}
err := m.validateFunction(&stacks{}, tc.flag, 0, []Index{0}, nil, tc.memory, tc.tables, nil, nil, bytes.NewReader(nil))
require.EqualError(t, err, tc.expectedErr)
})
}
})
}
var (
f32, f64, i32, i64, v128, externref = ValueTypeF32, ValueTypeF64, ValueTypeI32, ValueTypeI64, ValueTypeV128, ValueTypeExternref
f32i32_v = initFt([]ValueType{f32, i32}, nil)
f64f32_i64 = initFt([]ValueType{f64, f32}, []ValueType{i64})
f64i32_v128i64 = initFt([]ValueType{f64, i32}, []ValueType{v128, i64})
i32_i32 = initFt([]ValueType{i32}, []ValueType{i32})
i32f64_v = initFt([]ValueType{i32, f64}, nil)
i32i32_i32 = initFt([]ValueType{i32, i32}, []ValueType{i32})
i32_v = initFt([]ValueType{i32}, nil)
v_v = FunctionType{}
v_f32 = initFt(nil, []ValueType{f32})
v_f32f32 = initFt(nil, []ValueType{f32, f32})
v_f64i32 = initFt(nil, []ValueType{f64, i32})
v_f64f64 = initFt(nil, []ValueType{f64, f64})
v_i32 = initFt(nil, []ValueType{i32})
v_i32i32 = initFt(nil, []ValueType{i32, i32})
v_i32i64 = initFt(nil, []ValueType{i32, i64})
v_i64i64 = initFt(nil, []ValueType{i64, i64})
)
func initFt(params, results []ValueType) FunctionType {
ft := FunctionType{Params: params, Results: results}
ft.CacheNumInUint64()
return ft
}
// TestModule_ValidateFunction_MultiValue_TypeMismatch are "type mismatch" tests when "multi-value" was merged.
//
// See https://github.com/WebAssembly/spec/commit/484180ba3d9d7638ba1cb400b699ffede796927c
func TestModule_ValidateFunction_MultiValue_TypeMismatch(t *testing.T) {
tests := []struct {
name string
module *Module
expectedErr string
enabledFeatures api.CoreFeatures
}{
// test/core/func.wast
{
name: `func.wast - type-empty-f64-i32`,
module: &Module{
TypeSection: []FunctionType{v_f64i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{OpcodeEnd}}},
},
expectedErr: `not enough results
have ()
want (f64, i32)`,
},
{
name: `func.wast - type-value-void-vs-nums`,
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{OpcodeNop, OpcodeEnd}}},
},
expectedErr: `not enough results
have ()
want (i32, i32)`,
},
{
name: `func.wast - type-value-nums-vs-void`,
module: &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{OpcodeI32Const, 0, OpcodeI64Const, 0, OpcodeEnd}}},
},
expectedErr: `too many results
have (i32, i64)
want ()`,
},
{
name: `func.wast - type-value-num-vs-nums - v_f32f32 -> f32`,
module: &Module{
TypeSection: []FunctionType{v_f32f32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results
have (f32)
want (f32, f32)`,
},
{
name: `func.wast - type-value-num-vs-nums - v_f32 -> f32f32`,
module: &Module{
TypeSection: []FunctionType{v_f32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeF32Const, 0, 0, 0, 0, OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0) (f32.const 0)
OpcodeEnd, // func
}}},
},
expectedErr: `too many results
have (f32, f32)
want (f32)`,
},
{
name: `func.wast - type-return-last-empty-vs-nums`,
module: &Module{
TypeSection: []FunctionType{v_f32f32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{OpcodeReturn, OpcodeEnd}}},
},
expectedErr: `not enough results
have ()
want (f32, f32)`,
},
{
name: `func.wast - type-return-last-void-vs-nums`,
module: &Module{
TypeSection: []FunctionType{v_i32i64},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{OpcodeNop, OpcodeReturn, OpcodeEnd}}}, // (return (nop))
},
expectedErr: `not enough results
have ()
want (i32, i64)`,
},
{
name: `func.wast - type-return-last-num-vs-nums`,
module: &Module{
TypeSection: []FunctionType{v_i64i64},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI64Const, 0, OpcodeReturn, // (return (i64.const 0))
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results
have (i64)
want (i64, i64)`,
},
{
name: `func.wast - type-return-empty-vs-nums`,
// This should err because (return) precedes the values expected in the signature (i32i32):
// (module (func $type-return-empty-vs-nums (result i32 i32)
// (return) (i32.const 1) (i32.const 2)
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeReturn, OpcodeI32Const, 1, OpcodeI32Const, 2,
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results
have ()
want (i32, i32)`,
},
{
name: `func.wast - type-return-partial-vs-nums`,
// This should err because (return) precedes one of the values expected in the signature (i32i32):
// (module (func $type-return-partial-vs-nums (result i32 i32)
// (i32.const 1) (return) (i32.const 2)
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeReturn, OpcodeI32Const, 2,
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results
have (i32)
want (i32, i32)`,
},
{
name: `func.wast - type-return-void-vs-nums`,
// This should err because (return) is empty due to nop, but the signature requires i32i32:
// (module (func $type-return-void-vs-nums (result i32 i32)
// (return (nop)) (i32.const 1)
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeNop, OpcodeReturn, // (return (nop))
OpcodeI32Const, 1, // (i32.const 1)
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results
have ()
want (i32, i32)`,
},
{
name: `func.wast - type-return-num-vs-nums`,
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI64Const, 1, OpcodeReturn, // (return (i64.const 1))
OpcodeI32Const, 1, OpcodeI32Const, 2, // (i32.const 1) (i32.const 2)
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results
have (i64)
want (i32, i32)`,
},
{
name: `func.wast - type-return-first-num-vs-nums`,
// This should err because the return block doesn't return enough values.
// (module (func $type-return-first-num-vs-nums (result i32 i32)
// (return (i32.const 1)) (return (i32.const 1) (i32.const 2))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI64Const, 1, OpcodeReturn, // (return (i64.const 1))
OpcodeI32Const, 1, OpcodeI32Const, 2, OpcodeReturn, // (return (i32.const 1) (i32.const 2))
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results
have (i64)
want (i32, i32)`,
},
{
name: `func.wast - type-break-last-num-vs-nums`,
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 0, OpcodeBr, 0, // (br 0 (i32.const 0))
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have (i32)
want (i32, i32)`,
},
{
name: `func.wast - type-break-void-vs-nums`,
// This should err because (br 0) returns no values, but its enclosing function requires two:
// (module (func $type-break-void-vs-nums (result i32 i32)
// (br 0) (i32.const 1) (i32.const 2)
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeBr, 0, // (br 0)
OpcodeI32Const, 1, OpcodeI32Const, 2, // (i32.const 1) (i32.const 2)
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have ()
want (i32, i32)`,
},
{
name: `func.wast - type-break-num-vs-nums`,
// This should err because (br 0) returns one value, but its enclosing function requires two:
// (module (func $type-break-num-vs-nums (result i32 i32)
// (br 0 (i32.const 1)) (i32.const 1) (i32.const 2)
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeBr, 0, // (br 0 (i32.const 1))
OpcodeI32Const, 1, OpcodeI32Const, 2, // (i32.const 1) (i32.const 2)
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have (i32)
want (i32, i32)`,
},
{
name: `func.wast - type-break-nested-empty-vs-nums`,
// This should err because (br 1) doesn't return values, but its enclosing function does:
// (module (func $type-break-nested-empty-vs-nums (result i32 i32)
// (block (br 1)) (br 0 (i32.const 1) (i32.const 2))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeBlock, 0x40, OpcodeBr, 0x01, OpcodeEnd, // (block (br 1))
OpcodeI32Const, 1, OpcodeI32Const, 2, OpcodeBr, 0, // (br 0 (i32.const 1) (i32.const 2))
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have ()
want (i32, i32)`,
},
{
name: `func.wast - type-break-nested-void-vs-nums`,
// This should err because nop returns the empty type, but the enclosing function returns i32i32:
// (module (func $type-break-nested-void-vs-nums (result i32 i32)
// (block (br 1 (nop))) (br 0 (i32.const 1) (i32.const 2))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeBlock, 0x40, OpcodeNop, OpcodeBr, 0x01, OpcodeEnd, // (block (br 1 (nop)))
OpcodeI32Const, 1, OpcodeI32Const, 2, OpcodeBr, 0, // (br 0 (i32.const 1) (i32.const 2))
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have ()
want (i32, i32)`,
},
{
name: `func.wast - type-break-nested-num-vs-nums`,
// This should err because the block signature is v_i32, but the enclosing function is v_i32i32:
// (module (func $type-break-nested-num-vs-nums (result i32 i32)
// (block (result i32) (br 1 (i32.const 1))) (br 0 (i32.const 1) (i32.const 2))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeBlock, 0x7f, OpcodeI32Const, 1, OpcodeBr, 1, OpcodeEnd, // (block (result i32) (br 1 (i32.const 1)))
OpcodeI32Const, 1, OpcodeI32Const, 2, OpcodeBr, 0, // (br 0 (i32.const 1) (i32.const 2))
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have (i32)
want (i32, i32)`,
},
// test/core/if.wast
{
name: `if.wast - wrong signature for if type use`,
// This should err because (br 0) returns no values, but its enclosing function requires two:
// (module
// (type $sig (func))
// (func (i32.const 1) (if (type $sig) (i32.const 0) (then)))
// )
module: &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, // (i32.const 1)
OpcodeI32Const, 0, OpcodeIf, 0, // (if (type $sig) (i32.const 0)
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `too many results
have (i32)
want ()`,
},
{
name: `if.wast - type-then-value-nums-vs-void`,
// This should err because (if) without a type use returns no values, but its (then) returns two:
// (module (func $type-then-value-nums-vs-void
// (if (i32.const 1) (then (i32.const 1) (i32.const 2)))
// ))
module: &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x40, // (if (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 2, // (then (i32.const 1) (i32.const 2))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in if block
have (i32, i32)
want ()`,
},
{
name: `if.wast - type-then-value-nums-vs-void-else`,
// This should err because (if) without a type use returns no values, but its (then) returns two:
// (module (func $type-then-value-nums-vs-void-else
// (if (i32.const 1) (then (i32.const 1) (i32.const 2)) (else))
// ))
module: &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x40, // (if (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 2, // (then (i32.const 1) (i32.const 2))
OpcodeElse, // (else)
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in if block
have (i32, i32)
want ()`,
},
{
name: `if.wast - type-else-value-nums-vs-void`,
// This should err because (if) without a type use returns no values, but its (else) returns two:
// (module (func $type-else-value-nums-vs-void
// (if (i32.const 1) (then) (else (i32.const 1) (i32.const 2)))
// ))
module: &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x40, // (if (i32.const 1) (then)
OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 2, // (else (i32.const 1) (i32.const 2))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in else block
have (i32, i32)
want ()`,
},
{
name: `if.wast - type-both-value-nums-vs-void`,
// This should err because (if) without a type use returns no values, each branch returns two:
// (module (func $type-both-value-nums-vs-void
// (if (i32.const 1) (then (i32.const 1) (i32.const 2)) (else (i32.const 2) (i32.const 1)))
// ))
module: &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x40, // (if (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 2, // (then (i32.const 1) (i32.const 2))
OpcodeElse, OpcodeI32Const, 2, OpcodeI32Const, 1, // (else (i32.const 2) (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in if block
have (i32, i32)
want ()`,
},
{
name: `if.wast - type-then-value-empty-vs-nums`,
// This should err because the if branch is empty, but its type use requires two i32s:
// (module (func $type-then-value-empty-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then) (else (i32.const 0) (i32.const 2)))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (result i32 i32) (i32.const 1) (then)
OpcodeElse, OpcodeI32Const, 0, OpcodeI32Const, 2, // (else (i32.const 0) (i32.const 2)))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in if block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-else-value-empty-vs-nums`,
// This should err because the else branch is empty, but its type use requires two i32s:
// (module (func $type-else-value-empty-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (i32.const 0) (i32.const 1)) (else))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 0, OpcodeI32Const, 2, // (then (i32.const 0) (i32.const 1))
OpcodeElse, // (else)
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in else block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-both-value-empty-vs-nums`,
// This should err because the both branches are empty, but the if type use requires two i32s:
// (module (func $type-both-value-empty-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then) (else))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (result i32 i32) (i32.const 1) (then)
OpcodeElse, // (else)
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in if block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-no-else-vs-nums`,
// This should err because the else branch is missing, but its type use requires two i32s:
// (module (func $type-no-else-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (i32.const 1) (i32.const 1)))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in else block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-then-value-void-vs-nums`,
// This should err because the then branch evaluates to empty, but its type use requires two i32s:
// (module (func $type-then-value-void-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (nop)) (else (i32.const 0) (i32.const 0)))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (result i32 i32) (i32.const 1)
OpcodeNop, // (then (nop))
OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in if block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-then-value-void-vs-nums`,
// This should err because the else branch evaluates to empty, but its type use requires two i32s:
// (module (func $type-else-value-void-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (i32.const 0) (i32.const 0)) (else (nop)))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 0, OpcodeI32Const, 0, // (then (i32.const 0) (i32.const 0))
OpcodeElse, OpcodeNop, // (else (nop))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in else block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-both-value-void-vs-nums`,
// This should err because the if branch evaluates to empty, but its type use requires two i32s:
// (module (func $type-both-value-void-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (nop)) (else (nop)))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeNop, // (then (nop))
OpcodeElse, OpcodeNop, // (else (nop))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in if block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-then-value-num-vs-nums`,
// This should err because the if branch returns one value, but its type use requires two:
// (module (func $type-then-value-num-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (i32.const 1)) (else (i32.const 1) (i32.const 1)))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, // (then (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1)))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in if block
have (i32)
want (i32, i32)`,
},
{
name: `if.wast - type-else-value-num-vs-nums`,
// This should err because the else branch returns one value, but its type use requires two:
// (module (func $type-else-value-num-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (i32.const 1) (i32.const 1)) (else (i32.const 1)))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1)))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in else block
have (i32)
want (i32, i32)`,
},
{
name: `if.wast - type-both-value-num-vs-nums`,
// This should err because the if branch returns one value, but its type use requires two:
// (module (func $type-both-value-num-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (i32.const 1)) (else (i32.const 1)))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, // (then (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1)))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in if block
have (i32)
want (i32, i32)`,
},
{
name: `if.wast - type-then-value-partial-vs-nums`,
// This should err because the if branch returns one value, but its type use requires two:
// (module (func $type-then-value-partial-vs-nums (result i32 i32)
// (i32.const 0)
// (if (result i32 i32) (i32.const 1) (then (i32.const 1)) (else (i32.const 1) (i32.const 1)))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 0, // (i32.const 0) - NOTE: this is outside the (if)
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, // (then (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in if block
have (i32)
want (i32, i32)`,
},
{
name: `if.wast - type-else-value-partial-vs-nums`,
// This should err because the else branch returns one value, but its type use requires two:
// (module (func $type-else-value-partial-vs-nums (result i32 i32)
// (i32.const 0)
// (if (result i32 i32) (i32.const 1) (then (i32.const 1) (i32.const 1)) (else (i32.const 1)))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 0, // (i32.const 0) - NOTE: this is outside the (if)
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in else block
have (i32)
want (i32, i32)`,
},
{
name: `if.wast - type-both-value-partial-vs-nums`,
// This should err because the if branch returns one value, but its type use requires two:
// (module (func $type-both-value-partial-vs-nums (result i32 i32)
// (i32.const 0)
// (if (result i32 i32) (i32.const 1) (then (i32.const 1)) (else (i32.const 1)))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 0, // (i32.const 0) - NOTE: this is outside the (if)
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, // (then (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in if block
have (i32)
want (i32, i32)`,
},
{
name: `if.wast - type-then-value-nums-vs-num`,
// This should err because the if branch returns two values, but its type use requires one:
// (module (func $type-then-value-nums-vs-num (result i32)
// (if (result i32) (i32.const 1) (then (i32.const 1) (i32.const 1)) (else (i32.const 1)))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in if block
have (i32, i32)
want (i32)`,
},
{
name: `if.wast - type-else-value-nums-vs-num`,
// This should err because the else branch returns two values, but its type use requires one:
// (module (func $type-else-value-nums-vs-num (result i32)
// (if (result i32) (i32.const 1) (then (i32.const 1)) (else (i32.const 1) (i32.const 1)))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32) (i32.const 1)
OpcodeI32Const, 1, // (then (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in else block
have (i32, i32)
want (i32)`,
},
{
name: `if.wast - type-both-value-nums-vs-num`,
// This should err because the if branch returns two values, but its type use requires one:
// (module (func $type-both-value-nums-vs-num (result i32)
// (if (result i32) (i32.const 1) (then (i32.const 1) (i32.const 1)) (else (i32.const 1) (i32.const 1)))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in if block
have (i32, i32)
want (i32)`,
},
{
name: `if.wast - type-both-different-value-nums-vs-nums`,
// This should err because the if branch returns three values, but its type use requires two:
// (module (func $type-both-different-value-nums-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (i32.const 1) (i32.const 1) (i32.const 1)) (else (i32.const 1)))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1) (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in if block
have (i32, i32, i32)
want (i32, i32)`,
},
{
name: `if.wast - type-then-break-last-void-vs-nums`,
// This should err because the branch in the if returns no values, but its type use requires two:
// (module (func $type-then-break-last-void-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (br 0)) (else (i32.const 1) (i32.const 1)))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeBr, 0, // (then (br 0))
OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1)))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-else-break-last-void-vs-nums`,
// This should err because the branch in the else returns no values, but its type use requires two:
// (module (func $type-else-break-last-void-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (i32.const 1) (i32.const 1)) (else (br 0)))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
OpcodeElse, OpcodeBr, 0, // (else (br 0))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-then-break-empty-vs-nums`,
// This should err because the branch in the if returns no values, but its type use requires two:
// (module (func $type-then-break-empty-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1)
// (then (br 0) (i32.const 1) (i32.const 1))
// (else (i32.const 1) (i32.const 1))
// )
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeBr, 0, OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (br 0) (i32.const 1) (i32.const 1))
// ^^ NOTE: consts are outside the br block
OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-else-break-empty-vs-nums`,
// This should err because the branch in the else returns no values, but its type use requires two:
// (module (func $type-else-break-empty-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1)
// (then (i32.const 1) (i32.const 1))
// (else (br 0) (i32.const 1) (i32.const 1))
// )
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
OpcodeElse, OpcodeBr, 0, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (br 0) (i32.const 1) (i32.const 1))
// ^^ NOTE: consts are outside the br block
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-then-break-void-vs-nums`,
// This should err because the branch in the if evaluates to no values, but its type use requires two:
// (module (func $type-then-break-void-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1)
// (then (br 0 (nop)) (i32.const 1) (i32.const 1))
// (else (i32.const 1) (i32.const 1))
// )
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeNop, OpcodeBr, 0, OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (br 0 (nop)) (i32.const 1) (i32.const 1))
// ^^ NOTE: consts are outside the br block
OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-else-break-void-vs-nums`,
// This should err because the branch in the else evaluates to no values, but its type use requires two:
// (module (func $type-else-break-void-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1)
// (then (i32.const 1) (i32.const 1))
// (else (br 0 (nop)) (i32.const 1) (i32.const 1))
// )
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
OpcodeElse, OpcodeNop, OpcodeBr, 0, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (br 0 (nop)) (i32.const 1) (i32.const 1))
// ^^ NOTE: consts are outside the br block
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-then-break-num-vs-nums`,
// This should err because the branch in the if evaluates to one value, but its type use requires two:
// (module (func $type-then-break-num-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1)
// (then (br 0 (i64.const 1)) (i32.const 1) (i32.const 1))
// (else (i32.const 1) (i32.const 1))
// )
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI64Const, 1, OpcodeBr, 0, OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (br 0 (i64.const 1)) (i32.const 1) (i32.const 1))
// ^^ NOTE: only one (incorrect) const is inside the br block
OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have (i64)
want (i32, i32)`,
},
{
name: `if.wast - type-else-break-num-vs-nums`,
// This should err because the branch in the else evaluates to one value, but its type use requires two:
// (module (func $type-else-break-num-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1)
// (then (i32.const 1) (i32.const 1))
// (else (br 0 (i64.const 1)) (i32.const 1) (i32.const 1))
// )
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
OpcodeElse, OpcodeI64Const, 1, OpcodeBr, 0, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (br 0 (i64.const 1)) (i32.const 1) (i32.const 1))
// ^^ NOTE: only one (incorrect) const is inside the br block
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have (i64)
want (i32, i32)`,
},
{
name: `if.wast - type-then-break-partial-vs-nums`,
// This should err because the branch in the if evaluates to one value, but its type use requires two:
// (module (func $type-then-break-partial-vs-nums (result i32 i32)
// (i32.const 1)
// (if (result i32 i32) (i32.const 1)
// (then (br 0 (i64.const 1)) (i32.const 1))
// (else (i32.const 1))
// )
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, // (i32.const 1)
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI64Const, 1, OpcodeBr, 0, OpcodeI32Const, 1, // (then (br 0 (i64.const 1)) (i32.const 1))
// ^^ NOTE: only one (incorrect) const is inside the br block
OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have (i64)
want (i32, i32)`,
},
{
name: `if.wast - type-else-break-partial-vs-nums`,
// This should err because the branch in the if evaluates to one value, but its type use requires two:
// (module (func $type-else-break-partial-vs-nums (result i32 i32)
// (i32.const 1)
// (if (result i32 i32) (i32.const 1)
// (then (i32.const 1))
// (else (br 0 (i64.const 1)) (i32.const 1))
// )
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, // (i32.const 1)
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, // (then (i32.const 1))
OpcodeElse, OpcodeI64Const, 1, OpcodeBr, 0, OpcodeI32Const, 1, // (else (br 0 (i64.const 1)) (i32.const 1))
// ^^ NOTE: only one (incorrect) const is inside the br block
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in if block
have (i32)
want (i32, i32)`,
},
{
name: `if.wast - type-param-void-vs-num`,
// This should err because the stack has no values, but the if type use requires two:
// (module (func $type-param-void-vs-num
// (if (param i32) (i32.const 1) (then (drop)))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, i32_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param i32) (i32.const 1)
OpcodeDrop, // (then (drop)))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for if block
have ()
want (i32)`,
},
{
name: `if.wast - type-param-void-vs-nums`,
// This should err because the stack has no values, but the if type use requires two:
// (module (func $type-param-void-vs-nums
// (if (param i32 f64) (i32.const 1) (then (drop) (drop)))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, i32f64_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param i32 f64) (i32.const 1)
OpcodeI32Const, 1, OpcodeDrop, // (then (drop) (drop))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for if block
have ()
want (i32, f64)`,
},
{
name: `if.wast - type-param-num-vs-num`,
// This should err because the stack has a different value that what the if type use requires:
// (module (func $type-param-num-vs-num
// (f32.const 0) (if (param i32) (i32.const 1) (then (drop)))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, i32_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param i32) (i32.const 1)
OpcodeDrop, // (then (drop))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: "cannot use f32 in if block as param[0] type i32",
},
{
name: `if.wast - type-param-num-vs-nums`,
// This should err because the stack has one value, but the if type use requires two:
// (module (func $type-param-num-vs-nums
// (f32.const 0) (if (param f32 i32) (i32.const 1) (then (drop) (drop)))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, f32i32_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param f32 i32) (i32.const 1)
OpcodeDrop, OpcodeDrop, // (then (drop) (drop))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for if block
have (f32)
want (f32, i32)`,
},
{
name: `if.wast - type-param-nested-void-vs-num`,
// This should err because the stack has no values, but the if type use requires one:
// (module (func $type-param-nested-void-vs-num
// (block (if (param i32) (i32.const 1) (then (drop))))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, i32_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeBlock, 0x40, // (block
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param i32) (i32.const 1)
OpcodeDrop, // (then (drop))
OpcodeEnd, // block
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for if block
have ()
want (i32)`,
},
{
name: `if.wast - type-param-void-vs-nums`,
// This should err because the stack has no values, but the if type use requires two:
// (module (func $type-param-void-vs-nums
// (block (if (param i32 f64) (i32.const 1) (then (drop) (drop))))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, i32f64_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeBlock, 0x40, // (block
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param i32 f64) (i32.const 1)
OpcodeDrop, // (then (drop) (drop))
OpcodeEnd, // block
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for if block
have ()
want (i32, f64)`,
},
{
name: `if.wast - type-param-num-vs-num`,
// This should err because the stack has a different values than required by the if type use:
// (module (func $type-param-num-vs-num
// (block (f32.const 0) (if (param i32) (i32.const 1) (then (drop))))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, i32_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeBlock, 0x40, // (block
OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param i32) (i32.const 1)
OpcodeDrop, // (then (drop))
OpcodeEnd, // block
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: "cannot use f32 in if block as param[0] type i32",
},
{
name: `if.wast - type-param-num-vs-nums`,
// This should err because the stack has one value, but the if type use requires two:
// (module (func $type-param-num-vs-nums
// (block (f32.const 0) (if (param f32 i32) (i32.const 1) (then (drop) (drop))))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, f32i32_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeBlock, 0x40, // (block
OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param f32 i32) (i32.const 1)
OpcodeDrop, // (then (drop) (drop))
OpcodeEnd, // block
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for if block
have (f32)
want (f32, i32)`,
},
// test/core/loop.wast
{
name: `loop.wast - wrong signature for loop type use`,
// This should err because the loop type use returns no values, but its block returns one:
// (module
// (type $sig (func))
// (func (loop (type $sig) (i32.const 0)))
// )
module: &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeLoop, 0, OpcodeI32Const, 0, // (loop (type $sig) (i32.const 0))
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in loop block
have (i32)
want ()`,
},
{
name: `loop.wast - type-value-nums-vs-void`,
// This should err because the empty block type requires no values, but the loop returns two:
// (module (func $type-value-nums-vs-void
// (loop (i32.const 1) (i32.const 2))
// ))
module: &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeLoop, 0x40, OpcodeI32Const, 1, OpcodeI32Const, 2, // (loop (i32.const 1) (i32.const 2))
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in loop block
have (i32, i32)
want ()`,
},
{
name: `loop.wast - type-value-empty-vs-nums`,
// This should err because the loop type use returns two values, but the block returns none:
// (module (func $type-value-empty-vs-nums (result i32 i32)
// (loop (result i32 i32))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeLoop, 0x0, // (loop (result i32 i32)) - matches existing func type
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in loop block
have ()
want (i32, i32)`,
},
{
name: `loop.wast - type-value-void-vs-nums`,
// This should err because the loop type use returns two values, but the block returns none:
// (module (func $type-value-void-vs-nums (result i32 i32)
// (loop (result i32 i32) (nop))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeLoop, 0x0, // (loop (result i32 i32) - matches existing func type
OpcodeNop, // (nop)
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in loop block
have ()
want (i32, i32)`,
},
{
name: `loop.wast - type-value-num-vs-nums`,
// This should err because the loop type use returns two values, but the block returns one:
// (module (func $type-value-num-vs-nums (result i32 i32)
// (loop (result i32 i32) (i32.const 0))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeLoop, 0x0, // (loop (result i32 i32) - matches existing func type
OpcodeI32Const, 0, // (i32.const 0)
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in loop block
have (i32)
want (i32, i32)`,
},
{
name: `loop.wast - type-value-partial-vs-nums`,
// This should err because the loop type use returns two values, but the block returns one:
// (module (func $type-value-partial-vs-nums (result i32 i32)
// (i32.const 1) (loop (result i32 i32) (i32.const 2))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1, // (i32.const 1) - NOTE: outside the loop!
OpcodeLoop, 0x0, // (loop (result i32 i32) - matches existing func type
OpcodeI32Const, 2, // (i32.const 2)
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in loop block
have (i32)
want (i32, i32)`,
},
{
name: `loop.wast - type-value-nums-vs-num`,
// This should err because the loop type use returns one value, but the block returns two:
// (module (func $type-value-nums-vs-num (result i32)
// (loop (result i32) (i32.const 1) (i32.const 2))
// ))
module: &Module{
TypeSection: []FunctionType{v_i32},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeLoop, 0x0, // (loop (result i32) - matches existing func type
OpcodeI32Const, 1, OpcodeI32Const, 2, // (i32.const 1) (i32.const 2))
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in loop block
have (i32, i32)
want (i32)`,
},
{
name: `loop.wast - type-param-void-vs-num`,
// This should err because the loop type use requires one param, but the stack has none:
// (module (func $type-param-void-vs-num
// (loop (param i32) (drop))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, i32_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeLoop, 0x1, // (loop (param i32)
OpcodeDrop, // (drop)
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for loop block
have ()
want (i32)`,
},
{
name: `loop.wast - type-param-void-vs-nums`,
// This should err because the loop type use requires two params, but the stack has none:
// (module (func $type-param-void-vs-nums
// (loop (param i32 f64) (drop) (drop))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, i32f64_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeLoop, 0x1, // (loop (param i32 f64)
OpcodeDrop, // (drop)
OpcodeDrop, // (drop)
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for loop block
have ()
want (i32, f64)`,
},
{
name: `loop.wast - type-param-num-vs-num`,
// This should err because the loop type use requires a different param type than what's on the stack:
// (module (func $type-param-num-vs-num
// (f32.const 0) (loop (param i32) (drop))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, i32_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
OpcodeLoop, 0x1, // (loop (param i32)
OpcodeDrop, // (drop)
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: "cannot use f32 in loop block as param[0] type i32",
},
{
name: `loop.wast - type-param-num-vs-num`,
// This should err because the loop type use requires a more parameters than what's on the stack:
// (module (func $type-param-num-vs-nums
// (f32.const 0) (loop (param f32 i32) (drop) (drop))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, f32i32_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
OpcodeLoop, 0x1, // (loop (param f32 i32)
OpcodeDrop, OpcodeDrop, // (drop) (drop)
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for loop block
have (f32)
want (f32, i32)`,
},
{
name: `loop.wast - type-param-nested-void-vs-num`,
// This should err because the loop type use requires a more parameters than what's on the stack:
// (module (func $type-param-nested-void-vs-num
// (block (loop (param i32) (drop)))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, i32_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeBlock, 0x40, // (block
OpcodeLoop, 0x1, // (loop (param i32)
OpcodeDrop, // (drop)
OpcodeEnd, // loop
OpcodeEnd, // block
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for loop block
have ()
want (i32)`,
},
{
name: `loop.wast - type-param-void-vs-nums`,
// This should err because the loop type use requires a more parameters than what's on the stack:
// (module (func $type-param-void-vs-nums
// (block (loop (param i32 f64) (drop) (drop)))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, i32f64_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeBlock, 0x40, // (block
OpcodeLoop, 0x1, // (loop (param i32 f64)
OpcodeDrop, OpcodeDrop, // (drop) (drop)
OpcodeEnd, // loop
OpcodeEnd, // block
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for loop block
have ()
want (i32, f64)`,
},
{
name: `loop.wast - type-param-void-vs-nums`,
// This should err because the loop type use requires a different param type than what's on the stack:
// (module (func $type-param-num-vs-num
// (block (f32.const 0) (loop (param i32) (drop)))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, i32_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeBlock, 0x40, // (block
OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
OpcodeLoop, 0x1, // (loop (param i32)
OpcodeDrop, // (drop)
OpcodeEnd, // loop
OpcodeEnd, // block
OpcodeEnd, // func
}}},
},
expectedErr: "cannot use f32 in loop block as param[0] type i32",
},
{
name: `loop.wast - type-param-void-vs-nums`,
// This should err because the loop type use requires a more parameters than what's on the stack:
// (module (func $type-param-num-vs-nums
// (block (f32.const 0) (loop (param f32 i32) (drop) (drop)))
// ))
module: &Module{
TypeSection: []FunctionType{v_v, f32i32_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeBlock, 0x40, // (block
OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
OpcodeLoop, 0x1, // (loop (param f32 i32)
OpcodeDrop, OpcodeDrop, // (drop) (drop)
OpcodeEnd, // loop
OpcodeEnd, // block
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for loop block
have (f32)
want (f32, i32)`,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
err := tc.module.validateFunction(&stacks{}, api.CoreFeatureMultiValue,
0, []Index{0}, nil, nil, nil, nil, nil, bytes.NewReader(nil))
require.EqualError(t, err, tc.expectedErr)
})
}
}
func TestModule_funcValidation_CallIndirect(t *testing.T) {
t.Run("ok", func(t *testing.T) {
m := &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1,
OpcodeCallIndirect, 0, 0,
OpcodeEnd,
}}},
}
err := m.validateFunction(&stacks{}, api.CoreFeatureReferenceTypes,
0, []Index{0}, nil, &Memory{}, []Table{{Type: RefTypeFuncref}}, nil, nil, bytes.NewReader(nil))
require.NoError(t, err)
})
t.Run("non zero table index", func(t *testing.T) {
m := &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1,
OpcodeCallIndirect, 0, 100,
OpcodeEnd,
}}},
}
t.Run("disabled", func(t *testing.T) {
err := m.validateFunction(&stacks{}, api.CoreFeaturesV1,
0, []Index{0}, nil, &Memory{}, []Table{{}, {}}, nil, nil, bytes.NewReader(nil))
require.EqualError(t, err, "table index must be zero but was 100: feature \"reference-types\" is disabled")
})
t.Run("enabled but out of range", func(t *testing.T) {
err := m.validateFunction(&stacks{}, api.CoreFeatureReferenceTypes,
0, []Index{0}, nil, &Memory{}, []Table{{}, {}}, nil, nil, bytes.NewReader(nil))
require.EqualError(t, err, "unknown table index: 100")
})
})
t.Run("non funcref table", func(t *testing.T) {
m := &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{
OpcodeI32Const, 1,
OpcodeCallIndirect, 0, 0,
OpcodeEnd,
}}},
}
err := m.validateFunction(&stacks{}, api.CoreFeatureReferenceTypes,
0, []Index{0}, nil, &Memory{}, []Table{{Type: RefTypeExternref}}, nil, nil, bytes.NewReader(nil))
require.EqualError(t, err, "table is not funcref type but was externref for call_indirect")
})
}
func TestModule_funcValidation_RefTypes(t *testing.T) {
tests := []struct {
name string
body []byte
flag api.CoreFeatures
declaredFunctionIndexes map[Index]struct{}
expectedErr string
}{
{
name: "ref.null (funcref)",
flag: api.CoreFeatureReferenceTypes,
body: []byte{
OpcodeRefNull, ValueTypeFuncref,
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(&stacks{}, tc.flag,
0, []Index{0}, nil, nil, nil, nil, tc.declaredFunctionIndexes, bytes.NewReader(nil))
if tc.expectedErr != "" {
require.EqualError(t, err, tc.expectedErr)
} else {
require.NoError(t, err)
}
})
}
}
func TestModule_funcValidation_TableGrowSizeFill(t *testing.T) {
tables := []Table{{Type: RefTypeFuncref}, {Type: RefTypeExternref}}
tests := []struct {
name string
body []byte
flag api.CoreFeatures
expectedErr string
}{
{
name: "table.grow (funcref)",
body: []byte{
OpcodeRefNull, RefTypeFuncref,
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(&stacks{}, tc.flag,
0, []Index{0}, nil, nil, tables, nil, nil, bytes.NewReader(nil))
if tc.expectedErr != "" {
require.EqualError(t, err, tc.expectedErr)
} else {
require.NoError(t, err)
}
})
}
}
func TestModule_funcValidation_TableGetSet(t *testing.T) {
tables := []Table{{Type: RefTypeFuncref}, {Type: RefTypeExternref}}
tests := []struct {
name string
body []byte
flag api.CoreFeatures
expectedErr string
}{
{
name: "table.get (funcref)",
body: []byte{
OpcodeI32Const, 0,
OpcodeTableGet, 0,
OpcodeRefIsNull,
OpcodeDrop,
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
},
{
name: "table.get (externref)",
body: []byte{
OpcodeI32Const, 0,
OpcodeTableGet, 1,
OpcodeRefIsNull,
OpcodeDrop,
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
},
{
name: "table.get (disabled)",
body: []byte{
OpcodeI32Const, 0,
OpcodeTableGet, 0,
OpcodeDrop,
OpcodeEnd,
},
flag: api.CoreFeaturesV1,
expectedErr: `table.get is invalid as feature "reference-types" is disabled`,
},
{
name: "table.set (funcref)",
body: []byte{
OpcodeI32Const, 0,
OpcodeRefNull, ValueTypeFuncref,
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(&stacks{}, tc.flag,
0, []Index{0}, nil, nil, tables, nil, nil, bytes.NewReader(nil))
if tc.expectedErr != "" {
require.EqualError(t, err, tc.expectedErr)
} else {
require.NoError(t, err)
}
})
}
}
func TestModule_funcValidation_Select_error(t *testing.T) {
tests := []struct {
name string
body []byte
flag api.CoreFeatures
expectedErr string
}{
{
name: "typed_select (disabled)",
body: []byte{
OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeI32Const, 0,
OpcodeTypedSelect, 1, ValueTypeI32, // immediate vector's size must be one
OpcodeDrop,
OpcodeEnd,
},
flag: api.CoreFeaturesV1,
expectedErr: "typed_select is invalid as feature \"reference-types\" is disabled",
},
{
name: "typed_select (too many immediate types)",
body: []byte{
OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeI32Const, 0,
OpcodeTypedSelect, 2, // immediate vector's size must be one
},
flag: api.CoreFeatureReferenceTypes,
expectedErr: `too many type immediates for typed_select`,
},
{
name: "typed_select (immediate type not found)",
body: []byte{
OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeI32Const, 0,
OpcodeTypedSelect, 1, 0,
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
expectedErr: `invalid type unknown for typed_select`,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
m := &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: tc.body}},
}
err := m.validateFunction(&stacks{}, tc.flag,
0, []Index{0}, nil, nil, nil, nil, nil, bytes.NewReader(nil))
require.EqualError(t, err, tc.expectedErr)
})
}
}
func TestModule_funcValidation_SIMD(t *testing.T) {
addV128Const := func(in []byte) []byte {
return append(in, OpcodeVecPrefix,
OpcodeVecV128Const,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1)
}
vv2v := func(vec OpcodeVec) (ret []byte) {
ret = addV128Const(ret)
ret = addV128Const(ret)
return append(ret,
OpcodeVecPrefix,
vec,
OpcodeDrop,
OpcodeEnd,
)
}
vvv2v := func(vec OpcodeVec) (ret []byte) {
ret = addV128Const(ret)
ret = addV128Const(ret)
ret = addV128Const(ret)
return append(ret,
OpcodeVecPrefix,
vec,
OpcodeDrop,
OpcodeEnd,
)
}
v2v := func(vec OpcodeVec) (ret []byte) {
ret = addV128Const(ret)
return append(ret,
OpcodeVecPrefix,
vec,
OpcodeDrop,
OpcodeEnd,
)
}
vi2v := func(vec OpcodeVec) (ret []byte) {
ret = addV128Const(ret)
return append(ret,
OpcodeI32Const, 1,
OpcodeVecPrefix,
vec,
OpcodeDrop,
OpcodeEnd,
)
}
load := func(vec OpcodeVec, offset, align uint32) (ret []byte) {
ret = []byte{
OpcodeI32Const, 1,
OpcodeVecPrefix,
vec,
}
ret = append(ret, leb128.EncodeUint32(align)...)
ret = append(ret, leb128.EncodeUint32(offset)...)
ret = append(ret,
OpcodeDrop,
OpcodeEnd,
)
return
}
loadLane := func(vec OpcodeVec, offset, align uint32, lane byte) (ret []byte) {
ret = addV128Const([]byte{OpcodeI32Const, 1})
ret = append(ret,
OpcodeVecPrefix,
vec,
)
ret = append(ret, leb128.EncodeUint32(align)...)
ret = append(ret, leb128.EncodeUint32(offset)...)
ret = append(ret,
lane,
OpcodeDrop,
OpcodeEnd,
)
return
}
storeLane := func(vec OpcodeVec, offset, align uint32, lane byte) (ret []byte) {
ret = addV128Const([]byte{OpcodeI32Const, 1})
ret = append(ret,
OpcodeVecPrefix,
vec,
)
ret = append(ret, leb128.EncodeUint32(align)...)
ret = append(ret, leb128.EncodeUint32(offset)...)
ret = append(ret,
lane,
OpcodeEnd,
)
return
}
extractLane := func(vec OpcodeVec, lane byte) (ret []byte) {
ret = addV128Const(ret)
ret = append(ret,
OpcodeVecPrefix,
vec,
lane,
OpcodeDrop,
OpcodeEnd,
)
return
}
replaceLane := func(vec OpcodeVec, lane byte) (ret []byte) {
ret = addV128Const(ret)
switch vec {
case OpcodeVecI8x16ReplaceLane, OpcodeVecI16x8ReplaceLane, OpcodeVecI32x4ReplaceLane:
ret = append(ret, OpcodeI32Const, 0)
case OpcodeVecI64x2ReplaceLane:
ret = append(ret, OpcodeI64Const, 0)
case OpcodeVecF32x4ReplaceLane:
ret = append(ret, OpcodeF32Const, 0, 0, 0, 0)
case OpcodeVecF64x2ReplaceLane:
ret = append(ret, OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0, 0)
}
ret = append(ret,
OpcodeVecPrefix,
vec,
lane,
OpcodeDrop,
OpcodeEnd,
)
return
}
splat := func(vec OpcodeVec) (ret []byte) {
switch vec {
case OpcodeVecI8x16Splat, OpcodeVecI16x8Splat, OpcodeVecI32x4Splat:
ret = append(ret, OpcodeI32Const, 0, 0, 0, 0)
case OpcodeVecI64x2Splat:
ret = append(ret, OpcodeI64Const, 0, 0, 0, 0, 0, 0, 0, 0)
case OpcodeVecF32x4Splat:
ret = append(ret, OpcodeF32Const, 0, 0, 0, 0)
case OpcodeVecF64x2Splat:
ret = append(ret, OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0, 0)
}
ret = append(ret,
OpcodeVecPrefix,
vec,
OpcodeDrop,
OpcodeEnd,
)
return
}
tests := []struct {
name string
body []byte
expectedErr string
}{
{
name: "v128.const",
body: []byte{
OpcodeVecPrefix,
OpcodeVecV128Const,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
OpcodeDrop,
OpcodeEnd,
},
},
{name: OpcodeVecI8x16AddName, body: vv2v(OpcodeVecI8x16Add)},
{name: OpcodeVecI16x8AddName, body: vv2v(OpcodeVecI16x8Add)},
{name: OpcodeVecI32x4AddName, body: vv2v(OpcodeVecI32x4Add)},
{name: OpcodeVecI64x2AddName, body: vv2v(OpcodeVecI64x2Add)},
{name: OpcodeVecI8x16SubName, body: vv2v(OpcodeVecI8x16Sub)},
{name: OpcodeVecI16x8SubName, body: vv2v(OpcodeVecI16x8Sub)},
{name: OpcodeVecI32x4SubName, body: vv2v(OpcodeVecI32x4Sub)},
{name: OpcodeVecI64x2SubName, body: vv2v(OpcodeVecI64x2Sub)},
{name: OpcodeVecV128AnyTrueName, body: v2v(OpcodeVecV128AnyTrue)},
{name: OpcodeVecI8x16AllTrueName, body: v2v(OpcodeVecI8x16AllTrue)},
{name: OpcodeVecI16x8AllTrueName, body: v2v(OpcodeVecI16x8AllTrue)},
{name: OpcodeVecI32x4AllTrueName, body: v2v(OpcodeVecI32x4AllTrue)},
{name: OpcodeVecI64x2AllTrueName, body: v2v(OpcodeVecI64x2AllTrue)},
{name: OpcodeVecI8x16BitMaskName, body: v2v(OpcodeVecI8x16BitMask)},
{name: OpcodeVecI16x8BitMaskName, body: v2v(OpcodeVecI16x8BitMask)},
{name: OpcodeVecI32x4BitMaskName, body: v2v(OpcodeVecI32x4BitMask)},
{name: OpcodeVecI64x2BitMaskName, body: v2v(OpcodeVecI64x2BitMask)},
{name: OpcodeVecV128LoadName, body: load(OpcodeVecV128Load, 0, 0)},
{name: OpcodeVecV128LoadName + "/align=4", body: load(OpcodeVecV128Load, 0, 4)},
{name: OpcodeVecV128Load8x8SName, body: load(OpcodeVecV128Load8x8s, 1, 0)},
{name: OpcodeVecV128Load8x8SName + "/align=1", body: load(OpcodeVecV128Load8x8s, 0, 1)},
{name: OpcodeVecV128Load8x8UName, body: load(OpcodeVecV128Load8x8u, 0, 0)},
{name: OpcodeVecV128Load8x8UName + "/align=1", body: load(OpcodeVecV128Load8x8u, 0, 1)},
{name: OpcodeVecV128Load16x4SName, body: load(OpcodeVecV128Load16x4s, 1, 0)},
{name: OpcodeVecV128Load16x4SName + "/align=2", body: load(OpcodeVecV128Load16x4s, 0, 2)},
{name: OpcodeVecV128Load16x4UName, body: load(OpcodeVecV128Load16x4u, 0, 0)},
{name: OpcodeVecV128Load16x4UName + "/align=2", body: load(OpcodeVecV128Load16x4u, 0, 2)},
{name: OpcodeVecV128Load32x2SName, body: load(OpcodeVecV128Load32x2s, 1, 0)},
{name: OpcodeVecV128Load32x2SName + "/align=3", body: load(OpcodeVecV128Load32x2s, 0, 3)},
{name: OpcodeVecV128Load32x2UName, body: load(OpcodeVecV128Load32x2u, 0, 0)},
{name: OpcodeVecV128Load32x2UName + "/align=3", body: load(OpcodeVecV128Load32x2u, 0, 3)},
{name: OpcodeVecV128Load8SplatName, body: load(OpcodeVecV128Load8Splat, 2, 0)},
{name: OpcodeVecV128Load16SplatName, body: load(OpcodeVecV128Load16Splat, 0, 1)},
{name: OpcodeVecV128Load32SplatName, body: load(OpcodeVecV128Load32Splat, 3, 2)},
{name: OpcodeVecV128Load64SplatName, body: load(OpcodeVecV128Load64Splat, 0, 3)},
{name: OpcodeVecV128Load32zeroName, body: load(OpcodeVecV128Load32zero, 0, 2)},
{name: OpcodeVecV128Load64zeroName, body: load(OpcodeVecV128Load64zero, 5, 3)},
{name: OpcodeVecV128Load8LaneName, body: loadLane(OpcodeVecV128Load8Lane, 5, 0, 10)},
{name: OpcodeVecV128Load16LaneName, body: loadLane(OpcodeVecV128Load16Lane, 100, 1, 7)},
{name: OpcodeVecV128Load32LaneName, body: loadLane(OpcodeVecV128Load32Lane, 0, 2, 3)},
{name: OpcodeVecV128Load64LaneName, body: loadLane(OpcodeVecV128Load64Lane, 0, 3, 1)},
{
name: OpcodeVecV128StoreName, body: []byte{
OpcodeI32Const,
1, 1, 1, 1,
OpcodeVecPrefix,
OpcodeVecV128Const,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
OpcodeVecPrefix,
OpcodeVecV128Store,
4, // alignment
10, // offset
OpcodeEnd,
},
},
{name: OpcodeVecV128Store8LaneName, body: storeLane(OpcodeVecV128Store8Lane, 0, 0, 0)},
{name: OpcodeVecV128Store8LaneName + "/lane=15", body: storeLane(OpcodeVecV128Store8Lane, 100, 0, 15)},
{name: OpcodeVecV128Store16LaneName, body: storeLane(OpcodeVecV128Store16Lane, 0, 0, 0)},
{name: OpcodeVecV128Store16LaneName + "/lane=7/align=1", body: storeLane(OpcodeVecV128Store16Lane, 100, 1, 7)},
{name: OpcodeVecV128Store32LaneName, body: storeLane(OpcodeVecV128Store32Lane, 0, 0, 0)},
{name: OpcodeVecV128Store32LaneName + "/lane=3/align=2", body: storeLane(OpcodeVecV128Store32Lane, 100, 2, 3)},
{name: OpcodeVecV128Store64LaneName, body: storeLane(OpcodeVecV128Store64Lane, 0, 0, 0)},
{name: OpcodeVecV128Store64LaneName + "/lane=1/align=3", body: storeLane(OpcodeVecV128Store64Lane, 50, 3, 1)},
{name: OpcodeVecI8x16ExtractLaneSName, body: extractLane(OpcodeVecI8x16ExtractLaneS, 0)},
{name: OpcodeVecI8x16ExtractLaneSName + "/lane=15", body: extractLane(OpcodeVecI8x16ExtractLaneS, 15)},
{name: OpcodeVecI8x16ExtractLaneUName, body: extractLane(OpcodeVecI8x16ExtractLaneU, 0)},
{name: OpcodeVecI8x16ExtractLaneUName + "/lane=15", body: extractLane(OpcodeVecI8x16ExtractLaneU, 15)},
{name: OpcodeVecI16x8ExtractLaneSName, body: extractLane(OpcodeVecI16x8ExtractLaneS, 0)},
{name: OpcodeVecI16x8ExtractLaneSName + "/lane=7", body: extractLane(OpcodeVecI16x8ExtractLaneS, 7)},
{name: OpcodeVecI16x8ExtractLaneUName, body: extractLane(OpcodeVecI16x8ExtractLaneU, 0)},
{name: OpcodeVecI16x8ExtractLaneUName + "/lane=8", body: extractLane(OpcodeVecI16x8ExtractLaneU, 7)},
{name: OpcodeVecI32x4ExtractLaneName, body: extractLane(OpcodeVecI32x4ExtractLane, 0)},
{name: OpcodeVecI32x4ExtractLaneName + "/lane=3", body: extractLane(OpcodeVecI32x4ExtractLane, 3)},
{name: OpcodeVecI64x2ExtractLaneName, body: extractLane(OpcodeVecI64x2ExtractLane, 0)},
{name: OpcodeVecI64x2ExtractLaneName + "/lane=1", body: extractLane(OpcodeVecI64x2ExtractLane, 1)},
{name: OpcodeVecF32x4ExtractLaneName, body: extractLane(OpcodeVecF32x4ExtractLane, 0)},
{name: OpcodeVecF32x4ExtractLaneName + "/lane=3", body: extractLane(OpcodeVecF32x4ExtractLane, 3)},
{name: OpcodeVecF64x2ExtractLaneName, body: extractLane(OpcodeVecF64x2ExtractLane, 0)},
{name: OpcodeVecF64x2ExtractLaneName + "/lane=1", body: extractLane(OpcodeVecF64x2ExtractLane, 1)},
{name: OpcodeVecI8x16ReplaceLaneName, body: replaceLane(OpcodeVecI8x16ReplaceLane, 0)},
{name: OpcodeVecI8x16ReplaceLaneName + "/lane=15", body: replaceLane(OpcodeVecI8x16ReplaceLane, 15)},
{name: OpcodeVecI16x8ReplaceLaneName, body: replaceLane(OpcodeVecI16x8ReplaceLane, 0)},
{name: OpcodeVecI16x8ReplaceLaneName + "/lane=7", body: replaceLane(OpcodeVecI16x8ReplaceLane, 7)},
{name: OpcodeVecI32x4ReplaceLaneName, body: replaceLane(OpcodeVecI32x4ReplaceLane, 0)},
{name: OpcodeVecI32x4ReplaceLaneName + "/lane=3", body: replaceLane(OpcodeVecI32x4ReplaceLane, 3)},
{name: OpcodeVecI64x2ReplaceLaneName, body: replaceLane(OpcodeVecI64x2ReplaceLane, 0)},
{name: OpcodeVecI64x2ReplaceLaneName + "/lane=1", body: replaceLane(OpcodeVecI64x2ReplaceLane, 1)},
{name: OpcodeVecF32x4ReplaceLaneName, body: replaceLane(OpcodeVecF32x4ReplaceLane, 0)},
{name: OpcodeVecF32x4ReplaceLaneName + "/lane=3", body: replaceLane(OpcodeVecF32x4ReplaceLane, 3)},
{name: OpcodeVecF64x2ReplaceLaneName, body: replaceLane(OpcodeVecF64x2ReplaceLane, 0)},
{name: OpcodeVecF64x2ReplaceLaneName + "/lane=1", body: replaceLane(OpcodeVecF64x2ReplaceLane, 1)},
{name: OpcodeVecI8x16SplatName, body: splat(OpcodeVecI8x16Splat)},
{name: OpcodeVecI16x8SplatName, body: splat(OpcodeVecI16x8Splat)},
{name: OpcodeVecI32x4SplatName, body: splat(OpcodeVecI32x4Splat)},
{name: OpcodeVecI64x2SplatName, body: splat(OpcodeVecI64x2Splat)},
{name: OpcodeVecF32x4SplatName, body: splat(OpcodeVecF32x4Splat)},
{name: OpcodeVecF64x2SplatName, body: splat(OpcodeVecF64x2Splat)},
{name: OpcodeVecI8x16SwizzleName, body: vv2v(OpcodeVecI8x16Swizzle)},
{
name: OpcodeVecV128i8x16ShuffleName, body: []byte{
OpcodeVecPrefix,
OpcodeVecV128Const,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
OpcodeVecPrefix,
OpcodeVecV128Const,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
OpcodeVecPrefix,
OpcodeVecV128i8x16Shuffle,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
OpcodeDrop,
OpcodeEnd,
},
},
{name: OpcodeVecV128NotName, body: v2v(OpcodeVecV128Not)},
{name: OpcodeVecV128AndName, body: vv2v(OpcodeVecV128And)},
{name: OpcodeVecV128AndNotName, body: vv2v(OpcodeVecV128AndNot)},
{name: OpcodeVecV128OrName, body: vv2v(OpcodeVecV128Or)},
{name: OpcodeVecV128XorName, body: vv2v(OpcodeVecV128Xor)},
{name: OpcodeVecV128BitselectName, body: vvv2v(OpcodeVecV128Bitselect)},
{name: OpcodeVecI8x16ShlName, body: vi2v(OpcodeVecI8x16Shl)},
{name: OpcodeVecI8x16ShrSName, body: vi2v(OpcodeVecI8x16ShrS)},
{name: OpcodeVecI8x16ShrUName, body: vi2v(OpcodeVecI8x16ShrU)},
{name: OpcodeVecI16x8ShlName, body: vi2v(OpcodeVecI16x8Shl)},
{name: OpcodeVecI16x8ShrSName, body: vi2v(OpcodeVecI16x8ShrS)},
{name: OpcodeVecI16x8ShrUName, body: vi2v(OpcodeVecI16x8ShrU)},
{name: OpcodeVecI32x4ShlName, body: vi2v(OpcodeVecI32x4Shl)},
{name: OpcodeVecI32x4ShrSName, body: vi2v(OpcodeVecI32x4ShrS)},
{name: OpcodeVecI32x4ShrUName, body: vi2v(OpcodeVecI32x4ShrU)},
{name: OpcodeVecI64x2ShlName, body: vi2v(OpcodeVecI64x2Shl)},
{name: OpcodeVecI64x2ShrSName, body: vi2v(OpcodeVecI64x2ShrS)},
{name: OpcodeVecI64x2ShrUName, body: vi2v(OpcodeVecI64x2ShrU)},
{name: OpcodeVecI8x16EqName, body: vv2v(OpcodeVecI8x16Eq)},
{name: OpcodeVecI8x16NeName, body: vv2v(OpcodeVecI8x16Ne)},
{name: OpcodeVecI8x16LtSName, body: vv2v(OpcodeVecI8x16LtS)},
{name: OpcodeVecI8x16LtUName, body: vv2v(OpcodeVecI8x16LtU)},
{name: OpcodeVecI8x16GtSName, body: vv2v(OpcodeVecI8x16GtS)},
{name: OpcodeVecI8x16GtUName, body: vv2v(OpcodeVecI8x16GtU)},
{name: OpcodeVecI8x16LeSName, body: vv2v(OpcodeVecI8x16LeS)},
{name: OpcodeVecI8x16LeUName, body: vv2v(OpcodeVecI8x16LeU)},
{name: OpcodeVecI8x16GeSName, body: vv2v(OpcodeVecI8x16GeS)},
{name: OpcodeVecI8x16GeUName, body: vv2v(OpcodeVecI8x16GeU)},
{name: OpcodeVecI16x8EqName, body: vv2v(OpcodeVecI16x8Eq)},
{name: OpcodeVecI16x8NeName, body: vv2v(OpcodeVecI16x8Ne)},
{name: OpcodeVecI16x8LtSName, body: vv2v(OpcodeVecI16x8LtS)},
{name: OpcodeVecI16x8LtUName, body: vv2v(OpcodeVecI16x8LtU)},
{name: OpcodeVecI16x8GtSName, body: vv2v(OpcodeVecI16x8GtS)},
{name: OpcodeVecI16x8GtUName, body: vv2v(OpcodeVecI16x8GtU)},
{name: OpcodeVecI16x8LeSName, body: vv2v(OpcodeVecI16x8LeS)},
{name: OpcodeVecI16x8LeUName, body: vv2v(OpcodeVecI16x8LeU)},
{name: OpcodeVecI16x8GeSName, body: vv2v(OpcodeVecI16x8GeS)},
{name: OpcodeVecI16x8GeUName, body: vv2v(OpcodeVecI16x8GeU)},
{name: OpcodeVecI32x4EqName, body: vv2v(OpcodeVecI32x4Eq)},
{name: OpcodeVecI32x4NeName, body: vv2v(OpcodeVecI32x4Ne)},
{name: OpcodeVecI32x4LtSName, body: vv2v(OpcodeVecI32x4LtS)},
{name: OpcodeVecI32x4LtUName, body: vv2v(OpcodeVecI32x4LtU)},
{name: OpcodeVecI32x4GtSName, body: vv2v(OpcodeVecI32x4GtS)},
{name: OpcodeVecI32x4GtUName, body: vv2v(OpcodeVecI32x4GtU)},
{name: OpcodeVecI32x4LeSName, body: vv2v(OpcodeVecI32x4LeS)},
{name: OpcodeVecI32x4LeUName, body: vv2v(OpcodeVecI32x4LeU)},
{name: OpcodeVecI32x4GeSName, body: vv2v(OpcodeVecI32x4GeS)},
{name: OpcodeVecI32x4GeUName, body: vv2v(OpcodeVecI32x4GeU)},
{name: OpcodeVecI64x2EqName, body: vv2v(OpcodeVecI64x2Eq)},
{name: OpcodeVecI64x2NeName, body: vv2v(OpcodeVecI64x2Ne)},
{name: OpcodeVecI64x2LtSName, body: vv2v(OpcodeVecI64x2LtS)},
{name: OpcodeVecI64x2GtSName, body: vv2v(OpcodeVecI64x2GtS)},
{name: OpcodeVecI64x2LeSName, body: vv2v(OpcodeVecI64x2LeS)},
{name: OpcodeVecI64x2GeSName, body: vv2v(OpcodeVecI64x2GeS)},
{name: OpcodeVecF32x4EqName, body: vv2v(OpcodeVecF32x4Eq)},
{name: OpcodeVecF32x4NeName, body: vv2v(OpcodeVecF32x4Ne)},
{name: OpcodeVecF32x4LtName, body: vv2v(OpcodeVecF32x4Lt)},
{name: OpcodeVecF32x4GtName, body: vv2v(OpcodeVecF32x4Gt)},
{name: OpcodeVecF32x4LeName, body: vv2v(OpcodeVecF32x4Le)},
{name: OpcodeVecF32x4GeName, body: vv2v(OpcodeVecF32x4Ge)},
{name: OpcodeVecF64x2EqName, body: vv2v(OpcodeVecF64x2Eq)},
{name: OpcodeVecF64x2NeName, body: vv2v(OpcodeVecF64x2Ne)},
{name: OpcodeVecF64x2LtName, body: vv2v(OpcodeVecF64x2Lt)},
{name: OpcodeVecF64x2GtName, body: vv2v(OpcodeVecF64x2Gt)},
{name: OpcodeVecF64x2LeName, body: vv2v(OpcodeVecF64x2Le)},
{name: OpcodeVecF64x2GeName, body: vv2v(OpcodeVecF64x2Ge)},
{name: OpcodeVecI8x16AddName, body: vv2v(OpcodeVecI8x16Add)},
{name: OpcodeVecI8x16AddSatSName, body: vv2v(OpcodeVecI8x16AddSatS)},
{name: OpcodeVecI8x16AddSatUName, body: vv2v(OpcodeVecI8x16AddSatU)},
{name: OpcodeVecI8x16SubName, body: vv2v(OpcodeVecI8x16Sub)},
{name: OpcodeVecI8x16SubSatSName, body: vv2v(OpcodeVecI8x16SubSatS)},
{name: OpcodeVecI8x16SubSatUName, body: vv2v(OpcodeVecI8x16SubSatU)},
{name: OpcodeVecI16x8AddName, body: vv2v(OpcodeVecI16x8Add)},
{name: OpcodeVecI16x8AddSatSName, body: vv2v(OpcodeVecI16x8AddSatS)},
{name: OpcodeVecI16x8AddSatUName, body: vv2v(OpcodeVecI16x8AddSatU)},
{name: OpcodeVecI16x8SubName, body: vv2v(OpcodeVecI16x8Sub)},
{name: OpcodeVecI16x8SubSatSName, body: vv2v(OpcodeVecI16x8SubSatS)},
{name: OpcodeVecI16x8SubSatUName, body: vv2v(OpcodeVecI16x8SubSatU)},
{name: OpcodeVecI16x8MulName, body: vv2v(OpcodeVecI16x8Mul)},
{name: OpcodeVecI32x4AddName, body: vv2v(OpcodeVecI32x4Add)},
{name: OpcodeVecI32x4SubName, body: vv2v(OpcodeVecI32x4Sub)},
{name: OpcodeVecI32x4MulName, body: vv2v(OpcodeVecI32x4Mul)},
{name: OpcodeVecI64x2AddName, body: vv2v(OpcodeVecI64x2Add)},
{name: OpcodeVecI64x2SubName, body: vv2v(OpcodeVecI64x2Sub)},
{name: OpcodeVecI64x2MulName, body: vv2v(OpcodeVecI64x2Mul)},
{name: OpcodeVecF32x4AddName, body: vv2v(OpcodeVecF32x4Add)},
{name: OpcodeVecF32x4SubName, body: vv2v(OpcodeVecF32x4Sub)},
{name: OpcodeVecF32x4MulName, body: vv2v(OpcodeVecF32x4Mul)},
{name: OpcodeVecF32x4DivName, body: vv2v(OpcodeVecF32x4Div)},
{name: OpcodeVecF64x2AddName, body: vv2v(OpcodeVecF64x2Add)},
{name: OpcodeVecF64x2SubName, body: vv2v(OpcodeVecF64x2Sub)},
{name: OpcodeVecF64x2MulName, body: vv2v(OpcodeVecF64x2Mul)},
{name: OpcodeVecF64x2DivName, body: vv2v(OpcodeVecF64x2Div)},
{name: OpcodeVecI8x16NegName, body: v2v(OpcodeVecI8x16Neg)},
{name: OpcodeVecI16x8NegName, body: v2v(OpcodeVecI16x8Neg)},
{name: OpcodeVecI32x4NegName, body: v2v(OpcodeVecI32x4Neg)},
{name: OpcodeVecI64x2NegName, body: v2v(OpcodeVecI64x2Neg)},
{name: OpcodeVecF32x4NegName, body: v2v(OpcodeVecF32x4Neg)},
{name: OpcodeVecF64x2NegName, body: v2v(OpcodeVecF64x2Neg)},
{name: OpcodeVecF32x4SqrtName, body: v2v(OpcodeVecF32x4Sqrt)},
{name: OpcodeVecF64x2SqrtName, body: v2v(OpcodeVecF64x2Sqrt)},
{name: OpcodeVecI8x16MinSName, body: vv2v(OpcodeVecI8x16MinS)},
{name: OpcodeVecI8x16MinUName, body: vv2v(OpcodeVecI8x16MinU)},
{name: OpcodeVecI8x16MaxSName, body: vv2v(OpcodeVecI8x16MaxS)},
{name: OpcodeVecI8x16MaxUName, body: vv2v(OpcodeVecI8x16MaxU)},
{name: OpcodeVecI8x16AvgrUName, body: vv2v(OpcodeVecI8x16AvgrU)},
{name: OpcodeVecI8x16AbsName, body: v2v(OpcodeVecI8x16Abs)},
{name: OpcodeVecI8x16PopcntName, body: v2v(OpcodeVecI8x16Popcnt)},
{name: OpcodeVecI16x8MinSName, body: vv2v(OpcodeVecI16x8MinS)},
{name: OpcodeVecI16x8MinUName, body: vv2v(OpcodeVecI16x8MinU)},
{name: OpcodeVecI16x8MaxSName, body: vv2v(OpcodeVecI16x8MaxS)},
{name: OpcodeVecI16x8MaxUName, body: vv2v(OpcodeVecI16x8MaxU)},
{name: OpcodeVecI16x8AvgrUName, body: vv2v(OpcodeVecI16x8AvgrU)},
{name: OpcodeVecI16x8AbsName, body: v2v(OpcodeVecI16x8Abs)},
{name: OpcodeVecI32x4MinSName, body: vv2v(OpcodeVecI32x4MinS)},
{name: OpcodeVecI32x4MinUName, body: vv2v(OpcodeVecI32x4MinU)},
{name: OpcodeVecI32x4MaxSName, body: vv2v(OpcodeVecI32x4MaxS)},
{name: OpcodeVecI32x4MaxUName, body: vv2v(OpcodeVecI32x4MaxU)},
{name: OpcodeVecI32x4AbsName, body: v2v(OpcodeVecI32x4Abs)},
{name: OpcodeVecI64x2AbsName, body: v2v(OpcodeVecI64x2Abs)},
{name: OpcodeVecF32x4AbsName, body: v2v(OpcodeVecF32x4Abs)},
{name: OpcodeVecF64x2AbsName, body: v2v(OpcodeVecF64x2Abs)},
{name: OpcodeVecF32x4MinName, body: vv2v(OpcodeVecF32x4Min)},
{name: OpcodeVecF32x4MaxName, body: vv2v(OpcodeVecF32x4Max)},
{name: OpcodeVecF64x2MinName, body: vv2v(OpcodeVecF64x2Min)},
{name: OpcodeVecF64x2MaxName, body: vv2v(OpcodeVecF64x2Max)},
{name: OpcodeVecF32x4CeilName, body: v2v(OpcodeVecF32x4Ceil)},
{name: OpcodeVecF32x4FloorName, body: v2v(OpcodeVecF32x4Floor)},
{name: OpcodeVecF32x4TruncName, body: v2v(OpcodeVecF32x4Trunc)},
{name: OpcodeVecF32x4NearestName, body: v2v(OpcodeVecF32x4Nearest)},
{name: OpcodeVecF64x2CeilName, body: v2v(OpcodeVecF64x2Ceil)},
{name: OpcodeVecF64x2FloorName, body: v2v(OpcodeVecF64x2Floor)},
{name: OpcodeVecF64x2TruncName, body: v2v(OpcodeVecF64x2Trunc)},
{name: OpcodeVecF64x2NearestName, body: v2v(OpcodeVecF64x2Nearest)},
{name: OpcodeVecF32x4MinName, body: vv2v(OpcodeVecF32x4Pmin)},
{name: OpcodeVecF32x4MaxName, body: vv2v(OpcodeVecF32x4Pmax)},
{name: OpcodeVecF64x2MinName, body: vv2v(OpcodeVecF64x2Pmin)},
{name: OpcodeVecF64x2MaxName, body: vv2v(OpcodeVecF64x2Pmax)},
{name: OpcodeVecI16x8ExtendLowI8x16SName, body: v2v(OpcodeVecI16x8ExtendLowI8x16S)},
{name: OpcodeVecI16x8ExtendHighI8x16SName, body: v2v(OpcodeVecI16x8ExtendHighI8x16S)},
{name: OpcodeVecI16x8ExtendLowI8x16UName, body: v2v(OpcodeVecI16x8ExtendLowI8x16U)},
{name: OpcodeVecI16x8ExtendHighI8x16UName, body: v2v(OpcodeVecI16x8ExtendHighI8x16U)},
{name: OpcodeVecI32x4ExtendLowI16x8SName, body: v2v(OpcodeVecI32x4ExtendLowI16x8S)},
{name: OpcodeVecI32x4ExtendHighI16x8SName, body: v2v(OpcodeVecI32x4ExtendHighI16x8S)},
{name: OpcodeVecI32x4ExtendLowI16x8UName, body: v2v(OpcodeVecI32x4ExtendLowI16x8U)},
{name: OpcodeVecI32x4ExtendHighI16x8UName, body: v2v(OpcodeVecI32x4ExtendHighI16x8U)},
{name: OpcodeVecI64x2ExtendLowI32x4SName, body: v2v(OpcodeVecI64x2ExtendLowI32x4S)},
{name: OpcodeVecI64x2ExtendHighI32x4SName, body: v2v(OpcodeVecI64x2ExtendHighI32x4S)},
{name: OpcodeVecI64x2ExtendLowI32x4UName, body: v2v(OpcodeVecI64x2ExtendLowI32x4U)},
{name: OpcodeVecI64x2ExtendHighI32x4UName, body: v2v(OpcodeVecI64x2ExtendHighI32x4U)},
{name: OpcodeVecI16x8Q15mulrSatSName, body: vv2v(OpcodeVecI16x8Q15mulrSatS)},
{name: OpcodeVecI16x8ExtMulLowI8x16SName, body: vv2v(OpcodeVecI16x8ExtMulLowI8x16S)},
{name: OpcodeVecI16x8ExtMulHighI8x16SName, body: vv2v(OpcodeVecI16x8ExtMulHighI8x16S)},
{name: OpcodeVecI16x8ExtMulLowI8x16UName, body: vv2v(OpcodeVecI16x8ExtMulLowI8x16U)},
{name: OpcodeVecI16x8ExtMulHighI8x16UName, body: vv2v(OpcodeVecI16x8ExtMulHighI8x16U)},
{name: OpcodeVecI32x4ExtMulLowI16x8SName, body: vv2v(OpcodeVecI32x4ExtMulLowI16x8S)},
{name: OpcodeVecI32x4ExtMulHighI16x8SName, body: vv2v(OpcodeVecI32x4ExtMulHighI16x8S)},
{name: OpcodeVecI32x4ExtMulLowI16x8UName, body: vv2v(OpcodeVecI32x4ExtMulLowI16x8U)},
{name: OpcodeVecI32x4ExtMulHighI16x8UName, body: vv2v(OpcodeVecI32x4ExtMulHighI16x8U)},
{name: OpcodeVecI64x2ExtMulLowI32x4SName, body: vv2v(OpcodeVecI64x2ExtMulLowI32x4S)},
{name: OpcodeVecI64x2ExtMulHighI32x4SName, body: vv2v(OpcodeVecI64x2ExtMulHighI32x4S)},
{name: OpcodeVecI64x2ExtMulLowI32x4UName, body: vv2v(OpcodeVecI64x2ExtMulLowI32x4U)},
{name: OpcodeVecI64x2ExtMulHighI32x4UName, body: vv2v(OpcodeVecI64x2ExtMulHighI32x4U)},
{name: OpcodeVecI16x8ExtaddPairwiseI8x16SName, body: v2v(OpcodeVecI16x8ExtaddPairwiseI8x16S)},
{name: OpcodeVecI16x8ExtaddPairwiseI8x16UName, body: v2v(OpcodeVecI16x8ExtaddPairwiseI8x16U)},
{name: OpcodeVecI32x4ExtaddPairwiseI16x8SName, body: v2v(OpcodeVecI32x4ExtaddPairwiseI16x8S)},
{name: OpcodeVecI32x4ExtaddPairwiseI16x8UName, body: v2v(OpcodeVecI32x4ExtaddPairwiseI16x8U)},
{name: OpcodeVecF64x2PromoteLowF32x4ZeroName, body: v2v(OpcodeVecF64x2PromoteLowF32x4Zero)},
{name: OpcodeVecF32x4DemoteF64x2ZeroName, body: v2v(OpcodeVecF32x4DemoteF64x2Zero)},
{name: OpcodeVecF32x4ConvertI32x4SName, body: v2v(OpcodeVecF32x4ConvertI32x4S)},
{name: OpcodeVecF32x4ConvertI32x4UName, body: v2v(OpcodeVecF32x4ConvertI32x4U)},
{name: OpcodeVecF64x2ConvertLowI32x4SName, body: v2v(OpcodeVecF64x2ConvertLowI32x4S)},
{name: OpcodeVecF64x2ConvertLowI32x4UName, body: v2v(OpcodeVecF64x2ConvertLowI32x4U)},
{name: OpcodeVecI32x4DotI16x8SName, body: vv2v(OpcodeVecI32x4DotI16x8S)},
{name: OpcodeVecI8x16NarrowI16x8SName, body: vv2v(OpcodeVecI8x16NarrowI16x8S)},
{name: OpcodeVecI8x16NarrowI16x8UName, body: vv2v(OpcodeVecI8x16NarrowI16x8U)},
{name: OpcodeVecI16x8NarrowI32x4SName, body: vv2v(OpcodeVecI16x8NarrowI32x4S)},
{name: OpcodeVecI16x8NarrowI32x4UName, body: vv2v(OpcodeVecI16x8NarrowI32x4U)},
{name: OpcodeVecI32x4TruncSatF32x4SName, body: v2v(OpcodeVecI32x4TruncSatF32x4S)},
{name: OpcodeVecI32x4TruncSatF32x4UName, body: v2v(OpcodeVecI32x4TruncSatF32x4U)},
{name: OpcodeVecI32x4TruncSatF64x2SZeroName, body: v2v(OpcodeVecI32x4TruncSatF64x2SZero)},
{name: OpcodeVecI32x4TruncSatF64x2UZeroName, body: v2v(OpcodeVecI32x4TruncSatF64x2UZero)},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
m := &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: tc.body}},
}
err := m.validateFunction(&stacks{}, api.CoreFeatureSIMD,
0, []Index{0}, nil, &Memory{}, nil, nil, nil, bytes.NewReader(nil))
require.NoError(t, err)
})
}
}
func TestModule_funcValidation_SIMD_error(t *testing.T) {
type testCase struct {
name string
body []byte
flag api.CoreFeatures
expectedErr string
}
tests := []testCase{
{
name: "simd disabled",
body: []byte{
OpcodeVecPrefix,
OpcodeVecF32x4Abs,
},
flag: api.CoreFeaturesV1,
expectedErr: "f32x4.abs invalid as feature \"simd\" is disabled",
},
{
name: "v128.const immediate",
body: []byte{
OpcodeVecPrefix,
OpcodeVecV128Const,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1,
},
flag: api.CoreFeatureSIMD,
expectedErr: "cannot read constant vector value for v128.const",
},
{
name: "i32x4.add operand",
body: []byte{
OpcodeVecPrefix,
OpcodeVecV128Const,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
OpcodeVecPrefix,
OpcodeVecI32x4Add,
OpcodeDrop,
OpcodeEnd,
},
flag: api.CoreFeatureSIMD,
expectedErr: "cannot pop the operand for i32x4.add: v128 missing",
},
{
name: "i64x2.add operand",
body: []byte{
OpcodeVecPrefix,
OpcodeVecV128Const,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
OpcodeVecPrefix,
OpcodeVecI64x2Add,
OpcodeDrop,
OpcodeEnd,
},
flag: api.CoreFeatureSIMD,
expectedErr: "cannot pop the operand for i64x2.add: v128 missing",
},
{
name: "shuffle lane index not found",
flag: api.CoreFeatureSIMD,
body: []byte{
OpcodeVecPrefix,
OpcodeVecV128i8x16Shuffle,
},
expectedErr: "16 lane indexes for v128.shuffle not found",
},
{
name: "shuffle lane index not found",
flag: api.CoreFeatureSIMD,
body: []byte{
OpcodeVecPrefix,
OpcodeVecV128i8x16Shuffle,
0xff, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
},
expectedErr: "invalid lane index[0] 255 >= 32 for v128.shuffle",
},
}
addExtractOrReplaceLaneOutOfIndexCase := func(op OpcodeVec, lane, laneCeil byte) {
n := VectorInstructionName(op)
tests = append(tests, testCase{
name: n + "/lane index out of range",
flag: api.CoreFeatureSIMD,
body: []byte{
OpcodeVecPrefix, op, lane,
},
expectedErr: fmt.Sprintf("invalid lane index %d >= %d for %s", lane, laneCeil, n),
})
}
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI8x16ExtractLaneS, 16, 16)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI8x16ExtractLaneU, 20, 16)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI16x8ExtractLaneS, 8, 8)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI16x8ExtractLaneU, 8, 8)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI32x4ExtractLane, 4, 4)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecF32x4ExtractLane, 4, 4)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI64x2ExtractLane, 2, 2)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecF64x2ExtractLane, 2, 2)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI8x16ReplaceLane, 16, 16)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI16x8ReplaceLane, 8, 8)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI32x4ReplaceLane, 4, 4)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI64x2ReplaceLane, 2, 2)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecF32x4ReplaceLane, 10, 4)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecF64x2ReplaceLane, 3, 2)
addStoreOrLoadLaneOutOfIndexCase := func(op OpcodeVec, lane, laneCeil byte) {
n := VectorInstructionName(op)
tests = append(tests, testCase{
name: n + "/lane index out of range",
flag: api.CoreFeatureSIMD,
body: []byte{
OpcodeVecPrefix, op,
0, 0, // align and offset.
lane,
},
expectedErr: fmt.Sprintf("invalid lane index %d >= %d for %s", lane, laneCeil, n),
})
}
addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Load8Lane, 16, 16)
addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Load16Lane, 8, 8)
addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Load32Lane, 4, 4)
addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Load64Lane, 2, 2)
addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Store8Lane, 16, 16)
addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Store16Lane, 8, 8)
addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Store32Lane, 4, 4)
addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Store64Lane, 2, 2)
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
m := &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: tc.body}},
}
err := m.validateFunction(&stacks{}, tc.flag,
0, []Index{0}, nil, &Memory{}, nil, nil, nil, bytes.NewReader(nil))
require.EqualError(t, err, tc.expectedErr)
})
}
}
func TestDecodeBlockType(t *testing.T) {
t.Run("primitive", func(t *testing.T) {
for _, tc := range []struct {
name string
in byte
exp ValueType
expResultNumInUint64 int
}{
{name: "nil", in: 0x40},
{name: "i32", in: 0x7f, exp: ValueTypeI32, expResultNumInUint64: 1},
{name: "i64", in: 0x7e, exp: ValueTypeI64, expResultNumInUint64: 1},
{name: "f32", in: 0x7d, exp: ValueTypeF32, expResultNumInUint64: 1},
{name: "f64", in: 0x7c, exp: ValueTypeF64, expResultNumInUint64: 1},
{name: "v128", in: 0x7b, exp: ValueTypeV128, expResultNumInUint64: 2},
{name: "funcref", in: 0x70, exp: ValueTypeFuncref, expResultNumInUint64: 1},
{name: "externref", in: 0x6f, exp: ValueTypeExternref, expResultNumInUint64: 1},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
actual, read, err := DecodeBlockType(nil, bytes.NewReader([]byte{tc.in}), api.CoreFeaturesV2)
require.NoError(t, err)
require.Equal(t, uint64(1), read)
require.Equal(t, 0, len(actual.Params))
require.Equal(t, tc.expResultNumInUint64, actual.ResultNumInUint64)
require.Equal(t, 0, actual.ParamNumInUint64)
if tc.exp == 0 {
require.Equal(t, 0, len(actual.Results))
} else {
require.Equal(t, 1, len(actual.Results))
require.Equal(t, tc.exp, actual.Results[0])
}
})
}
})
t.Run("function type", func(t *testing.T) {
types := []FunctionType{
{},
{Params: []ValueType{ValueTypeI32}},
{Results: []ValueType{ValueTypeI32}},
{Params: []ValueType{ValueTypeF32, ValueTypeV128}, Results: []ValueType{ValueTypeI32}},
{Params: []ValueType{ValueTypeF32, ValueTypeV128}, Results: []ValueType{ValueTypeI32, ValueTypeF32, ValueTypeV128}},
}
for index := range types {
expected := &types[index]
actual, read, err := DecodeBlockType(types, bytes.NewReader([]byte{byte(index)}), api.CoreFeatureMultiValue)
require.NoError(t, err)
require.Equal(t, uint64(1), read)
require.Equal(t, expected, actual)
}
})
}
// TestFuncValidation_UnreachableBrTable_NotModifyTypes ensures that we do not modify the
// original function type during the function validation with the presence of unreachable br_table
// targeting the function return label.
func TestFuncValidation_UnreachableBrTable_NotModifyTypes(t *testing.T) {
funcType := FunctionType{Results: []ValueType{i32, i64}, Params: []ValueType{i32}}
copiedFuncType := FunctionType{
Params: make([]ValueType, len(funcType.Params)),
Results: make([]ValueType, len(funcType.Results)),
}
copy(copiedFuncType.Results, funcType.Results)
copy(copiedFuncType.Params, funcType.Params)
for _, tc := range []struct {
name string
m *Module
}{
{
name: "on function return",
m: &Module{
TypeSection: []FunctionType{funcType},
FunctionSection: []Index{0},
CodeSection: []Code{
{Body: []byte{
OpcodeUnreachable,
// Having br_table in unreachable state.
OpcodeI32Const, 1,
// Setting the destination as labels of index 0 which
// is the function return.
OpcodeBrTable, 2, 0, 0, 0,
OpcodeEnd,
}},
},
},
},
{
name: "on loop return",
m: &Module{
TypeSection: []FunctionType{funcType},
FunctionSection: []Index{0},
CodeSection: []Code{
{Body: []byte{
OpcodeUnreachable,
OpcodeLoop, 0, // indicates that loop has funcType as its block type
OpcodeUnreachable,
// Having br_table in unreachable state.
OpcodeI32Const, 1,
// Setting the destination as labels of index 0 which
// is the loop return.
OpcodeBrTable, 2, 0, 0, 0,
OpcodeEnd, // End of loop
OpcodeEnd,
}},
},
},
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
err := tc.m.validateFunction(&stacks{}, api.CoreFeaturesV2,
0, nil, nil, nil, nil, nil, nil, bytes.NewReader(nil))
require.NoError(t, err)
// Ensures that funcType has remained intact.
require.Equal(t, copiedFuncType, funcType)
})
}
}
func TestModule_funcValidation_loopWithParams(t *testing.T) {
tests := []struct {
name string
body []byte
expErr string
}{
{
name: "br",
body: []byte{
OpcodeI32Const, 1,
OpcodeLoop, 1, // loop (param i32)
OpcodeBr, 0,
OpcodeEnd,
OpcodeUnreachable,
OpcodeEnd,
},
},
{
name: "br_if",
body: []byte{
OpcodeI32Const, 1,
OpcodeLoop, 1, // loop (param i32)
OpcodeI32Const, 1, // operand for br_if
OpcodeBrIf, 0,
OpcodeUnreachable,
OpcodeEnd,
OpcodeUnreachable,
OpcodeEnd,
},
},
{
name: "br_table",
body: []byte{
OpcodeI32Const, 1,
OpcodeLoop, 1, // loop (param i32)
OpcodeI32Const, 4, // Operand for br_table.
OpcodeBrTable, 2, 0, 0, 0, 0, // Jump into the loop header anyway.
OpcodeEnd,
OpcodeUnreachable,
OpcodeEnd,
},
},
{
name: "br_table - nested",
body: []byte{
OpcodeI32Const, 1,
OpcodeLoop, 1, // loop (param i32)
OpcodeLoop, 1, // loop (param i32)
OpcodeI32Const, 4, // Operand for br_table.
OpcodeBrTable, 2, 0, 1, 0, // Jump into the loop header anyway.
OpcodeEnd,
OpcodeEnd,
OpcodeUnreachable,
OpcodeEnd,
},
},
{
name: "br / mismatch",
body: []byte{
OpcodeI32Const, 1,
OpcodeLoop, 1, // loop (param i32)
OpcodeDrop,
OpcodeBr, 0, // trying to jump the loop head after dropping the value which causes the type mismatch.
OpcodeEnd,
OpcodeUnreachable,
OpcodeEnd,
},
expErr: `not enough results in br block
have ()
want (i32)`,
},
{
name: "br_if / mismatch",
body: []byte{
OpcodeI32Const, 1,
OpcodeLoop, 1, // loop (param i32)
// Use up the param for the br_if, therefore at the time of jumping, we don't have any param to the header.
OpcodeBrIf, 0, // trying to jump the loop head after dropping the value which causes the type mismatch.
OpcodeUnreachable,
OpcodeEnd,
OpcodeUnreachable,
OpcodeEnd,
},
expErr: `not enough results in br_if block
have ()
want (i32)`,
},
{
name: "br_table",
body: []byte{
OpcodeI32Const, 1,
OpcodeLoop, 1, // loop (param i32)
// Use up the param for the br_table, therefore at the time of jumping, we don't have any param to the header.
OpcodeBrTable, 2, 0, 0, 0, // Jump into the loop header anyway.
OpcodeEnd,
OpcodeUnreachable,
OpcodeEnd,
},
expErr: `not enough results in br_table block
have ()
want (i32)`,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
m := &Module{
TypeSection: []FunctionType{
v_i32,
i32_v,
},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: tc.body}},
}
err := m.validateFunction(&stacks{}, api.CoreFeatureMultiValue,
0, []Index{0}, nil, nil, nil, nil, nil, bytes.NewReader(nil))
if tc.expErr != "" {
require.EqualError(t, err, tc.expErr)
} else {
require.NoError(t, err)
}
})
}
}
// TestFunctionValidation_redundantEnd is found in th validation fuzzing #879.
func TestFunctionValidation_redundantEnd(t *testing.T) {
m := &Module{
TypeSection: []FunctionType{{}},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{OpcodeEnd, OpcodeEnd}}},
}
err := m.validateFunction(&stacks{}, api.CoreFeaturesV2,
0, nil, nil, nil, nil, nil, nil, bytes.NewReader(nil))
require.EqualError(t, err, "unexpected end of function at pc=0x1")
}
// TestFunctionValidation_redundantEnd is found in th validation fuzzing.
func TestFunctionValidation_redundantElse(t *testing.T) {
for _, tc := range []struct {
body []byte
expErr string
}{
{
body: []byte{OpcodeEnd, OpcodeElse},
expErr: "unexpected end of function at pc=0x1",
},
{
body: []byte{OpcodeElse, OpcodeEnd},
expErr: "else instruction must be used in if block: 0x0",
},
{
body: []byte{OpcodeBlock, 0, OpcodeElse, OpcodeEnd},
expErr: "else instruction must be used in if block: 0x2",
},
{
body: []byte{
OpcodeI32Const, 0,
OpcodeIf, 0, OpcodeElse, OpcodeElse, OpcodeEnd, OpcodeEnd,
},
expErr: "else instruction must be used in if block: 0x5",
},
} {
t.Run(tc.expErr, func(t *testing.T) {
m := &Module{TypeSection: []FunctionType{{}}, FunctionSection: []Index{0}, CodeSection: []Code{{Body: tc.body}}}
err := m.validateFunction(&stacks{}, api.CoreFeaturesV2,
0, nil, nil, nil, nil, nil, nil, bytes.NewReader(nil))
require.EqualError(t, err, tc.expErr)
})
}
}
func Test_SplitCallStack(t *testing.T) {
oneToEight := []uint64{1, 2, 3, 4, 5, 6, 7, 8}
tests := []struct {
name string
ft *FunctionType
stack, expectedParams, expectedResults []uint64
expectedErr string
}{
{
name: "v_v",
ft: &v_v,
stack: oneToEight,
expectedParams: nil,
expectedResults: nil,
},
{
name: "v_v - stack nil",
ft: &v_v,
expectedParams: nil,
expectedResults: nil,
},
{
name: "v_i32",
ft: &v_i32,
stack: oneToEight,
expectedParams: nil,
expectedResults: []uint64{1},
},
{
name: "f32i32_v",
ft: &f32i32_v,
stack: oneToEight,
expectedParams: []uint64{1, 2},
expectedResults: nil,
},
{
name: "f64f32_i64",
ft: &f64f32_i64,
stack: oneToEight,
expectedParams: []uint64{1, 2},
expectedResults: []uint64{1},
},
{
name: "f64i32_v128i64",
ft: &f64i32_v128i64,
stack: oneToEight,
expectedParams: []uint64{1, 2},
expectedResults: []uint64{1, 2, 3},
},
{
name: "not enough room for params",
ft: &f64i32_v128i64,
stack: oneToEight[0:1],
expectedErr: "need 2 params, but stack size is 1",
},
{
name: "not enough room for results",
ft: &f64i32_v128i64,
stack: oneToEight[0:2],
expectedErr: "need 3 results, but stack size is 2",
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
params, results, err := SplitCallStack(tc.ft, tc.stack)
if tc.expectedErr != "" {
require.EqualError(t, err, tc.expectedErr)
} else {
require.Equal(t, tc.expectedParams, params)
require.Equal(t, tc.expectedResults, results)
}
})
}
}
func TestModule_funcValidation_Atomic(t *testing.T) {
t.Run("valid bytecode", func(t *testing.T) {
tests := []struct {
name string
body []byte
noDropBeforeReturn bool
}{
{
name: "i32.atomic.load8_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI32Load8U, 0x0, 0x8, // alignment=2^0, offset=8
},
},
{
name: "i32.atomic.load16_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI32Load16U, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i32.atomic.load",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI32Load, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.load8_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI64Load8U, 0x0, 0x8, // alignment=2^0, offset=8
},
},
{
name: "i64.atomic.load16_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI64Load16U, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i64.atomic.load32_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI64Load32U, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.load",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI64Load, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i32.atomic.store8",
body: []byte{
OpcodeI32Const, 0x1,
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI32Store8, 0x0, 0x8, // alignment=2^0, offset=8
},
noDropBeforeReturn: true,
},
{
name: "i32.atomic.store16",
body: []byte{
OpcodeI32Const, 0x1,
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI32Store16, 0x1, 0x8, // alignment=2^1, offset=8
},
noDropBeforeReturn: true,
},
{
name: "i32.atomic.store",
body: []byte{
OpcodeI32Const, 0x1,
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI32Store, 0x2, 0x8, // alignment=2^2, offset=8
},
noDropBeforeReturn: true,
},
{
name: "i64.atomic.store8",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Store8, 0x0, 0x8, // alignment=2^0, offset=8
},
noDropBeforeReturn: true,
},
{
name: "i64.atomic.store16",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Store16, 0x1, 0x8, // alignment=2^1, offset=8
},
noDropBeforeReturn: true,
},
{
name: "i64.atomic.store32",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Store32, 0x2, 0x8, // alignment=2^2, offset=8
},
noDropBeforeReturn: true,
},
{
name: "i64.atomic.store",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Store, 0x3, 0x8, // alignment=2^3, offset=8
},
noDropBeforeReturn: true,
},
{
name: "i32.atomic.rmw8.add_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8AddU, 0x0, 0x8, // alignment=2^0, offset=8
},
},
{
name: "i32.atomic.rmw16.add_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16AddU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i32.atomic.rmw.add",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32RmwAdd, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw8.add_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8AddU, 0x0, 0x8, // alignment=2^0, offset=8
},
},
{
name: "i64.atomic.rmw16.add_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16AddU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i64.atomic.rmw32.add_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32AddU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw.add",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64RmwAdd, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i32.atomic.rmw8.sub_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8SubU, 0x0, 0x8, // alignment=2^0, offset=8
},
},
{
name: "i32.atomic.rmw16.sub_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16SubU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i32.atomic.rmw.sub",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32RmwSub, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw8.sub_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8SubU, 0x0, 0x8, // alignment=2^0, offset=8
},
},
{
name: "i64.atomic.rmw16.sub_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16SubU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i64.atomic.rmw32.sub_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32SubU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw.sub",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64RmwSub, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i32.atomic.rmw8.and_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8AndU, 0x0, 0x8, // alignment=2^0, offset=8
},
},
{
name: "i32.atomic.rmw16.and_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16AndU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i32.atomic.rmw.and",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32RmwAnd, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw8.and_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8AndU, 0x0, 0x8, // alignment=2^0, offset=8
},
},
{
name: "i64.atomic.rmw16.and_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16AndU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i64.atomic.rmw32.and_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32AndU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw.and",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64RmwAnd, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i32.atomic.rmw8.or",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8OrU, 0x0, 0x8, // alignment=2^0, offset=8
},
},
{
name: "i32.atomic.rmw16.or_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16OrU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i32.atomic.rmw.or",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32RmwOr, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw8.or_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8OrU, 0x0, 0x8, // alignment=2^0, offset=8
},
},
{
name: "i64.atomic.rmw16.or_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16OrU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i64.atomic.rmw32.or_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32OrU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw.or",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64RmwOr, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i32.atomic.rmw8.xor_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8XorU, 0x0, 0x8, // alignment=2^0, offset=8
},
},
{
name: "i32.atomic.rmw16.xor_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16XorU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i32.atomic.rmw.xor",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32RmwXor, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw8.xor_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8XorU, 0x0, 0x8, // alignment=2^0, offset=8
},
},
{
name: "i64.atomic.rmw16.xor_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16XorU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i64.atomic.rmw32.xor_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32XorU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw.xor",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64RmwXor, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i32.atomic.rmw8.xchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8XchgU, 0x0, 0x8, // alignment=2^0, offset=8
},
},
{
name: "i32.atomic.rmw16.xchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16XchgU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i32.atomic.rmw.xchg",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32RmwXchg, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw8.xchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8XchgU, 0x0, 0x8, // alignment=2^0, offset=8
},
},
{
name: "i64.atomic.rmw16.xchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16XchgU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i64.atomic.rmw32.xchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32XchgU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw.xchg",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64RmwXchg, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i32.atomic.rmw8.cmpxchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeI32Const, 0x2,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8CmpxchgU, 0x0, 0x8, // alignment=2^0, offset=8
},
},
{
name: "i32.atomic.rmw16.cmpxchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeI32Const, 0x2,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16CmpxchgU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i32.atomic.rmw.cmpxchg",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeI32Const, 0x2,
OpcodeAtomicPrefix, OpcodeAtomicI32RmwCmpxchg, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw8.xchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeI64Const, 0x2,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8CmpxchgU, 0x0, 0x8, // alignment=2^0, offset=8
},
},
{
name: "i64.atomic.rmw16.cmpxchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeI64Const, 0x2,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16CmpxchgU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i64.atomic.rmw32.cmpxchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32CmpxchgU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw.cmpxchg",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeI64Const, 0x2,
OpcodeAtomicPrefix, OpcodeAtomicI64RmwCmpxchg, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "memory.atomic.wait32",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeI64Const, 0x2,
OpcodeAtomicPrefix, OpcodeAtomicMemoryWait32, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "memory.atomic.wait64",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeI64Const, 0x2,
OpcodeAtomicPrefix, OpcodeAtomicMemoryWait64, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "memory.atomic.notify",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicMemoryNotify, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "memory.atomic.fence",
body: []byte{
OpcodeAtomicPrefix, OpcodeAtomicFence, 0x0,
},
noDropBeforeReturn: true,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
body := append([]byte{}, tc.body...)
if !tt.noDropBeforeReturn {
body = append(body, OpcodeDrop)
}
body = append(body, OpcodeEnd)
m := &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: body}},
}
t.Run("with memory", func(t *testing.T) {
err := m.validateFunction(&stacks{}, experimental.CoreFeaturesThreads,
0, []Index{0}, nil, &Memory{}, []Table{}, nil, nil, bytes.NewReader(nil))
require.NoError(t, err)
})
t.Run("without memory", func(t *testing.T) {
err := m.validateFunction(&stacks{}, experimental.CoreFeaturesThreads,
0, []Index{0}, nil, nil, []Table{}, nil, nil, bytes.NewReader(nil))
// Only fence doesn't require memory
if tc.name == "memory.atomic.fence" {
require.NoError(t, err)
} else {
require.Error(t, err, fmt.Sprintf("memory must exist for %s", tc.name))
}
})
})
}
})
t.Run("atomic.fence bad immediate", func(t *testing.T) {
body := []byte{
OpcodeAtomicPrefix, OpcodeAtomicFence, 0x1,
OpcodeEnd,
}
m := &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: body}},
}
err := m.validateFunction(&stacks{}, experimental.CoreFeaturesThreads,
0, []Index{0}, nil, &Memory{}, []Table{}, nil, nil, bytes.NewReader(nil))
require.Error(t, err, "invalid immediate value for atomic.fence")
})
t.Run("bad alignment", func(t *testing.T) {
tests := []struct {
name string
body []byte
noDropBeforeReturn bool
}{
{
name: "i32.atomic.load8_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI32Load8U, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i32.atomic.load16_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI32Load16U, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i32.atomic.load",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI32Load, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i64.atomic.load8_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI64Load8U, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i64.atomic.load16_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI64Load16U, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.load32_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI64Load32U, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i64.atomic.load",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI64Load, 0x4, 0x8, // alignment=2^4, offset=8
},
},
{
name: "i32.atomic.store8",
body: []byte{
OpcodeI32Const, 0x1,
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI32Store8, 0x1, 0x8, // alignment=2^1, offset=8
},
noDropBeforeReturn: true,
},
{
name: "i32.atomic.store16",
body: []byte{
OpcodeI32Const, 0x1,
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI32Store16, 0x2, 0x8, // alignment=2^2, offset=8
},
noDropBeforeReturn: true,
},
{
name: "i32.atomic.store",
body: []byte{
OpcodeI32Const, 0x1,
OpcodeI32Const, 0x0,
OpcodeAtomicPrefix, OpcodeAtomicI32Store, 0x3, 0x8, // alignment=2^3, offset=8
},
noDropBeforeReturn: true,
},
{
name: "i64.atomic.store8",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Store8, 0x1, 0x8, // alignment=2^1, offset=8
},
noDropBeforeReturn: true,
},
{
name: "i64.atomic.store16",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Store16, 0x2, 0x8, // alignment=2^2, offset=8
},
noDropBeforeReturn: true,
},
{
name: "i64.atomic.store32",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Store32, 0x3, 0x8, // alignment=2^3, offset=8
},
noDropBeforeReturn: true,
},
{
name: "i64.atomic.store",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Store, 0x4, 0x8, // alignment=2^4, offset=8
},
noDropBeforeReturn: true,
},
{
name: "i32.atomic.rmw8.add_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8AddU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i32.atomic.rmw16.add_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16AddU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i32.atomic.rmw.add",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32RmwAdd, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i64.atomic.rmw8.add_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8AddU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i64.atomic.rmw16.add_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16AddU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw32.add_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32AddU, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i64.atomic.rmw.add",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64RmwAdd, 0x4, 0x8, // alignment=2^4, offset=8
},
},
{
name: "i32.atomic.rmw8.sub_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8SubU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i32.atomic.rmw16.sub_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16SubU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i32.atomic.rmw.sub",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32RmwSub, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i64.atomic.rmw8.sub_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8SubU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i64.atomic.rmw16.sub_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16SubU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw32.sub_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32SubU, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i64.atomic.rmw.sub",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64RmwSub, 0x4, 0x8, // alignment=2^4, offset=8
},
},
{
name: "i32.atomic.rmw8.and_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8AndU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i32.atomic.rmw16.and_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16AndU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i32.atomic.rmw.and",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32RmwAnd, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i64.atomic.rmw8.and_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8AndU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i64.atomic.rmw16.and_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16AndU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw32.and_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32AndU, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i64.atomic.rmw.and",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64RmwAnd, 0x4, 0x8, // alignment=2^4, offset=8
},
},
{
name: "i32.atomic.rmw8.or",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8OrU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i32.atomic.rmw16.or_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16OrU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i32.atomic.rmw.or",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32RmwOr, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i64.atomic.rmw8.or_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8OrU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i64.atomic.rmw16.or_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16OrU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw32.or_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32OrU, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i64.atomic.rmw.or",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64RmwOr, 0x4, 0x8, // alignment=2^4, offset=8
},
},
{
name: "i32.atomic.rmw8.xor_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8XorU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i32.atomic.rmw16.xor_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16XorU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i32.atomic.rmw.xor",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32RmwXor, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i64.atomic.rmw8.xor_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8XorU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i64.atomic.rmw16.xor_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16XorU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw32.xor_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32XorU, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i64.atomic.rmw.xor",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64RmwXor, 0x4, 0x8, // alignment=2^4, offset=8
},
},
{
name: "i32.atomic.rmw8.xchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8XchgU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i32.atomic.rmw16.xchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16XchgU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i32.atomic.rmw.xchg",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI32RmwXchg, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i64.atomic.rmw8.xchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8XchgU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i64.atomic.rmw16.xchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16XchgU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw32.xchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32XchgU, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i64.atomic.rmw.xchg",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64RmwXchg, 0x4, 0x8, // alignment=2^4, offset=8
},
},
{
name: "i32.atomic.rmw8.cmpxchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeI32Const, 0x2,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8CmpxchgU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i32.atomic.rmw16.cmpxchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeI32Const, 0x2,
OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16CmpxchgU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i32.atomic.rmw.cmpxchg",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeI32Const, 0x2,
OpcodeAtomicPrefix, OpcodeAtomicI32RmwCmpxchg, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i64.atomic.rmw8.xchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeI64Const, 0x2,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8CmpxchgU, 0x1, 0x8, // alignment=2^1, offset=8
},
},
{
name: "i64.atomic.rmw16.cmpxchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeI64Const, 0x2,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16CmpxchgU, 0x2, 0x8, // alignment=2^2, offset=8
},
},
{
name: "i64.atomic.rmw32.cmpxchg_u",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeI64Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32CmpxchgU, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "i64.atomic.rmw.cmpxchg",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeI64Const, 0x2,
OpcodeAtomicPrefix, OpcodeAtomicI64RmwCmpxchg, 0x4, 0x8, // alignment=2^4, offset=8
},
},
{
name: "memory.atomic.wait32",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeI64Const, 0x2,
OpcodeAtomicPrefix, OpcodeAtomicMemoryWait32, 0x3, 0x8, // alignment=2^3, offset=8
},
},
{
name: "memory.atomic.wait64",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI64Const, 0x1,
OpcodeI64Const, 0x2,
OpcodeAtomicPrefix, OpcodeAtomicMemoryWait64, 0x4, 0x8, // alignment=2^4, offset=8
},
},
{
name: "memory.atomic.notify",
body: []byte{
OpcodeI32Const, 0x0,
OpcodeI32Const, 0x1,
OpcodeAtomicPrefix, OpcodeAtomicMemoryNotify, 0x3, 0x8, // alignment=2^3, offset=8
},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
body := append([]byte{}, tc.body...)
if !tt.noDropBeforeReturn {
body = append(body, OpcodeDrop)
}
body = append(body, OpcodeEnd)
m := &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: body}},
}
err := m.validateFunction(&stacks{}, experimental.CoreFeaturesThreads,
0, []Index{0}, nil, &Memory{}, []Table{}, nil, nil, bytes.NewReader(nil))
require.Error(t, err, "invalid memory alignment")
})
}
})
}
func TestValidation_LegacyExceptionHandlingOpcodes(t *testing.T) {
for _, tc := range []struct {
opcode byte
name string
}{
{OpcodeLegacyTry, "try"},
{OpcodeLegacyCatch, "catch"},
{OpcodeLegacyRethrow, "rethrow"},
{OpcodeLegacyDelegate, "delegate"},
{OpcodeLegacyCatchAll, "catch_all"},
} {
t.Run(tc.name, func(t *testing.T) {
m := &Module{
TypeSection: []FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []Code{{Body: []byte{tc.opcode, OpcodeEnd}}},
}
t.Run("with EH enabled", func(t *testing.T) {
err := m.validateFunction(&stacks{}, api.CoreFeaturesV2|experimental.CoreFeaturesExceptionHandling,
0, []Index{0}, nil, nil, nil, nil, nil, bytes.NewReader(nil))
require.Error(t, err)
require.Contains(t, err.Error(), "legacy exception handling instruction")
require.Contains(t, err.Error(), "wasm-opt --translate-to-exnref")
})
t.Run("without EH enabled", func(t *testing.T) {
err := m.validateFunction(&stacks{}, api.CoreFeaturesV2,
0, []Index{0}, nil, nil, nil, nil, nil, bytes.NewReader(nil))
require.Error(t, err)
require.Contains(t, err.Error(), "invalid instruction")
})
})
}
}