/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 *
 * Copyright 2016 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef wasm_binary_iterator_h
#define wasm_binary_iterator_h

#include "mozilla/Poison.h"

#include "jsprf.h"

#include "jit/AtomicOp.h"
#include "wasm/WasmBinaryFormat.h"

namespace js {
namespace wasm {

// The kind of a control-flow stack item.
enum class LabelKind : uint8_t {
    Block,
    Loop,
    Then,
    UnreachableThen, // like Then, but not reachable
    Else
};

#ifdef DEBUG
// Families of opcodes that share a signature and validation logic.
enum class OpKind {
    Block,
    Loop,
    Unreachable,
    Drop,
    I32,
    I64,
    F32,
    F64,
    I8x16,
    I16x8,
    I32x4,
    F32x4,
    B8x16,
    B16x8,
    B32x4,
    Br,
    BrIf,
    BrTable,
    Nop,
    Nullary,
    Unary,
    Binary,
    Comparison,
    Conversion,
    Load,
    Store,
    TeeStore,
    CurrentMemory,
    GrowMemory,
    Select,
    GetLocal,
    SetLocal,
    TeeLocal,
    GetGlobal,
    SetGlobal,
    TeeGlobal,
    Call,
    CallIndirect,
    OldCallIndirect,
    Return,
    If,
    Else,
    End,
    AtomicLoad,
    AtomicStore,
    AtomicBinOp,
    AtomicCompareExchange,
    AtomicExchange,
    ExtractLane,
    ReplaceLane,
    Swizzle,
    Shuffle,
    Splat,
    SimdSelect,
    SimdCtor,
    SimdBooleanReduction,
    SimdShiftByScalar,
    SimdComparison,
};

// Return the OpKind for a given Op. This is used for sanity-checking that
// API users use the correct read function for a given Op.
OpKind
Classify(Op op);
#endif

// Common fields for linear memory access.
template <typename Value>
struct LinearMemoryAddress
{
    Value base;
    uint32_t offset;
    uint32_t align;

    LinearMemoryAddress()
    {}
    LinearMemoryAddress(Value base, uint32_t offset, uint32_t align)
      : base(base), offset(offset), align(align)
    {}
};

template <typename ControlItem>
class ControlStackEntry
{
    LabelKind kind_;
    bool reachable_;
    ExprType type_;
    size_t valueStackStart_;
    ControlItem controlItem_;

  public:
    ControlStackEntry(LabelKind kind, ExprType type, bool reachable, size_t valueStackStart)
      : kind_(kind), reachable_(reachable), type_(type), valueStackStart_(valueStackStart),
        controlItem_()
    {
        MOZ_ASSERT(type != ExprType::Limit);
    }

    LabelKind kind() const { return kind_; }
    ExprType type() const { return type_; }
    bool reachable() const { return reachable_; }
    size_t valueStackStart() const { return valueStackStart_; }
    ControlItem& controlItem() { return controlItem_; }

    void setReachable() { reachable_ = true; }

    void switchToElse(bool reachable) {
        MOZ_ASSERT(kind_ == LabelKind::Then || kind_ == LabelKind::UnreachableThen);
        reachable_ = reachable;
        kind_ = LabelKind::Else;
        controlItem_ = ControlItem();
    }
};

// Specialization for when there is no additional data needed.
template <>
class ControlStackEntry<Nothing>
{
    LabelKind kind_;
    bool reachable_;
    ExprType type_;
    size_t valueStackStart_;

  public:
    ControlStackEntry(LabelKind kind, ExprType type, bool reachable, size_t valueStackStart)
      : kind_(kind), reachable_(reachable), type_(type), valueStackStart_(valueStackStart)
    {
        MOZ_ASSERT(type != ExprType::Limit);
    }

    LabelKind kind() const { return kind_; }
    ExprType type() const { return type_; }
    bool reachable() const { return reachable_; }
    size_t valueStackStart() const { return valueStackStart_; }
    Nothing controlItem() { return Nothing(); }

    void setReachable() { reachable_ = true; }

    void switchToElse(bool reachable) {
        MOZ_ASSERT(kind_ == LabelKind::Then || kind_ == LabelKind::UnreachableThen);
        reachable_ = reachable;
        kind_ = LabelKind::Else;
    }
};

template <typename Value>
class TypeAndValue
{
    ValType type_;
    Value value_;

  public:
    TypeAndValue() : type_(ValType(TypeCode::Limit)), value_() {}
    explicit TypeAndValue(ValType type)
      : type_(type), value_()
    {}
    TypeAndValue(ValType type, Value value)
      : type_(type), value_(value)
    {}
    ValType type() const {
        return type_;
    }
    Value value() const {
        return value_;
    }
    void setValue(Value value) {
        value_ = value;
    }
};

// Specialization for when there is no additional data needed.
template <>
class TypeAndValue<Nothing>
{
    ValType type_;

  public:
    TypeAndValue() : type_(ValType(TypeCode::Limit)) {}
    explicit TypeAndValue(ValType type) : type_(type) {}

    TypeAndValue(ValType type, Nothing value)
      : type_(type)
    {}

    ValType type() const { return type_; }
    Nothing value() const { return Nothing(); }
    void setValue(Nothing value) {}
};

// A policy class for configuring OpIter. Clients can use this as a
// base class, and override the behavior as needed.
struct OpIterPolicy
{
    // Should the iterator perform validation, such as type checking and
    // validity checking?
    static const bool Validate = false;

    // Should the iterator produce output values?
    static const bool Output = false;

    // These members allow clients to add additional information to the value
    // and control stacks, respectively. Using Nothing means that no additional
    // field is added.
    typedef Nothing Value;
    typedef Nothing ControlItem;
};

// An iterator over the bytes of a function body. It performs validation
// (if Policy::Validate is true) and unpacks the data into a usable form.
//
// The MOZ_STACK_CLASS attribute here is because of the use of DebugOnly.
// There's otherwise nothing inherent in this class which would require
// it to be used on the stack.
template <typename Policy>
class MOZ_STACK_CLASS OpIter : private Policy
{
    static const bool Validate = Policy::Validate;
    static const bool Output = Policy::Output;
    typedef typename Policy::Value Value;
    typedef typename Policy::ControlItem ControlItem;

    Decoder& d_;
    const size_t offsetInModule_;

    Vector<TypeAndValue<Value>, 8, SystemAllocPolicy> valueStack_;
    Vector<ControlStackEntry<ControlItem>, 8, SystemAllocPolicy> controlStack_;
    bool reachable_;

    DebugOnly<Op> op_;
    size_t offsetOfExpr_;

