| /* |
| * 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 <sys/mman.h> |
| |
| #include "common_runtime_test.h" |
| #include "gc/collector/immune_spaces.h" |
| #include "gc/space/image_space.h" |
| #include "gc/space/space-inl.h" |
| #include "oat_file.h" |
| #include "thread-current-inl.h" |
| |
| namespace art { |
| namespace mirror { |
| class Object; |
| } // namespace mirror |
| namespace gc { |
| namespace collector { |
| |
| class DummyOatFile : public OatFile { |
| public: |
| DummyOatFile(uint8_t* begin, uint8_t* end) : OatFile("Location", /*executable=*/ false) { |
| begin_ = begin; |
| end_ = end; |
| } |
| }; |
| |
| class DummyImageSpace : public space::ImageSpace { |
| public: |
| DummyImageSpace(MemMap&& map, |
| accounting::ContinuousSpaceBitmap&& live_bitmap, |
| std::unique_ptr<DummyOatFile>&& oat_file, |
| MemMap&& oat_map) |
| : ImageSpace("DummyImageSpace", |
| /*image_location=*/"", |
| std::move(map), |
| std::move(live_bitmap), |
| map.End()), |
| oat_map_(std::move(oat_map)) { |
| oat_file_ = std::move(oat_file); |
| oat_file_non_owned_ = oat_file_.get(); |
| } |
| |
| private: |
| MemMap oat_map_; |
| }; |
| |
| class ImmuneSpacesTest : public CommonRuntimeTest { |
| static constexpr size_t kMaxBitmaps = 10; |
| |
| public: |
| ImmuneSpacesTest() {} |
| |
| void ReserveBitmaps() { |
| // Create a bunch of dummy bitmaps since these are required to create image spaces. The bitmaps |
| // do not need to cover the image spaces though. |
| for (size_t i = 0; i < kMaxBitmaps; ++i) { |
| accounting::ContinuousSpaceBitmap bitmap( |
| accounting::ContinuousSpaceBitmap::Create("bitmap", |
| reinterpret_cast<uint8_t*>(kPageSize), |
| kPageSize)); |
| CHECK(bitmap.IsValid()); |
| live_bitmaps_.push_back(std::move(bitmap)); |
| } |
| } |
| |
| // Create an image space, the oat file is optional. |
| DummyImageSpace* CreateImageSpace(size_t image_size, |
| size_t oat_size, |
| MemMap* image_reservation, |
| MemMap* oat_reservation) { |
| DCHECK(image_reservation != nullptr); |
| DCHECK(oat_reservation != nullptr); |
| std::string error_str; |
| MemMap image_map = MemMap::MapAnonymous("DummyImageSpace", |
| image_size, |
| PROT_READ | PROT_WRITE, |
| /*low_4gb=*/ true, |
| /*reservation=*/ image_reservation, |
| &error_str); |
| if (!image_map.IsValid()) { |
| LOG(ERROR) << error_str; |
| return nullptr; |
| } |
| CHECK(!live_bitmaps_.empty()); |
| accounting::ContinuousSpaceBitmap live_bitmap(std::move(live_bitmaps_.back())); |
| live_bitmaps_.pop_back(); |
| MemMap oat_map = MemMap::MapAnonymous("OatMap", |
| oat_size, |
| PROT_READ | PROT_WRITE, |
| /*low_4gb=*/ true, |
| /*reservation=*/ oat_reservation, |
| &error_str); |
| if (!oat_map.IsValid()) { |
| LOG(ERROR) << error_str; |
| return nullptr; |
| } |
| std::unique_ptr<DummyOatFile> oat_file(new DummyOatFile(oat_map.Begin(), oat_map.End())); |
| // Create image header. |
| ImageSection sections[ImageHeader::kSectionCount]; |
| new (image_map.Begin()) ImageHeader( |
| /*image_reservation_size=*/ image_size, |
| /*component_count=*/ 1u, |
| /*image_begin=*/ PointerToLowMemUInt32(image_map.Begin()), |
| /*image_size=*/ image_size, |
| sections, |
| /*image_roots=*/ PointerToLowMemUInt32(image_map.Begin()) + 1, |
| /*oat_checksum=*/ 0u, |
| // The oat file data in the header is always right after the image space. |
| /*oat_file_begin=*/ PointerToLowMemUInt32(oat_map.Begin()), |
| /*oat_data_begin=*/ PointerToLowMemUInt32(oat_map.Begin()), |
| /*oat_data_end=*/ PointerToLowMemUInt32(oat_map.Begin() + oat_size), |
| /*oat_file_end=*/ PointerToLowMemUInt32(oat_map.Begin() + oat_size), |
| /*boot_image_begin=*/ 0u, |
| /*boot_image_size=*/ 0u, |
| /*boot_image_component_count=*/ 0u, |
| /*boot_image_checksum=*/ 0u, |
| /*pointer_size=*/ sizeof(void*)); |
| return new DummyImageSpace(std::move(image_map), |
| std::move(live_bitmap), |
| std::move(oat_file), |
| std::move(oat_map)); |
| } |
| |
| private: |
| // Bitmap pool for pre-allocated dummy bitmaps. We need to pre-allocate them since we don't want |
| // them to randomly get placed somewhere where we want an image space. |
| std::vector<accounting::ContinuousSpaceBitmap> live_bitmaps_; |
| }; |
| |
| class DummySpace : public space::ContinuousSpace { |
| public: |
| DummySpace(uint8_t* begin, uint8_t* end) |
| : ContinuousSpace("DummySpace", |
| space::kGcRetentionPolicyNeverCollect, |
| begin, |
| end, |
| /*limit=*/end) {} |
| |
| space::SpaceType GetType() const override { |
| return space::kSpaceTypeMallocSpace; |
| } |
| |
| bool CanMoveObjects() const override { |
| return false; |
| } |
| |
| accounting::ContinuousSpaceBitmap* GetLiveBitmap() override { |
| return nullptr; |
| } |
| |
| accounting::ContinuousSpaceBitmap* GetMarkBitmap() override { |
| return nullptr; |
| } |
| }; |
| |
| TEST_F(ImmuneSpacesTest, AppendBasic) { |
| ImmuneSpaces spaces; |
| uint8_t* const base = reinterpret_cast<uint8_t*>(0x1000); |
| DummySpace a(base, base + 45 * KB); |
| DummySpace b(a.Limit(), a.Limit() + 813 * KB); |
| { |
| WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_); |
| spaces.AddSpace(&a); |
| spaces.AddSpace(&b); |
| } |
| EXPECT_TRUE(spaces.ContainsSpace(&a)); |
| EXPECT_TRUE(spaces.ContainsSpace(&b)); |
| EXPECT_EQ(reinterpret_cast<uint8_t*>(spaces.GetLargestImmuneRegion().Begin()), a.Begin()); |
| EXPECT_EQ(reinterpret_cast<uint8_t*>(spaces.GetLargestImmuneRegion().End()), b.Limit()); |
| } |
| |
| // Tests [image][oat][space] producing a single large immune region. |
| TEST_F(ImmuneSpacesTest, AppendAfterImage) { |
| ReserveBitmaps(); |
| ImmuneSpaces spaces; |
| constexpr size_t kImageSize = 123 * kPageSize; |
| constexpr size_t kImageOatSize = 321 * kPageSize; |
| constexpr size_t kOtherSpaceSize = 100 * kPageSize; |
| |
| std::string error_str; |
| MemMap reservation = MemMap::MapAnonymous("reserve", |
| kImageSize + kImageOatSize + kOtherSpaceSize, |
| PROT_READ | PROT_WRITE, |
| /*low_4gb=*/ true, |
| &error_str); |
| ASSERT_TRUE(reservation.IsValid()) << "Failed to allocate memory region " << error_str; |
| MemMap image_reservation = reservation.TakeReservedMemory(kImageSize); |
| ASSERT_TRUE(image_reservation.IsValid()); |
| ASSERT_TRUE(reservation.IsValid()); |
| |
| std::unique_ptr<DummyImageSpace> image_space(CreateImageSpace(kImageSize, |
| kImageOatSize, |
| &image_reservation, |
| &reservation)); |
| ASSERT_TRUE(image_space != nullptr); |
| ASSERT_FALSE(image_reservation.IsValid()); |
| ASSERT_TRUE(reservation.IsValid()); |
| |
| const ImageHeader& image_header = image_space->GetImageHeader(); |
| DummySpace space(image_header.GetOatFileEnd(), image_header.GetOatFileEnd() + kOtherSpaceSize); |
| |
| EXPECT_EQ(image_header.GetImageSize(), kImageSize); |
| EXPECT_EQ(static_cast<size_t>(image_header.GetOatFileEnd() - image_header.GetOatFileBegin()), |
| kImageOatSize); |
| EXPECT_EQ(image_space->GetOatFile()->Size(), kImageOatSize); |
| // Check that we do not include the oat if there is no space after. |
| { |
| WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_); |
| spaces.AddSpace(image_space.get()); |
| } |
| EXPECT_EQ(reinterpret_cast<uint8_t*>(spaces.GetLargestImmuneRegion().Begin()), |
| image_space->Begin()); |
| EXPECT_EQ(reinterpret_cast<uint8_t*>(spaces.GetLargestImmuneRegion().End()), |
| image_space->Limit()); |
| // Add another space and ensure it gets appended. |
| EXPECT_NE(image_space->Limit(), space.Begin()); |
| { |
| WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_); |
| spaces.AddSpace(&space); |
| } |
| EXPECT_TRUE(spaces.ContainsSpace(image_space.get())); |
| EXPECT_TRUE(spaces.ContainsSpace(&space)); |
| // CreateLargestImmuneRegion should have coalesced the two spaces since the oat code after the |
| // image prevents gaps. |
| // Check that we have a continuous region. |
| EXPECT_EQ(reinterpret_cast<uint8_t*>(spaces.GetLargestImmuneRegion().Begin()), |
| image_space->Begin()); |
| EXPECT_EQ(reinterpret_cast<uint8_t*>(spaces.GetLargestImmuneRegion().End()), space.Limit()); |
| } |
| |
| // Test [image1][image2][image1 oat][image2 oat][image3] producing a single large immune region. |
| TEST_F(ImmuneSpacesTest, MultiImage) { |
| ReserveBitmaps(); |
| // Image 2 needs to be smaller or else it may be chosen for immune region. |
| constexpr size_t kImage1Size = kPageSize * 17; |
| constexpr size_t kImage2Size = kPageSize * 13; |
| constexpr size_t kImage3Size = kPageSize * 3; |
| constexpr size_t kImage1OatSize = kPageSize * 5; |
| constexpr size_t kImage2OatSize = kPageSize * 8; |
| constexpr size_t kImage3OatSize = kPageSize; |
| constexpr size_t kImageBytes = kImage1Size + kImage2Size + kImage3Size; |
| constexpr size_t kMemorySize = kImageBytes + kImage1OatSize + kImage2OatSize + kImage3OatSize; |
| std::string error_str; |
| MemMap reservation = MemMap::MapAnonymous("reserve", |
| kMemorySize, |
| PROT_READ | PROT_WRITE, |
| /*low_4gb=*/ true, |
| &error_str); |
| ASSERT_TRUE(reservation.IsValid()) << "Failed to allocate memory region " << error_str; |
| MemMap image_reservation = reservation.TakeReservedMemory(kImage1Size + kImage2Size); |
| ASSERT_TRUE(image_reservation.IsValid()); |
| ASSERT_TRUE(reservation.IsValid()); |
| |
| std::unique_ptr<DummyImageSpace> space1(CreateImageSpace(kImage1Size, |
| kImage1OatSize, |
| &image_reservation, |
| &reservation)); |
| ASSERT_TRUE(space1 != nullptr); |
| ASSERT_TRUE(image_reservation.IsValid()); |
| ASSERT_TRUE(reservation.IsValid()); |
| |
| std::unique_ptr<DummyImageSpace> space2(CreateImageSpace(kImage2Size, |
| kImage2OatSize, |
| &image_reservation, |
| &reservation)); |
| ASSERT_TRUE(space2 != nullptr); |
| ASSERT_FALSE(image_reservation.IsValid()); |
| ASSERT_TRUE(reservation.IsValid()); |
| |
| // Finally put a 3rd image space. |
| image_reservation = reservation.TakeReservedMemory(kImage3Size); |
| ASSERT_TRUE(image_reservation.IsValid()); |
| ASSERT_TRUE(reservation.IsValid()); |
| std::unique_ptr<DummyImageSpace> space3(CreateImageSpace(kImage3Size, |
| kImage3OatSize, |
| &image_reservation, |
| &reservation)); |
| ASSERT_TRUE(space3 != nullptr); |
| ASSERT_FALSE(image_reservation.IsValid()); |
| ASSERT_FALSE(reservation.IsValid()); |
| |
| // Check that we do not include the oat if there is no space after. |
| ImmuneSpaces spaces; |
| { |
| WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_); |
| LOG(INFO) << "Adding space1 " << reinterpret_cast<const void*>(space1->Begin()); |
| spaces.AddSpace(space1.get()); |
| LOG(INFO) << "Adding space2 " << reinterpret_cast<const void*>(space2->Begin()); |
| spaces.AddSpace(space2.get()); |
| } |
| // There are no more heap bytes, the immune region should only be the first 2 image spaces and |
| // should exclude the image oat files. |
| EXPECT_EQ(reinterpret_cast<uint8_t*>(spaces.GetLargestImmuneRegion().Begin()), |
| space1->Begin()); |
| EXPECT_EQ(reinterpret_cast<uint8_t*>(spaces.GetLargestImmuneRegion().End()), |
| space2->Limit()); |
| |
| // Add another space after the oat files, now it should contain the entire memory region. |
| { |
| WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_); |
| LOG(INFO) << "Adding space3 " << reinterpret_cast<const void*>(space3->Begin()); |
| spaces.AddSpace(space3.get()); |
| } |
| EXPECT_EQ(reinterpret_cast<uint8_t*>(spaces.GetLargestImmuneRegion().Begin()), |
| space1->Begin()); |
| EXPECT_EQ(reinterpret_cast<uint8_t*>(spaces.GetLargestImmuneRegion().End()), |
| space3->Limit()); |
| |
| // Add a smaller non-adjacent space and ensure it does not become part of the immune region. |
| // Image size is kImageBytes - kPageSize |
| // Oat size is kPageSize. |
| // Guard pages to ensure it is not adjacent to an existing immune region. |
| // Layout: [guard page][image][oat][guard page] |
| constexpr size_t kGuardSize = kPageSize; |
| constexpr size_t kImage4Size = kImageBytes - kPageSize; |
| constexpr size_t kImage4OatSize = kPageSize; |
| |
| reservation = MemMap::MapAnonymous("reserve", |
| kImage4Size + kImage4OatSize + kGuardSize * 2, |
| PROT_READ | PROT_WRITE, |
| /*low_4gb=*/ true, |
| &error_str); |
| ASSERT_TRUE(reservation.IsValid()) << "Failed to allocate memory region " << error_str; |
| MemMap guard = reservation.TakeReservedMemory(kGuardSize); |
| ASSERT_TRUE(guard.IsValid()); |
| ASSERT_TRUE(reservation.IsValid()); |
| guard.Reset(); // Release the guard memory. |
| image_reservation = reservation.TakeReservedMemory(kImage4Size); |
| ASSERT_TRUE(image_reservation.IsValid()); |
| ASSERT_TRUE(reservation.IsValid()); |
| std::unique_ptr<DummyImageSpace> space4(CreateImageSpace(kImage4Size, |
| kImage4OatSize, |
| &image_reservation, |
| &reservation)); |
| ASSERT_TRUE(space4 != nullptr); |
| ASSERT_FALSE(image_reservation.IsValid()); |
| ASSERT_TRUE(reservation.IsValid()); |
| ASSERT_EQ(reservation.Size(), kGuardSize); |
| reservation.Reset(); // Release the guard memory. |
| { |
| WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_); |
| LOG(INFO) << "Adding space4 " << reinterpret_cast<const void*>(space4->Begin()); |
| spaces.AddSpace(space4.get()); |
| } |
| EXPECT_EQ(reinterpret_cast<uint8_t*>(spaces.GetLargestImmuneRegion().Begin()), |
| space1->Begin()); |
| EXPECT_EQ(reinterpret_cast<uint8_t*>(spaces.GetLargestImmuneRegion().End()), |
| space3->Limit()); |
| |
| // Add a larger non-adjacent space and ensure it becomes the new largest immune region. |
| // Image size is kImageBytes + kPageSize |
| // Oat size is kPageSize. |
| // Guard pages to ensure it is not adjacent to an existing immune region. |
| // Layout: [guard page][image][oat][guard page] |
| constexpr size_t kImage5Size = kImageBytes + kPageSize; |
| constexpr size_t kImage5OatSize = kPageSize; |
| reservation = MemMap::MapAnonymous("reserve", |
| kImage5Size + kImage5OatSize + kGuardSize * 2, |
| PROT_READ | PROT_WRITE, |
| /*low_4gb=*/ true, |
| &error_str); |
| ASSERT_TRUE(reservation.IsValid()) << "Failed to allocate memory region " << error_str; |
| guard = reservation.TakeReservedMemory(kGuardSize); |
| ASSERT_TRUE(guard.IsValid()); |
| ASSERT_TRUE(reservation.IsValid()); |
| guard.Reset(); // Release the guard memory. |
| image_reservation = reservation.TakeReservedMemory(kImage5Size); |
| ASSERT_TRUE(image_reservation.IsValid()); |
| ASSERT_TRUE(reservation.IsValid()); |
| std::unique_ptr<DummyImageSpace> space5(CreateImageSpace(kImage5Size, |
| kImage5OatSize, |
| &image_reservation, |
| &reservation)); |
| ASSERT_TRUE(space5 != nullptr); |
| ASSERT_FALSE(image_reservation.IsValid()); |
| ASSERT_TRUE(reservation.IsValid()); |
| ASSERT_EQ(reservation.Size(), kGuardSize); |
| reservation.Reset(); // Release the guard memory. |
| { |
| WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_); |
| LOG(INFO) << "Adding space5 " << reinterpret_cast<const void*>(space5->Begin()); |
| spaces.AddSpace(space5.get()); |
| } |
| EXPECT_EQ(reinterpret_cast<uint8_t*>(spaces.GetLargestImmuneRegion().Begin()), space5->Begin()); |
| EXPECT_EQ(reinterpret_cast<uint8_t*>(spaces.GetLargestImmuneRegion().End()), space5->Limit()); |
| } |
| |
| } // namespace collector |
| } // namespace gc |
| } // namespace art |