/*
 * Copyright (C) 2013 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 "dex_file_method_inliner.h"

#include <algorithm>

#include "base/macros.h"
#include "base/mutex.h"
#include "base/mutex-inl.h"
#include "dex/frontend.h"
#include "thread.h"
#include "thread-inl.h"
#include "dex/mir_graph.h"
#include "dex/quick/mir_to_lir.h"
#include "dex_instruction.h"
#include "dex_instruction-inl.h"
#include "driver/dex_compilation_unit.h"
#include "verifier/method_verifier.h"
#include "verifier/method_verifier-inl.h"

namespace art {

namespace {  // anonymous namespace

static constexpr bool kIntrinsicIsStatic[] = {
    true,   // kIntrinsicDoubleCvt
    true,   // kIntrinsicFloatCvt
    true,   // kIntrinsicReverseBits
    true,   // kIntrinsicReverseBytes
    true,   // kIntrinsicAbsInt
    true,   // kIntrinsicAbsLong
    true,   // kIntrinsicAbsFloat
    true,   // kIntrinsicAbsDouble
    true,   // kIntrinsicMinMaxInt
    true,   // kIntrinsicMinMaxLong
    true,   // kIntrinsicMinMaxFloat
    true,   // kIntrinsicMinMaxDouble
    true,   // kIntrinsicSqrt
    true,   // kIntrinsicCeil
    true,   // kIntrinsicFloor
    true,   // kIntrinsicRint
    true,   // kIntrinsicRoundFloat
    true,   // kIntrinsicRoundDouble
    false,  // kIntrinsicReferenceGetReferent
    false,  // kIntrinsicCharAt
    false,  // kIntrinsicCompareTo
    false,  // kIntrinsicIsEmptyOrLength
    false,  // kIntrinsicIndexOf
    true,   // kIntrinsicCurrentThread
    true,   // kIntrinsicPeek
    true,   // kIntrinsicPoke
    false,  // kIntrinsicCas
    false,  // kIntrinsicUnsafeGet
    false,  // kIntrinsicUnsafePut
    true,   // kIntrinsicSystemArrayCopyCharArray
};
COMPILE_ASSERT(arraysize(kIntrinsicIsStatic) == kInlineOpNop, check_arraysize_kIntrinsicIsStatic);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicDoubleCvt], DoubleCvt_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicFloatCvt], FloatCvt_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicReverseBits], ReverseBits_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicReverseBytes], ReverseBytes_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicAbsInt], AbsInt_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicAbsLong], AbsLong_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicAbsFloat], AbsFloat_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicAbsDouble], AbsDouble_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicMinMaxInt], MinMaxInt_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicMinMaxLong], MinMaxLong_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicMinMaxFloat], MinMaxFloat_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicMinMaxDouble], MinMaxDouble_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicSqrt], Sqrt_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicCeil], Ceil_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicFloor], Floor_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicRint], Rint_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicRoundFloat], RoundFloat_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicRoundDouble], RoundDouble_must_be_static);
COMPILE_ASSERT(!kIntrinsicIsStatic[kIntrinsicReferenceGetReferent], Get_must_not_be_static);
COMPILE_ASSERT(!kIntrinsicIsStatic[kIntrinsicCharAt], CharAt_must_not_be_static);
COMPILE_ASSERT(!kIntrinsicIsStatic[kIntrinsicCompareTo], CompareTo_must_not_be_static);
COMPILE_ASSERT(!kIntrinsicIsStatic[kIntrinsicIsEmptyOrLength], IsEmptyOrLength_must_not_be_static);
COMPILE_ASSERT(!kIntrinsicIsStatic[kIntrinsicIndexOf], IndexOf_must_not_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicCurrentThread], CurrentThread_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicPeek], Peek_must_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicPoke], Poke_must_be_static);
COMPILE_ASSERT(!kIntrinsicIsStatic[kIntrinsicCas], Cas_must_not_be_static);
COMPILE_ASSERT(!kIntrinsicIsStatic[kIntrinsicUnsafeGet], UnsafeGet_must_not_be_static);
COMPILE_ASSERT(!kIntrinsicIsStatic[kIntrinsicUnsafePut], UnsafePut_must_not_be_static);
COMPILE_ASSERT(kIntrinsicIsStatic[kIntrinsicSystemArrayCopyCharArray],
               SystemArrayCopyCharArray_must_be_static);

MIR* AllocReplacementMIR(MIRGraph* mir_graph, MIR* invoke, MIR* move_return) {
  MIR* insn = mir_graph->NewMIR();
  insn->offset = invoke->offset;
  insn->optimization_flags = MIR_CALLEE;
  return insn;
}

uint32_t GetInvokeReg(MIR* invoke, uint32_t arg) {
  DCHECK_LT(arg, invoke->dalvikInsn.vA);
  DCHECK(!MIR::DecodedInstruction::IsPseudoMirOp(invoke->dalvikInsn.opcode));
  if (Instruction::FormatOf(invoke->dalvikInsn.opcode) == Instruction::k3rc) {
    return invoke->dalvikInsn.vC + arg;  // Non-range invoke.
  } else {
    DCHECK_EQ(Instruction::FormatOf(invoke->dalvikInsn.opcode), Instruction::k35c);
    return invoke->dalvikInsn.arg[arg];  // Range invoke.
  }
}

bool WideArgIsInConsecutiveDalvikRegs(MIR* invoke, uint32_t arg) {
  DCHECK_LT(arg + 1, invoke->dalvikInsn.vA);
  DCHECK(!MIR::DecodedInstruction::IsPseudoMirOp(invoke->dalvikInsn.opcode));
  return Instruction::FormatOf(invoke->dalvikInsn.opcode) == Instruction::k3rc ||
      invoke->dalvikInsn.arg[arg + 1u] == invoke->dalvikInsn.arg[arg] + 1u;
}

}  // anonymous namespace

const uint32_t DexFileMethodInliner::kIndexUnresolved;
const char* const DexFileMethodInliner::kClassCacheNames[] = {
    "Z",                       // kClassCacheBoolean
    "B",                       // kClassCacheByte
    "C",                       // kClassCacheChar
    "S",                       // kClassCacheShort
    "I",                       // kClassCacheInt
    "J",                       // kClassCacheLong
    "F",                       // kClassCacheFloat
    "D",                       // kClassCacheDouble
    "V",                       // kClassCacheVoid
    "Ljava/lang/Object;",      // kClassCacheJavaLangObject
    "Ljava/lang/ref/Reference;",  // kClassCacheJavaLangRefReference
    "Ljava/lang/String;",      // kClassCacheJavaLangString
    "Ljava/lang/Double;",      // kClassCacheJavaLangDouble
    "Ljava/lang/Float;",       // kClassCacheJavaLangFloat
    "Ljava/lang/Integer;",     // kClassCacheJavaLangInteger
    "Ljava/lang/Long;",        // kClassCacheJavaLangLong
    "Ljava/lang/Short;",       // kClassCacheJavaLangShort
    "Ljava/lang/Math;",        // kClassCacheJavaLangMath
    "Ljava/lang/StrictMath;",  // kClassCacheJavaLangStrictMath
    "Ljava/lang/Thread;",      // kClassCacheJavaLangThread
    "Llibcore/io/Memory;",     // kClassCacheLibcoreIoMemory
    "Lsun/misc/Unsafe;",       // kClassCacheSunMiscUnsafe
    "Ljava/lang/System;",      // kClassCacheJavaLangSystem
    "[C"                       // kClassCacheJavaLangCharArray
};

const char* const DexFileMethodInliner::kNameCacheNames[] = {
    "reverse",               // kNameCacheReverse
    "reverseBytes",          // kNameCacheReverseBytes
    "doubleToRawLongBits",   // kNameCacheDoubleToRawLongBits
    "longBitsToDouble",      // kNameCacheLongBitsToDouble
    "floatToRawIntBits",     // kNameCacheFloatToRawIntBits
    "intBitsToFloat",        // kNameCacheIntBitsToFloat
    "abs",                   // kNameCacheAbs
    "max",                   // kNameCacheMax
    "min",                   // kNameCacheMin
    "sqrt",                  // kNameCacheSqrt
    "ceil",                  // kNameCacheCeil
    "floor",                 // kNameCacheFloor
    "rint",                  // kNameCacheRint
    "round",                 // kNameCacheRound
    "getReferent",           // kNameCacheReferenceGet
    "charAt",                // kNameCacheCharAt
    "compareTo",             // kNameCacheCompareTo
    "isEmpty",               // kNameCacheIsEmpty
    "indexOf",               // kNameCacheIndexOf
    "length",                // kNameCacheLength
    "currentThread",         // kNameCacheCurrentThread
    "peekByte",              // kNameCachePeekByte
    "peekIntNative",         // kNameCachePeekIntNative
    "peekLongNative",        // kNameCachePeekLongNative
    "peekShortNative",       // kNameCachePeekShortNative
    "pokeByte",              // kNameCachePokeByte
    "pokeIntNative",         // kNameCachePokeIntNative
    "pokeLongNative",        // kNameCachePokeLongNative
    "pokeShortNative",       // kNameCachePokeShortNative
    "compareAndSwapInt",     // kNameCacheCompareAndSwapInt
    "compareAndSwapLong",    // kNameCacheCompareAndSwapLong
    "compareAndSwapObject",  // kNameCacheCompareAndSwapObject
    "getInt",                // kNameCacheGetInt
    "getIntVolatile",        // kNameCacheGetIntVolatile
    "putInt",                // kNameCachePutInt
    "putIntVolatile",        // kNameCachePutIntVolatile
    "putOrderedInt",         // kNameCachePutOrderedInt
    "getLong",               // kNameCacheGetLong
    "getLongVolatile",       // kNameCacheGetLongVolatile
    "putLong",               // kNameCachePutLong
    "putLongVolatile",       // kNameCachePutLongVolatile
    "putOrderedLong",        // kNameCachePutOrderedLong
    "getObject",             // kNameCacheGetObject
    "getObjectVolatile",     // kNameCacheGetObjectVolatile
    "putObject",             // kNameCachePutObject
    "putObjectVolatile",     // kNameCachePutObjectVolatile
    "putOrderedObject",      // kNameCachePutOrderedObject
    "arraycopy",             // kNameCacheArrayCopy
};

const DexFileMethodInliner::ProtoDef DexFileMethodInliner::kProtoCacheDefs[] = {
    // kProtoCacheI_I
    { kClassCacheInt, 1, { kClassCacheInt } },
    // kProtoCacheJ_J
    { kClassCacheLong, 1, { kClassCacheLong } },
    // kProtoCacheS_S
    { kClassCacheShort, 1, { kClassCacheShort } },
    // kProtoCacheD_D
    { kClassCacheDouble, 1, { kClassCacheDouble } },
    // kProtoCacheDD_D
    { kClassCacheDouble, 2, { kClassCacheDouble, kClassCacheDouble } },
    // kProtoCacheF_F
    { kClassCacheFloat, 1, { kClassCacheFloat } },
    // kProtoCacheFF_F
    { kClassCacheFloat, 2, { kClassCacheFloat, kClassCacheFloat } },
    // kProtoCacheD_J
    { kClassCacheLong, 1, { kClassCacheDouble } },
    // kProtoCacheJ_D
    { kClassCacheDouble, 1, { kClassCacheLong } },
    // kProtoCacheF_I
    { kClassCacheInt, 1, { kClassCacheFloat } },
    // kProtoCacheI_F
    { kClassCacheFloat, 1, { kClassCacheInt } },
    // kProtoCacheII_I
    { kClassCacheInt, 2, { kClassCacheInt, kClassCacheInt } },
    // kProtoCacheI_C
    { kClassCacheChar, 1, { kClassCacheInt } },
    // kProtoCacheString_I
    { kClassCacheInt, 1, { kClassCacheJavaLangString } },
    // kProtoCache_Z
    { kClassCacheBoolean, 0, { } },
    // kProtoCache_I
    { kClassCacheInt, 0, { } },
    // kProtoCache_Object
    { kClassCacheJavaLangObject, 0, { } },
    // kProtoCache_Thread
    { kClassCacheJavaLangThread, 0, { } },
    // kProtoCacheJ_B
    { kClassCacheByte, 1, { kClassCacheLong } },
    // kProtoCacheJ_I
    { kClassCacheInt, 1, { kClassCacheLong } },
    // kProtoCacheJ_S
    { kClassCacheShort, 1, { kClassCacheLong } },
    // kProtoCacheJB_V
    { kClassCacheVoid, 2, { kClassCacheLong, kClassCacheByte } },
    // kProtoCacheJI_V
    { kClassCacheVoid, 2, { kClassCacheLong, kClassCacheInt } },
    // kProtoCacheJJ_J
    { kClassCacheLong, 2, { kClassCacheLong, kClassCacheLong } },
    // kProtoCacheJJ_V
    { kClassCacheVoid, 2, { kClassCacheLong, kClassCacheLong } },
    // kProtoCacheJS_V
    { kClassCacheVoid, 2, { kClassCacheLong, kClassCacheShort } },
    // kProtoCacheObjectJII_Z
    { kClassCacheBoolean, 4, { kClassCacheJavaLangObject, kClassCacheLong,
        kClassCacheInt, kClassCacheInt } },
    // kProtoCacheObjectJJJ_Z
    { kClassCacheBoolean, 4, { kClassCacheJavaLangObject, kClassCacheLong,
        kClassCacheLong, kClassCacheLong } },
    // kProtoCacheObjectJObjectObject_Z
    { kClassCacheBoolean, 4, { kClassCacheJavaLangObject, kClassCacheLong,
        kClassCacheJavaLangObject, kClassCacheJavaLangObject } },
    // kProtoCacheObjectJ_I
    { kClassCacheInt, 2, { kClassCacheJavaLangObject, kClassCacheLong } },
    // kProtoCacheObjectJI_V
    { kClassCacheVoid, 3, { kClassCacheJavaLangObject, kClassCacheLong, kClassCacheInt } },
    // kProtoCacheObjectJ_J
    { kClassCacheLong, 2, { kClassCacheJavaLangObject, kClassCacheLong } },
    // kProtoCacheObjectJJ_V
    { kClassCacheVoid, 3, { kClassCacheJavaLangObject, kClassCacheLong, kClassCacheLong } },
    // kProtoCacheObjectJ_Object
    { kClassCacheJavaLangObject, 2, { kClassCacheJavaLangObject, kClassCacheLong } },
    // kProtoCacheObjectJObject_V
    { kClassCacheVoid, 3, { kClassCacheJavaLangObject, kClassCacheLong,
        kClassCacheJavaLangObject } },
    // kProtoCacheCharArrayICharArrayII_V
    { kClassCacheVoid, 5, {kClassCacheJavaLangCharArray, kClassCacheInt,
                kClassCacheJavaLangCharArray, kClassCacheInt, kClassCacheInt}}
};

const DexFileMethodInliner::IntrinsicDef DexFileMethodInliner::kIntrinsicMethods[] = {
#define INTRINSIC(c, n, p, o, d) \
    { { kClassCache ## c, kNameCache ## n, kProtoCache ## p }, { o, kInlineIntrinsic, { d } } }

    INTRINSIC(JavaLangDouble, DoubleToRawLongBits, D_J, kIntrinsicDoubleCvt, 0),
    INTRINSIC(JavaLangDouble, LongBitsToDouble, J_D, kIntrinsicDoubleCvt, 0),
    INTRINSIC(JavaLangFloat, FloatToRawIntBits, F_I, kIntrinsicFloatCvt, 0),
    INTRINSIC(JavaLangFloat, IntBitsToFloat, I_F, kIntrinsicFloatCvt, 0),

    INTRINSIC(JavaLangInteger, ReverseBytes, I_I, kIntrinsicReverseBytes, k32),
    INTRINSIC(JavaLangLong, ReverseBytes, J_J, kIntrinsicReverseBytes, k64),
    INTRINSIC(JavaLangShort, ReverseBytes, S_S, kIntrinsicReverseBytes, kSignedHalf),
    INTRINSIC(JavaLangInteger, Reverse, I_I, kIntrinsicReverseBits, k32),
    INTRINSIC(JavaLangLong, Reverse, J_J, kIntrinsicReverseBits, k64),

    INTRINSIC(JavaLangMath,       Abs, I_I, kIntrinsicAbsInt, 0),
    INTRINSIC(JavaLangStrictMath, Abs, I_I, kIntrinsicAbsInt, 0),
    INTRINSIC(JavaLangMath,       Abs, J_J, kIntrinsicAbsLong, 0),
    INTRINSIC(JavaLangStrictMath, Abs, J_J, kIntrinsicAbsLong, 0),
    INTRINSIC(JavaLangMath,       Abs, F_F, kIntrinsicAbsFloat, 0),
    INTRINSIC(JavaLangStrictMath, Abs, F_F, kIntrinsicAbsFloat, 0),
    INTRINSIC(JavaLangMath,       Abs, D_D, kIntrinsicAbsDouble, 0),
    INTRINSIC(JavaLangStrictMath, Abs, D_D, kIntrinsicAbsDouble, 0),
    INTRINSIC(JavaLangMath,       Min, II_I, kIntrinsicMinMaxInt, kIntrinsicFlagMin),
    INTRINSIC(JavaLangStrictMath, Min, II_I, kIntrinsicMinMaxInt, kIntrinsicFlagMin),
    INTRINSIC(JavaLangMath,       Max, II_I, kIntrinsicMinMaxInt, kIntrinsicFlagMax),
    INTRINSIC(JavaLangStrictMath, Max, II_I, kIntrinsicMinMaxInt, kIntrinsicFlagMax),
    INTRINSIC(JavaLangMath,       Min, JJ_J, kIntrinsicMinMaxLong, kIntrinsicFlagMin),
    INTRINSIC(JavaLangStrictMath, Min, JJ_J, kIntrinsicMinMaxLong, kIntrinsicFlagMin),
    INTRINSIC(JavaLangMath,       Max, JJ_J, kIntrinsicMinMaxLong, kIntrinsicFlagMax),
    INTRINSIC(JavaLangStrictMath, Max, JJ_J, kIntrinsicMinMaxLong, kIntrinsicFlagMax),
    INTRINSIC(JavaLangMath,       Min, FF_F, kIntrinsicMinMaxFloat, kIntrinsicFlagMin),
    INTRINSIC(JavaLangStrictMath, Min, FF_F, kIntrinsicMinMaxFloat, kIntrinsicFlagMin),
    INTRINSIC(JavaLangMath,       Max, FF_F, kIntrinsicMinMaxFloat, kIntrinsicFlagMax),
    INTRINSIC(JavaLangStrictMath, Max, FF_F, kIntrinsicMinMaxFloat, kIntrinsicFlagMax),
    INTRINSIC(JavaLangMath,       Min, DD_D, kIntrinsicMinMaxDouble, kIntrinsicFlagMin),
    INTRINSIC(JavaLangStrictMath, Min, DD_D, kIntrinsicMinMaxDouble, kIntrinsicFlagMin),
    INTRINSIC(JavaLangMath,       Max, DD_D, kIntrinsicMinMaxDouble, kIntrinsicFlagMax),
    INTRINSIC(JavaLangStrictMath, Max, DD_D, kIntrinsicMinMaxDouble, kIntrinsicFlagMax),

    INTRINSIC(JavaLangMath,       Sqrt, D_D, kIntrinsicSqrt, 0),
    INTRINSIC(JavaLangStrictMath, Sqrt, D_D, kIntrinsicSqrt, 0),

    INTRINSIC(JavaLangMath,       Ceil, D_D, kIntrinsicCeil, 0),
    INTRINSIC(JavaLangStrictMath, Ceil, D_D, kIntrinsicCeil, 0),
    INTRINSIC(JavaLangMath,       Floor, D_D, kIntrinsicFloor, 0),
    INTRINSIC(JavaLangStrictMath, Floor, D_D, kIntrinsicFloor, 0),
    INTRINSIC(JavaLangMath,       Rint, D_D, kIntrinsicRint, 0),
    INTRINSIC(JavaLangStrictMath, Rint, D_D, kIntrinsicRint, 0),
    INTRINSIC(JavaLangMath,       Round, F_I, kIntrinsicRoundFloat, 0),
    INTRINSIC(JavaLangStrictMath, Round, F_I, kIntrinsicRoundFloat, 0),
    INTRINSIC(JavaLangMath,       Round, D_J, kIntrinsicRoundDouble, 0),
    INTRINSIC(JavaLangStrictMath, Round, D_J, kIntrinsicRoundDouble, 0),

    INTRINSIC(JavaLangRefReference, ReferenceGetReferent, _Object, kIntrinsicReferenceGetReferent, 0),

    INTRINSIC(JavaLangString, CharAt, I_C, kIntrinsicCharAt, 0),
    INTRINSIC(JavaLangString, CompareTo, String_I, kIntrinsicCompareTo, 0),
    INTRINSIC(JavaLangString, IsEmpty, _Z, kIntrinsicIsEmptyOrLength, kIntrinsicFlagIsEmpty),
    INTRINSIC(JavaLangString, IndexOf, II_I, kIntrinsicIndexOf, kIntrinsicFlagNone),
    INTRINSIC(JavaLangString, IndexOf, I_I, kIntrinsicIndexOf, kIntrinsicFlagBase0),
    INTRINSIC(JavaLangString, Length, _I, kIntrinsicIsEmptyOrLength, kIntrinsicFlagLength),

    INTRINSIC(JavaLangThread, CurrentThread, _Thread, kIntrinsicCurrentThread, 0),

    INTRINSIC(LibcoreIoMemory, PeekByte, J_B, kIntrinsicPeek, kSignedByte),
    INTRINSIC(LibcoreIoMemory, PeekIntNative, J_I, kIntrinsicPeek, k32),
    INTRINSIC(LibcoreIoMemory, PeekLongNative, J_J, kIntrinsicPeek, k64),
    INTRINSIC(LibcoreIoMemory, PeekShortNative, J_S, kIntrinsicPeek, kSignedHalf),
    INTRINSIC(LibcoreIoMemory, PokeByte, JB_V, kIntrinsicPoke, kSignedByte),
    INTRINSIC(LibcoreIoMemory, PokeIntNative, JI_V, kIntrinsicPoke, k32),
    INTRINSIC(LibcoreIoMemory, PokeLongNative, JJ_V, kIntrinsicPoke, k64),
    INTRINSIC(LibcoreIoMemory, PokeShortNative, JS_V, kIntrinsicPoke, kSignedHalf),

    INTRINSIC(SunMiscUnsafe, CompareAndSwapInt, ObjectJII_Z, kIntrinsicCas,
              kIntrinsicFlagNone),
    INTRINSIC(SunMiscUnsafe, CompareAndSwapLong, ObjectJJJ_Z, kIntrinsicCas,
              kIntrinsicFlagIsLong),
    INTRINSIC(SunMiscUnsafe, CompareAndSwapObject, ObjectJObjectObject_Z, kIntrinsicCas,
              kIntrinsicFlagIsObject),

#define UNSAFE_GET_PUT(type, code, type_flags) \
    INTRINSIC(SunMiscUnsafe, Get ## type, ObjectJ_ ## code, kIntrinsicUnsafeGet, \
              type_flags & ~kIntrinsicFlagIsObject), \
    INTRINSIC(SunMiscUnsafe, Get ## type ## Volatile, ObjectJ_ ## code, kIntrinsicUnsafeGet, \
              (type_flags | kIntrinsicFlagIsVolatile) & ~kIntrinsicFlagIsObject), \
    INTRINSIC(SunMiscUnsafe, Put ## type, ObjectJ ## code ## _V, kIntrinsicUnsafePut, \
              type_flags), \
    INTRINSIC(SunMiscUnsafe, Put ## type ## Volatile, ObjectJ ## code ## _V, kIntrinsicUnsafePut, \
              type_flags | kIntrinsicFlagIsVolatile), \
    INTRINSIC(SunMiscUnsafe, PutOrdered ## type, ObjectJ ## code ## _V, kIntrinsicUnsafePut, \
              type_flags | kIntrinsicFlagIsOrdered)

    UNSAFE_GET_PUT(Int, I, kIntrinsicFlagNone),
    UNSAFE_GET_PUT(Long, J, kIntrinsicFlagIsLong),
    UNSAFE_GET_PUT(Object, Object, kIntrinsicFlagIsObject),
#undef UNSAFE_GET_PUT

    INTRINSIC(JavaLangSystem, ArrayCopy, CharArrayICharArrayII_V , kIntrinsicSystemArrayCopyCharArray,
              0),


#undef INTRINSIC
};

DexFileMethodInliner::DexFileMethodInliner()
    : lock_("DexFileMethodInliner lock", kDexFileMethodInlinerLock),
      dex_file_(NULL) {
  COMPILE_ASSERT(kClassCacheFirst == 0, kClassCacheFirst_not_0);
  COMPILE_ASSERT(arraysize(kClassCacheNames) == kClassCacheLast, bad_arraysize_kClassCacheNames);
  COMPILE_ASSERT(kNameCacheFirst == 0, kNameCacheFirst_not_0);
  COMPILE_ASSERT(arraysize(kNameCacheNames) == kNameCacheLast, bad_arraysize_kNameCacheNames);
  COMPILE_ASSERT(kProtoCacheFirst == 0, kProtoCacheFirst_not_0);
  COMPILE_ASSERT(arraysize(kProtoCacheDefs) == kProtoCacheLast, bad_arraysize_kProtoCacheNames);
}

DexFileMethodInliner::~DexFileMethodInliner() {
}

bool DexFileMethodInliner::AnalyseMethodCode(verifier::MethodVerifier* verifier) {
  InlineMethod method;
  bool success = InlineMethodAnalyser::AnalyseMethodCode(verifier, &method);
  return success && AddInlineMethod(verifier->GetMethodReference().dex_method_index, method);
}

bool DexFileMethodInliner::IsIntrinsic(uint32_t method_index, InlineMethod* intrinsic) {
  ReaderMutexLock mu(Thread::Current(), lock_);
  auto it = inline_methods_.find(method_index);
  bool res = (it != inline_methods_.end() && (it->second.flags & kInlineIntrinsic) != 0);
  if (res && intrinsic != nullptr) {
    *intrinsic = it->second;
  }
  return res;
}

bool DexFileMethodInliner::GenIntrinsic(Mir2Lir* backend, CallInfo* info) {
  InlineMethod intrinsic;
  {
    ReaderMutexLock mu(Thread::Current(), lock_);
    auto it = inline_methods_.find(info->index);
    if (it == inline_methods_.end() || (it->second.flags & kInlineIntrinsic) == 0) {
      return false;
    }
    intrinsic = it->second;
  }
  if (kIntrinsicIsStatic[intrinsic.opcode] != (info->type == kStatic)) {
    // Invoke type mismatch.
    return false;
  }
  switch (intrinsic.opcode) {
    case kIntrinsicDoubleCvt:
      return backend->GenInlinedDoubleCvt(info);
    case kIntrinsicFloatCvt:
      return backend->GenInlinedFloatCvt(info);
    case kIntrinsicReverseBytes:
      return backend->GenInlinedReverseBytes(info, static_cast<OpSize>(intrinsic.d.data));
    case kIntrinsicReverseBits:
      return backend->GenInlinedReverseBits(info, static_cast<OpSize>(intrinsic.d.data));
    case kIntrinsicAbsInt:
      return backend->GenInlinedAbsInt(info);
    case kIntrinsicAbsLong:
      return backend->GenInlinedAbsLong(info);
    case kIntrinsicAbsFloat:
      return backend->GenInlinedAbsFloat(info);
    case kIntrinsicAbsDouble:
      return backend->GenInlinedAbsDouble(info);
    case kIntrinsicMinMaxInt:
      return backend->GenInlinedMinMax(info, intrinsic.d.data & kIntrinsicFlagMin, false /* is_long */);
    case kIntrinsicMinMaxLong:
      return backend->GenInlinedMinMax(info, intrinsic.d.data & kIntrinsicFlagMin, true /* is_long */);
    case kIntrinsicMinMaxFloat:
      return backend->GenInlinedMinMaxFP(info, intrinsic.d.data & kIntrinsicFlagMin, false /* is_double */);
    case kIntrinsicMinMaxDouble:
      return backend->GenInlinedMinMaxFP(info, intrinsic.d.data & kIntrinsicFlagMin, true /* is_double */);
    case kIntrinsicSqrt:
      return backend->GenInlinedSqrt(info);
    case kIntrinsicCeil:
      return backend->GenInlinedCeil(info);
    case kIntrinsicFloor:
      return backend->GenInlinedFloor(info);
    case kIntrinsicRint:
      return backend->GenInlinedRint(info);
    case kIntrinsicRoundFloat:
      return backend->GenInlinedRound(info, false /* is_double */);
    case kIntrinsicRoundDouble:
      return backend->GenInlinedRound(info, true /* is_double */);
    case kIntrinsicReferenceGetReferent:
      return backend->GenInlinedReferenceGetReferent(info);
    case kIntrinsicCharAt:
      return backend->GenInlinedCharAt(info);
    case kIntrinsicCompareTo:
      return backend->GenInlinedStringCompareTo(info);
    case kIntrinsicIsEmptyOrLength:
      return backend->GenInlinedStringIsEmptyOrLength(
          info, intrinsic.d.data & kIntrinsicFlagIsEmpty);
    case kIntrinsicIndexOf:
      return backend->GenInlinedIndexOf(info, intrinsic.d.data & kIntrinsicFlagBase0);
    case kIntrinsicCurrentThread:
      return backend->GenInlinedCurrentThread(info);
    case kIntrinsicPeek:
      return backend->GenInlinedPeek(info, static_cast<OpSize>(intrinsic.d.data));
    case kIntrinsicPoke:
      return backend->GenInlinedPoke(info, static_cast<OpSize>(intrinsic.d.data));
    case kIntrinsicCas:
      return backend->GenInlinedCas(info, intrinsic.d.data & kIntrinsicFlagIsLong,
                                    intrinsic.d.data & kIntrinsicFlagIsObject);
    case kIntrinsicUnsafeGet:
      return backend->GenInlinedUnsafeGet(info, intrinsic.d.data & kIntrinsicFlagIsLong,
                                          intrinsic.d.data & kIntrinsicFlagIsVolatile);
    case kIntrinsicUnsafePut:
      return backend->GenInlinedUnsafePut(info, intrinsic.d.data & kIntrinsicFlagIsLong,
                                          intrinsic.d.data & kIntrinsicFlagIsObject,
                                          intrinsic.d.data & kIntrinsicFlagIsVolatile,
                                          intrinsic.d.data & kIntrinsicFlagIsOrdered);
    case kIntrinsicSystemArrayCopyCharArray:
      return backend->GenInlinedArrayCopyCharArray(info);
    default:
      LOG(FATAL) << "Unexpected intrinsic opcode: " << intrinsic.opcode;
      return false;  // avoid warning "control reaches end of non-void function"
  }
}

