// 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_UNITTESTS_HEAP_HEAP_UTILS_H_
#define V8_UNITTESTS_HEAP_HEAP_UTILS_H_

#include "src/base/macros.h"
#include "src/common/globals.h"
#include "src/heap/heap.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace v8 {
namespace internal {

class NormalPage;
void ForceEvacuationCandidate(NormalPage* page);

class HeapInternalsBase {
 protected:
  size_t OldGenerationSpaceAvailable(Heap* heap);
  void SimulateIncrementalMarking(Heap* heap, bool force_completion);
  void SimulateFullSpace(
      v8::internal::NewSpace* space,
      std::vector<Handle<FixedArray>>* out_handles = nullptr);
  void SimulateFullSpace(v8::internal::PagedSpace* space);
  void FillCurrentPage(v8::internal::NewSpace* space,
                       std::vector<Handle<FixedArray>>* out_handles = nullptr);
};

inline void InvokeMajorGC(i::Isolate* isolate) {
  isolate->heap()->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting);
}

inline void InvokeMajorGC(i::Isolate* isolate, GCFlag gc_flag) {
  isolate->heap()->CollectAllGarbage(gc_flag,
                                     GarbageCollectionReason::kTesting);
}

inline void InvokeMinorGC(i::Isolate* isolate) {
  isolate->heap()->CollectGarbage(NEW_SPACE, GarbageCollectionReason::kTesting);
}

inline void InvokeAtomicMajorGC(i::Isolate* isolate) {
  Heap* heap = isolate->heap();
  heap->PreciseCollectAllGarbage(GCFlag::kNoFlags,
                                 GarbageCollectionReason::kTesting);
  if (heap->sweeping_in_progress()) {
    heap->EnsureSweepingCompleted(
        Heap::SweepingForcedFinalizationMode::kUnifiedHeap,
        CompleteSweepingReason::kTesting);
  }
}

inline void InvokeAtomicMinorGC(i::Isolate* isolate) {
  InvokeMinorGC(isolate);
  Heap* heap = isolate->heap();
  if (heap->sweeping_in_progress()) {
    heap->EnsureSweepingCompleted(
        Heap::SweepingForcedFinalizationMode::kUnifiedHeap,
        CompleteSweepingReason::kTesting);
  }
}

inline void InvokeMemoryReducingMajorGCs(i::Isolate* isolate) {
  isolate->heap()->CollectAllAvailableGarbage(
      GarbageCollectionReason::kTesting);
}

template <typename TMixin>
class WithHeapInternals : public TMixin, HeapInternalsBase {
 public:
  WithHeapInternals() = default;
  WithHeapInternals(const WithHeapInternals&) = delete;
  WithHeapInternals& operator=(const WithHeapInternals&) = delete;

  void InvokeMajorGC() { i::InvokeMajorGC(this->i_isolate()); }

  void InvokeMajorGC(GCFlag gc_flag) {
    i::InvokeMajorGC(this->i_isolate(), gc_flag);
  }

  void InvokeMinorGC() { i::InvokeMinorGC(this->i_isolate()); }

  void InvokeAtomicMajorGC() { i::InvokeAtomicMajorGC(this->i_isolate()); }

  void InvokeAtomicMinorGC() { i::InvokeAtomicMinorGC(this->i_isolate()); }

  void InvokeMemoryReducingMajorGCs() {
    i::InvokeMemoryReducingMajorGCs(this->i_isolate());
  }

  void PreciseCollectAllGarbage() {
    heap()->PreciseCollectAllGarbage(GCFlag::kNoFlags,
                                     GarbageCollectionReason::kTesting);
  }

  Heap* heap() const { return this->i_isolate()->heap(); }

  size_t OldGenerationSpaceAvailable() {
    return HeapInternalsBase::OldGenerationSpaceAvailable(heap());
  }

  void SimulateIncrementalMarking(bool force_completion = true) {
    return HeapInternalsBase::SimulateIncrementalMarking(heap(),
                                                         force_completion);
  }

  void SimulateFullSpace(
      v8::internal::NewSpace* space,
      std::vector<Handle<FixedArray>>* out_handles = nullptr) {
    return HeapInternalsBase::SimulateFullSpace(space, out_handles);
  }
  void SimulateFullSpace(v8::internal::PagedSpace* space) {
    return HeapInternalsBase::SimulateFullSpace(space);
  }

  void GrowNewSpaceToMaximumCapacity() {
    IsolateSafepointScope scope(heap());
    NewSpace* new_space = heap()->new_space();
    new_space->GrowToMaximumCapacityForTesting();
  }

  void SealCurrentObjects() {
    // If you see this check failing, disable the flag at the start of your
    // test: v8_flags.stress_concurrent_allocation = false; Background thread
    // allocating concurrently interferes with this function.
    CHECK(!v8_flags.stress_concurrent_allocation);
    InvokeMajorGC();
    InvokeMajorGC();
    heap()->EnsureSweepingCompleted(
        Heap::SweepingForcedFinalizationMode::kV8Only,
        CompleteSweepingReason::kTesting);
    heap()->FreeMainThreadLinearAllocationAreas();
    for (NormalPage* page : *heap()->old_space()) {
      page->MarkNeverAllocateForTesting();
    }
  }

