summaryrefslogtreecommitdiff
path: root/deps/v8/src/compiler/move-optimizer.cc
blob: 82f4b6327664e3c6a18550f35b751c87a147e3ed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
// 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.

#include "src/compiler/move-optimizer.h"

namespace v8 {
namespace internal {
namespace compiler {

namespace {

struct MoveKey {
  InstructionOperand source;
  InstructionOperand destination;
};

struct MoveKeyCompare {
  bool operator()(const MoveKey& a, const MoveKey& b) const {
    if (a.source.EqualsCanonicalized(b.source)) {
      return a.destination.CompareCanonicalized(b.destination);
    }
    return a.source.CompareCanonicalized(b.source);
  }
};

typedef ZoneMap<MoveKey, unsigned, MoveKeyCompare> MoveMap;

class OperandSet {
 public:
  explicit OperandSet(ZoneVector<InstructionOperand>* buffer)
      : set_(buffer), fp_reps_(0) {
    buffer->clear();
  }

  void InsertOp(const InstructionOperand& op) {
    set_->push_back(op);

    if (!kSimpleFPAliasing && op.IsFPRegister())
      fp_reps_ |= RepBit(LocationOperand::cast(op).representation());
  }

  bool Contains(const InstructionOperand& op) const {
    for (const InstructionOperand& elem : *set_) {
      if (elem.EqualsCanonicalized(op)) return true;
    }
    return false;
  }

  bool ContainsOpOrAlias(const InstructionOperand& op) const {
    if (Contains(op)) return true;

    if (!kSimpleFPAliasing && op.IsFPRegister()) {
      // Platforms where FP registers have complex aliasing need extra checks.
      const LocationOperand& loc = LocationOperand::cast(op);
      MachineRepresentation rep = loc.representation();
      // If haven't encountered mixed rep FP registers, skip the extra checks.
      if (!HasMixedFPReps(fp_reps_ | RepBit(rep))) return false;

      // Check register against aliasing registers of other FP representations.
      MachineRepresentation other_rep1, other_rep2;
      switch (rep) {
        case MachineRepresentation::kFloat32:
          other_rep1 = MachineRepresentation::kFloat64;
          other_rep2 = MachineRepresentation::kSimd128;
          break;
        case MachineRepresentation::kFloat64:
          other_rep1 = MachineRepresentation::kFloat32;
          other_rep2 = MachineRepresentation::kSimd128;
          break;
        case MachineRepresentation::kSimd128:
          other_rep1 = MachineRepresentation::kFloat32;
          other_rep2 = MachineRepresentation::kFloat64;
          break;
        default:
          UNREACHABLE();
          break;
      }
      const RegisterConfiguration* config = RegisterConfiguration::Default();
      int base = -1;
      int aliases =
          config->GetAliases(rep, loc.register_code(), other_rep1, &base);
      DCHECK(aliases > 0 || (aliases == 0 && base == -1));
      while (aliases--) {
        if (Contains(AllocatedOperand(LocationOperand::REGISTER, other_rep1,
                                      base + aliases))) {
          return true;
        }
      }
      aliases = config->GetAliases(rep, loc.register_code(), other_rep2, &base);
      DCHECK(aliases > 0 || (aliases == 0 && base == -1));
      while (aliases--) {
        if (Contains(AllocatedOperand(LocationOperand::REGISTER, other_rep2,
                                      base + aliases))) {
          return true;
        }
      }
    }
    return false;
  }

 private:
  static int RepBit(MachineRepresentation rep) {
    return 1 << static_cast<int>(rep);
  }

  static bool HasMixedFPReps(int reps) {
    return reps && !base::bits::IsPowerOfTwo(reps);
  }