bool DexFileMethodInliner::IsSpecial(uint32_t method_index) {
  ReaderMutexLock mu(Thread::Current(), lock_);
  auto it = inline_methods_.find(method_index);
  return it != inline_methods_.end() && (it->second.flags & kInlineSpecial) != 0;
}

bool DexFileMethodInliner::GenSpecial(Mir2Lir* backend, uint32_t method_idx) {
  InlineMethod special;
  {
    ReaderMutexLock mu(Thread::Current(), lock_);
    auto it = inline_methods_.find(method_idx);
    if (it == inline_methods_.end() || (it->second.flags & kInlineSpecial) == 0) {
      return false;
    }
    special = it->second;
  }
  return backend->SpecialMIR2LIR(special);
}

bool DexFileMethodInliner::GenInline(MIRGraph* mir_graph, BasicBlock* bb, MIR* invoke,
                                     uint32_t method_idx) {
  InlineMethod method;
  {
    ReaderMutexLock mu(Thread::Current(), lock_);
    auto it = inline_methods_.find(method_idx);
    if (it == inline_methods_.end() || (it->second.flags & kInlineSpecial) == 0) {
      return false;
    }
    method = it->second;
  }

  MIR* move_result = nullptr;
  bool result = true;
  switch (method.opcode) {
    case kInlineOpNop:
      break;
    case kInlineOpNonWideConst:
      move_result = mir_graph->FindMoveResult(bb, invoke);
      result = GenInlineConst(mir_graph, bb, invoke, move_result, method);
      break;
    case kInlineOpReturnArg:
      move_result = mir_graph->FindMoveResult(bb, invoke);
      result = GenInlineReturnArg(mir_graph, bb, invoke, move_result, method);
      break;
    case kInlineOpIGet:
      move_result = mir_graph->FindMoveResult(bb, invoke);
      result = GenInlineIGet(mir_graph, bb, invoke, move_result, method, method_idx);
      break;
    case kInlineOpIPut:
      move_result = mir_graph->FindMoveResult(bb, invoke);
      result = GenInlineIPut(mir_graph, bb, invoke, move_result, method, method_idx);
      break;
    default:
      LOG(FATAL) << "Unexpected inline op: " << method.opcode;
      break;
  }
  if (result) {
    invoke->optimization_flags |= MIR_INLINED;
    // If the invoke has not been eliminated yet, check now whether we should do it.
    // This is done so that dataflow analysis does not get tripped up seeing nop invoke.
    if (static_cast<int>(invoke->dalvikInsn.opcode) != kMirOpNop) {
      bool is_static = invoke->dalvikInsn.opcode == Instruction::INVOKE_STATIC ||
          invoke->dalvikInsn.opcode == Instruction::INVOKE_STATIC_RANGE;
      if (is_static || (invoke->optimization_flags & MIR_IGNORE_NULL_CHECK) != 0) {
        // No null object register involved here so we can eliminate the invoke.
        invoke->dalvikInsn.opcode = static_cast<Instruction::Code>(kMirOpNop);
      } else {
        // Invoke was kept around because null check needed to be done.
        invoke->dalvikInsn.opcode = static_cast<Instruction::Code>(kMirOpNullCheck);
        // For invokes, the object register is in vC. For null check mir, it is in vA.
        invoke->dalvikInsn.vA = invoke->dalvikInsn.vC;
      }
    }
    if (move_result != nullptr) {
      move_result->optimization_flags |= MIR_INLINED;
      move_result->dalvikInsn.opcode = static_cast<Instruction::Code>(kMirOpNop);
    }
  }
  return result;
}

