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

FunctionJITTimeInfo::FunctionJITTimeInfo(FunctionJITTimeDataIDL * data) : m_data(*data)
{
    // we will cast the data (i.e. midl struct) pointers into info pointers so we can extend with methods
    CompileAssert(sizeof(FunctionJITTimeDataIDL) == sizeof(FunctionJITTimeInfo));
}

/* static */
void
FunctionJITTimeInfo::BuildJITTimeData(
    __in ArenaAllocator * alloc,
    __in const Js::FunctionCodeGenJitTimeData * codeGenData,
    __in_opt const Js::FunctionCodeGenRuntimeData * runtimeData,
    __out FunctionJITTimeDataIDL * jitData,
    bool isInlinee,
    bool isForegroundJIT)
{
    jitData->functionInfoAddr = (intptr_t)codeGenData->GetFunctionInfo();
    jitData->localFuncId = codeGenData->GetFunctionInfo()->GetLocalFunctionId();
    jitData->isAggressiveInliningEnabled = codeGenData->GetIsAggressiveInliningEnabled();
    jitData->isInlined = codeGenData->GetIsInlined();
    jitData->weakFuncRef = (intptr_t)codeGenData->GetWeakFuncRef();
    jitData->inlineesBv = (BVFixedIDL*)(const BVFixed*)codeGenData->inlineesBv;
    jitData->entryPointInfoAddr = (intptr_t)codeGenData->GetEntryPointInfo();

    if (!codeGenData->GetFunctionBody() || !codeGenData->GetFunctionBody()->GetByteCode())
    {
        // outermost function must have a body, but inlinees may not (if they are builtins)
        Assert(isInlinee);
        return;
    }

    Js::FunctionBody * body = codeGenData->GetFunctionInfo()->GetParseableFunctionInfo()->GetFunctionBody();
    jitData->bodyData = AnewStructZ(alloc, FunctionBodyDataIDL);
    JITTimeFunctionBody::InitializeJITFunctionData(alloc, body, jitData->bodyData);

    Assert(isInlinee == !!runtimeData);
    const Js::FunctionCodeGenRuntimeData * targetRuntimeData = nullptr;
    if (runtimeData)
    {
        // may be polymorphic, so seek the runtime data matching our JIT time data
        targetRuntimeData = runtimeData->GetForTarget(codeGenData->GetFunctionInfo()->GetFunctionBody());
    }
    Js::FunctionBody * functionBody = codeGenData->GetFunctionBody();
    if (functionBody->HasDynamicProfileInfo())
    {
        ProfileDataIDL * profileData = AnewStruct(alloc, ProfileDataIDL);
        JITTimeProfileInfo::InitializeJITProfileData(alloc, functionBody->GetAnyDynamicProfileInfo(), functionBody, profileData, isForegroundJIT);

        jitData->bodyData->profileData = profileData;

        if (isInlinee)
        {
            // if not inlinee, NativeCodeGenerator will provide the address
            // REVIEW: OOP JIT, for inlinees, is this actually necessary?
            Js::ProxyEntryPointInfo *defaultEntryPointInfo = functionBody->GetDefaultEntryPointInfo();
            Assert(defaultEntryPointInfo->IsFunctionEntryPointInfo());
            Js::FunctionEntryPointInfo *functionEntryPointInfo = static_cast<Js::FunctionEntryPointInfo*>(defaultEntryPointInfo);
            jitData->callsCountAddress = (intptr_t)&functionEntryPointInfo->callsCount;

            jitData->sharedPropertyGuards = codeGenData->sharedPropertyGuards;
            jitData->sharedPropGuardCount = codeGenData->sharedPropertyGuardCount;
        }
    }
    if (jitData->bodyData->profiledCallSiteCount > 0)
    {
        jitData->inlineeCount = jitData->bodyData->profiledCallSiteCount;
        // using arena because we can't recycler allocate (may be on background), and heap freeing this is slightly complicated
        jitData->inlinees = AnewArrayZ(alloc, FunctionJITTimeDataIDL*, jitData->bodyData->profiledCallSiteCount);
        jitData->inlineesRecursionFlags = AnewArrayZ(alloc, boolean, jitData->bodyData->profiledCallSiteCount);

        for (Js::ProfileId i = 0; i < jitData->bodyData->profiledCallSiteCount; ++i)
        {
            const Js::FunctionCodeGenJitTimeData * inlineeJITData = codeGenData->GetInlinee(i);
            if (inlineeJITData == codeGenData)
            {
                jitData->inlineesRecursionFlags[i] = TRUE;
            }
            else if (inlineeJITData != nullptr)
            {
                const Js::FunctionCodeGenRuntimeData * inlineeRuntimeData = nullptr;
                if (inlineeJITData->GetFunctionInfo()->HasBody())
                {
                    inlineeRuntimeData = isInlinee ? targetRuntimeData->GetInlinee(i) : functionBody->GetInlineeCodeGenRuntimeData(i);
                }
                jitData->inlinees[i] = AnewStructZ(alloc, FunctionJITTimeDataIDL);
                BuildJITTimeData(alloc, inlineeJITData, inlineeRuntimeData, jitData->inlinees[i], true, isForegroundJIT);
            }
        }

        jitData->callbackInlineeCount = jitData->bodyData->profiledCallSiteCount;
        jitData->callbackInlinees = AnewArrayZ(alloc, FunctionJITTimeDataIDL*, jitData->bodyData->profiledCallSiteCount);

        for (Js::ProfileId i = 0; i < jitData->bodyData->profiledCallSiteCount; ++i)
        {
            const Js::FunctionCodeGenJitTimeData * inlineeJITData = codeGenData->GetCallbackInlinee(i);
            if (inlineeJITData != nullptr)
            {
                const Js::FunctionCodeGenRuntimeData * inlineeRuntimeData = nullptr;
                if (inlineeJITData->GetFunctionInfo()->HasBody())
                {
                    inlineeRuntimeData = isInlinee ? targetRuntimeData->GetCallbackInlinee(i) : functionBody->GetCallbackInlineeCodeGenRuntimeData(i);
                }
                jitData->callbackInlinees[i] = AnewStructZ(alloc, FunctionJITTimeDataIDL);
                BuildJITTimeData(alloc, inlineeJITData, inlineeRuntimeData, jitData->callbackInlinees[i], true, isForegroundJIT);
            }
        }
    }
    jitData->profiledRuntimeData = AnewStructZ(alloc, FunctionJITRuntimeIDL);
    if (isInlinee && targetRuntimeData->ClonedInlineCaches()->HasInlineCaches())
    {
        jitData->profiledRuntimeData->clonedCacheCount = jitData->bodyData->inlineCacheCount;
        jitData->profiledRuntimeData->clonedInlineCaches = AnewArray(alloc, intptr_t, jitData->profiledRuntimeData->clonedCacheCount);
        for (uint j = 0; j < jitData->bodyData->inlineCacheCount; ++j)
        {
            jitData->profiledRuntimeData->clonedInlineCaches[j] = (intptr_t)targetRuntimeData->ClonedInlineCaches()->GetInlineCache(j);
        }
    }
    if (jitData->bodyData->inlineCacheCount > 0)
    {
        jitData->ldFldInlineeCount = jitData->bodyData->inlineCacheCount;
        jitData->ldFldInlinees = AnewArrayZ(alloc, FunctionJITTimeDataIDL*, jitData->bodyData->inlineCacheCount);

        Field(ObjTypeSpecFldInfo*)* objTypeSpecInfo = codeGenData->GetObjTypeSpecFldInfoArray()->GetInfoArray();
        if(objTypeSpecInfo)
        {
            jitData->objTypeSpecFldInfoCount = jitData->bodyData->inlineCacheCount;
            jitData->objTypeSpecFldInfoArray = unsafe_write_barrier_cast<ObjTypeSpecFldIDL**>(objTypeSpecInfo);
        }
        for (Js::InlineCacheIndex i = 0; i < jitData->bodyData->inlineCacheCount; ++i)
        {
            const Js::FunctionCodeGenJitTimeData * inlineeJITData = codeGenData->GetLdFldInlinee(i);
            const Js::FunctionCodeGenRuntimeData * inlineeRuntimeData = isInlinee ? targetRuntimeData->GetLdFldInlinee(i) : functionBody->GetLdFldInlineeCodeGenRuntimeData(i);
            if (inlineeJITData != nullptr)
            {
                jitData->ldFldInlinees[i] = AnewStructZ(alloc, FunctionJITTimeDataIDL);
                BuildJITTimeData(alloc, inlineeJITData, inlineeRuntimeData, jitData->ldFldInlinees[i], true, isForegroundJIT);
            }
        }
    }
    if (!isInlinee && codeGenData->GetGlobalObjTypeSpecFldInfoCount() > 0)
    {
        Field(ObjTypeSpecFldInfo*)* globObjTypeSpecInfo = codeGenData->GetGlobalObjTypeSpecFldInfoArray();
        Assert(globObjTypeSpecInfo != nullptr);

        jitData->globalObjTypeSpecFldInfoCount = codeGenData->GetGlobalObjTypeSpecFldInfoCount();
        jitData->globalObjTypeSpecFldInfoArray = unsafe_write_barrier_cast<ObjTypeSpecFldIDL**>(globObjTypeSpecInfo);
    }
    const Js::FunctionCodeGenJitTimeData * nextJITData = codeGenData->GetNext();
    if (nextJITData != nullptr)
    {
        // only inlinee should be polymorphic
        Assert(isInlinee);
        jitData->next = AnewStructZ(alloc, FunctionJITTimeDataIDL);
        BuildJITTimeData(alloc, nextJITData, runtimeData, jitData->next, true, isForegroundJIT);
    }
}

