//-------------------------------------------------------------------------------------------------------
// 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"

IR::Instr *
SimpleLayout::MoveHelperBlock(IR::Instr * lastOpHelperLabel, uint32 lastOpHelperStatementIndex, Func* lastOpHelperFunc, IR::LabelInstr * nextLabel,
                              IR::Instr * instrAfter)
{
    // Add pragma instructions around the moved code to track source mapping

    Func* pragmatInstrFunc = lastOpHelperFunc ? lastOpHelperFunc : this->func;
    if (instrAfter->IsPragmaInstr() && instrAfter->m_opcode == Js::OpCode::StatementBoundary)
    {
        IR::PragmaInstr* pragmaInstr = instrAfter->AsPragmaInstr();
        pragmaInstr->m_statementIndex = lastOpHelperStatementIndex;
        pragmaInstr->m_func = pragmatInstrFunc;
    }
    else
    {
        IR::PragmaInstr* pragmaInstr = IR::PragmaInstr::New(Js::OpCode::StatementBoundary, lastOpHelperStatementIndex, pragmatInstrFunc);
        instrAfter->InsertAfter(pragmaInstr);
        instrAfter = pragmaInstr;
    }

    // Move all the instruction between lastOpHelperLabel to lastOpHelperInstr
    // to the end of the function

    IR::Instr * lastOpHelperInstr = nextLabel->GetPrevRealInstrOrLabel();
    IR::Instr::MoveRangeAfter(lastOpHelperLabel, lastOpHelperInstr, instrAfter);
    instrAfter = lastOpHelperInstr;

    // Add the jmp back if the lastOpHelperInstr has fall through

    if (instrAfter->HasFallThrough())
    {
        IR::BranchInstr * branchInstr = IR::BranchInstr::New(
            LowererMD::MDUncondBranchOpcode, nextLabel, this->func);
        instrAfter->InsertAfter(branchInstr);
        instrAfter = branchInstr;
    }

    // Add pragma terminating this source mapping range
    IR::PragmaInstr* pragmaInstr = IR::PragmaInstr::New(Js::OpCode::StatementBoundary, Js::Constants::NoStatementIndex, this->func);
    instrAfter->InsertAfter(pragmaInstr);
    instrAfter = pragmaInstr;

    return instrAfter;
}

void
SimpleLayout::Layout()
{
    if (PHASE_OFF(Js::LayoutPhase, this->func) || CONFIG_ISENABLED(Js::DebugFlag))
    {
        return;
    }

    // Do simple layout of helper block.  Push them to after FunctionExit.

    IR::Instr * lastInstr = func->m_tailInstr;
    IR::LabelInstr * lastOpHelperLabel = NULL;
    uint32 lastOpHelperStatementIndex = Js::Constants::NoStatementIndex;
    Func* lastOpHelperFunc = nullptr;
    FOREACH_INSTR_EDITING_IN_RANGE(instr, instrNext, func->m_headInstr, func->m_tailInstr->m_prev)
    {
        if (instr->IsPragmaInstr() && instr->m_opcode == Js::OpCode::StatementBoundary)
        {
            currentStatement = instr->AsPragmaInstr();
        }
        else if (instr->IsLabelInstr())
        {
            IR::LabelInstr * labelInstr = instr->AsLabelInstr();
            if (labelInstr->isOpHelper)
            {
                if (lastOpHelperLabel == NULL)
                {
                    lastOpHelperLabel = labelInstr;
                    lastOpHelperStatementIndex = currentStatement ? currentStatement->m_statementIndex : Js::Constants::NoStatementIndex;
                    lastOpHelperFunc = currentStatement ? currentStatement->m_func : nullptr;
                }
            }
            else if (lastOpHelperLabel != NULL)
            {
                IR::Instr * prevInstr = lastOpHelperLabel->GetPrevRealInstrOrLabel();
                if (prevInstr->IsBranchInstr())
                {
                    // If the previous instruction is to jump around this helper block
                    // Then we move the helper block to the end of the function to
                    // avoid the jmp in the fast path.

                    //      jxx $label          <== prevInstr
                    // $helper:                 <== lastOpHelperLabel
                    //      ...
                    //      ...                 <== lastOpHelperInstr
                    // $label:                  <== labelInstr

                    IR::BranchInstr * prevBranchInstr = prevInstr->AsBranchInstr();
                    if (prevBranchInstr->GetTarget() == labelInstr)
                    {
                        lastInstr = this->MoveHelperBlock(lastOpHelperLabel, lastOpHelperStatementIndex, lastOpHelperFunc, labelInstr, lastInstr);


                        if (prevBranchInstr->IsUnconditional())
                        {
                            // Remove the branch to next after the helper block is moved.
                            prevBranchInstr->Remove();
                        }
                        else
                        {
                            // Reverse the condition
                            LowererMD::InvertBranch(prevBranchInstr);
                            prevBranchInstr->SetTarget(lastOpHelperLabel);
                        }
                    }
                    else if (prevBranchInstr->IsUnconditional())
                    {
                        IR::Instr * prevPrevInstr = prevInstr->GetPrevRealInstrOrLabel();
                        if (prevPrevInstr->IsBranchInstr()
                            && prevPrevInstr->AsBranchInstr()->IsConditional()
                            && prevPrevInstr->AsBranchInstr()->GetTarget() == labelInstr)
                        {

                            //      jcc $label          <== prevPrevInstr
                            //      jmp $blah           <== prevInstr
                            // $helper:                 <== lastOpHelperLabel
                            //      ...
                            //      ...                 <== lastOpHelperInstr
                            // $label:                  <== labelInstr

                            // Transform to

                            //      jncc $blah          <== prevPrevInstr
                            // $label:                  <== labelInstr

                            // $helper:                 <== lastOpHelperLabel
                            //      ...
                            //      ...                 <== lastOpHelperInstr
                            //      jmp $label:         <== labelInstr

                            lastInstr = this->MoveHelperBlock(lastOpHelperLabel, lastOpHelperStatementIndex, lastOpHelperFunc, labelInstr, lastInstr);

                            LowererMD::InvertBranch(prevPrevInstr->AsBranchInstr());
                            prevPrevInstr->AsBranchInstr()->SetTarget(prevBranchInstr->GetTarget());
                            prevBranchInstr->Remove();
                        }
                        else
                        {
                            IR::Instr *lastOpHelperInstr = labelInstr->GetPrevRealInstr();
                            if (lastOpHelperInstr->IsBranchInstr())
                            {
                                IR::BranchInstr *lastOpHelperBranchInstr = lastOpHelperInstr->AsBranchInstr();

                                //      jmp $target         <== prevInstr           //this is unconditional jump
                                // $helper:                 <== lastOpHelperLabel
                                //      ...
                                //      jmp $labeln         <== lastOpHelperInstr   //Conditional/Unconditional jump
                                // $label:                  <== labelInstr

                                lastInstr = this->MoveHelperBlock(lastOpHelperLabel, lastOpHelperStatementIndex, lastOpHelperFunc, labelInstr, lastInstr);
                                //Compensation code if its not unconditional jump
                                if (!lastOpHelperBranchInstr->IsUnconditional())
                                {
                                    IR::BranchInstr *branchInstr = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelInstr, this->func);
                                    lastOpHelperBranchInstr->InsertAfter(branchInstr);
                                }
                            }

                        }
                    }
                }
                lastOpHelperLabel = NULL;
            }
        }
    }
    NEXT_INSTR_EDITING_IN_RANGE;

    func->m_tailInstr = lastInstr;
}
