%PDF- %PDF-
| Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/heap/ |
| Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/heap/memory-allocator.h |
// Copyright 2020 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.
#ifndef V8_HEAP_MEMORY_ALLOCATOR_H_
#define V8_HEAP_MEMORY_ALLOCATOR_H_
#include <atomic>
#include <memory>
#include <set>
#include <unordered_set>
#include <utility>
#include "include/v8-platform.h"
#include "src/base/bounded-page-allocator.h"
#include "src/base/export-template.h"
#include "src/base/functional.h"
#include "src/base/macros.h"
#include "src/base/platform/mutex.h"
#include "src/base/platform/semaphore.h"
#include "src/common/globals.h"
#include "src/heap/basic-memory-chunk.h"
#include "src/heap/code-range.h"
#include "src/heap/memory-chunk.h"
#include "src/heap/spaces.h"
#include "src/tasks/cancelable-task.h"
#include "src/utils/allocation.h"
namespace v8 {
namespace internal {
namespace heap {
class TestMemoryAllocatorScope;
} // namespace heap
class Heap;
class Isolate;
class ReadOnlyPage;
// ----------------------------------------------------------------------------
// A space acquires chunks of memory from the operating system. The memory
// allocator allocates and deallocates pages for the paged heap spaces and large
// pages for large object space.
class MemoryAllocator {
public:
// Unmapper takes care of concurrently unmapping and uncommitting memory
// chunks.
class Unmapper {
public:
class UnmapFreeMemoryJob;
Unmapper(Heap* heap, MemoryAllocator* allocator)
: heap_(heap), allocator_(allocator) {
chunks_[ChunkQueueType::kRegular].reserve(kReservedQueueingSlots);
chunks_[ChunkQueueType::kPooled].reserve(kReservedQueueingSlots);
}
void AddMemoryChunkSafe(MemoryChunk* chunk) {
if (!chunk->IsLargePage() && chunk->executable() != EXECUTABLE) {
AddMemoryChunkSafe(ChunkQueueType::kRegular, chunk);
} else {
AddMemoryChunkSafe(ChunkQueueType::kNonRegular, chunk);
}
}
MemoryChunk* TryGetPooledMemoryChunkSafe() {
// Procedure:
// (1) Try to get a chunk that was declared as pooled and already has
// been uncommitted.
// (2) Try to steal any memory chunk of kPageSize that would've been
// uncommitted.
MemoryChunk* chunk = GetMemoryChunkSafe(ChunkQueueType::kPooled);
if (chunk == nullptr) {
chunk = GetMemoryChunkSafe(ChunkQueueType::kRegular);
if (chunk != nullptr) {
// For stolen chunks we need to manually free any allocated memory.
chunk->ReleaseAllAllocatedMemory();
}
}
return chunk;
}
V8_EXPORT_PRIVATE void FreeQueuedChunks();
void CancelAndWaitForPendingTasks();
void PrepareForGC();
V8_EXPORT_PRIVATE void EnsureUnmappingCompleted();
V8_EXPORT_PRIVATE void TearDown();
size_t NumberOfCommittedChunks();
V8_EXPORT_PRIVATE int NumberOfChunks();
size_t CommittedBufferedMemory();
// Returns true when Unmapper task may be running.
bool IsRunning() const;
private:
static const int kReservedQueueingSlots = 64;
static const int kMaxUnmapperTasks = 4;
enum ChunkQueueType {
kRegular, // Pages of kPageSize that do not live in a CodeRange and
// can thus be used for stealing.
kNonRegular, // Large chunks and executable chunks.
kPooled, // Pooled chunks, already freed and ready for reuse.
kNumberOfChunkQueues,
};
enum class FreeMode {
// Disables any access on pooled pages before adding them to the pool.
kUncommitPooled,
// Free pooled pages. Only used on tear down and last-resort GCs.
kFreePooled,
};
void AddMemoryChunkSafe(ChunkQueueType type, MemoryChunk* chunk) {
base::MutexGuard guard(&mutex_);
chunks_[type].push_back(chunk);
}
MemoryChunk* GetMemoryChunkSafe(ChunkQueueType type) {
base::MutexGuard guard(&mutex_);
if (chunks_[type].empty()) return nullptr;
MemoryChunk* chunk = chunks_[type].back();
chunks_[type].pop_back();
return chunk;
}
bool MakeRoomForNewTasks();
void PerformFreeMemoryOnQueuedChunks(FreeMode mode,
JobDelegate* delegate = nullptr);
void PerformFreeMemoryOnQueuedNonRegularChunks(
JobDelegate* delegate = nullptr);
Heap* const heap_;
MemoryAllocator* const allocator_;
base::Mutex mutex_;
std::vector<MemoryChunk*> chunks_[ChunkQueueType::kNumberOfChunkQueues];
std::unique_ptr<v8::JobHandle> job_handle_;
friend class MemoryAllocator;
};
enum class AllocationMode {
// Regular allocation path. Does not use pool.
kRegular,
// Uses the pool for allocation first.
kUsePool,
};
enum class FreeMode {
// Frees page immediately on the main thread.
kImmediately,
// Frees page on background thread.
kConcurrently,
// Uncommits but does not free page on background thread. Page is added to
// pool. Used to avoid the munmap/mmap-cycle when we quickly reallocate
// pages.
kConcurrentlyAndPool,
};
// Initialize page sizes field in V8::Initialize.
static void InitializeOncePerProcess();
V8_INLINE static intptr_t GetCommitPageSize() {
DCHECK_LT(0, commit_page_size_);
return commit_page_size_;
}
V8_INLINE static intptr_t GetCommitPageSizeBits() {
DCHECK_LT(0, commit_page_size_bits_);
return commit_page_size_bits_;
}
// Computes the memory area of discardable memory within a given memory area
// [addr, addr+size) and returns the result as base::AddressRegion. If the
// memory is not discardable base::AddressRegion is an empty region.
V8_EXPORT_PRIVATE static base::AddressRegion ComputeDiscardMemoryArea(
Address addr, size_t size);
V8_EXPORT_PRIVATE MemoryAllocator(Isolate* isolate,
v8::PageAllocator* code_page_allocator,
size_t max_capacity);
V8_EXPORT_PRIVATE void TearDown();
// Allocates a Page from the allocator. AllocationMode is used to indicate
// whether pooled allocation, which only works for MemoryChunk::kPageSize,
// should be tried first.
V8_EXPORT_PRIVATE Page* AllocatePage(
MemoryAllocator::AllocationMode alloc_mode, Space* space,
Executability executable);
V8_EXPORT_PRIVATE LargePage* AllocateLargePage(LargeObjectSpace* space,
size_t object_size,
Executability executable);
ReadOnlyPage* AllocateReadOnlyPage(ReadOnlySpace* space,
Address hint = kNullAddress);
std::unique_ptr<::v8::PageAllocator::SharedMemoryMapping> RemapSharedPage(
::v8::PageAllocator::SharedMemory* shared_memory, Address new_address);
V8_EXPORT_PRIVATE void Free(MemoryAllocator::FreeMode mode,
MemoryChunk* chunk);
void FreeReadOnlyPage(ReadOnlyPage* chunk);
// Returns allocated spaces in bytes.
size_t Size() const { return size_; }
// Returns allocated executable spaces in bytes.
size_t SizeExecutable() const { return size_executable_; }
// Returns the maximum available bytes of heaps.
size_t Available() const {
const size_t size = Size();
return capacity_ < size ? 0 : capacity_ - size;
}
// Returns an indication of whether a pointer is in a space that has
// been allocated by this MemoryAllocator. It is conservative, allowing
// false negatives (i.e., if a pointer is outside the allocated space, it may
// return false) but not false positives (i.e., if a pointer is inside the
// allocated space, it will definitely return false).
V8_INLINE bool IsOutsideAllocatedSpace(Address address) const {
return IsOutsideAllocatedSpace(address, NOT_EXECUTABLE) &&
IsOutsideAllocatedSpace(address, EXECUTABLE);
}
V8_INLINE bool IsOutsideAllocatedSpace(Address address,
Executability executable) const {
switch (executable) {
case NOT_EXECUTABLE:
return address < lowest_not_executable_ever_allocated_ ||
address >= highest_not_executable_ever_allocated_;
case EXECUTABLE:
return address < lowest_executable_ever_allocated_ ||
address >= highest_executable_ever_allocated_;
}
}
// Partially release |bytes_to_free| bytes starting at |start_free|. Note that
// internally memory is freed from |start_free| to the end of the reservation.
// Additional memory beyond the page is not accounted though, so
// |bytes_to_free| is computed by the caller.
void PartialFreeMemory(BasicMemoryChunk* chunk, Address start_free,
size_t bytes_to_free, Address new_area_end);
#ifdef DEBUG
// Checks if an allocated MemoryChunk was intended to be used for executable
// memory.
bool IsMemoryChunkExecutable(MemoryChunk* chunk) {
base::MutexGuard guard(&executable_memory_mutex_);
return executable_memory_.find(chunk) != executable_memory_.end();
}
#endif // DEBUG
// Page allocator instance for allocating non-executable pages.
// Guaranteed to be a valid pointer.
v8::PageAllocator* data_page_allocator() { return data_page_allocator_; }
// Page allocator instance for allocating executable pages.
// Guaranteed to be a valid pointer.
v8::PageAllocator* code_page_allocator() { return code_page_allocator_; }
// Page allocator instance for allocating "trusted" pages. When the sandbox
// is enabled, these pages are guaranteed to be allocated outside of the
// sandbox, so their content cannot be corrupted by an attacker.
// Guaranteed to be a valid pointer.
v8::PageAllocator* trusted_page_allocator() {
return trusted_page_allocator_;
}
// Returns page allocator suitable for allocating pages for the given space.
v8::PageAllocator* page_allocator(AllocationSpace space) {
switch (space) {
case CODE_SPACE:
case CODE_LO_SPACE:
return code_page_allocator_;
case TRUSTED_SPACE:
case TRUSTED_LO_SPACE:
return trusted_page_allocator_;
default:
return data_page_allocator_;
}
}
Unmapper* unmapper() { return &unmapper_; }
void UnregisterReadOnlyPage(ReadOnlyPage* page);
Address HandleAllocationFailure(Executability executable);
#if defined(V8_ENABLE_CONSERVATIVE_STACK_SCANNING) || defined(DEBUG)
// Return the normal or large page that contains this address, if it is owned
// by this heap, otherwise a nullptr.
V8_EXPORT_PRIVATE const BasicMemoryChunk* LookupChunkContainingAddress(
Address addr) const;
#endif // V8_ENABLE_CONSERVATIVE_STACK_SCANNING || DEBUG
// Insert and remove normal and large pages that are owned by this heap.
void RecordNormalPageCreated(const Page& page);
void RecordNormalPageDestroyed(const Page& page);
void RecordLargePageCreated(const LargePage& page);
void RecordLargePageDestroyed(const LargePage& page);
private:
// Used to store all data about MemoryChunk allocation, e.g. in
// AllocateUninitializedChunk.
struct MemoryChunkAllocationResult {
void* start;
size_t size;
size_t area_start;
size_t area_end;
VirtualMemory reservation;
};
// Computes the size of a MemoryChunk from the size of the object_area and
// whether the chunk is executable or not.
static size_t ComputeChunkSize(size_t area_size, AllocationSpace space,
Executability executable);
// Internal allocation method for all pages/memory chunks. Returns data about
// the unintialized memory region.
V8_WARN_UNUSED_RESULT base::Optional<MemoryChunkAllocationResult>
AllocateUninitializedChunk(BaseSpace* space, size_t area_size,
Executability executable, PageSize page_size) {
return AllocateUninitializedChunkAt(space, area_size, executable,
kNullAddress, page_size);
}
V8_WARN_UNUSED_RESULT base::Optional<MemoryChunkAllocationResult>
AllocateUninitializedChunkAt(BaseSpace* space, size_t area_size,
Executability executable, Address hint,
PageSize page_size);
// Internal raw allocation method that allocates an aligned MemoryChunk and
// sets the right memory permissions.
Address AllocateAlignedMemory(size_t chunk_size, size_t area_size,
size_t alignment, AllocationSpace space,
Executability executable, void* hint,
VirtualMemory* controller);
// Commit memory region owned by given reservation object. Returns true if
// it succeeded and false otherwise.
bool CommitMemory(VirtualMemory* reservation, Executability executable);
// Sets memory permissions on executable memory chunks. This entails page
// header (RW), guard pages (no access) and the object area (code modification
// permissions).
V8_WARN_UNUSED_RESULT bool SetPermissionsOnExecutableMemoryChunk(
VirtualMemory* vm, Address start, size_t area_size, size_t reserved_size);
// Disallows any access on memory region owned by given reservation object.
// Returns true if it succeeded and false otherwise.
bool UncommitMemory(VirtualMemory* reservation);
// Frees the given memory region.
void FreeMemoryRegion(v8::PageAllocator* page_allocator, Address addr,
size_t size);
// PreFreeMemory logically frees the object, i.e., it unregisters the
// memory, logs a delete event and adds the chunk to remembered unmapped
// pages.
void PreFreeMemory(MemoryChunk* chunk);
// PerformFreeMemory can be called concurrently when PreFree was executed
// before.
void PerformFreeMemory(MemoryChunk* chunk);
// See AllocatePage for public interface. Note that currently we only
// support pools for NOT_EXECUTABLE pages of size MemoryChunk::kPageSize.
base::Optional<MemoryChunkAllocationResult> AllocateUninitializedPageFromPool(
Space* space);
// Frees a pooled page. Only used on tear-down and last-resort GCs.
void FreePooledChunk(MemoryChunk* chunk);
// Initializes pages in a chunk. Returns the first page address.
// This function and GetChunkId() are provided for the mark-compact
// collector to rebuild page headers in the from space, which is
// used as a marking stack and its page headers are destroyed.
Page* InitializePagesInChunk(int chunk_id, int pages_in_chunk,
PagedSpace* space);
void UpdateAllocatedSpaceLimits(Address low, Address high,
Executability executable) {
// The use of atomic primitives does not guarantee correctness (wrt.
// desired semantics) by default. The loop here ensures that we update the
// values only if they did not change in between.
Address ptr;
switch (executable) {
case NOT_EXECUTABLE:
ptr = lowest_not_executable_ever_allocated_.load(
std::memory_order_relaxed);
while ((low < ptr) &&
!lowest_not_executable_ever_allocated_.compare_exchange_weak(
ptr, low, std::memory_order_acq_rel)) {
}
ptr = highest_not_executable_ever_allocated_.load(
std::memory_order_relaxed);
while ((high > ptr) &&
!highest_not_executable_ever_allocated_.compare_exchange_weak(
ptr, high, std::memory_order_acq_rel)) {
}
break;
case EXECUTABLE:
ptr = lowest_executable_ever_allocated_.load(std::memory_order_relaxed);
while ((low < ptr) &&
!lowest_executable_ever_allocated_.compare_exchange_weak(
ptr, low, std::memory_order_acq_rel)) {
}
ptr =
highest_executable_ever_allocated_.load(std::memory_order_relaxed);
while ((high > ptr) &&
!highest_executable_ever_allocated_.compare_exchange_weak(
ptr, high, std::memory_order_acq_rel)) {
}
break;
}
}
// Performs all necessary bookkeeping to free the memory, but does not free
// it.
void UnregisterMemoryChunk(MemoryChunk* chunk);
void UnregisterSharedBasicMemoryChunk(BasicMemoryChunk* chunk);
void UnregisterBasicMemoryChunk(BasicMemoryChunk* chunk,
Executability executable = NOT_EXECUTABLE);
void RegisterReadOnlyMemory(ReadOnlyPage* page);
#ifdef DEBUG
void RegisterExecutableMemoryChunk(MemoryChunk* chunk) {
base::MutexGuard guard(&executable_memory_mutex_);
DCHECK(chunk->IsFlagSet(MemoryChunk::IS_EXECUTABLE));
DCHECK_EQ(executable_memory_.find(chunk), executable_memory_.end());
executable_memory_.insert(chunk);
}
void UnregisterExecutableMemoryChunk(MemoryChunk* chunk) {
base::MutexGuard guard(&executable_memory_mutex_);
DCHECK_NE(executable_memory_.find(chunk), executable_memory_.end());
executable_memory_.erase(chunk);
}
#endif // DEBUG
Isolate* isolate_;
// Page allocator used for allocating data pages. Depending on the
// configuration it may be a page allocator instance provided by
// v8::Platform or a BoundedPageAllocator (when pointer compression is
// enabled).
v8::PageAllocator* data_page_allocator_;
// Page allocator used for allocating code pages. Depending on the
// configuration it may be a page allocator instance provided by v8::Platform
// or a BoundedPageAllocator from Heap::code_range_ (when pointer compression
// is enabled or on those 64-bit architectures where pc-relative 32-bit
// displacement can be used for call and jump instructions).
v8::PageAllocator* code_page_allocator_;
// Page allocator used for allocating trusted pages. When the sandbox is
// enabled, trusted pages are allocated outside of the sandbox so that their
// content cannot be corrupted by an attacker. When the sandbox is disabled,
// this is the same as data_page_allocator_.
v8::PageAllocator* trusted_page_allocator_;
// Maximum space size in bytes.
size_t capacity_;
// Allocated space size in bytes.
std::atomic<size_t> size_ = 0;
// Allocated executable space size in bytes.
std::atomic<size_t> size_executable_ = 0;
// We keep the lowest and highest addresses allocated as a quick way
// of determining that pointers are outside the heap. The estimate is
// conservative, i.e. not all addresses in 'allocated' space are allocated
// to our heap. The range is [lowest, highest[, inclusive on the low end
// and exclusive on the high end. Addresses are distinguished between
// executable and not-executable, as they may generally be placed in distinct
// areas of the heap.
std::atomic<Address> lowest_not_executable_ever_allocated_{
static_cast<Address>(-1ll)};
std::atomic<Address> highest_not_executable_ever_allocated_{kNullAddress};
std::atomic<Address> lowest_executable_ever_allocated_{
static_cast<Address>(-1ll)};
std::atomic<Address> highest_executable_ever_allocated_{kNullAddress};
base::Optional<VirtualMemory> reserved_chunk_at_virtual_memory_limit_;
Unmapper unmapper_;
#ifdef DEBUG
// Data structure to remember allocated executable memory chunks.
// This data structure is used only in DCHECKs.
std::unordered_set<MemoryChunk*, base::hash<MemoryChunk*>> executable_memory_;
base::Mutex executable_memory_mutex_;
#endif // DEBUG
#if defined(V8_ENABLE_CONSERVATIVE_STACK_SCANNING) || defined(DEBUG)
// Allocated normal and large pages are stored here, to be used during
// conservative stack scanning. The normal page set is guaranteed to contain
// Page*, and the large page set is guaranteed to contain LargePage*. We will
// be looking up BasicMemoryChunk*, however, and we want to avoid pointer
// casts that are technically undefined behaviour.
std::unordered_set<const BasicMemoryChunk*,
base::hash<const BasicMemoryChunk*>>
normal_pages_;
std::set<const BasicMemoryChunk*> large_pages_;
mutable base::Mutex pages_mutex_;
#endif // V8_ENABLE_CONSERVATIVE_STACK_SCANNING || DEBUG
V8_EXPORT_PRIVATE static size_t commit_page_size_;
V8_EXPORT_PRIVATE static size_t commit_page_size_bits_;
friend class heap::TestCodePageAllocatorScope;
friend class heap::TestMemoryAllocatorScope;
DISALLOW_IMPLICIT_CONSTRUCTORS(MemoryAllocator);
};
} // namespace internal
} // namespace v8
#endif // V8_HEAP_MEMORY_ALLOCATOR_H_