//-------------------------------------------------------------------------------------------------------
// 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"
#include "Core/CRC.h"
#include "NativeEntryPointData.h"
#include "JitTransferData.h"

///----------------------------------------------------------------------------
///
/// Encoder::Encode
///
///     Main entrypoint of encoder.  Encode each IR instruction into the
///     appropriate machine encoding.
///
///----------------------------------------------------------------------------
void
Encoder::Encode()
{
    NoRecoverMemoryArenaAllocator localArena(_u("BE-Encoder"), m_func->m_alloc->GetPageAllocator(), Js::Throw::OutOfMemory);
    m_tempAlloc = &localArena;

#if ENABLE_OOP_NATIVE_CODEGEN
    class AutoLocalAlloc {
    public:
        AutoLocalAlloc(Func * func) : localXdataAddr(nullptr), localAddress(nullptr), segment(nullptr), func(func) { }
        ~AutoLocalAlloc()
        {
            if (localAddress)
            {
                this->func->GetOOPThreadContext()->GetCodePageAllocators()->FreeLocal(this->localAddress, this->segment);
            }
            if (localXdataAddr)
            {
                this->func->GetOOPThreadContext()->GetCodePageAllocators()->FreeLocal(this->localXdataAddr, this->segment);
            }
        }
        Func * func;
        char * localXdataAddr;
        char * localAddress;
        void * segment;
    } localAlloc(m_func);
#endif

    uint32 instrCount = m_func->GetInstrCount();
    size_t totalJmpTableSizeInBytes = 0;

    JmpTableList * jumpTableListForSwitchStatement = nullptr;

    m_encoderMD.Init(this);
    m_encodeBufferSize = UInt32Math::Mul(instrCount, MachMaxInstrSize);
    m_encodeBufferSize += m_func->m_totalJumpTableSizeInBytesForSwitchStatements;
    m_encodeBuffer = AnewArray(m_tempAlloc, BYTE, m_encodeBufferSize);
#if DBG_DUMP
    m_instrNumber = 0;
    m_offsetBuffer = AnewArray(m_tempAlloc, uint, instrCount);
#endif

    m_pragmaInstrToRecordMap = Anew(m_tempAlloc, PragmaInstrList, m_tempAlloc);
    if (DoTrackAllStatementBoundary())
    {
        // Create a new list, if we are tracking all statement boundaries.
        m_pragmaInstrToRecordOffset = Anew(m_tempAlloc, PragmaInstrList, m_tempAlloc);
    }
    else
    {
        // Set the list to the same as the throw map list, so that processing of the list
        // of pragma are done on those only.
        m_pragmaInstrToRecordOffset = m_pragmaInstrToRecordMap;
    }

#if defined(_M_IX86) || defined(_M_X64)
    // for BR shortening
    m_inlineeFrameRecords = Anew(m_tempAlloc, InlineeFrameRecords, m_tempAlloc);
#endif

    m_pc = m_encodeBuffer;
    m_inlineeFrameMap = Anew(m_tempAlloc, ArenaInlineeFrameMap, m_tempAlloc);
    m_sortedLazyBailoutRecordList = Anew(m_tempAlloc, ArenaLazyBailoutRecordList, m_tempAlloc);

    IR::PragmaInstr* pragmaInstr = nullptr;
    uint32 pragmaOffsetInBuffer = 0;

#ifdef _M_X64
    bool inProlog = false;
#endif
    bool isCallInstr = false;

    // CRC Check to ensure the integrity of the encoded bytes.
    uint initialCRCSeed = 0;
    errno_t err = rand_s(&initialCRCSeed);

    if (err != 0)
    {
        Fatal();
    }

    uint bufferCRC = initialCRCSeed;

    FOREACH_INSTR_IN_FUNC(instr, m_func)
    {
        Assert(Lowerer::ValidOpcodeAfterLower(instr, m_func));

        if (GetCurrentOffset() + MachMaxInstrSize < m_encodeBufferSize)
        {
            ptrdiff_t count;

#if DBG_DUMP
            AssertMsg(m_instrNumber < instrCount, "Bad instr count?");
            __analysis_assume(m_instrNumber < instrCount);
            m_offsetBuffer[m_instrNumber++] = GetCurrentOffset();
#endif
            if (instr->IsPragmaInstr())
            {
                switch (instr->m_opcode)
                {
#ifdef _M_X64
                case Js::OpCode::PrologStart:
                    m_func->m_prologEncoder.Begin(m_pc - m_encodeBuffer);
                    inProlog = true;
                    continue;

                case Js::OpCode::PrologEnd:
                    m_func->m_prologEncoder.End();
                    inProlog = false;
                    continue;
#endif
                case Js::OpCode::StatementBoundary:
                    pragmaOffsetInBuffer = GetCurrentOffset();
                    pragmaInstr = instr->AsPragmaInstr();
                    pragmaInstr->m_offsetInBuffer = pragmaOffsetInBuffer;

                    // will record after BR shortening with adjusted offsets
                    if (DoTrackAllStatementBoundary())
                    {
                        m_pragmaInstrToRecordOffset->Add(pragmaInstr);
                    }

                    break;

                default:
                    continue;
                }
            }
            else if (instr->IsBranchInstr() && instr->AsBranchInstr()->IsMultiBranch())
            {
                Assert(instr->GetSrc1() && instr->GetSrc1()->IsRegOpnd());
                IR::MultiBranchInstr * multiBranchInstr = instr->AsBranchInstr()->AsMultiBrInstr();

                if (multiBranchInstr->m_isSwitchBr &&
                    (multiBranchInstr->m_kind == IR::MultiBranchInstr::IntJumpTable || multiBranchInstr->m_kind == IR::MultiBranchInstr::SingleCharStrJumpTable))
                {
                    BranchJumpTableWrapper * branchJumpTableWrapper = multiBranchInstr->GetBranchJumpTable();
                    if (jumpTableListForSwitchStatement == nullptr)
                    {
                        jumpTableListForSwitchStatement = Anew(m_tempAlloc, JmpTableList, m_tempAlloc);
                    }
                    jumpTableListForSwitchStatement->Add(branchJumpTableWrapper);

                    totalJmpTableSizeInBytes += (branchJumpTableWrapper->tableSize * sizeof(void*));
                }
                else
                {
                    //Reloc Records
                    EncoderMD * encoderMD = &(this->m_encoderMD);
                    multiBranchInstr->MapMultiBrTargetByAddress([=](void ** offset) -> void
                    {
#if defined(_M_ARM32_OR_ARM64)
                        encoderMD->AddLabelReloc((byte*)offset);
#else
                        encoderMD->AppendRelocEntry(RelocTypeLabelUse, (void*)(offset), *(IR::LabelInstr**)(offset));
                        *((size_t*)offset) = 0;
#endif
                    });
                }
            }
            else
            {
                isCallInstr = LowererMD::IsCall(instr);
                if (pragmaInstr && (instr->isInlineeEntryInstr || isCallInstr))
                {
                    // will record throw map after BR shortening with adjusted offsets
                    m_pragmaInstrToRecordMap->Add(pragmaInstr);
                    pragmaInstr = nullptr; // Only once per pragma instr -- do we need to make this record?
                }

                if (instr->HasBailOutInfo())
                {
                    Assert(this->m_func->hasBailout);
                    Assert(LowererMD::IsCall(instr));
                    instr->GetBailOutInfo()->FinalizeBailOutRecord(this->m_func);
                }

                if (instr->isInlineeEntryInstr)
                {

                    m_encoderMD.EncodeInlineeCallInfo(instr, GetCurrentOffset());
                }

                if (instr->m_opcode == Js::OpCode::InlineeStart)
                {
                    Assert(!instr->isInlineeEntryInstr);
                    if (pragmaInstr)
                    {
                        m_pragmaInstrToRecordMap->Add(pragmaInstr);
                        pragmaInstr = nullptr;
                    }
                    Func* inlinee = instr->m_func;
                    if (inlinee->frameInfo && inlinee->frameInfo->record)
                    {
                        inlinee->frameInfo->record->Finalize(inlinee, GetCurrentOffset());

#if defined(_M_IX86) || defined(_M_X64)
                        // Store all records to be adjusted for BR shortening
                        m_inlineeFrameRecords->Add(inlinee->frameInfo->record);
#endif
                    }
                    continue;
                }
            }

            count = m_encoderMD.Encode(instr, m_pc, m_encodeBuffer);
#if defined(_M_IX86) || defined(_M_X64)
            bufferCRC = CalculateCRC(bufferCRC, count, m_pc);
#endif

#if DBG_DUMP
            if (PHASE_TRACE(Js::EncoderPhase, this->m_func))
            {
                instr->Dump((IRDumpFlags)(IRDumpFlags_SimpleForm | IRDumpFlags_SkipEndLine | IRDumpFlags_SkipByteCodeOffset));
                Output::SkipToColumn(80);
                for (BYTE * current = m_pc; current < m_pc + count; current++)
                {
                    Output::Print(_u("%02X "), *current);
                }
                Output::Print(_u("\n"));
                Output::Flush();
            }
#endif
#ifdef _M_X64
            if (inProlog)
                m_func->m_prologEncoder.EncodeInstr(instr, count & 0xFF);
#endif
            m_pc += count;

#if defined(_M_IX86) || defined(_M_X64)
            // for BR shortening.
            if (instr->isInlineeEntryInstr)
                m_encoderMD.AppendRelocEntry(RelocType::RelocTypeInlineeEntryOffset, (void*)(m_pc - MachPtr));
#endif
            if (isCallInstr)
            {
                isCallInstr = false;
                this->RecordInlineeFrame(instr->m_func, GetCurrentOffset());
            }

            if (instr->HasLazyBailOut())
            {
                this->SaveToLazyBailOutRecordList(instr, this->GetCurrentOffset());
            }

            if (instr->m_opcode == Js::OpCode::LazyBailOutThunkLabel)
            {
                this->SaveLazyBailOutThunkOffset(this->GetCurrentOffset());
            }
        }
        else
        {
            Fatal();
        }
    } NEXT_INSTR_IN_FUNC;

    ptrdiff_t codeSize = m_pc - m_encodeBuffer + totalJmpTableSizeInBytes;

    BOOL isSuccessBrShortAndLoopAlign = false;

#if defined(_M_IX86) || defined(_M_X64)
    // Shorten branches. ON by default
    if (!PHASE_OFF(Js::BrShortenPhase, m_func))
    {
        uint brShortenedbufferCRC = initialCRCSeed;
        isSuccessBrShortAndLoopAlign = ShortenBranchesAndLabelAlign(&m_encodeBuffer, &codeSize, &brShortenedbufferCRC, bufferCRC, totalJmpTableSizeInBytes);
        if (isSuccessBrShortAndLoopAlign)
        {
            bufferCRC = brShortenedbufferCRC;
        }
    }
#endif
#if DBG_DUMP | defined(VTUNE_PROFILING)
    if (this->m_func->DoRecordNativeMap())
    {
        // Record PragmaInstr offsets and throw maps
        for (int32 i = 0; i < m_pragmaInstrToRecordOffset->Count(); i++)
        {
            IR::PragmaInstr *inst = m_pragmaInstrToRecordOffset->Item(i);
            inst->Record(inst->m_offsetInBuffer);
        }
    }
#endif

    if (m_pragmaInstrToRecordMap->Count() > 0)
    {
        if (m_func->IsOOPJIT())
        {
            int allocSize = m_pragmaInstrToRecordMap->Count();
            Js::ThrowMapEntry * throwMap = NativeCodeDataNewArrayNoFixup(m_func->GetNativeCodeDataAllocator(), Js::ThrowMapEntry, allocSize);
            for (int i = 0; i < allocSize; i++)
            {
                IR::PragmaInstr *inst = m_pragmaInstrToRecordMap->Item(i);
                throwMap[i].nativeBufferOffset = inst->m_offsetInBuffer;
                throwMap[i].statementIndex = inst->m_statementIndex;
            }
            m_func->GetJITOutput()->RecordThrowMap(throwMap, m_pragmaInstrToRecordMap->Count());
        }
        else
        {
            auto entryPointInfo = m_func->GetInProcJITEntryPointInfo();
            auto functionBody = entryPointInfo->GetFunctionBody();
            Js::SmallSpanSequenceIter iter;
            for (int32 i = 0; i < m_pragmaInstrToRecordMap->Count(); i++)
            {
                IR::PragmaInstr *inst = m_pragmaInstrToRecordMap->Item(i);
                functionBody->RecordNativeThrowMap(iter, inst->m_offsetInBuffer, inst->m_statementIndex, entryPointInfo, Js::LoopHeader::NoLoop);
            }
        }
    }

    // Assembly Dump Phase
    // This phase exists to assist tooling that expects "assemblable" output - that is,
    // output that, with minimal manual handling, could theoretically be fed to another
    // assembler to make a valid function for the target platform. We don't guarantee a
    // dump from this will _actually_ be assemblable, but it is significantly closer to
    // that than our normal, annotated output
#if DBG_DUMP
    if (PHASE_DUMP(Js::AssemblyPhase, m_func))
    {
        FOREACH_INSTR_IN_FUNC(instr, m_func)
        {
            bool hasPrintedForOpnds = false;
            Func* localScopeFuncForLambda = m_func;
            auto printOpnd = [&hasPrintedForOpnds, localScopeFuncForLambda](IR::Opnd* opnd)
            {
                if (hasPrintedForOpnds)
                {
                    Output::Print(_u(", "));
                }
                switch (opnd->m_kind)
                {
                case IR::OpndKindInvalid:
                    AssertMsg(false, "Should be unreachable");
                    break;
                case IR::OpndKindIntConst:
                    Output::Print(_u("%lli"), (long long int)opnd->AsIntConstOpnd()->GetValue());
                    break;
                case IR::OpndKindInt64Const:
                case IR::OpndKindFloatConst:
                case IR::OpndKindFloat32Const:
                case IR::OpndKindSimd128Const:
                    AssertMsg(false, "Not Yet Implemented");
                    break;
                case IR::OpndKindHelperCall:
                    Output::Print(_u("%s"), IR::GetMethodName(opnd->AsHelperCallOpnd()->m_fnHelper));
                    break;
                case IR::OpndKindSym:
                    Output::Print(_u("SYM("));
                    opnd->Dump(IRDumpFlags_SimpleForm, localScopeFuncForLambda);
                    Output::Print(_u(")"));
                    break;
                case IR::OpndKindReg:
                    Output::Print(_u("%S"), RegNames[opnd->AsRegOpnd()->GetReg()]);
                    break;
                case IR::OpndKindAddr:
                    Output::Print(_u("0x%p"), opnd->AsAddrOpnd()->m_address);
                    break;
                case IR::OpndKindIndir:
                {
                    IR::IndirOpnd* indirOpnd = opnd->AsIndirOpnd();
                    IR::RegOpnd* baseOpnd = indirOpnd->GetBaseOpnd();
                    IR::RegOpnd* indexOpnd = indirOpnd->GetIndexOpnd();
                    Output::Print(_u("["));
                    bool hasPrintedComponent = false;
                    if (baseOpnd != nullptr)
                    {
                        Output::Print(_u("%S"), RegNames[baseOpnd->GetReg()]);
                        hasPrintedComponent = true;
                    }
                    if (indexOpnd != nullptr)
                    {
                        if (hasPrintedComponent)
                        {
                            Output::Print(_u(" + "));
                        }
                        Output::Print(_u("%S * %u"), RegNames[indexOpnd->GetReg()], indirOpnd->GetScale());
                        hasPrintedComponent = true;
                    }
                    if (hasPrintedComponent)
                    {
                        Output::Print(_u(" + "));
                    }
                    Output::Print(_u("(%i)]"), indirOpnd->GetOffset());
                    break;
                }
                case IR::OpndKindLabel:
                    opnd->Dump(IRDumpFlags_SimpleForm, localScopeFuncForLambda);
                    break;
                case IR::OpndKindMemRef:
                    opnd->DumpOpndKindMemRef(true, localScopeFuncForLambda);
                    break;
                case IR::OpndKindRegBV:
                    AssertMsg(false, "Should be unreachable");
                    break;
                case IR::OpndKindList:
                    AssertMsg(false, "Should be unreachable");
                    break;
                default:
                    AssertMsg(false, "Missing operand type");
                }
                hasPrintedForOpnds = true;
            };
            switch(instr->GetKind())
            {
            case IR::InstrKindInvalid:
                Assert(false);
                break;
            case IR::InstrKindJitProfiling:
            case IR::InstrKindProfiled:
            case IR::InstrKindInstr:
            {
                Output::SkipToColumn(4);
                Output::Print(_u("%s "), Js::OpCodeUtil::GetOpCodeName(instr->m_opcode));
                Output::SkipToColumn(18);
                IR::Opnd* dst = instr->GetDst();
                IR::Opnd* src1 = instr->GetSrc1();
                IR::Opnd* src2 = instr->GetSrc2();
                if (dst != nullptr && (src1 == nullptr || !dst->IsRegOpnd() || !src1->IsRegOpnd() || dst->AsRegOpnd()->GetReg() != src1->AsRegOpnd()->GetReg())) // Print dst if it's there, and not the same reg as src1 (which is usually an instr that has a srcdest
                {
                    printOpnd(dst);
                }
                if (src1 != nullptr)
                {
                    printOpnd(src1);
                }
                if (src2 != nullptr)
                {
                    printOpnd(src2);
                }
                break;
            }
            case IR::InstrKindBranch:
                Output::SkipToColumn(4);
                Output::Print(_u("%s "), Js::OpCodeUtil::GetOpCodeName(instr->m_opcode));
                Output::SkipToColumn(18);
                if (instr->AsBranchInstr()->IsMultiBranch())
                {
                    Assert(instr->GetSrc1() != nullptr);
                    printOpnd(instr->GetSrc1());
                }
                else
                {
                    Output::Print(_u("L%u"), instr->AsBranchInstr()->GetTarget()->m_id);
                }
                break;
            case IR::InstrKindProfiledLabel:
            case IR::InstrKindLabel:
                Output::Print(_u("L%u:"), instr->AsLabelInstr()->m_id);
                break;
            case IR::InstrKindEntry:
            case IR::InstrKindExit:
            case IR::InstrKindPragma:
                // No output
                break;
            case IR::InstrKindByteCodeUses:
                AssertMsg(false, "Instruction kind shouldn't be present here");
                break;
            default:
                Assert(false);
                break;
            }
            Output::SetAlignAndPrefix(60, _u("; "));
            instr->Dump();
            Output::ResetAlignAndPrefix();
        } NEXT_INSTR_IN_FUNC;
    }
#endif
    // End Assembly Dump Phase

    BEGIN_CODEGEN_PHASE(m_func, Js::EmitterPhase);

    // Copy to permanent buffer.

    Assert(Math::FitsInDWord(codeSize));

    ushort xdataSize;
    ushort pdataCount;
#ifdef _M_X64
    pdataCount = 1;
    xdataSize = (ushort)m_func->m_prologEncoder.SizeOfUnwindInfo();
#elif defined(_M_ARM64)
    pdataCount = 1;
    xdataSize = XDATA_SIZE;
#elif defined(_M_ARM)
    pdataCount = (ushort)m_func->m_unwindInfo.GetPDataCount(codeSize);
    xdataSize = (UnwindInfoManager::MaxXdataBytes + 3) * pdataCount;
#else
    xdataSize = 0;
    pdataCount = 0;
#endif
    OUTPUT_VERBOSE_TRACE(Js::EmitterPhase, _u("PDATA count:%u\n"), pdataCount);
    OUTPUT_VERBOSE_TRACE(Js::EmitterPhase, _u("Size of XDATA:%u\n"), xdataSize);
    OUTPUT_VERBOSE_TRACE(Js::EmitterPhase, _u("Size of code:%u\n"), codeSize);

    TryCopyAndAddRelocRecordsForSwitchJumpTableEntries(m_encodeBuffer, codeSize, jumpTableListForSwitchStatement, totalJmpTableSizeInBytes);

    CustomHeap::Allocation * allocation = nullptr;
    bool inPrereservedRegion = false;
    char * localAddress = nullptr;
#if ENABLE_OOP_NATIVE_CODEGEN
    if (JITManager::GetJITManager()->IsJITServer())
    {
        EmitBufferAllocation<SectionAllocWrapper, PreReservedSectionAllocWrapper> * alloc = m_func->GetJITOutput()->RecordOOPNativeCodeSize(m_func, (DWORD)codeSize, pdataCount, xdataSize);
        allocation = alloc->allocation;
        inPrereservedRegion = alloc->inPrereservedRegion;
        localAlloc.segment = (alloc->bytesCommitted > CustomHeap::Page::MaxAllocationSize) ? allocation->largeObjectAllocation.segment : allocation->page->segment;
        localAddress = m_func->GetOOPThreadContext()->GetCodePageAllocators()->AllocLocal(allocation->address, alloc->bytesCommitted, localAlloc.segment);
        localAlloc.localAddress = localAddress;
        if (localAddress == nullptr)
        {
            Js::Throw::OutOfMemory();
        }
    }
    else
#endif
    {
        EmitBufferAllocation<VirtualAllocWrapper, PreReservedVirtualAllocWrapper> * alloc = m_func->GetJITOutput()->RecordInProcNativeCodeSize(m_func, (DWORD)codeSize, pdataCount, xdataSize);
        allocation = alloc->allocation;
        inPrereservedRegion = alloc->inPrereservedRegion;
        localAddress = allocation->address;
    }

    if (!inPrereservedRegion)
    {
        m_func->GetThreadContextInfo()->ResetIsAllJITCodeInPreReservedRegion();
    }

    // Relocs
    m_encoderMD.ApplyRelocs((size_t)allocation->address, codeSize, &bufferCRC, isSuccessBrShortAndLoopAlign);

    m_func->GetJITOutput()->RecordNativeCode(m_encodeBuffer, (BYTE *)localAddress);

#if defined(_M_IX86) || defined(_M_X64)
    if (!JITManager::GetJITManager()->IsJITServer())
    {
        ValidateCRCOnFinalBuffer((BYTE *)allocation->address, codeSize, totalJmpTableSizeInBytes, m_encodeBuffer, initialCRCSeed, bufferCRC, isSuccessBrShortAndLoopAlign);
    }
#endif

#ifdef TARGET_64
#ifdef _M_X64
    PrologEncoder &unwindInfo = m_func->m_prologEncoder;
    unwindInfo.FinalizeUnwindInfo((BYTE*)m_func->GetJITOutput()->GetCodeAddress(), (DWORD)codeSize);
#else
    UnwindInfoManager &unwindInfo = m_func->m_unwindInfo;
    unwindInfo.FinalizeUnwindInfo((BYTE*)localAddress, (DWORD)codeSize);
#endif

    char * localXdataAddr = nullptr;
#if ENABLE_OOP_NATIVE_CODEGEN
    if (JITManager::GetJITManager()->IsJITServer())
    {
        localXdataAddr = m_func->GetOOPThreadContext()->GetCodePageAllocators()->AllocLocal((char*)allocation->xdata.address, XDATA_SIZE, localAlloc.segment);
        localAlloc.localXdataAddr = localXdataAddr;
        if (localXdataAddr == nullptr)
        {
            Js::Throw::OutOfMemory();
        }
    }
    else
#endif
    {
        localXdataAddr = (char*)allocation->xdata.address;
    }
    m_func->GetJITOutput()->RecordUnwindInfo(
        unwindInfo.GetUnwindInfo(),
        unwindInfo.SizeOfUnwindInfo(),
        allocation->xdata.address,
        (BYTE*)localXdataAddr);
#elif _M_ARM
    m_func->m_unwindInfo.EmitUnwindInfo(m_func->GetJITOutput(), allocation);
    if (m_func->IsOOPJIT())
    {
        size_t allocSize = XDataAllocator::GetAllocSize(allocation->xdata.pdataCount, allocation->xdata.xdataSize);
        BYTE * xprocXdata = NativeCodeDataNewArrayNoFixup(m_func->GetNativeCodeDataAllocator(), BYTE, allocSize);
        memcpy_s(xprocXdata, allocSize, allocation->xdata.address, allocSize);
        m_func->GetJITOutput()->RecordXData(xprocXdata);
    }
    else
    {
        XDataAllocator::Register(&allocation->xdata, m_func->GetJITOutput()->GetCodeAddress(), (DWORD)m_func->GetJITOutput()->GetCodeSize());
        m_func->GetInProcJITEntryPointInfo()->GetNativeEntryPointData()->SetXDataInfo(&allocation->xdata);
    }
    m_func->GetJITOutput()->SetCodeAddress(m_func->GetJITOutput()->GetCodeAddress() | 0x1); // Set thumb mode
#endif

    const bool isSimpleJit = m_func->IsSimpleJit();

    if (this->m_inlineeFrameMap->Count() > 0 &&
        !(this->m_inlineeFrameMap->Count() == 1 && this->m_inlineeFrameMap->Item(0).record == nullptr))
    {
        if (!m_func->IsOOPJIT()) // in-proc JIT
        {
            m_func->GetInProcJITEntryPointInfo()->GetInProcNativeEntryPointData()->RecordInlineeFrameMap(m_inlineeFrameMap);
        }
        else // OOP JIT
        {
            NativeOffsetInlineeFrameRecordOffset* pairs = NativeCodeDataNewArrayZNoFixup(m_func->GetNativeCodeDataAllocator(), NativeOffsetInlineeFrameRecordOffset, this->m_inlineeFrameMap->Count());

            this->m_inlineeFrameMap->Map([&pairs](int i, NativeOffsetInlineeFramePair& p)
            {
                pairs[i].offset = p.offset;
                if (p.record)
                {
                    pairs[i].recordOffset = NativeCodeData::GetDataChunk(p.record)->offset;
                }
                else
                {
                    pairs[i].recordOffset = NativeOffsetInlineeFrameRecordOffset::InvalidRecordOffset;
                }
            });

            m_func->GetJITOutput()->RecordInlineeFrameOffsetsInfo(NativeCodeData::GetDataChunk(pairs)->offset, this->m_inlineeFrameMap->Count());
        }
    }

    this->SaveLazyBailOutJitTransferData();

    if (this->m_func->pinnedTypeRefs != nullptr)
    {
        Assert(!isSimpleJit);
        int pinnedTypeRefCount = this->m_func->pinnedTypeRefs->Count();
        PinnedTypeRefsIDL* pinnedTypeRefs = nullptr;

        if (this->m_func->IsOOPJIT())
        {
            pinnedTypeRefs = (PinnedTypeRefsIDL*)midl_user_allocate(offsetof(PinnedTypeRefsIDL, typeRefs) + sizeof(void*)*pinnedTypeRefCount);
            if (!pinnedTypeRefs)
            {
                Js::Throw::OutOfMemory();
            }
            __analysis_assume(pinnedTypeRefs);

            pinnedTypeRefs->count = pinnedTypeRefCount;
            pinnedTypeRefs->isOOPJIT = true;
        }
        else
        {
            pinnedTypeRefs = HeapNewStructPlus(offsetof(PinnedTypeRefsIDL, typeRefs) + sizeof(void*)*pinnedTypeRefCount - sizeof(PinnedTypeRefsIDL), PinnedTypeRefsIDL);
            pinnedTypeRefs->count = pinnedTypeRefCount;
            pinnedTypeRefs->isOOPJIT = false;
        }

        int index = 0;
        this->m_func->pinnedTypeRefs->Map([&pinnedTypeRefs, &index](void* typeRef) -> void
        {
            pinnedTypeRefs->typeRefs[index++] = ((JITType*)typeRef)->GetAddr();
        });

        if (PHASE_TRACE(Js::TracePinnedTypesPhase, this->m_func))
        {
            char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
            Output::Print(_u("PinnedTypes: function %s(%s) pinned %d types.\n"),
                this->m_func->GetJITFunctionBody()->GetDisplayName(), this->m_func->GetDebugNumberSet(debugStringBuffer), pinnedTypeRefCount);
            Output::Flush();
        }

        this->m_func->GetJITOutput()->GetOutputData()->pinnedTypeRefs = pinnedTypeRefs;
    }

    // Save all equivalent type guards in a fixed size array on the JIT transfer data
    if (this->m_func->equivalentTypeGuards != nullptr)
    {
        AssertMsg(!PHASE_OFF(Js::EquivObjTypeSpecPhase, this->m_func), "Why do we have equivalent type guards if we don't do equivalent object type spec?");

        int equivalentTypeGuardsCount = this->m_func->equivalentTypeGuards->Count();

        if (this->m_func->IsOOPJIT())
        {
            auto& equivalentTypeGuardOffsets = this->m_func->GetJITOutput()->GetOutputData()->equivalentTypeGuardOffsets;
            size_t allocSize = offsetof(EquivalentTypeGuardOffsets, guards) + equivalentTypeGuardsCount * sizeof(EquivalentTypeGuardIDL);
            equivalentTypeGuardOffsets = (EquivalentTypeGuardOffsets*)midl_user_allocate(allocSize);
            if (equivalentTypeGuardOffsets == nullptr)
            {
                Js::Throw::OutOfMemory();
            }

            equivalentTypeGuardOffsets->count = equivalentTypeGuardsCount;

            int i = 0;
            this->m_func->equivalentTypeGuards->Map([&equivalentTypeGuardOffsets, &i](Js::JitEquivalentTypeGuard* srcGuard) -> void
            {
                equivalentTypeGuardOffsets->guards[i].offset = NativeCodeData::GetDataTotalOffset(srcGuard);

                auto cache = srcGuard->GetCache();
                equivalentTypeGuardOffsets->guards[i].cache.guardOffset = NativeCodeData::GetDataTotalOffset(cache->guard);
                equivalentTypeGuardOffsets->guards[i].cache.hasFixedValue = cache->hasFixedValue;
                equivalentTypeGuardOffsets->guards[i].cache.isLoadedFromProto = cache->isLoadedFromProto;
                equivalentTypeGuardOffsets->guards[i].cache.nextEvictionVictim = cache->nextEvictionVictim;
                equivalentTypeGuardOffsets->guards[i].cache.record.propertyCount = cache->record.propertyCount;
                equivalentTypeGuardOffsets->guards[i].cache.record.propertyOffset = NativeCodeData::GetDataTotalOffset(cache->record.properties);
                for (int j = 0; j < EQUIVALENT_TYPE_CACHE_SIZE; j++)
                {
                    equivalentTypeGuardOffsets->guards[i].cache.types[j] = (intptr_t)PointerValue(cache->types[j]);
                }
                i++;
            });
            Assert(equivalentTypeGuardsCount == i);
        }
        else
        {
            Js::JitEquivalentTypeGuard** guards = HeapNewArrayZ(Js::JitEquivalentTypeGuard*, equivalentTypeGuardsCount);
            Js::JitEquivalentTypeGuard** dstGuard = guards;
            this->m_func->equivalentTypeGuards->Map([&dstGuard](Js::JitEquivalentTypeGuard* srcGuard) -> void
            {
                *dstGuard++ = srcGuard;
            });
            m_func->GetInProcJITEntryPointInfo()->GetJitTransferData()->SetEquivalentTypeGuards(guards, equivalentTypeGuardsCount);
        }
    }

    // Save all property guards on the JIT transfer data in a map keyed by property ID. We will use this map when installing the entry
    // point to register each guard for invalidation.
    if (this->m_func->propertyGuardsByPropertyId != nullptr)
    {
        Assert(!isSimpleJit);
        AssertMsg(!(PHASE_OFF(Js::ObjTypeSpecPhase, this->m_func) && PHASE_OFF(Js::FixedMethodsPhase, this->m_func)),
            "Why do we have type guards if we don't do object type spec or fixed methods?");

#if DBG
        int totalGuardCount = (this->m_func->singleTypeGuards != nullptr ? this->m_func->singleTypeGuards->Count() : 0)
            + (this->m_func->equivalentTypeGuards != nullptr ? this->m_func->equivalentTypeGuards->Count() : 0);
        Assert(totalGuardCount > 0);
        Assert(totalGuardCount == this->m_func->indexedPropertyGuardCount);
#endif


        if (!this->m_func->IsOOPJIT())
        {
            int propertyCount = this->m_func->propertyGuardsByPropertyId->Count();
            Assert(propertyCount > 0);

            int guardSlotCount = 0;
            this->m_func->propertyGuardsByPropertyId->Map([&guardSlotCount](Js::PropertyId propertyId, Func::IndexedPropertyGuardSet* set) -> void
            {
                guardSlotCount += set->Count();
            });

            size_t typeGuardTransferSize =                              // Reserve enough room for:
                propertyCount * sizeof(Js::TypeGuardTransferEntry) +    //   each propertyId,
                propertyCount * sizeof(Js::JitIndexedPropertyGuard*) +  //   terminating nullptr guard for each propertyId,
                guardSlotCount * sizeof(Js::JitIndexedPropertyGuard*);  //   a pointer for each guard we counted above.

            // The extra room for sizeof(Js::TypePropertyGuardEntry) allocated by HeapNewPlus will be used for the terminating invalid propertyId.
            // Review (jedmiad): Skip zeroing?  This is heap allocated so there shouldn't be any false recycler references.
            Js::TypeGuardTransferEntry* typeGuardTransferRecord = HeapNewPlusZ(typeGuardTransferSize, Js::TypeGuardTransferEntry);

            Func* func = this->m_func;

            Js::TypeGuardTransferEntry* dstEntry = typeGuardTransferRecord;
            this->m_func->propertyGuardsByPropertyId->Map([func, &dstEntry](Js::PropertyId propertyId, Func::IndexedPropertyGuardSet* srcSet) -> void
            {
                dstEntry->propertyId = propertyId;

                int guardIndex = 0;

                srcSet->Map([dstEntry, &guardIndex](Js::JitIndexedPropertyGuard* guard) -> void
                {
                    dstEntry->guards[guardIndex++] = guard;
                });

                dstEntry->guards[guardIndex++] = nullptr;
                dstEntry = reinterpret_cast<Js::TypeGuardTransferEntry*>(&dstEntry->guards[guardIndex]);
            });
            dstEntry->propertyId = Js::Constants::NoProperty;
            dstEntry++;

            Assert(reinterpret_cast<char*>(dstEntry) <= reinterpret_cast<char*>(typeGuardTransferRecord) + typeGuardTransferSize + sizeof(Js::TypeGuardTransferEntry));

            m_func->GetInProcJITEntryPointInfo()->GetJitTransferData()->RecordTypeGuards(this->m_func->indexedPropertyGuardCount, typeGuardTransferRecord, typeGuardTransferSize);
        }
        else
        {
            Func* func = this->m_func;
            this->m_func->GetJITOutput()->GetOutputData()->propertyGuardCount = this->m_func->indexedPropertyGuardCount;
            auto entry = &this->m_func->GetJITOutput()->GetOutputData()->typeGuardEntries;

            this->m_func->propertyGuardsByPropertyId->Map([func, &entry](Js::PropertyId propertyId, Func::IndexedPropertyGuardSet* srcSet) -> void
            {
                auto count = srcSet->Count();
                (*entry) = (TypeGuardTransferEntryIDL*)midl_user_allocate(offsetof(TypeGuardTransferEntryIDL, guardOffsets) + count*sizeof(int));
                if (!*entry)
                {
                    Js::Throw::OutOfMemory();
                }
                __analysis_assume(*entry);
                (*entry)->propId = propertyId;
                (*entry)->guardsCount = count;
                (*entry)->next = nullptr;

                auto& guardOffsets = (*entry)->guardOffsets;
                int guardIndex = 0;
                srcSet->Map([&guardOffsets, &guardIndex](Js::JitIndexedPropertyGuard* guard) -> void
                {
                    guardOffsets[guardIndex++] = NativeCodeData::GetDataTotalOffset(guard);
                });
                Assert(guardIndex == count);
                entry = &(*entry)->next;
            });

        }
    }

    // Save all constructor caches on the JIT transfer data in a map keyed by property ID. We will use this map when installing the entry
    // point to register each cache for invalidation.
    if (this->m_func->ctorCachesByPropertyId != nullptr)
    {
        Assert(!isSimpleJit);

        AssertMsg(!(PHASE_OFF(Js::ObjTypeSpecPhase, this->m_func) && PHASE_OFF(Js::FixedMethodsPhase, this->m_func)),
            "Why do we have constructor cache guards if we don't do object type spec or fixed methods?");

        int propertyCount = this->m_func->ctorCachesByPropertyId->Count();
        Assert(propertyCount > 0);

        int cacheSlotCount = 0;
        this->m_func->ctorCachesByPropertyId->Map([&cacheSlotCount](Js::PropertyId propertyId, Func::CtorCacheSet* cacheSet) -> void
        {
            cacheSlotCount += cacheSet->Count();
        });

        if (m_func->IsOOPJIT())
        {
            Func* func = this->m_func;
            m_func->GetJITOutput()->GetOutputData()->ctorCachesCount = propertyCount;
            m_func->GetJITOutput()->GetOutputData()->ctorCacheEntries = (CtorCacheTransferEntryIDL**)midl_user_allocate(propertyCount * sizeof(CtorCacheTransferEntryIDL*));
            CtorCacheTransferEntryIDL** entries = m_func->GetJITOutput()->GetOutputData()->ctorCacheEntries;
            if (!entries)
            {
                Js::Throw::OutOfMemory();
            }
            __analysis_assume(entries);

            uint propIndex = 0;
            m_func->ctorCachesByPropertyId->Map([func, entries, &propIndex](Js::PropertyId propertyId, Func::CtorCacheSet* srcCacheSet) -> void
            {
                entries[propIndex] = (CtorCacheTransferEntryIDL*)midl_user_allocate(srcCacheSet->Count() * sizeof(intptr_t) + sizeof(CtorCacheTransferEntryIDL));
                if (!entries[propIndex])
                {
                    Js::Throw::OutOfMemory();
                }
                __analysis_assume(entries[propIndex]);
                entries[propIndex]->propId = propertyId;

                int cacheIndex = 0;

                srcCacheSet->Map([entries, propIndex, &cacheIndex](intptr_t cache) -> void
                {
                    entries[propIndex]->caches[cacheIndex++] = cache;
                });

                entries[propIndex]->cacheCount = cacheIndex;
                propIndex++;
            });
        }
        else
        {
            Assert(m_func->GetInProcJITEntryPointInfo()->GetNativeEntryPointData()->GetConstructorCacheCount() > 0);

            size_t ctorCachesTransferSize =                                // Reserve enough room for:
                propertyCount * sizeof(Js::CtorCacheGuardTransferEntry) +  //   each propertyId,
                propertyCount * sizeof(Js::ConstructorCache*) +            //   terminating null cache for each propertyId,
                cacheSlotCount * sizeof(Js::JitIndexedPropertyGuard*);     //   a pointer for each cache we counted above.

            // The extra room for sizeof(Js::CtorCacheGuardTransferEntry) allocated by HeapNewPlus will be used for the terminating invalid propertyId.
            // Review (jedmiad): Skip zeroing?  This is heap allocated so there shouldn't be any false recycler references.
            Js::CtorCacheGuardTransferEntry* ctorCachesTransferRecord = HeapNewPlusZ(ctorCachesTransferSize, Js::CtorCacheGuardTransferEntry);

            Func* func = this->m_func;

            Js::CtorCacheGuardTransferEntry* dstEntry = ctorCachesTransferRecord;
            this->m_func->ctorCachesByPropertyId->Map([func, &dstEntry](Js::PropertyId propertyId, Func::CtorCacheSet* srcCacheSet) -> void
            {
                dstEntry->propertyId = propertyId;

                int cacheIndex = 0;

                srcCacheSet->Map([dstEntry, &cacheIndex](intptr_t cache) -> void
                {
                    dstEntry->caches[cacheIndex++] = cache;
                });

                dstEntry->caches[cacheIndex++] = 0;
                dstEntry = reinterpret_cast<Js::CtorCacheGuardTransferEntry*>(&dstEntry->caches[cacheIndex]);
            });
            dstEntry->propertyId = Js::Constants::NoProperty;
            dstEntry++;

            Assert(reinterpret_cast<char*>(dstEntry) <= reinterpret_cast<char*>(ctorCachesTransferRecord) + ctorCachesTransferSize + sizeof(Js::CtorCacheGuardTransferEntry));

            m_func->GetInProcJITEntryPointInfo()->GetJitTransferData()->RecordCtorCacheGuards(ctorCachesTransferRecord, ctorCachesTransferSize);
        }
    }
    m_func->GetJITOutput()->FinalizeNativeCode();

    END_CODEGEN_PHASE(m_func, Js::EmitterPhase);

#if DBG_DUMP

    m_func->m_codeSize = codeSize;
    if (PHASE_DUMP(Js::EncoderPhase, m_func) || PHASE_DUMP(Js::BackEndPhase, m_func))
    {
        bool dumpIRAddressesValue = Js::Configuration::Global.flags.DumpIRAddresses;
        Js::Configuration::Global.flags.DumpIRAddresses = true;

        this->m_func->DumpHeader();

        m_instrNumber = 0;
        FOREACH_INSTR_IN_FUNC(instr, m_func)
        {
            __analysis_assume(m_instrNumber < instrCount);
            instr->DumpGlobOptInstrString();
#ifdef TARGET_64
            Output::Print(_u("%12IX  "), m_offsetBuffer[m_instrNumber++] + (BYTE *)m_func->GetJITOutput()->GetCodeAddress());
#else
            Output::Print(_u("%8IX  "), m_offsetBuffer[m_instrNumber++] + (BYTE *)m_func->GetJITOutput()->GetCodeAddress());
#endif
            instr->Dump();
        } NEXT_INSTR_IN_FUNC;
        Output::Flush();

        Js::Configuration::Global.flags.DumpIRAddresses = dumpIRAddressesValue;
    }

    if (PHASE_DUMP(Js::EncoderPhase, m_func) && Js::Configuration::Global.flags.Verbose && !m_func->IsOOPJIT())
    {
        this->DumpInlineeFrameMap(m_func->GetJITOutput()->GetCodeAddress());
        Output::Flush();
    }
#endif
}

