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/move-optimizer.h" 6 7 namespace v8 { 8 namespace internal { 9 namespace compiler { 10 11 namespace { 12 13 typedef std::pair<InstructionOperand, InstructionOperand> MoveKey; 14 15 struct MoveKeyCompare { 16 bool operator()(const MoveKey& a, const MoveKey& b) const { 17 if (a.first.EqualsCanonicalized(b.first)) { 18 return a.second.CompareCanonicalized(b.second); 19 } 20 return a.first.CompareCanonicalized(b.first); 21 } 22 }; 23 24 struct OperandCompare { 25 bool operator()(const InstructionOperand& a, 26 const InstructionOperand& b) const { 27 return a.CompareCanonicalized(b); 28 } 29 }; 30 31 typedef ZoneMap<MoveKey, unsigned, MoveKeyCompare> MoveMap; 32 typedef ZoneSet<InstructionOperand, CompareOperandModuloType> OperandSet; 33 34 35 bool GapsCanMoveOver(Instruction* instr, Zone* zone) { 36 if (instr->IsNop()) return true; 37 if (instr->ClobbersTemps() || instr->ClobbersRegisters() || 38 instr->ClobbersDoubleRegisters()) { 39 return false; 40 } 41 if (instr->arch_opcode() != ArchOpcode::kArchNop) return false; 42 43 ZoneSet<InstructionOperand, OperandCompare> operands(zone); 44 for (size_t i = 0; i < instr->InputCount(); ++i) { 45 operands.insert(*instr->InputAt(i)); 46 } 47 for (size_t i = 0; i < instr->OutputCount(); ++i) { 48 operands.insert(*instr->OutputAt(i)); 49 } 50 for (size_t i = 0; i < instr->TempCount(); ++i) { 51 operands.insert(*instr->TempAt(i)); 52 } 53 for (int i = Instruction::GapPosition::FIRST_GAP_POSITION; 54 i <= Instruction::GapPosition::LAST_GAP_POSITION; ++i) { 55 ParallelMove* moves = instr->parallel_moves()[i]; 56 if (moves == nullptr) continue; 57 for (MoveOperands* move : *moves) { 58 if (operands.count(move->source()) > 0 || 59 operands.count(move->destination()) > 0) { 60 return false; 61 } 62 } 63 } 64 return true; 65 } 66 67 68 int FindFirstNonEmptySlot(const Instruction* instr) { 69 int i = Instruction::FIRST_GAP_POSITION; 70 for (; i <= Instruction::LAST_GAP_POSITION; i++) { 71 ParallelMove* moves = instr->parallel_moves()[i]; 72 if (moves == nullptr) continue; 73 for (MoveOperands* move : *moves) { 74 if (!move->IsRedundant()) return i; 75 move->Eliminate(); 76 } 77 moves->clear(); // Clear this redundant move. 78 } 79 return i; 80 } 81 82 } // namespace 83 84 85 MoveOptimizer::MoveOptimizer(Zone* local_zone, InstructionSequence* code) 86 : local_zone_(local_zone), 87 code_(code), 88 to_finalize_(local_zone), 89 local_vector_(local_zone) {} 90 91 92 void MoveOptimizer::Run() { 93 for (InstructionBlock* block : code()->instruction_blocks()) { 94 CompressBlock(block); 95 } 96 for (InstructionBlock* block : code()->instruction_blocks()) { 97 if (block->PredecessorCount() <= 1) continue; 98 if (!block->IsDeferred()) { 99 bool has_only_deferred = true; 100 for (RpoNumber& pred_id : block->predecessors()) { 101 if (!code()->InstructionBlockAt(pred_id)->IsDeferred()) { 102 has_only_deferred = false; 103 break; 104 } 105 } 106 // This would pull down common moves. If the moves occur in deferred 107 // blocks, and the closest common successor is not deferred, we lose the 108 // optimization of just spilling/filling in deferred blocks, when the 109 // current block is not deferred. 110 if (has_only_deferred) continue; 111 } 112 OptimizeMerge(block); 113 } 114 for (Instruction* gap : to_finalize_) { 115 FinalizeMoves(gap); 116 } 117 } 118 119 120 void MoveOptimizer::CompressMoves(ParallelMove* left, ParallelMove* right) { 121 if (right == nullptr) return; 122 123 MoveOpVector& eliminated = local_vector(); 124 DCHECK(eliminated.empty()); 125 126 if (!left->empty()) { 127 // Modify the right moves in place and collect moves that will be killed by 128 // merging the two gaps. 129 for (MoveOperands* move : *right) { 130 if (move->IsRedundant()) continue; 131 MoveOperands* to_eliminate = left->PrepareInsertAfter(move); 132 if (to_eliminate != nullptr) eliminated.push_back(to_eliminate); 133 } 134 // Eliminate dead moves. 135 for (MoveOperands* to_eliminate : eliminated) { 136 to_eliminate->Eliminate(); 137 } 138 eliminated.clear(); 139 } 140 // Add all possibly modified moves from right side. 141 for (MoveOperands* move : *right) { 142 if (move->IsRedundant()) continue; 143 left->push_back(move); 144 } 145 // Nuke right. 146 right->clear(); 147 DCHECK(eliminated.empty()); 148 } 149 150 151 // Smash all consecutive moves into the left most move slot and accumulate them 152 // as much as possible across instructions. 153 void MoveOptimizer::CompressBlock(InstructionBlock* block) { 154 Instruction* prev_instr = nullptr; 155 for (int index = block->code_start(); index < block->code_end(); ++index) { 156 Instruction* instr = code()->instructions()[index]; 157 int i = FindFirstNonEmptySlot(instr); 158 bool has_moves = i <= Instruction::LAST_GAP_POSITION; 159 160 if (i == Instruction::LAST_GAP_POSITION) { 161 std::swap(instr->parallel_moves()[Instruction::FIRST_GAP_POSITION], 162 instr->parallel_moves()[Instruction::LAST_GAP_POSITION]); 163 } else if (i == Instruction::FIRST_GAP_POSITION) { 164 CompressMoves(instr->parallel_moves()[Instruction::FIRST_GAP_POSITION], 165 instr->parallel_moves()[Instruction::LAST_GAP_POSITION]); 166 } 167 // We either have no moves, or, after swapping or compressing, we have 168 // all the moves in the first gap position, and none in the second/end gap 169 // position. 170 ParallelMove* first = 171 instr->parallel_moves()[Instruction::FIRST_GAP_POSITION]; 172 ParallelMove* last = 173 instr->parallel_moves()[Instruction::LAST_GAP_POSITION]; 174 USE(last); 175 176 DCHECK(!has_moves || 177 (first != nullptr && (last == nullptr || last->empty()))); 178 179 if (prev_instr != nullptr) { 180 if (has_moves) { 181 // Smash first into prev_instr, killing left. 182 ParallelMove* pred_moves = prev_instr->parallel_moves()[0]; 183 CompressMoves(pred_moves, first); 184 } 185 // Slide prev_instr down so we always know where to look for it. 186 std::swap(prev_instr->parallel_moves()[0], instr->parallel_moves()[0]); 187 } 188 189 prev_instr = instr->parallel_moves()[0] == nullptr ? nullptr : instr; 190 if (GapsCanMoveOver(instr, local_zone())) continue; 191 if (prev_instr != nullptr) { 192 to_finalize_.push_back(prev_instr); 193 prev_instr = nullptr; 194 } 195 } 196 if (prev_instr != nullptr) { 197 to_finalize_.push_back(prev_instr); 198 } 199 } 200 201 202 const Instruction* MoveOptimizer::LastInstruction( 203 const InstructionBlock* block) const { 204 return code()->instructions()[block->last_instruction_index()]; 205 } 206 207 208 void MoveOptimizer::OptimizeMerge(InstructionBlock* block) { 209 DCHECK(block->PredecessorCount() > 1); 210 // Ensure that the last instruction in all incoming blocks don't contain 211 // things that would prevent moving gap moves across them. 212 for (RpoNumber& pred_index : block->predecessors()) { 213 const InstructionBlock* pred = code()->InstructionBlockAt(pred_index); 214 const Instruction* last_instr = 215 code()->instructions()[pred->last_instruction_index()]; 216 if (last_instr->IsCall()) return; 217 if (last_instr->TempCount() != 0) return; 218 if (last_instr->OutputCount() != 0) return; 219 for (size_t i = 0; i < last_instr->InputCount(); ++i) { 220 const InstructionOperand* op = last_instr->InputAt(i); 221 if (!op->IsConstant() && !op->IsImmediate()) return; 222 } 223 } 224 // TODO(dcarney): pass a ZonePool down for this? 225 MoveMap move_map(local_zone()); 226 size_t correct_counts = 0; 227 // Accumulate set of shared moves. 228 for (RpoNumber& pred_index : block->predecessors()) { 229 const InstructionBlock* pred = code()->InstructionBlockAt(pred_index); 230 const Instruction* instr = LastInstruction(pred); 231 if (instr->parallel_moves()[0] == nullptr || 232 instr->parallel_moves()[0]->empty()) { 233 return; 234 } 235 for (const MoveOperands* move : *instr->parallel_moves()[0]) { 236 if (move->IsRedundant()) continue; 237 InstructionOperand src = move->source(); 238 InstructionOperand dst = move->destination(); 239 MoveKey key = {src, dst}; 240 auto res = move_map.insert(std::make_pair(key, 1)); 241 if (!res.second) { 242 res.first->second++; 243 if (res.first->second == block->PredecessorCount()) { 244 correct_counts++; 245 } 246 } 247 } 248 } 249 if (move_map.empty() || correct_counts != move_map.size()) return; 250 // Find insertion point. 251 Instruction* instr = nullptr; 252 for (int i = block->first_instruction_index(); 253 i <= block->last_instruction_index(); ++i) { 254 instr = code()->instructions()[i]; 255 if (!GapsCanMoveOver(instr, local_zone()) || !instr->AreMovesRedundant()) 256 break; 257 } 258 DCHECK_NOT_NULL(instr); 259 bool gap_initialized = true; 260 if (instr->parallel_moves()[0] == nullptr || 261 instr->parallel_moves()[0]->empty()) { 262 to_finalize_.push_back(instr); 263 } else { 264 // Will compress after insertion. 265 gap_initialized = false; 266 std::swap(instr->parallel_moves()[0], instr->parallel_moves()[1]); 267 } 268 ParallelMove* moves = instr->GetOrCreateParallelMove( 269 static_cast<Instruction::GapPosition>(0), code_zone()); 270 // Delete relevant entries in predecessors and move everything to block. 271 bool first_iteration = true; 272 for (RpoNumber& pred_index : block->predecessors()) { 273 const InstructionBlock* pred = code()->InstructionBlockAt(pred_index); 274 for (MoveOperands* move : *LastInstruction(pred)->parallel_moves()[0]) { 275 if (move->IsRedundant()) continue; 276 MoveKey key = {move->source(), move->destination()}; 277 auto it = move_map.find(key); 278 USE(it); 279 DCHECK(it != move_map.end()); 280 if (first_iteration) { 281 moves->AddMove(move->source(), move->destination()); 282 } 283 move->Eliminate(); 284 } 285 first_iteration = false; 286 } 287 // Compress. 288 if (!gap_initialized) { 289 CompressMoves(instr->parallel_moves()[0], instr->parallel_moves()[1]); 290 } 291 } 292 293 294 namespace { 295 296 bool IsSlot(const InstructionOperand& op) { 297 return op.IsStackSlot() || op.IsDoubleStackSlot(); 298 } 299 300 301 bool LoadCompare(const MoveOperands* a, const MoveOperands* b) { 302 if (!a->source().EqualsCanonicalized(b->source())) { 303 return a->source().CompareCanonicalized(b->source()); 304 } 305 if (IsSlot(a->destination()) && !IsSlot(b->destination())) return false; 306 if (!IsSlot(a->destination()) && IsSlot(b->destination())) return true; 307 return a->destination().CompareCanonicalized(b->destination()); 308 } 309 310 } // namespace 311 312 313 // Split multiple loads of the same constant or stack slot off into the second 314 // slot and keep remaining moves in the first slot. 315 void MoveOptimizer::FinalizeMoves(Instruction* instr) { 316 MoveOpVector& loads = local_vector(); 317 DCHECK(loads.empty()); 318 319 // Find all the loads. 320 for (MoveOperands* move : *instr->parallel_moves()[0]) { 321 if (move->IsRedundant()) continue; 322 if (move->source().IsConstant() || IsSlot(move->source())) { 323 loads.push_back(move); 324 } 325 } 326 if (loads.empty()) return; 327 // Group the loads by source, moving the preferred destination to the 328 // beginning of the group. 329 std::sort(loads.begin(), loads.end(), LoadCompare); 330 MoveOperands* group_begin = nullptr; 331 for (MoveOperands* load : loads) { 332 // New group. 333 if (group_begin == nullptr || 334 !load->source().EqualsCanonicalized(group_begin->source())) { 335 group_begin = load; 336 continue; 337 } 338 // Nothing to be gained from splitting here. 339 if (IsSlot(group_begin->destination())) continue; 340 // Insert new move into slot 1. 341 ParallelMove* slot_1 = instr->GetOrCreateParallelMove( 342 static_cast<Instruction::GapPosition>(1), code_zone()); 343 slot_1->AddMove(group_begin->destination(), load->destination()); 344 load->Eliminate(); 345 } 346 loads.clear(); 347 } 348 349 } // namespace compiler 350 } // namespace internal 351 } // namespace v8 352