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