diff options
Diffstat (limited to 'compiler/optimizing/nodes.h')
| -rw-r--r-- | compiler/optimizing/nodes.h | 373 |
1 files changed, 286 insertions, 87 deletions
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 30d869d026..de448cc483 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -17,23 +17,28 @@ #ifndef ART_COMPILER_OPTIMIZING_NODES_H_ #define ART_COMPILER_OPTIMIZING_NODES_H_ +#include "base/arena_object.h" #include "entrypoints/quick/quick_entrypoints_enum.h" +#include "handle.h" +#include "handle_scope.h" #include "invoke_type.h" #include "locations.h" +#include "mirror/class.h" #include "offsets.h" #include "primitive.h" -#include "utils/arena_object.h" #include "utils/arena_bit_vector.h" #include "utils/growable_array.h" namespace art { +class GraphChecker; class HBasicBlock; class HEnvironment; class HInstruction; class HIntConstant; class HInvoke; class HGraphVisitor; +class HNullConstant; class HPhi; class HSuspendCheck; class LiveInterval; @@ -194,6 +199,8 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { return reverse_post_order_; } + HNullConstant* GetNullConstant(); + private: HBasicBlock* FindCommonDominator(HBasicBlock* first, HBasicBlock* second) const; void VisitBlockForDominatorTree(HBasicBlock* block, @@ -205,7 +212,6 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { ArenaBitVector* visiting); void RemoveInstructionsAsUsersFromDeadBlocks(const ArenaBitVector& visited) const; void RemoveDeadBlocks(const ArenaBitVector& visited) const; - void RemoveBlock(HBasicBlock* block) const; ArenaAllocator* const arena_; @@ -233,6 +239,9 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { // The current id to assign to a newly added instruction. See HInstruction.id_. int32_t current_instruction_id_; + // Cached null constant that might be created when building SSA form. + HNullConstant* cached_null_constant_; + ART_FRIEND_TEST(GraphTest, IfSuccessorSimpleJoinBlock1); DISALLOW_COPY_AND_ASSIGN(HGraph); }; @@ -481,14 +490,17 @@ class HBasicBlock : public ArenaObject<kArenaAllocMisc> { void ReplaceWith(HBasicBlock* other); void AddInstruction(HInstruction* instruction); - void RemoveInstruction(HInstruction* instruction); void InsertInstructionBefore(HInstruction* instruction, HInstruction* cursor); // Replace instruction `initial` with `replacement` within this block. void ReplaceAndRemoveInstructionWith(HInstruction* initial, HInstruction* replacement); void AddPhi(HPhi* phi); void InsertPhiAfter(HPhi* instruction, HPhi* cursor); - void RemovePhi(HPhi* phi); + // RemoveInstruction and RemovePhi delete a given instruction from the respective + // instruction list. With 'ensure_safety' set to true, it verifies that the + // instruction is not in use and removes it from the use lists of its inputs. + void RemoveInstruction(HInstruction* instruction, bool ensure_safety = true); + void RemovePhi(HPhi* phi, bool ensure_safety = true); bool IsLoopHeader() const { return (loop_information_ != nullptr) && (loop_information_->GetHeader() == this); @@ -574,6 +586,7 @@ class HBasicBlock : public ArenaObject<kArenaAllocMisc> { M(ArrayLength, Instruction) \ M(ArraySet, Instruction) \ M(BoundsCheck, Instruction) \ + M(BoundType, Instruction) \ M(CheckCast, Instruction) \ M(ClinitCheck, Instruction) \ M(Compare, BinaryOperation) \ @@ -610,6 +623,7 @@ class HBasicBlock : public ArenaObject<kArenaAllocMisc> { M(NewInstance, Instruction) \ M(Not, UnaryOperation) \ M(NotEqual, Condition) \ + M(NullConstant, Instruction) \ M(NullCheck, Instruction) \ M(Or, BinaryOperation) \ M(ParallelMove, Instruction) \ @@ -704,6 +718,9 @@ class HUseList : public ValueObject { } void Remove(HUseListNode<T>* node) { + DCHECK(node != nullptr); + DCHECK(Contains(node)); + if (node->prev_ != nullptr) { node->prev_->next_ = node->next_; } @@ -715,6 +732,18 @@ class HUseList : public ValueObject { } } + bool Contains(const HUseListNode<T>* node) const { + if (node == nullptr) { + return false; + } + for (HUseListNode<T>* current = first_; current != nullptr; current = current->GetNext()) { + if (current == node) { + return true; + } + } + return false; + } + bool IsEmpty() const { return first_ == nullptr; } @@ -750,6 +779,33 @@ class HUseIterator : public ValueObject { friend class HValue; }; +// This class is used by HEnvironment and HInstruction classes to record the +// instructions they use and pointers to the corresponding HUseListNodes kept +// by the used instructions. +template <typename T> +class HUserRecord : public ValueObject { + public: + HUserRecord() : instruction_(nullptr), use_node_(nullptr) {} + explicit HUserRecord(HInstruction* instruction) : instruction_(instruction), use_node_(nullptr) {} + + HUserRecord(const HUserRecord<T>& old_record, HUseListNode<T>* use_node) + : instruction_(old_record.instruction_), use_node_(use_node) { + DCHECK(instruction_ != nullptr); + DCHECK(use_node_ != nullptr); + DCHECK(old_record.use_node_ == nullptr); + } + + HInstruction* GetInstruction() const { return instruction_; } + HUseListNode<T>* GetUseNode() const { return use_node_; } + + private: + // Instruction used by the user. + HInstruction* instruction_; + + // Corresponding entry in the use list kept by 'instruction_'. + HUseListNode<T>* use_node_; +}; + // Represents the side effects an instruction may have. class SideEffects : public ValueObject { public: @@ -820,50 +876,118 @@ class HEnvironment : public ArenaObject<kArenaAllocMisc> { : vregs_(arena, number_of_vregs) { vregs_.SetSize(number_of_vregs); for (size_t i = 0; i < number_of_vregs; i++) { - vregs_.Put(i, VRegInfo(nullptr, nullptr)); + vregs_.Put(i, HUserRecord<HEnvironment*>()); } } void CopyFrom(HEnvironment* env); void SetRawEnvAt(size_t index, HInstruction* instruction) { - vregs_.Put(index, VRegInfo(instruction, nullptr)); + vregs_.Put(index, HUserRecord<HEnvironment*>(instruction)); } + HInstruction* GetInstructionAt(size_t index) const { + return vregs_.Get(index).GetInstruction(); + } + + void RemoveAsUserOfInput(size_t index) const; + + size_t Size() const { return vregs_.Size(); } + + private: // Record instructions' use entries of this environment for constant-time removal. + // It should only be called by HInstruction when a new environment use is added. void RecordEnvUse(HUseListNode<HEnvironment*>* env_use) { DCHECK(env_use->GetUser() == this); size_t index = env_use->GetIndex(); - VRegInfo info = vregs_.Get(index); - DCHECK(info.vreg_ != nullptr); - DCHECK(info.node_ == nullptr); - vregs_.Put(index, VRegInfo(info.vreg_, env_use)); + vregs_.Put(index, HUserRecord<HEnvironment*>(vregs_.Get(index), env_use)); } - HInstruction* GetInstructionAt(size_t index) const { - return vregs_.Get(index).vreg_; + GrowableArray<HUserRecord<HEnvironment*> > vregs_; + + friend HInstruction; + + DISALLOW_COPY_AND_ASSIGN(HEnvironment); +}; + +class ReferenceTypeInfo : ValueObject { + public: + typedef Handle<mirror::Class> TypeHandle; + + static ReferenceTypeInfo Create(TypeHandle type_handle, bool is_exact) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + if (type_handle->IsObjectClass()) { + // Override the type handle to be consistent with the case when we get to + // Top but don't have the Object class available. It avoids having to guess + // what value the type_handle has when it's Top. + return ReferenceTypeInfo(TypeHandle(), is_exact, true); + } else { + return ReferenceTypeInfo(type_handle, is_exact, false); + } } - HUseListNode<HEnvironment*>* GetInstructionEnvUseAt(size_t index) const { - return vregs_.Get(index).node_; + static ReferenceTypeInfo CreateTop(bool is_exact) { + return ReferenceTypeInfo(TypeHandle(), is_exact, true); } - size_t Size() const { return vregs_.Size(); } + bool IsExact() const { return is_exact_; } + bool IsTop() const { return is_top_; } - private: - struct VRegInfo { - HInstruction* vreg_; - HUseListNode<HEnvironment*>* node_; + Handle<mirror::Class> GetTypeHandle() const { return type_handle_; } - VRegInfo(HInstruction* instruction, HUseListNode<HEnvironment*>* env_use) - : vreg_(instruction), node_(env_use) {} - }; + bool IsSupertypeOf(ReferenceTypeInfo rti) const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + if (IsTop()) { + // Top (equivalent for java.lang.Object) is supertype of anything. + return true; + } + if (rti.IsTop()) { + // If we get here `this` is not Top() so it can't be a supertype. + return false; + } + return GetTypeHandle()->IsAssignableFrom(rti.GetTypeHandle().Get()); + } - GrowableArray<VRegInfo> vregs_; + // Returns true if the type information provide the same amount of details. + // Note that it does not mean that the instructions have the same actual type + // (e.g. tops are equal but they can be the result of a merge). + bool IsEqual(ReferenceTypeInfo rti) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + if (IsExact() != rti.IsExact()) { + return false; + } + if (IsTop() && rti.IsTop()) { + // `Top` means java.lang.Object, so the types are equivalent. + return true; + } + if (IsTop() || rti.IsTop()) { + // If only one is top or object than they are not equivalent. + // NB: We need this extra check because the type_handle of `Top` is invalid + // and we cannot inspect its reference. + return false; + } - DISALLOW_COPY_AND_ASSIGN(HEnvironment); + // Finally check the types. + return GetTypeHandle().Get() == rti.GetTypeHandle().Get(); + } + + private: + ReferenceTypeInfo() : ReferenceTypeInfo(TypeHandle(), false, true) {} + ReferenceTypeInfo(TypeHandle type_handle, bool is_exact, bool is_top) + : type_handle_(type_handle), is_exact_(is_exact), is_top_(is_top) {} + + // The class of the object. + TypeHandle type_handle_; + // Whether or not the type is exact or a superclass of the actual type. + // Whether or not we have any information about this type. + bool is_exact_; + // A true value here means that the object type should be java.lang.Object. + // We don't have access to the corresponding mirror object every time so this + // flag acts as a substitute. When true, the TypeHandle refers to a null + // pointer and should not be used. + bool is_top_; }; +std::ostream& operator<<(std::ostream& os, const ReferenceTypeInfo& rhs); + class HInstruction : public ArenaObject<kArenaAllocMisc> { public: explicit HInstruction(SideEffects side_effects) @@ -876,7 +1000,8 @@ class HInstruction : public ArenaObject<kArenaAllocMisc> { locations_(nullptr), live_interval_(nullptr), lifetime_position_(kNoLifetime), - side_effects_(side_effects) {} + side_effects_(side_effects), + reference_type_info_(ReferenceTypeInfo::CreateTop(/* is_exact */ false)) {} virtual ~HInstruction() {} @@ -899,13 +1024,15 @@ class HInstruction : public ArenaObject<kArenaAllocMisc> { bool IsLoopHeaderPhi() { return IsPhi() && block_->IsLoopHeader(); } virtual size_t InputCount() const = 0; - virtual HInstruction* InputAt(size_t i) const = 0; + HInstruction* InputAt(size_t i) const { return InputRecordAt(i).GetInstruction(); } virtual void Accept(HGraphVisitor* visitor) = 0; virtual const char* DebugName() const = 0; virtual Primitive::Type GetType() const { return Primitive::kPrimVoid; } - virtual void SetRawInputAt(size_t index, HInstruction* input) = 0; + void SetRawInputAt(size_t index, HInstruction* input) { + SetRawInputRecordAt(index, HUserRecord<HInstruction*>(input)); + } virtual bool NeedsEnvironment() const { return false; } virtual bool IsControlFlow() const { return false; } @@ -914,12 +1041,24 @@ class HInstruction : public ArenaObject<kArenaAllocMisc> { // Does not apply for all instructions, but having this at top level greatly // simplifies the null check elimination. - virtual bool CanBeNull() const { return true; } + virtual bool CanBeNull() const { + DCHECK_EQ(GetType(), Primitive::kPrimNot) << "CanBeNull only applies to reference types"; + return true; + } virtual bool CanDoImplicitNullCheck() const { return false; } + void SetReferenceTypeInfo(ReferenceTypeInfo reference_type_info) { + reference_type_info_ = reference_type_info; + } + + ReferenceTypeInfo GetReferenceTypeInfo() const { return reference_type_info_; } + void AddUseAt(HInstruction* user, size_t index) { - uses_.AddUse(user, index, GetBlock()->GetGraph()->GetArena()); + DCHECK(user != nullptr); + HUseListNode<HInstruction*>* use = + uses_.AddUse(user, index, GetBlock()->GetGraph()->GetArena()); + user->SetRawInputRecordAt(index, HUserRecord<HInstruction*>(user->InputRecordAt(index), use)); } void AddEnvUseAt(HEnvironment* user, size_t index) { @@ -929,11 +1068,13 @@ class HInstruction : public ArenaObject<kArenaAllocMisc> { user->RecordEnvUse(env_use); } - void RemoveUser(HInstruction* user, size_t index); - void RemoveEnvironmentUser(HUseListNode<HEnvironment*>* use); + void RemoveAsUserOfInput(size_t input) { + HUserRecord<HInstruction*> input_use = InputRecordAt(input); + input_use.GetInstruction()->uses_.Remove(input_use.GetUseNode()); + } - const HUseList<HInstruction*>& GetUses() { return uses_; } - const HUseList<HEnvironment*>& GetEnvUses() { return env_uses_; } + const HUseList<HInstruction*>& GetUses() const { return uses_; } + const HUseList<HEnvironment*>& GetEnvUses() const { return env_uses_; } bool HasUses() const { return !uses_.IsEmpty() || !env_uses_.IsEmpty(); } bool HasEnvironmentUses() const { return !env_uses_.IsEmpty(); } @@ -1015,7 +1156,25 @@ class HInstruction : public ArenaObject<kArenaAllocMisc> { void SetLiveInterval(LiveInterval* interval) { live_interval_ = interval; } bool HasLiveInterval() const { return live_interval_ != nullptr; } + bool IsSuspendCheckEntry() const { return IsSuspendCheck() && GetBlock()->IsEntryBlock(); } + + // Returns whether the code generation of the instruction will require to have access + // to the current method. Such instructions are: + // (1): Instructions that require an environment, as calling the runtime requires + // to walk the stack and have the current method stored at a specific stack address. + // (2): Object literals like classes and strings, that are loaded from the dex cache + // fields of the current method. + bool NeedsCurrentMethod() const { + return NeedsEnvironment() || IsLoadClass() || IsLoadString(); + } + + protected: + virtual const HUserRecord<HInstruction*> InputRecordAt(size_t i) const = 0; + virtual void SetRawInputRecordAt(size_t index, const HUserRecord<HInstruction*>& input) = 0; + private: + void RemoveEnvironmentUser(HUseListNode<HEnvironment*>* use_node) { env_uses_.Remove(use_node); } + HInstruction* previous_; HInstruction* next_; HBasicBlock* block_; @@ -1050,7 +1209,12 @@ class HInstruction : public ArenaObject<kArenaAllocMisc> { const SideEffects side_effects_; + // TODO: for primitive types this should be marked as invalid. + ReferenceTypeInfo reference_type_info_; + + friend class GraphChecker; friend class HBasicBlock; + friend class HEnvironment; friend class HGraph; friend class HInstructionList; @@ -1170,15 +1334,16 @@ class HTemplateInstruction: public HInstruction { virtual ~HTemplateInstruction() {} virtual size_t InputCount() const { return N; } - virtual HInstruction* InputAt(size_t i) const { return inputs_[i]; } protected: - virtual void SetRawInputAt(size_t i, HInstruction* instruction) { - inputs_[i] = instruction; + const HUserRecord<HInstruction*> InputRecordAt(size_t i) const OVERRIDE { return inputs_[i]; } + + void SetRawInputRecordAt(size_t i, const HUserRecord<HInstruction*>& input) OVERRIDE { + inputs_[i] = input; } private: - EmbeddedArray<HInstruction*, N> inputs_; + EmbeddedArray<HUserRecord<HInstruction*>, N> inputs_; friend class SsaBuilder; }; @@ -1663,6 +1828,22 @@ class HDoubleConstant : public HConstant { DISALLOW_COPY_AND_ASSIGN(HDoubleConstant); }; +class HNullConstant : public HConstant { + public: + HNullConstant() : HConstant(Primitive::kPrimNot) {} + + bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { + return true; + } + + size_t ComputeHashCode() const OVERRIDE { return 0; } + + DECLARE_INSTRUCTION(NullConstant); + + private: + DISALLOW_COPY_AND_ASSIGN(HNullConstant); +}; + // Constants of the type int. Those can be from Dex instructions, or // synthesized (for example with the if-eqz instruction). class HIntConstant : public HConstant { @@ -1718,7 +1899,6 @@ std::ostream& operator<<(std::ostream& os, const Intrinsics& intrinsic); class HInvoke : public HInstruction { public: virtual size_t InputCount() const { return inputs_.Size(); } - virtual HInstruction* InputAt(size_t i) const { return inputs_.Get(i); } // Runtime needs to walk the stack, so Dex -> Dex calls need to // know their environment. @@ -1728,10 +1908,6 @@ class HInvoke : public HInstruction { SetRawInputAt(index, argument); } - virtual void SetRawInputAt(size_t index, HInstruction* input) { - inputs_.Put(index, input); - } - virtual Primitive::Type GetType() const { return return_type_; } uint32_t GetDexPc() const { return dex_pc_; } @@ -1763,7 +1939,12 @@ class HInvoke : public HInstruction { inputs_.SetSize(number_of_arguments); } - GrowableArray<HInstruction*> inputs_; + const HUserRecord<HInstruction*> InputRecordAt(size_t i) const OVERRIDE { return inputs_.Get(i); } + void SetRawInputRecordAt(size_t index, const HUserRecord<HInstruction*>& input) OVERRIDE { + inputs_.Put(index, input); + } + + GrowableArray<HUserRecord<HInstruction*> > inputs_; const Primitive::Type return_type_; const uint32_t dex_pc_; const uint32_t dex_method_index_; @@ -2259,11 +2440,6 @@ class HPhi : public HInstruction { } size_t InputCount() const OVERRIDE { return inputs_.Size(); } - HInstruction* InputAt(size_t i) const OVERRIDE { return inputs_.Get(i); } - - void SetRawInputAt(size_t index, HInstruction* input) OVERRIDE { - inputs_.Put(index, input); - } void AddInput(HInstruction* input); @@ -2282,8 +2458,15 @@ class HPhi : public HInstruction { DECLARE_INSTRUCTION(Phi); + protected: + const HUserRecord<HInstruction*> InputRecordAt(size_t i) const OVERRIDE { return inputs_.Get(i); } + + void SetRawInputRecordAt(size_t index, const HUserRecord<HInstruction*>& input) OVERRIDE { + inputs_.Put(index, input); + } + private: - GrowableArray<HInstruction*> inputs_; + GrowableArray<HUserRecord<HInstruction*> > inputs_; const uint32_t reg_number_; Primitive::Type type_; bool is_live_; @@ -2608,7 +2791,8 @@ class HLoadClass : public HExpression<0> { type_index_(type_index), is_referrers_class_(is_referrers_class), dex_pc_(dex_pc), - generate_clinit_check_(false) {} + generate_clinit_check_(false), + loaded_class_rti_(ReferenceTypeInfo::CreateTop(/* is_exact */ false)) {} bool CanBeMoved() const OVERRIDE { return true; } @@ -2646,6 +2830,20 @@ class HLoadClass : public HExpression<0> { return !is_referrers_class_; } + ReferenceTypeInfo GetLoadedClassRTI() { + return loaded_class_rti_; + } + + void SetLoadedClassRTI(ReferenceTypeInfo rti) { + // Make sure we only set exact types (the loaded class should never be merged). + DCHECK(rti.IsExact()); + loaded_class_rti_ = rti; + } + + bool IsResolved() { + return loaded_class_rti_.IsExact(); + } + DECLARE_INSTRUCTION(LoadClass); private: @@ -2656,6 +2854,8 @@ class HLoadClass : public HExpression<0> { // Used for code generation. bool generate_clinit_check_; + ReferenceTypeInfo loaded_class_rti_; + DISALLOW_COPY_AND_ASSIGN(HLoadClass); }; @@ -2858,6 +3058,32 @@ class HInstanceOf : public HExpression<2> { DISALLOW_COPY_AND_ASSIGN(HInstanceOf); }; +class HBoundType : public HExpression<1> { + public: + HBoundType(HInstruction* input, ReferenceTypeInfo bound_type) + : HExpression(Primitive::kPrimNot, SideEffects::None()), + bound_type_(bound_type) { + SetRawInputAt(0, input); + } + + const ReferenceTypeInfo& GetBoundType() const { return bound_type_; } + + bool CanBeNull() const OVERRIDE { + // `null instanceof ClassX` always return false so we can't be null. + return false; + } + + DECLARE_INSTRUCTION(BoundType); + + private: + // Encodes the most upper class that this instruction can have. In other words + // it is always the case that GetBoundType().IsSupertypeOf(GetReferenceType()). + // It is used to bound the type in cases like `if (x instanceof ClassX) {}` + const ReferenceTypeInfo bound_type_; + + DISALLOW_COPY_AND_ASSIGN(HBoundType); +}; + class HCheckCast : public HTemplateInstruction<2> { public: HCheckCast(HInstruction* object, @@ -2959,7 +3185,7 @@ class MoveOperands : public ArenaObject<kArenaAllocMisc> { // True if this blocks a move from the given location. bool Blocks(Location loc) const { - return !IsEliminated() && source_.Equals(loc); + return !IsEliminated() && (source_.Contains(loc) || loc.Contains(source_)); } // A move is redundant if it's been eliminated, if its source and @@ -3000,46 +3226,19 @@ class HParallelMove : public HTemplateInstruction<0> { void AddMove(Location source, Location destination, HInstruction* instruction) { DCHECK(source.IsValid()); DCHECK(destination.IsValid()); - // The parallel move resolver does not handle pairs. So we decompose the - // pair locations into two moves. - if (source.IsPair() && destination.IsPair()) { - AddMove(source.ToLow(), destination.ToLow(), instruction); - AddMove(source.ToHigh(), destination.ToHigh(), nullptr); - } else if (source.IsPair()) { - DCHECK(destination.IsDoubleStackSlot()) << destination; - AddMove(source.ToLow(), Location::StackSlot(destination.GetStackIndex()), instruction); - AddMove(source.ToHigh(), Location::StackSlot(destination.GetHighStackIndex(4)), nullptr); - } else if (destination.IsPair()) { - if (source.IsConstant()) { - // We put the same constant in the move. The code generator will handle which - // low or high part to use. - AddMove(source, destination.ToLow(), instruction); - AddMove(source, destination.ToHigh(), nullptr); - } else { - DCHECK(source.IsDoubleStackSlot()); - AddMove(Location::StackSlot(source.GetStackIndex()), destination.ToLow(), instruction); - // TODO: rewrite GetHighStackIndex to not require a word size. It's supposed to - // always be 4. - static constexpr int kHighOffset = 4; - AddMove(Location::StackSlot(source.GetHighStackIndex(kHighOffset)), - destination.ToHigh(), - nullptr); - } - } else { - if (kIsDebugBuild) { - if (instruction != nullptr) { - for (size_t i = 0, e = moves_.Size(); i < e; ++i) { - DCHECK_NE(moves_.Get(i).GetInstruction(), instruction) - << "Doing parallel moves for the same instruction."; - } - } + if (kIsDebugBuild) { + if (instruction != nullptr) { for (size_t i = 0, e = moves_.Size(); i < e; ++i) { - DCHECK(!destination.Equals(moves_.Get(i).GetDestination())) - << "Same destination for two moves in a parallel move."; + DCHECK_NE(moves_.Get(i).GetInstruction(), instruction) + << "Doing parallel moves for the same instruction."; } } - moves_.Add(MoveOperands(source, destination, instruction)); + for (size_t i = 0, e = moves_.Size(); i < e; ++i) { + DCHECK(!destination.Equals(moves_.Get(i).GetDestination())) + << "Same destination for two moves in a parallel move."; + } } + moves_.Add(MoveOperands(source, destination, instruction)); } MoveOperands* MoveOperandsAt(size_t index) const { |