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