// 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_PAGED_SPACES_INL_H_
#define V8_HEAP_PAGED_SPACES_INL_H_

#include "src/common/globals.h"
#include "src/heap/heap-inl.h"
#include "src/heap/incremental-marking.h"
#include "src/heap/paged-spaces.h"
#include "src/objects/heap-object.h"
#include "src/objects/objects-inl.h"

namespace v8 {
namespace internal {

// -----------------------------------------------------------------------------
// PagedSpaceObjectIterator

HeapObject PagedSpaceObjectIterator::Next() {
  do {
    HeapObject next_obj = FromCurrentPage();
    if (!next_obj.is_null()) return next_obj;
  } while (AdvanceToNextPage());
  return HeapObject();
}

HeapObject PagedSpaceObjectIterator::FromCurrentPage() {
  while (cur_addr_ != cur_end_) {
    HeapObject obj = HeapObject::FromAddress(cur_addr_);
    const int obj_size = obj.Size();
    cur_addr_ += obj_size;
    DCHECK_LE(cur_addr_, cur_end_);
    if (!obj.IsFreeSpaceOrFiller(cage_base())) {
      if (obj.IsCode(cage_base())) {
        DCHECK_EQ(space_->identity(), CODE_SPACE);
        DCHECK_CODEOBJECT_SIZE(obj_size, space_);
      } else {
        DCHECK_OBJECT_SIZE(obj_size);
      }
      return obj;
    }
  }
  return HeapObject();
}

bool PagedSpace::Contains(Address addr) const {
  if (V8_ENABLE_THIRD_PARTY_HEAP_BOOL) {
    return true;
  }
  return Page::FromAddress(addr)->owner() == this;
}

bool PagedSpace::Contains(Object o) const {
  if (!o.IsHeapObject()) return false;
  return Page::FromAddress(o.ptr())->owner() == this;
}

void PagedSpace::UnlinkFreeListCategories(Page* page) {
  DCHECK_EQ(this, page->owner());
  page->ForAllFreeListCategories([this](FreeListCategory* category) {
    free_list()->RemoveCategory(category);
  });
}

size_t PagedSpace::RelinkFreeListCategories(Page* page) {
  DCHECK_EQ(this, page->owner());
  size_t added = 0;
  page->ForAllFreeListCategories([this, &added](FreeListCategory* category) {
    added += category->available();
    category->Relink(free_list());
  });

  DCHECK_IMPLIES(!page->IsFlagSet(Page::NEVER_ALLOCATE_ON_PAGE),
                 page->AvailableInFreeList() ==
                     page->AvailableInFreeListFromAllocatedBytes());
  return added;
}

bool PagedSpace::TryFreeLast(Address object_address, int object_size) {
  if (allocation_info_.top() != kNullAddress) {
    return allocation_info_.DecrementTopIfAdjacent(object_address, object_size);
  }
  return false;
}

bool PagedSpace::EnsureLabMain(int size_in_bytes, AllocationOrigin origin) {
  if (allocation_info_.top() + size_in_bytes <= allocation_info_.limit()) {
    return true;
  }
  return RefillLabMain(size_in_bytes, origin);
}

AllocationResult PagedSpace::AllocateFastUnaligned(int size_in_bytes) {
  if (!allocation_info_.CanIncrementTop(size_in_bytes)) {
    return AllocationResult::Retry(identity());
  }
  return AllocationResult(
      HeapObject::FromAddress(allocation_info_.IncrementTop(size_in_bytes)));
}

AllocationResult PagedSpace::AllocateFastAligned(
    int size_in_bytes, int* aligned_size_in_bytes,
    AllocationAlignment alignment) {
  Address current_top = allocation_info_.top();
  int filler_size = Heap::GetFillToAlign(current_top, alignment);
  int aligned_size = filler_size + size_in_bytes;
  if (!allocation_info_.CanIncrementTop(aligned_size)) {
    return AllocationResult::Retry(identity());
  }
  HeapObject obj =
      HeapObject::FromAddress(allocation_info_.IncrementTop(aligned_size));
  if (aligned_size_in_bytes) *aligned_size_in_bytes = aligned_size;
  if (filler_size > 0) {
    obj = heap()->PrecedeWithFiller(obj, filler_size);
  }
  return AllocationResult(obj);
}

AllocationResult PagedSpace::AllocateRawUnaligned(int size_in_bytes,
                                                  AllocationOrigin origin) {
  DCHECK(!FLAG_enable_third_party_heap);
  if (!EnsureLabMain(size_in_bytes, origin)) {
    return AllocationResult::Retry(identity());
  }

  AllocationResult result = AllocateFastUnaligned(size_in_bytes);
  DCHECK(!result.IsRetry());
  MSAN_ALLOCATED_UNINITIALIZED_MEMORY(result.ToObjectChecked().address(),
                                      size_in_bytes);

  if (FLAG_trace_allocations_origins) {
    UpdateAllocationOrigins(origin);
  }

  InvokeAllocationObservers(result.ToAddress(), size_in_bytes, size_in_bytes,
                            size_in_bytes);

  return result;
}

AllocationResult PagedSpace::AllocateRawAligned(int size_in_bytes,
                                                AllocationAlignment alignment,
                                                AllocationOrigin origin) {
  DCHECK(!FLAG_enable_third_party_heap);
  DCHECK_EQ(identity(), OLD_SPACE);
  int allocation_size = size_in_bytes;
  // We don't know exactly how much filler we need to align until space is
  // allocated, so assume the worst case.
  int filler_size = Heap::GetMaximumFillToAlign(alignment);
  allocation_size += filler_size;
  if (!EnsureLabMain(allocation_size, origin)) {
    return AllocationResult::Retry(identity());
  }
  int aligned_size_in_bytes;
  AllocationResult result =
      AllocateFastAligned(size_in_bytes, &aligned_size_in_bytes, alignment);
  DCHECK(!result.IsRetry());
  MSAN_ALLOCATED_UNINITIALIZED_MEMORY(result.ToObjectChecked().address(),
                                      size_in_bytes);

  if (FLAG_trace_allocations_origins) {
    UpdateAllocationOrigins(origin);
  }

  InvokeAllocationObservers(result.ToAddress(), size_in_bytes,
                            aligned_size_in_bytes, allocation_size);

  return result;
}

AllocationResult PagedSpace::AllocateRaw(int size_in_bytes,
                                         AllocationAlignment alignment,
                                         AllocationOrigin origin) {
  DCHECK(!FLAG_enable_third_party_heap);
  AllocationResult result;

  if (alignment != kWordAligned) {
    result = AllocateFastAligned(size_in_bytes, nullptr, alignment);
  } else {
    result = AllocateFastUnaligned(size_in_bytes);
  }

  if (!result.IsRetry()) {
    return result;
  } else {
    return AllocateRawSlow(size_in_bytes, alignment, origin);
  }
}

}  // namespace internal
}  // namespace v8

#endif  // V8_HEAP_PAGED_SPACES_INL_H_