tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }