V8 API Reference, 7.2.502.16 (for Deno 0.2.4)
register-allocator-verifier.cc
1 // Copyright 2014 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/compiler/backend/register-allocator-verifier.h"
6 
7 #include "src/bit-vector.h"
8 #include "src/compiler/backend/instruction.h"
9 #include "src/ostreams.h"
10 
11 namespace v8 {
12 namespace internal {
13 namespace compiler {
14 
15 namespace {
16 
17 size_t OperandCount(const Instruction* instr) {
18  return instr->InputCount() + instr->OutputCount() + instr->TempCount();
19 }
20 
21 void VerifyEmptyGaps(const Instruction* instr) {
22  for (int i = Instruction::FIRST_GAP_POSITION;
23  i <= Instruction::LAST_GAP_POSITION; i++) {
24  Instruction::GapPosition inner_pos =
25  static_cast<Instruction::GapPosition>(i);
26  CHECK_NULL(instr->GetParallelMove(inner_pos));
27  }
28 }
29 
30 void VerifyAllocatedGaps(const Instruction* instr, const char* caller_info) {
31  for (int i = Instruction::FIRST_GAP_POSITION;
32  i <= Instruction::LAST_GAP_POSITION; i++) {
33  Instruction::GapPosition inner_pos =
34  static_cast<Instruction::GapPosition>(i);
35  const ParallelMove* moves = instr->GetParallelMove(inner_pos);
36  if (moves == nullptr) continue;
37  for (const MoveOperands* move : *moves) {
38  if (move->IsRedundant()) continue;
39  CHECK_WITH_MSG(
40  move->source().IsAllocated() || move->source().IsConstant(),
41  caller_info);
42  CHECK_WITH_MSG(move->destination().IsAllocated(), caller_info);
43  }
44  }
45 }
46 
47 } // namespace
48 
49 RegisterAllocatorVerifier::RegisterAllocatorVerifier(
50  Zone* zone, const RegisterConfiguration* config,
51  const InstructionSequence* sequence)
52  : zone_(zone),
53  config_(config),
54  sequence_(sequence),
55  constraints_(zone),
56  assessments_(zone),
57  outstanding_assessments_(zone) {
58  constraints_.reserve(sequence->instructions().size());
59  // TODO(dcarney): model unique constraints.
60  // Construct OperandConstraints for all InstructionOperands, eliminating
61  // kSameAsFirst along the way.
62  for (const Instruction* instr : sequence->instructions()) {
63  // All gaps should be totally unallocated at this point.
64  VerifyEmptyGaps(instr);
65  const size_t operand_count = OperandCount(instr);
66  OperandConstraint* op_constraints =
67  zone->NewArray<OperandConstraint>(operand_count);
68  size_t count = 0;
69  for (size_t i = 0; i < instr->InputCount(); ++i, ++count) {
70  BuildConstraint(instr->InputAt(i), &op_constraints[count]);
71  VerifyInput(op_constraints[count]);
72  }
73  for (size_t i = 0; i < instr->TempCount(); ++i, ++count) {
74  BuildConstraint(instr->TempAt(i), &op_constraints[count]);
75  VerifyTemp(op_constraints[count]);
76  }
77  for (size_t i = 0; i < instr->OutputCount(); ++i, ++count) {
78  BuildConstraint(instr->OutputAt(i), &op_constraints[count]);
79  if (op_constraints[count].type_ == kSameAsFirst) {
80  CHECK_LT(0, instr->InputCount());
81  op_constraints[count].type_ = op_constraints[0].type_;
82  op_constraints[count].value_ = op_constraints[0].value_;
83  }
84  VerifyOutput(op_constraints[count]);
85  }
86  InstructionConstraint instr_constraint = {instr, operand_count,
87  op_constraints};
88  constraints()->push_back(instr_constraint);
89  }
90 }
91 
92 void RegisterAllocatorVerifier::VerifyInput(
93  const OperandConstraint& constraint) {
94  CHECK_NE(kSameAsFirst, constraint.type_);
95  if (constraint.type_ != kImmediate && constraint.type_ != kExplicit) {
96  CHECK_NE(InstructionOperand::kInvalidVirtualRegister,
97  constraint.virtual_register_);
98  }
99 }
100 
101 void RegisterAllocatorVerifier::VerifyTemp(
102  const OperandConstraint& constraint) {
103  CHECK_NE(kSameAsFirst, constraint.type_);
104  CHECK_NE(kImmediate, constraint.type_);
105  CHECK_NE(kExplicit, constraint.type_);
106  CHECK_NE(kConstant, constraint.type_);
107 }
108 
109 void RegisterAllocatorVerifier::VerifyOutput(
110  const OperandConstraint& constraint) {
111  CHECK_NE(kImmediate, constraint.type_);
112  CHECK_NE(kExplicit, constraint.type_);
113  CHECK_NE(InstructionOperand::kInvalidVirtualRegister,
114  constraint.virtual_register_);
115 }
116 
117 void RegisterAllocatorVerifier::VerifyAssignment(const char* caller_info) {
118  caller_info_ = caller_info;
119  CHECK(sequence()->instructions().size() == constraints()->size());
120  auto instr_it = sequence()->begin();
121  for (const auto& instr_constraint : *constraints()) {
122  const Instruction* instr = instr_constraint.instruction_;
123  // All gaps should be totally allocated at this point.
124  VerifyAllocatedGaps(instr, caller_info_);
125  const size_t operand_count = instr_constraint.operand_constaints_size_;
126  const OperandConstraint* op_constraints =
127  instr_constraint.operand_constraints_;
128  CHECK_EQ(instr, *instr_it);
129  CHECK(operand_count == OperandCount(instr));
130  size_t count = 0;
131  for (size_t i = 0; i < instr->InputCount(); ++i, ++count) {
132  CheckConstraint(instr->InputAt(i), &op_constraints[count]);
133  }
134  for (size_t i = 0; i < instr->TempCount(); ++i, ++count) {
135  CheckConstraint(instr->TempAt(i), &op_constraints[count]);
136  }
137  for (size_t i = 0; i < instr->OutputCount(); ++i, ++count) {
138  CheckConstraint(instr->OutputAt(i), &op_constraints[count]);
139  }
140  ++instr_it;
141  }
142 }
143 
144 void RegisterAllocatorVerifier::BuildConstraint(const InstructionOperand* op,
145  OperandConstraint* constraint) {
146  constraint->value_ = kMinInt;
147  constraint->virtual_register_ = InstructionOperand::kInvalidVirtualRegister;
148  if (op->IsConstant()) {
149  constraint->type_ = kConstant;
150  constraint->value_ = ConstantOperand::cast(op)->virtual_register();
151  constraint->virtual_register_ = constraint->value_;
152  } else if (op->IsExplicit()) {
153  constraint->type_ = kExplicit;
154  } else if (op->IsImmediate()) {
155  const ImmediateOperand* imm = ImmediateOperand::cast(op);
156  int value = imm->type() == ImmediateOperand::INLINE ? imm->inline_value()
157  : imm->indexed_value();
158  constraint->type_ = kImmediate;
159  constraint->value_ = value;
160  } else {
161  CHECK(op->IsUnallocated());
162  const UnallocatedOperand* unallocated = UnallocatedOperand::cast(op);
163  int vreg = unallocated->virtual_register();
164  constraint->virtual_register_ = vreg;
165  if (unallocated->basic_policy() == UnallocatedOperand::FIXED_SLOT) {
166  constraint->type_ = kFixedSlot;
167  constraint->value_ = unallocated->fixed_slot_index();
168  } else {
169  switch (unallocated->extended_policy()) {
170  case UnallocatedOperand::REGISTER_OR_SLOT:
171  case UnallocatedOperand::NONE:
172  if (sequence()->IsFP(vreg)) {
173  constraint->type_ = kRegisterOrSlotFP;
174  } else {
175  constraint->type_ = kRegisterOrSlot;
176  }
177  break;
178  case UnallocatedOperand::REGISTER_OR_SLOT_OR_CONSTANT:
179  DCHECK(!sequence()->IsFP(vreg));
180  constraint->type_ = kRegisterOrSlotOrConstant;
181  break;
182  case UnallocatedOperand::FIXED_REGISTER:
183  if (unallocated->HasSecondaryStorage()) {
184  constraint->type_ = kRegisterAndSlot;
185  constraint->spilled_slot_ = unallocated->GetSecondaryStorage();
186  } else {
187  constraint->type_ = kFixedRegister;
188  }
189  constraint->value_ = unallocated->fixed_register_index();
190  break;
191  case UnallocatedOperand::FIXED_FP_REGISTER:
192  constraint->type_ = kFixedFPRegister;
193  constraint->value_ = unallocated->fixed_register_index();
194  break;
195  case UnallocatedOperand::MUST_HAVE_REGISTER:
196  if (sequence()->IsFP(vreg)) {
197  constraint->type_ = kFPRegister;
198  } else {
199  constraint->type_ = kRegister;
200  }
201  break;
202  case UnallocatedOperand::MUST_HAVE_SLOT:
203  constraint->type_ = kSlot;
204  constraint->value_ =
205  ElementSizeLog2Of(sequence()->GetRepresentation(vreg));
206  break;
207  case UnallocatedOperand::SAME_AS_FIRST_INPUT:
208  constraint->type_ = kSameAsFirst;
209  break;
210  }
211  }
212  }
213 }
214 
215 void RegisterAllocatorVerifier::CheckConstraint(
216  const InstructionOperand* op, const OperandConstraint* constraint) {
217  switch (constraint->type_) {
218  case kConstant:
219  CHECK_WITH_MSG(op->IsConstant(), caller_info_);
220  CHECK_EQ(ConstantOperand::cast(op)->virtual_register(),
221  constraint->value_);
222  return;
223  case kImmediate: {
224  CHECK_WITH_MSG(op->IsImmediate(), caller_info_);
225  const ImmediateOperand* imm = ImmediateOperand::cast(op);
226  int value = imm->type() == ImmediateOperand::INLINE
227  ? imm->inline_value()
228  : imm->indexed_value();
229  CHECK_EQ(value, constraint->value_);
230  return;
231  }
232  case kRegister:
233  CHECK_WITH_MSG(op->IsRegister(), caller_info_);
234  return;
235  case kFPRegister:
236  CHECK_WITH_MSG(op->IsFPRegister(), caller_info_);
237  return;
238  case kExplicit:
239  CHECK_WITH_MSG(op->IsExplicit(), caller_info_);
240  return;
241  case kFixedRegister:
242  case kRegisterAndSlot:
243  CHECK_WITH_MSG(op->IsRegister(), caller_info_);
244  CHECK_EQ(LocationOperand::cast(op)->register_code(), constraint->value_);
245  return;
246  case kFixedFPRegister:
247  CHECK_WITH_MSG(op->IsFPRegister(), caller_info_);
248  CHECK_EQ(LocationOperand::cast(op)->register_code(), constraint->value_);
249  return;
250  case kFixedSlot:
251  CHECK_WITH_MSG(op->IsStackSlot() || op->IsFPStackSlot(), caller_info_);
252  CHECK_EQ(LocationOperand::cast(op)->index(), constraint->value_);
253  return;
254  case kSlot:
255  CHECK_WITH_MSG(op->IsStackSlot() || op->IsFPStackSlot(), caller_info_);
256  CHECK_EQ(ElementSizeLog2Of(LocationOperand::cast(op)->representation()),
257  constraint->value_);
258  return;
259  case kRegisterOrSlot:
260  CHECK_WITH_MSG(op->IsRegister() || op->IsStackSlot(), caller_info_);
261  return;
262  case kRegisterOrSlotFP:
263  CHECK_WITH_MSG(op->IsFPRegister() || op->IsFPStackSlot(), caller_info_);
264  return;
265  case kRegisterOrSlotOrConstant:
266  CHECK_WITH_MSG(op->IsRegister() || op->IsStackSlot() || op->IsConstant(),
267  caller_info_);
268  return;
269  case kSameAsFirst:
270  CHECK_WITH_MSG(false, caller_info_);
271  return;
272  }
273 }
274 
275 void BlockAssessments::PerformMoves(const Instruction* instruction) {
276  const ParallelMove* first =
277  instruction->GetParallelMove(Instruction::GapPosition::START);
278  PerformParallelMoves(first);
279  const ParallelMove* last =
280  instruction->GetParallelMove(Instruction::GapPosition::END);
281  PerformParallelMoves(last);
282 }
283 
284 void BlockAssessments::PerformParallelMoves(const ParallelMove* moves) {
285  if (moves == nullptr) return;
286 
287  CHECK(map_for_moves_.empty());
288  for (MoveOperands* move : *moves) {
289  if (move->IsEliminated() || move->IsRedundant()) continue;
290  auto it = map_.find(move->source());
291  // The RHS of a parallel move should have been already assessed.
292  CHECK(it != map_.end());
293  // The LHS of a parallel move should not have been assigned in this
294  // parallel move.
295  CHECK(map_for_moves_.find(move->destination()) == map_for_moves_.end());
296  // Copy the assessment to the destination.
297  map_for_moves_[move->destination()] = it->second;
298  }
299  for (auto pair : map_for_moves_) {
300  map_[pair.first] = pair.second;
301  }
302  map_for_moves_.clear();
303 }
304 
305 void BlockAssessments::DropRegisters() {
306  for (auto iterator = map().begin(), end = map().end(); iterator != end;) {
307  auto current = iterator;
308  ++iterator;
309  InstructionOperand op = current->first;
310  if (op.IsAnyRegister()) map().erase(current);
311  }
312 }
313 
314 void BlockAssessments::Print() const {
315  StdoutStream os;
316  for (const auto pair : map()) {
317  const InstructionOperand op = pair.first;
318  const Assessment* assessment = pair.second;
319  // Use operator<< so we can write the assessment on the same
320  // line.
321  os << op << " : ";
322  if (assessment->kind() == AssessmentKind::Final) {
323  os << "v" << FinalAssessment::cast(assessment)->virtual_register();
324  } else {
325  os << "P";
326  }
327  os << std::endl;
328  }
329  os << std::endl;
330 }
331 
332 BlockAssessments* RegisterAllocatorVerifier::CreateForBlock(
333  const InstructionBlock* block) {
334  RpoNumber current_block_id = block->rpo_number();
335 
336  BlockAssessments* ret = new (zone()) BlockAssessments(zone());
337  if (block->PredecessorCount() == 0) {
338  // TODO(mtrofin): the following check should hold, however, in certain
339  // unit tests it is invalidated by the last block. Investigate and
340  // normalize the CFG.
341  // CHECK_EQ(0, current_block_id.ToInt());
342  // The phi size test below is because we can, technically, have phi
343  // instructions with one argument. Some tests expose that, too.
344  } else if (block->PredecessorCount() == 1 && block->phis().size() == 0) {
345  const BlockAssessments* prev_block = assessments_[block->predecessors()[0]];
346  ret->CopyFrom(prev_block);
347  } else {
348  for (RpoNumber pred_id : block->predecessors()) {
349  // For every operand coming from any of the predecessors, create an
350  // Unfinalized assessment.
351  auto iterator = assessments_.find(pred_id);
352  if (iterator == assessments_.end()) {
353  // This block is the head of a loop, and this predecessor is the
354  // loopback
355  // arc.
356  // Validate this is a loop case, otherwise the CFG is malformed.
357  CHECK(pred_id >= current_block_id);
358  CHECK(block->IsLoopHeader());
359  continue;
360  }
361  const BlockAssessments* pred_assessments = iterator->second;
362  CHECK_NOT_NULL(pred_assessments);
363  for (auto pair : pred_assessments->map()) {
364  InstructionOperand operand = pair.first;
365  if (ret->map().find(operand) == ret->map().end()) {
366  ret->map().insert(std::make_pair(
367  operand, new (zone()) PendingAssessment(zone(), block, operand)));
368  }
369  }
370  }
371  }
372  return ret;
373 }
374 
375 void RegisterAllocatorVerifier::ValidatePendingAssessment(
376  RpoNumber block_id, InstructionOperand op,
377  const BlockAssessments* current_assessments,
378  PendingAssessment* const assessment, int virtual_register) {
379  if (assessment->IsAliasOf(virtual_register)) return;
380 
381  // When validating a pending assessment, it is possible some of the
382  // assessments for the original operand (the one where the assessment was
383  // created for first) are also pending. To avoid recursion, we use a work
384  // list. To deal with cycles, we keep a set of seen nodes.
385  Zone local_zone(zone()->allocator(), ZONE_NAME);
386  ZoneQueue<std::pair<const PendingAssessment*, int>> worklist(&local_zone);
387  ZoneSet<RpoNumber> seen(&local_zone);
388  worklist.push(std::make_pair(assessment, virtual_register));
389  seen.insert(block_id);
390 
391  while (!worklist.empty()) {
392  auto work = worklist.front();
393  const PendingAssessment* current_assessment = work.first;
394  int current_virtual_register = work.second;
395  InstructionOperand current_operand = current_assessment->operand();
396  worklist.pop();
397 
398  const InstructionBlock* origin = current_assessment->origin();
399  CHECK(origin->PredecessorCount() > 1 || origin->phis().size() > 0);
400 
401  // Check if the virtual register is a phi first, instead of relying on
402  // the incoming assessments. In particular, this handles the case
403  // v1 = phi v0 v0, which structurally is identical to v0 having been
404  // defined at the top of a diamond, and arriving at the node joining the
405  // diamond's branches.
406  const PhiInstruction* phi = nullptr;
407  for (const PhiInstruction* candidate : origin->phis()) {
408  if (candidate->virtual_register() == current_virtual_register) {
409  phi = candidate;
410  break;
411  }
412  }
413 
414  int op_index = 0;
415  for (RpoNumber pred : origin->predecessors()) {
416  int expected =
417  phi != nullptr ? phi->operands()[op_index] : current_virtual_register;
418 
419  ++op_index;
420  auto pred_assignment = assessments_.find(pred);
421  if (pred_assignment == assessments_.end()) {
422  CHECK(origin->IsLoopHeader());
423  auto todo_iter = outstanding_assessments_.find(pred);
424  DelayedAssessments* set = nullptr;
425  if (todo_iter == outstanding_assessments_.end()) {
426  set = new (zone()) DelayedAssessments(zone());
427  outstanding_assessments_.insert(std::make_pair(pred, set));
428  } else {
429  set = todo_iter->second;
430  }
431  set->AddDelayedAssessment(current_operand, expected);
432  continue;
433  }
434 
435  const BlockAssessments* pred_assessments = pred_assignment->second;
436  auto found_contribution = pred_assessments->map().find(current_operand);
437  CHECK(found_contribution != pred_assessments->map().end());
438  Assessment* contribution = found_contribution->second;
439 
440  switch (contribution->kind()) {
441  case Final:
442  CHECK_EQ(FinalAssessment::cast(contribution)->virtual_register(),
443  expected);
444  break;
445  case Pending: {
446  // This happens if we have a diamond feeding into another one, and
447  // the inner one never being used - other than for carrying the value.
448  const PendingAssessment* next = PendingAssessment::cast(contribution);
449  if (seen.find(pred) == seen.end()) {
450  worklist.push({next, expected});
451  seen.insert(pred);
452  }
453  // Note that we do not want to finalize pending assessments at the
454  // beginning of a block - which is the information we'd have
455  // available here. This is because this operand may be reused to
456  // define duplicate phis.
457  break;
458  }
459  }
460  }
461  }
462  assessment->AddAlias(virtual_register);
463 }
464 
465 void RegisterAllocatorVerifier::ValidateUse(
466  RpoNumber block_id, BlockAssessments* current_assessments,
467  InstructionOperand op, int virtual_register) {
468  auto iterator = current_assessments->map().find(op);
469  // We should have seen this operand before.
470  CHECK(iterator != current_assessments->map().end());
471  Assessment* assessment = iterator->second;
472 
473  switch (assessment->kind()) {
474  case Final:
475  CHECK_EQ(FinalAssessment::cast(assessment)->virtual_register(),
476  virtual_register);
477  break;
478  case Pending: {
479  PendingAssessment* pending = PendingAssessment::cast(assessment);
480  ValidatePendingAssessment(block_id, op, current_assessments, pending,
481  virtual_register);
482  break;
483  }
484  }
485 }
486 
487 void RegisterAllocatorVerifier::VerifyGapMoves() {
488  CHECK(assessments_.empty());
489  CHECK(outstanding_assessments_.empty());
490  const size_t block_count = sequence()->instruction_blocks().size();
491  for (size_t block_index = 0; block_index < block_count; ++block_index) {
492  const InstructionBlock* block =
493  sequence()->instruction_blocks()[block_index];
494  BlockAssessments* block_assessments = CreateForBlock(block);
495 
496  for (int instr_index = block->code_start(); instr_index < block->code_end();
497  ++instr_index) {
498  const InstructionConstraint& instr_constraint = constraints_[instr_index];
499  const Instruction* instr = instr_constraint.instruction_;
500  block_assessments->PerformMoves(instr);
501 
502  const OperandConstraint* op_constraints =
503  instr_constraint.operand_constraints_;
504  size_t count = 0;
505  for (size_t i = 0; i < instr->InputCount(); ++i, ++count) {
506  if (op_constraints[count].type_ == kImmediate ||
507  op_constraints[count].type_ == kExplicit) {
508  continue;
509  }
510  int virtual_register = op_constraints[count].virtual_register_;
511  InstructionOperand op = *instr->InputAt(i);
512  ValidateUse(block->rpo_number(), block_assessments, op,
513  virtual_register);
514  }
515  for (size_t i = 0; i < instr->TempCount(); ++i, ++count) {
516  block_assessments->Drop(*instr->TempAt(i));
517  }
518  if (instr->IsCall()) {
519  block_assessments->DropRegisters();
520  }
521  for (size_t i = 0; i < instr->OutputCount(); ++i, ++count) {
522  int virtual_register = op_constraints[count].virtual_register_;
523  block_assessments->AddDefinition(*instr->OutputAt(i), virtual_register);
524  if (op_constraints[count].type_ == kRegisterAndSlot) {
525  const AllocatedOperand* reg_op =
526  AllocatedOperand::cast(instr->OutputAt(i));
527  MachineRepresentation rep = reg_op->representation();
528  const AllocatedOperand* stack_op = AllocatedOperand::New(
529  zone(), LocationOperand::LocationKind::STACK_SLOT, rep,
530  op_constraints[i].spilled_slot_);
531  block_assessments->AddDefinition(*stack_op, virtual_register);
532  }
533  }
534  }
535  // Now commit the assessments for this block. If there are any delayed
536  // assessments, ValidatePendingAssessment should see this block, too.
537  assessments_[block->rpo_number()] = block_assessments;
538 
539  auto todo_iter = outstanding_assessments_.find(block->rpo_number());
540  if (todo_iter == outstanding_assessments_.end()) continue;
541  DelayedAssessments* todo = todo_iter->second;
542  for (auto pair : todo->map()) {
543  InstructionOperand op = pair.first;
544  int vreg = pair.second;
545  auto found_op = block_assessments->map().find(op);
546  CHECK(found_op != block_assessments->map().end());
547  switch (found_op->second->kind()) {
548  case Final:
549  CHECK_EQ(FinalAssessment::cast(found_op->second)->virtual_register(),
550  vreg);
551  break;
552  case Pending:
553  ValidatePendingAssessment(block->rpo_number(), op, block_assessments,
554  PendingAssessment::cast(found_op->second),
555  vreg);
556  break;
557  }
558  }
559  }
560 }
561 
562 } // namespace compiler
563 } // namespace internal
564 } // namespace v8
Definition: libplatform.h:13