| /* -*- 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/DebugOnly.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/ScopeObject.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; |
| |
| 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(!"Unexpected magic constant"); |
| } |
| } |
| return MIRTypeFromValueType(vp.extractNonDoubleType()); |
| } |
| |
| #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 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 |
| AsmJSGlobalVar = 1 << 7, // An asm.js global var |
| AsmJSHeap = 1 << 8, // An asm.js 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: |
| 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; |
| } |
| }; |
| |
| // 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 { |
| MInstruction* dependency_; // Implicit dependency (store, call, etc.) of this 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), |
| dependency_(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_), |
| dependency_(other.dependency_), |
| 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 |
| }; |
| |
| // |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. |
| void 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 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 |
| |
| bool isConstantValue() const { |
| return isConstant() || (isBox() && getOperand(0)->isConstant()); |
| } |
| const Value& constantValue(); |
| const Value* constantVp(); |
| bool constantToBoolean(); |
| |
| 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; |
| } |
| |
| MInstruction* dependency() const { |
| return dependency_; |
| } |
| void setDependency(MInstruction* dependency) { |
| dependency_ = dependency; |
| } |
| virtual AliasSet getAliasSet() const { |
| // Instructions are effectful by default. |
| return AliasSet::Store(AliasSet::Any); |
| } |
| 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 |
| virtual bool 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. |
| MOZ_ASSERT(!isEffectful() && store->isEffectful()); |
| MOZ_ASSERT(getAliasSet().flags() & store->getAliasSet().flags()); |
| return true; |
| } |
| |
| 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(); |
| } |
| }; |
| |
| typedef Vector<MDefinition*, 8, JitAllocPolicy> MDefinitionVector; |
| typedef Vector<MInstruction*, 6, JitAllocPolicy> MInstructionVector; |
| |
| // 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_; |
| |
| 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. |
| // |
| // Note: There is no need for such function in AsmJS functions as they do |
| // not use any MIRType_Value. |
| MDefinition* foldsToStoredValue(TempAllocator& alloc, MDefinition* loaded); |
| |
| 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(); |
| } |
| |
| // 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; \ |
| 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; \ |
| } |
| |
| 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; |
| } |
| 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: |
| MDefinition* input() const { |
| return getOperand(0); |
| } |
| }; |
| |
| class MBinaryInstruction : public MAryInstruction<2> |
| { |
| protected: |
| MBinaryInstruction(MDefinition* left, MDefinition* right) |
| { |
| initOperand(0, left); |
| initOperand(1, right); |
| } |
| |
| public: |
| MDefinition* lhs() const { |
| return getOperand(0); |
| } |
| MDefinition* rhs() const { |
| return getOperand(1); |
| } |
| 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: |
| 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: |
| enum StartType { |
| StartType_Default, |
| StartType_Osr |
| }; |
| |
| private: |
| StartType startType_; |
| |
| private: |
| explicit MStart(StartType startType) |
| : startType_(startType) |
| { } |
| |
| public: |
| INSTRUCTION_HEADER(Start) |
| static MStart* New(TempAllocator& alloc, StartType startType) { |
| return new(alloc) MStart(startType); |
| } |
| |
| StartType startType() { |
| return startType_; |
| } |
| }; |
| |
| // 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) |
| static MOsrEntry* New(TempAllocator& alloc) { |
| return new(alloc) MOsrEntry; |
| } |
| }; |
| |
| // 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) |
| static MNop* New(TempAllocator& alloc) { |
| return new(alloc) MNop(); |
| } |
| |
| 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) |
| static MLimitedTruncate* New(TempAllocator& alloc, MDefinition* input, TruncateKind kind) { |
| return new(alloc) MLimitedTruncate(input, kind); |
| } |
| |
| 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 |
| { |
| Value value_; |
| |
| protected: |
| MConstant(const Value& v, CompilerConstraintList* constraints); |
| explicit MConstant(JSObject* obj); |
| |
| public: |
| INSTRUCTION_HEADER(Constant) |
| static MConstant* New(TempAllocator& alloc, const Value& v, |
| CompilerConstraintList* constraints = nullptr); |
| static MConstant* NewTypedValue(TempAllocator& alloc, const Value& v, MIRType type, |
| CompilerConstraintList* constraints = nullptr); |
| static MConstant* NewAsmJS(TempAllocator& alloc, const Value& v, MIRType type); |
| static MConstant* NewConstraintlessObject(TempAllocator& alloc, JSObject* v); |
| |
| const js::Value& value() const { |
| return value_; |
| } |
| const js::Value* vp() const { |
| return &value_; |
| } |
| bool valueToBoolean() const { |
| // A hack to avoid this wordy pattern everywhere in the JIT. |
| return ToBoolean(HandleValue::fromMarkedLocation(&value_)); |
| } |
| |
| void printOpcode(GenericPrinter& out) const override; |
| |
| HashNumber valueHash() const override; |
| bool congruentTo(const MDefinition* ins) const override; |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| |
| 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) |
| }; |
| |
| // 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) |
| |
| static MSimdValueX4* New(TempAllocator& alloc, MIRType type, MDefinition* x, |
| MDefinition* y, MDefinition* z, MDefinition* w) |
| { |
| return new(alloc) MSimdValueX4(type, x, y, z, w); |
| } |
| |
| static MSimdValueX4* NewAsmJS(TempAllocator& alloc, MIRType type, MDefinition* x, |
| MDefinition* y, MDefinition* z, MDefinition* w) |
| { |
| mozilla::DebugOnly<MIRType> laneType = SimdTypeToLaneType(type); |
| MOZ_ASSERT(laneType == x->type()); |
| MOZ_ASSERT(laneType == y->type()); |
| MOZ_ASSERT(laneType == z->type()); |
| MOZ_ASSERT(laneType == w->type()); |
| return MSimdValueX4::New(alloc, type, x, y, z, w); |
| } |
| |
| 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 valuesX4. |
| class MSimdSplatX4 |
| : public MUnaryInstruction, |
| public SimdScalarPolicy<0>::Data |
| { |
| protected: |
| MSimdSplatX4(MIRType type, MDefinition* v) |
| : MUnaryInstruction(v) |
| { |
| MOZ_ASSERT(IsSimdType(type)); |
| setMovable(); |
| setResultType(type); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(SimdSplatX4) |
| |
| static MSimdSplatX4* NewAsmJS(TempAllocator& alloc, MDefinition* v, MIRType type) |
| { |
| MOZ_ASSERT(SimdTypeToLaneType(type) == v->type()); |
| return new(alloc) MSimdSplatX4(type, v); |
| } |
| |
| static MSimdSplatX4* New(TempAllocator& alloc, MDefinition* v, MIRType type) |
| { |
| return new(alloc) MSimdSplatX4(type, v); |
| } |
| |
| 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(MSimdSplatX4) |
| }; |
| |
| // 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) |
| static MSimdConstant* New(TempAllocator& alloc, const SimdConstant& v, MIRType type) { |
| return new(alloc) MSimdConstant(v, type); |
| } |
| |
| bool congruentTo(const MDefinition* ins) const override { |
| if (!ins->isSimdConstant()) |
| 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 |
| { |
| MSimdConvert(MDefinition* obj, MIRType fromType, MIRType toType) |
| : MUnaryInstruction(obj) |
| { |
| MOZ_ASSERT(IsSimdType(toType)); |
| setResultType(toType); |
| specialization_ = fromType; // expects fromType as input |
| |
| setMovable(); |
| if (IsFloatingPointSimdType(fromType) && IsIntegerSimdType(toType)) { |
| // Does the extra range check => do not remove |
| setGuard(); |
| } |
| } |
| |
| public: |
| INSTRUCTION_HEADER(SimdConvert) |
| static MSimdConvert* NewAsmJS(TempAllocator& alloc, MDefinition* obj, MIRType fromType, |
| MIRType toType) |
| { |
| MOZ_ASSERT(IsSimdType(obj->type()) && fromType == obj->type()); |
| return new(alloc) MSimdConvert(obj, fromType, toType); |
| } |
| |
| static MSimdConvert* New(TempAllocator& alloc, MDefinition* obj, MIRType fromType, |
| MIRType toType) |
| { |
| return new(alloc) MSimdConvert(obj, fromType, toType); |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| bool congruentTo(const MDefinition* ins) const override { |
| return congruentIfOperandsEqual(ins); |
| } |
| 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 fromType, MIRType toType) |
| : MUnaryInstruction(obj) |
| { |
| MOZ_ASSERT(IsSimdType(toType)); |
| setMovable(); |
| setResultType(toType); |
| specialization_ = fromType; // expects fromType as input |
| } |
| |
| public: |
| INSTRUCTION_HEADER(SimdReinterpretCast) |
| static MSimdReinterpretCast* NewAsmJS(TempAllocator& alloc, MDefinition* obj, MIRType fromType, |
| MIRType toType) |
| { |
| MOZ_ASSERT(IsSimdType(obj->type()) && fromType == obj->type()); |
| return new(alloc) MSimdReinterpretCast(obj, fromType, toType); |
| } |
| |
| static MSimdReinterpretCast* New(TempAllocator& alloc, MDefinition* obj, MIRType fromType, |
| MIRType toType) |
| { |
| return new(alloc) MSimdReinterpretCast(obj, fromType, toType); |
| } |
| |
| 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. |
| class MSimdExtractElement |
| : public MUnaryInstruction, |
| public SimdPolicy<0>::Data |
| { |
| protected: |
| SimdLane lane_; |
| |
| MSimdExtractElement(MDefinition* obj, MIRType vecType, MIRType laneType, SimdLane lane) |
| : MUnaryInstruction(obj), lane_(lane) |
| { |
| MOZ_ASSERT(IsSimdType(vecType)); |
| MOZ_ASSERT(uint32_t(lane) < SimdTypeToLength(vecType)); |
| MOZ_ASSERT(!IsSimdType(laneType)); |
| MOZ_ASSERT(SimdTypeToLaneType(vecType) == laneType); |
| |
| setMovable(); |
| specialization_ = vecType; |
| setResultType(laneType); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(SimdExtractElement) |
| |
| static MSimdExtractElement* NewAsmJS(TempAllocator& alloc, MDefinition* obj, MIRType type, |
| SimdLane lane) |
| { |
| return new(alloc) MSimdExtractElement(obj, obj->type(), type, lane); |
| } |
| |
| static MSimdExtractElement* New(TempAllocator& alloc, MDefinition* obj, MIRType vecType, |
| MIRType scalarType, SimdLane lane) |
| { |
| return new(alloc) MSimdExtractElement(obj, vecType, scalarType, lane); |
| } |
| |
| SimdLane lane() const { |
| return lane_; |
| } |
| |
| 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_) |
| 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: |
| SimdLane lane_; |
| |
| MSimdInsertElement(MDefinition* vec, MDefinition* val, MIRType type, SimdLane lane) |
| : MBinaryInstruction(vec, val), lane_(lane) |
| { |
| MOZ_ASSERT(IsSimdType(type)); |
| setMovable(); |
| setResultType(type); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(SimdInsertElement) |
| |
| static MSimdInsertElement* NewAsmJS(TempAllocator& alloc, MDefinition* vec, MDefinition* val, |
| MIRType type, SimdLane lane) |
| { |
| MOZ_ASSERT(vec->type() == type); |
| MOZ_ASSERT(SimdTypeToLaneType(type) == val->type()); |
| return new(alloc) MSimdInsertElement(vec, val, type, lane); |
| } |
| |
| static MSimdInsertElement* New(TempAllocator& alloc, MDefinition* vec, MDefinition* val, |
| MIRType type, SimdLane lane) |
| { |
| return new(alloc) MSimdInsertElement(vec, val, type, lane); |
| } |
| |
| MDefinition* vector() { |
| return getOperand(0); |
| } |
| MDefinition* value() { |
| return getOperand(1); |
| } |
| SimdLane lane() const { |
| return lane_; |
| } |
| |
| static const char* LaneName(SimdLane lane) { |
| switch (lane) { |
| case LaneX: return "lane x"; |
| case LaneY: return "lane y"; |
| case LaneZ: return "lane z"; |
| case LaneW: return "lane w"; |
| } |
| MOZ_CRASH("unknown 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) |
| }; |
| |
| // Extracts the sign bits from a given vector, returning an MIRType_Int32. |
| class MSimdSignMask |
| : public MUnaryInstruction, |
| public SimdPolicy<0>::Data |
| { |
| protected: |
| explicit MSimdSignMask(MDefinition* obj, MIRType type) |
| : MUnaryInstruction(obj) |
| { |
| setResultType(MIRType_Int32); |
| specialization_ = type; |
| setMovable(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(SimdSignMask) |
| |
| static MSimdSignMask* NewAsmJS(TempAllocator& alloc, MDefinition* obj) |
| { |
| MOZ_ASSERT(IsSimdType(obj->type())); |
| return new(alloc) MSimdSignMask(obj, obj->type()); |
| } |
| |
| static MSimdSignMask* New(TempAllocator& alloc, MDefinition* obj, MIRType type) |
| { |
| return new(alloc) MSimdSignMask(obj, type); |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| bool congruentTo(const MDefinition* ins) const override { |
| if (!ins->isSimdSignMask()) |
| return false; |
| return congruentIfOperandsEqual(ins); |
| } |
| |
| ALLOW_CLONE(MSimdSignMask) |
| }; |
| |
| // Base for the MSimdSwizzle and MSimdShuffle classes. |
| class MSimdShuffleBase |
| { |
| protected: |
| // As of now, there are at most 4 lanes. For each lane, we need to know |
| // which input we choose and which of the 4 lanes we choose; that can be |
| // packed in 3 bits for each lane, so 12 bits in total. |
| uint32_t laneMask_; |
| uint32_t arity_; |
| |
| MSimdShuffleBase(uint32_t laneX, uint32_t laneY, uint32_t laneZ, uint32_t laneW, MIRType type) |
| { |
| MOZ_ASSERT(SimdTypeToLength(type) == 4); |
| MOZ_ASSERT(IsSimdType(type)); |
| laneMask_ = (laneX << 0) | (laneY << 3) | (laneZ << 6) | (laneW << 9); |
| arity_ = 4; |
| } |
| |
| bool sameLanes(const MSimdShuffleBase* other) const { |
| return laneMask_ == other->laneMask_; |
| } |
| |
| public: |
| // For now, these formulas are fine for x4 types. They'll need to be |
| // generalized for other SIMD type lengths. |
| uint32_t laneX() const { MOZ_ASSERT(arity_ == 4); return laneMask_ & 7; } |
| uint32_t laneY() const { MOZ_ASSERT(arity_ == 4); return (laneMask_ >> 3) & 7; } |
| uint32_t laneZ() const { MOZ_ASSERT(arity_ == 4); return (laneMask_ >> 6) & 7; } |
| uint32_t laneW() const { MOZ_ASSERT(arity_ == 4); return (laneMask_ >> 9) & 7; } |
| |
| bool lanesMatch(uint32_t x, uint32_t y, uint32_t z, uint32_t w) const { |
| return ((x << 0) | (y << 3) | (z << 6) | (w << 9)) == laneMask_; |
| } |
| }; |
| |
| // Applies a shuffle operation to the input, putting the input lanes as |
| // indicated in the output register's lanes. This implements the SIMD.js |
| // "shuffle" function, that takes one vector and one mask. |
| class MSimdSwizzle |
| : public MUnaryInstruction, |
| public MSimdShuffleBase, |
| public NoTypePolicy::Data |
| { |
| protected: |
| MSimdSwizzle(MDefinition* obj, MIRType type, |
| uint32_t laneX, uint32_t laneY, uint32_t laneZ, uint32_t laneW) |
| : MUnaryInstruction(obj), MSimdShuffleBase(laneX, laneY, laneZ, laneW, type) |
| { |
| MOZ_ASSERT(laneX < 4 && laneY < 4 && laneZ < 4 && laneW < 4); |
| MOZ_ASSERT(IsSimdType(obj->type())); |
| MOZ_ASSERT(IsSimdType(type)); |
| MOZ_ASSERT(obj->type() == type); |
| setResultType(type); |
| setMovable(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(SimdSwizzle) |
| |
| static MSimdSwizzle* New(TempAllocator& alloc, MDefinition* obj, MIRType type, |
| uint32_t laneX, uint32_t laneY, uint32_t laneZ, uint32_t laneW) |
| { |
| return new(alloc) MSimdSwizzle(obj, type, laneX, laneY, laneZ, laneW); |
| } |
| |
| 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 swizzle" 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); |
| |
| static MSimdGeneralShuffle* New(TempAllocator& alloc, unsigned numVectors, unsigned numLanes, |
| MIRType type) |
| { |
| return new(alloc) MSimdGeneralShuffle(numVectors, numLanes, type); |
| } |
| |
| 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, selecting the 2 first lanes of the |
| // output from lanes of the first input, and the 2 last lanes of the output from |
| // lanes of the second input. |
| class MSimdShuffle |
| : public MBinaryInstruction, |
| public MSimdShuffleBase, |
| public NoTypePolicy::Data |
| { |
| MSimdShuffle(MDefinition* lhs, MDefinition* rhs, MIRType type, |
| uint32_t laneX, uint32_t laneY, uint32_t laneZ, uint32_t laneW) |
| : MBinaryInstruction(lhs, rhs), MSimdShuffleBase(laneX, laneY, laneZ, laneW, lhs->type()) |
| { |
| MOZ_ASSERT(laneX < 8 && laneY < 8 && laneZ < 8 && laneW < 8); |
| MOZ_ASSERT(IsSimdType(lhs->type())); |
| MOZ_ASSERT(IsSimdType(rhs->type())); |
| MOZ_ASSERT(lhs->type() == rhs->type()); |
| MOZ_ASSERT(IsSimdType(type)); |
| MOZ_ASSERT(lhs->type() == type); |
| setResultType(type); |
| setMovable(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(SimdShuffle) |
| |
| static MInstruction* New(TempAllocator& alloc, MDefinition* lhs, MDefinition* rhs, |
| MIRType type, uint32_t laneX, uint32_t laneY, uint32_t laneZ, |
| uint32_t laneW) |
| { |
| // 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 = (laneX < 4) + (laneY < 4) + (laneZ < 4) + (laneW < 4); |
| if (lanesFromLHS < 2 || (lanesFromLHS == 2 && laneX >= 4 && laneY >=4)) { |
| laneX = (laneX + 4) % 8; |
| laneY = (laneY + 4) % 8; |
| laneZ = (laneZ + 4) % 8; |
| laneW = (laneW + 4) % 8; |
| mozilla::Swap(lhs, rhs); |
| } |
| |
| // If all lanes come from the same vector, just use swizzle instead. |
| if (laneX < 4 && laneY < 4 && laneZ < 4 && laneW < 4) |
| return MSimdSwizzle::New(alloc, lhs, type, laneX, laneY, laneZ, laneW); |
| |
| return new(alloc) MSimdShuffle(lhs, rhs, type, laneX, laneY, laneZ, laneW); |
| } |
| |
| 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, |
| UNARY_ARITH_FLOAT32X4_SIMD_OP(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, MIRType type) |
| : MUnaryInstruction(def), operation_(op) |
| { |
| MOZ_ASSERT_IF(type == MIRType_Int32x4, op == neg || op == not_); |
| setResultType(type); |
| setMovable(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(SimdUnaryArith) |
| |
| static MSimdUnaryArith* New(TempAllocator& alloc, MDefinition* def, Operation op, MIRType t) |
| { |
| return new(alloc) MSimdUnaryArith(def, op, t); |
| } |
| |
| static MSimdUnaryArith* NewAsmJS(TempAllocator& alloc, MDefinition* def, |
| Operation op, MIRType t) |
| { |
| MOZ_ASSERT(IsSimdType(t)); |
| MOZ_ASSERT(def->type() == t); |
| return new(alloc) MSimdUnaryArith(def, op, t); |
| } |
| |
| 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 int32x4 vector containing the results of |
| // the comparison: all bits are set to 1 if the comparison is true, 0 otherwise. |
| class MSimdBinaryComp |
| : public MBinaryInstruction, |
| public SimdAllPolicy::Data |
| { |
| public: |
| enum Operation { |
| #define NAME_(x) x, |
| COMP_COMMONX4_TO_INT32X4_SIMD_OP(NAME_) |
| #undef NAME_ |
| }; |
| |
| static const char* OperationName(Operation op) { |
| switch (op) { |
| #define NAME_(x) case x: return #x; |
| COMP_COMMONX4_TO_INT32X4_SIMD_OP(NAME_) |
| #undef NAME_ |
| } |
| MOZ_CRASH("unexpected operation"); |
| } |
| |
| private: |
| Operation operation_; |
| |
| MSimdBinaryComp(MDefinition* left, MDefinition* right, Operation op, MIRType opType) |
| : MBinaryInstruction(left, right), operation_(op) |
| { |
| setResultType(MIRType_Int32x4); |
| specialization_ = opType; |
| setMovable(); |
| if (op == equal || op == notEqual) |
| setCommutative(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(SimdBinaryComp) |
| static MSimdBinaryComp* NewAsmJS(TempAllocator& alloc, MDefinition* left, MDefinition* right, |
| Operation op) |
| { |
| MOZ_ASSERT(IsSimdType(left->type())); |
| MOZ_ASSERT(left->type() == right->type()); |
| return new(alloc) MSimdBinaryComp(left, right, op, left->type()); |
| } |
| |
| static MSimdBinaryComp* New(TempAllocator& alloc, MDefinition* left, MDefinition* right, |
| Operation op, MIRType opType) |
| { |
| return new(alloc) MSimdBinaryComp(left, right, op, opType); |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| |
| Operation operation() const { return operation_; } |
| 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(); |
| } |
| |
| 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, |
| ARITH_COMMONX4_SIMD_OP(OP_LIST_) |
| BINARY_ARITH_FLOAT32X4_SIMD_OP(OP_LIST_) |
| #undef OP_LIST_ |
| }; |
| |
| static const char* OperationName(Operation op) { |
| switch (op) { |
| #define OP_CASE_LIST_(OP) case Op_##OP: return #OP; |
| ARITH_COMMONX4_SIMD_OP(OP_CASE_LIST_) |
| BINARY_ARITH_FLOAT32X4_SIMD_OP(OP_CASE_LIST_) |
| #undef OP_CASE_LIST_ |
| } |
| MOZ_CRASH("unexpected operation"); |
| } |
| |
| private: |
| Operation operation_; |
| |
| MSimdBinaryArith(MDefinition* left, MDefinition* right, Operation op, MIRType type) |
| : MBinaryInstruction(left, right), operation_(op) |
| { |
| MOZ_ASSERT_IF(type == MIRType_Int32x4, op == Op_add || op == Op_sub || op == Op_mul); |
| MOZ_ASSERT(IsSimdType(type)); |
| setResultType(type); |
| setMovable(); |
| if (op == Op_add || op == Op_mul || op == Op_min || op == Op_max) |
| setCommutative(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(SimdBinaryArith) |
| static MSimdBinaryArith* New(TempAllocator& alloc, MDefinition* left, MDefinition* right, |
| Operation op, MIRType t) |
| { |
| return new(alloc) MSimdBinaryArith(left, right, op, t); |
| } |
| |
| static MSimdBinaryArith* NewAsmJS(TempAllocator& alloc, MDefinition* left, MDefinition* right, |
| Operation op, MIRType t) |
| { |
| MOZ_ASSERT(left->type() == right->type()); |
| MOZ_ASSERT(left->type() == t); |
| return New(alloc, left, right, op, t); |
| } |
| |
| 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 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, MIRType type) |
| : MBinaryInstruction(left, right), operation_(op) |
| { |
| MOZ_ASSERT(IsSimdType(type)); |
| setResultType(type); |
| setMovable(); |
| setCommutative(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(SimdBinaryBitwise) |
| static MSimdBinaryBitwise* New(TempAllocator& alloc, MDefinition* left, MDefinition* right, |
| Operation op, MIRType t) |
| { |
| return new(alloc) MSimdBinaryBitwise(left, right, op, t); |
| } |
| |
| static MSimdBinaryBitwise* NewAsmJS(TempAllocator& alloc, MDefinition* left, |
| MDefinition* right, Operation op, MIRType t) |
| { |
| MOZ_ASSERT(left->type() == right->type()); |
| MOZ_ASSERT(left->type() == t); |
| return new(alloc) MSimdBinaryBitwise(left, right, op, t); |
| } |
| |
| 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) |
| { |
| setResultType(MIRType_Int32x4); |
| setMovable(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(SimdShift) |
| static MSimdShift* NewAsmJS(TempAllocator& alloc, MDefinition* left, |
| MDefinition* right, Operation op) |
| { |
| MOZ_ASSERT(left->type() == MIRType_Int32x4 && right->type() == MIRType_Int32); |
| return new(alloc) MSimdShift(left, right, op); |
| } |
| |
| static MSimdShift* New(TempAllocator& alloc, MDefinition* left, MDefinition* right, |
| Operation op, MIRType type) |
| { |
| MOZ_ASSERT(type == MIRType_Int32x4); |
| return new(alloc) MSimdShift(left, right, op); |
| } |
| |
| 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 "rhs-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 |
| { |
| bool isElementWise_; |
| |
| MSimdSelect(MDefinition* mask, MDefinition* lhs, MDefinition* rhs, MIRType type, |
| bool isElementWise) |
| : MTernaryInstruction(mask, lhs, rhs), isElementWise_(isElementWise) |
| { |
| MOZ_ASSERT(IsSimdType(type)); |
| setResultType(type); |
| specialization_ = type; |
| setMovable(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(SimdSelect) |
| static MSimdSelect* NewAsmJS(TempAllocator& alloc, MDefinition* mask, MDefinition* lhs, |
| MDefinition* rhs, MIRType t, bool isElementWise) |
| { |
| MOZ_ASSERT(mask->type() == MIRType_Int32x4); |
| MOZ_ASSERT(lhs->type() == rhs->type()); |
| MOZ_ASSERT(lhs->type() == t); |
| return new(alloc) MSimdSelect(mask, lhs, rhs, t, isElementWise); |
| } |
| |
| static MSimdSelect* New(TempAllocator& alloc, MDefinition* mask, MDefinition* lhs, |
| MDefinition* rhs, MIRType t, bool isElementWise) |
| { |
| return new(alloc) MSimdSelect(mask, lhs, rhs, t, isElementWise); |
| } |
| |
| MDefinition* mask() const { |
| return getOperand(0); |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| |
| bool isElementWise() const { |
| return isElementWise_; |
| } |
| |
| bool congruentTo(const MDefinition* ins) const override { |
| if (!congruentIfOperandsEqual(ins)) |
| return false; |
| return isElementWise_ == ins->toSimdSelect()->isElementWise(); |
| } |
| |
| 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) |
| static MCloneLiteral* New(TempAllocator& alloc, MDefinition* obj); |
| }; |
| |
| class MParameter : public MNullaryInstruction |
| { |
| int32_t index_; |
| |
| public: |
| static const int32_t THIS_SLOT = -1; |
| |
| MParameter(int32_t index, TemporaryTypeSet* types) |
| : index_(index) |
| { |
| setResultType(MIRType_Value); |
| setResultTypeSet(types); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(Parameter) |
| static MParameter* New(TempAllocator& alloc, int32_t index, TemporaryTypeSet* types); |
| |
| 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) |
| |
| bool congruentTo(const MDefinition* ins) const override { |
| return congruentIfOperandsEqual(ins); |
| } |
| |
| static MCallee* New(TempAllocator& alloc) { |
| return new(alloc) MCallee(); |
| } |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| }; |
| |
| class MIsConstructing : public MNullaryInstruction |
| { |
| public: |
| MIsConstructing() { |
| setResultType(MIRType_Boolean); |
| setMovable(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(IsConstructing) |
| |
| bool congruentTo(const MDefinition* ins) const override { |
| return congruentIfOperandsEqual(ins); |
| } |
| static MIsConstructing* New(TempAllocator& alloc) { |
| return new(alloc) MIsConstructing(); |
| } |
| 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(); |
| } |
| |
| 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; |
| } |
| |
| bool addDefault(MBasicBlock* block, size_t* index = nullptr) { |
| MOZ_ASSERT(successors_.empty()); |
| if (index) |
| *index = 0; |
| return successors_.append(block); |
| } |
| |
| bool addCase(size_t successorIndex) { |
| return cases_.append(successorIndex); |
| } |
| |
| MBasicBlock* getBlock(size_t i) const { |
| MOZ_ASSERT(i < numBlocks()); |
| return blocks_[i]; |
| } |
| |
| 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); |
| |
| 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* if_true, MBasicBlock* if_false) |
| : operandMightEmulateUndefined_(true) |
| { |
| initOperand(0, ins); |
| setSuccessor(0, if_true); |
| setSuccessor(1, if_false); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(Test) |
| static MTest* New(TempAllocator& alloc, MDefinition* ins, |
| MBasicBlock* ifTrue, MBasicBlock* ifFalse); |
| |
| MDefinition* input() const { |
| return getOperand(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* 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) |
| static MGotoWithFake* New(TempAllocator& alloc, MBasicBlock* successor, MBasicBlock* fake) { |
| return new(alloc) MGotoWithFake(successor, fake); |
| } |
| |
| 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) |
| static MReturn* New(TempAllocator& alloc, MDefinition* ins) { |
| return new(alloc) MReturn(ins); |
| } |
| |
| MDefinition* input() const { |
| return getOperand(0); |
| } |
| 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) |
| static MThrow* New(TempAllocator& alloc, MDefinition* ins) { |
| return new(alloc) MThrow(ins); |
| } |
| |
| 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); |
| |
| bool |
| MergeTypes(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); |
| |
| #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 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_; |
| |
| MNewArray(CompilerConstraintList* constraints, uint32_t length, MConstant* templateConst, |
| gc::InitialHeap initialHeap, jsbytecode* pc); |
| |
| public: |
| INSTRUCTION_HEADER(NewArray) |
| |
| static MNewArray* New(TempAllocator& alloc, CompilerConstraintList* constraints, |
| uint32_t length, MConstant* templateConst, |
| gc::InitialHeap initialHeap, jsbytecode* pc) |
| { |
| return new(alloc) MNewArray(constraints, length, templateConst, initialHeap, pc); |
| } |
| |
| uint32_t length() const { |
| return length_; |
| } |
| |
| JSObject* templateObject() const { |
| return getOperand(0)->toConstant()->value().toObjectOrNull(); |
| } |
| |
| gc::InitialHeap initialHeap() const { |
| return initialHeap_; |
| } |
| |
| jsbytecode* pc() const { |
| return pc_; |
| } |
| |
| bool convertDoubleElements() const { |
| return convertDoubleElements_; |
| } |
| |
| // Returns true if the code generator should call through to the |
| // VM rather than the fast path. |
| bool shouldUseVM() const; |
| |
| // 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(); |
| } |
| |
| 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) |
| |
| static MNewArrayCopyOnWrite* New(TempAllocator& alloc, |
| CompilerConstraintList* constraints, |
| ArrayObject* templateObject, |
| gc::InitialHeap initialHeap) |
| { |
| return new(alloc) MNewArrayCopyOnWrite(constraints, templateObject, initialHeap); |
| } |
| |
| ArrayObject* templateObject() const { |
| return templateObject_; |
| } |
| |
| gc::InitialHeap initialHeap() const { |
| return initialHeap_; |
| } |
| |
| virtual AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| }; |
| |
| 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) |
| |
| static MNewArrayDynamicLength* New(TempAllocator& alloc, CompilerConstraintList* constraints, |
| JSObject* templateObject, gc::InitialHeap initialHeap, |
| MDefinition* length) |
| { |
| return new(alloc) MNewArrayDynamicLength(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(); |
| } |
| }; |
| |
| class MNewObject |
| : public MUnaryInstruction, |
| public NoTypePolicy::Data |
| { |
| public: |
| enum Mode { ObjectLiteral, ObjectCreate }; |
| |
| private: |
| gc::InitialHeap initialHeap_; |
| Mode mode_; |
| |
| MNewObject(CompilerConstraintList* constraints, MConstant* templateConst, |
| gc::InitialHeap initialHeap, Mode mode) |
| : MUnaryInstruction(templateConst), |
| initialHeap_(initialHeap), |
| mode_(mode) |
| { |
| MOZ_ASSERT_IF(mode != ObjectLiteral, !shouldUseVM()); |
| 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()->value().isObject()) |
| templateConst->setEmittedAtUses(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(NewObject) |
| |
| static MNewObject* New(TempAllocator& alloc, CompilerConstraintList* constraints, |
| MConstant* templateConst, gc::InitialHeap initialHeap, |
| Mode mode) |
| { |
| return new(alloc) MNewObject(constraints, templateConst, initialHeap, mode); |
| } |
| |
| // Returns true if the code generator should call through to the |
| // VM rather than the fast path. |
| bool shouldUseVM() const; |
| |
| Mode mode() const { |
| return mode_; |
| } |
| |
| JSObject* templateObject() const { |
| return getOperand(0)->toConstant()->value().toObjectOrNull(); |
| } |
| |
| gc::InitialHeap initialHeap() const { |
| return initialHeap_; |
| } |
| |
| 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) |
| |
| static MNewTypedObject* New(TempAllocator& alloc, |
| CompilerConstraintList* constraints, |
| InlineTypedObject* templateObject, |
| gc::InitialHeap initialHeap) |
| { |
| return new(alloc) MNewTypedObject(constraints, templateObject, initialHeap); |
| } |
| |
| InlineTypedObject* templateObject() const { |
| return templateObject_; |
| } |
| |
| gc::InitialHeap initialHeap() const { |
| return initialHeap_; |
| } |
| |
| virtual AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| }; |
| |
| class MTypedObjectDescr |
| : public MUnaryInstruction, |
| public SingleObjectPolicy::Data |
| { |
| private: |
| explicit MTypedObjectDescr(MDefinition* object) |
| : MUnaryInstruction(object) |
| { |
| setResultType(MIRType_Object); |
| setMovable(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(TypedObjectDescr) |
| |
| static MTypedObjectDescr* New(TempAllocator& alloc, MDefinition* object) { |
| return new(alloc) MTypedObjectDescr(object); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| 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_; |
| gc::InitialHeap initialHeap_; |
| |
| MSimdBox(CompilerConstraintList* constraints, |
| MDefinition* op, |
| InlineTypedObject* templateObject, |
| gc::InitialHeap initialHeap) |
| : MUnaryInstruction(op), |
| templateObject_(templateObject), |
| initialHeap_(initialHeap) |
| { |
| MOZ_ASSERT(IsSimdType(op->type())); |
| setMovable(); |
| setResultType(MIRType_Object); |
| if (constraints) |
| setResultTypeSet(MakeSingletonTypeSet(constraints, templateObject)); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(SimdBox) |
| |
| static MSimdBox* New(TempAllocator& alloc, |
| CompilerConstraintList* constraints, |
| MDefinition* op, |
| InlineTypedObject* templateObject, |
| gc::InitialHeap initialHeap) |
| { |
| return new(alloc) MSimdBox(constraints, op, templateObject, initialHeap); |
| } |
| |
| InlineTypedObject* templateObject() const { |
| return templateObject_; |
| } |
| |
| gc::InitialHeap initialHeap() const { |
| return initialHeap_; |
| } |
| |
| bool congruentTo(const MDefinition* ins) const override { |
| if (congruentIfOperandsEqual(ins)) { |
| MOZ_ASSERT(ins->toSimdBox()->initialHeap() == initialHeap()); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| |
| bool writeRecoverData(CompactBufferWriter& writer) const override; |
| bool canRecoverOnBailout() const override { |
| return true; |
| } |
| }; |
| |
| class MSimdUnbox |
| : public MUnaryInstruction, |
| public SingleObjectPolicy::Data |
| { |
| protected: |
| MSimdUnbox(MDefinition* op, MIRType type) |
| : MUnaryInstruction(op) |
| { |
| MOZ_ASSERT(IsSimdType(type)); |
| setGuard(); |
| setMovable(); |
| setResultType(type); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(SimdUnbox) |
| ALLOW_CLONE(MSimdUnbox) |
| |
| static MSimdUnbox* New(TempAllocator& alloc, MDefinition* op, MIRType type) |
| { |
| return new(alloc) MSimdUnbox(op, type); |
| } |
| |
| MDefinition* foldsTo(TempAllocator& alloc) override; |
| bool congruentTo(const MDefinition* ins) const override { |
| return congruentIfOperandsEqual(ins); |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| }; |
| |
| // 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) |
| |
| static MNewDerivedTypedObject* New(TempAllocator& alloc, TypedObjectPrediction prediction, |
| MDefinition* type, MDefinition* owner, MDefinition* offset) |
| { |
| return new(alloc) MNewDerivedTypedObject(prediction, type, owner, offset); |
| } |
| |
| TypedObjectPrediction prediction() const { |
| return prediction_; |
| } |
| |
| MDefinition* type() const { |
| return getOperand(0); |
| } |
| |
| MDefinition* owner() const { |
| return getOperand(1); |
| } |
| |
| MDefinition* offset() const { |
| return getOperand(2); |
| } |
| |
| virtual AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| |
| 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; |
| |
| 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); |
| |
| bool init(TempAllocator& alloc, MDefinition* obj); |
| |
| void initSlot(uint32_t slot, MDefinition* def) { |
| initOperand(slot + 1, def); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(ObjectState) |
| |
| // 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, MDefinition* undefinedVal); |
| static MObjectState* Copy(TempAllocator& alloc, MObjectState* state); |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| |
| 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); |
| } |
| |
| 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); |
| |
| bool init(TempAllocator& alloc, MDefinition* obj, MDefinition* len); |
| |
| void initElement(uint32_t index, MDefinition* def) { |
| initOperand(index + 2, def); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(ArrayState) |
| |
| static MArrayState* New(TempAllocator& alloc, MDefinition* arr, MDefinition* undefinedVal, |
| MDefinition* initLength); |
| static MArrayState* Copy(TempAllocator& alloc, MArrayState* state); |
| |
| MDefinition* array() const { |
| return getOperand(0); |
| } |
| |
| MDefinition* initializedLength() const { |
| return getOperand(1); |
| } |
| 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); |
| } |
| |
| 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) |
| |
| static MMutateProto* New(TempAllocator& alloc, MDefinition* obj, MDefinition* value) |
| { |
| return new(alloc) MMutateProto(obj, value); |
| } |
| |
| MDefinition* getObject() const { |
| return getOperand(0); |
| } |
| MDefinition* getValue() const { |
| return getOperand(1); |
| } |
| |
| 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) |
| |
| static MInitProp* New(TempAllocator& alloc, MDefinition* obj, PropertyName* name, |
| MDefinition* value) |
| { |
| return new(alloc) MInitProp(obj, name, value); |
| } |
| |
| MDefinition* getObject() const { |
| return getOperand(0); |
| } |
| MDefinition* getValue() const { |
| return getOperand(1); |
| } |
| |
| PropertyName* propertyName() const { |
| return name_; |
| } |
| |
| bool possiblyCalls() const override { |
| return true; |
| } |
| }; |
| |
| 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) |
| |
| static MInitPropGetterSetter* New(TempAllocator& alloc, MDefinition* obj, PropertyName* name, |
| MDefinition* value) |
| { |
| return new(alloc) MInitPropGetterSetter(obj, name, value); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| MDefinition* value() const { |
| return getOperand(1); |
| } |
| PropertyName* name() const { |
| return 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) |
| |
| static MInitElem* New(TempAllocator& alloc, MDefinition* obj, MDefinition* id, |
| MDefinition* value) |
| { |
| return new(alloc) MInitElem(obj, id, value); |
| } |
| |
| MDefinition* getObject() const { |
| return getOperand(0); |
| } |
| MDefinition* getId() const { |
| return getOperand(1); |
| } |
| MDefinition* getValue() const { |
| return getOperand(2); |
| } |
| 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) |
| |
| static MInitElemGetterSetter* New(TempAllocator& alloc, MDefinition* obj, MDefinition* id, |
| MDefinition* value) |
| { |
| return new(alloc) MInitElemGetterSetter(obj, id, value); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| MDefinition* idValue() const { |
| return getOperand(1); |
| } |
| MDefinition* value() const { |
| return getOperand(2); |
| } |
| }; |
| |
| 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. |
| CompilerFunction target_; |
| |
| // Original value of argc from the bytecode. |
| uint32_t numActualArgs_; |
| |
| // True if the call is for JSOP_NEW. |
| bool construct_; |
| |
| bool needsArgCheck_; |
| |
| MCall(JSFunction* 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. |
| JSFunction* 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() { |
| } |
| }; |
| |
| 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(JSFunction* 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) |
| static MArraySplice* New(TempAllocator& alloc, MDefinition* object, |
| MDefinition* start, MDefinition* deleteCount) |
| { |
| return new(alloc) MArraySplice(object, start, deleteCount); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| |
| MDefinition* start() const { |
| return getOperand(1); |
| } |
| |
| MDefinition* deleteCount() const { |
| return getOperand(2); |
| } |
| |
| 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. |
| CompilerFunction target_; |
| |
| MApplyArgs(JSFunction* 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) |
| static MApplyArgs* New(TempAllocator& alloc, JSFunction* target, MDefinition* fun, |
| MDefinition* argc, MDefinition* self); |
| |
| MDefinition* getFunction() const { |
| return getOperand(0); |
| } |
| |
| // For TI-informed monomorphic callsites. |
| JSFunction* getSingleTarget() const { |
| return target_; |
| } |
| |
| MDefinition* getArgc() const { |
| return getOperand(1); |
| } |
| MDefinition* getThis() const { |
| return getOperand(2); |
| } |
| bool possiblyCalls() const override { |
| 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. |
| CompilerFunction target_; |
| |
| MApplyArray(JSFunction* 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) |
| static MApplyArray* New(TempAllocator& alloc, JSFunction* target, MDefinition* fun, |
| MDefinition* elements, MDefinition* self); |
| |
| MDefinition* getFunction() const { |
| return getOperand(0); |
| } |
| |
| // For TI-informed monomorphic callsites. |
| JSFunction* getSingleTarget() const { |
| return target_; |
| } |
| |
| MDefinition* getElements() const { |
| return getOperand(1); |
| } |
| MDefinition* getThis() const { |
| return getOperand(2); |
| } |
| bool possiblyCalls() const override { |
| 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) |
| |
| static MUnreachable* New(TempAllocator& alloc) { |
| return new(alloc) MUnreachable(); |
| } |
| |
| 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) |
| |
| static MAssertRecoveredOnBailout* New(TempAllocator& alloc, MDefinition* ins, |
| bool mustBeRecovered) |
| { |
| return new(alloc) MAssertRecoveredOnBailout(ins, mustBeRecovered); |
| } |
| |
| // Needed to assert that float32 instructions are correctly recovered. |
| bool canConsumeFloat32(MUse* use) const override { return true; } |
| |
| 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) |
| |
| static MAssertFloat32* New(TempAllocator& alloc, MDefinition* value, bool mustBeFloat32) { |
| return new(alloc) MAssertFloat32(value, mustBeFloat32); |
| } |
| |
| 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* scopeChain, MDefinition* name) |
| { |
| initOperand(0, scopeChain); |
| initOperand(1, name); |
| setResultType(MIRType_Value); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(GetDynamicName) |
| |
| static MGetDynamicName* |
| New(TempAllocator& alloc, MDefinition* scopeChain, MDefinition* name) { |
| return new(alloc) MGetDynamicName(scopeChain, name); |
| } |
| |
| MDefinition* getScopeChain() const { |
| return getOperand(0); |
| } |
| MDefinition* getName() const { |
| return getOperand(1); |
| } |
| bool possiblyCalls() const override { |
| return true; |
| } |
| }; |
| |
| class MCallDirectEval |
| : public MAryInstruction<3>, |
| public Mix3Policy<ObjectPolicy<0>, |
| StringPolicy<1>, |
| BoxPolicy<2> >::Data |
| { |
| protected: |
| MCallDirectEval(MDefinition* scopeChain, MDefinition* string, |
| MDefinition* newTargetValue, jsbytecode* pc) |
| : pc_(pc) |
| { |
| initOperand(0, scopeChain); |
| initOperand(1, string); |
| initOperand(2, newTargetValue); |
| setResultType(MIRType_Value); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(CallDirectEval) |
| |
| static MCallDirectEval* |
| New(TempAllocator& alloc, MDefinition* scopeChain, MDefinition* string, |
| MDefinition* newTargetValue, jsbytecode* pc) |
| { |
| return new(alloc) MCallDirectEval(scopeChain, string, newTargetValue, pc); |
| } |
| |
| MDefinition* getScopeChain() const { |
| return getOperand(0); |
| } |
| MDefinition* getString() const { |
| return getOperand(1); |
| } |
| MDefinition* getNewTargetValue() const { |
| return getOperand(2); |
| } |
| |
| 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, |
| |
| // 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(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(Compare) |
| static MCompare* New(TempAllocator& alloc, MDefinition* left, MDefinition* right, JSOp op); |
| static MCompare* NewAsmJS(TempAllocator& alloc, MDefinition* left, MDefinition* right, JSOp op, |
| CompareType compareType); |
| |
| bool tryFold(bool* result); |
| 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: |
| bool tryFoldEqualOperands(bool* result); |
| 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) |
| |
| static MGuardObject* New(TempAllocator& alloc, MDefinition* ins) { |
| return new(alloc) MGuardObject(ins); |
| } |
| 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) |
| |
| static MGuardString* New(TempAllocator& alloc, MDefinition* ins) { |
| return new(alloc) MGuardString(ins); |
| } |
| |
| 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) |
| |
| static MPolyInlineGuard* New(TempAllocator& alloc, MDefinition* ins) { |
| return new(alloc) MPolyInlineGuard(ins); |
| } |
| 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) |
| |
| static MAssertRange* New(TempAllocator& alloc, MDefinition* ins, const Range* assertedRange) { |
| return new(alloc) MAssertRange(ins, assertedRange); |
| } |
| |
| 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) |
| static MCreateThisWithTemplate* New(TempAllocator& alloc, CompilerConstraintList* constraints, |
| MConstant* templateConst, gc::InitialHeap initialHeap) |
| { |
| return new(alloc) MCreateThisWithTemplate(constraints, templateConst, initialHeap); |
| } |
| |
| // Template for |this|, provided by TI. |
| JSObject* templateObject() const { |
| return &getOperand(0)->toConstant()->value().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(); |
| } |
| |
| 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) |
| static MCreateThisWithProto* New(TempAllocator& alloc, MDefinition* callee, |
| MDefinition* newTarget, MDefinition* prototype) |
| { |
| return new(alloc) MCreateThisWithProto(callee, newTarget, prototype); |
| } |
| |
| MDefinition* getCallee() const { |
| return getOperand(0); |
| } |
| MDefinition* getNewTarget() const { |
| return getOperand(1); |
| } |
| MDefinition* getPrototype() const { |
| return getOperand(2); |
| } |
| |
| // 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) |
| static MCreateThis* New(TempAllocator& alloc, MDefinition* callee, MDefinition* newTarget) |
| { |
| return new(alloc) MCreateThis(callee, newTarget); |
| } |
| |
| MDefinition* getCallee() const { |
| return getOperand(0); |
| } |
| MDefinition* getNewTarget() const { |
| return getOperand(0); |
| } |
| |
| // 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 |
| { |
| explicit MCreateArgumentsObject(MDefinition* callObj) |
| : MUnaryInstruction(callObj) |
| { |
| setResultType(MIRType_Object); |
| setGuard(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(CreateArgumentsObject) |
| static MCreateArgumentsObject* New(TempAllocator& alloc, MDefinition* callObj) { |
| return new(alloc) MCreateArgumentsObject(callObj); |
| } |
| |
| MDefinition* getCallObject() const { |
| return getOperand(0); |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| |
| bool possiblyCalls() const override { |
| return true; |
| } |
| }; |
| |
| 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) |
| static MGetArgumentsObjectArg* New(TempAllocator& alloc, MDefinition* argsObj, size_t argno) |
| { |
| return new(alloc) MGetArgumentsObjectArg(argsObj, argno); |
| } |
| |
| MDefinition* getArgsObject() const { |
| return getOperand(0); |
| } |
| |
| 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) |
| static MSetArgumentsObjectArg* New(TempAllocator& alloc, MDefinition* argsObj, size_t argno, |
| MDefinition* value) |
| { |
| return new(alloc) MSetArgumentsObjectArg(argsObj, argno, value); |
| } |
| |
| MDefinition* getArgsObject() const { |
| return getOperand(0); |
| } |
| |
| size_t argno() const { |
| return argno_; |
| } |
| |
| MDefinition* getValue() const { |
| return getOperand(1); |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::Store(AliasSet::Any); |
| } |
| }; |
| |
| class MRunOncePrologue |
| : public MNullaryInstruction |
| { |
| protected: |
| MRunOncePrologue() |
| { |
| setGuard(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(RunOncePrologue) |
| |
| static MRunOncePrologue* New(TempAllocator& alloc) { |
| return new(alloc) MRunOncePrologue(); |
| } |
| 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) |
| static MReturnFromCtor* New(TempAllocator& alloc, MDefinition* value, MDefinition* object) |
| { |
| return new(alloc) MReturnFromCtor(value, object); |
| } |
| |
| MDefinition* getValue() const { |
| return getOperand(0); |
| } |
| MDefinition* getObject() const { |
| return getOperand(1); |
| } |
| |
| 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) |
| static MToDouble* New(TempAllocator& alloc, MDefinition* def, |
| ConversionKind conversion = NonStringPrimitives) |
| { |
| return new(alloc) MToDouble(def, conversion); |
| } |
| static MToDouble* NewAsmJS(TempAllocator& alloc, MDefinition* def) { |
| return new(alloc) MToDouble(def); |
| } |
| |
| 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); |
| } |
| |
| 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: |
| MToFloat32(MDefinition* def, ConversionKind conversion) |
| : MToFPInstruction(def, conversion) |
| { |
| 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(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(ToFloat32) |
| static MToFloat32* New(TempAllocator& alloc, MDefinition* def, |
| ConversionKind conversion = NonStringPrimitives) |
| { |
| return new(alloc) MToFloat32(def, conversion); |
| } |
| static MToFloat32* NewAsmJS(TempAllocator& alloc, MDefinition* def) { |
| return new(alloc) MToFloat32(def, NonStringPrimitives); |
| } |
| |
| virtual MDefinition* foldsTo(TempAllocator& alloc) override; |
| bool congruentTo(const MDefinition* ins) const override { |
| if (!ins->isToFloat32() || ins->toToFloat32()->conversion() != conversion()) |
| return false; |
| return congruentIfOperandsEqual(ins); |
| } |
| 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; } |
| |
| bool writeRecoverData(CompactBufferWriter& writer) const override; |
| bool canRecoverOnBailout() const override { |
| return true; |
| } |
| |
| ALLOW_CLONE(MToFloat32) |
| }; |
| |
| // Converts a uint32 to a double (coming from asm.js). |
| class MAsmJSUnsignedToDouble |
| : public MUnaryInstruction, |
| public NoTypePolicy::Data |
| { |
| explicit MAsmJSUnsignedToDouble(MDefinition* def) |
| : MUnaryInstruction(def) |
| { |
| setResultType(MIRType_Double); |
| setMovable(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(AsmJSUnsignedToDouble) |
| static MAsmJSUnsignedToDouble* NewAsmJS(TempAllocator& alloc, MDefinition* def) { |
| return new(alloc) MAsmJSUnsignedToDouble(def); |
| } |
| |
| 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 asm.js). |
| class MAsmJSUnsignedToFloat32 |
| : public MUnaryInstruction, |
| public NoTypePolicy::Data |
| { |
| explicit MAsmJSUnsignedToFloat32(MDefinition* def) |
| : MUnaryInstruction(def) |
| { |
| setResultType(MIRType_Float32); |
| setMovable(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(AsmJSUnsignedToFloat32) |
| static MAsmJSUnsignedToFloat32* NewAsmJS(TempAllocator& alloc, MDefinition* def) { |
| return new(alloc) MAsmJSUnsignedToFloat32(def); |
| } |
| |
| 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; } |
| }; |
| |
| // 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_; |
| |
| MToInt32(MDefinition* def, MacroAssembler::IntConversionInputKind conversion) |
| : 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) |
| static MToInt32* New(TempAllocator& alloc, MDefinition* def, |
| MacroAssembler::IntConversionInputKind conversion = |
| MacroAssembler::IntConversion_Any) |
| { |
| return new(alloc) MToInt32(def, conversion); |
| } |
| |
| 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) |
| static MTruncateToInt32* New(TempAllocator& alloc, MDefinition* def) { |
| return new(alloc) MTruncateToInt32(def); |
| } |
| static MTruncateToInt32* NewAsmJS(TempAllocator& alloc, MDefinition* def) { |
| return new(alloc) MTruncateToInt32(def); |
| } |
| |
| 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 |
| |
| 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(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(ToString) |
| static MToString* New(TempAllocator& alloc, MDefinition* def) |
| { |
| return new(alloc) MToString(def); |
| } |
| |
| 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) |
| static MToObjectOrNull* New(TempAllocator& alloc, MDefinition* def) |
| { |
| return new(alloc) MToObjectOrNull(def); |
| } |
| |
| 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) |
| static MBitNot* New(TempAllocator& alloc, MDefinition* input); |
| static MBitNot* NewAsmJS(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; |
| |
| 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) |
| |
| static MTypeOf* New(TempAllocator& alloc, MDefinition* def, MIRType inputType) { |
| return new(alloc) MTypeOf(def, inputType); |
| } |
| |
| 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); |
| } |
| |
| bool writeRecoverData(CompactBufferWriter& writer) const override; |
| bool canRecoverOnBailout() const override { |
| return true; |
| } |
| }; |
| |
| class MToId |
| : public MUnaryInstruction, |
| public BoxInputsPolicy::Data |
| { |
| explicit MToId(MDefinition* index) |
| : MUnaryInstruction(index) |
| { |
| setResultType(MIRType_Value); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(ToId) |
| |
| static MToId* New(TempAllocator& alloc, MDefinition* index) { |
| return new(alloc) MToId(index); |
| } |
| }; |
| |
| class MBinaryBitwiseInstruction |
| : public MBinaryInstruction, |
| public BitwisePolicy::Data |
| { |
| protected: |
| MBinaryBitwiseInstruction(MDefinition* left, MDefinition* right) |
| : MBinaryInstruction(left, right) |
| { |
| setResultType(MIRType_Int32); |
| setMovable(); |
| } |
| |
| void specializeAsInt32(); |
| |
| 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 void infer(BaselineInspector* inspector, jsbytecode* pc); |
| |
| 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) |
| : MBinaryBitwiseInstruction(left, right) |
| { } |
| |
| public: |
| INSTRUCTION_HEADER(BitAnd) |
| static MBitAnd* New(TempAllocator& alloc, MDefinition* left, MDefinition* right); |
| static MBitAnd* NewAsmJS(TempAllocator& alloc, MDefinition* left, MDefinition* right); |
| |
| 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; |
| } |
| void computeRange(TempAllocator& alloc) override; |
| |
| 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) |
| : MBinaryBitwiseInstruction(left, right) |
| { } |
| |
| public: |
| INSTRUCTION_HEADER(BitOr) |
| static MBitOr* New(TempAllocator& alloc, MDefinition* left, MDefinition* right); |
| static MBitOr* NewAsmJS(TempAllocator& alloc, MDefinition* left, MDefinition* right); |
| |
| 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 |
| } |
| void computeRange(TempAllocator& alloc) override; |
| 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) |
| : MBinaryBitwiseInstruction(left, right) |
| { } |
| |
| public: |
| INSTRUCTION_HEADER(BitXor) |
| static MBitXor* New(TempAllocator& alloc, MDefinition* left, MDefinition* right); |
| static MBitXor* NewAsmJS(TempAllocator& alloc, MDefinition* left, MDefinition* right); |
| |
| 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; |
| } |
| void computeRange(TempAllocator& alloc) override; |
| |
| 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) |
| : MBinaryBitwiseInstruction(left, right) |
| { } |
| |
| public: |
| MDefinition* foldIfNegOne(size_t operand) override { |
| return this; |
| } |
| MDefinition* foldIfEqual() override { |
| return this; |
| } |
| virtual void infer(BaselineInspector* inspector, jsbytecode* pc) override; |
| }; |
| |
| class MLsh : public MShiftInstruction |
| { |
| MLsh(MDefinition* left, MDefinition* right) |
| : MShiftInstruction(left, right) |
| { } |
| |
| public: |
| INSTRUCTION_HEADER(Lsh) |
| static MLsh* New(TempAllocator& alloc, MDefinition* left, MDefinition* right); |
| static MLsh* NewAsmJS(TempAllocator& alloc, MDefinition* left, MDefinition* right); |
| |
| MDefinition* foldIfZero(size_t operand) override { |
| // 0 << x => 0 |
| // x << 0 => x |
| return getOperand(0); |
| } |
| |
| void computeRange(TempAllocator& alloc) override; |
| 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) |
| : MShiftInstruction(left, right) |
| { } |
| |
| public: |
| INSTRUCTION_HEADER(Rsh) |
| static MRsh* New(TempAllocator& alloc, MDefinition* left, MDefinition* right); |
| static MRsh* NewAsmJS(TempAllocator& alloc, MDefinition* left, MDefinition* right); |
| |
| MDefinition* foldIfZero(size_t operand) override { |
| // 0 >> x => 0 |
| // x >> 0 => x |
| return getOperand(0); |
| } |
| void computeRange(TempAllocator& alloc) override; |
| |
| bool writeRecoverData(CompactBufferWriter& writer) const override; |
| bool canRecoverOnBailout() const override { |
| return specialization_ < MIRType_Object; |
| } |
| |
| ALLOW_CLONE(MRsh) |
| }; |
| |
| class MUrsh : public MShiftInstruction |
| { |
| bool bailoutsDisabled_; |
| |
| MUrsh(MDefinition* left, MDefinition* right) |
| : MShiftInstruction(left, right), |
| bailoutsDisabled_(false) |
| { } |
| |
| public: |
| INSTRUCTION_HEADER(Ursh) |
| static MUrsh* New(TempAllocator& alloc, MDefinition* left, MDefinition* right); |
| static MUrsh* NewAsmJS(TempAllocator& alloc, MDefinition* left, MDefinition* right); |
| |
| 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; |
| |
| bool writeRecoverData(CompactBufferWriter& writer) const override; |
| bool canRecoverOnBailout() const override { |
| return specialization_ < MIRType_Object; |
| } |
| |
| ALLOW_CLONE(MUrsh) |
| }; |
| |
| class MBinaryArithInstruction |
| : public MBinaryInstruction, |
| public ArithPolicy::Data |
| { |
| // Implicit truncate flag is set by the truncate backward range analysis |
| // optimization phase, and by asm.js 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_; |
| |
| public: |
| MBinaryArithInstruction(MDefinition* left, MDefinition* right) |
| : MBinaryInstruction(left, right), |
| implicitTruncate_(NoTruncate) |
| { |
| specialization_ = MIRType_None; |
| setMovable(); |
| } |
| |
| static MBinaryArithInstruction* New(TempAllocator& alloc, Opcode op, |
| MDefinition* left, MDefinition* right); |
| |
| bool constantDoubleResult(TempAllocator& alloc); |
| |
| MDefinition* foldsTo(TempAllocator& alloc) 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 { |
| return binaryCongruentTo(ins); |
| } |
| 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) |
| static MMinMax* New(TempAllocator& alloc, MDefinition* left, MDefinition* right, MIRType type, |
| bool isMax) |
| { |
| return new(alloc) MMinMax(left, right, type, isMax); |
| } |
| |
| bool isMax() const { |
| return isMax_; |
| } |
| |
| bool congruentTo(const MDefinition* ins) const override { |
| if (!ins->isMinMax()) |
| return false; |
| if (isMax() != ins->toMinMax()->isMax()) |
| return false; |
| return congruentIfOperandsEqual(ins); |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| MDefinition* foldsTo(TempAllocator& alloc) override; |
| void computeRange(TempAllocator& alloc) override; |
| 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) |
| static MAbs* New(TempAllocator& alloc, MDefinition* num, MIRType type) { |
| return new(alloc) MAbs(num, type); |
| } |
| static MAbs* NewAsmJS(TempAllocator& alloc, MDefinition* num, MIRType type) { |
| MAbs* 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; |
| |
| 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) |
| : MUnaryInstruction(num), |
| operandIsNeverZero_(false) |
| { |
| MOZ_ASSERT(IsNumberType(num->type())); |
| specialization_ = MIRType_Int32; |
| setResultType(MIRType_Int32); |
| setMovable(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(Clz) |
| static MClz* New(TempAllocator& alloc, MDefinition* num) { |
| return new(alloc) MClz(num); |
| } |
| static MClz* NewAsmJS(TempAllocator& alloc, MDefinition* num) { |
| return new(alloc) MClz(num); |
| } |
| MDefinition* num() const { |
| return getOperand(0); |
| } |
| 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; |
| }; |
| |
| // 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) |
| static MSqrt* New(TempAllocator& alloc, MDefinition* num) { |
| return new(alloc) MSqrt(num, MIRType_Double); |
| } |
| static MSqrt* NewAsmJS(TempAllocator& alloc, MDefinition* num, MIRType type) { |
| MOZ_ASSERT(IsFloatingPointType(type)); |
| return new(alloc) MSqrt(num, type); |
| } |
| 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; |
| |
| bool writeRecoverData(CompactBufferWriter& writer) const override; |
| bool canRecoverOnBailout() const override { |
| return true; |
| } |
| |
| ALLOW_CLONE(MSqrt) |
| }; |
| |
| // 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) |
| static MAtan2* New(TempAllocator& alloc, MDefinition* y, MDefinition* x) { |
| return new(alloc) MAtan2(y, x); |
| } |
| |
| MDefinition* y() const { |
| return getOperand(0); |
| } |
| |
| MDefinition* x() const { |
| return getOperand(1); |
| } |
| |
| bool congruentTo(const MDefinition* ins) const override { |
| return congruentIfOperandsEqual(ins); |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| |
| bool possiblyCalls() const override { |
| return true; |
| } |
| |
| 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; |
| } |
| |
| 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) |
| { |
| specialization_ = powerType; |
| setResultType(MIRType_Double); |
| setMovable(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(Pow) |
| static MPow* New(TempAllocator& alloc, MDefinition* input, MDefinition* power, |
| MIRType powerType) |
| { |
| MOZ_ASSERT(powerType == MIRType_Double || powerType == MIRType_Int32); |
| return new(alloc) MPow(input, power, powerType); |
| } |
| |
| 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; |
| } |
| bool writeRecoverData(CompactBufferWriter& writer) const override; |
| bool canRecoverOnBailout() const override { |
| // Temporarily disable recovery to relieve fuzzer pressure. See bug 1188586. |
| return false; |
| } |
| |
| 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) |
| static MPowHalf* New(TempAllocator& alloc, MDefinition* input) { |
| return new(alloc) MPowHalf(input); |
| } |
| 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; |
| 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) |
| static MRandom* New(TempAllocator& alloc) { |
| return new(alloc) MRandom; |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| |
| bool possiblyCalls() const override { |
| return true; |
| } |
| |
| void computeRange(TempAllocator& alloc) override; |
| |
| 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_; |
| |
| 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) |
| |
| // A nullptr cache means this function will neither access nor update the cache. |
| static MMathFunction* New(TempAllocator& alloc, MDefinition* input, Function function, |
| const MathCache* cache) |
| { |
| return new(alloc) MMathFunction(input, function, cache); |
| } |
| 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; |
| 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 |
| { |
| // Is this instruction really an int at heart? |
| MAdd(MDefinition* left, MDefinition* right) |
| : MBinaryArithInstruction(left, right) |
| { |
| setResultType(MIRType_Value); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(Add) |
| static MAdd* New(TempAllocator& alloc, MDefinition* left, MDefinition* right) { |
| return new(alloc) MAdd(left, right); |
| } |
| |
| static MAdd* NewAsmJS(TempAllocator& alloc, MDefinition* left, MDefinition* right, |
| MIRType type) |
| { |
| MAdd* add = new(alloc) MAdd(left, right); |
| add->specialization_ = type; |
| add->setResultType(type); |
| if (type == MIRType_Int32) { |
| add->setTruncateKind(Truncate); |
| add->setCommutative(); |
| } |
| return add; |
| } |
| |
| 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; |
| |
| 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); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(Sub) |
| static MSub* New(TempAllocator& alloc, MDefinition* left, MDefinition* right) { |
| return new(alloc) MSub(left, right); |
| } |
| static MSub* NewAsmJS(TempAllocator& alloc, MDefinition* left, MDefinition* right, |
| MIRType type) |
| { |
| MSub* sub = new(alloc) MSub(left, right); |
| sub->specialization_ = type; |
| sub->setResultType(type); |
| if (type == MIRType_Int32) |
| sub->setTruncateKind(Truncate); |
| return sub; |
| } |
| |
| 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; |
| |
| 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); |
| } |
| |
| 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; |
| |
| return binaryCongruentTo(ins); |
| } |
| |
| bool canOverflow() const; |
| |
| bool canBeNegativeZero() const { |
| return canBeNegativeZero_; |
| } |
| void setCanBeNegativeZero(bool negativeZero) { |
| canBeNegativeZero_ = negativeZero; |
| } |
| |
| 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_; } |
| |
| 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_; |
| |
| MDiv(MDefinition* left, MDefinition* right, MIRType type) |
| : MBinaryArithInstruction(left, right), |
| canBeNegativeZero_(true), |
| canBeNegativeOverflow_(true), |
| canBeDivideByZero_(true), |
| canBeNegativeDividend_(true), |
| unsigned_(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* NewAsmJS(TempAllocator& alloc, MDefinition* left, MDefinition* right, |
| MIRType type, bool unsignd) |
| { |
| MDiv* div = new(alloc) MDiv(left, right, type); |
| div->unsigned_ = unsignd; |
| 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 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; |
| |
| bool writeRecoverData(CompactBufferWriter& writer) const override; |
| bool canRecoverOnBailout() const override { |
| return specialization_ < MIRType_Object; |
| } |
| |
| bool congruentTo(const MDefinition* ins) const override { |
| return MBinaryArithInstruction::congruentTo(ins) && |
| unsigned_ == ins->toDiv()->isUnsigned(); |
| } |
| |
| ALLOW_CLONE(MDiv) |
| }; |
| |
| class MMod : public MBinaryArithInstruction |
| { |
| bool unsigned_; |
| bool canBeNegativeDividend_; |
| bool canBePowerOfTwoDivisor_; |
| bool canBeDivideByZero_; |
| |
| MMod(MDefinition* left, MDefinition* right, MIRType type) |
| : MBinaryArithInstruction(left, right), |
| unsigned_(false), |
| canBeNegativeDividend_(true), |
| canBePowerOfTwoDivisor_(true), |
| canBeDivideByZero_(true) |
| { |
| 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* NewAsmJS(TempAllocator& alloc, MDefinition* left, MDefinition* right, |
| MIRType type, bool unsignd) |
| { |
| MMod* mod = new(alloc) MMod(left, right, type); |
| mod->unsigned_ = unsignd; |
| 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); |
| MOZ_ASSERT(!unsigned_); |
| return canBeNegativeDividend_; |
| } |
| |
| bool canBeDivideByZero() const { |
| MOZ_ASSERT(specialization_ == MIRType_Int32); |
| return canBeDivideByZero_; |
| } |
| |
| bool canBePowerOfTwoDivisor() const { |
| MOZ_ASSERT(specialization_ == MIRType_Int32); |
| return canBePowerOfTwoDivisor_; |
| } |
| |
| void analyzeEdgeCasesForward() override; |
| |
| bool isUnsigned() const { |
| return unsigned_; |
| } |
| |
| 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) |
| static MConcat* New(TempAllocator& alloc, MDefinition* left, MDefinition* right) { |
| return new(alloc) MConcat(left, right); |
| } |
| |
| MDefinition* foldsTo(TempAllocator& alloc) override; |
| bool congruentTo(const MDefinition* ins) const override { |
| return congruentIfOperandsEqual(ins); |
| } |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| |
| 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) |
| |
| static MCharCodeAt* New(TempAllocator& alloc, MDefinition* str, MDefinition* index) { |
| return new(alloc) MCharCodeAt(str, index); |
| } |
| |
| 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; |
| |
| 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) |
| |
| static MFromCharCode* New(TempAllocator& alloc, MDefinition* code) { |
| return new(alloc) MFromCharCode(code); |
| } |
| |
| virtual AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| bool congruentTo(const MDefinition* ins) const override { |
| return congruentIfOperandsEqual(ins); |
| } |
| |
| bool writeRecoverData(CompactBufferWriter& writer) const override; |
| bool canRecoverOnBailout() const override { |
| return true; |
| } |
| |
| ALLOW_CLONE(MFromCharCode) |
| }; |
| |
| 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) |
| |
| static MStringSplit* New(TempAllocator& alloc, CompilerConstraintList* constraints, |
| MDefinition* string, MDefinition* sep, |
| MConstant* templateObject) |
| { |
| return new(alloc) MStringSplit(constraints, string, sep, templateObject); |
| } |
| MDefinition* string() const { |
| return getOperand(0); |
| } |
| MDefinition* separator() const { |
| return getOperand(1); |
| } |
| JSObject* templateObject() const { |
| return &getOperand(2)->toConstant()->value().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(); |
| } |
| 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) |
| |
| static MComputeThis* New(TempAllocator& alloc, MDefinition* def) { |
| return new(alloc) MComputeThis(def); |
| } |
| |
| 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) |
| |
| static MArrowNewTarget* New(TempAllocator& alloc, MDefinition* callee) { |
| return new(alloc) MArrowNewTarget(callee); |
| } |
| MDefinition* callee() const { |
| return getOperand(0); |
| } |
| 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 |
| { |
| js::Vector<MUse, 2, JitAllocPolicy> 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); |
| } |
| |
| 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(); |
| |
| #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. |
| bool addBackedgeType(MIRType type, TemporaryTypeSet* typeSet); |
| |
| // Initializes the operands vector to the given capacity, |
| // permitting use of addInput() instead of addInputSlow(). |
| bool reserveLength(size_t length) { |
| return inputs_.reserve(length); |
| } |
| |
| // Use only if capacity has been reserved by reserveLength |
| void addInput(MDefinition* ins) { |
| // Use infallibleGrowByUninitialized and placement-new instead of just |
| // infallibleAppend to avoid creating a temporary MUse which will get |
| // linked into |ins|'s use list and then unlinked in favor of the |
| // MUse in the Vector. We'd ideally like to use an emplace method here, |
| // once Vector supports that. |
| inputs_.infallibleGrowByUninitialized(1); |
| new (&inputs_.back()) MUse(ins, this); |
| } |
| |
| // Appends a new input to the input vector. May perform reallocation. |
| // Prefer reserveLength() and addInput() instead, where possible. |
| bool addInputSlow(MDefinition* ins) { |
| return inputs_.emplaceBack(ins, this); |
| } |
| |
| // Update the type of this phi after adding |ins| as an input. Set |
| // |*ptypeChange| to true if the type changed. |
| bool checkForTypeChange(MDefinition* ins, bool* ptypeChange); |
| |
| MDefinition* foldsTo(TempAllocator& alloc) override; |
| MDefinition* foldsTernary(); |
| 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) |
| void printOpcode(GenericPrinter& out) const override; |
| static MBeta* New(TempAllocator& alloc, MDefinition* val, const Range* comp) |
| { |
| return new(alloc) MBeta(val, comp); |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| |
| void computeRange(TempAllocator& alloc) override; |
| }; |
| |
| // 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) |
| static MOsrValue* New(TempAllocator& alloc, MOsrEntry* entry, ptrdiff_t frameOffset) { |
| return new(alloc) MOsrValue(entry, frameOffset); |
| } |
| |
| 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 MOsrScopeChain |
| : public MUnaryInstruction, |
| public NoTypePolicy::Data |
| { |
| private: |
| explicit MOsrScopeChain(MOsrEntry* entry) |
| : MUnaryInstruction(entry) |
| { |
| setResultType(MIRType_Object); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(OsrScopeChain) |
| static MOsrScopeChain* New(TempAllocator& alloc, MOsrEntry* entry) { |
| return new(alloc) MOsrScopeChain(entry); |
| } |
| |
| 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) |
| static MOsrArgumentsObject* New(TempAllocator& alloc, MOsrEntry* entry) { |
| return new(alloc) MOsrArgumentsObject(entry); |
| } |
| |
| 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) |
| static MOsrReturnValue* New(TempAllocator& alloc, MOsrEntry* entry) { |
| return new(alloc) MOsrReturnValue(entry); |
| } |
| |
| 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) |
| |
| static MBinarySharedStub* New(TempAllocator& alloc, MDefinition* left, MDefinition* right) |
| { |
| return new(alloc) MBinarySharedStub(left, right); |
| } |
| |
| }; |
| |
| class MUnarySharedStub |
| : public MUnaryInstruction, |
| public BoxPolicy<0>::Data |
| { |
| explicit MUnarySharedStub(MDefinition* input) |
| : MUnaryInstruction(input) |
| { |
| setResultType(MIRType_Value); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(UnarySharedStub) |
| |
| static MUnarySharedStub* New(TempAllocator& alloc, MDefinition* input) |
| { |
| return new(alloc) MUnarySharedStub(input); |
| } |
| }; |
| |
| // Check the current frame for over-recursion past the global stack limit. |
| class MCheckOverRecursed |
| : public MNullaryInstruction |
| { |
| public: |
| INSTRUCTION_HEADER(CheckOverRecursed) |
| |
| static MCheckOverRecursed* New(TempAllocator& alloc) { |
| return new(alloc) MCheckOverRecursed(); |
| } |
| 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) |
| |
| static MInterruptCheck* New(TempAllocator& alloc) { |
| return new(alloc) MInterruptCheck(); |
| } |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| }; |
| |
| // Check whether we need to fire the interrupt handler at loop headers and |
| // function prologues in asm.js. Generated only if we can't use implicit |
| // interrupt checks with signal handlers. |
| class MAsmJSInterruptCheck |
| : public MNullaryInstruction |
| { |
| Label* interruptExit_; |
| wasm::CallSiteDesc funcDesc_; |
| |
| MAsmJSInterruptCheck(Label* interruptExit, const wasm::CallSiteDesc& funcDesc) |
| : interruptExit_(interruptExit), funcDesc_(funcDesc) |
| {} |
| |
| public: |
| INSTRUCTION_HEADER(AsmJSInterruptCheck) |
| |
| static MAsmJSInterruptCheck* New(TempAllocator& alloc, Label* interruptExit, |
| const wasm::CallSiteDesc& funcDesc) |
| { |
| return new(alloc) MAsmJSInterruptCheck(interruptExit, funcDesc); |
| } |
| Label* interruptExit() const { |
| return interruptExit_; |
| } |
| const wasm::CallSiteDesc& funcDesc() const { |
| return funcDesc_; |
| } |
| }; |
| |
| // 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) |
| : MUnaryInstruction(input), |
| kind_(kind) |
| { |
| setResultType(MIRType_Value); |
| setResultTypeSet(input->resultTypeSet()); |
| setMovable(); |
| setGuard(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(LexicalCheck) |
| |
| static MLexicalCheck* New(TempAllocator& alloc, MDefinition* input, |
| BailoutKind kind = Bailout_UninitializedLexical) { |
| return new(alloc) MLexicalCheck(input, kind); |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| |
| MDefinition* input() const { |
| return getOperand(0); |
| } |
| |
| 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) |
| |
| static MThrowRuntimeLexicalError* New(TempAllocator& alloc, unsigned errorNumber) { |
| return new(alloc) MThrowRuntimeLexicalError(errorNumber); |
| } |
| |
| 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) |
| |
| static MGlobalNameConflictsCheck* New(TempAllocator& alloc) { |
| return new(alloc) MGlobalNameConflictsCheck(); |
| } |
| }; |
| |
| // 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* scopeChain) |
| : MUnaryInstruction(scopeChain), |
| name_(name), |
| attrs_(attrs) |
| { |
| } |
| |
| public: |
| INSTRUCTION_HEADER(DefVar) |
| |
| static MDefVar* New(TempAllocator& alloc, PropertyName* name, unsigned attrs, |
| MDefinition* scopeChain) |
| { |
| return new(alloc) MDefVar(name, attrs, scopeChain); |
| } |
| |
| PropertyName* name() const { |
| return name_; |
| } |
| unsigned attrs() const { |
| return attrs_; |
| } |
| MDefinition* scopeChain() const { |
| return getOperand(0); |
| } |
| bool possiblyCalls() const override { |
| return true; |
| } |
| }; |
| |
| 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) |
| |
| static MDefLexical* New(TempAllocator& alloc, PropertyName* name, unsigned attrs) { |
| return new(alloc) MDefLexical(name, attrs); |
| } |
| |
| PropertyName* name() const { |
| return name_; |
| } |
| unsigned attrs() const { |
| return attrs_; |
| } |
| }; |
| |
| class MDefFun |
| : public MUnaryInstruction, |
| public NoTypePolicy::Data |
| { |
| CompilerFunction fun_; |
| |
| private: |
| MDefFun(JSFunction* fun, MDefinition* scopeChain) |
| : MUnaryInstruction(scopeChain), |
| fun_(fun) |
| {} |
| |
| public: |
| INSTRUCTION_HEADER(DefFun) |
| |
| static MDefFun* New(TempAllocator& alloc, JSFunction* fun, MDefinition* scopeChain) { |
| return new(alloc) MDefFun(fun, scopeChain); |
| } |
| |
| JSFunction* fun() const { |
| return fun_; |
| } |
| MDefinition* scopeChain() const { |
| return getOperand(0); |
| } |
| bool possiblyCalls() const override { |
| return true; |
| } |
| }; |
| |
| class MRegExp : public MNullaryInstruction |
| { |
| CompilerGCPointer<RegExpObject*> source_; |
| bool mustClone_; |
| |
| MRegExp(CompilerConstraintList* constraints, RegExpObject* source, bool mustClone) |
| : source_(source), |
| mustClone_(mustClone) |
| { |
| setResultType(MIRType_Object); |
| setResultTypeSet(MakeSingletonTypeSet(constraints, source)); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(RegExp) |
| |
| static MRegExp* New(TempAllocator& alloc, CompilerConstraintList* constraints, |
| RegExpObject* source, bool mustClone) |
| { |
| return new(alloc) MRegExp(constraints, source, mustClone); |
| } |
| |
| bool mustClone() const { |
| return mustClone_; |
| } |
| RegExpObject* source() const { |
| return source_; |
| } |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| bool possiblyCalls() const override { |
| return true; |
| } |
| }; |
| |
| class MRegExpExec |
| : public MBinaryInstruction, |
| public MixPolicy<ConvertToStringPolicy<0>, ObjectPolicy<1> >::Data |
| { |
| private: |
| |
| MRegExpExec(MDefinition* regexp, MDefinition* string) |
| : MBinaryInstruction(string, regexp) |
| { |
| // May be object or null. |
| setResultType(MIRType_Value); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(RegExpExec) |
| |
| static MRegExpExec* New(TempAllocator& alloc, MDefinition* regexp, MDefinition* string) { |
| return new(alloc) MRegExpExec(regexp, string); |
| } |
| |
| MDefinition* string() const { |
| return getOperand(0); |
| } |
| |
| MDefinition* regexp() const { |
| return getOperand(1); |
| } |
| |
| bool writeRecoverData(CompactBufferWriter& writer) const override; |
| |
| bool canRecoverOnBailout() const override { |
| // XXX: always return false for now, to work around bug 1132128. |
| if (false && regexp()->isRegExp()) |
| return !regexp()->toRegExp()->source()->needUpdateLastIndex(); |
| return false; |
| } |
| |
| bool possiblyCalls() const override { |
| return true; |
| } |
| }; |
| |
| class MRegExpTest |
| : public MBinaryInstruction, |
| public MixPolicy<ObjectPolicy<1>, ConvertToStringPolicy<0> >::Data |
| { |
| private: |
| |
| MRegExpTest(MDefinition* regexp, MDefinition* string) |
| : MBinaryInstruction(string, regexp) |
| { |
| setResultType(MIRType_Boolean); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(RegExpTest) |
| |
| static MRegExpTest* New(TempAllocator& alloc, MDefinition* regexp, MDefinition* string) { |
| return new(alloc) MRegExpTest(regexp, string); |
| } |
| |
| MDefinition* string() const { |
| return getOperand(0); |
| } |
| MDefinition* regexp() const { |
| return getOperand(1); |
| } |
| |
| bool possiblyCalls() const override { |
| return true; |
| } |
| |
| bool writeRecoverData(CompactBufferWriter& writer) const override; |
| bool canRecoverOnBailout() const override { |
| // RegExpTest has a side-effect on the regexp object's lastIndex |
| // when sticky or global flags are set. |
| // Return false unless we are sure it's not the case. |
| // XXX: always return false for now, to work around bug 1132128. |
| if (false && regexp()->isRegExp()) |
| return !regexp()->toRegExp()->source()->needUpdateLastIndex(); |
| return false; |
| } |
| }; |
| |
| template <class Policy1> |
| class MStrReplace |
| : public MTernaryInstruction, |
| public Mix3Policy<StringPolicy<0>, Policy1, StringPolicy<2> >::Data |
| { |
| protected: |
| |
| MStrReplace(MDefinition* string, MDefinition* pattern, MDefinition* replacement) |
| : MTernaryInstruction(string, pattern, replacement) |
| { |
| setMovable(); |
| setResultType(MIRType_String); |
| } |
| |
| public: |
| |
| MDefinition* string() const { |
| return getOperand(0); |
| } |
| MDefinition* pattern() const { |
| return getOperand(1); |
| } |
| MDefinition* replacement() const { |
| return getOperand(2); |
| } |
| |
| bool possiblyCalls() const override { |
| return true; |
| } |
| }; |
| |
| class MRegExpReplace |
| : public MStrReplace< ObjectPolicy<1> > |
| { |
| private: |
| |
| MRegExpReplace(MDefinition* string, MDefinition* pattern, MDefinition* replacement) |
| : MStrReplace< ObjectPolicy<1> >(string, pattern, replacement) |
| { |
| } |
| |
| public: |
| INSTRUCTION_HEADER(RegExpReplace) |
| |
| static MRegExpReplace* New(TempAllocator& alloc, MDefinition* string, MDefinition* pattern, MDefinition* replacement) { |
| return new(alloc) MRegExpReplace(string, pattern, replacement); |
| } |
| |
| bool writeRecoverData(CompactBufferWriter& writer) const override; |
| bool canRecoverOnBailout() const override { |
| // RegExpReplace will zero the lastIndex field when global flag is set. |
| // So we can only remove this if it's non-global. |
| // XXX: always return false for now, to work around bug 1132128. |
| if (false && pattern()->isRegExp()) |
| return !pattern()->toRegExp()->source()->global(); |
| return false; |
| } |
| }; |
| |
| class MStringReplace |
| : public MStrReplace< StringPolicy<1> > |
| { |
| private: |
| |
| MStringReplace(MDefinition* string, MDefinition* pattern, MDefinition* replacement) |
| : MStrReplace< StringPolicy<1> >(string, pattern, replacement) |
| { |
| } |
| |
| public: |
| INSTRUCTION_HEADER(StringReplace) |
| |
| static MStringReplace* New(TempAllocator& alloc, MDefinition* string, MDefinition* pattern, MDefinition* replacement) { |
| return new(alloc) MStringReplace(string, pattern, replacement); |
| } |
| |
| bool congruentTo(const MDefinition* ins) const override { |
| return congruentIfOperandsEqual(ins); |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| |
| bool writeRecoverData(CompactBufferWriter& writer) const override; |
| bool canRecoverOnBailout() const override { |
| if (pattern()->isRegExp()) |
| return !pattern()->toRegExp()->source()->global(); |
| return false; |
| } |
| }; |
| |
| 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) |
| |
| static MSubstr* New(TempAllocator& alloc, MDefinition* string, MDefinition* begin, |
| MDefinition* length) |
| { |
| return new(alloc) MSubstr(string, begin, length); |
| } |
| |
| MDefinition* string() { |
| return getOperand(0); |
| } |
| |
| MDefinition* begin() { |
| return getOperand(1); |
| } |
| |
| MDefinition* length() { |
| return getOperand(2); |
| } |
| |
| 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)) |
| {} |
| |
| private: |
| LambdaFunctionInfo(const LambdaFunctionInfo&) = delete; |
| void operator=(const LambdaFunctionInfo&) = delete; |
| }; |
| |
| class MLambda |
| : public MBinaryInstruction, |
| public SingleObjectPolicy::Data |
| { |
| const LambdaFunctionInfo info_; |
| |
| MLambda(CompilerConstraintList* constraints, MDefinition* scopeChain, MConstant* cst) |
| : MBinaryInstruction(scopeChain, cst), info_(&cst->value().toObject().as<JSFunction>()) |
| { |
| setResultType(MIRType_Object); |
| if (!info().fun->isSingleton() && !ObjectGroup::useSingletonForClone(info().fun)) |
| setResultTypeSet(MakeSingletonTypeSet(constraints, info().fun)); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(Lambda) |
| |
| static MLambda* New(TempAllocator& alloc, CompilerConstraintList* constraints, |
| MDefinition* scopeChain, MConstant* fun) |
| { |
| return new(alloc) MLambda(constraints, scopeChain, fun); |
| } |
| MDefinition* scopeChain() const { |
| return getOperand(0); |
| } |
| MConstant* functionOperand() const { |
| return getOperand(1)->toConstant(); |
| } |
| const LambdaFunctionInfo& info() const { |
| return info_; |
| } |
| bool writeRecoverData(CompactBufferWriter& writer) const override; |
| bool canRecoverOnBailout() const override { |
| return true; |
| } |
| }; |
| |
| class MLambdaArrow |
| : public MBinaryInstruction, |
| public MixPolicy<ObjectPolicy<0>, BoxPolicy<1>>::Data |
| { |
| const LambdaFunctionInfo info_; |
| |
| MLambdaArrow(CompilerConstraintList* constraints, MDefinition* scopeChain, |
| MDefinition* newTarget_, JSFunction* fun) |
| : MBinaryInstruction(scopeChain, newTarget_), info_(fun) |
| { |
| setResultType(MIRType_Object); |
| MOZ_ASSERT(!ObjectGroup::useSingletonForClone(fun)); |
| if (!fun->isSingleton()) |
| setResultTypeSet(MakeSingletonTypeSet(constraints, fun)); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(LambdaArrow) |
| |
| static MLambdaArrow* New(TempAllocator& alloc, CompilerConstraintList* constraints, |
| MDefinition* scopeChain, MDefinition* newTarget_, JSFunction* fun) |
| { |
| return new(alloc) MLambdaArrow(constraints, scopeChain, newTarget_, fun); |
| } |
| MDefinition* scopeChain() const { |
| return getOperand(0); |
| } |
| MDefinition* newTargetDef() const { |
| return getOperand(1); |
| } |
| const LambdaFunctionInfo& info() const { |
| return info_; |
| } |
| }; |
| |
| // Returns obj->slots. |
| class MSlots |
| : public MUnaryInstruction, |
| public SingleObjectPolicy::Data |
| { |
| explicit MSlots(MDefinition* object) |
| : MUnaryInstruction(object) |
| { |
| setResultType(MIRType_Slots); |
| setMovable(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(Slots) |
| |
| static MSlots* New(TempAllocator& alloc, MDefinition* object) { |
| return new(alloc) MSlots(object); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| 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) |
| : MUnaryInstruction(object), unboxed_(unboxed) |
| { |
| setResultType(MIRType_Elements); |
| setMovable(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(Elements) |
| |
| static MElements* New(TempAllocator& alloc, MDefinition* object, bool unboxed = false) { |
| return new(alloc) MElements(object, unboxed); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| 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); |
| } |
| bool mightAlias(const MDefinition* store) const override; |
| |
| 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) |
| static MConstantElements* New(TempAllocator& alloc, SharedMem<void*> v) { |
| return new(alloc) MConstantElements(v); |
| } |
| |
| 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) |
| |
| static MConvertElementsToDoubles* New(TempAllocator& alloc, MDefinition* elements) { |
| return new(alloc) MConvertElementsToDoubles(elements); |
| } |
| |
| MDefinition* elements() const { |
| return getOperand(0); |
| } |
| 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) |
| |
| static MMaybeToDoubleElement* New(TempAllocator& alloc, MDefinition* elements, |
| MDefinition* value) |
| { |
| return new(alloc) MMaybeToDoubleElement(elements, value); |
| } |
| |
| MDefinition* elements() const { |
| return getOperand(0); |
| } |
| MDefinition* value() const { |
| return getOperand(1); |
| } |
| 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) |
| |
| static MMaybeCopyElementsForWrite* New(TempAllocator& alloc, MDefinition* object, bool checkNative) { |
| return new(alloc) MMaybeCopyElementsForWrite(object, checkNative); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| 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) |
| |
| static MInitializedLength* New(TempAllocator& alloc, MDefinition* elements) { |
| return new(alloc) MInitializedLength(elements); |
| } |
| |
| MDefinition* elements() const { |
| return getOperand(0); |
| } |
| bool congruentTo(const MDefinition* ins) const override { |
| return congruentIfOperandsEqual(ins); |
| } |
| AliasSet getAliasSet() const override { |
| return AliasSet::Load(AliasSet::ObjectFields); |
| } |
| bool mightAlias(const MDefinition* store) const override; |
| |
| 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) |
| |
| static MSetInitializedLength* New(TempAllocator& alloc, MDefinition* elements, MDefinition* index) { |
| return new(alloc) MSetInitializedLength(elements, index); |
| } |
| |
| MDefinition* elements() const { |
| return getOperand(0); |
| } |
| MDefinition* index() const { |
| return getOperand(1); |
| } |
| 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) |
| |
| static MUnboxedArrayLength* New(TempAllocator& alloc, MDefinition* object) { |
| return new(alloc) MUnboxedArrayLength(object); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| 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) |
| |
| static MUnboxedArrayInitializedLength* New(TempAllocator& alloc, MDefinition* object) { |
| return new(alloc) MUnboxedArrayInitializedLength(object); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| bool congruentTo(const MDefinition* ins) const override { |
| return congruentIfOperandsEqual(ins); |
| } |
| AliasSet getAliasSet() const override { |
| return AliasSet::Load(AliasSet::ObjectFields); |
| } |
| bool mightAlias(const MDefinition* store) const override; |
| |
| 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) |
| |
| static MIncrementUnboxedArrayInitializedLength* New(TempAllocator& alloc, MDefinition* obj) { |
| return new(alloc) MIncrementUnboxedArrayInitializedLength(obj); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| 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) |
| |
| static MSetUnboxedArrayInitializedLength* New(TempAllocator& alloc, MDefinition* obj, |
| MDefinition* length) { |
| return new(alloc) MSetUnboxedArrayInitializedLength(obj, length); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| MDefinition* length() const { |
| return getOperand(1); |
| } |
| 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) |
| |
| static MArrayLength* New(TempAllocator& alloc, MDefinition* elements) { |
| return new(alloc) MArrayLength(elements); |
| } |
| |
| MDefinition* elements() const { |
| return getOperand(0); |
| } |
| 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) |
| |
| static MSetArrayLength* New(TempAllocator& alloc, MDefinition* elements, MDefinition* index) { |
| return new(alloc) MSetArrayLength(elements, index); |
| } |
| |
| MDefinition* elements() const { |
| return getOperand(0); |
| } |
| MDefinition* index() const { |
| return getOperand(1); |
| } |
| AliasSet getAliasSet() const override { |
| return AliasSet::Store(AliasSet::ObjectFields); |
| } |
| }; |
| |
| // 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) |
| |
| static MTypedArrayLength* New(TempAllocator& alloc, MDefinition* obj) { |
| return new(alloc) MTypedArrayLength(obj); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| 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) |
| |
| static MTypedArrayElements* New(TempAllocator& alloc, MDefinition* object) { |
| return new(alloc) MTypedArrayElements(object); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| 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) |
| |
| static MSetDisjointTypedElements* |
| New(TempAllocator& alloc, MDefinition* target, MDefinition* targetOffset, |
| MDefinition* source) |
| { |
| return new(alloc) MSetDisjointTypedElements(target, targetOffset, source); |
| } |
| |
| MDefinition* target() const { |
| return getOperand(0); |
| } |
| |
| MDefinition* targetOffset() const { |
| return getOperand(1); |
| } |
| |
| MDefinition* source() const { |
| return getOperand(2); |
| } |
| |
| 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) |
| |
| static MTypedObjectElements* New(TempAllocator& alloc, MDefinition* object, |
| bool definitelyOutline) { |
| return new(alloc) MTypedObjectElements(object, definitelyOutline); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| 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) |
| |
| static MSetTypedObjectOffset* New(TempAllocator& alloc, |
| MDefinition* object, |
| MDefinition* offset) |
| { |
| return new(alloc) MSetTypedObjectOffset(object, offset); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| |
| MDefinition* offset() const { |
| return getOperand(1); |
| } |
| |
| 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) |
| |
| static MKeepAliveObject* New(TempAllocator& alloc, MDefinition* object) { |
| return new(alloc) MKeepAliveObject(object); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| }; |
| |
| // 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* New(TempAllocator& alloc, MDefinition* elements, |
| CompilerConstraintList* constraints = nullptr) |
| { |
| return new(alloc) MNot(elements, constraints); |
| } |
| static MNot* NewAsmJS(TempAllocator& alloc, MDefinition* elements) { |
| MNot* ins = new(alloc) MNot(elements); |
| ins->setResultType(MIRType_Int32); |
| return ins; |
| } |
| |
| INSTRUCTION_HEADER(Not) |
| |
| 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); |
| } |
| 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 NoTypePolicy::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) |
| |
| static MBoundsCheck* New(TempAllocator& alloc, MDefinition* index, MDefinition* length) { |
| return new(alloc) MBoundsCheck(index, length); |
| } |
| MDefinition* index() const { |
| return getOperand(0); |
| } |
| MDefinition* length() const { |
| return getOperand(1); |
| } |
| 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 NoTypePolicy::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) |
| |
| static MBoundsCheckLower* New(TempAllocator& alloc, MDefinition* index) { |
| return new(alloc) MBoundsCheckLower(index); |
| } |
| |
| MDefinition* index() const { |
| return getOperand(0); |
| } |
| 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) |
| : 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) |
| |
| static MLoadElement* New(TempAllocator& alloc, MDefinition* elements, MDefinition* index, |
| bool needsHoleCheck, bool loadDoubles, int32_t offsetAdjustment = 0) { |
| return new(alloc) MLoadElement(elements, index, needsHoleCheck, loadDoubles, offsetAdjustment); |
| } |
| |
| MDefinition* elements() const { |
| return getOperand(0); |
| } |
| MDefinition* index() const { |
| return getOperand(1); |
| } |
| 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); |
| } |
| MDefinition* foldsTo(TempAllocator& alloc) override; |
| AliasSet getAliasSet() const override { |
| return AliasSet::Load(AliasSet::Element); |
| } |
| bool mightAlias(const MDefinition* store) const override; |
| |
| 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) |
| |
| static MLoadElementHole* New(TempAllocator& alloc, MDefinition* elements, MDefinition* index, |
| MDefinition* initLength, JSValueType unboxedType, |
| bool needsHoleCheck) { |
| return new(alloc) MLoadElementHole(elements, index, initLength, |
| unboxedType, needsHoleCheck); |
| } |
| |
| MDefinition* elements() const { |
| return getOperand(0); |
| } |
| MDefinition* index() const { |
| return getOperand(1); |
| } |
| MDefinition* initLength() const { |
| return getOperand(2); |
| } |
| 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) |
| |
| static MLoadUnboxedObjectOrNull* New(TempAllocator& alloc, |
| MDefinition* elements, MDefinition* index, |
| NullBehavior nullBehavior, int32_t offsetAdjustment) { |
| return new(alloc) MLoadUnboxedObjectOrNull(elements, index, nullBehavior, |
| offsetAdjustment); |
| } |
| |
| MDefinition* elements() const { |
| return getOperand(0); |
| } |
| MDefinition* index() const { |
| return getOperand(1); |
| } |
| 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); |
| } |
| bool mightAlias(const MDefinition* store) const override; |
| |
| ALLOW_CLONE(MLoadUnboxedObjectOrNull) |
| }; |
| |
| class MLoadUnboxedString |
| : public MBinaryInstruction, |
| public SingleObjectPolicy::Data |
| { |
| int32_t offsetAdjustment_; |
| |
| MLoadUnboxedString(MDefinition* elements, MDefinition* index, int32_t offsetAdjustment) |
| : MBinaryInstruction(elements, index), |
| offsetAdjustment_(offsetAdjustment) |
| { |
| setResultType(MIRType_String); |
| setMovable(); |
| MOZ_ASSERT(IsValidElementsType(elements, offsetAdjustment)); |
| MOZ_ASSERT(index->type() == MIRType_Int32); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(LoadUnboxedString) |
| |
| static MLoadUnboxedString* New(TempAllocator& alloc, |
| MDefinition* elements, MDefinition* index, |
| int32_t offsetAdjustment = 0) { |
| return new(alloc) MLoadUnboxedString(elements, index, offsetAdjustment); |
| } |
| |
| MDefinition* elements() const { |
| return getOperand(0); |
| } |
| MDefinition* index() const { |
| return getOperand(1); |
| } |
| 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); |
| } |
| bool mightAlias(const MDefinition* store) const override; |
| |
| 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) { |
| 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) |
| |
| static MStoreElement* New(TempAllocator& alloc, MDefinition* elements, MDefinition* index, |
| MDefinition* value, |
| bool needsHoleCheck, int32_t offsetAdjustment = 0) { |
| return new(alloc) MStoreElement(elements, index, value, needsHoleCheck, offsetAdjustment); |
| } |
| MDefinition* elements() const { |
| return getOperand(0); |
| } |
| MDefinition* index() const { |
| return getOperand(1); |
| } |
| MDefinition* value() const { |
| return getOperand(2); |
| } |
| 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) |
| |
| static MStoreElementHole* New(TempAllocator& alloc, MDefinition* object, MDefinition* elements, |
| MDefinition* index, MDefinition* value, JSValueType unboxedType) { |
| return new(alloc) MStoreElementHole(object, elements, index, value, unboxedType); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| MDefinition* elements() const { |
| return getOperand(1); |
| } |
| MDefinition* index() const { |
| return getOperand(2); |
| } |
| MDefinition* value() const { |
| return getOperand(3); |
| } |
| 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) |
| }; |
| |
| // 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, bool preBarrier) |
| : 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) |
| |
| static MStoreUnboxedObjectOrNull* New(TempAllocator& alloc, |
| MDefinition* elements, MDefinition* index, |
| MDefinition* value, MDefinition* typedObj, |
| int32_t offsetAdjustment = 0, |
| bool preBarrier = true) { |
| return new(alloc) MStoreUnboxedObjectOrNull(elements, index, value, typedObj, |
| offsetAdjustment, preBarrier); |
| } |
| MDefinition* elements() const { |
| return getOperand(0); |
| } |
| MDefinition* index() const { |
| return getOperand(1); |
| } |
| MDefinition* value() const { |
| return getOperand(2); |
| } |
| MDefinition* typedObj() const { |
| return getOperand(3); |
| } |
| 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, bool preBarrier) |
| : 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) |
| |
| static MStoreUnboxedString* New(TempAllocator& alloc, |
| MDefinition* elements, MDefinition* index, |
| MDefinition* value, int32_t offsetAdjustment = 0, |
| bool preBarrier = true) { |
| return new(alloc) MStoreUnboxedString(elements, index, value, |
| offsetAdjustment, preBarrier); |
| } |
| MDefinition* elements() const { |
| return getOperand(0); |
| } |
| MDefinition* index() const { |
| return getOperand(1); |
| } |
| MDefinition* value() const { |
| return getOperand(2); |
| } |
| 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) |
| |
| static MConvertUnboxedObjectToNative* New(TempAllocator& alloc, MDefinition* obj, |
| ObjectGroup* group); |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| 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(); |
| } |
| }; |
| |
| // 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) |
| |
| static MArrayPopShift* New(TempAllocator& alloc, MDefinition* object, Mode mode, |
| JSValueType unboxedType, bool needsHoleCheck, bool maybeUndefined) |
| { |
| return new(alloc) MArrayPopShift(object, mode, unboxedType, needsHoleCheck, maybeUndefined); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| 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) |
| |
| static MArrayPush* New(TempAllocator& alloc, MDefinition* object, MDefinition* value, |
| JSValueType unboxedType) { |
| return new(alloc) MArrayPush(object, value, unboxedType); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| MDefinition* value() const { |
| return getOperand(1); |
| } |
| 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.concat on two dense arrays. |
| class MArrayConcat |
| : public MBinaryInstruction, |
| public MixPolicy<ObjectPolicy<0>, ObjectPolicy<1> >::Data |
| { |
| CompilerObject templateObj_; |
| gc::InitialHeap initialHeap_; |
| bool unboxedThis_, unboxedArg_; |
| |
| MArrayConcat(CompilerConstraintList* constraints, MDefinition* lhs, MDefinition* rhs, |
| JSObject* templateObj, gc::InitialHeap initialHeap, |
| bool unboxedThis, bool unboxedArg) |
| : MBinaryInstruction(lhs, rhs), |
| templateObj_(templateObj), |
| initialHeap_(initialHeap), |
| unboxedThis_(unboxedThis), |
| unboxedArg_(unboxedArg) |
| { |
| setResultType(MIRType_Object); |
| setResultTypeSet(MakeSingletonTypeSet(constraints, templateObj)); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(ArrayConcat) |
| |
| static MArrayConcat* New(TempAllocator& alloc, CompilerConstraintList* constraints, |
| MDefinition* lhs, MDefinition* rhs, |
| JSObject* templateObj, gc::InitialHeap initialHeap, |
| bool unboxedThis, bool unboxedArg) |
| { |
| return new(alloc) MArrayConcat(constraints, lhs, rhs, templateObj, |
| initialHeap, unboxedThis, unboxedArg); |
| } |
| |
| JSObject* templateObj() const { |
| return templateObj_; |
| } |
| |
| gc::InitialHeap initialHeap() const { |
| return initialHeap_; |
| } |
| |
| bool unboxedThis() const { |
| return unboxedThis_; |
| } |
| |
| bool unboxedArg() const { |
| return unboxedArg_; |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::Store(AliasSet::BoxedOrUnboxedElements(unboxedThis() ? JSVAL_TYPE_INT32 : JSVAL_TYPE_MAGIC) | |
| AliasSet::BoxedOrUnboxedElements(unboxedArg() ? JSVAL_TYPE_INT32 : JSVAL_TYPE_MAGIC) | |
| AliasSet::ObjectFields); |
| } |
| bool possiblyCalls() const override { |
| return true; |
| } |
| }; |
| |
| // 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); |
| setResultTypeSet(obj->resultTypeSet()); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(ArraySlice) |
| |
| static MArraySlice* New(TempAllocator& alloc, CompilerConstraintList* constraints, |
| MDefinition* obj, MDefinition* begin, MDefinition* end, |
| JSObject* templateObj, gc::InitialHeap initialHeap, |
| JSValueType unboxedType) |
| { |
| return new(alloc) MArraySlice(constraints, obj, begin, end, templateObj, |
| initialHeap, unboxedType); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| MDefinition* begin() const { |
| return getOperand(1); |
| } |
| MDefinition* end() const { |
| return getOperand(2); |
| } |
| |
| 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; |
| } |
| }; |
| |
| 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) |
| static MArrayJoin* New(TempAllocator& alloc, MDefinition* array, MDefinition* sep) |
| { |
| return new (alloc) MArrayJoin(array, sep); |
| } |
| MDefinition* array() const { |
| return getOperand(0); |
| } |
| MDefinition* sep() const { |
| return getOperand(1); |
| } |
| bool possiblyCalls() const override { |
| return true; |
| } |
| virtual AliasSet getAliasSet() const override { |
| return AliasSet::Load(AliasSet::Element | AliasSet::ObjectFields); |
| } |
| MDefinition* foldsTo(TempAllocator& alloc) override; |
| }; |
| |
| // See comments above MMemoryBarrier, below. |
| |
| enum MemoryBarrierRequirement |
| { |
| DoesNotRequireMemoryBarrier, |
| DoesRequireMemoryBarrier |
| }; |
| |
| // Also see comments above MMemoryBarrier, below. |
| |
| // 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, |
| int32_t offsetAdjustment, bool canonicalizeDoubles) |
| : 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) |
| |
| static MLoadUnboxedScalar* New(TempAllocator& alloc, MDefinition* elements, MDefinition* index, |
| Scalar::Type storageType, |
| MemoryBarrierRequirement requiresBarrier |
| = DoesNotRequireMemoryBarrier, |
| int32_t offsetAdjustment = 0, |
| bool canonicalizeDoubles = true) |
| { |
| return new(alloc) MLoadUnboxedScalar(elements, index, storageType, |
| requiresBarrier, offsetAdjustment, |
| canonicalizeDoubles); |
| } |
| |
| 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_; |
| } |
| MDefinition* elements() const { |
| return getOperand(0); |
| } |
| MDefinition* index() const { |
| return getOperand(1); |
| } |
| int32_t offsetAdjustment() const { |
| return 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 mightAlias(const MDefinition* store) const override; |
| |
| 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) |
| |
| static MLoadTypedArrayElementHole* New(TempAllocator& alloc, MDefinition* object, MDefinition* index, |
| Scalar::Type arrayType, bool allowDouble) |
| { |
| return new(alloc) MLoadTypedArrayElementHole(object, index, arrayType, allowDouble); |
| } |
| |
| Scalar::Type arrayType() const { |
| return arrayType_; |
| } |
| bool allowDouble() const { |
| return allowDouble_; |
| } |
| bool fallible() const { |
| return arrayType_ == Scalar::Uint32 && !allowDouble_; |
| } |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| MDefinition* index() const { |
| return getOperand(1); |
| } |
| 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, bool needsBoundsCheck) |
| : 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) |
| |
| static MLoadTypedArrayElementStatic* New(TempAllocator& alloc, JSObject* someTypedArray, |
| MDefinition* ptr, int32_t offset = 0, |
| bool needsBoundsCheck = true) |
| { |
| return new(alloc) MLoadTypedArrayElementStatic(someTypedArray, ptr, offset, |
| needsBoundsCheck); |
| } |
| |
| Scalar::Type accessType() const { |
| return AnyTypedArrayType(someTypedArray_); |
| } |
| 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; |
| }; |
| |
| // 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, int32_t offsetAdjustment) |
| : 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) |
| |
| static MStoreUnboxedScalar* New(TempAllocator& alloc, |
| MDefinition* elements, MDefinition* index, |
| MDefinition* value, Scalar::Type storageType, |
| TruncateInputKind truncateInput, |
| MemoryBarrierRequirement requiresBarrier = |
| DoesNotRequireMemoryBarrier, |
| int32_t offsetAdjustment = 0) |
| { |
| return new(alloc) MStoreUnboxedScalar(elements, index, value, storageType, |
| truncateInput, requiresBarrier, offsetAdjustment); |
| } |
| |
| 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_; |
| } |
| MDefinition* elements() const { |
| return getOperand(0); |
| } |
| MDefinition* index() const { |
| return getOperand(1); |
| } |
| MDefinition* value() const { |
| return getOperand(2); |
| } |
| 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) |
| |
| static MStoreTypedArrayElementHole* New(TempAllocator& alloc, MDefinition* elements, |
| MDefinition* length, MDefinition* index, |
| MDefinition* value, Scalar::Type arrayType) |
| { |
| return new(alloc) MStoreTypedArrayElementHole(elements, length, index, value, arrayType); |
| } |
| |
| Scalar::Type arrayType() const { |
| MOZ_ASSERT(!Scalar::isSimdType(writeType()), |
| "arrayType == writeType iff the write type isn't SIMD"); |
| return writeType(); |
| } |
| MDefinition* elements() const { |
| return getOperand(0); |
| } |
| MDefinition* length() const { |
| return getOperand(1); |
| } |
| MDefinition* index() const { |
| return getOperand(2); |
| } |
| MDefinition* value() const { |
| return getOperand(3); |
| } |
| 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, bool needsBoundsCheck) |
| : MBinaryInstruction(ptr, v), |
| StoreUnboxedScalarBase(AnyTypedArrayType(someTypedArray)), |
| 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) |
| |
| static MStoreTypedArrayElementStatic* New(TempAllocator& alloc, JSObject* someTypedArray, |
| MDefinition* ptr, MDefinition* v, |
| int32_t offset = 0, |
| bool needsBoundsCheck = true) |
| { |
| return new(alloc) MStoreTypedArrayElementStatic(someTypedArray, ptr, v, |
| offset, needsBoundsCheck); |
| } |
| |
| 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; |
| }; |
| |
| // 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) |
| |
| static MEffectiveAddress* New(TempAllocator& alloc, MDefinition* base, MDefinition* index, |
| Scale s, int32_t d) |
| { |
| return new(alloc) MEffectiveAddress(base, index, s, d); |
| } |
| 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) |
| |
| static MClampToUint8* New(TempAllocator& alloc, MDefinition* input) { |
| return new(alloc) MClampToUint8(input); |
| } |
| |
| 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) |
| |
| static MLoadFixedSlot* New(TempAllocator& alloc, MDefinition* obj, size_t slot) { |
| return new(alloc) MLoadFixedSlot(obj, slot); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| 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); |
| } |
| |
| bool 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) |
| |
| static MLoadFixedSlotAndUnbox* New(TempAllocator& alloc, MDefinition* obj, size_t slot, |
| MUnbox::Mode mode, MIRType type, BailoutKind kind) |
| { |
| return new(alloc) MLoadFixedSlotAndUnbox(obj, slot, mode, type, kind); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| 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); |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::Load(AliasSet::FixedSlot); |
| } |
| |
| bool 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) |
| |
| 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); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| MDefinition* value() const { |
| return getOperand(1); |
| } |
| 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) |
| { } |
| }; |
| |
| 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; |
| } |
| MResumePoint* takePriorResumePoint() { |
| MResumePoint* rp = priorResumePoint_; |
| priorResumePoint_ = nullptr; |
| return rp; |
| } |
| |
| jsbytecode* pc() const { |
| return pc_; |
| } |
| |
| 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); |
| }; |
| |
| 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) |
| |
| static MGetPropertyCache* New(TempAllocator& alloc, MDefinition* obj, MDefinition* id, |
| bool monitoredResult) { |
| return new(alloc) MGetPropertyCache(obj, id, monitoredResult); |
| } |
| |
| 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_; |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| MDefinition* idval() const { |
| return getOperand(1); |
| } |
| |
| 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; |
| bool updateForReplacement(MDefinition* ins) override; |
| |
| bool allowDoubleResult() const; |
| }; |
| |
| // 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 |
| { |
| struct Entry { |
| // The group and/or shape to guard against. |
| ReceiverGuard receiver; |
| |
| // The property to load, null for loads from unboxed properties. |
| Shape* shape; |
| }; |
| |
| Vector<Entry, 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) |
| |
| 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); |
| } |
| |
| bool addReceiver(const ReceiverGuard& receiver, Shape* shape) { |
| Entry 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_; |
| } |
| MDefinition* obj() const { |
| return getOperand(0); |
| } |
| 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)); |
| } |
| |
| bool mightAlias(const MDefinition* store) 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 |
| { |
| struct Entry { |
| // The group and/or shape to guard against. |
| ReceiverGuard receiver; |
| |
| // The property to store, null for stores to unboxed properties. |
| Shape* shape; |
| }; |
| |
| Vector<Entry, 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) |
| |
| static MSetPropertyPolymorphic* New(TempAllocator& alloc, MDefinition* obj, MDefinition* value, |
| PropertyName* name) { |
| return new(alloc) MSetPropertyPolymorphic(alloc, obj, value, name); |
| } |
| |
| bool addReceiver(const ReceiverGuard& receiver, Shape* shape) { |
| Entry 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_; |
| } |
| MDefinition* obj() const { |
| return getOperand(0); |
| } |
| MDefinition* value() const { |
| return getOperand(1); |
| } |
| 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)); |
| } |
| }; |
| |
| 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) |
| { } |
| }; |
| 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: |
| 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: |
| 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_; |
| } |
| |
| public: |
| MDefinition* input() const { |
| return getOperand(0); |
| } |
| }; |
| |
| // 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_; |
| } |
| }; |
| |
| // 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); |
| } |
| }; |
| |
| class MBindNameCache |
| : public MUnaryInstruction, |
| public SingleObjectPolicy::Data |
| { |
| CompilerPropertyName name_; |
| CompilerScript script_; |
| jsbytecode* pc_; |
| |
| MBindNameCache(MDefinition* scopeChain, PropertyName* name, JSScript* script, jsbytecode* pc) |
| : MUnaryInstruction(scopeChain), name_(name), script_(script), pc_(pc) |
| { |
| setResultType(MIRType_Object); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(BindNameCache) |
| |
| static MBindNameCache* New(TempAllocator& alloc, MDefinition* scopeChain, PropertyName* name, |
| JSScript* script, jsbytecode* pc) |
| { |
| return new(alloc) MBindNameCache(scopeChain, name, script, pc); |
| } |
| |
| MDefinition* scopeChain() const { |
| return getOperand(0); |
| } |
| PropertyName* name() const { |
| return name_; |
| } |
| JSScript* script() const { |
| return script_; |
| } |
| jsbytecode* pc() const { |
| return pc_; |
| } |
| }; |
| |
| // 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) |
| |
| static MGuardShape* New(TempAllocator& alloc, MDefinition* obj, Shape* shape, |
| BailoutKind bailoutKind) |
| { |
| return new(alloc) MGuardShape(obj, shape, bailoutKind); |
| } |
| |
| MDefinition* obj() const { |
| return getOperand(0); |
| } |
| 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); |
| } |
| }; |
| |
| // 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) |
| |
| static MGuardReceiverPolymorphic* New(TempAllocator& alloc, MDefinition* obj) { |
| return new(alloc) MGuardReceiverPolymorphic(alloc, obj); |
| } |
| |
| MDefinition* obj() const { |
| return getOperand(0); |
| } |
| |
| 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); |
| } |
| }; |
| |
| // 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) |
| |
| static MGuardObjectGroup* New(TempAllocator& alloc, MDefinition* obj, ObjectGroup* group, |
| bool bailOnEquality, BailoutKind bailoutKind) { |
| return new(alloc) MGuardObjectGroup(obj, group, bailOnEquality, bailoutKind); |
| } |
| |
| MDefinition* obj() const { |
| return getOperand(0); |
| } |
| 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); |
| } |
| }; |
| |
| // 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) |
| |
| static MGuardObjectIdentity* New(TempAllocator& alloc, MDefinition* obj, MDefinition* expected, |
| bool bailOnEquality) { |
| return new(alloc) MGuardObjectIdentity(obj, expected, bailOnEquality); |
| } |
| |
| MDefinition* obj() const { |
| return getOperand(0); |
| } |
| MDefinition* expected() const { |
| return getOperand(1); |
| } |
| 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) |
| |
| static MGuardClass* New(TempAllocator& alloc, MDefinition* obj, const Class* clasp) { |
| return new(alloc) MGuardClass(obj, clasp); |
| } |
| |
| MDefinition* obj() const { |
| return getOperand(0); |
| } |
| 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) |
| |
| static MGuardUnboxedExpando* New(TempAllocator& alloc, MDefinition* obj, |
| bool requireExpando, BailoutKind bailoutKind) { |
| return new(alloc) MGuardUnboxedExpando(obj, requireExpando, bailoutKind); |
| } |
| |
| MDefinition* obj() const { |
| return getOperand(0); |
| } |
| 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) |
| |
| static MLoadUnboxedExpando* New(TempAllocator& alloc, MDefinition* object) { |
| return new(alloc) MLoadUnboxedExpando(object); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| 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) |
| |
| static MLoadSlot* New(TempAllocator& alloc, MDefinition* slots, uint32_t slot) { |
| return new(alloc) MLoadSlot(slots, slot); |
| } |
| |
| MDefinition* slots() const { |
| return getOperand(0); |
| } |
| 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); |
| } |
| bool mightAlias(const MDefinition* store) const override; |
| |
| ALLOW_CLONE(MLoadSlot) |
| }; |
| |
| // Inline call to access a function's environment (scope chain). |
| class MFunctionEnvironment |
| : public MUnaryInstruction, |
| public SingleObjectPolicy::Data |
| { |
| public: |
| explicit MFunctionEnvironment(MDefinition* function) |
| : MUnaryInstruction(function) |
| { |
| setResultType(MIRType_Object); |
| setMovable(); |
| } |
| |
| INSTRUCTION_HEADER(FunctionEnvironment) |
| |
| static MFunctionEnvironment* New(TempAllocator& alloc, MDefinition* function) { |
| return new(alloc) MFunctionEnvironment(function); |
| } |
| |
| MDefinition* function() const { |
| return getOperand(0); |
| } |
| |
| 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) |
| |
| 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); |
| } |
| |
| MDefinition* slots() const { |
| return getOperand(0); |
| } |
| MDefinition* value() const { |
| return getOperand(1); |
| } |
| 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); |
| } |
| |
| 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) |
| |
| static MGetNameCache* New(TempAllocator& alloc, MDefinition* obj, PropertyName* name, |
| AccessKind kind) |
| { |
| return new(alloc) MGetNameCache(obj, name, kind); |
| } |
| MDefinition* scopeObj() const { |
| return getOperand(0); |
| } |
| PropertyName* name() const { |
| return name_; |
| } |
| AccessKind accessKind() const { |
| return kind_; |
| } |
| }; |
| |
| class MCallGetIntrinsicValue : public MNullaryInstruction |
| { |
| CompilerPropertyName name_; |
| |
| explicit MCallGetIntrinsicValue(PropertyName* name) |
| : name_(name) |
| { |
| setResultType(MIRType_Value); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(CallGetIntrinsicValue) |
| |
| static MCallGetIntrinsicValue* New(TempAllocator& alloc, PropertyName* name) { |
| return new(alloc) MCallGetIntrinsicValue(name); |
| } |
| PropertyName* name() const { |
| return name_; |
| } |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| bool possiblyCalls() const override { |
| return true; |
| } |
| }; |
| |
| 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: |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| MDefinition* value() const { |
| return getOperand(1); |
| } |
| PropertyName* name() const { |
| return name_; |
| } |
| bool strict() const { |
| return strict_; |
| } |
| }; |
| |
| class MSetElementInstruction |
| : public MTernaryInstruction |
| { |
| bool strict_; |
| protected: |
| MSetElementInstruction(MDefinition* object, MDefinition* index, MDefinition* value, bool strict) |
| : MTernaryInstruction(object, index, value), |
| strict_(strict) |
| { |
| } |
| |
| public: |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| MDefinition* index() const { |
| return getOperand(1); |
| } |
| MDefinition* value() const { |
| return getOperand(2); |
| } |
| 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) |
| |
| static MDeleteProperty* New(TempAllocator& alloc, MDefinition* obj, PropertyName* name, |
| bool strict) |
| { |
| return new(alloc) MDeleteProperty(obj, name, strict); |
| } |
| MDefinition* value() const { |
| return getOperand(0); |
| } |
| PropertyName* name() const { |
| return name_; |
| } |
| bool strict() const { |
| return strict_; |
| } |
| }; |
| |
| 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) |
| |
| static MDeleteElement* New(TempAllocator& alloc, MDefinition* value, MDefinition* index, |
| bool strict) |
| { |
| return new(alloc) MDeleteElement(value, index, strict); |
| } |
| MDefinition* value() const { |
| return getOperand(0); |
| } |
| MDefinition* index() const { |
| return getOperand(1); |
| } |
| 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) |
| |
| static MCallSetProperty* New(TempAllocator& alloc, MDefinition* obj, MDefinition* value, |
| PropertyName* name, bool strict) |
| { |
| return new(alloc) MCallSetProperty(obj, value, name, strict); |
| } |
| |
| 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) |
| |
| static MSetPropertyCache* New(TempAllocator& alloc, MDefinition* obj, MDefinition* id, |
| MDefinition* value, bool strict, bool typeBarrier, |
| bool guardHoles) |
| { |
| return new(alloc) MSetPropertyCache(obj, id, value, strict, typeBarrier, guardHoles); |
| } |
| |
| bool needsTypeBarrier() const { |
| return needsTypeBarrier_; |
| } |
| |
| bool guardHoles() const { |
| return guardHoles_; |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| MDefinition* idval() const { |
| return getOperand(1); |
| } |
| MDefinition* value() const { |
| return getOperand(2); |
| } |
| 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) |
| |
| static MCallGetProperty* New(TempAllocator& alloc, MDefinition* value, PropertyName* name) |
| { |
| return new(alloc) MCallGetProperty(value, name); |
| } |
| MDefinition* value() const { |
| return getOperand(0); |
| } |
| 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; |
| } |
| }; |
| |
| // 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) |
| |
| static MCallGetElement* New(TempAllocator& alloc, MDefinition* lhs, MDefinition* rhs) { |
| return new(alloc) MCallGetElement(lhs, rhs); |
| } |
| 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) |
| |
| static MCallSetElement* New(TempAllocator& alloc, MDefinition* object, MDefinition* index, |
| MDefinition* value, bool strict) |
| { |
| return new(alloc) MCallSetElement(object, index, value, strict); |
| } |
| |
| 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) |
| |
| static MCallInitElementArray* New(TempAllocator& alloc, MDefinition* obj, uint32_t index, |
| MDefinition* val) |
| { |
| return new(alloc) MCallInitElementArray(obj, index, val); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| |
| uint32_t index() const { |
| return index_; |
| } |
| |
| MDefinition* value() const { |
| return getOperand(1); |
| } |
| |
| 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) |
| |
| static MSetDOMProperty* New(TempAllocator& alloc, JSJitSetterOp func, MDefinition* obj, |
| MDefinition* val) |
| { |
| return new(alloc) MSetDOMProperty(func, obj, val); |
| } |
| |
| JSJitSetterOp fun() const { |
| return func_; |
| } |
| |
| MDefinition* object() { |
| return getOperand(0); |
| } |
| |
| MDefinition* value() |
| { |
| return getOperand(1); |
| } |
| |
| 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_; |
| } |
| |
| 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) |
| |
| static MGetDOMProperty* New(TempAllocator& alloc, const JSJitInfo* info, MDefinition* obj, |
| MDefinition* guard, MDefinition* globalGuard) |
| { |
| MGetDOMProperty* 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; |
| } |
| MDefinition* object() { |
| return getOperand(0); |
| } |
| |
| 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) |
| { |
| MGetDOMMember* 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) |
| |
| static MStringLength* New(TempAllocator& alloc, MDefinition* string) { |
| return new(alloc) MStringLength(string); |
| } |
| |
| MDefinition* foldsTo(TempAllocator& alloc) override; |
| |
| MDefinition* string() const { |
| return getOperand(0); |
| } |
| 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; |
| |
| 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) |
| |
| static MFloor* New(TempAllocator& alloc, MDefinition* num) { |
| return new(alloc) MFloor(num); |
| } |
| |
| 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; |
| 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) |
| |
| static MCeil* New(TempAllocator& alloc, MDefinition* num) { |
| return new(alloc) MCeil(num); |
| } |
| |
| 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; |
| 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) |
| |
| static MRound* New(TempAllocator& alloc, MDefinition* num) { |
| return new(alloc) MRound(num); |
| } |
| |
| 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); |
| } |
| |
| bool writeRecoverData(CompactBufferWriter& writer) const override; |
| bool canRecoverOnBailout() const override { |
| return true; |
| } |
| |
| ALLOW_CLONE(MRound) |
| }; |
| |
| class MIteratorStart |
| : public MUnaryInstruction, |
| public SingleObjectPolicy::Data |
| { |
| uint8_t flags_; |
| |
| MIteratorStart(MDefinition* obj, uint8_t flags) |
| : MUnaryInstruction(obj), flags_(flags) |
| { |
| setResultType(MIRType_Object); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(IteratorStart) |
| |
| static MIteratorStart* New(TempAllocator& alloc, MDefinition* obj, uint8_t flags) { |
| return new(alloc) MIteratorStart(obj, flags); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| 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) |
| |
| static MIteratorMore* New(TempAllocator& alloc, MDefinition* iter) { |
| return new(alloc) MIteratorMore(iter); |
| } |
| |
| MDefinition* iterator() const { |
| return getOperand(0); |
| } |
| }; |
| |
| class MIsNoIter |
| : public MUnaryInstruction, |
| public NoTypePolicy::Data |
| { |
| explicit MIsNoIter(MDefinition* def) |
| : MUnaryInstruction(def) |
| { |
| setResultType(MIRType_Boolean); |
| setMovable(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(IsNoIter) |
| |
| static MIsNoIter* New(TempAllocator& alloc, MDefinition* def) { |
| return new(alloc) MIsNoIter(def); |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| }; |
| |
| class MIteratorEnd |
| : public MUnaryInstruction, |
| public SingleObjectPolicy::Data |
| { |
| explicit MIteratorEnd(MDefinition* iter) |
| : MUnaryInstruction(iter) |
| { } |
| |
| public: |
| INSTRUCTION_HEADER(IteratorEnd) |
| |
| static MIteratorEnd* New(TempAllocator& alloc, MDefinition* iter) { |
| return new(alloc) MIteratorEnd(iter); |
| } |
| |
| MDefinition* iterator() const { |
| return getOperand(0); |
| } |
| }; |
| |
| // 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) |
| |
| static MIn* New(TempAllocator& alloc, MDefinition* key, MDefinition* obj) { |
| return new(alloc) MIn(key, obj); |
| } |
| |
| 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) |
| |
| static MInArray* New(TempAllocator& alloc, MDefinition* elements, MDefinition* index, |
| MDefinition* initLength, MDefinition* object, |
| bool needsHoleCheck, JSValueType unboxedType) |
| { |
| return new(alloc) MInArray(elements, index, initLength, object, needsHoleCheck, |
| unboxedType); |
| } |
| |
| MDefinition* elements() const { |
| return getOperand(0); |
| } |
| MDefinition* index() const { |
| return getOperand(1); |
| } |
| MDefinition* initLength() const { |
| return getOperand(2); |
| } |
| MDefinition* object() const { |
| return getOperand(3); |
| } |
| 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) |
| |
| static MInstanceOf* New(TempAllocator& alloc, MDefinition* obj, JSObject* proto) { |
| return new(alloc) MInstanceOf(obj, proto); |
| } |
| |
| JSObject* prototypeObject() { |
| return 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) |
| |
| static MCallInstanceOf* New(TempAllocator& alloc, MDefinition* obj, MDefinition* proto) { |
| return new(alloc) MCallInstanceOf(obj, proto); |
| } |
| |
| }; |
| |
| class MArgumentsLength : public MNullaryInstruction |
| { |
| MArgumentsLength() |
| { |
| setResultType(MIRType_Int32); |
| setMovable(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(ArgumentsLength) |
| |
| static MArgumentsLength* New(TempAllocator& alloc) { |
| return new(alloc) MArgumentsLength(); |
| } |
| |
| 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; |
| |
| 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) |
| |
| static MGetFrameArgument* New(TempAllocator& alloc, MDefinition* idx, bool scriptHasSetArg) { |
| return new(alloc) MGetFrameArgument(idx, scriptHasSetArg); |
| } |
| |
| MDefinition* index() const { |
| return getOperand(0); |
| } |
| |
| 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) |
| |
| static MNewTarget* New(TempAllocator& alloc) { |
| return new(alloc) MNewTarget(); |
| } |
| |
| 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) |
| |
| static MSetFrameArgument* New(TempAllocator& alloc, uint32_t argno, MDefinition* value) { |
| return new(alloc) MSetFrameArgument(argno, value); |
| } |
| |
| uint32_t argno() const { |
| return argno_; |
| } |
| |
| MDefinition* value() const { |
| return getOperand(0); |
| } |
| |
| 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) |
| |
| static MRest* New(TempAllocator& alloc, CompilerConstraintList* constraints, |
| MDefinition* numActuals, unsigned numFormals, |
| ArrayObject* templateObject) |
| { |
| return new(alloc) MRest(constraints, numActuals, numFormals, templateObject); |
| } |
| |
| MDefinition* numActuals() const { |
| return getOperand(0); |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| bool possiblyCalls() const override { |
| return true; |
| } |
| }; |
| |
| 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) |
| |
| static MFilterTypeSet* New(TempAllocator& alloc, MDefinition* def, TemporaryTypeSet* types) { |
| return new(alloc) MFilterTypeSet(def, types); |
| } |
| |
| 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 true; } |
| 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) |
| : 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) |
| |
| static MTypeBarrier* New(TempAllocator& alloc, MDefinition* def, TemporaryTypeSet* types, |
| BarrierKind kind = BarrierKind::TypeSet) { |
| return new(alloc) MTypeBarrier(def, types, kind); |
| } |
| |
| 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) |
| |
| static MMonitorTypes* New(TempAllocator& alloc, MDefinition* def, const TemporaryTypeSet* types, |
| BarrierKind kind) { |
| return new(alloc) MMonitorTypes(def, types, kind); |
| } |
| |
| 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) |
| |
| static MPostWriteBarrier* New(TempAllocator& alloc, MDefinition* obj, MDefinition* value) { |
| return new(alloc) MPostWriteBarrier(obj, value); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| |
| MDefinition* value() const { |
| return getOperand(1); |
| } |
| |
| 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) |
| }; |
| |
| class MNewDeclEnvObject : public MNullaryInstruction |
| { |
| CompilerGCPointer<DeclEnvObject*> templateObj_; |
| |
| explicit MNewDeclEnvObject(DeclEnvObject* templateObj) |
| : MNullaryInstruction(), |
| templateObj_(templateObj) |
| { |
| setResultType(MIRType_Object); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(NewDeclEnvObject) |
| |
| static MNewDeclEnvObject* New(TempAllocator& alloc, DeclEnvObject* templateObj) { |
| return new(alloc) MNewDeclEnvObject(templateObj); |
| } |
| |
| DeclEnvObject* templateObj() { |
| return templateObj_; |
| } |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| }; |
| |
| 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(); |
| } |
| }; |
| |
| class MNewCallObject : public MNewCallObjectBase |
| { |
| public: |
| INSTRUCTION_HEADER(NewCallObject) |
| |
| explicit MNewCallObject(CallObject* templateObj) |
| : MNewCallObjectBase(templateObj) |
| {} |
| |
| static MNewCallObject* |
| New(TempAllocator& alloc, CallObject* templateObj) |
| { |
| return new(alloc) MNewCallObject(templateObj); |
| } |
| }; |
| |
| class MNewRunOnceCallObject : public MNewCallObjectBase |
| { |
| public: |
| INSTRUCTION_HEADER(NewRunOnceCallObject) |
| |
| explicit MNewRunOnceCallObject(CallObject* templateObj) |
| : MNewCallObjectBase(templateObj) |
| {} |
| |
| static MNewRunOnceCallObject* |
| New(TempAllocator& alloc, CallObject* templateObj) |
| { |
| return new(alloc) MNewRunOnceCallObject(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) |
| |
| static MNewStringObject* New(TempAllocator& alloc, MDefinition* input, JSObject* templateObj) { |
| return new(alloc) MNewStringObject(input, templateObj); |
| } |
| |
| StringObject* templateObj() const; |
| }; |
| |
| // This is an alias for MLoadFixedSlot. |
| class MEnclosingScope : public MLoadFixedSlot |
| { |
| explicit MEnclosingScope(MDefinition* obj) |
| : MLoadFixedSlot(obj, ScopeObject::enclosingScopeSlot()) |
| { |
| setResultType(MIRType_Object); |
| } |
| |
| public: |
| static MEnclosingScope* New(TempAllocator& alloc, MDefinition* obj) { |
| return new(alloc) MEnclosingScope(obj); |
| } |
| |
| AliasSet getAliasSet() const override { |
| // ScopeObject 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(). |
| 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(); |
| } |
| } |
| |
| 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) |
| |
| static MIsCallable* New(TempAllocator& alloc, MDefinition* obj) { |
| return new(alloc) MIsCallable(obj); |
| } |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| 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) |
| static MIsObject* New(TempAllocator& alloc, MDefinition* obj) { |
| return new(alloc) MIsObject(obj); |
| } |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| 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) |
| |
| static MHasClass* New(TempAllocator& alloc, MDefinition* obj, const Class* clasp) { |
| return new(alloc) MHasClass(obj, clasp); |
| } |
| |
| MDefinition* object() const { |
| return getOperand(0); |
| } |
| 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) |
| |
| static MCheckReturn* New(TempAllocator& alloc, MDefinition* retVal, MDefinition* thisVal) { |
| return new (alloc) MCheckReturn(retVal, thisVal); |
| } |
| |
| MDefinition* returnValue() const { |
| return getOperand(0); |
| } |
| MDefinition* thisValue() const { |
| return getOperand(1); |
| } |
| }; |
| |
| // 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) |
| |
| static MRecompileCheck* New(TempAllocator& alloc, JSScript* script_, uint32_t recompileThreshold, |
| RecompileCheckType type) |
| { |
| return new(alloc) MRecompileCheck(script_, recompileThreshold, type); |
| } |
| |
| 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(); |
| } |
| }; |
| |
| // All barriered operations - MMemoryBarrier, 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. |
| |
| class MMemoryBarrier |
| : public MNullaryInstruction |
| { |
| // The type is a combination of the memory barrier types in AtomicOp.h. |
| const MemoryBarrierBits type_; |
| |
| explicit MMemoryBarrier(MemoryBarrierBits type) |
| : type_(type) |
| { |
| MOZ_ASSERT((type_ & ~MembarAllbits) == MembarNobits); |
| setGuard(); // Not removable |
| } |
| |
| public: |
| INSTRUCTION_HEADER(MemoryBarrier) |
| |
| static MMemoryBarrier* New(TempAllocator& alloc, MemoryBarrierBits type = MembarFull) { |
| return new(alloc) MMemoryBarrier(type); |
| } |
| MemoryBarrierBits type() const { |
| return type_; |
| } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::Store(AliasSet::UnboxedElement); |
| } |
| }; |
| |
| class MAtomicIsLockFree |
| : public MUnaryInstruction, |
| public ConvertToInt32Policy<0>::Data |
| { |
| explicit MAtomicIsLockFree(MDefinition* value) |
| : MUnaryInstruction(value) |
| { |
| setResultType(MIRType_Boolean); |
| setMovable(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(AtomicIsLockFree) |
| |
| static MAtomicIsLockFree* New(TempAllocator& alloc, MDefinition* value) { |
| return new(alloc) MAtomicIsLockFree(value); |
| } |
| |
| MDefinition* foldsTo(TempAllocator& alloc) override; |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::None(); |
| } |
| |
| bool congruentTo(const MDefinition* ins) const override { |
| return congruentIfOperandsEqual(ins); |
| } |
| |
| 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) |
| |
| static MGuardSharedTypedArray* New(TempAllocator& alloc, MDefinition* obj) { |
| return new(alloc) MGuardSharedTypedArray(obj); |
| } |
| MDefinition* obj() const { |
| return getOperand(0); |
| } |
| 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) |
| |
| static MCompareExchangeTypedArrayElement* New(TempAllocator& alloc, MDefinition* elements, |
| MDefinition* index, Scalar::Type arrayType, |
| MDefinition* oldval, MDefinition* newval) |
| { |
| return new(alloc) MCompareExchangeTypedArrayElement(elements, index, arrayType, oldval, newval); |
| } |
| bool isByteArray() const { |
| return (arrayType_ == Scalar::Int8 || |
| arrayType_ == Scalar::Uint8); |
| } |
| MDefinition* elements() { |
| return getOperand(0); |
| } |
| MDefinition* index() { |
| return getOperand(1); |
| } |
| MDefinition* oldval() { |
| return getOperand(2); |
| } |
| int oldvalOperand() { |
| return 2; |
| } |
| MDefinition* newval() { |
| return getOperand(3); |
| } |
| 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) |
| |
| static MAtomicExchangeTypedArrayElement* New(TempAllocator& alloc, MDefinition* elements, |
| MDefinition* index, MDefinition* value, |
| Scalar::Type arrayType) |
| { |
| return new(alloc) MAtomicExchangeTypedArrayElement(elements, index, value, arrayType); |
| } |
| |
| bool isByteArray() const { |
| return (arrayType_ == Scalar::Int8 || |
| arrayType_ == Scalar::Uint8); |
| } |
| MDefinition* elements() { |
| return getOperand(0); |
| } |
| MDefinition* index() { |
| return getOperand(1); |
| } |
| MDefinition* value() { |
| return getOperand(2); |
| } |
| 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) |
| |
| static MAtomicTypedArrayElementBinop* New(TempAllocator& alloc, AtomicOp op, |
| MDefinition* elements, MDefinition* index, |
| Scalar::Type arrayType, MDefinition* value) |
| { |
| return new(alloc) MAtomicTypedArrayElementBinop(op, elements, index, arrayType, value); |
| } |
| |
| bool isByteArray() const { |
| return (arrayType_ == Scalar::Int8 || |
| arrayType_ == Scalar::Uint8); |
| } |
| AtomicOp operation() const { |
| return op_; |
| } |
| Scalar::Type arrayType() const { |
| return arrayType_; |
| } |
| MDefinition* elements() { |
| return getOperand(0); |
| } |
| MDefinition* index() { |
| return getOperand(1); |
| } |
| MDefinition* value() { |
| return getOperand(2); |
| } |
| AliasSet getAliasSet() const override { |
| return AliasSet::Store(AliasSet::UnboxedElement); |
| } |
| }; |
| |
| class MDebugger : public MNullaryInstruction |
| { |
| public: |
| INSTRUCTION_HEADER(Debugger) |
| |
| static MDebugger* New(TempAllocator& alloc) { |
| return new(alloc) MDebugger(); |
| } |
| }; |
| |
| class MCheckObjCoercible |
| : public MUnaryInstruction, |
| public BoxInputsPolicy::Data |
| { |
| explicit MCheckObjCoercible(MDefinition* toCheck) |
| : MUnaryInstruction(toCheck) |
| { |
| setGuard(); |
| setResultType(MIRType_Value); |
| setResultTypeSet(toCheck->resultTypeSet()); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(CheckObjCoercible) |
| static MCheckObjCoercible* New(TempAllocator& alloc, MDefinition* toCheck) { |
| return new(alloc) MCheckObjCoercible(toCheck); |
| } |
| |
| MDefinition* checkValue() { |
| return getOperand(0); |
| } |
| }; |
| |
| class MAsmJSNeg |
| : public MUnaryInstruction, |
| public NoTypePolicy::Data |
| { |
| MAsmJSNeg(MDefinition* op, MIRType type) |
| : MUnaryInstruction(op) |
| { |
| setResultType(type); |
| setMovable(); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(AsmJSNeg) |
| static MAsmJSNeg* NewAsmJS(TempAllocator& alloc, MDefinition* op, MIRType type) { |
| return new(alloc) MAsmJSNeg(op, type); |
| } |
| }; |
| |
| class MAsmJSHeapAccess |
| { |
| int32_t offset_; |
| Scalar::Type accessType_ : 8; |
| bool needsBoundsCheck_; |
| unsigned numSimdElems_; |
| MemoryBarrierBits barrierBefore_; |
| MemoryBarrierBits barrierAfter_; |
| |
| public: |
| MAsmJSHeapAccess(Scalar::Type accessType, bool needsBoundsCheck, unsigned numSimdElems = 0, |
| MemoryBarrierBits barrierBefore = MembarNobits, |
| MemoryBarrierBits barrierAfter = MembarNobits) |
| : offset_(0), |
| accessType_(accessType), |
| needsBoundsCheck_(needsBoundsCheck), |
| numSimdElems_(numSimdElems), |
| barrierBefore_(barrierBefore), |
| barrierAfter_(barrierAfter) |
| { |
| MOZ_ASSERT(numSimdElems <= ScalarTypeToLength(accessType)); |
| } |
| |
| int32_t offset() const { return offset_; } |
| int32_t endOffset() const { return offset() + byteSize(); } |
| Scalar::Type accessType() const { return accessType_; } |
| unsigned byteSize() const { |
| return Scalar::isSimdType(accessType()) |
| ? Scalar::scalarByteSize(accessType()) * numSimdElems() |
| : TypedArrayElemSize(accessType()); |
| } |
| bool needsBoundsCheck() const { return needsBoundsCheck_; } |
| void removeBoundsCheck() { needsBoundsCheck_ = false; } |
| unsigned numSimdElems() const { MOZ_ASSERT(Scalar::isSimdType(accessType_)); return numSimdElems_; } |
| void setOffset(int32_t o) { |
| MOZ_ASSERT(o >= 0); |
| offset_ = o; |
| } |
| MemoryBarrierBits barrierBefore() const { return barrierBefore_; } |
| MemoryBarrierBits barrierAfter() const { return barrierAfter_; } |
| bool isAtomicAccess() const { return (barrierBefore_|barrierAfter_) != MembarNobits; } |
| }; |
| |
| class MAsmJSLoadHeap |
| : public MUnaryInstruction, |
| public MAsmJSHeapAccess, |
| public NoTypePolicy::Data |
| { |
| MAsmJSLoadHeap(Scalar::Type accessType, MDefinition* ptr, bool needsBoundsCheck, |
| unsigned numSimdElems, MemoryBarrierBits before, MemoryBarrierBits after) |
| : MUnaryInstruction(ptr), |
| MAsmJSHeapAccess(accessType, needsBoundsCheck, numSimdElems, before, after) |
| { |
| if (before|after) |
| setGuard(); // Not removable |
| else |
| setMovable(); |
| |
| switch (accessType) { |
| case Scalar::Int8: |
| case Scalar::Uint8: |
| case Scalar::Int16: |
| case Scalar::Uint16: |
| case Scalar::Int32: |
| case Scalar::Uint32: |
| setResultType(MIRType_Int32); |
| break; |
| case Scalar::Float32: |
| setResultType(MIRType_Float32); |
| break; |
| case Scalar::Float64: |
| setResultType(MIRType_Double); |
| break; |
| case Scalar::Float32x4: |
| setResultType(MIRType_Float32x4); |
| break; |
| case Scalar::Int32x4: |
| setResultType(MIRType_Int32x4); |
| break; |
| case Scalar::Uint8Clamped: |
| case Scalar::MaxTypedArrayViewType: |
| MOZ_CRASH("unexpected load heap in asm.js"); |
| } |
| } |
| |
| public: |
| INSTRUCTION_HEADER(AsmJSLoadHeap) |
| |
| static MAsmJSLoadHeap* New(TempAllocator& alloc, Scalar::Type accessType, |
| MDefinition* ptr, bool needsBoundsCheck, |
| unsigned numSimdElems = 0, |
| MemoryBarrierBits barrierBefore = MembarNobits, |
| MemoryBarrierBits barrierAfter = MembarNobits) |
| { |
| return new(alloc) MAsmJSLoadHeap(accessType, ptr, needsBoundsCheck, |
| numSimdElems, barrierBefore, barrierAfter); |
| } |
| |
| MDefinition* ptr() const { return getOperand(0); } |
| void replacePtr(MDefinition* newPtr) { replaceOperand(0, newPtr); } |
| |
| bool congruentTo(const MDefinition* ins) const override; |
| AliasSet getAliasSet() const override { |
| // When a barrier is needed make the instruction effectful by |
| // giving it a "store" effect. |
| if (isAtomicAccess()) |
| return AliasSet::Store(AliasSet::AsmJSHeap); |
| return AliasSet::Load(AliasSet::AsmJSHeap); |
| } |
| bool mightAlias(const MDefinition* def) const override; |
| }; |
| |
| class MAsmJSStoreHeap |
| : public MBinaryInstruction, |
| public MAsmJSHeapAccess, |
| public NoTypePolicy::Data |
| { |
| MAsmJSStoreHeap(Scalar::Type accessType, MDefinition* ptr, MDefinition* v, bool needsBoundsCheck, |
| unsigned numSimdElems, MemoryBarrierBits before, MemoryBarrierBits after) |
| : MBinaryInstruction(ptr, v), |
| MAsmJSHeapAccess(accessType, needsBoundsCheck, numSimdElems, before, after) |
| { |
| if (before|after) |
| setGuard(); // Not removable |
| } |
| |
| public: |
| INSTRUCTION_HEADER(AsmJSStoreHeap) |
| |
| static MAsmJSStoreHeap* New(TempAllocator& alloc, Scalar::Type accessType, |
| MDefinition* ptr, MDefinition* v, bool needsBoundsCheck, |
| unsigned numSimdElems = 0, |
| MemoryBarrierBits barrierBefore = MembarNobits, |
| MemoryBarrierBits barrierAfter = MembarNobits) |
| { |
| return new(alloc) MAsmJSStoreHeap(accessType, ptr, v, needsBoundsCheck, |
| numSimdElems, barrierBefore, barrierAfter); |
| } |
| |
| MDefinition* ptr() const { return getOperand(0); } |
| void replacePtr(MDefinition* newPtr) { replaceOperand(0, newPtr); } |
| MDefinition* value() const { return getOperand(1); } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::Store(AliasSet::AsmJSHeap); |
| } |
| }; |
| |
| class MAsmJSCompareExchangeHeap |
| : public MTernaryInstruction, |
| public MAsmJSHeapAccess, |
| public NoTypePolicy::Data |
| { |
| MAsmJSCompareExchangeHeap(Scalar::Type accessType, MDefinition* ptr, MDefinition* oldv, |
| MDefinition* newv, bool needsBoundsCheck) |
| : MTernaryInstruction(ptr, oldv, newv), |
| MAsmJSHeapAccess(accessType, needsBoundsCheck) |
| { |
| setGuard(); // Not removable |
| setResultType(MIRType_Int32); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(AsmJSCompareExchangeHeap) |
| |
| static MAsmJSCompareExchangeHeap* New(TempAllocator& alloc, Scalar::Type accessType, |
| MDefinition* ptr, MDefinition* oldv, |
| MDefinition* newv, bool needsBoundsCheck) |
| { |
| return new(alloc) MAsmJSCompareExchangeHeap(accessType, ptr, oldv, newv, needsBoundsCheck); |
| } |
| |
| MDefinition* ptr() const { return getOperand(0); } |
| MDefinition* oldValue() const { return getOperand(1); } |
| MDefinition* newValue() const { return getOperand(2); } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::Store(AliasSet::AsmJSHeap); |
| } |
| }; |
| |
| class MAsmJSAtomicExchangeHeap |
| : public MBinaryInstruction, |
| public MAsmJSHeapAccess, |
| public NoTypePolicy::Data |
| { |
| MAsmJSAtomicExchangeHeap(Scalar::Type accessType, MDefinition* ptr, MDefinition* value, |
| bool needsBoundsCheck) |
| : MBinaryInstruction(ptr, value), |
| MAsmJSHeapAccess(accessType, needsBoundsCheck) |
| { |
| setGuard(); // Not removable |
| setResultType(MIRType_Int32); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(AsmJSAtomicExchangeHeap) |
| |
| static MAsmJSAtomicExchangeHeap* New(TempAllocator& alloc, Scalar::Type accessType, |
| MDefinition* ptr, MDefinition* value, |
| bool needsBoundsCheck) |
| { |
| return new(alloc) MAsmJSAtomicExchangeHeap(accessType, ptr, value, needsBoundsCheck); |
| } |
| |
| MDefinition* ptr() const { return getOperand(0); } |
| MDefinition* value() const { return getOperand(1); } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::Store(AliasSet::AsmJSHeap); |
| } |
| }; |
| |
| class MAsmJSAtomicBinopHeap |
| : public MBinaryInstruction, |
| public MAsmJSHeapAccess, |
| public NoTypePolicy::Data |
| { |
| AtomicOp op_; |
| |
| MAsmJSAtomicBinopHeap(AtomicOp op, Scalar::Type accessType, MDefinition* ptr, MDefinition* v, |
| bool needsBoundsCheck) |
| : MBinaryInstruction(ptr, v), |
| MAsmJSHeapAccess(accessType, needsBoundsCheck), |
| op_(op) |
| { |
| setGuard(); // Not removable |
| setResultType(MIRType_Int32); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(AsmJSAtomicBinopHeap) |
| |
| static MAsmJSAtomicBinopHeap* New(TempAllocator& alloc, AtomicOp op, Scalar::Type accessType, |
| MDefinition* ptr, MDefinition* v, bool needsBoundsCheck) |
| { |
| return new(alloc) MAsmJSAtomicBinopHeap(op, accessType, ptr, v, needsBoundsCheck); |
| } |
| |
| AtomicOp operation() const { return op_; } |
| MDefinition* ptr() const { return getOperand(0); } |
| MDefinition* value() const { return getOperand(1); } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::Store(AliasSet::AsmJSHeap); |
| } |
| }; |
| |
| class MAsmJSLoadGlobalVar : public MNullaryInstruction |
| { |
| MAsmJSLoadGlobalVar(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(AsmJSLoadGlobalVar) |
| |
| static MAsmJSLoadGlobalVar* New(TempAllocator& alloc, MIRType type, unsigned globalDataOffset, |
| bool isConstant) |
| { |
| return new(alloc) MAsmJSLoadGlobalVar(type, globalDataOffset, isConstant); |
| } |
| |
| 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::AsmJSGlobalVar); |
| } |
| |
| bool mightAlias(const MDefinition* def) const override; |
| }; |
| |
| class MAsmJSStoreGlobalVar |
| : public MUnaryInstruction, |
| public NoTypePolicy::Data |
| { |
| MAsmJSStoreGlobalVar(unsigned globalDataOffset, MDefinition* v) |
| : MUnaryInstruction(v), globalDataOffset_(globalDataOffset) |
| {} |
| |
| unsigned globalDataOffset_; |
| |
| public: |
| INSTRUCTION_HEADER(AsmJSStoreGlobalVar) |
| |
| static MAsmJSStoreGlobalVar* New(TempAllocator& alloc, unsigned globalDataOffset, MDefinition* v) { |
| return new(alloc) MAsmJSStoreGlobalVar(globalDataOffset, v); |
| } |
| |
| unsigned globalDataOffset() const { return globalDataOffset_; } |
| MDefinition* value() const { return getOperand(0); } |
| |
| AliasSet getAliasSet() const override { |
| return AliasSet::Store(AliasSet::AsmJSGlobalVar); |
| } |
| }; |
| |
| class MAsmJSLoadFuncPtr |
| : public MUnaryInstruction, |
| public NoTypePolicy::Data |
| { |
| MAsmJSLoadFuncPtr(unsigned globalDataOffset, MDefinition* index) |
| : MUnaryInstruction(index), globalDataOffset_(globalDataOffset) |
| { |
| setResultType(MIRType_Pointer); |
| } |
| |
| unsigned globalDataOffset_; |
| |
| public: |
| INSTRUCTION_HEADER(AsmJSLoadFuncPtr) |
| |
| static MAsmJSLoadFuncPtr* New(TempAllocator& alloc, unsigned globalDataOffset, |
| MDefinition* index) |
| { |
| return new(alloc) MAsmJSLoadFuncPtr(globalDataOffset, index); |
| } |
| |
| unsigned globalDataOffset() const { return globalDataOffset_; } |
| MDefinition* index() const { return getOperand(0); } |
| |
| HashNumber valueHash() const override; |
| bool congruentTo(const MDefinition* ins) const override; |
| }; |
| |
| class MAsmJSLoadFFIFunc : public MNullaryInstruction |
| { |
| explicit MAsmJSLoadFFIFunc(unsigned globalDataOffset) |
| : globalDataOffset_(globalDataOffset) |
| { |
| setResultType(MIRType_Pointer); |
| } |
| |
| unsigned globalDataOffset_; |
| |
| public: |
| INSTRUCTION_HEADER(AsmJSLoadFFIFunc) |
| |
| static MAsmJSLoadFFIFunc* New(TempAllocator& alloc, unsigned globalDataOffset) |
| { |
| return new(alloc) MAsmJSLoadFFIFunc(globalDataOffset); |
| } |
| |
| unsigned globalDataOffset() const { return globalDataOffset_; } |
| |
| HashNumber valueHash() const override; |
| bool congruentTo(const MDefinition* ins) const override; |
| }; |
| |
| class MAsmJSParameter : public MNullaryInstruction |
| { |
| ABIArg abi_; |
| |
| MAsmJSParameter(ABIArg abi, MIRType mirType) |
| : abi_(abi) |
| { |
| setResultType(mirType); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(AsmJSParameter) |
| |
| static MAsmJSParameter* New(TempAllocator& alloc, ABIArg abi, MIRType mirType) { |
| return new(alloc) MAsmJSParameter(abi, mirType); |
| } |
| |
| ABIArg abi() const { return abi_; } |
| }; |
| |
| class MAsmJSReturn |
| : public MAryControlInstruction<1, 0>, |
| public NoTypePolicy::Data |
| { |
| explicit MAsmJSReturn(MDefinition* ins) { |
| initOperand(0, ins); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(AsmJSReturn) |
| static MAsmJSReturn* New(TempAllocator& alloc, MDefinition* ins) { |
| return new(alloc) MAsmJSReturn(ins); |
| } |
| }; |
| |
| class MAsmJSVoidReturn |
| : public MAryControlInstruction<0, 0>, |
| public NoTypePolicy::Data |
| { |
| public: |
| INSTRUCTION_HEADER(AsmJSVoidReturn) |
| static MAsmJSVoidReturn* New(TempAllocator& alloc) { |
| return new(alloc) MAsmJSVoidReturn(); |
| } |
| }; |
| |
| class MAsmJSPassStackArg |
| : public MUnaryInstruction, |
| public NoTypePolicy::Data |
| { |
| MAsmJSPassStackArg(uint32_t spOffset, MDefinition* ins) |
| : MUnaryInstruction(ins), |
| spOffset_(spOffset) |
| {} |
| |
| uint32_t spOffset_; |
| |
| public: |
| INSTRUCTION_HEADER(AsmJSPassStackArg) |
| static MAsmJSPassStackArg* New(TempAllocator& alloc, uint32_t spOffset, MDefinition* ins) { |
| return new(alloc) MAsmJSPassStackArg(spOffset, ins); |
| } |
| uint32_t spOffset() const { |
| return spOffset_; |
| } |
| void incrementOffset(uint32_t inc) { |
| spOffset_ += inc; |
| } |
| MDefinition* arg() const { |
| return getOperand(0); |
| } |
| }; |
| |
| class MAsmJSCall final |
| : public MVariadicInstruction, |
| public NoTypePolicy::Data |
| { |
| public: |
| class Callee { |
| public: |
| enum Which { Internal, Dynamic, Builtin }; |
| private: |
| Which which_; |
| union { |
| AsmJSInternalCallee internal_; |
| MDefinition* dynamic_; |
| wasm::Builtin builtin_; |
| } u; |
| public: |
| Callee() {} |
| explicit Callee(AsmJSInternalCallee callee) : which_(Internal) { u.internal_ = callee; } |
| explicit Callee(MDefinition* callee) : which_(Dynamic) { u.dynamic_ = callee; } |
| explicit Callee(wasm::Builtin callee) : which_(Builtin) { u.builtin_ = callee; } |
| Which which() const { return which_; } |
| AsmJSInternalCallee internal() const { MOZ_ASSERT(which_ == Internal); return u.internal_; } |
| MDefinition* dynamic() const { MOZ_ASSERT(which_ == Dynamic); return u.dynamic_; } |
| wasm::Builtin builtin() const { MOZ_ASSERT(which_ == Builtin); return u.builtin_; } |
| }; |
| |
| private: |
| wasm::CallSiteDesc desc_; |
| Callee callee_; |
| FixedList<AnyRegister> argRegs_; |
| size_t spIncrement_; |
| |
| MAsmJSCall(const wasm::CallSiteDesc& desc, Callee callee, size_t spIncrement) |
| : desc_(desc), callee_(callee), spIncrement_(spIncrement) |
| { } |
| |
| public: |
| INSTRUCTION_HEADER(AsmJSCall) |
| |
| struct Arg { |
| AnyRegister reg; |
| MDefinition* def; |
| Arg(AnyRegister reg, MDefinition* def) : reg(reg), def(def) {} |
| }; |
| typedef Vector<Arg, 8, SystemAllocPolicy> Args; |
| |
| static MAsmJSCall* New(TempAllocator& alloc, const wasm::CallSiteDesc& desc, Callee callee, |
| const Args& args, MIRType resultType, size_t spIncrement); |
| |
| 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_; |
| } |
| Callee callee() const { |
| return callee_; |
| } |
| size_t dynamicCalleeOperandIndex() const { |
| MOZ_ASSERT(callee_.which() == Callee::Dynamic); |
| MOZ_ASSERT(numArgs() == numOperands() - 1); |
| return numArgs(); |
| } |
| size_t spIncrement() const { |
| return spIncrement_; |
| } |
| |
| bool possiblyCalls() const override { |
| return true; |
| } |
| }; |
| |
| class MUnknownValue : public MNullaryInstruction |
| { |
| protected: |
| MUnknownValue() { |
| setResultType(MIRType_Value); |
| } |
| |
| public: |
| INSTRUCTION_HEADER(UnknownValue) |
| |
| static MUnknownValue* New(TempAllocator& alloc) { |
| return new(alloc) MUnknownValue(); |
| } |
| }; |
| |
| #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; |
| } |
| |
| // 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 ElementAccessIsAnyTypedArray(CompilerConstraintList* constraints, |
| MDefinition* obj, MDefinition* id, |
| Scalar::Type* arrayType); |
| bool ElementAccessIsPacked(CompilerConstraintList* constraints, MDefinition* obj); |
| bool ElementAccessMightBeCopyOnWrite(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); |
| 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); |
| |
| } // namespace jit |
| } // namespace js |
| |
| #endif /* jit_MIR_h */ |