// 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_CPPGC_INTERNAL_HEAP_BASE_H_
#define V8_HEAP_CPPGC_INTERNAL_HEAP_BASE_H_

#include <memory>
#include <set>

#include "include/cppgc/heap-handle.h"
#include "include/cppgc/heap-statistics.h"
#include "include/cppgc/heap.h"
#include "include/cppgc/internal/persistent-node.h"
#include "include/cppgc/macros.h"
#include "src/base/macros.h"
#include "src/heap/cppgc-internal/compactor.h"
#include "src/heap/cppgc-internal/heap-object-header.h"
#include "src/heap/cppgc-internal/marker.h"
#include "src/heap/cppgc-internal/metric-recorder.h"
#include "src/heap/cppgc-internal/object-allocator.h"
#include "src/heap/cppgc-internal/platform.h"
#include "src/heap/cppgc-internal/process-heap-statistics.h"
#include "src/heap/cppgc-internal/process-heap.h"
#include "src/heap/cppgc-internal/raw-heap.h"
#include "src/heap/cppgc-internal/sweeper.h"
#include "src/heap/cppgc-internal/write-barrier.h"
#include "v8config.h"  // NOLINT(build/include_directory)

#if defined(CPPGC_YOUNG_GENERATION)
#include "src/heap/cppgc-internal/remembered-set.h"
#endif

namespace v8 {
namespace base {
class LsanPageAllocator;
}  // namespace base
}  // namespace v8

namespace heap {
namespace base {
class Stack;
}  // namespace base
}  // namespace heap

namespace cppgc {
namespace subtle {
class DisallowGarbageCollectionScope;
class NoGarbageCollectionScope;
}  // namespace subtle

namespace testing {
class Heap;
}  // namespace testing

class Platform;

namespace internal {

class FatalOutOfMemoryHandler;
class GarbageCollector;
class PageBackend;
class PreFinalizerHandler;
class StatsCollector;

enum class HeapObjectNameForUnnamedObject : uint8_t;
enum class StickyBits : uint8_t {
  kDisabled,
  kEnabled,
};

class MoveListener {
 public:
  // This function may be called simultaneously on multiple threads.
  // Implementations must not attempt to allocate or do any other actions
  // which could trigger reentrant GC.
  virtual void OnMove(Address from, Address to,
                      size_t size_including_header) = 0;
};

// Base class for heap implementations.
class V8_EXPORT_PRIVATE HeapBase : public cppgc::HeapHandle {
 public:
  using StackSupport = cppgc::Heap::StackSupport;
  using MarkingType = cppgc::Heap::MarkingType;
  using SweepingType = cppgc::Heap::SweepingType;

  static HeapBase& From(cppgc::HeapHandle& heap_handle) {
    return static_cast<HeapBase&>(heap_handle);
  }
  static const HeapBase& From(const cppgc::HeapHandle& heap_handle) {
    return static_cast<const HeapBase&>(heap_handle);
  }

  HeapBase(std::shared_ptr<cppgc::Platform> platform,
           const std::vector<std::unique_ptr<CustomSpaceBase>>& custom_spaces,
           StackSupport stack_support, MarkingType marking_support,
           SweepingType sweeping_support, GarbageCollector& garbage_collector,
           std::optional<cppgc::StackStartMarker> stack_start_marker);
  virtual ~HeapBase();

  HeapBase(const HeapBase&) = delete;
  HeapBase& operator=(const HeapBase&) = delete;

  RawHeap& raw_heap() { return raw_heap_; }
  const RawHeap& raw_heap() const { return raw_heap_; }

  cppgc::Platform* platform() { return platform_.get(); }
  const cppgc::Platform* platform() const { return platform_.get(); }

  FatalOutOfMemoryHandler& oom_handler() { return *oom_handler_.get(); }
  const FatalOutOfMemoryHandler& oom_handler() const {
    return *oom_handler_.get();
  }

  PageBackend* page_backend() { return page_backend_.get(); }
  const PageBackend* page_backend() const { return page_backend_.get(); }

  StatsCollector* stats_collector() { return stats_collector_.get(); }
  const StatsCollector* stats_collector() const {
    return stats_collector_.get();
  }

  PreFinalizerHandler* prefinalizer_handler() {
    return prefinalizer_handler_.get();
  }
  const PreFinalizerHandler* prefinalizer_handler() const {
    return prefinalizer_handler_.get();
  }

  MarkerBase* marker() const { return marker_.get(); }
  std::unique_ptr<MarkerBase>& GetMarkerRefForTesting() { return marker_; }