  ZoneVector<InstructionOperand>* set_;
  int fp_reps_;
};

int FindFirstNonEmptySlot(const Instruction* instr) {
  int i = Instruction::FIRST_GAP_POSITION;
  for (; i <= Instruction::LAST_GAP_POSITION; i++) {
    ParallelMove* moves = instr->parallel_moves()[i];
    if (moves == nullptr) continue;
    for (MoveOperands* move : *moves) {
      if (!move->IsRedundant()) return i;
      move->Eliminate();
    }
    moves->clear();  // Clear this redundant move.
  }
  return i;
}

}  // namespace

MoveOptimizer::MoveOptimizer(Zone* local_zone, InstructionSequence* code)
    : local_zone_(local_zone),
      code_(code),
      local_vector_(local_zone),
      operand_buffer1(local_zone),
      operand_buffer2(local_zone) {}

void MoveOptimizer::Run() {
  for (Instruction* instruction : code()->instructions()) {
    CompressGaps(instruction);
  }
  for (InstructionBlock* block : code()->instruction_blocks()) {
    CompressBlock(block);
  }
  for (InstructionBlock* block : code()->instruction_blocks()) {
    if (block->PredecessorCount() <= 1) continue;
    if (!block->IsDeferred()) {
      bool has_only_deferred = true;
      for (RpoNumber& pred_id : block->predecessors()) {
        if (!code()->InstructionBlockAt(pred_id)->IsDeferred()) {
          has_only_deferred = false;
          break;
        }
      }
      // This would pull down common moves. If the moves occur in deferred
      // blocks, and the closest common successor is not deferred, we lose the
      // optimization of just spilling/filling in deferred blocks, when the
      // current block is not deferred.
      if (has_only_deferred) continue;
    }
    OptimizeMerge(block);
  }
  for (Instruction* gap : code()->instructions()) {
    FinalizeMoves(gap);
  }
}

void MoveOptimizer::RemoveClobberedDestinations(Instruction* instruction) {
  if (instruction->IsCall()) return;
  ParallelMove* moves = instruction->parallel_moves()[0];
  if (moves == nullptr) return;

  DCHECK(instruction->parallel_moves()[1] == nullptr ||
         instruction->parallel_moves()[1]->empty());

  OperandSet outputs(&operand_buffer1);
  OperandSet inputs(&operand_buffer2);

  // Outputs and temps are treated together as potentially clobbering a
  // destination operand.
  for (size_t i = 0; i < instruction->OutputCount(); ++i) {
    outputs.InsertOp(*instruction->OutputAt(i));
  }
  for (size_t i = 0; i < instruction->TempCount(); ++i) {
    outputs.InsertOp(*instruction->TempAt(i));
  }

  // Input operands block elisions.
  for (size_t i = 0; i < instruction->InputCount(); ++i) {
    inputs.InsertOp(*instruction->InputAt(i));
  }

  // Elide moves made redundant by the instruction.
  for (MoveOperands* move : *moves) {
    if (outputs.ContainsOpOrAlias(move->destination()) &&
        !inputs.ContainsOpOrAlias(move->destination())) {
      move->Eliminate();
    }
  }

  // The ret instruction makes any assignment before it unnecessary, except for
  // the one for its input.
  if (instruction->IsRet() || instruction->IsTailCall()) {
    for (MoveOperands* move : *moves) {
      if (!inputs.ContainsOpOrAlias(move->destination())) {
        move->Eliminate();
      }
    }
  }
}

void MoveOptimizer::MigrateMoves(Instruction* to, Instruction* from) {
  if (from->IsCall()) return;

  ParallelMove* from_moves = from->parallel_moves()[0];
  if (from_moves == nullptr || from_moves->empty()) return;

  OperandSet dst_cant_be(&operand_buffer1);
  OperandSet src_cant_be(&operand_buffer2);

  // If an operand is an input to the instruction, we cannot move assignments
  // where it appears on the LHS.
  for (size_t i = 0; i < from->InputCount(); ++i) {
    dst_cant_be.InsertOp(*from->InputAt(i));
  }
  // If an operand is output to the instruction, we cannot move assignments
  // where it appears on the RHS, because we would lose its value before the
  // instruction.
  // Same for temp operands.
  // The output can't appear on the LHS because we performed
  // RemoveClobberedDestinations for the "from" instruction.
  for (size_t i = 0; i < from->OutputCount(); ++i) {
    src_cant_be.InsertOp(*from->OutputAt(i));
  }
  for (size_t i = 0; i < from->TempCount(); ++i) {
    src_cant_be.InsertOp(*from->TempAt(i));
  }
  for (MoveOperands* move : *from_moves) {
    if (move->IsRedundant()) continue;
    // Assume dest has a value "V". If we have a "dest = y" move, then we can't
    // move "z = dest", because z would become y rather than "V".
    // We assume CompressMoves has happened before this, which means we don't
    // have more than one assignment to dest.
    src_cant_be.InsertOp(move->destination());
  }

  ZoneSet<MoveKey, MoveKeyCompare> move_candidates(local_zone());
  // We start with all the moves that don't have conflicting source or
  // destination operands are eligible for being moved down.
  for (MoveOperands* move : *from_moves) {
    if (move->IsRedundant()) continue;
    if (!dst_cant_be.ContainsOpOrAlias(move->destination())) {
      MoveKey key = {move->source(), move->destination()};
      move_candidates.insert(key);
    }
  }
  if (move_candidates.empty()) return;

  // Stabilize the candidate set.
  bool changed = false;
  do {
    changed = false;
    for (auto iter = move_candidates.begin(); iter != move_candidates.end();) {
      auto current = iter;
      ++iter;
      InstructionOperand src = current->source;
      if (src_cant_be.ContainsOpOrAlias(src)) {
        src_cant_be.InsertOp(current->destination);
        move_candidates.erase(current);
        changed = true;
      }
    }
  } while (changed);

  ParallelMove to_move(local_zone());
  for (MoveOperands* move : *from_moves) {
    if (move->IsRedundant()) continue;
    MoveKey key = {move->source(), move->destination()};
    if (move_candidates.find(key) != move_candidates.end()) {
      to_move.AddMove(move->source(), move->destination(), code_zone());
      move->Eliminate();
    }
  }
  if (to_move.empty()) return;

  ParallelMove* dest =
      to->GetOrCreateParallelMove(Instruction::GapPosition::START, code_zone());

  CompressMoves(&to_move, dest);
  DCHECK(dest->empty());
  for (MoveOperands* m : to_move) {
    dest->push_back(m);
  }
}

void MoveOptimizer::CompressMoves(ParallelMove* left, MoveOpVector* right) {
  if (right == nullptr) return;

  MoveOpVector& eliminated = local_vector();
  DCHECK(eliminated.empty());

  if (!left->empty()) {
    // Modify the right moves in place and collect moves that will be killed by
    // merging the two gaps.
    for (MoveOperands* move : *right) {
      if (move->IsRedundant()) continue;
      left->PrepareInsertAfter(move, &eliminated);
    }
    // Eliminate dead moves.
    for (MoveOperands* to_eliminate : eliminated) {
      to_eliminate->Eliminate();
    }
    eliminated.clear();
  }
  // Add all possibly modified moves from right side.
  for (MoveOperands* move : *right) {
    if (move->IsRedundant()) continue;
    left->push_back(move);
  }
  // Nuke right.
  right->clear();
  DCHECK(eliminated.empty());
}

void MoveOptimizer::CompressGaps(Instruction* instruction) {
  int i = FindFirstNonEmptySlot(instruction);
  bool has_moves = i <= Instruction::LAST_GAP_POSITION;
  USE(has_moves);

  if (i == Instruction::LAST_GAP_POSITION) {
    std::swap(instruction->parallel_moves()[Instruction::FIRST_GAP_POSITION],
              instruction->parallel_moves()[Instruction::LAST_GAP_POSITION]);
  } else if (i == Instruction::FIRST_GAP_POSITION) {
    CompressMoves(
        instruction->parallel_moves()[Instruction::FIRST_GAP_POSITION],
        instruction->parallel_moves()[Instruction::LAST_GAP_POSITION]);
  }
  // We either have no moves, or, after swapping or compressing, we have
  // all the moves in the first gap position, and none in the second/end gap
  // position.
  ParallelMove* first =
      instruction->parallel_moves()[Instruction::FIRST_GAP_POSITION];
  ParallelMove* last =
      instruction->parallel_moves()[Instruction::LAST_GAP_POSITION];
  USE(first);
  USE(last);

  DCHECK(!has_moves ||
         (first != nullptr && (last == nullptr || last->empty())));
}

void MoveOptimizer::CompressBlock(InstructionBlock* block) {
  int first_instr_index = block->first_instruction_index();
  int last_instr_index = block->last_instruction_index();

  // Start by removing gap assignments where the output of the subsequent
  // instruction appears on LHS, as long as they are not needed by its input.
  Instruction* prev_instr = code()->instructions()[first_instr_index];
  RemoveClobberedDestinations(prev_instr);

  for (int index = first_instr_index + 1; index <= last_instr_index; ++index) {
    Instruction* instr = code()->instructions()[index];
    // Migrate to the gap of prev_instr eligible moves from instr.
    MigrateMoves(instr, prev_instr);
    // Remove gap assignments clobbered by instr's output.
    RemoveClobberedDestinations(instr);
    prev_instr = instr;
  }
}


const Instruction* MoveOptimizer::LastInstruction(
    const InstructionBlock* block) const {
  return code()->instructions()[block->last_instruction_index()];
}


void MoveOptimizer::OptimizeMerge(InstructionBlock* block) {
  DCHECK_LT(1, block->PredecessorCount());
  // Ensure that the last instruction in all incoming blocks don't contain
  // things that would prevent moving gap moves across them.
  for (RpoNumber& pred_index : block->predecessors()) {
    const InstructionBlock* pred = code()->InstructionBlockAt(pred_index);

    // If the predecessor has more than one successor, we shouldn't attempt to
    // move down to this block (one of the successors) any of the gap moves,
    // because their effect may be necessary to the other successors.
    if (pred->SuccessorCount() > 1) return;

    const Instruction* last_instr =
        code()->instructions()[pred->last_instruction_index()];
    if (last_instr->IsCall()) return;
    if (last_instr->TempCount() != 0) return;
    if (last_instr->OutputCount() != 0) return;
    for (size_t i = 0; i < last_instr->InputCount(); ++i) {
      const InstructionOperand* op = last_instr->InputAt(i);
      if (!op->IsConstant() && !op->IsImmediate()) return;
    }
  }
  // TODO(dcarney): pass a ZoneStats down for this?
  MoveMap move_map(local_zone());
  size_t correct_counts = 0;
  // Accumulate set of shared moves.
  for (RpoNumber& pred_index : block->predecessors()) {
    const InstructionBlock* pred = code()->InstructionBlockAt(pred_index);
    const Instruction* instr = LastInstruction(pred);
    if (instr->parallel_moves()[0] == nullptr ||
        instr->parallel_moves()[0]->empty()) {
      return;
    }
    for (const MoveOperands* move : *instr->parallel_moves()[0]) {
      if (move->IsRedundant()) continue;
      InstructionOperand src = move->source();
      InstructionOperand dst = move->destination();
      MoveKey key = {src, dst};
      auto res = move_map.insert(std::make_pair(key, 1));
      if (!res.second) {
        res.first->second++;
        if (res.first->second == block->PredecessorCount()) {
          correct_counts++;
        }
      }
    }
  }
  if (move_map.empty() || correct_counts == 0) return;

  // Find insertion point.
  Instruction* instr = code()->instructions()[block->first_instruction_index()];

  if (correct_counts != move_map.size()) {
    // Moves that are unique to each predecessor won't be pushed to the common
    // successor.
    OperandSet conflicting_srcs(&operand_buffer1);
    for (auto iter = move_map.begin(), end = move_map.end(); iter != end;) {
      auto current = iter;
      ++iter;
      if (current->second != block->PredecessorCount()) {
        InstructionOperand dest = current->first.destination;
        // Not all the moves in all the gaps are the same. Maybe some are. If
        // there are such moves, we could move them, but the destination of the
        // moves staying behind can't appear as a source of a common move,
        // because the move staying behind will clobber this destination.
        conflicting_srcs.InsertOp(dest);
        move_map.erase(current);
      }
    }

    bool changed = false;
    do {
      // If a common move can't be pushed to the common successor, then its
      // destination also can't appear as source to any move being pushed.
      changed = false;
      for (auto iter = move_map.begin(), end = move_map.end(); iter != end;) {
        auto current = iter;
        ++iter;
        DCHECK_EQ(block->PredecessorCount(), current->second);
        if (conflicting_srcs.ContainsOpOrAlias(current->first.source)) {
          conflicting_srcs.InsertOp(current->first.destination);
          move_map.erase(current);
          changed = true;
        }
      }
    } while (changed);
  }

  if (move_map.empty()) return;

  DCHECK_NOT_NULL(instr);
  bool gap_initialized = true;
  if (instr->parallel_moves()[0] != nullptr &&
      !instr->parallel_moves()[0]->empty()) {
    // Will compress after insertion.
    gap_initialized = false;
    std::swap(instr->parallel_moves()[0], instr->parallel_moves()[1]);
  }
  ParallelMove* moves = instr->GetOrCreateParallelMove(
      static_cast<Instruction::GapPosition>(0), code_zone());
  // Delete relevant entries in predecessors and move everything to block.
  bool first_iteration = true;
  for (RpoNumber& pred_index : block->predecessors()) {
    const InstructionBlock* pred = code()->InstructionBlockAt(pred_index);
    for (MoveOperands* move : *LastInstruction(pred)->parallel_moves()[0]) {
      if (move->IsRedundant()) continue;
      MoveKey key = {move->source(), move->destination()};
      auto it = move_map.find(key);
      if (it != move_map.end()) {
        if (first_iteration) {
          moves->AddMove(move->source(), move->destination());
        }
        move->Eliminate();
      }
    }
    first_iteration = false;
  }
  // Compress.
  if (!gap_initialized) {
    CompressMoves(instr->parallel_moves()[0], instr->parallel_moves()[1]);
  }
  CompressBlock(block);
}


namespace {

bool IsSlot(const InstructionOperand& op) {
  return op.IsStackSlot() || op.IsFPStackSlot();
}


bool LoadCompare(const MoveOperands* a, const MoveOperands* b) {
  if (!a->source().EqualsCanonicalized(b->source())) {
    return a->source().CompareCanonicalized(b->source());
  }
  if (IsSlot(a->destination()) && !IsSlot(b->destination())) return false;
  if (!IsSlot(a->destination()) && IsSlot(b->destination())) return true;
  return a->destination().CompareCanonicalized(b->destination());
}

}  // namespace


// Split multiple loads of the same constant or stack slot off into the second
// slot and keep remaining moves in the first slot.
void MoveOptimizer::FinalizeMoves(Instruction* instr) {
  MoveOpVector& loads = local_vector();
  DCHECK(loads.empty());

  ParallelMove* parallel_moves = instr->parallel_moves()[0];
  if (parallel_moves == nullptr) return;
  // Find all the loads.
  for (MoveOperands* move : *parallel_moves) {
    if (move->IsRedundant()) continue;
    if (move->source().IsConstant() || IsSlot(move->source())) {
      loads.push_back(move);
    }
  }
  if (loads.empty()) return;
  // Group the loads by source, moving the preferred destination to the
  // beginning of the group.
  std::sort(loads.begin(), loads.end(), LoadCompare);
  MoveOperands* group_begin = nullptr;
  for (MoveOperands* load : loads) {
    // New group.
    if (group_begin == nullptr ||
        !load->source().EqualsCanonicalized(group_begin->source())) {
      group_begin = load;
      continue;
    }
    // Nothing to be gained from splitting here.
    if (IsSlot(group_begin->destination())) continue;
    // Insert new move into slot 1.
    ParallelMove* slot_1 = instr->GetOrCreateParallelMove(
        static_cast<Instruction::GapPosition>(1), code_zone());
    slot_1->AddMove(group_begin->destination(), load->destination());
    load->Eliminate();
  }
  loads.clear();
}

}  // namespace compiler
}  // namespace internal
}  // namespace v8