| // Copyright 2014 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef V8_REGISTER_ALLOCATOR_VERIFIER_H_ |
| #define V8_REGISTER_ALLOCATOR_VERIFIER_H_ |
| |
| #include "src/compiler/instruction.h" |
| #include "src/zone/zone-containers.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace compiler { |
| |
| class InstructionBlock; |
| class InstructionSequence; |
| |
| // The register allocator validator traverses instructions in the instruction |
| // sequence, and verifies the correctness of machine operand substitutions of |
| // virtual registers. It collects the virtual register instruction signatures |
| // before register allocation. Then, after the register allocation pipeline |
| // completes, it compares the operand substitutions against the pre-allocation |
| // data. |
| // At a high level, validation works as follows: we iterate through each block, |
| // and, in a block, through each instruction; then: |
| // - when an operand is the output of an instruction, we associate it to the |
| // virtual register that the instruction sequence declares as its output. We |
| // use the concept of "FinalAssessment" to model this. |
| // - when an operand is used in an instruction, we check that the assessment |
| // matches the expectation of the instruction |
| // - moves simply copy the assessment over to the new operand |
| // - blocks with more than one predecessor associate to each operand a "Pending" |
| // assessment. The pending assessment remembers the operand and block where it |
| // was created. Then, when the value is used (which may be as a different |
| // operand, because of moves), we check that the virtual register at the use |
| // site matches the definition of this pending operand: either the phi inputs |
| // match, or, if it's not a phi, all the predecessors at the point the pending |
| // assessment was defined have that operand assigned to the given virtual |
| // register. If all checks out, we record in the assessment that the virtual |
| // register is aliased by the specific operand. |
| // If a block is a loop header - so one or more of its predecessors are it or |
| // below - we still treat uses of operands as above, but we record which operand |
| // assessments haven't been made yet, and what virtual register they must |
| // correspond to, and verify that when we are done with the respective |
| // predecessor blocks. |
| // This way, the algorithm always makes a final decision about the operands |
| // in an instruction, ensuring convergence. |
| // Operand assessments are recorded per block, as the result at the exit from |
| // the block. When moving to a new block, we copy assessments from its single |
| // predecessor, or, if the block has multiple predecessors, the mechanism was |
| // described already. |
| |
| enum AssessmentKind { Final, Pending }; |
| |
| class Assessment : public ZoneObject { |
| public: |
| AssessmentKind kind() const { return kind_; } |
| |
| protected: |
| explicit Assessment(AssessmentKind kind) : kind_(kind) {} |
| AssessmentKind kind_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(Assessment); |
| }; |
| |
| // PendingAssessments are associated to operands coming from the multiple |
| // predecessors of a block. We only record the operand and the block, and |
| // will determine if the way the operand is defined (from the predecessors) |
| // matches a particular use. We allow more than one vreg association with |
| // an operand - this handles scenarios where multiple phis are |
| // defined with identical operands, and the move optimizer moved down the moves |
| // separating the 2 phis in the block defining them. |
| class PendingAssessment final : public Assessment { |
| public: |
| explicit PendingAssessment(Zone* zone, const InstructionBlock* origin, |
| InstructionOperand operand) |
| : Assessment(Pending), |
| origin_(origin), |
| operand_(operand), |
| aliases_(zone) {} |
| |
| static const PendingAssessment* cast(const Assessment* assessment) { |
| CHECK(assessment->kind() == Pending); |
| return static_cast<const PendingAssessment*>(assessment); |
| } |
| |
| static PendingAssessment* cast(Assessment* assessment) { |
| CHECK(assessment->kind() == Pending); |
| return static_cast<PendingAssessment*>(assessment); |
| } |
| |
| const InstructionBlock* origin() const { return origin_; } |
| InstructionOperand operand() const { return operand_; } |
| bool IsAliasOf(int vreg) const { return aliases_.count(vreg) > 0; } |
| void AddAlias(int vreg) { aliases_.insert(vreg); } |
| |
| private: |
| const InstructionBlock* const origin_; |
| InstructionOperand operand_; |
| ZoneSet<int> aliases_; |
| |
| DISALLOW_COPY_AND_ASSIGN(PendingAssessment); |
| }; |
| |
| // FinalAssessments are associated to operands that we know to be a certain |
| // virtual register. |
| class FinalAssessment final : public Assessment { |
| public: |
| explicit FinalAssessment(int virtual_register) |
| : Assessment(Final), virtual_register_(virtual_register) {} |
| |
| int virtual_register() const { return virtual_register_; } |
| static const FinalAssessment* cast(const Assessment* assessment) { |
| CHECK(assessment->kind() == Final); |
| return static_cast<const FinalAssessment*>(assessment); |
| } |
| |
| private: |
| int virtual_register_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FinalAssessment); |
| }; |
| |
| struct OperandAsKeyLess { |
| bool operator()(const InstructionOperand& a, |
| const InstructionOperand& b) const { |
| return a.CompareCanonicalized(b); |
| } |
| }; |
| |
| // Assessments associated with a basic block. |
| class BlockAssessments : public ZoneObject { |
| public: |
| typedef ZoneMap<InstructionOperand, Assessment*, OperandAsKeyLess> OperandMap; |
| explicit BlockAssessments(Zone* zone) |
| : map_(zone), map_for_moves_(zone), zone_(zone) {} |
| void Drop(InstructionOperand operand) { map_.erase(operand); } |
| void DropRegisters(); |
| void AddDefinition(InstructionOperand operand, int virtual_register) { |
| auto existent = map_.find(operand); |
| if (existent != map_.end()) { |
| // Drop the assignment |
| map_.erase(existent); |
| } |
| map_.insert( |
| std::make_pair(operand, new (zone_) FinalAssessment(virtual_register))); |
| } |
| |
| void PerformMoves(const Instruction* instruction); |
| void PerformParallelMoves(const ParallelMove* moves); |
| void CopyFrom(const BlockAssessments* other) { |
| CHECK(map_.empty()); |
| CHECK_NOT_NULL(other); |
| map_.insert(other->map_.begin(), other->map_.end()); |
| } |
| |
| OperandMap& map() { return map_; } |
| const OperandMap& map() const { return map_; } |
| void Print() const; |
| |
| private: |
| OperandMap map_; |
| OperandMap map_for_moves_; |
| Zone* zone_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BlockAssessments); |
| }; |
| |
| class RegisterAllocatorVerifier final : public ZoneObject { |
| public: |
| RegisterAllocatorVerifier(Zone* zone, const RegisterConfiguration* config, |
| const InstructionSequence* sequence); |
| |
| void VerifyAssignment(const char* caller_info); |
| void VerifyGapMoves(); |
| |
| private: |
| enum ConstraintType { |
| kConstant, |
| kImmediate, |
| kRegister, |
| kFixedRegister, |
| kFPRegister, |
| kFixedFPRegister, |
| kSlot, |
| kFixedSlot, |
| kNone, |
| kNoneFP, |
| kExplicit, |
| kSameAsFirst, |
| kRegisterAndSlot |
| }; |
| |
| struct OperandConstraint { |
| ConstraintType type_; |
| // Constant or immediate value, register code, slot index, or slot size |
| // when relevant. |
| int value_; |
| int spilled_slot_; |
| int virtual_register_; |
| }; |
| |
| struct InstructionConstraint { |
| const Instruction* instruction_; |
| size_t operand_constaints_size_; |
| OperandConstraint* operand_constraints_; |
| }; |
| |
| typedef ZoneVector<InstructionConstraint> Constraints; |
| |
| class DelayedAssessments : public ZoneObject { |
| public: |
| explicit DelayedAssessments(Zone* zone) : map_(zone) {} |
| |
| const ZoneMap<InstructionOperand, int, OperandAsKeyLess>& map() const { |
| return map_; |
| } |
| |
| void AddDelayedAssessment(InstructionOperand op, int vreg) { |
| auto it = map_.find(op); |
| if (it == map_.end()) { |
| map_.insert(std::make_pair(op, vreg)); |
| } else { |
| CHECK_EQ(it->second, vreg); |
| } |
| } |
| |
| private: |
| ZoneMap<InstructionOperand, int, OperandAsKeyLess> map_; |
| }; |
| |
| Zone* zone() const { return zone_; } |
| const RegisterConfiguration* config() { return config_; } |
| const InstructionSequence* sequence() const { return sequence_; } |
| Constraints* constraints() { return &constraints_; } |
| |
| static void VerifyInput(const OperandConstraint& constraint); |
| static void VerifyTemp(const OperandConstraint& constraint); |
| static void VerifyOutput(const OperandConstraint& constraint); |
| |
| void BuildConstraint(const InstructionOperand* op, |
| OperandConstraint* constraint); |
| void CheckConstraint(const InstructionOperand* op, |
| const OperandConstraint* constraint); |
| BlockAssessments* CreateForBlock(const InstructionBlock* block); |
| |
| // Prove that this operand is an alias of this virtual register in the given |
| // block. Update the assessment if that's the case. |
| void ValidatePendingAssessment(RpoNumber block_id, InstructionOperand op, |
| const BlockAssessments* current_assessments, |
| PendingAssessment* const assessment, |
| int virtual_register); |
| void ValidateUse(RpoNumber block_id, BlockAssessments* current_assessments, |
| InstructionOperand op, int virtual_register); |
| |
| Zone* const zone_; |
| const RegisterConfiguration* config_; |
| const InstructionSequence* const sequence_; |
| Constraints constraints_; |
| ZoneMap<RpoNumber, BlockAssessments*> assessments_; |
| ZoneMap<RpoNumber, DelayedAssessments*> outstanding_assessments_; |
| // TODO(chromium:725559): remove after we understand this bug's root cause. |
| const char* caller_info_ = nullptr; |
| |
| DISALLOW_COPY_AND_ASSIGN(RegisterAllocatorVerifier); |
| }; |
| |
| } // namespace compiler |
| } // namespace internal |
| } // namespace v8 |
| |
| #endif |