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

InliningThreshold::InliningThreshold(uint nonLoadByteCodeCount, bool forLoopBody, bool asmjs) :
    nonLoadByteCodeCount(nonLoadByteCodeCount),
    forLoopBody(forLoopBody),
    asmjs(asmjs)
{
    SetHeuristics();
}

void InliningThreshold::SetAggressiveHeuristics()
{
    Assert(!this->forLoopBody);
    int limit = CONFIG_FLAG(AggressiveInlineThreshold);

    inlineThreshold = limit;
    constructorInlineThreshold = limit;
    outsideLoopInlineThreshold = limit;
    leafInlineThreshold = limit;
    loopInlineThreshold = limit;
    polymorphicInlineThreshold = limit;
    maxNumberOfInlineesWithLoop = CONFIG_FLAG(MaxNumberOfInlineesWithLoop);

    inlineCountMax = CONFIG_FLAG(AggressiveInlineCountMax);
}

void InliningThreshold::Reset()
{
    SetHeuristics();
}

void InliningThreshold::SetHeuristics()
{
    inlineThreshold = CONFIG_FLAG(InlineThreshold);
    // Inline less aggressively in large functions since the register pressure is likely high.
    // Small functions shouldn't be a problem.
    if (nonLoadByteCodeCount > 800)
    {
        inlineThreshold -= CONFIG_FLAG(InlineThresholdAdjustCountInLargeFunction);
    }
    else if (nonLoadByteCodeCount > 200)
    {
        inlineThreshold -= CONFIG_FLAG(InlineThresholdAdjustCountInMediumSizedFunction);
    }
    else if (nonLoadByteCodeCount < 50)
    {
        inlineThreshold += CONFIG_FLAG(InlineThresholdAdjustCountInSmallFunction);
    }
    if (this->asmjs)
    {
        inlineThreshold += CONFIG_FLAG(AsmJsInlineAdjust);
    }

    constructorInlineThreshold = CONFIG_FLAG(ConstructorInlineThreshold);
    outsideLoopInlineThreshold = CONFIG_FLAG(OutsideLoopInlineThreshold);
    leafInlineThreshold = CONFIG_FLAG(LeafInlineThreshold);
    loopInlineThreshold = CONFIG_FLAG(LoopInlineThreshold);
    polymorphicInlineThreshold = CONFIG_FLAG(PolymorphicInlineThreshold);
    maxNumberOfInlineesWithLoop = CONFIG_FLAG(MaxNumberOfInlineesWithLoop);
    constantArgumentInlineThreshold = CONFIG_FLAG(ConstantArgumentInlineThreshold);
    inlineCountMax = !forLoopBody ? CONFIG_FLAG(InlineCountMax) : CONFIG_FLAG(InlineCountMaxInLoopBodies);
}