uint
FunctionJITTimeInfo::GetInlineeCount() const
{
    return m_data.inlineeCount;
}

bool
FunctionJITTimeInfo::IsLdFldInlineePresent() const
{
    return m_data.ldFldInlineeCount != 0;
}

bool
FunctionJITTimeInfo::HasSharedPropertyGuards() const
{
    return m_data.sharedPropGuardCount != 0;
}

bool
FunctionJITTimeInfo::HasSharedPropertyGuard(Js::PropertyId id) const
{
    for (uint i = 0; i < m_data.sharedPropGuardCount; ++i)
    {
        if (m_data.sharedPropertyGuards[i] == id)
        {
            return true;
        }
    }
    return false;
}

bool FunctionJITTimeInfo::IsJsBuiltInForceInline() const
{
    return this->GetBody()->GetSourceContextId() == Js::Constants::JsBuiltInSourceContextId;
}

intptr_t
FunctionJITTimeInfo::GetFunctionInfoAddr() const
{
    return m_data.functionInfoAddr;
}

intptr_t
FunctionJITTimeInfo::GetEntryPointInfoAddr() const
{
    return m_data.entryPointInfoAddr;
}

intptr_t
FunctionJITTimeInfo::GetWeakFuncRef() const
{
    return m_data.weakFuncRef;
}

