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

namespace Js
{
    void SimpleJitHelpers::ProfileParameters(void* framePtr)
    {
        auto layout = JavascriptCallStackLayout::FromFramePointer(framePtr);
        FunctionBody* executeFunction = layout->functionObject->GetFunctionBody();
        const auto dynamicInfo = executeFunction->GetDynamicProfileInfo();
        ArgSlot requiredInParamCount = executeFunction->GetInParamsCount();
        AssertMsg(requiredInParamCount > 1, "This function shouldn't be called unless we're expecting to have args to profile!");

        CallInfo callInfo(layout->callInfo);
        Js::ArgumentReader args(&callInfo, layout->args);

        // Ignore the first param. 'this' is profiled as part of LdThis.
        ArgSlot paramIndex = 1;

        const auto maxValid = min((ArgSlot)args.Info.Count, requiredInParamCount);
        for (; paramIndex < maxValid; paramIndex++)
        {
            dynamicInfo->RecordParameterInfo(executeFunction, paramIndex - 1, args[paramIndex]);
        }

        // If they didn't provide enough parameters, record the fact that the rest of the params are undefined.
        if (paramIndex < requiredInParamCount)
        {
            Var varUndef = executeFunction->GetScriptContext()->GetLibrary()->GetUndefined();

            do
            {
                dynamicInfo->RecordParameterInfo(executeFunction, paramIndex - 1, varUndef);
                ++paramIndex;
            } while (paramIndex < requiredInParamCount);
        }

        // Clearing of the implicit call flags is done in jitted code
    }

    void SimpleJitHelpers::CleanImplicitCallFlags(FunctionBody* body)
    {
        // Probably could inline this straight into JITd code.
        auto flags = body->GetScriptContext()->GetThreadContext()->GetImplicitCallFlags();

        body->GetDynamicProfileInfo()->RecordImplicitCallFlags(flags);
    }

    void SimpleJitHelpers::ProfileCall_DefaultInlineCacheIndex(void* framePtr, ProfileId profileId, Var retval, JavascriptFunction* callee, CallInfo info)
    {
        ProfileCall(framePtr, profileId, Constants::NoInlineCacheIndex, retval, callee, info);
    }

    void SimpleJitHelpers::ProfileCall(void* framePtr, ProfileId profileId, InlineCacheIndex inlineCacheIndex, Var retval, Var callee, CallInfo info)
    {
        JavascriptFunction* caller = JavascriptCallStackLayout::FromFramePointer(framePtr)->functionObject;

        FunctionBody* callerFunctionBody = caller->GetFunctionBody();
        DynamicProfileInfo * dynamicProfileInfo = callerFunctionBody->GetDynamicProfileInfo();

        JavascriptFunction *const calleeFunction =
            JavascriptFunction::Is(callee) ? JavascriptFunction::FromVar(callee) : nullptr;
        FunctionInfo* calleeFunctionInfo = calleeFunction ? calleeFunction->GetFunctionInfo() : nullptr;

        auto const ctor = !!(info.Flags & CallFlags_New);
        dynamicProfileInfo->RecordCallSiteInfo(callerFunctionBody, profileId, calleeFunctionInfo, calleeFunction, info.Count, ctor, inlineCacheIndex);

        if (info.Flags & CallFlags_Value)
        {
            dynamicProfileInfo->RecordReturnTypeOnCallSiteInfo(callerFunctionBody, profileId, retval);
        }
    }

    void SimpleJitHelpers::ProfileReturnTypeCall(void* framePtr, ProfileId profileId, Var retval, JavascriptFunction*callee, CallInfo info)
    {
        Assert(info.Flags & CallFlags_Value);
        JavascriptFunction* caller = JavascriptCallStackLayout::FromFramePointer(framePtr)->functionObject;

        FunctionBody* functionBody = caller->GetFunctionBody();
        DynamicProfileInfo * dynamicProfileInfo = functionBody->GetDynamicProfileInfo();
        dynamicProfileInfo->RecordReturnType(functionBody, profileId, retval);
    }

