// 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.

#include "vm/class_table.h"

#include <memory>

#include "platform/atomic.h"
#include "vm/flags.h"
#include "vm/growable_array.h"
#include "vm/heap/heap.h"
#include "vm/object.h"
#include "vm/object_graph.h"
#include "vm/raw_object.h"
#include "vm/visitor.h"

namespace dart {

DEFINE_FLAG(bool, print_class_table, false, "Print initial class table.");

ClassTable::ClassTable(ClassTableAllocator* allocator)
    : allocator_(allocator),
      classes_(allocator),
      top_level_classes_(allocator) {
  classes_.SetNumCidsAndCapacity(kNumPredefinedCids, kInitialCapacity);
  UpdateCachedAllocationTracingStateTablePointer();
}

ClassTable::~ClassTable() {
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
  for (intptr_t i = 1; i < classes_.num_cids(); i++) {
    const char* name = UserVisibleNameFor(i);
    if (name != nullptr) {
      free(const_cast<char*>(name));
    }
  }
#endif  // !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
}

void ClassTable::Register(const Class& cls) {
  ASSERT(Thread::Current()->IsDartMutatorThread());
  ASSERT(cls.id() == kIllegalCid || cls.id() < kNumPredefinedCids);
  bool did_grow = false;
  const classid_t cid =
      cls.id() != kIllegalCid ? cls.id() : classes_.AddRow(&did_grow);
  ASSERT(!IsTopLevelCid(cid));

  const intptr_t instance_size =
      cls.is_abstract() ? 0 : Class::host_instance_size(cls.ptr());

  cls.set_id(cid);
  classes_.At<kClassIndex>(cid) = cls.ptr();
  classes_.At<kSizeIndex>(cid) = static_cast<int32_t>(instance_size);
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
  classes_.At<kClassNameIndex>(cid) = nullptr;
#endif  // !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)

  if (did_grow) {
    IsolateGroup::Current()->set_cached_class_table_table(
        classes_.GetColumn<kClassIndex>());
    UpdateCachedAllocationTracingStateTablePointer();
  } else {
    // GCC warns that TSAN doesn't understand thread fences.
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic ignored "-Wtsan"
#endif
    std::atomic_thread_fence(std::memory_order_release);
  }
}

void ClassTable::RegisterTopLevel(const Class& cls) {
  ASSERT(Thread::Current()->IsDartMutatorThread());
  ASSERT(cls.id() == kIllegalCid);

  bool did_grow = false;
  const intptr_t index = top_level_classes_.AddRow(&did_grow);
  cls.set_id(ClassTable::CidFromTopLevelIndex(index));
  top_level_classes_.At<kClassIndex>(index) = cls.ptr();
}

void ClassTable::AllocateIndex(intptr_t index) {
  bool did_grow = false;
  if (IsTopLevelCid(index)) {
    top_level_classes_.AllocateIndex(IndexFromTopLevelCid(index), &did_grow);
    return;
  }

  classes_.AllocateIndex(index, &did_grow);
  if (did_grow) {
    IsolateGroup::Current()->set_cached_class_table_table(table());
    UpdateCachedAllocationTracingStateTablePointer();
  }
}

void ClassTable::UnregisterTopLevel(intptr_t cid) {
  ASSERT(IsTopLevelCid(cid));
  const intptr_t tlc_index = IndexFromTopLevelCid(cid);
  top_level_classes_.At<kClassIndex>(tlc_index) = nullptr;
}

void ClassTable::Remap(intptr_t* old_to_new_cid) {
  ASSERT(Thread::Current()->OwnsSafepoint());
  classes_.Remap(old_to_new_cid);
}

void ClassTable::VisitObjectPointers(ObjectPointerVisitor* visitor) {
  ASSERT(visitor != nullptr);
  visitor->set_gc_root_type("class table");

  const auto visit = [&](ClassPtr* table, intptr_t num_cids) {
    if (num_cids == 0) {
      return;
    }
    ObjectPtr* from = reinterpret_cast<ObjectPtr*>(&table[0]);
    ObjectPtr* to = reinterpret_cast<ObjectPtr*>(&table[num_cids - 1]);
    visitor->VisitPointers(from, to);
  };

  visit(classes_.GetColumn<kClassIndex>(), classes_.num_cids());
  visit(top_level_classes_.GetColumn<kClassIndex>(),
        top_level_classes_.num_cids());
  visitor->clear_gc_root_type();
}

void ClassTable::CopySizesFromClassObjects() {
  ASSERT(kIllegalCid == 0);
  for (intptr_t i = 1; i < classes_.num_cids(); i++) {
    UpdateClassSize(i, classes_.At<kClassIndex>(i));
  }
}

void ClassTable::SetAt(intptr_t cid, ClassPtr raw_cls) {
  if (IsTopLevelCid(cid)) {
    top_level_classes_.At<kClassIndex>(IndexFromTopLevelCid(cid)) = raw_cls;
    return;
  }

  // This is called by snapshot reader and class finalizer.
  UpdateClassSize(cid, raw_cls);
  classes_.At<kClassIndex>(cid) = raw_cls;
}

void ClassTable::UpdateClassSize(intptr_t cid, ClassPtr raw_cls) {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  ASSERT(!IsTopLevelCid(cid));  // "top-level" classes don't get instantiated
  const intptr_t size =
      raw_cls == nullptr ? 0 : Class::host_instance_size(raw_cls);
  classes_.At<kSizeIndex>(cid) = static_cast<int32_t>(size);
}

void ClassTable::Validate() {
  Class& cls = Class::Handle();
  for (intptr_t cid = kNumPredefinedCids; cid < classes_.num_cids(); cid++) {
    // Some of the class table entries maybe nullptr as we create some
    // top level classes but do not add them to the list of anonymous
    // classes in a library if there are no top level fields or functions.
    // Since there are no references to these top level classes they are
    // not written into a full snapshot and will not be recreated when
    // we read back the full snapshot. These class slots end up with nullptr
    // entries.
    if (HasValidClassAt(cid)) {
      cls = At(cid);
      ASSERT(cls.IsClass());
#if defined(DART_PRECOMPILER)
      // Precompiler can drop classes and set their id() to kIllegalCid.
      // It still leaves them in the class table so dropped program
      // structure could still be accessed while writing debug info.
      ASSERT((cls.id() == cid) || (cls.id() == kIllegalCid));
#else
      ASSERT(cls.id() == cid);
#endif  // defined(DART_PRECOMPILER)
    }
  }
}

void ClassTable::Print() {
  Class& cls = Class::Handle();
  String& name = String::Handle();

  for (intptr_t i = 1; i < classes_.num_cids(); i++) {
    if (!HasValidClassAt(i)) {
      continue;
    }
    cls = At(i);
    if (cls.ptr() != nullptr) {
      name = cls.Name();
      OS::PrintErr("%" Pd ": %s\n", i, name.ToCString());
    }
  }
}

#if defined(DART_PRECOMPILER)
void ClassTable::PrintObjectLayout(const char* filename) {
  Class& cls = Class::Handle();
  Array& fields = Array::Handle();
  Field& field = Field::Handle();

  JSONWriter js;
  js.OpenArray();
  for (intptr_t i = ClassId::kObjectCid; i < classes_.num_cids(); i++) {
    if (!HasValidClassAt(i)) {
      continue;
    }
    cls = At(i);
    ASSERT(!cls.IsNull());
    ASSERT(cls.id() != kIllegalCid);
    ASSERT(cls.is_finalized());  // Precompiler already finalized all classes.
    ASSERT(!cls.IsTopLevel());
    js.OpenObject();
    js.PrintProperty("class", cls.UserVisibleNameCString());
    js.PrintProperty("size", cls.target_instance_size());
    js.OpenArray("fields");
    fields = cls.fields();
    if (!fields.IsNull()) {
      for (intptr_t i = 0, n = fields.Length(); i < n; ++i) {
        field ^= fields.At(i);
        js.OpenObject();
        js.PrintProperty("field", field.UserVisibleNameCString());
        if (field.is_static()) {
          js.PrintPropertyBool("static", true);
        } else {
          js.PrintProperty("offset", field.TargetOffset());
        }
        js.CloseObject();
      }
    }
    js.CloseArray();
    js.CloseObject();
  }
  js.CloseArray();

  auto file_open = Dart::file_open_callback();
  auto file_write = Dart::file_write_callback();
  auto file_close = Dart::file_close_callback();
  if ((file_open == nullptr) || (file_write == nullptr) ||
      (file_close == nullptr)) {
    OS::PrintErr("warning: Could not access file callbacks.");
    return;
  }

  void* file = file_open(filename, /*write=*/true);
  if (file == nullptr) {
    OS::PrintErr("warning: Failed to write object layout: %s\n", filename);
    return;
  }

  char* output = nullptr;
  intptr_t output_length = 0;
  js.Steal(&output, &output_length);
  file_write(output, output_length, file);
  free(output);
  file_close(file);
}
#endif  // defined(DART_PRECOMPILER)

#if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
void ClassTable::PopulateUserVisibleNames() {
  Class& cls = Class::Handle();
  for (intptr_t i = 0; i < classes_.num_cids(); ++i) {
    if (HasValidClassAt(i) && UserVisibleNameFor(i) == nullptr) {
      cls = At(i);
      cls.SetUserVisibleNameInClassTable();
    }
  }
}
#endif  // !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)

#if !defined(PRODUCT)

void ClassTable::PrintToJSONObject(JSONObject* object) {
  Class& cls = Class::Handle();
  object->AddProperty("type", "ClassList");
  {
    JSONArray members(object, "classes");
    for (intptr_t i = ClassId::kObjectCid; i < classes_.num_cids(); i++) {
      if (HasValidClassAt(i)) {
        cls = At(i);
        members.AddValue(cls);
      }
    }
  }
}

void ClassTable::AllocationProfilePrintJSON(JSONStream* stream, bool internal) {
  Isolate* isolate = Isolate::Current();
  ASSERT(isolate != nullptr);
  auto isolate_group = isolate->group();
  Heap* heap = isolate_group->heap();
  ASSERT(heap != nullptr);
  JSONObject obj(stream);
  obj.AddProperty("type", "AllocationProfile");
  if (isolate_group->last_allocationprofile_accumulator_reset_timestamp() !=
      0) {
    obj.AddPropertyF(
        "dateLastAccumulatorReset", "%" Pd64 "",
        isolate_group->last_allocationprofile_accumulator_reset_timestamp());
  }
  if (isolate_group->last_allocationprofile_gc_timestamp() != 0) {
    obj.AddPropertyF("dateLastServiceGC", "%" Pd64 "",
                     isolate_group->last_allocationprofile_gc_timestamp());
  }

  if (internal) {
    JSONObject heaps(&obj, "_heaps");
    {
      heap->PrintToJSONObject(Heap::kNew, &heaps);
    }
    {
      heap->PrintToJSONObject(Heap::kOld, &heaps);
    }
  }

  {
    JSONObject memory(&obj, "memoryUsage");
    {
      heap->PrintMemoryUsageJSON(&memory);
    }
  }

  Thread* thread = Thread::Current();
  CountObjectsVisitor visitor(thread, NumCids());
  {
    HeapIterationScope iter(thread);
    iter.IterateObjects(&visitor);
    isolate->group()->VisitWeakPersistentHandles(&visitor);
  }

  {
    JSONArray arr(&obj, "members");
    Class& cls = Class::Handle();
    for (intptr_t i = 3; i < classes_.num_cids(); i++) {
      if (!HasValidClassAt(i)) continue;

      cls = At(i);
      if (cls.IsNull()) continue;

      JSONObject obj(&arr);
      obj.AddProperty("type", "ClassHeapStats");
      obj.AddProperty("class", cls);
      intptr_t count = visitor.new_count_[i] + visitor.old_count_[i];
      intptr_t size = visitor.new_size_[i] + visitor.old_size_[i];
      obj.AddProperty64("instancesAccumulated", count);
      obj.AddProperty64("accumulatedSize", size);
      obj.AddProperty64("instancesCurrent", count);
      obj.AddProperty64("bytesCurrent", size);

      if (internal) {
        {
          JSONArray new_stats(&obj, "_new");
          new_stats.AddValue(visitor.new_count_[i]);
          new_stats.AddValue(visitor.new_size_[i]);
          new_stats.AddValue(visitor.new_external_size_[i]);
        }
        {
          JSONArray old_stats(&obj, "_old");
          old_stats.AddValue(visitor.old_count_[i]);
          old_stats.AddValue(visitor.old_size_[i]);
          old_stats.AddValue(visitor.old_external_size_[i]);
        }
      }
    }
  }
}
#endif  // !PRODUCT

ClassTableAllocator::ClassTableAllocator()
    : pending_freed_(new MallocGrowableArray<std::pair<void*, Deleter>>()) {}

ClassTableAllocator::~ClassTableAllocator() {
  FreePending();
  delete pending_freed_;
}

void ClassTableAllocator::Free(ClassTable* ptr) {
  if (ptr != nullptr) {
    pending_freed_->Add(std::make_pair(
        ptr, [](void* ptr) { delete static_cast<ClassTable*>(ptr); }));
  }
}

void ClassTableAllocator::Free(void* ptr) {
  if (ptr != nullptr) {
    pending_freed_->Add(std::make_pair(ptr, nullptr));
  }
}

void ClassTableAllocator::FreePending() {
  while (!pending_freed_->is_empty()) {
    auto [ptr, deleter] = pending_freed_->RemoveLast();
    if (deleter == nullptr) {
      free(ptr);
    } else {
      deleter(ptr);
    }
  }
}

}  // namespace dart
