From b95c74b9788886b24b04db782ce64867cb4960d5 Mon Sep 17 00:00:00 2001 From: Andreas Gampe Date: Thu, 20 Apr 2017 19:43:21 -0700 Subject: ART: Move InlineMethodAnalyzer into compiler Move the infrastructure, which is now only used by the compiler. Test: mmma art Change-Id: I4a61e35f23736b226523d7349f49208ad852ad2f --- compiler/Android.bp | 1 + compiler/dex/inline_method_analyser.cc | 758 ++++++++++++++++++++++++++++++++ compiler/dex/inline_method_analyser.h | 277 ++++++++++++ compiler/optimizing/inliner.cc | 2 +- runtime/Android.bp | 2 - runtime/quick/inline_method_analyser.cc | 758 -------------------------------- runtime/quick/inline_method_analyser.h | 277 ------------ 7 files changed, 1037 insertions(+), 1038 deletions(-) create mode 100644 compiler/dex/inline_method_analyser.cc create mode 100644 compiler/dex/inline_method_analyser.h delete mode 100644 runtime/quick/inline_method_analyser.cc delete mode 100644 runtime/quick/inline_method_analyser.h diff --git a/compiler/Android.bp b/compiler/Android.bp index 312fc7b35a..dec8b577d8 100644 --- a/compiler/Android.bp +++ b/compiler/Android.bp @@ -29,6 +29,7 @@ art_cc_defaults { "debug/elf_debug_writer.cc", "dex/dex_to_dex_compiler.cc", "dex/dex_to_dex_decompiler.cc", + "dex/inline_method_analyser.cc", "dex/verified_method.cc", "dex/verification_results.cc", "dex/quick_compiler_callbacks.cc", diff --git a/compiler/dex/inline_method_analyser.cc b/compiler/dex/inline_method_analyser.cc new file mode 100644 index 0000000000..3347070468 --- /dev/null +++ b/compiler/dex/inline_method_analyser.cc @@ -0,0 +1,758 @@ +/* + * Copyright (C) 2014 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 "inline_method_analyser.h" + +#include "art_field-inl.h" +#include "art_method-inl.h" +#include "base/enums.h" +#include "class_linker-inl.h" +#include "dex_file-inl.h" +#include "dex_instruction.h" +#include "dex_instruction-inl.h" +#include "dex_instruction_utils.h" +#include "mirror/class-inl.h" +#include "mirror/dex_cache-inl.h" +#include "verifier/method_verifier-inl.h" + +/* + * NOTE: This code is part of the quick compiler. It lives in the runtime + * only to allow the debugger to check whether a method has been inlined. + */ + +namespace art { + +namespace { // anonymous namespace + +// Helper class for matching a pattern. +class Matcher { + public: + // Match function type. + typedef bool MatchFn(Matcher* matcher); + + template + static bool Match(const DexFile::CodeItem* code_item, MatchFn* const (&pattern)[size]); + + // Match and advance. + + static bool Mark(Matcher* matcher); + + template + static bool Required(Matcher* matcher); + + template + static bool Repeated(Matcher* matcher); // On match, returns to the mark. + + // Match an individual instruction. + + template bool Opcode(); + bool Const0(); + bool IPutOnThis(); + + private: + explicit Matcher(const DexFile::CodeItem* code_item) + : code_item_(code_item), + instruction_(Instruction::At(code_item->insns_)), + pos_(0u), + mark_(0u) { } + + static bool DoMatch(const DexFile::CodeItem* code_item, MatchFn* const* pattern, size_t size); + + const DexFile::CodeItem* const code_item_; + const Instruction* instruction_; + size_t pos_; + size_t mark_; +}; + +template +bool Matcher::Match(const DexFile::CodeItem* code_item, MatchFn* const (&pattern)[size]) { + return DoMatch(code_item, pattern, size); +} + +bool Matcher::Mark(Matcher* matcher) { + matcher->pos_ += 1u; // Advance to the next match function before marking. + matcher->mark_ = matcher->pos_; + return true; +} + +template +bool Matcher::Required(Matcher* matcher) { + if (!(matcher->*Fn)()) { + return false; + } + matcher->pos_ += 1u; + matcher->instruction_ = matcher->instruction_->Next(); + return true; +} + +template +bool Matcher::Repeated(Matcher* matcher) { + if (!(matcher->*Fn)()) { + // Didn't match optional instruction, try the next match function. + matcher->pos_ += 1u; + return true; + } + matcher->pos_ = matcher->mark_; + matcher->instruction_ = matcher->instruction_->Next(); + return true; +} + +template +bool Matcher::Opcode() { + return instruction_->Opcode() == opcode; +} + +// Match const 0. +bool Matcher::Const0() { + return IsInstructionDirectConst(instruction_->Opcode()) && + (instruction_->Opcode() == Instruction::CONST_WIDE ? instruction_->VRegB_51l() == 0 + : instruction_->VRegB() == 0); +} + +bool Matcher::IPutOnThis() { + DCHECK_NE(code_item_->ins_size_, 0u); + return IsInstructionIPut(instruction_->Opcode()) && + instruction_->VRegB_22c() == code_item_->registers_size_ - code_item_->ins_size_; +} + +bool Matcher::DoMatch(const DexFile::CodeItem* code_item, MatchFn* const* pattern, size_t size) { + Matcher matcher(code_item); + while (matcher.pos_ != size) { + if (!pattern[matcher.pos_](&matcher)) { + return false; + } + } + return true; +} + +// Used for a single invoke in a constructor. In that situation, the method verifier makes +// sure we invoke a constructor either in the same class or superclass with at least "this". +ArtMethod* GetTargetConstructor(ArtMethod* method, const Instruction* invoke_direct) + REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK_EQ(invoke_direct->Opcode(), Instruction::INVOKE_DIRECT); + DCHECK_EQ(invoke_direct->VRegC_35c(), + method->GetCodeItem()->registers_size_ - method->GetCodeItem()->ins_size_); + uint32_t method_index = invoke_direct->VRegB_35c(); + PointerSize pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); + ArtMethod* target_method = + method->GetDexCache()->GetResolvedMethod(method_index, pointer_size); + if (kIsDebugBuild && target_method != nullptr) { + CHECK(!target_method->IsStatic()); + CHECK(target_method->IsConstructor()); + CHECK(target_method->GetDeclaringClass() == method->GetDeclaringClass() || + target_method->GetDeclaringClass() == method->GetDeclaringClass()->GetSuperClass()); + } + return target_method; +} + +// Return the forwarded arguments and check that all remaining arguments are zero. +// If the check fails, return static_cast(-1). +size_t CountForwardedConstructorArguments(const DexFile::CodeItem* code_item, + const Instruction* invoke_direct, + uint16_t zero_vreg_mask) { + DCHECK_EQ(invoke_direct->Opcode(), Instruction::INVOKE_DIRECT); + size_t number_of_args = invoke_direct->VRegA_35c(); + DCHECK_NE(number_of_args, 0u); + uint32_t args[Instruction::kMaxVarArgRegs]; + invoke_direct->GetVarArgs(args); + uint16_t this_vreg = args[0]; + DCHECK_EQ(this_vreg, code_item->registers_size_ - code_item->ins_size_); // Checked by verifier. + size_t forwarded = 1u; + while (forwarded < number_of_args && + args[forwarded] == this_vreg + forwarded && + (zero_vreg_mask & (1u << args[forwarded])) == 0) { + ++forwarded; + } + for (size_t i = forwarded; i != number_of_args; ++i) { + if ((zero_vreg_mask & (1u << args[i])) == 0) { + return static_cast(-1); + } + } + return forwarded; +} + +uint16_t GetZeroVRegMask(const Instruction* const0) { + DCHECK(IsInstructionDirectConst(const0->Opcode())); + DCHECK((const0->Opcode() == Instruction::CONST_WIDE) ? const0->VRegB_51l() == 0u + : const0->VRegB() == 0); + uint16_t base_mask = IsInstructionConstWide(const0->Opcode()) ? 3u : 1u; + return base_mask << const0->VRegA(); +} + +// We limit the number of IPUTs storing parameters. There can be any number +// of IPUTs that store the value 0 as they are useless in a constructor as +// the object always starts zero-initialized. We also eliminate all but the +// last store to any field as they are not observable; not even if the field +// is volatile as no reference to the object can escape from a constructor +// with this pattern. +static constexpr size_t kMaxConstructorIPuts = 3u; + +struct ConstructorIPutData { + ConstructorIPutData() : field_index(DexFile::kDexNoIndex16), arg(0u) { } + + uint16_t field_index; + uint16_t arg; +}; + +bool RecordConstructorIPut(ArtMethod* method, + const Instruction* new_iput, + uint16_t this_vreg, + uint16_t zero_vreg_mask, + /*inout*/ ConstructorIPutData (&iputs)[kMaxConstructorIPuts]) + REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(IsInstructionIPut(new_iput->Opcode())); + uint32_t field_index = new_iput->VRegC_22c(); + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + ArtField* field = class_linker->LookupResolvedField(field_index, method, /* is_static */ false); + if (UNLIKELY(field == nullptr)) { + return false; + } + // Remove previous IPUT to the same field, if any. Different field indexes may refer + // to the same field, so we need to compare resolved fields from the dex cache. + for (size_t old_pos = 0; old_pos != arraysize(iputs); ++old_pos) { + if (iputs[old_pos].field_index == DexFile::kDexNoIndex16) { + break; + } + ArtField* f = class_linker->LookupResolvedField(iputs[old_pos].field_index, + method, + /* is_static */ false); + DCHECK(f != nullptr); + if (f == field) { + auto back_it = std::copy(iputs + old_pos + 1, iputs + arraysize(iputs), iputs + old_pos); + *back_it = ConstructorIPutData(); + break; + } + } + // If the stored value isn't zero, record the IPUT. + if ((zero_vreg_mask & (1u << new_iput->VRegA_22c())) == 0u) { + size_t new_pos = 0; + while (new_pos != arraysize(iputs) && iputs[new_pos].field_index != DexFile::kDexNoIndex16) { + ++new_pos; + } + if (new_pos == arraysize(iputs)) { + return false; // Exceeded capacity of the output array. + } + iputs[new_pos].field_index = field_index; + iputs[new_pos].arg = new_iput->VRegA_22c() - this_vreg; + } + return true; +} + +bool DoAnalyseConstructor(const DexFile::CodeItem* code_item, + ArtMethod* method, + /*inout*/ ConstructorIPutData (&iputs)[kMaxConstructorIPuts]) + REQUIRES_SHARED(Locks::mutator_lock_) { + // On entry we should not have any IPUTs yet. + DCHECK_EQ(0, std::count_if( + iputs, + iputs + arraysize(iputs), + [](const ConstructorIPutData& iput_data) { + return iput_data.field_index != DexFile::kDexNoIndex16; + })); + + // Limit the maximum number of code units we're willing to match. + static constexpr size_t kMaxCodeUnits = 16u; + + // Limit the number of registers that the constructor may use to 16. + // Given that IPUTs must use low 16 registers and we do not match MOVEs, + // this is a reasonable limitation. + static constexpr size_t kMaxVRegs = 16u; + + // We try to match a constructor that calls another constructor (either in + // superclass or in the same class) with the same parameters, or with some + // parameters truncated (allowed only for calls to superclass constructor) + // or with extra parameters with value 0 (with any type, including null). + // This call can be followed by optional IPUTs on "this" storing either one + // of the parameters or 0 and the code must then finish with RETURN_VOID. + // The called constructor must be either java.lang.Object.() or it + // must also match the same pattern. + static Matcher::MatchFn* const kConstructorPattern[] = { + &Matcher::Mark, + &Matcher::Repeated<&Matcher::Const0>, + &Matcher::Required<&Matcher::Opcode>, + &Matcher::Mark, + &Matcher::Repeated<&Matcher::Const0>, + &Matcher::Repeated<&Matcher::IPutOnThis>, + &Matcher::Required<&Matcher::Opcode>, + }; + + DCHECK(method != nullptr); + DCHECK(!method->IsStatic()); + DCHECK(method->IsConstructor()); + DCHECK(code_item != nullptr); + if (!method->GetDeclaringClass()->IsVerified() || + code_item->insns_size_in_code_units_ > kMaxCodeUnits || + code_item->registers_size_ > kMaxVRegs || + !Matcher::Match(code_item, kConstructorPattern)) { + return false; + } + + // Verify the invoke, prevent a few odd cases and collect IPUTs. + uint16_t this_vreg = code_item->registers_size_ - code_item->ins_size_; + uint16_t zero_vreg_mask = 0u; + for (const Instruction* instruction = Instruction::At(code_item->insns_); + instruction->Opcode() != Instruction::RETURN_VOID; + instruction = instruction->Next()) { + if (instruction->Opcode() == Instruction::INVOKE_DIRECT) { + ArtMethod* target_method = GetTargetConstructor(method, instruction); + if (target_method == nullptr) { + return false; + } + // We allow forwarding constructors only if they pass more arguments + // to prevent infinite recursion. + if (target_method->GetDeclaringClass() == method->GetDeclaringClass() && + instruction->VRegA_35c() <= code_item->ins_size_) { + return false; + } + size_t forwarded = CountForwardedConstructorArguments(code_item, instruction, zero_vreg_mask); + if (forwarded == static_cast(-1)) { + return false; + } + if (target_method->GetDeclaringClass()->IsObjectClass()) { + DCHECK_EQ(Instruction::At(target_method->GetCodeItem()->insns_)->Opcode(), + Instruction::RETURN_VOID); + } else { + const DexFile::CodeItem* target_code_item = target_method->GetCodeItem(); + if (target_code_item == nullptr) { + return false; // Native constructor? + } + if (!DoAnalyseConstructor(target_code_item, target_method, iputs)) { + return false; + } + // Prune IPUTs with zero input. + auto kept_end = std::remove_if( + iputs, + iputs + arraysize(iputs), + [forwarded](const ConstructorIPutData& iput_data) { + return iput_data.arg >= forwarded; + }); + std::fill(kept_end, iputs + arraysize(iputs), ConstructorIPutData()); + // If we have any IPUTs from the call, check that the target method is in the same + // dex file (compare DexCache references), otherwise field_indexes would be bogus. + if (iputs[0].field_index != DexFile::kDexNoIndex16 && + target_method->GetDexCache() != method->GetDexCache()) { + return false; + } + } + } else if (IsInstructionDirectConst(instruction->Opcode())) { + zero_vreg_mask |= GetZeroVRegMask(instruction); + if ((zero_vreg_mask & (1u << this_vreg)) != 0u) { + return false; // Overwriting `this` is unsupported. + } + } else { + DCHECK(IsInstructionIPut(instruction->Opcode())); + DCHECK_EQ(instruction->VRegB_22c(), this_vreg); + if (!RecordConstructorIPut(method, instruction, this_vreg, zero_vreg_mask, iputs)) { + return false; + } + } + } + return true; +} + +} // anonymous namespace + +bool AnalyseConstructor(const DexFile::CodeItem* code_item, + ArtMethod* method, + InlineMethod* result) + REQUIRES_SHARED(Locks::mutator_lock_) { + ConstructorIPutData iputs[kMaxConstructorIPuts]; + if (!DoAnalyseConstructor(code_item, method, iputs)) { + return false; + } + static_assert(kMaxConstructorIPuts == 3, "Unexpected limit"); // Code below depends on this. + DCHECK(iputs[0].field_index != DexFile::kDexNoIndex16 || + iputs[1].field_index == DexFile::kDexNoIndex16); + DCHECK(iputs[1].field_index != DexFile::kDexNoIndex16 || + iputs[2].field_index == DexFile::kDexNoIndex16); + +#define STORE_IPUT(n) \ + do { \ + result->d.constructor_data.iput##n##_field_index = iputs[n].field_index; \ + result->d.constructor_data.iput##n##_arg = iputs[n].arg; \ + } while (false) + + STORE_IPUT(0); + STORE_IPUT(1); + STORE_IPUT(2); +#undef STORE_IPUT + + result->opcode = kInlineOpConstructor; + result->flags = kInlineSpecial; + result->d.constructor_data.reserved = 0u; + return true; +} + +static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET), "iget type"); +static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET_WIDE), "iget_wide type"); +static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET_OBJECT), + "iget_object type"); +static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET_BOOLEAN), + "iget_boolean type"); +static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET_BYTE), "iget_byte type"); +static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET_CHAR), "iget_char type"); +static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET_SHORT), "iget_short type"); +static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT), "iput type"); +static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT_WIDE), "iput_wide type"); +static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT_OBJECT), + "iput_object type"); +static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT_BOOLEAN), + "iput_boolean type"); +static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT_BYTE), "iput_byte type"); +static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT_CHAR), "iput_char type"); +static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT_SHORT), "iput_short type"); +static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET) == + InlineMethodAnalyser::IPutVariant(Instruction::IPUT), "iget/iput variant"); +static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_WIDE) == + InlineMethodAnalyser::IPutVariant(Instruction::IPUT_WIDE), "iget/iput_wide variant"); +static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_OBJECT) == + InlineMethodAnalyser::IPutVariant(Instruction::IPUT_OBJECT), "iget/iput_object variant"); +static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_BOOLEAN) == + InlineMethodAnalyser::IPutVariant(Instruction::IPUT_BOOLEAN), "iget/iput_boolean variant"); +static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_BYTE) == + InlineMethodAnalyser::IPutVariant(Instruction::IPUT_BYTE), "iget/iput_byte variant"); +static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_CHAR) == + InlineMethodAnalyser::IPutVariant(Instruction::IPUT_CHAR), "iget/iput_char variant"); +static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_SHORT) == + InlineMethodAnalyser::IPutVariant(Instruction::IPUT_SHORT), "iget/iput_short variant"); + +// This is used by compiler and debugger. We look into the dex cache for resolved methods and +// fields. However, in the context of the debugger, not all methods and fields are resolved. Since +// we need to be able to detect possibly inlined method, we pass a null inline method to indicate +// we don't want to take unresolved methods and fields into account during analysis. +bool InlineMethodAnalyser::AnalyseMethodCode(verifier::MethodVerifier* verifier, + InlineMethod* result) { + DCHECK(verifier != nullptr); + if (!Runtime::Current()->UseJitCompilation()) { + DCHECK_EQ(verifier->CanLoadClasses(), result != nullptr); + } + + // Note: verifier->GetMethod() may be null. + return AnalyseMethodCode(verifier->CodeItem(), + verifier->GetMethodReference(), + (verifier->GetAccessFlags() & kAccStatic) != 0u, + verifier->GetMethod(), + result); +} + +bool InlineMethodAnalyser::AnalyseMethodCode(ArtMethod* method, InlineMethod* result) { + const DexFile::CodeItem* code_item = method->GetCodeItem(); + if (code_item == nullptr) { + // Native or abstract. + return false; + } + return AnalyseMethodCode( + code_item, method->ToMethodReference(), method->IsStatic(), method, result); +} + +bool InlineMethodAnalyser::AnalyseMethodCode(const DexFile::CodeItem* code_item, + const MethodReference& method_ref, + bool is_static, + ArtMethod* method, + InlineMethod* result) { + // We currently support only plain return or 2-instruction methods. + + DCHECK_NE(code_item->insns_size_in_code_units_, 0u); + const Instruction* instruction = Instruction::At(code_item->insns_); + Instruction::Code opcode = instruction->Opcode(); + + switch (opcode) { + case Instruction::RETURN_VOID: + if (result != nullptr) { + result->opcode = kInlineOpNop; + result->flags = kInlineSpecial; + result->d.data = 0u; + } + return true; + case Instruction::RETURN: + case Instruction::RETURN_OBJECT: + case Instruction::RETURN_WIDE: + return AnalyseReturnMethod(code_item, result); + case Instruction::CONST: + case Instruction::CONST_4: + case Instruction::CONST_16: + case Instruction::CONST_HIGH16: + // TODO: Support wide constants (RETURN_WIDE). + if (AnalyseConstMethod(code_item, result)) { + return true; + } + FALLTHROUGH_INTENDED; + case Instruction::CONST_WIDE: + case Instruction::CONST_WIDE_16: + case Instruction::CONST_WIDE_32: + case Instruction::CONST_WIDE_HIGH16: + case Instruction::INVOKE_DIRECT: + if (method != nullptr && !method->IsStatic() && method->IsConstructor()) { + return AnalyseConstructor(code_item, method, result); + } + return false; + case Instruction::IGET: + case Instruction::IGET_OBJECT: + case Instruction::IGET_BOOLEAN: + case Instruction::IGET_BYTE: + case Instruction::IGET_CHAR: + case Instruction::IGET_SHORT: + case Instruction::IGET_WIDE: + // TODO: Add handling for JIT. + // case Instruction::IGET_QUICK: + // case Instruction::IGET_WIDE_QUICK: + // case Instruction::IGET_OBJECT_QUICK: + return AnalyseIGetMethod(code_item, method_ref, is_static, method, result); + case Instruction::IPUT: + case Instruction::IPUT_OBJECT: + case Instruction::IPUT_BOOLEAN: + case Instruction::IPUT_BYTE: + case Instruction::IPUT_CHAR: + case Instruction::IPUT_SHORT: + case Instruction::IPUT_WIDE: + // TODO: Add handling for JIT. + // case Instruction::IPUT_QUICK: + // case Instruction::IPUT_WIDE_QUICK: + // case Instruction::IPUT_OBJECT_QUICK: + return AnalyseIPutMethod(code_item, method_ref, is_static, method, result); + default: + return false; + } +} + +bool InlineMethodAnalyser::IsSyntheticAccessor(MethodReference ref) { + const DexFile::MethodId& method_id = ref.dex_file->GetMethodId(ref.dex_method_index); + const char* method_name = ref.dex_file->GetMethodName(method_id); + // javac names synthetic accessors "access$nnn", + // jack names them "-getN", "-putN", "-wrapN". + return strncmp(method_name, "access$", strlen("access$")) == 0 || + strncmp(method_name, "-", strlen("-")) == 0; +} + +bool InlineMethodAnalyser::AnalyseReturnMethod(const DexFile::CodeItem* code_item, + InlineMethod* result) { + const Instruction* return_instruction = Instruction::At(code_item->insns_); + Instruction::Code return_opcode = return_instruction->Opcode(); + uint32_t reg = return_instruction->VRegA_11x(); + uint32_t arg_start = code_item->registers_size_ - code_item->ins_size_; + DCHECK_GE(reg, arg_start); + DCHECK_LT((return_opcode == Instruction::RETURN_WIDE) ? reg + 1 : reg, + code_item->registers_size_); + + if (result != nullptr) { + result->opcode = kInlineOpReturnArg; + result->flags = kInlineSpecial; + InlineReturnArgData* data = &result->d.return_data; + data->arg = reg - arg_start; + data->is_wide = (return_opcode == Instruction::RETURN_WIDE) ? 1u : 0u; + data->is_object = (return_opcode == Instruction::RETURN_OBJECT) ? 1u : 0u; + data->reserved = 0u; + data->reserved2 = 0u; + } + return true; +} + +bool InlineMethodAnalyser::AnalyseConstMethod(const DexFile::CodeItem* code_item, + InlineMethod* result) { + const Instruction* instruction = Instruction::At(code_item->insns_); + const Instruction* return_instruction = instruction->Next(); + Instruction::Code return_opcode = return_instruction->Opcode(); + if (return_opcode != Instruction::RETURN && + return_opcode != Instruction::RETURN_OBJECT) { + return false; + } + + int32_t return_reg = return_instruction->VRegA_11x(); + DCHECK_LT(return_reg, code_item->registers_size_); + + int32_t const_value = instruction->VRegB(); + if (instruction->Opcode() == Instruction::CONST_HIGH16) { + const_value <<= 16; + } + DCHECK_LT(instruction->VRegA(), code_item->registers_size_); + if (instruction->VRegA() != return_reg) { + return false; // Not returning the value set by const? + } + if (return_opcode == Instruction::RETURN_OBJECT && const_value != 0) { + return false; // Returning non-null reference constant? + } + if (result != nullptr) { + result->opcode = kInlineOpNonWideConst; + result->flags = kInlineSpecial; + result->d.data = static_cast(const_value); + } + return true; +} + +bool InlineMethodAnalyser::AnalyseIGetMethod(const DexFile::CodeItem* code_item, + const MethodReference& method_ref, + bool is_static, + ArtMethod* method, + InlineMethod* result) { + const Instruction* instruction = Instruction::At(code_item->insns_); + Instruction::Code opcode = instruction->Opcode(); + DCHECK(IsInstructionIGet(opcode)); + + const Instruction* return_instruction = instruction->Next(); + Instruction::Code return_opcode = return_instruction->Opcode(); + if (!(return_opcode == Instruction::RETURN_WIDE && opcode == Instruction::IGET_WIDE) && + !(return_opcode == Instruction::RETURN_OBJECT && opcode == Instruction::IGET_OBJECT) && + !(return_opcode == Instruction::RETURN && opcode != Instruction::IGET_WIDE && + opcode != Instruction::IGET_OBJECT)) { + return false; + } + + uint32_t return_reg = return_instruction->VRegA_11x(); + DCHECK_LT(return_opcode == Instruction::RETURN_WIDE ? return_reg + 1 : return_reg, + code_item->registers_size_); + + uint32_t dst_reg = instruction->VRegA_22c(); + uint32_t object_reg = instruction->VRegB_22c(); + uint32_t field_idx = instruction->VRegC_22c(); + uint32_t arg_start = code_item->registers_size_ - code_item->ins_size_; + DCHECK_GE(object_reg, arg_start); + DCHECK_LT(object_reg, code_item->registers_size_); + uint32_t object_arg = object_reg - arg_start; + + DCHECK_LT(opcode == Instruction::IGET_WIDE ? dst_reg + 1 : dst_reg, code_item->registers_size_); + if (dst_reg != return_reg) { + return false; // Not returning the value retrieved by IGET? + } + + if (is_static || object_arg != 0u) { + // 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 (!IsSyntheticAccessor(method_ref)) { + return false; + } + } + + // InlineIGetIPutData::object_arg is only 4 bits wide. + static constexpr uint16_t kMaxObjectArg = 15u; + if (object_arg > kMaxObjectArg) { + return false; + } + + if (result != nullptr) { + InlineIGetIPutData* data = &result->d.ifield_data; + if (!ComputeSpecialAccessorInfo(method, field_idx, false, data)) { + return false; + } + result->opcode = kInlineOpIGet; + result->flags = kInlineSpecial; + data->op_variant = IGetVariant(opcode); + data->method_is_static = is_static ? 1u : 0u; + data->object_arg = object_arg; // Allow IGET on any register, not just "this". + data->src_arg = 0u; + data->return_arg_plus1 = 0u; + } + return true; +} + +bool InlineMethodAnalyser::AnalyseIPutMethod(const DexFile::CodeItem* code_item, + const MethodReference& method_ref, + bool is_static, + ArtMethod* method, + InlineMethod* result) { + const Instruction* instruction = Instruction::At(code_item->insns_); + Instruction::Code opcode = instruction->Opcode(); + DCHECK(IsInstructionIPut(opcode)); + + const Instruction* return_instruction = instruction->Next(); + Instruction::Code return_opcode = return_instruction->Opcode(); + uint32_t arg_start = code_item->registers_size_ - code_item->ins_size_; + uint16_t return_arg_plus1 = 0u; + if (return_opcode != Instruction::RETURN_VOID) { + if (return_opcode != Instruction::RETURN && + return_opcode != Instruction::RETURN_OBJECT && + return_opcode != Instruction::RETURN_WIDE) { + return false; + } + // Returning an argument. + uint32_t return_reg = return_instruction->VRegA_11x(); + DCHECK_GE(return_reg, arg_start); + DCHECK_LT(return_opcode == Instruction::RETURN_WIDE ? return_reg + 1u : return_reg, + code_item->registers_size_); + return_arg_plus1 = return_reg - arg_start + 1u; + } + + uint32_t src_reg = instruction->VRegA_22c(); + uint32_t object_reg = instruction->VRegB_22c(); + uint32_t field_idx = instruction->VRegC_22c(); + DCHECK_GE(object_reg, arg_start); + DCHECK_LT(object_reg, code_item->registers_size_); + DCHECK_GE(src_reg, arg_start); + DCHECK_LT(opcode == Instruction::IPUT_WIDE ? src_reg + 1 : src_reg, code_item->registers_size_); + uint32_t object_arg = object_reg - arg_start; + uint32_t src_arg = src_reg - arg_start; + + if (is_static || object_arg != 0u) { + // 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 (!IsSyntheticAccessor(method_ref)) { + return false; + } + } + + // InlineIGetIPutData::object_arg/src_arg/return_arg_plus1 are each only 4 bits wide. + static constexpr uint16_t kMaxObjectArg = 15u; + static constexpr uint16_t kMaxSrcArg = 15u; + static constexpr uint16_t kMaxReturnArgPlus1 = 15u; + if (object_arg > kMaxObjectArg || src_arg > kMaxSrcArg || return_arg_plus1 > kMaxReturnArgPlus1) { + return false; + } + + if (result != nullptr) { + InlineIGetIPutData* data = &result->d.ifield_data; + if (!ComputeSpecialAccessorInfo(method, field_idx, true, data)) { + return false; + } + result->opcode = kInlineOpIPut; + result->flags = kInlineSpecial; + data->op_variant = IPutVariant(opcode); + data->method_is_static = is_static ? 1u : 0u; + data->object_arg = object_arg; // Allow IPUT on any register, not just "this". + data->src_arg = src_arg; + data->return_arg_plus1 = return_arg_plus1; + } + return true; +} + +bool InlineMethodAnalyser::ComputeSpecialAccessorInfo(ArtMethod* method, + uint32_t field_idx, + bool is_put, + InlineIGetIPutData* result) { + if (method == nullptr) { + return false; + } + ObjPtr dex_cache = method->GetDexCache(); + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + ArtField* field = class_linker->LookupResolvedField(field_idx, method, /* is_static */ false); + if (field == nullptr || field->IsStatic()) { + return false; + } + ObjPtr method_class = method->GetDeclaringClass(); + ObjPtr field_class = field->GetDeclaringClass(); + if (!method_class->CanAccessResolvedField(field_class, field, dex_cache, field_idx) || + (is_put && field->IsFinal() && method_class != field_class)) { + return false; + } + DCHECK_GE(field->GetOffset().Int32Value(), 0); + // Do not interleave function calls with bit field writes to placate valgrind. Bug: 27552451. + uint32_t field_offset = field->GetOffset().Uint32Value(); + bool is_volatile = field->IsVolatile(); + result->field_idx = field_idx; + result->field_offset = field_offset; + result->is_volatile = is_volatile ? 1u : 0u; + return true; +} + +} // namespace art diff --git a/compiler/dex/inline_method_analyser.h b/compiler/dex/inline_method_analyser.h new file mode 100644 index 0000000000..ab643abf21 --- /dev/null +++ b/compiler/dex/inline_method_analyser.h @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef ART_COMPILER_DEX_INLINE_METHOD_ANALYSER_H_ +#define ART_COMPILER_DEX_INLINE_METHOD_ANALYSER_H_ + +#include "base/macros.h" +#include "base/mutex.h" +#include "dex_file.h" +#include "dex_instruction.h" +#include "method_reference.h" + +/* + * NOTE: This code is part of the quick compiler. It lives in the runtime + * only to allow the debugger to check whether a method has been inlined. + */ + +namespace art { + +namespace verifier { +class MethodVerifier; +} // namespace verifier +class ArtMethod; + +enum InlineMethodOpcode : uint16_t { + kIntrinsicDoubleCvt, + kIntrinsicFloatCvt, + kIntrinsicFloat2Int, + kIntrinsicDouble2Long, + kIntrinsicFloatIsInfinite, + kIntrinsicDoubleIsInfinite, + kIntrinsicFloatIsNaN, + kIntrinsicDoubleIsNaN, + kIntrinsicReverseBits, + kIntrinsicReverseBytes, + kIntrinsicBitCount, + kIntrinsicCompare, + kIntrinsicHighestOneBit, + kIntrinsicLowestOneBit, + kIntrinsicNumberOfLeadingZeros, + kIntrinsicNumberOfTrailingZeros, + kIntrinsicRotateRight, + kIntrinsicRotateLeft, + kIntrinsicSignum, + kIntrinsicAbsInt, + kIntrinsicAbsLong, + kIntrinsicAbsFloat, + kIntrinsicAbsDouble, + kIntrinsicMinMaxInt, + kIntrinsicMinMaxLong, + kIntrinsicMinMaxFloat, + kIntrinsicMinMaxDouble, + kIntrinsicCos, + kIntrinsicSin, + kIntrinsicAcos, + kIntrinsicAsin, + kIntrinsicAtan, + kIntrinsicAtan2, + kIntrinsicCbrt, + kIntrinsicCosh, + kIntrinsicExp, + kIntrinsicExpm1, + kIntrinsicHypot, + kIntrinsicLog, + kIntrinsicLog10, + kIntrinsicNextAfter, + kIntrinsicSinh, + kIntrinsicTan, + kIntrinsicTanh, + kIntrinsicSqrt, + kIntrinsicCeil, + kIntrinsicFloor, + kIntrinsicRint, + kIntrinsicRoundFloat, + kIntrinsicRoundDouble, + kIntrinsicReferenceGetReferent, + kIntrinsicCharAt, + kIntrinsicCompareTo, + kIntrinsicEquals, + kIntrinsicGetCharsNoCheck, + kIntrinsicIsEmptyOrLength, + kIntrinsicIndexOf, + kIntrinsicNewStringFromBytes, + kIntrinsicNewStringFromChars, + kIntrinsicNewStringFromString, + kIntrinsicCurrentThread, + kIntrinsicPeek, + kIntrinsicPoke, + kIntrinsicCas, + kIntrinsicUnsafeGet, + kIntrinsicUnsafePut, + + // 1.8. + kIntrinsicUnsafeGetAndAddInt, + kIntrinsicUnsafeGetAndAddLong, + kIntrinsicUnsafeGetAndSetInt, + kIntrinsicUnsafeGetAndSetLong, + kIntrinsicUnsafeGetAndSetObject, + kIntrinsicUnsafeLoadFence, + kIntrinsicUnsafeStoreFence, + kIntrinsicUnsafeFullFence, + + kIntrinsicSystemArrayCopyCharArray, + kIntrinsicSystemArrayCopy, + + kInlineOpNop, + kInlineOpReturnArg, + kInlineOpNonWideConst, + kInlineOpIGet, + kInlineOpIPut, + kInlineOpConstructor, + kInlineStringInit, +}; +std::ostream& operator<<(std::ostream& os, const InlineMethodOpcode& rhs); + +enum InlineMethodFlags : uint16_t { + kNoInlineMethodFlags = 0x0000, + kInlineIntrinsic = 0x0001, + kInlineSpecial = 0x0002, +}; + +// IntrinsicFlags are stored in InlineMethod::d::raw_data +enum IntrinsicFlags { + kIntrinsicFlagNone = 0, + + // kIntrinsicMinMaxInt + kIntrinsicFlagMax = kIntrinsicFlagNone, + kIntrinsicFlagMin = 1, + + // kIntrinsicIsEmptyOrLength + kIntrinsicFlagLength = kIntrinsicFlagNone, + kIntrinsicFlagIsEmpty = kIntrinsicFlagMin, + + // kIntrinsicIndexOf + kIntrinsicFlagBase0 = kIntrinsicFlagMin, + + // kIntrinsicUnsafeGet, kIntrinsicUnsafePut, kIntrinsicUnsafeCas + kIntrinsicFlagIsLong = kIntrinsicFlagMin, + // kIntrinsicUnsafeGet, kIntrinsicUnsafePut + kIntrinsicFlagIsVolatile = 2, + // kIntrinsicUnsafePut, kIntrinsicUnsafeCas + kIntrinsicFlagIsObject = 4, + // kIntrinsicUnsafePut + kIntrinsicFlagIsOrdered = 8, + + // kIntrinsicDoubleCvt, kIntrinsicFloatCvt. + kIntrinsicFlagToFloatingPoint = kIntrinsicFlagMin, +}; + +struct InlineIGetIPutData { + // The op_variant below is DexMemAccessType but the runtime doesn't know that enumeration. + uint16_t op_variant : 3; + uint16_t method_is_static : 1; + uint16_t object_arg : 4; + uint16_t src_arg : 4; // iput only + uint16_t return_arg_plus1 : 4; // iput only, method argument to return + 1, 0 = return void. + uint16_t field_idx; + uint32_t is_volatile : 1; + uint32_t field_offset : 31; +}; +static_assert(sizeof(InlineIGetIPutData) == sizeof(uint64_t), "Invalid size of InlineIGetIPutData"); + +struct InlineReturnArgData { + uint16_t arg; + uint16_t is_wide : 1; + uint16_t is_object : 1; + uint16_t reserved : 14; + uint32_t reserved2; +}; +static_assert(sizeof(InlineReturnArgData) == sizeof(uint64_t), + "Invalid size of InlineReturnArgData"); + +struct InlineConstructorData { + // There can be up to 3 IPUTs, unused fields are marked with kNoDexIndex16. + uint16_t iput0_field_index; + uint16_t iput1_field_index; + uint16_t iput2_field_index; + uint16_t iput0_arg : 4; + uint16_t iput1_arg : 4; + uint16_t iput2_arg : 4; + uint16_t reserved : 4; +}; +static_assert(sizeof(InlineConstructorData) == sizeof(uint64_t), + "Invalid size of InlineConstructorData"); + +struct InlineMethod { + InlineMethodOpcode opcode; + InlineMethodFlags flags; + union { + uint64_t data; + InlineIGetIPutData ifield_data; + InlineReturnArgData return_data; + InlineConstructorData constructor_data; + } d; +}; + +class InlineMethodAnalyser { + public: + /** + * Analyse method code to determine if the method is a candidate for inlining. + * If it is, record the inlining data. + * + * @param verifier the method verifier holding data about the method to analyse. + * @param method placeholder for the inline method data. + * @return true if the method is a candidate for inlining, false otherwise. + */ + static bool AnalyseMethodCode(verifier::MethodVerifier* verifier, InlineMethod* result) + REQUIRES_SHARED(Locks::mutator_lock_); + static bool AnalyseMethodCode(ArtMethod* method, InlineMethod* result) + REQUIRES_SHARED(Locks::mutator_lock_); + + static constexpr bool IsInstructionIGet(Instruction::Code opcode) { + return Instruction::IGET <= opcode && opcode <= Instruction::IGET_SHORT; + } + + static constexpr bool IsInstructionIPut(Instruction::Code opcode) { + return Instruction::IPUT <= opcode && opcode <= Instruction::IPUT_SHORT; + } + + static constexpr uint16_t IGetVariant(Instruction::Code opcode) { + return opcode - Instruction::IGET; + } + + static constexpr uint16_t IPutVariant(Instruction::Code opcode) { + return opcode - Instruction::IPUT; + } + + // Determines whether the method is a synthetic accessor (method name starts with "access$"). + static bool IsSyntheticAccessor(MethodReference ref); + + private: + static bool AnalyseMethodCode(const DexFile::CodeItem* code_item, + const MethodReference& method_ref, + bool is_static, + ArtMethod* method, + InlineMethod* result) + REQUIRES_SHARED(Locks::mutator_lock_); + static bool AnalyseReturnMethod(const DexFile::CodeItem* code_item, InlineMethod* result); + static bool AnalyseConstMethod(const DexFile::CodeItem* code_item, InlineMethod* result); + static bool AnalyseIGetMethod(const DexFile::CodeItem* code_item, + const MethodReference& method_ref, + bool is_static, + ArtMethod* method, + InlineMethod* result) + REQUIRES_SHARED(Locks::mutator_lock_); + static bool AnalyseIPutMethod(const DexFile::CodeItem* code_item, + const MethodReference& method_ref, + bool is_static, + ArtMethod* method, + InlineMethod* result) + REQUIRES_SHARED(Locks::mutator_lock_); + + // Can we fast path instance field access in a verified accessor? + // If yes, computes field's offset and volatility and whether the method is static or not. + static bool ComputeSpecialAccessorInfo(ArtMethod* method, + uint32_t field_idx, + bool is_put, + InlineIGetIPutData* result) + REQUIRES_SHARED(Locks::mutator_lock_); +}; + +} // namespace art + +#endif // ART_COMPILER_DEX_INLINE_METHOD_ANALYSER_H_ diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index 4af2539812..1f8a58cdaa 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -22,6 +22,7 @@ #include "class_linker.h" #include "constant_folding.h" #include "dead_code_elimination.h" +#include "dex/inline_method_analyser.h" #include "dex/verified_method.h" #include "dex/verification_results.h" #include "driver/compiler_driver-inl.h" @@ -37,7 +38,6 @@ #include "optimizing_compiler.h" #include "reference_type_propagation.h" #include "register_allocator_linear_scan.h" -#include "quick/inline_method_analyser.h" #include "sharpening.h" #include "ssa_builder.h" #include "ssa_phi_elimination.h" diff --git a/runtime/Android.bp b/runtime/Android.bp index 8972e91321..2866d4b4a0 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -186,7 +186,6 @@ cc_defaults { "plugin.cc", "primitive.cc", "quick_exception_handler.cc", - "quick/inline_method_analyser.cc", "reference_table.cc", "reflection.cc", "runtime.cc", @@ -451,7 +450,6 @@ gensrcs { "oat.h", "object_callbacks.h", "process_state.h", - "quick/inline_method_analyser.h", "runtime.h", "stack.h", "thread.h", diff --git a/runtime/quick/inline_method_analyser.cc b/runtime/quick/inline_method_analyser.cc deleted file mode 100644 index 3347070468..0000000000 --- a/runtime/quick/inline_method_analyser.cc +++ /dev/null @@ -1,758 +0,0 @@ -/* - * Copyright (C) 2014 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 "inline_method_analyser.h" - -#include "art_field-inl.h" -#include "art_method-inl.h" -#include "base/enums.h" -#include "class_linker-inl.h" -#include "dex_file-inl.h" -#include "dex_instruction.h" -#include "dex_instruction-inl.h" -#include "dex_instruction_utils.h" -#include "mirror/class-inl.h" -#include "mirror/dex_cache-inl.h" -#include "verifier/method_verifier-inl.h" - -/* - * NOTE: This code is part of the quick compiler. It lives in the runtime - * only to allow the debugger to check whether a method has been inlined. - */ - -namespace art { - -namespace { // anonymous namespace - -// Helper class for matching a pattern. -class Matcher { - public: - // Match function type. - typedef bool MatchFn(Matcher* matcher); - - template - static bool Match(const DexFile::CodeItem* code_item, MatchFn* const (&pattern)[size]); - - // Match and advance. - - static bool Mark(Matcher* matcher); - - template - static bool Required(Matcher* matcher); - - template - static bool Repeated(Matcher* matcher); // On match, returns to the mark. - - // Match an individual instruction. - - template bool Opcode(); - bool Const0(); - bool IPutOnThis(); - - private: - explicit Matcher(const DexFile::CodeItem* code_item) - : code_item_(code_item), - instruction_(Instruction::At(code_item->insns_)), - pos_(0u), - mark_(0u) { } - - static bool DoMatch(const DexFile::CodeItem* code_item, MatchFn* const* pattern, size_t size); - - const DexFile::CodeItem* const code_item_; - const Instruction* instruction_; - size_t pos_; - size_t mark_; -}; - -template -bool Matcher::Match(const DexFile::CodeItem* code_item, MatchFn* const (&pattern)[size]) { - return DoMatch(code_item, pattern, size); -} - -bool Matcher::Mark(Matcher* matcher) { - matcher->pos_ += 1u; // Advance to the next match function before marking. - matcher->mark_ = matcher->pos_; - return true; -} - -template -bool Matcher::Required(Matcher* matcher) { - if (!(matcher->*Fn)()) { - return false; - } - matcher->pos_ += 1u; - matcher->instruction_ = matcher->instruction_->Next(); - return true; -} - -template -bool Matcher::Repeated(Matcher* matcher) { - if (!(matcher->*Fn)()) { - // Didn't match optional instruction, try the next match function. - matcher->pos_ += 1u; - return true; - } - matcher->pos_ = matcher->mark_; - matcher->instruction_ = matcher->instruction_->Next(); - return true; -} - -template -bool Matcher::Opcode() { - return instruction_->Opcode() == opcode; -} - -// Match const 0. -bool Matcher::Const0() { - return IsInstructionDirectConst(instruction_->Opcode()) && - (instruction_->Opcode() == Instruction::CONST_WIDE ? instruction_->VRegB_51l() == 0 - : instruction_->VRegB() == 0); -} - -bool Matcher::IPutOnThis() { - DCHECK_NE(code_item_->ins_size_, 0u); - return IsInstructionIPut(instruction_->Opcode()) && - instruction_->VRegB_22c() == code_item_->registers_size_ - code_item_->ins_size_; -} - -bool Matcher::DoMatch(const DexFile::CodeItem* code_item, MatchFn* const* pattern, size_t size) { - Matcher matcher(code_item); - while (matcher.pos_ != size) { - if (!pattern[matcher.pos_](&matcher)) { - return false; - } - } - return true; -} - -// Used for a single invoke in a constructor. In that situation, the method verifier makes -// sure we invoke a constructor either in the same class or superclass with at least "this". -ArtMethod* GetTargetConstructor(ArtMethod* method, const Instruction* invoke_direct) - REQUIRES_SHARED(Locks::mutator_lock_) { - DCHECK_EQ(invoke_direct->Opcode(), Instruction::INVOKE_DIRECT); - DCHECK_EQ(invoke_direct->VRegC_35c(), - method->GetCodeItem()->registers_size_ - method->GetCodeItem()->ins_size_); - uint32_t method_index = invoke_direct->VRegB_35c(); - PointerSize pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); - ArtMethod* target_method = - method->GetDexCache()->GetResolvedMethod(method_index, pointer_size); - if (kIsDebugBuild && target_method != nullptr) { - CHECK(!target_method->IsStatic()); - CHECK(target_method->IsConstructor()); - CHECK(target_method->GetDeclaringClass() == method->GetDeclaringClass() || - target_method->GetDeclaringClass() == method->GetDeclaringClass()->GetSuperClass()); - } - return target_method; -} - -// Return the forwarded arguments and check that all remaining arguments are zero. -// If the check fails, return static_cast(-1). -size_t CountForwardedConstructorArguments(const DexFile::CodeItem* code_item, - const Instruction* invoke_direct, - uint16_t zero_vreg_mask) { - DCHECK_EQ(invoke_direct->Opcode(), Instruction::INVOKE_DIRECT); - size_t number_of_args = invoke_direct->VRegA_35c(); - DCHECK_NE(number_of_args, 0u); - uint32_t args[Instruction::kMaxVarArgRegs]; - invoke_direct->GetVarArgs(args); - uint16_t this_vreg = args[0]; - DCHECK_EQ(this_vreg, code_item->registers_size_ - code_item->ins_size_); // Checked by verifier. - size_t forwarded = 1u; - while (forwarded < number_of_args && - args[forwarded] == this_vreg + forwarded && - (zero_vreg_mask & (1u << args[forwarded])) == 0) { - ++forwarded; - } - for (size_t i = forwarded; i != number_of_args; ++i) { - if ((zero_vreg_mask & (1u << args[i])) == 0) { - return static_cast(-1); - } - } - return forwarded; -} - -uint16_t GetZeroVRegMask(const Instruction* const0) { - DCHECK(IsInstructionDirectConst(const0->Opcode())); - DCHECK((const0->Opcode() == Instruction::CONST_WIDE) ? const0->VRegB_51l() == 0u - : const0->VRegB() == 0); - uint16_t base_mask = IsInstructionConstWide(const0->Opcode()) ? 3u : 1u; - return base_mask << const0->VRegA(); -} - -// We limit the number of IPUTs storing parameters. There can be any number -// of IPUTs that store the value 0 as they are useless in a constructor as -// the object always starts zero-initialized. We also eliminate all but the -// last store to any field as they are not observable; not even if the field -// is volatile as no reference to the object can escape from a constructor -// with this pattern. -static constexpr size_t kMaxConstructorIPuts = 3u; - -struct ConstructorIPutData { - ConstructorIPutData() : field_index(DexFile::kDexNoIndex16), arg(0u) { } - - uint16_t field_index; - uint16_t arg; -}; - -bool RecordConstructorIPut(ArtMethod* method, - const Instruction* new_iput, - uint16_t this_vreg, - uint16_t zero_vreg_mask, - /*inout*/ ConstructorIPutData (&iputs)[kMaxConstructorIPuts]) - REQUIRES_SHARED(Locks::mutator_lock_) { - DCHECK(IsInstructionIPut(new_iput->Opcode())); - uint32_t field_index = new_iput->VRegC_22c(); - ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); - ArtField* field = class_linker->LookupResolvedField(field_index, method, /* is_static */ false); - if (UNLIKELY(field == nullptr)) { - return false; - } - // Remove previous IPUT to the same field, if any. Different field indexes may refer - // to the same field, so we need to compare resolved fields from the dex cache. - for (size_t old_pos = 0; old_pos != arraysize(iputs); ++old_pos) { - if (iputs[old_pos].field_index == DexFile::kDexNoIndex16) { - break; - } - ArtField* f = class_linker->LookupResolvedField(iputs[old_pos].field_index, - method, - /* is_static */ false); - DCHECK(f != nullptr); - if (f == field) { - auto back_it = std::copy(iputs + old_pos + 1, iputs + arraysize(iputs), iputs + old_pos); - *back_it = ConstructorIPutData(); - break; - } - } - // If the stored value isn't zero, record the IPUT. - if ((zero_vreg_mask & (1u << new_iput->VRegA_22c())) == 0u) { - size_t new_pos = 0; - while (new_pos != arraysize(iputs) && iputs[new_pos].field_index != DexFile::kDexNoIndex16) { - ++new_pos; - } - if (new_pos == arraysize(iputs)) { - return false; // Exceeded capacity of the output array. - } - iputs[new_pos].field_index = field_index; - iputs[new_pos].arg = new_iput->VRegA_22c() - this_vreg; - } - return true; -} - -bool DoAnalyseConstructor(const DexFile::CodeItem* code_item, - ArtMethod* method, - /*inout*/ ConstructorIPutData (&iputs)[kMaxConstructorIPuts]) - REQUIRES_SHARED(Locks::mutator_lock_) { - // On entry we should not have any IPUTs yet. - DCHECK_EQ(0, std::count_if( - iputs, - iputs + arraysize(iputs), - [](const ConstructorIPutData& iput_data) { - return iput_data.field_index != DexFile::kDexNoIndex16; - })); - - // Limit the maximum number of code units we're willing to match. - static constexpr size_t kMaxCodeUnits = 16u; - - // Limit the number of registers that the constructor may use to 16. - // Given that IPUTs must use low 16 registers and we do not match MOVEs, - // this is a reasonable limitation. - static constexpr size_t kMaxVRegs = 16u; - - // We try to match a constructor that calls another constructor (either in - // superclass or in the same class) with the same parameters, or with some - // parameters truncated (allowed only for calls to superclass constructor) - // or with extra parameters with value 0 (with any type, including null). - // This call can be followed by optional IPUTs on "this" storing either one - // of the parameters or 0 and the code must then finish with RETURN_VOID. - // The called constructor must be either java.lang.Object.() or it - // must also match the same pattern. - static Matcher::MatchFn* const kConstructorPattern[] = { - &Matcher::Mark, - &Matcher::Repeated<&Matcher::Const0>, - &Matcher::Required<&Matcher::Opcode>, - &Matcher::Mark, - &Matcher::Repeated<&Matcher::Const0>, - &Matcher::Repeated<&Matcher::IPutOnThis>, - &Matcher::Required<&Matcher::Opcode>, - }; - - DCHECK(method != nullptr); - DCHECK(!method->IsStatic()); - DCHECK(method->IsConstructor()); - DCHECK(code_item != nullptr); - if (!method->GetDeclaringClass()->IsVerified() || - code_item->insns_size_in_code_units_ > kMaxCodeUnits || - code_item->registers_size_ > kMaxVRegs || - !Matcher::Match(code_item, kConstructorPattern)) { - return false; - } - - // Verify the invoke, prevent a few odd cases and collect IPUTs. - uint16_t this_vreg = code_item->registers_size_ - code_item->ins_size_; - uint16_t zero_vreg_mask = 0u; - for (const Instruction* instruction = Instruction::At(code_item->insns_); - instruction->Opcode() != Instruction::RETURN_VOID; - instruction = instruction->Next()) { - if (instruction->Opcode() == Instruction::INVOKE_DIRECT) { - ArtMethod* target_method = GetTargetConstructor(method, instruction); - if (target_method == nullptr) { - return false; - } - // We allow forwarding constructors only if they pass more arguments - // to prevent infinite recursion. - if (target_method->GetDeclaringClass() == method->GetDeclaringClass() && - instruction->VRegA_35c() <= code_item->ins_size_) { - return false; - } - size_t forwarded = CountForwardedConstructorArguments(code_item, instruction, zero_vreg_mask); - if (forwarded == static_cast(-1)) { - return false; - } - if (target_method->GetDeclaringClass()->IsObjectClass()) { - DCHECK_EQ(Instruction::At(target_method->GetCodeItem()->insns_)->Opcode(), - Instruction::RETURN_VOID); - } else { - const DexFile::CodeItem* target_code_item = target_method->GetCodeItem(); - if (target_code_item == nullptr) { - return false; // Native constructor? - } - if (!DoAnalyseConstructor(target_code_item, target_method, iputs)) { - return false; - } - // Prune IPUTs with zero input. - auto kept_end = std::remove_if( - iputs, - iputs + arraysize(iputs), - [forwarded](const ConstructorIPutData& iput_data) { - return iput_data.arg >= forwarded; - }); - std::fill(kept_end, iputs + arraysize(iputs), ConstructorIPutData()); - // If we have any IPUTs from the call, check that the target method is in the same - // dex file (compare DexCache references), otherwise field_indexes would be bogus. - if (iputs[0].field_index != DexFile::kDexNoIndex16 && - target_method->GetDexCache() != method->GetDexCache()) { - return false; - } - } - } else if (IsInstructionDirectConst(instruction->Opcode())) { - zero_vreg_mask |= GetZeroVRegMask(instruction); - if ((zero_vreg_mask & (1u << this_vreg)) != 0u) { - return false; // Overwriting `this` is unsupported. - } - } else { - DCHECK(IsInstructionIPut(instruction->Opcode())); - DCHECK_EQ(instruction->VRegB_22c(), this_vreg); - if (!RecordConstructorIPut(method, instruction, this_vreg, zero_vreg_mask, iputs)) { - return false; - } - } - } - return true; -} - -} // anonymous namespace - -bool AnalyseConstructor(const DexFile::CodeItem* code_item, - ArtMethod* method, - InlineMethod* result) - REQUIRES_SHARED(Locks::mutator_lock_) { - ConstructorIPutData iputs[kMaxConstructorIPuts]; - if (!DoAnalyseConstructor(code_item, method, iputs)) { - return false; - } - static_assert(kMaxConstructorIPuts == 3, "Unexpected limit"); // Code below depends on this. - DCHECK(iputs[0].field_index != DexFile::kDexNoIndex16 || - iputs[1].field_index == DexFile::kDexNoIndex16); - DCHECK(iputs[1].field_index != DexFile::kDexNoIndex16 || - iputs[2].field_index == DexFile::kDexNoIndex16); - -#define STORE_IPUT(n) \ - do { \ - result->d.constructor_data.iput##n##_field_index = iputs[n].field_index; \ - result->d.constructor_data.iput##n##_arg = iputs[n].arg; \ - } while (false) - - STORE_IPUT(0); - STORE_IPUT(1); - STORE_IPUT(2); -#undef STORE_IPUT - - result->opcode = kInlineOpConstructor; - result->flags = kInlineSpecial; - result->d.constructor_data.reserved = 0u; - return true; -} - -static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET), "iget type"); -static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET_WIDE), "iget_wide type"); -static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET_OBJECT), - "iget_object type"); -static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET_BOOLEAN), - "iget_boolean type"); -static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET_BYTE), "iget_byte type"); -static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET_CHAR), "iget_char type"); -static_assert(InlineMethodAnalyser::IsInstructionIGet(Instruction::IGET_SHORT), "iget_short type"); -static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT), "iput type"); -static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT_WIDE), "iput_wide type"); -static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT_OBJECT), - "iput_object type"); -static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT_BOOLEAN), - "iput_boolean type"); -static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT_BYTE), "iput_byte type"); -static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT_CHAR), "iput_char type"); -static_assert(InlineMethodAnalyser::IsInstructionIPut(Instruction::IPUT_SHORT), "iput_short type"); -static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET) == - InlineMethodAnalyser::IPutVariant(Instruction::IPUT), "iget/iput variant"); -static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_WIDE) == - InlineMethodAnalyser::IPutVariant(Instruction::IPUT_WIDE), "iget/iput_wide variant"); -static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_OBJECT) == - InlineMethodAnalyser::IPutVariant(Instruction::IPUT_OBJECT), "iget/iput_object variant"); -static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_BOOLEAN) == - InlineMethodAnalyser::IPutVariant(Instruction::IPUT_BOOLEAN), "iget/iput_boolean variant"); -static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_BYTE) == - InlineMethodAnalyser::IPutVariant(Instruction::IPUT_BYTE), "iget/iput_byte variant"); -static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_CHAR) == - InlineMethodAnalyser::IPutVariant(Instruction::IPUT_CHAR), "iget/iput_char variant"); -static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_SHORT) == - InlineMethodAnalyser::IPutVariant(Instruction::IPUT_SHORT), "iget/iput_short variant"); - -// This is used by compiler and debugger. We look into the dex cache for resolved methods and -// fields. However, in the context of the debugger, not all methods and fields are resolved. Since -// we need to be able to detect possibly inlined method, we pass a null inline method to indicate -// we don't want to take unresolved methods and fields into account during analysis. -bool InlineMethodAnalyser::AnalyseMethodCode(verifier::MethodVerifier* verifier, - InlineMethod* result) { - DCHECK(verifier != nullptr); - if (!Runtime::Current()->UseJitCompilation()) { - DCHECK_EQ(verifier->CanLoadClasses(), result != nullptr); - } - - // Note: verifier->GetMethod() may be null. - return AnalyseMethodCode(verifier->CodeItem(), - verifier->GetMethodReference(), - (verifier->GetAccessFlags() & kAccStatic) != 0u, - verifier->GetMethod(), - result); -} - -bool InlineMethodAnalyser::AnalyseMethodCode(ArtMethod* method, InlineMethod* result) { - const DexFile::CodeItem* code_item = method->GetCodeItem(); - if (code_item == nullptr) { - // Native or abstract. - return false; - } - return AnalyseMethodCode( - code_item, method->ToMethodReference(), method->IsStatic(), method, result); -} - -bool InlineMethodAnalyser::AnalyseMethodCode(const DexFile::CodeItem* code_item, - const MethodReference& method_ref, - bool is_static, - ArtMethod* method, - InlineMethod* result) { - // We currently support only plain return or 2-instruction methods. - - DCHECK_NE(code_item->insns_size_in_code_units_, 0u); - const Instruction* instruction = Instruction::At(code_item->insns_); - Instruction::Code opcode = instruction->Opcode(); - - switch (opcode) { - case Instruction::RETURN_VOID: - if (result != nullptr) { - result->opcode = kInlineOpNop; - result->flags = kInlineSpecial; - result->d.data = 0u; - } - return true; - case Instruction::RETURN: - case Instruction::RETURN_OBJECT: - case Instruction::RETURN_WIDE: - return AnalyseReturnMethod(code_item, result); - case Instruction::CONST: - case Instruction::CONST_4: - case Instruction::CONST_16: - case Instruction::CONST_HIGH16: - // TODO: Support wide constants (RETURN_WIDE). - if (AnalyseConstMethod(code_item, result)) { - return true; - } - FALLTHROUGH_INTENDED; - case Instruction::CONST_WIDE: - case Instruction::CONST_WIDE_16: - case Instruction::CONST_WIDE_32: - case Instruction::CONST_WIDE_HIGH16: - case Instruction::INVOKE_DIRECT: - if (method != nullptr && !method->IsStatic() && method->IsConstructor()) { - return AnalyseConstructor(code_item, method, result); - } - return false; - case Instruction::IGET: - case Instruction::IGET_OBJECT: - case Instruction::IGET_BOOLEAN: - case Instruction::IGET_BYTE: - case Instruction::IGET_CHAR: - case Instruction::IGET_SHORT: - case Instruction::IGET_WIDE: - // TODO: Add handling for JIT. - // case Instruction::IGET_QUICK: - // case Instruction::IGET_WIDE_QUICK: - // case Instruction::IGET_OBJECT_QUICK: - return AnalyseIGetMethod(code_item, method_ref, is_static, method, result); - case Instruction::IPUT: - case Instruction::IPUT_OBJECT: - case Instruction::IPUT_BOOLEAN: - case Instruction::IPUT_BYTE: - case Instruction::IPUT_CHAR: - case Instruction::IPUT_SHORT: - case Instruction::IPUT_WIDE: - // TODO: Add handling for JIT. - // case Instruction::IPUT_QUICK: - // case Instruction::IPUT_WIDE_QUICK: - // case Instruction::IPUT_OBJECT_QUICK: - return AnalyseIPutMethod(code_item, method_ref, is_static, method, result); - default: - return false; - } -} - -bool InlineMethodAnalyser::IsSyntheticAccessor(MethodReference ref) { - const DexFile::MethodId& method_id = ref.dex_file->GetMethodId(ref.dex_method_index); - const char* method_name = ref.dex_file->GetMethodName(method_id); - // javac names synthetic accessors "access$nnn", - // jack names them "-getN", "-putN", "-wrapN". - return strncmp(method_name, "access$", strlen("access$")) == 0 || - strncmp(method_name, "-", strlen("-")) == 0; -} - -bool InlineMethodAnalyser::AnalyseReturnMethod(const DexFile::CodeItem* code_item, - InlineMethod* result) { - const Instruction* return_instruction = Instruction::At(code_item->insns_); - Instruction::Code return_opcode = return_instruction->Opcode(); - uint32_t reg = return_instruction->VRegA_11x(); - uint32_t arg_start = code_item->registers_size_ - code_item->ins_size_; - DCHECK_GE(reg, arg_start); - DCHECK_LT((return_opcode == Instruction::RETURN_WIDE) ? reg + 1 : reg, - code_item->registers_size_); - - if (result != nullptr) { - result->opcode = kInlineOpReturnArg; - result->flags = kInlineSpecial; - InlineReturnArgData* data = &result->d.return_data; - data->arg = reg - arg_start; - data->is_wide = (return_opcode == Instruction::RETURN_WIDE) ? 1u : 0u; - data->is_object = (return_opcode == Instruction::RETURN_OBJECT) ? 1u : 0u; - data->reserved = 0u; - data->reserved2 = 0u; - } - return true; -} - -bool InlineMethodAnalyser::AnalyseConstMethod(const DexFile::CodeItem* code_item, - InlineMethod* result) { - const Instruction* instruction = Instruction::At(code_item->insns_); - const Instruction* return_instruction = instruction->Next(); - Instruction::Code return_opcode = return_instruction->Opcode(); - if (return_opcode != Instruction::RETURN && - return_opcode != Instruction::RETURN_OBJECT) { - return false; - } - - int32_t return_reg = return_instruction->VRegA_11x(); - DCHECK_LT(return_reg, code_item->registers_size_); - - int32_t const_value = instruction->VRegB(); - if (instruction->Opcode() == Instruction::CONST_HIGH16) { - const_value <<= 16; - } - DCHECK_LT(instruction->VRegA(), code_item->registers_size_); - if (instruction->VRegA() != return_reg) { - return false; // Not returning the value set by const? - } - if (return_opcode == Instruction::RETURN_OBJECT && const_value != 0) { - return false; // Returning non-null reference constant? - } - if (result != nullptr) { - result->opcode = kInlineOpNonWideConst; - result->flags = kInlineSpecial; - result->d.data = static_cast(const_value); - } - return true; -} - -bool InlineMethodAnalyser::AnalyseIGetMethod(const DexFile::CodeItem* code_item, - const MethodReference& method_ref, - bool is_static, - ArtMethod* method, - InlineMethod* result) { - const Instruction* instruction = Instruction::At(code_item->insns_); - Instruction::Code opcode = instruction->Opcode(); - DCHECK(IsInstructionIGet(opcode)); - - const Instruction* return_instruction = instruction->Next(); - Instruction::Code return_opcode = return_instruction->Opcode(); - if (!(return_opcode == Instruction::RETURN_WIDE && opcode == Instruction::IGET_WIDE) && - !(return_opcode == Instruction::RETURN_OBJECT && opcode == Instruction::IGET_OBJECT) && - !(return_opcode == Instruction::RETURN && opcode != Instruction::IGET_WIDE && - opcode != Instruction::IGET_OBJECT)) { - return false; - } - - uint32_t return_reg = return_instruction->VRegA_11x(); - DCHECK_LT(return_opcode == Instruction::RETURN_WIDE ? return_reg + 1 : return_reg, - code_item->registers_size_); - - uint32_t dst_reg = instruction->VRegA_22c(); - uint32_t object_reg = instruction->VRegB_22c(); - uint32_t field_idx = instruction->VRegC_22c(); - uint32_t arg_start = code_item->registers_size_ - code_item->ins_size_; - DCHECK_GE(object_reg, arg_start); - DCHECK_LT(object_reg, code_item->registers_size_); - uint32_t object_arg = object_reg - arg_start; - - DCHECK_LT(opcode == Instruction::IGET_WIDE ? dst_reg + 1 : dst_reg, code_item->registers_size_); - if (dst_reg != return_reg) { - return false; // Not returning the value retrieved by IGET? - } - - if (is_static || object_arg != 0u) { - // 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 (!IsSyntheticAccessor(method_ref)) { - return false; - } - } - - // InlineIGetIPutData::object_arg is only 4 bits wide. - static constexpr uint16_t kMaxObjectArg = 15u; - if (object_arg > kMaxObjectArg) { - return false; - } - - if (result != nullptr) { - InlineIGetIPutData* data = &result->d.ifield_data; - if (!ComputeSpecialAccessorInfo(method, field_idx, false, data)) { - return false; - } - result->opcode = kInlineOpIGet; - result->flags = kInlineSpecial; - data->op_variant = IGetVariant(opcode); - data->method_is_static = is_static ? 1u : 0u; - data->object_arg = object_arg; // Allow IGET on any register, not just "this". - data->src_arg = 0u; - data->return_arg_plus1 = 0u; - } - return true; -} - -bool InlineMethodAnalyser::AnalyseIPutMethod(const DexFile::CodeItem* code_item, - const MethodReference& method_ref, - bool is_static, - ArtMethod* method, - InlineMethod* result) { - const Instruction* instruction = Instruction::At(code_item->insns_); - Instruction::Code opcode = instruction->Opcode(); - DCHECK(IsInstructionIPut(opcode)); - - const Instruction* return_instruction = instruction->Next(); - Instruction::Code return_opcode = return_instruction->Opcode(); - uint32_t arg_start = code_item->registers_size_ - code_item->ins_size_; - uint16_t return_arg_plus1 = 0u; - if (return_opcode != Instruction::RETURN_VOID) { - if (return_opcode != Instruction::RETURN && - return_opcode != Instruction::RETURN_OBJECT && - return_opcode != Instruction::RETURN_WIDE) { - return false; - } - // Returning an argument. - uint32_t return_reg = return_instruction->VRegA_11x(); - DCHECK_GE(return_reg, arg_start); - DCHECK_LT(return_opcode == Instruction::RETURN_WIDE ? return_reg + 1u : return_reg, - code_item->registers_size_); - return_arg_plus1 = return_reg - arg_start + 1u; - } - - uint32_t src_reg = instruction->VRegA_22c(); - uint32_t object_reg = instruction->VRegB_22c(); - uint32_t field_idx = instruction->VRegC_22c(); - DCHECK_GE(object_reg, arg_start); - DCHECK_LT(object_reg, code_item->registers_size_); - DCHECK_GE(src_reg, arg_start); - DCHECK_LT(opcode == Instruction::IPUT_WIDE ? src_reg + 1 : src_reg, code_item->registers_size_); - uint32_t object_arg = object_reg - arg_start; - uint32_t src_arg = src_reg - arg_start; - - if (is_static || object_arg != 0u) { - // 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 (!IsSyntheticAccessor(method_ref)) { - return false; - } - } - - // InlineIGetIPutData::object_arg/src_arg/return_arg_plus1 are each only 4 bits wide. - static constexpr uint16_t kMaxObjectArg = 15u; - static constexpr uint16_t kMaxSrcArg = 15u; - static constexpr uint16_t kMaxReturnArgPlus1 = 15u; - if (object_arg > kMaxObjectArg || src_arg > kMaxSrcArg || return_arg_plus1 > kMaxReturnArgPlus1) { - return false; - } - - if (result != nullptr) { - InlineIGetIPutData* data = &result->d.ifield_data; - if (!ComputeSpecialAccessorInfo(method, field_idx, true, data)) { - return false; - } - result->opcode = kInlineOpIPut; - result->flags = kInlineSpecial; - data->op_variant = IPutVariant(opcode); - data->method_is_static = is_static ? 1u : 0u; - data->object_arg = object_arg; // Allow IPUT on any register, not just "this". - data->src_arg = src_arg; - data->return_arg_plus1 = return_arg_plus1; - } - return true; -} - -bool InlineMethodAnalyser::ComputeSpecialAccessorInfo(ArtMethod* method, - uint32_t field_idx, - bool is_put, - InlineIGetIPutData* result) { - if (method == nullptr) { - return false; - } - ObjPtr dex_cache = method->GetDexCache(); - ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); - ArtField* field = class_linker->LookupResolvedField(field_idx, method, /* is_static */ false); - if (field == nullptr || field->IsStatic()) { - return false; - } - ObjPtr method_class = method->GetDeclaringClass(); - ObjPtr field_class = field->GetDeclaringClass(); - if (!method_class->CanAccessResolvedField(field_class, field, dex_cache, field_idx) || - (is_put && field->IsFinal() && method_class != field_class)) { - return false; - } - DCHECK_GE(field->GetOffset().Int32Value(), 0); - // Do not interleave function calls with bit field writes to placate valgrind. Bug: 27552451. - uint32_t field_offset = field->GetOffset().Uint32Value(); - bool is_volatile = field->IsVolatile(); - result->field_idx = field_idx; - result->field_offset = field_offset; - result->is_volatile = is_volatile ? 1u : 0u; - return true; -} - -} // namespace art diff --git a/runtime/quick/inline_method_analyser.h b/runtime/quick/inline_method_analyser.h deleted file mode 100644 index 2df2ced7f4..0000000000 --- a/runtime/quick/inline_method_analyser.h +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) 2014 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. - */ - -#ifndef ART_RUNTIME_QUICK_INLINE_METHOD_ANALYSER_H_ -#define ART_RUNTIME_QUICK_INLINE_METHOD_ANALYSER_H_ - -#include "base/macros.h" -#include "base/mutex.h" -#include "dex_file.h" -#include "dex_instruction.h" -#include "method_reference.h" - -/* - * NOTE: This code is part of the quick compiler. It lives in the runtime - * only to allow the debugger to check whether a method has been inlined. - */ - -namespace art { - -namespace verifier { -class MethodVerifier; -} // namespace verifier -class ArtMethod; - -enum InlineMethodOpcode : uint16_t { - kIntrinsicDoubleCvt, - kIntrinsicFloatCvt, - kIntrinsicFloat2Int, - kIntrinsicDouble2Long, - kIntrinsicFloatIsInfinite, - kIntrinsicDoubleIsInfinite, - kIntrinsicFloatIsNaN, - kIntrinsicDoubleIsNaN, - kIntrinsicReverseBits, - kIntrinsicReverseBytes, - kIntrinsicBitCount, - kIntrinsicCompare, - kIntrinsicHighestOneBit, - kIntrinsicLowestOneBit, - kIntrinsicNumberOfLeadingZeros, - kIntrinsicNumberOfTrailingZeros, - kIntrinsicRotateRight, - kIntrinsicRotateLeft, - kIntrinsicSignum, - kIntrinsicAbsInt, - kIntrinsicAbsLong, - kIntrinsicAbsFloat, - kIntrinsicAbsDouble, - kIntrinsicMinMaxInt, - kIntrinsicMinMaxLong, - kIntrinsicMinMaxFloat, - kIntrinsicMinMaxDouble, - kIntrinsicCos, - kIntrinsicSin, - kIntrinsicAcos, - kIntrinsicAsin, - kIntrinsicAtan, - kIntrinsicAtan2, - kIntrinsicCbrt, - kIntrinsicCosh, - kIntrinsicExp, - kIntrinsicExpm1, - kIntrinsicHypot, - kIntrinsicLog, - kIntrinsicLog10, - kIntrinsicNextAfter, - kIntrinsicSinh, - kIntrinsicTan, - kIntrinsicTanh, - kIntrinsicSqrt, - kIntrinsicCeil, - kIntrinsicFloor, - kIntrinsicRint, - kIntrinsicRoundFloat, - kIntrinsicRoundDouble, - kIntrinsicReferenceGetReferent, - kIntrinsicCharAt, - kIntrinsicCompareTo, - kIntrinsicEquals, - kIntrinsicGetCharsNoCheck, - kIntrinsicIsEmptyOrLength, - kIntrinsicIndexOf, - kIntrinsicNewStringFromBytes, - kIntrinsicNewStringFromChars, - kIntrinsicNewStringFromString, - kIntrinsicCurrentThread, - kIntrinsicPeek, - kIntrinsicPoke, - kIntrinsicCas, - kIntrinsicUnsafeGet, - kIntrinsicUnsafePut, - - // 1.8. - kIntrinsicUnsafeGetAndAddInt, - kIntrinsicUnsafeGetAndAddLong, - kIntrinsicUnsafeGetAndSetInt, - kIntrinsicUnsafeGetAndSetLong, - kIntrinsicUnsafeGetAndSetObject, - kIntrinsicUnsafeLoadFence, - kIntrinsicUnsafeStoreFence, - kIntrinsicUnsafeFullFence, - - kIntrinsicSystemArrayCopyCharArray, - kIntrinsicSystemArrayCopy, - - kInlineOpNop, - kInlineOpReturnArg, - kInlineOpNonWideConst, - kInlineOpIGet, - kInlineOpIPut, - kInlineOpConstructor, - kInlineStringInit, -}; -std::ostream& operator<<(std::ostream& os, const InlineMethodOpcode& rhs); - -enum InlineMethodFlags : uint16_t { - kNoInlineMethodFlags = 0x0000, - kInlineIntrinsic = 0x0001, - kInlineSpecial = 0x0002, -}; - -// IntrinsicFlags are stored in InlineMethod::d::raw_data -enum IntrinsicFlags { - kIntrinsicFlagNone = 0, - - // kIntrinsicMinMaxInt - kIntrinsicFlagMax = kIntrinsicFlagNone, - kIntrinsicFlagMin = 1, - - // kIntrinsicIsEmptyOrLength - kIntrinsicFlagLength = kIntrinsicFlagNone, - kIntrinsicFlagIsEmpty = kIntrinsicFlagMin, - - // kIntrinsicIndexOf - kIntrinsicFlagBase0 = kIntrinsicFlagMin, - - // kIntrinsicUnsafeGet, kIntrinsicUnsafePut, kIntrinsicUnsafeCas - kIntrinsicFlagIsLong = kIntrinsicFlagMin, - // kIntrinsicUnsafeGet, kIntrinsicUnsafePut - kIntrinsicFlagIsVolatile = 2, - // kIntrinsicUnsafePut, kIntrinsicUnsafeCas - kIntrinsicFlagIsObject = 4, - // kIntrinsicUnsafePut - kIntrinsicFlagIsOrdered = 8, - - // kIntrinsicDoubleCvt, kIntrinsicFloatCvt. - kIntrinsicFlagToFloatingPoint = kIntrinsicFlagMin, -}; - -struct InlineIGetIPutData { - // The op_variant below is DexMemAccessType but the runtime doesn't know that enumeration. - uint16_t op_variant : 3; - uint16_t method_is_static : 1; - uint16_t object_arg : 4; - uint16_t src_arg : 4; // iput only - uint16_t return_arg_plus1 : 4; // iput only, method argument to return + 1, 0 = return void. - uint16_t field_idx; - uint32_t is_volatile : 1; - uint32_t field_offset : 31; -}; -static_assert(sizeof(InlineIGetIPutData) == sizeof(uint64_t), "Invalid size of InlineIGetIPutData"); - -struct InlineReturnArgData { - uint16_t arg; - uint16_t is_wide : 1; - uint16_t is_object : 1; - uint16_t reserved : 14; - uint32_t reserved2; -}; -static_assert(sizeof(InlineReturnArgData) == sizeof(uint64_t), - "Invalid size of InlineReturnArgData"); - -struct InlineConstructorData { - // There can be up to 3 IPUTs, unused fields are marked with kNoDexIndex16. - uint16_t iput0_field_index; - uint16_t iput1_field_index; - uint16_t iput2_field_index; - uint16_t iput0_arg : 4; - uint16_t iput1_arg : 4; - uint16_t iput2_arg : 4; - uint16_t reserved : 4; -}; -static_assert(sizeof(InlineConstructorData) == sizeof(uint64_t), - "Invalid size of InlineConstructorData"); - -struct InlineMethod { - InlineMethodOpcode opcode; - InlineMethodFlags flags; - union { - uint64_t data; - InlineIGetIPutData ifield_data; - InlineReturnArgData return_data; - InlineConstructorData constructor_data; - } d; -}; - -class InlineMethodAnalyser { - public: - /** - * Analyse method code to determine if the method is a candidate for inlining. - * If it is, record the inlining data. - * - * @param verifier the method verifier holding data about the method to analyse. - * @param method placeholder for the inline method data. - * @return true if the method is a candidate for inlining, false otherwise. - */ - static bool AnalyseMethodCode(verifier::MethodVerifier* verifier, InlineMethod* result) - REQUIRES_SHARED(Locks::mutator_lock_); - static bool AnalyseMethodCode(ArtMethod* method, InlineMethod* result) - REQUIRES_SHARED(Locks::mutator_lock_); - - static constexpr bool IsInstructionIGet(Instruction::Code opcode) { - return Instruction::IGET <= opcode && opcode <= Instruction::IGET_SHORT; - } - - static constexpr bool IsInstructionIPut(Instruction::Code opcode) { - return Instruction::IPUT <= opcode && opcode <= Instruction::IPUT_SHORT; - } - - static constexpr uint16_t IGetVariant(Instruction::Code opcode) { - return opcode - Instruction::IGET; - } - - static constexpr uint16_t IPutVariant(Instruction::Code opcode) { - return opcode - Instruction::IPUT; - } - - // Determines whether the method is a synthetic accessor (method name starts with "access$"). - static bool IsSyntheticAccessor(MethodReference ref); - - private: - static bool AnalyseMethodCode(const DexFile::CodeItem* code_item, - const MethodReference& method_ref, - bool is_static, - ArtMethod* method, - InlineMethod* result) - REQUIRES_SHARED(Locks::mutator_lock_); - static bool AnalyseReturnMethod(const DexFile::CodeItem* code_item, InlineMethod* result); - static bool AnalyseConstMethod(const DexFile::CodeItem* code_item, InlineMethod* result); - static bool AnalyseIGetMethod(const DexFile::CodeItem* code_item, - const MethodReference& method_ref, - bool is_static, - ArtMethod* method, - InlineMethod* result) - REQUIRES_SHARED(Locks::mutator_lock_); - static bool AnalyseIPutMethod(const DexFile::CodeItem* code_item, - const MethodReference& method_ref, - bool is_static, - ArtMethod* method, - InlineMethod* result) - REQUIRES_SHARED(Locks::mutator_lock_); - - // Can we fast path instance field access in a verified accessor? - // If yes, computes field's offset and volatility and whether the method is static or not. - static bool ComputeSpecialAccessorInfo(ArtMethod* method, - uint32_t field_idx, - bool is_put, - InlineIGetIPutData* result) - REQUIRES_SHARED(Locks::mutator_lock_); -}; - -} // namespace art - -#endif // ART_RUNTIME_QUICK_INLINE_METHOD_ANALYSER_H_ -- cgit v1.2.3-59-g8ed1b From 6b040570b5f71bdb6a0aacf51a7613edf35785b7 Mon Sep 17 00:00:00 2001 From: Andreas Gampe Date: Thu, 20 Apr 2017 20:07:21 -0700 Subject: ART: Clean up InlineMethodAnalyzer Remove dead code. Test: m test-art-host Change-Id: If86503fde2a77279a3169144bcae700101d81784 --- compiler/dex/inline_method_analyser.cc | 26 ------- compiler/dex/inline_method_analyser.h | 121 --------------------------------- 2 files changed, 147 deletions(-) diff --git a/compiler/dex/inline_method_analyser.cc b/compiler/dex/inline_method_analyser.cc index 3347070468..e691a67dc0 100644 --- a/compiler/dex/inline_method_analyser.cc +++ b/compiler/dex/inline_method_analyser.cc @@ -26,7 +26,6 @@ #include "dex_instruction_utils.h" #include "mirror/class-inl.h" #include "mirror/dex_cache-inl.h" -#include "verifier/method_verifier-inl.h" /* * NOTE: This code is part of the quick compiler. It lives in the runtime @@ -391,7 +390,6 @@ bool AnalyseConstructor(const DexFile::CodeItem* code_item, #undef STORE_IPUT result->opcode = kInlineOpConstructor; - result->flags = kInlineSpecial; result->d.constructor_data.reserved = 0u; return true; } @@ -429,25 +427,6 @@ static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_CHAR) == static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_SHORT) == InlineMethodAnalyser::IPutVariant(Instruction::IPUT_SHORT), "iget/iput_short variant"); -// This is used by compiler and debugger. We look into the dex cache for resolved methods and -// fields. However, in the context of the debugger, not all methods and fields are resolved. Since -// we need to be able to detect possibly inlined method, we pass a null inline method to indicate -// we don't want to take unresolved methods and fields into account during analysis. -bool InlineMethodAnalyser::AnalyseMethodCode(verifier::MethodVerifier* verifier, - InlineMethod* result) { - DCHECK(verifier != nullptr); - if (!Runtime::Current()->UseJitCompilation()) { - DCHECK_EQ(verifier->CanLoadClasses(), result != nullptr); - } - - // Note: verifier->GetMethod() may be null. - return AnalyseMethodCode(verifier->CodeItem(), - verifier->GetMethodReference(), - (verifier->GetAccessFlags() & kAccStatic) != 0u, - verifier->GetMethod(), - result); -} - bool InlineMethodAnalyser::AnalyseMethodCode(ArtMethod* method, InlineMethod* result) { const DexFile::CodeItem* code_item = method->GetCodeItem(); if (code_item == nullptr) { @@ -473,7 +452,6 @@ bool InlineMethodAnalyser::AnalyseMethodCode(const DexFile::CodeItem* code_item, case Instruction::RETURN_VOID: if (result != nullptr) { result->opcode = kInlineOpNop; - result->flags = kInlineSpecial; result->d.data = 0u; } return true; @@ -549,7 +527,6 @@ bool InlineMethodAnalyser::AnalyseReturnMethod(const DexFile::CodeItem* code_ite if (result != nullptr) { result->opcode = kInlineOpReturnArg; - result->flags = kInlineSpecial; InlineReturnArgData* data = &result->d.return_data; data->arg = reg - arg_start; data->is_wide = (return_opcode == Instruction::RETURN_WIDE) ? 1u : 0u; @@ -586,7 +563,6 @@ bool InlineMethodAnalyser::AnalyseConstMethod(const DexFile::CodeItem* code_item } if (result != nullptr) { result->opcode = kInlineOpNonWideConst; - result->flags = kInlineSpecial; result->d.data = static_cast(const_value); } return true; @@ -647,7 +623,6 @@ bool InlineMethodAnalyser::AnalyseIGetMethod(const DexFile::CodeItem* code_item, return false; } result->opcode = kInlineOpIGet; - result->flags = kInlineSpecial; data->op_variant = IGetVariant(opcode); data->method_is_static = is_static ? 1u : 0u; data->object_arg = object_arg; // Allow IGET on any register, not just "this". @@ -716,7 +691,6 @@ bool InlineMethodAnalyser::AnalyseIPutMethod(const DexFile::CodeItem* code_item, return false; } result->opcode = kInlineOpIPut; - result->flags = kInlineSpecial; data->op_variant = IPutVariant(opcode); data->method_is_static = is_static ? 1u : 0u; data->object_arg = object_arg; // Allow IPUT on any register, not just "this". diff --git a/compiler/dex/inline_method_analyser.h b/compiler/dex/inline_method_analyser.h index ab643abf21..a35e97fa11 100644 --- a/compiler/dex/inline_method_analyser.h +++ b/compiler/dex/inline_method_analyser.h @@ -36,128 +36,12 @@ class MethodVerifier; class ArtMethod; enum InlineMethodOpcode : uint16_t { - kIntrinsicDoubleCvt, - kIntrinsicFloatCvt, - kIntrinsicFloat2Int, - kIntrinsicDouble2Long, - kIntrinsicFloatIsInfinite, - kIntrinsicDoubleIsInfinite, - kIntrinsicFloatIsNaN, - kIntrinsicDoubleIsNaN, - kIntrinsicReverseBits, - kIntrinsicReverseBytes, - kIntrinsicBitCount, - kIntrinsicCompare, - kIntrinsicHighestOneBit, - kIntrinsicLowestOneBit, - kIntrinsicNumberOfLeadingZeros, - kIntrinsicNumberOfTrailingZeros, - kIntrinsicRotateRight, - kIntrinsicRotateLeft, - kIntrinsicSignum, - kIntrinsicAbsInt, - kIntrinsicAbsLong, - kIntrinsicAbsFloat, - kIntrinsicAbsDouble, - kIntrinsicMinMaxInt, - kIntrinsicMinMaxLong, - kIntrinsicMinMaxFloat, - kIntrinsicMinMaxDouble, - kIntrinsicCos, - kIntrinsicSin, - kIntrinsicAcos, - kIntrinsicAsin, - kIntrinsicAtan, - kIntrinsicAtan2, - kIntrinsicCbrt, - kIntrinsicCosh, - kIntrinsicExp, - kIntrinsicExpm1, - kIntrinsicHypot, - kIntrinsicLog, - kIntrinsicLog10, - kIntrinsicNextAfter, - kIntrinsicSinh, - kIntrinsicTan, - kIntrinsicTanh, - kIntrinsicSqrt, - kIntrinsicCeil, - kIntrinsicFloor, - kIntrinsicRint, - kIntrinsicRoundFloat, - kIntrinsicRoundDouble, - kIntrinsicReferenceGetReferent, - kIntrinsicCharAt, - kIntrinsicCompareTo, - kIntrinsicEquals, - kIntrinsicGetCharsNoCheck, - kIntrinsicIsEmptyOrLength, - kIntrinsicIndexOf, - kIntrinsicNewStringFromBytes, - kIntrinsicNewStringFromChars, - kIntrinsicNewStringFromString, - kIntrinsicCurrentThread, - kIntrinsicPeek, - kIntrinsicPoke, - kIntrinsicCas, - kIntrinsicUnsafeGet, - kIntrinsicUnsafePut, - - // 1.8. - kIntrinsicUnsafeGetAndAddInt, - kIntrinsicUnsafeGetAndAddLong, - kIntrinsicUnsafeGetAndSetInt, - kIntrinsicUnsafeGetAndSetLong, - kIntrinsicUnsafeGetAndSetObject, - kIntrinsicUnsafeLoadFence, - kIntrinsicUnsafeStoreFence, - kIntrinsicUnsafeFullFence, - - kIntrinsicSystemArrayCopyCharArray, - kIntrinsicSystemArrayCopy, - kInlineOpNop, kInlineOpReturnArg, kInlineOpNonWideConst, kInlineOpIGet, kInlineOpIPut, kInlineOpConstructor, - kInlineStringInit, -}; -std::ostream& operator<<(std::ostream& os, const InlineMethodOpcode& rhs); - -enum InlineMethodFlags : uint16_t { - kNoInlineMethodFlags = 0x0000, - kInlineIntrinsic = 0x0001, - kInlineSpecial = 0x0002, -}; - -// IntrinsicFlags are stored in InlineMethod::d::raw_data -enum IntrinsicFlags { - kIntrinsicFlagNone = 0, - - // kIntrinsicMinMaxInt - kIntrinsicFlagMax = kIntrinsicFlagNone, - kIntrinsicFlagMin = 1, - - // kIntrinsicIsEmptyOrLength - kIntrinsicFlagLength = kIntrinsicFlagNone, - kIntrinsicFlagIsEmpty = kIntrinsicFlagMin, - - // kIntrinsicIndexOf - kIntrinsicFlagBase0 = kIntrinsicFlagMin, - - // kIntrinsicUnsafeGet, kIntrinsicUnsafePut, kIntrinsicUnsafeCas - kIntrinsicFlagIsLong = kIntrinsicFlagMin, - // kIntrinsicUnsafeGet, kIntrinsicUnsafePut - kIntrinsicFlagIsVolatile = 2, - // kIntrinsicUnsafePut, kIntrinsicUnsafeCas - kIntrinsicFlagIsObject = 4, - // kIntrinsicUnsafePut - kIntrinsicFlagIsOrdered = 8, - - // kIntrinsicDoubleCvt, kIntrinsicFloatCvt. - kIntrinsicFlagToFloatingPoint = kIntrinsicFlagMin, }; struct InlineIGetIPutData { @@ -198,7 +82,6 @@ static_assert(sizeof(InlineConstructorData) == sizeof(uint64_t), struct InlineMethod { InlineMethodOpcode opcode; - InlineMethodFlags flags; union { uint64_t data; InlineIGetIPutData ifield_data; @@ -213,12 +96,8 @@ class InlineMethodAnalyser { * Analyse method code to determine if the method is a candidate for inlining. * If it is, record the inlining data. * - * @param verifier the method verifier holding data about the method to analyse. - * @param method placeholder for the inline method data. * @return true if the method is a candidate for inlining, false otherwise. */ - static bool AnalyseMethodCode(verifier::MethodVerifier* verifier, InlineMethod* result) - REQUIRES_SHARED(Locks::mutator_lock_); static bool AnalyseMethodCode(ArtMethod* method, InlineMethod* result) REQUIRES_SHARED(Locks::mutator_lock_); -- cgit v1.2.3-59-g8ed1b