blob: 6905b83410190e25633084a17aabcbe21377b26d [file] [log] [blame]
// 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_t>(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