// Called from background thread to commit inlining.
bool InliningHeuristics::BackendInlineIntoInliner(const FunctionJITTimeInfo * inlinee,
                                Func * inliner,
                                Func *topFunction,
                                Js::ProfileId callSiteId,
                                bool isConstructorCall,
                                bool isFixedMethodCall,                     // Reserved
                                bool isCallOutsideLoopInTopFunc,            // There is a loop for sure and this call is outside loop
                                bool isCallInsideLoop,
                                uint recursiveInlineDepth,
                                uint16 constantArguments
                                )
{
    // We have one piece of additional data in backend, whether  we are outside loop or inside
    // This function decides to inline or not based on that additional data. Most of the filtering is already done by DeciderInlineIntoInliner which is called
    // during work item creation.
    // This is additional filtering during actual inlining phase.

    // Note *order* is important
    // Following are
    // 1. Constructor is always inlined (irrespective of inside or outside)
    // 2. If the inlinee candidate has constant argument and that argument is used for a branch and the inlinee size is within ConstantArgumentInlineThreshold(157) we inline
    // 3. Inside loops:
    //     3a. Leaf function will always get inlined (irrespective of leaf has loop or not)
    //     3b. If the inlinee has loops, don't inline it (Basically avoiding inlining a loop within another loop unless its leaf).
    // 4. Outside loop (inliner has loops):
    //     4a. Only inline small inlinees. Governed by OutsideLoopInlineThreshold (16)
    // 5. Rest are inlined.
#if ENABLE_DEBUG_CONFIG_OPTIONS
    char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
    char16 debugStringBuffer2[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
    char16 debugStringBuffer3[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
#endif

    // TODO: OOP JIT, somehow need to track across functions
    bool doBackEndAggressiveInline = (constantArguments & inlinee->GetBody()->GetArgUsedForBranch()) != 0;

    if (!PHASE_OFF(Js::InlineRecursivePhase, inliner)
        && inlinee->GetBody()->GetAddr() == inliner->GetJITFunctionBody()->GetAddr()
        && (!inlinee->GetBody()->CanInlineRecursively(recursiveInlineDepth, doBackEndAggressiveInline)))
    {
        INLINE_TESTTRACE(_u("INLINING: Skip Inline (backend): Recursive inlining\tInlinee: %s (#%s)\tCaller: %s (#%s) \tRoot: %s (#%s) Depth: %d\n"),
            inlinee->GetBody()->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer),
            inliner->GetJITFunctionBody()->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2),
            topFunc->GetBody()->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3),
            recursiveInlineDepth);
        return false;
    }

    if(inlinee->IsJsBuiltInForceInline() ||
        PHASE_FORCE(Js::InlinePhase, this->topFunc) ||
        PHASE_FORCE(Js::InlinePhase, inliner) ||
        PHASE_FORCE(Js::InlinePhase, inlinee))
    {
        return true;
    }

    if (PHASE_FORCE(Js::InlineTreePhase, this->topFunc) ||
        PHASE_FORCE(Js::InlineTreePhase, inliner))
    {
        return true;
    }

    if (PHASE_FORCE(Js::InlineAtEveryCallerPhase, inlinee))
    {
        return true;
    }

    const JITTimeProfileInfo *dynamicProfile = inliner->GetReadOnlyProfileInfo();

    bool doConstantArgumentInlining = (dynamicProfile && dynamicProfile->GetConstantArgInfo(callSiteId) & inlinee->GetBody()->GetArgUsedForBranch()) != 0;
    if (doConstantArgumentInlining && inlinee->GetBody()->GetNonLoadByteCodeCount() <  (uint)threshold.constantArgumentInlineThreshold)
    {
        return true;
    }


    if (topFunction->GetWorkItem()->GetJITTimeInfo()->IsAggressiveInliningEnabled())
    {
        return true;
    }

    if (isConstructorCall)
    {
        return true;
    }


    if (isCallInsideLoop && IsInlineeLeaf(inlinee))
    {
        return true;
    }

    if (isCallInsideLoop && inlinee->GetBody()->HasLoops() )                            // Don't inline function with loops inside another loop unless it is a leaf
    {
        INLINE_TESTTRACE(_u("INLINING: Skip Inline (backend): Recursive loop inlining\tInlinee: %s (#%s)\tCaller: %s (#%s) \tRoot: %s (#%s)\n"),
            inlinee->GetBody()->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer),
            inliner->GetJITFunctionBody()->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2),
            topFunc->GetBody()->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3));
        return false;
    }
    byte scale = 1;

    if (doBackEndAggressiveInline)
    {
        scale = 2;
    }

    if (isCallOutsideLoopInTopFunc &&
        (threshold.outsideLoopInlineThreshold < 0 ||
        inlinee->GetBody()->GetNonLoadByteCodeCount() > (uint)threshold.outsideLoopInlineThreshold * scale))
    {
        Assert(!isCallInsideLoop);
        INLINE_TESTTRACE(_u("INLINING: Skip Inline (backend): Inlining outside loop doesn't meet OutsideLoopInlineThreshold: %d \tBytecode size: %d\tInlinee: %s (#%s)\tCaller: %s (#%s) \tRoot: %s (#%s)\n"),
            threshold.outsideLoopInlineThreshold,
            inlinee->GetBody()->GetByteCodeCount(),
            inlinee->GetBody()->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer),
            inliner->GetJITFunctionBody()->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2),
            topFunc->GetBody()->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3));
        return false;
    }
    return true;
}