bool Encoder::DoTrackAllStatementBoundary() const
{
#if DBG_DUMP | defined(VTUNE_PROFILING)
    return this->m_func->DoRecordNativeMap();
#else
    return false;
#endif
}

void Encoder::TryCopyAndAddRelocRecordsForSwitchJumpTableEntries(BYTE *codeStart, size_t codeSize, JmpTableList * jumpTableListForSwitchStatement, size_t totalJmpTableSizeInBytes)
{
    if (jumpTableListForSwitchStatement == nullptr)
    {
        return;
    }

    BYTE * jmpTableStartAddress = codeStart + codeSize - totalJmpTableSizeInBytes;
    EncoderMD * encoderMD = &m_encoderMD;

    jumpTableListForSwitchStatement->Map([&](uint index, BranchJumpTableWrapper * branchJumpTableWrapper) -> void
    {
        Assert(branchJumpTableWrapper != nullptr);

        void ** srcJmpTable = branchJumpTableWrapper->jmpTable;
        size_t jmpTableSizeInBytes = branchJumpTableWrapper->tableSize * sizeof(void*);

        AssertMsg(branchJumpTableWrapper->labelInstr != nullptr, "Label not yet created?");
        Assert(branchJumpTableWrapper->labelInstr->GetPC() == nullptr);

        branchJumpTableWrapper->labelInstr->SetPC(jmpTableStartAddress);
        memcpy(jmpTableStartAddress, srcJmpTable, jmpTableSizeInBytes);

        for (int i = 0; i < branchJumpTableWrapper->tableSize; i++)
        {
            void * addressOfJmpTableEntry = jmpTableStartAddress + (i * sizeof(void*));
            Assert((ptrdiff_t) addressOfJmpTableEntry - (ptrdiff_t) jmpTableStartAddress < (ptrdiff_t) jmpTableSizeInBytes);
#if defined(_M_ARM32_OR_ARM64)
            encoderMD->AddLabelReloc((byte*) addressOfJmpTableEntry);
#else
            encoderMD->AppendRelocEntry(RelocTypeLabelUse, addressOfJmpTableEntry, *(IR::LabelInstr**)addressOfJmpTableEntry);
            *((size_t*)addressOfJmpTableEntry) = 0;
#endif
        }

        jmpTableStartAddress += (jmpTableSizeInBytes);
    });

    Assert(jmpTableStartAddress == codeStart + codeSize);
}

