/* -*- 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 { // 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(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::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 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 bool is() const { return op() == MIRType::classOpcode; } template MIRType* to() { MOZ_ASSERT(this->is()); return static_cast(this); } template const MIRType* to() const { MOZ_ASSERT(this->is()); return static_cast(this); } # define OPCODE_CASTS(opcode) \ bool is##opcode() const { \ return this->is(); \ } \ M##opcode* to##opcode() { \ return this->to(); \ } \ const M##opcode* to##opcode() const { \ return this->to(); \ } 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 MDefinitionVector; typedef Vector MInstructionVector; class MRootList : public TempObject { private: Vector roots_; MRootList(const MRootList&) = delete; void operator=(const MRootList&) = delete; public: explicit MRootList(TempAllocator& alloc); void trace(JSTracer* trc); MOZ_MUST_USE bool append(JSScript* script) { if (script) return roots_.append(script); return true; } }; // An instruction is an SSA name that is inserted into a basic block's IR // stream. class MInstruction : public MDefinition, public InlineListNode { 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 class MAryInstruction : public MInstruction { mozilla::Array 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& 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(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 MVariadicT : public T { FixedList 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 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<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 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<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<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, 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 successors_; Vector cases_; // Contains the blocks/cases that still need to get build Vector 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 class MAryControlInstruction : public MControlInstruction { mozilla::Array operands_; mozilla::Array 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 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(ptr_); } T operator->() const { return static_cast(ptr_); } private: CompilerGCPointer() = delete; CompilerGCPointer(const CompilerGCPointer&) = delete; CompilerGCPointer& operator=(const CompilerGCPointer&) = delete; }; typedef CompilerGCPointer CompilerObject; typedef CompilerGCPointer CompilerNativeObject; typedef CompilerGCPointer CompilerFunction; typedef CompilerGCPointer CompilerScript; typedef CompilerGCPointer CompilerPropertyName; typedef CompilerGCPointer CompilerShape; typedef CompilerGCPointer 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 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 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 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<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 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, 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, 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<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, 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, 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, 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, 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<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, 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, 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_(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<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<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, 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, 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<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<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, 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<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, public NoTypePolicy::Data { js::Vector 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<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 MBinaryInstruction, public SingleObjectPolicy::Data { private: MDefFun(MDefinition* fun, MDefinition* scopeChain) : MBinaryInstruction(fun, scopeChain) {} public: INSTRUCTION_HEADER(DefFun) static MDefFun* New(TempAllocator& alloc, MDefinition* fun, MDefinition* scopeChain) { return new(alloc) MDefFun(fun, scopeChain); } MDefinition* fun() const { return getOperand(0); } MDefinition* scopeChain() const { return getOperand(1); } bool possiblyCalls() const override { return true; } }; class MRegExp : public MNullaryInstruction { CompilerGCPointer source_; MRegExp(CompilerConstraintList* constraints, RegExpObject* source) : source_(source) { setResultType(MIRType_Object); setResultTypeSet(MakeSingletonTypeSet(constraints, source)); } public: INSTRUCTION_HEADER(RegExp) static MRegExp* New(TempAllocator& alloc, CompilerConstraintList* constraints, RegExpObject* source) { return new(alloc) MRegExp(constraints, source); } RegExpObject* source() const { return source_; } AliasSet getAliasSet() const override { return AliasSet::None(); } bool possiblyCalls() const override { return true; } }; class MRegExpExec : public MBinaryInstruction, public MixPolicy, 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, 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 MStrReplace : public MTernaryInstruction, public Mix3Policy, 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: bool isFlatReplacement_; MStringReplace(MDefinition* string, MDefinition* pattern, MDefinition* replacement) : MStrReplace< StringPolicy<1> >(string, pattern, replacement), isFlatReplacement_(false) { } public: INSTRUCTION_HEADER(StringReplace) static MStringReplace* New(TempAllocator& alloc, MDefinition* string, MDefinition* pattern, MDefinition* replacement) { return new(alloc) MStringReplace(string, pattern, replacement); } void setFlatReplacement() { MOZ_ASSERT(!isFlatReplacement_); isFlatReplacement_ = true; } bool isFlatReplacement() const { return isFlatReplacement_; } bool congruentTo(const MDefinition* ins) const override { if (!ins->isStringReplace()) return false; if (isFlatReplacement_ != ins->toStringReplace()->isFlatReplacement()) return false; return congruentIfOperandsEqual(ins); } AliasSet getAliasSet() const override { return AliasSet::None(); } bool writeRecoverData(CompactBufferWriter& writer) const override; bool canRecoverOnBailout() const override { if (isFlatReplacement_) { MOZ_ASSERT(!pattern()->isRegExp()); return true; } if (pattern()->isRegExp()) return !pattern()->toRegExp()->source()->global(); return false; } }; class MSubstr : public MTernaryInstruction, public Mix3Policy, 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()) { 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, 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 value_; protected: explicit MConstantElements(SharedMem v) : value_(v) { setResultType(MIRType_Elements); setMovable(); } public: INSTRUCTION_HEADER(ConstantElements) static MConstantElements* New(TempAllocator& alloc, SharedMem v) { return new(alloc) MConstantElements(v); } SharedMem 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 >::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 >::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_; } 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 >::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 >::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<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, 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_; } bool possiblyCalls() const override { return true; } }; class MArrayJoin : public MBinaryInstruction, public MixPolicy, 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_; public: enum TargetKind { ScalarTarget = 0, TypedArrayTarget }; private: TargetKind target_; 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(); target_ = ScalarTarget; 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); } TargetKind target() { return target_; } void setTarget(TargetKind t) { target_ = t; } 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 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 }; enum TargetKind { ScalarTarget = 0, TypedArrayTarget }; 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 TargetKind target_; 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(); target_ = ScalarTarget; 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_; } TargetKind target() { return target_; } void setTarget(TargetKind t) { target_ = t; } 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 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 >::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 ObjectVector; typedef Vector 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 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 { public: CacheLocationList() : pc(nullptr), script(nullptr) { } jsbytecode* pc; JSScript* script; }; class MGetPropertyCache : public MBinaryInstruction, public MixPolicy, 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 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 >::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 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 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_; } }; class MCallBindVar : public MUnaryInstruction, public SingleObjectPolicy::Data { explicit MCallBindVar(MDefinition* scopeChain) : MUnaryInstruction(scopeChain) { setResultType(MIRType_Object); setMovable(); } public: INSTRUCTION_HEADER(CallBindVar) static MCallBindVar* New(TempAllocator& alloc, MDefinition* scopeChain) { return new(alloc) MCallBindVar(scopeChain); } MDefinition* scopeChain() const { return getOperand(0); } bool congruentTo(const MDefinition* ins) const override { if (!ins->isCallBindVar()) return false; return congruentIfOperandsEqual(ins); } AliasSet getAliasSet() const override { return AliasSet::None(); } }; // Guard on an object's shape. class MGuardShape : public MUnaryInstruction, public SingleObjectPolicy::Data { CompilerShape shape_; BailoutKind bailoutKind_; MGuardShape(MDefinition* obj, Shape* shape, BailoutKind bailoutKind) : MUnaryInstruction(obj), shape_(shape), bailoutKind_(bailoutKind) { setGuard(); setMovable(); setResultType(MIRType_Object); setResultTypeSet(obj->resultTypeSet()); // Disallow guarding on unboxed object shapes. The group is better to // guard on, and guarding on the shape can interact badly with // MConvertUnboxedObjectToNative. MOZ_ASSERT(shape->getObjectClass() != &UnboxedPlainObject::class_); } public: INSTRUCTION_HEADER(GuardShape) 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 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, 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, 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, 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, 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, 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, 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 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 IsFloatingPointType(type()); } bool canProduceFloat32() const override; bool canConsumeFloat32(MUse* operand) const override; void trySpecializeFloat32(TempAllocator& alloc) override; }; // Given a value, guard that the value is in a particular TypeSet, then returns // that value. class MTypeBarrier : public MUnaryInstruction, public TypeBarrierPolicy::Data { BarrierKind barrierKind_; MTypeBarrier(MDefinition* def, TemporaryTypeSet* types, BarrierKind kind) : 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 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 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 { MDefinition* operand; explicit MStoreToRecover(MDefinition* operand) : operand(operand) { } }; typedef InlineSpaghettiStack 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 #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 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, 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, 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 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 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 TypeCanHaveExtraIndexedProperties(IonBuilder* builder, TemporaryTypeSet* types); inline MIRType MIRTypeForTypedArrayRead(Scalar::Type arrayType, bool observedDouble) { switch (arrayType) { case Scalar::Int8: case Scalar::Uint8: case Scalar::Uint8Clamped: case Scalar::Int16: case Scalar::Uint16: case Scalar::Int32: return MIRType_Int32; case Scalar::Uint32: return observedDouble ? MIRType_Double : MIRType_Int32; case Scalar::Float32: return MIRType_Float32; case Scalar::Float64: return MIRType_Double; default: break; } MOZ_CRASH("Unknown typed array type"); } } // namespace jit } // namespace js #endif /* jit_MIR_h */