mirror of
https://github.com/wazero/wazero
synced 2026-06-21 14:12:37 +00:00
bfb20e0ba7
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>
4907 lines
162 KiB
Go
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")
|
|
})
|
|
})
|
|
}
|
|
}
|