diff options
73 files changed, 2343 insertions, 1106 deletions
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index ff41736a35..0afec2d5ef 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -74,6 +74,7 @@ ART_GTEST_jni_compiler_test_DEX_DEPS := MyClassNatives ART_GTEST_jni_internal_test_DEX_DEPS := AllFields StaticLeafMethods ART_GTEST_oat_file_assistant_test_DEX_DEPS := Main MainStripped MultiDex MultiDexModifiedSecondary Nested ART_GTEST_oat_file_test_DEX_DEPS := Main MultiDex +ART_GTEST_oat_test_DEX_DEPS := Main ART_GTEST_object_test_DEX_DEPS := ProtoCompare ProtoCompare2 StaticsFromCode XandY ART_GTEST_proxy_test_DEX_DEPS := Interfaces ART_GTEST_reflection_test_DEX_DEPS := Main NonStaticLeafMethods StaticLeafMethods diff --git a/compiler/compiler.h b/compiler/compiler.h index 8788dc1950..3a9ce1bc0e 100644 --- a/compiler/compiler.h +++ b/compiler/compiler.h @@ -22,6 +22,10 @@ namespace art { +namespace jit { + class JitCodeCache; +} + class ArtMethod; class Backend; struct CompilationUnit; @@ -58,6 +62,13 @@ class Compiler { uint32_t method_idx, const DexFile& dex_file) const = 0; + virtual bool JitCompile(Thread* self ATTRIBUTE_UNUSED, + jit::JitCodeCache* code_cache ATTRIBUTE_UNUSED, + ArtMethod* method ATTRIBUTE_UNUSED) + SHARED_REQUIRES(Locks::mutator_lock_) { + return false; + } + virtual uintptr_t GetEntryPointOf(ArtMethod* method) const SHARED_REQUIRES(Locks::mutator_lock_) = 0; diff --git a/compiler/dex/quick/arm64/call_arm64.cc b/compiler/dex/quick/arm64/call_arm64.cc index 036da2e2b2..b1acf5e691 100644 --- a/compiler/dex/quick/arm64/call_arm64.cc +++ b/compiler/dex/quick/arm64/call_arm64.cc @@ -447,7 +447,7 @@ void Arm64Mir2Lir::GenSpecialExitForSuspend() { static bool Arm64UseRelativeCall(CompilationUnit* cu, const MethodReference& target_method) { // Emit relative calls anywhere in the image or within a dex file otherwise. - return cu->compiler_driver->IsImage() || cu->dex_file == target_method.dex_file; + return cu->compiler_driver->IsBootImage() || cu->dex_file == target_method.dex_file; } /* diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index d055b37ea7..aa5e411ba8 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -341,7 +341,7 @@ CompilerDriver::CompilerDriver(const CompilerOptions* compiler_options, Compiler::Kind compiler_kind, InstructionSet instruction_set, const InstructionSetFeatures* instruction_set_features, - bool image, std::unordered_set<std::string>* image_classes, + bool boot_image, std::unordered_set<std::string>* image_classes, std::unordered_set<std::string>* compiled_classes, std::unordered_set<std::string>* compiled_methods, size_t thread_count, bool dump_stats, bool dump_passes, @@ -361,7 +361,7 @@ CompilerDriver::CompilerDriver(const CompilerOptions* compiler_options, compiled_methods_lock_("compiled method lock"), compiled_methods_(MethodTable::key_compare()), non_relative_linker_patch_count_(0u), - image_(image), + boot_image_(boot_image), image_classes_(image_classes), classes_to_compile_(compiled_classes), methods_to_compile_(compiled_methods), @@ -383,7 +383,7 @@ CompilerDriver::CompilerDriver(const CompilerOptions* compiler_options, compiler_->Init(); - CHECK_EQ(image_, image_classes_.get() != nullptr); + CHECK_EQ(boot_image_, image_classes_.get() != nullptr); // Read the profile file if one is provided. if (!profile_file.empty()) { @@ -559,7 +559,7 @@ static void CompileMethod(Thread* self, } } else if ((access_flags & kAccAbstract) != 0) { // Abstract methods don't have code. - } else if (Runtime::Current()->IsAotCompiler()) { + } else { const VerifiedMethod* verified_method = driver->GetVerificationResults()->GetVerifiedMethod(method_ref); bool compile = compilation_enabled && @@ -598,13 +598,6 @@ static void CompileMethod(Thread* self, ? dex_to_dex_compilation_level : optimizer::DexToDexCompilationLevel::kRequired); } - } else { - // This is for the JIT compiler, which has already ensured the class is verified. - // We can go straight to compiling. - DCHECK(Runtime::Current()->UseJit()); - compiled_method = driver->GetCompiler()->Compile(code_item, access_flags, invoke_type, - class_def_idx, method_idx, class_loader, - dex_file, dex_cache); } if (kTimeCompileMethod) { uint64_t duration_ns = NanoTime() - start_ns; @@ -696,42 +689,6 @@ void CompilerDriver::CompileOne(Thread* self, ArtMethod* method, TimingLogger* t self->GetJniEnv()->DeleteGlobalRef(jclass_loader); } -CompiledMethod* CompilerDriver::CompileArtMethod(Thread* self, ArtMethod* method) { - DCHECK_EQ(method, - method->GetInterfaceMethodIfProxy( - Runtime::Current()->GetClassLinker()->GetImagePointerSize())); - const uint32_t method_idx = method->GetDexMethodIndex(); - const uint32_t access_flags = method->GetAccessFlags(); - const InvokeType invoke_type = method->GetInvokeType(); - StackHandleScope<2> hs(self); - Handle<mirror::ClassLoader> class_loader(hs.NewHandle( - method->GetDeclaringClass()->GetClassLoader())); - Handle<mirror::DexCache> dex_cache(hs.NewHandle(method->GetDexCache())); - jobject jclass_loader = class_loader.ToJObject(); - const DexFile* dex_file = method->GetDexFile(); - const uint16_t class_def_idx = method->GetClassDefIndex(); - const DexFile::ClassDef& class_def = dex_file->GetClassDef(class_def_idx); - optimizer::DexToDexCompilationLevel dex_to_dex_compilation_level = - GetDexToDexCompilationLevel(self, *this, class_loader, *dex_file, class_def); - const DexFile::CodeItem* code_item = dex_file->GetCodeItem(method->GetCodeItemOffset()); - // Go to native so that we don't block GC during compilation. - ScopedThreadSuspension sts(self, kNative); - CompileMethod(self, - this, - code_item, - access_flags, - invoke_type, - class_def_idx, - method_idx, - jclass_loader, - *dex_file, - dex_to_dex_compilation_level, - true, - dex_cache); - auto* compiled_method = GetCompiledMethod(MethodReference(dex_file, method_idx)); - return compiled_method; -} - void CompilerDriver::Resolve(jobject class_loader, const std::vector<const DexFile*>& dex_files, ThreadPool* thread_pool, TimingLogger* timings) { for (size_t i = 0; i != dex_files.size(); ++i) { @@ -781,7 +738,7 @@ void CompilerDriver::PreCompile(jobject class_loader, const std::vector<const De } bool CompilerDriver::IsImageClass(const char* descriptor) const { - if (!IsImage()) { + if (!IsBootImage()) { // NOTE: Currently unreachable, all callers check IsImage(). return false; } else { @@ -790,7 +747,7 @@ bool CompilerDriver::IsImageClass(const char* descriptor) const { } bool CompilerDriver::IsClassToCompile(const char* descriptor) const { - if (kRestrictCompilationFiltersToImage && !IsImage()) { + if (kRestrictCompilationFiltersToImage && !IsBootImage()) { return true; } @@ -801,7 +758,7 @@ bool CompilerDriver::IsClassToCompile(const char* descriptor) const { } bool CompilerDriver::IsMethodToCompile(const MethodReference& method_ref) const { - if (kRestrictCompilationFiltersToImage && !IsImage()) { + if (kRestrictCompilationFiltersToImage && !IsBootImage()) { return true; } @@ -889,7 +846,7 @@ class RecordImageClassesVisitor : public ClassVisitor { // Make a list of descriptors for classes to include in the image void CompilerDriver::LoadImageClasses(TimingLogger* timings) { CHECK(timings != nullptr); - if (!IsImage()) { + if (!IsBootImage()) { return; } @@ -1118,7 +1075,7 @@ class ClinitImageUpdate { }; void CompilerDriver::UpdateImageClasses(TimingLogger* timings) { - if (IsImage()) { + if (IsBootImage()) { TimingLogger::ScopedTiming t("UpdateImageClasses", timings); Runtime* runtime = Runtime::Current(); @@ -1145,7 +1102,7 @@ bool CompilerDriver::CanAssumeClassIsLoaded(mirror::Class* klass) { // Having the klass reference here implies that the klass is already loaded. return true; } - if (!IsImage()) { + if (!IsBootImage()) { // Assume loaded only if klass is in the boot image. App classes cannot be assumed // loaded because we don't even know what class loader will be used to load them. bool class_in_image = runtime->GetHeap()->FindSpaceFromObject(klass, false)->IsImageSpace(); @@ -1157,7 +1114,7 @@ bool CompilerDriver::CanAssumeClassIsLoaded(mirror::Class* klass) { } bool CompilerDriver::CanAssumeTypeIsPresentInDexCache(const DexFile& dex_file, uint32_t type_idx) { - if (IsImage() && + if (IsBootImage() && IsImageClass(dex_file.StringDataByIdx(dex_file.GetTypeId(type_idx).descriptor_idx_))) { { ScopedObjectAccess soa(Thread::Current()); @@ -1183,7 +1140,7 @@ bool CompilerDriver::CanAssumeStringIsPresentInDexCache(const DexFile& dex_file, // See also Compiler::ResolveDexFile bool result = false; - if (IsImage()) { + if (IsBootImage()) { // We resolve all const-string strings when building for the image. ScopedObjectAccess soa(Thread::Current()); StackHandleScope<1> hs(soa.Self()); @@ -1300,7 +1257,7 @@ bool CompilerDriver::CanEmbedTypeInCode(const DexFile& dex_file, uint32_t type_i if (compiling_boot) { // boot -> boot class pointers. // True if the class is in the image at boot compiling time. - const bool is_image_class = IsImage() && IsImageClass( + const bool is_image_class = IsBootImage() && IsImageClass( dex_file.StringDataByIdx(dex_file.GetTypeId(type_idx).descriptor_idx_)); // True if pc relative load works. if (is_image_class && support_boot_image_fixup) { @@ -1548,7 +1505,7 @@ void CompilerDriver::GetCodeAndMethodForDirectCall(InvokeType* type, InvokeType } if (!use_dex_cache && force_relocations) { bool is_in_image; - if (IsImage()) { + if (IsBootImage()) { is_in_image = IsImageClass(method->GetDeclaringClassDescriptor()); } else { is_in_image = instruction_set_ != kX86 && instruction_set_ != kX86_64 && @@ -2019,7 +1976,7 @@ void CompilerDriver::ResolveDexFile(jobject class_loader, const DexFile& dex_fil ParallelCompilationManager context(class_linker, class_loader, this, &dex_file, dex_files, thread_pool); - if (IsImage()) { + if (IsBootImage()) { // For images we resolve all types, such as array, whereas for applications just those with // classdefs are resolved by ResolveClassFieldsAndMethods. TimingLogger::ScopedTiming t("Resolve Types", timings); @@ -2101,8 +2058,8 @@ class VerifyClassVisitor : public CompilationVisitor { // It is *very* problematic if there are verification errors in the boot classpath. For example, // we rely on things working OK without verification when the decryption dialog is brought up. // So abort in a debug build if we find this violated. - DCHECK(!manager_->GetCompiler()->IsImage() || klass->IsVerified()) << "Boot classpath class " - << PrettyClass(klass.Get()) << " failed to fully verify."; + DCHECK(!manager_->GetCompiler()->IsBootImage() || klass->IsVerified()) + << "Boot classpath class " << PrettyClass(klass.Get()) << " failed to fully verify."; } soa.Self()->AssertNoPendingException(); } @@ -2222,7 +2179,7 @@ class InitializeClassVisitor : public CompilationVisitor { if (!klass->IsInitialized()) { // We need to initialize static fields, we only do this for image classes that aren't // marked with the $NoPreloadHolder (which implies this should not be initialized early). - bool can_init_static_fields = manager_->GetCompiler()->IsImage() && + bool can_init_static_fields = manager_->GetCompiler()->IsBootImage() && manager_->GetCompiler()->IsImageClass(descriptor) && !StringPiece(descriptor).ends_with("$NoPreloadHolder;"); if (can_init_static_fields) { @@ -2286,7 +2243,7 @@ void CompilerDriver::InitializeClasses(jobject jni_class_loader, const DexFile& ParallelCompilationManager context(class_linker, jni_class_loader, this, &dex_file, dex_files, thread_pool); size_t thread_count; - if (IsImage()) { + if (IsBootImage()) { // TODO: remove this when transactional mode supports multithreading. thread_count = 1U; } else { @@ -2304,7 +2261,7 @@ void CompilerDriver::InitializeClasses(jobject class_loader, CHECK(dex_file != nullptr); InitializeClasses(class_loader, *dex_file, dex_files, thread_pool, timings); } - if (IsImage()) { + if (IsBootImage()) { // Prune garbage objects created during aborted transactions. Runtime::Current()->GetHeap()->CollectGarbage(true); } diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h index 4ed4dc60d2..5683b03a71 100644 --- a/compiler/driver/compiler_driver.h +++ b/compiler/driver/compiler_driver.h @@ -92,7 +92,7 @@ class CompilerDriver { Compiler::Kind compiler_kind, InstructionSet instruction_set, const InstructionSetFeatures* instruction_set_features, - bool image, std::unordered_set<std::string>* image_classes, + bool boot_image, std::unordered_set<std::string>* image_classes, std::unordered_set<std::string>* compiled_classes, std::unordered_set<std::string>* compiled_methods, size_t thread_count, bool dump_stats, bool dump_passes, @@ -119,9 +119,6 @@ class CompilerDriver { TimingLogger* timings) REQUIRES(!Locks::mutator_lock_, !compiled_classes_lock_); - CompiledMethod* CompileArtMethod(Thread* self, ArtMethod*) - SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!compiled_methods_lock_) WARN_UNUSED; - // Compile a single Method. void CompileOne(Thread* self, ArtMethod* method, TimingLogger* timings) SHARED_REQUIRES(Locks::mutator_lock_) @@ -156,8 +153,8 @@ class CompilerDriver { } // Are we compiling and creating an image file? - bool IsImage() const { - return image_; + bool IsBootImage() const { + return boot_image_; } const std::unordered_set<std::string>* GetImageClasses() const { @@ -637,7 +634,7 @@ class CompilerDriver { // in the .oat_patches ELF section if requested in the compiler options. size_t non_relative_linker_patch_count_ GUARDED_BY(compiled_methods_lock_); - const bool image_; + const bool boot_image_; // If image_ is true, specifies the classes that will be included in // the image. Note if image_classes_ is null, all classes are diff --git a/compiler/elf_builder.h b/compiler/elf_builder.h index bbd962fae2..e977798ab1 100644 --- a/compiler/elf_builder.h +++ b/compiler/elf_builder.h @@ -266,8 +266,8 @@ class ElfBuilder FINAL { // Writer of .dynstr .strtab and .shstrtab sections. class StrtabSection FINAL : public Section { public: - StrtabSection(const std::string& name, Elf_Word flags) - : Section(name, SHT_STRTAB, flags, nullptr, 0, 1, 0) { + StrtabSection(const std::string& name, Elf_Word flags, Elf_Word align) + : Section(name, SHT_STRTAB, flags, nullptr, 0, align, 0) { buffer_.reserve(4 * KB); // The first entry of strtab must be empty string. buffer_ += '\0'; @@ -459,16 +459,8 @@ class ElfBuilder FINAL { private: Elf_Word GetNumBuckets() const { const auto& symbols = symtab_->symbols_; - if (symbols.size() < 8) { - return 2; - } else if (symbols.size() < 32) { - return 4; - } else if (symbols.size() < 256) { - return 16; - } else { - // Have about 32 ids per bucket. - return RoundUp(symbols.size()/32, 2); - } + // Have about 32 ids per bucket. + return 1 + symbols.size()/32; } // from bionic @@ -495,7 +487,7 @@ class ElfBuilder FINAL { Elf_Word text_size, CodeOutput* text_writer, Elf_Word bss_size) : isa_(isa), - dynstr_(".dynstr", SHF_ALLOC), + dynstr_(".dynstr", SHF_ALLOC, kPageSize), dynsym_(".dynsym", SHT_DYNSYM, SHF_ALLOC, &dynstr_), hash_(".hash", SHF_ALLOC, &dynsym_), rodata_(".rodata", SHT_PROGBITS, SHF_ALLOC, @@ -504,9 +496,9 @@ class ElfBuilder FINAL { nullptr, 0, kPageSize, 0, text_size, text_writer), bss_(".bss", bss_size), dynamic_(".dynamic", &dynstr_), - strtab_(".strtab", 0), + strtab_(".strtab", 0, kPageSize), symtab_(".symtab", SHT_SYMTAB, 0, &strtab_), - shstrtab_(".shstrtab", 0) { + shstrtab_(".shstrtab", 0, 1) { } ~ElfBuilder() {} @@ -606,18 +598,19 @@ class ElfBuilder FINAL { // Create a list of all section which we want to write. // This is the order in which they will be written. std::vector<Section*> sections; - sections.push_back(&dynsym_); - sections.push_back(&dynstr_); - sections.push_back(&hash_); sections.push_back(&rodata_); + // Need to write text to update checksum of header even if it is empty. sections.push_back(&text_); if (bss_.GetSize() != 0u) { sections.push_back(&bss_); } + sections.push_back(&dynstr_); + sections.push_back(&dynsym_); + sections.push_back(&hash_); sections.push_back(&dynamic_); if (!symtab_.IsEmpty()) { - sections.push_back(&symtab_); sections.push_back(&strtab_); + sections.push_back(&symtab_); } for (Section* section : other_sections_) { sections.push_back(section); @@ -643,7 +636,7 @@ class ElfBuilder FINAL { // We do not know the number of headers until the final stages of write. // It is easiest to just reserve a fixed amount of space for them. - constexpr size_t kMaxProgramHeaders = 8; + constexpr size_t kMaxProgramHeaders = 16; constexpr size_t kProgramHeadersOffset = sizeof(Elf_Ehdr); // Layout of all sections - determine the final file offsets and addresses. @@ -690,10 +683,17 @@ class ElfBuilder FINAL { const Elf_Shdr* rodata = rodata_.GetHeader(); program_headers.push_back(MakeProgramHeader(PT_LOAD, PF_R, 0, rodata->sh_offset + rodata->sh_size, rodata->sh_addralign)); - program_headers.push_back(MakeProgramHeader(PT_LOAD, PF_R | PF_X, text_)); + if (text_.GetHeader()->sh_size != 0u) { + program_headers.push_back(MakeProgramHeader(PT_LOAD, PF_R | PF_X, text_)); + } if (bss_.GetHeader()->sh_size != 0u) { program_headers.push_back(MakeProgramHeader(PT_LOAD, PF_R | PF_W, bss_)); } + program_headers.push_back(MakeProgramHeader(PT_LOAD, PF_R, dynstr_)); + int dynstr_dynsym_hash_size = hash_.GetHeader()->sh_offset + + hash_.GetHeader()->sh_size - dynstr_.GetHeader()->sh_offset; + program_headers.back().p_filesz = dynstr_dynsym_hash_size; + program_headers.back().p_memsz = dynstr_dynsym_hash_size; program_headers.push_back(MakeProgramHeader(PT_LOAD, PF_R | PF_W, dynamic_)); program_headers.push_back(MakeProgramHeader(PT_DYNAMIC, PF_R | PF_W, dynamic_)); const Section* eh_frame = FindSection(".eh_frame"); @@ -912,10 +912,16 @@ class ElfBuilder FINAL { void BuildDynsymSection() { dynsym_.AddSymbol("oatdata", &rodata_, 0, true, rodata_.GetSize(), STB_GLOBAL, STT_OBJECT); - dynsym_.AddSymbol("oatexec", &text_, 0, true, - text_.GetSize(), STB_GLOBAL, STT_OBJECT); - dynsym_.AddSymbol("oatlastword", &text_, text_.GetSize() - 4, - true, 4, STB_GLOBAL, STT_OBJECT); + if (text_.GetSize() != 0u) { + dynsym_.AddSymbol("oatexec", &text_, 0, true, + text_.GetSize(), STB_GLOBAL, STT_OBJECT); + dynsym_.AddSymbol("oatlastword", &text_, text_.GetSize() - 4, + true, 4, STB_GLOBAL, STT_OBJECT); + } else if (rodata_.GetSize() != 0) { + // rodata_ be size 0 for dwarf_test. + dynsym_.AddSymbol("oatlastword", &rodata_, rodata_.GetSize() - 4, + true, 4, STB_GLOBAL, STT_OBJECT); + } if (bss_.GetSize() != 0u) { dynsym_.AddSymbol("oatbss", &bss_, 0, true, bss_.GetSize(), STB_GLOBAL, STT_OBJECT); diff --git a/compiler/image_test.cc b/compiler/image_test.cc index fd6cd82f7c..a38e1f54c0 100644 --- a/compiler/image_test.cc +++ b/compiler/image_test.cc @@ -64,8 +64,10 @@ TEST_F(ImageTest, WriteRead) { ScratchFile oat_file(OS::CreateEmptyFile(oat_filename.c_str())); const uintptr_t requested_image_base = ART_BASE_ADDRESS; - std::unique_ptr<ImageWriter> writer(new ImageWriter(*compiler_driver_, requested_image_base, - /*compile_pic*/false)); + std::unique_ptr<ImageWriter> writer(new ImageWriter(*compiler_driver_, + requested_image_base, + /*compile_pic*/false, + /*compile_app_image*/false)); // TODO: compile_pic should be a test argument. { { @@ -81,8 +83,15 @@ TEST_F(ImageTest, WriteRead) { t.NewTiming("WriteElf"); SafeMap<std::string, std::string> key_value_store; - OatWriter oat_writer(class_linker->GetBootClassPath(), 0, 0, 0, compiler_driver_.get(), - writer.get(), &timings, &key_value_store); + OatWriter oat_writer(class_linker->GetBootClassPath(), + 0, + 0, + 0, + compiler_driver_.get(), + writer.get(), + /*compiling_boot_image*/true, + &timings, + &key_value_store); bool success = writer->PrepareImageAddressSpace() && compiler_driver_->WriteElf(GetTestAndroidRoot(), !kIsTargetBuild, diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc index af2a4f9426..0c85323805 100644 --- a/compiler/image_writer.cc +++ b/compiler/image_writer.cc @@ -20,6 +20,7 @@ #include <memory> #include <numeric> +#include <unordered_set> #include <vector> #include "art_field-inl.h" @@ -72,6 +73,27 @@ namespace art { // Separate objects into multiple bins to optimize dirty memory use. static constexpr bool kBinObjects = true; +// Return true if an object is already in an image space. +bool ImageWriter::IsInBootImage(const void* obj) const { + if (!compile_app_image_) { + DCHECK(boot_image_space_ == nullptr); + return false; + } + const uint8_t* image_begin = boot_image_space_->Begin(); + // Real image end including ArtMethods and ArtField sections. + const uint8_t* image_end = image_begin + boot_image_space_->GetImageHeader().GetImageSize(); + return image_begin <= obj && obj < image_end; +} + +bool ImageWriter::IsInBootOatFile(const void* ptr) const { + if (!compile_app_image_) { + DCHECK(boot_image_space_ == nullptr); + return false; + } + const ImageHeader& image_header = boot_image_space_->GetImageHeader(); + return image_header.GetOatFileBegin() <= ptr && ptr < image_header.GetOatFileEnd(); +} + static void CheckNoDexObjectsCallback(Object* obj, void* arg ATTRIBUTE_UNUSED) SHARED_REQUIRES(Locks::mutator_lock_) { Class* klass = obj->GetClass(); @@ -85,12 +107,20 @@ static void CheckNoDexObjects() { bool ImageWriter::PrepareImageAddressSpace() { target_ptr_size_ = InstructionSetPointerSize(compiler_driver_.GetInstructionSet()); + gc::Heap* const heap = Runtime::Current()->GetHeap(); + // Cache boot image space. + for (gc::space::ContinuousSpace* space : heap->GetContinuousSpaces()) { + if (space->IsImageSpace()) { + CHECK(compile_app_image_); + CHECK(boot_image_space_ == nullptr) << "Multiple image spaces"; + boot_image_space_ = space->AsImageSpace(); + } + } { ScopedObjectAccess soa(Thread::Current()); PruneNonImageClasses(); // Remove junk ComputeLazyFieldsForImageClasses(); // Add useful information } - gc::Heap* heap = Runtime::Current()->GetHeap(); heap->CollectGarbage(false); // Remove garbage. // Dex caches must not have their dex fields set in the image. These are memory buffers of mapped @@ -144,21 +174,21 @@ bool ImageWriter::Write(int image_fd, Runtime::Current()->GetOatFileManager().RegisterOatFile( std::unique_ptr<const OatFile>(oat_file_)); - interpreter_to_interpreter_bridge_offset_ = - oat_file_->GetOatHeader().GetInterpreterToInterpreterBridgeOffset(); - interpreter_to_compiled_code_bridge_offset_ = - oat_file_->GetOatHeader().GetInterpreterToCompiledCodeBridgeOffset(); - - jni_dlsym_lookup_offset_ = oat_file_->GetOatHeader().GetJniDlsymLookupOffset(); - - quick_generic_jni_trampoline_offset_ = - oat_file_->GetOatHeader().GetQuickGenericJniTrampolineOffset(); - quick_imt_conflict_trampoline_offset_ = - oat_file_->GetOatHeader().GetQuickImtConflictTrampolineOffset(); - quick_resolution_trampoline_offset_ = - oat_file_->GetOatHeader().GetQuickResolutionTrampolineOffset(); - quick_to_interpreter_bridge_offset_ = - oat_file_->GetOatHeader().GetQuickToInterpreterBridgeOffset(); + const OatHeader& oat_header = oat_file_->GetOatHeader(); + oat_address_offsets_[kOatAddressInterpreterToInterpreterBridge] = + oat_header.GetInterpreterToInterpreterBridgeOffset(); + oat_address_offsets_[kOatAddressInterpreterToCompiledCodeBridge] = + oat_header.GetInterpreterToCompiledCodeBridgeOffset(); + oat_address_offsets_[kOatAddressJNIDlsymLookup] = + oat_header.GetJniDlsymLookupOffset(); + oat_address_offsets_[kOatAddressQuickGenericJNITrampoline] = + oat_header.GetQuickGenericJniTrampolineOffset(); + oat_address_offsets_[kOatAddressQuickIMTConflictTrampoline] = + oat_header.GetQuickImtConflictTrampolineOffset(); + oat_address_offsets_[kOatAddressQuickResolutionTrampoline] = + oat_header.GetQuickResolutionTrampolineOffset(); + oat_address_offsets_[kOatAddressQuickToInterpreterBridge] = + oat_header.GetQuickToInterpreterBridgeOffset(); size_t oat_loaded_size = 0; size_t oat_data_offset = 0; @@ -307,7 +337,7 @@ void ImageWriter::PrepareDexCacheArraySlots() { for (jobject weak_root : class_linker->GetDexCaches()) { mirror::DexCache* dex_cache = down_cast<mirror::DexCache*>(self->DecodeJObject(weak_root)); - if (dex_cache == nullptr) { + if (dex_cache == nullptr || IsInBootImage(dex_cache)) { continue; } const DexFile* dex_file = dex_cache->GetDexFile(); @@ -331,6 +361,7 @@ void ImageWriter::PrepareDexCacheArraySlots() { void ImageWriter::AddDexCacheArrayRelocation(void* array, size_t offset) { if (array != nullptr) { + DCHECK(!IsInBootImage(array)); native_object_relocations_.emplace( array, NativeObjectRelocation { offset, kNativeObjectRelocationTypeDexCacheArray }); @@ -344,8 +375,8 @@ void ImageWriter::AddMethodPointerArray(mirror::PointerArray* arr) { auto* method = arr->GetElementPtrSize<ArtMethod*>(i, target_ptr_size_); if (method != nullptr && !method->IsRuntimeMethod()) { auto* klass = method->GetDeclaringClass(); - CHECK(klass == nullptr || IsImageClass(klass)) << PrettyClass(klass) - << " should be an image class"; + CHECK(klass == nullptr || KeepClass(klass)) + << PrettyClass(klass) << " should be a kept class"; } } } @@ -539,10 +570,66 @@ void ImageWriter::ComputeLazyFieldsForImageClasses() { class_linker->VisitClassesWithoutClassesLock(&visitor); } -bool ImageWriter::IsImageClass(Class* klass) { +static bool IsBootClassLoaderClass(mirror::Class* klass) SHARED_REQUIRES(Locks::mutator_lock_) { + return klass->GetClassLoader() == nullptr; +} + +bool ImageWriter::IsBootClassLoaderNonImageClass(mirror::Class* klass) { + return IsBootClassLoaderClass(klass) && !IsInBootImage(klass); +} + +bool ImageWriter::ContainsBootClassLoaderNonImageClass(mirror::Class* klass) { + if (klass == nullptr) { + return false; + } + auto found = prune_class_memo_.find(klass); + if (found != prune_class_memo_.end()) { + // Already computed, return the found value. + return found->second; + } + // Place holder value to prevent infinite recursion. + prune_class_memo_.emplace(klass, false); + bool result = IsBootClassLoaderNonImageClass(klass); + if (!result) { + // Check interfaces since these wont be visited through VisitReferences.) + mirror::IfTable* if_table = klass->GetIfTable(); + for (size_t i = 0, num_interfaces = klass->GetIfTableCount(); i < num_interfaces; ++i) { + result = result || ContainsBootClassLoaderNonImageClass(if_table->GetInterface(i)); + } + } + // Check static fields and their classes. + size_t num_static_fields = klass->NumReferenceStaticFields(); + if (num_static_fields != 0 && klass->IsResolved()) { + // Presumably GC can happen when we are cross compiling, it should not cause performance + // problems to do pointer size logic. + MemberOffset field_offset = klass->GetFirstReferenceStaticFieldOffset( + Runtime::Current()->GetClassLinker()->GetImagePointerSize()); + for (size_t i = 0u; i < num_static_fields; ++i) { + mirror::Object* ref = klass->GetFieldObject<mirror::Object>(field_offset); + if (ref != nullptr) { + if (ref->IsClass()) { + result = result || ContainsBootClassLoaderNonImageClass(ref->AsClass()); + } + result = result || ContainsBootClassLoaderNonImageClass(ref->GetClass()); + } + field_offset = MemberOffset(field_offset.Uint32Value() + + sizeof(mirror::HeapReference<mirror::Object>)); + } + } + result = result || ContainsBootClassLoaderNonImageClass(klass->GetSuperClass()); + prune_class_memo_[klass] = result; + return result; +} + +bool ImageWriter::KeepClass(Class* klass) { if (klass == nullptr) { return false; } + if (compile_app_image_) { + // For app images, we need to prune boot loader classes that are not in the boot image since + // these may have already been loaded when the app image is loaded. + return !ContainsBootClassLoaderNonImageClass(klass); + } std::string temp; return compiler_driver_.IsImageClass(klass->GetDescriptor(&temp)); } @@ -552,21 +639,17 @@ class NonImageClassesVisitor : public ClassVisitor { explicit NonImageClassesVisitor(ImageWriter* image_writer) : image_writer_(image_writer) {} bool Visit(Class* klass) OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) { - if (!image_writer_->IsImageClass(klass)) { - std::string temp; - non_image_classes_.insert(klass->GetDescriptor(&temp)); + if (!image_writer_->KeepClass(klass)) { + classes_to_prune_.insert(klass); } return true; } - std::set<std::string> non_image_classes_; + std::unordered_set<mirror::Class*> classes_to_prune_; ImageWriter* const image_writer_; }; void ImageWriter::PruneNonImageClasses() { - if (compiler_driver_.GetImageClasses() == nullptr) { - return; - } Runtime* runtime = Runtime::Current(); ClassLinker* class_linker = runtime->GetClassLinker(); Thread* self = Thread::Current(); @@ -576,8 +659,14 @@ void ImageWriter::PruneNonImageClasses() { class_linker->VisitClasses(&visitor); // Remove the undesired classes from the class roots. - for (const std::string& it : visitor.non_image_classes_) { - bool result = class_linker->RemoveClass(it.c_str(), nullptr); + for (mirror::Class* klass : visitor.classes_to_prune_) { + std::string temp; + const char* name = klass->GetDescriptor(&temp); + VLOG(compiler) << "Pruning class " << name; + if (!compile_app_image_) { + DCHECK(IsBootClassLoaderClass(klass)); + } + bool result = class_linker->RemoveClass(name, klass->GetClassLoader()); DCHECK(result); } @@ -594,7 +683,7 @@ void ImageWriter::PruneNonImageClasses() { } for (size_t i = 0; i < dex_cache->NumResolvedTypes(); i++) { Class* klass = dex_cache->GetResolvedType(i); - if (klass != nullptr && !IsImageClass(klass)) { + if (klass != nullptr && !KeepClass(klass)) { dex_cache->SetResolvedType(i, nullptr); } } @@ -607,7 +696,7 @@ void ImageWriter::PruneNonImageClasses() { // Miranda methods may be held live by a class which was not an image class but have a // declaring class which is an image class. Set it to the resolution method to be safe and // prevent dangling pointers. - if (method->IsMiranda() || !IsImageClass(declaring_class)) { + if (method->IsMiranda() || !KeepClass(declaring_class)) { mirror::DexCache::SetElementPtrSize(resolved_methods, i, resolution_method, @@ -621,7 +710,7 @@ void ImageWriter::PruneNonImageClasses() { } for (size_t i = 0; i < dex_cache->NumResolvedFields(); i++) { ArtField* field = dex_cache->GetResolvedField(i, target_ptr_size_); - if (field != nullptr && !IsImageClass(field->GetDeclaringClass())) { + if (field != nullptr && !KeepClass(field->GetDeclaringClass())) { dex_cache->SetResolvedField(i, nullptr, target_ptr_size_); } } @@ -632,6 +721,9 @@ void ImageWriter::PruneNonImageClasses() { // Drop the array class cache in the ClassLinker, as these are roots holding those classes live. class_linker->DropFindArrayClassCache(); + + // Clear to save RAM. + prune_class_memo_.clear(); } void ImageWriter::CheckNonImageClassesRemoved() { @@ -643,13 +735,13 @@ void ImageWriter::CheckNonImageClassesRemoved() { void ImageWriter::CheckNonImageClassesRemovedCallback(Object* obj, void* arg) { ImageWriter* image_writer = reinterpret_cast<ImageWriter*>(arg); - if (obj->IsClass()) { + if (obj->IsClass() && !image_writer->IsInBootImage(obj)) { Class* klass = obj->AsClass(); - if (!image_writer->IsImageClass(klass)) { + if (!image_writer->KeepClass(klass)) { image_writer->DumpImageClasses(); std::string temp; - CHECK(image_writer->IsImageClass(klass)) << klass->GetDescriptor(&temp) - << " " << PrettyDescriptor(klass); + CHECK(image_writer->KeepClass(klass)) << klass->GetDescriptor(&temp) + << " " << PrettyDescriptor(klass); } } } @@ -703,25 +795,35 @@ ObjectArray<Object>* ImageWriter::CreateImageRoots() const { // ObjectArray, we lock the dex lock twice, first to get the number // of dex caches first and then lock it again to copy the dex // caches. We check that the number of dex caches does not change. - size_t dex_cache_count; + size_t dex_cache_count = 0; { ReaderMutexLock mu(self, *class_linker->DexLock()); - dex_cache_count = class_linker->GetDexCacheCount(); + // Count number of dex caches not in the boot image. + for (jobject weak_root : class_linker->GetDexCaches()) { + mirror::DexCache* dex_cache = down_cast<mirror::DexCache*>(self->DecodeJObject(weak_root)); + dex_cache_count += IsInBootImage(dex_cache) ? 0u : 1u; + } } Handle<ObjectArray<Object>> dex_caches( - hs.NewHandle(ObjectArray<Object>::Alloc(self, object_array_class.Get(), - dex_cache_count))); + hs.NewHandle(ObjectArray<Object>::Alloc(self, object_array_class.Get(), dex_cache_count))); CHECK(dex_caches.Get() != nullptr) << "Failed to allocate a dex cache array."; { ReaderMutexLock mu(self, *class_linker->DexLock()); - CHECK_EQ(dex_cache_count, class_linker->GetDexCacheCount()) - << "The number of dex caches changed."; + size_t non_image_dex_caches = 0; + // Re-count number of non image dex caches. + for (jobject weak_root : class_linker->GetDexCaches()) { + mirror::DexCache* dex_cache = down_cast<mirror::DexCache*>(self->DecodeJObject(weak_root)); + non_image_dex_caches += IsInBootImage(dex_cache) ? 0u : 1u; + } + CHECK_EQ(dex_cache_count, non_image_dex_caches) + << "The number of non-image dex caches changed."; size_t i = 0; for (jobject weak_root : class_linker->GetDexCaches()) { - mirror::DexCache* dex_cache = - down_cast<mirror::DexCache*>(self->DecodeJObject(weak_root)); - dex_caches->Set<false>(i, dex_cache); - ++i; + mirror::DexCache* dex_cache = down_cast<mirror::DexCache*>(self->DecodeJObject(weak_root)); + if (!IsInBootImage(dex_cache)) { + dex_caches->Set<false>(i, dex_cache); + ++i; + } } } @@ -761,6 +863,10 @@ void ImageWriter::WalkInstanceFields(mirror::Object* obj, mirror::Class* klass) // For an unvisited object, visit it then all its children found via fields. void ImageWriter::WalkFieldsInOrder(mirror::Object* obj) { + if (IsInBootImage(obj)) { + // Object is in the image, don't need to fix it up. + return; + } // Use our own visitor routine (instead of GC visitor) to get better locality between // an object and its fields if (!IsImageBinSlotAssigned(obj)) { @@ -797,6 +903,7 @@ void ImageWriter::WalkFieldsInOrder(mirror::Object* obj) { CHECK(it == native_object_relocations_.end()) << "Field array " << cur_fields << " already forwarded"; size_t& offset = bin_slot_sizes_[kBinArtField]; + DCHECK(!IsInBootImage(cur_fields)); native_object_relocations_.emplace( cur_fields, NativeObjectRelocation { offset, kNativeObjectRelocationTypeArtFieldArray }); @@ -808,6 +915,7 @@ void ImageWriter::WalkFieldsInOrder(mirror::Object* obj) { auto it2 = native_object_relocations_.find(field); CHECK(it2 == native_object_relocations_.end()) << "Field at index=" << i << " already assigned " << PrettyField(field) << " static=" << field->IsStatic(); + DCHECK(!IsInBootImage(field)); native_object_relocations_.emplace( field, NativeObjectRelocation {offset, kNativeObjectRelocationTypeArtField }); offset += sizeof(ArtField); @@ -843,6 +951,7 @@ void ImageWriter::WalkFieldsInOrder(mirror::Object* obj) { CHECK(it == native_object_relocations_.end()) << "Method array " << array << " already forwarded"; size_t& offset = bin_slot_sizes_[bin_type]; + DCHECK(!IsInBootImage(array)); native_object_relocations_.emplace(array, NativeObjectRelocation { offset, any_dirty ? kNativeObjectRelocationTypeArtMethodArrayDirty : kNativeObjectRelocationTypeArtMethodArrayClean }); @@ -867,6 +976,7 @@ void ImageWriter::WalkFieldsInOrder(mirror::Object* obj) { } void ImageWriter::AssignMethodOffset(ArtMethod* method, NativeObjectRelocationType type) { + DCHECK(!IsInBootImage(method)); auto it = native_object_relocations_.find(method); CHECK(it == native_object_relocations_.end()) << "Method " << method << " already assigned " << PrettyMethod(method); @@ -884,10 +994,13 @@ void ImageWriter::WalkFieldsCallback(mirror::Object* obj, void* arg) { void ImageWriter::UnbinObjectsIntoOffsetCallback(mirror::Object* obj, void* arg) { ImageWriter* writer = reinterpret_cast<ImageWriter*>(arg); DCHECK(writer != nullptr); - writer->UnbinObjectsIntoOffset(obj); + if (!writer->IsInBootImage(obj)) { + writer->UnbinObjectsIntoOffset(obj); + } } void ImageWriter::UnbinObjectsIntoOffset(mirror::Object* obj) { + DCHECK(!IsInBootImage(obj)); CHECK(obj != nullptr); // We know the bin slot, and the total bin sizes for all objects by now, @@ -925,13 +1038,15 @@ void ImageWriter::CalculateNewObjectOffsets() { image_methods_[ImageHeader::kRefsAndArgsSaveMethod] = runtime->GetCalleeSaveMethod(Runtime::kRefsAndArgs); - // Add room for fake length prefixed array. + // Add room for fake length prefixed array for holding the image methods. const auto image_method_type = kNativeObjectRelocationTypeArtMethodArrayClean; auto it = native_object_relocations_.find(&image_method_array_); CHECK(it == native_object_relocations_.end()); size_t& offset = bin_slot_sizes_[BinTypeForNativeRelocationType(image_method_type)]; - native_object_relocations_.emplace(&image_method_array_, - NativeObjectRelocation { offset, image_method_type }); + if (!compile_app_image_) { + native_object_relocations_.emplace(&image_method_array_, + NativeObjectRelocation { offset, image_method_type }); + } size_t method_alignment = ArtMethod::Alignment(target_ptr_size_); const size_t array_size = LengthPrefixedArray<ArtMethod>::ComputeSize( 0, ArtMethod::Size(target_ptr_size_), method_alignment); @@ -940,7 +1055,10 @@ void ImageWriter::CalculateNewObjectOffsets() { for (auto* m : image_methods_) { CHECK(m != nullptr); CHECK(m->IsRuntimeMethod()); - AssignMethodOffset(m, kNativeObjectRelocationTypeArtMethodClean); + DCHECK_EQ(compile_app_image_, IsInBootImage(m)) << "Trampolines should be in boot image"; + if (!IsInBootImage(m)) { + AssignMethodOffset(m, kNativeObjectRelocationTypeArtMethodClean); + } } // Calculate size of the dex cache arrays slot and prepare offsets. PrepareDexCacheArraySlots(); @@ -1090,6 +1208,7 @@ void ImageWriter::CopyAndFixupNativeData() { NativeObjectRelocation& relocation = pair.second; auto* dest = image_->Begin() + relocation.offset; DCHECK_GE(dest, image_->Begin() + image_end_); + DCHECK(!IsInBootImage(pair.first)); switch (relocation.type) { case kNativeObjectRelocationTypeArtField: { memcpy(dest, pair.first, sizeof(ArtField)); @@ -1126,16 +1245,18 @@ void ImageWriter::CopyAndFixupNativeData() { auto* image_header = reinterpret_cast<ImageHeader*>(image_->Begin()); const ImageSection& methods_section = image_header->GetMethodsSection(); for (size_t i = 0; i < ImageHeader::kImageMethodsCount; ++i) { - auto* m = image_methods_[i]; - CHECK(m != nullptr); - auto it = native_object_relocations_.find(m); - CHECK(it != native_object_relocations_.end()) << "No fowarding for " << PrettyMethod(m); - NativeObjectRelocation& relocation = it->second; - CHECK(methods_section.Contains(relocation.offset)) << relocation.offset << " not in " - << methods_section; - CHECK(relocation.IsArtMethodRelocation()) << relocation.type; - auto* dest = reinterpret_cast<ArtMethod*>(image_begin_ + it->second.offset); - image_header->SetImageMethod(static_cast<ImageHeader::ImageMethod>(i), dest); + ArtMethod* method = image_methods_[i]; + CHECK(method != nullptr); + if (!IsInBootImage(method)) { + auto it = native_object_relocations_.find(method); + CHECK(it != native_object_relocations_.end()) << "No fowarding for " << PrettyMethod(method); + NativeObjectRelocation& relocation = it->second; + CHECK(methods_section.Contains(relocation.offset)) << relocation.offset << " not in " + << methods_section; + CHECK(relocation.IsArtMethodRelocation()) << relocation.type; + method = reinterpret_cast<ArtMethod*>(image_begin_ + it->second.offset); + } + image_header->SetImageMethod(static_cast<ImageHeader::ImageMethod>(i), method); } // Write the intern table into the image. const ImageSection& intern_table_section = image_header->GetImageSection( @@ -1183,8 +1304,8 @@ void ImageWriter::FixupPointerArray(mirror::Object* dst, mirror::PointerArray* a dst->SetClass(GetImageAddress(arr->GetClass())); auto* dest_array = down_cast<mirror::PointerArray*>(dst); for (size_t i = 0, count = num_elements; i < count; ++i) { - auto* elem = arr->GetElementPtrSize<void*>(i, target_ptr_size_); - if (elem != nullptr) { + void* elem = arr->GetElementPtrSize<void*>(i, target_ptr_size_); + if (elem != nullptr && !IsInBootImage(elem)) { auto it = native_object_relocations_.find(elem); if (UNLIKELY(it == native_object_relocations_.end())) { if (it->second.IsArtMethodRelocation()) { @@ -1209,6 +1330,9 @@ void ImageWriter::FixupPointerArray(mirror::Object* dst, mirror::PointerArray* a } void ImageWriter::CopyAndFixupObject(Object* obj) { + if (IsInBootImage(obj)) { + return; + } size_t offset = GetImageOffset(obj); auto* dst = reinterpret_cast<Object*>(image_->Begin() + offset); DCHECK_LT(offset, image_end_); @@ -1282,18 +1406,19 @@ class FixupClassVisitor FINAL : public FixupVisitor { uintptr_t ImageWriter::NativeOffsetInImage(void* obj) { DCHECK(obj != nullptr); + DCHECK(!IsInBootImage(obj)); auto it = native_object_relocations_.find(obj); - CHECK(it != native_object_relocations_.end()) << obj; + CHECK(it != native_object_relocations_.end()) << obj << " spaces " + << Runtime::Current()->GetHeap()->DumpSpaces(); const NativeObjectRelocation& relocation = it->second; return relocation.offset; } template <typename T> T* ImageWriter::NativeLocationInImage(T* obj) { - if (obj == nullptr) { - return nullptr; - } - return reinterpret_cast<T*>(image_begin_ + NativeOffsetInImage(obj)); + return (obj == nullptr || IsInBootImage(obj)) + ? obj + : reinterpret_cast<T*>(image_begin_ + NativeOffsetInImage(obj)); } void ImageWriter::FixupClass(mirror::Class* orig, mirror::Class* copy) { @@ -1306,18 +1431,22 @@ void ImageWriter::FixupClass(mirror::Class* orig, mirror::Class* copy) { // Update dex cache strings. copy->SetDexCacheStrings(NativeLocationInImage(orig->GetDexCacheStrings())); // Fix up embedded tables. - if (orig->ShouldHaveEmbeddedImtAndVTable()) { - for (int32_t i = 0; i < orig->GetEmbeddedVTableLength(); ++i) { - auto it = native_object_relocations_.find(orig->GetEmbeddedVTableEntry(i, target_ptr_size_)); - CHECK(it != native_object_relocations_.end()) << PrettyClass(orig); - copy->SetEmbeddedVTableEntryUnchecked( - i, reinterpret_cast<ArtMethod*>(image_begin_ + it->second.offset), target_ptr_size_); - } - for (size_t i = 0; i < mirror::Class::kImtSize; ++i) { - auto it = native_object_relocations_.find(orig->GetEmbeddedImTableEntry(i, target_ptr_size_)); - CHECK(it != native_object_relocations_.end()) << PrettyClass(orig); - copy->SetEmbeddedImTableEntry( - i, reinterpret_cast<ArtMethod*>(image_begin_ + it->second.offset), target_ptr_size_); + if (!orig->IsTemp()) { + // TODO: Why do we have temp classes in some cases? + if (orig->ShouldHaveEmbeddedImtAndVTable()) { + for (int32_t i = 0; i < orig->GetEmbeddedVTableLength(); ++i) { + ArtMethod* orig_method = orig->GetEmbeddedVTableEntry(i, target_ptr_size_); + copy->SetEmbeddedVTableEntryUnchecked( + i, + NativeLocationInImage(orig_method), + target_ptr_size_); + } + for (size_t i = 0; i < mirror::Class::kImtSize; ++i) { + copy->SetEmbeddedImTableEntry( + i, + NativeLocationInImage(orig->GetEmbeddedImTableEntry(i, target_ptr_size_)), + target_ptr_size_); + } } } FixupClassVisitor visitor(this, copy); @@ -1419,7 +1548,7 @@ void ImageWriter::FixupDexCache(mirror::DexCache* orig_dex_cache, reinterpret_cast<ArtMethod**>(image_->Begin() + copy_methods_offset); for (size_t i = 0, num = orig_dex_cache->NumResolvedMethods(); i != num; ++i) { ArtMethod* orig = mirror::DexCache::GetElementPtrSize(orig_methods, i, target_ptr_size_); - ArtMethod* copy = NativeLocationInImage(orig); + ArtMethod* copy = IsInBootImage(orig) ? orig : NativeLocationInImage(orig); mirror::DexCache::SetElementPtrSize(copy_methods, i, copy, target_ptr_size_); } } @@ -1432,15 +1561,51 @@ void ImageWriter::FixupDexCache(mirror::DexCache* orig_dex_cache, ArtField** copy_fields = reinterpret_cast<ArtField**>(image_->Begin() + copy_fields_offset); for (size_t i = 0, num = orig_dex_cache->NumResolvedFields(); i != num; ++i) { ArtField* orig = mirror::DexCache::GetElementPtrSize(orig_fields, i, target_ptr_size_); - ArtField* copy = NativeLocationInImage(orig); + ArtField* copy = IsInBootImage(orig) ? orig : NativeLocationInImage(orig); mirror::DexCache::SetElementPtrSize(copy_fields, i, copy, target_ptr_size_); } } } +const uint8_t* ImageWriter::GetOatAddress(OatAddress type) const { + DCHECK_LT(type, kOatAddressCount); + // If we are compiling an app image, we need to use the stubs of the boot image. + if (compile_app_image_) { + // Use the current image pointers. + gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace(); + DCHECK(image_space != nullptr); + const OatFile* oat_file = image_space->GetOatFile(); + CHECK(oat_file != nullptr); + const OatHeader& header = oat_file->GetOatHeader(); + switch (type) { + // TODO: We could maybe clean this up if we stored them in an array in the oat header. + case kOatAddressQuickGenericJNITrampoline: + return static_cast<const uint8_t*>(header.GetQuickGenericJniTrampoline()); + case kOatAddressInterpreterToInterpreterBridge: + return static_cast<const uint8_t*>(header.GetInterpreterToInterpreterBridge()); + case kOatAddressInterpreterToCompiledCodeBridge: + return static_cast<const uint8_t*>(header.GetInterpreterToCompiledCodeBridge()); + case kOatAddressJNIDlsymLookup: + return static_cast<const uint8_t*>(header.GetJniDlsymLookup()); + case kOatAddressQuickIMTConflictTrampoline: + return static_cast<const uint8_t*>(header.GetQuickImtConflictTrampoline()); + case kOatAddressQuickResolutionTrampoline: + return static_cast<const uint8_t*>(header.GetQuickResolutionTrampoline()); + case kOatAddressQuickToInterpreterBridge: + return static_cast<const uint8_t*>(header.GetQuickToInterpreterBridge()); + default: + UNREACHABLE(); + } + } + return GetOatAddressForOffset(oat_address_offsets_[type]); +} + const uint8_t* ImageWriter::GetQuickCode(ArtMethod* method, bool* quick_is_interpreted) { - DCHECK(!method->IsResolutionMethod() && !method->IsImtConflictMethod() && - !method->IsImtUnimplementedMethod() && !method->IsAbstract()) << PrettyMethod(method); + DCHECK(!method->IsResolutionMethod()) << PrettyMethod(method); + DCHECK(!method->IsImtConflictMethod()) << PrettyMethod(method); + DCHECK(!method->IsImtUnimplementedMethod()) << PrettyMethod(method); + DCHECK(!method->IsAbstract()) << PrettyMethod(method); + DCHECK(!IsInBootImage(method)) << PrettyMethod(method); // Use original code if it exists. Otherwise, set the code pointer to the resolution // trampoline. @@ -1448,27 +1613,26 @@ const uint8_t* ImageWriter::GetQuickCode(ArtMethod* method, bool* quick_is_inter // Quick entrypoint: uint32_t quick_oat_code_offset = PointerToLowMemUInt32( method->GetEntryPointFromQuickCompiledCodePtrSize(target_ptr_size_)); - const uint8_t* quick_code = GetOatAddress(quick_oat_code_offset); + const uint8_t* quick_code = GetOatAddressForOffset(quick_oat_code_offset); *quick_is_interpreted = false; if (quick_code != nullptr && (!method->IsStatic() || method->IsConstructor() || method->GetDeclaringClass()->IsInitialized())) { // We have code for a non-static or initialized method, just use the code. - DCHECK_GE(quick_code, oat_data_begin_); } else if (quick_code == nullptr && method->IsNative() && (!method->IsStatic() || method->GetDeclaringClass()->IsInitialized())) { // Non-static or initialized native method missing compiled code, use generic JNI version. - quick_code = GetOatAddress(quick_generic_jni_trampoline_offset_); - DCHECK_GE(quick_code, oat_data_begin_); + quick_code = GetOatAddress(kOatAddressQuickGenericJNITrampoline); } else if (quick_code == nullptr && !method->IsNative()) { // We don't have code at all for a non-native method, use the interpreter. - quick_code = GetOatAddress(quick_to_interpreter_bridge_offset_); + quick_code = GetOatAddress(kOatAddressQuickToInterpreterBridge); *quick_is_interpreted = true; - DCHECK_GE(quick_code, oat_data_begin_); } else { CHECK(!method->GetDeclaringClass()->IsInitialized()); // We have code for a static method, but need to go through the resolution stub for class // initialization. - quick_code = GetOatAddress(quick_resolution_trampoline_offset_); + quick_code = GetOatAddress(kOatAddressQuickResolutionTrampoline); + } + if (!IsInBootOatFile(quick_code)) { DCHECK_GE(quick_code, oat_data_begin_); } return quick_code; @@ -1479,16 +1643,16 @@ const uint8_t* ImageWriter::GetQuickEntryPoint(ArtMethod* method) { // The resolution method has a special trampoline to call. Runtime* runtime = Runtime::Current(); if (UNLIKELY(method == runtime->GetResolutionMethod())) { - return GetOatAddress(quick_resolution_trampoline_offset_); + return GetOatAddress(kOatAddressQuickResolutionTrampoline); } else if (UNLIKELY(method == runtime->GetImtConflictMethod() || method == runtime->GetImtUnimplementedMethod())) { - return GetOatAddress(quick_imt_conflict_trampoline_offset_); + return GetOatAddress(kOatAddressQuickIMTConflictTrampoline); } else { // We assume all methods have code. If they don't currently then we set them to the use the // resolution trampoline. Abstract methods never have code and so we need to make sure their // use results in an AbstractMethodError. We use the interpreter to achieve this. if (UNLIKELY(method->IsAbstract())) { - return GetOatAddress(quick_to_interpreter_bridge_offset_); + return GetOatAddress(kOatAddressQuickToInterpreterBridge); } else { bool quick_is_interpreted; return GetQuickCode(method, &quick_is_interpreted); @@ -1513,11 +1677,11 @@ void ImageWriter::CopyAndFixupMethod(ArtMethod* orig, ArtMethod* copy) { Runtime* runtime = Runtime::Current(); if (UNLIKELY(orig == runtime->GetResolutionMethod())) { copy->SetEntryPointFromQuickCompiledCodePtrSize( - GetOatAddress(quick_resolution_trampoline_offset_), target_ptr_size_); + GetOatAddress(kOatAddressQuickResolutionTrampoline), target_ptr_size_); } else if (UNLIKELY(orig == runtime->GetImtConflictMethod() || orig == runtime->GetImtUnimplementedMethod())) { copy->SetEntryPointFromQuickCompiledCodePtrSize( - GetOatAddress(quick_imt_conflict_trampoline_offset_), target_ptr_size_); + GetOatAddress(kOatAddressQuickIMTConflictTrampoline), target_ptr_size_); } else if (UNLIKELY(orig->IsRuntimeMethod())) { bool found_one = false; for (size_t i = 0; i < static_cast<size_t>(Runtime::kLastCalleeSaveType); ++i) { @@ -1535,7 +1699,7 @@ void ImageWriter::CopyAndFixupMethod(ArtMethod* orig, ArtMethod* copy) { // use results in an AbstractMethodError. We use the interpreter to achieve this. if (UNLIKELY(orig->IsAbstract())) { copy->SetEntryPointFromQuickCompiledCodePtrSize( - GetOatAddress(quick_to_interpreter_bridge_offset_), target_ptr_size_); + GetOatAddress(kOatAddressQuickToInterpreterBridge), target_ptr_size_); } else { bool quick_is_interpreted; const uint8_t* quick_code = GetQuickCode(orig, &quick_is_interpreted); @@ -1546,7 +1710,7 @@ void ImageWriter::CopyAndFixupMethod(ArtMethod* orig, ArtMethod* copy) { // The native method's pointer is set to a stub to lookup via dlsym. // Note this is not the code_ pointer, that is handled above. copy->SetEntryPointFromJniPtrSize( - GetOatAddress(jni_dlsym_lookup_offset_), target_ptr_size_); + GetOatAddress(kOatAddressJNIDlsymLookup), target_ptr_size_); } } } diff --git a/compiler/image_writer.h b/compiler/image_writer.h index 7a2febcea1..120de97620 100644 --- a/compiler/image_writer.h +++ b/compiler/image_writer.h @@ -40,27 +40,42 @@ #include "utils.h" namespace art { +namespace gc { +namespace space { +class ImageSpace; +} // namespace space +} // namespace gc static constexpr int kInvalidImageFd = -1; // Write a Space built during compilation for use during execution. class ImageWriter FINAL { public: - ImageWriter(const CompilerDriver& compiler_driver, uintptr_t image_begin, - bool compile_pic) - : compiler_driver_(compiler_driver), image_begin_(reinterpret_cast<uint8_t*>(image_begin)), - image_end_(0), image_objects_offset_begin_(0), image_roots_address_(0), oat_file_(nullptr), - oat_data_begin_(nullptr), interpreter_to_interpreter_bridge_offset_(0), - interpreter_to_compiled_code_bridge_offset_(0), jni_dlsym_lookup_offset_(0), - quick_generic_jni_trampoline_offset_(0), - quick_imt_conflict_trampoline_offset_(0), quick_resolution_trampoline_offset_(0), - quick_to_interpreter_bridge_offset_(0), compile_pic_(compile_pic), + ImageWriter(const CompilerDriver& compiler_driver, + uintptr_t image_begin, + bool compile_pic, + bool compile_app_image) + : compiler_driver_(compiler_driver), + image_begin_(reinterpret_cast<uint8_t*>(image_begin)), + image_end_(0), + image_objects_offset_begin_(0), + image_roots_address_(0), + oat_file_(nullptr), + oat_data_begin_(nullptr), + compile_pic_(compile_pic), + compile_app_image_(compile_app_image), + boot_image_space_(nullptr), target_ptr_size_(InstructionSetPointerSize(compiler_driver_.GetInstructionSet())), - bin_slot_sizes_(), bin_slot_offsets_(), bin_slot_count_(), - intern_table_bytes_(0u), image_method_array_(ImageHeader::kImageMethodsCount), - dirty_methods_(0u), clean_methods_(0u) { + bin_slot_sizes_(), + bin_slot_offsets_(), + bin_slot_count_(), + intern_table_bytes_(0u), + image_method_array_(ImageHeader::kImageMethodsCount), + dirty_methods_(0u), + clean_methods_(0u) { CHECK_NE(image_begin, 0U); - std::fill(image_methods_, image_methods_ + arraysize(image_methods_), nullptr); + std::fill_n(image_methods_, arraysize(image_methods_), nullptr); + std::fill_n(oat_address_offsets_, arraysize(oat_address_offsets_), 0); } ~ImageWriter() { @@ -74,8 +89,9 @@ class ImageWriter FINAL { template <typename T> T* GetImageAddress(T* object) const SHARED_REQUIRES(Locks::mutator_lock_) { - return object == nullptr ? nullptr : - reinterpret_cast<T*>(image_begin_ + GetImageOffset(object)); + return (object == nullptr || IsInBootImage(object)) + ? object + : reinterpret_cast<T*>(image_begin_ + GetImageOffset(object)); } ArtMethod* GetImageMethodAddress(ArtMethod* method) SHARED_REQUIRES(Locks::mutator_lock_); @@ -150,6 +166,19 @@ class ImageWriter FINAL { }; friend std::ostream& operator<<(std::ostream& stream, const NativeObjectRelocationType& type); + enum OatAddress { + kOatAddressInterpreterToInterpreterBridge, + kOatAddressInterpreterToCompiledCodeBridge, + kOatAddressJNIDlsymLookup, + kOatAddressQuickGenericJNITrampoline, + kOatAddressQuickIMTConflictTrampoline, + kOatAddressQuickResolutionTrampoline, + kOatAddressQuickToInterpreterBridge, + // Number of elements in the enum. + kOatAddressCount, + }; + friend std::ostream& operator<<(std::ostream& stream, const OatAddress& oat_address); + static constexpr size_t kBinBits = MinimumBitsToStore<uint32_t>(kBinMirrorCount - 1); // uint32 = typeof(lockword_) // Subtract read barrier bits since we want these to remain 0, or else it may result in DCHECK @@ -215,7 +244,10 @@ class ImageWriter FINAL { return reinterpret_cast<mirror::Object*>(dst); } - const uint8_t* GetOatAddress(uint32_t offset) const { + // Returns the address in the boot image if we are compiling the app image. + const uint8_t* GetOatAddress(OatAddress type) const; + + const uint8_t* GetOatAddressForOffset(uint32_t offset) const { // With Quick, code is within the OatFile, as there are all in one // .o ELF object. DCHECK_LE(offset, oat_file_->Size()); @@ -224,7 +256,7 @@ class ImageWriter FINAL { } // Returns true if the class was in the original requested image classes list. - bool IsImageClass(mirror::Class* klass) SHARED_REQUIRES(Locks::mutator_lock_); + bool KeepClass(mirror::Class* klass) SHARED_REQUIRES(Locks::mutator_lock_); // Debug aid that list of requested image classes. void DumpImageClasses(); @@ -299,6 +331,11 @@ class ImageWriter FINAL { void AssignMethodOffset(ArtMethod* method, NativeObjectRelocationType type) SHARED_REQUIRES(Locks::mutator_lock_); + bool IsBootClassLoaderNonImageClass(mirror::Class* klass) SHARED_REQUIRES(Locks::mutator_lock_); + + bool ContainsBootClassLoaderNonImageClass(mirror::Class* klass) + SHARED_REQUIRES(Locks::mutator_lock_); + static Bin BinTypeForNativeRelocationType(NativeObjectRelocationType type); uintptr_t NativeOffsetInImage(void* obj); @@ -306,6 +343,13 @@ class ImageWriter FINAL { template <typename T> T* NativeLocationInImage(T* obj); + // Return true of obj is inside of the boot image space. This may only return true if we are + // compiling an app image. + bool IsInBootImage(const void* obj) const; + + // Return true if ptr is within the boot oat file. + bool IsInBootOatFile(const void* ptr) const; + const CompilerDriver& compiler_driver_; // Beginning target image address for the output image. @@ -344,14 +388,14 @@ class ImageWriter FINAL { std::unique_ptr<gc::accounting::ContinuousSpaceBitmap> image_bitmap_; // Offset from oat_data_begin_ to the stubs. - uint32_t interpreter_to_interpreter_bridge_offset_; - uint32_t interpreter_to_compiled_code_bridge_offset_; - uint32_t jni_dlsym_lookup_offset_; - uint32_t quick_generic_jni_trampoline_offset_; - uint32_t quick_imt_conflict_trampoline_offset_; - uint32_t quick_resolution_trampoline_offset_; - uint32_t quick_to_interpreter_bridge_offset_; + uint32_t oat_address_offsets_[kOatAddressCount]; + + // Boolean flags. const bool compile_pic_; + const bool compile_app_image_; + + // Boot image space for fast lookups. + gc::space::ImageSpace* boot_image_space_; // Size of pointers on the target architecture. size_t target_ptr_size_; @@ -388,6 +432,10 @@ class ImageWriter FINAL { uint64_t dirty_methods_; uint64_t clean_methods_; + // Prune class memoization table. + std::unordered_map<mirror::Class*, bool> prune_class_memo_; + + friend class ContainsBootClassLoaderNonImageClassVisitor; friend class FixupClassVisitor; friend class FixupRootVisitor; friend class FixupVisitor; diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc index d520208d32..5f4f47292b 100644 --- a/compiler/jit/jit_compiler.cc +++ b/compiler/jit/jit_compiler.cc @@ -189,13 +189,14 @@ bool JitCompiler::CompileMethod(Thread* self, ArtMethod* method) { } // Do the compilation. - CompiledMethod* compiled_method = nullptr; + JitCodeCache* const code_cache = runtime->GetJit()->GetCodeCache(); + bool success = false; { TimingLogger::ScopedTiming t2("Compiling", &logger); // If we get a request to compile a proxy method, we pass the actual Java method // of that proxy method, as the compiler does not expect a proxy method. ArtMethod* method_to_compile = method->GetInterfaceMethodIfProxy(sizeof(void*)); - compiled_method = compiler_driver_->CompileArtMethod(self, method_to_compile); + success = compiler_driver_->GetCompiler()->JitCompile(self, code_cache, method_to_compile); } // Trim maps to reduce memory usage. @@ -205,105 +206,14 @@ bool JitCompiler::CompileMethod(Thread* self, ArtMethod* method) { runtime->GetArenaPool()->TrimMaps(); } - // Check if we failed compiling. - if (compiled_method == nullptr) { - return false; - } - total_time_ += NanoTime() - start_time; - bool result = false; - const void* code = runtime->GetClassLinker()->GetOatMethodQuickCodeFor(method); - - if (code != nullptr) { - // Already have some compiled code, just use this instead of linking. - // TODO: Fix recompilation. - method->SetEntryPointFromQuickCompiledCode(code); - result = true; - } else { - TimingLogger::ScopedTiming t2("LinkCode", &logger); - if (AddToCodeCache(method, compiled_method)) { - result = true; - } - } - - // Remove the compiled method to save memory. - compiler_driver_->RemoveCompiledMethod( - MethodReference(h_class->GetDexCache()->GetDexFile(), method->GetDexMethodIndex())); runtime->GetJit()->AddTimingLogger(logger); - return result; + return success; } CompilerCallbacks* JitCompiler::GetCompilerCallbacks() const { return callbacks_.get(); } -bool JitCompiler::AddToCodeCache(ArtMethod* method, - const CompiledMethod* compiled_method) { - Runtime* runtime = Runtime::Current(); - JitCodeCache* const code_cache = runtime->GetJit()->GetCodeCache(); - auto const quick_code = compiled_method->GetQuickCode(); - if (quick_code.empty()) { - return false; - } - const auto code_size = quick_code.size(); - Thread* const self = Thread::Current(); - auto const mapping_table = compiled_method->GetMappingTable(); - auto const vmap_table = compiled_method->GetVmapTable(); - auto const gc_map = compiled_method->GetGcMap(); - uint8_t* mapping_table_ptr = nullptr; - uint8_t* vmap_table_ptr = nullptr; - uint8_t* gc_map_ptr = nullptr; - - if (!mapping_table.empty()) { - // Write out pre-header stuff. - mapping_table_ptr = code_cache->AddDataArray( - self, mapping_table.data(), mapping_table.data() + mapping_table.size()); - if (mapping_table_ptr == nullptr) { - return false; // Out of data cache. - } - } - - if (!vmap_table.empty()) { - vmap_table_ptr = code_cache->AddDataArray( - self, vmap_table.data(), vmap_table.data() + vmap_table.size()); - if (vmap_table_ptr == nullptr) { - return false; // Out of data cache. - } - } - - if (!gc_map.empty()) { - gc_map_ptr = code_cache->AddDataArray( - self, gc_map.data(), gc_map.data() + gc_map.size()); - if (gc_map_ptr == nullptr) { - return false; // Out of data cache. - } - } - - uint8_t* const code = code_cache->CommitCode(self, - method, - mapping_table_ptr, - vmap_table_ptr, - gc_map_ptr, - compiled_method->GetFrameSizeInBytes(), - compiled_method->GetCoreSpillMask(), - compiled_method->GetFpSpillMask(), - compiled_method->GetQuickCode().data(), - compiled_method->GetQuickCode().size()); - - if (code == nullptr) { - return false; - } - - const size_t thumb_offset = compiled_method->CodeDelta(); - const uint32_t code_offset = sizeof(OatQuickMethodHeader) + thumb_offset; - VLOG(jit) - << "JIT added " - << PrettyMethod(method) << "@" << method - << " ccache_size=" << PrettySize(code_cache->CodeCacheSize()) << ": " - << reinterpret_cast<void*>(code + code_offset) - << "," << reinterpret_cast<void*>(code + code_offset + code_size); - return true; -} - } // namespace jit } // namespace art diff --git a/compiler/oat_test.cc b/compiler/oat_test.cc index ea3cb667e2..16f641ab56 100644 --- a/compiler/oat_test.cc +++ b/compiler/oat_test.cc @@ -16,6 +16,7 @@ #include "arch/instruction_set_features.h" #include "art_method-inl.h" +#include "base/unix_file/fd_file.h" #include "class_linker.h" #include "common_compiler_test.h" #include "compiled_method.h" @@ -37,6 +38,16 @@ namespace art { +NO_RETURN static void Usage(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + std::string error; + StringAppendV(&error, fmt, ap); + LOG(FATAL) << error; + va_end(ap); + UNREACHABLE(); +} + class OatTest : public CommonCompilerTest { protected: static const bool kCompile = false; // DISABLED_ due to the time to compile libcore @@ -71,6 +82,67 @@ class OatTest : public CommonCompilerTest { CHECK_EQ(0, memcmp(quick_oat_code, &quick_code[0], code_size)); } } + + void SetupCompiler(Compiler::Kind compiler_kind, + InstructionSet insn_set, + const std::vector<std::string>& compiler_options, + /*out*/std::string* error_msg) { + ASSERT_TRUE(error_msg != nullptr); + insn_features_.reset(InstructionSetFeatures::FromVariant(insn_set, "default", error_msg)); + ASSERT_TRUE(insn_features_ != nullptr) << error_msg; + compiler_options_.reset(new CompilerOptions); + for (const std::string& option : compiler_options) { + compiler_options_->ParseCompilerOption(option, Usage); + } + verification_results_.reset(new VerificationResults(compiler_options_.get())); + method_inliner_map_.reset(new DexFileToMethodInlinerMap); + callbacks_.reset(new QuickCompilerCallbacks(verification_results_.get(), + method_inliner_map_.get(), + CompilerCallbacks::CallbackMode::kCompileApp)); + Runtime::Current()->SetCompilerCallbacks(callbacks_.get()); + timer_.reset(new CumulativeLogger("Compilation times")); + compiler_driver_.reset(new CompilerDriver(compiler_options_.get(), + verification_results_.get(), + method_inliner_map_.get(), + compiler_kind, + insn_set, + insn_features_.get(), + false, + nullptr, + nullptr, + nullptr, + 2, + true, + true, + "", + false, + timer_.get(), + -1, + "")); + } + + bool WriteElf(File* file, + const std::vector<const DexFile*>& dex_files, + SafeMap<std::string, std::string>& key_value_store) { + TimingLogger timings("WriteElf", false, false); + OatWriter oat_writer(dex_files, + 42U, + 4096U, + 0, + compiler_driver_.get(), + nullptr, + /*compiling_boot_image*/false, + &timings, + &key_value_store); + return compiler_driver_->WriteElf(GetTestAndroidRoot(), + !kIsTargetBuild, + dex_files, + &oat_writer, + file); + } + + std::unique_ptr<const InstructionSetFeatures> insn_features_; + std::unique_ptr<QuickCompilerCallbacks> callbacks_; }; TEST_F(OatTest, WriteRead) { @@ -80,21 +152,9 @@ TEST_F(OatTest, WriteRead) { // TODO: make selectable. Compiler::Kind compiler_kind = Compiler::kQuick; InstructionSet insn_set = kIsTargetBuild ? kThumb2 : kX86; - std::string error_msg; - std::unique_ptr<const InstructionSetFeatures> insn_features( - InstructionSetFeatures::FromVariant(insn_set, "default", &error_msg)); - ASSERT_TRUE(insn_features.get() != nullptr) << error_msg; - compiler_options_.reset(new CompilerOptions); - verification_results_.reset(new VerificationResults(compiler_options_.get())); - method_inliner_map_.reset(new DexFileToMethodInlinerMap); - timer_.reset(new CumulativeLogger("Compilation times")); - compiler_driver_.reset(new CompilerDriver(compiler_options_.get(), - verification_results_.get(), - method_inliner_map_.get(), - compiler_kind, insn_set, - insn_features.get(), false, nullptr, nullptr, nullptr, - 2, true, true, "", false, timer_.get(), -1, "")); + SetupCompiler(compiler_kind, insn_set, std::vector<std::string>(), /*out*/ &error_msg); + jobject class_loader = nullptr; if (kCompile) { TimingLogger timings2("OatTest::WriteRead", false, false); @@ -105,19 +165,7 @@ TEST_F(OatTest, WriteRead) { ScratchFile tmp; SafeMap<std::string, std::string> key_value_store; key_value_store.Put(OatHeader::kImageLocationKey, "lue.art"); - OatWriter oat_writer(class_linker->GetBootClassPath(), - 42U, - 4096U, - 0, - compiler_driver_.get(), - nullptr, - &timings, - &key_value_store); - bool success = compiler_driver_->WriteElf(GetTestAndroidRoot(), - !kIsTargetBuild, - class_linker->GetBootClassPath(), - &oat_writer, - tmp.GetFile()); + bool success = WriteElf(tmp.GetFile(), class_linker->GetBootClassPath(), key_value_store); ASSERT_TRUE(success); if (kCompile) { // OatWriter strips the code, regenerate to compare @@ -212,4 +260,53 @@ TEST_F(OatTest, OatHeaderIsValid) { ASSERT_FALSE(oat_header->IsValid()); } +TEST_F(OatTest, EmptyTextSection) { + TimingLogger timings("OatTest::EmptyTextSection", false, false); + + // TODO: make selectable. + Compiler::Kind compiler_kind = Compiler::kQuick; + InstructionSet insn_set = kRuntimeISA; + if (insn_set == kArm) insn_set = kThumb2; + std::string error_msg; + std::vector<std::string> compiler_options; + compiler_options.push_back("--compiler-filter=verify-at-runtime"); + SetupCompiler(compiler_kind, insn_set, compiler_options, /*out*/ &error_msg); + + jobject class_loader; + { + ScopedObjectAccess soa(Thread::Current()); + class_loader = LoadDex("Main"); + } + ASSERT_TRUE(class_loader != nullptr); + std::vector<const DexFile*> dex_files = GetDexFiles(class_loader); + ASSERT_TRUE(!dex_files.empty()); + + ClassLinker* const class_linker = Runtime::Current()->GetClassLinker(); + for (const DexFile* dex_file : dex_files) { + ScopedObjectAccess soa(Thread::Current()); + class_linker->RegisterDexFile( + *dex_file, + class_linker->GetOrCreateAllocatorForClassLoader( + soa.Decode<mirror::ClassLoader*>(class_loader))); + } + compiler_driver_->SetDexFilesForOatFile(dex_files); + compiler_driver_->CompileAll(class_loader, dex_files, &timings); + + 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); + ASSERT_TRUE(success); + + std::unique_ptr<OatFile> oat_file(OatFile::Open(tmp.GetFilename(), + tmp.GetFilename(), + nullptr, + nullptr, + false, + nullptr, + &error_msg)); + ASSERT_TRUE(oat_file != nullptr); + EXPECT_LT(static_cast<size_t>(oat_file->Size()), static_cast<size_t>(tmp.GetFile()->GetLength())); +} + } // namespace art diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc index c7b8884214..3f2271ef11 100644 --- a/compiler/oat_writer.cc +++ b/compiler/oat_writer.cc @@ -65,10 +65,12 @@ OatWriter::OatWriter(const std::vector<const DexFile*>& dex_files, int32_t image_patch_delta, const CompilerDriver* compiler, ImageWriter* image_writer, + bool compiling_boot_image, TimingLogger* timings, SafeMap<std::string, std::string>* key_value_store) : compiler_driver_(compiler), image_writer_(image_writer), + compiling_boot_image_(compiling_boot_image), dex_files_(&dex_files), size_(0u), bss_size_(0u), @@ -113,7 +115,9 @@ OatWriter::OatWriter(const std::vector<const DexFile*>& dex_files, size_oat_lookup_table_(0), method_offset_map_() { CHECK(key_value_store != nullptr); - + if (compiling_boot_image) { + CHECK(image_writer != nullptr); + } InstructionSet instruction_set = compiler_driver_->GetInstructionSet(); const InstructionSetFeatures* features = compiler_driver_->GetInstructionSetFeatures(); relative_patcher_ = linker::RelativePatcher::Create(instruction_set, features, @@ -154,7 +158,7 @@ OatWriter::OatWriter(const std::vector<const DexFile*>& dex_files, } size_ = offset; - if (!HasImage()) { + if (!HasBootImage()) { // Allocate space for app dex cache arrays in the .bss section. size_t bss_start = RoundUp(size_, kPageSize); size_t pointer_size = GetInstructionSetPointerSize(instruction_set); @@ -167,9 +171,10 @@ OatWriter::OatWriter(const std::vector<const DexFile*>& dex_files, } CHECK_EQ(dex_files_->size(), oat_dex_files_.size()); - CHECK_EQ(compiler->IsImage(), image_writer_ != nullptr); - CHECK_EQ(compiler->IsImage(), - key_value_store_->find(OatHeader::kImageLocationKey) == key_value_store_->end()); + if (compiling_boot_image_) { + CHECK_EQ(image_writer_ != nullptr, + key_value_store_->find(OatHeader::kImageLocationKey) == key_value_store_->end()); + } CHECK_ALIGNED(image_patch_delta_, kPageSize); } @@ -672,7 +677,7 @@ class OatWriter::WriteCodeMethodVisitor : public OatDexMethodVisitor { class_linker_(Runtime::Current()->GetClassLinker()), dex_cache_(nullptr) { patched_code_.reserve(16 * KB); - if (writer_->HasImage()) { + if (writer_->HasBootImage()) { // If we're creating the image, the address space must be ready so that we can apply patches. CHECK(writer_->image_writer_->IsImageAddressSpaceReady()); } @@ -855,7 +860,7 @@ class OatWriter::WriteCodeMethodVisitor : public OatDexMethodVisitor { } uint32_t GetDexCacheOffset(const LinkerPatch& patch) SHARED_REQUIRES(Locks::mutator_lock_) { - if (writer_->HasImage()) { + if (writer_->HasBootImage()) { auto* element = writer_->image_writer_->GetDexCacheArrayElementImageAddress<const uint8_t*>( patch.TargetDexCacheDexFile(), patch.TargetDexCacheElementOffset()); const uint8_t* oat_data = writer_->image_writer_->GetOatFileBegin() + file_offset_; @@ -868,7 +873,7 @@ class OatWriter::WriteCodeMethodVisitor : public OatDexMethodVisitor { void PatchObjectAddress(std::vector<uint8_t>* code, uint32_t offset, mirror::Object* object) SHARED_REQUIRES(Locks::mutator_lock_) { - if (writer_->HasImage()) { + if (writer_->HasBootImage()) { object = writer_->image_writer_->GetImageAddress(object); } else { // NOTE: We're using linker patches for app->boot references when the image can @@ -888,7 +893,7 @@ class OatWriter::WriteCodeMethodVisitor : public OatDexMethodVisitor { void PatchMethodAddress(std::vector<uint8_t>* code, uint32_t offset, ArtMethod* method) SHARED_REQUIRES(Locks::mutator_lock_) { - if (writer_->HasImage()) { + if (writer_->HasBootImage()) { method = writer_->image_writer_->GetImageMethodAddress(method); } else if (kIsDebugBuild) { // NOTE: We're using linker patches for app->boot references when the image can @@ -911,7 +916,7 @@ class OatWriter::WriteCodeMethodVisitor : public OatDexMethodVisitor { void PatchCodeAddress(std::vector<uint8_t>* code, uint32_t offset, uint32_t target_offset) SHARED_REQUIRES(Locks::mutator_lock_) { uint32_t address = target_offset; - if (writer_->HasImage()) { + if (writer_->HasBootImage()) { address = PointerToLowMemUInt32(writer_->image_writer_->GetOatFileBegin() + writer_->oat_data_offset_ + target_offset); } @@ -1123,7 +1128,7 @@ size_t OatWriter::InitOatCode(size_t offset) { offset = RoundUp(offset, kPageSize); oat_header_->SetExecutableOffset(offset); size_executable_offset_alignment_ = offset - old_offset; - if (compiler_driver_->IsImage()) { + if (compiler_driver_->IsBootImage()) { CHECK_EQ(image_patch_delta_, 0); InstructionSet instruction_set = compiler_driver_->GetInstructionSet(); @@ -1164,7 +1169,7 @@ size_t OatWriter::InitOatCodeDexFiles(size_t offset) { } while (false) VISIT(InitCodeMethodVisitor); - if (compiler_driver_->IsImage()) { + if (compiler_driver_->IsBootImage()) { VISIT(InitImageMethodVisitor); } @@ -1408,7 +1413,7 @@ size_t OatWriter::WriteMaps(OutputStream* out, const size_t file_offset, size_t } size_t OatWriter::WriteCode(OutputStream* out, const size_t file_offset, size_t relative_offset) { - if (compiler_driver_->IsImage()) { + if (compiler_driver_->IsBootImage()) { InstructionSet instruction_set = compiler_driver_->GetInstructionSet(); #define DO_TRAMPOLINE(field) \ diff --git a/compiler/oat_writer.h b/compiler/oat_writer.h index f2fe048174..7027434cca 100644 --- a/compiler/oat_writer.h +++ b/compiler/oat_writer.h @@ -93,6 +93,7 @@ class OatWriter { int32_t image_patch_delta, const CompilerDriver* compiler, ImageWriter* image_writer, + bool compiling_boot_image, TimingLogger* timings, SafeMap<std::string, std::string>* key_value_store); @@ -103,6 +104,10 @@ class OatWriter { return image_writer_ != nullptr; } + bool HasBootImage() const { + return compiling_boot_image_; + } + const OatHeader& GetOatHeader() const { return *oat_header_; } @@ -279,6 +284,7 @@ class OatWriter { const CompilerDriver* const compiler_driver_; ImageWriter* const image_writer_; + const bool compiling_boot_image_; // note OatFile does not take ownership of the DexFiles const std::vector<const DexFile*>* dex_files_; diff --git a/compiler/optimizing/bounds_check_elimination.cc b/compiler/optimizing/bounds_check_elimination.cc index bcc32403d3..cca0baf274 100644 --- a/compiler/optimizing/bounds_check_elimination.cc +++ b/compiler/optimizing/bounds_check_elimination.cc @@ -1169,8 +1169,10 @@ class BCEVisitor : public HGraphVisitor { // Return the range resulting from induction variable analysis of "instruction" when the value // is used from "context", for example, an index used from a bounds-check inside a loop body. ValueRange* LookupInductionRange(HInstruction* context, HInstruction* instruction) { - InductionVarRange::Value v1 = induction_range_.GetMinInduction(context, instruction); - InductionVarRange::Value v2 = induction_range_.GetMaxInduction(context, instruction); + InductionVarRange::Value v1; + InductionVarRange::Value v2; + bool needs_finite_test = false; + induction_range_.GetInductionRange(context, instruction, &v1, &v2, &needs_finite_test); if (v1.is_known && (v1.a_constant == 0 || v1.a_constant == 1) && v2.is_known && (v2.a_constant == 0 || v2.a_constant == 1)) { DCHECK(v1.a_constant == 1 || v1.instruction == nullptr); diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc index a1bb5e0838..ce92470868 100644 --- a/compiler/optimizing/code_generator.cc +++ b/compiler/optimizing/code_generator.cc @@ -42,7 +42,7 @@ #include "compiled_method.h" #include "dex/verified_method.h" -#include "driver/dex_compilation_unit.h" +#include "driver/compiler_driver.h" #include "gc_map_builder.h" #include "graph_visualizer.h" #include "intrinsics.h" @@ -787,9 +787,10 @@ CodeGenerator* CodeGenerator::Create(HGraph* graph, } void CodeGenerator::BuildNativeGCMap( - ArenaVector<uint8_t>* data, const DexCompilationUnit& dex_compilation_unit) const { + ArenaVector<uint8_t>* data, const CompilerDriver& compiler_driver) const { const std::vector<uint8_t>& gc_map_raw = - dex_compilation_unit.GetVerifiedMethod()->GetDexGcMap(); + compiler_driver.GetVerifiedMethod(&GetGraph()->GetDexFile(), GetGraph()->GetMethodIdx()) + ->GetDexGcMap(); verifier::DexPcToReferenceMap dex_gc_map(&(gc_map_raw)[0]); uint32_t max_native_offset = stack_map_stream_.ComputeMaxNativePcOffset(); @@ -911,19 +912,22 @@ void CodeGenerator::BuildVMapTable(ArenaVector<uint8_t>* data) const { vmap_encoder.PushBackUnsigned(VmapTable::kAdjustedFpMarker); } -void CodeGenerator::BuildStackMaps(ArenaVector<uint8_t>* data) { - uint32_t size = stack_map_stream_.PrepareForFillIn(); - data->resize(size); - MemoryRegion region(data->data(), size); +size_t CodeGenerator::ComputeStackMapsSize() { + return stack_map_stream_.PrepareForFillIn(); +} + +void CodeGenerator::BuildStackMaps(MemoryRegion region) { stack_map_stream_.FillIn(region); } void CodeGenerator::RecordNativeDebugInfo(uint32_t dex_pc, uintptr_t native_pc_begin, uintptr_t native_pc_end) { - if (src_map_ != nullptr && dex_pc != kNoDexPc && native_pc_begin != native_pc_end) { - src_map_->push_back(SrcMapElem({static_cast<uint32_t>(native_pc_begin), - static_cast<int32_t>(dex_pc)})); + if (compiler_options_.GetGenerateDebugInfo() && + dex_pc != kNoDexPc && + native_pc_begin != native_pc_end) { + src_map_.push_back(SrcMapElem({static_cast<uint32_t>(native_pc_begin), + static_cast<int32_t>(dex_pc)})); } } diff --git a/compiler/optimizing/code_generator.h b/compiler/optimizing/code_generator.h index 47b6f30450..a92014dc79 100644 --- a/compiler/optimizing/code_generator.h +++ b/compiler/optimizing/code_generator.h @@ -22,6 +22,7 @@ #include "base/arena_containers.h" #include "base/arena_object.h" #include "base/bit_field.h" +#include "compiled_method.h" #include "driver/compiler_options.h" #include "globals.h" #include "graph_visualizer.h" @@ -51,13 +52,9 @@ static int64_t constexpr kPrimLongMax = INT64_C(0x7fffffffffffffff); class Assembler; class CodeGenerator; -class DexCompilationUnit; +class CompilerDriver; class LinkerPatch; class ParallelMoveResolver; -class SrcMapElem; -template <class Alloc> -class SrcMap; -using DefaultSrcMap = SrcMap<std::allocator<SrcMapElem>>; class CodeAllocator { public: @@ -284,13 +281,12 @@ class CodeGenerator { slow_paths_.push_back(slow_path); } - void SetSrcMap(DefaultSrcMap* src_map) { src_map_ = src_map; } - void BuildMappingTable(ArenaVector<uint8_t>* vector) const; void BuildVMapTable(ArenaVector<uint8_t>* vector) const; void BuildNativeGCMap( - ArenaVector<uint8_t>* vector, const DexCompilationUnit& dex_compilation_unit) const; - void BuildStackMaps(ArenaVector<uint8_t>* vector); + ArenaVector<uint8_t>* vector, const CompilerDriver& compiler_driver) const; + void BuildStackMaps(MemoryRegion region); + size_t ComputeStackMapsSize(); bool IsBaseline() const { return is_baseline_; @@ -446,6 +442,10 @@ class CodeGenerator { // Copy the result of a call into the given target. virtual void MoveFromReturnRegister(Location trg, Primitive::Type type) = 0; + const ArenaVector<SrcMapElem>& GetSrcMappingTable() const { + return src_map_; + } + protected: // Method patch info used for recording locations of required linker patches and // target methods. The target method can be used for various purposes, whether for @@ -488,7 +488,7 @@ class CodeGenerator { stats_(stats), graph_(graph), compiler_options_(compiler_options), - src_map_(nullptr), + src_map_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)), slow_paths_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)), current_block_index_(0), is_leaf_(true), @@ -602,7 +602,7 @@ class CodeGenerator { const CompilerOptions& compiler_options_; // Native to dex_pc map used for native debugging/profiling tools. - DefaultSrcMap* src_map_; + ArenaVector<SrcMapElem> src_map_; ArenaVector<SlowPathCode*> slow_paths_; // The current block index in `block_order_` of the block diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc index 3dc3b7fba0..6d05293277 100644 --- a/compiler/optimizing/code_generator_arm.cc +++ b/compiler/optimizing/code_generator_arm.cc @@ -1300,20 +1300,29 @@ void InstructionCodeGeneratorARM::GenerateTestAndBranch(HInstruction* instructio DCHECK_EQ(cond_value, 0); } } else { - if (!cond->IsCondition() || cond->AsCondition()->NeedsMaterialization()) { - // Condition has been materialized, compare the output to 0 + // Can we optimize the jump if we know that the next block is the true case? + HCondition* condition = cond->AsCondition(); + bool can_jump_to_false = CanReverseCondition(always_true_target, false_target, condition); + if (condition == nullptr || condition->NeedsMaterialization()) { + // Condition has been materialized, compare the output to 0. DCHECK(instruction->GetLocations()->InAt(0).IsRegister()); + if (can_jump_to_false) { + __ CompareAndBranchIfZero(instruction->GetLocations()->InAt(0).AsRegister<Register>(), + false_target); + return; + } __ CompareAndBranchIfNonZero(instruction->GetLocations()->InAt(0).AsRegister<Register>(), true_target); } else { // Condition has not been materialized, use its inputs as the // comparison and its condition as the branch condition. - Primitive::Type type = - cond->IsCondition() ? cond->InputAt(0)->GetType() : Primitive::kPrimInt; + Primitive::Type type = (condition != nullptr) + ? cond->InputAt(0)->GetType() + : Primitive::kPrimInt; // Is this a long or FP comparison that has been folded into the HCondition? if (type == Primitive::kPrimLong || Primitive::IsFloatingPointType(type)) { // Generate the comparison directly. - GenerateCompareTestAndBranch(instruction->AsIf(), cond->AsCondition(), + GenerateCompareTestAndBranch(instruction->AsIf(), condition, true_target, false_target, always_true_target); return; } @@ -1328,7 +1337,12 @@ void InstructionCodeGeneratorARM::GenerateTestAndBranch(HInstruction* instructio DCHECK(right.IsConstant()); GenerateCompareWithImmediate(left, CodeGenerator::GetInt32ValueOf(right.GetConstant())); } - __ b(true_target, ARMCondition(cond->AsCondition()->GetCondition())); + if (can_jump_to_false) { + __ b(false_target, ARMCondition(condition->GetOppositeCondition())); + return; + } + + __ b(true_target, ARMCondition(condition->GetCondition())); } } if (false_target != nullptr) { diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc index 8106499c02..959adb4238 100644 --- a/compiler/optimizing/code_generator_mips.cc +++ b/compiler/optimizing/code_generator_mips.cc @@ -40,12 +40,8 @@ static constexpr int kCurrentMethodStackOffset = 0; static constexpr Register kMethodRegisterArgument = A0; // We need extra temporary/scratch registers (in addition to AT) in some cases. -static constexpr Register TMP = T8; static constexpr FRegister FTMP = F8; -// ART Thread Register. -static constexpr Register TR = S1; - Location MipsReturnLocation(Primitive::Type return_type) { switch (return_type) { case Primitive::kPrimBoolean: diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc index 55efd5f9de..d4fcaf9321 100644 --- a/compiler/optimizing/code_generator_mips64.cc +++ b/compiler/optimizing/code_generator_mips64.cc @@ -2659,14 +2659,10 @@ void InstructionCodeGeneratorMIPS64::VisitInvokeStaticOrDirect(HInvokeStaticOrDi codegen_->RecordPcInfo(invoke, invoke->GetDexPc()); } -void InstructionCodeGeneratorMIPS64::VisitInvokeVirtual(HInvokeVirtual* invoke) { - if (TryGenerateIntrinsicCode(invoke, codegen_)) { - return; - } - +void CodeGeneratorMIPS64::GenerateVirtualCall(HInvokeVirtual* invoke, Location temp_location) { LocationSummary* locations = invoke->GetLocations(); Location receiver = locations->InAt(0); - GpuRegister temp = invoke->GetLocations()->GetTemp(0).AsRegister<GpuRegister>(); + GpuRegister temp = temp_location.AsRegister<GpuRegister>(); size_t method_offset = mirror::Class::EmbeddedVTableEntryOffset( invoke->GetVTableIndex(), kMips64PointerSize).SizeValue(); uint32_t class_offset = mirror::Object::ClassOffset().Int32Value(); @@ -2675,13 +2671,21 @@ void InstructionCodeGeneratorMIPS64::VisitInvokeVirtual(HInvokeVirtual* invoke) // temp = object->GetClass(); DCHECK(receiver.IsRegister()); __ LoadFromOffset(kLoadUnsignedWord, temp, receiver.AsRegister<GpuRegister>(), class_offset); - codegen_->MaybeRecordImplicitNullCheck(invoke); + MaybeRecordImplicitNullCheck(invoke); // temp = temp->GetMethodAt(method_offset); __ LoadFromOffset(kLoadDoubleword, temp, temp, method_offset); // T9 = temp->GetEntryPoint(); __ LoadFromOffset(kLoadDoubleword, T9, temp, entry_point.Int32Value()); // T9(); __ Jalr(T9); +} + +void InstructionCodeGeneratorMIPS64::VisitInvokeVirtual(HInvokeVirtual* invoke) { + if (TryGenerateIntrinsicCode(invoke, codegen_)) { + return; + } + + codegen_->GenerateVirtualCall(invoke, invoke->GetLocations()->GetTemp(0)); DCHECK(!codegen_->IsLeafMethod()); codegen_->RecordPcInfo(invoke, invoke->GetDexPc()); } diff --git a/compiler/optimizing/code_generator_mips64.h b/compiler/optimizing/code_generator_mips64.h index 9bbd02759a..4f91c7179f 100644 --- a/compiler/optimizing/code_generator_mips64.h +++ b/compiler/optimizing/code_generator_mips64.h @@ -333,10 +333,7 @@ class CodeGeneratorMIPS64 : public CodeGenerator { MethodReference target_method) OVERRIDE; void GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) OVERRIDE; - void GenerateVirtualCall(HInvokeVirtual* invoke ATTRIBUTE_UNUSED, - Location temp ATTRIBUTE_UNUSED) OVERRIDE { - UNIMPLEMENTED(FATAL); - } + void GenerateVirtualCall(HInvokeVirtual* invoke, Location temp) OVERRIDE; void MoveFromReturnRegister(Location trg ATTRIBUTE_UNUSED, Primitive::Type type ATTRIBUTE_UNUSED) OVERRIDE { diff --git a/compiler/optimizing/code_generator_utils.cc b/compiler/optimizing/code_generator_utils.cc index 921c1d86c2..bf354e7ee2 100644 --- a/compiler/optimizing/code_generator_utils.cc +++ b/compiler/optimizing/code_generator_utils.cc @@ -15,6 +15,7 @@ */ #include "code_generator_utils.h" +#include "nodes.h" #include "base/logging.h" @@ -94,4 +95,19 @@ void CalculateMagicAndShiftForDivRem(int64_t divisor, bool is_long, *shift = is_long ? p - 64 : p - 32; } +// Is it valid to reverse the condition? Uses the values supplied to +// GenerateTestAndBranch() in instruction generators. +bool CanReverseCondition(Label* always_true_target, + Label* false_target, + HCondition* condition) { + // 'always_true_target' is null when the 'true' path is to the next + // block to be generated. Check the type of the condition to ensure that + // FP conditions are not swapped. This is for future fusing of HCompare and + // HCondition. + // Note: If the condition is nullptr, then it is always okay to reverse. + return always_true_target == nullptr && false_target != nullptr && + (condition == nullptr || + !Primitive::IsFloatingPointType(condition->InputAt(0)->GetType())); +} + } // namespace art diff --git a/compiler/optimizing/code_generator_utils.h b/compiler/optimizing/code_generator_utils.h index 59b495c2c9..628eee8885 100644 --- a/compiler/optimizing/code_generator_utils.h +++ b/compiler/optimizing/code_generator_utils.h @@ -21,10 +21,19 @@ namespace art { +class Label; +class HCondition; + // Computes the magic number and the shift needed in the div/rem by constant algorithm, as out // arguments `magic` and `shift` void CalculateMagicAndShiftForDivRem(int64_t divisor, bool is_long, int64_t* magic, int* shift); +// Is it valid to reverse the condition? Uses the values supplied to +// GenerateTestAndBranch() in instruction generators. +bool CanReverseCondition(Label* always_true_target, + Label* false_target, + HCondition* condition); + } // namespace art #endif // ART_COMPILER_OPTIMIZING_CODE_GENERATOR_UTILS_H_ diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc index 0df7e3b30a..8308d9ee20 100644 --- a/compiler/optimizing/code_generator_x86.cc +++ b/compiler/optimizing/code_generator_x86.cc @@ -1216,16 +1216,21 @@ void InstructionCodeGeneratorX86::GenerateTestAndBranch(HInstruction* instructio DCHECK_EQ(cond_value, 0); } } else { + HCondition* condition = cond->AsCondition(); bool is_materialized = - !cond->IsCondition() || cond->AsCondition()->NeedsMaterialization(); + condition == nullptr || condition->NeedsMaterialization(); // Moves do not affect the eflags register, so if the condition is // evaluated just before the if, we don't need to evaluate it // again. We can't use the eflags on long/FP conditions if they are // materialized due to the complex branching. - Primitive::Type type = cond->IsCondition() ? cond->InputAt(0)->GetType() : Primitive::kPrimInt; - bool eflags_set = cond->IsCondition() - && cond->AsCondition()->IsBeforeWhenDisregardMoves(instruction) + Primitive::Type type = (condition != nullptr) + ? cond->InputAt(0)->GetType() + : Primitive::kPrimInt; + bool eflags_set = condition != nullptr + && condition->IsBeforeWhenDisregardMoves(instruction) && (type != Primitive::kPrimLong && !Primitive::IsFloatingPointType(type)); + // Can we optimize the jump if we know that the next block is the true case? + bool can_jump_to_false = CanReverseCondition(always_true_target, false_target, condition); if (is_materialized) { if (!eflags_set) { // Materialized condition, compare against 0. @@ -1235,9 +1240,17 @@ void InstructionCodeGeneratorX86::GenerateTestAndBranch(HInstruction* instructio } else { __ cmpl(Address(ESP, lhs.GetStackIndex()), Immediate(0)); } + if (can_jump_to_false) { + __ j(kEqual, false_target); + return; + } __ j(kNotEqual, true_target); } else { - __ j(X86Condition(cond->AsCondition()->GetCondition()), true_target); + if (can_jump_to_false) { + __ j(X86Condition(condition->GetOppositeCondition()), false_target); + return; + } + __ j(X86Condition(condition->GetCondition()), true_target); } } else { // Condition has not been materialized, use its inputs as the @@ -1247,7 +1260,7 @@ void InstructionCodeGeneratorX86::GenerateTestAndBranch(HInstruction* instructio if (type == Primitive::kPrimLong || Primitive::IsFloatingPointType(type)) { // Generate the comparison directly. GenerateCompareTestAndBranch(instruction->AsIf(), - cond->AsCondition(), + condition, true_target, false_target, always_true_target); @@ -1270,7 +1283,13 @@ void InstructionCodeGeneratorX86::GenerateTestAndBranch(HInstruction* instructio } else { __ cmpl(lhs.AsRegister<Register>(), Address(ESP, rhs.GetStackIndex())); } - __ j(X86Condition(cond->AsCondition()->GetCondition()), true_target); + + if (can_jump_to_false) { + __ j(X86Condition(condition->GetOppositeCondition()), false_target); + return; + } + + __ j(X86Condition(condition->GetCondition()), true_target); } } if (false_target != nullptr) { @@ -4043,16 +4062,16 @@ void LocationsBuilderX86::HandleFieldSet(HInstruction* instruction, const FieldI // Ensure the value is in a byte register. locations->SetInAt(1, Location::RegisterLocation(EAX)); } else if (Primitive::IsFloatingPointType(field_type)) { - locations->SetInAt(1, Location::RequiresFpuRegister()); - } else { + if (is_volatile && field_type == Primitive::kPrimDouble) { + // In order to satisfy the semantics of volatile, this must be a single instruction store. + locations->SetInAt(1, Location::RequiresFpuRegister()); + } else { + locations->SetInAt(1, Location::FpuRegisterOrConstant(instruction->InputAt(1))); + } + } else if (is_volatile && field_type == Primitive::kPrimLong) { + // In order to satisfy the semantics of volatile, this must be a single instruction store. locations->SetInAt(1, Location::RequiresRegister()); - } - if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(1))) { - // Temporary registers for the write barrier. - locations->AddTemp(Location::RequiresRegister()); // Possibly used for reference poisoning too. - // Ensure the card is in a byte register. - locations->AddTemp(Location::RegisterLocation(ECX)); - } else if (is_volatile && (field_type == Primitive::kPrimLong)) { + // 64bits value can be atomically written to an address with movsd and an XMM register. // We need two XMM registers because there's no easier way to (bit) copy a register pair // into a single XMM register (we copy each pair part into the XMMs and then interleave them). @@ -4060,6 +4079,15 @@ void LocationsBuilderX86::HandleFieldSet(HInstruction* instruction, const FieldI // isolated cases when we need this it isn't worth adding the extra complexity. locations->AddTemp(Location::RequiresFpuRegister()); locations->AddTemp(Location::RequiresFpuRegister()); + } else { + locations->SetInAt(1, Location::RegisterOrConstant(instruction->InputAt(1))); + + if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(1))) { + // Temporary registers for the write barrier. + locations->AddTemp(Location::RequiresRegister()); // May be used for reference poisoning too. + // Ensure the card is in a byte register. + locations->AddTemp(Location::RegisterLocation(ECX)); + } } } @@ -4081,6 +4109,8 @@ void InstructionCodeGeneratorX86::HandleFieldSet(HInstruction* instruction, GenerateMemoryBarrier(MemBarrierKind::kAnyStore); } + bool maybe_record_implicit_null_check_done = false; + switch (field_type) { case Primitive::kPrimBoolean: case Primitive::kPrimByte: { @@ -4090,7 +4120,12 @@ void InstructionCodeGeneratorX86::HandleFieldSet(HInstruction* instruction, case Primitive::kPrimShort: case Primitive::kPrimChar: { - __ movw(Address(base, offset), value.AsRegister<Register>()); + if (value.IsConstant()) { + int16_t v = CodeGenerator::GetInt32ValueOf(value.GetConstant()); + __ movw(Address(base, offset), Immediate(v)); + } else { + __ movw(Address(base, offset), value.AsRegister<Register>()); + } break; } @@ -4105,6 +4140,9 @@ void InstructionCodeGeneratorX86::HandleFieldSet(HInstruction* instruction, __ movl(temp, value.AsRegister<Register>()); __ PoisonHeapReference(temp); __ movl(Address(base, offset), temp); + } else if (value.IsConstant()) { + int32_t v = CodeGenerator::GetInt32ValueOf(value.GetConstant()); + __ movl(Address(base, offset), Immediate(v)); } else { __ movl(Address(base, offset), value.AsRegister<Register>()); } @@ -4120,21 +4158,40 @@ void InstructionCodeGeneratorX86::HandleFieldSet(HInstruction* instruction, __ punpckldq(temp1, temp2); __ movsd(Address(base, offset), temp1); codegen_->MaybeRecordImplicitNullCheck(instruction); + } else if (value.IsConstant()) { + int64_t v = CodeGenerator::GetInt64ValueOf(value.GetConstant()); + __ movl(Address(base, offset), Immediate(Low32Bits(v))); + codegen_->MaybeRecordImplicitNullCheck(instruction); + __ movl(Address(base, kX86WordSize + offset), Immediate(High32Bits(v))); } else { __ movl(Address(base, offset), value.AsRegisterPairLow<Register>()); codegen_->MaybeRecordImplicitNullCheck(instruction); __ movl(Address(base, kX86WordSize + offset), value.AsRegisterPairHigh<Register>()); } + maybe_record_implicit_null_check_done = true; break; } case Primitive::kPrimFloat: { - __ movss(Address(base, offset), value.AsFpuRegister<XmmRegister>()); + if (value.IsConstant()) { + int32_t v = CodeGenerator::GetInt32ValueOf(value.GetConstant()); + __ movl(Address(base, offset), Immediate(v)); + } else { + __ movss(Address(base, offset), value.AsFpuRegister<XmmRegister>()); + } break; } case Primitive::kPrimDouble: { - __ movsd(Address(base, offset), value.AsFpuRegister<XmmRegister>()); + if (value.IsConstant()) { + int64_t v = CodeGenerator::GetInt64ValueOf(value.GetConstant()); + __ movl(Address(base, offset), Immediate(Low32Bits(v))); + codegen_->MaybeRecordImplicitNullCheck(instruction); + __ movl(Address(base, kX86WordSize + offset), Immediate(High32Bits(v))); + maybe_record_implicit_null_check_done = true; + } else { + __ movsd(Address(base, offset), value.AsFpuRegister<XmmRegister>()); + } break; } @@ -4143,8 +4200,7 @@ void InstructionCodeGeneratorX86::HandleFieldSet(HInstruction* instruction, UNREACHABLE(); } - // Longs are handled in the switch. - if (field_type != Primitive::kPrimLong) { + if (!maybe_record_implicit_null_check_done) { codegen_->MaybeRecordImplicitNullCheck(instruction); } @@ -4481,7 +4537,7 @@ void LocationsBuilderX86::VisitArraySet(HArraySet* instruction) { // Ensure the value is in a byte register. locations->SetInAt(2, Location::ByteRegisterOrConstant(EAX, instruction->InputAt(2))); } else if (Primitive::IsFloatingPointType(value_type)) { - locations->SetInAt(2, Location::RequiresFpuRegister()); + locations->SetInAt(2, Location::FpuRegisterOrConstant(instruction->InputAt(2))); } else { locations->SetInAt(2, Location::RegisterOrConstant(instruction->InputAt(2))); } @@ -4667,8 +4723,14 @@ void InstructionCodeGeneratorX86::VisitArraySet(HArraySet* instruction) { Address address = index.IsConstant() ? Address(array, (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_4) + offset) : Address(array, index.AsRegister<Register>(), TIMES_4, offset); - DCHECK(value.IsFpuRegister()); - __ movss(address, value.AsFpuRegister<XmmRegister>()); + if (value.IsFpuRegister()) { + __ movss(address, value.AsFpuRegister<XmmRegister>()); + } else { + DCHECK(value.IsConstant()); + int32_t v = bit_cast<int32_t, float>(value.GetConstant()->AsFloatConstant()->GetValue()); + __ movl(address, Immediate(v)); + } + codegen_->MaybeRecordImplicitNullCheck(instruction); break; } @@ -4677,8 +4739,19 @@ void InstructionCodeGeneratorX86::VisitArraySet(HArraySet* instruction) { Address address = index.IsConstant() ? Address(array, (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_8) + offset) : Address(array, index.AsRegister<Register>(), TIMES_8, offset); - DCHECK(value.IsFpuRegister()); - __ movsd(address, value.AsFpuRegister<XmmRegister>()); + if (value.IsFpuRegister()) { + __ movsd(address, value.AsFpuRegister<XmmRegister>()); + } else { + DCHECK(value.IsConstant()); + Address address_hi = index.IsConstant() ? + Address(array, (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_8) + + offset + kX86WordSize) : + Address(array, index.AsRegister<Register>(), TIMES_8, offset + kX86WordSize); + int64_t v = bit_cast<int64_t, double>(value.GetConstant()->AsDoubleConstant()->GetValue()); + __ movl(address, Immediate(Low32Bits(v))); + codegen_->MaybeRecordImplicitNullCheck(instruction); + __ movl(address_hi, Immediate(High32Bits(v))); + } break; } diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc index 5218d70995..ee8a299c5e 100644 --- a/compiler/optimizing/code_generator_x86_64.cc +++ b/compiler/optimizing/code_generator_x86_64.cc @@ -1183,16 +1183,20 @@ void InstructionCodeGeneratorX86_64::GenerateTestAndBranch(HInstruction* instruc DCHECK_EQ(cond_value, 0); } } else { - bool is_materialized = - !cond->IsCondition() || cond->AsCondition()->NeedsMaterialization(); + HCondition* condition = cond->AsCondition(); + bool is_materialized = condition == nullptr || condition->NeedsMaterialization(); // Moves do not affect the eflags register, so if the condition is // evaluated just before the if, we don't need to evaluate it // again. We can't use the eflags on FP conditions if they are // materialized due to the complex branching. - Primitive::Type type = cond->IsCondition() ? cond->InputAt(0)->GetType() : Primitive::kPrimInt; - bool eflags_set = cond->IsCondition() - && cond->AsCondition()->IsBeforeWhenDisregardMoves(instruction) + Primitive::Type type = (condition != nullptr) + ? cond->InputAt(0)->GetType() + : Primitive::kPrimInt; + bool eflags_set = condition != nullptr + && condition->IsBeforeWhenDisregardMoves(instruction) && !Primitive::IsFloatingPointType(type); + // Can we optimize the jump if we know that the next block is the true case? + bool can_jump_to_false = CanReverseCondition(always_true_target, false_target, condition); if (is_materialized) { if (!eflags_set) { @@ -1204,9 +1208,17 @@ void InstructionCodeGeneratorX86_64::GenerateTestAndBranch(HInstruction* instruc __ cmpl(Address(CpuRegister(RSP), lhs.GetStackIndex()), Immediate(0)); } + if (can_jump_to_false) { + __ j(kEqual, false_target); + return; + } __ j(kNotEqual, true_target); } else { - __ j(X86_64IntegerCondition(cond->AsCondition()->GetCondition()), true_target); + if (can_jump_to_false) { + __ j(X86_64IntegerCondition(condition->GetOppositeCondition()), false_target); + return; + } + __ j(X86_64IntegerCondition(condition->GetCondition()), true_target); } } else { // Condition has not been materialized, use its inputs as the @@ -1215,7 +1227,7 @@ void InstructionCodeGeneratorX86_64::GenerateTestAndBranch(HInstruction* instruc // Is this a long or FP comparison that has been folded into the HCondition? if (type == Primitive::kPrimLong || Primitive::IsFloatingPointType(type)) { // Generate the comparison directly. - GenerateCompareTestAndBranch(instruction->AsIf(), cond->AsCondition(), + GenerateCompareTestAndBranch(instruction->AsIf(), condition, true_target, false_target, always_true_target); return; } @@ -1235,7 +1247,13 @@ void InstructionCodeGeneratorX86_64::GenerateTestAndBranch(HInstruction* instruc __ cmpl(lhs.AsRegister<CpuRegister>(), Address(CpuRegister(RSP), rhs.GetStackIndex())); } - __ j(X86_64IntegerCondition(cond->AsCondition()->GetCondition()), true_target); + + if (can_jump_to_false) { + __ j(X86_64IntegerCondition(condition->GetOppositeCondition()), false_target); + return; + } + + __ j(X86_64IntegerCondition(condition->GetCondition()), true_target); } } if (false_target != nullptr) { @@ -2562,7 +2580,7 @@ void LocationsBuilderX86_64::VisitAdd(HAdd* add) { case Primitive::kPrimLong: { locations->SetInAt(0, Location::RequiresRegister()); // We can use a leaq or addq if the constant can fit in an immediate. - locations->SetInAt(1, Location::RegisterOrInt32LongConstant(add->InputAt(1))); + locations->SetInAt(1, Location::RegisterOrInt32Constant(add->InputAt(1))); locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap); break; } @@ -2682,7 +2700,7 @@ void LocationsBuilderX86_64::VisitSub(HSub* sub) { } case Primitive::kPrimLong: { locations->SetInAt(0, Location::RequiresRegister()); - locations->SetInAt(1, Location::RegisterOrInt32LongConstant(sub->InputAt(1))); + locations->SetInAt(1, Location::RegisterOrInt32Constant(sub->InputAt(1))); locations->SetOut(Location::SameAsFirstInput()); break; } @@ -3755,14 +3773,25 @@ void LocationsBuilderX86_64::HandleFieldSet(HInstruction* instruction, LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(instruction, LocationSummary::kNoCall); Primitive::Type field_type = field_info.GetFieldType(); + bool is_volatile = field_info.IsVolatile(); bool needs_write_barrier = CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(1)); locations->SetInAt(0, Location::RequiresRegister()); if (Primitive::IsFloatingPointType(instruction->InputAt(1)->GetType())) { - locations->SetInAt(1, Location::RequiresFpuRegister()); + if (is_volatile) { + // In order to satisfy the semantics of volatile, this must be a single instruction store. + locations->SetInAt(1, Location::FpuRegisterOrInt32Constant(instruction->InputAt(1))); + } else { + locations->SetInAt(1, Location::FpuRegisterOrConstant(instruction->InputAt(1))); + } } else { - locations->SetInAt(1, Location::RegisterOrInt32LongConstant(instruction->InputAt(1))); + if (is_volatile) { + // In order to satisfy the semantics of volatile, this must be a single instruction store. + locations->SetInAt(1, Location::RegisterOrInt32Constant(instruction->InputAt(1))); + } else { + locations->SetInAt(1, Location::RegisterOrConstant(instruction->InputAt(1))); + } } if (needs_write_barrier) { // Temporary registers for the write barrier. @@ -3790,11 +3819,13 @@ void InstructionCodeGeneratorX86_64::HandleFieldSet(HInstruction* instruction, GenerateMemoryBarrier(MemBarrierKind::kAnyStore); } + bool maybe_record_implicit_null_check_done = false; + switch (field_type) { case Primitive::kPrimBoolean: case Primitive::kPrimByte: { if (value.IsConstant()) { - int32_t v = CodeGenerator::GetInt32ValueOf(value.GetConstant()); + int8_t v = CodeGenerator::GetInt32ValueOf(value.GetConstant()); __ movb(Address(base, offset), Immediate(v)); } else { __ movb(Address(base, offset), value.AsRegister<CpuRegister>()); @@ -3805,7 +3836,7 @@ void InstructionCodeGeneratorX86_64::HandleFieldSet(HInstruction* instruction, case Primitive::kPrimShort: case Primitive::kPrimChar: { if (value.IsConstant()) { - int32_t v = CodeGenerator::GetInt32ValueOf(value.GetConstant()); + int16_t v = CodeGenerator::GetInt32ValueOf(value.GetConstant()); __ movw(Address(base, offset), Immediate(v)); } else { __ movw(Address(base, offset), value.AsRegister<CpuRegister>()); @@ -3838,9 +3869,11 @@ void InstructionCodeGeneratorX86_64::HandleFieldSet(HInstruction* instruction, case Primitive::kPrimLong: { if (value.IsConstant()) { int64_t v = value.GetConstant()->AsLongConstant()->GetValue(); - DCHECK(IsInt<32>(v)); - int32_t v_32 = v; - __ movq(Address(base, offset), Immediate(v_32)); + codegen_->MoveInt64ToAddress(Address(base, offset), + Address(base, offset + sizeof(int32_t)), + v, + instruction); + maybe_record_implicit_null_check_done = true; } else { __ movq(Address(base, offset), value.AsRegister<CpuRegister>()); } @@ -3848,12 +3881,28 @@ void InstructionCodeGeneratorX86_64::HandleFieldSet(HInstruction* instruction, } case Primitive::kPrimFloat: { - __ movss(Address(base, offset), value.AsFpuRegister<XmmRegister>()); + if (value.IsConstant()) { + int32_t v = + bit_cast<int32_t, float>(value.GetConstant()->AsFloatConstant()->GetValue()); + __ movl(Address(base, offset), Immediate(v)); + } else { + __ movss(Address(base, offset), value.AsFpuRegister<XmmRegister>()); + } break; } case Primitive::kPrimDouble: { - __ movsd(Address(base, offset), value.AsFpuRegister<XmmRegister>()); + if (value.IsConstant()) { + int64_t v = + bit_cast<int64_t, double>(value.GetConstant()->AsDoubleConstant()->GetValue()); + codegen_->MoveInt64ToAddress(Address(base, offset), + Address(base, offset + sizeof(int32_t)), + v, + instruction); + maybe_record_implicit_null_check_done = true; + } else { + __ movsd(Address(base, offset), value.AsFpuRegister<XmmRegister>()); + } break; } @@ -3862,7 +3911,9 @@ void InstructionCodeGeneratorX86_64::HandleFieldSet(HInstruction* instruction, UNREACHABLE(); } - codegen_->MaybeRecordImplicitNullCheck(instruction); + if (!maybe_record_implicit_null_check_done) { + codegen_->MaybeRecordImplicitNullCheck(instruction); + } if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(1))) { CpuRegister temp = locations->GetTemp(0).AsRegister<CpuRegister>(); @@ -4170,13 +4221,9 @@ void LocationsBuilderX86_64::VisitArraySet(HArraySet* instruction) { may_need_runtime_call ? LocationSummary::kCallOnSlowPath : LocationSummary::kNoCall); locations->SetInAt(0, Location::RequiresRegister()); - locations->SetInAt( - 1, Location::RegisterOrConstant(instruction->InputAt(1))); - locations->SetInAt(2, Location::RequiresRegister()); - if (value_type == Primitive::kPrimLong) { - locations->SetInAt(2, Location::RegisterOrInt32LongConstant(instruction->InputAt(2))); - } else if (value_type == Primitive::kPrimFloat || value_type == Primitive::kPrimDouble) { - locations->SetInAt(2, Location::RequiresFpuRegister()); + locations->SetInAt(1, Location::RegisterOrConstant(instruction->InputAt(1))); + if (Primitive::IsFloatingPointType(value_type)) { + locations->SetInAt(2, Location::FpuRegisterOrConstant(instruction->InputAt(2))); } else { locations->SetInAt(2, Location::RegisterOrConstant(instruction->InputAt(2))); } @@ -4330,13 +4377,15 @@ void InstructionCodeGeneratorX86_64::VisitArraySet(HArraySet* instruction) { : Address(array, index.AsRegister<CpuRegister>(), TIMES_8, offset); if (value.IsRegister()) { __ movq(address, value.AsRegister<CpuRegister>()); + codegen_->MaybeRecordImplicitNullCheck(instruction); } else { int64_t v = value.GetConstant()->AsLongConstant()->GetValue(); - DCHECK(IsInt<32>(v)); - int32_t v_32 = v; - __ movq(address, Immediate(v_32)); + Address address_high = index.IsConstant() + ? Address(array, (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_8) + + offset + sizeof(int32_t)) + : Address(array, index.AsRegister<CpuRegister>(), TIMES_8, offset + sizeof(int32_t)); + codegen_->MoveInt64ToAddress(address, address_high, v, instruction); } - codegen_->MaybeRecordImplicitNullCheck(instruction); break; } @@ -4345,8 +4394,14 @@ void InstructionCodeGeneratorX86_64::VisitArraySet(HArraySet* instruction) { Address address = index.IsConstant() ? Address(array, (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_4) + offset) : Address(array, index.AsRegister<CpuRegister>(), TIMES_4, offset); - DCHECK(value.IsFpuRegister()); - __ movss(address, value.AsFpuRegister<XmmRegister>()); + if (value.IsFpuRegister()) { + __ movss(address, value.AsFpuRegister<XmmRegister>()); + } else { + DCHECK(value.IsConstant()); + int32_t v = + bit_cast<int32_t, float>(value.GetConstant()->AsFloatConstant()->GetValue()); + __ movl(address, Immediate(v)); + } codegen_->MaybeRecordImplicitNullCheck(instruction); break; } @@ -4356,9 +4411,18 @@ void InstructionCodeGeneratorX86_64::VisitArraySet(HArraySet* instruction) { Address address = index.IsConstant() ? Address(array, (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_8) + offset) : Address(array, index.AsRegister<CpuRegister>(), TIMES_8, offset); - DCHECK(value.IsFpuRegister()); - __ movsd(address, value.AsFpuRegister<XmmRegister>()); - codegen_->MaybeRecordImplicitNullCheck(instruction); + if (value.IsFpuRegister()) { + __ movsd(address, value.AsFpuRegister<XmmRegister>()); + codegen_->MaybeRecordImplicitNullCheck(instruction); + } else { + int64_t v = + bit_cast<int64_t, double>(value.GetConstant()->AsDoubleConstant()->GetValue()); + Address address_high = index.IsConstant() + ? Address(array, (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_8) + + offset + sizeof(int32_t)) + : Address(array, index.AsRegister<CpuRegister>(), TIMES_8, offset + sizeof(int32_t)); + codegen_->MoveInt64ToAddress(address, address_high, v, instruction); + } break; } @@ -5564,6 +5628,24 @@ Address CodeGeneratorX86_64::LiteralCaseTable(HPackedSwitch* switch_instr) { return Address::RIP(table_fixup); } +void CodeGeneratorX86_64::MoveInt64ToAddress(const Address& addr_low, + const Address& addr_high, + int64_t v, + HInstruction* instruction) { + if (IsInt<32>(v)) { + int32_t v_32 = v; + __ movq(addr_low, Immediate(v_32)); + MaybeRecordImplicitNullCheck(instruction); + } else { + // Didn't fit in a register. Do it in pieces. + int32_t low_v = Low32Bits(v); + int32_t high_v = High32Bits(v); + __ movl(addr_low, Immediate(low_v)); + MaybeRecordImplicitNullCheck(instruction); + __ movl(addr_high, Immediate(high_v)); + } +} + #undef __ } // namespace x86_64 diff --git a/compiler/optimizing/code_generator_x86_64.h b/compiler/optimizing/code_generator_x86_64.h index fc485f5bb6..7a52473408 100644 --- a/compiler/optimizing/code_generator_x86_64.h +++ b/compiler/optimizing/code_generator_x86_64.h @@ -368,6 +368,12 @@ class CodeGeneratorX86_64 : public CodeGenerator { // Store a 64 bit value into a DoubleStackSlot in the most efficient manner. void Store64BitValueToStack(Location dest, int64_t value); + // Assign a 64 bit constant to an address. + void MoveInt64ToAddress(const Address& addr_low, + const Address& addr_high, + int64_t v, + HInstruction* instruction); + private: struct PcRelativeDexCacheAccessInfo { PcRelativeDexCacheAccessInfo(const DexFile& dex_file, uint32_t element_off) diff --git a/compiler/optimizing/induction_var_range.cc b/compiler/optimizing/induction_var_range.cc index 5530d261d2..b40ef5aa41 100644 --- a/compiler/optimizing/induction_var_range.cc +++ b/compiler/optimizing/induction_var_range.cc @@ -75,10 +75,12 @@ static InductionVarRange::Value SimplifyMax(InductionVarRange::Value v) { return v; } -static HInstruction* Insert(HBasicBlock* preheader, HInstruction* instruction) { - DCHECK(preheader != nullptr); +/** Helper method to insert an instruction. */ +static HInstruction* Insert(HBasicBlock* block, HInstruction* instruction) { + DCHECK(block != nullptr); + DCHECK(block->GetLastInstruction() != nullptr) << block->GetBlockId(); DCHECK(instruction != nullptr); - preheader->InsertInstructionBefore(instruction, preheader->GetLastInstruction()); + block->InsertInstructionBefore(instruction, block->GetLastInstruction()); return instruction; } @@ -91,48 +93,98 @@ InductionVarRange::InductionVarRange(HInductionVarAnalysis* induction_analysis) DCHECK(induction_analysis != nullptr); } -InductionVarRange::Value InductionVarRange::GetMinInduction(HInstruction* context, - HInstruction* instruction) { - return GetInduction(context, instruction, /* is_min */ true); -} - -InductionVarRange::Value InductionVarRange::GetMaxInduction(HInstruction* context, - HInstruction* instruction) { - return SimplifyMax(GetInduction(context, instruction, /* is_min */ false)); +void InductionVarRange::GetInductionRange(HInstruction* context, + HInstruction* instruction, + /*out*/Value* min_val, + /*out*/Value* max_val, + /*out*/bool* needs_finite_test) { + HLoopInformation* loop = context->GetBlock()->GetLoopInformation(); // closest enveloping loop + if (loop != nullptr) { + // Set up loop information. + HBasicBlock* header = loop->GetHeader(); + bool in_body = context->GetBlock() != header; + HInductionVarAnalysis::InductionInfo* info = + induction_analysis_->LookupInfo(loop, instruction); + HInductionVarAnalysis::InductionInfo* trip = + induction_analysis_->LookupInfo(loop, header->GetLastInstruction()); + // Find range. + *min_val = GetVal(info, trip, in_body, /* is_min */ true); + *max_val = SimplifyMax(GetVal(info, trip, in_body, /* is_min */ false)); + *needs_finite_test = NeedsTripCount(info) && IsUnsafeTripCount(trip); + } else { + // No loop to analyze. + *min_val = Value(); + *max_val = Value(); + *needs_finite_test = false; + } } bool InductionVarRange::CanGenerateCode(HInstruction* context, HInstruction* instruction, - /*out*/bool* top_test) { - return GenerateCode(context, instruction, nullptr, nullptr, nullptr, nullptr, top_test); + /*out*/bool* needs_finite_test, + /*out*/bool* needs_taken_test) { + return GenerateCode(context, + instruction, + nullptr, nullptr, nullptr, nullptr, nullptr, // nothing generated yet + needs_finite_test, + needs_taken_test); } -bool InductionVarRange::GenerateCode(HInstruction* context, - HInstruction* instruction, - HGraph* graph, - HBasicBlock* block, - /*out*/HInstruction** lower, - /*out*/HInstruction** upper) { - return GenerateCode(context, instruction, graph, block, lower, upper, nullptr); +void InductionVarRange::GenerateRangeCode(HInstruction* context, + HInstruction* instruction, + HGraph* graph, + HBasicBlock* block, + /*out*/HInstruction** lower, + /*out*/HInstruction** upper) { + bool b1, b2; // unused + if (!GenerateCode(context, instruction, graph, block, lower, upper, nullptr, &b1, &b2)) { + LOG(FATAL) << "Failed precondition: GenerateCode()"; + } +} + +void InductionVarRange::GenerateTakenTest(HInstruction* context, + HGraph* graph, + HBasicBlock* block, + /*out*/HInstruction** taken_test) { + bool b1, b2; // unused + if (!GenerateCode(context, context, graph, block, nullptr, nullptr, taken_test, &b1, &b2)) { + LOG(FATAL) << "Failed precondition: GenerateCode()"; + } } // // Private class methods. // -InductionVarRange::Value InductionVarRange::GetInduction(HInstruction* context, - HInstruction* instruction, - bool is_min) { - HLoopInformation* loop = context->GetBlock()->GetLoopInformation(); // closest enveloping loop - if (loop != nullptr) { - HBasicBlock* header = loop->GetHeader(); - bool in_body = context->GetBlock() != header; - return GetVal(induction_analysis_->LookupInfo(loop, instruction), - induction_analysis_->LookupInfo(loop, header->GetLastInstruction()), - in_body, - is_min); +bool InductionVarRange::NeedsTripCount(HInductionVarAnalysis::InductionInfo* info) { + if (info != nullptr) { + if (info->induction_class == HInductionVarAnalysis::kLinear) { + return true; + } else if (info->induction_class == HInductionVarAnalysis::kWrapAround) { + return NeedsTripCount(info->op_b); + } } - return Value(); + return false; +} + +bool InductionVarRange::IsBodyTripCount(HInductionVarAnalysis::InductionInfo* trip) { + if (trip != nullptr) { + if (trip->induction_class == HInductionVarAnalysis::kInvariant) { + return trip->operation == HInductionVarAnalysis::kTripCountInBody || + trip->operation == HInductionVarAnalysis::kTripCountInBodyUnsafe; + } + } + return false; +} + +bool InductionVarRange::IsUnsafeTripCount(HInductionVarAnalysis::InductionInfo* trip) { + if (trip != nullptr) { + if (trip->induction_class == HInductionVarAnalysis::kInvariant) { + return trip->operation == HInductionVarAnalysis::kTripCountInBodyUnsafe || + trip->operation == HInductionVarAnalysis::kTripCountInLoopUnsafe; + } + } + return false; } InductionVarRange::Value InductionVarRange::GetFetch(HInstruction* instruction, @@ -184,11 +236,13 @@ InductionVarRange::Value InductionVarRange::GetVal(HInductionVarAnalysis::Induct case HInductionVarAnalysis::kFetch: return GetFetch(info->fetch, trip, in_body, is_min); case HInductionVarAnalysis::kTripCountInLoop: + case HInductionVarAnalysis::kTripCountInLoopUnsafe: if (!in_body && !is_min) { // one extra! return GetVal(info->op_a, trip, in_body, is_min); } FALLTHROUGH_INTENDED; case HInductionVarAnalysis::kTripCountInBody: + case HInductionVarAnalysis::kTripCountInBodyUnsafe: if (is_min) { return Value(0); } else if (in_body) { @@ -356,25 +410,42 @@ bool InductionVarRange::GenerateCode(HInstruction* context, HBasicBlock* block, /*out*/HInstruction** lower, /*out*/HInstruction** upper, - /*out*/bool* top_test) { + /*out*/HInstruction** taken_test, + /*out*/bool* needs_finite_test, + /*out*/bool* needs_taken_test) { HLoopInformation* loop = context->GetBlock()->GetLoopInformation(); // closest enveloping loop if (loop != nullptr) { + // Set up loop information. HBasicBlock* header = loop->GetHeader(); bool in_body = context->GetBlock() != header; - HInductionVarAnalysis::InductionInfo* info = induction_analysis_->LookupInfo(loop, instruction); + HInductionVarAnalysis::InductionInfo* info = + induction_analysis_->LookupInfo(loop, instruction); + if (info == nullptr) { + return false; // nothing to analyze + } HInductionVarAnalysis::InductionInfo* trip = induction_analysis_->LookupInfo(loop, header->GetLastInstruction()); - if (info != nullptr && trip != nullptr) { - if (top_test != nullptr) { - *top_test = trip->operation != HInductionVarAnalysis::kTripCountInLoop; + // Determine what tests are needed. + *needs_finite_test = NeedsTripCount(info) && IsUnsafeTripCount(trip); + *needs_taken_test = NeedsTripCount(info) && IsBodyTripCount(trip); + // Code generation for taken test: generate the code when requested or otherwise analyze + // if code generation is feasible when taken test is needed. + if (taken_test != nullptr) { + return GenerateCode( + trip->op_b, nullptr, graph, block, taken_test, in_body, /* is_min */ false); + } else if (*needs_taken_test) { + if (!GenerateCode( + trip->op_b, nullptr, nullptr, nullptr, nullptr, in_body, /* is_min */ false)) { + return false; } - return + } + // Code generation for lower and upper. + return // Success on lower if invariant (not set), or code can be generated. ((info->induction_class == HInductionVarAnalysis::kInvariant) || GenerateCode(info, trip, graph, block, lower, in_body, /* is_min */ true)) && // And success on upper. GenerateCode(info, trip, graph, block, upper, in_body, /* is_min */ false); - } } return false; } @@ -387,19 +458,38 @@ bool InductionVarRange::GenerateCode(HInductionVarAnalysis::InductionInfo* info, bool in_body, bool is_min) { if (info != nullptr) { + // Handle current operation. Primitive::Type type = Primitive::kPrimInt; HInstruction* opa = nullptr; HInstruction* opb = nullptr; - int32_t value = 0; switch (info->induction_class) { case HInductionVarAnalysis::kInvariant: // Invariants. switch (info->operation) { case HInductionVarAnalysis::kAdd: + case HInductionVarAnalysis::kLT: + case HInductionVarAnalysis::kLE: + case HInductionVarAnalysis::kGT: + case HInductionVarAnalysis::kGE: if (GenerateCode(info->op_a, trip, graph, block, &opa, in_body, is_min) && GenerateCode(info->op_b, trip, graph, block, &opb, in_body, is_min)) { if (graph != nullptr) { - *result = Insert(block, new (graph->GetArena()) HAdd(type, opa, opb)); + HInstruction* operation = nullptr; + switch (info->operation) { + case HInductionVarAnalysis::kAdd: + operation = new (graph->GetArena()) HAdd(type, opa, opb); break; + case HInductionVarAnalysis::kLT: + operation = new (graph->GetArena()) HLessThan(opa, opb); break; + case HInductionVarAnalysis::kLE: + operation = new (graph->GetArena()) HLessThanOrEqual(opa, opb); break; + case HInductionVarAnalysis::kGT: + operation = new (graph->GetArena()) HGreaterThan(opa, opb); break; + case HInductionVarAnalysis::kGE: + operation = new (graph->GetArena()) HGreaterThanOrEqual(opa, opb); break; + default: + LOG(FATAL) << "unknown operation"; + } + *result = Insert(block, operation); } return true; } @@ -427,11 +517,13 @@ bool InductionVarRange::GenerateCode(HInductionVarAnalysis::InductionInfo* info, } return true; case HInductionVarAnalysis::kTripCountInLoop: + case HInductionVarAnalysis::kTripCountInLoopUnsafe: if (!in_body && !is_min) { // one extra! return GenerateCode(info->op_a, trip, graph, block, result, in_body, is_min); } FALLTHROUGH_INTENDED; case HInductionVarAnalysis::kTripCountInBody: + case HInductionVarAnalysis::kTripCountInBodyUnsafe: if (is_min) { if (graph != nullptr) { *result = graph->GetIntConstant(0); @@ -452,23 +544,31 @@ bool InductionVarRange::GenerateCode(HInductionVarAnalysis::InductionInfo* info, break; } break; - case HInductionVarAnalysis::kLinear: - // Linear induction a * i + b, for normalized 0 <= i < TC. Restrict to unit stride only - // to avoid arithmetic wrap-around situations that are hard to guard against. - if (GetConstant(info->op_a, &value)) { - if (value == 1 || value == -1) { - const bool is_min_a = value == 1 ? is_min : !is_min; - if (GenerateCode(trip, trip, graph, block, &opa, in_body, is_min_a) && - GenerateCode(info->op_b, trip, graph, block, &opb, in_body, is_min)) { - if (graph != nullptr) { - *result = Insert(block, new (graph->GetArena()) HAdd(type, opa, opb)); + case HInductionVarAnalysis::kLinear: { + // Linear induction a * i + b, for normalized 0 <= i < TC. Restrict to unit stride only + // to avoid arithmetic wrap-around situations that are hard to guard against. + int32_t stride_value = 0; + if (GetConstant(info->op_a, &stride_value)) { + if (stride_value == 1 || stride_value == -1) { + const bool is_min_a = stride_value == 1 ? is_min : !is_min; + if (GenerateCode(trip, trip, graph, block, &opa, in_body, is_min_a) && + GenerateCode(info->op_b, trip, graph, block, &opb, in_body, is_min)) { + if (graph != nullptr) { + HInstruction* oper; + if (stride_value == 1) { + oper = new (graph->GetArena()) HAdd(type, opa, opb); + } else { + oper = new (graph->GetArena()) HSub(type, opb, opa); + } + *result = Insert(block, oper); + } + return true; } - return true; } } } break; - default: // TODO(ajcbik): add more cases + default: break; } } diff --git a/compiler/optimizing/induction_var_range.h b/compiler/optimizing/induction_var_range.h index 7fa5a26dce..7984871b08 100644 --- a/compiler/optimizing/induction_var_range.h +++ b/compiler/optimizing/induction_var_range.h @@ -57,29 +57,33 @@ class InductionVarRange { explicit InductionVarRange(HInductionVarAnalysis* induction); /** - * Given a context denoted by the first instruction, returns a, - * possibly conservative, lower bound on the instruction's value. + * Given a context denoted by the first instruction, returns a possibly conservative + * lower and upper bound on the instruction's value in the output parameters min_val + * and max_val, respectively. The need_finite_test flag denotes if an additional finite-test + * is needed to protect the range evaluation inside its loop. */ - Value GetMinInduction(HInstruction* context, HInstruction* instruction); + void GetInductionRange(HInstruction* context, + HInstruction* instruction, + /*out*/Value* min_val, + /*out*/Value* max_val, + /*out*/bool* needs_finite_test); /** - * Given a context denoted by the first instruction, returns a, - * possibly conservative, upper bound on the instruction's value. + * Returns true if range analysis is able to generate code for the lower and upper + * bound expressions on the instruction in the given context. The need_finite_test + * and need_taken test flags denote if an additional finite-test and/or taken-test + * are needed to protect the range evaluation inside its loop. */ - Value GetMaxInduction(HInstruction* context, HInstruction* instruction); - - /** - * Returns true if range analysis is able to generate code for the lower and upper bound - * expressions on the instruction in the given context. Output parameter top_test denotes - * whether a top test is needed to protect the trip-count expression evaluation. - */ - bool CanGenerateCode(HInstruction* context, HInstruction* instruction, /*out*/bool* top_test); + bool CanGenerateCode(HInstruction* context, + HInstruction* instruction, + /*out*/bool* needs_finite_test, + /*out*/bool* needs_taken_test); /** * Generates the actual code in the HIR for the lower and upper bound expressions on the * instruction in the given context. Code for the lower and upper bound expression are - * generated in given block and graph and are returned in lower and upper, respectively. - * For a loop invariant, lower is not set. + * generated in given block and graph and are returned in the output parameters lower and + * upper, respectively. For a loop invariant, lower is not set. * * For example, given expression x+i with range [0, 5] for i, calling this method * will generate the following sequence: @@ -87,20 +91,35 @@ class InductionVarRange { * block: * lower: add x, 0 * upper: add x, 5 + * + * Precondition: CanGenerateCode() returns true. */ - bool GenerateCode(HInstruction* context, - HInstruction* instruction, - HGraph* graph, - HBasicBlock* block, - /*out*/HInstruction** lower, - /*out*/HInstruction** upper); + void GenerateRangeCode(HInstruction* context, + HInstruction* instruction, + HGraph* graph, + HBasicBlock* block, + /*out*/HInstruction** lower, + /*out*/HInstruction** upper); + + /** + * Generates explicit taken-test for the loop in the given context. Code is generated in + * given block and graph. The taken-test is returned in parameter test. + * + * Precondition: CanGenerateCode() returns true and needs_taken_test is set. + */ + void GenerateTakenTest(HInstruction* context, + HGraph* graph, + HBasicBlock* block, + /*out*/HInstruction** taken_test); private: // // Private helper methods. // - Value GetInduction(HInstruction* context, HInstruction* instruction, bool is_min); + static bool NeedsTripCount(HInductionVarAnalysis::InductionInfo* info); + static bool IsBodyTripCount(HInductionVarAnalysis::InductionInfo* trip); + static bool IsUnsafeTripCount(HInductionVarAnalysis::InductionInfo* trip); static Value GetFetch(HInstruction* instruction, HInductionVarAnalysis::InductionInfo* trip, @@ -130,8 +149,8 @@ class InductionVarRange { static Value MergeVal(Value v1, Value v2, bool is_min); /** - * Generates code for lower/upper expression in the HIR. Returns true on success. - * With graph == nullptr, the method can be used to determine if code generation + * Generates code for lower/upper/taken-test in the HIR. Returns true on success. + * With values nullptr, the method can be used to determine if code generation * would be successful without generating actual code yet. */ bool GenerateCode(HInstruction* context, @@ -140,7 +159,9 @@ class InductionVarRange { HBasicBlock* block, /*out*/HInstruction** lower, /*out*/HInstruction** upper, - bool* top_test); + /*out*/HInstruction** taken_test, + /*out*/bool* needs_finite_test, + /*out*/bool* needs_taken_test); static bool GenerateCode(HInductionVarAnalysis::InductionInfo* info, HInductionVarAnalysis::InductionInfo* trip, diff --git a/compiler/optimizing/induction_var_range_test.cc b/compiler/optimizing/induction_var_range_test.cc index ce8926ad72..fda5153d43 100644 --- a/compiler/optimizing/induction_var_range_test.cc +++ b/compiler/optimizing/induction_var_range_test.cc @@ -46,6 +46,10 @@ class InductionVarRangeTest : public testing::Test { EXPECT_EQ(v1.is_known, v2.is_known); } + // + // Construction methods. + // + /** Constructs bare minimum graph. */ void BuildGraph() { graph_->SetNumberOfVRegs(1); @@ -58,7 +62,7 @@ class InductionVarRangeTest : public testing::Test { } /** Constructs loop with given upper bound. */ - void BuildLoop(HInstruction* upper) { + void BuildLoop(int32_t lower, HInstruction* upper, int32_t stride) { // Control flow. loop_preheader_ = new (&allocator_) HBasicBlock(graph_); graph_->AddBlock(loop_preheader_); @@ -75,18 +79,22 @@ class InductionVarRangeTest : public testing::Test { HLocal* induc = new (&allocator_) HLocal(0); entry_block_->AddInstruction(induc); loop_preheader_->AddInstruction( - new (&allocator_) HStoreLocal(induc, graph_->GetIntConstant(0))); // i = 0 + new (&allocator_) HStoreLocal(induc, graph_->GetIntConstant(lower))); // i = l loop_preheader_->AddInstruction(new (&allocator_) HGoto()); HInstruction* load = new (&allocator_) HLoadLocal(induc, Primitive::kPrimInt); loop_header->AddInstruction(load); - condition_ = new (&allocator_) HLessThan(load, upper); + if (stride > 0) { + condition_ = new (&allocator_) HLessThan(load, upper); // i < u + } else { + condition_ = new (&allocator_) HGreaterThan(load, upper); // i > u + } loop_header->AddInstruction(condition_); - loop_header->AddInstruction(new (&allocator_) HIf(condition_)); // i < u + loop_header->AddInstruction(new (&allocator_) HIf(condition_)); load = new (&allocator_) HLoadLocal(induc, Primitive::kPrimInt); loop_body->AddInstruction(load); - increment_ = new (&allocator_) HAdd(Primitive::kPrimInt, load, graph_->GetIntConstant(1)); + increment_ = new (&allocator_) HAdd(Primitive::kPrimInt, load, graph_->GetIntConstant(stride)); loop_body->AddInstruction(increment_); - loop_body->AddInstruction(new (&allocator_) HStoreLocal(induc, increment_)); // i++ + loop_body->AddInstruction(new (&allocator_) HStoreLocal(induc, increment_)); // i += s loop_body->AddInstruction(new (&allocator_) HGoto()); exit_block_->AddInstruction(new (&allocator_) HReturnVoid()); } @@ -124,8 +132,20 @@ class InductionVarRangeTest : public testing::Test { } /** Constructs a trip-count. */ - HInductionVarAnalysis::InductionInfo* CreateTripCount(int32_t tc) { - return iva_->CreateTripCount(HInductionVarAnalysis::kTripCountInLoop, CreateConst(tc), nullptr); + HInductionVarAnalysis::InductionInfo* CreateTripCount(int32_t tc, bool in_loop, bool safe) { + if (in_loop && safe) { + return iva_->CreateTripCount( + HInductionVarAnalysis::kTripCountInLoop, CreateConst(tc), nullptr); + } else if (in_loop) { + return iva_->CreateTripCount( + HInductionVarAnalysis::kTripCountInLoopUnsafe, CreateConst(tc), nullptr); + } else if (safe) { + return iva_->CreateTripCount( + HInductionVarAnalysis::kTripCountInBody, CreateConst(tc), nullptr); + } else { + return iva_->CreateTripCount( + HInductionVarAnalysis::kTripCountInBodyUnsafe, CreateConst(tc), nullptr); + } } /** Constructs a linear a * i + b induction. */ @@ -139,16 +159,34 @@ class InductionVarRangeTest : public testing::Test { HInductionVarAnalysis::kPeriodic, CreateConst(lo), CreateConst(hi)); } + /** Constructs a wrap-around induction consisting of a constant, followed info */ + HInductionVarAnalysis::InductionInfo* CreateWrapAround( + int32_t initial, + HInductionVarAnalysis::InductionInfo* info) { + return iva_->CreateInduction(HInductionVarAnalysis::kWrapAround, CreateConst(initial), info); + } + /** Constructs a wrap-around induction consisting of a constant, followed by a range. */ HInductionVarAnalysis::InductionInfo* CreateWrapAround(int32_t initial, int32_t lo, int32_t hi) { - return iva_->CreateInduction( - HInductionVarAnalysis::kWrapAround, CreateConst(initial), CreateRange(lo, hi)); + return CreateWrapAround(initial, CreateRange(lo, hi)); } // // Relay methods. // + bool NeedsTripCount(HInductionVarAnalysis::InductionInfo* info) { + return InductionVarRange::NeedsTripCount(info); + } + + bool IsBodyTripCount(HInductionVarAnalysis::InductionInfo* trip) { + return InductionVarRange::IsBodyTripCount(trip); + } + + bool IsUnsafeTripCount(HInductionVarAnalysis::InductionInfo* trip) { + return InductionVarRange::IsUnsafeTripCount(trip); + } + Value GetMin(HInductionVarAnalysis::InductionInfo* info, HInductionVarAnalysis::InductionInfo* induc) { return InductionVarRange::GetVal(info, induc, /* in_body */ true, /* is_min */ true); @@ -202,6 +240,26 @@ class InductionVarRangeTest : public testing::Test { // Tests on static methods. // +TEST_F(InductionVarRangeTest, TripCountProperties) { + EXPECT_FALSE(NeedsTripCount(nullptr)); + EXPECT_FALSE(NeedsTripCount(CreateConst(1))); + EXPECT_TRUE(NeedsTripCount(CreateLinear(1, 1))); + EXPECT_FALSE(NeedsTripCount(CreateWrapAround(1, 2, 3))); + EXPECT_TRUE(NeedsTripCount(CreateWrapAround(1, CreateLinear(1, 1)))); + + EXPECT_FALSE(IsBodyTripCount(nullptr)); + EXPECT_FALSE(IsBodyTripCount(CreateTripCount(100, true, true))); + EXPECT_FALSE(IsBodyTripCount(CreateTripCount(100, true, false))); + EXPECT_TRUE(IsBodyTripCount(CreateTripCount(100, false, true))); + EXPECT_TRUE(IsBodyTripCount(CreateTripCount(100, false, false))); + + EXPECT_FALSE(IsUnsafeTripCount(nullptr)); + EXPECT_FALSE(IsUnsafeTripCount(CreateTripCount(100, true, true))); + EXPECT_TRUE(IsUnsafeTripCount(CreateTripCount(100, true, false))); + EXPECT_FALSE(IsUnsafeTripCount(CreateTripCount(100, false, true))); + EXPECT_TRUE(IsUnsafeTripCount(CreateTripCount(100, false, false))); +} + TEST_F(InductionVarRangeTest, GetMinMaxNull) { ExpectEqual(Value(), GetMin(nullptr, nullptr)); ExpectEqual(Value(), GetMax(nullptr, nullptr)); @@ -279,10 +337,10 @@ TEST_F(InductionVarRangeTest, GetMinMaxFetch) { } TEST_F(InductionVarRangeTest, GetMinMaxLinear) { - ExpectEqual(Value(20), GetMin(CreateLinear(10, 20), CreateTripCount(100))); - ExpectEqual(Value(1010), GetMax(CreateLinear(10, 20), CreateTripCount(100))); - ExpectEqual(Value(-970), GetMin(CreateLinear(-10, 20), CreateTripCount(100))); - ExpectEqual(Value(20), GetMax(CreateLinear(-10, 20), CreateTripCount(100))); + ExpectEqual(Value(20), GetMin(CreateLinear(10, 20), CreateTripCount(100, true, true))); + ExpectEqual(Value(1010), GetMax(CreateLinear(10, 20), CreateTripCount(100, true, true))); + ExpectEqual(Value(-970), GetMin(CreateLinear(-10, 20), CreateTripCount(100, true, true))); + ExpectEqual(Value(20), GetMax(CreateLinear(-10, 20), CreateTripCount(100, true, true))); } TEST_F(InductionVarRangeTest, GetMinMaxWrapAround) { @@ -398,61 +456,98 @@ TEST_F(InductionVarRangeTest, MaxValue) { // Tests on instance methods. // -TEST_F(InductionVarRangeTest, FindRangeConstantTripCount) { - BuildLoop(graph_->GetIntConstant(1000)); +TEST_F(InductionVarRangeTest, ConstantTripCountUp) { + BuildLoop(0, graph_->GetIntConstant(1000), 1); PerformInductionVarAnalysis(); InductionVarRange range(iva_); + Value v1, v2; + bool needs_finite_test = true; + // In context of header: known. - ExpectEqual(Value(0), range.GetMinInduction(condition_, condition_->InputAt(0))); - ExpectEqual(Value(1000), range.GetMaxInduction(condition_, condition_->InputAt(0))); + range.GetInductionRange(condition_, condition_->InputAt(0), &v1, &v2, &needs_finite_test); + EXPECT_FALSE(needs_finite_test); + ExpectEqual(Value(0), v1); + ExpectEqual(Value(1000), v2); // In context of loop-body: known. - ExpectEqual(Value(0), range.GetMinInduction(increment_, condition_->InputAt(0))); - ExpectEqual(Value(999), range.GetMaxInduction(increment_, condition_->InputAt(0))); - ExpectEqual(Value(1), range.GetMinInduction(increment_, increment_)); - ExpectEqual(Value(1000), range.GetMaxInduction(increment_, increment_)); + range.GetInductionRange(increment_, condition_->InputAt(0), &v1, &v2, &needs_finite_test); + EXPECT_FALSE(needs_finite_test); + ExpectEqual(Value(0), v1); + ExpectEqual(Value(999), v2); + range.GetInductionRange(increment_, increment_, &v1, &v2, &needs_finite_test); + EXPECT_FALSE(needs_finite_test); + ExpectEqual(Value(1), v1); + ExpectEqual(Value(1000), v2); } -TEST_F(InductionVarRangeTest, FindRangeSymbolicTripCount) { - HInstruction* parameter = new (&allocator_) HParameterValue( - graph_->GetDexFile(), 0, 0, Primitive::kPrimInt); - entry_block_->AddInstruction(parameter); - BuildLoop(parameter); +TEST_F(InductionVarRangeTest, ConstantTripCountDown) { + BuildLoop(1000, graph_->GetIntConstant(0), -1); PerformInductionVarAnalysis(); InductionVarRange range(iva_); - // In context of header: full range unknown. - ExpectEqual(Value(0), range.GetMinInduction(condition_, condition_->InputAt(0))); - ExpectEqual(Value(), range.GetMaxInduction(condition_, condition_->InputAt(0))); + Value v1, v2; + bool needs_finite_test = true; + + // In context of header: known. + range.GetInductionRange(condition_, condition_->InputAt(0), &v1, &v2, &needs_finite_test); + EXPECT_FALSE(needs_finite_test); + ExpectEqual(Value(0), v1); + ExpectEqual(Value(1000), v2); // In context of loop-body: known. - ExpectEqual(Value(0), range.GetMinInduction(increment_, condition_->InputAt(0))); - ExpectEqual(Value(parameter, 1, -1), range.GetMaxInduction(increment_, condition_->InputAt(0))); - ExpectEqual(Value(1), range.GetMinInduction(increment_, increment_)); - ExpectEqual(Value(parameter, 1, 0), range.GetMaxInduction(increment_, increment_)); + range.GetInductionRange(increment_, condition_->InputAt(0), &v1, &v2, &needs_finite_test); + EXPECT_FALSE(needs_finite_test); + ExpectEqual(Value(1), v1); + ExpectEqual(Value(1000), v2); + range.GetInductionRange(increment_, increment_, &v1, &v2, &needs_finite_test); + EXPECT_FALSE(needs_finite_test); + ExpectEqual(Value(0), v1); + ExpectEqual(Value(999), v2); } -TEST_F(InductionVarRangeTest, CodeGeneration) { +TEST_F(InductionVarRangeTest, SymbolicTripCountUp) { HInstruction* parameter = new (&allocator_) HParameterValue( graph_->GetDexFile(), 0, 0, Primitive::kPrimInt); entry_block_->AddInstruction(parameter); - BuildLoop(parameter); + BuildLoop(0, parameter, 1); PerformInductionVarAnalysis(); InductionVarRange range(iva_); + Value v1, v2; + bool needs_finite_test = true; + bool needs_taken_test = true; + + // In context of header: upper unknown. + range.GetInductionRange(condition_, condition_->InputAt(0), &v1, &v2, &needs_finite_test); + EXPECT_FALSE(needs_finite_test); + ExpectEqual(Value(0), v1); + ExpectEqual(Value(), v2); + + // In context of loop-body: known. + range.GetInductionRange(increment_, condition_->InputAt(0), &v1, &v2, &needs_finite_test); + EXPECT_FALSE(needs_finite_test); + ExpectEqual(Value(0), v1); + ExpectEqual(Value(parameter, 1, -1), v2); + range.GetInductionRange(increment_, increment_, &v1, &v2, &needs_finite_test); + EXPECT_FALSE(needs_finite_test); + ExpectEqual(Value(1), v1); + ExpectEqual(Value(parameter, 1, 0), v2); + HInstruction* lower = nullptr; HInstruction* upper = nullptr; - bool top_test = false; + HInstruction* taken = nullptr; // Can generate code in context of loop-body only. - EXPECT_FALSE(range.CanGenerateCode(condition_, condition_->InputAt(0), &top_test)); - ASSERT_TRUE(range.CanGenerateCode(increment_, condition_->InputAt(0), &top_test)); - EXPECT_TRUE(top_test); + EXPECT_FALSE(range.CanGenerateCode( + condition_, condition_->InputAt(0), &needs_finite_test, &needs_taken_test)); + ASSERT_TRUE(range.CanGenerateCode( + increment_, condition_->InputAt(0), &needs_finite_test, &needs_taken_test)); + EXPECT_FALSE(needs_finite_test); + EXPECT_TRUE(needs_taken_test); // Generates code. - EXPECT_TRUE(range.GenerateCode( - increment_, condition_->InputAt(0), graph_, loop_preheader_, &lower, &upper)); + range.GenerateRangeCode(increment_, condition_->InputAt(0), graph_, loop_preheader_, &lower, &upper); // Verify lower is 0+0. ASSERT_TRUE(lower != nullptr); @@ -462,7 +557,7 @@ TEST_F(InductionVarRangeTest, CodeGeneration) { ASSERT_TRUE(lower->InputAt(1)->IsIntConstant()); EXPECT_EQ(0, lower->InputAt(1)->AsIntConstant()->GetValue()); - // Verify upper is (V-1)+0 + // Verify upper is (V-1)+0. ASSERT_TRUE(upper != nullptr); ASSERT_TRUE(upper->IsAdd()); ASSERT_TRUE(upper->InputAt(0)->IsSub()); @@ -471,6 +566,91 @@ TEST_F(InductionVarRangeTest, CodeGeneration) { EXPECT_EQ(1, upper->InputAt(0)->InputAt(1)->AsIntConstant()->GetValue()); ASSERT_TRUE(upper->InputAt(1)->IsIntConstant()); EXPECT_EQ(0, upper->InputAt(1)->AsIntConstant()->GetValue()); + + // Verify taken-test is 0<V. + range.GenerateTakenTest(increment_, graph_, loop_preheader_, &taken); + ASSERT_TRUE(taken != nullptr); + ASSERT_TRUE(taken->IsLessThan()); + ASSERT_TRUE(taken->InputAt(0)->IsIntConstant()); + EXPECT_EQ(0, taken->InputAt(0)->AsIntConstant()->GetValue()); + EXPECT_TRUE(taken->InputAt(1)->IsParameterValue()); +} + +TEST_F(InductionVarRangeTest, SymbolicTripCountDown) { + HInstruction* parameter = new (&allocator_) HParameterValue( + graph_->GetDexFile(), 0, 0, Primitive::kPrimInt); + entry_block_->AddInstruction(parameter); + BuildLoop(1000, parameter, -1); + PerformInductionVarAnalysis(); + InductionVarRange range(iva_); + + Value v1, v2; + bool needs_finite_test = true; + bool needs_taken_test = true; + + // In context of header: lower unknown. + range.GetInductionRange(condition_, condition_->InputAt(0), &v1, &v2, &needs_finite_test); + EXPECT_FALSE(needs_finite_test); + ExpectEqual(Value(), v1); + ExpectEqual(Value(1000), v2); + + // In context of loop-body: known. + range.GetInductionRange(increment_, condition_->InputAt(0), &v1, &v2, &needs_finite_test); + EXPECT_FALSE(needs_finite_test); + ExpectEqual(Value(parameter, 1, 1), v1); + ExpectEqual(Value(1000), v2); + range.GetInductionRange(increment_, increment_, &v1, &v2, &needs_finite_test); + EXPECT_FALSE(needs_finite_test); + ExpectEqual(Value(parameter, 1, 0), v1); + ExpectEqual(Value(999), v2); + + HInstruction* lower = nullptr; + HInstruction* upper = nullptr; + HInstruction* taken = nullptr; + + // Can generate code in context of loop-body only. + EXPECT_FALSE(range.CanGenerateCode( + condition_, condition_->InputAt(0), &needs_finite_test, &needs_taken_test)); + ASSERT_TRUE(range.CanGenerateCode( + increment_, condition_->InputAt(0), &needs_finite_test, &needs_taken_test)); + EXPECT_FALSE(needs_finite_test); + EXPECT_TRUE(needs_taken_test); + + // Generates code. + range.GenerateRangeCode(increment_, condition_->InputAt(0), graph_, loop_preheader_, &lower, &upper); + + // Verify lower is 1000-(-(V-1000)-1). + ASSERT_TRUE(lower != nullptr); + ASSERT_TRUE(lower->IsSub()); + ASSERT_TRUE(lower->InputAt(0)->IsIntConstant()); + EXPECT_EQ(1000, lower->InputAt(0)->AsIntConstant()->GetValue()); + lower = lower->InputAt(1); + ASSERT_TRUE(lower->IsSub()); + ASSERT_TRUE(lower->InputAt(1)->IsIntConstant()); + EXPECT_EQ(1, lower->InputAt(1)->AsIntConstant()->GetValue()); + lower = lower->InputAt(0); + ASSERT_TRUE(lower->IsNeg()); + lower = lower->InputAt(0); + ASSERT_TRUE(lower->IsSub()); + EXPECT_TRUE(lower->InputAt(0)->IsParameterValue()); + ASSERT_TRUE(lower->InputAt(1)->IsIntConstant()); + EXPECT_EQ(1000, lower->InputAt(1)->AsIntConstant()->GetValue()); + + // Verify upper is 1000-0. + ASSERT_TRUE(upper != nullptr); + ASSERT_TRUE(upper->IsSub()); + ASSERT_TRUE(upper->InputAt(0)->IsIntConstant()); + EXPECT_EQ(1000, upper->InputAt(0)->AsIntConstant()->GetValue()); + ASSERT_TRUE(upper->InputAt(1)->IsIntConstant()); + EXPECT_EQ(0, upper->InputAt(1)->AsIntConstant()->GetValue()); + + // Verify taken-test is 1000>V. + range.GenerateTakenTest(increment_, graph_, loop_preheader_, &taken); + ASSERT_TRUE(taken != nullptr); + ASSERT_TRUE(taken->IsGreaterThan()); + ASSERT_TRUE(taken->InputAt(0)->IsIntConstant()); + EXPECT_EQ(1000, taken->InputAt(0)->AsIntConstant()->GetValue()); + EXPECT_TRUE(taken->InputAt(1)->IsParameterValue()); } } // namespace art diff --git a/compiler/optimizing/intrinsics.cc b/compiler/optimizing/intrinsics.cc index dbe75249be..b01324ec3b 100644 --- a/compiler/optimizing/intrinsics.cc +++ b/compiler/optimizing/intrinsics.cc @@ -89,10 +89,7 @@ static Primitive::Type GetType(uint64_t data, bool is_op_size) { } } -static Intrinsics GetIntrinsic(InlineMethod method, InstructionSet instruction_set) { - if (instruction_set == kMips) { - return Intrinsics::kNone; - } +static Intrinsics GetIntrinsic(InlineMethod method) { switch (method.opcode) { // Floating-point conversions. case kIntrinsicDoubleCvt: @@ -431,7 +428,7 @@ void IntrinsicsRecognizer::Run() { DexFileMethodInliner* inliner = driver_->GetMethodInlinerMap()->GetMethodInliner(&dex_file); DCHECK(inliner != nullptr); if (inliner->IsIntrinsic(invoke->GetDexMethodIndex(), &method)) { - Intrinsics intrinsic = GetIntrinsic(method, graph_->GetInstructionSet()); + Intrinsics intrinsic = GetIntrinsic(method); if (intrinsic != Intrinsics::kNone) { if (!CheckInvokeType(intrinsic, invoke, dex_file)) { diff --git a/compiler/optimizing/intrinsics_mips.cc b/compiler/optimizing/intrinsics_mips.cc index 5efcf4eadf..a94e3a8c23 100644 --- a/compiler/optimizing/intrinsics_mips.cc +++ b/compiler/optimizing/intrinsics_mips.cc @@ -138,6 +138,108 @@ bool IntrinsicLocationsBuilderMIPS::TryDispatch(HInvoke* invoke) { #define __ assembler-> +// boolean java.lang.String.equals(Object anObject) +void IntrinsicLocationsBuilderMIPS::VisitStringEquals(HInvoke* invoke) { + LocationSummary* locations = new (arena_) LocationSummary(invoke, + LocationSummary::kNoCall, + kIntrinsified); + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetInAt(1, Location::RequiresRegister()); + locations->SetOut(Location::RequiresRegister()); + + // Temporary registers to store lengths of strings and for calculations. + locations->AddTemp(Location::RequiresRegister()); + locations->AddTemp(Location::RequiresRegister()); + locations->AddTemp(Location::RequiresRegister()); +} + +void IntrinsicCodeGeneratorMIPS::VisitStringEquals(HInvoke* invoke) { + MipsAssembler* assembler = GetAssembler(); + LocationSummary* locations = invoke->GetLocations(); + + Register str = locations->InAt(0).AsRegister<Register>(); + Register arg = locations->InAt(1).AsRegister<Register>(); + Register out = locations->Out().AsRegister<Register>(); + + Register temp1 = locations->GetTemp(0).AsRegister<Register>(); + Register temp2 = locations->GetTemp(1).AsRegister<Register>(); + Register temp3 = locations->GetTemp(2).AsRegister<Register>(); + + MipsLabel loop; + MipsLabel end; + MipsLabel return_true; + MipsLabel return_false; + + // Get offsets of count, value, and class fields within a string object. + const uint32_t count_offset = mirror::String::CountOffset().Uint32Value(); + const uint32_t value_offset = mirror::String::ValueOffset().Uint32Value(); + const uint32_t class_offset = mirror::Object::ClassOffset().Uint32Value(); + + // Note that the null check must have been done earlier. + DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0))); + + // If the register containing the pointer to "this", and the register + // containing the pointer to "anObject" are the same register then + // "this", and "anObject" are the same object and we can + // short-circuit the logic to a true result. + if (str == arg) { + __ LoadConst32(out, 1); + return; + } + + // Check if input is null, return false if it is. + __ Beqz(arg, &return_false); + + // Reference equality check, return true if same reference. + __ Beq(str, arg, &return_true); + + // Instanceof check for the argument by comparing class fields. + // All string objects must have the same type since String cannot be subclassed. + // Receiver must be a string object, so its class field is equal to all strings' class fields. + // If the argument is a string object, its class field must be equal to receiver's class field. + __ Lw(temp1, str, class_offset); + __ Lw(temp2, arg, class_offset); + __ Bne(temp1, temp2, &return_false); + + // Load lengths of this and argument strings. + __ Lw(temp1, str, count_offset); + __ Lw(temp2, arg, count_offset); + // Check if lengths are equal, return false if they're not. + __ Bne(temp1, temp2, &return_false); + // Return true if both strings are empty. + __ Beqz(temp1, &return_true); + + // Don't overwrite input registers + __ Move(TMP, str); + __ Move(temp3, arg); + + // Assertions that must hold in order to compare strings 2 characters at a time. + DCHECK_ALIGNED(value_offset, 4); + static_assert(IsAligned<4>(kObjectAlignment), "String of odd length is not zero padded"); + + // Loop to compare strings 2 characters at a time starting at the beginning of the string. + // Ok to do this because strings are zero-padded. + __ Bind(&loop); + __ Lw(out, TMP, value_offset); + __ Lw(temp2, temp3, value_offset); + __ Bne(out, temp2, &return_false); + __ Addiu(TMP, TMP, 4); + __ Addiu(temp3, temp3, 4); + __ Addiu(temp1, temp1, -2); + __ Bgtz(temp1, &loop); + + // Return true and exit the function. + // If loop does not result in returning false, we return true. + __ Bind(&return_true); + __ LoadConst32(out, 1); + __ B(&end); + + // Return false and exit the function. + __ Bind(&return_false); + __ LoadConst32(out, 0); + __ Bind(&end); +} + // Unimplemented intrinsics. #define UNIMPLEMENTED_INTRINSIC(Name) \ @@ -204,7 +306,6 @@ UNIMPLEMENTED_INTRINSIC(UnsafeCASLong) UNIMPLEMENTED_INTRINSIC(UnsafeCASObject) UNIMPLEMENTED_INTRINSIC(StringCharAt) UNIMPLEMENTED_INTRINSIC(StringCompareTo) -UNIMPLEMENTED_INTRINSIC(StringEquals) UNIMPLEMENTED_INTRINSIC(StringIndexOf) UNIMPLEMENTED_INTRINSIC(StringIndexOfAfter) UNIMPLEMENTED_INTRINSIC(StringNewStringFromBytes) diff --git a/compiler/optimizing/intrinsics_mips64.cc b/compiler/optimizing/intrinsics_mips64.cc index 05c7eb02d9..ff843ebb1e 100644 --- a/compiler/optimizing/intrinsics_mips64.cc +++ b/compiler/optimizing/intrinsics_mips64.cc @@ -101,11 +101,10 @@ class IntrinsicSlowPathMIPS64 : public SlowPathCodeMIPS64 { if (invoke_->IsInvokeStaticOrDirect()) { codegen->GenerateStaticOrDirectCall(invoke_->AsInvokeStaticOrDirect(), Location::RegisterLocation(A0)); - codegen->RecordPcInfo(invoke_, invoke_->GetDexPc(), this); } else { - UNIMPLEMENTED(FATAL) << "Non-direct intrinsic slow-path not yet implemented"; - UNREACHABLE(); + codegen->GenerateVirtualCall(invoke_->AsInvokeVirtual(), Location::RegisterLocation(A0)); } + codegen->RecordPcInfo(invoke_, invoke_->GetDexPc(), this); // Copy the result back to the expected output. Location out = invoke_->GetLocations()->Out(); diff --git a/compiler/optimizing/intrinsics_x86_64.cc b/compiler/optimizing/intrinsics_x86_64.cc index 14c65c9aaf..a29f3ef1d1 100644 --- a/compiler/optimizing/intrinsics_x86_64.cc +++ b/compiler/optimizing/intrinsics_x86_64.cc @@ -1605,7 +1605,7 @@ static void CreateIntIntToVoidLocations(ArenaAllocator* arena, HInvoke* invoke) LocationSummary::kNoCall, kIntrinsified); locations->SetInAt(0, Location::RequiresRegister()); - locations->SetInAt(1, Location::RegisterOrInt32LongConstant(invoke->InputAt(1))); + locations->SetInAt(1, Location::RegisterOrInt32Constant(invoke->InputAt(1))); } static void GenPoke(LocationSummary* locations, Primitive::Type size, X86_64Assembler* assembler) { diff --git a/compiler/optimizing/locations.cc b/compiler/optimizing/locations.cc index ebdf7a2f65..1ab206f69e 100644 --- a/compiler/optimizing/locations.cc +++ b/compiler/optimizing/locations.cc @@ -17,6 +17,7 @@ #include "locations.h" #include "nodes.h" +#include "code_generator.h" namespace art { @@ -47,18 +48,26 @@ Location Location::RegisterOrConstant(HInstruction* instruction) { : Location::RequiresRegister(); } -Location Location::RegisterOrInt32LongConstant(HInstruction* instruction) { - if (instruction->IsIntConstant() || instruction->IsNullConstant()) { - return Location::ConstantLocation(instruction->AsConstant()); - } else if (instruction->IsLongConstant()) { - // Does the long constant fit in a 32 bit int? - int64_t value = instruction->AsLongConstant()->GetValue(); - return IsInt<32>(value) - ? Location::ConstantLocation(instruction->AsConstant()) - : Location::RequiresRegister(); - } else { - return Location::RequiresRegister(); +Location Location::RegisterOrInt32Constant(HInstruction* instruction) { + HConstant* constant = instruction->AsConstant(); + if (constant != nullptr) { + int64_t value = CodeGenerator::GetInt64ValueOf(constant); + if (IsInt<32>(value)) { + return Location::ConstantLocation(constant); + } } + return Location::RequiresRegister(); +} + +Location Location::FpuRegisterOrInt32Constant(HInstruction* instruction) { + HConstant* constant = instruction->AsConstant(); + if (constant != nullptr) { + int64_t value = CodeGenerator::GetInt64ValueOf(constant); + if (IsInt<32>(value)) { + return Location::ConstantLocation(constant); + } + } + return Location::RequiresFpuRegister(); } Location Location::ByteRegisterOrConstant(int reg, HInstruction* instruction) { @@ -67,6 +76,12 @@ Location Location::ByteRegisterOrConstant(int reg, HInstruction* instruction) { : Location::RegisterLocation(reg); } +Location Location::FpuRegisterOrConstant(HInstruction* instruction) { + return instruction->IsConstant() + ? Location::ConstantLocation(instruction->AsConstant()) + : Location::RequiresFpuRegister(); +} + std::ostream& operator<<(std::ostream& os, const Location& location) { os << location.DebugString(); if (location.IsRegister() || location.IsFpuRegister()) { diff --git a/compiler/optimizing/locations.h b/compiler/optimizing/locations.h index d014379bca..1181007666 100644 --- a/compiler/optimizing/locations.h +++ b/compiler/optimizing/locations.h @@ -354,8 +354,10 @@ class Location : public ValueObject { } static Location RegisterOrConstant(HInstruction* instruction); - static Location RegisterOrInt32LongConstant(HInstruction* instruction); + static Location RegisterOrInt32Constant(HInstruction* instruction); static Location ByteRegisterOrConstant(int reg, HInstruction* instruction); + static Location FpuRegisterOrConstant(HInstruction* instruction); + static Location FpuRegisterOrInt32Constant(HInstruction* instruction); // The location of the first input to the instruction will be // used to replace this unallocated location. diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc index 8cb2cfc816..7e3c5e602e 100644 --- a/compiler/optimizing/optimizing_compiler.cc +++ b/compiler/optimizing/optimizing_compiler.cc @@ -56,6 +56,7 @@ #include "inliner.h" #include "instruction_simplifier.h" #include "intrinsics.h" +#include "jit/jit_code_cache.h" #include "licm.h" #include "jni/quick/jni_compiler.h" #include "load_store_elimination.h" @@ -258,15 +259,6 @@ class OptimizingCompiler FINAL : public Compiler { const DexFile& dex_file, Handle<mirror::DexCache> dex_cache) const OVERRIDE; - CompiledMethod* TryCompile(const DexFile::CodeItem* code_item, - uint32_t access_flags, - InvokeType invoke_type, - uint16_t class_def_idx, - uint32_t method_idx, - jobject class_loader, - const DexFile& dex_file, - Handle<mirror::DexCache> dex_cache) const; - CompiledMethod* JniCompile(uint32_t access_flags, uint32_t method_idx, const DexFile& dex_file) const OVERRIDE { @@ -291,23 +283,45 @@ class OptimizingCompiler FINAL : public Compiler { } } + bool JitCompile(Thread* self, jit::JitCodeCache* code_cache, ArtMethod* method) + OVERRIDE + SHARED_REQUIRES(Locks::mutator_lock_); + private: // Whether we should run any optimization or register allocation. If false, will // just run the code generation after the graph was built. const bool run_optimizations_; - // Optimize and compile `graph`. - CompiledMethod* CompileOptimized(HGraph* graph, - CodeGenerator* codegen, - CompilerDriver* driver, - const DexCompilationUnit& dex_compilation_unit, - PassObserver* pass_observer) const; - - // Just compile without doing optimizations. - CompiledMethod* CompileBaseline(CodeGenerator* codegen, - CompilerDriver* driver, - const DexCompilationUnit& dex_compilation_unit, - PassObserver* pass_observer) const; + // Create a 'CompiledMethod' for an optimized graph. + CompiledMethod* EmitOptimized(ArenaAllocator* arena, + CodeVectorAllocator* code_allocator, + CodeGenerator* codegen, + CompilerDriver* driver) const; + + // Create a 'CompiledMethod' for a non-optimized graph. + CompiledMethod* EmitBaseline(ArenaAllocator* arena, + CodeVectorAllocator* code_allocator, + CodeGenerator* codegen, + CompilerDriver* driver) const; + + // Try compiling a method and return the code generator used for + // compiling it. + // This method: + // 1) Builds the graph. Returns null if it failed to build it. + // 2) If `run_optimizations_` is set: + // 2.1) Transform the graph to SSA. Returns null if it failed. + // 2.2) Run optimizations on the graph, including register allocator. + // 3) Generate code with the `code_allocator` provided. + CodeGenerator* TryCompile(ArenaAllocator* arena, + CodeVectorAllocator* code_allocator, + const DexFile::CodeItem* code_item, + uint32_t access_flags, + InvokeType invoke_type, + uint16_t class_def_idx, + uint32_t method_idx, + jobject class_loader, + const DexFile& dex_file, + Handle<mirror::DexCache> dex_cache) const; std::unique_ptr<OptimizingCompilerStats> compilation_stats_; @@ -446,13 +460,32 @@ static void RunArchOptimizations(InstructionSet instruction_set, } } +NO_INLINE // Avoid increasing caller's frame size by large stack-allocated objects. +static void AllocateRegisters(HGraph* graph, + CodeGenerator* codegen, + PassObserver* pass_observer) { + PrepareForRegisterAllocation(graph).Run(); + SsaLivenessAnalysis liveness(graph, codegen); + { + PassScope scope(SsaLivenessAnalysis::kLivenessPassName, pass_observer); + liveness.Analyze(); + } + { + PassScope scope(RegisterAllocator::kRegisterAllocatorPassName, pass_observer); + RegisterAllocator(graph->GetArena(), codegen, liveness).AllocateRegisters(); + } +} + static void RunOptimizations(HGraph* graph, CodeGenerator* codegen, CompilerDriver* driver, OptimizingCompilerStats* stats, const DexCompilationUnit& dex_compilation_unit, - PassObserver* pass_observer, - StackHandleScopeCollection* handles) { + PassObserver* pass_observer) { + ScopedObjectAccess soa(Thread::Current()); + StackHandleScopeCollection handles(soa.Self()); + ScopedThreadSuspension sts(soa.Self(), kNative); + ArenaAllocator* arena = graph->GetArena(); HDeadCodeElimination* dce1 = new (arena) HDeadCodeElimination( graph, stats, HDeadCodeElimination::kInitialDeadCodeEliminationPassName); @@ -469,7 +502,7 @@ static void RunOptimizations(HGraph* graph, HInductionVarAnalysis* induction = new (arena) HInductionVarAnalysis(graph); BoundsCheckElimination* bce = new (arena) BoundsCheckElimination(graph, induction); ReferenceTypePropagation* type_propagation = - new (arena) ReferenceTypePropagation(graph, handles); + new (arena) ReferenceTypePropagation(graph, &handles); HSharpening* sharpening = new (arena) HSharpening(graph, codegen, dex_compilation_unit, driver); InstructionSimplifier* simplify2 = new (arena) InstructionSimplifier( graph, stats, "instruction_simplifier_after_types"); @@ -492,7 +525,7 @@ static void RunOptimizations(HGraph* graph, RunOptimizations(optimizations1, arraysize(optimizations1), pass_observer); - MaybeRunInliner(graph, codegen, driver, stats, dex_compilation_unit, pass_observer, handles); + MaybeRunInliner(graph, codegen, driver, stats, dex_compilation_unit, pass_observer, &handles); // TODO: Update passes incompatible with try/catch so we have the same // pipeline for all methods. @@ -532,6 +565,7 @@ static void RunOptimizations(HGraph* graph, } RunArchOptimizations(driver->GetInstructionSet(), graph, stats, pass_observer); + AllocateRegisters(graph, codegen, pass_observer); } // The stack map we generate must be 4-byte aligned on ARM. Since existing @@ -545,22 +579,6 @@ static ArrayRef<const uint8_t> AlignVectorSize(ArenaVector<uint8_t>& vector) { return ArrayRef<const uint8_t>(vector); } -NO_INLINE // Avoid increasing caller's frame size by large stack-allocated objects. -static void AllocateRegisters(HGraph* graph, - CodeGenerator* codegen, - PassObserver* pass_observer) { - PrepareForRegisterAllocation(graph).Run(); - SsaLivenessAnalysis liveness(graph, codegen); - { - PassScope scope(SsaLivenessAnalysis::kLivenessPassName, pass_observer); - liveness.Analyze(); - } - { - PassScope scope(RegisterAllocator::kRegisterAllocatorPassName, pass_observer); - RegisterAllocator(graph->GetArena(), codegen, liveness).AllocateRegisters(); - } -} - static ArenaVector<LinkerPatch> EmitAndSortLinkerPatches(CodeGenerator* codegen) { ArenaVector<LinkerPatch> linker_patches(codegen->GetGraph()->GetArena()->Adapter()); codegen->EmitLinkerPatches(&linker_patches); @@ -574,74 +592,42 @@ static ArenaVector<LinkerPatch> EmitAndSortLinkerPatches(CodeGenerator* codegen) return linker_patches; } -CompiledMethod* OptimizingCompiler::CompileOptimized(HGraph* graph, - CodeGenerator* codegen, - CompilerDriver* compiler_driver, - const DexCompilationUnit& dex_compilation_unit, - PassObserver* pass_observer) const { - ScopedObjectAccess soa(Thread::Current()); - StackHandleScopeCollection handles(soa.Self()); - soa.Self()->TransitionFromRunnableToSuspended(kNative); - RunOptimizations(graph, - codegen, - compiler_driver, - compilation_stats_.get(), - dex_compilation_unit, - pass_observer, - &handles); - - AllocateRegisters(graph, codegen, pass_observer); - - ArenaAllocator* arena = graph->GetArena(); - CodeVectorAllocator allocator(arena); - DefaultSrcMap src_mapping_table; - codegen->SetSrcMap(compiler_driver->GetCompilerOptions().GetGenerateDebugInfo() - ? &src_mapping_table - : nullptr); - codegen->CompileOptimized(&allocator); - +CompiledMethod* OptimizingCompiler::EmitOptimized(ArenaAllocator* arena, + CodeVectorAllocator* code_allocator, + CodeGenerator* codegen, + CompilerDriver* compiler_driver) const { ArenaVector<LinkerPatch> linker_patches = EmitAndSortLinkerPatches(codegen); - ArenaVector<uint8_t> stack_map(arena->Adapter(kArenaAllocStackMaps)); - codegen->BuildStackMaps(&stack_map); + stack_map.resize(codegen->ComputeStackMapsSize()); + codegen->BuildStackMaps(MemoryRegion(stack_map.data(), stack_map.size())); MaybeRecordStat(MethodCompilationStat::kCompiledOptimized); CompiledMethod* compiled_method = CompiledMethod::SwapAllocCompiledMethod( compiler_driver, codegen->GetInstructionSet(), - ArrayRef<const uint8_t>(allocator.GetMemory()), + ArrayRef<const uint8_t>(code_allocator->GetMemory()), // Follow Quick's behavior and set the frame size to zero if it is // considered "empty" (see the definition of // art::CodeGenerator::HasEmptyFrame). codegen->HasEmptyFrame() ? 0 : codegen->GetFrameSize(), codegen->GetCoreSpillMask(), codegen->GetFpuSpillMask(), - ArrayRef<const SrcMapElem>(src_mapping_table), + ArrayRef<const SrcMapElem>(codegen->GetSrcMappingTable()), ArrayRef<const uint8_t>(), // mapping_table. ArrayRef<const uint8_t>(stack_map), ArrayRef<const uint8_t>(), // native_gc_map. ArrayRef<const uint8_t>(*codegen->GetAssembler()->cfi().data()), ArrayRef<const LinkerPatch>(linker_patches)); - pass_observer->DumpDisassembly(); - soa.Self()->TransitionFromSuspendedToRunnable(); return compiled_method; } -CompiledMethod* OptimizingCompiler::CompileBaseline( +CompiledMethod* OptimizingCompiler::EmitBaseline( + ArenaAllocator* arena, + CodeVectorAllocator* code_allocator, CodeGenerator* codegen, - CompilerDriver* compiler_driver, - const DexCompilationUnit& dex_compilation_unit, - PassObserver* pass_observer) const { - ArenaAllocator* arena = codegen->GetGraph()->GetArena(); - CodeVectorAllocator allocator(arena); - DefaultSrcMap src_mapping_table; - codegen->SetSrcMap(compiler_driver->GetCompilerOptions().GetGenerateDebugInfo() - ? &src_mapping_table - : nullptr); - codegen->CompileBaseline(&allocator); - + CompilerDriver* compiler_driver) const { ArenaVector<LinkerPatch> linker_patches = EmitAndSortLinkerPatches(codegen); ArenaVector<uint8_t> mapping_table(arena->Adapter(kArenaAllocBaselineMaps)); @@ -649,37 +635,38 @@ CompiledMethod* OptimizingCompiler::CompileBaseline( ArenaVector<uint8_t> vmap_table(arena->Adapter(kArenaAllocBaselineMaps)); codegen->BuildVMapTable(&vmap_table); ArenaVector<uint8_t> gc_map(arena->Adapter(kArenaAllocBaselineMaps)); - codegen->BuildNativeGCMap(&gc_map, dex_compilation_unit); + codegen->BuildNativeGCMap(&gc_map, *compiler_driver); MaybeRecordStat(MethodCompilationStat::kCompiledBaseline); CompiledMethod* compiled_method = CompiledMethod::SwapAllocCompiledMethod( compiler_driver, codegen->GetInstructionSet(), - ArrayRef<const uint8_t>(allocator.GetMemory()), + ArrayRef<const uint8_t>(code_allocator->GetMemory()), // Follow Quick's behavior and set the frame size to zero if it is // considered "empty" (see the definition of // art::CodeGenerator::HasEmptyFrame). codegen->HasEmptyFrame() ? 0 : codegen->GetFrameSize(), codegen->GetCoreSpillMask(), codegen->GetFpuSpillMask(), - ArrayRef<const SrcMapElem>(src_mapping_table), + ArrayRef<const SrcMapElem>(codegen->GetSrcMappingTable()), AlignVectorSize(mapping_table), AlignVectorSize(vmap_table), AlignVectorSize(gc_map), ArrayRef<const uint8_t>(*codegen->GetAssembler()->cfi().data()), ArrayRef<const LinkerPatch>(linker_patches)); - pass_observer->DumpDisassembly(); return compiled_method; } -CompiledMethod* OptimizingCompiler::TryCompile(const DexFile::CodeItem* code_item, - uint32_t access_flags, - InvokeType invoke_type, - uint16_t class_def_idx, - uint32_t method_idx, - jobject class_loader, - const DexFile& dex_file, - Handle<mirror::DexCache> dex_cache) const { +CodeGenerator* OptimizingCompiler::TryCompile(ArenaAllocator* arena, + CodeVectorAllocator* code_allocator, + const DexFile::CodeItem* code_item, + uint32_t access_flags, + InvokeType invoke_type, + uint16_t class_def_idx, + uint32_t method_idx, + jobject class_loader, + const DexFile& dex_file, + Handle<mirror::DexCache> dex_cache) const { std::string method_name = PrettyMethod(method_idx, dex_file); MaybeRecordStat(MethodCompilationStat::kAttemptCompilation); CompilerDriver* compiler_driver = GetCompilerDriver(); @@ -721,13 +708,10 @@ CompiledMethod* OptimizingCompiler::TryCompile(const DexFile::CodeItem* code_ite && compiler_driver->RequiresConstructorBarrier(Thread::Current(), dex_compilation_unit.GetDexFile(), dex_compilation_unit.GetClassDefIndex()); - ArenaAllocator arena(Runtime::Current()->GetArenaPool()); - HGraph* graph = new (&arena) HGraph( - &arena, dex_file, method_idx, requires_barrier, compiler_driver->GetInstructionSet(), + HGraph* graph = new (arena) HGraph( + arena, dex_file, method_idx, requires_barrier, compiler_driver->GetInstructionSet(), kInvalidInvokeType, compiler_driver->GetCompilerOptions().GetDebuggable()); - bool shouldOptimize = method_name.find("$opt$reg$") != std::string::npos && run_optimizations_; - std::unique_ptr<CodeGenerator> codegen( CodeGenerator::Create(graph, instruction_set, @@ -779,16 +763,8 @@ CompiledMethod* OptimizingCompiler::TryCompile(const DexFile::CodeItem* code_ite } } - bool can_allocate_registers = RegisterAllocator::CanAllocateRegistersFor(*graph, instruction_set); - - // `run_optimizations_` is set explicitly (either through a compiler filter - // or the debuggable flag). If it is set, we can run baseline. Otherwise, we fall back - // to Quick. - bool can_use_baseline = !run_optimizations_ && builder.CanUseBaselineForStringInit(); - CompiledMethod* compiled_method = nullptr; - if (run_optimizations_ && can_allocate_registers) { - VLOG(compiler) << "Optimizing " << method_name; - + VLOG(compiler) << "Optimizing " << method_name; + if (run_optimizations_) { { PassScope scope(SsaBuilder::kSsaBuilderPassName, &pass_observer); if (!graph->TryBuildingSsa()) { @@ -800,37 +776,26 @@ CompiledMethod* OptimizingCompiler::TryCompile(const DexFile::CodeItem* code_ite } } - compiled_method = CompileOptimized(graph, - codegen.get(), - compiler_driver, - dex_compilation_unit, - &pass_observer); - } else if (shouldOptimize && can_allocate_registers) { - LOG(FATAL) << "Could not allocate registers in optimizing compiler"; - UNREACHABLE(); - } else if (can_use_baseline) { - VLOG(compiler) << "Compile baseline " << method_name; - - if (!run_optimizations_) { - MaybeRecordStat(MethodCompilationStat::kNotOptimizedDisabled); - } else if (!can_allocate_registers) { - MaybeRecordStat(MethodCompilationStat::kNotOptimizedRegisterAllocator); - } - - compiled_method = CompileBaseline(codegen.get(), - compiler_driver, - dex_compilation_unit, - &pass_observer); + RunOptimizations(graph, + codegen.get(), + compiler_driver, + compilation_stats_.get(), + dex_compilation_unit, + &pass_observer); + codegen->CompileOptimized(code_allocator); + } else { + codegen->CompileBaseline(code_allocator); } + pass_observer.DumpDisassembly(); if (kArenaAllocatorCountAllocations) { - if (arena.BytesAllocated() > 4 * MB) { - MemStats mem_stats(arena.GetMemStats()); + if (arena->BytesAllocated() > 4 * MB) { + MemStats mem_stats(arena->GetMemStats()); LOG(INFO) << PrettyMethod(method_idx, dex_file) << " " << Dumpable<MemStats>(mem_stats); } } - return compiled_method; + return codegen.release(); } static bool CanHandleVerificationFailure(const VerifiedMethod* verified_method) { @@ -852,26 +817,37 @@ CompiledMethod* OptimizingCompiler::Compile(const DexFile::CodeItem* code_item, Handle<mirror::DexCache> dex_cache) const { CompilerDriver* compiler_driver = GetCompilerDriver(); CompiledMethod* method = nullptr; - if (Runtime::Current()->IsAotCompiler()) { - const VerifiedMethod* verified_method = compiler_driver->GetVerifiedMethod(&dex_file, method_idx); - DCHECK(!verified_method->HasRuntimeThrow()); - if (compiler_driver->IsMethodVerifiedWithoutFailures(method_idx, class_def_idx, dex_file) - || CanHandleVerificationFailure(verified_method)) { - method = TryCompile(code_item, access_flags, invoke_type, class_def_idx, - method_idx, jclass_loader, dex_file, dex_cache); - } else { - if (compiler_driver->GetCompilerOptions().VerifyAtRuntime()) { - MaybeRecordStat(MethodCompilationStat::kNotCompiledVerifyAtRuntime); + DCHECK(Runtime::Current()->IsAotCompiler()); + const VerifiedMethod* verified_method = compiler_driver->GetVerifiedMethod(&dex_file, method_idx); + DCHECK(!verified_method->HasRuntimeThrow()); + if (compiler_driver->IsMethodVerifiedWithoutFailures(method_idx, class_def_idx, dex_file) + || CanHandleVerificationFailure(verified_method)) { + ArenaAllocator arena(Runtime::Current()->GetArenaPool()); + CodeVectorAllocator code_allocator(&arena); + std::unique_ptr<CodeGenerator> codegen( + TryCompile(&arena, + &code_allocator, + code_item, + access_flags, + invoke_type, + class_def_idx, + method_idx, + jclass_loader, + dex_file, + dex_cache)); + if (codegen.get() != nullptr) { + if (run_optimizations_) { + method = EmitOptimized(&arena, &code_allocator, codegen.get(), compiler_driver); } else { - MaybeRecordStat(MethodCompilationStat::kNotCompiledClassNotVerified); + method = EmitBaseline(&arena, &code_allocator, codegen.get(), compiler_driver); } } } else { - // This is for the JIT compiler, which has already ensured the class is verified. - // We can go straight to compiling. - DCHECK(Runtime::Current()->UseJit()); - method = TryCompile(code_item, access_flags, invoke_type, class_def_idx, - method_idx, jclass_loader, dex_file, dex_cache); + if (compiler_driver->GetCompilerOptions().VerifyAtRuntime()) { + MaybeRecordStat(MethodCompilationStat::kNotCompiledVerifyAtRuntime); + } else { + MaybeRecordStat(MethodCompilationStat::kNotCompiledClassNotVerified); + } } if (kIsDebugBuild && @@ -896,4 +872,70 @@ bool IsCompilingWithCoreImage() { return EndsWith(image, "core.art") || EndsWith(image, "core-optimizing.art"); } +bool OptimizingCompiler::JitCompile(Thread* self, + jit::JitCodeCache* code_cache, + ArtMethod* method) { + StackHandleScope<2> hs(self); + Handle<mirror::ClassLoader> class_loader(hs.NewHandle( + method->GetDeclaringClass()->GetClassLoader())); + Handle<mirror::DexCache> dex_cache(hs.NewHandle(method->GetDexCache())); + + jobject jclass_loader = class_loader.ToJObject(); + const DexFile* dex_file = method->GetDexFile(); + const uint16_t class_def_idx = method->GetClassDefIndex(); + const DexFile::CodeItem* code_item = dex_file->GetCodeItem(method->GetCodeItemOffset()); + const uint32_t method_idx = method->GetDexMethodIndex(); + const uint32_t access_flags = method->GetAccessFlags(); + const InvokeType invoke_type = method->GetInvokeType(); + + ArenaAllocator arena(Runtime::Current()->GetArenaPool()); + CodeVectorAllocator code_allocator(&arena); + std::unique_ptr<CodeGenerator> codegen; + { + // Go to native so that we don't block GC during compilation. + ScopedThreadSuspension sts(self, kNative); + + DCHECK(run_optimizations_); + codegen.reset( + TryCompile(&arena, + &code_allocator, + code_item, + access_flags, + invoke_type, + class_def_idx, + method_idx, + jclass_loader, + *dex_file, + dex_cache)); + if (codegen.get() == nullptr) { + return false; + } + } + + size_t stack_map_size = codegen->ComputeStackMapsSize(); + uint8_t* stack_map_data = code_cache->ReserveData(self, stack_map_size); + if (stack_map_data == nullptr) { + return false; + } + codegen->BuildStackMaps(MemoryRegion(stack_map_data, stack_map_size)); + const void* code = code_cache->CommitCode( + self, + method, + nullptr, + stack_map_data, + nullptr, + codegen->HasEmptyFrame() ? 0 : codegen->GetFrameSize(), + codegen->GetCoreSpillMask(), + codegen->GetFpuSpillMask(), + code_allocator.GetMemory().data(), + code_allocator.GetSize()); + + if (code == nullptr) { + code_cache->ClearData(self, stack_map_data); + return false; + } + + return true; +} + } // namespace art diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 2653807369..92ed58cb86 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -1355,9 +1355,20 @@ class Dex2Oat FINAL { uint32_t image_file_location_oat_checksum = 0; uintptr_t image_file_location_oat_data_begin = 0; int32_t image_patch_delta = 0; + + if (app_image_ && image_base_ == 0) { + gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace(); + image_base_ = RoundUp( + reinterpret_cast<uintptr_t>(image_space->GetImageHeader().GetOatFileEnd()), + kPageSize); + VLOG(compiler) << "App image base=" << reinterpret_cast<void*>(image_base_); + } + if (IsImage()) { PrepareImageWriter(image_base_); - } else { + } + + if (!IsBootImage()) { TimingLogger::ScopedTiming t3("Loading image checksum", timings_); gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace(); image_file_location_oat_checksum = image_space->GetImageHeader().GetOatChecksum(); @@ -1371,11 +1382,13 @@ class Dex2Oat FINAL { key_value_store_->Put(OatHeader::kImageLocationKey, image_file_location); } - oat_writer.reset(new OatWriter(dex_files_, image_file_location_oat_checksum, + oat_writer.reset(new OatWriter(dex_files_, + image_file_location_oat_checksum, image_file_location_oat_data_begin, image_patch_delta, driver_.get(), image_writer_.get(), + IsBootImage(), timings_, key_value_store_.get())); } @@ -1591,7 +1604,11 @@ class Dex2Oat FINAL { } void PrepareImageWriter(uintptr_t image_base) { - image_writer_.reset(new ImageWriter(*driver_, image_base, compiler_options_->GetCompilePic())); + DCHECK(IsImage()); + image_writer_.reset(new ImageWriter(*driver_, + image_base, + compiler_options_->GetCompilePic(), + IsAppImage())); } // Let the ImageWriter write the image file. If we do not compile PIC, also fix up the oat file. diff --git a/runtime/arch/mips/registers_mips.h b/runtime/arch/mips/registers_mips.h index 0f784ed43f..1096af0131 100644 --- a/runtime/arch/mips/registers_mips.h +++ b/runtime/arch/mips/registers_mips.h @@ -59,6 +59,8 @@ enum Register { SP = 29, // Stack pointer. FP = 30, // Saved value/frame pointer. RA = 31, // Return address. + TR = S1, // ART Thread Register + TMP = T8, // scratch register (in addition to AT) kNumberOfCoreRegisters = 32, kNoRegister = -1 // Signals an illegal register. }; diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index cdb7712211..5dac95d3b3 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -876,8 +876,8 @@ void ClassLinker::InitFromImage() { hs.NewHandle(dex_caches_object->AsObjectArray<mirror::DexCache>())); Handle<mirror::ObjectArray<mirror::Class>> class_roots(hs.NewHandle( - space->GetImageHeader().GetImageRoot(ImageHeader::kClassRoots)-> - AsObjectArray<mirror::Class>())); + space->GetImageHeader().GetImageRoot(ImageHeader::kClassRoots)-> + AsObjectArray<mirror::Class>())); class_roots_ = GcRoot<mirror::ObjectArray<mirror::Class>>(class_roots.Get()); // Special case of setting up the String class early so that we can test arbitrary objects @@ -886,7 +886,7 @@ void ClassLinker::InitFromImage() { mirror::Class* java_lang_Object = GetClassRoot(kJavaLangObject); java_lang_Object->SetObjectSize(sizeof(mirror::Object)); - Runtime::Current()->SetSentinel(Runtime::Current()->GetHeap()->AllocObject<true>(self, + Runtime::Current()->SetSentinel(heap->AllocObject<true>(self, java_lang_Object, java_lang_Object->GetObjectSize(), VoidFunctor())); @@ -2155,8 +2155,6 @@ void ClassLinker::LoadClassMembers(Thread* self, last_field_idx = field_idx; } } - klass->SetSFieldsPtr(sfields); - DCHECK_EQ(klass->NumStaticFields(), num_sfields); // Load instance fields. LengthPrefixedArray<ArtField>* ifields = AllocArtFieldArray(self, allocator, @@ -2178,8 +2176,17 @@ void ClassLinker::LoadClassMembers(Thread* self, LOG(WARNING) << "Duplicate fields in class " << PrettyDescriptor(klass.Get()) << " (unique static fields: " << num_sfields << "/" << it.NumStaticFields() << ", unique instance fields: " << num_ifields << "/" << it.NumInstanceFields() << ")"; - // NOTE: Not shrinking the over-allocated sfields/ifields. + // NOTE: Not shrinking the over-allocated sfields/ifields, just setting size. + if (sfields != nullptr) { + sfields->SetSize(num_sfields); + } + if (ifields != nullptr) { + ifields->SetSize(num_ifields); + } } + // Set the field arrays. + klass->SetSFieldsPtr(sfields); + DCHECK_EQ(klass->NumStaticFields(), num_sfields); klass->SetIFieldsPtr(ifields); DCHECK_EQ(klass->NumInstanceFields(), num_ifields); // Load methods. diff --git a/runtime/debugger.cc b/runtime/debugger.cc index e523fbb104..6e4b7a3e10 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -4069,13 +4069,15 @@ void Dbg::ExecuteMethodWithoutPendingException(ScopedObjectAccess& soa, DebugInv if (is_constructor) { // If we invoked a constructor (which actually returns void), return the receiver, // unless we threw, in which case we return null. - result_tag = JDWP::JT_OBJECT; + DCHECK_EQ(JDWP::JT_VOID, result_tag); if (exceptionObjectId == 0) { // TODO we could keep the receiver ObjectId in the DebugInvokeReq to avoid looking into the // object registry. result_value = GetObjectRegistry()->Add(pReq->receiver.Read()); + result_tag = TagFromObject(soa, pReq->receiver.Read()); } else { result_value = 0; + result_tag = JDWP::JT_OBJECT; } } diff --git a/runtime/entrypoints/entrypoint_utils.cc b/runtime/entrypoints/entrypoint_utils.cc index e57569e140..87e29ae3c3 100644 --- a/runtime/entrypoints/entrypoint_utils.cc +++ b/runtime/entrypoints/entrypoint_utils.cc @@ -364,36 +364,34 @@ ArtMethod* GetCalleeSaveMethodCaller(ArtMethod** sp, (reinterpret_cast<uint8_t*>(sp) + callee_return_pc_offset)); ArtMethod* outer_method = *caller_sp; ArtMethod* caller = outer_method; - - if (outer_method != nullptr) { - const OatQuickMethodHeader* current_code = outer_method->GetOatQuickMethodHeader(caller_pc); - if (current_code->IsOptimized()) { - if (LIKELY(caller_pc != reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()))) { - uintptr_t native_pc_offset = current_code->NativeQuickPcOffset(caller_pc); - CodeInfo code_info = current_code->GetOptimizedCodeInfo(); - StackMapEncoding encoding = code_info.ExtractEncoding(); - StackMap stack_map = code_info.GetStackMapForNativePcOffset(native_pc_offset, encoding); - DCHECK(stack_map.IsValid()); - if (stack_map.HasInlineInfo(encoding)) { - InlineInfo inline_info = code_info.GetInlineInfoOf(stack_map, encoding); - caller = GetResolvedMethod(outer_method, inline_info, inline_info.GetDepth() - 1); - } - } else { - // We're instrumenting, just use the StackVisitor which knows how to - // handle instrumented frames. - NthCallerVisitor visitor(Thread::Current(), 1, true); - visitor.WalkStack(); - caller = visitor.caller; + if (LIKELY(caller_pc != reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()))) { + if (outer_method != nullptr) { + const OatQuickMethodHeader* current_code = outer_method->GetOatQuickMethodHeader(caller_pc); + if (current_code->IsOptimized()) { + uintptr_t native_pc_offset = current_code->NativeQuickPcOffset(caller_pc); + CodeInfo code_info = current_code->GetOptimizedCodeInfo(); + StackMapEncoding encoding = code_info.ExtractEncoding(); + StackMap stack_map = code_info.GetStackMapForNativePcOffset(native_pc_offset, encoding); + DCHECK(stack_map.IsValid()); + if (stack_map.HasInlineInfo(encoding)) { + InlineInfo inline_info = code_info.GetInlineInfoOf(stack_map, encoding); + caller = GetResolvedMethod(outer_method, inline_info, inline_info.GetDepth() - 1); + } } } - } - - if (kIsDebugBuild && do_caller_check) { - // Note that do_caller_check is optional, as this method can be called by - // stubs, and tests without a proper call stack. + if (kIsDebugBuild && do_caller_check) { + // Note that do_caller_check is optional, as this method can be called by + // stubs, and tests without a proper call stack. + NthCallerVisitor visitor(Thread::Current(), 1, true); + visitor.WalkStack(); + CHECK_EQ(caller, visitor.caller); + } + } else { + // We're instrumenting, just use the StackVisitor which knows how to + // handle instrumented frames. NthCallerVisitor visitor(Thread::Current(), 1, true); visitor.WalkStack(); - CHECK_EQ(caller, visitor.caller); + caller = visitor.caller; } return caller; diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc index 5afd28e7b5..f69115159f 100644 --- a/runtime/jit/jit.cc +++ b/runtime/jit/jit.cc @@ -26,7 +26,6 @@ #include "jit_instrumentation.h" #include "runtime.h" #include "runtime_options.h" -#include "thread_list.h" #include "utils.h" namespace art { @@ -145,7 +144,7 @@ void Jit::CreateThreadPool() { void Jit::DeleteThreadPool() { if (instrumentation_cache_.get() != nullptr) { - instrumentation_cache_->DeleteThreadPool(); + instrumentation_cache_->DeleteThreadPool(Thread::Current()); } } @@ -164,16 +163,8 @@ Jit::~Jit() { void Jit::CreateInstrumentationCache(size_t compile_threshold, size_t warmup_threshold) { CHECK_GT(compile_threshold, 0U); - ScopedSuspendAll ssa(__FUNCTION__); - // Add Jit interpreter instrumentation, tells the interpreter when to notify the jit to compile - // something. instrumentation_cache_.reset( new jit::JitInstrumentationCache(compile_threshold, warmup_threshold)); - Runtime::Current()->GetInstrumentation()->AddListener( - new jit::JitInstrumentationListener(instrumentation_cache_.get()), - instrumentation::Instrumentation::kMethodEntered | - instrumentation::Instrumentation::kBackwardBranch | - instrumentation::Instrumentation::kInvokeVirtualOrInterface); } } // namespace jit diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc index 4c7cb1e36a..fbcba1b881 100644 --- a/runtime/jit/jit_code_cache.cc +++ b/runtime/jit/jit_code_cache.cc @@ -248,40 +248,49 @@ uint8_t* JitCodeCache::CommitCodeInternal(Thread* self, OatQuickMethodHeader* method_header = nullptr; uint8_t* code_ptr = nullptr; - - ScopedThreadSuspension sts(self, kSuspended); - MutexLock mu(self, lock_); - WaitForPotentialCollectionToComplete(self); { - ScopedCodeCacheWrite scc(code_map_.get()); - uint8_t* result = reinterpret_cast<uint8_t*>( - mspace_memalign(code_mspace_, alignment, total_size)); - if (result == nullptr) { - return nullptr; + ScopedThreadSuspension sts(self, kSuspended); + MutexLock mu(self, lock_); + WaitForPotentialCollectionToComplete(self); + { + ScopedCodeCacheWrite scc(code_map_.get()); + uint8_t* result = reinterpret_cast<uint8_t*>( + mspace_memalign(code_mspace_, alignment, total_size)); + if (result == nullptr) { + return nullptr; + } + code_ptr = result + header_size; + DCHECK_ALIGNED_PARAM(reinterpret_cast<uintptr_t>(code_ptr), alignment); + + std::copy(code, code + code_size, code_ptr); + method_header = OatQuickMethodHeader::FromCodePointer(code_ptr); + new (method_header) OatQuickMethodHeader( + (mapping_table == nullptr) ? 0 : code_ptr - mapping_table, + (vmap_table == nullptr) ? 0 : code_ptr - vmap_table, + (gc_map == nullptr) ? 0 : code_ptr - gc_map, + frame_size_in_bytes, + core_spill_mask, + fp_spill_mask, + code_size); } - code_ptr = result + header_size; - DCHECK_ALIGNED_PARAM(reinterpret_cast<uintptr_t>(code_ptr), alignment); - - std::copy(code, code + code_size, code_ptr); - method_header = OatQuickMethodHeader::FromCodePointer(code_ptr); - new (method_header) OatQuickMethodHeader( - (mapping_table == nullptr) ? 0 : code_ptr - mapping_table, - (vmap_table == nullptr) ? 0 : code_ptr - vmap_table, - (gc_map == nullptr) ? 0 : code_ptr - gc_map, - frame_size_in_bytes, - core_spill_mask, - fp_spill_mask, - code_size); - } - - __builtin___clear_cache(reinterpret_cast<char*>(code_ptr), - reinterpret_cast<char*>(code_ptr + code_size)); - method_code_map_.Put(code_ptr, method); - // We have checked there was no collection in progress earlier. If we - // were, setting the entry point of a method would be unsafe, as the collection - // could delete it. - DCHECK(!collection_in_progress_); - method->SetEntryPointFromQuickCompiledCode(method_header->GetEntryPoint()); + + __builtin___clear_cache(reinterpret_cast<char*>(code_ptr), + reinterpret_cast<char*>(code_ptr + code_size)); + method_code_map_.Put(code_ptr, method); + // We have checked there was no collection in progress earlier. If we + // were, setting the entry point of a method would be unsafe, as the collection + // could delete it. + DCHECK(!collection_in_progress_); + method->SetEntryPointFromQuickCompiledCode(method_header->GetEntryPoint()); + } + VLOG(jit) + << "JIT added " + << PrettyMethod(method) << "@" << method + << " ccache_size=" << PrettySize(CodeCacheSize()) << ": " + << " dcache_size=" << PrettySize(DataCacheSize()) << ": " + << reinterpret_cast<const void*>(method_header->GetEntryPoint()) << "," + << reinterpret_cast<const void*>(method_header->GetEntryPoint() + method_header->code_size_); + return reinterpret_cast<uint8_t*>(method_header); } @@ -304,6 +313,11 @@ size_t JitCodeCache::NumberOfCompiledCode() { return method_code_map_.size(); } +void JitCodeCache::ClearData(Thread* self, void* data) { + MutexLock mu(self, lock_); + mspace_free(data_mspace_, data); +} + uint8_t* JitCodeCache::ReserveData(Thread* self, size_t size) { size = RoundUp(size, sizeof(void*)); uint8_t* result = nullptr; diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h index e10f9629ae..afff657880 100644 --- a/runtime/jit/jit_code_cache.h +++ b/runtime/jit/jit_code_cache.h @@ -88,6 +88,11 @@ class JitCodeCache { SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!lock_); + // Clear data from the data portion of the code cache. + void ClearData(Thread* self, void* data) + SHARED_REQUIRES(Locks::mutator_lock_) + REQUIRES(!lock_); + // Add a data array of size (end - begin) with the associated contents, returns null if there // is no more room. uint8_t* AddDataArray(Thread* self, const uint8_t* begin, const uint8_t* end) diff --git a/runtime/jit/jit_instrumentation.cc b/runtime/jit/jit_instrumentation.cc index 7931306ff6..6531325de5 100644 --- a/runtime/jit/jit_instrumentation.cc +++ b/runtime/jit/jit_instrumentation.cc @@ -20,6 +20,7 @@ #include "jit.h" #include "jit_code_cache.h" #include "scoped_thread_state_change.h" +#include "thread_list.h" namespace art { namespace jit { @@ -73,16 +74,48 @@ class JitCompileTask FINAL : public Task { JitInstrumentationCache::JitInstrumentationCache(size_t hot_method_threshold, size_t warm_method_threshold) : hot_method_threshold_(hot_method_threshold), - warm_method_threshold_(warm_method_threshold) { + warm_method_threshold_(warm_method_threshold), + listener_(this) { } void JitInstrumentationCache::CreateThreadPool() { + // Create the thread pool before setting the instrumentation, so that + // when the threads stopped being suspended, they can use it directly. + // There is a DCHECK in the 'AddSamples' method to ensure the tread pool + // is not null when we instrument. thread_pool_.reset(new ThreadPool("Jit thread pool", 1)); + thread_pool_->StartWorkers(Thread::Current()); + { + // Add Jit interpreter instrumentation, tells the interpreter when + // to notify the jit to compile something. + ScopedSuspendAll ssa(__FUNCTION__); + Runtime::Current()->GetInstrumentation()->AddListener( + &listener_, JitInstrumentationListener::kJitEvents); + } } -void JitInstrumentationCache::DeleteThreadPool() { - DCHECK(Runtime::Current()->IsShuttingDown(Thread::Current())); - thread_pool_.reset(); +void JitInstrumentationCache::DeleteThreadPool(Thread* self) { + DCHECK(Runtime::Current()->IsShuttingDown(self)); + if (thread_pool_ != nullptr) { + // First remove the listener, to avoid having mutators enter + // 'AddSamples'. + ThreadPool* cache = nullptr; + { + ScopedSuspendAll ssa(__FUNCTION__); + Runtime::Current()->GetInstrumentation()->RemoveListener( + &listener_, JitInstrumentationListener::kJitEvents); + // Clear thread_pool_ field while the threads are suspended. + // A mutator in the 'AddSamples' method will check against it. + cache = thread_pool_.release(); + } + cache->StopWorkers(self); + cache->RemoveAllTasks(self); + // We could just suspend all threads, but we know those threads + // will finish in a short period, so it's not worth adding a suspend logic + // here. Besides, this is only done for shutdown. + cache->Wait(self, false, false); + delete cache; + } } void JitInstrumentationCache::AddSamples(Thread* self, ArtMethod* method, size_t) { @@ -91,25 +124,32 @@ void JitInstrumentationCache::AddSamples(Thread* self, ArtMethod* method, size_t if (method->IsClassInitializer() || method->IsNative()) { return; } - if (thread_pool_.get() == nullptr) { - DCHECK(Runtime::Current()->IsShuttingDown(self)); - return; - } + DCHECK(thread_pool_ != nullptr); + uint16_t sample_count = method->IncrementCounter(); if (sample_count == warm_method_threshold_) { - if (ProfilingInfo::Create(self, method, /* retry_allocation */ false)) { + bool success = ProfilingInfo::Create(self, method, /* retry_allocation */ false); + if (success) { VLOG(jit) << "Start profiling " << PrettyMethod(method); - } else { + } + + if (thread_pool_ == nullptr) { + // Calling ProfilingInfo::Create might put us in a suspended state, which could + // lead to the thread pool being deleted when we are shutting down. + DCHECK(Runtime::Current()->IsShuttingDown(self)); + return; + } + + if (!success) { // We failed allocating. Instead of doing the collection on the Java thread, we push // an allocation to a compiler thread, that will do the collection. thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kAllocateProfile)); - thread_pool_->StartWorkers(self); } } if (sample_count == hot_method_threshold_) { + DCHECK(thread_pool_ != nullptr); thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kCompile)); - thread_pool_->StartWorkers(self); } } @@ -118,6 +158,20 @@ JitInstrumentationListener::JitInstrumentationListener(JitInstrumentationCache* CHECK(instrumentation_cache_ != nullptr); } +void JitInstrumentationListener::MethodEntered(Thread* thread, + mirror::Object* /*this_object*/, + ArtMethod* method, + uint32_t /*dex_pc*/) { + instrumentation_cache_->AddSamples(thread, method, 1); +} + +void JitInstrumentationListener::BackwardBranch(Thread* thread, + ArtMethod* method, + int32_t dex_pc_offset) { + CHECK_LE(dex_pc_offset, 0); + instrumentation_cache_->AddSamples(thread, method, 1); +} + void JitInstrumentationListener::InvokeVirtualOrInterface(Thread* thread, mirror::Object* this_object, ArtMethod* caller, @@ -138,7 +192,9 @@ void JitInstrumentationListener::InvokeVirtualOrInterface(Thread* thread, } void JitInstrumentationCache::WaitForCompilationToFinish(Thread* self) { - thread_pool_->Wait(self, false, false); + if (thread_pool_ != nullptr) { + thread_pool_->Wait(self, false, false); + } } } // namespace jit diff --git a/runtime/jit/jit_instrumentation.h b/runtime/jit/jit_instrumentation.h index 9eb464b841..1f96d59888 100644 --- a/runtime/jit/jit_instrumentation.h +++ b/runtime/jit/jit_instrumentation.h @@ -31,7 +31,6 @@ namespace art { namespace mirror { - class Class; class Object; class Throwable; } // namespace mirror @@ -42,24 +41,7 @@ class Thread; namespace jit { -// Keeps track of which methods are hot. -class JitInstrumentationCache { - public: - JitInstrumentationCache(size_t hot_method_threshold, size_t warm_method_threshold); - void AddSamples(Thread* self, ArtMethod* method, size_t samples) - SHARED_REQUIRES(Locks::mutator_lock_); - void CreateThreadPool(); - void DeleteThreadPool(); - // Wait until there is no more pending compilation tasks. - void WaitForCompilationToFinish(Thread* self); - - private: - size_t hot_method_threshold_; - size_t warm_method_threshold_; - std::unique_ptr<ThreadPool> thread_pool_; - - DISALLOW_IMPLICIT_CONSTRUCTORS(JitInstrumentationCache); -}; +class JitInstrumentationCache; class JitInstrumentationListener : public instrumentation::InstrumentationListener { public: @@ -67,9 +49,8 @@ class JitInstrumentationListener : public instrumentation::InstrumentationListen void MethodEntered(Thread* thread, mirror::Object* /*this_object*/, ArtMethod* method, uint32_t /*dex_pc*/) - OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) { - instrumentation_cache_->AddSamples(thread, method, 1); - } + OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_); + void MethodExited(Thread* /*thread*/, mirror::Object* /*this_object*/, ArtMethod* /*method*/, uint32_t /*dex_pc*/, const JValue& /*return_value*/) @@ -90,10 +71,7 @@ class JitInstrumentationListener : public instrumentation::InstrumentationListen ArtMethod* /*method*/, uint32_t /*new_dex_pc*/) OVERRIDE { } void BackwardBranch(Thread* thread, ArtMethod* method, int32_t dex_pc_offset) - OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) { - CHECK_LE(dex_pc_offset, 0); - instrumentation_cache_->AddSamples(thread, method, 1); - } + OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_); void InvokeVirtualOrInterface(Thread* thread, mirror::Object* this_object, @@ -102,12 +80,37 @@ class JitInstrumentationListener : public instrumentation::InstrumentationListen ArtMethod* callee) OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_); + static constexpr uint32_t kJitEvents = + instrumentation::Instrumentation::kMethodEntered | + instrumentation::Instrumentation::kBackwardBranch | + instrumentation::Instrumentation::kInvokeVirtualOrInterface; + private: JitInstrumentationCache* const instrumentation_cache_; DISALLOW_IMPLICIT_CONSTRUCTORS(JitInstrumentationListener); }; +// Keeps track of which methods are hot. +class JitInstrumentationCache { + public: + JitInstrumentationCache(size_t hot_method_threshold, size_t warm_method_threshold); + void AddSamples(Thread* self, ArtMethod* method, size_t samples) + SHARED_REQUIRES(Locks::mutator_lock_); + void CreateThreadPool(); + void DeleteThreadPool(Thread* self); + // Wait until there is no more pending compilation tasks. + void WaitForCompilationToFinish(Thread* self); + + private: + size_t hot_method_threshold_; + size_t warm_method_threshold_; + JitInstrumentationListener listener_; + std::unique_ptr<ThreadPool> thread_pool_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(JitInstrumentationCache); +}; + } // namespace jit } // namespace art diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc index 234a733967..415109fb06 100644 --- a/runtime/jni_internal.cc +++ b/runtime/jni_internal.cc @@ -1670,7 +1670,7 @@ class JNI { CHECK_NON_NULL_ARGUMENT_RETURN_VOID(java_string); ScopedObjectAccess soa(env); mirror::String* s = soa.Decode<mirror::String*>(java_string); - if (start < 0 || length < 0 || start + length > s->GetLength()) { + if (start < 0 || length < 0 || length > s->GetLength() - start) { ThrowSIOOBE(soa, start, length, s->GetLength()); } else { CHECK_NON_NULL_MEMCPY_ARGUMENT(length, buf); @@ -1684,7 +1684,7 @@ class JNI { CHECK_NON_NULL_ARGUMENT_RETURN_VOID(java_string); ScopedObjectAccess soa(env); mirror::String* s = soa.Decode<mirror::String*>(java_string); - if (start < 0 || length < 0 || start + length > s->GetLength()) { + if (start < 0 || length < 0 || length > s->GetLength() - start) { ThrowSIOOBE(soa, start, length, s->GetLength()); } else { CHECK_NON_NULL_MEMCPY_ARGUMENT(length, buf); @@ -2473,7 +2473,7 @@ class JNI { "GetPrimitiveArrayRegion", "get region of"); if (array != nullptr) { - if (start < 0 || length < 0 || start + length > array->GetLength()) { + if (start < 0 || length < 0 || length > array->GetLength() - start) { ThrowAIOOBE(soa, array, start, length, "src"); } else { CHECK_NON_NULL_MEMCPY_ARGUMENT(length, buf); @@ -2493,7 +2493,7 @@ class JNI { "SetPrimitiveArrayRegion", "set region of"); if (array != nullptr) { - if (start < 0 || length < 0 || start + length > array->GetLength()) { + if (start < 0 || length < 0 || length > array->GetLength() - start) { ThrowAIOOBE(soa, array, start, length, "dst"); } else { CHECK_NON_NULL_MEMCPY_ARGUMENT(length, buf); diff --git a/runtime/jni_internal_test.cc b/runtime/jni_internal_test.cc index 41b368ec32..649df5f62b 100644 --- a/runtime/jni_internal_test.cc +++ b/runtime/jni_internal_test.cc @@ -1077,6 +1077,12 @@ TEST_F(JniInternalTest, RegisterAndUnregisterNatives) { env_->set_region_fn(a, size - 1, size, nullptr); \ ExpectException(aioobe_); \ \ + /* Regression test against integer overflow in range check. */ \ + env_->get_region_fn(a, 0x7fffffff, 0x7fffffff, nullptr); \ + ExpectException(aioobe_); \ + env_->set_region_fn(a, 0x7fffffff, 0x7fffffff, nullptr); \ + ExpectException(aioobe_); \ + \ /* It's okay for the buffer to be null as long as the length is 0. */ \ env_->get_region_fn(a, 2, 0, nullptr); \ /* Even if the offset is invalid... */ \ @@ -1507,6 +1513,9 @@ TEST_F(JniInternalTest, GetStringRegion_GetStringUTFRegion) { ExpectException(sioobe_); env_->GetStringRegion(s, 10, 1, nullptr); ExpectException(sioobe_); + // Regression test against integer overflow in range check. + env_->GetStringRegion(s, 0x7fffffff, 0x7fffffff, nullptr); + ExpectException(sioobe_); jchar chars[4] = { 'x', 'x', 'x', 'x' }; env_->GetStringRegion(s, 1, 2, &chars[1]); @@ -1529,6 +1538,9 @@ TEST_F(JniInternalTest, GetStringRegion_GetStringUTFRegion) { ExpectException(sioobe_); env_->GetStringUTFRegion(s, 10, 1, nullptr); ExpectException(sioobe_); + // Regression test against integer overflow in range check. + env_->GetStringUTFRegion(s, 0x7fffffff, 0x7fffffff, nullptr); + ExpectException(sioobe_); char bytes[4] = { 'x', 'x', 'x', 'x' }; env_->GetStringUTFRegion(s, 1, 2, &bytes[1]); diff --git a/runtime/monitor.cc b/runtime/monitor.cc index 81e7e6d675..da21fee3d2 100644 --- a/runtime/monitor.cc +++ b/runtime/monitor.cc @@ -27,7 +27,7 @@ #include "base/time_utils.h" #include "class_linker.h" #include "dex_file-inl.h" -#include "dex_instruction.h" +#include "dex_instruction-inl.h" #include "lock_word-inl.h" #include "mirror/class-inl.h" #include "mirror/object-inl.h" @@ -1034,15 +1034,15 @@ void Monitor::VisitLocks(StackVisitor* stack_visitor, void (*callback)(mirror::O for (uint32_t monitor_dex_pc : monitor_enter_dex_pcs) { // The verifier works in terms of the dex pcs of the monitor-enter instructions. // We want the registers used by those instructions (so we can read the values out of them). - uint16_t monitor_enter_instruction = code_item->insns_[monitor_dex_pc]; + const Instruction* monitor_enter_instruction = + Instruction::At(&code_item->insns_[monitor_dex_pc]); // Quick sanity check. - if ((monitor_enter_instruction & 0xff) != Instruction::MONITOR_ENTER) { - LOG(FATAL) << "expected monitor-enter @" << monitor_dex_pc << "; was " - << reinterpret_cast<void*>(monitor_enter_instruction); - } + CHECK_EQ(monitor_enter_instruction->Opcode(), Instruction::MONITOR_ENTER) + << "expected monitor-enter @" << monitor_dex_pc << "; was " + << reinterpret_cast<const void*>(monitor_enter_instruction); - uint16_t monitor_register = ((monitor_enter_instruction >> 8) & 0xff); + uint16_t monitor_register = monitor_enter_instruction->VRegA(); uint32_t value; bool success = stack_visitor->GetVReg(m, monitor_register, kReferenceVReg, &value); CHECK(success) << "Failed to read v" << monitor_register << " of kind " diff --git a/runtime/runtime.h b/runtime/runtime.h index 7b1fdb21c4..a8ba19bd95 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -151,6 +151,11 @@ class Runtime { return compiler_callbacks_; } + void SetCompilerCallbacks(CompilerCallbacks* callbacks) { + CHECK(callbacks != nullptr); + compiler_callbacks_ = callbacks; + } + bool IsZygote() const { return is_zygote_; } diff --git a/runtime/thread_pool.cc b/runtime/thread_pool.cc index 0527d3ae14..5a4dfb8cfd 100644 --- a/runtime/thread_pool.cc +++ b/runtime/thread_pool.cc @@ -82,6 +82,11 @@ void ThreadPool::AddTask(Thread* self, Task* task) { } } +void ThreadPool::RemoveAllTasks(Thread* self) { + MutexLock mu(self, task_queue_lock_); + tasks_.clear(); +} + ThreadPool::ThreadPool(const char* name, size_t num_threads) : name_(name), task_queue_lock_("task queue lock"), diff --git a/runtime/thread_pool.h b/runtime/thread_pool.h index a2338d6fcc..6cd4ad3cdc 100644 --- a/runtime/thread_pool.h +++ b/runtime/thread_pool.h @@ -91,6 +91,9 @@ class ThreadPool { // after running it, it is the caller's responsibility. void AddTask(Thread* self, Task* task) REQUIRES(!task_queue_lock_); + // Remove all tasks in the queue. + void RemoveAllTasks(Thread* self) REQUIRES(!task_queue_lock_); + ThreadPool(const char* name, size_t num_threads); virtual ~ThreadPool(); diff --git a/test/537-checker-jump-over-jump/expected.txt b/test/537-checker-jump-over-jump/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/537-checker-jump-over-jump/expected.txt diff --git a/test/537-checker-jump-over-jump/info.txt b/test/537-checker-jump-over-jump/info.txt new file mode 100644 index 0000000000..aeb30bb90f --- /dev/null +++ b/test/537-checker-jump-over-jump/info.txt @@ -0,0 +1 @@ +Test for X86-64 elimination of jump over jump. diff --git a/test/537-checker-jump-over-jump/src/Main.java b/test/537-checker-jump-over-jump/src/Main.java new file mode 100644 index 0000000000..fb666eaaea --- /dev/null +++ b/test/537-checker-jump-over-jump/src/Main.java @@ -0,0 +1,44 @@ +/* + * 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. + */ + + +public class Main { + public static int FIBCOUNT = 64; + public static int[] fibs; + + /// CHECK-START-X86_64: int Main.test() disassembly (after) + /// CHECK: If + /// CHECK-NEXT: cmp + /// CHECK-NEXT: jnl/ge + /// CHECK-NOT: jmp + /// CHECK: ArrayGet + // Checks that there is no conditional jump over a jmp. The ArrayGet is in + // the next block. + public static int test() { + for (int i = 1; ; i++) { + if (i >= FIBCOUNT) { + return fibs[0]; + } + fibs[i] = (i + fibs[(i - 1)]); + } + } + + public static void main(String[] args) { + fibs = new int[FIBCOUNT]; + fibs[0] = 1; + test(); + } +} diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index 18867fd035..3efa6ff079 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -367,6 +367,7 @@ if [ "$PREBUILD" = "y" ]; then --boot-image=${BOOT_IMAGE} \ --dex-file=$DEX_LOCATION/$TEST_NAME.jar \ --oat-file=$DEX_LOCATION/dalvik-cache/$ISA/$(echo $DEX_LOCATION/$TEST_NAME.jar/classes.dex | cut -d/ -f 2- | sed "s:/:@:g") \ + --app-image-file=$DEX_LOCATION/dalvik-cache/$ISA/$(echo $DEX_LOCATION/$TEST_NAME.jar/classes.art | cut -d/ -f 2- | sed "s:/:@:g") \ --instruction-set=$ISA" if [ "x$INSTRUCTION_SET_FEATURES" != "x" ] ; then dex2oat_cmdline="${dex2oat_cmdline} --instruction-set-features=${INSTRUCTION_SET_FEATURES}" diff --git a/tools/ahat/README.txt b/tools/ahat/README.txt index d6f55aae16..aa548cca55 100644 --- a/tools/ahat/README.txt +++ b/tools/ahat/README.txt @@ -13,8 +13,6 @@ TODO: - Recommend how to start looking at a heap dump. - Say how to enable allocation sites. - Where to submit feedback, questions, and bug reports. - * Submit perflib fix for getting stack traces, then uncomment that code in - AhatSnapshot to use that. * Dim 'image' and 'zygote' heap sizes slightly? Why do we even show these? * Filter out RootObjs in mSnapshot.getGCRoots, not RootsHandler. * Let user re-sort sites objects info by clicking column headers. @@ -25,23 +23,15 @@ TODO: * Show root types. * Heaped Table - Make sortable by clicking on headers. - - Use consistent order for heap columns. - Sometimes I see "app" first, sometimes last (from one heap dump to - another) How about, always sort by name? * For HeapTable with single heap shown, the heap name isn't centered? * Consistently document functions. * Should help be part of an AhatHandler, that automatically gets the menu and stylesheet link rather than duplicating that? * Show version number with --version. * Show somewhere where to send bugs. - * /objects query takes a long time to load without parameters. * Include a link to /objects in the overview and menu? * Turn on LOCAL_JAVACFLAGS := -Xlint:unchecked -Werror * Use hex for object ids in URLs? - * In general, all tables and descriptions should show a limited amount to - start, and only show more when requested by the user. - * Don't have handlers inherit from HttpHandler - - because they should be independent from http. * [low priority] by site allocations won't line up if the stack has been truncated. Is there any way to manually line them up in that case? @@ -60,8 +50,6 @@ Things to Test: objects normally sorted by 'app' heap by default. * Visit /objects without parameters and verify it doesn't throw an exception. * Visit /objects with an invalid site, verify it doesn't throw an exception. - * That we can view an array with 3 million elements in a reasonably short - amount of time (not more than 1 second?) * That we can view the list of all objects in a reasonably short amount of time. * That we don't show the 'extra' column in the DominatedList if we are @@ -72,8 +60,6 @@ Things to Test: Reported Issues: * Request to be able to sort tables by size. - * Hangs on showing large arrays, where hat does not hang. - - Solution is probably to not show all the array elements by default. Perflib Requests: * Class objects should have java.lang.Class as their class object, not null. diff --git a/tools/ahat/src/AhatHandler.java b/tools/ahat/src/AhatHandler.java index 2da02f8671..d4b4d1b107 100644 --- a/tools/ahat/src/AhatHandler.java +++ b/tools/ahat/src/AhatHandler.java @@ -16,51 +16,17 @@ package com.android.ahat; -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; import java.io.IOException; -import java.io.PrintStream; /** * AhatHandler. * - * Common base class of all the ahat HttpHandlers. + * Interface for an ahat page handler. */ -abstract class AhatHandler implements HttpHandler { +interface AhatHandler { - protected AhatSnapshot mSnapshot; - - public AhatHandler(AhatSnapshot snapshot) { - mSnapshot = snapshot; - } - - public abstract void handle(Doc doc, Query query) throws IOException; - - @Override - public void handle(HttpExchange exchange) throws IOException { - exchange.getResponseHeaders().add("Content-Type", "text/html;charset=utf-8"); - exchange.sendResponseHeaders(200, 0); - PrintStream ps = new PrintStream(exchange.getResponseBody()); - try { - HtmlDoc doc = new HtmlDoc(ps, DocString.text("ahat"), DocString.uri("style.css")); - DocString menu = new DocString(); - menu.appendLink(DocString.uri("/"), DocString.text("overview")); - menu.append(" - "); - menu.appendLink(DocString.uri("roots"), DocString.text("roots")); - menu.append(" - "); - menu.appendLink(DocString.uri("sites"), DocString.text("allocations")); - menu.append(" - "); - menu.appendLink(DocString.uri("help"), DocString.text("help")); - doc.menu(menu); - handle(doc, new Query(exchange.getRequestURI())); - doc.close(); - } catch (RuntimeException e) { - // Print runtime exceptions to standard error for debugging purposes, - // because otherwise they are swallowed and not reported. - System.err.println("Exception when handling " + exchange.getRequestURI() + ": "); - e.printStackTrace(); - throw e; - } - ps.close(); - } + /** + * Handle the given query, rendering the page to the given document. + */ + void handle(Doc doc, Query query) throws IOException; } diff --git a/tools/ahat/src/AhatHttpHandler.java b/tools/ahat/src/AhatHttpHandler.java new file mode 100644 index 0000000000..0553713702 --- /dev/null +++ b/tools/ahat/src/AhatHttpHandler.java @@ -0,0 +1,64 @@ +/* + * 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 com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import java.io.IOException; +import java.io.PrintStream; + +/** + * AhatHttpHandler. + * + * HttpHandler for AhatHandlers. + */ +class AhatHttpHandler implements HttpHandler { + + private AhatHandler mAhatHandler; + + public AhatHttpHandler(AhatHandler handler) { + mAhatHandler = handler; + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + exchange.getResponseHeaders().add("Content-Type", "text/html;charset=utf-8"); + exchange.sendResponseHeaders(200, 0); + PrintStream ps = new PrintStream(exchange.getResponseBody()); + try { + HtmlDoc doc = new HtmlDoc(ps, DocString.text("ahat"), DocString.uri("style.css")); + DocString menu = new DocString(); + menu.appendLink(DocString.uri("/"), DocString.text("overview")); + menu.append(" - "); + menu.appendLink(DocString.uri("roots"), DocString.text("roots")); + menu.append(" - "); + menu.appendLink(DocString.uri("sites"), DocString.text("allocations")); + menu.append(" - "); + menu.appendLink(DocString.uri("help"), DocString.text("help")); + doc.menu(menu); + mAhatHandler.handle(doc, new Query(exchange.getRequestURI())); + doc.close(); + } catch (RuntimeException e) { + // Print runtime exceptions to standard error for debugging purposes, + // because otherwise they are swallowed and not reported. + System.err.println("Exception when handling " + exchange.getRequestURI() + ": "); + e.printStackTrace(); + throw e; + } + ps.close(); + } +} diff --git a/tools/ahat/src/Doc.java b/tools/ahat/src/Doc.java index 7fa70de915..5a70c4c74b 100644 --- a/tools/ahat/src/Doc.java +++ b/tools/ahat/src/Doc.java @@ -25,27 +25,27 @@ interface Doc extends AutoCloseable { /** * Output the title of the page. */ - public void title(String format, Object... args); + void title(String format, Object... args); /** * Print a line of text for a page menu. */ - public void menu(DocString string); + void menu(DocString string); /** * Start a new section with the given title. */ - public void section(String title); + void section(String title); /** * Print a line of text in a normal font. */ - public void println(DocString string); + void println(DocString string); /** * Print a line of text in a large font that is easy to see and click on. */ - public void big(DocString string); + void big(DocString string); /** * Start a table with the given columns. @@ -55,7 +55,7 @@ interface Doc extends AutoCloseable { * This should be followed by calls to the 'row' method to fill in the table * contents and the 'end' method to end the table. */ - public void table(Column... columns); + void table(Column... columns); /** * Start a table with the following heading structure: @@ -68,14 +68,14 @@ interface Doc extends AutoCloseable { * This should be followed by calls to the 'row' method to fill in the table * contents and the 'end' method to end the table. */ - public void table(DocString description, List<Column> subcols, List<Column> cols); + void table(DocString description, List<Column> subcols, List<Column> cols); /** * Add a row to the currently active table. * The number of values must match the number of columns provided for the * currently active table. */ - public void row(DocString... values); + void row(DocString... values); /** * Start a new description list. @@ -83,15 +83,15 @@ interface Doc extends AutoCloseable { * This should be followed by calls to description() and finally a call to * end(). */ - public void descriptions(); + void descriptions(); /** * Add a description to the currently active description list. */ - public void description(DocString key, DocString value); + void description(DocString key, DocString value); /** * End the currently active table or description list. */ - public void end(); + void end(); } diff --git a/tools/ahat/src/DominatedList.java b/tools/ahat/src/DominatedList.java index 123d8be82b..34a5665b3e 100644 --- a/tools/ahat/src/DominatedList.java +++ b/tools/ahat/src/DominatedList.java @@ -21,71 +21,35 @@ import com.android.tools.perflib.heap.Instance; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; /** * Class for rendering a list of instances dominated by a single instance in a * pretty way. */ class DominatedList { - private static final int kIncrAmount = 100; - private static final int kDefaultShown = 100; - /** * Render a table to the given HtmlWriter showing a pretty list of * instances. * - * Rather than show all of the instances (which may be very many), we use - * the query parameter "dominated" to specify a limited number of - * instances to show. The 'uri' parameter should be the current page URI, so - * that we can add links to "show more" and "show less" objects that go to - * the same page with only the number of objects adjusted. + * @param snapshot the snapshot where the instances reside + * @param doc the document to render the dominated list to + * @param query the current page query + * @param id a unique identifier to use for the dominated list in the current page + * @param instances the collection of instances to generate a list for */ - public static void render(final AhatSnapshot snapshot, Doc doc, - Collection<Instance> instances, Query query) { + public static void render(final AhatSnapshot snapshot, + Doc doc, Query query, String id, Collection<Instance> instances) { List<Instance> insts = new ArrayList<Instance>(instances); Collections.sort(insts, Sort.defaultInstanceCompare(snapshot)); - - int numInstancesToShow = getNumInstancesToShow(query, insts.size()); - List<Instance> shown = new ArrayList<Instance>(insts.subList(0, numInstancesToShow)); - List<Instance> hidden = insts.subList(numInstancesToShow, insts.size()); - - // Add 'null' as a marker for "all the rest of the objects". - if (!hidden.isEmpty()) { - shown.add(null); - } - HeapTable.render(doc, new TableConfig(snapshot, hidden), snapshot, shown); - - if (insts.size() > kDefaultShown) { - printMenu(doc, query, numInstancesToShow, insts.size()); - } + HeapTable.render(doc, query, id, new TableConfig(snapshot), snapshot, insts); } private static class TableConfig implements HeapTable.TableConfig<Instance> { AhatSnapshot mSnapshot; - // Map from heap name to the total size of the instances not shown in the - // table. - Map<Heap, Long> mHiddenSizes; - - public TableConfig(AhatSnapshot snapshot, List<Instance> hidden) { + public TableConfig(AhatSnapshot snapshot) { mSnapshot = snapshot; - mHiddenSizes = new HashMap<Heap, Long>(); - for (Heap heap : snapshot.getHeaps()) { - mHiddenSizes.put(heap, 0L); - } - - if (!hidden.isEmpty()) { - for (Instance inst : hidden) { - for (Heap heap : snapshot.getHeaps()) { - int index = snapshot.getHeapIndex(heap); - long size = inst.getRetainedSize(index); - mHiddenSizes.put(heap, mHiddenSizes.get(heap) + size); - } - } - } } @Override @@ -95,9 +59,6 @@ class DominatedList { @Override public long getSize(Instance element, Heap heap) { - if (element == null) { - return mHiddenSizes.get(heap); - } int index = mSnapshot.getHeapIndex(heap); return element.getRetainedSize(index); } @@ -110,56 +71,10 @@ class DominatedList { } public DocString render(Instance element) { - if (element == null) { - return DocString.text("..."); - } else { - return Value.render(element); - } + return Value.render(element); } }; return Collections.singletonList(value); } } - - // Figure out how many objects to show based on the query parameter. - // The resulting value is guaranteed to be at least zero, and no greater - // than the number of total objects. - private static int getNumInstancesToShow(Query query, int totalNumInstances) { - String value = query.get("dominated", null); - try { - int count = Math.min(totalNumInstances, Integer.parseInt(value)); - return Math.max(0, count); - } catch (NumberFormatException e) { - // We can't parse the value as a number. Ignore it. - } - return Math.min(kDefaultShown, totalNumInstances); - } - - // Print a menu line after the table to control how many objects are shown. - // It has the form: - // (showing X of Y objects - show none - show less - show more - show all) - private static void printMenu(Doc doc, Query query, int shown, int all) { - DocString menu = new DocString(); - menu.appendFormat("(%d of %d objects shown - ", shown, all); - if (shown > 0) { - int less = Math.max(0, shown - kIncrAmount); - menu.appendLink(query.with("dominated", 0), DocString.text("show none")); - menu.append(" - "); - menu.appendLink(query.with("dominated", less), DocString.text("show less")); - menu.append(" - "); - } else { - menu.append("show none - show less - "); - } - if (shown < all) { - int more = Math.min(shown + kIncrAmount, all); - menu.appendLink(query.with("dominated", more), DocString.text("show more")); - menu.append(" - "); - menu.appendLink(query.with("dominated", all), DocString.text("show all")); - menu.append(")"); - } else { - menu.append("show more - show all)"); - } - doc.println(menu); - } } - diff --git a/tools/ahat/src/HeapTable.java b/tools/ahat/src/HeapTable.java index 37d58164a8..ed11d1724a 100644 --- a/tools/ahat/src/HeapTable.java +++ b/tools/ahat/src/HeapTable.java @@ -18,7 +18,9 @@ package com.android.ahat; import com.android.tools.perflib.heap.Heap; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Class for rendering a table that includes sizes of some kind for each heap. @@ -27,22 +29,27 @@ class HeapTable { /** * Configuration for a value column of a heap table. */ - public static interface ValueConfig<T> { - public String getDescription(); - public DocString render(T element); + public interface ValueConfig<T> { + String getDescription(); + DocString render(T element); } /** * Configuration for the HeapTable. */ - public static interface TableConfig<T> { - public String getHeapsDescription(); - public long getSize(T element, Heap heap); - public List<ValueConfig<T>> getValueConfigs(); + public interface TableConfig<T> { + String getHeapsDescription(); + long getSize(T element, Heap heap); + List<ValueConfig<T>> getValueConfigs(); } - public static <T> void render(Doc doc, TableConfig<T> config, - AhatSnapshot snapshot, List<T> elements) { + /** + * Render the table to the given document. + * @param query - The page query. + * @param id - A unique identifier for the table on the page. + */ + public static <T> void render(Doc doc, Query query, String id, + TableConfig<T> config, AhatSnapshot snapshot, List<T> elements) { // Only show the heaps that have non-zero entries. List<Heap> heaps = new ArrayList<Heap>(); for (Heap heap : snapshot.getHeaps()) { @@ -68,9 +75,10 @@ class HeapTable { } doc.table(DocString.text(config.getHeapsDescription()), subcols, cols); - // Print the entries. + // Print the entries up to the selected limit. + SubsetSelector<T> selector = new SubsetSelector(query, id, elements); ArrayList<DocString> vals = new ArrayList<DocString>(); - for (T elem : elements) { + for (T elem : selector.selected()) { vals.clear(); long total = 0; for (Heap heap : heaps) { @@ -87,7 +95,39 @@ class HeapTable { } doc.row(vals.toArray(new DocString[0])); } + + // Print a summary of the remaining entries if there are any. + List<T> remaining = selector.remaining(); + if (!remaining.isEmpty()) { + Map<Heap, Long> summary = new HashMap<Heap, Long>(); + for (Heap heap : heaps) { + summary.put(heap, 0L); + } + + for (T elem : remaining) { + for (Heap heap : heaps) { + summary.put(heap, summary.get(heap) + config.getSize(elem, heap)); + } + } + + vals.clear(); + long total = 0; + for (Heap heap : heaps) { + long size = summary.get(heap); + total += size; + vals.add(DocString.format("%,14d", size)); + } + if (showTotal) { + vals.add(DocString.format("%,14d", total)); + } + + for (ValueConfig<T> value : values) { + vals.add(DocString.text("...")); + } + doc.row(vals.toArray(new DocString[0])); + } doc.end(); + selector.render(doc); } // Returns true if the given heap has a non-zero size entry. diff --git a/tools/ahat/src/Main.java b/tools/ahat/src/Main.java index 1563aa0262..96fc53b6bd 100644 --- a/tools/ahat/src/Main.java +++ b/tools/ahat/src/Main.java @@ -73,11 +73,11 @@ public class Main { InetAddress loopback = InetAddress.getLoopbackAddress(); InetSocketAddress addr = new InetSocketAddress(loopback, port); HttpServer server = HttpServer.create(addr, 0); - server.createContext("/", new OverviewHandler(ahat, hprof)); - server.createContext("/roots", new RootsHandler(ahat)); - server.createContext("/object", new ObjectHandler(ahat)); - server.createContext("/objects", new ObjectsHandler(ahat)); - server.createContext("/site", new SiteHandler(ahat)); + server.createContext("/", new AhatHttpHandler(new OverviewHandler(ahat, hprof))); + server.createContext("/roots", new AhatHttpHandler(new RootsHandler(ahat))); + 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("/bitmap", new BitmapHandler(ahat)); server.createContext("/help", new StaticHandler("help.html", "text/html")); server.createContext("/style.css", new StaticHandler("style.css", "text/css")); diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java index 5e321e267e..9e4ce563d5 100644 --- a/tools/ahat/src/ObjectHandler.java +++ b/tools/ahat/src/ObjectHandler.java @@ -25,13 +25,26 @@ import com.android.tools.perflib.heap.Instance; import com.android.tools.perflib.heap.RootObj; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; -class ObjectHandler extends AhatHandler { +class ObjectHandler implements AhatHandler { + + private static final String ARRAY_ELEMENTS_ID = "elements"; + private static final String DOMINATOR_PATH_ID = "dompath"; + private static final String ALLOCATION_SITE_ID = "frames"; + private static final String DOMINATED_OBJECTS_ID = "dominated"; + private static final String INSTANCE_FIELDS_ID = "ifields"; + private static final String STATIC_FIELDS_ID = "sfields"; + private static final String HARD_REFS_ID = "refs"; + private static final String SOFT_REFS_ID = "srefs"; + + private AhatSnapshot mSnapshot; + public ObjectHandler(AhatSnapshot snapshot) { - super(snapshot); + mSnapshot = snapshot; } @Override @@ -46,8 +59,8 @@ class ObjectHandler extends AhatHandler { doc.title("Object %08x", inst.getUniqueId()); doc.big(Value.render(inst)); - printAllocationSite(doc, inst); - printDominatorPath(doc, inst); + printAllocationSite(doc, query, inst); + printDominatorPath(doc, query, inst); doc.section("Object Info"); ClassObj cls = inst.getClassObj(); @@ -62,39 +75,46 @@ class ObjectHandler extends AhatHandler { printBitmap(doc, inst); if (inst instanceof ClassInstance) { - printClassInstanceFields(doc, (ClassInstance)inst); + printClassInstanceFields(doc, query, (ClassInstance)inst); } else if (inst instanceof ArrayInstance) { - printArrayElements(doc, (ArrayInstance)inst); + printArrayElements(doc, query, (ArrayInstance)inst); } else if (inst instanceof ClassObj) { - printClassInfo(doc, (ClassObj)inst); + printClassInfo(doc, query, (ClassObj)inst); } - printReferences(doc, inst); + printReferences(doc, query, inst); printDominatedObjects(doc, query, inst); } - private static void printClassInstanceFields(Doc doc, ClassInstance inst) { + private static void printClassInstanceFields(Doc doc, Query query, ClassInstance inst) { doc.section("Fields"); doc.table(new Column("Type"), new Column("Name"), new Column("Value")); - for (ClassInstance.FieldValue field : inst.getValues()) { + SubsetSelector<ClassInstance.FieldValue> selector + = new SubsetSelector(query, INSTANCE_FIELDS_ID, inst.getValues()); + for (ClassInstance.FieldValue field : selector.selected()) { doc.row( DocString.text(field.getField().getType().toString()), DocString.text(field.getField().getName()), Value.render(field.getValue())); } doc.end(); + selector.render(doc); } - private static void printArrayElements(Doc doc, ArrayInstance array) { + private static void printArrayElements(Doc doc, Query query, ArrayInstance array) { doc.section("Array Elements"); doc.table(new Column("Index", Column.Align.RIGHT), new Column("Value")); - Object[] elements = array.getValues(); - for (int i = 0; i < elements.length; i++) { - doc.row(DocString.format("%d", i), Value.render(elements[i])); + List<Object> elements = Arrays.asList(array.getValues()); + SubsetSelector<Object> selector = new SubsetSelector(query, ARRAY_ELEMENTS_ID, elements); + int i = 0; + for (Object elem : selector.selected()) { + doc.row(DocString.format("%d", i), Value.render(elem)); + i++; } doc.end(); + selector.render(doc); } - private static void printClassInfo(Doc doc, ClassObj clsobj) { + private static void printClassInfo(Doc doc, Query query, ClassObj clsobj) { doc.section("Class Info"); doc.descriptions(); doc.description(DocString.text("Super Class"), Value.render(clsobj.getSuperClassObj())); @@ -103,41 +123,52 @@ class ObjectHandler extends AhatHandler { doc.section("Static Fields"); doc.table(new Column("Type"), new Column("Name"), new Column("Value")); - for (Map.Entry<Field, Object> field : clsobj.getStaticFieldValues().entrySet()) { + List<Map.Entry<Field, Object>> fields + = new ArrayList<Map.Entry<Field, Object>>(clsobj.getStaticFieldValues().entrySet()); + SubsetSelector<Map.Entry<Field, Object>> selector + = new SubsetSelector(query, STATIC_FIELDS_ID, fields); + for (Map.Entry<Field, Object> field : selector.selected()) { doc.row( DocString.text(field.getKey().getType().toString()), DocString.text(field.getKey().getName()), Value.render(field.getValue())); } doc.end(); + selector.render(doc); } - private static void printReferences(Doc doc, Instance inst) { + private static void printReferences(Doc doc, Query query, Instance inst) { doc.section("Objects with References to this Object"); if (inst.getHardReferences().isEmpty()) { doc.println(DocString.text("(none)")); } else { doc.table(new Column("Object")); - for (Instance ref : inst.getHardReferences()) { + List<Instance> references = inst.getHardReferences(); + SubsetSelector<Instance> selector = new SubsetSelector(query, HARD_REFS_ID, references); + for (Instance ref : selector.selected()) { doc.row(Value.render(ref)); } doc.end(); + selector.render(doc); } if (inst.getSoftReferences() != null) { doc.section("Objects with Soft References to this Object"); doc.table(new Column("Object")); - for (Instance ref : inst.getSoftReferences()) { - doc.row(Value.render(inst)); + List<Instance> references = inst.getSoftReferences(); + SubsetSelector<Instance> selector = new SubsetSelector(query, SOFT_REFS_ID, references); + for (Instance ref : selector.selected()) { + doc.row(Value.render(ref)); } doc.end(); + selector.render(doc); } } - private void printAllocationSite(Doc doc, Instance inst) { + private void printAllocationSite(Doc doc, Query query, Instance inst) { doc.section("Allocation Site"); Site site = mSnapshot.getSiteForInstance(inst); - SitePrinter.printSite(doc, mSnapshot, site); + SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, site); } // Draw the bitmap corresponding to this instance if there is one. @@ -150,7 +181,7 @@ class ObjectHandler extends AhatHandler { } } - private void printDominatorPath(Doc doc, Instance inst) { + private void printDominatorPath(Doc doc, Query query, Instance inst) { doc.section("Dominator Path from Root"); List<Instance> path = new ArrayList<Instance>(); for (Instance parent = inst; @@ -193,14 +224,14 @@ class ObjectHandler extends AhatHandler { return Collections.singletonList(value); } }; - HeapTable.render(doc, table, mSnapshot, path); + HeapTable.render(doc, query, DOMINATOR_PATH_ID, table, mSnapshot, path); } public void printDominatedObjects(Doc doc, Query query, Instance inst) { doc.section("Immediately Dominated Objects"); List<Instance> instances = mSnapshot.getDominated(inst); if (instances != null) { - DominatedList.render(mSnapshot, doc, instances, query); + DominatedList.render(mSnapshot, doc, query, DOMINATED_OBJECTS_ID, instances); } else { doc.println(DocString.text("(none)")); } diff --git a/tools/ahat/src/ObjectsHandler.java b/tools/ahat/src/ObjectsHandler.java index 4e9c42e566..8ad3f481da 100644 --- a/tools/ahat/src/ObjectsHandler.java +++ b/tools/ahat/src/ObjectsHandler.java @@ -22,9 +22,13 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -class ObjectsHandler extends AhatHandler { +class ObjectsHandler implements AhatHandler { + private static final String OBJECTS_ID = "objects"; + + private AhatSnapshot mSnapshot; + public ObjectsHandler(AhatSnapshot snapshot) { - super(snapshot); + mSnapshot = snapshot; } @Override @@ -51,13 +55,15 @@ class ObjectsHandler extends AhatHandler { new Column("Size", Column.Align.RIGHT), new Column("Heap"), new Column("Object")); - for (Instance inst : insts) { + SubsetSelector<Instance> selector = new SubsetSelector(query, OBJECTS_ID, insts); + for (Instance inst : selector.selected()) { doc.row( DocString.format("%,d", inst.getSize()), DocString.text(inst.getHeap().getName()), Value.render(inst)); } doc.end(); + selector.render(doc); } } diff --git a/tools/ahat/src/OverviewHandler.java b/tools/ahat/src/OverviewHandler.java index f49c009b30..e86679f378 100644 --- a/tools/ahat/src/OverviewHandler.java +++ b/tools/ahat/src/OverviewHandler.java @@ -22,11 +22,15 @@ import java.io.File; import java.util.Collections; import java.util.List; -class OverviewHandler extends AhatHandler { +class OverviewHandler implements AhatHandler { + + private static final String OVERVIEW_ID = "overview"; + + private AhatSnapshot mSnapshot; private File mHprof; public OverviewHandler(AhatSnapshot snapshot, File hprof) { - super(snapshot); + mSnapshot = snapshot; mHprof = hprof; } @@ -43,7 +47,7 @@ class OverviewHandler extends AhatHandler { doc.end(); doc.section("Heap Sizes"); - printHeapSizes(doc); + printHeapSizes(doc, query); DocString menu = new DocString(); menu.appendLink(DocString.uri("roots"), DocString.text("Roots")); @@ -54,7 +58,7 @@ class OverviewHandler extends AhatHandler { doc.big(menu); } - private void printHeapSizes(Doc doc) { + private void printHeapSizes(Doc doc, Query query) { List<Object> dummy = Collections.singletonList(null); HeapTable.TableConfig<Object> table = new HeapTable.TableConfig<Object>() { @@ -70,7 +74,7 @@ class OverviewHandler extends AhatHandler { return Collections.emptyList(); } }; - HeapTable.render(doc, table, mSnapshot, dummy); + HeapTable.render(doc, query, OVERVIEW_ID, table, mSnapshot, dummy); } } diff --git a/tools/ahat/src/RootsHandler.java b/tools/ahat/src/RootsHandler.java index 185b9bf24f..2a92c90c4e 100644 --- a/tools/ahat/src/RootsHandler.java +++ b/tools/ahat/src/RootsHandler.java @@ -24,9 +24,14 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -class RootsHandler extends AhatHandler { +class RootsHandler implements AhatHandler { + + private static final String ROOTS_ID = "roots"; + + private AhatSnapshot mSnapshot; + public RootsHandler(AhatSnapshot snapshot) { - super(snapshot); + mSnapshot = snapshot; } @Override @@ -45,7 +50,7 @@ class RootsHandler extends AhatHandler { for (Instance inst : rootset) { roots.add(inst); } - DominatedList.render(mSnapshot, doc, roots, query); + DominatedList.render(mSnapshot, doc, query, ROOTS_ID, roots); } } diff --git a/tools/ahat/src/SiteHandler.java b/tools/ahat/src/SiteHandler.java index 0a9381ef24..0425a5a825 100644 --- a/tools/ahat/src/SiteHandler.java +++ b/tools/ahat/src/SiteHandler.java @@ -22,9 +22,15 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -class SiteHandler extends AhatHandler { +class SiteHandler implements AhatHandler { + private static final String ALLOCATION_SITE_ID = "frames"; + private static final String SITES_CALLED_ID = "called"; + private static final String OBJECTS_ALLOCATED_ID = "objects"; + + private AhatSnapshot mSnapshot; + public SiteHandler(AhatSnapshot snapshot) { - super(snapshot); + mSnapshot = snapshot; } @Override @@ -35,7 +41,7 @@ class SiteHandler extends AhatHandler { doc.title("Site %s", site.getName()); doc.section("Allocation Site"); - SitePrinter.printSite(doc, mSnapshot, site); + SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, site); doc.section("Sites Called from Here"); List<Site> children = site.getChildren(); @@ -69,7 +75,7 @@ class SiteHandler extends AhatHandler { return Collections.singletonList(value); } }; - HeapTable.render(doc, table, mSnapshot, children); + HeapTable.render(doc, query, SITES_CALLED_ID, table, mSnapshot, children); } doc.section("Objects Allocated"); @@ -84,7 +90,9 @@ class SiteHandler extends AhatHandler { new Sort.ObjectsInfoBySize(), new Sort.ObjectsInfoByClassName()); Collections.sort(infos, compare); - for (Site.ObjectsInfo info : infos) { + SubsetSelector<Site.ObjectsInfo> selector + = new SubsetSelector(query, OBJECTS_ALLOCATED_ID, infos); + for (Site.ObjectsInfo info : selector.selected()) { String className = AhatSnapshot.getClassName(info.classObj); doc.row( DocString.format("%,14d", info.numBytes), @@ -96,6 +104,7 @@ class SiteHandler extends AhatHandler { Value.render(info.classObj)); } doc.end(); + selector.render(doc); } } diff --git a/tools/ahat/src/SitePrinter.java b/tools/ahat/src/SitePrinter.java index be87032a2c..2c06b47a29 100644 --- a/tools/ahat/src/SitePrinter.java +++ b/tools/ahat/src/SitePrinter.java @@ -22,7 +22,7 @@ import java.util.Collections; import java.util.List; class SitePrinter { - public static void printSite(Doc doc, AhatSnapshot snapshot, Site site) { + public static void printSite(AhatSnapshot snapshot, Doc doc, Query query, String id, Site site) { List<Site> path = new ArrayList<Site>(); for (Site parent = site; parent != null; parent = parent.getParent()) { path.add(parent); @@ -60,6 +60,6 @@ class SitePrinter { return Collections.singletonList(value); } }; - HeapTable.render(doc, table, snapshot, path); + HeapTable.render(doc, query, id, table, snapshot, path); } } diff --git a/tools/ahat/src/SubsetSelector.java b/tools/ahat/src/SubsetSelector.java new file mode 100644 index 0000000000..79399c178b --- /dev/null +++ b/tools/ahat/src/SubsetSelector.java @@ -0,0 +1,109 @@ +/* + * 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.util.List; + +/** + * The SubsetSelector is that can be added to a page that lets the + * user select a limited number of elements to show. + * This is used to limit the number of elements shown on a page by default, + * requiring the user to explicitly request more, so users not interested in + * more don't have to wait for everything to render. + */ +class SubsetSelector<T> { + private static final int kIncrAmount = 1000; + private static final int kDefaultShown = 1000; + + private Query mQuery; + private String mId; + private int mLimit; + private List<T> mElements; + + /** + * @param id - the name of the query parameter key that should hold + * the limit selectors selected value. + * @param query - The query for the current page. This is required so the + * LimitSelector can add a link to the same page with modified limit + * selection. + * @param elements - the elements to select from. The collection of elements + * should not be modified during the lifetime of the SubsetSelector object. + */ + public SubsetSelector(Query query, String id, List<T> elements) { + mQuery = query; + mId = id; + mLimit = getSelectedLimit(query, id, elements.size()); + mElements = elements; + } + + // Return the list of elements included in the selected subset. + public List<T> selected() { + return mElements.subList(0, mLimit); + } + + // Return the list of remaining elements not included in the selected subset. + public List<T> remaining() { + return mElements.subList(mLimit, mElements.size()); + } + + /** + * Returns the currently selected limit. + * @param query the current page query + * @param size the total number of elements to select from + * @return the number of selected elements + */ + private static int getSelectedLimit(Query query, String id, int size) { + String value = query.get(id, null); + try { + int ivalue = Math.min(size, Integer.parseInt(value)); + return Math.max(0, ivalue); + } catch (NumberFormatException e) { + // We can't parse the value as a number. Ignore it. + } + return Math.min(kDefaultShown, size); + } + + // Render the limit selector to the given doc. + // It has the form: + // (showing X of Y - show none - show less - show more - show all) + public void render(Doc doc) { + int all = mElements.size(); + if (all > kDefaultShown) { + DocString menu = new DocString(); + menu.appendFormat("(%d of %d elements shown - ", mLimit, all); + if (mLimit > 0) { + int less = Math.max(0, mLimit - kIncrAmount); + menu.appendLink(mQuery.with(mId, 0), DocString.text("show none")); + menu.append(" - "); + menu.appendLink(mQuery.with(mId, less), DocString.text("show less")); + menu.append(" - "); + } else { + menu.append("show none - show less - "); + } + if (mLimit < all) { + int more = Math.min(mLimit + kIncrAmount, all); + menu.appendLink(mQuery.with(mId, more), DocString.text("show more")); + menu.append(" - "); + menu.appendLink(mQuery.with(mId, all), DocString.text("show all")); + menu.append(")"); + } else { + menu.append("show more - show all)"); + } + doc.println(menu); + } + } +} diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/test-dump/Main.java index 7b8774a34d..90cd7af2cc 100644 --- a/tools/ahat/test-dump/Main.java +++ b/tools/ahat/test-dump/Main.java @@ -39,6 +39,15 @@ public class Main { public ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>(); public PhantomReference aPhantomReference = new PhantomReference(anObject, referenceQueue); public WeakReference aWeakReference = new WeakReference(anObject, referenceQueue); + public byte[] bigArray; + + DumpedStuff() { + int N = 1000000; + bigArray = new byte[N]; + for (int i = 0; i < N; i++) { + bigArray[i] = (byte)((i*i) & 0xFF); + } + } } public static void main(String[] args) throws IOException { diff --git a/tools/ahat/test/PerformanceTest.java b/tools/ahat/test/PerformanceTest.java new file mode 100644 index 0000000000..6e46800603 --- /dev/null +++ b/tools/ahat/test/PerformanceTest.java @@ -0,0 +1,55 @@ +/* + * 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 com.android.tools.perflib.heap.Instance; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import org.junit.Test; + +public class PerformanceTest { + private static class NullOutputStream extends OutputStream { + public void write(int b) throws IOException { + } + } + + @Test + public void bigArray() throws IOException { + // It should not take more than 1 second to load the default object view + // for any object, including big arrays. + TestDump dump = TestDump.getTestDump(); + + Instance bigArray = (Instance)dump.getDumpedThing("bigArray"); + assertNotNull(bigArray); + + AhatSnapshot snapshot = dump.getAhatSnapshot(); + AhatHandler handler = new ObjectHandler(snapshot); + + PrintStream ps = new PrintStream(new NullOutputStream()); + HtmlDoc doc = new HtmlDoc(ps, DocString.text("bigArray test"), DocString.uri("style.css")); + String uri = "http://localhost:7100/object?id=" + bigArray.getId(); + Query query = new Query(DocString.uri(uri)); + + long start = System.currentTimeMillis(); + handler.handle(doc, query); + long time = System.currentTimeMillis() - start; + assertTrue("bigArray took too long: " + time + "ms", time < 1000); + } +} diff --git a/tools/ahat/test/Tests.java b/tools/ahat/test/Tests.java index bab712199c..e8894e2603 100644 --- a/tools/ahat/test/Tests.java +++ b/tools/ahat/test/Tests.java @@ -23,8 +23,9 @@ public class Tests { if (args.length == 0) { args = new String[]{ "com.android.ahat.InstanceUtilsTest", + "com.android.ahat.PerformanceTest", "com.android.ahat.QueryTest", - "com.android.ahat.SortTest" + "com.android.ahat.SortTest", }; } JUnitCore.main(args); diff --git a/tools/buildbot-build.sh b/tools/buildbot-build.sh index 631e0a0c59..047c24f8aa 100755 --- a/tools/buildbot-build.sh +++ b/tools/buildbot-build.sh @@ -21,7 +21,7 @@ fi out_dir=${OUT_DIR-out} java_libraries_dir=${out_dir}/target/common/obj/JAVA_LIBRARIES -common_targets="vogar vogar.jar ${java_libraries_dir}/core-tests_intermediates/javalib.jar apache-harmony-jdwp-tests-hostdex ${java_libraries_dir}/jsr166-tests_intermediates/javalib.jar" +common_targets="vogar ${java_libraries_dir}/core-tests_intermediates/javalib.jar apache-harmony-jdwp-tests-hostdex ${java_libraries_dir}/jsr166-tests_intermediates/javalib.jar" mode="target" j_arg="-j$(nproc)" showcommands= |