Bug 991153: Teach the MoveResolver how to deal with the more complex cycles that can result from aliased registers (r=sunfish)
authorMarty Rosenberg <mrosenberg@mozilla.com>
Tue, 15 Jul 2014 03:34:08 -0400
changeset 215956 414ac77b7f2d6eb48f96d991414b78459a3b42c5
parent 215955 60b9b810aa29725cdd6fff2b2656d6781991f1d7
child 215957 651fde63cc765b100cc9d83cee1dd6f69c6e3d03
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssunfish
bugs991153
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 991153: Teach the MoveResolver how to deal with the more complex cycles that can result from aliased registers (r=sunfish)
js/src/jit/MoveResolver.cpp
js/src/jit/MoveResolver.h
js/src/jit/RegisterSets.h
js/src/jit/Registers.h
js/src/jit/arm/Architecture-arm.h
js/src/jit/arm/MoveEmitter-arm.cpp
js/src/jit/arm/MoveEmitter-arm.h
js/src/jit/arm/Simulator-arm.cpp
js/src/jit/arm/Simulator-arm.h
js/src/jit/x64/Architecture-x64.h
js/src/jit/x86/Architecture-x86.h
js/src/jsapi-tests/moz.build
js/src/jsapi-tests/testJitMoveEmitterCycles.cpp
--- a/js/src/jit/MoveResolver.cpp
+++ b/js/src/jit/MoveResolver.cpp
@@ -1,28 +1,30 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/MoveResolver.h"
+#include "jit/RegisterSets.h"
 
 using namespace js;
 using namespace js::jit;
 
 MoveResolver::MoveResolver()
-  : hasCycles_(false)
+  : numCycles_(0), curCycles_(0)
 {
 }
 
 void
 MoveResolver::resetState()
 {
-    hasCycles_ = false;
+    numCycles_ = 0;
+    curCycles_ = 0;
 }
 
 bool
 MoveResolver::addMove(const MoveOperand &from, const MoveOperand &to, MoveOp::Type type)
 {
     // Assert that we're not doing no-op moves.
     JS_ASSERT(!(from == to));
     PendingMove *pm = movePool_.allocate();
@@ -47,16 +49,37 @@ MoveResolver::findBlockingMove(const Pen
             return other;
         }
     }
 
     // No blocking moves found.
     return nullptr;
 }
 
+// Given move (A -> B), this function attempts to find any move (B -> *) in the
+// move list iterator, and returns the first one.
+// N.B. It is unclear if a single move can complete more than one cycle, so to be
+// conservative, this function operates on iterators, so the caller can process all
+// instructions that start a cycle.
+MoveResolver::PendingMove *
+MoveResolver::findCycledMove(PendingMoveIterator *iter, PendingMoveIterator end, const PendingMove *last)
+{
+    for (; *iter != end; (*iter)++) {
+        PendingMove *other = **iter;
+        if (other->from().aliases(last->to())) {
+            // We now have pairs in the form (A -> X) (X -> y). The second pair
+            // blocks the move in the first pair, so return it.
+            (*iter)++;
+            return other;
+        }
+    }
+    // No blocking moves found.
+    return nullptr;
+}
+
 bool
 MoveResolver::resolve()
 {
     resetState();
     orderedMoves_.clear();
 
     InlineList<PendingMove> stack;
 
@@ -103,24 +126,32 @@ MoveResolver::resolve()
 
         // Add this pending move to the cycle detection stack.
         stack.pushBack(pm);
 
         while (!stack.empty()) {
             PendingMove *blocking = findBlockingMove(stack.peekBack());
 
             if (blocking) {
-                if (blocking->to() == pm->from()) {
+                PendingMoveIterator stackiter = stack.begin();
+                PendingMove *cycled = findCycledMove(&stackiter, stack.end(), blocking);
+                if (cycled) {
+                    // Find the cycle's start.
                     // We annotate cycles at each move in the cycle, and
                     // assert that we do not find two cycles in one move chain
                     // traversal (which would indicate two moves to the same
                     // destination).
-                    pm->setCycleEnd();
-                    blocking->setCycleBegin(pm->type());
-                    hasCycles_ = true;
+                    // Since there can be more than one cycle, find them all.
+                    do {
+                        cycled->setCycleEnd(curCycles_);
+                        cycled = findCycledMove(&stackiter, stack.end(), blocking);
+                    } while (cycled);
+
+                    blocking->setCycleBegin(pm->type(), curCycles_);
+                    curCycles_++;
                     pending_.remove(blocking);
                     stack.pushBack(blocking);
                 } else {
                     // This is a new link in the move chain, so keep
                     // searching for a cycle.
                     pending_.remove(blocking);
                     stack.pushBack(blocking);
                 }
@@ -129,12 +160,18 @@ MoveResolver::resolve()
                 // complete and not participating in a cycle. The resulting
                 // move can safely be added to the ordered move list.
                 PendingMove *done = stack.popBack();
                 if (!orderedMoves_.append(*done))
                     return false;
                 movePool_.free(done);
             }
         }
+        // If the current queue is empty, it is certain that there are
+        // all previous cycles cannot conflict with future cycles,
+        // so re-set the counter of pending cycles, while keeping a high-water mark.
+        if (numCycles_ < curCycles_)
+            numCycles_ = curCycles_;
+        curCycles_ = 0;
     }
 
     return true;
 }
--- a/js/src/jit/MoveResolver.h
+++ b/js/src/jit/MoveResolver.h
@@ -85,16 +85,38 @@ class MoveOperand
         JS_ASSERT(isMemoryOrEffectiveAddress());
         return Register::FromCode(code_);
     }
     int32_t disp() const {
         JS_ASSERT(isMemoryOrEffectiveAddress());
         return disp_;
     }
 
