mirror of
https://github.com/wazero/wazero
synced 2026-06-21 14:12:37 +00:00
bfb20e0ba7
Add experimental support to the Exception Handling spec. Exception handling adds structured error propagation to WebAssembly through `tags` (typed exception signatures), `throw/throw_ref` (raising exceptions), and `try_table` (catching exceptions with typed `catch` clauses). A new value type, `exnref`, represents a reference to a caught exception. Excluding tests and spec suites, the entire feature amounts to a little less than 2000 lines of code. Feature flag: `experimental.CoreFeaturesExceptionHandling` ## What's the use for this? In the spirit of always having a real use case for the specs we implement, I did check this implementation against the one large Wasm C++ codebase I am aware about, i.e. https://github.com/klippa-app/go-pdfium, and I verified that the entire suite, compiled with `emcc+wasm-opt` does work; incidentally, much of the existing support code, based on Emscripten's longjmp/setjmp, can be dropped (thx @jerbob92 for the help!) Broadly speaking, I don't know how much EH-enabled Wasm code you will find in the wild. For instance, code compiled using Clang's C++ backend does not really throw new-style exceptions, unless you compile WASI-SDK with some flags, or you build with `emcc` and translate old-style exceptions using `wasm-opt` (see exceptions_test.go) In short, this spec should be really seen as a step towards supporting the GC spec. The design decisions (esp. regarding the test suite) follow from this reasoning. ## Spec Suite `wast2json` **does not** support the EH suite, but `wasm-tools` does, albeit with a tiny quirk in the way negative numbers are rendered. In this PR _I am not_ moving the other suites to `wasm-tools` though. The spec test suite passes entirely, except for two tests that are intentionally skipped. These tests assume **a very small subset** of typed references is implemented; specifically, the distinction between nullable and non-nullable function references (`(ref null $t)` vs `(ref $t)`). I have decided to just flatten all non-nullable refs to nullable refs at decode time, so we cannot detect the type mismatch the spec expects in these cases. Passing the **full suite is actually straightforward** because it only requires introducing a non-nullable variant for each ref type: strictly speaking the 2 failing tests are only for related nullable vs non-nullable **func refs** -- so I can special-case these and/or add non-nullable types for each known value; however, we will **need** to rethink the representation of Values for the GC proposal anyway (because this will introduce custom type indices, and these won't fit our currently byte-sized `ValueType` enum). The subtype-checking functions (`isRefSubtypeOf`, `isStrictRefSubtypeOf`) are kept as abstraction points but currently they are just equalities. Nevertheless, as proven by the CPP example and https://github.com/klippa-app/go-pdfium this PR provides a starting point that is already useful (even if it is for limited cases). ## Interpreter Exception handling in the interpreter: **Within a frame**, `try_table` blocks are compiled into a static exception table with PC ranges. When `throw` or `throw_ref` executes, `searchExceptionTable` scans backwards (inner handlers first) for a catch clause whose PC range covers the current instruction. If a match is found, `applyExceptionHandler` adjusts the operand stack depth and jumps to the catch target PC directly. **Across frames**, if no handler is found in the current frame, `panic(&thrownException{...})` propagates up the Go call stack. Each cross-function call goes through `callWithUnwind()`, which uses `defer/recover` to intercept the panic. Its `canRestore` method unwinds `ce.frames` to the caller's depth and searches that frame's exception table. If a handler is found, `doRestore` applies it and `callWithUnwind` returns `true` (frame unwound -- caller refreshes locals and continues). If not, the panic re-propagates to the next outer `callWithUnwind`. Note on the implementation: this mechanism is unified with the existing `snapshot/restore` API through the `restorable` interface: both `*thrownException` and `*snapshot` implement `canRestore`/`doRestore`, so `callWithUnwind` handles both with a single defer/recover path. The short-circuit check (`len(exceptionTable) == 0 && no snapshotter`) skips the defer/recover overhead entirely for frames that don't need it. ## Compiler The compiler could not reuse the snapshot/restore mechanism directly, but it follows a similar pattern. One cool thing is that exception handling could be implemented entirely as an SSA-level lowering with new exit codes; there is no backend-specific code. Throwing uses a two-phase ABI: 1. `ExitCodeThrowAlloc`: exits to the Go runtime to allocate an `Exception` struct on the heap and writes a pointer to its params array into the execution context. Then we return control to compiled code, which stores the tag parameters directly into the struct. 2. `ExitCodeThrow`: exits to the Go runtime again to search for a matching handler, restore the stack checkpoint, and branch to the catch target. The idea is that we NEED to allocate the parameter slice dynamically, so we delegate to the Go runtime. Incidentally, this also allows us to unify `throw` and `throwRef`; in the latter case, we skip the `alloc` phase, and we exit straight to `ExitCodeThrow`. ### Trampolines The compiler uses four trampolines to return to host to manage exception state: | Trampoline | Exit Code | Purpose | |---|---|---| | **ThrowAlloc** | `ExitCodeThrowAlloc` | Allocates an `Exception` struct on the Go heap, writes `&exn.Params[0]` into `execCtx` so compiled code can store tag parameters directly. | | **Throw** | `ExitCodeThrow` | Searches `tryHandlers` for a matching catch clause, restores the stack checkpoint on match, sets `caughtExceptionClauseIdx` for the compiled dispatch. Used for both `throw` (after ThrowAlloc) and `throw_ref` (directly). | | **TryTableEnter** | `ExitCodeTryTableEnter` | Clones the current stack as a checkpoint, pushes a `tryHandler` with the catch clause table and saved module instance. Sets `caughtExceptionClauseIdx = -1` (no exception). | | **TryTableLeave** | `ExitCodeTryTableLeave` | Pops the most recent `tryHandler`. Emitted at `try_table` block ends, and before any branch or return that exits a `try_table` scope. | ### Lowering As mentioned earlier, this entirely resolved at the SSA-level. In particular: - **Entry**: when a `try_table` is encountered, the compiler emits a call to the TryTableEnter trampoline. Control returns to compiled code, which reads `caughtExceptionClauseIdx` from the execution context — if it's -1 (no exception), execution continues into the `try_table` body. - **Normal exit**: when execution reaches the `End` opcode of the `try_table` block, a TryTableLeave trampoline call is emitted. However, every TryTableEnter must have a matching TryTableLeave on every control flow path that doesn't throw; so we also "TryTableLeaves" also for the following: - **Early exit (branch)**: a `br`, `br_if`, or `br_table` can jump to a label outside the `try_table`, skipping its `End`. `emitTryTableLeaves(depth)` walks the control frame stack and emits one TryTableLeave for each `try_table` frame the branch crosses. For `br_if`, this happens in a trampoline basic block that only executes on the taken path. - **Early exit (return)**: `return`, `return_call`, and `return_call_indirect` call `emitTryTableLeaves` with the full control stack depth, popping all active handlers before leaving the frame. - **Throw**: on `throw`/`throw_ref`, compiled code exits to the dispatch loop via ExitCodeThrow. `doHandleException` searches `tryHandlers` innermost-to-outermost. On match, it restores the cloned stack checkpoint, sets `caughtExceptionClauseIdx`, and re-enters compiled code at the handler's return address. The compiled dispatch branches to the matching catch clause's handler block. ### Caveats - As described above, we are skipping 2 spec tests (will be addressed when we implement typed references) - Because of that, I am _not_ enabling by default the fuzzer: I would have to special-case for typed references and/or TODOs I have left behind; I did run the fuzzer with some special-casing while debugging the issue with pdfium (#2488) and did not find issues (the fuzzer did go frequently OOM though); in fact, the pdfium issue was actually unrelated (!) --------- Signed-off-by: Edoardo Vacchi <evacchi@users.noreply.github.com>
2553 lines
96 KiB
Go
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
|
|
}
|