uint32_t DexFileMethodInliner::FindClassIndex(const DexFile* dex_file, IndexCache* cache,
                                              ClassCacheIndex index) {
  uint32_t* class_index = &cache->class_indexes[index];
  if (*class_index != kIndexUnresolved) {
    return *class_index;
  }

  const DexFile::StringId* string_id = dex_file->FindStringId(kClassCacheNames[index]);
  if (string_id == nullptr) {
    *class_index = kIndexNotFound;
    return *class_index;
  }
  uint32_t string_index = dex_file->GetIndexForStringId(*string_id);

  const DexFile::TypeId* type_id = dex_file->FindTypeId(string_index);
  if (type_id == nullptr) {
    *class_index = kIndexNotFound;
    return *class_index;
  }
  *class_index = dex_file->GetIndexForTypeId(*type_id);
  return *class_index;
}

uint32_t DexFileMethodInliner::FindNameIndex(const DexFile* dex_file, IndexCache* cache,
                                             NameCacheIndex index) {
  uint32_t* name_index = &cache->name_indexes[index];
  if (*name_index != kIndexUnresolved) {
    return *name_index;
  }

  const DexFile::StringId* string_id = dex_file->FindStringId(kNameCacheNames[index]);
  if (string_id == nullptr) {
    *name_index = kIndexNotFound;
    return *name_index;
  }
  *name_index = dex_file->GetIndexForStringId(*string_id);
  return *name_index;
}