  Compactor& compactor() { return compactor_; }

  ObjectAllocator& object_allocator() { return object_allocator_; }
  const ObjectAllocator& object_allocator() const { return object_allocator_; }

  Sweeper& sweeper() { return sweeper_; }
  const Sweeper& sweeper() const { return sweeper_; }

  PersistentRegion& GetStrongPersistentRegion() {
    return strong_persistent_region_;
  }
  const PersistentRegion& GetStrongPersistentRegion() const {
    return strong_persistent_region_;
  }
  PersistentRegion& GetWeakPersistentRegion() {
    return weak_persistent_region_;
  }
  const PersistentRegion& GetWeakPersistentRegion() const {
    return weak_persistent_region_;
  }
  CrossThreadPersistentRegion& GetStrongCrossThreadPersistentRegion() {
    return strong_cross_thread_persistent_region_;
  }
  const CrossThreadPersistentRegion& GetStrongCrossThreadPersistentRegion()
      const {
    return strong_cross_thread_persistent_region_;
  }
  CrossThreadPersistentRegion& GetWeakCrossThreadPersistentRegion() {
    return weak_cross_thread_persistent_region_;
  }
  const CrossThreadPersistentRegion& GetWeakCrossThreadPersistentRegion()
      const {
    return weak_cross_thread_persistent_region_;
  }

#if defined(CPPGC_YOUNG_GENERATION)
  OldToNewRememberedSet& remembered_set() { return remembered_set_; }
#endif  // defined(CPPGC_YOUNG_GENERATION)

  size_t ObjectPayloadSize() const;

  virtual heap::base::Stack* stack() { return stack_.get(); }

  StackSupport stack_support() const { return stack_support_; }

  // These virtual methods are also present in class GarbageCollector.
  virtual void set_override_stack_state(EmbedderStackState state) = 0;
  virtual void clear_overridden_stack_state() = 0;

  std::optional<cppgc::StackStartMarker> stack_start_marker();

  // Termination drops all roots (clears them out) and runs garbage collections
  // in a bounded fixed point loop  until no new objects are created in
  // destructors. Exceeding the loop bound results in a crash.
  void Terminate();

  virtual bool IsGCForbidden() const;
  bool in_atomic_pause() const { return in_atomic_pause_; }

  HeapStatistics CollectStatistics(HeapStatistics::DetailLevel);

  EmbedderStackState stack_state_of_prev_gc() const {
    return stack_state_of_prev_gc_;
  }
  void SetStackStateOfPrevGC(EmbedderStackState stack_state) {
    stack_state_of_prev_gc_ = stack_state;
  }

  void SetInAtomicPauseForTesting(bool value) { in_atomic_pause_ = value; }

  virtual void StartIncrementalGarbageCollectionForTesting() = 0;
  virtual void FinalizeIncrementalGarbageCollectionForTesting(
      EmbedderStackState) = 0;

  void SetMetricRecorder(std::unique_ptr<MetricRecorder> histogram_recorder) {
    stats_collector_->SetMetricRecorder(std::move(histogram_recorder));
  }

  virtual bool CurrentThreadIsHeapThread() const;

  MarkingType marking_support() const { return marking_support_; }
  SweepingType sweeping_support() const { return sweeping_support_; }

  bool incremental_marking_supported() const {
    return marking_support_ != MarkingType::kAtomic;
  }

  bool generational_gc_supported() const {
    const bool supported = is_young_generation_enabled();
#if defined(CPPGC_YOUNG_GENERATION)
    DCHECK_IMPLIES(supported, YoungGenerationEnabler::IsEnabled());
#endif  // defined(CPPGC_YOUNG_GENERATION)
    return supported;
  }

  StickyBits sticky_bits() const {
    return generational_gc_supported() ? StickyBits::kEnabled
                                       : StickyBits::kDisabled;
  }

  // Returns whether objects should derive their name from C++ class names. Also
  // requires build-time support through `CPPGC_SUPPORTS_OBJECT_NAMES`.
  HeapObjectNameForUnnamedObject name_of_unnamed_object() const {
    return name_for_unnamed_object_;
  }
  void set_name_of_unnamed_object(HeapObjectNameForUnnamedObject value) {
    name_for_unnamed_object_ = value;
  }

  // Callback support so that other components can listen to when objects are
  // moved.
  bool HasMoveListeners() const { return !move_listeners_.empty(); }
  void CallMoveListeners(Address from, Address to,
                         size_t size_including_header);
  void RegisterMoveListener(MoveListener* listener);
  void UnregisterMoveListener(MoveListener* listener);

