// Copyright 2014 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/base/utils/random-number-generator.h"
#include "src/compiler/zone-pool.h"
#include "test/unittests/test-utils.h"

namespace v8 {
namespace internal {
namespace compiler {

class ZonePoolTest : public TestWithIsolate {
 public:
  ZonePoolTest() : zone_pool_(isolate()) {}

 protected:
  ZonePool* zone_pool() { return &zone_pool_; }

  void ExpectForPool(size_t current, size_t max, size_t total) {
    ASSERT_EQ(current, zone_pool()->GetCurrentAllocatedBytes());
    ASSERT_EQ(max, zone_pool()->GetMaxAllocatedBytes());
    ASSERT_EQ(total, zone_pool()->GetTotalAllocatedBytes());
  }

  void Expect(ZonePool::StatsScope* stats, size_t current, size_t max,
              size_t total) {
    ASSERT_EQ(current, stats->GetCurrentAllocatedBytes());
    ASSERT_EQ(max, stats->GetMaxAllocatedBytes());
    ASSERT_EQ(total, stats->GetTotalAllocatedBytes());
  }

  size_t Allocate(Zone* zone) {
    size_t bytes = rng.NextInt(25) + 7;
    int size_before = zone->allocation_size();
    zone->New(static_cast<int>(bytes));
    return static_cast<size_t>(zone->allocation_size() - size_before);
  }

 private:
  ZonePool zone_pool_;
  base::RandomNumberGenerator rng;
};


TEST_F(ZonePoolTest, Empty) {
  ExpectForPool(0, 0, 0);
  {
    ZonePool::StatsScope stats(zone_pool());
    Expect(&stats, 0, 0, 0);
  }
  ExpectForPool(0, 0, 0);
  {
    ZonePool::Scope scope(zone_pool());
    scope.zone();
  }
  ExpectForPool(0, 0, 0);
}


TEST_F(ZonePoolTest, MultipleZonesWithDeletion) {
  static const size_t kArraySize = 10;

  ZonePool::Scope* scopes[kArraySize];

  // Initialize.
  size_t before_stats = 0;
  for (size_t i = 0; i < kArraySize; ++i) {
    scopes[i] = new ZonePool::Scope(zone_pool());
    before_stats += Allocate(scopes[i]->zone());  // Add some stuff.
  }

  ExpectForPool(before_stats, before_stats, before_stats);

  ZonePool::StatsScope stats(zone_pool());

  size_t before_deletion = 0;
  for (size_t i = 0; i < kArraySize; ++i) {
    before_deletion += Allocate(scopes[i]->zone());  // Add some stuff.
  }

  Expect(&stats, before_deletion, before_deletion, before_deletion);
  ExpectForPool(before_stats + before_deletion, before_stats + before_deletion,
                before_stats + before_deletion);

  // Delete the scopes and create new ones.
  for (size_t i = 0; i < kArraySize; ++i) {
    delete scopes[i];
    scopes[i] = new ZonePool::Scope(zone_pool());
  }

  Expect(&stats, 0, before_deletion, before_deletion);
  ExpectForPool(0, before_stats + before_deletion,
                before_stats + before_deletion);

  size_t after_deletion = 0;
  for (size_t i = 0; i < kArraySize; ++i) {
    after_deletion += Allocate(scopes[i]->zone());  // Add some stuff.
  }

  Expect(&stats, after_deletion, std::max(after_deletion, before_deletion),
         before_deletion + after_deletion);
  ExpectForPool(after_deletion,
                std::max(after_deletion, before_stats + before_deletion),
                before_stats + before_deletion + after_deletion);

  // Cleanup.
  for (size_t i = 0; i < kArraySize; ++i) {
    delete scopes[i];
  }

  Expect(&stats, 0, std::max(after_deletion, before_deletion),
         before_deletion + after_deletion);
  ExpectForPool(0, std::max(after_deletion, before_stats + before_deletion),
                before_stats + before_deletion + after_deletion);
}


TEST_F(ZonePoolTest, SimpleAllocationLoop) {
  int runs = 20;
  size_t total_allocated = 0;
  size_t max_loop_allocation = 0;
  ZonePool::StatsScope outer_stats(zone_pool());
  {
    ZonePool::Scope outer_scope(zone_pool());
    size_t outer_allocated = 0;
    for (int i = 0; i < runs; ++i) {
      {
        size_t bytes = Allocate(outer_scope.zone());
        outer_allocated += bytes;
        total_allocated += bytes;
      }
      ZonePool::StatsScope inner_stats(zone_pool());
      size_t allocated = 0;
      {
        ZonePool::Scope inner_scope(zone_pool());
        for (int j = 0; j < 20; ++j) {
          size_t bytes = Allocate(inner_scope.zone());
          allocated += bytes;
          total_allocated += bytes;
          max_loop_allocation =
              std::max(max_loop_allocation, outer_allocated + allocated);
          Expect(&inner_stats, allocated, allocated, allocated);
          Expect(&outer_stats, outer_allocated + allocated, max_loop_allocation,
                 total_allocated);
          ExpectForPool(outer_allocated + allocated, max_loop_allocation,
                        total_allocated);
        }
      }
      Expect(&inner_stats, 0, allocated, allocated);
      Expect(&outer_stats, outer_allocated, max_loop_allocation,
             total_allocated);
      ExpectForPool(outer_allocated, max_loop_allocation, total_allocated);
    }
  }
  Expect(&outer_stats, 0, max_loop_allocation, total_allocated);
  ExpectForPool(0, max_loop_allocation, total_allocated);
}

}  // namespace compiler
}  // namespace internal
}  // namespace v8