uint32 Encoder::GetCurrentOffset() const
{
    Assert(m_pc - m_encodeBuffer <= UINT_MAX);      // encode buffer size is uint32
    return static_cast<uint32>(m_pc - m_encodeBuffer);
}

void Encoder::RecordInlineeFrame(Func* inlinee, uint32 currentOffset)
{
    // The only restriction for not supporting loop bodies is that inlinee frame map is created on FunctionEntryPointInfo & not
    // the base class EntryPointInfo.
    if (!(this->m_func->IsLoopBody() && PHASE_OFF(Js::InlineInJitLoopBodyPhase, this->m_func)) && !this->m_func->IsSimpleJit())
    {
        InlineeFrameRecord* record = nullptr;
        if (inlinee->frameInfo)
        {
            record = inlinee->frameInfo->record;
            Assert(record != nullptr);
        }
        if (m_inlineeFrameMap->Count() > 0)
        {
            // update existing record if the entry is the same.
            NativeOffsetInlineeFramePair& lastPair = m_inlineeFrameMap->Item(m_inlineeFrameMap->Count() - 1);

            if (lastPair.record == record)
            {
                lastPair.offset = currentOffset;
                return;
            }
        }
        NativeOffsetInlineeFramePair pair = { currentOffset, record };
        m_inlineeFrameMap->Add(pair);
    }
}

