/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "Dalvik.h"
#include "Dataflow.h"
#include "libdex/DexOpcodes.h"

/* Convert the reg id from the callee to the original id passed by the caller */
static inline u4 convertRegId(const DecodedInstruction *invoke,
                              const Method *calleeMethod,
                              int calleeRegId, bool isRange)
{
    /* The order in the original arg passing list */
    int rank = calleeRegId -
               (calleeMethod->registersSize - calleeMethod->insSize);
    assert(rank >= 0);
    if (!isRange) {
        return invoke->arg[rank];
    } else {
        return invoke->vC + rank;
    }
}

static bool inlineGetter(CompilationUnit *cUnit,
                         const Method *calleeMethod,
                         MIR *invokeMIR,
                         BasicBlock *invokeBB,
                         bool isPredicted,
                         bool isRange)
{
    BasicBlock *moveResultBB = invokeBB->fallThrough;
    MIR *moveResultMIR = moveResultBB->firstMIRInsn;
    MIR *newGetterMIR = (MIR *)dvmCompilerNew(sizeof(MIR), true);
    DecodedInstruction getterInsn;

    /*
     * Not all getter instructions have vC but vC will be read by
     * dvmCompilerGetDalvikDisassembly unconditionally.
     * Initialize it here to get Valgrind happy.
     */
    getterInsn.vC = 0;

    dexDecodeInstruction(calleeMethod->insns, &getterInsn);

    if (!dvmCompilerCanIncludeThisInstruction(calleeMethod, &getterInsn))
        return false;

    /*
     * Some getters (especially invoked through interface) are not followed
     * by a move result.
     */
    if ((moveResultMIR == NULL) ||
        (moveResultMIR->dalvikInsn.opcode != OP_MOVE_RESULT &&
         moveResultMIR->dalvikInsn.opcode != OP_MOVE_RESULT_OBJECT &&
         moveResultMIR->dalvikInsn.opcode != OP_MOVE_RESULT_WIDE)) {
        return false;
    }

    int dfFlags = dvmCompilerDataFlowAttributes[getterInsn.opcode];

    /* Expecting vA to be the destination register */
    if (dfFlags & (DF_UA | DF_UA_WIDE)) {
        ALOGE("opcode %d has DF_UA set (not expected)", getterInsn.opcode);
        dvmAbort();
    }

    if (dfFlags & DF_UB) {
        getterInsn.vB = convertRegId(&invokeMIR->dalvikInsn, calleeMethod,
                                     getterInsn.vB, isRange);
    }

    if (dfFlags & DF_UC) {
        getterInsn.vC = convertRegId(&invokeMIR->dalvikInsn, calleeMethod,
                                     getterInsn.vC, isRange);
    }

    getterInsn.vA = moveResultMIR->dalvikInsn.vA;

    /* Now setup the Dalvik instruction with converted src/dst registers */
    newGetterMIR->dalvikInsn = getterInsn;

    newGetterMIR->width = dexGetWidthFromOpcode(getterInsn.opcode);

    newGetterMIR->OptimizationFlags |= MIR_CALLEE;

    /*
     * If the getter instruction is about to raise any exception, punt to the
     * interpreter and re-execute the invoke.
     */
    newGetterMIR->offset = invokeMIR->offset;

    newGetterMIR->meta.calleeMethod = calleeMethod;

    dvmCompilerInsertMIRAfter(invokeBB, invokeMIR, newGetterMIR);

    if (isPredicted) {
        MIR *invokeMIRSlow = (MIR *)dvmCompilerNew(sizeof(MIR), true);
        *invokeMIRSlow = *invokeMIR;
        invokeMIR->dalvikInsn.opcode = (Opcode)kMirOpCheckInlinePrediction;

        /* Use vC to denote the first argument (ie this) */
        if (!isRange) {
            invokeMIR->dalvikInsn.vC = invokeMIRSlow->dalvikInsn.arg[0];
        }

        moveResultMIR->OptimizationFlags |= MIR_INLINED_PRED;

        dvmCompilerInsertMIRAfter(invokeBB, newGetterMIR, invokeMIRSlow);
        invokeMIRSlow->OptimizationFlags |= MIR_INLINED_PRED;
#if defined(WITH_JIT_TUNING)
        gDvmJit.invokePolyGetterInlined++;
#endif
    } else {
        invokeMIR->OptimizationFlags |= MIR_INLINED;
        moveResultMIR->OptimizationFlags |= MIR_INLINED;
#if defined(WITH_JIT_TUNING)
        gDvmJit.invokeMonoGetterInlined++;
#endif
    }

    return true;
}

