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