uint32_t DexFileMethodInliner::FindProtoIndex(const DexFile* dex_file, IndexCache* cache,
                                              ProtoCacheIndex index) {
  uint32_t* proto_index = &cache->proto_indexes[index];
  if (*proto_index != kIndexUnresolved) {
    return *proto_index;
  }

  const ProtoDef& proto_def = kProtoCacheDefs[index];
  uint32_t return_index = FindClassIndex(dex_file, cache, proto_def.return_type);
  if (return_index == kIndexNotFound) {
    *proto_index = kIndexNotFound;
    return *proto_index;
  }
  uint16_t return_type = static_cast<uint16_t>(return_index);
  DCHECK_EQ(static_cast<uint32_t>(return_type), return_index);

  uint32_t signature_length = proto_def.param_count;
  uint16_t signature_type_idxs[kProtoMaxParams];
  for (uint32_t i = 0; i != signature_length; ++i) {
    uint32_t param_index = FindClassIndex(dex_file, cache, proto_def.params[i]);
    if (param_index == kIndexNotFound) {
      *proto_index = kIndexNotFound;
      return *proto_index;
    }
    signature_type_idxs[i] = static_cast<uint16_t>(param_index);
    DCHECK_EQ(static_cast<uint32_t>(signature_type_idxs[i]), param_index);
  }

  const DexFile::ProtoId* proto_id = dex_file->FindProtoId(return_type, signature_type_idxs,
                                                           signature_length);
  if (proto_id == nullptr) {
    *proto_index = kIndexNotFound;
    return *proto_index;
  }
  *proto_index = dex_file->GetIndexForProtoId(*proto_id);
  return *proto_index;
}

