| /* -*- 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_; |
| } |
| |
| |