#if defined(_M_IX86) || defined(_M_X64)
/*
*   ValidateCRCOnFinalBuffer
*       - Validates the CRC that is last computed (could be either the one after BranchShortening or after encoding itself)
*       - We calculate the CRC for jump table and dictionary after computing the code section.
*       - Also, all reloc data are computed towards the end - after computing the code section - because we don't have to deal with the changes relocs while operating on the code section.
*       - The version of CRC that we are validating with, doesn't have Relocs applied but the final buffer does - So we have to make adjustments while calculating the final buffer's CRC.
*/
void Encoder::ValidateCRCOnFinalBuffer(_In_reads_bytes_(finalCodeSize) BYTE * finalCodeBufferStart, size_t finalCodeSize, size_t jumpTableSize, _In_reads_bytes_(finalCodeSize) BYTE * oldCodeBufferStart, uint initialCrcSeed, uint bufferCrcToValidate, BOOL isSuccessBrShortAndLoopAlign)
{
    RelocList * relocList = m_encoderMD.GetRelocList();

    BYTE * currentStartAddress = finalCodeBufferStart;
    BYTE * currentEndAddress = nullptr;
    size_t crcSizeToCompute = 0;

    size_t finalCodeSizeWithoutJumpTable = finalCodeSize - jumpTableSize;

    uint finalBufferCRC = initialCrcSeed;

    BYTE * oldPtr = nullptr;

    if (relocList != nullptr)
    {
        for (int index = 0; index < relocList->Count(); index++)
        {
            EncodeRelocAndLabels * relocTuple = &relocList->Item(index);

            //We will deal with the jump table and dictionary entries along with other reloc records in ApplyRelocs()
            if ((BYTE*)m_encoderMD.GetRelocBufferAddress(relocTuple) >= oldCodeBufferStart && (BYTE*)m_encoderMD.GetRelocBufferAddress(relocTuple) < (oldCodeBufferStart + finalCodeSizeWithoutJumpTable))
            {
                BYTE* finalBufferRelocTuplePtr = (BYTE*)m_encoderMD.GetRelocBufferAddress(relocTuple) - oldCodeBufferStart + finalCodeBufferStart;
                Assert(finalBufferRelocTuplePtr >= finalCodeBufferStart && finalBufferRelocTuplePtr < (finalCodeBufferStart + finalCodeSizeWithoutJumpTable));
                uint relocDataSize = m_encoderMD.GetRelocDataSize(relocTuple);
                if (relocDataSize != 0)
                {
                    AssertMsg(oldPtr == nullptr || oldPtr < finalBufferRelocTuplePtr, "Assumption here is that the reloc list is strictly increasing in terms of bufferAddress");
                    oldPtr = finalBufferRelocTuplePtr;

                    currentEndAddress = finalBufferRelocTuplePtr;
                    crcSizeToCompute = currentEndAddress - currentStartAddress;

                    Assert(currentEndAddress >= currentStartAddress);

                    finalBufferCRC = CalculateCRC(finalBufferCRC, crcSizeToCompute, currentStartAddress);
                    for (uint i = 0; i < relocDataSize; i++)
                    {
                        finalBufferCRC = CalculateCRC(finalBufferCRC, 0);
                    }
                    currentStartAddress = currentEndAddress + relocDataSize;
                }
            }
        }
    }

    currentEndAddress = finalCodeBufferStart + finalCodeSizeWithoutJumpTable;
    crcSizeToCompute = currentEndAddress - currentStartAddress;

    Assert(currentEndAddress >= currentStartAddress);

    finalBufferCRC = CalculateCRC(finalBufferCRC, crcSizeToCompute, currentStartAddress);

    //Include all offsets from the reloc records to the CRC.
    m_encoderMD.ApplyRelocs((size_t)finalCodeBufferStart, finalCodeSize, &finalBufferCRC, isSuccessBrShortAndLoopAlign, true);

    if (finalBufferCRC != bufferCrcToValidate)
    {
        Assert(false);
        Fatal();
    }
}
#endif

