summaryrefslogtreecommitdiffstats
path: root/js/src/wasm/WasmOpIter.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/wasm/WasmOpIter.h')
-rw-r--r--js/src/wasm/WasmOpIter.h4239
1 files changed, 4239 insertions, 0 deletions
diff --git a/js/src/wasm/WasmOpIter.h b/js/src/wasm/WasmOpIter.h
new file mode 100644
index 0000000000..c175921dc9
--- /dev/null
+++ b/js/src/wasm/WasmOpIter.h
@@ -0,0 +1,4239 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ *
+ * 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_op_iter_h
+#define wasm_op_iter_h
+
+#include "mozilla/CompactPair.h"
+#include "mozilla/Poison.h"
+
+#include <type_traits>
+
+#include "js/Printf.h"
+#include "wasm/WasmIntrinsic.h"
+#include "wasm/WasmUtility.h"
+#include "wasm/WasmValidate.h"
+
+namespace js {
+namespace wasm {
+
+// The kind of a control-flow stack item.
+enum class LabelKind : uint8_t {
+ Body,
+ Block,
+ Loop,
+ Then,
+ Else,
+ Try,
+ Catch,
+ CatchAll,
+};
+
+// The type of values on the operand stack during validation. This is either a
+// ValType or the special type "Bottom".
+
+class StackType {
+ PackedTypeCode tc_;
+
+ explicit StackType(PackedTypeCode tc) : tc_(tc) {}
+
+ public:
+ StackType() : tc_(PackedTypeCode::invalid()) {}
+
+ explicit StackType(const ValType& t) : tc_(t.packed()) {
+ MOZ_ASSERT(tc_.isValid());
+ MOZ_ASSERT(!isStackBottom());
+ }
+
+ static StackType bottom() {
+ return StackType(PackedTypeCode::pack(TypeCode::Limit));
+ }
+
+ bool isStackBottom() const {
+ MOZ_ASSERT(tc_.isValid());
+ return tc_.typeCode() == TypeCode::Limit;
+ }
+
+ // Returns whether this input is nullable when interpreted as an operand.
+ // When the type is bottom for unreachable code, this returns false as that
+ // is the most permissive option.
+ bool isNullableAsOperand() const {
+ MOZ_ASSERT(tc_.isValid());
+ return isStackBottom() ? false : tc_.isNullable();
+ }
+
+ ValType valType() const {
+ MOZ_ASSERT(tc_.isValid());
+ MOZ_ASSERT(!isStackBottom());
+ return ValType(tc_);
+ }
+
+ ValType valTypeOr(ValType ifBottom) const {
+ MOZ_ASSERT(tc_.isValid());
+ if (isStackBottom()) {
+ return ifBottom;
+ }
+ return valType();
+ }
+
+ ValType asNonNullable() const {
+ MOZ_ASSERT(tc_.isValid());
+ MOZ_ASSERT(!isStackBottom());
+ return ValType(tc_.withIsNullable(false));
+ }
+
+ bool isValidForUntypedSelect() const {
+ MOZ_ASSERT(tc_.isValid());
+ if (isStackBottom()) {
+ return true;
+ }
+ switch (valType().kind()) {
+ case ValType::I32:
+ case ValType::F32:
+ case ValType::I64:
+ case ValType::F64:
+#ifdef ENABLE_WASM_SIMD
+ case ValType::V128:
+#endif
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ bool operator==(const StackType& that) const {
+ MOZ_ASSERT(tc_.isValid() && that.tc_.isValid());
+ return tc_ == that.tc_;
+ }
+
+ bool operator!=(const StackType& that) const {
+ MOZ_ASSERT(tc_.isValid() && that.tc_.isValid());
+ return tc_ != that.tc_;
+ }
+};
+
+#ifdef DEBUG
+// Families of opcodes that share a signature and validation logic.
+enum class OpKind {
+ Block,
+ Loop,
+ Unreachable,
+ Drop,
+ I32,
+ I64,
+ F32,
+ F64,
+ V128,
+ Br,
+ BrIf,
+ BrTable,
+ Nop,
+ Unary,
+ Binary,
+ Ternary,
+ Comparison,
+ Conversion,
+ Load,
+ Store,
+ TeeStore,
+ MemorySize,
+ MemoryGrow,
+ Select,
+ GetLocal,
+ SetLocal,
+ TeeLocal,
+ GetGlobal,
+ SetGlobal,
+ TeeGlobal,
+ Call,
+ CallIndirect,
+# ifdef ENABLE_WASM_FUNCTION_REFERENCES
+ CallRef,
+# endif
+ OldCallDirect,
+ OldCallIndirect,
+ Return,
+ If,
+ Else,
+ End,
+ Wait,
+ Wake,
+ Fence,
+ AtomicLoad,
+ AtomicStore,
+ AtomicBinOp,
+ AtomicCompareExchange,
+ MemOrTableCopy,
+ DataOrElemDrop,
+ MemFill,
+ MemOrTableInit,
+ TableFill,
+ MemDiscard,
+ TableGet,
+ TableGrow,
+ TableSet,
+ TableSize,
+ RefNull,
+ RefFunc,
+ RefAsNonNull,
+ BrOnNull,
+ BrOnNonNull,
+ StructNew,
+ StructNewDefault,
+ StructGet,
+ StructSet,
+ ArrayNew,
+ ArrayNewFixed,
+ ArrayNewDefault,
+ ArrayNewData,
+ ArrayNewElem,
+ ArrayGet,
+ ArraySet,
+ ArrayLen,
+ ArrayCopy,
+ RefTestV5,
+ RefCastV5,
+ BrOnCastV5,
+ BrOnCastFailV5,
+ BrOnNonStructV5,
+ RefTest,
+ RefCast,
+ BrOnCast,
+ RefConversion,
+# ifdef ENABLE_WASM_SIMD
+ ExtractLane,
+ ReplaceLane,
+ LoadLane,
+ StoreLane,
+ VectorShift,
+ VectorShuffle,
+# endif
+ Catch,
+ CatchAll,
+ Delegate,
+ Throw,
+ Rethrow,
+ Try,
+ Intrinsic,
+};
+
+// 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(OpBytes op);
+#endif
+
+// Common fields for linear memory access.
+template <typename Value>
+struct LinearMemoryAddress {
+ Value base;
+ uint64_t offset;
+ uint32_t align;
+
+ LinearMemoryAddress() : offset(0), align(0) {}
+ LinearMemoryAddress(Value base, uint64_t offset, uint32_t align)
+ : base(base), offset(offset), align(align) {}
+};
+
+template <typename ControlItem>
+class ControlStackEntry {
+ // Use a pair to optimize away empty ControlItem.
+ mozilla::CompactPair<BlockType, ControlItem> typeAndItem_;
+
+ // The "base" of a control stack entry is valueStack_.length() minus
+ // type().params().length(), i.e., the size of the value stack "below"
+ // this block.
+ uint32_t valueStackBase_;
+ bool polymorphicBase_;
+
+ LabelKind kind_;
+
+ public:
+ ControlStackEntry(LabelKind kind, BlockType type, uint32_t valueStackBase)
+ : typeAndItem_(type, ControlItem()),
+ valueStackBase_(valueStackBase),
+ polymorphicBase_(false),
+ kind_(kind) {
+ MOZ_ASSERT(type != BlockType());
+ }
+
+ LabelKind kind() const { return kind_; }
+ BlockType type() const { return typeAndItem_.first(); }
+ ResultType resultType() const { return type().results(); }
+ ResultType branchTargetType() const {
+ return kind_ == LabelKind::Loop ? type().params() : type().results();
+ }
+ uint32_t valueStackBase() const { return valueStackBase_; }
+ ControlItem& controlItem() { return typeAndItem_.second(); }
+ void setPolymorphicBase() { polymorphicBase_ = true; }
+ bool polymorphicBase() const { return polymorphicBase_; }
+
+ void switchToElse() {
+ MOZ_ASSERT(kind() == LabelKind::Then);
+ kind_ = LabelKind::Else;
+ polymorphicBase_ = false;
+ }
+
+ void switchToCatch() {
+ MOZ_ASSERT(kind() == LabelKind::Try || kind() == LabelKind::Catch);
+ kind_ = LabelKind::Catch;
+ polymorphicBase_ = false;
+ }
+
+ void switchToCatchAll() {
+ MOZ_ASSERT(kind() == LabelKind::Try || kind() == LabelKind::Catch);
+ kind_ = LabelKind::CatchAll;
+ polymorphicBase_ = false;
+ }
+};
+
+// Track state of the non-defaultable locals. Every time such local is
+// initialized, the stack will record at what depth and which local was set.
+// On a block end, the "unset" state will be rolled back to how it was before
+// the block started.
+//
+// It is very likely only a few functions will have non-defaultable locals and
+// very few locals will be non-defaultable. This class is optimized to be fast
+// for this common case.
+class UnsetLocalsState {
+ struct SetLocalEntry {
+ uint32_t depth;
+ uint32_t localUnsetIndex;
+ SetLocalEntry(uint32_t depth_, uint32_t localUnsetIndex_)
+ : depth(depth_), localUnsetIndex(localUnsetIndex_) {}
+ };
+ using SetLocalsStack = Vector<SetLocalEntry, 16, SystemAllocPolicy>;
+ using UnsetLocals = Vector<uint32_t, 16, SystemAllocPolicy>;
+
+ static constexpr size_t WordSize = 4;
+ static constexpr size_t WordBits = WordSize * 8;
+
+ // Bit array of "unset" function locals. Stores only unset states of the
+ // locals that are declared after the first non-defaultable local.
+ UnsetLocals unsetLocals_;
+ // Stack of "set" operations. Contains pair where the first field is a depth,
+ // and the second field is local id (offset by firstNonDefaultLocal_).
+ SetLocalsStack setLocalsStack_;
+ uint32_t firstNonDefaultLocal_;
+
+ public:
+ UnsetLocalsState() : firstNonDefaultLocal_(UINT32_MAX) {}
+
+ [[nodiscard]] bool init(const ValTypeVector& locals, size_t numParams);
+
+ inline bool isUnset(uint32_t id) const {
+ if (MOZ_LIKELY(id < firstNonDefaultLocal_)) {
+ return false;
+ }
+ uint32_t localUnsetIndex = id - firstNonDefaultLocal_;
+ return unsetLocals_[localUnsetIndex / WordBits] &
+ (1 << (localUnsetIndex % WordBits));
+ }
+
+ inline void set(uint32_t id, uint32_t depth) {
+ MOZ_ASSERT(isUnset(id));
+ MOZ_ASSERT(id >= firstNonDefaultLocal_ &&
+ (id - firstNonDefaultLocal_) / WordBits < unsetLocals_.length());
+ uint32_t localUnsetIndex = id - firstNonDefaultLocal_;
+ unsetLocals_[localUnsetIndex / WordBits] ^= 1
+ << (localUnsetIndex % WordBits);
+ // The setLocalsStack_ is reserved upfront in the UnsetLocalsState::init.
+ // A SetLocalEntry will be pushed only once per local.
+ setLocalsStack_.infallibleEmplaceBack(depth, localUnsetIndex);
+ }
+
+ inline void resetToBlock(uint32_t controlDepth) {
+ while (MOZ_UNLIKELY(setLocalsStack_.length() > 0) &&
+ setLocalsStack_.back().depth > controlDepth) {
+ uint32_t localUnsetIndex = setLocalsStack_.back().localUnsetIndex;
+ MOZ_ASSERT(!(unsetLocals_[localUnsetIndex / WordBits] &
+ (1 << (localUnsetIndex % WordBits))));
+ unsetLocals_[localUnsetIndex / WordBits] |=
+ 1 << (localUnsetIndex % WordBits);
+ setLocalsStack_.popBack();
+ }
+ }
+
+ int empty() const { return setLocalsStack_.empty(); }
+};
+
+template <typename Value>
+class TypeAndValueT {
+ // Use a Pair to optimize away empty Value.
+ mozilla::CompactPair<StackType, Value> tv_;
+
+ public:
+ TypeAndValueT() : tv_(StackType::bottom(), Value()) {}
+ explicit TypeAndValueT(StackType type) : tv_(type, Value()) {}
+ explicit TypeAndValueT(ValType type) : tv_(StackType(type), Value()) {}
+ TypeAndValueT(StackType type, Value value) : tv_(type, value) {}
+ TypeAndValueT(ValType type, Value value) : tv_(StackType(type), value) {}
+ StackType type() const { return tv_.first(); }
+ void setType(StackType type) { tv_.first() = type; }
+ Value value() const { return tv_.second(); }
+ void setValue(Value value) { tv_.second() = value; }
+};
+
+// An iterator over the bytes of a function body. It performs validation
+// 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 {
+ public:
+ using Value = typename Policy::Value;
+ using ValueVector = typename Policy::ValueVector;
+ using TypeAndValue = TypeAndValueT<Value>;
+ using TypeAndValueStack = Vector<TypeAndValue, 32, SystemAllocPolicy>;
+ using ControlItem = typename Policy::ControlItem;
+ using Control = ControlStackEntry<ControlItem>;
+ using ControlStack = Vector<Control, 16, SystemAllocPolicy>;
+
+ enum Kind {
+ Func,
+ InitExpr,
+ };
+
+ private:
+ Kind kind_;
+ Decoder& d_;
+ const ModuleEnvironment& env_;
+
+ TypeAndValueStack valueStack_;
+ TypeAndValueStack elseParamStack_;
+ ControlStack controlStack_;
+ UnsetLocalsState unsetLocals_;
+ // The exclusive max index of a global that can be accessed by global.get in
+ // this expression. When GC is enabled, this is any previously defined
+ // global. Otherwise this is always set to zero, and only imported immutable
+ // globals are allowed.
+ uint32_t maxInitializedGlobalsIndexPlus1_;
+
+#ifdef DEBUG
+ OpBytes op_;
+#endif
+ size_t offsetOfLastReadOp_;
+
+ [[nodiscard]] bool readFixedU8(uint8_t* out) { return d_.readFixedU8(out); }
+ [[nodiscard]] bool readFixedU32(uint32_t* out) {
+ return d_.readFixedU32(out);
+ }
+ [[nodiscard]] bool readVarS32(int32_t* out) { return d_.readVarS32(out); }
+ [[nodiscard]] bool readVarU32(uint32_t* out) { return d_.readVarU32(out); }
+ [[nodiscard]] bool readVarS64(int64_t* out) { return d_.readVarS64(out); }
+ [[nodiscard]] bool readVarU64(uint64_t* out) { return d_.readVarU64(out); }
+ [[nodiscard]] bool readFixedF32(float* out) { return d_.readFixedF32(out); }
+ [[nodiscard]] bool readFixedF64(double* out) { return d_.readFixedF64(out); }
+
+ [[nodiscard]] bool readMemOrTableIndex(bool isMem, uint32_t* index);
+ [[nodiscard]] bool readLinearMemoryAddress(uint32_t byteSize,
+ LinearMemoryAddress<Value>* addr);
+ [[nodiscard]] bool readLinearMemoryAddressAligned(
+ uint32_t byteSize, LinearMemoryAddress<Value>* addr);
+ [[nodiscard]] bool readBlockType(BlockType* type);
+ [[nodiscard]] bool readGcTypeIndex(uint32_t* typeIndex);
+ [[nodiscard]] bool readStructTypeIndex(uint32_t* typeIndex);
+ [[nodiscard]] bool readArrayTypeIndex(uint32_t* typeIndex);
+ [[nodiscard]] bool readFuncTypeIndex(uint32_t* typeIndex);
+ [[nodiscard]] bool readFieldIndex(uint32_t* fieldIndex,
+ const StructType& structType);
+
+ [[nodiscard]] bool popCallArgs(const ValTypeVector& expectedTypes,
+ ValueVector* values);
+
+ [[nodiscard]] bool failEmptyStack();
+ [[nodiscard]] bool popStackType(StackType* type, Value* value);
+ [[nodiscard]] bool popWithType(ValType expected, Value* value,
+ StackType* stackType);
+ [[nodiscard]] bool popWithType(ValType expected, Value* value);
+ [[nodiscard]] bool popWithType(ResultType expected, ValueVector* values);
+ template <typename ValTypeSpanT>
+ [[nodiscard]] bool popWithTypes(ValTypeSpanT expected, ValueVector* values);
+ [[nodiscard]] bool popWithRefType(Value* value, StackType* type);
+ // Check that the top of the value stack has type `expected`, bearing in
+ // mind that it may be a block type, hence involving multiple values.
+ //
+ // If the block's stack contains polymorphic values at its base (because we
+ // are in unreachable code) then suitable extra values are inserted into the
+ // value stack, as controlled by `rewriteStackTypes`: if this is true,
+ // polymorphic values have their types created/updated from `expected`. If
+ // it is false, such values are left as `StackType::bottom()`.
+ //
+ // If `values` is non-null, it is filled in with Value components of the
+ // relevant stack entries, including those of any new entries created.
+ [[nodiscard]] bool checkTopTypeMatches(ResultType expected,
+ ValueVector* values,
+ bool rewriteStackTypes);
+
+ [[nodiscard]] bool pushControl(LabelKind kind, BlockType type);
+ [[nodiscard]] bool checkStackAtEndOfBlock(ResultType* type,
+ ValueVector* values);
+ [[nodiscard]] bool getControl(uint32_t relativeDepth, Control** controlEntry);
+ [[nodiscard]] bool checkBranchValueAndPush(uint32_t relativeDepth,
+ ResultType* type,
+ ValueVector* values);
+ [[nodiscard]] bool checkBrTableEntryAndPush(uint32_t* relativeDepth,
+ ResultType prevBranchType,
+ ResultType* branchType,
+ ValueVector* branchValues);
+
+ [[nodiscard]] bool push(StackType t) { return valueStack_.emplaceBack(t); }
+ [[nodiscard]] bool push(ValType t) { return valueStack_.emplaceBack(t); }
+ [[nodiscard]] bool push(TypeAndValue tv) { return valueStack_.append(tv); }
+ [[nodiscard]] bool push(ResultType t) {
+ for (size_t i = 0; i < t.length(); i++) {
+ if (!push(t[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+ void infalliblePush(StackType t) { valueStack_.infallibleEmplaceBack(t); }
+ void infalliblePush(ValType t) {
+ valueStack_.infallibleEmplaceBack(StackType(t));
+ }
+ void infalliblePush(TypeAndValue tv) { valueStack_.infallibleAppend(tv); }
+
+ void afterUnconditionalBranch() {
+ valueStack_.shrinkTo(controlStack_.back().valueStackBase());
+ controlStack_.back().setPolymorphicBase();
+ }
+
+ inline bool checkIsSubtypeOf(FieldType actual, FieldType expected);
+
+ inline bool checkIsSubtypeOf(RefType actual, RefType expected) {
+ return checkIsSubtypeOf(ValType(actual).fieldType(),
+ ValType(expected).fieldType());
+ }
+ inline bool checkIsSubtypeOf(ValType actual, ValType expected) {
+ return checkIsSubtypeOf(actual.fieldType(), expected.fieldType());
+ }
+
+ inline bool checkIsSubtypeOf(ResultType params, ResultType results);
+
+#ifdef ENABLE_WASM_FUNCTION_REFERENCES
+ inline bool checkIsSubtypeOf(uint32_t actualTypeIndex,
+ uint32_t expectedTypeIndex);
+#endif
+
+ public:
+#ifdef DEBUG
+ explicit OpIter(const ModuleEnvironment& env, Decoder& decoder,
+ Kind kind = OpIter::Func)
+ : kind_(kind),
+ d_(decoder),
+ env_(env),
+ maxInitializedGlobalsIndexPlus1_(0),
+ op_(OpBytes(Op::Limit)),
+ offsetOfLastReadOp_(0) {}
+#else
+ explicit OpIter(const ModuleEnvironment& env, Decoder& decoder,
+ Kind kind = OpIter::Func)
+ : kind_(kind),
+ d_(decoder),
+ env_(env),
+ maxInitializedGlobalsIndexPlus1_(0),
+ offsetOfLastReadOp_(0) {}
+#endif
+
+ // Return the decoding byte offset.
+ uint32_t currentOffset() const { return d_.currentOffset(); }
+
+ // Return the offset within the entire module of the last-read op.
+ size_t lastOpcodeOffset() const {
+ return offsetOfLastReadOp_ ? offsetOfLastReadOp_ : d_.currentOffset();
+ }
+
+ // Return a BytecodeOffset describing where the current op should be reported
+ // to trap/call.
+ BytecodeOffset bytecodeOffset() const {
+ return BytecodeOffset(lastOpcodeOffset());
+ }
+
+ // Test whether the iterator has reached the end of the buffer.
+ bool done() const { return d_.done(); }
+
+ // Return a pointer to the end of the buffer being decoded by this iterator.
+ const uint8_t* end() const { return d_.end(); }
+
+ // Report a general failure.
+ [[nodiscard]] bool fail(const char* msg) MOZ_COLD;
+
+ // Report a general failure with a context
+ [[nodiscard]] bool fail_ctx(const char* fmt, const char* context) MOZ_COLD;
+
+ // Report an unrecognized opcode.
+ [[nodiscard]] bool unrecognizedOpcode(const OpBytes* expr) MOZ_COLD;
+
+ // Return whether the innermost block has a polymorphic base of its stack.
+ // Ideally this accessor would be removed; consider using something else.
+ bool currentBlockHasPolymorphicBase() const {
+ return !controlStack_.empty() && controlStack_.back().polymorphicBase();
+ }
+
+ // ------------------------------------------------------------------------
+ // Decoding and validation interface.
+
+ // Initialization and termination
+
+ [[nodiscard]] bool startFunction(uint32_t funcIndex,
+ const ValTypeVector& locals);
+ [[nodiscard]] bool endFunction(const uint8_t* bodyEnd);
+
+ [[nodiscard]] bool startInitExpr(ValType expected,
+ uint32_t maxInitializedGlobalsIndexPlus1);
+ [[nodiscard]] bool endInitExpr();
+
+ // Value and reference types
+
+ [[nodiscard]] bool readValType(ValType* type);
+ [[nodiscard]] bool readHeapType(bool nullable, RefType* type);
+
+ // Instructions
+
+ [[nodiscard]] bool readOp(OpBytes* op);
+ [[nodiscard]] bool readReturn(ValueVector* values);
+ [[nodiscard]] bool readBlock(ResultType* paramType);
+ [[nodiscard]] bool readLoop(ResultType* paramType);
+ [[nodiscard]] bool readIf(ResultType* paramType, Value* condition);
+ [[nodiscard]] bool readElse(ResultType* paramType, ResultType* resultType,
+ ValueVector* thenResults);
+ [[nodiscard]] bool readEnd(LabelKind* kind, ResultType* type,
+ ValueVector* results,
+ ValueVector* resultsForEmptyElse);
+ void popEnd();
+ [[nodiscard]] bool readBr(uint32_t* relativeDepth, ResultType* type,
+ ValueVector* values);
+ [[nodiscard]] bool readBrIf(uint32_t* relativeDepth, ResultType* type,
+ ValueVector* values, Value* condition);
+ [[nodiscard]] bool readBrTable(Uint32Vector* depths, uint32_t* defaultDepth,
+ ResultType* defaultBranchType,
+ ValueVector* branchValues, Value* index);
+ [[nodiscard]] bool readTry(ResultType* type);
+ [[nodiscard]] bool readCatch(LabelKind* kind, uint32_t* tagIndex,
+ ResultType* paramType, ResultType* resultType,
+ ValueVector* tryResults);
+ [[nodiscard]] bool readCatchAll(LabelKind* kind, ResultType* paramType,
+ ResultType* resultType,
+ ValueVector* tryResults);
+ [[nodiscard]] bool readDelegate(uint32_t* relativeDepth,
+ ResultType* resultType,
+ ValueVector* tryResults);
+ void popDelegate();
+ [[nodiscard]] bool readThrow(uint32_t* tagIndex, ValueVector* argValues);
+ [[nodiscard]] bool readRethrow(uint32_t* relativeDepth);
+ [[nodiscard]] bool readUnreachable();
+ [[nodiscard]] bool readDrop();
+ [[nodiscard]] bool readUnary(ValType operandType, Value* input);
+ [[nodiscard]] bool readConversion(ValType operandType, ValType resultType,
+ Value* input);
+ [[nodiscard]] bool readBinary(ValType operandType, Value* lhs, Value* rhs);
+ [[nodiscard]] bool readComparison(ValType operandType, Value* lhs,
+ Value* rhs);
+ [[nodiscard]] bool readTernary(ValType operandType, Value* v0, Value* v1,
+ Value* v2);
+ [[nodiscard]] bool readLoad(ValType resultType, uint32_t byteSize,
+ LinearMemoryAddress<Value>* addr);
+ [[nodiscard]] bool readStore(ValType resultType, uint32_t byteSize,
+ LinearMemoryAddress<Value>* addr, Value* value);
+ [[nodiscard]] bool readTeeStore(ValType resultType, uint32_t byteSize,
+ LinearMemoryAddress<Value>* addr,
+ Value* value);
+ [[nodiscard]] bool readNop();
+ [[nodiscard]] bool readMemorySize();
+ [[nodiscard]] bool readMemoryGrow(Value* input);
+ [[nodiscard]] bool readSelect(bool typed, StackType* type, Value* trueValue,
+ Value* falseValue, Value* condition);
+ [[nodiscard]] bool readGetLocal(const ValTypeVector& locals, uint32_t* id);
+ [[nodiscard]] bool readSetLocal(const ValTypeVector& locals, uint32_t* id,
+ Value* value);
+ [[nodiscard]] bool readTeeLocal(const ValTypeVector& locals, uint32_t* id,
+ Value* value);
+ [[nodiscard]] bool readGetGlobal(uint32_t* id);
+ [[nodiscard]] bool readSetGlobal(uint32_t* id, Value* value);
+ [[nodiscard]] bool readTeeGlobal(uint32_t* id, Value* value);
+ [[nodiscard]] bool readI32Const(int32_t* i32);
+ [[nodiscard]] bool readI64Const(int64_t* i64);
+ [[nodiscard]] bool readF32Const(float* f32);
+ [[nodiscard]] bool readF64Const(double* f64);
+ [[nodiscard]] bool readRefFunc(uint32_t* funcIndex);
+ [[nodiscard]] bool readRefNull(RefType* type);
+ [[nodiscard]] bool readRefIsNull(Value* input);
+ [[nodiscard]] bool readRefAsNonNull(Value* input);
+ [[nodiscard]] bool readBrOnNull(uint32_t* relativeDepth, ResultType* type,
+ ValueVector* values, Value* condition);
+ [[nodiscard]] bool readBrOnNonNull(uint32_t* relativeDepth, ResultType* type,
+ ValueVector* values, Value* condition);
+ [[nodiscard]] bool readCall(uint32_t* funcTypeIndex, ValueVector* argValues);
+ [[nodiscard]] bool readCallIndirect(uint32_t* funcTypeIndex,
+ uint32_t* tableIndex, Value* callee,
+ ValueVector* argValues);
+#ifdef ENABLE_WASM_FUNCTION_REFERENCES
+ [[nodiscard]] bool readCallRef(const FuncType** funcType, Value* callee,
+ ValueVector* argValues);
+#endif
+ [[nodiscard]] bool readOldCallDirect(uint32_t numFuncImports,
+ uint32_t* funcTypeIndex,
+ ValueVector* argValues);
+ [[nodiscard]] bool readOldCallIndirect(uint32_t* funcTypeIndex, Value* callee,
+ ValueVector* argValues);
+ [[nodiscard]] bool readWake(LinearMemoryAddress<Value>* addr, Value* count);
+ [[nodiscard]] bool readWait(LinearMemoryAddress<Value>* addr,
+ ValType valueType, uint32_t byteSize,
+ Value* value, Value* timeout);
+ [[nodiscard]] bool readFence();
+ [[nodiscard]] bool readAtomicLoad(LinearMemoryAddress<Value>* addr,
+ ValType resultType, uint32_t byteSize);
+ [[nodiscard]] bool readAtomicStore(LinearMemoryAddress<Value>* addr,
+ ValType resultType, uint32_t byteSize,
+ Value* value);
+ [[nodiscard]] bool readAtomicRMW(LinearMemoryAddress<Value>* addr,
+ ValType resultType, uint32_t byteSize,
+ Value* value);
+ [[nodiscard]] bool readAtomicCmpXchg(LinearMemoryAddress<Value>* addr,
+ ValType resultType, uint32_t byteSize,
+ Value* oldValue, Value* newValue);
+ [[nodiscard]] bool readMemOrTableCopy(bool isMem,
+ uint32_t* dstMemOrTableIndex,
+ Value* dst,
+ uint32_t* srcMemOrTableIndex,
+ Value* src, Value* len);
+ [[nodiscard]] bool readDataOrElemDrop(bool isData, uint32_t* segIndex);
+ [[nodiscard]] bool readMemFill(Value* start, Value* val, Value* len);
+ [[nodiscard]] bool readMemOrTableInit(bool isMem, uint32_t* segIndex,
+ uint32_t* dstTableIndex, Value* dst,
+ Value* src, Value* len);
+ [[nodiscard]] bool readTableFill(uint32_t* tableIndex, Value* start,
+ Value* val, Value* len);
+ [[nodiscard]] bool readMemDiscard(Value* start, Value* len);
+ [[nodiscard]] bool readTableGet(uint32_t* tableIndex, Value* index);
+ [[nodiscard]] bool readTableGrow(uint32_t* tableIndex, Value* initValue,
+ Value* delta);
+ [[nodiscard]] bool readTableSet(uint32_t* tableIndex, Value* index,
+ Value* value);
+
+ [[nodiscard]] bool readTableSize(uint32_t* tableIndex);
+
+#ifdef ENABLE_WASM_GC
+ [[nodiscard]] bool readStructNew(uint32_t* typeIndex, ValueVector* argValues);
+ [[nodiscard]] bool readStructNewDefault(uint32_t* typeIndex);
+ [[nodiscard]] bool readStructGet(uint32_t* typeIndex, uint32_t* fieldIndex,
+ FieldWideningOp wideningOp, Value* ptr);
+ [[nodiscard]] bool readStructSet(uint32_t* typeIndex, uint32_t* fieldIndex,
+ Value* ptr, Value* val);
+ [[nodiscard]] bool readArrayNew(uint32_t* typeIndex, Value* numElements,
+ Value* argValue);
+ [[nodiscard]] bool readArrayNewFixed(uint32_t* typeIndex,
+ uint32_t* numElements,
+ ValueVector* values);
+ [[nodiscard]] bool readArrayNewDefault(uint32_t* typeIndex,
+ Value* numElements);
+ [[nodiscard]] bool readArrayNewData(uint32_t* typeIndex, uint32_t* segIndex,
+ Value* offset, Value* numElements);
+ [[nodiscard]] bool readArrayNewElem(uint32_t* typeIndex, uint32_t* segIndex,
+ Value* offset, Value* numElements);
+ [[nodiscard]] bool readArrayGet(uint32_t* typeIndex,
+ FieldWideningOp wideningOp, Value* index,
+ Value* ptr);
+ [[nodiscard]] bool readArraySet(uint32_t* typeIndex, Value* val, Value* index,
+ Value* ptr);
+ [[nodiscard]] bool readArrayLen(bool decodeIgnoredTypeIndex, Value* ptr);
+ [[nodiscard]] bool readArrayCopy(int32_t* elemSize, bool* elemsAreRefTyped,
+ Value* dstArray, Value* dstIndex,
+ Value* srcArray, Value* srcIndex,
+ Value* numElements);
+ [[nodiscard]] bool readRefTestV5(RefType* sourceType, uint32_t* typeIndex,
+ Value* ref);
+ [[nodiscard]] bool readRefCastV5(RefType* sourceType, uint32_t* typeIndex,
+ Value* ref);
+ [[nodiscard]] bool readBrOnCastV5(uint32_t* labelRelativeDepth,
+ RefType* sourceType,
+ uint32_t* castTypeIndex,
+ ResultType* labelType, ValueVector* values);
+ [[nodiscard]] bool readBrOnCastHeapV5(bool nullable,
+ uint32_t* labelRelativeDepth,
+ RefType* sourceType, RefType* destType,
+ ResultType* labelType,
+ ValueVector* values);
+ [[nodiscard]] bool readBrOnCastFailV5(uint32_t* labelRelativeDepth,
+ RefType* sourceType,
+ uint32_t* castTypeIndex,
+ ResultType* labelType,
+ ValueVector* values);
+ [[nodiscard]] bool readBrOnCastFailHeapV5(
+ bool nullable, uint32_t* labelRelativeDepth, RefType* sourceType,
+ RefType* destType, ResultType* labelType, ValueVector* values);
+ [[nodiscard]] bool readRefTest(bool nullable, RefType* sourceType,
+ RefType* destType, Value* ref);
+ [[nodiscard]] bool readRefCast(bool nullable, RefType* sourceType,
+ RefType* destType, Value* ref);
+ [[nodiscard]] bool readBrOnCast(bool* onSuccess, uint32_t* labelRelativeDepth,
+ RefType* sourceType, RefType* destType,
+ ResultType* labelType, ValueVector* values);
+ [[nodiscard]] bool checkBrOnCastCommonV5(uint32_t labelRelativeDepth,
+ RefType* sourceType,
+ ValType castToType,
+ ResultType* labelType,
+ ValueVector* values);
+ [[nodiscard]] bool checkBrOnCastFailCommonV5(uint32_t labelRelativeDepth,
+ RefType* sourceType,
+ ValType castToType,
+ ResultType* labelType,
+ ValueVector* values);
+ [[nodiscard]] bool readBrOnNonStructV5(uint32_t* labelRelativeDepth,
+ ResultType* labelType,
+ ValueVector* values);
+ [[nodiscard]] bool readRefConversion(RefType operandType, RefType resultType,
+ Value* operandValue);
+#endif
+
+#ifdef ENABLE_WASM_SIMD
+ [[nodiscard]] bool readLaneIndex(uint32_t inputLanes, uint32_t* laneIndex);
+ [[nodiscard]] bool readExtractLane(ValType resultType, uint32_t inputLanes,
+ uint32_t* laneIndex, Value* input);
+ [[nodiscard]] bool readReplaceLane(ValType operandType, uint32_t inputLanes,
+ uint32_t* laneIndex, Value* baseValue,
+ Value* operand);
+ [[nodiscard]] bool readVectorShift(Value* baseValue, Value* shift);
+ [[nodiscard]] bool readVectorShuffle(Value* v1, Value* v2, V128* selectMask);
+ [[nodiscard]] bool readV128Const(V128* value);
+ [[nodiscard]] bool readLoadSplat(uint32_t byteSize,
+ LinearMemoryAddress<Value>* addr);
+ [[nodiscard]] bool readLoadExtend(LinearMemoryAddress<Value>* addr);
+ [[nodiscard]] bool readLoadLane(uint32_t byteSize,
+ LinearMemoryAddress<Value>* addr,
+ uint32_t* laneIndex, Value* input);
+ [[nodiscard]] bool readStoreLane(uint32_t byteSize,
+ LinearMemoryAddress<Value>* addr,
+ uint32_t* laneIndex, Value* input);
+#endif
+
+ [[nodiscard]] bool readIntrinsic(const Intrinsic** intrinsic,
+ ValueVector* params);
+
+ // 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) in op->b0 if it can't read.
+ void peekOp(OpBytes* op);
+
+ // ------------------------------------------------------------------------
+ // Stack management.
+
+ // Set the top N result values.
+ void setResults(size_t count, const ValueVector& values) {
+ MOZ_ASSERT(valueStack_.length() >= count);
+ size_t base = valueStack_.length() - count;
+ for (size_t i = 0; i < count; i++) {
+ valueStack_[base + i].setValue(values[i]);
+ }
+ }
+
+ bool getResults(size_t count, ValueVector* values) {
+ MOZ_ASSERT(valueStack_.length() >= count);
+ if (!values->resize(count)) {
+ return false;
+ }
+ size_t base = valueStack_.length() - count;
+ for (size_t i = 0; i < count; i++) {
+ (*values)[i] = valueStack_[base + i].value();
+ }
+ return true;
+ }
+
+ // Set the result value of the current top-of-value-stack expression.
+ void setResult(Value value) { valueStack_.back().setValue(value); }
+
+ // Return the result value of the current top-of-value-stack expression.
+ Value getResult() { return valueStack_.back().value(); }
+
+ // Return a reference to the top of the control stack.
+ ControlItem& controlItem() { return controlStack_.back().controlItem(); }
+
+ // Return a reference to an element in the control stack.
+ ControlItem& controlItem(uint32_t relativeDepth) {
+ return controlStack_[controlStack_.length() - 1 - relativeDepth]
+ .controlItem();
+ }
+
+ // Return the LabelKind of an element in the control stack.
+ LabelKind controlKind(uint32_t relativeDepth) {
+ return controlStack_[controlStack_.length() - 1 - relativeDepth].kind();
+ }
+
+ // Return a reference to the outermost element on the control stack.
+ ControlItem& controlOutermost() { return controlStack_[0].controlItem(); }
+
+ // Test whether the control-stack is empty, meaning we've consumed the final
+ // end of the function body.
+ bool controlStackEmpty() const { return controlStack_.empty(); }
+
+ // Return the depth of the control stack.
+ size_t controlStackDepth() const { return controlStack_.length(); }
+
+ // Find the innermost control item of a specific kind, starting to search from
+ // a certain relative depth, and returning true if such innermost control item
+ // is found. The relative depth of the found item is returned via a parameter.
+ bool controlFindInnermostFrom(LabelKind kind, uint32_t fromRelativeDepth,
+ uint32_t* foundRelativeDepth) {
+ int32_t fromAbsoluteDepth = controlStack_.length() - fromRelativeDepth - 1;
+ for (int32_t i = fromAbsoluteDepth; i >= 0; i--) {
+ if (controlStack_[i].kind() == kind) {
+ *foundRelativeDepth = controlStack_.length() - 1 - i;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool controlFindInnermost(LabelKind kind, uint32_t* foundRelativeDepth) {
+ return controlFindInnermostFrom(kind, 0, foundRelativeDepth);
+ }
+};
+
+template <typename Policy>
+inline bool OpIter<Policy>::checkIsSubtypeOf(FieldType subType,
+ FieldType superType) {
+ return CheckIsSubtypeOf(d_, env_, lastOpcodeOffset(), subType, superType);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::checkIsSubtypeOf(ResultType params,
+ ResultType results) {
+ if (params.length() != results.length()) {
+ UniqueChars error(
+ JS_smprintf("type mismatch: expected %zu values, got %zu values",
+ results.length(), params.length()));
+ if (!error) {
+ return false;
+ }
+ return fail(error.get());
+ }
+ for (uint32_t i = 0; i < params.length(); i++) {
+ ValType param = params[i];
+ ValType result = results[i];
+ if (!checkIsSubtypeOf(param, result)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+#ifdef ENABLE_WASM_FUNCTION_REFERENCES
+template <typename Policy>
+inline bool OpIter<Policy>::checkIsSubtypeOf(uint32_t actualTypeIndex,
+ uint32_t expectedTypeIndex) {
+ const TypeDef& actualTypeDef = env_.types->type(actualTypeIndex);
+ const TypeDef& expectedTypeDef = env_.types->type(expectedTypeIndex);
+ return CheckIsSubtypeOf(
+ d_, env_, lastOpcodeOffset(),
+ ValType(RefType::fromTypeDef(&actualTypeDef, true)),
+ ValType(RefType::fromTypeDef(&expectedTypeDef, true)));
+}
+#endif
+
+template <typename Policy>
+inline bool OpIter<Policy>::unrecognizedOpcode(const OpBytes* expr) {
+ UniqueChars error(JS_smprintf("unrecognized opcode: %x %x", expr->b0,
+ IsPrefixByte(expr->b0) ? expr->b1 : 0));
+ if (!error) {
+ return false;
+ }
+
+ return fail(error.get());
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::fail(const char* msg) {
+ return d_.fail(lastOpcodeOffset(), msg);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::fail_ctx(const char* fmt, const char* context) {
+ UniqueChars error(JS_smprintf(fmt, context));
+ if (!error) {
+ return false;
+ }
+ return fail(error.get());
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::failEmptyStack() {
+ return valueStack_.empty() ? fail("popping value from empty stack")
+ : fail("popping value from outside block");
+}
+
+// This function pops exactly one value from the stack, yielding Bottom types in
+// various cases and therefore making it the caller's responsibility to do the
+// right thing for StackType::Bottom. Prefer (pop|top)WithType. This is an
+// optimization for the super-common case where the caller is statically
+// expecting the resulttype `[valtype]`.
+template <typename Policy>
+inline bool OpIter<Policy>::popStackType(StackType* type, Value* value) {
+ Control& block = controlStack_.back();
+
+ MOZ_ASSERT(valueStack_.length() >= block.valueStackBase());
+ if (MOZ_UNLIKELY(valueStack_.length() == block.valueStackBase())) {
+ // If the base of this block's stack is polymorphic, then we can pop a
+ // dummy value of the bottom type; it won't be used since we're in
+ // unreachable code.
+ if (block.polymorphicBase()) {
+ *type = StackType::bottom();
+ *value = Value();
+
+ // Maintain the invariant that, after a pop, there is always memory
+ // reserved to push a value infallibly.
+ return valueStack_.reserve(valueStack_.length() + 1);
+ }
+
+ return failEmptyStack();
+ }
+
+ TypeAndValue& tv = valueStack_.back();
+ *type = tv.type();
+ *value = tv.value();
+ valueStack_.popBack();
+ return true;
+}
+
+// This function pops exactly one value from the stack, checking that it has the
+// expected type which can either be a specific value type or the bottom type.
+template <typename Policy>
+inline bool OpIter<Policy>::popWithType(ValType expectedType, Value* value,
+ StackType* stackType) {
+ if (!popStackType(stackType, value)) {
+ return false;
+ }
+
+ return stackType->isStackBottom() ||
+ checkIsSubtypeOf(stackType->valType(), expectedType);
+}
+
+// This function pops exactly one value from the stack, checking that it has the
+// expected type which can either be a specific value type or the bottom type.
+template <typename Policy>
+inline bool OpIter<Policy>::popWithType(ValType expectedType, Value* value) {
+ StackType stackType;
+ return popWithType(expectedType, value, &stackType);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::popWithType(ResultType expected,
+ ValueVector* values) {
+ return popWithTypes(expected, values);
+}
+
+// Pops each of the given expected types (in reverse, because it's a stack).
+template <typename Policy>
+template <typename ValTypeSpanT>
+inline bool OpIter<Policy>::popWithTypes(ValTypeSpanT expected,
+ ValueVector* values) {
+ size_t expectedLength = expected.size();
+ if (!values->resize(expectedLength)) {
+ return false;
+ }
+ for (size_t i = 0; i < expectedLength; i++) {
+ size_t reverseIndex = expectedLength - i - 1;
+ ValType expectedType = expected[reverseIndex];
+ Value* value = &(*values)[reverseIndex];
+ if (!popWithType(expectedType, value)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// This function pops exactly one value from the stack, checking that it is a
+// reference type.
+template <typename Policy>
+inline bool OpIter<Policy>::popWithRefType(Value* value, StackType* type) {
+ if (!popStackType(type, value)) {
+ return false;
+ }
+
+ if (type->isStackBottom() || type->valType().isRefType()) {
+ return true;
+ }
+
+ UniqueChars actualText = ToString(type->valType(), env_.types);
+ if (!actualText) {
+ return false;
+ }
+
+ UniqueChars error(JS_smprintf(
+ "type mismatch: expression has type %s but expected a reference type",
+ actualText.get()));
+ if (!error) {
+ return false;
+ }
+
+ return fail(error.get());
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::checkTopTypeMatches(ResultType expected,
+ ValueVector* values,
+ bool rewriteStackTypes) {
+ if (expected.empty()) {
+ return true;
+ }
+
+ Control& block = controlStack_.back();
+
+ size_t expectedLength = expected.length();
+ if (values && !values->resize(expectedLength)) {
+ return false;
+ }
+
+ for (size_t i = 0; i != expectedLength; i++) {
+ // We're iterating as-if we were popping each expected/actual type one by
+ // one, which means iterating the array of expected results backwards.
+ // The "current" value stack length refers to what the value stack length
+ // would have been if we were popping it.
+ size_t reverseIndex = expectedLength - i - 1;
+ ValType expectedType = expected[reverseIndex];
+ auto collectValue = [&](const Value& v) {
+ if (values) {
+ (*values)[reverseIndex] = v;
+ }
+ };
+
+ size_t currentValueStackLength = valueStack_.length() - i;
+
+ MOZ_ASSERT(currentValueStackLength >= block.valueStackBase());
+ if (currentValueStackLength == block.valueStackBase()) {
+ if (!block.polymorphicBase()) {
+ return failEmptyStack();
+ }
+
+ // If the base of this block's stack is polymorphic, then we can just
+ // pull out as many fake values as we need to validate, and create dummy
+ // stack entries accordingly; they won't be used since we're in
+ // unreachable code. However, if `rewriteStackTypes` is true, we must
+ // set the types on these new entries to whatever `expected` requires
+ // them to be.
+ TypeAndValue newTandV =
+ rewriteStackTypes ? TypeAndValue(expectedType) : TypeAndValue();
+ if (!valueStack_.insert(valueStack_.begin() + currentValueStackLength,
+ newTandV)) {
+ return false;
+ }
+
+ collectValue(Value());
+ } else {
+ TypeAndValue& observed = valueStack_[currentValueStackLength - 1];
+
+ if (observed.type().isStackBottom()) {
+ collectValue(Value());
+ } else {
+ if (!checkIsSubtypeOf(observed.type().valType(), expectedType)) {
+ return false;
+ }
+
+ collectValue(observed.value());
+ }
+
+ if (rewriteStackTypes) {
+ observed.setType(StackType(expectedType));
+ }
+ }
+ }
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::pushControl(LabelKind kind, BlockType type) {
+ ResultType paramType = type.params();
+
+ ValueVector values;
+ if (!checkTopTypeMatches(paramType, &values, /*rewriteStackTypes=*/true)) {
+ return false;
+ }
+ MOZ_ASSERT(valueStack_.length() >= paramType.length());
+ uint32_t valueStackBase = valueStack_.length() - paramType.length();
+ return controlStack_.emplaceBack(kind, type, valueStackBase);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::checkStackAtEndOfBlock(ResultType* expectedType,
+ ValueVector* values) {
+ Control& block = controlStack_.back();
+ *expectedType = block.type().results();
+
+ MOZ_ASSERT(valueStack_.length() >= block.valueStackBase());
+ if (expectedType->length() < valueStack_.length() - block.valueStackBase()) {
+ return fail("unused values not explicitly dropped by end of block");
+ }
+
+ return checkTopTypeMatches(*expectedType, values,
+ /*rewriteStackTypes=*/true);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::getControl(uint32_t relativeDepth,
+ Control** controlEntry) {
+ if (relativeDepth >= controlStack_.length()) {
+ return fail("branch depth exceeds current nesting level");
+ }
+
+ *controlEntry = &controlStack_[controlStack_.length() - 1 - relativeDepth];
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readBlockType(BlockType* type) {
+ uint8_t nextByte;
+ if (!d_.peekByte(&nextByte)) {
+ return fail("unable to read block type");
+ }
+
+ if (nextByte == uint8_t(TypeCode::BlockVoid)) {
+ d_.uncheckedReadFixedU8();
+ *type = BlockType::VoidToVoid();
+ return true;
+ }
+
+ if ((nextByte & SLEB128SignMask) == SLEB128SignBit) {
+ ValType v;
+ if (!readValType(&v)) {
+ return false;
+ }
+ *type = BlockType::VoidToSingle(v);
+ return true;
+ }
+
+ int32_t x;
+ if (!d_.readVarS32(&x) || x < 0 || uint32_t(x) >= env_.types->length()) {
+ return fail("invalid block type type index");
+ }
+
+ const TypeDef* typeDef = &env_.types->type(x);
+ if (!typeDef->isFuncType()) {
+ return fail("block type type index must be func type");
+ }
+
+ *type = BlockType::Func(typeDef->funcType());
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readOp(OpBytes* op) {
+ MOZ_ASSERT(!controlStack_.empty());
+
+ offsetOfLastReadOp_ = d_.currentOffset();
+
+ if (MOZ_UNLIKELY(!d_.readOp(op))) {
+ return fail("unable to read opcode");
+ }
+
+#ifdef DEBUG
+ op_ = *op;
+#endif
+
+ return true;
+}
+
+template <typename Policy>
+inline void OpIter<Policy>::peekOp(OpBytes* op) {
+ const uint8_t* pos = d_.currentPosition();
+
+ if (MOZ_UNLIKELY(!d_.readOp(op))) {
+ op->b0 = uint16_t(Op::Limit);
+ }
+
+ d_.rollbackPosition(pos);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::startFunction(uint32_t funcIndex,
+ const ValTypeVector& locals) {
+ MOZ_ASSERT(kind_ == OpIter::Func);
+ MOZ_ASSERT(elseParamStack_.empty());
+ MOZ_ASSERT(valueStack_.empty());
+ MOZ_ASSERT(controlStack_.empty());
+ MOZ_ASSERT(op_.b0 == uint16_t(Op::Limit));
+ MOZ_ASSERT(maxInitializedGlobalsIndexPlus1_ == 0);
+ BlockType type = BlockType::FuncResults(*env_.funcs[funcIndex].type);
+
+ size_t numArgs = env_.funcs[funcIndex].type->args().length();
+ if (!unsetLocals_.init(locals, numArgs)) {
+ return false;
+ }
+
+ return pushControl(LabelKind::Body, type);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::endFunction(const uint8_t* bodyEnd) {
+ if (d_.currentPosition() != bodyEnd) {
+ return fail("function body length mismatch");
+ }
+
+ if (!controlStack_.empty()) {
+ return fail("unbalanced function body control flow");
+ }
+ MOZ_ASSERT(elseParamStack_.empty());
+ MOZ_ASSERT(unsetLocals_.empty());
+
+#ifdef DEBUG
+ op_ = OpBytes(Op::Limit);
+#endif
+ valueStack_.clear();
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::startInitExpr(
+ ValType expected, uint32_t maxInitializedGlobalsIndexPlus1) {
+ MOZ_ASSERT(kind_ == OpIter::InitExpr);
+ MOZ_ASSERT(elseParamStack_.empty());
+ MOZ_ASSERT(valueStack_.empty());
+ MOZ_ASSERT(controlStack_.empty());
+ MOZ_ASSERT(maxInitializedGlobalsIndexPlus1_ == 0);
+ MOZ_ASSERT(op_.b0 == uint16_t(Op::Limit));
+
+ // GC allows accessing any previously defined global, not just those that are
+ // imported and immutable.
+ if (env_.features.gc) {
+ maxInitializedGlobalsIndexPlus1_ = maxInitializedGlobalsIndexPlus1;
+ }
+
+ BlockType type = BlockType::VoidToSingle(expected);
+ return pushControl(LabelKind::Body, type);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::endInitExpr() {
+ MOZ_ASSERT(controlStack_.empty());
+ MOZ_ASSERT(elseParamStack_.empty());
+
+#ifdef DEBUG
+ op_ = OpBytes(Op::Limit);
+#endif
+ valueStack_.clear();
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readValType(ValType* type) {
+ return d_.readValType(*env_.types, env_.features, type);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readHeapType(bool nullable, RefType* type) {
+ return d_.readHeapType(*env_.types, env_.features, nullable, type);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readReturn(ValueVector* values) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Return);
+
+ Control& body = controlStack_[0];
+ MOZ_ASSERT(body.kind() == LabelKind::Body);
+
+ if (!popWithType(body.resultType(), values)) {
+ return false;
+ }
+
+ afterUnconditionalBranch();
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readBlock(ResultType* paramType) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Block);
+
+ BlockType type;
+ if (!readBlockType(&type)) {
+ return false;
+ }
+
+ *paramType = type.params();
+ return pushControl(LabelKind::Block, type);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readLoop(ResultType* paramType) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Loop);
+
+ BlockType type;
+ if (!readBlockType(&type)) {
+ return false;
+ }
+
+ *paramType = type.params();
+ return pushControl(LabelKind::Loop, type);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readIf(ResultType* paramType, Value* condition) {
+ MOZ_ASSERT(Classify(op_) == OpKind::If);
+
+ BlockType type;
+ if (!readBlockType(&type)) {
+ return false;
+ }
+
+ if (!popWithType(ValType::I32, condition)) {
+ return false;
+ }
+
+ if (!pushControl(LabelKind::Then, type)) {
+ return false;
+ }
+
+ *paramType = type.params();
+ size_t paramsLength = type.params().length();
+ return elseParamStack_.append(valueStack_.end() - paramsLength, paramsLength);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readElse(ResultType* paramType,
+ ResultType* resultType,
+ ValueVector* thenResults) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Else);
+
+ Control& block = controlStack_.back();
+ if (block.kind() != LabelKind::Then) {
+ return fail("else can only be used within an if");
+ }
+
+ *paramType = block.type().params();
+ if (!checkStackAtEndOfBlock(resultType, thenResults)) {
+ return false;
+ }
+
+ valueStack_.shrinkTo(block.valueStackBase());
+
+ size_t nparams = block.type().params().length();
+ MOZ_ASSERT(elseParamStack_.length() >= nparams);
+ valueStack_.infallibleAppend(elseParamStack_.end() - nparams, nparams);
+ elseParamStack_.shrinkBy(nparams);
+
+ // Reset local state to the beginning of the 'if' block for the new block
+ // started by 'else'.
+ unsetLocals_.resetToBlock(controlStack_.length() - 1);
+
+ block.switchToElse();
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readEnd(LabelKind* kind, ResultType* type,
+ ValueVector* results,
+ ValueVector* resultsForEmptyElse) {
+ MOZ_ASSERT(Classify(op_) == OpKind::End);
+
+ Control& block = controlStack_.back();
+
+ if (!checkStackAtEndOfBlock(type, results)) {
+ return false;
+ }
+
+ if (block.kind() == LabelKind::Then) {
+ ResultType params = block.type().params();
+ // If an `if` block ends with `end` instead of `else`, then the `else` block
+ // implicitly passes the `if` parameters as the `else` results. In that
+ // case, assert that the `if`'s param type matches the result type.
+ if (params != block.type().results()) {
+ return fail("if without else with a result value");
+ }
+
+ size_t nparams = params.length();
+ MOZ_ASSERT(elseParamStack_.length() >= nparams);
+ if (!resultsForEmptyElse->resize(nparams)) {
+ return false;
+ }
+ const TypeAndValue* elseParams = elseParamStack_.end() - nparams;
+ for (size_t i = 0; i < nparams; i++) {
+ (*resultsForEmptyElse)[i] = elseParams[i].value();
+ }
+ elseParamStack_.shrinkBy(nparams);
+ }
+
+ *kind = block.kind();
+ return true;
+}
+
+template <typename Policy>
+inline void OpIter<Policy>::popEnd() {
+ MOZ_ASSERT(Classify(op_) == OpKind::End);
+
+ controlStack_.popBack();
+ unsetLocals_.resetToBlock(controlStack_.length());
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::checkBranchValueAndPush(uint32_t relativeDepth,
+ ResultType* type,
+ ValueVector* values) {
+ Control* block = nullptr;
+ if (!getControl(relativeDepth, &block)) {
+ return false;
+ }
+
+ *type = block->branchTargetType();
+ return checkTopTypeMatches(*type, values, /*rewriteStackTypes=*/false);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readBr(uint32_t* relativeDepth, ResultType* type,
+ ValueVector* values) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Br);
+
+ if (!readVarU32(relativeDepth)) {
+ return fail("unable to read br depth");
+ }
+
+ if (!checkBranchValueAndPush(*relativeDepth, type, values)) {
+ return false;
+ }
+
+ afterUnconditionalBranch();
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readBrIf(uint32_t* relativeDepth, ResultType* type,
+ ValueVector* values, Value* condition) {
+ MOZ_ASSERT(Classify(op_) == OpKind::BrIf);
+
+ if (!readVarU32(relativeDepth)) {
+ return fail("unable to read br_if depth");
+ }
+
+ if (!popWithType(ValType::I32, condition)) {
+ return false;
+ }
+
+ return checkBranchValueAndPush(*relativeDepth, type, values);
+}
+
+#define UNKNOWN_ARITY UINT32_MAX
+
+template <typename Policy>
+inline bool OpIter<Policy>::checkBrTableEntryAndPush(
+ uint32_t* relativeDepth, ResultType prevBranchType, ResultType* type,
+ ValueVector* branchValues) {
+ if (!readVarU32(relativeDepth)) {
+ return fail("unable to read br_table depth");
+ }
+
+ Control* block = nullptr;
+ if (!getControl(*relativeDepth, &block)) {
+ return false;
+ }
+
+ *type = block->branchTargetType();
+
+ if (prevBranchType.valid()) {
+ if (prevBranchType.length() != type->length()) {
+ return fail("br_table targets must all have the same arity");
+ }
+
+ // Avoid re-collecting the same values for subsequent branch targets.
+ branchValues = nullptr;
+ }
+
+ return checkTopTypeMatches(*type, branchValues, /*rewriteStackTypes=*/false);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readBrTable(Uint32Vector* depths,
+ uint32_t* defaultDepth,
+ ResultType* defaultBranchType,
+ ValueVector* branchValues,
+ Value* index) {
+ MOZ_ASSERT(Classify(op_) == OpKind::BrTable);
+
+ uint32_t tableLength;
+ if (!readVarU32(&tableLength)) {
+ return fail("unable to read br_table table length");
+ }
+
+ if (tableLength > MaxBrTableElems) {
+ return fail("br_table too big");
+ }
+
+ if (!popWithType(ValType::I32, index)) {
+ return false;
+ }
+
+ if (!depths->resize(tableLength)) {
+ return false;
+ }
+
+ ResultType prevBranchType;
+ for (uint32_t i = 0; i < tableLength; i++) {
+ ResultType branchType;
+ if (!checkBrTableEntryAndPush(&(*depths)[i], prevBranchType, &branchType,
+ branchValues)) {
+ return false;
+ }
+ prevBranchType = branchType;
+ }
+
+ if (!checkBrTableEntryAndPush(defaultDepth, prevBranchType, defaultBranchType,
+ branchValues)) {
+ return false;
+ }
+
+ MOZ_ASSERT(defaultBranchType->valid());
+
+ afterUnconditionalBranch();
+ return true;
+}
+
+#undef UNKNOWN_ARITY
+
+template <typename Policy>
+inline bool OpIter<Policy>::readTry(ResultType* paramType) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Try);
+
+ BlockType type;
+ if (!readBlockType(&type)) {
+ return false;
+ }
+
+ *paramType = type.params();
+ return pushControl(LabelKind::Try, type);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readCatch(LabelKind* kind, uint32_t* tagIndex,
+ ResultType* paramType,
+ ResultType* resultType,
+ ValueVector* tryResults) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Catch);
+
+ if (!readVarU32(tagIndex)) {
+ return fail("expected tag index");
+ }
+ if (*tagIndex >= env_.tags.length()) {
+ return fail("tag index out of range");
+ }
+
+ Control& block = controlStack_.back();
+ if (block.kind() == LabelKind::CatchAll) {
+ return fail("catch cannot follow a catch_all");
+ }
+ if (block.kind() != LabelKind::Try && block.kind() != LabelKind::Catch) {
+ return fail("catch can only be used within a try-catch");
+ }
+ *kind = block.kind();
+ *paramType = block.type().params();
+
+ if (!checkStackAtEndOfBlock(resultType, tryResults)) {
+ return false;
+ }
+
+ valueStack_.shrinkTo(block.valueStackBase());
+ block.switchToCatch();
+ // Reset local state to the beginning of the 'try' block.
+ unsetLocals_.resetToBlock(controlStack_.length() - 1);
+
+ return push(env_.tags[*tagIndex].type->resultType());
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readCatchAll(LabelKind* kind, ResultType* paramType,
+ ResultType* resultType,
+ ValueVector* tryResults) {
+ MOZ_ASSERT(Classify(op_) == OpKind::CatchAll);
+
+ Control& block = controlStack_.back();
+ if (block.kind() != LabelKind::Try && block.kind() != LabelKind::Catch) {
+ return fail("catch_all can only be used within a try-catch");
+ }
+ *kind = block.kind();
+ *paramType = block.type().params();
+
+ if (!checkStackAtEndOfBlock(resultType, tryResults)) {
+ return false;
+ }
+
+ valueStack_.shrinkTo(block.valueStackBase());
+ block.switchToCatchAll();
+ // Reset local state to the beginning of the 'try' block.
+ unsetLocals_.resetToBlock(controlStack_.length() - 1);
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readDelegate(uint32_t* relativeDepth,
+ ResultType* resultType,
+ ValueVector* tryResults) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Delegate);
+
+ Control& block = controlStack_.back();
+ if (block.kind() != LabelKind::Try) {
+ return fail("delegate can only be used within a try");
+ }
+
+ uint32_t delegateDepth;
+ if (!readVarU32(&delegateDepth)) {
+ return fail("unable to read delegate depth");
+ }
+
+ // Depths for delegate start counting in the surrounding block.
+ if (delegateDepth >= controlStack_.length() - 1) {
+ return fail("delegate depth exceeds current nesting level");
+ }
+ *relativeDepth = delegateDepth + 1;
+
+ // Because `delegate` acts like `end` and ends the block, we will check
+ // the stack here.
+ return checkStackAtEndOfBlock(resultType, tryResults);
+}
+
+// We need popDelegate because readDelegate cannot pop the control stack
+// itself, as its caller may need to use the control item for delegate.
+template <typename Policy>
+inline void OpIter<Policy>::popDelegate() {
+ MOZ_ASSERT(Classify(op_) == OpKind::Delegate);
+
+ controlStack_.popBack();
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readThrow(uint32_t* tagIndex,
+ ValueVector* argValues) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Throw);
+
+ if (!readVarU32(tagIndex)) {
+ return fail("expected tag index");
+ }
+ if (*tagIndex >= env_.tags.length()) {
+ return fail("tag index out of range");
+ }
+
+ if (!popWithType(env_.tags[*tagIndex].type->resultType(), argValues)) {
+ return false;
+ }
+
+ afterUnconditionalBranch();
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readRethrow(uint32_t* relativeDepth) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Rethrow);
+
+ if (!readVarU32(relativeDepth)) {
+ return fail("unable to read rethrow depth");
+ }
+
+ if (*relativeDepth >= controlStack_.length()) {
+ return fail("rethrow depth exceeds current nesting level");
+ }
+ LabelKind kind = controlKind(*relativeDepth);
+ if (kind != LabelKind::Catch && kind != LabelKind::CatchAll) {
+ return fail("rethrow target was not a catch block");
+ }
+
+ afterUnconditionalBranch();
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readUnreachable() {
+ MOZ_ASSERT(Classify(op_) == OpKind::Unreachable);
+
+ afterUnconditionalBranch();
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readDrop() {
+ MOZ_ASSERT(Classify(op_) == OpKind::Drop);
+ StackType type;
+ Value value;
+ return popStackType(&type, &value);
+}
+
+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>::readTernary(ValType operandType, Value* v0,
+ Value* v1, Value* v2) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Ternary);
+
+ if (!popWithType(operandType, v2)) {
+ return false;
+ }
+
+ if (!popWithType(operandType, v1)) {
+ return false;
+ }
+
+ if (!popWithType(operandType, v0)) {
+ return false;
+ }
+
+ infalliblePush(operandType);
+
+ return true;
+}
+
+// For memories, the index is currently always a placeholder zero byte.
+//
+// For tables, the index is a placeholder zero byte until we get multi-table
+// with the reftypes proposal.
+//
+// The zero-ness of the value must be checked by the caller.
+template <typename Policy>
+inline bool OpIter<Policy>::readMemOrTableIndex(bool isMem, uint32_t* index) {
+ bool readByte = isMem;
+ if (readByte) {
+ uint8_t indexTmp;
+ if (!readFixedU8(&indexTmp)) {
+ return fail("unable to read memory or table index");
+ }
+ *index = indexTmp;
+ } else {
+ if (!readVarU32(index)) {
+ return fail("unable to read memory or table index");
+ }
+ }
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readLinearMemoryAddress(
+ uint32_t byteSize, LinearMemoryAddress<Value>* addr) {
+ if (!env_.usesMemory()) {
+ return fail("can't touch memory without memory");
+ }
+
+ IndexType it = env_.memory->indexType();
+
+ uint32_t alignLog2;
+ if (!readVarU32(&alignLog2)) {
+ return fail("unable to read load alignment");
+ }
+
+ if (!readVarU64(&addr->offset)) {
+ return fail("unable to read load offset");
+ }
+
+ if (it == IndexType::I32 && addr->offset > UINT32_MAX) {
+ return fail("offset too large for memory type");
+ }
+
+ if (alignLog2 >= 32 || (uint32_t(1) << alignLog2) > byteSize) {
+ return fail("greater than natural alignment");
+ }
+
+ if (!popWithType(ToValType(it), &addr->base)) {
+ return false;
+ }
+
+ addr->align = uint32_t(1) << alignLog2;
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readLinearMemoryAddressAligned(
+ uint32_t byteSize, LinearMemoryAddress<Value>* addr) {
+ if (!readLinearMemoryAddress(byteSize, addr)) {
+ return false;
+ }
+
+ if (addr->align != byteSize) {
+ return fail("not natural alignment");
+ }
+
+ 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(resultType, *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>::readMemorySize() {
+ MOZ_ASSERT(Classify(op_) == OpKind::MemorySize);
+
+ if (!env_.usesMemory()) {
+ return fail("can't touch memory without memory");
+ }
+
+ uint8_t flags;
+ if (!readFixedU8(&flags)) {
+ return fail("failed to read memory flags");
+ }
+
+ if (flags != uint8_t(0)) {
+ return fail("unexpected flags");
+ }
+
+ ValType ptrType = ToValType(env_.memory->indexType());
+ return push(ptrType);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readMemoryGrow(Value* input) {
+ MOZ_ASSERT(Classify(op_) == OpKind::MemoryGrow);
+
+ if (!env_.usesMemory()) {
+ return fail("can't touch memory without memory");
+ }
+
+ uint8_t flags;
+ if (!readFixedU8(&flags)) {
+ return fail("failed to read memory flags");
+ }
+
+ if (flags != uint8_t(0)) {
+ return fail("unexpected flags");
+ }
+
+ ValType ptrType = ToValType(env_.memory->indexType());
+ if (!popWithType(ptrType, input)) {
+ return false;
+ }
+
+ infalliblePush(ptrType);
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readSelect(bool typed, StackType* type,
+ Value* trueValue, Value* falseValue,
+ Value* condition) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Select);
+
+ if (typed) {
+ uint32_t length;
+ if (!readVarU32(&length)) {
+ return fail("unable to read select result length");
+ }
+ if (length != 1) {
+ return fail("bad number of results");
+ }
+ ValType result;
+ if (!readValType(&result)) {
+ return fail("invalid result type for select");
+ }
+
+ if (!popWithType(ValType::I32, condition)) {
+ return false;
+ }
+ if (!popWithType(result, falseValue)) {
+ return false;
+ }
+ if (!popWithType(result, trueValue)) {
+ return false;
+ }
+
+ *type = StackType(result);
+ infalliblePush(*type);
+ return true;
+ }
+
+ if (!popWithType(ValType::I32, condition)) {
+ return false;
+ }
+
+ StackType falseType;
+ if (!popStackType(&falseType, falseValue)) {
+ return false;
+ }
+
+ StackType trueType;
+ if (!popStackType(&trueType, trueValue)) {
+ return false;
+ }
+
+ if (!falseType.isValidForUntypedSelect() ||
+ !trueType.isValidForUntypedSelect()) {
+ return fail("invalid types for untyped select");
+ }
+
+ if (falseType.isStackBottom()) {
+ *type = trueType;
+ } else if (trueType.isStackBottom() || falseType == trueType) {
+ *type = falseType;
+ } else {
+ return fail("select operand types must match");
+ }
+
+ infalliblePush(*type);
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readGetLocal(const ValTypeVector& locals,
+ uint32_t* id) {
+ MOZ_ASSERT(Classify(op_) == OpKind::GetLocal);
+
+ if (!readVarU32(id)) {
+ return fail("unable to read local index");
+ }
+
+ if (*id >= locals.length()) {
+ return fail("local.get index out of range");
+ }
+
+ if (unsetLocals_.isUnset(*id)) {
+ return fail("local.get read from unset local");
+ }
+
+ return push(locals[*id]);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readSetLocal(const ValTypeVector& locals,
+ uint32_t* id, Value* value) {
+ MOZ_ASSERT(Classify(op_) == OpKind::SetLocal);
+
+ if (!readVarU32(id)) {
+ return fail("unable to read local index");
+ }
+
+ if (*id >= locals.length()) {
+ return fail("local.set index out of range");
+ }
+
+ if (unsetLocals_.isUnset(*id)) {
+ unsetLocals_.set(*id, controlStackDepth());
+ }
+
+ return popWithType(locals[*id], value);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readTeeLocal(const ValTypeVector& locals,
+ uint32_t* id, Value* value) {
+ MOZ_ASSERT(Classify(op_) == OpKind::TeeLocal);
+
+ if (!readVarU32(id)) {
+ return fail("unable to read local index");
+ }
+
+ if (*id >= locals.length()) {
+ return fail("local.set index out of range");
+ }
+
+ if (unsetLocals_.isUnset(*id)) {
+ unsetLocals_.set(*id, controlStackDepth());
+ }
+
+ ValueVector single;
+ if (!checkTopTypeMatches(ResultType::Single(locals[*id]), &single,
+ /*rewriteStackTypes=*/true)) {
+ return false;
+ }
+
+ *value = single[0];
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readGetGlobal(uint32_t* id) {
+ MOZ_ASSERT(Classify(op_) == OpKind::GetGlobal);
+
+ if (!d_.readGlobalIndex(id)) {
+ return false;
+ }
+
+ if (*id >= env_.globals.length()) {
+ return fail("global.get index out of range");
+ }
+
+ // Initializer expressions can access immutable imported globals, or any
+ // previously defined global with GC enabled.
+ if (kind_ == OpIter::InitExpr && *id >= maxInitializedGlobalsIndexPlus1_ &&
+ (!env_.globals[*id].isImport() || env_.globals[*id].isMutable())) {
+ return fail(
+ "global.get in initializer expression must reference a global "
+ "immutable import");
+ }
+
+ return push(env_.globals[*id].type());
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readSetGlobal(uint32_t* id, Value* value) {
+ MOZ_ASSERT(Classify(op_) == OpKind::SetGlobal);
+
+ if (!d_.readGlobalIndex(id)) {
+ return false;
+ }
+
+ if (*id >= env_.globals.length()) {
+ return fail("global.set index out of range");
+ }
+
+ if (!env_.globals[*id].isMutable()) {
+ return fail("can't write an immutable global");
+ }
+
+ return popWithType(env_.globals[*id].type(), value);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readTeeGlobal(uint32_t* id, Value* value) {
+ MOZ_ASSERT(Classify(op_) == OpKind::TeeGlobal);
+
+ if (!d_.readGlobalIndex(id)) {
+ return false;
+ }
+
+ if (*id >= env_.globals.length()) {
+ return fail("global.set index out of range");
+ }
+
+ if (!env_.globals[*id].isMutable()) {
+ return fail("can't write an immutable global");
+ }
+
+ ValueVector single;
+ if (!checkTopTypeMatches(ResultType::Single(env_.globals[*id].type()),
+ &single,
+ /*rewriteStackTypes=*/true)) {
+ return false;
+ }
+
+ MOZ_ASSERT(single.length() == 1);
+ *value = single[0];
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readI32Const(int32_t* i32) {
+ MOZ_ASSERT(Classify(op_) == OpKind::I32);
+
+ if (!d_.readI32Const(i32)) {
+ return false;
+ }
+
+ return push(ValType::I32);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readI64Const(int64_t* i64) {
+ MOZ_ASSERT(Classify(op_) == OpKind::I64);
+
+ if (!d_.readI64Const(i64)) {
+ return false;
+ }
+
+ return push(ValType::I64);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readF32Const(float* f32) {
+ MOZ_ASSERT(Classify(op_) == OpKind::F32);
+
+ if (!d_.readF32Const(f32)) {
+ return false;
+ }
+
+ return push(ValType::F32);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readF64Const(double* f64) {
+ MOZ_ASSERT(Classify(op_) == OpKind::F64);
+
+ if (!d_.readF64Const(f64)) {
+ return false;
+ }
+
+ return push(ValType::F64);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readRefFunc(uint32_t* funcIndex) {
+ MOZ_ASSERT(Classify(op_) == OpKind::RefFunc);
+
+ if (!d_.readFuncIndex(funcIndex)) {
+ return false;
+ }
+ if (*funcIndex >= env_.funcs.length()) {
+ return fail("function index out of range");
+ }
+ if (kind_ == OpIter::Func && !env_.funcs[*funcIndex].canRefFunc()) {
+ return fail(
+ "function index is not declared in a section before the code section");
+ }
+
+#ifdef ENABLE_WASM_FUNCTION_REFERENCES
+ // When function references enabled, push type index on the stack, e.g. for
+ // validation of the call_ref instruction.
+ if (env_.functionReferencesEnabled()) {
+ const uint32_t typeIndex = env_.funcs[*funcIndex].typeIndex;
+ const TypeDef& typeDef = env_.types->type(typeIndex);
+ return push(RefType::fromTypeDef(&typeDef, false));
+ }
+#endif
+ return push(RefType::func());
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readRefNull(RefType* type) {
+ MOZ_ASSERT(Classify(op_) == OpKind::RefNull);
+
+ if (!d_.readRefNull(*env_.types, env_.features, type)) {
+ return false;
+ }
+ return push(*type);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readRefIsNull(Value* input) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Conversion);
+
+ StackType type;
+ if (!popWithRefType(input, &type)) {
+ return false;
+ }
+ return push(ValType::I32);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readRefAsNonNull(Value* input) {
+ MOZ_ASSERT(Classify(op_) == OpKind::RefAsNonNull);
+
+ StackType type;
+ if (!popWithRefType(input, &type)) {
+ return false;
+ }
+
+ if (type.isStackBottom()) {
+ infalliblePush(type);
+ } else {
+ infalliblePush(TypeAndValue(type.asNonNullable(), *input));
+ }
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readBrOnNull(uint32_t* relativeDepth,
+ ResultType* type, ValueVector* values,
+ Value* condition) {
+ MOZ_ASSERT(Classify(op_) == OpKind::BrOnNull);
+
+ if (!readVarU32(relativeDepth)) {
+ return fail("unable to read br_on_null depth");
+ }
+
+ StackType refType;
+ if (!popWithRefType(condition, &refType)) {
+ return false;
+ }
+
+ if (!checkBranchValueAndPush(*relativeDepth, type, values)) {
+ return false;
+ }
+
+ if (refType.isStackBottom()) {
+ return push(refType);
+ }
+ return push(TypeAndValue(refType.asNonNullable(), *condition));
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readBrOnNonNull(uint32_t* relativeDepth,
+ ResultType* type,
+ ValueVector* values,
+ Value* condition) {
+ MOZ_ASSERT(Classify(op_) == OpKind::BrOnNonNull);
+
+ if (!readVarU32(relativeDepth)) {
+ return fail("unable to read br_on_non_null depth");
+ }
+
+ Control* block = nullptr;
+ if (!getControl(*relativeDepth, &block)) {
+ return false;
+ }
+
+ *type = block->branchTargetType();
+
+ // Check we at least have one type in the branch target type.
+ if (type->length() < 1) {
+ return fail("type mismatch: target block type expected to be [_, ref]");
+ }
+
+ // Pop the condition reference.
+ StackType refType;
+ if (!popWithRefType(condition, &refType)) {
+ return false;
+ }
+
+ // Push non-nullable version of condition reference on the stack, prior
+ // checking the target type below.
+ if (!(refType.isStackBottom()
+ ? push(refType)
+ : push(TypeAndValue(refType.asNonNullable(), *condition)))) {
+ return false;
+ }
+
+ // Check if the type stack matches the branch target type.
+ if (!checkTopTypeMatches(*type, values, /*rewriteStackTypes=*/false)) {
+ return false;
+ }
+
+ // Pop the condition reference -- the null-branch does not receive the value.
+ StackType unusedType;
+ Value unusedValue;
+ return popStackType(&unusedType, &unusedValue);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::popCallArgs(const ValTypeVector& expectedTypes,
+ ValueVector* values) {
+ // Iterate through the argument types backward so that pops occur in the
+ // right order.
+
+ if (!values->resize(expectedTypes.length())) {
+ return false;
+ }
+
+ for (int32_t i = int32_t(expectedTypes.length()) - 1; i >= 0; i--) {
+ if (!popWithType(expectedTypes[i], &(*values)[i])) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readCall(uint32_t* funcTypeIndex,
+ ValueVector* argValues) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Call);
+
+ if (!readVarU32(funcTypeIndex)) {
+ return fail("unable to read call function index");
+ }
+
+ if (*funcTypeIndex >= env_.funcs.length()) {
+ return fail("callee index out of range");
+ }
+
+ const FuncType& funcType = *env_.funcs[*funcTypeIndex].type;
+
+ if (!popCallArgs(funcType.args(), argValues)) {
+ return false;
+ }
+
+ return push(ResultType::Vector(funcType.results()));
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readCallIndirect(uint32_t* funcTypeIndex,
+ uint32_t* tableIndex,
+ Value* callee,
+ ValueVector* argValues) {
+ MOZ_ASSERT(Classify(op_) == OpKind::CallIndirect);
+ MOZ_ASSERT(funcTypeIndex != tableIndex);
+
+ if (!readVarU32(funcTypeIndex)) {
+ return fail("unable to read call_indirect signature index");
+ }
+
+ if (*funcTypeIndex >= env_.numTypes()) {
+ return fail("signature index out of range");
+ }
+
+ if (!readVarU32(tableIndex)) {
+ return fail("unable to read call_indirect table index");
+ }
+ if (*tableIndex >= env_.tables.length()) {
+ // Special case this for improved user experience.
+ if (!env_.tables.length()) {
+ return fail("can't call_indirect without a table");
+ }
+ return fail("table index out of range for call_indirect");
+ }
+ if (!env_.tables[*tableIndex].elemType.isFuncHierarchy()) {
+ return fail("indirect calls must go through a table of 'funcref'");
+ }
+
+ if (!popWithType(ValType::I32, callee)) {
+ return false;
+ }
+
+ const TypeDef& typeDef = env_.types->type(*funcTypeIndex);
+ if (!typeDef.isFuncType()) {
+ return fail("expected signature type");
+ }
+ const FuncType& funcType = typeDef.funcType();
+
+ if (!popCallArgs(funcType.args(), argValues)) {
+ return false;
+ }
+
+ return push(ResultType::Vector(funcType.results()));
+}
+
+#ifdef ENABLE_WASM_FUNCTION_REFERENCES
+template <typename Policy>
+inline bool OpIter<Policy>::readCallRef(const FuncType** funcType,
+ Value* callee, ValueVector* argValues) {
+ MOZ_ASSERT(Classify(op_) == OpKind::CallRef);
+
+ uint32_t funcTypeIndex;
+ if (!readFuncTypeIndex(&funcTypeIndex)) {
+ return false;
+ }
+
+ const TypeDef& typeDef = env_.types->type(funcTypeIndex);
+ *funcType = &typeDef.funcType();
+
+ if (!popWithType(ValType(RefType::fromTypeDef(&typeDef, true)), callee)) {
+ return false;
+ }
+
+ if (!popCallArgs((*funcType)->args(), argValues)) {
+ return false;
+ }
+
+ return push(ResultType::Vector((*funcType)->results()));
+}
+#endif
+
+template <typename Policy>
+inline bool OpIter<Policy>::readOldCallDirect(uint32_t numFuncImports,
+ uint32_t* funcTypeIndex,
+ ValueVector* argValues) {
+ MOZ_ASSERT(Classify(op_) == OpKind::OldCallDirect);
+
+ uint32_t funcDefIndex;
+ if (!readVarU32(&funcDefIndex)) {
+ return fail("unable to read call function index");
+ }
+
+ if (UINT32_MAX - funcDefIndex < numFuncImports) {
+ return fail("callee index out of range");
+ }
+
+ *funcTypeIndex = numFuncImports + funcDefIndex;
+
+ if (*funcTypeIndex >= env_.funcs.length()) {
+ return fail("callee index out of range");
+ }
+
+ const FuncType& funcType = *env_.funcs[*funcTypeIndex].type;
+
+ if (!popCallArgs(funcType.args(), argValues)) {
+ return false;
+ }
+
+ return push(ResultType::Vector(funcType.results()));
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readOldCallIndirect(uint32_t* funcTypeIndex,
+ Value* callee,
+ ValueVector* argValues) {
+ MOZ_ASSERT(Classify(op_) == OpKind::OldCallIndirect);
+
+ if (!readVarU32(funcTypeIndex)) {
+ return fail("unable to read call_indirect signature index");
+ }
+
+ if (*funcTypeIndex >= env_.numTypes()) {
+ return fail("signature index out of range");
+ }
+
+ const TypeDef& typeDef = env_.types->type(*funcTypeIndex);
+ if (!typeDef.isFuncType()) {
+ return fail("expected signature type");
+ }
+ const FuncType& funcType = typeDef.funcType();
+
+ if (!popCallArgs(funcType.args(), argValues)) {
+ return false;
+ }
+
+ if (!popWithType(ValType::I32, callee)) {
+ return false;
+ }
+
+ return push(ResultType::Vector(funcType.results()));
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readWake(LinearMemoryAddress<Value>* addr,
+ Value* count) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Wake);
+
+ if (!popWithType(ValType::I32, count)) {
+ return false;
+ }
+
+ uint32_t byteSize = 4; // Per spec; smallest WAIT is i32.
+
+ if (!readLinearMemoryAddressAligned(byteSize, addr)) {
+ return false;
+ }
+
+ infalliblePush(ValType::I32);
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readWait(LinearMemoryAddress<Value>* addr,
+ ValType valueType, uint32_t byteSize,
+ Value* value, Value* timeout) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Wait);
+
+ if (!popWithType(ValType::I64, timeout)) {
+ return false;
+ }
+
+ if (!popWithType(valueType, value)) {
+ return false;
+ }
+
+ if (!readLinearMemoryAddressAligned(byteSize, addr)) {
+ return false;
+ }
+
+ infalliblePush(ValType::I32);
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readFence() {
+ MOZ_ASSERT(Classify(op_) == OpKind::Fence);
+ uint8_t flags;
+ if (!readFixedU8(&flags)) {
+ return fail("expected memory order after fence");
+ }
+ if (flags != 0) {
+ return fail("non-zero memory order not supported yet");
+ }
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readAtomicLoad(LinearMemoryAddress<Value>* addr,
+ ValType resultType,
+ uint32_t byteSize) {
+ MOZ_ASSERT(Classify(op_) == OpKind::AtomicLoad);
+
+ if (!readLinearMemoryAddressAligned(byteSize, addr)) {
+ return false;
+ }
+
+ infalliblePush(resultType);
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readAtomicStore(LinearMemoryAddress<Value>* addr,
+ ValType resultType,
+ uint32_t byteSize, Value* value) {
+ MOZ_ASSERT(Classify(op_) == OpKind::AtomicStore);
+
+ if (!popWithType(resultType, value)) {
+ return false;
+ }
+
+ if (!readLinearMemoryAddressAligned(byteSize, addr)) {
+ return false;
+ }
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readAtomicRMW(LinearMemoryAddress<Value>* addr,
+ ValType resultType, uint32_t byteSize,
+ Value* value) {
+ MOZ_ASSERT(Classify(op_) == OpKind::AtomicBinOp);
+
+ if (!popWithType(resultType, value)) {
+ return false;
+ }
+
+ if (!readLinearMemoryAddressAligned(byteSize, addr)) {
+ return false;
+ }
+
+ infalliblePush(resultType);
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readAtomicCmpXchg(LinearMemoryAddress<Value>* addr,
+ ValType resultType,
+ uint32_t byteSize,
+ Value* oldValue,
+ Value* newValue) {
+ MOZ_ASSERT(Classify(op_) == OpKind::AtomicCompareExchange);
+
+ if (!popWithType(resultType, newValue)) {
+ return false;
+ }
+
+ if (!popWithType(resultType, oldValue)) {
+ return false;
+ }
+
+ if (!readLinearMemoryAddressAligned(byteSize, addr)) {
+ return false;
+ }
+
+ infalliblePush(resultType);
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readMemOrTableCopy(bool isMem,
+ uint32_t* dstMemOrTableIndex,
+ Value* dst,
+ uint32_t* srcMemOrTableIndex,
+ Value* src, Value* len) {
+ MOZ_ASSERT(Classify(op_) == OpKind::MemOrTableCopy);
+ MOZ_ASSERT(dstMemOrTableIndex != srcMemOrTableIndex);
+
+ // Spec requires (dest, src) as of 2019-10-04.
+ if (!readMemOrTableIndex(isMem, dstMemOrTableIndex)) {
+ return false;
+ }
+ if (!readMemOrTableIndex(isMem, srcMemOrTableIndex)) {
+ return false;
+ }
+
+ if (isMem) {
+ if (!env_.usesMemory()) {
+ return fail("can't touch memory without memory");
+ }
+ if (*srcMemOrTableIndex != 0 || *dstMemOrTableIndex != 0) {
+ return fail("memory index out of range for memory.copy");
+ }
+ } else {
+ if (*dstMemOrTableIndex >= env_.tables.length() ||
+ *srcMemOrTableIndex >= env_.tables.length()) {
+ return fail("table index out of range for table.copy");
+ }
+ ValType dstElemType = env_.tables[*dstMemOrTableIndex].elemType;
+ ValType srcElemType = env_.tables[*srcMemOrTableIndex].elemType;
+ if (!checkIsSubtypeOf(srcElemType, dstElemType)) {
+ return false;
+ }
+ }
+
+ ValType ptrType = isMem ? ToValType(env_.memory->indexType()) : ValType::I32;
+
+ if (!popWithType(ptrType, len)) {
+ return false;
+ }
+
+ if (!popWithType(ptrType, src)) {
+ return false;
+ }
+
+ if (!popWithType(ptrType, dst)) {
+ return false;
+ }
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readDataOrElemDrop(bool isData,
+ uint32_t* segIndex) {
+ MOZ_ASSERT(Classify(op_) == OpKind::DataOrElemDrop);
+
+ if (!readVarU32(segIndex)) {
+ return fail("unable to read segment index");
+ }
+
+ if (isData) {
+ if (env_.dataCount.isNothing()) {
+ return fail("data.drop requires a DataCount section");
+ }
+ if (*segIndex >= *env_.dataCount) {
+ return fail("data.drop segment index out of range");
+ }
+ } else {
+ if (*segIndex >= env_.elemSegments.length()) {
+ return fail("element segment index out of range for elem.drop");
+ }
+ }
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readMemFill(Value* start, Value* val, Value* len) {
+ MOZ_ASSERT(Classify(op_) == OpKind::MemFill);
+
+ if (!env_.usesMemory()) {
+ return fail("can't touch memory without memory");
+ }
+
+ uint8_t memoryIndex;
+ if (!readFixedU8(&memoryIndex)) {
+ return fail("failed to read memory index");
+ }
+ if (!env_.usesMemory()) {
+ return fail("can't touch memory without memory");
+ }
+ if (memoryIndex != 0) {
+ return fail("memory index must be zero");
+ }
+
+ ValType ptrType = ToValType(env_.memory->indexType());
+
+ if (!popWithType(ptrType, len)) {
+ return false;
+ }
+
+ if (!popWithType(ValType::I32, val)) {
+ return false;
+ }
+
+ if (!popWithType(ptrType, start)) {
+ return false;
+ }
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readMemOrTableInit(bool isMem, uint32_t* segIndex,
+ uint32_t* dstTableIndex,
+ Value* dst, Value* src,
+ Value* len) {
+ MOZ_ASSERT(Classify(op_) == OpKind::MemOrTableInit);
+ MOZ_ASSERT(segIndex != dstTableIndex);
+
+ if (!readVarU32(segIndex)) {
+ return fail("unable to read segment index");
+ }
+
+ uint32_t memOrTableIndex = 0;
+ if (!readMemOrTableIndex(isMem, &memOrTableIndex)) {
+ return false;
+ }
+
+ if (isMem) {
+ if (!env_.usesMemory()) {
+ return fail("can't touch memory without memory");
+ }
+ if (memOrTableIndex != 0) {
+ return fail("memory index must be zero");
+ }
+ if (env_.dataCount.isNothing()) {
+ return fail("memory.init requires a DataCount section");
+ }
+ if (*segIndex >= *env_.dataCount) {
+ return fail("memory.init segment index out of range");
+ }
+ } else {
+ if (memOrTableIndex >= env_.tables.length()) {
+ return fail("table index out of range for table.init");
+ }
+ *dstTableIndex = memOrTableIndex;
+
+ if (*segIndex >= env_.elemSegments.length()) {
+ return fail("table.init segment index out of range");
+ }
+ if (!checkIsSubtypeOf(env_.elemSegments[*segIndex]->elemType,
+ env_.tables[*dstTableIndex].elemType)) {
+ return false;
+ }
+ }
+
+ if (!popWithType(ValType::I32, len)) {
+ return false;
+ }
+
+ if (!popWithType(ValType::I32, src)) {
+ return false;
+ }
+
+ ValType ptrType = isMem ? ToValType(env_.memory->indexType()) : ValType::I32;
+ return popWithType(ptrType, dst);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readTableFill(uint32_t* tableIndex, Value* start,
+ Value* val, Value* len) {
+ MOZ_ASSERT(Classify(op_) == OpKind::TableFill);
+
+ if (!readVarU32(tableIndex)) {
+ return fail("unable to read table index");
+ }
+ if (*tableIndex >= env_.tables.length()) {
+ return fail("table index out of range for table.fill");
+ }
+
+ if (!popWithType(ValType::I32, len)) {
+ return false;
+ }
+ if (!popWithType(env_.tables[*tableIndex].elemType, val)) {
+ return false;
+ }
+ if (!popWithType(ValType::I32, start)) {
+ return false;
+ }
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readMemDiscard(Value* start, Value* len) {
+ MOZ_ASSERT(Classify(op_) == OpKind::MemDiscard);
+
+ if (!env_.usesMemory()) {
+ return fail("can't touch memory without memory");
+ }
+
+ uint8_t memoryIndex;
+ if (!readFixedU8(&memoryIndex)) {
+ return fail("failed to read memory index");
+ }
+ if (memoryIndex != 0) {
+ return fail("memory index must be zero");
+ }
+
+ ValType ptrType = ToValType(env_.memory->indexType());
+
+ if (!popWithType(ptrType, len)) {
+ return false;
+ }
+
+ if (!popWithType(ptrType, start)) {
+ return false;
+ }
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readTableGet(uint32_t* tableIndex, Value* index) {
+ MOZ_ASSERT(Classify(op_) == OpKind::TableGet);
+
+ if (!readVarU32(tableIndex)) {
+ return fail("unable to read table index");
+ }
+ if (*tableIndex >= env_.tables.length()) {
+ return fail("table index out of range for table.get");
+ }
+
+ if (!popWithType(ValType::I32, index)) {
+ return false;
+ }
+
+ infalliblePush(env_.tables[*tableIndex].elemType);
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readTableGrow(uint32_t* tableIndex,
+ Value* initValue, Value* delta) {
+ MOZ_ASSERT(Classify(op_) == OpKind::TableGrow);
+
+ if (!readVarU32(tableIndex)) {
+ return fail("unable to read table index");
+ }
+ if (*tableIndex >= env_.tables.length()) {
+ return fail("table index out of range for table.grow");
+ }
+
+ if (!popWithType(ValType::I32, delta)) {
+ return false;
+ }
+ if (!popWithType(env_.tables[*tableIndex].elemType, initValue)) {
+ return false;
+ }
+
+ infalliblePush(ValType::I32);
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readTableSet(uint32_t* tableIndex, Value* index,
+ Value* value) {
+ MOZ_ASSERT(Classify(op_) == OpKind::TableSet);
+
+ if (!readVarU32(tableIndex)) {
+ return fail("unable to read table index");
+ }
+ if (*tableIndex >= env_.tables.length()) {
+ return fail("table index out of range for table.set");
+ }
+
+ if (!popWithType(env_.tables[*tableIndex].elemType, value)) {
+ return false;
+ }
+ if (!popWithType(ValType::I32, index)) {
+ return false;
+ }
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readTableSize(uint32_t* tableIndex) {
+ MOZ_ASSERT(Classify(op_) == OpKind::TableSize);
+
+ *tableIndex = 0;
+
+ if (!readVarU32(tableIndex)) {
+ return fail("unable to read table index");
+ }
+ if (*tableIndex >= env_.tables.length()) {
+ return fail("table index out of range for table.size");
+ }
+
+ return push(ValType::I32);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readGcTypeIndex(uint32_t* typeIndex) {
+ if (!d_.readTypeIndex(typeIndex)) {
+ return false;
+ }
+
+ if (*typeIndex >= env_.types->length()) {
+ return fail("type index out of range");
+ }
+
+ if (!env_.types->type(*typeIndex).isStructType() &&
+ !env_.types->type(*typeIndex).isArrayType()) {
+ return fail("not a gc type");
+ }
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readStructTypeIndex(uint32_t* typeIndex) {
+ if (!readVarU32(typeIndex)) {
+ return fail("unable to read type index");
+ }
+
+ if (*typeIndex >= env_.types->length()) {
+ return fail("type index out of range");
+ }
+
+ if (!env_.types->type(*typeIndex).isStructType()) {
+ return fail("not a struct type");
+ }
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readArrayTypeIndex(uint32_t* typeIndex) {
+ if (!readVarU32(typeIndex)) {
+ return fail("unable to read type index");
+ }
+
+ if (*typeIndex >= env_.types->length()) {
+ return fail("type index out of range");
+ }
+
+ if (!env_.types->type(*typeIndex).isArrayType()) {
+ return fail("not an array type");
+ }
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readFuncTypeIndex(uint32_t* typeIndex) {
+ if (!readVarU32(typeIndex)) {
+ return fail("unable to read type index");
+ }
+
+ if (*typeIndex >= env_.types->length()) {
+ return fail("type index out of range");
+ }
+
+ if (!env_.types->type(*typeIndex).isFuncType()) {
+ return fail("not an func type");
+ }
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readFieldIndex(uint32_t* fieldIndex,
+ const StructType& structType) {
+ if (!readVarU32(fieldIndex)) {
+ return fail("unable to read field index");
+ }
+
+ if (structType.fields_.length() <= *fieldIndex) {
+ return fail("field index out of range");
+ }
+
+ return true;
+}
+
+#ifdef ENABLE_WASM_GC
+
+template <typename Policy>
+inline bool OpIter<Policy>::readStructNew(uint32_t* typeIndex,
+ ValueVector* argValues) {
+ MOZ_ASSERT(Classify(op_) == OpKind::StructNew);
+
+ if (!readStructTypeIndex(typeIndex)) {
+ return false;
+ }
+
+ const TypeDef& typeDef = env_.types->type(*typeIndex);
+ const StructType& structType = typeDef.structType();
+
+ if (!argValues->resize(structType.fields_.length())) {
+ return false;
+ }
+
+ static_assert(MaxStructFields <= INT32_MAX, "Or we iloop below");
+
+ for (int32_t i = structType.fields_.length() - 1; i >= 0; i--) {
+ if (!popWithType(structType.fields_[i].type.widenToValType(),
+ &(*argValues)[i])) {
+ return false;
+ }
+ }
+
+ return push(RefType::fromTypeDef(&typeDef, false));
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readStructNewDefault(uint32_t* typeIndex) {
+ MOZ_ASSERT(Classify(op_) == OpKind::StructNewDefault);
+
+ if (!readStructTypeIndex(typeIndex)) {
+ return false;
+ }
+
+ const TypeDef& typeDef = env_.types->type(*typeIndex);
+ const StructType& structType = typeDef.structType();
+
+ if (!structType.isDefaultable()) {
+ return fail("struct must be defaultable");
+ }
+
+ return push(RefType::fromTypeDef(&typeDef, false));
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readStructGet(uint32_t* typeIndex,
+ uint32_t* fieldIndex,
+ FieldWideningOp wideningOp,
+ Value* ptr) {
+ MOZ_ASSERT(typeIndex != fieldIndex);
+ MOZ_ASSERT(Classify(op_) == OpKind::StructGet);
+
+ if (!readStructTypeIndex(typeIndex)) {
+ return false;
+ }
+
+ const TypeDef& typeDef = env_.types->type(*typeIndex);
+ const StructType& structType = typeDef.structType();
+
+ if (!readFieldIndex(fieldIndex, structType)) {
+ return false;
+ }
+
+ if (!popWithType(RefType::fromTypeDef(&typeDef, true), ptr)) {
+ return false;
+ }
+
+ FieldType fieldType = structType.fields_[*fieldIndex].type;
+
+ if (fieldType.isValType() && wideningOp != FieldWideningOp::None) {
+ return fail("must not specify signedness for unpacked field type");
+ }
+
+ if (!fieldType.isValType() && wideningOp == FieldWideningOp::None) {
+ return fail("must specify signedness for packed field type");
+ }
+
+ return push(fieldType.widenToValType());
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readStructSet(uint32_t* typeIndex,
+ uint32_t* fieldIndex, Value* ptr,
+ Value* val) {
+ MOZ_ASSERT(typeIndex != fieldIndex);
+ MOZ_ASSERT(Classify(op_) == OpKind::StructSet);
+
+ if (!readStructTypeIndex(typeIndex)) {
+ return false;
+ }
+
+ const TypeDef& typeDef = env_.types->type(*typeIndex);
+ const StructType& structType = typeDef.structType();
+
+ if (!readFieldIndex(fieldIndex, structType)) {
+ return false;
+ }
+
+ if (!popWithType(structType.fields_[*fieldIndex].type.widenToValType(),
+ val)) {
+ return false;
+ }
+
+ if (!structType.fields_[*fieldIndex].isMutable) {
+ return fail("field is not mutable");
+ }
+
+ if (!popWithType(RefType::fromTypeDef(&typeDef, true), ptr)) {
+ return false;
+ }
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readArrayNew(uint32_t* typeIndex,
+ Value* numElements, Value* argValue) {
+ MOZ_ASSERT(Classify(op_) == OpKind::ArrayNew);
+
+ if (!readArrayTypeIndex(typeIndex)) {
+ return false;
+ }
+
+ const TypeDef& typeDef = env_.types->type(*typeIndex);
+ const ArrayType& arrayType = typeDef.arrayType();
+
+ if (!popWithType(ValType::I32, numElements)) {
+ return false;
+ }
+
+ if (!popWithType(arrayType.elementType_.widenToValType(), argValue)) {
+ return false;
+ }
+
+ return push(RefType::fromTypeDef(&typeDef, false));
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readArrayNewFixed(uint32_t* typeIndex,
+ uint32_t* numElements,
+ ValueVector* values) {
+ MOZ_ASSERT(Classify(op_) == OpKind::ArrayNewFixed);
+ MOZ_ASSERT(values->length() == 0);
+
+ if (!readArrayTypeIndex(typeIndex)) {
+ return false;
+ }
+
+ const TypeDef& typeDef = env_.types->type(*typeIndex);
+ const ArrayType& arrayType = typeDef.arrayType();
+
+ if (!readVarU32(numElements)) {
+ return false;
+ }
+ // Don't resize `values` so as to hold `numElements`. If `numElements` is
+ // absurdly large, this will will take a large amount of time and memory,
+ // which will be wasted because `popWithType` in the loop below will soon
+ // start failing anyway.
+
+ ValType widenedElementType = arrayType.elementType_.widenToValType();
+ for (uint32_t i = 0; i < *numElements; i++) {
+ Value v;
+ if (!popWithType(widenedElementType, &v)) {
+ return false;
+ }
+ if (!values->append(v)) {
+ return false;
+ }
+ }
+
+ return push(RefType::fromTypeDef(&typeDef, false));
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readArrayNewDefault(uint32_t* typeIndex,
+ Value* numElements) {
+ MOZ_ASSERT(Classify(op_) == OpKind::ArrayNewDefault);
+
+ if (!readArrayTypeIndex(typeIndex)) {
+ return false;
+ }
+
+ const TypeDef& typeDef = env_.types->type(*typeIndex);
+ const ArrayType& arrayType = typeDef.arrayType();
+
+ if (!popWithType(ValType::I32, numElements)) {
+ return false;
+ }
+
+ if (!arrayType.elementType_.isDefaultable()) {
+ return fail("array must be defaultable");
+ }
+
+ return push(RefType::fromTypeDef(&typeDef, false));
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readArrayNewData(uint32_t* typeIndex,
+ uint32_t* segIndex, Value* offset,
+ Value* numElements) {
+ MOZ_ASSERT(Classify(op_) == OpKind::ArrayNewData);
+
+ if (!readArrayTypeIndex(typeIndex)) {
+ return false;
+ }
+
+ if (!readVarU32(segIndex)) {
+ return fail("unable to read segment index");
+ }
+
+ const TypeDef& typeDef = env_.types->type(*typeIndex);
+ const ArrayType& arrayType = typeDef.arrayType();
+ FieldType elemType = arrayType.elementType_;
+ if (!elemType.isNumber() && !elemType.isPacked() && !elemType.isVector()) {
+ return fail("element type must be i8/i16/i32/i64/f32/f64/v128");
+ }
+ if (env_.dataCount.isNothing()) {
+ return fail("datacount section missing");
+ }
+ if (*segIndex >= *env_.dataCount) {
+ return fail("segment index is out of range");
+ }
+
+ if (!popWithType(ValType::I32, numElements)) {
+ return false;
+ }
+ if (!popWithType(ValType::I32, offset)) {
+ return false;
+ }
+
+ return push(RefType::fromTypeDef(&typeDef, false));
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readArrayNewElem(uint32_t* typeIndex,
+ uint32_t* segIndex, Value* offset,
+ Value* numElements) {
+ MOZ_ASSERT(Classify(op_) == OpKind::ArrayNewData);
+
+ if (!readArrayTypeIndex(typeIndex)) {
+ return false;
+ }
+
+ if (!readVarU32(segIndex)) {
+ return fail("unable to read segment index");
+ }
+
+ const TypeDef& typeDef = env_.types->type(*typeIndex);
+ const ArrayType& arrayType = typeDef.arrayType();
+ FieldType dstElemType = arrayType.elementType_;
+ if (!dstElemType.isRefType()) {
+ return fail("element type is not a reftype");
+ }
+ if (*segIndex >= env_.elemSegments.length()) {
+ return fail("segment index is out of range");
+ }
+
+ const ElemSegment* elemSeg = env_.elemSegments[*segIndex];
+ RefType srcElemType = elemSeg->elemType;
+ // srcElemType needs to be a subtype (child) of dstElemType
+ if (!checkIsSubtypeOf(srcElemType, dstElemType.refType())) {
+ return fail("incompatible element types");
+ }
+
+ if (!popWithType(ValType::I32, numElements)) {
+ return false;
+ }
+ if (!popWithType(ValType::I32, offset)) {
+ return false;
+ }
+
+ return push(RefType::fromTypeDef(&typeDef, false));
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readArrayGet(uint32_t* typeIndex,
+ FieldWideningOp wideningOp,
+ Value* index, Value* ptr) {
+ MOZ_ASSERT(Classify(op_) == OpKind::ArrayGet);
+
+ if (!readArrayTypeIndex(typeIndex)) {
+ return false;
+ }
+
+ const TypeDef& typeDef = env_.types->type(*typeIndex);
+ const ArrayType& arrayType = typeDef.arrayType();
+
+ if (!popWithType(ValType::I32, index)) {
+ return false;
+ }
+
+ if (!popWithType(RefType::fromTypeDef(&typeDef, true), ptr)) {
+ return false;
+ }
+
+ FieldType fieldType = arrayType.elementType_;
+
+ if (fieldType.isValType() && wideningOp != FieldWideningOp::None) {
+ return fail("must not specify signedness for unpacked element type");
+ }
+
+ if (!fieldType.isValType() && wideningOp == FieldWideningOp::None) {
+ return fail("must specify signedness for packed element type");
+ }
+
+ return push(fieldType.widenToValType());
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readArraySet(uint32_t* typeIndex, Value* val,
+ Value* index, Value* ptr) {
+ MOZ_ASSERT(Classify(op_) == OpKind::ArraySet);
+
+ if (!readArrayTypeIndex(typeIndex)) {
+ return false;
+ }
+
+ const TypeDef& typeDef = env_.types->type(*typeIndex);
+ const ArrayType& arrayType = typeDef.arrayType();
+
+ if (!arrayType.isMutable_) {
+ return fail("array is not mutable");
+ }
+
+ if (!popWithType(arrayType.elementType_.widenToValType(), val)) {
+ return false;
+ }
+
+ if (!popWithType(ValType::I32, index)) {
+ return false;
+ }
+
+ if (!popWithType(RefType::fromTypeDef(&typeDef, true), ptr)) {
+ return false;
+ }
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readArrayLen(bool decodeIgnoredTypeIndex,
+ Value* ptr) {
+ MOZ_ASSERT(Classify(op_) == OpKind::ArrayLen);
+
+ // TODO: remove once V8 removes array.len with type index from their snapshot
+ uint32_t unused;
+ if (decodeIgnoredTypeIndex && !readVarU32(&unused)) {
+ return false;
+ }
+
+ if (!popWithType(RefType::array(), ptr)) {
+ return false;
+ }
+
+ return push(ValType::I32);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readArrayCopy(int32_t* elemSize,
+ bool* elemsAreRefTyped,
+ Value* dstArray, Value* dstIndex,
+ Value* srcArray, Value* srcIndex,
+ Value* numElements) {
+ // *elemSize is set to 1/2/4/8/16, and *elemsAreRefTyped is set to indicate
+ // *ref-typeness of elements.
+ MOZ_ASSERT(Classify(op_) == OpKind::ArrayCopy);
+
+ uint32_t dstTypeIndex, srcTypeIndex;
+ if (!readArrayTypeIndex(&dstTypeIndex)) {
+ return false;
+ }
+ if (!readArrayTypeIndex(&srcTypeIndex)) {
+ return false;
+ }
+
+ // `dstTypeIndex`/`srcTypeIndex` are ensured by the above to both be array
+ // types. Reject if:
+ // * the dst array is not of mutable type
+ // * the element types are incompatible
+ const TypeDef& dstTypeDef = env_.types->type(dstTypeIndex);
+ const ArrayType& dstArrayType = dstTypeDef.arrayType();
+ const TypeDef& srcTypeDef = env_.types->type(srcTypeIndex);
+ const ArrayType& srcArrayType = srcTypeDef.arrayType();
+ FieldType dstElemType = dstArrayType.elementType_;
+ FieldType srcElemType = srcArrayType.elementType_;
+ if (!dstArrayType.isMutable_) {
+ return fail("destination array is not mutable");
+ }
+
+ if (!checkIsSubtypeOf(srcElemType, dstElemType)) {
+ return fail("incompatible element types");
+ }
+ bool dstIsRefType = dstElemType.isRefType();
+ MOZ_ASSERT(dstIsRefType == srcElemType.isRefType());
+
+ *elemSize = int32_t(dstElemType.size());
+ *elemsAreRefTyped = dstIsRefType;
+ MOZ_ASSERT(*elemSize >= 1 && *elemSize <= 16);
+ MOZ_ASSERT_IF(*elemsAreRefTyped, *elemSize == 4 || *elemSize == 8);
+
+ if (!popWithType(ValType::I32, numElements)) {
+ return false;
+ }
+ if (!popWithType(ValType::I32, srcIndex)) {
+ return false;
+ }
+ if (!popWithType(RefType::fromTypeDef(&srcTypeDef, true), srcArray)) {
+ return false;
+ }
+ if (!popWithType(ValType::I32, dstIndex)) {
+ return false;
+ }
+ if (!popWithType(RefType::fromTypeDef(&dstTypeDef, true), dstArray)) {
+ return false;
+ }
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readRefTestV5(RefType* sourceType,
+ uint32_t* typeIndex, Value* ref) {
+ MOZ_ASSERT(Classify(op_) == OpKind::RefTestV5);
+
+ if (!readGcTypeIndex(typeIndex)) {
+ return false;
+ }
+
+ StackType inputType;
+ if (!popWithType(RefType::any(), ref)) {
+ return false;
+ }
+ *sourceType = inputType.valTypeOr(RefType::any()).refType();
+
+ return push(ValType(ValType::I32));
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readRefCastV5(RefType* sourceType,
+ uint32_t* typeIndex, Value* ref) {
+ MOZ_ASSERT(Classify(op_) == OpKind::RefCastV5);
+
+ if (!readGcTypeIndex(typeIndex)) {
+ return false;
+ }
+
+ StackType inputType;
+ if (!popWithType(RefType::any(), ref, &inputType)) {
+ return false;
+ }
+ *sourceType = inputType.valTypeOr(RefType::any()).refType();
+
+ const TypeDef& typeDef = env_.types->type(*typeIndex);
+ return push(RefType::fromTypeDef(&typeDef, inputType.isNullableAsOperand()));
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readRefTest(bool nullable, RefType* sourceType,
+ RefType* destType, Value* ref) {
+ MOZ_ASSERT(Classify(op_) == OpKind::RefTest);
+
+ if (!readHeapType(nullable, destType)) {
+ return false;
+ }
+
+ StackType inputType;
+ if (!popWithType(destType->topType(), ref, &inputType)) {
+ return false;
+ }
+ *sourceType = inputType.valTypeOr(RefType::any()).refType();
+
+ if (!destType->isAnyHierarchy()) {
+ return fail("ref.test only supports the any hierarchy for now");
+ }
+
+ return push(ValType(ValType::I32));
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readRefCast(bool nullable, RefType* sourceType,
+ RefType* destType, Value* ref) {
+ MOZ_ASSERT(Classify(op_) == OpKind::RefCast);
+
+ if (!readHeapType(nullable, destType)) {
+ return false;
+ }
+
+ StackType inputType;
+ if (!popWithType(destType->topType(), ref, &inputType)) {
+ return false;
+ }
+ *sourceType = inputType.valTypeOr(RefType::any()).refType();
+
+ if (!destType->isAnyHierarchy()) {
+ return fail("ref.cast only supports the any hierarchy for now");
+ }
+
+ return push(*destType);
+}
+
+// `br_on_cast <flags> <labelRelativeDepth> <rt1> <rt2>`
+// branches if a reference has a given heap type.
+//
+// V6 spec text follows - note that br_on_cast and br_on_cast_fail are both
+// handled by this function (disambiguated by a flag).
+//
+// * `br_on_cast <labelidx> <reftype> <reftype>` branches if a reference has a
+// given type
+// - `br_on_cast $l rt1 rt2 : [t0* rt1] -> [t0* rt1\rt2]`
+// - iff `$l : [t0* rt2]`
+// - and `rt2 <: rt1`
+// - passes operand along with branch under target type, plus possible extra
+// args
+// - if `rt2` contains `null`, branches on null, otherwise does not
+// * `br_on_cast_fail <labelidx> <reftype> <reftype>` branches if a reference
+// does not have a given type
+// - `br_on_cast_fail $l rt1 rt2 : [t0* rt1] -> [t0* rt2]`
+// - iff `$l : [t0* rt1\rt2]`
+// - and `rt2 <: rt1`
+// - passes operand along with branch, plus possible extra args
+// - if `rt2` contains `null`, does not branch on null, otherwise does
+// where:
+// - `(ref null1? ht1)\(ref null ht2) = (ref ht1)`
+// - `(ref null1? ht1)\(ref ht2) = (ref null1? ht1)`
+//
+// The `rt1\rt2` syntax is a "diff" - it is basically rt1 minus rt2, because a
+// successful cast to rt2 will branch away. So if rt2 allows null, the result
+// after a non-branch will be non-null; on the other hand, if rt2 is
+// non-nullable, the cast will have nothing to say about nullability and the
+// nullability of rt1 will be preserved.
+//
+// `values` will be nonempty after the call, and its last entry will be the
+// type that causes a branch (rt1\rt2 or rt2, depending).
+
+template <typename Policy>
+inline bool OpIter<Policy>::readBrOnCast(bool* onSuccess,
+ uint32_t* labelRelativeDepth,
+ RefType* sourceType, RefType* destType,
+ ResultType* labelType,
+ ValueVector* values) {
+ MOZ_ASSERT(Classify(op_) == OpKind::BrOnCast);
+
+ uint8_t flags;
+ if (!readFixedU8(&flags)) {
+ return fail("unable to read br_on_cast flags");
+ }
+ bool sourceNullable = flags & (1 << 0);
+ bool destNullable = flags & (1 << 1);
+ *onSuccess = !(flags & (1 << 2));
+
+ if (!readVarU32(labelRelativeDepth)) {
+ return fail("unable to read br_on_cast depth");
+ }
+
+ // This is distinct from the actual source type we pop from the stack, which
+ // can be more specific and allow for better optimizations.
+ RefType immediateSourceType;
+ if (!readHeapType(sourceNullable, &immediateSourceType)) {
+ return fail("unable to read br_on_cast source type");
+ }
+
+ if (!readHeapType(destNullable, destType)) {
+ return fail("unable to read br_on_cast dest type");
+ }
+
+ // Check that source and destination types are compatible
+ if (!checkIsSubtypeOf(*destType, immediateSourceType)) {
+ return fail(
+ "type mismatch: source and destination types for cast are "
+ "incompatible");
+ }
+
+ RefType typeOnSuccess = *destType;
+ // This is rt1\rt2
+ RefType typeOnFail =
+ destNullable ? immediateSourceType.asNonNullable() : immediateSourceType;
+ RefType typeOnBranch = *onSuccess ? typeOnSuccess : typeOnFail;
+ RefType typeOnFallthrough = *onSuccess ? typeOnFail : typeOnSuccess;
+
+ if (!typeOnSuccess.isAnyHierarchy() || !typeOnFail.isAnyHierarchy()) {
+ return fail(
+ "br_on_cast and br_on_cast_fail only support the any hierarchy for "
+ "now");
+ }
+
+ // Get the branch target type, which will also determine the type of extra
+ // values that are passed along on branch.
+ Control* block = nullptr;
+ if (!getControl(*labelRelativeDepth, &block)) {
+ return false;
+ }
+ *labelType = block->branchTargetType();
+
+ // Check we have at least one value slot in the branch target type, so as to
+ // receive the casted or non-casted type when we branch.
+ const size_t labelTypeNumValues = labelType->length();
+ if (labelTypeNumValues < 1) {
+ return fail("type mismatch: branch target type has no value types");
+ }
+
+ // The last value slot in the branch target type is what is being cast.
+ // This slot is guaranteed to exist by the above check.
+
+ // Check that the branch target type can accept typeOnBranch.
+ if (!checkIsSubtypeOf(typeOnBranch, (*labelType)[labelTypeNumValues - 1])) {
+ return false;
+ }
+
+ // Replace the top operand with the result of falling through. Even branching
+ // on success can change the type on top of the stack on fallthrough.
+ Value inputValue;
+ StackType inputType;
+ if (!popWithType(immediateSourceType, &inputValue, &inputType)) {
+ return false;
+ }
+ *sourceType = inputType.valTypeOr(RefType::any()).refType();
+ infalliblePush(TypeAndValue(typeOnFallthrough, inputValue));
+
+ // Create a copy of the branch target type, with the relevant value slot
+ // replaced by typeOnFallthrough.
+ ValTypeVector fallthroughTypes;
+ if (!labelType->cloneToVector(&fallthroughTypes)) {
+ return false;
+ }
+ fallthroughTypes[labelTypeNumValues - 1] = typeOnFallthrough;
+
+ return checkTopTypeMatches(ResultType::Vector(fallthroughTypes), values,
+ /*rewriteStackTypes=*/false);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::checkBrOnCastCommonV5(uint32_t labelRelativeDepth,
+ RefType* sourceType,
+ ValType castToType,
+ ResultType* labelType,
+ ValueVector* values) {
+ if (!(castToType.isRefType() && castToType.refType().isAnyHierarchy())) {
+ return fail("br_on_cast v5 only supports the any hierarchy");
+ }
+
+ // The casted from type is any subtype of anyref.
+ ValType anyrefType(RefType::any());
+ *sourceType = RefType::any();
+
+ // Get the branch target type, which will also determine the type of extra
+ // values that are passed along with the casted type. This validates
+ // requirement (1).
+ Control* block = nullptr;
+ if (!getControl(labelRelativeDepth, &block)) {
+ return false;
+ }
+ *labelType = block->branchTargetType();
+
+ // Check we have at least one value slot in the branch target type, so as to
+ // receive the casted type in the case where the cast succeeds.
+ const size_t labelTypeNumValues = labelType->length();
+ if (labelTypeNumValues < 1) {
+ return fail("type mismatch: branch target type has no value slots");
+ }
+
+ // The last value slot in the branch target type is what is being cast.
+ // This slot is guaranteed to exist by the above check.
+
+ // Check that the branch target type can accept castType. The branch target
+ // may specify a supertype of castType, and this is okay. Validates (2).
+ if (!checkIsSubtypeOf(castToType, (*labelType)[labelTypeNumValues - 1])) {
+ return false;
+ }
+
+ // Create a copy of the branch target type, with the relevant value slot
+ // replaced by anyrefType. Use this to check that the stack has the proper
+ // types to branch to the target type.
+ //
+ // TODO: We could avoid a potential allocation here by handwriting a custom
+ // checkTopTypeMatches that handles this case.
+ ValTypeVector fallthroughType;
+ if (!labelType->cloneToVector(&fallthroughType)) {
+ return false;
+ }
+ fallthroughType[labelTypeNumValues - 1] = anyrefType;
+
+ // Validates the first half of (3), if we pretend that topType is eqref,
+ // which it isn't really.
+ return checkTopTypeMatches(ResultType::Vector(fallthroughType), values,
+ /*rewriteStackTypes=*/false);
+}
+
+// `br_on_cast <labelRelativeDepth> null? <castTypeIndex>`
+// branches if a reference has a given heap type
+//
+// br_on_cast $label null? castType : [t0* (ref null argType)] ->
+// [t0* (ref null2? argType)]
+// (1) iff $label : [t0* labelType]
+// (2) and (ref null3? castType) <: labelType
+// (3) and castType <: topType and argType <: topType
+// where topType is a common super type
+// (4) and null? = null3? =/= null2?
+//
+// - passes operand along with branch under target type,
+// plus possible extra args
+// - if null? is present, branches on null, otherwise does not
+//
+// Currently unhandled:
+// (3) partial check, and not really right
+// (4) neither checked nor implemented
+//
+// `values` will be nonempty after the call, and its last entry will be that
+// of the argument.
+
+template <typename Policy>
+inline bool OpIter<Policy>::readBrOnCastV5(uint32_t* labelRelativeDepth,
+ RefType* sourceType,
+ uint32_t* castTypeIndex,
+ ResultType* labelType,
+ ValueVector* values) {
+ MOZ_ASSERT(Classify(op_) == OpKind::BrOnCastV5);
+
+ if (!readVarU32(labelRelativeDepth)) {
+ return fail("unable to read br_on_cast depth");
+ }
+
+ if (!readGcTypeIndex(castTypeIndex)) {
+ return false;
+ }
+
+ // The casted to type is a non-nullable reference to the type index
+ // specified as an immediate.
+ const TypeDef& castTypeDef = env_.types->type(*castTypeIndex);
+ ValType castType(RefType::fromTypeDef(&castTypeDef, false));
+
+ return checkBrOnCastCommonV5(*labelRelativeDepth, sourceType, castType,
+ labelType, values);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readBrOnCastHeapV5(
+ bool nullable, uint32_t* labelRelativeDepth, RefType* sourceType,
+ RefType* destType, ResultType* labelType, ValueVector* values) {
+ MOZ_ASSERT(Classify(op_) == OpKind::BrOnCastV5);
+
+ if (!readVarU32(labelRelativeDepth)) {
+ return fail("unable to read br_on_cast depth");
+ }
+
+ if (!readHeapType(nullable, destType)) {
+ return false;
+ }
+
+ ValType castToType(*destType);
+ return checkBrOnCastCommonV5(*labelRelativeDepth, sourceType, castToType,
+ labelType, values);
+}
+
+// `br_on_cast_fail <labelRelativeDepth> null? <castTypeIndex>`
+// branches if a reference does not have a given heap type
+//
+// br_on_cast_fail $label null? castType : [t0* (ref null argType)] ->
+// [t0* (ref null2? castType)]
+// (1) iff $label : [t0* labelType]
+// (2) and (ref null3? argType) <: labelType
+// (3) and castType <: topType and argType <: topType
+// where topType is a common super type
+// (4) and null? = null2? =/= null3?
+//
+// - passes operand along with branch, plus possible extra args
+// - if null? is present, does not branch on null, otherwise does
+//
+// Currently unhandled:
+// (3) partial check, and not really right
+// (4) neither checked nor implemented
+//
+// `values` will be nonempty after the call, and its last entry will be that
+// of the argument.
+template <typename Policy>
+inline bool OpIter<Policy>::checkBrOnCastFailCommonV5(
+ uint32_t labelRelativeDepth, RefType* sourceType, ValType castToType,
+ ResultType* labelType, ValueVector* values) {
+ if (!(castToType.isRefType() && castToType.refType().isAnyHierarchy())) {
+ return fail("br_on_cast_fail v5 only supports the any hierarchy");
+ }
+
+ // Get the branch target type, which will also determine the type of extra
+ // values that are passed along with the casted type. This validates
+ // requirement (1).
+ Control* block = nullptr;
+ if (!getControl(labelRelativeDepth, &block)) {
+ return false;
+ }
+ *labelType = block->branchTargetType();
+
+ // Check we at least have one value slot in the branch target type, so as to
+ // receive the argument value in the case where the cast fails.
+ if (labelType->length() < 1) {
+ return fail("type mismatch: branch target type has no value slots");
+ }
+
+ // Check all operands match the failure label's target type. Validates (2).
+ if (!checkTopTypeMatches(*labelType, values,
+ /*rewriteStackTypes=*/false)) {
+ return false;
+ }
+
+ // The top operand needs to be compatible with the casted from type.
+ // Validates the first half of (3), if we pretend that topType is eqref,
+ // which it isn't really.
+ Value ignored;
+ StackType inputValue;
+ if (!popWithType(ValType(RefType::any()), &ignored, &inputValue)) {
+ return false;
+ }
+ *sourceType = inputValue.valTypeOr(RefType::any()).refType();
+
+ // The top result in the fallthrough case is the casted to type.
+ infalliblePush(TypeAndValue(castToType, ignored));
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readBrOnCastFailV5(uint32_t* labelRelativeDepth,
+ RefType* sourceType,
+ uint32_t* castTypeIndex,
+ ResultType* labelType,
+ ValueVector* values) {
+ MOZ_ASSERT(Classify(op_) == OpKind::BrOnCastFailV5);
+
+ if (!readVarU32(labelRelativeDepth)) {
+ return fail("unable to read br_on_cast_fail depth");
+ }
+
+ if (!readGcTypeIndex(castTypeIndex)) {
+ return false;
+ }
+
+ // The casted to type is a non-nullable reference to the type index
+ // specified as an immediate.
+ const TypeDef& castToTypeDef = env_.types->type(*castTypeIndex);
+ ValType castToType(RefType::fromTypeDef(&castToTypeDef, false));
+
+ return checkBrOnCastFailCommonV5(*labelRelativeDepth, sourceType, castToType,
+ labelType, values);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readBrOnCastFailHeapV5(
+ bool nullable, uint32_t* labelRelativeDepth, RefType* sourceType,
+ RefType* destType, ResultType* labelType, ValueVector* values) {
+ MOZ_ASSERT(Classify(op_) == OpKind::BrOnCastFailV5);
+
+ if (!readVarU32(labelRelativeDepth)) {
+ return fail("unable to read br_on_cast_fail depth");
+ }
+
+ if (!readHeapType(nullable, destType)) {
+ return false;
+ }
+
+ ValType castToType(*destType);
+ return checkBrOnCastFailCommonV5(*labelRelativeDepth, sourceType, castToType,
+ labelType, values);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readBrOnNonStructV5(uint32_t* labelRelativeDepth,
+ ResultType* labelType,
+ ValueVector* values) {
+ MOZ_ASSERT(Classify(op_) == OpKind::BrOnNonStructV5);
+
+ if (!readVarU32(labelRelativeDepth)) {
+ return fail("unable to read br_on_non_struct depth");
+ }
+
+ RefType sourceTypeIgnored;
+
+ // The casted to type is a non-nullable reference to a struct.
+ ValType castToType(RefType::struct_().asNonNullable());
+
+ return checkBrOnCastFailCommonV5(*labelRelativeDepth, &sourceTypeIgnored,
+ castToType, labelType, values);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readRefConversion(RefType operandType,
+ RefType resultType,
+ Value* operandValue) {
+ MOZ_ASSERT(Classify(op_) == OpKind::RefConversion);
+
+ StackType actualOperandType;
+ if (!popWithType(ValType(operandType), operandValue, &actualOperandType)) {
+ return false;
+ }
+
+ // The result nullability is the same as the operand nullability
+ bool outputNullable = actualOperandType.isNullableAsOperand();
+ infalliblePush(ValType(resultType.withIsNullable(outputNullable)));
+ return true;
+}
+
+#endif // ENABLE_WASM_GC
+
+#ifdef ENABLE_WASM_SIMD
+
+template <typename Policy>
+inline bool OpIter<Policy>::readLaneIndex(uint32_t inputLanes,
+ uint32_t* laneIndex) {
+ uint8_t tmp;
+ if (!readFixedU8(&tmp)) {
+ return false; // Caller signals error
+ }
+ if (tmp >= inputLanes) {
+ return false;
+ }
+ *laneIndex = tmp;
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readExtractLane(ValType resultType,
+ uint32_t inputLanes,
+ uint32_t* laneIndex, Value* input) {
+ MOZ_ASSERT(Classify(op_) == OpKind::ExtractLane);
+
+ if (!readLaneIndex(inputLanes, laneIndex)) {
+ return fail("missing or invalid extract_lane lane index");
+ }
+
+ if (!popWithType(ValType::V128, input)) {
+ return false;
+ }
+
+ infalliblePush(resultType);
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readReplaceLane(ValType operandType,
+ uint32_t inputLanes,
+ uint32_t* laneIndex,
+ Value* baseValue, Value* operand) {
+ MOZ_ASSERT(Classify(op_) == OpKind::ReplaceLane);
+
+ if (!readLaneIndex(inputLanes, laneIndex)) {
+ return fail("missing or invalid replace_lane lane index");
+ }
+
+ if (!popWithType(operandType, operand)) {
+ return false;
+ }
+
+ if (!popWithType(ValType::V128, baseValue)) {
+ return false;
+ }
+
+ infalliblePush(ValType::V128);
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readVectorShift(Value* baseValue, Value* shift) {
+ MOZ_ASSERT(Classify(op_) == OpKind::VectorShift);
+
+ if (!popWithType(ValType::I32, shift)) {
+ return false;
+ }
+
+ if (!popWithType(ValType::V128, baseValue)) {
+ return false;
+ }
+
+ infalliblePush(ValType::V128);
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readVectorShuffle(Value* v1, Value* v2,
+ V128* selectMask) {
+ MOZ_ASSERT(Classify(op_) == OpKind::VectorShuffle);
+
+ for (unsigned char& byte : selectMask->bytes) {
+ uint8_t tmp;
+ if (!readFixedU8(&tmp)) {
+ return fail("unable to read shuffle index");
+ }
+ if (tmp > 31) {
+ return fail("shuffle index out of range");
+ }
+ byte = tmp;
+ }
+
+ if (!popWithType(ValType::V128, v2)) {
+ return false;
+ }
+
+ if (!popWithType(ValType::V128, v1)) {
+ return false;
+ }
+
+ infalliblePush(ValType::V128);
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readV128Const(V128* value) {
+ MOZ_ASSERT(Classify(op_) == OpKind::V128);
+
+ if (!d_.readV128Const(value)) {
+ return false;
+ }
+
+ return push(ValType::V128);
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readLoadSplat(uint32_t byteSize,
+ LinearMemoryAddress<Value>* addr) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Load);
+
+ if (!readLinearMemoryAddress(byteSize, addr)) {
+ return false;
+ }
+
+ infalliblePush(ValType::V128);
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readLoadExtend(LinearMemoryAddress<Value>* addr) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Load);
+
+ if (!readLinearMemoryAddress(/*byteSize=*/8, addr)) {
+ return false;
+ }
+
+ infalliblePush(ValType::V128);
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readLoadLane(uint32_t byteSize,
+ LinearMemoryAddress<Value>* addr,
+ uint32_t* laneIndex, Value* input) {
+ MOZ_ASSERT(Classify(op_) == OpKind::LoadLane);
+
+ if (!popWithType(ValType::V128, input)) {
+ return false;
+ }
+
+ if (!readLinearMemoryAddress(byteSize, addr)) {
+ return false;
+ }
+
+ uint32_t inputLanes = 16 / byteSize;
+ if (!readLaneIndex(inputLanes, laneIndex)) {
+ return fail("missing or invalid load_lane lane index");
+ }
+
+ infalliblePush(ValType::V128);
+
+ return true;
+}
+
+template <typename Policy>
+inline bool OpIter<Policy>::readStoreLane(uint32_t byteSize,
+ LinearMemoryAddress<Value>* addr,
+ uint32_t* laneIndex, Value* input) {
+ MOZ_ASSERT(Classify(op_) == OpKind::StoreLane);
+
+ if (!popWithType(ValType::V128, input)) {
+ return false;
+ }
+
+ if (!readLinearMemoryAddress(byteSize, addr)) {
+ return false;
+ }
+
+ uint32_t inputLanes = 16 / byteSize;
+ if (!readLaneIndex(inputLanes, laneIndex)) {
+ return fail("missing or invalid store_lane lane index");
+ }
+
+ return true;
+}
+
+#endif // ENABLE_WASM_SIMD
+
+template <typename Policy>
+inline bool OpIter<Policy>::readIntrinsic(const Intrinsic** intrinsic,
+ ValueVector* params) {
+ MOZ_ASSERT(Classify(op_) == OpKind::Intrinsic);
+
+ uint32_t id;
+ if (!d_.readVarU32(&id)) {
+ return false;
+ }
+
+ if (id >= uint32_t(IntrinsicId::Limit)) {
+ return fail("intrinsic index out of range");
+ }
+
+ *intrinsic = &Intrinsic::getFromId(IntrinsicId(id));
+
+ if (!env_.usesMemory()) {
+ return fail("can't touch memory without memory");
+ }
+ return popWithTypes((*intrinsic)->params, params);
+}
+
+} // namespace wasm
+} // namespace js
+
+static_assert(std::is_trivially_copyable<
+ js::wasm::TypeAndValueT<mozilla::Nothing>>::value,
+ "Must be trivially copyable");
+static_assert(std::is_trivially_destructible<
+ js::wasm::TypeAndValueT<mozilla::Nothing>>::value,
+ "Must be trivially destructible");
+
+static_assert(std::is_trivially_copyable<
+ js::wasm::ControlStackEntry<mozilla::Nothing>>::value,
+ "Must be trivially copyable");
+static_assert(std::is_trivially_destructible<
+ js::wasm::ControlStackEntry<mozilla::Nothing>>::value,
+ "Must be trivially destructible");
+
+#endif // wasm_op_iter_h