diff options
80 files changed, 2922 insertions, 857 deletions
diff --git a/build/Android.oat.mk b/build/Android.oat.mk index 710b130282..728469c2c4 100644 --- a/build/Android.oat.mk +++ b/build/Android.oat.mk @@ -113,7 +113,7 @@ $$(core_image_name): $$(HOST_CORE_DEX_LOCATIONS) $$(core_dex2oat_dependency) --oat-location=$$(PRIVATE_CORE_OAT_NAME) --image=$$(PRIVATE_CORE_IMG_NAME) \ --base=$$(LIBART_IMG_HOST_BASE_ADDRESS) --instruction-set=$$($(3)ART_HOST_ARCH) \ --instruction-set-features=$$($(3)DEX2OAT_HOST_INSTRUCTION_SET_FEATURES) \ - --host --android-root=$$(HOST_OUT) --include-patch-information \ + --host --android-root=$$(HOST_OUT) --include-patch-information --generate-debug-info \ $$(PRIVATE_CORE_COMPILE_OPTIONS) $$(core_oat_name): $$(core_image_name) @@ -232,7 +232,7 @@ $$(core_image_name): $$(TARGET_CORE_DEX_FILES) $$(core_dex2oat_dependency) --base=$$(LIBART_IMG_TARGET_BASE_ADDRESS) --instruction-set=$$($(3)TARGET_ARCH) \ --instruction-set-variant=$$($(3)DEX2OAT_TARGET_CPU_VARIANT) \ --instruction-set-features=$$($(3)DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) \ - --android-root=$$(PRODUCT_OUT)/system --include-patch-information \ + --android-root=$$(PRODUCT_OUT)/system --include-patch-information --generate-debug-info \ $$(PRIVATE_CORE_COMPILE_OPTIONS) || (rm $$(PRIVATE_CORE_OAT_NAME); exit 1) $$(core_oat_name): $$(core_image_name) diff --git a/compiler/dex/verified_method.cc b/compiler/dex/verified_method.cc index ac7a4a7758..6d485986f6 100644 --- a/compiler/dex/verified_method.cc +++ b/compiler/dex/verified_method.cc @@ -98,7 +98,7 @@ bool VerifiedMethod::GenerateGcMap(verifier::MethodVerifier* method_verifier) { } size_t ref_bitmap_bytes = RoundUp(ref_bitmap_bits, kBitsPerByte) / kBitsPerByte; // There are 2 bytes to encode the number of entries. - if (num_entries >= 65536) { + if (num_entries > std::numeric_limits<uint16_t>::max()) { LOG(WARNING) << "Cannot encode GC map for method with " << num_entries << " entries: " << PrettyMethod(method_verifier->GetMethodReference().dex_method_index, *method_verifier->GetMethodReference().dex_file); diff --git a/compiler/dex/verified_method.h b/compiler/dex/verified_method.h index 242e3dfe6e..07f9a9bd9f 100644 --- a/compiler/dex/verified_method.h +++ b/compiler/dex/verified_method.h @@ -120,7 +120,7 @@ class VerifiedMethod { DequickenMap dequicken_map_; SafeCastSet safe_cast_set_; - bool has_verification_failures_; + bool has_verification_failures_ = false; // Copy of mapping generated by verifier of dex PCs of string init invocations // to the set of other registers that the receiver has been copied into. diff --git a/compiler/dwarf/dwarf_test.cc b/compiler/dwarf/dwarf_test.cc index 4971f0ef10..4d423d007f 100644 --- a/compiler/dwarf/dwarf_test.cc +++ b/compiler/dwarf/dwarf_test.cc @@ -26,11 +26,11 @@ namespace art { namespace dwarf { -constexpr CFIFormat kCFIFormat = DW_DEBUG_FRAME_FORMAT; - // Run the tests only on host since we need objdump. #ifndef HAVE_ANDROID_OS +constexpr CFIFormat kCFIFormat = DW_DEBUG_FRAME_FORMAT; + TEST_F(DwarfTest, DebugFrame) { const bool is64bit = false; diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc index 32bde8e3b4..73e121f1cd 100644 --- a/compiler/image_writer.cc +++ b/compiler/image_writer.cc @@ -110,10 +110,6 @@ bool ImageWriter::PrepareImageAddressSpace() { CheckNoDexObjects(); } - if (!AllocMemory()) { - return false; - } - if (kIsDebugBuild) { ScopedObjectAccess soa(Thread::Current()); CheckNonImageClassesRemoved(); @@ -123,6 +119,12 @@ bool ImageWriter::PrepareImageAddressSpace() { CalculateNewObjectOffsets(); Thread::Current()->TransitionFromRunnableToSuspended(kNative); + // This needs to happen after CalculateNewObjectOffsets since it relies on intern_table_bytes_ and + // bin size sums being calculated. + if (!AllocMemory()) { + return false; + } + return true; } @@ -205,7 +207,7 @@ bool ImageWriter::Write(const std::string& image_filename, } // Write out the image bitmap at the page aligned start of the image end. - const auto& bitmap_section = image_header->GetImageSection(ImageHeader::kSectionImageBitmap); + const ImageSection& bitmap_section = image_header->GetImageSection(ImageHeader::kSectionImageBitmap); CHECK_ALIGNED(bitmap_section.Offset(), kPageSize); if (!image_file->Write(reinterpret_cast<char*>(image_bitmap_->Begin()), bitmap_section.Size(), bitmap_section.Offset())) { @@ -222,26 +224,10 @@ bool ImageWriter::Write(const std::string& image_filename, return true; } -void ImageWriter::SetImageOffset(mirror::Object* object, - ImageWriter::BinSlot bin_slot, - size_t offset) { +void ImageWriter::SetImageOffset(mirror::Object* object, size_t offset) { DCHECK(object != nullptr); DCHECK_NE(offset, 0U); - mirror::Object* obj = reinterpret_cast<mirror::Object*>(image_->Begin() + offset); - DCHECK_ALIGNED(obj, kObjectAlignment); - static size_t max_offset = 0; - max_offset = std::max(max_offset, offset); - image_bitmap_->Set(obj); // Mark the obj as mutated, since we will end up changing it. - { - // Remember the object-inside-of-the-image's hash code so we can restore it after the copy. - auto hash_it = saved_hashes_map_.find(bin_slot); - if (hash_it != saved_hashes_map_.end()) { - std::pair<BinSlot, uint32_t> slot_hash = *hash_it; - saved_hashes_.push_back(std::make_pair(obj, slot_hash.second)); - saved_hashes_map_.erase(hash_it); - } - } // The object is already deflated from when we set the bin slot. Just overwrite the lock word. object->SetLockWord(LockWord::FromForwardingAddress(offset), false); DCHECK_EQ(object->GetLockWord(false).ReadBarrierState(), 0u); @@ -262,7 +248,7 @@ void ImageWriter::AssignImageOffset(mirror::Object* object, ImageWriter::BinSlot size_t new_offset = image_objects_offset_begin_ + previous_bin_sizes + bin_slot.GetIndex(); DCHECK_ALIGNED(new_offset, kObjectAlignment); - SetImageOffset(object, bin_slot, new_offset); + SetImageOffset(object, new_offset); DCHECK_LT(new_offset, image_end_); } @@ -302,14 +288,14 @@ void ImageWriter::SetImageBinSlot(mirror::Object* object, BinSlot bin_slot) { // No hash, don't need to save it. break; case LockWord::kHashCode: - saved_hashes_map_[bin_slot] = lw.GetHashCode(); + DCHECK(saved_hashcode_map_.find(object) == saved_hashcode_map_.end()); + saved_hashcode_map_.emplace(object, lw.GetHashCode()); break; default: LOG(FATAL) << "Unreachable."; UNREACHABLE(); } - object->SetLockWord(LockWord::FromForwardingAddress(static_cast<uint32_t>(bin_slot)), - false); + object->SetLockWord(LockWord::FromForwardingAddress(bin_slot.Uint32Value()), false); DCHECK_EQ(object->GetLockWord(false).ReadBarrierState(), 0u); DCHECK(IsImageBinSlotAssigned(object)); } @@ -487,11 +473,8 @@ void ImageWriter::AssignImageBinSlot(mirror::Object* object) { ++bin_slot_count_[bin]; - DCHECK_LT(GetBinSizeSum(), image_->Size()); - // Grow the image closer to the end by the object we just assigned. image_end_ += offset_delta; - DCHECK_LT(image_end_, image_->Size()); } bool ImageWriter::WillMethodBeDirty(ArtMethod* m) const { @@ -535,10 +518,8 @@ ImageWriter::BinSlot ImageWriter::GetImageBinSlot(mirror::Object* object) const } bool ImageWriter::AllocMemory() { - auto* runtime = Runtime::Current(); - const size_t heap_size = runtime->GetHeap()->GetTotalMemory(); - // Add linear alloc usage since we need to have room for the ArtFields. - const size_t length = RoundUp(heap_size + runtime->GetLinearAlloc()->GetUsedMemory(), kPageSize); + const size_t length = RoundUp(image_objects_offset_begin_ + GetBinSizeSum() + intern_table_bytes_, + kPageSize); std::string error_msg; image_.reset(MemMap::MapAnonymous("image writer image", nullptr, length, PROT_READ | PROT_WRITE, false, false, &error_msg)); @@ -547,9 +528,10 @@ bool ImageWriter::AllocMemory() { return false; } - // Create the image bitmap. - image_bitmap_.reset(gc::accounting::ContinuousSpaceBitmap::Create("image bitmap", image_->Begin(), - RoundUp(length, kPageSize))); + // Create the image bitmap, only needs to cover mirror object section which is up to image_end_. + CHECK_LE(image_end_, length); + image_bitmap_.reset(gc::accounting::ContinuousSpaceBitmap::Create( + "image bitmap", image_->Begin(), RoundUp(image_end_, kPageSize))); if (image_bitmap_.get() == nullptr) { LOG(ERROR) << "Failed to allocate memory for image bitmap"; return false; @@ -569,42 +551,6 @@ bool ImageWriter::ComputeLazyFieldsForClassesVisitor(Class* c, void* /*arg*/) { return true; } -// Collect all the java.lang.String in the heap and put them in the output strings_ array. -class StringCollector { - public: - StringCollector(Handle<mirror::ObjectArray<mirror::String>> strings, size_t index) - : strings_(strings), index_(index) { - } - static void Callback(Object* obj, void* arg) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - auto* collector = reinterpret_cast<StringCollector*>(arg); - if (obj->GetClass()->IsStringClass()) { - collector->strings_->SetWithoutChecks<false>(collector->index_++, obj->AsString()); - } - } - size_t GetIndex() const { - return index_; - } - - private: - Handle<mirror::ObjectArray<mirror::String>> strings_; - size_t index_; -}; - -// Compare strings based on length, used for sorting strings by length / reverse length. -class LexicographicalStringComparator { - public: - bool operator()(const mirror::HeapReference<mirror::String>& lhs, - const mirror::HeapReference<mirror::String>& rhs) const - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - mirror::String* lhs_s = lhs.AsMirrorPtr(); - mirror::String* rhs_s = rhs.AsMirrorPtr(); - uint16_t* lhs_begin = lhs_s->GetValue(); - uint16_t* rhs_begin = rhs_s->GetValue(); - return std::lexicographical_compare(lhs_begin, lhs_begin + lhs_s->GetLength(), - rhs_begin, rhs_begin + rhs_s->GetLength()); - } -}; - void ImageWriter::ComputeEagerResolvedStringsCallback(Object* obj, void* arg ATTRIBUTE_UNUSED) { if (!obj->GetClass()->IsStringClass()) { return; @@ -769,7 +715,8 @@ void ImageWriter::CalculateObjectBinSlots(Object* obj) { DCHECK_EQ(obj, obj->AsString()->Intern()); return; } - mirror::String* const interned = obj->AsString()->Intern(); + mirror::String* const interned = Runtime::Current()->GetInternTable()->InternStrong( + obj->AsString()->Intern()); if (obj != interned) { if (!IsImageBinSlotAssigned(interned)) { // interned obj is after us, allocate its location early @@ -965,7 +912,6 @@ void ImageWriter::CalculateNewObjectOffsets() { // know where image_roots is going to end up image_end_ += RoundUp(sizeof(ImageHeader), kObjectAlignment); // 64-bit-alignment - DCHECK_LT(image_end_, image_->Size()); image_objects_offset_begin_ = image_end_; // Prepare bin slots for dex cache arrays. PrepareDexCacheArraySlots(); @@ -997,7 +943,6 @@ void ImageWriter::CalculateNewObjectOffsets() { // Transform each object's bin slot into an offset which will be used to do the final copy. heap->VisitObjects(UnbinObjectsIntoOffsetCallback, this); - DCHECK(saved_hashes_map_.empty()); // All binslot hashes should've been put into vector by now. DCHECK_EQ(image_end_, GetBinSizeSum(kBinMirrorCount) + image_objects_offset_begin_); @@ -1010,6 +955,11 @@ void ImageWriter::CalculateNewObjectOffsets() { bin_slot_previous_sizes_[native_reloc.bin_type]; } + // Calculate how big the intern table will be after being serialized. + auto* const intern_table = Runtime::Current()->GetInternTable(); + CHECK_EQ(intern_table->WeakSize(), 0u) << " should have strong interned all the strings"; + intern_table_bytes_ = intern_table->WriteToMemory(nullptr); + // Note that image_end_ is left at end of used mirror object section. } @@ -1039,6 +989,10 @@ void ImageWriter::CreateHeader(size_t oat_loaded_size, size_t oat_data_offset) { CHECK_EQ(image_objects_offset_begin_ + bin_slot_previous_sizes_[kBinArtMethodClean], methods_section->Offset()); cur_pos = methods_section->End(); + // Calculate the size of the interned strings. + auto* interned_strings_section = §ions[ImageHeader::kSectionInternedStrings]; + *interned_strings_section = ImageSection(cur_pos, intern_table_bytes_); + cur_pos = interned_strings_section->End(); // Finally bitmap section. const size_t bitmap_bytes = image_bitmap_->Size(); auto* bitmap_section = §ions[ImageHeader::kSectionImageBitmap]; @@ -1046,16 +1000,19 @@ void ImageWriter::CreateHeader(size_t oat_loaded_size, size_t oat_data_offset) { cur_pos = bitmap_section->End(); if (kIsDebugBuild) { size_t idx = 0; - for (auto& section : sections) { + for (const ImageSection& section : sections) { LOG(INFO) << static_cast<ImageHeader::ImageSections>(idx) << " " << section; ++idx; } LOG(INFO) << "Methods: clean=" << clean_methods_ << " dirty=" << dirty_methods_; } + const size_t image_end = static_cast<uint32_t>(interned_strings_section->End()); + CHECK_EQ(AlignUp(image_begin_ + image_end, kPageSize), oat_file_begin) << + "Oat file should be right after the image."; // Create the header. new (image_->Begin()) ImageHeader( - PointerToLowMemUInt32(image_begin_), static_cast<uint32_t>(methods_section->End()), sections, - image_roots_address_, oat_file_->GetOatHeader().GetChecksum(), + PointerToLowMemUInt32(image_begin_), image_end, + sections, image_roots_address_, oat_file_->GetOatHeader().GetChecksum(), PointerToLowMemUInt32(oat_file_begin), PointerToLowMemUInt32(oat_data_begin_), PointerToLowMemUInt32(oat_data_end), PointerToLowMemUInt32(oat_file_end), target_ptr_size_, compile_pic_); @@ -1068,6 +1025,37 @@ ArtMethod* ImageWriter::GetImageMethodAddress(ArtMethod* method) { return reinterpret_cast<ArtMethod*>(image_begin_ + it->second.offset); } +class FixupRootVisitor : public RootVisitor { + public: + explicit FixupRootVisitor(ImageWriter* image_writer) : image_writer_(image_writer) { + } + + void VisitRoots(mirror::Object*** roots, size_t count, const RootInfo& info ATTRIBUTE_UNUSED) + OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + for (size_t i = 0; i < count; ++i) { + *roots[i] = ImageAddress(*roots[i]); + } + } + + void VisitRoots(mirror::CompressedReference<mirror::Object>** roots, size_t count, + const RootInfo& info ATTRIBUTE_UNUSED) + OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + for (size_t i = 0; i < count; ++i) { + roots[i]->Assign(ImageAddress(roots[i]->AsMirrorPtr())); + } + } + + private: + ImageWriter* const image_writer_; + + mirror::Object* ImageAddress(mirror::Object* obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + const size_t offset = image_writer_->GetImageOffset(obj); + auto* const dest = reinterpret_cast<Object*>(image_writer_->image_begin_ + offset); + VLOG(compiler) << "Update root from " << obj << " to " << dest; + return dest; + } +}; + void ImageWriter::CopyAndFixupNativeData() { // Copy ArtFields and methods to their locations and update the array for convenience. for (auto& pair : native_object_reloc_) { @@ -1088,7 +1076,7 @@ void ImageWriter::CopyAndFixupNativeData() { } // Fixup the image method roots. auto* image_header = reinterpret_cast<ImageHeader*>(image_->Begin()); - const auto& methods_section = image_header->GetMethodsSection(); + const ImageSection& methods_section = image_header->GetMethodsSection(); for (size_t i = 0; i < ImageHeader::kImageMethodsCount; ++i) { auto* m = image_methods_[i]; CHECK(m != nullptr); @@ -1101,18 +1089,35 @@ void ImageWriter::CopyAndFixupNativeData() { auto* dest = reinterpret_cast<ArtMethod*>(image_begin_ + it->second.offset); image_header->SetImageMethod(static_cast<ImageHeader::ImageMethod>(i), dest); } + // Write the intern table into the image. + const ImageSection& intern_table_section = image_header->GetImageSection( + ImageHeader::kSectionInternedStrings); + InternTable* const intern_table = Runtime::Current()->GetInternTable(); + uint8_t* const memory_ptr = image_->Begin() + intern_table_section.Offset(); + const size_t intern_table_bytes = intern_table->WriteToMemory(memory_ptr); + // Fixup the pointers in the newly written intern table to contain image addresses. + InternTable temp_table; + // Note that we require that ReadFromMemory does not make an internal copy of the elements so that + // the VisitRoots() will update the memory directly rather than the copies. + // This also relies on visit roots not doing any verification which could fail after we update + // the roots to be the image addresses. + temp_table.ReadFromMemory(memory_ptr); + CHECK_EQ(temp_table.Size(), intern_table->Size()); + FixupRootVisitor visitor(this); + temp_table.VisitRoots(&visitor, kVisitRootFlagAllRoots); + CHECK_EQ(intern_table_bytes, intern_table_bytes_); } void ImageWriter::CopyAndFixupObjects() { gc::Heap* heap = Runtime::Current()->GetHeap(); heap->VisitObjects(CopyAndFixupObjectsCallback, this); // Fix up the object previously had hash codes. - for (const std::pair<mirror::Object*, uint32_t>& hash_pair : saved_hashes_) { + for (const auto& hash_pair : saved_hashcode_map_) { Object* obj = hash_pair.first; DCHECK_EQ(obj->GetLockWord<kVerifyNone>(false).ReadBarrierState(), 0U); obj->SetLockWord<kVerifyNone>(LockWord::FromHashCode(hash_pair.second, 0U), false); } - saved_hashes_.clear(); + saved_hashcode_map_.clear(); } void ImageWriter::CopyAndFixupObjectsCallback(Object* obj, void* arg) { @@ -1155,18 +1160,22 @@ void ImageWriter::FixupPointerArray(mirror::Object* dst, mirror::PointerArray* a } void ImageWriter::CopyAndFixupObject(Object* obj) { - // see GetLocalAddress for similar computation size_t offset = GetImageOffset(obj); auto* dst = reinterpret_cast<Object*>(image_->Begin() + offset); - const uint8_t* src = reinterpret_cast<const uint8_t*>(obj); + DCHECK_LT(offset, image_end_); + const auto* src = reinterpret_cast<const uint8_t*>(obj); + + image_bitmap_->Set(dst); // Mark the obj as live. - size_t n = obj->SizeOf(); + const size_t n = obj->SizeOf(); DCHECK_LE(offset + n, image_->Size()); memcpy(dst, src, n); // Write in a hash code of objects which have inflated monitors or a hash code in their monitor // word. - dst->SetLockWord(LockWord::Default(), false); + const auto it = saved_hashcode_map_.find(obj); + dst->SetLockWord(it != saved_hashcode_map_.end() ? + LockWord::FromHashCode(it->second, 0u) : LockWord::Default(), false); FixupObject(obj, dst); } @@ -1176,7 +1185,7 @@ class FixupVisitor { FixupVisitor(ImageWriter* image_writer, Object* copy) : image_writer_(image_writer), copy_(copy) { } - void operator()(Object* obj, MemberOffset offset, bool /*is_static*/) const + void operator()(Object* obj, MemberOffset offset, bool is_static ATTRIBUTE_UNUSED) const EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_, Locks::heap_bitmap_lock_) { Object* ref = obj->GetFieldObject<Object, kVerifyNone>(offset); // Use SetFieldObjectWithoutWriteBarrier to avoid card marking since we are writing to the @@ -1186,7 +1195,7 @@ class FixupVisitor { } // java.lang.ref.Reference visitor. - void operator()(mirror::Class* /*klass*/, mirror::Reference* ref) const + void operator()(mirror::Class* klass ATTRIBUTE_UNUSED, mirror::Reference* ref) const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) EXCLUSIVE_LOCKS_REQUIRED(Locks::heap_bitmap_lock_) { copy_->SetFieldObjectWithoutWriteBarrier<false, true, kVerifyNone>( @@ -1490,4 +1499,11 @@ uint32_t ImageWriter::BinSlot::GetIndex() const { return lockword_ & ~kBinMask; } +uint8_t* ImageWriter::GetOatFileBegin() const { + DCHECK_GT(intern_table_bytes_, 0u); + return image_begin_ + RoundUp( + image_end_ + bin_slot_sizes_[kBinArtField] + bin_slot_sizes_[kBinArtMethodDirty] + + bin_slot_sizes_[kBinArtMethodClean] + intern_table_bytes_, kPageSize); +} + } // namespace art diff --git a/compiler/image_writer.h b/compiler/image_writer.h index a35d6ad9c9..9d45ce2bd4 100644 --- a/compiler/image_writer.h +++ b/compiler/image_writer.h @@ -54,7 +54,7 @@ class ImageWriter FINAL { quick_to_interpreter_bridge_offset_(0), compile_pic_(compile_pic), target_ptr_size_(InstructionSetPointerSize(compiler_driver_.GetInstructionSet())), bin_slot_sizes_(), bin_slot_previous_sizes_(), bin_slot_count_(), - dirty_methods_(0u), clean_methods_(0u) { + intern_table_bytes_(0u), dirty_methods_(0u), clean_methods_(0u) { CHECK_NE(image_begin, 0U); std::fill(image_methods_, image_methods_ + arraysize(image_methods_), nullptr); } @@ -84,11 +84,7 @@ class ImageWriter FINAL { image_begin_ + RoundUp(sizeof(ImageHeader), kObjectAlignment) + it->second + offset); } - uint8_t* GetOatFileBegin() const { - return image_begin_ + RoundUp( - image_end_ + bin_slot_sizes_[kBinArtField] + bin_slot_sizes_[kBinArtMethodDirty] + - bin_slot_sizes_[kBinArtMethodClean], kPageSize); - } + uint8_t* GetOatFileBegin() const; bool Write(const std::string& image_filename, const std::string& oat_filename, const std::string& oat_location) @@ -158,7 +154,7 @@ class ImageWriter FINAL { // The offset in bytes from the beginning of the bin. Aligned to object size. uint32_t GetIndex() const; // Pack into a single uint32_t, for storing into a lock word. - explicit operator uint32_t() const { return lockword_; } + uint32_t Uint32Value() const { return lockword_; } // Comparison operator for map support bool operator<(const BinSlot& other) const { return lockword_ < other.lockword_; } @@ -170,7 +166,7 @@ class ImageWriter FINAL { // We use the lock word to store the offset of the object in the image. void AssignImageOffset(mirror::Object* object, BinSlot bin_slot) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - void SetImageOffset(mirror::Object* object, BinSlot bin_slot, size_t offset) + void SetImageOffset(mirror::Object* object, size_t offset) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); bool IsImageOffsetAssigned(mirror::Object* object) const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); @@ -330,11 +326,9 @@ class ImageWriter FINAL { // The start offsets of the dex cache arrays. SafeMap<const DexFile*, size_t> dex_cache_array_starts_; - // Saved hashes (objects are inside of the image so that they don't move). - std::vector<std::pair<mirror::Object*, uint32_t>> saved_hashes_; - - // Saved hashes (objects are bin slots to inside of the image, not yet allocated an address). - std::map<BinSlot, uint32_t> saved_hashes_map_; + // Saved hash codes. We use these to restore lockwords which were temporarily used to have + // forwarding addresses as well as copying over hash codes. + std::unordered_map<mirror::Object*, uint32_t> saved_hashcode_map_; // Beginning target oat address for the pointers from the output image to its oat file. const uint8_t* oat_data_begin_; @@ -360,6 +354,9 @@ class ImageWriter FINAL { size_t bin_slot_previous_sizes_[kBinSize]; // Number of bytes in previous bins. size_t bin_slot_count_[kBinSize]; // Number of objects in a bin + // Cached size of the intern table for when we allocate memory. + size_t intern_table_bytes_; + // ArtField, ArtMethod relocating map. These are allocated as array of structs but we want to // have one entry per art field for convenience. ArtFields are placed right after the end of the // image objects (aka sum of bin_slot_sizes_). ArtMethods are placed right after the ArtFields. @@ -376,8 +373,9 @@ class ImageWriter FINAL { uint64_t dirty_methods_; uint64_t clean_methods_; - friend class FixupVisitor; friend class FixupClassVisitor; + friend class FixupRootVisitor; + friend class FixupVisitor; DISALLOW_COPY_AND_ASSIGN(ImageWriter); }; diff --git a/compiler/optimizing/bounds_check_elimination.cc b/compiler/optimizing/bounds_check_elimination.cc index b2b54965b5..9d5e3fdca1 100644 --- a/compiler/optimizing/bounds_check_elimination.cc +++ b/compiler/optimizing/bounds_check_elimination.cc @@ -126,11 +126,14 @@ class ValueBound : public ValueObject { return instruction_ == bound.instruction_ && constant_ == bound.constant_; } - static HInstruction* FromArrayLengthToNewArrayIfPossible(HInstruction* instruction) { - // Null check on the NewArray should have been eliminated by instruction - // simplifier already. - if (instruction->IsArrayLength() && instruction->InputAt(0)->IsNewArray()) { - return instruction->InputAt(0)->AsNewArray(); + static HInstruction* FromArrayLengthToArray(HInstruction* instruction) { + DCHECK(instruction->IsArrayLength() || instruction->IsNewArray()); + if (instruction->IsArrayLength()) { + HInstruction* input = instruction->InputAt(0); + if (input->IsNullCheck()) { + input = input->AsNullCheck()->InputAt(0); + } + return input; } return instruction; } @@ -146,8 +149,9 @@ class ValueBound : public ValueObject { // Some bounds are created with HNewArray* as the instruction instead // of HArrayLength*. They are treated the same. - instruction1 = FromArrayLengthToNewArrayIfPossible(instruction1); - instruction2 = FromArrayLengthToNewArrayIfPossible(instruction2); + // HArrayLength with the same array input are considered equal also. + instruction1 = FromArrayLengthToArray(instruction1); + instruction2 = FromArrayLengthToArray(instruction2); return instruction1 == instruction2; } @@ -271,7 +275,7 @@ class ArrayAccessInsideLoopFinder : public ValueObject { // Loop header of loop_info. Exiting loop is normal. return false; } - const GrowableArray<HBasicBlock*> successors = block->GetSuccessors(); + const GrowableArray<HBasicBlock*>& successors = block->GetSuccessors(); for (size_t i = 0; i < successors.Size(); i++) { if (!loop_info->Contains(*successors.Get(i))) { // One of the successors exits the loop. @@ -293,8 +297,14 @@ class ArrayAccessInsideLoopFinder : public ValueObject { void Run() { HLoopInformation* loop_info = induction_variable_->GetBlock()->GetLoopInformation(); - for (HBlocksInLoopIterator it_loop(*loop_info); !it_loop.Done(); it_loop.Advance()) { - HBasicBlock* block = it_loop.Current(); + HBlocksInLoopReversePostOrderIterator it_loop(*loop_info); + HBasicBlock* block = it_loop.Current(); + DCHECK(block == induction_variable_->GetBlock()); + // Skip loop header. Since narrowed value range of a MonotonicValueRange only + // applies to the loop body (after the test at the end of the loop header). + it_loop.Advance(); + for (; !it_loop.Done(); it_loop.Advance()) { + block = it_loop.Current(); DCHECK(block->IsInLoop()); if (!DominatesAllBackEdges(block, loop_info)) { // In order not to trigger deoptimization unnecessarily, make sure @@ -308,30 +318,35 @@ class ArrayAccessInsideLoopFinder : public ValueObject { // that the loop will loop through the full monotonic value range from // initial_ to end_. So adding deoptimization might be too aggressive and can // trigger deoptimization unnecessarily even if the loop won't actually throw - // AIOOBE. Otherwise, the loop induction variable is going to cover the full - // monotonic value range from initial_ to end_, and deoptimizations are added - // iff the loop will throw AIOOBE. + // AIOOBE. found_array_length_ = nullptr; return; } for (HInstruction* instruction = block->GetFirstInstruction(); instruction != nullptr; instruction = instruction->GetNext()) { - if (!instruction->IsArrayGet() && !instruction->IsArraySet()) { + if (!instruction->IsBoundsCheck()) { continue; } - HInstruction* index = instruction->InputAt(1); - if (!index->IsBoundsCheck()) { + + HInstruction* length_value = instruction->InputAt(1); + if (length_value->IsIntConstant()) { + // TODO: may optimize for constant case. continue; } - HArrayLength* array_length = index->InputAt(1)->AsArrayLength(); - if (array_length == nullptr) { - DCHECK(index->InputAt(1)->IsIntConstant()); - // TODO: may optimize for constant case. + DCHECK(!length_value->IsPhi()); + if (length_value->IsPhi()) { + // Outer loop shouldn't collect bounds checks inside inner + // loop because the inner loop body doen't dominate + // outer loop's back edges. However just to be on the safe side, + // if there are any such cases, we just skip over them. continue; } + DCHECK(length_value->IsArrayLength()); + HArrayLength* array_length = length_value->AsArrayLength(); + HInstruction* array = array_length->InputAt(0); if (array->IsNullCheck()) { array = array->AsNullCheck()->InputAt(0); @@ -347,7 +362,7 @@ class ArrayAccessInsideLoopFinder : public ValueObject { continue; } - index = index->AsBoundsCheck()->InputAt(0); + HInstruction* index = instruction->AsBoundsCheck()->InputAt(0); HInstruction* left = index; int32_t right = 0; if (left == induction_variable_ || @@ -375,7 +390,7 @@ class ArrayAccessInsideLoopFinder : public ValueObject { // The instruction that corresponds to a MonotonicValueRange. HInstruction* induction_variable_; - // The array length of the array that's accessed inside the loop. + // The array length of the array that's accessed inside the loop body. HArrayLength* found_array_length_; // The lowest and highest constant offsets relative to induction variable @@ -411,6 +426,8 @@ class ValueRange : public ArenaObject<kArenaAllocMisc> { ValueBound GetLower() const { return lower_; } ValueBound GetUpper() const { return upper_; } + bool IsConstantValueRange() { return lower_.IsConstant() && upper_.IsConstant(); } + // If it's certain that this value range fits in other_range. virtual bool FitsIn(ValueRange* other_range) const { if (other_range == nullptr) { @@ -495,13 +512,30 @@ class MonotonicValueRange : public ValueRange { ValueBound GetBound() const { return bound_; } void SetEnd(HInstruction* end) { end_ = end; } void SetInclusive(bool inclusive) { inclusive_ = inclusive; } - HBasicBlock* GetLoopHead() const { + HBasicBlock* GetLoopHeader() const { DCHECK(induction_variable_->GetBlock()->IsLoopHeader()); return induction_variable_->GetBlock(); } MonotonicValueRange* AsMonotonicValueRange() OVERRIDE { return this; } + HBasicBlock* GetLoopHeaderSuccesorInLoop() { + HBasicBlock* header = GetLoopHeader(); + HInstruction* instruction = header->GetLastInstruction(); + DCHECK(instruction->IsIf()); + HIf* h_if = instruction->AsIf(); + HLoopInformation* loop_info = header->GetLoopInformation(); + bool true_successor_in_loop = loop_info->Contains(*h_if->IfTrueSuccessor()); + bool false_successor_in_loop = loop_info->Contains(*h_if->IfFalseSuccessor()); + + // Just in case it's some strange loop structure. + if (true_successor_in_loop && false_successor_in_loop) { + return nullptr; + } + DCHECK(true_successor_in_loop || false_successor_in_loop); + return false_successor_in_loop ? h_if->IfFalseSuccessor() : h_if->IfTrueSuccessor(); + } + // If it's certain that this value range fits in other_range. bool FitsIn(ValueRange* other_range) const OVERRIDE { if (other_range == nullptr) { @@ -593,12 +627,114 @@ class MonotonicValueRange : public ValueRange { } } + // Try to add HDeoptimize's in the loop pre-header first to narrow this range. + // For example, this loop: + // + // for (int i = start; i < end; i++) { + // array[i - 1] = array[i] + array[i + 1]; + // } + // + // will be transformed to: + // + // int array_length_in_loop_body_if_needed; + // if (start >= end) { + // array_length_in_loop_body_if_needed = 0; + // } else { + // if (start < 1) deoptimize(); + // if (array == null) deoptimize(); + // array_length = array.length; + // if (end > array_length - 1) deoptimize; + // array_length_in_loop_body_if_needed = array_length; + // } + // for (int i = start; i < end; i++) { + // // No more null check and bounds check. + // // array.length value is replaced with array_length_in_loop_body_if_needed + // // in the loop body. + // array[i - 1] = array[i] + array[i + 1]; + // } + // + // We basically first go through the loop body and find those array accesses whose + // index is at a constant offset from the induction variable ('i' in the above example), + // and update offset_low and offset_high along the way. We then add the following + // deoptimizations in the loop pre-header (suppose end is not inclusive). + // if (start < -offset_low) deoptimize(); + // if (end >= array.length - offset_high) deoptimize(); + // It might be necessary to first hoist array.length (and the null check on it) out of + // the loop with another deoptimization. + // + // In order not to trigger deoptimization unnecessarily, we want to make a strong + // guarantee that no deoptimization is triggered if the loop body itself doesn't + // throw AIOOBE. (It's the same as saying if deoptimization is triggered, the loop + // body must throw AIOOBE). + // This is achieved by the following: + // 1) We only process loops that iterate through the full monotonic range from + // initial_ to end_. We do the following checks to make sure that's the case: + // a) The loop doesn't have early exit (via break, return, etc.) + // b) The increment_ is 1/-1. An increment of 2, for example, may skip end_. + // 2) We only collect array accesses of blocks in the loop body that dominate + // all loop back edges, these array accesses are guaranteed to happen + // at each loop iteration. + // With 1) and 2), if the loop body doesn't throw AIOOBE, collected array accesses + // when the induction variable is at initial_ and end_ must be in a legal range. + // Since the added deoptimizations are basically checking the induction variable + // at initial_ and end_ values, no deoptimization will be triggered either. + // + // A special case is the loop body isn't entered at all. In that case, we may still + // add deoptimization due to the analysis described above. In order not to trigger + // deoptimization, we do a test between initial_ and end_ first and skip over + // the added deoptimization. + ValueRange* NarrowWithDeoptimization() { + if (increment_ != 1 && increment_ != -1) { + // In order not to trigger deoptimization unnecessarily, we want to + // make sure the loop iterates through the full range from initial_ to + // end_ so that boundaries are covered by the loop. An increment of 2, + // for example, may skip end_. + return this; + } + + if (end_ == nullptr) { + // No full info to add deoptimization. + return this; + } + + HBasicBlock* header = induction_variable_->GetBlock(); + DCHECK(header->IsLoopHeader()); + HBasicBlock* pre_header = header->GetLoopInformation()->GetPreHeader(); + if (!initial_->GetBlock()->Dominates(pre_header) || + !end_->GetBlock()->Dominates(pre_header)) { + // Can't add a check in loop pre-header if the value isn't available there. + return this; + } + + ArrayAccessInsideLoopFinder finder(induction_variable_); + + if (!finder.HasFoundArrayLength()) { + // No array access was found inside the loop that can benefit + // from deoptimization. + return this; + } + + if (!AddDeoptimization(finder)) { + return this; + } + + // After added deoptimizations, induction variable fits in + // [-offset_low, array.length-1-offset_high], adjusted with collected offsets. + ValueBound lower = ValueBound(0, -finder.GetOffsetLow()); + ValueBound upper = ValueBound(finder.GetFoundArrayLength(), -1 - finder.GetOffsetHigh()); + // We've narrowed the range after added deoptimizations. + return new (GetAllocator()) ValueRange(GetAllocator(), lower, upper); + } + // Returns true if adding a (constant >= value) check for deoptimization // is allowed and will benefit compiled code. - bool CanAddDeoptimizationConstant(HInstruction* value, - int32_t constant, - bool* is_proven) { + bool CanAddDeoptimizationConstant(HInstruction* value, int32_t constant, bool* is_proven) { *is_proven = false; + HBasicBlock* header = induction_variable_->GetBlock(); + DCHECK(header->IsLoopHeader()); + HBasicBlock* pre_header = header->GetLoopInformation()->GetPreHeader(); + DCHECK(value->GetBlock()->Dominates(pre_header)); + // See if we can prove the relationship first. if (value->IsIntConstant()) { if (value->AsIntConstant()->GetValue() >= constant) { @@ -615,22 +751,118 @@ class MonotonicValueRange : public ValueRange { return true; } + // Try to filter out cases that the loop entry test will never be true. + bool LoopEntryTestUseful() { + if (initial_->IsIntConstant() && end_->IsIntConstant()) { + int32_t initial_val = initial_->AsIntConstant()->GetValue(); + int32_t end_val = end_->AsIntConstant()->GetValue(); + if (increment_ == 1) { + if (inclusive_) { + return initial_val > end_val; + } else { + return initial_val >= end_val; + } + } else { + DCHECK_EQ(increment_, -1); + if (inclusive_) { + return initial_val < end_val; + } else { + return initial_val <= end_val; + } + } + } + return true; + } + + // Returns the block for adding deoptimization. + HBasicBlock* TransformLoopForDeoptimizationIfNeeded() { + HBasicBlock* header = induction_variable_->GetBlock(); + DCHECK(header->IsLoopHeader()); + HBasicBlock* pre_header = header->GetLoopInformation()->GetPreHeader(); + // Deoptimization is only added when both initial_ and end_ are defined + // before the loop. + DCHECK(initial_->GetBlock()->Dominates(pre_header)); + DCHECK(end_->GetBlock()->Dominates(pre_header)); + + // If it can be proven the loop body is definitely entered (unless exception + // is thrown in the loop header for which triggering deoptimization is fine), + // there is no need for tranforming the loop. In that case, deoptimization + // will just be added in the loop pre-header. + if (!LoopEntryTestUseful()) { + return pre_header; + } + + HGraph* graph = header->GetGraph(); + graph->TransformLoopHeaderForBCE(header); + HBasicBlock* new_pre_header = header->GetDominator(); + DCHECK(new_pre_header == header->GetLoopInformation()->GetPreHeader()); + HBasicBlock* if_block = new_pre_header->GetDominator(); + HBasicBlock* dummy_block = if_block->GetSuccessors().Get(0); // True successor. + HBasicBlock* deopt_block = if_block->GetSuccessors().Get(1); // False successor. + + dummy_block->AddInstruction(new (graph->GetArena()) HGoto()); + deopt_block->AddInstruction(new (graph->GetArena()) HGoto()); + new_pre_header->AddInstruction(new (graph->GetArena()) HGoto()); + return deopt_block; + } + + // Adds a test between initial_ and end_ to see if the loop body is entered. + // If the loop body isn't entered at all, it jumps to the loop pre-header (after + // transformation) to avoid any deoptimization. + void AddLoopBodyEntryTest() { + HBasicBlock* header = induction_variable_->GetBlock(); + DCHECK(header->IsLoopHeader()); + HBasicBlock* pre_header = header->GetLoopInformation()->GetPreHeader(); + HBasicBlock* if_block = pre_header->GetDominator(); + HGraph* graph = header->GetGraph(); + + HCondition* cond; + if (increment_ == 1) { + if (inclusive_) { + cond = new (graph->GetArena()) HGreaterThan(initial_, end_); + } else { + cond = new (graph->GetArena()) HGreaterThanOrEqual(initial_, end_); + } + } else { + DCHECK_EQ(increment_, -1); + if (inclusive_) { + cond = new (graph->GetArena()) HLessThan(initial_, end_); + } else { + cond = new (graph->GetArena()) HLessThanOrEqual(initial_, end_); + } + } + HIf* h_if = new (graph->GetArena()) HIf(cond); + if_block->AddInstruction(cond); + if_block->AddInstruction(h_if); + } + // Adds a check that (value >= constant), and HDeoptimize otherwise. void AddDeoptimizationConstant(HInstruction* value, - int32_t constant) { - HBasicBlock* block = induction_variable_->GetBlock(); - DCHECK(block->IsLoopHeader()); - HGraph* graph = block->GetGraph(); - HBasicBlock* pre_header = block->GetLoopInformation()->GetPreHeader(); - HSuspendCheck* suspend_check = block->GetLoopInformation()->GetSuspendCheck(); + int32_t constant, + HBasicBlock* deopt_block, + bool loop_entry_test_block_added) { + HBasicBlock* header = induction_variable_->GetBlock(); + DCHECK(header->IsLoopHeader()); + HBasicBlock* pre_header = header->GetDominator(); + if (loop_entry_test_block_added) { + DCHECK(deopt_block->GetSuccessors().Get(0) == pre_header); + } else { + DCHECK(deopt_block == pre_header); + } + HGraph* graph = header->GetGraph(); + HSuspendCheck* suspend_check = header->GetLoopInformation()->GetSuspendCheck(); + if (loop_entry_test_block_added) { + DCHECK_EQ(deopt_block, header->GetDominator()->GetDominator()->GetSuccessors().Get(1)); + } + HIntConstant* const_instr = graph->GetIntConstant(constant); HCondition* cond = new (graph->GetArena()) HLessThan(value, const_instr); HDeoptimize* deoptimize = new (graph->GetArena()) HDeoptimize(cond, suspend_check->GetDexPc()); - pre_header->InsertInstructionBefore(cond, pre_header->GetLastInstruction()); - pre_header->InsertInstructionBefore(deoptimize, pre_header->GetLastInstruction()); + deopt_block->InsertInstructionBefore(cond, deopt_block->GetLastInstruction()); + deopt_block->InsertInstructionBefore(deoptimize, deopt_block->GetLastInstruction()); deoptimize->CopyEnvironmentFromWithLoopPhiAdjustment( - suspend_check->GetEnvironment(), block); + suspend_check->GetEnvironment(), header); } // Returns true if adding a (value <= array_length + offset) check for deoptimization @@ -640,6 +872,26 @@ class MonotonicValueRange : public ValueRange { int32_t offset, bool* is_proven) { *is_proven = false; + HBasicBlock* header = induction_variable_->GetBlock(); + DCHECK(header->IsLoopHeader()); + HBasicBlock* pre_header = header->GetLoopInformation()->GetPreHeader(); + DCHECK(value->GetBlock()->Dominates(pre_header)); + + if (array_length->GetBlock() == header) { + // array_length_in_loop_body_if_needed only has correct value when the loop + // body is entered. We bail out in this case. Usually array_length defined + // in the loop header is already hoisted by licm. + return false; + } else { + // array_length is defined either before the loop header already, or in + // the loop body since it's used in the loop body. If it's defined in the loop body, + // a phi array_length_in_loop_body_if_needed is used to replace it. In that case, + // all the uses of array_length must be dominated by its definition in the loop + // body. array_length_in_loop_body_if_needed is guaranteed to be the same as + // array_length once the loop body is entered so all the uses of the phi will + // use the correct value. + } + if (offset > 0) { // There might be overflow issue. // TODO: handle this, possibly with some distance relationship between @@ -667,56 +919,99 @@ class MonotonicValueRange : public ValueRange { // Adds a check that (value <= array_length + offset), and HDeoptimize otherwise. void AddDeoptimizationArrayLength(HInstruction* value, HArrayLength* array_length, - int32_t offset) { - HBasicBlock* block = induction_variable_->GetBlock(); - DCHECK(block->IsLoopHeader()); - HGraph* graph = block->GetGraph(); - HBasicBlock* pre_header = block->GetLoopInformation()->GetPreHeader(); - HSuspendCheck* suspend_check = block->GetLoopInformation()->GetSuspendCheck(); + int32_t offset, + HBasicBlock* deopt_block, + bool loop_entry_test_block_added) { + HBasicBlock* header = induction_variable_->GetBlock(); + DCHECK(header->IsLoopHeader()); + HBasicBlock* pre_header = header->GetDominator(); + if (loop_entry_test_block_added) { + DCHECK(deopt_block->GetSuccessors().Get(0) == pre_header); + } else { + DCHECK(deopt_block == pre_header); + } + HGraph* graph = header->GetGraph(); + HSuspendCheck* suspend_check = header->GetLoopInformation()->GetSuspendCheck(); // We may need to hoist null-check and array_length out of loop first. - if (!array_length->GetBlock()->Dominates(pre_header)) { + if (!array_length->GetBlock()->Dominates(deopt_block)) { + // array_length must be defined in the loop body. + DCHECK(header->GetLoopInformation()->Contains(*array_length->GetBlock())); + DCHECK(array_length->GetBlock() != header); + HInstruction* array = array_length->InputAt(0); HNullCheck* null_check = array->AsNullCheck(); if (null_check != nullptr) { array = null_check->InputAt(0); } - // We've already made sure array is defined before the loop when collecting + // We've already made sure the array is defined before the loop when collecting // array accesses for the loop. - DCHECK(array->GetBlock()->Dominates(pre_header)); - if (null_check != nullptr && !null_check->GetBlock()->Dominates(pre_header)) { + DCHECK(array->GetBlock()->Dominates(deopt_block)); + if (null_check != nullptr && !null_check->GetBlock()->Dominates(deopt_block)) { // Hoist null check out of loop with a deoptimization. HNullConstant* null_constant = graph->GetNullConstant(); HCondition* null_check_cond = new (graph->GetArena()) HEqual(array, null_constant); // TODO: for one dex_pc, share the same deoptimization slow path. HDeoptimize* null_check_deoptimize = new (graph->GetArena()) HDeoptimize(null_check_cond, suspend_check->GetDexPc()); - pre_header->InsertInstructionBefore(null_check_cond, pre_header->GetLastInstruction()); - pre_header->InsertInstructionBefore( - null_check_deoptimize, pre_header->GetLastInstruction()); + deopt_block->InsertInstructionBefore( + null_check_cond, deopt_block->GetLastInstruction()); + deopt_block->InsertInstructionBefore( + null_check_deoptimize, deopt_block->GetLastInstruction()); // Eliminate null check in the loop. null_check->ReplaceWith(array); null_check->GetBlock()->RemoveInstruction(null_check); null_check_deoptimize->CopyEnvironmentFromWithLoopPhiAdjustment( - suspend_check->GetEnvironment(), block); + suspend_check->GetEnvironment(), header); } - // Hoist array_length out of loop. - array_length->MoveBefore(pre_header->GetLastInstruction()); + + HArrayLength* new_array_length = new (graph->GetArena()) HArrayLength(array); + deopt_block->InsertInstructionBefore(new_array_length, deopt_block->GetLastInstruction()); + + if (loop_entry_test_block_added) { + // Replace array_length defined inside the loop body with a phi + // array_length_in_loop_body_if_needed. This is a synthetic phi so there is + // no vreg number for it. + HPhi* phi = new (graph->GetArena()) HPhi( + graph->GetArena(), kNoRegNumber, 2, Primitive::kPrimInt); + // Set to 0 if the loop body isn't entered. + phi->SetRawInputAt(0, graph->GetIntConstant(0)); + // Set to array.length if the loop body is entered. + phi->SetRawInputAt(1, new_array_length); + pre_header->AddPhi(phi); + array_length->ReplaceWith(phi); + // Make sure phi is only used after the loop body is entered. + if (kIsDebugBuild) { + for (HUseIterator<HInstruction*> it(phi->GetUses()); + !it.Done(); + it.Advance()) { + HInstruction* user = it.Current()->GetUser(); + DCHECK(GetLoopHeaderSuccesorInLoop()->Dominates(user->GetBlock())); + } + } + } else { + array_length->ReplaceWith(new_array_length); + } + + array_length->GetBlock()->RemoveInstruction(array_length); + // Use new_array_length for deopt. + array_length = new_array_length; } - HIntConstant* offset_instr = graph->GetIntConstant(offset); - HAdd* add = new (graph->GetArena()) HAdd(Primitive::kPrimInt, array_length, offset_instr); - HCondition* cond = new (graph->GetArena()) HGreaterThan(value, add); - HDeoptimize* deoptimize = new (graph->GetArena()) - HDeoptimize(cond, suspend_check->GetDexPc()); - pre_header->InsertInstructionBefore(add, pre_header->GetLastInstruction()); - pre_header->InsertInstructionBefore(cond, pre_header->GetLastInstruction()); - pre_header->InsertInstructionBefore(deoptimize, pre_header->GetLastInstruction()); - deoptimize->CopyEnvironmentFromWithLoopPhiAdjustment( - suspend_check->GetEnvironment(), block); + HInstruction* added = array_length; + if (offset != 0) { + HIntConstant* offset_instr = graph->GetIntConstant(offset); + added = new (graph->GetArena()) HAdd(Primitive::kPrimInt, array_length, offset_instr); + deopt_block->InsertInstructionBefore(added, deopt_block->GetLastInstruction()); + } + HCondition* cond = new (graph->GetArena()) HGreaterThan(value, added); + HDeoptimize* deopt = new (graph->GetArena()) HDeoptimize(cond, suspend_check->GetDexPc()); + deopt_block->InsertInstructionBefore(cond, deopt_block->GetLastInstruction()); + deopt_block->InsertInstructionBefore(deopt, deopt_block->GetLastInstruction()); + deopt->CopyEnvironmentFromWithLoopPhiAdjustment(suspend_check->GetEnvironment(), header); } - // Add deoptimizations in loop pre-header with the collected array access + // Adds deoptimizations in loop pre-header with the collected array access // data so that value ranges can be established in loop body. // Returns true if deoptimizations are successfully added, or if it's proven // it's not necessary. @@ -733,70 +1028,60 @@ class MonotonicValueRange : public ValueRange { return false; } + HBasicBlock* deopt_block; + bool loop_entry_test_block_added = false; bool is_constant_proven, is_length_proven; + + HInstruction* const_comparing_instruction; + int32_t const_compared_to; + HInstruction* array_length_comparing_instruction; + int32_t array_length_offset; if (increment_ == 1) { // Increasing from initial_ to end_. - int32_t offset = inclusive_ ? -offset_high - 1 : -offset_high; - if (CanAddDeoptimizationConstant(initial_, -offset_low, &is_constant_proven) && - CanAddDeoptimizationArrayLength(end_, array_length, offset, &is_length_proven)) { - if (!is_constant_proven) { - AddDeoptimizationConstant(initial_, -offset_low); - } - if (!is_length_proven) { - AddDeoptimizationArrayLength(end_, array_length, offset); + const_comparing_instruction = initial_; + const_compared_to = -offset_low; + array_length_comparing_instruction = end_; + array_length_offset = inclusive_ ? -offset_high - 1 : -offset_high; + } else { + const_comparing_instruction = end_; + const_compared_to = inclusive_ ? -offset_low : -offset_low - 1; + array_length_comparing_instruction = initial_; + array_length_offset = -offset_high - 1; + } + + if (CanAddDeoptimizationConstant(const_comparing_instruction, + const_compared_to, + &is_constant_proven) && + CanAddDeoptimizationArrayLength(array_length_comparing_instruction, + array_length, + array_length_offset, + &is_length_proven)) { + if (!is_constant_proven || !is_length_proven) { + deopt_block = TransformLoopForDeoptimizationIfNeeded(); + loop_entry_test_block_added = (deopt_block != pre_header); + if (loop_entry_test_block_added) { + // Loop body may be entered. + AddLoopBodyEntryTest(); } - return true; } - } else if (increment_ == -1) { - // Decreasing from initial_ to end_. - int32_t constant = inclusive_ ? -offset_low : -offset_low - 1; - if (CanAddDeoptimizationConstant(end_, constant, &is_constant_proven) && - CanAddDeoptimizationArrayLength( - initial_, array_length, -offset_high - 1, &is_length_proven)) { - if (!is_constant_proven) { - AddDeoptimizationConstant(end_, constant); - } - if (!is_length_proven) { - AddDeoptimizationArrayLength(initial_, array_length, -offset_high - 1); - } - return true; + if (!is_constant_proven) { + AddDeoptimizationConstant(const_comparing_instruction, + const_compared_to, + deopt_block, + loop_entry_test_block_added); + } + if (!is_length_proven) { + AddDeoptimizationArrayLength(array_length_comparing_instruction, + array_length, + array_length_offset, + deopt_block, + loop_entry_test_block_added); } + return true; } return false; } - // Try to add HDeoptimize's in the loop pre-header first to narrow this range. - ValueRange* NarrowWithDeoptimization() { - if (increment_ != 1 && increment_ != -1) { - // TODO: possibly handle overflow/underflow issues with deoptimization. - return this; - } - - if (end_ == nullptr) { - // No full info to add deoptimization. - return this; - } - - ArrayAccessInsideLoopFinder finder(induction_variable_); - - if (!finder.HasFoundArrayLength()) { - // No array access was found inside the loop that can benefit - // from deoptimization. - return this; - } - - if (!AddDeoptimization(finder)) { - return this; - } - - // After added deoptimizations, induction variable fits in - // [-offset_low, array.length-1-offset_high], adjusted with collected offsets. - ValueBound lower = ValueBound(0, -finder.GetOffsetLow()); - ValueBound upper = ValueBound(finder.GetFoundArrayLength(), -1 - finder.GetOffsetHigh()); - // We've narrowed the range after added deoptimizations. - return new (GetAllocator()) ValueRange(GetAllocator(), lower, upper); - } - private: HPhi* const induction_variable_; // Induction variable for this monotonic value range. HInstruction* const initial_; // Initial value. @@ -819,12 +1104,17 @@ class BCEVisitor : public HGraphVisitor { // it's likely some AIOOBE will be thrown. static constexpr int32_t kMaxConstantForAddingDeoptimize = INT_MAX - 1024 * 1024; + // Added blocks for loop body entry test. + bool IsAddedBlock(HBasicBlock* block) const { + return block->GetBlockId() >= initial_block_size_; + } + explicit BCEVisitor(HGraph* graph) - : HGraphVisitor(graph), - maps_(graph->GetBlocks().Size()), - need_to_revisit_block_(false) {} + : HGraphVisitor(graph), maps_(graph->GetBlocks().Size()), + need_to_revisit_block_(false), initial_block_size_(graph->GetBlocks().Size()) {} void VisitBasicBlock(HBasicBlock* block) OVERRIDE { + DCHECK(!IsAddedBlock(block)); first_constant_index_bounds_check_map_.clear(); HGraphVisitor::VisitBasicBlock(block); if (need_to_revisit_block_) { @@ -839,6 +1129,10 @@ class BCEVisitor : public HGraphVisitor { private: // Return the map of proven value ranges at the beginning of a basic block. ArenaSafeMap<int, ValueRange*>* GetValueRangeMap(HBasicBlock* basic_block) { + if (IsAddedBlock(basic_block)) { + // Added blocks don't keep value ranges. + return nullptr; + } int block_id = basic_block->GetBlockId(); if (maps_.at(block_id) == nullptr) { std::unique_ptr<ArenaSafeMap<int, ValueRange*>> map( @@ -853,8 +1147,12 @@ class BCEVisitor : public HGraphVisitor { ValueRange* LookupValueRange(HInstruction* instruction, HBasicBlock* basic_block) { while (basic_block != nullptr) { ArenaSafeMap<int, ValueRange*>* map = GetValueRangeMap(basic_block); - if (map->find(instruction->GetId()) != map->end()) { - return map->Get(instruction->GetId()); + if (map != nullptr) { + if (map->find(instruction->GetId()) != map->end()) { + return map->Get(instruction->GetId()); + } + } else { + DCHECK(IsAddedBlock(basic_block)); } basic_block = basic_block->GetDominator(); } @@ -971,7 +1269,7 @@ class BCEVisitor : public HGraphVisitor { if (left_range != nullptr) { left_monotonic_range = left_range->AsMonotonicValueRange(); if (left_monotonic_range != nullptr) { - HBasicBlock* loop_head = left_monotonic_range->GetLoopHead(); + HBasicBlock* loop_head = left_monotonic_range->GetLoopHeader(); if (instruction->GetBlock() != loop_head) { // For monotonic value range, don't handle `instruction` // if it's not defined in the loop header. @@ -1013,7 +1311,7 @@ class BCEVisitor : public HGraphVisitor { // Update the info for monotonic value range. if (left_monotonic_range->GetInductionVariable() == left && left_monotonic_range->GetIncrement() < 0 && - block == left_monotonic_range->GetLoopHead() && + block == left_monotonic_range->GetLoopHeader() && instruction->IfFalseSuccessor()->GetLoopInformation() == block->GetLoopInformation()) { left_monotonic_range->SetEnd(right); left_monotonic_range->SetInclusive(cond == kCondLT); @@ -1047,7 +1345,7 @@ class BCEVisitor : public HGraphVisitor { // Update the info for monotonic value range. if (left_monotonic_range->GetInductionVariable() == left && left_monotonic_range->GetIncrement() > 0 && - block == left_monotonic_range->GetLoopHead() && + block == left_monotonic_range->GetLoopHeader() && instruction->IfFalseSuccessor()->GetLoopInformation() == block->GetLoopInformation()) { left_monotonic_range->SetEnd(right); left_monotonic_range->SetInclusive(cond == kCondGT); @@ -1083,7 +1381,16 @@ class BCEVisitor : public HGraphVisitor { HBasicBlock* block = bounds_check->GetBlock(); HInstruction* index = bounds_check->InputAt(0); HInstruction* array_length = bounds_check->InputAt(1); - DCHECK(array_length->IsIntConstant() || array_length->IsArrayLength()); + DCHECK(array_length->IsIntConstant() || + array_length->IsArrayLength() || + array_length->IsPhi()); + + if (array_length->IsPhi()) { + // Input 1 of the phi contains the real array.length once the loop body is + // entered. That value will be used for bound analysis. The graph is still + // strickly in SSA form. + array_length = array_length->AsPhi()->InputAt(1)->AsArrayLength(); + } if (!index->IsIntConstant()) { ValueRange* index_range = LookupValueRange(index, block); @@ -1238,25 +1545,26 @@ class BCEVisitor : public HGraphVisitor { } if (left_range->IsMonotonicValueRange() && - block == left_range->AsMonotonicValueRange()->GetLoopHead()) { + block == left_range->AsMonotonicValueRange()->GetLoopHeader()) { // The comparison is for an induction variable in the loop header. DCHECK(left == left_range->AsMonotonicValueRange()->GetInductionVariable()); - HBasicBlock* loop_body_successor; - if (LIKELY(block->GetLoopInformation()-> - Contains(*instruction->IfFalseSuccessor()))) { - loop_body_successor = instruction->IfFalseSuccessor(); - } else { - loop_body_successor = instruction->IfTrueSuccessor(); + HBasicBlock* loop_body_successor = + left_range->AsMonotonicValueRange()->GetLoopHeaderSuccesorInLoop(); + if (loop_body_successor == nullptr) { + // In case it's some strange loop structure. + return; } ValueRange* new_left_range = LookupValueRange(left, loop_body_successor); - if (new_left_range == left_range) { + if ((new_left_range == left_range) || + // Range narrowed with deoptimization is usually more useful than + // a constant range. + new_left_range->IsConstantValueRange()) { // We are not successful in narrowing the monotonic value range to // a regular value range. Try using deoptimization. new_left_range = left_range->AsMonotonicValueRange()-> NarrowWithDeoptimization(); if (new_left_range != left_range) { - GetValueRangeMap(instruction->IfFalseSuccessor())-> - Overwrite(left->GetId(), new_left_range); + GetValueRangeMap(loop_body_successor)->Overwrite(left->GetId(), new_left_range); } } } @@ -1511,6 +1819,9 @@ class BCEVisitor : public HGraphVisitor { // eliminate those bounds checks. bool need_to_revisit_block_; + // Initial number of blocks. + int32_t initial_block_size_; + DISALLOW_COPY_AND_ASSIGN(BCEVisitor); }; @@ -1527,7 +1838,22 @@ void BoundsCheckElimination::Run() { // value can be narrowed further down in the dominator tree. // // TODO: only visit blocks that dominate some array accesses. - visitor.VisitReversePostOrder(); + HBasicBlock* last_visited_block = nullptr; + for (HReversePostOrderIterator it(*graph_); !it.Done(); it.Advance()) { + HBasicBlock* current = it.Current(); + if (current == last_visited_block) { + // We may insert blocks into the reverse post order list when processing + // a loop header. Don't process it again. + DCHECK(current->IsLoopHeader()); + continue; + } + if (visitor.IsAddedBlock(current)) { + // Skip added blocks. Their effects are already taken care of. + continue; + } + visitor.VisitBasicBlock(current); + last_visited_block = current; + } } } // namespace art diff --git a/compiler/optimizing/bounds_check_elimination_test.cc b/compiler/optimizing/bounds_check_elimination_test.cc index e383ec664b..4701bddd48 100644 --- a/compiler/optimizing/bounds_check_elimination_test.cc +++ b/compiler/optimizing/bounds_check_elimination_test.cc @@ -440,22 +440,16 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination1) { HInstruction* bounds_check = nullptr; HGraph* graph = BuildSSAGraph1(&allocator, &bounds_check, 0, 1); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); + RunSimplifierAndGvn(graph); BoundsCheckElimination bounds_check_elimination(graph); bounds_check_elimination.Run(); - ASSERT_FALSE(IsRemoved(bounds_check)); - - // This time add gvn. Need gvn to eliminate the second - // HArrayLength which uses the null check as its input. - graph = BuildSSAGraph1(&allocator, &bounds_check, 0, 1); - graph->BuildDominatorTree(); - RunSimplifierAndGvn(graph); - BoundsCheckElimination bounds_check_elimination_after_gvn(graph); - bounds_check_elimination_after_gvn.Run(); ASSERT_TRUE(IsRemoved(bounds_check)); // for (int i=1; i<array.length; i++) { array[i] = 10; // Can eliminate. } graph = BuildSSAGraph1(&allocator, &bounds_check, 1, 1); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); RunSimplifierAndGvn(graph); BoundsCheckElimination bounds_check_elimination_with_initial_1(graph); bounds_check_elimination_with_initial_1.Run(); @@ -464,6 +458,7 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination1) { // for (int i=-1; i<array.length; i++) { array[i] = 10; // Can't eliminate. } graph = BuildSSAGraph1(&allocator, &bounds_check, -1, 1); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); RunSimplifierAndGvn(graph); BoundsCheckElimination bounds_check_elimination_with_initial_minus_1(graph); bounds_check_elimination_with_initial_minus_1.Run(); @@ -472,6 +467,7 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination1) { // for (int i=0; i<=array.length; i++) { array[i] = 10; // Can't eliminate. } graph = BuildSSAGraph1(&allocator, &bounds_check, 0, 1, kCondGT); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); RunSimplifierAndGvn(graph); BoundsCheckElimination bounds_check_elimination_with_greater_than(graph); bounds_check_elimination_with_greater_than.Run(); @@ -481,6 +477,7 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination1) { // array[i] = 10; // Can't eliminate due to overflow concern. } graph = BuildSSAGraph1(&allocator, &bounds_check, 0, 2); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); RunSimplifierAndGvn(graph); BoundsCheckElimination bounds_check_elimination_with_increment_2(graph); bounds_check_elimination_with_increment_2.Run(); @@ -489,6 +486,7 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination1) { // for (int i=1; i<array.length; i += 2) { array[i] = 10; // Can eliminate. } graph = BuildSSAGraph1(&allocator, &bounds_check, 1, 2); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); RunSimplifierAndGvn(graph); BoundsCheckElimination bounds_check_elimination_with_increment_2_from_1(graph); bounds_check_elimination_with_increment_2_from_1.Run(); @@ -579,22 +577,16 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination2) { HInstruction* bounds_check = nullptr; HGraph* graph = BuildSSAGraph2(&allocator, &bounds_check, 0); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); + RunSimplifierAndGvn(graph); BoundsCheckElimination bounds_check_elimination(graph); bounds_check_elimination.Run(); - ASSERT_FALSE(IsRemoved(bounds_check)); - - // This time add gvn. Need gvn to eliminate the second - // HArrayLength which uses the null check as its input. - graph = BuildSSAGraph2(&allocator, &bounds_check, 0); - graph->BuildDominatorTree(); - RunSimplifierAndGvn(graph); - BoundsCheckElimination bounds_check_elimination_after_gvn(graph); - bounds_check_elimination_after_gvn.Run(); ASSERT_TRUE(IsRemoved(bounds_check)); // for (int i=array.length; i>1; i--) { array[i-1] = 10; // Can eliminate. } graph = BuildSSAGraph2(&allocator, &bounds_check, 1); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); RunSimplifierAndGvn(graph); BoundsCheckElimination bounds_check_elimination_with_initial_1(graph); bounds_check_elimination_with_initial_1.Run(); @@ -603,6 +595,7 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination2) { // for (int i=array.length; i>-1; i--) { array[i-1] = 10; // Can't eliminate. } graph = BuildSSAGraph2(&allocator, &bounds_check, -1); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); RunSimplifierAndGvn(graph); BoundsCheckElimination bounds_check_elimination_with_initial_minus_1(graph); bounds_check_elimination_with_initial_minus_1.Run(); @@ -611,6 +604,7 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination2) { // for (int i=array.length; i>=0; i--) { array[i-1] = 10; // Can't eliminate. } graph = BuildSSAGraph2(&allocator, &bounds_check, 0, -1, kCondLT); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); RunSimplifierAndGvn(graph); BoundsCheckElimination bounds_check_elimination_with_less_than(graph); bounds_check_elimination_with_less_than.Run(); @@ -619,6 +613,7 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination2) { // for (int i=array.length; i>0; i-=2) { array[i-1] = 10; // Can eliminate. } graph = BuildSSAGraph2(&allocator, &bounds_check, 0, -2); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); RunSimplifierAndGvn(graph); BoundsCheckElimination bounds_check_elimination_increment_minus_2(graph); bounds_check_elimination_increment_minus_2.Run(); @@ -710,15 +705,17 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination3) { HInstruction* bounds_check = nullptr; HGraph* graph = BuildSSAGraph3(&allocator, &bounds_check, 0, 1, kCondGE); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); RunSimplifierAndGvn(graph); - BoundsCheckElimination bounds_check_elimination_after_gvn(graph); - bounds_check_elimination_after_gvn.Run(); + BoundsCheckElimination bounds_check_elimination(graph); + bounds_check_elimination.Run(); ASSERT_TRUE(IsRemoved(bounds_check)); // int[] array = new int[10]; // for (int i=1; i<10; i++) { array[i] = 10; // Can eliminate. } graph = BuildSSAGraph3(&allocator, &bounds_check, 1, 1, kCondGE); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); RunSimplifierAndGvn(graph); BoundsCheckElimination bounds_check_elimination_with_initial_1(graph); bounds_check_elimination_with_initial_1.Run(); @@ -728,6 +725,7 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination3) { // for (int i=0; i<=10; i++) { array[i] = 10; // Can't eliminate. } graph = BuildSSAGraph3(&allocator, &bounds_check, 0, 1, kCondGT); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); RunSimplifierAndGvn(graph); BoundsCheckElimination bounds_check_elimination_with_greater_than(graph); bounds_check_elimination_with_greater_than.Run(); @@ -737,6 +735,7 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination3) { // for (int i=1; i<10; i+=8) { array[i] = 10; // Can eliminate. } graph = BuildSSAGraph3(&allocator, &bounds_check, 1, 8, kCondGE); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); RunSimplifierAndGvn(graph); BoundsCheckElimination bounds_check_elimination_increment_8(graph); bounds_check_elimination_increment_8.Run(); @@ -828,22 +827,16 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination4) { HInstruction* bounds_check = nullptr; HGraph* graph = BuildSSAGraph4(&allocator, &bounds_check, 0); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); + RunSimplifierAndGvn(graph); BoundsCheckElimination bounds_check_elimination(graph); bounds_check_elimination.Run(); - ASSERT_FALSE(IsRemoved(bounds_check)); - - // This time add gvn. Need gvn to eliminate the second - // HArrayLength which uses the null check as its input. - graph = BuildSSAGraph4(&allocator, &bounds_check, 0); - graph->BuildDominatorTree(); - RunSimplifierAndGvn(graph); - BoundsCheckElimination bounds_check_elimination_after_gvn(graph); - bounds_check_elimination_after_gvn.Run(); ASSERT_TRUE(IsRemoved(bounds_check)); // for (int i=1; i<array.length; i++) { array[array.length-i-1] = 10; // Can eliminate. } graph = BuildSSAGraph4(&allocator, &bounds_check, 1); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); RunSimplifierAndGvn(graph); BoundsCheckElimination bounds_check_elimination_with_initial_1(graph); bounds_check_elimination_with_initial_1.Run(); @@ -852,6 +845,7 @@ TEST(BoundsCheckEliminationTest, LoopArrayBoundsElimination4) { // for (int i=0; i<=array.length; i++) { array[array.length-i] = 10; // Can't eliminate. } graph = BuildSSAGraph4(&allocator, &bounds_check, 0, kCondGT); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); RunSimplifierAndGvn(graph); BoundsCheckElimination bounds_check_elimination_with_greater_than(graph); bounds_check_elimination_with_greater_than.Run(); @@ -1027,6 +1021,7 @@ TEST(BoundsCheckEliminationTest, BubbleSortArrayBoundsElimination) { outer_body_add->AddSuccessor(outer_header); graph->BuildDominatorTree(); + graph->AnalyzeNaturalLoops(); RunSimplifierAndGvn(graph); // gvn should remove the same bounds check. ASSERT_FALSE(IsRemoved(bounds_check1)); diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc index e4680ff2fa..1f9287cbfc 100644 --- a/compiler/optimizing/builder.cc +++ b/compiler/optimizing/builder.cc @@ -723,10 +723,16 @@ bool HGraphBuilder::BuildInvoke(const Instruction& instruction, } } - invoke = new (arena_) HInvokeStaticOrDirect( - arena_, number_of_arguments, return_type, dex_pc, target_method.dex_method_index, - is_recursive, string_init_offset, invoke_type, optimized_invoke_type, - clinit_check_requirement); + invoke = new (arena_) HInvokeStaticOrDirect(arena_, + number_of_arguments, + return_type, + dex_pc, + target_method.dex_method_index, + is_recursive, + string_init_offset, + invoke_type, + optimized_invoke_type, + clinit_check_requirement); } size_t start_index = 0; diff --git a/compiler/optimizing/code_generator_arm.h b/compiler/optimizing/code_generator_arm.h index af2481661a..824e48cc9f 100644 --- a/compiler/optimizing/code_generator_arm.h +++ b/compiler/optimizing/code_generator_arm.h @@ -139,10 +139,16 @@ class LocationsBuilderARM : public HGraphVisitor { #define DECLARE_VISIT_INSTRUCTION(name, super) \ void Visit##name(H##name* instr); - FOR_EACH_CONCRETE_INSTRUCTION(DECLARE_VISIT_INSTRUCTION) + FOR_EACH_CONCRETE_INSTRUCTION_COMMON(DECLARE_VISIT_INSTRUCTION) + FOR_EACH_CONCRETE_INSTRUCTION_ARM(DECLARE_VISIT_INSTRUCTION) #undef DECLARE_VISIT_INSTRUCTION + void VisitInstruction(HInstruction* instruction) OVERRIDE { + LOG(FATAL) << "Unreachable instruction " << instruction->DebugName() + << " (id " << instruction->GetId() << ")"; + } + private: void HandleInvoke(HInvoke* invoke); void HandleBitwiseOperation(HBinaryOperation* operation); @@ -163,10 +169,16 @@ class InstructionCodeGeneratorARM : public HGraphVisitor { #define DECLARE_VISIT_INSTRUCTION(name, super) \ void Visit##name(H##name* instr); - FOR_EACH_CONCRETE_INSTRUCTION(DECLARE_VISIT_INSTRUCTION) + FOR_EACH_CONCRETE_INSTRUCTION_COMMON(DECLARE_VISIT_INSTRUCTION) + FOR_EACH_CONCRETE_INSTRUCTION_ARM(DECLARE_VISIT_INSTRUCTION) #undef DECLARE_VISIT_INSTRUCTION + void VisitInstruction(HInstruction* instruction) OVERRIDE { + LOG(FATAL) << "Unreachable instruction " << instruction->DebugName() + << " (id " << instruction->GetId() << ")"; + } + ArmAssembler* GetAssembler() const { return assembler_; } private: diff --git a/compiler/optimizing/code_generator_arm64.h b/compiler/optimizing/code_generator_arm64.h index 2d2419a284..f96810ff80 100644 --- a/compiler/optimizing/code_generator_arm64.h +++ b/compiler/optimizing/code_generator_arm64.h @@ -147,9 +147,17 @@ class InstructionCodeGeneratorARM64 : public HGraphVisitor { #define DECLARE_VISIT_INSTRUCTION(name, super) \ void Visit##name(H##name* instr) OVERRIDE; - FOR_EACH_CONCRETE_INSTRUCTION(DECLARE_VISIT_INSTRUCTION) + + FOR_EACH_CONCRETE_INSTRUCTION_COMMON(DECLARE_VISIT_INSTRUCTION) + FOR_EACH_CONCRETE_INSTRUCTION_ARM64(DECLARE_VISIT_INSTRUCTION) + #undef DECLARE_VISIT_INSTRUCTION + void VisitInstruction(HInstruction* instruction) OVERRIDE { + LOG(FATAL) << "Unreachable instruction " << instruction->DebugName() + << " (id " << instruction->GetId() << ")"; + } + Arm64Assembler* GetAssembler() const { return assembler_; } vixl::MacroAssembler* GetVIXLAssembler() { return GetAssembler()->vixl_masm_; } @@ -188,9 +196,17 @@ class LocationsBuilderARM64 : public HGraphVisitor { #define DECLARE_VISIT_INSTRUCTION(name, super) \ void Visit##name(H##name* instr) OVERRIDE; - FOR_EACH_CONCRETE_INSTRUCTION(DECLARE_VISIT_INSTRUCTION) + + FOR_EACH_CONCRETE_INSTRUCTION_COMMON(DECLARE_VISIT_INSTRUCTION) + FOR_EACH_CONCRETE_INSTRUCTION_ARM64(DECLARE_VISIT_INSTRUCTION) + #undef DECLARE_VISIT_INSTRUCTION + void VisitInstruction(HInstruction* instruction) OVERRIDE { + LOG(FATAL) << "Unreachable instruction " << instruction->DebugName() + << " (id " << instruction->GetId() << ")"; + } + private: void HandleBinaryOp(HBinaryOperation* instr); void HandleFieldSet(HInstruction* instruction); diff --git a/compiler/optimizing/code_generator_x86.h b/compiler/optimizing/code_generator_x86.h index faf3cf9ffa..696d8d549e 100644 --- a/compiler/optimizing/code_generator_x86.h +++ b/compiler/optimizing/code_generator_x86.h @@ -124,10 +124,16 @@ class LocationsBuilderX86 : public HGraphVisitor { #define DECLARE_VISIT_INSTRUCTION(name, super) \ void Visit##name(H##name* instr) OVERRIDE; - FOR_EACH_CONCRETE_INSTRUCTION(DECLARE_VISIT_INSTRUCTION) + FOR_EACH_CONCRETE_INSTRUCTION_COMMON(DECLARE_VISIT_INSTRUCTION) + FOR_EACH_CONCRETE_INSTRUCTION_X86(DECLARE_VISIT_INSTRUCTION) #undef DECLARE_VISIT_INSTRUCTION + void VisitInstruction(HInstruction* instruction) OVERRIDE { + LOG(FATAL) << "Unreachable instruction " << instruction->DebugName() + << " (id " << instruction->GetId() << ")"; + } + private: void HandleBitwiseOperation(HBinaryOperation* instruction); void HandleInvoke(HInvoke* invoke); @@ -148,10 +154,16 @@ class InstructionCodeGeneratorX86 : public HGraphVisitor { #define DECLARE_VISIT_INSTRUCTION(name, super) \ void Visit##name(H##name* instr) OVERRIDE; - FOR_EACH_CONCRETE_INSTRUCTION(DECLARE_VISIT_INSTRUCTION) + FOR_EACH_CONCRETE_INSTRUCTION_COMMON(DECLARE_VISIT_INSTRUCTION) + FOR_EACH_CONCRETE_INSTRUCTION_X86(DECLARE_VISIT_INSTRUCTION) #undef DECLARE_VISIT_INSTRUCTION + void VisitInstruction(HInstruction* instruction) OVERRIDE { + LOG(FATAL) << "Unreachable instruction " << instruction->DebugName() + << " (id " << instruction->GetId() << ")"; + } + X86Assembler* GetAssembler() const { return assembler_; } private: diff --git a/compiler/optimizing/code_generator_x86_64.h b/compiler/optimizing/code_generator_x86_64.h index e46994c79e..215754cd46 100644 --- a/compiler/optimizing/code_generator_x86_64.h +++ b/compiler/optimizing/code_generator_x86_64.h @@ -134,10 +134,16 @@ class LocationsBuilderX86_64 : public HGraphVisitor { #define DECLARE_VISIT_INSTRUCTION(name, super) \ void Visit##name(H##name* instr) OVERRIDE; - FOR_EACH_CONCRETE_INSTRUCTION(DECLARE_VISIT_INSTRUCTION) + FOR_EACH_CONCRETE_INSTRUCTION_COMMON(DECLARE_VISIT_INSTRUCTION) + FOR_EACH_CONCRETE_INSTRUCTION_X86_64(DECLARE_VISIT_INSTRUCTION) #undef DECLARE_VISIT_INSTRUCTION + void VisitInstruction(HInstruction* instruction) OVERRIDE { + LOG(FATAL) << "Unreachable instruction " << instruction->DebugName() + << " (id " << instruction->GetId() << ")"; + } + private: void HandleInvoke(HInvoke* invoke); void HandleBitwiseOperation(HBinaryOperation* operation); @@ -158,10 +164,16 @@ class InstructionCodeGeneratorX86_64 : public HGraphVisitor { #define DECLARE_VISIT_INSTRUCTION(name, super) \ void Visit##name(H##name* instr) OVERRIDE; - FOR_EACH_CONCRETE_INSTRUCTION(DECLARE_VISIT_INSTRUCTION) + FOR_EACH_CONCRETE_INSTRUCTION_COMMON(DECLARE_VISIT_INSTRUCTION) + FOR_EACH_CONCRETE_INSTRUCTION_X86_64(DECLARE_VISIT_INSTRUCTION) #undef DECLARE_VISIT_INSTRUCTION + void VisitInstruction(HInstruction* instruction) OVERRIDE { + LOG(FATAL) << "Unreachable instruction " << instruction->DebugName() + << " (id " << instruction->GetId() << ")"; + } + X86_64Assembler* GetAssembler() const { return assembler_; } private: diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index c3fc33735a..92ebf060eb 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -27,6 +27,7 @@ #include "mirror/class_loader.h" #include "mirror/dex_cache.h" #include "nodes.h" +#include "reference_type_propagation.h" #include "register_allocator.h" #include "ssa_phi_elimination.h" #include "scoped_thread_state_change.h" @@ -57,7 +58,7 @@ void HInliner::Run() { next_block = (i == blocks.Size() - 1) ? nullptr : blocks.Get(i + 1); for (HInstruction* instruction = block->GetFirstInstruction(); instruction != nullptr;) { HInstruction* next = instruction->GetNext(); - HInvokeStaticOrDirect* call = instruction->AsInvokeStaticOrDirect(); + HInvoke* call = instruction->AsInvoke(); // As long as the call is not intrinsified, it is worth trying to inline. if (call != nullptr && call->GetIntrinsic() == Intrinsics::kNone) { // We use the original invoke type to ensure the resolution of the called method @@ -83,6 +84,93 @@ void HInliner::Run() { } } +static bool IsMethodOrDeclaringClassFinal(ArtMethod* method) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + return method->IsFinal() || method->GetDeclaringClass()->IsFinal(); +} + +/** + * Given the `resolved_method` looked up in the dex cache, try to find + * the actual runtime target of an interface or virtual call. + * Return nullptr if the runtime target cannot be proven. + */ +static ArtMethod* FindVirtualOrInterfaceTarget(HInvoke* invoke, ArtMethod* resolved_method) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + if (IsMethodOrDeclaringClassFinal(resolved_method)) { + // No need to lookup further, the resolved method will be the target. + return resolved_method; + } + + HInstruction* receiver = invoke->InputAt(0); + if (receiver->IsNullCheck()) { + // Due to multiple levels of inlining within the same pass, it might be that + // null check does not have the reference type of the actual receiver. + receiver = receiver->InputAt(0); + } + ReferenceTypeInfo info = receiver->GetReferenceTypeInfo(); + if (info.IsTop()) { + // We have no information on the receiver. + return nullptr; + } else if (!info.IsExact()) { + // We currently only support inlining with known receivers. + // TODO: Remove this check, we should be able to inline final methods + // on unknown receivers. + return nullptr; + } else if (info.GetTypeHandle()->IsInterface()) { + // Statically knowing that the receiver has an interface type cannot + // help us find what is the target method. + return nullptr; + } else if (!resolved_method->GetDeclaringClass()->IsAssignableFrom(info.GetTypeHandle().Get())) { + // The method that we're trying to call is not in the receiver's class or super classes. + return nullptr; + } + + ClassLinker* cl = Runtime::Current()->GetClassLinker(); + size_t pointer_size = cl->GetImagePointerSize(); + if (invoke->IsInvokeInterface()) { + resolved_method = info.GetTypeHandle()->FindVirtualMethodForInterface( + resolved_method, pointer_size); + } else { + DCHECK(invoke->IsInvokeVirtual()); + resolved_method = info.GetTypeHandle()->FindVirtualMethodForVirtual( + resolved_method, pointer_size); + } + + if (resolved_method == nullptr) { + // The information we had on the receiver was not enough to find + // the target method. Since we check above the exact type of the receiver, + // the only reason this can happen is an IncompatibleClassChangeError. + return nullptr; + } else if (resolved_method->IsAbstract()) { + // The information we had on the receiver was not enough to find + // the target method. Since we check above the exact type of the receiver, + // the only reason this can happen is an IncompatibleClassChangeError. + return nullptr; + } else if (IsMethodOrDeclaringClassFinal(resolved_method)) { + // A final method has to be the target method. + return resolved_method; + } else if (info.IsExact()) { + // If we found a method and the receiver's concrete type is statically + // known, we know for sure the target. + return resolved_method; + } else { + // Even if we did find a method, the receiver type was not enough to + // statically find the runtime target. + return nullptr; + } +} + +static uint32_t FindMethodIndexIn(ArtMethod* method, + const DexFile& dex_file, + uint32_t referrer_index) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + if (method->GetDexFile()->GetLocation().compare(dex_file.GetLocation()) == 0) { + return method->GetDexMethodIndex(); + } else { + return method->FindDexMethodIndexInOtherDexFile(dex_file, referrer_index); + } +} + bool HInliner::TryInline(HInvoke* invoke_instruction, uint32_t method_index) const { ScopedObjectAccess soa(Thread::Current()); const DexFile& caller_dex_file = *caller_compilation_unit_.GetDexFile(); @@ -99,6 +187,25 @@ bool HInliner::TryInline(HInvoke* invoke_instruction, uint32_t method_index) con return false; } + if (!invoke_instruction->IsInvokeStaticOrDirect()) { + resolved_method = FindVirtualOrInterfaceTarget(invoke_instruction, resolved_method); + if (resolved_method == nullptr) { + VLOG(compiler) << "Interface or virtual call to " + << PrettyMethod(method_index, caller_dex_file) + << " could not be statically determined"; + return false; + } + // We have found a method, but we need to find where that method is for the caller's + // dex file. + method_index = FindMethodIndexIn(resolved_method, caller_dex_file, method_index); + if (method_index == DexFile::kDexNoIndex) { + VLOG(compiler) << "Interface or virtual call to " + << PrettyMethod(resolved_method) + << " cannot be inlined because unaccessible to caller"; + return false; + } + } + bool same_dex_file = true; const DexFile& outer_dex_file = *outer_compilation_unit_.GetDexFile(); if (resolved_method->GetDexFile()->GetLocation().compare(outer_dex_file.GetLocation()) != 0) { @@ -149,7 +256,7 @@ bool HInliner::TryInline(HInvoke* invoke_instruction, uint32_t method_index) con return false; } - if (!TryBuildAndInline(resolved_method, invoke_instruction, method_index, same_dex_file)) { + if (!TryBuildAndInline(resolved_method, invoke_instruction, same_dex_file)) { return false; } @@ -160,11 +267,11 @@ bool HInliner::TryInline(HInvoke* invoke_instruction, uint32_t method_index) con bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, HInvoke* invoke_instruction, - uint32_t method_index, bool same_dex_file) const { ScopedObjectAccess soa(Thread::Current()); const DexFile::CodeItem* code_item = resolved_method->GetCodeItem(); - const DexFile& caller_dex_file = *caller_compilation_unit_.GetDexFile(); + const DexFile& callee_dex_file = *resolved_method->GetDexFile(); + uint32_t method_index = resolved_method->GetDexMethodIndex(); DexCompilationUnit dex_compilation_unit( nullptr, @@ -204,7 +311,7 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, } HGraph* callee_graph = new (graph_->GetArena()) HGraph( graph_->GetArena(), - caller_dex_file, + callee_dex_file, method_index, requires_ctor_barrier, compiler_driver_->GetInstructionSet(), @@ -221,7 +328,7 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, &inline_stats); if (!builder.BuildGraph(*code_item)) { - VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) + VLOG(compiler) << "Method " << PrettyMethod(method_index, callee_dex_file) << " could not be built, so cannot be inlined"; // There could be multiple reasons why the graph could not be built, including // unaccessible methods/fields due to using a different dex cache. We do not mark @@ -231,14 +338,14 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, if (!RegisterAllocator::CanAllocateRegistersFor(*callee_graph, compiler_driver_->GetInstructionSet())) { - VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) + VLOG(compiler) << "Method " << PrettyMethod(method_index, callee_dex_file) << " cannot be inlined because of the register allocator"; resolved_method->SetShouldNotInline(); return false; } if (!callee_graph->TryBuildingSsa()) { - VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) + VLOG(compiler) << "Method " << PrettyMethod(method_index, callee_dex_file) << " could not be transformed to SSA"; resolved_method->SetShouldNotInline(); return false; @@ -247,11 +354,13 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, // Run simple optimizations on the graph. HDeadCodeElimination dce(callee_graph, stats_); HConstantFolding fold(callee_graph); + ReferenceTypePropagation type_propagation(callee_graph, handles_); InstructionSimplifier simplify(callee_graph, stats_); HOptimization* optimizations[] = { &dce, &fold, + &type_propagation, &simplify, }; @@ -265,6 +374,7 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, outer_compilation_unit_, dex_compilation_unit, compiler_driver_, + handles_, stats_, depth_ + 1); inliner.Run(); @@ -275,7 +385,7 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, // a throw predecessor. HBasicBlock* exit_block = callee_graph->GetExitBlock(); if (exit_block == nullptr) { - VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) + VLOG(compiler) << "Method " << PrettyMethod(method_index, callee_dex_file) << " could not be inlined because it has an infinite loop"; resolved_method->SetShouldNotInline(); return false; @@ -289,7 +399,7 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, } } if (has_throw_predecessor) { - VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) + VLOG(compiler) << "Method " << PrettyMethod(method_index, callee_dex_file) << " could not be inlined because one branch always throws"; resolved_method->SetShouldNotInline(); return false; @@ -300,7 +410,7 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, for (; !it.Done(); it.Advance()) { HBasicBlock* block = it.Current(); if (block->IsLoopHeader()) { - VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) + VLOG(compiler) << "Method " << PrettyMethod(method_index, callee_dex_file) << " could not be inlined because it contains a loop"; resolved_method->SetShouldNotInline(); return false; @@ -314,21 +424,21 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, if (current->IsInvokeInterface()) { // Disable inlining of interface calls. The cost in case of entering the // resolution conflict is currently too high. - VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) + VLOG(compiler) << "Method " << PrettyMethod(method_index, callee_dex_file) << " could not be inlined because it has an interface call."; resolved_method->SetShouldNotInline(); return false; } if (!same_dex_file && current->NeedsEnvironment()) { - VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) + VLOG(compiler) << "Method " << PrettyMethod(method_index, callee_dex_file) << " could not be inlined because " << current->DebugName() << " needs an environment and is in a different dex file"; return false; } if (!same_dex_file && current->NeedsDexCache()) { - VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) + VLOG(compiler) << "Method " << PrettyMethod(method_index, callee_dex_file) << " could not be inlined because " << current->DebugName() << " it is in a different dex file and requires access to the dex cache"; // Do not flag the method as not-inlineable. A caller within the same diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h index f7d8cf8715..24044b73a1 100644 --- a/compiler/optimizing/inliner.h +++ b/compiler/optimizing/inliner.h @@ -34,13 +34,15 @@ class HInliner : public HOptimization { const DexCompilationUnit& outer_compilation_unit, const DexCompilationUnit& caller_compilation_unit, CompilerDriver* compiler_driver, + StackHandleScopeCollection* handles, OptimizingCompilerStats* stats, size_t depth = 0) : HOptimization(outer_graph, true, kInlinerPassName, stats), outer_compilation_unit_(outer_compilation_unit), caller_compilation_unit_(caller_compilation_unit), compiler_driver_(compiler_driver), - depth_(depth) {} + depth_(depth), + handles_(handles) {} void Run() OVERRIDE; @@ -50,13 +52,13 @@ class HInliner : public HOptimization { bool TryInline(HInvoke* invoke_instruction, uint32_t method_index) const; bool TryBuildAndInline(ArtMethod* resolved_method, HInvoke* invoke_instruction, - uint32_t method_index, bool same_dex_file) const; const DexCompilationUnit& outer_compilation_unit_; const DexCompilationUnit& caller_compilation_unit_; CompilerDriver* const compiler_driver_; const size_t depth_; + StackHandleScopeCollection* const handles_; DISALLOW_COPY_AND_ASSIGN(HInliner); }; diff --git a/compiler/optimizing/instruction_simplifier.cc b/compiler/optimizing/instruction_simplifier.cc index fcb3471821..98a5841f80 100644 --- a/compiler/optimizing/instruction_simplifier.cc +++ b/compiler/optimizing/instruction_simplifier.cc @@ -186,33 +186,92 @@ bool InstructionSimplifierVisitor::IsDominatedByInputNullCheck(HInstruction* ins return false; } -void InstructionSimplifierVisitor::VisitCheckCast(HCheckCast* check_cast) { - HLoadClass* load_class = check_cast->InputAt(1)->AsLoadClass(); - if (!check_cast->InputAt(0)->CanBeNull() || IsDominatedByInputNullCheck(check_cast)) { - check_cast->ClearMustDoNullCheck(); - } - - if (!load_class->IsResolved()) { +// Returns whether doing a type test between the class of `object` against `klass` has +// a statically known outcome. The result of the test is stored in `outcome`. +static bool TypeCheckHasKnownOutcome(HLoadClass* klass, HInstruction* object, bool* outcome) { + if (!klass->IsResolved()) { // If the class couldn't be resolve it's not safe to compare against it. It's // default type would be Top which might be wider that the actual class type // and thus producing wrong results. - return; + return false; } - ReferenceTypeInfo obj_rti = check_cast->InputAt(0)->GetReferenceTypeInfo(); - ReferenceTypeInfo class_rti = load_class->GetLoadedClassRTI(); + + ReferenceTypeInfo obj_rti = object->GetReferenceTypeInfo(); + ReferenceTypeInfo class_rti = klass->GetLoadedClassRTI(); ScopedObjectAccess soa(Thread::Current()); if (class_rti.IsSupertypeOf(obj_rti)) { + *outcome = true; + return true; + } else if (obj_rti.IsExact()) { + // The test failed at compile time so will also fail at runtime. + *outcome = false; + return true; + } else if (!class_rti.IsInterface() && !obj_rti.IsSupertypeOf(class_rti)) { + // Different type hierarchy. The test will fail. + *outcome = false; + return true; + } + return false; +} + +void InstructionSimplifierVisitor::VisitCheckCast(HCheckCast* check_cast) { + HInstruction* object = check_cast->InputAt(0); + if (!object->CanBeNull() || IsDominatedByInputNullCheck(check_cast)) { + check_cast->ClearMustDoNullCheck(); + } + + if (object->IsNullConstant()) { check_cast->GetBlock()->RemoveInstruction(check_cast); if (stats_ != nullptr) { stats_->RecordStat(MethodCompilationStat::kRemovedCheckedCast); } + return; + } + + bool outcome; + if (TypeCheckHasKnownOutcome(check_cast->InputAt(1)->AsLoadClass(), object, &outcome)) { + if (outcome) { + check_cast->GetBlock()->RemoveInstruction(check_cast); + if (stats_ != nullptr) { + stats_->RecordStat(MethodCompilationStat::kRemovedCheckedCast); + } + } else { + // Don't do anything for exceptional cases for now. Ideally we should remove + // all instructions and blocks this instruction dominates. + } } } void InstructionSimplifierVisitor::VisitInstanceOf(HInstanceOf* instruction) { - if (!instruction->InputAt(0)->CanBeNull() || IsDominatedByInputNullCheck(instruction)) { + HInstruction* object = instruction->InputAt(0); + bool can_be_null = true; + if (!object->CanBeNull() || IsDominatedByInputNullCheck(instruction)) { + can_be_null = false; instruction->ClearMustDoNullCheck(); } + + HGraph* graph = GetGraph(); + if (object->IsNullConstant()) { + instruction->ReplaceWith(graph->GetIntConstant(0)); + instruction->GetBlock()->RemoveInstruction(instruction); + RecordSimplification(); + return; + } + + bool outcome; + if (TypeCheckHasKnownOutcome(instruction->InputAt(1)->AsLoadClass(), object, &outcome)) { + if (outcome && can_be_null) { + // Type test will succeed, we just need a null test. + HNotEqual* test = new (graph->GetArena()) HNotEqual(graph->GetNullConstant(), object); + instruction->GetBlock()->InsertInstructionBefore(test, instruction); + instruction->ReplaceWith(test); + } else { + // We've statically determined the result of the instanceof. + instruction->ReplaceWith(graph->GetIntConstant(outcome)); + } + RecordSimplification(); + instruction->GetBlock()->RemoveInstruction(instruction); + } } void InstructionSimplifierVisitor::VisitInstanceFieldSet(HInstanceFieldSet* instruction) { diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc index cd91d2c87b..4baa05c80c 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -1510,6 +1510,81 @@ void HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { invoke->GetBlock()->RemoveInstruction(invoke); } +/* + * Loop will be transformed to: + * old_pre_header + * | + * if_block + * / \ + * dummy_block deopt_block + * \ / + * new_pre_header + * | + * header + */ +void HGraph::TransformLoopHeaderForBCE(HBasicBlock* header) { + DCHECK(header->IsLoopHeader()); + HBasicBlock* pre_header = header->GetDominator(); + + // Need this to avoid critical edge. + HBasicBlock* if_block = new (arena_) HBasicBlock(this, header->GetDexPc()); + // Need this to avoid critical edge. + HBasicBlock* dummy_block = new (arena_) HBasicBlock(this, header->GetDexPc()); + HBasicBlock* deopt_block = new (arena_) HBasicBlock(this, header->GetDexPc()); + HBasicBlock* new_pre_header = new (arena_) HBasicBlock(this, header->GetDexPc()); + AddBlock(if_block); + AddBlock(dummy_block); + AddBlock(deopt_block); + AddBlock(new_pre_header); + + header->ReplacePredecessor(pre_header, new_pre_header); + pre_header->successors_.Reset(); + pre_header->dominated_blocks_.Reset(); + + pre_header->AddSuccessor(if_block); + if_block->AddSuccessor(dummy_block); // True successor + if_block->AddSuccessor(deopt_block); // False successor + dummy_block->AddSuccessor(new_pre_header); + deopt_block->AddSuccessor(new_pre_header); + + pre_header->dominated_blocks_.Add(if_block); + if_block->SetDominator(pre_header); + if_block->dominated_blocks_.Add(dummy_block); + dummy_block->SetDominator(if_block); + if_block->dominated_blocks_.Add(deopt_block); + deopt_block->SetDominator(if_block); + if_block->dominated_blocks_.Add(new_pre_header); + new_pre_header->SetDominator(if_block); + new_pre_header->dominated_blocks_.Add(header); + header->SetDominator(new_pre_header); + + size_t index_of_header = 0; + while (reverse_post_order_.Get(index_of_header) != header) { + index_of_header++; + } + MakeRoomFor(&reverse_post_order_, 4, index_of_header - 1); + reverse_post_order_.Put(index_of_header++, if_block); + reverse_post_order_.Put(index_of_header++, dummy_block); + reverse_post_order_.Put(index_of_header++, deopt_block); + reverse_post_order_.Put(index_of_header++, new_pre_header); + + HLoopInformation* info = pre_header->GetLoopInformation(); + if (info != nullptr) { + if_block->SetLoopInformation(info); + dummy_block->SetLoopInformation(info); + deopt_block->SetLoopInformation(info); + new_pre_header->SetLoopInformation(info); + for (HLoopInformationOutwardIterator loop_it(*pre_header); + !loop_it.Done(); + loop_it.Advance()) { + loop_it.Current()->Add(if_block); + loop_it.Current()->Add(dummy_block); + loop_it.Current()->Add(deopt_block); + loop_it.Current()->Add(new_pre_header); + } + } +} + std::ostream& operator<<(std::ostream& os, const ReferenceTypeInfo& rhs) { ScopedObjectAccess soa(Thread::Current()); os << "[" diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index f87775e195..126b3b9879 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -195,6 +195,10 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { // Inline this graph in `outer_graph`, replacing the given `invoke` instruction. void InlineInto(HGraph* outer_graph, HInvoke* invoke); + // Need to add a couple of blocks to test if the loop body is entered and + // put deoptimization instructions, etc. + void TransformLoopHeaderForBCE(HBasicBlock* header); + // Removes `block` from the graph. void DeleteDeadBlock(HBasicBlock* block); @@ -824,7 +828,7 @@ class HLoopInformationOutwardIterator : public ValueObject { DISALLOW_COPY_AND_ASSIGN(HLoopInformationOutwardIterator); }; -#define FOR_EACH_CONCRETE_INSTRUCTION(M) \ +#define FOR_EACH_CONCRETE_INSTRUCTION_COMMON(M) \ M(Add, BinaryOperation) \ M(And, BinaryOperation) \ M(ArrayGet, Instruction) \ @@ -894,6 +898,21 @@ class HLoopInformationOutwardIterator : public ValueObject { M(UShr, BinaryOperation) \ M(Xor, BinaryOperation) \ +#define FOR_EACH_CONCRETE_INSTRUCTION_ARM(M) + +#define FOR_EACH_CONCRETE_INSTRUCTION_ARM64(M) + +#define FOR_EACH_CONCRETE_INSTRUCTION_X86(M) + +#define FOR_EACH_CONCRETE_INSTRUCTION_X86_64(M) + +#define FOR_EACH_CONCRETE_INSTRUCTION(M) \ + FOR_EACH_CONCRETE_INSTRUCTION_COMMON(M) \ + FOR_EACH_CONCRETE_INSTRUCTION_ARM(M) \ + FOR_EACH_CONCRETE_INSTRUCTION_ARM64(M) \ + FOR_EACH_CONCRETE_INSTRUCTION_X86(M) \ + FOR_EACH_CONCRETE_INSTRUCTION_X86_64(M) + #define FOR_EACH_INSTRUCTION(M) \ FOR_EACH_CONCRETE_INSTRUCTION(M) \ M(Constant, Instruction) \ @@ -1281,6 +1300,9 @@ class ReferenceTypeInfo : ValueObject { bool IsExact() const { return is_exact_; } bool IsTop() const { return is_top_; } + bool IsInterface() const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + return !IsTop() && GetTypeHandle()->IsInterface(); + } Handle<mirror::Class> GetTypeHandle() const { return type_handle_; } @@ -4246,6 +4268,39 @@ class HBlocksInLoopIterator : public ValueObject { DISALLOW_COPY_AND_ASSIGN(HBlocksInLoopIterator); }; +// Iterator over the blocks that art part of the loop. Includes blocks part +// of an inner loop. The order in which the blocks are iterated is reverse +// post order. +class HBlocksInLoopReversePostOrderIterator : public ValueObject { + public: + explicit HBlocksInLoopReversePostOrderIterator(const HLoopInformation& info) + : blocks_in_loop_(info.GetBlocks()), + blocks_(info.GetHeader()->GetGraph()->GetReversePostOrder()), + index_(0) { + if (!blocks_in_loop_.IsBitSet(blocks_.Get(index_)->GetBlockId())) { + Advance(); + } + } + + bool Done() const { return index_ == blocks_.Size(); } + HBasicBlock* Current() const { return blocks_.Get(index_); } + void Advance() { + ++index_; + for (size_t e = blocks_.Size(); index_ < e; ++index_) { + if (blocks_in_loop_.IsBitSet(blocks_.Get(index_)->GetBlockId())) { + break; + } + } + } + + private: + const BitVector& blocks_in_loop_; + const GrowableArray<HBasicBlock*>& blocks_; + size_t index_; + + DISALLOW_COPY_AND_ASSIGN(HBlocksInLoopReversePostOrderIterator); +}; + inline int64_t Int64FromConstant(HConstant* constant) { DCHECK(constant->IsIntConstant() || constant->IsLongConstant()); return constant->IsIntConstant() ? constant->AsIntConstant()->GetValue() diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc index f6ef2f7e82..bf0b9fac0f 100644 --- a/compiler/optimizing/optimizing_compiler.cc +++ b/compiler/optimizing/optimizing_compiler.cc @@ -326,7 +326,7 @@ static void RunOptimizations(HGraph* graph, InstructionSimplifier simplify1(graph, stats); HBooleanSimplifier boolean_simplify(graph); - HInliner inliner(graph, dex_compilation_unit, dex_compilation_unit, driver, stats); + HInliner inliner(graph, dex_compilation_unit, dex_compilation_unit, driver, handles, stats); HConstantFolding fold2(graph, "constant_folding_after_inlining"); SideEffectsAnalysis side_effects(graph); @@ -335,6 +335,8 @@ static void RunOptimizations(HGraph* graph, BoundsCheckElimination bce(graph); ReferenceTypePropagation type_propagation(graph, handles); InstructionSimplifier simplify2(graph, stats, "instruction_simplifier_after_types"); + InstructionSimplifier simplify3(graph, stats, "last_instruction_simplifier"); + ReferenceTypePropagation type_propagation2(graph, handles); IntrinsicsRecognizer intrinsics(graph, driver); @@ -343,7 +345,12 @@ static void RunOptimizations(HGraph* graph, &dce1, &fold1, &simplify1, + &type_propagation, + &simplify2, &inliner, + // Run another type propagation phase: inlining will open up more opprotunities + // to remove checkast/instanceof and null checks. + &type_propagation2, // BooleanSimplifier depends on the InstructionSimplifier removing redundant // suspend checks to recognize empty blocks. &boolean_simplify, @@ -352,8 +359,7 @@ static void RunOptimizations(HGraph* graph, &gvn, &licm, &bce, - &type_propagation, - &simplify2, + &simplify3, &dce2, }; diff --git a/compiler/optimizing/reference_type_propagation.cc b/compiler/optimizing/reference_type_propagation.cc index 4f1f45769d..cd907361d7 100644 --- a/compiler/optimizing/reference_type_propagation.cc +++ b/compiler/optimizing/reference_type_propagation.cc @@ -27,7 +27,7 @@ void ReferenceTypePropagation::Run() { // To properly propagate type info we need to visit in the dominator-based order. // Reverse post order guarantees a node's dominators are visited first. // We take advantage of this order in `VisitBasicBlock`. - for (HReversePostOrderIterator it(*graph_); !it.Done(); it.Advance()) { + for (HReversePostOrderIterator it(*GetGraph()); !it.Done(); it.Advance()) { VisitBasicBlock(it.Current()); } ProcessWorklist(); @@ -35,23 +35,12 @@ void ReferenceTypePropagation::Run() { void ReferenceTypePropagation::VisitBasicBlock(HBasicBlock* block) { // TODO: handle other instructions that give type info - // (Call/array accesses) + // (array accesses) // Initialize exact types first for faster convergence. for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { HInstruction* instr = it.Current(); - // TODO: Make ReferenceTypePropagation a visitor or create a new one. - if (instr->IsNewInstance()) { - VisitNewInstance(instr->AsNewInstance()); - } else if (instr->IsLoadClass()) { - VisitLoadClass(instr->AsLoadClass()); - } else if (instr->IsNewArray()) { - VisitNewArray(instr->AsNewArray()); - } else if (instr->IsInstanceFieldGet()) { - VisitInstanceFieldGet(instr->AsInstanceFieldGet()); - } else if (instr->IsStaticFieldGet()) { - VisitStaticFieldGet(instr->AsStaticFieldGet()); - } + instr->Accept(this); } // Handle Phis. @@ -96,7 +85,7 @@ void ReferenceTypePropagation::BoundTypeForIfNotNull(HBasicBlock* block) { HInstruction* user = it.Current()->GetUser(); if (notNullBlock->Dominates(user->GetBlock())) { if (bound_type == nullptr) { - bound_type = new (graph_->GetArena()) HBoundType(obj, ReferenceTypeInfo::CreateTop(false)); + bound_type = new (GetGraph()->GetArena()) HBoundType(obj, ReferenceTypeInfo::CreateTop(false)); notNullBlock->InsertInstructionBefore(bound_type, notNullBlock->GetFirstInstruction()); } user->ReplaceInput(bound_type, it.Current()->GetIndex()); @@ -145,7 +134,7 @@ void ReferenceTypePropagation::BoundTypeForIfInstanceOf(HBasicBlock* block) { ReferenceTypeInfo obj_rti = obj->GetReferenceTypeInfo(); ReferenceTypeInfo class_rti = load_class->GetLoadedClassRTI(); - bound_type = new (graph_->GetArena()) HBoundType(obj, class_rti); + bound_type = new (GetGraph()->GetArena()) HBoundType(obj, class_rti); // Narrow the type as much as possible. { @@ -166,31 +155,35 @@ void ReferenceTypePropagation::BoundTypeForIfInstanceOf(HBasicBlock* block) { } } -void ReferenceTypePropagation::SetClassAsTypeInfo(HInstruction* instr, mirror::Class* klass) { +void ReferenceTypePropagation::SetClassAsTypeInfo(HInstruction* instr, + mirror::Class* klass, + bool is_exact) { if (klass != nullptr) { ScopedObjectAccess soa(Thread::Current()); MutableHandle<mirror::Class> handle = handles_->NewHandle(klass); - instr->SetReferenceTypeInfo(ReferenceTypeInfo::Create(handle, true)); + is_exact = is_exact || klass->IsFinal(); + instr->SetReferenceTypeInfo(ReferenceTypeInfo::Create(handle, is_exact)); } } void ReferenceTypePropagation::UpdateReferenceTypeInfo(HInstruction* instr, uint16_t type_idx, - const DexFile& dex_file) { + const DexFile& dex_file, + bool is_exact) { DCHECK_EQ(instr->GetType(), Primitive::kPrimNot); ScopedObjectAccess soa(Thread::Current()); mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache(dex_file); // Get type from dex cache assuming it was populated by the verifier. - SetClassAsTypeInfo(instr, dex_cache->GetResolvedType(type_idx)); + SetClassAsTypeInfo(instr, dex_cache->GetResolvedType(type_idx), is_exact); } void ReferenceTypePropagation::VisitNewInstance(HNewInstance* instr) { - UpdateReferenceTypeInfo(instr, instr->GetTypeIndex(), instr->GetDexFile()); + UpdateReferenceTypeInfo(instr, instr->GetTypeIndex(), instr->GetDexFile(), /* is_exact */ true); } void ReferenceTypePropagation::VisitNewArray(HNewArray* instr) { - UpdateReferenceTypeInfo(instr, instr->GetTypeIndex(), instr->GetDexFile()); + UpdateReferenceTypeInfo(instr, instr->GetTypeIndex(), instr->GetDexFile(), /* is_exact */ true); } void ReferenceTypePropagation::UpdateFieldAccessTypeInfo(HInstruction* instr, @@ -206,7 +199,7 @@ void ReferenceTypePropagation::UpdateFieldAccessTypeInfo(HInstruction* instr, ArtField* field = cl->GetResolvedField(info.GetFieldIndex(), dex_cache); DCHECK(field != nullptr); mirror::Class* klass = field->GetType<false>(); - SetClassAsTypeInfo(instr, klass); + SetClassAsTypeInfo(instr, klass, /* is_exact */ false); } void ReferenceTypePropagation::VisitInstanceFieldGet(HInstanceFieldGet* instr) { @@ -295,6 +288,21 @@ bool ReferenceTypePropagation::UpdateReferenceTypeInfo(HInstruction* instr) { return !previous_rti.IsEqual(instr->GetReferenceTypeInfo()); } +void ReferenceTypePropagation::VisitInvoke(HInvoke* instr) { + if (instr->GetType() != Primitive::kPrimNot) { + return; + } + + ScopedObjectAccess soa(Thread::Current()); + ClassLinker* cl = Runtime::Current()->GetClassLinker(); + mirror::DexCache* dex_cache = cl->FindDexCache(instr->GetDexFile()); + ArtMethod* method = dex_cache->GetResolvedMethod( + instr->GetDexMethodIndex(), cl->GetImagePointerSize()); + DCHECK(method != nullptr); + mirror::Class* klass = method->GetReturnType(false); + SetClassAsTypeInfo(instr, klass, /* is_exact */ false); +} + void ReferenceTypePropagation::UpdateBoundType(HBoundType* instr) { ReferenceTypeInfo new_rti = instr->InputAt(0)->GetReferenceTypeInfo(); // Be sure that we don't go over the bounded type. diff --git a/compiler/optimizing/reference_type_propagation.h b/compiler/optimizing/reference_type_propagation.h index 74e425fb3e..5902770d3f 100644 --- a/compiler/optimizing/reference_type_propagation.h +++ b/compiler/optimizing/reference_type_propagation.h @@ -28,10 +28,11 @@ namespace art { /** * Propagates reference types to instructions. */ -class ReferenceTypePropagation : public HOptimization { +class ReferenceTypePropagation : public HOptimization, public HGraphDelegateVisitor { public: ReferenceTypePropagation(HGraph* graph, StackHandleScopeCollection* handles) : HOptimization(graph, true, kReferenceTypePropagationPassName), + HGraphDelegateVisitor(graph), handles_(handles), worklist_(graph->GetArena(), kDefaultWorklistSize) {} @@ -46,16 +47,20 @@ class ReferenceTypePropagation : public HOptimization { void VisitPhi(HPhi* phi); void VisitBasicBlock(HBasicBlock* block); void UpdateFieldAccessTypeInfo(HInstruction* instr, const FieldInfo& info); - void SetClassAsTypeInfo(HInstruction* instr, mirror::Class* klass); + void SetClassAsTypeInfo(HInstruction* instr, mirror::Class* klass, bool is_exact); void UpdateBoundType(HBoundType* bound_type) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); void UpdatePhi(HPhi* phi) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); void BoundTypeForIfNotNull(HBasicBlock* block); void BoundTypeForIfInstanceOf(HBasicBlock* block); - void UpdateReferenceTypeInfo(HInstruction* instr, uint16_t type_idx, const DexFile& dex_file); + void UpdateReferenceTypeInfo(HInstruction* instr, + uint16_t type_idx, + const DexFile& dex_file, + bool is_exact); void VisitInstanceFieldGet(HInstanceFieldGet* instr); void VisitStaticFieldGet(HStaticFieldGet* instr); + void VisitInvoke(HInvoke* instr); void ProcessWorklist(); void AddToWorklist(HInstruction* instr); diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index 96d5654d65..9e9dea64c6 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -1610,6 +1610,8 @@ class ImageDumper { const auto& bitmap_section = image_header_.GetImageSection(ImageHeader::kSectionImageBitmap); const auto& field_section = image_header_.GetImageSection(ImageHeader::kSectionArtFields); const auto& method_section = image_header_.GetMethodsSection(); + const auto& intern_section = image_header_.GetImageSection( + ImageHeader::kSectionInternedStrings); stats_.header_bytes = header_bytes; size_t alignment_bytes = RoundUp(header_bytes, kObjectAlignment) - header_bytes; stats_.alignment_bytes += alignment_bytes; @@ -1617,6 +1619,7 @@ class ImageDumper { stats_.bitmap_bytes += bitmap_section.Size(); stats_.art_field_bytes += field_section.Size(); stats_.art_method_bytes += method_section.Size(); + stats_.interned_strings_bytes += intern_section.Size(); stats_.Dump(os); os << "\n"; @@ -1945,6 +1948,7 @@ class ImageDumper { size_t object_bytes; size_t art_field_bytes; size_t art_method_bytes; + size_t interned_strings_bytes; size_t bitmap_bytes; size_t alignment_bytes; @@ -1974,6 +1978,7 @@ class ImageDumper { object_bytes(0), art_field_bytes(0), art_method_bytes(0), + interned_strings_bytes(0), bitmap_bytes(0), alignment_bytes(0), managed_code_bytes(0), @@ -2131,21 +2136,24 @@ class ImageDumper { << "art_file_bytes = header_bytes + object_bytes + alignment_bytes\n"; Indenter indent_filter(os.rdbuf(), kIndentChar, kIndentBy1Count); std::ostream indent_os(&indent_filter); - indent_os << StringPrintf("header_bytes = %8zd (%2.0f%% of art file bytes)\n" - "object_bytes = %8zd (%2.0f%% of art file bytes)\n" - "art_field_bytes = %8zd (%2.0f%% of art file bytes)\n" - "art_method_bytes = %8zd (%2.0f%% of art file bytes)\n" - "bitmap_bytes = %8zd (%2.0f%% of art file bytes)\n" - "alignment_bytes = %8zd (%2.0f%% of art file bytes)\n\n", + indent_os << StringPrintf("header_bytes = %8zd (%2.0f%% of art file bytes)\n" + "object_bytes = %8zd (%2.0f%% of art file bytes)\n" + "art_field_bytes = %8zd (%2.0f%% of art file bytes)\n" + "art_method_bytes = %8zd (%2.0f%% of art file bytes)\n" + "interned_string_bytes = %8zd (%2.0f%% of art file bytes)\n" + "bitmap_bytes = %8zd (%2.0f%% of art file bytes)\n" + "alignment_bytes = %8zd (%2.0f%% of art file bytes)\n\n", header_bytes, PercentOfFileBytes(header_bytes), object_bytes, PercentOfFileBytes(object_bytes), art_field_bytes, PercentOfFileBytes(art_field_bytes), art_method_bytes, PercentOfFileBytes(art_method_bytes), + interned_strings_bytes, + PercentOfFileBytes(interned_strings_bytes), bitmap_bytes, PercentOfFileBytes(bitmap_bytes), alignment_bytes, PercentOfFileBytes(alignment_bytes)) << std::flush; CHECK_EQ(file_bytes, header_bytes + object_bytes + art_field_bytes + art_method_bytes + - bitmap_bytes + alignment_bytes); + interned_strings_bytes + bitmap_bytes + alignment_bytes); } os << "object_bytes breakdown:\n"; diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc index 007125cfbe..04017273a8 100644 --- a/patchoat/patchoat.cc +++ b/patchoat/patchoat.cc @@ -437,6 +437,41 @@ void PatchOat::PatchArtMethods(const ImageHeader* image_header) { } } +class FixupRootVisitor : public RootVisitor { + public: + explicit FixupRootVisitor(const PatchOat* patch_oat) : patch_oat_(patch_oat) { + } + + void VisitRoots(mirror::Object*** roots, size_t count, const RootInfo& info ATTRIBUTE_UNUSED) + OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + for (size_t i = 0; i < count; ++i) { + *roots[i] = patch_oat_->RelocatedAddressOfPointer(*roots[i]); + } + } + + void VisitRoots(mirror::CompressedReference<mirror::Object>** roots, size_t count, + const RootInfo& info ATTRIBUTE_UNUSED) + OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + for (size_t i = 0; i < count; ++i) { + roots[i]->Assign(patch_oat_->RelocatedAddressOfPointer(roots[i]->AsMirrorPtr())); + } + } + + private: + const PatchOat* const patch_oat_; +}; + +void PatchOat::PatchInternedStrings(const ImageHeader* image_header) { + const auto& section = image_header->GetImageSection(ImageHeader::kSectionInternedStrings); + InternTable temp_table; + // Note that we require that ReadFromMemory does not make an internal copy of the elements. + // This also relies on visit roots not doing any verification which could fail after we update + // the roots to be the image addresses. + temp_table.ReadFromMemory(image_->Begin() + section.Offset()); + FixupRootVisitor visitor(this); + temp_table.VisitRoots(&visitor, kVisitRootFlagAllRoots); +} + void PatchOat::PatchDexFileArrays(mirror::ObjectArray<mirror::Object>* img_roots) { auto* dex_caches = down_cast<mirror::ObjectArray<mirror::DexCache>*>( img_roots->Get(ImageHeader::kDexCaches)); @@ -483,12 +518,9 @@ bool PatchOat::PatchImage() { auto* img_roots = image_header->GetImageRoots(); image_header->RelocateImage(delta_); - // Patch and update ArtFields. PatchArtFields(image_header); - - // Patch and update ArtMethods. PatchArtMethods(image_header); - + PatchInternedStrings(image_header); // Patch dex file int/long arrays which point to ArtFields. PatchDexFileArrays(img_roots); diff --git a/patchoat/patchoat.h b/patchoat/patchoat.h index 7b9c8bd508..23abca8c7e 100644 --- a/patchoat/patchoat.h +++ b/patchoat/patchoat.h @@ -116,6 +116,8 @@ class PatchOat { bool PatchImage() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); void PatchArtFields(const ImageHeader* image_header) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); void PatchArtMethods(const ImageHeader* image_header) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + void PatchInternedStrings(const ImageHeader* image_header) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); void PatchDexFileArrays(mirror::ObjectArray<mirror::Object>* img_roots) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); @@ -123,7 +125,7 @@ class PatchOat { bool WriteImage(File* out); template <typename T> - T* RelocatedCopyOf(T* obj) { + T* RelocatedCopyOf(T* obj) const { if (obj == nullptr) { return nullptr; } @@ -136,7 +138,7 @@ class PatchOat { } template <typename T> - T* RelocatedAddressOfPointer(T* obj) { + T* RelocatedAddressOfPointer(T* obj) const { if (obj == nullptr) { return obj; } @@ -149,7 +151,7 @@ class PatchOat { } template <typename T> - T RelocatedAddressOfIntPointer(T obj) { + T RelocatedAddressOfIntPointer(T obj) const { if (obj == 0) { return obj; } @@ -199,6 +201,7 @@ class PatchOat { TimingLogger* timings_; + friend class FixupRootVisitor; DISALLOW_IMPLICIT_CONSTRUCTORS(PatchOat); }; diff --git a/runtime/Android.mk b/runtime/Android.mk index c1e6e09728..5ed6955185 100644 --- a/runtime/Android.mk +++ b/runtime/Android.mk @@ -45,6 +45,7 @@ LIBART_COMMON_SRC_FILES := \ dex_file_verifier.cc \ dex_instruction.cc \ elf_file.cc \ + gc/allocation_record.cc \ gc/allocator/dlmalloc.cc \ gc/allocator/rosalloc.cc \ gc/accounting/bitmap.cc \ diff --git a/runtime/arch/mips/quick_entrypoints_mips.S b/runtime/arch/mips/quick_entrypoints_mips.S index 3a0ea646e1..cc1de43723 100644 --- a/runtime/arch/mips/quick_entrypoints_mips.S +++ b/runtime/arch/mips/quick_entrypoints_mips.S @@ -380,7 +380,7 @@ END art_quick_do_long_jump /* * Called by managed code, saves most registers (forms basis of long jump context) and passes * the bottom of the stack. artDeliverExceptionFromCode will place the callee save Method* at - * the bottom of the thread. On entry r0 holds Throwable* + * the bottom of the thread. On entry a0 holds Throwable* */ ENTRY art_quick_deliver_exception SETUP_SAVE_ALL_CALLEE_SAVE_FRAME diff --git a/runtime/arch/mips64/quick_entrypoints_mips64.S b/runtime/arch/mips64/quick_entrypoints_mips64.S index b2cd7f26c7..37c6c5b3f9 100644 --- a/runtime/arch/mips64/quick_entrypoints_mips64.S +++ b/runtime/arch/mips64/quick_entrypoints_mips64.S @@ -87,11 +87,11 @@ s.d $f24, 8($sp) # load appropriate callee-save-method - ld $v0, %got(_ZN3art7Runtime9instance_E)($gp) - ld $v0, 0($v0) + ld $t1, %got(_ZN3art7Runtime9instance_E)($gp) + ld $t1, 0($t1) THIS_LOAD_REQUIRES_READ_BARRIER - ld $v0, RUNTIME_SAVE_ALL_CALLEE_SAVE_FRAME_OFFSET($v0) - sd $v0, 0($sp) # Place ArtMethod* at bottom of stack. + ld $t1, RUNTIME_SAVE_ALL_CALLEE_SAVE_FRAME_OFFSET($t1) + sd $t1, 0($sp) # Place ArtMethod* at bottom of stack. sd $sp, THREAD_TOP_QUICK_FRAME_OFFSET(rSELF) # Place sp in Thread::Current()->top_quick_frame. .endm @@ -130,11 +130,11 @@ sd $s2, 8($sp) .cfi_rel_offset 18, 8 # load appropriate callee-save-method - ld $v0, %got(_ZN3art7Runtime9instance_E)($gp) - ld $v0, 0($v0) + ld $t1, %got(_ZN3art7Runtime9instance_E)($gp) + ld $t1, 0($t1) THIS_LOAD_REQUIRES_READ_BARRIER - ld $v0, RUNTIME_REFS_ONLY_CALLEE_SAVE_FRAME_OFFSET($v0) - sd $v0, 0($sp) # Place Method* at bottom of stack. + ld $t1, RUNTIME_REFS_ONLY_CALLEE_SAVE_FRAME_OFFSET($t1) + sd $t1, 0($sp) # Place Method* at bottom of stack. sd $sp, THREAD_TOP_QUICK_FRAME_OFFSET(rSELF) # Place sp in Thread::Current()->top_quick_frame. .endm @@ -253,11 +253,11 @@ .macro SETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME SETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME_INTERNAL # load appropriate callee-save-method - ld $v0, %got(_ZN3art7Runtime9instance_E)($gp) - ld $v0, 0($v0) + ld $t1, %got(_ZN3art7Runtime9instance_E)($gp) + ld $t1, 0($t1) THIS_LOAD_REQUIRES_READ_BARRIER - ld $v0, RUNTIME_REFS_AND_ARGS_CALLEE_SAVE_FRAME_OFFSET($v0) - sd $v0, 0($sp) # Place Method* at bottom of stack. + ld $t1, RUNTIME_REFS_AND_ARGS_CALLEE_SAVE_FRAME_OFFSET($t1) + sd $t1, 0($sp) # Place Method* at bottom of stack. sd $sp, THREAD_TOP_QUICK_FRAME_OFFSET(rSELF) # Place sp in Thread::Current()->top_quick_frame. .endm @@ -442,7 +442,7 @@ END art_quick_do_long_jump * Called by managed code, saves most registers (forms basis of long jump * context) and passes the bottom of the stack. * artDeliverExceptionFromCode will place the callee save Method* at - * the bottom of the thread. On entry v0 holds Throwable* + * the bottom of the thread. On entry a0 holds Throwable* */ ENTRY art_quick_deliver_exception SETUP_SAVE_ALL_CALLEE_SAVE_FRAME diff --git a/runtime/base/hash_set.h b/runtime/base/hash_set.h index ab63dddaff..8daf6d4c9e 100644 --- a/runtime/base/hash_set.h +++ b/runtime/base/hash_set.h @@ -22,6 +22,7 @@ #include <stdint.h> #include <utility> +#include "bit_utils.h" #include "logging.h" namespace art { @@ -121,6 +122,7 @@ class HashSet { typedef BaseIterator<T, HashSet> Iterator; typedef BaseIterator<const T, const HashSet> ConstIterator; + // If we don't own the data, this will create a new array which owns the data. void Clear() { DeallocateStorage(); AllocateStorage(1); @@ -128,19 +130,70 @@ class HashSet { elements_until_expand_ = 0; } - HashSet() : num_elements_(0), num_buckets_(0), data_(nullptr), + HashSet() : num_elements_(0), num_buckets_(0), owns_data_(false), data_(nullptr), min_load_factor_(kDefaultMinLoadFactor), max_load_factor_(kDefaultMaxLoadFactor) { Clear(); } - HashSet(const HashSet& other) : num_elements_(0), num_buckets_(0), data_(nullptr) { + HashSet(const HashSet& other) : num_elements_(0), num_buckets_(0), owns_data_(false), + data_(nullptr) { *this = other; } - HashSet(HashSet&& other) : num_elements_(0), num_buckets_(0), data_(nullptr) { + HashSet(HashSet&& other) : num_elements_(0), num_buckets_(0), owns_data_(false), + data_(nullptr) { *this = std::move(other); } + // Construct from existing data. + // Read from a block of memory, if make_copy_of_data is false, then data_ points to within the + // passed in ptr_. + HashSet(const uint8_t* ptr, bool make_copy_of_data, size_t* read_count) { + uint64_t temp; + size_t offset = 0; + offset = ReadFromBytes(ptr, offset, &temp); + num_elements_ = static_cast<uint64_t>(temp); + offset = ReadFromBytes(ptr, offset, &temp); + num_buckets_ = static_cast<uint64_t>(temp); + CHECK_LE(num_elements_, num_buckets_); + offset = ReadFromBytes(ptr, offset, &temp); + elements_until_expand_ = static_cast<uint64_t>(temp); + offset = ReadFromBytes(ptr, offset, &min_load_factor_); + offset = ReadFromBytes(ptr, offset, &max_load_factor_); + if (!make_copy_of_data) { + owns_data_ = false; + data_ = const_cast<T*>(reinterpret_cast<const T*>(ptr + offset)); + offset += sizeof(*data_) * num_buckets_; + } else { + AllocateStorage(num_buckets_); + // Write elements, not that this may not be safe for cross compilation if the elements are + // pointer sized. + for (size_t i = 0; i < num_buckets_; ++i) { + offset = ReadFromBytes(ptr, offset, &data_[i]); + } + } + // Caller responsible for aligning. + *read_count = offset; + } + + // Returns how large the table is after being written. If target is null, then no writing happens + // but the size is still returned. Target must be 8 byte aligned. + size_t WriteToMemory(uint8_t* ptr) { + size_t offset = 0; + offset = WriteToBytes(ptr, offset, static_cast<uint64_t>(num_elements_)); + offset = WriteToBytes(ptr, offset, static_cast<uint64_t>(num_buckets_)); + offset = WriteToBytes(ptr, offset, static_cast<uint64_t>(elements_until_expand_)); + offset = WriteToBytes(ptr, offset, min_load_factor_); + offset = WriteToBytes(ptr, offset, max_load_factor_); + // Write elements, not that this may not be safe for cross compilation if the elements are + // pointer sized. + for (size_t i = 0; i < num_buckets_; ++i) { + offset = WriteToBytes(ptr, offset, data_[i]); + } + // Caller responsible for aligning. + return offset; + } + ~HashSet() { DeallocateStorage(); } @@ -152,6 +205,7 @@ class HashSet { std::swap(elements_until_expand_, other.elements_until_expand_); std::swap(min_load_factor_, other.min_load_factor_); std::swap(max_load_factor_, other.max_load_factor_); + std::swap(owns_data_, other.owns_data_); return *this; } @@ -386,6 +440,7 @@ class HashSet { void AllocateStorage(size_t num_buckets) { num_buckets_ = num_buckets; data_ = allocfn_.allocate(num_buckets_); + owns_data_ = true; for (size_t i = 0; i < num_buckets_; ++i) { allocfn_.construct(allocfn_.address(data_[i])); emptyfn_.MakeEmpty(data_[i]); @@ -394,10 +449,13 @@ class HashSet { void DeallocateStorage() { if (num_buckets_ != 0) { - for (size_t i = 0; i < NumBuckets(); ++i) { - allocfn_.destroy(allocfn_.address(data_[i])); + if (owns_data_) { + for (size_t i = 0; i < NumBuckets(); ++i) { + allocfn_.destroy(allocfn_.address(data_[i])); + } + allocfn_.deallocate(data_, NumBuckets()); + owns_data_ = false; } - allocfn_.deallocate(data_, NumBuckets()); data_ = nullptr; num_buckets_ = 0; } @@ -418,18 +476,23 @@ class HashSet { // Expand / shrink the table to the new specified size. void Resize(size_t new_size) { DCHECK_GE(new_size, Size()); - T* old_data = data_; + T* const old_data = data_; size_t old_num_buckets = num_buckets_; // Reinsert all of the old elements. + const bool owned_data = owns_data_; AllocateStorage(new_size); for (size_t i = 0; i < old_num_buckets; ++i) { T& element = old_data[i]; if (!emptyfn_.IsEmpty(element)) { data_[FirstAvailableSlot(IndexForHash(hashfn_(element)))] = std::move(element); } - allocfn_.destroy(allocfn_.address(element)); + if (owned_data) { + allocfn_.destroy(allocfn_.address(element)); + } + } + if (owned_data) { + allocfn_.deallocate(old_data, old_num_buckets); } - allocfn_.deallocate(old_data, old_num_buckets); } ALWAYS_INLINE size_t FirstAvailableSlot(size_t index) const { @@ -439,6 +502,24 @@ class HashSet { return index; } + // Return new offset. + template <typename Elem> + static size_t WriteToBytes(uint8_t* ptr, size_t offset, Elem n) { + DCHECK_ALIGNED(ptr + offset, sizeof(n)); + if (ptr != nullptr) { + *reinterpret_cast<Elem*>(ptr + offset) = n; + } + return offset + sizeof(n); + } + + template <typename Elem> + static size_t ReadFromBytes(const uint8_t* ptr, size_t offset, Elem* out) { + DCHECK(ptr != nullptr); + DCHECK_ALIGNED(ptr + offset, sizeof(*out)); + *out = *reinterpret_cast<const Elem*>(ptr + offset); + return offset + sizeof(*out); + } + Alloc allocfn_; // Allocator function. HashFn hashfn_; // Hashing function. EmptyFn emptyfn_; // IsEmpty/SetEmpty function. @@ -446,6 +527,7 @@ class HashSet { size_t num_elements_; // Number of inserted elements. size_t num_buckets_; // Number of hash table buckets. size_t elements_until_expand_; // Maxmimum number of elements until we expand the table. + bool owns_data_; // If we own data_ and are responsible for freeing it. T* data_; // Backing storage. double min_load_factor_; double max_load_factor_; diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 429fa5bfe0..dc8a3d158e 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -1055,7 +1055,7 @@ static void SanityCheckArtMethodPointerArray( static void SanityCheckObjectsCallback(mirror::Object* obj, void* arg ATTRIBUTE_UNUSED) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { DCHECK(obj != nullptr); - CHECK(obj->GetClass() != nullptr) << "Null class " << obj; + CHECK(obj->GetClass() != nullptr) << "Null class in object " << obj; CHECK(obj->GetClass()->GetClass() != nullptr) << "Null class class " << obj; if (obj->IsClass()) { auto klass = obj->AsClass(); diff --git a/runtime/debugger.cc b/runtime/debugger.cc index 3c6f98af16..b2e40b4e92 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -29,6 +29,7 @@ #include "dex_file-inl.h" #include "dex_instruction.h" #include "gc/accounting/card_table-inl.h" +#include "gc/allocation_record.h" #include "gc/space/large_object_space.h" #include "gc/space/space-inl.h" #include "handle_scope.h" @@ -62,127 +63,30 @@ namespace art { // The key identifying the debugger to update instrumentation. static constexpr const char* kDbgInstrumentationKey = "Debugger"; -static const size_t kMaxAllocRecordStackDepth = 16; // Max 255. -static const size_t kDefaultNumAllocRecords = 64*1024; // Must be a power of 2. 2BE can hold 64k-1. - -// Limit alloc_record_count to the 2BE value that is the limit of the current protocol. +// Limit alloc_record_count to the 2BE value (64k-1) that is the limit of the current protocol. static uint16_t CappedAllocRecordCount(size_t alloc_record_count) { - if (alloc_record_count > 0xffff) { - return 0xffff; - } - return alloc_record_count; -} - -class AllocRecordStackTraceElement { - public: - AllocRecordStackTraceElement() : method_(nullptr), dex_pc_(0) { - } - - int32_t LineNumber() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - ArtMethod* method = Method(); - DCHECK(method != nullptr); - return method->GetLineNumFromDexPC(DexPc()); - } - - ArtMethod* Method() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - ScopedObjectAccessUnchecked soa(Thread::Current()); - return soa.DecodeMethod(method_); - } - - void SetMethod(ArtMethod* m) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - ScopedObjectAccessUnchecked soa(Thread::Current()); - method_ = soa.EncodeMethod(m); - } - - uint32_t DexPc() const { - return dex_pc_; - } - - void SetDexPc(uint32_t pc) { - dex_pc_ = pc; - } - - private: - jmethodID method_; - uint32_t dex_pc_; -}; - -jobject Dbg::TypeCache::Add(mirror::Class* t) { - ScopedObjectAccessUnchecked soa(Thread::Current()); - JNIEnv* const env = soa.Env(); - ScopedLocalRef<jobject> local_ref(soa.Env(), soa.AddLocalReference<jobject>(t)); - const int32_t hash_code = soa.Decode<mirror::Class*>(local_ref.get())->IdentityHashCode(); - auto range = objects_.equal_range(hash_code); - for (auto it = range.first; it != range.second; ++it) { - if (soa.Decode<mirror::Class*>(it->second) == soa.Decode<mirror::Class*>(local_ref.get())) { - // Found a matching weak global, return it. - return it->second; + size_t cap = 0xffff; +#ifdef HAVE_ANDROID_OS + // Check whether there's a system property overriding the number of recent records. + const char* propertyName = "dalvik.vm.recentAllocMax"; + char recentAllocMaxString[PROPERTY_VALUE_MAX]; + if (property_get(propertyName, recentAllocMaxString, "") > 0) { + char* end; + size_t value = strtoul(recentAllocMaxString, &end, 10); + if (*end != '\0') { + LOG(ERROR) << "Ignoring " << propertyName << " '" << recentAllocMaxString + << "' --- invalid"; + } else { + cap = value; } } - const jobject weak_global = env->NewWeakGlobalRef(local_ref.get()); - objects_.insert(std::make_pair(hash_code, weak_global)); - return weak_global; -} - -void Dbg::TypeCache::Clear() { - JavaVMExt* vm = Runtime::Current()->GetJavaVM(); - Thread* self = Thread::Current(); - for (const auto& p : objects_) { - vm->DeleteWeakGlobalRef(self, p.second); +#endif + if (alloc_record_count > cap) { + return cap; } - objects_.clear(); + return alloc_record_count; } -class AllocRecord { - public: - AllocRecord() : type_(nullptr), byte_count_(0), thin_lock_id_(0) {} - - mirror::Class* Type() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - return down_cast<mirror::Class*>(Thread::Current()->DecodeJObject(type_)); - } - - void SetType(mirror::Class* t) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_, - Locks::alloc_tracker_lock_) { - type_ = Dbg::type_cache_.Add(t); - } - - size_t GetDepth() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - size_t depth = 0; - while (depth < kMaxAllocRecordStackDepth && stack_[depth].Method() != nullptr) { - ++depth; - } - return depth; - } - - size_t ByteCount() const { - return byte_count_; - } - - void SetByteCount(size_t count) { - byte_count_ = count; - } - - uint16_t ThinLockId() const { - return thin_lock_id_; - } - - void SetThinLockId(uint16_t id) { - thin_lock_id_ = id; - } - - AllocRecordStackTraceElement* StackElement(size_t index) { - DCHECK_LT(index, kMaxAllocRecordStackDepth); - return &stack_[index]; - } - - private: - jobject type_; // This is a weak global. - size_t byte_count_; - uint16_t thin_lock_id_; - // Unused entries have null method. - AllocRecordStackTraceElement stack_[kMaxAllocRecordStackDepth]; -}; - class Breakpoint { public: Breakpoint(ArtMethod* method, uint32_t dex_pc, @@ -383,13 +287,6 @@ bool Dbg::gDebuggerActive = false; bool Dbg::gDisposed = false; ObjectRegistry* Dbg::gRegistry = nullptr; -// Recent allocation tracking. -AllocRecord* Dbg::recent_allocation_records_ = nullptr; // TODO: CircularBuffer<AllocRecord> -size_t Dbg::alloc_record_max_ = 0; -size_t Dbg::alloc_record_head_ = 0; -size_t Dbg::alloc_record_count_ = 0; -Dbg::TypeCache Dbg::type_cache_; - // Deoptimization support. std::vector<DeoptimizationRequest> Dbg::deoptimization_requests_; size_t Dbg::full_deoptimization_event_count_ = 0; @@ -4742,177 +4639,41 @@ void Dbg::DdmSendHeapSegments(bool native) { Dbg::DdmSendChunk(native ? CHUNK_TYPE("NHEN") : CHUNK_TYPE("HPEN"), sizeof(heap_id), heap_id); } -static size_t GetAllocTrackerMax() { -#ifdef HAVE_ANDROID_OS - // Check whether there's a system property overriding the number of records. - const char* propertyName = "dalvik.vm.allocTrackerMax"; - char allocRecordMaxString[PROPERTY_VALUE_MAX]; - if (property_get(propertyName, allocRecordMaxString, "") > 0) { - char* end; - size_t value = strtoul(allocRecordMaxString, &end, 10); - if (*end != '\0') { - LOG(ERROR) << "Ignoring " << propertyName << " '" << allocRecordMaxString - << "' --- invalid"; - return kDefaultNumAllocRecords; - } - if (!IsPowerOfTwo(value)) { - LOG(ERROR) << "Ignoring " << propertyName << " '" << allocRecordMaxString - << "' --- not power of two"; - return kDefaultNumAllocRecords; - } - return value; - } -#endif - return kDefaultNumAllocRecords; -} - void Dbg::SetAllocTrackingEnabled(bool enable) { - Thread* self = Thread::Current(); - if (enable) { - { - MutexLock mu(self, *Locks::alloc_tracker_lock_); - if (recent_allocation_records_ != nullptr) { - return; // Already enabled, bail. - } - alloc_record_max_ = GetAllocTrackerMax(); - LOG(INFO) << "Enabling alloc tracker (" << alloc_record_max_ << " entries of " - << kMaxAllocRecordStackDepth << " frames, taking " - << PrettySize(sizeof(AllocRecord) * alloc_record_max_) << ")"; - DCHECK_EQ(alloc_record_head_, 0U); - DCHECK_EQ(alloc_record_count_, 0U); - recent_allocation_records_ = new AllocRecord[alloc_record_max_]; - CHECK(recent_allocation_records_ != nullptr); - } - Runtime::Current()->GetInstrumentation()->InstrumentQuickAllocEntryPoints(); - } else { - { - ScopedObjectAccess soa(self); // For type_cache_.Clear(); - MutexLock mu(self, *Locks::alloc_tracker_lock_); - if (recent_allocation_records_ == nullptr) { - return; // Already disabled, bail. - } - LOG(INFO) << "Disabling alloc tracker"; - delete[] recent_allocation_records_; - recent_allocation_records_ = nullptr; - alloc_record_head_ = 0; - alloc_record_count_ = 0; - type_cache_.Clear(); - } - // If an allocation comes in before we uninstrument, we will safely drop it on the floor. - Runtime::Current()->GetInstrumentation()->UninstrumentQuickAllocEntryPoints(); - } -} - -struct AllocRecordStackVisitor : public StackVisitor { - AllocRecordStackVisitor(Thread* thread, AllocRecord* record_in) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) - : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), - record(record_in), - depth(0) {} - - // TODO: Enable annotalysis. We know lock is held in constructor, but abstraction confuses - // annotalysis. - bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS { - if (depth >= kMaxAllocRecordStackDepth) { - return false; - } - ArtMethod* m = GetMethod(); - if (!m->IsRuntimeMethod()) { - record->StackElement(depth)->SetMethod(m); - record->StackElement(depth)->SetDexPc(GetDexPc()); - ++depth; - } - return true; - } - - ~AllocRecordStackVisitor() { - // Clear out any unused stack trace elements. - for (; depth < kMaxAllocRecordStackDepth; ++depth) { - record->StackElement(depth)->SetMethod(nullptr); - record->StackElement(depth)->SetDexPc(0); - } - } - - AllocRecord* record; - size_t depth; -}; - -void Dbg::RecordAllocation(Thread* self, mirror::Class* type, size_t byte_count) { - MutexLock mu(self, *Locks::alloc_tracker_lock_); - if (recent_allocation_records_ == nullptr) { - // In the process of shutting down recording, bail. - return; - } - - // Advance and clip. - if (++alloc_record_head_ == alloc_record_max_) { - alloc_record_head_ = 0; - } - - // Fill in the basics. - AllocRecord* record = &recent_allocation_records_[alloc_record_head_]; - record->SetType(type); - record->SetByteCount(byte_count); - record->SetThinLockId(self->GetThreadId()); - - // Fill in the stack trace. - AllocRecordStackVisitor visitor(self, record); - visitor.WalkStack(); - - if (alloc_record_count_ < alloc_record_max_) { - ++alloc_record_count_; - } -} - -// Returns the index of the head element. -// -// We point at the most-recently-written record, so if alloc_record_count_ is 1 -// we want to use the current element. Take "head+1" and subtract count -// from it. -// -// We need to handle underflow in our circular buffer, so we add -// alloc_record_max_ and then mask it back down. -size_t Dbg::HeadIndex() { - return (Dbg::alloc_record_head_ + 1 + Dbg::alloc_record_max_ - Dbg::alloc_record_count_) & - (Dbg::alloc_record_max_ - 1); + gc::AllocRecordObjectMap::SetAllocTrackingEnabled(enable); } void Dbg::DumpRecentAllocations() { ScopedObjectAccess soa(Thread::Current()); MutexLock mu(soa.Self(), *Locks::alloc_tracker_lock_); - if (recent_allocation_records_ == nullptr) { + if (!Runtime::Current()->GetHeap()->IsAllocTrackingEnabled()) { LOG(INFO) << "Not recording tracked allocations"; return; } + gc::AllocRecordObjectMap* records = Runtime::Current()->GetHeap()->GetAllocationRecords(); + CHECK(records != nullptr); - // "i" is the head of the list. We want to start at the end of the - // list and move forward to the tail. - size_t i = HeadIndex(); - const uint16_t capped_count = CappedAllocRecordCount(Dbg::alloc_record_count_); + const uint16_t capped_count = CappedAllocRecordCount(records->Size()); uint16_t count = capped_count; - LOG(INFO) << "Tracked allocations, (head=" << alloc_record_head_ << " count=" << count << ")"; - while (count--) { - AllocRecord* record = &recent_allocation_records_[i]; + LOG(INFO) << "Tracked allocations, (count=" << count << ")"; + for (auto it = records->RBegin(), end = records->REnd(); + count > 0 && it != end; count--, it++) { + const gc::AllocRecord* record = it->second; - LOG(INFO) << StringPrintf(" Thread %-2d %6zd bytes ", record->ThinLockId(), record->ByteCount()) - << PrettyClass(record->Type()); + LOG(INFO) << StringPrintf(" Thread %-2d %6zd bytes ", record->GetTid(), record->ByteCount()) + << PrettyClass(it->first.Read()->GetClass()); - for (size_t stack_frame = 0; stack_frame < kMaxAllocRecordStackDepth; ++stack_frame) { - AllocRecordStackTraceElement* stack_element = record->StackElement(stack_frame); - ArtMethod* m = stack_element->Method(); - if (m == nullptr) { - break; - } - LOG(INFO) << " " << PrettyMethod(m) << " line " << stack_element->LineNumber(); + for (size_t stack_frame = 0, depth = record->GetDepth(); stack_frame < depth; ++stack_frame) { + const gc::AllocRecordStackTraceElement& stack_element = record->StackElement(stack_frame); + ArtMethod* m = stack_element.GetMethod(); + LOG(INFO) << " " << PrettyMethod(m) << " line " << stack_element.ComputeLineNumber(); } // pause periodically to help logcat catch up if ((count % 5) == 0) { usleep(40000); } - - i = (i + 1) & (alloc_record_max_ - 1); } } @@ -5014,6 +4775,15 @@ jbyteArray Dbg::GetRecentAllocations() { std::vector<uint8_t> bytes; { MutexLock mu(self, *Locks::alloc_tracker_lock_); + gc::AllocRecordObjectMap* records = Runtime::Current()->GetHeap()->GetAllocationRecords(); + // In case this method is called when allocation tracker is disabled, + // we should still send some data back. + gc::AllocRecordObjectMap dummy; + if (records == nullptr) { + CHECK(!Runtime::Current()->GetHeap()->IsAllocTrackingEnabled()); + records = &dummy; + } + // // Part 1: generate string tables. // @@ -5021,26 +4791,23 @@ jbyteArray Dbg::GetRecentAllocations() { StringTable method_names; StringTable filenames; - const uint16_t capped_count = CappedAllocRecordCount(Dbg::alloc_record_count_); + const uint16_t capped_count = CappedAllocRecordCount(records->Size()); uint16_t count = capped_count; - size_t idx = HeadIndex(); - while (count--) { - AllocRecord* record = &recent_allocation_records_[idx]; + for (auto it = records->RBegin(), end = records->REnd(); + count > 0 && it != end; count--, it++) { + const gc::AllocRecord* record = it->second; std::string temp; - class_names.Add(record->Type()->GetDescriptor(&temp)); - for (size_t i = 0; i < kMaxAllocRecordStackDepth; i++) { - ArtMethod* m = record->StackElement(i)->Method(); - if (m != nullptr) { - class_names.Add(m->GetDeclaringClassDescriptor()); - method_names.Add(m->GetName()); - filenames.Add(GetMethodSourceFile(m)); - } + class_names.Add(it->first.Read()->GetClass()->GetDescriptor(&temp)); + for (size_t i = 0, depth = record->GetDepth(); i < depth; i++) { + ArtMethod* m = record->StackElement(i).GetMethod(); + class_names.Add(m->GetDeclaringClassDescriptor()); + method_names.Add(m->GetName()); + filenames.Add(GetMethodSourceFile(m)); } - - idx = (idx + 1) & (alloc_record_max_ - 1); } - LOG(INFO) << "allocation records: " << capped_count; + LOG(INFO) << "recent allocation records: " << capped_count; + LOG(INFO) << "allocation records all objects: " << records->Size(); // // Part 2: Generate the output and store it in the buffer. @@ -5068,20 +4835,23 @@ jbyteArray Dbg::GetRecentAllocations() { JDWP::Append2BE(bytes, method_names.Size()); JDWP::Append2BE(bytes, filenames.Size()); - idx = HeadIndex(); std::string temp; - for (count = capped_count; count != 0; --count) { + count = capped_count; + // The last "count" number of allocation records in "records" are the most recent "count" number + // of allocations. Reverse iterate to get them. The most recent allocation is sent first. + for (auto it = records->RBegin(), end = records->REnd(); + count > 0 && it != end; count--, it++) { // For each entry: // (4b) total allocation size // (2b) thread id // (2b) allocated object's class name index // (1b) stack depth - AllocRecord* record = &recent_allocation_records_[idx]; + const gc::AllocRecord* record = it->second; size_t stack_depth = record->GetDepth(); size_t allocated_object_class_name_index = - class_names.IndexOf(record->Type()->GetDescriptor(&temp)); + class_names.IndexOf(it->first.Read()->GetClass()->GetDescriptor(&temp)); JDWP::Append4BE(bytes, record->ByteCount()); - JDWP::Append2BE(bytes, record->ThinLockId()); + JDWP::Append2BE(bytes, static_cast<uint16_t>(record->GetTid())); JDWP::Append2BE(bytes, allocated_object_class_name_index); JDWP::Append1BE(bytes, stack_depth); @@ -5091,16 +4861,15 @@ jbyteArray Dbg::GetRecentAllocations() { // (2b) method name // (2b) method source file // (2b) line number, clipped to 32767; -2 if native; -1 if no source - ArtMethod* m = record->StackElement(stack_frame)->Method(); + ArtMethod* m = record->StackElement(stack_frame).GetMethod(); size_t class_name_index = class_names.IndexOf(m->GetDeclaringClassDescriptor()); size_t method_name_index = method_names.IndexOf(m->GetName()); size_t file_name_index = filenames.IndexOf(GetMethodSourceFile(m)); JDWP::Append2BE(bytes, class_name_index); JDWP::Append2BE(bytes, method_name_index); JDWP::Append2BE(bytes, file_name_index); - JDWP::Append2BE(bytes, record->StackElement(stack_frame)->LineNumber()); + JDWP::Append2BE(bytes, record->StackElement(stack_frame).ComputeLineNumber()); } - idx = (idx + 1) & (alloc_record_max_ - 1); } // (xb) class name strings diff --git a/runtime/debugger.h b/runtime/debugger.h index e40f10fdf9..fd7d46c37e 100644 --- a/runtime/debugger.h +++ b/runtime/debugger.h @@ -23,7 +23,6 @@ #include <pthread.h> -#include <map> #include <set> #include <string> #include <vector> @@ -32,7 +31,6 @@ #include "jdwp/jdwp.h" #include "jni.h" #include "jvalue.h" -#include "object_callbacks.h" #include "thread_state.h" namespace art { @@ -41,7 +39,6 @@ class Class; class Object; class Throwable; } // namespace mirror -class AllocRecord; class ArtField; class ArtMethod; class ObjectRegistry; @@ -202,19 +199,6 @@ std::ostream& operator<<(std::ostream& os, const DeoptimizationRequest::Kind& rh class Dbg { public: - class TypeCache { - public: - // Returns a weak global for the input type. Deduplicates. - jobject Add(mirror::Class* t) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_, - Locks::alloc_tracker_lock_); - // Clears the type cache and deletes all the weak global refs. - void Clear() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_, - Locks::alloc_tracker_lock_); - - private: - std::multimap<int32_t, jobject> objects_; - }; - static void SetJdwpAllowed(bool allowed); static void StartJdwp(); @@ -675,19 +659,12 @@ class Dbg { SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); /* - * Recent allocation tracking support. + * Allocation tracking support. */ - static void RecordAllocation(Thread* self, mirror::Class* type, size_t byte_count) - LOCKS_EXCLUDED(Locks::alloc_tracker_lock_) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); static void SetAllocTrackingEnabled(bool enabled) LOCKS_EXCLUDED(Locks::alloc_tracker_lock_); - static bool IsAllocTrackingEnabled() { - return recent_allocation_records_ != nullptr; - } static jbyteArray GetRecentAllocations() LOCKS_EXCLUDED(Locks::alloc_tracker_lock_) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - static size_t HeadIndex() EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_); static void DumpRecentAllocations() LOCKS_EXCLUDED(Locks::alloc_tracker_lock_); enum HpifWhen { @@ -783,11 +760,6 @@ class Dbg { static bool IsForcedInterpreterNeededForUpcallImpl(Thread* thread, ArtMethod* m) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - static AllocRecord* recent_allocation_records_ PT_GUARDED_BY(Locks::alloc_tracker_lock_); - static size_t alloc_record_max_ GUARDED_BY(Locks::alloc_tracker_lock_); - static size_t alloc_record_head_ GUARDED_BY(Locks::alloc_tracker_lock_); - static size_t alloc_record_count_ GUARDED_BY(Locks::alloc_tracker_lock_); - // Indicates whether the debugger is making requests. static bool gDebuggerActive; @@ -812,9 +784,6 @@ class Dbg { static size_t* GetReferenceCounterForEvent(uint32_t instrumentation_event); - // Weak global type cache, TODO improve this. - static TypeCache type_cache_ GUARDED_BY(Locks::alloc_tracker_lock_); - // Instrumentation event reference counters. // TODO we could use an array instead of having all these dedicated counters. Instrumentation // events are bits of a mask so we could convert them to array index. @@ -826,7 +795,6 @@ class Dbg { static size_t exception_catch_event_ref_count_ GUARDED_BY(Locks::deoptimization_lock_); static uint32_t instrumentation_events_ GUARDED_BY(Locks::mutator_lock_); - friend class AllocRecord; // For type_cache_ with proper annotalysis. DISALLOW_COPY_AND_ASSIGN(Dbg); }; diff --git a/runtime/dex_file.h b/runtime/dex_file.h index d017601565..7ac264a0c5 100644 --- a/runtime/dex_file.h +++ b/runtime/dex_file.h @@ -264,13 +264,18 @@ class DexFile { // Raw code_item. struct CodeItem { - uint16_t registers_size_; - uint16_t ins_size_; - uint16_t outs_size_; - uint16_t tries_size_; - uint32_t debug_info_off_; // file offset to debug info stream + uint16_t registers_size_; // the number of registers used by this code + // (locals + parameters) + uint16_t ins_size_; // the number of words of incoming arguments to the method + // that this code is for + uint16_t outs_size_; // the number of words of outgoing argument space required + // by this code for method invocation + uint16_t tries_size_; // the number of try_items for this instance. If non-zero, + // then these appear as the tries array just after the + // insns in this instance. + uint32_t debug_info_off_; // file offset to debug info stream uint32_t insns_size_in_code_units_; // size of the insns array, in 2 byte code units - uint16_t insns_[1]; + uint16_t insns_[1]; // actual array of bytecode. private: DISALLOW_COPY_AND_ASSIGN(CodeItem); diff --git a/runtime/gc/accounting/space_bitmap-inl.h b/runtime/gc/accounting/space_bitmap-inl.h index c16f5d35e0..006d2c7d30 100644 --- a/runtime/gc/accounting/space_bitmap-inl.h +++ b/runtime/gc/accounting/space_bitmap-inl.h @@ -159,6 +159,7 @@ template<size_t kAlignment> template<bool kSetBit> inline bool SpaceBitmap<kAlignment>::Modify(const mirror::Object* obj) { uintptr_t addr = reinterpret_cast<uintptr_t>(obj); DCHECK_GE(addr, heap_begin_); + DCHECK(HasAddress(obj)) << obj; const uintptr_t offset = addr - heap_begin_; const size_t index = OffsetToIndex(offset); const uintptr_t mask = OffsetToMask(offset); diff --git a/runtime/gc/accounting/space_bitmap.cc b/runtime/gc/accounting/space_bitmap.cc index fe2b284fcb..6546eb4245 100644 --- a/runtime/gc/accounting/space_bitmap.cc +++ b/runtime/gc/accounting/space_bitmap.cc @@ -35,6 +35,11 @@ size_t SpaceBitmap<kAlignment>::ComputeBitmapSize(uint64_t capacity) { } template<size_t kAlignment> +size_t SpaceBitmap<kAlignment>::ComputeHeapSize(uint64_t bitmap_bytes) { + return bitmap_bytes * kBitsPerByte * kAlignment; +} + +template<size_t kAlignment> SpaceBitmap<kAlignment>* SpaceBitmap<kAlignment>::CreateFromMemMap( const std::string& name, MemMap* mem_map, uint8_t* heap_begin, size_t heap_capacity) { CHECK(mem_map != nullptr); diff --git a/runtime/gc/accounting/space_bitmap.h b/runtime/gc/accounting/space_bitmap.h index d6b3ed4f26..35faff3774 100644 --- a/runtime/gc/accounting/space_bitmap.h +++ b/runtime/gc/accounting/space_bitmap.h @@ -188,15 +188,16 @@ class SpaceBitmap { std::string Dump() const; + // Helper function for computing bitmap size based on a 64 bit capacity. + static size_t ComputeBitmapSize(uint64_t capacity); + static size_t ComputeHeapSize(uint64_t bitmap_bytes); + private: // TODO: heap_end_ is initialized so that the heap bitmap is empty, this doesn't require the -1, // however, we document that this is expected on heap_end_ SpaceBitmap(const std::string& name, MemMap* mem_map, uintptr_t* bitmap_begin, size_t bitmap_size, const void* heap_begin); - // Helper function for computing bitmap size based on a 64 bit capacity. - static size_t ComputeBitmapSize(uint64_t capacity); - template<bool kSetBit> bool Modify(const mirror::Object* obj); diff --git a/runtime/gc/allocation_record.cc b/runtime/gc/allocation_record.cc new file mode 100644 index 0000000000..a385363428 --- /dev/null +++ b/runtime/gc/allocation_record.cc @@ -0,0 +1,206 @@ +/* + * 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. + */ + +#include "allocation_record.h" + +#include "art_method-inl.h" +#include "base/stl_util.h" +#include "stack.h" + +#ifdef HAVE_ANDROID_OS +#include "cutils/properties.h" +#endif + +namespace art { +namespace gc { + +int32_t AllocRecordStackTraceElement::ComputeLineNumber() const { + DCHECK(method_ != nullptr); + return method_->GetLineNumFromDexPC(dex_pc_); +} + +void AllocRecordObjectMap::SetProperties() { +#ifdef HAVE_ANDROID_OS + // Check whether there's a system property overriding the max number of records. + const char* propertyName = "dalvik.vm.allocTrackerMax"; + char allocMaxString[PROPERTY_VALUE_MAX]; + if (property_get(propertyName, allocMaxString, "") > 0) { + char* end; + size_t value = strtoul(allocMaxString, &end, 10); + if (*end != '\0') { + LOG(ERROR) << "Ignoring " << propertyName << " '" << allocMaxString + << "' --- invalid"; + } else { + alloc_record_max_ = value; + } + } + // Check whether there's a system property overriding the max depth of stack trace. + propertyName = "dalvik.vm.allocStackDepth"; + char stackDepthString[PROPERTY_VALUE_MAX]; + if (property_get(propertyName, stackDepthString, "") > 0) { + char* end; + size_t value = strtoul(stackDepthString, &end, 10); + if (*end != '\0') { + LOG(ERROR) << "Ignoring " << propertyName << " '" << stackDepthString + << "' --- invalid"; + } else { + max_stack_depth_ = value; + } + } +#endif +} + +AllocRecordObjectMap::~AllocRecordObjectMap() { + STLDeleteValues(&entries_); +} + +void AllocRecordObjectMap::SweepAllocationRecords(IsMarkedCallback* callback, void* arg) { + VLOG(heap) << "Start SweepAllocationRecords()"; + size_t count_deleted = 0, count_moved = 0; + for (auto it = entries_.begin(), end = entries_.end(); it != end;) { + // This does not need a read barrier because this is called by GC. + mirror::Object* old_object = it->first.Read<kWithoutReadBarrier>(); + AllocRecord* record = it->second; + mirror::Object* new_object = callback(old_object, arg); + if (new_object == nullptr) { + delete record; + it = entries_.erase(it); + ++count_deleted; + } else { + if (old_object != new_object) { + it->first = GcRoot<mirror::Object>(new_object); + ++count_moved; + } + ++it; + } + } + VLOG(heap) << "Deleted " << count_deleted << " allocation records"; + VLOG(heap) << "Updated " << count_moved << " allocation records"; +} + +struct AllocRecordStackVisitor : public StackVisitor { + AllocRecordStackVisitor(Thread* thread, AllocRecordStackTrace* trace_in, size_t max) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) + : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), + trace(trace_in), + depth(0), + max_depth(max) {} + + // TODO: Enable annotalysis. We know lock is held in constructor, but abstraction confuses + // annotalysis. + bool VisitFrame() OVERRIDE NO_THREAD_SAFETY_ANALYSIS { + if (depth >= max_depth) { + return false; + } + ArtMethod* m = GetMethod(); + if (!m->IsRuntimeMethod()) { + trace->SetStackElementAt(depth, m, GetDexPc()); + ++depth; + } + return true; + } + + ~AllocRecordStackVisitor() { + trace->SetDepth(depth); + } + + AllocRecordStackTrace* trace; + size_t depth; + const size_t max_depth; +}; + +void AllocRecordObjectMap::SetAllocTrackingEnabled(bool enable) { + Thread* self = Thread::Current(); + Heap* heap = Runtime::Current()->GetHeap(); + if (enable) { + { + MutexLock mu(self, *Locks::alloc_tracker_lock_); + if (heap->IsAllocTrackingEnabled()) { + return; // Already enabled, bail. + } + AllocRecordObjectMap* records = new AllocRecordObjectMap(); + CHECK(records != nullptr); + records->SetProperties(); + std::string self_name; + self->GetThreadName(self_name); + if (self_name == "JDWP") { + records->alloc_ddm_thread_id_ = self->GetTid(); + } + size_t sz = sizeof(AllocRecordStackTraceElement) * records->max_stack_depth_ + + sizeof(AllocRecord) + sizeof(AllocRecordStackTrace); + LOG(INFO) << "Enabling alloc tracker (" << records->alloc_record_max_ << " entries of " + << records->max_stack_depth_ << " frames, taking up to " + << PrettySize(sz * records->alloc_record_max_) << ")"; + heap->SetAllocationRecords(records); + heap->SetAllocTrackingEnabled(true); + } + Runtime::Current()->GetInstrumentation()->InstrumentQuickAllocEntryPoints(); + } else { + { + MutexLock mu(self, *Locks::alloc_tracker_lock_); + if (!heap->IsAllocTrackingEnabled()) { + return; // Already disabled, bail. + } + heap->SetAllocTrackingEnabled(false); + LOG(INFO) << "Disabling alloc tracker"; + heap->SetAllocationRecords(nullptr); + } + // If an allocation comes in before we uninstrument, we will safely drop it on the floor. + Runtime::Current()->GetInstrumentation()->UninstrumentQuickAllocEntryPoints(); + } +} + +void AllocRecordObjectMap::RecordAllocation(Thread* self, mirror::Object* obj, size_t byte_count) { + MutexLock mu(self, *Locks::alloc_tracker_lock_); + Heap* heap = Runtime::Current()->GetHeap(); + if (!heap->IsAllocTrackingEnabled()) { + // In the process of shutting down recording, bail. + return; + } + + AllocRecordObjectMap* records = heap->GetAllocationRecords(); + DCHECK(records != nullptr); + + // Do not record for DDM thread + if (records->alloc_ddm_thread_id_ == self->GetTid()) { + return; + } + + DCHECK_LE(records->Size(), records->alloc_record_max_); + + // Remove oldest record. + if (records->Size() == records->alloc_record_max_) { + records->RemoveOldest(); + } + + // Get stack trace. + const size_t max_depth = records->max_stack_depth_; + AllocRecordStackTrace* trace = new AllocRecordStackTrace(self->GetTid(), max_depth); + // add scope to make "visitor" destroyed promptly, in order to set the trace->depth_ + { + AllocRecordStackVisitor visitor(self, trace, max_depth); + visitor.WalkStack(); + } + + // Fill in the basics. + AllocRecord* record = new AllocRecord(byte_count, trace); + + records->Put(obj, record); + DCHECK_LE(records->Size(), records->alloc_record_max_); +} + +} // namespace gc +} // namespace art diff --git a/runtime/gc/allocation_record.h b/runtime/gc/allocation_record.h new file mode 100644 index 0000000000..45b3406cea --- /dev/null +++ b/runtime/gc/allocation_record.h @@ -0,0 +1,271 @@ +/* + * 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. + */ + +#ifndef ART_RUNTIME_GC_ALLOCATION_RECORD_H_ +#define ART_RUNTIME_GC_ALLOCATION_RECORD_H_ + +#include <list> + +#include "base/mutex.h" +#include "object_callbacks.h" +#include "gc_root.h" + +namespace art { + +class ArtMethod; +class Thread; + +namespace mirror { + class Class; + class Object; +} + +namespace gc { + +class AllocRecordStackTraceElement { + public: + AllocRecordStackTraceElement() : method_(nullptr), dex_pc_(0) {} + + int32_t ComputeLineNumber() const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + ArtMethod* GetMethod() const { + return method_; + } + + void SetMethod(ArtMethod* m) { + method_ = m; + } + + uint32_t GetDexPc() const { + return dex_pc_; + } + + void SetDexPc(uint32_t pc) { + dex_pc_ = pc; + } + + bool operator==(const AllocRecordStackTraceElement& other) const { + if (this == &other) return true; + return method_ == other.method_ && dex_pc_ == other.dex_pc_; + } + + private: + ArtMethod* method_; + uint32_t dex_pc_; +}; + +class AllocRecordStackTrace { + public: + static constexpr size_t kHashMultiplier = 17; + + AllocRecordStackTrace(pid_t tid, size_t max_depth) + : tid_(tid), depth_(0), stack_(new AllocRecordStackTraceElement[max_depth]) {} + + ~AllocRecordStackTrace() { + delete[] stack_; + } + + pid_t GetTid() const { + return tid_; + } + + size_t GetDepth() const { + return depth_; + } + + void SetDepth(size_t depth) { + depth_ = depth; + } + + const AllocRecordStackTraceElement& GetStackElement(size_t index) const { + DCHECK_LT(index, depth_); + return stack_[index]; + } + + void SetStackElementAt(size_t index, ArtMethod* m, uint32_t dex_pc) { + stack_[index].SetMethod(m); + stack_[index].SetDexPc(dex_pc); + } + + bool operator==(const AllocRecordStackTrace& other) const { + if (this == &other) return true; + if (depth_ != other.depth_) return false; + for (size_t i = 0; i < depth_; ++i) { + if (!(stack_[i] == other.stack_[i])) return false; + } + return true; + } + + private: + const pid_t tid_; + size_t depth_; + AllocRecordStackTraceElement* const stack_; +}; + +struct HashAllocRecordTypes { + size_t operator()(const AllocRecordStackTraceElement& r) const { + return std::hash<void*>()(reinterpret_cast<void*>(r.GetMethod())) * + AllocRecordStackTrace::kHashMultiplier + std::hash<uint32_t>()(r.GetDexPc()); + } + + size_t operator()(const AllocRecordStackTrace& r) const { + size_t depth = r.GetDepth(); + size_t result = r.GetTid() * AllocRecordStackTrace::kHashMultiplier + depth; + for (size_t i = 0; i < depth; ++i) { + result = result * AllocRecordStackTrace::kHashMultiplier + (*this)(r.GetStackElement(i)); + } + return result; + } +}; + +template <typename T> struct HashAllocRecordTypesPtr { + size_t operator()(const T* r) const { + if (r == nullptr) return 0; + return HashAllocRecordTypes()(*r); + } +}; + +template <typename T> struct EqAllocRecordTypesPtr { + bool operator()(const T* r1, const T* r2) const { + if (r1 == r2) return true; + if (r1 == nullptr || r2 == nullptr) return false; + return *r1 == *r2; + } +}; + +class AllocRecord { + public: + // All instances of AllocRecord should be managed by an instance of AllocRecordObjectMap. + AllocRecord(size_t count, AllocRecordStackTrace* trace) + : byte_count_(count), trace_(trace) {} + + ~AllocRecord() { + delete trace_; + } + + size_t GetDepth() const { + return trace_->GetDepth(); + } + + const AllocRecordStackTrace* GetStackTrace() const { + return trace_; + } + + size_t ByteCount() const { + return byte_count_; + } + + pid_t GetTid() const { + return trace_->GetTid(); + } + + const AllocRecordStackTraceElement& StackElement(size_t index) const { + return trace_->GetStackElement(index); + } + + private: + const size_t byte_count_; + // TODO: Currently trace_ is like a std::unique_ptr, + // but in future with deduplication it could be a std::shared_ptr. + const AllocRecordStackTrace* const trace_; +}; + +class AllocRecordObjectMap { + public: + // Since the entries contain weak roots, they need a read barrier. Do not directly access + // the mirror::Object pointers in it. Use functions that contain read barriers. + // No need for "const AllocRecord*" in the list, because all fields of AllocRecord are const. + typedef std::list<std::pair<GcRoot<mirror::Object>, AllocRecord*>> EntryList; + + // "static" because it is part of double-checked locking. It needs to check a bool first, + // in order to make sure the AllocRecordObjectMap object is not null. + static void RecordAllocation(Thread* self, mirror::Object* obj, size_t byte_count) + LOCKS_EXCLUDED(Locks::alloc_tracker_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + static void SetAllocTrackingEnabled(bool enabled) LOCKS_EXCLUDED(Locks::alloc_tracker_lock_); + + AllocRecordObjectMap() EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_) + : alloc_record_max_(kDefaultNumAllocRecords), + max_stack_depth_(kDefaultAllocStackDepth), + alloc_ddm_thread_id_(0) {} + + ~AllocRecordObjectMap(); + + void Put(mirror::Object* obj, AllocRecord* record) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) + EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_) { + entries_.emplace_back(GcRoot<mirror::Object>(obj), record); + } + + size_t Size() const SHARED_LOCKS_REQUIRED(Locks::alloc_tracker_lock_) { + return entries_.size(); + } + + void SweepAllocationRecords(IsMarkedCallback* callback, void* arg) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) + EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_); + + void RemoveOldest() + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) + EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_) { + DCHECK(!entries_.empty()); + delete entries_.front().second; + entries_.pop_front(); + } + + // TODO: Is there a better way to hide the entries_'s type? + EntryList::iterator Begin() + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) + EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_) { + return entries_.begin(); + } + + EntryList::iterator End() + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) + EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_) { + return entries_.end(); + } + + EntryList::reverse_iterator RBegin() + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) + EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_) { + return entries_.rbegin(); + } + + EntryList::reverse_iterator REnd() + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) + EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_) { + return entries_.rend(); + } + + private: + static constexpr size_t kDefaultNumAllocRecords = 512 * 1024; + static constexpr size_t kDefaultAllocStackDepth = 4; + size_t alloc_record_max_ GUARDED_BY(Locks::alloc_tracker_lock_); + // The implementation always allocates max_stack_depth_ number of frames for each stack trace. + // As long as the max depth is not very large, this is not a waste of memory since most stack + // traces will fill up the max depth number of the frames. + size_t max_stack_depth_ GUARDED_BY(Locks::alloc_tracker_lock_); + pid_t alloc_ddm_thread_id_ GUARDED_BY(Locks::alloc_tracker_lock_); + EntryList entries_ GUARDED_BY(Locks::alloc_tracker_lock_); + + void SetProperties() EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_); +}; + +} // namespace gc +} // namespace art +#endif // ART_RUNTIME_GC_ALLOCATION_RECORD_H_ diff --git a/runtime/gc/heap-inl.h b/runtime/gc/heap-inl.h index 2d5433032d..ee4568ecea 100644 --- a/runtime/gc/heap-inl.h +++ b/runtime/gc/heap-inl.h @@ -22,6 +22,7 @@ #include "base/time_utils.h" #include "debugger.h" #include "gc/accounting/card_table-inl.h" +#include "gc/allocation_record.h" #include "gc/collector/semi_space.h" #include "gc/space/bump_pointer_space-inl.h" #include "gc/space/dlmalloc_space-inl.h" @@ -168,11 +169,11 @@ inline mirror::Object* Heap::AllocObjectWithAllocator(Thread* self, mirror::Clas PushOnAllocationStack(self, &obj); } if (kInstrumented) { - if (Dbg::IsAllocTrackingEnabled()) { - Dbg::RecordAllocation(self, klass, bytes_allocated); + if (IsAllocTrackingEnabled()) { + AllocRecordObjectMap::RecordAllocation(self, obj, bytes_allocated); } } else { - DCHECK(!Dbg::IsAllocTrackingEnabled()); + DCHECK(!IsAllocTrackingEnabled()); } // IsConcurrentGc() isn't known at compile time so we can optimize by not checking it for // the BumpPointer or TLAB allocators. This is nice since it allows the entire if statement to be diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index aeab7d80b4..22207ee21c 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -209,7 +209,8 @@ Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max blocking_gc_count_last_window_(0U), gc_count_rate_histogram_("gc count rate histogram", 1U, kGcCountRateMaxBucketCount), blocking_gc_count_rate_histogram_("blocking gc count rate histogram", 1U, - kGcCountRateMaxBucketCount) { + kGcCountRateMaxBucketCount), + alloc_tracking_enabled_(false) { if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) { LOG(INFO) << "Heap() entering"; } @@ -1000,6 +1001,27 @@ void Heap::DumpGcPerformanceInfo(std::ostream& os) { BaseMutex::DumpAll(os); } +void Heap::ResetGcPerformanceInfo() { + for (auto& collector : garbage_collectors_) { + collector->ResetMeasurements(); + } + total_allocation_time_.StoreRelaxed(0); + total_bytes_freed_ever_ = 0; + total_objects_freed_ever_ = 0; + total_wait_time_ = 0; + blocking_gc_count_ = 0; + blocking_gc_time_ = 0; + gc_count_last_window_ = 0; + blocking_gc_count_last_window_ = 0; + last_update_time_gc_count_rate_histograms_ = // Round down by the window duration. + (NanoTime() / kGcCountRateHistogramWindowDuration) * kGcCountRateHistogramWindowDuration; + { + MutexLock mu(Thread::Current(), *gc_complete_lock_); + gc_count_rate_histogram_.Reset(); + blocking_gc_count_rate_histogram_.Reset(); + } +} + uint64_t Heap::GetGcCount() const { uint64_t gc_count = 0U; for (auto& collector : garbage_collectors_) { @@ -1043,6 +1065,7 @@ Heap::~Heap() { STLDeleteElements(&garbage_collectors_); // If we don't reset then the mark stack complains in its destructor. allocation_stack_->Reset(); + allocation_records_.reset(); live_stack_->Reset(); STLDeleteValues(&mod_union_tables_); STLDeleteValues(&remembered_sets_); @@ -3653,5 +3676,18 @@ void Heap::ClearMarkedObjects() { } } +void Heap::SetAllocationRecords(AllocRecordObjectMap* records) { + allocation_records_.reset(records); +} + +void Heap::SweepAllocationRecords(IsMarkedCallback* visitor, void* arg) const { + if (IsAllocTrackingEnabled()) { + MutexLock mu(Thread::Current(), *Locks::alloc_tracker_lock_); + if (IsAllocTrackingEnabled()) { + GetAllocationRecords()->SweepAllocationRecords(visitor, arg); + } + } +} + } // namespace gc } // namespace art diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h index 81a97414ba..18244c856b 100644 --- a/runtime/gc/heap.h +++ b/runtime/gc/heap.h @@ -58,6 +58,7 @@ namespace mirror { namespace gc { +class AllocRecordObjectMap; class ReferenceProcessor; class TaskProcessor; @@ -597,6 +598,7 @@ class Heap { // GC performance measuring void DumpGcPerformanceInfo(std::ostream& os); + void ResetGcPerformanceInfo(); // Returns true if we currently care about pause times. bool CareAboutPauseTimes() const { @@ -683,6 +685,27 @@ class Heap { void DumpGcCountRateHistogram(std::ostream& os) const; void DumpBlockingGcCountRateHistogram(std::ostream& os) const; + // Allocation tracking support + // Callers to this function use double-checked locking to ensure safety on allocation_records_ + bool IsAllocTrackingEnabled() const { + return alloc_tracking_enabled_.LoadRelaxed(); + } + + void SetAllocTrackingEnabled(bool enabled) EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_) { + alloc_tracking_enabled_.StoreRelaxed(enabled); + } + + AllocRecordObjectMap* GetAllocationRecords() const + EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_) { + return allocation_records_.get(); + } + + void SetAllocationRecords(AllocRecordObjectMap* records) + EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_); + + void SweepAllocationRecords(IsMarkedCallback* visitor, void* arg) const + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + private: class ConcurrentGCTask; class CollectorTransitionTask; @@ -1191,6 +1214,11 @@ class Heap { // The histogram of the number of blocking GC invocations per window duration. Histogram<uint64_t> blocking_gc_count_rate_histogram_ GUARDED_BY(gc_complete_lock_); + // Allocation tracking support + Atomic<bool> alloc_tracking_enabled_; + std::unique_ptr<AllocRecordObjectMap> allocation_records_ + GUARDED_BY(Locks::alloc_tracker_lock_); + friend class CollectorTransitionTask; friend class collector::GarbageCollector; friend class collector::MarkCompact; diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc index 437fd8c5c9..f7ceb841c2 100644 --- a/runtime/gc/space/image_space.cc +++ b/runtime/gc/space/image_space.cc @@ -694,7 +694,7 @@ ImageSpace* ImageSpace::Init(const char* image_filename, const char* image_locat const auto section_idx = static_cast<ImageHeader::ImageSections>(i); auto& section = image_header.GetImageSection(section_idx); LOG(INFO) << section_idx << " start=" - << reinterpret_cast<void*>(image_header.GetImageBegin() + section.Offset()) + << reinterpret_cast<void*>(image_header.GetImageBegin() + section.Offset()) << " " << section; } } @@ -730,9 +730,9 @@ ImageSpace* ImageSpace::Init(const char* image_filename, const char* image_locat std::string bitmap_name(StringPrintf("imagespace %s live-bitmap %u", image_filename, bitmap_index)); std::unique_ptr<accounting::ContinuousSpaceBitmap> bitmap( - accounting::ContinuousSpaceBitmap::CreateFromMemMap(bitmap_name, image_map.release(), - reinterpret_cast<uint8_t*>(map->Begin()), - map->Size())); + accounting::ContinuousSpaceBitmap::CreateFromMemMap( + bitmap_name, image_map.release(), reinterpret_cast<uint8_t*>(map->Begin()), + accounting::ContinuousSpaceBitmap::ComputeHeapSize(bitmap_section.Size()))); if (bitmap.get() == nullptr) { *error_msg = StringPrintf("Could not create bitmap '%s'", bitmap_name.c_str()); return nullptr; diff --git a/runtime/hprof/hprof.cc b/runtime/hprof/hprof.cc index 6e0e56e82a..f32d5a1b81 100644 --- a/runtime/hprof/hprof.cc +++ b/runtime/hprof/hprof.cc @@ -48,6 +48,7 @@ #include "dex_file-inl.h" #include "gc_root.h" #include "gc/accounting/heap_bitmap.h" +#include "gc/allocation_record.h" #include "gc/heap.h" #include "gc/space/space.h" #include "globals.h" @@ -68,14 +69,13 @@ namespace hprof { static constexpr bool kDirectStream = true; static constexpr uint32_t kHprofTime = 0; -static constexpr uint32_t kHprofNullStackTrace = 0; static constexpr uint32_t kHprofNullThread = 0; static constexpr size_t kMaxObjectsPerSegment = 128; static constexpr size_t kMaxBytesPerSegment = 4096; // The static field-name for the synthetic object generated to account for class static overhead. -static constexpr const char* kStaticOverheadName = "$staticOverhead"; +static constexpr const char* kClassOverheadName = "$classOverhead"; enum HprofTag { HPROF_TAG_STRING = 0x01, @@ -144,6 +144,10 @@ enum HprofBasicType { typedef uint32_t HprofStringId; typedef uint32_t HprofClassObjectId; +typedef uint32_t HprofClassSerialNumber; +typedef uint32_t HprofStackTraceSerialNumber; +typedef uint32_t HprofStackFrameId; +static constexpr HprofStackTraceSerialNumber kHprofNullStackTrace = 0; class EndianOutput { public: @@ -194,6 +198,10 @@ class EndianOutput { AddU4(PointerToLowMemUInt32(value)); } + void AddStackTraceSerialNumber(HprofStackTraceSerialNumber value) { + AddU4(value); + } + // The ID for the synthetic object generated to account for class static overhead. void AddClassStaticsId(const mirror::Class* value) { AddU4(1 | PointerToLowMemUInt32(value)); @@ -415,13 +423,21 @@ class Hprof : public SingleRootVisitor { start_ns_(NanoTime()), current_heap_(HPROF_HEAP_DEFAULT), objects_in_segment_(0), - next_string_id_(0x400000) { + next_string_id_(0x400000), + next_class_serial_number_(1) { LOG(INFO) << "hprof: heap dump \"" << filename_ << "\" starting..."; } void Dump() EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_) - LOCKS_EXCLUDED(Locks::heap_bitmap_lock_) { + LOCKS_EXCLUDED(Locks::heap_bitmap_lock_, Locks::alloc_tracker_lock_) { + { + MutexLock mu(Thread::Current(), *Locks::alloc_tracker_lock_); + if (Runtime::Current()->GetHeap()->IsAllocTrackingEnabled()) { + PopulateAllocationTrackingTraces(); + } + } + // First pass to measure the size of the dump. size_t overall_size; size_t max_length; @@ -480,11 +496,11 @@ class Hprof : public SingleRootVisitor { objects_in_segment_ = 0; if (header_first) { - ProcessHeader(); + ProcessHeader(true); ProcessBody(); } else { ProcessBody(); - ProcessHeader(); + ProcessHeader(false); } } @@ -501,21 +517,29 @@ class Hprof : public SingleRootVisitor { output_->EndRecord(); } - void ProcessHeader() EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_) { + void ProcessHeader(bool string_first) EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_) { // Write the header. WriteFixedHeader(); // Write the string and class tables, and any stack traces, to the header. // (jhat requires that these appear before any of the data in the body that refers to them.) - WriteStringTable(); + // jhat also requires the string table appear before class table and stack traces. + // However, WriteStackTraces() can modify the string table, so it's necessary to call + // WriteStringTable() last in the first pass, to compute the correct length of the output. + if (string_first) { + WriteStringTable(); + } WriteClassTable(); WriteStackTraces(); + if (!string_first) { + WriteStringTable(); + } output_->EndRecord(); } void WriteClassTable() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - uint32_t nextSerialNumber = 1; - - for (mirror::Class* c : classes_) { + for (const auto& p : classes_) { + mirror::Class* c = p.first; + HprofClassSerialNumber sn = p.second; CHECK(c != nullptr); output_->StartNewRecord(HPROF_TAG_LOAD_CLASS, kHprofTime); // LOAD CLASS format: @@ -523,9 +547,9 @@ class Hprof : public SingleRootVisitor { // ID: class object ID. We use the address of the class object structure as its ID. // U4: stack trace serial number // ID: class name string ID - __ AddU4(nextSerialNumber++); + __ AddU4(sn); __ AddObjectId(c); - __ AddU4(kHprofNullStackTrace); + __ AddStackTraceSerialNumber(LookupStackTraceSerialNumber(c)); __ AddStringId(LookupClassNameId(c)); } } @@ -567,15 +591,31 @@ class Hprof : public SingleRootVisitor { HprofClassObjectId LookupClassId(mirror::Class* c) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { if (c != nullptr) { - auto result = classes_.insert(c); - const mirror::Class* present = *result.first; - CHECK_EQ(present, c); - // Make sure that we've assigned a string ID for this class' name - LookupClassNameId(c); + auto it = classes_.find(c); + if (it == classes_.end()) { + // first time to see this class + HprofClassSerialNumber sn = next_class_serial_number_++; + classes_.Put(c, sn); + // Make sure that we've assigned a string ID for this class' name + LookupClassNameId(c); + } } return PointerToLowMemUInt32(c); } + HprofStackTraceSerialNumber LookupStackTraceSerialNumber(const mirror::Object* obj) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + auto r = allocation_records_.find(obj); + if (r == allocation_records_.end()) { + return kHprofNullStackTrace; + } else { + const gc::AllocRecordStackTrace* trace = r->second; + auto result = traces_.find(trace); + CHECK(result != traces_.end()); + return result->second; + } + } + HprofStringId LookupStringId(mirror::String* string) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { return LookupStringId(string->ToModifiedUtf8()); } @@ -622,12 +662,66 @@ class Hprof : public SingleRootVisitor { __ AddU4(static_cast<uint32_t>(nowMs & 0xFFFFFFFF)); } - void WriteStackTraces() { + void WriteStackTraces() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { // Write a dummy stack trace record so the analysis tools don't freak out. output_->StartNewRecord(HPROF_TAG_STACK_TRACE, kHprofTime); - __ AddU4(kHprofNullStackTrace); + __ AddStackTraceSerialNumber(kHprofNullStackTrace); __ AddU4(kHprofNullThread); __ AddU4(0); // no frames + + // TODO: jhat complains "WARNING: Stack trace not found for serial # -1", but no trace should + // have -1 as its serial number (as long as HprofStackTraceSerialNumber doesn't overflow). + for (const auto& it : traces_) { + const gc::AllocRecordStackTrace* trace = it.first; + HprofStackTraceSerialNumber trace_sn = it.second; + size_t depth = trace->GetDepth(); + + // First write stack frames of the trace + for (size_t i = 0; i < depth; ++i) { + const gc::AllocRecordStackTraceElement* frame = &trace->GetStackElement(i); + ArtMethod* method = frame->GetMethod(); + CHECK(method != nullptr); + output_->StartNewRecord(HPROF_TAG_STACK_FRAME, kHprofTime); + // STACK FRAME format: + // ID: stack frame ID. We use the address of the AllocRecordStackTraceElement object as its ID. + // ID: method name string ID + // ID: method signature string ID + // ID: source file name string ID + // U4: class serial number + // U4: >0, line number; 0, no line information available; -1, unknown location + auto frame_result = frames_.find(frame); + CHECK(frame_result != frames_.end()); + __ AddU4(frame_result->second); + __ AddStringId(LookupStringId(method->GetName())); + __ AddStringId(LookupStringId(method->GetSignature().ToString())); + const char* source_file = method->GetDeclaringClassSourceFile(); + if (source_file == nullptr) { + source_file = ""; + } + __ AddStringId(LookupStringId(source_file)); + auto class_result = classes_.find(method->GetDeclaringClass()); + CHECK(class_result != classes_.end()); + __ AddU4(class_result->second); + __ AddU4(frame->ComputeLineNumber()); + } + + // Then write the trace itself + output_->StartNewRecord(HPROF_TAG_STACK_TRACE, kHprofTime); + // STACK TRACE format: + // U4: stack trace serial number. We use the address of the AllocRecordStackTrace object as its serial number. + // U4: thread serial number. We use Thread::GetTid(). + // U4: number of frames + // [ID]*: series of stack frame ID's + __ AddStackTraceSerialNumber(trace_sn); + __ AddU4(trace->GetTid()); + __ AddU4(depth); + for (size_t i = 0; i < depth; ++i) { + const gc::AllocRecordStackTraceElement* frame = &trace->GetStackElement(i); + auto frame_result = frames_.find(frame); + CHECK(frame_result != frames_.end()); + __ AddU4(frame_result->second); + } + } } bool DumpToDdmsBuffered(size_t overall_size ATTRIBUTE_UNUSED, size_t max_length ATTRIBUTE_UNUSED) @@ -723,6 +817,40 @@ class Hprof : public SingleRootVisitor { return true; } + void PopulateAllocationTrackingTraces() + EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_, Locks::alloc_tracker_lock_) { + gc::AllocRecordObjectMap* records = Runtime::Current()->GetHeap()->GetAllocationRecords(); + CHECK(records != nullptr); + HprofStackTraceSerialNumber next_trace_sn = kHprofNullStackTrace + 1; + HprofStackFrameId next_frame_id = 0; + + for (auto it = records->Begin(), end = records->End(); it != end; ++it) { + const mirror::Object* obj = it->first.Read(); + const gc::AllocRecordStackTrace* trace = it->second->GetStackTrace(); + + // Copy the pair into a real hash map to speed up look up. + auto records_result = allocation_records_.emplace(obj, trace); + // The insertion should always succeed, i.e. no duplicate object pointers in "records" + CHECK(records_result.second); + + // Generate serial numbers for traces, and IDs for frames. + auto traces_result = traces_.find(trace); + if (traces_result == traces_.end()) { + traces_.emplace(trace, next_trace_sn++); + // only check frames if the trace is newly discovered + for (size_t i = 0, depth = trace->GetDepth(); i < depth; ++i) { + const gc::AllocRecordStackTraceElement* frame = &trace->GetStackElement(i); + auto frames_result = frames_.find(frame); + if (frames_result == frames_.end()) { + frames_.emplace(frame, next_frame_id++); + } + } + } + } + CHECK_EQ(traces_.size(), next_trace_sn - kHprofNullStackTrace - 1); + CHECK_EQ(frames_.size(), next_frame_id); + } + // If direct_to_ddms_ is set, "filename_" and "fd" will be ignored. // Otherwise, "filename_" must be valid, though if "fd" >= 0 it will // only be used for debug messages. @@ -737,9 +865,18 @@ class Hprof : public SingleRootVisitor { HprofHeapId current_heap_; // Which heap we're currently dumping. size_t objects_in_segment_; - std::set<mirror::Class*> classes_; HprofStringId next_string_id_; SafeMap<std::string, HprofStringId> strings_; + HprofClassSerialNumber next_class_serial_number_; + SafeMap<mirror::Class*, HprofClassSerialNumber> classes_; + + std::unordered_map<const gc::AllocRecordStackTrace*, HprofStackTraceSerialNumber, + gc::HashAllocRecordTypesPtr<gc::AllocRecordStackTrace>, + gc::EqAllocRecordTypesPtr<gc::AllocRecordStackTrace>> traces_; + std::unordered_map<const gc::AllocRecordStackTraceElement*, HprofStackFrameId, + gc::HashAllocRecordTypesPtr<gc::AllocRecordStackTraceElement>, + gc::EqAllocRecordTypesPtr<gc::AllocRecordStackTraceElement>> frames_; + std::unordered_map<const mirror::Object*, const gc::AllocRecordStackTrace*> allocation_records_; DISALLOW_COPY_AND_ASSIGN(Hprof); }; @@ -881,10 +1018,6 @@ void Hprof::MarkRootObject(const mirror::Object* obj, jobject jni_obj, HprofHeap ++objects_in_segment_; } -static int StackTraceSerialNumber(const mirror::Object* /*obj*/) { - return kHprofNullStackTrace; -} - void Hprof::DumpHeapObject(mirror::Object* obj) { // Ignore classes that are retired. if (obj->IsClass() && obj->AsClass()->IsRetired()) { @@ -959,24 +1092,30 @@ void Hprof::DumpHeapClass(mirror::Class* klass) { // Class is allocated but not yet loaded: we cannot access its fields or super class. return; } - size_t sFieldCount = klass->NumStaticFields(); - if (sFieldCount != 0) { - int byteLength = sFieldCount * sizeof(JValue); // TODO bogus; fields are packed + const size_t num_static_fields = klass->NumStaticFields(); + // Total class size including embedded IMT, embedded vtable, and static fields. + const size_t class_size = klass->GetClassSize(); + // Class size excluding static fields (relies on reference fields being the first static fields). + const size_t class_size_without_overhead = sizeof(mirror::Class); + CHECK_LE(class_size_without_overhead, class_size); + const size_t overhead_size = class_size - class_size_without_overhead; + + if (overhead_size != 0) { // Create a byte array to reflect the allocation of the // StaticField array at the end of this class. __ AddU1(HPROF_PRIMITIVE_ARRAY_DUMP); __ AddClassStaticsId(klass); - __ AddU4(StackTraceSerialNumber(klass)); - __ AddU4(byteLength); + __ AddStackTraceSerialNumber(LookupStackTraceSerialNumber(klass)); + __ AddU4(overhead_size); __ AddU1(hprof_basic_byte); - for (int i = 0; i < byteLength; ++i) { + for (size_t i = 0; i < overhead_size; ++i) { __ AddU1(0); } } __ AddU1(HPROF_CLASS_DUMP); __ AddClassId(LookupClassId(klass)); - __ AddU4(StackTraceSerialNumber(klass)); + __ AddStackTraceSerialNumber(LookupStackTraceSerialNumber(klass)); __ AddClassId(LookupClassId(klass->GetSuperClass())); __ AddObjectId(klass->GetClassLoader()); __ AddObjectId(nullptr); // no signer @@ -986,7 +1125,7 @@ void Hprof::DumpHeapClass(mirror::Class* klass) { if (klass->IsClassClass()) { // ClassObjects have their static fields appended, so aren't all the same size. // But they're at least this size. - __ AddU4(sizeof(mirror::Class)); // instance size + __ AddU4(class_size_without_overhead); // instance size } else if (klass->IsStringClass()) { // Strings are variable length with character data at the end like arrays. // This outputs the size of an empty string. @@ -1000,15 +1139,15 @@ void Hprof::DumpHeapClass(mirror::Class* klass) { __ AddU2(0); // empty const pool // Static fields - if (sFieldCount == 0) { - __ AddU2((uint16_t)0); + if (overhead_size == 0) { + __ AddU2(static_cast<uint16_t>(0)); } else { - __ AddU2((uint16_t)(sFieldCount+1)); - __ AddStringId(LookupStringId(kStaticOverheadName)); + __ AddU2(static_cast<uint16_t>(num_static_fields + 1)); + __ AddStringId(LookupStringId(kClassOverheadName)); __ AddU1(hprof_basic_object); __ AddClassStaticsId(klass); - for (size_t i = 0; i < sFieldCount; ++i) { + for (size_t i = 0; i < num_static_fields; ++i) { ArtField* f = klass->GetStaticField(i); size_t size; @@ -1072,7 +1211,7 @@ void Hprof::DumpHeapArray(mirror::Array* obj, mirror::Class* klass) { __ AddU1(HPROF_OBJECT_ARRAY_DUMP); __ AddObjectId(obj); - __ AddU4(StackTraceSerialNumber(obj)); + __ AddStackTraceSerialNumber(LookupStackTraceSerialNumber(obj)); __ AddU4(length); __ AddClassId(LookupClassId(klass)); @@ -1087,7 +1226,7 @@ void Hprof::DumpHeapArray(mirror::Array* obj, mirror::Class* klass) { __ AddU1(HPROF_PRIMITIVE_ARRAY_DUMP); __ AddObjectId(obj); - __ AddU4(StackTraceSerialNumber(obj)); + __ AddStackTraceSerialNumber(LookupStackTraceSerialNumber(obj)); __ AddU4(length); __ AddU1(t); @@ -1108,7 +1247,7 @@ void Hprof::DumpHeapInstanceObject(mirror::Object* obj, mirror::Class* klass) { // obj is an instance object. __ AddU1(HPROF_INSTANCE_DUMP); __ AddObjectId(obj); - __ AddU4(StackTraceSerialNumber(obj)); + __ AddStackTraceSerialNumber(LookupStackTraceSerialNumber(obj)); __ AddClassId(LookupClassId(klass)); // Reserve some space for the length of the instance data, which we won't @@ -1170,7 +1309,7 @@ void Hprof::DumpHeapInstanceObject(mirror::Object* obj, mirror::Class* klass) { __ AddU1(HPROF_PRIMITIVE_ARRAY_DUMP); __ AddObjectId(value); - __ AddU4(StackTraceSerialNumber(obj)); + __ AddStackTraceSerialNumber(LookupStackTraceSerialNumber(obj)); __ AddU4(s->GetLength()); __ AddU1(hprof_basic_char); __ AddU2List(s->GetValue(), s->GetLength()); diff --git a/runtime/image.cc b/runtime/image.cc index 947c914de6..44193da4ee 100644 --- a/runtime/image.cc +++ b/runtime/image.cc @@ -24,7 +24,7 @@ namespace art { const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' }; -const uint8_t ImageHeader::kImageVersion[] = { '0', '1', '6', '\0' }; +const uint8_t ImageHeader::kImageVersion[] = { '0', '1', '7', '\0' }; ImageHeader::ImageHeader(uint32_t image_begin, uint32_t image_size, diff --git a/runtime/image.h b/runtime/image.h index c6be7ef3f7..d856f218af 100644 --- a/runtime/image.h +++ b/runtime/image.h @@ -142,6 +142,7 @@ class PACKED(4) ImageHeader { kSectionObjects, kSectionArtFields, kSectionArtMethods, + kSectionInternedStrings, kSectionImageBitmap, kSectionCount, // Number of elements in enum. }; diff --git a/runtime/intern_table.cc b/runtime/intern_table.cc index 9abbca8460..2a962784ca 100644 --- a/runtime/intern_table.cc +++ b/runtime/intern_table.cc @@ -152,20 +152,28 @@ void InternTable::AddImageStringsToTable(gc::space::ImageSpace* image_space) { CHECK(image_space != nullptr); MutexLock mu(Thread::Current(), *Locks::intern_table_lock_); if (!image_added_to_intern_table_) { - mirror::Object* root = image_space->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches); - mirror::ObjectArray<mirror::DexCache>* dex_caches = root->AsObjectArray<mirror::DexCache>(); - for (int32_t i = 0; i < dex_caches->GetLength(); ++i) { - mirror::DexCache* dex_cache = dex_caches->Get(i); - const DexFile* dex_file = dex_cache->GetDexFile(); - const size_t num_strings = dex_file->NumStringIds(); - for (size_t j = 0; j < num_strings; ++j) { - mirror::String* image_string = dex_cache->GetResolvedString(j); - if (image_string != nullptr) { - mirror::String* found = LookupStrong(image_string); - if (found == nullptr) { - InsertStrong(image_string); - } else { - DCHECK_EQ(found, image_string); + const ImageHeader* const header = &image_space->GetImageHeader(); + // Check if we have the interned strings section. + const ImageSection& section = header->GetImageSection(ImageHeader::kSectionInternedStrings); + if (section.Size() > 0) { + ReadFromMemoryLocked(image_space->Begin() + section.Offset()); + } else { + // TODO: Delete this logic? + mirror::Object* root = header->GetImageRoot(ImageHeader::kDexCaches); + mirror::ObjectArray<mirror::DexCache>* dex_caches = root->AsObjectArray<mirror::DexCache>(); + for (int32_t i = 0; i < dex_caches->GetLength(); ++i) { + mirror::DexCache* dex_cache = dex_caches->Get(i); + const DexFile* dex_file = dex_cache->GetDexFile(); + const size_t num_strings = dex_file->NumStringIds(); + for (size_t j = 0; j < num_strings; ++j) { + mirror::String* image_string = dex_cache->GetResolvedString(j); + if (image_string != nullptr) { + mirror::String* found = LookupStrong(image_string); + if (found == nullptr) { + InsertStrong(image_string); + } else { + DCHECK_EQ(found, image_string); + } } } } @@ -285,6 +293,29 @@ void InternTable::SweepInternTableWeaks(IsMarkedCallback* callback, void* arg) { weak_interns_.SweepWeaks(callback, arg); } +void InternTable::AddImageInternTable(gc::space::ImageSpace* image_space) { + const ImageSection& intern_section = image_space->GetImageHeader().GetImageSection( + ImageHeader::kSectionInternedStrings); + // Read the string tables from the image. + const uint8_t* ptr = image_space->Begin() + intern_section.Offset(); + const size_t offset = ReadFromMemory(ptr); + CHECK_LE(offset, intern_section.Size()); +} + +size_t InternTable::ReadFromMemory(const uint8_t* ptr) { + MutexLock mu(Thread::Current(), *Locks::intern_table_lock_); + return ReadFromMemoryLocked(ptr); +} + +size_t InternTable::ReadFromMemoryLocked(const uint8_t* ptr) { + return strong_interns_.ReadIntoPreZygoteTable(ptr); +} + +size_t InternTable::WriteToMemory(uint8_t* ptr) { + MutexLock mu(Thread::Current(), *Locks::intern_table_lock_); + return strong_interns_.WriteFromPostZygoteTable(ptr); +} + std::size_t InternTable::StringHashEquals::operator()(const GcRoot<mirror::String>& root) const { if (kIsDebugBuild) { Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); @@ -300,6 +331,17 @@ bool InternTable::StringHashEquals::operator()(const GcRoot<mirror::String>& a, return a.Read()->Equals(b.Read()); } +size_t InternTable::Table::ReadIntoPreZygoteTable(const uint8_t* ptr) { + CHECK_EQ(pre_zygote_table_.Size(), 0u); + size_t read_count = 0; + pre_zygote_table_ = UnorderedSet(ptr, false /* make copy */, &read_count); + return read_count; +} + +size_t InternTable::Table::WriteFromPostZygoteTable(uint8_t* ptr) { + return post_zygote_table_.WriteToMemory(ptr); +} + void InternTable::Table::Remove(mirror::String* s) { auto it = post_zygote_table_.Find(GcRoot<mirror::String>(s)); if (it != post_zygote_table_.end()) { @@ -325,9 +367,13 @@ mirror::String* InternTable::Table::Find(mirror::String* s) { } void InternTable::Table::SwapPostZygoteWithPreZygote() { - CHECK(pre_zygote_table_.Empty()); - std::swap(pre_zygote_table_, post_zygote_table_); - VLOG(heap) << "Swapping " << pre_zygote_table_.Size() << " interns to the pre zygote table"; + if (pre_zygote_table_.Empty()) { + std::swap(pre_zygote_table_, post_zygote_table_); + VLOG(heap) << "Swapping " << pre_zygote_table_.Size() << " interns to the pre zygote table"; + } else { + // This case happens if read the intern table from the image. + VLOG(heap) << "Not swapping due to non-empty pre_zygote_table_"; + } } void InternTable::Table::Insert(mirror::String* s) { diff --git a/runtime/intern_table.h b/runtime/intern_table.h index 1e5d3c22c9..97ce73c52e 100644 --- a/runtime/intern_table.h +++ b/runtime/intern_table.h @@ -97,6 +97,20 @@ class InternTable { void SwapPostZygoteWithPreZygote() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) LOCKS_EXCLUDED(Locks::intern_table_lock_); + // Add an intern table which was serialized to the image. + void AddImageInternTable(gc::space::ImageSpace* image_space) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) LOCKS_EXCLUDED(Locks::intern_table_lock_); + + // Read the intern table from memory. The elements aren't copied, the intern hash set data will + // point to somewhere within ptr. Only reads the strong interns. + size_t ReadFromMemory(const uint8_t* ptr) LOCKS_EXCLUDED(Locks::intern_table_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Write the post zygote intern table to a pointer. Only writes the strong interns since it is + // expected that there is no weak interns since this is called from the image writer. + size_t WriteToMemory(uint8_t* ptr) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) + LOCKS_EXCLUDED(Locks::intern_table_lock_); + private: class StringHashEquals { public: @@ -133,6 +147,16 @@ class InternTable { EXCLUSIVE_LOCKS_REQUIRED(Locks::intern_table_lock_); void SwapPostZygoteWithPreZygote() EXCLUSIVE_LOCKS_REQUIRED(Locks::intern_table_lock_); size_t Size() const EXCLUSIVE_LOCKS_REQUIRED(Locks::intern_table_lock_); + // Read pre zygote table is called from ReadFromMemory which happens during runtime creation + // when we load the image intern table. Returns how many bytes were read. + size_t ReadIntoPreZygoteTable(const uint8_t* ptr) + EXCLUSIVE_LOCKS_REQUIRED(Locks::intern_table_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + // The image writer calls WritePostZygoteTable through WriteToMemory, it writes the interns in + // the post zygote table. Returns how many bytes were written. + size_t WriteFromPostZygoteTable(uint8_t* ptr) + EXCLUSIVE_LOCKS_REQUIRED(Locks::intern_table_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); private: typedef HashSet<GcRoot<mirror::String>, GcRootEmptyFn, StringHashEquals, StringHashEquals, @@ -192,6 +216,10 @@ class InternTable { EXCLUSIVE_LOCKS_REQUIRED(Locks::intern_table_lock_); friend class Transaction; + size_t ReadFromMemoryLocked(const uint8_t* ptr) + EXCLUSIVE_LOCKS_REQUIRED(Locks::intern_table_lock_) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + bool image_added_to_intern_table_ GUARDED_BY(Locks::intern_table_lock_); bool log_new_roots_ GUARDED_BY(Locks::intern_table_lock_); bool allow_new_interns_ GUARDED_BY(Locks::intern_table_lock_); diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index 86a79cead9..0f6f788013 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -450,10 +450,13 @@ void UnexpectedOpcode(const Instruction* inst, const ShadowFrame& shadow_frame) static inline void AssignRegister(ShadowFrame* new_shadow_frame, const ShadowFrame& shadow_frame, size_t dest_reg, size_t src_reg) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { - // If both register locations contains the same value, the register probably holds a reference. // Uint required, so that sign extension does not make this wrong on 64b systems uint32_t src_value = shadow_frame.GetVReg(src_reg); mirror::Object* o = shadow_frame.GetVRegReference<kVerifyNone>(src_reg); + + // If both register locations contains the same value, the register probably holds a reference. + // Note: As an optimization, non-moving collectors leave a stale reference value + // in the references array even after the original vreg was overwritten to a non-reference. if (src_value == reinterpret_cast<uintptr_t>(o)) { new_shadow_frame->SetVRegReference(dest_reg, o); } else { diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc index dd7aa40368..fcf083cbe1 100644 --- a/runtime/interpreter/interpreter_switch_impl.cc +++ b/runtime/interpreter/interpreter_switch_impl.cc @@ -56,7 +56,7 @@ namespace interpreter { template<bool do_access_check, bool transaction_active> JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, ShadowFrame& shadow_frame, JValue result_register) { - bool do_assignability_check = do_access_check; + constexpr bool do_assignability_check = do_access_check; if (UNLIKELY(!shadow_frame.HasReferenceArray())) { LOG(FATAL) << "Invalid shadow frame for interpreter use"; return JValue(); diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h index 835b94ade4..8c9222f6a4 100644 --- a/runtime/mirror/class-inl.h +++ b/runtime/mirror/class-inl.h @@ -666,7 +666,7 @@ template <bool kVisitClass, typename Visitor> inline void Class::VisitReferences(mirror::Class* klass, const Visitor& visitor) { VisitInstanceFieldsReferences<kVisitClass>(klass, visitor); // Right after a class is allocated, but not yet loaded - // (kStatusNotReady, see ClassLinkder::LoadClass()), GC may find it + // (kStatusNotReady, see ClassLinker::LoadClass()), GC may find it // and scan it. IsTemp() may call Class::GetAccessFlags() but may // fail in the DCHECK in Class::GetAccessFlags() because the class // status is kStatusNotReady. To avoid it, rely on IsResolved() diff --git a/runtime/mirror/string-inl.h b/runtime/mirror/string-inl.h index 9f6cd11c3e..8d9c08d9d5 100644 --- a/runtime/mirror/string-inl.h +++ b/runtime/mirror/string-inl.h @@ -176,11 +176,13 @@ inline String* String::AllocFromByteArray(Thread* self, int32_t byte_length, } template <bool kIsInstrumented> -inline String* String::AllocFromCharArray(Thread* self, int32_t array_length, +inline String* String::AllocFromCharArray(Thread* self, int32_t count, Handle<CharArray> array, int32_t offset, gc::AllocatorType allocator_type) { - SetStringCountAndValueVisitorFromCharArray visitor(array_length, array, offset); - String* new_string = Alloc<kIsInstrumented>(self, array_length, allocator_type, visitor); + // It is a caller error to have a count less than the actual array's size. + DCHECK_GE(array->GetLength(), count); + SetStringCountAndValueVisitorFromCharArray visitor(count, array, offset); + String* new_string = Alloc<kIsInstrumented>(self, count, allocator_type, visitor); return new_string; } diff --git a/runtime/mirror/string.h b/runtime/mirror/string.h index a8f16d78ff..af06385401 100644 --- a/runtime/mirror/string.h +++ b/runtime/mirror/string.h @@ -95,7 +95,7 @@ class MANAGED String FINAL : public Object { SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); template <bool kIsInstrumented> - ALWAYS_INLINE static String* AllocFromCharArray(Thread* self, int32_t array_length, + ALWAYS_INLINE static String* AllocFromCharArray(Thread* self, int32_t count, Handle<CharArray> array, int32_t offset, gc::AllocatorType allocator_type) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc index 94024ef4b2..67dcc9c6af 100644 --- a/runtime/native/java_lang_Class.cc +++ b/runtime/native/java_lang_Class.cc @@ -380,8 +380,8 @@ static jobject Class_getDeclaredMethodInternal(JNIEnv* env, jobject javaThis, static jobjectArray Class_getDeclaredMethodsUnchecked(JNIEnv* env, jobject javaThis, jboolean publicOnly) { ScopedFastNativeObjectAccess soa(env); - StackHandleScope<3> hs(soa.Self()); - auto* klass = DecodeClass(soa, javaThis); + StackHandleScope<2> hs(soa.Self()); + auto klass = hs.NewHandle(DecodeClass(soa, javaThis)); size_t num_methods = 0; for (auto& m : klass->GetVirtualMethods(sizeof(void*))) { auto modifiers = m.GetAccessFlags(); diff --git a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc index b96ddc8102..9ce4a02f1b 100644 --- a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc +++ b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc @@ -38,7 +38,7 @@ static jbyteArray DdmVmInternal_getRecentAllocations(JNIEnv* env, jclass) { } static jboolean DdmVmInternal_getRecentAllocationStatus(JNIEnv*, jclass) { - return Dbg::IsAllocTrackingEnabled(); + return Runtime::Current()->GetHeap()->IsAllocTrackingEnabled(); } /* diff --git a/runtime/read_barrier-inl.h b/runtime/read_barrier-inl.h index d341ee1017..8d84c35bd9 100644 --- a/runtime/read_barrier-inl.h +++ b/runtime/read_barrier-inl.h @@ -31,7 +31,7 @@ namespace art { template <typename MirrorType, ReadBarrierOption kReadBarrierOption, bool kMaybeDuringStartup> inline MirrorType* ReadBarrier::Barrier( mirror::Object* obj, MemberOffset offset, mirror::HeapReference<MirrorType>* ref_addr) { - const bool with_read_barrier = kReadBarrierOption == kWithReadBarrier; + constexpr bool with_read_barrier = kReadBarrierOption == kWithReadBarrier; if (with_read_barrier && kUseBakerReadBarrier) { // The higher bits of the rb ptr, rb_ptr_high_bits (must be zero) // is used to create artificial data dependency from the is_gray diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 9d651bf6a4..66ec7ccf7a 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -402,6 +402,7 @@ void Runtime::SweepSystemWeaks(IsMarkedCallback* visitor, void* arg) { GetInternTable()->SweepInternTableWeaks(visitor, arg); GetMonitorList()->SweepMonitorList(visitor, arg); GetJavaVM()->SweepJniWeakGlobals(visitor, arg); + GetHeap()->SweepAllocationRecords(visitor, arg); } bool Runtime::Create(const RuntimeOptions& options, bool ignore_unrecognized) { @@ -645,6 +646,10 @@ void Runtime::DidForkFromZygote(JNIEnv* env, NativeBridgeAction action, const ch // Create the thread pools. heap_->CreateThreadPool(); + // Reset the gc performance data at zygote fork so that the GCs + // before fork aren't attributed to an app. + heap_->ResetGcPerformanceInfo(); + if (jit_.get() == nullptr && jit_options_->UseJIT()) { // Create the JIT if the flag is set and we haven't already create it (happens for run-tests). CreateJit(); @@ -1471,6 +1476,11 @@ void Runtime::DisallowNewSystemWeaks() { monitor_list_->DisallowNewMonitors(); intern_table_->DisallowNewInterns(); java_vm_->DisallowNewWeakGlobals(); + // TODO: add a similar call for heap.allocation_records_, otherwise some of the newly allocated + // objects that are not marked might be swept from the records, making the records incomplete. + // It is safe for now since the only effect is that those objects do not have allocation records. + // The number of such objects should be small, and current allocation tracker cannot collect + // allocation records for all objects anyway. } void Runtime::AllowNewSystemWeaks() { diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def index 922334ee99..4a307d5ed6 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -30,6 +30,8 @@ // If a default value is omitted here, T{} is used as the default value, which is // almost-always the value of the type as if it was memset to all 0. // +// Please keep the columns aligned if possible when adding new rows. +// // Parse-able keys from the command line. RUNTIME_OPTIONS_KEY (Unit, Zygote) @@ -64,9 +66,9 @@ RUNTIME_OPTIONS_KEY (Unit, IgnoreMaxFootprint) RUNTIME_OPTIONS_KEY (Unit, LowMemoryMode) RUNTIME_OPTIONS_KEY (bool, UseTLAB, (kUseTlab || kUseReadBarrier)) RUNTIME_OPTIONS_KEY (bool, EnableHSpaceCompactForOOM, true) -RUNTIME_OPTIONS_KEY (bool, UseJIT, false) -RUNTIME_OPTIONS_KEY (unsigned int, JITCompileThreshold, jit::Jit::kDefaultCompileThreshold) -RUNTIME_OPTIONS_KEY (MemoryKiB, JITCodeCacheCapacity, jit::JitCodeCache::kDefaultCapacity) +RUNTIME_OPTIONS_KEY (bool, UseJIT, false) +RUNTIME_OPTIONS_KEY (unsigned int, JITCompileThreshold, jit::Jit::kDefaultCompileThreshold) +RUNTIME_OPTIONS_KEY (MemoryKiB, JITCodeCacheCapacity, jit::JitCodeCache::kDefaultCapacity) RUNTIME_OPTIONS_KEY (MillisecondsToNanoseconds, \ HSpaceCompactForOOMMinIntervalsMs,\ MsToNs(100 * 1000)) // 100s @@ -105,9 +107,12 @@ RUNTIME_OPTIONS_KEY (std::vector<std::string>, \ ImageCompilerOptions) // -Ximage-compiler-option ... RUNTIME_OPTIONS_KEY (bool, Verify, true) RUNTIME_OPTIONS_KEY (std::string, NativeBridge) +RUNTIME_OPTIONS_KEY (unsigned int, ZygoteMaxFailedBoots, 10) +RUNTIME_OPTIONS_KEY (Unit, NoDexFileFallback) RUNTIME_OPTIONS_KEY (std::string, CpuAbiList) // Not parse-able from command line, but can be provided explicitly. +// (Do not add anything here that is defined in ParsedOptions::MakeParser) RUNTIME_OPTIONS_KEY (const std::vector<const DexFile*>*, \ BootClassPathDexList) // TODO: make unique_ptr RUNTIME_OPTIONS_KEY (InstructionSet, ImageInstructionSet, kRuntimeISA) @@ -120,7 +125,5 @@ RUNTIME_OPTIONS_KEY (void (*)(int32_t status), \ // We don't call abort(3) by default; see // Runtime::Abort. RUNTIME_OPTIONS_KEY (void (*)(), HookAbort, nullptr) -RUNTIME_OPTIONS_KEY (unsigned int, ZygoteMaxFailedBoots, 10) -RUNTIME_OPTIONS_KEY (Unit, NoDexFileFallback) #undef RUNTIME_OPTIONS_KEY diff --git a/runtime/stack.h b/runtime/stack.h index 79d2f40d73..d60714f7a3 100644 --- a/runtime/stack.h +++ b/runtime/stack.h @@ -95,6 +95,8 @@ class ShadowFrame { } ~ShadowFrame() {} + // TODO(iam): Clean references array up since they're always there, + // we don't need to do conditionals. bool HasReferenceArray() const { return true; } @@ -149,6 +151,9 @@ class ShadowFrame { return *reinterpret_cast<unaligned_double*>(vreg); } + // Look up the reference given its virtual register number. + // If this returns non-null then this does not mean the vreg is currently a reference + // on non-moving collectors. Check that the raw reg with GetVReg is equal to this if not certain. template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> mirror::Object* GetVRegReference(size_t i) const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { DCHECK_LT(i, NumberOfVRegs()); @@ -283,6 +288,8 @@ class ShadowFrame { ShadowFrame(uint32_t num_vregs, ShadowFrame* link, ArtMethod* method, uint32_t dex_pc, bool has_reference_array) : number_of_vregs_(num_vregs), link_(link), method_(method), dex_pc_(dex_pc) { + // TODO(iam): Remove this parameter, it's an an artifact of portable removal + DCHECK(has_reference_array); if (has_reference_array) { memset(vregs_, 0, num_vregs * (sizeof(uint32_t) + sizeof(StackReference<mirror::Object>))); } else { @@ -306,6 +313,15 @@ class ShadowFrame { ShadowFrame* link_; ArtMethod* method_; uint32_t dex_pc_; + + // This is a two-part array: + // - [0..number_of_vregs) holds the raw virtual registers, and each element here is always 4 + // bytes. + // - [number_of_vregs..number_of_vregs*2) holds only reference registers. Each element here is + // ptr-sized. + // In other words when a primitive is stored in vX, the second (reference) part of the array will + // be null. When a reference is stored in vX, the second (reference) part of the array will be a + // copy of vX. uint32_t vregs_[0]; DISALLOW_IMPLICIT_CONSTRUCTORS(ShadowFrame); diff --git a/test/098-ddmc/src/Main.java b/test/098-ddmc/src/Main.java index f41ff2a94a..4914ba2289 100644 --- a/test/098-ddmc/src/Main.java +++ b/test/098-ddmc/src/Main.java @@ -43,14 +43,24 @@ public class Main { System.out.println("Confirm when we overflow, we don't roll over to zero. b/17392248"); final int overflowAllocations = 64 * 1024; // Won't fit in unsigned 16-bit value. + // TODO: Temporary fix. Keep the new objects live so they are not garbage collected. + // This will cause OOM exception for GC stress tests. The root cause is changed behaviour of + // getRecentAllocations(). Working on restoring its old behaviour. b/20037135 + Object[] objects = new Object[overflowAllocations]; for (int i = 0; i < overflowAllocations; i++) { - new Object(); + objects[i] = new Object(); } Allocations after = new Allocations(DdmVmInternal.getRecentAllocations()); System.out.println("before < overflowAllocations=" + (before.numberOfEntries < overflowAllocations)); System.out.println("after > before=" + (after.numberOfEntries > before.numberOfEntries)); System.out.println("after.numberOfEntries=" + after.numberOfEntries); + // TODO: Temporary fix as above. b/20037135 + objects = null; + Runtime.getRuntime().gc(); + final int fillerStrings = 16 * 1024; + String[] strings = new String[fillerStrings]; + System.out.println("Disable and confirm back to empty"); DdmVmInternal.enableRecentAllocations(false); System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus()); @@ -66,8 +76,8 @@ public class Main { System.out.println("Confirm we can reenable twice in a row without losing allocations"); DdmVmInternal.enableRecentAllocations(true); System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus()); - for (int i = 0; i < 16 * 1024; i++) { - new String("fnord"); + for (int i = 0; i < fillerStrings; i++) { + strings[i] = new String("fnord"); } Allocations first = new Allocations(DdmVmInternal.getRecentAllocations()); DdmVmInternal.enableRecentAllocations(true); diff --git a/test/137-cfi/cfi.cc b/test/137-cfi/cfi.cc index b2d7e55214..83f77114f6 100644 --- a/test/137-cfi/cfi.cc +++ b/test/137-cfi/cfi.cc @@ -73,6 +73,13 @@ static bool CheckStack(Backtrace* bt, const std::vector<std::string>& seq) { } } + printf("Can not find %s in backtrace:\n", seq[cur_search_index].c_str()); + for (Backtrace::const_iterator it = bt->begin(); it != bt->end(); ++it) { + if (BacktraceMap::IsValid(it->map)) { + printf(" %s\n", it->func_name.c_str()); + } + } + return false; } #endif @@ -83,8 +90,10 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_unwindInProcess(JNIEnv*, jobject std::unique_ptr<Backtrace> bt(Backtrace::Create(BACKTRACE_CURRENT_PROCESS, GetTid())); if (!bt->Unwind(0, nullptr)) { + printf("Can not unwind in process.\n"); return JNI_FALSE; } else if (bt->NumFrames() == 0) { + printf("No frames for unwind in process.\n"); return JNI_FALSE; } @@ -94,6 +103,7 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_unwindInProcess(JNIEnv*, jobject std::vector<std::string> seq = { "Java_Main_unwindInProcess", // This function. "boolean Main.unwindInProcess(int, boolean)", // The corresponding Java native method frame. + "int java.util.Arrays.binarySearch(java.lang.Object[], int, int, java.lang.Object, java.util.Comparator)", // Framework method. "void Main.main(java.lang.String[])" // The Java entry method. }; @@ -155,6 +165,7 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_unwindOtherProcess(JNIEnv*, jobj if (ptrace(PTRACE_ATTACH, pid, 0, 0)) { // Were not able to attach, bad. + printf("Failed to attach to other process.\n"); PLOG(ERROR) << "Failed to attach."; kill(pid, SIGCONT); return JNI_FALSE; @@ -172,8 +183,10 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_unwindOtherProcess(JNIEnv*, jobj std::unique_ptr<Backtrace> bt(Backtrace::Create(pid, BACKTRACE_CURRENT_THREAD)); bool result = true; if (!bt->Unwind(0, nullptr)) { + printf("Can not unwind other process.\n"); result = false; } else if (bt->NumFrames() == 0) { + printf("No frames for unwind of other process.\n"); result = false; } @@ -185,6 +198,7 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_unwindOtherProcess(JNIEnv*, jobj // Note: For some reason, the name isn't // resolved, so don't look for it right now. "boolean Main.sleep(int, boolean, double)", // The corresponding Java native method frame. + "int java.util.Arrays.binarySearch(java.lang.Object[], int, int, java.lang.Object, java.util.Comparator)", // Framework method. "void Main.main(java.lang.String[])" // The Java entry method. }; diff --git a/test/137-cfi/src/Main.java b/test/137-cfi/src/Main.java index e184e66e6f..658ba53099 100644 --- a/test/137-cfi/src/Main.java +++ b/test/137-cfi/src/Main.java @@ -20,8 +20,10 @@ import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Comparator; -public class Main { +public class Main implements Comparator<Main> { // Whether to test local unwinding. Libunwind uses linker info to find executables. As we do // not dlopen at the moment, this doesn't work, so keep it off for now. public final static boolean TEST_LOCAL_UNWINDING = false; @@ -32,6 +34,8 @@ public class Main { private boolean secondary; + private boolean passed; + public Main(boolean secondary) { this.secondary = secondary; } @@ -60,13 +64,13 @@ public class Main { } private void runSecondary() { - foo(true); + foo(); throw new RuntimeException("Didn't expect to get back..."); } private void runPrimary() { // First do the in-process unwinding. - if (TEST_LOCAL_UNWINDING && !foo(false)) { + if (TEST_LOCAL_UNWINDING && !foo()) { System.out.println("Unwinding self failed."); } @@ -134,8 +138,19 @@ public class Main { } } - public boolean foo(boolean b) { - return bar(b); + public boolean foo() { + // Call bar via Arrays.binarySearch. + // This tests that we can unwind from framework code. + Main[] array = { this, this, this }; + Arrays.binarySearch(array, 0, 3, this /* value */, this /* comparator */); + return passed; + } + + public int compare(Main lhs, Main rhs) { + passed = bar(secondary); + // Returning "equal" ensures that we terminate search + // after first item and thus call bar() only once. + return 0; } public boolean bar(boolean b) { diff --git a/test/444-checker-nce/src/Main.java b/test/444-checker-nce/src/Main.java index 6ac0cad7e8..32122e4dcd 100644 --- a/test/444-checker-nce/src/Main.java +++ b/test/444-checker-nce/src/Main.java @@ -27,37 +27,37 @@ public class Main { return m.g(); } - /// CHECK-START: Main Main.thisTest() instruction_simplifier (before) + /// CHECK-START: Main Main.thisTest() ssa_builder (after) /// CHECK: NullCheck /// CHECK: InvokeStaticOrDirect - /// CHECK-START: Main Main.thisTest() instruction_simplifier (after) + /// CHECK-START: Main Main.thisTest() instruction_simplifier_after_types (after) /// CHECK-NOT: NullCheck /// CHECK: InvokeStaticOrDirect public Main thisTest() { return g(); } - /// CHECK-START: Main Main.newInstanceRemoveTest() instruction_simplifier (before) + /// CHECK-START: Main Main.newInstanceRemoveTest() ssa_builder (after) /// CHECK: NewInstance /// CHECK: NullCheck /// CHECK: InvokeStaticOrDirect /// CHECK: NullCheck /// CHECK: InvokeStaticOrDirect - /// CHECK-START: Main Main.newInstanceRemoveTest() instruction_simplifier (after) + /// CHECK-START: Main Main.newInstanceRemoveTest() instruction_simplifier_after_types (after) /// CHECK-NOT: NullCheck public Main newInstanceRemoveTest() { Main m = new Main(); return m.g(); } - /// CHECK-START: Main Main.newArrayRemoveTest() instruction_simplifier (before) + /// CHECK-START: Main Main.newArrayRemoveTest() ssa_builder (after) /// CHECK: NewArray /// CHECK: NullCheck /// CHECK: ArrayGet - /// CHECK-START: Main Main.newArrayRemoveTest() instruction_simplifier (after) + /// CHECK-START: Main Main.newArrayRemoveTest() instruction_simplifier_after_types (after) /// CHECK: NewArray /// CHECK-NOT: NullCheck /// CHECK: ArrayGet @@ -178,10 +178,10 @@ public class Main { return n.g(); } - /// CHECK-START: Main Main.scopeRemoveTest(int, Main) instruction_simplifier (before) + /// CHECK-START: Main Main.scopeRemoveTest(int, Main) ssa_builder (after) /// CHECK: NullCheck - /// CHECK-START: Main Main.scopeRemoveTest(int, Main) instruction_simplifier (after) + /// CHECK-START: Main Main.scopeRemoveTest(int, Main) instruction_simplifier_after_types (after) /// CHECK-NOT: NullCheck public Main scopeRemoveTest(int count, Main a) { Main m = null; diff --git a/test/449-checker-bce/src/Main.java b/test/449-checker-bce/src/Main.java index 8960df896b..ed6fc1ee2b 100644 --- a/test/449-checker-bce/src/Main.java +++ b/test/449-checker-bce/src/Main.java @@ -617,15 +617,21 @@ public class Main { /// CHECK: ArrayGet /// CHECK-START: void Main.foo1(int[], int, int) BCE (after) - /// CHECK: Deoptimize - /// CHECK: Deoptimize - /// CHECK: Deoptimize - /// CHECK-NOT: Deoptimize /// CHECK: Phi /// CHECK-NOT: BoundsCheck /// CHECK: ArraySet /// CHECK-NOT: BoundsCheck /// CHECK: ArrayGet + // Added blocks for deoptimization. + /// CHECK: If + /// CHECK: Goto + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK-NOT: Deoptimize + /// CHECK: Goto + /// CHECK: Phi + /// CHECK: Goto void foo1(int[] array, int start, int end) { // Three HDeoptimize will be added. One for @@ -646,15 +652,21 @@ public class Main { /// CHECK: ArrayGet /// CHECK-START: void Main.foo2(int[], int, int) BCE (after) - /// CHECK: Deoptimize - /// CHECK: Deoptimize - /// CHECK: Deoptimize - /// CHECK-NOT: Deoptimize /// CHECK: Phi /// CHECK-NOT: BoundsCheck /// CHECK: ArraySet /// CHECK-NOT: BoundsCheck /// CHECK: ArrayGet + // Added blocks for deoptimization. + /// CHECK: If + /// CHECK: Goto + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK-NOT: Deoptimize + /// CHECK: Goto + /// CHECK: Phi + /// CHECK: Goto void foo2(int[] array, int start, int end) { // Three HDeoptimize will be added. One for @@ -675,14 +687,20 @@ public class Main { /// CHECK: ArrayGet /// CHECK-START: void Main.foo3(int[], int) BCE (after) - /// CHECK: Deoptimize - /// CHECK: Deoptimize - /// CHECK-NOT: Deoptimize /// CHECK: Phi /// CHECK-NOT: BoundsCheck /// CHECK: ArraySet /// CHECK-NOT: BoundsCheck /// CHECK: ArrayGet + // Added blocks for deoptimization. + /// CHECK: If + /// CHECK: Goto + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK-NOT: Deoptimize + /// CHECK: Goto + /// CHECK: Phi + /// CHECK: Goto void foo3(int[] array, int end) { // Two HDeoptimize will be added. One for end < array.length, @@ -694,6 +712,7 @@ public class Main { } } + /// CHECK-START: void Main.foo4(int[], int) BCE (before) /// CHECK: BoundsCheck /// CHECK: ArraySet @@ -701,14 +720,20 @@ public class Main { /// CHECK: ArrayGet /// CHECK-START: void Main.foo4(int[], int) BCE (after) - /// CHECK: Deoptimize - /// CHECK: Deoptimize - /// CHECK-NOT: Deoptimize /// CHECK: Phi /// CHECK-NOT: BoundsCheck /// CHECK: ArraySet /// CHECK-NOT: BoundsCheck /// CHECK: ArrayGet + // Added blocks for deoptimization. + /// CHECK: If + /// CHECK: Goto + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK-NOT: Deoptimize + /// CHECK: Goto + /// CHECK: Phi + /// CHECK: Goto void foo4(int[] array, int end) { // Two HDeoptimize will be added. One for end <= array.length, @@ -734,8 +759,6 @@ public class Main { /// CHECK-START: void Main.foo5(int[], int) BCE (after) /// CHECK-NOT: BoundsCheck /// CHECK: ArraySet - /// CHECK: Deoptimize - /// CHECK-NOT: Deoptimize /// CHECK: Phi /// CHECK-NOT: BoundsCheck /// CHECK: ArrayGet @@ -743,6 +766,15 @@ public class Main { /// CHECK: ArrayGet /// CHECK-NOT: BoundsCheck /// CHECK: ArrayGet + // Added blocks for deoptimization. + /// CHECK: If + /// CHECK: Goto + /// CHECK: Deoptimize + /// CHECK-NOT: Deoptimize + /// CHECK: Goto + // array.length is defined before the loop header so no phi is needed. + /// CHECK-NOT: Phi + /// CHECK: Goto void foo5(int[] array, int end) { // Bounds check in this loop can be eliminated without deoptimization. @@ -774,10 +806,6 @@ public class Main { /// CHECK: ArraySet /// CHECK-START: void Main.foo6(int[], int, int) BCE (after) - /// CHECK: Deoptimize - /// CHECK: Deoptimize - /// CHECK: Deoptimize - /// CHECK-NOT: Deoptimize /// CHECK: Phi /// CHECK-NOT: BoundsCheck /// CHECK: ArrayGet @@ -791,6 +819,17 @@ public class Main { /// CHECK: ArrayGet /// CHECK-NOT: BoundsCheck /// CHECK: ArraySet + // Added blocks for deoptimization. + /// CHECK: If + /// CHECK: Goto + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK-NOT: Deoptimize + /// CHECK: Goto + /// CHECK: Phi + /// CHECK: Goto + /// CHECK-NOT: Deoptimize void foo6(int[] array, int start, int end) { // Three HDeoptimize will be added. One for @@ -810,15 +849,21 @@ public class Main { /// CHECK: ArrayGet /// CHECK-START: void Main.foo7(int[], int, int, boolean) BCE (after) - /// CHECK: Deoptimize - /// CHECK: Deoptimize - /// CHECK: Deoptimize - /// CHECK-NOT: Deoptimize /// CHECK: Phi /// CHECK: BoundsCheck /// CHECK: ArrayGet /// CHECK-NOT: BoundsCheck /// CHECK: ArrayGet + // Added blocks for deoptimization. + /// CHECK: If + /// CHECK: Goto + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK-NOT: Deoptimize + /// CHECK: Goto + /// CHECK: Phi + /// CHECK: Goto void foo7(int[] array, int start, int end, boolean lowEnd) { // Three HDeoptimize will be added. One for @@ -837,6 +882,73 @@ public class Main { } + /// CHECK-START: void Main.foo8(int[][], int, int) BCE (before) + /// CHECK: BoundsCheck + /// CHECK: ArrayGet + /// CHECK: BoundsCheck + /// CHECK: ArraySet + + /// CHECK-START: void Main.foo8(int[][], int, int) BCE (after) + /// CHECK: Phi + /// CHECK-NOT: BoundsCheck + /// CHECK: ArrayGet + /// CHECK: Phi + /// CHECK-NOT: BoundsCheck + /// CHECK: ArraySet + // Added blocks for deoptimization. + /// CHECK: If + /// CHECK: Goto + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK-NOT: Deoptimize + /// CHECK: Goto + /// CHECK: Phi + /// CHECK: Goto + + void foo8(int[][] matrix, int start, int end) { + // Three HDeoptimize will be added for the outer loop. + // start >= 0, end <= matrix.length, and null check on matrix. + // Three HDeoptimize will be added for the inner loop + // start >= 0 (TODO: this may be optimized away), + // end <= row.length, and null check on row. + for (int i = start; i < end; i++) { + int[] row = matrix[i]; + for (int j = start; j < end; j++) { + row[j] = 1; + } + } + } + + + /// CHECK-START: void Main.foo9(int[]) BCE (before) + /// CHECK: NullCheck + /// CHECK: BoundsCheck + /// CHECK: ArrayGet + + /// CHECK-START: void Main.foo9(int[]) BCE (after) + // The loop is guaranteed to be entered. No need to transform the + // loop for loop body entry test. + /// CHECK: Deoptimize + /// CHECK: Deoptimize + /// CHECK-NOT: Deoptimize + /// CHECK: Phi + /// CHECK-NOT: NullCheck + /// CHECK-NOT: BoundsCheck + /// CHECK: ArrayGet + + void foo9(int[] array) { + // Two HDeoptimize will be added. One for + // 10 <= array.length, and one for null check on array. + for (int i = 0 ; i < 10; i++) { + sum += array[i]; + } + } + + /// CHECK-START: void Main.partialLooping(int[], int, int) BCE (before) /// CHECK: BoundsCheck /// CHECK: ArraySet @@ -951,6 +1063,13 @@ public class Main { main.foo6(new int[10], 2, 7); main = new Main(); + int[] array9 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + main.foo9(array9); + if (main.sum != 45) { + System.out.println("foo9 failed!"); + } + + main = new Main(); int[] array = new int[4]; main.partialLooping(new int[3], 0, 4); if ((array[0] != 1) && (array[1] != 1) && diff --git a/test/450-checker-types/src/Main.java b/test/450-checker-types/src/Main.java index 4056275d3d..9fb1e367b0 100644 --- a/test/450-checker-types/src/Main.java +++ b/test/450-checker-types/src/Main.java @@ -364,6 +364,18 @@ public class Main { ((SubclassA)b).$noinline$g(); } + public SubclassA $noinline$getSubclass() { throw new RuntimeException(); } + + /// CHECK-START: void Main.testInvokeSimpleRemove() instruction_simplifier_after_types (before) + /// CHECK: CheckCast + + /// CHECK-START: void Main.testInvokeSimpleRemove() instruction_simplifier_after_types (after) + /// CHECK-NOT: CheckCast + public void testInvokeSimpleRemove() { + Super b = $noinline$getSubclass(); + ((SubclassA)b).$noinline$g(); + } + public static void main(String[] args) { } } diff --git a/test/458-checker-instruction-simplification/src/Main.java b/test/458-checker-instruction-simplification/src/Main.java index ad5fc8ef93..ef18f64a37 100644 --- a/test/458-checker-instruction-simplification/src/Main.java +++ b/test/458-checker-instruction-simplification/src/Main.java @@ -933,18 +933,18 @@ public class Main { * remove the second. */ - /// CHECK-START: boolean Main.NotNotBool(boolean) instruction_simplifier_after_types (before) + /// CHECK-START: boolean Main.NotNotBool(boolean) last_instruction_simplifier (before) /// CHECK-DAG: <<Arg:z\d+>> ParameterValue /// CHECK-DAG: <<NotArg:z\d+>> BooleanNot [<<Arg>>] /// CHECK-DAG: <<NotNotArg:z\d+>> BooleanNot [<<NotArg>>] /// CHECK-DAG: Return [<<NotNotArg>>] - /// CHECK-START: boolean Main.NotNotBool(boolean) instruction_simplifier_after_types (after) + /// CHECK-START: boolean Main.NotNotBool(boolean) last_instruction_simplifier (after) /// CHECK-DAG: <<Arg:z\d+>> ParameterValue /// CHECK-DAG: BooleanNot [<<Arg>>] /// CHECK-DAG: Return [<<Arg>>] - /// CHECK-START: boolean Main.NotNotBool(boolean) instruction_simplifier_after_types (after) + /// CHECK-START: boolean Main.NotNotBool(boolean) last_instruction_simplifier (after) /// CHECK: BooleanNot /// CHECK-NOT: BooleanNot diff --git a/test/490-checker-inline/expected.txt b/test/490-checker-inline/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/490-checker-inline/expected.txt diff --git a/test/490-checker-inline/info.txt b/test/490-checker-inline/info.txt new file mode 100644 index 0000000000..0e42d771fe --- /dev/null +++ b/test/490-checker-inline/info.txt @@ -0,0 +1 @@ +Check that we inline virtual and interface calls. diff --git a/test/490-checker-inline/src/Main.java b/test/490-checker-inline/src/Main.java new file mode 100644 index 0000000000..21a01897e2 --- /dev/null +++ b/test/490-checker-inline/src/Main.java @@ -0,0 +1,52 @@ +/* + * 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. + */ + +interface Itf { + public void invokeInterface(); +} + +public class Main implements Itf { + + public void invokeInterface () { + } + + public void invokeVirtual() { + } + + public static Main createMain() { + return new Main(); + } + + public static Itf createItf() { + return new Main(); + } + + /// CHECK-START: void Main.testMethod() inliner (before) + /// CHECK-DAG: InvokeVirtual + /// CHECK-DAG: InvokeInterface + + /// CHECK-START: void Main.testMethod() inliner (after) + /// CHECK-NOT: Invoke{{.*}} + + public static void testMethod() { + createMain().invokeVirtual(); + createItf().invokeInterface(); + } + + public static void main(String[] args) { + testMethod(); + } +} diff --git a/test/493-checker-inline-invoke-interface/expected.txt b/test/493-checker-inline-invoke-interface/expected.txt new file mode 100644 index 0000000000..93620a6fb5 --- /dev/null +++ b/test/493-checker-inline-invoke-interface/expected.txt @@ -0,0 +1,5 @@ +Hello from clinit +java.lang.Exception + at ForceStatic.<clinit>(Main.java:24) + at Main.foo(Main.java:31) + at Main.main(Main.java:42) diff --git a/test/493-checker-inline-invoke-interface/info.txt b/test/493-checker-inline-invoke-interface/info.txt new file mode 100644 index 0000000000..bac9c82c9d --- /dev/null +++ b/test/493-checker-inline-invoke-interface/info.txt @@ -0,0 +1,2 @@ +Check that we can optimize interface calls without +requiring the verifier to sharpen them. diff --git a/test/493-checker-inline-invoke-interface/src/Main.java b/test/493-checker-inline-invoke-interface/src/Main.java new file mode 100644 index 0000000000..44b727fe55 --- /dev/null +++ b/test/493-checker-inline-invoke-interface/src/Main.java @@ -0,0 +1,48 @@ +/* + * 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. + */ + +interface Itf { + public void foo(); +} + +class ForceStatic { + static { + System.out.println("Hello from clinit"); + new Exception().printStackTrace(); + } + static int field; +} + +public class Main implements Itf { + public void foo() { + int a = ForceStatic.field; + } + + /// CHECK-START: void Main.main(java.lang.String[]) inliner (before) + /// CHECK: InvokeStaticOrDirect + /// CHECK: InvokeInterface + + /// CHECK-START: void Main.main(java.lang.String[]) inliner (after) + /// CHECK-NOT: Invoke{{.*}} + public static void main(String[] args) { + Itf itf = bar(); + itf.foo(); + } + + public static Itf bar() { + return new Main(); + } +} diff --git a/test/494-checker-instanceof-tests/expected.txt b/test/494-checker-instanceof-tests/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/494-checker-instanceof-tests/expected.txt diff --git a/test/494-checker-instanceof-tests/info.txt b/test/494-checker-instanceof-tests/info.txt new file mode 100644 index 0000000000..59e20bd6a9 --- /dev/null +++ b/test/494-checker-instanceof-tests/info.txt @@ -0,0 +1 @@ +Checker test for optimizations on instanceof. diff --git a/test/494-checker-instanceof-tests/src/Main.java b/test/494-checker-instanceof-tests/src/Main.java new file mode 100644 index 0000000000..bff9c72ded --- /dev/null +++ b/test/494-checker-instanceof-tests/src/Main.java @@ -0,0 +1,203 @@ +/* + * 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 boolean $inline$classTypeTest(Object o) { + return o instanceof SubMain; + } + + public static boolean $inline$interfaceTypeTest(Object o) { + return o instanceof Itf; + } + + public static SubMain subMain; + public static Main mainField; + public static Unrelated unrelatedField; + public static FinalUnrelated finalUnrelatedField; + + /// CHECK-START: boolean Main.classTypeTestNull() register (after) + /// CHECK-DAG: <<Const:i\d+>> IntConstant 0 + /// CHECK-DAG: Return [<<Const>>] + public static boolean classTypeTestNull() { + return $inline$classTypeTest(null); + } + + /// CHECK-START: boolean Main.classTypeTestExactMain() register (after) + /// CHECK-DAG: <<Const:i\d+>> IntConstant 0 + /// CHECK-DAG: Return [<<Const>>] + public static boolean classTypeTestExactMain() { + return $inline$classTypeTest(new Main()); + } + + /// CHECK-START: boolean Main.classTypeTestExactSubMain() register (after) + /// CHECK-DAG: <<Const:i\d+>> IntConstant 1 + /// CHECK-DAG: Return [<<Const>>] + public static boolean classTypeTestExactSubMain() { + return $inline$classTypeTest(new SubMain()); + } + + /// CHECK-START: boolean Main.classTypeTestSubMainOrNull() register (after) + /// CHECK-DAG: <<Value:z\d+>> NotEqual + /// CHECK-DAG: Return [<<Value>>] + public static boolean classTypeTestSubMainOrNull() { + return $inline$classTypeTest(subMain); + } + + /// CHECK-START: boolean Main.classTypeTestMainOrNull() register (after) + /// CHECK-DAG: <<Value:z\d+>> InstanceOf + /// CHECK-DAG: Return [<<Value>>] + public static boolean classTypeTestMainOrNull() { + return $inline$classTypeTest(mainField); + } + + /// CHECK-START: boolean Main.classTypeTestUnrelated() register (after) + /// CHECK-DAG: <<Const:i\d+>> IntConstant 0 + /// CHECK-DAG: Return [<<Const>>] + public static boolean classTypeTestUnrelated() { + return $inline$classTypeTest(unrelatedField); + } + + /// CHECK-START: boolean Main.classTypeTestFinalUnrelated() register (after) + /// CHECK-DAG: <<Const:i\d+>> IntConstant 0 + /// CHECK-DAG: Return [<<Const>>] + public static boolean classTypeTestFinalUnrelated() { + return $inline$classTypeTest(finalUnrelatedField); + } + + /// CHECK-START: boolean Main.interfaceTypeTestNull() register (after) + /// CHECK-DAG: <<Const:i\d+>> IntConstant 0 + /// CHECK-DAG: Return [<<Const>>] + public static boolean interfaceTypeTestNull() { + return $inline$interfaceTypeTest(null); + } + + /// CHECK-START: boolean Main.interfaceTypeTestExactMain() register (after) + /// CHECK-DAG: <<Const:i\d+>> IntConstant 0 + /// CHECK-DAG: Return [<<Const>>] + public static boolean interfaceTypeTestExactMain() { + return $inline$interfaceTypeTest(new Main()); + } + + /// CHECK-START: boolean Main.interfaceTypeTestExactSubMain() register (after) + /// CHECK-DAG: <<Const:i\d+>> IntConstant 1 + /// CHECK-DAG: Return [<<Const>>] + public static boolean interfaceTypeTestExactSubMain() { + return $inline$interfaceTypeTest(new SubMain()); + } + + /// CHECK-START: boolean Main.interfaceTypeTestSubMainOrNull() register (after) + /// CHECK-DAG: <<Value:z\d+>> NotEqual + /// CHECK-DAG: Return [<<Value>>] + public static boolean interfaceTypeTestSubMainOrNull() { + return $inline$interfaceTypeTest(subMain); + } + + /// CHECK-START: boolean Main.interfaceTypeTestMainOrNull() register (after) + /// CHECK-DAG: <<Value:z\d+>> InstanceOf + /// CHECK-DAG: Return [<<Value>>] + public static boolean interfaceTypeTestMainOrNull() { + return $inline$interfaceTypeTest(mainField); + } + + /// CHECK-START: boolean Main.interfaceTypeTestUnrelated() register (after) + /// CHECK-DAG: <<Value:z\d+>> InstanceOf + /// CHECK-DAG: Return [<<Value>>] + public static boolean interfaceTypeTestUnrelated() { + // This method is the main difference between doing an instanceof on an interface + // or a class. We have to keep the instanceof in case a subclass of Unrelated + // implements the interface. + return $inline$interfaceTypeTest(unrelatedField); + } + + /// CHECK-START: boolean Main.interfaceTypeTestFinalUnrelated() register (after) + /// CHECK-DAG: <<Const:i\d+>> IntConstant 0 + /// CHECK-DAG: Return [<<Const>>] + public static boolean interfaceTypeTestFinalUnrelated() { + return $inline$interfaceTypeTest(finalUnrelatedField); + } + + public static void expect(boolean expected, boolean actual) { + if (expected != actual) { + throw new Error("Unexpected result"); + } + } + + public static void main(String[] args) { + expect(false, classTypeTestNull()); + expect(false, classTypeTestExactMain()); + expect(true, classTypeTestExactSubMain()); + + subMain = null; + expect(false, classTypeTestSubMainOrNull()); + subMain = new SubMain(); + expect(true, classTypeTestSubMainOrNull()); + + mainField = null; + expect(false, classTypeTestMainOrNull()); + mainField = new Main(); + expect(false, classTypeTestMainOrNull()); + mainField = new SubMain(); + expect(true, classTypeTestMainOrNull()); + + unrelatedField = null; + expect(false, classTypeTestUnrelated()); + unrelatedField = new Unrelated(); + expect(false, classTypeTestUnrelated()); + + finalUnrelatedField = null; + expect(false, classTypeTestFinalUnrelated()); + finalUnrelatedField = new FinalUnrelated(); + expect(false, classTypeTestFinalUnrelated()); + + expect(false, interfaceTypeTestNull()); + expect(false, interfaceTypeTestExactMain()); + expect(true, interfaceTypeTestExactSubMain()); + + subMain = null; + expect(false, interfaceTypeTestSubMainOrNull()); + subMain = new SubMain(); + expect(true, interfaceTypeTestSubMainOrNull()); + + mainField = null; + expect(false, interfaceTypeTestMainOrNull()); + mainField = new Main(); + expect(false, interfaceTypeTestMainOrNull()); + mainField = new SubMain(); + expect(true, interfaceTypeTestMainOrNull()); + + unrelatedField = null; + expect(false, interfaceTypeTestUnrelated()); + unrelatedField = new Unrelated(); + expect(false, interfaceTypeTestUnrelated()); + + finalUnrelatedField = null; + expect(false, interfaceTypeTestFinalUnrelated()); + finalUnrelatedField = new FinalUnrelated(); + expect(false, interfaceTypeTestFinalUnrelated()); + } +} + +interface Itf { +} + +class SubMain extends Main implements Itf { +} + +class Unrelated { +} + +final class FinalUnrelated { +} diff --git a/test/495-checker-checkcast-tests/expected.txt b/test/495-checker-checkcast-tests/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/495-checker-checkcast-tests/expected.txt diff --git a/test/495-checker-checkcast-tests/info.txt b/test/495-checker-checkcast-tests/info.txt new file mode 100644 index 0000000000..4517b22c69 --- /dev/null +++ b/test/495-checker-checkcast-tests/info.txt @@ -0,0 +1 @@ +Checker tests for optimizations on checkcast. diff --git a/test/495-checker-checkcast-tests/src/Main.java b/test/495-checker-checkcast-tests/src/Main.java new file mode 100644 index 0000000000..aa6d5a75f7 --- /dev/null +++ b/test/495-checker-checkcast-tests/src/Main.java @@ -0,0 +1,204 @@ +/* + * 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 boolean $inline$classTypeTest(Object o) { + return ((SubMain)o) == o; + } + + public static boolean $inline$interfaceTypeTest(Object o) { + return ((Itf)o) == o; + } + + public static SubMain subMain; + public static Main mainField; + public static Unrelated unrelatedField; + public static FinalUnrelated finalUnrelatedField; + + /// CHECK-START: boolean Main.classTypeTestNull() register (after) + /// CHECK-NOT: CheckCast + public static boolean classTypeTestNull() { + return $inline$classTypeTest(null); + } + + /// CHECK-START: boolean Main.classTypeTestExactMain() register (after) + /// CHECK: CheckCast + public static boolean classTypeTestExactMain() { + return $inline$classTypeTest(new Main()); + } + + /// CHECK-START: boolean Main.classTypeTestExactSubMain() register (after) + /// CHECK-NOT: CheckCast + public static boolean classTypeTestExactSubMain() { + return $inline$classTypeTest(new SubMain()); + } + + /// CHECK-START: boolean Main.classTypeTestSubMainOrNull() register (after) + /// CHECK-NOT: CheckCast + public static boolean classTypeTestSubMainOrNull() { + return $inline$classTypeTest(subMain); + } + + /// CHECK-START: boolean Main.classTypeTestMainOrNull() register (after) + /// CHECK: CheckCast + public static boolean classTypeTestMainOrNull() { + return $inline$classTypeTest(mainField); + } + + /// CHECK-START: boolean Main.classTypeTestUnrelated() register (after) + /// CHECK: CheckCast + public static boolean classTypeTestUnrelated() { + return $inline$classTypeTest(unrelatedField); + } + + /// CHECK-START: boolean Main.classTypeTestFinalUnrelated() register (after) + /// CHECK: CheckCast + public static boolean classTypeTestFinalUnrelated() { + return $inline$classTypeTest(finalUnrelatedField); + } + + /// CHECK-START: boolean Main.interfaceTypeTestNull() register (after) + /// CHECK-NOT: CheckCast + public static boolean interfaceTypeTestNull() { + return $inline$interfaceTypeTest(null); + } + + /// CHECK-START: boolean Main.interfaceTypeTestExactMain() register (after) + /// CHECK: CheckCast + public static boolean interfaceTypeTestExactMain() { + return $inline$interfaceTypeTest(new Main()); + } + + /// CHECK-START: boolean Main.interfaceTypeTestExactSubMain() register (after) + /// CHECK-NOT: CheckCast + public static boolean interfaceTypeTestExactSubMain() { + return $inline$interfaceTypeTest(new SubMain()); + } + + /// CHECK-START: boolean Main.interfaceTypeTestSubMainOrNull() register (after) + /// CHECK-NOT: CheckCast + public static boolean interfaceTypeTestSubMainOrNull() { + return $inline$interfaceTypeTest(subMain); + } + + /// CHECK-START: boolean Main.interfaceTypeTestMainOrNull() register (after) + /// CHECK: CheckCast + public static boolean interfaceTypeTestMainOrNull() { + return $inline$interfaceTypeTest(mainField); + } + + /// CHECK-START: boolean Main.interfaceTypeTestUnrelated() register (after) + /// CHECK: CheckCast + public static boolean interfaceTypeTestUnrelated() { + return $inline$interfaceTypeTest(unrelatedField); + } + + /// CHECK-START: boolean Main.interfaceTypeTestFinalUnrelated() register (after) + /// CHECK: CheckCast + public static boolean interfaceTypeTestFinalUnrelated() { + return $inline$interfaceTypeTest(finalUnrelatedField); + } + + public static void main(String[] args) { + classTypeTestNull(); + try { + classTypeTestExactMain(); + throw new Error("ClassCastException expected"); + } catch (ClassCastException e) {} + classTypeTestExactSubMain(); + + subMain = null; + classTypeTestSubMainOrNull(); + subMain = new SubMain(); + classTypeTestSubMainOrNull(); + + mainField = null; + classTypeTestMainOrNull(); + mainField = new Main(); + try { + classTypeTestMainOrNull(); + throw new Error("ClassCastException expected"); + } catch (ClassCastException e) {} + mainField = new SubMain(); + classTypeTestMainOrNull(); + + unrelatedField = null; + classTypeTestUnrelated(); + unrelatedField = new Unrelated(); + try { + classTypeTestUnrelated(); + throw new Error("ClassCastException expected"); + } catch (ClassCastException e) {} + + finalUnrelatedField = null; + classTypeTestFinalUnrelated(); + finalUnrelatedField = new FinalUnrelated(); + try { + classTypeTestFinalUnrelated(); + throw new Error("ClassCastException expected"); + } catch (ClassCastException e) {} + + interfaceTypeTestNull(); + try { + interfaceTypeTestExactMain(); + throw new Error("ClassCastException expected"); + } catch (ClassCastException e) {} + interfaceTypeTestExactSubMain(); + + subMain = null; + interfaceTypeTestSubMainOrNull(); + subMain = new SubMain(); + interfaceTypeTestSubMainOrNull(); + + mainField = null; + interfaceTypeTestMainOrNull(); + mainField = new Main(); + try { + interfaceTypeTestMainOrNull(); + throw new Error("ClassCastException expected"); + } catch (ClassCastException e) {} + mainField = new SubMain(); + interfaceTypeTestMainOrNull(); + + unrelatedField = null; + interfaceTypeTestUnrelated(); + unrelatedField = new Unrelated(); + try { + interfaceTypeTestUnrelated(); + throw new Error("ClassCastException expected"); + } catch (ClassCastException e) {} + + finalUnrelatedField = null; + interfaceTypeTestFinalUnrelated(); + finalUnrelatedField = new FinalUnrelated(); + try { + interfaceTypeTestFinalUnrelated(); + throw new Error("ClassCastException expected"); + } catch (ClassCastException e) {} + } +} + +interface Itf { +} + +class SubMain extends Main implements Itf { +} + +class Unrelated { +} + +final class FinalUnrelated { +} diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index e95f147e65..b948e4b1b0 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -229,16 +229,10 @@ endif TEST_ART_BROKEN_NO_RELOCATE_TESTS := -# Tests that are broken with GC stress. -TEST_ART_BROKEN_GCSTRESS_RUN_TESTS := - -ifneq (,$(filter gcstress,$(GC_TYPES))) - ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \ - $(COMPILER_TYPES),$(RELOCATE_TYPES),$(TRACE_TYPES),gcstress,$(JNI_TYPES), \ - $(IMAGE_TYPES), $(PICTEST_TYPES), $(DBEUGGABLE_TYPES), $(TEST_ART_BROKEN_GCSTRESS_RUN_TESTS), $(ALL_ADDRESS_SIZES)) -endif - -TEST_ART_BROKEN_GCSTRESS_RUN_TESTS := +# 098-ddmc is broken until we restore the old behavior of getRecentAllocation() of DDMS. b/20037135 +ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \ + $(COMPILER_TYPES),$(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES), \ + $(IMAGE_TYPES), $(PICTEST_TYPES), $(DEBUGGABLE_TYPES), 098-ddmc, $(ALL_ADDRESS_SIZES)) # 115-native-bridge setup is complicated. Need to implement it correctly for the target. ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,target,$(RUN_TYPES),$(PREBUILD_TYPES),$(COMPILER_TYPES), \ diff --git a/test/run-test b/test/run-test index ed3309923b..ed033217b8 100755 --- a/test/run-test +++ b/test/run-test @@ -96,6 +96,7 @@ basic_verify="false" gc_verify="false" gc_stress="false" always_clean="no" +never_clean="no" have_dex2oat="yes" have_patchoat="yes" have_image="yes" @@ -270,6 +271,9 @@ while true; do elif [ "x$1" = "x--always-clean" ]; then always_clean="yes" shift + elif [ "x$1" = "x--never-clean" ]; then + never_clean="yes" + shift elif [ "x$1" = "x--dex2oat-swap" ]; then run_args="${run_args} --dex2oat-swap" shift @@ -472,6 +476,7 @@ if [ "$usage" = "yes" ]; then echo " --gcstress Run with gc stress testing" echo " --gcverify Run with gc verification" echo " --always-clean Delete the test files even if the test fails." + echo " --never-clean Keep the test files even if the test succeeds." echo " --android-root [path] The path on target for the android root. (/system by default)." echo " --dex2oat-swap Use a dex2oat swap file." ) 1>&2 @@ -668,7 +673,7 @@ fi ) 1>&2 # Clean up test files. -if [ "$always_clean" = "yes" -o "$good" = "yes" ]; then +if [ "$always_clean" = "yes" -o "$good" = "yes" ] && [ "$never_clean" = "no" ]; then cd "$oldwd" rm -rf "$tmp_dir" if [ "$target_mode" = "yes" -a "$build_exit" = "0" ]; then diff --git a/tools/buildbot-build.sh b/tools/buildbot-build.sh index 77e6b1ad14..b84ae473f0 100755 --- a/tools/buildbot-build.sh +++ b/tools/buildbot-build.sh @@ -19,7 +19,7 @@ if [ ! -d art ]; then exit 1 fi -common_targets="vogar vogar.jar core-tests apache-harmony-jdwp-tests-hostdex out/host/linux-x86/bin/adb jsr166-tests" +common_targets="vogar vogar.jar core-tests apache-harmony-jdwp-tests-hostdex out/host/linux-x86/bin/adb jsr166-tests libjavacoretests" android_root="/data/local/tmp/system" linker="linker" mode="target" diff --git a/tools/run-libcore-tests.sh b/tools/run-libcore-tests.sh index 344d2dedb3..4e76eb4354 100755 --- a/tools/run-libcore-tests.sh +++ b/tools/run-libcore-tests.sh @@ -33,7 +33,8 @@ if [ ! -f $test_jar ]; then fi # Packages that currently work correctly with the expectation files. -working_packages=("libcore.icu" +working_packages=("dalvik.system" + "libcore.icu" "libcore.io" "libcore.java.lang" "libcore.java.math" |