static bool inlineSetter(CompilationUnit *cUnit,
                         const Method *calleeMethod,
                         MIR *invokeMIR,
                         BasicBlock *invokeBB,
                         bool isPredicted,
                         bool isRange)
{
    MIR *newSetterMIR = (MIR *)dvmCompilerNew(sizeof(MIR), true);
    DecodedInstruction setterInsn;

    /*
     * Not all setter instructions have vC but vC will be read by
     * dvmCompilerGetDalvikDisassembly unconditionally.
     * Initialize it here to get Valgrind happy.
     */
    setterInsn.vC = 0;

    dexDecodeInstruction(calleeMethod->insns, &setterInsn);

    if (!dvmCompilerCanIncludeThisInstruction(calleeMethod, &setterInsn))
        return false;

    int dfFlags = dvmCompilerDataFlowAttributes[setterInsn.opcode];

    if (dfFlags & (DF_UA | DF_UA_WIDE)) {
        setterInsn.vA = convertRegId(&invokeMIR->dalvikInsn, calleeMethod,
                                     setterInsn.vA, isRange);

    }

    if (dfFlags & DF_UB) {
        setterInsn.vB = convertRegId(&invokeMIR->dalvikInsn, calleeMethod,
                                     setterInsn.vB, isRange);

    }

    if (dfFlags & DF_UC) {
        setterInsn.vC = convertRegId(&invokeMIR->dalvikInsn, calleeMethod,
                                     setterInsn.vC, isRange);
    }

    /* Now setup the Dalvik instruction with converted src/dst registers */
    newSetterMIR->dalvikInsn = setterInsn;

    newSetterMIR->width = dexGetWidthFromOpcode(setterInsn.opcode);

    newSetterMIR->OptimizationFlags |= MIR_CALLEE;

    /*
     * If the setter instruction is about to raise any exception, punt to the
     * interpreter and re-execute the invoke.
     */
    newSetterMIR->offset = invokeMIR->offset;

    newSetterMIR->meta.calleeMethod = calleeMethod;

    dvmCompilerInsertMIRAfter(invokeBB, invokeMIR, newSetterMIR);

    if (isPredicted) {
        MIR *invokeMIRSlow = (MIR *)dvmCompilerNew(sizeof(MIR), true);
        *invokeMIRSlow = *invokeMIR;
        invokeMIR->dalvikInsn.opcode = (Opcode)kMirOpCheckInlinePrediction;

        /* Use vC to denote the first argument (ie this) */
        if (!isRange) {
            invokeMIR->dalvikInsn.vC = invokeMIRSlow->dalvikInsn.arg[0];
        }

        dvmCompilerInsertMIRAfter(invokeBB, newSetterMIR, invokeMIRSlow);
        invokeMIRSlow->OptimizationFlags |= MIR_INLINED_PRED;
#if defined(WITH_JIT_TUNING)
        gDvmJit.invokePolySetterInlined++;
#endif
    } else {
        /*
         * The invoke becomes no-op so it needs an explicit branch to jump to
         * the chaining cell.
         */
        invokeBB->needFallThroughBranch = true;
        invokeMIR->OptimizationFlags |= MIR_INLINED;
#if defined(WITH_JIT_TUNING)
        gDvmJit.invokeMonoSetterInlined++;
#endif
    }

    return true;
}

