// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#ifndef RUNTIME_VM_CLASS_TABLE_H_
#define RUNTIME_VM_CLASS_TABLE_H_

#include <memory>
#include <tuple>
#include <utility>

#include "platform/allocation.h"
#include "platform/assert.h"
#include "platform/atomic.h"
#include "platform/utils.h"

#include "vm/bitfield.h"
#include "vm/class_id.h"
#include "vm/flags.h"
#include "vm/globals.h"
#include "vm/tagged_pointer.h"

namespace dart {

class Class;
class ClassTable;
class Isolate;
class IsolateGroup;
class JSONArray;
class JSONObject;
class JSONStream;
template <typename T>
class MallocGrowableArray;
class ObjectPointerVisitor;
class PersistentHandle;

// A 64-bit bitmap describing unboxed fields in a class.
//
// There is a bit for each word in an instance of the class.
//
// Words corresponding to set bits must be ignored by the GC because they
// don't contain pointers. All words beyond the first 64 words of an object
// are expected to contain pointers.
class UnboxedFieldBitmap {
 public:
  UnboxedFieldBitmap() : bitmap_(0) {}
  explicit UnboxedFieldBitmap(uint64_t bitmap) : bitmap_(bitmap) {}
  UnboxedFieldBitmap(const UnboxedFieldBitmap&) = default;
  UnboxedFieldBitmap& operator=(const UnboxedFieldBitmap&) = default;

  DART_FORCE_INLINE bool Get(intptr_t position) const {
    if (position >= Length()) return false;
    return Utils::TestBit(bitmap_, position);
  }
  DART_FORCE_INLINE void Set(intptr_t position) {
    ASSERT(position < Length());
    bitmap_ |= Utils::Bit<decltype(bitmap_)>(position);
  }
  DART_FORCE_INLINE void Clear(intptr_t position) {
    ASSERT(position < Length());
    bitmap_ &= ~Utils::Bit<decltype(bitmap_)>(position);
  }
  DART_FORCE_INLINE uint64_t Value() const { return bitmap_; }
  DART_FORCE_INLINE bool IsEmpty() const { return bitmap_ == 0; }
  DART_FORCE_INLINE void Reset() { bitmap_ = 0; }

  DART_FORCE_INLINE static constexpr intptr_t Length() {
    return sizeof(decltype(bitmap_)) * kBitsPerByte;
  }

 private:
  uint64_t bitmap_;
};

// Allocator used to manage memory for ClassTable arrays and ClassTable
// objects themselves.
//
// This allocator provides delayed free functionality: normally class tables
// can't be freed unless all mutator and helper threads are stopped because
// some of these threads might be holding a pointer to a table which we
// want to free. Instead of stopping the world whenever we need to free
// a table (e.g. freeing old table after growing) we delay freeing until an
// occasional GC which will need to stop the world anyway.
class ClassTableAllocator : public ValueObject {
 public:
  ClassTableAllocator();
  ~ClassTableAllocator();

  // Allocate an array of T with |len| elements.
  //
  // Does *not* initialize the memory.
  template <class T>
  inline T* Alloc(intptr_t len) {
    return reinterpret_cast<T*>(dart::malloc(len * sizeof(T)));
  }

  // Allocate a zero initialized array of T with |len| elements.
  template <class T>
  inline T* AllocZeroInitialized(intptr_t len) {
    return reinterpret_cast<T*>(dart::calloc(len, sizeof(T)));
  }

  // Clone the given |array| with |size| elements.
  template <class T>
  inline T* Clone(T* array, intptr_t size) {
    if (array == nullptr) {
      ASSERT(size == 0);
      return nullptr;
    }
    auto result = Alloc<T>(size);
    memmove(result, array, size * sizeof(T));
    return result;
  }

  // Copy |size| elements from the given |array| into a new
  // array with space for |new_size| elements. Then |Free|
  // the original |array|.
  //
  // |new_size| is expected to be larger than |size|.
  template <class T>
  inline T* Realloc(T* array, intptr_t size, intptr_t new_size) {
    ASSERT(size < new_size);
    auto result = AllocZeroInitialized<T>(new_size);
    if (size != 0) {
      ASSERT(result != nullptr);
      memmove(result, array, size * sizeof(T));
    }
    Free(array);
    return result;
  }

