From afcd2191811adcf40f41f677225311199b9a3396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20Gro=C3=9F?= Date: Mon, 29 Nov 2021 15:40:13 +0100 Subject: [PATCH] [platform] Introduce AddressSpaceReservation API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This low-level API implements virtual address space reservations on the supported platforms. An AddressSpaceReservation supports similar functionality as the global page management APIs in the OS class but operates inside a continuous region of previously reserved virtual address space. A reservation is backed by regular mmap mappings on Posix, by placeholder mappings on Windows, and by VMARs on Fuchsia. Bug: chromium:1218005 Change-Id: I99bc6bcbc26eb4aa3b54a31c671c9e06e92c471b Cq-Include-Trybots: luci.v8.try:v8_linux64_heap_sandbox_dbg_ng,v8_linux_arm64_sim_heap_sandbox_dbg_ng Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3270540 Commit-Queue: Samuel Groß Reviewed-by: Victor Gomes Reviewed-by: Igor Sheludko Reviewed-by: Michael Lippautz Cr-Commit-Position: refs/heads/main@{#78130} --- src/base/platform/platform-fuchsia.cc | 261 +++++++++++++++++++++----- src/base/platform/platform-posix.cc | 77 ++++++++ src/base/platform/platform-win32.cc | 196 +++++++++++++++---- src/base/platform/platform.h | 94 ++++++++++ 4 files changed, 545 insertions(+), 83 deletions(-) diff --git a/src/base/platform/platform-fuchsia.cc b/src/base/platform/platform-fuchsia.cc index a0fd83e939..b9614f069a 100644 --- a/src/base/platform/platform-fuchsia.cc +++ b/src/base/platform/platform-fuchsia.cc @@ -34,19 +34,35 @@ zx_vm_option_t GetProtectionFromMemoryPermission(OS::MemoryPermission access) { UNREACHABLE(); } -} // namespace - -TimezoneCache* OS::CreateTimezoneCache() { - return new PosixDefaultTimezoneCache(); +// Determine ZX_VM_ALIGN_X constant corresponding to the specified alignment. +// Returns 0 if there is none. +zx_vm_option_t GetAlignmentOptionFromAlignment(size_t alignment) { + // The alignment must be one of the ZX_VM_ALIGN_X constants. + // See zircon/system/public/zircon/types.h. + static_assert( + ZX_VM_ALIGN_1KB == (10 << ZX_VM_ALIGN_BASE), + "Fuchsia's ZX_VM_ALIGN_1KB constant doesn't match expected value"); + static_assert( + ZX_VM_ALIGN_4GB == (32 << ZX_VM_ALIGN_BASE), + "Fuchsia's ZX_VM_ALIGN_4GB constant doesn't match expected value"); + zx_vm_option_t alignment_log2 = 0; + for (int shift = 10; shift <= 32; shift++) { + if (alignment == (size_t{1} << shift)) { + alignment_log2 = shift; + break; + } + } + return alignment_log2 << ZX_VM_ALIGN_BASE; } -// static -void* OS::Allocate(void* address, size_t size, size_t alignment, - OS::MemoryPermission access) { - size_t page_size = OS::AllocatePageSize(); +void* AllocateInternal(const zx::vmar& vmar, size_t page_size, + size_t vmar_offset, bool vmar_offset_is_hint, + size_t size, size_t alignment, + OS::MemoryPermission access) { DCHECK_EQ(0, size % page_size); DCHECK_EQ(0, alignment % page_size); - address = AlignedAddress(address, alignment); + DCHECK_EQ(0, vmar_offset % alignment); + // Add the maximum misalignment so we are guaranteed an aligned base address. size_t request_size = size + (alignment - page_size); @@ -68,20 +84,19 @@ void* OS::Allocate(void* address, size_t size, size_t alignment, zx_vm_option_t options = GetProtectionFromMemoryPermission(access); - uint64_t vmar_offset = 0; - if (address) { - vmar_offset = reinterpret_cast(address); + if (vmar_offset != 0) { options |= ZX_VM_SPECIFIC; } zx_vaddr_t reservation; - zx_status_t status = zx::vmar::root_self()->map(options, vmar_offset, vmo, 0, - request_size, &reservation); - if (status != ZX_OK && address != nullptr) { - // Retry without the hint, if we supplied one. + zx_status_t status = + vmar.map(options, vmar_offset, vmo, 0, request_size, &reservation); + if (status != ZX_OK && vmar_offset != 0 && vmar_offset_is_hint) { + // If a vmar_offset was specified and the allocation failed (for example, + // because the offset overlapped another mapping), then we should retry + // again without a vmar_offset if that offset was just meant to be a hint. options &= ~(ZX_VM_SPECIFIC); - status = zx::vmar::root_self()->map(options, 0, vmo, 0, request_size, - &reservation); + status = vmar.map(options, 0, vmo, 0, request_size, &reservation); } if (status != ZX_OK) { return nullptr; @@ -95,8 +110,7 @@ void* OS::Allocate(void* address, size_t size, size_t alignment, if (aligned_base != base) { DCHECK_LT(base, aligned_base); size_t prefix_size = static_cast(aligned_base - base); - zx::vmar::root_self()->unmap(reinterpret_cast(base), - prefix_size); + vmar.unmap(reinterpret_cast(base), prefix_size); request_size -= prefix_size; } @@ -105,8 +119,8 @@ void* OS::Allocate(void* address, size_t size, size_t alignment, if (aligned_size != request_size) { DCHECK_LT(aligned_size, request_size); size_t suffix_size = request_size - aligned_size; - zx::vmar::root_self()->unmap( - reinterpret_cast(aligned_base + aligned_size), suffix_size); + vmar.unmap(reinterpret_cast(aligned_base + aligned_size), + suffix_size); request_size -= suffix_size; } @@ -114,39 +128,106 @@ void* OS::Allocate(void* address, size_t size, size_t alignment, return static_cast(aligned_base); } -// static -bool OS::Free(void* address, const size_t size) { - DCHECK_EQ(0, reinterpret_cast(address) % AllocatePageSize()); - DCHECK_EQ(0, size % AllocatePageSize()); - return zx::vmar::root_self()->unmap(reinterpret_cast(address), - size) == ZX_OK; +bool FreeInternal(const zx::vmar& vmar, size_t page_size, void* address, + const size_t size) { + DCHECK_EQ(0, reinterpret_cast(address) % page_size); + DCHECK_EQ(0, size % page_size); + return vmar.unmap(reinterpret_cast(address), size) == ZX_OK; +} + +bool SetPermissionsInternal(const zx::vmar& vmar, size_t page_size, + void* address, size_t size, + OS::MemoryPermission access) { + DCHECK_EQ(0, reinterpret_cast(address) % page_size); + DCHECK_EQ(0, size % page_size); + uint32_t prot = GetProtectionFromMemoryPermission(access); + return vmar.protect(prot, reinterpret_cast(address), size) == + ZX_OK; +} + +bool DiscardSystemPagesInternal(const zx::vmar& vmar, size_t page_size, + void* address, size_t size) { + DCHECK_EQ(0, reinterpret_cast(address) % page_size); + DCHECK_EQ(0, size % page_size); + uint64_t address_int = reinterpret_cast(address); + return vmar.op_range(ZX_VMO_OP_DECOMMIT, address_int, size, nullptr, 0) == + ZX_OK; +} + +zx_status_t CreateAddressSpaceReservationInternal( + const zx::vmar& vmar, size_t page_size, size_t vmar_offset, + bool vmar_offset_is_hint, size_t size, size_t alignment, + OS::MemoryPermission max_permission, zx::vmar* child, + zx_vaddr_t* child_addr) { + DCHECK_EQ(0, size % page_size); + DCHECK_GE(alignment, page_size); + DCHECK_EQ(0, alignment % page_size); + DCHECK_EQ(0, vmar_offset % alignment); + + // TODO(v8) determine these based on max_permission. + zx_vm_option_t options = ZX_VM_CAN_MAP_READ | ZX_VM_CAN_MAP_WRITE | + ZX_VM_CAN_MAP_EXECUTE | ZX_VM_CAN_MAP_SPECIFIC; + + zx_vm_option_t alignment_option = GetAlignmentOptionFromAlignment(alignment); + if (!alignment_option) { + return ZX_ERR_INVALID_ARGS; + } + options |= alignment_option; + + if (vmar_offset != 0) { + options |= ZX_VM_SPECIFIC; + } + + zx_status_t status = + vmar.allocate(options, vmar_offset, size, child, child_addr); + if (status != ZX_OK && vmar_offset != 0 && vmar_offset_is_hint) { + // If a vmar_offset was specified and the allocation failed (for example, + // because the offset overlapped another mapping), then we should retry + // again without a vmar_offset if that offset was just meant to be a hint. + options &= ~(ZX_VM_SPECIFIC); + status = vmar.allocate(options, 0, size, child, child_addr); + } + + return status; +} + +} // namespace + +TimezoneCache* OS::CreateTimezoneCache() { + return new PosixDefaultTimezoneCache(); } // static -bool OS::Release(void* address, size_t size) { - DCHECK_EQ(0, reinterpret_cast(address) % CommitPageSize()); - DCHECK_EQ(0, size % CommitPageSize()); - return zx::vmar::root_self()->unmap(reinterpret_cast(address), - size) == ZX_OK; +void* OS::Allocate(void* address, size_t size, size_t alignment, + MemoryPermission access) { + constexpr bool vmar_offset_is_hint = true; + return AllocateInternal(*zx::vmar::root_self(), AllocatePageSize(), + reinterpret_cast(address), + vmar_offset_is_hint, size, alignment, access); } +// static +bool OS::Free(void* address, const size_t size) { + return FreeInternal(*zx::vmar::root_self(), AllocatePageSize(), address, + size); +} + +// static +bool OS::Release(void* address, size_t size) { return Free(address, size); } + // static bool OS::SetPermissions(void* address, size_t size, MemoryPermission access) { - DCHECK_EQ(0, reinterpret_cast(address) % CommitPageSize()); - DCHECK_EQ(0, size % CommitPageSize()); - uint32_t prot = GetProtectionFromMemoryPermission(access); - return zx::vmar::root_self()->protect( - prot, reinterpret_cast(address), size) == ZX_OK; + return SetPermissionsInternal(*zx::vmar::root_self(), CommitPageSize(), + address, size, access); } // static bool OS::DiscardSystemPages(void* address, size_t size) { - uint64_t address_int = reinterpret_cast(address); - zx_status_t status = zx::vmar::root_self()->op_range( - ZX_VMO_OP_DECOMMIT, address_int, size, nullptr, 0); - return status == ZX_OK; + return DiscardSystemPagesInternal(*zx::vmar::root_self(), CommitPageSize(), + address, size); } +// static bool OS::DecommitPages(void* address, size_t size) { // We rely on DiscardSystemPages decommitting the pages immediately (via // ZX_VMO_OP_DECOMMIT) so that they are guaranteed to be zero-initialized @@ -155,6 +236,33 @@ bool OS::DecommitPages(void* address, size_t size) { DiscardSystemPages(address, size); } +// static +bool OS::CanReserveAddressSpace() { return true; } + +// static +Optional OS::CreateAddressSpaceReservation( + void* hint, size_t size, size_t alignment, + MemoryPermission max_permission) { + zx::vmar child; + zx_vaddr_t child_addr; + uint64_t vmar_offset = reinterpret_cast(hint); + constexpr bool vmar_offset_is_hint = true; + zx_status_t status = CreateAddressSpaceReservationInternal( + *zx::vmar::root_self(), AllocatePageSize(), vmar_offset, + vmar_offset_is_hint, size, alignment, max_permission, &child, + &child_addr); + if (status != ZX_OK) return {}; + return AddressSpaceReservation(reinterpret_cast(child_addr), size, + child.release()); +} + +// static +bool OS::FreeAddressSpaceReservation(AddressSpaceReservation reservation) { + // Destroy the vmar and release the handle. + zx::vmar vmar(reservation.vmar_); + return vmar.destroy() == ZX_OK; +} + // static bool OS::HasLazyCommits() { return true; } @@ -194,5 +302,74 @@ std::vector OS::GetFreeMemoryRangesWithin( return {}; } +Optional AddressSpaceReservation::CreateSubReservation( + void* address, size_t size, OS::MemoryPermission max_permission) { + DCHECK(Contains(address, size)); + + zx::vmar child; + zx_vaddr_t child_addr; + size_t vmar_offset = 0; + if (address != 0) { + vmar_offset = + reinterpret_cast(address) - reinterpret_cast(base()); + } + constexpr bool vmar_offset_is_hint = false; + zx_status_t status = CreateAddressSpaceReservationInternal( + *zx::unowned_vmar(vmar_), OS::AllocatePageSize(), vmar_offset, + vmar_offset_is_hint, size, OS::AllocatePageSize(), max_permission, &child, + &child_addr); + if (status != ZX_OK) return {}; + DCHECK_EQ(reinterpret_cast(child_addr), address); + return AddressSpaceReservation(reinterpret_cast(child_addr), size, + child.release()); +} + +bool AddressSpaceReservation::FreeSubReservation( + AddressSpaceReservation reservation) { + return OS::FreeAddressSpaceReservation(reservation); +} + +bool AddressSpaceReservation::Allocate(void* address, size_t size, + OS::MemoryPermission access) { + DCHECK(Contains(address, size)); + size_t vmar_offset = 0; + if (address != 0) { + vmar_offset = + reinterpret_cast(address) - reinterpret_cast(base()); + } + constexpr bool vmar_offset_is_hint = false; + void* allocation = AllocateInternal( + *zx::unowned_vmar(vmar_), OS::AllocatePageSize(), vmar_offset, + vmar_offset_is_hint, size, OS::AllocatePageSize(), access); + DCHECK(!allocation || allocation == address); + return allocation != nullptr; +} + +bool AddressSpaceReservation::Free(void* address, size_t size) { + DCHECK(Contains(address, size)); + return FreeInternal(*zx::unowned_vmar(vmar_), OS::AllocatePageSize(), address, + size); +} + +bool AddressSpaceReservation::SetPermissions(void* address, size_t size, + OS::MemoryPermission access) { + DCHECK(Contains(address, size)); + return SetPermissionsInternal(*zx::unowned_vmar(vmar_), OS::CommitPageSize(), + address, size, access); +} + +bool AddressSpaceReservation::DiscardSystemPages(void* address, size_t size) { + DCHECK(Contains(address, size)); + return DiscardSystemPagesInternal(*zx::unowned_vmar(vmar_), + OS::CommitPageSize(), address, size); +} + +bool AddressSpaceReservation::DecommitPages(void* address, size_t size) { + DCHECK(Contains(address, size)); + // See comment in OS::DecommitPages. + return SetPermissions(address, size, OS::MemoryPermission::kNoAccess) && + DiscardSystemPages(address, size); +} + } // namespace base } // namespace v8 diff --git a/src/base/platform/platform-posix.cc b/src/base/platform/platform-posix.cc index d4957aabb0..784174a179 100644 --- a/src/base/platform/platform-posix.cc +++ b/src/base/platform/platform-posix.cc @@ -467,6 +467,7 @@ bool OS::SetPermissions(void* address, size_t size, MemoryPermission access) { return ret == 0; } +// static bool OS::DiscardSystemPages(void* address, size_t size) { DCHECK_EQ(0, reinterpret_cast(address) % CommitPageSize()); DCHECK_EQ(0, size % CommitPageSize()); @@ -495,6 +496,7 @@ bool OS::DiscardSystemPages(void* address, size_t size) { return ret == 0; } +// static bool OS::DecommitPages(void* address, size_t size) { DCHECK_EQ(0, reinterpret_cast(address) % CommitPageSize()); DCHECK_EQ(0, size % CommitPageSize()); @@ -509,6 +511,30 @@ bool OS::DecommitPages(void* address, size_t size) { return ptr == address; } +// static +bool OS::CanReserveAddressSpace() { return true; } + +// static +Optional OS::CreateAddressSpaceReservation( + void* hint, size_t size, size_t alignment, + MemoryPermission max_permission) { + // On POSIX, address space reservations are backed by private memory mappings. + MemoryPermission permission = MemoryPermission::kNoAccess; + if (max_permission == MemoryPermission::kReadWriteExecute) { + permission = MemoryPermission::kNoAccessWillJitLater; + } + + void* reservation = Allocate(hint, size, alignment, permission); + if (!reservation) return {}; + + return AddressSpaceReservation(reservation, size); +} + +// static +bool OS::FreeAddressSpaceReservation(AddressSpaceReservation reservation) { + return Free(reservation.base(), reservation.size()); +} + // static bool OS::HasLazyCommits() { #if V8_OS_AIX || V8_OS_LINUX || V8_OS_MACOSX @@ -823,6 +849,57 @@ void OS::StrNCpy(char* dest, int length, const char* src, size_t n) { strncpy(dest, src, n); } +// ---------------------------------------------------------------------------- +// POSIX Address space reservation support. +// + +#if !V8_OS_CYGWIN && !V8_OS_FUCHSIA + +Optional AddressSpaceReservation::CreateSubReservation( + void* address, size_t size, OS::MemoryPermission max_permission) { + DCHECK(Contains(address, size)); + DCHECK_EQ(0, size % OS::AllocatePageSize()); + DCHECK_EQ(0, reinterpret_cast(address) % OS::AllocatePageSize()); + + return AddressSpaceReservation(address, size); +} + +bool AddressSpaceReservation::FreeSubReservation( + AddressSpaceReservation reservation) { + // Nothing to do. + // Pages allocated inside the reservation must've already been freed. + return true; +} + +bool AddressSpaceReservation::Allocate(void* address, size_t size, + OS::MemoryPermission access) { + // The region is already mmap'ed, so it just has to be made accessible now. + DCHECK(Contains(address, size)); + return OS::SetPermissions(address, size, access); +} + +bool AddressSpaceReservation::Free(void* address, size_t size) { + DCHECK(Contains(address, size)); + return OS::DecommitPages(address, size); +} + +bool AddressSpaceReservation::SetPermissions(void* address, size_t size, + OS::MemoryPermission access) { + DCHECK(Contains(address, size)); + return OS::SetPermissions(address, size, access); +} + +bool AddressSpaceReservation::DiscardSystemPages(void* address, size_t size) { + DCHECK(Contains(address, size)); + return OS::DiscardSystemPages(address, size); +} + +bool AddressSpaceReservation::DecommitPages(void* address, size_t size) { + DCHECK(Contains(address, size)); + return OS::DecommitPages(address, size); +} + +#endif // !V8_OS_CYGWIN && !V8_OS_FUCHSIA // ---------------------------------------------------------------------------- // POSIX thread support. diff --git a/src/base/platform/platform-win32.cc b/src/base/platform/platform-win32.cc index 919c3ef4df..d00c4f5ebb 100644 --- a/src/base/platform/platform-win32.cc +++ b/src/base/platform/platform-win32.cc @@ -722,6 +722,20 @@ void OS::Initialize(bool hard_abort, const char* const gc_fake_mmap) { g_hard_abort = hard_abort; } +typedef PVOID (*VirtualAlloc2_t)(HANDLE, PVOID, SIZE_T, ULONG, ULONG, + MEM_EXTENDED_PARAMETER*, ULONG); +VirtualAlloc2_t VirtualAlloc2; + +void OS::EnsureWin32MemoryAPILoaded() { + static bool loaded = false; + if (!loaded) { + VirtualAlloc2 = (VirtualAlloc2_t)GetProcAddress( + GetModuleHandle(L"kernelbase.dll"), "VirtualAlloc2"); + + loaded = true; + } +} + // static size_t OS::AllocatePageSize() { static size_t allocate_alignment = 0; @@ -801,6 +815,14 @@ DWORD GetProtectionFromMemoryPermission(OS::MemoryPermission access) { UNREACHABLE(); } +void* VirtualAllocWrapper(void* hint, size_t size, DWORD flags, DWORD protect) { + if (VirtualAlloc2) { + return VirtualAlloc2(nullptr, hint, size, flags, protect, NULL, 0); + } else { + return VirtualAlloc(hint, size, flags, protect); + } +} + uint8_t* RandomizedVirtualAlloc(size_t size, DWORD flags, DWORD protect, void* hint) { LPVOID base = nullptr; @@ -816,16 +838,57 @@ uint8_t* RandomizedVirtualAlloc(size_t size, DWORD flags, DWORD protect, if (use_aslr && protect != PAGE_READWRITE) { // For executable or reserved pages try to randomize the allocation address. - base = VirtualAlloc(hint, size, flags, protect); + base = VirtualAllocWrapper(hint, size, flags, protect); } // On failure, let the OS find an address to use. if (base == nullptr) { - base = VirtualAlloc(nullptr, size, flags, protect); + base = VirtualAllocWrapper(nullptr, size, flags, protect); } return reinterpret_cast(base); } +void* AllocateInternal(void* hint, size_t size, size_t alignment, + size_t page_size, DWORD flags, DWORD protect) { + // First, try an exact size aligned allocation. + uint8_t* base = RandomizedVirtualAlloc(size, flags, protect, hint); + if (base == nullptr) return nullptr; // Can't allocate, we're OOM. + + // If address is suitably aligned, we're done. + uint8_t* aligned_base = reinterpret_cast( + RoundUp(reinterpret_cast(base), alignment)); + if (base == aligned_base) return reinterpret_cast(base); + + // Otherwise, free it and try a larger allocation. + CHECK(VirtualFree(base, 0, MEM_RELEASE)); + + // Clear the hint. It's unlikely we can allocate at this address. + hint = nullptr; + + // Add the maximum misalignment so we are guaranteed an aligned base address + // in the allocated region. + size_t padded_size = size + (alignment - page_size); + const int kMaxAttempts = 3; + aligned_base = nullptr; + for (int i = 0; i < kMaxAttempts; ++i) { + base = RandomizedVirtualAlloc(padded_size, flags, protect, hint); + if (base == nullptr) return nullptr; // Can't allocate, we're OOM. + + // Try to trim the allocation by freeing the padded allocation and then + // calling VirtualAlloc at the aligned base. + CHECK(VirtualFree(base, 0, MEM_RELEASE)); + aligned_base = reinterpret_cast( + RoundUp(reinterpret_cast(base), alignment)); + base = reinterpret_cast( + VirtualAllocWrapper(aligned_base, size, flags, protect)); + // We might not get the reduced allocation due to a race. In that case, + // base will be nullptr. + if (base != nullptr) break; + } + DCHECK_IMPLIES(base, base == aligned_base); + return reinterpret_cast(base); +} + } // namespace // static @@ -842,43 +905,7 @@ void* OS::Allocate(void* hint, size_t size, size_t alignment, : MEM_RESERVE | MEM_COMMIT; DWORD protect = GetProtectionFromMemoryPermission(access); - // First, try an exact size aligned allocation. - uint8_t* base = RandomizedVirtualAlloc(size, flags, protect, hint); - if (base == nullptr) return nullptr; // Can't allocate, we're OOM. - - // If address is suitably aligned, we're done. - uint8_t* aligned_base = reinterpret_cast( - RoundUp(reinterpret_cast(base), alignment)); - if (base == aligned_base) return reinterpret_cast(base); - - // Otherwise, free it and try a larger allocation. - CHECK(Free(base, size)); - - // Clear the hint. It's unlikely we can allocate at this address. - hint = nullptr; - - // Add the maximum misalignment so we are guaranteed an aligned base address - // in the allocated region. - size_t padded_size = size + (alignment - page_size); - const int kMaxAttempts = 3; - aligned_base = nullptr; - for (int i = 0; i < kMaxAttempts; ++i) { - base = RandomizedVirtualAlloc(padded_size, flags, protect, hint); - if (base == nullptr) return nullptr; // Can't allocate, we're OOM. - - // Try to trim the allocation by freeing the padded allocation and then - // calling VirtualAlloc at the aligned base. - CHECK(Free(base, padded_size)); - aligned_base = reinterpret_cast( - RoundUp(reinterpret_cast(base), alignment)); - base = reinterpret_cast( - VirtualAlloc(aligned_base, size, flags, protect)); - // We might not get the reduced allocation due to a race. In that case, - // base will be nullptr. - if (base != nullptr) break; - } - DCHECK_IMPLIES(base, base == aligned_base); - return reinterpret_cast(base); + return AllocateInternal(hint, size, alignment, page_size, flags, protect); } // static @@ -904,7 +931,7 @@ bool OS::SetPermissions(void* address, size_t size, MemoryPermission access) { return VirtualFree(address, size, MEM_DECOMMIT) != 0; } DWORD protect = GetProtectionFromMemoryPermission(access); - return VirtualAlloc(address, size, MEM_COMMIT, protect) != nullptr; + return VirtualAllocWrapper(address, size, MEM_COMMIT, protect) != nullptr; } // static @@ -929,7 +956,7 @@ bool OS::DiscardSystemPages(void* address, size_t size) { } // DiscardVirtualMemory is buggy in Win10 SP0, so fall back to MEM_RESET on // failure. - void* ptr = VirtualAlloc(address, size, MEM_RESET, PAGE_READWRITE); + void* ptr = VirtualAllocWrapper(address, size, MEM_RESET, PAGE_READWRITE); CHECK(ptr); return ptr; } @@ -949,6 +976,35 @@ bool OS::DecommitPages(void* address, size_t size) { return VirtualFree(address, size, MEM_DECOMMIT) != 0; } +// static +bool OS::CanReserveAddressSpace() { return VirtualAlloc2 != nullptr; } + +// static +Optional OS::CreateAddressSpaceReservation( + void* hint, size_t size, size_t alignment, + MemoryPermission max_permission) { + CHECK(CanReserveAddressSpace()); + + size_t page_size = AllocatePageSize(); + DCHECK_EQ(0, size % page_size); + DCHECK_EQ(0, alignment % page_size); + DCHECK_LE(page_size, alignment); + hint = AlignedAddress(hint, alignment); + + // On Windows, address space reservations are backed by placeholder mappings. + void* reservation = + AllocateInternal(hint, size, alignment, page_size, + MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS); + if (!reservation) return {}; + + return AddressSpaceReservation(reservation, size); +} + +// static +bool OS::FreeAddressSpaceReservation(AddressSpaceReservation reservation) { + return OS::Free(reservation.base(), reservation.size()); +} + // static bool OS::HasLazyCommits() { // TODO(alph): implement for the platform. @@ -1068,6 +1124,64 @@ Win32MemoryMappedFile::~Win32MemoryMappedFile() { CloseHandle(file_); } +Optional AddressSpaceReservation::CreateSubReservation( + void* address, size_t size, OS::MemoryPermission max_permission) { + // Nothing to do, the sub reservation must already have been split by now. + DCHECK(Contains(address, size)); + DCHECK_EQ(0, size % OS::AllocatePageSize()); + DCHECK_EQ(0, reinterpret_cast(address) % OS::AllocatePageSize()); + + return AddressSpaceReservation(address, size); +} + +bool AddressSpaceReservation::FreeSubReservation( + AddressSpaceReservation reservation) { + // Nothing to do. + // Pages allocated inside the reservation must've already been freed. + return true; +} + +bool AddressSpaceReservation::SplitPlaceholder(void* address, size_t size) { + DCHECK(Contains(address, size)); + return VirtualFree(address, size, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER); +} + +bool AddressSpaceReservation::MergePlaceholders(void* address, size_t size) { + DCHECK(Contains(address, size)); + return VirtualFree(address, size, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS); +} + +bool AddressSpaceReservation::Allocate(void* address, size_t size, + OS::MemoryPermission access) { + DCHECK(Contains(address, size)); + CHECK(VirtualAlloc2); + DWORD flags = (access == OS::MemoryPermission::kNoAccess) + ? MEM_RESERVE | MEM_REPLACE_PLACEHOLDER + : MEM_RESERVE | MEM_COMMIT | MEM_REPLACE_PLACEHOLDER; + DWORD protect = GetProtectionFromMemoryPermission(access); + return VirtualAlloc2(nullptr, address, size, flags, protect, NULL, 0); +} + +bool AddressSpaceReservation::Free(void* address, size_t size) { + DCHECK(Contains(address, size)); + return VirtualFree(address, size, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER); +} + +bool AddressSpaceReservation::SetPermissions(void* address, size_t size, + OS::MemoryPermission access) { + DCHECK(Contains(address, size)); + return OS::SetPermissions(address, size, access); +} + +bool AddressSpaceReservation::DiscardSystemPages(void* address, size_t size) { + DCHECK(Contains(address, size)); + return OS::DiscardSystemPages(address, size); +} + +bool AddressSpaceReservation::DecommitPages(void* address, size_t size) { + DCHECK(Contains(address, size)); + return OS::DecommitPages(address, size); +} // The following code loads functions defined in DbhHelp.h and TlHelp32.h // dynamically. This is to avoid being depending on dbghelp.dll and diff --git a/src/base/platform/platform.h b/src/base/platform/platform.h index bc1edc9c03..ce8d6c3dea 100644 --- a/src/base/platform/platform.h +++ b/src/base/platform/platform.h @@ -29,6 +29,7 @@ #include "src/base/base-export.h" #include "src/base/build_config.h" #include "src/base/compiler-specific.h" +#include "src/base/optional.h" #include "src/base/platform/mutex.h" #include "src/base/platform/semaphore.h" @@ -36,6 +37,10 @@ #include "src/base/qnx-math.h" #endif +#if V8_OS_FUCHSIA +#include +#endif // V8_OS_FUCHSIA + #ifdef V8_USE_ADDRESS_SANITIZER #include #endif // V8_USE_ADDRESS_SANITIZER @@ -115,6 +120,7 @@ inline intptr_t InternalGetExistingThreadLocal(intptr_t index) { #endif // V8_NO_FAST_TLS +class AddressSpaceReservation; class PageAllocator; class TimezoneCache; @@ -132,6 +138,17 @@ class V8_BASE_EXPORT OS { // - gc_fake_mmap: Name of the file for fake gc mmap used in ll_prof. static void Initialize(bool hard_abort, const char* const gc_fake_mmap); +#if V8_OS_WIN + // On Windows, ensure the newer memory API is loaded if available. This + // includes function like VirtualAlloc2 and MapViewOfFile3. + // TODO(chromium:1218005) this should probably happen as part of Initialize, + // but that is currently invoked too late, after the virtual memory cage + // is initialized. However, eventually the virtual memory cage initialization + // will happen as part of V8::Initialize, at which point this function can + // probably be merged into OS::Initialize. + static void EnsureWin32MemoryAPILoaded(); +#endif + // Returns the accumulated user time for thread. This routine // can be used for profiling. The implementation should // strive for high-precision timer resolution, preferable @@ -291,6 +308,7 @@ class V8_BASE_EXPORT OS { private: // These classes use the private memory management API below. + friend class AddressSpaceReservation; friend class MemoryMappedFile; friend class PosixMemoryMappedFile; friend class v8::base::PageAllocator; @@ -326,6 +344,15 @@ class V8_BASE_EXPORT OS { V8_WARN_UNUSED_RESULT static bool DecommitPages(void* address, size_t size); + V8_WARN_UNUSED_RESULT static bool CanReserveAddressSpace(); + + V8_WARN_UNUSED_RESULT static Optional + CreateAddressSpaceReservation(void* hint, size_t size, size_t alignment, + MemoryPermission max_permission); + + V8_WARN_UNUSED_RESULT static bool FreeAddressSpaceReservation( + AddressSpaceReservation reservation); + static const int msPerSecond = 1000; #if V8_OS_POSIX @@ -347,6 +374,73 @@ inline void EnsureConsoleOutput() { #endif // (defined(_WIN32) || defined(_WIN64)) } +// ---------------------------------------------------------------------------- +// AddressSpaceReservation +// +// This class provides the same memory management functions as OS but operates +// inside a previously reserved contiguous region of virtual address space. +class V8_BASE_EXPORT AddressSpaceReservation { + public: + using Address = uintptr_t; + + void* base() const { return base_; } + size_t size() const { return size_; } + + bool Contains(void* region_addr, size_t region_size) const { + Address base = reinterpret_cast
(base_); + Address region_base = reinterpret_cast
(region_addr); + return (region_base >= base) && + ((region_base + region_size) <= (base + size_)); + } + + V8_WARN_UNUSED_RESULT bool Allocate(void* address, size_t size, + OS::MemoryPermission access); + + V8_WARN_UNUSED_RESULT bool Free(void* address, size_t size); + + V8_WARN_UNUSED_RESULT bool SetPermissions(void* address, size_t size, + OS::MemoryPermission access); + + V8_WARN_UNUSED_RESULT bool DiscardSystemPages(void* address, size_t size); + + V8_WARN_UNUSED_RESULT bool DecommitPages(void* address, size_t size); + + V8_WARN_UNUSED_RESULT Optional CreateSubReservation( + void* address, size_t size, OS::MemoryPermission max_permission); + + V8_WARN_UNUSED_RESULT static bool FreeSubReservation( + AddressSpaceReservation reservation); + +#if V8_OS_WIN + // On Windows, the placeholder mappings backing address space reservations + // need to be split and merged as page allocations can only replace an entire + // placeholder mapping, not parts of it. This must be done by the users of + // this API as it requires a RegionAllocator (or equivalent) to keep track of + // sub-regions and decide when to split and when to coalesce multiple free + // regions into a single one. + V8_WARN_UNUSED_RESULT bool SplitPlaceholder(void* address, size_t size); + V8_WARN_UNUSED_RESULT bool MergePlaceholders(void* address, size_t size); +#endif // V8_OS_WIN + + private: + friend class OS; + +#if V8_OS_FUCHSIA + AddressSpaceReservation(void* base, size_t size, zx_handle_t vmar) + : base_(base), size_(size), vmar_(vmar) {} +#else + AddressSpaceReservation(void* base, size_t size) : base_(base), size_(size) {} +#endif // V8_OS_FUCHSIA + + void* base_ = nullptr; + size_t size_ = 0; + +#if V8_OS_FUCHSIA + // On Fuchsia, address space reservations are backed by VMARs. + zx_handle_t vmar_ = ZX_HANDLE_INVALID; +#endif // V8_OS_FUCHSIA +}; + // ---------------------------------------------------------------------------- // Thread //