uint
FunctionJITTimeInfo::GetLocalFunctionId() const
{
    return m_data.localFuncId;
}

bool
FunctionJITTimeInfo::IsAggressiveInliningEnabled() const
{
    return m_data.isAggressiveInliningEnabled != FALSE;
}

bool
FunctionJITTimeInfo::IsInlined() const
{
    return m_data.isInlined != FALSE;
}

const BVFixed *
FunctionJITTimeInfo::GetInlineesBV() const
{
    return reinterpret_cast<const BVFixed *>(m_data.inlineesBv);
}

const FunctionJITTimeInfo *
FunctionJITTimeInfo::GetJitTimeDataFromFunctionInfoAddr(intptr_t polyFuncInfo) const
{
    const FunctionJITTimeInfo *next = this;
    while (next && next->GetFunctionInfoAddr() != polyFuncInfo)
    {
        next = next->GetNext();
    }
    return next;
}

const FunctionJITRuntimeInfo *
FunctionJITTimeInfo::GetInlineeForTargetInlineeRuntimeData(const Js::ProfileId profiledCallSiteId, intptr_t inlineeFuncBodyAddr) const
{
    const FunctionJITTimeInfo *inlineeData = GetInlinee(profiledCallSiteId);
    while (inlineeData && inlineeData->GetBody()->GetAddr() != inlineeFuncBodyAddr)
    {
        inlineeData = inlineeData->GetNext();
    }
    __analysis_assume(inlineeData != nullptr);
    return inlineeData->GetRuntimeInfo();
}