uint32_t DexFileMethodInliner::FindMethodIndex(const DexFile* dex_file, IndexCache* cache,
                                               const MethodDef& method_def) {
  uint32_t declaring_class_index = FindClassIndex(dex_file, cache, method_def.declaring_class);
  if (declaring_class_index == kIndexNotFound) {
    return kIndexNotFound;
  }
  uint32_t name_index = FindNameIndex(dex_file, cache, method_def.name);
  if (name_index == kIndexNotFound) {
    return kIndexNotFound;
  }
  uint32_t proto_index = FindProtoIndex(dex_file, cache, method_def.proto);
  if (proto_index == kIndexNotFound) {
    return kIndexNotFound;
  }
  const DexFile::MethodId* method_id =
      dex_file->FindMethodId(dex_file->GetTypeId(declaring_class_index),
                             dex_file->GetStringId(name_index),
                             dex_file->GetProtoId(proto_index));
  if (method_id == nullptr) {
    return kIndexNotFound;
  }
  return dex_file->GetIndexForMethodId(*method_id);
}

DexFileMethodInliner::IndexCache::IndexCache() {
  std::fill_n(class_indexes, arraysize(class_indexes), kIndexUnresolved);
  std::fill_n(name_indexes, arraysize(name_indexes), kIndexUnresolved);
  std::fill_n(proto_indexes, arraysize(proto_indexes), kIndexUnresolved);
}

