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