//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "Backend.h"

///----------------------------------------------------------------------------
///
/// AgenPeeps::PeepFunc
///
///     Looks for AGEN dependencies between a MOV and another instruction. The heuristic works as follows:
///     - Look for an AGEN dependency. If found, move the first instruction upwards in the BB as far as possible.
///     - For subsequent dependencies (dep. chain), we move the instruction at least 3 slots from the previous instruction.
///     - We assume no dependency between different dep. chains.
///     Example:
///         BB_start
///         ...
///         s2 = MOV [s1 + offset]      ; start of dep chain. Move instruction up as far as possible
///         s3 = MOV [s2]               ; inside dep chain. Move instruction up at least 3 slots from prev. instruction
///         s4 = MOV [s3 + offset]      ; inside dep chain. Move instruction up at least 3 slots from prev. instruction
///         ...
///         s5 = MOV [xxxx]             ; new dep chain. Move instruction up as far as possible
///         s6 = MOV [s5 + offset]
///----------------------------------------------------------------------------
void AgenPeeps::PeepFunc()
{
    int distance = 0;
    IR::Instr *blockStart, *nextRealInstr;
    const uint stall_cycles = 3;

    if (AutoSystemInfo::Data.IsAtomPlatform())
    {
        // On Atom, always optimize unless phase is off
        if (PHASE_OFF(Js::AtomPhase, func) || PHASE_OFF(Js::AgenPeepsPhase, func))
            return;
    }
    else
    {
        // On other platforms, don't optimize unless phase is forced
        if (!PHASE_FORCE(Js::AtomPhase, func) && !PHASE_FORCE(Js::AgenPeepsPhase, func))
            return;
    }

    blockStart = nullptr;
    FOREACH_INSTR_IN_FUNC_EDITING(instr, instrNext, this->func)
    {
        if (!instr->IsRealInstr() && !instr->IsLabelInstr())
            continue;

        // BB boundary ?
        if (instr->EndsBasicBlock() || instr->StartsBasicBlock())
        {
            distance = 0;
            instrNext = blockStart = instr->GetNextRealInstr();
            continue;
        }
        nextRealInstr = instr->GetNextRealInstrOrLabel();
        // Check for AGEN dependency with the next instruction in the same BB
        if (!nextRealInstr->EndsBasicBlock() && !nextRealInstr->StartsBasicBlock() &&
            AgenDependentInstrs(instr, nextRealInstr))
        {
            Assert(blockStart);
            // Move instr up
            distance = MoveInstrUp(instr, blockStart, distance) - stall_cycles;
        } else
            distance = 0;

    } NEXT_INSTR_IN_FUNC_EDITING;
}

///----------------------------------------------------------------------------
///
/// AgenPeeps::MoveInstrUp
///
///     Moves an instruction up in the BB up to a bound or until it hits
///     a dependent instruction. If  bound <= 0, move as far as possible.
///----------------------------------------------------------------------------

int AgenPeeps::MoveInstrUp(IR::Instr *instrToMove, IR::Instr *blockStart, int bound)
{
    AssertMsg(LowererMD::IsAssign(instrToMove), "We only reschedule move instructions for now");
    IR::Instr *instr;
    int i = 1;
    if (instrToMove == blockStart || DependentInstrs(instrToMove, instrToMove->GetPrevRealInstr()))
        return 0;

    instr = instrToMove->GetPrevRealInstr();
    instrToMove->Unlink();

    while (!DependentInstrs(instrToMove, instr))
    {
        if (instr == blockStart || (bound > 0 && i == bound))
        {
            instr->InsertBefore(instrToMove);
            return i;
        }
        instr = instr->GetPrevRealInstr();
        i++;
    }
    // Insert after dependent instruction
    instr->InsertAfter(instrToMove);

    return i - 1;
}

