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

#include "src/v8.h"

#include "src/heap/heap-inl.h"
#include "src/heap/heap.h"
#include "src/heap/invalidated-slots-inl.h"
#include "src/heap/invalidated-slots.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-tester.h"
#include "test/cctest/heap/heap-utils.h"

namespace v8 {
namespace internal {
namespace heap {

Page* HeapTester::AllocateByteArraysOnPage(
    Heap* heap, std::vector<ByteArray*>* byte_arrays) {
  const int kLength = 256 - ByteArray::kHeaderSize;
  const int kSize = ByteArray::SizeFor(kLength);
  CHECK_EQ(kSize, 256);
  Isolate* isolate = heap->isolate();
  PagedSpace* old_space = heap->old_space();
  Page* page;
  // Fill a page with byte arrays.
  {
    AlwaysAllocateScope always_allocate(isolate);
    heap::SimulateFullSpace(old_space);
    ByteArray* byte_array;
    CHECK(heap->AllocateByteArray(kLength, TENURED).To(&byte_array));
    byte_arrays->push_back(byte_array);
    page = Page::FromAddress(byte_array->address());
    CHECK_EQ(page->area_size() % kSize, 0u);
    size_t n = page->area_size() / kSize;
    for (size_t i = 1; i < n; i++) {
      CHECK(heap->AllocateByteArray(kLength, TENURED).To(&byte_array));
      byte_arrays->push_back(byte_array);
      CHECK_EQ(page, Page::FromAddress(byte_array->address()));
    }
  }
  CHECK_NULL(page->invalidated_slots());
  return page;
}

HEAP_TEST(InvalidatedSlotsNoInvalidatedRanges) {
  CcTest::InitializeVM();
  Heap* heap = CcTest::heap();
  std::vector<ByteArray*> byte_arrays;
  Page* page = AllocateByteArraysOnPage(heap, &byte_arrays);
  InvalidatedSlotsFilter filter(page);
  for (auto byte_array : byte_arrays) {
    Address start = byte_array->address() + ByteArray::kHeaderSize;
    Address end = byte_array->address() + byte_array->Size();
    for (Address addr = start; addr < end; addr += kPointerSize) {
      CHECK(filter.IsValid(addr));
    }
  }
}

HEAP_TEST(InvalidatedSlotsSomeInvalidatedRanges) {
  CcTest::InitializeVM();
  Heap* heap = CcTest::heap();
  std::vector<ByteArray*> byte_arrays;
  Page* page = AllocateByteArraysOnPage(heap, &byte_arrays);
  // Register every second byte arrays as invalidated.
  for (size_t i = 0; i < byte_arrays.size(); i += 2) {
    page->RegisterObjectWithInvalidatedSlots(byte_arrays[i],
                                             byte_arrays[i]->Size());
  }
  InvalidatedSlotsFilter filter(page);
  for (size_t i = 0; i < byte_arrays.size(); i++) {
    ByteArray* byte_array = byte_arrays[i];
    Address start = byte_array->address() + ByteArray::kHeaderSize;
    Address end = byte_array->address() + byte_array->Size();
    for (Address addr = start; addr < end; addr += kPointerSize) {
      if (i % 2 == 0) {
        CHECK(!filter.IsValid(addr));
      } else {
        CHECK(filter.IsValid(addr));
      }
    }
  }
}

HEAP_TEST(InvalidatedSlotsAllInvalidatedRanges) {
  CcTest::InitializeVM();
  Heap* heap = CcTest::heap();
  std::vector<ByteArray*> byte_arrays;
  Page* page = AllocateByteArraysOnPage(heap, &byte_arrays);
  // Register the all byte arrays as invalidated.
  for (size_t i = 0; i < byte_arrays.size(); i++) {
    page->RegisterObjectWithInvalidatedSlots(byte_arrays[i],
                                             byte_arrays[i]->Size());
  }
  InvalidatedSlotsFilter filter(page);
  for (size_t i = 0; i < byte_arrays.size(); i++) {
    ByteArray* byte_array = byte_arrays[i];
    Address start = byte_array->address() + ByteArray::kHeaderSize;
    Address end = byte_array->address() + byte_array->Size();
    for (Address addr = start; addr < end; addr += kPointerSize) {
      CHECK(!filter.IsValid(addr));
    }
  }
}

HEAP_TEST(InvalidatedSlotsAfterTrimming) {
  CcTest::InitializeVM();
  Heap* heap = CcTest::heap();
  std::vector<ByteArray*> byte_arrays;
  Page* page = AllocateByteArraysOnPage(heap, &byte_arrays);
  // Register the all byte arrays as invalidated.
  for (size_t i = 0; i < byte_arrays.size(); i++) {
    page->RegisterObjectWithInvalidatedSlots(byte_arrays[i],
                                             byte_arrays[i]->Size());
  }
  // Trim byte arrays and check that the slots outside the byte arrays are
  // considered valid. Free space outside invalidated object can be reused
  // during evacuation for allocation of the evacuated objects. That can
  // add new valid slots to evacuation candidates.
  InvalidatedSlotsFilter filter(page);
  for (size_t i = 0; i < byte_arrays.size(); i++) {
    ByteArray* byte_array = byte_arrays[i];
    Address start = byte_array->address() + ByteArray::kHeaderSize;
    Address end = byte_array->address() + byte_array->Size();
    heap->RightTrimFixedArray(byte_array, byte_array->length());
    for (Address addr = start; addr < end; addr += kPointerSize) {
      CHECK(filter.IsValid(addr));
    }
  }
}

HEAP_TEST(InvalidatedSlotsEvacuationCandidate) {
  CcTest::InitializeVM();
  Heap* heap = CcTest::heap();
  std::vector<ByteArray*> byte_arrays;
  Page* page = AllocateByteArraysOnPage(heap, &byte_arrays);
  page->MarkEvacuationCandidate();
  // Register the all byte arrays as invalidated.
  // This should be no-op because the page is marked as evacuation
  // candidate.
  for (size_t i = 0; i < byte_arrays.size(); i++) {
    page->RegisterObjectWithInvalidatedSlots(byte_arrays[i],
                                             byte_arrays[i]->Size());
  }
  // All slots must still be valid.
  InvalidatedSlotsFilter filter(page);
  for (size_t i = 0; i < byte_arrays.size(); i++) {
    ByteArray* byte_array = byte_arrays[i];
    Address start = byte_array->address() + ByteArray::kHeaderSize;
    Address end = byte_array->address() + byte_array->Size();
    for (Address addr = start; addr < end; addr += kPointerSize) {
      CHECK(filter.IsValid(addr));
    }
  }
}

HEAP_TEST(InvalidatedSlotsResetObjectRegression) {
  CcTest::InitializeVM();
  Heap* heap = CcTest::heap();
  std::vector<ByteArray*> byte_arrays;
  Page* page = AllocateByteArraysOnPage(heap, &byte_arrays);
  // Ensure that the first array has smaller size then the rest.
  heap->RightTrimFixedArray(byte_arrays[0], byte_arrays[0]->length() - 8);
  // Register the all byte arrays as invalidated.
  for (size_t i = 0; i < byte_arrays.size(); i++) {
    page->RegisterObjectWithInvalidatedSlots(byte_arrays[i],
                                             byte_arrays[i]->Size());
  }
  // All slots must still be invalid.
  InvalidatedSlotsFilter filter(page);
  for (size_t i = 0; i < byte_arrays.size(); i++) {
    ByteArray* byte_array = byte_arrays[i];
    Address start = byte_array->address() + ByteArray::kHeaderSize;
    Address end = byte_array->address() + byte_array->Size();
    for (Address addr = start; addr < end; addr += kPointerSize) {
      CHECK(!filter.IsValid(addr));
    }
  }
}

}  // namespace heap
}  // namespace internal
}  // namespace v8