    MOZ_MUST_USE bool readFixedU8(uint8_t* out) {
        if (Validate)
            return d_.readFixedU8(out);
        *out = d_.uncheckedReadFixedU8();
        return true;
    }
    MOZ_MUST_USE bool readFixedU32(uint32_t* out) {
        if (Validate)
            return d_.readFixedU32(out);
        *out = d_.uncheckedReadFixedU32();
        return true;
    }
    MOZ_MUST_USE bool readVarS32(int32_t* out) {
        if (Validate)
            return d_.readVarS32(out);
        *out = d_.uncheckedReadVarS32();
        return true;
    }
    MOZ_MUST_USE bool readVarU32(uint32_t* out) {
        if (Validate)
            return d_.readVarU32(out);
        *out = d_.uncheckedReadVarU32();
        return true;
    }
    MOZ_MUST_USE bool readVarS64(int64_t* out) {
        if (Validate)
            return d_.readVarS64(out);
        *out = d_.uncheckedReadVarS64();
        return true;
    }
    MOZ_MUST_USE bool readVarU64(uint64_t* out) {
        if (Validate)
            return d_.readVarU64(out);
        *out = d_.uncheckedReadVarU64();
        return true;
    }
    MOZ_MUST_USE bool readFixedF32(RawF32* out) {
        if (Validate)
            return d_.readFixedF32(out);
        *out = d_.uncheckedReadFixedF32();
        return true;
    }
    MOZ_MUST_USE bool readFixedF64(RawF64* out) {
        if (Validate)
            return d_.readFixedF64(out);
        *out = d_.uncheckedReadFixedF64();
        return true;
    }
    MOZ_MUST_USE bool readFixedI8x16(I8x16* out) {
        if (Validate)
            return d_.readFixedI8x16(out);
        d_.uncheckedReadFixedI8x16(out);
        return true;
    }
    MOZ_MUST_USE bool readFixedI16x8(I16x8* out) {
        if (Validate)
            return d_.readFixedI16x8(out);
        d_.uncheckedReadFixedI16x8(out);
        return true;
    }
    MOZ_MUST_USE bool readFixedI32x4(I32x4* out) {
        if (Validate)
            return d_.readFixedI32x4(out);
        d_.uncheckedReadFixedI32x4(out);
        return true;
    }
    MOZ_MUST_USE bool readFixedF32x4(F32x4* out) {
        if (Validate)
            return d_.readFixedF32x4(out);
        d_.uncheckedReadFixedF32x4(out);
        return true;
    }

    MOZ_MUST_USE bool readAtomicViewType(Scalar::Type* viewType) {
        uint8_t x;
        if (!readFixedU8(&x))
            return fail("unable to read atomic view");
        if (Validate && x >= Scalar::MaxTypedArrayViewType)
            return fail("invalid atomic view type");
        *viewType = Scalar::Type(x);
        return true;
    }

    MOZ_MUST_USE bool readAtomicBinOpOp(jit::AtomicOp* op) {
        uint8_t x;
        if (!readFixedU8(&x))
            return fail("unable to read atomic opcode");
        if (Validate) {
            switch (x) {
              case jit::AtomicFetchAddOp:
              case jit::AtomicFetchSubOp:
              case jit::AtomicFetchAndOp:
              case jit::AtomicFetchOrOp:
              case jit::AtomicFetchXorOp:
                break;
              default:
                return fail("unrecognized atomic binop");
            }
        }
        *op = jit::AtomicOp(x);
        return true;
    }

    MOZ_MUST_USE bool readLinearMemoryAddress(uint32_t byteSize, LinearMemoryAddress<Value>* addr);
    MOZ_MUST_USE bool readBlockType(ExprType* expr);

    MOZ_MUST_USE bool typeMismatch(ExprType actual, ExprType expected) MOZ_COLD;
    MOZ_MUST_USE bool checkType(ValType actual, ValType expected);
    MOZ_MUST_USE bool checkType(ExprType actual, ExprType expected);

    MOZ_MUST_USE bool pushControl(LabelKind kind, ExprType type, bool reachable);
    MOZ_MUST_USE bool mergeControl(LabelKind* kind, ExprType* type, Value* value);
    MOZ_MUST_USE bool popControl(LabelKind* kind, ExprType* type, Value* value);

    MOZ_MUST_USE bool push(ValType t) {
        if (MOZ_UNLIKELY(!reachable_))
            return true;
        return valueStack_.emplaceBack(t);
    }
    MOZ_MUST_USE bool push(TypeAndValue<Value> tv) {
        if (MOZ_UNLIKELY(!reachable_))
            return true;
        return valueStack_.append(tv);
    }
    void infalliblePush(ValType t) {
        if (MOZ_UNLIKELY(!reachable_))
            return;
        valueStack_.infallibleEmplaceBack(t);
    }
    void infalliblePush(TypeAndValue<Value> tv) {
        if (MOZ_UNLIKELY(!reachable_))
            return;
        valueStack_.infallibleAppend(tv);
    }

    // Test whether reading the top of the value stack is currently valid.
    MOZ_MUST_USE bool checkTop() {
        MOZ_ASSERT(reachable_);
        if (Validate && valueStack_.length() <= controlStack_.back().valueStackStart()) {
            if (valueStack_.empty())
                return fail("popping value from empty stack");
            return fail("popping value from outside block");
        }
        return true;
    }

    // Pop the top of the value stack.
    MOZ_MUST_USE bool pop(TypeAndValue<Value>* tv) {
        if (MOZ_UNLIKELY(!reachable_))
            return true;
        if (!checkTop())
            return false;
        *tv = valueStack_.popCopy();
        return true;
    }

    // Pop the top of the value stack and check that it has the given type.
    MOZ_MUST_USE bool popWithType(ValType expectedType, Value* value) {
        if (MOZ_UNLIKELY(!reachable_))
            return true;
        if (!checkTop())
            return false;
        TypeAndValue<Value> tv = valueStack_.popCopy();
        if (!checkType(tv.type(), expectedType))
            return false;
        if (Output)
            *value = tv.value();
        return true;
    }

    // Pop the top of the value stack and discard the result.
    MOZ_MUST_USE bool pop() {
        if (MOZ_UNLIKELY(!reachable_))
            return true;
        if (!checkTop())
            return false;
        valueStack_.popBack();
        return true;
    }

    // Read the top of the value stack (without popping it).
    MOZ_MUST_USE bool top(TypeAndValue<Value>* tv) {
        if (MOZ_UNLIKELY(!reachable_))
            return true;
        if (!checkTop())
            return false;
        *tv = valueStack_.back();
        return true;
    }

    // Read the top of the value stack (without popping it) and check that it
    // has the given type.
    MOZ_MUST_USE bool topWithType(ValType expectedType, Value* value) {
        if (MOZ_UNLIKELY(!reachable_))
            return true;
        if (!checkTop())
            return false;
        TypeAndValue<Value>& tv = valueStack_.back();
        if (!checkType(tv.type(), expectedType))
            return false;
        if (Output)
            *value = tv.value();
        return true;
    }

    // Read the value stack entry at depth |index|.
    MOZ_MUST_USE bool peek(uint32_t index, TypeAndValue<Value>* tv) {
        MOZ_ASSERT(reachable_);
        if (Validate && valueStack_.length() - controlStack_.back().valueStackStart() < index)
            return fail("peeking at value from outside block");
        *tv = valueStack_[valueStack_.length() - index];
        return true;
    }

    bool getControl(uint32_t relativeDepth, ControlStackEntry<ControlItem>** controlEntry) {
        if (Validate && relativeDepth >= controlStack_.length())
            return fail("branch depth exceeds current nesting level");

        *controlEntry = &controlStack_[controlStack_.length() - 1 - relativeDepth];
        return true;
    }

    void enterUnreachableCode() {
        valueStack_.shrinkTo(controlStack_.back().valueStackStart());
        reachable_ = false;
    }

    bool checkBrValue(uint32_t relativeDepth, ExprType* type, Value* value);
    bool checkBrIfValues(uint32_t relativeDepth, Value* condition, ExprType* type, Value* value);