    Var SimpleJitHelpers::ProfiledLdLen_A(FunctionBody* body, DynamicObject * instance, ProfileId profileId)
    {
        FunctionBody*const functionBody = body;
        const auto profileData = functionBody->GetDynamicProfileInfo();

        LdElemInfo ldElemInfo;
        ldElemInfo.arrayType = ValueType::Uninitialized.Merge(instance);

        Var length = JavascriptOperators::OP_GetProperty(instance, PropertyIds::length, body->GetScriptContext());

        ldElemInfo.elemType = ldElemInfo.elemType.Merge(length);
        profileData->RecordElementLoad(functionBody, profileId, ldElemInfo);

        return length;
    }

    Var SimpleJitHelpers::ProfiledStrictLdThis(Var thisVar, FunctionBody* functionBody)
    {
        //Adapted from InterpreterStackFrame::OP_ProfiledStrictLdThis
        DynamicProfileInfo * dynamicProfileInfo = functionBody->GetDynamicProfileInfo();
        TypeId typeId = JavascriptOperators::GetTypeId(thisVar);

        if (typeId == TypeIds_ActivationObject)
        {
            thisVar = functionBody->GetScriptContext()->GetLibrary()->GetUndefined();
            dynamicProfileInfo->RecordThisInfo(thisVar, ThisType_Mapped);
            return thisVar;
        }

        dynamicProfileInfo->RecordThisInfo(thisVar, ThisType_Simple);
        return thisVar;
    }


    Var SimpleJitHelpers::ProfiledLdThis(Var thisVar, int moduleID, FunctionBody* functionBody)
    {
        //Adapted from InterpreterStackFrame::OP_ProfiledLdThis
        DynamicProfileInfo * dynamicProfileInfo = functionBody->GetDynamicProfileInfo();
        TypeId typeId = JavascriptOperators::GetTypeId(thisVar);

        if (JavascriptOperators::IsThisSelf(typeId))
        {
            Assert(typeId != TypeIds_GlobalObject || ((GlobalObject*)thisVar)->ToThis() == thisVar);
            Assert(typeId != TypeIds_ModuleRoot || JavascriptOperators::GetThisFromModuleRoot(thisVar) == thisVar);

            dynamicProfileInfo->RecordThisInfo(thisVar, ThisType_Simple);

            return thisVar;
        }

        thisVar = JavascriptOperators::OP_GetThis(thisVar, moduleID, functionBody->GetScriptContext());
        dynamicProfileInfo->RecordThisInfo(thisVar, ThisType_Mapped);

        return thisVar;
    }

    Var SimpleJitHelpers::ProfiledSwitch(FunctionBody* functionBody, ProfileId profileId, Var exp)
    {
        functionBody->GetDynamicProfileInfo()->RecordSwitchType(functionBody, profileId, exp);
        return exp;
    }

    Var SimpleJitHelpers::ProfiledDivide(FunctionBody* functionBody, ProfileId profileId, Var aLeft, Var aRight)
    {
        Var result = JavascriptMath::Divide(aLeft, aRight,functionBody->GetScriptContext());
        functionBody->GetDynamicProfileInfo()->RecordDivideResultType(functionBody, profileId, result);
        return result;
    }

    Var SimpleJitHelpers::ProfiledRemainder(FunctionBody* functionBody, ProfileId profileId, Var aLeft, Var aRight)
    {
        if(TaggedInt::IsPair(aLeft, aRight))
        {
            int nLeft    = TaggedInt::ToInt32(aLeft);
            int nRight   = TaggedInt::ToInt32(aRight);

            // nLeft is positive and nRight is +2^i
            // Fast path for Power of 2 divisor
            if (nLeft > 0 && ::Math::IsPow2(nRight))
            {
                functionBody->GetDynamicProfileInfo()->RecordModulusOpType(functionBody, profileId, /*isModByPowerOf2*/ true);
                return TaggedInt::ToVarUnchecked(nLeft & (nRight - 1));
            }
        }
        functionBody->GetDynamicProfileInfo()->RecordModulusOpType(functionBody, profileId, /*isModByPowerOf2*/ false);
        return JavascriptMath::Modulus(aLeft, aRight,functionBody->GetScriptContext());
    }

