blob: ecb6203ac5aba5091dbc9a2a08f9f03e57bf0090 [file] [log] [blame]
// Copyright 2017 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_WASM_WASM_MEMORY_H_
#define V8_WASM_WASM_MEMORY_H_
#include <atomic>
#include <unordered_map>
#include <unordered_set>
#include "src/base/platform/mutex.h"
#include "src/flags/flags.h"
#include "src/handles/handles.h"
#include "src/objects/js-array-buffer.h"
namespace v8 {
namespace internal {
namespace wasm {
// The {WasmMemoryTracker} tracks reservations and allocations for wasm memory
// and wasm code. There is an upper limit on the total reserved memory which is
// checked by this class. Allocations are stored so we can look them up when an
// array buffer dies and figure out the reservation and allocation bounds for
// that buffer.
class WasmMemoryTracker {
public:
WasmMemoryTracker() = default;
V8_EXPORT_PRIVATE ~WasmMemoryTracker();
// ReserveAddressSpace attempts to increase the reserved address space counter
// by {num_bytes}. Returns true if successful (meaning it is okay to go ahead
// and reserve {num_bytes} bytes), false otherwise.
bool ReserveAddressSpace(size_t num_bytes);
void RegisterAllocation(Isolate* isolate, void* allocation_base,
size_t allocation_length, void* buffer_start,
size_t buffer_length);
struct SharedMemoryObjectState {
Handle<WasmMemoryObject> memory_object;
Isolate* isolate;
SharedMemoryObjectState() = default;
SharedMemoryObjectState(Handle<WasmMemoryObject> memory_object,
Isolate* isolate)
: memory_object(memory_object), isolate(isolate) {}
};
struct AllocationData {
void* allocation_base = nullptr;
size_t allocation_length = 0;
void* buffer_start = nullptr;
size_t buffer_length = 0;
bool is_shared = false;
// Wasm memories are growable by default, this will be false only when
// shared with an asmjs module.
bool is_growable = true;
// Track Wasm Memory instances across isolates, this is populated on
// PostMessage using persistent handles for memory objects.
std::vector<WasmMemoryTracker::SharedMemoryObjectState>
memory_object_vector;
private:
AllocationData() = default;
AllocationData(void* allocation_base, size_t allocation_length,
void* buffer_start, size_t buffer_length)
: allocation_base(allocation_base),
allocation_length(allocation_length),
buffer_start(buffer_start),
buffer_length(buffer_length) {
DCHECK_LE(reinterpret_cast<uintptr_t>(allocation_base),
reinterpret_cast<uintptr_t>(buffer_start));
DCHECK_GE(
reinterpret_cast<uintptr_t>(allocation_base) + allocation_length,
reinterpret_cast<uintptr_t>(buffer_start));
DCHECK_GE(
reinterpret_cast<uintptr_t>(allocation_base) + allocation_length,
reinterpret_cast<uintptr_t>(buffer_start) + buffer_length);
}
friend WasmMemoryTracker;
};
// Allow tests to allocate a backing store the same way as we do it for
// WebAssembly memory. This is used in unit tests for trap handler to
// generate the same signals/exceptions for invalid memory accesses as
// we would get with WebAssembly memory.
V8_EXPORT_PRIVATE void* TryAllocateBackingStoreForTesting(
Heap* heap, size_t size, void** allocation_base,
size_t* allocation_length);
// Free memory allocated with TryAllocateBackingStoreForTesting.
V8_EXPORT_PRIVATE void FreeBackingStoreForTesting(base::AddressRegion memory,
void* buffer_start);
// Decreases the amount of reserved address space.
void ReleaseReservation(size_t num_bytes);
V8_EXPORT_PRIVATE bool IsWasmMemory(const void* buffer_start);
bool IsWasmSharedMemory(const void* buffer_start);
// Returns a pointer to a Wasm buffer's allocation data, or nullptr if the
// buffer is not tracked.
V8_EXPORT_PRIVATE const AllocationData* FindAllocationData(
const void* buffer_start);
// Free Memory allocated by the Wasm memory tracker
bool FreeWasmMemory(Isolate* isolate, const void* buffer_start);
void MarkWasmMemoryNotGrowable(Handle<JSArrayBuffer> buffer);
bool IsWasmMemoryGrowable(Handle<JSArrayBuffer> buffer);
// When WebAssembly.Memory is transferred over PostMessage, register the
// allocation as shared and track the memory objects that will need
// updating if memory is resized.
void RegisterWasmMemoryAsShared(Handle<WasmMemoryObject> object,
Isolate* isolate);
// This method is called when the underlying backing store is grown, but
// instances that share the backing_store have not yet been updated.
void SetPendingUpdateOnGrow(Handle<JSArrayBuffer> old_buffer,
size_t new_size);
// Interrupt handler for GROW_SHARED_MEMORY interrupt. Update memory objects
// and instances that share the memory objects after a Grow call.
void UpdateSharedMemoryInstances(Isolate* isolate);
// Due to timing of when buffers are garbage collected, vs. when isolate
// object handles are destroyed, it is possible to leak global handles. To
// avoid this, cleanup any global handles on isolate destruction if any exist.
void DeleteSharedMemoryObjectsOnIsolate(Isolate* isolate);
// Allocation results are reported to UMA
//
// See wasm_memory_allocation_result in counters.h
enum class AllocationStatus {
kSuccess, // Succeeded on the first try
kSuccessAfterRetry, // Succeeded after garbage collection
kAddressSpaceLimitReachedFailure, // Failed because Wasm is at its address
// space limit
kOtherFailure // Failed for an unknown reason
};
private:
// Helper methods to free memory only if not shared by other isolates, memory
// objects.
void FreeMemoryIfNotShared_Locked(Isolate* isolate,
const void* backing_store);
bool CanFreeSharedMemory_Locked(const void* backing_store);
void RemoveSharedBufferState_Locked(Isolate* isolate,
const void* backing_store);
// Registers the allocation as shared, and tracks all the memory objects
// associates with this allocation across isolates.
void RegisterSharedWasmMemory_Locked(Handle<WasmMemoryObject> object,
Isolate* isolate);
// Map the new size after grow to the buffer backing store, so that instances
// and memory objects that share the WebAssembly.Memory across isolates can
// be updated..
void AddBufferToGrowMap_Locked(Handle<JSArrayBuffer> old_buffer,
size_t new_size);
// Trigger a GROW_SHARED_MEMORY interrupt on all the isolates that have memory
// objects that share this buffer.
void TriggerSharedGrowInterruptOnAllIsolates_Locked(
Handle<JSArrayBuffer> old_buffer);
// When isolates hit a stack check, update the memory objects associated with
// that isolate.
void UpdateSharedMemoryStateOnInterrupt_Locked(Isolate* isolate,
void* backing_store,
size_t new_size);
// Check if all the isolates that share a backing_store have hit a stack
// check. If a stack check is hit, and the backing store is pending grow,
// this isolate will have updated memory objects.
bool AreAllIsolatesUpdated_Locked(const void* backing_store);
// If a grow call is made to a buffer with a pending grow, and all the
// isolates that share this buffer have not hit a StackCheck, clear the set of
// already updated instances so they can be updated with the new size on the
// most recent grow call.
void ClearUpdatedInstancesOnPendingGrow_Locked(const void* backing_store);
// Helper functions to update memory objects on grow, and maintain state for
// which isolates hit a stack check.
void UpdateMemoryObjectsForIsolate_Locked(Isolate* isolate,
void* backing_store,
size_t new_size);
bool MemoryObjectsNeedUpdate_Locked(Isolate* isolate,
const void* backing_store);
// Destroy global handles to memory objects, and remove backing store from
// isolates_per_buffer on Free.
void DestroyMemoryObjectsAndRemoveIsolateEntry_Locked(
Isolate* isolate, const void* backing_store);
void DestroyMemoryObjectsAndRemoveIsolateEntry_Locked(
const void* backing_store);
void RemoveIsolateFromBackingStore_Locked(Isolate* isolate,
const void* backing_store);
// Removes an allocation from the tracker.
AllocationData ReleaseAllocation_Locked(Isolate* isolate,
const void* buffer_start);
// Clients use a two-part process. First they "reserve" the address space,
// which signifies an intent to actually allocate it. This determines whether
// doing the allocation would put us over our limit. Once there is a
// reservation, clients can do the allocation and register the result.
//
// We should always have:
// allocated_address_space_ <= reserved_address_space_ <= kAddressSpaceLimit
std::atomic<size_t> reserved_address_space_{0};
// Used to protect access to the allocated address space counter and
// allocation map. This is needed because Wasm memories can be freed on
// another thread by the ArrayBufferTracker.
base::Mutex mutex_;
size_t allocated_address_space_ = 0;
//////////////////////////////////////////////////////////////////////////////
// Protected by {mutex_}:
// Track Wasm memory allocation information. This is keyed by the start of the
// buffer, rather than by the start of the allocation.
std::unordered_map<const void*, AllocationData> allocations_;
// Maps each buffer to the isolates that share the backing store.
std::unordered_map<const void*, std::unordered_set<Isolate*>>
isolates_per_buffer_;
// Maps which isolates have had a grow interrupt handled on the buffer. This
// is maintained to ensure that the instances are updated with the right size
// on Grow.
std::unordered_map<const void*, std::unordered_set<Isolate*>>
isolates_updated_on_grow_;
// Maps backing stores(void*) to the size of the underlying memory in
// (size_t). An entry to this map is made on a grow call to the corresponding
// backing store. On consecutive grow calls to the same backing store,
// the size entry is updated. This entry is made right after the mprotect
// call to change the protections on a backing_store, so the memory objects
// have not been updated yet. The backing store entry in this map is erased
// when all the memory objects, or instances that share this backing store
// have their bounds updated.
std::unordered_map<void*, size_t> grow_update_map_;
// End of fields protected by {mutex_}.
//////////////////////////////////////////////////////////////////////////////
DISALLOW_COPY_AND_ASSIGN(WasmMemoryTracker);
};
// Attempts to allocate an array buffer with guard regions suitable for trap
// handling. If address space is not available, it will return a buffer with
// mini-guards that will require bounds checks.
V8_EXPORT_PRIVATE MaybeHandle<JSArrayBuffer> NewArrayBuffer(Isolate*,
size_t size);
// Attempts to allocate a SharedArrayBuffer with guard regions suitable for
// trap handling. If address space is not available, it will try to reserve
// up to the maximum for that memory. If all else fails, it will return a
// buffer with mini-guards of initial size.
V8_EXPORT_PRIVATE MaybeHandle<JSArrayBuffer> NewSharedArrayBuffer(
Isolate*, size_t initial_size, size_t max_size);
Handle<JSArrayBuffer> SetupArrayBuffer(
Isolate*, void* backing_store, size_t size, bool is_external,
SharedFlag shared = SharedFlag::kNotShared);
V8_EXPORT_PRIVATE void DetachMemoryBuffer(Isolate* isolate,
Handle<JSArrayBuffer> buffer,
bool free_memory);
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_WASM_WASM_MEMORY_H_