• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/move-optimizer.h"
6 
7 #include "src/codegen/register-configuration.h"
8 
9 namespace v8 {
10 namespace internal {
11 namespace compiler {
12 
13 namespace {
14 
15 struct MoveKey {
16   InstructionOperand source;
17   InstructionOperand destination;
18 };
19 
20 struct MoveKeyCompare {
operator ()v8::internal::compiler::__anon227017630111::MoveKeyCompare21   bool operator()(const MoveKey& a, const MoveKey& b) const {
22     if (a.source.EqualsCanonicalized(b.source)) {
23       return a.destination.CompareCanonicalized(b.destination);
24     }
25     return a.source.CompareCanonicalized(b.source);
26   }
27 };
28 
29 using MoveMap = ZoneMap<MoveKey, unsigned, MoveKeyCompare>;
30 
31 class OperandSet {
32  public:
OperandSet(ZoneVector<InstructionOperand> * buffer)33   explicit OperandSet(ZoneVector<InstructionOperand>* buffer)
34       : set_(buffer), fp_reps_(0) {
35     buffer->clear();
36   }
37 
InsertOp(const InstructionOperand & op)38   void InsertOp(const InstructionOperand& op) {
39     set_->push_back(op);
40 
41     if (kFPAliasing == AliasingKind::kCombine && op.IsFPRegister())
42       fp_reps_ |= RepresentationBit(LocationOperand::cast(op).representation());
43   }
44 
Contains(const InstructionOperand & op) const45   bool Contains(const InstructionOperand& op) const {
46     for (const InstructionOperand& elem : *set_) {
47       if (elem.EqualsCanonicalized(op)) return true;
48     }
49     return false;
50   }
51 
ContainsOpOrAlias(const InstructionOperand & op) const52   bool ContainsOpOrAlias(const InstructionOperand& op) const {
53     if (Contains(op)) return true;
54 
55     if (kFPAliasing == AliasingKind::kCombine && op.IsFPRegister()) {
56       // Platforms where FP registers have complex aliasing need extra checks.
57       const LocationOperand& loc = LocationOperand::cast(op);
58       MachineRepresentation rep = loc.representation();
59       // If haven't encountered mixed rep FP registers, skip the extra checks.
60       if (!HasMixedFPReps(fp_reps_ | RepresentationBit(rep))) return false;
61 
62       // Check register against aliasing registers of other FP representations.
63       MachineRepresentation other_rep1, other_rep2;
64       switch (rep) {
65         case MachineRepresentation::kFloat32:
66           other_rep1 = MachineRepresentation::kFloat64;
67           other_rep2 = MachineRepresentation::kSimd128;
68           break;
69         case MachineRepresentation::kFloat64:
70           other_rep1 = MachineRepresentation::kFloat32;
71           other_rep2 = MachineRepresentation::kSimd128;
72           break;
73         case MachineRepresentation::kSimd128:
74           other_rep1 = MachineRepresentation::kFloat32;
75           other_rep2 = MachineRepresentation::kFloat64;
76           break;
77         default:
78           UNREACHABLE();
79       }
80       const RegisterConfiguration* config = RegisterConfiguration::Default();
81       int base = -1;
82       int aliases =
83           config->GetAliases(rep, loc.register_code(), other_rep1, &base);
84       DCHECK(aliases > 0 || (aliases == 0 && base == -1));
85       while (aliases--) {
86         if (Contains(AllocatedOperand(LocationOperand::REGISTER, other_rep1,
87                                       base + aliases))) {
88           return true;
89         }
90       }
91       aliases = config->GetAliases(rep, loc.register_code(), other_rep2, &base);
92       DCHECK(aliases > 0 || (aliases == 0 && base == -1));
93       while (aliases--) {
94         if (Contains(AllocatedOperand(LocationOperand::REGISTER, other_rep2,
95                                       base + aliases))) {
96           return true;
97         }
98       }
99     }
100     return false;
101   }
102 
103  private:
HasMixedFPReps(int reps)104   static bool HasMixedFPReps(int reps) {
105     return reps && !base::bits::IsPowerOfTwo(reps);
106   }
107 
108   ZoneVector<InstructionOperand>* set_;
109   int fp_reps_;
110 };
111 
FindFirstNonEmptySlot(const Instruction * instr)112 int FindFirstNonEmptySlot(const Instruction* instr) {
113   int i = Instruction::FIRST_GAP_POSITION;
114   for (; i <= Instruction::LAST_GAP_POSITION; i++) {
115     ParallelMove* moves = instr->parallel_moves()[i];
116     if (moves == nullptr) continue;
117     for (MoveOperands* move : *moves) {
118       if (!move->IsRedundant()) return i;
119       move->Eliminate();
120     }
121     moves->clear();  // Clear this redundant move.
122   }
123   return i;
124 }
125 
126 }  // namespace
127 
MoveOptimizer(Zone * local_zone,InstructionSequence * code)128 MoveOptimizer::MoveOptimizer(Zone* local_zone, InstructionSequence* code)
129     : local_zone_(local_zone),
130       code_(code),
131       local_vector_(local_zone),
132       operand_buffer1(local_zone),
133       operand_buffer2(local_zone) {}
134 
Run()135 void MoveOptimizer::Run() {
136   for (Instruction* instruction : code()->instructions()) {
137     CompressGaps(instruction);
138   }
139   for (InstructionBlock* block : code()->instruction_blocks()) {
140     CompressBlock(block);
141   }
142   for (InstructionBlock* block : code()->instruction_blocks()) {
143     if (block->PredecessorCount() <= 1) continue;
144     if (!block->IsDeferred()) {
145       bool has_only_deferred = true;
146       for (RpoNumber& pred_id : block->predecessors()) {
147         if (!code()->InstructionBlockAt(pred_id)->IsDeferred()) {
148           has_only_deferred = false;
149           break;
150         }
151       }
152       // This would pull down common moves. If the moves occur in deferred
153       // blocks, and the closest common successor is not deferred, we lose the
154       // optimization of just spilling/filling in deferred blocks, when the
155       // current block is not deferred.
156       if (has_only_deferred) continue;
157     }
158     OptimizeMerge(block);
159   }
160   for (Instruction* gap : code()->instructions()) {
161     FinalizeMoves(gap);
162   }
163 }
164 
RemoveClobberedDestinations(Instruction * instruction)165 void MoveOptimizer::RemoveClobberedDestinations(Instruction* instruction) {
166   if (instruction->IsCall()) return;
167   ParallelMove* moves = instruction->parallel_moves()[0];
168   if (moves == nullptr) return;
169 
170   DCHECK(instruction->parallel_moves()[1] == nullptr ||
171          instruction->parallel_moves()[1]->empty());
172 
173   OperandSet outputs(&operand_buffer1);
174   OperandSet inputs(&operand_buffer2);
175 
176   // Outputs and temps are treated together as potentially clobbering a
177   // destination operand.
178   for (size_t i = 0; i < instruction->OutputCount(); ++i) {
179     outputs.InsertOp(*instruction->OutputAt(i));
180   }
181   for (size_t i = 0; i < instruction->TempCount(); ++i) {
182     outputs.InsertOp(*instruction->TempAt(i));
183   }
184 
185   // Input operands block elisions.
186   for (size_t i = 0; i < instruction->InputCount(); ++i) {
187     inputs.InsertOp(*instruction->InputAt(i));
188   }
189 
190   // Elide moves made redundant by the instruction.
191   for (MoveOperands* move : *moves) {
192     if (outputs.ContainsOpOrAlias(move->destination()) &&
193         !inputs.ContainsOpOrAlias(move->destination())) {
194       move->Eliminate();
195     }
196   }
197 
198   // The ret instruction makes any assignment before it unnecessary, except for
199   // the one for its input.
200   if (instruction->IsRet() || instruction->IsTailCall()) {
201     for (MoveOperands* move : *moves) {
202       if (!inputs.ContainsOpOrAlias(move->destination())) {
203         move->Eliminate();
204       }
205     }
206   }
207 }
208 
MigrateMoves(Instruction * to,Instruction * from)209 void MoveOptimizer::MigrateMoves(Instruction* to, Instruction* from) {
210   if (from->IsCall()) return;
211 
212   ParallelMove* from_moves = from->parallel_moves()[0];
213   if (from_moves == nullptr || from_moves->empty()) return;
214 
215   OperandSet dst_cant_be(&operand_buffer1);
216   OperandSet src_cant_be(&operand_buffer2);
217 
218   // If an operand is an input to the instruction, we cannot move assignments
219   // where it appears on the LHS.
220   for (size_t i = 0; i < from->InputCount(); ++i) {
221     dst_cant_be.InsertOp(*from->InputAt(i));
222   }
223   // If an operand is output to the instruction, we cannot move assignments
224   // where it appears on the RHS, because we would lose its value before the
225   // instruction.
226   // Same for temp operands.
227   // The output can't appear on the LHS because we performed
228   // RemoveClobberedDestinations for the "from" instruction.
229   for (size_t i = 0; i < from->OutputCount(); ++i) {
230     src_cant_be.InsertOp(*from->OutputAt(i));
231   }
232   for (size_t i = 0; i < from->TempCount(); ++i) {
233     src_cant_be.InsertOp(*from->TempAt(i));
234   }
235   for (MoveOperands* move : *from_moves) {
236     if (move->IsRedundant()) continue;
237     // Assume dest has a value "V". If we have a "dest = y" move, then we can't
238     // move "z = dest", because z would become y rather than "V".
239     // We assume CompressMoves has happened before this, which means we don't
240     // have more than one assignment to dest.
241     src_cant_be.InsertOp(move->destination());
242   }
243 
244   ZoneSet<MoveKey, MoveKeyCompare> move_candidates(local_zone());
245   // We start with all the moves that don't have conflicting source or
246   // destination operands are eligible for being moved down.
247   for (MoveOperands* move : *from_moves) {
248     if (move->IsRedundant()) continue;
249     if (!dst_cant_be.ContainsOpOrAlias(move->destination())) {
250       MoveKey key = {move->source(), move->destination()};
251       move_candidates.insert(key);
252     }
253   }
254   if (move_candidates.empty()) return;
255 
256   // Stabilize the candidate set.
257   bool changed = false;
258   do {
259     changed = false;
260     for (auto iter = move_candidates.begin(); iter != move_candidates.end();) {
261       auto current = iter;
262       ++iter;
263       InstructionOperand src = current->source;
264       if (src_cant_be.ContainsOpOrAlias(src)) {
265         src_cant_be.InsertOp(current->destination);
266         move_candidates.erase(current);
267         changed = true;
268       }
269     }
270   } while (changed);
271 
272   ParallelMove to_move(local_zone());
273   for (MoveOperands* move : *from_moves) {
274     if (move->IsRedundant()) continue;
275     MoveKey key = {move->source(), move->destination()};
276     if (move_candidates.find(key) != move_candidates.end()) {
277       to_move.AddMove(move->source(), move->destination(), code_zone());
278       move->Eliminate();
279     }
280   }
281   if (to_move.empty()) return;
282 
283   ParallelMove* dest =
284       to->GetOrCreateParallelMove(Instruction::GapPosition::START, code_zone());
285 
286   CompressMoves(&to_move, dest);
287   DCHECK(dest->empty());
288   for (MoveOperands* m : to_move) {
289     dest->push_back(m);
290   }
291 }
292 
CompressMoves(ParallelMove * left,MoveOpVector * right)293 void MoveOptimizer::CompressMoves(ParallelMove* left, MoveOpVector* right) {
294   if (right == nullptr) return;
295 
296   MoveOpVector& eliminated = local_vector();
297   DCHECK(eliminated.empty());
298 
299   if (!left->empty()) {
300     // Modify the right moves in place and collect moves that will be killed by
301     // merging the two gaps.
302     for (MoveOperands* move : *right) {
303       if (move->IsRedundant()) continue;
304       left->PrepareInsertAfter(move, &eliminated);
305     }
306     // Eliminate dead moves.
307     for (MoveOperands* to_eliminate : eliminated) {
308       to_eliminate->Eliminate();
309     }
310     eliminated.clear();
311   }
312   // Add all possibly modified moves from right side.
313   for (MoveOperands* move : *right) {
314     if (move->IsRedundant()) continue;
315     left->push_back(move);
316   }
317   // Nuke right.
318   right->clear();
319   DCHECK(eliminated.empty());
320 }
321 
CompressGaps(Instruction * instruction)322 void MoveOptimizer::CompressGaps(Instruction* instruction) {
323   int i = FindFirstNonEmptySlot(instruction);
324   bool has_moves = i <= Instruction::LAST_GAP_POSITION;
325   USE(has_moves);
326 
327   if (i == Instruction::LAST_GAP_POSITION) {
328     std::swap(instruction->parallel_moves()[Instruction::FIRST_GAP_POSITION],
329               instruction->parallel_moves()[Instruction::LAST_GAP_POSITION]);
330   } else if (i == Instruction::FIRST_GAP_POSITION) {
331     CompressMoves(
332         instruction->parallel_moves()[Instruction::FIRST_GAP_POSITION],
333         instruction->parallel_moves()[Instruction::LAST_GAP_POSITION]);
334   }
335   // We either have no moves, or, after swapping or compressing, we have
336   // all the moves in the first gap position, and none in the second/end gap
337   // position.
338   ParallelMove* first =
339       instruction->parallel_moves()[Instruction::FIRST_GAP_POSITION];
340   ParallelMove* last =
341       instruction->parallel_moves()[Instruction::LAST_GAP_POSITION];
342   USE(first);
343   USE(last);
344 
345   DCHECK(!has_moves ||
346          (first != nullptr && (last == nullptr || last->empty())));
347 }
348 
CompressBlock(InstructionBlock * block)349 void MoveOptimizer::CompressBlock(InstructionBlock* block) {
350   int first_instr_index = block->first_instruction_index();
351   int last_instr_index = block->last_instruction_index();
352 
353   // Start by removing gap assignments where the output of the subsequent
354   // instruction appears on LHS, as long as they are not needed by its input.
355   Instruction* prev_instr = code()->instructions()[first_instr_index];
356   RemoveClobberedDestinations(prev_instr);
357 
358   for (int index = first_instr_index + 1; index <= last_instr_index; ++index) {
359     Instruction* instr = code()->instructions()[index];
360     // Migrate to the gap of prev_instr eligible moves from instr.
361     MigrateMoves(instr, prev_instr);
362     // Remove gap assignments clobbered by instr's output.
363     RemoveClobberedDestinations(instr);
364     prev_instr = instr;
365   }
366 }
367 
LastInstruction(const InstructionBlock * block) const368 const Instruction* MoveOptimizer::LastInstruction(
369     const InstructionBlock* block) const {
370   return code()->instructions()[block->last_instruction_index()];
371 }
372 
OptimizeMerge(InstructionBlock * block)373 void MoveOptimizer::OptimizeMerge(InstructionBlock* block) {
374   DCHECK_LT(1, block->PredecessorCount());
375   // Ensure that the last instruction in all incoming blocks don't contain
376   // things that would prevent moving gap moves across them.
377   for (RpoNumber& pred_index : block->predecessors()) {
378     const InstructionBlock* pred = code()->InstructionBlockAt(pred_index);
379 
380     // If the predecessor has more than one successor, we shouldn't attempt to
381     // move down to this block (one of the successors) any of the gap moves,
382     // because their effect may be necessary to the other successors.
383     if (pred->SuccessorCount() > 1) return;
384 
385     const Instruction* last_instr =
386         code()->instructions()[pred->last_instruction_index()];
387     if (last_instr->IsCall()) return;
388     if (last_instr->TempCount() != 0) return;
389     if (last_instr->OutputCount() != 0) return;
390     for (size_t i = 0; i < last_instr->InputCount(); ++i) {
391       const InstructionOperand* op = last_instr->InputAt(i);
392       if (!op->IsConstant() && !op->IsImmediate()) return;
393     }
394   }
395   // TODO(dcarney): pass a ZoneStats down for this?
396   MoveMap move_map(local_zone());
397   size_t correct_counts = 0;
398   // Accumulate set of shared moves.
399   for (RpoNumber& pred_index : block->predecessors()) {
400     const InstructionBlock* pred = code()->InstructionBlockAt(pred_index);
401     const Instruction* instr = LastInstruction(pred);
402     if (instr->parallel_moves()[0] == nullptr ||
403         instr->parallel_moves()[0]->empty()) {
404       return;
405     }
406     for (const MoveOperands* move : *instr->parallel_moves()[0]) {
407       if (move->IsRedundant()) continue;
408       InstructionOperand src = move->source();
409       InstructionOperand dst = move->destination();
410       MoveKey key = {src, dst};
411       auto res = move_map.insert(std::make_pair(key, 1));
412       if (!res.second) {
413         res.first->second++;
414         if (res.first->second == block->PredecessorCount()) {
415           correct_counts++;
416         }
417       }
418     }
419   }
420   if (move_map.empty() || correct_counts == 0) return;
421 
422   // Find insertion point.
423   Instruction* instr = code()->instructions()[block->first_instruction_index()];
424 
425   if (correct_counts != move_map.size()) {
426     // Moves that are unique to each predecessor won't be pushed to the common
427     // successor.
428     OperandSet conflicting_srcs(&operand_buffer1);
429     for (auto iter = move_map.begin(), end = move_map.end(); iter != end;) {
430       auto current = iter;
431       ++iter;
432       if (current->second != block->PredecessorCount()) {
433         InstructionOperand dest = current->first.destination;
434         // Not all the moves in all the gaps are the same. Maybe some are. If
435         // there are such moves, we could move them, but the destination of the
436         // moves staying behind can't appear as a source of a common move,
437         // because the move staying behind will clobber this destination.
438         conflicting_srcs.InsertOp(dest);
439         move_map.erase(current);
440       }
441     }
442 
443     bool changed = false;
444     do {
445       // If a common move can't be pushed to the common successor, then its
446       // destination also can't appear as source to any move being pushed.
447       changed = false;
448       for (auto iter = move_map.begin(), end = move_map.end(); iter != end;) {
449         auto current = iter;
450         ++iter;
451         DCHECK_EQ(block->PredecessorCount(), current->second);
452         if (conflicting_srcs.ContainsOpOrAlias(current->first.source)) {
453           conflicting_srcs.InsertOp(current->first.destination);
454           move_map.erase(current);
455           changed = true;
456         }
457       }
458     } while (changed);
459   }
460 
461   if (move_map.empty()) return;
462 
463   DCHECK_NOT_NULL(instr);
464   bool gap_initialized = true;
465   if (instr->parallel_moves()[0] != nullptr &&
466       !instr->parallel_moves()[0]->empty()) {
467     // Will compress after insertion.
468     gap_initialized = false;
469     std::swap(instr->parallel_moves()[0], instr->parallel_moves()[1]);
470   }
471   ParallelMove* moves = instr->GetOrCreateParallelMove(
472       static_cast<Instruction::GapPosition>(0), code_zone());
473   // Delete relevant entries in predecessors and move everything to block.
474   bool first_iteration = true;
475   for (RpoNumber& pred_index : block->predecessors()) {
476     const InstructionBlock* pred = code()->InstructionBlockAt(pred_index);
477     for (MoveOperands* move : *LastInstruction(pred)->parallel_moves()[0]) {
478       if (move->IsRedundant()) continue;
479       MoveKey key = {move->source(), move->destination()};
480       auto it = move_map.find(key);
481       if (it != move_map.end()) {
482         if (first_iteration) {
483           moves->AddMove(move->source(), move->destination());
484         }
485         move->Eliminate();
486       }
487     }
488     first_iteration = false;
489   }
490   // Compress.
491   if (!gap_initialized) {
492     CompressMoves(instr->parallel_moves()[0], instr->parallel_moves()[1]);
493   }
494   CompressBlock(block);
495 }
496 
497 namespace {
498 
IsSlot(const InstructionOperand & op)499 bool IsSlot(const InstructionOperand& op) {
500   return op.IsStackSlot() || op.IsFPStackSlot();
501 }
502 
LoadCompare(const MoveOperands * a,const MoveOperands * b)503 bool LoadCompare(const MoveOperands* a, const MoveOperands* b) {
504   if (!a->source().EqualsCanonicalized(b->source())) {
505     return a->source().CompareCanonicalized(b->source());
506   }
507   if (IsSlot(a->destination()) && !IsSlot(b->destination())) return false;
508   if (!IsSlot(a->destination()) && IsSlot(b->destination())) return true;
509   return a->destination().CompareCanonicalized(b->destination());
510 }
511 
512 }  // namespace
513 
514 // Split multiple loads of the same constant or stack slot off into the second
515 // slot and keep remaining moves in the first slot.
FinalizeMoves(Instruction * instr)516 void MoveOptimizer::FinalizeMoves(Instruction* instr) {
517   MoveOpVector& loads = local_vector();
518   DCHECK(loads.empty());
519 
520   ParallelMove* parallel_moves = instr->parallel_moves()[0];
521   if (parallel_moves == nullptr) return;
522   // Find all the loads.
523   for (MoveOperands* move : *parallel_moves) {
524     if (move->IsRedundant()) continue;
525     if (move->source().IsConstant() || IsSlot(move->source())) {
526       loads.push_back(move);
527     }
528   }
529   if (loads.empty()) return;
530   // Group the loads by source, moving the preferred destination to the
531   // beginning of the group.
532   std::sort(loads.begin(), loads.end(), LoadCompare);
533   MoveOperands* group_begin = nullptr;
534   for (MoveOperands* load : loads) {
535     // New group.
536     if (group_begin == nullptr ||
537         !load->source().EqualsCanonicalized(group_begin->source())) {
538       group_begin = load;
539       continue;
540     }
541     // Nothing to be gained from splitting here.
542     if (IsSlot(group_begin->destination())) continue;
543     // Insert new move into slot 1.
544     ParallelMove* slot_1 = instr->GetOrCreateParallelMove(
545         static_cast<Instruction::GapPosition>(1), code_zone());
546     slot_1->AddMove(group_begin->destination(), load->destination());
547     load->Eliminate();
548   }
549   loads.clear();
550 }
551 
552 }  // namespace compiler
553 }  // namespace internal
554 }  // namespace v8
555