    void SimpleJitHelpers::StoreArrayHelper(Var arr, uint32 index, Var value)
    {
        //Adapted from InterpreterStackFrame::OP_SetArrayItemC_CI4
        JavascriptArray* array = JavascriptArray::FromAnyArray(arr);

        TypeId typeId = array->GetTypeId();
        if (typeId == TypeIds_NativeIntArray)
        {
            JavascriptArray::OP_SetNativeIntElementC(reinterpret_cast<JavascriptNativeIntArray*>(array), index, value, array->GetScriptContext());
        }
        else if (typeId == TypeIds_NativeFloatArray)
        {
            JavascriptArray::OP_SetNativeFloatElementC(reinterpret_cast<JavascriptNativeFloatArray*>(array), index, value, array->GetScriptContext());
        }
        else
        {
            array->SetArrayLiteralItem(index, value);
        }
    }

    void SimpleJitHelpers::StoreArraySegHelper(Var arr, uint32 index, Var value)
    {
        //Adapted from InterpreterStackFrame::OP_SetArraySegmentItem_CI4
        SparseArraySegment<Var> * segment = (SparseArraySegment<Var> *)arr;

        Assert(segment->left == 0);
        Assert(index < segment->length);

        segment->elements[index] = value;
    }

    LoopEntryPointInfo* SimpleJitHelpers::GetScheduledEntryPoint(void* framePtr, uint loopnum)
    {
        auto layout = JavascriptCallStackLayout::FromFramePointer(framePtr);
        FunctionBody* functionBody = layout->functionObject->GetFunctionBody();

        //REVIEW: Simplejit: Optimization: Don't emit this call if this is true during JIT time

        auto loopHeader = functionBody->GetLoopHeader(loopnum);
        LoopEntryPointInfo * entryPointInfo = loopHeader->GetCurrentEntryPointInfo();

        if (entryPointInfo->IsNotScheduled())
        {
            // Not scheduled yet! It will be impossible for this loop to be scheduled for a JIT unless we're the ones doing it,
            //   so there's no need to check whether the existing job is finished.
            return nullptr;
        }

        // The entry point has been scheduled, it is finished, or something else. Just return the entry point and the
        //   SimpleJitted function will check this on each iteration.
        return entryPointInfo;
    }

    bool SimpleJitHelpers::IsLoopCodeGenDone(LoopEntryPointInfo* info)
    {
        // It is possible for this work item to be removed from the queue after we found out that it was at some point in the queue.
        // In that case, we must manually increment the interpretCount. Once the work item gets scheduled again, it will trigger
        // a bailout when the threshold is reached.
        if (info->IsNotScheduled())
        {
            ++info->loopHeader->interpretCount;
            return false;
        }

        return info->IsCodeGenDone();
    }

    void SimpleJitHelpers::RecordLoopImplicitCallFlags(void* framePtr, uint loopNum, int restoreCallFlags)
    {
        auto layout = JavascriptCallStackLayout::FromFramePointer(framePtr);
        FunctionBody* functionBody = layout->functionObject->GetFunctionBody();
        DynamicProfileInfo * dynamicProfileInfo = functionBody->GetDynamicProfileInfo();
        ThreadContext * threadContext = functionBody->GetScriptContext()->GetThreadContext();
        auto flags = threadContext->GetAddressOfImplicitCallFlags();

        const auto fls = *flags;
        dynamicProfileInfo->RecordLoopImplicitCallFlags(functionBody, loopNum, fls);

        // Due to how JITed code is lowered, even we requested the Opnd to be the size of ImplicitCallArgs, the full
        //     register is pushed, with whatever junk is in the upper bits. So mask them off.
        restoreCallFlags = restoreCallFlags & ((1 << sizeof(ImplicitCallFlags)*8) - 1);

        // If the caller doesn't want to add in any call flags they will pass zero in restoreCallFlags.
        //    and since ORing with 0 is idempotent, we can just OR it either way
        *flags = (ImplicitCallFlags)(restoreCallFlags | fls);
    }
}
