blob: 0450d7f064c463ddfcc690eab4b9f53b294f8b5d [file] [log] [blame]
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* 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.
*/
// Adapted from OSAllocatorShell.cpp
#include "config.h"
#include "OSAllocatorStarboard.h"
#include "starboard/memory.h"
namespace {
uint8_t BitScanForward(uint32_t *index, uint32_t mask) {
*index = 0;
while (!(mask & 1)) {
mask >>= 1;
++(*index);
}
return !!(mask & 1);
}
// Find the least-significant bit set.
int FindFirstSet(uint32_t i) {
uint32_t index;
if (BitScanForward(&index, i)) {
// ffs returns values from 1 - 32
return index + 1;
} else {
return 0;
}
}
} // namespace
namespace WTF {
// ===== on allocating =====
// Allocations must be aligned to >= getpagesize().
// getpagesize() must be <= 64k for PageAllocationAligned.
// getpagesize() must be <= JSC::RegisterFile::commitSize (default 16k).
// To minimize waste in PageAllocationAligned, getpagesize() must be 64k
// and commitSize must be increased to 64k.
// Define this when performing leak analysis. It will avoid pooling memory.
//#define LEAK_ANALYSIS
// static
StarboardPageAllocator* StarboardPageAllocator::instance_ = NULL;
size_t StarboardPageAllocator::kPageSize;
#if SB_HAS(MMAP)
StarboardPageAllocatorMmap::StarboardPageAllocatorMmap() {}
void* StarboardPageAllocatorMmap::allocateInternal(size_t alignment, size_t size) {
void* out = NULL;
if (alignment == kPageSize) {
// TODO: compute and track wasted space here.
// (Basically just the diff between allocated_bytes
// and page_count * page_size)
void* mapped = SbMemoryMap(size, kSbMemoryMapProtectReadWrite, "WTFStarboardPageAllocator");
if (mapped != SB_MEMORY_MAP_FAILED) {
ASSERT(reinterpret_cast<uintptr_t>(mapped) % kPageSize == 0);
updateAllocatedPages((size + kPageSize - 1) / kPageSize);
out = mapped;
}
} else {
ASSERT(alignment <= 16);
out = SbMemoryAllocateAligned(alignment, size);
if (out != NULL) {
non_page_blocks_.insert(out);
}
}
if (out) {
updateAllocatedBytes(size);
}
return out;
}
void StarboardPageAllocatorMmap::freeInternal(void* addr, size_t size) {
updateAllocatedBytes(-static_cast<ssize_t>(size));
std::set<void*>::iterator it = non_page_blocks_.find(addr);
if (it == non_page_blocks_.end()) {
ssize_t page_count = (
static_cast<ssize_t>(size) + kPageSize - 1) / kPageSize;
updateAllocatedPages(-page_count);
SbMemoryUnmap(addr, size);
} else {
SbMemoryDeallocateAligned(addr);
non_page_blocks_.erase(it);
}
}
#endif // SB_HAS(MMAP)
StarboardPageAllocatorFixed::StarboardPageAllocatorFixed()
#if !defined(__LB_SHELL__FOR_RELEASE__) && defined(MEMORY_STATISTICS)
: excess_page_count_(
"WebKit.StarboardPageAllocator.ExcessPageCount", 0,
"Number of requested pages that the StarboardPageAllocator could not "
"fulfill.")
, excess_high_water_mark_(
"WebKit.StarboardPageAllocator.ExcessHighWaterMark", 0,
"Most excess pages at any time during the app's current lifetime.")
#endif
{
#if defined(LEAK_ANALYSIS)
static const int kBufferSize = 64 * 1024;
#else
static const int kBufferSize = 19 * 1024 * 1024;
#endif
buffer_size_ = kBufferSize;
const int page_count = align(buffer_size_, kPageSize) / kPageSize;
bitmap_size_ = align(page_count, 32) / 32;
buffer_orig_ = SbMemoryAllocate(buffer_size_ + kPageSize);
buffer_ = alignPtr(buffer_orig_, kPageSize);
ASSERT(buffer_ != NULL);
free_bitmap_ = (uint32_t*)SbMemoryAllocate(bitmap_size_ * sizeof(uint32_t));
ASSERT(free_bitmap_);
for (int i = 0; i < bitmap_size_; ++i) {
free_bitmap_[i] = 0xffffffff;
}
// If number of pages doesn't fit neatly into bitmap, mark any trailing
// extra bits as allocated so we never return them.
const int trailing_bits = (32 - (page_count % 32)) % 32;
for (int i = 32 - trailing_bits; i < 32; ++i) {
clear_bit(bitmap_size_ - 1, i);
}
}
void* StarboardPageAllocatorFixed::allocateInternal(
size_t alignment, size_t size) {
void* p = 0;
if (size == kPageSize) {
p = allocatePage();
}
if (!p) {
p = allocateBlock(alignment, size);
}
if (p) {
updateAllocatedBytes(size);
}
return p;
}
void StarboardPageAllocatorFixed::freeInternal(void* addr, size_t size) {
if (isValidPage(addr)) {
freePage(addr);
} else {
freeBlock(addr);
}
updateAllocatedBytes(-static_cast<ssize_t>(size));
}
void* StarboardPageAllocatorFixed::allocatePage() {
void* p = NULL;
for (int i = 0; i < bitmap_size_; ++i) {
if (free_bitmap_[i] != 0) {
// index of the first bit set in this integer.
int first_free = FindFirstSet(free_bitmap_[i]) - 1;
int page_index = i * 32 + first_free;
p = (void*) (uintptr_t(buffer_) + kPageSize * page_index);
clear_bit(i, first_free);
#if !defined(__LB_SHELL__FOR_RELEASE__) && defined(MEMORY_STATISTICS)
excess_page_count_ = 0;
#endif
break;
}
}
#if !defined(__LB_SHELL__FOR_RELEASE__) && defined(MEMORY_STATISTICS)
if (!p) {
excess_page_count_ += 1;
excess_high_water_mark_ =
std::max(excess_high_water_mark_, excess_page_count_);
}
#endif
return p;
}
void StarboardPageAllocatorFixed::freePage(void* p) {
uintptr_t address = (uintptr_t)p;
ASSERT(address % kPageSize == 0);
uintptr_t start = (uintptr_t)buffer_;
uint32_t offset = (address - start) / kPageSize;
uint32_t int_index = offset / 32;
uint32_t bit_index = offset % 32;
set_bit(int_index, bit_index);
}
// A valid page is one that was allocated from buffer_.
bool StarboardPageAllocatorFixed::isValidPage(void* p) const {
uintptr_t address = (uintptr_t)p;
uintptr_t start = (uintptr_t)buffer_;
if (address >= start && address < start + buffer_size_) {
return true;
} else {
return false;
}
}
void* StarboardPageAllocatorFixed::allocateBlock(size_t alignment, size_t size) {
// Overallocate so we can guarantee an aligned block. We do not use
// memalign() here because some platforms do not support memalign() with
// alignment greater than 16 bytes (e.g. 64kb). We write the address of the
// original pointer just behind the block to be returned.
void* ptr = SbMemoryAllocate(size + alignment + sizeof(void*));
if (!ptr) {
return NULL;
}
void* aligned_ptr = alignPtr(
(void*)((uintptr_t)ptr + sizeof(void*)), alignment);
// Check that we have room to write the header
ASSERT((uintptr_t)aligned_ptr - (uintptr_t)ptr >= sizeof(void*));
void** orig_ptr = (void**)((uintptr_t)aligned_ptr - sizeof(void*));
*orig_ptr = ptr;
return aligned_ptr;
}
StarboardPageAllocator* StarboardPageAllocator::getInstance() {
if (!instance_) {
#if SB_HAS(MMAP)
instance_ = StarboardPageAllocatorMmap::Create();
#else
instance_ = StarboardPageAllocatorFixed::Create();
#endif
}
return instance_;
}
void* OSAllocator::reserveUncommitted(size_t vm_size, Usage usage, bool writable, bool executable, bool includesGuardPages)
{
StarboardPageAllocator* allocator = StarboardPageAllocator::getInstance();
const size_t alignment = usage == JSUnalignedPages ?
16 : StarboardPageAllocator::kPageSize;
return allocator->allocate(alignment, vm_size);
}
// static
void OSAllocator::releaseDecommitted(void* addr, size_t size)
{
StarboardPageAllocator* allocator = StarboardPageAllocator::getInstance();
allocator->free(addr, size);
}
// static
void OSAllocator::commit(void* addr, size_t size, bool writable, bool executable)
{
// no commit/decommit scheme on these platforms
}
// static
void OSAllocator::decommit(void* addr, size_t size)
{
// no commit/decommit scheme on these platforms
}
// static
void* OSAllocator::reserveAndCommit(size_t size, Usage usage, bool writable, bool executable, bool includesGuardPages)
{
// no commit/decommit scheme on these platforms
return reserveUncommitted(size, usage, writable, executable, includesGuardPages);
}
// static
size_t OSAllocator::getCurrentBytesAllocated() {
#if !defined(__LB_SHELL__FOR_RELEASE__) && defined(MEMORY_STATISTICS)
if (StarboardPageAllocator::instanceExists()) {
return StarboardPageAllocator::getInstance()->getCurrentBytesAllocated();
} else {
return 0;
}
#else
return 0;
#endif
}
} // namespace WTF