void DexFileMethodInliner::FindIntrinsics(const DexFile* dex_file) {
  DCHECK(dex_file != nullptr);
  DCHECK(dex_file_ == nullptr);
  IndexCache cache;
  for (const IntrinsicDef& def : kIntrinsicMethods) {
    uint32_t method_idx = FindMethodIndex(dex_file, &cache, def.method_def);
    if (method_idx != kIndexNotFound) {
      DCHECK(inline_methods_.find(method_idx) == inline_methods_.end());
      inline_methods_.Put(method_idx, def.intrinsic);
    }
  }
  dex_file_ = dex_file;
}

bool DexFileMethodInliner::AddInlineMethod(int32_t method_idx, const InlineMethod& method) {
  WriterMutexLock mu(Thread::Current(), lock_);
  if (LIKELY(inline_methods_.find(method_idx) == inline_methods_.end())) {
    inline_methods_.Put(method_idx, method);
    return true;
  } else {
    if (PrettyMethod(method_idx, *dex_file_) == "int java.lang.String.length()") {
      // TODO: String.length is both kIntrinsicIsEmptyOrLength and kInlineOpIGet.
    } else {
      LOG(ERROR) << "Inliner: " << PrettyMethod(method_idx, *dex_file_) << " already inline";
    }
    return false;
  }
}

