blob: 857e1e4f3f4408f89505b3bce5062ab83ba41b4f [file]
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "vm/virtual_memory_compressed.h"
#include "platform/utils.h"
#include "vm/flags.h"
#include "vm/lockers.h"
#if defined(DART_HOST_OS_FUCHSIA)
#include <zircon/process.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#endif
#if defined(DART_COMPRESSED_POINTERS)
namespace dart {
DEFINE_FLAG(bool,
pointer_cage,
false,
"Pad compressed heaps with guard regions large enough to prevent "
"any indexed load from reaching outside the compressed heap.");
#if defined(DART_HOST_OS_FUCHSIA)
Cage::Cage() : cache_() {
const size_t kEffectiveGuardRegionSize =
FLAG_pointer_cage ? kGuardRegionSize : 0;
zx_status_t status = zx_vmar_allocate(
zx_vmar_root_self(),
ZX_VM_CAN_MAP_READ | ZX_VM_CAN_MAP_WRITE | ZX_VM_CAN_MAP_SPECIFIC |
ZX_VM_ALIGN_4GB,
/*offset=*/0,
/*size=*/kEffectiveGuardRegionSize * 2 + kCompressedHeapSize,
&outer_vmar_, &outer_addr_);
if (status != ZX_OK) {
FATAL("zx_vmar_allocate failed: %s\n", zx_status_get_string(status));
}
ASSERT(Utils::IsAligned(outer_addr_, kCompressedHeapAlignment));
status = zx_vmar_allocate(
outer_vmar_, ZX_VM_CAN_MAP_READ | ZX_VM_CAN_MAP_WRITE | ZX_VM_SPECIFIC,
/*offset=*/kEffectiveGuardRegionSize,
/*size=*/kCompressedHeapSize, &inner_vmar_, &inner_addr_);
if (status != ZX_OK) {
FATAL("zx_vmar_allocate failed: %s\n", zx_status_get_string(status));
}
ASSERT(Utils::IsAligned(inner_addr_, kCompressedHeapAlignment));
}
Cage::~Cage() {
cache_.Abandon();
zx_vmar_destroy(inner_vmar_);
zx_vmar_destroy(outer_vmar_);
}
VirtualMemory* Cage::Allocate(intptr_t size, intptr_t alignment) {
const zx_vm_option_t align_flag = Utils::ShiftForPowerOfTwo(alignment)
<< ZX_VM_ALIGN_BASE;
ASSERT((ZX_VM_ALIGN_1KB <= align_flag) && (align_flag <= ZX_VM_ALIGN_4GB));
zx_handle_t vmo = ZX_HANDLE_INVALID;
zx_status_t status = zx_vmo_create(size, 0u, &vmo);
if (status == ZX_ERR_NO_MEMORY) {
return nullptr;
} else if (status != ZX_OK) {
FATAL("zx_vmo_create(0x%lx) failed: %s\n", size,
zx_status_get_string(status));
}
const zx_vm_option_t region_options =
ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | align_flag;
uword base;
status = zx_vmar_map(inner_vmar_, region_options, 0, vmo, 0u, size, &base);
if (status != ZX_OK) {
zx_handle_close(vmo);
return nullptr;
}
MemoryRegion region(reinterpret_cast<void*>(base), size);
VirtualMemory* result = new VirtualMemory(region, region, this);
zx_handle_close(vmo);
return result;
}
void Cage::Free(void* address, intptr_t size) {
zx_status_t status =
zx_vmar_unmap(inner_vmar_, reinterpret_cast<uword>(address), size);
if (status != ZX_OK) {
FATAL("zx_vmar_unmap failed: %s\n", zx_status_get_string(status));
}
}
void* Cage::GetRegion() {
return reinterpret_cast<void*>(inner_addr_);
}
#else // defined(DART_HOST_OS_FUCHSIA)
static uint8_t PageMask(uword page_id) {
return static_cast<uint8_t>(1 << (page_id % 8));
}
bool Cage::IsPageUsed(uword page_id) {
if (page_id >= kCompressedHeapNumPages) return false;
return pages_[page_id / 8] & PageMask(page_id);
}
void Cage::SetPageUsed(uword page_id) {
ASSERT(page_id < kCompressedHeapNumPages);
pages_[page_id / 8] |= PageMask(page_id);
}
void Cage::ClearPageUsed(uword page_id) {
ASSERT(page_id < kCompressedHeapNumPages);
pages_[page_id / 8] &= ~PageMask(page_id);
}
Cage::Cage() : mutex_(), cache_() {
const size_t kEffectiveGuardRegionSize =
FLAG_pointer_cage ? kGuardRegionSize : 0;
reservation_ = VirtualMemory::Reserve(
kEffectiveGuardRegionSize * 2 + kCompressedHeapSize,
kCompressedHeapAlignment);
if (reservation_ == nullptr) {
#if defined(DART_HOST_OS_WINDOWS)
int error = GetLastError();
FATAL("Failed to reserve region for compressed heap: %d", error);
#else
int error = errno;
const int kBufferSize = 1024;
char error_buf[kBufferSize];
FATAL("Failed to reserve region for compressed heap: %d (%s)", error,
Utils::StrError(error, error_buf, kBufferSize));
#endif
}
Init(reinterpret_cast<void*>(reservation_->start() +
kEffectiveGuardRegionSize),
kCompressedHeapSize);
}
void Cage::Init(void* compressed_heap_region, size_t size) {
pages_ = new uint8_t[kCompressedHeapBitmapSize];
memset(pages_, 0, kCompressedHeapBitmapSize);
ASSERT(size > 0);
ASSERT(size <= kCompressedHeapSize);
for (intptr_t page_id = size / kCompressedPageSize;
page_id < kCompressedHeapNumPages; page_id++) {
SetPageUsed(page_id);
}
base_ = reinterpret_cast<uword>(compressed_heap_region);
size_ = size;
ASSERT(base_ != 0);
ASSERT(size_ != 0);
ASSERT(size_ <= kCompressedHeapSize);
ASSERT(Utils::IsAligned(base_, kCompressedPageSize));
ASSERT(Utils::IsAligned(size_, kCompressedPageSize));
// base_ is not necessarily 4GB-aligned, because on some systems we can't make
// a large enough reservation to guarantee it. Instead, we have only the
// weaker property that all addresses in [base_, base_ + size_) have the same
// same upper 32 bits, which is what we really need for compressed pointers.
intptr_t mask = ~(kCompressedHeapAlignment - 1);
ASSERT((base_ & mask) == ((base_ + size_ - 1) & mask));
}
Cage::~Cage() {
cache_.Clear();
delete[] pages_;
base_ = 0;
size_ = 0;
pages_ = nullptr;
minimum_free_page_id_ = 0;
delete reservation_;
}
void* Cage::GetRegion() {
return reinterpret_cast<void*>(base_);
}
VirtualMemory* Cage::Allocate(intptr_t size, intptr_t alignment) {
ASSERT(alignment <= kCompressedHeapAlignment);
const intptr_t allocated_size = Utils::RoundUp(size, kCompressedPageSize);
uword pages = allocated_size / kCompressedPageSize;
uword page_alignment =
alignment > kCompressedPageSize ? alignment / kCompressedPageSize : 1;
uword page_id;
{
MutexLocker ml(&mutex_);
// Find a gap with enough empty pages, using the bitmap. Note that reading
// outside the bitmap range always returns 0, so this loop will terminate.
page_id = Utils::RoundUp(minimum_free_page_id_, page_alignment);
for (uword gap = 0;;) {
if (IsPageUsed(page_id)) {
gap = 0;
page_id = Utils::RoundUp(page_id + 1, page_alignment);
} else {
++gap;
if (gap >= pages) {
page_id += 1 - gap;
break;
}
++page_id;
}
}
ASSERT(page_id % page_alignment == 0);
// Make sure we're not trying to allocate past the end of the heap.
uword end = page_id + pages;
if (end > kCompressedHeapSize / kCompressedPageSize) {
return nullptr;
}
// Mark all the pages in the bitmap as allocated.
for (uword i = page_id; i < end; ++i) {
ASSERT(!IsPageUsed(i));
SetPageUsed(i);
}
// Find the next free page, to speed up subsequent allocations.
while (IsPageUsed(minimum_free_page_id_)) {
++minimum_free_page_id_;
}
}
uword address = base_ + page_id * kCompressedPageSize;
ASSERT(Utils::IsAligned(address, kCompressedPageSize));
MemoryRegion region(reinterpret_cast<void*>(address), allocated_size);
VirtualMemory::Commit(region.pointer(), region.size());
return new VirtualMemory(region, region, this);
}
void Cage::Free(void* address, intptr_t size) {
uword start = reinterpret_cast<uword>(address);
ASSERT(Utils::IsAligned(start, kCompressedPageSize));
ASSERT(Utils::IsAligned(size, kCompressedPageSize));
VirtualMemory::Decommit(address, size);
MutexLocker ml(&mutex_);
ASSERT(start >= base_);
uword page_id = (start - base_) / kCompressedPageSize;
uword end = page_id + size / kCompressedPageSize;
for (uword i = page_id; i < end; ++i) {
ClearPageUsed(i);
}
if (page_id < minimum_free_page_id_) {
minimum_free_page_id_ = page_id;
}
}
#endif // !defined(DART_HOST_OS_FUCHSIA)
} // namespace dart
#endif // defined(DART_COMPRESSED_POINTERS)