blob: 2523380bcfae13703a4ca73f12c3da333c7b66db [file] [log] [blame]
/*
* 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