///----------------------------------------------------------------------------
///
/// AgenPeeps::AgenDependentInstr
///
///     Determines if two instructions are Agen dependent
///
///----------------------------------------------------------------------------
bool AgenPeeps::AgenDependentInstrs(IR::Instr *instr1, IR::Instr *instr2)
{
    // We only deal with assign instructions for now.
    if (!LowererMD::IsAssign(instr1) || !DependentInstrs(instr1, instr2))
        return false;

    if (instr1->GetDst()->IsRegOpnd())
    {
        IR::IndirOpnd *src1 = (instr2->GetSrc1() && instr2->GetSrc1()->IsIndirOpnd()) ? instr2->GetSrc1()->AsIndirOpnd() : nullptr;
        IR::IndirOpnd *src2 = (instr2->GetSrc2() && instr2->GetSrc2()->IsIndirOpnd()) ? instr2->GetSrc2()->AsIndirOpnd() : nullptr;
        IR::IndirOpnd *dst  = (instr2->GetDst()  && instr2->GetDst()->IsIndirOpnd())  ? instr2->GetDst()->AsIndirOpnd() : nullptr;
        IR::RegOpnd   *regOpnd = instr1->GetDst()->AsRegOpnd();

        IR::RegOpnd *base, *index;
        if (src1)
        {
            base = src1->GetBaseOpnd();
            index = src1->GetIndexOpnd();
            return (base && regOpnd->IsSameRegUntyped(base)) || (index && regOpnd->IsSameRegUntyped(index));
        }

        if (src2)
        {
            base = src2->GetBaseOpnd();
            index = src2->GetIndexOpnd();
            return (base && regOpnd->IsSameRegUntyped(base)) || (index && regOpnd->IsSameRegUntyped(index));
        }

        if (dst)
        {
            base = dst->GetBaseOpnd();
            index = dst->GetIndexOpnd();
            return (base && regOpnd->IsSameRegUntyped(base)) || (index && regOpnd->IsSameRegUntyped(index));
        }
    }
    return false;
}

///----------------------------------------------------------------------------
///
/// AgenPeeps::DependentInstr
///
///     Determines if two instructions are dependent
///
///----------------------------------------------------------------------------
bool AgenPeeps::DependentInstrs(IR::Instr *instr1, IR::Instr *instr2)
{

    if (AlwaysDependent(instr1) || AlwaysDependent(instr2))
        return true;

    // Check for RAW, WAR and WAW dependence
    return \
        DependentOpnds(instr1->GetSrc1(), instr2->GetDst()) ||
        DependentOpnds(instr1->GetSrc2(), instr2->GetDst()) ||
        DependentOpnds(instr1->GetDst(), instr2->GetDst()) ||
        DependentOpnds(instr2->GetSrc1(), instr1->GetDst()) ||
        DependentOpnds(instr2->GetSrc2(), instr1->GetDst()) ||
        (instr1->m_opcode == Js::OpCode::XCHG &&                    // XCHG's src2 is also a dst
            (DependentOpnds(instr2->GetSrc1(), instr1->GetSrc2()) ||
            DependentOpnds(instr2->GetSrc2(), instr1->GetSrc2()))) ||
        (instr2->m_opcode == Js::OpCode::XCHG &&                    // XCHG's src2 is also a dst
            (DependentOpnds(instr1->GetSrc1(), instr2->GetSrc2()) ||
            DependentOpnds(instr1->GetSrc2(), instr2->GetSrc2())));
}

// Being conservative here about instructions that implicitly reads/writes memory/regs without explicit Opnds
bool AgenPeeps::AlwaysDependent(IR::Instr *instr)
{
    return LowererMD::IsCall(instr) ||
        instr->m_opcode == Js::OpCode::PUSH ||
        instr->m_opcode == Js::OpCode::POP ||
        instr->m_opcode == Js::OpCode::DIV ||
        instr->m_opcode == Js::OpCode::IDIV ||
        instr->m_opcode == Js::OpCode::IMUL;
}

