diff options
Diffstat (limited to 'js/src/wasm/WasmBinaryIterator.h')
-rw-r--r-- | js/src/wasm/WasmBinaryIterator.h | 2246 |
1 files changed, 2246 insertions, 0 deletions
diff --git a/js/src/wasm/WasmBinaryIterator.h b/js/src/wasm/WasmBinaryIterator.h new file mode 100644 index 000000000..76e0c2875 --- /dev/null +++ b/js/src/wasm/WasmBinaryIterator.h @@ -0,0 +1,2246 @@ +/* -*- 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 |