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

#include <limits>

#include "src/heap/heap-inl.h"
#include "src/logging/counters.h"
#include "src/objects/js-array-buffer-inl.h"
#include "src/objects/objects-inl.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-limits.h"
#include "src/wasm/wasm-memory.h"
#include "src/wasm/wasm-module.h"

namespace v8 {
namespace internal {
namespace wasm {

namespace {

constexpr size_t kNegativeGuardSize = 1u << 31;  // 2GiB

void AddAllocationStatusSample(Isolate* isolate,
                               WasmMemoryTracker::AllocationStatus status) {
  isolate->counters()->wasm_memory_allocation_result()->AddSample(
      static_cast<int>(status));
}

bool RunWithGCAndRetry(const std::function<bool()>& fn, Heap* heap,
                       bool* did_retry) {
  // Try up to three times; getting rid of dead JSArrayBuffer allocations might
  // require two GCs because the first GC maybe incremental and may have
  // floating garbage.
  static constexpr int kAllocationRetries = 2;

  for (int trial = 0;; ++trial) {
    if (fn()) return true;
    // {fn} failed. If {kAllocationRetries} is reached, fail.
    *did_retry = true;
    if (trial == kAllocationRetries) return false;
    // Otherwise, collect garbage and retry.
    // TODO(wasm): Since reservation limits are engine-wide, we should do an
    // engine-wide GC here (i.e. trigger a GC in each isolate using the engine,
    // and wait for them all to finish). See https://crbug.com/v8/9405.
    heap->MemoryPressureNotification(MemoryPressureLevel::kCritical, true);
  }
}

void* TryAllocateBackingStore(WasmMemoryTracker* memory_tracker, Heap* heap,
                              size_t size, size_t max_size,
                              void** allocation_base,
                              size_t* allocation_length) {
  using AllocationStatus = WasmMemoryTracker::AllocationStatus;
#if V8_TARGET_ARCH_64_BIT
  constexpr bool kRequireFullGuardRegions = true;
#else
  constexpr bool kRequireFullGuardRegions = false;
#endif
  // Let the WasmMemoryTracker know we are going to reserve a bunch of
  // address space.
  size_t reservation_size = std::max(max_size, size);
  bool did_retry = false;

  auto reserve_memory_space = [&] {
    // For guard regions, we always allocate the largest possible offset
    // into the heap, so the addressable memory after the guard page can
    // be made inaccessible.
    //
    // To protect against 32-bit integer overflow issues, we also
    // protect the 2GiB before the valid part of the memory buffer.
    *allocation_length =
        kRequireFullGuardRegions
            ? RoundUp(kWasmMaxHeapOffset + kNegativeGuardSize, CommitPageSize())
            : RoundUp(base::bits::RoundUpToPowerOfTwo(reservation_size),
                      kWasmPageSize);
    DCHECK_GE(*allocation_length, size);
    DCHECK_GE(*allocation_length, kWasmPageSize);

    return memory_tracker->ReserveAddressSpace(*allocation_length);
  };
  if (!RunWithGCAndRetry(reserve_memory_space, heap, &did_retry)) {
    // Reset reservation_size to initial size so that at least the initial size
    // can be allocated if maximum size reservation is not possible.
    reservation_size = size;

    // We are over the address space limit. Fail.
    //
    // When running under the correctness fuzzer (i.e.
    // --correctness-fuzzer-suppressions is preset), we crash
    // instead so it is not incorrectly reported as a correctness
    // violation. See https://crbug.com/828293#c4
    if (FLAG_correctness_fuzzer_suppressions) {
      FATAL("could not allocate wasm memory");
    }
    AddAllocationStatusSample(
        heap->isolate(), AllocationStatus::kAddressSpaceLimitReachedFailure);
    return nullptr;
  }

  // The Reserve makes the whole region inaccessible by default.
  DCHECK_NULL(*allocation_base);
  auto allocate_pages = [&] {
    *allocation_base =
        AllocatePages(GetPlatformPageAllocator(), nullptr, *allocation_length,
                      kWasmPageSize, PageAllocator::kNoAccess);
    return *allocation_base != nullptr;
  };
  if (!RunWithGCAndRetry(allocate_pages, heap, &did_retry)) {
    memory_tracker->ReleaseReservation(*allocation_length);
    AddAllocationStatusSample(heap->isolate(), AllocationStatus::kOtherFailure);
    return nullptr;
  }

  byte* memory = reinterpret_cast<byte*>(*allocation_base);
  if (kRequireFullGuardRegions) {
    memory += kNegativeGuardSize;
  }

  // Make the part we care about accessible.
  auto commit_memory = [&] {
    return size == 0 || SetPermissions(GetPlatformPageAllocator(), memory,
                                       RoundUp(size, kWasmPageSize),
                                       PageAllocator::kReadWrite);
  };
  // SetPermissions commits the extra memory, which may put us over the
  // process memory limit. If so, report this as an OOM.
  if (!RunWithGCAndRetry(commit_memory, heap, &did_retry)) {
    V8::FatalProcessOutOfMemory(nullptr, "TryAllocateBackingStore");
  }

  memory_tracker->RegisterAllocation(heap->isolate(), *allocation_base,
                                     *allocation_length, memory, size);
  AddAllocationStatusSample(heap->isolate(),
                            did_retry ? AllocationStatus::kSuccessAfterRetry
                                      : AllocationStatus::kSuccess);
  return memory;
}

#if V8_TARGET_ARCH_MIPS64
// MIPS64 has a user space of 2^40 bytes on most processors,
// address space limits needs to be smaller.
constexpr size_t kAddressSpaceLimit = 0x8000000000L;  // 512 GiB
#elif V8_TARGET_ARCH_64_BIT
constexpr size_t kAddressSpaceLimit = 0x10100000000L;  // 1 TiB + 4 GiB
#else
constexpr size_t kAddressSpaceLimit = 0xC0000000;  // 3 GiB
#endif

}  // namespace

WasmMemoryTracker::~WasmMemoryTracker() {
  // All reserved address space should be released before the allocation tracker
  // is destroyed.
  DCHECK_EQ(reserved_address_space_, 0u);
  DCHECK_EQ(allocated_address_space_, 0u);
  DCHECK(allocations_.empty());
}

void* WasmMemoryTracker::TryAllocateBackingStoreForTesting(
    Heap* heap, size_t size, void** allocation_base,
    size_t* allocation_length) {
  return TryAllocateBackingStore(this, heap, size, size, allocation_base,
                                 allocation_length);
}

void WasmMemoryTracker::FreeBackingStoreForTesting(base::AddressRegion memory,
                                                   void* buffer_start) {
  base::MutexGuard scope_lock(&mutex_);
  ReleaseAllocation_Locked(nullptr, buffer_start);
  CHECK(FreePages(GetPlatformPageAllocator(),
                  reinterpret_cast<void*>(memory.begin()), memory.size()));
}

bool WasmMemoryTracker::ReserveAddressSpace(size_t num_bytes) {
  size_t reservation_limit = kAddressSpaceLimit;
  while (true) {
    size_t old_count = reserved_address_space_.load();
    if (old_count > reservation_limit) return false;
    if (reservation_limit - old_count < num_bytes) return false;
    if (reserved_address_space_.compare_exchange_weak(old_count,
                                                      old_count + num_bytes)) {
      return true;
    }
  }
}

void WasmMemoryTracker::ReleaseReservation(size_t num_bytes) {
  size_t const old_reserved = reserved_address_space_.fetch_sub(num_bytes);
  USE(old_reserved);
  DCHECK_LE(num_bytes, old_reserved);
}

void WasmMemoryTracker::RegisterAllocation(Isolate* isolate,
                                           void* allocation_base,
                                           size_t allocation_length,
                                           void* buffer_start,
                                           size_t buffer_length) {
  base::MutexGuard scope_lock(&mutex_);

  allocated_address_space_ += allocation_length;
  // Report address space usage in MiB so the full range fits in an int on all
  // platforms.
  isolate->counters()->wasm_address_space_usage_mb()->AddSample(
      static_cast<int>(allocated_address_space_ / MB));

  allocations_.emplace(buffer_start,
                       AllocationData{allocation_base, allocation_length,
                                      buffer_start, buffer_length});
}

WasmMemoryTracker::AllocationData WasmMemoryTracker::ReleaseAllocation_Locked(
    Isolate* isolate, const void* buffer_start) {
  auto find_result = allocations_.find(buffer_start);
  CHECK_NE(find_result, allocations_.end());

  size_t num_bytes = find_result->second.allocation_length;
  DCHECK_LE(num_bytes, reserved_address_space_);
  DCHECK_LE(num_bytes, allocated_address_space_);
  reserved_address_space_ -= num_bytes;
  allocated_address_space_ -= num_bytes;

  AllocationData allocation_data = find_result->second;
  allocations_.erase(find_result);
  return allocation_data;
}

const WasmMemoryTracker::AllocationData* WasmMemoryTracker::FindAllocationData(
    const void* buffer_start) {
  base::MutexGuard scope_lock(&mutex_);
  const auto& result = allocations_.find(buffer_start);
  if (result != allocations_.end()) {
    return &result->second;
  }
  return nullptr;
}

bool WasmMemoryTracker::IsWasmMemory(const void* buffer_start) {
  base::MutexGuard scope_lock(&mutex_);
  return allocations_.find(buffer_start) != allocations_.end();
}

bool WasmMemoryTracker::IsWasmSharedMemory(const void* buffer_start) {
  base::MutexGuard scope_lock(&mutex_);
  const auto& result = allocations_.find(buffer_start);
  // Should be a wasm allocation, and registered as a shared allocation.
  return (result != allocations_.end() && result->second.is_shared);
}

void WasmMemoryTracker::MarkWasmMemoryNotGrowable(
    Handle<JSArrayBuffer> buffer) {
  base::MutexGuard scope_lock(&mutex_);
  const auto& allocation = allocations_.find(buffer->backing_store());
  if (allocation == allocations_.end()) return;
  allocation->second.is_growable = false;
}

bool WasmMemoryTracker::IsWasmMemoryGrowable(Handle<JSArrayBuffer> buffer) {
  base::MutexGuard scope_lock(&mutex_);
  if (buffer->backing_store() == nullptr) return true;
  const auto& allocation = allocations_.find(buffer->backing_store());
  if (allocation == allocations_.end()) return false;
  return allocation->second.is_growable;
}

bool WasmMemoryTracker::FreeWasmMemory(Isolate* isolate,
                                       const void* buffer_start) {
  base::MutexGuard scope_lock(&mutex_);
  const auto& result = allocations_.find(buffer_start);
  if (result == allocations_.end()) return false;
  if (result->second.is_shared) {
    // This is a shared WebAssembly.Memory allocation
    FreeMemoryIfNotShared_Locked(isolate, buffer_start);
    return true;
  }
  // This is a WebAssembly.Memory allocation
  const AllocationData allocation =
      ReleaseAllocation_Locked(isolate, buffer_start);
  CHECK(FreePages(GetPlatformPageAllocator(), allocation.allocation_base,
                  allocation.allocation_length));
  return true;
}

void WasmMemoryTracker::RegisterWasmMemoryAsShared(
    Handle<WasmMemoryObject> object, Isolate* isolate) {
  // Only register with the tracker if shared grow is enabled.
  if (!FLAG_wasm_grow_shared_memory) return;
  const void* backing_store = object->array_buffer().backing_store();
  // TODO(V8:8810): This should be a DCHECK, currently some tests do not
  // use a full WebAssembly.Memory, and fail on registering so return early.
  if (!IsWasmMemory(backing_store)) return;
  {
    base::MutexGuard scope_lock(&mutex_);
    // Register as shared allocation when it is post messaged. This happens only
    // the first time a buffer is shared over Postmessage, and track all the
    // memory objects that are associated with this backing store.
    RegisterSharedWasmMemory_Locked(object, isolate);
    // Add isolate to backing store mapping.
    isolates_per_buffer_[backing_store].emplace(isolate);
  }
}

void WasmMemoryTracker::SetPendingUpdateOnGrow(Handle<JSArrayBuffer> old_buffer,
                                               size_t new_size) {
  base::MutexGuard scope_lock(&mutex_);
  // Keep track of the new size of the buffer associated with each backing
  // store.
  AddBufferToGrowMap_Locked(old_buffer, new_size);
  // Request interrupt to GROW_SHARED_MEMORY to other isolates
  TriggerSharedGrowInterruptOnAllIsolates_Locked(old_buffer);
}

void WasmMemoryTracker::UpdateSharedMemoryInstances(Isolate* isolate) {
  base::MutexGuard scope_lock(&mutex_);
  // For every buffer in the grow_entry_map_, update the size for all the
  // memory objects associated with this isolate.
  for (auto it = grow_update_map_.begin(); it != grow_update_map_.end();) {
    UpdateSharedMemoryStateOnInterrupt_Locked(isolate, it->first, it->second);
    // If all the isolates that share this buffer have hit a stack check, their
    // memory objects are updated, and this grow entry can be erased.
    if (AreAllIsolatesUpdated_Locked(it->first)) {
      it = grow_update_map_.erase(it);
    } else {
      it++;
    }
  }
}

void WasmMemoryTracker::RegisterSharedWasmMemory_Locked(
    Handle<WasmMemoryObject> object, Isolate* isolate) {
  DCHECK(object->array_buffer().is_shared());

  void* backing_store = object->array_buffer().backing_store();
  // The allocation of a WasmMemoryObject should always be registered with the
  // WasmMemoryTracker.
  const auto& result = allocations_.find(backing_store);
  if (result == allocations_.end()) return;

  // Register the allocation as shared, if not alreadt marked as shared.
  if (!result->second.is_shared) result->second.is_shared = true;

  // Create persistent global handles for the memory objects that are shared
  GlobalHandles* global_handles = isolate->global_handles();
  object = global_handles->Create(*object);

  // Add to memory_object_vector to track memory objects, instance objects
  // that will need to be updated on a Grow call
  result->second.memory_object_vector.push_back(
      SharedMemoryObjectState(object, isolate));
}

void WasmMemoryTracker::AddBufferToGrowMap_Locked(
    Handle<JSArrayBuffer> old_buffer, size_t new_size) {
  void* backing_store = old_buffer->backing_store();
  auto entry = grow_update_map_.find(old_buffer->backing_store());
  if (entry == grow_update_map_.end()) {
    // No pending grow for this backing store, add to map.
    grow_update_map_.emplace(backing_store, new_size);
    return;
  }
  // If grow on the same buffer is requested before the update is complete,
  // the new_size should always be greater or equal to the old_size. Equal
  // in the case that grow(0) is called, but new buffer handles are mandated
  // by the Spec.
  CHECK_LE(entry->second, new_size);
  entry->second = new_size;
  // Flush instances_updated everytime a new grow size needs to be updates
  ClearUpdatedInstancesOnPendingGrow_Locked(backing_store);
}

void WasmMemoryTracker::TriggerSharedGrowInterruptOnAllIsolates_Locked(
    Handle<JSArrayBuffer> old_buffer) {
  // Request a GrowShareMemory interrupt on all the isolates that share
  // the backing store.
  const auto& isolates = isolates_per_buffer_.find(old_buffer->backing_store());
  for (const auto& isolate : isolates->second) {
    isolate->stack_guard()->RequestGrowSharedMemory();
  }
}

void WasmMemoryTracker::UpdateSharedMemoryStateOnInterrupt_Locked(
    Isolate* isolate, void* backing_store, size_t new_size) {
  // Update objects only if there are memory objects that share this backing
  // store, and this isolate is marked as one of the isolates that shares this
  // buffer.
  if (MemoryObjectsNeedUpdate_Locked(isolate, backing_store)) {
    UpdateMemoryObjectsForIsolate_Locked(isolate, backing_store, new_size);
    // As the memory objects are updated, add this isolate to a set of isolates
    // that are updated on grow. This state is maintained to track if all the
    // isolates that share the backing store have hit a StackCheck.
    isolates_updated_on_grow_[backing_store].emplace(isolate);
  }
}

bool WasmMemoryTracker::AreAllIsolatesUpdated_Locked(
    const void* backing_store) {
  const auto& buffer_isolates = isolates_per_buffer_.find(backing_store);
  // No isolates share this buffer.
  if (buffer_isolates == isolates_per_buffer_.end()) return true;
  const auto& updated_isolates = isolates_updated_on_grow_.find(backing_store);
  // Some isolates share the buffer, but no isolates have been updated yet.
  if (updated_isolates == isolates_updated_on_grow_.end()) return false;
  if (buffer_isolates->second == updated_isolates->second) {
    // If all the isolates that share this backing_store have hit a stack check,
    // and the memory objects have been updated, remove the entry from the
    // updatemap, and return true.
    isolates_updated_on_grow_.erase(backing_store);
    return true;
  }
  return false;
}

void WasmMemoryTracker::ClearUpdatedInstancesOnPendingGrow_Locked(
    const void* backing_store) {
  // On multiple grows to the same buffer, the entries for that buffer should be
  // flushed. This is done so that any consecutive grows to the same buffer will
  // update all instances that share this buffer.
  const auto& value = isolates_updated_on_grow_.find(backing_store);
  if (value != isolates_updated_on_grow_.end()) {
    value->second.clear();
  }
}

void WasmMemoryTracker::UpdateMemoryObjectsForIsolate_Locked(
    Isolate* isolate, void* backing_store, size_t new_size) {
  const auto& result = allocations_.find(backing_store);
  if (result == allocations_.end() || !result->second.is_shared) return;
  for (const auto& memory_obj_state : result->second.memory_object_vector) {
    DCHECK_NE(memory_obj_state.isolate, nullptr);
    if (isolate == memory_obj_state.isolate) {
      HandleScope scope(isolate);
      Handle<WasmMemoryObject> memory_object = memory_obj_state.memory_object;
      DCHECK(memory_object->IsWasmMemoryObject());
      DCHECK(memory_object->array_buffer().is_shared());
      // Permissions adjusted, but create a new buffer with new size
      // and old attributes. Buffer has already been allocated,
      // just create a new buffer with same backing store.
      bool is_external = memory_object->array_buffer().is_external();
      Handle<JSArrayBuffer> new_buffer = SetupArrayBuffer(
          isolate, backing_store, new_size, is_external, SharedFlag::kShared);
      memory_obj_state.memory_object->update_instances(isolate, new_buffer);
    }
  }
}

bool WasmMemoryTracker::MemoryObjectsNeedUpdate_Locked(
    Isolate* isolate, const void* backing_store) {
  // Return true if this buffer has memory_objects it needs to update.
  const auto& result = allocations_.find(backing_store);
  if (result == allocations_.end() || !result->second.is_shared) return false;
  // Only update if the buffer has memory objects that need to be updated.
  if (result->second.memory_object_vector.empty()) return false;
  const auto& isolate_entry = isolates_per_buffer_.find(backing_store);
  return (isolate_entry != isolates_per_buffer_.end() &&
          isolate_entry->second.count(isolate) != 0);
}

void WasmMemoryTracker::FreeMemoryIfNotShared_Locked(
    Isolate* isolate, const void* backing_store) {
  RemoveSharedBufferState_Locked(isolate, backing_store);
  if (CanFreeSharedMemory_Locked(backing_store)) {
    const AllocationData allocation =
        ReleaseAllocation_Locked(isolate, backing_store);
    CHECK(FreePages(GetPlatformPageAllocator(), allocation.allocation_base,
                    allocation.allocation_length));
  }
}

bool WasmMemoryTracker::CanFreeSharedMemory_Locked(const void* backing_store) {
  const auto& value = isolates_per_buffer_.find(backing_store);
  // If no isolates share this buffer, backing store can be freed.
  // Erase the buffer entry.
  if (value == isolates_per_buffer_.end() || value->second.empty()) return true;
  return false;
}

void WasmMemoryTracker::RemoveSharedBufferState_Locked(
    Isolate* isolate, const void* backing_store) {
  if (isolate != nullptr) {
    DestroyMemoryObjectsAndRemoveIsolateEntry_Locked(isolate, backing_store);
    RemoveIsolateFromBackingStore_Locked(isolate, backing_store);
  } else {
    // This happens for externalized contents cleanup shared memory state
    // associated with this buffer across isolates.
    DestroyMemoryObjectsAndRemoveIsolateEntry_Locked(backing_store);
  }
}

void WasmMemoryTracker::DestroyMemoryObjectsAndRemoveIsolateEntry_Locked(
    const void* backing_store) {
  const auto& result = allocations_.find(backing_store);
  CHECK(result != allocations_.end() && result->second.is_shared);
  auto& object_vector = result->second.memory_object_vector;
  if (object_vector.empty()) return;
  for (const auto& mem_obj_state : object_vector) {
    GlobalHandles::Destroy(mem_obj_state.memory_object.location());
  }
  object_vector.clear();
  // Remove isolate from backing store map.
  isolates_per_buffer_.erase(backing_store);
}

void WasmMemoryTracker::DestroyMemoryObjectsAndRemoveIsolateEntry_Locked(
    Isolate* isolate, const void* backing_store) {
  // This gets called when an internal handle to the ArrayBuffer should be
  // freed, on heap tear down for that isolate, remove the memory objects
  // that are associated with this buffer and isolate.
  const auto& result = allocations_.find(backing_store);
  CHECK(result != allocations_.end() && result->second.is_shared);
  auto& object_vector = result->second.memory_object_vector;
  if (object_vector.empty()) return;
  for (auto it = object_vector.begin(); it != object_vector.end();) {
    if (isolate == it->isolate) {
      GlobalHandles::Destroy(it->memory_object.location());
      it = object_vector.erase(it);
    } else {
      ++it;
    }
  }
}

void WasmMemoryTracker::RemoveIsolateFromBackingStore_Locked(
    Isolate* isolate, const void* backing_store) {
  const auto& isolates = isolates_per_buffer_.find(backing_store);
  if (isolates == isolates_per_buffer_.end() || isolates->second.empty())
    return;
  isolates->second.erase(isolate);
}

void WasmMemoryTracker::DeleteSharedMemoryObjectsOnIsolate(Isolate* isolate) {
  base::MutexGuard scope_lock(&mutex_);
  // This is possible for buffers that are externalized, and their handles have
  // been freed, the backing store wasn't released because externalized contents
  // were using it.
  if (isolates_per_buffer_.empty()) return;
  for (auto& entry : isolates_per_buffer_) {
    if (entry.second.find(isolate) == entry.second.end()) continue;
    const void* backing_store = entry.first;
    entry.second.erase(isolate);
    DestroyMemoryObjectsAndRemoveIsolateEntry_Locked(isolate, backing_store);
  }
  for (auto& buffer_isolates : isolates_updated_on_grow_) {
    auto& isolates = buffer_isolates.second;
    isolates.erase(isolate);
  }
}

Handle<JSArrayBuffer> SetupArrayBuffer(Isolate* isolate, void* backing_store,
                                       size_t size, bool is_external,
                                       SharedFlag shared) {
  Handle<JSArrayBuffer> buffer =
      isolate->factory()->NewJSArrayBuffer(shared, AllocationType::kOld);
  constexpr bool is_wasm_memory = true;
  JSArrayBuffer::Setup(buffer, isolate, is_external, backing_store, size,
                       shared, is_wasm_memory);
  buffer->set_is_detachable(false);
  return buffer;
}

MaybeHandle<JSArrayBuffer> AllocateAndSetupArrayBuffer(Isolate* isolate,
                                                       size_t size,
                                                       size_t maximum_size,
                                                       SharedFlag shared) {
  // Enforce flag-limited maximum allocation size.
  if (size > max_mem_bytes()) return {};

  WasmMemoryTracker* memory_tracker = isolate->wasm_engine()->memory_tracker();

  // Set by TryAllocateBackingStore.
  void* allocation_base = nullptr;
  size_t allocation_length = 0;

  void* memory = TryAllocateBackingStore(memory_tracker, isolate->heap(), size,
                                         maximum_size, &allocation_base,
                                         &allocation_length);
  if (memory == nullptr) return {};

#if DEBUG
  // Double check the API allocator actually zero-initialized the memory.
  const byte* bytes = reinterpret_cast<const byte*>(memory);
  for (size_t i = 0; i < size; ++i) {
    DCHECK_EQ(0, bytes[i]);
  }
#endif

  reinterpret_cast<v8::Isolate*>(isolate)
      ->AdjustAmountOfExternalAllocatedMemory(size);

  constexpr bool is_external = false;
  return SetupArrayBuffer(isolate, memory, size, is_external, shared);
}

MaybeHandle<JSArrayBuffer> NewArrayBuffer(Isolate* isolate, size_t size) {
  return AllocateAndSetupArrayBuffer(isolate, size, size,
                                     SharedFlag::kNotShared);
}

MaybeHandle<JSArrayBuffer> NewSharedArrayBuffer(Isolate* isolate,
                                                size_t initial_size,
                                                size_t max_size) {
  return AllocateAndSetupArrayBuffer(isolate, initial_size, max_size,
                                     SharedFlag::kShared);
}

void DetachMemoryBuffer(Isolate* isolate, Handle<JSArrayBuffer> buffer,
                        bool free_memory) {
  if (buffer->is_shared()) return;  // Detaching shared buffers is impossible.
  DCHECK(!buffer->is_detachable());

  const bool is_external = buffer->is_external();
  DCHECK(!buffer->is_detachable());
  if (!is_external) {
    buffer->set_is_external(true);
    isolate->heap()->UnregisterArrayBuffer(*buffer);
    if (free_memory) {
      // We need to free the memory before detaching the buffer because
      // FreeBackingStore reads buffer->allocation_base(), which is nulled out
      // by Detach. This means there is a dangling pointer until we detach the
      // buffer. Since there is no way for the user to directly call
      // FreeBackingStore, we can ensure this is safe.
      buffer->FreeBackingStoreFromMainThread();
    }
  }

  DCHECK(buffer->is_external());
  buffer->set_is_wasm_memory(false);
  buffer->set_is_detachable(true);
  buffer->Detach();
}

}  // namespace wasm
}  // namespace internal
}  // namespace v8