const FunctionJITRuntimeInfo *
FunctionJITTimeInfo::GetInlineeRuntimeData(const Js::ProfileId profiledCallSiteId) const
{
    return GetInlinee(profiledCallSiteId) ? GetInlinee(profiledCallSiteId)->GetRuntimeInfo() : nullptr;
}

const FunctionJITRuntimeInfo *
FunctionJITTimeInfo::GetLdFldInlineeRuntimeData(const Js::InlineCacheIndex inlineCacheIndex) const
{
    return GetLdFldInlinee(inlineCacheIndex) ? GetLdFldInlinee(inlineCacheIndex)->GetRuntimeInfo() : nullptr;
}

const FunctionJITRuntimeInfo *
FunctionJITTimeInfo::GetCallbackInlineeRuntimeData(const Js::ProfileId profiledCallSiteId) const
{
    return GetCallbackInlinee(profiledCallSiteId) ? GetCallbackInlinee(profiledCallSiteId)->GetRuntimeInfo() : nullptr;
}

const FunctionJITRuntimeInfo *
FunctionJITTimeInfo::GetInlineeForCallbackInlineeRuntimeData(const Js::ProfileId profiledCallSiteId, intptr_t inlineeFuncBodyAddr) const
{
    const FunctionJITTimeInfo *inlineeData = GetCallbackInlinee(profiledCallSiteId);
    while (inlineeData && inlineeData->GetBody()->GetAddr() != inlineeFuncBodyAddr)
    {
        inlineeData = inlineeData->GetNext();
    }
    __analysis_assume(inlineeData != nullptr);
    return inlineeData->GetRuntimeInfo();
}

const FunctionJITRuntimeInfo *
FunctionJITTimeInfo::GetRuntimeInfo() const
{
    return reinterpret_cast<const FunctionJITRuntimeInfo*>(m_data.profiledRuntimeData);
}

ObjTypeSpecFldInfo *
FunctionJITTimeInfo::GetObjTypeSpecFldInfo(uint index) const
{
    if (m_data.objTypeSpecFldInfoArray == nullptr)
    {
        return nullptr;
    }
    AssertOrFailFast(index < m_data.objTypeSpecFldInfoCount);

    return reinterpret_cast<ObjTypeSpecFldInfo *>(m_data.objTypeSpecFldInfoArray[index]);
}

void
FunctionJITTimeInfo::ClearObjTypeSpecFldInfo(uint index)
{
    if (m_data.objTypeSpecFldInfoArray == nullptr)
    {
        return;
    }
    AssertOrFailFast(index < m_data.objTypeSpecFldInfoCount);

    m_data.objTypeSpecFldInfoArray[index] = nullptr;
}

ObjTypeSpecFldInfo *
FunctionJITTimeInfo::GetGlobalObjTypeSpecFldInfo(uint index) const
{
    AssertOrFailFast(index < m_data.globalObjTypeSpecFldInfoCount);

    return reinterpret_cast<ObjTypeSpecFldInfo *>(m_data.globalObjTypeSpecFldInfoArray[index]);
}

uint
FunctionJITTimeInfo::GetGlobalObjTypeSpecFldInfoCount() const
{
    return m_data.globalObjTypeSpecFldInfoCount;
}