+    bool aliases(MoveOperand other) const {
+
+        // These are not handled presently, but MEMORY and EFFECTIVE_ADDRESS
+        // only appear in controlled circumstances in the trampoline code
+        // which ensures these cases never come up.
+
+        JS_ASSERT_IF(isMemoryOrEffectiveAddress() && other.isGeneralReg(),
+                     base() != other.reg());
+        JS_ASSERT_IF(other.isMemoryOrEffectiveAddress() && isGeneralReg(),
+                     other.base() != reg());
+
+        if (kind_ != other.kind_)
+            return false;
+        if (kind_ == FLOAT_REG)
+            return floatReg().aliases(other.floatReg());
+        if (code_ != other.code_)
+            return false;
+        if (isMemoryOrEffectiveAddress())
+            return disp_ == other.disp_;
+        return true;
+    }
+
     bool operator ==(const MoveOperand &other) const {
         if (kind_ != other.kind_)
             return false;
         if (code_ != other.code_)
             return false;
         if (isMemoryOrEffectiveAddress())
             return disp_ == other.disp_;
         return true;
@@ -107,17 +129,18 @@ class MoveOperand
 // This represents a move operation.
 class MoveOp
 {
   protected:
     MoveOperand from_;
     MoveOperand to_;
     bool cycleBegin_;
     bool cycleEnd_;
-
+    int cycleBeginSlot_;
+    int cycleEndSlot_;
   public:
     enum Type {
         GENERAL,
         INT32,
         FLOAT32,
         DOUBLE
     };
 
@@ -135,25 +158,35 @@ class MoveOp
   public:
     MoveOp()
     { }
     MoveOp(const MoveOperand &from, const MoveOperand &to, Type type)
       : from_(from),
         to_(to),
         cycleBegin_(false),
         cycleEnd_(false),
+        cycleBeginSlot_(-1),
+        cycleEndSlot_(-1),
         type_(type)
     { }
 
     bool isCycleBegin() const {
         return cycleBegin_;
     }
     bool isCycleEnd() const {
         return cycleEnd_;
     }
+    uint32_t cycleBeginSlot() const {
+        MOZ_ASSERT(cycleBeginSlot_ != -1);
+        return cycleBeginSlot_;
+    }
+    uint32_t cycleEndSlot() const {
+        MOZ_ASSERT(cycleEndSlot_ != -1);
+        return cycleEndSlot_;
+    }
     const MoveOperand &from() const {
         return from_;
     }
     const MoveOperand &to() const {
         return to_;
     }
     Type type() const {
         return type_;
@@ -173,41 +206,43 @@ class MoveResolver
         public InlineListNode<PendingMove>
     {
         PendingMove()
         { }
         PendingMove(const MoveOperand &from, const MoveOperand &to, Type type)
           : MoveOp(from, to, type)
         { }
 
-        void setCycleBegin(Type endCycleType) {
-            JS_ASSERT(!isCycleBegin() && !isCycleEnd());
+        void setCycleBegin(Type endCycleType, int cycleSlot) {
+            JS_ASSERT(!cycleBegin_);
             cycleBegin_ = true;
+            cycleBeginSlot_ = cycleSlot;
             endCycleType_ = endCycleType;
         }
-        void setCycleEnd() {
-            JS_ASSERT(!isCycleBegin() && !isCycleEnd());
+        void setCycleEnd(int cycleSlot) {
+            JS_ASSERT(!cycleEnd_);
             cycleEnd_ = true;
+            cycleEndSlot_ = cycleSlot;
         }
     };
 
     typedef InlineList<MoveResolver::PendingMove>::iterator PendingMoveIterator;
 
   private:
     // Moves that are definitely unblocked (constants to registers). These are
     // emitted last.
     js::Vector<MoveOp, 16, SystemAllocPolicy> orderedMoves_;
-    bool hasCycles_;
-
+    int numCycles_;
+    int curCycles_;
     TempObjectPool<PendingMove> movePool_;
 
     InlineList<PendingMove> pending_;
 
     PendingMove *findBlockingMove(const PendingMove *last);
-
+    PendingMove *findCycledMove(PendingMoveIterator *stack, PendingMoveIterator end, const PendingMove *first);
     // Internal reset function. Does not clear lists.
     void resetState();
 
   public:
     MoveResolver();
 
     // Resolves a move group into two lists of ordered moves. These moves must
     // be executed in the order provided. Some moves may indicate that they
@@ -220,18 +255,18 @@ class MoveResolver
     bool resolve();
 
     size_t numMoves() const {
         return orderedMoves_.length();
     }
     const MoveOp &getMove(size_t i) const {
         return orderedMoves_[i];
     }
-    bool hasCycles() const {
-        return hasCycles_;
+    uint32_t numCycles() const {
+        return numCycles_;
     }
     void clearTempObjectPool() {
         movePool_.clear();
     }
     void setAllocator(TempAllocator &alloc) {
         movePool_.setAllocator(alloc);
     }
 };
--- a/js/src/jit/RegisterSets.h
+++ b/js/src/jit/RegisterSets.h
@@ -318,20 +318,22 @@ struct Int32Key {
     inline bool isConstant() const {
         return !isRegister_;
     }
 };
 
 template <typename T>
 class TypedRegisterSet
 {
-    uint32_t bits_;
+    typedef typename T::SetType SetType;
+    SetType bits_;
+
 
   public:
-    explicit MOZ_CONSTEXPR TypedRegisterSet(uint32_t bits)
+    explicit MOZ_CONSTEXPR TypedRegisterSet(SetType bits)
       : bits_(bits)
     { }
 
     MOZ_CONSTEXPR TypedRegisterSet() : bits_(0)
     { }
     MOZ_CONSTEXPR TypedRegisterSet(const TypedRegisterSet<T> &set) : bits_(set.bits_)
     { }
 
@@ -345,32 +347,33 @@ class TypedRegisterSet
     static inline TypedRegisterSet Union(const TypedRegisterSet &lhs,
                                          const TypedRegisterSet &rhs) {
         return TypedRegisterSet(lhs.bits_ | rhs.bits_);
     }
     static inline TypedRegisterSet Not(const TypedRegisterSet &in) {
         return TypedRegisterSet(~in.bits_ & T::Codes::AllocatableMask);
     }
     static inline TypedRegisterSet VolatileNot(const TypedRegisterSet &in) {
-        const uint32_t allocatableVolatile =
+        const SetType allocatableVolatile =
             T::Codes::AllocatableMask & T::Codes::VolatileMask;
         return TypedRegisterSet(~in.bits_ & allocatableVolatile);
     }
     static inline TypedRegisterSet Volatile() {
         return TypedRegisterSet(T::Codes::AllocatableMask & T::Codes::VolatileMask);
     }
     static inline TypedRegisterSet NonVolatile() {
         return TypedRegisterSet(T::Codes::AllocatableMask & T::Codes::NonVolatileMask);
     }
     bool has(T reg) const {
-        return !!(bits_ & (1 << reg.code()));
+        return !!(bits_ & (SetType(1) << reg.code()));
     }
     void addUnchecked(T reg) {
-        bits_ |= (1 << reg.code());
+        bits_ |= (SetType(1) << reg.code());
     }
+
     void add(T reg) {
         JS_ASSERT(!has(reg));
         addUnchecked(reg);
     }
     void add(ValueOperand value) {
 #if defined(JS_NUNBOX32)
         add(value.payloadReg());
         add(value.typeReg());
@@ -389,17 +392,17 @@ class TypedRegisterSet
     bool empty() const {
         return !bits_;
     }
     void take(T reg) {
         JS_ASSERT(has(reg));
         takeUnchecked(reg);
     }
     void takeUnchecked(T reg) {
-        bits_ &= ~(1 << reg.code());
+        bits_ &= ~(SetType(1) << reg.code());
     }
     void take(ValueOperand value) {
 #if defined(JS_NUNBOX32)
         take(value.payloadReg());
         take(value.typeReg());
 #elif defined(JS_PUNBOX64)
         take(value.valueReg());
 #else
@@ -486,17 +489,17 @@ class TypedRegisterSet
         JS_ASSERT(!empty());
         T reg = getLast();
         take(reg);
         return reg;
     }
     void clear() {
         bits_ = 0;
     }
-    uint32_t bits() const {
+    SetType bits() const {
         return bits_;
     }
     uint32_t size() const {
         return mozilla::CountPopulation32(bits_);
     }
     bool operator ==(const TypedRegisterSet<T> &other) const {
         return other.bits_ == bits_;
     }
@@ -595,16 +598,18 @@ class RegisterSet {
         fpu_.addUnchecked(reg);
     }
     void addUnchecked(AnyRegister any) {
         if (any.isFloat())
             addUnchecked(any.fpu());
         else
             addUnchecked(any.gpr());
     }
+
+
     bool empty(bool floats) const {
         return floats ? fpu_.empty() : gpr_.empty();
     }
     FloatRegister takeFloat() {
         return fpu_.takeAny();
     }
     Register takeGeneral() {
         return gpr_.takeAny();
--- a/js/src/jit/Registers.h
+++ b/js/src/jit/Registers.h
@@ -23,18 +23,18 @@
 #endif
 
 namespace js {
 namespace jit {
 
 struct Register {
     typedef Registers Codes;
     typedef Codes::Code Code;
+    typedef Codes::SetType SetType;
     Code code_;
-
     static Register FromCode(uint32_t i) {
         JS_ASSERT(i < Registers::Total);
         Register r = { (Registers::Code)i };
         return r;
     }
     static Register FromName(const char *name) {
         Registers::Code code = Registers::FromName(name);
         Register r = { code };
@@ -57,16 +57,20 @@ struct Register {
         return !!((1 << code()) & Registers::VolatileMask);
     }
     bool aliases(const Register &other) const {
         return code_ == other.code_;
     }
     uint32_t numAliased() const {
         return 1;
     }
+
+    // N.B. FloatRegister is an explicit outparam here because msvc-2010
+    // miscompiled it on win64 when the value was simply returned.  This
+    // now has an explicit outparam for compatability.
     void aliased(uint32_t aliasIdx, Register *ret) const {
         JS_ASSERT(aliasIdx == 0);
         *ret = *this;
     }
 };
 
 class RegisterDump
 {
--- a/js/src/jit/arm/Architecture-arm.h
+++ b/js/src/jit/arm/Architecture-arm.h
@@ -359,16 +359,19 @@ class VFPRegister
         if (isDouble()) {
             if (code_ < NumAliasedDoubles)
                 return 3;
             return 1;
         }
         return 2;
 #endif
     }
+
+    // N.B. FloatRegister is an explicit outparam here because msvc-2010
+    // miscompiled it on win64 when the value was simply returned
     void aliased(uint32_t aliasIdx, VFPRegister *ret) {
         if (aliasIdx == 0) {
             *ret = *this;
             return;
         }
         if (isDouble()) {
             JS_ASSERT(code_ < NumAliasedDoubles);
             JS_ASSERT(aliasIdx <= 2);
--- a/js/src/jit/arm/MoveEmitter-arm.cpp
+++ b/js/src/jit/arm/MoveEmitter-arm.cpp
@@ -5,57 +5,57 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/arm/MoveEmitter-arm.h"
 
 using namespace js;
 using namespace js::jit;
 
 MoveEmitterARM::MoveEmitterARM(MacroAssemblerARMCompat &masm)
-  : inCycle_(false),
+  : inCycle_(0),
     masm(masm),
     pushedAtCycle_(-1),
     pushedAtSpill_(-1),
     spilledReg_(InvalidReg),
     spilledFloatReg_(InvalidFloatReg)
 {
     pushedAtStart_ = masm.framePushed();
 }
 
 void
 MoveEmitterARM::emit(const MoveResolver &moves)
 {
-    if (moves.hasCycles()) {
+    if (moves.numCycles()) {
         // Reserve stack for cycle resolution
-        masm.reserveStack(sizeof(double));
+        masm.reserveStack(moves.numCycles() * sizeof(double));
         pushedAtCycle_ = masm.framePushed();
     }
 
     for (size_t i = 0; i < moves.numMoves(); i++)
         emit(moves.getMove(i));
 }
 
 MoveEmitterARM::~MoveEmitterARM()
 {
     assertDone();
 }
 
 Operand
-MoveEmitterARM::cycleSlot() const
+MoveEmitterARM::cycleSlot(uint32_t slot, uint32_t subslot) const
 {
-    int offset =  masm.framePushed() - pushedAtCycle_;
+    int32_t offset =  masm.framePushed() - pushedAtCycle_;
     JS_ASSERT(offset < 4096 && offset > -4096);
-    return Operand(StackPointer, offset);
+    return Operand(StackPointer, offset + slot * sizeof(double) + subslot);
 }
 
 // THIS IS ALWAYS AN LDRAddr. It should not be wrapped in an operand, methinks.
 Operand
 MoveEmitterARM::spillSlot() const
 {
-    int offset =  masm.framePushed() - pushedAtSpill_;
+    int32_t offset =  masm.framePushed() - pushedAtSpill_;
     JS_ASSERT(offset < 4096 && offset > -4096);
     return Operand(StackPointer, offset);
 }
 
 Operand
 MoveEmitterARM::toOperand(const MoveOperand &operand, bool isFloat) const
 {
     if (operand.isMemoryOrEffectiveAddress()) {
@@ -95,88 +95,108 @@ MoveEmitterARM::tempReg()
         pushedAtSpill_ = masm.framePushed();
     } else {
         masm.ma_str(spilledReg_, spillSlot());
     }
     return spilledReg_;
 }
 
 void
-MoveEmitterARM::breakCycle(const MoveOperand &from, const MoveOperand &to, MoveOp::Type type)
+MoveEmitterARM::breakCycle(const MoveOperand &from, const MoveOperand &to,
+                           MoveOp::Type type, uint32_t slotId)
 {
     // There is some pattern:
     //   (A -> B)
     //   (B -> A)
     //
     // This case handles (A -> B), which we reach first. We save B, then allow
     // the original move to continue.
     switch (type) {
       case MoveOp::FLOAT32:
+        if (to.isMemory()) {
+            VFPRegister temp = ScratchFloat32Reg;
+            masm.ma_vldr(toOperand(to, true), temp);
+            // Since it is uncertain if the load will be aligned or not
+            // just fill both of them with the same value.
+            masm.ma_vstr(temp, cycleSlot(slotId, 0));
+            masm.ma_vstr(temp, cycleSlot(slotId, 4));
+        } else {
+            FloatRegister src = to.floatReg();
+            // Just always store the largest possible size. Currently, this is
+            // a double. When SIMD is added, two doubles will need to be stored.
+            FloatRegister src2 = src.doubleOverlay();
+            masm.ma_vstr(src.doubleOverlay(), cycleSlot(slotId, 0));
+        }
+        break;
       case MoveOp::DOUBLE:
         if (to.isMemory()) {
             FloatRegister temp = ScratchDoubleReg;
             masm.ma_vldr(toOperand(to, true), temp);
-            masm.ma_vstr(temp, cycleSlot());
+            masm.ma_vstr(temp, cycleSlot(slotId, 0));
         } else {
-            masm.ma_vstr(to.floatReg(), cycleSlot());
+            masm.ma_vstr(to.floatReg().doubleOverlay(), cycleSlot(slotId, 0));
         }
         break;
       case MoveOp::INT32:
       case MoveOp::GENERAL:
         // an non-vfp value
         if (to.isMemory()) {
             Register temp = tempReg();
             masm.ma_ldr(toOperand(to, false), temp);
-            masm.ma_str(temp, cycleSlot());
+            masm.ma_str(temp, cycleSlot(0,0));
         } else {
             if (to.reg() == spilledReg_) {
                 // If the destination was spilled, restore it first.
                 masm.ma_ldr(spillSlot(), spilledReg_);
                 spilledReg_ = InvalidReg;
             }
-            masm.ma_str(to.reg(), cycleSlot());
+            masm.ma_str(to.reg(), cycleSlot(0,0));
         }
         break;
       default:
         MOZ_ASSUME_UNREACHABLE("Unexpected move type");
     }
 }
 
 void
-MoveEmitterARM::completeCycle(const MoveOperand &from, const MoveOperand &to, MoveOp::Type type)
+MoveEmitterARM::completeCycle(const MoveOperand &from, const MoveOperand &to, MoveOp::Type type, uint32_t slotId)
 {
     // There is some pattern:
     //   (A -> B)
     //   (B -> A)
     //
     // This case handles (B -> A), which we reach last. We emit a move from the
     // saved value of B, to A.
     switch (type) {
       case MoveOp::FLOAT32:
       case MoveOp::DOUBLE:
         if (to.isMemory()) {
             FloatRegister temp = ScratchDoubleReg;
-            masm.ma_vldr(cycleSlot(), temp);
+            masm.ma_vldr(cycleSlot(slotId, 0), temp);
             masm.ma_vstr(temp, toOperand(to, true));
         } else {
-            masm.ma_vldr(cycleSlot(), to.floatReg());
+            uint32_t offset = 0;
+            if ((!from.isMemory()) && from.floatReg().numAlignedAliased() == 1)
+                offset = sizeof(float);
+            masm.ma_vldr(cycleSlot(slotId, offset), to.floatReg());
         }
         break;
       case MoveOp::INT32:
       case MoveOp::GENERAL:
+        JS_ASSERT(slotId == 0);
         if (to.isMemory()) {
             Register temp = tempReg();
-            masm.ma_ldr(cycleSlot(), temp);
+            masm.ma_ldr(cycleSlot(slotId, 0), temp);
             masm.ma_str(temp, toOperand(to, false));
         } else {
             if (to.reg() == spilledReg_) {
                 // Make sure we don't re-clobber the spilled register later.
                 spilledReg_ = InvalidReg;
             }
-            masm.ma_ldr(cycleSlot(), to.reg());
+            masm.ma_ldr(cycleSlot(slotId, 0), to.reg());
         }
         break;
       default:
         MOZ_ASSUME_UNREACHABLE("Unexpected move type");
     }
 }
 
 void
@@ -269,27 +289,35 @@ MoveEmitterARM::emitDoubleMove(const Mov
 }
 
 void
 MoveEmitterARM::emit(const MoveOp &move)
 {
     const MoveOperand &from = move.from();
     const MoveOperand &to = move.to();
 
+    if (move.isCycleEnd() && move.isCycleBegin()) {
+        // A fun consequence of aliased registers is you can have multiple
+        // cycles at once, and one can end exactly where another begins.
+        breakCycle(from, to, move.endCycleType(), move.cycleBeginSlot());
+        completeCycle(from, to, move.type(), move.cycleEndSlot());
+        return;
+    }
+
     if (move.isCycleEnd()) {
         JS_ASSERT(inCycle_);
-        completeCycle(from, to, move.type());
-        inCycle_ = false;
+        completeCycle(from, to, move.type(), move.cycleEndSlot());
+        JS_ASSERT(inCycle_ > 0);
+        inCycle_--;
         return;
     }
 
     if (move.isCycleBegin()) {
-        JS_ASSERT(!inCycle_);
-        breakCycle(from, to, move.endCycleType());
-        inCycle_ = true;
+        breakCycle(from, to, move.endCycleType(), move.cycleBeginSlot());
+        inCycle_++;
     }
 
     switch (move.type()) {
       case MoveOp::FLOAT32:
         emitFloat32Move(from, to);
         break;
       case MoveOp::DOUBLE:
         emitDoubleMove(from, to);
@@ -301,17 +329,17 @@ MoveEmitterARM::emit(const MoveOp &move)
       default:
         MOZ_ASSUME_UNREACHABLE("Unexpected move type");
     }
 }
 
 void
 MoveEmitterARM::assertDone()
 {
-    JS_ASSERT(!inCycle_);
+    JS_ASSERT(inCycle_ == 0);
 }
 
 void
 MoveEmitterARM::finish()
 {
     assertDone();
 
     if (pushedAtSpill_ != -1 && spilledReg_ != InvalidReg)
--- a/js/src/jit/arm/MoveEmitter-arm.h
+++ b/js/src/jit/arm/MoveEmitter-arm.h
@@ -12,17 +12,17 @@
 
 namespace js {
 namespace jit {
 
 class CodeGenerator;
 
 class MoveEmitterARM
 {
-    bool inCycle_;
+    uint32_t inCycle_;
     MacroAssemblerARMCompat &masm;
 
     // Original stack push value.
     uint32_t pushedAtStart_;
 
     // These store stack offsets to spill locations, snapshotting
     // codegen->framePushed_ at the time they were allocated. They are -1 if no
     // stack space has been allocated for that particular spill.
@@ -33,25 +33,27 @@ class MoveEmitterARM
     // assigned InvalidReg. If no corresponding spill space has been assigned,
     // then these registers do not need to be spilled.
     Register spilledReg_;
     FloatRegister spilledFloatReg_;
 
     void assertDone();
     Register tempReg();
     FloatRegister tempFloatReg();
-    Operand cycleSlot() const;
+    Operand cycleSlot(uint32_t slot, uint32_t subslot) const;
     Operand spillSlot() const;
     Operand toOperand(const MoveOperand &operand, bool isFloat) const;
 
     void emitMove(const MoveOperand &from, const MoveOperand &to);
     void emitFloat32Move(const MoveOperand &from, const MoveOperand &to);
     void emitDoubleMove(const MoveOperand &from, const MoveOperand &to);
-    void breakCycle(const MoveOperand &from, const MoveOperand &to, MoveOp::Type type);
-    void completeCycle(const MoveOperand &from, const MoveOperand &to, MoveOp::Type type);
+    void breakCycle(const MoveOperand &from, const MoveOperand &to,
+                    MoveOp::Type type, uint32_t slot);
+    void completeCycle(const MoveOperand &from, const MoveOperand &to,
+                       MoveOp::Type type, uint32_t slot);
     void emit(const MoveOp &move);
 
   public:
     MoveEmitterARM(MacroAssemblerARMCompat &masm);
     ~MoveEmitterARM();
     void emit(const MoveResolver &moves);
     void finish();
 };
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -1135,16 +1135,17 @@ Simulator::Simulator(SimulatorRuntime *s
         MOZ_ReportAssertionFailure("[unhandlable oom] Simulator stack", __FILE__, __LINE__);
         MOZ_CRASH();
     }
     pc_modified_ = false;
     icount_ = 0L;
     resume_pc_ = 0;
     break_pc_ = nullptr;
     break_instr_ = 0;
+    skipCalleeSavedRegsCheck = true;
 
     // Set up architecture state.
     // All registers are initialized to zero to start with.
     for (int i = 0; i < num_registers; i++)
         registers_[i] = 0;
 
     n_flag_ = false;
     z_flag_ = false;
@@ -1431,16 +1432,21 @@ ReturnType Simulator::getFromVFPRegister
     ReturnType value = 0;
     char buffer[register_size * sizeof(vfp_registers_[0])];
     memcpy(buffer, &vfp_registers_[register_size * reg_index],
            register_size * sizeof(vfp_registers_[0]));
     memcpy(&value, buffer, register_size * sizeof(vfp_registers_[0]));
     return value;
 }
 
+// These forced-instantiations are for jsapi-tests. Evidently, nothing
+// requires these to be instantiated.
+template double Simulator::getFromVFPRegister<double, 2>(int reg_index);
+template float Simulator::getFromVFPRegister<float, 1>(int reg_index);
+
 void
 Simulator::getFpArgs(double *x, double *y, int32_t *z)
 {
     if (UseHardFpABI()) {
         *x = get_double_from_d_register(0);
         *y = get_double_from_d_register(1);
         *z = get_register(0);
     } else {
@@ -4122,87 +4128,92 @@ Simulator::callInternal(uint8_t *entry)
     uint64_t d14_val;
     get_d_register(d14, &d14_val);
     uint64_t d15_val;
     get_d_register(d15, &d15_val);
 
     // Set up the callee-saved registers with a known value. To be able to check
     // that they are preserved properly across JS execution.
     int32_t callee_saved_value = uint32_t(icount_);
-    set_register(r4, callee_saved_value);
-    set_register(r5, callee_saved_value);
-    set_register(r6, callee_saved_value);
-    set_register(r7, callee_saved_value);
-    set_register(r8, callee_saved_value);
-    set_register(r9, callee_saved_value);
-    set_register(r10, callee_saved_value);
-    set_register(r11, callee_saved_value);
-
     uint64_t callee_saved_value_d = uint64_t(icount_);
-    set_d_register(d8, &callee_saved_value_d);
-    set_d_register(d9, &callee_saved_value_d);
-    set_d_register(d10, &callee_saved_value_d);
-    set_d_register(d11, &callee_saved_value_d);
-    set_d_register(d12, &callee_saved_value_d);
-    set_d_register(d13, &callee_saved_value_d);
-    set_d_register(d14, &callee_saved_value_d);
-    set_d_register(d15, &callee_saved_value_d);
-
+
+    if (!skipCalleeSavedRegsCheck) {
+        set_register(r4, callee_saved_value);
+        set_register(r5, callee_saved_value);
+        set_register(r6, callee_saved_value);
+        set_register(r7, callee_saved_value);
+        set_register(r8, callee_saved_value);
+        set_register(r9, callee_saved_value);
+        set_register(r10, callee_saved_value);
+        set_register(r11, callee_saved_value);
+
+        set_d_register(d8, &callee_saved_value_d);
+        set_d_register(d9, &callee_saved_value_d);
+        set_d_register(d10, &callee_saved_value_d);
+        set_d_register(d11, &callee_saved_value_d);
+        set_d_register(d12, &callee_saved_value_d);
+        set_d_register(d13, &callee_saved_value_d);
+        set_d_register(d14, &callee_saved_value_d);
+        set_d_register(d15, &callee_saved_value_d);
+
+    }
     // Start the simulation.
     if (Simulator::StopSimAt != -1L)
         execute<true>();
     else
         execute<false>();
 
-    // Check that the callee-saved registers have been preserved.
-    MOZ_ASSERT(callee_saved_value == get_register(r4));
-    MOZ_ASSERT(callee_saved_value == get_register(r5));
-    MOZ_ASSERT(callee_saved_value == get_register(r6));
-    MOZ_ASSERT(callee_saved_value == get_register(r7));
-    MOZ_ASSERT(callee_saved_value == get_register(r8));
-    MOZ_ASSERT(callee_saved_value == get_register(r9));
-    MOZ_ASSERT(callee_saved_value == get_register(r10));
-    MOZ_ASSERT(callee_saved_value == get_register(r11));
-
-    uint64_t value;
-    get_d_register(d8, &value);
-    MOZ_ASSERT(callee_saved_value_d == value);
-    get_d_register(d9, &value);
-    MOZ_ASSERT(callee_saved_value_d == value);
-    get_d_register(d10, &value);
-    MOZ_ASSERT(callee_saved_value_d == value);
-    get_d_register(d11, &value);
-    MOZ_ASSERT(callee_saved_value_d == value);
-    get_d_register(d12, &value);
-    MOZ_ASSERT(callee_saved_value_d == value);
-    get_d_register(d13, &value);
-    MOZ_ASSERT(callee_saved_value_d == value);
-    get_d_register(d14, &value);
-    MOZ_ASSERT(callee_saved_value_d == value);
-    get_d_register(d15, &value);
-    MOZ_ASSERT(callee_saved_value_d == value);
-
-    // Restore callee-saved registers with the original value.
-    set_register(r4, r4_val);
-    set_register(r5, r5_val);
-    set_register(r6, r6_val);
-    set_register(r7, r7_val);
-    set_register(r8, r8_val);
-    set_register(r9, r9_val);
-    set_register(r10, r10_val);
-    set_register(r11, r11_val);
-
-    set_d_register(d8, &d8_val);
-    set_d_register(d9, &d9_val);
-    set_d_register(d10, &d10_val);
-    set_d_register(d11, &d11_val);
-    set_d_register(d12, &d12_val);
-    set_d_register(d13, &d13_val);
-    set_d_register(d14, &d14_val);
-    set_d_register(d15, &d15_val);
+    if (!skipCalleeSavedRegsCheck) {
+        // Check that the callee-saved registers have been preserved.
+        MOZ_ASSERT(callee_saved_value == get_register(r4));
+        MOZ_ASSERT(callee_saved_value == get_register(r5));
+        MOZ_ASSERT(callee_saved_value == get_register(r6));
+        MOZ_ASSERT(callee_saved_value == get_register(r7));
+        MOZ_ASSERT(callee_saved_value == get_register(r8));
+        MOZ_ASSERT(callee_saved_value == get_register(r9));
+        MOZ_ASSERT(callee_saved_value == get_register(r10));
+        MOZ_ASSERT(callee_saved_value == get_register(r11));
+
+        uint64_t value;
+        get_d_register(d8, &value);
+        MOZ_ASSERT(callee_saved_value_d == value);
+        get_d_register(d9, &value);
+        MOZ_ASSERT(callee_saved_value_d == value);
+        get_d_register(d10, &value);
+        MOZ_ASSERT(callee_saved_value_d == value);
+        get_d_register(d11, &value);
+        MOZ_ASSERT(callee_saved_value_d == value);
+        get_d_register(d12, &value);
+        MOZ_ASSERT(callee_saved_value_d == value);
+        get_d_register(d13, &value);
+        MOZ_ASSERT(callee_saved_value_d == value);
+        get_d_register(d14, &value);
+        MOZ_ASSERT(callee_saved_value_d == value);
+        get_d_register(d15, &value);
+        MOZ_ASSERT(callee_saved_value_d == value);
+
+        // Restore callee-saved registers with the original value.
+        set_register(r4, r4_val);
+        set_register(r5, r5_val);
+        set_register(r6, r6_val);
+        set_register(r7, r7_val);
+        set_register(r8, r8_val);
+        set_register(r9, r9_val);
+        set_register(r10, r10_val);
+        set_register(r11, r11_val);
+
+        set_d_register(d8, &d8_val);
+        set_d_register(d9, &d9_val);
+        set_d_register(d10, &d10_val);
+        set_d_register(d11, &d11_val);
+        set_d_register(d12, &d12_val);
+        set_d_register(d13, &d13_val);
+        set_d_register(d14, &d14_val);
+        set_d_register(d15, &d15_val);
+    }
 }
 
 int64_t
 Simulator::call(uint8_t* entry, int argument_count, ...)
 {
     va_list parameters;
     va_start(parameters, argument_count);
 
--- a/js/src/jit/arm/Simulator-arm.h
+++ b/js/src/jit/arm/Simulator-arm.h
@@ -259,16 +259,23 @@ class Simulator
     void instructionDecode(SimInstruction *instr);
 
   public:
     static bool ICacheCheckingEnabled;
     static void FlushICache(void *start, size_t size);
 
     static int64_t StopSimAt;
 
+    // For testing the MoveResolver code, a MoveResolver is set up, and
+    // the VFP registers are loaded with pre-determined values,
+    // then the sequence of code is simulated.  In order to test this with the
+    // simulator, the callee-saved registers can't be trashed. This flag
+    // disables that feature.
+    bool skipCalleeSavedRegsCheck;
+
     // Runtime call support.
     static void *RedirectNativeFunction(void *nativeFunction, ABIFunctionType type);
 
   private:
     // Handle arguments and return value for runtime FP functions.
     void getFpArgs(double *x, double *y, int32_t *z);
     void setCallResultDouble(double result);
     void setCallResultFloat(float result);
--- a/js/src/jit/x64/Architecture-x64.h
+++ b/js/src/jit/x64/Architecture-x64.h
@@ -205,16 +205,19 @@ struct FloatRegister {
         return other.code_ == code_;
     }
     bool aliases(FloatRegister other) const {
         return other.code_ == code_;
     }
     uint32_t numAliased() const {
         return 1;
     }
+
+    // N.B. FloatRegister is an explicit outparam here because msvc-2010
+    // miscompiled it on win64 when the value was simply returned
     void aliased(uint32_t aliasIdx, FloatRegister *ret) {
         JS_ASSERT(aliasIdx == 0);
         *ret = *this;
     }
     // This function mostly exists for the ARM backend.  It is to ensure that two
     // floating point registers' types are equivalent.  e.g. S0 is not equivalent
     // to D16, since S0 holds a float32, and D16 holds a Double.
     // Since all floating point registers on x86 and x64 are equivalent, it is
--- a/js/src/jit/x86/Architecture-x86.h
+++ b/js/src/jit/x86/Architecture-x86.h
@@ -181,16 +181,18 @@ struct FloatRegister {
         return other.code_ == code_;
     }
     bool aliases(FloatRegister other) const {
         return other.code_ == code_;
     }
     uint32_t numAliased() const {
         return 1;
     }
+    // N.B. FloatRegister is an explicit outparam here because msvc-2010
+    // miscompiled it on win64 when the value was simply returned
     void aliased(uint32_t aliasIdx, FloatRegister *ret) {
         JS_ASSERT(aliasIdx == 0);
         *ret = *this;
     }
     // This function mostly exists for the ARM backend.  It is to ensure that two
     // floating point registers' types are equivalent.  e.g. S0 is not equivalent
     // to D16, since S0 holds a float32, and D16 holds a Double.
     // Since all floating point registers on x86 and x64 are equivalent, it is
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -77,16 +77,17 @@ UNIFIED_SOURCES += [
     'testUTF8.cpp',
     'testWeakMap.cpp',
     'testXDR.cpp',
 ]
 
 if CONFIG['ENABLE_ION']:
     UNIFIED_SOURCES += [
         'testJitFoldsTo.cpp',
+        'testJitMoveEmitterCycles.cpp',
         'testJitRValueAlloc.cpp',
     ]
 
 DEFINES['EXPORT_JS_API'] = True
 # Building against js_static requires that we declare mfbt sybols "exported"
 # on its behalf.
 DEFINES['IMPL_MFBT'] = True
 
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testJitMoveEmitterCycles.cpp
@@ -0,0 +1,431 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if defined(JS_ARM_SIMULATOR) && defined(EVERYONE_KNOWS_ABOUT_ALIASING)
+#include "jit/arm/Assembler-arm.h"
+#include "jit/arm/MoveEmitter-arm.h"
+#include "jit/arm/Simulator-arm.h"
+#include "jit/IonLinker.h"
+#include "jit/IonMacroAssembler.h"
+#include "jit/MoveResolver.h"
+
+#include "jsapi-tests/tests.h"
+
+#include "vm/Runtime.h"
+
+static const int LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 4*1024;
+
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s0(0, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s1(1, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s2(2, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s3(3, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s4(4, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s5(5, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s6(6, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s7(7, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s8(8, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s9(9, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s10(10, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s11(11, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s12(12, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s13(13, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s14(14, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s15(15, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s16(16, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s17(17, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s18(18, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s19(19, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s20(20, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s21(21, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s22(22, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s23(23, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s24(24, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s25(25, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s26(26, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s27(27, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s28(28, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s29(29, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s30(30, js::jit::VFPRegister::Single);
+static MOZ_CONSTEXPR_VAR js::jit::FloatRegister s31(31, js::jit::VFPRegister::Single);
+
+BEGIN_TEST(testJitMoveEmitterCycles_simple)
+{
+    using namespace js;
+    using namespace js::jit;
+    LifoAlloc lifo(LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+    TempAllocator alloc(&lifo);
+    IonContext ic(cx, &alloc);
+    JitRuntime *jrt = cx->runtime()->getJitRuntime(cx);
+    AutoFlushICache afc("test");
+
+    MacroAssembler masm;
+    MoveEmitter mover(masm);
+    MoveResolver mr;
+    mr.setAllocator(alloc);
+    Simulator *sim = Simulator::Current();
+    mr.addMove(MoveOperand(d0), MoveOperand(d2), MoveOp::DOUBLE);
+    sim->set_d_register_from_double(0, 2);
+    mr.addMove(MoveOperand(d3), MoveOperand(d1), MoveOp::DOUBLE);
+    sim->set_d_register_from_double(3, 1);
+    mr.addMove(MoveOperand(s4), MoveOperand(s0), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(4, 0);
+    mr.addMove(MoveOperand(s5), MoveOperand(s6), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(5, 6);
+    mr.addMove(MoveOperand(s2), MoveOperand(s1), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(2, 1);
+    mr.addMove(MoveOperand(s3), MoveOperand(s7), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(3, 7);
+    // don't explode!
+    mr.resolve();
+    mover.emit(mr);
+    mover.finish();
+    masm.abiret();
+    Linker l(masm);
+    JitCode *code = l.newCodeForIonScript(cx);
+
+    sim->call(code->raw(), 1, 1);
+    CHECK(sim->get_double_from_d_register(2) != 2);
+    CHECK(int(sim->get_double_from_d_register(1)) != 1);
+    CHECK(int(sim->get_float_from_s_register(0)) != 0);
+    CHECK(int(sim->get_float_from_s_register(6)) != 6);
+    CHECK(int(sim->get_float_from_s_register(1)) != 1);
+    CHECK(int(sim->get_float_from_s_register(7)) != 7);
+    return true;
+}
+END_TEST(testJitMoveEmitterCycles_simple)
+BEGIN_TEST(testJitMoveEmitterCycles_autogen)
+{
+    using namespace js;
+    using namespace js::jit;
+    LifoAlloc lifo(LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+    TempAllocator alloc(&lifo);
+    IonContext ic(cx, &alloc);
+    JitRuntime *jrt = cx->runtime()->getJitRuntime(cx);
+    AutoFlushICache afc("test");
+    MacroAssembler masm;
+    MoveEmitter mover(masm);
+    MoveResolver mr;
+    mr.setAllocator(alloc);
+    Simulator *sim = Simulator::Current();
+    mr.addMove(MoveOperand(d9), MoveOperand(d14), MoveOp::DOUBLE);
+    sim->set_d_register_from_double(9, 9);
+    mr.addMove(MoveOperand(s24), MoveOperand(s25), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(24, 24);
+    mr.addMove(MoveOperand(d3), MoveOperand(d0), MoveOp::DOUBLE);
+    sim->set_d_register_from_double(3, 3);
+    mr.addMove(MoveOperand(s10), MoveOperand(s31), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(10, 10);
+    mr.addMove(MoveOperand(d1), MoveOperand(d10), MoveOp::DOUBLE);
+    sim->set_d_register_from_double(1, 1);
+    mr.addMove(MoveOperand(s8), MoveOperand(s10), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(8, 8);
+    mr.addMove(MoveOperand(d2), MoveOperand(d7), MoveOp::DOUBLE);
+    sim->set_d_register_from_double(2, 2);
+    mr.addMove(MoveOperand(s20), MoveOperand(s18), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(20, 20);
+    mr.addMove(MoveOperand(s1), MoveOperand(s3), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(1, 1);
+    mr.addMove(MoveOperand(s17), MoveOperand(s11), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(17, 17);
+    mr.addMove(MoveOperand(s22), MoveOperand(s30), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(22, 22);
+    mr.addMove(MoveOperand(s31), MoveOperand(s7), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(31, 31);
+    mr.addMove(MoveOperand(d3), MoveOperand(d13), MoveOp::DOUBLE);
+    sim->set_d_register_from_double(3, 3);
+    mr.addMove(MoveOperand(d9), MoveOperand(d8), MoveOp::DOUBLE);
+    sim->set_d_register_from_double(9, 9);
+    mr.addMove(MoveOperand(s31), MoveOperand(s23), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(31, 31);
+    mr.addMove(MoveOperand(s13), MoveOperand(s8), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(13, 13);
+    mr.addMove(MoveOperand(s28), MoveOperand(s5), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(28, 28);
+    mr.addMove(MoveOperand(s31), MoveOperand(s19), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(31, 31);
+    mr.addMove(MoveOperand(s20), MoveOperand(s6), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(20, 20);
+    mr.addMove(MoveOperand(s0), MoveOperand(s2), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(0, 0);
+    mr.addMove(MoveOperand(d7), MoveOperand(d6), MoveOp::DOUBLE);
+    sim->set_d_register_from_double(7, 7);
+    mr.addMove(MoveOperand(s13), MoveOperand(s9), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(13, 13);
+    mr.addMove(MoveOperand(s1), MoveOperand(s4), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(1, 1);
+    mr.addMove(MoveOperand(s29), MoveOperand(s22), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(29, 29);
+    mr.addMove(MoveOperand(s25), MoveOperand(s24), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(25, 25);
+    // don't explode!
+    mr.resolve();
+    mover.emit(mr);
+    mover.finish();
+    masm.abiret();
+    Linker l(masm);
+    JitCode *code = l.newCodeForIonScript(cx);
+    sim->skipCalleeSavedRegsCheck = true;
+    sim->call(code->raw(), 1, 1);
+    CHECK(int(sim->get_double_from_d_register(14)) != 9);
+    CHECK(int(sim->get_float_from_s_register(25)) != 24);
+    CHECK(int(sim->get_double_from_d_register(0)) != 3);
+    CHECK(int(sim->get_float_from_s_register(31)) != 10);
+    CHECK(int(sim->get_double_from_d_register(10)) != 1);
+    CHECK(int(sim->get_float_from_s_register(10)) != 8);
+    CHECK(int(sim->get_double_from_d_register(7)) != 2);
+    CHECK(int(sim->get_float_from_s_register(18)) != 20);
+    CHECK(int(sim->get_float_from_s_register(3)) != 1);
+    CHECK(int(sim->get_float_from_s_register(11)) != 17);
+    CHECK(int(sim->get_float_from_s_register(30)) != 22);
+    CHECK(int(sim->get_float_from_s_register(7)) != 31);
+    CHECK(int(sim->get_double_from_d_register(13)) != 3);
+    CHECK(int(sim->get_double_from_d_register(8)) != 9);
+    CHECK(int(sim->get_float_from_s_register(23)) != 31);
+    CHECK(int(sim->get_float_from_s_register(8)) != 13);
+    CHECK(int(sim->get_float_from_s_register(5)) != 28);
+    CHECK(int(sim->get_float_from_s_register(19)) != 31);
+    CHECK(int(sim->get_float_from_s_register(6)) != 20);
+    CHECK(int(sim->get_float_from_s_register(2)) != 0);
+    CHECK(int(sim->get_double_from_d_register(6)) != 7);
+    CHECK(int(sim->get_float_from_s_register(9)) != 13);
+    CHECK(int(sim->get_float_from_s_register(4)) != 1);
+    CHECK(int(sim->get_float_from_s_register(22)) != 29);
+    CHECK(int(sim->get_float_from_s_register(24)) != 25);
+    return true;
+}
+END_TEST(testJitMoveEmitterCycles_autogen)
+
+BEGIN_TEST(testJitMoveEmitterCycles_autogen2)
+{
+    using namespace js;
+    using namespace js::jit;
+    LifoAlloc lifo(LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+    TempAllocator alloc(&lifo);
+    IonContext ic(cx, &alloc);
+    JitRuntime *jrt = cx->runtime()->getJitRuntime(cx);
+    AutoFlushICache afc("test");
+    MacroAssembler masm;
+    MoveEmitter mover(masm);
+    MoveResolver mr;
+    mr.setAllocator(alloc);
+    Simulator *sim = Simulator::Current();
+    mr.addMove(MoveOperand(d10), MoveOperand(d0), MoveOp::DOUBLE);
+    sim->set_d_register_from_double(10, 10);
+    mr.addMove(MoveOperand(s15), MoveOperand(s3), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(15, 15);
+    mr.addMove(MoveOperand(s2), MoveOperand(s28), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(2, 2);
+    mr.addMove(MoveOperand(s30), MoveOperand(s25), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(30, 30);
+    mr.addMove(MoveOperand(s16), MoveOperand(s2), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(16, 16);
+    mr.addMove(MoveOperand(s2), MoveOperand(s29), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(2, 2);
+    mr.addMove(MoveOperand(s17), MoveOperand(s10), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(17, 17);
+    mr.addMove(MoveOperand(s2), MoveOperand(s19), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(2, 2);
+    mr.addMove(MoveOperand(s9), MoveOperand(s26), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(9, 9);
+    mr.addMove(MoveOperand(s1), MoveOperand(s23), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(1, 1);
+    mr.addMove(MoveOperand(s8), MoveOperand(s6), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(8, 8);
+    mr.addMove(MoveOperand(s24), MoveOperand(s16), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(24, 24);
+    mr.addMove(MoveOperand(s19), MoveOperand(s4), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(19, 19);
+    mr.addMove(MoveOperand(d5), MoveOperand(d6), MoveOp::DOUBLE);
+    sim->set_d_register_from_double(5, 5);
+    mr.addMove(MoveOperand(s18), MoveOperand(s15), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(18, 18);
+    mr.addMove(MoveOperand(s23), MoveOperand(s30), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(23, 23);
+    mr.addMove(MoveOperand(s27), MoveOperand(s17), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(27, 27);
+    mr.addMove(MoveOperand(d3), MoveOperand(d4), MoveOp::DOUBLE);
+    sim->set_d_register_from_double(3, 3);
+    mr.addMove(MoveOperand(s14), MoveOperand(s27), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(14, 14);
+    mr.addMove(MoveOperand(s2), MoveOperand(s31), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(2, 2);
+    mr.addMove(MoveOperand(s2), MoveOperand(s24), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(2, 2);
+    mr.addMove(MoveOperand(s31), MoveOperand(s11), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(31, 31);
+    mr.addMove(MoveOperand(s0), MoveOperand(s18), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(0, 0);
+    mr.addMove(MoveOperand(s24), MoveOperand(s7), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(24, 24);
+    mr.addMove(MoveOperand(s0), MoveOperand(s21), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(0, 0);
+    mr.addMove(MoveOperand(s27), MoveOperand(s20), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(27, 27);
+    mr.addMove(MoveOperand(s14), MoveOperand(s5), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(14, 14);
+    mr.addMove(MoveOperand(s2), MoveOperand(s14), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(2, 2);
+    mr.addMove(MoveOperand(s12), MoveOperand(s22), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(12, 12);
+    // don't explode!
+    mr.resolve();
+    mover.emit(mr);
+    mover.finish();
+    masm.abiret();
+    Linker l(masm);
+    JitCode *code = l.newCodeForIonScript(cx);
+    sim->skipCalleeSavedRegsCheck = true;
+    sim->call(code->raw(), 1, 1);
+    CHECK(int(sim->get_double_from_d_register(0)) != 10);
+    CHECK(int(sim->get_float_from_s_register(3)) != 15);
+    CHECK(int(sim->get_float_from_s_register(28)) != 2);
+    CHECK(int(sim->get_float_from_s_register(25)) != 30);
+    CHECK(int(sim->get_float_from_s_register(2)) != 16);
+    CHECK(int(sim->get_float_from_s_register(29)) != 2);
+    CHECK(int(sim->get_float_from_s_register(10)) != 17);
+    CHECK(int(sim->get_float_from_s_register(19)) != 2);
+    CHECK(int(sim->get_float_from_s_register(26)) != 9);
+    CHECK(int(sim->get_float_from_s_register(23)) != 1);
+    CHECK(int(sim->get_float_from_s_register(6)) != 8);
+    CHECK(int(sim->get_float_from_s_register(16)) != 24);
+    CHECK(int(sim->get_float_from_s_register(4)) != 19);
+    CHECK(int(sim->get_double_from_d_register(6)) != 5);
+    CHECK(int(sim->get_float_from_s_register(15)) != 18);
+    CHECK(int(sim->get_float_from_s_register(30)) != 23);
+    CHECK(int(sim->get_float_from_s_register(17)) != 27);
+    CHECK(int(sim->get_double_from_d_register(4)) != 3);
+    CHECK(int(sim->get_float_from_s_register(27)) != 14);
+    CHECK(int(sim->get_float_from_s_register(31)) != 2);
+    CHECK(int(sim->get_float_from_s_register(24)) != 2);
+    CHECK(int(sim->get_float_from_s_register(11)) != 31);
+    CHECK(int(sim->get_float_from_s_register(18)) != 0);
+    CHECK(int(sim->get_float_from_s_register(7)) != 24);
+    CHECK(int(sim->get_float_from_s_register(21)) != 0);
+    CHECK(int(sim->get_float_from_s_register(20)) != 27);
+    CHECK(int(sim->get_float_from_s_register(5)) != 14);
+    CHECK(int(sim->get_float_from_s_register(14)) != 2);
+    CHECK(int(sim->get_float_from_s_register(22)) != 12);
+    return true;
+}
+END_TEST(testJitMoveEmitterCycles_autogen2)
+
+
+BEGIN_TEST(testJitMoveEmitterCycles_autogen3)
+{
+    using namespace js;
+    using namespace js::jit;
+    LifoAlloc lifo(LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+    TempAllocator alloc(&lifo);
+    IonContext ic(cx, &alloc);
+    JitRuntime *jrt = cx->runtime()->getJitRuntime(cx);
+    AutoFlushICache afc("test");
+    MacroAssembler masm;
+    MoveEmitter mover(masm);
+    MoveResolver mr;
+    mr.setAllocator(alloc);
+    Simulator *sim = Simulator::Current();
+    mr.addMove(MoveOperand(s0), MoveOperand(s21), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(0, 0);
+    mr.addMove(MoveOperand(s2), MoveOperand(s26), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(2, 2);
+    mr.addMove(MoveOperand(s19), MoveOperand(s20), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(19, 19);
+    mr.addMove(MoveOperand(s4), MoveOperand(s24), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(4, 4);
+    mr.addMove(MoveOperand(s22), MoveOperand(s9), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(22, 22);
+    mr.addMove(MoveOperand(s5), MoveOperand(s28), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(5, 5);
+    mr.addMove(MoveOperand(s15), MoveOperand(s7), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(15, 15);
+    mr.addMove(MoveOperand(s26), MoveOperand(s14), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(26, 26);
+    mr.addMove(MoveOperand(s13), MoveOperand(s30), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(13, 13);
+    mr.addMove(MoveOperand(s26), MoveOperand(s22), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(26, 26);
+    mr.addMove(MoveOperand(s21), MoveOperand(s6), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(21, 21);
+    mr.addMove(MoveOperand(s23), MoveOperand(s31), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(23, 23);
+    mr.addMove(MoveOperand(s7), MoveOperand(s12), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(7, 7);
+    mr.addMove(MoveOperand(s14), MoveOperand(s10), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(14, 14);
+    mr.addMove(MoveOperand(d12), MoveOperand(d8), MoveOp::DOUBLE);
+    sim->set_d_register_from_double(12, 12);
+    mr.addMove(MoveOperand(s5), MoveOperand(s1), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(5, 5);
+    mr.addMove(MoveOperand(d12), MoveOperand(d2), MoveOp::DOUBLE);
+    sim->set_d_register_from_double(12, 12);
+    mr.addMove(MoveOperand(s3), MoveOperand(s8), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(3, 3);
+    mr.addMove(MoveOperand(s14), MoveOperand(s0), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(14, 14);
+    mr.addMove(MoveOperand(s28), MoveOperand(s29), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(28, 28);
+    mr.addMove(MoveOperand(d12), MoveOperand(d9), MoveOp::DOUBLE);
+    sim->set_d_register_from_double(12, 12);
+    mr.addMove(MoveOperand(s29), MoveOperand(s2), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(29, 29);
+    mr.addMove(MoveOperand(s22), MoveOperand(s27), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(22, 22);
+    mr.addMove(MoveOperand(s19), MoveOperand(s3), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(19, 19);
+    mr.addMove(MoveOperand(s21), MoveOperand(s11), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(21, 21);
+    mr.addMove(MoveOperand(s22), MoveOperand(s13), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(22, 22);
+    mr.addMove(MoveOperand(s29), MoveOperand(s25), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(29, 29);
+    mr.addMove(MoveOperand(s29), MoveOperand(s15), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(29, 29);
+    mr.addMove(MoveOperand(s16), MoveOperand(s23), MoveOp::FLOAT32);
+    sim->set_s_register_from_float(16, 16);
+    // don't explode!
+    mr.resolve();
+    mover.emit(mr);
+    mover.finish();
+    masm.abiret();
+    Linker l(masm);
+    JitCode *code = l.newCodeForIonScript(cx);
+    sim->skipCalleeSavedRegsCheck = true;
+    sim->call(code->raw(), 1, 1);
+    CHECK(int(sim->get_float_from_s_register(21)) != 0);
+    CHECK(int(sim->get_float_from_s_register(26)) != 2);
+    CHECK(int(sim->get_float_from_s_register(20)) != 19);
+    CHECK(int(sim->get_float_from_s_register(24)) != 4);
+    CHECK(int(sim->get_float_from_s_register(9)) != 22);
+    CHECK(int(sim->get_float_from_s_register(28)) != 5);
+    CHECK(int(sim->get_float_from_s_register(7)) != 15);
+    CHECK(int(sim->get_float_from_s_register(14)) != 26);
+    CHECK(int(sim->get_float_from_s_register(30)) != 13);
+    CHECK(int(sim->get_float_from_s_register(22)) != 26);
+    CHECK(int(sim->get_float_from_s_register(6)) != 21);
+    CHECK(int(sim->get_float_from_s_register(31)) != 23);
+    CHECK(int(sim->get_float_from_s_register(12)) != 7);
+    CHECK(int(sim->get_float_from_s_register(10)) != 14);
+    CHECK(int(sim->get_double_from_d_register(8)) != 12);
+    CHECK(int(sim->get_float_from_s_register(1)) != 5);
+    CHECK(int(sim->get_double_from_d_register(2)) != 12);
+    CHECK(int(sim->get_float_from_s_register(8)) != 3);
+    CHECK(int(sim->get_float_from_s_register(0)) != 14);
+    CHECK(int(sim->get_float_from_s_register(29)) != 28);
+    CHECK(int(sim->get_double_from_d_register(9)) != 12);
+    CHECK(int(sim->get_float_from_s_register(2)) != 29);
+    CHECK(int(sim->get_float_from_s_register(27)) != 22);
+    CHECK(int(sim->get_float_from_s_register(3)) != 19);
+    CHECK(int(sim->get_float_from_s_register(11)) != 21);
+    CHECK(int(sim->get_float_from_s_register(13)) != 22);
+    CHECK(int(sim->get_float_from_s_register(25)) != 29);
+    CHECK(int(sim->get_float_from_s_register(15)) != 29);
+    CHECK(int(sim->get_float_from_s_register(23)) != 16);
+    return true;
+}
+END_TEST(testJitMoveEmitterCycles_autogen3)
+
+#endif