static bool tryInlineSingletonCallsite(CompilationUnit *cUnit,
                                       const Method *calleeMethod,
                                       MIR *invokeMIR,
                                       BasicBlock *invokeBB,
                                       bool isRange)
{
    /* Not a Java method */
    if (dvmIsNativeMethod(calleeMethod)) return false;

    CompilerMethodStats *methodStats =
        dvmCompilerAnalyzeMethodBody(calleeMethod, true);

    /* Empty callee - do nothing */
    if (methodStats->attributes & METHOD_IS_EMPTY) {
        /* The original invoke instruction is effectively turned into NOP */
        invokeMIR->OptimizationFlags |= MIR_INLINED;
        /*
         * Need to insert an explicit branch to catch the falling knife (into
         * the PC reconstruction or chaining cell).
         */
        invokeBB->needFallThroughBranch = true;
        return true;
    }

    if (methodStats->attributes & METHOD_IS_GETTER) {
        return inlineGetter(cUnit, calleeMethod, invokeMIR, invokeBB, false,
                            isRange);
    } else if (methodStats->attributes & METHOD_IS_SETTER) {
        return inlineSetter(cUnit, calleeMethod, invokeMIR, invokeBB, false,
                            isRange);
    }
    return false;
}

static bool inlineEmptyVirtualCallee(CompilationUnit *cUnit,
                                     const Method *calleeMethod,
                                     MIR *invokeMIR,
                                     BasicBlock *invokeBB)
{
    MIR *invokeMIRSlow = (MIR *)dvmCompilerNew(sizeof(MIR), true);
    *invokeMIRSlow = *invokeMIR;
    invokeMIR->dalvikInsn.opcode = (Opcode)kMirOpCheckInlinePrediction;

    dvmCompilerInsertMIRAfter(invokeBB, invokeMIR, invokeMIRSlow);
    invokeMIRSlow->OptimizationFlags |= MIR_INLINED_PRED;
    return true;
}

static bool tryInlineVirtualCallsite(CompilationUnit *cUnit,
                                     const Method *calleeMethod,
                                     MIR *invokeMIR,
                                     BasicBlock *invokeBB,
                                     bool isRange)
{
    /* Not a Java method */
    if (dvmIsNativeMethod(calleeMethod)) return false;

    CompilerMethodStats *methodStats =
        dvmCompilerAnalyzeMethodBody(calleeMethod, true);

    /* Empty callee - do nothing by checking the clazz pointer */
    if (methodStats->attributes & METHOD_IS_EMPTY) {
        return inlineEmptyVirtualCallee(cUnit, calleeMethod, invokeMIR,
                                        invokeBB);
    }

    if (methodStats->attributes & METHOD_IS_GETTER) {
        return inlineGetter(cUnit, calleeMethod, invokeMIR, invokeBB, true,
                            isRange);
    } else if (methodStats->attributes & METHOD_IS_SETTER) {
        return inlineSetter(cUnit, calleeMethod, invokeMIR, invokeBB, true,
                            isRange);
    }
    return false;
}