/*
*   EnsureRelocEntryIntegrity
*       - We compute the target address as the processor would compute it and check if the target is within the final buffer's bounds.
*       - For relative addressing, Target = current m_pc + offset
*       - For absolute addressing, Target = direct address
*/
void Encoder::EnsureRelocEntryIntegrity(size_t newBufferStartAddress, size_t codeSize, size_t oldBufferAddress, size_t relocAddress, uint offsetBytes, ptrdiff_t opndData, bool isRelativeAddr)
{
    size_t targetBrAddress = 0;
    size_t newBufferEndAddress = newBufferStartAddress + codeSize;

    //Handle Dictionary addresses here - The target address will be in the dictionary.
    if (relocAddress < oldBufferAddress || relocAddress >= (oldBufferAddress + codeSize))
    {
        targetBrAddress = (size_t)(*(size_t*)relocAddress);
    }
    else
    {
        size_t newBufferRelocAddr = relocAddress - oldBufferAddress + newBufferStartAddress;

        if (isRelativeAddr)
        {
            targetBrAddress = (size_t)newBufferRelocAddr + offsetBytes + opndData;
        }
        else  // Absolute Address
        {
            targetBrAddress = (size_t)opndData;
        }
    }

    if (targetBrAddress < newBufferStartAddress || targetBrAddress >= newBufferEndAddress)
    {
        Assert(false);
        Fatal();
    }
}

