// 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(assessment); } static PendingAssessment* cast(Assessment* assessment) { CHECK(assessment->kind() == Pending); return static_cast(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 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(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 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 Constraints; class DelayedAssessments : public ZoneObject { public: explicit DelayedAssessments(Zone* zone) : map_(zone) {} const ZoneMap& 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 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 assessments_; ZoneMap 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