///----------------------------------------------------------------------------
///
/// AgenPeeps::DependentOpnd
///
///     Determines if two operands are dependent. This function is commutative.
///
///----------------------------------------------------------------------------
bool AgenPeeps::DependentOpnds(IR::Opnd *opnd1, IR::Opnd *opnd2)
{
#if defined (_M_IX86)
    const RegNum baseReg = RegEBP;
#elif defined (_M_X64)
    const RegNum baseReg = RegRBP;
#else
    AssertMsg(false, "Optimization not supported for ARM");
#endif

    if (!opnd1 || !opnd2)
        return false;

    // Memory dependence
    if (IsMemoryOpnd(opnd1) && IsMemoryOpnd(opnd2))
    {
        IR::SymOpnd *symOpnd1 = opnd1->IsSymOpnd() ? opnd1->AsSymOpnd() : nullptr;
        IR::SymOpnd *symOpnd2 = opnd2->IsSymOpnd() ? opnd2->AsSymOpnd() : nullptr;

        if (symOpnd1 || symOpnd2)
        {
            // SymOpnd do not alias with  MemRefOpnd/IndirOpnd
            if (opnd1->IsMemRefOpnd() || opnd2->IsMemRefOpnd() || opnd1->IsIndirOpnd() || opnd2->IsIndirOpnd())
                return false;

            // Two symOpnds are dependent if they point to the same stack symbol
            if (symOpnd1 && symOpnd2 &&
                symOpnd1->m_sym->IsStackSym() && symOpnd2->m_sym->IsStackSym() )
            {
                return symOpnd1->m_sym->AsStackSym()->m_offset == symOpnd2->m_sym->AsStackSym()->m_offset;
            }
        }
        // all other memory operands are dependent
        return true;
    }

    IR::RegOpnd *regOpnd, *base, *index;

    // Register dependences
    if (opnd1->IsRegOpnd())
    {
        regOpnd = opnd1->AsRegOpnd();
        // reg-to-reg
        if (opnd2->IsRegOpnd() && regOpnd->IsSameRegUntyped(opnd2->AsRegOpnd()))
            return true;

        // opnd2 = [base + indx + offset] and (opnd1 = base or opnd1 = indx)
        if (opnd2->IsIndirOpnd())
        {
            base = opnd2->AsIndirOpnd()->GetBaseOpnd();
            index = opnd2->AsIndirOpnd()->GetIndexOpnd();
            if ( (base && regOpnd->IsSameRegUntyped(base)) || (index && regOpnd->IsSameRegUntyped(index)) )
                return true;
        }
        // opnd1 = ebp/rbp and opnd2 = [ebp/rbp + offset]
        if (opnd2->IsSymOpnd() && opnd2->AsSymOpnd()->m_sym->IsStackSym() && regOpnd->GetReg() == baseReg)
            return true;
    }

    if (opnd2->IsRegOpnd())
    {
        regOpnd = opnd2->AsRegOpnd();

        // opnd1 = [base + indx + offset] and (opnd2 = base or opnd2 = indx)
        if (opnd1->IsIndirOpnd())
        {
            base = opnd1->AsIndirOpnd()->GetBaseOpnd();
            index = opnd1->AsIndirOpnd()->GetIndexOpnd();
            if ( (base && regOpnd->IsSameRegUntyped(base)) || (index && regOpnd->IsSameRegUntyped(index)) )
                return true;
        }

        // opnd2 = ebp/rbp and opnd1 = [ebp/rbp + offset]
        if (opnd1->IsSymOpnd() && opnd1->AsSymOpnd()->m_sym->IsStackSym() && regOpnd->GetReg() == baseReg)
            return true;
    }
    return false;
}

bool AgenPeeps::IsMemoryOpnd(IR::Opnd *opnd)
{
    return opnd->IsSymOpnd() || opnd->IsIndirOpnd() || opnd->IsMemRefOpnd();
}