void dvmCompilerInlineMIR(CompilationUnit *cUnit, JitTranslationInfo *info)
{
    bool isRange = false;
    GrowableListIterator iterator;

    dvmGrowableListIteratorInit(&cUnit->blockList, &iterator);
    /*
     * Analyze the basic block containing an invoke to see if it can be inlined
     */
    while (true) {
        BasicBlock *bb = (BasicBlock *) dvmGrowableListIteratorNext(&iterator);
        if (bb == NULL) break;
        if (bb->blockType != kDalvikByteCode)
            continue;
        MIR *lastMIRInsn = bb->lastMIRInsn;
        Opcode opcode = lastMIRInsn->dalvikInsn.opcode;
        int flags = (int)dexGetFlagsFromOpcode(opcode);

        /* No invoke - continue */
        if ((flags & kInstrInvoke) == 0)
            continue;

        /* Disable inlining when doing method tracing */
        if (gDvmJit.methodTraceSupport)
            continue;

        /*
         * If the invoke itself is selected for single stepping, don't bother
         * to inline it.
         */
        if (SINGLE_STEP_OP(opcode))
            continue;

        const Method *calleeMethod;

        switch (opcode) {
            case OP_INVOKE_SUPER:
            case OP_INVOKE_DIRECT:
            case OP_INVOKE_STATIC:
            case OP_INVOKE_SUPER_QUICK:
                calleeMethod = lastMIRInsn->meta.callsiteInfo->method;
                break;
            case OP_INVOKE_SUPER_RANGE:
            case OP_INVOKE_DIRECT_RANGE:
            case OP_INVOKE_STATIC_RANGE:
            case OP_INVOKE_SUPER_QUICK_RANGE:
                isRange = true;
                calleeMethod = lastMIRInsn->meta.callsiteInfo->method;
                break;
            default:
                calleeMethod = NULL;
                break;
        }

        if (calleeMethod) {
            bool inlined = tryInlineSingletonCallsite(cUnit, calleeMethod,
                                                      lastMIRInsn, bb, isRange);
            if (!inlined &&
                !(gDvmJit.disableOpt & (1 << kMethodJit)) &&
                !dvmIsNativeMethod(calleeMethod)) {
                CompilerMethodStats *methodStats =
                    dvmCompilerAnalyzeMethodBody(calleeMethod, true);
                if ((methodStats->attributes & METHOD_IS_LEAF) &&
                    !(methodStats->attributes & METHOD_CANNOT_COMPILE)) {
                    /* Callee has been previously compiled */
                    if (dvmJitGetMethodAddr(calleeMethod->insns)) {
                        lastMIRInsn->OptimizationFlags |= MIR_INVOKE_METHOD_JIT;
                    } else {
                        /* Compile the callee first */
                        dvmCompileMethod(calleeMethod, info);
                        if (dvmJitGetMethodAddr(calleeMethod->insns)) {
                            lastMIRInsn->OptimizationFlags |=
                                MIR_INVOKE_METHOD_JIT;
                        } else {
                            methodStats->attributes |= METHOD_CANNOT_COMPILE;
                        }
                    }
                }
            }
            return;
        }

        switch (opcode) {
            case OP_INVOKE_VIRTUAL:
            case OP_INVOKE_VIRTUAL_QUICK:
            case OP_INVOKE_INTERFACE:
                isRange = false;
                calleeMethod = lastMIRInsn->meta.callsiteInfo->method;
                break;
            case OP_INVOKE_VIRTUAL_RANGE:
            case OP_INVOKE_VIRTUAL_QUICK_RANGE:
            case OP_INVOKE_INTERFACE_RANGE:
                isRange = true;
                calleeMethod = lastMIRInsn->meta.callsiteInfo->method;
                break;
            default:
                break;
        }

        if (calleeMethod) {
            bool inlined = tryInlineVirtualCallsite(cUnit, calleeMethod,
                                                    lastMIRInsn, bb, isRange);
            if (!inlined &&
                !(gDvmJit.disableOpt & (1 << kMethodJit)) &&
                !dvmIsNativeMethod(calleeMethod)) {
                CompilerMethodStats *methodStats =
                    dvmCompilerAnalyzeMethodBody(calleeMethod, true);
                if ((methodStats->attributes & METHOD_IS_LEAF) &&
                    !(methodStats->attributes & METHOD_CANNOT_COMPILE)) {
                    /* Callee has been previously compiled */
                    if (dvmJitGetMethodAddr(calleeMethod->insns)) {
                        lastMIRInsn->OptimizationFlags |= MIR_INVOKE_METHOD_JIT;
                    } else {
                        /* Compile the callee first */
                        dvmCompileMethod(calleeMethod, info);
                        if (dvmJitGetMethodAddr(calleeMethod->insns)) {
                            lastMIRInsn->OptimizationFlags |=
                                MIR_INVOKE_METHOD_JIT;
                        } else {
                            methodStats->attributes |= METHOD_CANNOT_COMPILE;
                        }
                    }
                }
            }
            return;
        }
    }
}