  public:
    explicit OpIter(Decoder& decoder, uint32_t offsetInModule = 0)
      : d_(decoder), offsetInModule_(offsetInModule), reachable_(true),
        op_(Op::Limit), offsetOfExpr_(0)
    {}

    // Return the decoding byte offset.
    uint32_t currentOffset() const { return d_.currentOffset(); }

    // Returning the offset within the entire module of the last-read Op.
    TrapOffset trapOffset() const {
        return TrapOffset(offsetInModule_ + offsetOfExpr_);
    }

    // Test whether the iterator has reached the end of the buffer.
    bool done() const { return d_.done(); }

    // Report a general failure.
    MOZ_MUST_USE bool fail(const char* msg) MOZ_COLD;

    // Report an unimplemented feature.
    MOZ_MUST_USE bool notYetImplemented(const char* what) MOZ_COLD;

    // Report an unrecognized opcode.
    MOZ_MUST_USE bool unrecognizedOpcode(uint32_t expr) MOZ_COLD;

    // Test whether the iterator is currently in "reachable" code.
    bool inReachableCode() const { return reachable_; }

    // ------------------------------------------------------------------------
    // Decoding and validation interface.

    MOZ_MUST_USE bool readOp(uint16_t* op);
    MOZ_MUST_USE bool readFunctionStart(ExprType ret);
    MOZ_MUST_USE bool readFunctionEnd();
    MOZ_MUST_USE bool readReturn(Value* value);
    MOZ_MUST_USE bool readBlock();
    MOZ_MUST_USE bool readLoop();
    MOZ_MUST_USE bool readIf(Value* condition);
    MOZ_MUST_USE bool readElse(ExprType* thenType, Value* thenValue);
    MOZ_MUST_USE bool readEnd(LabelKind* kind, ExprType* type, Value* value);
    MOZ_MUST_USE bool readBr(uint32_t* relativeDepth, ExprType* type, Value* value);
    MOZ_MUST_USE bool readBrIf(uint32_t* relativeDepth, ExprType* type,
                               Value* value, Value* condition);
    MOZ_MUST_USE bool readBrTable(uint32_t* tableLength, ExprType* type,
                                  Value* value, Value* index);
    MOZ_MUST_USE bool readBrTableEntry(ExprType* type, Value* value, uint32_t* depth);
    MOZ_MUST_USE bool readBrTableDefault(ExprType* type, Value* value, uint32_t* depth);
    MOZ_MUST_USE bool readUnreachable();
    MOZ_MUST_USE bool readDrop();
    MOZ_MUST_USE bool readUnary(ValType operandType, Value* input);
    MOZ_MUST_USE bool readConversion(ValType operandType, ValType resultType, Value* input);
    MOZ_MUST_USE bool readBinary(ValType operandType, Value* lhs, Value* rhs);
    MOZ_MUST_USE bool readComparison(ValType operandType, Value* lhs, Value* rhs);
    MOZ_MUST_USE bool readLoad(ValType resultType, uint32_t byteSize,
                               LinearMemoryAddress<Value>* addr);
    MOZ_MUST_USE bool readStore(ValType resultType, uint32_t byteSize,
                                LinearMemoryAddress<Value>* addr, Value* value);
    MOZ_MUST_USE bool readTeeStore(ValType resultType, uint32_t byteSize,
                                   LinearMemoryAddress<Value>* addr, Value* value);
    MOZ_MUST_USE bool readNop();
    MOZ_MUST_USE bool readCurrentMemory();
    MOZ_MUST_USE bool readGrowMemory(Value* input);
    MOZ_MUST_USE bool readSelect(ValType* type,
                                 Value* trueValue, Value* falseValue, Value* condition);
    MOZ_MUST_USE bool readGetLocal(const ValTypeVector& locals, uint32_t* id);
    MOZ_MUST_USE bool readSetLocal(const ValTypeVector& locals, uint32_t* id, Value* value);
    MOZ_MUST_USE bool readTeeLocal(const ValTypeVector& locals, uint32_t* id, Value* value);
    MOZ_MUST_USE bool readGetGlobal(const GlobalDescVector& globals, uint32_t* id);
    MOZ_MUST_USE bool readSetGlobal(const GlobalDescVector& globals, uint32_t* id, Value* value);
    MOZ_MUST_USE bool readTeeGlobal(const GlobalDescVector& globals, uint32_t* id, Value* value);
    MOZ_MUST_USE bool readI32Const(int32_t* i32);
    MOZ_MUST_USE bool readI64Const(int64_t* i64);
    MOZ_MUST_USE bool readF32Const(RawF32* f32);
    MOZ_MUST_USE bool readF64Const(RawF64* f64);
    MOZ_MUST_USE bool readI8x16Const(I8x16* i8x16);
    MOZ_MUST_USE bool readI16x8Const(I16x8* i16x8);
    MOZ_MUST_USE bool readI32x4Const(I32x4* i32x4);
    MOZ_MUST_USE bool readF32x4Const(F32x4* f32x4);
    MOZ_MUST_USE bool readB8x16Const(I8x16* i8x16);
    MOZ_MUST_USE bool readB16x8Const(I16x8* i16x8);
    MOZ_MUST_USE bool readB32x4Const(I32x4* i32x4);
    MOZ_MUST_USE bool readCall(uint32_t* calleeIndex);
    MOZ_MUST_USE bool readCallIndirect(uint32_t* sigIndex, Value* callee);
    MOZ_MUST_USE bool readOldCallIndirect(uint32_t* sigIndex);
    MOZ_MUST_USE bool readCallArg(ValType type, uint32_t numArgs, uint32_t argIndex, Value* arg);
    MOZ_MUST_USE bool readCallArgsEnd(uint32_t numArgs);
    MOZ_MUST_USE bool readOldCallIndirectCallee(Value* callee);
    MOZ_MUST_USE bool readCallReturn(ExprType ret);
    MOZ_MUST_USE bool readAtomicLoad(LinearMemoryAddress<Value>* addr,
                                     Scalar::Type* viewType);
    MOZ_MUST_USE bool readAtomicStore(LinearMemoryAddress<Value>* addr,
                                      Scalar::Type* viewType,
                                      Value* value);
    MOZ_MUST_USE bool readAtomicBinOp(LinearMemoryAddress<Value>* addr,
                                      Scalar::Type* viewType,
                                      jit::AtomicOp* op,
                                      Value* value);
    MOZ_MUST_USE bool readAtomicCompareExchange(LinearMemoryAddress<Value>* addr,
                                                Scalar::Type* viewType,
                                                Value* oldValue,
                                                Value* newValue);
    MOZ_MUST_USE bool readAtomicExchange(LinearMemoryAddress<Value>* addr,
                                         Scalar::Type* viewType,
                                         Value* newValue);
    MOZ_MUST_USE bool readSimdComparison(ValType simdType, Value* lhs,
                                         Value* rhs);
    MOZ_MUST_USE bool readSimdShiftByScalar(ValType simdType, Value* lhs,
                                            Value* rhs);
    MOZ_MUST_USE bool readSimdBooleanReduction(ValType simdType, Value* input);
    MOZ_MUST_USE bool readExtractLane(ValType simdType, uint8_t* lane,
                                      Value* vector);
    MOZ_MUST_USE bool readReplaceLane(ValType simdType, uint8_t* lane,
                                      Value* vector, Value* scalar);
    MOZ_MUST_USE bool readSplat(ValType simdType, Value* scalar);
    MOZ_MUST_USE bool readSwizzle(ValType simdType, uint8_t (* lanes)[16], Value* vector);
    MOZ_MUST_USE bool readShuffle(ValType simdType, uint8_t (* lanes)[16],
                                  Value* lhs, Value* rhs);
    MOZ_MUST_USE bool readSimdSelect(ValType simdType, Value* trueValue,
                                     Value* falseValue,
                                     Value* condition);
    MOZ_MUST_USE bool readSimdCtor();
    MOZ_MUST_USE bool readSimdCtorArg(ValType elementType, uint32_t numElements, uint32_t argIndex,
                                      Value* arg);
    MOZ_MUST_USE bool readSimdCtorArgsEnd(uint32_t numElements);
    MOZ_MUST_USE bool readSimdCtorReturn(ValType simdType);