  // Schedule deletion of the given ClassTable.
  void Free(ClassTable* table);

  // Schedule freeing of the given pointer.
  void Free(void* ptr);

  // Free all objects which were scheduled by |Free|. Expected to only be
  // called on |IsolateGroup| shutdown or when the world is stopped and no
  // thread can be using a stale class table pointer.
  void FreePending();

 private:
  typedef void (*Deleter)(void*);
  MallocGrowableArray<std::pair<void*, Deleter>>* pending_freed_;
};

// A table with the given |Columns| indexed by class id.
//
// Each column is a continuous array of a the given type. All columns have
// the same number of used elements (|num_cids()|) and the same capacity.
template <typename CidType, typename... Columns>
class CidIndexedTable {
 public:
  explicit CidIndexedTable(ClassTableAllocator* allocator)
      : allocator_(allocator) {}

  ~CidIndexedTable() {
    std::apply([&](auto&... column) { (allocator_->Free(column.load()), ...); },
               columns_);
  }

  CidIndexedTable(const CidIndexedTable& other) = delete;

  void SetNumCidsAndCapacity(intptr_t new_num_cids, intptr_t new_capacity) {
    columns_ = std::apply(
        [&](auto&... column) {
          return std::make_tuple(
              allocator_->Realloc(column.load(), num_cids_, new_capacity)...);
        },
        columns_);
    capacity_ = new_capacity;
    SetNumCids(new_num_cids);
  }

  void AllocateIndex(intptr_t index, bool* did_grow) {
    *did_grow = EnsureCapacity(index);
    SetNumCids(Utils::Maximum(num_cids_, index + 1));
  }

  intptr_t AddRow(bool* did_grow) {
    *did_grow = EnsureCapacity(num_cids_);
    intptr_t id = num_cids_;
    SetNumCids(num_cids_ + 1);
    return id;
  }

  void ShrinkTo(intptr_t new_num_cids) {
    ASSERT(new_num_cids <= num_cids_);
    num_cids_ = new_num_cids;
  }

  bool IsValidIndex(intptr_t index) const {
    return 0 <= index && index < num_cids_;
  }

  void CopyFrom(const CidIndexedTable& other) {
    ASSERT(allocator_ == other.allocator_);

    std::apply([&](auto&... column) { (allocator_->Free(column.load()), ...); },
               columns_);

    columns_ = std::apply(
        [&](auto&... column) {
          return std::make_tuple(
              allocator_->Clone(column.load(), other.num_cids_)...);
        },
        other.columns_);
    capacity_ = num_cids_ = other.num_cids_;
  }

  void Remap(intptr_t* old_to_new_cid) {
    CidIndexedTable clone(allocator_);
    clone.CopyFrom(*this);
    RemapAllColumns(clone, old_to_new_cid,
                    std::index_sequence_for<Columns...>{});
  }

  template <
      intptr_t kColumnIndex,
      typename T = std::tuple_element_t<kColumnIndex, std::tuple<Columns...>>>
  T* GetColumn() {
    return std::get<kColumnIndex>(columns_).load();
  }

  template <
      intptr_t kColumnIndex,
      typename T = std::tuple_element_t<kColumnIndex, std::tuple<Columns...>>>
  const T* GetColumn() const {
    return std::get<kColumnIndex>(columns_).load();
  }

  template <
      intptr_t kColumnIndex,
      typename T = std::tuple_element_t<kColumnIndex, std::tuple<Columns...>>>
  T& At(intptr_t index) {
    ASSERT(IsValidIndex(index));
    return GetColumn<kColumnIndex>()[index];
  }

  template <
      intptr_t kColumnIndex,
      typename T = std::tuple_element_t<kColumnIndex, std::tuple<Columns...>>>
  const T& At(intptr_t index) const {
    ASSERT(IsValidIndex(index));
    return GetColumn<kColumnIndex>()[index];
  }

  intptr_t num_cids() const { return num_cids_; }
  intptr_t capacity() const { return capacity_; }