bool DexFileMethodInliner::GenInlineConst(MIRGraph* mir_graph, BasicBlock* bb, MIR* invoke,
                                          MIR* move_result, const InlineMethod& method) {
  if (move_result == nullptr) {
    // Result is unused.
    return true;
  }

  // Check the opcode and for MOVE_RESULT_OBJECT check also that the constant is null.
  DCHECK(move_result->dalvikInsn.opcode == Instruction::MOVE_RESULT ||
         (move_result->dalvikInsn.opcode == Instruction::MOVE_RESULT_OBJECT &&
             method.d.data == 0u));

  // Insert the CONST instruction.
  MIR* insn = AllocReplacementMIR(mir_graph, invoke, move_result);
  insn->dalvikInsn.opcode = Instruction::CONST;
  insn->dalvikInsn.vA = move_result->dalvikInsn.vA;
  insn->dalvikInsn.vB = method.d.data;
  bb->InsertMIRAfter(move_result, insn);
  return true;
}

bool DexFileMethodInliner::GenInlineReturnArg(MIRGraph* mir_graph, BasicBlock* bb, MIR* invoke,
                                              MIR* move_result, const InlineMethod& method) {
  if (move_result == nullptr) {
    // Result is unused.
    return true;
  }

  // Select opcode and argument.
  const InlineReturnArgData& data = method.d.return_data;
  Instruction::Code opcode = Instruction::MOVE_FROM16;
  uint32_t arg = GetInvokeReg(invoke, data.arg);
  if (move_result->dalvikInsn.opcode == Instruction::MOVE_RESULT_OBJECT) {
    DCHECK_EQ(data.is_object, 1u);
    DCHECK_EQ(data.is_wide, 0u);
    opcode = Instruction::MOVE_OBJECT_FROM16;
  } else if (move_result->dalvikInsn.opcode == Instruction::MOVE_RESULT_WIDE) {
    DCHECK_EQ(data.is_wide, 1u);
    DCHECK_EQ(data.is_object, 0u);
    opcode = Instruction::MOVE_WIDE_FROM16;
    if (!WideArgIsInConsecutiveDalvikRegs(invoke, data.arg)) {
      // The two halfs of the source value are not in consecutive dalvik registers in INVOKE.
      return false;
    }
  } else {
    DCHECK(move_result->dalvikInsn.opcode == Instruction::MOVE_RESULT);
    DCHECK_EQ(data.is_wide, 0u);
    DCHECK_EQ(data.is_object, 0u);
  }

  // Insert the move instruction
  MIR* insn = AllocReplacementMIR(mir_graph, invoke, move_result);
  insn->dalvikInsn.opcode = opcode;
  insn->dalvikInsn.vA = move_result->dalvikInsn.vA;
  insn->dalvikInsn.vB = arg;
  bb->InsertMIRAfter(move_result, insn);
  return true;
}

