diff options
83 files changed, 2658 insertions, 344 deletions
diff --git a/compiler/dex/quick/codegen_util.cc b/compiler/dex/quick/codegen_util.cc index d68835a9cf..af6f91f21d 100644 --- a/compiler/dex/quick/codegen_util.cc +++ b/compiler/dex/quick/codegen_util.cc @@ -1076,7 +1076,7 @@ Mir2Lir::Mir2Lir(CompilationUnit* cu, MIRGraph* mir_graph, ArenaAllocator* arena pc_rel_temp_(nullptr), dex_cache_arrays_min_offset_(std::numeric_limits<uint32_t>::max()), cfi_(&last_lir_insn_, - cu->compiler_driver->GetCompilerOptions().GetGenerateDebugInfo(), + cu->compiler_driver->GetCompilerOptions().GenerateAnyDebugInfo(), arena), in_to_reg_storage_mapping_(arena) { switch_tables_.reserve(4); diff --git a/compiler/dex/quick/quick_cfi_test.cc b/compiler/dex/quick/quick_cfi_test.cc index c5df134493..0cd41bbf4c 100644 --- a/compiler/dex/quick/quick_cfi_test.cc +++ b/compiler/dex/quick/quick_cfi_test.cc @@ -71,6 +71,7 @@ class QuickCFITest : public CFITest { nullptr, false, "", + false, false); VerificationResults verification_results(&compiler_options); DexFileToMethodInlinerMap method_inliner_map; diff --git a/compiler/dex/quick/x86/quick_assemble_x86_test.cc b/compiler/dex/quick/x86/quick_assemble_x86_test.cc index d63878d6b9..efdc333261 100644 --- a/compiler/dex/quick/x86/quick_assemble_x86_test.cc +++ b/compiler/dex/quick/x86/quick_assemble_x86_test.cc @@ -54,6 +54,7 @@ class QuickAssembleX86TestBase : public testing::Test { nullptr, false, "", + false, false)); verification_results_.reset(new VerificationResults(compiler_options_.get())); method_inliner_map_.reset(new DexFileToMethodInlinerMap()); diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index 818d50a994..f1b745895f 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -39,6 +39,7 @@ #include "compiler_driver-inl.h" #include "dex_compilation_unit.h" #include "dex_file-inl.h" +#include "dex_instruction-inl.h" #include "dex/dex_to_dex_compiler.h" #include "dex/verification_results.h" #include "dex/verified_method.h" @@ -365,7 +366,7 @@ CompilerDriver::CompilerDriver( classes_to_compile_(compiled_classes), methods_to_compile_(compiled_methods), had_hard_verifier_failure_(false), - thread_count_(thread_count), + parallel_thread_count_(thread_count), stats_(new AOTCompilationStats), dump_stats_(dump_stats), dump_passes_(dump_passes), @@ -435,24 +436,27 @@ void CompilerDriver::CompileAll(jobject class_loader, const std::vector<const DexFile*>& dex_files, TimingLogger* timings) { DCHECK(!Runtime::Current()->IsStarted()); - std::unique_ptr<ThreadPool> thread_pool( - new ThreadPool("Compiler driver thread pool", thread_count_ - 1)); + + InitializeThreadPools(); + VLOG(compiler) << "Before precompile " << GetMemoryUsageString(false); // Precompile: // 1) Load image classes // 2) Resolve all classes // 3) Attempt to verify all classes // 4) Attempt to initialize image classes, and trivially initialized classes - PreCompile(class_loader, dex_files, thread_pool.get(), timings); + PreCompile(class_loader, dex_files, timings); // Compile: // 1) Compile all classes and methods enabled for compilation. May fall back to dex-to-dex // compilation. if (!GetCompilerOptions().VerifyAtRuntime()) { - Compile(class_loader, dex_files, thread_pool.get(), timings); + Compile(class_loader, dex_files, timings); } if (dump_stats_) { stats_->Dump(); } + + FreeThreadPools(); } static optimizer::DexToDexCompilationLevel GetDexToDexCompilationLevel( @@ -653,8 +657,9 @@ void CompilerDriver::CompileOne(Thread* self, ArtMethod* method, TimingLogger* t std::vector<const DexFile*> dex_files; dex_files.push_back(dex_file); - std::unique_ptr<ThreadPool> thread_pool(new ThreadPool("Compiler driver thread pool", 0U)); - PreCompile(jclass_loader, dex_files, thread_pool.get(), timings); + InitializeThreadPools(); + + PreCompile(jclass_loader, dex_files, timings); // Can we run DEX-to-DEX compiler on this class ? optimizer::DexToDexCompilationLevel dex_to_dex_compilation_level = @@ -677,20 +682,147 @@ void CompilerDriver::CompileOne(Thread* self, ArtMethod* method, TimingLogger* t true, dex_cache); + FreeThreadPools(); + self->GetJniEnv()->DeleteGlobalRef(jclass_loader); } -void CompilerDriver::Resolve(jobject class_loader, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) { +void CompilerDriver::Resolve(jobject class_loader, + const std::vector<const DexFile*>& dex_files, + TimingLogger* timings) { + // Resolution allocates classes and needs to run single-threaded to be deterministic. + bool force_determinism = GetCompilerOptions().IsForceDeterminism(); + ThreadPool* resolve_thread_pool = force_determinism + ? single_thread_pool_.get() + : parallel_thread_pool_.get(); + size_t resolve_thread_count = force_determinism ? 1U : parallel_thread_count_; + for (size_t i = 0; i != dex_files.size(); ++i) { const DexFile* dex_file = dex_files[i]; CHECK(dex_file != nullptr); - ResolveDexFile(class_loader, *dex_file, dex_files, thread_pool, timings); + ResolveDexFile(class_loader, + *dex_file, + dex_files, + resolve_thread_pool, + resolve_thread_count, + timings); + } +} + +// Resolve const-strings in the code. Done to have deterministic allocation behavior. Right now +// this is single-threaded for simplicity. +// TODO: Collect the relevant string indices in parallel, then allocate them sequentially in a +// stable order. + +static void ResolveConstStrings(CompilerDriver* driver, + const DexFile& dex_file, + const DexFile::CodeItem* code_item) { + if (code_item == nullptr) { + // Abstract or native method. + return; + } + + const uint16_t* code_ptr = code_item->insns_; + const uint16_t* code_end = code_item->insns_ + code_item->insns_size_in_code_units_; + + while (code_ptr < code_end) { + const Instruction* inst = Instruction::At(code_ptr); + switch (inst->Opcode()) { + case Instruction::CONST_STRING: { + uint32_t string_index = inst->VRegB_21c(); + driver->CanAssumeStringIsPresentInDexCache(dex_file, string_index); + break; + } + case Instruction::CONST_STRING_JUMBO: { + uint32_t string_index = inst->VRegB_31c(); + driver->CanAssumeStringIsPresentInDexCache(dex_file, string_index); + break; + } + + default: + break; + } + + code_ptr += inst->SizeInCodeUnits(); + } +} + +static void ResolveConstStrings(CompilerDriver* driver, + const std::vector<const DexFile*>& dex_files, + TimingLogger* timings) { + for (const DexFile* dex_file : dex_files) { + TimingLogger::ScopedTiming t("Resolve const-string Strings", timings); + + size_t class_def_count = dex_file->NumClassDefs(); + for (size_t class_def_index = 0; class_def_index < class_def_count; ++class_def_index) { + const DexFile::ClassDef& class_def = dex_file->GetClassDef(class_def_index); + + const uint8_t* class_data = dex_file->GetClassData(class_def); + if (class_data == nullptr) { + // empty class, probably a marker interface + continue; + } + + ClassDataItemIterator it(*dex_file, class_data); + // Skip fields + while (it.HasNextStaticField()) { + it.Next(); + } + while (it.HasNextInstanceField()) { + it.Next(); + } + + bool compilation_enabled = driver->IsClassToCompile( + dex_file->StringByTypeIdx(class_def.class_idx_)); + if (!compilation_enabled) { + // Compilation is skipped, do not resolve const-string in code of this class. + // TODO: Make sure that inlining honors this. + continue; + } + + // Direct methods. + int64_t previous_direct_method_idx = -1; + while (it.HasNextDirectMethod()) { + uint32_t method_idx = it.GetMemberIndex(); + if (method_idx == previous_direct_method_idx) { + // smali can create dex files with two encoded_methods sharing the same method_idx + // http://code.google.com/p/smali/issues/detail?id=119 + it.Next(); + continue; + } + previous_direct_method_idx = method_idx; + ResolveConstStrings(driver, *dex_file, it.GetMethodCodeItem()); + it.Next(); + } + // Virtual methods. + int64_t previous_virtual_method_idx = -1; + while (it.HasNextVirtualMethod()) { + uint32_t method_idx = it.GetMemberIndex(); + if (method_idx == previous_virtual_method_idx) { + // smali can create dex files with two encoded_methods sharing the same method_idx + // http://code.google.com/p/smali/issues/detail?id=119 + it.Next(); + continue; + } + previous_virtual_method_idx = method_idx; + ResolveConstStrings(driver, *dex_file, it.GetMethodCodeItem()); + it.Next(); + } + DCHECK(!it.HasNext()); + } } } -void CompilerDriver::PreCompile(jobject class_loader, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) { +inline void CompilerDriver::CheckThreadPools() { + DCHECK(parallel_thread_pool_ != nullptr); + DCHECK(single_thread_pool_ != nullptr); +} + +void CompilerDriver::PreCompile(jobject class_loader, + const std::vector<const DexFile*>& dex_files, + TimingLogger* timings) { + CheckThreadPools(); + LoadImageClasses(timings); VLOG(compiler) << "LoadImageClasses: " << GetMemoryUsageString(false); @@ -700,20 +832,26 @@ void CompilerDriver::PreCompile(jobject class_loader, const std::vector<const De // We need to resolve for never_verify since it needs to run dex to dex to add the // RETURN_VOID_NO_BARRIER. if (never_verify || verification_enabled) { - Resolve(class_loader, dex_files, thread_pool, timings); + Resolve(class_loader, dex_files, timings); VLOG(compiler) << "Resolve: " << GetMemoryUsageString(false); } if (never_verify) { VLOG(compiler) << "Verify none mode specified, skipping verification."; - SetVerified(class_loader, dex_files, thread_pool, timings); + SetVerified(class_loader, dex_files, timings); } if (!verification_enabled) { return; } - Verify(class_loader, dex_files, thread_pool, timings); + if (GetCompilerOptions().IsForceDeterminism() && IsBootImage()) { + // Resolve strings from const-string. Do this now to have a deterministic image. + ResolveConstStrings(this, dex_files, timings); + VLOG(compiler) << "Resolve const-strings: " << GetMemoryUsageString(false); + } + + Verify(class_loader, dex_files, timings); VLOG(compiler) << "Verify: " << GetMemoryUsageString(false); if (had_hard_verifier_failure_ && GetCompilerOptions().AbortOnHardVerifierFailure()) { @@ -721,7 +859,7 @@ void CompilerDriver::PreCompile(jobject class_loader, const std::vector<const De << "situations. Please check the log."; } - InitializeClasses(class_loader, dex_files, thread_pool, timings); + InitializeClasses(class_loader, dex_files, timings); VLOG(compiler) << "InitializeClasses: " << GetMemoryUsageString(false); UpdateImageClasses(timings); @@ -1759,6 +1897,9 @@ class ParallelCompilationManager { // Wait for all the worker threads to finish. thread_pool_->Wait(self, true, false); + + // And stop the workers accepting jobs. + thread_pool_->StopWorkers(self); } size_t NextIndex() { @@ -1995,9 +2136,12 @@ class ResolveTypeVisitor : public CompilationVisitor { const ParallelCompilationManager* const manager_; }; -void CompilerDriver::ResolveDexFile(jobject class_loader, const DexFile& dex_file, +void CompilerDriver::ResolveDexFile(jobject class_loader, + const DexFile& dex_file, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) { + ThreadPool* thread_pool, + size_t thread_count, + TimingLogger* timings) { ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); // TODO: we could resolve strings here, although the string table is largely filled with class @@ -2010,27 +2154,43 @@ void CompilerDriver::ResolveDexFile(jobject class_loader, const DexFile& dex_fil // classdefs are resolved by ResolveClassFieldsAndMethods. TimingLogger::ScopedTiming t("Resolve Types", timings); ResolveTypeVisitor visitor(&context); - context.ForAll(0, dex_file.NumTypeIds(), &visitor, thread_count_); + context.ForAll(0, dex_file.NumTypeIds(), &visitor, thread_count); } TimingLogger::ScopedTiming t("Resolve MethodsAndFields", timings); ResolveClassFieldsAndMethodsVisitor visitor(&context); - context.ForAll(0, dex_file.NumClassDefs(), &visitor, thread_count_); + context.ForAll(0, dex_file.NumClassDefs(), &visitor, thread_count); } -void CompilerDriver::SetVerified(jobject class_loader, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) { +void CompilerDriver::SetVerified(jobject class_loader, + const std::vector<const DexFile*>& dex_files, + TimingLogger* timings) { + // This can be run in parallel. for (const DexFile* dex_file : dex_files) { CHECK(dex_file != nullptr); - SetVerifiedDexFile(class_loader, *dex_file, dex_files, thread_pool, timings); + SetVerifiedDexFile(class_loader, + *dex_file, + dex_files, + parallel_thread_pool_.get(), + parallel_thread_count_, + timings); } } -void CompilerDriver::Verify(jobject class_loader, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) { +void CompilerDriver::Verify(jobject class_loader, + const std::vector<const DexFile*>& dex_files, + TimingLogger* timings) { + // Note: verification should not be pulling in classes anymore when compiling the boot image, + // as all should have been resolved before. As such, doing this in parallel should still + // be deterministic. for (const DexFile* dex_file : dex_files) { CHECK(dex_file != nullptr); - VerifyDexFile(class_loader, *dex_file, dex_files, thread_pool, timings); + VerifyDexFile(class_loader, + *dex_file, + dex_files, + parallel_thread_pool_.get(), + parallel_thread_count_, + timings); } } @@ -2104,15 +2264,18 @@ class VerifyClassVisitor : public CompilationVisitor { const ParallelCompilationManager* const manager_; }; -void CompilerDriver::VerifyDexFile(jobject class_loader, const DexFile& dex_file, +void CompilerDriver::VerifyDexFile(jobject class_loader, + const DexFile& dex_file, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) { + ThreadPool* thread_pool, + size_t thread_count, + TimingLogger* timings) { TimingLogger::ScopedTiming t("Verify Dex File", timings); ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); ParallelCompilationManager context(class_linker, class_loader, this, &dex_file, dex_files, thread_pool); VerifyClassVisitor visitor(&context); - context.ForAll(0, dex_file.NumClassDefs(), &visitor, thread_count_); + context.ForAll(0, dex_file.NumClassDefs(), &visitor, thread_count); } class SetVerifiedClassVisitor : public CompilationVisitor { @@ -2162,15 +2325,18 @@ class SetVerifiedClassVisitor : public CompilationVisitor { const ParallelCompilationManager* const manager_; }; -void CompilerDriver::SetVerifiedDexFile(jobject class_loader, const DexFile& dex_file, +void CompilerDriver::SetVerifiedDexFile(jobject class_loader, + const DexFile& dex_file, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) { + ThreadPool* thread_pool, + size_t thread_count, + TimingLogger* timings) { TimingLogger::ScopedTiming t("Verify Dex File", timings); ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); ParallelCompilationManager context(class_linker, class_loader, this, &dex_file, dex_files, thread_pool); SetVerifiedClassVisitor visitor(&context); - context.ForAll(0, dex_file.NumClassDefs(), &visitor, thread_count_); + context.ForAll(0, dex_file.NumClassDefs(), &visitor, thread_count); } class InitializeClassVisitor : public CompilationVisitor { @@ -2271,31 +2437,37 @@ class InitializeClassVisitor : public CompilationVisitor { const ParallelCompilationManager* const manager_; }; -void CompilerDriver::InitializeClasses(jobject jni_class_loader, const DexFile& dex_file, +void CompilerDriver::InitializeClasses(jobject jni_class_loader, + const DexFile& dex_file, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) { + TimingLogger* timings) { TimingLogger::ScopedTiming t("InitializeNoClinit", timings); + + // Initialization allocates objects and needs to run single-threaded to be deterministic. + bool force_determinism = GetCompilerOptions().IsForceDeterminism(); + ThreadPool* init_thread_pool = force_determinism + ? single_thread_pool_.get() + : parallel_thread_pool_.get(); + size_t init_thread_count = force_determinism ? 1U : parallel_thread_count_; + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); ParallelCompilationManager context(class_linker, jni_class_loader, this, &dex_file, dex_files, - thread_pool); - size_t thread_count; + init_thread_pool); if (IsBootImage()) { // TODO: remove this when transactional mode supports multithreading. - thread_count = 1U; - } else { - thread_count = thread_count_; + init_thread_count = 1U; } InitializeClassVisitor visitor(&context); - context.ForAll(0, dex_file.NumClassDefs(), &visitor, thread_count); + context.ForAll(0, dex_file.NumClassDefs(), &visitor, init_thread_count); } void CompilerDriver::InitializeClasses(jobject class_loader, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) { + TimingLogger* timings) { for (size_t i = 0; i != dex_files.size(); ++i) { const DexFile* dex_file = dex_files[i]; CHECK(dex_file != nullptr); - InitializeClasses(class_loader, *dex_file, dex_files, thread_pool, timings); + InitializeClasses(class_loader, *dex_file, dex_files, timings); } if (IsBootImage()) { // Prune garbage objects created during aborted transactions. @@ -2303,8 +2475,9 @@ void CompilerDriver::InitializeClasses(jobject class_loader, } } -void CompilerDriver::Compile(jobject class_loader, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) { +void CompilerDriver::Compile(jobject class_loader, + const std::vector<const DexFile*>& dex_files, + TimingLogger* timings) { if (kDebugProfileGuidedCompilation) { LOG(INFO) << "[ProfileGuidedCompilation] " << ((profile_compilation_info_ == nullptr) @@ -2314,7 +2487,12 @@ void CompilerDriver::Compile(jobject class_loader, const std::vector<const DexFi for (size_t i = 0; i != dex_files.size(); ++i) { const DexFile* dex_file = dex_files[i]; CHECK(dex_file != nullptr); - CompileDexFile(class_loader, *dex_file, dex_files, thread_pool, timings); + CompileDexFile(class_loader, + *dex_file, + dex_files, + parallel_thread_pool_.get(), + parallel_thread_count_, + timings); } VLOG(compiler) << "Compile: " << GetMemoryUsageString(false); } @@ -2421,14 +2599,17 @@ class CompileClassVisitor : public CompilationVisitor { const ParallelCompilationManager* const manager_; }; -void CompilerDriver::CompileDexFile(jobject class_loader, const DexFile& dex_file, +void CompilerDriver::CompileDexFile(jobject class_loader, + const DexFile& dex_file, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) { + ThreadPool* thread_pool, + size_t thread_count, + TimingLogger* timings) { TimingLogger::ScopedTiming t("Compile Dex File", timings); ParallelCompilationManager context(Runtime::Current()->GetClassLinker(), class_loader, this, &dex_file, dex_files, thread_pool); CompileClassVisitor visitor(&context); - context.ForAll(0, dex_file.NumClassDefs(), &visitor, thread_count_); + context.ForAll(0, dex_file.NumClassDefs(), &visitor, thread_count); } void CompilerDriver::AddCompiledMethod(const MethodReference& method_ref, @@ -2582,11 +2763,24 @@ bool CompilerDriver::MayInlineInternal(const DexFile* inlined_from, const DexFile* inlined_into) const { // We're not allowed to inline across dex files if we're the no-inline-from dex file. if (inlined_from != inlined_into && - compiler_options_->GetNoInlineFromDexFile() == inlined_from) { + compiler_options_->GetNoInlineFromDexFile() != nullptr && + ContainsElement(*compiler_options_->GetNoInlineFromDexFile(), inlined_from)) { return false; } return true; } +void CompilerDriver::InitializeThreadPools() { + size_t parallel_count = parallel_thread_count_ > 0 ? parallel_thread_count_ - 1 : 0; + parallel_thread_pool_.reset( + new ThreadPool("Compiler driver thread pool", parallel_count)); + single_thread_pool_.reset(new ThreadPool("Single-threaded Compiler driver thread pool", 0)); +} + +void CompilerDriver::FreeThreadPools() { + parallel_thread_pool_.reset(); + single_thread_pool_.reset(); +} + } // namespace art diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h index 6a2f7bfd4e..5e35cbb309 100644 --- a/compiler/driver/compiler_driver.h +++ b/compiler/driver/compiler_driver.h @@ -411,7 +411,7 @@ class CompilerDriver { } size_t GetThreadCount() const { - return thread_count_; + return parallel_thread_count_; } bool GetDumpStats() const { @@ -550,8 +550,9 @@ class CompilerDriver { SHARED_REQUIRES(Locks::mutator_lock_); private: - void PreCompile(jobject class_loader, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) + void PreCompile(jobject class_loader, + const std::vector<const DexFile*>& dex_files, + TimingLogger* timings) REQUIRES(!Locks::mutator_lock_, !compiled_classes_lock_); void LoadImageClasses(TimingLogger* timings) REQUIRES(!Locks::mutator_lock_); @@ -559,49 +560,71 @@ class CompilerDriver { // Attempt to resolve all type, methods, fields, and strings // referenced from code in the dex file following PathClassLoader // ordering semantics. - void Resolve(jobject class_loader, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) + void Resolve(jobject class_loader, + const std::vector<const DexFile*>& dex_files, + TimingLogger* timings) REQUIRES(!Locks::mutator_lock_); - void ResolveDexFile(jobject class_loader, const DexFile& dex_file, + void ResolveDexFile(jobject class_loader, + const DexFile& dex_file, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) + ThreadPool* thread_pool, + size_t thread_count, + TimingLogger* timings) REQUIRES(!Locks::mutator_lock_); - void Verify(jobject class_loader, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings); - void VerifyDexFile(jobject class_loader, const DexFile& dex_file, + void Verify(jobject class_loader, + const std::vector<const DexFile*>& dex_files, + TimingLogger* timings); + void VerifyDexFile(jobject class_loader, + const DexFile& dex_file, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) + ThreadPool* thread_pool, + size_t thread_count, + TimingLogger* timings) REQUIRES(!Locks::mutator_lock_); - void SetVerified(jobject class_loader, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings); - void SetVerifiedDexFile(jobject class_loader, const DexFile& dex_file, + void SetVerified(jobject class_loader, + const std::vector<const DexFile*>& dex_files, + TimingLogger* timings); + void SetVerifiedDexFile(jobject class_loader, + const DexFile& dex_file, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) + ThreadPool* thread_pool, + size_t thread_count, + TimingLogger* timings) REQUIRES(!Locks::mutator_lock_); - void InitializeClasses(jobject class_loader, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) + void InitializeClasses(jobject class_loader, + const std::vector<const DexFile*>& dex_files, + TimingLogger* timings) REQUIRES(!Locks::mutator_lock_, !compiled_classes_lock_); - void InitializeClasses(jobject class_loader, const DexFile& dex_file, + void InitializeClasses(jobject class_loader, + const DexFile& dex_file, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) + TimingLogger* timings) REQUIRES(!Locks::mutator_lock_, !compiled_classes_lock_); void UpdateImageClasses(TimingLogger* timings) REQUIRES(!Locks::mutator_lock_); static void FindClinitImageClassesCallback(mirror::Object* object, void* arg) SHARED_REQUIRES(Locks::mutator_lock_); - void Compile(jobject class_loader, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings); - void CompileDexFile(jobject class_loader, const DexFile& dex_file, + void Compile(jobject class_loader, + const std::vector<const DexFile*>& dex_files, + TimingLogger* timings); + void CompileDexFile(jobject class_loader, + const DexFile& dex_file, const std::vector<const DexFile*>& dex_files, - ThreadPool* thread_pool, TimingLogger* timings) + ThreadPool* thread_pool, + size_t thread_count, + TimingLogger* timings) REQUIRES(!Locks::mutator_lock_); bool MayInlineInternal(const DexFile* inlined_from, const DexFile* inlined_into) const; + void InitializeThreadPools(); + void FreeThreadPools(); + void CheckThreadPools(); + const CompilerOptions* const compiler_options_; VerificationResults* const verification_results_; DexFileToMethodInlinerMap* const method_inliner_map_; @@ -652,7 +675,12 @@ class CompilerDriver { bool had_hard_verifier_failure_; - size_t thread_count_; + // A thread pool that can (potentially) run tasks in parallel. + std::unique_ptr<ThreadPool> parallel_thread_pool_; + size_t parallel_thread_count_; + + // A thread pool that guarantees running single-threaded on the main thread. + std::unique_ptr<ThreadPool> single_thread_pool_; class AOTCompilationStats; std::unique_ptr<AOTCompilationStats> stats_; diff --git a/compiler/driver/compiler_options.cc b/compiler/driver/compiler_options.cc index 2644528e56..3bf89214d7 100644 --- a/compiler/driver/compiler_options.cc +++ b/compiler/driver/compiler_options.cc @@ -37,6 +37,7 @@ CompilerOptions::CompilerOptions() debuggable_(false), native_debuggable_(kDefaultNativeDebuggable), generate_debug_info_(kDefaultGenerateDebugInfo), + generate_mini_debug_info_(kDefaultGenerateMiniDebugInfo), implicit_null_checks_(true), implicit_so_checks_(true), implicit_suspend_checks_(false), @@ -46,7 +47,8 @@ CompilerOptions::CompilerOptions() abort_on_hard_verifier_failure_(false), init_failure_output_(nullptr), dump_cfg_file_name_(""), - dump_cfg_append_(false) { + dump_cfg_append_(false), + force_determinism_(false) { } CompilerOptions::~CompilerOptions() { @@ -62,7 +64,7 @@ CompilerOptions::CompilerOptions(CompilerFilter compiler_filter, size_t num_dex_methods_threshold, size_t inline_depth_limit, size_t inline_max_code_units, - const DexFile* no_inline_from, + const std::vector<const DexFile*>* no_inline_from, bool include_patch_information, double top_k_profile_threshold, bool debuggable, @@ -75,7 +77,8 @@ CompilerOptions::CompilerOptions(CompilerFilter compiler_filter, std::ostream* init_failure_output, bool abort_on_hard_verifier_failure, const std::string& dump_cfg_file_name, - bool dump_cfg_append + bool dump_cfg_append, + bool force_determinism ) : // NOLINT(whitespace/parens) compiler_filter_(compiler_filter), huge_method_threshold_(huge_method_threshold), @@ -91,6 +94,7 @@ CompilerOptions::CompilerOptions(CompilerFilter compiler_filter, debuggable_(debuggable), native_debuggable_(kDefaultNativeDebuggable), generate_debug_info_(generate_debug_info), + generate_mini_debug_info_(kDefaultGenerateMiniDebugInfo), implicit_null_checks_(implicit_null_checks), implicit_so_checks_(implicit_so_checks), implicit_suspend_checks_(implicit_suspend_checks), @@ -100,7 +104,8 @@ CompilerOptions::CompilerOptions(CompilerFilter compiler_filter, abort_on_hard_verifier_failure_(abort_on_hard_verifier_failure), init_failure_output_(init_failure_output), dump_cfg_file_name_(dump_cfg_file_name), - dump_cfg_append_(dump_cfg_append) { + dump_cfg_append_(dump_cfg_append), + force_determinism_(force_determinism) { } void CompilerOptions::ParseHugeMethodMax(const StringPiece& option, UsageFn Usage) { @@ -215,6 +220,10 @@ bool CompilerOptions::ParseCompilerOption(const StringPiece& option, UsageFn Usa generate_debug_info_ = true; } else if (option == "--no-generate-debug-info") { generate_debug_info_ = false; + } else if (option == "--generate-mini-debug-info") { + generate_mini_debug_info_ = true; + } else if (option == "--no-generate-mini-debug-info") { + generate_mini_debug_info_ = false; } else if (option == "--debuggable") { debuggable_ = true; } else if (option == "--native-debuggable") { diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h index d47fc2ad4b..39372b36b8 100644 --- a/compiler/driver/compiler_options.h +++ b/compiler/driver/compiler_options.h @@ -51,6 +51,7 @@ class CompilerOptions FINAL { static constexpr double kDefaultTopKProfileThreshold = 90.0; static const bool kDefaultNativeDebuggable = false; static const bool kDefaultGenerateDebugInfo = false; + static const bool kDefaultGenerateMiniDebugInfo = false; static const bool kDefaultIncludePatchInformation = false; static const size_t kDefaultInlineDepthLimit = 3; static const size_t kDefaultInlineMaxCodeUnits = 32; @@ -72,7 +73,7 @@ class CompilerOptions FINAL { size_t num_dex_methods_threshold, size_t inline_depth_limit, size_t inline_max_code_units, - const DexFile* no_inline_from, + const std::vector<const DexFile*>* no_inline_from, bool include_patch_information, double top_k_profile_threshold, bool debuggable, @@ -85,7 +86,8 @@ class CompilerOptions FINAL { std::ostream* init_failure_output, bool abort_on_hard_verifier_failure, const std::string& dump_cfg_file_name, - bool dump_cfg_append); + bool dump_cfg_append, + bool force_determinism); CompilerFilter GetCompilerFilter() const { return compiler_filter_; @@ -170,10 +172,20 @@ class CompilerOptions FINAL { return native_debuggable_; } + // This flag controls whether the compiler collects debugging information. + // The other flags control how the information is written to disk. + bool GenerateAnyDebugInfo() const { + return GetGenerateDebugInfo() || GetGenerateMiniDebugInfo(); + } + bool GetGenerateDebugInfo() const { return generate_debug_info_; } + bool GetGenerateMiniDebugInfo() const { + return generate_mini_debug_info_; + } + bool GetImplicitNullChecks() const { return implicit_null_checks_; } @@ -220,7 +232,7 @@ class CompilerOptions FINAL { return abort_on_hard_verifier_failure_; } - const DexFile* GetNoInlineFromDexFile() const { + const std::vector<const DexFile*>* GetNoInlineFromDexFile() const { return no_inline_from_; } @@ -234,6 +246,10 @@ class CompilerOptions FINAL { return dump_cfg_append_; } + bool IsForceDeterminism() const { + return force_determinism_; + } + private: void ParseDumpInitFailures(const StringPiece& option, UsageFn Usage); void ParsePassOptions(const StringPiece& option, UsageFn Usage); @@ -257,8 +273,10 @@ class CompilerOptions FINAL { size_t inline_depth_limit_; size_t inline_max_code_units_; - // A dex file from which we should not inline code. - const DexFile* no_inline_from_; + // Dex files from which we should not inline code. + // This is usually a very short list (i.e. a single dex file), so we + // prefer vector<> over a lookup-oriented container, such as set<>. + const std::vector<const DexFile*>* no_inline_from_; bool include_patch_information_; // When using a profile file only the top K% of the profiled samples will be compiled. @@ -266,6 +284,7 @@ class CompilerOptions FINAL { bool debuggable_; bool native_debuggable_; bool generate_debug_info_; + bool generate_mini_debug_info_; bool implicit_null_checks_; bool implicit_so_checks_; bool implicit_suspend_checks_; @@ -286,6 +305,10 @@ class CompilerOptions FINAL { std::string dump_cfg_file_name_; bool dump_cfg_append_; + // Whether the compiler should trade performance for determinism to guarantee exactly reproducable + // outcomes. + bool force_determinism_; + friend class Dex2Oat; DISALLOW_COPY_AND_ASSIGN(CompilerOptions); diff --git a/compiler/elf_builder.h b/compiler/elf_builder.h index 46484b1cd6..3d24d19919 100644 --- a/compiler/elf_builder.h +++ b/compiler/elf_builder.h @@ -165,10 +165,15 @@ class ElfBuilder FINAL { } } - // Set desired allocation size for .bss section. - void SetSize(Elf_Word size) { - CHECK_EQ(header_.sh_type, (Elf_Word)SHT_NOBITS); + // Write this section as "NOBITS" section. (used for the .bss section) + // This means that the ELF file does not contain the initial data for this section + // and it will be zero-initialized when the ELF file is loaded in the running program. + void WriteNoBitsSection(Elf_Word size) { + DCHECK_NE(header_.sh_flags & SHF_ALLOC, 0u); + Start(); + header_.sh_type = SHT_NOBITS; header_.sh_size = size; + End(); } // This function always succeeds to simplify code. @@ -346,6 +351,12 @@ class ElfBuilder FINAL { other_sections_.push_back(std::move(s)); } + // Set where the next section will be allocated in the virtual address space. + void SetVirtualAddress(Elf_Addr address) { + DCHECK_GE(address, virtual_address_); + virtual_address_ = address; + } + void Start() { // Reserve space for ELF header and program headers. // We do not know the number of headers until later, so diff --git a/compiler/elf_writer_debug.cc b/compiler/elf_writer_debug.cc index 73e6aa38f0..2e98b69c47 100644 --- a/compiler/elf_writer_debug.cc +++ b/compiler/elf_writer_debug.cc @@ -16,6 +16,7 @@ #include "elf_writer_debug.h" +#include <algorithm> #include <unordered_set> #include <vector> #include <cstdio> @@ -40,6 +41,11 @@ #include "stack_map.h" #include "utils.h" +// liblzma. +#include "XzEnc.h" +#include "7zCrc.h" +#include "XzCrc64.h" + namespace art { namespace dwarf { @@ -222,7 +228,8 @@ static void WriteCIE(InstructionSet isa, template<typename ElfTypes> void WriteCFISection(ElfBuilder<ElfTypes>* builder, const ArrayRef<const MethodDebugInfo>& method_infos, - CFIFormat format) { + CFIFormat format, + bool write_oat_patches) { CHECK(format == DW_DEBUG_FRAME_FORMAT || format == DW_EH_FRAME_FORMAT); typedef typename ElfTypes::Addr Elf_Addr; @@ -238,6 +245,24 @@ void WriteCFISection(ElfBuilder<ElfTypes>* builder, patch_locations.reserve(method_infos.size()); } + // The methods can be written any order. + // Let's therefore sort them in the lexicographical order of the opcodes. + // This has no effect on its own. However, if the final .debug_frame section is + // compressed it reduces the size since similar opcodes sequences are grouped. + std::vector<const MethodDebugInfo*> sorted_method_infos; + sorted_method_infos.reserve(method_infos.size()); + for (size_t i = 0; i < method_infos.size(); i++) { + sorted_method_infos.push_back(&method_infos[i]); + } + std::sort( + sorted_method_infos.begin(), + sorted_method_infos.end(), + [](const MethodDebugInfo* lhs, const MethodDebugInfo* rhs) { + ArrayRef<const uint8_t> l = lhs->compiled_method_->GetCFIInfo(); + ArrayRef<const uint8_t> r = rhs->compiled_method_->GetCFIInfo(); + return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end()); + }); + // Write .eh_frame/.debug_frame section. auto* cfi_section = (format == DW_DEBUG_FRAME_FORMAT ? builder->GetDebugFrame() @@ -256,11 +281,11 @@ void WriteCFISection(ElfBuilder<ElfTypes>* builder, cfi_section->WriteFully(buffer.data(), buffer.size()); buffer_address += buffer.size(); buffer.clear(); - for (const MethodDebugInfo& mi : method_infos) { - if (!mi.deduped_) { // Only one FDE per unique address. - ArrayRef<const uint8_t> opcodes = mi.compiled_method_->GetCFIInfo(); + for (const MethodDebugInfo* mi : sorted_method_infos) { + if (!mi->deduped_) { // Only one FDE per unique address. + ArrayRef<const uint8_t> opcodes = mi->compiled_method_->GetCFIInfo(); if (!opcodes.empty()) { - const Elf_Addr code_address = text_address + mi.low_pc_; + const Elf_Addr code_address = text_address + mi->low_pc_; if (format == DW_EH_FRAME_FORMAT) { binary_search_table.push_back( dchecked_integral_cast<uint32_t>(code_address)); @@ -268,7 +293,7 @@ void WriteCFISection(ElfBuilder<ElfTypes>* builder, dchecked_integral_cast<uint32_t>(buffer_address)); } WriteFDE(is64bit, cfi_address, cie_address, - code_address, mi.high_pc_ - mi.low_pc_, + code_address, mi->high_pc_ - mi->low_pc_, opcodes, format, buffer_address, &buffer, &patch_locations); cfi_section->WriteFully(buffer.data(), buffer.size()); @@ -309,8 +334,10 @@ void WriteCFISection(ElfBuilder<ElfTypes>* builder, header_section->WriteFully(binary_search_table.data(), binary_search_table.size()); header_section->End(); } else { - builder->WritePatches(".debug_frame.oat_patches", - ArrayRef<const uintptr_t>(patch_locations)); + if (write_oat_patches) { + builder->WritePatches(".debug_frame.oat_patches", + ArrayRef<const uintptr_t>(patch_locations)); + } } } @@ -1338,8 +1365,9 @@ static void WriteDebugSections(ElfBuilder<ElfTypes>* builder, } template <typename ElfTypes> -void WriteDebugSymbols(ElfBuilder<ElfTypes>* builder, - const ArrayRef<const MethodDebugInfo>& method_infos) { +static void WriteDebugSymbols(ElfBuilder<ElfTypes>* builder, + const ArrayRef<const MethodDebugInfo>& method_infos, + bool with_signature) { bool generated_mapping_symbol = false; auto* strtab = builder->GetStrTab(); auto* symtab = builder->GetSymTab(); @@ -1359,22 +1387,31 @@ void WriteDebugSymbols(ElfBuilder<ElfTypes>* builder, strtab->Start(); strtab->Write(""); // strtab should start with empty string. + std::string last_name; + size_t last_name_offset = 0; for (const MethodDebugInfo& info : method_infos) { if (info.deduped_) { continue; // Add symbol only for the first instance. } - std::string name = PrettyMethod(info.dex_method_index_, *info.dex_file_, true); + std::string name = PrettyMethod(info.dex_method_index_, *info.dex_file_, with_signature); if (deduped_addresses.find(info.low_pc_) != deduped_addresses.end()) { name += " [DEDUPED]"; } + // If we write method names without signature, we might see the same name multiple times. + size_t name_offset = (name == last_name ? last_name_offset : strtab->Write(name)); const auto* text = builder->GetText()->Exists() ? builder->GetText() : nullptr; const bool is_relative = (text != nullptr); uint32_t low_pc = info.low_pc_; // Add in code delta, e.g., thumb bit 0 for Thumb2 code. low_pc += info.compiled_method_->CodeDelta(); - symtab->Add(strtab->Write(name), text, low_pc, - is_relative, info.high_pc_ - info.low_pc_, STB_GLOBAL, STT_FUNC); + symtab->Add(name_offset, + text, + low_pc, + is_relative, + info.high_pc_ - info.low_pc_, + STB_GLOBAL, + STT_FUNC); // Conforming to aaelf, add $t mapping symbol to indicate start of a sequence of thumb2 // instructions, so that disassembler tools can correctly disassemble. @@ -1387,6 +1424,9 @@ void WriteDebugSymbols(ElfBuilder<ElfTypes>* builder, generated_mapping_symbol = true; } } + + last_name = std::move(name); + last_name_offset = name_offset; } strtab->End(); @@ -1402,13 +1442,83 @@ void WriteDebugInfo(ElfBuilder<ElfTypes>* builder, const ArrayRef<const MethodDebugInfo>& method_infos, CFIFormat cfi_format) { // Add methods to .symtab. - WriteDebugSymbols(builder, method_infos); + WriteDebugSymbols(builder, method_infos, true /* with_signature */); // Generate CFI (stack unwinding information). - WriteCFISection(builder, method_infos, cfi_format); + WriteCFISection(builder, method_infos, cfi_format, true /* write_oat_patches */); // Write DWARF .debug_* sections. WriteDebugSections(builder, method_infos); } +static void XzCompress(const std::vector<uint8_t>* src, std::vector<uint8_t>* dst) { + // Configure the compression library. + CrcGenerateTable(); + Crc64GenerateTable(); + CLzma2EncProps lzma2Props; + Lzma2EncProps_Init(&lzma2Props); + lzma2Props.lzmaProps.level = 1; // Fast compression. + Lzma2EncProps_Normalize(&lzma2Props); + CXzProps props; + XzProps_Init(&props); + props.lzma2Props = &lzma2Props; + // Implement the required interface for communication (written in C so no virtual methods). + struct XzCallbacks : public ISeqInStream, public ISeqOutStream, public ICompressProgress { + static SRes ReadImpl(void* p, void* buf, size_t* size) { + auto* ctx = static_cast<XzCallbacks*>(reinterpret_cast<ISeqInStream*>(p)); + *size = std::min(*size, ctx->src_->size() - ctx->src_pos_); + memcpy(buf, ctx->src_->data() + ctx->src_pos_, *size); + ctx->src_pos_ += *size; + return SZ_OK; + } + static size_t WriteImpl(void* p, const void* buf, size_t size) { + auto* ctx = static_cast<XzCallbacks*>(reinterpret_cast<ISeqOutStream*>(p)); + const uint8_t* buffer = reinterpret_cast<const uint8_t*>(buf); + ctx->dst_->insert(ctx->dst_->end(), buffer, buffer + size); + return size; + } + static SRes ProgressImpl(void* , UInt64, UInt64) { + return SZ_OK; + } + size_t src_pos_; + const std::vector<uint8_t>* src_; + std::vector<uint8_t>* dst_; + }; + XzCallbacks callbacks; + callbacks.Read = XzCallbacks::ReadImpl; + callbacks.Write = XzCallbacks::WriteImpl; + callbacks.Progress = XzCallbacks::ProgressImpl; + callbacks.src_pos_ = 0; + callbacks.src_ = src; + callbacks.dst_ = dst; + // Compress. + SRes res = Xz_Encode(&callbacks, &callbacks, &props, &callbacks); + CHECK_EQ(res, SZ_OK); +} + +template <typename ElfTypes> +void WriteMiniDebugInfo(ElfBuilder<ElfTypes>* parent_builder, + const ArrayRef<const MethodDebugInfo>& method_infos) { + const InstructionSet isa = parent_builder->GetIsa(); + std::vector<uint8_t> buffer; + buffer.reserve(KB); + VectorOutputStream out("Mini-debug-info ELF file", &buffer); + std::unique_ptr<ElfBuilder<ElfTypes>> builder(new ElfBuilder<ElfTypes>(isa, &out)); + builder->Start(); + // Write .rodata and .text as NOBITS sections. + // This allows tools to detect virtual address relocation of the parent ELF file. + builder->SetVirtualAddress(parent_builder->GetRoData()->GetAddress()); + builder->GetRoData()->WriteNoBitsSection(parent_builder->GetRoData()->GetSize()); + builder->SetVirtualAddress(parent_builder->GetText()->GetAddress()); + builder->GetText()->WriteNoBitsSection(parent_builder->GetText()->GetSize()); + WriteDebugSymbols(builder.get(), method_infos, false /* with_signature */); + WriteCFISection(builder.get(), method_infos, DW_DEBUG_FRAME_FORMAT, false /* write_oat_paches */); + builder->End(); + CHECK(builder->Good()); + std::vector<uint8_t> compressed_buffer; + compressed_buffer.reserve(buffer.size() / 4); + XzCompress(&buffer, &compressed_buffer); + parent_builder->WriteSection(".gnu_debugdata", &compressed_buffer); +} + template <typename ElfTypes> static ArrayRef<const uint8_t> WriteDebugElfFileForMethodInternal( const dwarf::MethodDebugInfo& method_info) { @@ -1481,6 +1591,12 @@ template void WriteDebugInfo<ElfTypes64>( ElfBuilder<ElfTypes64>* builder, const ArrayRef<const MethodDebugInfo>& method_infos, CFIFormat cfi_format); +template void WriteMiniDebugInfo<ElfTypes32>( + ElfBuilder<ElfTypes32>* builder, + const ArrayRef<const MethodDebugInfo>& method_infos); +template void WriteMiniDebugInfo<ElfTypes64>( + ElfBuilder<ElfTypes64>* builder, + const ArrayRef<const MethodDebugInfo>& method_infos); } // namespace dwarf } // namespace art diff --git a/compiler/elf_writer_debug.h b/compiler/elf_writer_debug.h index e4bc856c5e..e19da088da 100644 --- a/compiler/elf_writer_debug.h +++ b/compiler/elf_writer_debug.h @@ -35,6 +35,10 @@ void WriteDebugInfo(ElfBuilder<ElfTypes>* builder, const ArrayRef<const MethodDebugInfo>& method_infos, CFIFormat cfi_format); +template <typename ElfTypes> +void WriteMiniDebugInfo(ElfBuilder<ElfTypes>* builder, + const ArrayRef<const MethodDebugInfo>& method_infos); + ArrayRef<const uint8_t> WriteDebugElfFileForMethod(const dwarf::MethodDebugInfo& method_info); ArrayRef<const uint8_t> WriteDebugElfFileForClasses(const InstructionSet isa, diff --git a/compiler/elf_writer_quick.cc b/compiler/elf_writer_quick.cc index 7b1bdd72e5..6bf080a083 100644 --- a/compiler/elf_writer_quick.cc +++ b/compiler/elf_writer_quick.cc @@ -137,9 +137,7 @@ template <typename ElfTypes> void ElfWriterQuick<ElfTypes>::SetBssSize(size_t bss_size) { auto* bss = builder_->GetBss(); if (bss_size != 0u) { - bss->Start(); - bss->SetSize(bss_size); - bss->End(); + bss->WriteNoBitsSection(bss_size); } } @@ -152,8 +150,13 @@ template <typename ElfTypes> void ElfWriterQuick<ElfTypes>::WriteDebugInfo( const ArrayRef<const dwarf::MethodDebugInfo>& method_infos) { if (compiler_options_->GetGenerateDebugInfo()) { + // Generate all the debug information we can. dwarf::WriteDebugInfo(builder_.get(), method_infos, kCFIFormat); } + if (compiler_options_->GetGenerateMiniDebugInfo()) { + // Generate only some information and compress it. + dwarf::WriteMiniDebugInfo(builder_.get(), method_infos); + } } template <typename ElfTypes> diff --git a/compiler/image_test.cc b/compiler/image_test.cc index b65fb36167..a5a7796614 100644 --- a/compiler/image_test.cc +++ b/compiler/image_test.cc @@ -119,6 +119,7 @@ void ImageTest::TestWriteRead(ImageHeader::StorageMode storage_mode) { compiler_driver_->GetInstructionSet(), compiler_driver_->GetInstructionSetFeatures(), &key_value_store, + /* verify */ false, // Dex files may be dex-to-dex-ed, don't verify. &opened_dex_files_map, &opened_dex_files); ASSERT_TRUE(dex_files_ok); diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc index 72c615e4bc..c8720eab8a 100644 --- a/compiler/image_writer.cc +++ b/compiler/image_writer.cc @@ -1866,6 +1866,9 @@ void ImageWriter::FixupClass(mirror::Class* orig, mirror::Class* copy) { orig->FixupNativePointers(copy, target_ptr_size_, NativeLocationVisitor(this, oat_filename)); FixupClassVisitor visitor(this, copy); static_cast<mirror::Object*>(orig)->VisitReferences(visitor, visitor); + + // Remove the clinitThreadId. This is required for image determinism. + copy->SetClinitThreadId(static_cast<pid_t>(0)); } void ImageWriter::FixupObject(Object* orig, Object* copy) { @@ -1993,6 +1996,10 @@ void ImageWriter::FixupDexCache(mirror::DexCache* orig_dex_cache, mirror::DexCache::SetElementPtrSize(copy_fields, i, copy, target_ptr_size_); } } + + // Remove the DexFile pointers. They will be fixed up when the runtime loads the oat file. Leaving + // compiler pointers in here will make the output non-deterministic. + copy_dex_cache->SetDexFile(nullptr); } const uint8_t* ImageWriter::GetOatAddress(OatAddress type) const { diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc index 083ba8f343..67747586c4 100644 --- a/compiler/jit/jit_compiler.cc +++ b/compiler/jit/jit_compiler.cc @@ -112,7 +112,8 @@ JitCompiler::JitCompiler() : total_time_(0) { /* init_failure_output */ nullptr, /* abort_on_hard_verifier_failure */ false, /* dump_cfg_file_name */ "", - /* dump_cfg_append */ false)); + /* dump_cfg_append */ false, + /* force_determinism */ false)); for (const std::string& argument : Runtime::Current()->GetCompilerOptions()) { compiler_options_->ParseCompilerOption(argument, Usage); } diff --git a/compiler/jni/quick/jni_compiler.cc b/compiler/jni/quick/jni_compiler.cc index 52a238233b..e92046057c 100644 --- a/compiler/jni/quick/jni_compiler.cc +++ b/compiler/jni/quick/jni_compiler.cc @@ -95,7 +95,7 @@ CompiledMethod* ArtJniCompileMethodInternal(CompilerDriver* driver, // Assembler that holds generated instructions std::unique_ptr<Assembler> jni_asm(Assembler::Create(instruction_set, instruction_set_features)); - jni_asm->cfi().SetEnabled(driver->GetCompilerOptions().GetGenerateDebugInfo()); + jni_asm->cfi().SetEnabled(driver->GetCompilerOptions().GenerateAnyDebugInfo()); // Offsets into data structures // TODO: if cross compiling these offsets are for the host not the target diff --git a/compiler/oat_test.cc b/compiler/oat_test.cc index c0d15f3439..cff2f471bf 100644 --- a/compiler/oat_test.cc +++ b/compiler/oat_test.cc @@ -126,7 +126,8 @@ class OatTest : public CommonCompilerTest { bool WriteElf(File* file, const std::vector<const DexFile*>& dex_files, - SafeMap<std::string, std::string>& key_value_store) { + SafeMap<std::string, std::string>& key_value_store, + bool verify) { TimingLogger timings("WriteElf", false, false); OatWriter oat_writer(/*compiling_boot_image*/false, &timings); for (const DexFile* dex_file : dex_files) { @@ -139,12 +140,13 @@ class OatTest : public CommonCompilerTest { return false; } } - return DoWriteElf(file, oat_writer, key_value_store); + return DoWriteElf(file, oat_writer, key_value_store, verify); } bool WriteElf(File* file, const std::vector<const char*>& dex_filenames, - SafeMap<std::string, std::string>& key_value_store) { + SafeMap<std::string, std::string>& key_value_store, + bool verify) { TimingLogger timings("WriteElf", false, false); OatWriter oat_writer(/*compiling_boot_image*/false, &timings); for (const char* dex_filename : dex_filenames) { @@ -152,24 +154,26 @@ class OatTest : public CommonCompilerTest { return false; } } - return DoWriteElf(file, oat_writer, key_value_store); + return DoWriteElf(file, oat_writer, key_value_store, verify); } bool WriteElf(File* file, ScopedFd&& zip_fd, const char* location, - SafeMap<std::string, std::string>& key_value_store) { + SafeMap<std::string, std::string>& key_value_store, + bool verify) { TimingLogger timings("WriteElf", false, false); OatWriter oat_writer(/*compiling_boot_image*/false, &timings); if (!oat_writer.AddZippedDexFilesSource(std::move(zip_fd), location)) { return false; } - return DoWriteElf(file, oat_writer, key_value_store); + return DoWriteElf(file, oat_writer, key_value_store, verify); } bool DoWriteElf(File* file, OatWriter& oat_writer, - SafeMap<std::string, std::string>& key_value_store) { + SafeMap<std::string, std::string>& key_value_store, + bool verify) { std::unique_ptr<ElfWriter> elf_writer = CreateElfWriterQuick( compiler_driver_->GetInstructionSet(), &compiler_driver_->GetCompilerOptions(), @@ -183,6 +187,7 @@ class OatTest : public CommonCompilerTest { compiler_driver_->GetInstructionSet(), compiler_driver_->GetInstructionSetFeatures(), &key_value_store, + verify, &opened_dex_files_map, &opened_dex_files)) { return false; @@ -219,6 +224,9 @@ class OatTest : public CommonCompilerTest { return elf_writer->End(); } + void TestDexFileInput(bool verify); + void TestZipFileInput(bool verify); + std::unique_ptr<const InstructionSetFeatures> insn_features_; std::unique_ptr<QuickCompilerCallbacks> callbacks_; }; @@ -354,7 +362,7 @@ TEST_F(OatTest, WriteRead) { ScratchFile tmp; SafeMap<std::string, std::string> key_value_store; key_value_store.Put(OatHeader::kImageLocationKey, "lue.art"); - bool success = WriteElf(tmp.GetFile(), class_linker->GetBootClassPath(), key_value_store); + bool success = WriteElf(tmp.GetFile(), class_linker->GetBootClassPath(), key_value_store, false); ASSERT_TRUE(success); if (kCompile) { // OatWriter strips the code, regenerate to compare @@ -480,7 +488,7 @@ TEST_F(OatTest, EmptyTextSection) { ScratchFile tmp; SafeMap<std::string, std::string> key_value_store; key_value_store.Put(OatHeader::kImageLocationKey, "test.art"); - bool success = WriteElf(tmp.GetFile(), dex_files, key_value_store); + bool success = WriteElf(tmp.GetFile(), dex_files, key_value_store, false); ASSERT_TRUE(success); std::unique_ptr<OatFile> oat_file(OatFile::Open(tmp.GetFilename(), @@ -494,7 +502,15 @@ TEST_F(OatTest, EmptyTextSection) { EXPECT_LT(static_cast<size_t>(oat_file->Size()), static_cast<size_t>(tmp.GetFile()->GetLength())); } -TEST_F(OatTest, DexFileInput) { +static void MaybeModifyDexFileToFail(bool verify, std::unique_ptr<const DexFile>& data) { + // If in verify mode (= fail the verifier mode), make sure we fail early. We'll fail already + // because of the missing map, but that may lead to out of bounds reads. + if (verify) { + const_cast<DexFile::Header*>(&data->GetHeader())->checksum_++; + } +} + +void OatTest::TestDexFileInput(bool verify) { TimingLogger timings("OatTest::DexFileInput", false, false); std::vector<const char*> input_filenames; @@ -504,6 +520,9 @@ TEST_F(OatTest, DexFileInput) { builder1.AddField("Lsome.TestClass;", "int", "someField"); builder1.AddMethod("Lsome.TestClass;", "()I", "foo"); std::unique_ptr<const DexFile> dex_file1_data = builder1.Build(dex_file1.GetFilename()); + + MaybeModifyDexFileToFail(verify, dex_file1_data); + bool success = dex_file1.GetFile()->WriteFully(&dex_file1_data->GetHeader(), dex_file1_data->GetHeader().file_size_); ASSERT_TRUE(success); @@ -516,6 +535,9 @@ TEST_F(OatTest, DexFileInput) { builder2.AddField("Land.AnotherTestClass;", "boolean", "someOtherField"); builder2.AddMethod("Land.AnotherTestClass;", "()J", "bar"); std::unique_ptr<const DexFile> dex_file2_data = builder2.Build(dex_file2.GetFilename()); + + MaybeModifyDexFileToFail(verify, dex_file2_data); + success = dex_file2.GetFile()->WriteFully(&dex_file2_data->GetHeader(), dex_file2_data->GetHeader().file_size_); ASSERT_TRUE(success); @@ -526,7 +548,14 @@ TEST_F(OatTest, DexFileInput) { ScratchFile oat_file; SafeMap<std::string, std::string> key_value_store; key_value_store.Put(OatHeader::kImageLocationKey, "test.art"); - success = WriteElf(oat_file.GetFile(), input_filenames, key_value_store); + success = WriteElf(oat_file.GetFile(), input_filenames, key_value_store, verify); + + // In verify mode, we expect failure. + if (verify) { + ASSERT_FALSE(success); + return; + } + ASSERT_TRUE(success); std::string error_msg; @@ -557,7 +586,15 @@ TEST_F(OatTest, DexFileInput) { ASSERT_EQ(dex_file2_data->GetLocation(), opened_dex_file2->GetLocation()); } -TEST_F(OatTest, ZipFileInput) { +TEST_F(OatTest, DexFileInputCheckOutput) { + TestDexFileInput(false); +} + +TEST_F(OatTest, DexFileInputCheckVerifier) { + TestDexFileInput(true); +} + +void OatTest::TestZipFileInput(bool verify) { TimingLogger timings("OatTest::DexFileInput", false, false); ScratchFile zip_file; @@ -568,6 +605,9 @@ TEST_F(OatTest, ZipFileInput) { builder1.AddField("Lsome.TestClass;", "long", "someField"); builder1.AddMethod("Lsome.TestClass;", "()D", "foo"); std::unique_ptr<const DexFile> dex_file1_data = builder1.Build(dex_file1.GetFilename()); + + MaybeModifyDexFileToFail(verify, dex_file1_data); + bool success = dex_file1.GetFile()->WriteFully(&dex_file1_data->GetHeader(), dex_file1_data->GetHeader().file_size_); ASSERT_TRUE(success); @@ -583,6 +623,9 @@ TEST_F(OatTest, ZipFileInput) { builder2.AddField("Land.AnotherTestClass;", "boolean", "someOtherField"); builder2.AddMethod("Land.AnotherTestClass;", "()J", "bar"); std::unique_ptr<const DexFile> dex_file2_data = builder2.Build(dex_file2.GetFilename()); + + MaybeModifyDexFileToFail(verify, dex_file2_data); + success = dex_file2.GetFile()->WriteFully(&dex_file2_data->GetHeader(), dex_file2_data->GetHeader().file_size_); ASSERT_TRUE(success); @@ -603,37 +646,42 @@ TEST_F(OatTest, ZipFileInput) { std::vector<const char*> input_filenames { zip_file.GetFilename().c_str() }; // NOLINT [readability/braces] [4] ScratchFile oat_file; - success = WriteElf(oat_file.GetFile(), input_filenames, key_value_store); - ASSERT_TRUE(success); + success = WriteElf(oat_file.GetFile(), input_filenames, key_value_store, verify); - std::string error_msg; - std::unique_ptr<OatFile> opened_oat_file(OatFile::Open(oat_file.GetFilename(), - oat_file.GetFilename(), - nullptr, - nullptr, - false, - nullptr, - &error_msg)); - ASSERT_TRUE(opened_oat_file != nullptr); - ASSERT_EQ(2u, opened_oat_file->GetOatDexFiles().size()); - std::unique_ptr<const DexFile> opened_dex_file1 = - opened_oat_file->GetOatDexFiles()[0]->OpenDexFile(&error_msg); - std::unique_ptr<const DexFile> opened_dex_file2 = - opened_oat_file->GetOatDexFiles()[1]->OpenDexFile(&error_msg); - - ASSERT_EQ(dex_file1_data->GetHeader().file_size_, opened_dex_file1->GetHeader().file_size_); - ASSERT_EQ(0, memcmp(&dex_file1_data->GetHeader(), - &opened_dex_file1->GetHeader(), - dex_file1_data->GetHeader().file_size_)); - ASSERT_EQ(DexFile::GetMultiDexLocation(0, zip_file.GetFilename().c_str()), - opened_dex_file1->GetLocation()); - - ASSERT_EQ(dex_file2_data->GetHeader().file_size_, opened_dex_file2->GetHeader().file_size_); - ASSERT_EQ(0, memcmp(&dex_file2_data->GetHeader(), - &opened_dex_file2->GetHeader(), - dex_file2_data->GetHeader().file_size_)); - ASSERT_EQ(DexFile::GetMultiDexLocation(1, zip_file.GetFilename().c_str()), - opened_dex_file2->GetLocation()); + if (verify) { + ASSERT_FALSE(success); + } else { + ASSERT_TRUE(success); + + std::string error_msg; + std::unique_ptr<OatFile> opened_oat_file(OatFile::Open(oat_file.GetFilename(), + oat_file.GetFilename(), + nullptr, + nullptr, + false, + nullptr, + &error_msg)); + ASSERT_TRUE(opened_oat_file != nullptr); + ASSERT_EQ(2u, opened_oat_file->GetOatDexFiles().size()); + std::unique_ptr<const DexFile> opened_dex_file1 = + opened_oat_file->GetOatDexFiles()[0]->OpenDexFile(&error_msg); + std::unique_ptr<const DexFile> opened_dex_file2 = + opened_oat_file->GetOatDexFiles()[1]->OpenDexFile(&error_msg); + + ASSERT_EQ(dex_file1_data->GetHeader().file_size_, opened_dex_file1->GetHeader().file_size_); + ASSERT_EQ(0, memcmp(&dex_file1_data->GetHeader(), + &opened_dex_file1->GetHeader(), + dex_file1_data->GetHeader().file_size_)); + ASSERT_EQ(DexFile::GetMultiDexLocation(0, zip_file.GetFilename().c_str()), + opened_dex_file1->GetLocation()); + + ASSERT_EQ(dex_file2_data->GetHeader().file_size_, opened_dex_file2->GetHeader().file_size_); + ASSERT_EQ(0, memcmp(&dex_file2_data->GetHeader(), + &opened_dex_file2->GetHeader(), + dex_file2_data->GetHeader().file_size_)); + ASSERT_EQ(DexFile::GetMultiDexLocation(1, zip_file.GetFilename().c_str()), + opened_dex_file2->GetLocation()); + } } { @@ -645,38 +693,51 @@ TEST_F(OatTest, ZipFileInput) { success = WriteElf(oat_file.GetFile(), std::move(zip_fd), zip_file.GetFilename().c_str(), - key_value_store); - ASSERT_TRUE(success); - - std::string error_msg; - std::unique_ptr<OatFile> opened_oat_file(OatFile::Open(oat_file.GetFilename(), - oat_file.GetFilename(), - nullptr, - nullptr, - false, - nullptr, - &error_msg)); - ASSERT_TRUE(opened_oat_file != nullptr); - ASSERT_EQ(2u, opened_oat_file->GetOatDexFiles().size()); - std::unique_ptr<const DexFile> opened_dex_file1 = - opened_oat_file->GetOatDexFiles()[0]->OpenDexFile(&error_msg); - std::unique_ptr<const DexFile> opened_dex_file2 = - opened_oat_file->GetOatDexFiles()[1]->OpenDexFile(&error_msg); - - ASSERT_EQ(dex_file1_data->GetHeader().file_size_, opened_dex_file1->GetHeader().file_size_); - ASSERT_EQ(0, memcmp(&dex_file1_data->GetHeader(), - &opened_dex_file1->GetHeader(), - dex_file1_data->GetHeader().file_size_)); - ASSERT_EQ(DexFile::GetMultiDexLocation(0, zip_file.GetFilename().c_str()), - opened_dex_file1->GetLocation()); - - ASSERT_EQ(dex_file2_data->GetHeader().file_size_, opened_dex_file2->GetHeader().file_size_); - ASSERT_EQ(0, memcmp(&dex_file2_data->GetHeader(), - &opened_dex_file2->GetHeader(), - dex_file2_data->GetHeader().file_size_)); - ASSERT_EQ(DexFile::GetMultiDexLocation(1, zip_file.GetFilename().c_str()), - opened_dex_file2->GetLocation()); + key_value_store, + verify); + if (verify) { + ASSERT_FALSE(success); + } else { + ASSERT_TRUE(success); + + std::string error_msg; + std::unique_ptr<OatFile> opened_oat_file(OatFile::Open(oat_file.GetFilename(), + oat_file.GetFilename(), + nullptr, + nullptr, + false, + nullptr, + &error_msg)); + ASSERT_TRUE(opened_oat_file != nullptr); + ASSERT_EQ(2u, opened_oat_file->GetOatDexFiles().size()); + std::unique_ptr<const DexFile> opened_dex_file1 = + opened_oat_file->GetOatDexFiles()[0]->OpenDexFile(&error_msg); + std::unique_ptr<const DexFile> opened_dex_file2 = + opened_oat_file->GetOatDexFiles()[1]->OpenDexFile(&error_msg); + + ASSERT_EQ(dex_file1_data->GetHeader().file_size_, opened_dex_file1->GetHeader().file_size_); + ASSERT_EQ(0, memcmp(&dex_file1_data->GetHeader(), + &opened_dex_file1->GetHeader(), + dex_file1_data->GetHeader().file_size_)); + ASSERT_EQ(DexFile::GetMultiDexLocation(0, zip_file.GetFilename().c_str()), + opened_dex_file1->GetLocation()); + + ASSERT_EQ(dex_file2_data->GetHeader().file_size_, opened_dex_file2->GetHeader().file_size_); + ASSERT_EQ(0, memcmp(&dex_file2_data->GetHeader(), + &opened_dex_file2->GetHeader(), + dex_file2_data->GetHeader().file_size_)); + ASSERT_EQ(DexFile::GetMultiDexLocation(1, zip_file.GetFilename().c_str()), + opened_dex_file2->GetLocation()); + } } } +TEST_F(OatTest, ZipFileInputCheckOutput) { + TestZipFileInput(false); +} + +TEST_F(OatTest, ZipFileInputCheckVerifier) { + TestZipFileInput(true); +} + } // namespace art diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc index a5421622b7..90ac499ba2 100644 --- a/compiler/oat_writer.cc +++ b/compiler/oat_writer.cc @@ -397,6 +397,7 @@ bool OatWriter::WriteAndOpenDexFiles( InstructionSet instruction_set, const InstructionSetFeatures* instruction_set_features, SafeMap<std::string, std::string>* key_value_store, + bool verify, /*out*/ std::unique_ptr<MemMap>* opened_dex_files_map, /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files) { CHECK(write_state_ == WriteState::kAddingDexFileSources); @@ -424,7 +425,7 @@ bool OatWriter::WriteAndOpenDexFiles( } if (!WriteOatDexFiles(rodata) || !ExtendForTypeLookupTables(rodata, file, size_after_type_lookup_tables) || - !OpenDexFiles(file, &dex_files_map, &dex_files) || + !OpenDexFiles(file, verify, &dex_files_map, &dex_files) || !WriteTypeLookupTables(dex_files_map.get(), dex_files)) { return false; } @@ -806,7 +807,7 @@ class OatWriter::InitCodeMethodVisitor : public OatDexMethodVisitor { } } - if (writer_->compiler_driver_->GetCompilerOptions().GetGenerateDebugInfo()) { + if (writer_->compiler_driver_->GetCompilerOptions().GenerateAnyDebugInfo()) { // Record debug information for this function if we are doing that. const uint32_t quick_code_start = quick_code_offset - writer_->oat_header_->GetExecutableOffset() - thumb_offset; @@ -2143,6 +2144,7 @@ bool OatWriter::ExtendForTypeLookupTables(OutputStream* rodata, File* file, size bool OatWriter::OpenDexFiles( File* file, + bool verify, /*out*/ std::unique_ptr<MemMap>* opened_dex_files_map, /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files) { TimingLogger::ScopedTiming split("OpenDexFiles", timings_); @@ -2201,9 +2203,11 @@ bool OatWriter::OpenDexFiles( oat_dex_file.GetLocation(), oat_dex_file.dex_file_location_checksum_, /* oat_dex_file */ nullptr, + verify, &error_msg)); if (dex_files.back() == nullptr) { - LOG(ERROR) << "Failed to open dex file from oat file. File:" << oat_dex_file.GetLocation(); + LOG(ERROR) << "Failed to open dex file from oat file. File: " << oat_dex_file.GetLocation() + << " Error: " << error_msg; return false; } } diff --git a/compiler/oat_writer.h b/compiler/oat_writer.h index d681998774..14c6d5054a 100644 --- a/compiler/oat_writer.h +++ b/compiler/oat_writer.h @@ -139,12 +139,15 @@ class OatWriter { CreateTypeLookupTable create_type_lookup_table = CreateTypeLookupTable::kDefault); dchecked_vector<const char*> GetSourceLocations() const; - // Write raw dex files to the .rodata section and open them from the oat file. + // Write raw dex files to the .rodata section and open them from the oat file. The verify + // setting dictates whether the dex file verifier should check the dex files. This is generally + // the case, and should only be false for tests. bool WriteAndOpenDexFiles(OutputStream* rodata, File* file, InstructionSet instruction_set, const InstructionSetFeatures* instruction_set_features, SafeMap<std::string, std::string>* key_value_store, + bool verify, /*out*/ std::unique_ptr<MemMap>* opened_dex_files_map, /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files); // Prepare layout of remaining data. @@ -258,6 +261,7 @@ class OatWriter { bool WriteOatDexFiles(OutputStream* rodata); bool ExtendForTypeLookupTables(OutputStream* rodata, File* file, size_t offset); bool OpenDexFiles(File* file, + bool verify, /*out*/ std::unique_ptr<MemMap>* opened_dex_files_map, /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files); bool WriteTypeLookupTables(MemMap* opened_dex_files_map, diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc index 280516252b..9d796c1004 100644 --- a/compiler/optimizing/graph_visualizer.cc +++ b/compiler/optimizing/graph_visualizer.cc @@ -507,6 +507,7 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { || IsPass(HDeadCodeElimination::kFinalDeadCodeEliminationPassName) || IsPass(HDeadCodeElimination::kInitialDeadCodeEliminationPassName) || IsPass(BoundsCheckElimination::kBoundsCheckEliminationPassName) + || IsPass(RegisterAllocator::kRegisterAllocatorPassName) || IsPass(SsaBuilder::kSsaBuilderPassName)) { HLoopInformation* info = instruction->GetBlock()->GetLoopInformation(); if (info == nullptr) { diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc index fffd00535c..3fac914017 100644 --- a/compiler/optimizing/optimizing_compiler.cc +++ b/compiler/optimizing/optimizing_compiler.cc @@ -676,7 +676,7 @@ CodeGenerator* OptimizingCompiler::TryCompile(ArenaAllocator* arena, return nullptr; } codegen->GetAssembler()->cfi().SetEnabled( - compiler_driver->GetCompilerOptions().GetGenerateDebugInfo()); + compiler_driver->GetCompilerOptions().GenerateAnyDebugInfo()); PassObserver pass_observer(graph, codegen.get(), diff --git a/compiler/optimizing/register_allocator.cc b/compiler/optimizing/register_allocator.cc index d77639d608..5cd30adb45 100644 --- a/compiler/optimizing/register_allocator.cc +++ b/compiler/optimizing/register_allocator.cc @@ -179,7 +179,7 @@ void RegisterAllocator::AllocateRegistersInternal() { } if (block->IsCatchBlock() || - (block->GetLoopInformation() != nullptr && block->GetLoopInformation()->IsIrreducible())) { + (block->IsLoopHeader() && block->GetLoopInformation()->IsIrreducible())) { // By blocking all registers at the top of each catch block or irreducible loop, we force // intervals belonging to the live-in set of the catch/header block to be spilled. // TODO(ngeoffray): Phis in this block could be allocated in register. @@ -1749,8 +1749,10 @@ void RegisterAllocator::ConnectSplitSiblings(LiveInterval* interval, } // Find the intervals that cover `from` and `to`. - LiveInterval* destination = interval->GetSiblingAt(to->GetLifetimeStart()); - LiveInterval* source = interval->GetSiblingAt(from->GetLifetimeEnd() - 1); + size_t destination_position = to->GetLifetimeStart(); + size_t source_position = from->GetLifetimeEnd() - 1; + LiveInterval* destination = interval->GetSiblingAt(destination_position); + LiveInterval* source = interval->GetSiblingAt(source_position); if (destination == source) { // Interval was not split. @@ -1759,7 +1761,8 @@ void RegisterAllocator::ConnectSplitSiblings(LiveInterval* interval, LiveInterval* parent = interval->GetParent(); HInstruction* defined_by = parent->GetDefinedBy(); - if (destination == nullptr) { + if (codegen_->GetGraph()->HasIrreducibleLoops() && + (destination == nullptr || !destination->CoversSlow(destination_position))) { // Our live_in fixed point calculation has found that the instruction is live // in the `to` block because it will eventually enter an irreducible loop. Our // live interval computation however does not compute a fixed point, and @@ -1775,18 +1778,41 @@ void RegisterAllocator::ConnectSplitSiblings(LiveInterval* interval, return; } + Location location_source; + // `GetSiblingAt` returns the interval whose start and end cover `position`, + // but does not check whether the interval is inactive at that position. + // The only situation where the interval is inactive at that position is in the + // presence of irreducible loops for constants and ArtMethod. + if (codegen_->GetGraph()->HasIrreducibleLoops() && + (source == nullptr || !source->CoversSlow(source_position))) { + DCHECK(IsMaterializableEntryBlockInstructionOfGraphWithIrreducibleLoop(defined_by)); + if (defined_by->IsConstant()) { + location_source = defined_by->GetLocations()->Out(); + } else { + DCHECK(defined_by->IsCurrentMethod()); + location_source = parent->NeedsTwoSpillSlots() + ? Location::DoubleStackSlot(parent->GetSpillSlot()) + : Location::StackSlot(parent->GetSpillSlot()); + } + } else { + DCHECK(source != nullptr); + DCHECK(source->CoversSlow(source_position)); + DCHECK(destination->CoversSlow(destination_position)); + location_source = source->ToLocation(); + } + // If `from` has only one successor, we can put the moves at the exit of it. Otherwise // we need to put the moves at the entry of `to`. if (from->GetNormalSuccessors().size() == 1) { InsertParallelMoveAtExitOf(from, defined_by, - source->ToLocation(), + location_source, destination->ToLocation()); } else { DCHECK_EQ(to->GetPredecessors().size(), 1u); InsertParallelMoveAtEntryOf(to, defined_by, - source->ToLocation(), + location_source, destination->ToLocation()); } } @@ -1890,7 +1916,7 @@ void RegisterAllocator::Resolve() { for (HLinearOrderIterator it(*codegen_->GetGraph()); !it.Done(); it.Advance()) { HBasicBlock* block = it.Current(); if (block->IsCatchBlock() || - (block->GetLoopInformation() != nullptr && block->GetLoopInformation()->IsIrreducible())) { + (block->IsLoopHeader() && block->GetLoopInformation()->IsIrreducible())) { // Instructions live at the top of catch blocks or irreducible loop header // were forced to spill. if (kIsDebugBuild) { diff --git a/compiler/utils/test_dex_file_builder.h b/compiler/utils/test_dex_file_builder.h index e57a540669..2958dc6e2a 100644 --- a/compiler/utils/test_dex_file_builder.h +++ b/compiler/utils/test_dex_file_builder.h @@ -89,11 +89,12 @@ class TestDexFileBuilder { DexFile::Header* header = reinterpret_cast<DexFile::Header*>(&header_data.data); std::copy_n(DexFile::kDexMagic, 4u, header->magic_); std::copy_n(DexFile::kDexMagicVersion, 4u, header->magic_ + 4u); - header->header_size_ = sizeof(header); + header->header_size_ = sizeof(DexFile::Header); header->endian_tag_ = DexFile::kDexEndianConstant; header->link_size_ = 0u; // Unused. header->link_off_ = 0u; // Unused. - header->map_off_ = 0u; // Unused. + header->map_off_ = 0u; // Unused. TODO: This is wrong. Dex files created by this builder + // cannot be verified. b/26808512 uint32_t data_section_size = 0u; @@ -213,13 +214,22 @@ class TestDexFileBuilder { // Leave signature as zeros. header->file_size_ = dex_file_data_.size(); + + // Write the complete header early, as part of it needs to be checksummed. + std::memcpy(&dex_file_data_[0], header_data.data, sizeof(DexFile::Header)); + + // Checksum starts after the checksum field. size_t skip = sizeof(header->magic_) + sizeof(header->checksum_); - header->checksum_ = adler32(0u, dex_file_data_.data() + skip, dex_file_data_.size() - skip); + header->checksum_ = adler32(adler32(0L, Z_NULL, 0), + dex_file_data_.data() + skip, + dex_file_data_.size() - skip); + + // Write the complete header again, just simpler that way. std::memcpy(&dex_file_data_[0], header_data.data, sizeof(DexFile::Header)); std::string error_msg; std::unique_ptr<const DexFile> dex_file(DexFile::Open( - &dex_file_data_[0], dex_file_data_.size(), dex_location, 0u, nullptr, &error_msg)); + &dex_file_data_[0], dex_file_data_.size(), dex_location, 0u, nullptr, false, &error_msg)); CHECK(dex_file != nullptr) << error_msg; return dex_file; } diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 86f51e1131..bbed09ce14 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -318,6 +318,11 @@ NO_RETURN static void Usage(const char* fmt, ...) { UsageError(""); UsageError(" --no-generate-debug-info: Do not generate debug information for native debugging."); UsageError(""); + UsageError(" --generate-mini-debug-info: Generate minimal amount of LZMA-compressed"); + UsageError(" debug information necessary to print backtraces. (disabled by default)"); + UsageError(""); + UsageError(" --no-generate-mini-debug-info: Do do generated backtrace info."); + UsageError(""); UsageError(" --debuggable: Produce code debuggable with Java debugger."); UsageError(""); UsageError(" --native-debuggable: Produce code debuggable with native debugger (like LLDB)."); @@ -548,13 +553,15 @@ class Dex2Oat FINAL { driver_(nullptr), opened_dex_files_maps_(), opened_dex_files_(), + no_inline_from_dex_files_(), dump_stats_(false), dump_passes_(false), dump_timing_(false), dump_slow_timing_(kIsDebugBuild), swap_fd_(-1), app_image_fd_(kInvalidFd), - timings_(timings) {} + timings_(timings), + force_determinism_(false) {} ~Dex2Oat() { // Log completion time before deleting the runtime_, because this accesses @@ -916,6 +923,12 @@ class Dex2Oat FINAL { // Fill some values into the key-value store for the oat header. key_value_store_.reset(new SafeMap<std::string, std::string>()); + + // Automatically force determinism for the boot image in a host build. + if (!kIsTargetBuild && IsBootImage()) { + force_determinism_ = true; + } + compiler_options_->force_determinism_ = force_determinism_; } void ExpandOatAndImageFilenames() { @@ -1159,6 +1172,8 @@ class Dex2Oat FINAL { multi_image_ = true; } else if (option.starts_with("--no-inline-from=")) { no_inline_from_string_ = option.substr(strlen("--no-inline-from=")).data(); + } else if (option == "--force-determinism") { + force_determinism_ = true; } else if (!compiler_options_->ParseCompilerOption(option, Usage)) { Usage("Unknown argument %s", option.data()); } @@ -1351,6 +1366,7 @@ class Dex2Oat FINAL { instruction_set_, instruction_set_features_.get(), key_value_store_.get(), + /* verify */ true, &opened_dex_files_map, &opened_dex_files)) { return false; @@ -1452,14 +1468,18 @@ class Dex2Oat FINAL { TimingLogger::ScopedTiming t("dex2oat Compile", timings_); compiler_phases_timings_.reset(new CumulativeLogger("compilation times")); - // Find the dex file we should not inline from. + // Find the dex files we should not inline from. + + std::vector<std::string> no_inline_filters; + Split(no_inline_from_string_, ',', &no_inline_filters); // For now, on the host always have core-oj removed. - if (!kIsTargetBuild && no_inline_from_string_.empty()) { - no_inline_from_string_ = "core-oj"; + const std::string core_oj = "core-oj"; + if (!kIsTargetBuild && !ContainsElement(no_inline_filters, core_oj)) { + no_inline_filters.push_back(core_oj); } - if (!no_inline_from_string_.empty()) { + if (!no_inline_filters.empty()) { ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); std::vector<const DexFile*> class_path_files = MakeNonOwningPointerVector(class_path_files_); std::vector<const std::vector<const DexFile*>*> dex_file_vectors = { @@ -1468,34 +1488,30 @@ class Dex2Oat FINAL { &dex_files_ }; for (const std::vector<const DexFile*>* dex_file_vector : dex_file_vectors) { - if (dex_file_vector == nullptr) { - continue; - } - - bool found = false; - for (const DexFile* dex_file : *dex_file_vector) { - // Try the complete location first. - found = no_inline_from_string_ == dex_file->GetLocation(); - // The try just the name. - if (!found) { - size_t last_slash = dex_file->GetLocation().rfind('/'); - if (last_slash != std::string::npos) { - found = StartsWith(dex_file->GetLocation().substr(last_slash + 1), - no_inline_from_string_.c_str()); + for (const std::string& filter : no_inline_filters) { + // Use dex_file->GetLocation() rather than dex_file->GetBaseLocation(). This + // allows tests to specify <test-dexfile>:classes2.dex if needed but if the + // base location passes the StartsWith() test, so do all extra locations. + std::string dex_location = dex_file->GetLocation(); + if (filter.find('/') == std::string::npos) { + // The filter does not contain the path. Remove the path from dex_location as well. + size_t last_slash = dex_file->GetLocation().rfind('/'); + if (last_slash != std::string::npos) { + dex_location = dex_location.substr(last_slash + 1); + } } - } - if (found) { - VLOG(compiler) << "Disabling inlining from " << dex_file->GetLocation(); - compiler_options_->no_inline_from_ = dex_file; - break; + if (StartsWith(dex_location, filter.c_str())) { + VLOG(compiler) << "Disabling inlining from " << dex_file->GetLocation(); + no_inline_from_dex_files_.push_back(dex_file); + break; + } } } - - if (found) { - break; - } + } + if (!no_inline_from_dex_files_.empty()) { + compiler_options_->no_inline_from_ = &no_inline_from_dex_files_; } } @@ -2105,6 +2121,21 @@ class Dex2Oat FINAL { // foreground collector by default for dex2oat. raw_options.push_back(std::make_pair("-XX:DisableHSpaceCompactForOOM", nullptr)); + // If we're asked to be deterministic, ensure non-concurrent GC for determinism. Also + // force the free-list implementation for large objects. + if (compiler_options_->IsForceDeterminism()) { + raw_options.push_back(std::make_pair("-Xgc:nonconcurrent", nullptr)); + raw_options.push_back(std::make_pair("-XX:LargeObjectSpace=freelist", nullptr)); + + // We also need to turn off the nonmoving space. For that, we need to disable HSpace + // compaction (done above) and ensure that neither foreground nor background collectors + // are concurrent. + raw_options.push_back(std::make_pair("-XX:BackgroundGC=nonconcurrent", nullptr)); + + // To make identity hashcode deterministic, set a known seed. + mirror::Object::SetHashCodeSeed(987654321U); + } + if (!Runtime::ParseOptions(raw_options, false, runtime_options)) { LOG(ERROR) << "Failed to parse runtime options"; return false; @@ -2384,6 +2415,8 @@ class Dex2Oat FINAL { std::vector<std::unique_ptr<MemMap>> opened_dex_files_maps_; std::vector<std::unique_ptr<const DexFile>> opened_dex_files_; + std::vector<const DexFile*> no_inline_from_dex_files_; + std::vector<std::string> verbose_methods_; bool dump_stats_; bool dump_passes_; @@ -2406,6 +2439,9 @@ class Dex2Oat FINAL { // Backing storage. std::vector<std::string> char_backing_storage_; + // See CompilerOptions.force_determinism_. + bool force_determinism_; + DISALLOW_IMPLICIT_CONSTRUCTORS(Dex2Oat); }; diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index 57b52fcdd8..a1f6eee42c 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -142,9 +142,7 @@ class OatSymbolizer FINAL { text->End(); if (oat_file_->BssSize() != 0) { - bss->Start(); - bss->SetSize(oat_file_->BssSize()); - bss->End(); + bss->WriteNoBitsSection(oat_file_->BssSize()); } builder_->WriteDynamicSection(elf_file->GetPath()); @@ -1656,7 +1654,7 @@ class ImageDumper { stats_.file_bytes = file->GetLength(); } size_t header_bytes = sizeof(ImageHeader); - const auto& bitmap_section = image_header_.GetImageSection(ImageHeader::kSectionImageBitmap); + const auto& object_section = image_header_.GetImageSection(ImageHeader::kSectionObjects); const auto& field_section = image_header_.GetImageSection(ImageHeader::kSectionArtFields); const auto& method_section = image_header_.GetMethodsSection(); const auto& dex_cache_arrays_section = image_header_.GetImageSection( @@ -1665,17 +1663,46 @@ class ImageDumper { ImageHeader::kSectionInternedStrings); const auto& class_table_section = image_header_.GetImageSection( ImageHeader::kSectionClassTable); + const auto& bitmap_section = image_header_.GetImageSection(ImageHeader::kSectionImageBitmap); + stats_.header_bytes = header_bytes; - stats_.alignment_bytes += RoundUp(header_bytes, kObjectAlignment) - header_bytes; - // Add padding between the field and method section. - // (Field section is 4-byte aligned, method section is 8-byte aligned on 64-bit targets.) - stats_.alignment_bytes += method_section.Offset() - - (field_section.Offset() + field_section.Size()); - // Add padding between the dex cache arrays section and the intern table. (Dex cache - // arrays section is 4-byte aligned on 32-bit targets, intern table is 8-byte aligned.) - stats_.alignment_bytes += intern_section.Offset() - - (dex_cache_arrays_section.Offset() + dex_cache_arrays_section.Size()); - stats_.alignment_bytes += bitmap_section.Offset() - image_header_.GetImageSize(); + + // Objects are kObjectAlignment-aligned. + // CHECK_EQ(RoundUp(header_bytes, kObjectAlignment), object_section.Offset()); + if (object_section.Offset() > header_bytes) { + stats_.alignment_bytes += object_section.Offset() - header_bytes; + } + + // Field section is 4-byte aligned. + constexpr size_t kFieldSectionAlignment = 4U; + uint32_t end_objects = object_section.Offset() + object_section.Size(); + CHECK_EQ(RoundUp(end_objects, kFieldSectionAlignment), field_section.Offset()); + stats_.alignment_bytes += field_section.Offset() - end_objects; + + // Method section is 4/8 byte aligned depending on target. Just check for 4-byte alignment. + uint32_t end_fields = field_section.Offset() + field_section.Size(); + CHECK_ALIGNED(method_section.Offset(), 4); + stats_.alignment_bytes += method_section.Offset() - end_fields; + + // Dex cache arrays section is aligned depending on the target. Just check for 4-byte alignment. + uint32_t end_methods = method_section.Offset() + method_section.Size(); + CHECK_ALIGNED(dex_cache_arrays_section.Offset(), 4); + stats_.alignment_bytes += dex_cache_arrays_section.Offset() - end_methods; + + // Intern table is 8-byte aligned. + uint32_t end_caches = dex_cache_arrays_section.Offset() + dex_cache_arrays_section.Size(); + CHECK_EQ(RoundUp(end_caches, 8U), intern_section.Offset()); + stats_.alignment_bytes += intern_section.Offset() - end_caches; + + // Add space between intern table and class table. + uint32_t end_intern = intern_section.Offset() + intern_section.Size(); + stats_.alignment_bytes += class_table_section.Offset() - end_intern; + + // Add space between class table and bitmap. Expect the bitmap to be page-aligned. + uint32_t end_ctable = class_table_section.Offset() + class_table_section.Size(); + CHECK_ALIGNED(bitmap_section.Offset(), kPageSize); + stats_.alignment_bytes += bitmap_section.Offset() - end_ctable; + stats_.bitmap_bytes += bitmap_section.Size(); stats_.art_field_bytes += field_section.Size(); stats_.art_method_bytes += method_section.Size(); diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index bb709e8774..44cc9b7bf2 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -1533,6 +1533,14 @@ bool ClassLinker::AddImageSpace( } } if (!equal) { + VLOG(image) << "Image dex files " << image_dex_file_names.size(); + for (mirror::String* name : image_dex_file_names) { + VLOG(image) << name->ToModifiedUtf8(); + } + VLOG(image) << "Loader dex files " << loader_dex_file_names.size(); + for (mirror::String* name : loader_dex_file_names) { + VLOG(image) << name->ToModifiedUtf8(); + } *error_msg = "Rejecting application image due to class loader mismatch"; return false; } diff --git a/runtime/dex_file.cc b/runtime/dex_file.cc index 9b93c131df..81a3e4b08c 100644 --- a/runtime/dex_file.cc +++ b/runtime/dex_file.cc @@ -195,6 +195,30 @@ bool DexFile::DisableWrite() const { } } +std::unique_ptr<const DexFile> DexFile::Open(const uint8_t* base, size_t size, + const std::string& location, + uint32_t location_checksum, + const OatDexFile* oat_dex_file, + bool verify, + std::string* error_msg) { + std::unique_ptr<const DexFile> dex_file = OpenMemory(base, + size, + location, + location_checksum, + nullptr, + oat_dex_file, + error_msg); + if (verify && !DexFileVerifier::Verify(dex_file.get(), + dex_file->Begin(), + dex_file->Size(), + location.c_str(), + error_msg)) { + return nullptr; + } + + return dex_file; +} + std::unique_ptr<const DexFile> DexFile::OpenFile(int fd, const char* location, bool verify, std::string* error_msg) { CHECK(location != nullptr); diff --git a/runtime/dex_file.h b/runtime/dex_file.h index 200121e61f..e497e9c6db 100644 --- a/runtime/dex_file.h +++ b/runtime/dex_file.h @@ -417,9 +417,8 @@ class DexFile { const std::string& location, uint32_t location_checksum, const OatDexFile* oat_dex_file, - std::string* error_msg) { - return OpenMemory(base, size, location, location_checksum, nullptr, oat_dex_file, error_msg); - } + bool verify, + std::string* error_msg); // Open all classesXXX.dex files from a zip archive. static bool OpenFromZip(const ZipArchive& zip_archive, const std::string& location, diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index 137540afd2..84483b4370 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -114,6 +114,9 @@ static constexpr size_t kDefaultAllocationStackSize = 8 * MB / // timeout on how long we wait for finalizers to run. b/21544853 static constexpr uint64_t kNativeAllocationFinalizeTimeout = MsToNs(250u); +// For deterministic compilation, we need the heap to be at a well-known address. +static constexpr uint32_t kAllocSpaceBeginForDeterministicAoT = 0x40000000; + Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, @@ -347,11 +350,16 @@ Heap::Heap(size_t initial_size, bool separate_non_moving_space = is_zygote || support_homogeneous_space_compaction || IsMovingGc(foreground_collector_type_) || IsMovingGc(background_collector_type_); - if (foreground_collector_type == kCollectorTypeGSS) { + if (foreground_collector_type_ == kCollectorTypeGSS) { separate_non_moving_space = false; } std::unique_ptr<MemMap> main_mem_map_1; std::unique_ptr<MemMap> main_mem_map_2; + + // Gross hack to make dex2oat deterministic. + if (requested_alloc_space_begin == nullptr && Runtime::Current()->IsAotCompiler()) { + requested_alloc_space_begin = reinterpret_cast<uint8_t*>(kAllocSpaceBeginForDeterministicAoT); + } uint8_t* request_begin = requested_alloc_space_begin; if (request_begin != nullptr && separate_non_moving_space) { request_begin += non_moving_space_capacity; @@ -2346,6 +2354,9 @@ void Heap::PreZygoteFork() { // We still want to GC in case there is some unreachable non moving objects that could cause a // suboptimal bin packing when we compact the zygote space. CollectGarbageInternal(collector::kGcTypeFull, kGcCauseBackground, false); + // Trim the pages at the end of the non moving space. Trim while not holding zygote lock since + // the trim process may require locking the mutator lock. + non_moving_space_->Trim(); } Thread* self = Thread::Current(); MutexLock mu(self, zygote_creation_lock_); @@ -2356,8 +2367,6 @@ void Heap::PreZygoteFork() { Runtime::Current()->GetInternTable()->AddNewTable(); Runtime::Current()->GetClassLinker()->MoveClassTableToPreZygote(); VLOG(heap) << "Starting PreZygoteFork"; - // Trim the pages at the end of the non moving space. - non_moving_space_->Trim(); // The end of the non-moving space may be protected, unprotect it so that we can copy the zygote // there. non_moving_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE); diff --git a/runtime/mirror/array-inl.h b/runtime/mirror/array-inl.h index b6f424b3a4..b3439f7643 100644 --- a/runtime/mirror/array-inl.h +++ b/runtime/mirror/array-inl.h @@ -381,19 +381,26 @@ inline T PointerArray::GetElementPtrSize(uint32_t idx, size_t ptr_size) { return (T)static_cast<uintptr_t>(AsIntArray()->GetWithoutChecks(idx)); } -template<bool kTransactionActive, bool kUnchecked, typename T> -inline void PointerArray::SetElementPtrSize(uint32_t idx, T element, size_t ptr_size) { +template<bool kTransactionActive, bool kUnchecked> +inline void PointerArray::SetElementPtrSize(uint32_t idx, uint64_t element, size_t ptr_size) { if (ptr_size == 8) { (kUnchecked ? down_cast<LongArray*>(static_cast<Object*>(this)) : AsLongArray())-> - SetWithoutChecks<kTransactionActive>(idx, (uint64_t)(element)); + SetWithoutChecks<kTransactionActive>(idx, element); } else { DCHECK_EQ(ptr_size, 4u); - DCHECK_LE((uintptr_t)element, 0xFFFFFFFFu); + DCHECK_LE(element, static_cast<uint64_t>(0xFFFFFFFFu)); (kUnchecked ? down_cast<IntArray*>(static_cast<Object*>(this)) : AsIntArray()) - ->SetWithoutChecks<kTransactionActive>(idx, static_cast<uint32_t>((uintptr_t)element)); + ->SetWithoutChecks<kTransactionActive>(idx, static_cast<uint32_t>(element)); } } +template<bool kTransactionActive, bool kUnchecked, typename T> +inline void PointerArray::SetElementPtrSize(uint32_t idx, T* element, size_t ptr_size) { + SetElementPtrSize<kTransactionActive, kUnchecked>(idx, + reinterpret_cast<uintptr_t>(element), + ptr_size); +} + template <typename Visitor> inline void PointerArray::Fixup(mirror::PointerArray* dest, size_t pointer_size, diff --git a/runtime/mirror/array.h b/runtime/mirror/array.h index 50d77ebdb8..2bd6c5b9a1 100644 --- a/runtime/mirror/array.h +++ b/runtime/mirror/array.h @@ -187,8 +187,11 @@ class PointerArray : public Array { T GetElementPtrSize(uint32_t idx, size_t ptr_size) SHARED_REQUIRES(Locks::mutator_lock_); + template<bool kTransactionActive = false, bool kUnchecked = false> + void SetElementPtrSize(uint32_t idx, uint64_t element, size_t ptr_size) + SHARED_REQUIRES(Locks::mutator_lock_); template<bool kTransactionActive = false, bool kUnchecked = false, typename T> - void SetElementPtrSize(uint32_t idx, T element, size_t ptr_size) + void SetElementPtrSize(uint32_t idx, T* element, size_t ptr_size) SHARED_REQUIRES(Locks::mutator_lock_); // Fixup the pointers in the dest arrays by passing our pointers through the visitor. Only copies diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h index 75a3f1aa05..d5783c04b5 100644 --- a/runtime/mirror/class-inl.h +++ b/runtime/mirror/class-inl.h @@ -157,7 +157,7 @@ inline ArraySlice<ArtMethod> Class::GetCopiedMethodsSliceUnchecked(size_t pointe inline LengthPrefixedArray<ArtMethod>* Class::GetMethodsPtr() { return reinterpret_cast<LengthPrefixedArray<ArtMethod>*>( - GetField64(OFFSET_OF_OBJECT_MEMBER(Class, methods_))); + static_cast<uintptr_t>(GetField64(OFFSET_OF_OBJECT_MEMBER(Class, methods_)))); } template<VerifyObjectFlags kVerifyFlags> @@ -208,7 +208,7 @@ inline void Class::SetMethodsPtrUnchecked(LengthPrefixedArray<ArtMethod>* new_me inline void Class::SetMethodsPtrInternal(LengthPrefixedArray<ArtMethod>* new_methods) { SetField64<false>(OFFSET_OF_OBJECT_MEMBER(Class, methods_), - reinterpret_cast<uint64_t>(new_methods)); + static_cast<uint64_t>(reinterpret_cast<uintptr_t>(new_methods))); } template<VerifyObjectFlags kVerifyFlags> diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h index 3017820ea1..195bbc9c33 100644 --- a/runtime/mirror/class.h +++ b/runtime/mirror/class.h @@ -561,7 +561,7 @@ class MANAGED Class FINAL : public Object { // The size of java.lang.Class.class. static uint32_t ClassClassSize(size_t pointer_size) { // The number of vtable entries in java.lang.Class. - uint32_t vtable_entries = Object::kVTableLength + 69; + uint32_t vtable_entries = Object::kVTableLength + 72; return ComputeClassSize(true, vtable_entries, 0, 0, 4, 1, 0, pointer_size); } diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc index 0ddd4a280c..a80585abca 100644 --- a/runtime/native/java_lang_Class.cc +++ b/runtime/native/java_lang_Class.cc @@ -469,14 +469,21 @@ static jobjectArray Class_getDeclaredMethodsUnchecked(JNIEnv* env, jobject javaT return soa.AddLocalReference<jobjectArray>(ret.Get()); } -static jobject Class_getDeclaredAnnotation(JNIEnv* env, jobject javaThis, jclass annotationType) { +static jobject Class_getDeclaredAnnotation(JNIEnv* env, jobject javaThis, jclass annotationClass) { ScopedFastNativeObjectAccess soa(env); StackHandleScope<2> hs(soa.Self()); Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis))); + + // Handle public contract to throw NPE if the "annotationClass" argument was null. + if (UNLIKELY(annotationClass == nullptr)) { + ThrowNullPointerException("annotationClass"); + return nullptr; + } + if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) { return nullptr; } - Handle<mirror::Class> annotation_class(hs.NewHandle(soa.Decode<mirror::Class*>(annotationType))); + Handle<mirror::Class> annotation_class(hs.NewHandle(soa.Decode<mirror::Class*>(annotationClass))); return soa.AddLocalReference<jobject>( klass->GetDexFile().GetAnnotationForClass(klass, annotation_class)); } diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index 82b39331dd..8f321a092c 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -1072,8 +1072,13 @@ size_t OatFile::OatDexFile::FileSize() const { } std::unique_ptr<const DexFile> OatFile::OatDexFile::OpenDexFile(std::string* error_msg) const { - return DexFile::Open(dex_file_pointer_, FileSize(), dex_file_location_, - dex_file_location_checksum_, this, error_msg); + return DexFile::Open(dex_file_pointer_, + FileSize(), + dex_file_location_, + dex_file_location_checksum_, + this, + false /* verify */, + error_msg); } uint32_t OatFile::OatDexFile::GetOatClassOffset(uint16_t class_def_index) const { diff --git a/test/048-reflect-v8/expected.txt b/test/048-reflect-v8/expected.txt index 2d0b4ccb6b..3109eccda6 100644 --- a/test/048-reflect-v8/expected.txt +++ b/test/048-reflect-v8/expected.txt @@ -1,4 +1,95 @@ -Main$DefaultInterface is default = yes -Main$RegularInterface is default = no -Main$ImplementsWithDefault is default = yes -Main$ImplementsWithRegular is default = no +============================== +Are These Methods Default: +============================== +IsDefaultTest$DefaultInterface is default = yes +IsDefaultTest$RegularInterface is default = no +IsDefaultTest$ImplementsWithDefault is default = yes +IsDefaultTest$ImplementsWithRegular is default = no +============================== +Class annotations by type: +============================== +Annotations by type, defined by class SingleUser with annotation Calendar: @Calendar(dayOfMonth=unspecified_month, dayOfWeek=single, hour=23) +Annotations by type, defined by class SingleUser with annotation Calendars: <empty> +Annotations by type, defined by class User with annotation Calendar: @Calendar(dayOfMonth=last, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=unspecified_month, dayOfWeek=Fri, hour=23) +Annotations by type, defined by class User with annotation Calendars: @Calendars(value=[@Calendar(dayOfMonth=last, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=unspecified_month, dayOfWeek=Fri, hour=23)]) +Annotations by type, defined by class User2 with annotation Calendar: @Calendar(dayOfMonth=z, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=x, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=y, dayOfWeek=unspecified_week, hour=6) +Annotations by type, defined by class User2 with annotation Calendars: @Calendars(value=[@Calendar(dayOfMonth=z, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=x, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=y, dayOfWeek=unspecified_week, hour=6)]) +Annotations by type, defined by class UserComplex with annotation Calendar: @Calendar(dayOfMonth=afirst, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=zsecond, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=athird, dayOfWeek=unspecified_week, hour=23) +Annotations by type, defined by class UserComplex with annotation Calendars: @Calendars(value=[@Calendar(dayOfMonth=zsecond, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=athird, dayOfWeek=unspecified_week, hour=23)]) +Annotations by type, defined by class UserSub with annotation Calendar: @Calendar(dayOfMonth=last, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=unspecified_month, dayOfWeek=Fri, hour=23) +Annotations by type, defined by class UserSub with annotation Calendars: @Calendars(value=[@Calendar(dayOfMonth=last, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=unspecified_month, dayOfWeek=Fri, hour=23)]) +Annotations by type, defined by class UserSub2 with annotation Calendar: @Calendar(dayOfMonth=sub2, dayOfWeek=unspecified_week, hour=6) +Annotations by type, defined by class UserSub2 with annotation Calendars: @Calendars(value=[@Calendar(dayOfMonth=last, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=unspecified_month, dayOfWeek=Fri, hour=23)]) +----------------------------- +----------------------------- +============================== +Class declared annotation: +============================== +Declared annotations by class class SingleUser, annotation interface Calendar: @Calendar(dayOfMonth=unspecified_month, dayOfWeek=single, hour=23) +Declared annotations by class class SingleUser, annotation interface Calendars: <null> +Declared annotations by class class User, annotation interface Calendar: <null> +Declared annotations by class class User, annotation interface Calendars: @Calendars(value=[@Calendar(dayOfMonth=last, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=unspecified_month, dayOfWeek=Fri, hour=23)]) +Declared annotations by class class UserComplex, annotation interface Calendar: @Calendar(dayOfMonth=afirst, dayOfWeek=unspecified_week, hour=6) +Declared annotations by class class UserComplex, annotation interface Calendars: @Calendars(value=[@Calendar(dayOfMonth=zsecond, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=athird, dayOfWeek=unspecified_week, hour=23)]) +Declared annotations by class class UserSub, annotation interface Calendar: <null> +Declared annotations by class class UserSub, annotation interface Calendars: <null> +Declared annotations by class class UserSub2, annotation interface Calendar: @Calendar(dayOfMonth=sub2, dayOfWeek=unspecified_week, hour=6) +Declared annotations by class class UserSub2, annotation interface Calendars: <null> +----------------------------- +----------------------------- +============================== +Declared class annotations by type: +============================== +Declared annnotations by type, defined by class SingleUser with annotation Calendar: @Calendar(dayOfMonth=unspecified_month, dayOfWeek=single, hour=23) +Declared annnotations by type, defined by class SingleUser with annotation Calendars: <empty> +Declared annnotations by type, defined by class User with annotation Calendar: @Calendar(dayOfMonth=last, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=unspecified_month, dayOfWeek=Fri, hour=23) +Declared annnotations by type, defined by class User with annotation Calendars: @Calendars(value=[@Calendar(dayOfMonth=last, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=unspecified_month, dayOfWeek=Fri, hour=23)]) +Declared annnotations by type, defined by class User2 with annotation Calendar: @Calendar(dayOfMonth=z, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=x, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=y, dayOfWeek=unspecified_week, hour=6) +Declared annnotations by type, defined by class User2 with annotation Calendars: @Calendars(value=[@Calendar(dayOfMonth=z, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=x, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=y, dayOfWeek=unspecified_week, hour=6)]) +Declared annnotations by type, defined by class UserComplex with annotation Calendar: @Calendar(dayOfMonth=afirst, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=zsecond, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=athird, dayOfWeek=unspecified_week, hour=23) +Declared annnotations by type, defined by class UserComplex with annotation Calendars: @Calendars(value=[@Calendar(dayOfMonth=zsecond, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=athird, dayOfWeek=unspecified_week, hour=23)]) +Declared annnotations by type, defined by class UserSub with annotation Calendar: <empty> +Declared annnotations by type, defined by class UserSub with annotation Calendars: <empty> +Declared annnotations by type, defined by class UserSub2 with annotation Calendar: @Calendar(dayOfMonth=sub2, dayOfWeek=unspecified_week, hour=6) +Declared annnotations by type, defined by class UserSub2 with annotation Calendars: <empty> +----------------------------- +----------------------------- +============================== +Method annotations by type: +============================== +Annotations by type, defined by method singleUser with annotation Calendar: @Calendar(dayOfMonth=unspecified_month, dayOfWeek=single, hour=23) +Annotations by type, defined by method singleUser with annotation Calendars: <empty> +Annotations by type, defined by method user with annotation Calendar: @Calendar(dayOfMonth=last, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=unspecified_month, dayOfWeek=Fri, hour=23) +Annotations by type, defined by method user with annotation Calendars: @Calendars(value=[@Calendar(dayOfMonth=last, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=unspecified_month, dayOfWeek=Fri, hour=23)]) +Annotations by type, defined by method user2 with annotation Calendar: @Calendar(dayOfMonth=z, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=x, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=y, dayOfWeek=unspecified_week, hour=6) +Annotations by type, defined by method user2 with annotation Calendars: @Calendars(value=[@Calendar(dayOfMonth=z, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=x, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=y, dayOfWeek=unspecified_week, hour=6)]) +Annotations by type, defined by method userComplex with annotation Calendar: @Calendar(dayOfMonth=afirst, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=zsecond, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=athird, dayOfWeek=unspecified_week, hour=23) +Annotations by type, defined by method userComplex with annotation Calendars: @Calendars(value=[@Calendar(dayOfMonth=zsecond, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=athird, dayOfWeek=unspecified_week, hour=23)]) +----------------------------- +----------------------------- +============================== +Declared method annotations: +============================== +Annotations declared by method singleUser with annotation Calendar: @Calendar(dayOfMonth=unspecified_month, dayOfWeek=single, hour=23) +Annotations declared by method singleUser with annotation Calendars: <null> +Annotations declared by method user with annotation Calendar: <null> +Annotations declared by method user with annotation Calendars: @Calendars(value=[@Calendar(dayOfMonth=last, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=unspecified_month, dayOfWeek=Fri, hour=23)]) +Annotations declared by method user2 with annotation Calendar: <null> +Annotations declared by method user2 with annotation Calendars: @Calendars(value=[@Calendar(dayOfMonth=z, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=x, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=y, dayOfWeek=unspecified_week, hour=6)]) +Annotations declared by method userComplex with annotation Calendar: @Calendar(dayOfMonth=afirst, dayOfWeek=unspecified_week, hour=6) +Annotations declared by method userComplex with annotation Calendars: @Calendars(value=[@Calendar(dayOfMonth=zsecond, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=athird, dayOfWeek=unspecified_week, hour=23)]) +----------------------------- +----------------------------- +============================== +Declared method annotations by type: +============================== +Annotations by type, defined by method singleUser with annotation Calendar: @Calendar(dayOfMonth=unspecified_month, dayOfWeek=single, hour=23) +Annotations by type, defined by method singleUser with annotation Calendars: <empty> +Annotations by type, defined by method user with annotation Calendar: @Calendar(dayOfMonth=last, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=unspecified_month, dayOfWeek=Fri, hour=23) +Annotations by type, defined by method user with annotation Calendars: @Calendars(value=[@Calendar(dayOfMonth=last, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=unspecified_month, dayOfWeek=Fri, hour=23)]) +Annotations by type, defined by method user2 with annotation Calendar: @Calendar(dayOfMonth=z, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=x, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=y, dayOfWeek=unspecified_week, hour=6) +Annotations by type, defined by method user2 with annotation Calendars: @Calendars(value=[@Calendar(dayOfMonth=z, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=x, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=y, dayOfWeek=unspecified_week, hour=6)]) +Annotations by type, defined by method userComplex with annotation Calendar: @Calendar(dayOfMonth=afirst, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=zsecond, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=athird, dayOfWeek=unspecified_week, hour=23) +Annotations by type, defined by method userComplex with annotation Calendars: @Calendars(value=[@Calendar(dayOfMonth=zsecond, dayOfWeek=unspecified_week, hour=6), @Calendar(dayOfMonth=athird, dayOfWeek=unspecified_week, hour=23)]) +----------------------------- +----------------------------- diff --git a/test/048-reflect-v8/src/AnnotationTest.java b/test/048-reflect-v8/src/AnnotationTest.java new file mode 100644 index 0000000000..75e684557c --- /dev/null +++ b/test/048-reflect-v8/src/AnnotationTest.java @@ -0,0 +1,291 @@ +/* + * 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. + */ + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +public class AnnotationTest extends AnnotationTestHelpers { + public static void testAnnotationsByType() { + System.out.println("=============================="); + System.out.println("Class annotations by type:"); + System.out.println("=============================="); + + // Print associated annotations: + // * A is directly present or repeatably present on an element E; + // * No annotation of A is directly/repeatably present on an element + // AND E is a class AND A's type is inheritable, AND A is associated with its superclass. + // (Looks through subtypes recursively only if there's 0 result at each level, + // and the annotation is @Inheritable). + printAnnotationsByType(Calendar.class, SingleUser.class); + printAnnotationsByType(Calendars.class, SingleUser.class); + + printAnnotationsByType(Calendar.class, User.class); + printAnnotationsByType(Calendars.class, User.class); + + printAnnotationsByType(Calendar.class, User2.class); // Enforce ordering 'z,x,y' + printAnnotationsByType(Calendars.class, User2.class); + + // NOTE: + // Order of outer-most annotations Calendars[C,C],S vs C,Calendars[C,C] is unspecified. + // In particular it's the order of #getDeclaredAnnotations which is completely unmentioned. + // The only requirement for #getAnnotationsByType is to have same ordering as + // #getDeclaredAnnotations. + // (Calendars[] itself has to maintain value() order). + printAnnotationsByType(Calendar.class, UserComplex.class); // Cs(C,C),C collapses into C,C,C. + printAnnotationsByType(Calendars.class, UserComplex.class); + + printAnnotationsByType(Calendar.class, UserSub.class); + printAnnotationsByType(Calendars.class, UserSub.class); + + printAnnotationsByType(Calendar.class, UserSub2.class); + // The directly present "Calendar" annotation masks all the repeatably present + // "Calendar" annotations coming from User. + printAnnotationsByType(Calendars.class, UserSub2.class); + // Edge case: UserSub2 doesn't directly have a Calendars annotation, + // so it doesn't mask the "User" Calendars annotation. + + System.out.println("-----------------------------"); + System.out.println("-----------------------------"); + + } + + public static void testDeclaredAnnotation() { + System.out.println("=============================="); + System.out.println("Class declared annotation:"); + System.out.println("=============================="); + + // Print directly present annotations: + // + // The element E has an annotation_item for it (accessible through an + // annotations_directory_item) corresponding to an annotation A, + // and A's type_idx must match that on the encoded_annotation (from the annotation_item). + // (Does not look through the subtypes recursively) + printDeclaredAnnotation(SingleUser.class, Calendar.class); + printDeclaredAnnotation(SingleUser.class, Calendars.class); + + printDeclaredAnnotation(User.class, Calendar.class); + printDeclaredAnnotation(User.class, Calendars.class); + + printDeclaredAnnotation(UserComplex.class, Calendar.class); + printDeclaredAnnotation(UserComplex.class, Calendars.class); + + printDeclaredAnnotation(UserSub.class, Calendar.class); + printDeclaredAnnotation(UserSub.class, Calendars.class); + + printDeclaredAnnotation(UserSub2.class, Calendar.class); + printDeclaredAnnotation(UserSub2.class, Calendars.class); + + System.out.println("-----------------------------"); + System.out.println("-----------------------------"); + } + + public static void testDeclaredAnnotationsByType() { + System.out.println("=============================="); + System.out.println("Declared class annotations by type:"); + System.out.println("=============================="); + + // A is directly present or repeatably present on an element E; + // -- (does not do any recursion for classes regardless of @Inherited) + printDeclaredAnnotationsByType(Calendar.class, SingleUser.class); + printDeclaredAnnotationsByType(Calendars.class, SingleUser.class); + + printDeclaredAnnotationsByType(Calendar.class, User.class); + printDeclaredAnnotationsByType(Calendars.class, User.class); + + printDeclaredAnnotationsByType(Calendar.class, User2.class); // Enforce ordering 'z,x,y' + printDeclaredAnnotationsByType(Calendars.class, User2.class); + + printDeclaredAnnotationsByType(Calendar.class, UserComplex.class); + printDeclaredAnnotationsByType(Calendars.class, UserComplex.class); + + printDeclaredAnnotationsByType(Calendar.class, UserSub.class); + printDeclaredAnnotationsByType(Calendars.class, UserSub.class); + + printDeclaredAnnotationsByType(Calendar.class, UserSub2.class); + // The directly present "Calendar" annotation masks all the repeatably present "Calendar" + // annotations coming from User. + printDeclaredAnnotationsByType(Calendars.class, UserSub2.class); + // Edge case: UserSub2 doesn't directly have a Calendars annotation, + // so it doesn't mask the "User" Calendars annotation. + + System.out.println("-----------------------------"); + System.out.println("-----------------------------"); + } + + // Print the annotation "annotationClass" that is associated with an element denoted by + // "annotationUseClass." + private static <A extends Annotation> void printAnnotationsByType(Class<A> annotationClass, + Class<?> annotationUseClass) { + A[] annotationsByType = annotationUseClass.getAnnotationsByType(annotationClass); + + String msg = "Annotations by type, defined by class " + + annotationUseClass.getName() + " with annotation " + annotationClass.getName() + ": " + + asString(annotationsByType); + + + System.out.println(msg); + } + + private static <A extends Annotation> void printDeclaredAnnotation(Class<?> annotationUseClass, + Class<A> annotationDefClass) { + A anno = annotationUseClass.getDeclaredAnnotation(annotationDefClass); + + String msg = asString(anno); + + System.out.println("Declared annotations by class " + annotationUseClass + + ", annotation " + annotationDefClass + ": " + msg); + } + + // Print the annotation "annotationClass" that is directly/indirectly present with an element + // denoted by "annotationUseClass." + private static <A extends Annotation> void printDeclaredAnnotationsByType( + Class<A> annotationClass, Class<?> annotationUseClass) { + A[] annotationsByType = annotationUseClass.getDeclaredAnnotationsByType(annotationClass); + + String msg = "Declared annnotations by type, defined by class " + annotationUseClass.getName() + + " with annotation " + annotationClass.getName() + ": " + + asString(annotationsByType); + + System.out.println(msg); + } + + public static void testMethodAnnotationsByType() { + System.out.println("=============================="); + System.out.println("Method annotations by type:"); + System.out.println("=============================="); + + // Print associated annotations: + // * A is directly present or repeatably present on an element E; + // * No annotation of A is directly/repeatably present on an element AND E is a class + // AND A's type is inheritable, AND A is associated with its superclass. + // (Looks through subtypes recursively only if there's 0 result at each level, + // and the annotation is @Inheritable). + printMethodAnnotationsByType(Calendar.class, "singleUser", AnnotationTestFixture.class); + printMethodAnnotationsByType(Calendars.class, "singleUser", AnnotationTestFixture.class); + + printMethodAnnotationsByType(Calendar.class, "user", AnnotationTestFixture.class); + printMethodAnnotationsByType(Calendars.class, "user", AnnotationTestFixture.class); + + printMethodAnnotationsByType(Calendar.class, "user2", AnnotationTestFixture.class); + printMethodAnnotationsByType(Calendars.class, "user2", AnnotationTestFixture.class); + + printMethodAnnotationsByType(Calendar.class, "userComplex", AnnotationTestFixture.class); + printMethodAnnotationsByType(Calendars.class, "userComplex", AnnotationTestFixture.class); + + System.out.println("-----------------------------"); + System.out.println("-----------------------------"); + } + + // Print the annotation "annotationClass" that is associated with an element denoted by + // "annotationUseClass" method methodName. + private static <A extends Annotation> void printMethodAnnotationsByType(Class<A> annotationClass, + String methodName, Class<?> annotationUseClass) { + Method m = null; + try { + m = annotationUseClass.getDeclaredMethod(methodName); + } catch (Throwable t) { + throw new AssertionError(t); + } + A[] annotationsByType = m.getAnnotationsByType(annotationClass); + + String msg = "Annotations by type, defined by method " + m.getName() + " with annotation " + + annotationClass.getName() + ": " + + asString(annotationsByType); + + System.out.println(msg); + } + + public static void testMethodDeclaredAnnotations() { + System.out.println("=============================="); + System.out.println("Declared method annotations:"); + System.out.println("=============================="); + + printMethodDeclaredAnnotation(Calendar.class, "singleUser", AnnotationTestFixture.class); + printMethodDeclaredAnnotation(Calendars.class, "singleUser", AnnotationTestFixture.class); + + printMethodDeclaredAnnotation(Calendar.class, "user", AnnotationTestFixture.class); + printMethodDeclaredAnnotation(Calendars.class, "user", AnnotationTestFixture.class); + + printMethodDeclaredAnnotation(Calendar.class, "user2", AnnotationTestFixture.class); + printMethodDeclaredAnnotation(Calendars.class, "user2", AnnotationTestFixture.class); + + printMethodDeclaredAnnotation(Calendar.class, "userComplex", AnnotationTestFixture.class); + printMethodDeclaredAnnotation(Calendars.class, "userComplex", AnnotationTestFixture.class); + + System.out.println("-----------------------------"); + System.out.println("-----------------------------"); + } + + // Print the annotation "annotationClass" that is associated with an element denoted by + // methodName in annotationUseClass. + private static <A extends Annotation> void printMethodDeclaredAnnotation(Class<A> annotationClass, + String methodName, Class<?> annotationUseClass) { + Method m = null; + try { + m = annotationUseClass.getDeclaredMethod(methodName); + } catch (Throwable t) { + throw new AssertionError(t); + } + Annotation annotationsByType = m.getDeclaredAnnotation(annotationClass); + + String msg = "Annotations declared by method " + m.getName() + " with annotation " + + annotationClass.getName() + ": " + + asString(annotationsByType); + + System.out.println(msg); + } + + public static void testMethodDeclaredAnnotationsByType() { + System.out.println("=============================="); + System.out.println("Declared method annotations by type:"); + System.out.println("=============================="); + + printMethodDeclaredAnnotationByType(Calendar.class, "singleUser", AnnotationTestFixture.class); + printMethodDeclaredAnnotationByType(Calendars.class, "singleUser", AnnotationTestFixture.class); + + printMethodDeclaredAnnotationByType(Calendar.class, "user", AnnotationTestFixture.class); + printMethodDeclaredAnnotationByType(Calendars.class, "user", AnnotationTestFixture.class); + + printMethodDeclaredAnnotationByType(Calendar.class, "user2", AnnotationTestFixture.class); + printMethodDeclaredAnnotationByType(Calendars.class, "user2", AnnotationTestFixture.class); + + printMethodDeclaredAnnotationByType(Calendar.class, "userComplex", AnnotationTestFixture.class); + printMethodDeclaredAnnotationByType(Calendars.class, "userComplex", + AnnotationTestFixture.class); + + System.out.println("-----------------------------"); + System.out.println("-----------------------------"); + } + + // Print the annotation "annotationClass" that is associated with an element denoted by + // methodName in annotationUseClass. + private static <A extends Annotation> void printMethodDeclaredAnnotationByType( + Class<A> annotationClass, String methodName, Class<?> annotationUseClass) { + Method m = null; + try { + m = annotationUseClass.getDeclaredMethod(methodName); + } catch (Throwable t) { + throw new AssertionError(t); + } + A[] annotationsByType = m.getDeclaredAnnotationsByType(annotationClass); + + String msg = "Annotations by type, defined by method " + m.getName() + " with annotation " + + annotationClass.getName() + ": " + + asString(annotationsByType); + + System.out.println(msg); + } +} diff --git a/test/048-reflect-v8/src/AnnotationTestFixture.java b/test/048-reflect-v8/src/AnnotationTestFixture.java new file mode 100644 index 0000000000..248dfacf3b --- /dev/null +++ b/test/048-reflect-v8/src/AnnotationTestFixture.java @@ -0,0 +1,48 @@ +/* + * 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. + */ + +public class AnnotationTestFixture { + + @Calendar(dayOfWeek="single", hour=23) + public static void singleUser() { + + } + @Calendars ({ + @Calendar(dayOfMonth="last"), + @Calendar(dayOfWeek="Fri", hour=23) + }) + public static void user() { + + } + + @Calendars ({ + @Calendar(dayOfMonth="z"), + @Calendar(dayOfMonth="x"), + @Calendar(dayOfMonth="y") + }) + public static void user2() { + + } + + @Calendar(dayOfMonth="afirst") + @Calendars ({ + @Calendar(dayOfMonth="zsecond"), + @Calendar(dayOfMonth="athird", hour=23) + }) + public static void userComplex() { + + } +} diff --git a/test/048-reflect-v8/src/AnnotationTestHelpers.java b/test/048-reflect-v8/src/AnnotationTestHelpers.java new file mode 100644 index 0000000000..6b5bea266e --- /dev/null +++ b/test/048-reflect-v8/src/AnnotationTestHelpers.java @@ -0,0 +1,86 @@ +/* + * 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. + */ + +import java.lang.annotation.Annotation; + +public class AnnotationTestHelpers { + // Provide custom print function that print a deterministic output. + // Note that Annotation#toString has unspecified order: it prints out the + // fields, which is why we can't rely on it. + + public static String asString(Annotation anno) { + if (anno instanceof Calendar) { + return asString((Calendar)anno); + } else if (anno instanceof Calendars) { + return asString((Calendars)anno); + } else { + if (anno == null) { + return "<null>"; + } + // Fall-back, usually would only go here in a test failure. + return anno.toString(); + } + } + + public static String asString(Annotation[] annos) { + String msg = ""; + + if (annos == null) { + msg += "<null>"; + } else if (annos.length == 0) { + msg += "<empty>"; + } else { + for (int i = 0; i < annos.length; ++i) { + msg += asString(annos[i]); + + if (i != annos.length - 1) { + msg += ", "; + } + } + } + + return msg; + } + + public static String asString(Calendar calendar) { + if (calendar == null) { + return "<null>"; + } + + return "@Calendar(dayOfMonth=" + calendar.dayOfMonth() + ", dayOfWeek=" + + calendar.dayOfWeek() + ", hour=" + calendar.hour() + ")"; + } + + public static String asString(Calendars calendars) { + if (calendars == null) { + return "<null>"; + } + + String s = "@Calendars(value=["; + + Calendar[] allValues = calendars.value(); + for (int i = 0; i < allValues.length; ++i) { + s += asString(allValues[i]); + if (i != allValues.length - 1) { + s += ", "; + } + } + + s += "])"; + + return s; + } +} diff --git a/test/048-reflect-v8/src/Calendar.java b/test/048-reflect-v8/src/Calendar.java new file mode 100644 index 0000000000..4a16573bca --- /dev/null +++ b/test/048-reflect-v8/src/Calendar.java @@ -0,0 +1,32 @@ +/* + * 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. + */ + +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +// This is a plain old non-1.8 annotation. At runtime we can see that it has a +// "Repeatable" annotation if we query with getDeclaredAnnotation(Repeatable.class) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(Calendars.class) +@Inherited // note: container must also be @Inherited by JLS. +public @interface Calendar { + String dayOfMonth() default "unspecified_month"; + String dayOfWeek() default "unspecified_week"; + int hour() default 6; +} + diff --git a/test/048-reflect-v8/src/Calendars.java b/test/048-reflect-v8/src/Calendars.java new file mode 100644 index 0000000000..caeda52ea6 --- /dev/null +++ b/test/048-reflect-v8/src/Calendars.java @@ -0,0 +1,26 @@ +/* + * 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. + */ + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +// Plain old annotation, there's nothing 1.8 specific about it. +@Retention(RetentionPolicy.RUNTIME) +@Inherited // note: elements must also be @Inherited by JLS. +public @interface Calendars { + Calendar[] value(); +} diff --git a/test/048-reflect-v8/src/IFaceA.java b/test/048-reflect-v8/src/IFaceA.java new file mode 100644 index 0000000000..9b1f610f84 --- /dev/null +++ b/test/048-reflect-v8/src/IFaceA.java @@ -0,0 +1,24 @@ +/* + * 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. + */ + +// Stored as a complex annotation Calendars(Calendar,Calendar) +// in the binary. +@Calendars ({ + @Calendar(dayOfMonth="if_a_first"), + @Calendar(dayOfMonth="if_b_last") +}) +public interface IFaceA { +} diff --git a/test/048-reflect-v8/src/IFaceSimple.java b/test/048-reflect-v8/src/IFaceSimple.java new file mode 100644 index 0000000000..93cf61057e --- /dev/null +++ b/test/048-reflect-v8/src/IFaceSimple.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// Simple annotation, no container. +@Calendar(dayOfMonth="if_simple_first") +public interface IFaceSimple { + +} diff --git a/test/048-reflect-v8/src/IsDefaultTest.java b/test/048-reflect-v8/src/IsDefaultTest.java new file mode 100644 index 0000000000..177dcf1ab9 --- /dev/null +++ b/test/048-reflect-v8/src/IsDefaultTest.java @@ -0,0 +1,60 @@ +/* + * 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. + */ + +import java.lang.reflect.Method; + +public class IsDefaultTest { + interface DefaultInterface { + default void sayHi() { + System.out.println("hi default"); + } + } + + interface RegularInterface { + void sayHi(); + } + + class ImplementsWithDefault implements DefaultInterface {} + class ImplementsWithRegular implements RegularInterface { + public void sayHi() { + System.out.println("hello specific"); + } + } + + private static void printIsDefault(Class<?> klass) { + Method m; + try { + m = klass.getMethod("sayHi"); + } catch (Throwable t) { + System.out.println(t); + return; + } + + boolean isDefault = m.isDefault(); + System.out.println(klass.getName() + " is default = " + (isDefault ? "yes" : "no")); + } + + public static void test() { + System.out.println("=============================="); + System.out.println("Are These Methods Default:"); + System.out.println("=============================="); + + printIsDefault(DefaultInterface.class); + printIsDefault(RegularInterface.class); + printIsDefault(ImplementsWithDefault.class); + printIsDefault(ImplementsWithRegular.class); + } +} diff --git a/test/048-reflect-v8/src/Main.java b/test/048-reflect-v8/src/Main.java index 7fa2a923d7..f2b8287d18 100644 --- a/test/048-reflect-v8/src/Main.java +++ b/test/048-reflect-v8/src/Main.java @@ -14,43 +14,14 @@ * limitations under the License. */ -import java.lang.reflect.Method; - public class Main { - interface DefaultInterface { - default void sayHi() { - System.out.println("hi default"); - } - } - - interface RegularInterface { - void sayHi(); - } - - class ImplementsWithDefault implements DefaultInterface {} - class ImplementsWithRegular implements RegularInterface { - public void sayHi() { - System.out.println("hello specific"); - } - } - - private static void printIsDefault(Class<?> klass) { - Method m; - try { - m = klass.getMethod("sayHi"); - } catch (Throwable t) { - System.out.println(t); - return; - } - - boolean isDefault = m.isDefault(); - System.out.println(klass.getName() + " is default = " + (isDefault ? "yes" : "no")); - } - public static void main(String[] args) { - printIsDefault(DefaultInterface.class); - printIsDefault(RegularInterface.class); - printIsDefault(ImplementsWithDefault.class); - printIsDefault(ImplementsWithRegular.class); + IsDefaultTest.test(); + AnnotationTest.testAnnotationsByType(); + AnnotationTest.testDeclaredAnnotation(); + AnnotationTest.testDeclaredAnnotationsByType(); + AnnotationTest.testMethodAnnotationsByType(); + AnnotationTest.testMethodDeclaredAnnotations(); + AnnotationTest.testMethodDeclaredAnnotationsByType(); } } diff --git a/test/048-reflect-v8/src/SingleUser.java b/test/048-reflect-v8/src/SingleUser.java new file mode 100644 index 0000000000..0f9c430109 --- /dev/null +++ b/test/048-reflect-v8/src/SingleUser.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// Stored as a single "Calendar" annotation in the binary. +@Calendar(dayOfWeek="single", hour=23) +public class SingleUser { + +} diff --git a/test/048-reflect-v8/src/User.java b/test/048-reflect-v8/src/User.java new file mode 100644 index 0000000000..003ceeb093 --- /dev/null +++ b/test/048-reflect-v8/src/User.java @@ -0,0 +1,31 @@ +/* + * 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. + */ + +// Stored as a complex annotation Calendars(Calendar,Calendar) +// in the binary. +// +/* FIXME: Use this code instead, when Jack supports repeatable annotations properly. + * + * @Calendar(dayOfMonth="last") + * @Calendar(dayOfWeek="Fri", hour=23) + */ +@Calendars ({ + @Calendar(dayOfMonth="last"), + @Calendar(dayOfWeek="Fri", hour=23) +}) +public class User { + +} diff --git a/test/048-reflect-v8/src/User2.java b/test/048-reflect-v8/src/User2.java new file mode 100644 index 0000000000..1a6049f508 --- /dev/null +++ b/test/048-reflect-v8/src/User2.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. + */ + +// Stored as a complex annotation Calendars(Calendar,Calendar,Calendar) +// in the binary. +// (Check for order, should be z,x,y) +@Calendars ({ + @Calendar(dayOfMonth="z"), + @Calendar(dayOfMonth="x"), + @Calendar(dayOfMonth="y") +}) +public class User2 { + +} diff --git a/test/048-reflect-v8/src/UserComplex.java b/test/048-reflect-v8/src/UserComplex.java new file mode 100644 index 0000000000..e26234960b --- /dev/null +++ b/test/048-reflect-v8/src/UserComplex.java @@ -0,0 +1,31 @@ +/* + * 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. + */ + +// Stored as a complex annotation Calendars(Calendar,Calendar) +// followed by a Calendar in the binary. +// In other words { Calendars([C,C]), C } +// +// Note that trying to do {C,Calendars,C} or similar +// is illegal by the JLS. +@Calendar(dayOfMonth="afirst") +@Calendars ({ + @Calendar(dayOfMonth="zsecond"), + @Calendar(dayOfMonth="athird", hour=23) +}) +// @Calendar(dayOfMonth="zlast") // Leave for future ordering test +public class UserComplex { + +} diff --git a/test/048-reflect-v8/src/UserSub.java b/test/048-reflect-v8/src/UserSub.java new file mode 100644 index 0000000000..d60aa6a3f1 --- /dev/null +++ b/test/048-reflect-v8/src/UserSub.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +public class UserSub + extends User + implements IFaceA, IFaceSimple { + +} diff --git a/test/048-reflect-v8/src/UserSub2.java b/test/048-reflect-v8/src/UserSub2.java new file mode 100644 index 0000000000..13e2eb07c8 --- /dev/null +++ b/test/048-reflect-v8/src/UserSub2.java @@ -0,0 +1,23 @@ +/* + * 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. + */ + +// This calendar subsumes anything else we would've normally gotten from the subclass. +@Calendar(dayOfMonth="sub2") +public class UserSub2 + extends User + implements IFaceA, IFaceSimple { + +} diff --git a/test/137-cfi/cfi.cc b/test/137-cfi/cfi.cc index 77301d20e8..87656bcf0e 100644 --- a/test/137-cfi/cfi.cc +++ b/test/137-cfi/cfi.cc @@ -101,7 +101,12 @@ static bool IsPicImage() { } #endif -extern "C" JNIEXPORT jboolean JNICALL Java_Main_unwindInProcess(JNIEnv*, jobject, jint, jboolean) { +extern "C" JNIEXPORT jboolean JNICALL Java_Main_unwindInProcess( + JNIEnv*, + jobject, + jboolean full_signatrues, + jint, + jboolean) { #if __linux__ if (IsPicImage()) { LOG(INFO) << "Image is pic, in-process unwinding check bypassed."; @@ -122,14 +127,21 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_unwindInProcess(JNIEnv*, jobject // We cannot really parse an exact stack, as the optimizing compiler may inline some functions. // This is also risky, as deduping might play a trick on us, so the test needs to make sure that // only unique functions are being expected. + // "mini-debug-info" does not include parameters to save space. std::vector<std::string> seq = { "Java_Main_unwindInProcess", // This function. - "boolean Main.unwindInProcess(int, boolean)", // The corresponding Java native method frame. + "Main.unwindInProcess", // The corresponding Java native method frame. + "int java.util.Arrays.binarySearch(java.lang.Object[], int, int, java.lang.Object, java.util.Comparator)", // Framework method. + "Main.main" // The Java entry method. + }; + std::vector<std::string> full_seq = { + "Java_Main_unwindInProcess", // This function. + "boolean Main.unwindInProcess(boolean, int, boolean)", // The corresponding Java native method frame. "int java.util.Arrays.binarySearch(java.lang.Object[], int, int, java.lang.Object, java.util.Comparator)", // Framework method. "void Main.main(java.lang.String[])" // The Java entry method. }; - bool result = CheckStack(bt.get(), seq); + bool result = CheckStack(bt.get(), full_signatrues ? full_seq : seq); if (!kCauseSegfault) { return result ? JNI_TRUE : JNI_FALSE; } else { @@ -178,7 +190,11 @@ int wait_for_sigstop(pid_t tid, int* total_sleep_time_usec, bool* detach_failed } #endif -extern "C" JNIEXPORT jboolean JNICALL Java_Main_unwindOtherProcess(JNIEnv*, jobject, jint pid_int) { +extern "C" JNIEXPORT jboolean JNICALL Java_Main_unwindOtherProcess( + JNIEnv*, + jobject, + jboolean full_signatrues, + jint pid_int) { #if __linux__ // TODO: What to do on Valgrind? pid_t pid = static_cast<pid_t>(pid_int); @@ -214,17 +230,27 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_unwindOtherProcess(JNIEnv*, jobj if (result) { // See comment in unwindInProcess for non-exact stack matching. + // "mini-debug-info" does not include parameters to save space. std::vector<std::string> seq = { // "Java_Main_sleep", // The sleep function being executed in the // other runtime. // Note: For some reason, the name isn't // resolved, so don't look for it right now. + "Main.sleep", // The corresponding Java native method frame. + "int java.util.Arrays.binarySearch(java.lang.Object[], int, int, java.lang.Object, java.util.Comparator)", // Framework method. + "Main.main" // The Java entry method. + }; + std::vector<std::string> full_seq = { + // "Java_Main_sleep", // The sleep function being executed in the + // other runtime. + // Note: For some reason, the name isn't + // resolved, so don't look for it right now. "boolean Main.sleep(int, boolean, double)", // The corresponding Java native method frame. "int java.util.Arrays.binarySearch(java.lang.Object[], int, int, java.lang.Object, java.util.Comparator)", // Framework method. "void Main.main(java.lang.String[])" // The Java entry method. }; - result = CheckStack(bt.get(), seq); + result = CheckStack(bt.get(), full_signatrues ? full_seq : seq); } if (ptrace(PTRACE_DETACH, pid, 0, 0) != 0) { diff --git a/test/137-cfi/expected.txt b/test/137-cfi/expected.txt index 6a5618ebc6..8db7853696 100644 --- a/test/137-cfi/expected.txt +++ b/test/137-cfi/expected.txt @@ -1 +1,2 @@ JNI_OnLoad called +JNI_OnLoad called diff --git a/test/137-cfi/run b/test/137-cfi/run index 9c567b6813..6f4bcfe658 100755 --- a/test/137-cfi/run +++ b/test/137-cfi/run @@ -14,4 +14,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -exec ${RUN} "$@" -Xcompiler-option --generate-debug-info +# Test with full DWARF debugging information. +# Check full signatures of methods. +${RUN} "$@" -Xcompiler-option --generate-debug-info --args --full-signatures + +# Test with minimal compressed debugging information. +# Check only method names (parameters are omitted to save space). +${RUN} "$@" -Xcompiler-option --generate-mini-debug-info diff --git a/test/137-cfi/src/Main.java b/test/137-cfi/src/Main.java index 5474c9b7b6..77553380c8 100644 --- a/test/137-cfi/src/Main.java +++ b/test/137-cfi/src/Main.java @@ -34,19 +34,28 @@ public class Main implements Comparator<Main> { private boolean secondary; + private boolean full_signatures; + private boolean passed; - public Main(boolean secondary) { + public Main(boolean secondary, boolean full_signatures) { this.secondary = secondary; + this.full_signatures = full_signatures; } public static void main(String[] args) throws Exception { System.loadLibrary(args[0]); boolean secondary = false; - if (args.length > 0 && args[args.length - 1].equals("--secondary")) { + boolean full_signatures = false; + for (String arg : args) { + if (arg.equals("--secondary")) { secondary = true; + } + if (arg.equals("--full-signatures")) { + full_signatures = true; + } } - new Main(secondary).run(); + new Main(secondary, full_signatures).run(); } private void run() { @@ -96,7 +105,7 @@ public class Main implements Comparator<Main> { throw new RuntimeException(e); } - if (!unwindOtherProcess(pid)) { + if (!unwindOtherProcess(full_signatures, pid)) { System.out.println("Unwinding other process failed."); } } finally { @@ -154,7 +163,7 @@ public class Main implements Comparator<Main> { if (b) { return sleep(2, b, 1.0); } else { - return unwindInProcess(1, b); + return unwindInProcess(full_signatures, 1, b); } } @@ -162,6 +171,6 @@ public class Main implements Comparator<Main> { public native boolean sleep(int i, boolean b, double dummy); - public native boolean unwindInProcess(int i, boolean b); - public native boolean unwindOtherProcess(int pid); + public native boolean unwindInProcess(boolean full_signatures, int i, boolean b); + public native boolean unwindOtherProcess(boolean full_signatures, int pid); } diff --git a/test/564-checker-bitcount/src/Main.java b/test/564-checker-bitcount/src/Main.java index b250145ef3..2683b2548f 100644 --- a/test/564-checker-bitcount/src/Main.java +++ b/test/564-checker-bitcount/src/Main.java @@ -16,24 +16,20 @@ public class Main { - // TODO: make this work when b/26700769 is done. - // + // TODO: make something like this work when b/26700769 is done. // CHECK-START-X86_64: int Main.bits32(int) disassembly (after) // CHECK-DAG: popcnt - // - // CHECK-START-X86_64: int Main.bits32(int) disassembly (after) - // CHECK-NOT: call + + /// CHECK-START: int Main.bits32(int) intrinsics_recognition (after) + /// CHECK-DAG: <<Result:i\d+>> InvokeStaticOrDirect intrinsic:IntegerBitCount + /// CHECK-DAG: Return [<<Result>>] private static int bits32(int x) { return Integer.bitCount(x); } - // TODO: make this work when b/26700769 is done. - // - // CHECK-START-X86_64: int Main.bits64(long) disassembly (after) - // CHECK-DAG: popcnt - // - // CHECK-START-X86_64: int Main.bits64(long) disassembly (after) - // CHECK-NOT: call + /// CHECK-START: int Main.bits64(long) intrinsics_recognition (after) + /// CHECK-DAG: <<Result:i\d+>> InvokeStaticOrDirect intrinsic:LongBitCount + /// CHECK-DAG: Return [<<Result>>] private static int bits64(long x) { return Long.bitCount(x); } diff --git a/test/565-checker-irreducible-loop/expected.txt b/test/565-checker-irreducible-loop/expected.txt new file mode 100644 index 0000000000..6ed281c757 --- /dev/null +++ b/test/565-checker-irreducible-loop/expected.txt @@ -0,0 +1,2 @@ +1 +1 diff --git a/test/565-checker-irreducible-loop/info.txt b/test/565-checker-irreducible-loop/info.txt new file mode 100644 index 0000000000..1e0dd02284 --- /dev/null +++ b/test/565-checker-irreducible-loop/info.txt @@ -0,0 +1,2 @@ +Regression test for optimizing in the presence of +an irreducible loop. diff --git a/test/565-checker-irreducible-loop/smali/IrreducibleLoop.smali b/test/565-checker-irreducible-loop/smali/IrreducibleLoop.smali new file mode 100644 index 0000000000..29547ca85e --- /dev/null +++ b/test/565-checker-irreducible-loop/smali/IrreducibleLoop.smali @@ -0,0 +1,101 @@ +# 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. + +.class public LIrreducibleLoop; + +.super Ljava/lang/Object; + +# Check that both the irreducible loop and the other loop entry +# move the constant-folded value to where it's expected. + +## CHECK-START-X86: int IrreducibleLoop.test1(int, long) register (after) +## CHECK-DAG: ParallelMove {{.*84->.*}} loop:none +## CHECK-DAG: ParallelMove {{.*84->.*}} loop:{{B\d+}} irreducible:true +.method public static test1(IJ)I + .registers 10 + const/16 v6, 2 + const/16 v4, 1 + const-wide/16 v0, 42 + add-long v2, v0, v0 + + if-eqz p0, :loop_entry + goto :other_loop_pre_entry + + # The then part: beginning of the irreducible loop. + :loop_entry + if-eqz p0, :exit + cmp-long v6, v2, p1 + :other_loop_entry + sub-int p0, p0, v4 + goto :loop_entry + + # The other block branching to the irreducible loop. + # In that block, v4 has no live range. + :other_loop_pre_entry + goto :other_loop_entry + + :exit + return v6 +.end method + +# Check that the compiler does not crash when +# a live interval is found while connecting siblings, but that +# live interval is inactive at the desired position. + +## CHECK-START-X86: int IrreducibleLoop.test2(int, long) register (after) +## CHECK-DAG: ParallelMove {{.*84->.*}} loop:none +## CHECK-DAG: ParallelMove {{.*84->.*}} loop:{{B\d+}} irreducible:true +.method public static test2(IJ)I + .registers 14 + const/16 v6, 2 + const/16 v4, 1 + const-wide/16 v0, 42 + const-wide/16 v8, 68 + add-long v2, v0, v0 + + if-eqz p0, :loop_entry + goto :other_loop_pre_entry + + # The then part: beginning of the irreducible loop. + :loop_entry + if-eqz p0, :exit + cmp-long v6, v2, p1 + :other_loop_entry + sub-int p0, p0, v4 + goto :loop_entry + + # The other block branching to the irreducible loop. + :other_loop_pre_entry + # Make v2 have a register location. + sput-wide v2, LIrreducibleLoop;->myField:J + # Stress register allocator on x86 to split v2. + sput-wide v0, LIrreducibleLoop;->myField:J + sput-wide p1, LIrreducibleLoop;->myField:J + sput-wide v8, LIrreducibleLoop;->myField:J + if-eqz p0, :join + # Stress register allocator on x86 to split v2. + sput-wide p1, LIrreducibleLoop;->myField:J + sput-wide v8, LIrreducibleLoop;->myField:J + sput-wide v0, LIrreducibleLoop;->myField:J + # Last use of v2 before the irreducible loop, that + # will create an interval hole. + sput-wide v2, LIrreducibleLoop;->myField:J + :join + goto :other_loop_entry + + :exit + return v6 +.end method + +.field public static volatile myField:J diff --git a/test/565-checker-irreducible-loop/src/Main.java b/test/565-checker-irreducible-loop/src/Main.java new file mode 100644 index 0000000000..e48bd6b411 --- /dev/null +++ b/test/565-checker-irreducible-loop/src/Main.java @@ -0,0 +1,37 @@ +/* + * 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. + */ + +import java.lang.reflect.Method; + +public class Main { + // Workaround for b/18051191. + class InnerClass {} + + public static void main(String[] args) throws Exception { + Class<?> c = Class.forName("IrreducibleLoop"); + { + Method m = c.getMethod("test1", int.class, long.class); + Object[] arguments = { 42, 31L }; + System.out.println(m.invoke(null, arguments)); + } + + { + Method m = c.getMethod("test2", int.class, long.class); + Object[] arguments = { 42, 31L }; + System.out.println(m.invoke(null, arguments)); + } + } +} diff --git a/test/565-checker-rotate/expected.txt b/test/565-checker-rotate/expected.txt new file mode 100644 index 0000000000..b0aad4deb5 --- /dev/null +++ b/test/565-checker-rotate/expected.txt @@ -0,0 +1 @@ +passed diff --git a/test/565-checker-rotate/info.txt b/test/565-checker-rotate/info.txt new file mode 100644 index 0000000000..c6a8091d6c --- /dev/null +++ b/test/565-checker-rotate/info.txt @@ -0,0 +1 @@ +Unit test for 32-bit and 64-bit rotate operations. diff --git a/test/565-checker-rotate/src/Main.java b/test/565-checker-rotate/src/Main.java new file mode 100644 index 0000000000..33bbe0255a --- /dev/null +++ b/test/565-checker-rotate/src/Main.java @@ -0,0 +1,129 @@ +/* + * 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. + */ + +public class Main { + + /// CHECK-START: int Main.rotateLeft32(int, int) intrinsics_recognition (after) + /// CHECK-DAG: <<Result:i\d+>> InvokeStaticOrDirect intrinsic:IntegerRotateLeft + /// CHECK-DAG: Return [<<Result>>] + private static int rotateLeft32(int x, int y) { + return Integer.rotateLeft(x, y); + } + + /// CHECK-START: long Main.rotateLeft64(long, int) intrinsics_recognition (after) + /// CHECK-DAG: <<Result:j\d+>> InvokeStaticOrDirect intrinsic:LongRotateLeft + /// CHECK-DAG: Return [<<Result>>] + private static long rotateLeft64(long x, int y) { + return Long.rotateLeft(x, y); + } + + /// CHECK-START: int Main.rotateRight32(int, int) intrinsics_recognition (after) + /// CHECK-DAG: <<Result:i\d+>> InvokeStaticOrDirect intrinsic:IntegerRotateRight + /// CHECK-DAG: Return [<<Result>>] + private static int rotateRight32(int x, int y) { + return Integer.rotateRight(x, y); + } + + /// CHECK-START: long Main.rotateRight64(long, int) intrinsics_recognition (after) + /// CHECK-DAG: <<Result:j\d+>> InvokeStaticOrDirect intrinsic:LongRotateRight + /// CHECK-DAG: Return [<<Result>>] + private static long rotateRight64(long x, int y) { + return Long.rotateRight(x, y); + } + + public static void main(String args[]) { + expectEquals32(0x00000001, rotateLeft32(0x00000001, 0)); + expectEquals32(0x00000002, rotateLeft32(0x00000001, 1)); + expectEquals32(0x80000000, rotateLeft32(0x00000001, 31)); + expectEquals32(0x00000001, rotateLeft32(0x00000001, 32)); // overshoot + expectEquals32(0x00000003, rotateLeft32(0x80000001, 1)); + expectEquals32(0x00000006, rotateLeft32(0x80000001, 2)); + expectEquals32(0x23456781, rotateLeft32(0x12345678, 4)); + expectEquals32(0xBCDEF09A, rotateLeft32(0x9ABCDEF0, 8)); + for (int i = 0; i < 40; i++) { // overshoot a bit + int j = i & 31; + expectEquals32(0x00000000, rotateLeft32(0x00000000, i)); + expectEquals32(0xFFFFFFFF, rotateLeft32(0xFFFFFFFF, i)); + expectEquals32(1 << j, rotateLeft32(0x00000001, i)); + expectEquals32((0x12345678 << j) | (0x12345678 >>> -j), + rotateLeft32(0x12345678, i)); + } + + expectEquals64(0x0000000000000001L, rotateLeft64(0x0000000000000001L, 0)); + expectEquals64(0x0000000000000002L, rotateLeft64(0x0000000000000001L, 1)); + expectEquals64(0x8000000000000000L, rotateLeft64(0x0000000000000001L, 63)); + expectEquals64(0x0000000000000001L, rotateLeft64(0x0000000000000001L, 64)); // overshoot + expectEquals64(0x0000000000000003L, rotateLeft64(0x8000000000000001L, 1)); + expectEquals64(0x0000000000000006L, rotateLeft64(0x8000000000000001L, 2)); + expectEquals64(0x23456789ABCDEF01L, rotateLeft64(0x123456789ABCDEF0L, 4)); + expectEquals64(0x3456789ABCDEF012L, rotateLeft64(0x123456789ABCDEF0L, 8)); + for (int i = 0; i < 70; i++) { // overshoot a bit + int j = i & 63; + expectEquals64(0x0000000000000000L, rotateLeft64(0x0000000000000000L, i)); + expectEquals64(0xFFFFFFFFFFFFFFFFL, rotateLeft64(0xFFFFFFFFFFFFFFFFL, i)); + expectEquals64(1L << j, rotateLeft64(0x0000000000000001, i)); + expectEquals64((0x123456789ABCDEF0L << j) | (0x123456789ABCDEF0L >>> -j), + rotateLeft64(0x123456789ABCDEF0L, i)); + } + + expectEquals32(0x80000000, rotateRight32(0x80000000, 0)); + expectEquals32(0x40000000, rotateRight32(0x80000000, 1)); + expectEquals32(0x00000001, rotateRight32(0x80000000, 31)); + expectEquals32(0x80000000, rotateRight32(0x80000000, 32)); // overshoot + expectEquals32(0xC0000000, rotateRight32(0x80000001, 1)); + expectEquals32(0x60000000, rotateRight32(0x80000001, 2)); + expectEquals32(0x81234567, rotateRight32(0x12345678, 4)); + expectEquals32(0xF09ABCDE, rotateRight32(0x9ABCDEF0, 8)); + for (int i = 0; i < 40; i++) { // overshoot a bit + int j = i & 31; + expectEquals32(0x00000000, rotateRight32(0x00000000, i)); + expectEquals32(0xFFFFFFFF, rotateRight32(0xFFFFFFFF, i)); + expectEquals32(0x80000000 >>> j, rotateRight32(0x80000000, i)); + expectEquals32((0x12345678 >>> j) | (0x12345678 << -j), + rotateRight32(0x12345678, i)); + } + + expectEquals64(0x8000000000000000L, rotateRight64(0x8000000000000000L, 0)); + expectEquals64(0x4000000000000000L, rotateRight64(0x8000000000000000L, 1)); + expectEquals64(0x0000000000000001L, rotateRight64(0x8000000000000000L, 63)); + expectEquals64(0x8000000000000000L, rotateRight64(0x8000000000000000L, 64)); // overshoot + expectEquals64(0xC000000000000000L, rotateRight64(0x8000000000000001L, 1)); + expectEquals64(0x6000000000000000L, rotateRight64(0x8000000000000001L, 2)); + expectEquals64(0x0123456789ABCDEFL, rotateRight64(0x123456789ABCDEF0L, 4)); + expectEquals64(0xF0123456789ABCDEL, rotateRight64(0x123456789ABCDEF0L, 8)); + for (int i = 0; i < 70; i++) { // overshoot a bit + int j = i & 63; + expectEquals64(0x0000000000000000L, rotateRight64(0x0000000000000000L, i)); + expectEquals64(0xFFFFFFFFFFFFFFFFL, rotateRight64(0xFFFFFFFFFFFFFFFFL, i)); + expectEquals64(0x8000000000000000L >>> j, rotateRight64(0x8000000000000000L, i)); + expectEquals64((0x123456789ABCDEF0L >>> j) | (0x123456789ABCDEF0L << -j), + rotateRight64(0x123456789ABCDEF0L, i)); + } + + System.out.println("passed"); + } + + private static void expectEquals32(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + private static void expectEquals64(long expected, long result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } +} diff --git a/test/566-checker-signum/expected.txt b/test/566-checker-signum/expected.txt new file mode 100644 index 0000000000..b0aad4deb5 --- /dev/null +++ b/test/566-checker-signum/expected.txt @@ -0,0 +1 @@ +passed diff --git a/test/566-checker-signum/info.txt b/test/566-checker-signum/info.txt new file mode 100644 index 0000000000..328e494291 --- /dev/null +++ b/test/566-checker-signum/info.txt @@ -0,0 +1 @@ +Unit test for 32-bit and 64-bit signum operations. diff --git a/test/566-checker-signum/src/Main.java b/test/566-checker-signum/src/Main.java new file mode 100644 index 0000000000..cc4a98469c --- /dev/null +++ b/test/566-checker-signum/src/Main.java @@ -0,0 +1,72 @@ +/* + * 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. + */ + +public class Main { + + /// CHECK-START: int Main.sign32(int) intrinsics_recognition (after) + /// CHECK-DAG: <<Result:i\d+>> InvokeStaticOrDirect intrinsic:IntegerSignum + /// CHECK-DAG: Return [<<Result>>] + private static int sign32(int x) { + return Integer.signum(x); + } + + /// CHECK-START: int Main.sign64(long) intrinsics_recognition (after) + /// CHECK-DAG: <<Result:i\d+>> InvokeStaticOrDirect intrinsic:LongSignum + /// CHECK-DAG: Return [<<Result>>] + private static int sign64(long x) { + return Long.signum(x); + } + + public static void main(String args[]) { + expectEquals(-1, sign32(Integer.MIN_VALUE)); + expectEquals(-1, sign32(-12345)); + expectEquals(-1, sign32(-1)); + expectEquals(0, sign32(0)); + expectEquals(1, sign32(1)); + expectEquals(1, sign32(12345)); + expectEquals(1, sign32(Integer.MAX_VALUE)); + + for (int i = -11; i <= 11; i++) { + int expected = 0; + if (i < 0) expected = -1; + else if (i > 0) expected = 1; + expectEquals(expected, sign32(i)); + } + + expectEquals(-1, sign64(Long.MIN_VALUE)); + expectEquals(-1, sign64(-12345L)); + expectEquals(-1, sign64(-1L)); + expectEquals(0, sign64(0L)); + expectEquals(1, sign64(1L)); + expectEquals(1, sign64(12345L)); + expectEquals(1, sign64(Long.MAX_VALUE)); + + for (long i = -11L; i <= 11L; i++) { + int expected = 0; + if (i < 0) expected = -1; + else if (i > 0) expected = 1; + expectEquals(expected, sign64(i)); + } + + System.out.println("passed"); + } + + private static void expectEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } +} diff --git a/test/567-checker-compare/expected.txt b/test/567-checker-compare/expected.txt new file mode 100644 index 0000000000..b0aad4deb5 --- /dev/null +++ b/test/567-checker-compare/expected.txt @@ -0,0 +1 @@ +passed diff --git a/test/567-checker-compare/info.txt b/test/567-checker-compare/info.txt new file mode 100644 index 0000000000..5bac7b1165 --- /dev/null +++ b/test/567-checker-compare/info.txt @@ -0,0 +1 @@ +Unit test for 32-bit and 64-bit compare operations. diff --git a/test/567-checker-compare/src/Main.java b/test/567-checker-compare/src/Main.java new file mode 100644 index 0000000000..52abb75e89 --- /dev/null +++ b/test/567-checker-compare/src/Main.java @@ -0,0 +1,108 @@ +/* + * 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. + */ + +public class Main { + + /// CHECK-START: int Main.compare32(int, int) intrinsics_recognition (after) + /// CHECK-DAG: <<Result:i\d+>> InvokeStaticOrDirect intrinsic:IntegerCompare + /// CHECK-DAG: Return [<<Result>>] + private static int compare32(int x, int y) { + return Integer.compare(x, y); + } + + /// CHECK-START: int Main.compare64(long, long) intrinsics_recognition (after) + /// CHECK-DAG: <<Result:i\d+>> InvokeStaticOrDirect intrinsic:LongCompare + /// CHECK-DAG: Return [<<Result>>] + private static int compare64(long x, long y) { + return Long.compare(x, y); + } + + public static void main(String args[]) { + expectEquals(-1, compare32(Integer.MIN_VALUE, Integer.MIN_VALUE + 1)); + expectEquals(-1, compare32(Integer.MIN_VALUE, -1)); + expectEquals(-1, compare32(Integer.MIN_VALUE, 0)); + expectEquals(-1, compare32(Integer.MIN_VALUE, 1)); + expectEquals(-1, compare32(Integer.MIN_VALUE, Integer.MAX_VALUE)); + expectEquals(-1, compare32(-1, 0)); + expectEquals(-1, compare32(-1, 1)); + expectEquals(-1, compare32(0, 1)); + + expectEquals(0, compare32(Integer.MIN_VALUE, Integer.MIN_VALUE)); + expectEquals(0, compare32(-1, -1)); + expectEquals(0, compare32(0, 0)); + expectEquals(0, compare32(1, 1)); + expectEquals(0, compare32(Integer.MAX_VALUE, Integer.MAX_VALUE)); + + expectEquals(1, compare32(0, -1)); + expectEquals(1, compare32(1, -1)); + expectEquals(1, compare32(1, 0)); + expectEquals(1, compare32(Integer.MAX_VALUE, Integer.MIN_VALUE)); + expectEquals(1, compare32(Integer.MAX_VALUE, -1)); + expectEquals(1, compare32(Integer.MAX_VALUE, 0)); + expectEquals(1, compare32(Integer.MAX_VALUE, 1)); + expectEquals(1, compare32(Integer.MAX_VALUE, Integer.MAX_VALUE - 1)); + + for (int i = -11; i <= 11; i++) { + for (int j = -11; j <= 11; j++) { + int expected = 0; + if (i < j) expected = -1; + else if (i > j) expected = 1; + expectEquals(expected, compare32(i, j)); + } + } + + expectEquals(-1, compare64(Long.MIN_VALUE, Long.MIN_VALUE + 1L)); + expectEquals(-1, compare64(Long.MIN_VALUE, -1L)); + expectEquals(-1, compare64(Long.MIN_VALUE, 0L)); + expectEquals(-1, compare64(Long.MIN_VALUE, 1L)); + expectEquals(-1, compare64(Long.MIN_VALUE, Long.MAX_VALUE)); + expectEquals(-1, compare64(-1L, 0L)); + expectEquals(-1, compare64(-1L, 1L)); + expectEquals(-1, compare64(0L, 1L)); + + expectEquals(0, compare64(Long.MIN_VALUE, Long.MIN_VALUE)); + expectEquals(0, compare64(-1L, -1L)); + expectEquals(0, compare64(0L, 0L)); + expectEquals(0, compare64(1L, 1L)); + expectEquals(0, compare64(Long.MAX_VALUE, Long.MAX_VALUE)); + + expectEquals(1, compare64(0L, -1L)); + expectEquals(1, compare64(1L, -1L)); + expectEquals(1, compare64(1L, 0L)); + expectEquals(1, compare64(Long.MAX_VALUE, Long.MIN_VALUE)); + expectEquals(1, compare64(Long.MAX_VALUE, -1L)); + expectEquals(1, compare64(Long.MAX_VALUE, 0L)); + expectEquals(1, compare64(Long.MAX_VALUE, 1L)); + expectEquals(1, compare64(Long.MAX_VALUE, Long.MAX_VALUE - 1L)); + + for (long i = -11L; i <= 11L; i++) { + for (long j = -11L; j <= 11L; j++) { + int expected = 0; + if (i < j) expected = -1; + else if (i > j) expected = 1; + expectEquals(expected, compare64(i, j)); + } + } + + System.out.println("passed"); + } + + private static void expectEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } +} diff --git a/test/568-checker-onebit/expected.txt b/test/568-checker-onebit/expected.txt new file mode 100644 index 0000000000..b0aad4deb5 --- /dev/null +++ b/test/568-checker-onebit/expected.txt @@ -0,0 +1 @@ +passed diff --git a/test/568-checker-onebit/info.txt b/test/568-checker-onebit/info.txt new file mode 100644 index 0000000000..c2b5bf8688 --- /dev/null +++ b/test/568-checker-onebit/info.txt @@ -0,0 +1 @@ +Unit test for 32-bit and 64-bit high/low-bit operations. diff --git a/test/568-checker-onebit/src/Main.java b/test/568-checker-onebit/src/Main.java new file mode 100644 index 0000000000..7007c6a507 --- /dev/null +++ b/test/568-checker-onebit/src/Main.java @@ -0,0 +1,95 @@ +/* + * 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. + */ + +public class Main { + + /// CHECK-START: int Main.hi32(int) intrinsics_recognition (after) + /// CHECK-DAG: <<Result:i\d+>> InvokeStaticOrDirect intrinsic:IntegerHighestOneBit + /// CHECK-DAG: Return [<<Result>>] + private static int hi32(int x) { + return Integer.highestOneBit(x); + } + + /// CHECK-START: int Main.lo32(int) intrinsics_recognition (after) + /// CHECK-DAG: <<Result:i\d+>> InvokeStaticOrDirect intrinsic:IntegerLowestOneBit + /// CHECK-DAG: Return [<<Result>>] + private static int lo32(int x) { + return Integer.lowestOneBit(x); + } + + /// CHECK-START: long Main.hi64(long) intrinsics_recognition (after) + /// CHECK-DAG: <<Result:j\d+>> InvokeStaticOrDirect intrinsic:LongHighestOneBit + /// CHECK-DAG: Return [<<Result>>] + private static long hi64(long x) { + return Long.highestOneBit(x); + } + + /// CHECK-START: long Main.lo64(long) intrinsics_recognition (after) + /// CHECK-DAG: <<Result:j\d+>> InvokeStaticOrDirect intrinsic:LongLowestOneBit + /// CHECK-DAG: Return [<<Result>>] + private static long lo64(long x) { + return Long.lowestOneBit(x); + } + + public static void main(String args[]) { + expectEquals32(0x00000000, hi32(0x00000000)); + expectEquals32(0x00000000, lo32(0x00000000)); + expectEquals32(0x00010000, hi32(0x00010000)); + expectEquals32(0x00010000, lo32(0x00010000)); + expectEquals32(0x00800000, hi32(0x00FF0000)); + expectEquals32(0x00010000, lo32(0x00FF0000)); + expectEquals32(0x80000000, hi32(0xFFFFFFFF)); + expectEquals32(0x00000001, lo32(0xFFFFFFFF)); + + for (int i = 0; i < 32; i++) { + expectEquals32(1 << i, hi32(1 << i)); + expectEquals32(1 << i, lo32(1 << i)); + int expected = i < 29 ? 0x8 << i : 0x80000000; + expectEquals32(expected, hi32(0xF << i)); + expectEquals32(0x1 << i, lo32(0xF << i)); + } + + expectEquals64(0x0000000000000000L, hi64(0x0000000000000000L)); + expectEquals64(0x0000000000000000L, lo64(0x0000000000000000L)); + expectEquals64(0x0000000100000000L, hi64(0x0000000100000000L)); + expectEquals64(0x0000000100000000L, lo64(0x0000000100000000L)); + expectEquals64(0x0000008000000000L, hi64(0x000000FF00000000L)); + expectEquals64(0x0000000100000000L, lo64(0x000000FF00000000L)); + expectEquals64(0x8000000000000000L, hi64(0xFFFFFFFFFFFFFFFFL)); + expectEquals64(0x0000000000000001L, lo64(0xFFFFFFFFFFFFFFFFL)); + + for (int i = 0; i < 64; i++) { + expectEquals64(1L << i, hi64(1L << i)); + expectEquals64(1L << i, lo64(1L << i)); + long expected = i < 61 ? 0x8L << i : 0x8000000000000000L; + expectEquals64(expected, hi64(0xFL << i)); + expectEquals64(0x1L << i, lo64(0xFL << i)); + } + + System.out.println("passed"); + } + + private static void expectEquals32(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + private static void expectEquals64(long expected, long result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } +} diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index e004b6cff4..8245ccfde5 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -74,6 +74,14 @@ while true; do fi ARGS="${ARGS} $1" shift + elif [ "x$1" = "x--args" ]; then + shift + if [ "x$1" = "x" ]; then + echo "$0 missing argument to --args" 1>&2 + exit 1 + fi + ARGS="${ARGS} $1" + shift elif [ "x$1" = "x-Xcompiler-option" ]; then shift option="$1" @@ -533,6 +541,7 @@ else cd $ANDROID_BUILD_TOP + rm -rf ${DEX_LOCATION}/dalvik-cache/ $mkdir_cmdline || exit 1 $dex2oat_cmdline || { echo "Dex2oat failed." >&2 ; exit 2; } diff --git a/tools/ahat/README.txt b/tools/ahat/README.txt index a3ecf86ab7..da5225c066 100644 --- a/tools/ahat/README.txt +++ b/tools/ahat/README.txt @@ -77,6 +77,9 @@ Things to move to perflib: * Instance.isRoot and Instance.getRootTypes. Release History: + 0.4 Pending + Show registered native allocations for heap dumps that support it. + 0.3 Dec 15, 2015 Fix page loading performance by showing a limited number of entries by default. Fix mismatch between overview and "roots" totals. diff --git a/tools/ahat/src/AhatSnapshot.java b/tools/ahat/src/AhatSnapshot.java index fc7911b71b..2adec6f17b 100644 --- a/tools/ahat/src/AhatSnapshot.java +++ b/tools/ahat/src/AhatSnapshot.java @@ -43,22 +43,27 @@ import java.util.Map; * ahat. */ class AhatSnapshot { - private Snapshot mSnapshot; - private List<Heap> mHeaps; + private final Snapshot mSnapshot; + private final List<Heap> mHeaps; // Map from Instance to the list of Instances it immediately dominates. - private Map<Instance, List<Instance>> mDominated; + private final Map<Instance, List<Instance>> mDominated + = new HashMap<Instance, List<Instance>>(); // Collection of objects whose immediate dominator is the SENTINEL_ROOT. - private List<Instance> mRooted; + private final List<Instance> mRooted = new ArrayList<Instance>(); // Map from roots to their types. // Instances are only included if they are roots, and the collection of root // types is guaranteed to be non-empty. - private Map<Instance, Collection<RootType>> mRoots; + private final Map<Instance, Collection<RootType>> mRoots + = new HashMap<Instance, Collection<RootType>>(); - private Site mRootSite; - private Map<Heap, Long> mHeapSizes; + private final Site mRootSite = new Site("ROOT"); + private final Map<Heap, Long> mHeapSizes = new HashMap<Heap, Long>(); + + private final List<InstanceUtils.NativeAllocation> mNativeAllocations + = new ArrayList<InstanceUtils.NativeAllocation>(); /** * Create an AhatSnapshot from an hprof file. @@ -77,10 +82,6 @@ class AhatSnapshot { private AhatSnapshot(Snapshot snapshot) { mSnapshot = snapshot; mHeaps = new ArrayList<Heap>(mSnapshot.getHeaps()); - mDominated = new HashMap<Instance, List<Instance>>(); - mRootSite = new Site("ROOT"); - mHeapSizes = new HashMap<Heap, Long>(); - mRooted = new ArrayList<Instance>(); ClassObj javaLangClass = mSnapshot.findClass("java.lang.Class"); for (Heap heap : mHeaps) { @@ -118,13 +119,18 @@ class AhatSnapshot { } } mRootSite.add(stackId, 0, path.iterator(), inst); + + // Update native allocations. + InstanceUtils.NativeAllocation alloc = InstanceUtils.getNativeAllocation(inst); + if (alloc != null) { + mNativeAllocations.add(alloc); + } } } mHeapSizes.put(heap, total); } // Record the roots and their types. - mRoots = new HashMap<Instance, Collection<RootType>>(); for (RootObj root : snapshot.getGCRoots()) { Instance inst = root.getReferredInstance(); Collection<RootType> types = mRoots.get(inst); @@ -259,4 +265,9 @@ class AhatSnapshot { } return site; } + + // Return a list of known native allocations in the snapshot. + public List<InstanceUtils.NativeAllocation> getNativeAllocations() { + return mNativeAllocations; + } } diff --git a/tools/ahat/src/InstanceUtils.java b/tools/ahat/src/InstanceUtils.java index 7fa53c78b5..8b7f9ea41a 100644 --- a/tools/ahat/src/InstanceUtils.java +++ b/tools/ahat/src/InstanceUtils.java @@ -20,6 +20,7 @@ import com.android.tools.perflib.heap.ArrayInstance; import com.android.tools.perflib.heap.ClassInstance; import com.android.tools.perflib.heap.ClassObj; import com.android.tools.perflib.heap.Instance; +import com.android.tools.perflib.heap.Heap; import com.android.tools.perflib.heap.Type; import java.awt.image.BufferedImage; @@ -31,7 +32,7 @@ class InstanceUtils { * Returns true if the given instance is an instance of a class with the * given name. */ - public static boolean isInstanceOfClass(Instance inst, String className) { + private static boolean isInstanceOfClass(Instance inst, String className) { ClassObj cls = (inst == null) ? null : inst.getClassObj(); return (cls != null && className.equals(cls.getClassName())); } @@ -118,12 +119,12 @@ class InstanceUtils { return null; } - Integer width = getIntField(inst, "mWidth"); + Integer width = getIntField(inst, "mWidth", null); if (width == null) { return null; } - Integer height = getIntField(inst, "mHeight"); + Integer height = getIntField(inst, "mHeight", null); if (height == null) { return null; } @@ -186,23 +187,29 @@ class InstanceUtils { /** * Read an int field of an instance. * The field is assumed to be an int type. - * Returns null if the field value is not an int or could not be read. + * Returns <code>def</code> if the field value is not an int or could not be + * read. */ - private static Integer getIntField(Instance inst, String fieldName) { + private static Integer getIntField(Instance inst, String fieldName, Integer def) { Object value = getField(inst, fieldName); if (!(value instanceof Integer)) { - return null; + return def; } return (Integer)value; } /** - * Read an int field of an instance, returning a default value if the field - * was not an int or could not be read. + * Read a long field of an instance. + * The field is assumed to be a long type. + * Returns <code>def</code> if the field value is not an long or could not + * be read. */ - private static int getIntField(Instance inst, String fieldName, int def) { - Integer value = getIntField(inst, fieldName); - return value == null ? def : value; + private static Long getLongField(Instance inst, String fieldName, Long def) { + Object value = getField(inst, fieldName); + if (!(value instanceof Long)) { + return def; + } + return (Long)value; } /** @@ -278,4 +285,73 @@ class InstanceUtils { } return null; } + + public static class NativeAllocation { + public long size; + public Heap heap; + public long pointer; + public Instance referent; + + public NativeAllocation(long size, Heap heap, long pointer, Instance referent) { + this.size = size; + this.heap = heap; + this.pointer = pointer; + this.referent = referent; + } + } + + /** + * Assuming inst represents a NativeAllocation, return information about the + * native allocation. Returns null if the given instance doesn't represent a + * native allocation. + */ + public static NativeAllocation getNativeAllocation(Instance inst) { + if (!isInstanceOfClass(inst, "libcore.util.NativeAllocationRegistry$CleanerThunk")) { + return null; + } + + Long pointer = InstanceUtils.getLongField(inst, "nativePtr", null); + if (pointer == null) { + return null; + } + + // Search for the registry field of inst. + // Note: We know inst as an instance of ClassInstance because we already + // read the nativePtr field from it. + Instance registry = null; + for (ClassInstance.FieldValue field : ((ClassInstance)inst).getValues()) { + Object fieldValue = field.getValue(); + if (fieldValue instanceof Instance) { + Instance fieldInst = (Instance)fieldValue; + if (isInstanceOfClass(fieldInst, "libcore.util.NativeAllocationRegistry")) { + registry = fieldInst; + break; + } + } + } + + if (registry == null) { + return null; + } + + Long size = InstanceUtils.getLongField(registry, "size", null); + if (size == null) { + return null; + } + + Instance referent = null; + for (Instance ref : inst.getHardReferences()) { + if (isInstanceOfClass(ref, "sun.misc.Cleaner")) { + referent = InstanceUtils.getReferent(ref); + if (referent != null) { + break; + } + } + } + + if (referent == null) { + return null; + } + return new NativeAllocation(size, inst.getHeap(), pointer, referent); + } } diff --git a/tools/ahat/src/Main.java b/tools/ahat/src/Main.java index 091820f7fc..d7845996b7 100644 --- a/tools/ahat/src/Main.java +++ b/tools/ahat/src/Main.java @@ -78,6 +78,7 @@ public class Main { server.createContext("/object", new AhatHttpHandler(new ObjectHandler(ahat))); server.createContext("/objects", new AhatHttpHandler(new ObjectsHandler(ahat))); server.createContext("/site", new AhatHttpHandler(new SiteHandler(ahat))); + server.createContext("/native", new AhatHttpHandler(new NativeAllocationsHandler(ahat))); server.createContext("/bitmap", new BitmapHandler(ahat)); server.createContext("/help", new HelpHandler()); server.createContext("/style.css", new StaticHandler("style.css", "text/css")); diff --git a/tools/ahat/src/Menu.java b/tools/ahat/src/Menu.java index 018e019503..232b849c28 100644 --- a/tools/ahat/src/Menu.java +++ b/tools/ahat/src/Menu.java @@ -27,6 +27,8 @@ class Menu { .append(" - ") .appendLink(DocString.uri("sites"), DocString.text("allocations")) .append(" - ") + .appendLink(DocString.uri("native"), DocString.text("native")) + .append(" - ") .appendLink(DocString.uri("help"), DocString.text("help")); /** diff --git a/tools/ahat/src/NativeAllocationsHandler.java b/tools/ahat/src/NativeAllocationsHandler.java new file mode 100644 index 0000000000..17407e1ae1 --- /dev/null +++ b/tools/ahat/src/NativeAllocationsHandler.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2015 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 java.io.IOException; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +class NativeAllocationsHandler implements AhatHandler { + private static final String ALLOCATIONS_ID = "allocations"; + + private AhatSnapshot mSnapshot; + + public NativeAllocationsHandler(AhatSnapshot snapshot) { + mSnapshot = snapshot; + } + + @Override + public void handle(Doc doc, Query query) throws IOException { + List<InstanceUtils.NativeAllocation> allocs = mSnapshot.getNativeAllocations(); + + doc.title("Registered Native Allocations"); + + doc.section("Overview"); + long totalSize = 0; + for (InstanceUtils.NativeAllocation alloc : allocs) { + totalSize += alloc.size; + } + doc.descriptions(); + doc.description(DocString.text("Number of Registered Native Allocations"), + DocString.format("%,14d", allocs.size())); + doc.description(DocString.text("Total Size of Registered Native Allocations"), + DocString.format("%,14d", totalSize)); + doc.end(); + + doc.section("List of Allocations"); + if (allocs.isEmpty()) { + doc.println(DocString.text("(none)")); + } else { + doc.table( + new Column("Size", Column.Align.RIGHT), + new Column("Heap"), + new Column("Native Pointer"), + new Column("Referent")); + Comparator<InstanceUtils.NativeAllocation> compare + = new Sort.WithPriority<InstanceUtils.NativeAllocation>( + new Sort.NativeAllocationByHeapName(), + new Sort.NativeAllocationBySize()); + Collections.sort(allocs, compare); + SubsetSelector<InstanceUtils.NativeAllocation> selector + = new SubsetSelector(query, ALLOCATIONS_ID, allocs); + for (InstanceUtils.NativeAllocation alloc : selector.selected()) { + doc.row( + DocString.format("%,14d", alloc.size), + DocString.text(alloc.heap.getName()), + DocString.format("0x%x", alloc.pointer), + Value.render(mSnapshot, alloc.referent)); + } + + // Print a summary of the remaining entries if there are any. + List<InstanceUtils.NativeAllocation> remaining = selector.remaining(); + if (!remaining.isEmpty()) { + long total = 0; + for (InstanceUtils.NativeAllocation alloc : remaining) { + total += alloc.size; + } + + doc.row( + DocString.format("%,14d", total), + DocString.text("..."), + DocString.text("..."), + DocString.text("...")); + } + + doc.end(); + selector.render(doc); + } + } +} + diff --git a/tools/ahat/src/OverviewHandler.java b/tools/ahat/src/OverviewHandler.java index 720fcb42ff..0dbad7e00c 100644 --- a/tools/ahat/src/OverviewHandler.java +++ b/tools/ahat/src/OverviewHandler.java @@ -48,6 +48,22 @@ class OverviewHandler implements AhatHandler { doc.section("Heap Sizes"); printHeapSizes(doc, query); + + List<InstanceUtils.NativeAllocation> allocs = mSnapshot.getNativeAllocations(); + if (!allocs.isEmpty()) { + doc.section("Registered Native Allocations"); + long totalSize = 0; + for (InstanceUtils.NativeAllocation alloc : allocs) { + totalSize += alloc.size; + } + doc.descriptions(); + doc.description(DocString.text("Number of Registered Native Allocations"), + DocString.format("%,14d", allocs.size())); + doc.description(DocString.text("Total Size of Registered Native Allocations"), + DocString.format("%,14d", totalSize)); + doc.end(); + } + doc.big(Menu.getMenu()); } diff --git a/tools/ahat/src/Sort.java b/tools/ahat/src/Sort.java index 3b7916661d..c5f89c315a 100644 --- a/tools/ahat/src/Sort.java +++ b/tools/ahat/src/Sort.java @@ -177,5 +177,31 @@ class Sort { return aName.compareTo(bName); } } + + /** + * Compare AhatSnapshot.NativeAllocation by heap name. + * Different allocations with the same heap name are considered equal for + * the purposes of comparison. + */ + public static class NativeAllocationByHeapName + implements Comparator<InstanceUtils.NativeAllocation> { + @Override + public int compare(InstanceUtils.NativeAllocation a, InstanceUtils.NativeAllocation b) { + return a.heap.getName().compareTo(b.heap.getName()); + } + } + + /** + * Compare InstanceUtils.NativeAllocation by their size. + * Different allocations with the same size are considered equal for the + * purposes of comparison. + * This sorts allocations from larger size to smaller size. + */ + public static class NativeAllocationBySize implements Comparator<InstanceUtils.NativeAllocation> { + @Override + public int compare(InstanceUtils.NativeAllocation a, InstanceUtils.NativeAllocation b) { + return Long.compare(b.size, a.size); + } + } } diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/test-dump/Main.java index 90cd7af2cc..701d60ef1f 100644 --- a/tools/ahat/test-dump/Main.java +++ b/tools/ahat/test-dump/Main.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; +import libcore.util.NativeAllocationRegistry; /** * Program used to create a heap dump for test purposes. @@ -47,6 +48,9 @@ public class Main { for (int i = 0; i < N; i++) { bigArray[i] = (byte)((i*i) & 0xFF); } + + NativeAllocationRegistry registry = new NativeAllocationRegistry(0x12345, 42); + registry.registerNativeAllocation(anObject, 0xABCDABCD); } } diff --git a/tools/ahat/test/NativeAllocationTest.java b/tools/ahat/test/NativeAllocationTest.java new file mode 100644 index 0000000000..7ad4c1d431 --- /dev/null +++ b/tools/ahat/test/NativeAllocationTest.java @@ -0,0 +1,44 @@ +/* + * 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; + +import com.android.tools.perflib.heap.Instance; +import java.io.IOException; +import static org.junit.Assert.fail; +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +public class NativeAllocationTest { + + @Test + public void nativeAllocation() throws IOException { + TestDump dump = TestDump.getTestDump(); + + AhatSnapshot snapshot = dump.getAhatSnapshot(); + Instance referent = (Instance)dump.getDumpedThing("anObject"); + for (InstanceUtils.NativeAllocation alloc : snapshot.getNativeAllocations()) { + if (alloc.referent == referent) { + assertEquals(42 , alloc.size); + assertEquals(referent.getHeap(), alloc.heap); + assertEquals(0xABCDABCD , alloc.pointer); + return; + } + } + fail("No native allocation found with anObject as the referent"); + } +} + diff --git a/tools/ahat/test/Tests.java b/tools/ahat/test/Tests.java index e8894e2603..3291470df9 100644 --- a/tools/ahat/test/Tests.java +++ b/tools/ahat/test/Tests.java @@ -23,6 +23,7 @@ public class Tests { if (args.length == 0) { args = new String[]{ "com.android.ahat.InstanceUtilsTest", + "com.android.ahat.NativeAllocationTest", "com.android.ahat.PerformanceTest", "com.android.ahat.QueryTest", "com.android.ahat.SortTest", |