void Encoder::ValidateCRC(uint bufferCRC, uint initialCRCSeed, _In_reads_bytes_(count) void* buffer, size_t count)
{
    uint validationCRC = initialCRCSeed;

    validationCRC = CalculateCRC(validationCRC, count, buffer);

    if (validationCRC != bufferCRC)
    {
        //TODO: This throws internal error. Is this error type, Fine?
        Fatal();
    }
}

#if defined(_M_IX86) || defined(_M_X64)
///----------------------------------------------------------------------------
///
/// EncoderMD::ShortenBranchesAndLabelAlign
/// We try to shorten branches if the label instr is within 8-bits target range (-128 to 127)
/// and fix the relocList accordingly.
/// Also align LoopTop Label and TryCatchLabel
///----------------------------------------------------------------------------
BOOL
Encoder::ShortenBranchesAndLabelAlign(BYTE **codeStart, ptrdiff_t *codeSize, uint * pShortenedBufferCRC, uint bufferCrcToValidate, size_t jumpTableSize)
{
#ifdef  ENABLE_DEBUG_CONFIG_OPTIONS
    static uint32 globalTotalBytesSaved = 0, globalTotalBytesWithoutShortening = 0;
    static uint32 globalTotalBytesInserted = 0; // loop alignment nops
#endif

    uint32 brShortenedCount = 0;
    bool   codeChange       = false; // any overall BR shortened or label aligned ?

    BYTE* buffStart = *codeStart;
    BYTE* buffEnd = buffStart + *codeSize;
    ptrdiff_t newCodeSize = *codeSize;

    RelocList* relocList = m_encoderMD.GetRelocList();

    if (relocList == nullptr)
    {
        return false;
    }

#if DBG
    // Sanity check
    m_encoderMD.VerifyRelocList(buffStart, buffEnd);
#endif

    // Copy of original maps. Used to revert from BR shortening.
    OffsetList  *m_origInlineeFrameRecords = nullptr,
        *m_origInlineeFrameMap = nullptr,
        *m_origPragmaInstrToRecordOffset = nullptr;

    OffsetList  *m_origOffsetBuffer = nullptr;

    // we record the original maps, in case we have to revert.
    CopyMaps<false>(&m_origInlineeFrameRecords
        , &m_origInlineeFrameMap
        , &m_origPragmaInstrToRecordOffset
        , &m_origOffsetBuffer );

    // Here we mark BRs to be shortened and adjust Labels and relocList entries offsets.
    FixUpMapIndex mapIndices;

    int32 totalBytesSaved = 0;

    // loop over all BRs, find the ones we can convert to short form
    for (int32 j = 0; j < relocList->Count(); j++)
    {
        IR::LabelInstr *targetLabel;
        int32 relOffset;
        uint32 bytesSaved = 0;
        BYTE* labelPc, *opcodeByte;
        BYTE* shortBrPtr, *fixedBrPtr; // without shortening

        EncodeRelocAndLabels &reloc = relocList->Item(j);

        // If not a long branch, just fix the reloc entry and skip.
        if (!reloc.isLongBr())
        {
            // if loop alignment is required, total bytes saved can change
            int32 newTotalBytesSaved = m_encoderMD.FixRelocListEntry(j, totalBytesSaved, buffStart, buffEnd);

            if (newTotalBytesSaved != totalBytesSaved)
            {
                AssertMsg(reloc.isAlignedLabel(), "Expecting aligned label.");
                // we aligned a loop, fix maps
                m_encoderMD.FixMaps((uint32)(reloc.getLabelOrigPC() - buffStart), totalBytesSaved, &mapIndices);
                codeChange = true;
            }
            totalBytesSaved = newTotalBytesSaved;
            continue;
        }

        AssertMsg(reloc.isLongBr(), "Cannot shorten already shortened branch.");
        // long branch
        opcodeByte = reloc.getBrOpCodeByte();
        targetLabel = reloc.getBrTargetLabel();
        AssertMsg(targetLabel != nullptr, "Branch to non-existing label");

        labelPc = targetLabel->GetPC();

        // compute the new offset of that Br because of previous shortening/alignment
        shortBrPtr = fixedBrPtr = (BYTE*)reloc.m_ptr - totalBytesSaved;

        if (*opcodeByte == 0xe9 /* JMP rel32 */)
        {
            bytesSaved = 3;
        }
        else if (*opcodeByte >= 0x80 && *opcodeByte < 0x90 /* Jcc rel32 */)
        {
            Assert(*(opcodeByte - 1) == 0x0f);
            bytesSaved = 4;
            // Jcc rel8 is one byte shorter in opcode, fix Br ptr to point to start of rel8
            shortBrPtr--;
        }
        else
        {
            Assert(false);
        }

        // compute current distance to label
        if (labelPc >= (BYTE*) reloc.m_ptr)
        {
            // forward Br. We compare using the unfixed m_ptr, because the label is ahead and its Pc is not fixed it.
            relOffset = (int32)(labelPc - ((BYTE*)reloc.m_ptr + 4));
        }
        else
        {
            // backward Br. We compute relOffset after fixing the Br, since the label is already fixed.
            // We also include the 3-4 bytes saved after shortening the Br since the Br itself is included in the relative offset.
            relOffset =  (int32)(labelPc - (shortBrPtr + 1));
        }

        // update Br offset (overwritten later if Br is shortened)
        reloc.m_ptr = fixedBrPtr;

        // can we shorten ?
        if (relOffset >= -128 && relOffset <= 127)
        {
            uint32 brOffset;

            brShortenedCount++;
            // update with shortened br offset
            reloc.m_ptr = shortBrPtr;

            // fix all maps entries from last shortened br to this one, before updating total bytes saved.
            brOffset = (uint32) ((BYTE*)reloc.m_origPtr - buffStart);
            m_encoderMD.FixMaps(brOffset, totalBytesSaved, &mapIndices);
            codeChange = true;
            totalBytesSaved += bytesSaved;

            // mark br reloc entry as shortened
#ifdef _M_IX86
            reloc.setAsShortBr(targetLabel);
#else
            reloc.setAsShortBr();
#endif
        }
    }

    // Fix the rest of the maps, if needed.
    if (totalBytesSaved != 0)
    {
        m_encoderMD.FixMaps((uint32)-1, totalBytesSaved, &mapIndices);
        codeChange = true;
        newCodeSize -= totalBytesSaved;
        this->FixLazyBailOutThunkOffset(totalBytesSaved);
    }

    // no BR shortening or Label alignment happened, no need to copy code
    if (!codeChange)
        return codeChange;

#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
    globalTotalBytesWithoutShortening += (uint32)(*codeSize);
    globalTotalBytesSaved += (uint32)(*codeSize - newCodeSize);

    if (PHASE_TRACE(Js::BrShortenPhase, this->m_func))
    {
        OUTPUT_VERBOSE_TRACE(Js::BrShortenPhase, _u("func: %s, bytes saved: %d, bytes saved %%:%.2f, total bytes saved: %d, total bytes saved%%: %.2f, BR shortened: %d\n"),
            this->m_func->GetJITFunctionBody()->GetDisplayName(), (*codeSize - newCodeSize), ((float)*codeSize - newCodeSize) / *codeSize * 100,
            globalTotalBytesSaved, ((float)globalTotalBytesSaved) / globalTotalBytesWithoutShortening * 100 , brShortenedCount);
        Output::Flush();
    }
#endif

    // At this point BRs are marked to be shortened, and relocList offsets are adjusted to new instruction length.
    // Next, we re-write the code to shorten the BRs and adjust relocList offsets to point to new buffer.
    // We also write NOPs for aligned loops.
    BYTE* tmpBuffer = AnewArray(m_tempAlloc, BYTE, newCodeSize);

    uint srcBufferCrc = *pShortenedBufferCRC;   //This has the intial Random CRC seed to start with.

    // start copying to new buffer
    // this can possibly be done during fixing, but there is no evidence it is an overhead to justify the complexity.
    BYTE *from = buffStart, *to = nullptr;
    BYTE *dst_p = (BYTE*)tmpBuffer;
    size_t dst_size = newCodeSize;
    size_t src_size;
    for (int32 i = 0; i < relocList->Count(); i++)
    {
        EncodeRelocAndLabels &reloc = relocList->Item(i);
        // shorten BR and copy
        if (reloc.isShortBr())
        {
            // validate that short BR offset is within 1 byte offset range.
            // This handles the rare case with loop alignment breaks br shortening.
            // Consider:
            //      BR $L1 // shortened
            //      ...
            //      L2:    // aligned, and makes the BR $L1 non-shortable anymore
            //      ...
            //      BR $L2
            //      ...
            //      L1:
            // In this case, we simply give up and revert the relocList.
            if(!reloc.validateShortBrTarget())
            {
                revertRelocList();
                // restore maps
                CopyMaps<true>(&m_origInlineeFrameRecords
                    , &m_origInlineeFrameMap
                    , &m_origPragmaInstrToRecordOffset
                    , &m_origOffsetBuffer
                    );

                return false;
            }

            // m_origPtr points to imm32 field in the original buffer
            BYTE *opcodeByte = (BYTE*)reloc.m_origPtr - 1;

            if (*opcodeByte == 0xe9 /* JMP rel32 */)
            {
                to = opcodeByte - 1;
            }
            else if (*opcodeByte >= 0x80 && *opcodeByte < 0x90 /* Jcc rel32 */)
            {
                Assert(*(opcodeByte - 1) == 0x0f);
                to = opcodeByte - 2;
            }
            else
            {
                Assert(false);
            }

            src_size = to - from + 1;
            AnalysisAssert(dst_size >= src_size);

            memcpy_s(dst_p, dst_size, from, src_size);

            srcBufferCrc = CalculateCRC(srcBufferCrc, (BYTE*)reloc.m_origPtr - from + 4, from);
            *pShortenedBufferCRC = CalculateCRC(*pShortenedBufferCRC, src_size, dst_p);

            dst_p += src_size;
            dst_size -= src_size;

            // fix the BR
            // write new opcode
            AnalysisAssert(dst_p < tmpBuffer + newCodeSize);
            *dst_p = (*opcodeByte == 0xe9) ? (BYTE)0xeb : (BYTE)(*opcodeByte - 0x10);
            *(dst_p + 1) = 0;   // imm8

            *pShortenedBufferCRC = CalculateCRC(*pShortenedBufferCRC, 2, dst_p);
            dst_p += 2; // 1 byte for opcode + 1 byte for imm8
            dst_size -= 2;
            from = (BYTE*)reloc.m_origPtr + 4;
        }
        else if (reloc.m_type == RelocTypeInlineeEntryOffset)
        {
            to = (BYTE*)reloc.m_origPtr - 1;
            CopyPartialBufferAndCalculateCRC(&dst_p, dst_size, from, to, pShortenedBufferCRC);

            *(size_t*)dst_p = reloc.GetInlineOffset();

            *pShortenedBufferCRC = CalculateCRC(*pShortenedBufferCRC, sizeof(size_t), dst_p);

            dst_p += sizeof(size_t);
            dst_size -= sizeof(size_t);

            srcBufferCrc = CalculateCRC(srcBufferCrc, (BYTE*)reloc.m_origPtr + sizeof(size_t) - from , from);

            from = (BYTE*)reloc.m_origPtr + sizeof(size_t);
        }
        // insert NOPs for aligned labels
        else if ((!PHASE_OFF(Js::LoopAlignPhase, m_func) && reloc.isAlignedLabel()) && reloc.getLabelNopCount() > 0)
        {
            IR::LabelInstr *label = reloc.getLabel();
            BYTE nop_count = reloc.getLabelNopCount();

            AssertMsg((BYTE*)label < buffStart || (BYTE*)label >= buffEnd, "Invalid label pointer.");
            AssertMsg((((uint32)(label->GetPC() - buffStart)) & 0xf) == 0, "Misaligned Label");

            to = reloc.getLabelOrigPC() - 1;

            CopyPartialBufferAndCalculateCRC(&dst_p, dst_size, from, to, pShortenedBufferCRC);
            srcBufferCrc = CalculateCRC(srcBufferCrc, to - from + 1, from);

#ifdef  ENABLE_DEBUG_CONFIG_OPTIONS
            if (PHASE_TRACE(Js::LoopAlignPhase, this->m_func))
            {
                globalTotalBytesInserted += nop_count;

                OUTPUT_VERBOSE_TRACE(Js::LoopAlignPhase, _u("func: %s, bytes inserted: %d, bytes inserted %%:%.4f, total bytes inserted:%d, total bytes inserted %%:%.4f\n"),
                    this->m_func->GetJITFunctionBody()->GetDisplayName(), nop_count, (float)nop_count / newCodeSize * 100, globalTotalBytesInserted, (float)globalTotalBytesInserted / (globalTotalBytesWithoutShortening - globalTotalBytesSaved) * 100);
                Output::Flush();
            }
#endif
            BYTE * tmpDst_p = dst_p;
            InsertNopsForLabelAlignment(nop_count, &dst_p);
            *pShortenedBufferCRC = CalculateCRC(*pShortenedBufferCRC, nop_count, tmpDst_p);

            dst_size -= nop_count;
            from = to + 1;
        }
    }
    // copy last chunk
    //Exclude jumpTable content from CRC calculation.
    //Though jumpTable is not part of the encoded bytes, codeSize has jumpTableSize included in it.
    CopyPartialBufferAndCalculateCRC(&dst_p, dst_size, from, buffStart + *codeSize - 1, pShortenedBufferCRC, jumpTableSize);
    srcBufferCrc = CalculateCRC(srcBufferCrc, buffStart + *codeSize - from - jumpTableSize, from);

    m_encoderMD.UpdateRelocListWithNewBuffer(relocList, tmpBuffer, buffStart, buffEnd);

    if (srcBufferCrc != bufferCrcToValidate)
    {
        Assert(false);
        Fatal();
    }

    // switch buffers
    *codeStart = tmpBuffer;
    *codeSize = newCodeSize;

    return true;
}