 private:
  friend class ClassTable;

  // Wrapper around AcqRelAtomic<T*> which makes it assignable and copyable
  // so that we could put it inside an std::tuple.
  template <typename T>
  struct Ptr {
    Ptr() : ptr(nullptr) {}
    Ptr(T* ptr) : ptr(ptr) {}  // NOLINT

    Ptr(const Ptr& other) { ptr.store(other.ptr.load()); }

    Ptr& operator=(const Ptr& other) {
      ptr.store(other.load());
      return *this;
    }

    T* load() const { return ptr.load(); }

    AcqRelAtomic<T*> ptr = {nullptr};
  };

  void SetNumCids(intptr_t new_num_cids) {
    if (new_num_cids > kClassIdTagMax) {
      FATAL("Too many classes");
    }
    num_cids_ = new_num_cids;
  }

  bool EnsureCapacity(intptr_t index) {
    if (index >= capacity_) {
      SetNumCidsAndCapacity(num_cids_, index + kCapacityIncrement);
      return true;
    }
    return false;
  }

  template <intptr_t kColumnIndex>
  void RemapColumn(const CidIndexedTable& old, intptr_t* old_to_new_cid) {
    auto new_column = GetColumn<kColumnIndex>();
    auto old_column = old.GetColumn<kColumnIndex>();
    for (intptr_t i = 0; i < num_cids_; i++) {
      new_column[old_to_new_cid[i]] = old_column[i];
    }
  }

  template <std::size_t... Is>
  void RemapAllColumns(const CidIndexedTable& old,
                       intptr_t* old_to_new_cid,
                       std::index_sequence<Is...>) {
    (RemapColumn<Is>(old, old_to_new_cid), ...);
  }

  static constexpr intptr_t kCapacityIncrement = 256;

  ClassTableAllocator* allocator_;
  intptr_t num_cids_ = 0;
  intptr_t capacity_ = 0;
  std::tuple<Ptr<Columns>...> columns_;
};

// Registry of all known classes.
//
// The GC will only use information about instance size and unboxed field maps
// to scan instances and will not access class objects themselves. This
// information is stored in separate columns of the |classes_| table.
//
// # Concurrency & atomicity
//
// This table is read concurrently without locking (e.g. by GC threads) so
// there are some invariants that need to be observed when working with it.
//
// * When table is updated (e.g. when the table is grown or a new class is
// registered in a table) there must be a release barrier after the update.
// Such barrier will ensure that stores which populate the table are not
// reordered past the store which exposes the new grown table or exposes
// a new class id;
// * Old versions of the table can only be freed when the world is stopped:
// no mutator and no helper threads are running. To avoid freeing a table
// which some other thread is reading from.
//
// Note that torn reads are not a concern (e.g. it is fine to use
// memmove to copy class table contents) as long as an appropriate
// barrier is issued before the copy of the table can be observed.
//
// # Hot reload
//
// Each IsolateGroup contains two ClassTable fields: |class_table| and
// |heap_walk_class_table|. GC visitors use the second field to get ClassTable
// instance which they will use for visiting pointers inside instances in
// the heap. Usually these two fields will be pointing to the same table,
// except when IsolateGroup is in the middle of reload.
//
// When reloading |class_table| will be pointing to a copy of the original
// table. Kernel loading will be modifying this table, while GC
// workers can continue using original table still available through
// |heap_walk_class_table|. If hot reload succeeds, |heap_walk_class_table|
// will be dropped and |class_table| will become the source of truth. Otherwise,
// original table will be restored from |heap_walk_class_table|.
//
// See IsolateGroup methods CloneClassTableForReload, RestoreOriginalClassTable,
// DropOriginalClassTable.
class ClassTable : public MallocAllocated {
 public:
  explicit ClassTable(ClassTableAllocator* allocator);

  ~ClassTable();

  ClassTable* Clone() const { return new ClassTable(*this); }

  ClassPtr At(intptr_t cid) const {
    if (IsTopLevelCid(cid)) {
      return top_level_classes_.At<kClassIndex>(IndexFromTopLevelCid(cid));
    }
    return classes_.At<kClassIndex>(cid);
  }

