diff options
267 files changed, 8421 insertions, 1932 deletions
diff --git a/Android.bp b/Android.bp index 0ce7916590..8678fd06d2 100644 --- a/Android.bp +++ b/Android.bp @@ -41,4 +41,5 @@ subdirs = [ "test", "tools/cpp-define-generator", "tools/dmtracedump", + "tools/titrace", ] diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h index 20c4a7ed62..521156a319 100644 --- a/cmdline/cmdline_types.h +++ b/cmdline/cmdline_types.h @@ -36,6 +36,7 @@ #include "jdwp/jdwp.h" #include "jit/profile_saver_options.h" #include "plugin.h" +#include "read_barrier_config.h" #include "ti/agent.h" #include "unit.h" diff --git a/compiler/debug/elf_debug_info_writer.h b/compiler/debug/elf_debug_info_writer.h index de32351abf..6c6bd63b14 100644 --- a/compiler/debug/elf_debug_info_writer.h +++ b/compiler/debug/elf_debug_info_writer.h @@ -122,17 +122,39 @@ class ElfCompilationUnitWriter { const Elf_Addr base_address = compilation_unit.is_code_address_text_relative ? owner_->builder_->GetText()->GetAddress() : 0; - const uint64_t cu_size = compilation_unit.code_end - compilation_unit.code_address; + const bool is64bit = Is64BitInstructionSet(owner_->builder_->GetIsa()); using namespace dwarf; // NOLINT. For easy access to DWARF constants. info_.StartTag(DW_TAG_compile_unit); info_.WriteString(DW_AT_producer, "Android dex2oat"); info_.WriteData1(DW_AT_language, DW_LANG_Java); info_.WriteString(DW_AT_comp_dir, "$JAVA_SRC_ROOT"); + // The low_pc acts as base address for several other addresses/ranges. info_.WriteAddr(DW_AT_low_pc, base_address + compilation_unit.code_address); - info_.WriteUdata(DW_AT_high_pc, dchecked_integral_cast<uint32_t>(cu_size)); info_.WriteSecOffset(DW_AT_stmt_list, compilation_unit.debug_line_offset); + // Write .debug_ranges entries covering code ranges of the whole compilation unit. + dwarf::Writer<> debug_ranges(&owner_->debug_ranges_); + info_.WriteSecOffset(DW_AT_ranges, owner_->debug_ranges_.size()); + for (auto mi : compilation_unit.methods) { + uint64_t low_pc = mi->code_address - compilation_unit.code_address; + uint64_t high_pc = low_pc + mi->code_size; + if (is64bit) { + debug_ranges.PushUint64(low_pc); + debug_ranges.PushUint64(high_pc); + } else { + debug_ranges.PushUint32(low_pc); + debug_ranges.PushUint32(high_pc); + } + } + if (is64bit) { + debug_ranges.PushUint64(0); // End of list. + debug_ranges.PushUint64(0); + } else { + debug_ranges.PushUint32(0); // End of list. + debug_ranges.PushUint32(0); + } + const char* last_dex_class_desc = nullptr; for (auto mi : compilation_unit.methods) { DCHECK(mi->dex_file != nullptr); diff --git a/compiler/debug/elf_debug_writer.cc b/compiler/debug/elf_debug_writer.cc index 7fa6e146c5..c5ff85827e 100644 --- a/compiler/debug/elf_debug_writer.cc +++ b/compiler/debug/elf_debug_writer.cc @@ -17,6 +17,7 @@ #include "elf_debug_writer.h" #include <vector> +#include <unordered_map> #include "base/array_ref.h" #include "debug/dwarf/dwarf_constants.h" @@ -46,27 +47,42 @@ void WriteDebugInfo(ElfBuilder<ElfTypes>* builder, // Write .debug_frame. WriteCFISection(builder, method_infos, cfi_format, write_oat_patches); - // Group the methods into compilation units based on source file. - std::vector<ElfCompilationUnit> compilation_units; - const char* last_source_file = nullptr; + // Group the methods into compilation units based on class. + std::unordered_map<const DexFile::ClassDef*, ElfCompilationUnit> class_to_compilation_unit; for (const MethodDebugInfo& mi : method_infos) { if (mi.dex_file != nullptr) { auto& dex_class_def = mi.dex_file->GetClassDef(mi.class_def_index); - const char* source_file = mi.dex_file->GetSourceFile(dex_class_def); - if (compilation_units.empty() || source_file != last_source_file) { - compilation_units.push_back(ElfCompilationUnit()); - } - ElfCompilationUnit& cu = compilation_units.back(); + ElfCompilationUnit& cu = class_to_compilation_unit[&dex_class_def]; cu.methods.push_back(&mi); // All methods must have the same addressing mode otherwise the min/max below does not work. DCHECK_EQ(cu.methods.front()->is_code_address_text_relative, mi.is_code_address_text_relative); cu.is_code_address_text_relative = mi.is_code_address_text_relative; cu.code_address = std::min(cu.code_address, mi.code_address); cu.code_end = std::max(cu.code_end, mi.code_address + mi.code_size); - last_source_file = source_file; } } + // Sort compilation units to make the compiler output deterministic. + std::vector<ElfCompilationUnit> compilation_units; + compilation_units.reserve(class_to_compilation_unit.size()); + for (auto& it : class_to_compilation_unit) { + // The .debug_line section requires the methods to be sorted by code address. + std::stable_sort(it.second.methods.begin(), + it.second.methods.end(), + [](const MethodDebugInfo* a, const MethodDebugInfo* b) { + return a->code_address < b->code_address; + }); + compilation_units.push_back(std::move(it.second)); + } + std::sort(compilation_units.begin(), + compilation_units.end(), + [](ElfCompilationUnit& a, ElfCompilationUnit& b) { + // Sort by index of the first method within the method_infos array. + // This assumes that the order of method_infos is deterministic. + // Code address is not good for sorting due to possible duplicates. + return a.methods.front() < b.methods.front(); + }); + // Write .debug_line section. if (!compilation_units.empty()) { ElfDebugLineWriter<ElfTypes> line_writer(builder); @@ -174,31 +190,6 @@ std::vector<uint8_t> WriteDebugElfFileForClasses(InstructionSet isa, } } -std::vector<MethodDebugInfo> MakeTrampolineInfos(const OatHeader& header) { - std::map<const char*, uint32_t> trampolines = { - { "interpreterToInterpreterBridge", header.GetInterpreterToInterpreterBridgeOffset() }, - { "interpreterToCompiledCodeBridge", header.GetInterpreterToCompiledCodeBridgeOffset() }, - { "jniDlsymLookup", header.GetJniDlsymLookupOffset() }, - { "quickGenericJniTrampoline", header.GetQuickGenericJniTrampolineOffset() }, - { "quickImtConflictTrampoline", header.GetQuickImtConflictTrampolineOffset() }, - { "quickResolutionTrampoline", header.GetQuickResolutionTrampolineOffset() }, - { "quickToInterpreterBridge", header.GetQuickToInterpreterBridgeOffset() }, - }; - std::vector<MethodDebugInfo> result; - for (const auto& it : trampolines) { - if (it.second != 0) { - MethodDebugInfo info = MethodDebugInfo(); - info.trampoline_name = it.first; - info.isa = header.GetInstructionSet(); - info.is_code_address_text_relative = true; - info.code_address = it.second - header.GetExecutableOffset(); - info.code_size = 0; // The symbol lasts until the next symbol. - result.push_back(std::move(info)); - } - } - return result; -} - // Explicit instantiations template void WriteDebugInfo<ElfTypes32>( ElfBuilder<ElfTypes32>* builder, diff --git a/compiler/debug/elf_debug_writer.h b/compiler/debug/elf_debug_writer.h index 5d688108e7..6e26ba36c4 100644 --- a/compiler/debug/elf_debug_writer.h +++ b/compiler/debug/elf_debug_writer.h @@ -58,8 +58,6 @@ std::vector<uint8_t> WriteDebugElfFileForClasses( const ArrayRef<mirror::Class*>& types) REQUIRES_SHARED(Locks::mutator_lock_); -std::vector<MethodDebugInfo> MakeTrampolineInfos(const OatHeader& oat_header); - } // namespace debug } // namespace art diff --git a/compiler/debug/elf_symtab_writer.h b/compiler/debug/elf_symtab_writer.h index af9f091529..abd2699a1f 100644 --- a/compiler/debug/elf_symtab_writer.h +++ b/compiler/debug/elf_symtab_writer.h @@ -65,7 +65,7 @@ static void WriteDebugSymbols(ElfBuilder<ElfTypes>* builder, continue; // Add symbol only for the first instance. } size_t name_offset; - if (info.trampoline_name != nullptr) { + if (!info.trampoline_name.empty()) { name_offset = strtab->Write(info.trampoline_name); } else { DCHECK(info.dex_file != nullptr); diff --git a/compiler/debug/method_debug_info.h b/compiler/debug/method_debug_info.h index ed1da2c26e..567891087a 100644 --- a/compiler/debug/method_debug_info.h +++ b/compiler/debug/method_debug_info.h @@ -17,6 +17,8 @@ #ifndef ART_COMPILER_DEBUG_METHOD_DEBUG_INFO_H_ #define ART_COMPILER_DEBUG_METHOD_DEBUG_INFO_H_ +#include <string> + #include "compiled_method.h" #include "dex_file.h" @@ -24,7 +26,7 @@ namespace art { namespace debug { struct MethodDebugInfo { - const char* trampoline_name; + std::string trampoline_name; const DexFile* dex_file; // Native methods (trampolines) do not reference dex file. size_t class_def_index; uint32_t dex_method_index; diff --git a/compiler/dex/inline_method_analyser.cc b/compiler/dex/inline_method_analyser.cc index 54ddc2188b..c8e3d5edd8 100644 --- a/compiler/dex/inline_method_analyser.cc +++ b/compiler/dex/inline_method_analyser.cc @@ -509,7 +509,7 @@ bool InlineMethodAnalyser::AnalyseMethodCode(const DexFile::CodeItem* code_item, } bool InlineMethodAnalyser::IsSyntheticAccessor(MethodReference ref) { - const DexFile::MethodId& method_id = ref.dex_file->GetMethodId(ref.dex_method_index); + const DexFile::MethodId& method_id = ref.dex_file->GetMethodId(ref.index); const char* method_name = ref.dex_file->GetMethodName(method_id); // javac names synthetic accessors "access$nnn", // jack names them "-getN", "-putN", "-wrapN". diff --git a/compiler/dex/quick_compiler_callbacks.cc b/compiler/dex/quick_compiler_callbacks.cc index 23511e55fc..92b123013d 100644 --- a/compiler/dex/quick_compiler_callbacks.cc +++ b/compiler/dex/quick_compiler_callbacks.cc @@ -44,11 +44,14 @@ ClassStatus QuickCompilerCallbacks::GetPreviousClassState(ClassReference ref) { // In the case of the quicken filter: avoiding verification of quickened instructions, which the // verifier doesn't currently support. // In the case of the verify filter, avoiding verifiying twice. - ClassStatus status; - if (!compiler_driver_->GetCompiledClass(ref, &status)) { - return ClassStatus::kStatusNotReady; + return compiler_driver_->GetClassStatus(ref); +} + +void QuickCompilerCallbacks::UpdateClassState(ClassReference ref, ClassStatus status) { + // Driver is null when bootstrapping the runtime. + if (compiler_driver_ != nullptr) { + compiler_driver_->RecordClassStatus(ref, status); } - return status; } } // namespace art diff --git a/compiler/dex/quick_compiler_callbacks.h b/compiler/dex/quick_compiler_callbacks.h index 45456f2a1c..6d22f955a3 100644 --- a/compiler/dex/quick_compiler_callbacks.h +++ b/compiler/dex/quick_compiler_callbacks.h @@ -26,48 +26,50 @@ class CompilerDriver; class VerificationResults; class QuickCompilerCallbacks FINAL : public CompilerCallbacks { - public: - explicit QuickCompilerCallbacks(CompilerCallbacks::CallbackMode mode) - : CompilerCallbacks(mode) {} + public: + explicit QuickCompilerCallbacks(CompilerCallbacks::CallbackMode mode) + : CompilerCallbacks(mode) {} - ~QuickCompilerCallbacks() { } + ~QuickCompilerCallbacks() { } - void MethodVerified(verifier::MethodVerifier* verifier) - REQUIRES_SHARED(Locks::mutator_lock_) OVERRIDE; + void MethodVerified(verifier::MethodVerifier* verifier) + REQUIRES_SHARED(Locks::mutator_lock_) OVERRIDE; - void ClassRejected(ClassReference ref) OVERRIDE; + void ClassRejected(ClassReference ref) OVERRIDE; - // We are running in an environment where we can call patchoat safely so we should. - bool IsRelocationPossible() OVERRIDE { - return true; - } + // We are running in an environment where we can call patchoat safely so we should. + bool IsRelocationPossible() OVERRIDE { + return true; + } - verifier::VerifierDeps* GetVerifierDeps() const OVERRIDE { - return verifier_deps_.get(); - } + verifier::VerifierDeps* GetVerifierDeps() const OVERRIDE { + return verifier_deps_.get(); + } - void SetVerifierDeps(verifier::VerifierDeps* deps) OVERRIDE { - verifier_deps_.reset(deps); - } + void SetVerifierDeps(verifier::VerifierDeps* deps) OVERRIDE { + verifier_deps_.reset(deps); + } - void SetVerificationResults(VerificationResults* verification_results) { - verification_results_ = verification_results; - } + void SetVerificationResults(VerificationResults* verification_results) { + verification_results_ = verification_results; + } - ClassStatus GetPreviousClassState(ClassReference ref) OVERRIDE; + ClassStatus GetPreviousClassState(ClassReference ref) OVERRIDE; - void SetDoesClassUnloading(bool does_class_unloading, CompilerDriver* compiler_driver) - OVERRIDE { - does_class_unloading_ = does_class_unloading; - compiler_driver_ = compiler_driver; - DCHECK(!does_class_unloading || compiler_driver_ != nullptr); - } + void SetDoesClassUnloading(bool does_class_unloading, CompilerDriver* compiler_driver) + OVERRIDE { + does_class_unloading_ = does_class_unloading; + compiler_driver_ = compiler_driver; + DCHECK(!does_class_unloading || compiler_driver_ != nullptr); + } - private: - VerificationResults* verification_results_ = nullptr; - bool does_class_unloading_ = false; - CompilerDriver* compiler_driver_ = nullptr; - std::unique_ptr<verifier::VerifierDeps> verifier_deps_; + void UpdateClassState(ClassReference ref, ClassStatus state) OVERRIDE; + + private: + VerificationResults* verification_results_ = nullptr; + bool does_class_unloading_ = false; + CompilerDriver* compiler_driver_ = nullptr; + std::unique_ptr<verifier::VerifierDeps> verifier_deps_; }; } // namespace art diff --git a/compiler/dex/verification_results.cc b/compiler/dex/verification_results.cc index cfb56e30a8..03c90d82c8 100644 --- a/compiler/dex/verification_results.cc +++ b/compiler/dex/verification_results.cc @@ -52,18 +52,16 @@ void VerificationResults::ProcessVerifiedMethod(verifier::MethodVerifier* method // We'll punt this later. return; } - AtomicMap::InsertResult result = atomic_verified_methods_.Insert( - DexFileReference(ref.dex_file, ref.dex_method_index), - /*expected*/ nullptr, - verified_method.get()); + AtomicMap::InsertResult result = atomic_verified_methods_.Insert(ref, + /*expected*/ nullptr, + verified_method.get()); const VerifiedMethod* existing = nullptr; bool inserted; if (result != AtomicMap::kInsertResultInvalidDexFile) { inserted = (result == AtomicMap::kInsertResultSuccess); if (!inserted) { // Rare case. - CHECK(atomic_verified_methods_.Get(DexFileReference(ref.dex_file, ref.dex_method_index), - &existing)); + CHECK(atomic_verified_methods_.Get(ref, &existing)); CHECK_NE(verified_method.get(), existing); } } else { @@ -100,7 +98,7 @@ void VerificationResults::ProcessVerifiedMethod(verifier::MethodVerifier* method const VerifiedMethod* VerificationResults::GetVerifiedMethod(MethodReference ref) { const VerifiedMethod* ret = nullptr; - if (atomic_verified_methods_.Get(DexFileReference(ref.dex_file, ref.dex_method_index), &ret)) { + if (atomic_verified_methods_.Get(ref, &ret)) { return ret; } ReaderMutexLock mu(Thread::Current(), verified_methods_lock_); @@ -114,7 +112,7 @@ void VerificationResults::CreateVerifiedMethodFor(MethodReference ref) { // at runtime. std::unique_ptr<VerifiedMethod> verified_method = std::make_unique<VerifiedMethod>( /* encountered_error_types */ 0, /* has_runtime_throw */ false); - if (atomic_verified_methods_.Insert(DexFileReference(ref.dex_file, ref.dex_method_index), + if (atomic_verified_methods_.Insert(ref, /*expected*/ nullptr, verified_method.get()) == AtomicMap::InsertResult::kInsertResultSuccess) { @@ -149,7 +147,7 @@ bool VerificationResults::IsCandidateForCompilation(MethodReference&, } void VerificationResults::AddDexFile(const DexFile* dex_file) { - atomic_verified_methods_.AddDexFile(dex_file, dex_file->NumMethodIds()); + atomic_verified_methods_.AddDexFile(dex_file); WriterMutexLock mu(Thread::Current(), verified_methods_lock_); // There can be some verified methods that are already registered for the dex_file since we set // up well known classes earlier. Remove these and put them in the array so that we don't @@ -157,9 +155,7 @@ void VerificationResults::AddDexFile(const DexFile* dex_file) { for (auto it = verified_methods_.begin(); it != verified_methods_.end(); ) { MethodReference ref = it->first; if (ref.dex_file == dex_file) { - CHECK(atomic_verified_methods_.Insert(DexFileReference(ref.dex_file, ref.dex_method_index), - nullptr, - it->second) == + CHECK(atomic_verified_methods_.Insert(ref, nullptr, it->second) == AtomicMap::kInsertResultSuccess); it = verified_methods_.erase(it); } else { diff --git a/compiler/dex/verification_results.h b/compiler/dex/verification_results.h index 5a03599de0..d19e993661 100644 --- a/compiler/dex/verification_results.h +++ b/compiler/dex/verification_results.h @@ -64,10 +64,8 @@ class VerificationResults { private: // Verified methods. The method array is fixed to avoid needing a lock to extend it. - using AtomicMap = AtomicDexRefMap<const VerifiedMethod*>; - using VerifiedMethodMap = SafeMap<MethodReference, - const VerifiedMethod*, - MethodReferenceComparator>; + using AtomicMap = AtomicDexRefMap<MethodReference, const VerifiedMethod*>; + using VerifiedMethodMap = SafeMap<MethodReference, const VerifiedMethod*>; VerifiedMethodMap verified_methods_ GUARDED_BY(verified_methods_lock_); const CompilerOptions* const compiler_options_; diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index 18b54eefba..678f090532 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -896,7 +896,7 @@ void CompilerDriver::PreCompile(jobject class_loader, for (const DexFile* dex_file : dex_files) { // Can be already inserted if the caller is CompileOne. This happens for gtests. if (!compiled_methods_.HaveDexFile(dex_file)) { - compiled_methods_.AddDexFile(dex_file, dex_file->NumMethodIds()); + compiled_methods_.AddDexFile(dex_file); } } // Resolve eagerly to prepare for compilation. @@ -964,7 +964,7 @@ bool CompilerDriver::IsMethodToCompile(const MethodReference& method_ref) const return true; } - std::string tmp = method_ref.dex_file->PrettyMethod(method_ref.dex_method_index, true); + std::string tmp = method_ref.PrettyMethod(); return methods_to_compile_->find(tmp.c_str()) != methods_to_compile_->end(); } @@ -985,8 +985,7 @@ bool CompilerDriver::ShouldCompileBasedOnProfile(const MethodReference& method_r if (kDebugProfileGuidedCompilation) { LOG(INFO) << "[ProfileGuidedCompilation] " - << (result ? "Compiled" : "Skipped") << " method:" - << method_ref.dex_file->PrettyMethod(method_ref.dex_method_index, true); + << (result ? "Compiled" : "Skipped") << " method:" << method_ref.PrettyMethod(true); } return result; } @@ -1359,7 +1358,7 @@ void CompilerDriver::MarkForDexToDexCompilation(Thread* self, const MethodRefere &dex_to_dex_references_.back().GetDexFile() != method_ref.dex_file) { dex_to_dex_references_.emplace_back(*method_ref.dex_file); } - dex_to_dex_references_.back().GetMethodIndexes().SetBit(method_ref.dex_method_index); + dex_to_dex_references_.back().GetMethodIndexes().SetBit(method_ref.index); } bool CompilerDriver::CanAccessTypeWithoutChecks(ObjPtr<mirror::Class> referrer_class, @@ -1944,7 +1943,7 @@ bool CompilerDriver::FastVerify(jobject jclass_loader, if (compiler_only_verifies) { // Just update the compiled_classes_ map. The compiler doesn't need to resolve // the type. - DexFileReference ref(dex_file, i); + ClassReference ref(dex_file, i); mirror::Class::Status existing = mirror::Class::kStatusNotReady; DCHECK(compiled_classes_.Get(ref, &existing)) << ref.dex_file->GetLocation(); ClassStateTable::InsertResult result = @@ -2220,7 +2219,7 @@ void CompilerDriver::SetVerifiedDexFile(jobject class_loader, TimingLogger* timings) { TimingLogger::ScopedTiming t("Verify Dex File", timings); if (!compiled_classes_.HaveDexFile(&dex_file)) { - compiled_classes_.AddDexFile(&dex_file, dex_file.NumClassDefs()); + compiled_classes_.AddDexFile(&dex_file); } ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); ParallelCompilationManager context(class_linker, class_loader, this, &dex_file, dex_files, @@ -2877,30 +2876,36 @@ void CompilerDriver::CompileDexFile(jobject class_loader, void CompilerDriver::AddCompiledMethod(const MethodReference& method_ref, CompiledMethod* const compiled_method, size_t non_relative_linker_patch_count) { - DCHECK(GetCompiledMethod(method_ref) == nullptr) - << method_ref.dex_file->PrettyMethod(method_ref.dex_method_index); - MethodTable::InsertResult result = compiled_methods_.Insert( - DexFileReference(method_ref.dex_file, method_ref.dex_method_index), - /*expected*/ nullptr, - compiled_method); + DCHECK(GetCompiledMethod(method_ref) == nullptr) << method_ref.PrettyMethod(); + MethodTable::InsertResult result = compiled_methods_.Insert(method_ref, + /*expected*/ nullptr, + compiled_method); CHECK(result == MethodTable::kInsertResultSuccess); non_relative_linker_patch_count_.FetchAndAddRelaxed(non_relative_linker_patch_count); - DCHECK(GetCompiledMethod(method_ref) != nullptr) - << method_ref.dex_file->PrettyMethod(method_ref.dex_method_index); + DCHECK(GetCompiledMethod(method_ref) != nullptr) << method_ref.PrettyMethod(); } -bool CompilerDriver::GetCompiledClass(ClassReference ref, mirror::Class::Status* status) const { +bool CompilerDriver::GetCompiledClass(const ClassReference& ref, + mirror::Class::Status* status) const { DCHECK(status != nullptr); // The table doesn't know if something wasn't inserted. For this case it will return // kStatusNotReady. To handle this, just assume anything we didn't try to verify is not compiled. - if (!compiled_classes_.Get(DexFileReference(ref.first, ref.second), status) || + if (!compiled_classes_.Get(ref, status) || *status < mirror::Class::kStatusRetryVerificationAtRuntime) { return false; } return true; } -void CompilerDriver::RecordClassStatus(ClassReference ref, mirror::Class::Status status) { +mirror::Class::Status CompilerDriver::GetClassStatus(const ClassReference& ref) const { + mirror::Class::Status status = ClassStatus::kStatusNotReady; + if (!GetCompiledClass(ref, &status)) { + classpath_classes_.Get(ref, &status); + } + return status; +} + +void CompilerDriver::RecordClassStatus(const ClassReference& ref, mirror::Class::Status status) { switch (status) { case mirror::Class::kStatusErrorResolved: case mirror::Class::kStatusErrorUnresolved: @@ -2913,24 +2918,30 @@ void CompilerDriver::RecordClassStatus(ClassReference ref, mirror::Class::Status break; // Expected states. default: LOG(FATAL) << "Unexpected class status for class " - << PrettyDescriptor(ref.first->GetClassDescriptor(ref.first->GetClassDef(ref.second))) + << PrettyDescriptor( + ref.dex_file->GetClassDescriptor(ref.dex_file->GetClassDef(ref.index))) << " of " << status; } ClassStateTable::InsertResult result; + ClassStateTable* table = &compiled_classes_; do { - DexFileReference dex_ref(ref.first, ref.second); mirror::Class::Status existing = mirror::Class::kStatusNotReady; - if (!compiled_classes_.Get(dex_ref, &existing)) { - // Probably a uses library class, bail. + if (!table->Get(ref, &existing)) { + // A classpath class. if (kIsDebugBuild) { // Check to make sure it's not a dex file for an oat file we are compiling since these // should always succeed. These do not include classes in for used libraries. for (const DexFile* dex_file : GetDexFilesForOatFile()) { - CHECK_NE(dex_ref.dex_file, dex_file) << dex_ref.dex_file->GetLocation(); + CHECK_NE(ref.dex_file, dex_file) << ref.dex_file->GetLocation(); } } - return; + if (!classpath_classes_.HaveDexFile(ref.dex_file)) { + // Boot classpath dex file. + return; + } + table = &classpath_classes_; + table->Get(ref, &existing); } if (existing >= status) { // Existing status is already better than we expect, break. @@ -2938,14 +2949,14 @@ void CompilerDriver::RecordClassStatus(ClassReference ref, mirror::Class::Status } // Update the status if we now have a greater one. This happens with vdex, // which records a class is verified, but does not resolve it. - result = compiled_classes_.Insert(dex_ref, existing, status); - CHECK(result != ClassStateTable::kInsertResultInvalidDexFile); + result = table->Insert(ref, existing, status); + CHECK(result != ClassStateTable::kInsertResultInvalidDexFile) << ref.dex_file->GetLocation(); } while (result != ClassStateTable::kInsertResultSuccess); } CompiledMethod* CompilerDriver::GetCompiledMethod(MethodReference ref) const { CompiledMethod* compiled_method = nullptr; - compiled_methods_.Get(DexFileReference(ref.dex_file, ref.dex_method_index), &compiled_method); + compiled_methods_.Get(ref, &compiled_method); return compiled_method; } @@ -3046,11 +3057,11 @@ void CompilerDriver::FreeThreadPools() { void CompilerDriver::SetDexFilesForOatFile(const std::vector<const DexFile*>& dex_files) { dex_files_for_oat_file_ = dex_files; - for (const DexFile* dex_file : dex_files) { - if (!compiled_classes_.HaveDexFile(dex_file)) { - compiled_classes_.AddDexFile(dex_file, dex_file->NumClassDefs()); - } - } + compiled_classes_.AddDexFiles(dex_files); +} + +void CompilerDriver::SetClasspathDexFiles(const std::vector<const DexFile*>& dex_files) { + classpath_classes_.AddDexFiles(dex_files); } } // namespace art diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h index d08d9d7940..f16e2ed7d3 100644 --- a/compiler/driver/compiler_driver.h +++ b/compiler/driver/compiler_driver.h @@ -34,7 +34,6 @@ #include "dex_file.h" #include "dex_file_types.h" #include "driver/compiled_method_storage.h" -#include "invoke_type.h" #include "jit/profile_compilation_info.h" #include "method_reference.h" #include "mirror/class.h" // For mirror::Class::Status. @@ -62,6 +61,7 @@ class DexCompilationUnit; struct InlineIGetIPutData; class InstructionSetFeatures; class InternTable; +enum InvokeType : uint32_t; class ParallelCompilationManager; class ScopedObjectAccess; template <class Allocator> class SrcMap; @@ -107,6 +107,9 @@ class CompilerDriver { // Set dex files that will be stored in the oat file after being compiled. void SetDexFilesForOatFile(const std::vector<const DexFile*>& dex_files); + // Set dex files classpath. + void SetClasspathDexFiles(const std::vector<const DexFile*>& dex_files); + // Get dex file that will be stored in the oat file after being compiled. ArrayRef<const DexFile* const> GetDexFilesForOatFile() const { return ArrayRef<const DexFile* const>(dex_files_for_oat_file_); @@ -151,7 +154,8 @@ class CompilerDriver { std::unique_ptr<const std::vector<uint8_t>> CreateQuickResolutionTrampoline() const; std::unique_ptr<const std::vector<uint8_t>> CreateQuickToInterpreterBridge() const; - bool GetCompiledClass(ClassReference ref, mirror::Class::Status* status) const; + mirror::Class::Status GetClassStatus(const ClassReference& ref) const; + bool GetCompiledClass(const ClassReference& ref, mirror::Class::Status* status) const; CompiledMethod* GetCompiledMethod(MethodReference ref) const; size_t GetNonRelativeLinkerPatchCount() const; @@ -334,7 +338,7 @@ class CompilerDriver { // according to the profile file. bool ShouldVerifyClassBasedOnProfile(const DexFile& dex_file, uint16_t class_idx) const; - void RecordClassStatus(ClassReference ref, mirror::Class::Status status); + void RecordClassStatus(const ClassReference& ref, mirror::Class::Status status); // Checks if the specified method has been verified without failures. Returns // false if the method is not in the verification results (GetVerificationResults). @@ -486,10 +490,12 @@ class CompilerDriver { GUARDED_BY(requires_constructor_barrier_lock_); // All class references that this compiler has compiled. Indexed by class defs. - using ClassStateTable = AtomicDexRefMap<mirror::Class::Status>; + using ClassStateTable = AtomicDexRefMap<ClassReference, mirror::Class::Status>; ClassStateTable compiled_classes_; + // All class references that are in the classpath. Indexed by class defs. + ClassStateTable classpath_classes_; - typedef AtomicDexRefMap<CompiledMethod*> MethodTable; + typedef AtomicDexRefMap<MethodReference, CompiledMethod*> MethodTable; private: // All method references that this compiler has compiled. diff --git a/compiler/jni/jni_cfi_test.cc b/compiler/jni/jni_cfi_test.cc index b552a6e531..347f4ea9d4 100644 --- a/compiler/jni/jni_cfi_test.cc +++ b/compiler/jni/jni_cfi_test.cc @@ -23,6 +23,7 @@ #include "cfi_test.h" #include "gtest/gtest.h" #include "jni/quick/calling_convention.h" +#include "read_barrier_config.h" #include "utils/assembler.h" #include "utils/jni_macro_assembler.h" diff --git a/compiler/jni/quick/arm/calling_convention_arm.cc b/compiler/jni/quick/arm/calling_convention_arm.cc index 7e1ad9fd7b..3e637bcf43 100644 --- a/compiler/jni/quick/arm/calling_convention_arm.cc +++ b/compiler/jni/quick/arm/calling_convention_arm.cc @@ -125,18 +125,7 @@ ManagedRegister ArmJniCallingConvention::InterproceduralScratchRegister() { } ManagedRegister ArmManagedRuntimeCallingConvention::ReturnRegister() { - if (kArm32QuickCodeUseSoftFloat) { - switch (GetShorty()[0]) { - case 'V': - return ArmManagedRegister::NoRegister(); - case 'D': - case 'J': - return ArmManagedRegister::FromRegisterPair(R0_R1); - default: - return ArmManagedRegister::FromCoreRegister(R0); - } - } else { - switch (GetShorty()[0]) { + switch (GetShorty()[0]) { case 'V': return ArmManagedRegister::NoRegister(); case 'D': @@ -147,7 +136,6 @@ ManagedRegister ArmManagedRuntimeCallingConvention::ReturnRegister() { return ArmManagedRegister::FromRegisterPair(R0_R1); default: return ArmManagedRegister::FromCoreRegister(R0); - } } } @@ -198,80 +186,65 @@ FrameOffset ArmManagedRuntimeCallingConvention::CurrentParamStackOffset() { const ManagedRegisterEntrySpills& ArmManagedRuntimeCallingConvention::EntrySpills() { // We spill the argument registers on ARM to free them up for scratch use, we then assume // all arguments are on the stack. - if (kArm32QuickCodeUseSoftFloat) { - if (entry_spills_.size() == 0) { - size_t num_spills = NumArgs() + NumLongOrDoubleArgs(); - if (num_spills > 0) { - entry_spills_.push_back(ArmManagedRegister::FromCoreRegister(R1)); - if (num_spills > 1) { - entry_spills_.push_back(ArmManagedRegister::FromCoreRegister(R2)); - if (num_spills > 2) { - entry_spills_.push_back(ArmManagedRegister::FromCoreRegister(R3)); + if ((entry_spills_.size() == 0) && (NumArgs() > 0)) { + uint32_t gpr_index = 1; // R0 ~ R3. Reserve r0 for ArtMethod*. + uint32_t fpr_index = 0; // S0 ~ S15. + uint32_t fpr_double_index = 0; // D0 ~ D7. + + ResetIterator(FrameOffset(0)); + while (HasNext()) { + if (IsCurrentParamAFloatOrDouble()) { + if (IsCurrentParamADouble()) { // Double. + // Double should not overlap with float. + fpr_double_index = (std::max(fpr_double_index * 2, RoundUp(fpr_index, 2))) / 2; + if (fpr_double_index < arraysize(kHFDArgumentRegisters)) { + entry_spills_.push_back( + ArmManagedRegister::FromDRegister(kHFDArgumentRegisters[fpr_double_index++])); + } else { + entry_spills_.push_back(ManagedRegister::NoRegister(), 8); } - } - } - } - } else { - if ((entry_spills_.size() == 0) && (NumArgs() > 0)) { - uint32_t gpr_index = 1; // R0 ~ R3. Reserve r0 for ArtMethod*. - uint32_t fpr_index = 0; // S0 ~ S15. - uint32_t fpr_double_index = 0; // D0 ~ D7. - - ResetIterator(FrameOffset(0)); - while (HasNext()) { - if (IsCurrentParamAFloatOrDouble()) { - if (IsCurrentParamADouble()) { // Double. - // Double should not overlap with float. - fpr_double_index = (std::max(fpr_double_index * 2, RoundUp(fpr_index, 2))) / 2; - if (fpr_double_index < arraysize(kHFDArgumentRegisters)) { - entry_spills_.push_back( - ArmManagedRegister::FromDRegister(kHFDArgumentRegisters[fpr_double_index++])); - } else { - entry_spills_.push_back(ManagedRegister::NoRegister(), 8); - } - } else { // Float. - // Float should not overlap with double. - if (fpr_index % 2 == 0) { - fpr_index = std::max(fpr_double_index * 2, fpr_index); - } - if (fpr_index < arraysize(kHFSArgumentRegisters)) { - entry_spills_.push_back( - ArmManagedRegister::FromSRegister(kHFSArgumentRegisters[fpr_index++])); - } else { - entry_spills_.push_back(ManagedRegister::NoRegister(), 4); - } + } else { // Float. + // Float should not overlap with double. + if (fpr_index % 2 == 0) { + fpr_index = std::max(fpr_double_index * 2, fpr_index); } - } else { - // FIXME: Pointer this returns as both reference and long. - if (IsCurrentParamALong() && !IsCurrentParamAReference()) { // Long. - if (gpr_index < arraysize(kHFCoreArgumentRegisters) - 1) { - // Skip R1, and use R2_R3 if the long is the first parameter. - if (gpr_index == 1) { - gpr_index++; - } - } - - // If it spans register and memory, we must use the value in memory. - if (gpr_index < arraysize(kHFCoreArgumentRegisters) - 1) { - entry_spills_.push_back( - ArmManagedRegister::FromCoreRegister(kHFCoreArgumentRegisters[gpr_index++])); - } else if (gpr_index == arraysize(kHFCoreArgumentRegisters) - 1) { + if (fpr_index < arraysize(kHFSArgumentRegisters)) { + entry_spills_.push_back( + ArmManagedRegister::FromSRegister(kHFSArgumentRegisters[fpr_index++])); + } else { + entry_spills_.push_back(ManagedRegister::NoRegister(), 4); + } + } + } else { + // FIXME: Pointer this returns as both reference and long. + if (IsCurrentParamALong() && !IsCurrentParamAReference()) { // Long. + if (gpr_index < arraysize(kHFCoreArgumentRegisters) - 1) { + // Skip R1, and use R2_R3 if the long is the first parameter. + if (gpr_index == 1) { gpr_index++; - entry_spills_.push_back(ManagedRegister::NoRegister(), 4); - } else { - entry_spills_.push_back(ManagedRegister::NoRegister(), 4); } } - // High part of long or 32-bit argument. - if (gpr_index < arraysize(kHFCoreArgumentRegisters)) { + + // If it spans register and memory, we must use the value in memory. + if (gpr_index < arraysize(kHFCoreArgumentRegisters) - 1) { entry_spills_.push_back( ArmManagedRegister::FromCoreRegister(kHFCoreArgumentRegisters[gpr_index++])); + } else if (gpr_index == arraysize(kHFCoreArgumentRegisters) - 1) { + gpr_index++; + entry_spills_.push_back(ManagedRegister::NoRegister(), 4); } else { entry_spills_.push_back(ManagedRegister::NoRegister(), 4); } } - Next(); + // High part of long or 32-bit argument. + if (gpr_index < arraysize(kHFCoreArgumentRegisters)) { + entry_spills_.push_back( + ArmManagedRegister::FromCoreRegister(kHFCoreArgumentRegisters[gpr_index++])); + } else { + entry_spills_.push_back(ManagedRegister::NoRegister(), 4); + } } + Next(); } } return entry_spills_; diff --git a/compiler/linker/arm/relative_patcher_arm_base.cc b/compiler/linker/arm/relative_patcher_arm_base.cc index 4ca5afe177..cb6522cbbb 100644 --- a/compiler/linker/arm/relative_patcher_arm_base.cc +++ b/compiler/linker/arm/relative_patcher_arm_base.cc @@ -18,6 +18,8 @@ #include "base/stl_util.h" #include "compiled_method.h" +#include "debug/method_debug_info.h" +#include "dex_file_types.h" #include "linker/output_stream.h" #include "oat.h" #include "oat_quick_method_header.h" @@ -119,6 +121,25 @@ class ArmBaseRelativePatcher::ThunkData { return offsets_[pending_offset_ - 1u]; } + size_t IndexOfFirstThunkAtOrAfter(uint32_t offset) const { + size_t number_of_thunks = NumberOfThunks(); + for (size_t i = 0; i != number_of_thunks; ++i) { + if (GetThunkOffset(i) >= offset) { + return i; + } + } + return number_of_thunks; + } + + size_t NumberOfThunks() const { + return offsets_.size(); + } + + uint32_t GetThunkOffset(size_t index) const { + DCHECK_LT(index, NumberOfThunks()); + return offsets_[index]; + } + private: std::vector<uint8_t> code_; // The code of the thunk. std::vector<uint32_t> offsets_; // Offsets at which the thunk needs to be written. @@ -149,7 +170,7 @@ uint32_t ArmBaseRelativePatcher::ReserveSpaceEnd(uint32_t offset) { // to place thunk will be soon enough, we need to reserve all needed thunks now. Code for // subsequent oat files can still call back to them. if (!unprocessed_method_call_patches_.empty()) { - ResolveMethodCalls(offset, MethodReference(nullptr, DexFile::kDexNoIndex)); + ResolveMethodCalls(offset, MethodReference(nullptr, dex::kDexNoIndex)); } for (ThunkData* data : unreserved_thunks_) { uint32_t thunk_offset = CompiledCode::AlignCode(offset, instruction_set_); @@ -203,6 +224,48 @@ uint32_t ArmBaseRelativePatcher::WriteThunks(OutputStream* out, uint32_t offset) return offset; } +std::vector<debug::MethodDebugInfo> ArmBaseRelativePatcher::GenerateThunkDebugInfo( + uint32_t executable_offset) { + // For multi-oat compilation (boot image), `thunks_` records thunks for all oat files. + // To return debug info for the current oat file, we must ignore thunks before the + // `executable_offset` as they are in the previous oat files and this function must be + // called before reserving thunk positions for subsequent oat files. + size_t number_of_thunks = 0u; + for (auto&& entry : thunks_) { + const ThunkData& data = entry.second; + number_of_thunks += data.NumberOfThunks() - data.IndexOfFirstThunkAtOrAfter(executable_offset); + } + std::vector<debug::MethodDebugInfo> result; + result.reserve(number_of_thunks); + for (auto&& entry : thunks_) { + const ThunkKey& key = entry.first; + const ThunkData& data = entry.second; + size_t start = data.IndexOfFirstThunkAtOrAfter(executable_offset); + if (start == data.NumberOfThunks()) { + continue; + } + // Get the base name to use for the first occurrence of the thunk. + std::string base_name = GetThunkDebugName(key); + for (size_t i = start, num = data.NumberOfThunks(); i != num; ++i) { + debug::MethodDebugInfo info = {}; + if (i == 0u) { + info.trampoline_name = base_name; + } else { + // Add a disambiguating tag for subsequent identical thunks. Since the `thunks_` + // keeps records also for thunks in previous oat files, names based on the thunk + // index shall be unique across the whole multi-oat output. + info.trampoline_name = base_name + "_" + std::to_string(i); + } + info.isa = instruction_set_; + info.is_code_address_text_relative = true; + info.code_address = data.GetThunkOffset(i) - executable_offset; + info.code_size = data.CodeSize(); + result.push_back(std::move(info)); + } + } + return result; +} + ArmBaseRelativePatcher::ArmBaseRelativePatcher(RelativePatcherTargetProvider* provider, InstructionSet instruction_set) : provider_(provider), @@ -407,8 +470,7 @@ void ArmBaseRelativePatcher::ResolveMethodCalls(uint32_t quick_code_offset, if (!method_call_thunk_->HasReservedOffset() || patch_offset - method_call_thunk_->LastReservedOffset() > max_negative_displacement) { // No previous thunk in range, check if we can reach the target directly. - if (target_method.dex_file == method_ref.dex_file && - target_method.dex_method_index == method_ref.dex_method_index) { + if (target_method == method_ref) { DCHECK_GT(quick_code_offset, patch_offset); if (quick_code_offset - patch_offset > max_positive_displacement) { break; diff --git a/compiler/linker/arm/relative_patcher_arm_base.h b/compiler/linker/arm/relative_patcher_arm_base.h index 5197ce2549..4c2be1e1a7 100644 --- a/compiler/linker/arm/relative_patcher_arm_base.h +++ b/compiler/linker/arm/relative_patcher_arm_base.h @@ -34,6 +34,7 @@ class ArmBaseRelativePatcher : public RelativePatcher { MethodReference method_ref) OVERRIDE; uint32_t ReserveSpaceEnd(uint32_t offset) OVERRIDE; uint32_t WriteThunks(OutputStream* out, uint32_t offset) OVERRIDE; + std::vector<debug::MethodDebugInfo> GenerateThunkDebugInfo(uint32_t executable_offset) OVERRIDE; protected: ArmBaseRelativePatcher(RelativePatcherTargetProvider* provider, @@ -94,6 +95,7 @@ class ArmBaseRelativePatcher : public RelativePatcher { uint32_t target_offset); virtual std::vector<uint8_t> CompileThunk(const ThunkKey& key) = 0; + virtual std::string GetThunkDebugName(const ThunkKey& key) = 0; virtual uint32_t MaxPositiveDisplacement(const ThunkKey& key) = 0; virtual uint32_t MaxNegativeDisplacement(const ThunkKey& key) = 0; diff --git a/compiler/linker/arm/relative_patcher_thumb2.cc b/compiler/linker/arm/relative_patcher_thumb2.cc index 2ac2a1d2fc..704feeb387 100644 --- a/compiler/linker/arm/relative_patcher_thumb2.cc +++ b/compiler/linker/arm/relative_patcher_thumb2.cc @@ -281,7 +281,7 @@ void Thumb2RelativePatcher::CompileBakerReadBarrierThunk(arm::ArmVIXLAssembler& Register base_reg(BakerReadBarrierFirstRegField::Decode(encoded_data)); CheckValidReg(base_reg.GetCode()); DCHECK_EQ(kInvalidEncodedReg, BakerReadBarrierSecondRegField::Decode(encoded_data)); - DCHECK(BakerReadBarrierWidth::kWide == BakerReadBarrierWidthField::Decode(encoded_data)); + DCHECK(BakerReadBarrierWidthField::Decode(encoded_data) == BakerReadBarrierWidth::kWide); UseScratchRegisterScope temps(assembler.GetVIXLAssembler()); temps.Exclude(ip); vixl::aarch32::Label slow_path; @@ -379,6 +379,44 @@ std::vector<uint8_t> Thumb2RelativePatcher::CompileThunk(const ThunkKey& key) { return thunk_code; } +std::string Thumb2RelativePatcher::GetThunkDebugName(const ThunkKey& key) { + switch (key.GetType()) { + case ThunkType::kMethodCall: + return "MethodCallThunk"; + + case ThunkType::kBakerReadBarrier: { + uint32_t encoded_data = key.GetCustomValue1(); + BakerReadBarrierKind kind = BakerReadBarrierKindField::Decode(encoded_data); + std::ostringstream oss; + oss << "BakerReadBarrierThunk"; + switch (kind) { + case BakerReadBarrierKind::kField: + oss << "Field"; + if (BakerReadBarrierWidthField::Decode(encoded_data) == BakerReadBarrierWidth::kWide) { + oss << "Wide"; + } + oss << "_r" << BakerReadBarrierFirstRegField::Decode(encoded_data) + << "_r" << BakerReadBarrierSecondRegField::Decode(encoded_data); + break; + case BakerReadBarrierKind::kArray: + oss << "Array_r" << BakerReadBarrierFirstRegField::Decode(encoded_data); + DCHECK_EQ(kInvalidEncodedReg, BakerReadBarrierSecondRegField::Decode(encoded_data)); + DCHECK(BakerReadBarrierWidthField::Decode(encoded_data) == BakerReadBarrierWidth::kWide); + break; + case BakerReadBarrierKind::kGcRoot: + oss << "GcRoot"; + if (BakerReadBarrierWidthField::Decode(encoded_data) == BakerReadBarrierWidth::kWide) { + oss << "Wide"; + } + oss << "_r" << BakerReadBarrierFirstRegField::Decode(encoded_data); + DCHECK_EQ(kInvalidEncodedReg, BakerReadBarrierSecondRegField::Decode(encoded_data)); + break; + } + return oss.str(); + } + } +} + #undef __ uint32_t Thumb2RelativePatcher::MaxPositiveDisplacement(const ThunkKey& key) { diff --git a/compiler/linker/arm/relative_patcher_thumb2.h b/compiler/linker/arm/relative_patcher_thumb2.h index 183e5e6c96..68386c00f4 100644 --- a/compiler/linker/arm/relative_patcher_thumb2.h +++ b/compiler/linker/arm/relative_patcher_thumb2.h @@ -84,6 +84,7 @@ class Thumb2RelativePatcher FINAL : public ArmBaseRelativePatcher { protected: std::vector<uint8_t> CompileThunk(const ThunkKey& key) OVERRIDE; + std::string GetThunkDebugName(const ThunkKey& key) OVERRIDE; uint32_t MaxPositiveDisplacement(const ThunkKey& key) OVERRIDE; uint32_t MaxNegativeDisplacement(const ThunkKey& key) OVERRIDE; diff --git a/compiler/linker/arm64/relative_patcher_arm64.cc b/compiler/linker/arm64/relative_patcher_arm64.cc index 4960f4d856..270ba3c08d 100644 --- a/compiler/linker/arm64/relative_patcher_arm64.cc +++ b/compiler/linker/arm64/relative_patcher_arm64.cc @@ -535,6 +535,35 @@ std::vector<uint8_t> Arm64RelativePatcher::CompileThunk(const ThunkKey& key) { return thunk_code; } +std::string Arm64RelativePatcher::GetThunkDebugName(const ThunkKey& key) { + switch (key.GetType()) { + case ThunkType::kMethodCall: + return "MethodCallThunk"; + + case ThunkType::kBakerReadBarrier: { + uint32_t encoded_data = key.GetCustomValue1(); + BakerReadBarrierKind kind = BakerReadBarrierKindField::Decode(encoded_data); + std::ostringstream oss; + oss << "BakerReadBarrierThunk"; + switch (kind) { + case BakerReadBarrierKind::kField: + oss << "Field_r" << BakerReadBarrierFirstRegField::Decode(encoded_data) + << "_r" << BakerReadBarrierSecondRegField::Decode(encoded_data); + break; + case BakerReadBarrierKind::kArray: + oss << "Array_r" << BakerReadBarrierFirstRegField::Decode(encoded_data); + DCHECK_EQ(kInvalidEncodedReg, BakerReadBarrierSecondRegField::Decode(encoded_data)); + break; + case BakerReadBarrierKind::kGcRoot: + oss << "GcRoot_r" << BakerReadBarrierFirstRegField::Decode(encoded_data); + DCHECK_EQ(kInvalidEncodedReg, BakerReadBarrierSecondRegField::Decode(encoded_data)); + break; + } + return oss.str(); + } + } +} + #undef __ uint32_t Arm64RelativePatcher::MaxPositiveDisplacement(const ThunkKey& key) { diff --git a/compiler/linker/arm64/relative_patcher_arm64.h b/compiler/linker/arm64/relative_patcher_arm64.h index b00dd081b6..8ba59976e7 100644 --- a/compiler/linker/arm64/relative_patcher_arm64.h +++ b/compiler/linker/arm64/relative_patcher_arm64.h @@ -76,6 +76,7 @@ class Arm64RelativePatcher FINAL : public ArmBaseRelativePatcher { protected: std::vector<uint8_t> CompileThunk(const ThunkKey& key) OVERRIDE; + std::string GetThunkDebugName(const ThunkKey& key) OVERRIDE; uint32_t MaxPositiveDisplacement(const ThunkKey& key) OVERRIDE; uint32_t MaxNegativeDisplacement(const ThunkKey& key) OVERRIDE; diff --git a/compiler/linker/arm64/relative_patcher_arm64_test.cc b/compiler/linker/arm64/relative_patcher_arm64_test.cc index d6919e9417..8a5b4cc8e5 100644 --- a/compiler/linker/arm64/relative_patcher_arm64_test.cc +++ b/compiler/linker/arm64/relative_patcher_arm64_test.cc @@ -344,7 +344,7 @@ class Arm64RelativePatcherTest : public RelativePatcherTest { uint32_t use_insn) { uint32_t method1_offset = GetMethodOffset(1u); CHECK(!compiled_method_refs_.empty()); - CHECK_EQ(compiled_method_refs_[0].dex_method_index, 1u); + CHECK_EQ(compiled_method_refs_[0].index, 1u); CHECK_EQ(compiled_method_refs_.size(), compiled_methods_.size()); uint32_t method1_size = compiled_methods_[0]->GetQuickCode().size(); uint32_t thunk_offset = CompiledCode::AlignCode(method1_offset + method1_size, kArm64); diff --git a/compiler/linker/mips/relative_patcher_mips.cc b/compiler/linker/mips/relative_patcher_mips.cc index 6c974c308f..408ac22976 100644 --- a/compiler/linker/mips/relative_patcher_mips.cc +++ b/compiler/linker/mips/relative_patcher_mips.cc @@ -17,6 +17,7 @@ #include "linker/mips/relative_patcher_mips.h" #include "compiled_method.h" +#include "debug/method_debug_info.h" namespace art { namespace linker { @@ -90,5 +91,10 @@ void MipsRelativePatcher::PatchBakerReadBarrierBranch(std::vector<uint8_t>* code LOG(FATAL) << "UNIMPLEMENTED"; } +std::vector<debug::MethodDebugInfo> MipsRelativePatcher::GenerateThunkDebugInfo( + uint32_t executable_offset ATTRIBUTE_UNUSED) { + return std::vector<debug::MethodDebugInfo>(); // No thunks added. +} + } // namespace linker } // namespace art diff --git a/compiler/linker/mips/relative_patcher_mips.h b/compiler/linker/mips/relative_patcher_mips.h index d6eda34592..5714a7d1b0 100644 --- a/compiler/linker/mips/relative_patcher_mips.h +++ b/compiler/linker/mips/relative_patcher_mips.h @@ -44,6 +44,7 @@ class MipsRelativePatcher FINAL : public RelativePatcher { void PatchBakerReadBarrierBranch(std::vector<uint8_t>* code, const LinkerPatch& patch, uint32_t patch_offset) OVERRIDE; + std::vector<debug::MethodDebugInfo> GenerateThunkDebugInfo(uint32_t executable_offset) OVERRIDE; private: bool is_r6; diff --git a/compiler/linker/mips64/relative_patcher_mips64.cc b/compiler/linker/mips64/relative_patcher_mips64.cc index d9f4758eb1..2bcd98a2b0 100644 --- a/compiler/linker/mips64/relative_patcher_mips64.cc +++ b/compiler/linker/mips64/relative_patcher_mips64.cc @@ -17,6 +17,7 @@ #include "linker/mips64/relative_patcher_mips64.h" #include "compiled_method.h" +#include "debug/method_debug_info.h" namespace art { namespace linker { @@ -88,5 +89,10 @@ void Mips64RelativePatcher::PatchBakerReadBarrierBranch(std::vector<uint8_t>* co LOG(FATAL) << "UNIMPLEMENTED"; } +std::vector<debug::MethodDebugInfo> Mips64RelativePatcher::GenerateThunkDebugInfo( + uint32_t executable_offset ATTRIBUTE_UNUSED) { + return std::vector<debug::MethodDebugInfo>(); // No thunks added. +} + } // namespace linker } // namespace art diff --git a/compiler/linker/mips64/relative_patcher_mips64.h b/compiler/linker/mips64/relative_patcher_mips64.h index f478d7f2ef..6d7c638312 100644 --- a/compiler/linker/mips64/relative_patcher_mips64.h +++ b/compiler/linker/mips64/relative_patcher_mips64.h @@ -42,6 +42,7 @@ class Mips64RelativePatcher FINAL : public RelativePatcher { void PatchBakerReadBarrierBranch(std::vector<uint8_t>* code, const LinkerPatch& patch, uint32_t patch_offset) OVERRIDE; + std::vector<debug::MethodDebugInfo> GenerateThunkDebugInfo(uint32_t executable_offset) OVERRIDE; private: DISALLOW_COPY_AND_ASSIGN(Mips64RelativePatcher); diff --git a/compiler/linker/multi_oat_relative_patcher.h b/compiler/linker/multi_oat_relative_patcher.h index bdc1ee1d27..02cd4b0118 100644 --- a/compiler/linker/multi_oat_relative_patcher.h +++ b/compiler/linker/multi_oat_relative_patcher.h @@ -18,6 +18,7 @@ #define ART_COMPILER_LINKER_MULTI_OAT_RELATIVE_PATCHER_H_ #include "arch/instruction_set.h" +#include "debug/method_debug_info.h" #include "method_reference.h" #include "relative_patcher.h" #include "safe_map.h" @@ -36,8 +37,7 @@ namespace linker { // to the value set by SetAdjustment(). class MultiOatRelativePatcher FINAL { public: - using const_iterator = - SafeMap<MethodReference, uint32_t, MethodReferenceComparator>::const_iterator; + using const_iterator = SafeMap<MethodReference, uint32_t>::const_iterator; MultiOatRelativePatcher(InstructionSet instruction_set, const InstructionSetFeatures* features); @@ -119,6 +119,11 @@ class MultiOatRelativePatcher FINAL { relative_patcher_->PatchBakerReadBarrierBranch(code, patch, patch_offset); } + std::vector<debug::MethodDebugInfo> GenerateThunkDebugInfo(size_t executable_offset) { + executable_offset += adjustment_; + return relative_patcher_->GenerateThunkDebugInfo(executable_offset); + } + // Wrappers around RelativePatcher for statistics retrieval. uint32_t CodeAlignmentSize() const; uint32_t RelativeCallThunksSize() const; @@ -130,7 +135,7 @@ class MultiOatRelativePatcher FINAL { class MethodOffsetMap : public linker::RelativePatcherTargetProvider { public: std::pair<bool, uint32_t> FindMethodOffset(MethodReference ref) OVERRIDE; - SafeMap<MethodReference, uint32_t, MethodReferenceComparator> map; + SafeMap<MethodReference, uint32_t> map; }; MethodOffsetMap method_offset_map_; diff --git a/compiler/linker/multi_oat_relative_patcher_test.cc b/compiler/linker/multi_oat_relative_patcher_test.cc index e96790115a..5c359dc9ca 100644 --- a/compiler/linker/multi_oat_relative_patcher_test.cc +++ b/compiler/linker/multi_oat_relative_patcher_test.cc @@ -17,6 +17,7 @@ #include "multi_oat_relative_patcher.h" #include "compiled_method.h" +#include "debug/method_debug_info.h" #include "gtest/gtest.h" #include "vector_output_stream.h" @@ -25,10 +26,6 @@ namespace linker { static const MethodReference kNullMethodRef = MethodReference(nullptr, 0u); -static bool EqualRef(MethodReference lhs, MethodReference rhs) { - return lhs.dex_file == rhs.dex_file && lhs.dex_method_index == rhs.dex_method_index; -} - class MultiOatRelativePatcherTest : public testing::Test { protected: class MockPatcher : public RelativePatcher { @@ -102,6 +99,12 @@ class MultiOatRelativePatcherTest : public testing::Test { LOG(FATAL) << "UNIMPLEMENTED"; } + std::vector<debug::MethodDebugInfo> GenerateThunkDebugInfo( + uint32_t executable_offset ATTRIBUTE_UNUSED) { + LOG(FATAL) << "UNIMPLEMENTED"; + UNREACHABLE(); + } + uint32_t last_reserve_offset_ = 0u; MethodReference last_reserve_method_ = kNullMethodRef; uint32_t next_reserve_adjustment_ = 0u; @@ -175,7 +178,7 @@ TEST_F(MultiOatRelativePatcherTest, OffsetsInReserve) { uint32_t method1_offset = 0x100; uint32_t method1_offset_check = patcher_.ReserveSpace(method1_offset, method, ref1); ASSERT_EQ(adjustment1 + method1_offset, mock_->last_reserve_offset_); - ASSERT_TRUE(EqualRef(ref1, mock_->last_reserve_method_)); + ASSERT_TRUE(ref1 == mock_->last_reserve_method_); ASSERT_EQ(method1_offset, method1_offset_check); uint32_t method2_offset = 0x1230; @@ -183,13 +186,13 @@ TEST_F(MultiOatRelativePatcherTest, OffsetsInReserve) { mock_->next_reserve_adjustment_ = method2_reserve_adjustment; uint32_t method2_offset_adjusted = patcher_.ReserveSpace(method2_offset, method, ref2); ASSERT_EQ(adjustment1 + method2_offset, mock_->last_reserve_offset_); - ASSERT_TRUE(EqualRef(ref2, mock_->last_reserve_method_)); + ASSERT_TRUE(ref2 == mock_->last_reserve_method_); ASSERT_EQ(method2_offset + method2_reserve_adjustment, method2_offset_adjusted); uint32_t end1_offset = 0x4320; uint32_t end1_offset_check = patcher_.ReserveSpaceEnd(end1_offset); ASSERT_EQ(adjustment1 + end1_offset, mock_->last_reserve_offset_); - ASSERT_TRUE(EqualRef(kNullMethodRef, mock_->last_reserve_method_)); + ASSERT_TRUE(kNullMethodRef == mock_->last_reserve_method_); ASSERT_EQ(end1_offset, end1_offset_check); uint32_t adjustment2 = 0xd000; @@ -198,7 +201,7 @@ TEST_F(MultiOatRelativePatcherTest, OffsetsInReserve) { uint32_t method3_offset = 0xf00; uint32_t method3_offset_check = patcher_.ReserveSpace(method3_offset, method, ref3); ASSERT_EQ(adjustment2 + method3_offset, mock_->last_reserve_offset_); - ASSERT_TRUE(EqualRef(ref3, mock_->last_reserve_method_)); + ASSERT_TRUE(ref3 == mock_->last_reserve_method_); ASSERT_EQ(method3_offset, method3_offset_check); uint32_t end2_offset = 0x2400; @@ -206,7 +209,7 @@ TEST_F(MultiOatRelativePatcherTest, OffsetsInReserve) { mock_->next_reserve_adjustment_ = end2_reserve_adjustment; uint32_t end2_offset_adjusted = patcher_.ReserveSpaceEnd(end2_offset); ASSERT_EQ(adjustment2 + end2_offset, mock_->last_reserve_offset_); - ASSERT_TRUE(EqualRef(kNullMethodRef, mock_->last_reserve_method_)); + ASSERT_TRUE(kNullMethodRef == mock_->last_reserve_method_); ASSERT_EQ(end2_offset + end2_reserve_adjustment, end2_offset_adjusted); } diff --git a/compiler/linker/relative_patcher.cc b/compiler/linker/relative_patcher.cc index ee49453938..dc15bb087e 100644 --- a/compiler/linker/relative_patcher.cc +++ b/compiler/linker/relative_patcher.cc @@ -16,6 +16,7 @@ #include "linker/relative_patcher.h" +#include "debug/method_debug_info.h" #ifdef ART_ENABLE_CODEGEN_arm #include "linker/arm/relative_patcher_thumb2.h" #endif @@ -81,6 +82,11 @@ std::unique_ptr<RelativePatcher> RelativePatcher::Create( LOG(FATAL) << "Unexpected baker read barrier branch patch."; } + std::vector<debug::MethodDebugInfo> GenerateThunkDebugInfo( + uint32_t executable_offset ATTRIBUTE_UNUSED) OVERRIDE { + return std::vector<debug::MethodDebugInfo>(); // No thunks added. + } + private: DISALLOW_COPY_AND_ASSIGN(RelativePatcherNone); }; diff --git a/compiler/linker/relative_patcher.h b/compiler/linker/relative_patcher.h index 38c8228422..53a096627f 100644 --- a/compiler/linker/relative_patcher.h +++ b/compiler/linker/relative_patcher.h @@ -31,6 +31,10 @@ class CompiledMethod; class LinkerPatch; class OutputStream; +namespace debug { +struct MethodDebugInfo; +} // namespace debug + namespace linker { /** @@ -114,6 +118,9 @@ class RelativePatcher { const LinkerPatch& patch, uint32_t patch_offset) = 0; + virtual std::vector<debug::MethodDebugInfo> GenerateThunkDebugInfo( + uint32_t executable_offset) = 0; + protected: RelativePatcher() : size_code_alignment_(0u), diff --git a/compiler/linker/relative_patcher_test.h b/compiler/linker/relative_patcher_test.h index bff68080c6..ca8743a561 100644 --- a/compiler/linker/relative_patcher_test.h +++ b/compiler/linker/relative_patcher_test.h @@ -194,8 +194,7 @@ class RelativePatcherTest : public testing::Test { // Sanity check: original code size must match linked_code.size(). size_t idx = 0u; for (auto ref : compiled_method_refs_) { - if (ref.dex_file == method_ref.dex_file && - ref.dex_method_index == method_ref.dex_method_index) { + if (ref == method_ref) { break; } ++idx; @@ -264,7 +263,7 @@ class RelativePatcherTest : public testing::Test { return std::pair<bool, uint32_t>(true, it->second); } } - SafeMap<MethodReference, uint32_t, MethodReferenceComparator> map; + SafeMap<MethodReference, uint32_t> map; }; static const uint32_t kTrampolineSize = 4u; diff --git a/compiler/linker/x86/relative_patcher_x86_base.cc b/compiler/linker/x86/relative_patcher_x86_base.cc index bf3a648218..6a9690d768 100644 --- a/compiler/linker/x86/relative_patcher_x86_base.cc +++ b/compiler/linker/x86/relative_patcher_x86_base.cc @@ -16,6 +16,8 @@ #include "linker/x86/relative_patcher_x86_base.h" +#include "debug/method_debug_info.h" + namespace art { namespace linker { @@ -34,6 +36,11 @@ uint32_t X86BaseRelativePatcher::WriteThunks(OutputStream* out ATTRIBUTE_UNUSED, return offset; // No thunks added; no limit on relative call distance. } +std::vector<debug::MethodDebugInfo> X86BaseRelativePatcher::GenerateThunkDebugInfo( + uint32_t executable_offset ATTRIBUTE_UNUSED) { + return std::vector<debug::MethodDebugInfo>(); // No thunks added. +} + void X86BaseRelativePatcher::PatchCall(std::vector<uint8_t>* code, uint32_t literal_offset, uint32_t patch_offset, diff --git a/compiler/linker/x86/relative_patcher_x86_base.h b/compiler/linker/x86/relative_patcher_x86_base.h index ca83a72f48..6097345657 100644 --- a/compiler/linker/x86/relative_patcher_x86_base.h +++ b/compiler/linker/x86/relative_patcher_x86_base.h @@ -33,6 +33,7 @@ class X86BaseRelativePatcher : public RelativePatcher { uint32_t literal_offset, uint32_t patch_offset, uint32_t target_offset) OVERRIDE; + std::vector<debug::MethodDebugInfo> GenerateThunkDebugInfo(uint32_t executable_offset) OVERRIDE; protected: X86BaseRelativePatcher() { } diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc index cc8c6dfac0..ce1b755a9d 100644 --- a/compiler/oat_writer.cc +++ b/compiler/oat_writer.cc @@ -33,6 +33,7 @@ #include "debug/method_debug_info.h" #include "dex/verification_results.h" #include "dex_file-inl.h" +#include "dex_file_types.h" #include "dexlayout.h" #include "driver/compiler_driver-inl.h" #include "driver/compiler_options.h" @@ -667,11 +668,11 @@ class OatWriter::DexMethodVisitor { : writer_(writer), offset_(offset), dex_file_(nullptr), - class_def_index_(DexFile::kDexNoIndex) {} + class_def_index_(dex::kDexNoIndex) {} virtual bool StartClass(const DexFile* dex_file, size_t class_def_index) { DCHECK(dex_file_ == nullptr); - DCHECK_EQ(class_def_index_, DexFile::kDexNoIndex); + DCHECK_EQ(class_def_index_, dex::kDexNoIndex); dex_file_ = dex_file; class_def_index_ = class_def_index; return true; @@ -682,7 +683,7 @@ class OatWriter::DexMethodVisitor { virtual bool EndClass() { if (kIsDebugBuild) { dex_file_ = nullptr; - class_def_index_ = DexFile::kDexNoIndex; + class_def_index_ = dex::kDexNoIndex; } return true; } @@ -765,7 +766,7 @@ class OatWriter::InitBssLayoutMethodVisitor : public DexMethodVisitor { Allocator::GetMallocAllocator())); refs_it->second.ClearAllBits(); } - refs_it->second.SetBit(target_method.dex_method_index); + refs_it->second.SetBit(target_method.index); writer_->bss_method_entries_.Overwrite(target_method, /* placeholder */ 0u); } else if (patch.GetType() == LinkerPatch::Type::kTypeBssEntry) { TypeReference ref(patch.TargetTypeDexFile(), patch.TargetTypeIndex()); @@ -867,16 +868,19 @@ class OatWriter::InitOatClassesMethodVisitor : public DexMethodVisitor { class OatWriter::InitCodeMethodVisitor : public OatDexMethodVisitor { public: InitCodeMethodVisitor(OatWriter* writer, size_t offset) - : OatDexMethodVisitor(writer, offset), - debuggable_(writer->GetCompilerDriver()->GetCompilerOptions().GetDebuggable()) { - writer_->absolute_patch_locations_.reserve( - writer_->compiler_driver_->GetNonRelativeLinkerPatchCount()); - } + : InitCodeMethodVisitor(writer, offset, writer->GetCompilerDriver()->GetCompilerOptions()) {} bool EndClass() OVERRIDE { OatDexMethodVisitor::EndClass(); if (oat_class_index_ == writer_->oat_classes_.size()) { - offset_ = writer_->relative_patcher_->ReserveSpaceEnd(offset_); + offset_ = relative_patcher_->ReserveSpaceEnd(offset_); + if (generate_debug_info_) { + std::vector<debug::MethodDebugInfo> thunk_infos = + relative_patcher_->GenerateThunkDebugInfo(executable_offset_); + writer_->method_info_.insert(writer_->method_info_.end(), + std::make_move_iterator(thunk_infos.begin()), + std::make_move_iterator(thunk_infos.end())); + } } return true; } @@ -898,7 +902,7 @@ class OatWriter::InitCodeMethodVisitor : public OatDexMethodVisitor { bool deduped = true; MethodReference method_ref(dex_file_, it.GetMemberIndex()); if (debuggable_) { - quick_code_offset = writer_->relative_patcher_->GetOffset(method_ref); + quick_code_offset = relative_patcher_->GetOffset(method_ref); if (quick_code_offset != 0u) { // Duplicate methods, we want the same code for both of them so that the oat writer puts // the same code in both ArtMethods so that we do not get different oat code at runtime. @@ -916,14 +920,14 @@ class OatWriter::InitCodeMethodVisitor : public OatDexMethodVisitor { } if (code_size != 0) { - if (writer_->relative_patcher_->GetOffset(method_ref) != 0u) { + if (relative_patcher_->GetOffset(method_ref) != 0u) { // TODO: Should this be a hard failure? LOG(WARNING) << "Multiple definitions of " - << method_ref.dex_file->PrettyMethod(method_ref.dex_method_index) - << " offsets " << writer_->relative_patcher_->GetOffset(method_ref) + << method_ref.PrettyMethod() + << " offsets " << relative_patcher_->GetOffset(method_ref) << " " << quick_code_offset; } else { - writer_->relative_patcher_->SetOffset(method_ref, quick_code_offset); + relative_patcher_->SetOffset(method_ref, quick_code_offset); } } @@ -977,13 +981,12 @@ class OatWriter::InitCodeMethodVisitor : public OatDexMethodVisitor { } } - const CompilerOptions& compiler_options = writer_->compiler_driver_->GetCompilerOptions(); // Exclude quickened dex methods (code_size == 0) since they have no native code. - if (compiler_options.GenerateAnyDebugInfo() && code_size != 0) { + if (generate_debug_info_ && code_size != 0) { bool has_code_info = method_header->IsOptimized(); // Record debug information for this function if we are doing that. - debug::MethodDebugInfo info = debug::MethodDebugInfo(); - info.trampoline_name = nullptr; + debug::MethodDebugInfo info = {}; + DCHECK(info.trampoline_name.empty()); info.dex_file = dex_file_; info.class_def_index = class_def_index_; info.dex_method_index = it.GetMemberIndex(); @@ -991,10 +994,10 @@ class OatWriter::InitCodeMethodVisitor : public OatDexMethodVisitor { info.code_item = it.GetMethodCodeItem(); info.isa = compiled_method->GetInstructionSet(); info.deduped = deduped; - info.is_native_debuggable = compiler_options.GetNativeDebuggable(); + info.is_native_debuggable = native_debuggable_; info.is_optimized = method_header->IsOptimized(); info.is_code_address_text_relative = true; - info.code_address = code_offset - writer_->oat_header_->GetExecutableOffset(); + info.code_address = code_offset - executable_offset_; info.code_size = code_size; info.frame_size_in_bytes = compiled_method->GetFrameSizeInBytes(); info.code_info = has_code_info ? compiled_method->GetVmapTable().data() : nullptr; @@ -1012,6 +1015,17 @@ class OatWriter::InitCodeMethodVisitor : public OatDexMethodVisitor { } private: + InitCodeMethodVisitor(OatWriter* writer, size_t offset, const CompilerOptions& compiler_options) + : OatDexMethodVisitor(writer, offset), + relative_patcher_(writer->relative_patcher_), + executable_offset_(writer->oat_header_->GetExecutableOffset()), + debuggable_(compiler_options.GetDebuggable()), + native_debuggable_(compiler_options.GetNativeDebuggable()), + generate_debug_info_(compiler_options.GenerateAnyDebugInfo()) { + writer->absolute_patch_locations_.reserve( + writer->GetCompilerDriver()->GetNonRelativeLinkerPatchCount()); + } + struct CodeOffsetsKeyComparator { bool operator()(const CompiledMethod* lhs, const CompiledMethod* rhs) const { // Code is deduplicated by CompilerDriver, compare only data pointers. @@ -1035,7 +1049,7 @@ class OatWriter::InitCodeMethodVisitor : public OatDexMethodVisitor { uint32_t NewQuickCodeOffset(CompiledMethod* compiled_method, const ClassDataItemIterator& it, uint32_t thumb_offset) { - offset_ = writer_->relative_patcher_->ReserveSpace( + offset_ = relative_patcher_->ReserveSpace( offset_, compiled_method, MethodReference(dex_file_, it.GetMemberIndex())); offset_ += CodeAlignmentSize(offset_, *compiled_method); DCHECK_ALIGNED_PARAM(offset_ + sizeof(OatQuickMethodHeader), @@ -1047,8 +1061,12 @@ class OatWriter::InitCodeMethodVisitor : public OatDexMethodVisitor { // so we can simply compare the pointers to find out if things are duplicated. SafeMap<const CompiledMethod*, uint32_t, CodeOffsetsKeyComparator> dedupe_map_; - // Cache of compiler's --debuggable option. + // Cache writer_'s members and compiler options. + linker::MultiOatRelativePatcher* relative_patcher_; + uint32_t executable_offset_; const bool debuggable_; + const bool native_debuggable_; + const bool generate_debug_info_; }; class OatWriter::InitMapMethodVisitor : public OatDexMethodVisitor { @@ -1509,8 +1527,7 @@ class OatWriter::WriteCodeMethodVisitor : public OatDexMethodVisitor { ObjPtr<mirror::DexCache> dex_cache = (dex_file_ == ref.dex_file) ? dex_cache_ : class_linker_->FindDexCache( Thread::Current(), *ref.dex_file); - ArtMethod* method = - class_linker_->LookupResolvedMethod(ref.dex_method_index, dex_cache, class_loader_); + ArtMethod* method = class_linker_->LookupResolvedMethod(ref.index, dex_cache, class_loader_); CHECK(method != nullptr); return method; } @@ -1935,19 +1952,33 @@ size_t OatWriter::InitOatDexFiles(size_t offset) { size_t OatWriter::InitOatCode(size_t offset) { // calculate the offsets within OatHeader to executable code size_t old_offset = offset; - size_t adjusted_offset = offset; // required to be on a new page boundary offset = RoundUp(offset, kPageSize); oat_header_->SetExecutableOffset(offset); size_executable_offset_alignment_ = offset - old_offset; + // TODO: Remove unused trampoline offsets from the OatHeader (requires oat version change). + oat_header_->SetInterpreterToInterpreterBridgeOffset(0); + oat_header_->SetInterpreterToCompiledCodeBridgeOffset(0); if (compiler_driver_->GetCompilerOptions().IsBootImage()) { InstructionSet instruction_set = compiler_driver_->GetInstructionSet(); - - #define DO_TRAMPOLINE(field, fn_name) \ - offset = CompiledCode::AlignCode(offset, instruction_set); \ - adjusted_offset = offset + CompiledCode::CodeDelta(instruction_set); \ - oat_header_->Set ## fn_name ## Offset(adjusted_offset); \ - (field) = compiler_driver_->Create ## fn_name(); \ + const bool generate_debug_info = compiler_driver_->GetCompilerOptions().GenerateAnyDebugInfo(); + size_t adjusted_offset = offset; + + #define DO_TRAMPOLINE(field, fn_name) \ + offset = CompiledCode::AlignCode(offset, instruction_set); \ + adjusted_offset = offset + CompiledCode::CodeDelta(instruction_set); \ + oat_header_->Set ## fn_name ## Offset(adjusted_offset); \ + (field) = compiler_driver_->Create ## fn_name(); \ + if (generate_debug_info) { \ + debug::MethodDebugInfo info = {}; \ + info.trampoline_name = #fn_name; \ + info.isa = instruction_set; \ + info.is_code_address_text_relative = true; \ + /* Use the code offset rather than the `adjusted_offset`. */ \ + info.code_address = offset - oat_header_->GetExecutableOffset(); \ + info.code_size = (field)->size(); \ + method_info_.push_back(std::move(info)); \ + } \ offset += (field)->size(); DO_TRAMPOLINE(jni_dlsym_lookup_, JniDlsymLookup); @@ -1958,8 +1989,6 @@ size_t OatWriter::InitOatCode(size_t offset) { #undef DO_TRAMPOLINE } else { - oat_header_->SetInterpreterToInterpreterBridgeOffset(0); - oat_header_->SetInterpreterToCompiledCodeBridgeOffset(0); oat_header_->SetJniDlsymLookupOffset(0); oat_header_->SetQuickGenericJniTrampolineOffset(0); oat_header_->SetQuickImtConflictTrampolineOffset(0); diff --git a/compiler/oat_writer.h b/compiler/oat_writer.h index 7f2045f8da..ef0ce52743 100644 --- a/compiler/oat_writer.h +++ b/compiler/oat_writer.h @@ -231,10 +231,6 @@ class OatWriter { ~OatWriter(); - void AddMethodDebugInfos(const std::vector<debug::MethodDebugInfo>& infos) { - method_info_.insert(method_info_.end(), infos.begin(), infos.end()); - } - ArrayRef<const debug::MethodDebugInfo> GetMethodDebugInfo() const { return ArrayRef<const debug::MethodDebugInfo>(method_info_); } diff --git a/compiler/optimizing/bounds_check_elimination.cc b/compiler/optimizing/bounds_check_elimination.cc index 6b832da2bb..a170734ff2 100644 --- a/compiler/optimizing/bounds_check_elimination.cc +++ b/compiler/optimizing/bounds_check_elimination.cc @@ -596,6 +596,7 @@ class BCEVisitor : public HGraphVisitor { // Helper method to assign a new range to an instruction in given basic block. void AssignRange(HBasicBlock* basic_block, HInstruction* instruction, ValueRange* range) { + DCHECK(!range->IsMonotonicValueRange() || instruction->IsLoopHeaderPhi()); GetValueRangeMap(basic_block)->Overwrite(instruction->GetId(), range); } diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc index 19e5d067a1..c61ef0a0bc 100644 --- a/compiler/optimizing/code_generator_arm64.cc +++ b/compiler/optimizing/code_generator_arm64.cc @@ -4641,7 +4641,7 @@ vixl::aarch64::Label* CodeGeneratorARM64::NewPcRelativeMethodPatch( MethodReference target_method, vixl::aarch64::Label* adrp_label) { return NewPcRelativePatch(*target_method.dex_file, - target_method.dex_method_index, + target_method.index, adrp_label, &pc_relative_method_patches_); } @@ -4650,7 +4650,7 @@ vixl::aarch64::Label* CodeGeneratorARM64::NewMethodBssEntryPatch( MethodReference target_method, vixl::aarch64::Label* adrp_label) { return NewPcRelativePatch(*target_method.dex_file, - target_method.dex_method_index, + target_method.index, adrp_label, &method_bss_entry_patches_); } diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc index 8b9495d564..6147259bd3 100644 --- a/compiler/optimizing/code_generator_arm_vixl.cc +++ b/compiler/optimizing/code_generator_arm_vixl.cc @@ -9119,14 +9119,14 @@ void CodeGeneratorARMVIXL::GenerateVirtualCall( CodeGeneratorARMVIXL::PcRelativePatchInfo* CodeGeneratorARMVIXL::NewPcRelativeMethodPatch( MethodReference target_method) { return NewPcRelativePatch(*target_method.dex_file, - target_method.dex_method_index, + target_method.index, &pc_relative_method_patches_); } CodeGeneratorARMVIXL::PcRelativePatchInfo* CodeGeneratorARMVIXL::NewMethodBssEntryPatch( MethodReference target_method) { return NewPcRelativePatch(*target_method.dex_file, - target_method.dex_method_index, + target_method.index, &method_bss_entry_patches_); } diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc index f0ef0071b6..9db2bd35ca 100644 --- a/compiler/optimizing/code_generator_mips.cc +++ b/compiler/optimizing/code_generator_mips.cc @@ -1683,7 +1683,7 @@ CodeGeneratorMIPS::PcRelativePatchInfo* CodeGeneratorMIPS::NewPcRelativeMethodPa MethodReference target_method, const PcRelativePatchInfo* info_high) { return NewPcRelativePatch(*target_method.dex_file, - target_method.dex_method_index, + target_method.index, info_high, &pc_relative_method_patches_); } @@ -1692,7 +1692,7 @@ CodeGeneratorMIPS::PcRelativePatchInfo* CodeGeneratorMIPS::NewMethodBssEntryPatc MethodReference target_method, const PcRelativePatchInfo* info_high) { return NewPcRelativePatch(*target_method.dex_file, - target_method.dex_method_index, + target_method.index, info_high, &method_bss_entry_patches_); } diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc index 201b1b065f..a27cbce3db 100644 --- a/compiler/optimizing/code_generator_mips64.cc +++ b/compiler/optimizing/code_generator_mips64.cc @@ -1592,7 +1592,7 @@ CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewPcRelativeMeth MethodReference target_method, const PcRelativePatchInfo* info_high) { return NewPcRelativePatch(*target_method.dex_file, - target_method.dex_method_index, + target_method.index, info_high, &pc_relative_method_patches_); } @@ -1601,7 +1601,7 @@ CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewMethodBssEntry MethodReference target_method, const PcRelativePatchInfo* info_high) { return NewPcRelativePatch(*target_method.dex_file, - target_method.dex_method_index, + target_method.index, info_high, &method_bss_entry_patches_); } diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc index e45ad0a9a3..c153cf78da 100644 --- a/compiler/optimizing/code_generator_x86.cc +++ b/compiler/optimizing/code_generator_x86.cc @@ -4623,7 +4623,7 @@ void CodeGeneratorX86::RecordBootMethodPatch(HInvokeStaticOrDirect* invoke) { invoke->InputAt(invoke->GetSpecialInputIndex())->AsX86ComputeBaseMethodAddress(); boot_image_method_patches_.emplace_back(address, *invoke->GetTargetMethod().dex_file, - invoke->GetTargetMethod().dex_method_index); + invoke->GetTargetMethod().index); __ Bind(&boot_image_method_patches_.back().label); } @@ -4633,7 +4633,7 @@ Label* CodeGeneratorX86::NewMethodBssEntryPatch( // Add the patch entry and bind its label at the end of the instruction. method_bss_entry_patches_.emplace_back(method_address, *target_method.dex_file, - target_method.dex_method_index); + target_method.index); return &method_bss_entry_patches_.back().label; } diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc index 8c4374d71e..bbf05a70d6 100644 --- a/compiler/optimizing/code_generator_x86_64.cc +++ b/compiler/optimizing/code_generator_x86_64.cc @@ -1068,13 +1068,13 @@ void CodeGeneratorX86_64::GenerateVirtualCall( void CodeGeneratorX86_64::RecordBootMethodPatch(HInvokeStaticOrDirect* invoke) { boot_image_method_patches_.emplace_back(*invoke->GetTargetMethod().dex_file, - invoke->GetTargetMethod().dex_method_index); + invoke->GetTargetMethod().index); __ Bind(&boot_image_method_patches_.back().label); } Label* CodeGeneratorX86_64::NewMethodBssEntryPatch(MethodReference target_method) { // Add a patch entry and return the label. - method_bss_entry_patches_.emplace_back(*target_method.dex_file, target_method.dex_method_index); + method_bss_entry_patches_.emplace_back(*target_method.dex_file, target_method.index); return &method_bss_entry_patches_.back().label; } diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index 6567a3a445..793e781bae 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -1236,7 +1236,7 @@ bool HInliner::TryInlineAndReplace(HInvoke* invoke_instruction, const DexFile& caller_dex_file = *caller_compilation_unit_.GetDexFile(); uint32_t dex_method_index = FindMethodIndexIn( method, caller_dex_file, invoke_instruction->GetDexMethodIndex()); - if (dex_method_index == DexFile::kDexNoIndex) { + if (dex_method_index == dex::kDexNoIndex) { return false; } HInvokeVirtual* new_invoke = new (graph_->GetArena()) HInvokeVirtual( diff --git a/compiler/optimizing/load_store_elimination.cc b/compiler/optimizing/load_store_elimination.cc index 98b859210c..8a9acf108c 100644 --- a/compiler/optimizing/load_store_elimination.cc +++ b/compiler/optimizing/load_store_elimination.cc @@ -670,6 +670,11 @@ void LoadStoreElimination::Run() { return; } + // TODO: analyze VecLoad/VecStore better. + if (graph_->HasSIMD()) { + return; + } + LSEVisitor lse_visitor(graph_, heap_location_collector, side_effects_, stats_); for (HBasicBlock* block : graph_->GetReversePostOrder()) { lse_visitor.VisitBasicBlock(block); diff --git a/compiler/optimizing/optimizing_cfi_test.cc b/compiler/optimizing/optimizing_cfi_test.cc index c24f1de93d..99d5284714 100644 --- a/compiler/optimizing/optimizing_cfi_test.cc +++ b/compiler/optimizing/optimizing_cfi_test.cc @@ -23,6 +23,7 @@ #include "gtest/gtest.h" #include "optimizing/code_generator.h" #include "optimizing/optimizing_unit_test.h" +#include "read_barrier_config.h" #include "utils/arm/assembler_arm_vixl.h" #include "utils/assembler.h" #include "utils/mips/assembler_mips.h" diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc index 399cd98983..e128a15cfd 100644 --- a/compiler/optimizing/optimizing_compiler.cc +++ b/compiler/optimizing/optimizing_compiler.cc @@ -434,9 +434,9 @@ bool OptimizingCompiler::CanCompileMethod(uint32_t method_idx ATTRIBUTE_UNUSED, } static bool IsInstructionSetSupported(InstructionSet instruction_set) { - return (instruction_set == kArm && !kArm32QuickCodeUseSoftFloat) + return instruction_set == kArm || instruction_set == kArm64 - || (instruction_set == kThumb2 && !kArm32QuickCodeUseSoftFloat) + || instruction_set == kThumb2 || instruction_set == kMips || instruction_set == kMips64 || instruction_set == kX86 @@ -1269,8 +1269,8 @@ bool OptimizingCompiler::JitCompile(Thread* self, if (compiler_options.GetGenerateDebugInfo()) { const auto* method_header = reinterpret_cast<const OatQuickMethodHeader*>(code); const uintptr_t code_address = reinterpret_cast<uintptr_t>(method_header->GetCode()); - debug::MethodDebugInfo info = debug::MethodDebugInfo(); - info.trampoline_name = nullptr; + debug::MethodDebugInfo info = {}; + DCHECK(info.trampoline_name.empty()); info.dex_file = dex_file; info.class_def_index = class_def_idx; info.dex_method_index = method_idx; diff --git a/compiler/optimizing/prepare_for_register_allocation.cc b/compiler/optimizing/prepare_for_register_allocation.cc index 5de707a50f..2c856cd3d9 100644 --- a/compiler/optimizing/prepare_for_register_allocation.cc +++ b/compiler/optimizing/prepare_for_register_allocation.cc @@ -16,6 +16,7 @@ #include "prepare_for_register_allocation.h" +#include "dex_file_types.h" #include "jni_internal.h" #include "optimizing_compiler_stats.h" #include "well_known_classes.h" @@ -59,7 +60,7 @@ void PrepareForRegisterAllocation::VisitBoundsCheck(HBoundsCheck* check) { HEnvironment* environment = new (arena) HEnvironment(arena, /* number_of_vregs */ 0u, char_at_method, - /* dex_pc */ DexFile::kDexNoIndex, + /* dex_pc */ dex::kDexNoIndex, check); check->InsertRawEnvironment(environment); } diff --git a/compiler/optimizing/stack_map_stream.cc b/compiler/optimizing/stack_map_stream.cc index b7840d73db..7eb2188a28 100644 --- a/compiler/optimizing/stack_map_stream.cc +++ b/compiler/optimizing/stack_map_stream.cc @@ -18,6 +18,7 @@ #include "art_method-inl.h" #include "base/stl_util.h" +#include "dex_file_types.h" #include "optimizing/optimizing_compiler.h" #include "runtime.h" #include "scoped_thread_state_change-inl.h" @@ -39,7 +40,7 @@ void StackMapStream::BeginStackMapEntry(uint32_t dex_pc, current_entry_.inlining_depth = inlining_depth; current_entry_.inline_infos_start_index = inline_infos_.size(); current_entry_.stack_mask_index = 0; - current_entry_.dex_method_index = DexFile::kDexNoIndex; + current_entry_.dex_method_index = dex::kDexNoIndex; current_entry_.dex_register_entry.num_dex_registers = num_dex_registers; current_entry_.dex_register_entry.locations_start_index = dex_register_locations_.size(); current_entry_.dex_register_entry.live_dex_registers_mask = (num_dex_registers != 0) @@ -226,7 +227,7 @@ void StackMapStream::ComputeInvokeInfoEncoding(CodeInfoEncoding* encoding) { size_t invoke_infos_count = 0; size_t invoke_type_max = 0; for (const StackMapEntry& entry : stack_maps_) { - if (entry.dex_method_index != DexFile::kDexNoIndex) { + if (entry.dex_method_index != dex::kDexNoIndex) { native_pc_max = std::max(native_pc_max, entry.native_pc_code_offset.CompressedValue()); method_index_max = std::max(method_index_max, static_cast<uint16_t>(entry.dex_method_index)); invoke_type_max = std::max(invoke_type_max, static_cast<size_t>(entry.invoke_type)); @@ -240,7 +241,7 @@ void StackMapStream::ComputeInvokeInfoEncoding(CodeInfoEncoding* encoding) { void StackMapStream::ComputeInlineInfoEncoding(InlineInfoEncoding* encoding, size_t dex_register_maps_bytes) { uint32_t method_index_max = 0; - uint32_t dex_pc_max = DexFile::kDexNoIndex; + uint32_t dex_pc_max = dex::kDexNoIndex; uint32_t extra_data_max = 0; uint32_t inline_info_index = 0; @@ -256,8 +257,8 @@ void StackMapStream::ComputeInlineInfoEncoding(InlineInfoEncoding* encoding, extra_data_max = std::max( extra_data_max, Low32Bits(reinterpret_cast<uintptr_t>(inline_entry.method))); } - if (inline_entry.dex_pc != DexFile::kDexNoIndex && - (dex_pc_max == DexFile::kDexNoIndex || dex_pc_max < inline_entry.dex_pc)) { + if (inline_entry.dex_pc != dex::kDexNoIndex && + (dex_pc_max == dex::kDexNoIndex || dex_pc_max < inline_entry.dex_pc)) { dex_pc_max = inline_entry.dex_pc; } } @@ -362,7 +363,7 @@ void StackMapStream::FillInCodeInfo(MemoryRegion region) { dex_register_locations_region); stack_map.SetDexRegisterMapOffset(encoding.stack_map.encoding, offset); - if (entry.dex_method_index != DexFile::kDexNoIndex) { + if (entry.dex_method_index != dex::kDexNoIndex) { InvokeInfo invoke_info(code_info.GetInvokeInfo(encoding, invoke_info_idx)); invoke_info.SetNativePcCodeOffset(encoding.invoke_info.encoding, entry.native_pc_code_offset); invoke_info.SetInvokeType(encoding.invoke_info.encoding, entry.invoke_type); @@ -561,7 +562,7 @@ void StackMapStream::PrepareMethodIndices() { for (StackMapEntry& stack_map : stack_maps_) { const size_t index = dedupe.size(); const uint32_t method_index = stack_map.dex_method_index; - if (method_index != DexFile::kDexNoIndex) { + if (method_index != dex::kDexNoIndex) { stack_map.dex_method_index_idx = dedupe.emplace(method_index, index).first->second; method_indices_[index] = method_index; } @@ -569,7 +570,7 @@ void StackMapStream::PrepareMethodIndices() { for (InlineInfoEntry& inline_info : inline_infos_) { const size_t index = dedupe.size(); const uint32_t method_index = inline_info.method_index; - CHECK_NE(method_index, DexFile::kDexNoIndex); + CHECK_NE(method_index, dex::kDexNoIndex); inline_info.dex_method_index_idx = dedupe.emplace(method_index, index).first->second; method_indices_[index] = method_index; } @@ -629,7 +630,7 @@ void StackMapStream::CheckCodeInfo(MemoryRegion region) const { DCHECK_EQ(stack_mask.LoadBit(b), 0u); } } - if (entry.dex_method_index != DexFile::kDexNoIndex) { + if (entry.dex_method_index != dex::kDexNoIndex) { InvokeInfo invoke_info = code_info.GetInvokeInfo(encoding, invoke_info_index); DCHECK_EQ(invoke_info.GetNativePcOffset(encoding.invoke_info.encoding, instruction_set_), entry.native_pc_code_offset.Uint32Value(instruction_set_)); diff --git a/compiler/optimizing/stack_map_stream.h b/compiler/optimizing/stack_map_stream.h index e6471e1bc5..a574566e33 100644 --- a/compiler/optimizing/stack_map_stream.h +++ b/compiler/optimizing/stack_map_stream.h @@ -126,7 +126,7 @@ class StackMapStream : public ValueObject { }; struct InlineInfoEntry { - uint32_t dex_pc; // DexFile::kDexNoIndex for intrinsified native methods. + uint32_t dex_pc; // dex::kDexNoIndex for intrinsified native methods. ArtMethod* method; uint32_t method_index; DexRegisterMapEntry dex_register_entry; diff --git a/compiler/utils/assembler_test.h b/compiler/utils/assembler_test.h index ef53d7237c..12954a4c32 100644 --- a/compiler/utils/assembler_test.h +++ b/compiler/utils/assembler_test.h @@ -46,7 +46,12 @@ enum class RegisterView { // private // For use in the template as the default type to get a nonvector registers version. struct NoVectorRegs {}; -template<typename Ass, typename Reg, typename FPReg, typename Imm, typename VecReg = NoVectorRegs> +template<typename Ass, + typename Addr, + typename Reg, + typename FPReg, + typename Imm, + typename VecReg = NoVectorRegs> class AssemblerTest : public testing::Test { public: Ass* GetAssembler() { @@ -64,6 +69,10 @@ class AssemblerTest : public testing::Test { DriverWrapper(assembly_string, test_name); } + // + // Register repeats. + // + std::string RepeatR(void (Ass::*f)(Reg), const std::string& fmt) { return RepeatTemplatedRegister<Reg>(f, GetRegisters(), @@ -159,7 +168,9 @@ class AssemblerTest : public testing::Test { for (auto reg2 : reg2_registers) { for (int64_t imm : imms) { ImmType new_imm = CreateImmediate(imm); - (assembler_.get()->*f)(*reg1, *reg2, new_imm * multiplier + bias); + if (f != nullptr) { + (assembler_.get()->*f)(*reg1, *reg2, new_imm * multiplier + bias); + } std::string base = fmt; std::string reg1_string = (this->*GetName1)(*reg1); @@ -213,7 +224,9 @@ class AssemblerTest : public testing::Test { for (auto reg3 : reg3_registers) { for (int64_t imm : imms) { ImmType new_imm = CreateImmediate(imm); - (assembler_.get()->*f)(*reg1, *reg2, *reg3, new_imm + bias); + if (f != nullptr) { + (assembler_.get()->*f)(*reg1, *reg2, *reg3, new_imm + bias); + } std::string base = fmt; std::string reg1_string = (this->*GetName1)(*reg1); @@ -272,7 +285,9 @@ class AssemblerTest : public testing::Test { for (auto reg2 : reg2_registers) { for (int64_t imm : imms) { ImmType new_imm = CreateImmediate(imm); - (assembler_.get()->*f)(new_imm, *reg1, *reg2); + if (f != nullptr) { + (assembler_.get()->*f)(new_imm, *reg1, *reg2); + } std::string base = fmt; std::string reg1_string = (this->*GetName1)(*reg1); @@ -320,7 +335,9 @@ class AssemblerTest : public testing::Test { for (auto reg : registers) { for (int64_t imm : imms) { ImmType new_imm = CreateImmediate(imm); - (assembler_.get()->*f)(*reg, new_imm + bias); + if (f != nullptr) { + (assembler_.get()->*f)(*reg, new_imm + bias); + } std::string base = fmt; std::string reg_string = (this->*GetName)(*reg); @@ -522,7 +539,9 @@ class AssemblerTest : public testing::Test { for (int64_t imm : imms) { Imm new_imm = CreateImmediate(imm); - (assembler_.get()->*f)(new_imm); + if (f != nullptr) { + (assembler_.get()->*f)(new_imm); + } std::string base = fmt; size_t imm_index = base.find(IMM_TOKEN); @@ -626,13 +645,23 @@ class AssemblerTest : public testing::Test { // The following functions are public so that TestFn can use them... + // Returns a vector of address used by any of the repeat methods + // involving an "A" (e.g. RepeatA). + virtual std::vector<Addr> GetAddresses() = 0; + + // Returns a vector of registers used by any of the repeat methods + // involving an "R" (e.g. RepeatR). virtual std::vector<Reg*> GetRegisters() = 0; + // Returns a vector of fp-registers used by any of the repeat methods + // involving an "F" (e.g. RepeatFF). virtual std::vector<FPReg*> GetFPRegisters() { UNIMPLEMENTED(FATAL) << "Architecture does not support floating-point registers"; UNREACHABLE(); } + // Returns a vector of dedicated simd-registers used by any of the repeat + // methods involving an "V" (e.g. RepeatVV). virtual std::vector<VecReg*> GetVectorRegisters() { UNIMPLEMENTED(FATAL) << "Architecture does not support vector registers"; UNREACHABLE(); @@ -828,6 +857,268 @@ class AssemblerTest : public testing::Test { // Create an immediate from the specific value. virtual Imm CreateImmediate(int64_t imm_value) = 0; + // + // Addresses repeats. + // + + // Repeats over addresses provided by fixture. + std::string RepeatA(void (Ass::*f)(const Addr&), const std::string& fmt) { + return RepeatA(f, GetAddresses(), fmt); + } + + // Variant that takes explicit vector of addresss + // (to test restricted addressing modes set). + std::string RepeatA(void (Ass::*f)(const Addr&), + const std::vector<Addr>& a, + const std::string& fmt) { + return RepeatTemplatedMem<Addr>(f, a, &AssemblerTest::GetAddrName, fmt); + } + + // Repeats over addresses and immediates provided by fixture. + std::string RepeatAI(void (Ass::*f)(const Addr&, const Imm&), + size_t imm_bytes, + const std::string& fmt) { + return RepeatAI(f, imm_bytes, GetAddresses(), fmt); + } + + // Variant that takes explicit vector of addresss + // (to test restricted addressing modes set). + std::string RepeatAI(void (Ass::*f)(const Addr&, const Imm&), + size_t imm_bytes, + const std::vector<Addr>& a, + const std::string& fmt) { + return RepeatTemplatedMemImm<Addr>(f, imm_bytes, a, &AssemblerTest::GetAddrName, fmt); + } + + // Repeats over registers and addresses provided by fixture. + std::string RepeatRA(void (Ass::*f)(Reg, const Addr&), const std::string& fmt) { + return RepeatRA(f, GetAddresses(), fmt); + } + + // Variant that takes explicit vector of addresss + // (to test restricted addressing modes set). + std::string RepeatRA(void (Ass::*f)(Reg, const Addr&), + const std::vector<Addr>& a, + const std::string& fmt) { + return RepeatTemplatedRegMem<Reg, Addr>( + f, + GetRegisters(), + a, + &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>, + &AssemblerTest::GetAddrName, + fmt); + } + + // Repeats over fp-registers and addresses provided by fixture. + std::string RepeatFA(void (Ass::*f)(FPReg, const Addr&), const std::string& fmt) { + return RepeatFA(f, GetAddresses(), fmt); + } + + // Variant that takes explicit vector of addresss + // (to test restricted addressing modes set). + std::string RepeatFA(void (Ass::*f)(FPReg, const Addr&), + const std::vector<Addr>& a, + const std::string& fmt) { + return RepeatTemplatedRegMem<FPReg, Addr>( + f, + GetFPRegisters(), + a, + &AssemblerTest::GetFPRegName, + &AssemblerTest::GetAddrName, + fmt); + } + + // Repeats over addresses and registers provided by fixture. + std::string RepeatAR(void (Ass::*f)(const Addr&, Reg), const std::string& fmt) { + return RepeatAR(f, GetAddresses(), fmt); + } + + // Variant that takes explicit vector of addresss + // (to test restricted addressing modes set). + std::string RepeatAR(void (Ass::*f)(const Addr&, Reg), + const std::vector<Addr>& a, + const std::string& fmt) { + return RepeatTemplatedMemReg<Addr, Reg>( + f, + a, + GetRegisters(), + &AssemblerTest::GetAddrName, + &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>, + fmt); + } + + // Repeats over addresses and fp-registers provided by fixture. + std::string RepeatAF(void (Ass::*f)(const Addr&, FPReg), const std::string& fmt) { + return RepeatAF(f, GetAddresses(), fmt); + } + + // Variant that takes explicit vector of addresss + // (to test restricted addressing modes set). + std::string RepeatAF(void (Ass::*f)(const Addr&, FPReg), + const std::vector<Addr>& a, + const std::string& fmt) { + return RepeatTemplatedMemReg<Addr, FPReg>( + f, + a, + GetFPRegisters(), + &AssemblerTest::GetAddrName, + &AssemblerTest::GetFPRegName, + fmt); + } + + template <typename AddrType> + std::string RepeatTemplatedMem(void (Ass::*f)(const AddrType&), + const std::vector<AddrType> addresses, + std::string (AssemblerTest::*GetAName)(const AddrType&), + const std::string& fmt) { + WarnOnCombinations(addresses.size()); + std::string str; + for (auto addr : addresses) { + if (f != nullptr) { + (assembler_.get()->*f)(addr); + } + std::string base = fmt; + + std::string addr_string = (this->*GetAName)(addr); + size_t addr_index; + if ((addr_index = base.find(ADDRESS_TOKEN)) != std::string::npos) { + base.replace(addr_index, ConstexprStrLen(ADDRESS_TOKEN), addr_string); + } + + if (str.size() > 0) { + str += "\n"; + } + str += base; + } + // Add a newline at the end. + str += "\n"; + return str; + } + + template <typename AddrType> + std::string RepeatTemplatedMemImm(void (Ass::*f)(const AddrType&, const Imm&), + size_t imm_bytes, + const std::vector<AddrType> addresses, + std::string (AssemblerTest::*GetAName)(const AddrType&), + const std::string& fmt) { + std::vector<int64_t> imms = CreateImmediateValues(imm_bytes); + WarnOnCombinations(addresses.size() * imms.size()); + std::string str; + for (auto addr : addresses) { + for (int64_t imm : imms) { + Imm new_imm = CreateImmediate(imm); + if (f != nullptr) { + (assembler_.get()->*f)(addr, new_imm); + } + std::string base = fmt; + + std::string addr_string = (this->*GetAName)(addr); + size_t addr_index; + if ((addr_index = base.find(ADDRESS_TOKEN)) != std::string::npos) { + base.replace(addr_index, ConstexprStrLen(ADDRESS_TOKEN), addr_string); + } + + size_t imm_index = base.find(IMM_TOKEN); + if (imm_index != std::string::npos) { + std::ostringstream sreg; + sreg << imm; + std::string imm_string = sreg.str(); + base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string); + } + + if (str.size() > 0) { + str += "\n"; + } + str += base; + } + } + // Add a newline at the end. + str += "\n"; + return str; + } + + template <typename RegType, typename AddrType> + std::string RepeatTemplatedRegMem(void (Ass::*f)(RegType, const AddrType&), + const std::vector<RegType*> registers, + const std::vector<AddrType> addresses, + std::string (AssemblerTest::*GetRName)(const RegType&), + std::string (AssemblerTest::*GetAName)(const AddrType&), + const std::string& fmt) { + WarnOnCombinations(addresses.size() * registers.size()); + std::string str; + for (auto reg : registers) { + for (auto addr : addresses) { + if (f != nullptr) { + (assembler_.get()->*f)(*reg, addr); + } + std::string base = fmt; + + std::string reg_string = (this->*GetRName)(*reg); + size_t reg_index; + if ((reg_index = base.find(REG_TOKEN)) != std::string::npos) { + base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string); + } + + std::string addr_string = (this->*GetAName)(addr); + size_t addr_index; + if ((addr_index = base.find(ADDRESS_TOKEN)) != std::string::npos) { + base.replace(addr_index, ConstexprStrLen(ADDRESS_TOKEN), addr_string); + } + + if (str.size() > 0) { + str += "\n"; + } + str += base; + } + } + // Add a newline at the end. + str += "\n"; + return str; + } + + template <typename AddrType, typename RegType> + std::string RepeatTemplatedMemReg(void (Ass::*f)(const AddrType&, RegType), + const std::vector<AddrType> addresses, + const std::vector<RegType*> registers, + std::string (AssemblerTest::*GetAName)(const AddrType&), + std::string (AssemblerTest::*GetRName)(const RegType&), + const std::string& fmt) { + WarnOnCombinations(addresses.size() * registers.size()); + std::string str; + for (auto addr : addresses) { + for (auto reg : registers) { + if (f != nullptr) { + (assembler_.get()->*f)(addr, *reg); + } + std::string base = fmt; + + std::string addr_string = (this->*GetAName)(addr); + size_t addr_index; + if ((addr_index = base.find(ADDRESS_TOKEN)) != std::string::npos) { + base.replace(addr_index, ConstexprStrLen(ADDRESS_TOKEN), addr_string); + } + + std::string reg_string = (this->*GetRName)(*reg); + size_t reg_index; + if ((reg_index = base.find(REG_TOKEN)) != std::string::npos) { + base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string); + } + + if (str.size() > 0) { + str += "\n"; + } + str += base; + } + } + // Add a newline at the end. + str += "\n"; + return str; + } + + // + // Register repeats. + // + template <typename RegType> std::string RepeatTemplatedRegister(void (Ass::*f)(RegType), const std::vector<RegType*> registers, @@ -835,7 +1126,9 @@ class AssemblerTest : public testing::Test { const std::string& fmt) { std::string str; for (auto reg : registers) { - (assembler_.get()->*f)(*reg); + if (f != nullptr) { + (assembler_.get()->*f)(*reg); + } std::string base = fmt; std::string reg_string = (this->*GetName)(*reg); @@ -866,7 +1159,9 @@ class AssemblerTest : public testing::Test { std::string str; for (auto reg1 : reg1_registers) { for (auto reg2 : reg2_registers) { - (assembler_.get()->*f)(*reg1, *reg2); + if (f != nullptr) { + (assembler_.get()->*f)(*reg1, *reg2); + } std::string base = fmt; std::string reg1_string = (this->*GetName1)(*reg1); @@ -905,7 +1200,9 @@ class AssemblerTest : public testing::Test { for (auto reg1 : reg1_registers) { for (auto reg2 : reg2_registers) { if (reg1 == reg2) continue; - (assembler_.get()->*f)(*reg1, *reg2); + if (f != nullptr) { + (assembler_.get()->*f)(*reg1, *reg2); + } std::string base = fmt; std::string reg1_string = (this->*GetName1)(*reg1); @@ -944,7 +1241,9 @@ class AssemblerTest : public testing::Test { for (auto reg1 : reg1_registers) { for (auto reg2 : reg2_registers) { for (auto reg3 : reg3_registers) { - (assembler_.get()->*f)(*reg1, *reg2, *reg3); + if (f != nullptr) { + (assembler_.get()->*f)(*reg1, *reg2, *reg3); + } std::string base = fmt; std::string reg1_string = (this->*GetName1)(*reg1); @@ -993,7 +1292,9 @@ class AssemblerTest : public testing::Test { for (auto reg2 : reg2_registers) { for (int64_t imm : imms) { Imm new_imm = CreateImmediate(imm); - (assembler_.get()->*f)(*reg1, *reg2, new_imm); + if (f != nullptr) { + (assembler_.get()->*f)(*reg1, *reg2, new_imm); + } std::string base = fmt; std::string reg1_string = (this->*GetName1)(*reg1); @@ -1028,6 +1329,12 @@ class AssemblerTest : public testing::Test { return str; } + std::string GetAddrName(const Addr& addr) { + std::ostringstream saddr; + saddr << addr; + return saddr.str(); + } + template <RegisterView kRegView> std::string GetRegName(const Reg& reg) { std::ostringstream sreg; @@ -1074,6 +1381,7 @@ class AssemblerTest : public testing::Test { } } + static constexpr const char* ADDRESS_TOKEN = "{mem}"; static constexpr const char* REG_TOKEN = "{reg}"; static constexpr const char* REG1_TOKEN = "{reg1}"; static constexpr const char* REG2_TOKEN = "{reg2}"; @@ -1094,7 +1402,9 @@ class AssemblerTest : public testing::Test { for (auto reg : registers) { for (int64_t imm : imms) { Imm new_imm = CreateImmediate(imm); - (assembler_.get()->*f)(*reg, new_imm); + if (f != nullptr) { + (assembler_.get()->*f)(*reg, new_imm); + } std::string base = fmt; std::string reg_string = GetRegName<kRegView>(*reg); diff --git a/compiler/utils/atomic_dex_ref_map-inl.h b/compiler/utils/atomic_dex_ref_map-inl.h index c41d8fc071..33d59f9d42 100644 --- a/compiler/utils/atomic_dex_ref_map-inl.h +++ b/compiler/utils/atomic_dex_ref_map-inl.h @@ -19,15 +19,40 @@ #include "atomic_dex_ref_map.h" +#include <type_traits> + #include "dex_file-inl.h" +#include "class_reference.h" +#include "method_reference.h" +#include "type_reference.h" namespace art { -template <typename T> -inline typename AtomicDexRefMap<T>::InsertResult AtomicDexRefMap<T>::Insert( - DexFileReference ref, - const T& expected, - const T& desired) { +template <typename DexFileReferenceType, typename Value> +inline size_t AtomicDexRefMap<DexFileReferenceType, Value>::NumberOfDexIndices( + const DexFile* dex_file) { + // TODO: Use specialization for this? Not sure if worth it. + static_assert(std::is_same<DexFileReferenceType, MethodReference>::value || + std::is_same<DexFileReferenceType, ClassReference>::value || + std::is_same<DexFileReferenceType, TypeReference>::value, + "invalid index type"); + if (std::is_same<DexFileReferenceType, MethodReference>::value) { + return dex_file->NumMethodIds(); + } + if (std::is_same<DexFileReferenceType, ClassReference>::value) { + return dex_file->NumClassDefs(); + } + if (std::is_same<DexFileReferenceType, TypeReference>::value) { + return dex_file->NumTypeIds(); + } + UNREACHABLE(); +} + +template <typename DexFileReferenceType, typename Value> +inline typename AtomicDexRefMap<DexFileReferenceType, Value>::InsertResult + AtomicDexRefMap<DexFileReferenceType, Value>::Insert(const DexFileReferenceType& ref, + const Value& expected, + const Value& desired) { ElementArray* const array = GetArray(ref.dex_file); if (array == nullptr) { return kInsertResultInvalidDexFile; @@ -38,8 +63,9 @@ inline typename AtomicDexRefMap<T>::InsertResult AtomicDexRefMap<T>::Insert( : kInsertResultCASFailure; } -template <typename T> -inline bool AtomicDexRefMap<T>::Get(DexFileReference ref, T* out) const { +template <typename DexFileReferenceType, typename Value> +inline bool AtomicDexRefMap<DexFileReferenceType, Value>::Get(const DexFileReferenceType& ref, + Value* out) const { const ElementArray* const array = GetArray(ref.dex_file); if (array == nullptr) { return false; @@ -48,27 +74,37 @@ inline bool AtomicDexRefMap<T>::Get(DexFileReference ref, T* out) const { return true; } -template <typename T> -inline void AtomicDexRefMap<T>::AddDexFile(const DexFile* dex_file, size_t max_index) { - arrays_.Put(dex_file, std::move(ElementArray(max_index))); +template <typename DexFileReferenceType, typename Value> +inline void AtomicDexRefMap<DexFileReferenceType, Value>::AddDexFile(const DexFile* dex_file) { + arrays_.Put(dex_file, std::move(ElementArray(NumberOfDexIndices(dex_file)))); } -template <typename T> -inline typename AtomicDexRefMap<T>::ElementArray* AtomicDexRefMap<T>::GetArray( - const DexFile* dex_file) { +template <typename DexFileReferenceType, typename Value> +inline void AtomicDexRefMap<DexFileReferenceType, Value>::AddDexFiles( + const std::vector<const DexFile*>& dex_files) { + for (const DexFile* dex_file : dex_files) { + if (!HaveDexFile(dex_file)) { + AddDexFile(dex_file); + } + } +} + +template <typename DexFileReferenceType, typename Value> +inline typename AtomicDexRefMap<DexFileReferenceType, Value>::ElementArray* + AtomicDexRefMap<DexFileReferenceType, Value>::GetArray(const DexFile* dex_file) { auto it = arrays_.find(dex_file); return (it != arrays_.end()) ? &it->second : nullptr; } -template <typename T> -inline const typename AtomicDexRefMap<T>::ElementArray* AtomicDexRefMap<T>::GetArray( - const DexFile* dex_file) const { +template <typename DexFileReferenceType, typename Value> +inline const typename AtomicDexRefMap<DexFileReferenceType, Value>::ElementArray* + AtomicDexRefMap<DexFileReferenceType, Value>::GetArray(const DexFile* dex_file) const { auto it = arrays_.find(dex_file); return (it != arrays_.end()) ? &it->second : nullptr; } -template <typename T> template <typename Visitor> -inline void AtomicDexRefMap<T>::Visit(const Visitor& visitor) { +template <typename DexFileReferenceType, typename Value> template <typename Visitor> +inline void AtomicDexRefMap<DexFileReferenceType, Value>::Visit(const Visitor& visitor) { for (auto& pair : arrays_) { const DexFile* dex_file = pair.first; const ElementArray& elements = pair.second; @@ -78,8 +114,8 @@ inline void AtomicDexRefMap<T>::Visit(const Visitor& visitor) { } } -template <typename T> -inline void AtomicDexRefMap<T>::ClearEntries() { +template <typename DexFileReferenceType, typename Value> +inline void AtomicDexRefMap<DexFileReferenceType, Value>::ClearEntries() { for (auto& it : arrays_) { for (auto& element : it.second) { element.StoreRelaxed(nullptr); diff --git a/compiler/utils/atomic_dex_ref_map.h b/compiler/utils/atomic_dex_ref_map.h index 2da4ffa27b..fad056c191 100644 --- a/compiler/utils/atomic_dex_ref_map.h +++ b/compiler/utils/atomic_dex_ref_map.h @@ -18,7 +18,7 @@ #define ART_COMPILER_UTILS_ATOMIC_DEX_REF_MAP_H_ #include "base/dchecked_vector.h" -#include "dex_file.h" +#include "dex_file_reference.h" #include "safe_map.h" namespace art { @@ -26,7 +26,7 @@ namespace art { class DexFile; // Used by CompilerCallbacks to track verification information from the Runtime. -template <typename T> +template <typename DexFileReferenceType, typename Value> class AtomicDexRefMap { public: explicit AtomicDexRefMap() {} @@ -38,14 +38,17 @@ class AtomicDexRefMap { kInsertResultCASFailure, kInsertResultSuccess, }; - InsertResult Insert(DexFileReference ref, const T& expected, const T& desired); + InsertResult Insert(const DexFileReferenceType& ref, + const Value& expected, + const Value& desired); // Retreive an item, returns false if the dex file is not added. - bool Get(DexFileReference ref, T* out) const; + bool Get(const DexFileReferenceType& ref, Value* out) const; // Dex files must be added before method references belonging to them can be used as keys. Not // thread safe. - void AddDexFile(const DexFile* dex_file, size_t max_index); + void AddDexFile(const DexFile* dex_file); + void AddDexFiles(const std::vector<const DexFile*>& dex_files); bool HaveDexFile(const DexFile* dex_file) const { return arrays_.find(dex_file) != arrays_.end(); @@ -59,12 +62,14 @@ class AtomicDexRefMap { private: // Verified methods. The method array is fixed to avoid needing a lock to extend it. - using ElementArray = dchecked_vector<Atomic<T>>; + using ElementArray = dchecked_vector<Atomic<Value>>; using DexFileArrays = SafeMap<const DexFile*, ElementArray>; const ElementArray* GetArray(const DexFile* dex_file) const; ElementArray* GetArray(const DexFile* dex_file); + static size_t NumberOfDexIndices(const DexFile* dex_file); + DexFileArrays arrays_; }; diff --git a/compiler/utils/atomic_dex_ref_map_test.cc b/compiler/utils/atomic_dex_ref_map_test.cc index ae19a9c6da..8fce36f021 100644 --- a/compiler/utils/atomic_dex_ref_map_test.cc +++ b/compiler/utils/atomic_dex_ref_map_test.cc @@ -31,40 +31,40 @@ TEST_F(AtomicDexRefMapTest, RunTests) { ScopedObjectAccess soa(Thread::Current()); std::unique_ptr<const DexFile> dex(OpenTestDexFile("Interfaces")); ASSERT_TRUE(dex != nullptr); - using Map = AtomicDexRefMap<int>; + using Map = AtomicDexRefMap<MethodReference, int>; Map map; int value = 123; // Error case: Not already inserted. - EXPECT_FALSE(map.Get(DexFileReference(dex.get(), 1), &value)); + EXPECT_FALSE(map.Get(MethodReference(dex.get(), 1), &value)); EXPECT_FALSE(map.HaveDexFile(dex.get())); // Error case: Dex file not registered. - EXPECT_TRUE(map.Insert(DexFileReference(dex.get(), 1), 0, 1) == Map::kInsertResultInvalidDexFile); - map.AddDexFile(dex.get(), dex->NumMethodIds()); + EXPECT_TRUE(map.Insert(MethodReference(dex.get(), 1), 0, 1) == Map::kInsertResultInvalidDexFile); + map.AddDexFile(dex.get()); EXPECT_TRUE(map.HaveDexFile(dex.get())); EXPECT_GT(dex->NumMethodIds(), 10u); // After we have added the get should succeed but return the default value. - EXPECT_TRUE(map.Get(DexFileReference(dex.get(), 1), &value)); + EXPECT_TRUE(map.Get(MethodReference(dex.get(), 1), &value)); EXPECT_EQ(value, 0); // Actually insert an item and make sure we can retreive it. static const int kInsertValue = 44; - EXPECT_TRUE(map.Insert(DexFileReference(dex.get(), 1), 0, kInsertValue) == + EXPECT_TRUE(map.Insert(MethodReference(dex.get(), 1), 0, kInsertValue) == Map::kInsertResultSuccess); - EXPECT_TRUE(map.Get(DexFileReference(dex.get(), 1), &value)); + EXPECT_TRUE(map.Get(MethodReference(dex.get(), 1), &value)); EXPECT_EQ(value, kInsertValue); static const int kInsertValue2 = 123; - EXPECT_TRUE(map.Insert(DexFileReference(dex.get(), 2), 0, kInsertValue2) == + EXPECT_TRUE(map.Insert(MethodReference(dex.get(), 2), 0, kInsertValue2) == Map::kInsertResultSuccess); - EXPECT_TRUE(map.Get(DexFileReference(dex.get(), 1), &value)); + EXPECT_TRUE(map.Get(MethodReference(dex.get(), 1), &value)); EXPECT_EQ(value, kInsertValue); - EXPECT_TRUE(map.Get(DexFileReference(dex.get(), 2), &value)); + EXPECT_TRUE(map.Get(MethodReference(dex.get(), 2), &value)); EXPECT_EQ(value, kInsertValue2); // Error case: Incorrect expected value for CAS. - EXPECT_TRUE(map.Insert(DexFileReference(dex.get(), 1), 0, kInsertValue + 1) == + EXPECT_TRUE(map.Insert(MethodReference(dex.get(), 1), 0, kInsertValue + 1) == Map::kInsertResultCASFailure); // Correctly overwrite the value and verify. - EXPECT_TRUE(map.Insert(DexFileReference(dex.get(), 1), kInsertValue, kInsertValue + 1) == + EXPECT_TRUE(map.Insert(MethodReference(dex.get(), 1), kInsertValue, kInsertValue + 1) == Map::kInsertResultSuccess); - EXPECT_TRUE(map.Get(DexFileReference(dex.get(), 1), &value)); + EXPECT_TRUE(map.Get(MethodReference(dex.get(), 1), &value)); EXPECT_EQ(value, kInsertValue + 1); } diff --git a/compiler/utils/mips/assembler_mips32r5_test.cc b/compiler/utils/mips/assembler_mips32r5_test.cc index 24b09b5524..a3662db935 100644 --- a/compiler/utils/mips/assembler_mips32r5_test.cc +++ b/compiler/utils/mips/assembler_mips32r5_test.cc @@ -32,12 +32,14 @@ struct MIPSCpuRegisterCompare { }; class AssemblerMIPS32r5Test : public AssemblerTest<mips::MipsAssembler, + mips::MipsLabel, mips::Register, mips::FRegister, uint32_t, mips::VectorRegister> { public: typedef AssemblerTest<mips::MipsAssembler, + mips::MipsLabel, mips::Register, mips::FRegister, uint32_t, @@ -217,6 +219,11 @@ class AssemblerMIPS32r5Test : public AssemblerTest<mips::MipsAssembler, STLDeleteElements(&vec_registers_); } + std::vector<mips::MipsLabel> GetAddresses() { + UNIMPLEMENTED(FATAL) << "Feature not implemented yet"; + UNREACHABLE(); + } + std::vector<mips::Register*> GetRegisters() OVERRIDE { return registers_; } diff --git a/compiler/utils/mips/assembler_mips32r6_test.cc b/compiler/utils/mips/assembler_mips32r6_test.cc index a5cd5a7c65..b6cb30a6f0 100644 --- a/compiler/utils/mips/assembler_mips32r6_test.cc +++ b/compiler/utils/mips/assembler_mips32r6_test.cc @@ -32,12 +32,14 @@ struct MIPSCpuRegisterCompare { }; class AssemblerMIPS32r6Test : public AssemblerTest<mips::MipsAssembler, + mips::MipsLabel, mips::Register, mips::FRegister, uint32_t, mips::VectorRegister> { public: typedef AssemblerTest<mips::MipsAssembler, + mips::MipsLabel, mips::Register, mips::FRegister, uint32_t, @@ -230,6 +232,11 @@ class AssemblerMIPS32r6Test : public AssemblerTest<mips::MipsAssembler, STLDeleteElements(&vec_registers_); } + std::vector<mips::MipsLabel> GetAddresses() { + UNIMPLEMENTED(FATAL) << "Feature not implemented yet"; + UNREACHABLE(); + } + std::vector<mips::Register*> GetRegisters() OVERRIDE { return registers_; } diff --git a/compiler/utils/mips/assembler_mips_test.cc b/compiler/utils/mips/assembler_mips_test.cc index 680c347fef..eed83a5528 100644 --- a/compiler/utils/mips/assembler_mips_test.cc +++ b/compiler/utils/mips/assembler_mips_test.cc @@ -32,11 +32,16 @@ struct MIPSCpuRegisterCompare { }; class AssemblerMIPSTest : public AssemblerTest<mips::MipsAssembler, + mips::MipsLabel, mips::Register, mips::FRegister, uint32_t> { public: - typedef AssemblerTest<mips::MipsAssembler, mips::Register, mips::FRegister, uint32_t> Base; + typedef AssemblerTest<mips::MipsAssembler, + mips::MipsLabel, + mips::Register, + mips::FRegister, + uint32_t> Base; protected: // Get the typically used name for this architecture, e.g., aarch64, x86-64, ... @@ -161,6 +166,11 @@ class AssemblerMIPSTest : public AssemblerTest<mips::MipsAssembler, STLDeleteElements(&fp_registers_); } + std::vector<mips::MipsLabel> GetAddresses() { + UNIMPLEMENTED(FATAL) << "Feature not implemented yet"; + UNREACHABLE(); + } + std::vector<mips::Register*> GetRegisters() OVERRIDE { return registers_; } diff --git a/compiler/utils/mips64/assembler_mips64_test.cc b/compiler/utils/mips64/assembler_mips64_test.cc index fc0bd368ea..16a36f9069 100644 --- a/compiler/utils/mips64/assembler_mips64_test.cc +++ b/compiler/utils/mips64/assembler_mips64_test.cc @@ -35,12 +35,14 @@ struct MIPS64CpuRegisterCompare { }; class AssemblerMIPS64Test : public AssemblerTest<mips64::Mips64Assembler, + mips64::Mips64Label, mips64::GpuRegister, mips64::FpuRegister, uint32_t, mips64::VectorRegister> { public: typedef AssemblerTest<mips64::Mips64Assembler, + mips64::Mips64Label, mips64::GpuRegister, mips64::FpuRegister, uint32_t, @@ -228,6 +230,11 @@ class AssemblerMIPS64Test : public AssemblerTest<mips64::Mips64Assembler, STLDeleteElements(&vec_registers_); } + std::vector<mips64::Mips64Label> GetAddresses() { + UNIMPLEMENTED(FATAL) << "Feature not implemented yet"; + UNREACHABLE(); + } + std::vector<mips64::GpuRegister*> GetRegisters() OVERRIDE { return registers_; } diff --git a/compiler/utils/x86/assembler_x86.cc b/compiler/utils/x86/assembler_x86.cc index b89af10749..3162a32994 100644 --- a/compiler/utils/x86/assembler_x86.cc +++ b/compiler/utils/x86/assembler_x86.cc @@ -32,6 +32,33 @@ std::ostream& operator<<(std::ostream& os, const X87Register& reg) { return os << "ST" << static_cast<int>(reg); } +std::ostream& operator<<(std::ostream& os, const Address& addr) { + switch (addr.mod()) { + case 0: + if (addr.rm() == ESP && addr.index() != ESP) { + return os << "(%" << addr.base() << ",%" + << addr.index() << "," << (1 << addr.scale()) << ")"; + } + return os << "(%" << addr.rm() << ")"; + case 1: + if (addr.rm() == ESP && addr.index() != ESP) { + return os << static_cast<int>(addr.disp8()) + << "(%" << addr.base() << ",%" + << addr.index() << "," << (1 << addr.scale()) << ")"; + } + return os << static_cast<int>(addr.disp8()) << "(%" << addr.rm() << ")"; + case 2: + if (addr.rm() == ESP && addr.index() != ESP) { + return os << static_cast<int>(addr.disp32()) + << "(%" << addr.base() << ",%" + << addr.index() << "," << (1 << addr.scale()) << ")"; + } + return os << static_cast<int>(addr.disp32()) << "(%" << addr.rm() << ")"; + default: + return os << "<address?>"; + } +} + void X86Assembler::call(Register reg) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitUint8(0xFF); diff --git a/compiler/utils/x86/assembler_x86.h b/compiler/utils/x86/assembler_x86.h index 511eeb9973..2964dbaabc 100644 --- a/compiler/utils/x86/assembler_x86.h +++ b/compiler/utils/x86/assembler_x86.h @@ -235,6 +235,7 @@ class Address : public Operand { } }; +std::ostream& operator<<(std::ostream& os, const Address& addr); // This is equivalent to the Label class, used in a slightly different context. We // inherit the functionality of the Label class, but prevent unintended diff --git a/compiler/utils/x86/assembler_x86_test.cc b/compiler/utils/x86/assembler_x86_test.cc index d2122db3fa..c28ed3b815 100644 --- a/compiler/utils/x86/assembler_x86_test.cc +++ b/compiler/utils/x86/assembler_x86_test.cc @@ -33,11 +33,21 @@ TEST(AssemblerX86, CreateBuffer) { ASSERT_EQ(static_cast<size_t>(5), buffer.Size()); } -class AssemblerX86Test : public AssemblerTest<x86::X86Assembler, x86::Register, - x86::XmmRegister, x86::Immediate> { +// +// Test fixture. +// + +class AssemblerX86Test : public AssemblerTest<x86::X86Assembler, + x86::Address, + x86::Register, + x86::XmmRegister, + x86::Immediate> { public: - typedef AssemblerTest<x86::X86Assembler, x86::Register, - x86::XmmRegister, x86::Immediate> Base; + typedef AssemblerTest<x86::X86Assembler, + x86::Address, + x86::Register, + x86::XmmRegister, + x86::Immediate> Base; protected: std::string GetArchitectureString() OVERRIDE { @@ -53,6 +63,31 @@ class AssemblerX86Test : public AssemblerTest<x86::X86Assembler, x86::Register, } void SetUpHelpers() OVERRIDE { + if (addresses_singleton_.size() == 0) { + // One addressing mode to test the repeat drivers. + addresses_singleton_.push_back(x86::Address(x86::EAX, x86::EBX, x86::TIMES_1, 2)); + } + + if (addresses_.size() == 0) { + // Several addressing modes. + addresses_.push_back(x86::Address(x86::EDI, x86::EAX, x86::TIMES_1, 15)); + addresses_.push_back(x86::Address(x86::EDI, x86::EBX, x86::TIMES_2, 16)); + addresses_.push_back(x86::Address(x86::EDI, x86::ECX, x86::TIMES_4, 17)); + addresses_.push_back(x86::Address(x86::EDI, x86::EDX, x86::TIMES_8, 18)); + addresses_.push_back(x86::Address(x86::EAX, -1)); + addresses_.push_back(x86::Address(x86::EBX, 0)); + addresses_.push_back(x86::Address(x86::ESI, 1)); + addresses_.push_back(x86::Address(x86::EDI, 987654321)); + // Several addressing modes with the special ESP. + addresses_.push_back(x86::Address(x86::ESP, x86::EAX, x86::TIMES_1, 15)); + addresses_.push_back(x86::Address(x86::ESP, x86::EBX, x86::TIMES_2, 16)); + addresses_.push_back(x86::Address(x86::ESP, x86::ECX, x86::TIMES_4, 17)); + addresses_.push_back(x86::Address(x86::ESP, x86::EDX, x86::TIMES_8, 18)); + addresses_.push_back(x86::Address(x86::ESP, -1)); + addresses_.push_back(x86::Address(x86::ESP, 0)); + addresses_.push_back(x86::Address(x86::ESP, 1)); + addresses_.push_back(x86::Address(x86::ESP, 987654321)); + } if (registers_.size() == 0) { registers_.insert(end(registers_), { // NOLINT(whitespace/braces) @@ -88,6 +123,10 @@ class AssemblerX86Test : public AssemblerTest<x86::X86Assembler, x86::Register, STLDeleteElements(&fp_registers_); } + std::vector<x86::Address> GetAddresses() OVERRIDE { + return addresses_; + } + std::vector<x86::Register*> GetRegisters() OVERRIDE { return registers_; } @@ -100,26 +139,116 @@ class AssemblerX86Test : public AssemblerTest<x86::X86Assembler, x86::Register, return x86::Immediate(imm_value); } + std::vector<x86::Address> addresses_singleton_; + private: + std::vector<x86::Address> addresses_; std::vector<x86::Register*> registers_; std::vector<x86::XmmRegister*> fp_registers_; }; +// +// Test repeat drivers used in the tests. +// + +TEST_F(AssemblerX86Test, RepeatRR) { + EXPECT_EQ("%eax %eax\n%eax %ebx\n%eax %ecx\n%eax %edx\n%eax %ebp\n%eax %esp\n%eax %esi\n" + "%eax %edi\n%ebx %eax\n%ebx %ebx\n%ebx %ecx\n%ebx %edx\n%ebx %ebp\n%ebx %esp\n" + "%ebx %esi\n%ebx %edi\n%ecx %eax\n%ecx %ebx\n%ecx %ecx\n%ecx %edx\n%ecx %ebp\n" + "%ecx %esp\n%ecx %esi\n%ecx %edi\n%edx %eax\n%edx %ebx\n%edx %ecx\n%edx %edx\n" + "%edx %ebp\n%edx %esp\n%edx %esi\n%edx %edi\n%ebp %eax\n%ebp %ebx\n%ebp %ecx\n" + "%ebp %edx\n%ebp %ebp\n%ebp %esp\n%ebp %esi\n%ebp %edi\n%esp %eax\n%esp %ebx\n" + "%esp %ecx\n%esp %edx\n%esp %ebp\n%esp %esp\n%esp %esi\n%esp %edi\n%esi %eax\n" + "%esi %ebx\n%esi %ecx\n%esi %edx\n%esi %ebp\n%esi %esp\n%esi %esi\n%esi %edi\n" + "%edi %eax\n%edi %ebx\n%edi %ecx\n%edi %edx\n%edi %ebp\n%edi %esp\n%edi %esi\n" + "%edi %edi\n", + RepeatRR(/*f*/ nullptr, "%{reg1} %{reg2}")); +} + +TEST_F(AssemblerX86Test, RepeatRI) { + EXPECT_EQ("%eax $0\n%eax $-1\n%eax $18\n%ebx $0\n%ebx $-1\n%ebx $18\n%ecx $0\n%ecx $-1\n" + "%ecx $18\n%edx $0\n%edx $-1\n%edx $18\n%ebp $0\n%ebp $-1\n%ebp $18\n%esp $0\n" + "%esp $-1\n%esp $18\n%esi $0\n%esi $-1\n%esi $18\n%edi $0\n%edi $-1\n%edi $18\n", + RepeatRI(/*f*/ nullptr, /*imm_bytes*/ 1U, "%{reg} ${imm}")); +} + +TEST_F(AssemblerX86Test, RepeatFF) { + EXPECT_EQ("%XMM0 %XMM0\n%XMM0 %XMM1\n%XMM0 %XMM2\n%XMM0 %XMM3\n%XMM0 %XMM4\n%XMM0 %XMM5\n" + "%XMM0 %XMM6\n%XMM0 %XMM7\n%XMM1 %XMM0\n%XMM1 %XMM1\n%XMM1 %XMM2\n%XMM1 %XMM3\n" + "%XMM1 %XMM4\n%XMM1 %XMM5\n%XMM1 %XMM6\n%XMM1 %XMM7\n%XMM2 %XMM0\n%XMM2 %XMM1\n" + "%XMM2 %XMM2\n%XMM2 %XMM3\n%XMM2 %XMM4\n%XMM2 %XMM5\n%XMM2 %XMM6\n%XMM2 %XMM7\n" + "%XMM3 %XMM0\n%XMM3 %XMM1\n%XMM3 %XMM2\n%XMM3 %XMM3\n%XMM3 %XMM4\n%XMM3 %XMM5\n" + "%XMM3 %XMM6\n%XMM3 %XMM7\n%XMM4 %XMM0\n%XMM4 %XMM1\n%XMM4 %XMM2\n%XMM4 %XMM3\n" + "%XMM4 %XMM4\n%XMM4 %XMM5\n%XMM4 %XMM6\n%XMM4 %XMM7\n%XMM5 %XMM0\n%XMM5 %XMM1\n" + "%XMM5 %XMM2\n%XMM5 %XMM3\n%XMM5 %XMM4\n%XMM5 %XMM5\n%XMM5 %XMM6\n%XMM5 %XMM7\n" + "%XMM6 %XMM0\n%XMM6 %XMM1\n%XMM6 %XMM2\n%XMM6 %XMM3\n%XMM6 %XMM4\n%XMM6 %XMM5\n" + "%XMM6 %XMM6\n%XMM6 %XMM7\n%XMM7 %XMM0\n%XMM7 %XMM1\n%XMM7 %XMM2\n%XMM7 %XMM3\n" + "%XMM7 %XMM4\n%XMM7 %XMM5\n%XMM7 %XMM6\n%XMM7 %XMM7\n", + RepeatFF(/*f*/ nullptr, "%{reg1} %{reg2}")); +} + +TEST_F(AssemblerX86Test, RepeatFFI) { + EXPECT_NE(RepeatFFI(/*f*/ nullptr, /*imm_bytes*/ 1U, "%{reg1} %{reg2} ${imm}") + .find("%XMM0 %XMM0 $0\n%XMM0 %XMM0 $-1\n%XMM0 %XMM0 $18\n" + "%XMM0 %XMM1 $0\n%XMM0 %XMM1 $-1\n%XMM0 %XMM1 $18\n"), + std::string::npos); +} + +TEST_F(AssemblerX86Test, RepeatA) { + EXPECT_EQ("2(%eax,%ebx,1)\n", RepeatA(/*f*/ nullptr, addresses_singleton_, "{mem}")); +} + +TEST_F(AssemblerX86Test, RepeatAI) { + EXPECT_EQ("2(%eax,%ebx,1) $0\n2(%eax,%ebx,1) $-1\n2(%eax,%ebx,1) $18\n", + RepeatAI(/*f*/ nullptr, /*imm_bytes*/ 1U, addresses_singleton_, "{mem} ${imm}")); +} + +TEST_F(AssemblerX86Test, RepeatRA) { + EXPECT_EQ("%eax 2(%eax,%ebx,1)\n%ebx 2(%eax,%ebx,1)\n%ecx 2(%eax,%ebx,1)\n" + "%edx 2(%eax,%ebx,1)\n%ebp 2(%eax,%ebx,1)\n%esp 2(%eax,%ebx,1)\n" + "%esi 2(%eax,%ebx,1)\n%edi 2(%eax,%ebx,1)\n", + RepeatRA(/*f*/ nullptr, addresses_singleton_, "%{reg} {mem}")); +} + +TEST_F(AssemblerX86Test, RepeatAR) { + EXPECT_EQ("2(%eax,%ebx,1) %eax\n2(%eax,%ebx,1) %ebx\n2(%eax,%ebx,1) %ecx\n" + "2(%eax,%ebx,1) %edx\n2(%eax,%ebx,1) %ebp\n2(%eax,%ebx,1) %esp\n" + "2(%eax,%ebx,1) %esi\n2(%eax,%ebx,1) %edi\n", + RepeatAR(/*f*/ nullptr, addresses_singleton_, "{mem} %{reg}")); +} + +TEST_F(AssemblerX86Test, RepeatFA) { + EXPECT_EQ("%XMM0 2(%eax,%ebx,1)\n%XMM1 2(%eax,%ebx,1)\n%XMM2 2(%eax,%ebx,1)\n" + "%XMM3 2(%eax,%ebx,1)\n%XMM4 2(%eax,%ebx,1)\n%XMM5 2(%eax,%ebx,1)\n" + "%XMM6 2(%eax,%ebx,1)\n%XMM7 2(%eax,%ebx,1)\n", + RepeatFA(/*f*/ nullptr, addresses_singleton_, "%{reg} {mem}")); +} + +TEST_F(AssemblerX86Test, RepeatAF) { + EXPECT_EQ("2(%eax,%ebx,1) %XMM0\n2(%eax,%ebx,1) %XMM1\n2(%eax,%ebx,1) %XMM2\n" + "2(%eax,%ebx,1) %XMM3\n2(%eax,%ebx,1) %XMM4\n2(%eax,%ebx,1) %XMM5\n" + "2(%eax,%ebx,1) %XMM6\n2(%eax,%ebx,1) %XMM7\n", + RepeatAF(/*f*/ nullptr, addresses_singleton_, "{mem} %{reg}")); +} + +// +// Actual x86 instruction assembler tests. +// TEST_F(AssemblerX86Test, Movl) { - GetAssembler()->movl(x86::EAX, x86::EBX); - const char* expected = "mov %ebx, %eax\n"; - DriverStr(expected, "movl"); + DriverStr(RepeatRR(&x86::X86Assembler::movl, "movl %{reg2}, %{reg1}"), "movl"); } -TEST_F(AssemblerX86Test, Movntl) { - GetAssembler()->movntl(x86::Address(x86::EDI, x86::EBX, x86::TIMES_4, 12), x86::EAX); - GetAssembler()->movntl(x86::Address(x86::EDI, 0), x86::EAX); - const char* expected = - "movntil %EAX, 0xc(%EDI,%EBX,4)\n" - "movntil %EAX, (%EDI)\n"; +TEST_F(AssemblerX86Test, MovlLoad) { + DriverStr(RepeatRA(&x86::X86Assembler::movl, "movl {mem}, %{reg}"), "movl-load"); +} - DriverStr(expected, "movntl"); +TEST_F(AssemblerX86Test, MovlStore) { + DriverStr(RepeatAR(&x86::X86Assembler::movl, "movl %{reg}, {mem}"), "movl-store"); +} + +TEST_F(AssemblerX86Test, Movntl) { + DriverStr(RepeatAR(&x86::X86Assembler::movntl, "movntil %{reg}, {mem}"), "movntl"); } TEST_F(AssemblerX86Test, LoadLongConstant) { @@ -133,66 +262,29 @@ TEST_F(AssemblerX86Test, LoadLongConstant) { } TEST_F(AssemblerX86Test, LockCmpxchgl) { - GetAssembler()->LockCmpxchgl(x86::Address( - x86::Register(x86::EDI), x86::Register(x86::EBX), x86::TIMES_4, 12), - x86::Register(x86::ESI)); - GetAssembler()->LockCmpxchgl(x86::Address( - x86::Register(x86::EDI), x86::Register(x86::ESI), x86::TIMES_4, 12), - x86::Register(x86::ESI)); - GetAssembler()->LockCmpxchgl(x86::Address( - x86::Register(x86::EDI), x86::Register(x86::ESI), x86::TIMES_4, 12), - x86::Register(x86::EDI)); - GetAssembler()->LockCmpxchgl(x86::Address( - x86::Register(x86::EBP), 0), x86::Register(x86::ESI)); - GetAssembler()->LockCmpxchgl(x86::Address( - x86::Register(x86::EBP), x86::Register(x86::ESI), x86::TIMES_1, 0), - x86::Register(x86::ESI)); - const char* expected = - "lock cmpxchgl %ESI, 0xc(%EDI,%EBX,4)\n" - "lock cmpxchgl %ESI, 0xc(%EDI,%ESI,4)\n" - "lock cmpxchgl %EDI, 0xc(%EDI,%ESI,4)\n" - "lock cmpxchgl %ESI, (%EBP)\n" - "lock cmpxchgl %ESI, (%EBP,%ESI,1)\n"; - - DriverStr(expected, "lock_cmpxchgl"); + DriverStr(RepeatAR(&x86::X86Assembler::LockCmpxchgl, + "lock cmpxchgl %{reg}, {mem}"), "lock_cmpxchgl"); } TEST_F(AssemblerX86Test, LockCmpxchg8b) { - GetAssembler()->LockCmpxchg8b(x86::Address( - x86::Register(x86::EDI), x86::Register(x86::EBX), x86::TIMES_4, 12)); - GetAssembler()->LockCmpxchg8b(x86::Address( - x86::Register(x86::EDI), x86::Register(x86::ESI), x86::TIMES_4, 12)); - GetAssembler()->LockCmpxchg8b(x86::Address( - x86::Register(x86::EDI), x86::Register(x86::ESI), x86::TIMES_4, 12)); - GetAssembler()->LockCmpxchg8b(x86::Address(x86::Register(x86::EBP), 0)); - GetAssembler()->LockCmpxchg8b(x86::Address( - x86::Register(x86::EBP), x86::Register(x86::ESI), x86::TIMES_1, 0)); - const char* expected = - "lock cmpxchg8b 0xc(%EDI,%EBX,4)\n" - "lock cmpxchg8b 0xc(%EDI,%ESI,4)\n" - "lock cmpxchg8b 0xc(%EDI,%ESI,4)\n" - "lock cmpxchg8b (%EBP)\n" - "lock cmpxchg8b (%EBP,%ESI,1)\n"; + DriverStr(RepeatA(&x86::X86Assembler::LockCmpxchg8b, + "lock cmpxchg8b {mem}"), "lock_cmpxchg8b"); +} - DriverStr(expected, "lock_cmpxchg8b"); +TEST_F(AssemblerX86Test, FPUIntegerLoadS) { + DriverStr(RepeatA(&x86::X86Assembler::filds, "fildl {mem}"), "fildd"); } -TEST_F(AssemblerX86Test, FPUIntegerLoad) { - GetAssembler()->filds(x86::Address(x86::Register(x86::ESP), 4)); - GetAssembler()->fildl(x86::Address(x86::Register(x86::ESP), 12)); - const char* expected = - "fildl 0x4(%ESP)\n" - "fildll 0xc(%ESP)\n"; - DriverStr(expected, "FPUIntegerLoad"); +TEST_F(AssemblerX86Test, FPUIntegerLoadL) { + DriverStr(RepeatA(&x86::X86Assembler::fildl, "fildll {mem}"), "fildl"); } -TEST_F(AssemblerX86Test, FPUIntegerStore) { - GetAssembler()->fistps(x86::Address(x86::Register(x86::ESP), 16)); - GetAssembler()->fistpl(x86::Address(x86::Register(x86::ESP), 24)); - const char* expected = - "fistpl 0x10(%ESP)\n" - "fistpll 0x18(%ESP)\n"; - DriverStr(expected, "FPUIntegerStore"); +TEST_F(AssemblerX86Test, FPUIntegerStoreS) { + DriverStr(RepeatA(&x86::X86Assembler::fistps, "fistpl {mem}"), "fistps"); +} + +TEST_F(AssemblerX86Test, FPUIntegerStoreL) { + DriverStr(RepeatA(&x86::X86Assembler::fistpl, "fistpll {mem}"), "fistpl"); } TEST_F(AssemblerX86Test, Repnescasb) { @@ -242,12 +334,7 @@ TEST_F(AssemblerX86Test, Bsfl) { } TEST_F(AssemblerX86Test, BsflAddress) { - GetAssembler()->bsfl(x86::Register(x86::EDI), x86::Address( - x86::Register(x86::EDI), x86::Register(x86::EBX), x86::TIMES_4, 12)); - const char* expected = - "bsfl 0xc(%EDI,%EBX,4), %EDI\n"; - - DriverStr(expected, "bsfl_address"); + DriverStr(RepeatRA(&x86::X86Assembler::bsfl, "bsfl {mem}, %{reg}"), "bsfl_address"); } TEST_F(AssemblerX86Test, Bsrl) { @@ -255,12 +342,7 @@ TEST_F(AssemblerX86Test, Bsrl) { } TEST_F(AssemblerX86Test, BsrlAddress) { - GetAssembler()->bsrl(x86::Register(x86::EDI), x86::Address( - x86::Register(x86::EDI), x86::Register(x86::EBX), x86::TIMES_4, 12)); - const char* expected = - "bsrl 0xc(%EDI,%EBX,4), %EDI\n"; - - DriverStr(expected, "bsrl_address"); + DriverStr(RepeatRA(&x86::X86Assembler::bsrl, "bsrl {mem}, %{reg}"), "bsrl_address"); } TEST_F(AssemblerX86Test, Popcntl) { @@ -268,26 +350,18 @@ TEST_F(AssemblerX86Test, Popcntl) { } TEST_F(AssemblerX86Test, PopcntlAddress) { - GetAssembler()->popcntl(x86::Register(x86::EDI), x86::Address( - x86::Register(x86::EDI), x86::Register(x86::EBX), x86::TIMES_4, 12)); - const char* expected = - "popcntl 0xc(%EDI,%EBX,4), %EDI\n"; - - DriverStr(expected, "popcntl_address"); + DriverStr(RepeatRA(&x86::X86Assembler::popcntl, "popcntl {mem}, %{reg}"), "popcntl_address"); } // Rorl only allows CL as the shift count. std::string rorl_fn(AssemblerX86Test::Base* assembler_test, x86::X86Assembler* assembler) { std::ostringstream str; - std::vector<x86::Register*> registers = assembler_test->GetRegisters(); - x86::Register shifter(x86::ECX); for (auto reg : registers) { assembler->rorl(*reg, shifter); str << "rorl %cl, %" << assembler_test->GetRegisterName(*reg) << "\n"; } - return str.str(); } @@ -302,15 +376,12 @@ TEST_F(AssemblerX86Test, RorlImm) { // Roll only allows CL as the shift count. std::string roll_fn(AssemblerX86Test::Base* assembler_test, x86::X86Assembler* assembler) { std::ostringstream str; - std::vector<x86::Register*> registers = assembler_test->GetRegisters(); - x86::Register shifter(x86::ECX); for (auto reg : registers) { assembler->roll(*reg, shifter); str << "roll %cl, %" << assembler_test->GetRegisterName(*reg) << "\n"; } - return str.str(); } @@ -331,41 +402,29 @@ TEST_F(AssemblerX86Test, Cvtdq2pd) { } TEST_F(AssemblerX86Test, ComissAddr) { - GetAssembler()->comiss(x86::XmmRegister(x86::XMM0), x86::Address(x86::EAX, 0)); - const char* expected = "comiss 0(%EAX), %xmm0\n"; - DriverStr(expected, "comiss"); + DriverStr(RepeatFA(&x86::X86Assembler::comiss, "comiss {mem}, %{reg}"), "comiss"); } TEST_F(AssemblerX86Test, UComissAddr) { - GetAssembler()->ucomiss(x86::XmmRegister(x86::XMM0), x86::Address(x86::EAX, 0)); - const char* expected = "ucomiss 0(%EAX), %xmm0\n"; - DriverStr(expected, "ucomiss"); + DriverStr(RepeatFA(&x86::X86Assembler::ucomiss, "ucomiss {mem}, %{reg}"), "ucomiss"); } TEST_F(AssemblerX86Test, ComisdAddr) { - GetAssembler()->comisd(x86::XmmRegister(x86::XMM0), x86::Address(x86::EAX, 0)); - const char* expected = "comisd 0(%EAX), %xmm0\n"; - DriverStr(expected, "comisd"); + DriverStr(RepeatFA(&x86::X86Assembler::comisd, "comisd {mem}, %{reg}"), "comisd"); } TEST_F(AssemblerX86Test, UComisdAddr) { - GetAssembler()->ucomisd(x86::XmmRegister(x86::XMM0), x86::Address(x86::EAX, 0)); - const char* expected = "ucomisd 0(%EAX), %xmm0\n"; - DriverStr(expected, "ucomisd"); + DriverStr(RepeatFA(&x86::X86Assembler::ucomisd, "ucomisd {mem}, %{reg}"), "ucomisd"); } TEST_F(AssemblerX86Test, RoundSS) { - GetAssembler()->roundss( - x86::XmmRegister(x86::XMM0), x86::XmmRegister(x86::XMM1), x86::Immediate(1)); - const char* expected = "roundss $1, %xmm1, %xmm0\n"; - DriverStr(expected, "roundss"); + DriverStr(RepeatFFI(&x86::X86Assembler::roundss, 1U, + "roundss ${imm}, %{reg2}, %{reg1}"), "roundss"); } TEST_F(AssemblerX86Test, RoundSD) { - GetAssembler()->roundsd( - x86::XmmRegister(x86::XMM0), x86::XmmRegister(x86::XMM1), x86::Immediate(1)); - const char* expected = "roundsd $1, %xmm1, %xmm0\n"; - DriverStr(expected, "roundsd"); + DriverStr(RepeatFFI(&x86::X86Assembler::roundsd, 1U, + "roundsd ${imm}, %{reg2}, %{reg1}"), "roundsd"); } TEST_F(AssemblerX86Test, CmovlAddress) { @@ -379,110 +438,75 @@ TEST_F(AssemblerX86Test, CmovlAddress) { "cmovzl 0xc(%EDI,%EBX,4), %eax\n" "cmovnzl 0xc(%ESI,%EBX,4), %edi\n" "cmovzl 0xc(%EDI,%EAX,4), %edi\n"; - DriverStr(expected, "cmovl_address"); } TEST_F(AssemblerX86Test, TestbAddressImmediate) { - GetAssembler()->testb( - x86::Address(x86::Register(x86::EDI), x86::Register(x86::EBX), x86::TIMES_4, 12), - x86::Immediate(1)); - GetAssembler()->testb( - x86::Address(x86::Register(x86::ESP), FrameOffset(7)), - x86::Immediate(-128)); - GetAssembler()->testb( - x86::Address(x86::Register(x86::EBX), MemberOffset(130)), - x86::Immediate(127)); - const char* expected = - "testb $1, 0xc(%EDI,%EBX,4)\n" - "testb $-128, 0x7(%ESP)\n" - "testb $127, 0x82(%EBX)\n"; - - DriverStr(expected, "TestbAddressImmediate"); + DriverStr(RepeatAI(&x86::X86Assembler::testb, /*imm_bytes*/ 1U, "testb ${imm}, {mem}"), "testb"); } TEST_F(AssemblerX86Test, TestlAddressImmediate) { - GetAssembler()->testl( - x86::Address(x86::Register(x86::EDI), x86::Register(x86::EBX), x86::TIMES_4, 12), - x86::Immediate(1)); - GetAssembler()->testl( - x86::Address(x86::Register(x86::ESP), FrameOffset(7)), - x86::Immediate(-100000)); - GetAssembler()->testl( - x86::Address(x86::Register(x86::EBX), MemberOffset(130)), - x86::Immediate(77777777)); - const char* expected = - "testl $1, 0xc(%EDI,%EBX,4)\n" - "testl $-100000, 0x7(%ESP)\n" - "testl $77777777, 0x82(%EBX)\n"; - - DriverStr(expected, "TestlAddressImmediate"); + DriverStr(RepeatAI(&x86::X86Assembler::testl, /*imm_bytes*/ 4U, "testl ${imm}, {mem}"), "testl"); } TEST_F(AssemblerX86Test, Movaps) { DriverStr(RepeatFF(&x86::X86Assembler::movaps, "movaps %{reg2}, %{reg1}"), "movaps"); } -TEST_F(AssemblerX86Test, MovapsAddr) { - GetAssembler()->movaps(x86::XmmRegister(x86::XMM0), x86::Address(x86::Register(x86::ESP), 4)); - GetAssembler()->movaps(x86::Address(x86::Register(x86::ESP), 2), x86::XmmRegister(x86::XMM1)); - const char* expected = - "movaps 0x4(%ESP), %xmm0\n" - "movaps %xmm1, 0x2(%ESP)\n"; - DriverStr(expected, "movaps_address"); +TEST_F(AssemblerX86Test, MovapsLoad) { + DriverStr(RepeatFA(&x86::X86Assembler::movaps, "movaps {mem}, %{reg}"), "movaps_load"); } -TEST_F(AssemblerX86Test, MovupsAddr) { - GetAssembler()->movups(x86::XmmRegister(x86::XMM0), x86::Address(x86::Register(x86::ESP), 4)); - GetAssembler()->movups(x86::Address(x86::Register(x86::ESP), 2), x86::XmmRegister(x86::XMM1)); - const char* expected = - "movups 0x4(%ESP), %xmm0\n" - "movups %xmm1, 0x2(%ESP)\n"; - DriverStr(expected, "movups_address"); +TEST_F(AssemblerX86Test, MovapsStore) { + DriverStr(RepeatAF(&x86::X86Assembler::movaps, "movaps %{reg}, {mem}"), "movaps_store"); +} + +TEST_F(AssemblerX86Test, MovupsLoad) { + DriverStr(RepeatFA(&x86::X86Assembler::movups, "movups {mem}, %{reg}"), "movups_load"); +} + +TEST_F(AssemblerX86Test, MovupsStore) { + DriverStr(RepeatAF(&x86::X86Assembler::movups, "movups %{reg}, {mem}"), "movups_store"); } TEST_F(AssemblerX86Test, Movapd) { DriverStr(RepeatFF(&x86::X86Assembler::movapd, "movapd %{reg2}, %{reg1}"), "movapd"); } -TEST_F(AssemblerX86Test, MovapdAddr) { - GetAssembler()->movapd(x86::XmmRegister(x86::XMM0), x86::Address(x86::Register(x86::ESP), 4)); - GetAssembler()->movapd(x86::Address(x86::Register(x86::ESP), 2), x86::XmmRegister(x86::XMM1)); - const char* expected = - "movapd 0x4(%ESP), %xmm0\n" - "movapd %xmm1, 0x2(%ESP)\n"; - DriverStr(expected, "movapd_address"); +TEST_F(AssemblerX86Test, MovapdLoad) { + DriverStr(RepeatFA(&x86::X86Assembler::movapd, "movapd {mem}, %{reg}"), "movapd_load"); } -TEST_F(AssemblerX86Test, MovupdAddr) { - GetAssembler()->movupd(x86::XmmRegister(x86::XMM0), x86::Address(x86::Register(x86::ESP), 4)); - GetAssembler()->movupd(x86::Address(x86::Register(x86::ESP), 2), x86::XmmRegister(x86::XMM1)); - const char* expected = - "movupd 0x4(%ESP), %xmm0\n" - "movupd %xmm1, 0x2(%ESP)\n"; - DriverStr(expected, "movupd_address"); +TEST_F(AssemblerX86Test, MovapdStore) { + DriverStr(RepeatAF(&x86::X86Assembler::movapd, "movapd %{reg}, {mem}"), "movapd_store"); +} + +TEST_F(AssemblerX86Test, MovupdLoad) { + DriverStr(RepeatFA(&x86::X86Assembler::movupd, "movupd {mem}, %{reg}"), "movupd_load"); +} + +TEST_F(AssemblerX86Test, MovupdStore) { + DriverStr(RepeatAF(&x86::X86Assembler::movupd, "movupd %{reg}, {mem}"), "movupd_store"); } TEST_F(AssemblerX86Test, Movdqa) { DriverStr(RepeatFF(&x86::X86Assembler::movdqa, "movdqa %{reg2}, %{reg1}"), "movdqa"); } -TEST_F(AssemblerX86Test, MovdqaAddr) { - GetAssembler()->movdqa(x86::XmmRegister(x86::XMM0), x86::Address(x86::Register(x86::ESP), 4)); - GetAssembler()->movdqa(x86::Address(x86::Register(x86::ESP), 2), x86::XmmRegister(x86::XMM1)); - const char* expected = - "movdqa 0x4(%ESP), %xmm0\n" - "movdqa %xmm1, 0x2(%ESP)\n"; - DriverStr(expected, "movdqa_address"); +TEST_F(AssemblerX86Test, MovdqaLoad) { + DriverStr(RepeatFA(&x86::X86Assembler::movdqa, "movdqa {mem}, %{reg}"), "movdqa_load"); } -TEST_F(AssemblerX86Test, MovdquAddr) { - GetAssembler()->movdqu(x86::XmmRegister(x86::XMM0), x86::Address(x86::Register(x86::ESP), 4)); - GetAssembler()->movdqu(x86::Address(x86::Register(x86::ESP), 2), x86::XmmRegister(x86::XMM1)); - const char* expected = - "movdqu 0x4(%ESP), %xmm0\n" - "movdqu %xmm1, 0x2(%ESP)\n"; - DriverStr(expected, "movdqu_address"); +TEST_F(AssemblerX86Test, MovdqaStore) { + DriverStr(RepeatAF(&x86::X86Assembler::movdqa, "movdqa %{reg}, {mem}"), "movdqa_store"); +} + +TEST_F(AssemblerX86Test, MovdquLoad) { + DriverStr(RepeatFA(&x86::X86Assembler::movdqu, "movdqu {mem}, %{reg}"), "movdqu_load"); +} + +TEST_F(AssemblerX86Test, MovdquStore) { + DriverStr(RepeatAF(&x86::X86Assembler::movdqu, "movdqu %{reg}, {mem}"), "movdqu_store"); } TEST_F(AssemblerX86Test, AddPS) { @@ -838,10 +862,6 @@ TEST_F(AssemblerX86Test, psrldq) { DriverStr("psrldq $0x10, %xmm0\n", "psrldqi"); } -///////////////// -// Near labels // -///////////////// - TEST_F(AssemblerX86Test, Jecxz) { x86::NearLabel target; GetAssembler()->jecxz(&target); @@ -851,7 +871,6 @@ TEST_F(AssemblerX86Test, Jecxz) { "jecxz 1f\n" "addl 4(%ESP),%EDI\n" "1:\n"; - DriverStr(expected, "jecxz"); } @@ -873,14 +892,11 @@ TEST_F(AssemblerX86Test, NearLabel) { "addl 4(%ESP),%EDI\n" "2: jne 1b\n" "jmp 1b\n"; - DriverStr(expected, "near_label"); } TEST_F(AssemblerX86Test, Cmpb) { - GetAssembler()->cmpb(x86::Address(x86::EDI, 128), x86::Immediate(0)); - const char* expected = "cmpb $0, 128(%EDI)\n"; - DriverStr(expected, "cmpb"); + DriverStr(RepeatAI(&x86::X86Assembler::cmpb, /*imm_bytes*/ 1U, "cmpb ${imm}, {mem}"), "cmpb"); } } // namespace art diff --git a/compiler/utils/x86_64/assembler_x86_64_test.cc b/compiler/utils/x86_64/assembler_x86_64_test.cc index 85afee0746..3e6110d4d0 100644 --- a/compiler/utils/x86_64/assembler_x86_64_test.cc +++ b/compiler/utils/x86_64/assembler_x86_64_test.cc @@ -126,11 +126,21 @@ struct X86_64CpuRegisterCompare { } }; -class AssemblerX86_64Test : public AssemblerTest<x86_64::X86_64Assembler, x86_64::CpuRegister, - x86_64::XmmRegister, x86_64::Immediate> { +// +// Test fixture. +// + +class AssemblerX86_64Test : public AssemblerTest<x86_64::X86_64Assembler, + x86_64::Address, + x86_64::CpuRegister, + x86_64::XmmRegister, + x86_64::Immediate> { public: - typedef AssemblerTest<x86_64::X86_64Assembler, x86_64::CpuRegister, - x86_64::XmmRegister, x86_64::Immediate> Base; + typedef AssemblerTest<x86_64::X86_64Assembler, + x86_64::Address, + x86_64::CpuRegister, + x86_64::XmmRegister, + x86_64::Immediate> Base; protected: // Get the typically used name for this architecture, e.g., aarch64, x86-64, ... @@ -237,6 +247,11 @@ class AssemblerX86_64Test : public AssemblerTest<x86_64::X86_64Assembler, x86_64 STLDeleteElements(&fp_registers_); } + std::vector<x86_64::Address> GetAddresses() { + UNIMPLEMENTED(FATAL) << "Feature not implemented yet"; + UNREACHABLE(); + } + std::vector<x86_64::CpuRegister*> GetRegisters() OVERRIDE { return registers_; } @@ -273,12 +288,130 @@ class AssemblerX86_64Test : public AssemblerTest<x86_64::X86_64Assembler, x86_64 std::vector<x86_64::XmmRegister*> fp_registers_; }; +// +// Test repeat drivers used in the tests. +// + +TEST_F(AssemblerX86_64Test, RepeatI4) { + EXPECT_EQ("%0\n%-1\n%18\n%4660\n%-4660\n%305419896\n%-305419896\n", + RepeatI(/*f*/ nullptr, /*imm_bytes*/ 4U, "%{imm}")); +} + +TEST_F(AssemblerX86_64Test, RepeatI8) { + EXPECT_EQ("%0\n%-1\n%18\n%4660\n%-4660\n%305419896\n%-305419896\n" + "%20015998343868\n%-20015998343868\n%1311768467463790320\n" + "%-1311768467463790320\n", + RepeatI(/*f*/ nullptr, /*imm_bytes*/ 8U, "%{imm}")); +} + +TEST_F(AssemblerX86_64Test, Repeatr) { + EXPECT_EQ("%eax\n%ebx\n%ecx\n%edx\n%ebp\n%esp\n%esi\n%edi\n" + "%r8d\n%r9d\n%r10d\n%r11d\n%r12d\n%r13d\n%r14d\n%r15d\n", + Repeatr(/*f*/ nullptr, "%{reg}")); +} + +TEST_F(AssemblerX86_64Test, Repeatri) { + EXPECT_NE(Repeatri(/*f*/ nullptr, /*imm_bytes*/ 1U, "%{reg} %{imm}"). + find("%eax %0\n%eax %-1\n%eax %18\n%ebx %0\n%ebx %-1\n%ebx %18\n" + "%ecx %0\n%ecx %-1\n%ecx %18\n%edx %0\n%edx %-1\n%edx %18\n"), + std::string::npos); +} + +TEST_F(AssemblerX86_64Test, Repeatrr) { + EXPECT_NE(Repeatrr(/*f*/ nullptr, "%{reg1} %{reg2}") + .find("%eax %eax\n%eax %ebx\n%eax %ecx\n%eax %edx\n" + "%eax %ebp\n%eax %esp\n%eax %esi\n%eax %edi\n"), + std::string::npos); +} + +TEST_F(AssemblerX86_64Test, Repeatrb) { + EXPECT_NE(Repeatrb(/*f*/ nullptr, "%{reg1} %{reg2}"). + find("%eax %al\n%eax %bl\n%eax %cl\n%eax %dl\n%eax %bpl\n" + "%eax %spl\n%eax %sil\n%eax %dil\n%eax %r8b\n%eax %r9b\n"), + std::string::npos); +} + +TEST_F(AssemblerX86_64Test, RepeatrF) { + EXPECT_NE(RepeatrF(/*f*/ nullptr, "%{reg1} %{reg2}") + .find("%eax %xmm0\n%eax %xmm1\n%eax %xmm2\n%eax %xmm3\n" + "%eax %xmm4\n%eax %xmm5\n%eax %xmm6\n%eax %xmm7\n" + "%eax %xmm8\n%eax %xmm9\n%eax %xmm10\n%eax %xmm11\n" + "%eax %xmm12\n%eax %xmm13\n%eax %xmm14\n%eax %xmm15\n" + "%ebx %xmm0\n%ebx %xmm1\n%ebx %xmm2\n%ebx %xmm3\n%ebx %xmm4\n"), + std::string::npos); +} + +TEST_F(AssemblerX86_64Test, RepeatR) { + EXPECT_EQ("%rax\n%rbx\n%rcx\n%rdx\n%rbp\n%rsp\n%rsi\n%rdi\n" + "%r8\n%r9\n%r10\n%r11\n%r12\n%r13\n%r14\n%r15\n", + RepeatR(/*f*/ nullptr, "%{reg}")); +} + +TEST_F(AssemblerX86_64Test, RepeatRI) { + EXPECT_EQ("%rax %0\n%rax %-1\n%rax %18\n%rbx %0\n%rbx %-1\n%rbx %18\n" + "%rcx %0\n%rcx %-1\n%rcx %18\n%rdx %0\n%rdx %-1\n%rdx %18\n" + "%rbp %0\n%rbp %-1\n%rbp %18\n%rsp %0\n%rsp %-1\n%rsp %18\n" + "%rsi %0\n%rsi %-1\n%rsi %18\n%rdi %0\n%rdi %-1\n%rdi %18\n" + "%r8 %0\n%r8 %-1\n%r8 %18\n%r9 %0\n%r9 %-1\n%r9 %18\n" + "%r10 %0\n%r10 %-1\n%r10 %18\n%r11 %0\n%r11 %-1\n%r11 %18\n" + "%r12 %0\n%r12 %-1\n%r12 %18\n%r13 %0\n%r13 %-1\n%r13 %18\n" + "%r14 %0\n%r14 %-1\n%r14 %18\n%r15 %0\n%r15 %-1\n%r15 %18\n", + RepeatRI(/*f*/ nullptr, /*imm_bytes*/ 1U, "%{reg} %{imm}")); +} + +TEST_F(AssemblerX86_64Test, RepeatRr) { + EXPECT_NE(RepeatRr(/*f*/ nullptr, "%{reg1} %{reg2}") + .find("%rax %eax\n%rax %ebx\n%rax %ecx\n%rax %edx\n%rax %ebp\n" + "%rax %esp\n%rax %esi\n%rax %edi\n%rax %r8d\n%rax %r9d\n" + "%rax %r10d\n%rax %r11d\n%rax %r12d\n%rax %r13d\n%rax %r14d\n" + "%rax %r15d\n%rbx %eax\n%rbx %ebx\n%rbx %ecx\n%rbx %edx\n"), + std::string::npos); +} + +TEST_F(AssemblerX86_64Test, RepeatRR) { + EXPECT_NE(RepeatRR(/*f*/ nullptr, "%{reg1} %{reg2}") + .find("%rax %rax\n%rax %rbx\n%rax %rcx\n%rax %rdx\n%rax %rbp\n" + "%rax %rsp\n%rax %rsi\n%rax %rdi\n%rax %r8\n%rax %r9\n" + "%rax %r10\n%rax %r11\n%rax %r12\n%rax %r13\n%rax %r14\n" + "%rax %r15\n%rbx %rax\n%rbx %rbx\n%rbx %rcx\n%rbx %rdx\n"), + std::string::npos); +} + +TEST_F(AssemblerX86_64Test, RepeatRF) { + EXPECT_NE(RepeatRF(/*f*/ nullptr, "%{reg1} %{reg2}") + .find("%rax %xmm0\n%rax %xmm1\n%rax %xmm2\n%rax %xmm3\n%rax %xmm4\n" + "%rax %xmm5\n%rax %xmm6\n%rax %xmm7\n%rax %xmm8\n%rax %xmm9\n" + "%rax %xmm10\n%rax %xmm11\n%rax %xmm12\n%rax %xmm13\n%rax %xmm14\n" + "%rax %xmm15\n%rbx %xmm0\n%rbx %xmm1\n%rbx %xmm2\n%rbx %xmm3\n"), + std::string::npos); +} + +TEST_F(AssemblerX86_64Test, RepeatFF) { + EXPECT_NE(RepeatFF(/*f*/ nullptr, "%{reg1} %{reg2}") + .find("%xmm0 %xmm0\n%xmm0 %xmm1\n%xmm0 %xmm2\n%xmm0 %xmm3\n%xmm0 %xmm4\n" + "%xmm0 %xmm5\n%xmm0 %xmm6\n%xmm0 %xmm7\n%xmm0 %xmm8\n%xmm0 %xmm9\n" + "%xmm0 %xmm10\n%xmm0 %xmm11\n%xmm0 %xmm12\n%xmm0 %xmm13\n%xmm0 %xmm14\n" + "%xmm0 %xmm15\n%xmm1 %xmm0\n%xmm1 %xmm1\n%xmm1 %xmm2\n%xmm1 %xmm3\n"), + std::string::npos); +} + +TEST_F(AssemblerX86_64Test, RepeatFFI) { + EXPECT_NE(RepeatFFI(/*f*/ nullptr, /*imm_bytes*/ 1U, "%{reg1} %{reg2} %{imm}") + .find("%xmm0 %xmm0 %0\n%xmm0 %xmm0 %-1\n%xmm0 %xmm0 %18\n" + "%xmm0 %xmm1 %0\n%xmm0 %xmm1 %-1\n%xmm0 %xmm1 %18\n" + "%xmm0 %xmm2 %0\n%xmm0 %xmm2 %-1\n%xmm0 %xmm2 %18\n" + "%xmm0 %xmm3 %0\n%xmm0 %xmm3 %-1\n%xmm0 %xmm3 %18\n"), + std::string::npos); +} + +// +// Actual x86-64 instruction assembler tests. +// TEST_F(AssemblerX86_64Test, Toolchain) { EXPECT_TRUE(CheckTools()); } - TEST_F(AssemblerX86_64Test, PushqRegs) { DriverStr(RepeatR(&x86_64::X86_64Assembler::pushq, "pushq %{reg}"), "pushq"); } @@ -978,10 +1111,6 @@ TEST_F(AssemblerX86_64Test, Movsxd) { DriverStr(RepeatRr(&x86_64::X86_64Assembler::movsxd, "movsxd %{reg2}, %{reg1}"), "movsxd"); } -/////////////////// -// FP Operations // -/////////////////// - TEST_F(AssemblerX86_64Test, Movaps) { DriverStr(RepeatFF(&x86_64::X86_64Assembler::movaps, "movaps %{reg2}, %{reg1}"), "movaps"); } @@ -1176,17 +1305,14 @@ TEST_F(AssemblerX86_64Test, Cvtsi2sd) { DriverStr(RepeatFr(&x86_64::X86_64Assembler::cvtsi2sd, "cvtsi2sd %{reg2}, %{reg1}"), "cvtsi2sd"); } - TEST_F(AssemblerX86_64Test, Cvtss2si) { DriverStr(RepeatrF(&x86_64::X86_64Assembler::cvtss2si, "cvtss2si %{reg2}, %{reg1}"), "cvtss2si"); } - TEST_F(AssemblerX86_64Test, Cvtss2sd) { DriverStr(RepeatFF(&x86_64::X86_64Assembler::cvtss2sd, "cvtss2sd %{reg2}, %{reg1}"), "cvtss2sd"); } - TEST_F(AssemblerX86_64Test, Cvtsd2si) { DriverStr(RepeatrF(&x86_64::X86_64Assembler::cvtsd2si, "cvtsd2si %{reg2}, %{reg1}"), "cvtsd2si"); } @@ -1586,8 +1712,6 @@ TEST_F(AssemblerX86_64Test, UcomisdAddress) { DriverStr(expected, "ucomisd_address"); } -// X87 - std::string x87_fn(AssemblerX86_64Test::Base* assembler_test ATTRIBUTE_UNUSED, x86_64::X86_64Assembler* assembler) { std::ostringstream str; @@ -1629,10 +1753,6 @@ TEST_F(AssemblerX86_64Test, FPUIntegerStore) { DriverStr(expected, "FPUIntegerStore"); } -//////////////// -// CALL / JMP // -//////////////// - TEST_F(AssemblerX86_64Test, Call) { DriverStr(RepeatR(&x86_64::X86_64Assembler::call, "call *%{reg}"), "call"); } @@ -1668,10 +1788,6 @@ TEST_F(AssemblerX86_64Test, RetAndLeave) { DriverFn(&ret_and_leave_fn, "retleave"); } -////////// -// MISC // -////////// - TEST_F(AssemblerX86_64Test, Bswapl) { DriverStr(Repeatr(&x86_64::X86_64Assembler::bswapl, "bswap %{reg}"), "bswapl"); } @@ -1824,11 +1940,6 @@ TEST_F(AssemblerX86_64Test, CmovqAddress) { DriverStr(expected, "cmovq_address"); } - -///////////////// -// Near labels // -///////////////// - TEST_F(AssemblerX86_64Test, Jrcxz) { x86_64::NearLabel target; GetAssembler()->jrcxz(&target); diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc index 5c097da16f..f243e33e1f 100644 --- a/compiler/verifier_deps_test.cc +++ b/compiler/verifier_deps_test.cc @@ -23,7 +23,7 @@ #include "compiler_callbacks.h" #include "dex/verification_results.h" #include "dex/verified_method.h" -#include "dex_file.h" +#include "dex_file-inl.h" #include "dex_file_types.h" #include "driver/compiler_driver-inl.h" #include "driver/compiler_options.h" @@ -97,7 +97,7 @@ class VerifierDepsTest : public CommonCompilerTest { callbacks_->SetVerifierDeps(nullptr); // Clear entries in the verification results to avoid hitting a DCHECK that // we always succeed inserting a new entry after verifying. - AtomicDexRefMap<const VerifiedMethod*>* map = + AtomicDexRefMap<MethodReference, const VerifiedMethod*>* map = &compiler_driver_->GetVerificationResults()->atomic_verified_methods_; map->Visit([](const DexFileReference& ref ATTRIBUTE_UNUSED, const VerifiedMethod* method) { delete method; diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 7ba165e2cb..d8caf42f69 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -1865,6 +1865,9 @@ class Dex2Oat FINAL { swap_fd_, profile_compilation_info_.get())); driver_->SetDexFilesForOatFile(dex_files_); + if (!IsBootImage()) { + driver_->SetClasspathDexFiles(class_loader_context_->FlattenOpenedDexFiles()); + } const bool compile_individually = ShouldCompileDexFilesIndividually(); if (compile_individually) { @@ -2116,8 +2119,6 @@ class Dex2Oat FINAL { std::unique_ptr<ElfWriter>& elf_writer = elf_writers_[i]; std::unique_ptr<OatWriter>& oat_writer = oat_writers_[i]; - oat_writer->AddMethodDebugInfos(debug::MakeTrampolineInfos(oat_writer->GetOatHeader())); - // We need to mirror the layout of the ELF file in the compressed debug-info. // Therefore PrepareDebugInfo() relies on the SetLoadedSectionSizes() call further above. elf_writer->PrepareDebugInfo(oat_writer->GetMethodDebugInfo()); @@ -2363,27 +2364,6 @@ class Dex2Oat FINAL { return dex_files_size >= very_large_threshold_; } - std::vector<std::string> GetClassPathLocations(const std::string& class_path) { - // This function is used only for apps and for an app we have exactly one oat file. - DCHECK(!IsBootImage()); - DCHECK_EQ(oat_writers_.size(), 1u); - std::vector<std::string> dex_files_canonical_locations; - for (const std::string& location : oat_writers_[0]->GetSourceLocations()) { - dex_files_canonical_locations.push_back(DexFile::GetDexCanonicalLocation(location.c_str())); - } - - std::vector<std::string> parsed; - Split(class_path, ':', &parsed); - auto kept_it = std::remove_if(parsed.begin(), - parsed.end(), - [dex_files_canonical_locations](const std::string& location) { - return ContainsElement(dex_files_canonical_locations, - DexFile::GetDexCanonicalLocation(location.c_str())); - }); - parsed.erase(kept_it, parsed.end()); - return parsed; - } - bool PrepareImageClasses() { // If --image-classes was specified, calculate the full list of classes to include in the image. if (image_classes_filename_ != nullptr) { diff --git a/dex2oat/dex2oat_image_test.cc b/dex2oat/dex2oat_image_test.cc index 04c2fcdd4c..7ddf1c16ec 100644 --- a/dex2oat/dex2oat_image_test.cc +++ b/dex2oat/dex2oat_image_test.cc @@ -87,16 +87,14 @@ class Dex2oatImageTest : public CommonRuntimeTest { void GenerateClasses(File* out_file, size_t frequency = 1) { VisitLibcoreDexes(VoidFunctor(), [out_file](TypeReference ref) { - WriteLine(out_file, - ref.dex_file->PrettyType(ref.type_index)); + WriteLine(out_file, ref.dex_file->PrettyType(ref.TypeIndex())); }, frequency, frequency); EXPECT_EQ(out_file->Flush(), 0); } void GenerateMethods(File* out_file, size_t frequency = 1) { VisitLibcoreDexes([out_file](MethodReference ref) { - WriteLine(out_file, - ref.dex_file->PrettyMethod(ref.dex_method_index)); + WriteLine(out_file, ref.PrettyMethod()); }, VoidFunctor(), frequency, frequency); EXPECT_EQ(out_file->Flush(), 0); } @@ -315,7 +313,7 @@ TEST_F(Dex2oatImageTest, TestModesAndFilters) { VisitLibcoreDexes([&profile](MethodReference ref) { EXPECT_TRUE(profile.AddMethodIndex(ProfileCompilationInfo::MethodHotness::kFlagHot, ref)); }, [&profile](TypeReference ref) { - EXPECT_TRUE(profile.AddClassesForDex(ref.dex_file, &ref.type_index, &ref.type_index + 1)); + EXPECT_TRUE(profile.AddClassForDex(ref)); }, kMethodFrequency, kTypeFrequency); ScratchFile profile_file; profile.Save(profile_file.GetFile()->Fd()); diff --git a/dexlayout/dex_ir.h b/dexlayout/dex_ir.h index 9c887b9a17..362c08b918 100644 --- a/dexlayout/dex_ir.h +++ b/dexlayout/dex_ir.h @@ -26,6 +26,7 @@ #include "base/stl_util.h" #include "dex_file-inl.h" +#include "dex_file_types.h" #include "leb128.h" #include "utf.h" @@ -250,7 +251,7 @@ class Collections { } StringId* GetStringIdOrNullPtr(uint32_t index) { - return index == DexFile::kDexNoIndex ? nullptr : GetStringId(index); + return index == dex::kDexNoIndex ? nullptr : GetStringId(index); } TypeId* GetTypeIdOrNullPtr(uint16_t index) { return index == DexFile::kDexNoIndex16 ? nullptr : GetTypeId(index); diff --git a/dexlayout/dex_writer.cc b/dexlayout/dex_writer.cc index e1b828ca52..11ba2a6357 100644 --- a/dexlayout/dex_writer.cc +++ b/dexlayout/dex_writer.cc @@ -16,12 +16,14 @@ * Header file of an in-memory representation of DEX files. */ +#include "dex_writer.h" + #include <stdint.h> #include <queue> #include <vector> -#include "dex_writer.h" +#include "dex_file_types.h" #include "utf.h" namespace art { @@ -462,10 +464,10 @@ void DexWriter::WriteClasses() { for (std::unique_ptr<dex_ir::ClassDef>& class_def : header_->GetCollections().ClassDefs()) { class_def_buffer[0] = class_def->ClassType()->GetIndex(); class_def_buffer[1] = class_def->GetAccessFlags(); - class_def_buffer[2] = class_def->Superclass() == nullptr ? DexFile::kDexNoIndex : + class_def_buffer[2] = class_def->Superclass() == nullptr ? dex::kDexNoIndex : class_def->Superclass()->GetIndex(); class_def_buffer[3] = class_def->InterfacesOffset(); - class_def_buffer[4] = class_def->SourceFile() == nullptr ? DexFile::kDexNoIndex : + class_def_buffer[4] = class_def->SourceFile() == nullptr ? dex::kDexNoIndex : class_def->SourceFile()->GetIndex(); class_def_buffer[5] = class_def->Annotations() == nullptr ? 0 : class_def->Annotations()->GetOffset(); diff --git a/dexlayout/dexlayout.cc b/dexlayout/dexlayout.cc index 401a3ee284..92a1366778 100644 --- a/dexlayout/dexlayout.cc +++ b/dexlayout/dexlayout.cc @@ -35,6 +35,7 @@ #include "dex_file-inl.h" #include "dex_file_layout.h" +#include "dex_file_types.h" #include "dex_file_verifier.h" #include "dex_instruction-inl.h" #include "dex_ir_builder.h" @@ -366,7 +367,7 @@ static std::unique_ptr<char[]> IndexString(dex_ir::Header* header, std::unique_ptr<char[]> buf(new char[buf_size]); // Determine index and width of the string. uint32_t index = 0; - uint32_t secondary_index = DexFile::kDexNoIndex; + uint32_t secondary_index = dex::kDexNoIndex; uint32_t width = 4; switch (Instruction::FormatOf(dec_insn->Opcode())) { // SOME NOT SUPPORTED: diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc index fc72bbdb87..51a67ca45e 100644 --- a/dexoptanalyzer/dexoptanalyzer.cc +++ b/dexoptanalyzer/dexoptanalyzer.cc @@ -229,6 +229,8 @@ class DexoptAnalyzer FINAL { if (oat_file_assistant.IsInBootClassPath()) { return kNoDexOptNeeded; } + + // TODO(calin): Pass the class loader context as an argument to dexoptanalyzer. b/62269291. int dexoptNeeded = oat_file_assistant.GetDexOptNeeded( compiler_filter_, assume_profile_changed_, downgrade_); diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index 4161067250..7b11258a89 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -178,10 +178,32 @@ class OatSymbolizer FINAL { oat_file_->BssRootsOffset()); builder_->WriteDynamicSection(); + const OatHeader& oat_header = oat_file_->GetOatHeader(); + #define DO_TRAMPOLINE(fn_name) \ + if (oat_header.Get ## fn_name ## Offset() != 0) { \ + debug::MethodDebugInfo info = {}; \ + info.trampoline_name = #fn_name; \ + info.isa = oat_header.GetInstructionSet(); \ + info.is_code_address_text_relative = true; \ + size_t code_offset = oat_header.Get ## fn_name ## Offset(); \ + code_offset -= CompiledCode::CodeDelta(oat_header.GetInstructionSet()); \ + info.code_address = code_offset - oat_header.GetExecutableOffset(); \ + info.code_size = 0; /* The symbol lasts until the next symbol. */ \ + method_debug_infos_.push_back(std::move(info)); \ + } + DO_TRAMPOLINE(InterpreterToInterpreterBridge) + DO_TRAMPOLINE(InterpreterToCompiledCodeBridge) + DO_TRAMPOLINE(JniDlsymLookup); + DO_TRAMPOLINE(QuickGenericJniTrampoline); + DO_TRAMPOLINE(QuickImtConflictTrampoline); + DO_TRAMPOLINE(QuickResolutionTrampoline); + DO_TRAMPOLINE(QuickToInterpreterBridge); + #undef DO_TRAMPOLINE + Walk(); - for (const auto& trampoline : debug::MakeTrampolineInfos(oat_file_->GetOatHeader())) { - method_debug_infos_.push_back(trampoline); - } + + // TODO: Try to symbolize link-time thunks? + // This would require disassembling all methods to find branches outside the method code. debug::WriteDebugInfo(builder_.get(), ArrayRef<const debug::MethodDebugInfo>(method_debug_infos_), @@ -282,8 +304,8 @@ class OatSymbolizer FINAL { // Clear Thumb2 bit. const void* code_address = EntryPointToCodePointer(reinterpret_cast<void*>(entry_point)); - debug::MethodDebugInfo info = debug::MethodDebugInfo(); - info.trampoline_name = nullptr; + debug::MethodDebugInfo info = {}; + DCHECK(info.trampoline_name.empty()); info.dex_file = &dex_file; info.class_def_index = class_def_index; info.dex_method_index = dex_method_index; @@ -914,7 +936,7 @@ class OatDumper { } // Unique string ids loaded from dex code. - std::set<StringReference, StringReferenceComparator> unique_string_ids_from_code_; + std::set<StringReference> unique_string_ids_from_code_; // Total string ids loaded from dex code. size_t num_string_ids_from_code_ = 0; diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc index 277f611eb7..4339b2bdef 100644 --- a/openjdkjvmti/OpenjdkJvmTi.cc +++ b/openjdkjvmti/OpenjdkJvmTi.cc @@ -207,11 +207,11 @@ class JvmtiFunctions { } static jvmtiError GetCurrentContendedMonitor(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jobject* monitor_ptr ATTRIBUTE_UNUSED) { + jthread thread, + jobject* monitor_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_get_current_contended_monitor); - return ERR(NOT_IMPLEMENTED); + return MonitorUtil::GetCurrentContendedMonitor(env, thread, monitor_ptr); } static jvmtiError RunAgentThread(jvmtiEnv* env, diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h index d3f52f638d..10ddfc1fe4 100644 --- a/openjdkjvmti/art_jvmti.h +++ b/openjdkjvmti/art_jvmti.h @@ -225,7 +225,7 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_get_bytecodes = 1, .can_get_synthetic_attribute = 1, .can_get_owned_monitor_info = 1, - .can_get_current_contended_monitor = 0, + .can_get_current_contended_monitor = 1, .can_get_monitor_info = 1, .can_pop_frame = 0, .can_redefine_classes = 1, @@ -247,7 +247,7 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_generate_method_exit_events = 1, .can_generate_all_class_hook_events = 0, .can_generate_compiled_method_load_events = 0, - .can_generate_monitor_events = 0, + .can_generate_monitor_events = 1, .can_generate_vm_object_alloc_events = 1, .can_generate_native_method_bind_events = 1, .can_generate_garbage_collection_events = 1, diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h index 31edc73ac6..ab8e6def2d 100644 --- a/openjdkjvmti/events-inl.h +++ b/openjdkjvmti/events-inl.h @@ -18,10 +18,13 @@ #define ART_OPENJDKJVMTI_EVENTS_INL_H_ #include <array> +#include <type_traits> +#include <tuple> #include "events.h" #include "jni_internal.h" #include "nativehelper/ScopedLocalRef.h" +#include "scoped_thread_state_change-inl.h" #include "ti_breakpoint.h" #include "art_jvmti.h" @@ -115,7 +118,6 @@ FORALL_EVENT_TYPES(GET_CALLBACK) // C++ does not allow partial template function specialization. The dispatch for our separated // ClassFileLoadHook event types is the same, so use this helper for code deduplication. -// TODO Locking of some type! template <ArtJvmtiEvent kEvent> inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, JNIEnv* jnienv, @@ -137,37 +139,30 @@ inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, if (env == nullptr) { continue; } - if (ShouldDispatch<kEvent>(env, thread)) { - ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); - jnienv->ExceptionClear(); - jint new_len = 0; - unsigned char* new_data = nullptr; - auto callback = impl::GetCallback<kEvent>(env); - callback(env, - jnienv, - class_being_redefined, - loader, - name, - protection_domain, - current_len, - current_class_data, - &new_len, - &new_data); - if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { - jnienv->Throw(thr.get()); - } - if (new_data != nullptr && new_data != current_class_data) { - // Destroy the data the last transformer made. We skip this if the previous state was the - // initial one since we don't know here which jvmtiEnv allocated it. - // NB Currently this doesn't matter since all allocations just go to malloc but in the - // future we might have jvmtiEnv's keep track of their allocations for leak-checking. - if (last_env != nullptr) { - last_env->Deallocate(current_class_data); - } - last_env = env; - current_class_data = new_data; - current_len = new_len; + jint new_len = 0; + unsigned char* new_data = nullptr; + DispatchEventOnEnv<kEvent>(env, + thread, + jnienv, + class_being_redefined, + loader, + name, + protection_domain, + current_len, + static_cast<const unsigned char*>(current_class_data), + &new_len, + &new_data); + if (new_data != nullptr && new_data != current_class_data) { + // Destroy the data the last transformer made. We skip this if the previous state was the + // initial one since we don't know here which jvmtiEnv allocated it. + // NB Currently this doesn't matter since all allocations just go to malloc but in the + // future we might have jvmtiEnv's keep track of their allocations for leak-checking. + if (last_env != nullptr) { + last_env->Deallocate(current_class_data); } + last_env = env; + current_class_data = new_data; + current_len = new_len; } } if (last_env != nullptr) { @@ -180,97 +175,125 @@ inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, // exactly the argument types of the corresponding Jvmti kEvent function pointer. template <ArtJvmtiEvent kEvent, typename ...Args> +inline void EventHandler::ExecuteCallback(ArtJvmTiEnv* env, Args... args) { + using FnType = typename impl::EventFnType<kEvent>::type; + FnType callback = impl::GetCallback<kEvent>(env); + if (callback != nullptr) { + (*callback)(env, args...); + } +} + +template <ArtJvmtiEvent kEvent, typename ...Args> inline void EventHandler::DispatchEvent(art::Thread* thread, Args... args) const { + static_assert(!std::is_same<JNIEnv*, + typename std::decay_t< + std::tuple_element_t<0, std::tuple<Args..., nullptr_t>>>>::value, + "Should be calling DispatchEvent with explicit JNIEnv* argument!"); + DCHECK(thread == nullptr || !thread->IsExceptionPending()); for (ArtJvmTiEnv* env : envs) { if (env != nullptr) { - DispatchEvent<kEvent, Args...>(env, thread, args...); + DispatchEventOnEnv<kEvent, Args...>(env, thread, args...); } } } -// Events with JNIEnvs need to stash pending exceptions since they can cause new ones to be thrown. -// In accordance with the JVMTI specification we allow exceptions originating from events to -// overwrite the current exception, including exceptions originating from earlier events. -// TODO It would be nice to add the overwritten exceptions to the suppressed exceptions list of the -// newest exception. +// Helper for ensuring that the dispatch environment is sane. Events with JNIEnvs need to stash +// pending exceptions since they can cause new ones to be thrown. In accordance with the JVMTI +// specification we allow exceptions originating from events to overwrite the current exception, +// including exceptions originating from earlier events. +class ScopedEventDispatchEnvironment FINAL : public art::ValueObject { + public: + explicit ScopedEventDispatchEnvironment(JNIEnv* env) + : env_(env), + thr_(env_, env_->ExceptionOccurred()), + suspend_(art::Thread::Current(), art::kNative) { + // The spec doesn't say how much local data should be there, so we just give 128 which seems + // likely to be enough for most cases. + env_->PushLocalFrame(128); + env_->ExceptionClear(); + UNUSED(suspend_); + } + + ~ScopedEventDispatchEnvironment() { + if (thr_.get() != nullptr && !env_->ExceptionCheck()) { + // TODO It would be nice to add the overwritten exceptions to the suppressed exceptions list + // of the newest exception. + env_->Throw(thr_.get()); + } + env_->PopLocalFrame(nullptr); + } + + private: + JNIEnv* env_; + ScopedLocalRef<jthrowable> thr_; + // Not actually unused. The destructor/constructor does important work. + art::ScopedThreadStateChange suspend_; + + DISALLOW_COPY_AND_ASSIGN(ScopedEventDispatchEnvironment); +}; + template <ArtJvmtiEvent kEvent, typename ...Args> inline void EventHandler::DispatchEvent(art::Thread* thread, JNIEnv* jnienv, Args... args) const { for (ArtJvmTiEnv* env : envs) { if (env != nullptr) { - ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); - jnienv->ExceptionClear(); - DispatchEvent<kEvent, JNIEnv*, Args...>(env, thread, jnienv, args...); - if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { - jnienv->Throw(thr.get()); - } + DispatchEventOnEnv<kEvent, Args...>(env, thread, jnienv, args...); } } } template <ArtJvmtiEvent kEvent, typename ...Args> -inline void EventHandler::DispatchEvent(ArtJvmTiEnv* env, art::Thread* thread, Args... args) const { - using FnType = void(jvmtiEnv*, Args...); - if (ShouldDispatch<kEvent>(env, thread)) { - FnType* callback = impl::GetCallback<kEvent>(env); - if (callback != nullptr) { - (*callback)(env, args...); - } +inline void EventHandler::DispatchEventOnEnv( + ArtJvmTiEnv* env, art::Thread* thread, JNIEnv* jnienv, Args... args) const { + DCHECK(env != nullptr); + if (ShouldDispatch<kEvent, JNIEnv*, Args...>(env, thread, jnienv, args...)) { + ScopedEventDispatchEnvironment sede(jnienv); + ExecuteCallback<kEvent, JNIEnv*, Args...>(env, jnienv, args...); } } +template <ArtJvmtiEvent kEvent, typename ...Args> +inline void EventHandler::DispatchEventOnEnv( + ArtJvmTiEnv* env, art::Thread* thread, Args... args) const { + static_assert(!std::is_same<JNIEnv*, + typename std::decay_t< + std::tuple_element_t<0, std::tuple<Args..., nullptr_t>>>>::value, + "Should be calling DispatchEventOnEnv with explicit JNIEnv* argument!"); + if (ShouldDispatch<kEvent>(env, thread, args...)) { + ExecuteCallback<kEvent, Args...>(env, args...); + } +} + +// Events that need custom logic for if we send the event but are otherwise normal. This includes +// the kBreakpoint, kFramePop, kFieldAccess, and kFieldModification events. + // Need to give custom specializations for Breakpoint since it needs to filter out which particular // methods/dex_pcs agents get notified on. template <> -inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kBreakpoint>(art::Thread* thread, - JNIEnv* jnienv, - jthread jni_thread, - jmethodID jmethod, - jlocation location) const { +inline bool EventHandler::ShouldDispatch<ArtJvmtiEvent::kBreakpoint>( + ArtJvmTiEnv* env, + art::Thread* thread, + JNIEnv* jnienv ATTRIBUTE_UNUSED, + jthread jni_thread ATTRIBUTE_UNUSED, + jmethodID jmethod, + jlocation location) const { art::ArtMethod* method = art::jni::DecodeArtMethod(jmethod); - for (ArtJvmTiEnv* env : envs) { - // Search for a breakpoint on this particular method and location. - if (env != nullptr && - ShouldDispatch<ArtJvmtiEvent::kBreakpoint>(env, thread) && - env->breakpoints.find({method, location}) != env->breakpoints.end()) { - // We temporarily clear any pending exceptions so the event can call back into java code. - ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); - jnienv->ExceptionClear(); - auto callback = impl::GetCallback<ArtJvmtiEvent::kBreakpoint>(env); - (*callback)(env, jnienv, jni_thread, jmethod, location); - if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { - jnienv->Throw(thr.get()); - } - } - } + return ShouldDispatchOnThread<ArtJvmtiEvent::kBreakpoint>(env, thread) && + env->breakpoints.find({method, location}) != env->breakpoints.end(); } -// Need to give custom specializations for FramePop since it needs to filter out which particular -// agents get the event. This specialization gets an extra argument so we can determine which (if -// any) environments have the frame pop. template <> -inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFramePop>( +inline bool EventHandler::ShouldDispatch<ArtJvmtiEvent::kFramePop>( + ArtJvmTiEnv* env, art::Thread* thread, - JNIEnv* jnienv, - jthread jni_thread, - jmethodID jmethod, - jboolean is_exception, + JNIEnv* jnienv ATTRIBUTE_UNUSED, + jthread jni_thread ATTRIBUTE_UNUSED, + jmethodID jmethod ATTRIBUTE_UNUSED, + jboolean is_exception ATTRIBUTE_UNUSED, const art::ShadowFrame* frame) const { - for (ArtJvmTiEnv* env : envs) { - // Search for the frame. Do this before checking if we need to send the event so that we don't - // have to deal with use-after-free or the frames being reallocated later. - if (env != nullptr && env->notify_frames.erase(frame) != 0) { - if (ShouldDispatch<ArtJvmtiEvent::kFramePop>(env, thread)) { - // We temporarily clear any pending exceptions so the event can call back into java code. - ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); - jnienv->ExceptionClear(); - auto callback = impl::GetCallback<ArtJvmtiEvent::kFramePop>(env); - (*callback)(env, jnienv, jni_thread, jmethod, is_exception); - if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { - jnienv->Throw(thr.get()); - } - } - } - } + // Search for the frame. Do this before checking if we need to send the event so that we don't + // have to deal with use-after-free or the frames being reallocated later. + return env->notify_frames.erase(frame) != 0 && + ShouldDispatchOnThread<ArtJvmtiEvent::kFramePop>(env, thread); } // Need to give custom specializations for FieldAccess and FieldModification since they need to @@ -278,64 +301,54 @@ inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFramePop>( // TODO The spec allows us to do shortcuts like only allow one agent to ever set these watches. This // could make the system more performant. template <> -inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFieldModification>(art::Thread* thread, - JNIEnv* jnienv, - jthread jni_thread, - jmethodID method, - jlocation location, - jclass field_klass, - jobject object, - jfieldID field, - char type_char, - jvalue val) const { - for (ArtJvmTiEnv* env : envs) { - if (env != nullptr && - ShouldDispatch<ArtJvmtiEvent::kFieldModification>(env, thread) && - env->modify_watched_fields.find( - art::jni::DecodeArtField(field)) != env->modify_watched_fields.end()) { - ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); - jnienv->ExceptionClear(); - auto callback = impl::GetCallback<ArtJvmtiEvent::kFieldModification>(env); - (*callback)(env, - jnienv, - jni_thread, - method, - location, - field_klass, - object, - field, - type_char, - val); - if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { - jnienv->Throw(thr.get()); - } - } - } +inline bool EventHandler::ShouldDispatch<ArtJvmtiEvent::kFieldModification>( + ArtJvmTiEnv* env, + art::Thread* thread, + JNIEnv* jnienv ATTRIBUTE_UNUSED, + jthread jni_thread ATTRIBUTE_UNUSED, + jmethodID method ATTRIBUTE_UNUSED, + jlocation location ATTRIBUTE_UNUSED, + jclass field_klass ATTRIBUTE_UNUSED, + jobject object ATTRIBUTE_UNUSED, + jfieldID field, + char type_char ATTRIBUTE_UNUSED, + jvalue val ATTRIBUTE_UNUSED) const { + return ShouldDispatchOnThread<ArtJvmtiEvent::kFieldModification>(env, thread) && + env->modify_watched_fields.find( + art::jni::DecodeArtField(field)) != env->modify_watched_fields.end(); } template <> -inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFieldAccess>(art::Thread* thread, - JNIEnv* jnienv, - jthread jni_thread, - jmethodID method, - jlocation location, - jclass field_klass, - jobject object, - jfieldID field) const { - for (ArtJvmTiEnv* env : envs) { - if (env != nullptr && - ShouldDispatch<ArtJvmtiEvent::kFieldAccess>(env, thread) && - env->access_watched_fields.find( - art::jni::DecodeArtField(field)) != env->access_watched_fields.end()) { - ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); - jnienv->ExceptionClear(); - auto callback = impl::GetCallback<ArtJvmtiEvent::kFieldAccess>(env); - (*callback)(env, jnienv, jni_thread, method, location, field_klass, object, field); - if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { - jnienv->Throw(thr.get()); - } - } - } +inline bool EventHandler::ShouldDispatch<ArtJvmtiEvent::kFieldAccess>( + ArtJvmTiEnv* env, + art::Thread* thread, + JNIEnv* jnienv ATTRIBUTE_UNUSED, + jthread jni_thread ATTRIBUTE_UNUSED, + jmethodID method ATTRIBUTE_UNUSED, + jlocation location ATTRIBUTE_UNUSED, + jclass field_klass ATTRIBUTE_UNUSED, + jobject object ATTRIBUTE_UNUSED, + jfieldID field) const { + return ShouldDispatchOnThread<ArtJvmtiEvent::kFieldAccess>(env, thread) && + env->access_watched_fields.find( + art::jni::DecodeArtField(field)) != env->access_watched_fields.end(); +} + +// Need to give custom specializations for FramePop since it needs to filter out which particular +// agents get the event. This specialization gets an extra argument so we can determine which (if +// any) environments have the frame pop. +// TODO It might be useful to use more template magic to have this only define ShouldDispatch or +// something. +template <> +inline void EventHandler::ExecuteCallback<ArtJvmtiEvent::kFramePop>( + ArtJvmTiEnv* env, + JNIEnv* jnienv, + jthread jni_thread, + jmethodID jmethod, + jboolean is_exception, + const art::ShadowFrame* frame ATTRIBUTE_UNUSED) { + ExecuteCallback<ArtJvmtiEvent::kFramePop>( + env, jnienv, jni_thread, jmethod, is_exception); } // Need to give a custom specialization for NativeMethodBind since it has to deal with an out @@ -349,14 +362,21 @@ inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kNativeMethodBind>(art::T void** new_method) const { *new_method = cur_method; for (ArtJvmTiEnv* env : envs) { - if (env != nullptr && ShouldDispatch<ArtJvmtiEvent::kNativeMethodBind>(env, thread)) { - auto callback = impl::GetCallback<ArtJvmtiEvent::kNativeMethodBind>(env); - (*callback)(env, jnienv, jni_thread, method, cur_method, new_method); + if (env != nullptr) { + *new_method = cur_method; + DispatchEventOnEnv<ArtJvmtiEvent::kNativeMethodBind>(env, + thread, + jnienv, + jni_thread, + method, + cur_method, + new_method); if (*new_method != nullptr) { cur_method = *new_method; } } } + *new_method = cur_method; } // C++ does not allow partial template function specialization. The dispatch for our separated @@ -386,6 +406,7 @@ inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetrans new_class_data_len, new_class_data); } + template <> inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>( art::Thread* thread, @@ -412,8 +433,7 @@ inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetr } template <ArtJvmtiEvent kEvent> -inline bool EventHandler::ShouldDispatch(ArtJvmTiEnv* env, - art::Thread* thread) { +inline bool EventHandler::ShouldDispatchOnThread(ArtJvmTiEnv* env, art::Thread* thread) { bool dispatch = env->event_masks.global_event_mask.Test(kEvent); if (!dispatch && thread != nullptr && env->event_masks.unioned_thread_event_mask.Test(kEvent)) { @@ -423,6 +443,17 @@ inline bool EventHandler::ShouldDispatch(ArtJvmTiEnv* env, return dispatch; } +template <ArtJvmtiEvent kEvent, typename ...Args> +inline bool EventHandler::ShouldDispatch(ArtJvmTiEnv* env, + art::Thread* thread, + Args... args ATTRIBUTE_UNUSED) const { + static_assert(std::is_same<typename impl::EventFnType<kEvent>::type, + void(*)(jvmtiEnv*, Args...)>::value, + "Unexpected different type of shouldDispatch"); + + return ShouldDispatchOnThread<kEvent>(env, thread); +} + inline void EventHandler::RecalculateGlobalEventMask(ArtJvmtiEvent event) { bool union_value = false; for (const ArtJvmTiEnv* stored_env : envs) { diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc index 00c81e2b3b..32dd69e0e6 100644 --- a/openjdkjvmti/events.cc +++ b/openjdkjvmti/events.cc @@ -31,10 +31,13 @@ #include "events-inl.h" +#include <array> + #include "art_field-inl.h" #include "art_jvmti.h" #include "art_method-inl.h" #include "base/logging.h" +#include "dex_file_types.h" #include "gc/allocation_listener.h" #include "gc/gc_pause_listener.h" #include "gc/heap.h" @@ -45,6 +48,7 @@ #include "jni_internal.h" #include "mirror/class.h" #include "mirror/object-inl.h" +#include "monitor.h" #include "nativehelper/ScopedLocalRef.h" #include "runtime.h" #include "scoped_thread_state_change-inl.h" @@ -189,6 +193,25 @@ static bool IsThreadControllable(ArtJvmtiEvent event) { } } +template<typename Type> +static Type AddLocalRef(art::JNIEnvExt* e, art::mirror::Object* obj) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + return (obj == nullptr) ? nullptr : e->AddLocalReference<Type>(obj); +} + +template<ArtJvmtiEvent kEvent, typename ...Args> +static void RunEventCallback(EventHandler* handler, + art::Thread* self, + art::JNIEnvExt* jnienv, + Args... args) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + ScopedLocalRef<jthread> thread_jni(jnienv, AddLocalRef<jthread>(jnienv, self->GetPeer())); + handler->DispatchEvent<kEvent>(self, + static_cast<JNIEnv*>(jnienv), + thread_jni.get(), + args...); +} + class JvmtiAllocationListener : public art::gc::AllocationListener { public: explicit JvmtiAllocationListener(EventHandler* handler) : handler_(handler) {} @@ -208,26 +231,17 @@ class JvmtiAllocationListener : public art::gc::AllocationListener { // jclass object_klass, // jlong size art::JNIEnvExt* jni_env = self->GetJniEnv(); - - jthread thread_peer; - if (self->IsStillStarting()) { - thread_peer = nullptr; - } else { - thread_peer = jni_env->AddLocalReference<jthread>(self->GetPeer()); - } - - ScopedLocalRef<jthread> thread(jni_env, thread_peer); ScopedLocalRef<jobject> object( jni_env, jni_env->AddLocalReference<jobject>(*obj)); ScopedLocalRef<jclass> klass( jni_env, jni_env->AddLocalReference<jclass>(obj->Ptr()->GetClass())); - handler_->DispatchEvent<ArtJvmtiEvent::kVmObjectAlloc>(self, - reinterpret_cast<JNIEnv*>(jni_env), - thread.get(), - object.get(), - klass.get(), - static_cast<jlong>(byte_count)); + RunEventCallback<ArtJvmtiEvent::kVmObjectAlloc>(handler_, + self, + jni_env, + object.get(), + klass.get(), + static_cast<jlong>(byte_count)); } } @@ -247,6 +261,95 @@ static void SetupObjectAllocationTracking(art::gc::AllocationListener* listener, } } +class JvmtiMonitorListener : public art::MonitorCallback { + public: + explicit JvmtiMonitorListener(EventHandler* handler) : handler_(handler) {} + + void MonitorContendedLocking(art::Monitor* m) + OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMonitorContendedEnter)) { + art::Thread* self = art::Thread::Current(); + art::JNIEnvExt* jnienv = self->GetJniEnv(); + ScopedLocalRef<jobject> mon(jnienv, AddLocalRef<jobject>(jnienv, m->GetObject())); + RunEventCallback<ArtJvmtiEvent::kMonitorContendedEnter>( + handler_, + self, + jnienv, + mon.get()); + } + } + + void MonitorContendedLocked(art::Monitor* m) + OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMonitorContendedEntered)) { + art::Thread* self = art::Thread::Current(); + art::JNIEnvExt* jnienv = self->GetJniEnv(); + ScopedLocalRef<jobject> mon(jnienv, AddLocalRef<jobject>(jnienv, m->GetObject())); + RunEventCallback<ArtJvmtiEvent::kMonitorContendedEntered>( + handler_, + self, + jnienv, + mon.get()); + } + } + + void ObjectWaitStart(art::Handle<art::mirror::Object> obj, int64_t timeout) + OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMonitorWait)) { + art::Thread* self = art::Thread::Current(); + art::JNIEnvExt* jnienv = self->GetJniEnv(); + ScopedLocalRef<jobject> mon(jnienv, AddLocalRef<jobject>(jnienv, obj.Get())); + RunEventCallback<ArtJvmtiEvent::kMonitorWait>( + handler_, + self, + jnienv, + mon.get(), + static_cast<jlong>(timeout)); + } + } + + + // Our interpretation of the spec is that the JVMTI_EVENT_MONITOR_WAITED will be sent immediately + // after a thread has woken up from a sleep caused by a call to Object#wait. If the thread will + // never go to sleep (due to not having the lock, having bad arguments, or having an exception + // propogated from JVMTI_EVENT_MONITOR_WAIT) we will not send this event. + // + // This does not fully match the RI semantics. Specifically, we will not send the + // JVMTI_EVENT_MONITOR_WAITED event in one situation where the RI would, there was an exception in + // the JVMTI_EVENT_MONITOR_WAIT event but otherwise the call was fine. In that case the RI would + // send this event and return without going to sleep. + // + // See b/65558434 for more discussion. + void MonitorWaitFinished(art::Monitor* m, bool timeout) + OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMonitorWaited)) { + art::Thread* self = art::Thread::Current(); + art::JNIEnvExt* jnienv = self->GetJniEnv(); + ScopedLocalRef<jobject> mon(jnienv, AddLocalRef<jobject>(jnienv, m->GetObject())); + RunEventCallback<ArtJvmtiEvent::kMonitorWaited>( + handler_, + self, + jnienv, + mon.get(), + static_cast<jboolean>(timeout)); + } + } + + private: + EventHandler* handler_; +}; + +static void SetupMonitorListener(art::MonitorCallback* listener, bool enable) { + // We must not hold the mutator lock here, but if we're in FastJNI, for example, we might. For + // now, do a workaround: (possibly) acquire and release. + art::ScopedObjectAccess soa(art::Thread::Current()); + if (enable) { + art::Runtime::Current()->GetRuntimeCallbacks()->AddMonitorCallback(listener); + } else { + art::Runtime::Current()->GetRuntimeCallbacks()->RemoveMonitorCallback(listener); + } +} + // Report GC pauses (see spec) as GARBAGE_COLLECTION_START and GARBAGE_COLLECTION_END. class JvmtiGcPauseListener : public art::gc::GcPauseListener { public: @@ -301,33 +404,10 @@ static void SetupGcPauseTracking(JvmtiGcPauseListener* listener, ArtJvmtiEvent e } } -template<typename Type> -static Type AddLocalRef(art::JNIEnvExt* e, art::mirror::Object* obj) - REQUIRES_SHARED(art::Locks::mutator_lock_) { - return (obj == nullptr) ? nullptr : e->AddLocalReference<Type>(obj); -} - class JvmtiMethodTraceListener FINAL : public art::instrumentation::InstrumentationListener { public: explicit JvmtiMethodTraceListener(EventHandler* handler) : event_handler_(handler) {} - template<ArtJvmtiEvent kEvent, typename ...Args> - void RunEventCallback(art::Thread* self, art::JNIEnvExt* jnienv, Args... args) - REQUIRES_SHARED(art::Locks::mutator_lock_) { - ScopedLocalRef<jthread> thread_jni(jnienv, AddLocalRef<jthread>(jnienv, self->GetPeer())); - // Just give the event a good sized JNI frame. 100 should be fine. - jnienv->PushFrame(100); - { - // Need to do trampoline! :( - art::ScopedThreadSuspension sts(self, art::ThreadState::kNative); - event_handler_->DispatchEvent<kEvent>(self, - static_cast<JNIEnv*>(jnienv), - thread_jni.get(), - args...); - } - jnienv->PopFrame(); - } - // Call-back for when a method is entered. void MethodEntered(art::Thread* self, art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, @@ -337,7 +417,8 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat if (!method->IsRuntimeMethod() && event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodEntry)) { art::JNIEnvExt* jnienv = self->GetJniEnv(); - RunEventCallback<ArtJvmtiEvent::kMethodEntry>(self, + RunEventCallback<ArtJvmtiEvent::kMethodEntry>(event_handler_, + self, jnienv, art::jni::EncodeArtMethod(method)); } @@ -360,6 +441,7 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat ScopedLocalRef<jobject> return_jobj(jnienv, AddLocalRef<jobject>(jnienv, return_value.Get())); val.l = return_jobj.get(); RunEventCallback<ArtJvmtiEvent::kMethodExit>( + event_handler_, self, jnienv, art::jni::EncodeArtMethod(method), @@ -386,6 +468,7 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat // the union. val.j = return_value.GetJ(); RunEventCallback<ArtJvmtiEvent::kMethodExit>( + event_handler_, self, jnienv, art::jni::EncodeArtMethod(method), @@ -412,6 +495,7 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat CHECK(!old_exception.IsNull()); self->ClearException(); RunEventCallback<ArtJvmtiEvent::kMethodExit>( + event_handler_, self, jnienv, art::jni::EncodeArtMethod(method), @@ -442,11 +526,11 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat jlocation location = static_cast<jlocation>(new_dex_pc); // Step event is reported first according to the spec. if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kSingleStep)) { - RunEventCallback<ArtJvmtiEvent::kSingleStep>(self, jnienv, jmethod, location); + RunEventCallback<ArtJvmtiEvent::kSingleStep>(event_handler_, self, jnienv, jmethod, location); } // Next we do the Breakpoint events. The Dispatch code will filter the individual if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kBreakpoint)) { - RunEventCallback<ArtJvmtiEvent::kBreakpoint>(self, jnienv, jmethod, location); + RunEventCallback<ArtJvmtiEvent::kBreakpoint>(event_handler_, self, jnienv, jmethod, location); } } @@ -464,7 +548,8 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat ScopedLocalRef<jobject> fklass(jnienv, AddLocalRef<jobject>(jnienv, field->GetDeclaringClass().Ptr())); - RunEventCallback<ArtJvmtiEvent::kFieldAccess>(self, + RunEventCallback<ArtJvmtiEvent::kFieldAccess>(event_handler_, + self, jnienv, art::jni::EncodeArtMethod(method), static_cast<jlocation>(dex_pc), @@ -492,6 +577,7 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat jvalue val; val.l = fval.get(); RunEventCallback<ArtJvmtiEvent::kFieldModification>( + event_handler_, self, jnienv, art::jni::EncodeArtMethod(method), @@ -525,6 +611,7 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat // the union. val.j = field_value.GetJ(); RunEventCallback<ArtJvmtiEvent::kFieldModification>( + event_handler_, self, jnienv, art::jni::EncodeArtMethod(method), @@ -541,16 +628,15 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat void WatchedFramePop(art::Thread* self, const art::ShadowFrame& frame) REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { - if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFramePop)) { art::JNIEnvExt* jnienv = self->GetJniEnv(); - jboolean is_exception_pending = self->IsExceptionPending(); - RunEventCallback<ArtJvmtiEvent::kFramePop>( - self, - jnienv, - art::jni::EncodeArtMethod(frame.GetMethod()), - is_exception_pending, - &frame); - } + jboolean is_exception_pending = self->IsExceptionPending(); + RunEventCallback<ArtJvmtiEvent::kFramePop>( + event_handler_, + self, + jnienv, + art::jni::EncodeArtMethod(frame.GetMethod()), + is_exception_pending, + &frame); } static void FindCatchMethodsFromThrow(art::Thread* self, @@ -583,14 +669,14 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat if (!method->IsNative()) { uint32_t cur_dex_pc = GetDexPc(); - if (cur_dex_pc == art::DexFile::kDexNoIndex) { + if (cur_dex_pc == art::dex::kDexNoIndex) { // This frame looks opaque. Just keep on going. return true; } bool has_no_move_exception = false; uint32_t found_dex_pc = method->FindCatchBlock( exception_class_, cur_dex_pc, &has_no_move_exception); - if (found_dex_pc != art::DexFile::kDexNoIndex) { + if (found_dex_pc != art::dex::kDexNoIndex) { // We found the catch. Store the result and return. *catch_method_ptr_ = method; *catch_dex_pc_ptr_ = found_dex_pc; @@ -639,6 +725,7 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat ScopedLocalRef<jobject> exception(jnienv, AddLocalRef<jobject>(jnienv, exception_object.Get())); RunEventCallback<ArtJvmtiEvent::kException>( + event_handler_, self, jnienv, art::jni::EncodeArtMethod(method), @@ -664,6 +751,7 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat ScopedLocalRef<jobject> exception(jnienv, AddLocalRef<jobject>(jnienv, exception_object.Get())); RunEventCallback<ArtJvmtiEvent::kExceptionCatch>( + event_handler_, self, jnienv, art::jni::EncodeArtMethod(method), @@ -774,6 +862,23 @@ void EventHandler::HandleLocalAccessCapabilityAdded() { art::Runtime::Current()->GetClassLinker()->VisitClasses(&visitor); } +bool EventHandler::OtherMonitorEventsEnabledAnywhere(ArtJvmtiEvent event) { + std::array<ArtJvmtiEvent, 4> events { + { + ArtJvmtiEvent::kMonitorContendedEnter, + ArtJvmtiEvent::kMonitorContendedEntered, + ArtJvmtiEvent::kMonitorWait, + ArtJvmtiEvent::kMonitorWaited + } + }; + for (ArtJvmtiEvent e : events) { + if (e != event && IsEventEnabledAnywhere(e)) { + return true; + } + } + return false; +} + // Handle special work for the given event type, if necessary. void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) { switch (event) { @@ -814,7 +919,14 @@ void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) { case ArtJvmtiEvent::kExceptionCatch: SetupTraceListener(method_trace_listener_.get(), event, enable); return; - + case ArtJvmtiEvent::kMonitorContendedEnter: + case ArtJvmtiEvent::kMonitorContendedEntered: + case ArtJvmtiEvent::kMonitorWait: + case ArtJvmtiEvent::kMonitorWaited: + if (!OtherMonitorEventsEnabledAnywhere(event)) { + SetupMonitorListener(monitor_listener_.get(), enable); + } + return; default: break; } @@ -943,6 +1055,7 @@ EventHandler::EventHandler() { alloc_listener_.reset(new JvmtiAllocationListener(this)); gc_pause_listener_.reset(new JvmtiGcPauseListener(this)); method_trace_listener_.reset(new JvmtiMethodTraceListener(this)); + monitor_listener_.reset(new JvmtiMonitorListener(this)); } EventHandler::~EventHandler() { diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h index b49e80c46d..a062e1589e 100644 --- a/openjdkjvmti/events.h +++ b/openjdkjvmti/events.h @@ -30,6 +30,7 @@ struct ArtJvmTiEnv; class JvmtiAllocationListener; class JvmtiGcPauseListener; class JvmtiMethodTraceListener; +class JvmtiMonitorListener; // an enum for ArtEvents. This differs from the JVMTI events only in that we distinguish between // retransformation capable and incapable loading @@ -160,20 +161,21 @@ class EventHandler { ArtJvmtiEvent event, jvmtiEventMode mode); - // Dispatch event to all registered environments. + // Dispatch event to all registered environments. Since this one doesn't have a JNIEnv* it doesn't + // matter if it has the mutator_lock. template <ArtJvmtiEvent kEvent, typename ...Args> ALWAYS_INLINE inline void DispatchEvent(art::Thread* thread, Args... args) const; + // Dispatch event to all registered environments stashing exceptions as needed. This works since // JNIEnv* is always the second argument if it is passed to an event. Needed since C++ does not // allow partial template function specialization. + // + // We need both of these since we want to make sure to push a stack frame when it is possible for + // the event to allocate local references. template <ArtJvmtiEvent kEvent, typename ...Args> ALWAYS_INLINE - void DispatchEvent(art::Thread* thread, JNIEnv* jnienv, Args... args) const; - // Dispatch event to the given environment, only. - template <ArtJvmtiEvent kEvent, typename ...Args> - ALWAYS_INLINE - inline void DispatchEvent(ArtJvmTiEnv* env, art::Thread* thread, Args... args) const; + inline void DispatchEvent(art::Thread* thread, JNIEnv* jnienv, Args... args) const; // Tell the event handler capabilities were added/lost so it can adjust the sent events.If // caps_added is true then caps is all the newly set capabilities of the jvmtiEnv. If it is false @@ -183,10 +185,29 @@ class EventHandler { const jvmtiCapabilities& caps, bool added); + // Dispatch event to the given environment, only. + template <ArtJvmtiEvent kEvent, typename ...Args> + ALWAYS_INLINE + inline void DispatchEventOnEnv( + ArtJvmTiEnv* env, art::Thread* thread, JNIEnv* jnienv, Args... args) const; + + // Dispatch event to the given environment, only. + template <ArtJvmtiEvent kEvent, typename ...Args> + ALWAYS_INLINE + inline void DispatchEventOnEnv(ArtJvmTiEnv* env, art::Thread* thread, Args... args) const; + private: template <ArtJvmtiEvent kEvent> ALWAYS_INLINE - static inline bool ShouldDispatch(ArtJvmTiEnv* env, art::Thread* thread); + static inline bool ShouldDispatchOnThread(ArtJvmTiEnv* env, art::Thread* thread); + + template <ArtJvmtiEvent kEvent, typename ...Args> + ALWAYS_INLINE + static inline void ExecuteCallback(ArtJvmTiEnv* env, Args... args); + + template <ArtJvmtiEvent kEvent, typename ...Args> + ALWAYS_INLINE + inline bool ShouldDispatch(ArtJvmTiEnv* env, art::Thread* thread, Args... args) const; ALWAYS_INLINE inline bool NeedsEventUpdate(ArtJvmTiEnv* env, @@ -212,6 +233,8 @@ class EventHandler { void HandleEventType(ArtJvmtiEvent event, bool enable); void HandleLocalAccessCapabilityAdded(); + bool OtherMonitorEventsEnabledAnywhere(ArtJvmtiEvent event); + // List of all JvmTiEnv objects that have been created, in their creation order. // NB Some elements might be null representing envs that have been deleted. They should be skipped // anytime this list is used. @@ -223,6 +246,7 @@ class EventHandler { std::unique_ptr<JvmtiAllocationListener> alloc_listener_; std::unique_ptr<JvmtiGcPauseListener> gc_pause_listener_; std::unique_ptr<JvmtiMethodTraceListener> method_trace_listener_; + std::unique_ptr<JvmtiMonitorListener> monitor_listener_; // True if frame pop has ever been enabled. Since we store pointers to stack frames we need to // continue to listen to this event even if it has been disabled. diff --git a/openjdkjvmti/object_tagging.cc b/openjdkjvmti/object_tagging.cc index dcdd3ede13..6ba7165577 100644 --- a/openjdkjvmti/object_tagging.cc +++ b/openjdkjvmti/object_tagging.cc @@ -61,7 +61,7 @@ bool ObjectTagTable::DoesHandleNullOnSweep() { return event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kObjectFree); } void ObjectTagTable::HandleNullSweep(jlong tag) { - event_handler_->DispatchEvent<ArtJvmtiEvent::kObjectFree>(jvmti_env_, nullptr, tag); + event_handler_->DispatchEventOnEnv<ArtJvmtiEvent::kObjectFree>(jvmti_env_, nullptr, tag); } } // namespace openjdkjvmti diff --git a/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc index 954b5d1d03..daf4a8b7f2 100644 --- a/openjdkjvmti/ti_class.cc +++ b/openjdkjvmti/ti_class.cc @@ -318,14 +318,11 @@ struct ClassCallback : public art::ClassLoadCallback { ScopedLocalRef<jthread> thread_jni( thread->GetJniEnv(), peer.IsNull() ? nullptr : thread->GetJniEnv()->AddLocalReference<jthread>(peer)); - { - art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative); - event_handler->DispatchEvent<ArtJvmtiEvent::kClassLoad>( - thread, - static_cast<JNIEnv*>(thread->GetJniEnv()), - thread_jni.get(), - jklass.get()); - } + event_handler->DispatchEvent<ArtJvmtiEvent::kClassLoad>( + thread, + static_cast<JNIEnv*>(thread->GetJniEnv()), + thread_jni.get(), + jklass.get()); if (klass->IsTemp()) { AddTempClass(thread, jklass.get()); } @@ -348,7 +345,6 @@ struct ClassCallback : public art::ClassLoadCallback { ScopedLocalRef<jthread> thread_jni( thread->GetJniEnv(), peer.IsNull() ? nullptr : thread->GetJniEnv()->AddLocalReference<jthread>(peer)); - art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative); event_handler->DispatchEvent<ArtJvmtiEvent::kClassPrepare>( thread, static_cast<JNIEnv*>(thread->GetJniEnv()), diff --git a/openjdkjvmti/ti_method.cc b/openjdkjvmti/ti_method.cc index 05943e70a4..62603aa187 100644 --- a/openjdkjvmti/ti_method.cc +++ b/openjdkjvmti/ti_method.cc @@ -38,6 +38,7 @@ #include "base/enums.h" #include "base/mutex-inl.h" #include "dex_file_annotations.h" +#include "dex_file_types.h" #include "events-inl.h" #include "jit/jit.h" #include "jni_internal.h" @@ -563,7 +564,7 @@ class CommonLocalVariableClosure : public art::Closure { } bool needs_instrument = !visitor.IsShadowFrame(); uint32_t pc = visitor.GetDexPc(/*abort_on_failure*/ false); - if (pc == art::DexFile::kDexNoIndex) { + if (pc == art::dex::kDexNoIndex) { // Cannot figure out current PC. result_ = ERR(OPAQUE_FRAME); return; diff --git a/openjdkjvmti/ti_monitor.cc b/openjdkjvmti/ti_monitor.cc index adaa48c0cd..f92d81ef17 100644 --- a/openjdkjvmti/ti_monitor.cc +++ b/openjdkjvmti/ti_monitor.cc @@ -37,10 +37,13 @@ #include <mutex> #include "art_jvmti.h" +#include "monitor.h" #include "runtime.h" #include "scoped_thread_state_change-inl.h" #include "thread-current-inl.h" #include "ti_thread.h" +#include "thread.h" +#include "thread_pool.h" namespace openjdkjvmti { @@ -323,4 +326,77 @@ jvmtiError MonitorUtil::RawMonitorNotifyAll(jvmtiEnv* env ATTRIBUTE_UNUSED, jraw return ERR(NONE); } +jvmtiError MonitorUtil::GetCurrentContendedMonitor(jvmtiEnv* env ATTRIBUTE_UNUSED, + jthread thread, + jobject* monitor) { + if (monitor == nullptr) { + return ERR(NULL_POINTER); + } + art::Thread* self = art::Thread::Current(); + art::ScopedObjectAccess soa(self); + art::MutexLock mu(self, *art::Locks::thread_list_lock_); + art::Thread* target = ThreadUtil::GetNativeThread(thread, soa); + if (target == nullptr && thread == nullptr) { + return ERR(INVALID_THREAD); + } + if (target == nullptr) { + return ERR(THREAD_NOT_ALIVE); + } + struct GetContendedMonitorClosure : public art::Closure { + public: + explicit GetContendedMonitorClosure(art::Thread* current, jobject* out) + : result_thread_(current), out_(out) {} + + void Run(art::Thread* target_thread) REQUIRES_SHARED(art::Locks::mutator_lock_) { + switch (target_thread->GetState()) { + // These three we are actually currently waiting on a monitor and have sent the appropriate + // events (if anyone is listening). + case art::kBlocked: + case art::kTimedWaiting: + case art::kWaiting: { + art::mirror::Object* mon = art::Monitor::GetContendedMonitor(target_thread); + *out_ = (mon == nullptr) ? nullptr + : result_thread_->GetJniEnv()->AddLocalReference<jobject>(mon); + return; + } + case art::kTerminated: + case art::kRunnable: + case art::kSleeping: + case art::kWaitingForLockInflation: + case art::kWaitingForTaskProcessor: + case art::kWaitingForGcToComplete: + case art::kWaitingForCheckPointsToRun: + case art::kWaitingPerformingGc: + case art::kWaitingForDebuggerSend: + case art::kWaitingForDebuggerToAttach: + case art::kWaitingInMainDebuggerLoop: + case art::kWaitingForDebuggerSuspension: + case art::kWaitingForJniOnLoad: + case art::kWaitingForSignalCatcherOutput: + case art::kWaitingInMainSignalCatcherLoop: + case art::kWaitingForDeoptimization: + case art::kWaitingForMethodTracingStart: + case art::kWaitingForVisitObjects: + case art::kWaitingForGetObjectsAllocated: + case art::kWaitingWeakGcRootRead: + case art::kWaitingForGcThreadFlip: + case art::kStarting: + case art::kNative: + case art::kSuspended: { + // We aren't currently (explicitly) waiting for a monitor anything so just return null. + *out_ = nullptr; + return; + } + } + } + + private: + art::Thread* result_thread_; + jobject* out_; + }; + GetContendedMonitorClosure closure(self, monitor); + target->RequestSynchronousCheckpoint(&closure); + return OK; +} + } // namespace openjdkjvmti diff --git a/openjdkjvmti/ti_monitor.h b/openjdkjvmti/ti_monitor.h index add089c377..e0a865b9fa 100644 --- a/openjdkjvmti/ti_monitor.h +++ b/openjdkjvmti/ti_monitor.h @@ -52,6 +52,8 @@ class MonitorUtil { static jvmtiError RawMonitorNotify(jvmtiEnv* env, jrawMonitorID monitor); static jvmtiError RawMonitorNotifyAll(jvmtiEnv* env, jrawMonitorID monitor); + + static jvmtiError GetCurrentContendedMonitor(jvmtiEnv* env, jthread thr, jobject* monitor); }; } // namespace openjdkjvmti diff --git a/openjdkjvmti/ti_stack.cc b/openjdkjvmti/ti_stack.cc index e165187348..699f6952c4 100644 --- a/openjdkjvmti/ti_stack.cc +++ b/openjdkjvmti/ti_stack.cc @@ -46,6 +46,7 @@ #include "base/mutex.h" #include "dex_file.h" #include "dex_file_annotations.h" +#include "dex_file_types.h" #include "gc_root.h" #include "handle_scope-inl.h" #include "jni_env_ext.h" @@ -88,7 +89,7 @@ struct GetStackTraceVisitor : public art::StackVisitor { jmethodID id = art::jni::EncodeArtMethod(m); uint32_t dex_pc = GetDexPc(false); - jlong dex_location = (dex_pc == art::DexFile::kDexNoIndex) ? -1 : static_cast<jlong>(dex_pc); + jlong dex_location = (dex_pc == art::dex::kDexNoIndex) ? -1 : static_cast<jlong>(dex_pc); jvmtiFrameInfo info = { id, dex_location }; fn(info); @@ -819,7 +820,7 @@ jvmtiError StackUtil::GetFrameLocation(jvmtiEnv* env ATTRIBUTE_UNUSED, if (closure.method->IsNative()) { *location_ptr = -1; } else { - if (closure.dex_pc == art::DexFile::kDexNoIndex) { + if (closure.dex_pc == art::dex::kDexNoIndex) { return ERR(INTERNAL); } *location_ptr = static_cast<jlocation>(closure.dex_pc); diff --git a/openjdkjvmti/ti_thread.cc b/openjdkjvmti/ti_thread.cc index 27d01ea17d..d437e52d0f 100644 --- a/openjdkjvmti/ti_thread.cc +++ b/openjdkjvmti/ti_thread.cc @@ -392,6 +392,8 @@ static jint GetJavaStateFromInternal(const InternalThreadState& state) { return JVMTI_JAVA_LANG_THREAD_STATE_NEW; case art::ThreadState::kWaiting: + case art::ThreadState::kWaitingForTaskProcessor: + case art::ThreadState::kWaitingForLockInflation: case art::ThreadState::kWaitingForGcToComplete: case art::ThreadState::kWaitingPerformingGc: case art::ThreadState::kWaitingForCheckPointsToRun: diff --git a/profman/boot_image_profile.cc b/profman/boot_image_profile.cc index 21de0831b8..4092f6ed98 100644 --- a/profman/boot_image_profile.cc +++ b/profman/boot_image_profile.cc @@ -60,8 +60,7 @@ void GenerateBootImageProfile( if (hotness.IsInProfile()) { ++counter; out_profile->AddMethodHotness(ref, hotness); - inferred_classes.emplace(profile.get(), - dex_file->GetMethodId(ref.dex_method_index).class_idx_); + inferred_classes.emplace(profile.get(), ref.GetMethodId().class_idx_); } } // If the counter is greater or equal to the compile threshold, mark the method as hot. @@ -110,9 +109,9 @@ void GenerateBootImageProfile( // This counter is how many profiles contain the class. size_t counter = 0; for (const std::unique_ptr<const ProfileCompilationInfo>& profile : profiles) { - auto it = inferred_classes.find(std::make_pair(profile.get(), ref.type_index)); + auto it = inferred_classes.find(std::make_pair(profile.get(), ref.TypeIndex())); if (it != inferred_classes.end() || - profile->ContainsClass(*ref.dex_file, ref.type_index)) { + profile->ContainsClass(*ref.dex_file, ref.TypeIndex())) { ++counter; } } @@ -121,10 +120,10 @@ void GenerateBootImageProfile( } if (counter >= options.image_class_theshold) { ++class_count; - out_profile->AddClassesForDex(ref.dex_file, &ref.type_index, &ref.type_index + 1); + out_profile->AddClassForDex(ref); } else if (is_clean && counter >= options.image_class_clean_theshold) { ++clean_class_count; - out_profile->AddClassesForDex(ref.dex_file, &ref.type_index, &ref.type_index + 1); + out_profile->AddClassForDex(ref); } } } diff --git a/profman/profman.cc b/profman/profman.cc index d0c99e0201..9b4f5794b7 100644 --- a/profman/profman.cc +++ b/profman/profman.cc @@ -39,6 +39,7 @@ #include "boot_image_profile.h" #include "bytecode_utils.h" #include "dex_file.h" +#include "dex_file_types.h" #include "jit/profile_compilation_info.h" #include "profile_assistant.h" #include "runtime.h" @@ -634,8 +635,7 @@ class ProfMan FINAL { if (kInvalidTypeIndex >= dex_file->NumTypeIds()) { // The dex file does not contain all possible type ids which leaves us room // to add an "invalid" type id. - class_ref->dex_file = dex_file; - class_ref->type_index = dex::TypeIndex(kInvalidTypeIndex); + *class_ref = TypeReference(dex_file, dex::TypeIndex(kInvalidTypeIndex)); return true; } else { // The dex file contains all possible type ids. We don't have any free type id @@ -653,8 +653,7 @@ class ProfMan FINAL { // Class is only referenced in the current dex file but not defined in it. continue; } - class_ref->dex_file = dex_file; - class_ref->type_index = type_index; + *class_ref = TypeReference(dex_file, type_index); return true; } return false; @@ -668,14 +667,14 @@ class ProfMan FINAL { constexpr uint16_t kInvalidMethodIndex = std::numeric_limits<uint16_t>::max() - 1; return kInvalidMethodIndex >= dex_file->NumMethodIds() ? kInvalidMethodIndex - : DexFile::kDexNoIndex; + : dex::kDexNoIndex; } std::vector<std::string> name_and_signature; Split(method_spec, kProfileParsingFirstCharInSignature, &name_and_signature); if (name_and_signature.size() != 2) { LOG(ERROR) << "Invalid method name and signature " << method_spec; - return DexFile::kDexNoIndex; + return dex::kDexNoIndex; } const std::string& name = name_and_signature[0]; @@ -684,24 +683,24 @@ class ProfMan FINAL { const DexFile::StringId* name_id = dex_file->FindStringId(name.c_str()); if (name_id == nullptr) { LOG(WARNING) << "Could not find name: " << name; - return DexFile::kDexNoIndex; + return dex::kDexNoIndex; } dex::TypeIndex return_type_idx; std::vector<dex::TypeIndex> param_type_idxs; if (!dex_file->CreateTypeList(signature, &return_type_idx, ¶m_type_idxs)) { LOG(WARNING) << "Could not create type list" << signature; - return DexFile::kDexNoIndex; + return dex::kDexNoIndex; } const DexFile::ProtoId* proto_id = dex_file->FindProtoId(return_type_idx, param_type_idxs); if (proto_id == nullptr) { LOG(WARNING) << "Could not find proto_id: " << name; - return DexFile::kDexNoIndex; + return dex::kDexNoIndex; } const DexFile::MethodId* method_id = dex_file->FindMethodId( - dex_file->GetTypeId(class_ref.type_index), *name_id, *proto_id); + dex_file->GetTypeId(class_ref.TypeIndex()), *name_id, *proto_id); if (method_id == nullptr) { LOG(WARNING) << "Could not find method_id: " << name; - return DexFile::kDexNoIndex; + return dex::kDexNoIndex; } return dex_file->GetIndexForMethodId(*method_id); @@ -718,7 +717,7 @@ class ProfMan FINAL { /*out*/uint32_t* dex_pc) { const DexFile* dex_file = class_ref.dex_file; uint32_t offset = dex_file->FindCodeItemOffset( - *dex_file->FindClassDef(class_ref.type_index), + *dex_file->FindClassDef(class_ref.TypeIndex()), method_index); const DexFile::CodeItem* code_item = dex_file->GetCodeItem(offset); @@ -799,11 +798,11 @@ class ProfMan FINAL { dex_file->GetBaseLocation(), dex_file->GetLocationChecksum(), dex_file->NumMethodIds()); - dex_resolved_classes.first->AddClass(class_ref.type_index); + dex_resolved_classes.first->AddClass(class_ref.TypeIndex()); std::vector<ProfileMethodInfo> methods; if (method_str == kClassAllMethods) { // Add all of the methods. - const DexFile::ClassDef* class_def = dex_file->FindClassDef(class_ref.type_index); + const DexFile::ClassDef* class_def = dex_file->FindClassDef(class_ref.TypeIndex()); const uint8_t* class_data = dex_file->GetClassData(*class_def); if (class_data != nullptr) { ClassDataItemIterator it(*dex_file, class_data); @@ -848,7 +847,7 @@ class ProfMan FINAL { } const uint32_t method_index = FindMethodIndex(class_ref, method_spec); - if (method_index == DexFile::kDexNoIndex) { + if (method_index == dex::kDexNoIndex) { return false; } diff --git a/runtime/Android.bp b/runtime/Android.bp index 6144869415..db9707f290 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -634,3 +634,9 @@ art_cc_test { "libvixld-arm64", ], } + +cc_library_headers { + name: "libart_runtime_headers", + host_supported: true, + export_include_dirs: ["."], +} diff --git a/runtime/arch/arm/entrypoints_init_arm.cc b/runtime/arch/arm/entrypoints_init_arm.cc index 090bab7e4b..78b9e46d77 100644 --- a/runtime/arch/arm/entrypoints_init_arm.cc +++ b/runtime/arch/arm/entrypoints_init_arm.cc @@ -131,19 +131,11 @@ void InitEntryPoints(JniEntryPoints* jpoints, QuickEntryPoints* qpoints) { qpoints->pShlLong = art_quick_shl_long; qpoints->pShrLong = art_quick_shr_long; qpoints->pUshrLong = art_quick_ushr_long; - if (kArm32QuickCodeUseSoftFloat) { - qpoints->pFmod = fmod; - qpoints->pFmodf = fmodf; - qpoints->pD2l = art_d2l; - qpoints->pF2l = art_f2l; - qpoints->pL2f = art_l2f; - } else { - qpoints->pFmod = art_quick_fmod; - qpoints->pFmodf = art_quick_fmodf; - qpoints->pD2l = art_quick_d2l; - qpoints->pF2l = art_quick_f2l; - qpoints->pL2f = art_quick_l2f; - } + qpoints->pFmod = art_quick_fmod; + qpoints->pFmodf = art_quick_fmodf; + qpoints->pD2l = art_quick_d2l; + qpoints->pF2l = art_quick_f2l; + qpoints->pL2f = art_quick_l2f; // More math. qpoints->pCos = cos; diff --git a/runtime/arch/arm/quick_entrypoints_cc_arm.cc b/runtime/arch/arm/quick_entrypoints_cc_arm.cc index ce531f0709..232ec3140e 100644 --- a/runtime/arch/arm/quick_entrypoints_cc_arm.cc +++ b/runtime/arch/arm/quick_entrypoints_cc_arm.cc @@ -34,8 +34,7 @@ static void quick_invoke_reg_setup(ArtMethod* method, uint32_t* args, uint32_t a uint32_t fpr_index = 0; // Index into float registers. uint32_t fpr_double_index = 0; // Index into float registers for doubles. uint32_t arg_index = 0; // Index into argument array. - const uint32_t result_in_float = kArm32QuickCodeUseSoftFloat ? 0 : - (shorty[0] == 'F' || shorty[0] == 'D') ? 1 : 0; + const uint32_t result_in_float = (shorty[0] == 'F' || shorty[0] == 'D') ? 1 : 0; if (!kIsStatic) { // Copy receiver for non-static methods. @@ -44,10 +43,6 @@ static void quick_invoke_reg_setup(ArtMethod* method, uint32_t* args, uint32_t a for (uint32_t shorty_index = 1; shorty[shorty_index] != '\0'; ++shorty_index, ++arg_index) { char arg_type = shorty[shorty_index]; - if (kArm32QuickCodeUseSoftFloat) { - arg_type = (arg_type == 'D') ? 'J' : arg_type; // Regard double as long. - arg_type = (arg_type == 'F') ? 'I' : arg_type; // Regard float as int. - } switch (arg_type) { case 'D': { // Copy double argument into fp_reg_args if there are still floating point reg arguments. @@ -75,13 +70,13 @@ static void quick_invoke_reg_setup(ArtMethod* method, uint32_t* args, uint32_t a } break; case 'J': - if (gpr_index == 1 && !kArm32QuickCodeUseSoftFloat) { + if (gpr_index == 1) { // Don't use r1-r2 as a register pair, move to r2-r3 instead. gpr_index++; } if (gpr_index < arraysize(core_reg_args)) { // Note that we don't need to do this if two registers are not available - // when !kArm32QuickCodeUseSoftFloat. We do it anyway to leave this + // when using hard-fp. We do it anyway to leave this // code simple. core_reg_args[gpr_index++] = args[arg_index]; } diff --git a/runtime/arch/mips/quick_entrypoints_mips.S b/runtime/arch/mips/quick_entrypoints_mips.S index b876353183..74e0a7ae25 100644 --- a/runtime/arch/mips/quick_entrypoints_mips.S +++ b/runtime/arch/mips/quick_entrypoints_mips.S @@ -2317,58 +2317,50 @@ END art_invoke_obsolete_method_stub .extern artInstrumentationMethodExitFromCode ENTRY art_quick_instrumentation_entry SETUP_SAVE_REFS_AND_ARGS_FRAME - sw $a0, 28($sp) # save arg0 in free arg slot - addiu $a3, $sp, ARG_SLOT_SIZE # Pass $sp. - la $t9, artInstrumentationMethodEntryFromCode - jalr $t9 # (Method*, Object*, Thread*, SP) - move $a2, rSELF # pass Thread::Current - beqz $v0, .Ldeliver_instrumentation_entry_exception - move $t9, $v0 # $t9 holds reference to code - lw $a0, 28($sp) # restore arg0 from free arg slot + sw $a0, 28($sp) # save arg0 in free arg slot + addiu $a3, $sp, ARG_SLOT_SIZE # Pass $sp. + la $t9, artInstrumentationMethodEntryFromCode + jalr $t9 # (Method*, Object*, Thread*, SP) + move $a2, rSELF # pass Thread::Current + beqz $v0, .Ldeliver_instrumentation_entry_exception + move $t9, $v0 # $t9 holds reference to code + lw $a0, 28($sp) # restore arg0 from free arg slot RESTORE_SAVE_REFS_AND_ARGS_FRAME - jalr $t9 # call method + la $ra, art_quick_instrumentation_exit + jalr $zero, $t9 # call method, returning to art_quick_instrumentation_exit nop +.Ldeliver_instrumentation_entry_exception: + RESTORE_SAVE_REFS_AND_ARGS_FRAME + DELIVER_PENDING_EXCEPTION END art_quick_instrumentation_entry - /* intentional fallthrough */ - .global art_quick_instrumentation_exit -art_quick_instrumentation_exit: - .cfi_startproc - addiu $t9, $ra, 4 # put current address into $t9 to rebuild $gp - .cpload $t9 - move $ra, $zero # link register is to here, so clobber with 0 for later checks - SETUP_SAVE_REFS_ONLY_FRAME - addiu $sp, $sp, -16 # allocate temp storage on the stack - .cfi_adjust_cfa_offset 16 - sw $v0, ARG_SLOT_SIZE+8($sp) - .cfi_rel_offset 2, ARG_SLOT_SIZE+8 - sw $v1, ARG_SLOT_SIZE+12($sp) - .cfi_rel_offset 3, ARG_SLOT_SIZE+12 - s.d $f0, ARG_SLOT_SIZE($sp) - addiu $a3, $sp, ARG_SLOT_SIZE # Pass fpr_res pointer. - addiu $a2, $sp, ARG_SLOT_SIZE+8 # Pass gpr_res pointer. - addiu $a1, $sp, ARG_SLOT_SIZE+16 # Pass $sp (remove arg slots and temp storage). - la $t9, artInstrumentationMethodExitFromCode - jalr $t9 # (Thread*, SP, gpr_res*, fpr_res*) - move $a0, rSELF # Pass Thread::Current. - move $t9, $v0 # Set aside returned link register. - move $ra, $v1 # Set link register for deoptimization. - lw $v0, ARG_SLOT_SIZE+8($sp) # Restore return values. - lw $v1, ARG_SLOT_SIZE+12($sp) - l.d $f0, ARG_SLOT_SIZE($sp) - addiu $sp, $sp, 16 - .cfi_adjust_cfa_offset -16 - RESTORE_SAVE_REFS_ONLY_FRAME - beqz $t9, .Ldo_deliver_instrumentation_exception - nop # Deliver exception if we got nullptr as function. - jalr $zero, $t9 # Otherwise, return. +ENTRY_NO_GP art_quick_instrumentation_exit + move $ra, $zero # RA points here, so clobber with 0 for later checks. + SETUP_SAVE_EVERYTHING_FRAME # Allocates ARG_SLOT_SIZE bytes at the bottom of the stack. + move $s2, $gp # Preserve $gp across the call for exception delivery. + + addiu $a3, $sp, ARG_SLOT_SIZE+16 # Pass fpr_res pointer ($f0 in SAVE_EVERYTHING_FRAME). + addiu $a2, $sp, ARG_SLOT_SIZE+148 # Pass gpr_res pointer ($v0 in SAVE_EVERYTHING_FRAME). + addiu $a1, $sp, ARG_SLOT_SIZE # Pass $sp. + la $t9, artInstrumentationMethodExitFromCode + jalr $t9 # (Thread*, SP, gpr_res*, fpr_res*) + move $a0, rSELF # Pass Thread::Current. + + beqz $v0, .Ldo_deliver_instrumentation_exception + move $gp, $s2 # Deliver exception if we got nullptr as function. + bnez $v1, .Ldeoptimize + + # Normal return. + sw $v0, (ARG_SLOT_SIZE+FRAME_SIZE_SAVE_EVERYTHING-4)($sp) # Set return pc. + RESTORE_SAVE_EVERYTHING_FRAME + jalr $zero, $ra nop -.Ldeliver_instrumentation_entry_exception: - # Deliver exception for art_quick_instrumentation_entry placed after - # art_quick_instrumentation_exit so that the fallthrough works. - RESTORE_SAVE_REFS_AND_ARGS_FRAME .Ldo_deliver_instrumentation_exception: - DELIVER_PENDING_EXCEPTION + DELIVER_PENDING_EXCEPTION_FRAME_READY +.Ldeoptimize: + b art_quick_deoptimize + sw $v1, (ARG_SLOT_SIZE+FRAME_SIZE_SAVE_EVERYTHING-4)($sp) + # Fake a call from instrumentation return pc. END art_quick_instrumentation_exit /* @@ -2376,11 +2368,41 @@ END art_quick_instrumentation_exit * will long jump to the upcall with a special exception of -1. */ .extern artDeoptimize -ENTRY art_quick_deoptimize - SETUP_SAVE_ALL_CALLEE_SAVES_FRAME - la $t9, artDeoptimize - jalr $t9 # (Thread*) - move $a0, rSELF # pass Thread::current +ENTRY_NO_GP_CUSTOM_CFA art_quick_deoptimize, ARG_SLOT_SIZE+FRAME_SIZE_SAVE_EVERYTHING + # SETUP_SAVE_EVERYTHING_FRAME has been done by art_quick_instrumentation_exit. + .cfi_rel_offset 31, ARG_SLOT_SIZE+252 + .cfi_rel_offset 30, ARG_SLOT_SIZE+248 + .cfi_rel_offset 28, ARG_SLOT_SIZE+244 + .cfi_rel_offset 25, ARG_SLOT_SIZE+240 + .cfi_rel_offset 24, ARG_SLOT_SIZE+236 + .cfi_rel_offset 23, ARG_SLOT_SIZE+232 + .cfi_rel_offset 22, ARG_SLOT_SIZE+228 + .cfi_rel_offset 21, ARG_SLOT_SIZE+224 + .cfi_rel_offset 20, ARG_SLOT_SIZE+220 + .cfi_rel_offset 19, ARG_SLOT_SIZE+216 + .cfi_rel_offset 18, ARG_SLOT_SIZE+212 + .cfi_rel_offset 17, ARG_SLOT_SIZE+208 + .cfi_rel_offset 16, ARG_SLOT_SIZE+204 + .cfi_rel_offset 15, ARG_SLOT_SIZE+200 + .cfi_rel_offset 14, ARG_SLOT_SIZE+196 + .cfi_rel_offset 13, ARG_SLOT_SIZE+192 + .cfi_rel_offset 12, ARG_SLOT_SIZE+188 + .cfi_rel_offset 11, ARG_SLOT_SIZE+184 + .cfi_rel_offset 10, ARG_SLOT_SIZE+180 + .cfi_rel_offset 9, ARG_SLOT_SIZE+176 + .cfi_rel_offset 8, ARG_SLOT_SIZE+172 + .cfi_rel_offset 7, ARG_SLOT_SIZE+168 + .cfi_rel_offset 6, ARG_SLOT_SIZE+164 + .cfi_rel_offset 5, ARG_SLOT_SIZE+160 + .cfi_rel_offset 4, ARG_SLOT_SIZE+156 + .cfi_rel_offset 3, ARG_SLOT_SIZE+152 + .cfi_rel_offset 2, ARG_SLOT_SIZE+148 + .cfi_rel_offset 1, ARG_SLOT_SIZE+144 + + la $t9, artDeoptimize + jalr $t9 # (Thread*) + move $a0, rSELF # pass Thread::current + break END art_quick_deoptimize /* @@ -2388,7 +2410,7 @@ END art_quick_deoptimize * will long jump to the upcall with a special exception of -1. */ .extern artDeoptimizeFromCompiledCode -ENTRY art_quick_deoptimize_from_compiled_code +ENTRY_NO_GP art_quick_deoptimize_from_compiled_code SETUP_SAVE_EVERYTHING_FRAME la $t9, artDeoptimizeFromCompiledCode jalr $t9 # (DeoptimizationKind, Thread*) diff --git a/runtime/arch/mips64/quick_entrypoints_mips64.S b/runtime/arch/mips64/quick_entrypoints_mips64.S index eeaae3c698..1817502c77 100644 --- a/runtime/arch/mips64/quick_entrypoints_mips64.S +++ b/runtime/arch/mips64/quick_entrypoints_mips64.S @@ -2203,53 +2203,47 @@ END art_invoke_obsolete_method_stub ENTRY art_quick_instrumentation_entry SETUP_SAVE_REFS_AND_ARGS_FRAME # Preserve $a0 knowing there is a spare slot in kSaveRefsAndArgs. - sd $a0, 8($sp) # Save arg0. - move $a3, $sp # Pass $sp. - jal artInstrumentationMethodEntryFromCode # (Method*, Object*, Thread*, SP) - move $a2, rSELF # pass Thread::Current - beqzc $v0, .Ldeliver_instrumentation_entry_exception - # Deliver exception if we got nullptr as function. - move $t9, $v0 # $t9 holds reference to code - ld $a0, 8($sp) # Restore arg0. + sd $a0, 8($sp) # Save arg0. + move $a3, $sp # Pass $sp. + jal artInstrumentationMethodEntryFromCode # (Method*, Object*, Thread*, SP) + move $a2, rSELF # pass Thread::Current + beqzc $v0, .Ldeliver_instrumentation_entry_exception + # Deliver exception if we got nullptr as function. + move $t9, $v0 # $t9 holds reference to code + ld $a0, 8($sp) # Restore arg0. RESTORE_SAVE_REFS_AND_ARGS_FRAME - jalr $t9 # call method - nop -END art_quick_instrumentation_entry - /* intentional fallthrough */ - .global art_quick_instrumentation_exit -art_quick_instrumentation_exit: - .cfi_startproc - SETUP_GP - move $ra, $zero # link register is to here, so clobber with 0 for later checks - SETUP_SAVE_REFS_ONLY_FRAME - daddiu $sp, $sp, -16 # save return values and set up args - .cfi_adjust_cfa_offset 16 - sd $v0, 0($sp) - .cfi_rel_offset 2, 0 - s.d $f0, 8($sp) - daddiu $a3, $sp, 8 # Pass fpr_res pointer. - move $a2, $sp # Pass gpr_res pointer. - daddiu $a1, $sp, 16 # Pass $sp. - jal artInstrumentationMethodExitFromCode # (Thread*, SP, gpr_res*, fpr_res*) - move $a0, rSELF # pass Thread::Current - - move $t9, $v0 # set aside returned link register - move $ra, $v1 # set link register for deoptimization - ld $v0, 0($sp) # restore return values - l.d $f0, 8($sp) - daddiu $sp, $sp, 16 - .cfi_adjust_cfa_offset -16 - RESTORE_SAVE_REFS_ONLY_FRAME - beqz $t9, .Ldo_deliver_instrumentation_exception - nop # Deliver exception if we got nullptr as function. - jalr $zero, $t9 # Otherwise, return. - nop + dla $ra, art_quick_instrumentation_exit + jic $t9, 0 # call method, returning to art_quick_instrumentation_exit .Ldeliver_instrumentation_entry_exception: - # Deliver exception for art_quick_instrumentation_entry placed after - # art_quick_instrumentation_exit so that the fallthrough works. RESTORE_SAVE_REFS_AND_ARGS_FRAME -.Ldo_deliver_instrumentation_exception: DELIVER_PENDING_EXCEPTION +END art_quick_instrumentation_entry + +ENTRY_NO_GP art_quick_instrumentation_exit + move $ra, $zero # RA points here, so clobber with 0 for later checks. + SETUP_SAVE_EVERYTHING_FRAME + + daddiu $a3, $sp, 16 # Pass fpr_res pointer ($f0 in SAVE_EVERYTHING_FRAME). + daddiu $a2, $sp, 280 # Pass gpr_res pointer ($v0 in SAVE_EVERYTHING_FRAME). + move $a1, $sp # Pass $sp. + jal artInstrumentationMethodExitFromCode # (Thread*, SP, gpr_res*, fpr_res*) + move $a0, rSELF # pass Thread::Current + + beqzc $v0, .Ldo_deliver_instrumentation_exception + # Deliver exception if we got nullptr as function. + nop + bnez $v1, .Ldeoptimize + + # Normal return. + sd $v0, (FRAME_SIZE_SAVE_EVERYTHING-8)($sp) # Set return pc. + RESTORE_SAVE_EVERYTHING_FRAME + jic $ra, 0 +.Ldo_deliver_instrumentation_exception: + DELIVER_PENDING_EXCEPTION_FRAME_READY +.Ldeoptimize: + b art_quick_deoptimize + sd $v1, (FRAME_SIZE_SAVE_EVERYTHING-8)($sp) + # Fake a call from instrumentation return pc. END art_quick_instrumentation_exit /* @@ -2257,11 +2251,40 @@ END art_quick_instrumentation_exit * will long jump to the upcall with a special exception of -1. */ .extern artDeoptimize - .extern artEnterInterpreterFromDeoptimize -ENTRY art_quick_deoptimize - SETUP_SAVE_ALL_CALLEE_SAVES_FRAME - jal artDeoptimize # artDeoptimize(Thread*) - move $a0, rSELF # pass Thread::current +ENTRY_NO_GP_CUSTOM_CFA art_quick_deoptimize, FRAME_SIZE_SAVE_EVERYTHING + # SETUP_SAVE_EVERYTHING_FRAME has been done by art_quick_instrumentation_exit. + .cfi_rel_offset 31, 488 + .cfi_rel_offset 30, 480 + .cfi_rel_offset 28, 472 + .cfi_rel_offset 25, 464 + .cfi_rel_offset 24, 456 + .cfi_rel_offset 23, 448 + .cfi_rel_offset 22, 440 + .cfi_rel_offset 21, 432 + .cfi_rel_offset 20, 424 + .cfi_rel_offset 19, 416 + .cfi_rel_offset 18, 408 + .cfi_rel_offset 17, 400 + .cfi_rel_offset 16, 392 + .cfi_rel_offset 15, 384 + .cfi_rel_offset 14, 376 + .cfi_rel_offset 13, 368 + .cfi_rel_offset 12, 360 + .cfi_rel_offset 11, 352 + .cfi_rel_offset 10, 344 + .cfi_rel_offset 9, 336 + .cfi_rel_offset 8, 328 + .cfi_rel_offset 7, 320 + .cfi_rel_offset 6, 312 + .cfi_rel_offset 5, 304 + .cfi_rel_offset 4, 296 + .cfi_rel_offset 3, 288 + .cfi_rel_offset 2, 280 + .cfi_rel_offset 1, 272 + + jal artDeoptimize # artDeoptimize(Thread*) + move $a0, rSELF # pass Thread::current + break END art_quick_deoptimize /* diff --git a/runtime/art_field-inl.h b/runtime/art_field-inl.h index 2532db9082..fced9954a9 100644 --- a/runtime/art_field-inl.h +++ b/runtime/art_field-inl.h @@ -340,7 +340,7 @@ inline const DexFile* ArtField::GetDexFile() REQUIRES_SHARED(Locks::mutator_lock inline ObjPtr<mirror::String> ArtField::GetStringName(Thread* self, bool resolve) { auto dex_field_index = GetDexFieldIndex(); - CHECK_NE(dex_field_index, DexFile::kDexNoIndex); + CHECK_NE(dex_field_index, dex::kDexNoIndex); ObjPtr<mirror::DexCache> dex_cache = GetDexCache(); const auto* dex_file = dex_cache->GetDexFile(); const auto& field_id = dex_file->GetFieldId(dex_field_index); diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h index 1588920e94..7ff35acf74 100644 --- a/runtime/art_method-inl.h +++ b/runtime/art_method-inl.h @@ -25,8 +25,8 @@ #include "class_linker-inl.h" #include "common_throws.h" #include "dex_file-inl.h" -#include "dex_file.h" #include "dex_file_annotations.h" +#include "dex_file_types.h" #include "gc_root-inl.h" #include "invoke_type.h" #include "jit/profiling_info.h" @@ -184,7 +184,7 @@ inline const DexFile* ArtMethod::GetDexFile() { inline const char* ArtMethod::GetDeclaringClassDescriptor() { uint32_t dex_method_idx = GetDexMethodIndex(); - if (UNLIKELY(dex_method_idx == DexFile::kDexNoIndex)) { + if (UNLIKELY(dex_method_idx == dex::kDexNoIndex)) { return "<runtime method>"; } DCHECK(!IsProxyMethod()); @@ -205,7 +205,7 @@ inline const char* ArtMethod::GetShorty(uint32_t* out_length) { inline const Signature ArtMethod::GetSignature() { uint32_t dex_method_idx = GetDexMethodIndex(); - if (dex_method_idx != DexFile::kDexNoIndex) { + if (dex_method_idx != dex::kDexNoIndex) { DCHECK(!IsProxyMethod()); const DexFile* dex_file = GetDexFile(); return dex_file->GetMethodSignature(dex_file->GetMethodId(dex_method_idx)); @@ -215,7 +215,7 @@ inline const Signature ArtMethod::GetSignature() { inline const char* ArtMethod::GetName() { uint32_t dex_method_idx = GetDexMethodIndex(); - if (LIKELY(dex_method_idx != DexFile::kDexNoIndex)) { + if (LIKELY(dex_method_idx != dex::kDexNoIndex)) { DCHECK(!IsProxyMethod()); const DexFile* dex_file = GetDexFile(); return dex_file->GetMethodName(dex_file->GetMethodId(dex_method_idx)); @@ -253,7 +253,7 @@ inline bool ArtMethod::IsResolvedTypeIdx(dex::TypeIndex type_idx) { inline int32_t ArtMethod::GetLineNumFromDexPC(uint32_t dex_pc) { DCHECK(!IsProxyMethod()); - if (dex_pc == DexFile::kDexNoIndex) { + if (dex_pc == dex::kDexNoIndex) { return IsNative() ? -2 : -1; } return annotations::GetLineNumFromPC(GetDexFile(), this, dex_pc); diff --git a/runtime/art_method.cc b/runtime/art_method.cc index 7d8dedab6f..ece853f016 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -59,7 +59,7 @@ extern "C" void art_quick_invoke_static_stub(ArtMethod*, uint32_t*, uint32_t, Th DEFINE_RUNTIME_DEBUG_FLAG(ArtMethod, kCheckDeclaringClassState); // Enforce that we he have the right index for runtime methods. -static_assert(ArtMethod::kRuntimeMethodDexMethodIndex == DexFile::kDexNoIndex, +static_assert(ArtMethod::kRuntimeMethodDexMethodIndex == dex::kDexNoIndex, "Wrong runtime-method dex method index"); ArtMethod* ArtMethod::GetCanonicalMethod(PointerSize pointer_size) { @@ -258,7 +258,7 @@ uint32_t ArtMethod::FindDexMethodIndexInOtherDexFile(const DexFile& other_dexfil return other_dexfile.GetIndexForMethodId(*other_mid); } } - return DexFile::kDexNoIndex; + return dex::kDexNoIndex; } uint32_t ArtMethod::FindCatchBlock(Handle<mirror::Class> exception_type, @@ -270,7 +270,7 @@ uint32_t ArtMethod::FindCatchBlock(Handle<mirror::Class> exception_type, Handle<mirror::Throwable> exception(hs.NewHandle(self->GetException())); self->ClearException(); // Default to handler not found. - uint32_t found_dex_pc = DexFile::kDexNoIndex; + uint32_t found_dex_pc = dex::kDexNoIndex; // Iterate over the catch handlers associated with dex_pc. for (CatchHandlerIterator it(*code_item, dex_pc); it.HasNext(); it.Next()) { dex::TypeIndex iter_type_idx = it.GetHandlerTypeIndex(); @@ -296,7 +296,7 @@ uint32_t ArtMethod::FindCatchBlock(Handle<mirror::Class> exception_type, break; } } - if (found_dex_pc != DexFile::kDexNoIndex) { + if (found_dex_pc != dex::kDexNoIndex) { const Instruction* first_catch_instr = Instruction::At(&code_item->insns_[found_dex_pc]); *has_no_move_exception = (first_catch_instr->Opcode() != Instruction::MOVE_EXCEPTION); diff --git a/runtime/art_method.h b/runtime/art_method.h index dab3f234e2..fbdc32d8ec 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -375,7 +375,7 @@ class ArtMethod FINAL { REQUIRES_SHARED(Locks::mutator_lock_); // Find the method index for this method within other_dexfile. If this method isn't present then - // return DexFile::kDexNoIndex. The name_and_signature_idx MUST refer to a MethodId with the same + // return dex::kDexNoIndex. The name_and_signature_idx MUST refer to a MethodId with the same // name and signature in the other_dexfile, such as the method index used to resolve this method // in the other_dexfile. uint32_t FindDexMethodIndexInOtherDexFile(const DexFile& other_dexfile, diff --git a/runtime/asm_support.h b/runtime/asm_support.h index f4830e2db5..a4e34592f5 100644 --- a/runtime/asm_support.h +++ b/runtime/asm_support.h @@ -17,7 +17,8 @@ #ifndef ART_RUNTIME_ASM_SUPPORT_H_ #define ART_RUNTIME_ASM_SUPPORT_H_ -#include "read_barrier_c.h" +#include "heap_poisoning.h" +#include "read_barrier_config.h" #if defined(__arm__) || defined(__mips__) // In quick code for ARM and MIPS we make poor use of registers and perform frequent suspend diff --git a/runtime/check_reference_map_visitor.h b/runtime/check_reference_map_visitor.h index f6c8fa9659..d9e89159f5 100644 --- a/runtime/check_reference_map_visitor.h +++ b/runtime/check_reference_map_visitor.h @@ -18,6 +18,7 @@ #define ART_RUNTIME_CHECK_REFERENCE_MAP_VISITOR_H_ #include "art_method-inl.h" +#include "dex_file_types.h" #include "oat_quick_method_header.h" #include "scoped_thread_state_change-inl.h" #include "stack.h" @@ -35,7 +36,7 @@ class CheckReferenceMapVisitor : public StackVisitor { bool VisitFrame() REQUIRES_SHARED(Locks::mutator_lock_) { ArtMethod* m = GetMethod(); if (m->IsCalleeSaveMethod() || m->IsNative()) { - CHECK_EQ(GetDexPc(), DexFile::kDexNoIndex); + CHECK_EQ(GetDexPc(), dex::kDexNoIndex); } if (m == nullptr || m->IsNative() || m->IsRuntimeMethod() || IsShadowFrame()) { diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 835d940af3..772f0420fd 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -2771,11 +2771,11 @@ uint32_t ClassLinker::SizeOfClassWithoutEmbeddedTables(const DexFile& dex_file, if (class_data != nullptr) { // We allow duplicate definitions of the same field in a class_data_item // but ignore the repeated indexes here, b/21868015. - uint32_t last_field_idx = DexFile::kDexNoIndex; + uint32_t last_field_idx = dex::kDexNoIndex; for (ClassDataItemIterator it(dex_file, class_data); it.HasNextStaticField(); it.Next()) { uint32_t field_idx = it.GetMemberIndex(); // Ordering enforced by DexFileVerifier. - DCHECK(last_field_idx == DexFile::kDexNoIndex || last_field_idx <= field_idx); + DCHECK(last_field_idx == dex::kDexNoIndex || last_field_idx <= field_idx); if (UNLIKELY(field_idx == last_field_idx)) { continue; } @@ -3164,7 +3164,7 @@ void ClassLinker::LoadClassMembers(Thread* self, it.NumDirectMethods(), it.NumVirtualMethods()); size_t class_def_method_index = 0; - uint32_t last_dex_method_index = DexFile::kDexNoIndex; + uint32_t last_dex_method_index = dex::kDexNoIndex; size_t last_class_def_method_index = 0; // TODO These should really use the iterators. for (size_t i = 0; it.HasNextDirectMethod(); i++, it.Next()) { @@ -4121,6 +4121,12 @@ verifier::FailureKind ClassLinker::VerifyClass( EnsureSkipAccessChecksMethods(klass, image_pointer_size_); } } + // Done verifying. Notify the compiler about the verification status, in case the class + // was verified implicitly (eg super class of a compiled class). + if (Runtime::Current()->IsAotCompiler()) { + Runtime::Current()->GetCompilerCallbacks()->UpdateClassState( + ClassReference(&klass->GetDexFile(), klass->GetDexClassDefIndex()), klass->GetStatus()); + } return verifier_failure; } @@ -4427,10 +4433,6 @@ void ClassLinker::CreateProxyConstructor(Handle<mirror::Class> klass, ArtMethod* DCHECK(proxy_constructor != nullptr) << "Could not find <init> method in java.lang.reflect.Proxy"; - // Ensure constructor is in dex cache so that we can use the dex cache to look up the overridden - // constructor method. - GetClassRoot(kJavaLangReflectProxy)->GetDexCache()->SetResolvedMethod( - proxy_constructor->GetDexMethodIndex(), proxy_constructor, image_pointer_size_); // Clone the existing constructor of Proxy (our constructor would just invoke it so steal its // code_ too) DCHECK(out != nullptr); @@ -4457,15 +4459,6 @@ void ClassLinker::CheckProxyConstructor(ArtMethod* constructor) const { void ClassLinker::CreateProxyMethod(Handle<mirror::Class> klass, ArtMethod* prototype, ArtMethod* out) { - // Ensure prototype is in dex cache so that we can use the dex cache to look up the overridden - // prototype method - auto* dex_cache = prototype->GetDeclaringClass()->GetDexCache(); - // Avoid dirtying the dex cache unless we need to. - if (dex_cache->GetResolvedMethod(prototype->GetDexMethodIndex(), image_pointer_size_) != - prototype) { - dex_cache->SetResolvedMethod( - prototype->GetDexMethodIndex(), prototype, image_pointer_size_); - } // We steal everything from the prototype (such as DexCache, invoke stub, etc.) then specialize // as necessary DCHECK(out != nullptr); diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc index 56573f550e..3bd45966e4 100644 --- a/runtime/class_loader_context.cc +++ b/runtime/class_loader_context.cc @@ -187,7 +187,10 @@ bool ClassLoaderContext::Parse(const std::string& spec, bool parse_checksums) { // Opens requested class path files and appends them to opened_dex_files. If the dex files have // been stripped, this opens them from their oat files (which get added to opened_oat_files). bool ClassLoaderContext::OpenDexFiles(InstructionSet isa, const std::string& classpath_dir) { - CHECK(!dex_files_open_attempted_) << "OpenDexFiles should not be called twice"; + if (dex_files_open_attempted_) { + // Do not attempt to re-open the files if we already tried. + return dex_files_open_result_; + } dex_files_open_attempted_ = true; // Assume we can open all dex files. If not, we will set this to false as we go. @@ -203,6 +206,7 @@ bool ClassLoaderContext::OpenDexFiles(InstructionSet isa, const std::string& cla // TODO(calin): Refine the dex opening interface to be able to tell if an archive contains // no dex files. So that we can distinguish the real failures... for (ClassLoaderInfo& info : class_loader_chain_) { + size_t opened_dex_files_index = info.opened_dex_files.size(); for (const std::string& cp_elem : info.classpath) { // If path is relative, append it to the provided base directory. std::string raw_location = cp_elem; @@ -249,6 +253,23 @@ bool ClassLoaderContext::OpenDexFiles(InstructionSet isa, const std::string& cla } } } + + // We finished opening the dex files from the classpath. + // Now update the classpath and the checksum with the locations of the dex files. + // + // We do this because initially the classpath contains the paths of the dex files; and + // some of them might be multi-dexes. So in order to have a consistent view we replace all the + // file paths with the actual dex locations being loaded. + // This will allow the context to VerifyClassLoaderContextMatch which expects or multidex + // location in the class paths. + // Note that this will also remove the paths that could not be opened. + info.classpath.clear(); + info.checksums.clear(); + for (size_t k = opened_dex_files_index; k < info.opened_dex_files.size(); k++) { + std::unique_ptr<const DexFile>& dex = info.opened_dex_files[k]; + info.classpath.push_back(dex->GetLocation()); + info.checksums.push_back(dex->GetLocationChecksum()); + } } return dex_files_open_result_; @@ -637,13 +658,20 @@ static bool IsAbsoluteLocation(const std::string& location) { } bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& context_spec) const { + DCHECK(dex_files_open_attempted_); + DCHECK(dex_files_open_result_); + ClassLoaderContext expected_context; if (!expected_context.Parse(context_spec, /*parse_checksums*/ true)) { LOG(WARNING) << "Invalid class loader context: " << context_spec; return false; } - if (expected_context.special_shared_library_) { + // Special shared library contexts always match. They essentially instruct the runtime + // to ignore the class path check because the oat file is known to be loaded in different + // contexts. OatFileManager will further verify if the oat file can be loaded based on the + // collision check. + if (special_shared_library_ || expected_context.special_shared_library_) { return true; } diff --git a/runtime/class_loader_context.h b/runtime/class_loader_context.h index 9afa880da4..692a6cda5b 100644 --- a/runtime/class_loader_context.h +++ b/runtime/class_loader_context.h @@ -41,7 +41,12 @@ class ClassLoaderContext { // to ClassLoaderInfo::opened_oat_files. The 'classpath_dir' argument specifies the directory to // use for the relative class paths. // Returns true if all dex files where successfully opened. - // It may be called only once per ClassLoaderContext. The second call will abort. + // It may be called only once per ClassLoaderContext. Subsequent calls will return the same + // result without doing anything. + // + // This will replace the class path locations with the locations of the opened dex files. + // (Note that one dex file can contain multidexes. Each multidex will be added to the classpath + // separately.) // // Note that a "false" return could mean that either an apk/jar contained no dex files or // that we hit a I/O or checksum mismatch error. @@ -98,6 +103,7 @@ class ClassLoaderContext { // - the number and type of the class loaders from the chain matches // - the class loader from the same position have the same classpath // (the order and checksum of the dex files matches) + // This should be called after OpenDexFiles(). bool VerifyClassLoaderContextMatch(const std::string& context_spec) const; // Creates the class loader context from the given string. diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc index 18472743fb..ae3dcecb4a 100644 --- a/runtime/class_loader_context_test.cc +++ b/runtime/class_loader_context_test.cc @@ -87,60 +87,29 @@ class ClassLoaderContextTest : public CommonRuntimeTest { void VerifyOpenDexFiles( ClassLoaderContext* context, size_t index, - std::vector<std::vector<std::unique_ptr<const DexFile>>*>& all_dex_files, - LocationCheck mode = LocationCheck::kEquals, - BaseLocationCheck base_mode = BaseLocationCheck::kEquals) { + std::vector<std::unique_ptr<const DexFile>>* all_dex_files) { ASSERT_TRUE(context != nullptr); ASSERT_TRUE(context->dex_files_open_attempted_); ASSERT_TRUE(context->dex_files_open_result_); ClassLoaderContext::ClassLoaderInfo& info = context->class_loader_chain_[index]; - ASSERT_EQ(all_dex_files.size(), info.classpath.size()); + ASSERT_EQ(all_dex_files->size(), info.classpath.size()); + ASSERT_EQ(all_dex_files->size(), info.opened_dex_files.size()); size_t cur_open_dex_index = 0; - for (size_t k = 0; k < all_dex_files.size(); k++) { - std::vector<std::unique_ptr<const DexFile>>& dex_files_for_cp_elem = *(all_dex_files[k]); - for (size_t i = 0; i < dex_files_for_cp_elem.size(); i++) { - ASSERT_LT(cur_open_dex_index, info.opened_dex_files.size()); - - std::unique_ptr<const DexFile>& opened_dex_file = + for (size_t k = 0; k < all_dex_files->size(); k++) { + std::unique_ptr<const DexFile>& opened_dex_file = info.opened_dex_files[cur_open_dex_index++]; - std::unique_ptr<const DexFile>& expected_dex_file = dex_files_for_cp_elem[i]; - - std::string expected_location = expected_dex_file->GetBaseLocation(); - UniqueCPtr<const char[]> expected_real_location( - realpath(expected_location.c_str(), nullptr)); - ASSERT_TRUE(expected_real_location != nullptr) << expected_location; - expected_location.assign(expected_real_location.get()); - expected_location += DexFile::GetMultiDexSuffix(expected_dex_file->GetLocation()); - - switch (mode) { - case LocationCheck::kEquals: - ASSERT_EQ(expected_dex_file->GetLocation(), opened_dex_file->GetLocation()); - break; - case LocationCheck::kEndsWith: - ASSERT_TRUE(android::base::EndsWith(expected_dex_file->GetLocation(), - opened_dex_file->GetLocation().c_str())) - << opened_dex_file->GetLocation() << " vs " << expected_dex_file->GetLocation(); - break; - } - ASSERT_EQ(expected_dex_file->GetLocationChecksum(), opened_dex_file->GetLocationChecksum()); - - std::string class_path_location = info.classpath[k]; - UniqueCPtr<const char[]> class_path_location_real( - realpath(class_path_location.c_str(), nullptr)); - ASSERT_TRUE(class_path_location_real != nullptr); - class_path_location.assign(class_path_location_real.get()); - switch (base_mode) { - case BaseLocationCheck::kEquals: - ASSERT_EQ(class_path_location, opened_dex_file->GetBaseLocation()); - break; - - case BaseLocationCheck::kEndsWith: - ASSERT_TRUE(android::base::EndsWith(opened_dex_file->GetBaseLocation(), - class_path_location.c_str())) - << info.classpath[k] << " vs " << opened_dex_file->GetBaseLocation(); - break; - } - } + std::unique_ptr<const DexFile>& expected_dex_file = (*all_dex_files)[k]; + + std::string expected_location = expected_dex_file->GetBaseLocation(); + UniqueCPtr<const char[]> expected_real_location( + realpath(expected_location.c_str(), nullptr)); + ASSERT_TRUE(expected_real_location != nullptr) << expected_location; + expected_location.assign(expected_real_location.get()); + expected_location += DexFile::GetMultiDexSuffix(expected_dex_file->GetLocation()); + + ASSERT_EQ(expected_location, opened_dex_file->GetLocation()); + ASSERT_EQ(expected_dex_file->GetLocationChecksum(), opened_dex_file->GetLocationChecksum()); + ASSERT_EQ(info.classpath[k], opened_dex_file->GetLocation()); } } @@ -182,6 +151,11 @@ class ClassLoaderContextTest : public CommonRuntimeTest { } } + void PretendContextOpenedDexFiles(ClassLoaderContext* context) { + context->dex_files_open_attempted_ = true; + context->dex_files_open_result_ = true; + } + private: void VerifyClassLoaderInfo(ClassLoaderContext* context, size_t index, @@ -201,11 +175,9 @@ class ClassLoaderContextTest : public CommonRuntimeTest { ClassLoaderContext::ClassLoaderType type, const std::string& test_name) { std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles(test_name.c_str()); - std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files; - all_dex_files.push_back(&dex_files); VerifyClassLoaderInfo(context, index, type, GetTestDexFileName(test_name.c_str())); - VerifyOpenDexFiles(context, index, all_dex_files); + VerifyOpenDexFiles(context, index, &dex_files); } }; @@ -276,11 +248,8 @@ TEST_F(ClassLoaderContextTest, OpenInvalidDexFiles) { TEST_F(ClassLoaderContextTest, OpenValidDexFiles) { std::string multidex_name = GetTestDexFileName("MultiDex"); - std::vector<std::unique_ptr<const DexFile>> multidex_files = OpenTestDexFiles("MultiDex"); std::string myclass_dex_name = GetTestDexFileName("MyClass"); - std::vector<std::unique_ptr<const DexFile>> myclass_dex_files = OpenTestDexFiles("MyClass"); std::string dex_name = GetTestDexFileName("Main"); - std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("Main"); std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create( @@ -290,14 +259,16 @@ TEST_F(ClassLoaderContextTest, OpenValidDexFiles) { ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, /*classpath_dir*/ "")); VerifyContextSize(context.get(), 2); - std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files0; - all_dex_files0.push_back(&multidex_files); - all_dex_files0.push_back(&myclass_dex_files); - std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files1; - all_dex_files1.push_back(&dex_files); - - VerifyOpenDexFiles(context.get(), 0, all_dex_files0); - VerifyOpenDexFiles(context.get(), 1, all_dex_files1); + + std::vector<std::unique_ptr<const DexFile>> all_dex_files0 = OpenTestDexFiles("MultiDex"); + std::vector<std::unique_ptr<const DexFile>> myclass_dex_files = OpenTestDexFiles("MyClass"); + for (size_t i = 0; i < myclass_dex_files.size(); i++) { + all_dex_files0.emplace_back(myclass_dex_files[i].release()); + } + VerifyOpenDexFiles(context.get(), 0, &all_dex_files0); + + std::vector<std::unique_ptr<const DexFile>> all_dex_files1 = OpenTestDexFiles("Main"); + VerifyOpenDexFiles(context.get(), 1, &all_dex_files1); } class ScratchSymLink { @@ -330,11 +301,10 @@ TEST_F(ClassLoaderContextTest, OpenValidDexFilesSymLink) { ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, /*classpath_dir*/ "")); VerifyContextSize(context.get(), 1); - std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files0; + std::vector<std::unique_ptr<const DexFile>> myclass_dex_files = OpenTestDexFiles("MyClass"); - all_dex_files0.push_back(&myclass_dex_files); - VerifyOpenDexFiles(context.get(), 0, all_dex_files0); + VerifyOpenDexFiles(context.get(), 0, &myclass_dex_files); } static std::string CreateRelativeString(const std::string& in, const char* cwd) { @@ -353,11 +323,8 @@ TEST_F(ClassLoaderContextTest, OpenValidDexFilesRelative) { PLOG(FATAL) << "Could not get working directory"; } std::string multidex_name = CreateRelativeString(GetTestDexFileName("MultiDex"), cwd_buf); - std::vector<std::unique_ptr<const DexFile>> multidex_files = OpenTestDexFiles("MultiDex"); std::string myclass_dex_name = CreateRelativeString(GetTestDexFileName("MyClass"), cwd_buf); - std::vector<std::unique_ptr<const DexFile>> myclass_dex_files = OpenTestDexFiles("MyClass"); std::string dex_name = CreateRelativeString(GetTestDexFileName("Main"), cwd_buf); - std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("Main"); std::unique_ptr<ClassLoaderContext> context = @@ -367,15 +334,15 @@ TEST_F(ClassLoaderContextTest, OpenValidDexFilesRelative) { ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, /*classpath_dir*/ "")); - VerifyContextSize(context.get(), 2); - std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files0; - all_dex_files0.push_back(&multidex_files); - all_dex_files0.push_back(&myclass_dex_files); - std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files1; - all_dex_files1.push_back(&dex_files); - - VerifyOpenDexFiles(context.get(), 0, all_dex_files0, LocationCheck::kEndsWith); - VerifyOpenDexFiles(context.get(), 1, all_dex_files1, LocationCheck::kEndsWith); + std::vector<std::unique_ptr<const DexFile>> all_dex_files0 = OpenTestDexFiles("MultiDex"); + std::vector<std::unique_ptr<const DexFile>> myclass_dex_files = OpenTestDexFiles("MyClass"); + for (size_t i = 0; i < myclass_dex_files.size(); i++) { + all_dex_files0.emplace_back(myclass_dex_files[i].release()); + } + VerifyOpenDexFiles(context.get(), 0, &all_dex_files0); + + std::vector<std::unique_ptr<const DexFile>> all_dex_files1 = OpenTestDexFiles("Main"); + VerifyOpenDexFiles(context.get(), 1, &all_dex_files1); } TEST_F(ClassLoaderContextTest, OpenValidDexFilesClasspathDir) { @@ -384,12 +351,8 @@ TEST_F(ClassLoaderContextTest, OpenValidDexFilesClasspathDir) { PLOG(FATAL) << "Could not get working directory"; } std::string multidex_name = CreateRelativeString(GetTestDexFileName("MultiDex"), cwd_buf); - std::vector<std::unique_ptr<const DexFile>> multidex_files = OpenTestDexFiles("MultiDex"); std::string myclass_dex_name = CreateRelativeString(GetTestDexFileName("MyClass"), cwd_buf); - std::vector<std::unique_ptr<const DexFile>> myclass_dex_files = OpenTestDexFiles("MyClass"); std::string dex_name = CreateRelativeString(GetTestDexFileName("Main"), cwd_buf); - std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("Main"); - std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create( @@ -399,16 +362,15 @@ TEST_F(ClassLoaderContextTest, OpenValidDexFilesClasspathDir) { ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, cwd_buf)); VerifyContextSize(context.get(), 2); - std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files0; - all_dex_files0.push_back(&multidex_files); - all_dex_files0.push_back(&myclass_dex_files); - std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files1; - all_dex_files1.push_back(&dex_files); - - VerifyOpenDexFiles( - context.get(), 0, all_dex_files0, LocationCheck::kEquals, BaseLocationCheck::kEndsWith); - VerifyOpenDexFiles( - context.get(), 1, all_dex_files1, LocationCheck::kEquals, BaseLocationCheck::kEndsWith); + std::vector<std::unique_ptr<const DexFile>> all_dex_files0 = OpenTestDexFiles("MultiDex"); + std::vector<std::unique_ptr<const DexFile>> myclass_dex_files = OpenTestDexFiles("MyClass"); + for (size_t i = 0; i < myclass_dex_files.size(); i++) { + all_dex_files0.emplace_back(myclass_dex_files[i].release()); + } + VerifyOpenDexFiles(context.get(), 0, &all_dex_files0); + + std::vector<std::unique_ptr<const DexFile>> all_dex_files1 = OpenTestDexFiles("Main"); + VerifyOpenDexFiles(context.get(), 1, &all_dex_files1); } TEST_F(ClassLoaderContextTest, OpenInvalidDexFilesMix) { @@ -660,6 +622,9 @@ TEST_F(ClassLoaderContextTest, CreateContextForClassLoader) { TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextMatch) { std::string context_spec = "PCL[a.dex*123:b.dex*456];DLC[c.dex*890]"; std::unique_ptr<ClassLoaderContext> context = ParseContextWithChecksums(context_spec); + // Pretend that we successfully open the dex files to pass the DCHECKS. + // (as it's much easier to test all the corner cases without relying on actual dex files). + PretendContextOpenedDexFiles(context.get()); VerifyContextSize(context.get(), 2); VerifyClassLoaderPCL(context.get(), 0, "a.dex:b.dex"); diff --git a/runtime/class_reference.h b/runtime/class_reference.h index 7b206126d6..2ef9ab8959 100644 --- a/runtime/class_reference.h +++ b/runtime/class_reference.h @@ -20,22 +20,22 @@ #include <stdint.h> #include <utility> +#include "dex_file_reference.h" + namespace art { class DexFile; // A class is uniquely located by its DexFile and the class_defs_ table index into that DexFile -typedef std::pair<const DexFile*, uint32_t> ClassReference; - -inline bool operator<(const ClassReference& lhs, const ClassReference& rhs) { - if (lhs.second < rhs.second) { - return true; - } else if (lhs.second > rhs.second) { - return false; - } else { - return (lhs.first < rhs.first); +class ClassReference : public DexFileReference { + public: + ClassReference(const DexFile* file, uint32_t class_def_idx) + : DexFileReference(file, class_def_idx) {} + + uint32_t ClassDefIdx() const { + return index; } -} +}; } // namespace art diff --git a/runtime/common_throws.h b/runtime/common_throws.h index 4afef7993d..2fc2016e50 100644 --- a/runtime/common_throws.h +++ b/runtime/common_throws.h @@ -18,7 +18,6 @@ #define ART_RUNTIME_COMMON_THROWS_H_ #include "base/mutex.h" -#include "invoke_type.h" #include "obj_ptr.h" namespace art { @@ -30,6 +29,7 @@ namespace mirror { class ArtField; class ArtMethod; class DexFile; +enum InvokeType : uint32_t; class Signature; class StringPiece; diff --git a/runtime/compiler_callbacks.h b/runtime/compiler_callbacks.h index 9b227141e0..9041df94b9 100644 --- a/runtime/compiler_callbacks.h +++ b/runtime/compiler_callbacks.h @@ -65,6 +65,9 @@ class CompilerCallbacks { return mode_ == CallbackMode::kCompileBootImage; } + virtual void UpdateClassState(ClassReference ref ATTRIBUTE_UNUSED, + ClassStatus state ATTRIBUTE_UNUSED) {} + protected: explicit CompilerCallbacks(CallbackMode mode) : mode_(mode) { } diff --git a/runtime/debugger.cc b/runtime/debugger.cc index af56810fcb..6daec72229 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -34,6 +34,7 @@ #include "class_linker.h" #include "dex_file-inl.h" #include "dex_file_annotations.h" +#include "dex_file_types.h" #include "dex_instruction.h" #include "entrypoints/runtime_asm_entrypoints.h" #include "gc/accounting/card_table-inl.h" @@ -268,6 +269,11 @@ class DebugInstrumentationListener FINAL : public instrumentation::Instrumentati LOG(ERROR) << "Unexpected exception handled event in debugger"; } + // TODO Might be worth it to implement this. + void WatchedFramePop(Thread* thread ATTRIBUTE_UNUSED, + const ShadowFrame& frame ATTRIBUTE_UNUSED) OVERRIDE { + LOG(ERROR) << "Unexpected WatchedFramePop event in debugger"; + } private: static bool IsReturn(ArtMethod* method, uint32_t dex_pc) @@ -2212,6 +2218,8 @@ JDWP::JdwpThreadStatus Dbg::ToJdwpThreadStatus(ThreadState state) { case kTerminated: return JDWP::TS_ZOMBIE; case kTimedWaiting: + case kWaitingForTaskProcessor: + case kWaitingForLockInflation: case kWaitingForCheckPointsToRun: case kWaitingForDebuggerSend: case kWaitingForDebuggerSuspension: @@ -2967,8 +2975,8 @@ class CatchLocationFinder : public StackVisitor { this_at_throw_(handle_scope_.NewHandle<mirror::Object>(nullptr)), catch_method_(nullptr), throw_method_(nullptr), - catch_dex_pc_(DexFile::kDexNoIndex), - throw_dex_pc_(DexFile::kDexNoIndex) { + catch_dex_pc_(dex::kDexNoIndex), + throw_dex_pc_(dex::kDexNoIndex) { } bool VisitFrame() OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) { @@ -2990,13 +2998,13 @@ class CatchLocationFinder : public StackVisitor { throw_dex_pc_ = dex_pc; } - if (dex_pc != DexFile::kDexNoIndex) { + if (dex_pc != dex::kDexNoIndex) { StackHandleScope<1> hs(GetThread()); uint32_t found_dex_pc; Handle<mirror::Class> exception_class(hs.NewHandle(exception_->GetClass())); bool unused_clear_exception; found_dex_pc = method->FindCatchBlock(exception_class, dex_pc, &unused_clear_exception); - if (found_dex_pc != DexFile::kDexNoIndex) { + if (found_dex_pc != dex::kDexNoIndex) { catch_method_ = method; catch_dex_pc_ = found_dex_pc; return false; // End stack walk. diff --git a/runtime/dex_file-inl.h b/runtime/dex_file-inl.h index b163cdb8dc..1b7c31859c 100644 --- a/runtime/dex_file-inl.h +++ b/runtime/dex_file-inl.h @@ -21,6 +21,7 @@ #include "base/logging.h" #include "base/stringpiece.h" #include "dex_file.h" +#include "invoke_type.h" #include "leb128.h" namespace art { @@ -199,6 +200,25 @@ inline bool Signature::operator==(const Signature& rhs) const { return true; } +inline +InvokeType ClassDataItemIterator::GetMethodInvokeType(const DexFile::ClassDef& class_def) const { + if (HasNextDirectMethod()) { + if ((GetRawMemberAccessFlags() & kAccStatic) != 0) { + return kStatic; + } else { + return kDirect; + } + } else { + DCHECK_EQ(GetRawMemberAccessFlags() & kAccStatic, 0U); + if ((class_def.access_flags_ & kAccInterface) != 0) { + return kInterface; + } else if ((GetRawMemberAccessFlags() & kAccConstructor) != 0) { + return kSuper; + } else { + return kVirtual; + } + } +} } // namespace art diff --git a/runtime/dex_file.cc b/runtime/dex_file.cc index 6d1158260a..f70846bce0 100644 --- a/runtime/dex_file.cc +++ b/runtime/dex_file.cc @@ -1066,7 +1066,7 @@ bool DexFile::DecodeDebugLocalInfo(const CodeItem* code_item, bool is_static, ui uint32_t name_idx = DecodeUnsignedLeb128P1(&stream); uint16_t descriptor_idx = DecodeUnsignedLeb128P1(&stream); - uint32_t signature_idx = kDexNoIndex; + uint32_t signature_idx = dex::kDexNoIndex; if (opcode == DBG_START_LOCAL_EXTENDED) { signature_idx = DecodeUnsignedLeb128P1(&stream); } diff --git a/runtime/dex_file.h b/runtime/dex_file.h index 5f81b981dd..480742775b 100644 --- a/runtime/dex_file.h +++ b/runtime/dex_file.h @@ -25,12 +25,12 @@ #include "base/value_object.h" #include "dex_file_types.h" #include "globals.h" -#include "invoke_type.h" #include "jni.h" #include "modifiers.h" namespace art { +enum InvokeType : uint32_t; class MemMap; class OatDexFile; class Signature; @@ -56,9 +56,6 @@ class DexFile { static const char* kClassesDex; // The value of an invalid index. - static const uint32_t kDexNoIndex = 0xFFFFFFFF; - - // The value of an invalid index. static const uint16_t kDexNoIndex16 = 0xFFFF; // The separator character in MultiDex locations. @@ -1238,12 +1235,6 @@ class DexFile { ART_FRIEND_TEST(ClassLinkerTest, RegisterDexFileName); // for constructor }; -struct DexFileReference { - DexFileReference(const DexFile* file, uint32_t idx) : dex_file(file), index(idx) { } - const DexFile* dex_file; - uint32_t index; -}; - std::ostream& operator<<(std::ostream& os, const DexFile& dex_file); // Iterate over a dex file's ProtoId's paramters @@ -1427,24 +1418,7 @@ class ClassDataItemIterator { bool MemberIsFinal() const { return GetRawMemberAccessFlags() & kAccFinal; } - InvokeType GetMethodInvokeType(const DexFile::ClassDef& class_def) const { - if (HasNextDirectMethod()) { - if ((GetRawMemberAccessFlags() & kAccStatic) != 0) { - return kStatic; - } else { - return kDirect; - } - } else { - DCHECK_EQ(GetRawMemberAccessFlags() & kAccStatic, 0U); - if ((class_def.access_flags_ & kAccInterface) != 0) { - return kInterface; - } else if ((GetRawMemberAccessFlags() & kAccConstructor) != 0) { - return kSuper; - } else { - return kVirtual; - } - } - } + ALWAYS_INLINE InvokeType GetMethodInvokeType(const DexFile::ClassDef& class_def) const; const DexFile::CodeItem* GetMethodCodeItem() const { return dex_file_.GetCodeItem(method_.code_off_); } diff --git a/runtime/dex_file_reference.h b/runtime/dex_file_reference.h new file mode 100644 index 0000000000..01a64257a8 --- /dev/null +++ b/runtime/dex_file_reference.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017 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_DEX_FILE_REFERENCE_H_ +#define ART_RUNTIME_DEX_FILE_REFERENCE_H_ + +#include <cstdint> + +namespace art { + +class DexFile; + +class DexFileReference { + public: + DexFileReference(const DexFile* file, uint32_t idx) : dex_file(file), index(idx) {} + const DexFile* dex_file; + uint32_t index; + + struct Comparator { + bool operator()(const DexFileReference& a, const DexFileReference& b) const { + if (a.dex_file != b.dex_file) { + return a.dex_file < b.dex_file; + } + return a.index < b.index; + } + }; +}; + +// Default comparators, compares the indicies, not the backing data. +inline bool operator<(const DexFileReference& a, const DexFileReference& b) { + return DexFileReference::Comparator()(a, b); +} +inline bool operator==(const DexFileReference& a, const DexFileReference& b) { + return a.dex_file == b.dex_file && a.index == b.index; +} + +} // namespace art + +#endif // ART_RUNTIME_DEX_FILE_REFERENCE_H_ diff --git a/runtime/dex_file_types.h b/runtime/dex_file_types.h index bd779c4ab0..acca7c055b 100644 --- a/runtime/dex_file_types.h +++ b/runtime/dex_file_types.h @@ -23,6 +23,8 @@ namespace art { namespace dex { +constexpr uint32_t kDexNoIndex = 0xFFFFFFFF; + class StringIndex { public: uint32_t index_; diff --git a/runtime/dex_method_iterator.h b/runtime/dex_method_iterator.h index 8a4bed31b1..a44bc16287 100644 --- a/runtime/dex_method_iterator.h +++ b/runtime/dex_method_iterator.h @@ -19,7 +19,7 @@ #include <vector> -#include "dex_file.h" +#include "dex_file-inl.h" namespace art { diff --git a/runtime/entrypoints/entrypoint_utils.h b/runtime/entrypoints/entrypoint_utils.h index 4f9090815f..d2c98e153e 100644 --- a/runtime/entrypoints/entrypoint_utils.h +++ b/runtime/entrypoints/entrypoint_utils.h @@ -27,7 +27,6 @@ #include "dex_instruction.h" #include "gc/allocator_type.h" #include "handle.h" -#include "invoke_type.h" #include "jvalue.h" namespace art { @@ -41,6 +40,7 @@ namespace mirror { class ArtField; class ArtMethod; +enum InvokeType : uint32_t; class OatQuickMethodHeader; class ScopedObjectAccessAlreadyRunnable; class Thread; diff --git a/runtime/entrypoints/quick/quick_lock_entrypoints.cc b/runtime/entrypoints/quick/quick_lock_entrypoints.cc index b4f945a5d6..4bcce217d6 100644 --- a/runtime/entrypoints/quick/quick_lock_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_lock_entrypoints.cc @@ -29,15 +29,22 @@ extern "C" int artLockObjectFromCode(mirror::Object* obj, Thread* self) ThrowNullPointerException("Null reference used for synchronization (monitor-enter)"); return -1; // Failure. } else { - if (kIsDebugBuild) { - obj = obj->MonitorEnter(self); // May block - CHECK(self->HoldsLock(obj)); - CHECK(!self->IsExceptionPending()); + obj = obj->MonitorEnter(self); // May block + DCHECK(self->HoldsLock(obj)); + // Exceptions can be thrown by monitor event listeners. This is expected to be rare however. + if (UNLIKELY(self->IsExceptionPending())) { + // TODO Remove this DCHECK if we expand the use of monitor callbacks. + DCHECK(Runtime::Current()->HasLoadedPlugins()) + << "Exceptions are only expected to be thrown by plugin code which doesn't seem to be " + << "loaded."; + // We need to get rid of the lock + bool unlocked = obj->MonitorExit(self); + DCHECK(unlocked); + return -1; // Failure. } else { - obj->MonitorEnter(self); // May block + DCHECK(self->HoldsLock(obj)); + return 0; // Success. } - return 0; // Success. - // Only possible exception is NPE and is handled before entry } } diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index 7b83f20450..211381ccbe 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -21,6 +21,7 @@ #include "common_throws.h" #include "debugger.h" #include "dex_file-inl.h" +#include "dex_file_types.h" #include "dex_instruction-inl.h" #include "entrypoints/entrypoint_utils-inl.h" #include "entrypoints/runtime_asm_entrypoints.h" @@ -76,13 +77,13 @@ class QuickArgumentVisitor { // | S0 | // | | 4x2 bytes padding // | Method* | <- sp - static constexpr bool kSplitPairAcrossRegisterAndStack = kArm32QuickCodeUseSoftFloat; - static constexpr bool kAlignPairRegister = !kArm32QuickCodeUseSoftFloat; - static constexpr bool kQuickSoftFloatAbi = kArm32QuickCodeUseSoftFloat; - static constexpr bool kQuickDoubleRegAlignedFloatBackFilled = !kArm32QuickCodeUseSoftFloat; + static constexpr bool kSplitPairAcrossRegisterAndStack = false; + static constexpr bool kAlignPairRegister = true; + static constexpr bool kQuickSoftFloatAbi = false; + static constexpr bool kQuickDoubleRegAlignedFloatBackFilled = true; static constexpr bool kQuickSkipOddFpRegisters = false; static constexpr size_t kNumQuickGprArgs = 3; - static constexpr size_t kNumQuickFprArgs = kArm32QuickCodeUseSoftFloat ? 0 : 16; + static constexpr size_t kNumQuickFprArgs = 16; static constexpr bool kGprFprLockstep = false; static constexpr size_t kQuickCalleeSaveFrame_RefAndArgs_Fpr1Offset = arm::ArmCalleeSaveFpr1Offset(CalleeSaveType::kSaveRefsAndArgs); // Offset of first FPR arg. @@ -697,6 +698,72 @@ void BuildQuickShadowFrameVisitor::Visit() { ++cur_reg_; } +// Don't inline. See b/65159206. +NO_INLINE +static void HandleDeoptimization(JValue* result, + ArtMethod* method, + ShadowFrame* deopt_frame, + ManagedStack* fragment) + REQUIRES_SHARED(Locks::mutator_lock_) { + // Coming from partial-fragment deopt. + Thread* self = Thread::Current(); + if (kIsDebugBuild) { + // Sanity-check: are the methods as expected? We check that the last shadow frame (the bottom + // of the call-stack) corresponds to the called method. + ShadowFrame* linked = deopt_frame; + while (linked->GetLink() != nullptr) { + linked = linked->GetLink(); + } + CHECK_EQ(method, linked->GetMethod()) << method->PrettyMethod() << " " + << ArtMethod::PrettyMethod(linked->GetMethod()); + } + + if (VLOG_IS_ON(deopt)) { + // Print out the stack to verify that it was a partial-fragment deopt. + LOG(INFO) << "Continue-ing from deopt. Stack is:"; + QuickExceptionHandler::DumpFramesWithType(self, true); + } + + ObjPtr<mirror::Throwable> pending_exception; + bool from_code = false; + DeoptimizationMethodType method_type; + self->PopDeoptimizationContext(/* out */ result, + /* out */ &pending_exception, + /* out */ &from_code, + /* out */ &method_type); + + // Push a transition back into managed code onto the linked list in thread. + self->PushManagedStackFragment(fragment); + + // Ensure that the stack is still in order. + if (kIsDebugBuild) { + class DummyStackVisitor : public StackVisitor { + public: + explicit DummyStackVisitor(Thread* self_in) REQUIRES_SHARED(Locks::mutator_lock_) + : StackVisitor(self_in, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames) {} + + bool VisitFrame() OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) { + // Nothing to do here. In a debug build, SanityCheckFrame will do the work in the walking + // logic. Just always say we want to continue. + return true; + } + }; + DummyStackVisitor dsv(self); + dsv.WalkStack(); + } + + // Restore the exception that was pending before deoptimization then interpret the + // deoptimized frames. + if (pending_exception != nullptr) { + self->SetException(pending_exception); + } + interpreter::EnterInterpreterFromDeoptimize(self, + deopt_frame, + result, + from_code, + DeoptimizationMethodType::kDefault); +} + extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, ArtMethod** sp) REQUIRES_SHARED(Locks::mutator_lock_) { // Ensure we don't get thread suspension until the object arguments are safely in the shadow @@ -722,64 +789,8 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, JValue result; - if (deopt_frame != nullptr) { - // Coming from partial-fragment deopt. - - if (kIsDebugBuild) { - // Sanity-check: are the methods as expected? We check that the last shadow frame (the bottom - // of the call-stack) corresponds to the called method. - ShadowFrame* linked = deopt_frame; - while (linked->GetLink() != nullptr) { - linked = linked->GetLink(); - } - CHECK_EQ(method, linked->GetMethod()) << method->PrettyMethod() << " " - << ArtMethod::PrettyMethod(linked->GetMethod()); - } - - if (VLOG_IS_ON(deopt)) { - // Print out the stack to verify that it was a partial-fragment deopt. - LOG(INFO) << "Continue-ing from deopt. Stack is:"; - QuickExceptionHandler::DumpFramesWithType(self, true); - } - - ObjPtr<mirror::Throwable> pending_exception; - bool from_code = false; - DeoptimizationMethodType method_type; - self->PopDeoptimizationContext(/* out */ &result, - /* out */ &pending_exception, - /* out */ &from_code, - /* out */ &method_type); - - // Push a transition back into managed code onto the linked list in thread. - self->PushManagedStackFragment(&fragment); - - // Ensure that the stack is still in order. - if (kIsDebugBuild) { - class DummyStackVisitor : public StackVisitor { - public: - explicit DummyStackVisitor(Thread* self_in) REQUIRES_SHARED(Locks::mutator_lock_) - : StackVisitor(self_in, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames) {} - - bool VisitFrame() OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) { - // Nothing to do here. In a debug build, SanityCheckFrame will do the work in the walking - // logic. Just always say we want to continue. - return true; - } - }; - DummyStackVisitor dsv(self); - dsv.WalkStack(); - } - - // Restore the exception that was pending before deoptimization then interpret the - // deoptimized frames. - if (pending_exception != nullptr) { - self->SetException(pending_exception); - } - interpreter::EnterInterpreterFromDeoptimize(self, - deopt_frame, - &result, - from_code, - DeoptimizationMethodType::kDefault); + if (UNLIKELY(deopt_frame != nullptr)) { + HandleDeoptimization(&result, method, deopt_frame, &fragment); } else { const char* old_cause = self->StartAssertNoThreadSuspension( "Building interpreter shadow frame"); @@ -1156,34 +1167,33 @@ extern "C" const void* artQuickResolutionTrampoline( LOG(FATAL) << "Unexpected call into trampoline: " << instr->DumpString(nullptr); UNREACHABLE(); } - called_method.dex_method_index = (is_range) ? instr->VRegB_3rc() : instr->VRegB_35c(); + called_method.index = (is_range) ? instr->VRegB_3rc() : instr->VRegB_35c(); // Check that the invoke matches what we expected, note that this path only happens for debug // builds. if (found_stack_map) { DCHECK_EQ(stack_map_invoke_type, invoke_type); if (invoke_type != kSuper) { // Super may be sharpened. - DCHECK_EQ(stack_map_dex_method_idx, called_method.dex_method_index) + DCHECK_EQ(stack_map_dex_method_idx, called_method.index) << called_method.dex_file->PrettyMethod(stack_map_dex_method_idx) << " " - << called_method.dex_file->PrettyMethod(called_method.dex_method_index); + << called_method.PrettyMethod(); } } else { VLOG(dex) << "Accessed dex file for invoke " << invoke_type << " " - << called_method.dex_method_index; + << called_method.index; } } else { invoke_type = stack_map_invoke_type; - called_method.dex_method_index = stack_map_dex_method_idx; + called_method.index = stack_map_dex_method_idx; } } else { invoke_type = kStatic; called_method.dex_file = called->GetDexFile(); - called_method.dex_method_index = called->GetDexMethodIndex(); + called_method.index = called->GetDexMethodIndex(); } uint32_t shorty_len; const char* shorty = - called_method.dex_file->GetMethodShorty( - called_method.dex_file->GetMethodId(called_method.dex_method_index), &shorty_len); + called_method.dex_file->GetMethodShorty(called_method.GetMethodId(), &shorty_len); RememberForGcArgumentVisitor visitor(sp, invoke_type == kStatic, shorty, shorty_len, &soa); visitor.VisitArguments(); self->EndAssertNoThreadSuspension(old_cause); @@ -1196,7 +1206,7 @@ extern "C" const void* artQuickResolutionTrampoline( hs.NewHandleWrapper(virtual_or_interface ? &receiver : &dummy)); DCHECK_EQ(caller->GetDexFile(), called_method.dex_file); called = linker->ResolveMethod<ClassLinker::ResolveMode::kCheckICCEAndIAE>( - self, called_method.dex_method_index, caller, invoke_type); + self, called_method.index, caller, invoke_type); // Update .bss entry in oat file if any. if (called != nullptr && called_method.dex_file->GetOatDexFile() != nullptr) { @@ -1207,10 +1217,10 @@ extern "C" const void* artQuickResolutionTrampoline( mapping->begin(), mapping->end(), [called_method](const MethodBssMappingEntry& entry) { - return entry.method_index < called_method.dex_method_index; + return entry.method_index < called_method.index; }); - if (pp != mapping->end() && pp->CoversIndex(called_method.dex_method_index)) { - size_t bss_offset = pp->GetBssOffset(called_method.dex_method_index, + if (pp != mapping->end() && pp->CoversIndex(called_method.index)) { + size_t bss_offset = pp->GetBssOffset(called_method.index, static_cast<size_t>(kRuntimePointerSize)); DCHECK_ALIGNED(bss_offset, static_cast<size_t>(kRuntimePointerSize)); const OatFile* oat_file = called_method.dex_file->GetOatDexFile()->GetOatFile(); @@ -1250,7 +1260,7 @@ extern "C" const void* artQuickResolutionTrampoline( // TODO Maybe put this into a mirror::Class function. ObjPtr<mirror::Class> ref_class = linker->LookupResolvedType( *dex_cache->GetDexFile(), - dex_cache->GetDexFile()->GetMethodId(called_method.dex_method_index).class_idx_, + dex_cache->GetDexFile()->GetMethodId(called_method.index).class_idx_, dex_cache.Get(), class_loader.Get()); if (ref_class->IsInterface()) { @@ -1264,48 +1274,6 @@ extern "C" const void* artQuickResolutionTrampoline( CHECK(called != nullptr) << orig_called->PrettyMethod() << " " << mirror::Object::PrettyTypeOf(receiver) << " " << invoke_type << " " << orig_called->GetVtableIndex(); - - // We came here because of sharpening. Ensure the dex cache is up-to-date on the method index - // of the sharpened method avoiding dirtying the dex cache if possible. - // Note, called_method.dex_method_index references the dex method before the - // FindVirtualMethodFor... This is ok for FindDexMethodIndexInOtherDexFile that only cares - // about the name and signature. - uint32_t update_dex_cache_method_index = called->GetDexMethodIndex(); - if (called->GetDexFile() != caller->GetDexFile()) { - // Calling from one dex file to another, need to compute the method index appropriate to - // the caller's dex file. Since we get here only if the original called was a runtime - // method, we've got the correct dex_file and a dex_method_idx from above. - DCHECK(!called_method_known_on_entry); - DCHECK_EQ(caller->GetDexFile(), called_method.dex_file); - const DexFile* caller_dex_file = called_method.dex_file; - uint32_t caller_method_name_and_sig_index = called_method.dex_method_index; - update_dex_cache_method_index = - called->FindDexMethodIndexInOtherDexFile(*caller_dex_file, - caller_method_name_and_sig_index); - } - if (update_dex_cache_method_index != DexFile::kDexNoIndex) { - // Note: We do not need the read barrier for the dex cache as the SetResolvedMethod() - // operates on native (non-moveable) data and constants (num_resolved_methods_). - ObjPtr<mirror::DexCache> caller_dex_cache = caller->GetDexCache<kWithoutReadBarrier>(); - if (caller_dex_cache->GetResolvedMethod( - update_dex_cache_method_index, kRuntimePointerSize) != called) { - caller_dex_cache->SetResolvedMethod(update_dex_cache_method_index, - called, - kRuntimePointerSize); - } - } - } else if (invoke_type == kStatic) { - const auto called_dex_method_idx = called->GetDexMethodIndex(); - // For static invokes, we may dispatch to the static method in the superclass but resolve - // using the subclass. To prevent getting slow paths on each invoke, we force set the - // resolved method for the super class dex method index if we are in the same dex file. - // b/19175856 - if (called->GetDexFile() == called_method.dex_file && - called_method.dex_method_index != called_dex_method_idx) { - called->GetDexCache()->SetResolvedMethod(called_dex_method_idx, - called, - kRuntimePointerSize); - } } // Ensure that the called method's class is initialized. diff --git a/runtime/fault_handler.cc b/runtime/fault_handler.cc index 7d01af02a3..6a4e5b5f01 100644 --- a/runtime/fault_handler.cc +++ b/runtime/fault_handler.cc @@ -23,6 +23,7 @@ #include "art_method-inl.h" #include "base/safe_copy.h" #include "base/stl_util.h" +#include "dex_file_types.h" #include "mirror/class.h" #include "mirror/object_reference.h" #include "oat_quick_method_header.h" @@ -311,7 +312,7 @@ bool FaultManager::IsInGeneratedCode(siginfo_t* siginfo, void* context, bool che } uint32_t dexpc = method_header->ToDexPc(method_obj, return_pc, false); VLOG(signals) << "dexpc: " << dexpc; - return !check_dex_pc || dexpc != DexFile::kDexNoIndex; + return !check_dex_pc || dexpc != dex::kDexNoIndex; } FaultHandler::FaultHandler(FaultManager* manager) : manager_(manager) { diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h index 1534fd69d1..d673b4ac29 100644 --- a/runtime/gc/heap.h +++ b/runtime/gc/heap.h @@ -37,6 +37,7 @@ #include "obj_ptr.h" #include "offsets.h" #include "process_state.h" +#include "read_barrier_config.h" #include "safe_map.h" #include "verify_object.h" diff --git a/runtime/gc/task_processor.cc b/runtime/gc/task_processor.cc index 0704a68d95..e928644054 100644 --- a/runtime/gc/task_processor.cc +++ b/runtime/gc/task_processor.cc @@ -34,14 +34,14 @@ TaskProcessor::~TaskProcessor() { } void TaskProcessor::AddTask(Thread* self, HeapTask* task) { - ScopedThreadStateChange tsc(self, kBlocked); + ScopedThreadStateChange tsc(self, kWaitingForTaskProcessor); MutexLock mu(self, *lock_); tasks_.insert(task); cond_->Signal(self); } HeapTask* TaskProcessor::GetTask(Thread* self) { - ScopedThreadStateChange tsc(self, kBlocked); + ScopedThreadStateChange tsc(self, kWaitingForTaskProcessor); MutexLock mu(self, *lock_); while (true) { if (tasks_.empty()) { diff --git a/runtime/gc_root.h b/runtime/gc_root.h index 0894e9bee5..986e28ec79 100644 --- a/runtime/gc_root.h +++ b/runtime/gc_root.h @@ -20,6 +20,7 @@ #include "base/macros.h" #include "base/mutex.h" // For Locks::mutator_lock_. #include "mirror/object_reference.h" +#include "read_barrier_option.h" namespace art { class ArtField; diff --git a/runtime/globals.h b/runtime/globals.h index 256306d1de..f10c7805c5 100644 --- a/runtime/globals.h +++ b/runtime/globals.h @@ -19,8 +19,8 @@ #include <stddef.h> #include <stdint.h> -#include "read_barrier_c.h" -#include "read_barrier_option.h" + +#include "heap_poisoning.h" namespace art { @@ -105,49 +105,6 @@ static constexpr bool kMarkCompactSupport = false && kMovingCollector; // True if we allow moving classes. static constexpr bool kMovingClasses = !kMarkCompactSupport; -// If true, the quick compiler embeds class pointers in the compiled -// code, if possible. -static constexpr bool kEmbedClassInCode = true; - -#ifdef USE_BAKER_READ_BARRIER -static constexpr bool kUseBakerReadBarrier = true; -#else -static constexpr bool kUseBakerReadBarrier = false; -#endif - -#ifdef USE_BROOKS_READ_BARRIER -static constexpr bool kUseBrooksReadBarrier = true; -#else -static constexpr bool kUseBrooksReadBarrier = false; -#endif - -#ifdef USE_TABLE_LOOKUP_READ_BARRIER -static constexpr bool kUseTableLookupReadBarrier = true; -#else -static constexpr bool kUseTableLookupReadBarrier = false; -#endif - -static constexpr bool kUseBakerOrBrooksReadBarrier = kUseBakerReadBarrier || kUseBrooksReadBarrier; -static constexpr bool kUseReadBarrier = - kUseBakerReadBarrier || kUseBrooksReadBarrier || kUseTableLookupReadBarrier; - -// Debugging flag that forces the generation of read barriers, but -// does not trigger the use of the concurrent copying GC. -// -// TODO: Remove this flag when the read barriers compiler -// instrumentation is completed. -static constexpr bool kForceReadBarrier = false; -// TODO: Likewise, remove this flag when kForceReadBarrier is removed -// and replace it with kUseReadBarrier. -static constexpr bool kEmitCompilerReadBarrier = kForceReadBarrier || kUseReadBarrier; - -// If true, references within the heap are poisoned (negated). -#ifdef USE_HEAP_POISONING -static constexpr bool kPoisonHeapReferences = true; -#else -static constexpr bool kPoisonHeapReferences = false; -#endif - // If true, enable the tlab allocator by default. #ifdef ART_USE_TLAB static constexpr bool kUseTlab = true; @@ -170,8 +127,6 @@ static constexpr TraceClockSource kDefaultTraceClockSource = TraceClockSource::k static constexpr bool kDefaultMustRelocate = true; -static constexpr bool kArm32QuickCodeUseSoftFloat = false; - #ifdef ART_ENABLE_VDEX static constexpr bool kIsVdexEnabled = true; #else diff --git a/runtime/heap_poisoning.h b/runtime/heap_poisoning.h new file mode 100644 index 0000000000..5e6b635135 --- /dev/null +++ b/runtime/heap_poisoning.h @@ -0,0 +1,41 @@ +/* + * 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_HEAP_POISONING_H_ +#define ART_RUNTIME_HEAP_POISONING_H_ + +// This is a C and C++ header used for both assembly code and mainline code. + +#ifdef ART_HEAP_POISONING +#define USE_HEAP_POISONING +#endif + +#ifdef __cplusplus + +namespace art { + +// If true, references within the heap are poisoned (negated). +#ifdef USE_HEAP_POISONING +static constexpr bool kPoisonHeapReferences = true; +#else +static constexpr bool kPoisonHeapReferences = false; +#endif + +} // namespace art + +#endif // __cplusplus + +#endif // ART_RUNTIME_HEAP_POISONING_H_ diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc index d7f6b83e8e..2c82cb1acd 100644 --- a/runtime/instrumentation.cc +++ b/runtime/instrumentation.cc @@ -26,6 +26,7 @@ #include "class_linker.h" #include "debugger.h" #include "dex_file-inl.h" +#include "dex_file_types.h" #include "dex_instruction-inl.h" #include "entrypoints/quick/quick_alloc_entrypoints.h" #include "entrypoints/quick/quick_entrypoints.h" @@ -245,7 +246,7 @@ static void InstrumentationInstallStack(Thread* thread, void* arg) // pushed when executing the instrumented interpreter bridge. So method // enter event must have been reported. However we need to push a DEX pc // into the dex_pcs_ list to match size of instrumentation stack. - uint32_t dex_pc = DexFile::kDexNoIndex; + uint32_t dex_pc = dex::kDexNoIndex; dex_pcs_.push_back(dex_pc); last_return_pc_ = frame.return_pc_; ++instrumentation_stack_depth_; @@ -290,7 +291,7 @@ static void InstrumentationInstallStack(Thread* thread, void* arg) instrumentation_stack_->insert(it, instrumentation_frame); SetReturnPc(instrumentation_exit_pc_); } - uint32_t dex_pc = DexFile::kDexNoIndex; + uint32_t dex_pc = dex::kDexNoIndex; if (last_return_pc_ != 0 && GetCurrentOatQuickMethodHeader() != nullptr) { dex_pc = GetCurrentOatQuickMethodHeader()->ToDexPc(m, last_return_pc_); @@ -1334,7 +1335,7 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, } // TODO: improve the dex pc information here, requires knowledge of current PC as opposed to // return_pc. - uint32_t dex_pc = DexFile::kDexNoIndex; + uint32_t dex_pc = dex::kDexNoIndex; mirror::Object* this_object = instrumentation_frame.this_object_; if (!method->IsRuntimeMethod() && !instrumentation_frame.interpreter_entry_) { MethodExitEvent(self, this_object, instrumentation_frame.method_, dex_pc, return_value); @@ -1403,7 +1404,7 @@ uintptr_t Instrumentation::PopMethodForUnwind(Thread* self, bool is_deoptimizati // Notify listeners of method unwind. // TODO: improve the dex pc information here, requires knowledge of current PC as opposed to // return_pc. - uint32_t dex_pc = DexFile::kDexNoIndex; + uint32_t dex_pc = dex::kDexNoIndex; if (!method->IsRuntimeMethod()) { MethodUnwindEvent(self, instrumentation_frame.this_object_, method, dex_pc); } diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h index 3c5bfca316..5bac931875 100644 --- a/runtime/instrumentation.h +++ b/runtime/instrumentation.h @@ -155,9 +155,7 @@ struct InstrumentationListener { // shadow-frames by deoptimizing stacks. virtual void WatchedFramePop(Thread* thread ATTRIBUTE_UNUSED, const ShadowFrame& frame ATTRIBUTE_UNUSED) - REQUIRES_SHARED(Locks::mutator_lock_) { - return; - } + REQUIRES_SHARED(Locks::mutator_lock_) = 0; }; // Instrumentation is a catch-all for when extra information is required from the runtime. The diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc index a1f212305f..68a75b0196 100644 --- a/runtime/interpreter/interpreter.cc +++ b/runtime/interpreter/interpreter.cc @@ -19,6 +19,7 @@ #include <limits> #include "common_throws.h" +#include "dex_file_types.h" #include "interpreter_common.h" #include "interpreter_mterp_impl.h" #include "interpreter_switch_impl.h" @@ -318,7 +319,7 @@ static inline JValue Execute( // Mterp didn't like that instruction. Single-step it with the reference interpreter. result_register = ExecuteSwitchImpl<false, false>(self, code_item, shadow_frame, result_register, true); - if (shadow_frame.GetDexPC() == DexFile::kDexNoIndex) { + if (shadow_frame.GetDexPC() == dex::kDexNoIndex) { // Single-stepped a return or an exception not handled locally. Return to caller. return result_register; } @@ -501,7 +502,7 @@ void EnterInterpreterFromDeoptimize(Thread* self, const instrumentation::Instrumentation* const instrumentation = first ? nullptr : Runtime::Current()->GetInstrumentation(); new_dex_pc = MoveToExceptionHandler( - self, *shadow_frame, instrumentation) ? shadow_frame->GetDexPC() : DexFile::kDexNoIndex; + self, *shadow_frame, instrumentation) ? shadow_frame->GetDexPC() : dex::kDexNoIndex; } else if (!from_code) { // Deoptimization is not called from code directly. const Instruction* instr = Instruction::At(&code_item->insns_[dex_pc]); @@ -558,7 +559,7 @@ void EnterInterpreterFromDeoptimize(Thread* self, DCHECK(first); DCHECK_EQ(new_dex_pc, dex_pc); } - if (new_dex_pc != DexFile::kDexNoIndex) { + if (new_dex_pc != dex::kDexNoIndex) { shadow_frame->SetDexPC(new_dex_pc); value = Execute(self, code_item, *shadow_frame, value); } diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index 0028b21f94..c3450138eb 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -20,6 +20,7 @@ #include "base/enums.h" #include "debugger.h" +#include "dex_file_types.h" #include "entrypoints/runtime_asm_entrypoints.h" #include "jit/jit.h" #include "jvalue.h" @@ -438,7 +439,7 @@ bool MoveToExceptionHandler(Thread* self, bool clear_exception = false; uint32_t found_dex_pc = shadow_frame.GetMethod()->FindCatchBlock( hs.NewHandle(exception->GetClass()), shadow_frame.GetDexPC(), &clear_exception); - if (found_dex_pc == DexFile::kDexNoIndex) { + if (found_dex_pc == dex::kDexNoIndex) { if (instrumentation != nullptr) { if (shadow_frame.NeedsNotifyPop()) { instrumentation->WatchedFramePopped(self, shadow_frame); diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h index 3ccab853c3..50bd7e73cd 100644 --- a/runtime/interpreter/interpreter_common.h +++ b/runtime/interpreter/interpreter_common.h @@ -65,9 +65,16 @@ template <bool kMonitorCounting> static inline void DoMonitorEnter(Thread* self, ShadowFrame* frame, ObjPtr<mirror::Object> ref) NO_THREAD_SAFETY_ANALYSIS REQUIRES(!Roles::uninterruptible_) { + DCHECK(!ref.IsNull()); StackHandleScope<1> hs(self); Handle<mirror::Object> h_ref(hs.NewHandle(ref)); h_ref->MonitorEnter(self); + DCHECK(self->HoldsLock(h_ref.Get())); + if (UNLIKELY(self->IsExceptionPending())) { + bool unlocked = h_ref->MonitorExit(self); + DCHECK(unlocked); + return; + } if (kMonitorCounting && frame->GetMethod()->MustCountLocks()) { frame->GetLockCountData().AddMonitor(self, h_ref.Get()); } diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc index 69e091b42d..850419bd9d 100644 --- a/runtime/interpreter/interpreter_switch_impl.cc +++ b/runtime/interpreter/interpreter_switch_impl.cc @@ -17,6 +17,7 @@ #include "interpreter_switch_impl.h" #include "base/enums.h" +#include "dex_file_types.h" #include "experimental_flags.h" #include "interpreter_common.h" #include "jit/jit.h" @@ -35,7 +36,7 @@ namespace interpreter { DoMonitorCheckOnExit<do_assignability_check>(self, &shadow_frame); \ if (interpret_one_instruction) { \ /* Signal mterp to return to caller */ \ - shadow_frame.SetDexPC(DexFile::kDexNoIndex); \ + shadow_frame.SetDexPC(dex::kDexNoIndex); \ } \ return JValue(); /* Handled in caller. */ \ } else { \ @@ -88,7 +89,7 @@ namespace interpreter { if (jit::Jit::MaybeDoOnStackReplacement(self, method, dex_pc, offset, &result)) { \ if (interpret_one_instruction) { \ /* OSR has completed execution of the method. Signal mterp to return to caller */ \ - shadow_frame.SetDexPC(DexFile::kDexNoIndex); \ + shadow_frame.SetDexPC(dex::kDexNoIndex); \ } \ return result; \ } \ @@ -303,7 +304,7 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, } if (interpret_one_instruction) { /* Signal mterp to return to caller */ - shadow_frame.SetDexPC(DexFile::kDexNoIndex); + shadow_frame.SetDexPC(dex::kDexNoIndex); } return result; } @@ -325,7 +326,7 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, } if (interpret_one_instruction) { /* Signal mterp to return to caller */ - shadow_frame.SetDexPC(DexFile::kDexNoIndex); + shadow_frame.SetDexPC(dex::kDexNoIndex); } return result; } @@ -348,7 +349,7 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, } if (interpret_one_instruction) { /* Signal mterp to return to caller */ - shadow_frame.SetDexPC(DexFile::kDexNoIndex); + shadow_frame.SetDexPC(dex::kDexNoIndex); } return result; } @@ -370,7 +371,7 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, } if (interpret_one_instruction) { /* Signal mterp to return to caller */ - shadow_frame.SetDexPC(DexFile::kDexNoIndex); + shadow_frame.SetDexPC(dex::kDexNoIndex); } return result; } @@ -414,7 +415,7 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, result.SetL(shadow_frame.GetVRegReference(ref_idx)); if (interpret_one_instruction) { /* Signal mterp to return to caller */ - shadow_frame.SetDexPC(DexFile::kDexNoIndex); + shadow_frame.SetDexPC(dex::kDexNoIndex); } return result; } diff --git a/runtime/interpreter/mterp/arm/entry.S b/runtime/interpreter/mterp/arm/entry.S index e53c0544c6..ce14b548d0 100644 --- a/runtime/interpreter/mterp/arm/entry.S +++ b/runtime/interpreter/mterp/arm/entry.S @@ -67,6 +67,7 @@ ENTRY ExecuteMterpImpl /* Set up for backwards branches & osr profiling */ ldr r0, [rFP, #OFF_FP_METHOD] add r1, rFP, #OFF_FP_SHADOWFRAME + mov r2, rSELF bl MterpSetUpHotnessCountdown mov rPROFILE, r0 @ Starting hotness countdown to rPROFILE diff --git a/runtime/interpreter/mterp/arm64/entry.S b/runtime/interpreter/mterp/arm64/entry.S index 441c1a1e88..73c5a88e5f 100644 --- a/runtime/interpreter/mterp/arm64/entry.S +++ b/runtime/interpreter/mterp/arm64/entry.S @@ -60,6 +60,7 @@ ExecuteMterpImpl: /* Set up for backwards branches & osr profiling */ ldr x0, [xFP, #OFF_FP_METHOD] add x1, xFP, #OFF_FP_SHADOWFRAME + mov x2, xSELF bl MterpSetUpHotnessCountdown mov wPROFILE, w0 // Starting hotness countdown to xPROFILE diff --git a/runtime/interpreter/mterp/mips/entry.S b/runtime/interpreter/mterp/mips/entry.S index c806a679b9..f617a4d08b 100644 --- a/runtime/interpreter/mterp/mips/entry.S +++ b/runtime/interpreter/mterp/mips/entry.S @@ -63,7 +63,8 @@ ExecuteMterpImpl: /* Set up for backwards branches & osr profiling */ lw a0, OFF_FP_METHOD(rFP) addu a1, rFP, OFF_FP_SHADOWFRAME - JAL(MterpSetUpHotnessCountdown) # (method, shadow_frame) + move a2, rSELF + JAL(MterpSetUpHotnessCountdown) # (method, shadow_frame, self) move rPROFILE, v0 # Starting hotness countdown to rPROFILE /* start executing the instruction at rPC */ diff --git a/runtime/interpreter/mterp/mips64/entry.S b/runtime/interpreter/mterp/mips64/entry.S index cc48d45298..5536966be2 100644 --- a/runtime/interpreter/mterp/mips64/entry.S +++ b/runtime/interpreter/mterp/mips64/entry.S @@ -82,6 +82,7 @@ ExecuteMterpImpl: /* Set up for backwards branches & osr profiling */ ld a0, OFF_FP_METHOD(rFP) daddu a1, rFP, OFF_FP_SHADOWFRAME + move a2, rSELF jal MterpSetUpHotnessCountdown move rPROFILE, v0 # Starting hotness countdown to rPROFILE diff --git a/runtime/interpreter/mterp/mterp.cc b/runtime/interpreter/mterp/mterp.cc index b8a7a2ad3f..6c2475391b 100644 --- a/runtime/interpreter/mterp/mterp.cc +++ b/runtime/interpreter/mterp/mterp.cc @@ -888,7 +888,9 @@ extern "C" mirror::Object* artIGetObjectFromMterp(mirror::Object* obj, * to the full instrumentation via MterpAddHotnessBatch. Called once on entry to the method, * and regenerated following batch updates. */ -extern "C" ssize_t MterpSetUpHotnessCountdown(ArtMethod* method, ShadowFrame* shadow_frame) +extern "C" ssize_t MterpSetUpHotnessCountdown(ArtMethod* method, + ShadowFrame* shadow_frame, + Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { uint16_t hotness_count = method->GetCounter(); int32_t countdown_value = jit::kJitHotnessDisabled; @@ -906,7 +908,7 @@ extern "C" ssize_t MterpSetUpHotnessCountdown(ArtMethod* method, ShadowFrame* sh } else { countdown_value = jit::kJitCheckForOSR; } - if (jit::Jit::ShouldUsePriorityThreadWeight()) { + if (jit::Jit::ShouldUsePriorityThreadWeight(self)) { int32_t priority_thread_weight = jit->PriorityThreadWeight(); countdown_value = std::min(countdown_value, countdown_value / priority_thread_weight); } @@ -935,7 +937,7 @@ extern "C" ssize_t MterpAddHotnessBatch(ArtMethod* method, int16_t count = shadow_frame->GetCachedHotnessCountdown() - shadow_frame->GetHotnessCountdown(); jit->AddSamples(self, method, count, /*with_backedges*/ true); } - return MterpSetUpHotnessCountdown(method, shadow_frame); + return MterpSetUpHotnessCountdown(method, shadow_frame, self); } extern "C" size_t MterpMaybeDoOnStackReplacement(Thread* self, diff --git a/runtime/interpreter/mterp/out/mterp_arm.S b/runtime/interpreter/mterp/out/mterp_arm.S index e2b693f269..d6a27b8fc9 100644 --- a/runtime/interpreter/mterp/out/mterp_arm.S +++ b/runtime/interpreter/mterp/out/mterp_arm.S @@ -386,6 +386,7 @@ ENTRY ExecuteMterpImpl /* Set up for backwards branches & osr profiling */ ldr r0, [rFP, #OFF_FP_METHOD] add r1, rFP, #OFF_FP_SHADOWFRAME + mov r2, rSELF bl MterpSetUpHotnessCountdown mov rPROFILE, r0 @ Starting hotness countdown to rPROFILE diff --git a/runtime/interpreter/mterp/out/mterp_arm64.S b/runtime/interpreter/mterp/out/mterp_arm64.S index ef5a4daa51..3d05996521 100644 --- a/runtime/interpreter/mterp/out/mterp_arm64.S +++ b/runtime/interpreter/mterp/out/mterp_arm64.S @@ -401,6 +401,7 @@ ExecuteMterpImpl: /* Set up for backwards branches & osr profiling */ ldr x0, [xFP, #OFF_FP_METHOD] add x1, xFP, #OFF_FP_SHADOWFRAME + mov x2, xSELF bl MterpSetUpHotnessCountdown mov wPROFILE, w0 // Starting hotness countdown to xPROFILE diff --git a/runtime/interpreter/mterp/out/mterp_mips.S b/runtime/interpreter/mterp/out/mterp_mips.S index 636289798c..144c8e5165 100644 --- a/runtime/interpreter/mterp/out/mterp_mips.S +++ b/runtime/interpreter/mterp/out/mterp_mips.S @@ -797,7 +797,8 @@ ExecuteMterpImpl: /* Set up for backwards branches & osr profiling */ lw a0, OFF_FP_METHOD(rFP) addu a1, rFP, OFF_FP_SHADOWFRAME - JAL(MterpSetUpHotnessCountdown) # (method, shadow_frame) + move a2, rSELF + JAL(MterpSetUpHotnessCountdown) # (method, shadow_frame, self) move rPROFILE, v0 # Starting hotness countdown to rPROFILE /* start executing the instruction at rPC */ diff --git a/runtime/interpreter/mterp/out/mterp_mips64.S b/runtime/interpreter/mterp/out/mterp_mips64.S index bc0d90c7cb..28f1887539 100644 --- a/runtime/interpreter/mterp/out/mterp_mips64.S +++ b/runtime/interpreter/mterp/out/mterp_mips64.S @@ -383,6 +383,7 @@ ExecuteMterpImpl: /* Set up for backwards branches & osr profiling */ ld a0, OFF_FP_METHOD(rFP) daddu a1, rFP, OFF_FP_SHADOWFRAME + move a2, rSELF jal MterpSetUpHotnessCountdown move rPROFILE, v0 # Starting hotness countdown to rPROFILE @@ -3764,7 +3765,6 @@ artMterpAsmInstructionStart = .L_op_nop GOTO_OPCODE v0 # jump to next instruction - /* ------------------------------ */ .balign 128 .L_op_float_to_double: /* 0x89 */ diff --git a/runtime/interpreter/mterp/out/mterp_x86.S b/runtime/interpreter/mterp/out/mterp_x86.S index 21d9671f8b..169501d563 100644 --- a/runtime/interpreter/mterp/out/mterp_x86.S +++ b/runtime/interpreter/mterp/out/mterp_x86.S @@ -387,6 +387,8 @@ SYMBOL(ExecuteMterpImpl): movl %eax, OUT_ARG0(%esp) leal OFF_FP_SHADOWFRAME(rFP), %ecx movl %ecx, OUT_ARG1(%esp) + movl rSELF, %eax + movl %eax, OUT_ARG2(%esp) call SYMBOL(MterpSetUpHotnessCountdown) /* Starting ibase */ diff --git a/runtime/interpreter/mterp/out/mterp_x86_64.S b/runtime/interpreter/mterp/out/mterp_x86_64.S index b5a5ae5963..b643072dde 100644 --- a/runtime/interpreter/mterp/out/mterp_x86_64.S +++ b/runtime/interpreter/mterp/out/mterp_x86_64.S @@ -198,9 +198,12 @@ unspecified registers or condition codes. * restore it in such cases also. * */ +.macro REFRESH_IBASE_REG self_reg + movq THREAD_CURRENT_IBASE_OFFSET(\self_reg), rIBASE +.endm .macro REFRESH_IBASE movq rSELF, rIBASE - movq THREAD_CURRENT_IBASE_OFFSET(rIBASE), rIBASE + REFRESH_IBASE_REG rIBASE .endm /* @@ -364,9 +367,10 @@ SYMBOL(ExecuteMterpImpl): /* Starting ibase */ movq IN_ARG0, rSELF - REFRESH_IBASE + REFRESH_IBASE_REG IN_ARG0 /* Set up for backwards branches & osr profiling */ + movq IN_ARG0, OUT_ARG2 /* Set up OUT_ARG2 before clobbering IN_ARG0 */ movq OFF_FP_METHOD(rFP), OUT_ARG0 leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1 call SYMBOL(MterpSetUpHotnessCountdown) @@ -11908,7 +11912,7 @@ MterpCommonTakenBranch: .L_resume_backward_branch: movq rSELF, %rax testl $(THREAD_SUSPEND_OR_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(%rax) - REFRESH_IBASE + REFRESH_IBASE_REG %rax leaq (rPC, rINSTq, 2), rPC FETCH_INST jnz .L_suspend_request_pending diff --git a/runtime/interpreter/mterp/x86/entry.S b/runtime/interpreter/mterp/x86/entry.S index 384dd9a5bb..34adf53b99 100644 --- a/runtime/interpreter/mterp/x86/entry.S +++ b/runtime/interpreter/mterp/x86/entry.S @@ -69,6 +69,8 @@ SYMBOL(ExecuteMterpImpl): movl %eax, OUT_ARG0(%esp) leal OFF_FP_SHADOWFRAME(rFP), %ecx movl %ecx, OUT_ARG1(%esp) + movl rSELF, %eax + movl %eax, OUT_ARG2(%esp) call SYMBOL(MterpSetUpHotnessCountdown) /* Starting ibase */ diff --git a/runtime/interpreter/mterp/x86_64/entry.S b/runtime/interpreter/mterp/x86_64/entry.S index d992956769..0f969eb79f 100644 --- a/runtime/interpreter/mterp/x86_64/entry.S +++ b/runtime/interpreter/mterp/x86_64/entry.S @@ -63,9 +63,10 @@ SYMBOL(ExecuteMterpImpl): /* Starting ibase */ movq IN_ARG0, rSELF - REFRESH_IBASE + REFRESH_IBASE_REG IN_ARG0 /* Set up for backwards branches & osr profiling */ + movq IN_ARG0, OUT_ARG2 /* Set up OUT_ARG2 before clobbering IN_ARG0 */ movq OFF_FP_METHOD(rFP), OUT_ARG0 leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1 call SYMBOL(MterpSetUpHotnessCountdown) diff --git a/runtime/interpreter/mterp/x86_64/footer.S b/runtime/interpreter/mterp/x86_64/footer.S index ed5e5eabfa..ac6cd19f4e 100644 --- a/runtime/interpreter/mterp/x86_64/footer.S +++ b/runtime/interpreter/mterp/x86_64/footer.S @@ -152,7 +152,7 @@ MterpCommonTakenBranch: .L_resume_backward_branch: movq rSELF, %rax testl $$(THREAD_SUSPEND_OR_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(%rax) - REFRESH_IBASE + REFRESH_IBASE_REG %rax leaq (rPC, rINSTq, 2), rPC FETCH_INST jnz .L_suspend_request_pending diff --git a/runtime/interpreter/mterp/x86_64/header.S b/runtime/interpreter/mterp/x86_64/header.S index 7699fc4dd7..f229e84eb8 100644 --- a/runtime/interpreter/mterp/x86_64/header.S +++ b/runtime/interpreter/mterp/x86_64/header.S @@ -191,9 +191,12 @@ unspecified registers or condition codes. * restore it in such cases also. * */ +.macro REFRESH_IBASE_REG self_reg + movq THREAD_CURRENT_IBASE_OFFSET(\self_reg), rIBASE +.endm .macro REFRESH_IBASE movq rSELF, rIBASE - movq THREAD_CURRENT_IBASE_OFFSET(rIBASE), rIBASE + REFRESH_IBASE_REG rIBASE .endm /* diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc index 7abf52ea60..8c27bfe85e 100644 --- a/runtime/jit/jit.cc +++ b/runtime/jit/jit.cc @@ -144,9 +144,8 @@ JitOptions* JitOptions::CreateFromRuntimeArguments(const RuntimeArgumentMap& opt return jit_options; } -bool Jit::ShouldUsePriorityThreadWeight() { - return Runtime::Current()->InJankPerceptibleProcessState() - && Thread::Current()->IsJitSensitiveThread(); +bool Jit::ShouldUsePriorityThreadWeight(Thread* self) { + return self->IsJitSensitiveThread() && Runtime::Current()->InJankPerceptibleProcessState(); } void Jit::DumpInfo(std::ostream& os) { @@ -653,7 +652,7 @@ void Jit::AddSamples(Thread* self, ArtMethod* method, uint16_t count, bool with_ DCHECK_LE(priority_thread_weight_, hot_method_threshold_); int32_t starting_count = method->GetCounter(); - if (Jit::ShouldUsePriorityThreadWeight()) { + if (Jit::ShouldUsePriorityThreadWeight(self)) { count *= priority_thread_weight_; } int32_t new_count = starting_count + count; // int32 here to avoid wrap-around; diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h index 51e49ec489..791c3386cf 100644 --- a/runtime/jit/jit.h +++ b/runtime/jit/jit.h @@ -152,7 +152,7 @@ class Jit { bool CanInvokeCompiledCode(ArtMethod* method); // Return whether the runtime should use a priority thread weight when sampling. - static bool ShouldUsePriorityThreadWeight(); + static bool ShouldUsePriorityThreadWeight(Thread* self); // If an OSR compiled version is available for `method`, // and `dex_pc + dex_pc_offset` is an entry point of that compiled diff --git a/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc index 1f2163f30d..57fc4976f7 100644 --- a/runtime/jit/profile_compilation_info.cc +++ b/runtime/jit/profile_compilation_info.cc @@ -143,7 +143,7 @@ bool ProfileCompilationInfo::AddMethodIndex(MethodHotness::Flag flags, const Met if (data == nullptr) { return false; } - data->AddMethod(flags, ref.dex_method_index); + data->AddMethod(flags, ref.index); return true; } @@ -683,7 +683,7 @@ bool ProfileCompilationInfo::AddMethod(const ProfileMethodInfo& pmi) { if (data == nullptr) { // checksum mismatch return false; } - InlineCacheMap* inline_cache = data->FindOrAddMethod(pmi.ref.dex_method_index); + InlineCacheMap* inline_cache = data->FindOrAddMethod(pmi.ref.index); for (const ProfileMethodInfo::ProfileInlineCache& cache : pmi.inline_caches) { if (cache.is_missing_types) { @@ -700,7 +700,7 @@ bool ProfileCompilationInfo::AddMethod(const ProfileMethodInfo& pmi) { // Don't bother adding classes if we are missing types. break; } - dex_pc_data->AddClass(class_dex_data->profile_index, class_ref.type_index); + dex_pc_data->AddClass(class_dex_data->profile_index, class_ref.TypeIndex()); } } return true; @@ -1333,7 +1333,7 @@ ProfileCompilationInfo::MethodHotness ProfileCompilationInfo::GetMethodHotness( const MethodReference& method_ref) const { const DexFileData* dex_data = FindDexData(method_ref.dex_file); return dex_data != nullptr - ? dex_data->GetHotnessInfo(method_ref.dex_method_index) + ? dex_data->GetHotnessInfo(method_ref.index) : MethodHotness(); } @@ -1342,8 +1342,7 @@ bool ProfileCompilationInfo::AddMethodHotness(const MethodReference& method_ref, DexFileData* dex_data = GetOrAddDexFileData(method_ref.dex_file); if (dex_data != nullptr) { // TODO: Add inline caches. - dex_data->AddMethod(static_cast<MethodHotness::Flag>(hotness.GetFlags()), - method_ref.dex_method_index); + dex_data->AddMethod(static_cast<MethodHotness::Flag>(hotness.GetFlags()), method_ref.index); return true; } return false; diff --git a/runtime/jit/profile_compilation_info.h b/runtime/jit/profile_compilation_info.h index 7fd7a2d049..5c7448fe63 100644 --- a/runtime/jit/profile_compilation_info.h +++ b/runtime/jit/profile_compilation_info.h @@ -254,6 +254,16 @@ class ProfileCompilationInfo { data->class_set.insert(index_begin, index_end); return true; } + // Add a single type id for a dex file. + bool AddClassForDex(const TypeReference& ref) { + DexFileData* data = GetOrAddDexFileData(ref.dex_file); + if (data == nullptr) { + return false; + } + data->class_set.insert(ref.TypeIndex()); + return true; + } + // Add a method index to the profile (without inline caches). The method flags determine if it is // hot, startup, or post startup, or a combination of the previous. diff --git a/runtime/jit/profile_compilation_info_test.cc b/runtime/jit/profile_compilation_info_test.cc index 40d303fc13..2cb8294dbd 100644 --- a/runtime/jit/profile_compilation_info_test.cc +++ b/runtime/jit/profile_compilation_info_test.cc @@ -195,7 +195,7 @@ class ProfileCompilationInfoTest : public CommonRuntimeTest { for (const auto& class_ref : inline_cache.classes) { uint8_t dex_profile_index = dex_map.FindOrAdd(const_cast<DexFile*>(class_ref.dex_file), static_cast<uint8_t>(dex_map.size()))->second; - dex_pc_data.AddClass(dex_profile_index, class_ref.type_index); + dex_pc_data.AddClass(dex_profile_index, class_ref.TypeIndex()); if (dex_profile_index >= offline_pmi.dex_references.size()) { // This is a new dex. const std::string& dex_key = ProfileCompilationInfo::GetProfileDexFileKey( diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc index f96792dcb7..d74cec325a 100644 --- a/runtime/jni_internal.cc +++ b/runtime/jni_internal.cc @@ -2398,10 +2398,12 @@ class JNI { ScopedObjectAccess soa(env); ObjPtr<mirror::Object> o = soa.Decode<mirror::Object>(java_object); o = o->MonitorEnter(soa.Self()); + if (soa.Self()->HoldsLock(o)) { + soa.Env()->monitors.Add(o); + } if (soa.Self()->IsExceptionPending()) { return JNI_ERR; } - soa.Env()->monitors.Add(o); return JNI_OK; } @@ -2409,11 +2411,14 @@ class JNI { CHECK_NON_NULL_ARGUMENT_RETURN(java_object, JNI_ERR); ScopedObjectAccess soa(env); ObjPtr<mirror::Object> o = soa.Decode<mirror::Object>(java_object); + bool remove_mon = soa.Self()->HoldsLock(o); o->MonitorExit(soa.Self()); + if (remove_mon) { + soa.Env()->monitors.Remove(o); + } if (soa.Self()->IsExceptionPending()) { return JNI_ERR; } - soa.Env()->monitors.Remove(o); return JNI_OK; } diff --git a/runtime/method_reference.h b/runtime/method_reference.h index 3948ed5bb9..31f3b8e84e 100644 --- a/runtime/method_reference.h +++ b/runtime/method_reference.h @@ -20,27 +20,19 @@ #include <stdint.h> #include <string> #include "dex_file.h" +#include "dex_file_reference.h" namespace art { // A method is uniquely located by its DexFile and the method_ids_ table index into that DexFile -struct MethodReference { - MethodReference(const DexFile* file, uint32_t index) : dex_file(file), dex_method_index(index) { +class MethodReference : public DexFileReference { + public: + MethodReference(const DexFile* file, uint32_t index) : DexFileReference(file, index) {} + std::string PrettyMethod(bool with_signature = true) const { + return dex_file->PrettyMethod(index, with_signature); } - std::string PrettyMethod(bool with_signature = true) { - return dex_file->PrettyMethod(dex_method_index, with_signature); - } - const DexFile* dex_file; - uint32_t dex_method_index; -}; - -struct MethodReferenceComparator { - bool operator()(MethodReference mr1, MethodReference mr2) const { - if (mr1.dex_file == mr2.dex_file) { - return mr1.dex_method_index < mr2.dex_method_index; - } else { - return mr1.dex_file < mr2.dex_file; - } + const DexFile::MethodId& GetMethodId() const { + return dex_file->GetMethodId(index); } }; @@ -48,8 +40,8 @@ struct MethodReferenceComparator { struct MethodReferenceValueComparator { bool operator()(MethodReference mr1, MethodReference mr2) const { if (mr1.dex_file == mr2.dex_file) { - DCHECK_EQ(mr1.dex_method_index < mr2.dex_method_index, SlowCompare(mr1, mr2)); - return mr1.dex_method_index < mr2.dex_method_index; + DCHECK_EQ(mr1.index < mr2.index, SlowCompare(mr1, mr2)); + return mr1.index < mr2.index; } else { return SlowCompare(mr1, mr2); } @@ -58,8 +50,8 @@ struct MethodReferenceValueComparator { bool SlowCompare(MethodReference mr1, MethodReference mr2) const { // The order is the same as for method ids in a single dex file. // Compare the class descriptors first. - const DexFile::MethodId& mid1 = mr1.dex_file->GetMethodId(mr1.dex_method_index); - const DexFile::MethodId& mid2 = mr2.dex_file->GetMethodId(mr2.dex_method_index); + const DexFile::MethodId& mid1 = mr1.GetMethodId(); + const DexFile::MethodId& mid2 = mr2.GetMethodId(); int descriptor_diff = strcmp(mr1.dex_file->StringByTypeIdx(mid1.class_idx_), mr2.dex_file->StringByTypeIdx(mid2.class_idx_)); if (descriptor_diff != 0) { diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h index 67aeeff2e8..77a5b559e3 100644 --- a/runtime/mirror/class-inl.h +++ b/runtime/mirror/class-inl.h @@ -30,6 +30,7 @@ #include "dex_file-inl.h" #include "gc/heap-inl.h" #include "iftable.h" +#include "invoke_type.h" #include "object-inl.h" #include "object_array.h" #include "read_barrier-inl.h" diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h index 4c9b1464dc..c44b6160ad 100644 --- a/runtime/mirror/class.h +++ b/runtime/mirror/class.h @@ -27,7 +27,6 @@ #include "gc/allocator_type.h" #include "gc_root.h" #include "imtable.h" -#include "invoke_type.h" #include "modifiers.h" #include "object.h" #include "object_array.h" @@ -43,6 +42,7 @@ class ArtField; class ArtMethod; struct ClassOffsets; template<class T> class Handle; +enum InvokeType : uint32_t; template<typename T> class LengthPrefixedArray; template<typename T> class ArraySlice; class Signature; diff --git a/runtime/mirror/object.h b/runtime/mirror/object.h index aedcd66082..25b86a527d 100644 --- a/runtime/mirror/object.h +++ b/runtime/mirror/object.h @@ -24,6 +24,8 @@ #include "obj_ptr.h" #include "object_reference.h" #include "offsets.h" +#include "read_barrier_config.h" +#include "read_barrier_option.h" #include "verify_object.h" namespace art { diff --git a/runtime/monitor.cc b/runtime/monitor.cc index 80e6ad3d47..d85479a40c 100644 --- a/runtime/monitor.cc +++ b/runtime/monitor.cc @@ -27,6 +27,7 @@ #include "base/time_utils.h" #include "class_linker.h" #include "dex_file-inl.h" +#include "dex_file_types.h" #include "dex_instruction-inl.h" #include "lock_word-inl.h" #include "mirror/class-inl.h" @@ -346,11 +347,31 @@ bool Monitor::TryLock(Thread* self) { return TryLockLocked(self); } +// Asserts that a mutex isn't held when the class comes into and out of scope. +class ScopedAssertNotHeld { + public: + ScopedAssertNotHeld(Thread* self, Mutex& mu) : self_(self), mu_(mu) { + mu_.AssertNotHeld(self_); + } + + ~ScopedAssertNotHeld() { + mu_.AssertNotHeld(self_); + } + + private: + Thread* const self_; + Mutex& mu_; + DISALLOW_COPY_AND_ASSIGN(ScopedAssertNotHeld); +}; + +template <LockReason reason> void Monitor::Lock(Thread* self) { - MutexLock mu(self, monitor_lock_); + ScopedAssertNotHeld sanh(self, monitor_lock_); + bool called_monitors_callback = false; + monitor_lock_.Lock(self); while (true) { if (TryLockLocked(self)) { - return; + break; } // Contended. const bool log_contention = (lock_profiling_threshold_ != 0); @@ -389,6 +410,12 @@ void Monitor::Lock(Thread* self) { } monitor_lock_.Unlock(self); // Let go of locks in order. + // Call the contended locking cb once and only once. Also only call it if we are locking for + // the first time, not during a Wait wakeup. + if (reason == LockReason::kForLock && !called_monitors_callback) { + called_monitors_callback = true; + Runtime::Current()->GetRuntimeCallbacks()->MonitorContendedLocking(this); + } self->SetMonitorEnterObject(GetObject()); { ScopedThreadSuspension tsc(self, kBlocked); // Change to blocked and give up mutator_lock_. @@ -492,10 +519,10 @@ void Monitor::Lock(Thread* self) { << PrettyDuration(MsToNs(wait_ms)); } LogContentionEvent(self, - wait_ms, - sample_percent, - owners_method, - owners_dex_pc); + wait_ms, + sample_percent, + owners_method, + owners_dex_pc); } } } @@ -508,8 +535,18 @@ void Monitor::Lock(Thread* self) { monitor_lock_.Lock(self); // Reacquire locks in order. --num_waiters_; } + monitor_lock_.Unlock(self); + // We need to pair this with a single contended locking call. NB we match the RI behavior and call + // this even if MonitorEnter failed. + if (called_monitors_callback) { + CHECK(reason == LockReason::kForLock); + Runtime::Current()->GetRuntimeCallbacks()->MonitorContendedLocked(this); + } } +template void Monitor::Lock<LockReason::kForLock>(Thread* self); +template void Monitor::Lock<LockReason::kForWait>(Thread* self); + static void ThrowIllegalMonitorStateExceptionF(const char* fmt, ...) __attribute__((format(printf, 1, 2))); @@ -690,6 +727,7 @@ void Monitor::Wait(Thread* self, int64_t ms, int32_t ns, AtraceMonitorLock(self, GetObject(), true /* is_wait */); bool was_interrupted = false; + bool timed_out = false; { // Update thread state. If the GC wakes up, it'll ignore us, knowing // that we won't touch any references in this state, and we'll check @@ -718,7 +756,7 @@ void Monitor::Wait(Thread* self, int64_t ms, int32_t ns, self->GetWaitConditionVariable()->Wait(self); } else { DCHECK(why == kTimedWaiting || why == kSleeping) << why; - self->GetWaitConditionVariable()->TimedWait(self, ms, ns); + timed_out = self->GetWaitConditionVariable()->TimedWait(self, ms, ns); } was_interrupted = self->IsInterrupted(); } @@ -751,8 +789,11 @@ void Monitor::Wait(Thread* self, int64_t ms, int32_t ns, AtraceMonitorUnlock(); // End Wait(). + // We just slept, tell the runtime callbacks about this. + Runtime::Current()->GetRuntimeCallbacks()->MonitorWaitFinished(this, timed_out); + // Re-acquire the monitor and lock. - Lock(self); + Lock<LockReason::kForWait>(self); monitor_lock_.Lock(self); self->GetWaitMutex()->AssertNotHeld(self); @@ -897,7 +938,7 @@ void Monitor::InflateThinLocked(Thread* self, Handle<mirror::Object> obj, LockWo bool timed_out; Thread* owner; { - ScopedThreadSuspension sts(self, kBlocked); + ScopedThreadSuspension sts(self, kWaitingForLockInflation); owner = thread_list->SuspendThreadByThreadId(owner_thread_id, SuspendReason::kInternal, &timed_out); @@ -989,10 +1030,10 @@ mirror::Object* Monitor::MonitorEnter(Thread* self, mirror::Object* obj, bool tr contention_count++; Runtime* runtime = Runtime::Current(); if (contention_count <= runtime->GetMaxSpinsBeforeThinLockInflation()) { - // TODO: Consider switching the thread state to kBlocked when we are yielding. - // Use sched_yield instead of NanoSleep since NanoSleep can wait much longer than the - // parameter you pass in. This can cause thread suspension to take excessively long - // and make long pauses. See b/16307460. + // TODO: Consider switching the thread state to kWaitingForLockInflation when we are + // yielding. Use sched_yield instead of NanoSleep since NanoSleep can wait much longer + // than the parameter you pass in. This can cause thread suspension to take excessively + // long and make long pauses. See b/16307460. // TODO: We should literally spin first, without sched_yield. Sched_yield either does // nothing (at significant expense), or guarantees that we wait at least microseconds. // If the owner is running, I would expect the median lock hold time to be hundreds @@ -1098,7 +1139,16 @@ void Monitor::Wait(Thread* self, mirror::Object *obj, int64_t ms, int32_t ns, bool interruptShouldThrow, ThreadState why) { DCHECK(self != nullptr); DCHECK(obj != nullptr); - LockWord lock_word = obj->GetLockWord(true); + StackHandleScope<1> hs(self); + Handle<mirror::Object> h_obj(hs.NewHandle(obj)); + + Runtime::Current()->GetRuntimeCallbacks()->ObjectWaitStart(h_obj, ms); + if (UNLIKELY(self->IsExceptionPending())) { + // See b/65558434 for information on handling of exceptions here. + return; + } + + LockWord lock_word = h_obj->GetLockWord(true); while (lock_word.GetState() != LockWord::kFatLocked) { switch (lock_word.GetState()) { case LockWord::kHashCode: @@ -1115,8 +1165,8 @@ void Monitor::Wait(Thread* self, mirror::Object *obj, int64_t ms, int32_t ns, } else { // We own the lock, inflate to enqueue ourself on the Monitor. May fail spuriously so // re-load. - Inflate(self, self, obj, 0); - lock_word = obj->GetLockWord(true); + Inflate(self, self, h_obj.Get(), 0); + lock_word = h_obj->GetLockWord(true); } break; } @@ -1203,8 +1253,9 @@ void Monitor::DescribeWait(std::ostream& os, const Thread* thread) { if (monitor != nullptr) { pretty_object = monitor->GetObject(); } - } else if (state == kBlocked) { - wait_message = " - waiting to lock "; + } else if (state == kBlocked || state == kWaitingForLockInflation) { + wait_message = (state == kBlocked) ? " - waiting to lock " + : " - waiting for lock inflation of "; pretty_object = thread->GetMonitorEnterObject(); if (pretty_object != nullptr) { if (kUseReadBarrier && Thread::Current()->GetIsGcMarking()) { @@ -1294,7 +1345,7 @@ void Monitor::VisitLocks(StackVisitor* stack_visitor, void (*callback)(mirror::O // find the dex pc, and instead return kDexNoIndex. Then bail out, as it indicates we have an // inconsistent stack anyways. uint32_t dex_pc = stack_visitor->GetDexPc(abort_on_failure); - if (!abort_on_failure && dex_pc == DexFile::kDexNoIndex) { + if (!abort_on_failure && dex_pc == dex::kDexNoIndex) { LOG(ERROR) << "Could not find dex_pc for " << m->PrettyMethod(); return; } diff --git a/runtime/monitor.h b/runtime/monitor.h index 09faeba9a6..d7aef34e0b 100644 --- a/runtime/monitor.h +++ b/runtime/monitor.h @@ -31,6 +31,7 @@ #include "gc_root.h" #include "lock_word.h" #include "read_barrier_option.h" +#include "runtime_callbacks.h" #include "thread_state.h" namespace art { @@ -47,6 +48,11 @@ namespace mirror { class Object; } // namespace mirror +enum class LockReason { + kForWait, + kForLock, +}; + class Monitor { public: // The default number of spins that are done before thread suspension is used to forcibly inflate @@ -205,9 +211,11 @@ class Monitor { REQUIRES(monitor_lock_) REQUIRES_SHARED(Locks::mutator_lock_); + template<LockReason reason = LockReason::kForLock> void Lock(Thread* self) REQUIRES(!monitor_lock_) REQUIRES_SHARED(Locks::mutator_lock_); + bool Unlock(Thread* thread) REQUIRES(!monitor_lock_) REQUIRES_SHARED(Locks::mutator_lock_); diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc index 07dfb65972..d40e6d94c9 100644 --- a/runtime/native/dalvik_system_DexFile.cc +++ b/runtime/native/dalvik_system_DexFile.cc @@ -493,6 +493,8 @@ static jint GetDexOptNeeded(JNIEnv* env, if (oat_file_assistant.IsInBootClassPath()) { return OatFileAssistant::kNoDexOptNeeded; } + + // TODO(calin): Extend DexFile.getDexOptNeeded to accept the class loader context. b/62269291. return oat_file_assistant.GetDexOptNeeded(filter, profile_changed, downgrade); } diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc index 2e4db7a7fa..e40a071223 100644 --- a/runtime/native/dalvik_system_ZygoteHooks.cc +++ b/runtime/native/dalvik_system_ZygoteHooks.cc @@ -31,6 +31,8 @@ #include "nativehelper/JNIHelp.h" #include "nativehelper/ScopedUtfChars.h" #include "non_debuggable_classes.h" +#include "oat_file.h" +#include "oat_file_manager.h" #include "scoped_thread_state_change-inl.h" #include "stack.h" #include "thread-current-inl.h" @@ -154,22 +156,24 @@ static void CollectNonDebuggableClasses() REQUIRES(!Locks::mutator_lock_) { } } -static void EnableDebugFeatures(uint32_t debug_flags) { - // Must match values in com.android.internal.os.Zygote. - enum { - DEBUG_ENABLE_JDWP = 1, - DEBUG_ENABLE_CHECKJNI = 1 << 1, - DEBUG_ENABLE_ASSERT = 1 << 2, - DEBUG_ENABLE_SAFEMODE = 1 << 3, - DEBUG_ENABLE_JNI_LOGGING = 1 << 4, - DEBUG_GENERATE_DEBUG_INFO = 1 << 5, - DEBUG_ALWAYS_JIT = 1 << 6, - DEBUG_NATIVE_DEBUGGABLE = 1 << 7, - DEBUG_JAVA_DEBUGGABLE = 1 << 8, - }; +// Must match values in com.android.internal.os.Zygote. +enum { + DEBUG_ENABLE_JDWP = 1, + DEBUG_ENABLE_CHECKJNI = 1 << 1, + DEBUG_ENABLE_ASSERT = 1 << 2, + DEBUG_ENABLE_SAFEMODE = 1 << 3, + DEBUG_ENABLE_JNI_LOGGING = 1 << 4, + DEBUG_GENERATE_DEBUG_INFO = 1 << 5, + DEBUG_ALWAYS_JIT = 1 << 6, + DEBUG_NATIVE_DEBUGGABLE = 1 << 7, + DEBUG_JAVA_DEBUGGABLE = 1 << 8, + DISABLE_VERIFIER = 1 << 9, + ONLY_USE_SYSTEM_OAT_FILES = 1 << 10, +}; +static uint32_t EnableDebugFeatures(uint32_t runtime_flags) { Runtime* const runtime = Runtime::Current(); - if ((debug_flags & DEBUG_ENABLE_CHECKJNI) != 0) { + if ((runtime_flags & DEBUG_ENABLE_CHECKJNI) != 0) { JavaVMExt* vm = runtime->GetJavaVM(); if (!vm->IsCheckJniEnabled()) { LOG(INFO) << "Late-enabling -Xcheck:jni"; @@ -179,67 +183,65 @@ static void EnableDebugFeatures(uint32_t debug_flags) { } else { LOG(INFO) << "Not late-enabling -Xcheck:jni (already on)"; } - debug_flags &= ~DEBUG_ENABLE_CHECKJNI; + runtime_flags &= ~DEBUG_ENABLE_CHECKJNI; } - if ((debug_flags & DEBUG_ENABLE_JNI_LOGGING) != 0) { + if ((runtime_flags & DEBUG_ENABLE_JNI_LOGGING) != 0) { gLogVerbosity.third_party_jni = true; - debug_flags &= ~DEBUG_ENABLE_JNI_LOGGING; + runtime_flags &= ~DEBUG_ENABLE_JNI_LOGGING; } - Dbg::SetJdwpAllowed((debug_flags & DEBUG_ENABLE_JDWP) != 0); - if ((debug_flags & DEBUG_ENABLE_JDWP) != 0) { + Dbg::SetJdwpAllowed((runtime_flags & DEBUG_ENABLE_JDWP) != 0); + if ((runtime_flags & DEBUG_ENABLE_JDWP) != 0) { EnableDebugger(); } - debug_flags &= ~DEBUG_ENABLE_JDWP; + runtime_flags &= ~DEBUG_ENABLE_JDWP; - const bool safe_mode = (debug_flags & DEBUG_ENABLE_SAFEMODE) != 0; + const bool safe_mode = (runtime_flags & DEBUG_ENABLE_SAFEMODE) != 0; if (safe_mode) { // Only quicken oat files. runtime->AddCompilerOption("--compiler-filter=quicken"); runtime->SetSafeMode(true); - debug_flags &= ~DEBUG_ENABLE_SAFEMODE; + runtime_flags &= ~DEBUG_ENABLE_SAFEMODE; } - const bool generate_debug_info = (debug_flags & DEBUG_GENERATE_DEBUG_INFO) != 0; + const bool generate_debug_info = (runtime_flags & DEBUG_GENERATE_DEBUG_INFO) != 0; if (generate_debug_info) { runtime->AddCompilerOption("--generate-debug-info"); - debug_flags &= ~DEBUG_GENERATE_DEBUG_INFO; + runtime_flags &= ~DEBUG_GENERATE_DEBUG_INFO; } // This is for backwards compatibility with Dalvik. - debug_flags &= ~DEBUG_ENABLE_ASSERT; + runtime_flags &= ~DEBUG_ENABLE_ASSERT; - if ((debug_flags & DEBUG_ALWAYS_JIT) != 0) { + if ((runtime_flags & DEBUG_ALWAYS_JIT) != 0) { jit::JitOptions* jit_options = runtime->GetJITOptions(); CHECK(jit_options != nullptr); jit_options->SetJitAtFirstUse(); - debug_flags &= ~DEBUG_ALWAYS_JIT; + runtime_flags &= ~DEBUG_ALWAYS_JIT; } bool needs_non_debuggable_classes = false; - if ((debug_flags & DEBUG_JAVA_DEBUGGABLE) != 0) { + if ((runtime_flags & DEBUG_JAVA_DEBUGGABLE) != 0) { runtime->AddCompilerOption("--debuggable"); runtime->SetJavaDebuggable(true); // Deoptimize the boot image as it may be non-debuggable. runtime->DeoptimizeBootImage(); - debug_flags &= ~DEBUG_JAVA_DEBUGGABLE; + runtime_flags &= ~DEBUG_JAVA_DEBUGGABLE; needs_non_debuggable_classes = true; } if (needs_non_debuggable_classes || kAlwaysCollectNonDebuggableClasses) { CollectNonDebuggableClasses(); } - if ((debug_flags & DEBUG_NATIVE_DEBUGGABLE) != 0) { + if ((runtime_flags & DEBUG_NATIVE_DEBUGGABLE) != 0) { runtime->AddCompilerOption("--debuggable"); runtime->AddCompilerOption("--generate-debug-info"); runtime->SetNativeDebuggable(true); - debug_flags &= ~DEBUG_NATIVE_DEBUGGABLE; + runtime_flags &= ~DEBUG_NATIVE_DEBUGGABLE; } - if (debug_flags != 0) { - LOG(ERROR) << StringPrintf("Unknown bits set in debug_flags: %#x", debug_flags); - } + return runtime_flags; } static jlong ZygoteHooks_nativePreFork(JNIEnv* env, jclass) { @@ -260,13 +262,27 @@ static jlong ZygoteHooks_nativePreFork(JNIEnv* env, jclass) { static void ZygoteHooks_nativePostForkChild(JNIEnv* env, jclass, jlong token, - jint debug_flags, + jint runtime_flags, jboolean is_system_server, jstring instruction_set) { Thread* thread = reinterpret_cast<Thread*>(token); // Our system thread ID, etc, has changed so reset Thread state. thread->InitAfterFork(); - EnableDebugFeatures(debug_flags); + runtime_flags = EnableDebugFeatures(runtime_flags); + + if ((runtime_flags & DISABLE_VERIFIER) != 0) { + Runtime::Current()->DisableVerifier(); + runtime_flags &= ~DISABLE_VERIFIER; + } + + if ((runtime_flags & ONLY_USE_SYSTEM_OAT_FILES) != 0) { + Runtime::Current()->GetOatFileManager().SetOnlyUseSystemOatFiles(); + runtime_flags &= ~ONLY_USE_SYSTEM_OAT_FILES; + } + + if (runtime_flags != 0) { + LOG(ERROR) << StringPrintf("Unknown bits set in runtime_flags: %#x", runtime_flags); + } // Update tracing. if (Trace::GetMethodTracingMode() != TracingMode::kTracingInactive) { diff --git a/runtime/native/java_lang_Thread.cc b/runtime/native/java_lang_Thread.cc index 4fbbb72631..94007ffa1e 100644 --- a/runtime/native/java_lang_Thread.cc +++ b/runtime/native/java_lang_Thread.cc @@ -86,6 +86,8 @@ static jint Thread_nativeGetStatus(JNIEnv* env, jobject java_thread, jboolean ha case kWaiting: return kJavaWaiting; case kStarting: return kJavaNew; case kNative: return kJavaRunnable; + case kWaitingForTaskProcessor: return kJavaWaiting; + case kWaitingForLockInflation: return kJavaWaiting; case kWaitingForGcToComplete: return kJavaWaiting; case kWaitingPerformingGc: return kJavaWaiting; case kWaitingForCheckPointsToRun: return kJavaWaiting; diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index 6515cfa864..b2f6c036d1 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -1466,7 +1466,7 @@ const DexFile::ClassDef* OatFile::OatDexFile::FindClassDef(const DexFile& dex_fi DCHECK_EQ(ComputeModifiedUtf8Hash(descriptor), hash); if (LIKELY((oat_dex_file != nullptr) && (oat_dex_file->GetTypeLookupTable() != nullptr))) { const uint32_t class_def_idx = oat_dex_file->GetTypeLookupTable()->Lookup(descriptor, hash); - return (class_def_idx != DexFile::kDexNoIndex) ? &dex_file.GetClassDef(class_def_idx) : nullptr; + return (class_def_idx != dex::kDexNoIndex) ? &dex_file.GetClassDef(class_def_idx) : nullptr; } // Fast path for rare no class defs case. const uint32_t num_class_defs = dex_file.NumClassDefs(); @@ -1604,6 +1604,10 @@ CompilerFilter::Filter OatFile::GetCompilerFilter() const { return GetOatHeader().GetCompilerFilter(); } +std::string OatFile::GetClassLoaderContext() const { + return GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey); +}; + OatFile::OatClass OatFile::FindOatClass(const DexFile& dex_file, uint16_t class_def_idx, bool* found) { diff --git a/runtime/oat_file.h b/runtime/oat_file.h index e13126bae3..04cb3a0a6e 100644 --- a/runtime/oat_file.h +++ b/runtime/oat_file.h @@ -115,6 +115,8 @@ class OatFile { CompilerFilter::Filter GetCompilerFilter() const; + std::string GetClassLoaderContext() const; + const std::string& GetLocation() const { return location_; } diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc index 3794f51cb7..83a8e096e4 100644 --- a/runtime/oat_file_assistant.cc +++ b/runtime/oat_file_assistant.cc @@ -190,9 +190,13 @@ bool OatFileAssistant::Lock(std::string* error_msg) { int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target, bool profile_changed, - bool downgrade) { + bool downgrade, + ClassLoaderContext* class_loader_context) { OatFileInfo& info = GetBestInfo(); - DexOptNeeded dexopt_needed = info.GetDexOptNeeded(target, profile_changed, downgrade); + DexOptNeeded dexopt_needed = info.GetDexOptNeeded(target, + profile_changed, + downgrade, + class_loader_context); if (info.IsOatLocation() || dexopt_needed == kDex2OatFromScratch) { return dexopt_needed; } @@ -227,7 +231,7 @@ bool OatFileAssistant::IsUpToDate() { OatFileAssistant::ResultOfAttemptToUpdate OatFileAssistant::MakeUpToDate(bool profile_changed, - const std::string& class_loader_context, + ClassLoaderContext* class_loader_context, std::string* error_msg) { CompilerFilter::Filter target; if (!GetRuntimeCompilerFilterOption(&target, error_msg)) { @@ -245,7 +249,8 @@ OatFileAssistant::MakeUpToDate(bool profile_changed, // - however, MakeUpToDate will not always succeed (e.g. for primary apks, or for dex files // loaded in other processes). So it boils down to how far do we want to complicate // the logic in order to enable the use of oat files. Maybe its time to try simplify it. - switch (info.GetDexOptNeeded(target, profile_changed, /*downgrade*/ false)) { + switch (info.GetDexOptNeeded( + target, profile_changed, /*downgrade*/ false, class_loader_context)) { case kNoDexOptNeeded: return kUpdateSucceeded; @@ -643,7 +648,7 @@ static bool PrepareOdexDirectories(const std::string& dex_location, OatFileAssistant::ResultOfAttemptToUpdate OatFileAssistant::GenerateOatFileNoChecks( OatFileAssistant::OatFileInfo& info, CompilerFilter::Filter filter, - const std::string& class_loader_context, + const ClassLoaderContext* class_loader_context, std::string* error_msg) { CHECK(error_msg != nullptr); @@ -720,7 +725,10 @@ OatFileAssistant::ResultOfAttemptToUpdate OatFileAssistant::GenerateOatFileNoChe args.push_back("--oat-fd=" + std::to_string(oat_file->Fd())); args.push_back("--oat-location=" + oat_file_name); args.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(filter)); - args.push_back("--class-loader-context=" + class_loader_context); + const std::string dex2oat_context = class_loader_context == nullptr + ? OatFile::kSpecialSharedLibrary + : class_loader_context->EncodeContextForDex2oat(/*base_dir*/ ""); + args.push_back("--class-loader-context=" + dex2oat_context); if (!Dex2Oat(args, error_msg)) { // Manually delete the oat and vdex files. This ensures there is no garbage @@ -1016,31 +1024,40 @@ OatFileAssistant::OatStatus OatFileAssistant::OatFileInfo::Status() { } OatFileAssistant::DexOptNeeded OatFileAssistant::OatFileInfo::GetDexOptNeeded( - CompilerFilter::Filter target, bool profile_changed, bool downgrade) { + CompilerFilter::Filter target, + bool profile_changed, + bool downgrade, + ClassLoaderContext* context) { + bool compilation_desired = CompilerFilter::IsAotCompilationEnabled(target); bool filter_okay = CompilerFilterIsOkay(target, profile_changed, downgrade); + bool class_loader_context_okay = ClassLoaderContextIsOkay(context); + + // Only check the filter and relocation if the class loader context is ok. + // If it is not, we will return kDex2OatFromScratch as the compilation needs to be redone. + if (class_loader_context_okay) { + if (filter_okay && Status() == kOatUpToDate) { + // The oat file is in good shape as is. + return kNoDexOptNeeded; + } - if (filter_okay && Status() == kOatUpToDate) { - // The oat file is in good shape as is. - return kNoDexOptNeeded; - } - - if (filter_okay && !compilation_desired && Status() == kOatRelocationOutOfDate) { - // If no compilation is desired, then it doesn't matter if the oat - // file needs relocation. It's in good shape as is. - return kNoDexOptNeeded; - } + if (filter_okay && !compilation_desired && Status() == kOatRelocationOutOfDate) { + // If no compilation is desired, then it doesn't matter if the oat + // file needs relocation. It's in good shape as is. + return kNoDexOptNeeded; + } - if (filter_okay && Status() == kOatRelocationOutOfDate) { - return kDex2OatForRelocation; - } + if (filter_okay && Status() == kOatRelocationOutOfDate) { + return kDex2OatForRelocation; + } - if (IsUseable()) { - return kDex2OatForFilter; - } + if (IsUseable()) { + return kDex2OatForFilter; + } - if (Status() == kOatBootImageOutOfDate) { - return kDex2OatForBootImage; + if (Status() == kOatBootImageOutOfDate) { + return kDex2OatForBootImage; + } } if (oat_file_assistant_->HasOriginalDexFiles()) { @@ -1090,6 +1107,36 @@ bool OatFileAssistant::OatFileInfo::CompilerFilterIsOkay( CompilerFilter::IsAsGoodAs(current, target); } +bool OatFileAssistant::OatFileInfo::ClassLoaderContextIsOkay(ClassLoaderContext* context) { + if (context == nullptr) { + VLOG(oat) << "ClassLoaderContext check ignored: null context"; + return true; + } + + const OatFile* file = GetFile(); + if (file == nullptr) { + return false; + } + + size_t dir_index = file->GetLocation().rfind('/'); + std::string classpath_dir = (dir_index != std::string::npos) + ? file->GetLocation().substr(0, dir_index) + : ""; + + if (!context->OpenDexFiles(oat_file_assistant_->isa_, classpath_dir)) { + VLOG(oat) << "ClassLoaderContext check failed: dex files from the context could not be opened"; + return false; + } + + bool result = context->VerifyClassLoaderContextMatch(file->GetClassLoaderContext()); + if (!result) { + VLOG(oat) << "ClassLoaderContext check failed. Context was " + << file->GetClassLoaderContext() + << ". The expected context is " << context->EncodeContextForOatFile(classpath_dir); + } + return result; +} + bool OatFileAssistant::OatFileInfo::IsExecutable() { const OatFile* file = GetFile(); return (file != nullptr && file->IsExecutable()); diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h index 5eec943689..6dc3c197b2 100644 --- a/runtime/oat_file_assistant.h +++ b/runtime/oat_file_assistant.h @@ -26,6 +26,7 @@ #include "base/scoped_flock.h" #include "base/unix_file/fd_file.h" #include "compiler_filter.h" +#include "class_loader_context.h" #include "oat_file.h" #include "os.h" @@ -164,7 +165,8 @@ class OatFileAssistant { // the oat file in the odex location. int GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter, bool profile_changed = false, - bool downgrade = false); + bool downgrade = false, + ClassLoaderContext* context = nullptr); // Returns true if there is up-to-date code for this dex location, // irrespective of the compiler filter of the up-to-date code. @@ -194,7 +196,7 @@ class OatFileAssistant { // to a string describing why there was a failure or the update was not // attempted. error_msg must not be null. ResultOfAttemptToUpdate MakeUpToDate(bool profile_changed, - const std::string& class_loader_context, + ClassLoaderContext* class_loader_context, std::string* error_msg); // Returns an oat file that can be used for loading dex files. @@ -330,7 +332,8 @@ class OatFileAssistant { // compiler filter. DexOptNeeded GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter, bool profile_changed, - bool downgrade); + bool downgrade, + ClassLoaderContext* context); // Returns the loaded file. // Loads the file if needed. Returns null if the file failed to load. @@ -367,6 +370,8 @@ class OatFileAssistant { // compiler filter. bool CompilerFilterIsOkay(CompilerFilter::Filter target, bool profile_changed, bool downgrade); + bool ClassLoaderContextIsOkay(ClassLoaderContext* context); + // Release the loaded oat file. // Returns null if the oat file hasn't been loaded. // @@ -404,7 +409,7 @@ class OatFileAssistant { // attempted. error_msg must not be null. ResultOfAttemptToUpdate GenerateOatFileNoChecks(OatFileInfo& info, CompilerFilter::Filter target, - const std::string& class_loader_context, + const ClassLoaderContext* class_loader_context, std::string* error_msg); // Return info for the best oat file. diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc index e048177fa4..3ecd1b516d 100644 --- a/runtime/oat_file_assistant_test.cc +++ b/runtime/oat_file_assistant_test.cc @@ -40,6 +40,7 @@ namespace art { static const std::string kSpecialSharedLibrary = "&"; +static ClassLoaderContext* kSpecialSharedLibraryContext = nullptr; class OatFileAssistantTest : public DexoptTest {}; @@ -121,7 +122,7 @@ TEST_F(OatFileAssistantTest, NoDexNoOat) { // Trying to make the oat file up to date should not fail or crash. std::string error_msg; EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)); + oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)); // Trying to get the best oat file should fail, but not crash. std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); @@ -774,7 +775,7 @@ TEST_F(OatFileAssistantTest, ResourceOnlyDex) { std::string error_msg; Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)) << + oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)) << error_msg; EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, @@ -955,7 +956,7 @@ TEST_F(OatFileAssistantTest, GenNoDex) { // We should get kUpdateSucceeded from MakeUpToDate since there's nothing // that can be done in this situation. ASSERT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)); + oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)); // Verify it didn't create an oat in the default location (dalvik-cache). OatFileAssistant ofm(dex_location.c_str(), kRuntimeISA, false); @@ -1034,7 +1035,7 @@ TEST_F(OatFileAssistantTest, ShortDexLocation) { std::string error_msg; Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)); + oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)); EXPECT_TRUE(error_msg.empty()); } @@ -1181,7 +1182,7 @@ TEST_F(OatFileAssistantTest, RuntimeCompilerFilterOptionUsed) { std::string error_msg; Runtime::Current()->AddCompilerOption("--compiler-filter=quicken"); EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)) << + oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)) << error_msg; EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, oat_file_assistant.GetDexOptNeeded(CompilerFilter::kQuicken)); @@ -1190,7 +1191,7 @@ TEST_F(OatFileAssistantTest, RuntimeCompilerFilterOptionUsed) { Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)) + oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)) << error_msg; EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, oat_file_assistant.GetDexOptNeeded(CompilerFilter::kQuicken)); @@ -1199,7 +1200,7 @@ TEST_F(OatFileAssistantTest, RuntimeCompilerFilterOptionUsed) { Runtime::Current()->AddCompilerOption("--compiler-filter=bogus"); EXPECT_EQ(OatFileAssistant::kUpdateNotAttempted, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)); + oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)); } TEST(OatFileAssistantUtilsTest, DexLocationToOdexFilename) { @@ -1259,7 +1260,7 @@ TEST_F(OatFileAssistantTest, DefaultMakeUpToDateFilter) { OatFileAssistant::kDefaultCompilerFilterForDexLoading; std::string error_msg; EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)) << + oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)) << error_msg; EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, oat_file_assistant.GetDexOptNeeded(default_filter)); @@ -1277,7 +1278,7 @@ TEST_F(OatFileAssistantTest, MakeUpToDateWithSpecialSharedLibrary) { const CompilerFilter::Filter default_filter = OatFileAssistant::kDefaultCompilerFilterForDexLoading; std::string error_msg; - int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg); + int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, oat_file_assistant.GetDexOptNeeded(default_filter)); @@ -1299,19 +1300,52 @@ TEST_F(OatFileAssistantTest, MakeUpToDateWithContext) { OatFileAssistant::kDefaultCompilerFilterForDexLoading; std::string error_msg; std::string context_str = "PCL[" + context_location + "]"; - int status = oat_file_assistant.MakeUpToDate(false, context_str, &error_msg); + std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str); + ASSERT_TRUE(context != nullptr); + ASSERT_TRUE(context->OpenDexFiles(kRuntimeISA, "")); + + int status = oat_file_assistant.MakeUpToDate(false, context.get(), &error_msg); EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, - oat_file_assistant.GetDexOptNeeded(default_filter)); + oat_file_assistant.GetDexOptNeeded(default_filter, false, false, context.get())); + std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); EXPECT_NE(nullptr, oat_file.get()); - std::unique_ptr<ClassLoaderContext> context = - ClassLoaderContext::Create(context_str); - context->OpenDexFiles(kRuntimeISA, ""); EXPECT_EQ(context->EncodeContextForOatFile(""), oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey)); } +TEST_F(OatFileAssistantTest, GetDexOptNeededWithOutOfDateContext) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string context_location = GetScratchDir() + "/ContextDex.jar"; + Copy(GetDexSrc1(), dex_location); + Copy(GetDexSrc2(), context_location); + + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); + + const CompilerFilter::Filter default_filter = + OatFileAssistant::kDefaultCompilerFilterForDexLoading; + std::string error_msg; + std::string context_str = "PCL[" + context_location + "]"; + std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str); + ASSERT_TRUE(context != nullptr); + ASSERT_TRUE(context->OpenDexFiles(kRuntimeISA, "")); + + int status = oat_file_assistant.MakeUpToDate(false, context.get(), &error_msg); + EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; + EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, + oat_file_assistant.GetDexOptNeeded(default_filter, false, false, context.get())); + + // Update the context by overriding the jar file. + Copy(GetMultiDexSrc2(), context_location); + std::unique_ptr<ClassLoaderContext> updated_context = ClassLoaderContext::Create(context_str); + ASSERT_TRUE(updated_context != nullptr); + // DexOptNeeded should advise compilation from scratch. + EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch, + oat_file_assistant.GetDexOptNeeded( + default_filter, false, false, updated_context.get())); +} + // TODO: More Tests: // * Test class linker falls back to unquickened dex for DexNoOat // * Test class linker falls back to unquickened dex for MultiDexNoOat diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc index 499f356a3a..05b63d285a 100644 --- a/runtime/oat_file_manager.cc +++ b/runtime/oat_file_manager.cc @@ -21,6 +21,7 @@ #include <vector> #include "android-base/stringprintf.h" +#include "android-base/strings.h" #include "art_field-inl.h" #include "base/bit_vector-inl.h" @@ -51,8 +52,15 @@ using android::base::StringPrintf; // If true, we attempt to load the application image if it exists. static constexpr bool kEnableAppImage = true; +static bool OatFileIsOnSystem(const std::unique_ptr<const OatFile>& oat_file) { + UniqueCPtr<const char[]> path(realpath(oat_file->GetLocation().c_str(), nullptr)); + return path != nullptr && android::base::StartsWith(oat_file->GetLocation(), GetAndroidRoot()); +} + const OatFile* OatFileManager::RegisterOatFile(std::unique_ptr<const OatFile> oat_file) { WriterMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_); + CHECK(!only_use_system_oat_files_ || OatFileIsOnSystem(oat_file)) + << "Registering a non /system oat file: " << oat_file->GetLocation(); DCHECK(oat_file != nullptr); if (kIsDebugBuild) { CHECK(oat_files_.find(oat_file) == oat_files_.end()); @@ -361,8 +369,7 @@ bool OatFileManager::HasCollisions(const OatFile* oat_file, // If the pat file loading context matches the context used during compilation then we accept // the oat file without addition checks - if (context->VerifyClassLoaderContextMatch( - oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey))) { + if (context->VerifyClassLoaderContextMatch(oat_file->GetClassLoaderContext())) { return false; } @@ -422,16 +429,14 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( const OatFile* source_oat_file = nullptr; - if (!oat_file_assistant.IsUpToDate()) { + // No point in trying to make up-to-date if we can only use system oat files. + if (!only_use_system_oat_files_ && !oat_file_assistant.IsUpToDate()) { // Update the oat file on disk if we can, based on the --compiler-filter // option derived from the current runtime options. // This may fail, but that's okay. Best effort is all that matters here. - - const std::string& dex2oat_context = context == nullptr - ? OatFile::kSpecialSharedLibrary - : context->EncodeContextForDex2oat(/*base_dir*/ ""); - switch (oat_file_assistant.MakeUpToDate( - /*profile_changed*/false, dex2oat_context, /*out*/ &error_msg)) { + switch (oat_file_assistant.MakeUpToDate(/*profile_changed*/false, + context.get(), + /*out*/ &error_msg)) { case OatFileAssistant::kUpdateFailed: LOG(WARNING) << error_msg; break; @@ -451,10 +456,12 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( // Get the oat file on disk. std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release()); - // Prevent oat files from being loaded if no class_loader or dex_elements are provided. - // This can happen when the deprecated DexFile.<init>(String) is called directly, and it - // could load oat files without checking the classpath, which would be incorrect. - if ((class_loader != nullptr || dex_elements != nullptr) && oat_file != nullptr) { + if (oat_file != nullptr && only_use_system_oat_files_ && !OatFileIsOnSystem(oat_file)) { + // If the oat file is not on /system, don't use it. + } else if ((class_loader != nullptr || dex_elements != nullptr) && oat_file != nullptr) { + // Prevent oat files from being loaded if no class_loader or dex_elements are provided. + // This can happen when the deprecated DexFile.<init>(String) is called directly, and it + // could load oat files without checking the classpath, which would be incorrect. // Take the file only if it has no collisions, or we must take it because of preopting. bool accept_oat_file = !HasCollisions(oat_file.get(), context.get(), /*out*/ &error_msg); @@ -601,6 +608,12 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( return dex_files; } +void OatFileManager::SetOnlyUseSystemOatFiles() { + ReaderMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_); + CHECK_EQ(oat_files_.size(), GetBootOatFiles().size()); + only_use_system_oat_files_ = true; +} + void OatFileManager::DumpForSigQuit(std::ostream& os) { ReaderMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_); std::vector<const OatFile*> boot_oat_files = GetBootOatFiles(); diff --git a/runtime/oat_file_manager.h b/runtime/oat_file_manager.h index 4523494c5c..12057735eb 100644 --- a/runtime/oat_file_manager.h +++ b/runtime/oat_file_manager.h @@ -45,7 +45,7 @@ class OatFile; // pointers returned from functions are always valid. class OatFileManager { public: - OatFileManager() : have_non_pic_oat_file_(false) {} + OatFileManager() : have_non_pic_oat_file_(false), only_use_system_oat_files_(false) {} ~OatFileManager(); // Add an oat file to the internal accounting, std::aborts if there already exists an oat file @@ -105,6 +105,8 @@ class OatFileManager { void DumpForSigQuit(std::ostream& os); + void SetOnlyUseSystemOatFiles(); + private: // Check that the class loader context of the given oat file matches the given context. // This will perform a check that all class loaders in the chain have the same type and @@ -125,6 +127,7 @@ class OatFileManager { std::set<std::unique_ptr<const OatFile>> oat_files_ GUARDED_BY(Locks::oat_file_manager_lock_); bool have_non_pic_oat_file_; + bool only_use_system_oat_files_; DISALLOW_COPY_AND_ASSIGN(OatFileManager); }; diff --git a/runtime/oat_quick_method_header.cc b/runtime/oat_quick_method_header.cc index 8eef5867e2..aa28fd8c9b 100644 --- a/runtime/oat_quick_method_header.cc +++ b/runtime/oat_quick_method_header.cc @@ -17,6 +17,7 @@ #include "oat_quick_method_header.h" #include "art_method.h" +#include "dex_file_types.h" #include "scoped_thread_state_change-inl.h" #include "thread.h" @@ -49,7 +50,7 @@ uint32_t OatQuickMethodHeader::ToDexPc(ArtMethod* method, } } else { DCHECK(method->IsNative()); - return DexFile::kDexNoIndex; + return dex::kDexNoIndex; } if (abort_on_failure) { ScopedObjectAccess soa(Thread::Current()); @@ -59,7 +60,7 @@ uint32_t OatQuickMethodHeader::ToDexPc(ArtMethod* method, << " current entry_point=" << method->GetEntryPointFromQuickCompiledCode() << ") in " << method->PrettyMethod(); } - return DexFile::kDexNoIndex; + return dex::kDexNoIndex; } uintptr_t OatQuickMethodHeader::ToNativeQuickPc(ArtMethod* method, diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc index b592247da3..d8b62370b8 100644 --- a/runtime/quick_exception_handler.cc +++ b/runtime/quick_exception_handler.cc @@ -19,6 +19,7 @@ #include "arch/context.h" #include "art_method-inl.h" #include "base/enums.h" +#include "dex_file_types.h" #include "dex_instruction.h" #include "entrypoints/entrypoint_utils.h" #include "entrypoints/quick/quick_entrypoints_enum.h" @@ -98,17 +99,17 @@ class CatchBlockStackVisitor FINAL : public StackVisitor { private: bool HandleTryItems(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) { - uint32_t dex_pc = DexFile::kDexNoIndex; + uint32_t dex_pc = dex::kDexNoIndex; if (!method->IsNative()) { dex_pc = GetDexPc(); } - if (dex_pc != DexFile::kDexNoIndex) { + if (dex_pc != dex::kDexNoIndex) { bool clear_exception = false; StackHandleScope<1> hs(GetThread()); Handle<mirror::Class> to_find(hs.NewHandle((*exception_)->GetClass())); uint32_t found_dex_pc = method->FindCatchBlock(to_find, dex_pc, &clear_exception); exception_handler_->SetClearException(clear_exception); - if (found_dex_pc != DexFile::kDexNoIndex) { + if (found_dex_pc != dex::kDexNoIndex) { exception_handler_->SetHandlerMethod(method); exception_handler_->SetHandlerDexPc(found_dex_pc); exception_handler_->SetHandlerQuickFramePc( diff --git a/runtime/read_barrier.h b/runtime/read_barrier.h index 8a106aabb0..45e78bcc07 100644 --- a/runtime/read_barrier.h +++ b/runtime/read_barrier.h @@ -24,10 +24,7 @@ #include "jni.h" #include "mirror/object_reference.h" #include "offsets.h" -#include "read_barrier_c.h" - -// This is a C++ (not C) header file, separate from read_barrier_c.h -// which needs to be a C header file for asm_support.h. +#include "read_barrier_config.h" namespace art { namespace mirror { diff --git a/runtime/read_barrier_c.h b/runtime/read_barrier_c.h deleted file mode 100644 index 8e5b1872f2..0000000000 --- a/runtime/read_barrier_c.h +++ /dev/null @@ -1,56 +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_READ_BARRIER_C_H_ -#define ART_RUNTIME_READ_BARRIER_C_H_ - -// This is a C (not C++) header file and is in a separate file (from -// globals.h) because asm_support.h is a C header file and can't -// include globals.h. - -// Uncomment one of the following two and the two fields in -// Object.java (libcore) to enable baker, brooks (unimplemented), or -// table-lookup read barriers. - -#ifdef ART_USE_READ_BARRIER -#if ART_READ_BARRIER_TYPE_IS_BAKER -#define USE_BAKER_READ_BARRIER -#elif ART_READ_BARRIER_TYPE_IS_BROOKS -#define USE_BROOKS_READ_BARRIER -#elif ART_READ_BARRIER_TYPE_IS_TABLELOOKUP -#define USE_TABLE_LOOKUP_READ_BARRIER -#else -#error "ART read barrier type must be set" -#endif -#endif // ART_USE_READ_BARRIER - -#ifdef ART_HEAP_POISONING -#define USE_HEAP_POISONING -#endif - -#if defined(USE_BAKER_READ_BARRIER) || defined(USE_BROOKS_READ_BARRIER) -#define USE_BAKER_OR_BROOKS_READ_BARRIER -#endif - -#if defined(USE_BAKER_READ_BARRIER) || defined(USE_BROOKS_READ_BARRIER) || defined(USE_TABLE_LOOKUP_READ_BARRIER) -#define USE_READ_BARRIER -#endif - -#if defined(USE_BAKER_READ_BARRIER) && defined(USE_BROOKS_READ_BARRIER) -#error "Only one of Baker or Brooks can be enabled at a time." -#endif - -#endif // ART_RUNTIME_READ_BARRIER_C_H_ diff --git a/runtime/read_barrier_config.h b/runtime/read_barrier_config.h new file mode 100644 index 0000000000..7067f9b086 --- /dev/null +++ b/runtime/read_barrier_config.h @@ -0,0 +1,97 @@ +/* + * 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_READ_BARRIER_CONFIG_H_ +#define ART_RUNTIME_READ_BARRIER_CONFIG_H_ + +// This is a mixed C-C++ header file that has a global section at the start +// and a C++ section at the end, because asm_support.h is a C header file and +// cannot include any C++ syntax. + +// Global (C) part. + +// Uncomment one of the following two and the two fields in +// Object.java (libcore) to enable baker, brooks (unimplemented), or +// table-lookup read barriers. + +#ifdef ART_USE_READ_BARRIER +#if ART_READ_BARRIER_TYPE_IS_BAKER +#define USE_BAKER_READ_BARRIER +#elif ART_READ_BARRIER_TYPE_IS_BROOKS +#define USE_BROOKS_READ_BARRIER +#elif ART_READ_BARRIER_TYPE_IS_TABLELOOKUP +#define USE_TABLE_LOOKUP_READ_BARRIER +#else +#error "ART read barrier type must be set" +#endif +#endif // ART_USE_READ_BARRIER + +#if defined(USE_BAKER_READ_BARRIER) || defined(USE_BROOKS_READ_BARRIER) +#define USE_BAKER_OR_BROOKS_READ_BARRIER +#endif + +#if defined(USE_BAKER_READ_BARRIER) || defined(USE_BROOKS_READ_BARRIER) || defined(USE_TABLE_LOOKUP_READ_BARRIER) +#define USE_READ_BARRIER +#endif + +#if defined(USE_BAKER_READ_BARRIER) && defined(USE_BROOKS_READ_BARRIER) +#error "Only one of Baker or Brooks can be enabled at a time." +#endif + + +// C++-specific configuration part.. + +#ifdef __cplusplus + +namespace art { + +#ifdef USE_BAKER_READ_BARRIER +static constexpr bool kUseBakerReadBarrier = true; +#else +static constexpr bool kUseBakerReadBarrier = false; +#endif + +#ifdef USE_BROOKS_READ_BARRIER +static constexpr bool kUseBrooksReadBarrier = true; +#else +static constexpr bool kUseBrooksReadBarrier = false; +#endif + +#ifdef USE_TABLE_LOOKUP_READ_BARRIER +static constexpr bool kUseTableLookupReadBarrier = true; +#else +static constexpr bool kUseTableLookupReadBarrier = false; +#endif + +static constexpr bool kUseBakerOrBrooksReadBarrier = kUseBakerReadBarrier || kUseBrooksReadBarrier; +static constexpr bool kUseReadBarrier = + kUseBakerReadBarrier || kUseBrooksReadBarrier || kUseTableLookupReadBarrier; + +// Debugging flag that forces the generation of read barriers, but +// does not trigger the use of the concurrent copying GC. +// +// TODO: Remove this flag when the read barriers compiler +// instrumentation is completed. +static constexpr bool kForceReadBarrier = false; +// TODO: Likewise, remove this flag when kForceReadBarrier is removed +// and replace it with kUseReadBarrier. +static constexpr bool kEmitCompilerReadBarrier = kForceReadBarrier || kUseReadBarrier; + +} // namespace art + +#endif // __cplusplus + +#endif // ART_RUNTIME_READ_BARRIER_CONFIG_H_ diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 3edf343edf..7c05cb6174 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -134,6 +134,7 @@ #include "native_bridge_art_interface.h" #include "native_stack_dump.h" #include "nativehelper/JniConstants.h" +#include "nativehelper/JniConstants-priv.h" #include "nativehelper/ScopedLocalRef.h" #include "oat_file.h" #include "oat_file_manager.h" @@ -405,6 +406,10 @@ Runtime::~Runtime() { // instance. We rely on a small initialization order issue in Runtime::Start() that requires // elements of WellKnownClasses to be null, see b/65500943. WellKnownClasses::Clear(); + + // Ensure that libnativehelper caching is invalidated, in case a new runtime is to be brought + // up later. + android::ClearJniConstantsCache(); } struct AbortState { @@ -1899,7 +1904,7 @@ static ArtMethod* CreateRuntimeMethod(ClassLinker* class_linker, LinearAlloc* li 1); ArtMethod* method = &method_array->At(0, method_size, method_alignment); CHECK(method != nullptr); - method->SetDexMethodIndex(DexFile::kDexNoIndex); + method->SetDexMethodIndex(dex::kDexNoIndex); CHECK(method->IsRuntimeMethod()); return method; } @@ -2325,6 +2330,10 @@ void Runtime::FixupConflictTables() { } } +void Runtime::DisableVerifier() { + verify_ = verifier::VerifyMode::kNone; +} + bool Runtime::IsVerificationEnabled() const { return verify_ == verifier::VerifyMode::kEnable || verify_ == verifier::VerifyMode::kSoftFail; diff --git a/runtime/runtime.h b/runtime/runtime.h index 4e84468744..399e1c1622 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -514,6 +514,7 @@ class Runtime { return !implicit_so_checks_; } + void DisableVerifier(); bool IsVerificationEnabled() const; bool IsVerificationSoftFail() const; @@ -658,6 +659,10 @@ class Runtime { RuntimeCallbacks* GetRuntimeCallbacks(); + bool HasLoadedPlugins() const { + return !plugins_.empty(); + } + void InitThreadGroups(Thread* self); void SetDumpGCPerformanceOnShutdown(bool value) { diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc index 16d6c13722..88d3f28583 100644 --- a/runtime/runtime_callbacks.cc +++ b/runtime/runtime_callbacks.cc @@ -21,6 +21,7 @@ #include "art_method.h" #include "base/macros.h" #include "class_linker.h" +#include "monitor.h" #include "thread.h" namespace art { @@ -38,6 +39,38 @@ static inline void Remove(T* cb, std::vector<T*>* data) { } } +void RuntimeCallbacks::MonitorContendedLocking(Monitor* m) { + for (MonitorCallback* cb : monitor_callbacks_) { + cb->MonitorContendedLocking(m); + } +} + +void RuntimeCallbacks::MonitorContendedLocked(Monitor* m) { + for (MonitorCallback* cb : monitor_callbacks_) { + cb->MonitorContendedLocked(m); + } +} + +void RuntimeCallbacks::ObjectWaitStart(Handle<mirror::Object> m, int64_t timeout) { + for (MonitorCallback* cb : monitor_callbacks_) { + cb->ObjectWaitStart(m, timeout); + } +} + +void RuntimeCallbacks::MonitorWaitFinished(Monitor* m, bool timeout) { + for (MonitorCallback* cb : monitor_callbacks_) { + cb->MonitorWaitFinished(m, timeout); + } +} + +void RuntimeCallbacks::AddMonitorCallback(MonitorCallback* cb) { + monitor_callbacks_.push_back(cb); +} + +void RuntimeCallbacks::RemoveMonitorCallback(MonitorCallback* cb) { + Remove(cb, &monitor_callbacks_); +} + void RuntimeCallbacks::RemoveThreadLifecycleCallback(ThreadLifecycleCallback* cb) { Remove(cb, &thread_callbacks_); } diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h index e8f1824262..fa686d30e1 100644 --- a/runtime/runtime_callbacks.h +++ b/runtime/runtime_callbacks.h @@ -29,12 +29,14 @@ namespace art { namespace mirror { class Class; class ClassLoader; +class Object; } // namespace mirror class ArtMethod; class ClassLoadCallback; class Thread; class MethodCallback; +class Monitor; class ThreadLifecycleCallback; // Note: RuntimeCallbacks uses the mutator lock to synchronize the callback lists. A thread must @@ -73,6 +75,25 @@ class RuntimePhaseCallback { virtual void NextRuntimePhase(RuntimePhase phase) REQUIRES_SHARED(Locks::mutator_lock_) = 0; }; +class MonitorCallback { + public: + // Called just before the thread goes to sleep to wait for the monitor to become unlocked. + virtual void MonitorContendedLocking(Monitor* mon) REQUIRES_SHARED(Locks::mutator_lock_) = 0; + // Called just after the monitor has been successfully acquired when it was already locked. + virtual void MonitorContendedLocked(Monitor* mon) REQUIRES_SHARED(Locks::mutator_lock_) = 0; + // Called on entry to the Object#wait method regardless of whether or not the call is valid. + virtual void ObjectWaitStart(Handle<mirror::Object> obj, int64_t millis_timeout) + REQUIRES_SHARED(Locks::mutator_lock_) = 0; + + // Called just after the monitor has woken up from going to sleep for a wait(). At this point the + // thread does not possess a lock on the monitor. This will only be called for threads wait calls + // where the thread did (or at least could have) gone to sleep. + virtual void MonitorWaitFinished(Monitor* m, bool timed_out) + REQUIRES_SHARED(Locks::mutator_lock_) = 0; + + virtual ~MonitorCallback() {} +}; + class RuntimeCallbacks { public: void AddThreadLifecycleCallback(ThreadLifecycleCallback* cb) REQUIRES(Locks::mutator_lock_); @@ -120,6 +141,16 @@ class RuntimeCallbacks { /*out*/void** new_implementation) REQUIRES_SHARED(Locks::mutator_lock_); + void MonitorContendedLocking(Monitor* m) REQUIRES_SHARED(Locks::mutator_lock_); + void MonitorContendedLocked(Monitor* m) REQUIRES_SHARED(Locks::mutator_lock_); + void ObjectWaitStart(Handle<mirror::Object> m, int64_t timeout) + REQUIRES_SHARED(Locks::mutator_lock_); + void MonitorWaitFinished(Monitor* m, bool timed_out) + REQUIRES_SHARED(Locks::mutator_lock_); + + void AddMonitorCallback(MonitorCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_); + void RemoveMonitorCallback(MonitorCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_); + private: std::vector<ThreadLifecycleCallback*> thread_callbacks_ GUARDED_BY(Locks::mutator_lock_); @@ -131,6 +162,8 @@ class RuntimeCallbacks { GUARDED_BY(Locks::mutator_lock_); std::vector<MethodCallback*> method_callbacks_ GUARDED_BY(Locks::mutator_lock_); + std::vector<MonitorCallback*> monitor_callbacks_ + GUARDED_BY(Locks::mutator_lock_); }; } // namespace art diff --git a/runtime/runtime_callbacks_test.cc b/runtime/runtime_callbacks_test.cc index ac2ed9e295..ef172586cf 100644 --- a/runtime/runtime_callbacks_test.cc +++ b/runtime/runtime_callbacks_test.cc @@ -22,6 +22,7 @@ #include <initializer_list> #include <memory> +#include <mutex> #include <string> #include "jni.h" @@ -29,12 +30,14 @@ #include "art_method-inl.h" #include "base/mutex.h" #include "class_linker.h" +#include "class_reference.h" #include "common_runtime_test.h" #include "handle.h" #include "handle_scope-inl.h" #include "mem_map.h" #include "mirror/class-inl.h" #include "mirror/class_loader.h" +#include "monitor.h" #include "nativehelper/ScopedLocalRef.h" #include "obj_ptr.h" #include "runtime.h" @@ -433,4 +436,87 @@ TEST_F(RuntimePhaseCallbackRuntimeCallbacksTest, Phases) { ASSERT_EQ(1u, cb_.death_seen); } +class MonitorWaitCallbacksTest : public RuntimeCallbacksTest { + protected: + void AddListener() OVERRIDE REQUIRES(Locks::mutator_lock_) { + Runtime::Current()->GetRuntimeCallbacks()->AddMonitorCallback(&cb_); + } + void RemoveListener() OVERRIDE REQUIRES(Locks::mutator_lock_) { + Runtime::Current()->GetRuntimeCallbacks()->RemoveMonitorCallback(&cb_); + } + + struct Callback : public MonitorCallback { + bool IsInterestingObject(mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (!obj->IsClass()) { + return false; + } + std::lock_guard<std::mutex> lock(ref_guard_); + mirror::Class* k = obj->AsClass(); + ClassReference test = { &k->GetDexFile(), k->GetDexClassDefIndex() }; + return ref_ == test; + } + + void SetInterestingObject(mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) { + std::lock_guard<std::mutex> lock(ref_guard_); + mirror::Class* k = obj->AsClass(); + ref_ = { &k->GetDexFile(), k->GetDexClassDefIndex() }; + } + + void MonitorContendedLocking(Monitor* mon ATTRIBUTE_UNUSED) + REQUIRES_SHARED(Locks::mutator_lock_) { } + + void MonitorContendedLocked(Monitor* mon ATTRIBUTE_UNUSED) + REQUIRES_SHARED(Locks::mutator_lock_) { } + + void ObjectWaitStart(Handle<mirror::Object> obj, int64_t millis ATTRIBUTE_UNUSED) + REQUIRES_SHARED(Locks::mutator_lock_) { + if (IsInterestingObject(obj.Get())) { + saw_wait_start_ = true; + } + } + + void MonitorWaitFinished(Monitor* m, bool timed_out ATTRIBUTE_UNUSED) + REQUIRES_SHARED(Locks::mutator_lock_) { + if (IsInterestingObject(m->GetObject())) { + saw_wait_finished_ = true; + } + } + + std::mutex ref_guard_; + ClassReference ref_ = {nullptr, 0}; + bool saw_wait_start_ = false; + bool saw_wait_finished_ = false; + }; + + Callback cb_; +}; + +// TODO It would be good to have more tests for this but due to the multi-threaded nature of the +// callbacks this is difficult. For now the run-tests 1931 & 1932 should be sufficient. +TEST_F(MonitorWaitCallbacksTest, WaitUnlocked) { + ASSERT_FALSE(cb_.saw_wait_finished_); + ASSERT_FALSE(cb_.saw_wait_start_); + { + Thread* self = Thread::Current(); + self->TransitionFromSuspendedToRunnable(); + bool started = runtime_->Start(); + ASSERT_TRUE(started); + { + ScopedObjectAccess soa(self); + cb_.SetInterestingObject( + soa.Decode<mirror::Class>(WellKnownClasses::java_util_Collections).Ptr()); + Monitor::Wait( + self, + // Just a random class + soa.Decode<mirror::Class>(WellKnownClasses::java_util_Collections).Ptr(), + /*ms*/0, + /*ns*/0, + /*interruptShouldThrow*/false, + /*why*/kWaiting); + } + } + ASSERT_TRUE(cb_.saw_wait_start_); + ASSERT_FALSE(cb_.saw_wait_finished_); +} + } // namespace art diff --git a/runtime/stack.cc b/runtime/stack.cc index 2e0653666f..ab9fb0d73f 100644 --- a/runtime/stack.cc +++ b/runtime/stack.cc @@ -23,6 +23,7 @@ #include "base/callee_save_type.h" #include "base/enums.h" #include "base/hex_dump.h" +#include "dex_file_types.h" #include "entrypoints/entrypoint_utils-inl.h" #include "entrypoints/runtime_asm_entrypoints.h" #include "gc/space/image_space.h" @@ -120,7 +121,7 @@ uint32_t StackVisitor::GetDexPc(bool abort_on_failure) const { return GetCurrentInlineInfo(GetCurrentOatQuickMethodHeader(), cur_quick_frame_pc_). GetDexPcAtDepth(encoding.inline_info.encoding, depth_in_stack_map); } else if (cur_oat_quick_method_header_ == nullptr) { - return DexFile::kDexNoIndex; + return dex::kDexNoIndex; } else { return cur_oat_quick_method_header_->ToDexPc( GetMethod(), cur_quick_frame_pc_, abort_on_failure); diff --git a/runtime/stack_map.h b/runtime/stack_map.h index 3931b6242f..db776eaa1a 100644 --- a/runtime/stack_map.h +++ b/runtime/stack_map.h @@ -23,8 +23,8 @@ #include "base/bit_utils.h" #include "base/bit_vector.h" #include "bit_memory_region.h" -#include "dex_file.h" #include "leb128.h" +#include "dex_file_types.h" #include "memory_region.h" #include "method_info.h" @@ -906,7 +906,7 @@ class InlineInfoEncoding { dex_pc_bit_offset_ = dchecked_integral_cast<uint8_t>(total_bit_size_); // Note: We're not encoding the dex pc if there is none. That's the case // for an intrinsified native method, such as String.charAt(). - if (dex_pc_max != DexFile::kDexNoIndex) { + if (dex_pc_max != dex::kDexNoIndex) { total_bit_size_ += MinimumBitsToStore(1 /* kNoDexPc */ + dex_pc_max); } diff --git a/runtime/string_reference.h b/runtime/string_reference.h index 6ba47736ec..d0ab4e40d0 100644 --- a/runtime/string_reference.h +++ b/runtime/string_reference.h @@ -21,37 +21,30 @@ #include "base/logging.h" #include "dex_file-inl.h" +#include "dex_file_reference.h" #include "dex_file_types.h" #include "utf-inl.h" namespace art { // A string is located by its DexFile and the string_ids_ table index into that DexFile. -struct StringReference { +class StringReference : public DexFileReference { + public: StringReference(const DexFile* file, dex::StringIndex index) - : dex_file(file), string_index(index) { } + : DexFileReference(file, index.index_) {} - const char* GetStringData() const { - return dex_file->GetStringData(dex_file->GetStringId(string_index)); + dex::StringIndex StringIndex() const { + return dex::StringIndex(index); } - const DexFile* dex_file; - dex::StringIndex string_index; -}; - -// Compare only the reference and not the string contents. -struct StringReferenceComparator { - bool operator()(const StringReference& a, const StringReference& b) const { - if (a.dex_file != b.dex_file) { - return a.dex_file < b.dex_file; - } - return a.string_index < b.string_index; + const char* GetStringData() const { + return dex_file->GetStringData(dex_file->GetStringId(StringIndex())); } }; // Compare the actual referenced string values. Used for string reference deduplication. struct StringReferenceValueComparator { - bool operator()(StringReference sr1, StringReference sr2) const { + bool operator()(const StringReference& sr1, const StringReference& sr2) const { // Note that we want to deduplicate identical strings even if they are referenced // by different dex files, so we need some (any) total ordering of strings, rather // than references. However, the references should usually be from the same dex file, @@ -60,10 +53,10 @@ struct StringReferenceValueComparator { if (sr1.dex_file == sr2.dex_file) { // Use the string order enforced by the dex file verifier. DCHECK_EQ( - sr1.string_index < sr2.string_index, + sr1.index < sr2.index, CompareModifiedUtf8ToModifiedUtf8AsUtf16CodePointValues(sr1.GetStringData(), sr2.GetStringData()) < 0); - return sr1.string_index < sr2.string_index; + return sr1.index < sr2.index; } else { // Cannot compare indexes, so do the string comparison. return CompareModifiedUtf8ToModifiedUtf8AsUtf16CodePointValues(sr1.GetStringData(), diff --git a/runtime/thread.cc b/runtime/thread.cc index 8cceeaa5b8..968a23b926 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -48,6 +48,7 @@ #include "debugger.h" #include "dex_file-inl.h" #include "dex_file_annotations.h" +#include "dex_file_types.h" #include "entrypoints/entrypoint_utils.h" #include "entrypoints/quick/quick_alloc_entrypoints.h" #include "gc/accounting/card_table-inl.h" @@ -2421,7 +2422,7 @@ class FetchStackTraceVisitor : public StackVisitor { if (!m->IsRuntimeMethod()) { // Ignore runtime frames (in particular callee save). if (depth_ < max_saved_frames_) { saved_frames_[depth_].first = m; - saved_frames_[depth_].second = m->IsProxyMethod() ? DexFile::kDexNoIndex : GetDexPc(); + saved_frames_[depth_].second = m->IsProxyMethod() ? dex::kDexNoIndex : GetDexPc(); } ++depth_; } @@ -2507,7 +2508,7 @@ class BuildInternalStackTraceVisitor : public StackVisitor { if (m->IsRuntimeMethod()) { return true; // Ignore runtime frames (in particular callee save). } - AddFrame(m, m->IsProxyMethod() ? DexFile::kDexNoIndex : GetDexPc()); + AddFrame(m, m->IsProxyMethod() ? dex::kDexNoIndex : GetDexPc()); return true; } diff --git a/runtime/thread.h b/runtime/thread.h index 59c4fa9dfb..2e4a3da348 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -40,6 +40,7 @@ #include "jvalue.h" #include "managed_stack.h" #include "offsets.h" +#include "read_barrier_config.h" #include "runtime_stats.h" #include "suspend_reason.h" #include "thread_state.h" diff --git a/runtime/thread_state.h b/runtime/thread_state.h index 8f2f70f46e..8edfeecbdd 100644 --- a/runtime/thread_state.h +++ b/runtime/thread_state.h @@ -29,6 +29,8 @@ enum ThreadState { kSleeping, // TIMED_WAITING TS_SLEEPING in Thread.sleep() kBlocked, // BLOCKED TS_MONITOR blocked on a monitor kWaiting, // WAITING TS_WAIT in Object.wait() + kWaitingForLockInflation, // WAITING TS_WAIT blocked inflating a thin-lock + kWaitingForTaskProcessor, // WAITING TS_WAIT blocked waiting for taskProcessor kWaitingForGcToComplete, // WAITING TS_WAIT blocked waiting for GC kWaitingForCheckPointsToRun, // WAITING TS_WAIT GC waiting for checkpoints to run kWaitingPerformingGc, // WAITING TS_WAIT performing GC diff --git a/runtime/trace.cc b/runtime/trace.cc index b30de795a4..4c3fa20c9d 100644 --- a/runtime/trace.cc +++ b/runtime/trace.cc @@ -832,6 +832,11 @@ void Trace::InvokeVirtualOrInterface(Thread*, << " " << dex_pc; } +void Trace::WatchedFramePop(Thread* self ATTRIBUTE_UNUSED, + const ShadowFrame& frame ATTRIBUTE_UNUSED) { + LOG(ERROR) << "Unexpected WatchedFramePop event in tracing"; +} + void Trace::ReadClocks(Thread* thread, uint32_t* thread_clock_diff, uint32_t* wall_clock_diff) { if (UseThreadCpuClock()) { uint64_t clock_base = thread->GetTraceClockBase(); diff --git a/runtime/trace.h b/runtime/trace.h index 49d5b22373..a888dcbcd1 100644 --- a/runtime/trace.h +++ b/runtime/trace.h @@ -38,6 +38,7 @@ namespace art { class ArtField; class ArtMethod; class DexFile; +class ShadowFrame; class Thread; using DexIndexBitSet = std::bitset<65536>; @@ -194,6 +195,8 @@ class Trace FINAL : public instrumentation::InstrumentationListener { uint32_t dex_pc, ArtMethod* callee) REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!*unique_methods_lock_) OVERRIDE; + void WatchedFramePop(Thread* thread, const ShadowFrame& frame) + REQUIRES_SHARED(Locks::mutator_lock_) OVERRIDE; // Reuse an old stack trace if it exists, otherwise allocate a new one. static std::vector<ArtMethod*>* AllocStackTrace(); // Clear and store an old stack trace for later use. diff --git a/runtime/type_lookup_table.h b/runtime/type_lookup_table.h index fd68deb71c..780b3804e0 100644 --- a/runtime/type_lookup_table.h +++ b/runtime/type_lookup_table.h @@ -17,12 +17,14 @@ #ifndef ART_RUNTIME_TYPE_LOOKUP_TABLE_H_ #define ART_RUNTIME_TYPE_LOOKUP_TABLE_H_ -#include "dex_file.h" +#include "dex_file_types.h" #include "leb128.h" #include "utf.h" namespace art { +class DexFile; + /** * TypeLookupTable used to find class_def_idx by class descriptor quickly. * Implementation of TypeLookupTable is based on hash table. @@ -40,7 +42,7 @@ class TypeLookupTable { } // Method search class_def_idx by class descriptor and it's hash. - // If no data found then the method returns DexFile::kDexNoIndex + // If no data found then the method returns dex::kDexNoIndex. ALWAYS_INLINE uint32_t Lookup(const char* str, uint32_t hash) const { uint32_t pos = hash & GetSizeMask(); // Thanks to special insertion algorithm, the element at position pos can be empty or start of @@ -51,12 +53,12 @@ class TypeLookupTable { return GetClassDefIdx(entry->data); } if (entry->IsLast()) { - return DexFile::kDexNoIndex; + return dex::kDexNoIndex; } pos = (pos + entry->next_pos_delta) & GetSizeMask(); entry = &entries_[pos]; } - return DexFile::kDexNoIndex; + return dex::kDexNoIndex; } // Method creates lookup table for dex file diff --git a/runtime/type_lookup_table_test.cc b/runtime/type_lookup_table_test.cc index ac11871d19..0f8f2880fe 100644 --- a/runtime/type_lookup_table_test.cc +++ b/runtime/type_lookup_table_test.cc @@ -25,8 +25,6 @@ namespace art { -static const size_t kDexNoIndex = DexFile::kDexNoIndex; // Make copy to prevent linking errors. - using DescriptorClassDefIdxPair = std::pair<const char*, uint32_t>; class TypeLookupTableTest : public CommonRuntimeTestWithParam<DescriptorClassDefIdxPair> {}; @@ -56,7 +54,7 @@ INSTANTIATE_TEST_CASE_P(FindNonExistingClassWithoutCollisions, testing::Values(DescriptorClassDefIdxPair("LAB;", 1U))); INSTANTIATE_TEST_CASE_P(FindNonExistingClassWithCollisions, TypeLookupTableTest, - testing::Values(DescriptorClassDefIdxPair("LDA;", kDexNoIndex))); + testing::Values(DescriptorClassDefIdxPair("LDA;", dex::kDexNoIndex))); INSTANTIATE_TEST_CASE_P(FindClassNoCollisions, TypeLookupTableTest, testing::Values(DescriptorClassDefIdxPair("LC;", 2U))); diff --git a/runtime/type_reference.h b/runtime/type_reference.h index c44019dde3..5ddc9d05d4 100644 --- a/runtime/type_reference.h +++ b/runtime/type_reference.h @@ -28,31 +28,23 @@ namespace art { class DexFile; // A type is located by its DexFile and the string_ids_ table index into that DexFile. -struct TypeReference { +class TypeReference : public DexFileReference { + public: TypeReference(const DexFile* file = nullptr, dex::TypeIndex index = dex::TypeIndex()) - : dex_file(file), - type_index(index) {} + : DexFileReference(file, index.index_) {} - const DexFile* dex_file; - dex::TypeIndex type_index; -}; - -struct TypeReferenceComparator { - bool operator()(TypeReference mr1, TypeReference mr2) const { - if (mr1.dex_file != mr2.dex_file) { - return mr1.dex_file < mr2.dex_file; - } - return mr1.type_index < mr2.type_index; + dex::TypeIndex TypeIndex() const { + return dex::TypeIndex(index); } }; // Compare the actual referenced type names. Used for type reference deduplication. struct TypeReferenceValueComparator { - bool operator()(TypeReference tr1, TypeReference tr2) const { + bool operator()(const TypeReference& tr1, const TypeReference& tr2) const { // Note that we want to deduplicate identical boot image types even if they are // referenced by different dex files, so we simply compare the descriptors. - StringReference sr1(tr1.dex_file, tr1.dex_file->GetTypeId(tr1.type_index).descriptor_idx_); - StringReference sr2(tr2.dex_file, tr2.dex_file->GetTypeId(tr2.type_index).descriptor_idx_); + StringReference sr1(tr1.dex_file, tr1.dex_file->GetTypeId(tr1.TypeIndex()).descriptor_idx_); + StringReference sr2(tr2.dex_file, tr2.dex_file->GetTypeId(tr2.TypeIndex()).descriptor_idx_); return StringReferenceValueComparator()(sr1, sr2); } }; diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index ce9deaf3cd..791af94bd3 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -554,7 +554,7 @@ MethodVerifier::MethodVerifier(Thread* self, arena_(&arena_stack_), reg_types_(can_load_classes, arena_), reg_table_(arena_), - work_insn_idx_(DexFile::kDexNoIndex), + work_insn_idx_(dex::kDexNoIndex), dex_method_idx_(dex_method_idx), mirror_method_(method), method_access_flags_(method_access_flags), @@ -928,7 +928,7 @@ std::ostream& MethodVerifier::Fail(VerifyError error) { // Note: this assumes that Fail is called before we do any work_line modifications. // Note: this can fail before we touch any instruction, for the signature of a method. So // add a check. - if (work_insn_idx_ < DexFile::kDexNoIndex) { + if (work_insn_idx_ < dex::kDexNoIndex) { const uint16_t* insns = code_item_->insns_ + work_insn_idx_; const Instruction* inst = Instruction::At(insns); int opcode_flags = Instruction::FlagsOf(inst->Opcode()); @@ -1992,7 +1992,7 @@ static uint32_t GetFirstFinalInstanceFieldIndex(const DexFile& dex_file, dex::Ty } it.Next(); } - return DexFile::kDexNoIndex; + return dex::kDexNoIndex; } // Setup a register line for the given return instruction. @@ -3377,7 +3377,7 @@ bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) { // manually over the underlying dex file. uint32_t first_index = GetFirstFinalInstanceFieldIndex(*dex_file_, dex_file_->GetMethodId(dex_method_idx_).class_idx_); - if (first_index != DexFile::kDexNoIndex) { + if (first_index != dex::kDexNoIndex) { Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "return-void-no-barrier not expected for field " << first_index; } diff --git a/test/063-process-manager/src/Main.java b/test/063-process-manager/src/Main.java index e31a0df053..6cb594959e 100644 --- a/test/063-process-manager/src/Main.java +++ b/test/063-process-manager/src/Main.java @@ -31,7 +31,13 @@ public class Main { Thread t = entry.getKey(); String name = t.getName(); if (name.indexOf("process reaper") >= 0) { - System.out.println("process manager: " + t.getState()); + Thread.State state = t.getState(); + System.out.println("process manager: " + state); + if (state != Thread.State.RUNNABLE && state != Thread.State.TIMED_WAITING) { + for (StackTraceElement e : entry.getValue()) { + System.out.println(" " + e); + } + } found = true; } } diff --git a/test/164-resolution-trampoline-dex-cache/expected.txt b/test/164-resolution-trampoline-dex-cache/expected.txt new file mode 100644 index 0000000000..d549cb1664 --- /dev/null +++ b/test/164-resolution-trampoline-dex-cache/expected.txt @@ -0,0 +1,3 @@ +JNI_OnLoad called +Base.foo() on MostDerived +MostDerived.test(.) done. diff --git a/test/164-resolution-trampoline-dex-cache/info.txt b/test/164-resolution-trampoline-dex-cache/info.txt new file mode 100644 index 0000000000..4e4d82ae5b --- /dev/null +++ b/test/164-resolution-trampoline-dex-cache/info.txt @@ -0,0 +1,3 @@ +Regression test for artQuickResolutionTrampoline() erroneously storing an +ArtMethod to a DexCache for a MethodId referencing a class missing from the +associated ClassTable. This discrepancy then led to a crash when JITting. diff --git a/test/164-resolution-trampoline-dex-cache/profile b/test/164-resolution-trampoline-dex-cache/profile new file mode 100644 index 0000000000..d232ff20ca --- /dev/null +++ b/test/164-resolution-trampoline-dex-cache/profile @@ -0,0 +1 @@ +LMostDerived;->test(Ljava/lang/Class;)V diff --git a/test/164-resolution-trampoline-dex-cache/run b/test/164-resolution-trampoline-dex-cache/run new file mode 100644 index 0000000000..5e77cd56ed --- /dev/null +++ b/test/164-resolution-trampoline-dex-cache/run @@ -0,0 +1,22 @@ +#!/bin/bash +# +# Copyright (C) 2017 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. + +# Use the --secondary switch to add secondary dex file to class path. +# Make sure we compile the required method using speed-profile compiler filter. +# Enable JIT through runtime option to avoid the compiler filter set by --jit. +exec ${RUN} "${@}" --secondary \ + -Xcompiler-option --compiler-filter=speed-profile --profile \ + --runtime-option -Xusejit:true diff --git a/test/164-resolution-trampoline-dex-cache/src-ex/MostDerived.java b/test/164-resolution-trampoline-dex-cache/src-ex/MostDerived.java new file mode 100644 index 0000000000..0c85528b55 --- /dev/null +++ b/test/164-resolution-trampoline-dex-cache/src-ex/MostDerived.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 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. + */ + +public class MostDerived extends Derived { + public static void test(Class main) { + // The defining class loader of MostDerived (MDCL) is also the initiating loader of + // superclass Derived but delegates the loading to its parent class loader (PCL) which + // defines both Derived and Base. Thus Derived.class is recorded in MDCL's ClassTable + // but the Base.class is not because the Base's initiating loader is PCL. This is the + // case when loading the MostDerived class and remains the case after resolving the + // "invoke-super Derived.foo(.)" called from from MostDerived.foo(.). When that + // invoke-super is executed from AOT-compiled code, it goes through the .bss ArtMethod* + // entry and on first execution goes through the resolution method. After resolving to + // the Base.foo(.), the artQuickResolutionTrampoline() used to erroneously fill the + // Base.foo(.) entry in the MostDerived's DexCache which is wrong as the referenced + // class Base is not in the associated, i.e. MDCL's, ClassTable. + new MostDerived().foo(main); + try { + // This discrepancy then used to crash when resolving the Base.foo(.) method + // for JIT compilation of another method. + main.getDeclaredMethod("ensureJitCompiled", Class.class, String.class).invoke( + null, MostDerived.class, "bar"); + } catch (Throwable t) { + t.printStackTrace(System.out); + } + System.out.println("MostDerived.test(.) done."); + } + + public void foo(Class main) { + super.foo(main); + } + + public void bar(Class main) { + Base b = this; + b.foo(main); + } +} diff --git a/test/164-resolution-trampoline-dex-cache/src/Base.java b/test/164-resolution-trampoline-dex-cache/src/Base.java new file mode 100644 index 0000000000..d4ef78bf49 --- /dev/null +++ b/test/164-resolution-trampoline-dex-cache/src/Base.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 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. + */ + +public class Base { + public void foo(Class main) { + System.out.println("Base.foo() on " + getClass().getName()); + } +} diff --git a/test/164-resolution-trampoline-dex-cache/src/Derived.java b/test/164-resolution-trampoline-dex-cache/src/Derived.java new file mode 100644 index 0000000000..acca9ce5af --- /dev/null +++ b/test/164-resolution-trampoline-dex-cache/src/Derived.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2017 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. + */ + +public class Derived extends Base { +} diff --git a/test/164-resolution-trampoline-dex-cache/src/Main.java b/test/164-resolution-trampoline-dex-cache/src/Main.java new file mode 100644 index 0000000000..b4731ae0b6 --- /dev/null +++ b/test/164-resolution-trampoline-dex-cache/src/Main.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017 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. + */ + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public class Main { + public static String TEST_NAME = "164-resolution-trampoline-dex-cache"; + + public static void main(String[] args) { + // Load the test JNI for ensureJitCompiled(). Note that classes Main loaded + // by other class loaders do not have the binding, so we need to pass the + // current Main.class around and call ensureJitCompiled() via reflection. + System.loadLibrary(args[0]); + try { + String dex_location = System.getenv("DEX_LOCATION"); + ClassLoader systemLoader = ClassLoader.getSystemClassLoader().getParent(); + ClassLoader baseLoader = getClassLoaderFor(dex_location, systemLoader, /* ex */ false); + ClassLoader mainLoader = getClassLoaderFor(dex_location, baseLoader, /* ex */ true); + + Class<?> tc = Class.forName("MostDerived", true, mainLoader); + Method m = tc.getDeclaredMethod("test", Class.class); + m.invoke(null, Main.class); + } catch (Throwable t) { + t.printStackTrace(System.out); + } + } + + public static ClassLoader getClassLoaderFor(String location, ClassLoader parent, boolean ex) + throws Exception { + try { + Class<?> class_loader_class = Class.forName("dalvik.system.PathClassLoader"); + Constructor<?> ctor = + class_loader_class.getConstructor(String.class, ClassLoader.class); + String path = location + "/" + TEST_NAME + (ex ? "-ex.jar" : ".jar"); + return (ClassLoader)ctor.newInstance(path, parent); + } catch (ClassNotFoundException e) { + // Running on RI. Use URLClassLoader. + String url = "file://" + location + (ex ? "/classes-ex/" : "/classes/"); + return new java.net.URLClassLoader( + new java.net.URL[] { new java.net.URL(url) }, parent); + } + } + + public static native void ensureJitCompiled(Class<?> klass, String method_name); +} diff --git a/test/1930-monitor-info/src/art/Monitors.java b/test/1930-monitor-info/src/art/Monitors.java index 26f7718dc5..b28a3ee035 100644 --- a/test/1930-monitor-info/src/art/Monitors.java +++ b/test/1930-monitor-info/src/art/Monitors.java @@ -16,12 +16,24 @@ package art; -import java.util.Arrays; -import java.util.Objects; +import java.lang.reflect.Method; +import java.util.concurrent.atomic.*; import java.util.function.Function; import java.util.stream.Stream; +import java.util.Arrays; +import java.util.Objects; public class Monitors { + public native static void setupMonitorEvents( + Class<?> method_klass, + Method monitor_contended_enter_event, + Method monitor_contended_entered_event, + Method monitor_wait_event, + Method monitor_waited_event, + Class<?> lock_klass, + Thread thr); + public native static void stopMonitorEvents(); + public static class NamedLock { public final String name; public NamedLock(String name) { @@ -68,5 +80,237 @@ public class Monitors { } public static native MonitorUsage getObjectMonitorUsage(Object monitor); + public static native Object getCurrentContendedMonitor(Thread thr); + + public static class TestException extends Error { + public TestException() { super(); } + public TestException(String s) { super(s); } + public TestException(String s, Throwable c) { super(s, c); } + } + + public static class LockController { + private static enum Action { HOLD, RELEASE, NOTIFY, NOTIFY_ALL, WAIT, TIMED_WAIT } + + public final Object lock; + public final long timeout; + private final AtomicStampedReference<Action> action; + private volatile Thread runner = null; + private volatile boolean started = false; + private volatile boolean held = false; + private static final AtomicInteger cnt = new AtomicInteger(0); + private volatile Throwable exe; + + public LockController(Object lock) { + this(lock, 10 * 1000); + } + public LockController(Object lock, long timeout) { + this.lock = lock; + this.timeout = timeout; + this.action = new AtomicStampedReference(Action.HOLD, 0); + this.exe = null; + } + + public boolean IsWorkerThread(Thread thd) { + return Objects.equals(runner, thd); + } + + public boolean IsLocked() { + checkException(); + return held; + } + + public void checkException() { + if (exe != null) { + throw new TestException("Exception thrown by other thread!", exe); + } + } + + private void setAction(Action a) { + int stamp = action.getStamp(); + // Wait for it to be HOLD before updating. + while (!action.compareAndSet(Action.HOLD, a, stamp, stamp + 1)) { + stamp = action.getStamp(); + } + } + + public synchronized void suspendWorker() throws Exception { + checkException(); + if (runner == null) { + throw new TestException("We don't have any runner holding " + lock); + } + Suspension.suspend(runner); + } + + public Object getWorkerContendedMonitor() throws Exception { + checkException(); + if (runner == null) { + return null; + } + return getCurrentContendedMonitor(runner); + } + + public synchronized void DoLock() { + if (IsLocked()) { + throw new Error("lock is already acquired or being acquired."); + } + if (runner != null) { + throw new Error("Already have thread!"); + } + runner = new Thread(() -> { + started = true; + try { + synchronized (lock) { + held = true; + int[] stamp_h = new int[] { -1 }; + Action cur_action = Action.HOLD; + try { + while (true) { + cur_action = action.get(stamp_h); + int stamp = stamp_h[0]; + if (cur_action == Action.RELEASE) { + // The other thread will deal with reseting action. + break; + } + try { + switch (cur_action) { + case HOLD: + Thread.yield(); + break; + case NOTIFY: + lock.notify(); + break; + case NOTIFY_ALL: + lock.notifyAll(); + break; + case TIMED_WAIT: + lock.wait(timeout); + break; + case WAIT: + lock.wait(); + break; + default: + throw new Error("Unknown action " + action); + } + } finally { + // reset action back to hold if it isn't something else. + action.compareAndSet(cur_action, Action.HOLD, stamp, stamp+1); + } + } + } catch (Exception e) { + throw new TestException("Got an error while performing action " + cur_action, e); + } + } + } finally { + held = false; + started = false; + } + }, "Locker thread " + cnt.getAndIncrement() + " for " + lock); + // Make sure we can get any exceptions this throws. + runner.setUncaughtExceptionHandler((t, e) -> { exe = e; }); + runner.start(); + } + + public void waitForLockToBeHeld() throws Exception { + while (true) { + if (IsLocked() && Objects.equals(runner, Monitors.getObjectMonitorUsage(lock).owner)) { + return; + } + } + } + + public synchronized void waitForNotifySleep() throws Exception { + if (runner == null) { + throw new Error("No thread trying to lock!"); + } + do { + checkException(); + } while (!started || + !Arrays.asList(Monitors.getObjectMonitorUsage(lock).notifyWaiters).contains(runner)); + } + + public synchronized void waitForContendedSleep() throws Exception { + if (runner == null) { + throw new Error("No thread trying to lock!"); + } + do { + checkException(); + } while (!started || + runner.getState() != Thread.State.BLOCKED || + !Arrays.asList(Monitors.getObjectMonitorUsage(lock).waiters).contains(runner)); + } + + public synchronized void DoNotify() { + if (!IsLocked()) { + throw new Error("Not locked"); + } + setAction(Action.NOTIFY); + } + + public synchronized void DoNotifyAll() { + if (!IsLocked()) { + throw new Error("Not locked"); + } + setAction(Action.NOTIFY_ALL); + } + + public synchronized void DoTimedWait() throws Exception { + if (!IsLocked()) { + throw new Error("Not locked"); + } + setAction(Action.TIMED_WAIT); + } + + public synchronized void DoWait() throws Exception { + if (!IsLocked()) { + throw new Error("Not locked"); + } + setAction(Action.WAIT); + } + + public synchronized void interruptWorker() throws Exception { + if (!IsLocked()) { + throw new Error("Not locked"); + } + runner.interrupt(); + } + + public synchronized void waitForActionToFinish() throws Exception { + checkException(); + while (action.getReference() != Action.HOLD) { checkException(); } + } + + public synchronized void DoUnlock() throws Exception { + Error throwing = null; + if (!IsLocked()) { + // We might just be racing some exception that was thrown by the worker thread. Cache the + // exception, we will throw one from the worker before this one. + throwing = new Error("Not locked!"); + } + setAction(Action.RELEASE); + Thread run = runner; + runner = null; + while (held) {} + run.join(); + action.set(Action.HOLD, 0); + // Make sure to throw any exception that occurred since it might not have unlocked due to our + // request. + checkException(); + DoCleanup(); + if (throwing != null) { + throw throwing; + } + } + + public synchronized void DoCleanup() throws Exception { + if (runner != null) { + Thread run = runner; + runner = null; + while (held) {} + run.join(); + } + action.set(Action.HOLD, 0); + exe = null; + } + } } diff --git a/test/1930-monitor-info/src/art/Suspension.java b/test/1930-monitor-info/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1930-monitor-info/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1931-monitor-events/expected.txt b/test/1931-monitor-events/expected.txt new file mode 100644 index 0000000000..33a9bd3684 --- /dev/null +++ b/test/1931-monitor-events/expected.txt @@ -0,0 +1,29 @@ +Testing contended locking. +Locker thread 1 for NamedLock[Lock testLock] contended-LOCKING NamedLock[Lock testLock] +Locker thread 1 for NamedLock[Lock testLock] LOCKED NamedLock[Lock testLock] +Testing monitor wait. +Locker thread 2 for NamedLock[Lock testWait] start-monitor-wait NamedLock[Lock testWait] timeout: 0 +Locker thread 2 for NamedLock[Lock testWait] monitor-waited NamedLock[Lock testWait] timed_out: false +Testing monitor timed wait. +Locker thread 4 for NamedLock[Lock testTimedWait] start-monitor-wait NamedLock[Lock testTimedWait] timeout: 3600000 +Locker thread 4 for NamedLock[Lock testTimedWait] monitor-waited NamedLock[Lock testTimedWait] timed_out: false +Testing monitor timed with timeout. +Waiting for 10 seconds. +Locker thread 6 for NamedLock[Lock testTimedWaitTimeout] start-monitor-wait NamedLock[Lock testTimedWaitTimeout] timeout: 10000 +Locker thread 6 for NamedLock[Lock testTimedWaitTimeout] monitor-waited NamedLock[Lock testTimedWaitTimeout] timed_out: true +Wait finished with timeout. +Waiting on an unlocked monitor. +Unlocked wait thread: start-monitor-wait NamedLock[Lock testUnlockedWait] timeout: 0 +Caught exception: java.lang.reflect.InvocationTargetException + Caused by: class java.lang.IllegalMonitorStateException +Waiting with an illegal argument (negative timeout) +Locker thread 7 for NamedLock[Lock testIllegalWait] start-monitor-wait NamedLock[Lock testIllegalWait] timeout: -100 +Caught exception: art.Monitors$TestException: Exception thrown by other thread! + Caused by: art.Monitors$TestException: Got an error while performing action TIMED_WAIT + Caused by: class java.lang.IllegalArgumentException +Interrupt a monitor being waited on. +Locker thread 8 for NamedLock[Lock testInteruptWait] start-monitor-wait NamedLock[Lock testInteruptWait] timeout: 0 +Locker thread 8 for NamedLock[Lock testInteruptWait] monitor-waited NamedLock[Lock testInteruptWait] timed_out: false +Caught exception: art.Monitors$TestException: Exception thrown by other thread! + Caused by: art.Monitors$TestException: Got an error while performing action WAIT + Caused by: class java.lang.InterruptedException diff --git a/test/1931-monitor-events/info.txt b/test/1931-monitor-events/info.txt new file mode 100644 index 0000000000..ae07c53c2a --- /dev/null +++ b/test/1931-monitor-events/info.txt @@ -0,0 +1,3 @@ +Tests basic functions in the jvmti plugin. + +Tests that the basic monitor-events work as we expect them to. diff --git a/test/1931-monitor-events/run b/test/1931-monitor-events/run new file mode 100755 index 0000000000..e92b873956 --- /dev/null +++ b/test/1931-monitor-events/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2017 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. + +./default-run "$@" --jvmti diff --git a/test/1931-monitor-events/src/Main.java b/test/1931-monitor-events/src/Main.java new file mode 100644 index 0000000000..81c9d2c568 --- /dev/null +++ b/test/1931-monitor-events/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 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. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1931.run(); + } +} diff --git a/test/1931-monitor-events/src/art/Monitors.java b/test/1931-monitor-events/src/art/Monitors.java new file mode 100644 index 0000000000..b28a3ee035 --- /dev/null +++ b/test/1931-monitor-events/src/art/Monitors.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.*; +import java.util.function.Function; +import java.util.stream.Stream; +import java.util.Arrays; +import java.util.Objects; + +public class Monitors { + public native static void setupMonitorEvents( + Class<?> method_klass, + Method monitor_contended_enter_event, + Method monitor_contended_entered_event, + Method monitor_wait_event, + Method monitor_waited_event, + Class<?> lock_klass, + Thread thr); + public native static void stopMonitorEvents(); + + public static class NamedLock { + public final String name; + public NamedLock(String name) { + this.name = name; + } + public String toString() { + return String.format("NamedLock[%s]", name); + } + } + + public static final class MonitorUsage { + public final Object monitor; + public final Thread owner; + public final int entryCount; + public final Thread[] waiters; + public final Thread[] notifyWaiters; + + public MonitorUsage( + Object monitor, + Thread owner, + int entryCount, + Thread[] waiters, + Thread[] notifyWaiters) { + this.monitor = monitor; + this.entryCount = entryCount; + this.owner = owner; + this.waiters = waiters; + this.notifyWaiters = notifyWaiters; + } + + private static String toNameList(Thread[] ts) { + return Arrays.toString(Arrays.stream(ts).map((Thread t) -> t.getName()).toArray()); + } + + public String toString() { + return String.format( + "MonitorUsage{ monitor: %s, owner: %s, entryCount: %d, waiters: %s, notify_waiters: %s }", + monitor, + (owner != null) ? owner.getName() : "<NULL>", + entryCount, + toNameList(waiters), + toNameList(notifyWaiters)); + } + } + + public static native MonitorUsage getObjectMonitorUsage(Object monitor); + public static native Object getCurrentContendedMonitor(Thread thr); + + public static class TestException extends Error { + public TestException() { super(); } + public TestException(String s) { super(s); } + public TestException(String s, Throwable c) { super(s, c); } + } + + public static class LockController { + private static enum Action { HOLD, RELEASE, NOTIFY, NOTIFY_ALL, WAIT, TIMED_WAIT } + + public final Object lock; + public final long timeout; + private final AtomicStampedReference<Action> action; + private volatile Thread runner = null; + private volatile boolean started = false; + private volatile boolean held = false; + private static final AtomicInteger cnt = new AtomicInteger(0); + private volatile Throwable exe; + + public LockController(Object lock) { + this(lock, 10 * 1000); + } + public LockController(Object lock, long timeout) { + this.lock = lock; + this.timeout = timeout; + this.action = new AtomicStampedReference(Action.HOLD, 0); + this.exe = null; + } + + public boolean IsWorkerThread(Thread thd) { + return Objects.equals(runner, thd); + } + + public boolean IsLocked() { + checkException(); + return held; + } + + public void checkException() { + if (exe != null) { + throw new TestException("Exception thrown by other thread!", exe); + } + } + + private void setAction(Action a) { + int stamp = action.getStamp(); + // Wait for it to be HOLD before updating. + while (!action.compareAndSet(Action.HOLD, a, stamp, stamp + 1)) { + stamp = action.getStamp(); + } + } + + public synchronized void suspendWorker() throws Exception { + checkException(); + if (runner == null) { + throw new TestException("We don't have any runner holding " + lock); + } + Suspension.suspend(runner); + } + + public Object getWorkerContendedMonitor() throws Exception { + checkException(); + if (runner == null) { + return null; + } + return getCurrentContendedMonitor(runner); + } + + public synchronized void DoLock() { + if (IsLocked()) { + throw new Error("lock is already acquired or being acquired."); + } + if (runner != null) { + throw new Error("Already have thread!"); + } + runner = new Thread(() -> { + started = true; + try { + synchronized (lock) { + held = true; + int[] stamp_h = new int[] { -1 }; + Action cur_action = Action.HOLD; + try { + while (true) { + cur_action = action.get(stamp_h); + int stamp = stamp_h[0]; + if (cur_action == Action.RELEASE) { + // The other thread will deal with reseting action. + break; + } + try { + switch (cur_action) { + case HOLD: + Thread.yield(); + break; + case NOTIFY: + lock.notify(); + break; + case NOTIFY_ALL: + lock.notifyAll(); + break; + case TIMED_WAIT: + lock.wait(timeout); + break; + case WAIT: + lock.wait(); + break; + default: + throw new Error("Unknown action " + action); + } + } finally { + // reset action back to hold if it isn't something else. + action.compareAndSet(cur_action, Action.HOLD, stamp, stamp+1); + } + } + } catch (Exception e) { + throw new TestException("Got an error while performing action " + cur_action, e); + } + } + } finally { + held = false; + started = false; + } + }, "Locker thread " + cnt.getAndIncrement() + " for " + lock); + // Make sure we can get any exceptions this throws. + runner.setUncaughtExceptionHandler((t, e) -> { exe = e; }); + runner.start(); + } + + public void waitForLockToBeHeld() throws Exception { + while (true) { + if (IsLocked() && Objects.equals(runner, Monitors.getObjectMonitorUsage(lock).owner)) { + return; + } + } + } + + public synchronized void waitForNotifySleep() throws Exception { + if (runner == null) { + throw new Error("No thread trying to lock!"); + } + do { + checkException(); + } while (!started || + !Arrays.asList(Monitors.getObjectMonitorUsage(lock).notifyWaiters).contains(runner)); + } + + public synchronized void waitForContendedSleep() throws Exception { + if (runner == null) { + throw new Error("No thread trying to lock!"); + } + do { + checkException(); + } while (!started || + runner.getState() != Thread.State.BLOCKED || + !Arrays.asList(Monitors.getObjectMonitorUsage(lock).waiters).contains(runner)); + } + + public synchronized void DoNotify() { + if (!IsLocked()) { + throw new Error("Not locked"); + } + setAction(Action.NOTIFY); + } + + public synchronized void DoNotifyAll() { + if (!IsLocked()) { + throw new Error("Not locked"); + } + setAction(Action.NOTIFY_ALL); + } + + public synchronized void DoTimedWait() throws Exception { + if (!IsLocked()) { + throw new Error("Not locked"); + } + setAction(Action.TIMED_WAIT); + } + + public synchronized void DoWait() throws Exception { + if (!IsLocked()) { + throw new Error("Not locked"); + } + setAction(Action.WAIT); + } + + public synchronized void interruptWorker() throws Exception { + if (!IsLocked()) { + throw new Error("Not locked"); + } + runner.interrupt(); + } + + public synchronized void waitForActionToFinish() throws Exception { + checkException(); + while (action.getReference() != Action.HOLD) { checkException(); } + } + + public synchronized void DoUnlock() throws Exception { + Error throwing = null; + if (!IsLocked()) { + // We might just be racing some exception that was thrown by the worker thread. Cache the + // exception, we will throw one from the worker before this one. + throwing = new Error("Not locked!"); + } + setAction(Action.RELEASE); + Thread run = runner; + runner = null; + while (held) {} + run.join(); + action.set(Action.HOLD, 0); + // Make sure to throw any exception that occurred since it might not have unlocked due to our + // request. + checkException(); + DoCleanup(); + if (throwing != null) { + throw throwing; + } + } + + public synchronized void DoCleanup() throws Exception { + if (runner != null) { + Thread run = runner; + runner = null; + while (held) {} + run.join(); + } + action.set(Action.HOLD, 0); + exe = null; + } + } +} + diff --git a/test/1931-monitor-events/src/art/Suspension.java b/test/1931-monitor-events/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1931-monitor-events/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1931-monitor-events/src/art/Test1931.java b/test/1931-monitor-events/src/art/Test1931.java new file mode 100644 index 0000000000..ccefede9f8 --- /dev/null +++ b/test/1931-monitor-events/src/art/Test1931.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Objects; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.*; +import java.util.ListIterator; +import java.util.function.Consumer; +import java.util.function.Function; + +public class Test1931 { + public static void printStackTrace(Throwable t) { + System.out.println("Caught exception: " + t); + for (Throwable c = t.getCause(); c != null; c = c.getCause()) { + System.out.println("\tCaused by: " + + (Test1931.class.getPackage().equals(c.getClass().getPackage()) + ? c.toString() : c.getClass().toString())); + } + } + + public static void handleMonitorEnter(Thread thd, Object lock) { + System.out.println(thd.getName() + " contended-LOCKING " + lock); + } + + public static void handleMonitorEntered(Thread thd, Object lock) { + System.out.println(thd.getName() + " LOCKED " + lock); + } + public static void handleMonitorWait(Thread thd, Object lock, long timeout) { + System.out.println(thd.getName() + " start-monitor-wait " + lock + " timeout: " + timeout); + } + + public static void handleMonitorWaited(Thread thd, Object lock, boolean timed_out) { + System.out.println(thd.getName() + " monitor-waited " + lock + " timed_out: " + timed_out); + } + + public static void run() throws Exception { + Monitors.setupMonitorEvents( + Test1931.class, + Test1931.class.getDeclaredMethod("handleMonitorEnter", Thread.class, Object.class), + Test1931.class.getDeclaredMethod("handleMonitorEntered", Thread.class, Object.class), + Test1931.class.getDeclaredMethod("handleMonitorWait", + Thread.class, Object.class, Long.TYPE), + Test1931.class.getDeclaredMethod("handleMonitorWaited", + Thread.class, Object.class, Boolean.TYPE), + Monitors.NamedLock.class, + null); + + System.out.println("Testing contended locking."); + testLock(new Monitors.NamedLock("Lock testLock")); + + System.out.println("Testing monitor wait."); + testWait(new Monitors.NamedLock("Lock testWait")); + + System.out.println("Testing monitor timed wait."); + testTimedWait(new Monitors.NamedLock("Lock testTimedWait")); + + System.out.println("Testing monitor timed with timeout."); + testTimedWaitTimeout(new Monitors.NamedLock("Lock testTimedWaitTimeout")); + + // TODO It would be good (but annoying) to do this with jasmin/smali in order to test if it's + // different without the reflection. + System.out.println("Waiting on an unlocked monitor."); + testUnlockedWait(new Monitors.NamedLock("Lock testUnlockedWait")); + + System.out.println("Waiting with an illegal argument (negative timeout)"); + testIllegalWait(new Monitors.NamedLock("Lock testIllegalWait")); + + System.out.println("Interrupt a monitor being waited on."); + testInteruptWait(new Monitors.NamedLock("Lock testInteruptWait")); + } + + public static void testInteruptWait(final Monitors.NamedLock lk) throws Exception { + final Monitors.LockController controller1 = new Monitors.LockController(lk); + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + controller1.DoWait(); + controller1.waitForNotifySleep(); + try { + controller1.interruptWorker(); + controller1.waitForLockToBeHeld(); + controller1.DoUnlock(); + System.out.println("No Exception thrown!"); + } catch (Monitors.TestException e) { + printStackTrace(e); + } + controller1.DoCleanup(); + } + + public static void testIllegalWait(final Monitors.NamedLock lk) throws Exception { + Monitors.LockController controller1 = new Monitors.LockController(lk, /*timed_wait time*/-100); + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + try { + controller1.DoTimedWait(); + controller1.waitForNotifySleep(); + controller1.waitForLockToBeHeld(); + controller1.DoUnlock(); + System.out.println("No Exception thrown!"); + } catch (Monitors.TestException e) { + printStackTrace(e); + } + controller1.DoCleanup(); + } + + public static void testUnlockedWait(final Monitors.NamedLock lk) throws Exception { + synchronized (lk) { + Thread thd = new Thread(() -> { + try { + Method m = Object.class.getDeclaredMethod("wait"); + m.invoke(lk); + } catch (Exception e) { + printStackTrace(e); + } + }, "Unlocked wait thread:"); + thd.start(); + thd.join(); + } + } + + public static void testLock(Monitors.NamedLock lk) throws Exception { + Monitors.LockController controller1 = new Monitors.LockController(lk); + Monitors.LockController controller2 = new Monitors.LockController(lk); + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + controller2.DoLock(); + if (controller2.IsLocked()) { + throw new Exception("c2 was able to gain lock while it was held by c1"); + } + controller2.waitForContendedSleep(); + controller1.DoUnlock(); + controller2.waitForLockToBeHeld(); + controller2.DoUnlock(); + } + + public static void testWait(Monitors.NamedLock lk) throws Exception { + Monitors.LockController controller1 = new Monitors.LockController(lk); + Monitors.LockController controller2 = new Monitors.LockController(lk); + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + controller1.DoWait(); + controller1.waitForNotifySleep(); + controller2.DoLock(); + controller2.waitForLockToBeHeld(); + controller2.DoNotifyAll(); + controller2.DoUnlock(); + controller1.waitForLockToBeHeld(); + controller1.DoUnlock(); + } + + public static void testTimedWait(Monitors.NamedLock lk) throws Exception { + // Time to wait (1 hour). We will wake it up before timeout. + final long millis = 60l * 60l * 1000l; + Monitors.LockController controller1 = new Monitors.LockController(lk, millis); + Monitors.LockController controller2 = new Monitors.LockController(lk); + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + controller1.DoTimedWait(); + controller1.waitForNotifySleep(); + controller2.DoLock(); + controller2.waitForLockToBeHeld(); + controller2.DoNotifyAll(); + controller2.DoUnlock(); + controller1.waitForLockToBeHeld(); + controller1.DoUnlock(); + } + + public static void testTimedWaitTimeout(Monitors.NamedLock lk) throws Exception { + // Time to wait (10 seconds). We will wait for the timeout. + final long millis = 10l * 1000l; + Monitors.LockController controller1 = new Monitors.LockController(lk, millis); + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + System.out.println("Waiting for 10 seconds."); + controller1.DoTimedWait(); + controller1.waitForNotifySleep(); + controller1.DoUnlock(); + System.out.println("Wait finished with timeout."); + } +} diff --git a/test/1932-monitor-events-misc/check b/test/1932-monitor-events-misc/check new file mode 100644 index 0000000000..8a84388a8f --- /dev/null +++ b/test/1932-monitor-events-misc/check @@ -0,0 +1,22 @@ +#!/bin/bash +# +# Copyright (C) 2017 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. + +# The RI sends an extra event that art doesn't. Add it to the expected output. +if [[ "$TEST_RUNTIME" == "jvm" ]]; then + patch -p0 expected.txt < jvm-expected.patch >/dev/null +fi + +./default-check "$@" diff --git a/test/1932-monitor-events-misc/expected.txt b/test/1932-monitor-events-misc/expected.txt new file mode 100644 index 0000000000..b33aa7d3a3 --- /dev/null +++ b/test/1932-monitor-events-misc/expected.txt @@ -0,0 +1,104 @@ +Testing contended locking where lock is released before callback ends. +Locker thread 1 for NamedLock[Lock testLockUncontend] contended-LOCKING NamedLock[Lock testLockUncontend] +Releasing NamedLock[Lock testLockUncontend] during monitorEnter event. +Locker thread 1 for NamedLock[Lock testLockUncontend] LOCKED NamedLock[Lock testLockUncontend] +Testing throwing exceptions in monitor_enter +Locker thread 3 for NamedLock[Lock testLockThrowEnter] contended-LOCKING NamedLock[Lock testLockThrowEnter] +Throwing exception in MonitorEnter +Locker thread 3 for NamedLock[Lock testLockThrowEnter] LOCKED NamedLock[Lock testLockThrowEnter] +Caught exception: art.Monitors$TestException: Exception thrown by other thread! + Caused by: art.Monitors$TestException: throwing exception during monitorEnter of NamedLock[Lock testLockThrowEnter] +lock state is: MonitorUsage{ monitor: NamedLock[Lock testLockThrowEnter], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] } +Testing throwing exceptions in monitor_entered +Locker thread 5 for NamedLock[Lock testLockThrowEntered] contended-LOCKING NamedLock[Lock testLockThrowEntered] +Locker thread 5 for NamedLock[Lock testLockThrowEntered] LOCKED NamedLock[Lock testLockThrowEntered] +Throwing exception in MonitorEntered +Caught exception: art.Monitors$TestException: Exception thrown by other thread! + Caused by: art.Monitors$TestException: throwing exception during monitorEntered of NamedLock[Lock testLockThrowEntered] +lock state is: MonitorUsage{ monitor: NamedLock[Lock testLockThrowEntered], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] } +Testing throwing exceptions in both monitorEnter & MonitorEntered +Locker thread 7 for NamedLock[Lock testLockThrowBoth] contended-LOCKING NamedLock[Lock testLockThrowBoth] +Throwing exception in MonitorEnter +Locker thread 7 for NamedLock[Lock testLockThrowBoth] LOCKED NamedLock[Lock testLockThrowBoth] +Throwing exception in MonitorEntered +Caught exception: art.Monitors$TestException: Exception thrown by other thread! + Caused by: art.Monitors$TestException: throwing exception during monitorEntered of NamedLock[Lock testLockThrowBoth] +lock state is: MonitorUsage{ monitor: NamedLock[Lock testLockThrowBoth], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] } +Testing throwing exception in MonitorWait event +Locker thread 8 for NamedLock[Lock testThrowWait] start-monitor-wait NamedLock[Lock testThrowWait] timeout: 0 +Throwing exception in MonitorWait +Caught exception: art.Monitors$TestException: Exception thrown by other thread! + Caused by: art.Monitors$TestException: throwing exception during MonitorWait of NamedLock[Lock testThrowWait] +lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWait], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] } +Testing throwing exception in MonitorWait event with illegal aruments +Locker thread 9 for NamedLock[Lock testThrowIllegalWait] start-monitor-wait NamedLock[Lock testThrowIllegalWait] timeout: -100000 +Throwing exception in MonitorWait timeout = -100000 +Caught exception: art.Monitors$TestException: Exception thrown by other thread! + Caused by: art.Monitors$TestException: throwing exception during monitorWait of NamedLock[Lock testThrowIllegalWait] +lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowIllegalWait], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] } +Testing throwing exception in MonitorWaited event +Locker thread 10 for NamedLock[Lock testThrowWaited] start-monitor-wait NamedLock[Lock testThrowWaited] timeout: 0 +Locker thread 10 for NamedLock[Lock testThrowWaited] monitor-waited NamedLock[Lock testThrowWaited] timed_out: false +Throwing exception in MonitorWaited +Caught exception: art.Monitors$TestException: Exception thrown by other thread! + Caused by: art.Monitors$TestException: throwing exception during monitorWaited of NamedLock[Lock testThrowWaited] +lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWaited], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] } +Testing throwing exception in MonitorWaited event caused by timeout +Locker thread 12 for NamedLock[Lock testThrowWaitedTimeout] start-monitor-wait NamedLock[Lock testThrowWaitedTimeout] timeout: 5000 +Locker thread 12 for NamedLock[Lock testThrowWaitedTimeout] monitor-waited NamedLock[Lock testThrowWaitedTimeout] timed_out: true +Throwing exception in MonitorWaited +Caught exception: art.Monitors$TestException: Exception thrown by other thread! + Caused by: art.Monitors$TestException: throwing exception during monitorWaited of NamedLock[Lock testThrowWaitedTimeout] +lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWaitedTimeout], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] } +Testing throwing exception in MonitorWaited event caused by interrupt +Locker thread 13 for NamedLock[Lock testThrowWaitedInterrupt] start-monitor-wait NamedLock[Lock testThrowWaitedInterrupt] timeout: 0 +Locker thread 13 for NamedLock[Lock testThrowWaitedInterrupt] monitor-waited NamedLock[Lock testThrowWaitedInterrupt] timed_out: false +Throwing exception in MonitorWaited +Caught exception: art.Monitors$TestException: Exception thrown by other thread! + Caused by: art.Monitors$TestException: throwing exception during monitorWaited of NamedLock[Lock testThrowWaitedInterrupt] +lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWaitedInterrupt], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] } +Testing ObjectMonitorInfo inside of events +Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] contended-LOCKING NamedLock[Lock testMonitorInfoInEvents] +Monitor usage in MonitorEnter: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: Locker thread 14 for NamedLock[Lock testMonitorInfoInEvents], entryCount: 1, waiters: [], notify_waiters: [] } +Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] LOCKED NamedLock[Lock testMonitorInfoInEvents] +Monitor usage in MonitorEntered: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents], entryCount: 1, waiters: [], notify_waiters: [] } +Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] start-monitor-wait NamedLock[Lock testMonitorInfoInEvents] timeout: 0 +Monitor usage in MonitorWait: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents], entryCount: 1, waiters: [], notify_waiters: [] } +Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] monitor-waited NamedLock[Lock testMonitorInfoInEvents] timed_out: false +Monitor usage in MonitorWaited: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] } +Testing that the monitor can be stolen during the MonitorWaited event. +Locker thread 17 for NamedLock[test testWaitEnterInterleaving] start-monitor-wait NamedLock[test testWaitEnterInterleaving] timeout: 0 +Locker thread 17 for NamedLock[test testWaitEnterInterleaving] monitor-waited NamedLock[test testWaitEnterInterleaving] timed_out: false +locking controller3 in controller2 MonitorWaited event +Controller3 now holds the lock the monitor wait will try to re-acquire +Testing that we can lock and release the monitor in the MonitorWait event +Locker thread 20 for NamedLock[test testWaitMonitorEnter] start-monitor-wait NamedLock[test testWaitMonitorEnter] timeout: 0 +In wait monitor usage: MonitorUsage{ monitor: NamedLock[test testWaitMonitorEnter], owner: Locker thread 20 for NamedLock[test testWaitMonitorEnter], entryCount: 1, waiters: [], notify_waiters: [] } +In wait monitor usage sync: MonitorUsage{ monitor: NamedLock[test testWaitMonitorEnter], owner: Locker thread 20 for NamedLock[test testWaitMonitorEnter], entryCount: 2, waiters: [], notify_waiters: [] } +Locker thread 20 for NamedLock[test testWaitMonitorEnter] monitor-waited NamedLock[test testWaitMonitorEnter] timed_out: false +Testing that we can lock and release the monitor in the MonitorWaited event +Locker thread 22 for NamedLock[test testWaitedMonitorEnter] start-monitor-wait NamedLock[test testWaitedMonitorEnter] timeout: 0 +Locker thread 22 for NamedLock[test testWaitedMonitorEnter] monitor-waited NamedLock[test testWaitedMonitorEnter] timed_out: false +In waited monitor usage: MonitorUsage{ monitor: NamedLock[test testWaitedMonitorEnter], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] } +In waited monitor usage sync: MonitorUsage{ monitor: NamedLock[test testWaitedMonitorEnter], owner: Locker thread 22 for NamedLock[test testWaitedMonitorEnter], entryCount: 1, waiters: [], notify_waiters: [] } +Testing we can perform recursive lock in MonitorEntered +Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock] contended-LOCKING NamedLock[test testRecursiveMontiorEnteredLock] +Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock] LOCKED NamedLock[test testRecursiveMontiorEnteredLock] +In MonitorEntered usage: MonitorUsage{ monitor: NamedLock[test testRecursiveMontiorEnteredLock], owner: Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock], entryCount: 1, waiters: [], notify_waiters: [] } +In MonitorEntered sync: MonitorUsage{ monitor: NamedLock[test testRecursiveMontiorEnteredLock], owner: Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock], entryCount: 2, waiters: [], notify_waiters: [] } +Testing the lock state if MonitorEnter throws in a native method +NativeLockStateThrowEnter thread contended-LOCKING NamedLock[test testNativeLockStateThrowEnter] +Unlocking controller1 in MonitorEnter +Throwing exception in MonitorEnter +NativeLockStateThrowEnter thread LOCKED NamedLock[test testNativeLockStateThrowEnter] +MonitorEnter returned: -1 +Lock state is: MonitorUsage{ monitor: NamedLock[test testNativeLockStateThrowEnter], owner: NativeLockStateThrowEnter thread, entryCount: 1, waiters: [], notify_waiters: [] } +Caught exception: art.Monitors$TestException: throwing exception during monitorEnter of NamedLock[test testNativeLockStateThrowEnter] +Testing the lock state if MonitorEntered throws in a native method +NativeLockStateThrowEntered thread contended-LOCKING NamedLock[test testNativeLockStateThrowEntered] +Unlocking controller1 in MonitorEnter +NativeLockStateThrowEntered thread LOCKED NamedLock[test testNativeLockStateThrowEntered] +Throwing exception in MonitorEntered +MonitorEnter returned: -1 +Lock state is: MonitorUsage{ monitor: NamedLock[test testNativeLockStateThrowEntered], owner: NativeLockStateThrowEntered thread, entryCount: 1, waiters: [], notify_waiters: [] } +Caught exception: art.Monitors$TestException: throwing exception during monitorEntered of NamedLock[test testNativeLockStateThrowEntered] diff --git a/test/1932-monitor-events-misc/info.txt b/test/1932-monitor-events-misc/info.txt new file mode 100644 index 0000000000..674ef56c70 --- /dev/null +++ b/test/1932-monitor-events-misc/info.txt @@ -0,0 +1,4 @@ +Tests jvmti monitor events in odd situations. + +Checks that the JVMTI monitor events are correctly dispatched and handled for +many odd situations. diff --git a/test/1932-monitor-events-misc/jvm-expected.patch b/test/1932-monitor-events-misc/jvm-expected.patch new file mode 100644 index 0000000000..f6b2285a3f --- /dev/null +++ b/test/1932-monitor-events-misc/jvm-expected.patch @@ -0,0 +1,2 @@ +29a30 +> Locker thread 8 for NamedLock[Lock testThrowWait] monitor-waited NamedLock[Lock testThrowWait] timed_out: false diff --git a/test/1932-monitor-events-misc/monitor_misc.cc b/test/1932-monitor-events-misc/monitor_misc.cc new file mode 100644 index 0000000000..842c550c7c --- /dev/null +++ b/test/1932-monitor-events-misc/monitor_misc.cc @@ -0,0 +1,59 @@ +/* + * 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 <pthread.h> + +#include <cstdio> +#include <iostream> +#include <vector> + +#include "android-base/logging.h" +#include "jni.h" +#include "jvmti.h" + +#include "scoped_local_ref.h" +#include "scoped_primitive_array.h" + +// Test infrastructure +#include "jvmti_helper.h" +#include "test_env.h" + +namespace art { +namespace Test1932MonitorEventsMisc { + +extern "C" JNIEXPORT void JNICALL Java_art_Test1932_doNativeLockPrint(JNIEnv* env, + jclass klass, + jobject lock) { + // jobject atomic_boolean) { + // ScopedLocalRef<jclass> atomic_klass(env, env->FindClass("java/util/concurrent/AtomicBoolean")); + // if (env->ExceptionCheck()) { + // return; + // } + // jmethodID atomic_set = env->GetMethodID(atomic_klass.get(), "set", "(z)V"); + jmethodID print_state = env->GetStaticMethodID( + klass, "printLockState", "(Lart/Monitors$NamedLock;Ljava/lang/Object;I)V"); + if (env->ExceptionCheck()) { + return; + } + jint res = env->MonitorEnter(lock); + ScopedLocalRef<jobject> exc(env, env->ExceptionOccurred()); + env->ExceptionClear(); + env->CallStaticVoidMethod(klass, print_state, lock, exc.get(), res); + env->MonitorExit(lock); +} + +} // namespace Test1932MonitorEventsMisc +} // namespace art diff --git a/test/1932-monitor-events-misc/run b/test/1932-monitor-events-misc/run new file mode 100755 index 0000000000..e92b873956 --- /dev/null +++ b/test/1932-monitor-events-misc/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2017 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. + +./default-run "$@" --jvmti diff --git a/test/1932-monitor-events-misc/src/Main.java b/test/1932-monitor-events-misc/src/Main.java new file mode 100644 index 0000000000..0100074bfe --- /dev/null +++ b/test/1932-monitor-events-misc/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 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. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1932.run(); + } +} diff --git a/test/1932-monitor-events-misc/src/art/Monitors.java b/test/1932-monitor-events-misc/src/art/Monitors.java new file mode 100644 index 0000000000..b28a3ee035 --- /dev/null +++ b/test/1932-monitor-events-misc/src/art/Monitors.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.*; +import java.util.function.Function; +import java.util.stream.Stream; +import java.util.Arrays; +import java.util.Objects; + +public class Monitors { + public native static void setupMonitorEvents( + Class<?> method_klass, + Method monitor_contended_enter_event, + Method monitor_contended_entered_event, + Method monitor_wait_event, + Method monitor_waited_event, + Class<?> lock_klass, + Thread thr); + public native static void stopMonitorEvents(); + + public static class NamedLock { + public final String name; + public NamedLock(String name) { + this.name = name; + } + public String toString() { + return String.format("NamedLock[%s]", name); + } + } + + public static final class MonitorUsage { + public final Object monitor; + public final Thread owner; + public final int entryCount; + public final Thread[] waiters; + public final Thread[] notifyWaiters; + + public MonitorUsage( + Object monitor, + Thread owner, + int entryCount, + Thread[] waiters, + Thread[] notifyWaiters) { + this.monitor = monitor; + this.entryCount = entryCount; + this.owner = owner; + this.waiters = waiters; + this.notifyWaiters = notifyWaiters; + } + + private static String toNameList(Thread[] ts) { + return Arrays.toString(Arrays.stream(ts).map((Thread t) -> t.getName()).toArray()); + } + + public String toString() { + return String.format( + "MonitorUsage{ monitor: %s, owner: %s, entryCount: %d, waiters: %s, notify_waiters: %s }", + monitor, + (owner != null) ? owner.getName() : "<NULL>", + entryCount, + toNameList(waiters), + toNameList(notifyWaiters)); + } + } + + public static native MonitorUsage getObjectMonitorUsage(Object monitor); + public static native Object getCurrentContendedMonitor(Thread thr); + + public static class TestException extends Error { + public TestException() { super(); } + public TestException(String s) { super(s); } + public TestException(String s, Throwable c) { super(s, c); } + } + + public static class LockController { + private static enum Action { HOLD, RELEASE, NOTIFY, NOTIFY_ALL, WAIT, TIMED_WAIT } + + public final Object lock; + public final long timeout; + private final AtomicStampedReference<Action> action; + private volatile Thread runner = null; + private volatile boolean started = false; + private volatile boolean held = false; + private static final AtomicInteger cnt = new AtomicInteger(0); + private volatile Throwable exe; + + public LockController(Object lock) { + this(lock, 10 * 1000); + } + public LockController(Object lock, long timeout) { + this.lock = lock; + this.timeout = timeout; + this.action = new AtomicStampedReference(Action.HOLD, 0); + this.exe = null; + } + + public boolean IsWorkerThread(Thread thd) { + return Objects.equals(runner, thd); + } + + public boolean IsLocked() { + checkException(); + return held; + } + + public void checkException() { + if (exe != null) { + throw new TestException("Exception thrown by other thread!", exe); + } + } + + private void setAction(Action a) { + int stamp = action.getStamp(); + // Wait for it to be HOLD before updating. + while (!action.compareAndSet(Action.HOLD, a, stamp, stamp + 1)) { + stamp = action.getStamp(); + } + } + + public synchronized void suspendWorker() throws Exception { + checkException(); + if (runner == null) { + throw new TestException("We don't have any runner holding " + lock); + } + Suspension.suspend(runner); + } + + public Object getWorkerContendedMonitor() throws Exception { + checkException(); + if (runner == null) { + return null; + } + return getCurrentContendedMonitor(runner); + } + + public synchronized void DoLock() { + if (IsLocked()) { + throw new Error("lock is already acquired or being acquired."); + } + if (runner != null) { + throw new Error("Already have thread!"); + } + runner = new Thread(() -> { + started = true; + try { + synchronized (lock) { + held = true; + int[] stamp_h = new int[] { -1 }; + Action cur_action = Action.HOLD; + try { + while (true) { + cur_action = action.get(stamp_h); + int stamp = stamp_h[0]; + if (cur_action == Action.RELEASE) { + // The other thread will deal with reseting action. + break; + } + try { + switch (cur_action) { + case HOLD: + Thread.yield(); + break; + case NOTIFY: + lock.notify(); + break; + case NOTIFY_ALL: + lock.notifyAll(); + break; + case TIMED_WAIT: + lock.wait(timeout); + break; + case WAIT: + lock.wait(); + break; + default: + throw new Error("Unknown action " + action); + } + } finally { + // reset action back to hold if it isn't something else. + action.compareAndSet(cur_action, Action.HOLD, stamp, stamp+1); + } + } + } catch (Exception e) { + throw new TestException("Got an error while performing action " + cur_action, e); + } + } + } finally { + held = false; + started = false; + } + }, "Locker thread " + cnt.getAndIncrement() + " for " + lock); + // Make sure we can get any exceptions this throws. + runner.setUncaughtExceptionHandler((t, e) -> { exe = e; }); + runner.start(); + } + + public void waitForLockToBeHeld() throws Exception { + while (true) { + if (IsLocked() && Objects.equals(runner, Monitors.getObjectMonitorUsage(lock).owner)) { + return; + } + } + } + + public synchronized void waitForNotifySleep() throws Exception { + if (runner == null) { + throw new Error("No thread trying to lock!"); + } + do { + checkException(); + } while (!started || + !Arrays.asList(Monitors.getObjectMonitorUsage(lock).notifyWaiters).contains(runner)); + } + + public synchronized void waitForContendedSleep() throws Exception { + if (runner == null) { + throw new Error("No thread trying to lock!"); + } + do { + checkException(); + } while (!started || + runner.getState() != Thread.State.BLOCKED || + !Arrays.asList(Monitors.getObjectMonitorUsage(lock).waiters).contains(runner)); + } + + public synchronized void DoNotify() { + if (!IsLocked()) { + throw new Error("Not locked"); + } + setAction(Action.NOTIFY); + } + + public synchronized void DoNotifyAll() { + if (!IsLocked()) { + throw new Error("Not locked"); + } + setAction(Action.NOTIFY_ALL); + } + + public synchronized void DoTimedWait() throws Exception { + if (!IsLocked()) { + throw new Error("Not locked"); + } + setAction(Action.TIMED_WAIT); + } + + public synchronized void DoWait() throws Exception { + if (!IsLocked()) { + throw new Error("Not locked"); + } + setAction(Action.WAIT); + } + + public synchronized void interruptWorker() throws Exception { + if (!IsLocked()) { + throw new Error("Not locked"); + } + runner.interrupt(); + } + + public synchronized void waitForActionToFinish() throws Exception { + checkException(); + while (action.getReference() != Action.HOLD) { checkException(); } + } + + public synchronized void DoUnlock() throws Exception { + Error throwing = null; + if (!IsLocked()) { + // We might just be racing some exception that was thrown by the worker thread. Cache the + // exception, we will throw one from the worker before this one. + throwing = new Error("Not locked!"); + } + setAction(Action.RELEASE); + Thread run = runner; + runner = null; + while (held) {} + run.join(); + action.set(Action.HOLD, 0); + // Make sure to throw any exception that occurred since it might not have unlocked due to our + // request. + checkException(); + DoCleanup(); + if (throwing != null) { + throw throwing; + } + } + + public synchronized void DoCleanup() throws Exception { + if (runner != null) { + Thread run = runner; + runner = null; + while (held) {} + run.join(); + } + action.set(Action.HOLD, 0); + exe = null; + } + } +} + diff --git a/test/1932-monitor-events-misc/src/art/Suspension.java b/test/1932-monitor-events-misc/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1932-monitor-events-misc/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1932-monitor-events-misc/src/art/Test1932.java b/test/1932-monitor-events-misc/src/art/Test1932.java new file mode 100644 index 0000000000..7f66884ce4 --- /dev/null +++ b/test/1932-monitor-events-misc/src/art/Test1932.java @@ -0,0 +1,623 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.util.concurrent.Semaphore; + +public class Test1932 { + public static final boolean PRINT_FULL_STACK_TRACE = false; + public static final boolean INCLUDE_ANDROID_ONLY_TESTS = false; + + public static interface MonitorHandler { + public default void handleMonitorEnter(Thread thd, Object lock) {} + public default void handleMonitorEntered(Thread thd, Object lock) {} + public default void handleMonitorWait(Thread thd, Object lock, long timeout) {} + public default void handleMonitorWaited(Thread thd, Object lock, boolean timed_out) {} + } + + public static volatile MonitorHandler HANDLER = null; + + public static void run() throws Exception { + Monitors.setupMonitorEvents( + Test1932.class, + Test1932.class.getDeclaredMethod("handleMonitorEnter", Thread.class, Object.class), + Test1932.class.getDeclaredMethod("handleMonitorEntered", Thread.class, Object.class), + Test1932.class.getDeclaredMethod("handleMonitorWait", + Thread.class, Object.class, Long.TYPE), + Test1932.class.getDeclaredMethod("handleMonitorWaited", + Thread.class, Object.class, Boolean.TYPE), + Monitors.NamedLock.class, + null); + + System.out.println("Testing contended locking where lock is released before callback ends."); + testLockUncontend(new Monitors.NamedLock("Lock testLockUncontend")); + + System.out.println("Testing throwing exceptions in monitor_enter"); + testLockThrowEnter(new Monitors.NamedLock("Lock testLockThrowEnter")); + + System.out.println("Testing throwing exceptions in monitor_entered"); + testLockThrowEntered(new Monitors.NamedLock("Lock testLockThrowEntered")); + + System.out.println("Testing throwing exceptions in both monitorEnter & MonitorEntered"); + testLockThrowBoth(new Monitors.NamedLock("Lock testLockThrowBoth")); + + // This exposes a difference between the RI and ART. On the RI this test will cause a + // JVMTI_EVENT_MONITOR_WAITED event to be sent even though we threw an exception during the + // JVMTI_EVENT_MONITOR_WAIT. See b/65558434. + System.out.println("Testing throwing exception in MonitorWait event"); + testThrowWait(new Monitors.NamedLock("Lock testThrowWait")); + + System.out.println("Testing throwing exception in MonitorWait event with illegal aruments"); + testThrowIllegalWait(new Monitors.NamedLock("Lock testThrowIllegalWait")); + + System.out.println("Testing throwing exception in MonitorWaited event"); + testThrowWaited(new Monitors.NamedLock("Lock testThrowWaited")); + + System.out.println("Testing throwing exception in MonitorWaited event caused by timeout"); + testThrowWaitedTimeout(new Monitors.NamedLock("Lock testThrowWaitedTimeout")); + + System.out.println("Testing throwing exception in MonitorWaited event caused by interrupt"); + testThrowWaitedInterrupt(new Monitors.NamedLock("Lock testThrowWaitedInterrupt")); + + System.out.println("Testing ObjectMonitorInfo inside of events"); + testMonitorInfoInEvents(new Monitors.NamedLock("Lock testMonitorInfoInEvents")); + + System.out.println("Testing that the monitor can be stolen during the MonitorWaited event."); + testWaitEnterInterleaving(new Monitors.NamedLock("test testWaitEnterInterleaving")); + + // TODO We keep this here since it works on android but it's not clear it's behavior we want to + // support long term or at all. + if (INCLUDE_ANDROID_ONLY_TESTS) { + System.out.println( + "Testing that the monitor can be still held by notifier during the MonitorWaited " + + "event. NB This doesn't work on the RI."); + testWaitExitInterleaving(new Monitors.NamedLock("test testWaitExitInterleaving")); + } + + System.out.println( + "Testing that we can lock and release the monitor in the MonitorWait event"); + testWaitMonitorEnter(new Monitors.NamedLock("test testWaitMonitorEnter")); + + System.out.println( + "Testing that we can lock and release the monitor in the MonitorWaited event"); + testWaitedMonitorEnter(new Monitors.NamedLock("test testWaitedMonitorEnter")); + + System.out.println("Testing we can perform recursive lock in MonitorEntered"); + testRecursiveMontiorEnteredLock(new Monitors.NamedLock("test testRecursiveMontiorEnteredLock")); + + System.out.println("Testing the lock state if MonitorEnter throws in a native method"); + testNativeLockStateThrowEnter(new Monitors.NamedLock("test testNativeLockStateThrowEnter")); + + System.out.println("Testing the lock state if MonitorEntered throws in a native method"); + testNativeLockStateThrowEntered(new Monitors.NamedLock("test testNativeLockStateThrowEntered")); + } + + public static native void doNativeLockPrint(Monitors.NamedLock lk); + public static void printLockState(Monitors.NamedLock lk, Object exception, int res) { + System.out.println( + "MonitorEnter returned: " + res + "\n" + + "Lock state is: " + Monitors.getObjectMonitorUsage(lk)); + printExceptions((Throwable)exception); + } + + public static void testNativeLockStateThrowEnter(final Monitors.NamedLock lk) throws Exception { + final Monitors.LockController controller1 = new Monitors.LockController(lk); + HANDLER = new MonitorHandler() { + @Override public void handleMonitorEnter(Thread t, Object l) { + System.out.println("Unlocking controller1 in MonitorEnter"); + try { + controller1.DoUnlock(); + } catch (Exception e) { + throw new Monitors.TestException("Exception unlocking monitor in MonitorEnter " + l, e); + } + System.out.println("Throwing exception in MonitorEnter"); + throw new Monitors.TestException("throwing exception during monitorEnter of " + l); + } + }; + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + Thread native_thd = new Thread(() -> { + try { + doNativeLockPrint(lk); + } catch (Throwable e) { + System.out.println("Unhandled exception: " + e); + e.printStackTrace(); + } + }, "NativeLockStateThrowEnter thread"); + native_thd.start(); + native_thd.join(); + } + + public static void testNativeLockStateThrowEntered(final Monitors.NamedLock lk) throws Exception { + final Monitors.LockController controller1 = new Monitors.LockController(lk); + HANDLER = new MonitorHandler() { + @Override public void handleMonitorEnter(Thread t, Object l) { + System.out.println("Unlocking controller1 in MonitorEnter"); + try { + controller1.DoUnlock(); + } catch (Exception e) { + throw new Monitors.TestException("Exception unlocking monitor in MonitorEnter " + l, e); + } + } + @Override public void handleMonitorEntered(Thread t, Object l) { + System.out.println("Throwing exception in MonitorEntered"); + throw new Monitors.TestException("throwing exception during monitorEntered of " + l); + } + }; + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + Thread native_thd = new Thread(() -> { + try { + doNativeLockPrint(lk); + } catch (Throwable e) { + System.out.println("Unhandled exception: " + e); + e.printStackTrace(); + } + }, "NativeLockStateThrowEntered thread"); + native_thd.start(); + native_thd.join(); + } + + public static void testRecursiveMontiorEnteredLock(final Monitors.NamedLock lk) throws Exception { + Monitors.LockController controller1 = new Monitors.LockController(lk); + Monitors.LockController controller2 = new Monitors.LockController(lk); + HANDLER = new MonitorHandler() { + @Override public void handleMonitorEntered(Thread thd, Object l) { + try { + System.out.println("In MonitorEntered usage: " + Monitors.getObjectMonitorUsage(lk)); + synchronized (lk) { + System.out.println("In MonitorEntered sync: " + Monitors.getObjectMonitorUsage(lk)); + } + } catch (Exception e) { + throw new Monitors.TestException("error while recursive locking!", e); + } + } + }; + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + controller2.DoLock(); + controller2.waitForContendedSleep(); + controller1.DoUnlock(); + controller2.waitForLockToBeHeld(); + controller2.DoUnlock(); + } + + public static void testWaitedMonitorEnter(final Monitors.NamedLock lk) throws Exception { + final Monitors.LockController controller1 = new Monitors.LockController(lk); + final Monitors.LockController controller2 = new Monitors.LockController(lk); + HANDLER = new MonitorHandler() { + @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) { + try { + // make sure that controller2 has acutally unlocked everything, we can be sent earlier + // than that on ART. + while (controller2.IsLocked()) {} + System.out.println("In waited monitor usage: " + Monitors.getObjectMonitorUsage(lk)); + synchronized (lk) { + System.out.println( + "In waited monitor usage sync: " + Monitors.getObjectMonitorUsage(lk)); + } + } catch (Exception e) { + throw new Monitors.TestException("error while doing unlock in other thread!", e); + } + } + }; + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + controller1.DoWait(); + controller1.waitForNotifySleep(); + controller2.DoLock(); + controller2.waitForLockToBeHeld(); + controller2.DoNotifyAll(); + controller2.DoUnlock(); + controller1.waitForLockToBeHeld(); + controller1.DoUnlock(); + } + + public static void testWaitMonitorEnter(final Monitors.NamedLock lk) throws Exception { + final Monitors.LockController controller1 = new Monitors.LockController(lk); + final Monitors.LockController controller2 = new Monitors.LockController(lk); + HANDLER = new MonitorHandler() { + @Override public void handleMonitorWait(Thread thd, Object l, long timeout) { + try { + System.out.println("In wait monitor usage: " + Monitors.getObjectMonitorUsage(lk)); + synchronized (lk) { + System.out.println("In wait monitor usage sync: " + Monitors.getObjectMonitorUsage(lk)); + } + } catch (Exception e) { + throw new Monitors.TestException("error while doing unlock in other thread!", e); + } + } + }; + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + controller1.DoWait(); + controller1.waitForNotifySleep(); + controller2.DoLock(); + controller2.waitForLockToBeHeld(); + controller2.DoNotifyAll(); + controller2.DoUnlock(); + controller1.waitForLockToBeHeld(); + controller1.DoUnlock(); + } + + // NB This test cannot be run on the RI. It deadlocks. Leaving for documentation. + public static void testWaitExitInterleaving(Monitors.NamedLock lk) throws Exception { + final Monitors.LockController controller1 = new Monitors.LockController(lk); + final Monitors.LockController controller2 = new Monitors.LockController(lk); + HANDLER = new MonitorHandler() { + @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) { + System.out.println("un-locking controller1 in controller2 MonitorWaited event"); + try { + controller1.DoUnlock(); + } catch (Exception e) { + throw new Monitors.TestException("error while doing unlock in other thread!", e); + } + } + }; + controller2.DoLock(); + controller2.waitForLockToBeHeld(); + controller2.DoWait(); + controller2.waitForNotifySleep(); + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + controller1.DoNotifyAll(); + controller2.waitForLockToBeHeld(); + controller2.DoUnlock(); + } + + public static void testWaitEnterInterleaving(Monitors.NamedLock lk) throws Exception { + final Monitors.LockController controller1 = new Monitors.LockController(lk); + final Monitors.LockController controller2 = new Monitors.LockController(lk); + final Monitors.LockController controller3 = new Monitors.LockController(lk); + final Semaphore unlocked_sem = new Semaphore(0); + final Semaphore continue_sem = new Semaphore(0); + HANDLER = new MonitorHandler() { + @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) { + System.out.println("locking controller3 in controller2 MonitorWaited event"); + try { + unlocked_sem.acquire(); + controller3.DoLock(); + controller3.waitForLockToBeHeld(); + System.out.println( + "Controller3 now holds the lock the monitor wait will try to re-acquire"); + continue_sem.release(); + } catch (Exception e) { + throw new Monitors.TestException("error while doing unlock in other thread!", e); + } + } + }; + controller2.DoLock(); + controller2.waitForLockToBeHeld(); + controller2.DoWait(); + controller2.waitForNotifySleep(); + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + controller1.DoNotifyAll(); + controller1.DoUnlock(); + // Wait for controller3 to have locked. + // We cannot use waitForLockToBeHeld since we could race with the HANDLER waitForLockToBeHeld + // function. + unlocked_sem.release(); + continue_sem.acquire(); + controller3.DoUnlock(); + controller2.waitForLockToBeHeld(); + controller2.DoUnlock(); + } + + public static void testMonitorInfoInEvents(Monitors.NamedLock lk) throws Exception { + final Monitors.LockController controller1 = new Monitors.LockController(lk); + final Monitors.LockController controller2 = new Monitors.LockController(lk); + HANDLER = new MonitorHandler() { + @Override public void handleMonitorEnter(Thread thd, Object l) { + System.out.println("Monitor usage in MonitorEnter: " + Monitors.getObjectMonitorUsage(l)); + } + @Override public void handleMonitorEntered(Thread thd, Object l) { + System.out.println("Monitor usage in MonitorEntered: " + Monitors.getObjectMonitorUsage(l)); + } + @Override public void handleMonitorWait(Thread thd, Object l, long timeout) { + System.out.println("Monitor usage in MonitorWait: " + Monitors.getObjectMonitorUsage(l)); + } + @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) { + // make sure that controller1 has acutally unlocked everything, we can be sent earlier than + // that on ART. + while (controller1.IsLocked()) {} + System.out.println("Monitor usage in MonitorWaited: " + Monitors.getObjectMonitorUsage(l)); + } + }; + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + controller2.DoLock(); + controller2.waitForContendedSleep(); + controller1.DoUnlock(); + controller2.waitForLockToBeHeld(); + controller2.DoWait(); + controller2.waitForNotifySleep(); + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + controller1.DoNotifyAll(); + controller1.DoUnlock(); + controller2.waitForLockToBeHeld(); + controller2.DoUnlock(); + } + + public static void testThrowWaitedInterrupt(Monitors.NamedLock lk) throws Exception { + final Monitors.LockController controller1 = new Monitors.LockController(lk); + HANDLER = new MonitorHandler() { + @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) { + System.out.println("Throwing exception in MonitorWaited"); + throw new Monitors.TestException("throwing exception during monitorWaited of " + l); + } + }; + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + try { + controller1.DoWait(); + controller1.waitForNotifySleep(); + controller1.interruptWorker(); + controller1.waitForLockToBeHeld(); + controller1.DoUnlock(); + System.out.println("No Exception thrown!"); + } catch (Monitors.TestException e) { + printExceptions(e); + System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk)); + controller1.DoCleanup(); + } + } + + public static void testThrowWaitedTimeout(Monitors.NamedLock lk) throws Exception { + final Monitors.LockController controller1 = new Monitors.LockController(lk, 5 * 1000); + HANDLER = new MonitorHandler() { + @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) { + System.out.println("Throwing exception in MonitorWaited"); + throw new Monitors.TestException("throwing exception during monitorWaited of " + l); + } + }; + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + try { + controller1.DoTimedWait(); + controller1.waitForNotifySleep(); + controller1.waitForLockToBeHeld(); + controller1.DoUnlock(); + System.out.println("No Exception thrown!"); + } catch (Monitors.TestException e) { + printExceptions(e); + System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk)); + controller1.DoCleanup(); + } + } + + public static void testThrowWaited(Monitors.NamedLock lk) throws Exception { + final Monitors.LockController controller1 = new Monitors.LockController(lk); + final Monitors.LockController controller2 = new Monitors.LockController(lk); + HANDLER = new MonitorHandler() { + @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) { + System.out.println("Throwing exception in MonitorWaited"); + throw new Monitors.TestException("throwing exception during monitorWaited of " + l); + } + }; + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + controller1.DoWait(); + controller1.waitForNotifySleep(); + + controller2.DoLock(); + controller2.waitForLockToBeHeld(); + controller2.DoNotifyAll(); + controller2.DoUnlock(); + try { + controller1.waitForLockToBeHeld(); + controller1.DoUnlock(); + System.out.println("No Exception thrown!"); + } catch (Monitors.TestException e) { + printExceptions(e); + System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk)); + controller1.DoCleanup(); + } + } + + public static void testThrowWait(final Monitors.NamedLock lk) throws Exception { + final Monitors.LockController controller1 = new Monitors.LockController(lk); + HANDLER = new MonitorHandler() { + @Override public void handleMonitorWait(Thread thd, Object l, long timeout) { + System.out.println("Throwing exception in MonitorWait"); + throw new Monitors.TestException("throwing exception during MonitorWait of " + l); + } + }; + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + try { + controller1.DoWait(); + controller1.waitForNotifySleep(); + controller1.waitForLockToBeHeld(); + controller1.DoUnlock(); + System.out.println("No Exception thrown!"); + } catch (Monitors.TestException e) { + printExceptions(e); + System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk)); + controller1.DoCleanup(); + } + } + + public static void testThrowIllegalWait(final Monitors.NamedLock lk) throws Exception { + final Monitors.LockController controller1 = new Monitors.LockController(lk, -100000); + HANDLER = new MonitorHandler() { + @Override public void handleMonitorWait(Thread thd, Object l, long timeout) { + System.out.println("Throwing exception in MonitorWait timeout = " + timeout); + throw new Monitors.TestException("throwing exception during monitorWait of " + l); + } + }; + try { + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + controller1.DoTimedWait(); + controller1.waitForLockToBeHeld(); + controller1.DoUnlock(); + System.out.println("No Exception thrown!"); + } catch (Monitors.TestException e) { + printExceptions(e); + System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk)); + controller1.DoCleanup(); + } + } + + public static void testLockUncontend(final Monitors.NamedLock lk) throws Exception { + final Monitors.LockController controller1 = new Monitors.LockController(lk); + final Monitors.LockController controller2 = new Monitors.LockController(lk); + HANDLER = new MonitorHandler() { + @Override public void handleMonitorEnter(Thread thd, Object lock) { + if (controller1.IsLocked()) { + System.out.println("Releasing " + lk + " during monitorEnter event."); + try { + controller1.DoUnlock(); + } catch (Exception e) { + throw new Error("Unable to unlock controller1", e); + } + } else { + throw new Error("controller1 does not seem to hold the lock!"); + } + } + }; + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + // This will call handleMonitorEnter but will release during the callback. + controller2.DoLock(); + controller2.waitForLockToBeHeld(); + if (controller1.IsLocked()) { + throw new Error("controller1 still holds the lock somehow!"); + } + controller2.DoUnlock(); + } + + public static void testLockThrowEnter(Monitors.NamedLock lk) throws Exception { + Monitors.LockController controller1 = new Monitors.LockController(lk); + Monitors.LockController controller2 = new Monitors.LockController(lk); + HANDLER = new MonitorHandler() { + @Override public void handleMonitorEnter(Thread t, Object l) { + System.out.println("Throwing exception in MonitorEnter"); + throw new Monitors.TestException("throwing exception during monitorEnter of " + l); + } + }; + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + try { + controller2.DoLock(); + controller2.waitForContendedSleep(); + controller1.DoUnlock(); + controller2.waitForLockToBeHeld(); + controller2.DoUnlock(); + System.out.println("Did not get an exception!"); + } catch (Monitors.TestException e) { + printExceptions(e); + System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk)); + controller2.DoCleanup(); + } + } + + public static void testLockThrowEntered(Monitors.NamedLock lk) throws Exception { + Monitors.LockController controller1 = new Monitors.LockController(lk); + Monitors.LockController controller2 = new Monitors.LockController(lk); + HANDLER = new MonitorHandler() { + @Override public void handleMonitorEntered(Thread t, Object l) { + System.out.println("Throwing exception in MonitorEntered"); + throw new Monitors.TestException("throwing exception during monitorEntered of " + l); + } + }; + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + try { + controller2.DoLock(); + controller2.waitForContendedSleep(); + controller1.DoUnlock(); + controller2.waitForLockToBeHeld(); + controller2.DoUnlock(); + System.out.println("Did not get an exception!"); + } catch (Monitors.TestException e) { + printExceptions(e); + System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk)); + controller2.DoCleanup(); + } + } + + public static void testLockThrowBoth(Monitors.NamedLock lk) throws Exception { + Monitors.LockController controller1 = new Monitors.LockController(lk); + Monitors.LockController controller2 = new Monitors.LockController(lk); + HANDLER = new MonitorHandler() { + @Override public void handleMonitorEnter(Thread t, Object l) { + System.out.println("Throwing exception in MonitorEnter"); + throw new Monitors.TestException("throwing exception during monitorEnter of " + l); + } + @Override public void handleMonitorEntered(Thread t, Object l) { + System.out.println("Throwing exception in MonitorEntered"); + throw new Monitors.TestException("throwing exception during monitorEntered of " + l); + } + }; + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + try { + controller2.DoLock(); + controller2.waitForContendedSleep(); + controller1.DoUnlock(); + controller2.waitForLockToBeHeld(); + controller2.DoUnlock(); + System.out.println("Did not get an exception!"); + } catch (Monitors.TestException e) { + printExceptions(e); + System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk)); + controller2.DoCleanup(); + } + } + + public static void printExceptions(Throwable t) { + System.out.println("Caught exception: " + t); + for (Throwable c = t.getCause(); c != null; c = c.getCause()) { + System.out.println("\tCaused by: " + + (Test1932.class.getPackage().equals(c.getClass().getPackage()) + ? c.toString() : c.getClass().toString())); + } + if (PRINT_FULL_STACK_TRACE) { + t.printStackTrace(); + } + } + + public static void handleMonitorEnter(Thread thd, Object lock) { + System.out.println(thd.getName() + " contended-LOCKING " + lock); + if (HANDLER != null) { + HANDLER.handleMonitorEnter(thd, lock); + } + } + + public static void handleMonitorEntered(Thread thd, Object lock) { + System.out.println(thd.getName() + " LOCKED " + lock); + if (HANDLER != null) { + HANDLER.handleMonitorEntered(thd, lock); + } + } + public static void handleMonitorWait(Thread thd, Object lock, long timeout) { + System.out.println(thd.getName() + " start-monitor-wait " + lock + " timeout: " + timeout); + if (HANDLER != null) { + HANDLER.handleMonitorWait(thd, lock, timeout); + } + } + + public static void handleMonitorWaited(Thread thd, Object lock, boolean timed_out) { + System.out.println(thd.getName() + " monitor-waited " + lock + " timed_out: " + timed_out); + if (HANDLER != null) { + HANDLER.handleMonitorWaited(thd, lock, timed_out); + } + } +} diff --git a/test/1933-monitor-current-contended/expected.txt b/test/1933-monitor-current-contended/expected.txt new file mode 100644 index 0000000000..d27c21c115 --- /dev/null +++ b/test/1933-monitor-current-contended/expected.txt @@ -0,0 +1,6 @@ +No contention +current thread is contending for monitor: null +Normal contended monitor +c2 is contending for monitor: NamedLock[test testNormalContendedMonitor] +Waiting on a monitor +c1 is contending for monitor: NamedLock[test testNormalWaitMonitor] diff --git a/test/1933-monitor-current-contended/info.txt b/test/1933-monitor-current-contended/info.txt new file mode 100644 index 0000000000..674ef56c70 --- /dev/null +++ b/test/1933-monitor-current-contended/info.txt @@ -0,0 +1,4 @@ +Tests jvmti monitor events in odd situations. + +Checks that the JVMTI monitor events are correctly dispatched and handled for +many odd situations. diff --git a/test/1933-monitor-current-contended/run b/test/1933-monitor-current-contended/run new file mode 100755 index 0000000000..e92b873956 --- /dev/null +++ b/test/1933-monitor-current-contended/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2017 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. + +./default-run "$@" --jvmti diff --git a/test/1933-monitor-current-contended/src/Main.java b/test/1933-monitor-current-contended/src/Main.java new file mode 100644 index 0000000000..3f2bbcd3a7 --- /dev/null +++ b/test/1933-monitor-current-contended/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 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. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1933.run(); + } +} diff --git a/test/1933-monitor-current-contended/src/art/Monitors.java b/test/1933-monitor-current-contended/src/art/Monitors.java new file mode 100644 index 0000000000..b28a3ee035 --- /dev/null +++ b/test/1933-monitor-current-contended/src/art/Monitors.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.*; +import java.util.function.Function; +import java.util.stream.Stream; +import java.util.Arrays; +import java.util.Objects; + +public class Monitors { + public native static void setupMonitorEvents( + Class<?> method_klass, + Method monitor_contended_enter_event, + Method monitor_contended_entered_event, + Method monitor_wait_event, + Method monitor_waited_event, + Class<?> lock_klass, + Thread thr); + public native static void stopMonitorEvents(); + + public static class NamedLock { + public final String name; + public NamedLock(String name) { + this.name = name; + } + public String toString() { + return String.format("NamedLock[%s]", name); + } + } + + public static final class MonitorUsage { + public final Object monitor; + public final Thread owner; + public final int entryCount; + public final Thread[] waiters; + public final Thread[] notifyWaiters; + + public MonitorUsage( + Object monitor, + Thread owner, + int entryCount, + Thread[] waiters, + Thread[] notifyWaiters) { + this.monitor = monitor; + this.entryCount = entryCount; + this.owner = owner; + this.waiters = waiters; + this.notifyWaiters = notifyWaiters; + } + + private static String toNameList(Thread[] ts) { + return Arrays.toString(Arrays.stream(ts).map((Thread t) -> t.getName()).toArray()); + } + + public String toString() { + return String.format( + "MonitorUsage{ monitor: %s, owner: %s, entryCount: %d, waiters: %s, notify_waiters: %s }", + monitor, + (owner != null) ? owner.getName() : "<NULL>", + entryCount, + toNameList(waiters), + toNameList(notifyWaiters)); + } + } + + public static native MonitorUsage getObjectMonitorUsage(Object monitor); + public static native Object getCurrentContendedMonitor(Thread thr); + + public static class TestException extends Error { + public TestException() { super(); } + public TestException(String s) { super(s); } + public TestException(String s, Throwable c) { super(s, c); } + } + + public static class LockController { + private static enum Action { HOLD, RELEASE, NOTIFY, NOTIFY_ALL, WAIT, TIMED_WAIT } + + public final Object lock; + public final long timeout; + private final AtomicStampedReference<Action> action; + private volatile Thread runner = null; + private volatile boolean started = false; + private volatile boolean held = false; + private static final AtomicInteger cnt = new AtomicInteger(0); + private volatile Throwable exe; + + public LockController(Object lock) { + this(lock, 10 * 1000); + } + public LockController(Object lock, long timeout) { + this.lock = lock; + this.timeout = timeout; + this.action = new AtomicStampedReference(Action.HOLD, 0); + this.exe = null; + } + + public boolean IsWorkerThread(Thread thd) { + return Objects.equals(runner, thd); + } + + public boolean IsLocked() { + checkException(); + return held; + } + + public void checkException() { + if (exe != null) { + throw new TestException("Exception thrown by other thread!", exe); + } + } + + private void setAction(Action a) { + int stamp = action.getStamp(); + // Wait for it to be HOLD before updating. + while (!action.compareAndSet(Action.HOLD, a, stamp, stamp + 1)) { + stamp = action.getStamp(); + } + } + + public synchronized void suspendWorker() throws Exception { + checkException(); + if (runner == null) { + throw new TestException("We don't have any runner holding " + lock); + } + Suspension.suspend(runner); + } + + public Object getWorkerContendedMonitor() throws Exception { + checkException(); + if (runner == null) { + return null; + } + return getCurrentContendedMonitor(runner); + } + + public synchronized void DoLock() { + if (IsLocked()) { + throw new Error("lock is already acquired or being acquired."); + } + if (runner != null) { + throw new Error("Already have thread!"); + } + runner = new Thread(() -> { + started = true; + try { + synchronized (lock) { + held = true; + int[] stamp_h = new int[] { -1 }; + Action cur_action = Action.HOLD; + try { + while (true) { + cur_action = action.get(stamp_h); + int stamp = stamp_h[0]; + if (cur_action == Action.RELEASE) { + // The other thread will deal with reseting action. + break; + } + try { + switch (cur_action) { + case HOLD: + Thread.yield(); + break; + case NOTIFY: + lock.notify(); + break; + case NOTIFY_ALL: + lock.notifyAll(); + break; + case TIMED_WAIT: + lock.wait(timeout); + break; + case WAIT: + lock.wait(); + break; + default: + throw new Error("Unknown action " + action); + } + } finally { + // reset action back to hold if it isn't something else. + action.compareAndSet(cur_action, Action.HOLD, stamp, stamp+1); + } + } + } catch (Exception e) { + throw new TestException("Got an error while performing action " + cur_action, e); + } + } + } finally { + held = false; + started = false; + } + }, "Locker thread " + cnt.getAndIncrement() + " for " + lock); + // Make sure we can get any exceptions this throws. + runner.setUncaughtExceptionHandler((t, e) -> { exe = e; }); + runner.start(); + } + + public void waitForLockToBeHeld() throws Exception { + while (true) { + if (IsLocked() && Objects.equals(runner, Monitors.getObjectMonitorUsage(lock).owner)) { + return; + } + } + } + + public synchronized void waitForNotifySleep() throws Exception { + if (runner == null) { + throw new Error("No thread trying to lock!"); + } + do { + checkException(); + } while (!started || + !Arrays.asList(Monitors.getObjectMonitorUsage(lock).notifyWaiters).contains(runner)); + } + + public synchronized void waitForContendedSleep() throws Exception { + if (runner == null) { + throw new Error("No thread trying to lock!"); + } + do { + checkException(); + } while (!started || + runner.getState() != Thread.State.BLOCKED || + !Arrays.asList(Monitors.getObjectMonitorUsage(lock).waiters).contains(runner)); + } + + public synchronized void DoNotify() { + if (!IsLocked()) { + throw new Error("Not locked"); + } + setAction(Action.NOTIFY); + } + + public synchronized void DoNotifyAll() { + if (!IsLocked()) { + throw new Error("Not locked"); + } + setAction(Action.NOTIFY_ALL); + } + + public synchronized void DoTimedWait() throws Exception { + if (!IsLocked()) { + throw new Error("Not locked"); + } + setAction(Action.TIMED_WAIT); + } + + public synchronized void DoWait() throws Exception { + if (!IsLocked()) { + throw new Error("Not locked"); + } + setAction(Action.WAIT); + } + + public synchronized void interruptWorker() throws Exception { + if (!IsLocked()) { + throw new Error("Not locked"); + } + runner.interrupt(); + } + + public synchronized void waitForActionToFinish() throws Exception { + checkException(); + while (action.getReference() != Action.HOLD) { checkException(); } + } + + public synchronized void DoUnlock() throws Exception { + Error throwing = null; + if (!IsLocked()) { + // We might just be racing some exception that was thrown by the worker thread. Cache the + // exception, we will throw one from the worker before this one. + throwing = new Error("Not locked!"); + } + setAction(Action.RELEASE); + Thread run = runner; + runner = null; + while (held) {} + run.join(); + action.set(Action.HOLD, 0); + // Make sure to throw any exception that occurred since it might not have unlocked due to our + // request. + checkException(); + DoCleanup(); + if (throwing != null) { + throw throwing; + } + } + + public synchronized void DoCleanup() throws Exception { + if (runner != null) { + Thread run = runner; + runner = null; + while (held) {} + run.join(); + } + action.set(Action.HOLD, 0); + exe = null; + } + } +} + diff --git a/test/1933-monitor-current-contended/src/art/Suspension.java b/test/1933-monitor-current-contended/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1933-monitor-current-contended/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1933-monitor-current-contended/src/art/Test1933.java b/test/1933-monitor-current-contended/src/art/Test1933.java new file mode 100644 index 0000000000..e21c395196 --- /dev/null +++ b/test/1933-monitor-current-contended/src/art/Test1933.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +public class Test1933 { + public static void run() throws Exception { + System.out.println("No contention"); + testNoContention(new Monitors.NamedLock("test testNoContention")); + + System.out.println("Normal contended monitor"); + testNormalContendedMonitor(new Monitors.NamedLock("test testNormalContendedMonitor")); + + System.out.println("Waiting on a monitor"); + testNormalWaitMonitor(new Monitors.NamedLock("test testNormalWaitMonitor")); + } + + public static void testNormalWaitMonitor(final Monitors.NamedLock lk) throws Exception { + final Monitors.LockController controller1 = new Monitors.LockController(lk); + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + controller1.DoWait(); + controller1.waitForNotifySleep(); + System.out.println("c1 is contending for monitor: " + controller1.getWorkerContendedMonitor()); + synchronized (lk) { + lk.notifyAll(); + } + controller1.DoUnlock(); + } + + public static void testNormalContendedMonitor(final Monitors.NamedLock lk) throws Exception { + final Monitors.LockController controller1 = new Monitors.LockController(lk); + final Monitors.LockController controller2 = new Monitors.LockController(lk); + controller1.DoLock(); + controller1.waitForLockToBeHeld(); + controller2.DoLock(); + controller2.waitForContendedSleep(); + System.out.println("c2 is contending for monitor: " + controller2.getWorkerContendedMonitor()); + controller1.DoUnlock(); + controller2.waitForLockToBeHeld(); + controller2.DoUnlock(); + } + + public static void testNoContention(final Monitors.NamedLock lk) throws Exception { + synchronized (lk) { + System.out.println("current thread is contending for monitor: " + + Monitors.getCurrentContendedMonitor(null)); + } + } +} diff --git a/test/478-checker-clinit-check-pruning/src/Main.java b/test/478-checker-clinit-check-pruning/src/Main.java index 63e2b9503c..752e761f1f 100644 --- a/test/478-checker-clinit-check-pruning/src/Main.java +++ b/test/478-checker-clinit-check-pruning/src/Main.java @@ -529,7 +529,7 @@ public class Main { // TODO: Add a test for the case of a static method whose declaring // class type index is not available (i.e. when `storage_index` - // equals `DexFile::kDexNoIndex` in + // equals `dex::kDexNoIndex` in // art::HGraphBuilder::BuildInvoke). public static void main(String[] args) { diff --git a/test/530-checker-lse-ctor-fences/smali/Smali.smali b/test/530-checker-lse-ctor-fences/smali/Smali.smali new file mode 100644 index 0000000000..856af96a9b --- /dev/null +++ b/test/530-checker-lse-ctor-fences/smali/Smali.smali @@ -0,0 +1,97 @@ +# Copyright (C) 2017 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. + +.class public LSmali; +.super Ljava/lang/Object; + +## CHECK-START: double Smali.calcCircleAreaOrCircumference(double, boolean) load_store_elimination (before) +## CHECK: NewInstance +## CHECK: InstanceFieldSet +## CHECK: ConstructorFence +## CHECK: InstanceFieldGet + +## CHECK-START: double Smali.calcCircleAreaOrCircumference(double, boolean) load_store_elimination (after) +## CHECK: NewInstance +## CHECK-NOT: ConstructorFence + +# The object allocation will not be eliminated by LSE because of aliased stores. +# However the object is still a singleton, so it never escapes the current thread. +# There should not be a constructor fence here after LSE. + +.method public static calcCircleAreaOrCircumference(DZ)D + .registers 7 + + # CalcCircleAreaOrCircumference calc = + # new CalcCircleAreaOrCircumference( + # area_or_circumference ? CalcCircleAreaOrCircumference.TYPE_AREA : + # CalcCircleAreaOrCircumference.TYPE_CIRCUMFERENCE); + + # if (area_or_circumference) { + # // Area + # calc.value = Math.PI * Math.PI * radius; + # } else { + # // Circumference + # calc.value = 2 * Math.PI * radius; + # } + + # Please note that D8 would merge the iput togother which looks like : + + # if (area_or_circumference) { + # // Area + # tmp = Math.PI * Math.PI * radius; + # } else { + # // Circumference + # tmp = 2 * Math.PI * radius; + # } + # calc.value = tmp; + + # which makes the LSE valid and defeat the purpose of this test. + + new-instance v0, LCalcCircleAreaOrCircumference; + + if-eqz p2, :cond_15 + + const/4 v1, 0x0 + + :goto_5 + invoke-direct {v0, v1}, LCalcCircleAreaOrCircumference;-><init>(I)V + + if-eqz p2, :cond_17 + + const-wide v2, 0x4023bd3cc9be45deL # 9.869604401089358 + + mul-double/2addr v2, p0 + + iput-wide v2, v0, LCalcCircleAreaOrCircumference;->value:D + + :goto_12 + iget-wide v2, v0, LCalcCircleAreaOrCircumference;->value:D + + return-wide v2 + + :cond_15 + const/4 v1, 0x1 + + goto :goto_5 + + :cond_17 + const-wide v2, 0x401921fb54442d18L # 6.283185307179586 + + mul-double/2addr v2, p0 + + iput-wide v2, v0, LCalcCircleAreaOrCircumference;->value:D + + goto :goto_12 +.end method + diff --git a/test/530-checker-lse-ctor-fences/src/Main.java b/test/530-checker-lse-ctor-fences/src/Main.java index 7755875b65..c3796eaa51 100644 --- a/test/530-checker-lse-ctor-fences/src/Main.java +++ b/test/530-checker-lse-ctor-fences/src/Main.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import java.lang.reflect.Method; // This base class has a single final field; // the constructor should have one fence. @@ -112,14 +113,7 @@ public class Main { return new Ellipse(vertex, covertex).getArea(); } - /// CHECK-START: double Main.calcCircleAreaOrCircumference(double, boolean) load_store_elimination (before) - /// CHECK: NewInstance - /// CHECK: InstanceFieldSet - /// CHECK: ConstructorFence - /// CHECK: InstanceFieldGet - /// CHECK-START: double Main.calcCircleAreaOrCircumference(double, boolean) load_store_elimination (after) - /// CHECK: NewInstance /// CHECK-NOT: ConstructorFence // @@ -143,6 +137,16 @@ public class Main { return calc.value; } + static double calcCircleAreaOrCircumferenceSmali(double radius, boolean area_or_circumference) { + try { + Class<?> c = Class.forName("Smali"); + Method m = c.getMethod("calcCircleAreaOrCircumference", double.class, boolean.class); + return (Double) m.invoke(null, radius, area_or_circumference); + } catch (Exception ex) { + throw new Error(ex); + } + } + /// CHECK-START: Circle Main.makeCircle(double) load_store_elimination (after) /// CHECK: NewInstance /// CHECK: ConstructorFence @@ -184,6 +188,7 @@ public class Main { assertDoubleEquals(Math.PI * Math.PI * Math.PI, calcCircleArea(Math.PI)); assertDoubleEquals(Math.PI * Math.PI * Math.PI, calcEllipseArea(Math.PI, Math.PI)); assertDoubleEquals(2 * Math.PI * Math.PI, calcCircleAreaOrCircumference(Math.PI, false)); + assertDoubleEquals(2 * Math.PI * Math.PI, calcCircleAreaOrCircumferenceSmali(Math.PI, false)); assertInstanceOf(makeCircle(Math.PI), Circle.class); } diff --git a/test/567-checker-compare/smali/Smali.smali b/test/567-checker-compare/smali/Smali.smali new file mode 100644 index 0000000000..8fc39f1acd --- /dev/null +++ b/test/567-checker-compare/smali/Smali.smali @@ -0,0 +1,92 @@ +# Copyright (C) 2017 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. + +.class public LSmali; +.super Ljava/lang/Object; + +## CHECK-START: int Smali.compareBooleans(boolean, boolean) intrinsics_recognition (after) +## CHECK-DAG: <<Method:[ij]\d+>> CurrentMethod +## CHECK-DAG: <<Zero:i\d+>> IntConstant 0 +## CHECK-DAG: <<One:i\d+>> IntConstant 1 +## CHECK-DAG: <<PhiX:i\d+>> Phi [<<One>>,<<Zero>>] +## CHECK-DAG: <<PhiY:i\d+>> Phi [<<One>>,<<Zero>>] +## CHECK-DAG: <<Result:i\d+>> InvokeStaticOrDirect [<<PhiX>>,<<PhiY>>,<<Method>>] intrinsic:IntegerCompare +## CHECK-DAG: Return [<<Result>>] + +## CHECK-START: int Smali.compareBooleans(boolean, boolean) instruction_simplifier (after) +## CHECK-DAG: <<Zero:i\d+>> IntConstant 0 +## CHECK-DAG: <<One:i\d+>> IntConstant 1 +## CHECK-DAG: <<PhiX:i\d+>> Phi [<<One>>,<<Zero>>] +## CHECK-DAG: <<PhiY:i\d+>> Phi [<<One>>,<<Zero>>] +## CHECK-DAG: <<Result:i\d+>> Compare [<<PhiX>>,<<PhiY>>] +## CHECK-DAG: Return [<<Result>>] + +## CHECK-START: int Smali.compareBooleans(boolean, boolean) instruction_simplifier (after) +## CHECK-NOT: InvokeStaticOrDirect + +## CHECK-START: int Smali.compareBooleans(boolean, boolean) select_generator (after) +## CHECK: <<ArgX:z\d+>> ParameterValue +## CHECK: <<ArgY:z\d+>> ParameterValue +## CHECK-DAG: <<Zero:i\d+>> IntConstant 0 +## CHECK-DAG: <<One:i\d+>> IntConstant 1 +## CHECK-DAG: <<SelX:i\d+>> Select [<<Zero>>,<<One>>,<<ArgX>>] +## CHECK-DAG: <<SelY:i\d+>> Select [<<Zero>>,<<One>>,<<ArgY>>] +## CHECK-DAG: <<Result:i\d+>> Compare [<<SelX>>,<<SelY>>] +## CHECK-DAG: Return [<<Result>>] + +## CHECK-START: int Smali.compareBooleans(boolean, boolean) select_generator (after) +## CHECK-NOT: Phi + +## CHECK-START: int Smali.compareBooleans(boolean, boolean) instruction_simplifier$after_bce (after) +## CHECK: <<ArgX:z\d+>> ParameterValue +## CHECK: <<ArgY:z\d+>> ParameterValue +## CHECK-DAG: <<Result:i\d+>> Compare [<<ArgX>>,<<ArgY>>] +## CHECK-DAG: Return [<<Result>>] + +## CHECK-START: int Smali.compareBooleans(boolean, boolean) instruction_simplifier$after_bce (after) +## CHECK-NOT: Select + +# Note: This test has been written in smali (in addition to the source version) because Dexers +# such as D8 can perform the same type of intrinsic replacements. +.method public static compareBooleans(ZZ)I + # return Integer.compare((x ? 1 : 0), (y ? 1 : 0)); + .registers 5 + const/4 v0, 0x1 + + const/4 v1, 0x0 + + if-eqz p0, :cond_c + + move v2, v0 + + :goto_5 + if-eqz p1, :cond_e + + :goto_7 + invoke-static {v2, v0}, Ljava/lang/Integer;->compare(II)I + + move-result v0 + + return v0 + + :cond_c + move v2, v1 + + goto :goto_5 + + :cond_e + move v0, v1 + + goto :goto_7 +.end method diff --git a/test/567-checker-compare/src/Main.java b/test/567-checker-compare/src/Main.java index a05bb60fad..abfaf9f146 100644 --- a/test/567-checker-compare/src/Main.java +++ b/test/567-checker-compare/src/Main.java @@ -14,6 +14,8 @@ * limitations under the License. */ +import java.lang.reflect.Method; + public class Main { public static boolean doThrow = false; @@ -42,36 +44,6 @@ public class Main { } } - /// CHECK-START: int Main.compareBooleans(boolean, boolean) intrinsics_recognition (after) - /// CHECK-DAG: <<Method:[ij]\d+>> CurrentMethod - /// CHECK-DAG: <<Zero:i\d+>> IntConstant 0 - /// CHECK-DAG: <<One:i\d+>> IntConstant 1 - /// CHECK-DAG: <<PhiX:i\d+>> Phi [<<One>>,<<Zero>>] - /// CHECK-DAG: <<PhiY:i\d+>> Phi [<<One>>,<<Zero>>] - /// CHECK-DAG: <<Result:i\d+>> InvokeStaticOrDirect [<<PhiX>>,<<PhiY>>,<<Method>>] intrinsic:IntegerCompare - /// CHECK-DAG: Return [<<Result>>] - - /// CHECK-START: int Main.compareBooleans(boolean, boolean) instruction_simplifier (after) - /// CHECK-DAG: <<Zero:i\d+>> IntConstant 0 - /// CHECK-DAG: <<One:i\d+>> IntConstant 1 - /// CHECK-DAG: <<PhiX:i\d+>> Phi [<<One>>,<<Zero>>] - /// CHECK-DAG: <<PhiY:i\d+>> Phi [<<One>>,<<Zero>>] - /// CHECK-DAG: <<Result:i\d+>> Compare [<<PhiX>>,<<PhiY>>] - /// CHECK-DAG: Return [<<Result>>] - - /// CHECK-START: int Main.compareBooleans(boolean, boolean) instruction_simplifier (after) - /// CHECK-NOT: InvokeStaticOrDirect - - /// CHECK-START: int Main.compareBooleans(boolean, boolean) select_generator (after) - /// CHECK: <<ArgX:z\d+>> ParameterValue - /// CHECK: <<ArgY:z\d+>> ParameterValue - /// CHECK-DAG: <<Zero:i\d+>> IntConstant 0 - /// CHECK-DAG: <<One:i\d+>> IntConstant 1 - /// CHECK-DAG: <<SelX:i\d+>> Select [<<Zero>>,<<One>>,<<ArgX>>] - /// CHECK-DAG: <<SelY:i\d+>> Select [<<Zero>>,<<One>>,<<ArgY>>] - /// CHECK-DAG: <<Result:i\d+>> Compare [<<SelX>>,<<SelY>>] - /// CHECK-DAG: Return [<<Result>>] - /// CHECK-START: int Main.compareBooleans(boolean, boolean) select_generator (after) /// CHECK-NOT: Phi @@ -88,6 +60,12 @@ public class Main { return Integer.compare((x ? 1 : 0), (y ? 1 : 0)); } + private static int compareBooleansSmali(boolean x, boolean y) throws Exception { + Class<?> c = Class.forName("Smali"); + Method m = c.getMethod("compareBooleans", boolean.class, boolean.class); + return (Integer) m.invoke(null, x, y); + } + /// CHECK-START: int Main.compareBytes(byte, byte) intrinsics_recognition (after) /// CHECK-DAG: <<Result:i\d+>> InvokeStaticOrDirect intrinsic:IntegerCompare /// CHECK-DAG: Return [<<Result>>] @@ -348,13 +326,17 @@ public class Main { } - public static void testCompareBooleans() { + public static void testCompareBooleans() throws Exception { expectEquals(-1, compareBooleans(false, true)); + expectEquals(-1, compareBooleansSmali(false, true)); expectEquals(0, compareBooleans(false, false)); expectEquals(0, compareBooleans(true, true)); + expectEquals(0, compareBooleansSmali(false, false)); + expectEquals(0, compareBooleansSmali(true, true)); expectEquals(1, compareBooleans(true, false)); + expectEquals(1, compareBooleansSmali(true, false)); } public static void testCompareBytes() { @@ -915,7 +897,7 @@ public class Main { } - public static void main(String args[]) { + public static void main(String args[]) throws Exception { $opt$noinline$testReplaceInputWithItself(42); testCompareBooleans(); diff --git a/test/656-checker-simd-opt/smali/Smali.smali b/test/656-checker-simd-opt/smali/Smali.smali new file mode 100644 index 0000000000..802bcab773 --- /dev/null +++ b/test/656-checker-simd-opt/smali/Smali.smali @@ -0,0 +1,121 @@ +# Copyright (C) 2017 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. + +.class public LSmali; +.super Ljava/lang/Object; + +## CHECK-START: void Smali.stencilSubInt(int[], int[], int) loop_optimization (before) +## CHECK-DAG: <<PAR3:i\d+>> ParameterValue loop:none +## CHECK-DAG: <<CP1:i\d+>> IntConstant 1 loop:none +## CHECK-DAG: <<Sub1:i\d+>> Sub [<<PAR3>>,<<CP1>>] loop:none +## CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none +## CHECK-DAG: <<Sub2:i\d+>> Sub [<<Phi>>,<<CP1>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Get1:i\d+>> ArrayGet [{{l\d+}},<<Sub2>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Get2:i\d+>> ArrayGet [{{l\d+}},<<Phi>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Add1:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Add2:i\d+>> Add [<<Phi>>,<<CP1>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Get3:i\d+>> ArrayGet [{{l\d+}},<<Add2>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Add3:i\d+>> Add [<<Add1>>,<<Get3>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Add3>>] loop:<<Loop>> outer_loop:none + +## CHECK-START-ARM64: void Smali.stencilSubInt(int[], int[], int) loop_optimization (after) +## CHECK-DAG: <<CP1:i\d+>> IntConstant 1 loop:none +## CHECK-DAG: <<CP2:i\d+>> IntConstant 2 loop:none +## CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none +## CHECK-DAG: <<Add1:i\d+>> Add [<<Phi>>,<<CP1>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Get1:d\d+>> VecLoad [{{l\d+}},<<Phi>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Get2:d\d+>> VecLoad [{{l\d+}},<<Add1>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Add2:d\d+>> VecAdd [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Add3:i\d+>> Add [<<Phi>>,<<CP2>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Get3:d\d+>> VecLoad [{{l\d+}},<<Add3>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Add4:d\d+>> VecAdd [<<Add2>>,<<Get3>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: VecStore [{{l\d+}},<<Add1>>,<<Add4>>] loop:<<Loop>> outer_loop:none +.method public static stencilSubInt([I[II)V + .registers 7 + + const/4 v0, 0x1 + + move v1, v0 + + :goto_2 + sub-int v2, p2, v0 + + if-ge v1, v2, :cond_17 + + sub-int v2, v1, v0 + aget v2, p1, v2 + aget v3, p1, v1 + add-int/2addr v2, v3 + add-int v3, v1, v0 + aget v3, p1, v3 + add-int/2addr v2, v3 + aput v2, p0, v1 + add-int/lit8 v1, v1, 0x1 + + goto :goto_2 + + :cond_17 + return-void +.end method + +## CHECK-START: void Smali.stencilAddInt(int[], int[], int) loop_optimization (before) +## CHECK-DAG: <<CP1:i\d+>> IntConstant 1 loop:none +## CHECK-DAG: <<CM1:i\d+>> IntConstant -1 loop:none +## CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none +## CHECK-DAG: <<Add1:i\d+>> Add [<<Phi>>,<<CM1>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Get1:i\d+>> ArrayGet [{{l\d+}},<<Add1>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Get2:i\d+>> ArrayGet [{{l\d+}},<<Phi>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Add2:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Add3:i\d+>> Add [<<Phi>>,<<CP1>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Get3:i\d+>> ArrayGet [{{l\d+}},<<Add3>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Add4:i\d+>> Add [<<Add2>>,<<Get3>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Add4>>] loop:<<Loop>> outer_loop:none + +## CHECK-START-ARM64: void Smali.stencilAddInt(int[], int[], int) loop_optimization (after) +## CHECK-DAG: <<CP1:i\d+>> IntConstant 1 loop:none +## CHECK-DAG: <<CP2:i\d+>> IntConstant 2 loop:none +## CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none +## CHECK-DAG: <<Add1:i\d+>> Add [<<Phi>>,<<CP1>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Get1:d\d+>> VecLoad [{{l\d+}},<<Phi>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Get2:d\d+>> VecLoad [{{l\d+}},<<Add1>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Add2:d\d+>> VecAdd [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Add3:i\d+>> Add [<<Phi>>,<<CP2>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Get3:d\d+>> VecLoad [{{l\d+}},<<Add3>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: <<Add4:d\d+>> VecAdd [<<Add2>>,<<Get3>>] loop:<<Loop>> outer_loop:none +## CHECK-DAG: VecStore [{{l\d+}},<<Add1>>,<<Add4>>] loop:<<Loop>> outer_loop:none +.method public static stencilAddInt([I[II)V + .registers 6 + + const/4 v0, 0x1 + + :goto_1 + add-int/lit8 v1, p2, -0x1 + + if-ge v0, v1, :cond_16 + + add-int/lit8 v1, v0, -0x1 + aget v1, p1, v1 + aget v2, p1, v0 + add-int/2addr v1, v2 + add-int/lit8 v2, v0, 0x1 + aget v2, p1, v2 + add-int/2addr v1, v2 + aput v1, p0, v0 + add-int/lit8 v0, v0, 0x1 + + goto :goto_1 + + :cond_16 + return-void +.end method diff --git a/test/656-checker-simd-opt/src/Main.java b/test/656-checker-simd-opt/src/Main.java index 794c9b6c0d..091633ff34 100644 --- a/test/656-checker-simd-opt/src/Main.java +++ b/test/656-checker-simd-opt/src/Main.java @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import java.lang.reflect.Array; +import java.lang.reflect.Method; /** * Tests for SIMD related optimizations. @@ -46,19 +48,6 @@ public class Main { } } - /// CHECK-START: void Main.stencil(int[], int[], int) loop_optimization (before) - /// CHECK-DAG: <<CP1:i\d+>> IntConstant 1 loop:none - /// CHECK-DAG: <<CM1:i\d+>> IntConstant -1 loop:none - /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none - /// CHECK-DAG: <<Add1:i\d+>> Add [<<Phi>>,<<CM1>>] loop:<<Loop>> outer_loop:none - /// CHECK-DAG: <<Get1:i\d+>> ArrayGet [{{l\d+}},<<Add1>>] loop:<<Loop>> outer_loop:none - /// CHECK-DAG: <<Get2:i\d+>> ArrayGet [{{l\d+}},<<Phi>>] loop:<<Loop>> outer_loop:none - /// CHECK-DAG: <<Add2:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none - /// CHECK-DAG: <<Add3:i\d+>> Add [<<Phi>>,<<CP1>>] loop:<<Loop>> outer_loop:none - /// CHECK-DAG: <<Get3:i\d+>> ArrayGet [{{l\d+}},<<Add3>>] loop:<<Loop>> outer_loop:none - /// CHECK-DAG: <<Add4:i\d+>> Add [<<Add2>>,<<Get3>>] loop:<<Loop>> outer_loop:none - /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Add4>>] loop:<<Loop>> outer_loop:none - // /// CHECK-START-ARM64: void Main.stencil(int[], int[], int) loop_optimization (after) /// CHECK-DAG: <<CP1:i\d+>> IntConstant 1 loop:none /// CHECK-DAG: <<CP2:i\d+>> IntConstant 2 loop:none @@ -77,6 +66,32 @@ public class Main { } } + private static void stencilAddInt(int[] a, int[] b, int n) { + try { + Class<?> c = Class.forName("Smali"); + Method m = c.getMethod("stencilAddInt", + Array.newInstance(int.class, 1).getClass(), + Array.newInstance(int.class, 1).getClass(), + int.class); + m.invoke(null, a, b, n); + } catch (Exception ex) { + throw new Error(ex); + } + } + + private static void stencilSubInt(int[] a, int[] b, int n) { + try { + Class<?> c = Class.forName("Smali"); + Method m = c.getMethod("stencilSubInt", + Array.newInstance(int.class, 1).getClass(), + Array.newInstance(int.class, 1).getClass(), + int.class); + m.invoke(null, a, b, n); + } catch (Exception ex) { + throw new Error(ex); + } + } + public static void main(String[] args) { float[] x = new float[100]; float[] y = new float[100]; @@ -89,17 +104,47 @@ public class Main { expectEquals(5.0f, x[i]); expectEquals(2.0f, y[i]); } - int[] a = new int[100]; - int[] b = new int[100]; - for (int i = 0; i < 100; i++) { - a[i] = 0; - b[i] = i; + { + int[] a = new int[100]; + int[] b = new int[100]; + for (int i = 0; i < 100; i++) { + a[i] = 0; + b[i] = i; + } + stencil(a, b, 100); + for (int i = 1; i < 99; i++) { + int e = i + i + i; + expectEquals(e, a[i]); + expectEquals(i, b[i]); + } + } + { + int[] a = new int[100]; + int[] b = new int[100]; + for (int i = 0; i < 100; i++) { + a[i] = 0; + b[i] = i; + } + stencilSubInt(a, b, 100); + for (int i = 1; i < 99; i++) { + int e = i + i + i; + expectEquals(e, a[i]); + expectEquals(i, b[i]); + } } - stencil(a, b, 100); - for (int i = 1; i < 99; i++) { - int e = i + i + i; - expectEquals(e, a[i]); - expectEquals(i, b[i]); + { + int[] a = new int[100]; + int[] b = new int[100]; + for (int i = 0; i < 100; i++) { + a[i] = 0; + b[i] = i; + } + stencilAddInt(a, b, 100); + for (int i = 1; i < 99; i++) { + int e = i + i + i; + expectEquals(e, a[i]); + expectEquals(i, b[i]); + } } System.out.println("passed"); } diff --git a/test/661-checker-simd-reduc/src/Main.java b/test/661-checker-simd-reduc/src/Main.java index 8208a9e7b5..71eb3cde9c 100644 --- a/test/661-checker-simd-reduc/src/Main.java +++ b/test/661-checker-simd-reduc/src/Main.java @@ -59,6 +59,7 @@ public class Main { /// CHECK-DAG: <<Get:i\d+>> ArrayGet [{{l\d+}},<<Phi1>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: Add [<<Phi2>>,<<Get>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: Add [<<Phi1>>,<<Cons1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: Return [<<Phi2>>] loop:none // /// CHECK-START-ARM64: int Main.reductionInt(int[]) loop_optimization (after) /// CHECK-DAG: <<Cons0:i\d+>> IntConstant 0 loop:none @@ -88,6 +89,7 @@ public class Main { /// CHECK-DAG: <<Get:j\d+>> ArrayGet [{{l\d+}},<<Phi1>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: Add [<<Phi2>>,<<Get>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: Add [<<Phi1>>,<<Cons1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: Return [<<Phi2>>] loop:none // /// CHECK-START-ARM64: long Main.reductionLong(long[]) loop_optimization (after) /// CHECK-DAG: <<Cons0:i\d+>> IntConstant 0 loop:none @@ -142,6 +144,7 @@ public class Main { /// CHECK-DAG: <<Get:i\d+>> ArrayGet [{{l\d+}},<<Phi1>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: Add [<<Phi2>>,<<Get>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: Add [<<Phi1>>,<<Cons1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: Return [<<Phi2>>] loop:none // /// CHECK-START-ARM64: int Main.reductionIntM1(int[]) loop_optimization (after) /// CHECK-DAG: <<Cons0:i\d+>> IntConstant 0 loop:none @@ -172,6 +175,7 @@ public class Main { /// CHECK-DAG: <<Get:j\d+>> ArrayGet [{{l\d+}},<<Phi1>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: Add [<<Phi2>>,<<Get>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: Add [<<Phi1>>,<<Cons1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: Return [<<Phi2>>] loop:none // /// CHECK-START-ARM64: long Main.reductionLongM1(long[]) loop_optimization (after) /// CHECK-DAG: <<Cons0:i\d+>> IntConstant 0 loop:none @@ -225,6 +229,7 @@ public class Main { /// CHECK-DAG: <<Get:i\d+>> ArrayGet [{{l\d+}},<<Phi1>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: Sub [<<Phi2>>,<<Get>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: Add [<<Phi1>>,<<Cons1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: Return [<<Phi2>>] loop:none // /// CHECK-START-ARM64: int Main.reductionMinusInt(int[]) loop_optimization (after) /// CHECK-DAG: <<Cons0:i\d+>> IntConstant 0 loop:none @@ -254,6 +259,7 @@ public class Main { /// CHECK-DAG: <<Get:j\d+>> ArrayGet [{{l\d+}},<<Phi1>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: Sub [<<Phi2>>,<<Get>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: Add [<<Phi1>>,<<Cons1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: Return [<<Phi2>>] loop:none // /// CHECK-START-ARM64: long Main.reductionMinusLong(long[]) loop_optimization (after) /// CHECK-DAG: <<Cons0:i\d+>> IntConstant 0 loop:none @@ -308,6 +314,7 @@ public class Main { /// CHECK-DAG: <<Get:i\d+>> ArrayGet [{{l\d+}},<<Phi1>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: InvokeStaticOrDirect [<<Phi2>>,<<Get>>] intrinsic:MathMinIntInt loop:<<Loop>> outer_loop:none /// CHECK-DAG: Add [<<Phi1>>,<<Cons1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: Return [<<Phi2>>] loop:none // /// CHECK-START-ARM64: int Main.reductionMinInt(int[]) loop_optimization (after) /// CHECK-DAG: <<Cons0:i\d+>> IntConstant 0 loop:none @@ -370,6 +377,7 @@ public class Main { /// CHECK-DAG: <<Get:i\d+>> ArrayGet [{{l\d+}},<<Phi1>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: InvokeStaticOrDirect [<<Phi2>>,<<Get>>] intrinsic:MathMaxIntInt loop:<<Loop>> outer_loop:none /// CHECK-DAG: Add [<<Phi1>>,<<Cons1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: Return [<<Phi2>>] loop:none // /// CHECK-START-ARM64: int Main.reductionMaxInt(int[]) loop_optimization (after) /// CHECK-DAG: <<Cons0:i\d+>> IntConstant 0 loop:none diff --git a/test/666-dex-cache-itf/expected.txt b/test/666-dex-cache-itf/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/666-dex-cache-itf/expected.txt diff --git a/test/666-dex-cache-itf/info.txt b/test/666-dex-cache-itf/info.txt new file mode 100644 index 0000000000..6af3b21c2d --- /dev/null +++ b/test/666-dex-cache-itf/info.txt @@ -0,0 +1,2 @@ +We used to put wrong entries in the dex cache, which would +confuse the interface trampoline. diff --git a/test/666-dex-cache-itf/src/Main.java b/test/666-dex-cache-itf/src/Main.java new file mode 100644 index 0000000000..d43627668c --- /dev/null +++ b/test/666-dex-cache-itf/src/Main.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2017 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. + */ + + +// Define enough methods to enter the conflict trampoline. +interface Itf { + default int $noinline$def1() { return 42; } + default int $noinline$def2() { return 42; } + default int $noinline$def3() { return 42; } + default int $noinline$def4() { return 42; } + default int $noinline$def5() { return 42; } + default int $noinline$def6() { return 42; } + default int $noinline$def7() { return 42; } + default int $noinline$def8() { return 42; } + default int $noinline$def9() { return 42; } + default int $noinline$def10() { return 42; } + default int $noinline$def11() { return 42; } + default int $noinline$def12() { return 42; } + default int $noinline$def13() { return 42; } + default int $noinline$def14() { return 42; } + default int $noinline$def15() { return 42; } + default int $noinline$def16() { return 42; } + default int $noinline$def17() { return 42; } + default int $noinline$def18() { return 42; } + default int $noinline$def19() { return 42; } + default int $noinline$def20() { return 42; } + default int $noinline$def21() { return 42; } + default int $noinline$def22() { return 42; } + + // Known name conflict in OC-dev. + default int $noinline$defAa() { return 42; } + default int $noinline$defbb() { return 42; } +} + +// Define an abstract class so that the super calls in Main.instanceMethod +// end up finding the miranda method (which was then wrongly stashed into +// the dex cache). +class AbstractItf implements Itf { +} + + +public class Main extends AbstractItf { + static Itf itf = new Main(); + public static void main(String[] args) throws Exception { + new Main().instanceMethod(); + } + + public void instanceMethod() { + // Do super calls to invoke artQuickResolutionTrampoline, which used to + // put the copied method AbstractIf.<name> into the slot for the MethodId + // referencing the Itf method. + super.$noinline$def1(); + super.$noinline$def2(); + super.$noinline$def3(); + super.$noinline$def4(); + super.$noinline$def5(); + super.$noinline$def6(); + super.$noinline$def7(); + super.$noinline$def8(); + super.$noinline$def9(); + super.$noinline$def10(); + super.$noinline$def11(); + super.$noinline$def12(); + super.$noinline$def13(); + super.$noinline$def14(); + super.$noinline$def15(); + super.$noinline$def16(); + super.$noinline$def17(); + super.$noinline$def18(); + super.$noinline$def19(); + super.$noinline$def20(); + super.$noinline$def21(); + super.$noinline$def22(); + super.$noinline$defAa(); + super.$noinline$defbb(); + + // Now call the same methods but through an invoke-interface, which used to crash + // if the wrong method was in the dex cache. + itf.$noinline$def1(); + itf.$noinline$def2(); + itf.$noinline$def3(); + itf.$noinline$def4(); + itf.$noinline$def5(); + itf.$noinline$def6(); + itf.$noinline$def7(); + itf.$noinline$def8(); + itf.$noinline$def9(); + itf.$noinline$def10(); + itf.$noinline$def11(); + itf.$noinline$def12(); + itf.$noinline$def13(); + itf.$noinline$def14(); + itf.$noinline$def15(); + itf.$noinline$def16(); + itf.$noinline$def17(); + itf.$noinline$def18(); + itf.$noinline$def19(); + itf.$noinline$def20(); + itf.$noinline$def21(); + itf.$noinline$def22(); + itf.$noinline$defAa(); + itf.$noinline$defbb(); + } +} diff --git a/test/912-classes/classes.cc b/test/912-classes/classes.cc index 869eacd82c..9727057668 100644 --- a/test/912-classes/classes.cc +++ b/test/912-classes/classes.cc @@ -16,6 +16,7 @@ #include <stdio.h> +#include <condition_variable> #include <mutex> #include <vector> @@ -378,6 +379,48 @@ extern "C" JNIEXPORT void JNICALL Java_art_Test912_enableClassLoadPreparePrintEv ClassLoadPreparePrinter::ClassPrepareCallback); } +template<typename T> +static jthread RunEventThread(const std::string& name, + jvmtiEnv* jvmti, + JNIEnv* env, + void (*func)(jvmtiEnv*, JNIEnv*, T*), + T* data) { + // Create a Thread object. + std::string name_str = name; + name_str += ": JVMTI_THREAD-Test912"; + ScopedLocalRef<jobject> thread_name(env, env->NewStringUTF(name_str.c_str())); + CHECK(thread_name.get() != nullptr); + + ScopedLocalRef<jclass> thread_klass(env, env->FindClass("java/lang/Thread")); + CHECK(thread_klass.get() != nullptr); + + ScopedLocalRef<jobject> thread(env, env->AllocObject(thread_klass.get())); + CHECK(thread.get() != nullptr); + + jmethodID initID = env->GetMethodID(thread_klass.get(), "<init>", "(Ljava/lang/String;)V"); + CHECK(initID != nullptr); + + env->CallNonvirtualVoidMethod(thread.get(), thread_klass.get(), initID, thread_name.get()); + CHECK(!env->ExceptionCheck()); + + // Run agent thread. + CheckJvmtiError(jvmti, jvmti->RunAgentThread(thread.get(), + reinterpret_cast<jvmtiStartFunction>(func), + reinterpret_cast<void*>(data), + JVMTI_THREAD_NORM_PRIORITY)); + return thread.release(); +} + +static void JoinTread(JNIEnv* env, jthread thr) { + ScopedLocalRef<jclass> thread_klass(env, env->FindClass("java/lang/Thread")); + CHECK(thread_klass.get() != nullptr); + + jmethodID joinID = env->GetMethodID(thread_klass.get(), "join", "()V"); + CHECK(joinID != nullptr); + + env->CallVoidMethod(thr, joinID); +} + class ClassLoadPrepareEquality { public: static constexpr const char* kClassName = "Lart/Test912$ClassE;"; @@ -389,6 +432,21 @@ class ClassLoadPrepareEquality { static constexpr const char* kWeakInitSig = "(Ljava/lang/Object;)V"; static constexpr const char* kWeakGetSig = "()Ljava/lang/Object;"; + static void AgentThreadTest(jvmtiEnv* jvmti ATTRIBUTE_UNUSED, + JNIEnv* env, + jobject* obj_global) { + jobject target = *obj_global; + jobject target_local = env->NewLocalRef(target); + { + std::unique_lock<std::mutex> lk(mutex_); + started_ = true; + cond_started_.notify_all(); + cond_finished_.wait(lk, [] { return finished_; }); + CHECK(finished_); + } + CHECK(env->IsSameObject(target, target_local)); + } + static void JNICALL ClassLoadCallback(jvmtiEnv* jenv, JNIEnv* jni_env, jthread thread ATTRIBUTE_UNUSED, @@ -398,9 +456,13 @@ class ClassLoadPrepareEquality { found_ = true; stored_class_ = jni_env->NewGlobalRef(klass); weakly_stored_class_ = jni_env->NewWeakGlobalRef(klass); - // The following is bad and relies on implementation details. But otherwise a test would be - // a lot more complicated. - local_stored_class_ = jni_env->NewLocalRef(klass); + // Check that we update the local refs. + agent_thread_ = static_cast<jthread>(jni_env->NewGlobalRef(RunEventThread<jobject>( + "local-ref", jenv, jni_env, &AgentThreadTest, static_cast<jobject*>(&stored_class_)))); + { + std::unique_lock<std::mutex> lk(mutex_); + cond_started_.wait(lk, [] { return started_; }); + } // Store the value into a field in the heap. SetOrCompare(jni_env, klass, true); } @@ -415,9 +477,14 @@ class ClassLoadPrepareEquality { CHECK(stored_class_ != nullptr); CHECK(jni_env->IsSameObject(stored_class_, klass)); CHECK(jni_env->IsSameObject(weakly_stored_class_, klass)); - CHECK(jni_env->IsSameObject(local_stored_class_, klass)); + { + std::unique_lock<std::mutex> lk(mutex_); + finished_ = true; + cond_finished_.notify_all(); + } // Look up the value in a field in the heap. SetOrCompare(jni_env, klass, false); + JoinTread(jni_env, agent_thread_); compared_ = true; } } @@ -487,14 +554,25 @@ class ClassLoadPrepareEquality { private: static jobject stored_class_; static jweak weakly_stored_class_; - static jobject local_stored_class_; + static jthread agent_thread_; + static std::mutex mutex_; + static bool started_; + static std::condition_variable cond_finished_; + static bool finished_; + static std::condition_variable cond_started_; static bool found_; static bool compared_; }; + jclass ClassLoadPrepareEquality::storage_class_ = nullptr; jobject ClassLoadPrepareEquality::stored_class_ = nullptr; jweak ClassLoadPrepareEquality::weakly_stored_class_ = nullptr; -jobject ClassLoadPrepareEquality::local_stored_class_ = nullptr; +jthread ClassLoadPrepareEquality::agent_thread_ = nullptr; +std::mutex ClassLoadPrepareEquality::mutex_; +bool ClassLoadPrepareEquality::started_ = false; +std::condition_variable ClassLoadPrepareEquality::cond_started_; +bool ClassLoadPrepareEquality::finished_ = false; +std::condition_variable ClassLoadPrepareEquality::cond_finished_; bool ClassLoadPrepareEquality::found_ = false; bool ClassLoadPrepareEquality::compared_ = false; diff --git a/test/912-classes/src-art/art/Test912.java b/test/912-classes/src-art/art/Test912.java index fbf8794c50..2e41c2646d 100644 --- a/test/912-classes/src-art/art/Test912.java +++ b/test/912-classes/src-art/art/Test912.java @@ -230,6 +230,7 @@ public class Test912 { "java.nio.charset.CoderMalfunctionError", "java.util.NoSuchElementException", "java.io.FileNotFoundException", // b/63581208 + "java.util.zip.ZipException", // b/63581208 }; for (String s : PRELOAD_FOR_JIT) { Class.forName(s); diff --git a/test/Android.bp b/test/Android.bp index 2f2305607e..d56c0b50c2 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -302,6 +302,7 @@ art_cc_defaults { "1926-missed-frame-pop/frame_pop_missed.cc", "1927-exception-event/exception_event.cc", "1930-monitor-info/monitor.cc", + "1932-monitor-events-misc/monitor_misc.cc" ], shared_libs: [ "libbase", diff --git a/test/knownfailures.json b/test/knownfailures.json index 84758c9f54..252d13ae29 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -100,6 +100,14 @@ "description": ["This test sometimes runs out of memory initializing the boot classpath."] }, { + "tests": "164-resolution-trampoline-dex-cache", + "variant": "interp-ac | interpreter", + "description": ["This test requires AOT mixed with JIT and enables the JIT by the ", + "runtime option -Xusejit:true. This conflicts with -Xint passed for ", + "interpreter configurations (interp-ac | interpreter). The 'jit' ", + "configuration succeeds even though it does not test anything useful."] + }, + { "tests": ["908-gc-start-finish", "913-heaps"], "variant": "gcstress", diff --git a/test/run-test b/test/run-test index 9996986a92..79f3d1e256 100755 --- a/test/run-test +++ b/test/run-test @@ -810,6 +810,7 @@ fi good="no" good_build="yes" good_run="yes" +export TEST_RUNTIME="${runtime}" if [ "$dev_mode" = "yes" ]; then "./${build}" $build_args 2>&1 build_exit="$?" diff --git a/test/ti-agent/jvmti_helper.cc b/test/ti-agent/jvmti_helper.cc index c290e9b1ae..4ca2d5d9a0 100644 --- a/test/ti-agent/jvmti_helper.cc +++ b/test/ti-agent/jvmti_helper.cc @@ -49,7 +49,7 @@ static const jvmtiCapabilities standard_caps = { .can_get_bytecodes = 1, .can_get_synthetic_attribute = 1, .can_get_owned_monitor_info = 0, - .can_get_current_contended_monitor = 0, + .can_get_current_contended_monitor = 1, .can_get_monitor_info = 1, .can_pop_frame = 0, .can_redefine_classes = 1, diff --git a/test/ti-agent/monitors_helper.cc b/test/ti-agent/monitors_helper.cc index 7c28edecd2..81d4cdc3ae 100644 --- a/test/ti-agent/monitors_helper.cc +++ b/test/ti-agent/monitors_helper.cc @@ -13,16 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #include "jni.h" #include "jvmti.h" + #include <vector> + #include "jvmti_helper.h" #include "jni_helper.h" #include "test_env.h" #include "scoped_local_ref.h" + namespace art { namespace common_monitors { +extern "C" JNIEXPORT jobject JNICALL Java_art_Monitors_getCurrentContendedMonitor( + JNIEnv* env, jclass, jthread thr) { + jobject out = nullptr; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetCurrentContendedMonitor(thr, &out)); + return out; +} + extern "C" JNIEXPORT jobject JNICALL Java_art_Monitors_getObjectMonitorUsage( JNIEnv* env, jclass, jobject obj) { ScopedLocalRef<jclass> klass(env, env->FindClass("art/Monitors$MonitorUsage")); @@ -58,5 +69,154 @@ extern "C" JNIEXPORT jobject JNICALL Java_art_Monitors_getObjectMonitorUsage( obj, usage.owner, usage.entry_count, wait, notify_wait); } +struct MonitorsData { + jclass test_klass; + jmethodID monitor_enter; + jmethodID monitor_entered; + jmethodID monitor_wait; + jmethodID monitor_waited; + jclass monitor_klass; +}; + +static void monitorEnterCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thr, + jobject obj) { + MonitorsData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + if (!jnienv->IsInstanceOf(obj, data->monitor_klass)) { + return; + } + jnienv->CallStaticVoidMethod(data->test_klass, data->monitor_enter, thr, obj); +} +static void monitorEnteredCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thr, + jobject obj) { + MonitorsData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + if (!jnienv->IsInstanceOf(obj, data->monitor_klass)) { + return; + } + jnienv->CallStaticVoidMethod(data->test_klass, data->monitor_entered, thr, obj); +} +static void monitorWaitCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thr, + jobject obj, + jlong timeout) { + MonitorsData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + if (!jnienv->IsInstanceOf(obj, data->monitor_klass)) { + return; + } + jnienv->CallStaticVoidMethod(data->test_klass, data->monitor_wait, thr, obj, timeout); +} +static void monitorWaitedCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thr, + jobject obj, + jboolean timed_out) { + MonitorsData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + if (!jnienv->IsInstanceOf(obj, data->monitor_klass)) { + return; + } + jnienv->CallStaticVoidMethod(data->test_klass, data->monitor_waited, thr, obj, timed_out); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Monitors_setupMonitorEvents( + JNIEnv* env, + jclass, + jclass test_klass, + jobject monitor_enter, + jobject monitor_entered, + jobject monitor_wait, + jobject monitor_waited, + jclass monitor_klass, + jthread thr) { + MonitorsData* data = nullptr; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->Allocate(sizeof(MonitorsData), + reinterpret_cast<unsigned char**>(&data)))) { + return; + } + jvmtiCapabilities caps; + memset(&caps, 0, sizeof(caps)); + caps.can_generate_monitor_events = 1; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->AddCapabilities(&caps))) { + return; + } + + memset(data, 0, sizeof(MonitorsData)); + data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(test_klass)); + data->monitor_enter = env->FromReflectedMethod(monitor_enter); + data->monitor_entered = env->FromReflectedMethod(monitor_entered); + data->monitor_wait = env->FromReflectedMethod(monitor_wait); + data->monitor_waited = env->FromReflectedMethod(monitor_waited); + data->monitor_klass = reinterpret_cast<jclass>(env->NewGlobalRef(monitor_klass)); + MonitorsData* old_data = nullptr; + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->GetEnvironmentLocalStorage( + reinterpret_cast<void**>(&old_data)))) { + return; + } else if (old_data != nullptr && old_data->test_klass != nullptr) { + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), "Environment already has local storage set!"); + return; + } + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) { + return; + } + + jvmtiEventCallbacks cb; + memset(&cb, 0, sizeof(cb)); + cb.MonitorContendedEnter = monitorEnterCB; + cb.MonitorContendedEntered = monitorEnteredCB; + cb.MonitorWait = monitorWaitCB; + cb.MonitorWaited = monitorWaitedCB; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode( + JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTER, thr))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode( + JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, thr))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode( + JVMTI_ENABLE, JVMTI_EVENT_MONITOR_WAIT, thr))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode( + JVMTI_ENABLE, JVMTI_EVENT_MONITOR_WAITED, thr))) { + return; + } +} + } // namespace common_monitors } // namespace art + diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java index 8262910bf0..f4926aa83b 100644 --- a/tools/ahat/src/ObjectHandler.java +++ b/tools/ahat/src/ObjectHandler.java @@ -152,7 +152,7 @@ class ObjectHandler implements AhatHandler { * ignored otherwise. */ private static void printFields(Doc doc, Query query, String id, boolean diff, - List<FieldValue> current, List<FieldValue> baseline) { + Iterable<FieldValue> current, Iterable<FieldValue> baseline) { if (!diff) { // To avoid having to special case when diff is disabled, always diff diff --git a/tools/ahat/src/ObjectsHandler.java b/tools/ahat/src/ObjectsHandler.java index fd226c24bf..1a8f018bd5 100644 --- a/tools/ahat/src/ObjectsHandler.java +++ b/tools/ahat/src/ObjectsHandler.java @@ -37,10 +37,9 @@ class ObjectsHandler implements AhatHandler { @Override public void handle(Doc doc, Query query) throws IOException { int id = query.getInt("id", 0); - int depth = query.getInt("depth", 0); String className = query.get("class", null); String heapName = query.get("heap", null); - Site site = mSnapshot.getSite(id, depth); + Site site = mSnapshot.getSite(id); List<AhatInstance> insts = new ArrayList<AhatInstance>(); site.getObjects(heapName, className, insts); diff --git a/tools/ahat/src/SiteHandler.java b/tools/ahat/src/SiteHandler.java index 7a831d3018..543eaa376a 100644 --- a/tools/ahat/src/SiteHandler.java +++ b/tools/ahat/src/SiteHandler.java @@ -21,6 +21,7 @@ import com.android.ahat.heapdump.AhatSnapshot; import com.android.ahat.heapdump.Site; import com.android.ahat.heapdump.Sort; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -39,8 +40,7 @@ class SiteHandler implements AhatHandler { @Override public void handle(Doc doc, Query query) throws IOException { int id = query.getInt("id", 0); - int depth = query.getInt("depth", 0); - Site site = mSnapshot.getSite(id, depth); + Site site = mSnapshot.getSite(id); doc.title("Site"); doc.big(Summarizer.summarize(site)); @@ -49,7 +49,8 @@ class SiteHandler implements AhatHandler { SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, site); doc.section("Sites Called from Here"); - List<Site> children = site.getChildren(); + List<Site> children = new ArrayList<Site>(site.getChildren()); + if (children.isEmpty()) { doc.println(DocString.text("(none)")); } else { @@ -99,8 +100,8 @@ class SiteHandler implements AhatHandler { String className = info.getClassName(); SizeTable.row(doc, info.numBytes, baseinfo.numBytes, DocString.link( - DocString.formattedUri("objects?id=%d&depth=%d&heap=%s&class=%s", - site.getId(), site.getDepth(), info.heap.getName(), className), + DocString.formattedUri("objects?id=%d&heap=%s&class=%s", + site.getId(), info.heap.getName(), className), DocString.format("%,14d", info.numInstances)), DocString.delta(false, false, info.numInstances, baseinfo.numInstances), DocString.text(info.heap.getName()), diff --git a/tools/ahat/src/Summarizer.java b/tools/ahat/src/Summarizer.java index 3e9da31e96..50b2e4b3b3 100644 --- a/tools/ahat/src/Summarizer.java +++ b/tools/ahat/src/Summarizer.java @@ -130,7 +130,7 @@ class Summarizer { if (site.getLineNumber() > 0) { text.append(":").append(Integer.toString(site.getLineNumber())); } - URI uri = DocString.formattedUri("site?id=%d&depth=%d", site.getId(), site.getDepth()); + URI uri = DocString.formattedUri("site?id=%d", site.getId()); return DocString.link(uri, text); } } diff --git a/tools/ahat/src/heapdump/AhatArrayInstance.java b/tools/ahat/src/heapdump/AhatArrayInstance.java index 6d4485d4b9..8d23276fde 100644 --- a/tools/ahat/src/heapdump/AhatArrayInstance.java +++ b/tools/ahat/src/heapdump/AhatArrayInstance.java @@ -58,8 +58,7 @@ public class AhatArrayInstance extends AhatInstance { } @Override public Value get(int index) { - AhatInstance obj = insts[index]; - return obj == null ? null : new Value(insts[index]); + return Value.pack(insts[index]); } }; break; @@ -73,7 +72,7 @@ public class AhatArrayInstance extends AhatInstance { } @Override public Value get(int index) { - return new Value(chars[index]); + return Value.pack(chars[index]); } }; break; @@ -87,7 +86,7 @@ public class AhatArrayInstance extends AhatInstance { } @Override public Value get(int index) { - return new Value(bytes[index]); + return Value.pack(bytes[index]); } }; break; @@ -100,8 +99,7 @@ public class AhatArrayInstance extends AhatInstance { } @Override public Value get(int index) { - Object obj = values[index]; - return obj == null ? null : new Value(obj); + return Value.pack(values[index]); } }; break; @@ -130,7 +128,7 @@ public class AhatArrayInstance extends AhatInstance { } @Override - ReferenceIterator getReferences() { + Iterable<Reference> getReferences() { // The list of references will be empty if this is a primitive array. List<Reference> refs = Collections.emptyList(); if (!mValues.isEmpty()) { @@ -155,7 +153,7 @@ public class AhatArrayInstance extends AhatInstance { }; } } - return new ReferenceIterator(refs); + return new SkipNullsIterator(refs); } @Override public boolean isArrayInstance() { diff --git a/tools/ahat/src/heapdump/AhatClassInstance.java b/tools/ahat/src/heapdump/AhatClassInstance.java index 211592388c..f7d8431a7b 100644 --- a/tools/ahat/src/heapdump/AhatClassInstance.java +++ b/tools/ahat/src/heapdump/AhatClassInstance.java @@ -19,12 +19,16 @@ package com.android.ahat.heapdump; import com.android.tools.perflib.heap.ClassInstance; import com.android.tools.perflib.heap.Instance; import java.awt.image.BufferedImage; -import java.util.AbstractList; -import java.util.Arrays; +import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; public class AhatClassInstance extends AhatInstance { - private FieldValue[] mFieldValues; + // Instance fields of the object. These are stored in order of the instance + // field descriptors from the class object, starting with this class first, + // followed by the super class, and so on. We store the values separate from + // the field types and names to save memory. + private Value[] mFields; public AhatClassInstance(long id) { super(id); @@ -35,18 +39,14 @@ public class AhatClassInstance extends AhatInstance { ClassInstance classInst = (ClassInstance)inst; List<ClassInstance.FieldValue> fieldValues = classInst.getValues(); - mFieldValues = new FieldValue[fieldValues.size()]; - for (int i = 0; i < mFieldValues.length; i++) { - ClassInstance.FieldValue field = fieldValues.get(i); - String name = field.getField().getName(); - String type = field.getField().getType().toString(); - Value value = snapshot.getValue(field.getValue()); - mFieldValues[i] = new FieldValue(name, type, value); + mFields = new Value[fieldValues.size()]; + for (int i = 0; i < mFields.length; i++) { + mFields[i] = snapshot.getValue(fieldValues.get(i).getValue()); } } @Override public Value getField(String fieldName) { - for (FieldValue field : mFieldValues) { + for (FieldValue field : getInstanceFields()) { if (fieldName.equals(field.name)) { return field.value; } @@ -90,36 +90,21 @@ public class AhatClassInstance extends AhatInstance { /** * Returns the list of class instance fields for this instance. */ - public List<FieldValue> getInstanceFields() { - return Arrays.asList(mFieldValues); + public Iterable<FieldValue> getInstanceFields() { + return new InstanceFieldIterator(mFields, getClassObj()); } @Override - ReferenceIterator getReferences() { - List<Reference> refs = new AbstractList<Reference>() { - @Override - public int size() { - return mFieldValues.length; - } - - @Override - public Reference get(int index) { - FieldValue field = mFieldValues[index]; - Value value = field.value; - if (value != null && value.isAhatInstance()) { - boolean strong = !field.name.equals("referent") - || !isInstanceOfClass("java.lang.ref.Reference"); - AhatInstance ref = value.asAhatInstance(); - return new Reference(AhatClassInstance.this, "." + field.name, ref, strong); - } - return null; - } - }; - return new ReferenceIterator(refs); + Iterable<Reference> getReferences() { + if (isInstanceOfClass("java.lang.ref.Reference")) { + return new WeakReferentReferenceIterator(); + } + return new StrongReferenceIterator(); } /** - * Returns true if this is an instance of a class with the given name. + * Returns true if this is an instance of a (subclass of a) class with the + * given name. */ private boolean isInstanceOfClass(String className) { AhatClassObj cls = getClassObj(); @@ -262,4 +247,131 @@ public class AhatClassInstance extends AhatInstance { bitmap.setRGB(0, 0, info.width, info.height, abgr, 0, info.width); return bitmap; } + + private static class InstanceFieldIterator implements Iterable<FieldValue>, + Iterator<FieldValue> { + // The complete list of instance field values to iterate over, including + // superclass field values. + private Value[] mValues; + private int mValueIndex; + + // The list of field descriptors specific to the current class in the + // class hierarchy, not including superclass field descriptors. + // mFields and mFieldIndex are reset each time we walk up to the next + // superclass in the call hierarchy. + private Field[] mFields; + private int mFieldIndex; + private AhatClassObj mNextClassObj; + + public InstanceFieldIterator(Value[] values, AhatClassObj classObj) { + mValues = values; + mFields = classObj.getInstanceFields(); + mValueIndex = 0; + mFieldIndex = 0; + mNextClassObj = classObj.getSuperClassObj(); + } + + @Override + public boolean hasNext() { + // If we have reached the end of the fields in the current class, + // continue walking up the class hierarchy to get superclass fields as + // well. + while (mFieldIndex == mFields.length && mNextClassObj != null) { + mFields = mNextClassObj.getInstanceFields(); + mFieldIndex = 0; + mNextClassObj = mNextClassObj.getSuperClassObj(); + } + return mFieldIndex < mFields.length; + } + + @Override + public FieldValue next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Field field = mFields[mFieldIndex++]; + Value value = mValues[mValueIndex++]; + return new FieldValue(field.name, field.type, value); + } + + @Override + public Iterator<FieldValue> iterator() { + return this; + } + } + + /** + * A Reference iterator that iterates over the fields of this instance + * assuming all field references are strong references. + */ + private class StrongReferenceIterator implements Iterable<Reference>, + Iterator<Reference> { + private Iterator<FieldValue> mIter = getInstanceFields().iterator(); + private Reference mNext = null; + + @Override + public boolean hasNext() { + while (mNext == null && mIter.hasNext()) { + FieldValue field = mIter.next(); + if (field.value != null && field.value.isAhatInstance()) { + AhatInstance ref = field.value.asAhatInstance(); + mNext = new Reference(AhatClassInstance.this, "." + field.name, ref, true); + } + } + return mNext != null; + } + + @Override + public Reference next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Reference next = mNext; + mNext = null; + return next; + } + + @Override + public Iterator<Reference> iterator() { + return this; + } + } + + /** + * A Reference iterator that iterates over the fields of a subclass of + * java.lang.ref.Reference, where the 'referent' field is considered weak. + */ + private class WeakReferentReferenceIterator implements Iterable<Reference>, + Iterator<Reference> { + private Iterator<FieldValue> mIter = getInstanceFields().iterator(); + private Reference mNext = null; + + @Override + public boolean hasNext() { + while (mNext == null && mIter.hasNext()) { + FieldValue field = mIter.next(); + if (field.value != null && field.value.isAhatInstance()) { + boolean strong = !field.name.equals("referent"); + AhatInstance ref = field.value.asAhatInstance(); + mNext = new Reference(AhatClassInstance.this, "." + field.name, ref, strong); + } + } + return mNext != null; + } + + @Override + public Reference next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Reference next = mNext; + mNext = null; + return next; + } + + @Override + public Iterator<Reference> iterator() { + return this; + } + } } diff --git a/tools/ahat/src/heapdump/AhatClassObj.java b/tools/ahat/src/heapdump/AhatClassObj.java index 052d7a8e88..08c70974c6 100644 --- a/tools/ahat/src/heapdump/AhatClassObj.java +++ b/tools/ahat/src/heapdump/AhatClassObj.java @@ -17,7 +17,6 @@ package com.android.ahat.heapdump; import com.android.tools.perflib.heap.ClassObj; -import com.android.tools.perflib.heap.Field; import com.android.tools.perflib.heap.Instance; import java.util.AbstractList; import java.util.Arrays; @@ -30,6 +29,7 @@ public class AhatClassObj extends AhatInstance { private AhatClassObj mSuperClassObj; private AhatInstance mClassLoader; private FieldValue[] mStaticFieldValues; + private Field[] mInstanceFields; public AhatClassObj(long id) { super(id); @@ -51,15 +51,22 @@ public class AhatClassObj extends AhatInstance { mClassLoader = snapshot.findInstance(loader.getId()); } - Collection<Map.Entry<Field, Object>> fieldValues = classObj.getStaticFieldValues().entrySet(); + Collection<Map.Entry<com.android.tools.perflib.heap.Field, Object>> fieldValues + = classObj.getStaticFieldValues().entrySet(); mStaticFieldValues = new FieldValue[fieldValues.size()]; int index = 0; - for (Map.Entry<Field, Object> field : fieldValues) { + for (Map.Entry<com.android.tools.perflib.heap.Field, Object> field : fieldValues) { String name = field.getKey().getName(); String type = field.getKey().getType().toString(); Value value = snapshot.getValue(field.getValue()); mStaticFieldValues[index++] = new FieldValue(name, type, value); } + + com.android.tools.perflib.heap.Field[] fields = classObj.getFields(); + mInstanceFields = new Field[fields.length]; + for (int i = 0; i < fields.length; i++) { + mInstanceFields[i] = new Field(fields[i].getName(), fields[i].getType().toString()); + } } /** @@ -90,8 +97,15 @@ public class AhatClassObj extends AhatInstance { return Arrays.asList(mStaticFieldValues); } + /** + * Returns the fields of instances of this class. + */ + public Field[] getInstanceFields() { + return mInstanceFields; + } + @Override - ReferenceIterator getReferences() { + Iterable<Reference> getReferences() { List<Reference> refs = new AbstractList<Reference>() { @Override public int size() { @@ -108,7 +122,7 @@ public class AhatClassObj extends AhatInstance { return null; } }; - return new ReferenceIterator(refs); + return new SkipNullsIterator(refs); } @Override public boolean isClassObj() { diff --git a/tools/ahat/src/heapdump/AhatInstance.java b/tools/ahat/src/heapdump/AhatInstance.java index 39a844af5f..0e7855801d 100644 --- a/tools/ahat/src/heapdump/AhatInstance.java +++ b/tools/ahat/src/heapdump/AhatInstance.java @@ -72,6 +72,7 @@ public abstract class AhatInstance implements Diffable<AhatInstance>, * snapshot.findInstance have been initialized yet. */ void initialize(AhatSnapshot snapshot, Instance inst, Site site) { + site.addInstance(this); mSize = new Size(inst.getSize(), 0); mHeap = snapshot.getHeap(inst.getHeap().getName()); @@ -147,7 +148,7 @@ public abstract class AhatInstance implements Diffable<AhatInstance>, * Returns an iterator over the references this AhatInstance has to other * AhatInstances. */ - abstract ReferenceIterator getReferences(); + abstract Iterable<Reference> getReferences(); /** * Returns true if this instance is marked as a root instance. diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java b/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java index 2b3e056a1e..8b4c6796aa 100644 --- a/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java +++ b/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java @@ -29,10 +29,6 @@ public class AhatPlaceHolderClassObj extends AhatClassObj { baseline.setBaseline(this); } - @Override public Size getSize() { - return Size.ZERO; - } - @Override public Size getRetainedSize(AhatHeap heap) { return Size.ZERO; } @@ -68,4 +64,8 @@ public class AhatPlaceHolderClassObj extends AhatClassObj { @Override public AhatInstance getClassLoader() { return getBaseline().asClassObj().getClassLoader().getBaseline(); } + + @Override public Field[] getInstanceFields() { + return getBaseline().asClassObj().getInstanceFields(); + } } diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java b/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java index d797b11030..9abc952072 100644 --- a/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java +++ b/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java @@ -65,8 +65,8 @@ public class AhatPlaceHolderInstance extends AhatInstance { } @Override - ReferenceIterator getReferences() { + Iterable<Reference> getReferences() { List<Reference> refs = Collections.emptyList(); - return new ReferenceIterator(refs); + return refs; } } diff --git a/tools/ahat/src/heapdump/AhatSnapshot.java b/tools/ahat/src/heapdump/AhatSnapshot.java index 7df78c50b5..1b2cf3c59f 100644 --- a/tools/ahat/src/heapdump/AhatSnapshot.java +++ b/tools/ahat/src/heapdump/AhatSnapshot.java @@ -35,7 +35,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -48,9 +47,6 @@ public class AhatSnapshot implements Diffable<AhatSnapshot> { // List of all ahat instances stored in increasing order by id. private final List<AhatInstance> mInstances = new ArrayList<AhatInstance>(); - // Map from class name to class object. - private final Map<String, AhatClassObj> mClasses = new HashMap<String, AhatClassObj>(); - private final List<AhatHeap> mHeaps = new ArrayList<AhatHeap>(); private AhatSnapshot mBaseline = this; @@ -113,7 +109,6 @@ public class AhatSnapshot implements Diffable<AhatSnapshot> { } else if (inst instanceof ClassObj) { AhatClassObj classObj = new AhatClassObj(id); mInstances.add(classObj); - mClasses.put(((ClassObj)inst).getClassName(), classObj); } return true; } @@ -145,8 +140,7 @@ public class AhatSnapshot implements Diffable<AhatSnapshot> { if (stack != null) { frames = stack.getFrames(); } - Site site = mRootSite.add(frames, frames == null ? 0 : frames.length, ahat); - ahat.initialize(this, inst, site); + ahat.initialize(this, inst, mRootSite.getSite(frames)); Long registeredNativeSize = registeredNative.get(inst); if (registeredNativeSize != null) { @@ -177,7 +171,7 @@ public class AhatSnapshot implements Diffable<AhatSnapshot> { heap.addToSize(superRoot.getRetainedSize(heap)); } - mRootSite.computeObjectsInfos(mHeaps.size()); + mRootSite.prepareForUse(0, mHeaps.size()); } /** @@ -213,15 +207,6 @@ public class AhatSnapshot implements Diffable<AhatSnapshot> { } /** - * Returns the class object for the class with given name. - * Returns null if there is no class object for the given name. - * Note: This method is exposed for testing purposes. - */ - public AhatClassObj findClass(String name) { - return mClasses.get(name); - } - - /** * Returns the heap with the given name, if any. * Returns null if no heap with the given name could be found. */ @@ -260,19 +245,11 @@ public class AhatSnapshot implements Diffable<AhatSnapshot> { return mRootSite; } - // Get the site associated with the given id and depth. + // Get the site associated with the given id. // Returns the root site if no such site found. - public Site getSite(int id, int depth) { - AhatInstance obj = findInstance(id); - if (obj == null) { - return mRootSite; - } - - Site site = obj.getSite(); - for (int i = 0; i < depth && site.getParent() != null; i++) { - site = site.getParent(); - } - return site; + public Site getSite(long id) { + Site site = mRootSite.findSite(id); + return site == null ? mRootSite : site; } // Return the Value for the given perflib value object. @@ -280,7 +257,7 @@ public class AhatSnapshot implements Diffable<AhatSnapshot> { if (value instanceof Instance) { value = findInstance(((Instance)value).getId()); } - return value == null ? null : new Value(value); + return Value.pack(value); } public void setBaseline(AhatSnapshot baseline) { diff --git a/tools/ahat/src/heapdump/Diff.java b/tools/ahat/src/heapdump/Diff.java index 489f709b04..98c7e58d56 100644 --- a/tools/ahat/src/heapdump/Diff.java +++ b/tools/ahat/src/heapdump/Diff.java @@ -333,7 +333,7 @@ public class Diff { // Add placeholders to their corresponding sites. // This requires the sites have already been diffed. for (AhatInstance placeholder : placeholders) { - placeholder.getBaseline().getSite().getBaseline().addPlaceHolderInstance(placeholder); + placeholder.getBaseline().getSite().getBaseline().addInstance(placeholder); } } } diff --git a/tools/ahat/src/heapdump/DiffFields.java b/tools/ahat/src/heapdump/DiffFields.java index dd73456c0f..e3c671fe21 100644 --- a/tools/ahat/src/heapdump/DiffFields.java +++ b/tools/ahat/src/heapdump/DiffFields.java @@ -17,7 +17,6 @@ package com.android.ahat.heapdump; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -29,14 +28,19 @@ import java.util.List; public class DiffFields { /** * Return the result of diffing two collections of field values. - * The input collections 'current' and 'baseline' are not modified by this function. */ - public static List<DiffedFieldValue> diff(Collection<FieldValue> current, - Collection<FieldValue> baseline) { - List<FieldValue> currentSorted = new ArrayList<FieldValue>(current); + public static List<DiffedFieldValue> diff(Iterable<FieldValue> current, + Iterable<FieldValue> baseline) { + List<FieldValue> currentSorted = new ArrayList<FieldValue>(); + for (FieldValue field : current) { + currentSorted.add(field); + } Collections.sort(currentSorted, FOR_DIFF); - List<FieldValue> baselineSorted = new ArrayList<FieldValue>(baseline); + List<FieldValue> baselineSorted = new ArrayList<FieldValue>(); + for (FieldValue field : baseline) { + baselineSorted.add(field); + } Collections.sort(baselineSorted, FOR_DIFF); // Merge the two lists to form the diffed list of fields. diff --git a/tools/ahat/src/heapdump/DominatorReferenceIterator.java b/tools/ahat/src/heapdump/DominatorReferenceIterator.java index ce2e6efa6e..0b99e496cc 100644 --- a/tools/ahat/src/heapdump/DominatorReferenceIterator.java +++ b/tools/ahat/src/heapdump/DominatorReferenceIterator.java @@ -25,11 +25,11 @@ import java.util.NoSuchElementException; */ class DominatorReferenceIterator implements Iterator<AhatInstance>, Iterable<AhatInstance> { - private ReferenceIterator mIter; + private Iterator<Reference> mIter; private AhatInstance mNext; - public DominatorReferenceIterator(ReferenceIterator iter) { - mIter = iter; + public DominatorReferenceIterator(Iterable<Reference> iter) { + mIter = iter.iterator(); mNext = null; } diff --git a/tools/ahat/src/heapdump/Field.java b/tools/ahat/src/heapdump/Field.java new file mode 100644 index 0000000000..01f87c726e --- /dev/null +++ b/tools/ahat/src/heapdump/Field.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2016 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. + */ + +package com.android.ahat.heapdump; + +public class Field { + public final String name; + public final String type; + + public Field(String name, String type) { + this.name = name; + this.type = type; + } +} diff --git a/tools/ahat/src/heapdump/Site.java b/tools/ahat/src/heapdump/Site.java index f0fc5d2d6c..82931f0056 100644 --- a/tools/ahat/src/heapdump/Site.java +++ b/tools/ahat/src/heapdump/Site.java @@ -19,6 +19,7 @@ package com.android.ahat.heapdump; import com.android.tools.perflib.heap.StackFrame; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -33,16 +34,20 @@ public class Site implements Diffable<Site> { private String mFilename; private int mLineNumber; - // To identify this site, we pick a stack trace that includes the site. - // mId is the id of an object allocated at that stack trace, and mDepth - // is the number of calls between this site and the innermost site of - // allocation of the object with mId. - // For the root site, mId is 0 and mDepth is 0. - private long mId; - private int mDepth; + // A unique id to identify this site with. The id is chosen based on a + // depth first traversal of the complete site tree, which gives it the + // following desired properties: + // * The id can easily be represented in a URL. + // * The id is determined by the hprof file, so that the same id can be used + // across different instances for viewing the same hprof file. + // * A binary search can be used to find a site by id from the root site in + // log time. + // + // The id is set by prepareForUse after the complete site tree is constructed. + private long mId = -1; // The total size of objects allocated in this site (including child sites), - // organized by heap index. Computed as part of computeObjectsInfos. + // organized by heap index. Computed as part of prepareForUse. private Size[] mSizesByHeap; // List of child sites. @@ -99,18 +104,15 @@ public class Site implements Diffable<Site> { * Construct a root site. */ public Site(String name) { - this(null, name, "", "", 0, 0, 0); + this(null, name, "", "", 0); } - public Site(Site parent, String method, String signature, String file, - int line, long id, int depth) { + private Site(Site parent, String method, String signature, String file, int line) { mParent = parent; mMethodName = method; mSignature = signature; mFilename = file; mLineNumber = line; - mId = id; - mDepth = depth; mChildren = new ArrayList<Site>(); mObjects = new ArrayList<AhatInstance>(); mObjectsInfos = new ArrayList<ObjectsInfo>(); @@ -119,50 +121,63 @@ public class Site implements Diffable<Site> { } /** - * Add an instance to this site. + * Get a child site of this site. * Returns the site at which the instance was allocated. - * @param frames - The list of frames in the stack trace, starting with the inner-most frame. - * @param depth - The number of frames remaining before the inner-most frame is reached. + * @param frames - The list of frames in the stack trace, starting with the + * inner-most frame. May be null, in which case this site is + * returned. */ - Site add(StackFrame[] frames, int depth, AhatInstance inst) { - return add(this, frames, depth, inst); + Site getSite(StackFrame frames[]) { + return frames == null ? this : getSite(this, frames); } - private static Site add(Site site, StackFrame[] frames, int depth, AhatInstance inst) { - while (depth > 0) { - StackFrame next = frames[depth - 1]; + private static Site getSite(Site site, StackFrame frames[]) { + for (int s = frames.length - 1; s >= 0; --s) { + StackFrame frame = frames[s]; Site child = null; for (int i = 0; i < site.mChildren.size(); i++) { Site curr = site.mChildren.get(i); - if (curr.mLineNumber == next.getLineNumber() - && curr.mMethodName.equals(next.getMethodName()) - && curr.mSignature.equals(next.getSignature()) - && curr.mFilename.equals(next.getFilename())) { + if (curr.mLineNumber == frame.getLineNumber() + && curr.mMethodName.equals(frame.getMethodName()) + && curr.mSignature.equals(frame.getSignature()) + && curr.mFilename.equals(frame.getFilename())) { child = curr; break; } } if (child == null) { - child = new Site(site, next.getMethodName(), next.getSignature(), - next.getFilename(), next.getLineNumber(), inst.getId(), depth - 1); + child = new Site(site, frame.getMethodName(), frame.getSignature(), + frame.getFilename(), frame.getLineNumber()); site.mChildren.add(child); } - depth = depth - 1; site = child; } - site.mObjects.add(inst); return site; } /** - * Recompute the ObjectsInfos for this and all child sites. - * This should be done after the sites tree has been formed. It should also - * be done after dominators computation has been performed to ensure only - * reachable objects are included in the ObjectsInfos. + * Add an instance allocated at this site. + */ + void addInstance(AhatInstance inst) { + mObjects.add(inst); + } + + /** + * Prepare this and all child sites for use. + * Recomputes site ids, sizes, ObjectInfos for this and all child sites. + * This should be called after the sites tree has been formed and after + * dominators computation has been performed to ensure only reachable + * objects are included in the ObjectsInfos. * + * @param id - The smallest id that is allowed to be used for this site or + * any of its children. * @param numHeaps - The number of heaps in the heap dump. + * @return An id larger than the largest id used for this site or any of its + * children. */ - void computeObjectsInfos(int numHeaps) { + long prepareForUse(long id, int numHeaps) { + mId = id++; + // Count up the total sizes by heap. mSizesByHeap = new Size[numHeaps]; for (int i = 0; i < numHeaps; ++i) { @@ -183,7 +198,7 @@ public class Site implements Diffable<Site> { // Add objects allocated in child sites. for (Site child : mChildren) { - child.computeObjectsInfos(numHeaps); + id = child.prepareForUse(id, numHeaps); for (ObjectsInfo childInfo : child.mObjectsInfos) { ObjectsInfo info = getObjectsInfo(childInfo.heap, childInfo.classObj); info.numInstances += childInfo.numInstances; @@ -193,6 +208,7 @@ public class Site implements Diffable<Site> { mSizesByHeap[i] = mSizesByHeap[i].plus(child.mSizesByHeap[i]); } } + return id; } // Get the size of a site for a specific heap. @@ -285,22 +301,49 @@ public class Site implements Diffable<Site> { } /** - * Returns the id of some object allocated in this site. + * Returns the unique id of this site. */ public long getId() { return mId; } /** - * Returns the number of frames between this site and the site where the - * object with id getId() was allocated. + * Find the child site with the given id. + * Returns null if no such site was found. */ - public int getDepth() { - return mDepth; + public Site findSite(long id) { + if (id == mId) { + return this; + } + + // Binary search over the children to find the right child to search in. + int start = 0; + int end = mChildren.size(); + while (start < end) { + int mid = start + ((end - start) / 2); + Site midSite = mChildren.get(mid); + if (id < midSite.mId) { + end = mid; + } else if (mid + 1 == end) { + // This is the last child we could possibly find the desired site in, + // so search in this child. + return midSite.findSite(id); + } else if (id < mChildren.get(mid + 1).mId) { + // The desired site has an id between this child's id and the next + // child's id, so search in this child. + return midSite.findSite(id); + } else { + start = mid + 1; + } + } + return null; } + /** + * Returns an unmodifiable list of this site's immediate children. + */ public List<Site> getChildren() { - return mChildren; + return Collections.unmodifiableList(mChildren); } void setBaseline(Site baseline) { @@ -314,13 +357,4 @@ public class Site implements Diffable<Site> { @Override public boolean isPlaceHolder() { return false; } - - /** - * Adds a place holder instance to this site and all parent sites. - */ - void addPlaceHolderInstance(AhatInstance placeholder) { - for (Site site = this; site != null; site = site.mParent) { - site.mObjects.add(placeholder); - } - } } diff --git a/tools/ahat/src/heapdump/ReferenceIterator.java b/tools/ahat/src/heapdump/SkipNullsIterator.java index a707fb24ef..e99fe5e8ea 100644 --- a/tools/ahat/src/heapdump/ReferenceIterator.java +++ b/tools/ahat/src/heapdump/SkipNullsIterator.java @@ -17,49 +17,40 @@ package com.android.ahat.heapdump; import java.util.Iterator; -import java.util.List; import java.util.NoSuchElementException; -class ReferenceIterator implements Iterator<Reference>, - Iterable<Reference> { - private List<Reference> mRefs; - private int mLength; - private int mNextIndex; - private Reference mNext; +/** + * An iterator that skips over nulls. + */ +class SkipNullsIterator<T> implements Iterator<T>, Iterable<T> { + Iterator<T> mIter; + private T mNext; - /** - * Construct a ReferenceIterator that iterators over the given list of - * references. Elements of the given list of references may be null, in - * which case the ReferenceIterator will skip over them. - */ - public ReferenceIterator(List<Reference> refs) { - mRefs = refs; - mLength = refs.size(); - mNextIndex = 0; + public SkipNullsIterator(Iterable<T> iterable) { + mIter = iterable.iterator(); mNext = null; } @Override public boolean hasNext() { - while (mNext == null && mNextIndex < mLength) { - mNext = mRefs.get(mNextIndex); - mNextIndex++; + while (mNext == null && mIter.hasNext()) { + mNext = mIter.next(); } return mNext != null; } @Override - public Reference next() { + public T next() { if (!hasNext()) { throw new NoSuchElementException(); } - Reference next = mNext; + T next = mNext; mNext = null; return next; } @Override - public Iterator<Reference> iterator() { + public Iterator<T> iterator() { return this; } } diff --git a/tools/ahat/src/heapdump/SuperRoot.java b/tools/ahat/src/heapdump/SuperRoot.java index 54410cf1a6..d377113862 100644 --- a/tools/ahat/src/heapdump/SuperRoot.java +++ b/tools/ahat/src/heapdump/SuperRoot.java @@ -39,8 +39,8 @@ public class SuperRoot extends AhatInstance implements DominatorsComputation.Nod } @Override - ReferenceIterator getReferences() { - List<Reference> refs = new AbstractList<Reference>() { + Iterable<Reference> getReferences() { + return new AbstractList<Reference>() { @Override public int size() { return mRoots.size(); @@ -52,6 +52,5 @@ public class SuperRoot extends AhatInstance implements DominatorsComputation.Nod return new Reference(null, field, mRoots.get(index), true); } }; - return new ReferenceIterator(refs); } } diff --git a/tools/ahat/src/heapdump/Value.java b/tools/ahat/src/heapdump/Value.java index c1f30228e0..7f86c01efb 100644 --- a/tools/ahat/src/heapdump/Value.java +++ b/tools/ahat/src/heapdump/Value.java @@ -20,19 +20,72 @@ package com.android.ahat.heapdump; * Value represents a field value in a heap dump. The field value is either a * subclass of AhatInstance or a primitive Java type. */ -public class Value { - private Object mObject; +public abstract class Value { + public static Value pack(AhatInstance value) { + return value == null ? null : new InstanceValue(value); + } /** * Constructs a value from a generic Java Object. * The Object must either be a boxed Java primitive type or a subclass of * AhatInstance. The object must not be null. */ - public Value(Object object) { - // TODO: Check that the Object is either an AhatSnapshot or boxed Java - // primitive type? - assert object != null; - mObject = object; + public static Value pack(Object object) { + if (object == null) { + return null; + } else if (object instanceof AhatInstance) { + return Value.pack((AhatInstance)object); + } else if (object instanceof Boolean) { + return Value.pack(((Boolean)object).booleanValue()); + } else if (object instanceof Character) { + return Value.pack(((Character)object).charValue()); + } else if (object instanceof Float) { + return Value.pack(((Float)object).floatValue()); + } else if (object instanceof Double) { + return Value.pack(((Double)object).doubleValue()); + } else if (object instanceof Byte) { + return Value.pack(((Byte)object).byteValue()); + } else if (object instanceof Short) { + return Value.pack(((Short)object).shortValue()); + } else if (object instanceof Integer) { + return Value.pack(((Integer)object).intValue()); + } else if (object instanceof Long) { + return Value.pack(((Long)object).longValue()); + } + throw new IllegalArgumentException( + "AhatInstance or primitive type required, but got: " + object.toString()); + } + + public static Value pack(boolean value) { + return new BooleanValue(value); + } + + public static Value pack(char value) { + return new CharValue(value); + } + + public static Value pack(float value) { + return new FloatValue(value); + } + + public static Value pack(double value) { + return new DoubleValue(value); + } + + public static Value pack(byte value) { + return new ByteValue(value); + } + + public static Value pack(short value) { + return new ShortValue(value); + } + + public static Value pack(int value) { + return new IntValue(value); + } + + public static Value pack(long value) { + return new LongValue(value); } /** @@ -40,7 +93,7 @@ public class Value { * primitive value. */ public boolean isAhatInstance() { - return mObject instanceof AhatInstance; + return false; } /** @@ -48,9 +101,6 @@ public class Value { * Returns null if the Value represents a Java primitive value. */ public AhatInstance asAhatInstance() { - if (isAhatInstance()) { - return (AhatInstance)mObject; - } return null; } @@ -58,7 +108,7 @@ public class Value { * Returns true if the Value is an Integer. */ public boolean isInteger() { - return mObject instanceof Integer; + return false; } /** @@ -66,9 +116,6 @@ public class Value { * Returns null if the Value does not represent an Integer. */ public Integer asInteger() { - if (isInteger()) { - return (Integer)mObject; - } return null; } @@ -76,7 +123,7 @@ public class Value { * Returns true if the Value is an Long. */ public boolean isLong() { - return mObject instanceof Long; + return false; } /** @@ -84,9 +131,6 @@ public class Value { * Returns null if the Value does not represent an Long. */ public Long asLong() { - if (isLong()) { - return (Long)mObject; - } return null; } @@ -95,9 +139,6 @@ public class Value { * Returns null if the Value does not represent a Byte. */ public Byte asByte() { - if (mObject instanceof Byte) { - return (Byte)mObject; - } return null; } @@ -106,28 +147,255 @@ public class Value { * Returns null if the Value does not represent a Char. */ public Character asChar() { - if (mObject instanceof Character) { - return (Character)mObject; - } return null; } - public String toString() { - return mObject.toString(); + @Override + public abstract String toString(); + + public Value getBaseline() { + return this; } public static Value getBaseline(Value value) { - if (value == null || !value.isAhatInstance()) { - return value; + return value == null ? null : value.getBaseline(); + } + + @Override + public abstract boolean equals(Object other); + + private static class BooleanValue extends Value { + private boolean mBool; + + BooleanValue(boolean bool) { + mBool = bool; + } + + @Override + public String toString() { + return Boolean.toString(mBool); + } + + @Override public boolean equals(Object other) { + if (other instanceof BooleanValue) { + BooleanValue value = (BooleanValue)other; + return mBool == value.mBool; + } + return false; } - return new Value(value.asAhatInstance().getBaseline()); } - @Override public boolean equals(Object other) { - if (other instanceof Value) { - Value value = (Value)other; - return mObject.equals(value.mObject); + private static class ByteValue extends Value { + private byte mByte; + + ByteValue(byte b) { + mByte = b; + } + + @Override + public Byte asByte() { + return mByte; + } + + @Override + public String toString() { + return Byte.toString(mByte); + } + + @Override public boolean equals(Object other) { + if (other instanceof ByteValue) { + ByteValue value = (ByteValue)other; + return mByte == value.mByte; + } + return false; + } + } + + private static class CharValue extends Value { + private char mChar; + + CharValue(char c) { + mChar = c; + } + + @Override + public Character asChar() { + return mChar; + } + + @Override + public String toString() { + return Character.toString(mChar); + } + + @Override public boolean equals(Object other) { + if (other instanceof CharValue) { + CharValue value = (CharValue)other; + return mChar == value.mChar; + } + return false; + } + } + + private static class DoubleValue extends Value { + private double mDouble; + + DoubleValue(double d) { + mDouble = d; + } + + @Override + public String toString() { + return Double.toString(mDouble); + } + + @Override public boolean equals(Object other) { + if (other instanceof DoubleValue) { + DoubleValue value = (DoubleValue)other; + return mDouble == value.mDouble; + } + return false; + } + } + + private static class FloatValue extends Value { + private float mFloat; + + FloatValue(float f) { + mFloat = f; + } + + @Override + public String toString() { + return Float.toString(mFloat); + } + + @Override public boolean equals(Object other) { + if (other instanceof FloatValue) { + FloatValue value = (FloatValue)other; + return mFloat == value.mFloat; + } + return false; + } + } + + private static class InstanceValue extends Value { + private AhatInstance mInstance; + + InstanceValue(AhatInstance inst) { + assert(inst != null); + mInstance = inst; + } + + @Override + public boolean isAhatInstance() { + return true; + } + + @Override + public AhatInstance asAhatInstance() { + return mInstance; + } + + @Override + public String toString() { + return mInstance.toString(); + } + + @Override + public Value getBaseline() { + return InstanceValue.pack(mInstance.getBaseline()); + } + + @Override public boolean equals(Object other) { + if (other instanceof InstanceValue) { + InstanceValue value = (InstanceValue)other; + return mInstance.equals(value.mInstance); + } + return false; + } + } + + private static class IntValue extends Value { + private int mInt; + + IntValue(int i) { + mInt = i; + } + + @Override + public boolean isInteger() { + return true; + } + + @Override + public Integer asInteger() { + return mInt; + } + + @Override + public String toString() { + return Integer.toString(mInt); + } + + @Override public boolean equals(Object other) { + if (other instanceof IntValue) { + IntValue value = (IntValue)other; + return mInt == value.mInt; + } + return false; + } + } + + private static class LongValue extends Value { + private long mLong; + + LongValue(long l) { + mLong = l; + } + + @Override + public boolean isLong() { + return true; + } + + @Override + public Long asLong() { + return mLong; + } + + @Override + public String toString() { + return Long.toString(mLong); + } + + @Override public boolean equals(Object other) { + if (other instanceof LongValue) { + LongValue value = (LongValue)other; + return mLong == value.mLong; + } + return false; + } + } + + private static class ShortValue extends Value { + private short mShort; + + ShortValue(short s) { + mShort = s; + } + + @Override + public String toString() { + return Short.toString(mShort); + } + + @Override public boolean equals(Object other) { + if (other instanceof ShortValue) { + ShortValue value = (ShortValue)other; + return mShort == value.mShort; + } + return false; } - return false; } } diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/test-dump/Main.java index 14c09af01d..333d28c214 100644 --- a/tools/ahat/test-dump/Main.java +++ b/tools/ahat/test-dump/Main.java @@ -102,12 +102,23 @@ public class Main { public StackSmasher stackSmasherAdded; public static String modifiedStaticField; public int[] modifiedArray; + public Object objectAllocatedAtKnownSite1; + public Object objectAllocatedAtKnownSite2; + + private void allocateObjectAtKnownSite1() { + objectAllocatedAtKnownSite1 = new Object(); + allocateObjectAtKnownSite2(); + } + + private void allocateObjectAtKnownSite2() { + objectAllocatedAtKnownSite2 = new Object(); + } DumpedStuff(boolean baseline) { - int N = baseline ? 400000 : 1000000; - bigArray = new byte[N]; - for (int i = 0; i < N; i++) { - bigArray[i] = (byte)((i*i) & 0xFF); + int n = baseline ? 400000 : 1000000; + bigArray = new byte[n]; + for (int i = 0; i < n; i++) { + bigArray[i] = (byte)((i * i) & 0xFF); } // 0x12345, 50000, and 0xABCDABCD are arbitrary values. @@ -127,7 +138,9 @@ public class Main { modifiedObject.modifiedRefField = baseline ? "A1" : "A2"; modifiedObject.unmodifiedRefField = "B"; modifiedStaticField = baseline ? "C1" : "C2"; - modifiedArray = baseline ? new int[]{0,1,2,3} : new int[]{3,1,2,0}; + modifiedArray = baseline ? new int[]{0, 1, 2, 3} : new int[]{3, 1, 2, 0}; + + allocateObjectAtKnownSite1(); // Deep matching dominator trees shouldn't smash the stack when we try // to diff them. Make some deep dominator trees to help test it. diff --git a/tools/ahat/test/DiffFieldsTest.java b/tools/ahat/test/DiffFieldsTest.java index 6abdd47ef6..7dc519d60b 100644 --- a/tools/ahat/test/DiffFieldsTest.java +++ b/tools/ahat/test/DiffFieldsTest.java @@ -30,26 +30,26 @@ import static org.junit.Assert.assertNull; public class DiffFieldsTest { @Test public void normalMatchedDiffedFieldValues() { - FieldValue normal1 = new FieldValue("name", "type", new Value(1)); - FieldValue normal2 = new FieldValue("name", "type", new Value(2)); + FieldValue normal1 = new FieldValue("name", "type", Value.pack(1)); + FieldValue normal2 = new FieldValue("name", "type", Value.pack(2)); DiffedFieldValue x = DiffedFieldValue.matched(normal1, normal2); assertEquals("name", x.name); assertEquals("type", x.type); - assertEquals(new Value(1), x.current); - assertEquals(new Value(2), x.baseline); + assertEquals(Value.pack(1), x.current); + assertEquals(Value.pack(2), x.baseline); assertEquals(DiffedFieldValue.Status.MATCHED, x.status); } @Test public void nulledMatchedDiffedFieldValues() { - FieldValue normal = new FieldValue("name", "type", new Value(1)); + FieldValue normal = new FieldValue("name", "type", Value.pack(1)); FieldValue nulled = new FieldValue("name", "type", null); DiffedFieldValue x = DiffedFieldValue.matched(normal, nulled); assertEquals("name", x.name); assertEquals("type", x.type); - assertEquals(new Value(1), x.current); + assertEquals(Value.pack(1), x.current); assertNull(x.baseline); assertEquals(DiffedFieldValue.Status.MATCHED, x.status); @@ -57,18 +57,18 @@ public class DiffFieldsTest { assertEquals("name", y.name); assertEquals("type", y.type); assertNull(y.current); - assertEquals(new Value(1), y.baseline); + assertEquals(Value.pack(1), y.baseline); assertEquals(DiffedFieldValue.Status.MATCHED, y.status); } @Test public void normalAddedDiffedFieldValues() { - FieldValue normal = new FieldValue("name", "type", new Value(1)); + FieldValue normal = new FieldValue("name", "type", Value.pack(1)); DiffedFieldValue x = DiffedFieldValue.added(normal); assertEquals("name", x.name); assertEquals("type", x.type); - assertEquals(new Value(1), x.current); + assertEquals(Value.pack(1), x.current); assertEquals(DiffedFieldValue.Status.ADDED, x.status); } @@ -85,12 +85,12 @@ public class DiffFieldsTest { @Test public void normalDeletedDiffedFieldValues() { - FieldValue normal = new FieldValue("name", "type", new Value(1)); + FieldValue normal = new FieldValue("name", "type", Value.pack(1)); DiffedFieldValue x = DiffedFieldValue.deleted(normal); assertEquals("name", x.name); assertEquals("type", x.type); - assertEquals(new Value(1), x.baseline); + assertEquals(Value.pack(1), x.baseline); assertEquals(DiffedFieldValue.Status.DELETED, x.status); } diff --git a/tools/ahat/test/InstanceTest.java b/tools/ahat/test/InstanceTest.java index e05782c0b6..63055db93d 100644 --- a/tools/ahat/test/InstanceTest.java +++ b/tools/ahat/test/InstanceTest.java @@ -237,7 +237,7 @@ public class InstanceTest { public void gcRootPath() throws IOException { TestDump dump = TestDump.getTestDump(); - AhatClassObj main = dump.getAhatSnapshot().findClass("Main"); + AhatClassObj main = dump.findClass("Main"); AhatInstance gcPathArray = dump.getDumpedAhatInstance("gcPathArray"); Value value = gcPathArray.asArrayInstance().getValue(2); AhatInstance base = value.asAhatInstance(); @@ -333,7 +333,7 @@ public class InstanceTest { @Test public void classObjNotABitmap() throws IOException { TestDump dump = TestDump.getTestDump(); - AhatInstance obj = dump.getAhatSnapshot().findClass("Main"); + AhatInstance obj = dump.findClass("Main"); assertNull(obj.asBitmap()); } @@ -348,7 +348,7 @@ public class InstanceTest { @Test public void classObjToString() throws IOException { TestDump dump = TestDump.getTestDump(); - AhatInstance obj = dump.getAhatSnapshot().findClass("Main"); + AhatInstance obj = dump.findClass("Main"); assertEquals("class Main", obj.toString()); } diff --git a/tools/ahat/test/ObjectHandlerTest.java b/tools/ahat/test/ObjectHandlerTest.java index cd0ba23013..1b8a781e0c 100644 --- a/tools/ahat/test/ObjectHandlerTest.java +++ b/tools/ahat/test/ObjectHandlerTest.java @@ -42,7 +42,7 @@ public class ObjectHandlerTest { AhatSnapshot snapshot = dump.getAhatSnapshot(); AhatHandler handler = new ObjectHandler(snapshot); - AhatInstance object = snapshot.findClass("Main"); + AhatInstance object = dump.findClass("Main"); assertNotNull(object); TestHandler.testNoCrash(handler, "http://localhost:7100/object?id=" + object.getId()); @@ -55,7 +55,7 @@ public class ObjectHandlerTest { AhatSnapshot snapshot = dump.getAhatSnapshot(); AhatHandler handler = new ObjectHandler(snapshot); - AhatInstance object = snapshot.findClass("java.lang.String"); + AhatInstance object = dump.findClass("java.lang.String"); assertNotNull(object); TestHandler.testNoCrash(handler, "http://localhost:7100/object?id=" + object.getId()); diff --git a/tools/ahat/test/SiteTest.java b/tools/ahat/test/SiteTest.java new file mode 100644 index 0000000000..dc0fe08297 --- /dev/null +++ b/tools/ahat/test/SiteTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017 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. + */ + +package com.android.ahat; + +import com.android.ahat.heapdump.AhatInstance; +import com.android.ahat.heapdump.AhatSnapshot; +import com.android.ahat.heapdump.Site; +import java.io.IOException; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +public class SiteTest { + @Test + public void objectsAllocatedAtKnownSites() throws IOException { + TestDump dump = TestDump.getTestDump(); + AhatSnapshot snapshot = dump.getAhatSnapshot(); + + AhatInstance obj1 = dump.getDumpedAhatInstance("objectAllocatedAtKnownSite1"); + AhatInstance obj2 = dump.getDumpedAhatInstance("objectAllocatedAtKnownSite2"); + Site s2 = obj2.getSite(); + Site s1b = s2.getParent(); + Site s1a = obj1.getSite(); + Site s = s1a.getParent(); + + // TODO: The following commented out assertion fails due to a problem with + // proguard deobfuscation. That bug should be fixed. + // assertEquals("Main.java", s.getFilename()); + + assertEquals("Main.java", s1a.getFilename()); + assertEquals("Main.java", s1b.getFilename()); + assertEquals("Main.java", s2.getFilename()); + + assertEquals("allocateObjectAtKnownSite1", s1a.getMethodName()); + assertEquals("allocateObjectAtKnownSite1", s1b.getMethodName()); + assertEquals("allocateObjectAtKnownSite2", s2.getMethodName()); + + // TODO: The following commented out assertion fails due to a problem with + // stack frame line numbers - we don't get different line numbers + // for the different sites, so they are indistiguishable. The + // problem with line numbers should be understood and fixed. + // assertNotSame(s1a, s1b); + + assertSame(s1a.getParent(), s1b.getParent()); + + assertSame(s, snapshot.getSite(s.getId())); + assertSame(s1a, snapshot.getSite(s1a.getId())); + assertSame(s1b, snapshot.getSite(s1b.getId())); + assertSame(s2, snapshot.getSite(s2.getId())); + } +} diff --git a/tools/ahat/test/TestDump.java b/tools/ahat/test/TestDump.java index 3dce2dccfe..db9b25646a 100644 --- a/tools/ahat/test/TestDump.java +++ b/tools/ahat/test/TestDump.java @@ -21,11 +21,14 @@ import com.android.ahat.heapdump.AhatInstance; import com.android.ahat.heapdump.AhatSnapshot; import com.android.ahat.heapdump.Diff; import com.android.ahat.heapdump.FieldValue; +import com.android.ahat.heapdump.Site; import com.android.ahat.heapdump.Value; import com.android.tools.perflib.heap.ProguardMap; import java.io.File; import java.io.IOException; import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collection; /** * The TestDump class is used to get an AhatSnapshot for the test-dump @@ -45,8 +48,10 @@ public class TestDump { // fails and don't try to load it again. private static boolean mTestDumpFailed = false; - private AhatSnapshot mSnapshot = null; - private AhatSnapshot mBaseline = null; + private AhatSnapshot mSnapshot; + private AhatSnapshot mBaseline; + private AhatClassObj mMain; + private AhatClassObj mBaselineMain; /** * Load the test-dump.hprof and test-dump-base.hprof files. @@ -79,6 +84,12 @@ public class TestDump { mSnapshot = AhatSnapshot.fromHprof(new File(hprof), map); mBaseline = AhatSnapshot.fromHprof(new File(hprofBase), map); Diff.snapshots(mSnapshot, mBaseline); + + mMain = findClass(mSnapshot, "Main"); + assert(mMain != null); + + mBaselineMain = findClass(mBaseline, "Main"); + assert(mBaselineMain != null); } /** @@ -100,7 +111,7 @@ public class TestDump { * snapshot for the test-dump program. */ public Value getDumpedValue(String name) { - return getDumpedValue(name, mSnapshot); + return getDumpedValue(name, mMain); } /** @@ -108,15 +119,14 @@ public class TestDump { * baseline snapshot for the test-dump program. */ public Value getBaselineDumpedValue(String name) { - return getDumpedValue(name, mBaseline); + return getDumpedValue(name, mBaselineMain); } /** - * Returns the value of a field in the DumpedStuff instance in the - * given snapshot for the test-dump program. + * Returns the value of a field in the DumpedStuff instance given the Main + * class object for the snapshot. */ - private Value getDumpedValue(String name, AhatSnapshot snapshot) { - AhatClassObj main = snapshot.findClass("Main"); + private static Value getDumpedValue(String name, AhatClassObj main) { AhatInstance stuff = null; for (FieldValue field : main.getStaticFieldValues()) { if ("stuff".equals(field.name)) { @@ -127,6 +137,33 @@ public class TestDump { } /** + * Returns a class object in the given heap dump whose name matches the + * given name, or null if no such class object could be found. + */ + private static AhatClassObj findClass(AhatSnapshot snapshot, String name) { + Site root = snapshot.getRootSite(); + Collection<AhatInstance> classes = new ArrayList<AhatInstance>(); + root.getObjects(null, "java.lang.Class", classes); + for (AhatInstance inst : classes) { + if (inst.isClassObj()) { + AhatClassObj cls = inst.asClassObj(); + if (name.equals(cls.getName())) { + return cls; + } + } + } + return null; + } + + /** + * Returns a class object in the heap dump whose name matches the given + * name, or null if no such class object could be found. + */ + public AhatClassObj findClass(String name) { + return findClass(mSnapshot, name); + } + + /** * Returns the value of a non-primitive field in the DumpedStuff instance in * the snapshot for the test-dump program. */ diff --git a/tools/ahat/test/Tests.java b/tools/ahat/test/Tests.java index a1e3246cd1..cd33a9059b 100644 --- a/tools/ahat/test/Tests.java +++ b/tools/ahat/test/Tests.java @@ -33,6 +33,7 @@ public class Tests { "com.android.ahat.RootedHandlerTest", "com.android.ahat.QueryTest", "com.android.ahat.SiteHandlerTest", + "com.android.ahat.SiteTest", }; } JUnitCore.main(args); diff --git a/tools/titrace/Android.bp b/tools/titrace/Android.bp new file mode 100644 index 0000000000..b95ec9db25 --- /dev/null +++ b/tools/titrace/Android.bp @@ -0,0 +1,77 @@ +// +// Copyright (C) 2017 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. +// + +// Build variants {target,host} x {debug,ndebug} x {32,64} + +cc_defaults { + name: "titrace-defaults", + host_supported: true, + srcs: ["titrace.cc", "instruction_decoder.cc"], + defaults: ["art_defaults"], + + // Note that this tool needs to be built for both 32-bit and 64-bit since it requires + // to be same ISA as what it is attached to. + compile_multilib: "both", + + shared_libs: [ + "libbase" + ], + target: { + android: { + }, + host: { + }, + }, + header_libs: [ + "libopenjdkjvmti_headers", + "libart_runtime_headers" // for dex_instruction_list.h only + // "libbase_headers", + ], + multilib: { + lib32: { + suffix: "32", + }, + lib64: { + suffix: "64", + }, + }, + symlink_preferred_arch: true, +} + +art_cc_library { + name: "libtitrace", + defaults: ["titrace-defaults"], + shared_libs: [ + ], +} + +art_cc_library { + name: "libtitraced", + defaults: [ + "art_debug_defaults", + "titrace-defaults", + ], + shared_libs: [ + ], +} + +//art_cc_test { +// name: "art_titrace_tests", +// defaults: [ +// "art_gtest_defaults", +// ], +// srcs: ["titrace_test.cc"], +//} diff --git a/tools/titrace/README.md b/tools/titrace/README.md new file mode 100644 index 0000000000..a82025be8b --- /dev/null +++ b/tools/titrace/README.md @@ -0,0 +1,62 @@ +# Titrace + +Titrace is a bytecode instruction tracing tool that uses JVMTI and works on both ART and the RI. + +# Usage +### Build +> `make libtitrace` # or 'make libtitraced' with debugging checks enabled + +The libraries will be built for 32-bit, 64-bit, host and target. Below examples assume you want to use the 64-bit version. +### Command Line +#### ART +> `art -Xplugin:$ANDROID_HOST_OUT/lib64/libopenjdkjvmti.so -agentpath:$ANDROID_HOST_OUT/lib64/libtitrace.so -cp tmp/java/helloworld.dex -Xint helloworld` + +* `-Xplugin` and `-agentpath` need to be used, otherwise libtitrace agent will fail during init. +* If using `libartd.so`, make sure to use the debug version of jvmti. +#### Reference Implementation +> `java -agentpath:$ANDROID_HOST_OUT/lib64/libtitrace.so helloworld` + +Only needs `-agentpath` to be specified. +### Android Applications +Replace __com.littleinc.orm_benchmark__ with the name of your application below. +#### Enable permissions for attaching an agent +Normal applications require that `debuggable=true` to be set in their AndroidManifest.xml. + +By using a *eng* or *userdebug* build of Android, we can override this requirement: +> `adb root` +> `adb shell setprop dalvik.vm.dex2oat-flags --debuggable` + +Then restart the runtime to pick it up. +> `adb shell stop && adb shell start` + +If this step is skipped, attaching the agent will not succeed. +#### Deploy agent to device +The agent must be located in an app-accessible directory. + +> `adb push $ANDROID_PRODUCT_OUT/system/lib64/libtitrace.so /data/local/tmp` + +Upload to device first (it gets shell/root permissions). + +> `adb shell run-as com.littleinc.orm_benchmark 'cp /data/local/tmp/libtitrace.so /data/data/com.littleinc.orm_benchmark/files/libtitrace.so'` + +Copy the agent into an app-accessible directory, and make the file owned by the app. + +#### Attach agent to application + +##### Option 1: Attach the agent before any app code runs. +> `adb shell am start --attach-agent /data/data/com.littleinc.orm_benchmark/files/libtitrace.so com.littleinc.orm_benchmark/.MainActivity` + +Note: To determine the arguments to `am start`, launch the application manually first and then look for this in logcat: + +> 09-14 13:28:08.680 7584 8192 I ActivityManager: Start proc 17614:com.littleinc.orm_benchmark/u0a138 for activity **com.littleinc.orm_benchmark/.MainActivity** + +##### Option 2: Attach the agent to an already-running app. +> `adb shell am attach-agent $(pid com.littleinc.orm_benchmark) /data/data/com.littleinc.orm_benchmark/files/libtitrace.so` + +### Printing the Results +All statitics gathered during the trace are printed automatically when the program normally exists. In the case of Android applications, they are always killed, so we need to manually print the results. + +> `kill -SIGQUIT $(pid com.littleinc.orm_benchmark)` + +Will initiate a dump of the agent (to logcat). + diff --git a/tools/titrace/instruction_decoder.cc b/tools/titrace/instruction_decoder.cc new file mode 100644 index 0000000000..5d2f22feb3 --- /dev/null +++ b/tools/titrace/instruction_decoder.cc @@ -0,0 +1,518 @@ +// Copyright (C) 2017 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 "instruction_decoder.h" + +#include "dex_instruction_list.h" + +#include <android-base/logging.h> + +namespace titrace { + +class ClassInstructionDecoder : public InstructionDecoder { + public: + virtual size_t GetMaximumOpcode() override { + return 0xff; + } + + virtual const char* GetName(size_t opcode) override { + Bytecode::Opcode op = static_cast<Bytecode::Opcode>(opcode); + return Bytecode::ToString(op); + } + + virtual size_t LocationToOffset(size_t j_location) { + return j_location; + } + + private: + class Bytecode { + public: + enum Opcode { + // Java bytecode opcodes from 0x00 to 0xFF. + kNop = 0x00, + kAconst_null = 0x01, + kIconst_m1 = 0x02, + kIconst_0 = 0x03, + kIconst_1 = 0x04, + kIconst_2 = 0x05, + kIconst_3 = 0x06, + kIconst_4 = 0x07, + kIconst_5 = 0x08, + kLconst_0 = 0x09, + kLconst_1 = 0x0a, + kFconst_0 = 0x0b, + kFconst_1 = 0x0c, + kFconst_2 = 0x0d, + kDconst_0 = 0x0e, + kDconst_1 = 0x0f, + kBipush = 0x10, + kSipush = 0x11, + kLdc = 0x12, + kLdc_w = 0x13, + kLdc2_w = 0x14, + kIload = 0x15, + kLload = 0x16, + kFload = 0x17, + kDload = 0x18, + kAload = 0x19, + kIload_0 = 0x1a, + kIload_1 = 0x1b, + kIload_2 = 0x1c, + kIload_3 = 0x1d, + kLload_0 = 0x1e, + kLload_1 = 0x1f, + kLload_2 = 0x20, + kLload_3 = 0x21, + kFload_0 = 0x22, + kFload_1 = 0x23, + kFload_2 = 0x24, + kFload_3 = 0x25, + kDload_0 = 0x26, + kDload_1 = 0x27, + kDload_2 = 0x28, + kDload_3 = 0x29, + kAload_0 = 0x2a, + kAload_1 = 0x2b, + kAload_2 = 0x2c, + kAload_3 = 0x2d, + kIaload = 0x2e, + kLaload = 0x2f, + kFaload = 0x30, + kDaload = 0x31, + kAaload = 0x32, + kBaload = 0x33, + kCaload = 0x34, + kSaload = 0x35, + kIstore = 0x36, + kLstore = 0x37, + kFstore = 0x38, + kDstore = 0x39, + kAstore = 0x3a, + kIstore_0 = 0x3b, + kIstore_1 = 0x3c, + kIstore_2 = 0x3d, + kIstore_3 = 0x3e, + kLstore_0 = 0x3f, + kLstore_1 = 0x40, + kLstore_2 = 0x41, + kLstore_3 = 0x42, + kFstore_0 = 0x43, + kFstore_1 = 0x44, + kFstore_2 = 0x45, + kFstore_3 = 0x46, + kDstore_0 = 0x47, + kDstore_1 = 0x48, + kDstore_2 = 0x49, + kDstore_3 = 0x4a, + kAstore_0 = 0x4b, + kAstore_1 = 0x4c, + kAstore_2 = 0x4d, + kAstore_3 = 0x4e, + kIastore = 0x4f, + kLastore = 0x50, + kFastore = 0x51, + kDastore = 0x52, + kAastore = 0x53, + kBastore = 0x54, + kCastore = 0x55, + kSastore = 0x56, + kPop = 0x57, + kPop2 = 0x58, + kDup = 0x59, + kDup_x1 = 0x5a, + kDup_x2 = 0x5b, + kDup2 = 0x5c, + kDup2_x1 = 0x5d, + kDup2_x2 = 0x5e, + kSwap = 0x5f, + kIadd = 0x60, + kLadd = 0x61, + kFadd = 0x62, + kDadd = 0x63, + kIsub = 0x64, + kLsub = 0x65, + kFsub = 0x66, + kDsub = 0x67, + kImul = 0x68, + kLmul = 0x69, + kFmul = 0x6a, + kDmul = 0x6b, + kIdiv = 0x6c, + kLdiv = 0x6d, + kFdiv = 0x6e, + kDdiv = 0x6f, + kIrem = 0x70, + kLrem = 0x71, + kFrem = 0x72, + kDrem = 0x73, + kIneg = 0x74, + kLneg = 0x75, + kFneg = 0x76, + kDneg = 0x77, + kIshl = 0x78, + kLshl = 0x79, + kIshr = 0x7a, + kLshr = 0x7b, + kIushr = 0x7c, + kLushr = 0x7d, + kIand = 0x7e, + kLand = 0x7f, + kIor = 0x80, + kLor = 0x81, + kIxor = 0x82, + kLxor = 0x83, + kIinc = 0x84, + kI2l = 0x85, + kI2f = 0x86, + kI2d = 0x87, + kL2i = 0x88, + kL2f = 0x89, + kL2d = 0x8a, + kF2i = 0x8b, + kF2l = 0x8c, + kF2d = 0x8d, + kD2i = 0x8e, + kD2l = 0x8f, + kD2f = 0x90, + kI2b = 0x91, + kI2c = 0x92, + kI2s = 0x93, + kLcmp = 0x94, + kFcmpl = 0x95, + kFcmpg = 0x96, + kDcmpl = 0x97, + kDcmpg = 0x98, + kIfeq = 0x99, + kIfne = 0x9a, + kIflt = 0x9b, + kIfge = 0x9c, + kIfgt = 0x9d, + kIfle = 0x9e, + kIf_icmpeq = 0x9f, + kIf_icmpne = 0xa0, + kIf_icmplt = 0xa1, + kIf_icmpge = 0xa2, + kIf_icmpgt = 0xa3, + kIf_icmple = 0xa4, + kIf_acmpeq = 0xa5, + kIf_acmpne = 0xa6, + kGoto = 0xa7, + kJsr = 0xa8, + kRet = 0xa9, + kTableswitch = 0xaa, + kLookupswitch = 0xab, + kIreturn = 0xac, + kLreturn = 0xad, + kFreturn = 0xae, + kDreturn = 0xaf, + kAreturn = 0xb0, + kReturn = 0xb1, + kGetstatic = 0xb2, + kPutstatic = 0xb3, + kGetfield = 0xb4, + kPutfield = 0xb5, + kInvokevirtual = 0xb6, + kInvokespecial = 0xb7, + kInvokestatic = 0xb8, + kInvokeinterface = 0xb9, + kInvokedynamic = 0xba, + kNew = 0xbb, + kNewarray = 0xbc, + kAnewarray = 0xbd, + kArraylength = 0xbe, + kAthrow = 0xbf, + kCheckcast = 0xc0, + kInstanceof = 0xc1, + kMonitorenter = 0xc2, + kMonitorexit = 0xc3, + kWide = 0xc4, + kMultianewarray = 0xc5, + kIfnull = 0xc6, + kIfnonnull = 0xc7, + kGoto_w = 0xc8, + kJsr_w = 0xc9, + kBreakpoint = 0xca, + // Instructions 0xcb-0xfd are undefined. + kImpdep1 = 0xfe, + kImpdep2 = 0xff, + }; + + static const char* ToString(Bytecode::Opcode op) { + switch (op) { + case kNop: return "nop"; + case kAconst_null: return "aconst_null"; + case kIconst_m1: return "iconst_m1"; + case kIconst_0: return "iconst_0"; + case kIconst_1: return "iconst_1"; + case kIconst_2: return "iconst_2"; + case kIconst_3: return "iconst_3"; + case kIconst_4: return "iconst_4"; + case kIconst_5: return "iconst_5"; + case kLconst_0: return "lconst_0"; + case kLconst_1: return "lconst_1"; + case kFconst_0: return "fconst_0"; + case kFconst_1: return "fconst_1"; + case kFconst_2: return "fconst_2"; + case kDconst_0: return "dconst_0"; + case kDconst_1: return "dconst_1"; + case kBipush: return "bipush"; + case kSipush: return "sipush"; + case kLdc: return "ldc"; + case kLdc_w: return "ldc_w"; + case kLdc2_w: return "ldc2_w"; + case kIload: return "iload"; + case kLload: return "lload"; + case kFload: return "fload"; + case kDload: return "dload"; + case kAload: return "aload"; + case kIload_0: return "iload_0"; + case kIload_1: return "iload_1"; + case kIload_2: return "iload_2"; + case kIload_3: return "iload_3"; + case kLload_0: return "lload_0"; + case kLload_1: return "lload_1"; + case kLload_2: return "lload_2"; + case kLload_3: return "lload_3"; + case kFload_0: return "fload_0"; + case kFload_1: return "fload_1"; + case kFload_2: return "fload_2"; + case kFload_3: return "fload_3"; + case kDload_0: return "dload_0"; + case kDload_1: return "dload_1"; + case kDload_2: return "dload_2"; + case kDload_3: return "dload_3"; + case kAload_0: return "aload_0"; + case kAload_1: return "aload_1"; + case kAload_2: return "aload_2"; + case kAload_3: return "aload_3"; + case kIaload: return "iaload"; + case kLaload: return "laload"; + case kFaload: return "faload"; + case kDaload: return "daload"; + case kAaload: return "aaload"; + case kBaload: return "baload"; + case kCaload: return "caload"; + case kSaload: return "saload"; + case kIstore: return "istore"; + case kLstore: return "lstore"; + case kFstore: return "fstore"; + case kDstore: return "dstore"; + case kAstore: return "astore"; + case kIstore_0: return "istore_0"; + case kIstore_1: return "istore_1"; + case kIstore_2: return "istore_2"; + case kIstore_3: return "istore_3"; + case kLstore_0: return "lstore_0"; + case kLstore_1: return "lstore_1"; + case kLstore_2: return "lstore_2"; + case kLstore_3: return "lstore_3"; + case kFstore_0: return "fstore_0"; + case kFstore_1: return "fstore_1"; + case kFstore_2: return "fstore_2"; + case kFstore_3: return "fstore_3"; + case kDstore_0: return "dstore_0"; + case kDstore_1: return "dstore_1"; + case kDstore_2: return "dstore_2"; + case kDstore_3: return "dstore_3"; + case kAstore_0: return "astore_0"; + case kAstore_1: return "astore_1"; + case kAstore_2: return "astore_2"; + case kAstore_3: return "astore_3"; + case kIastore: return "iastore"; + case kLastore: return "lastore"; + case kFastore: return "fastore"; + case kDastore: return "dastore"; + case kAastore: return "aastore"; + case kBastore: return "bastore"; + case kCastore: return "castore"; + case kSastore: return "sastore"; + case kPop: return "pop"; + case kPop2: return "pop2"; + case kDup: return "dup"; + case kDup_x1: return "dup_x1"; + case kDup_x2: return "dup_x2"; + case kDup2: return "dup2"; + case kDup2_x1: return "dup2_x1"; + case kDup2_x2: return "dup2_x2"; + case kSwap: return "swap"; + case kIadd: return "iadd"; + case kLadd: return "ladd"; + case kFadd: return "fadd"; + case kDadd: return "dadd"; + case kIsub: return "isub"; + case kLsub: return "lsub"; + case kFsub: return "fsub"; + case kDsub: return "dsub"; + case kImul: return "imul"; + case kLmul: return "lmul"; + case kFmul: return "fmul"; + case kDmul: return "dmul"; + case kIdiv: return "idiv"; + case kLdiv: return "ldiv"; + case kFdiv: return "fdiv"; + case kDdiv: return "ddiv"; + case kIrem: return "irem"; + case kLrem: return "lrem"; + case kFrem: return "frem"; + case kDrem: return "drem"; + case kIneg: return "ineg"; + case kLneg: return "lneg"; + case kFneg: return "fneg"; + case kDneg: return "dneg"; + case kIshl: return "ishl"; + case kLshl: return "lshl"; + case kIshr: return "ishr"; + case kLshr: return "lshr"; + case kIushr: return "iushr"; + case kLushr: return "lushr"; + case kIand: return "iand"; + case kLand: return "land"; + case kIor: return "ior"; + case kLor: return "lor"; + case kIxor: return "ixor"; + case kLxor: return "lxor"; + case kIinc: return "iinc"; + case kI2l: return "i2l"; + case kI2f: return "i2f"; + case kI2d: return "i2d"; + case kL2i: return "l2i"; + case kL2f: return "l2f"; + case kL2d: return "l2d"; + case kF2i: return "f2i"; + case kF2l: return "f2l"; + case kF2d: return "f2d"; + case kD2i: return "d2i"; + case kD2l: return "d2l"; + case kD2f: return "d2f"; + case kI2b: return "i2b"; + case kI2c: return "i2c"; + case kI2s: return "i2s"; + case kLcmp: return "lcmp"; + case kFcmpl: return "fcmpl"; + case kFcmpg: return "fcmpg"; + case kDcmpl: return "dcmpl"; + case kDcmpg: return "dcmpg"; + case kIfeq: return "ifeq"; + case kIfne: return "ifne"; + case kIflt: return "iflt"; + case kIfge: return "ifge"; + case kIfgt: return "ifgt"; + case kIfle: return "ifle"; + case kIf_icmpeq: return "if_icmpeq"; + case kIf_icmpne: return "if_icmpne"; + case kIf_icmplt: return "if_icmplt"; + case kIf_icmpge: return "if_icmpge"; + case kIf_icmpgt: return "if_icmpgt"; + case kIf_icmple: return "if_icmple"; + case kIf_acmpeq: return "if_acmpeq"; + case kIf_acmpne: return "if_acmpne"; + case kGoto: return "goto"; + case kJsr: return "jsr"; + case kRet: return "ret"; + case kTableswitch: return "tableswitch"; + case kLookupswitch: return "lookupswitch"; + case kIreturn: return "ireturn"; + case kLreturn: return "lreturn"; + case kFreturn: return "freturn"; + case kDreturn: return "dreturn"; + case kAreturn: return "areturn"; + case kReturn: return "return"; + case kGetstatic: return "getstatic"; + case kPutstatic: return "putstatic"; + case kGetfield: return "getfield"; + case kPutfield: return "putfield"; + case kInvokevirtual: return "invokevirtual"; + case kInvokespecial: return "invokespecial"; + case kInvokestatic: return "invokestatic"; + case kInvokeinterface: return "invokeinterface"; + case kInvokedynamic: return "invokedynamic"; + case kNew: return "new"; + case kNewarray: return "newarray"; + case kAnewarray: return "anewarray"; + case kArraylength: return "arraylength"; + case kAthrow: return "athrow"; + case kCheckcast: return "checkcast"; + case kInstanceof: return "instanceof"; + case kMonitorenter: return "monitorenter"; + case kMonitorexit: return "monitorexit"; + case kWide: return "wide"; + case kMultianewarray: return "multianewarray"; + case kIfnull: return "ifnull"; + case kIfnonnull: return "ifnonnull"; + case kGoto_w: return "goto_w"; + case kJsr_w: return "jsr_w"; + case kBreakpoint: return "breakpoint"; + case kImpdep1: return "impdep1"; + case kImpdep2: return "impdep2"; + default: LOG(FATAL) << "Unknown opcode " << op; + } + return ""; + } + }; +}; + +class DexInstructionDecoder : public InstructionDecoder { + public: + virtual size_t GetMaximumOpcode() override { + return 0xff; + } + + virtual const char* GetName(size_t opcode) override { + Bytecode::Opcode op = static_cast<Bytecode::Opcode>(opcode); + return Bytecode::ToString(op); + } + + virtual size_t LocationToOffset(size_t j_location) { + // dex pc is uint16_t*, but offset needs to be in bytes. + return j_location * (sizeof(uint16_t) / sizeof(uint8_t)); + } + + private: + class Bytecode { + public: + enum Opcode { +#define MAKE_ENUM_DEFINITION(opcode, instruction_code, name, format, index, flags, extended_flags, verifier_flags) \ + instruction_code = opcode, +DEX_INSTRUCTION_LIST(MAKE_ENUM_DEFINITION) +#undef MAKE_ENUM_DEFINITION + }; + + static_assert(static_cast<uint32_t>(Bytecode::Opcode::NOP) == 0, ""); + static_assert(static_cast<uint32_t>(Bytecode::Opcode::MOVE) == 1, ""); + + static const char* ToString(Bytecode::Opcode op) { + switch (op) { +#define MAKE_ENUM_DEFINITION(opcode, instruction_code, name, format, index, flags, extended_flags, verifier_flags) \ + case instruction_code: return (name); +DEX_INSTRUCTION_LIST(MAKE_ENUM_DEFINITION) +#undef MAKE_ENUM_DEFINITION + default: LOG(FATAL) << "Unknown opcode " << op; + } + return ""; + } + }; +}; + +InstructionDecoder* InstructionDecoder::NewInstance(InstructionFileFormat file_format) { + switch (file_format) { + case InstructionFileFormat::kClass: + return new ClassInstructionDecoder(); + case InstructionFileFormat::kDex: + return new DexInstructionDecoder(); + default: + return nullptr; + } +} +} // namespace titrace diff --git a/tools/titrace/instruction_decoder.h b/tools/titrace/instruction_decoder.h new file mode 100644 index 0000000000..34be2e903d --- /dev/null +++ b/tools/titrace/instruction_decoder.h @@ -0,0 +1,42 @@ +// Copyright (C) 2017 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_TOOLS_TITRACE_INSTRUCTION_DECODER_H_ +#define ART_TOOLS_TITRACE_INSTRUCTION_DECODER_H_ + +#include <stddef.h> + +namespace titrace { + +enum class InstructionFileFormat { + kClass, + kDex +}; + +class InstructionDecoder { + public: + virtual size_t GetMaximumOpcode() = 0; + virtual const char* GetName(size_t opcode) = 0; + virtual size_t LocationToOffset(size_t j_location) = 0; + + virtual ~InstructionDecoder() {} + + static InstructionDecoder* NewInstance(InstructionFileFormat file_format); +}; + +} // namespace titrace + +#endif // ART_TOOLS_TITRACE_INSTRUCTION_DECODER_H_ diff --git a/tools/titrace/titrace.cc b/tools/titrace/titrace.cc new file mode 100644 index 0000000000..bf1218b006 --- /dev/null +++ b/tools/titrace/titrace.cc @@ -0,0 +1,309 @@ +// Copyright (C) 2017 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 "instruction_decoder.h" + +#include <android-base/logging.h> +#include <atomic> +#include <jni.h> +#include <jvmti.h> +#include <map> +#include <memory> +#include <mutex> + +// We could probably return a JNI_ERR here but lets crash instead if something fails. +#define CHECK_JVMTI_ERROR(jvmti, errnum) \ + CHECK_EQ(JVMTI_ERROR_NONE, (errnum)) << GetJvmtiErrorString((jvmti), (errnum)) << (" ") + +namespace titrace { + +static const char* GetJvmtiErrorString(jvmtiEnv* jvmti, jvmtiError errnum) { + char* errnum_str = nullptr; + jvmti->GetErrorName(errnum, /*out*/ &errnum_str); + if (errnum_str == nullptr) { + return "Unknown"; + } + + return errnum_str; +} + +// Type-safe wrapper for JVMTI-allocated memory. +// Deallocates with jvmtiEnv::Deallocate. +template <typename T> +struct TiMemory { + explicit TiMemory(jvmtiEnv* env, T* mem, size_t size) : env_(env), mem_(mem), size_(size) { + } + + ~TiMemory() { + if (mem_ != nullptr) { + env_->Deallocate(static_cast<unsigned char*>(mem_)); + } + mem_ = nullptr; + } + + TiMemory(const TiMemory& other) = delete; + TiMemory(TiMemory&& other) { + env_ = other.env_; + mem_ = other.mem_; + size_ = other.size_; + + if (this != &other) { + other.env_ = nullptr; + other.mem_ = nullptr; + other.size_ = 0u; + } + } + + TiMemory& operator=(TiMemory&& other) { + if (mem_ != other.mem_) { + TiMemory::~TiMemory(); + } + new (this) TiMemory(std::move(other)); + return *this; + } + + T* GetMemory() { + return mem_; + } + + size_t Size() { + return size_ / sizeof(T); + } + + private: + jvmtiEnv* env_; + T* mem_; + size_t size_; +}; + +struct MethodBytecode { + explicit MethodBytecode(jvmtiEnv* env, unsigned char* memory, jint size) + : bytecode_(env, memory, static_cast<size_t>(size)) { + } + + TiMemory<uint8_t> bytecode_; +}; + +struct TraceStatistics { + static void Initialize(jvmtiEnv* jvmti) { + TraceStatistics& stats = GetSingleton(); + + bool is_ri = true; + { + jvmtiError error; + char* value_ptr; + error = jvmti->GetSystemProperty("java.vm.name", /*out*/ &value_ptr); + CHECK_JVMTI_ERROR(jvmti, error) << "Failed to get property 'java.vm.name'"; + CHECK(value_ptr != nullptr) << "Returned property was null for 'java.vm.name'"; + + if (strcmp("Dalvik", value_ptr) == 0) { + is_ri = false; + } + } + + InstructionFileFormat format = + is_ri ? InstructionFileFormat::kClass : InstructionFileFormat::kDex; + stats.instruction_decoder_.reset(InstructionDecoder::NewInstance(format)); + + CHECK_GE(arraysize(stats.instruction_counter_), + stats.instruction_decoder_->GetMaximumOpcode()); + } + + static TraceStatistics& GetSingleton() { + static TraceStatistics stats; + return stats; + } + + void Log() { + LOG(INFO) << "================================================"; + LOG(INFO) << " TI Trace // Summary "; + LOG(INFO) << "++++++++++++++++++++++++++++++++++++++++++++++++"; + LOG(INFO) << " * Single step counter: " << single_step_counter_; + LOG(INFO) << "+++++++++++ Instructions Count ++++++++++++"; + + size_t total = single_step_counter_; + for (size_t i = 0; i < arraysize(instruction_counter_); ++i) { + size_t inst_count = instruction_counter_[i]; + if (inst_count > 0) { + const char* opcode_name = instruction_decoder_->GetName(i); + LOG(INFO) << " * " << opcode_name << "(op:" << i << "), count: " << inst_count + << ", % of total: " << (100.0 * inst_count / total); + } + } + + LOG(INFO) << "------------------------------------------------"; + } + + void OnSingleStep(jvmtiEnv* jvmti_env, jmethodID method, jlocation location) { + // Counters do not need a happens-before. + // Use the weakest memory order simply to avoid tearing. + single_step_counter_.fetch_add(1u, std::memory_order_relaxed); + + MethodBytecode& bytecode = LookupBytecode(jvmti_env, method); + + // Decode jlocation value that depends on the bytecode format. + size_t actual_location = instruction_decoder_->LocationToOffset(static_cast<size_t>(location)); + + // Decode the exact instruction and increment its counter. + CHECK_LE(actual_location, bytecode.bytecode_.Size()); + RecordInstruction(bytecode.bytecode_.GetMemory() + actual_location); + } + + private: + void RecordInstruction(const uint8_t* instruction) { + uint8_t opcode = instruction[0]; + // Counters do not need a happens-before. + // Use the weakest memory order simply to avoid tearing. + instruction_counter_[opcode].fetch_add(1u, std::memory_order_relaxed); + } + + MethodBytecode& LookupBytecode(jvmtiEnv* jvmti_env, jmethodID method) { + jvmtiError error; + std::lock_guard<std::mutex> lock(bytecode_cache_mutex_); + + auto it = bytecode_cache_.find(method); + if (it == bytecode_cache_.end()) { + jint bytecode_count_ptr = 0; + unsigned char* bytecodes_ptr = nullptr; + + error = jvmti_env->GetBytecodes(method, &bytecode_count_ptr, &bytecodes_ptr); + CHECK_JVMTI_ERROR(jvmti_env, error) << "Failed to get bytecodes for method " << method; + CHECK(bytecodes_ptr != nullptr) << "Bytecode ptr was null for method " << method; + CHECK_GE(bytecode_count_ptr, 0) << "Bytecode size too small for method " << method; + + // std::pair<iterator, bool inserted> + auto&& pair = bytecode_cache_.insert( + std::make_pair(method, MethodBytecode(jvmti_env, bytecodes_ptr, bytecode_count_ptr))); + it = pair.first; + } + + // Returning the address is safe. if map is resized, the contents will not move. + return it->second; + } + + std::unique_ptr<InstructionDecoder> instruction_decoder_; + + std::atomic<size_t> single_step_counter_{0u}; // NOLINT [readability/braces] [4] [whitespace/braces] [5] + std::atomic<size_t> instruction_counter_[256]{}; // NOLINT [whitespace/braces] [5] + + // Cache the bytecode to avoid calling into JVMTI repeatedly. + // TODO: invalidate if the bytecode was updated? + std::map<jmethodID, MethodBytecode> bytecode_cache_; + // bytecode cache is thread-safe. + std::mutex bytecode_cache_mutex_; +}; + +struct EventCallbacks { + static void SingleStep(jvmtiEnv* jvmti_env, + JNIEnv* jni_env ATTRIBUTE_UNUSED, + jthread thread ATTRIBUTE_UNUSED, + jmethodID method, + jlocation location) { + TraceStatistics& stats = TraceStatistics::GetSingleton(); + stats.OnSingleStep(jvmti_env, method, location); + } + + // Use "kill -SIGQUIT" to generate a data dump request. + // Useful when running an android app since it doesn't go through + // a normal Agent_OnUnload. + static void DataDumpRequest(jvmtiEnv* jvmti_env ATTRIBUTE_UNUSED) { + TraceStatistics& stats = TraceStatistics::GetSingleton(); + stats.Log(); + } +}; + +} // namespace titrace + +// Late attachment (e.g. 'am attach-agent'). +JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char* options, void* reserved) { + return Agent_OnLoad(vm, options, reserved); +} + +// Early attachment (e.g. 'java -agent[lib|path]:filename.so'). +JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, + char* /* options */, + void* /* reserved */) { + using namespace titrace; // NOLINT [build/namespaces] [5] + + android::base::InitLogging(/* argv */nullptr); + + jvmtiEnv* jvmti = nullptr; + { + jint res = 0; + res = jvm->GetEnv(reinterpret_cast<void**>(&jvmti), JVMTI_VERSION_1_1); + + if (res != JNI_OK || jvmti == nullptr) { + LOG(FATAL) << "Unable to access JVMTI, error code " << res; + } + } + + LOG(INFO) << "Agent_OnLoad: Hello World"; + + { + // Initialize our instruction file-format decoder. + TraceStatistics::Initialize(jvmti); + } + + jvmtiError error{}; // NOLINT [readability/braces] [4] [whitespace/braces] [5] + + // Set capabilities. + { + jvmtiCapabilities caps = {}; + caps.can_generate_single_step_events = 1; + caps.can_get_bytecodes = 1; + + error = jvmti->AddCapabilities(&caps); + CHECK_JVMTI_ERROR(jvmti, error) + << "Unable to get necessary JVMTI capabilities"; + } + + // Set callbacks. + { + jvmtiEventCallbacks callbacks = {}; + callbacks.SingleStep = &EventCallbacks::SingleStep; + callbacks.DataDumpRequest = &EventCallbacks::DataDumpRequest; + + error = jvmti->SetEventCallbacks(&callbacks, + static_cast<jint>(sizeof(callbacks))); + CHECK_JVMTI_ERROR(jvmti, error) << "Unable to set event callbacks"; + } + + // Enable events notification. + { + error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_SINGLE_STEP, + nullptr /* all threads */); + CHECK_JVMTI_ERROR(jvmti, error) + << "Failed to enable SINGLE_STEP notification"; + + error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_DATA_DUMP_REQUEST, + nullptr /* all threads */); + CHECK_JVMTI_ERROR(jvmti, error) + << "Failed to enable DATA_DUMP_REQUEST notification"; + } + + return JNI_OK; +} + +// Note: This is not called for normal Android apps, +// use "kill -SIGQUIT" instead to generate a data dump request. +JNIEXPORT void JNICALL Agent_OnUnload(JavaVM* vm ATTRIBUTE_UNUSED) { + using namespace titrace; // NOLINT [build/namespaces] [5] + LOG(INFO) << "Agent_OnUnload: Goodbye"; + + TraceStatistics::GetSingleton().Log(); +} + |