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

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

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

Feature flag: `experimental.CoreFeaturesExceptionHandling`


## What's the use for this?

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

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

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


## Spec Suite

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

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

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

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

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

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


## Interpreter

Exception handling in the interpreter:

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

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

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


## Compiler

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

Throwing uses a two-phase ABI:

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

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

### Trampolines

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

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

### Lowering

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

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

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

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

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

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

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


### Caveats

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

---------

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

2553 lines
96 KiB
Go

package wasm
import (
"bytes"
"errors"
"fmt"
"slices"
"strconv"
"strings"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental"
"github.com/tetratelabs/wazero/internal/leb128"
)
// The wazero specific limitation described at RATIONALE.md.
const maximumValuesOnStack = 1 << 27
// validateFunction validates the instruction sequence of a function.
// following the specification https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#instructions%E2%91%A2.
//
// * idx is the index in the FunctionSection
// * functions are the function index, which is prefixed by imports. The value is the TypeSection index.
// * globals are the global index, which is prefixed by imports.
// * memory is the potentially imported memory and can be nil.
// * table is the potentially imported table and can be nil.
// * declaredFunctionIndexes is the set of function indexes declared by declarative element segments which can be acceed by OpcodeRefFunc instruction.
//
// Returns an error if the instruction sequence is not valid,
// or potentially it can exceed the maximum number of values on the stack.
func (m *Module) validateFunction(sts *stacks, enabledFeatures api.CoreFeatures, idx Index, functions []Index,
globals []GlobalType, memory *Memory, tables []Table, tags []Index, declaredFunctionIndexes map[Index]struct{}, br *bytes.Reader,
) error {
return m.validateFunctionWithMaxStackValues(sts, enabledFeatures, idx, functions, globals, memory, tables, tags, maximumValuesOnStack, declaredFunctionIndexes, br)
}
func readMemArg(pc uint64, body []byte) (align, offset uint32, read uint64, err error) {
align, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
err = fmt.Errorf("read memory align: %v", err)
return
}
if align >= 32 {
// Prevent 1<<align uint32 overflow.
err = fmt.Errorf("invalid memory alignment")
return
}
read += num
offset, num, err = leb128.LoadUint32(body[pc+num:])
if err != nil {
err = fmt.Errorf("read memory offset: %v", err)
return
}
read += num
return align, offset, read, nil
}
// validateFunctionWithMaxStackValues is like validateFunction, but allows overriding maxStackValues for testing.
//
// * stacks is to track the state of Wasm value and control frame stacks at anypoint of execution, and reused to reduce allocation.
// * maxStackValues is the maximum height of values stack which the target is allowed to reach.
func (m *Module) validateFunctionWithMaxStackValues(
sts *stacks,
enabledFeatures api.CoreFeatures,
idx Index,
functions []Index,
globals []GlobalType,
memory *Memory,
tables []Table,
tags []Index,
maxStackValues int,
declaredFunctionIndexes map[Index]struct{},
br *bytes.Reader,
) error {
functionType := &m.TypeSection[m.FunctionSection[idx]]
code := &m.CodeSection[idx]
body := code.Body
localTypes := code.LocalTypes
sts.reset(functionType)
valueTypeStack := &sts.vs
// We start with the outermost control block which is for function return if the code branches into it.
controlBlockStack := &sts.cs
// Now start walking through all the instructions in the body while tracking
// control blocks and value types to check the validity of all instructions.
for pc := uint64(0); pc < uint64(len(body)); pc++ {
op := body[pc]
if false {
var instName string
if op == OpcodeMiscPrefix {
instName = MiscInstructionName(body[pc+1])
} else if op == OpcodeVecPrefix {
instName = VectorInstructionName(body[pc+1])
} else if op == OpcodeAtomicPrefix {
instName = AtomicInstructionName(body[pc+1])
} else {
instName = InstructionName(op)
}
fmt.Printf("handling %s, stack=%s, blocks: %v\n", instName, valueTypeStack.stack, controlBlockStack)
}
if len(controlBlockStack.stack) == 0 {
return fmt.Errorf("unexpected end of function at pc=%#x", pc)
}
if OpcodeI32Load <= op && op <= OpcodeI64Store32 {
if memory == nil {
return fmt.Errorf("memory must exist for %s", InstructionName(op))
}
pc++
align, _, read, err := readMemArg(pc, body)
if err != nil {
return err
}
pc += read - 1
switch op {
case OpcodeI32Load:
if 1<<align > 32/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI32)
case OpcodeF32Load:
if 1<<align > 32/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeF32)
case OpcodeI32Store:
if 1<<align > 32/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
case OpcodeF32Store:
if 1<<align > 32/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
case OpcodeI64Load:
if 1<<align > 64/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI64)
case OpcodeF64Load:
if 1<<align > 64/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeF64)
case OpcodeI64Store:
if 1<<align > 64/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
case OpcodeF64Store:
if 1<<align > 64/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
case OpcodeI32Load8S:
if 1<<align > 1 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI32)
case OpcodeI32Load8U:
if 1<<align > 1 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI32)
case OpcodeI64Load8S, OpcodeI64Load8U:
if 1<<align > 1 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI64)
case OpcodeI32Store8:
if 1<<align > 1 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
case OpcodeI64Store8:
if 1<<align > 1 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
case OpcodeI32Load16S, OpcodeI32Load16U:
if 1<<align > 16/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI32)
case OpcodeI64Load16S, OpcodeI64Load16U:
if 1<<align > 16/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI64)
case OpcodeI32Store16:
if 1<<align > 16/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
case OpcodeI64Store16:
if 1<<align > 16/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
case OpcodeI64Load32S, OpcodeI64Load32U:
if 1<<align > 32/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI64)
case OpcodeI64Store32:
if 1<<align > 32/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
}
} else if OpcodeMemorySize <= op && op <= OpcodeMemoryGrow {
if memory == nil {
return fmt.Errorf("memory must exist for %s", InstructionName(op))
}
pc++
val, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("read immediate: %v", err)
}
if val != 0 || num != 1 {
return fmt.Errorf("memory instruction reserved bytes not zero with 1 byte")
}
switch Opcode(op) {
case OpcodeMemoryGrow:
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI32)
case OpcodeMemorySize:
valueTypeStack.push(ValueTypeI32)
}
pc += num - 1
} else if OpcodeI32Const <= op && op <= OpcodeF64Const {
pc++
switch Opcode(op) {
case OpcodeI32Const:
_, num, err := leb128.LoadInt32(body[pc:])
if err != nil {
return fmt.Errorf("read i32 immediate: %s", err)
}
pc += num - 1
valueTypeStack.push(ValueTypeI32)
case OpcodeI64Const:
_, num, err := leb128.LoadInt64(body[pc:])
if err != nil {
return fmt.Errorf("read i64 immediate: %v", err)
}
valueTypeStack.push(ValueTypeI64)
pc += num - 1
case OpcodeF32Const:
valueTypeStack.push(ValueTypeF32)
pc += 3
case OpcodeF64Const:
valueTypeStack.push(ValueTypeF64)
pc += 7
}
} else if OpcodeLocalGet <= op && op <= OpcodeGlobalSet {
pc++
index, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("read immediate: %v", err)
}
pc += num - 1
switch op {
case OpcodeLocalGet:
inputLen := uint32(len(functionType.Params))
if l := uint32(len(localTypes)) + inputLen; index >= l {
return fmt.Errorf("invalid local index for %s %d >= %d(=len(locals)+len(parameters))",
OpcodeLocalGetName, index, l)
}
if index < inputLen {
valueTypeStack.push(functionType.Params[index])
} else {
valueTypeStack.push(localTypes[index-inputLen])
}
case OpcodeLocalSet:
inputLen := uint32(len(functionType.Params))
if l := uint32(len(localTypes)) + inputLen; index >= l {
return fmt.Errorf("invalid local index for %s %d >= %d(=len(locals)+len(parameters))",
OpcodeLocalSetName, index, l)
}
var expType ValueType
if index < inputLen {
expType = functionType.Params[index]
} else {
expType = localTypes[index-inputLen]
}
if err := valueTypeStack.popAndVerifyType(expType); err != nil {
return err
}
case OpcodeLocalTee:
inputLen := uint32(len(functionType.Params))
if l := uint32(len(localTypes)) + inputLen; index >= l {
return fmt.Errorf("invalid local index for %s %d >= %d(=len(locals)+len(parameters))",
OpcodeLocalTeeName, index, l)
}
var expType ValueType
if index < inputLen {
expType = functionType.Params[index]
} else {
expType = localTypes[index-inputLen]
}
if err := valueTypeStack.popAndVerifyType(expType); err != nil {
return err
}
valueTypeStack.push(expType)
case OpcodeGlobalGet:
if index >= uint32(len(globals)) {
return fmt.Errorf("invalid index for %s", OpcodeGlobalGetName)
}
valueTypeStack.push(globals[index].ValType)
case OpcodeGlobalSet:
if index >= uint32(len(globals)) {
return fmt.Errorf("invalid global index")
} else if !globals[index].Mutable {
return fmt.Errorf("%s when not mutable", OpcodeGlobalSetName)
} else if err := valueTypeStack.popAndVerifyType(
globals[index].ValType); err != nil {
return err
}
}
} else if op == OpcodeBr {
pc++
index, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("read immediate: %v", err)
} else if int(index) >= len(controlBlockStack.stack) {
return fmt.Errorf("invalid %s operation: index out of range", OpcodeBrName)
}
pc += num - 1
// Check type soundness.
target := &controlBlockStack.stack[len(controlBlockStack.stack)-int(index)-1]
var targetResultType []ValueType
if target.op == OpcodeLoop {
targetResultType = target.blockType.Params
} else {
targetResultType = target.blockType.Results
}
if err = valueTypeStack.popResults(op, targetResultType, false); err != nil {
return err
}
// br instruction is stack-polymorphic.
valueTypeStack.unreachable()
} else if op == OpcodeBrIf {
pc++
index, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("read immediate: %v", err)
} else if int(index) >= len(controlBlockStack.stack) {
return fmt.Errorf(
"invalid ln param given for %s: index=%d with %d for the current label stack length",
OpcodeBrIfName, index, len(controlBlockStack.stack))
}
pc += num - 1
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the required operand for %s", OpcodeBrIfName)
}
// Check type soundness.
target := &controlBlockStack.stack[len(controlBlockStack.stack)-int(index)-1]
var targetResultType []ValueType
if target.op == OpcodeLoop {
targetResultType = target.blockType.Params
} else {
targetResultType = target.blockType.Results
}
if err := valueTypeStack.popResults(op, targetResultType, false); err != nil {
return err
}
// Push back the result
for _, t := range targetResultType {
valueTypeStack.push(t)
}
} else if op == OpcodeBrTable {
pc++
br.Reset(body[pc:])
nl, num, err := leb128.DecodeUint32(br)
if err != nil {
return fmt.Errorf("read immediate: %w", err)
}
sts.ls = sts.ls[:0]
for i := uint32(0); i < nl; i++ {
l, n, err := leb128.DecodeUint32(br)
if err != nil {
return fmt.Errorf("read immediate: %w", err)
}
num += n
sts.ls = append(sts.ls, l)
}
ln, n, err := leb128.DecodeUint32(br)
if err != nil {
return fmt.Errorf("read immediate: %w", err)
} else if int(ln) >= len(controlBlockStack.stack) {
return fmt.Errorf(
"invalid ln param given for %s: ln=%d with %d for the current label stack length",
OpcodeBrTableName, ln, len(controlBlockStack.stack))
}
pc += n + num - 1
// Check type soundness.
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the required operand for %s", OpcodeBrTableName)
}
lnLabel := &controlBlockStack.stack[len(controlBlockStack.stack)-1-int(ln)]
var defaultLabelType []ValueType
// Below, we might modify the slice in case of unreachable. Therefore,
// we have to copy the content of block result types, otherwise the original
// function type might result in invalid value types if the block is the outermost label
// which equals the function's type.
if lnLabel.op != OpcodeLoop { // Loop operation doesn't require results since the continuation is the beginning of the loop.
defaultLabelType = slices.Clone(lnLabel.blockType.Results)
} else {
defaultLabelType = slices.Clone(lnLabel.blockType.Params)
}
if enabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes) {
// As of reference-types proposal, br_table on unreachable state
// can choose unknown types for expected parameter types for each label.
// https://github.com/WebAssembly/reference-types/pull/116
for i := range defaultLabelType {
index := len(defaultLabelType) - 1 - i
exp := defaultLabelType[index]
actual, err := valueTypeStack.pop()
if err != nil {
return err
}
if actual == valueTypeUnknown {
// Re-assign the expected type to unknown.
defaultLabelType[index] = valueTypeUnknown
} else if actual != exp {
return typeMismatchError(true, OpcodeBrTableName, actual, exp, i)
}
}
} else {
if err = valueTypeStack.popResults(op, defaultLabelType, false); err != nil {
return err
}
}
for _, l := range sts.ls {
if int(l) >= len(controlBlockStack.stack) {
return fmt.Errorf("invalid l param given for %s", OpcodeBrTableName)
}
label := &controlBlockStack.stack[len(controlBlockStack.stack)-1-int(l)]
var tableLabelType []ValueType
if label.op != OpcodeLoop {
tableLabelType = label.blockType.Results
} else {
tableLabelType = label.blockType.Params
}
if len(defaultLabelType) != len(tableLabelType) {
return fmt.Errorf("inconsistent block type length for %s at %d; %v (ln=%d) != %v (l=%d)", OpcodeBrTableName, l, defaultLabelType, ln, tableLabelType, l)
}
for i := range defaultLabelType {
if defaultLabelType[i] != valueTypeUnknown && defaultLabelType[i] != tableLabelType[i] {
return fmt.Errorf("incosistent block type for %s at %d", OpcodeBrTableName, l)
}
}
}
// br_table instruction is stack-polymorphic.
valueTypeStack.unreachable()
} else if op == OpcodeThrow {
if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesExceptionHandling); err != nil {
return fmt.Errorf("%s invalid as %v", OpcodeThrowName, err)
}
pc++
tagIndex, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("read immediate: %v", err)
}
pc += num - 1
if tagIndex >= uint32(len(tags)) {
return fmt.Errorf("invalid tag index for %s: %d", OpcodeThrowName, tagIndex)
}
tagType := &m.TypeSection[tags[tagIndex]]
// Pop values matching the tag's params in reverse order.
for i := len(tagType.Params) - 1; i >= 0; i-- {
if err := valueTypeStack.popAndVerifyType(tagType.Params[i]); err != nil {
return fmt.Errorf("type mismatch on %s operation: %v", OpcodeThrowName, err)
}
}
// throw is stack-polymorphic (never returns).
valueTypeStack.unreachable()
} else if op == OpcodeThrowRef {
if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesExceptionHandling); err != nil {
return fmt.Errorf("%s invalid as %v", OpcodeThrowRefName, err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeExnref); err != nil {
return fmt.Errorf("type mismatch on %s: %v", OpcodeThrowRefName, err)
}
// throw_ref is stack-polymorphic (never returns).
valueTypeStack.unreachable()
} else if op == OpcodeTryTable {
if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesExceptionHandling); err != nil {
return fmt.Errorf("%s invalid as %v", OpcodeTryTableName, err)
}
br.Reset(body[pc+1:])
bt, num, err := DecodeBlockType(m.TypeSection, br, enabledFeatures)
if err != nil {
return fmt.Errorf("read block: %w", err)
}
pc += num
// Read catch clause count.
catchCount, catchNum, err := leb128.LoadUint32(body[pc+1:])
if err != nil {
return fmt.Errorf("read catch count: %v", err)
}
pc += catchNum
// Validate each catch clause.
for i := uint32(0); i < catchCount; i++ {
pc++
catchKind := body[pc]
switch catchKind {
case CatchKindCatch, CatchKindCatchRef:
pc++
tagIdx, tagNum, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("read catch tag index: %v", err)
}
pc += tagNum - 1
if tagIdx >= uint32(len(tags)) {
return fmt.Errorf("invalid tag index in catch clause: %d", tagIdx)
}
tagType := &m.TypeSection[tags[tagIdx]]
pc++
labelIdx, labelNum, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("read catch label index: %v", err)
}
pc += labelNum - 1
if int(labelIdx) >= len(controlBlockStack.stack) {
return fmt.Errorf("invalid label index in catch clause: %d", labelIdx)
}
// Validate that the target label can accept the catch values.
target := &controlBlockStack.stack[len(controlBlockStack.stack)-int(labelIdx)-1]
var expectedTypes []ValueType
if target.op == OpcodeLoop {
expectedTypes = target.blockType.Params
} else {
expectedTypes = target.blockType.Results
}
var catchTypes []ValueType
catchTypes = append(catchTypes, tagType.Params...)
if catchKind == CatchKindCatchRef {
catchTypes = append(catchTypes, ValueTypeExnref)
}
if len(catchTypes) != len(expectedTypes) {
return fmt.Errorf("catch clause type mismatch: catch delivers %d values but label expects %d", len(catchTypes), len(expectedTypes))
}
for j := range catchTypes {
if !isStrictRefSubtypeOf(catchTypes[j], expectedTypes[j]) {
return fmt.Errorf("catch clause type mismatch at index %d: %v is not a subtype of %v", j, catchTypes[j], expectedTypes[j])
}
}
case CatchKindCatchAll, CatchKindCatchAllRef:
pc++
labelIdx, labelNum, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("read catch_all label index: %v", err)
}
pc += labelNum - 1
if int(labelIdx) >= len(controlBlockStack.stack) {
return fmt.Errorf("invalid label index in catch_all clause: %d", labelIdx)
}
target := &controlBlockStack.stack[len(controlBlockStack.stack)-int(labelIdx)-1]
var expectedTypes []ValueType
if target.op == OpcodeLoop {
expectedTypes = target.blockType.Params
} else {
expectedTypes = target.blockType.Results
}
var catchTypes []ValueType
if catchKind == CatchKindCatchAllRef {
catchTypes = append(catchTypes, ValueTypeExnref)
}
if len(catchTypes) != len(expectedTypes) {
return fmt.Errorf("catch_all clause type mismatch: catch delivers %d values but label expects %d", len(catchTypes), len(expectedTypes))
}
for j := range catchTypes {
if catchTypes[j] != expectedTypes[j] {
return fmt.Errorf("catch_all clause type mismatch at index %d", j)
}
}
default:
return fmt.Errorf("invalid catch kind: %#x", catchKind)
}
}
controlBlockStack.push(pc, 0, 0, bt, 0, op)
if err = valueTypeStack.popParams(op, bt.Params, false); err != nil {
return err
}
for _, p := range bt.Params {
valueTypeStack.push(p)
}
valueTypeStack.pushStackLimit(len(bt.Params))
} else if op == OpcodeCall || op == OpcodeTailCallReturnCall {
pc++
index, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("read immediate: %v", err)
}
pc += num - 1
if int(index) >= len(functions) {
return fmt.Errorf("invalid function index")
}
var opcodeName string
if op == OpcodeCall {
opcodeName = OpcodeCallName
} else {
opcodeName = OpcodeTailCallReturnCallName
}
funcType := &m.TypeSection[functions[index]]
for i := 0; i < len(funcType.Params); i++ {
if err := valueTypeStack.popAndVerifyType(funcType.Params[len(funcType.Params)-1-i]); err != nil {
return fmt.Errorf("type mismatch on %s operation param type: %v", opcodeName, err)
}
}
for _, exp := range funcType.Results {
valueTypeStack.push(exp)
}
if op == OpcodeTailCallReturnCall {
if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesTailCall); err != nil {
return fmt.Errorf("%s invalid as %v", OpcodeTailCallReturnCallName, err)
}
// Same formatting as OpcodeEnd on the outer-most block
if err := valueTypeStack.requireStackValues(false, "", functionType.Results, false); err != nil {
return err
}
// behaves as a jump.
valueTypeStack.unreachable()
}
} else if op == OpcodeCallIndirect || op == OpcodeTailCallReturnCallIndirect {
pc++
typeIndex, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("read immediate: %v", err)
}
pc += num
var opcodeName string
if op == OpcodeCallIndirect {
opcodeName = OpcodeCallIndirectName
} else {
opcodeName = OpcodeTailCallReturnCallIndirectName
}
if int(typeIndex) >= len(m.TypeSection) {
return fmt.Errorf("invalid type index at %s: %d", opcodeName, typeIndex)
}
tableIndex, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("read table index: %v", err)
}
pc += num - 1
if tableIndex != 0 {
if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
return fmt.Errorf("table index must be zero but was %d: %w", tableIndex, err)
}
}
if tableIndex >= uint32(len(tables)) {
return fmt.Errorf("unknown table index: %d", tableIndex)
}
table := tables[tableIndex]
if table.Type != RefTypeFuncref {
return fmt.Errorf("table is not funcref type but was %s for %s", RefTypeName(table.Type), opcodeName)
}
if err = valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the offset in table for %s", opcodeName)
}
funcType := &m.TypeSection[typeIndex]
for i := 0; i < len(funcType.Params); i++ {
if err = valueTypeStack.popAndVerifyType(funcType.Params[len(funcType.Params)-1-i]); err != nil {
return fmt.Errorf("type mismatch on %s operation input type", opcodeName)
}
}
for _, exp := range funcType.Results {
valueTypeStack.push(exp)
}
if op == OpcodeTailCallReturnCallIndirect {
if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesTailCall); err != nil {
return fmt.Errorf("%s invalid as %v", OpcodeTailCallReturnCallIndirectName, err)
}
// Same formatting as OpcodeEnd on the outer-most block
if err := valueTypeStack.requireStackValues(false, "", functionType.Results, false); err != nil {
return err
}
// behaves as a jump.
valueTypeStack.unreachable()
}
} else if OpcodeI32Eqz <= op && op <= OpcodeI64Extend32S {
switch op {
case OpcodeI32Eqz:
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI32EqzName, err)
}
valueTypeStack.push(ValueTypeI32)
case OpcodeI32Eq, OpcodeI32Ne, OpcodeI32LtS,
OpcodeI32LtU, OpcodeI32GtS, OpcodeI32GtU, OpcodeI32LeS,
OpcodeI32LeU, OpcodeI32GeS, OpcodeI32GeU:
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the 1st i32 operand for %s: %v", InstructionName(op), err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the 2nd i32 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeI32)
case OpcodeI64Eqz:
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI64EqzName, err)
}
valueTypeStack.push(ValueTypeI32)
case OpcodeI64Eq, OpcodeI64Ne, OpcodeI64LtS,
OpcodeI64LtU, OpcodeI64GtS, OpcodeI64GtU,
OpcodeI64LeS, OpcodeI64LeU, OpcodeI64GeS, OpcodeI64GeU:
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return fmt.Errorf("cannot pop the 1st i64 operand for %s: %v", InstructionName(op), err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return fmt.Errorf("cannot pop the 2nd i64 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeI32)
case OpcodeF32Eq, OpcodeF32Ne, OpcodeF32Lt, OpcodeF32Gt, OpcodeF32Le, OpcodeF32Ge:
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
return fmt.Errorf("cannot pop the 1st f32 operand for %s: %v", InstructionName(op), err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
return fmt.Errorf("cannot pop the 2nd f32 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeI32)
case OpcodeF64Eq, OpcodeF64Ne, OpcodeF64Lt, OpcodeF64Gt, OpcodeF64Le, OpcodeF64Ge:
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
return fmt.Errorf("cannot pop the 1st f64 operand for %s: %v", InstructionName(op), err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
return fmt.Errorf("cannot pop the 2nd f64 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeI32)
case OpcodeI32Clz, OpcodeI32Ctz, OpcodeI32Popcnt:
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the i32 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeI32)
case OpcodeI32Add, OpcodeI32Sub, OpcodeI32Mul, OpcodeI32DivS,
OpcodeI32DivU, OpcodeI32RemS, OpcodeI32RemU, OpcodeI32And,
OpcodeI32Or, OpcodeI32Xor, OpcodeI32Shl, OpcodeI32ShrS,
OpcodeI32ShrU, OpcodeI32Rotl, OpcodeI32Rotr:
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the 1st operand for %s: %v", InstructionName(op), err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the 2nd operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeI32)
case OpcodeI64Clz, OpcodeI64Ctz, OpcodeI64Popcnt:
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return fmt.Errorf("cannot pop the i64 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeI64)
case OpcodeI64Add, OpcodeI64Sub, OpcodeI64Mul, OpcodeI64DivS,
OpcodeI64DivU, OpcodeI64RemS, OpcodeI64RemU, OpcodeI64And,
OpcodeI64Or, OpcodeI64Xor, OpcodeI64Shl, OpcodeI64ShrS,
OpcodeI64ShrU, OpcodeI64Rotl, OpcodeI64Rotr:
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return fmt.Errorf("cannot pop the 1st i64 operand for %s: %v", InstructionName(op), err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return fmt.Errorf("cannot pop the 2nd i64 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeI64)
case OpcodeF32Abs, OpcodeF32Neg, OpcodeF32Ceil,
OpcodeF32Floor, OpcodeF32Trunc, OpcodeF32Nearest,
OpcodeF32Sqrt:
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
return fmt.Errorf("cannot pop the 1st f32 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeF32)
case OpcodeF32Add, OpcodeF32Sub, OpcodeF32Mul,
OpcodeF32Div, OpcodeF32Min, OpcodeF32Max,
OpcodeF32Copysign:
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
return fmt.Errorf("cannot pop the 1st f32 operand for %s: %v", InstructionName(op), err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
return fmt.Errorf("cannot pop the 2nd f32 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeF32)
case OpcodeF64Abs, OpcodeF64Neg, OpcodeF64Ceil,
OpcodeF64Floor, OpcodeF64Trunc, OpcodeF64Nearest,
OpcodeF64Sqrt:
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
return fmt.Errorf("cannot pop the 1st f64 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeF64)
case OpcodeF64Add, OpcodeF64Sub, OpcodeF64Mul,
OpcodeF64Div, OpcodeF64Min, OpcodeF64Max,
OpcodeF64Copysign:
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
return fmt.Errorf("cannot pop the 1st f64 operand for %s: %v", InstructionName(op), err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
return fmt.Errorf("cannot pop the 2nd f64 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeF64)
case OpcodeI32WrapI64:
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI32WrapI64Name, err)
}
valueTypeStack.push(ValueTypeI32)
case OpcodeI32TruncF32S, OpcodeI32TruncF32U:
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
return fmt.Errorf("cannot pop the f32 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeI32)
case OpcodeI32TruncF64S, OpcodeI32TruncF64U:
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
return fmt.Errorf("cannot pop the f64 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeI32)
case OpcodeI64ExtendI32S, OpcodeI64ExtendI32U:
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the i32 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeI64)
case OpcodeI64TruncF32S, OpcodeI64TruncF32U:
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
return fmt.Errorf("cannot pop the f32 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeI64)
case OpcodeI64TruncF64S, OpcodeI64TruncF64U:
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
return fmt.Errorf("cannot pop the f64 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeI64)
case OpcodeF32ConvertI32S, OpcodeF32ConvertI32U:
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the i32 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeF32)
case OpcodeF32ConvertI64S, OpcodeF32ConvertI64U:
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return fmt.Errorf("cannot pop the i64 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeF32)
case OpcodeF32DemoteF64:
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeF32DemoteF64Name, err)
}
valueTypeStack.push(ValueTypeF32)
case OpcodeF64ConvertI32S, OpcodeF64ConvertI32U:
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the i32 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeF64)
case OpcodeF64ConvertI64S, OpcodeF64ConvertI64U:
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return fmt.Errorf("cannot pop the i64 operand for %s: %v", InstructionName(op), err)
}
valueTypeStack.push(ValueTypeF64)
case OpcodeF64PromoteF32:
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeF64PromoteF32Name, err)
}
valueTypeStack.push(ValueTypeF64)
case OpcodeI32ReinterpretF32:
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI32ReinterpretF32Name, err)
}
valueTypeStack.push(ValueTypeI32)
case OpcodeI64ReinterpretF64:
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI64ReinterpretF64Name, err)
}
valueTypeStack.push(ValueTypeI64)
case OpcodeF32ReinterpretI32:
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeF32ReinterpretI32Name, err)
}
valueTypeStack.push(ValueTypeF32)
case OpcodeF64ReinterpretI64:
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeF64ReinterpretI64Name, err)
}
valueTypeStack.push(ValueTypeF64)
case OpcodeI32Extend8S, OpcodeI32Extend16S:
if err := enabledFeatures.RequireEnabled(api.CoreFeatureSignExtensionOps); err != nil {
return fmt.Errorf("%s invalid as %v", instructionNames[op], err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", instructionNames[op], err)
}
valueTypeStack.push(ValueTypeI32)
case OpcodeI64Extend8S, OpcodeI64Extend16S, OpcodeI64Extend32S:
if err := enabledFeatures.RequireEnabled(api.CoreFeatureSignExtensionOps); err != nil {
return fmt.Errorf("%s invalid as %v", instructionNames[op], err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", instructionNames[op], err)
}
valueTypeStack.push(ValueTypeI64)
default:
return fmt.Errorf("invalid numeric instruction 0x%x", op)
}
} else if op >= OpcodeRefNull && op <= OpcodeRefFunc {
if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
return fmt.Errorf("%s invalid as %v", instructionNames[op], err)
}
switch op {
case OpcodeRefNull:
pc++
switch reftype := body[pc]; reftype {
case ValueTypeExternref:
valueTypeStack.push(ValueTypeExternref)
case ValueTypeFuncref:
valueTypeStack.push(ValueTypeFuncref)
default:
return fmt.Errorf("unknown type for ref.null: 0x%x", reftype)
}
case OpcodeRefIsNull:
tp, err := valueTypeStack.pop()
if err != nil {
return fmt.Errorf("cannot pop the operand for ref.is_null: %v", err)
} else if !isReferenceValueType(tp) && tp != valueTypeUnknown {
return fmt.Errorf("type mismatch: expected reference type but was %s", ValueTypeName(tp))
}
valueTypeStack.push(ValueTypeI32)
case OpcodeRefFunc:
pc++
index, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("failed to read function index for ref.func: %v", err)
}
if _, ok := declaredFunctionIndexes[index]; !ok {
return fmt.Errorf("undeclared function index %d for ref.func", index)
}
pc += num - 1
// ref.func always produces a non-null reference.
valueTypeStack.push(ValueTypeFuncref)
}
} else if op == OpcodeTableGet || op == OpcodeTableSet {
if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
return fmt.Errorf("%s is invalid as %v", InstructionName(op), err)
}
pc++
tableIndex, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("read immediate: %v", err)
}
if tableIndex >= uint32(len(tables)) {
return fmt.Errorf("table of index %d not found", tableIndex)
}
refType := tables[tableIndex].Type
if op == OpcodeTableGet {
if err := valueTypeStack.popAndVerifyType(api.ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the operand for table.get: %v", err)
}
valueTypeStack.push(refType)
} else {
if err := valueTypeStack.popAndVerifyType(refType); err != nil {
return fmt.Errorf("cannot pop the operand for table.set: %v", err)
}
if err := valueTypeStack.popAndVerifyType(api.ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the operand for table.set: %v", err)
}
}
pc += num - 1
} else if op == OpcodeMiscPrefix {
pc++
// A misc opcode is encoded as an unsigned variable 32-bit integer.
miscOp32, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("failed to read misc opcode: %v", err)
}
pc += num - 1
miscOpcode := byte(miscOp32)
// If the misc opcode is beyond byte range, it is highly likely this is an invalid binary, or
// it is due to the new opcode from a new proposal. In the latter case, we have to
// change the alias type of OpcodeMisc (which is currently byte) to uint32.
if uint32(byte(miscOp32)) != miscOp32 {
return fmt.Errorf("invalid misc opcode: %#x", miscOp32)
}
if miscOpcode >= OpcodeMiscI32TruncSatF32S && miscOpcode <= OpcodeMiscI64TruncSatF64U {
if err := enabledFeatures.RequireEnabled(api.CoreFeatureNonTrappingFloatToIntConversion); err != nil {
return fmt.Errorf("%s invalid as %v", miscInstructionNames[miscOpcode], err)
}
var inType, outType ValueType
switch miscOpcode {
case OpcodeMiscI32TruncSatF32S, OpcodeMiscI32TruncSatF32U:
inType, outType = ValueTypeF32, ValueTypeI32
case OpcodeMiscI32TruncSatF64S, OpcodeMiscI32TruncSatF64U:
inType, outType = ValueTypeF64, ValueTypeI32
case OpcodeMiscI64TruncSatF32S, OpcodeMiscI64TruncSatF32U:
inType, outType = ValueTypeF32, ValueTypeI64
case OpcodeMiscI64TruncSatF64S, OpcodeMiscI64TruncSatF64U:
inType, outType = ValueTypeF64, ValueTypeI64
}
if err := valueTypeStack.popAndVerifyType(inType); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", miscInstructionNames[miscOpcode], err)
}
valueTypeStack.push(outType)
} else if miscOpcode >= OpcodeMiscMemoryInit && miscOpcode <= OpcodeMiscTableCopy {
if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil {
return fmt.Errorf("%s invalid as %v", miscInstructionNames[miscOpcode], err)
}
var params []ValueType
// Handle opcodes added in bulk-memory-operations/WebAssembly 2.0.
switch miscOpcode {
case OpcodeMiscDataDrop:
if m.DataCountSection == nil {
return fmt.Errorf("%s requires data count section", MiscInstructionName(miscOpcode))
}
// We need to read the index to the data section.
pc++
index, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("failed to read data segment index for %s: %v", MiscInstructionName(miscOpcode), err)
}
if int(index) >= len(m.DataSection) {
return fmt.Errorf("index %d out of range of data section(len=%d)", index, len(m.DataSection))
}
pc += num - 1
case OpcodeMiscMemoryInit, OpcodeMiscMemoryCopy, OpcodeMiscMemoryFill:
if memory == nil {
return fmt.Errorf("memory must exist for %s", MiscInstructionName(miscOpcode))
}
params = []ValueType{ValueTypeI32, ValueTypeI32, ValueTypeI32}
if miscOpcode == OpcodeMiscMemoryInit {
if m.DataCountSection == nil {
return fmt.Errorf("%s requires data count section", MiscInstructionName(miscOpcode))
}
// We need to read the index to the data section.
pc++
index, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("failed to read data segment index for %s: %v", MiscInstructionName(miscOpcode), err)
}
if int(index) >= len(m.DataSection) {
return fmt.Errorf("index %d out of range of data section(len=%d)", index, len(m.DataSection))
}
pc += num - 1
}
pc++
val, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("failed to read memory index for %s: %v", MiscInstructionName(miscOpcode), err)
}
if val != 0 || num != 1 {
return fmt.Errorf("%s reserved byte must be zero encoded with 1 byte", MiscInstructionName(miscOpcode))
}
if miscOpcode == OpcodeMiscMemoryCopy {
pc++
// memory.copy needs two memory index which are reserved as zero.
val, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("failed to read memory index for %s: %v", MiscInstructionName(miscOpcode), err)
}
if val != 0 || num != 1 {
return fmt.Errorf("%s reserved byte must be zero encoded with 1 byte", MiscInstructionName(miscOpcode))
}
}
case OpcodeMiscTableInit:
params = []ValueType{ValueTypeI32, ValueTypeI32, ValueTypeI32}
pc++
elementIndex, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("failed to read element segment index for %s: %v", MiscInstructionName(miscOpcode), err)
}
if int(elementIndex) >= len(m.ElementSection) {
return fmt.Errorf("index %d out of range of element section(len=%d)", elementIndex, len(m.ElementSection))
}
pc += num
tableIndex, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("failed to read source table index for %s: %v", MiscInstructionName(miscOpcode), err)
}
if tableIndex != 0 {
if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
return fmt.Errorf("source table index must be zero for %s as %v", MiscInstructionName(miscOpcode), err)
}
}
if tableIndex >= uint32(len(tables)) {
return fmt.Errorf("table of index %d not found", tableIndex)
}
if m.ElementSection[elementIndex].Type != tables[tableIndex].Type {
return fmt.Errorf("type mismatch for table.init: element type %s does not match table type %s",
RefTypeName(m.ElementSection[elementIndex].Type),
RefTypeName(tables[tableIndex].Type),
)
}
pc += num - 1
case OpcodeMiscElemDrop:
pc++
elementIndex, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("failed to read element segment index for %s: %v", MiscInstructionName(miscOpcode), err)
} else if int(elementIndex) >= len(m.ElementSection) {
return fmt.Errorf("index %d out of range of element section(len=%d)", elementIndex, len(m.ElementSection))
}
pc += num - 1
case OpcodeMiscTableCopy:
params = []ValueType{ValueTypeI32, ValueTypeI32, ValueTypeI32}
pc++
dstTableIndex, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("failed to read destination table index for %s: %v", MiscInstructionName(miscOpcode), err)
}
if dstTableIndex != 0 {
if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
return fmt.Errorf("destination table index must be zero for %s as %v", MiscInstructionName(miscOpcode), err)
}
}
if dstTableIndex >= uint32(len(tables)) {
return fmt.Errorf("table of index %d not found", dstTableIndex)
}
pc += num
srcTableIndex, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("failed to read source table index for %s: %v", MiscInstructionName(miscOpcode), err)
}
if srcTableIndex != 0 {
if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
return fmt.Errorf("source table index must be zero for %s as %v", MiscInstructionName(miscOpcode), err)
}
}
if srcTableIndex >= uint32(len(tables)) {
return fmt.Errorf("table of index %d not found", srcTableIndex)
}
if tables[srcTableIndex].Type != tables[dstTableIndex].Type {
return fmt.Errorf("table type mismatch for table.copy: %s (src) != %s (dst)",
RefTypeName(tables[srcTableIndex].Type), RefTypeName(tables[dstTableIndex].Type))
}
pc += num - 1
}
for _, p := range params {
if err := valueTypeStack.popAndVerifyType(p); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", miscInstructionNames[miscOpcode], err)
}
}
} else if miscOpcode >= OpcodeMiscTableGrow && miscOpcode <= OpcodeMiscTableFill {
if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
return fmt.Errorf("%s invalid as %v", miscInstructionNames[miscOpcode], err)
}
pc++
tableIndex, num, err := leb128.LoadUint32(body[pc:])
if err != nil {
return fmt.Errorf("failed to read table index for %s: %v", MiscInstructionName(miscOpcode), err)
}
if tableIndex >= uint32(len(tables)) {
return fmt.Errorf("table of index %d not found", tableIndex)
}
pc += num - 1
var params, results []ValueType
reftype := tables[tableIndex].Type
if miscOpcode == OpcodeMiscTableGrow {
params = []ValueType{ValueTypeI32, reftype}
results = []ValueType{ValueTypeI32}
} else if miscOpcode == OpcodeMiscTableSize {
results = []ValueType{ValueTypeI32}
} else if miscOpcode == OpcodeMiscTableFill {
params = []ValueType{ValueTypeI32, reftype, ValueTypeI32}
}
for _, p := range params {
if err := valueTypeStack.popAndVerifyType(p); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", miscInstructionNames[miscOpcode], err)
}
}
for _, r := range results {
valueTypeStack.push(r)
}
} else {
return fmt.Errorf("unknown misc opcode %#x", miscOpcode)
}
} else if op == OpcodeVecPrefix {
pc++
// Vector instructions come with two bytes where the first byte is always OpcodeVecPrefix,
// and the second byte determines the actual instruction.
vecOpcode := body[pc]
if err := enabledFeatures.RequireEnabled(api.CoreFeatureSIMD); err != nil {
return fmt.Errorf("%s invalid as %v", vectorInstructionName[vecOpcode], err)
}
switch vecOpcode {
case OpcodeVecV128Const:
// Read 128-bit = 16 bytes constants
if int(pc+16) >= len(body) {
return fmt.Errorf("cannot read constant vector value for %s", vectorInstructionName[vecOpcode])
}
pc += 16
valueTypeStack.push(ValueTypeV128)
case OpcodeVecV128AnyTrue, OpcodeVecI8x16AllTrue, OpcodeVecI16x8AllTrue, OpcodeVecI32x4AllTrue, OpcodeVecI64x2AllTrue,
OpcodeVecI8x16BitMask, OpcodeVecI16x8BitMask, OpcodeVecI32x4BitMask, OpcodeVecI64x2BitMask:
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
valueTypeStack.push(ValueTypeI32)
case OpcodeVecV128Load, OpcodeVecV128Load8x8s, OpcodeVecV128Load8x8u, OpcodeVecV128Load16x4s, OpcodeVecV128Load16x4u,
OpcodeVecV128Load32x2s, OpcodeVecV128Load32x2u, OpcodeVecV128Load8Splat, OpcodeVecV128Load16Splat,
OpcodeVecV128Load32Splat, OpcodeVecV128Load64Splat,
OpcodeVecV128Load32zero, OpcodeVecV128Load64zero:
if memory == nil {
return fmt.Errorf("memory must exist for %s", VectorInstructionName(vecOpcode))
}
pc++
align, _, read, err := readMemArg(pc, body)
if err != nil {
return err
}
pc += read - 1
var maxAlign uint32
switch vecOpcode {
case OpcodeVecV128Load:
maxAlign = 128 / 8
case OpcodeVecV128Load8x8s, OpcodeVecV128Load8x8u, OpcodeVecV128Load16x4s, OpcodeVecV128Load16x4u,
OpcodeVecV128Load32x2s, OpcodeVecV128Load32x2u:
maxAlign = 64 / 8
case OpcodeVecV128Load8Splat:
maxAlign = 1
case OpcodeVecV128Load16Splat:
maxAlign = 16 / 8
case OpcodeVecV128Load32Splat:
maxAlign = 32 / 8
case OpcodeVecV128Load64Splat:
maxAlign = 64 / 8
case OpcodeVecV128Load32zero:
maxAlign = 32 / 8
case OpcodeVecV128Load64zero:
maxAlign = 64 / 8
}
if 1<<align > maxAlign {
return fmt.Errorf("invalid memory alignment %d for %s", align, VectorInstructionName(vecOpcode))
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", VectorInstructionName(vecOpcode), err)
}
valueTypeStack.push(ValueTypeV128)
case OpcodeVecV128Store:
if memory == nil {
return fmt.Errorf("memory must exist for %s", VectorInstructionName(vecOpcode))
}
pc++
align, _, read, err := readMemArg(pc, body)
if err != nil {
return err
}
pc += read - 1
if 1<<align > 128/8 {
return fmt.Errorf("invalid memory alignment %d for %s", align, OpcodeVecV128StoreName)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeVecV128StoreName, err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeVecV128StoreName, err)
}
case OpcodeVecV128Load8Lane, OpcodeVecV128Load16Lane, OpcodeVecV128Load32Lane, OpcodeVecV128Load64Lane:
if memory == nil {
return fmt.Errorf("memory must exist for %s", VectorInstructionName(vecOpcode))
}
attr := vecLoadLanes[vecOpcode]
pc++
align, _, read, err := readMemArg(pc, body)
if err != nil {
return err
}
if 1<<align > attr.alignMax {
return fmt.Errorf("invalid memory alignment %d for %s", align, vectorInstructionName[vecOpcode])
}
pc += read
if pc >= uint64(len(body)) {
return fmt.Errorf("lane for %s not found", OpcodeVecV128Load64LaneName)
}
lane := body[pc]
if lane >= attr.laneCeil {
return fmt.Errorf("invalid lane index %d >= %d for %s", lane, attr.laneCeil, vectorInstructionName[vecOpcode])
}
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
valueTypeStack.push(ValueTypeV128)
case OpcodeVecV128Store8Lane, OpcodeVecV128Store16Lane, OpcodeVecV128Store32Lane, OpcodeVecV128Store64Lane:
if memory == nil {
return fmt.Errorf("memory must exist for %s", VectorInstructionName(vecOpcode))
}
attr := vecStoreLanes[vecOpcode]
pc++
align, _, read, err := readMemArg(pc, body)
if err != nil {
return err
}
if 1<<align > attr.alignMax {
return fmt.Errorf("invalid memory alignment %d for %s", align, vectorInstructionName[vecOpcode])
}
pc += read
if pc >= uint64(len(body)) {
return fmt.Errorf("lane for %s not found", vectorInstructionName[vecOpcode])
}
lane := body[pc]
if lane >= attr.laneCeil {
return fmt.Errorf("invalid lane index %d >= %d for %s", lane, attr.laneCeil, vectorInstructionName[vecOpcode])
}
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
case OpcodeVecI8x16ExtractLaneS,
OpcodeVecI8x16ExtractLaneU,
OpcodeVecI16x8ExtractLaneS,
OpcodeVecI16x8ExtractLaneU,
OpcodeVecI32x4ExtractLane,
OpcodeVecI64x2ExtractLane,
OpcodeVecF32x4ExtractLane,
OpcodeVecF64x2ExtractLane:
pc++
if pc >= uint64(len(body)) {
return fmt.Errorf("lane for %s not found", vectorInstructionName[vecOpcode])
}
attr := vecExtractLanes[vecOpcode]
lane := body[pc]
if lane >= attr.laneCeil {
return fmt.Errorf("invalid lane index %d >= %d for %s", lane, attr.laneCeil, vectorInstructionName[vecOpcode])
}
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
valueTypeStack.push(attr.resultType)
case OpcodeVecI8x16ReplaceLane, OpcodeVecI16x8ReplaceLane, OpcodeVecI32x4ReplaceLane,
OpcodeVecI64x2ReplaceLane, OpcodeVecF32x4ReplaceLane, OpcodeVecF64x2ReplaceLane:
pc++
if pc >= uint64(len(body)) {
return fmt.Errorf("lane for %s not found", vectorInstructionName[vecOpcode])
}
attr := vecReplaceLanes[vecOpcode]
lane := body[pc]
if lane >= attr.laneCeil {
return fmt.Errorf("invalid lane index %d >= %d for %s", lane, attr.laneCeil, vectorInstructionName[vecOpcode])
}
if err := valueTypeStack.popAndVerifyType(attr.paramType); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
valueTypeStack.push(ValueTypeV128)
case OpcodeVecI8x16Splat, OpcodeVecI16x8Splat, OpcodeVecI32x4Splat,
OpcodeVecI64x2Splat, OpcodeVecF32x4Splat, OpcodeVecF64x2Splat:
tp := vecSplatValueTypes[vecOpcode]
if err := valueTypeStack.popAndVerifyType(tp); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
valueTypeStack.push(ValueTypeV128)
case OpcodeVecI8x16Swizzle, OpcodeVecV128And, OpcodeVecV128Or, OpcodeVecV128Xor, OpcodeVecV128AndNot:
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
valueTypeStack.push(ValueTypeV128)
case OpcodeVecV128Bitselect:
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
valueTypeStack.push(ValueTypeV128)
case OpcodeVecV128Not:
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
valueTypeStack.push(ValueTypeV128)
case OpcodeVecV128i8x16Shuffle:
pc++
if pc+15 >= uint64(len(body)) {
return fmt.Errorf("16 lane indexes for %s not found", vectorInstructionName[vecOpcode])
}
lanes := body[pc : pc+16]
for i, l := range lanes {
if l >= 32 {
return fmt.Errorf("invalid lane index[%d] %d >= %d for %s", i, l, 32, vectorInstructionName[vecOpcode])
}
}
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
valueTypeStack.push(ValueTypeV128)
pc += 15
case OpcodeVecI8x16Shl, OpcodeVecI8x16ShrS, OpcodeVecI8x16ShrU,
OpcodeVecI16x8Shl, OpcodeVecI16x8ShrS, OpcodeVecI16x8ShrU,
OpcodeVecI32x4Shl, OpcodeVecI32x4ShrS, OpcodeVecI32x4ShrU,
OpcodeVecI64x2Shl, OpcodeVecI64x2ShrS, OpcodeVecI64x2ShrU:
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
valueTypeStack.push(ValueTypeV128)
case OpcodeVecI8x16Eq, OpcodeVecI8x16Ne, OpcodeVecI8x16LtS, OpcodeVecI8x16LtU, OpcodeVecI8x16GtS,
OpcodeVecI8x16GtU, OpcodeVecI8x16LeS, OpcodeVecI8x16LeU, OpcodeVecI8x16GeS, OpcodeVecI8x16GeU,
OpcodeVecI16x8Eq, OpcodeVecI16x8Ne, OpcodeVecI16x8LtS, OpcodeVecI16x8LtU, OpcodeVecI16x8GtS,
OpcodeVecI16x8GtU, OpcodeVecI16x8LeS, OpcodeVecI16x8LeU, OpcodeVecI16x8GeS, OpcodeVecI16x8GeU,
OpcodeVecI32x4Eq, OpcodeVecI32x4Ne, OpcodeVecI32x4LtS, OpcodeVecI32x4LtU, OpcodeVecI32x4GtS,
OpcodeVecI32x4GtU, OpcodeVecI32x4LeS, OpcodeVecI32x4LeU, OpcodeVecI32x4GeS, OpcodeVecI32x4GeU,
OpcodeVecI64x2Eq, OpcodeVecI64x2Ne, OpcodeVecI64x2LtS, OpcodeVecI64x2GtS, OpcodeVecI64x2LeS,
OpcodeVecI64x2GeS, OpcodeVecF32x4Eq, OpcodeVecF32x4Ne, OpcodeVecF32x4Lt, OpcodeVecF32x4Gt,
OpcodeVecF32x4Le, OpcodeVecF32x4Ge, OpcodeVecF64x2Eq, OpcodeVecF64x2Ne, OpcodeVecF64x2Lt,
OpcodeVecF64x2Gt, OpcodeVecF64x2Le, OpcodeVecF64x2Ge,
OpcodeVecI32x4DotI16x8S,
OpcodeVecI8x16NarrowI16x8S, OpcodeVecI8x16NarrowI16x8U, OpcodeVecI16x8NarrowI32x4S, OpcodeVecI16x8NarrowI32x4U:
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
valueTypeStack.push(ValueTypeV128)
case OpcodeVecI8x16Neg, OpcodeVecI16x8Neg, OpcodeVecI32x4Neg, OpcodeVecI64x2Neg, OpcodeVecF32x4Neg, OpcodeVecF64x2Neg,
OpcodeVecF32x4Sqrt, OpcodeVecF64x2Sqrt,
OpcodeVecI8x16Abs, OpcodeVecI8x16Popcnt, OpcodeVecI16x8Abs, OpcodeVecI32x4Abs, OpcodeVecI64x2Abs,
OpcodeVecF32x4Abs, OpcodeVecF64x2Abs,
OpcodeVecF32x4Ceil, OpcodeVecF32x4Floor, OpcodeVecF32x4Trunc, OpcodeVecF32x4Nearest,
OpcodeVecF64x2Ceil, OpcodeVecF64x2Floor, OpcodeVecF64x2Trunc, OpcodeVecF64x2Nearest,
OpcodeVecI16x8ExtendLowI8x16S, OpcodeVecI16x8ExtendHighI8x16S, OpcodeVecI16x8ExtendLowI8x16U, OpcodeVecI16x8ExtendHighI8x16U,
OpcodeVecI32x4ExtendLowI16x8S, OpcodeVecI32x4ExtendHighI16x8S, OpcodeVecI32x4ExtendLowI16x8U, OpcodeVecI32x4ExtendHighI16x8U,
OpcodeVecI64x2ExtendLowI32x4S, OpcodeVecI64x2ExtendHighI32x4S, OpcodeVecI64x2ExtendLowI32x4U, OpcodeVecI64x2ExtendHighI32x4U,
OpcodeVecI16x8ExtaddPairwiseI8x16S, OpcodeVecI16x8ExtaddPairwiseI8x16U,
OpcodeVecI32x4ExtaddPairwiseI16x8S, OpcodeVecI32x4ExtaddPairwiseI16x8U,
OpcodeVecF64x2PromoteLowF32x4Zero, OpcodeVecF32x4DemoteF64x2Zero,
OpcodeVecF32x4ConvertI32x4S, OpcodeVecF32x4ConvertI32x4U,
OpcodeVecF64x2ConvertLowI32x4S, OpcodeVecF64x2ConvertLowI32x4U,
OpcodeVecI32x4TruncSatF32x4S, OpcodeVecI32x4TruncSatF32x4U, OpcodeVecI32x4TruncSatF64x2SZero, OpcodeVecI32x4TruncSatF64x2UZero:
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
valueTypeStack.push(ValueTypeV128)
case OpcodeVecI8x16Add, OpcodeVecI8x16AddSatS, OpcodeVecI8x16AddSatU, OpcodeVecI8x16Sub, OpcodeVecI8x16SubSatS, OpcodeVecI8x16SubSatU,
OpcodeVecI16x8Add, OpcodeVecI16x8AddSatS, OpcodeVecI16x8AddSatU, OpcodeVecI16x8Sub, OpcodeVecI16x8SubSatS, OpcodeVecI16x8SubSatU, OpcodeVecI16x8Mul,
OpcodeVecI32x4Add, OpcodeVecI32x4Sub, OpcodeVecI32x4Mul,
OpcodeVecI64x2Add, OpcodeVecI64x2Sub, OpcodeVecI64x2Mul,
OpcodeVecF32x4Add, OpcodeVecF32x4Sub, OpcodeVecF32x4Mul, OpcodeVecF32x4Div,
OpcodeVecF64x2Add, OpcodeVecF64x2Sub, OpcodeVecF64x2Mul, OpcodeVecF64x2Div,
OpcodeVecI8x16MinS, OpcodeVecI8x16MinU, OpcodeVecI8x16MaxS, OpcodeVecI8x16MaxU,
OpcodeVecI8x16AvgrU,
OpcodeVecI16x8MinS, OpcodeVecI16x8MinU, OpcodeVecI16x8MaxS, OpcodeVecI16x8MaxU,
OpcodeVecI16x8AvgrU,
OpcodeVecI32x4MinS, OpcodeVecI32x4MinU, OpcodeVecI32x4MaxS, OpcodeVecI32x4MaxU,
OpcodeVecF32x4Min, OpcodeVecF32x4Max, OpcodeVecF64x2Min, OpcodeVecF64x2Max,
OpcodeVecF32x4Pmin, OpcodeVecF32x4Pmax, OpcodeVecF64x2Pmin, OpcodeVecF64x2Pmax,
OpcodeVecI16x8Q15mulrSatS,
OpcodeVecI16x8ExtMulLowI8x16S, OpcodeVecI16x8ExtMulHighI8x16S, OpcodeVecI16x8ExtMulLowI8x16U, OpcodeVecI16x8ExtMulHighI8x16U,
OpcodeVecI32x4ExtMulLowI16x8S, OpcodeVecI32x4ExtMulHighI16x8S, OpcodeVecI32x4ExtMulLowI16x8U, OpcodeVecI32x4ExtMulHighI16x8U,
OpcodeVecI64x2ExtMulLowI32x4S, OpcodeVecI64x2ExtMulHighI32x4S, OpcodeVecI64x2ExtMulLowI32x4U, OpcodeVecI64x2ExtMulHighI32x4U:
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
}
valueTypeStack.push(ValueTypeV128)
default:
return fmt.Errorf("unknown SIMD instruction %s", vectorInstructionName[vecOpcode])
}
} else if op == OpcodeBlock {
br.Reset(body[pc+1:])
bt, num, err := DecodeBlockType(m.TypeSection, br, enabledFeatures)
if err != nil {
return fmt.Errorf("read block: %w", err)
}
controlBlockStack.push(pc, 0, 0, bt, num, 0)
if err = valueTypeStack.popParams(op, bt.Params, false); err != nil {
return err
}
// Plus we have to push any block params again.
for _, p := range bt.Params {
valueTypeStack.push(p)
}
valueTypeStack.pushStackLimit(len(bt.Params))
pc += num
} else if op == OpcodeAtomicPrefix {
pc++
// Atomic instructions come with two bytes where the first byte is always OpcodeAtomicPrefix,
// and the second byte determines the actual instruction.
atomicOpcode := body[pc]
if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesThreads); err != nil {
return fmt.Errorf("%s invalid as %v", atomicInstructionName[atomicOpcode], err)
}
pc++
if atomicOpcode == OpcodeAtomicFence {
// No memory requirement and no arguments or return, however the immediate byte value must be 0.
imm := body[pc]
if imm != 0x0 {
return fmt.Errorf("invalid immediate value for %s", AtomicInstructionName(atomicOpcode))
}
continue
}
// All atomic operations except fence (checked above) require memory
if memory == nil {
return fmt.Errorf("memory must exist for %s", AtomicInstructionName(atomicOpcode))
}
align, _, read, err := readMemArg(pc, body)
if err != nil {
return err
}
pc += read - 1
switch atomicOpcode {
case OpcodeAtomicMemoryNotify:
if 1<<align > 32/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI32)
case OpcodeAtomicMemoryWait32:
if 1<<align > 32/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI32)
case OpcodeAtomicMemoryWait64:
if 1<<align > 64/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI32)
case OpcodeAtomicI32Load:
if 1<<align > 32/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI32)
case OpcodeAtomicI64Load:
if 1<<align > 64/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI64)
case OpcodeAtomicI32Load8U:
if 1<<align != 1 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI32)
case OpcodeAtomicI32Load16U:
if 1<<align != 16/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI32)
case OpcodeAtomicI64Load8U:
if 1<<align != 1 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI64)
case OpcodeAtomicI64Load16U:
if 1<<align > 16/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI64)
case OpcodeAtomicI64Load32U:
if 1<<align > 32/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI64)
case OpcodeAtomicI32Store:
if 1<<align > 32/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
case OpcodeAtomicI64Store:
if 1<<align > 64/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
case OpcodeAtomicI32Store8:
if 1<<align > 1 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
case OpcodeAtomicI32Store16:
if 1<<align > 16/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
case OpcodeAtomicI64Store8:
if 1<<align > 1 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
case OpcodeAtomicI64Store16:
if 1<<align > 16/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
case OpcodeAtomicI64Store32:
if 1<<align > 32/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
case OpcodeAtomicI32RmwAdd, OpcodeAtomicI32RmwSub, OpcodeAtomicI32RmwAnd, OpcodeAtomicI32RmwOr, OpcodeAtomicI32RmwXor, OpcodeAtomicI32RmwXchg:
if 1<<align > 32/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI32)
case OpcodeAtomicI32Rmw8AddU, OpcodeAtomicI32Rmw8SubU, OpcodeAtomicI32Rmw8AndU, OpcodeAtomicI32Rmw8OrU, OpcodeAtomicI32Rmw8XorU, OpcodeAtomicI32Rmw8XchgU:
if 1<<align > 1 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI32)
case OpcodeAtomicI32Rmw16AddU, OpcodeAtomicI32Rmw16SubU, OpcodeAtomicI32Rmw16AndU, OpcodeAtomicI32Rmw16OrU, OpcodeAtomicI32Rmw16XorU, OpcodeAtomicI32Rmw16XchgU:
if 1<<align > 16/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI32)
case OpcodeAtomicI64RmwAdd, OpcodeAtomicI64RmwSub, OpcodeAtomicI64RmwAnd, OpcodeAtomicI64RmwOr, OpcodeAtomicI64RmwXor, OpcodeAtomicI64RmwXchg:
if 1<<align > 64/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI64)
case OpcodeAtomicI64Rmw8AddU, OpcodeAtomicI64Rmw8SubU, OpcodeAtomicI64Rmw8AndU, OpcodeAtomicI64Rmw8OrU, OpcodeAtomicI64Rmw8XorU, OpcodeAtomicI64Rmw8XchgU:
if 1<<align > 1 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI64)
case OpcodeAtomicI64Rmw16AddU, OpcodeAtomicI64Rmw16SubU, OpcodeAtomicI64Rmw16AndU, OpcodeAtomicI64Rmw16OrU, OpcodeAtomicI64Rmw16XorU, OpcodeAtomicI64Rmw16XchgU:
if 1<<align > 16/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI64)
case OpcodeAtomicI64Rmw32AddU, OpcodeAtomicI64Rmw32SubU, OpcodeAtomicI64Rmw32AndU, OpcodeAtomicI64Rmw32OrU, OpcodeAtomicI64Rmw32XorU, OpcodeAtomicI64Rmw32XchgU:
if 1<<align > 32/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI64)
case OpcodeAtomicI32RmwCmpxchg:
if 1<<align > 32/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI32)
case OpcodeAtomicI32Rmw8CmpxchgU:
if 1<<align > 1 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI32)
case OpcodeAtomicI32Rmw16CmpxchgU:
if 1<<align > 16/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI32)
case OpcodeAtomicI64RmwCmpxchg:
if 1<<align > 64/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI64)
case OpcodeAtomicI64Rmw8CmpxchgU:
if 1<<align > 1 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI64)
case OpcodeAtomicI64Rmw16CmpxchgU:
if 1<<align > 16/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI64)
case OpcodeAtomicI64Rmw32CmpxchgU:
if 1<<align > 32/8 {
return fmt.Errorf("invalid memory alignment")
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
return err
}
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return err
}
valueTypeStack.push(ValueTypeI64)
default:
return fmt.Errorf("invalid atomic opcode: 0x%x", atomicOpcode)
}
} else if op == OpcodeLoop {
br.Reset(body[pc+1:])
bt, num, err := DecodeBlockType(m.TypeSection, br, enabledFeatures)
if err != nil {
return fmt.Errorf("read block: %w", err)
}
controlBlockStack.push(pc, 0, 0, bt, num, op)
if err = valueTypeStack.popParams(op, bt.Params, false); err != nil {
return err
}
// Plus we have to push any block params again.
for _, p := range bt.Params {
valueTypeStack.push(p)
}
valueTypeStack.pushStackLimit(len(bt.Params))
pc += num
} else if op == OpcodeIf {
br.Reset(body[pc+1:])
bt, num, err := DecodeBlockType(m.TypeSection, br, enabledFeatures)
if err != nil {
return fmt.Errorf("read block: %w", err)
}
controlBlockStack.push(pc, 0, 0, bt, num, op)
if err = valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("cannot pop the operand for 'if': %v", err)
}
if err = valueTypeStack.popParams(op, bt.Params, false); err != nil {
return err
}
// Plus we have to push any block params again.
for _, p := range bt.Params {
valueTypeStack.push(p)
}
valueTypeStack.pushStackLimit(len(bt.Params))
pc += num
} else if op == OpcodeElse {
bl := &controlBlockStack.stack[len(controlBlockStack.stack)-1]
if bl.op != OpcodeIf {
return fmt.Errorf("else instruction must be used in if block: %#x", pc)
}
bl.op = OpcodeElse
bl.elseAt = pc
// Check the type soundness of the instructions *before* entering this else Op.
if err := valueTypeStack.popResults(OpcodeIf, bl.blockType.Results, true); err != nil {
return err
}
// Before entering instructions inside else, we pop all the values pushed by then block.
valueTypeStack.resetAtStackLimit()
// Plus we have to push any block params again.
for _, p := range bl.blockType.Params {
valueTypeStack.push(p)
}
} else if op == OpcodeEnd {
bl := controlBlockStack.pop()
bl.endAt = pc
// OpcodeEnd can end a block or the function itself. Check to see what it is:
ifMissingElse := bl.op == OpcodeIf && bl.elseAt <= bl.startAt
if ifMissingElse {
// If this is the end of block without else, the number of block's results and params must be same.
// Otherwise, the value stack would result in the inconsistent state at runtime.
if !bytes.Equal(bl.blockType.Results, bl.blockType.Params) {
return typeCountError(false, OpcodeElseName, bl.blockType.Params, bl.blockType.Results)
}
// -1 skips else, to handle if block without else properly.
bl.elseAt = bl.endAt - 1
}
// Determine the block context
ctx := "" // the outer-most block: the function return
if bl.op == OpcodeIf && !ifMissingElse && bl.elseAt > 0 {
ctx = OpcodeElseName
} else if bl.op != 0 {
ctx = InstructionName(bl.op)
}
// Check return types match
if err := valueTypeStack.requireStackValues(false, ctx, bl.blockType.Results, true); err != nil {
return err
}
// Put the result types at the end after resetting at the stack limit
// since we might have Any type between the limit and the current top.
valueTypeStack.resetAtStackLimit()
for _, exp := range bl.blockType.Results {
valueTypeStack.push(exp)
}
// We exit if/loop/block, so reset the constraints on the stack manipulation
// on values previously pushed by outer blocks.
valueTypeStack.popStackLimit()
} else if op == OpcodeReturn {
// Same formatting as OpcodeEnd on the outer-most block
if err := valueTypeStack.requireStackValues(false, "", functionType.Results, false); err != nil {
return err
}
// return instruction is stack-polymorphic.
valueTypeStack.unreachable()
} else if op == OpcodeDrop {
_, err := valueTypeStack.pop()
if err != nil {
return fmt.Errorf("invalid drop: %v", err)
}
} else if op == OpcodeSelect || op == OpcodeTypedSelect {
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
return fmt.Errorf("type mismatch on 3rd select operand: %v", err)
}
v1, err := valueTypeStack.pop()
if err != nil {
return fmt.Errorf("invalid select: %v", err)
}
v2, err := valueTypeStack.pop()
if err != nil {
return fmt.Errorf("invalid select: %v", err)
}
if op == OpcodeTypedSelect {
if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
return fmt.Errorf("%s is invalid as %w", InstructionName(op), err)
}
pc++
if numTypeImmeidates := body[pc]; numTypeImmeidates != 1 {
return fmt.Errorf("too many type immediates for %s", InstructionName(op))
}
pc++
tp := body[pc]
if tp != ValueTypeI32 && tp != ValueTypeI64 && tp != ValueTypeF32 && tp != ValueTypeF64 &&
tp != api.ValueTypeExternref && tp != ValueTypeFuncref && tp != ValueTypeV128 {
return fmt.Errorf("invalid type %s for %s", ValueTypeName(tp), OpcodeTypedSelectName)
}
} else if isReferenceValueType(v1) || isReferenceValueType(v2) {
return fmt.Errorf("reference types cannot be used for non typed select instruction")
}
if v1 != v2 && v1 != valueTypeUnknown && v2 != valueTypeUnknown {
return fmt.Errorf("type mismatch on 1st and 2nd select operands")
}
if v1 == valueTypeUnknown {
valueTypeStack.push(v2)
} else {
valueTypeStack.push(v1)
}
} else if op == OpcodeUnreachable {
// unreachable instruction is stack-polymorphic.
valueTypeStack.unreachable()
} else if op == OpcodeNop {
} else if enabledFeatures.IsEnabled(experimental.CoreFeaturesExceptionHandling) &&
(op == OpcodeLegacyTry || op == OpcodeLegacyCatch || op == OpcodeLegacyRethrow ||
op == OpcodeLegacyDelegate || op == OpcodeLegacyCatchAll) {
return fmt.Errorf("legacy exception handling instruction 0x%x not supported; recompile with wasm-opt --translate-to-exnref", op)
} else {
return fmt.Errorf("invalid instruction 0x%x", op)
}
}
if len(controlBlockStack.stack) > 0 {
return fmt.Errorf("ill-nested block exists")
}
if valueTypeStack.maximumStackPointer > maxStackValues {
return fmt.Errorf("function may have %d stack values, which exceeds limit %d", valueTypeStack.maximumStackPointer, maxStackValues)
}
return nil
}
var vecExtractLanes = [...]struct {
laneCeil byte
resultType ValueType
}{
OpcodeVecI8x16ExtractLaneS: {laneCeil: 16, resultType: ValueTypeI32},
OpcodeVecI8x16ExtractLaneU: {laneCeil: 16, resultType: ValueTypeI32},
OpcodeVecI16x8ExtractLaneS: {laneCeil: 8, resultType: ValueTypeI32},
OpcodeVecI16x8ExtractLaneU: {laneCeil: 8, resultType: ValueTypeI32},
OpcodeVecI32x4ExtractLane: {laneCeil: 4, resultType: ValueTypeI32},
OpcodeVecI64x2ExtractLane: {laneCeil: 2, resultType: ValueTypeI64},
OpcodeVecF32x4ExtractLane: {laneCeil: 4, resultType: ValueTypeF32},
OpcodeVecF64x2ExtractLane: {laneCeil: 2, resultType: ValueTypeF64},
}
var vecReplaceLanes = [...]struct {
laneCeil byte
paramType ValueType
}{
OpcodeVecI8x16ReplaceLane: {laneCeil: 16, paramType: ValueTypeI32},
OpcodeVecI16x8ReplaceLane: {laneCeil: 8, paramType: ValueTypeI32},
OpcodeVecI32x4ReplaceLane: {laneCeil: 4, paramType: ValueTypeI32},
OpcodeVecI64x2ReplaceLane: {laneCeil: 2, paramType: ValueTypeI64},
OpcodeVecF32x4ReplaceLane: {laneCeil: 4, paramType: ValueTypeF32},
OpcodeVecF64x2ReplaceLane: {laneCeil: 2, paramType: ValueTypeF64},
}
var vecStoreLanes = [...]struct {
alignMax uint32
laneCeil byte
}{
OpcodeVecV128Store64Lane: {alignMax: 64 / 8, laneCeil: 128 / 64},
OpcodeVecV128Store32Lane: {alignMax: 32 / 8, laneCeil: 128 / 32},
OpcodeVecV128Store16Lane: {alignMax: 16 / 8, laneCeil: 128 / 16},
OpcodeVecV128Store8Lane: {alignMax: 1, laneCeil: 128 / 8},
}
var vecLoadLanes = [...]struct {
alignMax uint32
laneCeil byte
}{
OpcodeVecV128Load64Lane: {alignMax: 64 / 8, laneCeil: 128 / 64},
OpcodeVecV128Load32Lane: {alignMax: 32 / 8, laneCeil: 128 / 32},
OpcodeVecV128Load16Lane: {alignMax: 16 / 8, laneCeil: 128 / 16},
OpcodeVecV128Load8Lane: {alignMax: 1, laneCeil: 128 / 8},
}
var vecSplatValueTypes = [...]ValueType{
OpcodeVecI8x16Splat: ValueTypeI32,
OpcodeVecI16x8Splat: ValueTypeI32,
OpcodeVecI32x4Splat: ValueTypeI32,
OpcodeVecI64x2Splat: ValueTypeI64,
OpcodeVecF32x4Splat: ValueTypeF32,
OpcodeVecF64x2Splat: ValueTypeF64,
}
type stacks struct {
vs valueTypeStack
cs controlBlockStack
// ls is the label slice that is reused for each br_table instruction.
ls []uint32
}
func (sts *stacks) reset(functionType *FunctionType) {
// Reset valueStack for reuse.
sts.vs.stack = sts.vs.stack[:0]
sts.vs.stackLimits = sts.vs.stackLimits[:0]
sts.vs.maximumStackPointer = 0
sts.cs.stack = sts.cs.stack[:0]
sts.cs.stack = append(sts.cs.stack, controlBlock{blockType: functionType})
sts.ls = sts.ls[:0]
}
type controlBlockStack struct {
stack []controlBlock
}
func (s *controlBlockStack) pop() *controlBlock {
tail := len(s.stack) - 1
ret := &s.stack[tail]
s.stack = s.stack[:tail]
return ret
}
func (s *controlBlockStack) push(startAt, elseAt, endAt uint64, blockType *FunctionType, blockTypeBytes uint64, op Opcode) {
s.stack = append(s.stack, controlBlock{
startAt: startAt,
elseAt: elseAt,
endAt: endAt,
blockType: blockType,
blockTypeBytes: blockTypeBytes,
op: op,
})
}
type valueTypeStack struct {
stack []ValueType
stackLimits []int
maximumStackPointer int
// requireStackValuesTmp is used in requireStackValues function to reduce the allocation.
requireStackValuesTmp []ValueType
}
// Only used in the analyzeFunction below.
const valueTypeUnknown = ValueType(0xFF)
func (s *valueTypeStack) tryPop() (vt ValueType, limit int, ok bool) {
if len(s.stackLimits) > 0 {
limit = s.stackLimits[len(s.stackLimits)-1]
}
stackLen := len(s.stack)
if stackLen <= limit {
return
} else if stackLen == limit+1 && s.stack[limit] == valueTypeUnknown {
vt = valueTypeUnknown
ok = true
return
} else {
vt = s.stack[stackLen-1]
s.stack = s.stack[:stackLen-1]
ok = true
return
}
}
func (s *valueTypeStack) pop() (ValueType, error) {
if vt, limit, ok := s.tryPop(); ok {
return vt, nil
} else {
return 0, fmt.Errorf("invalid operation: trying to pop at %d with limit %d", len(s.stack), limit)
}
}
// popAndVerifyType returns an error if the stack value is unexpected.
func (s *valueTypeStack) popAndVerifyType(expected ValueType) error {
have, _, ok := s.tryPop()
if !ok {
return fmt.Errorf("%s missing", ValueTypeName(expected))
}
if have != expected && have != valueTypeUnknown && expected != valueTypeUnknown && !isRefSubtypeOf(have, expected) {
return fmt.Errorf("type mismatch: expected %s, but was %s", ValueTypeName(expected), ValueTypeName(have))
}
return nil
}
func (s *valueTypeStack) push(v ValueType) {
s.stack = append(s.stack, v)
if sp := len(s.stack); sp > s.maximumStackPointer {
s.maximumStackPointer = sp
}
}
func (s *valueTypeStack) unreachable() {
s.resetAtStackLimit()
s.stack = append(s.stack, valueTypeUnknown)
}
func (s *valueTypeStack) resetAtStackLimit() {
if len(s.stackLimits) != 0 {
s.stack = s.stack[:s.stackLimits[len(s.stackLimits)-1]]
} else {
s.stack = s.stack[:0]
}
}
func (s *valueTypeStack) popStackLimit() {
if len(s.stackLimits) != 0 {
s.stackLimits = s.stackLimits[:len(s.stackLimits)-1]
}
}
// pushStackLimit pushes the control frame's bottom of the stack.
func (s *valueTypeStack) pushStackLimit(params int) {
limit := len(s.stack) - params
s.stackLimits = append(s.stackLimits, limit)
}
func (s *valueTypeStack) popParams(oc Opcode, want []ValueType, checkAboveLimit bool) error {
return s.requireStackValues(true, InstructionName(oc), want, checkAboveLimit)
}
func (s *valueTypeStack) popResults(oc Opcode, want []ValueType, checkAboveLimit bool) error {
return s.requireStackValues(false, InstructionName(oc), want, checkAboveLimit)
}
func (s *valueTypeStack) requireStackValues(
isParam bool,
context string,
want []ValueType,
checkAboveLimit bool,
) error {
limit := 0
if len(s.stackLimits) > 0 {
limit = s.stackLimits[len(s.stackLimits)-1]
}
// Iterate backwards as we are comparing the desired slice against stack value types.
countWanted := len(want)
// First, check if there are enough values on the stack.
s.requireStackValuesTmp = s.requireStackValuesTmp[:0]
for i := countWanted - 1; i >= 0; i-- {
popped, _, ok := s.tryPop()
if !ok {
if len(s.requireStackValuesTmp) > len(want) {
return typeCountError(isParam, context, s.requireStackValuesTmp, want)
}
return typeCountError(isParam, context, s.requireStackValuesTmp, want)
}
s.requireStackValuesTmp = append(s.requireStackValuesTmp, popped)
}
// Now, check if there are too many values.
if checkAboveLimit {
if !(limit == len(s.stack) || (limit+1 == len(s.stack) && s.stack[limit] == valueTypeUnknown)) {
return typeCountError(isParam, context, append(s.stack, want...), want)
}
}
// Finally, check the types of the values:
for i, v := range s.requireStackValuesTmp {
nextWant := want[countWanted-i-1] // have is in reverse order (stack)
if v != nextWant && v != valueTypeUnknown && nextWant != valueTypeUnknown && !isRefSubtypeOf(v, nextWant) {
return typeMismatchError(isParam, context, v, nextWant, i)
}
}
return nil
}
// typeMismatchError returns an error similar to go compiler's error on type mismatch.
func typeMismatchError(isParam bool, context string, have ValueType, want ValueType, i int) error {
var ret strings.Builder
ret.WriteString("cannot use ")
ret.WriteString(ValueTypeName(have))
if context != "" {
ret.WriteString(" in ")
ret.WriteString(context)
ret.WriteString(" block")
}
if isParam {
ret.WriteString(" as param")
} else {
ret.WriteString(" as result")
}
ret.WriteString("[")
ret.WriteString(strconv.Itoa(i))
ret.WriteString("] type ")
ret.WriteString(ValueTypeName(want))
return errors.New(ret.String())
}
// typeCountError returns an error similar to go compiler's error on type count mismatch.
func typeCountError(isParam bool, context string, have []ValueType, want []ValueType) error {
var ret strings.Builder
if len(have) > len(want) {
ret.WriteString("too many ")
} else {
ret.WriteString("not enough ")
}
if isParam {
ret.WriteString("params")
} else {
ret.WriteString("results")
}
if context != "" {
if isParam {
ret.WriteString(" for ")
} else {
ret.WriteString(" in ")
}
ret.WriteString(context)
ret.WriteString(" block")
}
ret.WriteString("\n\thave (")
writeValueTypes(have, &ret)
ret.WriteString(")\n\twant (")
writeValueTypes(want, &ret)
ret.WriteByte(')')
return errors.New(ret.String())
}
func writeValueTypes(vts []ValueType, ret *strings.Builder) {
switch len(vts) {
case 0:
case 1:
ret.WriteString(ValueTypeName(vts[0]))
default:
ret.WriteString(ValueTypeName(vts[0]))
for _, vt := range vts[1:] {
ret.WriteString(", ")
ret.WriteString(ValueTypeName(vt))
}
}
}
func (s *valueTypeStack) String() string {
var typeStrs, limits []string
for _, v := range s.stack {
var str string
if v == valueTypeUnknown {
str = "unknown"
} else {
str = ValueTypeName(v)
}
typeStrs = append(typeStrs, str)
}
for _, d := range s.stackLimits {
limits = append(limits, fmt.Sprintf("%d", d))
}
return fmt.Sprintf("{stack: [%s], limits: [%s]}",
strings.Join(typeStrs, ", "), strings.Join(limits, ","))
}
type controlBlock struct {
startAt, elseAt, endAt uint64
blockType *FunctionType
blockTypeBytes uint64
// op is zero when the outermost block
op Opcode
}
// DecodeBlockType decodes the type index from a positive 33-bit signed integer. Negative numbers indicate up to one
// WebAssembly 1.0 (20191205) compatible result type. Positive numbers are decoded when `enabledFeatures` include
// CoreFeatureMultiValue and include an index in the Module.TypeSection.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-blocktype
// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/multi-value/Overview.md
func DecodeBlockType(types []FunctionType, r *bytes.Reader, enabledFeatures api.CoreFeatures) (*FunctionType, uint64, error) {
raw, num, err := leb128.DecodeInt33AsInt64(r)
if err != nil {
return nil, 0, fmt.Errorf("decode int33: %w", err)
}
var ret *FunctionType
switch raw {
case -64: // 0x40 in original byte = nil
ret = blockType_v_v
case -1: // 0x7f in original byte = i32
ret = blockType_v_i32
case -2: // 0x7e in original byte = i64
ret = blockType_v_i64
case -3: // 0x7d in original byte = f32
ret = blockType_v_f32
case -4: // 0x7c in original byte = f64
ret = blockType_v_f64
case -5: // 0x7b in original byte = v128
ret = blockType_v_v128
case -16: // 0x70 in original byte = funcref
ret = blockType_v_funcref
case -17: // 0x6f in original byte = externref
ret = blockType_v_externref
case -23: // 0x69 in original byte = exnref
ret = blockType_v_exnref
case -29: // 0x63 = ref null (nullable) — GC proposal
ht, htNum, err := leb128.DecodeInt33AsInt64(r)
if err != nil {
return nil, 0, fmt.Errorf("read ref heap type in block: %w", err)
}
num += htNum
switch ht {
case -23: // exn
ret = blockType_v_exnref
case -16: // func
ret = blockType_v_funcref
case -17: // extern
ret = blockType_v_externref
default: // concrete type index — treat as nullable funcref
ret = blockType_v_funcref
}
case -28: // 0x64 = ref (non-nullable) — GC proposal
ht, htNum, err := leb128.DecodeInt33AsInt64(r)
if err != nil {
return nil, 0, fmt.Errorf("read ref heap type in block: %w", err)
}
num += htNum
switch ht {
case -23: // exn
ret = blockType_v_exnref // TODO: non-null exnref
default:
ret = blockType_v_funcref
}
default:
if err = enabledFeatures.RequireEnabled(api.CoreFeatureMultiValue); err != nil {
return nil, num, fmt.Errorf("block with function type return invalid as %v", err)
}
if raw < 0 || (raw >= int64(len(types))) {
return nil, 0, fmt.Errorf("type index out of range: %d", raw)
}
ret = &types[raw]
}
return ret, num, err
}
// These block types are defined as globals in order to avoid allocations in DecodeBlockType.
var (
blockType_v_v = &FunctionType{}
blockType_v_i32 = &FunctionType{Results: []ValueType{ValueTypeI32}, ResultNumInUint64: 1}
blockType_v_i64 = &FunctionType{Results: []ValueType{ValueTypeI64}, ResultNumInUint64: 1}
blockType_v_f32 = &FunctionType{Results: []ValueType{ValueTypeF32}, ResultNumInUint64: 1}
blockType_v_f64 = &FunctionType{Results: []ValueType{ValueTypeF64}, ResultNumInUint64: 1}
blockType_v_v128 = &FunctionType{Results: []ValueType{ValueTypeV128}, ResultNumInUint64: 2}
blockType_v_funcref = &FunctionType{Results: []ValueType{ValueTypeFuncref}, ResultNumInUint64: 1}
blockType_v_externref = &FunctionType{Results: []ValueType{ValueTypeExternref}, ResultNumInUint64: 1}
blockType_v_exnref = &FunctionType{Results: []ValueType{ValueTypeExnref}, ResultNumInUint64: 1}
)
// SplitCallStack returns the input stack resliced to the count of params and
// results, or errors if it isn't long enough for either.
func SplitCallStack(ft *FunctionType, stack []uint64) (params []uint64, results []uint64, err error) {
stackLen := len(stack)
if n := ft.ParamNumInUint64; n > stackLen {
return nil, nil, fmt.Errorf("need %d params, but stack size is %d", n, stackLen)
} else if n > 0 {
params = stack[:n]
}
if n := ft.ResultNumInUint64; n > stackLen {
return nil, nil, fmt.Errorf("need %d results, but stack size is %d", n, stackLen)
} else if n > 0 {
results = stack[:n]
}
return
}