// 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 "src/wasm/wasm-memory.h"
#include "src/objects-inl.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-limits.h"
#include "src/wasm/wasm-module.h"

namespace v8 {
namespace internal {
namespace wasm {

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

bool WasmAllocationTracker::ReserveAddressSpace(size_t num_bytes) {
// Address space reservations are currently only meaningful using guard
// regions, which is currently only supported on 64-bit systems. On other
// platforms, we always fall back on bounds checks.
#if V8_TARGET_ARCH_64_BIT
  static constexpr size_t kAddressSpaceLimit = 0x10000000000L;  // 1 TiB

  size_t const old_count = allocated_address_space_.fetch_add(num_bytes);
  DCHECK_GE(old_count + num_bytes, old_count);
  if (old_count + num_bytes <= kAddressSpaceLimit) {
    return true;
  }
  allocated_address_space_ -= num_bytes;
#endif
  return false;
}

void WasmAllocationTracker::ReleaseAddressSpace(size_t num_bytes) {
  DCHECK_LE(num_bytes, allocated_address_space_);
  allocated_address_space_ -= num_bytes;
}

void* TryAllocateBackingStore(Isolate* isolate, size_t size,
                              bool require_guard_regions,
                              void** allocation_base,
                              size_t* allocation_length) {
  // TODO(eholk): Right now require_guard_regions has no effect on 32-bit
  // systems. It may be safer to fail instead, given that other code might do
  // things that would be unsafe if they expected guard pages where there
  // weren't any.
  if (require_guard_regions) {
    // TODO(eholk): On Windows we want to make sure we don't commit the guard
    // pages yet.

    // We always allocate the largest possible offset into the heap, so the
    // addressable memory after the guard page can be made inaccessible.
    size_t page_size = AllocatePageSize();
    *allocation_length = RoundUp(kWasmMaxHeapOffset, page_size);
    DCHECK_EQ(0, size % page_size);

    WasmAllocationTracker* const allocation_tracker =
        isolate->wasm_engine()->allocation_tracker();

    // Let the WasmAllocationTracker know we are going to reserve a bunch of
    // address space.
    if (!allocation_tracker->ReserveAddressSpace(*allocation_length)) {
      // If we are over the address space limit, fail.
      return nullptr;
    }

    // Make the whole region inaccessible by default.
    *allocation_base = AllocatePages(nullptr, *allocation_length, kWasmPageSize,
                                     PageAllocator::kNoAccess);
    if (*allocation_base == nullptr) {
      allocation_tracker->ReleaseAddressSpace(*allocation_length);
      return nullptr;
    }

    void* memory = *allocation_base;

    // Make the part we care about accessible.
    CHECK(SetPermissions(memory, size, PageAllocator::kReadWrite));

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

    return memory;
  } else {
    // TODO(titzer): use guard regions for minicage and merge with above code.
    CHECK_LE(size, kV8MaxWasmMemoryBytes);
    *allocation_length =
        base::bits::RoundUpToPowerOfTwo32(static_cast<uint32_t>(size));
    void* memory =
        size == 0
            ? nullptr
            : isolate->array_buffer_allocator()->Allocate(*allocation_length);
    *allocation_base = memory;
    return memory;
  }
}

Handle<JSArrayBuffer> SetupArrayBuffer(Isolate* isolate, void* allocation_base,
                                       size_t allocation_length,
                                       void* backing_store, size_t size,
                                       bool is_external,
                                       bool enable_guard_regions,
                                       SharedFlag shared) {
  Handle<JSArrayBuffer> buffer =
      isolate->factory()->NewJSArrayBuffer(shared, TENURED);
  DCHECK_GE(kMaxInt, size);
  if (shared == SharedFlag::kShared) DCHECK(FLAG_experimental_wasm_threads);
  JSArrayBuffer::Setup(buffer, isolate, is_external, allocation_base,
                       allocation_length, backing_store, static_cast<int>(size),
                       shared);
  buffer->set_is_neuterable(false);
  buffer->set_is_growable(true);
  buffer->set_has_guard_region(enable_guard_regions);
  return buffer;
}

Handle<JSArrayBuffer> NewArrayBuffer(Isolate* isolate, size_t size,
                                     bool require_guard_regions,
                                     SharedFlag shared) {
  // Check against kMaxInt, since the byte length is stored as int in the
  // JSArrayBuffer. Note that wasm_max_mem_pages can be raised from the command
  // line, and we don't want to fail a CHECK then.
  if (size > FLAG_wasm_max_mem_pages * kWasmPageSize || size > kMaxInt) {
    // TODO(titzer): lift restriction on maximum memory allocated here.
    return Handle<JSArrayBuffer>::null();
  }

  void* allocation_base = nullptr;  // Set by TryAllocateBackingStore
  size_t allocation_length = 0;     // Set by TryAllocateBackingStore
  // Do not reserve memory till non zero memory is encountered.
  void* memory = (size == 0) ? nullptr
                             : TryAllocateBackingStore(
                                   isolate, size, require_guard_regions,
                                   &allocation_base, &allocation_length);

  if (size > 0 && memory == nullptr) {
    return Handle<JSArrayBuffer>::null();
  }

#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

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

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

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

  DCHECK(buffer->is_external());
  buffer->set_is_neuterable(true);
  buffer->Neuter();
}

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