MoveEmitter-x86-shared.cpp (17840B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "jit/x86-shared/MoveEmitter-x86-shared.h" 8 9 #include "jit/MacroAssembler-inl.h" 10 11 using namespace js; 12 using namespace js::jit; 13 14 using mozilla::Maybe; 15 16 MoveEmitterX86::MoveEmitterX86(MacroAssembler& masm) 17 : inCycle_(false), masm(masm), pushedAtCycle_(-1) { 18 pushedAtStart_ = masm.framePushed(); 19 } 20 21 // Examine the cycle in moves starting at position i. Determine if it's a 22 // simple cycle consisting of all register-to-register moves in a single class, 23 // and whether it can be implemented entirely by swaps. 24 size_t MoveEmitterX86::characterizeCycle(const MoveResolver& moves, size_t i, 25 bool* allGeneralRegs, 26 bool* allFloatRegs) { 27 size_t swapCount = 0; 28 29 for (size_t j = i;; j++) { 30 const MoveOp& move = moves.getMove(j); 31 32 // If it isn't a cycle of registers of the same kind, we won't be able 33 // to optimize it. 34 if (!move.to().isGeneralReg()) { 35 *allGeneralRegs = false; 36 } 37 if (!move.to().isFloatReg()) { 38 *allFloatRegs = false; 39 } 40 if (!*allGeneralRegs && !*allFloatRegs) { 41 return -1; 42 } 43 44 // Stop iterating when we see the last one. 45 if (j != i && move.isCycleEnd()) { 46 break; 47 } 48 49 // Check that this move is actually part of the cycle. This is 50 // over-conservative when there are multiple reads from the same source, 51 // but that's expected to be rare. 52 if (move.from() != moves.getMove(j + 1).to()) { 53 *allGeneralRegs = false; 54 *allFloatRegs = false; 55 return -1; 56 } 57 58 swapCount++; 59 } 60 61 // Check that the last move cycles back to the first move. 62 const MoveOp& move = moves.getMove(i + swapCount); 63 if (move.from() != moves.getMove(i).to()) { 64 *allGeneralRegs = false; 65 *allFloatRegs = false; 66 return -1; 67 } 68 69 return swapCount; 70 } 71 72 // If we can emit optimized code for the cycle in moves starting at position i, 73 // do so, and return true. 74 bool MoveEmitterX86::maybeEmitOptimizedCycle(const MoveResolver& moves, 75 size_t i, bool allGeneralRegs, 76 bool allFloatRegs, 77 size_t swapCount) { 78 MOZ_ASSERT(swapCount > 0); 79 80 if (allGeneralRegs && swapCount <= 2) { 81 // Use x86's swap-integer-registers instruction if we only have a few 82 // swaps. (x86 also has a swap between registers and memory but it's 83 // slow.) 84 for (size_t k = 0; k < swapCount; k++) { 85 masm.xchg(moves.getMove(i + k).to().reg(), 86 moves.getMove(i + k + 1).to().reg()); 87 } 88 return true; 89 } 90 91 if (allFloatRegs) { 92 // There's no xchg for xmm registers, but we can use the scratch register. 93 // |characterizeCycle| ensures this is a simple cycle where each move's 94 // source is the next move's target (wrapping around for the last move): 95 // 96 // A => B 97 // C => A 98 // B => C 99 // 100 // We break the cycle by using the scratch register: 101 // 102 // B => scratch 103 // A => B 104 // C => A 105 // scratch => C 106 ScratchSimd128Scope scratch(masm); 107 for (size_t k = 0; k <= swapCount; k++) { 108 FloatRegister from = moves.getMove(i + k).from().floatReg(); 109 FloatRegister to = moves.getMove(i + k).to().floatReg(); 110 MOZ_ASSERT(from != scratch && to != scratch); 111 if (k == 0) { 112 MOZ_ASSERT(from == moves.getMove(i + 1).to().floatReg()); 113 masm.vmovapd(to, scratch); 114 masm.vmovapd(from, to); 115 } else if (k == swapCount) { 116 MOZ_ASSERT(from == moves.getMove(i).to().floatReg()); 117 masm.vmovapd(scratch, to); 118 } else { 119 MOZ_ASSERT(from == moves.getMove(i + k + 1).to().floatReg()); 120 masm.vmovapd(from, to); 121 } 122 } 123 return true; 124 } 125 126 return false; 127 } 128 129 void MoveEmitterX86::emit(const MoveResolver& moves) { 130 #if defined(JS_CODEGEN_X86) && defined(DEBUG) 131 // Clobber any scratch register we have, to make regalloc bugs more visible. 132 if (scratchRegister_.isSome()) { 133 masm.mov(ImmWord(0xdeadbeef), scratchRegister_.value()); 134 } 135 #endif 136 137 for (size_t i = 0; i < moves.numMoves(); i++) { 138 #if defined(JS_CODEGEN_X86) && defined(DEBUG) 139 if (!scratchRegister_.isSome()) { 140 Maybe<Register> reg = findScratchRegister(moves, i); 141 if (reg.isSome()) { 142 masm.mov(ImmWord(0xdeadbeef), reg.value()); 143 } 144 } 145 #endif 146 147 const MoveOp& move = moves.getMove(i); 148 const MoveOperand& from = move.from(); 149 const MoveOperand& to = move.to(); 150 151 if (move.isCycleEnd()) { 152 MOZ_ASSERT(inCycle_); 153 completeCycle(to, move.type()); 154 inCycle_ = false; 155 continue; 156 } 157 158 if (move.isCycleBegin()) { 159 MOZ_ASSERT(!inCycle_); 160 161 // Characterize the cycle. 162 bool allGeneralRegs = true, allFloatRegs = true; 163 size_t swapCount = 164 characterizeCycle(moves, i, &allGeneralRegs, &allFloatRegs); 165 166 // Attempt to optimize it to avoid using the stack. 167 if (maybeEmitOptimizedCycle(moves, i, allGeneralRegs, allFloatRegs, 168 swapCount)) { 169 i += swapCount; 170 continue; 171 } 172 173 // Otherwise use the stack. 174 breakCycle(to, move.endCycleType()); 175 inCycle_ = true; 176 } 177 178 // A normal move which is not part of a cycle. 179 switch (move.type()) { 180 case MoveOp::FLOAT32: 181 emitFloat32Move(from, to); 182 break; 183 case MoveOp::DOUBLE: 184 emitDoubleMove(from, to); 185 break; 186 case MoveOp::INT32: 187 emitInt32Move(from, to, moves, i); 188 break; 189 case MoveOp::GENERAL: 190 emitGeneralMove(from, to, moves, i); 191 break; 192 case MoveOp::SIMD128: 193 emitSimd128Move(from, to); 194 break; 195 default: 196 MOZ_CRASH("Unexpected move type"); 197 } 198 } 199 } 200 201 MoveEmitterX86::~MoveEmitterX86() { assertDone(); } 202 203 Address MoveEmitterX86::cycleSlot() { 204 if (pushedAtCycle_ == -1) { 205 // Reserve stack for cycle resolution 206 static_assert(SpillSlotSize == 16); 207 masm.reserveStack(SpillSlotSize); 208 pushedAtCycle_ = masm.framePushed(); 209 } 210 211 return Address(StackPointer, masm.framePushed() - pushedAtCycle_); 212 } 213 214 Address MoveEmitterX86::toAddress(const MoveOperand& operand) const { 215 if (operand.base() != StackPointer) { 216 return Address(operand.base(), operand.disp()); 217 } 218 219 MOZ_ASSERT(operand.disp() >= 0); 220 221 // Otherwise, the stack offset may need to be adjusted. 222 return Address(StackPointer, 223 operand.disp() + (masm.framePushed() - pushedAtStart_)); 224 } 225 226 // Warning, do not use the resulting operand with pop instructions, since they 227 // compute the effective destination address after altering the stack pointer. 228 // Use toPopOperand if an Operand is needed for a pop. 229 Operand MoveEmitterX86::toOperand(const MoveOperand& operand) const { 230 if (operand.isMemoryOrEffectiveAddress()) { 231 return Operand(toAddress(operand)); 232 } 233 if (operand.isGeneralReg()) { 234 return Operand(operand.reg()); 235 } 236 237 MOZ_ASSERT(operand.isFloatReg()); 238 return Operand(operand.floatReg()); 239 } 240 241 // This is the same as toOperand except that it computes an Operand suitable for 242 // use in a pop. 243 Operand MoveEmitterX86::toPopOperand(const MoveOperand& operand) const { 244 if (operand.isMemory()) { 245 if (operand.base() != StackPointer) { 246 return Operand(operand.base(), operand.disp()); 247 } 248 249 MOZ_ASSERT(operand.disp() >= 0); 250 251 // Otherwise, the stack offset may need to be adjusted. 252 // Note the adjustment by the stack slot here, to offset for the fact that 253 // pop computes its effective address after incrementing the stack pointer. 254 return Operand( 255 StackPointer, 256 operand.disp() + (masm.framePushed() - sizeof(void*) - pushedAtStart_)); 257 } 258 if (operand.isGeneralReg()) { 259 return Operand(operand.reg()); 260 } 261 262 MOZ_ASSERT(operand.isFloatReg()); 263 return Operand(operand.floatReg()); 264 } 265 266 void MoveEmitterX86::breakCycle(const MoveOperand& to, MoveOp::Type type) { 267 // There is some pattern: 268 // (A -> B) 269 // (B -> A) 270 // 271 // This case handles (A -> B), which we reach first. We save B, then allow 272 // the original move to continue. 273 switch (type) { 274 case MoveOp::SIMD128: 275 if (to.isMemory()) { 276 ScratchSimd128Scope scratch(masm); 277 masm.loadUnalignedSimd128(toAddress(to), scratch); 278 masm.storeUnalignedSimd128(scratch, cycleSlot()); 279 } else { 280 masm.storeUnalignedSimd128(to.floatReg(), cycleSlot()); 281 } 282 break; 283 case MoveOp::FLOAT32: 284 if (to.isMemory()) { 285 ScratchFloat32Scope scratch(masm); 286 masm.loadFloat32(toAddress(to), scratch); 287 masm.storeFloat32(scratch, cycleSlot()); 288 } else { 289 masm.storeFloat32(to.floatReg(), cycleSlot()); 290 } 291 break; 292 case MoveOp::DOUBLE: 293 if (to.isMemory()) { 294 ScratchDoubleScope scratch(masm); 295 masm.loadDouble(toAddress(to), scratch); 296 masm.storeDouble(scratch, cycleSlot()); 297 } else { 298 masm.storeDouble(to.floatReg(), cycleSlot()); 299 } 300 break; 301 case MoveOp::INT32: 302 #ifdef JS_CODEGEN_X64 303 // x64 can't pop to a 32-bit destination, so don't push. 304 if (to.isMemory()) { 305 masm.load32(toAddress(to), ScratchReg); 306 masm.store32(ScratchReg, cycleSlot()); 307 } else { 308 masm.store32(to.reg(), cycleSlot()); 309 } 310 break; 311 #endif 312 case MoveOp::GENERAL: 313 masm.Push(toOperand(to)); 314 break; 315 default: 316 MOZ_CRASH("Unexpected move type"); 317 } 318 } 319 320 void MoveEmitterX86::completeCycle(const MoveOperand& to, MoveOp::Type type) { 321 // There is some pattern: 322 // (A -> B) 323 // (B -> A) 324 // 325 // This case handles (B -> A), which we reach last. We emit a move from the 326 // saved value of B, to A. 327 switch (type) { 328 case MoveOp::SIMD128: 329 MOZ_ASSERT(pushedAtCycle_ != -1); 330 MOZ_ASSERT(pushedAtCycle_ - pushedAtStart_ >= Simd128DataSize); 331 if (to.isMemory()) { 332 ScratchSimd128Scope scratch(masm); 333 masm.loadUnalignedSimd128(cycleSlot(), scratch); 334 masm.storeUnalignedSimd128(scratch, toAddress(to)); 335 } else { 336 masm.loadUnalignedSimd128(cycleSlot(), to.floatReg()); 337 } 338 break; 339 case MoveOp::FLOAT32: 340 MOZ_ASSERT(pushedAtCycle_ != -1); 341 MOZ_ASSERT(pushedAtCycle_ - pushedAtStart_ >= sizeof(float)); 342 if (to.isMemory()) { 343 ScratchFloat32Scope scratch(masm); 344 masm.loadFloat32(cycleSlot(), scratch); 345 masm.storeFloat32(scratch, toAddress(to)); 346 } else { 347 masm.loadFloat32(cycleSlot(), to.floatReg()); 348 } 349 break; 350 case MoveOp::DOUBLE: 351 MOZ_ASSERT(pushedAtCycle_ != -1); 352 MOZ_ASSERT(pushedAtCycle_ - pushedAtStart_ >= sizeof(double)); 353 if (to.isMemory()) { 354 ScratchDoubleScope scratch(masm); 355 masm.loadDouble(cycleSlot(), scratch); 356 masm.storeDouble(scratch, toAddress(to)); 357 } else { 358 masm.loadDouble(cycleSlot(), to.floatReg()); 359 } 360 break; 361 case MoveOp::INT32: 362 #ifdef JS_CODEGEN_X64 363 MOZ_ASSERT(pushedAtCycle_ != -1); 364 MOZ_ASSERT(pushedAtCycle_ - pushedAtStart_ >= sizeof(int32_t)); 365 // x64 can't pop to a 32-bit destination. 366 if (to.isMemory()) { 367 masm.load32(cycleSlot(), ScratchReg); 368 masm.store32(ScratchReg, toAddress(to)); 369 } else { 370 masm.load32(cycleSlot(), to.reg()); 371 } 372 break; 373 #endif 374 case MoveOp::GENERAL: 375 MOZ_ASSERT(masm.framePushed() - pushedAtStart_ >= sizeof(intptr_t)); 376 masm.Pop(toPopOperand(to)); 377 break; 378 default: 379 MOZ_CRASH("Unexpected move type"); 380 } 381 } 382 383 void MoveEmitterX86::emitInt32Move(const MoveOperand& from, 384 const MoveOperand& to, 385 const MoveResolver& moves, size_t i) { 386 if (from.isGeneralReg()) { 387 masm.move32(from.reg(), toOperand(to)); 388 } else if (to.isGeneralReg()) { 389 MOZ_ASSERT(from.isMemory()); 390 masm.load32(toAddress(from), to.reg()); 391 } else { 392 // Memory to memory gpr move. 393 MOZ_ASSERT(from.isMemory()); 394 Maybe<Register> reg = findScratchRegister(moves, i); 395 if (reg.isSome()) { 396 masm.load32(toAddress(from), reg.value()); 397 masm.move32(reg.value(), toOperand(to)); 398 } else { 399 // No scratch register available; bounce it off the stack. 400 masm.Push(toOperand(from)); 401 masm.Pop(toPopOperand(to)); 402 } 403 } 404 } 405 406 void MoveEmitterX86::emitGeneralMove(const MoveOperand& from, 407 const MoveOperand& to, 408 const MoveResolver& moves, size_t i) { 409 if (from.isGeneralReg()) { 410 masm.mov(from.reg(), toOperand(to)); 411 } else if (to.isGeneralReg()) { 412 MOZ_ASSERT(from.isMemoryOrEffectiveAddress()); 413 if (from.isMemory()) { 414 masm.loadPtr(toAddress(from), to.reg()); 415 } else { 416 masm.lea(toOperand(from), to.reg()); 417 } 418 } else if (from.isMemory()) { 419 // Memory to memory gpr move. 420 Maybe<Register> reg = findScratchRegister(moves, i); 421 if (reg.isSome()) { 422 masm.loadPtr(toAddress(from), reg.value()); 423 masm.mov(reg.value(), toOperand(to)); 424 } else { 425 // No scratch register available; bounce it off the stack. 426 masm.Push(toOperand(from)); 427 masm.Pop(toPopOperand(to)); 428 } 429 } else { 430 // Effective address to memory move. 431 MOZ_ASSERT(from.isEffectiveAddress()); 432 Maybe<Register> reg = findScratchRegister(moves, i); 433 if (reg.isSome()) { 434 masm.lea(toOperand(from), reg.value()); 435 masm.mov(reg.value(), toOperand(to)); 436 } else { 437 // This is tricky without a scratch reg. We can't do an lea. Bounce the 438 // base register off the stack, then add the offset in place. Note that 439 // this clobbers FLAGS! 440 masm.Push(from.base()); 441 masm.Pop(toPopOperand(to)); 442 MOZ_ASSERT(to.isMemoryOrEffectiveAddress()); 443 masm.addPtr(Imm32(from.disp()), toAddress(to)); 444 } 445 } 446 } 447 448 void MoveEmitterX86::emitFloat32Move(const MoveOperand& from, 449 const MoveOperand& to) { 450 MOZ_ASSERT_IF(from.isFloatReg(), from.floatReg().isSingle()); 451 MOZ_ASSERT_IF(to.isFloatReg(), to.floatReg().isSingle()); 452 453 if (from.isFloatReg()) { 454 if (to.isFloatReg()) { 455 masm.moveFloat32(from.floatReg(), to.floatReg()); 456 } else { 457 masm.storeFloat32(from.floatReg(), toAddress(to)); 458 } 459 } else if (to.isFloatReg()) { 460 masm.loadFloat32(toAddress(from), to.floatReg()); 461 } else { 462 // Memory to memory move. 463 MOZ_ASSERT(from.isMemory()); 464 ScratchFloat32Scope scratch(masm); 465 masm.loadFloat32(toAddress(from), scratch); 466 masm.storeFloat32(scratch, toAddress(to)); 467 } 468 } 469 470 void MoveEmitterX86::emitDoubleMove(const MoveOperand& from, 471 const MoveOperand& to) { 472 MOZ_ASSERT_IF(from.isFloatReg(), from.floatReg().isDouble()); 473 MOZ_ASSERT_IF(to.isFloatReg(), to.floatReg().isDouble()); 474 475 if (from.isFloatReg()) { 476 if (to.isFloatReg()) { 477 masm.moveDouble(from.floatReg(), to.floatReg()); 478 } else { 479 masm.storeDouble(from.floatReg(), toAddress(to)); 480 } 481 } else if (to.isFloatReg()) { 482 masm.loadDouble(toAddress(from), to.floatReg()); 483 } else { 484 // Memory to memory move. 485 MOZ_ASSERT(from.isMemory()); 486 ScratchDoubleScope scratch(masm); 487 masm.loadDouble(toAddress(from), scratch); 488 masm.storeDouble(scratch, toAddress(to)); 489 } 490 } 491 492 void MoveEmitterX86::emitSimd128Move(const MoveOperand& from, 493 const MoveOperand& to) { 494 MOZ_ASSERT_IF(from.isFloatReg(), from.floatReg().isSimd128()); 495 MOZ_ASSERT_IF(to.isFloatReg(), to.floatReg().isSimd128()); 496 497 if (from.isFloatReg()) { 498 if (to.isFloatReg()) { 499 masm.moveSimd128(from.floatReg(), to.floatReg()); 500 } else { 501 masm.storeUnalignedSimd128(from.floatReg(), toAddress(to)); 502 } 503 } else if (to.isFloatReg()) { 504 masm.loadUnalignedSimd128(toAddress(from), to.floatReg()); 505 } else { 506 // Memory to memory move. 507 MOZ_ASSERT(from.isMemory()); 508 ScratchSimd128Scope scratch(masm); 509 masm.loadUnalignedSimd128(toAddress(from), scratch); 510 masm.storeUnalignedSimd128(scratch, toAddress(to)); 511 } 512 } 513 514 void MoveEmitterX86::assertDone() { MOZ_ASSERT(!inCycle_); } 515 516 void MoveEmitterX86::finish() { 517 assertDone(); 518 519 masm.freeStack(masm.framePushed() - pushedAtStart_); 520 } 521 522 Maybe<Register> MoveEmitterX86::findScratchRegister(const MoveResolver& moves, 523 size_t initial) { 524 #ifdef JS_CODEGEN_X86 525 if (scratchRegister_.isSome()) { 526 return scratchRegister_; 527 } 528 529 // All registers are either in use by this move group or are live 530 // afterwards. Look through the remaining moves for a register which is 531 // clobbered before it is used, and is thus dead at this point. 532 AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All()); 533 for (size_t i = initial; i < moves.numMoves(); i++) { 534 const MoveOp& move = moves.getMove(i); 535 if (move.from().isGeneralReg()) { 536 regs.takeUnchecked(move.from().reg()); 537 } else if (move.from().isMemoryOrEffectiveAddress()) { 538 regs.takeUnchecked(move.from().base()); 539 } 540 if (move.to().isGeneralReg()) { 541 if (i != initial && !move.isCycleBegin() && regs.has(move.to().reg())) { 542 return mozilla::Some(move.to().reg()); 543 } 544 regs.takeUnchecked(move.to().reg()); 545 } else if (move.to().isMemoryOrEffectiveAddress()) { 546 regs.takeUnchecked(move.to().base()); 547 } 548 } 549 550 return mozilla::Nothing(); 551 #else 552 return mozilla::Some(ScratchReg); 553 #endif 554 }