  int32_t SizeAt(intptr_t index) const {
    if (IsTopLevelCid(index)) {
      return 0;
    }
    return classes_.At<kSizeIndex>(index);
  }

  void SetAt(intptr_t index, ClassPtr raw_cls);
  void UpdateClassSize(intptr_t cid, ClassPtr raw_cls);

  bool IsValidIndex(intptr_t cid) const {
    if (IsTopLevelCid(cid)) {
      return top_level_classes_.IsValidIndex(IndexFromTopLevelCid(cid));
    }
    return classes_.IsValidIndex(cid);
  }

  bool HasValidClassAt(intptr_t cid) const { return At(cid) != nullptr; }

  UnboxedFieldBitmap GetUnboxedFieldsMapAt(intptr_t cid) const {
    ASSERT(IsValidIndex(cid));
    return classes_.At<kUnboxedFieldBitmapIndex>(cid);
  }

  void SetUnboxedFieldsMapAt(intptr_t cid, UnboxedFieldBitmap map) {
    ASSERT(IsValidIndex(cid));
    classes_.At<kUnboxedFieldBitmapIndex>(cid) = map;
  }

#if !defined(PRODUCT)
  bool ShouldTraceAllocationFor(intptr_t cid) {
    return !IsTopLevelCid(cid) &&
           (classes_.At<kAllocationTracingStateIndex>(cid) != kTracingDisabled);
  }

  void SetTraceAllocationFor(intptr_t cid, bool trace) {
    classes_.At<kAllocationTracingStateIndex>(cid) =
        trace ? kTraceAllocationBit : kTracingDisabled;
  }

  void SetCollectInstancesFor(intptr_t cid, bool trace) {
    auto& slot = classes_.At<kAllocationTracingStateIndex>(cid);
    if (trace) {
      slot |= kCollectInstancesBit;
    } else {
      slot &= ~kCollectInstancesBit;
    }
  }

  bool CollectInstancesFor(intptr_t cid) {
    auto& slot = classes_.At<kAllocationTracingStateIndex>(cid);
    return (slot & kCollectInstancesBit) != 0;
  }

  void UpdateCachedAllocationTracingStateTablePointer() {
    cached_allocation_tracing_state_table_.store(
        classes_.GetColumn<kAllocationTracingStateIndex>());
  }
#else
  void UpdateCachedAllocationTracingStateTablePointer() {}
#endif  // !defined(PRODUCT)

#if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
  void PopulateUserVisibleNames();

  const char* UserVisibleNameFor(intptr_t cid) {
    if (!classes_.IsValidIndex(cid)) {
      return nullptr;
    }
    return classes_.At<kClassNameIndex>(cid);
  }

  void SetUserVisibleNameFor(intptr_t cid, const char* name) {
    ASSERT(classes_.At<kClassNameIndex>(cid) == nullptr);
    classes_.At<kClassNameIndex>(cid) = name;
  }
#endif  // !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)

  intptr_t NumCids() const { return classes_.num_cids(); }
  intptr_t Capacity() const { return classes_.capacity(); }

  intptr_t NumTopLevelCids() const { return top_level_classes_.num_cids(); }

  void Register(const Class& cls);
  void AllocateIndex(intptr_t index);

  void RegisterTopLevel(const Class& cls);
  void UnregisterTopLevel(intptr_t index);

  void Remap(intptr_t* old_to_new_cids);

  void VisitObjectPointers(ObjectPointerVisitor* visitor);

  // If a snapshot reader has populated the class table then the
  // sizes in the class table are not correct. Iterates through the
  // table, updating the sizes.
  void CopySizesFromClassObjects();

  void Validate();

  void Print();

#if defined(DART_PRECOMPILER)
  void PrintObjectLayout(const char* filename);
#endif

#ifndef PRODUCT
  // Describes layout of heap stats for code generation. See offset_extractor.cc
  struct ArrayTraits {
    static intptr_t elements_start_offset() { return 0; }

    static constexpr intptr_t kElementSize = sizeof(uint8_t);
  };