uint
FunctionJITTimeInfo::GetSourceContextId() const
{
    Assert(HasBody());

    return GetBody()->GetSourceContextId();
}

const FunctionJITTimeInfo *
FunctionJITTimeInfo::GetCallbackInlinee(Js::ProfileId profileId) const
{
    Assert(profileId < m_data.bodyData->profiledCallSiteCount);
    if (!m_data.callbackInlinees)
    {
        return nullptr;
    }
    AssertOrFailFast(profileId < m_data.callbackInlineeCount);

    return reinterpret_cast<const FunctionJITTimeInfo *>(m_data.callbackInlinees[profileId]);
}

const FunctionJITTimeInfo *
FunctionJITTimeInfo::GetLdFldInlinee(Js::InlineCacheIndex inlineCacheIndex) const
{
    Assert(inlineCacheIndex < m_data.bodyData->inlineCacheCount);
    if (!m_data.ldFldInlinees)
    {
        return nullptr;
    }
    AssertOrFailFast(inlineCacheIndex < m_data.ldFldInlineeCount);

    return reinterpret_cast<const FunctionJITTimeInfo*>(m_data.ldFldInlinees[inlineCacheIndex]);
}

const FunctionJITTimeInfo *
FunctionJITTimeInfo::GetInlinee(Js::ProfileId profileId) const
{
    Assert(profileId < m_data.bodyData->profiledCallSiteCount);
    if (!m_data.inlinees)
    {
        return nullptr;
    }
    AssertOrFailFast(profileId < m_data.inlineeCount);

    auto inlinee = reinterpret_cast<const FunctionJITTimeInfo *>(m_data.inlinees[profileId]);
    if (inlinee == nullptr && m_data.inlineesRecursionFlags[profileId])
    {
        inlinee = this;
    }
    return inlinee;
}

const FunctionJITTimeInfo *
FunctionJITTimeInfo::GetNext() const
{
    return reinterpret_cast<const FunctionJITTimeInfo *>(m_data.next);
}

JITTimeFunctionBody *
FunctionJITTimeInfo::GetBody() const
{
    return reinterpret_cast<JITTimeFunctionBody *>(m_data.bodyData);
}

bool
FunctionJITTimeInfo::HasBody() const
{
    return m_data.bodyData != nullptr;
}

bool
FunctionJITTimeInfo::IsPolymorphicCallSite(Js::ProfileId profiledCallSiteId) const
{
    Assert(profiledCallSiteId < m_data.bodyData->profiledCallSiteCount);

    if (!m_data.inlinees)
    {
        return false;
    }
    Assert(profiledCallSiteId < m_data.inlineeCount);

    return ((FunctionJITTimeDataIDL*)this->GetInlinee(profiledCallSiteId))->next != nullptr;
}

bool
FunctionJITTimeInfo::ForceJITLoopBody() const
{
    return
        !PHASE_OFF(Js::JITLoopBodyPhase, this) &&
        !PHASE_OFF(Js::FullJitPhase, this) &&
        !GetBody()->IsGenerator() &&
        !GetBody()->HasTry() &&
        (
            PHASE_FORCE(Js::JITLoopBodyPhase, this)
#ifdef ENABLE_PREJIT
            || Js::Configuration::Global.flags.Prejit
#endif
            );
}


char16*
FunctionJITTimeInfo::GetDisplayName() const
{
    return GetBody()->GetDisplayName();
}

char16*
FunctionJITTimeInfo::GetDebugNumberSet(wchar(&bufferToWriteTo)[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE]) const
{
    // (#%u.%u), #%u --> (source file Id . function Id) , function Number
    int len = swprintf_s(bufferToWriteTo, MAX_FUNCTION_BODY_DEBUG_STRING_SIZE, _u(" (#%d.%u), #%u"),
        (int)GetSourceContextId(), GetLocalFunctionId(), GetBody()->GetFunctionNumber());
    Assert(len > 8);
    return bufferToWriteTo;
}
