summaryrefslogtreecommitdiffstats
path: root/js/src/jit/MIR.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit/MIR.h')
-rw-r--r--js/src/jit/MIR.h14267
1 files changed, 14267 insertions, 0 deletions
diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h
new file mode 100644
index 000000000..dcb08c317
--- /dev/null
+++ b/js/src/jit/MIR.h
@@ -0,0 +1,14267 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Everything needed to build actual MIR instructions: the actual opcodes and
+ * instructions, the instruction interface, and use chains.
+ */
+
+#ifndef jit_MIR_h
+#define jit_MIR_h
+
+#include "mozilla/Array.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/MacroForEach.h"
+
+#include "builtin/SIMD.h"
+#include "jit/AtomicOp.h"
+#include "jit/BaselineIC.h"
+#include "jit/FixedList.h"
+#include "jit/InlineList.h"
+#include "jit/JitAllocPolicy.h"
+#include "jit/MacroAssembler.h"
+#include "jit/MOpcodes.h"
+#include "jit/TypedObjectPrediction.h"
+#include "jit/TypePolicy.h"
+#include "vm/ArrayObject.h"
+#include "vm/EnvironmentObject.h"
+#include "vm/SharedMem.h"
+#include "vm/TypedArrayCommon.h"
+#include "vm/UnboxedObject.h"
+
+// Undo windows.h damage on Win64
+#undef MemoryBarrier
+
+namespace js {
+
+class StringObject;
+
+namespace jit {
+
+class BaselineInspector;
+class Range;
+
+template <typename T>
+struct ResultWithOOM {
+ T value;
+ bool oom;
+
+ static ResultWithOOM<T> ok(T val) {
+ return { val, false };
+ }
+ static ResultWithOOM<T> fail() {
+ return { T(), true };
+ }
+};
+
+static inline
+MIRType MIRTypeFromValue(const js::Value& vp)
+{
+ if (vp.isDouble())
+ return MIRType::Double;
+ if (vp.isMagic()) {
+ switch (vp.whyMagic()) {
+ case JS_OPTIMIZED_ARGUMENTS:
+ return MIRType::MagicOptimizedArguments;
+ case JS_OPTIMIZED_OUT:
+ return MIRType::MagicOptimizedOut;
+ case JS_ELEMENTS_HOLE:
+ return MIRType::MagicHole;
+ case JS_IS_CONSTRUCTING:
+ return MIRType::MagicIsConstructing;
+ case JS_UNINITIALIZED_LEXICAL:
+ return MIRType::MagicUninitializedLexical;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected magic constant");
+ }
+ }
+ return MIRTypeFromValueType(vp.extractNonDoubleType());
+}
+
+// If simdType is one of the SIMD types suported by Ion, set mirType to the
+// corresponding MIRType, and return true.
+//
+// If simdType is not suported by Ion, return false.
+static inline MOZ_MUST_USE
+bool MaybeSimdTypeToMIRType(SimdType type, MIRType* mirType)
+{
+ switch (type) {
+ case SimdType::Uint32x4:
+ case SimdType::Int32x4: *mirType = MIRType::Int32x4; return true;
+ case SimdType::Uint16x8:
+ case SimdType::Int16x8: *mirType = MIRType::Int16x8; return true;
+ case SimdType::Uint8x16:
+ case SimdType::Int8x16: *mirType = MIRType::Int8x16; return true;
+ case SimdType::Float32x4: *mirType = MIRType::Float32x4; return true;
+ case SimdType::Bool32x4: *mirType = MIRType::Bool32x4; return true;
+ case SimdType::Bool16x8: *mirType = MIRType::Bool16x8; return true;
+ case SimdType::Bool8x16: *mirType = MIRType::Bool8x16; return true;
+ default: return false;
+ }
+}
+
+// Convert a SimdType to the corresponding MIRType, or crash.
+//
+// Note that this is not an injective mapping: SimdType has signed and unsigned
+// integer types that map to the same MIRType.
+static inline
+MIRType SimdTypeToMIRType(SimdType type)
+{
+ MIRType ret = MIRType::None;
+ JS_ALWAYS_TRUE(MaybeSimdTypeToMIRType(type, &ret));
+ return ret;
+}
+
+static inline
+SimdType MIRTypeToSimdType(MIRType type)
+{
+ switch (type) {
+ case MIRType::Int32x4: return SimdType::Int32x4;
+ case MIRType::Int16x8: return SimdType::Int16x8;
+ case MIRType::Int8x16: return SimdType::Int8x16;
+ case MIRType::Float32x4: return SimdType::Float32x4;
+ case MIRType::Bool32x4: return SimdType::Bool32x4;
+ case MIRType::Bool16x8: return SimdType::Bool16x8;
+ case MIRType::Bool8x16: return SimdType::Bool8x16;
+ default: break;
+ }
+ MOZ_CRASH("unhandled MIRType");
+}
+
+// Get the boolean MIRType with the same shape as type.
+static inline
+MIRType MIRTypeToBooleanSimdType(MIRType type)
+{
+ return SimdTypeToMIRType(GetBooleanSimdType(MIRTypeToSimdType(type)));
+}
+
+#define MIR_FLAG_LIST(_) \
+ _(InWorklist) \
+ _(EmittedAtUses) \
+ _(Commutative) \
+ _(Movable) /* Allow passes like LICM to move this instruction */ \
+ _(Lowered) /* (Debug only) has a virtual register */ \
+ _(Guard) /* Not removable if uses == 0 */ \
+ \
+ /* Flag an instruction to be considered as a Guard if the instructions
+ * bails out on some inputs.
+ *
+ * Some optimizations can replace an instruction, and leave its operands
+ * unused. When the type information of the operand got used as a
+ * predicate of the transformation, then we have to flag the operands as
+ * GuardRangeBailouts.
+ *
+ * This flag prevents further optimization of instructions, which
+ * might remove the run-time checks (bailout conditions) used as a
+ * predicate of the previous transformation.
+ */ \
+ _(GuardRangeBailouts) \
+ \
+ /* Keep the flagged instruction in resume points and do not substitute this
+ * instruction by an UndefinedValue. This might be used by call inlining
+ * when a function argument is not used by the inlined instructions.
+ */ \
+ _(ImplicitlyUsed) \
+ \
+ /* The instruction has been marked dead for lazy removal from resume
+ * points.
+ */ \
+ _(Unused) \
+ \
+ /* When a branch is removed, the uses of multiple instructions are removed.
+ * The removal of branches is based on hypotheses. These hypotheses might
+ * fail, in which case we need to bailout from the current code.
+ *
+ * When we implement a destructive optimization, we need to consider the
+ * failing cases, and consider the fact that we might resume the execution
+ * into a branch which was removed from the compiler. As such, a
+ * destructive optimization need to take into acount removed branches.
+ *
+ * In order to let destructive optimizations know about removed branches, we
+ * have to annotate instructions with the UseRemoved flag. This flag
+ * annotates instruction which were used in removed branches.
+ */ \
+ _(UseRemoved) \
+ \
+ /* Marks if the current instruction should go to the bailout paths instead
+ * of producing code as part of the control flow. This flag can only be set
+ * on instructions which are only used by ResumePoint or by other flagged
+ * instructions.
+ */ \
+ _(RecoveredOnBailout) \
+ \
+ /* Some instructions might represent an object, but the memory of these
+ * objects might be incomplete if we have not recovered all the stores which
+ * were supposed to happen before. This flag is used to annotate
+ * instructions which might return a pointer to a memory area which is not
+ * yet fully initialized. This flag is used to ensure that stores are
+ * executed before returning the value.
+ */ \
+ _(IncompleteObject) \
+ \
+ /* The current instruction got discarded from the MIR Graph. This is useful
+ * when we want to iterate over resume points and instructions, while
+ * handling instructions which are discarded without reporting to the
+ * iterator.
+ */ \
+ _(Discarded)
+
+class MDefinition;
+class MInstruction;
+class MBasicBlock;
+class MNode;
+class MUse;
+class MPhi;
+class MIRGraph;
+class MResumePoint;
+class MControlInstruction;
+
+// Represents a use of a node.
+class MUse : public TempObject, public InlineListNode<MUse>
+{
+ // Grant access to setProducerUnchecked.
+ friend class MDefinition;
+ friend class MPhi;
+
+ MDefinition* producer_; // MDefinition that is being used.
+ MNode* consumer_; // The node that is using this operand.
+
+ // Low-level unchecked edit method for replaceAllUsesWith and
+ // MPhi::removeOperand. This doesn't update use lists!
+ // replaceAllUsesWith and MPhi::removeOperand do that manually.
+ void setProducerUnchecked(MDefinition* producer) {
+ MOZ_ASSERT(consumer_);
+ MOZ_ASSERT(producer_);
+ MOZ_ASSERT(producer);
+ producer_ = producer;
+ }
+
+ public:
+ // Default constructor for use in vectors.
+ MUse()
+ : producer_(nullptr), consumer_(nullptr)
+ { }
+
+ // Move constructor for use in vectors. When an MUse is moved, it stays
+ // in its containing use list.
+ MUse(MUse&& other)
+ : InlineListNode<MUse>(mozilla::Move(other)),
+ producer_(other.producer_), consumer_(other.consumer_)
+ { }
+
+ // Construct an MUse initialized with |producer| and |consumer|.
+ MUse(MDefinition* producer, MNode* consumer)
+ {
+ initUnchecked(producer, consumer);
+ }
+
+ // Set this use, which was previously clear.
+ inline void init(MDefinition* producer, MNode* consumer);
+ // Like init, but works even when the use contains uninitialized data.
+ inline void initUnchecked(MDefinition* producer, MNode* consumer);
+ // Like initUnchecked, but set the producer to nullptr.
+ inline void initUncheckedWithoutProducer(MNode* consumer);
+ // Set this use, which was not previously clear.
+ inline void replaceProducer(MDefinition* producer);
+ // Clear this use.
+ inline void releaseProducer();
+
+ MDefinition* producer() const {
+ MOZ_ASSERT(producer_ != nullptr);
+ return producer_;
+ }
+ bool hasProducer() const {
+ return producer_ != nullptr;
+ }
+ MNode* consumer() const {
+ MOZ_ASSERT(consumer_ != nullptr);
+ return consumer_;
+ }
+
+#ifdef DEBUG
+ // Return the operand index of this MUse in its consumer. This is DEBUG-only
+ // as normal code should instead to call indexOf on the casted consumer
+ // directly, to allow it to be devirtualized and inlined.
+ size_t index() const;
+#endif
+};
+
+typedef InlineList<MUse>::iterator MUseIterator;
+
+// A node is an entry in the MIR graph. It has two kinds:
+// MInstruction: an instruction which appears in the IR stream.
+// MResumePoint: a list of instructions that correspond to the state of the
+// interpreter/Baseline stack.
+//
+// Nodes can hold references to MDefinitions. Each MDefinition has a list of
+// nodes holding such a reference (its use chain).
+class MNode : public TempObject
+{
+ protected:
+ MBasicBlock* block_; // Containing basic block.
+
+ public:
+ enum Kind {
+ Definition,
+ ResumePoint
+ };
+
+ MNode()
+ : block_(nullptr)
+ { }
+
+ explicit MNode(MBasicBlock* block)
+ : block_(block)
+ { }
+
+ virtual Kind kind() const = 0;
+
+ // Returns the definition at a given operand.
+ virtual MDefinition* getOperand(size_t index) const = 0;
+ virtual size_t numOperands() const = 0;
+ virtual size_t indexOf(const MUse* u) const = 0;
+
+ bool isDefinition() const {
+ return kind() == Definition;
+ }
+ bool isResumePoint() const {
+ return kind() == ResumePoint;
+ }
+ MBasicBlock* block() const {
+ return block_;
+ }
+ MBasicBlock* caller() const;
+
+ // Sets an already set operand, updating use information. If you're looking
+ // for setOperand, this is probably what you want.
+ virtual void replaceOperand(size_t index, MDefinition* operand) = 0;
+
+ // Resets the operand to an uninitialized state, breaking the link
+ // with the previous operand's producer.
+ void releaseOperand(size_t index) {
+ getUseFor(index)->releaseProducer();
+ }
+ bool hasOperand(size_t index) const {
+ return getUseFor(index)->hasProducer();
+ }
+
+ inline MDefinition* toDefinition();
+ inline MResumePoint* toResumePoint();
+
+ virtual MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const;
+
+ virtual void dump(GenericPrinter& out) const = 0;
+ virtual void dump() const = 0;
+
+ protected:
+ // Need visibility on getUseFor to avoid O(n^2) complexity.
+ friend void AssertBasicGraphCoherency(MIRGraph& graph);
+
+ // Gets the MUse corresponding to given operand.
+ virtual MUse* getUseFor(size_t index) = 0;
+ virtual const MUse* getUseFor(size_t index) const = 0;
+};
+
+class AliasSet {
+ private:
+ uint32_t flags_;
+
+ public:
+ enum Flag {
+ None_ = 0,
+ ObjectFields = 1 << 0, // shape, class, slots, length etc.
+ Element = 1 << 1, // A Value member of obj->elements or
+ // a typed object.
+ UnboxedElement = 1 << 2, // An unboxed scalar or reference member of
+ // a typed array, typed object, or unboxed
+ // object.
+ DynamicSlot = 1 << 3, // A Value member of obj->slots.
+ FixedSlot = 1 << 4, // A Value member of obj->fixedSlots().
+ DOMProperty = 1 << 5, // A DOM property
+ FrameArgument = 1 << 6, // An argument kept on the stack frame
+ WasmGlobalVar = 1 << 7, // An asm.js/wasm global var
+ WasmHeap = 1 << 8, // An asm.js/wasm heap load
+ TypedArrayLength = 1 << 9,// A typed array's length
+ Last = TypedArrayLength,
+ Any = Last | (Last - 1),
+
+ NumCategories = 10,
+
+ // Indicates load or store.
+ Store_ = 1 << 31
+ };
+
+ static_assert((1 << NumCategories) - 1 == Any,
+ "NumCategories must include all flags present in Any");
+
+ explicit AliasSet(uint32_t flags)
+ : flags_(flags)
+ {
+ }
+
+ public:
+ static const char* Name(size_t flag);
+
+ inline bool isNone() const {
+ return flags_ == None_;
+ }
+ uint32_t flags() const {
+ return flags_ & Any;
+ }
+ inline bool isStore() const {
+ return !!(flags_ & Store_);
+ }
+ inline bool isLoad() const {
+ return !isStore() && !isNone();
+ }
+ inline AliasSet operator |(const AliasSet& other) const {
+ return AliasSet(flags_ | other.flags_);
+ }
+ inline AliasSet operator&(const AliasSet& other) const {
+ return AliasSet(flags_ & other.flags_);
+ }
+ static AliasSet None() {
+ return AliasSet(None_);
+ }
+ static AliasSet Load(uint32_t flags) {
+ MOZ_ASSERT(flags && !(flags & Store_));
+ return AliasSet(flags);
+ }
+ static AliasSet Store(uint32_t flags) {
+ MOZ_ASSERT(flags && !(flags & Store_));
+ return AliasSet(flags | Store_);
+ }
+ static uint32_t BoxedOrUnboxedElements(JSValueType type) {
+ return (type == JSVAL_TYPE_MAGIC) ? Element : UnboxedElement;
+ }
+};
+
+typedef Vector<MDefinition*, 6, JitAllocPolicy> MDefinitionVector;
+typedef Vector<MInstruction*, 6, JitAllocPolicy> MInstructionVector;
+typedef Vector<MDefinition*, 1, JitAllocPolicy> MStoreVector;
+
+class StoreDependency : public TempObject
+{
+ MStoreVector all_;
+
+ public:
+ explicit StoreDependency(TempAllocator& alloc)
+ : all_(alloc)
+ { }
+
+ MOZ_MUST_USE bool init(MDefinitionVector& all) {
+ if (!all_.appendAll(all))
+ return false;
+ return true;
+ }
+
+ MStoreVector& get() {
+ return all_;
+ }
+};
+
+// An MDefinition is an SSA name.
+class MDefinition : public MNode
+{
+ friend class MBasicBlock;
+
+ public:
+ enum Opcode {
+# define DEFINE_OPCODES(op) Op_##op,
+ MIR_OPCODE_LIST(DEFINE_OPCODES)
+# undef DEFINE_OPCODES
+ Op_Invalid
+ };
+
+ private:
+ InlineList<MUse> uses_; // Use chain.
+ uint32_t id_; // Instruction ID, which after block re-ordering
+ // is sorted within a basic block.
+ uint32_t flags_; // Bit flags.
+ Range* range_; // Any computed range for this def.
+ MIRType resultType_; // Representation of result type.
+ TemporaryTypeSet* resultTypeSet_; // Optional refinement of the result type.
+ union {
+ MDefinition* loadDependency_; // Implicit dependency (store, call, etc.) of this
+ StoreDependency* storeDependency_; // instruction. Used by alias analysis, GVN and LICM.
+ uint32_t virtualRegister_; // Used by lowering to map definitions to virtual registers.
+ };
+
+ // Track bailouts by storing the current pc in MIR instruction. Also used
+ // for profiling and keeping track of what the last known pc was.
+ const BytecodeSite* trackedSite_;
+
+ private:
+ enum Flag {
+ None = 0,
+# define DEFINE_FLAG(flag) flag,
+ MIR_FLAG_LIST(DEFINE_FLAG)
+# undef DEFINE_FLAG
+ Total
+ };
+
+ bool hasFlags(uint32_t flags) const {
+ return (flags_ & flags) == flags;
+ }
+ void removeFlags(uint32_t flags) {
+ flags_ &= ~flags;
+ }
+ void setFlags(uint32_t flags) {
+ flags_ |= flags;
+ }
+
+ protected:
+ virtual void setBlock(MBasicBlock* block) {
+ block_ = block;
+ }
+
+ static HashNumber addU32ToHash(HashNumber hash, uint32_t data);
+
+ public:
+ MDefinition()
+ : id_(0),
+ flags_(0),
+ range_(nullptr),
+ resultType_(MIRType::None),
+ resultTypeSet_(nullptr),
+ loadDependency_(nullptr),
+ trackedSite_(nullptr)
+ { }
+
+ // Copying a definition leaves the list of uses and the block empty.
+ explicit MDefinition(const MDefinition& other)
+ : id_(0),
+ flags_(other.flags_),
+ range_(other.range_),
+ resultType_(other.resultType_),
+ resultTypeSet_(other.resultTypeSet_),
+ loadDependency_(other.loadDependency_),
+ trackedSite_(other.trackedSite_)
+ { }
+
+ virtual Opcode op() const = 0;
+ virtual const char* opName() const = 0;
+ virtual void accept(MDefinitionVisitor* visitor) = 0;
+
+ void printName(GenericPrinter& out) const;
+ static void PrintOpcodeName(GenericPrinter& out, Opcode op);
+ virtual void printOpcode(GenericPrinter& out) const;
+ void dump(GenericPrinter& out) const override;
+ void dump() const override;
+ void dumpLocation(GenericPrinter& out) const;
+ void dumpLocation() const;
+
+ // For LICM.
+ virtual bool neverHoist() const { return false; }
+
+ // Also for LICM. Test whether this definition is likely to be a call, which
+ // would clobber all or many of the floating-point registers, such that
+ // hoisting floating-point constants out of containing loops isn't likely to
+ // be worthwhile.
+ virtual bool possiblyCalls() const { return false; }
+
+ void setTrackedSite(const BytecodeSite* site) {
+ MOZ_ASSERT(site);
+ trackedSite_ = site;
+ }
+ const BytecodeSite* trackedSite() const {
+ return trackedSite_;
+ }
+ jsbytecode* trackedPc() const {
+ return trackedSite_ ? trackedSite_->pc() : nullptr;
+ }
+ InlineScriptTree* trackedTree() const {
+ return trackedSite_ ? trackedSite_->tree() : nullptr;
+ }
+ TrackedOptimizations* trackedOptimizations() const {
+ return trackedSite_ && trackedSite_->hasOptimizations()
+ ? trackedSite_->optimizations()
+ : nullptr;
+ }
+
+ JSScript* profilerLeaveScript() const {
+ return trackedTree()->outermostCaller()->script();
+ }
+
+ jsbytecode* profilerLeavePc() const {
+ // If this is in a top-level function, use the pc directly.
+ if (trackedTree()->isOutermostCaller())
+ return trackedPc();
+
+ // Walk up the InlineScriptTree chain to find the top-most callPC
+ InlineScriptTree* curTree = trackedTree();
+ InlineScriptTree* callerTree = curTree->caller();
+ while (!callerTree->isOutermostCaller()) {
+ curTree = callerTree;
+ callerTree = curTree->caller();
+ }
+
+ // Return the callPc of the topmost inlined script.
+ return curTree->callerPc();
+ }
+
+ // Return the range of this value, *before* any bailout checks. Contrast
+ // this with the type() method, and the Range constructor which takes an
+ // MDefinition*, which describe the value *after* any bailout checks.
+ //
+ // Warning: Range analysis is removing the bit-operations such as '| 0' at
+ // the end of the transformations. Using this function to analyse any
+ // operands after the truncate phase of the range analysis will lead to
+ // errors. Instead, one should define the collectRangeInfoPreTrunc() to set
+ // the right set of flags which are dependent on the range of the inputs.
+ Range* range() const {
+ MOZ_ASSERT(type() != MIRType::None);
+ return range_;
+ }
+ void setRange(Range* range) {
+ MOZ_ASSERT(type() != MIRType::None);
+ range_ = range;
+ }
+
+ virtual HashNumber valueHash() const;
+ virtual bool congruentTo(const MDefinition* ins) const {
+ return false;
+ }
+ bool congruentIfOperandsEqual(const MDefinition* ins) const;
+ virtual MDefinition* foldsTo(TempAllocator& alloc);
+ virtual void analyzeEdgeCasesForward();
+ virtual void analyzeEdgeCasesBackward();
+
+ // When a floating-point value is used by nodes which would prefer to
+ // recieve integer inputs, we may be able to help by computing our result
+ // into an integer directly.
+ //
+ // A value can be truncated in 4 differents ways:
+ // 1. Ignore Infinities (x / 0 --> 0).
+ // 2. Ignore overflow (INT_MIN / -1 == (INT_MAX + 1) --> INT_MIN)
+ // 3. Ignore negative zeros. (-0 --> 0)
+ // 4. Ignore remainder. (3 / 4 --> 0)
+ //
+ // Indirect truncation is used to represent that we are interested in the
+ // truncated result, but only if it can safely flow into operations which
+ // are computed modulo 2^32, such as (2) and (3). Infinities are not safe,
+ // as they would have absorbed other math operations. Remainders are not
+ // safe, as fractions can be scaled up by multiplication.
+ //
+ // Division is a particularly interesting node here because it covers all 4
+ // cases even when its own operands are integers.
+ //
+ // Note that these enum values are ordered from least value-modifying to
+ // most value-modifying, and code relies on this ordering.
+ enum TruncateKind {
+ // No correction.
+ NoTruncate = 0,
+ // An integer is desired, but we can't skip bailout checks.
+ TruncateAfterBailouts = 1,
+ // The value will be truncated after some arithmetic (see above).
+ IndirectTruncate = 2,
+ // Direct and infallible truncation to int32.
+ Truncate = 3
+ };
+
+ static const char * TruncateKindString(TruncateKind kind) {
+ switch(kind) {
+ case NoTruncate:
+ return "NoTruncate";
+ case TruncateAfterBailouts:
+ return "TruncateAfterBailouts";
+ case IndirectTruncate:
+ return "IndirectTruncate";
+ case Truncate:
+ return "Truncate";
+ default:
+ MOZ_CRASH("Unknown truncate kind.");
+ }
+ }
+
+ // |needTruncation| records the truncation kind of the results, such that it
+ // can be used to truncate the operands of this instruction. If
+ // |needTruncation| function returns true, then the |truncate| function is
+ // called on the same instruction to mutate the instruction, such as
+ // updating the return type, the range and the specialization of the
+ // instruction.
+ virtual bool needTruncation(TruncateKind kind);
+ virtual void truncate();
+
+ // Determine what kind of truncate this node prefers for the operand at the
+ // given index.
+ virtual TruncateKind operandTruncateKind(size_t index) const;
+
+ // Compute an absolute or symbolic range for the value of this node.
+ virtual void computeRange(TempAllocator& alloc) {
+ }
+
+ // Collect information from the pre-truncated ranges.
+ virtual void collectRangeInfoPreTrunc() {
+ }
+
+ MNode::Kind kind() const override {
+ return MNode::Definition;
+ }
+
+ uint32_t id() const {
+ MOZ_ASSERT(block_);
+ return id_;
+ }
+ void setId(uint32_t id) {
+ id_ = id;
+ }
+
+#define FLAG_ACCESSOR(flag) \
+ bool is##flag() const {\
+ return hasFlags(1 << flag);\
+ }\
+ void set##flag() {\
+ MOZ_ASSERT(!hasFlags(1 << flag));\
+ setFlags(1 << flag);\
+ }\
+ void setNot##flag() {\
+ MOZ_ASSERT(hasFlags(1 << flag));\
+ removeFlags(1 << flag);\
+ }\
+ void set##flag##Unchecked() {\
+ setFlags(1 << flag);\
+ } \
+ void setNot##flag##Unchecked() {\
+ removeFlags(1 << flag);\
+ }
+
+ MIR_FLAG_LIST(FLAG_ACCESSOR)
+#undef FLAG_ACCESSOR
+
+ // Return the type of this value. This may be speculative, and enforced
+ // dynamically with the use of bailout checks. If all the bailout checks
+ // pass, the value will have this type.
+ //
+ // Unless this is an MUrsh that has bailouts disabled, which, as a special
+ // case, may return a value in (INT32_MAX,UINT32_MAX] even when its type()
+ // is MIRType::Int32.
+ MIRType type() const {
+ return resultType_;
+ }
+
+ TemporaryTypeSet* resultTypeSet() const {
+ return resultTypeSet_;
+ }
+ bool emptyResultTypeSet() const;
+
+ bool mightBeType(MIRType type) const {
+ MOZ_ASSERT(type != MIRType::Value);
+ MOZ_ASSERT(type != MIRType::ObjectOrNull);
+
+ if (type == this->type())
+ return true;
+
+ if (this->type() == MIRType::ObjectOrNull)
+ return type == MIRType::Object || type == MIRType::Null;
+
+ if (this->type() == MIRType::Value)
+ return !resultTypeSet() || resultTypeSet()->mightBeMIRType(type);
+
+ return false;
+ }
+
+ bool mightBeMagicType() const;
+
+ bool maybeEmulatesUndefined(CompilerConstraintList* constraints);
+
+ // Float32 specialization operations (see big comment in IonAnalysis before the Float32
+ // specialization algorithm).
+ virtual bool isFloat32Commutative() const { return false; }
+ virtual bool canProduceFloat32() const { return false; }
+ virtual bool canConsumeFloat32(MUse* use) const { return false; }
+ virtual void trySpecializeFloat32(TempAllocator& alloc) {}
+#ifdef DEBUG
+ // Used during the pass that checks that Float32 flow into valid MDefinitions
+ virtual bool isConsistentFloat32Use(MUse* use) const {
+ return type() == MIRType::Float32 || canConsumeFloat32(use);
+ }
+#endif
+
+ // Returns the beginning of this definition's use chain.
+ MUseIterator usesBegin() const {
+ return uses_.begin();
+ }
+
+ // Returns the end of this definition's use chain.
+ MUseIterator usesEnd() const {
+ return uses_.end();
+ }
+
+ bool canEmitAtUses() const {
+ return !isEmittedAtUses();
+ }
+
+ // Removes a use at the given position
+ void removeUse(MUse* use) {
+ uses_.remove(use);
+ }
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ // Number of uses of this instruction. This function is only available
+ // in DEBUG mode since it requires traversing the list. Most users should
+ // use hasUses() or hasOneUse() instead.
+ size_t useCount() const;
+
+ // Number of uses of this instruction (only counting MDefinitions, ignoring
+ // MResumePoints). This function is only available in DEBUG mode since it
+ // requires traversing the list. Most users should use hasUses() or
+ // hasOneUse() instead.
+ size_t defUseCount() const;
+#endif
+
+ // Test whether this MDefinition has exactly one use.
+ bool hasOneUse() const;
+
+ // Test whether this MDefinition has exactly one use.
+ // (only counting MDefinitions, ignoring MResumePoints)
+ bool hasOneDefUse() const;
+
+ // Test whether this MDefinition has at least one use.
+ // (only counting MDefinitions, ignoring MResumePoints)
+ bool hasDefUses() const;
+
+ // Test whether this MDefinition has at least one non-recovered use.
+ // (only counting MDefinitions, ignoring MResumePoints)
+ bool hasLiveDefUses() const;
+
+ bool hasUses() const {
+ return !uses_.empty();
+ }
+
+ void addUse(MUse* use) {
+ MOZ_ASSERT(use->producer() == this);
+ uses_.pushFront(use);
+ }
+ void addUseUnchecked(MUse* use) {
+ MOZ_ASSERT(use->producer() == this);
+ uses_.pushFrontUnchecked(use);
+ }
+ void replaceUse(MUse* old, MUse* now) {
+ MOZ_ASSERT(now->producer() == this);
+ uses_.replace(old, now);
+ }
+
+ // Replace the current instruction by a dominating instruction |dom| in all
+ // uses of the current instruction.
+ void replaceAllUsesWith(MDefinition* dom);
+
+ // Like replaceAllUsesWith, but doesn't set UseRemoved on |this|'s operands.
+ void justReplaceAllUsesWith(MDefinition* dom);
+
+ // Like justReplaceAllUsesWith, but doesn't replace its own use to the
+ // dominating instruction (which would introduce a circular dependency).
+ void justReplaceAllUsesWithExcept(MDefinition* dom);
+
+ // Replace the current instruction by an optimized-out constant in all uses
+ // of the current instruction. Note, that optimized-out constant should not
+ // be observed, and thus they should not flow in any computation.
+ MOZ_MUST_USE bool optimizeOutAllUses(TempAllocator& alloc);
+
+ // Replace the current instruction by a dominating instruction |dom| in all
+ // instruction, but keep the current instruction for resume point and
+ // instruction which are recovered on bailouts.
+ void replaceAllLiveUsesWith(MDefinition* dom);
+
+ // Mark this instruction as having replaced all uses of ins, as during GVN,
+ // returning false if the replacement should not be performed. For use when
+ // GVN eliminates instructions which are not equivalent to one another.
+ virtual MOZ_MUST_USE bool updateForReplacement(MDefinition* ins) {
+ return true;
+ }
+
+ void setVirtualRegister(uint32_t vreg) {
+ virtualRegister_ = vreg;
+ setLoweredUnchecked();
+ }
+ uint32_t virtualRegister() const {
+ MOZ_ASSERT(isLowered());
+ return virtualRegister_;
+ }
+
+ public:
+ // Opcode testing and casts.
+ template<typename MIRType> bool is() const {
+ return op() == MIRType::classOpcode;
+ }
+ template<typename MIRType> MIRType* to() {
+ MOZ_ASSERT(this->is<MIRType>());
+ return static_cast<MIRType*>(this);
+ }
+ template<typename MIRType> const MIRType* to() const {
+ MOZ_ASSERT(this->is<MIRType>());
+ return static_cast<const MIRType*>(this);
+ }
+# define OPCODE_CASTS(opcode) \
+ bool is##opcode() const { \
+ return this->is<M##opcode>(); \
+ } \
+ M##opcode* to##opcode() { \
+ return this->to<M##opcode>(); \
+ } \
+ const M##opcode* to##opcode() const { \
+ return this->to<M##opcode>(); \
+ }
+ MIR_OPCODE_LIST(OPCODE_CASTS)
+# undef OPCODE_CASTS
+
+ inline MConstant* maybeConstantValue();
+
+ inline MInstruction* toInstruction();
+ inline const MInstruction* toInstruction() const;
+ bool isInstruction() const {
+ return !isPhi();
+ }
+
+ virtual bool isControlInstruction() const {
+ return false;
+ }
+ inline MControlInstruction* toControlInstruction();
+
+ void setResultType(MIRType type) {
+ resultType_ = type;
+ }
+ void setResultTypeSet(TemporaryTypeSet* types) {
+ resultTypeSet_ = types;
+ }
+ virtual AliasSet getAliasSet() const {
+ // Instructions are effectful by default.
+ return AliasSet::Store(AliasSet::Any);
+ }
+
+ MDefinition* dependency() const {
+ if (getAliasSet().isStore())
+ return nullptr;
+ return loadDependency_;
+ }
+ void setDependency(MDefinition* dependency) {
+ MOZ_ASSERT(!getAliasSet().isStore());
+ loadDependency_ = dependency;
+ }
+ void setStoreDependency(StoreDependency* dependency) {
+ MOZ_ASSERT(getAliasSet().isStore());
+ storeDependency_ = dependency;
+ }
+ StoreDependency* storeDependency() {
+ MOZ_ASSERT_IF(!getAliasSet().isStore(), !storeDependency_);
+ return storeDependency_;
+ }
+ bool isEffectful() const {
+ return getAliasSet().isStore();
+ }
+
+#ifdef DEBUG
+ virtual bool needsResumePoint() const {
+ // Return whether this instruction should have its own resume point.
+ return isEffectful();
+ }
+#endif
+
+ enum class AliasType : uint32_t {
+ NoAlias = 0,
+ MayAlias = 1,
+ MustAlias = 2
+ };
+ virtual AliasType mightAlias(const MDefinition* store) const {
+ // Return whether this load may depend on the specified store, given
+ // that the alias sets intersect. This may be refined to exclude
+ // possible aliasing in cases where alias set flags are too imprecise.
+ if (!(getAliasSet().flags() & store->getAliasSet().flags()))
+ return AliasType::NoAlias;
+ MOZ_ASSERT(!isEffectful() && store->isEffectful());
+ return AliasType::MayAlias;
+ }
+
+ virtual bool canRecoverOnBailout() const {
+ return false;
+ }
+};
+
+// An MUseDefIterator walks over uses in a definition, skipping any use that is
+// not a definition. Items from the use list must not be deleted during
+// iteration.
+class MUseDefIterator
+{
+ const MDefinition* def_;
+ MUseIterator current_;
+
+ MUseIterator search(MUseIterator start) {
+ MUseIterator i(start);
+ for (; i != def_->usesEnd(); i++) {
+ if (i->consumer()->isDefinition())
+ return i;
+ }
+ return def_->usesEnd();
+ }
+
+ public:
+ explicit MUseDefIterator(const MDefinition* def)
+ : def_(def),
+ current_(search(def->usesBegin()))
+ { }
+
+ explicit operator bool() const {
+ return current_ != def_->usesEnd();
+ }
+ MUseDefIterator operator ++() {
+ MOZ_ASSERT(current_ != def_->usesEnd());
+ ++current_;
+ current_ = search(current_);
+ return *this;
+ }
+ MUseDefIterator operator ++(int) {
+ MUseDefIterator old(*this);
+ operator++();
+ return old;
+ }
+ MUse* use() const {
+ return *current_;
+ }
+ MDefinition* def() const {
+ return current_->consumer()->toDefinition();
+ }
+};
+
+#ifdef DEBUG
+bool
+IonCompilationCanUseNurseryPointers();
+#endif
+
+// Helper class to check that GC pointers embedded in MIR instructions are in
+// in the nursery only when the store buffer has been marked as needing to
+// cancel all ion compilations. Otherwise, off-thread Ion compilation and
+// nursery GCs can happen in parallel, so it's invalid to store pointers to
+// nursery things. There's no need to root these pointers, as GC is suppressed
+// during compilation and off-thread compilations are canceled on major GCs.
+template <typename T>
+class CompilerGCPointer
+{
+ js::gc::Cell* ptr_;
+
+ public:
+ explicit CompilerGCPointer(T ptr)
+ : ptr_(ptr)
+ {
+ MOZ_ASSERT_IF(IsInsideNursery(ptr), IonCompilationCanUseNurseryPointers());
+#ifdef DEBUG
+ PerThreadData* pt = TlsPerThreadData.get();
+ MOZ_ASSERT_IF(pt->runtimeIfOnOwnerThread(), pt->suppressGC);
+#endif
+ }
+
+ operator T() const { return static_cast<T>(ptr_); }
+ T operator->() const { return static_cast<T>(ptr_); }
+
+ private:
+ CompilerGCPointer() = delete;
+ CompilerGCPointer(const CompilerGCPointer<T>&) = delete;
+ CompilerGCPointer<T>& operator=(const CompilerGCPointer<T>&) = delete;
+};
+
+typedef CompilerGCPointer<JSObject*> CompilerObject;
+typedef CompilerGCPointer<NativeObject*> CompilerNativeObject;
+typedef CompilerGCPointer<JSFunction*> CompilerFunction;
+typedef CompilerGCPointer<JSScript*> CompilerScript;
+typedef CompilerGCPointer<PropertyName*> CompilerPropertyName;
+typedef CompilerGCPointer<Shape*> CompilerShape;
+typedef CompilerGCPointer<ObjectGroup*> CompilerObjectGroup;
+
+class MRootList : public TempObject
+{
+ public:
+ using RootVector = Vector<void*, 0, JitAllocPolicy>;
+
+ private:
+ mozilla::EnumeratedArray<JS::RootKind, JS::RootKind::Limit, mozilla::Maybe<RootVector>> roots_;
+
+ MRootList(const MRootList&) = delete;
+ void operator=(const MRootList&) = delete;
+
+ public:
+ explicit MRootList(TempAllocator& alloc);
+
+ void trace(JSTracer* trc);
+
+ template <typename T>
+ MOZ_MUST_USE bool append(T ptr) {
+ if (ptr)
+ return roots_[JS::MapTypeToRootKind<T>::kind]->append(ptr);
+ return true;
+ }
+
+ template <typename T>
+ MOZ_MUST_USE bool append(const CompilerGCPointer<T>& ptr) {
+ return append(static_cast<T>(ptr));
+ }
+ MOZ_MUST_USE bool append(const ReceiverGuard& guard) {
+ return append(guard.group) && append(guard.shape);
+ }
+};
+
+// An instruction is an SSA name that is inserted into a basic block's IR
+// stream.
+class MInstruction
+ : public MDefinition,
+ public InlineListNode<MInstruction>
+{
+ MResumePoint* resumePoint_;
+
+ protected:
+ // All MInstructions are using the "MFoo::New(alloc)" notation instead of
+ // the TempObject new operator. This code redefines the new operator as
+ // protected, and delegates to the TempObject new operator. Thus, the
+ // following code prevents calls to "new(alloc) MFoo" outside the MFoo
+ // members.
+ inline void* operator new(size_t nbytes, TempAllocator::Fallible view) throw() {
+ return TempObject::operator new(nbytes, view);
+ }
+ inline void* operator new(size_t nbytes, TempAllocator& alloc) {
+ return TempObject::operator new(nbytes, alloc);
+ }
+ template <class T>
+ inline void* operator new(size_t nbytes, T* pos) {
+ return TempObject::operator new(nbytes, pos);
+ }
+
+ public:
+ MInstruction()
+ : resumePoint_(nullptr)
+ { }
+
+ // Copying an instruction leaves the block and resume point as empty.
+ explicit MInstruction(const MInstruction& other)
+ : MDefinition(other),
+ resumePoint_(nullptr)
+ { }
+
+ // Convenient function used for replacing a load by the value of the store
+ // if the types are match, and boxing the value if they do not match.
+ MDefinition* foldsToStore(TempAllocator& alloc);
+
+ void setResumePoint(MResumePoint* resumePoint);
+
+ // Used to transfer the resume point to the rewritten instruction.
+ void stealResumePoint(MInstruction* ins);
+ void moveResumePointAsEntry();
+ void clearResumePoint();
+ MResumePoint* resumePoint() const {
+ return resumePoint_;
+ }
+
+ // For instructions which can be cloned with new inputs, with all other
+ // information being the same. clone() implementations do not need to worry
+ // about cloning generic MInstruction/MDefinition state like flags and
+ // resume points.
+ virtual bool canClone() const {
+ return false;
+ }
+ virtual MInstruction* clone(TempAllocator& alloc, const MDefinitionVector& inputs) const {
+ MOZ_CRASH();
+ }
+
+ // MIR instructions containing GC pointers should override this to append
+ // these pointers to the root list.
+ virtual bool appendRoots(MRootList& roots) const {
+ return true;
+ }
+
+ // Instructions needing to hook into type analysis should return a
+ // TypePolicy.
+ virtual TypePolicy* typePolicy() = 0;
+ virtual MIRType typePolicySpecialization() = 0;
+};
+
+#define INSTRUCTION_HEADER_WITHOUT_TYPEPOLICY(opcode) \
+ static const Opcode classOpcode = MDefinition::Op_##opcode; \
+ using MThisOpcode = M##opcode; \
+ Opcode op() const override { \
+ return classOpcode; \
+ } \
+ const char* opName() const override { \
+ return #opcode; \
+ } \
+ void accept(MDefinitionVisitor* visitor) override { \
+ visitor->visit##opcode(this); \
+ }
+
+#define INSTRUCTION_HEADER(opcode) \
+ INSTRUCTION_HEADER_WITHOUT_TYPEPOLICY(opcode) \
+ virtual TypePolicy* typePolicy() override; \
+ virtual MIRType typePolicySpecialization() override;
+
+#define ALLOW_CLONE(typename) \
+ bool canClone() const override { \
+ return true; \
+ } \
+ MInstruction* clone(TempAllocator& alloc, \
+ const MDefinitionVector& inputs) const override { \
+ MInstruction* res = new(alloc) typename(*this); \
+ for (size_t i = 0; i < numOperands(); i++) \
+ res->replaceOperand(i, inputs[i]); \
+ return res; \
+ }
+
+// Adds MFoo::New functions which are mirroring the arguments of the
+// constructors. Opcodes which are using this macro can be called with a
+// TempAllocator, or the fallible version of the TempAllocator.
+#define TRIVIAL_NEW_WRAPPERS \
+ template <typename... Args> \
+ static MThisOpcode* New(TempAllocator& alloc, Args&&... args) { \
+ return new(alloc) MThisOpcode(mozilla::Forward<Args>(args)...); \
+ } \
+ template <typename... Args> \
+ static MThisOpcode* New(TempAllocator::Fallible alloc, Args&&... args) \
+ { \
+ return new(alloc) MThisOpcode(mozilla::Forward<Args>(args)...); \
+ }
+
+
+// These macros are used as a syntactic sugar for writting getOperand
+// accessors. They are meant to be used in the body of MIR Instructions as
+// follows:
+//
+// public:
+// INSTRUCTION_HEADER(Foo)
+// NAMED_OPERANDS((0, lhs), (1, rhs))
+//
+// The above example defines 2 accessors, one named "lhs" accessing the first
+// operand, and a one named "rhs" accessing the second operand.
+#define NAMED_OPERAND_ACCESSOR(Index, Name) \
+ MDefinition* Name() const { \
+ return getOperand(Index); \
+ }
+#define NAMED_OPERAND_ACCESSOR_APPLY(Args) \
+ NAMED_OPERAND_ACCESSOR Args
+#define NAMED_OPERANDS(...) \
+ MOZ_FOR_EACH(NAMED_OPERAND_ACCESSOR_APPLY, (), (__VA_ARGS__))
+
+template <size_t Arity>
+class MAryInstruction : public MInstruction
+{
+ mozilla::Array<MUse, Arity> operands_;
+
+ protected:
+ MUse* getUseFor(size_t index) final override {
+ return &operands_[index];
+ }
+ const MUse* getUseFor(size_t index) const final override {
+ return &operands_[index];
+ }
+ void initOperand(size_t index, MDefinition* operand) {
+ operands_[index].init(operand, this);
+ }
+
+ public:
+ MDefinition* getOperand(size_t index) const final override {
+ return operands_[index].producer();
+ }
+ size_t numOperands() const final override {
+ return Arity;
+ }
+#ifdef DEBUG
+ static const size_t staticNumOperands = Arity;
+#endif
+ size_t indexOf(const MUse* u) const final override {
+ MOZ_ASSERT(u >= &operands_[0]);
+ MOZ_ASSERT(u <= &operands_[numOperands() - 1]);
+ return u - &operands_[0];
+ }
+ void replaceOperand(size_t index, MDefinition* operand) final override {
+ operands_[index].replaceProducer(operand);
+ }
+
+ MAryInstruction() { }
+
+ explicit MAryInstruction(const MAryInstruction<Arity>& other)
+ : MInstruction(other)
+ {
+ for (int i = 0; i < (int) Arity; i++) // N.B. use |int| to avoid warnings when Arity == 0
+ operands_[i].init(other.operands_[i].producer(), this);
+ }
+};
+
+class MNullaryInstruction
+ : public MAryInstruction<0>,
+ public NoTypePolicy::Data
+{ };
+
+class MUnaryInstruction : public MAryInstruction<1>
+{
+ protected:
+ explicit MUnaryInstruction(MDefinition* ins)
+ {
+ initOperand(0, ins);
+ }
+
+ public:
+ NAMED_OPERANDS((0, input))
+};
+
+class MBinaryInstruction : public MAryInstruction<2>
+{
+ protected:
+ MBinaryInstruction(MDefinition* left, MDefinition* right)
+ {
+ initOperand(0, left);
+ initOperand(1, right);
+ }
+
+ public:
+ NAMED_OPERANDS((0, lhs), (1, rhs))
+ void swapOperands() {
+ MDefinition* temp = getOperand(0);
+ replaceOperand(0, getOperand(1));
+ replaceOperand(1, temp);
+ }
+
+ protected:
+ HashNumber valueHash() const
+ {
+ MDefinition* lhs = getOperand(0);
+ MDefinition* rhs = getOperand(1);
+
+ return op() + lhs->id() + rhs->id();
+ }
+ bool binaryCongruentTo(const MDefinition* ins) const
+ {
+ if (op() != ins->op())
+ return false;
+
+ if (type() != ins->type())
+ return false;
+
+ if (isEffectful() || ins->isEffectful())
+ return false;
+
+ const MDefinition* left = getOperand(0);
+ const MDefinition* right = getOperand(1);
+ const MDefinition* tmp;
+
+ if (isCommutative() && left->id() > right->id()) {
+ tmp = right;
+ right = left;
+ left = tmp;
+ }
+
+ const MBinaryInstruction* bi = static_cast<const MBinaryInstruction*>(ins);
+ const MDefinition* insLeft = bi->getOperand(0);
+ const MDefinition* insRight = bi->getOperand(1);
+ if (isCommutative() && insLeft->id() > insRight->id()) {
+ tmp = insRight;
+ insRight = insLeft;
+ insLeft = tmp;
+ }
+
+ return left == insLeft &&
+ right == insRight;
+ }
+
+ public:
+ // Return if the operands to this instruction are both unsigned.
+ static bool unsignedOperands(MDefinition* left, MDefinition* right);
+ bool unsignedOperands();
+
+ // Replace any wrapping operands with the underlying int32 operands
+ // in case of unsigned operands.
+ void replaceWithUnsignedOperands();
+};
+
+class MTernaryInstruction : public MAryInstruction<3>
+{
+ protected:
+ MTernaryInstruction(MDefinition* first, MDefinition* second, MDefinition* third)
+ {
+ initOperand(0, first);
+ initOperand(1, second);
+ initOperand(2, third);
+ }
+
+ protected:
+ HashNumber valueHash() const
+ {
+ MDefinition* first = getOperand(0);
+ MDefinition* second = getOperand(1);
+ MDefinition* third = getOperand(2);
+
+ return op() + first->id() + second->id() + third->id();
+ }
+};
+
+class MQuaternaryInstruction : public MAryInstruction<4>
+{
+ protected:
+ MQuaternaryInstruction(MDefinition* first, MDefinition* second,
+ MDefinition* third, MDefinition* fourth)
+ {
+ initOperand(0, first);
+ initOperand(1, second);
+ initOperand(2, third);
+ initOperand(3, fourth);
+ }
+
+ protected:
+ HashNumber valueHash() const
+ {
+ MDefinition* first = getOperand(0);
+ MDefinition* second = getOperand(1);
+ MDefinition* third = getOperand(2);
+ MDefinition* fourth = getOperand(3);
+
+ return op() + first->id() + second->id() +
+ third->id() + fourth->id();
+ }
+};
+
+template <class T>
+class MVariadicT : public T
+{
+ FixedList<MUse> operands_;
+
+ protected:
+ MOZ_MUST_USE bool init(TempAllocator& alloc, size_t length) {
+ return operands_.init(alloc, length);
+ }
+ void initOperand(size_t index, MDefinition* operand) {
+ // FixedList doesn't initialize its elements, so do an unchecked init.
+ operands_[index].initUnchecked(operand, this);
+ }
+ MUse* getUseFor(size_t index) final override {
+ return &operands_[index];
+ }
+ const MUse* getUseFor(size_t index) const final override {
+ return &operands_[index];
+ }
+
+ public:
+ // Will assert if called before initialization.
+ MDefinition* getOperand(size_t index) const final override {
+ return operands_[index].producer();
+ }
+ size_t numOperands() const final override {
+ return operands_.length();
+ }
+ size_t indexOf(const MUse* u) const final override {
+ MOZ_ASSERT(u >= &operands_[0]);
+ MOZ_ASSERT(u <= &operands_[numOperands() - 1]);
+ return u - &operands_[0];
+ }
+ void replaceOperand(size_t index, MDefinition* operand) final override {
+ operands_[index].replaceProducer(operand);
+ }
+};
+
+typedef MVariadicT<MInstruction> MVariadicInstruction;
+
+// Generates an LSnapshot without further effect.
+class MStart : public MNullaryInstruction
+{
+ public:
+ INSTRUCTION_HEADER(Start)
+ TRIVIAL_NEW_WRAPPERS
+};
+
+// Instruction marking on entrypoint for on-stack replacement.
+// OSR may occur at loop headers (at JSOP_TRACE).
+// There is at most one MOsrEntry per MIRGraph.
+class MOsrEntry : public MNullaryInstruction
+{
+ protected:
+ MOsrEntry() {
+ setResultType(MIRType::Pointer);
+ }
+
+ public:
+ INSTRUCTION_HEADER(OsrEntry)
+ TRIVIAL_NEW_WRAPPERS
+};
+
+// No-op instruction. This cannot be moved or eliminated, and is intended for
+// anchoring resume points at arbitrary points in a block.
+class MNop : public MNullaryInstruction
+{
+ protected:
+ MNop() {
+ }
+
+ public:
+ INSTRUCTION_HEADER(Nop)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ ALLOW_CLONE(MNop)
+};
+
+// Truncation barrier. This is intended for protecting its input against
+// follow-up truncation optimizations.
+class MLimitedTruncate
+ : public MUnaryInstruction,
+ public ConvertToInt32Policy<0>::Data
+{
+ public:
+ TruncateKind truncate_;
+ TruncateKind truncateLimit_;
+
+ protected:
+ MLimitedTruncate(MDefinition* input, TruncateKind limit)
+ : MUnaryInstruction(input),
+ truncate_(NoTruncate),
+ truncateLimit_(limit)
+ {
+ setResultType(MIRType::Int32);
+ setResultTypeSet(input->resultTypeSet());
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(LimitedTruncate)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ void computeRange(TempAllocator& alloc) override;
+ bool needTruncation(TruncateKind kind) override;
+ TruncateKind operandTruncateKind(size_t index) const override;
+ TruncateKind truncateKind() const {
+ return truncate_;
+ }
+ void setTruncateKind(TruncateKind kind) {
+ truncate_ = kind;
+ }
+};
+
+// A constant js::Value.
+class MConstant : public MNullaryInstruction
+{
+ struct Payload {
+ union {
+ bool b;
+ int32_t i32;
+ int64_t i64;
+ float f;
+ double d;
+ JSString* str;
+ JS::Symbol* sym;
+ JSObject* obj;
+ uint64_t asBits;
+ };
+ Payload() : asBits(0) {}
+ };
+
+ Payload payload_;
+
+ static_assert(sizeof(Payload) == sizeof(uint64_t),
+ "asBits must be big enough for all payload bits");
+
+#ifdef DEBUG
+ void assertInitializedPayload() const;
+#else
+ void assertInitializedPayload() const {}
+#endif
+
+ protected:
+ MConstant(const Value& v, CompilerConstraintList* constraints);
+ explicit MConstant(JSObject* obj);
+ explicit MConstant(float f);
+ explicit MConstant(double d);
+ explicit MConstant(int64_t i);
+
+ public:
+ INSTRUCTION_HEADER(Constant)
+ static MConstant* New(TempAllocator& alloc, const Value& v,
+ CompilerConstraintList* constraints = nullptr);
+ static MConstant* New(TempAllocator::Fallible alloc, const Value& v,
+ CompilerConstraintList* constraints = nullptr);
+ static MConstant* New(TempAllocator& alloc, const Value& v, MIRType type);
+ static MConstant* New(TempAllocator& alloc, wasm::RawF32 bits);
+ static MConstant* New(TempAllocator& alloc, wasm::RawF64 bits);
+ static MConstant* NewFloat32(TempAllocator& alloc, double d);
+ static MConstant* NewInt64(TempAllocator& alloc, int64_t i);
+ static MConstant* NewConstraintlessObject(TempAllocator& alloc, JSObject* v);
+ static MConstant* Copy(TempAllocator& alloc, MConstant* src) {
+ return new(alloc) MConstant(*src);
+ }
+
+ // Try to convert this constant to boolean, similar to js::ToBoolean.
+ // Returns false if the type is MIRType::Magic*.
+ bool MOZ_MUST_USE valueToBoolean(bool* res) const;
+
+ // Like valueToBoolean, but returns the result directly instead of using
+ // an outparam. Should not be used if this constant might be a magic value.
+ bool valueToBooleanInfallible() const {
+ bool res;
+ MOZ_ALWAYS_TRUE(valueToBoolean(&res));
+ return res;
+ }
+
+ void printOpcode(GenericPrinter& out) const override;
+
+ HashNumber valueHash() const override;
+ bool congruentTo(const MDefinition* ins) const override;
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ MOZ_MUST_USE bool updateForReplacement(MDefinition* def) override {
+ MConstant* c = def->toConstant();
+ // During constant folding, we don't want to replace a float32
+ // value by a double value.
+ if (type() == MIRType::Float32)
+ return c->type() == MIRType::Float32;
+ if (type() == MIRType::Double)
+ return c->type() != MIRType::Float32;
+ return true;
+ }
+
+ void computeRange(TempAllocator& alloc) override;
+ bool needTruncation(TruncateKind kind) override;
+ void truncate() override;
+
+ bool canProduceFloat32() const override;
+
+ ALLOW_CLONE(MConstant)
+
+ bool equals(const MConstant* other) const {
+ assertInitializedPayload();
+ return type() == other->type() && payload_.asBits == other->payload_.asBits;
+ }
+
+ bool toBoolean() const {
+ MOZ_ASSERT(type() == MIRType::Boolean);
+ return payload_.b;
+ }
+ int32_t toInt32() const {
+ MOZ_ASSERT(type() == MIRType::Int32);
+ return payload_.i32;
+ }
+ int64_t toInt64() const {
+ MOZ_ASSERT(type() == MIRType::Int64);
+ return payload_.i64;
+ }
+ bool isInt32(int32_t i) const {
+ return type() == MIRType::Int32 && payload_.i32 == i;
+ }
+ double toDouble() const {
+ MOZ_ASSERT(type() == MIRType::Double);
+ return payload_.d;
+ }
+ wasm::RawF64 toRawF64() const {
+ MOZ_ASSERT(type() == MIRType::Double);
+ return wasm::RawF64::fromBits(payload_.i64);
+ }
+ float toFloat32() const {
+ MOZ_ASSERT(type() == MIRType::Float32);
+ return payload_.f;
+ }
+ wasm::RawF32 toRawF32() const {
+ MOZ_ASSERT(type() == MIRType::Float32);
+ return wasm::RawF32::fromBits(payload_.i32);
+ }
+ JSString* toString() const {
+ MOZ_ASSERT(type() == MIRType::String);
+ return payload_.str;
+ }
+ JS::Symbol* toSymbol() const {
+ MOZ_ASSERT(type() == MIRType::Symbol);
+ return payload_.sym;
+ }
+ JSObject& toObject() const {
+ MOZ_ASSERT(type() == MIRType::Object);
+ return *payload_.obj;
+ }
+ JSObject* toObjectOrNull() const {
+ if (type() == MIRType::Object)
+ return payload_.obj;
+ MOZ_ASSERT(type() == MIRType::Null);
+ return nullptr;
+ }
+
+ bool isTypeRepresentableAsDouble() const {
+ return IsTypeRepresentableAsDouble(type());
+ }
+ double numberToDouble() const {
+ MOZ_ASSERT(isTypeRepresentableAsDouble());
+ if (type() == MIRType::Int32)
+ return toInt32();
+ if (type() == MIRType::Double)
+ return toDouble();
+ return toFloat32();
+ }
+
+ // Convert this constant to a js::Value. Float32 constants will be stored
+ // as DoubleValue and NaNs are canonicalized. Callers must be careful: not
+ // all constants can be represented by js::Value (wasm supports int64).
+ Value toJSValue() const;
+
+ bool appendRoots(MRootList& roots) const override;
+};
+
+// Generic constructor of SIMD valuesX4.
+class MSimdValueX4
+ : public MQuaternaryInstruction,
+ public Mix4Policy<SimdScalarPolicy<0>, SimdScalarPolicy<1>,
+ SimdScalarPolicy<2>, SimdScalarPolicy<3> >::Data
+{
+ protected:
+ MSimdValueX4(MIRType type, MDefinition* x, MDefinition* y, MDefinition* z, MDefinition* w)
+ : MQuaternaryInstruction(x, y, z, w)
+ {
+ MOZ_ASSERT(IsSimdType(type));
+ MOZ_ASSERT(SimdTypeToLength(type) == 4);
+
+ setMovable();
+ setResultType(type);
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdValueX4)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool canConsumeFloat32(MUse* use) const override {
+ return SimdTypeToLaneType(type()) == MIRType::Float32;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ ALLOW_CLONE(MSimdValueX4)
+};
+
+// Generic constructor of SIMD values with identical lanes.
+class MSimdSplat
+ : public MUnaryInstruction,
+ public SimdScalarPolicy<0>::Data
+{
+ protected:
+ MSimdSplat(MDefinition* v, MIRType type)
+ : MUnaryInstruction(v)
+ {
+ MOZ_ASSERT(IsSimdType(type));
+ setMovable();
+ setResultType(type);
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdSplat)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool canConsumeFloat32(MUse* use) const override {
+ return SimdTypeToLaneType(type()) == MIRType::Float32;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ ALLOW_CLONE(MSimdSplat)
+};
+
+// A constant SIMD value.
+class MSimdConstant
+ : public MNullaryInstruction
+{
+ SimdConstant value_;
+
+ protected:
+ MSimdConstant(const SimdConstant& v, MIRType type) : value_(v) {
+ MOZ_ASSERT(IsSimdType(type));
+ setMovable();
+ setResultType(type);
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdConstant)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isSimdConstant())
+ return false;
+ // Bool32x4 and Int32x4 share the same underlying SimdConstant representation.
+ if (type() != ins->type())
+ return false;
+ return value() == ins->toSimdConstant()->value();
+ }
+
+ const SimdConstant& value() const {
+ return value_;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ ALLOW_CLONE(MSimdConstant)
+};
+
+// Converts all lanes of a given vector into the type of another vector
+class MSimdConvert
+ : public MUnaryInstruction,
+ public SimdPolicy<0>::Data
+{
+ // When either fromType or toType is an integer vector, should it be treated
+ // as signed or unsigned. Note that we don't support int-int conversions -
+ // use MSimdReinterpretCast for that.
+ SimdSign sign_;
+ wasm::TrapOffset trapOffset_;
+
+ MSimdConvert(MDefinition* obj, MIRType toType, SimdSign sign, wasm::TrapOffset trapOffset)
+ : MUnaryInstruction(obj), sign_(sign), trapOffset_(trapOffset)
+ {
+ MIRType fromType = obj->type();
+ MOZ_ASSERT(IsSimdType(fromType));
+ MOZ_ASSERT(IsSimdType(toType));
+ // All conversions are int <-> float, so signedness is required.
+ MOZ_ASSERT(sign != SimdSign::NotApplicable);
+
+ setResultType(toType);
+ specialization_ = fromType; // expects fromType as input
+
+ setMovable();
+ if (IsFloatingPointSimdType(fromType) && IsIntegerSimdType(toType)) {
+ // Does the extra range check => do not remove
+ setGuard();
+ }
+ }
+
+ static MSimdConvert* New(TempAllocator& alloc, MDefinition* obj, MIRType toType, SimdSign sign,
+ wasm::TrapOffset trapOffset)
+ {
+ return new (alloc) MSimdConvert(obj, toType, sign, trapOffset);
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdConvert)
+
+ // Create a MSimdConvert instruction and add it to the basic block.
+ // Possibly create and add an equivalent sequence of instructions instead if
+ // the current target doesn't support the requested conversion directly.
+ // Return the inserted MInstruction that computes the converted value.
+ static MInstruction* AddLegalized(TempAllocator& alloc, MBasicBlock* addTo, MDefinition* obj,
+ MIRType toType, SimdSign sign,
+ wasm::TrapOffset trapOffset = wasm::TrapOffset());
+
+ SimdSign signedness() const {
+ return sign_;
+ }
+ wasm::TrapOffset trapOffset() const {
+ return trapOffset_;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!congruentIfOperandsEqual(ins))
+ return false;
+ const MSimdConvert* other = ins->toSimdConvert();
+ return sign_ == other->sign_;
+ }
+ ALLOW_CLONE(MSimdConvert)
+};
+
+// Casts bits of a vector input to another SIMD type (doesn't generate code).
+class MSimdReinterpretCast
+ : public MUnaryInstruction,
+ public SimdPolicy<0>::Data
+{
+ MSimdReinterpretCast(MDefinition* obj, MIRType toType)
+ : MUnaryInstruction(obj)
+ {
+ MIRType fromType = obj->type();
+ MOZ_ASSERT(IsSimdType(fromType));
+ MOZ_ASSERT(IsSimdType(toType));
+ setMovable();
+ setResultType(toType);
+ specialization_ = fromType; // expects fromType as input
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdReinterpretCast)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ ALLOW_CLONE(MSimdReinterpretCast)
+};
+
+// Extracts a lane element from a given vector type, given by its lane symbol.
+//
+// For integer SIMD types, a SimdSign must be provided so the lane value can be
+// converted to a scalar correctly.
+class MSimdExtractElement
+ : public MUnaryInstruction,
+ public SimdPolicy<0>::Data
+{
+ protected:
+ unsigned lane_;
+ SimdSign sign_;
+
+ MSimdExtractElement(MDefinition* obj, MIRType laneType, unsigned lane, SimdSign sign)
+ : MUnaryInstruction(obj), lane_(lane), sign_(sign)
+ {
+ MIRType vecType = obj->type();
+ MOZ_ASSERT(IsSimdType(vecType));
+ MOZ_ASSERT(lane < SimdTypeToLength(vecType));
+ MOZ_ASSERT(!IsSimdType(laneType));
+ MOZ_ASSERT((sign != SimdSign::NotApplicable) == IsIntegerSimdType(vecType),
+ "Signedness must be specified for integer SIMD extractLanes");
+ // The resulting type should match the lane type.
+ // Allow extracting boolean lanes directly into an Int32 (for wasm).
+ // Allow extracting Uint32 lanes into a double.
+ //
+ // We also allow extracting Uint32 lanes into a MIRType::Int32. This is
+ // equivalent to extracting the Uint32 lane to a double and then
+ // applying MTruncateToInt32, but it bypasses the conversion to/from
+ // double.
+ MOZ_ASSERT(SimdTypeToLaneType(vecType) == laneType ||
+ (IsBooleanSimdType(vecType) && laneType == MIRType::Int32) ||
+ (vecType == MIRType::Int32x4 && laneType == MIRType::Double &&
+ sign == SimdSign::Unsigned));
+
+ setMovable();
+ specialization_ = vecType;
+ setResultType(laneType);
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdExtractElement)
+ TRIVIAL_NEW_WRAPPERS
+
+ unsigned lane() const {
+ return lane_;
+ }
+
+ SimdSign signedness() const {
+ return sign_;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isSimdExtractElement())
+ return false;
+ const MSimdExtractElement* other = ins->toSimdExtractElement();
+ if (other->lane_ != lane_ || other->sign_ != sign_)
+ return false;
+ return congruentIfOperandsEqual(other);
+ }
+ ALLOW_CLONE(MSimdExtractElement)
+};
+
+// Replaces the datum in the given lane by a scalar value of the same type.
+class MSimdInsertElement
+ : public MBinaryInstruction,
+ public MixPolicy< SimdSameAsReturnedTypePolicy<0>, SimdScalarPolicy<1> >::Data
+{
+ private:
+ unsigned lane_;
+
+ MSimdInsertElement(MDefinition* vec, MDefinition* val, unsigned lane)
+ : MBinaryInstruction(vec, val), lane_(lane)
+ {
+ MIRType type = vec->type();
+ MOZ_ASSERT(IsSimdType(type));
+ MOZ_ASSERT(lane < SimdTypeToLength(type));
+ setMovable();
+ setResultType(type);
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdInsertElement)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, vector), (1, value))
+
+ unsigned lane() const {
+ return lane_;
+ }
+
+ bool canConsumeFloat32(MUse* use) const override {
+ return use == getUseFor(1) && SimdTypeToLaneType(type()) == MIRType::Float32;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return binaryCongruentTo(ins) && lane_ == ins->toSimdInsertElement()->lane();
+ }
+
+ void printOpcode(GenericPrinter& out) const override;
+
+ ALLOW_CLONE(MSimdInsertElement)
+};
+
+// Returns true if all lanes are true.
+class MSimdAllTrue
+ : public MUnaryInstruction,
+ public SimdPolicy<0>::Data
+{
+ protected:
+ explicit MSimdAllTrue(MDefinition* obj, MIRType result)
+ : MUnaryInstruction(obj)
+ {
+ MIRType simdType = obj->type();
+ MOZ_ASSERT(IsBooleanSimdType(simdType));
+ MOZ_ASSERT(result == MIRType::Boolean || result == MIRType::Int32);
+ setResultType(result);
+ specialization_ = simdType;
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdAllTrue)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ ALLOW_CLONE(MSimdAllTrue)
+};
+
+// Returns true if any lane is true.
+class MSimdAnyTrue
+ : public MUnaryInstruction,
+ public SimdPolicy<0>::Data
+{
+ protected:
+ explicit MSimdAnyTrue(MDefinition* obj, MIRType result)
+ : MUnaryInstruction(obj)
+ {
+ MIRType simdType = obj->type();
+ MOZ_ASSERT(IsBooleanSimdType(simdType));
+ MOZ_ASSERT(result == MIRType::Boolean || result == MIRType::Int32);
+ setResultType(result);
+ specialization_ = simdType;
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdAnyTrue)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ ALLOW_CLONE(MSimdAnyTrue)
+};
+
+// Base for the MSimdSwizzle and MSimdShuffle classes.
+class MSimdShuffleBase
+{
+ protected:
+ // As of now, there are at most 16 lanes. For each lane, we need to know
+ // which input we choose and which of the lanes we choose.
+ mozilla::Array<uint8_t, 16> lane_;
+ uint32_t arity_;
+
+ MSimdShuffleBase(const uint8_t lanes[], MIRType type)
+ {
+ arity_ = SimdTypeToLength(type);
+ for (unsigned i = 0; i < arity_; i++)
+ lane_[i] = lanes[i];
+ }
+
+ bool sameLanes(const MSimdShuffleBase* other) const {
+ return arity_ == other->arity_ &&
+ memcmp(&lane_[0], &other->lane_[0], arity_) == 0;
+ }
+
+ public:
+ unsigned numLanes() const {
+ return arity_;
+ }
+
+ unsigned lane(unsigned i) const {
+ MOZ_ASSERT(i < arity_);
+ return lane_[i];
+ }
+
+ bool lanesMatch(uint32_t x, uint32_t y, uint32_t z, uint32_t w) const {
+ return arity_ == 4 && lane(0) == x && lane(1) == y && lane(2) == z &&
+ lane(3) == w;
+ }
+};
+
+// Applies a swizzle operation to the input, putting the input lanes as
+// indicated in the output register's lanes. This implements the SIMD.js
+// "swizzle" function, that takes one vector and an array of lane indexes.
+class MSimdSwizzle
+ : public MUnaryInstruction,
+ public MSimdShuffleBase,
+ public NoTypePolicy::Data
+{
+ protected:
+ MSimdSwizzle(MDefinition* obj, const uint8_t lanes[])
+ : MUnaryInstruction(obj), MSimdShuffleBase(lanes, obj->type())
+ {
+ for (unsigned i = 0; i < arity_; i++)
+ MOZ_ASSERT(lane(i) < arity_);
+ setResultType(obj->type());
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdSwizzle)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isSimdSwizzle())
+ return false;
+ const MSimdSwizzle* other = ins->toSimdSwizzle();
+ return sameLanes(other) && congruentIfOperandsEqual(other);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ ALLOW_CLONE(MSimdSwizzle)
+};
+
+// A "general shuffle" is a swizzle or a shuffle with non-constant lane
+// indices. This is the one that Ion inlines and it can be folded into a
+// MSimdSwizzle/MSimdShuffle if lane indices are constant. Performance of
+// general swizzle/shuffle does not really matter, as we expect to get
+// constant indices most of the time.
+class MSimdGeneralShuffle :
+ public MVariadicInstruction,
+ public SimdShufflePolicy::Data
+{
+ unsigned numVectors_;
+ unsigned numLanes_;
+
+ protected:
+ MSimdGeneralShuffle(unsigned numVectors, unsigned numLanes, MIRType type)
+ : numVectors_(numVectors), numLanes_(numLanes)
+ {
+ MOZ_ASSERT(IsSimdType(type));
+ MOZ_ASSERT(SimdTypeToLength(type) == numLanes_);
+
+ setResultType(type);
+ specialization_ = type;
+ setGuard(); // throws if lane index is out of bounds
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdGeneralShuffle);
+ TRIVIAL_NEW_WRAPPERS
+
+ MOZ_MUST_USE bool init(TempAllocator& alloc) {
+ return MVariadicInstruction::init(alloc, numVectors_ + numLanes_);
+ }
+ void setVector(unsigned i, MDefinition* vec) {
+ MOZ_ASSERT(i < numVectors_);
+ initOperand(i, vec);
+ }
+ void setLane(unsigned i, MDefinition* laneIndex) {
+ MOZ_ASSERT(i < numLanes_);
+ initOperand(numVectors_ + i, laneIndex);
+ }
+
+ unsigned numVectors() const {
+ return numVectors_;
+ }
+ unsigned numLanes() const {
+ return numLanes_;
+ }
+ MDefinition* vector(unsigned i) const {
+ MOZ_ASSERT(i < numVectors_);
+ return getOperand(i);
+ }
+ MDefinition* lane(unsigned i) const {
+ MOZ_ASSERT(i < numLanes_);
+ return getOperand(numVectors_ + i);
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isSimdGeneralShuffle())
+ return false;
+ const MSimdGeneralShuffle* other = ins->toSimdGeneralShuffle();
+ return numVectors_ == other->numVectors() &&
+ numLanes_ == other->numLanes() &&
+ congruentIfOperandsEqual(other);
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+// Applies a shuffle operation to the inputs. The lane indexes select a source
+// lane from the concatenation of the two input vectors.
+class MSimdShuffle
+ : public MBinaryInstruction,
+ public MSimdShuffleBase,
+ public NoTypePolicy::Data
+{
+ MSimdShuffle(MDefinition* lhs, MDefinition* rhs, const uint8_t lanes[])
+ : MBinaryInstruction(lhs, rhs), MSimdShuffleBase(lanes, lhs->type())
+ {
+ MOZ_ASSERT(IsSimdType(lhs->type()));
+ MOZ_ASSERT(IsSimdType(rhs->type()));
+ MOZ_ASSERT(lhs->type() == rhs->type());
+ for (unsigned i = 0; i < arity_; i++)
+ MOZ_ASSERT(lane(i) < 2 * arity_);
+ setResultType(lhs->type());
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdShuffle)
+
+ static MInstruction* New(TempAllocator& alloc, MDefinition* lhs, MDefinition* rhs,
+ const uint8_t lanes[])
+ {
+ unsigned arity = SimdTypeToLength(lhs->type());
+
+ // Swap operands so that new lanes come from LHS in majority.
+ // In the balanced case, swap operands if needs be, in order to be able
+ // to do only one vshufps on x86.
+ unsigned lanesFromLHS = 0;
+ for (unsigned i = 0; i < arity; i++) {
+ if (lanes[i] < arity)
+ lanesFromLHS++;
+ }
+
+ if (lanesFromLHS < arity / 2 ||
+ (arity == 4 && lanesFromLHS == 2 && lanes[0] >= 4 && lanes[1] >= 4)) {
+ mozilla::Array<uint8_t, 16> newLanes;
+ for (unsigned i = 0; i < arity; i++)
+ newLanes[i] = (lanes[i] + arity) % (2 * arity);
+ return New(alloc, rhs, lhs, &newLanes[0]);
+ }
+
+ // If all lanes come from the same vector, just use swizzle instead.
+ if (lanesFromLHS == arity)
+ return MSimdSwizzle::New(alloc, lhs, lanes);
+
+ return new(alloc) MSimdShuffle(lhs, rhs, lanes);
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isSimdShuffle())
+ return false;
+ const MSimdShuffle* other = ins->toSimdShuffle();
+ return sameLanes(other) && binaryCongruentTo(other);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ ALLOW_CLONE(MSimdShuffle)
+};
+
+class MSimdUnaryArith
+ : public MUnaryInstruction,
+ public SimdSameAsReturnedTypePolicy<0>::Data
+{
+ public:
+ enum Operation {
+#define OP_LIST_(OP) OP,
+ FOREACH_FLOAT_SIMD_UNOP(OP_LIST_)
+ neg,
+ not_
+#undef OP_LIST_
+ };
+
+ static const char* OperationName(Operation op) {
+ switch (op) {
+ case abs: return "abs";
+ case neg: return "neg";
+ case not_: return "not";
+ case reciprocalApproximation: return "reciprocalApproximation";
+ case reciprocalSqrtApproximation: return "reciprocalSqrtApproximation";
+ case sqrt: return "sqrt";
+ }
+ MOZ_CRASH("unexpected operation");
+ }
+
+ private:
+ Operation operation_;
+
+ MSimdUnaryArith(MDefinition* def, Operation op)
+ : MUnaryInstruction(def), operation_(op)
+ {
+ MIRType type = def->type();
+ MOZ_ASSERT(IsSimdType(type));
+ MOZ_ASSERT_IF(IsIntegerSimdType(type), op == neg || op == not_);
+ setResultType(type);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdUnaryArith)
+ TRIVIAL_NEW_WRAPPERS
+
+ Operation operation() const { return operation_; }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins) && ins->toSimdUnaryArith()->operation() == operation();
+ }
+
+ void printOpcode(GenericPrinter& out) const override;
+
+ ALLOW_CLONE(MSimdUnaryArith);
+};
+
+// Compares each value of a SIMD vector to each corresponding lane's value of
+// another SIMD vector, and returns a boolean vector containing the results of
+// the comparison: all bits are set to 1 if the comparison is true, 0 otherwise.
+// When comparing integer vectors, a SimdSign must be provided to request signed
+// or unsigned comparison.
+class MSimdBinaryComp
+ : public MBinaryInstruction,
+ public SimdAllPolicy::Data
+{
+ public:
+ enum Operation {
+#define NAME_(x) x,
+ FOREACH_COMP_SIMD_OP(NAME_)
+#undef NAME_
+ };
+
+ static const char* OperationName(Operation op) {
+ switch (op) {
+#define NAME_(x) case x: return #x;
+ FOREACH_COMP_SIMD_OP(NAME_)
+#undef NAME_
+ }
+ MOZ_CRASH("unexpected operation");
+ }
+
+ private:
+ Operation operation_;
+ SimdSign sign_;
+
+ MSimdBinaryComp(MDefinition* left, MDefinition* right, Operation op, SimdSign sign)
+ : MBinaryInstruction(left, right), operation_(op), sign_(sign)
+ {
+ MOZ_ASSERT(left->type() == right->type());
+ MIRType opType = left->type();
+ MOZ_ASSERT(IsSimdType(opType));
+ MOZ_ASSERT((sign != SimdSign::NotApplicable) == IsIntegerSimdType(opType),
+ "Signedness must be specified for integer SIMD compares");
+ setResultType(MIRTypeToBooleanSimdType(opType));
+ specialization_ = opType;
+ setMovable();
+ if (op == equal || op == notEqual)
+ setCommutative();
+ }
+
+ static MSimdBinaryComp* New(TempAllocator& alloc, MDefinition* left, MDefinition* right,
+ Operation op, SimdSign sign)
+ {
+ return new (alloc) MSimdBinaryComp(left, right, op, sign);
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdBinaryComp)
+
+ // Create a MSimdBinaryComp or an equivalent sequence of instructions
+ // supported by the current target.
+ // Add all instructions to the basic block |addTo|.
+ static MInstruction* AddLegalized(TempAllocator& alloc, MBasicBlock* addTo, MDefinition* left,
+ MDefinition* right, Operation op, SimdSign sign);
+
+ AliasSet getAliasSet() const override
+ {
+ return AliasSet::None();
+ }
+
+ Operation operation() const { return operation_; }
+ SimdSign signedness() const { return sign_; }
+ MIRType specialization() const { return specialization_; }
+
+ // Swap the operands and reverse the comparison predicate.
+ void reverse() {
+ switch (operation()) {
+ case greaterThan: operation_ = lessThan; break;
+ case greaterThanOrEqual: operation_ = lessThanOrEqual; break;
+ case lessThan: operation_ = greaterThan; break;
+ case lessThanOrEqual: operation_ = greaterThanOrEqual; break;
+ case equal:
+ case notEqual:
+ break;
+ default: MOZ_CRASH("Unexpected compare operation");
+ }
+ swapOperands();
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!binaryCongruentTo(ins))
+ return false;
+ const MSimdBinaryComp* other = ins->toSimdBinaryComp();
+ return specialization_ == other->specialization() &&
+ operation_ == other->operation() &&
+ sign_ == other->signedness();
+ }
+
+ void printOpcode(GenericPrinter& out) const override;
+
+ ALLOW_CLONE(MSimdBinaryComp)
+};
+
+class MSimdBinaryArith
+ : public MBinaryInstruction,
+ public MixPolicy<SimdSameAsReturnedTypePolicy<0>, SimdSameAsReturnedTypePolicy<1> >::Data
+{
+ public:
+ enum Operation {
+#define OP_LIST_(OP) Op_##OP,
+ FOREACH_NUMERIC_SIMD_BINOP(OP_LIST_)
+ FOREACH_FLOAT_SIMD_BINOP(OP_LIST_)
+#undef OP_LIST_
+ };
+
+ static const char* OperationName(Operation op) {
+ switch (op) {
+#define OP_CASE_LIST_(OP) case Op_##OP: return #OP;
+ FOREACH_NUMERIC_SIMD_BINOP(OP_CASE_LIST_)
+ FOREACH_FLOAT_SIMD_BINOP(OP_CASE_LIST_)
+#undef OP_CASE_LIST_
+ }
+ MOZ_CRASH("unexpected operation");
+ }
+
+ private:
+ Operation operation_;
+
+ MSimdBinaryArith(MDefinition* left, MDefinition* right, Operation op)
+ : MBinaryInstruction(left, right), operation_(op)
+ {
+ MOZ_ASSERT(left->type() == right->type());
+ MIRType type = left->type();
+ MOZ_ASSERT(IsSimdType(type));
+ MOZ_ASSERT_IF(IsIntegerSimdType(type), op == Op_add || op == Op_sub || op == Op_mul);
+ setResultType(type);
+ setMovable();
+ if (op == Op_add || op == Op_mul || op == Op_min || op == Op_max)
+ setCommutative();
+ }
+
+ static MSimdBinaryArith* New(TempAllocator& alloc, MDefinition* left, MDefinition* right,
+ Operation op)
+ {
+ return new (alloc) MSimdBinaryArith(left, right, op);
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdBinaryArith)
+
+ // Create an MSimdBinaryArith instruction and add it to the basic block. Possibly
+ // create and add an equivalent sequence of instructions instead if the
+ // current target doesn't support the requested shift operation directly.
+ static MInstruction* AddLegalized(TempAllocator& alloc, MBasicBlock* addTo, MDefinition* left,
+ MDefinition* right, Operation op);
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ Operation operation() const { return operation_; }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!binaryCongruentTo(ins))
+ return false;
+ return operation_ == ins->toSimdBinaryArith()->operation();
+ }
+
+ void printOpcode(GenericPrinter& out) const override;
+
+ ALLOW_CLONE(MSimdBinaryArith)
+};
+
+class MSimdBinarySaturating
+ : public MBinaryInstruction,
+ public MixPolicy<SimdSameAsReturnedTypePolicy<0>, SimdSameAsReturnedTypePolicy<1>>::Data
+{
+ public:
+ enum Operation
+ {
+ add,
+ sub,
+ };
+
+ static const char* OperationName(Operation op)
+ {
+ switch (op) {
+ case add:
+ return "add";
+ case sub:
+ return "sub";
+ }
+ MOZ_CRASH("unexpected operation");
+ }
+
+ private:
+ Operation operation_;
+ SimdSign sign_;
+
+ MSimdBinarySaturating(MDefinition* left, MDefinition* right, Operation op, SimdSign sign)
+ : MBinaryInstruction(left, right)
+ , operation_(op)
+ , sign_(sign)
+ {
+ MOZ_ASSERT(left->type() == right->type());
+ MIRType type = left->type();
+ MOZ_ASSERT(type == MIRType::Int8x16 || type == MIRType::Int16x8);
+ setResultType(type);
+ setMovable();
+ if (op == add)
+ setCommutative();
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdBinarySaturating)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override { return AliasSet::None(); }
+
+ Operation operation() const { return operation_; }
+ SimdSign signedness() const { return sign_; }
+
+ bool congruentTo(const MDefinition* ins) const override
+ {
+ if (!binaryCongruentTo(ins))
+ return false;
+ return operation_ == ins->toSimdBinarySaturating()->operation() &&
+ sign_ == ins->toSimdBinarySaturating()->signedness();
+ }
+
+ void printOpcode(GenericPrinter& out) const override;
+
+ ALLOW_CLONE(MSimdBinarySaturating)
+};
+
+class MSimdBinaryBitwise
+ : public MBinaryInstruction,
+ public MixPolicy<SimdSameAsReturnedTypePolicy<0>, SimdSameAsReturnedTypePolicy<1> >::Data
+{
+ public:
+ enum Operation {
+ and_,
+ or_,
+ xor_
+ };
+
+ static const char* OperationName(Operation op) {
+ switch (op) {
+ case and_: return "and";
+ case or_: return "or";
+ case xor_: return "xor";
+ }
+ MOZ_CRASH("unexpected operation");
+ }
+
+ private:
+ Operation operation_;
+
+ MSimdBinaryBitwise(MDefinition* left, MDefinition* right, Operation op)
+ : MBinaryInstruction(left, right), operation_(op)
+ {
+ MOZ_ASSERT(left->type() == right->type());
+ MIRType type = left->type();
+ MOZ_ASSERT(IsSimdType(type));
+ setResultType(type);
+ setMovable();
+ setCommutative();
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdBinaryBitwise)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ Operation operation() const { return operation_; }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!binaryCongruentTo(ins))
+ return false;
+ return operation_ == ins->toSimdBinaryBitwise()->operation();
+ }
+
+ void printOpcode(GenericPrinter& out) const override;
+
+ ALLOW_CLONE(MSimdBinaryBitwise)
+};
+
+class MSimdShift
+ : public MBinaryInstruction,
+ public MixPolicy<SimdSameAsReturnedTypePolicy<0>, SimdScalarPolicy<1> >::Data
+{
+ public:
+ enum Operation {
+ lsh,
+ rsh,
+ ursh
+ };
+
+ private:
+ Operation operation_;
+
+ MSimdShift(MDefinition* left, MDefinition* right, Operation op)
+ : MBinaryInstruction(left, right), operation_(op)
+ {
+ MIRType type = left->type();
+ MOZ_ASSERT(IsIntegerSimdType(type));
+ setResultType(type);
+ setMovable();
+ }
+
+ static MSimdShift* New(TempAllocator& alloc, MDefinition* left, MDefinition* right,
+ Operation op)
+ {
+ return new (alloc) MSimdShift(left, right, op);
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdShift)
+
+ // Create an MSimdShift instruction and add it to the basic block. Possibly
+ // create and add an equivalent sequence of instructions instead if the
+ // current target doesn't support the requested shift operation directly.
+ // Return the inserted MInstruction that computes the shifted value.
+ static MInstruction* AddLegalized(TempAllocator& alloc, MBasicBlock* addTo, MDefinition* left,
+ MDefinition* right, Operation op);
+
+ // Get the relevant right shift operation given the signedness of a type.
+ static Operation rshForSign(SimdSign sign) {
+ return sign == SimdSign::Unsigned ? ursh : rsh;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ Operation operation() const { return operation_; }
+
+ static const char* OperationName(Operation op) {
+ switch (op) {
+ case lsh: return "lsh";
+ case rsh: return "rsh-arithmetic";
+ case ursh: return "rsh-logical";
+ }
+ MOZ_CRASH("unexpected operation");
+ }
+
+ void printOpcode(GenericPrinter& out) const override;
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!binaryCongruentTo(ins))
+ return false;
+ return operation_ == ins->toSimdShift()->operation();
+ }
+
+ ALLOW_CLONE(MSimdShift)
+};
+
+class MSimdSelect
+ : public MTernaryInstruction,
+ public SimdSelectPolicy::Data
+{
+ MSimdSelect(MDefinition* mask, MDefinition* lhs, MDefinition* rhs)
+ : MTernaryInstruction(mask, lhs, rhs)
+ {
+ MOZ_ASSERT(IsBooleanSimdType(mask->type()));
+ MOZ_ASSERT(lhs->type() == lhs->type());
+ MIRType type = lhs->type();
+ MOZ_ASSERT(IsSimdType(type));
+ setResultType(type);
+ specialization_ = type;
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdSelect)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, mask))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ ALLOW_CLONE(MSimdSelect)
+};
+
+// Deep clone a constant JSObject.
+class MCloneLiteral
+ : public MUnaryInstruction,
+ public ObjectPolicy<0>::Data
+{
+ protected:
+ explicit MCloneLiteral(MDefinition* obj)
+ : MUnaryInstruction(obj)
+ {
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(CloneLiteral)
+ TRIVIAL_NEW_WRAPPERS
+};
+
+class MParameter : public MNullaryInstruction
+{
+ int32_t index_;
+
+ MParameter(int32_t index, TemporaryTypeSet* types)
+ : index_(index)
+ {
+ setResultType(MIRType::Value);
+ setResultTypeSet(types);
+ }
+
+ public:
+ INSTRUCTION_HEADER(Parameter)
+ TRIVIAL_NEW_WRAPPERS
+
+ static const int32_t THIS_SLOT = -1;
+ int32_t index() const {
+ return index_;
+ }
+ void printOpcode(GenericPrinter& out) const override;
+
+ HashNumber valueHash() const override;
+ bool congruentTo(const MDefinition* ins) const override;
+};
+
+class MCallee : public MNullaryInstruction
+{
+ public:
+ MCallee()
+ {
+ setResultType(MIRType::Object);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(Callee)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MIsConstructing : public MNullaryInstruction
+{
+ public:
+ MIsConstructing() {
+ setResultType(MIRType::Boolean);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(IsConstructing)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MControlInstruction : public MInstruction
+{
+ public:
+ MControlInstruction()
+ { }
+
+ virtual size_t numSuccessors() const = 0;
+ virtual MBasicBlock* getSuccessor(size_t i) const = 0;
+ virtual void replaceSuccessor(size_t i, MBasicBlock* successor) = 0;
+
+ bool isControlInstruction() const override {
+ return true;
+ }
+
+ void printOpcode(GenericPrinter& out) const override;
+};
+
+class MTableSwitch final
+ : public MControlInstruction,
+ public NoFloatPolicy<0>::Data
+{
+ // The successors of the tableswitch
+ // - First successor = the default case
+ // - Successor 2 and higher = the cases sorted on case index.
+ Vector<MBasicBlock*, 0, JitAllocPolicy> successors_;
+ Vector<size_t, 0, JitAllocPolicy> cases_;
+
+ // Contains the blocks/cases that still need to get build
+ Vector<MBasicBlock*, 0, JitAllocPolicy> blocks_;
+
+ MUse operand_;
+ int32_t low_;
+ int32_t high_;
+
+ void initOperand(size_t index, MDefinition* operand) {
+ MOZ_ASSERT(index == 0);
+ operand_.init(operand, this);
+ }
+
+ MTableSwitch(TempAllocator& alloc, MDefinition* ins,
+ int32_t low, int32_t high)
+ : successors_(alloc),
+ cases_(alloc),
+ blocks_(alloc),
+ low_(low),
+ high_(high)
+ {
+ initOperand(0, ins);
+ }
+
+ protected:
+ MUse* getUseFor(size_t index) override {
+ MOZ_ASSERT(index == 0);
+ return &operand_;
+ }
+
+ const MUse* getUseFor(size_t index) const override {
+ MOZ_ASSERT(index == 0);
+ return &operand_;
+ }
+
+ public:
+ INSTRUCTION_HEADER(TableSwitch)
+ static MTableSwitch* New(TempAllocator& alloc, MDefinition* ins, int32_t low, int32_t high);
+
+ size_t numSuccessors() const override {
+ return successors_.length();
+ }
+
+ MOZ_MUST_USE bool addSuccessor(MBasicBlock* successor, size_t* index) {
+ MOZ_ASSERT(successors_.length() < (size_t)(high_ - low_ + 2));
+ MOZ_ASSERT(!successors_.empty());
+ *index = successors_.length();
+ return successors_.append(successor);
+ }
+
+ MBasicBlock* getSuccessor(size_t i) const override {
+ MOZ_ASSERT(i < numSuccessors());
+ return successors_[i];
+ }
+
+ void replaceSuccessor(size_t i, MBasicBlock* successor) override {
+ MOZ_ASSERT(i < numSuccessors());
+ successors_[i] = successor;
+ }
+
+ MBasicBlock** blocks() {
+ return &blocks_[0];
+ }
+
+ size_t numBlocks() const {
+ return blocks_.length();
+ }
+
+ int32_t low() const {
+ return low_;
+ }
+
+ int32_t high() const {
+ return high_;
+ }
+
+ MBasicBlock* getDefault() const {
+ return getSuccessor(0);
+ }
+
+ MBasicBlock* getCase(size_t i) const {
+ return getSuccessor(cases_[i]);
+ }
+
+ size_t numCases() const {
+ return high() - low() + 1;
+ }
+
+ MOZ_MUST_USE bool addDefault(MBasicBlock* block, size_t* index = nullptr) {
+ MOZ_ASSERT(successors_.empty());
+ if (index)
+ *index = 0;
+ return successors_.append(block);
+ }
+
+ MOZ_MUST_USE bool addCase(size_t successorIndex) {
+ return cases_.append(successorIndex);
+ }
+
+ MBasicBlock* getBlock(size_t i) const {
+ MOZ_ASSERT(i < numBlocks());
+ return blocks_[i];
+ }
+
+ MOZ_MUST_USE bool addBlock(MBasicBlock* block) {
+ return blocks_.append(block);
+ }
+
+ MDefinition* getOperand(size_t index) const override {
+ MOZ_ASSERT(index == 0);
+ return operand_.producer();
+ }
+
+ size_t numOperands() const override {
+ return 1;
+ }
+
+ size_t indexOf(const MUse* u) const final override {
+ MOZ_ASSERT(u == getUseFor(0));
+ return 0;
+ }
+
+ void replaceOperand(size_t index, MDefinition* operand) final override {
+ MOZ_ASSERT(index == 0);
+ operand_.replaceProducer(operand);
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+};
+
+template <size_t Arity, size_t Successors>
+class MAryControlInstruction : public MControlInstruction
+{
+ mozilla::Array<MUse, Arity> operands_;
+ mozilla::Array<MBasicBlock*, Successors> successors_;
+
+ protected:
+ void setSuccessor(size_t index, MBasicBlock* successor) {
+ successors_[index] = successor;
+ }
+
+ MUse* getUseFor(size_t index) final override {
+ return &operands_[index];
+ }
+ const MUse* getUseFor(size_t index) const final override {
+ return &operands_[index];
+ }
+ void initOperand(size_t index, MDefinition* operand) {
+ operands_[index].init(operand, this);
+ }
+
+ public:
+ MDefinition* getOperand(size_t index) const final override {
+ return operands_[index].producer();
+ }
+ size_t numOperands() const final override {
+ return Arity;
+ }
+ size_t indexOf(const MUse* u) const final override {
+ MOZ_ASSERT(u >= &operands_[0]);
+ MOZ_ASSERT(u <= &operands_[numOperands() - 1]);
+ return u - &operands_[0];
+ }
+ void replaceOperand(size_t index, MDefinition* operand) final override {
+ operands_[index].replaceProducer(operand);
+ }
+ size_t numSuccessors() const final override {
+ return Successors;
+ }
+ MBasicBlock* getSuccessor(size_t i) const final override {
+ return successors_[i];
+ }
+ void replaceSuccessor(size_t i, MBasicBlock* succ) final override {
+ successors_[i] = succ;
+ }
+};
+
+// Jump to the start of another basic block.
+class MGoto
+ : public MAryControlInstruction<0, 1>,
+ public NoTypePolicy::Data
+{
+ explicit MGoto(MBasicBlock* target) {
+ setSuccessor(0, target);
+ }
+
+ public:
+ INSTRUCTION_HEADER(Goto)
+ static MGoto* New(TempAllocator& alloc, MBasicBlock* target);
+ static MGoto* New(TempAllocator::Fallible alloc, MBasicBlock* target);
+
+ // Variant that may patch the target later.
+ static MGoto* New(TempAllocator& alloc);
+
+ static const size_t TargetIndex = 0;
+
+ MBasicBlock* target() {
+ return getSuccessor(0);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+enum BranchDirection {
+ FALSE_BRANCH,
+ TRUE_BRANCH
+};
+
+static inline BranchDirection
+NegateBranchDirection(BranchDirection dir)
+{
+ return (dir == FALSE_BRANCH) ? TRUE_BRANCH : FALSE_BRANCH;
+}
+
+// Tests if the input instruction evaluates to true or false, and jumps to the
+// start of a corresponding basic block.
+class MTest
+ : public MAryControlInstruction<1, 2>,
+ public TestPolicy::Data
+{
+ bool operandMightEmulateUndefined_;
+
+ MTest(MDefinition* ins, MBasicBlock* trueBranch, MBasicBlock* falseBranch)
+ : operandMightEmulateUndefined_(true)
+ {
+ initOperand(0, ins);
+ setSuccessor(0, trueBranch);
+ setSuccessor(1, falseBranch);
+ }
+
+ // Variant which may patch the ifTrue branch later.
+ MTest(MDefinition* ins, MBasicBlock* falseBranch)
+ : MTest(ins, nullptr, falseBranch)
+ {}
+
+ public:
+ INSTRUCTION_HEADER(Test)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, input))
+
+ static const size_t TrueBranchIndex = 0;
+
+ MBasicBlock* ifTrue() const {
+ return getSuccessor(0);
+ }
+ MBasicBlock* ifFalse() const {
+ return getSuccessor(1);
+ }
+ MBasicBlock* branchSuccessor(BranchDirection dir) const {
+ return (dir == TRUE_BRANCH) ? ifTrue() : ifFalse();
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ // We cache whether our operand might emulate undefined, but we don't want
+ // to do that from New() or the constructor, since those can be called on
+ // background threads. So make callers explicitly call it if they want us
+ // to check whether the operand might do this. If this method is never
+ // called, we'll assume our operand can emulate undefined.
+ void cacheOperandMightEmulateUndefined(CompilerConstraintList* constraints);
+ MDefinition* foldsDoubleNegation(TempAllocator& alloc);
+ MDefinition* foldsConstant(TempAllocator& alloc);
+ MDefinition* foldsTypes(TempAllocator& alloc);
+ MDefinition* foldsNeedlessControlFlow(TempAllocator& alloc);
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ void filtersUndefinedOrNull(bool trueBranch, MDefinition** subject, bool* filtersUndefined,
+ bool* filtersNull);
+
+ void markNoOperandEmulatesUndefined() {
+ operandMightEmulateUndefined_ = false;
+ }
+ bool operandMightEmulateUndefined() const {
+ return operandMightEmulateUndefined_;
+ }
+#ifdef DEBUG
+ bool isConsistentFloat32Use(MUse* use) const override {
+ return true;
+ }
+#endif
+};
+
+// Equivalent to MTest(true, successor, fake), except without the foldsTo
+// method. This allows IonBuilder to insert fake CFG edges to magically protect
+// control flow for try-catch blocks.
+class MGotoWithFake
+ : public MAryControlInstruction<0, 2>,
+ public NoTypePolicy::Data
+{
+ MGotoWithFake(MBasicBlock* successor, MBasicBlock* fake)
+ {
+ setSuccessor(0, successor);
+ setSuccessor(1, fake);
+ }
+
+ public:
+ INSTRUCTION_HEADER(GotoWithFake)
+ TRIVIAL_NEW_WRAPPERS
+
+ MBasicBlock* target() const {
+ return getSuccessor(0);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+// Returns from this function to the previous caller.
+class MReturn
+ : public MAryControlInstruction<1, 0>,
+ public BoxInputsPolicy::Data
+{
+ explicit MReturn(MDefinition* ins) {
+ initOperand(0, ins);
+ }
+
+ public:
+ INSTRUCTION_HEADER(Return)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, input))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MThrow
+ : public MAryControlInstruction<1, 0>,
+ public BoxInputsPolicy::Data
+{
+ explicit MThrow(MDefinition* ins) {
+ initOperand(0, ins);
+ }
+
+ public:
+ INSTRUCTION_HEADER(Throw)
+ TRIVIAL_NEW_WRAPPERS
+
+ virtual AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+// Fabricate a type set containing only the type of the specified object.
+TemporaryTypeSet*
+MakeSingletonTypeSet(CompilerConstraintList* constraints, JSObject* obj);
+
+TemporaryTypeSet*
+MakeSingletonTypeSet(CompilerConstraintList* constraints, ObjectGroup* obj);
+
+MOZ_MUST_USE bool
+MergeTypes(TempAllocator& alloc, MIRType* ptype, TemporaryTypeSet** ptypeSet,
+ MIRType newType, TemporaryTypeSet* newTypeSet);
+
+bool
+TypeSetIncludes(TypeSet* types, MIRType input, TypeSet* inputTypes);
+
+bool
+EqualTypes(MIRType type1, TemporaryTypeSet* typeset1,
+ MIRType type2, TemporaryTypeSet* typeset2);
+
+bool
+CanStoreUnboxedType(TempAllocator& alloc,
+ JSValueType unboxedType, MIRType input, TypeSet* inputTypes);
+
+class MNewArray
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ private:
+ // Number of elements to allocate for the array.
+ uint32_t length_;
+
+ // Heap where the array should be allocated.
+ gc::InitialHeap initialHeap_;
+
+ // Whether values written to this array should be converted to double first.
+ bool convertDoubleElements_;
+
+ jsbytecode* pc_;
+
+ bool vmCall_;
+
+ MNewArray(CompilerConstraintList* constraints, uint32_t length, MConstant* templateConst,
+ gc::InitialHeap initialHeap, jsbytecode* pc, bool vmCall = false);
+
+ public:
+ INSTRUCTION_HEADER(NewArray)
+ TRIVIAL_NEW_WRAPPERS
+
+ static MNewArray* NewVM(TempAllocator& alloc, CompilerConstraintList* constraints,
+ uint32_t length, MConstant* templateConst,
+ gc::InitialHeap initialHeap, jsbytecode* pc)
+ {
+ return new(alloc) MNewArray(constraints, length, templateConst, initialHeap, pc, true);
+ }
+
+ uint32_t length() const {
+ return length_;
+ }
+
+ JSObject* templateObject() const {
+ return getOperand(0)->toConstant()->toObjectOrNull();
+ }
+
+ gc::InitialHeap initialHeap() const {
+ return initialHeap_;
+ }
+
+ jsbytecode* pc() const {
+ return pc_;
+ }
+
+ bool isVMCall() const {
+ return vmCall_;
+ }
+
+ bool convertDoubleElements() const {
+ return convertDoubleElements_;
+ }
+
+ // NewArray is marked as non-effectful because all our allocations are
+ // either lazy when we are using "new Array(length)" or bounded by the
+ // script or the stack size when we are using "new Array(...)" or "[...]"
+ // notations. So we might have to allocate the array twice if we bail
+ // during the computation of the first element of the square braket
+ // notation.
+ virtual AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ // The template object can safely be used in the recover instruction
+ // because it can never be mutated by any other function execution.
+ return templateObject() != nullptr;
+ }
+};
+
+class MNewArrayCopyOnWrite : public MNullaryInstruction
+{
+ CompilerGCPointer<ArrayObject*> templateObject_;
+ gc::InitialHeap initialHeap_;
+
+ MNewArrayCopyOnWrite(CompilerConstraintList* constraints, ArrayObject* templateObject,
+ gc::InitialHeap initialHeap)
+ : templateObject_(templateObject),
+ initialHeap_(initialHeap)
+ {
+ MOZ_ASSERT(!templateObject->isSingleton());
+ setResultType(MIRType::Object);
+ setResultTypeSet(MakeSingletonTypeSet(constraints, templateObject));
+ }
+
+ public:
+ INSTRUCTION_HEADER(NewArrayCopyOnWrite)
+ TRIVIAL_NEW_WRAPPERS
+
+ ArrayObject* templateObject() const {
+ return templateObject_;
+ }
+
+ gc::InitialHeap initialHeap() const {
+ return initialHeap_;
+ }
+
+ virtual AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(templateObject_);
+ }
+};
+
+class MNewArrayDynamicLength
+ : public MUnaryInstruction,
+ public IntPolicy<0>::Data
+{
+ CompilerObject templateObject_;
+ gc::InitialHeap initialHeap_;
+
+ MNewArrayDynamicLength(CompilerConstraintList* constraints, JSObject* templateObject,
+ gc::InitialHeap initialHeap, MDefinition* length)
+ : MUnaryInstruction(length),
+ templateObject_(templateObject),
+ initialHeap_(initialHeap)
+ {
+ setGuard(); // Need to throw if length is negative.
+ setResultType(MIRType::Object);
+ if (!templateObject->isSingleton())
+ setResultTypeSet(MakeSingletonTypeSet(constraints, templateObject));
+ }
+
+ public:
+ INSTRUCTION_HEADER(NewArrayDynamicLength)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, length))
+
+ JSObject* templateObject() const {
+ return templateObject_;
+ }
+ gc::InitialHeap initialHeap() const {
+ return initialHeap_;
+ }
+
+ virtual AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(templateObject_);
+ }
+};
+
+class MNewTypedArray
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ gc::InitialHeap initialHeap_;
+
+ MNewTypedArray(CompilerConstraintList* constraints, MConstant* templateConst,
+ gc::InitialHeap initialHeap)
+ : MUnaryInstruction(templateConst),
+ initialHeap_(initialHeap)
+ {
+ MOZ_ASSERT(!templateObject()->isSingleton());
+ setResultType(MIRType::Object);
+ setResultTypeSet(MakeSingletonTypeSet(constraints, templateObject()));
+ }
+
+ public:
+ INSTRUCTION_HEADER(NewTypedArray)
+ TRIVIAL_NEW_WRAPPERS
+
+ TypedArrayObject* templateObject() const {
+ return &getOperand(0)->toConstant()->toObject().as<TypedArrayObject>();
+ }
+
+ gc::InitialHeap initialHeap() const {
+ return initialHeap_;
+ }
+
+ virtual AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+};
+
+class MNewTypedArrayDynamicLength
+ : public MUnaryInstruction,
+ public IntPolicy<0>::Data
+{
+ CompilerObject templateObject_;
+ gc::InitialHeap initialHeap_;
+
+ MNewTypedArrayDynamicLength(CompilerConstraintList* constraints, JSObject* templateObject,
+ gc::InitialHeap initialHeap, MDefinition* length)
+ : MUnaryInstruction(length),
+ templateObject_(templateObject),
+ initialHeap_(initialHeap)
+ {
+ setGuard(); // Need to throw if length is negative.
+ setResultType(MIRType::Object);
+ if (!templateObject->isSingleton())
+ setResultTypeSet(MakeSingletonTypeSet(constraints, templateObject));
+ }
+
+ public:
+ INSTRUCTION_HEADER(NewTypedArrayDynamicLength)
+
+ static MNewTypedArrayDynamicLength* New(TempAllocator& alloc, CompilerConstraintList* constraints,
+ JSObject* templateObject, gc::InitialHeap initialHeap,
+ MDefinition* length)
+ {
+ return new(alloc) MNewTypedArrayDynamicLength(constraints, templateObject, initialHeap, length);
+ }
+
+ MDefinition* length() const {
+ return getOperand(0);
+ }
+ JSObject* templateObject() const {
+ return templateObject_;
+ }
+ gc::InitialHeap initialHeap() const {
+ return initialHeap_;
+ }
+
+ virtual AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(templateObject_);
+ }
+};
+
+class MNewObject
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ public:
+ enum Mode { ObjectLiteral, ObjectCreate };
+
+ private:
+ gc::InitialHeap initialHeap_;
+ Mode mode_;
+ bool vmCall_;
+
+ MNewObject(CompilerConstraintList* constraints, MConstant* templateConst,
+ gc::InitialHeap initialHeap, Mode mode, bool vmCall = false)
+ : MUnaryInstruction(templateConst),
+ initialHeap_(initialHeap),
+ mode_(mode),
+ vmCall_(vmCall)
+ {
+ MOZ_ASSERT_IF(mode != ObjectLiteral, templateObject());
+ setResultType(MIRType::Object);
+
+ if (JSObject* obj = templateObject())
+ setResultTypeSet(MakeSingletonTypeSet(constraints, obj));
+
+ // The constant is kept separated in a MConstant, this way we can safely
+ // mark it during GC if we recover the object allocation. Otherwise, by
+ // making it emittedAtUses, we do not produce register allocations for
+ // it and inline its content inside the code produced by the
+ // CodeGenerator.
+ if (templateConst->toConstant()->type() == MIRType::Object)
+ templateConst->setEmittedAtUses();
+ }
+
+ public:
+ INSTRUCTION_HEADER(NewObject)
+ TRIVIAL_NEW_WRAPPERS
+
+ static MNewObject* NewVM(TempAllocator& alloc, CompilerConstraintList* constraints,
+ MConstant* templateConst, gc::InitialHeap initialHeap,
+ Mode mode)
+ {
+ return new(alloc) MNewObject(constraints, templateConst, initialHeap, mode, true);
+ }
+
+ Mode mode() const {
+ return mode_;
+ }
+
+ JSObject* templateObject() const {
+ return getOperand(0)->toConstant()->toObjectOrNull();
+ }
+
+ gc::InitialHeap initialHeap() const {
+ return initialHeap_;
+ }
+
+ bool isVMCall() const {
+ return vmCall_;
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ // The template object can safely be used in the recover instruction
+ // because it can never be mutated by any other function execution.
+ return templateObject() != nullptr;
+ }
+};
+
+class MNewTypedObject : public MNullaryInstruction
+{
+ CompilerGCPointer<InlineTypedObject*> templateObject_;
+ gc::InitialHeap initialHeap_;
+
+ MNewTypedObject(CompilerConstraintList* constraints,
+ InlineTypedObject* templateObject,
+ gc::InitialHeap initialHeap)
+ : templateObject_(templateObject),
+ initialHeap_(initialHeap)
+ {
+ setResultType(MIRType::Object);
+ setResultTypeSet(MakeSingletonTypeSet(constraints, templateObject));
+ }
+
+ public:
+ INSTRUCTION_HEADER(NewTypedObject)
+ TRIVIAL_NEW_WRAPPERS
+
+ InlineTypedObject* templateObject() const {
+ return templateObject_;
+ }
+
+ gc::InitialHeap initialHeap() const {
+ return initialHeap_;
+ }
+
+ virtual AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(templateObject_);
+ }
+};
+
+class MTypedObjectDescr
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ private:
+ explicit MTypedObjectDescr(MDefinition* object)
+ : MUnaryInstruction(object)
+ {
+ setResultType(MIRType::Object);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(TypedObjectDescr)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+};
+
+// Generic way for constructing a SIMD object in IonMonkey, this instruction
+// takes as argument a SIMD instruction and returns a new SIMD object which
+// corresponds to the MIRType of its operand.
+class MSimdBox
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ protected:
+ CompilerGCPointer<InlineTypedObject*> templateObject_;
+ SimdType simdType_;
+ gc::InitialHeap initialHeap_;
+
+ MSimdBox(CompilerConstraintList* constraints,
+ MDefinition* op,
+ InlineTypedObject* templateObject,
+ SimdType simdType,
+ gc::InitialHeap initialHeap)
+ : MUnaryInstruction(op),
+ templateObject_(templateObject),
+ simdType_(simdType),
+ initialHeap_(initialHeap)
+ {
+ MOZ_ASSERT(IsSimdType(op->type()));
+ setMovable();
+ setResultType(MIRType::Object);
+ if (constraints)
+ setResultTypeSet(MakeSingletonTypeSet(constraints, templateObject));
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdBox)
+ TRIVIAL_NEW_WRAPPERS
+
+ InlineTypedObject* templateObject() const {
+ return templateObject_;
+ }
+
+ SimdType simdType() const {
+ return simdType_;
+ }
+
+ gc::InitialHeap initialHeap() const {
+ return initialHeap_;
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!congruentIfOperandsEqual(ins))
+ return false;
+ const MSimdBox* box = ins->toSimdBox();
+ if (box->simdType() != simdType())
+ return false;
+ MOZ_ASSERT(box->templateObject() == templateObject());
+ if (box->initialHeap() != initialHeap())
+ return false;
+ return true;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ void printOpcode(GenericPrinter& out) const override;
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(templateObject_);
+ }
+};
+
+class MSimdUnbox
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ protected:
+ SimdType simdType_;
+
+ MSimdUnbox(MDefinition* op, SimdType simdType)
+ : MUnaryInstruction(op),
+ simdType_(simdType)
+ {
+ MIRType type = SimdTypeToMIRType(simdType);
+ MOZ_ASSERT(IsSimdType(type));
+ setGuard();
+ setMovable();
+ setResultType(type);
+ }
+
+ public:
+ INSTRUCTION_HEADER(SimdUnbox)
+ TRIVIAL_NEW_WRAPPERS
+ ALLOW_CLONE(MSimdUnbox)
+
+ SimdType simdType() const { return simdType_; }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!congruentIfOperandsEqual(ins))
+ return false;
+ return ins->toSimdUnbox()->simdType() == simdType();
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ void printOpcode(GenericPrinter& out) const override;
+};
+
+// Creates a new derived type object. At runtime, this is just a call
+// to `BinaryBlock::createDerived()`. That is, the MIR itself does not
+// compile to particularly optimized code. However, using a distinct
+// MIR for creating derived type objects allows the compiler to
+// optimize ephemeral typed objects as would be created for a
+// reference like `a.b.c` -- here, the `a.b` will create an ephemeral
+// derived type object that aliases the memory of `a` itself. The
+// specific nature of `a.b` is revealed by using
+// `MNewDerivedTypedObject` rather than `MGetProperty` or what have
+// you. Moreover, the compiler knows that there are no side-effects,
+// so `MNewDerivedTypedObject` instructions can be reordered or pruned
+// as dead code.
+class MNewDerivedTypedObject
+ : public MTernaryInstruction,
+ public Mix3Policy<ObjectPolicy<0>,
+ ObjectPolicy<1>,
+ IntPolicy<2> >::Data
+{
+ private:
+ TypedObjectPrediction prediction_;
+
+ MNewDerivedTypedObject(TypedObjectPrediction prediction,
+ MDefinition* type,
+ MDefinition* owner,
+ MDefinition* offset)
+ : MTernaryInstruction(type, owner, offset),
+ prediction_(prediction)
+ {
+ setMovable();
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(NewDerivedTypedObject)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, type), (1, owner), (2, offset))
+
+ TypedObjectPrediction prediction() const {
+ return prediction_;
+ }
+
+ virtual AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+};
+
+// This vector is used when the recovered object is kept unboxed. We map the
+// offset of each property to the index of the corresponding operands in the
+// object state.
+struct OperandIndexMap : public TempObject
+{
+ // The number of properties is limited by scalar replacement. Thus we cannot
+ // have any large number of properties.
+ FixedList<uint8_t> map;
+
+ MOZ_MUST_USE bool init(TempAllocator& alloc, JSObject* templateObject);
+};
+
+// Represent the content of all slots of an object. This instruction is not
+// lowered and is not used to generate code.
+class MObjectState
+ : public MVariadicInstruction,
+ public NoFloatPolicyAfter<1>::Data
+{
+ private:
+ uint32_t numSlots_;
+ uint32_t numFixedSlots_; // valid if isUnboxed() == false.
+ OperandIndexMap* operandIndex_; // valid if isUnboxed() == true.
+
+ bool isUnboxed() const {
+ return operandIndex_ != nullptr;
+ }
+
+ MObjectState(JSObject *templateObject, OperandIndexMap* operandIndex);
+ explicit MObjectState(MObjectState* state);
+
+ MOZ_MUST_USE bool init(TempAllocator& alloc, MDefinition* obj);
+
+ void initSlot(uint32_t slot, MDefinition* def) {
+ initOperand(slot + 1, def);
+ }
+
+ public:
+ INSTRUCTION_HEADER(ObjectState)
+ NAMED_OPERANDS((0, object))
+
+ // Return the template object of any object creation which can be recovered
+ // on bailout.
+ static JSObject* templateObjectOf(MDefinition* obj);
+
+ static MObjectState* New(TempAllocator& alloc, MDefinition* obj);
+ static MObjectState* Copy(TempAllocator& alloc, MObjectState* state);
+
+ // As we might do read of uninitialized properties, we have to copy the
+ // initial values from the template object.
+ MOZ_MUST_USE bool initFromTemplateObject(TempAllocator& alloc, MDefinition* undefinedVal);
+
+ size_t numFixedSlots() const {
+ MOZ_ASSERT(!isUnboxed());
+ return numFixedSlots_;
+ }
+ size_t numSlots() const {
+ return numSlots_;
+ }
+
+ MDefinition* getSlot(uint32_t slot) const {
+ return getOperand(slot + 1);
+ }
+ void setSlot(uint32_t slot, MDefinition* def) {
+ replaceOperand(slot + 1, def);
+ }
+
+ bool hasFixedSlot(uint32_t slot) const {
+ return slot < numSlots() && slot < numFixedSlots();
+ }
+ MDefinition* getFixedSlot(uint32_t slot) const {
+ MOZ_ASSERT(slot < numFixedSlots());
+ return getSlot(slot);
+ }
+ void setFixedSlot(uint32_t slot, MDefinition* def) {
+ MOZ_ASSERT(slot < numFixedSlots());
+ setSlot(slot, def);
+ }
+
+ bool hasDynamicSlot(uint32_t slot) const {
+ return numFixedSlots() < numSlots() && slot < numSlots() - numFixedSlots();
+ }
+ MDefinition* getDynamicSlot(uint32_t slot) const {
+ return getSlot(slot + numFixedSlots());
+ }
+ void setDynamicSlot(uint32_t slot, MDefinition* def) {
+ setSlot(slot + numFixedSlots(), def);
+ }
+
+ // Interface reserved for unboxed objects.
+ bool hasOffset(uint32_t offset) const {
+ MOZ_ASSERT(isUnboxed());
+ return offset < operandIndex_->map.length() && operandIndex_->map[offset] != 0;
+ }
+ MDefinition* getOffset(uint32_t offset) const {
+ return getOperand(operandIndex_->map[offset]);
+ }
+ void setOffset(uint32_t offset, MDefinition* def) {
+ replaceOperand(operandIndex_->map[offset], def);
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+};
+
+// Represent the contents of all elements of an array. This instruction is not
+// lowered and is not used to generate code.
+class MArrayState
+ : public MVariadicInstruction,
+ public NoFloatPolicyAfter<2>::Data
+{
+ private:
+ uint32_t numElements_;
+
+ explicit MArrayState(MDefinition* arr);
+
+ MOZ_MUST_USE bool init(TempAllocator& alloc, MDefinition* obj, MDefinition* len);
+
+ void initElement(uint32_t index, MDefinition* def) {
+ initOperand(index + 2, def);
+ }
+
+ public:
+ INSTRUCTION_HEADER(ArrayState)
+ NAMED_OPERANDS((0, array), (1, initializedLength))
+
+ static MArrayState* New(TempAllocator& alloc, MDefinition* arr, MDefinition* undefinedVal,
+ MDefinition* initLength);
+ static MArrayState* Copy(TempAllocator& alloc, MArrayState* state);
+
+ void setInitializedLength(MDefinition* def) {
+ replaceOperand(1, def);
+ }
+
+
+ size_t numElements() const {
+ return numElements_;
+ }
+
+ MDefinition* getElement(uint32_t index) const {
+ return getOperand(index + 2);
+ }
+ void setElement(uint32_t index, MDefinition* def) {
+ replaceOperand(index + 2, def);
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+};
+
+// Setting __proto__ in an object literal.
+class MMutateProto
+ : public MAryInstruction<2>,
+ public MixPolicy<ObjectPolicy<0>, BoxPolicy<1> >::Data
+{
+ protected:
+ MMutateProto(MDefinition* obj, MDefinition* value)
+ {
+ initOperand(0, obj);
+ initOperand(1, value);
+ setResultType(MIRType::None);
+ }
+
+ public:
+ INSTRUCTION_HEADER(MutateProto)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, getObject), (1, getValue))
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+// Slow path for adding a property to an object without a known base.
+class MInitProp
+ : public MAryInstruction<2>,
+ public MixPolicy<ObjectPolicy<0>, BoxPolicy<1> >::Data
+{
+ CompilerPropertyName name_;
+
+ protected:
+ MInitProp(MDefinition* obj, PropertyName* name, MDefinition* value)
+ : name_(name)
+ {
+ initOperand(0, obj);
+ initOperand(1, value);
+ setResultType(MIRType::None);
+ }
+
+ public:
+ INSTRUCTION_HEADER(InitProp)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, getObject), (1, getValue))
+
+ PropertyName* propertyName() const {
+ return name_;
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(name_);
+ }
+};
+
+class MInitPropGetterSetter
+ : public MBinaryInstruction,
+ public MixPolicy<ObjectPolicy<0>, ObjectPolicy<1> >::Data
+{
+ CompilerPropertyName name_;
+
+ MInitPropGetterSetter(MDefinition* obj, PropertyName* name, MDefinition* value)
+ : MBinaryInstruction(obj, value),
+ name_(name)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(InitPropGetterSetter)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, value))
+
+ PropertyName* name() const {
+ return name_;
+ }
+
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(name_);
+ }
+};
+
+class MInitElem
+ : public MAryInstruction<3>,
+ public Mix3Policy<ObjectPolicy<0>, BoxPolicy<1>, BoxPolicy<2> >::Data
+{
+ MInitElem(MDefinition* obj, MDefinition* id, MDefinition* value)
+ {
+ initOperand(0, obj);
+ initOperand(1, id);
+ initOperand(2, value);
+ setResultType(MIRType::None);
+ }
+
+ public:
+ INSTRUCTION_HEADER(InitElem)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, getObject), (1, getId), (2, getValue))
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+class MInitElemGetterSetter
+ : public MTernaryInstruction,
+ public Mix3Policy<ObjectPolicy<0>, BoxPolicy<1>, ObjectPolicy<2> >::Data
+{
+ MInitElemGetterSetter(MDefinition* obj, MDefinition* id, MDefinition* value)
+ : MTernaryInstruction(obj, id, value)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(InitElemGetterSetter)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, idValue), (2, value))
+
+};
+
+// WrappedFunction wraps a JSFunction so it can safely be used off-thread.
+// In particular, a function's flags can be modified on the main thread as
+// functions are relazified and delazified, so we must be careful not to access
+// these flags off-thread.
+class WrappedFunction : public TempObject
+{
+ CompilerFunction fun_;
+ uint16_t nargs_;
+ bool isNative_ : 1;
+ bool isConstructor_ : 1;
+ bool isClassConstructor_ : 1;
+ bool isSelfHostedBuiltin_ : 1;
+
+ public:
+ explicit WrappedFunction(JSFunction* fun);
+ size_t nargs() const { return nargs_; }
+ bool isNative() const { return isNative_; }
+ bool isConstructor() const { return isConstructor_; }
+ bool isClassConstructor() const { return isClassConstructor_; }
+ bool isSelfHostedBuiltin() const { return isSelfHostedBuiltin_; }
+
+ // fun->native() and fun->jitInfo() can safely be called off-thread: these
+ // fields never change.
+ JSNative native() const { return fun_->native(); }
+ const JSJitInfo* jitInfo() const { return fun_->jitInfo(); }
+
+ JSFunction* rawJSFunction() const { return fun_; }
+
+ bool appendRoots(MRootList& roots) const {
+ return roots.append(fun_);
+ }
+};
+
+class MCall
+ : public MVariadicInstruction,
+ public CallPolicy::Data
+{
+ private:
+ // An MCall uses the MPrepareCall, MDefinition for the function, and
+ // MPassArg instructions. They are stored in the same list.
+ static const size_t FunctionOperandIndex = 0;
+ static const size_t NumNonArgumentOperands = 1;
+
+ protected:
+ // Monomorphic cache of single target from TI, or nullptr.
+ WrappedFunction* target_;
+
+ // Original value of argc from the bytecode.
+ uint32_t numActualArgs_;
+
+ // True if the call is for JSOP_NEW.
+ bool construct_;
+
+ bool needsArgCheck_;
+
+ MCall(WrappedFunction* target, uint32_t numActualArgs, bool construct)
+ : target_(target),
+ numActualArgs_(numActualArgs),
+ construct_(construct),
+ needsArgCheck_(true)
+ {
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(Call)
+ static MCall* New(TempAllocator& alloc, JSFunction* target, size_t maxArgc, size_t numActualArgs,
+ bool construct, bool isDOMCall);
+
+ void initFunction(MDefinition* func) {
+ initOperand(FunctionOperandIndex, func);
+ }
+
+ bool needsArgCheck() const {
+ return needsArgCheck_;
+ }
+
+ void disableArgCheck() {
+ needsArgCheck_ = false;
+ }
+ MDefinition* getFunction() const {
+ return getOperand(FunctionOperandIndex);
+ }
+ void replaceFunction(MInstruction* newfunc) {
+ replaceOperand(FunctionOperandIndex, newfunc);
+ }
+
+ void addArg(size_t argnum, MDefinition* arg);
+
+ MDefinition* getArg(uint32_t index) const {
+ return getOperand(NumNonArgumentOperands + index);
+ }
+
+ static size_t IndexOfThis() {
+ return NumNonArgumentOperands;
+ }
+ static size_t IndexOfArgument(size_t index) {
+ return NumNonArgumentOperands + index + 1; // +1 to skip |this|.
+ }
+ static size_t IndexOfStackArg(size_t index) {
+ return NumNonArgumentOperands + index;
+ }
+
+ // For TI-informed monomorphic callsites.
+ WrappedFunction* getSingleTarget() const {
+ return target_;
+ }
+
+ bool isConstructing() const {
+ return construct_;
+ }
+
+ // The number of stack arguments is the max between the number of formal
+ // arguments and the number of actual arguments. The number of stack
+ // argument includes the |undefined| padding added in case of underflow.
+ // Includes |this|.
+ uint32_t numStackArgs() const {
+ return numOperands() - NumNonArgumentOperands;
+ }
+
+ // Does not include |this|.
+ uint32_t numActualArgs() const {
+ return numActualArgs_;
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+
+ virtual bool isCallDOMNative() const {
+ return false;
+ }
+
+ // A method that can be called to tell the MCall to figure out whether it's
+ // movable or not. This can't be done in the constructor, because it
+ // depends on the arguments to the call, and those aren't passed to the
+ // constructor but are set up later via addArg.
+ virtual void computeMovable() {
+ }
+
+ bool appendRoots(MRootList& roots) const override {
+ if (target_)
+ return target_->appendRoots(roots);
+ return true;
+ }
+};
+
+class MCallDOMNative : public MCall
+{
+ // A helper class for MCalls for DOM natives. Note that this is NOT
+ // actually a separate MIR op from MCall, because all sorts of places use
+ // isCall() to check for calls and all we really want is to overload a few
+ // virtual things from MCall.
+ protected:
+ MCallDOMNative(WrappedFunction* target, uint32_t numActualArgs)
+ : MCall(target, numActualArgs, false)
+ {
+ MOZ_ASSERT(getJitInfo()->type() != JSJitInfo::InlinableNative);
+
+ // If our jitinfo is not marked eliminatable, that means that our C++
+ // implementation is fallible or that it never wants to be eliminated or
+ // that we have no hope of ever doing the sort of argument analysis that
+ // would allow us to detemine that we're side-effect-free. In the
+ // latter case we wouldn't get DCEd no matter what, but for the former
+ // two cases we have to explicitly say that we can't be DCEd.
+ if (!getJitInfo()->isEliminatable)
+ setGuard();
+ }
+
+ friend MCall* MCall::New(TempAllocator& alloc, JSFunction* target, size_t maxArgc,
+ size_t numActualArgs, bool construct, bool isDOMCall);
+
+ const JSJitInfo* getJitInfo() const;
+ public:
+ virtual AliasSet getAliasSet() const override;
+
+ virtual bool congruentTo(const MDefinition* ins) const override;
+
+ virtual bool isCallDOMNative() const override {
+ return true;
+ }
+
+ virtual void computeMovable() override;
+};
+
+// arr.splice(start, deleteCount) with unused return value.
+class MArraySplice
+ : public MTernaryInstruction,
+ public Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, IntPolicy<2> >::Data
+{
+ private:
+
+ MArraySplice(MDefinition* object, MDefinition* start, MDefinition* deleteCount)
+ : MTernaryInstruction(object, start, deleteCount)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(ArraySplice)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, start), (2, deleteCount))
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+// fun.apply(self, arguments)
+class MApplyArgs
+ : public MAryInstruction<3>,
+ public Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, BoxPolicy<2> >::Data
+{
+ protected:
+ // Monomorphic cache of single target from TI, or nullptr.
+ WrappedFunction* target_;
+
+ MApplyArgs(WrappedFunction* target, MDefinition* fun, MDefinition* argc, MDefinition* self)
+ : target_(target)
+ {
+ initOperand(0, fun);
+ initOperand(1, argc);
+ initOperand(2, self);
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(ApplyArgs)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, getFunction), (1, getArgc), (2, getThis))
+
+ // For TI-informed monomorphic callsites.
+ WrappedFunction* getSingleTarget() const {
+ return target_;
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+
+ bool appendRoots(MRootList& roots) const override {
+ if (target_)
+ return target_->appendRoots(roots);
+ return true;
+ }
+};
+
+// fun.apply(fn, array)
+class MApplyArray
+ : public MAryInstruction<3>,
+ public Mix3Policy<ObjectPolicy<0>, ObjectPolicy<1>, BoxPolicy<2> >::Data
+{
+ protected:
+ // Monomorphic cache of single target from TI, or nullptr.
+ WrappedFunction* target_;
+
+ MApplyArray(WrappedFunction* target, MDefinition* fun, MDefinition* elements, MDefinition* self)
+ : target_(target)
+ {
+ initOperand(0, fun);
+ initOperand(1, elements);
+ initOperand(2, self);
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(ApplyArray)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, getFunction), (1, getElements), (2, getThis))
+
+ // For TI-informed monomorphic callsites.
+ WrappedFunction* getSingleTarget() const {
+ return target_;
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+
+ bool appendRoots(MRootList& roots) const override {
+ if (target_)
+ return target_->appendRoots(roots);
+ return true;
+ }
+};
+
+class MBail : public MNullaryInstruction
+{
+ protected:
+ explicit MBail(BailoutKind kind)
+ : MNullaryInstruction()
+ {
+ bailoutKind_ = kind;
+ setGuard();
+ }
+
+ private:
+ BailoutKind bailoutKind_;
+
+ public:
+ INSTRUCTION_HEADER(Bail)
+
+ static MBail*
+ New(TempAllocator& alloc, BailoutKind kind) {
+ return new(alloc) MBail(kind);
+ }
+ static MBail*
+ New(TempAllocator& alloc) {
+ return new(alloc) MBail(Bailout_Inevitable);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ BailoutKind bailoutKind() const {
+ return bailoutKind_;
+ }
+};
+
+class MUnreachable
+ : public MAryControlInstruction<0, 0>,
+ public NoTypePolicy::Data
+{
+ public:
+ INSTRUCTION_HEADER(Unreachable)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+// This class serve as a way to force the encoding of a snapshot, even if there
+// is no resume point using it. This is useful to run MAssertRecoveredOnBailout
+// assertions.
+class MEncodeSnapshot : public MNullaryInstruction
+{
+ protected:
+ MEncodeSnapshot()
+ : MNullaryInstruction()
+ {
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(EncodeSnapshot)
+
+ static MEncodeSnapshot*
+ New(TempAllocator& alloc) {
+ return new(alloc) MEncodeSnapshot();
+ }
+};
+
+class MAssertRecoveredOnBailout
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ protected:
+ bool mustBeRecovered_;
+
+ MAssertRecoveredOnBailout(MDefinition* ins, bool mustBeRecovered)
+ : MUnaryInstruction(ins), mustBeRecovered_(mustBeRecovered)
+ {
+ setResultType(MIRType::Value);
+ setRecoveredOnBailout();
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(AssertRecoveredOnBailout)
+ TRIVIAL_NEW_WRAPPERS
+
+ // Needed to assert that float32 instructions are correctly recovered.
+ bool canConsumeFloat32(MUse* use) const override { return true; }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+};
+
+class MAssertFloat32
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ protected:
+ bool mustBeFloat32_;
+
+ MAssertFloat32(MDefinition* value, bool mustBeFloat32)
+ : MUnaryInstruction(value), mustBeFloat32_(mustBeFloat32)
+ {
+ }
+
+ public:
+ INSTRUCTION_HEADER(AssertFloat32)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool canConsumeFloat32(MUse* use) const override { return true; }
+
+ bool mustBeFloat32() const { return mustBeFloat32_; }
+};
+
+class MGetDynamicName
+ : public MAryInstruction<2>,
+ public MixPolicy<ObjectPolicy<0>, ConvertToStringPolicy<1> >::Data
+{
+ protected:
+ MGetDynamicName(MDefinition* envChain, MDefinition* name)
+ {
+ initOperand(0, envChain);
+ initOperand(1, name);
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(GetDynamicName)
+ NAMED_OPERANDS((0, getEnvironmentChain), (1, getName))
+
+ static MGetDynamicName*
+ New(TempAllocator& alloc, MDefinition* envChain, MDefinition* name) {
+ return new(alloc) MGetDynamicName(envChain, name);
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+class MCallDirectEval
+ : public MAryInstruction<3>,
+ public Mix3Policy<ObjectPolicy<0>,
+ StringPolicy<1>,
+ BoxPolicy<2> >::Data
+{
+ protected:
+ MCallDirectEval(MDefinition* envChain, MDefinition* string,
+ MDefinition* newTargetValue, jsbytecode* pc)
+ : pc_(pc)
+ {
+ initOperand(0, envChain);
+ initOperand(1, string);
+ initOperand(2, newTargetValue);
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(CallDirectEval)
+ NAMED_OPERANDS((0, getEnvironmentChain), (1, getString), (2, getNewTargetValue))
+
+ static MCallDirectEval*
+ New(TempAllocator& alloc, MDefinition* envChain, MDefinition* string,
+ MDefinition* newTargetValue, jsbytecode* pc)
+ {
+ return new(alloc) MCallDirectEval(envChain, string, newTargetValue, pc);
+ }
+
+ jsbytecode* pc() const {
+ return pc_;
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+
+ private:
+ jsbytecode* pc_;
+};
+
+class MCompare
+ : public MBinaryInstruction,
+ public ComparePolicy::Data
+{
+ public:
+ enum CompareType {
+
+ // Anything compared to Undefined
+ Compare_Undefined,
+
+ // Anything compared to Null
+ Compare_Null,
+
+ // Undefined compared to Boolean
+ // Null compared to Boolean
+ // Double compared to Boolean
+ // String compared to Boolean
+ // Symbol compared to Boolean
+ // Object compared to Boolean
+ // Value compared to Boolean
+ Compare_Boolean,
+
+ // Int32 compared to Int32
+ // Boolean compared to Boolean
+ Compare_Int32,
+ Compare_Int32MaybeCoerceBoth,
+ Compare_Int32MaybeCoerceLHS,
+ Compare_Int32MaybeCoerceRHS,
+
+ // Int32 compared as unsigneds
+ Compare_UInt32,
+
+ // Int64 compared to Int64.
+ Compare_Int64,
+
+ // Int64 compared as unsigneds.
+ Compare_UInt64,
+
+ // Double compared to Double
+ Compare_Double,
+
+ Compare_DoubleMaybeCoerceLHS,
+ Compare_DoubleMaybeCoerceRHS,
+
+ // Float compared to Float
+ Compare_Float32,
+
+ // String compared to String
+ Compare_String,
+
+ // Undefined compared to String
+ // Null compared to String
+ // Boolean compared to String
+ // Int32 compared to String
+ // Double compared to String
+ // Object compared to String
+ // Value compared to String
+ Compare_StrictString,
+
+ // Object compared to Object
+ Compare_Object,
+
+ // Compare 2 values bitwise
+ Compare_Bitwise,
+
+ // All other possible compares
+ Compare_Unknown
+ };
+
+ private:
+ CompareType compareType_;
+ JSOp jsop_;
+ bool operandMightEmulateUndefined_;
+ bool operandsAreNeverNaN_;
+
+ // When a floating-point comparison is converted to an integer comparison
+ // (when range analysis proves it safe), we need to convert the operands
+ // to integer as well.
+ bool truncateOperands_;
+
+ MCompare(MDefinition* left, MDefinition* right, JSOp jsop)
+ : MBinaryInstruction(left, right),
+ compareType_(Compare_Unknown),
+ jsop_(jsop),
+ operandMightEmulateUndefined_(true),
+ operandsAreNeverNaN_(false),
+ truncateOperands_(false)
+ {
+ setResultType(MIRType::Boolean);
+ setMovable();
+ }
+
+ MCompare(MDefinition* left, MDefinition* right, JSOp jsop, CompareType compareType)
+ : MCompare(left, right, jsop)
+ {
+ MOZ_ASSERT(compareType == Compare_Int32 || compareType == Compare_UInt32 ||
+ compareType == Compare_Int64 || compareType == Compare_UInt64 ||
+ compareType == Compare_Double || compareType == Compare_Float32);
+ compareType_ = compareType;
+ operandMightEmulateUndefined_ = false;
+ setResultType(MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(Compare)
+ TRIVIAL_NEW_WRAPPERS
+
+ MOZ_MUST_USE bool tryFold(bool* result);
+ MOZ_MUST_USE bool evaluateConstantOperands(TempAllocator& alloc, bool* result);
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ void filtersUndefinedOrNull(bool trueBranch, MDefinition** subject, bool* filtersUndefined,
+ bool* filtersNull);
+
+ CompareType compareType() const {
+ return compareType_;
+ }
+ bool isInt32Comparison() const {
+ return compareType() == Compare_Int32 ||
+ compareType() == Compare_Int32MaybeCoerceBoth ||
+ compareType() == Compare_Int32MaybeCoerceLHS ||
+ compareType() == Compare_Int32MaybeCoerceRHS;
+ }
+ bool isDoubleComparison() const {
+ return compareType() == Compare_Double ||
+ compareType() == Compare_DoubleMaybeCoerceLHS ||
+ compareType() == Compare_DoubleMaybeCoerceRHS;
+ }
+ bool isFloat32Comparison() const {
+ return compareType() == Compare_Float32;
+ }
+ bool isNumericComparison() const {
+ return isInt32Comparison() ||
+ isDoubleComparison() ||
+ isFloat32Comparison();
+ }
+ void setCompareType(CompareType type) {
+ compareType_ = type;
+ }
+ MIRType inputType();
+
+ JSOp jsop() const {
+ return jsop_;
+ }
+ void markNoOperandEmulatesUndefined() {
+ operandMightEmulateUndefined_ = false;
+ }
+ bool operandMightEmulateUndefined() const {
+ return operandMightEmulateUndefined_;
+ }
+ bool operandsAreNeverNaN() const {
+ return operandsAreNeverNaN_;
+ }
+ AliasSet getAliasSet() const override {
+ // Strict equality is never effectful.
+ if (jsop_ == JSOP_STRICTEQ || jsop_ == JSOP_STRICTNE)
+ return AliasSet::None();
+ if (compareType_ == Compare_Unknown)
+ return AliasSet::Store(AliasSet::Any);
+ MOZ_ASSERT(compareType_ <= Compare_Bitwise);
+ return AliasSet::None();
+ }
+
+ void printOpcode(GenericPrinter& out) const override;
+ void collectRangeInfoPreTrunc() override;
+
+ void trySpecializeFloat32(TempAllocator& alloc) override;
+ bool isFloat32Commutative() const override { return true; }
+ bool needTruncation(TruncateKind kind) override;
+ void truncate() override;
+ TruncateKind operandTruncateKind(size_t index) const override;
+
+ static CompareType determineCompareType(JSOp op, MDefinition* left, MDefinition* right);
+ void cacheOperandMightEmulateUndefined(CompilerConstraintList* constraints);
+
+# ifdef DEBUG
+ bool isConsistentFloat32Use(MUse* use) const override {
+ // Both sides of the compare can be Float32
+ return compareType_ == Compare_Float32;
+ }
+# endif
+
+ ALLOW_CLONE(MCompare)
+
+ protected:
+ MOZ_MUST_USE bool tryFoldEqualOperands(bool* result);
+ MOZ_MUST_USE bool tryFoldTypeOf(bool* result);
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!binaryCongruentTo(ins))
+ return false;
+ return compareType() == ins->toCompare()->compareType() &&
+ jsop() == ins->toCompare()->jsop();
+ }
+};
+
+// Takes a typed value and returns an untyped value.
+class MBox
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ MBox(TempAllocator& alloc, MDefinition* ins)
+ : MUnaryInstruction(ins)
+ {
+ setResultType(MIRType::Value);
+ if (ins->resultTypeSet()) {
+ setResultTypeSet(ins->resultTypeSet());
+ } else if (ins->type() != MIRType::Value) {
+ TypeSet::Type ntype = ins->type() == MIRType::Object
+ ? TypeSet::AnyObjectType()
+ : TypeSet::PrimitiveType(ValueTypeFromMIRType(ins->type()));
+ setResultTypeSet(alloc.lifoAlloc()->new_<TemporaryTypeSet>(alloc.lifoAlloc(), ntype));
+ }
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(Box)
+ static MBox* New(TempAllocator& alloc, MDefinition* ins)
+ {
+ // Cannot box a box.
+ MOZ_ASSERT(ins->type() != MIRType::Value);
+
+ return new(alloc) MBox(alloc, ins);
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ ALLOW_CLONE(MBox)
+};
+
+// Note: the op may have been inverted during lowering (to put constants in a
+// position where they can be immediates), so it is important to use the
+// lir->jsop() instead of the mir->jsop() when it is present.
+static inline Assembler::Condition
+JSOpToCondition(MCompare::CompareType compareType, JSOp op)
+{
+ bool isSigned = (compareType != MCompare::Compare_UInt32);
+ return JSOpToCondition(op, isSigned);
+}
+
+// Takes a typed value and checks if it is a certain type. If so, the payload
+// is unpacked and returned as that type. Otherwise, it is considered a
+// deoptimization.
+class MUnbox final : public MUnaryInstruction, public BoxInputsPolicy::Data
+{
+ public:
+ enum Mode {
+ Fallible, // Check the type, and deoptimize if unexpected.
+ Infallible, // Type guard is not necessary.
+ TypeBarrier // Guard on the type, and act like a TypeBarrier on failure.
+ };
+
+ private:
+ Mode mode_;
+ BailoutKind bailoutKind_;
+
+ MUnbox(MDefinition* ins, MIRType type, Mode mode, BailoutKind kind, TempAllocator& alloc)
+ : MUnaryInstruction(ins),
+ mode_(mode)
+ {
+ // Only allow unboxing a non MIRType::Value when input and output types
+ // don't match. This is often used to force a bailout. Boxing happens
+ // during type analysis.
+ MOZ_ASSERT_IF(ins->type() != MIRType::Value, type != ins->type());
+
+ MOZ_ASSERT(type == MIRType::Boolean ||
+ type == MIRType::Int32 ||
+ type == MIRType::Double ||
+ type == MIRType::String ||
+ type == MIRType::Symbol ||
+ type == MIRType::Object);
+
+ TemporaryTypeSet* resultSet = ins->resultTypeSet();
+ if (resultSet && type == MIRType::Object)
+ resultSet = resultSet->cloneObjectsOnly(alloc.lifoAlloc());
+
+ setResultType(type);
+ setResultTypeSet(resultSet);
+ setMovable();
+
+ if (mode_ == TypeBarrier || mode_ == Fallible)
+ setGuard();
+
+ bailoutKind_ = kind;
+ }
+ public:
+ INSTRUCTION_HEADER(Unbox)
+ static MUnbox* New(TempAllocator& alloc, MDefinition* ins, MIRType type, Mode mode)
+ {
+ // Unless we were given a specific BailoutKind, pick a default based on
+ // the type we expect.
+ BailoutKind kind;
+ switch (type) {
+ case MIRType::Boolean:
+ kind = Bailout_NonBooleanInput;
+ break;
+ case MIRType::Int32:
+ kind = Bailout_NonInt32Input;
+ break;
+ case MIRType::Double:
+ kind = Bailout_NonNumericInput; // Int32s are fine too
+ break;
+ case MIRType::String:
+ kind = Bailout_NonStringInput;
+ break;
+ case MIRType::Symbol:
+ kind = Bailout_NonSymbolInput;
+ break;
+ case MIRType::Object:
+ kind = Bailout_NonObjectInput;
+ break;
+ default:
+ MOZ_CRASH("Given MIRType cannot be unboxed.");
+ }
+
+ return new(alloc) MUnbox(ins, type, mode, kind, alloc);
+ }
+
+ static MUnbox* New(TempAllocator& alloc, MDefinition* ins, MIRType type, Mode mode,
+ BailoutKind kind)
+ {
+ return new(alloc) MUnbox(ins, type, mode, kind, alloc);
+ }
+
+ Mode mode() const {
+ return mode_;
+ }
+ BailoutKind bailoutKind() const {
+ // If infallible, no bailout should be generated.
+ MOZ_ASSERT(fallible());
+ return bailoutKind_;
+ }
+ bool fallible() const {
+ return mode() != Infallible;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isUnbox() || ins->toUnbox()->mode() != mode())
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ void printOpcode(GenericPrinter& out) const override;
+ void makeInfallible() {
+ // Should only be called if we're already Infallible or TypeBarrier
+ MOZ_ASSERT(mode() != Fallible);
+ mode_ = Infallible;
+ }
+
+ ALLOW_CLONE(MUnbox)
+};
+
+class MGuardObject
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MGuardObject(MDefinition* ins)
+ : MUnaryInstruction(ins)
+ {
+ setGuard();
+ setMovable();
+ setResultType(MIRType::Object);
+ setResultTypeSet(ins->resultTypeSet());
+ }
+
+ public:
+ INSTRUCTION_HEADER(GuardObject)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MGuardString
+ : public MUnaryInstruction,
+ public StringPolicy<0>::Data
+{
+ explicit MGuardString(MDefinition* ins)
+ : MUnaryInstruction(ins)
+ {
+ setGuard();
+ setMovable();
+ setResultType(MIRType::String);
+ }
+
+ public:
+ INSTRUCTION_HEADER(GuardString)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MPolyInlineGuard
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MPolyInlineGuard(MDefinition* ins)
+ : MUnaryInstruction(ins)
+ {
+ setGuard();
+ setResultType(MIRType::Object);
+ setResultTypeSet(ins->resultTypeSet());
+ }
+
+ public:
+ INSTRUCTION_HEADER(PolyInlineGuard)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MAssertRange
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ // This is the range checked by the assertion. Don't confuse this with the
+ // range_ member or the range() accessor. Since MAssertRange doesn't return
+ // a value, it doesn't use those.
+ const Range* assertedRange_;
+
+ MAssertRange(MDefinition* ins, const Range* assertedRange)
+ : MUnaryInstruction(ins), assertedRange_(assertedRange)
+ {
+ setGuard();
+ setResultType(MIRType::None);
+ }
+
+ public:
+ INSTRUCTION_HEADER(AssertRange)
+ TRIVIAL_NEW_WRAPPERS
+
+ const Range* assertedRange() const {
+ return assertedRange_;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ void printOpcode(GenericPrinter& out) const override;
+};
+
+// Caller-side allocation of |this| for |new|:
+// Given a templateobject, construct |this| for JSOP_NEW
+class MCreateThisWithTemplate
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ gc::InitialHeap initialHeap_;
+
+ MCreateThisWithTemplate(CompilerConstraintList* constraints, MConstant* templateConst,
+ gc::InitialHeap initialHeap)
+ : MUnaryInstruction(templateConst),
+ initialHeap_(initialHeap)
+ {
+ setResultType(MIRType::Object);
+ setResultTypeSet(MakeSingletonTypeSet(constraints, templateObject()));
+ }
+
+ public:
+ INSTRUCTION_HEADER(CreateThisWithTemplate)
+ TRIVIAL_NEW_WRAPPERS
+
+ // Template for |this|, provided by TI.
+ JSObject* templateObject() const {
+ return &getOperand(0)->toConstant()->toObject();
+ }
+
+ gc::InitialHeap initialHeap() const {
+ return initialHeap_;
+ }
+
+ // Although creation of |this| modifies global state, it is safely repeatable.
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override;
+};
+
+// Caller-side allocation of |this| for |new|:
+// Given a prototype operand, construct |this| for JSOP_NEW.
+class MCreateThisWithProto
+ : public MTernaryInstruction,
+ public Mix3Policy<ObjectPolicy<0>, ObjectPolicy<1>, ObjectPolicy<2> >::Data
+{
+ MCreateThisWithProto(MDefinition* callee, MDefinition* newTarget, MDefinition* prototype)
+ : MTernaryInstruction(callee, newTarget, prototype)
+ {
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(CreateThisWithProto)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, getCallee), (1, getNewTarget), (2, getPrototype))
+
+ // Although creation of |this| modifies global state, it is safely repeatable.
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+// Caller-side allocation of |this| for |new|:
+// Constructs |this| when possible, else MagicValue(JS_IS_CONSTRUCTING).
+class MCreateThis
+ : public MBinaryInstruction,
+ public MixPolicy<ObjectPolicy<0>, ObjectPolicy<1> >::Data
+{
+ explicit MCreateThis(MDefinition* callee, MDefinition* newTarget)
+ : MBinaryInstruction(callee, newTarget)
+ {
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(CreateThis)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, getCallee), (1, getNewTarget))
+
+ // Although creation of |this| modifies global state, it is safely repeatable.
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+// Eager initialization of arguments object.
+class MCreateArgumentsObject
+ : public MUnaryInstruction,
+ public ObjectPolicy<0>::Data
+{
+ CompilerGCPointer<ArgumentsObject*> templateObj_;
+
+ MCreateArgumentsObject(MDefinition* callObj, ArgumentsObject* templateObj)
+ : MUnaryInstruction(callObj),
+ templateObj_(templateObj)
+ {
+ setResultType(MIRType::Object);
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(CreateArgumentsObject)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, getCallObject))
+
+ ArgumentsObject* templateObject() const {
+ return templateObj_;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(templateObj_);
+ }
+};
+
+class MGetArgumentsObjectArg
+ : public MUnaryInstruction,
+ public ObjectPolicy<0>::Data
+{
+ size_t argno_;
+
+ MGetArgumentsObjectArg(MDefinition* argsObject, size_t argno)
+ : MUnaryInstruction(argsObject),
+ argno_(argno)
+ {
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(GetArgumentsObjectArg)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, getArgsObject))
+
+ size_t argno() const {
+ return argno_;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::Any);
+ }
+};
+
+class MSetArgumentsObjectArg
+ : public MBinaryInstruction,
+ public MixPolicy<ObjectPolicy<0>, BoxPolicy<1> >::Data
+{
+ size_t argno_;
+
+ MSetArgumentsObjectArg(MDefinition* argsObj, size_t argno, MDefinition* value)
+ : MBinaryInstruction(argsObj, value),
+ argno_(argno)
+ {
+ }
+
+ public:
+ INSTRUCTION_HEADER(SetArgumentsObjectArg)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, getArgsObject), (1, getValue))
+
+ size_t argno() const {
+ return argno_;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::Any);
+ }
+};
+
+class MRunOncePrologue
+ : public MNullaryInstruction
+{
+ protected:
+ MRunOncePrologue()
+ {
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(RunOncePrologue)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+// Given a MIRType::Value A and a MIRType::Object B:
+// If the Value may be safely unboxed to an Object, return Object(A).
+// Otherwise, return B.
+// Used to implement return behavior for inlined constructors.
+class MReturnFromCtor
+ : public MAryInstruction<2>,
+ public MixPolicy<BoxPolicy<0>, ObjectPolicy<1> >::Data
+{
+ MReturnFromCtor(MDefinition* value, MDefinition* object) {
+ initOperand(0, value);
+ initOperand(1, object);
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(ReturnFromCtor)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, getValue), (1, getObject))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MToFPInstruction
+ : public MUnaryInstruction,
+ public ToDoublePolicy::Data
+{
+ public:
+ // Types of values which can be converted.
+ enum ConversionKind {
+ NonStringPrimitives,
+ NonNullNonStringPrimitives,
+ NumbersOnly
+ };
+
+ private:
+ ConversionKind conversion_;
+
+ protected:
+ explicit MToFPInstruction(MDefinition* def, ConversionKind conversion = NonStringPrimitives)
+ : MUnaryInstruction(def), conversion_(conversion)
+ { }
+
+ public:
+ ConversionKind conversion() const {
+ return conversion_;
+ }
+};
+
+// Converts a primitive (either typed or untyped) to a double. If the input is
+// not primitive at runtime, a bailout occurs.
+class MToDouble
+ : public MToFPInstruction
+{
+ private:
+ TruncateKind implicitTruncate_;
+
+ explicit MToDouble(MDefinition* def, ConversionKind conversion = NonStringPrimitives)
+ : MToFPInstruction(def, conversion), implicitTruncate_(NoTruncate)
+ {
+ setResultType(MIRType::Double);
+ setMovable();
+
+ // An object might have "valueOf", which means it is effectful.
+ // ToNumber(symbol) throws.
+ if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol))
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(ToDouble)
+ TRIVIAL_NEW_WRAPPERS
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isToDouble() || ins->toToDouble()->conversion() != conversion())
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ void computeRange(TempAllocator& alloc) override;
+ bool needTruncation(TruncateKind kind) override;
+ void truncate() override;
+ TruncateKind operandTruncateKind(size_t index) const override;
+
+#ifdef DEBUG
+ bool isConsistentFloat32Use(MUse* use) const override { return true; }
+#endif
+
+ TruncateKind truncateKind() const {
+ return implicitTruncate_;
+ }
+ void setTruncateKind(TruncateKind kind) {
+ implicitTruncate_ = Max(implicitTruncate_, kind);
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ if (input()->type() == MIRType::Value)
+ return false;
+ if (input()->type() == MIRType::Symbol)
+ return false;
+
+ return true;
+ }
+
+ ALLOW_CLONE(MToDouble)
+};
+
+// Converts a primitive (either typed or untyped) to a float32. If the input is
+// not primitive at runtime, a bailout occurs.
+class MToFloat32
+ : public MToFPInstruction
+{
+ protected:
+ bool mustPreserveNaN_;
+
+ explicit MToFloat32(MDefinition* def, ConversionKind conversion = NonStringPrimitives)
+ : MToFPInstruction(def, conversion),
+ mustPreserveNaN_(false)
+ {
+ setResultType(MIRType::Float32);
+ setMovable();
+
+ // An object might have "valueOf", which means it is effectful.
+ // ToNumber(symbol) throws.
+ if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol))
+ setGuard();
+ }
+
+ explicit MToFloat32(MDefinition* def, bool mustPreserveNaN)
+ : MToFloat32(def)
+ {
+ mustPreserveNaN_ = mustPreserveNaN;
+ }
+
+ public:
+ INSTRUCTION_HEADER(ToFloat32)
+ TRIVIAL_NEW_WRAPPERS
+
+ virtual MDefinition* foldsTo(TempAllocator& alloc) override;
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!congruentIfOperandsEqual(ins))
+ return false;
+ auto* other = ins->toToFloat32();
+ return other->conversion() == conversion() &&
+ other->mustPreserveNaN_ == mustPreserveNaN_;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ void computeRange(TempAllocator& alloc) override;
+
+ bool canConsumeFloat32(MUse* use) const override { return true; }
+ bool canProduceFloat32() const override { return true; }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ ALLOW_CLONE(MToFloat32)
+};
+
+// Converts a uint32 to a double (coming from wasm).
+class MWasmUnsignedToDouble
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ explicit MWasmUnsignedToDouble(MDefinition* def)
+ : MUnaryInstruction(def)
+ {
+ setResultType(MIRType::Double);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(WasmUnsignedToDouble)
+ TRIVIAL_NEW_WRAPPERS
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+// Converts a uint32 to a float32 (coming from wasm).
+class MWasmUnsignedToFloat32
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ explicit MWasmUnsignedToFloat32(MDefinition* def)
+ : MUnaryInstruction(def)
+ {
+ setResultType(MIRType::Float32);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(WasmUnsignedToFloat32)
+ TRIVIAL_NEW_WRAPPERS
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool canProduceFloat32() const override { return true; }
+};
+
+class MWrapInt64ToInt32
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ bool bottomHalf_;
+
+ explicit MWrapInt64ToInt32(MDefinition* def, bool bottomHalf = true)
+ : MUnaryInstruction(def),
+ bottomHalf_(bottomHalf)
+ {
+ setResultType(MIRType::Int32);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(WrapInt64ToInt32)
+ TRIVIAL_NEW_WRAPPERS
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isWrapInt64ToInt32())
+ return false;
+ if (ins->toWrapInt64ToInt32()->bottomHalf() != bottomHalf())
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool bottomHalf() const {
+ return bottomHalf_;
+ }
+};
+
+class MExtendInt32ToInt64
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ bool isUnsigned_;
+
+ MExtendInt32ToInt64(MDefinition* def, bool isUnsigned)
+ : MUnaryInstruction(def),
+ isUnsigned_(isUnsigned)
+ {
+ setResultType(MIRType::Int64);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(ExtendInt32ToInt64)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool isUnsigned() const { return isUnsigned_; }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isExtendInt32ToInt64())
+ return false;
+ if (ins->toExtendInt32ToInt64()->isUnsigned_ != isUnsigned_)
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MWasmTruncateToInt64
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ bool isUnsigned_;
+ wasm::TrapOffset trapOffset_;
+
+ MWasmTruncateToInt64(MDefinition* def, bool isUnsigned, wasm::TrapOffset trapOffset)
+ : MUnaryInstruction(def),
+ isUnsigned_(isUnsigned),
+ trapOffset_(trapOffset)
+ {
+ setResultType(MIRType::Int64);
+ setGuard(); // neither removable nor movable because of possible side-effects.
+ }
+
+ public:
+ INSTRUCTION_HEADER(WasmTruncateToInt64)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool isUnsigned() const { return isUnsigned_; }
+ wasm::TrapOffset trapOffset() const { return trapOffset_; }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins) &&
+ ins->toWasmTruncateToInt64()->isUnsigned() == isUnsigned_;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+// Truncate a value to an int32, with wasm semantics: this will trap when the
+// value is out of range.
+class MWasmTruncateToInt32
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ bool isUnsigned_;
+ wasm::TrapOffset trapOffset_;
+
+ explicit MWasmTruncateToInt32(MDefinition* def, bool isUnsigned, wasm::TrapOffset trapOffset)
+ : MUnaryInstruction(def), isUnsigned_(isUnsigned), trapOffset_(trapOffset)
+ {
+ setResultType(MIRType::Int32);
+ setGuard(); // neither removable nor movable because of possible side-effects.
+ }
+
+ public:
+ INSTRUCTION_HEADER(WasmTruncateToInt32)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool isUnsigned() const {
+ return isUnsigned_;
+ }
+ wasm::TrapOffset trapOffset() const {
+ return trapOffset_;
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins) &&
+ ins->toWasmTruncateToInt32()->isUnsigned() == isUnsigned_;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MInt64ToFloatingPoint
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ bool isUnsigned_;
+
+ MInt64ToFloatingPoint(MDefinition* def, MIRType type, bool isUnsigned)
+ : MUnaryInstruction(def),
+ isUnsigned_(isUnsigned)
+ {
+ MOZ_ASSERT(IsFloatingPointType(type));
+ setResultType(type);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(Int64ToFloatingPoint)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool isUnsigned() const { return isUnsigned_; }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isInt64ToFloatingPoint())
+ return false;
+ if (ins->toInt64ToFloatingPoint()->isUnsigned_ != isUnsigned_)
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+// Converts a primitive (either typed or untyped) to an int32. If the input is
+// not primitive at runtime, a bailout occurs. If the input cannot be converted
+// to an int32 without loss (i.e. "5.5" or undefined) then a bailout occurs.
+class MToInt32
+ : public MUnaryInstruction,
+ public ToInt32Policy::Data
+{
+ bool canBeNegativeZero_;
+ MacroAssembler::IntConversionInputKind conversion_;
+
+ explicit MToInt32(MDefinition* def, MacroAssembler::IntConversionInputKind conversion =
+ MacroAssembler::IntConversion_Any)
+ : MUnaryInstruction(def),
+ canBeNegativeZero_(true),
+ conversion_(conversion)
+ {
+ setResultType(MIRType::Int32);
+ setMovable();
+
+ // An object might have "valueOf", which means it is effectful.
+ // ToNumber(symbol) throws.
+ if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol))
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(ToInt32)
+ TRIVIAL_NEW_WRAPPERS
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ // this only has backwards information flow.
+ void analyzeEdgeCasesBackward() override;
+
+ bool canBeNegativeZero() const {
+ return canBeNegativeZero_;
+ }
+ void setCanBeNegativeZero(bool negativeZero) {
+ canBeNegativeZero_ = negativeZero;
+ }
+
+ MacroAssembler::IntConversionInputKind conversion() const {
+ return conversion_;
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isToInt32() || ins->toToInt32()->conversion() != conversion())
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ void computeRange(TempAllocator& alloc) override;
+ void collectRangeInfoPreTrunc() override;
+
+#ifdef DEBUG
+ bool isConsistentFloat32Use(MUse* use) const override { return true; }
+#endif
+
+ ALLOW_CLONE(MToInt32)
+};
+
+// Converts a value or typed input to a truncated int32, for use with bitwise
+// operations. This is an infallible ValueToECMAInt32.
+class MTruncateToInt32
+ : public MUnaryInstruction,
+ public ToInt32Policy::Data
+{
+ explicit MTruncateToInt32(MDefinition* def)
+ : MUnaryInstruction(def)
+ {
+ setResultType(MIRType::Int32);
+ setMovable();
+
+ // An object might have "valueOf", which means it is effectful.
+ // ToInt32(symbol) throws.
+ if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol))
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(TruncateToInt32)
+ TRIVIAL_NEW_WRAPPERS
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ void computeRange(TempAllocator& alloc) override;
+ TruncateKind operandTruncateKind(size_t index) const override;
+# ifdef DEBUG
+ bool isConsistentFloat32Use(MUse* use) const override {
+ return true;
+ }
+#endif
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return input()->type() < MIRType::Symbol;
+ }
+
+ ALLOW_CLONE(MTruncateToInt32)
+};
+
+// Converts any type to a string
+class MToString :
+ public MUnaryInstruction,
+ public ToStringPolicy::Data
+{
+ explicit MToString(MDefinition* def)
+ : MUnaryInstruction(def)
+ {
+ setResultType(MIRType::String);
+ setMovable();
+
+ // Objects might override toString and Symbols throw.
+ if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol))
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(ToString)
+ TRIVIAL_NEW_WRAPPERS
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool fallible() const {
+ return input()->mightBeType(MIRType::Object);
+ }
+
+ ALLOW_CLONE(MToString)
+};
+
+// Converts any type to an object or null value, throwing on undefined.
+class MToObjectOrNull :
+ public MUnaryInstruction,
+ public BoxInputsPolicy::Data
+{
+ explicit MToObjectOrNull(MDefinition* def)
+ : MUnaryInstruction(def)
+ {
+ setResultType(MIRType::ObjectOrNull);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(ToObjectOrNull)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ ALLOW_CLONE(MToObjectOrNull)
+};
+
+class MBitNot
+ : public MUnaryInstruction,
+ public BitwisePolicy::Data
+{
+ protected:
+ explicit MBitNot(MDefinition* input)
+ : MUnaryInstruction(input)
+ {
+ specialization_ = MIRType::None;
+ setResultType(MIRType::Int32);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(BitNot)
+ TRIVIAL_NEW_WRAPPERS
+
+ static MBitNot* NewInt32(TempAllocator& alloc, MDefinition* input);
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ void setSpecialization(MIRType type) {
+ specialization_ = type;
+ setResultType(type);
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ if (specialization_ == MIRType::None)
+ return AliasSet::Store(AliasSet::Any);
+ return AliasSet::None();
+ }
+ void computeRange(TempAllocator& alloc) override;
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return specialization_ != MIRType::None;
+ }
+
+ ALLOW_CLONE(MBitNot)
+};
+
+class MTypeOf
+ : public MUnaryInstruction,
+ public BoxInputsPolicy::Data
+{
+ MIRType inputType_;
+ bool inputMaybeCallableOrEmulatesUndefined_;
+
+ MTypeOf(MDefinition* def, MIRType inputType)
+ : MUnaryInstruction(def), inputType_(inputType),
+ inputMaybeCallableOrEmulatesUndefined_(true)
+ {
+ setResultType(MIRType::String);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(TypeOf)
+ TRIVIAL_NEW_WRAPPERS
+
+ MIRType inputType() const {
+ return inputType_;
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ void cacheInputMaybeCallableOrEmulatesUndefined(CompilerConstraintList* constraints);
+
+ bool inputMaybeCallableOrEmulatesUndefined() const {
+ return inputMaybeCallableOrEmulatesUndefined_;
+ }
+ void markInputNotCallableOrEmulatesUndefined() {
+ inputMaybeCallableOrEmulatesUndefined_ = false;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isTypeOf())
+ return false;
+ if (inputType() != ins->toTypeOf()->inputType())
+ return false;
+ if (inputMaybeCallableOrEmulatesUndefined() !=
+ ins->toTypeOf()->inputMaybeCallableOrEmulatesUndefined())
+ {
+ return false;
+ }
+ return congruentIfOperandsEqual(ins);
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+};
+
+class MToAsync
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MToAsync(MDefinition* unwrapped)
+ : MUnaryInstruction(unwrapped)
+ {
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(ToAsync)
+ TRIVIAL_NEW_WRAPPERS
+};
+
+class MToId
+ : public MUnaryInstruction,
+ public BoxInputsPolicy::Data
+{
+ explicit MToId(MDefinition* index)
+ : MUnaryInstruction(index)
+ {
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(ToId)
+ TRIVIAL_NEW_WRAPPERS
+};
+
+class MBinaryBitwiseInstruction
+ : public MBinaryInstruction,
+ public BitwisePolicy::Data
+{
+ protected:
+ MBinaryBitwiseInstruction(MDefinition* left, MDefinition* right, MIRType type)
+ : MBinaryInstruction(left, right), maskMatchesLeftRange(false),
+ maskMatchesRightRange(false)
+ {
+ MOZ_ASSERT(type == MIRType::Int32 || type == MIRType::Int64);
+ setResultType(type);
+ setMovable();
+ }
+
+ void specializeAs(MIRType type);
+ bool maskMatchesLeftRange;
+ bool maskMatchesRightRange;
+
+ public:
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ MDefinition* foldUnnecessaryBitop();
+ virtual MDefinition* foldIfZero(size_t operand) = 0;
+ virtual MDefinition* foldIfNegOne(size_t operand) = 0;
+ virtual MDefinition* foldIfEqual() = 0;
+ virtual MDefinition* foldIfAllBitsSet(size_t operand) = 0;
+ virtual void infer(BaselineInspector* inspector, jsbytecode* pc);
+ void collectRangeInfoPreTrunc() override;
+
+ void setInt32Specialization() {
+ specialization_ = MIRType::Int32;
+ setResultType(MIRType::Int32);
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return binaryCongruentTo(ins);
+ }
+ AliasSet getAliasSet() const override {
+ if (specialization_ >= MIRType::Object)
+ return AliasSet::Store(AliasSet::Any);
+ return AliasSet::None();
+ }
+
+ TruncateKind operandTruncateKind(size_t index) const override;
+};
+
+class MBitAnd : public MBinaryBitwiseInstruction
+{
+ MBitAnd(MDefinition* left, MDefinition* right, MIRType type)
+ : MBinaryBitwiseInstruction(left, right, type)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(BitAnd)
+ static MBitAnd* New(TempAllocator& alloc, MDefinition* left, MDefinition* right);
+ static MBitAnd* New(TempAllocator& alloc, MDefinition* left, MDefinition* right, MIRType type);
+
+ MDefinition* foldIfZero(size_t operand) override {
+ return getOperand(operand); // 0 & x => 0;
+ }
+ MDefinition* foldIfNegOne(size_t operand) override {
+ return getOperand(1 - operand); // x & -1 => x
+ }
+ MDefinition* foldIfEqual() override {
+ return getOperand(0); // x & x => x;
+ }
+ MDefinition* foldIfAllBitsSet(size_t operand) override {
+ // e.g. for uint16: x & 0xffff => x;
+ return getOperand(1 - operand);
+ }
+ void computeRange(TempAllocator& alloc) override;
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return specialization_ != MIRType::None;
+ }
+
+ ALLOW_CLONE(MBitAnd)
+};
+
+class MBitOr : public MBinaryBitwiseInstruction
+{
+ MBitOr(MDefinition* left, MDefinition* right, MIRType type)
+ : MBinaryBitwiseInstruction(left, right, type)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(BitOr)
+ static MBitOr* New(TempAllocator& alloc, MDefinition* left, MDefinition* right);
+ static MBitOr* New(TempAllocator& alloc, MDefinition* left, MDefinition* right, MIRType type);
+
+ MDefinition* foldIfZero(size_t operand) override {
+ return getOperand(1 - operand); // 0 | x => x, so if ith is 0, return (1-i)th
+ }
+ MDefinition* foldIfNegOne(size_t operand) override {
+ return getOperand(operand); // x | -1 => -1
+ }
+ MDefinition* foldIfEqual() override {
+ return getOperand(0); // x | x => x
+ }
+ MDefinition* foldIfAllBitsSet(size_t operand) override {
+ return this;
+ }
+ void computeRange(TempAllocator& alloc) override;
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return specialization_ != MIRType::None;
+ }
+
+ ALLOW_CLONE(MBitOr)
+};
+
+class MBitXor : public MBinaryBitwiseInstruction
+{
+ MBitXor(MDefinition* left, MDefinition* right, MIRType type)
+ : MBinaryBitwiseInstruction(left, right, type)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(BitXor)
+ static MBitXor* New(TempAllocator& alloc, MDefinition* left, MDefinition* right);
+ static MBitXor* New(TempAllocator& alloc, MDefinition* left, MDefinition* right, MIRType type);
+
+ MDefinition* foldIfZero(size_t operand) override {
+ return getOperand(1 - operand); // 0 ^ x => x
+ }
+ MDefinition* foldIfNegOne(size_t operand) override {
+ return this;
+ }
+ MDefinition* foldIfEqual() override {
+ return this;
+ }
+ MDefinition* foldIfAllBitsSet(size_t operand) override {
+ return this;
+ }
+ void computeRange(TempAllocator& alloc) override;
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return specialization_ < MIRType::Object;
+ }
+
+ ALLOW_CLONE(MBitXor)
+};
+
+class MShiftInstruction
+ : public MBinaryBitwiseInstruction
+{
+ protected:
+ MShiftInstruction(MDefinition* left, MDefinition* right, MIRType type)
+ : MBinaryBitwiseInstruction(left, right, type)
+ { }
+
+ public:
+ MDefinition* foldIfNegOne(size_t operand) override {
+ return this;
+ }
+ MDefinition* foldIfEqual() override {
+ return this;
+ }
+ MDefinition* foldIfAllBitsSet(size_t operand) override {
+ return this;
+ }
+ virtual void infer(BaselineInspector* inspector, jsbytecode* pc) override;
+};
+
+class MLsh : public MShiftInstruction
+{
+ MLsh(MDefinition* left, MDefinition* right, MIRType type)
+ : MShiftInstruction(left, right, type)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(Lsh)
+ static MLsh* New(TempAllocator& alloc, MDefinition* left, MDefinition* right);
+ static MLsh* New(TempAllocator& alloc, MDefinition* left, MDefinition* right, MIRType type);
+
+ MDefinition* foldIfZero(size_t operand) override {
+ // 0 << x => 0
+ // x << 0 => x
+ return getOperand(0);
+ }
+
+ void computeRange(TempAllocator& alloc) override;
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return specialization_ != MIRType::None;
+ }
+
+ ALLOW_CLONE(MLsh)
+};
+
+class MRsh : public MShiftInstruction
+{
+ MRsh(MDefinition* left, MDefinition* right, MIRType type)
+ : MShiftInstruction(left, right, type)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(Rsh)
+ static MRsh* New(TempAllocator& alloc, MDefinition* left, MDefinition* right);
+ static MRsh* New(TempAllocator& alloc, MDefinition* left, MDefinition* right, MIRType type);
+
+ MDefinition* foldIfZero(size_t operand) override {
+ // 0 >> x => 0
+ // x >> 0 => x
+ return getOperand(0);
+ }
+ void computeRange(TempAllocator& alloc) override;
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return specialization_ < MIRType::Object;
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ ALLOW_CLONE(MRsh)
+};
+
+class MUrsh : public MShiftInstruction
+{
+ bool bailoutsDisabled_;
+
+ MUrsh(MDefinition* left, MDefinition* right, MIRType type)
+ : MShiftInstruction(left, right, type),
+ bailoutsDisabled_(false)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(Ursh)
+ static MUrsh* New(TempAllocator& alloc, MDefinition* left, MDefinition* right);
+ static MUrsh* New(TempAllocator& alloc, MDefinition* left, MDefinition* right, MIRType type);
+
+ MDefinition* foldIfZero(size_t operand) override {
+ // 0 >>> x => 0
+ if (operand == 0)
+ return getOperand(0);
+
+ return this;
+ }
+
+ void infer(BaselineInspector* inspector, jsbytecode* pc) override;
+
+ bool bailoutsDisabled() const {
+ return bailoutsDisabled_;
+ }
+
+ bool fallible() const;
+
+ void computeRange(TempAllocator& alloc) override;
+ void collectRangeInfoPreTrunc() override;
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return specialization_ < MIRType::Object;
+ }
+
+ ALLOW_CLONE(MUrsh)
+};
+
+class MSignExtend
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ public:
+ enum Mode {
+ Byte,
+ Half
+ };
+
+ private:
+ Mode mode_;
+
+ MSignExtend(MDefinition* op, Mode mode)
+ : MUnaryInstruction(op), mode_(mode)
+ {
+ setResultType(MIRType::Int32);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(SignExtend)
+ TRIVIAL_NEW_WRAPPERS
+
+ Mode mode() { return mode_; }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ ALLOW_CLONE(MSignExtend)
+};
+
+class MBinaryArithInstruction
+ : public MBinaryInstruction,
+ public ArithPolicy::Data
+{
+ // Implicit truncate flag is set by the truncate backward range analysis
+ // optimization phase, and by wasm pre-processing. It is used in
+ // NeedNegativeZeroCheck to check if the result of a multiplication needs to
+ // produce -0 double value, and for avoiding overflow checks.
+
+ // This optimization happens when the multiplication cannot be truncated
+ // even if all uses are truncating its result, such as when the range
+ // analysis detect a precision loss in the multiplication.
+ TruncateKind implicitTruncate_;
+
+ // Whether we must preserve NaN semantics, and in particular not fold
+ // (x op id) or (id op x) to x, or replace a division by a multiply of the
+ // exact reciprocal.
+ bool mustPreserveNaN_;
+
+ public:
+ MBinaryArithInstruction(MDefinition* left, MDefinition* right)
+ : MBinaryInstruction(left, right),
+ implicitTruncate_(NoTruncate),
+ mustPreserveNaN_(false)
+ {
+ specialization_ = MIRType::None;
+ setMovable();
+ }
+
+ static MBinaryArithInstruction* New(TempAllocator& alloc, Opcode op,
+ MDefinition* left, MDefinition* right);
+
+ bool constantDoubleResult(TempAllocator& alloc);
+
+ void setMustPreserveNaN(bool b) { mustPreserveNaN_ = b; }
+ bool mustPreserveNaN() const { return mustPreserveNaN_; }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ void printOpcode(GenericPrinter& out) const override;
+
+ virtual double getIdentity() = 0;
+
+ void setSpecialization(MIRType type) {
+ specialization_ = type;
+ setResultType(type);
+ }
+ void setInt32Specialization() {
+ specialization_ = MIRType::Int32;
+ setResultType(MIRType::Int32);
+ }
+ void setNumberSpecialization(TempAllocator& alloc, BaselineInspector* inspector, jsbytecode* pc);
+
+ virtual void trySpecializeFloat32(TempAllocator& alloc) override;
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!binaryCongruentTo(ins))
+ return false;
+ const auto* other = static_cast<const MBinaryArithInstruction*>(ins);
+ return other->mustPreserveNaN_ == mustPreserveNaN_;
+ }
+ AliasSet getAliasSet() const override {
+ if (specialization_ >= MIRType::Object)
+ return AliasSet::Store(AliasSet::Any);
+ return AliasSet::None();
+ }
+
+ bool isTruncated() const {
+ return implicitTruncate_ == Truncate;
+ }
+ TruncateKind truncateKind() const {
+ return implicitTruncate_;
+ }
+ void setTruncateKind(TruncateKind kind) {
+ implicitTruncate_ = Max(implicitTruncate_, kind);
+ }
+};
+
+class MMinMax
+ : public MBinaryInstruction,
+ public ArithPolicy::Data
+{
+ bool isMax_;
+
+ MMinMax(MDefinition* left, MDefinition* right, MIRType type, bool isMax)
+ : MBinaryInstruction(left, right),
+ isMax_(isMax)
+ {
+ MOZ_ASSERT(IsNumberType(type));
+ setResultType(type);
+ setMovable();
+ specialization_ = type;
+ }
+
+ public:
+ INSTRUCTION_HEADER(MinMax)
+ TRIVIAL_NEW_WRAPPERS
+
+ static MMinMax* NewWasm(TempAllocator& alloc, MDefinition* left, MDefinition* right,
+ MIRType type, bool isMax)
+ {
+ return New(alloc, left, right, type, isMax);
+ }
+
+ bool isMax() const {
+ return isMax_;
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!congruentIfOperandsEqual(ins))
+ return false;
+ const MMinMax* other = ins->toMinMax();
+ return other->isMax() == isMax();
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ void computeRange(TempAllocator& alloc) override;
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ bool isFloat32Commutative() const override { return true; }
+ void trySpecializeFloat32(TempAllocator& alloc) override;
+
+ ALLOW_CLONE(MMinMax)
+};
+
+class MAbs
+ : public MUnaryInstruction,
+ public ArithPolicy::Data
+{
+ bool implicitTruncate_;
+
+ MAbs(MDefinition* num, MIRType type)
+ : MUnaryInstruction(num),
+ implicitTruncate_(false)
+ {
+ MOZ_ASSERT(IsNumberType(type));
+ setResultType(type);
+ setMovable();
+ specialization_ = type;
+ }
+
+ public:
+ INSTRUCTION_HEADER(Abs)
+ TRIVIAL_NEW_WRAPPERS
+
+ static MAbs* NewWasm(TempAllocator& alloc, MDefinition* num, MIRType type) {
+ auto* ins = new(alloc) MAbs(num, type);
+ if (type == MIRType::Int32)
+ ins->implicitTruncate_ = true;
+ return ins;
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ bool fallible() const;
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ void computeRange(TempAllocator& alloc) override;
+ bool isFloat32Commutative() const override { return true; }
+ void trySpecializeFloat32(TempAllocator& alloc) override;
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ ALLOW_CLONE(MAbs)
+};
+
+class MClz
+ : public MUnaryInstruction
+ , public BitwisePolicy::Data
+{
+ bool operandIsNeverZero_;
+
+ explicit MClz(MDefinition* num, MIRType type)
+ : MUnaryInstruction(num),
+ operandIsNeverZero_(false)
+ {
+ MOZ_ASSERT(IsIntType(type));
+ MOZ_ASSERT(IsNumberType(num->type()));
+ specialization_ = type;
+ setResultType(type);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(Clz)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, num))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool operandIsNeverZero() const {
+ return operandIsNeverZero_;
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ void computeRange(TempAllocator& alloc) override;
+ void collectRangeInfoPreTrunc() override;
+};
+
+class MCtz
+ : public MUnaryInstruction
+ , public BitwisePolicy::Data
+{
+ bool operandIsNeverZero_;
+
+ explicit MCtz(MDefinition* num, MIRType type)
+ : MUnaryInstruction(num),
+ operandIsNeverZero_(false)
+ {
+ MOZ_ASSERT(IsIntType(type));
+ MOZ_ASSERT(IsNumberType(num->type()));
+ specialization_ = type;
+ setResultType(type);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(Ctz)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, num))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool operandIsNeverZero() const {
+ return operandIsNeverZero_;
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ void computeRange(TempAllocator& alloc) override;
+ void collectRangeInfoPreTrunc() override;
+};
+
+class MPopcnt
+ : public MUnaryInstruction
+ , public BitwisePolicy::Data
+{
+ explicit MPopcnt(MDefinition* num, MIRType type)
+ : MUnaryInstruction(num)
+ {
+ MOZ_ASSERT(IsNumberType(num->type()));
+ MOZ_ASSERT(IsIntType(type));
+ specialization_ = type;
+ setResultType(type);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(Popcnt)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, num))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ void computeRange(TempAllocator& alloc) override;
+};
+
+// Inline implementation of Math.sqrt().
+class MSqrt
+ : public MUnaryInstruction,
+ public FloatingPointPolicy<0>::Data
+{
+ MSqrt(MDefinition* num, MIRType type)
+ : MUnaryInstruction(num)
+ {
+ setResultType(type);
+ specialization_ = type;
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(Sqrt)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ void computeRange(TempAllocator& alloc) override;
+
+ bool isFloat32Commutative() const override { return true; }
+ void trySpecializeFloat32(TempAllocator& alloc) override;
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ ALLOW_CLONE(MSqrt)
+};
+
+class MCopySign
+ : public MBinaryInstruction,
+ public NoTypePolicy::Data
+{
+ MCopySign(MDefinition* lhs, MDefinition* rhs, MIRType type)
+ : MBinaryInstruction(lhs, rhs)
+ {
+ setResultType(type);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(CopySign)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ ALLOW_CLONE(MCopySign)
+};
+
+// Inline implementation of atan2 (arctangent of y/x).
+class MAtan2
+ : public MBinaryInstruction,
+ public MixPolicy<DoublePolicy<0>, DoublePolicy<1> >::Data
+{
+ MAtan2(MDefinition* y, MDefinition* x)
+ : MBinaryInstruction(y, x)
+ {
+ setResultType(MIRType::Double);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(Atan2)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, y), (1, x))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ ALLOW_CLONE(MAtan2)
+};
+
+// Inline implementation of Math.hypot().
+class MHypot
+ : public MVariadicInstruction,
+ public AllDoublePolicy::Data
+{
+ MHypot()
+ {
+ setResultType(MIRType::Double);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(Hypot)
+ static MHypot* New(TempAllocator& alloc, const MDefinitionVector& vector);
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ bool canClone() const override {
+ return true;
+ }
+
+ MInstruction* clone(TempAllocator& alloc,
+ const MDefinitionVector& inputs) const override {
+ return MHypot::New(alloc, inputs);
+ }
+};
+
+// Inline implementation of Math.pow().
+class MPow
+ : public MBinaryInstruction,
+ public PowPolicy::Data
+{
+ MPow(MDefinition* input, MDefinition* power, MIRType powerType)
+ : MBinaryInstruction(input, power)
+ {
+ MOZ_ASSERT(powerType == MIRType::Double || powerType == MIRType::Int32);
+ specialization_ = powerType;
+ setResultType(MIRType::Double);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(Pow)
+ TRIVIAL_NEW_WRAPPERS
+
+ MDefinition* input() const {
+ return lhs();
+ }
+ MDefinition* power() const {
+ return rhs();
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool possiblyCalls() const override {
+ return true;
+ }
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ // Temporarily disable recovery to relieve fuzzer pressure. See bug 1188586.
+ return false;
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ ALLOW_CLONE(MPow)
+};
+
+// Inline implementation of Math.pow(x, 0.5), which subtly differs from Math.sqrt(x).
+class MPowHalf
+ : public MUnaryInstruction,
+ public DoublePolicy<0>::Data
+{
+ bool operandIsNeverNegativeInfinity_;
+ bool operandIsNeverNegativeZero_;
+ bool operandIsNeverNaN_;
+
+ explicit MPowHalf(MDefinition* input)
+ : MUnaryInstruction(input),
+ operandIsNeverNegativeInfinity_(false),
+ operandIsNeverNegativeZero_(false),
+ operandIsNeverNaN_(false)
+ {
+ setResultType(MIRType::Double);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(PowHalf)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ bool operandIsNeverNegativeInfinity() const {
+ return operandIsNeverNegativeInfinity_;
+ }
+ bool operandIsNeverNegativeZero() const {
+ return operandIsNeverNegativeZero_;
+ }
+ bool operandIsNeverNaN() const {
+ return operandIsNeverNaN_;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ void collectRangeInfoPreTrunc() override;
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ ALLOW_CLONE(MPowHalf)
+};
+
+// Inline implementation of Math.random().
+class MRandom : public MNullaryInstruction
+{
+ MRandom()
+ {
+ setResultType(MIRType::Double);
+ }
+
+ public:
+ INSTRUCTION_HEADER(Random)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+
+ void computeRange(TempAllocator& alloc) override;
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+
+ bool canRecoverOnBailout() const override {
+#ifdef JS_MORE_DETERMINISTIC
+ return false;
+#else
+ return true;
+#endif
+ }
+
+ ALLOW_CLONE(MRandom)
+};
+
+class MMathFunction
+ : public MUnaryInstruction,
+ public FloatingPointPolicy<0>::Data
+{
+ public:
+ enum Function {
+ Log,
+ Sin,
+ Cos,
+ Exp,
+ Tan,
+ ACos,
+ ASin,
+ ATan,
+ Log10,
+ Log2,
+ Log1P,
+ ExpM1,
+ CosH,
+ SinH,
+ TanH,
+ ACosH,
+ ASinH,
+ ATanH,
+ Sign,
+ Trunc,
+ Cbrt,
+ Floor,
+ Ceil,
+ Round
+ };
+
+ private:
+ Function function_;
+ const MathCache* cache_;
+
+ // A nullptr cache means this function will neither access nor update the cache.
+ MMathFunction(MDefinition* input, Function function, const MathCache* cache)
+ : MUnaryInstruction(input), function_(function), cache_(cache)
+ {
+ setResultType(MIRType::Double);
+ specialization_ = MIRType::Double;
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(MathFunction)
+ TRIVIAL_NEW_WRAPPERS
+
+ Function function() const {
+ return function_;
+ }
+ const MathCache* cache() const {
+ return cache_;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isMathFunction())
+ return false;
+ if (ins->toMathFunction()->function() != function())
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ void printOpcode(GenericPrinter& out) const override;
+
+ static const char* FunctionName(Function function);
+
+ bool isFloat32Commutative() const override {
+ return function_ == Floor || function_ == Ceil || function_ == Round;
+ }
+ void trySpecializeFloat32(TempAllocator& alloc) override;
+ void computeRange(TempAllocator& alloc) override;
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ if (input()->type() == MIRType::SinCosDouble)
+ return false;
+ switch(function_) {
+ case Sin:
+ case Log:
+ case Round:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ ALLOW_CLONE(MMathFunction)
+};
+
+class MAdd : public MBinaryArithInstruction
+{
+ MAdd(MDefinition* left, MDefinition* right)
+ : MBinaryArithInstruction(left, right)
+ {
+ setResultType(MIRType::Value);
+ }
+
+ MAdd(MDefinition* left, MDefinition* right, MIRType type)
+ : MAdd(left, right)
+ {
+ specialization_ = type;
+ setResultType(type);
+ if (type == MIRType::Int32) {
+ setTruncateKind(Truncate);
+ setCommutative();
+ }
+ }
+
+ public:
+ INSTRUCTION_HEADER(Add)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool isFloat32Commutative() const override { return true; }
+
+ double getIdentity() override {
+ return 0;
+ }
+
+ bool fallible() const;
+ void computeRange(TempAllocator& alloc) override;
+ bool needTruncation(TruncateKind kind) override;
+ void truncate() override;
+ TruncateKind operandTruncateKind(size_t index) const override;
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return specialization_ < MIRType::Object;
+ }
+
+ ALLOW_CLONE(MAdd)
+};
+
+class MSub : public MBinaryArithInstruction
+{
+ MSub(MDefinition* left, MDefinition* right)
+ : MBinaryArithInstruction(left, right)
+ {
+ setResultType(MIRType::Value);
+ }
+
+ MSub(MDefinition* left, MDefinition* right, MIRType type, bool mustPreserveNaN = false)
+ : MSub(left, right)
+ {
+ specialization_ = type;
+ setResultType(type);
+ setMustPreserveNaN(mustPreserveNaN);
+ if (type == MIRType::Int32)
+ setTruncateKind(Truncate);
+ }
+
+ public:
+ INSTRUCTION_HEADER(Sub)
+ TRIVIAL_NEW_WRAPPERS
+
+ double getIdentity() override {
+ return 0;
+ }
+
+ bool isFloat32Commutative() const override { return true; }
+
+ bool fallible() const;
+ void computeRange(TempAllocator& alloc) override;
+ bool needTruncation(TruncateKind kind) override;
+ void truncate() override;
+ TruncateKind operandTruncateKind(size_t index) const override;
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return specialization_ < MIRType::Object;
+ }
+
+ ALLOW_CLONE(MSub)
+};
+
+class MMul : public MBinaryArithInstruction
+{
+ public:
+ enum Mode {
+ Normal,
+ Integer
+ };
+
+ private:
+ // Annotation the result could be a negative zero
+ // and we need to guard this during execution.
+ bool canBeNegativeZero_;
+
+ Mode mode_;
+
+ MMul(MDefinition* left, MDefinition* right, MIRType type, Mode mode)
+ : MBinaryArithInstruction(left, right),
+ canBeNegativeZero_(true),
+ mode_(mode)
+ {
+ if (mode == Integer) {
+ // This implements the required behavior for Math.imul, which
+ // can never fail and always truncates its output to int32.
+ canBeNegativeZero_ = false;
+ setTruncateKind(Truncate);
+ setCommutative();
+ }
+ MOZ_ASSERT_IF(mode != Integer, mode == Normal);
+
+ if (type != MIRType::Value)
+ specialization_ = type;
+ setResultType(type);
+ }
+
+ public:
+ INSTRUCTION_HEADER(Mul)
+ static MMul* New(TempAllocator& alloc, MDefinition* left, MDefinition* right) {
+ return new(alloc) MMul(left, right, MIRType::Value, MMul::Normal);
+ }
+ static MMul* New(TempAllocator& alloc, MDefinition* left, MDefinition* right, MIRType type,
+ Mode mode = Normal)
+ {
+ return new(alloc) MMul(left, right, type, mode);
+ }
+ static MMul* NewWasm(TempAllocator& alloc, MDefinition* left, MDefinition* right, MIRType type,
+ Mode mode, bool mustPreserveNaN)
+ {
+ auto* ret = new(alloc) MMul(left, right, type, mode);
+ ret->setMustPreserveNaN(mustPreserveNaN);
+ return ret;
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ void analyzeEdgeCasesForward() override;
+ void analyzeEdgeCasesBackward() override;
+ void collectRangeInfoPreTrunc() override;
+
+ double getIdentity() override {
+ return 1;
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isMul())
+ return false;
+
+ const MMul* mul = ins->toMul();
+ if (canBeNegativeZero_ != mul->canBeNegativeZero())
+ return false;
+
+ if (mode_ != mul->mode())
+ return false;
+
+ if (mustPreserveNaN() != mul->mustPreserveNaN())
+ return false;
+
+ return binaryCongruentTo(ins);
+ }
+
+ bool canOverflow() const;
+
+ bool canBeNegativeZero() const {
+ return canBeNegativeZero_;
+ }
+ void setCanBeNegativeZero(bool negativeZero) {
+ canBeNegativeZero_ = negativeZero;
+ }
+
+ MOZ_MUST_USE bool updateForReplacement(MDefinition* ins) override;
+
+ bool fallible() const {
+ return canBeNegativeZero_ || canOverflow();
+ }
+
+ void setSpecialization(MIRType type) {
+ specialization_ = type;
+ }
+
+ bool isFloat32Commutative() const override { return true; }
+
+ void computeRange(TempAllocator& alloc) override;
+ bool needTruncation(TruncateKind kind) override;
+ void truncate() override;
+ TruncateKind operandTruncateKind(size_t index) const override;
+
+ Mode mode() const { return mode_; }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return specialization_ < MIRType::Object;
+ }
+
+ ALLOW_CLONE(MMul)
+};
+
+class MDiv : public MBinaryArithInstruction
+{
+ bool canBeNegativeZero_;
+ bool canBeNegativeOverflow_;
+ bool canBeDivideByZero_;
+ bool canBeNegativeDividend_;
+ bool unsigned_;
+ bool trapOnError_;
+ wasm::TrapOffset trapOffset_;
+
+ MDiv(MDefinition* left, MDefinition* right, MIRType type)
+ : MBinaryArithInstruction(left, right),
+ canBeNegativeZero_(true),
+ canBeNegativeOverflow_(true),
+ canBeDivideByZero_(true),
+ canBeNegativeDividend_(true),
+ unsigned_(false),
+ trapOnError_(false)
+ {
+ if (type != MIRType::Value)
+ specialization_ = type;
+ setResultType(type);
+ }
+
+ public:
+ INSTRUCTION_HEADER(Div)
+ static MDiv* New(TempAllocator& alloc, MDefinition* left, MDefinition* right) {
+ return new(alloc) MDiv(left, right, MIRType::Value);
+ }
+ static MDiv* New(TempAllocator& alloc, MDefinition* left, MDefinition* right, MIRType type) {
+ return new(alloc) MDiv(left, right, type);
+ }
+ static MDiv* New(TempAllocator& alloc, MDefinition* left, MDefinition* right,
+ MIRType type, bool unsignd, bool trapOnError = false,
+ wasm::TrapOffset trapOffset = wasm::TrapOffset(),
+ bool mustPreserveNaN = false)
+ {
+ auto* div = new(alloc) MDiv(left, right, type);
+ div->unsigned_ = unsignd;
+ div->trapOnError_ = trapOnError;
+ div->trapOffset_ = trapOffset;
+ if (trapOnError) {
+ div->setGuard(); // not removable because of possible side-effects.
+ div->setNotMovable();
+ }
+ div->setMustPreserveNaN(mustPreserveNaN);
+ if (type == MIRType::Int32)
+ div->setTruncateKind(Truncate);
+ return div;
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ void analyzeEdgeCasesForward() override;
+ void analyzeEdgeCasesBackward() override;
+
+ double getIdentity() override {
+ MOZ_CRASH("not used");
+ }
+
+ bool canBeNegativeZero() const {
+ return canBeNegativeZero_;
+ }
+ void setCanBeNegativeZero(bool negativeZero) {
+ canBeNegativeZero_ = negativeZero;
+ }
+
+ bool canBeNegativeOverflow() const {
+ return canBeNegativeOverflow_;
+ }
+
+ bool canBeDivideByZero() const {
+ return canBeDivideByZero_;
+ }
+
+ bool canBeNegativeDividend() const {
+ // "Dividend" is an ambiguous concept for unsigned truncated
+ // division, because of the truncation procedure:
+ // ((x>>>0)/2)|0, for example, gets transformed in
+ // MDiv::truncate into a node with lhs representing x (not
+ // x>>>0) and rhs representing the constant 2; in other words,
+ // the MIR node corresponds to "cast operands to unsigned and
+ // divide" operation. In this case, is the dividend x or is it
+ // x>>>0? In order to resolve such ambiguities, we disallow
+ // the usage of this method for unsigned division.
+ MOZ_ASSERT(!unsigned_);
+ return canBeNegativeDividend_;
+ }
+
+ bool isUnsigned() const {
+ return unsigned_;
+ }
+
+ bool isTruncatedIndirectly() const {
+ return truncateKind() >= IndirectTruncate;
+ }
+
+ bool canTruncateInfinities() const {
+ return isTruncated();
+ }
+ bool canTruncateRemainder() const {
+ return isTruncated();
+ }
+ bool canTruncateOverflow() const {
+ return isTruncated() || isTruncatedIndirectly();
+ }
+ bool canTruncateNegativeZero() const {
+ return isTruncated() || isTruncatedIndirectly();
+ }
+
+ bool trapOnError() const {
+ return trapOnError_;
+ }
+ wasm::TrapOffset trapOffset() const {
+ MOZ_ASSERT(trapOnError_);
+ return trapOffset_;
+ }
+
+ bool isFloat32Commutative() const override { return true; }
+
+ void computeRange(TempAllocator& alloc) override;
+ bool fallible() const;
+ bool needTruncation(TruncateKind kind) override;
+ void truncate() override;
+ void collectRangeInfoPreTrunc() override;
+ TruncateKind operandTruncateKind(size_t index) const override;
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return specialization_ < MIRType::Object;
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!MBinaryArithInstruction::congruentTo(ins))
+ return false;
+ const MDiv* other = ins->toDiv();
+ MOZ_ASSERT(other->trapOnError() == trapOnError_);
+ return unsigned_ == other->isUnsigned();
+ }
+
+ ALLOW_CLONE(MDiv)
+};
+
+class MMod : public MBinaryArithInstruction
+{
+ bool unsigned_;
+ bool canBeNegativeDividend_;
+ bool canBePowerOfTwoDivisor_;
+ bool canBeDivideByZero_;
+ bool trapOnError_;
+ wasm::TrapOffset trapOffset_;
+
+ MMod(MDefinition* left, MDefinition* right, MIRType type)
+ : MBinaryArithInstruction(left, right),
+ unsigned_(false),
+ canBeNegativeDividend_(true),
+ canBePowerOfTwoDivisor_(true),
+ canBeDivideByZero_(true),
+ trapOnError_(false)
+ {
+ if (type != MIRType::Value)
+ specialization_ = type;
+ setResultType(type);
+ }
+
+ public:
+ INSTRUCTION_HEADER(Mod)
+ static MMod* New(TempAllocator& alloc, MDefinition* left, MDefinition* right) {
+ return new(alloc) MMod(left, right, MIRType::Value);
+ }
+ static MMod* New(TempAllocator& alloc, MDefinition* left, MDefinition* right,
+ MIRType type, bool unsignd, bool trapOnError = false,
+ wasm::TrapOffset trapOffset = wasm::TrapOffset())
+ {
+ auto* mod = new(alloc) MMod(left, right, type);
+ mod->unsigned_ = unsignd;
+ mod->trapOnError_ = trapOnError;
+ mod->trapOffset_ = trapOffset;
+ if (trapOnError) {
+ mod->setGuard(); // not removable because of possible side-effects.
+ mod->setNotMovable();
+ }
+ if (type == MIRType::Int32)
+ mod->setTruncateKind(Truncate);
+ return mod;
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ double getIdentity() override {
+ MOZ_CRASH("not used");
+ }
+
+ bool canBeNegativeDividend() const {
+ MOZ_ASSERT(specialization_ == MIRType::Int32 || specialization_ == MIRType::Int64);
+ MOZ_ASSERT(!unsigned_);
+ return canBeNegativeDividend_;
+ }
+
+ bool canBeDivideByZero() const {
+ MOZ_ASSERT(specialization_ == MIRType::Int32 || specialization_ == MIRType::Int64);
+ return canBeDivideByZero_;
+ }
+
+ bool canBePowerOfTwoDivisor() const {
+ MOZ_ASSERT(specialization_ == MIRType::Int32);
+ return canBePowerOfTwoDivisor_;
+ }
+
+ void analyzeEdgeCasesForward() override;
+
+ bool isUnsigned() const {
+ return unsigned_;
+ }
+
+ bool trapOnError() const {
+ return trapOnError_;
+ }
+ wasm::TrapOffset trapOffset() const {
+ MOZ_ASSERT(trapOnError_);
+ return trapOffset_;
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return specialization_ < MIRType::Object;
+ }
+
+ bool fallible() const;
+
+ void computeRange(TempAllocator& alloc) override;
+ bool needTruncation(TruncateKind kind) override;
+ void truncate() override;
+ void collectRangeInfoPreTrunc() override;
+ TruncateKind operandTruncateKind(size_t index) const override;
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return MBinaryArithInstruction::congruentTo(ins) &&
+ unsigned_ == ins->toMod()->isUnsigned();
+ }
+
+ ALLOW_CLONE(MMod)
+};
+
+class MConcat
+ : public MBinaryInstruction,
+ public MixPolicy<ConvertToStringPolicy<0>, ConvertToStringPolicy<1> >::Data
+{
+ MConcat(MDefinition* left, MDefinition* right)
+ : MBinaryInstruction(left, right)
+ {
+ // At least one input should be definitely string
+ MOZ_ASSERT(left->type() == MIRType::String || right->type() == MIRType::String);
+
+ setMovable();
+ setResultType(MIRType::String);
+ }
+
+ public:
+ INSTRUCTION_HEADER(Concat)
+ TRIVIAL_NEW_WRAPPERS
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ ALLOW_CLONE(MConcat)
+};
+
+class MCharCodeAt
+ : public MBinaryInstruction,
+ public MixPolicy<StringPolicy<0>, IntPolicy<1> >::Data
+{
+ MCharCodeAt(MDefinition* str, MDefinition* index)
+ : MBinaryInstruction(str, index)
+ {
+ setMovable();
+ setResultType(MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(CharCodeAt)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ virtual AliasSet getAliasSet() const override {
+ // Strings are immutable, so there is no implicit dependency.
+ return AliasSet::None();
+ }
+
+ void computeRange(TempAllocator& alloc) override;
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ ALLOW_CLONE(MCharCodeAt)
+};
+
+class MFromCharCode
+ : public MUnaryInstruction,
+ public IntPolicy<0>::Data
+{
+ explicit MFromCharCode(MDefinition* code)
+ : MUnaryInstruction(code)
+ {
+ setMovable();
+ setResultType(MIRType::String);
+ }
+
+ public:
+ INSTRUCTION_HEADER(FromCharCode)
+ TRIVIAL_NEW_WRAPPERS
+
+ virtual AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ ALLOW_CLONE(MFromCharCode)
+};
+
+class MFromCodePoint
+ : public MUnaryInstruction,
+ public IntPolicy<0>::Data
+{
+ explicit MFromCodePoint(MDefinition* codePoint)
+ : MUnaryInstruction(codePoint)
+ {
+ setGuard(); // throws on invalid code point
+ setMovable();
+ setResultType(MIRType::String);
+ }
+
+ public:
+ INSTRUCTION_HEADER(FromCodePoint)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+class MSinCos
+ : public MUnaryInstruction,
+ public FloatingPointPolicy<0>::Data
+{
+ const MathCache* cache_;
+
+ MSinCos(MDefinition *input, const MathCache *cache) : MUnaryInstruction(input), cache_(cache)
+ {
+ setResultType(MIRType::SinCosDouble);
+ specialization_ = MIRType::Double;
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(SinCos)
+
+ static MSinCos *New(TempAllocator &alloc, MDefinition *input, const MathCache *cache)
+ {
+ return new (alloc) MSinCos(input, cache);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool congruentTo(const MDefinition *ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ bool possiblyCalls() const override {
+ return true;
+ }
+ const MathCache* cache() const {
+ return cache_;
+ }
+};
+
+class MStringSplit
+ : public MTernaryInstruction,
+ public MixPolicy<StringPolicy<0>, StringPolicy<1> >::Data
+{
+ MStringSplit(CompilerConstraintList* constraints, MDefinition* string, MDefinition* sep,
+ MConstant* templateObject)
+ : MTernaryInstruction(string, sep, templateObject)
+ {
+ setResultType(MIRType::Object);
+ setResultTypeSet(templateObject->resultTypeSet());
+ }
+
+ public:
+ INSTRUCTION_HEADER(StringSplit)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, string), (1, separator))
+
+ JSObject* templateObject() const {
+ return &getOperand(2)->toConstant()->toObject();
+ }
+ ObjectGroup* group() const {
+ return templateObject()->group();
+ }
+ bool possiblyCalls() const override {
+ return true;
+ }
+ virtual AliasSet getAliasSet() const override {
+ // Although this instruction returns a new array, we don't have to mark
+ // it as store instruction, see also MNewArray.
+ return AliasSet::None();
+ }
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+};
+
+// Returns the value to use as |this| value. See also ComputeThis and
+// BoxNonStrictThis in Interpreter.h.
+class MComputeThis
+ : public MUnaryInstruction,
+ public BoxPolicy<0>::Data
+{
+ explicit MComputeThis(MDefinition* def)
+ : MUnaryInstruction(def)
+ {
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(ComputeThis)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+
+ // Note: don't override getAliasSet: the thisValue hook can be effectful.
+};
+
+// Load an arrow function's |new.target| value.
+class MArrowNewTarget
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MArrowNewTarget(MDefinition* callee)
+ : MUnaryInstruction(callee)
+ {
+ setResultType(MIRType::Value);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(ArrowNewTarget)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, callee))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ // An arrow function's lexical |this| value is immutable.
+ return AliasSet::None();
+ }
+};
+
+class MPhi final
+ : public MDefinition,
+ public InlineListNode<MPhi>,
+ public NoTypePolicy::Data
+{
+ using InputVector = js::Vector<MUse, 2, JitAllocPolicy>;
+ InputVector inputs_;
+
+ TruncateKind truncateKind_;
+ bool hasBackedgeType_;
+ bool triedToSpecialize_;
+ bool isIterator_;
+ bool canProduceFloat32_;
+ bool canConsumeFloat32_;
+
+#if DEBUG
+ bool specialized_;
+#endif
+
+ protected:
+ MUse* getUseFor(size_t index) override {
+ // Note: after the initial IonBuilder pass, it is OK to change phi
+ // operands such that they do not include the type sets of their
+ // operands. This can arise during e.g. value numbering, where
+ // definitions producing the same value may have different type sets.
+ MOZ_ASSERT(index < numOperands());
+ return &inputs_[index];
+ }
+ const MUse* getUseFor(size_t index) const override {
+ return &inputs_[index];
+ }
+
+ public:
+ INSTRUCTION_HEADER_WITHOUT_TYPEPOLICY(Phi)
+ virtual TypePolicy* typePolicy();
+ virtual MIRType typePolicySpecialization();
+
+ MPhi(TempAllocator& alloc, MIRType resultType)
+ : inputs_(alloc),
+ truncateKind_(NoTruncate),
+ hasBackedgeType_(false),
+ triedToSpecialize_(false),
+ isIterator_(false),
+ canProduceFloat32_(false),
+ canConsumeFloat32_(false)
+#if DEBUG
+ , specialized_(false)
+#endif
+ {
+ setResultType(resultType);
+ }
+
+ static MPhi* New(TempAllocator& alloc, MIRType resultType = MIRType::Value) {
+ return new(alloc) MPhi(alloc, resultType);
+ }
+ static MPhi* New(TempAllocator::Fallible alloc, MIRType resultType = MIRType::Value) {
+ return new(alloc) MPhi(alloc.alloc, resultType);
+ }
+
+ void removeOperand(size_t index);
+ void removeAllOperands();
+
+ MDefinition* getOperand(size_t index) const override {
+ return inputs_[index].producer();
+ }
+ size_t numOperands() const override {
+ return inputs_.length();
+ }
+ size_t indexOf(const MUse* u) const final override {
+ MOZ_ASSERT(u >= &inputs_[0]);
+ MOZ_ASSERT(u <= &inputs_[numOperands() - 1]);
+ return u - &inputs_[0];
+ }
+ void replaceOperand(size_t index, MDefinition* operand) final override {
+ inputs_[index].replaceProducer(operand);
+ }
+ bool hasBackedgeType() const {
+ return hasBackedgeType_;
+ }
+ bool triedToSpecialize() const {
+ return triedToSpecialize_;
+ }
+ void specialize(MIRType type) {
+ triedToSpecialize_ = true;
+ setResultType(type);
+ }
+ bool specializeType(TempAllocator& alloc);
+
+#ifdef DEBUG
+ // Assert that this is a phi in a loop header with a unique predecessor and
+ // a unique backedge.
+ void assertLoopPhi() const;
+#else
+ void assertLoopPhi() const {}
+#endif
+
+ // Assuming this phi is in a loop header with a unique loop entry, return
+ // the phi operand along the loop entry.
+ MDefinition* getLoopPredecessorOperand() const {
+ assertLoopPhi();
+ return getOperand(0);
+ }
+
+ // Assuming this phi is in a loop header with a unique loop entry, return
+ // the phi operand along the loop backedge.
+ MDefinition* getLoopBackedgeOperand() const {
+ assertLoopPhi();
+ return getOperand(1);
+ }
+
+ // Whether this phi's type already includes information for def.
+ bool typeIncludes(MDefinition* def);
+
+ // Add types for this phi which speculate about new inputs that may come in
+ // via a loop backedge.
+ MOZ_MUST_USE bool addBackedgeType(TempAllocator& alloc, MIRType type,
+ TemporaryTypeSet* typeSet);
+
+ // Initializes the operands vector to the given capacity,
+ // permitting use of addInput() instead of addInputSlow().
+ MOZ_MUST_USE bool reserveLength(size_t length) {
+ return inputs_.reserve(length);
+ }
+
+ // Use only if capacity has been reserved by reserveLength
+ void addInput(MDefinition* ins) {
+ inputs_.infallibleEmplaceBack(ins, this);
+ }
+
+ // Appends a new input to the input vector. May perform reallocation.
+ // Prefer reserveLength() and addInput() instead, where possible.
+ MOZ_MUST_USE bool addInputSlow(MDefinition* ins) {
+ return inputs_.emplaceBack(ins, this);
+ }
+
+ // Appends a new input to the input vector. Infallible because
+ // we know the inputs fits in the vector's inline storage.
+ void addInlineInput(MDefinition* ins) {
+ MOZ_ASSERT(inputs_.length() < InputVector::InlineLength);
+ MOZ_ALWAYS_TRUE(addInputSlow(ins));
+ }
+
+ // Update the type of this phi after adding |ins| as an input. Set
+ // |*ptypeChange| to true if the type changed.
+ bool checkForTypeChange(TempAllocator& alloc, MDefinition* ins, bool* ptypeChange);
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ MDefinition* foldsTernary(TempAllocator& alloc);
+ MDefinition* foldsFilterTypeSet();
+
+ bool congruentTo(const MDefinition* ins) const override;
+
+ bool isIterator() const {
+ return isIterator_;
+ }
+ void setIterator() {
+ isIterator_ = true;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ void computeRange(TempAllocator& alloc) override;
+
+ MDefinition* operandIfRedundant();
+
+ bool canProduceFloat32() const override {
+ return canProduceFloat32_;
+ }
+
+ void setCanProduceFloat32(bool can) {
+ canProduceFloat32_ = can;
+ }
+
+ bool canConsumeFloat32(MUse* use) const override {
+ return canConsumeFloat32_;
+ }
+
+ void setCanConsumeFloat32(bool can) {
+ canConsumeFloat32_ = can;
+ }
+
+ TruncateKind operandTruncateKind(size_t index) const override;
+ bool needTruncation(TruncateKind kind) override;
+ void truncate() override;
+};
+
+// The goal of a Beta node is to split a def at a conditionally taken
+// branch, so that uses dominated by it have a different name.
+class MBeta
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ private:
+ // This is the range induced by a comparison and branch in a preceding
+ // block. Note that this does not reflect any range constraints from
+ // the input value itself, so this value may differ from the range()
+ // range after it is computed.
+ const Range* comparison_;
+
+ MBeta(MDefinition* val, const Range* comp)
+ : MUnaryInstruction(val),
+ comparison_(comp)
+ {
+ setResultType(val->type());
+ setResultTypeSet(val->resultTypeSet());
+ }
+
+ public:
+ INSTRUCTION_HEADER(Beta)
+ TRIVIAL_NEW_WRAPPERS
+
+ void printOpcode(GenericPrinter& out) const override;
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ void computeRange(TempAllocator& alloc) override;
+};
+
+// If input evaluates to false (i.e. it's NaN, 0 or -0), 0 is returned, else the input is returned
+class MNaNToZero
+ : public MUnaryInstruction,
+ public DoublePolicy<0>::Data
+{
+ bool operandIsNeverNaN_;
+ bool operandIsNeverNegativeZero_;
+ explicit MNaNToZero(MDefinition* input)
+ : MUnaryInstruction(input), operandIsNeverNaN_(false), operandIsNeverNegativeZero_(false)
+ {
+ setResultType(MIRType::Double);
+ setMovable();
+ }
+ public:
+ INSTRUCTION_HEADER(NaNToZero)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool operandIsNeverNaN() const {
+ return operandIsNeverNaN_;
+ }
+
+ bool operandIsNeverNegativeZero() const {
+ return operandIsNeverNegativeZero_;
+ }
+
+ void collectRangeInfoPreTrunc() override;
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ void computeRange(TempAllocator& alloc) override;
+
+ bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ ALLOW_CLONE(MNaNToZero)
+};
+
+// MIR representation of a Value on the OSR BaselineFrame.
+// The Value is indexed off of OsrFrameReg.
+class MOsrValue
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ private:
+ ptrdiff_t frameOffset_;
+
+ MOsrValue(MOsrEntry* entry, ptrdiff_t frameOffset)
+ : MUnaryInstruction(entry),
+ frameOffset_(frameOffset)
+ {
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(OsrValue)
+ TRIVIAL_NEW_WRAPPERS
+
+ ptrdiff_t frameOffset() const {
+ return frameOffset_;
+ }
+
+ MOsrEntry* entry() {
+ return getOperand(0)->toOsrEntry();
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+// MIR representation of a JSObject scope chain pointer on the OSR BaselineFrame.
+// The pointer is indexed off of OsrFrameReg.
+class MOsrEnvironmentChain
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ private:
+ explicit MOsrEnvironmentChain(MOsrEntry* entry)
+ : MUnaryInstruction(entry)
+ {
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(OsrEnvironmentChain)
+ TRIVIAL_NEW_WRAPPERS
+
+ MOsrEntry* entry() {
+ return getOperand(0)->toOsrEntry();
+ }
+};
+
+// MIR representation of a JSObject ArgumentsObject pointer on the OSR BaselineFrame.
+// The pointer is indexed off of OsrFrameReg.
+class MOsrArgumentsObject
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ private:
+ explicit MOsrArgumentsObject(MOsrEntry* entry)
+ : MUnaryInstruction(entry)
+ {
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(OsrArgumentsObject)
+ TRIVIAL_NEW_WRAPPERS
+
+ MOsrEntry* entry() {
+ return getOperand(0)->toOsrEntry();
+ }
+};
+
+// MIR representation of the return value on the OSR BaselineFrame.
+// The Value is indexed off of OsrFrameReg.
+class MOsrReturnValue
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ private:
+ explicit MOsrReturnValue(MOsrEntry* entry)
+ : MUnaryInstruction(entry)
+ {
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(OsrReturnValue)
+ TRIVIAL_NEW_WRAPPERS
+
+ MOsrEntry* entry() {
+ return getOperand(0)->toOsrEntry();
+ }
+};
+
+class MBinarySharedStub
+ : public MBinaryInstruction,
+ public MixPolicy<BoxPolicy<0>, BoxPolicy<1> >::Data
+{
+ protected:
+ explicit MBinarySharedStub(MDefinition* left, MDefinition* right)
+ : MBinaryInstruction(left, right)
+ {
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(BinarySharedStub)
+ TRIVIAL_NEW_WRAPPERS
+};
+
+class MUnarySharedStub
+ : public MUnaryInstruction,
+ public BoxPolicy<0>::Data
+{
+ explicit MUnarySharedStub(MDefinition* input)
+ : MUnaryInstruction(input)
+ {
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(UnarySharedStub)
+ TRIVIAL_NEW_WRAPPERS
+};
+
+class MNullarySharedStub
+ : public MNullaryInstruction
+{
+ explicit MNullarySharedStub()
+ : MNullaryInstruction()
+ {
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(NullarySharedStub)
+ TRIVIAL_NEW_WRAPPERS
+};
+
+// Check the current frame for over-recursion past the global stack limit.
+class MCheckOverRecursed
+ : public MNullaryInstruction
+{
+ public:
+ INSTRUCTION_HEADER(CheckOverRecursed)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+// Check whether we need to fire the interrupt handler.
+class MInterruptCheck : public MNullaryInstruction
+{
+ MInterruptCheck() {
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(InterruptCheck)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+// Directly jumps to the indicated trap, leaving Wasm code and reporting a
+// runtime error.
+
+class MWasmTrap
+ : public MAryControlInstruction<0, 0>,
+ public NoTypePolicy::Data
+{
+ wasm::Trap trap_;
+ wasm::TrapOffset trapOffset_;
+
+ explicit MWasmTrap(wasm::Trap trap, wasm::TrapOffset trapOffset)
+ : trap_(trap),
+ trapOffset_(trapOffset)
+ {}
+
+ public:
+ INSTRUCTION_HEADER(WasmTrap)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ wasm::Trap trap() const { return trap_; }
+ wasm::TrapOffset trapOffset() const { return trapOffset_; }
+};
+
+// Checks if a value is JS_UNINITIALIZED_LEXICAL, bailout out if so, leaving
+// it to baseline to throw at the correct pc.
+class MLexicalCheck
+ : public MUnaryInstruction,
+ public BoxPolicy<0>::Data
+{
+ BailoutKind kind_;
+ explicit MLexicalCheck(MDefinition* input, BailoutKind kind = Bailout_UninitializedLexical)
+ : MUnaryInstruction(input),
+ kind_(kind)
+ {
+ setResultType(MIRType::Value);
+ setResultTypeSet(input->resultTypeSet());
+ setMovable();
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(LexicalCheck)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ BailoutKind bailoutKind() const {
+ return kind_;
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+};
+
+// Unconditionally throw an uninitialized let error.
+class MThrowRuntimeLexicalError : public MNullaryInstruction
+{
+ unsigned errorNumber_;
+
+ explicit MThrowRuntimeLexicalError(unsigned errorNumber)
+ : errorNumber_(errorNumber)
+ {
+ setGuard();
+ setResultType(MIRType::None);
+ }
+
+ public:
+ INSTRUCTION_HEADER(ThrowRuntimeLexicalError)
+ TRIVIAL_NEW_WRAPPERS
+
+ unsigned errorNumber() const {
+ return errorNumber_;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+// In the prologues of global and eval scripts, check for redeclarations.
+class MGlobalNameConflictsCheck : public MNullaryInstruction
+{
+ MGlobalNameConflictsCheck() {
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(GlobalNameConflictsCheck)
+ TRIVIAL_NEW_WRAPPERS
+};
+
+// If not defined, set a global variable to |undefined|.
+class MDefVar
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ CompilerPropertyName name_; // Target name to be defined.
+ unsigned attrs_; // Attributes to be set.
+
+ private:
+ MDefVar(PropertyName* name, unsigned attrs, MDefinition* envChain)
+ : MUnaryInstruction(envChain),
+ name_(name),
+ attrs_(attrs)
+ {
+ }
+
+ public:
+ INSTRUCTION_HEADER(DefVar)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, environmentChain))
+
+ PropertyName* name() const {
+ return name_;
+ }
+ unsigned attrs() const {
+ return attrs_;
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(name_);
+ }
+};
+
+class MDefLexical
+ : public MNullaryInstruction
+{
+ CompilerPropertyName name_; // Target name to be defined.
+ unsigned attrs_; // Attributes to be set.
+
+ private:
+ MDefLexical(PropertyName* name, unsigned attrs)
+ : name_(name),
+ attrs_(attrs)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(DefLexical)
+ TRIVIAL_NEW_WRAPPERS
+
+ PropertyName* name() const {
+ return name_;
+ }
+ unsigned attrs() const {
+ return attrs_;
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(name_);
+ }
+};
+
+class MDefFun
+ : public MBinaryInstruction,
+ public ObjectPolicy<0>::Data
+{
+ private:
+ MDefFun(MDefinition* fun, MDefinition* envChain)
+ : MBinaryInstruction(fun, envChain)
+ {}
+
+ public:
+ INSTRUCTION_HEADER(DefFun)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, fun), (1, environmentChain))
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+class MRegExp : public MNullaryInstruction
+{
+ CompilerGCPointer<RegExpObject*> source_;
+ bool mustClone_;
+
+ MRegExp(CompilerConstraintList* constraints, RegExpObject* source)
+ : source_(source),
+ mustClone_(true)
+ {
+ setResultType(MIRType::Object);
+ setResultTypeSet(MakeSingletonTypeSet(constraints, source));
+ }
+
+ public:
+ INSTRUCTION_HEADER(RegExp)
+ TRIVIAL_NEW_WRAPPERS
+
+ void setDoNotClone() {
+ mustClone_ = false;
+ }
+ bool mustClone() const {
+ return mustClone_;
+ }
+ RegExpObject* source() const {
+ return source_;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool possiblyCalls() const override {
+ return true;
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(source_);
+ }
+};
+
+class MRegExpMatcher
+ : public MAryInstruction<3>,
+ public Mix3Policy<ObjectPolicy<0>,
+ StringPolicy<1>,
+ IntPolicy<2> >::Data
+{
+ private:
+
+ MRegExpMatcher(MDefinition* regexp, MDefinition* string, MDefinition* lastIndex)
+ : MAryInstruction<3>()
+ {
+ initOperand(0, regexp);
+ initOperand(1, string);
+ initOperand(2, lastIndex);
+
+ setMovable();
+ // May be object or null.
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(RegExpMatcher)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, regexp), (1, string), (2, lastIndex))
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+class MRegExpSearcher
+ : public MAryInstruction<3>,
+ public Mix3Policy<ObjectPolicy<0>,
+ StringPolicy<1>,
+ IntPolicy<2> >::Data
+{
+ private:
+
+ MRegExpSearcher(MDefinition* regexp, MDefinition* string, MDefinition* lastIndex)
+ : MAryInstruction<3>()
+ {
+ initOperand(0, regexp);
+ initOperand(1, string);
+ initOperand(2, lastIndex);
+
+ setMovable();
+ setResultType(MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(RegExpSearcher)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, regexp), (1, string), (2, lastIndex))
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+class MRegExpTester
+ : public MAryInstruction<3>,
+ public Mix3Policy<ObjectPolicy<0>,
+ StringPolicy<1>,
+ IntPolicy<2> >::Data
+{
+ private:
+
+ MRegExpTester(MDefinition* regexp, MDefinition* string, MDefinition* lastIndex)
+ : MAryInstruction<3>()
+ {
+ initOperand(0, regexp);
+ initOperand(1, string);
+ initOperand(2, lastIndex);
+
+ setMovable();
+ setResultType(MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(RegExpTester)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, regexp), (1, string), (2, lastIndex))
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+};
+
+class MRegExpPrototypeOptimizable
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MRegExpPrototypeOptimizable(MDefinition* object)
+ : MUnaryInstruction(object)
+ {
+ setResultType(MIRType::Boolean);
+ }
+
+ public:
+ INSTRUCTION_HEADER(RegExpPrototypeOptimizable)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MRegExpInstanceOptimizable
+ : public MBinaryInstruction,
+ public MixPolicy<ObjectPolicy<0>, ObjectPolicy<1> >::Data
+{
+ explicit MRegExpInstanceOptimizable(MDefinition* object, MDefinition* proto)
+ : MBinaryInstruction(object, proto)
+ {
+ setResultType(MIRType::Boolean);
+ }
+
+ public:
+ INSTRUCTION_HEADER(RegExpInstanceOptimizable)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, proto))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MGetFirstDollarIndex
+ : public MUnaryInstruction,
+ public StringPolicy<0>::Data
+{
+ explicit MGetFirstDollarIndex(MDefinition* str)
+ : MUnaryInstruction(str)
+ {
+ setResultType(MIRType::Int32);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(GetFirstDollarIndex)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, str))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+};
+
+class MStringReplace
+ : public MTernaryInstruction,
+ public Mix3Policy<StringPolicy<0>, StringPolicy<1>, StringPolicy<2> >::Data
+{
+ private:
+
+ bool isFlatReplacement_;
+
+ MStringReplace(MDefinition* string, MDefinition* pattern, MDefinition* replacement)
+ : MTernaryInstruction(string, pattern, replacement), isFlatReplacement_(false)
+ {
+ setMovable();
+ setResultType(MIRType::String);
+ }
+
+ public:
+ INSTRUCTION_HEADER(StringReplace)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, string), (1, pattern), (2, replacement))
+
+ void setFlatReplacement() {
+ MOZ_ASSERT(!isFlatReplacement_);
+ isFlatReplacement_ = true;
+ }
+
+ bool isFlatReplacement() const {
+ return isFlatReplacement_;
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isStringReplace())
+ return false;
+ if (isFlatReplacement_ != ins->toStringReplace()->isFlatReplacement())
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ if (isFlatReplacement_) {
+ MOZ_ASSERT(!pattern()->isRegExp());
+ return true;
+ }
+ return false;
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+class MSubstr
+ : public MTernaryInstruction,
+ public Mix3Policy<StringPolicy<0>, IntPolicy<1>, IntPolicy<2>>::Data
+{
+ private:
+
+ MSubstr(MDefinition* string, MDefinition* begin, MDefinition* length)
+ : MTernaryInstruction(string, begin, length)
+ {
+ setResultType(MIRType::String);
+ }
+
+ public:
+ INSTRUCTION_HEADER(Substr)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, string), (1, begin), (2, length))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+struct LambdaFunctionInfo
+{
+ // The functions used in lambdas are the canonical original function in
+ // the script, and are immutable except for delazification. Record this
+ // information while still on the main thread to avoid races.
+ CompilerFunction fun;
+ uint16_t flags;
+ uint16_t nargs;
+ gc::Cell* scriptOrLazyScript;
+ bool singletonType;
+ bool useSingletonForClone;
+
+ explicit LambdaFunctionInfo(JSFunction* fun)
+ : fun(fun), flags(fun->flags()), nargs(fun->nargs()),
+ scriptOrLazyScript(fun->hasScript()
+ ? (gc::Cell*) fun->nonLazyScript()
+ : (gc::Cell*) fun->lazyScript()),
+ singletonType(fun->isSingleton()),
+ useSingletonForClone(ObjectGroup::useSingletonForClone(fun))
+ {}
+
+ bool appendRoots(MRootList& roots) const {
+ if (!roots.append(fun))
+ return false;
+ if (fun->hasScript())
+ return roots.append(fun->nonLazyScript());
+ return roots.append(fun->lazyScript());
+ }
+
+ private:
+ LambdaFunctionInfo(const LambdaFunctionInfo&) = delete;
+ void operator=(const LambdaFunctionInfo&) = delete;
+};
+
+class MLambda
+ : public MBinaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ const LambdaFunctionInfo info_;
+
+ MLambda(CompilerConstraintList* constraints, MDefinition* envChain, MConstant* cst)
+ : MBinaryInstruction(envChain, cst), info_(&cst->toObject().as<JSFunction>())
+ {
+ setResultType(MIRType::Object);
+ if (!info().fun->isSingleton() && !ObjectGroup::useSingletonForClone(info().fun))
+ setResultTypeSet(MakeSingletonTypeSet(constraints, info().fun));
+ }
+
+ public:
+ INSTRUCTION_HEADER(Lambda)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, environmentChain))
+
+ MConstant* functionOperand() const {
+ return getOperand(1)->toConstant();
+ }
+ const LambdaFunctionInfo& info() const {
+ return info_;
+ }
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return info_.appendRoots(roots);
+ }
+};
+
+class MLambdaArrow
+ : public MBinaryInstruction,
+ public MixPolicy<ObjectPolicy<0>, BoxPolicy<1>>::Data
+{
+ const LambdaFunctionInfo info_;
+
+ MLambdaArrow(CompilerConstraintList* constraints, MDefinition* envChain,
+ MDefinition* newTarget_, JSFunction* fun)
+ : MBinaryInstruction(envChain, newTarget_), info_(fun)
+ {
+ setResultType(MIRType::Object);
+ MOZ_ASSERT(!ObjectGroup::useSingletonForClone(fun));
+ if (!fun->isSingleton())
+ setResultTypeSet(MakeSingletonTypeSet(constraints, fun));
+ }
+
+ public:
+ INSTRUCTION_HEADER(LambdaArrow)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, environmentChain), (1, newTargetDef))
+
+ const LambdaFunctionInfo& info() const {
+ return info_;
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return info_.appendRoots(roots);
+ }
+};
+
+// Returns obj->slots.
+class MSlots
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MSlots(MDefinition* object)
+ : MUnaryInstruction(object)
+ {
+ setResultType(MIRType::Slots);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(Slots)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+
+ ALLOW_CLONE(MSlots)
+};
+
+// Returns obj->elements.
+class MElements
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ bool unboxed_;
+
+ explicit MElements(MDefinition* object, bool unboxed = false)
+ : MUnaryInstruction(object), unboxed_(unboxed)
+ {
+ setResultType(MIRType::Elements);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(Elements)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool unboxed() const {
+ return unboxed_;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins) &&
+ ins->toElements()->unboxed() == unboxed();
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+
+ ALLOW_CLONE(MElements)
+};
+
+// A constant value for some object's typed array elements.
+class MConstantElements : public MNullaryInstruction
+{
+ SharedMem<void*> value_;
+
+ protected:
+ explicit MConstantElements(SharedMem<void*> v)
+ : value_(v)
+ {
+ setResultType(MIRType::Elements);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(ConstantElements)
+ TRIVIAL_NEW_WRAPPERS
+
+ SharedMem<void*> value() const {
+ return value_;
+ }
+
+ void printOpcode(GenericPrinter& out) const override;
+
+ HashNumber valueHash() const override {
+ return (HashNumber)(size_t) value_.asValue();
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return ins->isConstantElements() && ins->toConstantElements()->value() == value();
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ ALLOW_CLONE(MConstantElements)
+};
+
+// Passes through an object's elements, after ensuring it is entirely doubles.
+class MConvertElementsToDoubles
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ explicit MConvertElementsToDoubles(MDefinition* elements)
+ : MUnaryInstruction(elements)
+ {
+ setGuard();
+ setMovable();
+ setResultType(MIRType::Elements);
+ }
+
+ public:
+ INSTRUCTION_HEADER(ConvertElementsToDoubles)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ // This instruction can read and write to the elements' contents.
+ // However, it is alright to hoist this from loops which explicitly
+ // read or write to the elements: such reads and writes will use double
+ // values and can be reordered freely wrt this conversion, except that
+ // definite double loads must follow the conversion. The latter
+ // property is ensured by chaining this instruction with the elements
+ // themselves, in the same manner as MBoundsCheck.
+ return AliasSet::None();
+ }
+};
+
+// If |elements| has the CONVERT_DOUBLE_ELEMENTS flag, convert value to
+// double. Else return the original value.
+class MMaybeToDoubleElement
+ : public MBinaryInstruction,
+ public IntPolicy<1>::Data
+{
+ MMaybeToDoubleElement(MDefinition* elements, MDefinition* value)
+ : MBinaryInstruction(elements, value)
+ {
+ MOZ_ASSERT(elements->type() == MIRType::Elements);
+ setMovable();
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(MaybeToDoubleElement)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements), (1, value))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+};
+
+// Passes through an object, after ensuring its elements are not copy on write.
+class MMaybeCopyElementsForWrite
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ bool checkNative_;
+
+ explicit MMaybeCopyElementsForWrite(MDefinition* object, bool checkNative)
+ : MUnaryInstruction(object), checkNative_(checkNative)
+ {
+ setGuard();
+ setMovable();
+ setResultType(MIRType::Object);
+ setResultTypeSet(object->resultTypeSet());
+ }
+
+ public:
+ INSTRUCTION_HEADER(MaybeCopyElementsForWrite)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool checkNative() const {
+ return checkNative_;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins) &&
+ checkNative() == ins->toMaybeCopyElementsForWrite()->checkNative();
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::ObjectFields);
+ }
+#ifdef DEBUG
+ bool needsResumePoint() const override {
+ // This instruction is idempotent and does not change observable
+ // behavior, so does not need its own resume point.
+ return false;
+ }
+#endif
+
+};
+
+// Load the initialized length from an elements header.
+class MInitializedLength
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ explicit MInitializedLength(MDefinition* elements)
+ : MUnaryInstruction(elements)
+ {
+ setResultType(MIRType::Int32);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(InitializedLength)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+
+ void computeRange(TempAllocator& alloc) override;
+
+ ALLOW_CLONE(MInitializedLength)
+};
+
+// Store to the initialized length in an elements header. Note the input is an
+// *index*, one less than the desired length.
+class MSetInitializedLength
+ : public MAryInstruction<2>,
+ public NoTypePolicy::Data
+{
+ MSetInitializedLength(MDefinition* elements, MDefinition* index) {
+ initOperand(0, elements);
+ initOperand(1, index);
+ }
+
+ public:
+ INSTRUCTION_HEADER(SetInitializedLength)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements), (1, index))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::ObjectFields);
+ }
+
+ ALLOW_CLONE(MSetInitializedLength)
+};
+
+// Load the length from an unboxed array.
+class MUnboxedArrayLength
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MUnboxedArrayLength(MDefinition* object)
+ : MUnaryInstruction(object)
+ {
+ setResultType(MIRType::Int32);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(UnboxedArrayLength)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+
+ ALLOW_CLONE(MUnboxedArrayLength)
+};
+
+// Load the initialized length from an unboxed array.
+class MUnboxedArrayInitializedLength
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MUnboxedArrayInitializedLength(MDefinition* object)
+ : MUnaryInstruction(object)
+ {
+ setResultType(MIRType::Int32);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(UnboxedArrayInitializedLength)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+
+ ALLOW_CLONE(MUnboxedArrayInitializedLength)
+};
+
+// Increment the initialized length of an unboxed array object.
+class MIncrementUnboxedArrayInitializedLength
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MIncrementUnboxedArrayInitializedLength(MDefinition* obj)
+ : MUnaryInstruction(obj)
+ {}
+
+ public:
+ INSTRUCTION_HEADER(IncrementUnboxedArrayInitializedLength)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::ObjectFields);
+ }
+
+ ALLOW_CLONE(MIncrementUnboxedArrayInitializedLength)
+};
+
+// Set the initialized length of an unboxed array object.
+class MSetUnboxedArrayInitializedLength
+ : public MBinaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MSetUnboxedArrayInitializedLength(MDefinition* obj, MDefinition* length)
+ : MBinaryInstruction(obj, length)
+ {}
+
+ public:
+ INSTRUCTION_HEADER(SetUnboxedArrayInitializedLength)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, length))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::ObjectFields);
+ }
+
+ ALLOW_CLONE(MSetUnboxedArrayInitializedLength)
+};
+
+// Load the array length from an elements header.
+class MArrayLength
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ explicit MArrayLength(MDefinition* elements)
+ : MUnaryInstruction(elements)
+ {
+ setResultType(MIRType::Int32);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(ArrayLength)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+
+ void computeRange(TempAllocator& alloc) override;
+
+ ALLOW_CLONE(MArrayLength)
+};
+
+// Store to the length in an elements header. Note the input is an *index*, one
+// less than the desired length.
+class MSetArrayLength
+ : public MAryInstruction<2>,
+ public NoTypePolicy::Data
+{
+ MSetArrayLength(MDefinition* elements, MDefinition* index) {
+ initOperand(0, elements);
+ initOperand(1, index);
+ }
+
+ public:
+ INSTRUCTION_HEADER(SetArrayLength)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements), (1, index))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::ObjectFields);
+ }
+};
+
+class MGetNextEntryForIterator
+ : public MBinaryInstruction,
+ public MixPolicy<ObjectPolicy<0>, ObjectPolicy<1> >::Data
+{
+ public:
+ enum Mode {
+ Map,
+ Set
+ };
+
+ private:
+ Mode mode_;
+
+ explicit MGetNextEntryForIterator(MDefinition* iter, MDefinition* result, Mode mode)
+ : MBinaryInstruction(iter, result), mode_(mode)
+ {
+ setResultType(MIRType::Boolean);
+ }
+
+ public:
+ INSTRUCTION_HEADER(GetNextEntryForIterator)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, iter), (1, result))
+
+ Mode mode() const {
+ return mode_;
+ }
+};
+
+// Read the length of a typed array.
+class MTypedArrayLength
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MTypedArrayLength(MDefinition* obj)
+ : MUnaryInstruction(obj)
+ {
+ setResultType(MIRType::Int32);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(TypedArrayLength)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::TypedArrayLength);
+ }
+
+ void computeRange(TempAllocator& alloc) override;
+};
+
+// Load a typed array's elements vector.
+class MTypedArrayElements
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MTypedArrayElements(MDefinition* object)
+ : MUnaryInstruction(object)
+ {
+ setResultType(MIRType::Elements);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(TypedArrayElements)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+
+ ALLOW_CLONE(MTypedArrayElements)
+};
+
+class MSetDisjointTypedElements
+ : public MTernaryInstruction,
+ public NoTypePolicy::Data
+{
+ explicit MSetDisjointTypedElements(MDefinition* target, MDefinition* targetOffset,
+ MDefinition* source)
+ : MTernaryInstruction(target, targetOffset, source)
+ {
+ MOZ_ASSERT(target->type() == MIRType::Object);
+ MOZ_ASSERT(targetOffset->type() == MIRType::Int32);
+ MOZ_ASSERT(source->type() == MIRType::Object);
+ setResultType(MIRType::None);
+ }
+
+ public:
+ INSTRUCTION_HEADER(SetDisjointTypedElements)
+ NAMED_OPERANDS((0, target), (1, targetOffset), (2, source))
+
+ static MSetDisjointTypedElements*
+ New(TempAllocator& alloc, MDefinition* target, MDefinition* targetOffset,
+ MDefinition* source)
+ {
+ return new(alloc) MSetDisjointTypedElements(target, targetOffset, source);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::UnboxedElement);
+ }
+
+ ALLOW_CLONE(MSetDisjointTypedElements)
+};
+
+// Load a binary data object's "elements", which is just its opaque
+// binary data space. Eventually this should probably be
+// unified with `MTypedArrayElements`.
+class MTypedObjectElements
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ bool definitelyOutline_;
+
+ private:
+ explicit MTypedObjectElements(MDefinition* object, bool definitelyOutline)
+ : MUnaryInstruction(object),
+ definitelyOutline_(definitelyOutline)
+ {
+ setResultType(MIRType::Elements);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(TypedObjectElements)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool definitelyOutline() const {
+ return definitelyOutline_;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isTypedObjectElements())
+ return false;
+ const MTypedObjectElements* other = ins->toTypedObjectElements();
+ if (other->definitelyOutline() != definitelyOutline())
+ return false;
+ return congruentIfOperandsEqual(other);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+};
+
+// Inlined version of the js::SetTypedObjectOffset() intrinsic.
+class MSetTypedObjectOffset
+ : public MBinaryInstruction,
+ public NoTypePolicy::Data
+{
+ private:
+ MSetTypedObjectOffset(MDefinition* object, MDefinition* offset)
+ : MBinaryInstruction(object, offset)
+ {
+ MOZ_ASSERT(object->type() == MIRType::Object);
+ MOZ_ASSERT(offset->type() == MIRType::Int32);
+ setResultType(MIRType::None);
+ }
+
+ public:
+ INSTRUCTION_HEADER(SetTypedObjectOffset)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, offset))
+
+ AliasSet getAliasSet() const override {
+ // This affects the result of MTypedObjectElements,
+ // which is described as a load of ObjectFields.
+ return AliasSet::Store(AliasSet::ObjectFields);
+ }
+};
+
+class MKeepAliveObject
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MKeepAliveObject(MDefinition* object)
+ : MUnaryInstruction(object)
+ {
+ setResultType(MIRType::None);
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(KeepAliveObject)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+};
+
+// Perform !-operation
+class MNot
+ : public MUnaryInstruction,
+ public TestPolicy::Data
+{
+ bool operandMightEmulateUndefined_;
+ bool operandIsNeverNaN_;
+
+ explicit MNot(MDefinition* input, CompilerConstraintList* constraints = nullptr)
+ : MUnaryInstruction(input),
+ operandMightEmulateUndefined_(true),
+ operandIsNeverNaN_(false)
+ {
+ setResultType(MIRType::Boolean);
+ setMovable();
+ if (constraints)
+ cacheOperandMightEmulateUndefined(constraints);
+ }
+
+ void cacheOperandMightEmulateUndefined(CompilerConstraintList* constraints);
+
+ public:
+ static MNot* NewInt32(TempAllocator& alloc, MDefinition* input) {
+ MOZ_ASSERT(input->type() == MIRType::Int32 || input->type() == MIRType::Int64);
+ auto* ins = new(alloc) MNot(input);
+ ins->setResultType(MIRType::Int32);
+ return ins;
+ }
+
+ INSTRUCTION_HEADER(Not)
+ TRIVIAL_NEW_WRAPPERS
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ void markNoOperandEmulatesUndefined() {
+ operandMightEmulateUndefined_ = false;
+ }
+ bool operandMightEmulateUndefined() const {
+ return operandMightEmulateUndefined_;
+ }
+ bool operandIsNeverNaN() const {
+ return operandIsNeverNaN_;
+ }
+
+ virtual AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ void collectRangeInfoPreTrunc() override;
+
+ void trySpecializeFloat32(TempAllocator& alloc) override;
+ bool isFloat32Commutative() const override { return true; }
+#ifdef DEBUG
+ bool isConsistentFloat32Use(MUse* use) const override {
+ return true;
+ }
+#endif
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+};
+
+// Bailout if index + minimum < 0 or index + maximum >= length. The length used
+// in a bounds check must not be negative, or the wrong result may be computed
+// (unsigned comparisons may be used).
+class MBoundsCheck
+ : public MBinaryInstruction,
+ public MixPolicy<IntPolicy<0>, IntPolicy<1>>::Data
+{
+ // Range over which to perform the bounds check, may be modified by GVN.
+ int32_t minimum_;
+ int32_t maximum_;
+ bool fallible_;
+
+ MBoundsCheck(MDefinition* index, MDefinition* length)
+ : MBinaryInstruction(index, length), minimum_(0), maximum_(0), fallible_(true)
+ {
+ setGuard();
+ setMovable();
+ MOZ_ASSERT(index->type() == MIRType::Int32);
+ MOZ_ASSERT(length->type() == MIRType::Int32);
+
+ // Returns the checked index.
+ setResultType(MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(BoundsCheck)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, index), (1, length))
+
+ int32_t minimum() const {
+ return minimum_;
+ }
+ void setMinimum(int32_t n) {
+ MOZ_ASSERT(fallible_);
+ minimum_ = n;
+ }
+ int32_t maximum() const {
+ return maximum_;
+ }
+ void setMaximum(int32_t n) {
+ MOZ_ASSERT(fallible_);
+ maximum_ = n;
+ }
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isBoundsCheck())
+ return false;
+ const MBoundsCheck* other = ins->toBoundsCheck();
+ if (minimum() != other->minimum() || maximum() != other->maximum())
+ return false;
+ if (fallible() != other->fallible())
+ return false;
+ return congruentIfOperandsEqual(other);
+ }
+ virtual AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ void computeRange(TempAllocator& alloc) override;
+ bool fallible() const {
+ return fallible_;
+ }
+ void collectRangeInfoPreTrunc() override;
+
+ ALLOW_CLONE(MBoundsCheck)
+};
+
+// Bailout if index < minimum.
+class MBoundsCheckLower
+ : public MUnaryInstruction,
+ public IntPolicy<0>::Data
+{
+ int32_t minimum_;
+ bool fallible_;
+
+ explicit MBoundsCheckLower(MDefinition* index)
+ : MUnaryInstruction(index), minimum_(0), fallible_(true)
+ {
+ setGuard();
+ setMovable();
+ MOZ_ASSERT(index->type() == MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(BoundsCheckLower)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, index))
+
+ int32_t minimum() const {
+ return minimum_;
+ }
+ void setMinimum(int32_t n) {
+ minimum_ = n;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool fallible() const {
+ return fallible_;
+ }
+ void collectRangeInfoPreTrunc() override;
+};
+
+// Instructions which access an object's elements can either do so on a
+// definition accessing that elements pointer, or on the object itself, if its
+// elements are inline. In the latter case there must be an offset associated
+// with the access.
+static inline bool
+IsValidElementsType(MDefinition* elements, int32_t offsetAdjustment)
+{
+ return elements->type() == MIRType::Elements ||
+ (elements->type() == MIRType::Object && offsetAdjustment != 0);
+}
+
+// Load a value from a dense array's element vector and does a hole check if the
+// array is not known to be packed.
+class MLoadElement
+ : public MBinaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ bool needsHoleCheck_;
+ bool loadDoubles_;
+ int32_t offsetAdjustment_;
+
+ MLoadElement(MDefinition* elements, MDefinition* index,
+ bool needsHoleCheck, bool loadDoubles, int32_t offsetAdjustment = 0)
+ : MBinaryInstruction(elements, index),
+ needsHoleCheck_(needsHoleCheck),
+ loadDoubles_(loadDoubles),
+ offsetAdjustment_(offsetAdjustment)
+ {
+ if (needsHoleCheck) {
+ // Uses may be optimized away based on this instruction's result
+ // type. This means it's invalid to DCE this instruction, as we
+ // have to invalidate when we read a hole.
+ setGuard();
+ }
+ setResultType(MIRType::Value);
+ setMovable();
+ MOZ_ASSERT(IsValidElementsType(elements, offsetAdjustment));
+ MOZ_ASSERT(index->type() == MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(LoadElement)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements), (1, index))
+
+ bool needsHoleCheck() const {
+ return needsHoleCheck_;
+ }
+ bool loadDoubles() const {
+ return loadDoubles_;
+ }
+ int32_t offsetAdjustment() const {
+ return offsetAdjustment_;
+ }
+ bool fallible() const {
+ return needsHoleCheck();
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isLoadElement())
+ return false;
+ const MLoadElement* other = ins->toLoadElement();
+ if (needsHoleCheck() != other->needsHoleCheck())
+ return false;
+ if (loadDoubles() != other->loadDoubles())
+ return false;
+ if (offsetAdjustment() != other->offsetAdjustment())
+ return false;
+ return congruentIfOperandsEqual(other);
+ }
+ AliasType mightAlias(const MDefinition* store) const override;
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::Element);
+ }
+
+ ALLOW_CLONE(MLoadElement)
+};
+
+// Load a value from the elements vector for a dense native or unboxed array.
+// If the index is out-of-bounds, or the indexed slot has a hole, undefined is
+// returned instead.
+class MLoadElementHole
+ : public MTernaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ // Unboxed element type, JSVAL_TYPE_MAGIC for dense native elements.
+ JSValueType unboxedType_;
+
+ bool needsNegativeIntCheck_;
+ bool needsHoleCheck_;
+
+ MLoadElementHole(MDefinition* elements, MDefinition* index, MDefinition* initLength,
+ JSValueType unboxedType, bool needsHoleCheck)
+ : MTernaryInstruction(elements, index, initLength),
+ unboxedType_(unboxedType),
+ needsNegativeIntCheck_(true),
+ needsHoleCheck_(needsHoleCheck)
+ {
+ setResultType(MIRType::Value);
+ setMovable();
+
+ // Set the guard flag to make sure we bail when we see a negative
+ // index. We can clear this flag (and needsNegativeIntCheck_) in
+ // collectRangeInfoPreTrunc.
+ setGuard();
+
+ MOZ_ASSERT(elements->type() == MIRType::Elements);
+ MOZ_ASSERT(index->type() == MIRType::Int32);
+ MOZ_ASSERT(initLength->type() == MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(LoadElementHole)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements), (1, index), (2, initLength))
+
+ JSValueType unboxedType() const {
+ return unboxedType_;
+ }
+ bool needsNegativeIntCheck() const {
+ return needsNegativeIntCheck_;
+ }
+ bool needsHoleCheck() const {
+ return needsHoleCheck_;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isLoadElementHole())
+ return false;
+ const MLoadElementHole* other = ins->toLoadElementHole();
+ if (unboxedType() != other->unboxedType())
+ return false;
+ if (needsHoleCheck() != other->needsHoleCheck())
+ return false;
+ if (needsNegativeIntCheck() != other->needsNegativeIntCheck())
+ return false;
+ return congruentIfOperandsEqual(other);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::BoxedOrUnboxedElements(unboxedType()));
+ }
+ void collectRangeInfoPreTrunc() override;
+
+ ALLOW_CLONE(MLoadElementHole)
+};
+
+class MLoadUnboxedObjectOrNull
+ : public MBinaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ public:
+ enum NullBehavior {
+ HandleNull,
+ BailOnNull,
+ NullNotPossible
+ };
+
+ private:
+ NullBehavior nullBehavior_;
+ int32_t offsetAdjustment_;
+
+ MLoadUnboxedObjectOrNull(MDefinition* elements, MDefinition* index,
+ NullBehavior nullBehavior, int32_t offsetAdjustment)
+ : MBinaryInstruction(elements, index),
+ nullBehavior_(nullBehavior),
+ offsetAdjustment_(offsetAdjustment)
+ {
+ if (nullBehavior == BailOnNull) {
+ // Don't eliminate loads which bail out on a null pointer, for the
+ // same reason as MLoadElement.
+ setGuard();
+ }
+ setResultType(nullBehavior == HandleNull ? MIRType::Value : MIRType::Object);
+ setMovable();
+ MOZ_ASSERT(IsValidElementsType(elements, offsetAdjustment));
+ MOZ_ASSERT(index->type() == MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(LoadUnboxedObjectOrNull)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements), (1, index))
+
+ NullBehavior nullBehavior() const {
+ return nullBehavior_;
+ }
+ int32_t offsetAdjustment() const {
+ return offsetAdjustment_;
+ }
+ bool fallible() const {
+ return nullBehavior() == BailOnNull;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isLoadUnboxedObjectOrNull())
+ return false;
+ const MLoadUnboxedObjectOrNull* other = ins->toLoadUnboxedObjectOrNull();
+ if (nullBehavior() != other->nullBehavior())
+ return false;
+ if (offsetAdjustment() != other->offsetAdjustment())
+ return false;
+ return congruentIfOperandsEqual(other);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::UnboxedElement);
+ }
+ AliasType mightAlias(const MDefinition* store) const override;
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ ALLOW_CLONE(MLoadUnboxedObjectOrNull)
+};
+
+class MLoadUnboxedString
+ : public MBinaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ int32_t offsetAdjustment_;
+
+ MLoadUnboxedString(MDefinition* elements, MDefinition* index, int32_t offsetAdjustment = 0)
+ : MBinaryInstruction(elements, index),
+ offsetAdjustment_(offsetAdjustment)
+ {
+ setResultType(MIRType::String);
+ setMovable();
+ MOZ_ASSERT(IsValidElementsType(elements, offsetAdjustment));
+ MOZ_ASSERT(index->type() == MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(LoadUnboxedString)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements), (1, index))
+
+ int32_t offsetAdjustment() const {
+ return offsetAdjustment_;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isLoadUnboxedString())
+ return false;
+ const MLoadUnboxedString* other = ins->toLoadUnboxedString();
+ if (offsetAdjustment() != other->offsetAdjustment())
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::UnboxedElement);
+ }
+
+ ALLOW_CLONE(MLoadUnboxedString)
+};
+
+class MStoreElementCommon
+{
+ MIRType elementType_;
+ bool needsBarrier_;
+
+ protected:
+ MStoreElementCommon()
+ : elementType_(MIRType::Value),
+ needsBarrier_(false)
+ { }
+
+ public:
+ MIRType elementType() const {
+ return elementType_;
+ }
+ void setElementType(MIRType elementType) {
+ MOZ_ASSERT(elementType != MIRType::None);
+ elementType_ = elementType;
+ }
+ bool needsBarrier() const {
+ return needsBarrier_;
+ }
+ void setNeedsBarrier() {
+ needsBarrier_ = true;
+ }
+};
+
+// Store a value to a dense array slots vector.
+class MStoreElement
+ : public MAryInstruction<3>,
+ public MStoreElementCommon,
+ public MixPolicy<SingleObjectPolicy, NoFloatPolicy<2> >::Data
+{
+ bool needsHoleCheck_;
+ int32_t offsetAdjustment_;
+
+ MStoreElement(MDefinition* elements, MDefinition* index, MDefinition* value,
+ bool needsHoleCheck, int32_t offsetAdjustment = 0)
+ {
+ initOperand(0, elements);
+ initOperand(1, index);
+ initOperand(2, value);
+ needsHoleCheck_ = needsHoleCheck;
+ offsetAdjustment_ = offsetAdjustment;
+ MOZ_ASSERT(IsValidElementsType(elements, offsetAdjustment));
+ MOZ_ASSERT(index->type() == MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(StoreElement)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements), (1, index), (2, value))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::Element);
+ }
+ bool needsHoleCheck() const {
+ return needsHoleCheck_;
+ }
+ int32_t offsetAdjustment() const {
+ return offsetAdjustment_;
+ }
+ bool fallible() const {
+ return needsHoleCheck();
+ }
+
+ ALLOW_CLONE(MStoreElement)
+};
+
+// Like MStoreElement, but supports indexes >= initialized length, and can
+// handle unboxed arrays. The downside is that we cannot hoist the elements
+// vector and bounds check, since this instruction may update the (initialized)
+// length and reallocate the elements vector.
+class MStoreElementHole
+ : public MAryInstruction<4>,
+ public MStoreElementCommon,
+ public MixPolicy<SingleObjectPolicy, NoFloatPolicy<3> >::Data
+{
+ JSValueType unboxedType_;
+
+ MStoreElementHole(MDefinition* object, MDefinition* elements,
+ MDefinition* index, MDefinition* value, JSValueType unboxedType)
+ : unboxedType_(unboxedType)
+ {
+ initOperand(0, object);
+ initOperand(1, elements);
+ initOperand(2, index);
+ initOperand(3, value);
+ MOZ_ASSERT(elements->type() == MIRType::Elements);
+ MOZ_ASSERT(index->type() == MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(StoreElementHole)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, elements), (2, index), (3, value))
+
+ JSValueType unboxedType() const {
+ return unboxedType_;
+ }
+ AliasSet getAliasSet() const override {
+ // StoreElementHole can update the initialized length, the array length
+ // or reallocate obj->elements.
+ return AliasSet::Store(AliasSet::ObjectFields |
+ AliasSet::BoxedOrUnboxedElements(unboxedType()));
+ }
+
+ ALLOW_CLONE(MStoreElementHole)
+};
+
+// Try to store a value to a dense array slots vector. May fail due to the object being frozen.
+// Cannot be used on an object that has extra indexed properties.
+class MFallibleStoreElement
+ : public MAryInstruction<4>,
+ public MStoreElementCommon,
+ public MixPolicy<SingleObjectPolicy, NoFloatPolicy<3> >::Data
+{
+ JSValueType unboxedType_;
+ bool strict_;
+
+ MFallibleStoreElement(MDefinition* object, MDefinition* elements,
+ MDefinition* index, MDefinition* value,
+ JSValueType unboxedType, bool strict)
+ : unboxedType_(unboxedType)
+ {
+ initOperand(0, object);
+ initOperand(1, elements);
+ initOperand(2, index);
+ initOperand(3, value);
+ strict_ = strict;
+ MOZ_ASSERT(elements->type() == MIRType::Elements);
+ MOZ_ASSERT(index->type() == MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(FallibleStoreElement)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, elements), (2, index), (3, value))
+
+ JSValueType unboxedType() const {
+ return unboxedType_;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::ObjectFields |
+ AliasSet::BoxedOrUnboxedElements(unboxedType()));
+ }
+ bool strict() const {
+ return strict_;
+ }
+
+ ALLOW_CLONE(MFallibleStoreElement)
+};
+
+
+// Store an unboxed object or null pointer to a v\ector.
+class MStoreUnboxedObjectOrNull
+ : public MAryInstruction<4>,
+ public StoreUnboxedObjectOrNullPolicy::Data
+{
+ int32_t offsetAdjustment_;
+ bool preBarrier_;
+
+ MStoreUnboxedObjectOrNull(MDefinition* elements, MDefinition* index,
+ MDefinition* value, MDefinition* typedObj,
+ int32_t offsetAdjustment = 0, bool preBarrier = true)
+ : offsetAdjustment_(offsetAdjustment), preBarrier_(preBarrier)
+ {
+ initOperand(0, elements);
+ initOperand(1, index);
+ initOperand(2, value);
+ initOperand(3, typedObj);
+ MOZ_ASSERT(IsValidElementsType(elements, offsetAdjustment));
+ MOZ_ASSERT(index->type() == MIRType::Int32);
+ MOZ_ASSERT(typedObj->type() == MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(StoreUnboxedObjectOrNull)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements), (1, index), (2, value), (3, typedObj))
+
+ int32_t offsetAdjustment() const {
+ return offsetAdjustment_;
+ }
+ bool preBarrier() const {
+ return preBarrier_;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::UnboxedElement);
+ }
+
+ // For StoreUnboxedObjectOrNullPolicy.
+ void setValue(MDefinition* def) {
+ replaceOperand(2, def);
+ }
+
+ ALLOW_CLONE(MStoreUnboxedObjectOrNull)
+};
+
+// Store an unboxed object or null pointer to a vector.
+class MStoreUnboxedString
+ : public MAryInstruction<3>,
+ public MixPolicy<SingleObjectPolicy, ConvertToStringPolicy<2> >::Data
+{
+ int32_t offsetAdjustment_;
+ bool preBarrier_;
+
+ MStoreUnboxedString(MDefinition* elements, MDefinition* index, MDefinition* value,
+ int32_t offsetAdjustment = 0, bool preBarrier = true)
+ : offsetAdjustment_(offsetAdjustment), preBarrier_(preBarrier)
+ {
+ initOperand(0, elements);
+ initOperand(1, index);
+ initOperand(2, value);
+ MOZ_ASSERT(IsValidElementsType(elements, offsetAdjustment));
+ MOZ_ASSERT(index->type() == MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(StoreUnboxedString)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements), (1, index), (2, value))
+
+ int32_t offsetAdjustment() const {
+ return offsetAdjustment_;
+ }
+ bool preBarrier() const {
+ return preBarrier_;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::UnboxedElement);
+ }
+
+ ALLOW_CLONE(MStoreUnboxedString)
+};
+
+// Passes through an object, after ensuring it is converted from an unboxed
+// object to a native representation.
+class MConvertUnboxedObjectToNative
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ CompilerObjectGroup group_;
+
+ explicit MConvertUnboxedObjectToNative(MDefinition* obj, ObjectGroup* group)
+ : MUnaryInstruction(obj),
+ group_(group)
+ {
+ setGuard();
+ setMovable();
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(ConvertUnboxedObjectToNative)
+ NAMED_OPERANDS((0, object))
+
+ static MConvertUnboxedObjectToNative* New(TempAllocator& alloc, MDefinition* obj,
+ ObjectGroup* group);
+
+ ObjectGroup* group() const {
+ return group_;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!congruentIfOperandsEqual(ins))
+ return false;
+ return ins->toConvertUnboxedObjectToNative()->group() == group();
+ }
+ AliasSet getAliasSet() const override {
+ // This instruction can read and write to all parts of the object, but
+ // is marked as non-effectful so it can be consolidated by LICM and GVN
+ // and avoid inhibiting other optimizations.
+ //
+ // This is valid to do because when unboxed objects might have a native
+ // group they can be converted to, we do not optimize accesses to the
+ // unboxed objects and do not guard on their group or shape (other than
+ // in this opcode).
+ //
+ // Later accesses can assume the object has a native representation
+ // and optimize accordingly. Those accesses cannot be reordered before
+ // this instruction, however. This is prevented by chaining this
+ // instruction with the object itself, in the same way as MBoundsCheck.
+ return AliasSet::None();
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(group_);
+ }
+};
+
+// Array.prototype.pop or Array.prototype.shift on a dense array.
+class MArrayPopShift
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ public:
+ enum Mode {
+ Pop,
+ Shift
+ };
+
+ private:
+ Mode mode_;
+ JSValueType unboxedType_;
+ bool needsHoleCheck_;
+ bool maybeUndefined_;
+
+ MArrayPopShift(MDefinition* object, Mode mode, JSValueType unboxedType,
+ bool needsHoleCheck, bool maybeUndefined)
+ : MUnaryInstruction(object), mode_(mode), unboxedType_(unboxedType),
+ needsHoleCheck_(needsHoleCheck), maybeUndefined_(maybeUndefined)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(ArrayPopShift)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool needsHoleCheck() const {
+ return needsHoleCheck_;
+ }
+ bool maybeUndefined() const {
+ return maybeUndefined_;
+ }
+ bool mode() const {
+ return mode_;
+ }
+ JSValueType unboxedType() const {
+ return unboxedType_;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::ObjectFields |
+ AliasSet::BoxedOrUnboxedElements(unboxedType()));
+ }
+
+ ALLOW_CLONE(MArrayPopShift)
+};
+
+// Array.prototype.push on a dense array. Returns the new array length.
+class MArrayPush
+ : public MBinaryInstruction,
+ public MixPolicy<SingleObjectPolicy, NoFloatPolicy<1> >::Data
+{
+ JSValueType unboxedType_;
+
+ MArrayPush(MDefinition* object, MDefinition* value, JSValueType unboxedType)
+ : MBinaryInstruction(object, value), unboxedType_(unboxedType)
+ {
+ setResultType(MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(ArrayPush)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, value))
+
+ JSValueType unboxedType() const {
+ return unboxedType_;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::ObjectFields |
+ AliasSet::BoxedOrUnboxedElements(unboxedType()));
+ }
+ void computeRange(TempAllocator& alloc) override;
+
+ ALLOW_CLONE(MArrayPush)
+};
+
+// Array.prototype.slice on a dense array.
+class MArraySlice
+ : public MTernaryInstruction,
+ public Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, IntPolicy<2>>::Data
+{
+ CompilerObject templateObj_;
+ gc::InitialHeap initialHeap_;
+ JSValueType unboxedType_;
+
+ MArraySlice(CompilerConstraintList* constraints, MDefinition* obj,
+ MDefinition* begin, MDefinition* end,
+ JSObject* templateObj, gc::InitialHeap initialHeap, JSValueType unboxedType)
+ : MTernaryInstruction(obj, begin, end),
+ templateObj_(templateObj),
+ initialHeap_(initialHeap),
+ unboxedType_(unboxedType)
+ {
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(ArraySlice)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, begin), (2, end))
+
+ JSObject* templateObj() const {
+ return templateObj_;
+ }
+
+ gc::InitialHeap initialHeap() const {
+ return initialHeap_;
+ }
+
+ JSValueType unboxedType() const {
+ return unboxedType_;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::BoxedOrUnboxedElements(unboxedType()) |
+ AliasSet::ObjectFields);
+ }
+ bool possiblyCalls() const override {
+ return true;
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(templateObj_);
+ }
+};
+
+class MArrayJoin
+ : public MBinaryInstruction,
+ public MixPolicy<ObjectPolicy<0>, StringPolicy<1> >::Data
+{
+ MArrayJoin(MDefinition* array, MDefinition* sep)
+ : MBinaryInstruction(array, sep)
+ {
+ setResultType(MIRType::String);
+ }
+ public:
+ INSTRUCTION_HEADER(ArrayJoin)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, array), (1, sep))
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+ virtual AliasSet getAliasSet() const override {
+ // Array.join might coerce the elements of the Array to strings. This
+ // coercion might cause the evaluation of the some JavaScript code.
+ return AliasSet::Store(AliasSet::Any);
+ }
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+};
+
+// All barriered operations - MCompareExchangeTypedArrayElement,
+// MExchangeTypedArrayElement, and MAtomicTypedArrayElementBinop, as
+// well as MLoadUnboxedScalar and MStoreUnboxedScalar when they are
+// marked as requiring a memory barrer - have the following
+// attributes:
+//
+// - Not movable
+// - Not removable
+// - Not congruent with any other instruction
+// - Effectful (they alias every TypedArray store)
+//
+// The intended effect of those constraints is to prevent all loads
+// and stores preceding the barriered operation from being moved to
+// after the barriered operation, and vice versa, and to prevent the
+// barriered operation from being removed or hoisted.
+
+enum MemoryBarrierRequirement
+{
+ DoesNotRequireMemoryBarrier,
+ DoesRequireMemoryBarrier
+};
+
+// Also see comments at MMemoryBarrierRequirement, above.
+
+// Load an unboxed scalar value from a typed array or other object.
+class MLoadUnboxedScalar
+ : public MBinaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ Scalar::Type storageType_;
+ Scalar::Type readType_;
+ unsigned numElems_; // used only for SIMD
+ bool requiresBarrier_;
+ int32_t offsetAdjustment_;
+ bool canonicalizeDoubles_;
+
+ MLoadUnboxedScalar(MDefinition* elements, MDefinition* index, Scalar::Type storageType,
+ MemoryBarrierRequirement requiresBarrier = DoesNotRequireMemoryBarrier,
+ int32_t offsetAdjustment = 0, bool canonicalizeDoubles = true)
+ : MBinaryInstruction(elements, index),
+ storageType_(storageType),
+ readType_(storageType),
+ numElems_(1),
+ requiresBarrier_(requiresBarrier == DoesRequireMemoryBarrier),
+ offsetAdjustment_(offsetAdjustment),
+ canonicalizeDoubles_(canonicalizeDoubles)
+ {
+ setResultType(MIRType::Value);
+ if (requiresBarrier_)
+ setGuard(); // Not removable or movable
+ else
+ setMovable();
+ MOZ_ASSERT(IsValidElementsType(elements, offsetAdjustment));
+ MOZ_ASSERT(index->type() == MIRType::Int32);
+ MOZ_ASSERT(storageType >= 0 && storageType < Scalar::MaxTypedArrayViewType);
+ }
+
+ public:
+ INSTRUCTION_HEADER(LoadUnboxedScalar)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements), (1, index))
+
+ void setSimdRead(Scalar::Type type, unsigned numElems) {
+ readType_ = type;
+ numElems_ = numElems;
+ }
+ unsigned numElems() const {
+ return numElems_;
+ }
+ Scalar::Type readType() const {
+ return readType_;
+ }
+
+ Scalar::Type storageType() const {
+ return storageType_;
+ }
+ bool fallible() const {
+ // Bailout if the result does not fit in an int32.
+ return readType_ == Scalar::Uint32 && type() == MIRType::Int32;
+ }
+ bool requiresMemoryBarrier() const {
+ return requiresBarrier_;
+ }
+ bool canonicalizeDoubles() const {
+ return canonicalizeDoubles_;
+ }
+ int32_t offsetAdjustment() const {
+ return offsetAdjustment_;
+ }
+ void setOffsetAdjustment(int32_t offsetAdjustment) {
+ offsetAdjustment_ = offsetAdjustment;
+ }
+ AliasSet getAliasSet() const override {
+ // When a barrier is needed make the instruction effectful by
+ // giving it a "store" effect.
+ if (requiresBarrier_)
+ return AliasSet::Store(AliasSet::UnboxedElement);
+ return AliasSet::Load(AliasSet::UnboxedElement);
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (requiresBarrier_)
+ return false;
+ if (!ins->isLoadUnboxedScalar())
+ return false;
+ const MLoadUnboxedScalar* other = ins->toLoadUnboxedScalar();
+ if (storageType_ != other->storageType_)
+ return false;
+ if (readType_ != other->readType_)
+ return false;
+ if (numElems_ != other->numElems_)
+ return false;
+ if (offsetAdjustment() != other->offsetAdjustment())
+ return false;
+ if (canonicalizeDoubles() != other->canonicalizeDoubles())
+ return false;
+ return congruentIfOperandsEqual(other);
+ }
+
+ void printOpcode(GenericPrinter& out) const override;
+
+ void computeRange(TempAllocator& alloc) override;
+
+ bool canProduceFloat32() const override { return storageType_ == Scalar::Float32; }
+
+ ALLOW_CLONE(MLoadUnboxedScalar)
+};
+
+// Load a value from a typed array. Out-of-bounds accesses are handled in-line.
+class MLoadTypedArrayElementHole
+ : public MBinaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ Scalar::Type arrayType_;
+ bool allowDouble_;
+
+ MLoadTypedArrayElementHole(MDefinition* object, MDefinition* index, Scalar::Type arrayType, bool allowDouble)
+ : MBinaryInstruction(object, index), arrayType_(arrayType), allowDouble_(allowDouble)
+ {
+ setResultType(MIRType::Value);
+ setMovable();
+ MOZ_ASSERT(index->type() == MIRType::Int32);
+ MOZ_ASSERT(arrayType >= 0 && arrayType < Scalar::MaxTypedArrayViewType);
+ }
+
+ public:
+ INSTRUCTION_HEADER(LoadTypedArrayElementHole)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, index))
+
+ Scalar::Type arrayType() const {
+ return arrayType_;
+ }
+ bool allowDouble() const {
+ return allowDouble_;
+ }
+ bool fallible() const {
+ return arrayType_ == Scalar::Uint32 && !allowDouble_;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isLoadTypedArrayElementHole())
+ return false;
+ const MLoadTypedArrayElementHole* other = ins->toLoadTypedArrayElementHole();
+ if (arrayType() != other->arrayType())
+ return false;
+ if (allowDouble() != other->allowDouble())
+ return false;
+ return congruentIfOperandsEqual(other);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::UnboxedElement);
+ }
+ bool canProduceFloat32() const override { return arrayType_ == Scalar::Float32; }
+
+ ALLOW_CLONE(MLoadTypedArrayElementHole)
+};
+
+// Load a value fallibly or infallibly from a statically known typed array.
+class MLoadTypedArrayElementStatic
+ : public MUnaryInstruction,
+ public ConvertToInt32Policy<0>::Data
+{
+ MLoadTypedArrayElementStatic(JSObject* someTypedArray, MDefinition* ptr,
+ int32_t offset = 0, bool needsBoundsCheck = true)
+ : MUnaryInstruction(ptr), someTypedArray_(someTypedArray), offset_(offset),
+ needsBoundsCheck_(needsBoundsCheck), fallible_(true)
+ {
+ int type = accessType();
+ if (type == Scalar::Float32)
+ setResultType(MIRType::Float32);
+ else if (type == Scalar::Float64)
+ setResultType(MIRType::Double);
+ else
+ setResultType(MIRType::Int32);
+ }
+
+ CompilerObject someTypedArray_;
+
+ // An offset to be encoded in the load instruction - taking advantage of the
+ // addressing modes. This is only non-zero when the access is proven to be
+ // within bounds.
+ int32_t offset_;
+ bool needsBoundsCheck_;
+ bool fallible_;
+
+ public:
+ INSTRUCTION_HEADER(LoadTypedArrayElementStatic)
+ TRIVIAL_NEW_WRAPPERS
+
+ Scalar::Type accessType() const {
+ return someTypedArray_->as<TypedArrayObject>().type();
+ }
+ SharedMem<void*> base() const;
+ size_t length() const;
+
+ MDefinition* ptr() const { return getOperand(0); }
+ int32_t offset() const { return offset_; }
+ void setOffset(int32_t offset) { offset_ = offset; }
+ bool congruentTo(const MDefinition* ins) const override;
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::UnboxedElement);
+ }
+
+ bool needsBoundsCheck() const { return needsBoundsCheck_; }
+ void setNeedsBoundsCheck(bool v) { needsBoundsCheck_ = v; }
+
+ bool fallible() const {
+ return fallible_;
+ }
+
+ void setInfallible() {
+ fallible_ = false;
+ }
+
+ void computeRange(TempAllocator& alloc) override;
+ bool needTruncation(TruncateKind kind) override;
+ bool canProduceFloat32() const override { return accessType() == Scalar::Float32; }
+ void collectRangeInfoPreTrunc() override;
+
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(someTypedArray_);
+ }
+};
+
+// Base class for MIR ops that write unboxed scalar values.
+class StoreUnboxedScalarBase
+{
+ Scalar::Type writeType_;
+
+ protected:
+ explicit StoreUnboxedScalarBase(Scalar::Type writeType)
+ : writeType_(writeType)
+ {
+ MOZ_ASSERT(isIntegerWrite() || isFloatWrite() || isSimdWrite());
+ }
+
+ public:
+ void setWriteType(Scalar::Type type) {
+ writeType_ = type;
+ }
+ Scalar::Type writeType() const {
+ return writeType_;
+ }
+ bool isByteWrite() const {
+ return writeType_ == Scalar::Int8 ||
+ writeType_ == Scalar::Uint8 ||
+ writeType_ == Scalar::Uint8Clamped;
+ }
+ bool isIntegerWrite() const {
+ return isByteWrite () ||
+ writeType_ == Scalar::Int16 ||
+ writeType_ == Scalar::Uint16 ||
+ writeType_ == Scalar::Int32 ||
+ writeType_ == Scalar::Uint32;
+ }
+ bool isFloatWrite() const {
+ return writeType_ == Scalar::Float32 ||
+ writeType_ == Scalar::Float64;
+ }
+ bool isSimdWrite() const {
+ return Scalar::isSimdType(writeType());
+ }
+};
+
+// Store an unboxed scalar value to a typed array or other object.
+class MStoreUnboxedScalar
+ : public MTernaryInstruction,
+ public StoreUnboxedScalarBase,
+ public StoreUnboxedScalarPolicy::Data
+{
+ public:
+ enum TruncateInputKind {
+ DontTruncateInput,
+ TruncateInput
+ };
+
+ private:
+ Scalar::Type storageType_;
+
+ // Whether this store truncates out of range inputs, for use by range analysis.
+ TruncateInputKind truncateInput_;
+
+ bool requiresBarrier_;
+ int32_t offsetAdjustment_;
+ unsigned numElems_; // used only for SIMD
+
+ MStoreUnboxedScalar(MDefinition* elements, MDefinition* index, MDefinition* value,
+ Scalar::Type storageType, TruncateInputKind truncateInput,
+ MemoryBarrierRequirement requiresBarrier = DoesNotRequireMemoryBarrier,
+ int32_t offsetAdjustment = 0)
+ : MTernaryInstruction(elements, index, value),
+ StoreUnboxedScalarBase(storageType),
+ storageType_(storageType),
+ truncateInput_(truncateInput),
+ requiresBarrier_(requiresBarrier == DoesRequireMemoryBarrier),
+ offsetAdjustment_(offsetAdjustment),
+ numElems_(1)
+ {
+ if (requiresBarrier_)
+ setGuard(); // Not removable or movable
+ else
+ setMovable();
+ MOZ_ASSERT(IsValidElementsType(elements, offsetAdjustment));
+ MOZ_ASSERT(index->type() == MIRType::Int32);
+ MOZ_ASSERT(storageType >= 0 && storageType < Scalar::MaxTypedArrayViewType);
+ }
+
+ public:
+ INSTRUCTION_HEADER(StoreUnboxedScalar)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements), (1, index), (2, value))
+
+ void setSimdWrite(Scalar::Type writeType, unsigned numElems) {
+ MOZ_ASSERT(Scalar::isSimdType(writeType));
+ setWriteType(writeType);
+ numElems_ = numElems;
+ }
+ unsigned numElems() const {
+ return numElems_;
+ }
+ Scalar::Type storageType() const {
+ return storageType_;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::UnboxedElement);
+ }
+ TruncateInputKind truncateInput() const {
+ return truncateInput_;
+ }
+ bool requiresMemoryBarrier() const {
+ return requiresBarrier_;
+ }
+ int32_t offsetAdjustment() const {
+ return offsetAdjustment_;
+ }
+ TruncateKind operandTruncateKind(size_t index) const override;
+
+ bool canConsumeFloat32(MUse* use) const override {
+ return use == getUseFor(2) && writeType() == Scalar::Float32;
+ }
+
+ ALLOW_CLONE(MStoreUnboxedScalar)
+};
+
+class MStoreTypedArrayElementHole
+ : public MAryInstruction<4>,
+ public StoreUnboxedScalarBase,
+ public StoreTypedArrayHolePolicy::Data
+{
+ MStoreTypedArrayElementHole(MDefinition* elements, MDefinition* length, MDefinition* index,
+ MDefinition* value, Scalar::Type arrayType)
+ : MAryInstruction<4>(),
+ StoreUnboxedScalarBase(arrayType)
+ {
+ initOperand(0, elements);
+ initOperand(1, length);
+ initOperand(2, index);
+ initOperand(3, value);
+ setMovable();
+ MOZ_ASSERT(elements->type() == MIRType::Elements);
+ MOZ_ASSERT(length->type() == MIRType::Int32);
+ MOZ_ASSERT(index->type() == MIRType::Int32);
+ MOZ_ASSERT(arrayType >= 0 && arrayType < Scalar::MaxTypedArrayViewType);
+ }
+
+ public:
+ INSTRUCTION_HEADER(StoreTypedArrayElementHole)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements), (1, length), (2, index), (3, value))
+
+ Scalar::Type arrayType() const {
+ MOZ_ASSERT(!Scalar::isSimdType(writeType()),
+ "arrayType == writeType iff the write type isn't SIMD");
+ return writeType();
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::UnboxedElement);
+ }
+ TruncateKind operandTruncateKind(size_t index) const override;
+
+ bool canConsumeFloat32(MUse* use) const override {
+ return use == getUseFor(3) && arrayType() == Scalar::Float32;
+ }
+
+ ALLOW_CLONE(MStoreTypedArrayElementHole)
+};
+
+// Store a value infallibly to a statically known typed array.
+class MStoreTypedArrayElementStatic :
+ public MBinaryInstruction,
+ public StoreUnboxedScalarBase,
+ public StoreTypedArrayElementStaticPolicy::Data
+{
+ MStoreTypedArrayElementStatic(JSObject* someTypedArray, MDefinition* ptr, MDefinition* v,
+ int32_t offset = 0, bool needsBoundsCheck = true)
+ : MBinaryInstruction(ptr, v),
+ StoreUnboxedScalarBase(someTypedArray->as<TypedArrayObject>().type()),
+ someTypedArray_(someTypedArray),
+ offset_(offset), needsBoundsCheck_(needsBoundsCheck)
+ {}
+
+ CompilerObject someTypedArray_;
+
+ // An offset to be encoded in the store instruction - taking advantage of the
+ // addressing modes. This is only non-zero when the access is proven to be
+ // within bounds.
+ int32_t offset_;
+ bool needsBoundsCheck_;
+
+ public:
+ INSTRUCTION_HEADER(StoreTypedArrayElementStatic)
+ TRIVIAL_NEW_WRAPPERS
+
+ Scalar::Type accessType() const {
+ return writeType();
+ }
+
+ SharedMem<void*> base() const;
+ size_t length() const;
+
+ MDefinition* ptr() const { return getOperand(0); }
+ MDefinition* value() const { return getOperand(1); }
+ bool needsBoundsCheck() const { return needsBoundsCheck_; }
+ void setNeedsBoundsCheck(bool v) { needsBoundsCheck_ = v; }
+ int32_t offset() const { return offset_; }
+ void setOffset(int32_t offset) { offset_ = offset; }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::UnboxedElement);
+ }
+ TruncateKind operandTruncateKind(size_t index) const override;
+
+ bool canConsumeFloat32(MUse* use) const override {
+ return use == getUseFor(1) && accessType() == Scalar::Float32;
+ }
+ void collectRangeInfoPreTrunc() override;
+
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(someTypedArray_);
+ }
+};
+
+// Compute an "effective address", i.e., a compound computation of the form:
+// base + index * scale + displacement
+class MEffectiveAddress
+ : public MBinaryInstruction,
+ public NoTypePolicy::Data
+{
+ MEffectiveAddress(MDefinition* base, MDefinition* index, Scale scale, int32_t displacement)
+ : MBinaryInstruction(base, index), scale_(scale), displacement_(displacement)
+ {
+ MOZ_ASSERT(base->type() == MIRType::Int32);
+ MOZ_ASSERT(index->type() == MIRType::Int32);
+ setMovable();
+ setResultType(MIRType::Int32);
+ }
+
+ Scale scale_;
+ int32_t displacement_;
+
+ public:
+ INSTRUCTION_HEADER(EffectiveAddress)
+ TRIVIAL_NEW_WRAPPERS
+
+ MDefinition* base() const {
+ return lhs();
+ }
+ MDefinition* index() const {
+ return rhs();
+ }
+ Scale scale() const {
+ return scale_;
+ }
+ int32_t displacement() const {
+ return displacement_;
+ }
+
+ ALLOW_CLONE(MEffectiveAddress)
+};
+
+// Clamp input to range [0, 255] for Uint8ClampedArray.
+class MClampToUint8
+ : public MUnaryInstruction,
+ public ClampPolicy::Data
+{
+ explicit MClampToUint8(MDefinition* input)
+ : MUnaryInstruction(input)
+ {
+ setResultType(MIRType::Int32);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(ClampToUint8)
+ TRIVIAL_NEW_WRAPPERS
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ void computeRange(TempAllocator& alloc) override;
+
+ ALLOW_CLONE(MClampToUint8)
+};
+
+class MLoadFixedSlot
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ size_t slot_;
+
+ protected:
+ MLoadFixedSlot(MDefinition* obj, size_t slot)
+ : MUnaryInstruction(obj), slot_(slot)
+ {
+ setResultType(MIRType::Value);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(LoadFixedSlot)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ size_t slot() const {
+ return slot_;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isLoadFixedSlot())
+ return false;
+ if (slot() != ins->toLoadFixedSlot()->slot())
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::FixedSlot);
+ }
+
+ AliasType mightAlias(const MDefinition* store) const override;
+
+ ALLOW_CLONE(MLoadFixedSlot)
+};
+
+class MLoadFixedSlotAndUnbox
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ size_t slot_;
+ MUnbox::Mode mode_;
+ BailoutKind bailoutKind_;
+ protected:
+ MLoadFixedSlotAndUnbox(MDefinition* obj, size_t slot, MUnbox::Mode mode, MIRType type,
+ BailoutKind kind)
+ : MUnaryInstruction(obj), slot_(slot), mode_(mode), bailoutKind_(kind)
+ {
+ setResultType(type);
+ setMovable();
+ if (mode_ == MUnbox::TypeBarrier || mode_ == MUnbox::Fallible)
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(LoadFixedSlotAndUnbox)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ size_t slot() const {
+ return slot_;
+ }
+ MUnbox::Mode mode() const {
+ return mode_;
+ }
+ BailoutKind bailoutKind() const {
+ return bailoutKind_;
+ }
+ bool fallible() const {
+ return mode_ != MUnbox::Infallible;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isLoadFixedSlotAndUnbox() ||
+ slot() != ins->toLoadFixedSlotAndUnbox()->slot() ||
+ mode() != ins->toLoadFixedSlotAndUnbox()->mode())
+ {
+ return false;
+ }
+ return congruentIfOperandsEqual(ins);
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::FixedSlot);
+ }
+
+ AliasType mightAlias(const MDefinition* store) const override;
+
+ ALLOW_CLONE(MLoadFixedSlotAndUnbox);
+};
+
+class MStoreFixedSlot
+ : public MBinaryInstruction,
+ public MixPolicy<SingleObjectPolicy, NoFloatPolicy<1> >::Data
+{
+ bool needsBarrier_;
+ size_t slot_;
+
+ MStoreFixedSlot(MDefinition* obj, MDefinition* rval, size_t slot, bool barrier)
+ : MBinaryInstruction(obj, rval),
+ needsBarrier_(barrier),
+ slot_(slot)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(StoreFixedSlot)
+ NAMED_OPERANDS((0, object), (1, value))
+
+ static MStoreFixedSlot* New(TempAllocator& alloc, MDefinition* obj, size_t slot,
+ MDefinition* rval)
+ {
+ return new(alloc) MStoreFixedSlot(obj, rval, slot, false);
+ }
+ static MStoreFixedSlot* NewBarriered(TempAllocator& alloc, MDefinition* obj, size_t slot,
+ MDefinition* rval)
+ {
+ return new(alloc) MStoreFixedSlot(obj, rval, slot, true);
+ }
+
+ size_t slot() const {
+ return slot_;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::FixedSlot);
+ }
+ bool needsBarrier() const {
+ return needsBarrier_;
+ }
+ void setNeedsBarrier(bool needsBarrier = true) {
+ needsBarrier_ = needsBarrier;
+ }
+
+ ALLOW_CLONE(MStoreFixedSlot)
+};
+
+typedef Vector<JSObject*, 4, JitAllocPolicy> ObjectVector;
+typedef Vector<bool, 4, JitAllocPolicy> BoolVector;
+
+class InlinePropertyTable : public TempObject
+{
+ struct Entry : public TempObject {
+ CompilerObjectGroup group;
+ CompilerFunction func;
+
+ Entry(ObjectGroup* group, JSFunction* func)
+ : group(group), func(func)
+ { }
+ bool appendRoots(MRootList& roots) const {
+ return roots.append(group) && roots.append(func);
+ }
+ };
+
+ jsbytecode* pc_;
+ MResumePoint* priorResumePoint_;
+ Vector<Entry*, 4, JitAllocPolicy> entries_;
+
+ public:
+ InlinePropertyTable(TempAllocator& alloc, jsbytecode* pc)
+ : pc_(pc), priorResumePoint_(nullptr), entries_(alloc)
+ { }
+
+ void setPriorResumePoint(MResumePoint* resumePoint) {
+ MOZ_ASSERT(priorResumePoint_ == nullptr);
+ priorResumePoint_ = resumePoint;
+ }
+ bool hasPriorResumePoint() { return bool(priorResumePoint_); }
+ MResumePoint* takePriorResumePoint() {
+ MResumePoint* rp = priorResumePoint_;
+ priorResumePoint_ = nullptr;
+ return rp;
+ }
+
+ jsbytecode* pc() const {
+ return pc_;
+ }
+
+ MOZ_MUST_USE bool addEntry(TempAllocator& alloc, ObjectGroup* group, JSFunction* func) {
+ return entries_.append(new(alloc) Entry(group, func));
+ }
+
+ size_t numEntries() const {
+ return entries_.length();
+ }
+
+ ObjectGroup* getObjectGroup(size_t i) const {
+ MOZ_ASSERT(i < numEntries());
+ return entries_[i]->group;
+ }
+
+ JSFunction* getFunction(size_t i) const {
+ MOZ_ASSERT(i < numEntries());
+ return entries_[i]->func;
+ }
+
+ bool hasFunction(JSFunction* func) const;
+ bool hasObjectGroup(ObjectGroup* group) const;
+
+ TemporaryTypeSet* buildTypeSetForFunction(JSFunction* func) const;
+
+ // Remove targets that vetoed inlining from the InlinePropertyTable.
+ void trimTo(const ObjectVector& targets, const BoolVector& choiceSet);
+
+ // Ensure that the InlinePropertyTable's domain is a subset of |targets|.
+ void trimToTargets(const ObjectVector& targets);
+
+ bool appendRoots(MRootList& roots) const;
+};
+
+class CacheLocationList : public InlineConcatList<CacheLocationList>
+{
+ public:
+ CacheLocationList()
+ : pc(nullptr),
+ script(nullptr)
+ { }
+
+ jsbytecode* pc;
+ JSScript* script;
+};
+
+class MGetPropertyCache
+ : public MBinaryInstruction,
+ public MixPolicy<ObjectPolicy<0>, CacheIdPolicy<1>>::Data
+{
+ bool idempotent_ : 1;
+ bool monitoredResult_ : 1;
+
+ CacheLocationList location_;
+
+ InlinePropertyTable* inlinePropertyTable_;
+
+ MGetPropertyCache(MDefinition* obj, MDefinition* id, bool monitoredResult)
+ : MBinaryInstruction(obj, id),
+ idempotent_(false),
+ monitoredResult_(monitoredResult),
+ location_(),
+ inlinePropertyTable_(nullptr)
+ {
+ setResultType(MIRType::Value);
+
+ // The cache will invalidate if there are objects with e.g. lookup or
+ // resolve hooks on the proto chain. setGuard ensures this check is not
+ // eliminated.
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(GetPropertyCache)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, idval))
+
+ InlinePropertyTable* initInlinePropertyTable(TempAllocator& alloc, jsbytecode* pc) {
+ MOZ_ASSERT(inlinePropertyTable_ == nullptr);
+ inlinePropertyTable_ = new(alloc) InlinePropertyTable(alloc, pc);
+ return inlinePropertyTable_;
+ }
+
+ void clearInlinePropertyTable() {
+ inlinePropertyTable_ = nullptr;
+ }
+
+ InlinePropertyTable* propTable() const {
+ return inlinePropertyTable_;
+ }
+
+ bool idempotent() const {
+ return idempotent_;
+ }
+ void setIdempotent() {
+ idempotent_ = true;
+ setMovable();
+ }
+ bool monitoredResult() const {
+ return monitoredResult_;
+ }
+ CacheLocationList& location() {
+ return location_;
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!idempotent_)
+ return false;
+ if (!ins->isGetPropertyCache())
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+
+ AliasSet getAliasSet() const override {
+ if (idempotent_) {
+ return AliasSet::Load(AliasSet::ObjectFields |
+ AliasSet::FixedSlot |
+ AliasSet::DynamicSlot);
+ }
+ return AliasSet::Store(AliasSet::Any);
+ }
+
+ void setBlock(MBasicBlock* block) override;
+ MOZ_MUST_USE bool updateForReplacement(MDefinition* ins) override;
+
+ bool allowDoubleResult() const;
+
+ bool appendRoots(MRootList& roots) const override {
+ if (inlinePropertyTable_)
+ return inlinePropertyTable_->appendRoots(roots);
+ return true;
+ }
+};
+
+struct PolymorphicEntry {
+ // The group and/or shape to guard against.
+ ReceiverGuard receiver;
+
+ // The property to load, null for loads from unboxed properties.
+ Shape* shape;
+
+ bool appendRoots(MRootList& roots) const {
+ return roots.append(receiver) && roots.append(shape);
+ }
+};
+
+// Emit code to load a value from an object if it matches one of the receivers
+// observed by the baseline IC, else bails out.
+class MGetPropertyPolymorphic
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ Vector<PolymorphicEntry, 4, JitAllocPolicy> receivers_;
+ CompilerPropertyName name_;
+
+ MGetPropertyPolymorphic(TempAllocator& alloc, MDefinition* obj, PropertyName* name)
+ : MUnaryInstruction(obj),
+ receivers_(alloc),
+ name_(name)
+ {
+ setGuard();
+ setMovable();
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(GetPropertyPolymorphic)
+ NAMED_OPERANDS((0, object))
+
+ static MGetPropertyPolymorphic* New(TempAllocator& alloc, MDefinition* obj, PropertyName* name) {
+ return new(alloc) MGetPropertyPolymorphic(alloc, obj, name);
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isGetPropertyPolymorphic())
+ return false;
+ if (name() != ins->toGetPropertyPolymorphic()->name())
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+
+ MOZ_MUST_USE bool addReceiver(const ReceiverGuard& receiver, Shape* shape) {
+ PolymorphicEntry entry;
+ entry.receiver = receiver;
+ entry.shape = shape;
+ return receivers_.append(entry);
+ }
+ size_t numReceivers() const {
+ return receivers_.length();
+ }
+ const ReceiverGuard receiver(size_t i) const {
+ return receivers_[i].receiver;
+ }
+ Shape* shape(size_t i) const {
+ return receivers_[i].shape;
+ }
+ PropertyName* name() const {
+ return name_;
+ }
+ AliasSet getAliasSet() const override {
+ bool hasUnboxedLoad = false;
+ for (size_t i = 0; i < numReceivers(); i++) {
+ if (!shape(i)) {
+ hasUnboxedLoad = true;
+ break;
+ }
+ }
+ return AliasSet::Load(AliasSet::ObjectFields |
+ AliasSet::FixedSlot |
+ AliasSet::DynamicSlot |
+ (hasUnboxedLoad ? AliasSet::UnboxedElement : 0));
+ }
+
+ AliasType mightAlias(const MDefinition* store) const override;
+
+ bool appendRoots(MRootList& roots) const override;
+};
+
+// Emit code to store a value to an object's slots if its shape/group matches
+// one of the shapes/groups observed by the baseline IC, else bails out.
+class MSetPropertyPolymorphic
+ : public MBinaryInstruction,
+ public MixPolicy<SingleObjectPolicy, NoFloatPolicy<1> >::Data
+{
+ Vector<PolymorphicEntry, 4, JitAllocPolicy> receivers_;
+ CompilerPropertyName name_;
+ bool needsBarrier_;
+
+ MSetPropertyPolymorphic(TempAllocator& alloc, MDefinition* obj, MDefinition* value,
+ PropertyName* name)
+ : MBinaryInstruction(obj, value),
+ receivers_(alloc),
+ name_(name),
+ needsBarrier_(false)
+ {
+ }
+
+ public:
+ INSTRUCTION_HEADER(SetPropertyPolymorphic)
+ NAMED_OPERANDS((0, object), (1, value))
+
+ static MSetPropertyPolymorphic* New(TempAllocator& alloc, MDefinition* obj, MDefinition* value,
+ PropertyName* name) {
+ return new(alloc) MSetPropertyPolymorphic(alloc, obj, value, name);
+ }
+
+ MOZ_MUST_USE bool addReceiver(const ReceiverGuard& receiver, Shape* shape) {
+ PolymorphicEntry entry;
+ entry.receiver = receiver;
+ entry.shape = shape;
+ return receivers_.append(entry);
+ }
+ size_t numReceivers() const {
+ return receivers_.length();
+ }
+ const ReceiverGuard& receiver(size_t i) const {
+ return receivers_[i].receiver;
+ }
+ Shape* shape(size_t i) const {
+ return receivers_[i].shape;
+ }
+ PropertyName* name() const {
+ return name_;
+ }
+ bool needsBarrier() const {
+ return needsBarrier_;
+ }
+ void setNeedsBarrier() {
+ needsBarrier_ = true;
+ }
+ AliasSet getAliasSet() const override {
+ bool hasUnboxedStore = false;
+ for (size_t i = 0; i < numReceivers(); i++) {
+ if (!shape(i)) {
+ hasUnboxedStore = true;
+ break;
+ }
+ }
+ return AliasSet::Store(AliasSet::ObjectFields |
+ AliasSet::FixedSlot |
+ AliasSet::DynamicSlot |
+ (hasUnboxedStore ? AliasSet::UnboxedElement : 0));
+ }
+ bool appendRoots(MRootList& roots) const override;
+};
+
+class MDispatchInstruction
+ : public MControlInstruction,
+ public SingleObjectPolicy::Data
+{
+ // Map from JSFunction* -> MBasicBlock.
+ struct Entry {
+ JSFunction* func;
+ // If |func| has a singleton group, |funcGroup| is null. Otherwise,
+ // |funcGroup| holds the ObjectGroup for |func|, and dispatch guards
+ // on the group instead of directly on the function.
+ ObjectGroup* funcGroup;
+ MBasicBlock* block;
+
+ Entry(JSFunction* func, ObjectGroup* funcGroup, MBasicBlock* block)
+ : func(func), funcGroup(funcGroup), block(block)
+ { }
+ bool appendRoots(MRootList& roots) const {
+ return roots.append(func) && roots.append(funcGroup);
+ }
+ };
+ Vector<Entry, 4, JitAllocPolicy> map_;
+
+ // An optional fallback path that uses MCall.
+ MBasicBlock* fallback_;
+ MUse operand_;
+
+ void initOperand(size_t index, MDefinition* operand) {
+ MOZ_ASSERT(index == 0);
+ operand_.init(operand, this);
+ }
+
+ public:
+ NAMED_OPERANDS((0, input))
+ MDispatchInstruction(TempAllocator& alloc, MDefinition* input)
+ : map_(alloc), fallback_(nullptr)
+ {
+ initOperand(0, input);
+ }
+
+ protected:
+ MUse* getUseFor(size_t index) final override {
+ MOZ_ASSERT(index == 0);
+ return &operand_;
+ }
+ const MUse* getUseFor(size_t index) const final override {
+ MOZ_ASSERT(index == 0);
+ return &operand_;
+ }
+ MDefinition* getOperand(size_t index) const final override {
+ MOZ_ASSERT(index == 0);
+ return operand_.producer();
+ }
+ size_t numOperands() const final override {
+ return 1;
+ }
+ size_t indexOf(const MUse* u) const final override {
+ MOZ_ASSERT(u == getUseFor(0));
+ return 0;
+ }
+ void replaceOperand(size_t index, MDefinition* operand) final override {
+ MOZ_ASSERT(index == 0);
+ operand_.replaceProducer(operand);
+ }
+
+ public:
+ void setSuccessor(size_t i, MBasicBlock* successor) {
+ MOZ_ASSERT(i < numSuccessors());
+ if (i == map_.length())
+ fallback_ = successor;
+ else
+ map_[i].block = successor;
+ }
+ size_t numSuccessors() const final override {
+ return map_.length() + (fallback_ ? 1 : 0);
+ }
+ void replaceSuccessor(size_t i, MBasicBlock* successor) final override {
+ setSuccessor(i, successor);
+ }
+ MBasicBlock* getSuccessor(size_t i) const final override {
+ MOZ_ASSERT(i < numSuccessors());
+ if (i == map_.length())
+ return fallback_;
+ return map_[i].block;
+ }
+
+ public:
+ MOZ_MUST_USE bool addCase(JSFunction* func, ObjectGroup* funcGroup, MBasicBlock* block) {
+ return map_.append(Entry(func, funcGroup, block));
+ }
+ uint32_t numCases() const {
+ return map_.length();
+ }
+ JSFunction* getCase(uint32_t i) const {
+ return map_[i].func;
+ }
+ ObjectGroup* getCaseObjectGroup(uint32_t i) const {
+ return map_[i].funcGroup;
+ }
+ MBasicBlock* getCaseBlock(uint32_t i) const {
+ return map_[i].block;
+ }
+
+ bool hasFallback() const {
+ return bool(fallback_);
+ }
+ void addFallback(MBasicBlock* block) {
+ MOZ_ASSERT(!hasFallback());
+ fallback_ = block;
+ }
+ MBasicBlock* getFallback() const {
+ MOZ_ASSERT(hasFallback());
+ return fallback_;
+ }
+ bool appendRoots(MRootList& roots) const override;
+};
+
+// Polymorphic dispatch for inlining, keyed off incoming ObjectGroup.
+class MObjectGroupDispatch : public MDispatchInstruction
+{
+ // Map ObjectGroup (of CallProp's Target Object) -> JSFunction (yielded by the CallProp).
+ InlinePropertyTable* inlinePropertyTable_;
+
+ MObjectGroupDispatch(TempAllocator& alloc, MDefinition* input, InlinePropertyTable* table)
+ : MDispatchInstruction(alloc, input),
+ inlinePropertyTable_(table)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(ObjectGroupDispatch)
+
+ static MObjectGroupDispatch* New(TempAllocator& alloc, MDefinition* ins,
+ InlinePropertyTable* table)
+ {
+ return new(alloc) MObjectGroupDispatch(alloc, ins, table);
+ }
+
+ InlinePropertyTable* propTable() const {
+ return inlinePropertyTable_;
+ }
+ bool appendRoots(MRootList& roots) const override;
+};
+
+// Polymorphic dispatch for inlining, keyed off incoming JSFunction*.
+class MFunctionDispatch : public MDispatchInstruction
+{
+ MFunctionDispatch(TempAllocator& alloc, MDefinition* input)
+ : MDispatchInstruction(alloc, input)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(FunctionDispatch)
+
+ static MFunctionDispatch* New(TempAllocator& alloc, MDefinition* ins) {
+ return new(alloc) MFunctionDispatch(alloc, ins);
+ }
+ bool appendRoots(MRootList& roots) const override;
+};
+
+class MBindNameCache
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ CompilerPropertyName name_;
+ CompilerScript script_;
+ jsbytecode* pc_;
+
+ MBindNameCache(MDefinition* envChain, PropertyName* name, JSScript* script, jsbytecode* pc)
+ : MUnaryInstruction(envChain), name_(name), script_(script), pc_(pc)
+ {
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(BindNameCache)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, environmentChain))
+
+ PropertyName* name() const {
+ return name_;
+ }
+ JSScript* script() const {
+ return script_;
+ }
+ jsbytecode* pc() const {
+ return pc_;
+ }
+ bool appendRoots(MRootList& roots) const override {
+ // Don't append the script, all scripts are added anyway.
+ return roots.append(name_);
+ }
+};
+
+class MCallBindVar
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MCallBindVar(MDefinition* envChain)
+ : MUnaryInstruction(envChain)
+ {
+ setResultType(MIRType::Object);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(CallBindVar)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, environmentChain))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isCallBindVar())
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+// Guard on an object's shape.
+class MGuardShape
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ CompilerShape shape_;
+ BailoutKind bailoutKind_;
+
+ MGuardShape(MDefinition* obj, Shape* shape, BailoutKind bailoutKind)
+ : MUnaryInstruction(obj),
+ shape_(shape),
+ bailoutKind_(bailoutKind)
+ {
+ setGuard();
+ setMovable();
+ setResultType(MIRType::Object);
+ setResultTypeSet(obj->resultTypeSet());
+
+ // Disallow guarding on unboxed object shapes. The group is better to
+ // guard on, and guarding on the shape can interact badly with
+ // MConvertUnboxedObjectToNative.
+ MOZ_ASSERT(shape->getObjectClass() != &UnboxedPlainObject::class_);
+ }
+
+ public:
+ INSTRUCTION_HEADER(GuardShape)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ const Shape* shape() const {
+ return shape_;
+ }
+ BailoutKind bailoutKind() const {
+ return bailoutKind_;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isGuardShape())
+ return false;
+ if (shape() != ins->toGuardShape()->shape())
+ return false;
+ if (bailoutKind() != ins->toGuardShape()->bailoutKind())
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(shape_);
+ }
+};
+
+// Bail if the object's shape or unboxed group is not in the input list.
+class MGuardReceiverPolymorphic
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ Vector<ReceiverGuard, 4, JitAllocPolicy> receivers_;
+
+ MGuardReceiverPolymorphic(TempAllocator& alloc, MDefinition* obj)
+ : MUnaryInstruction(obj),
+ receivers_(alloc)
+ {
+ setGuard();
+ setMovable();
+ setResultType(MIRType::Object);
+ setResultTypeSet(obj->resultTypeSet());
+ }
+
+ public:
+ INSTRUCTION_HEADER(GuardReceiverPolymorphic)
+ NAMED_OPERANDS((0, object))
+
+ static MGuardReceiverPolymorphic* New(TempAllocator& alloc, MDefinition* obj) {
+ return new(alloc) MGuardReceiverPolymorphic(alloc, obj);
+ }
+
+ MOZ_MUST_USE bool addReceiver(const ReceiverGuard& receiver) {
+ return receivers_.append(receiver);
+ }
+ size_t numReceivers() const {
+ return receivers_.length();
+ }
+ const ReceiverGuard& receiver(size_t i) const {
+ return receivers_[i];
+ }
+
+ bool congruentTo(const MDefinition* ins) const override;
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+
+ bool appendRoots(MRootList& roots) const override;
+
+};
+
+// Guard on an object's group, inclusively or exclusively.
+class MGuardObjectGroup
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ CompilerObjectGroup group_;
+ bool bailOnEquality_;
+ BailoutKind bailoutKind_;
+
+ MGuardObjectGroup(MDefinition* obj, ObjectGroup* group, bool bailOnEquality,
+ BailoutKind bailoutKind)
+ : MUnaryInstruction(obj),
+ group_(group),
+ bailOnEquality_(bailOnEquality),
+ bailoutKind_(bailoutKind)
+ {
+ setGuard();
+ setMovable();
+ setResultType(MIRType::Object);
+
+ // Unboxed groups which might be converted to natives can't be guarded
+ // on, due to MConvertUnboxedObjectToNative.
+ MOZ_ASSERT_IF(group->maybeUnboxedLayoutDontCheckGeneration(),
+ !group->unboxedLayoutDontCheckGeneration().nativeGroup());
+ }
+
+ public:
+ INSTRUCTION_HEADER(GuardObjectGroup)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ const ObjectGroup* group() const {
+ return group_;
+ }
+ bool bailOnEquality() const {
+ return bailOnEquality_;
+ }
+ BailoutKind bailoutKind() const {
+ return bailoutKind_;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isGuardObjectGroup())
+ return false;
+ if (group() != ins->toGuardObjectGroup()->group())
+ return false;
+ if (bailOnEquality() != ins->toGuardObjectGroup()->bailOnEquality())
+ return false;
+ if (bailoutKind() != ins->toGuardObjectGroup()->bailoutKind())
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(group_);
+ }
+};
+
+// Guard on an object's identity, inclusively or exclusively.
+class MGuardObjectIdentity
+ : public MBinaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ bool bailOnEquality_;
+
+ MGuardObjectIdentity(MDefinition* obj, MDefinition* expected, bool bailOnEquality)
+ : MBinaryInstruction(obj, expected),
+ bailOnEquality_(bailOnEquality)
+ {
+ setGuard();
+ setMovable();
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(GuardObjectIdentity)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, expected))
+
+ bool bailOnEquality() const {
+ return bailOnEquality_;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isGuardObjectIdentity())
+ return false;
+ if (bailOnEquality() != ins->toGuardObjectIdentity()->bailOnEquality())
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+};
+
+// Guard on an object's class.
+class MGuardClass
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ const Class* class_;
+
+ MGuardClass(MDefinition* obj, const Class* clasp)
+ : MUnaryInstruction(obj),
+ class_(clasp)
+ {
+ setGuard();
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(GuardClass)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ const Class* getClass() const {
+ return class_;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isGuardClass())
+ return false;
+ if (getClass() != ins->toGuardClass()->getClass())
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+
+ ALLOW_CLONE(MGuardClass)
+};
+
+// Guard on the presence or absence of an unboxed object's expando.
+class MGuardUnboxedExpando
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ bool requireExpando_;
+ BailoutKind bailoutKind_;
+
+ MGuardUnboxedExpando(MDefinition* obj, bool requireExpando, BailoutKind bailoutKind)
+ : MUnaryInstruction(obj),
+ requireExpando_(requireExpando),
+ bailoutKind_(bailoutKind)
+ {
+ setGuard();
+ setMovable();
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(GuardUnboxedExpando)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool requireExpando() const {
+ return requireExpando_;
+ }
+ BailoutKind bailoutKind() const {
+ return bailoutKind_;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!congruentIfOperandsEqual(ins))
+ return false;
+ if (requireExpando() != ins->toGuardUnboxedExpando()->requireExpando())
+ return false;
+ return true;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+};
+
+// Load an unboxed plain object's expando.
+class MLoadUnboxedExpando
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ private:
+ explicit MLoadUnboxedExpando(MDefinition* object)
+ : MUnaryInstruction(object)
+ {
+ setResultType(MIRType::Object);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(LoadUnboxedExpando)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+};
+
+// Load from vp[slot] (slots that are not inline in an object).
+class MLoadSlot
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ uint32_t slot_;
+
+ MLoadSlot(MDefinition* slots, uint32_t slot)
+ : MUnaryInstruction(slots),
+ slot_(slot)
+ {
+ setResultType(MIRType::Value);
+ setMovable();
+ MOZ_ASSERT(slots->type() == MIRType::Slots);
+ }
+
+ public:
+ INSTRUCTION_HEADER(LoadSlot)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, slots))
+
+ uint32_t slot() const {
+ return slot_;
+ }
+
+ HashNumber valueHash() const override;
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isLoadSlot())
+ return false;
+ if (slot() != ins->toLoadSlot()->slot())
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ AliasSet getAliasSet() const override {
+ MOZ_ASSERT(slots()->type() == MIRType::Slots);
+ return AliasSet::Load(AliasSet::DynamicSlot);
+ }
+ AliasType mightAlias(const MDefinition* store) const override;
+
+ void printOpcode(GenericPrinter& out) const override;
+
+ ALLOW_CLONE(MLoadSlot)
+};
+
+// Inline call to access a function's environment (scope chain).
+class MFunctionEnvironment
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MFunctionEnvironment(MDefinition* function)
+ : MUnaryInstruction(function)
+ {
+ setResultType(MIRType::Object);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(FunctionEnvironment)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, function))
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ // A function's environment is fixed.
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+// Store to vp[slot] (slots that are not inline in an object).
+class MStoreSlot
+ : public MBinaryInstruction,
+ public MixPolicy<ObjectPolicy<0>, NoFloatPolicy<1> >::Data
+{
+ uint32_t slot_;
+ MIRType slotType_;
+ bool needsBarrier_;
+
+ MStoreSlot(MDefinition* slots, uint32_t slot, MDefinition* value, bool barrier)
+ : MBinaryInstruction(slots, value),
+ slot_(slot),
+ slotType_(MIRType::Value),
+ needsBarrier_(barrier)
+ {
+ MOZ_ASSERT(slots->type() == MIRType::Slots);
+ }
+
+ public:
+ INSTRUCTION_HEADER(StoreSlot)
+ NAMED_OPERANDS((0, slots), (1, value))
+
+ static MStoreSlot* New(TempAllocator& alloc, MDefinition* slots, uint32_t slot,
+ MDefinition* value)
+ {
+ return new(alloc) MStoreSlot(slots, slot, value, false);
+ }
+ static MStoreSlot* NewBarriered(TempAllocator& alloc, MDefinition* slots, uint32_t slot,
+ MDefinition* value)
+ {
+ return new(alloc) MStoreSlot(slots, slot, value, true);
+ }
+
+ uint32_t slot() const {
+ return slot_;
+ }
+ MIRType slotType() const {
+ return slotType_;
+ }
+ void setSlotType(MIRType slotType) {
+ MOZ_ASSERT(slotType != MIRType::None);
+ slotType_ = slotType;
+ }
+ bool needsBarrier() const {
+ return needsBarrier_;
+ }
+ void setNeedsBarrier() {
+ needsBarrier_ = true;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::DynamicSlot);
+ }
+ void printOpcode(GenericPrinter& out) const override;
+
+ ALLOW_CLONE(MStoreSlot)
+};
+
+class MGetNameCache
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ public:
+ enum AccessKind {
+ NAMETYPEOF,
+ NAME
+ };
+
+ private:
+ CompilerPropertyName name_;
+ AccessKind kind_;
+
+ MGetNameCache(MDefinition* obj, PropertyName* name, AccessKind kind)
+ : MUnaryInstruction(obj),
+ name_(name),
+ kind_(kind)
+ {
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(GetNameCache)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, envObj))
+
+ PropertyName* name() const {
+ return name_;
+ }
+ AccessKind accessKind() const {
+ return kind_;
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(name_);
+ }
+};
+
+class MCallGetIntrinsicValue : public MNullaryInstruction
+{
+ CompilerPropertyName name_;
+
+ explicit MCallGetIntrinsicValue(PropertyName* name)
+ : name_(name)
+ {
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(CallGetIntrinsicValue)
+ TRIVIAL_NEW_WRAPPERS
+
+ PropertyName* name() const {
+ return name_;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool possiblyCalls() const override {
+ return true;
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(name_);
+ }
+};
+
+class MSetPropertyInstruction : public MBinaryInstruction
+{
+ CompilerPropertyName name_;
+ bool strict_;
+
+ protected:
+ MSetPropertyInstruction(MDefinition* obj, MDefinition* value, PropertyName* name,
+ bool strict)
+ : MBinaryInstruction(obj, value),
+ name_(name), strict_(strict)
+ {}
+
+ public:
+ NAMED_OPERANDS((0, object), (1, value))
+ PropertyName* name() const {
+ return name_;
+ }
+ bool strict() const {
+ return strict_;
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(name_);
+ }
+};
+
+class MSetElementInstruction
+ : public MTernaryInstruction
+{
+ bool strict_;
+ protected:
+ MSetElementInstruction(MDefinition* object, MDefinition* index, MDefinition* value, bool strict)
+ : MTernaryInstruction(object, index, value),
+ strict_(strict)
+ {
+ }
+
+ public:
+ NAMED_OPERANDS((0, object), (1, index), (2, value))
+ bool strict() const {
+ return strict_;
+ }
+};
+
+class MDeleteProperty
+ : public MUnaryInstruction,
+ public BoxInputsPolicy::Data
+{
+ CompilerPropertyName name_;
+ bool strict_;
+
+ protected:
+ MDeleteProperty(MDefinition* val, PropertyName* name, bool strict)
+ : MUnaryInstruction(val),
+ name_(name),
+ strict_(strict)
+ {
+ setResultType(MIRType::Boolean);
+ }
+
+ public:
+ INSTRUCTION_HEADER(DeleteProperty)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, value))
+
+ PropertyName* name() const {
+ return name_;
+ }
+ bool strict() const {
+ return strict_;
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(name_);
+ }
+};
+
+class MDeleteElement
+ : public MBinaryInstruction,
+ public BoxInputsPolicy::Data
+{
+ bool strict_;
+
+ MDeleteElement(MDefinition* value, MDefinition* index, bool strict)
+ : MBinaryInstruction(value, index),
+ strict_(strict)
+ {
+ setResultType(MIRType::Boolean);
+ }
+
+ public:
+ INSTRUCTION_HEADER(DeleteElement)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, value), (1, index))
+
+ bool strict() const {
+ return strict_;
+ }
+};
+
+// Note: This uses CallSetElementPolicy to always box its second input,
+// ensuring we don't need two LIR instructions to lower this.
+class MCallSetProperty
+ : public MSetPropertyInstruction,
+ public CallSetElementPolicy::Data
+{
+ MCallSetProperty(MDefinition* obj, MDefinition* value, PropertyName* name, bool strict)
+ : MSetPropertyInstruction(obj, value, name, strict)
+ {
+ }
+
+ public:
+ INSTRUCTION_HEADER(CallSetProperty)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+class MSetPropertyCache
+ : public MTernaryInstruction,
+ public Mix3Policy<SingleObjectPolicy, CacheIdPolicy<1>, NoFloatPolicy<2>>::Data
+{
+ bool strict_ : 1;
+ bool needsTypeBarrier_ : 1;
+ bool guardHoles_ : 1;
+
+ MSetPropertyCache(MDefinition* obj, MDefinition* id, MDefinition* value, bool strict,
+ bool typeBarrier, bool guardHoles)
+ : MTernaryInstruction(obj, id, value),
+ strict_(strict),
+ needsTypeBarrier_(typeBarrier),
+ guardHoles_(guardHoles)
+ {
+ }
+
+ public:
+ INSTRUCTION_HEADER(SetPropertyCache)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, idval), (2, value))
+
+ bool needsTypeBarrier() const {
+ return needsTypeBarrier_;
+ }
+
+ bool guardHoles() const {
+ return guardHoles_;
+ }
+
+ bool strict() const {
+ return strict_;
+ }
+};
+
+class MCallGetProperty
+ : public MUnaryInstruction,
+ public BoxInputsPolicy::Data
+{
+ CompilerPropertyName name_;
+ bool idempotent_;
+
+ MCallGetProperty(MDefinition* value, PropertyName* name)
+ : MUnaryInstruction(value), name_(name),
+ idempotent_(false)
+ {
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(CallGetProperty)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, value))
+
+ PropertyName* name() const {
+ return name_;
+ }
+
+ // Constructors need to perform a GetProp on the function prototype.
+ // Since getters cannot be set on the prototype, fetching is non-effectful.
+ // The operation may be safely repeated in case of bailout.
+ void setIdempotent() {
+ idempotent_ = true;
+ }
+ AliasSet getAliasSet() const override {
+ if (!idempotent_)
+ return AliasSet::Store(AliasSet::Any);
+ return AliasSet::None();
+ }
+ bool possiblyCalls() const override {
+ return true;
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(name_);
+ }
+};
+
+// Inline call to handle lhs[rhs]. The first input is a Value so that this
+// instruction can handle both objects and strings.
+class MCallGetElement
+ : public MBinaryInstruction,
+ public BoxInputsPolicy::Data
+{
+ MCallGetElement(MDefinition* lhs, MDefinition* rhs)
+ : MBinaryInstruction(lhs, rhs)
+ {
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(CallGetElement)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+class MCallSetElement
+ : public MSetElementInstruction,
+ public CallSetElementPolicy::Data
+{
+ MCallSetElement(MDefinition* object, MDefinition* index, MDefinition* value, bool strict)
+ : MSetElementInstruction(object, index, value, strict)
+ {
+ }
+
+ public:
+ INSTRUCTION_HEADER(CallSetElement)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+class MCallInitElementArray
+ : public MAryInstruction<2>,
+ public MixPolicy<ObjectPolicy<0>, BoxPolicy<1> >::Data
+{
+ uint32_t index_;
+
+ MCallInitElementArray(MDefinition* obj, uint32_t index, MDefinition* val)
+ : index_(index)
+ {
+ initOperand(0, obj);
+ initOperand(1, val);
+ }
+
+ public:
+ INSTRUCTION_HEADER(CallInitElementArray)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, value))
+
+ uint32_t index() const {
+ return index_;
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+class MSetDOMProperty
+ : public MAryInstruction<2>,
+ public MixPolicy<ObjectPolicy<0>, BoxPolicy<1> >::Data
+{
+ const JSJitSetterOp func_;
+
+ MSetDOMProperty(const JSJitSetterOp func, MDefinition* obj, MDefinition* val)
+ : func_(func)
+ {
+ initOperand(0, obj);
+ initOperand(1, val);
+ }
+
+ public:
+ INSTRUCTION_HEADER(SetDOMProperty)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, value))
+
+ JSJitSetterOp fun() const {
+ return func_;
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+class MGetDOMProperty
+ : public MVariadicInstruction,
+ public ObjectPolicy<0>::Data
+{
+ const JSJitInfo* info_;
+
+ protected:
+ explicit MGetDOMProperty(const JSJitInfo* jitinfo)
+ : info_(jitinfo)
+ {
+ MOZ_ASSERT(jitinfo);
+ MOZ_ASSERT(jitinfo->type() == JSJitInfo::Getter);
+
+ // We are movable iff the jitinfo says we can be.
+ if (isDomMovable()) {
+ MOZ_ASSERT(jitinfo->aliasSet() != JSJitInfo::AliasEverything);
+ setMovable();
+ } else {
+ // If we're not movable, that means we shouldn't be DCEd either,
+ // because we might throw an exception when called, and getting rid
+ // of that is observable.
+ setGuard();
+ }
+
+ setResultType(MIRType::Value);
+ }
+
+ const JSJitInfo* info() const {
+ return info_;
+ }
+
+ MOZ_MUST_USE bool init(TempAllocator& alloc, MDefinition* obj, MDefinition* guard,
+ MDefinition* globalGuard) {
+ MOZ_ASSERT(obj);
+ // guard can be null.
+ // globalGuard can be null.
+ size_t operandCount = 1;
+ if (guard)
+ ++operandCount;
+ if (globalGuard)
+ ++operandCount;
+ if (!MVariadicInstruction::init(alloc, operandCount))
+ return false;
+ initOperand(0, obj);
+
+ size_t operandIndex = 1;
+ // Pin the guard, if we have one as an operand if we want to hoist later.
+ if (guard)
+ initOperand(operandIndex++, guard);
+
+ // And the same for the global guard, if we have one.
+ if (globalGuard)
+ initOperand(operandIndex, globalGuard);
+
+ return true;
+ }
+
+ public:
+ INSTRUCTION_HEADER(GetDOMProperty)
+ NAMED_OPERANDS((0, object))
+
+ static MGetDOMProperty* New(TempAllocator& alloc, const JSJitInfo* info, MDefinition* obj,
+ MDefinition* guard, MDefinition* globalGuard)
+ {
+ auto* res = new(alloc) MGetDOMProperty(info);
+ if (!res || !res->init(alloc, obj, guard, globalGuard))
+ return nullptr;
+ return res;
+ }
+
+ JSJitGetterOp fun() const {
+ return info_->getter;
+ }
+ bool isInfallible() const {
+ return info_->isInfallible;
+ }
+ bool isDomMovable() const {
+ return info_->isMovable;
+ }
+ JSJitInfo::AliasSet domAliasSet() const {
+ return info_->aliasSet();
+ }
+ size_t domMemberSlotIndex() const {
+ MOZ_ASSERT(info_->isAlwaysInSlot || info_->isLazilyCachedInSlot);
+ return info_->slotIndex;
+ }
+ bool valueMayBeInSlot() const {
+ return info_->isLazilyCachedInSlot;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isGetDOMProperty())
+ return false;
+
+ return congruentTo(ins->toGetDOMProperty());
+ }
+
+ bool congruentTo(const MGetDOMProperty* ins) const {
+ if (!isDomMovable())
+ return false;
+
+ // Checking the jitinfo is the same as checking the constant function
+ if (!(info() == ins->info()))
+ return false;
+
+ return congruentIfOperandsEqual(ins);
+ }
+
+ AliasSet getAliasSet() const override {
+ JSJitInfo::AliasSet aliasSet = domAliasSet();
+ if (aliasSet == JSJitInfo::AliasNone)
+ return AliasSet::None();
+ if (aliasSet == JSJitInfo::AliasDOMSets)
+ return AliasSet::Load(AliasSet::DOMProperty);
+ MOZ_ASSERT(aliasSet == JSJitInfo::AliasEverything);
+ return AliasSet::Store(AliasSet::Any);
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+class MGetDOMMember : public MGetDOMProperty
+{
+ // We inherit everything from MGetDOMProperty except our
+ // possiblyCalls value and the congruentTo behavior.
+ explicit MGetDOMMember(const JSJitInfo* jitinfo)
+ : MGetDOMProperty(jitinfo)
+ {
+ setResultType(MIRTypeFromValueType(jitinfo->returnType()));
+ }
+
+ public:
+ INSTRUCTION_HEADER(GetDOMMember)
+
+ static MGetDOMMember* New(TempAllocator& alloc, const JSJitInfo* info, MDefinition* obj,
+ MDefinition* guard, MDefinition* globalGuard)
+ {
+ auto* res = new(alloc) MGetDOMMember(info);
+ if (!res || !res->init(alloc, obj, guard, globalGuard))
+ return nullptr;
+ return res;
+ }
+
+ bool possiblyCalls() const override {
+ return false;
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isGetDOMMember())
+ return false;
+
+ return MGetDOMProperty::congruentTo(ins->toGetDOMMember());
+ }
+};
+
+class MStringLength
+ : public MUnaryInstruction,
+ public StringPolicy<0>::Data
+{
+ explicit MStringLength(MDefinition* string)
+ : MUnaryInstruction(string)
+ {
+ setResultType(MIRType::Int32);
+ setMovable();
+ }
+ public:
+ INSTRUCTION_HEADER(StringLength)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, string))
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ // The string |length| property is immutable, so there is no
+ // implicit dependency.
+ return AliasSet::None();
+ }
+
+ void computeRange(TempAllocator& alloc) override;
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ ALLOW_CLONE(MStringLength)
+};
+
+// Inlined version of Math.floor().
+class MFloor
+ : public MUnaryInstruction,
+ public FloatingPointPolicy<0>::Data
+{
+ explicit MFloor(MDefinition* num)
+ : MUnaryInstruction(num)
+ {
+ setResultType(MIRType::Int32);
+ specialization_ = MIRType::Double;
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(Floor)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool isFloat32Commutative() const override {
+ return true;
+ }
+ void trySpecializeFloat32(TempAllocator& alloc) override;
+#ifdef DEBUG
+ bool isConsistentFloat32Use(MUse* use) const override {
+ return true;
+ }
+#endif
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ void computeRange(TempAllocator& alloc) override;
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ ALLOW_CLONE(MFloor)
+};
+
+// Inlined version of Math.ceil().
+class MCeil
+ : public MUnaryInstruction,
+ public FloatingPointPolicy<0>::Data
+{
+ explicit MCeil(MDefinition* num)
+ : MUnaryInstruction(num)
+ {
+ setResultType(MIRType::Int32);
+ specialization_ = MIRType::Double;
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(Ceil)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool isFloat32Commutative() const override {
+ return true;
+ }
+ void trySpecializeFloat32(TempAllocator& alloc) override;
+#ifdef DEBUG
+ bool isConsistentFloat32Use(MUse* use) const override {
+ return true;
+ }
+#endif
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ void computeRange(TempAllocator& alloc) override;
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ ALLOW_CLONE(MCeil)
+};
+
+// Inlined version of Math.round().
+class MRound
+ : public MUnaryInstruction,
+ public FloatingPointPolicy<0>::Data
+{
+ explicit MRound(MDefinition* num)
+ : MUnaryInstruction(num)
+ {
+ setResultType(MIRType::Int32);
+ specialization_ = MIRType::Double;
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(Round)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool isFloat32Commutative() const override {
+ return true;
+ }
+ void trySpecializeFloat32(TempAllocator& alloc) override;
+#ifdef DEBUG
+ bool isConsistentFloat32Use(MUse* use) const override {
+ return true;
+ }
+#endif
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ ALLOW_CLONE(MRound)
+};
+
+class MIteratorStart
+ : public MUnaryInstruction,
+ public BoxExceptPolicy<0, MIRType::Object>::Data
+{
+ uint8_t flags_;
+
+ MIteratorStart(MDefinition* obj, uint8_t flags)
+ : MUnaryInstruction(obj), flags_(flags)
+ {
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(IteratorStart)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ uint8_t flags() const {
+ return flags_;
+ }
+};
+
+class MIteratorMore
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MIteratorMore(MDefinition* iter)
+ : MUnaryInstruction(iter)
+ {
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(IteratorMore)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, iterator))
+
+};
+
+class MIsNoIter
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ explicit MIsNoIter(MDefinition* def)
+ : MUnaryInstruction(def)
+ {
+ setResultType(MIRType::Boolean);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(IsNoIter)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MIteratorEnd
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MIteratorEnd(MDefinition* iter)
+ : MUnaryInstruction(iter)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(IteratorEnd)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, iterator))
+
+};
+
+// Implementation for 'in' operator.
+class MIn
+ : public MBinaryInstruction,
+ public MixPolicy<BoxPolicy<0>, ObjectPolicy<1> >::Data
+{
+ MIn(MDefinition* key, MDefinition* obj)
+ : MBinaryInstruction(key, obj)
+ {
+ setResultType(MIRType::Boolean);
+ }
+
+ public:
+ INSTRUCTION_HEADER(In)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+
+// Test whether the index is in the array bounds or a hole.
+class MInArray
+ : public MQuaternaryInstruction,
+ public ObjectPolicy<3>::Data
+{
+ bool needsHoleCheck_;
+ bool needsNegativeIntCheck_;
+ JSValueType unboxedType_;
+
+ MInArray(MDefinition* elements, MDefinition* index,
+ MDefinition* initLength, MDefinition* object,
+ bool needsHoleCheck, JSValueType unboxedType)
+ : MQuaternaryInstruction(elements, index, initLength, object),
+ needsHoleCheck_(needsHoleCheck),
+ needsNegativeIntCheck_(true),
+ unboxedType_(unboxedType)
+ {
+ setResultType(MIRType::Boolean);
+ setMovable();
+ MOZ_ASSERT(elements->type() == MIRType::Elements);
+ MOZ_ASSERT(index->type() == MIRType::Int32);
+ MOZ_ASSERT(initLength->type() == MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(InArray)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements), (1, index), (2, initLength), (3, object))
+
+ bool needsHoleCheck() const {
+ return needsHoleCheck_;
+ }
+ bool needsNegativeIntCheck() const {
+ return needsNegativeIntCheck_;
+ }
+ JSValueType unboxedType() const {
+ return unboxedType_;
+ }
+ void collectRangeInfoPreTrunc() override;
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::Element);
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isInArray())
+ return false;
+ const MInArray* other = ins->toInArray();
+ if (needsHoleCheck() != other->needsHoleCheck())
+ return false;
+ if (needsNegativeIntCheck() != other->needsNegativeIntCheck())
+ return false;
+ if (unboxedType() != other->unboxedType())
+ return false;
+ return congruentIfOperandsEqual(other);
+ }
+};
+
+// Implementation for instanceof operator with specific rhs.
+class MInstanceOf
+ : public MUnaryInstruction,
+ public InstanceOfPolicy::Data
+{
+ CompilerObject protoObj_;
+
+ MInstanceOf(MDefinition* obj, JSObject* proto)
+ : MUnaryInstruction(obj),
+ protoObj_(proto)
+ {
+ setResultType(MIRType::Boolean);
+ }
+
+ public:
+ INSTRUCTION_HEADER(InstanceOf)
+ TRIVIAL_NEW_WRAPPERS
+
+ JSObject* prototypeObject() {
+ return protoObj_;
+ }
+
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(protoObj_);
+ }
+};
+
+// Implementation for instanceof operator with unknown rhs.
+class MCallInstanceOf
+ : public MBinaryInstruction,
+ public MixPolicy<BoxPolicy<0>, ObjectPolicy<1> >::Data
+{
+ MCallInstanceOf(MDefinition* obj, MDefinition* proto)
+ : MBinaryInstruction(obj, proto)
+ {
+ setResultType(MIRType::Boolean);
+ }
+
+ public:
+ INSTRUCTION_HEADER(CallInstanceOf)
+ TRIVIAL_NEW_WRAPPERS
+};
+
+class MArgumentsLength : public MNullaryInstruction
+{
+ MArgumentsLength()
+ {
+ setResultType(MIRType::Int32);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(ArgumentsLength)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ // Arguments |length| cannot be mutated by Ion Code.
+ return AliasSet::None();
+ }
+
+ void computeRange(TempAllocator& alloc) override;
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+};
+
+// This MIR instruction is used to get an argument from the actual arguments.
+class MGetFrameArgument
+ : public MUnaryInstruction,
+ public IntPolicy<0>::Data
+{
+ bool scriptHasSetArg_;
+
+ MGetFrameArgument(MDefinition* idx, bool scriptHasSetArg)
+ : MUnaryInstruction(idx),
+ scriptHasSetArg_(scriptHasSetArg)
+ {
+ setResultType(MIRType::Value);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(GetFrameArgument)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, index))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ // If the script doesn't have any JSOP_SETARG ops, then this instruction is never
+ // aliased.
+ if (scriptHasSetArg_)
+ return AliasSet::Load(AliasSet::FrameArgument);
+ return AliasSet::None();
+ }
+};
+
+class MNewTarget : public MNullaryInstruction
+{
+ MNewTarget() : MNullaryInstruction() {
+ setResultType(MIRType::Value);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(NewTarget)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+// This MIR instruction is used to set an argument value in the frame.
+class MSetFrameArgument
+ : public MUnaryInstruction,
+ public NoFloatPolicy<0>::Data
+{
+ uint32_t argno_;
+
+ MSetFrameArgument(uint32_t argno, MDefinition* value)
+ : MUnaryInstruction(value),
+ argno_(argno)
+ {
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(SetFrameArgument)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, value))
+
+ uint32_t argno() const {
+ return argno_;
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return false;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::FrameArgument);
+ }
+};
+
+class MRestCommon
+{
+ unsigned numFormals_;
+ CompilerGCPointer<ArrayObject*> templateObject_;
+
+ protected:
+ MRestCommon(unsigned numFormals, ArrayObject* templateObject)
+ : numFormals_(numFormals),
+ templateObject_(templateObject)
+ { }
+
+ public:
+ unsigned numFormals() const {
+ return numFormals_;
+ }
+ ArrayObject* templateObject() const {
+ return templateObject_;
+ }
+};
+
+class MRest
+ : public MUnaryInstruction,
+ public MRestCommon,
+ public IntPolicy<0>::Data
+{
+ MRest(CompilerConstraintList* constraints, MDefinition* numActuals, unsigned numFormals,
+ ArrayObject* templateObject)
+ : MUnaryInstruction(numActuals),
+ MRestCommon(numFormals, templateObject)
+ {
+ setResultType(MIRType::Object);
+ setResultTypeSet(MakeSingletonTypeSet(constraints, templateObject));
+ }
+
+ public:
+ INSTRUCTION_HEADER(Rest)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, numActuals))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool possiblyCalls() const override {
+ return true;
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(templateObject());
+ }
+};
+
+class MFilterTypeSet
+ : public MUnaryInstruction,
+ public FilterTypeSetPolicy::Data
+{
+ MFilterTypeSet(MDefinition* def, TemporaryTypeSet* types)
+ : MUnaryInstruction(def)
+ {
+ MOZ_ASSERT(!types->unknown());
+ setResultType(types->getKnownMIRType());
+ setResultTypeSet(types);
+ }
+
+ public:
+ INSTRUCTION_HEADER(FilterTypeSet)
+ TRIVIAL_NEW_WRAPPERS
+
+ bool congruentTo(const MDefinition* def) const override {
+ return false;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ virtual bool neverHoist() const override {
+ return resultTypeSet()->empty();
+ }
+ void computeRange(TempAllocator& alloc) override;
+
+ bool isFloat32Commutative() const override {
+ return IsFloatingPointType(type());
+ }
+
+ bool canProduceFloat32() const override;
+ bool canConsumeFloat32(MUse* operand) const override;
+ void trySpecializeFloat32(TempAllocator& alloc) override;
+};
+
+// Given a value, guard that the value is in a particular TypeSet, then returns
+// that value.
+class MTypeBarrier
+ : public MUnaryInstruction,
+ public TypeBarrierPolicy::Data
+{
+ BarrierKind barrierKind_;
+
+ MTypeBarrier(MDefinition* def, TemporaryTypeSet* types,
+ BarrierKind kind = BarrierKind::TypeSet)
+ : MUnaryInstruction(def),
+ barrierKind_(kind)
+ {
+ MOZ_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet);
+
+ MOZ_ASSERT(!types->unknown());
+ setResultType(types->getKnownMIRType());
+ setResultTypeSet(types);
+
+ setGuard();
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(TypeBarrier)
+ TRIVIAL_NEW_WRAPPERS
+
+ void printOpcode(GenericPrinter& out) const override;
+ bool congruentTo(const MDefinition* def) const override;
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ virtual bool neverHoist() const override {
+ return resultTypeSet()->empty();
+ }
+ BarrierKind barrierKind() const {
+ return barrierKind_;
+ }
+
+ bool alwaysBails() const {
+ // If mirtype of input doesn't agree with mirtype of barrier,
+ // we will definitely bail.
+ MIRType type = resultTypeSet()->getKnownMIRType();
+ if (type == MIRType::Value)
+ return false;
+ if (input()->type() == MIRType::Value)
+ return false;
+ if (input()->type() == MIRType::ObjectOrNull) {
+ // The ObjectOrNull optimization is only performed when the
+ // barrier's type is MIRType::Null.
+ MOZ_ASSERT(type == MIRType::Null);
+ return false;
+ }
+ return input()->type() != type;
+ }
+
+ ALLOW_CLONE(MTypeBarrier)
+};
+
+// Like MTypeBarrier, guard that the value is in the given type set. This is
+// used before property writes to ensure the value being written is represented
+// in the property types for the object.
+class MMonitorTypes
+ : public MUnaryInstruction,
+ public BoxInputsPolicy::Data
+{
+ const TemporaryTypeSet* typeSet_;
+ BarrierKind barrierKind_;
+
+ MMonitorTypes(MDefinition* def, const TemporaryTypeSet* types, BarrierKind kind)
+ : MUnaryInstruction(def),
+ typeSet_(types),
+ barrierKind_(kind)
+ {
+ MOZ_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet);
+
+ setGuard();
+ MOZ_ASSERT(!types->unknown());
+ }
+
+ public:
+ INSTRUCTION_HEADER(MonitorTypes)
+ TRIVIAL_NEW_WRAPPERS
+
+ const TemporaryTypeSet* typeSet() const {
+ return typeSet_;
+ }
+ BarrierKind barrierKind() const {
+ return barrierKind_;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+// Given a value being written to another object, update the generational store
+// buffer if the value is in the nursery and object is in the tenured heap.
+class MPostWriteBarrier : public MBinaryInstruction, public ObjectPolicy<0>::Data
+{
+ MPostWriteBarrier(MDefinition* obj, MDefinition* value)
+ : MBinaryInstruction(obj, value)
+ {
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(PostWriteBarrier)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, value))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+#ifdef DEBUG
+ bool isConsistentFloat32Use(MUse* use) const override {
+ // During lowering, values that neither have object nor value MIR type
+ // are ignored, thus Float32 can show up at this point without any issue.
+ return use == getUseFor(1);
+ }
+#endif
+
+ ALLOW_CLONE(MPostWriteBarrier)
+};
+
+// Given a value being written to another object's elements at the specified
+// index, update the generational store buffer if the value is in the nursery
+// and object is in the tenured heap.
+class MPostWriteElementBarrier : public MTernaryInstruction
+ , public MixPolicy<ObjectPolicy<0>, IntPolicy<2>>::Data
+{
+ MPostWriteElementBarrier(MDefinition* obj, MDefinition* value, MDefinition* index)
+ : MTernaryInstruction(obj, value, index)
+ {
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(PostWriteElementBarrier)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, value), (2, index))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+#ifdef DEBUG
+ bool isConsistentFloat32Use(MUse* use) const override {
+ // During lowering, values that neither have object nor value MIR type
+ // are ignored, thus Float32 can show up at this point without any issue.
+ return use == getUseFor(1);
+ }
+#endif
+
+ ALLOW_CLONE(MPostWriteElementBarrier)
+};
+
+class MNewNamedLambdaObject : public MNullaryInstruction
+{
+ CompilerGCPointer<LexicalEnvironmentObject*> templateObj_;
+
+ explicit MNewNamedLambdaObject(LexicalEnvironmentObject* templateObj)
+ : MNullaryInstruction(),
+ templateObj_(templateObj)
+ {
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(NewNamedLambdaObject)
+ TRIVIAL_NEW_WRAPPERS
+
+ LexicalEnvironmentObject* templateObj() {
+ return templateObj_;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(templateObj_);
+ }
+};
+
+class MNewCallObjectBase : public MNullaryInstruction
+{
+ CompilerGCPointer<CallObject*> templateObj_;
+
+ protected:
+ explicit MNewCallObjectBase(CallObject* templateObj)
+ : MNullaryInstruction(),
+ templateObj_(templateObj)
+ {
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ CallObject* templateObject() {
+ return templateObj_;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(templateObj_);
+ }
+};
+
+class MNewCallObject : public MNewCallObjectBase
+{
+ public:
+ INSTRUCTION_HEADER(NewCallObject)
+
+ explicit MNewCallObject(CallObject* templateObj)
+ : MNewCallObjectBase(templateObj)
+ {
+ MOZ_ASSERT(!templateObj->isSingleton());
+ }
+
+ static MNewCallObject*
+ New(TempAllocator& alloc, CallObject* templateObj)
+ {
+ return new(alloc) MNewCallObject(templateObj);
+ }
+};
+
+class MNewSingletonCallObject : public MNewCallObjectBase
+{
+ public:
+ INSTRUCTION_HEADER(NewSingletonCallObject)
+
+ explicit MNewSingletonCallObject(CallObject* templateObj)
+ : MNewCallObjectBase(templateObj)
+ {}
+
+ static MNewSingletonCallObject*
+ New(TempAllocator& alloc, CallObject* templateObj)
+ {
+ return new(alloc) MNewSingletonCallObject(templateObj);
+ }
+};
+
+class MNewStringObject :
+ public MUnaryInstruction,
+ public ConvertToStringPolicy<0>::Data
+{
+ CompilerObject templateObj_;
+
+ MNewStringObject(MDefinition* input, JSObject* templateObj)
+ : MUnaryInstruction(input),
+ templateObj_(templateObj)
+ {
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(NewStringObject)
+ TRIVIAL_NEW_WRAPPERS
+
+ StringObject* templateObj() const;
+
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(templateObj_);
+ }
+};
+
+// This is an alias for MLoadFixedSlot.
+class MEnclosingEnvironment : public MLoadFixedSlot
+{
+ explicit MEnclosingEnvironment(MDefinition* obj)
+ : MLoadFixedSlot(obj, EnvironmentObject::enclosingEnvironmentSlot())
+ {
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ static MEnclosingEnvironment* New(TempAllocator& alloc, MDefinition* obj) {
+ return new(alloc) MEnclosingEnvironment(obj);
+ }
+
+ AliasSet getAliasSet() const override {
+ // EnvironmentObject reserved slots are immutable.
+ return AliasSet::None();
+ }
+};
+
+// This is an element of a spaghetti stack which is used to represent the memory
+// context which has to be restored in case of a bailout.
+struct MStoreToRecover : public TempObject, public InlineSpaghettiStackNode<MStoreToRecover>
+{
+ MDefinition* operand;
+
+ explicit MStoreToRecover(MDefinition* operand)
+ : operand(operand)
+ { }
+};
+
+typedef InlineSpaghettiStack<MStoreToRecover> MStoresToRecoverList;
+
+// A resume point contains the information needed to reconstruct the Baseline
+// state from a position in the JIT. See the big comment near resumeAfter() in
+// IonBuilder.cpp.
+class MResumePoint final :
+ public MNode
+#ifdef DEBUG
+ , public InlineForwardListNode<MResumePoint>
+#endif
+{
+ public:
+ enum Mode {
+ ResumeAt, // Resume until before the current instruction
+ ResumeAfter, // Resume after the current instruction
+ Outer // State before inlining.
+ };
+
+ private:
+ friend class MBasicBlock;
+ friend void AssertBasicGraphCoherency(MIRGraph& graph);
+
+ // List of stack slots needed to reconstruct the frame corresponding to the
+ // function which is compiled by IonBuilder.
+ FixedList<MUse> operands_;
+
+ // List of stores needed to reconstruct the content of objects which are
+ // emulated by EmulateStateOf variants.
+ MStoresToRecoverList stores_;
+
+ jsbytecode* pc_;
+ MInstruction* instruction_;
+ Mode mode_;
+
+ MResumePoint(MBasicBlock* block, jsbytecode* pc, Mode mode);
+ void inherit(MBasicBlock* state);
+
+ protected:
+ // Initializes operands_ to an empty array of a fixed length.
+ // The array may then be filled in by inherit().
+ MOZ_MUST_USE bool init(TempAllocator& alloc);
+
+ void clearOperand(size_t index) {
+ // FixedList doesn't initialize its elements, so do an unchecked init.
+ operands_[index].initUncheckedWithoutProducer(this);
+ }
+
+ MUse* getUseFor(size_t index) override {
+ return &operands_[index];
+ }
+ const MUse* getUseFor(size_t index) const override {
+ return &operands_[index];
+ }
+
+ public:
+ static MResumePoint* New(TempAllocator& alloc, MBasicBlock* block, jsbytecode* pc,
+ Mode mode);
+ static MResumePoint* New(TempAllocator& alloc, MBasicBlock* block, MResumePoint* model,
+ const MDefinitionVector& operands);
+ static MResumePoint* Copy(TempAllocator& alloc, MResumePoint* src);
+
+ MNode::Kind kind() const override {
+ return MNode::ResumePoint;
+ }
+ size_t numAllocatedOperands() const {
+ return operands_.length();
+ }
+ uint32_t stackDepth() const {
+ return numAllocatedOperands();
+ }
+ size_t numOperands() const override {
+ return numAllocatedOperands();
+ }
+ size_t indexOf(const MUse* u) const final override {
+ MOZ_ASSERT(u >= &operands_[0]);
+ MOZ_ASSERT(u <= &operands_[numOperands() - 1]);
+ return u - &operands_[0];
+ }
+ void initOperand(size_t index, MDefinition* operand) {
+ // FixedList doesn't initialize its elements, so do an unchecked init.
+ operands_[index].initUnchecked(operand, this);
+ }
+ void replaceOperand(size_t index, MDefinition* operand) final override {
+ operands_[index].replaceProducer(operand);
+ }
+
+ bool isObservableOperand(MUse* u) const;
+ bool isObservableOperand(size_t index) const;
+ bool isRecoverableOperand(MUse* u) const;
+
+ MDefinition* getOperand(size_t index) const override {
+ return operands_[index].producer();
+ }
+ jsbytecode* pc() const {
+ return pc_;
+ }
+ MResumePoint* caller() const;
+ uint32_t frameCount() const {
+ uint32_t count = 1;
+ for (MResumePoint* it = caller(); it; it = it->caller())
+ count++;
+ return count;
+ }
+ MInstruction* instruction() {
+ return instruction_;
+ }
+ void setInstruction(MInstruction* ins) {
+ MOZ_ASSERT(!instruction_);
+ instruction_ = ins;
+ }
+ // Only to be used by stealResumePoint.
+ void replaceInstruction(MInstruction* ins) {
+ MOZ_ASSERT(instruction_);
+ instruction_ = ins;
+ }
+ void resetInstruction() {
+ MOZ_ASSERT(instruction_);
+ instruction_ = nullptr;
+ }
+ Mode mode() const {
+ return mode_;
+ }
+
+ void releaseUses() {
+ for (size_t i = 0, e = numOperands(); i < e; i++) {
+ if (operands_[i].hasProducer())
+ operands_[i].releaseProducer();
+ }
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+
+ // Register a store instruction on the current resume point. This
+ // instruction would be recovered when we are bailing out. The |cache|
+ // argument can be any resume point, it is used to share memory if we are
+ // doing the same modification.
+ void addStore(TempAllocator& alloc, MDefinition* store, const MResumePoint* cache = nullptr);
+
+ MStoresToRecoverList::iterator storesBegin() const {
+ return stores_.begin();
+ }
+ MStoresToRecoverList::iterator storesEnd() const {
+ return stores_.end();
+ }
+
+ virtual void dump(GenericPrinter& out) const override;
+ virtual void dump() const override;
+};
+
+class MIsCallable
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MIsCallable(MDefinition* object)
+ : MUnaryInstruction(object)
+ {
+ setResultType(MIRType::Boolean);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(IsCallable)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MIsConstructor
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ public:
+ explicit MIsConstructor(MDefinition* object)
+ : MUnaryInstruction(object)
+ {
+ setResultType(MIRType::Boolean);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(IsConstructor)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MIsObject
+ : public MUnaryInstruction,
+ public BoxInputsPolicy::Data
+{
+ explicit MIsObject(MDefinition* object)
+ : MUnaryInstruction(object)
+ {
+ setResultType(MIRType::Boolean);
+ setMovable();
+ }
+ public:
+ INSTRUCTION_HEADER(IsObject)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MHasClass
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ const Class* class_;
+
+ MHasClass(MDefinition* object, const Class* clasp)
+ : MUnaryInstruction(object)
+ , class_(clasp)
+ {
+ MOZ_ASSERT(object->type() == MIRType::Object);
+ setResultType(MIRType::Boolean);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(HasClass)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ const Class* getClass() const {
+ return class_;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!ins->isHasClass())
+ return false;
+ if (getClass() != ins->toHasClass()->getClass())
+ return false;
+ return congruentIfOperandsEqual(ins);
+ }
+};
+
+class MCheckReturn
+ : public MBinaryInstruction,
+ public BoxInputsPolicy::Data
+{
+ explicit MCheckReturn(MDefinition* retVal, MDefinition* thisVal)
+ : MBinaryInstruction(retVal, thisVal)
+ {
+ setGuard();
+ setResultType(MIRType::Value);
+ setResultTypeSet(retVal->resultTypeSet());
+ }
+
+ public:
+ INSTRUCTION_HEADER(CheckReturn)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, returnValue), (1, thisValue))
+
+};
+
+// Increase the warm-up counter of the provided script upon execution and test if
+// the warm-up counter surpasses the threshold. Upon hit it will recompile the
+// outermost script (i.e. not the inlined script).
+class MRecompileCheck : public MNullaryInstruction
+{
+ public:
+ enum RecompileCheckType {
+ RecompileCheck_OptimizationLevel,
+ RecompileCheck_Inlining
+ };
+
+ private:
+ JSScript* script_;
+ uint32_t recompileThreshold_;
+ bool forceRecompilation_;
+ bool increaseWarmUpCounter_;
+
+ MRecompileCheck(JSScript* script, uint32_t recompileThreshold, RecompileCheckType type)
+ : script_(script),
+ recompileThreshold_(recompileThreshold)
+ {
+ switch (type) {
+ case RecompileCheck_OptimizationLevel:
+ forceRecompilation_ = false;
+ increaseWarmUpCounter_ = true;
+ break;
+ case RecompileCheck_Inlining:
+ forceRecompilation_ = true;
+ increaseWarmUpCounter_ = false;
+ break;
+ default:
+ MOZ_CRASH("Unexpected recompile check type");
+ }
+
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(RecompileCheck)
+ TRIVIAL_NEW_WRAPPERS
+
+ JSScript* script() const {
+ return script_;
+ }
+
+ uint32_t recompileThreshold() const {
+ return recompileThreshold_;
+ }
+
+ bool forceRecompilation() const {
+ return forceRecompilation_;
+ }
+
+ bool increaseWarmUpCounter() const {
+ return increaseWarmUpCounter_;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MAtomicIsLockFree
+ : public MUnaryInstruction,
+ public ConvertToInt32Policy<0>::Data
+{
+ explicit MAtomicIsLockFree(MDefinition* value)
+ : MUnaryInstruction(value)
+ {
+ setResultType(MIRType::Boolean);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(AtomicIsLockFree)
+ TRIVIAL_NEW_WRAPPERS
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+ bool canRecoverOnBailout() const override {
+ return true;
+ }
+
+ ALLOW_CLONE(MAtomicIsLockFree)
+};
+
+// This applies to an object that is known to be a TypedArray, it bails out
+// if the obj does not map a SharedArrayBuffer.
+
+class MGuardSharedTypedArray
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MGuardSharedTypedArray(MDefinition* obj)
+ : MUnaryInstruction(obj)
+ {
+ setGuard();
+ setMovable();
+ }
+
+public:
+ INSTRUCTION_HEADER(GuardSharedTypedArray)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MCompareExchangeTypedArrayElement
+ : public MAryInstruction<4>,
+ public Mix4Policy<ObjectPolicy<0>, IntPolicy<1>, TruncateToInt32Policy<2>, TruncateToInt32Policy<3>>::Data
+{
+ Scalar::Type arrayType_;
+
+ explicit MCompareExchangeTypedArrayElement(MDefinition* elements, MDefinition* index,
+ Scalar::Type arrayType, MDefinition* oldval,
+ MDefinition* newval)
+ : arrayType_(arrayType)
+ {
+ initOperand(0, elements);
+ initOperand(1, index);
+ initOperand(2, oldval);
+ initOperand(3, newval);
+ setGuard(); // Not removable
+ }
+
+ public:
+ INSTRUCTION_HEADER(CompareExchangeTypedArrayElement)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements), (1, index), (2, oldval), (3, newval))
+
+ bool isByteArray() const {
+ return (arrayType_ == Scalar::Int8 ||
+ arrayType_ == Scalar::Uint8);
+ }
+ int oldvalOperand() {
+ return 2;
+ }
+ Scalar::Type arrayType() const {
+ return arrayType_;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::UnboxedElement);
+ }
+};
+
+class MAtomicExchangeTypedArrayElement
+ : public MAryInstruction<3>,
+ public Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, TruncateToInt32Policy<2>>::Data
+{
+ Scalar::Type arrayType_;
+
+ MAtomicExchangeTypedArrayElement(MDefinition* elements, MDefinition* index, MDefinition* value,
+ Scalar::Type arrayType)
+ : arrayType_(arrayType)
+ {
+ MOZ_ASSERT(arrayType <= Scalar::Uint32);
+ initOperand(0, elements);
+ initOperand(1, index);
+ initOperand(2, value);
+ setGuard(); // Not removable
+ }
+
+ public:
+ INSTRUCTION_HEADER(AtomicExchangeTypedArrayElement)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements), (1, index), (2, value))
+
+ bool isByteArray() const {
+ return (arrayType_ == Scalar::Int8 ||
+ arrayType_ == Scalar::Uint8);
+ }
+ Scalar::Type arrayType() const {
+ return arrayType_;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::UnboxedElement);
+ }
+};
+
+class MAtomicTypedArrayElementBinop
+ : public MAryInstruction<3>,
+ public Mix3Policy< ObjectPolicy<0>, IntPolicy<1>, TruncateToInt32Policy<2> >::Data
+{
+ private:
+ AtomicOp op_;
+ Scalar::Type arrayType_;
+
+ protected:
+ explicit MAtomicTypedArrayElementBinop(AtomicOp op, MDefinition* elements, MDefinition* index,
+ Scalar::Type arrayType, MDefinition* value)
+ : op_(op),
+ arrayType_(arrayType)
+ {
+ initOperand(0, elements);
+ initOperand(1, index);
+ initOperand(2, value);
+ setGuard(); // Not removable
+ }
+
+ public:
+ INSTRUCTION_HEADER(AtomicTypedArrayElementBinop)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, elements), (1, index), (2, value))
+
+ bool isByteArray() const {
+ return (arrayType_ == Scalar::Int8 ||
+ arrayType_ == Scalar::Uint8);
+ }
+ AtomicOp operation() const {
+ return op_;
+ }
+ Scalar::Type arrayType() const {
+ return arrayType_;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::UnboxedElement);
+ }
+};
+
+class MDebugger : public MNullaryInstruction
+{
+ public:
+ INSTRUCTION_HEADER(Debugger)
+ TRIVIAL_NEW_WRAPPERS
+};
+
+class MCheckIsObj
+ : public MUnaryInstruction,
+ public BoxInputsPolicy::Data
+{
+ uint8_t checkKind_;
+
+ explicit MCheckIsObj(MDefinition* toCheck, uint8_t checkKind)
+ : MUnaryInstruction(toCheck), checkKind_(checkKind)
+ {
+ setResultType(MIRType::Value);
+ setResultTypeSet(toCheck->resultTypeSet());
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(CheckIsObj)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, checkValue))
+
+ uint8_t checkKind() const { return checkKind_; }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MCheckObjCoercible
+ : public MUnaryInstruction,
+ public BoxInputsPolicy::Data
+{
+ explicit MCheckObjCoercible(MDefinition* toCheck)
+ : MUnaryInstruction(toCheck)
+ {
+ setGuard();
+ setResultType(MIRType::Value);
+ setResultTypeSet(toCheck->resultTypeSet());
+ }
+
+ public:
+ INSTRUCTION_HEADER(CheckObjCoercible)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, checkValue))
+};
+
+class MDebugCheckSelfHosted
+ : public MUnaryInstruction,
+ public BoxInputsPolicy::Data
+{
+ explicit MDebugCheckSelfHosted(MDefinition* toCheck)
+ : MUnaryInstruction(toCheck)
+ {
+ setGuard();
+ setResultType(MIRType::Value);
+ setResultTypeSet(toCheck->resultTypeSet());
+ }
+
+ public:
+ INSTRUCTION_HEADER(DebugCheckSelfHosted)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, checkValue))
+
+};
+
+class MAsmJSNeg
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ MAsmJSNeg(MDefinition* op, MIRType type)
+ : MUnaryInstruction(op)
+ {
+ setResultType(type);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(AsmJSNeg)
+ TRIVIAL_NEW_WRAPPERS
+};
+
+class MWasmBoundsCheck
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ bool redundant_;
+ wasm::TrapOffset trapOffset_;
+
+ explicit MWasmBoundsCheck(MDefinition* index, wasm::TrapOffset trapOffset)
+ : MUnaryInstruction(index),
+ redundant_(false),
+ trapOffset_(trapOffset)
+ {
+ setGuard(); // Effectful: throws for OOB.
+ }
+
+ public:
+ INSTRUCTION_HEADER(WasmBoundsCheck)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool isRedundant() const {
+ return redundant_;
+ }
+
+ void setRedundant(bool val) {
+ redundant_ = val;
+ }
+
+ wasm::TrapOffset trapOffset() const {
+ return trapOffset_;
+ }
+};
+
+class MWasmAddOffset
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ uint32_t offset_;
+ wasm::TrapOffset trapOffset_;
+
+ MWasmAddOffset(MDefinition* base, uint32_t offset, wasm::TrapOffset trapOffset)
+ : MUnaryInstruction(base),
+ offset_(offset),
+ trapOffset_(trapOffset)
+ {
+ setGuard();
+ setResultType(MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(WasmAddOffset)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, base))
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ uint32_t offset() const {
+ return offset_;
+ }
+ wasm::TrapOffset trapOffset() const {
+ return trapOffset_;
+ }
+};
+
+class MWasmLoad
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ wasm::MemoryAccessDesc access_;
+
+ MWasmLoad(MDefinition* base, const wasm::MemoryAccessDesc& access, MIRType resultType)
+ : MUnaryInstruction(base),
+ access_(access)
+ {
+ setGuard();
+ setResultType(resultType);
+ }
+
+ public:
+ INSTRUCTION_HEADER(WasmLoad)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, base))
+
+ const wasm::MemoryAccessDesc& access() const {
+ return access_;
+ }
+
+ AliasSet getAliasSet() const override {
+ // When a barrier is needed, make the instruction effectful by giving
+ // it a "store" effect.
+ if (access_.isAtomic())
+ return AliasSet::Store(AliasSet::WasmHeap);
+ return AliasSet::Load(AliasSet::WasmHeap);
+ }
+};
+
+class MWasmStore
+ : public MBinaryInstruction,
+ public NoTypePolicy::Data
+{
+ wasm::MemoryAccessDesc access_;
+
+ MWasmStore(MDefinition* base, const wasm::MemoryAccessDesc& access, MDefinition* value)
+ : MBinaryInstruction(base, value),
+ access_(access)
+ {
+ setGuard();
+ }
+
+ public:
+ INSTRUCTION_HEADER(WasmStore)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, base), (1, value))
+
+ const wasm::MemoryAccessDesc& access() const {
+ return access_;
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::WasmHeap);
+ }
+};
+
+class MAsmJSMemoryAccess
+{
+ uint32_t offset_;
+ Scalar::Type accessType_;
+ bool needsBoundsCheck_;
+
+ public:
+ explicit MAsmJSMemoryAccess(Scalar::Type accessType)
+ : offset_(0),
+ accessType_(accessType),
+ needsBoundsCheck_(true)
+ {
+ MOZ_ASSERT(accessType != Scalar::Uint8Clamped);
+ MOZ_ASSERT(!Scalar::isSimdType(accessType));
+ }
+
+ uint32_t offset() const { return offset_; }
+ uint32_t endOffset() const { return offset() + byteSize(); }
+ Scalar::Type accessType() const { return accessType_; }
+ unsigned byteSize() const { return TypedArrayElemSize(accessType()); }
+ bool needsBoundsCheck() const { return needsBoundsCheck_; }
+
+ wasm::MemoryAccessDesc access() const {
+ return wasm::MemoryAccessDesc(accessType_, Scalar::byteSize(accessType_), offset_,
+ mozilla::Nothing());
+ }
+
+ void removeBoundsCheck() { needsBoundsCheck_ = false; }
+ void setOffset(uint32_t o) { offset_ = o; }
+};
+
+class MAsmJSLoadHeap
+ : public MUnaryInstruction,
+ public MAsmJSMemoryAccess,
+ public NoTypePolicy::Data
+{
+ MAsmJSLoadHeap(MDefinition* base, Scalar::Type accessType)
+ : MUnaryInstruction(base),
+ MAsmJSMemoryAccess(accessType)
+ {
+ setResultType(ScalarTypeToMIRType(accessType));
+ }
+
+ public:
+ INSTRUCTION_HEADER(AsmJSLoadHeap)
+ TRIVIAL_NEW_WRAPPERS
+
+ MDefinition* base() const { return getOperand(0); }
+ void replaceBase(MDefinition* newBase) { replaceOperand(0, newBase); }
+
+ bool congruentTo(const MDefinition* ins) const override;
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::WasmHeap);
+ }
+ AliasType mightAlias(const MDefinition* def) const override;
+};
+
+class MAsmJSStoreHeap
+ : public MBinaryInstruction,
+ public MAsmJSMemoryAccess,
+ public NoTypePolicy::Data
+{
+ MAsmJSStoreHeap(MDefinition* base, Scalar::Type accessType, MDefinition* v)
+ : MBinaryInstruction(base, v),
+ MAsmJSMemoryAccess(accessType)
+ {}
+
+ public:
+ INSTRUCTION_HEADER(AsmJSStoreHeap)
+ TRIVIAL_NEW_WRAPPERS
+
+ MDefinition* base() const { return getOperand(0); }
+ void replaceBase(MDefinition* newBase) { replaceOperand(0, newBase); }
+ MDefinition* value() const { return getOperand(1); }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::WasmHeap);
+ }
+};
+
+class MAsmJSCompareExchangeHeap
+ : public MQuaternaryInstruction,
+ public NoTypePolicy::Data
+{
+ wasm::MemoryAccessDesc access_;
+
+ MAsmJSCompareExchangeHeap(MDefinition* base, const wasm::MemoryAccessDesc& access,
+ MDefinition* oldv, MDefinition* newv, MDefinition* tls)
+ : MQuaternaryInstruction(base, oldv, newv, tls),
+ access_(access)
+ {
+ setGuard(); // Not removable
+ setResultType(MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(AsmJSCompareExchangeHeap)
+ TRIVIAL_NEW_WRAPPERS
+
+ const wasm::MemoryAccessDesc& access() const { return access_; }
+
+ MDefinition* base() const { return getOperand(0); }
+ MDefinition* oldValue() const { return getOperand(1); }
+ MDefinition* newValue() const { return getOperand(2); }
+ MDefinition* tls() const { return getOperand(3); }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::WasmHeap);
+ }
+};
+
+class MAsmJSAtomicExchangeHeap
+ : public MTernaryInstruction,
+ public NoTypePolicy::Data
+{
+ wasm::MemoryAccessDesc access_;
+
+ MAsmJSAtomicExchangeHeap(MDefinition* base, const wasm::MemoryAccessDesc& access,
+ MDefinition* value, MDefinition* tls)
+ : MTernaryInstruction(base, value, tls),
+ access_(access)
+ {
+ setGuard(); // Not removable
+ setResultType(MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(AsmJSAtomicExchangeHeap)
+ TRIVIAL_NEW_WRAPPERS
+
+ const wasm::MemoryAccessDesc& access() const { return access_; }
+
+ MDefinition* base() const { return getOperand(0); }
+ MDefinition* value() const { return getOperand(1); }
+ MDefinition* tls() const { return getOperand(2); }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::WasmHeap);
+ }
+};
+
+class MAsmJSAtomicBinopHeap
+ : public MTernaryInstruction,
+ public NoTypePolicy::Data
+{
+ AtomicOp op_;
+ wasm::MemoryAccessDesc access_;
+
+ MAsmJSAtomicBinopHeap(AtomicOp op, MDefinition* base, const wasm::MemoryAccessDesc& access,
+ MDefinition* v, MDefinition* tls)
+ : MTernaryInstruction(base, v, tls),
+ op_(op),
+ access_(access)
+ {
+ setGuard(); // Not removable
+ setResultType(MIRType::Int32);
+ }
+
+ public:
+ INSTRUCTION_HEADER(AsmJSAtomicBinopHeap)
+ TRIVIAL_NEW_WRAPPERS
+
+ AtomicOp operation() const { return op_; }
+ const wasm::MemoryAccessDesc& access() const { return access_; }
+
+ MDefinition* base() const { return getOperand(0); }
+ MDefinition* value() const { return getOperand(1); }
+ MDefinition* tls() const { return getOperand(2); }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::WasmHeap);
+ }
+};
+
+class MWasmLoadGlobalVar : public MNullaryInstruction
+{
+ MWasmLoadGlobalVar(MIRType type, unsigned globalDataOffset, bool isConstant)
+ : globalDataOffset_(globalDataOffset), isConstant_(isConstant)
+ {
+ MOZ_ASSERT(IsNumberType(type) || IsSimdType(type));
+ setResultType(type);
+ setMovable();
+ }
+
+ unsigned globalDataOffset_;
+ bool isConstant_;
+
+ public:
+ INSTRUCTION_HEADER(WasmLoadGlobalVar)
+ TRIVIAL_NEW_WRAPPERS
+
+ unsigned globalDataOffset() const { return globalDataOffset_; }
+
+ HashNumber valueHash() const override;
+ bool congruentTo(const MDefinition* ins) const override;
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+
+ AliasSet getAliasSet() const override {
+ return isConstant_ ? AliasSet::None() : AliasSet::Load(AliasSet::WasmGlobalVar);
+ }
+
+ AliasType mightAlias(const MDefinition* def) const override;
+};
+
+class MWasmStoreGlobalVar
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ MWasmStoreGlobalVar(unsigned globalDataOffset, MDefinition* v)
+ : MUnaryInstruction(v), globalDataOffset_(globalDataOffset)
+ {}
+
+ unsigned globalDataOffset_;
+
+ public:
+ INSTRUCTION_HEADER(WasmStoreGlobalVar)
+ TRIVIAL_NEW_WRAPPERS
+
+ unsigned globalDataOffset() const { return globalDataOffset_; }
+ MDefinition* value() const { return getOperand(0); }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::WasmGlobalVar);
+ }
+};
+
+class MWasmParameter : public MNullaryInstruction
+{
+ ABIArg abi_;
+
+ MWasmParameter(ABIArg abi, MIRType mirType)
+ : abi_(abi)
+ {
+ setResultType(mirType);
+ }
+
+ public:
+ INSTRUCTION_HEADER(WasmParameter)
+ TRIVIAL_NEW_WRAPPERS
+
+ ABIArg abi() const { return abi_; }
+};
+
+class MWasmReturn
+ : public MAryControlInstruction<2, 0>,
+ public NoTypePolicy::Data
+{
+ explicit MWasmReturn(MDefinition* ins, MDefinition* tlsPtr) {
+ initOperand(0, ins);
+ initOperand(1, tlsPtr);
+ }
+
+ public:
+ INSTRUCTION_HEADER(WasmReturn)
+ TRIVIAL_NEW_WRAPPERS
+};
+
+class MWasmReturnVoid
+ : public MAryControlInstruction<1, 0>,
+ public NoTypePolicy::Data
+{
+ explicit MWasmReturnVoid(MDefinition* tlsPtr) {
+ initOperand(0, tlsPtr);
+ }
+
+ public:
+ INSTRUCTION_HEADER(WasmReturnVoid)
+ TRIVIAL_NEW_WRAPPERS
+};
+
+class MWasmStackArg
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ MWasmStackArg(uint32_t spOffset, MDefinition* ins)
+ : MUnaryInstruction(ins),
+ spOffset_(spOffset)
+ {}
+
+ uint32_t spOffset_;
+
+ public:
+ INSTRUCTION_HEADER(WasmStackArg)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, arg))
+
+ uint32_t spOffset() const {
+ return spOffset_;
+ }
+ void incrementOffset(uint32_t inc) {
+ spOffset_ += inc;
+ }
+};
+
+class MWasmCall final
+ : public MVariadicInstruction,
+ public NoTypePolicy::Data
+{
+ wasm::CallSiteDesc desc_;
+ wasm::CalleeDesc callee_;
+ FixedList<AnyRegister> argRegs_;
+ uint32_t spIncrement_;
+ uint32_t tlsStackOffset_;
+ ABIArg instanceArg_;
+
+ MWasmCall(const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee, uint32_t spIncrement,
+ uint32_t tlsStackOffset)
+ : desc_(desc),
+ callee_(callee),
+ spIncrement_(spIncrement),
+ tlsStackOffset_(tlsStackOffset)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(WasmCall)
+
+ struct Arg {
+ AnyRegister reg;
+ MDefinition* def;
+ Arg(AnyRegister reg, MDefinition* def) : reg(reg), def(def) {}
+ };
+ typedef Vector<Arg, 8, SystemAllocPolicy> Args;
+
+ static const uint32_t DontSaveTls = UINT32_MAX;
+
+ static MWasmCall* New(TempAllocator& alloc,
+ const wasm::CallSiteDesc& desc,
+ const wasm::CalleeDesc& callee,
+ const Args& args,
+ MIRType resultType,
+ uint32_t spIncrement,
+ uint32_t tlsStackOffset,
+ MDefinition* tableIndex = nullptr);
+
+ static MWasmCall* NewBuiltinInstanceMethodCall(TempAllocator& alloc,
+ const wasm::CallSiteDesc& desc,
+ const wasm::SymbolicAddress builtin,
+ const ABIArg& instanceArg,
+ const Args& args,
+ MIRType resultType,
+ uint32_t spIncrement,
+ uint32_t tlsStackOffset);
+
+ size_t numArgs() const {
+ return argRegs_.length();
+ }
+ AnyRegister registerForArg(size_t index) const {
+ MOZ_ASSERT(index < numArgs());
+ return argRegs_[index];
+ }
+ const wasm::CallSiteDesc& desc() const {
+ return desc_;
+ }
+ const wasm::CalleeDesc &callee() const {
+ return callee_;
+ }
+ uint32_t spIncrement() const {
+ return spIncrement_;
+ }
+ bool saveTls() const {
+ return tlsStackOffset_ != DontSaveTls;
+ }
+ uint32_t tlsStackOffset() const {
+ MOZ_ASSERT(saveTls());
+ return tlsStackOffset_;
+ }
+
+ bool possiblyCalls() const override {
+ return true;
+ }
+
+ const ABIArg& instanceArg() const {
+ return instanceArg_;
+ }
+};
+
+class MWasmSelect
+ : public MTernaryInstruction,
+ public NoTypePolicy::Data
+{
+ MWasmSelect(MDefinition* trueExpr, MDefinition* falseExpr, MDefinition *condExpr)
+ : MTernaryInstruction(trueExpr, falseExpr, condExpr)
+ {
+ MOZ_ASSERT(condExpr->type() == MIRType::Int32);
+ MOZ_ASSERT(trueExpr->type() == falseExpr->type());
+ setResultType(trueExpr->type());
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(WasmSelect)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, trueExpr), (1, falseExpr), (2, condExpr))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ ALLOW_CLONE(MWasmSelect)
+};
+
+class MWasmReinterpret
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+{
+ MWasmReinterpret(MDefinition* val, MIRType toType)
+ : MUnaryInstruction(val)
+ {
+ switch (val->type()) {
+ case MIRType::Int32: MOZ_ASSERT(toType == MIRType::Float32); break;
+ case MIRType::Float32: MOZ_ASSERT(toType == MIRType::Int32); break;
+ case MIRType::Double: MOZ_ASSERT(toType == MIRType::Int64); break;
+ case MIRType::Int64: MOZ_ASSERT(toType == MIRType::Double); break;
+ default: MOZ_CRASH("unexpected reinterpret conversion");
+ }
+ setMovable();
+ setResultType(toType);
+ }
+
+ public:
+ INSTRUCTION_HEADER(WasmReinterpret)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+
+ ALLOW_CLONE(MWasmReinterpret)
+};
+
+class MRotate
+ : public MBinaryInstruction,
+ public NoTypePolicy::Data
+{
+ bool isLeftRotate_;
+
+ MRotate(MDefinition* input, MDefinition* count, MIRType type, bool isLeftRotate)
+ : MBinaryInstruction(input, count), isLeftRotate_(isLeftRotate)
+ {
+ setMovable();
+ setResultType(type);
+ }
+
+ public:
+ INSTRUCTION_HEADER(Rotate)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, input), (1, count))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins) && ins->toRotate()->isLeftRotate() == isLeftRotate_;
+ }
+
+ bool isLeftRotate() const {
+ return isLeftRotate_;
+ }
+
+ ALLOW_CLONE(MRotate)
+};
+
+class MUnknownValue : public MNullaryInstruction
+{
+ protected:
+ MUnknownValue() {
+ setResultType(MIRType::Value);
+ }
+
+ public:
+ INSTRUCTION_HEADER(UnknownValue)
+ TRIVIAL_NEW_WRAPPERS
+};
+
+#undef INSTRUCTION_HEADER
+
+void MUse::init(MDefinition* producer, MNode* consumer)
+{
+ MOZ_ASSERT(!consumer_, "Initializing MUse that already has a consumer");
+ MOZ_ASSERT(!producer_, "Initializing MUse that already has a producer");
+ initUnchecked(producer, consumer);
+}
+
+void MUse::initUnchecked(MDefinition* producer, MNode* consumer)
+{
+ MOZ_ASSERT(consumer, "Initializing to null consumer");
+ consumer_ = consumer;
+ producer_ = producer;
+ producer_->addUseUnchecked(this);
+}
+
+void MUse::initUncheckedWithoutProducer(MNode* consumer)
+{
+ MOZ_ASSERT(consumer, "Initializing to null consumer");
+ consumer_ = consumer;
+ producer_ = nullptr;
+}
+
+void MUse::replaceProducer(MDefinition* producer)
+{
+ MOZ_ASSERT(consumer_, "Resetting MUse without a consumer");
+ producer_->removeUse(this);
+ producer_ = producer;
+ producer_->addUse(this);
+}
+
+void MUse::releaseProducer()
+{
+ MOZ_ASSERT(consumer_, "Clearing MUse without a consumer");
+ producer_->removeUse(this);
+ producer_ = nullptr;
+}
+
+// Implement cast functions now that the compiler can see the inheritance.
+
+MDefinition*
+MNode::toDefinition()
+{
+ MOZ_ASSERT(isDefinition());
+ return (MDefinition*)this;
+}
+
+MResumePoint*
+MNode::toResumePoint()
+{
+ MOZ_ASSERT(isResumePoint());
+ return (MResumePoint*)this;
+}
+
+MInstruction*
+MDefinition::toInstruction()
+{
+ MOZ_ASSERT(!isPhi());
+ return (MInstruction*)this;
+}
+
+const MInstruction*
+MDefinition::toInstruction() const
+{
+ MOZ_ASSERT(!isPhi());
+ return (const MInstruction*)this;
+}
+
+MControlInstruction*
+MDefinition::toControlInstruction()
+{
+ MOZ_ASSERT(isControlInstruction());
+ return (MControlInstruction*)this;
+}
+
+MConstant*
+MDefinition::maybeConstantValue()
+{
+ MDefinition* op = this;
+ if (op->isBox())
+ op = op->toBox()->input();
+ if (op->isConstant())
+ return op->toConstant();
+ return nullptr;
+}
+
+// Helper functions used to decide how to build MIR.
+
+bool ElementAccessIsDenseNative(CompilerConstraintList* constraints,
+ MDefinition* obj, MDefinition* id);
+JSValueType UnboxedArrayElementType(CompilerConstraintList* constraints, MDefinition* obj,
+ MDefinition* id);
+bool ElementAccessIsTypedArray(CompilerConstraintList* constraints,
+ MDefinition* obj, MDefinition* id,
+ Scalar::Type* arrayType);
+bool ElementAccessIsPacked(CompilerConstraintList* constraints, MDefinition* obj);
+bool ElementAccessMightBeCopyOnWrite(CompilerConstraintList* constraints, MDefinition* obj);
+bool ElementAccessMightBeFrozen(CompilerConstraintList* constraints, MDefinition* obj);
+bool ElementAccessHasExtraIndexedProperty(IonBuilder* builder, MDefinition* obj);
+MIRType DenseNativeElementType(CompilerConstraintList* constraints, MDefinition* obj);
+BarrierKind PropertyReadNeedsTypeBarrier(JSContext* propertycx,
+ CompilerConstraintList* constraints,
+ TypeSet::ObjectKey* key, PropertyName* name,
+ TemporaryTypeSet* observed, bool updateObserved);
+BarrierKind PropertyReadNeedsTypeBarrier(JSContext* propertycx,
+ CompilerConstraintList* constraints,
+ MDefinition* obj, PropertyName* name,
+ TemporaryTypeSet* observed);
+ResultWithOOM<BarrierKind>
+PropertyReadOnPrototypeNeedsTypeBarrier(IonBuilder* builder,
+ MDefinition* obj, PropertyName* name,
+ TemporaryTypeSet* observed);
+bool PropertyReadIsIdempotent(CompilerConstraintList* constraints,
+ MDefinition* obj, PropertyName* name);
+void AddObjectsForPropertyRead(MDefinition* obj, PropertyName* name,
+ TemporaryTypeSet* observed);
+bool CanWriteProperty(TempAllocator& alloc, CompilerConstraintList* constraints,
+ HeapTypeSetKey property, MDefinition* value,
+ MIRType implicitType = MIRType::None);
+bool PropertyWriteNeedsTypeBarrier(TempAllocator& alloc, CompilerConstraintList* constraints,
+ MBasicBlock* current, MDefinition** pobj,
+ PropertyName* name, MDefinition** pvalue,
+ bool canModify, MIRType implicitType = MIRType::None);
+bool ArrayPrototypeHasIndexedProperty(IonBuilder* builder, JSScript* script);
+bool TypeCanHaveExtraIndexedProperties(IonBuilder* builder, TemporaryTypeSet* types);
+
+inline MIRType
+MIRTypeForTypedArrayRead(Scalar::Type arrayType, bool observedDouble)
+{
+ switch (arrayType) {
+ case Scalar::Int8:
+ case Scalar::Uint8:
+ case Scalar::Uint8Clamped:
+ case Scalar::Int16:
+ case Scalar::Uint16:
+ case Scalar::Int32:
+ return MIRType::Int32;
+ case Scalar::Uint32:
+ return observedDouble ? MIRType::Double : MIRType::Int32;
+ case Scalar::Float32:
+ return MIRType::Float32;
+ case Scalar::Float64:
+ return MIRType::Double;
+ default:
+ break;
+ }
+ MOZ_CRASH("Unknown typed array type");
+}
+
+} // namespace jit
+} // namespace js
+
+#endif /* jit_MIR_h */