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