// Copyright 2018 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/base/region-allocator.h" #include "src/base/bits.h" #include "src/base/macros.h" namespace v8 { namespace base { // If |free_size| < |region_size| * |kMaxLoadFactorForRandomization| stop trying // to randomize region allocation. constexpr double kMaxLoadFactorForRandomization = 0.40; // Max number of attempts to allocate page at random address. constexpr int kMaxRandomizationAttempts = 3; RegionAllocator::RegionAllocator(Address memory_region_begin, size_t memory_region_size, size_t page_size) : whole_region_(memory_region_begin, memory_region_size, false), region_size_in_pages_(size() / page_size), max_load_for_randomization_( static_cast(size() * kMaxLoadFactorForRandomization)), free_size_(0), page_size_(page_size) { CHECK_LT(begin(), end()); CHECK(base::bits::IsPowerOfTwo(page_size_)); CHECK(IsAligned(size(), page_size_)); CHECK(IsAligned(begin(), page_size_)); // Initial region. Region* region = new Region(whole_region_); all_regions_.insert(region); FreeListAddRegion(region); } RegionAllocator::~RegionAllocator() { for (Region* region : all_regions_) { delete region; } } RegionAllocator::AllRegionsSet::iterator RegionAllocator::FindRegion( Address address) { if (!whole_region_.contains(address)) return all_regions_.end(); Region key(address, 0, false); AllRegionsSet::iterator iter = all_regions_.upper_bound(&key); // Regions in |all_regions_| are compared by end() values and key's end() // points exactly to the address we are querying, so the upper_bound will // find the region whose |end()| is greater than the requested address. DCHECK_NE(iter, all_regions_.end()); DCHECK((*iter)->contains(address)); return iter; } void RegionAllocator::FreeListAddRegion(Region* region) { free_size_ += region->size(); free_regions_.insert(region); } RegionAllocator::Region* RegionAllocator::FreeListFindRegion(size_t size) { Region key(0, size, false); auto iter = free_regions_.lower_bound(&key); return iter == free_regions_.end() ? nullptr : *iter; } void RegionAllocator::FreeListRemoveRegion(Region* region) { DCHECK(!region->is_used()); auto iter = free_regions_.find(region); DCHECK_NE(iter, free_regions_.end()); DCHECK_EQ(region, *iter); DCHECK_LE(region->size(), free_size_); free_size_ -= region->size(); free_regions_.erase(iter); } RegionAllocator::Region* RegionAllocator::Split(Region* region, size_t new_size) { DCHECK(IsAligned(new_size, page_size_)); DCHECK_NE(new_size, 0); DCHECK_GT(region->size(), new_size); // Create new region and put it to the lists after the |region|. bool used = region->is_used(); Region* new_region = new Region(region->begin() + new_size, region->size() - new_size, used); if (!used) { // Remove region from the free list before updating it's size. FreeListRemoveRegion(region); } region->set_size(new_size); all_regions_.insert(new_region); if (!used) { FreeListAddRegion(region); FreeListAddRegion(new_region); } return new_region; } void RegionAllocator::Merge(AllRegionsSet::iterator prev_iter, AllRegionsSet::iterator next_iter) { Region* prev = *prev_iter; Region* next = *next_iter; DCHECK_EQ(prev->end(), next->begin()); prev->set_size(prev->size() + next->size()); all_regions_.erase(next_iter); // prev_iter stays valid. // The |next| region must already not be in the free list. DCHECK_EQ(free_regions_.find(next), free_regions_.end()); delete next; } RegionAllocator::Address RegionAllocator::AllocateRegion(size_t size) { DCHECK_NE(size, 0); DCHECK(IsAligned(size, page_size_)); Region* region = FreeListFindRegion(size); if (region == nullptr) return kAllocationFailure; if (region->size() != size) { Split(region, size); } DCHECK(IsAligned(region->begin(), page_size_)); DCHECK_EQ(region->size(), size); // Mark region as used. FreeListRemoveRegion(region); region->set_is_used(true); return region->begin(); } RegionAllocator::Address RegionAllocator::AllocateRegion( RandomNumberGenerator* rng, size_t size) { if (free_size() >= max_load_for_randomization_) { // There is enough free space for trying to randomize the address. size_t random = 0; for (int i = 0; i < kMaxRandomizationAttempts; i++) { rng->NextBytes(&random, sizeof(random)); size_t random_offset = page_size_ * (random % region_size_in_pages_); Address address = begin() + random_offset; if (AllocateRegionAt(address, size)) { return address; } } // Fall back to free list allocation. } return AllocateRegion(size); } bool RegionAllocator::AllocateRegionAt(Address requested_address, size_t size) { DCHECK(IsAligned(requested_address, page_size_)); DCHECK_NE(size, 0); DCHECK(IsAligned(size, page_size_)); Address requested_end = requested_address + size; DCHECK_LE(requested_end, end()); Region* region; { AllRegionsSet::iterator region_iter = FindRegion(requested_address); if (region_iter == all_regions_.end()) { return false; } region = *region_iter; } if (region->is_used() || region->end() < requested_end) { return false; } // Found free region that includes the requested one. if (region->begin() != requested_address) { // Split the region at the |requested_address| boundary. size_t new_size = requested_address - region->begin(); DCHECK(IsAligned(new_size, page_size_)); region = Split(region, new_size); } if (region->end() != requested_end) { // Split the region at the |requested_end| boundary. Split(region, size); } DCHECK_EQ(region->begin(), requested_address); DCHECK_EQ(region->size(), size); // Mark region as used. FreeListRemoveRegion(region); region->set_is_used(true); return true; } size_t RegionAllocator::TrimRegion(Address address, size_t new_size) { DCHECK(IsAligned(new_size, page_size_)); AllRegionsSet::iterator region_iter = FindRegion(address); if (region_iter == all_regions_.end()) { return 0; } Region* region = *region_iter; if (region->begin() != address || !region->is_used()) { return 0; } // The region must not be in the free list. DCHECK_EQ(free_regions_.find(*region_iter), free_regions_.end()); if (new_size > 0) { region = Split(region, new_size); ++region_iter; } size_t size = region->size(); region->set_is_used(false); // Merge current region with the surrounding ones if they are free. if (region->end() != whole_region_.end()) { // There must be a range after the current one. AllRegionsSet::iterator next_iter = std::next(region_iter); DCHECK_NE(next_iter, all_regions_.end()); if (!(*next_iter)->is_used()) { // |next| region object will be deleted during merge, remove it from // the free list. FreeListRemoveRegion(*next_iter); Merge(region_iter, next_iter); } } if (new_size == 0 && region->begin() != whole_region_.begin()) { // There must be a range before the current one. AllRegionsSet::iterator prev_iter = std::prev(region_iter); DCHECK_NE(prev_iter, all_regions_.end()); if (!(*prev_iter)->is_used()) { // |prev| region's size will change, we'll have to re-insert it into // the proper place of the free list. FreeListRemoveRegion(*prev_iter); Merge(prev_iter, region_iter); // |prev| region becomes the current region. region_iter = prev_iter; region = *region_iter; } } FreeListAddRegion(region); return size; } size_t RegionAllocator::CheckRegion(Address address) { AllRegionsSet::iterator region_iter = FindRegion(address); if (region_iter == all_regions_.end()) { return 0; } Region* region = *region_iter; if (region->begin() != address || !region->is_used()) { return 0; } return region->size(); } bool RegionAllocator::IsFree(Address address, size_t size) { CHECK(contains(address, size)); AllRegionsSet::iterator region_iter = FindRegion(address); if (region_iter == all_regions_.end()) { return true; } Region* region = *region_iter; return !region->is_used() && region->contains(address, size); } void RegionAllocator::Region::Print(std::ostream& os) const { std::ios::fmtflags flags = os.flags(std::ios::hex | std::ios::showbase); os << "[" << begin() << ", " << end() << "), size: " << size(); os << ", " << (is_used() ? "used" : "free"); os.flags(flags); } void RegionAllocator::Print(std::ostream& os) const { std::ios::fmtflags flags = os.flags(std::ios::hex | std::ios::showbase); os << "RegionAllocator: [" << begin() << ", " << end() << ")"; os << "\nsize: " << size(); os << "\nfree_size: " << free_size(); os << "\npage_size: " << page_size_; os << "\nall regions: "; for (const Region* region : all_regions_) { os << "\n "; region->Print(os); } os << "\nfree regions: "; for (const Region* region : free_regions_) { os << "\n "; region->Print(os); } os << "\n"; os.flags(flags); } } // namespace base } // namespace v8