// Copyright 2015 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/heap/spaces.h"
#include "test/unittests/heap/bitmap-test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace v8 {
namespace internal {

const uint32_t kBlackCell = 0xAAAAAAAA;
const uint32_t kWhiteCell = 0x00000000;
const uint32_t kBlackByte = 0xAA;
const uint32_t kWhiteByte = 0x00;

template <typename T>
using BitmapTest = TestWithBitmap<T>;

TYPED_TEST_SUITE(BitmapTest, BitmapTypes);

using NonAtomicBitmapTest =
    TestWithBitmap<ConcurrentBitmap<AccessMode::NON_ATOMIC>>;

TEST_F(NonAtomicBitmapTest, IsZeroInitialized) {
  // We require all tests to start from a zero-initialized bitmap. Manually
  // verify this invariant here.
  for (size_t i = 0; i < Bitmap::kSize; i++) {
    EXPECT_EQ(raw_bitmap()[i], kWhiteByte);
  }
}

TEST_F(NonAtomicBitmapTest, Cells) {
  auto bm = bitmap();
  bm->cells()[1] = kBlackCell;
  uint8_t* raw = raw_bitmap();
  int second_cell_base = Bitmap::kBytesPerCell;
  for (size_t i = 0; i < Bitmap::kBytesPerCell; i++) {
    EXPECT_EQ(raw[second_cell_base + i], kBlackByte);
  }
}

TEST_F(NonAtomicBitmapTest, CellsCount) {
  size_t last_cell_index = bitmap()->CellsCount() - 1;
  bitmap()->cells()[last_cell_index] = kBlackCell;
  // Manually verify on raw memory.
  uint8_t* raw = raw_bitmap();
  for (size_t i = 0; i < Bitmap::kSize; i++) {
    // Last cell should be set.
    if (i >= (Bitmap::kSize - Bitmap::kBytesPerCell)) {
      EXPECT_EQ(raw[i], kBlackByte);
    } else {
      EXPECT_EQ(raw[i], kWhiteByte);
    }
  }
}

TEST_F(NonAtomicBitmapTest, IsClean) {
  auto bm = bitmap();
  EXPECT_TRUE(bm->IsClean());
  bm->cells()[0] = kBlackCell;
  EXPECT_FALSE(bm->IsClean());
}

TYPED_TEST(BitmapTest, Clear) {
  auto bm = this->bitmap();
  for (size_t i = 0; i < Bitmap::kSize; i++) {
    this->raw_bitmap()[i] = 0xFFu;
  }
  bm->Clear();
  for (size_t i = 0; i < Bitmap::kSize; i++) {
    EXPECT_EQ(this->raw_bitmap()[i], 0);
  }
}

TYPED_TEST(BitmapTest, MarkAllBits) {
  auto bm = this->bitmap();
  bm->MarkAllBits();
  for (size_t i = 0; i < Bitmap::kSize; i++) {
    EXPECT_EQ(this->raw_bitmap()[i], 0xFF);
  }
}

TYPED_TEST(BitmapTest, ClearRange1) {
  auto bm = this->bitmap();
  bm->cells()[0] = kBlackCell;
  bm->cells()[1] = kBlackCell;
  bm->cells()[2] = kBlackCell;
  bm->ClearRange(0, Bitmap::kBitsPerCell + Bitmap::kBitsPerCell / 2);
  EXPECT_EQ(bm->cells()[0], kWhiteCell);
  EXPECT_EQ(bm->cells()[1], 0xAAAA0000);
  EXPECT_EQ(bm->cells()[2], kBlackCell);
}

TYPED_TEST(BitmapTest, ClearRange2) {
  auto bm = this->bitmap();
  bm->cells()[0] = kBlackCell;
  bm->cells()[1] = kBlackCell;
  bm->cells()[2] = kBlackCell;
  bm->ClearRange(Bitmap::kBitsPerCell,
                 Bitmap::kBitsPerCell + Bitmap::kBitsPerCell / 2);
  EXPECT_EQ(bm->cells()[0], kBlackCell);
  EXPECT_EQ(bm->cells()[1], 0xAAAA0000);
  EXPECT_EQ(bm->cells()[2], kBlackCell);
}

TYPED_TEST(BitmapTest, SetAndClearRange) {
  auto bm = this->bitmap();
  for (int i = 0; i < 3; i++) {
    bm->SetRange(i, Bitmap::kBitsPerCell + i);
    CHECK_EQ(bm->cells()[0], 0xFFFFFFFFu << i);
    CHECK_EQ(bm->cells()[1], (1u << i) - 1);
    bm->ClearRange(i, Bitmap::kBitsPerCell + i);
    CHECK_EQ(bm->cells()[0], 0x0u);
    CHECK_EQ(bm->cells()[1], 0x0u);
  }
}

// AllBitsSetInRange() and AllBitsClearInRange() are only used when verifying
// the heap on the main thread so they don't have atomic implementations.
TEST_F(NonAtomicBitmapTest, ClearMultipleRanges) {
  auto bm = this->bitmap();

  bm->SetRange(0, Bitmap::kBitsPerCell * 3);
  CHECK(bm->AllBitsSetInRange(0, Bitmap::kBitsPerCell));

  bm->ClearRange(Bitmap::kBitsPerCell / 2, Bitmap::kBitsPerCell);
  bm->ClearRange(Bitmap::kBitsPerCell,
                 Bitmap::kBitsPerCell + Bitmap::kBitsPerCell / 2);
  bm->ClearRange(Bitmap::kBitsPerCell * 2 + 8, Bitmap::kBitsPerCell * 2 + 16);
  bm->ClearRange(Bitmap::kBitsPerCell * 2 + 24, Bitmap::kBitsPerCell * 3);

  CHECK_EQ(bm->cells()[0], 0xFFFFu);
  CHECK(bm->AllBitsSetInRange(0, Bitmap::kBitsPerCell / 2));
  CHECK(
      bm->AllBitsClearInRange(Bitmap::kBitsPerCell / 2, Bitmap::kBitsPerCell));

  CHECK_EQ(bm->cells()[1], 0xFFFF0000u);
  CHECK(bm->AllBitsClearInRange(
      Bitmap::kBitsPerCell, Bitmap::kBitsPerCell + Bitmap::kBitsPerCell / 2));
  CHECK(bm->AllBitsSetInRange(Bitmap::kBitsPerCell + Bitmap::kBitsPerCell / 2,
                              Bitmap::kBitsPerCell * 2));

  CHECK_EQ(bm->cells()[2], 0xFF00FFu);
  CHECK(bm->AllBitsSetInRange(
      Bitmap::kBitsPerCell * 2,
      Bitmap::kBitsPerCell * 2 + Bitmap::kBitsPerCell / 4));
  CHECK(bm->AllBitsClearInRange(
      Bitmap::kBitsPerCell * 2 + Bitmap::kBitsPerCell / 4,
      Bitmap::kBitsPerCell * 2 + Bitmap::kBitsPerCell / 2));
  CHECK(bm->AllBitsSetInRange(
      Bitmap::kBitsPerCell * 2 + Bitmap::kBitsPerCell / 2,
      Bitmap::kBitsPerCell * 2 + Bitmap::kBitsPerCell / 2 +
          Bitmap::kBitsPerCell / 4));
  CHECK(bm->AllBitsClearInRange(Bitmap::kBitsPerCell * 2 +
                                    Bitmap::kBitsPerCell / 2 +
                                    Bitmap::kBitsPerCell / 4,
                                Bitmap::kBitsPerCell * 3));
}

}  // namespace internal
}  // namespace v8