bool DexFileMethodInliner::GenInlineIGet(MIRGraph* mir_graph, BasicBlock* bb, MIR* invoke,
                                         MIR* move_result, const InlineMethod& method,
                                         uint32_t method_idx) {
  CompilationUnit* cu = mir_graph->GetCurrentDexCompilationUnit()->GetCompilationUnit();
  if (cu->enable_debug & (1 << kDebugSlowFieldPath)) {
    return false;
  }

  const InlineIGetIPutData& data = method.d.ifield_data;
  Instruction::Code opcode = static_cast<Instruction::Code>(Instruction::IGET + data.op_variant);
  DCHECK_EQ(InlineMethodAnalyser::IGetVariant(opcode), data.op_variant);
  uint32_t object_reg = GetInvokeReg(invoke, data.object_arg);

  if (move_result == nullptr) {
    // Result is unused. If volatile, we still need to emit the IGET but we have no destination.
    return !data.is_volatile;
  }

  DCHECK_EQ(data.method_is_static != 0u,
            invoke->dalvikInsn.opcode == Instruction::INVOKE_STATIC ||
            invoke->dalvikInsn.opcode == Instruction::INVOKE_STATIC_RANGE);
  bool object_is_this = (data.method_is_static == 0u && data.object_arg == 0u);
  if (!object_is_this) {
    // TODO: Implement inlining of IGET on non-"this" registers (needs correct stack trace for NPE).
    // Allow synthetic accessors. We don't care about losing their stack frame in NPE.
    if (!InlineMethodAnalyser::IsSyntheticAccessor(
        mir_graph->GetMethodLoweringInfo(invoke).GetTargetMethod())) {
      return false;
    }
  }

  if (object_is_this) {
    // Mark invoke as NOP, null-check is done on IGET. No aborts after this.
    invoke->dalvikInsn.opcode = static_cast<Instruction::Code>(kMirOpNop);
  }

  MIR* insn = AllocReplacementMIR(mir_graph, invoke, move_result);
  insn->offset = invoke->offset;
  insn->dalvikInsn.opcode = opcode;
  insn->dalvikInsn.vA = move_result->dalvikInsn.vA;
  insn->dalvikInsn.vB = object_reg;
  mir_graph->ComputeInlineIFieldLoweringInfo(data.field_idx, invoke, insn);

  DCHECK(mir_graph->GetIFieldLoweringInfo(insn).IsResolved());
  DCHECK(mir_graph->GetIFieldLoweringInfo(insn).FastGet());
  DCHECK_EQ(data.field_offset, mir_graph->GetIFieldLoweringInfo(insn).FieldOffset().Uint32Value());
  DCHECK_EQ(data.is_volatile, mir_graph->GetIFieldLoweringInfo(insn).IsVolatile() ? 1u : 0u);

  bb->InsertMIRAfter(move_result, insn);
  return true;
}

bool DexFileMethodInliner::GenInlineIPut(MIRGraph* mir_graph, BasicBlock* bb, MIR* invoke,
                                         MIR* move_result, const InlineMethod& method,
                                         uint32_t method_idx) {
  CompilationUnit* cu = mir_graph->GetCurrentDexCompilationUnit()->GetCompilationUnit();
  if (cu->enable_debug & (1 << kDebugSlowFieldPath)) {
    return false;
  }

  const InlineIGetIPutData& data = method.d.ifield_data;
  Instruction::Code opcode = static_cast<Instruction::Code>(Instruction::IPUT + data.op_variant);
  DCHECK_EQ(InlineMethodAnalyser::IPutVariant(opcode), data.op_variant);
  uint32_t object_reg = GetInvokeReg(invoke, data.object_arg);
  uint32_t src_reg = GetInvokeReg(invoke, data.src_arg);
  uint32_t return_reg =
      data.return_arg_plus1 != 0u ? GetInvokeReg(invoke, data.return_arg_plus1 - 1u) : 0u;

  if (opcode == Instruction::IPUT_WIDE && !WideArgIsInConsecutiveDalvikRegs(invoke, data.src_arg)) {
    // The two halfs of the source value are not in consecutive dalvik registers in INVOKE.
    return false;
  }

  DCHECK(move_result == nullptr || data.return_arg_plus1 != 0u);
  if (move_result != nullptr && move_result->dalvikInsn.opcode == Instruction::MOVE_RESULT_WIDE &&
      !WideArgIsInConsecutiveDalvikRegs(invoke, data.return_arg_plus1 - 1u)) {
    // The two halfs of the return value are not in consecutive dalvik registers in INVOKE.
    return false;
  }

  DCHECK_EQ(data.method_is_static != 0u,
            invoke->dalvikInsn.opcode == Instruction::INVOKE_STATIC ||
            invoke->dalvikInsn.opcode == Instruction::INVOKE_STATIC_RANGE);
  bool object_is_this = (data.method_is_static == 0u && data.object_arg == 0u);
  if (!object_is_this) {
    // TODO: Implement inlining of IPUT on non-"this" registers (needs correct stack trace for NPE).
    // Allow synthetic accessors. We don't care about losing their stack frame in NPE.
    if (!InlineMethodAnalyser::IsSyntheticAccessor(
        mir_graph->GetMethodLoweringInfo(invoke).GetTargetMethod())) {
      return false;
    }
  }

  if (object_is_this) {
    // Mark invoke as NOP, null-check is done on IPUT. No aborts after this.
    invoke->dalvikInsn.opcode = static_cast<Instruction::Code>(kMirOpNop);
  }

  MIR* insn = AllocReplacementMIR(mir_graph, invoke, move_result);
  insn->dalvikInsn.opcode = opcode;
  insn->dalvikInsn.vA = src_reg;
  insn->dalvikInsn.vB = object_reg;
  mir_graph->ComputeInlineIFieldLoweringInfo(data.field_idx, invoke, insn);

  DCHECK(mir_graph->GetIFieldLoweringInfo(insn).IsResolved());
  DCHECK(mir_graph->GetIFieldLoweringInfo(insn).FastPut());
  DCHECK_EQ(data.field_offset, mir_graph->GetIFieldLoweringInfo(insn).FieldOffset().Uint32Value());
  DCHECK_EQ(data.is_volatile, mir_graph->GetIFieldLoweringInfo(insn).IsVolatile() ? 1u : 0u);

  bb->InsertMIRAfter(invoke, insn);

  if (move_result != nullptr) {
    MIR* move = AllocReplacementMIR(mir_graph, invoke, move_result);
    move->offset = move_result->offset;
    if (move_result->dalvikInsn.opcode == Instruction::MOVE_RESULT) {
      move->dalvikInsn.opcode = Instruction::MOVE_FROM16;
    } else if (move_result->dalvikInsn.opcode == Instruction::MOVE_RESULT_OBJECT) {
      move->dalvikInsn.opcode = Instruction::MOVE_OBJECT_FROM16;
    } else {
      DCHECK_EQ(move_result->dalvikInsn.opcode, Instruction::MOVE_RESULT_WIDE);
      move->dalvikInsn.opcode = Instruction::MOVE_WIDE_FROM16;
    }
    move->dalvikInsn.vA = move_result->dalvikInsn.vA;
    move->dalvikInsn.vB = return_reg;
    bb->InsertMIRAfter(insn, move);
  }
  return true;
}

}  // namespace art