  void ForceEvacuationCandidate(NormalPage* page) {
    i::ForceEvacuationCandidate(page);
  }

  void EmptyNewSpaceUsingGC() { InvokeMajorGC(); }

  int NumberOfGlobalObjects() {
    int count = 0;
    HeapObjectIterator iterator(heap());
    for (Tagged<HeapObject> obj = iterator.Next(); !obj.is_null();
         obj = iterator.Next()) {
      if (IsJSGlobalObject(obj)) count++;
    }
    return count;
  }
};

template <typename TMixin>
class WithCppHeap : public TMixin {
 public:
  WithCppHeap() {
    IsolateWrapper::set_cpp_heap_for_next_isolate(
        v8::CppHeap::Create(V8::GetCurrentPlatform(), CppHeapCreateParams{{}}));
  }
};

using TestWithHeapInternals =                      //
    WithHeapInternals<                             //
        WithInternalIsolateMixin<                  //
            WithIsolateScopeMixin<                 //
                WithIsolateMixin<                  //
                    WithCppHeap<                   //
                        WithDefaultPlatformMixin<  //
                            ::testing::Test>>>>>>;

using TestWithHeapInternalsAndContext =  //
    WithContextMixin<                    //
        TestWithHeapInternals>;

template <typename GlobalOrPersistent>
bool InYoungGeneration(v8::Isolate* isolate, const GlobalOrPersistent& global) {
  CHECK(!v8_flags.single_generation);
  v8::HandleScope scope(isolate);
  auto tmp = global.Get(isolate);
  return HeapLayout::InYoungGeneration(*v8::Utils::OpenDirectHandle(*tmp));
}

bool IsNewObjectInCorrectGeneration(Tagged<HeapObject> object);

template <typename GlobalOrPersistent>
bool IsNewObjectInCorrectGeneration(v8::Isolate* isolate,
                                    const GlobalOrPersistent& global) {
  v8::HandleScope scope(isolate);
  auto tmp = global.Get(isolate);
  return IsNewObjectInCorrectGeneration(*v8::Utils::OpenDirectHandle(*tmp));
}

// ManualGCScope allows for disabling GC heuristics. This is useful for tests
// that want to check specific corner cases around GC.
//
// The scope will finalize any ongoing GC on the provided Isolate.
class V8_NODISCARD ManualGCScope final {
 public:
  explicit ManualGCScope(Isolate* isolate);
  ~ManualGCScope();

 private:
  Isolate* const isolate_;
  const bool flag_concurrent_marking_;
  const bool flag_concurrent_sweeping_;
  const bool flag_concurrent_minor_ms_marking_;
  const bool flag_stress_concurrent_allocation_;
  const bool flag_stress_incremental_marking_;
  const bool flag_parallel_marking_;
  const bool flag_detect_ineffective_gcs_near_heap_limit_;
  const bool flag_cppheap_concurrent_marking_;
};

class V8_NODISCARD ManualEvacuationCandidatesSelectionScope final {
 public:
  explicit ManualEvacuationCandidatesSelectionScope(ManualGCScope&) {
    DCHECK(!v8_flags.manual_evacuation_candidates_selection);
    v8_flags.manual_evacuation_candidates_selection = true;
  }
  ~ManualEvacuationCandidatesSelectionScope() {
    DCHECK(v8_flags.manual_evacuation_candidates_selection);
    v8_flags.manual_evacuation_candidates_selection = false;
  }
};

// DisableHandleChecksForMockingScope disables the checks for v8::Local and
// internal::DirectHandle, so that such handles can be allocated off-stack.
// This is required for mocking functions that take such handles as parameters
// and/or return them as results. For correctness (with direct handles), when
// this scope is used, it is important to ensure that the objects stored in
// handles used for mocking are retained by other means, so that they will not
// be reclaimed by a garbage collection.
// Note: The check is only performed in debug builds with enabled slow DCHECKs.
#ifdef ENABLE_SLOW_DCHECKS
class V8_NODISCARD DisableHandleChecksForMockingScope final
    : public StackAllocatedCheck::Scope {
 public:
  DisableHandleChecksForMockingScope() : StackAllocatedCheck::Scope(false) {}
};
#else
class V8_NODISCARD DisableHandleChecksForMockingScope final {
 public:
  DisableHandleChecksForMockingScope() {}
};
#endif

void AbandonCurrentlyFreeMemory(PagedSpace* space);

Tagged<HeapObject> AllocateAligned(Heap* heap, MainAllocator* allocator,
                                   int size, AllocationAlignment alignment);

Address AlignOldSpace(Heap* heap, AllocationAlignment alignment, int offset);

}  // namespace internal
}  // namespace v8

#endif  // V8_UNITTESTS_HEAP_HEAP_UTILS_H_