  void set_incremental_marking_in_progress(bool value) {
    is_incremental_marking_in_progress_ = value;
  }

  void EnterNoGCScope() { ++no_gc_scope_; }
  void LeaveNoGCScope() {
    DCHECK_GT(no_gc_scope_, 0);
    --no_gc_scope_;
  }

  void EnterDisallowGCScope() { ++disallow_gc_scope_; }
  void LeaveDisallowGCScope() {
    DCHECK_GT(disallow_gc_scope_, 0);
    --disallow_gc_scope_;
  }

  using HeapHandle::is_incremental_marking_in_progress;

 protected:
  static std::unique_ptr<PageBackend> InitializePageBackend(
      PageAllocator& allocator);

  // Used by the incremental scheduler to finalize a GC if supported.
  virtual void FinalizeIncrementalGarbageCollectionIfNeeded(
      cppgc::Heap::StackState) = 0;

  virtual bool IsGCAllowed() const;

  bool in_no_gc_scope() const { return no_gc_scope_ > 0; }

  bool IsMarking() const { return marker_.get(); }

  // Returns amount of bytes allocated while executing prefinalizers.
  size_t ExecutePreFinalizers();

#if defined(CPPGC_YOUNG_GENERATION)
  void EnableGenerationalGC();
  void ResetRememberedSet();
#endif  // defined(CPPGC_YOUNG_GENERATION)

  PageAllocator* page_allocator() const;

  // This field should be first so that it is initialized first at heap creation
  // and is available upon initialization of other fields.
  int heap_thread_id_ = v8::base::OS::GetCurrentThreadId();

  RawHeap raw_heap_;
  std::shared_ptr<cppgc::Platform> platform_;
  std::unique_ptr<FatalOutOfMemoryHandler> oom_handler_;

#if defined(LEAK_SANITIZER)
  std::unique_ptr<v8::base::LsanPageAllocator> lsan_page_allocator_;
#endif  // LEAK_SANITIZER

  std::unique_ptr<PageBackend> page_backend_;

  // HeapRegistry requires access to page_backend_.
  HeapRegistry::Subscription heap_registry_subscription_{*this};

  std::unique_ptr<StatsCollector> stats_collector_;
  std::unique_ptr<heap::base::Stack> stack_;
  std::unique_ptr<PreFinalizerHandler> prefinalizer_handler_;
  std::unique_ptr<MarkerBase> marker_;

  Compactor compactor_;
  ObjectAllocator object_allocator_;
  Sweeper sweeper_;

  PersistentRegion strong_persistent_region_;
  PersistentRegion weak_persistent_region_;
  CrossThreadPersistentRegion strong_cross_thread_persistent_region_;
  CrossThreadPersistentRegion weak_cross_thread_persistent_region_;

  ProcessHeapStatisticsUpdater::AllocationObserverImpl
      allocation_observer_for_PROCESS_HEAP_STATISTICS_;
#if defined(CPPGC_YOUNG_GENERATION)
  OldToNewRememberedSet remembered_set_;
#endif  // defined(CPPGC_YOUNG_GENERATION)

  size_t no_gc_scope_ = 0;
  size_t disallow_gc_scope_ = 0;

  const StackSupport stack_support_;
  EmbedderStackState stack_state_of_prev_gc_ =
      EmbedderStackState::kNoHeapPointers;

  const std::optional<StackStartMarker> stack_start_marker_;

  bool in_atomic_pause_ = false;

  MarkingType marking_support_;
  SweepingType sweeping_support_;

  HeapObjectNameForUnnamedObject name_for_unnamed_object_ =
      HeapObjectNameForUnnamedObject::kUseHiddenName;

  std::vector<MoveListener*> move_listeners_;

  friend class MarkerBase::IncrementalMarkingTask;
  friend class cppgc::subtle::DisallowGarbageCollectionScope;
  friend class cppgc::testing::Heap;
};

class V8_NODISCARD V8_EXPORT_PRIVATE ClassNameAsHeapObjectNameScope final {
 public:
  explicit ClassNameAsHeapObjectNameScope(HeapBase& heap);
  ~ClassNameAsHeapObjectNameScope();

 private:
  HeapBase& heap_;
  const HeapObjectNameForUnnamedObject saved_heap_object_name_value_;
};

}  // namespace internal
}  // namespace cppgc

#endif  // V8_HEAP_CPPGC_INTERNAL_HEAP_BASE_H_