  static intptr_t allocation_tracing_state_table_offset() {
    static_assert(sizeof(cached_allocation_tracing_state_table_) == kWordSize);
    return OFFSET_OF(ClassTable, cached_allocation_tracing_state_table_);
  }

  void AllocationProfilePrintJSON(JSONStream* stream, bool internal);

  void PrintToJSONObject(JSONObject* object);
#endif  // !PRODUCT

  // Deallocates table copies. Do not call during concurrent access to table.
  void FreeOldTables();

  static bool IsTopLevelCid(intptr_t cid) { return cid >= kTopLevelCidOffset; }

  static intptr_t IndexFromTopLevelCid(intptr_t cid) {
    ASSERT(IsTopLevelCid(cid));
    return cid - kTopLevelCidOffset;
  }

  static intptr_t CidFromTopLevelIndex(intptr_t index) {
    return kTopLevelCidOffset + index;
  }

 private:
  friend class ClassTableAllocator;
  friend class Dart;
  friend Isolate* CreateWithinExistingIsolateGroup(IsolateGroup* group,
                                                   const char* name,
                                                   char** error);
  friend class IsolateGroup;  // for table()
  static constexpr int kInitialCapacity = 4 * KB;

  static constexpr intptr_t kTopLevelCidOffset = kClassIdTagMax + 1;

  ClassTable(const ClassTable& original)
      : allocator_(original.allocator_),
        classes_(original.allocator_),
        top_level_classes_(original.allocator_) {
    classes_.CopyFrom(original.classes_);

#if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
    // Copying classes_ doesn't perform a deep copy. Ensure we duplicate
    // the class names to avoid double free crashes at shutdown.
    for (intptr_t cid = 1; cid < classes_.num_cids(); ++cid) {
      if (classes_.IsValidIndex(cid)) {
        const char* cls_name = classes_.At<kClassNameIndex>(cid);
        if (cls_name != nullptr) {
          classes_.At<kClassNameIndex>(cid) = Utils::StrDup(cls_name);
        }
      }
    }
#endif  // !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)

    top_level_classes_.CopyFrom(original.top_level_classes_);
    UpdateCachedAllocationTracingStateTablePointer();
  }

  void AllocateTopLevelIndex(intptr_t index);

  ClassPtr* table() { return classes_.GetColumn<kClassIndex>(); }

  // Used to drop recently added classes.
  void SetNumCids(intptr_t num_cids, intptr_t num_tlc_cids) {
    classes_.ShrinkTo(num_cids);
    top_level_classes_.ShrinkTo(num_tlc_cids);
  }

  ClassTableAllocator* allocator_;

  // Unfortunately std::tuple used by CidIndexedTable does not have a stable
  // layout so we can't refer to its elements from generated code.
  NOT_IN_PRODUCT(AcqRelAtomic<uint8_t*> cached_allocation_tracing_state_table_ =
                     {nullptr});

  enum {
    kClassIndex = 0,
    kSizeIndex,
    kUnboxedFieldBitmapIndex,
#if !defined(PRODUCT)
    kAllocationTracingStateIndex,
#endif
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
    kClassNameIndex,
#endif
  };

#if !defined(PRODUCT)
  CidIndexedTable<ClassIdTagType,
                  ClassPtr,
                  uint32_t,
                  UnboxedFieldBitmap,
                  uint8_t,
                  const char*>
      classes_;
#elif defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
  CidIndexedTable<ClassIdTagType,
                  ClassPtr,
                  uint32_t,
                  UnboxedFieldBitmap,
                  const char*>
      classes_;
#else
  CidIndexedTable<ClassIdTagType, ClassPtr, uint32_t, UnboxedFieldBitmap>
      classes_;
#endif

#ifndef PRODUCT
  enum {
    kTracingDisabled = 0,
    kTraceAllocationBit = (1 << 0),
    kCollectInstancesBit = (1 << 1),
  };
#endif  // !PRODUCT

  CidIndexedTable<classid_t, ClassPtr> top_level_classes_;
};

}  // namespace dart

#endif  // RUNTIME_VM_CLASS_TABLE_H_
