| // 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) |