/* -*- 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/arm64/MoveEmitter-arm64.h"
#include "jit/MacroAssembler-inl.h"

using namespace js;
using namespace js::jit;

MemOperand
MoveEmitterARM64::toMemOperand(const MoveOperand& operand) const
{
    MOZ_ASSERT(operand.isMemory());
    ARMRegister base(operand.base(), 64);
    if (operand.base() == masm.getStackPointer())
        return MemOperand(base, operand.disp() + (masm.framePushed() - pushedAtStart_));
    return MemOperand(base, operand.disp());
}

void
MoveEmitterARM64::emit(const MoveResolver& moves)
{
    if (moves.numCycles()) {
        masm.reserveStack(sizeof(void*));
        pushedAtCycle_ = masm.framePushed();
    }

    for (size_t i = 0; i < moves.numMoves(); i++)
        emitMove(moves.getMove(i));
}

void
MoveEmitterARM64::finish()
{
    assertDone();
    masm.freeStack(masm.framePushed() - pushedAtStart_);
    MOZ_ASSERT(masm.framePushed() == pushedAtStart_);
}

void
MoveEmitterARM64::emitMove(const MoveOp& move)
{
    const MoveOperand& from = move.from();
    const MoveOperand& to = move.to();

    if (move.isCycleBegin()) {
        MOZ_ASSERT(!inCycle_ && !move.isCycleEnd());
        breakCycle(from, to, move.endCycleType());
        inCycle_ = true;
    } else if (move.isCycleEnd()) {
        MOZ_ASSERT(inCycle_);
        completeCycle(from, to, move.type());
        inCycle_ = false;
        return;
    }

    switch (move.type()) {
      case MoveOp::FLOAT32:
        emitFloat32Move(from, to);
        break;
      case MoveOp::DOUBLE:
        emitDoubleMove(from, to);
        break;
      case MoveOp::INT32:
        emitInt32Move(from, to);
        break;
      case MoveOp::GENERAL:
        emitGeneralMove(from, to);
        break;
      default:
        MOZ_CRASH("Unexpected move type");
    }
}

void
MoveEmitterARM64::emitFloat32Move(const MoveOperand& from, const MoveOperand& to)
{
    if (from.isFloatReg()) {
        if (to.isFloatReg())
            masm.Fmov(toFPReg(to, MoveOp::FLOAT32), toFPReg(from, MoveOp::FLOAT32));
        else
            masm.Str(toFPReg(from, MoveOp::FLOAT32), toMemOperand(to));
        return;
    }

    if (to.isFloatReg()) {
        masm.Ldr(toFPReg(to, MoveOp::FLOAT32), toMemOperand(from));
        return;
    }

    vixl::UseScratchRegisterScope temps(&masm.asVIXL());
    const ARMFPRegister scratch32 = temps.AcquireS();
    masm.Ldr(scratch32, toMemOperand(from));
    masm.Str(scratch32, toMemOperand(to));
}

void
MoveEmitterARM64::emitDoubleMove(const MoveOperand& from, const MoveOperand& to)
{
    if (from.isFloatReg()) {
        if (to.isFloatReg())
            masm.Fmov(toFPReg(to, MoveOp::DOUBLE), toFPReg(from, MoveOp::DOUBLE));
        else
            masm.Str(toFPReg(from, MoveOp::DOUBLE), toMemOperand(to));
        return;
    }

    if (to.isFloatReg()) {
        masm.Ldr(toFPReg(to, MoveOp::DOUBLE), toMemOperand(from));
        return;
    }

    vixl::UseScratchRegisterScope temps(&masm.asVIXL());
    const ARMFPRegister scratch = temps.AcquireD();
    masm.Ldr(scratch, toMemOperand(from));
    masm.Str(scratch, toMemOperand(to));
}

void
MoveEmitterARM64::emitInt32Move(const MoveOperand& from, const MoveOperand& to)
{
    if (from.isGeneralReg()) {
        if (to.isGeneralReg())
            masm.Mov(toARMReg32(to), toARMReg32(from));
        else
            masm.Str(toARMReg32(from), toMemOperand(to));
        return;
    }

    if (to.isGeneralReg()) {
        masm.Ldr(toARMReg32(to), toMemOperand(from));
        return;
    }

    vixl::UseScratchRegisterScope temps(&masm.asVIXL());
    const ARMRegister scratch32 = temps.AcquireW();
    masm.Ldr(scratch32, toMemOperand(from));
    masm.Str(scratch32, toMemOperand(to));
}

void
MoveEmitterARM64::emitGeneralMove(const MoveOperand& from, const MoveOperand& to)
{
    if (from.isGeneralReg()) {
        MOZ_ASSERT(to.isGeneralReg() || to.isMemory());
        if (to.isGeneralReg())
            masm.Mov(toARMReg64(to), toARMReg64(from));
        else
            masm.Str(toARMReg64(from), toMemOperand(to));
        return;
    }

    // {Memory OR EffectiveAddress} -> Register move.
    if (to.isGeneralReg()) {
        MOZ_ASSERT(from.isMemoryOrEffectiveAddress());
        if (from.isMemory())
            masm.Ldr(toARMReg64(to), toMemOperand(from));
        else
            masm.Add(toARMReg64(to), toARMReg64(from), Operand(from.disp()));
        return;
    }

    vixl::UseScratchRegisterScope temps(&masm.asVIXL());
    const ARMRegister scratch64 = temps.AcquireX();

    // Memory -> Memory move.
    if (from.isMemory()) {
        MOZ_ASSERT(to.isMemory());
        masm.Ldr(scratch64, toMemOperand(from));
        masm.Str(scratch64, toMemOperand(to));
        return;
    }

    // EffectiveAddress -> Memory move.
    MOZ_ASSERT(from.isEffectiveAddress());
    MOZ_ASSERT(to.isMemory());
    masm.Add(scratch64, toARMReg64(from), Operand(from.disp()));
    masm.Str(scratch64, toMemOperand(to));
}

MemOperand
MoveEmitterARM64::cycleSlot()
{
    // Using SP as stack pointer requires alignment preservation below.
    MOZ_ASSERT(!masm.GetStackPointer64().Is(sp));

    // emit() already allocated a slot for resolving the cycle.
    MOZ_ASSERT(pushedAtCycle_ != -1);

    return MemOperand(masm.GetStackPointer64(), masm.framePushed() - pushedAtCycle_);
}

void
MoveEmitterARM64::breakCycle(const MoveOperand& from, const MoveOperand& to, MoveOp::Type type)
{
    switch (type) {
      case MoveOp::FLOAT32:
        if (to.isMemory()) {
            vixl::UseScratchRegisterScope temps(&masm.asVIXL());
            const ARMFPRegister scratch32 = temps.AcquireS();
            masm.Ldr(scratch32, toMemOperand(to));
            masm.Str(scratch32, cycleSlot());
        } else {
            masm.Str(toFPReg(to, type), cycleSlot());
        }
        break;

      case MoveOp::DOUBLE:
        if (to.isMemory()) {
            vixl::UseScratchRegisterScope temps(&masm.asVIXL());
            const ARMFPRegister scratch64 = temps.AcquireD();
            masm.Ldr(scratch64, toMemOperand(to));
            masm.Str(scratch64, cycleSlot());
        } else {
            masm.Str(toFPReg(to, type), cycleSlot());
        }
        break;

      case MoveOp::INT32:
        if (to.isMemory()) {
            vixl::UseScratchRegisterScope temps(&masm.asVIXL());
            const ARMRegister scratch32 = temps.AcquireW();
            masm.Ldr(scratch32, toMemOperand(to));
            masm.Str(scratch32, cycleSlot());
        } else {
            masm.Str(toARMReg32(to), cycleSlot());
        }
        break;

      case MoveOp::GENERAL:
        if (to.isMemory()) {
            vixl::UseScratchRegisterScope temps(&masm.asVIXL());
            const ARMRegister scratch64 = temps.AcquireX();
            masm.Ldr(scratch64, toMemOperand(to));
            masm.Str(scratch64, cycleSlot());
        } else {
            masm.Str(toARMReg64(to), cycleSlot());
        }
        break;

      default:
        MOZ_CRASH("Unexpected move type");
    }
}

void
MoveEmitterARM64::completeCycle(const MoveOperand& from, const MoveOperand& to, MoveOp::Type type)
{
    switch (type) {
      case MoveOp::FLOAT32:
        if (to.isMemory()) {
            vixl::UseScratchRegisterScope temps(&masm.asVIXL());
            const ARMFPRegister scratch32 = temps.AcquireS();
            masm.Ldr(scratch32, cycleSlot());
            masm.Str(scratch32, toMemOperand(to));
        } else {
            masm.Ldr(toFPReg(to, type), cycleSlot());
        }
        break;

      case MoveOp::DOUBLE:
        if (to.isMemory()) {
            vixl::UseScratchRegisterScope temps(&masm.asVIXL());
            const ARMFPRegister scratch = temps.AcquireD();
            masm.Ldr(scratch, cycleSlot());
            masm.Str(scratch, toMemOperand(to));
        } else {
            masm.Ldr(toFPReg(to, type), cycleSlot());
        }
        break;

      case MoveOp::INT32:
        if (to.isMemory()) {
            vixl::UseScratchRegisterScope temps(&masm.asVIXL());
            const ARMRegister scratch32 = temps.AcquireW();
            masm.Ldr(scratch32, cycleSlot());
            masm.Str(scratch32, toMemOperand(to));
        } else {
            masm.Ldr(toARMReg64(to), cycleSlot());
        }
        break;

      case MoveOp::GENERAL:
        if (to.isMemory()) {
            vixl::UseScratchRegisterScope temps(&masm.asVIXL());
            const ARMRegister scratch64 = temps.AcquireX();
            masm.Ldr(scratch64, cycleSlot());
            masm.Str(scratch64, toMemOperand(to));
        } else {
            masm.Ldr(toARMReg64(to), cycleSlot());
        }
        break;

      default:
        MOZ_CRASH("Unexpected move type");
    }
}