| Index: src/compiler/register-allocator-verifier.h
|
| diff --git a/src/compiler/register-allocator-verifier.h b/src/compiler/register-allocator-verifier.h
|
| index f3ab54f01818788ae70fb2a6aa59714aefd9e776..18d38c58a1511af434eeaf290c9d69a8a083f282 100644
|
| --- a/src/compiler/register-allocator-verifier.h
|
| +++ b/src/compiler/register-allocator-verifier.h
|
| @@ -14,6 +14,145 @@ namespace compiler {
|
| class InstructionOperand;
|
| 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 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);
|
| +};
|
| +
|
| +// FinalAssessmens 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);
|
| +};
|
| +
|
| +// 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. 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(const InstructionBlock* origin,
|
| + InstructionOperand operand)
|
| + : Assessment(Pending), origin_(origin), operand_(operand) {}
|
| +
|
| + 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_; }
|
| +
|
| + private:
|
| + const InstructionBlock* const origin_;
|
| + InstructionOperand operand_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(PendingAssessment);
|
| +};
|
| +
|
| +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,
|
| @@ -53,10 +192,29 @@ class RegisterAllocatorVerifier final : public ZoneObject {
|
| OperandConstraint* operand_constraints_;
|
| };
|
|
|
| - class BlockMaps;
|
| -
|
| 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_; }
|
| @@ -70,13 +228,25 @@ class RegisterAllocatorVerifier final : public ZoneObject {
|
| OperandConstraint* constraint);
|
| void CheckConstraint(const InstructionOperand* op,
|
| const OperandConstraint* constraint);
|
| + BlockAssessments* CreateForBlock(const InstructionBlock* block);
|
|
|
| - void VerifyGapMoves(BlockMaps* outgoing_mappings, bool initial_pass);
|
| + void ValidatePendingAssessment(RpoNumber block_id, InstructionOperand op,
|
| + PendingAssessment* 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_;
|
| +
|
| + // Cached structures, to avoid memory churn. Needed solely in
|
| + // ValidatePendingAssessment.
|
| + ZoneQueue<std::pair<PendingAssessment*, int>> worklist_;
|
| + ZoneSet<RpoNumber> seen_;
|
|
|
| DISALLOW_COPY_AND_ASSIGN(RegisterAllocatorVerifier);
|
| };
|
|
|