BYTE Encoder::FindNopCountFor16byteAlignment(size_t address)
{
    return (16 - (BYTE) (address & 0xf)) % 16;
}

void Encoder::CopyPartialBufferAndCalculateCRC(BYTE ** ptrDstBuffer, size_t &dstSize, BYTE * srcStart, BYTE * srcEnd, uint* pBufferCRC, size_t jumpTableSize)
{
    BYTE * destBuffer = *ptrDstBuffer;

    size_t srcSize = srcEnd - srcStart + 1;
    Assert(dstSize >= srcSize);
    memcpy_s(destBuffer, dstSize, srcStart, srcSize);

    Assert(srcSize >= jumpTableSize);

    //Exclude the jump table content (which is at the end of the buffer) for calculating CRC - at this point.
    *pBufferCRC = CalculateCRC(*pBufferCRC, srcSize - jumpTableSize, destBuffer);

    *ptrDstBuffer += srcSize;
    dstSize -= srcSize;
}

void Encoder::InsertNopsForLabelAlignment(int nopCount, BYTE ** ptrDstBuffer)
{
    // write NOPs
    for (int32 i = 0; i < nopCount; i++, (*ptrDstBuffer)++)
    {
        **ptrDstBuffer = 0x90;
    }
}
void Encoder::revertRelocList()
{
    RelocList* relocList = m_encoderMD.GetRelocList();

    for (int32 i = 0; i < relocList->Count(); i++)
    {
        relocList->Item(i).revert();
    }
}