    // At a location where readOp is allowed, peek at the next opcode
    // without consuming it or updating any internal state.
    // Never fails: returns uint16_t(Op::Limit) if it can't read.
    uint16_t peekOp();

    // ------------------------------------------------------------------------
    // Stack management.

    // Set the result value of the current top-of-value-stack expression.
    void setResult(Value value) {
        if (MOZ_LIKELY(reachable_))
            valueStack_.back().setValue(value);
    }

    // Return the result value of the current top-of-value-stack expression.
    Value getResult() {
        MOZ_ASSERT(reachable_);
        return valueStack_.back().value();
    }

    // Return a reference to the top of the control stack.
    ControlItem& controlItem() {
        return controlStack_.back().controlItem();
    }

    // Return the signature of the top of the control stack.
    ExprType controlType() {
        return controlStack_.back().type();
    }

    // Test whether the control-stack is empty, meaning we've consumed the final
    // end of the function body.
    bool controlStackEmpty() const {
        return controlStack_.empty();
    }
};

template <typename Policy>
bool
OpIter<Policy>::typeMismatch(ExprType actual, ExprType expected)
{
    MOZ_ASSERT(Validate);
    MOZ_ASSERT(reachable_);

    UniqueChars error(JS_smprintf("type mismatch: expression has type %s but expected %s",
                                  ToCString(actual), ToCString(expected)));
    if (!error)
        return false;

    return fail(error.get());
}

template <typename Policy>
inline bool
OpIter<Policy>::checkType(ValType actual, ValType expected)
{
    return checkType(ToExprType(actual), ToExprType(expected));
}

template <typename Policy>
inline bool
OpIter<Policy>::checkType(ExprType actual, ExprType expected)
{
    MOZ_ASSERT(reachable_);

    if (!Validate) {
        MOZ_ASSERT(actual == expected, "type mismatch");
        return true;
    }

    if (MOZ_LIKELY(actual == expected))
        return true;

    return typeMismatch(actual, expected);
}

template <typename Policy>
bool
OpIter<Policy>::notYetImplemented(const char* what)
{
    UniqueChars error(JS_smprintf("not yet implemented: %s", what));
    if (!error)
        return false;

    return fail(error.get());
}

template <typename Policy>
bool
OpIter<Policy>::unrecognizedOpcode(uint32_t expr)
{
    UniqueChars error(JS_smprintf("unrecognized opcode: %x", expr));
    if (!error)
        return false;

    return fail(error.get());
}

template <typename Policy>
bool
OpIter<Policy>::fail(const char* msg)
{
    return d_.fail("%s", msg);
}

template <typename Policy>
inline bool
OpIter<Policy>::pushControl(LabelKind kind, ExprType type, bool reachable)
{
    return controlStack_.emplaceBack(kind, type, reachable, valueStack_.length());
}

template <typename Policy>
inline bool
OpIter<Policy>::mergeControl(LabelKind* kind, ExprType* type, Value* value)
{
    MOZ_ASSERT(!controlStack_.empty());

    ControlStackEntry<ControlItem>& controlItem = controlStack_.back();
    *kind = controlItem.kind();

    if (reachable_) {
        // Unlike branching, exiting a scope via fallthrough does not implicitly
        // pop excess items on the stack.
        size_t valueStackStart = controlItem.valueStackStart();
        size_t valueStackLength = valueStack_.length();
        MOZ_ASSERT(valueStackLength >= valueStackStart);
        if (valueStackLength == valueStackStart) {
            *type = ExprType::Void;
            if (!checkType(ExprType::Void, controlItem.type()))
                return false;
        } else {
            *type = controlItem.type();
            if (Validate && valueStackLength - valueStackStart > (IsVoid(*type) ? 0u : 1u))
                return fail("unused values not explicitly dropped by end of block");
            if (!topWithType(NonVoidToValType(*type), value))
                return false;
        }
    } else {
        if (*kind != LabelKind::Loop && controlItem.reachable()) {
            // There was no fallthrough path, but there was some other reachable
            // branch to the end.
            reachable_ = true;
            *type = controlItem.type();
            if (!IsVoid(*type)) {
                if (!push(NonVoidToValType(*type)))
                    return false;
            }
        } else {
            // No fallthrough and no branch to the end either; we remain
            // unreachable.
            *type = ExprType::Void;
        }
        if (Output)
            *value = Value();
    }

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::popControl(LabelKind* kind, ExprType* type, Value* value)
{
    if (!mergeControl(kind, type, value))
        return false;

    if (*kind == LabelKind::Then) {
        // A reachable If without an Else. Forbid a result value.
        if (reachable_) {
            if (Validate && !IsVoid(*type))
                return fail("if without else with a result value");
        }
        reachable_ = true;
    }

    controlStack_.popBack();

    if (!reachable_ && !controlStack_.empty())
        valueStack_.shrinkTo(controlStack_.back().valueStackStart());

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readBlockType(ExprType* type)
{
    uint8_t unchecked;
    if (!d_.readBlockType(&unchecked))
        return fail("unable to read block signature");

    if (Validate) {
        switch (unchecked) {
          case uint8_t(ExprType::Void):
          case uint8_t(ExprType::I32):
          case uint8_t(ExprType::I64):
          case uint8_t(ExprType::F32):
          case uint8_t(ExprType::F64):
          case uint8_t(ExprType::I8x16):
          case uint8_t(ExprType::I16x8):
          case uint8_t(ExprType::I32x4):
          case uint8_t(ExprType::F32x4):
          case uint8_t(ExprType::B8x16):
          case uint8_t(ExprType::B16x8):
          case uint8_t(ExprType::B32x4):
            break;
          default:
            return fail("invalid inline block type");
        }
    }

    *type = ExprType(unchecked);
    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readOp(uint16_t* op)
{
    offsetOfExpr_ = d_.currentOffset();

    if (Validate) {
        if (MOZ_UNLIKELY(!d_.readOp(op)))
            return fail("unable to read opcode");
    } else {
        *op = uint16_t(d_.uncheckedReadOp());
    }

    op_ = Op(*op);  // debug-only

    return true;
}

template <typename Policy>
inline uint16_t
OpIter<Policy>::peekOp()
{
    const uint8_t* pos = d_.currentPosition();
    uint16_t op;

    if (Validate) {
        if (MOZ_UNLIKELY(!d_.readOp(&op)))
            op = uint16_t(Op::Limit);
    } else {
        op = uint16_t(d_.uncheckedReadOp());
    }

    d_.rollbackPosition(pos);

    return op;
}

template <typename Policy>
inline bool
OpIter<Policy>::readFunctionStart(ExprType ret)
{
    MOZ_ASSERT(valueStack_.empty());
    MOZ_ASSERT(controlStack_.empty());
    MOZ_ASSERT(Op(op_) == Op::Limit);
    MOZ_ASSERT(reachable_);

    return pushControl(LabelKind::Block, ret, false);
}

template <typename Policy>
inline bool
OpIter<Policy>::readFunctionEnd()
{
    if (Validate) {
        if (!controlStack_.empty())
            return fail("unbalanced function body control flow");
    } else {
        MOZ_ASSERT(controlStack_.empty());
    }

    op_ = Op::Limit;
    valueStack_.clear();
    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readReturn(Value* value)
{
    MOZ_ASSERT(Classify(op_) == OpKind::Return);

    if (MOZ_LIKELY(reachable_)) {
        ControlStackEntry<ControlItem>& controlItem = controlStack_[0];
        MOZ_ASSERT(controlItem.kind() == LabelKind::Block);

        controlItem.setReachable();

        if (!IsVoid(controlItem.type())) {
            if (!popWithType(NonVoidToValType(controlItem.type()), value))
                return false;
        }
    }

    enterUnreachableCode();
    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readBlock()
{
    MOZ_ASSERT(Classify(op_) == OpKind::Block);

    ExprType type = ExprType::Limit;
    if (!readBlockType(&type))
        return false;

    return pushControl(LabelKind::Block, type, false);
}

template <typename Policy>
inline bool
OpIter<Policy>::readLoop()
{
    MOZ_ASSERT(Classify(op_) == OpKind::Loop);

    ExprType type = ExprType::Limit;
    if (!readBlockType(&type))
        return false;

    return pushControl(LabelKind::Loop, type, reachable_);
}

template <typename Policy>
inline bool
OpIter<Policy>::readIf(Value* condition)
{
    MOZ_ASSERT(Classify(op_) == OpKind::If);

    ExprType type = ExprType::Limit;
    if (!readBlockType(&type))
        return false;

    if (MOZ_LIKELY(reachable_)) {
        if (!popWithType(ValType::I32, condition))
            return false;

        return pushControl(LabelKind::Then, type, false);
    }

    return pushControl(LabelKind::UnreachableThen, type, false);
}

template <typename Policy>
inline bool
OpIter<Policy>::readElse(ExprType* thenType, Value* thenValue)
{
    MOZ_ASSERT(Classify(op_) == OpKind::Else);

    // Finish up the then arm.
    ExprType type = ExprType::Limit;
    LabelKind kind;
    if (!mergeControl(&kind, &type, thenValue))
        return false;

    if (Output)
        *thenType = type;

    // Pop the old then value from the stack.
    if (!IsVoid(type))
        valueStack_.popBack();

    if (Validate && kind != LabelKind::Then && kind != LabelKind::UnreachableThen)
        return fail("else can only be used within an if");

    // Switch to the else arm.
    controlStack_.back().switchToElse(reachable_);

    reachable_ = kind != LabelKind::UnreachableThen;

    MOZ_ASSERT(valueStack_.length() == controlStack_.back().valueStackStart());

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readEnd(LabelKind* kind, ExprType* type, Value* value)
{
    MOZ_ASSERT(Classify(op_) == OpKind::End);

    LabelKind validateKind = static_cast<LabelKind>(-1);
    ExprType validateType = ExprType::Limit;
    if (!popControl(&validateKind, &validateType, value))
        return false;

    if (Output) {
        *kind = validateKind;
        *type = validateType;
    }

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::checkBrValue(uint32_t relativeDepth, ExprType* type, Value* value)
{
    if (MOZ_LIKELY(reachable_)) {
        ControlStackEntry<ControlItem>* controlItem = nullptr;
        if (!getControl(relativeDepth, &controlItem))
            return false;

        if (controlItem->kind() != LabelKind::Loop) {
            controlItem->setReachable();

            ExprType expectedType = controlItem->type();
            if (Output)
                *type = expectedType;

            if (!IsVoid(expectedType))
                return topWithType(NonVoidToValType(expectedType), value);
        }
    }

    if (Output) {
        *type = ExprType::Void;
        *value = Value();
    }

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readBr(uint32_t* relativeDepth, ExprType* type, Value* value)
{
    MOZ_ASSERT(Classify(op_) == OpKind::Br);

    uint32_t validateRelativeDepth;
    if (!readVarU32(&validateRelativeDepth))
        return fail("unable to read br depth");

    if (!checkBrValue(validateRelativeDepth, type, value))
        return false;

    if (Output)
        *relativeDepth = validateRelativeDepth;

    enterUnreachableCode();
    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::checkBrIfValues(uint32_t relativeDepth, Value* condition,
                                  ExprType* type, Value* value)
{
    if (MOZ_LIKELY(reachable_)) {
        if (!popWithType(ValType::I32, condition))
            return false;

        ControlStackEntry<ControlItem>* controlItem = nullptr;
        if (!getControl(relativeDepth, &controlItem))
            return false;

        if (controlItem->kind() != LabelKind::Loop) {
            controlItem->setReachable();

            ExprType expectedType = controlItem->type();
            if (Output)
                *type = expectedType;

            if (!IsVoid(expectedType))
                return topWithType(NonVoidToValType(expectedType), value);
        }
    }

    if (Output) {
        *type = ExprType::Void;
        *value = Value();
    }

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readBrIf(uint32_t* relativeDepth, ExprType* type, Value* value, Value* condition)
{
    MOZ_ASSERT(Classify(op_) == OpKind::BrIf);

    uint32_t validateRelativeDepth;
    if (!readVarU32(&validateRelativeDepth))
        return fail("unable to read br_if depth");

    if (!checkBrIfValues(validateRelativeDepth, condition, type, value))
        return false;

    if (Output)
        *relativeDepth = validateRelativeDepth;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readBrTable(uint32_t* tableLength, ExprType* type,
                              Value* value, Value* index)
{
    MOZ_ASSERT(Classify(op_) == OpKind::BrTable);

    if (!readVarU32(tableLength))
        return fail("unable to read br_table table length");

    if (MOZ_LIKELY(reachable_)) {
        if (!popWithType(ValType::I32, index))
            return false;
    }

    // Set *type to indicate that we don't know the type yet.
    *type = ExprType::Limit;
    if (Output)
        *value = Value();

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readBrTableEntry(ExprType* type, Value* value, uint32_t* depth)
{
    MOZ_ASSERT(Classify(op_) == OpKind::BrTable);

    if (!readVarU32(depth))
        return false;

    ExprType knownType = *type;

    if (MOZ_LIKELY(reachable_)) {
        ControlStackEntry<ControlItem>* controlItem = nullptr;
        if (!getControl(*depth, &controlItem))
            return false;

        if (controlItem->kind() != LabelKind::Loop) {
            controlItem->setReachable();

            // If we've already seen one label, we know the type and can check
            // that the type for the current label matches it.
            if (knownType != ExprType::Limit)
                return checkType(knownType, controlItem->type());

            // This is the first label; record the type and the value now.
            ExprType expectedType = controlItem->type();
            if (!IsVoid(expectedType)) {
                *type = expectedType;
                return popWithType(NonVoidToValType(expectedType), value);
            }
        }

        if (knownType != ExprType::Limit && knownType != ExprType::Void)
            return typeMismatch(knownType, ExprType::Void);
    }

    *type = ExprType::Void;
    if (Output)
        *value = Value();
    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readBrTableDefault(ExprType* type, Value* value, uint32_t* depth)
{
    if (!readBrTableEntry(type, value, depth))
        return false;

    MOZ_ASSERT(!reachable_ || *type != ExprType::Limit);

    enterUnreachableCode();
    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readUnreachable()
{
    MOZ_ASSERT(Classify(op_) == OpKind::Unreachable);

    enterUnreachableCode();
    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readDrop()
{
    MOZ_ASSERT(Classify(op_) == OpKind::Drop);

    if (!pop())
        return false;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readUnary(ValType operandType, Value* input)
{
    MOZ_ASSERT(Classify(op_) == OpKind::Unary);

    if (!popWithType(operandType, input))
        return false;

    infalliblePush(operandType);

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readConversion(ValType operandType, ValType resultType, Value* input)
{
    MOZ_ASSERT(Classify(op_) == OpKind::Conversion);

    if (!popWithType(operandType, input))
        return false;

    infalliblePush(resultType);

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readBinary(ValType operandType, Value* lhs, Value* rhs)
{
    MOZ_ASSERT(Classify(op_) == OpKind::Binary);

    if (!popWithType(operandType, rhs))
        return false;

    if (!popWithType(operandType, lhs))
        return false;

    infalliblePush(operandType);

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readComparison(ValType operandType, Value* lhs, Value* rhs)
{
    MOZ_ASSERT(Classify(op_) == OpKind::Comparison);

    if (!popWithType(operandType, rhs))
        return false;

    if (!popWithType(operandType, lhs))
        return false;

    infalliblePush(ValType::I32);

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readLinearMemoryAddress(uint32_t byteSize, LinearMemoryAddress<Value>* addr)
{
    uint8_t alignLog2;
    if (!readFixedU8(&alignLog2))
        return fail("unable to read load alignment");

    uint32_t unusedOffset;
    if (!readVarU32(Output ? &addr->offset : &unusedOffset))
        return fail("unable to read load offset");

    if (Validate && (alignLog2 >= 32 || (uint32_t(1) << alignLog2) > byteSize))
        return fail("greater than natural alignment");

    Value unused;
    if (!popWithType(ValType::I32, Output ? &addr->base : &unused))
        return false;

    if (Output)
        addr->align = uint32_t(1) << alignLog2;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readLoad(ValType resultType, uint32_t byteSize, LinearMemoryAddress<Value>* addr)
{
    MOZ_ASSERT(Classify(op_) == OpKind::Load);

    if (!readLinearMemoryAddress(byteSize, addr))
        return false;

    infalliblePush(resultType);

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readStore(ValType resultType, uint32_t byteSize, LinearMemoryAddress<Value>* addr,
                          Value* value)
{
    MOZ_ASSERT(Classify(op_) == OpKind::Store);

    if (!popWithType(resultType, value))
        return false;

    if (!readLinearMemoryAddress(byteSize, addr))
        return false;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readTeeStore(ValType resultType, uint32_t byteSize, LinearMemoryAddress<Value>* addr,
                             Value* value)
{
    MOZ_ASSERT(Classify(op_) == OpKind::TeeStore);

    if (!popWithType(resultType, value))
        return false;

    if (!readLinearMemoryAddress(byteSize, addr))
        return false;

    infalliblePush(TypeAndValue<Value>(resultType, Output ? *value : Value()));

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readNop()
{
    MOZ_ASSERT(Classify(op_) == OpKind::Nop);

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readCurrentMemory()
{
    MOZ_ASSERT(Classify(op_) == OpKind::CurrentMemory);

    uint32_t flags;
    if (!readVarU32(&flags))
        return false;

    if (Validate && flags != uint32_t(MemoryTableFlags::Default))
        return fail("unexpected flags");

    if (!push(ValType::I32))
        return false;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readGrowMemory(Value* input)
{
    MOZ_ASSERT(Classify(op_) == OpKind::GrowMemory);

    uint32_t flags;
    if (!readVarU32(&flags))
        return false;

    if (Validate && flags != uint32_t(MemoryTableFlags::Default))
        return fail("unexpected flags");

    if (!popWithType(ValType::I32, input))
        return false;

    infalliblePush(ValType::I32);

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readSelect(ValType* type, Value* trueValue, Value* falseValue, Value* condition)
{
    MOZ_ASSERT(Classify(op_) == OpKind::Select);

    if (!popWithType(ValType::I32, condition))
        return false;

    TypeAndValue<Value> false_;
    if (!pop(&false_))
        return false;

    TypeAndValue<Value> true_;
    if (!pop(&true_))
        return false;

    ValType resultType = true_.type();
    if (Validate && resultType != false_.type())
        return fail("select operand types must match");

    infalliblePush(resultType);

    if (Output) {
        *type = resultType;
        *trueValue = true_.value();
        *falseValue = false_.value();
    }

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readGetLocal(const ValTypeVector& locals, uint32_t* id)
{
    MOZ_ASSERT(Classify(op_) == OpKind::GetLocal);

    uint32_t validateId;
    if (!readVarU32(&validateId))
        return false;

    if (Validate && validateId >= locals.length())
        return fail("get_local index out of range");

    if (!push(locals[validateId]))
        return false;

    if (Output)
        *id = validateId;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readSetLocal(const ValTypeVector& locals, uint32_t* id, Value* value)
{
    MOZ_ASSERT(Classify(op_) == OpKind::SetLocal);

    uint32_t validateId;
    if (!readVarU32(&validateId))
        return false;

    if (Validate && validateId >= locals.length())
        return fail("set_local index out of range");

    if (!popWithType(locals[validateId], value))
        return false;

    if (Output)
        *id = validateId;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readTeeLocal(const ValTypeVector& locals, uint32_t* id, Value* value)
{
    MOZ_ASSERT(Classify(op_) == OpKind::TeeLocal);

    uint32_t validateId;
    if (!readVarU32(&validateId))
        return false;

    if (Validate && validateId >= locals.length())
        return fail("set_local index out of range");

    if (!topWithType(locals[validateId], value))
        return false;

    if (Output)
        *id = validateId;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readGetGlobal(const GlobalDescVector& globals, uint32_t* id)
{
    MOZ_ASSERT(Classify(op_) == OpKind::GetGlobal);

    uint32_t validateId;
    if (!readVarU32(&validateId))
        return false;

    if (Validate && validateId >= globals.length())
        return fail("get_global index out of range");

    if (!push(globals[validateId].type()))
        return false;

    if (Output)
        *id = validateId;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readSetGlobal(const GlobalDescVector& globals, uint32_t* id, Value* value)
{
    MOZ_ASSERT(Classify(op_) == OpKind::SetGlobal);

    uint32_t validateId;
    if (!readVarU32(&validateId))
        return false;

    if (Validate && validateId >= globals.length())
        return fail("set_global index out of range");

    if (Validate && !globals[validateId].isMutable())
        return fail("can't write an immutable global");

    if (!popWithType(globals[validateId].type(), value))
        return false;

    if (Output)
        *id = validateId;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readTeeGlobal(const GlobalDescVector& globals, uint32_t* id, Value* value)
{
    MOZ_ASSERT(Classify(op_) == OpKind::TeeGlobal);

    uint32_t validateId;
    if (!readVarU32(&validateId))
        return false;

    if (Validate && validateId >= globals.length())
        return fail("set_global index out of range");

    if (Validate && !globals[validateId].isMutable())
        return fail("can't write an immutable global");

    if (!topWithType(globals[validateId].type(), value))
        return false;

    if (Output)
        *id = validateId;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readI32Const(int32_t* i32)
{
    MOZ_ASSERT(Classify(op_) == OpKind::I32);

    int32_t unused;
    if (!readVarS32(Output ? i32 : &unused))
        return false;

    if (!push(ValType::I32))
       return false;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readI64Const(int64_t* i64)
{
    MOZ_ASSERT(Classify(op_) == OpKind::I64);

    int64_t unused;
    if (!readVarS64(Output ? i64 : &unused))
        return false;

    if (!push(ValType::I64))
        return false;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readF32Const(RawF32* f32)
{
    MOZ_ASSERT(Classify(op_) == OpKind::F32);

    RawF32 unused;
    if (!readFixedF32(Output ? f32 : &unused))
        return false;

    if (!push(ValType::F32))
        return false;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readF64Const(RawF64* f64)
{
    MOZ_ASSERT(Classify(op_) == OpKind::F64);

    RawF64 unused;
    if (!readFixedF64(Output ? f64 : &unused))
       return false;

    if (!push(ValType::F64))
        return false;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readI8x16Const(I8x16* i8x16)
{
    MOZ_ASSERT(Classify(op_) == OpKind::I8x16);

    I8x16 unused;
    if (!readFixedI8x16(Output ? i8x16 : &unused))
        return false;

    if (!push(ValType::I8x16))
        return false;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readI16x8Const(I16x8* i16x8)
{
    MOZ_ASSERT(Classify(op_) == OpKind::I16x8);

    I16x8 unused;
    if (!readFixedI16x8(Output ? i16x8 : &unused))
        return false;

    if (!push(ValType::I16x8))
        return false;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readI32x4Const(I32x4* i32x4)
{
    MOZ_ASSERT(Classify(op_) == OpKind::I32x4);

    I32x4 unused;
    if (!readFixedI32x4(Output ? i32x4 : &unused))
        return false;

    if (!push(ValType::I32x4))
        return false;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readF32x4Const(F32x4* f32x4)
{
    MOZ_ASSERT(Classify(op_) == OpKind::F32x4);

    F32x4 unused;
    if (!readFixedF32x4(Output ? f32x4 : &unused))
        return false;

    if (!push(ValType::F32x4))
        return false;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readB8x16Const(I8x16* i8x16)
{
    MOZ_ASSERT(Classify(op_) == OpKind::B8x16);

    I8x16 unused;
    if (!readFixedI8x16(Output ? i8x16 : &unused))
        return false;

    if (!push(ValType::B8x16))
        return false;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readB16x8Const(I16x8* i16x8)
{
    MOZ_ASSERT(Classify(op_) == OpKind::B16x8);

    I16x8 unused;
    if (!readFixedI16x8(Output ? i16x8 : &unused))
        return false;

    if (!push(ValType::B16x8))
        return false;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readB32x4Const(I32x4* i32x4)
{
    MOZ_ASSERT(Classify(op_) == OpKind::B32x4);

    I32x4 unused;
    if (!readFixedI32x4(Output ? i32x4 : &unused))
        return false;

    if (!push(ValType::B32x4))
        return false;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readCall(uint32_t* calleeIndex)
{
    MOZ_ASSERT(Classify(op_) == OpKind::Call);

    if (!readVarU32(calleeIndex))
        return fail("unable to read call function index");

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readCallIndirect(uint32_t* sigIndex, Value* callee)
{
    MOZ_ASSERT(Classify(op_) == OpKind::CallIndirect);

    if (!readVarU32(sigIndex))
        return fail("unable to read call_indirect signature index");

    uint32_t flags;
    if (!readVarU32(&flags))
        return false;

    if (Validate && flags != uint32_t(MemoryTableFlags::Default))
        return fail("unexpected flags");

    if (reachable_) {
        if (!popWithType(ValType::I32, callee))
            return false;
    }

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readOldCallIndirect(uint32_t* sigIndex)
{
    MOZ_ASSERT(Classify(op_) == OpKind::OldCallIndirect);

    if (!readVarU32(sigIndex))
        return fail("unable to read call_indirect signature index");

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readCallArg(ValType type, uint32_t numArgs, uint32_t argIndex, Value* arg)
{
    MOZ_ASSERT(reachable_);

    TypeAndValue<Value> tv;

    if (!peek(numArgs - argIndex, &tv))
        return false;
    if (!checkType(tv.type(), type))
        return false;

    if (Output)
        *arg = tv.value();

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readCallArgsEnd(uint32_t numArgs)
{
    MOZ_ASSERT(reachable_);
    MOZ_ASSERT(numArgs <= valueStack_.length());

    valueStack_.shrinkBy(numArgs);

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readOldCallIndirectCallee(Value* callee)
{
    MOZ_ASSERT(Classify(op_) == OpKind::OldCallIndirect);
    MOZ_ASSERT(reachable_);

    if (!popWithType(ValType::I32, callee))
        return false;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readCallReturn(ExprType ret)
{
    MOZ_ASSERT(reachable_);

    if (!IsVoid(ret)) {
        if (!push(NonVoidToValType(ret)))
            return false;
    }

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readAtomicLoad(LinearMemoryAddress<Value>* addr, Scalar::Type* viewType)
{
    MOZ_ASSERT(Classify(op_) == OpKind::AtomicLoad);

    Scalar::Type validateViewType;
    if (!readAtomicViewType(&validateViewType))
        return false;

    uint32_t byteSize = Scalar::byteSize(validateViewType);
    if (!readLinearMemoryAddress(byteSize, addr))
        return false;

    infalliblePush(ValType::I32);

    if (Output)
        *viewType = validateViewType;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readAtomicStore(LinearMemoryAddress<Value>* addr, Scalar::Type* viewType,
                                Value* value)
{
    MOZ_ASSERT(Classify(op_) == OpKind::AtomicStore);

    Scalar::Type validateViewType;
    if (!readAtomicViewType(&validateViewType))
        return false;

    uint32_t byteSize = Scalar::byteSize(validateViewType);
    if (!readLinearMemoryAddress(byteSize, addr))
        return false;

    if (!popWithType(ValType::I32, value))
        return false;

    infalliblePush(ValType::I32);

    if (Output)
        *viewType = validateViewType;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readAtomicBinOp(LinearMemoryAddress<Value>* addr, Scalar::Type* viewType,
                                jit::AtomicOp* op, Value* value)
{
    MOZ_ASSERT(Classify(op_) == OpKind::AtomicBinOp);

    Scalar::Type validateViewType;
    if (!readAtomicViewType(&validateViewType))
        return false;

    if (!readAtomicBinOpOp(op))
        return false;

    uint32_t byteSize = Scalar::byteSize(validateViewType);
    if (!readLinearMemoryAddress(byteSize, addr))
        return false;

    if (!popWithType(ValType::I32, value))
        return false;

    infalliblePush(ValType::I32);

    if (Output)
        *viewType = validateViewType;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readAtomicCompareExchange(LinearMemoryAddress<Value>* addr, Scalar::Type* viewType,
                                          Value* oldValue, Value* newValue)
{
    MOZ_ASSERT(Classify(op_) == OpKind::AtomicCompareExchange);

    Scalar::Type validateViewType;
    if (!readAtomicViewType(&validateViewType))
        return false;

    uint32_t byteSize = Scalar::byteSize(validateViewType);
    if (!readLinearMemoryAddress(byteSize, addr))
        return false;

    if (!popWithType(ValType::I32, newValue))
        return false;

    if (!popWithType(ValType::I32, oldValue))
        return false;

    infalliblePush(ValType::I32);

    if (Output)
        *viewType = validateViewType;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readAtomicExchange(LinearMemoryAddress<Value>* addr, Scalar::Type* viewType,
                                   Value* value)
{
    MOZ_ASSERT(Classify(op_) == OpKind::AtomicExchange);

    Scalar::Type validateViewType;
    if (!readAtomicViewType(&validateViewType))
        return false;

    uint32_t byteSize = Scalar::byteSize(validateViewType);
    if (!readLinearMemoryAddress(byteSize, addr))
        return false;

    if (!popWithType(ValType::I32, value))
        return false;

    infalliblePush(ValType::I32);

    if (Output)
        *viewType = validateViewType;

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readSimdComparison(ValType simdType, Value* lhs, Value* rhs)
{
    MOZ_ASSERT(Classify(op_) == OpKind::SimdComparison);

    if (!popWithType(simdType, rhs))
        return false;

    if (!popWithType(simdType, lhs))
        return false;

    infalliblePush(SimdBoolType(simdType));

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readSimdShiftByScalar(ValType simdType, Value* lhs, Value* rhs)
{
    MOZ_ASSERT(Classify(op_) == OpKind::SimdShiftByScalar);

    if (!popWithType(ValType::I32, rhs))
        return false;

    if (!popWithType(simdType, lhs))
        return false;

    infalliblePush(simdType);

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readSimdBooleanReduction(ValType simdType, Value* input)
{
    MOZ_ASSERT(Classify(op_) == OpKind::SimdBooleanReduction);

    if (!popWithType(simdType, input))
        return false;

    infalliblePush(ValType::I32);

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readExtractLane(ValType simdType, uint8_t* lane, Value* vector)
{
    MOZ_ASSERT(Classify(op_) == OpKind::ExtractLane);

    uint32_t laneBits;
    if (!readVarU32(&laneBits))
        return false;

    if (Validate && laneBits >= NumSimdElements(simdType))
        return fail("simd lane out of bounds for simd type");

    if (!popWithType(simdType, vector))
        return false;

    infalliblePush(SimdElementType(simdType));

    if (Output)
        *lane = uint8_t(laneBits);

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readReplaceLane(ValType simdType, uint8_t* lane, Value* vector, Value* scalar)
{
    MOZ_ASSERT(Classify(op_) == OpKind::ReplaceLane);

    uint32_t laneBits;
    if (!readVarU32(&laneBits))
        return false;

    if (Validate && laneBits >= NumSimdElements(simdType))
        return fail("simd lane out of bounds for simd type");

    if (!popWithType(SimdElementType(simdType), scalar))
        return false;

    if (!popWithType(simdType, vector))
        return false;

    infalliblePush(simdType);

    if (Output)
        *lane = uint8_t(laneBits);

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readSplat(ValType simdType, Value* scalar)
{
    MOZ_ASSERT(Classify(op_) == OpKind::Splat);

    if (!popWithType(SimdElementType(simdType), scalar))
        return false;

    infalliblePush(simdType);

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readSwizzle(ValType simdType, uint8_t (* lanes)[16], Value* vector)
{
    MOZ_ASSERT(Classify(op_) == OpKind::Swizzle);

    uint32_t numSimdLanes = NumSimdElements(simdType);
    MOZ_ASSERT(numSimdLanes <= mozilla::ArrayLength(*lanes));
    for (uint32_t i = 0; i < numSimdLanes; ++i) {
        uint8_t validateLane;
        if (!readFixedU8(Output ? &(*lanes)[i] : &validateLane))
            return fail("unable to read swizzle lane");
        if (Validate && (Output ? (*lanes)[i] : validateLane) >= numSimdLanes)
            return fail("swizzle index out of bounds");
    }

    if (!popWithType(simdType, vector))
        return false;

    infalliblePush(simdType);

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readShuffle(ValType simdType, uint8_t (* lanes)[16], Value* lhs, Value* rhs)
{
    MOZ_ASSERT(Classify(op_) == OpKind::Shuffle);

    uint32_t numSimdLanes = NumSimdElements(simdType);
    MOZ_ASSERT(numSimdLanes <= mozilla::ArrayLength(*lanes));
    for (uint32_t i = 0; i < numSimdLanes; ++i) {
        uint8_t validateLane;
        if (!readFixedU8(Output ? &(*lanes)[i] : &validateLane))
            return fail("unable to read shuffle lane");
        if (Validate && (Output ? (*lanes)[i] : validateLane) >= numSimdLanes * 2)
            return fail("shuffle index out of bounds");
    }

    if (!popWithType(simdType, rhs))
        return false;

    if (!popWithType(simdType, lhs))
        return false;

    infalliblePush(simdType);

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readSimdSelect(ValType simdType, Value* trueValue, Value* falseValue,
                               Value* condition)
{
    MOZ_ASSERT(Classify(op_) == OpKind::SimdSelect);

    if (!popWithType(simdType, falseValue))
        return false;
    if (!popWithType(simdType, trueValue))
        return false;
    if (!popWithType(SimdBoolType(simdType), condition))
        return false;

    infalliblePush(simdType);

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readSimdCtor()
{
    MOZ_ASSERT(Classify(op_) == OpKind::SimdCtor);

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readSimdCtorArg(ValType elementType, uint32_t numElements, uint32_t index,
                                Value* arg)
{
    MOZ_ASSERT(Classify(op_) == OpKind::SimdCtor);
    MOZ_ASSERT(numElements > 0);

    TypeAndValue<Value> tv;

    if (!peek(numElements - index, &tv))
        return false;
    if (!checkType(tv.type(), elementType))
        return false;

    *arg = tv.value();
    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readSimdCtorArgsEnd(uint32_t numElements)
{
    MOZ_ASSERT(Classify(op_) == OpKind::SimdCtor);
    MOZ_ASSERT(numElements <= valueStack_.length());

    valueStack_.shrinkBy(numElements);

    return true;
}

template <typename Policy>
inline bool
OpIter<Policy>::readSimdCtorReturn(ValType simdType)
{
    MOZ_ASSERT(Classify(op_) == OpKind::SimdCtor);

    infalliblePush(simdType);

    return true;
}

} // namespace wasm
} // namespace js

namespace mozilla {

// Specialize IsPod for the Nothing specializations.
template<> struct IsPod<js::wasm::TypeAndValue<Nothing>> : TrueType {};
template<> struct IsPod<js::wasm::ControlStackEntry<Nothing>> : TrueType {};

} // namespace mozilla

#endif // wasm_iterator_h
