// Copyright (c) 2020, 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/compiler/aot/precompiler_tracer.h"

#include "vm/compiler/aot/precompiler.h"
#include "vm/zone_text_buffer.h"

namespace dart {

#if defined(DART_PRECOMPILER)

DEFINE_FLAG(charp,
            trace_precompiler_to,
            nullptr,
            "Output machine readable precompilation trace into the given file");

PrecompilerTracer* PrecompilerTracer::StartTracingIfRequested(
    Precompiler* precompiler) {
  const char* filename = FLAG_trace_precompiler_to;
  if (filename == nullptr) {
    return nullptr;
  }
  if ((Dart::file_write_callback() == nullptr) ||
      (Dart::file_open_callback() == nullptr) ||
      (Dart::file_close_callback() == nullptr)) {
    OS::PrintErr("warning: Could not access file callbacks.");
    return nullptr;
  }
  void* file = Dart::file_open_callback()(filename, /*write=*/true);
  if (file == nullptr) {
    OS::PrintErr("warning: Failed to write precompiler trace: %s\n", filename);
    return nullptr;
  }
  return new PrecompilerTracer(precompiler, file);
}

PrecompilerTracer::PrecompilerTracer(Precompiler* precompiler, void* stream)
    : zone_(Thread::Current()->zone()),
      precompiler_(precompiler),
      buffer_(1024),
      stream_(stream),
      strings_(HashTables::New<StringTable>(1024)),
      entities_(HashTables::New<EntityTable>(1024)),
      object_(Object::Handle()),
      cls_(Class::Handle()) {
  Write("{\"trace\":[\"R\",");
}

void PrecompilerTracer::Finalize() {
  Write("\"E\"],");
  WriteEntityTable();
  Write(",");
  WriteStringTable();
  Write("}\n");

  const intptr_t output_length = buffer_.length();
  char* output = buffer_.Steal();
  Dart::file_write_callback()(output, output_length, stream_);
  free(output);
  Dart::file_close_callback()(stream_);

  strings_.Release();
  entities_.Release();
}

void PrecompilerTracer::WriteEntityTable() {
  Write("\"entities\":[");
  const auto& entities_by_id =
      Array::Handle(zone_, Array::New(entities_.NumOccupied()));

  EntityTable::Iterator it(&entities_);
  while (it.MoveNext()) {
    object_ = entities_.GetPayload(it.Current(), 0);
    const intptr_t index = Smi::Cast(object_).Value();
    object_ = entities_.GetKey(it.Current());
    entities_by_id.SetAt(index, object_);
  }

  auto& obj = Object::Handle(zone_);
  auto& lib = Library::Handle(zone_);
  auto& str = String::Handle(zone_);
  for (intptr_t i = 0; i < entities_by_id.Length(); i++) {
    if (i > 0) {
      Write(",");
    }
    obj = entities_by_id.At(i);
    if (obj.IsFunction()) {
      const auto& fun = Function::Cast(obj);
      cls_ = fun.Owner();
      const intptr_t selector_id =
          precompiler_->selector_map()->SelectorId(fun);
      Write("\"%c\",%" Pd ",%" Pd ",%" Pd "",
            fun.IsDynamicFunction() ? 'F' : 'S', InternEntity(cls_),
            InternString(NameForTrace(fun)), selector_id);
    } else if (obj.IsField()) {
      const auto& field = Field::Cast(obj);
      cls_ = field.Owner();
      str = field.name();
      Write("\"V\",%" Pd ",%" Pd ",0", InternEntity(cls_), InternString(str));
    } else if (obj.IsClass()) {
      const auto& cls = Class::Cast(obj);
      lib = cls.library();
      str = lib.url();
      const auto url_id = InternString(str);
      str = cls.ScrubbedName();
      const auto name_id = InternString(str);
      Write("\"C\",%" Pd ",%" Pd ",0", url_id, name_id);
    } else {
      UNREACHABLE();
    }
  }
  Write("]");
}

void PrecompilerTracer::WriteStringTable() {
  Write("\"strings\":[");
  GrowableArray<const char*> strings_by_id(strings_.NumOccupied());
  strings_by_id.EnsureLength(strings_.NumOccupied(), nullptr);
  StringTable::Iterator it(&strings_);
  while (it.MoveNext()) {
    object_ = strings_.GetPayload(it.Current(), 0);
    const auto index = Smi::Cast(object_).Value();
    object_ = strings_.GetKey(it.Current());
    strings_by_id[index] = String::Cast(object_).ToCString();
  }
  auto comma = false;
  for (auto str : strings_by_id) {
    Write("%s\"%s\"", comma ? "," : "", str);
    comma = true;
  }
  Write("]");
}

intptr_t PrecompilerTracer::InternString(const CString& cstr) {
  object_ = Smi::New(strings_.NumOccupied());
  object_ = strings_.InsertNewOrGetValue(cstr, object_);
  return Smi::Cast(object_).Value();
}

intptr_t PrecompilerTracer::InternString(const String& str) {
  object_ = Smi::New(strings_.NumOccupied());
  object_ = strings_.InsertOrGetValue(str, object_);
  return Smi::Cast(object_).Value();
}

intptr_t PrecompilerTracer::InternEntity(const Object& obj) {
  ASSERT(obj.IsFunction() || obj.IsClass() || obj.IsField());
  const auto num_occupied = entities_.NumOccupied();
  object_ = Smi::New(num_occupied);
  object_ = entities_.InsertOrGetValue(obj, object_);
  const auto id = Smi::Cast(object_).Value();
  if (id == num_occupied) {
    cls_ = Class::null();
    if (obj.IsFunction()) {
      cls_ = Function::Cast(obj).Owner();
    } else if (obj.IsField()) {
      cls_ = Field::Cast(obj).Owner();
    }
    if (cls_.ptr() != Class::null()) {
      InternEntity(cls_);
    }
  }
  return id;
}

PrecompilerTracer::CString PrecompilerTracer::NameForTrace(const Function& f) {
  ZoneTextBuffer buffer(zone_);
  f.PrintName(NameFormattingParams::DisambiguatedWithoutClassName(
                  Object::NameVisibility::kInternalName),
              &buffer);
  return {buffer.buffer(), buffer.length(),
          String::Hash(buffer.buffer(), buffer.length())};
}

#endif  // defined(DART_PRECOMPILER)

}  // namespace dart