template <bool restore>
void Encoder::CopyMaps(OffsetList **m_origInlineeFrameRecords
    , OffsetList **m_origInlineeFrameMap
    , OffsetList **m_origPragmaInstrToRecordOffset
    , OffsetList **m_origOffsetBuffer
    )
{
    InlineeFrameRecords *recList = m_inlineeFrameRecords;
    ArenaInlineeFrameMap *mapList = m_inlineeFrameMap;
    PragmaInstrList *pInstrList = m_pragmaInstrToRecordOffset;

    OffsetList *origRecList, *origMapList, *origPInstrList;
    if (!restore)
    {
        Assert(*m_origInlineeFrameRecords == nullptr);
        Assert(*m_origInlineeFrameMap == nullptr);
        Assert(*m_origPragmaInstrToRecordOffset == nullptr);

        *m_origInlineeFrameRecords = origRecList = Anew(m_tempAlloc, OffsetList, m_tempAlloc);
        *m_origInlineeFrameMap = origMapList = Anew(m_tempAlloc, OffsetList, m_tempAlloc);
        *m_origPragmaInstrToRecordOffset = origPInstrList = Anew(m_tempAlloc, OffsetList, m_tempAlloc);

#if DBG_DUMP
        Assert((*m_origOffsetBuffer) == nullptr);
        *m_origOffsetBuffer = Anew(m_tempAlloc, OffsetList, m_tempAlloc);
#endif
    }
    else
    {
        Assert((*m_origInlineeFrameRecords) && (*m_origInlineeFrameMap) && (*m_origPragmaInstrToRecordOffset));
        origRecList = *m_origInlineeFrameRecords;
        origMapList = *m_origInlineeFrameMap;
        origPInstrList = *m_origPragmaInstrToRecordOffset;
        Assert(origRecList->Count() == recList->Count());
        Assert(origMapList->Count() == mapList->Count());
        Assert(origPInstrList->Count() == pInstrList->Count());

#if DBG_DUMP
        Assert(m_origOffsetBuffer);
        Assert((uint32)(*m_origOffsetBuffer)->Count() == m_instrNumber);
#endif
    }

    for (int i = 0; i < recList->Count(); i++)
    {
        if (!restore)
        {
            origRecList->Add(recList->Item(i)->inlineeStartOffset);
        }
        else
        {
            recList->Item(i)->inlineeStartOffset = origRecList->Item(i);
        }
    }

    for (int i = 0; i < mapList->Count(); i++)
    {
        if (!restore)
        {
            origMapList->Add(mapList->Item(i).offset);
        }
        else
        {
            mapList->Item(i).offset = origMapList->Item(i);
        }
    }

    for (int i = 0; i < pInstrList->Count(); i++)
    {
        if (!restore)
        {
            origPInstrList->Add(pInstrList->Item(i)->m_offsetInBuffer);
        }
        else
        {
            pInstrList->Item(i)->m_offsetInBuffer = origPInstrList->Item(i);
        }
    }

    if (restore)
    {
        (*m_origInlineeFrameRecords)->Delete();
        (*m_origInlineeFrameMap)->Delete();
        (*m_origPragmaInstrToRecordOffset)->Delete();
        (*m_origInlineeFrameRecords) = nullptr;
        (*m_origInlineeFrameMap) = nullptr;
        (*m_origPragmaInstrToRecordOffset) = nullptr;
    }

#if DBG_DUMP
    for (uint i = 0; i < m_instrNumber; i++)
    {
        if (!restore)
        {
            (*m_origOffsetBuffer)->Add(m_offsetBuffer[i]);
        }
        else
        {
            m_offsetBuffer[i] = (*m_origOffsetBuffer)->Item(i);
        }
    }

    if (restore)
    {
        (*m_origOffsetBuffer)->Delete();
        (*m_origOffsetBuffer) = nullptr;
    }
#endif
}

#endif

#if DBG_DUMP
void Encoder::DumpInlineeFrameMap(size_t baseAddress)
{
    Output::Print(_u("Inlinee frame info mapping\n"));
    Output::Print(_u("---------------------------------------\n"));
    m_inlineeFrameMap->Map([=](uint index, NativeOffsetInlineeFramePair& pair) {
        Output::Print(_u("%Ix"), baseAddress + pair.offset);
        Output::SkipToColumn(20);
        if (pair.record)
        {
            pair.record->Dump();
        }
        else
        {
            Output::Print(_u("<NULL>"));
        }
        Output::Print(_u("\n"));
    });
}
#endif

void
Encoder::SaveToLazyBailOutRecordList(IR::Instr* instr, uint32 currentOffset)
{
    BailOutInfo* bailOutInfo = instr->GetBailOutInfo();

    Assert(instr->OnlyHasLazyBailOut() && bailOutInfo->bailOutRecord != nullptr);

#if DBG_DUMP
    if (PHASE_DUMP(Js::LazyBailoutPhase, m_func))
    {
        Output::Print(_u("Offset: %u Instr: "), currentOffset);
        instr->Dump();
        Output::Print(_u("Bailout label: "));
        bailOutInfo->bailOutInstr->Dump();
    }
#endif

    LazyBailOutRecord record(currentOffset, bailOutInfo->bailOutRecord);
    this->m_sortedLazyBailoutRecordList->Add(record);
}

void
Encoder::SaveLazyBailOutThunkOffset(uint32 currentOffset)
{
    AssertMsg(
        this->m_lazyBailOutThunkOffset == 0,
        "We should only have one thunk generated during final lowerer"
    );
    this->m_lazyBailOutThunkOffset = this->GetCurrentOffset();
}

void
Encoder::SaveLazyBailOutJitTransferData()
{
    if (this->m_func->HasLazyBailOut())
    {
        Assert(this->m_sortedLazyBailoutRecordList->Count() > 0);
        Assert(this->m_lazyBailOutThunkOffset != 0);
        Assert(this->m_func->GetLazyBailOutRecordSlot() != nullptr);

        auto nativeEntryPointData = this->m_func->GetInProcJITEntryPointInfo()->GetInProcNativeEntryPointData();
        nativeEntryPointData->SetSortedLazyBailOutRecordList(this->m_sortedLazyBailoutRecordList);
        nativeEntryPointData->SetLazyBailOutRecordSlotOffset(this->m_func->GetLazyBailOutRecordSlot()->m_offset);
        nativeEntryPointData->SetLazyBailOutThunkOffset(this->m_lazyBailOutThunkOffset);
    }

    if (this->m_func->lazyBailoutProperties.Count() > 0)
    {
        const int count = this->m_func->lazyBailoutProperties.Count();
        Js::PropertyId* lazyBailoutProperties = HeapNewArrayZ(Js::PropertyId, count);
        Js::PropertyId* dstProperties = lazyBailoutProperties;
        this->m_func->lazyBailoutProperties.Map([&](Js::PropertyId propertyId)
        {
            *dstProperties++ = propertyId;
        });
        this->m_func->GetInProcJITEntryPointInfo()->GetJitTransferData()->SetLazyBailoutProperties(lazyBailoutProperties, count);
    }
}

void
Encoder::FixLazyBailOutThunkOffset(uint32 bytesSaved)
{
    // Lazy bailout thunk is inserted at the end of the function,
    // so just decrease the offset by the number of bytes saved
    this->m_lazyBailOutThunkOffset -= bytesSaved;
}
