// Copyright (c) 2011, 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/assembler/disassembler.h"

#include "platform/text_buffer.h"
#include "platform/unaligned.h"
#include "vm/code_comments.h"
#include "vm/code_patcher.h"
#include "vm/dart_entry.h"
#include "vm/deopt_instructions.h"
#include "vm/globals.h"
#include "vm/instructions.h"
#include "vm/json_stream.h"
#include "vm/log.h"
#include "vm/os.h"

namespace dart {

#if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)

#if !defined(DART_PRECOMPILED_RUNTIME)
DECLARE_FLAG(bool, trace_inlining_intervals);
#endif

DEFINE_FLAG(bool, trace_source_positions, false, "Source position diagnostics");
DEFINE_FLAG(bool,
            include_inlining_info_in_disassembly,
            true,
            "Include inlining information when printing disassembly")

void DisassembleToStdout::ConsumeInstruction(char* hex_buffer,
                                             intptr_t hex_size,
                                             char* human_buffer,
                                             intptr_t human_size,
                                             Object* object,
                                             uword pc) {
  const int kHexColumnWidth = 23;
#if defined(TARGET_ARCH_IS_32_BIT)
  THR_Print("0x%" Px32 "    %s", static_cast<uint32_t>(pc), hex_buffer);
#else
  THR_Print("0x%" Px64 "    %s", static_cast<uint64_t>(pc), hex_buffer);
#endif
  int hex_length = strlen(hex_buffer);
  if (hex_length < kHexColumnWidth) {
    for (int i = kHexColumnWidth - hex_length; i > 0; i--) {
      THR_Print(" ");
    }
  }
  THR_Print("%s", human_buffer);
  if (object != nullptr) {
    if (object->IsString()) {
      *object = String::EscapeSpecialCharacters(String::Cast(*object));
      THR_Print("   \"%s\"", object->ToCString());
    } else {
      THR_Print("   %s", object->ToCString());
    }
  }
  THR_Print("\n");
}

void DisassembleToStdout::Print(const char* format, ...) {
  va_list args;
  va_start(args, format);
  THR_VPrint(format, args);
  va_end(args);
}

void DisassembleToMemory::ConsumeInstruction(char* hex_buffer,
                                             intptr_t hex_size,
                                             char* human_buffer,
                                             intptr_t human_size,
                                             Object* object,
                                             uword pc) {
  if (overflowed_) {
    return;
  }
  intptr_t len;

  // TODO(compiler): Update assembler tests for other architectures so there is
  // coverage of encodings, not just mnemonics.
#if defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64) ||            \
    defined(TARGET_ARCH_ARM)
  len = strlen(hex_buffer);
  if (remaining_ < len + 100) {
    *buffer_++ = '.';
    *buffer_++ = '.';
    *buffer_++ = '.';
    *buffer_++ = '\n';
    *buffer_++ = '\0';
    overflowed_ = true;
    return;
  }
  memmove(buffer_, hex_buffer, len);
  buffer_ += len;
  remaining_ -= len;
  *buffer_++ = ' ';
  remaining_--;
  *buffer_ = '\0';
#endif

  len = strlen(human_buffer);
  if (remaining_ < len + 100) {
    *buffer_++ = '.';
    *buffer_++ = '.';
    *buffer_++ = '.';
    *buffer_++ = '\n';
    *buffer_++ = '\0';
    overflowed_ = true;
    return;
  }
  memmove(buffer_, human_buffer, len);
  buffer_ += len;
  remaining_ -= len;
  *buffer_++ = '\n';
  remaining_--;
  *buffer_ = '\0';
}

void DisassembleToMemory::Print(const char* format, ...) {
  if (overflowed_) {
    return;
  }
  va_list measure_args;
  va_start(measure_args, format);
  intptr_t len = Utils::VSNPrint(nullptr, 0, format, measure_args);
  va_end(measure_args);
  if (remaining_ < len + 100) {
    *buffer_++ = '.';
    *buffer_++ = '.';
    *buffer_++ = '.';
    *buffer_++ = '\n';
    *buffer_++ = '\0';
    overflowed_ = true;
    return;
  }
  va_list print_args;
  va_start(print_args, format);
  intptr_t len2 = Utils::VSNPrint(buffer_, len, format, print_args);
  va_end(print_args);
  ASSERT(len == len2);
  buffer_ += len;
  remaining_ -= len;
  *buffer_++ = '\n';
  remaining_--;
  *buffer_ = '\0';
}

void Disassembler::Disassemble(uword start,
                               uword end,
                               DisassemblyFormatter* formatter,
                               const Code& code,
                               const CodeComments* comments) {
  if (comments == nullptr) {
    comments = code.IsNull() ? &Code::Comments::New(0) : &code.comments();
  }
  ASSERT(formatter != nullptr);
  char hex_buffer[kHexadecimalBufferSize];  // Instruction in hexadecimal form.
  char human_buffer[kUserReadableBufferSize];  // Human-readable instruction.
  uword pc = start;
  intptr_t comment_finger = 0;
  GrowableArray<const Function*> inlined_functions;
  GrowableArray<TokenPosition> token_positions;
  while (pc < end) {
    const intptr_t offset = pc - start;
    const intptr_t old_comment_finger = comment_finger;
    while (comment_finger < comments->Length() &&
           comments->PCOffsetAt(comment_finger) <= offset) {
      formatter->Print("        ;; %s\n", comments->CommentAt(comment_finger));
      comment_finger++;
    }
    if (FLAG_include_inlining_info_in_disassembly &&
        old_comment_finger != comment_finger && !code.IsNull()) {
      char str[4000];
      BufferFormatter f(str, sizeof(str));
      // Comment emitted, emit inlining information.
      code.GetInlinedFunctionsAtInstruction(offset, &inlined_functions,
                                            &token_positions);
      // Skip top scope function printing (last entry in 'inlined_functions').
      bool first = true;
      for (intptr_t i = 1; i < inlined_functions.length(); i++) {
        const char* name = inlined_functions[i]->ToQualifiedCString();
        if (first) {
          f.Printf("        ;; Inlined [%s", name);
          first = false;
        } else {
          f.Printf(" -> %s", name);
        }
      }
      if (!first) {
        f.AddString("]\n");
        formatter->Print("%s", str);
      }
    }
    int instruction_length;
    Object* object;
    DecodeInstruction(hex_buffer, sizeof(hex_buffer), human_buffer,
                      sizeof(human_buffer), &instruction_length, code, &object,
                      pc);
    formatter->ConsumeInstruction(hex_buffer, sizeof(hex_buffer), human_buffer,
                                  sizeof(human_buffer), object,
                                  FLAG_disassemble_relative ? offset : pc);
    pc += instruction_length;
  }
}

void Disassembler::DisassembleCodeHelper(const char* function_fullname,
                                         const char* function_info,
                                         const Code& code,
                                         bool optimized) {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  THR_Print("Code for %sfunction '%s' (%s) {\n", optimized ? "optimized " : "",
            function_fullname, function_info);
  code.Disassemble();
  THR_Print("}\n");

#if defined(TARGET_ARCH_IA32)
  if (code.pointer_offsets_length() > 0) {
    THR_Print("Pointer offsets for function: {\n");
    // Pointer offsets are stored in descending order.
    Object& obj = Object::Handle(zone);
    for (intptr_t i = code.pointer_offsets_length() - 1; i >= 0; i--) {
      const uword addr = code.GetPointerOffsetAt(i) + code.PayloadStart();
      obj = LoadUnaligned(reinterpret_cast<ObjectPtr*>(addr));
      THR_Print(" %d : %#" Px " '%s'\n", code.GetPointerOffsetAt(i), addr,
                obj.ToCString());
    }
    THR_Print("}\n");
  }
#else
  ASSERT(code.pointer_offsets_length() == 0);
#endif

  if (FLAG_precompiled_mode) {
    // Global object pool emitted after it is finalized instead of per-function.
  } else {
    const ObjectPool& object_pool =
        ObjectPool::Handle(zone, code.GetObjectPool());
    if (!object_pool.IsNull() && object_pool.Length() > 0) {
      object_pool.DebugPrint();
    }
  }

  code.DumpSourcePositions(/*relative_addresses=*/FLAG_disassemble_relative);

  const uword start = code.PayloadStart();
  const uword base = FLAG_disassemble_relative ? 0 : start;

  const PcDescriptors& descriptors =
      PcDescriptors::Handle(zone, code.pc_descriptors());
  if (descriptors.Length() > 0) {
    TextBuffer buffer(100);
    buffer.Printf("PC Descriptors for function '%s' {\n", function_fullname);
    descriptors.WriteToBuffer(&buffer, base);
    buffer.AddString("}\n");
    THR_Print("%s", buffer.buffer());
  }

#if !defined(DART_PRECOMPILED_RUNTIME)
  const Array& deopt_table = Array::Handle(zone, code.deopt_info_array());
  if (!deopt_table.IsNull()) {
    intptr_t deopt_table_length = DeoptTable::GetLength(deopt_table);
    if (deopt_table_length > 0) {
      THR_Print("DeoptInfo: {\n");
      Smi& offset = Smi::Handle(zone);
      TypedData& info = TypedData::Handle(zone);
      Smi& reason_and_flags = Smi::Handle(zone);
      for (intptr_t i = 0; i < deopt_table_length; ++i) {
        DeoptTable::GetEntry(deopt_table, i, &offset, &info, &reason_and_flags);
        const intptr_t reason =
            DeoptTable::ReasonField::decode(reason_and_flags.Value());
        ASSERT((0 <= reason) && (reason < ICData::kDeoptNumReasons));
        THR_Print(
            "%4" Pd ": 0x%" Px "  %s  (%s)\n", i, base + offset.Value(),
            DeoptInfo::ToCString(deopt_table, info),
            DeoptReasonToCString(static_cast<ICData::DeoptReasonId>(reason)));
      }
      THR_Print("}\n");
    }
  }
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

  const auto& stackmaps =
      CompressedStackMaps::Handle(zone, code.compressed_stackmaps());
  if (!stackmaps.IsNull() && stackmaps.payload_size() > 0) {
    TextBuffer buffer(100);
    buffer.Printf("StackMaps for function '%s' {\n", function_fullname);
    stackmaps.WriteToBuffer(&buffer, base, "\n");
    buffer.AddString("\n}\n");
    THR_Print("%s", buffer.buffer());
  }

  LocalVarDescriptors& var_descriptors = LocalVarDescriptors::Handle(zone);
  if (FLAG_print_variable_descriptors) {
    var_descriptors = code.GetLocalVarDescriptors();
  }
  const intptr_t var_desc_length =
      var_descriptors.IsNull() ? 0 : var_descriptors.Length();
  if (var_desc_length > 0) {
    THR_Print("Variable Descriptors for function '%s' {\n", function_fullname);
    String& var_name = String::Handle(zone);
    for (intptr_t i = 0; i < var_desc_length; i++) {
      var_name = var_descriptors.GetName(i);
      UntaggedLocalVarDescriptors::VarInfo var_info;
      var_descriptors.GetInfo(i, &var_info);
      const int8_t kind = var_info.kind();
      if (kind == UntaggedLocalVarDescriptors::kSavedCurrentContext) {
        THR_Print("  saved current CTX reg offset %d\n", var_info.index());
      } else {
        if (kind == UntaggedLocalVarDescriptors::kContextLevel) {
          THR_Print("  context level %d scope %d", var_info.index(),
                    var_info.scope_id);
        } else if (kind == UntaggedLocalVarDescriptors::kStackVar) {
          THR_Print("  stack var '%s' offset %d", var_name.ToCString(),
                    var_info.index());
        } else {
          ASSERT(kind == UntaggedLocalVarDescriptors::kContextVar);
          THR_Print("  context var '%s' level %d offset %d",
                    var_name.ToCString(), var_info.scope_id, var_info.index());
        }
        THR_Print(" (valid %s-%s)\n", var_info.begin_pos.ToCString(),
                  var_info.end_pos.ToCString());
      }
    }
    THR_Print("}\n");
  }

  const ExceptionHandlers& handlers =
      ExceptionHandlers::Handle(zone, code.exception_handlers());
  if (handlers.num_entries() > 0 || handlers.has_async_handler()) {
    TextBuffer buffer(100);
    buffer.Printf("Exception Handlers for function '%s' {\n",
                  function_fullname);
    handlers.WriteToBuffer(&buffer, base);
    buffer.AddString("}\n");
    THR_Print("%s", buffer.buffer());
  }

#if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER)
  if (FLAG_precompiled_mode &&
      code.catch_entry_moves_maps() != Object::null()) {
    THR_Print("Catch entry moves for function '%s' {\n", function_fullname);
    CatchEntryMovesMapReader reader(
        TypedData::Handle(code.catch_entry_moves_maps()));
    reader.PrintEntries();
    THR_Print("}\n");
  }
#endif  // defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER)

  {
    THR_Print("Entry points for function '%s' {\n", function_fullname);
    THR_Print("  [code+0x%02" Px "] %" Px " kNormal\n",
              Code::entry_point_offset(CodeEntryKind::kNormal) - kHeapObjectTag,
              code.EntryPoint() - start + base);
    THR_Print(
        "  [code+0x%02" Px "] %" Px " kMonomorphic\n",
        Code::entry_point_offset(CodeEntryKind::kMonomorphic) - kHeapObjectTag,
        code.MonomorphicEntryPoint() - start + base);
    THR_Print(
        "  [code+0x%02" Px "] %" Px " kUnchecked\n",
        Code::entry_point_offset(CodeEntryKind::kUnchecked) - kHeapObjectTag,
        code.UncheckedEntryPoint() - start + base);
    THR_Print("  [code+0x%02" Px "] %" Px " kMonomorphicUnchecked\n",
              Code::entry_point_offset(CodeEntryKind::kMonomorphicUnchecked) -
                  kHeapObjectTag,
              code.MonomorphicUncheckedEntryPoint() - start + base);
    THR_Print("}\n");
  }

#if defined(DART_PRECOMPILED_RUNTIME)
  THR_Print("(Cannot show static call target functions in AOT runtime.)\n");
#else
  const auto& table = Array::Handle(zone, code.static_calls_target_table());
  if (!table.IsNull()) {
    StaticCallsTable static_calls(table);
    if (static_calls.Length() > 0) {
      THR_Print("Static call target functions {\n");
      auto& cls = Class::Handle(zone);
      auto& kind_type_and_offset = Smi::Handle(zone);
      auto& function = Function::Handle(zone);
      auto& object = Object::Handle(zone);
      auto& code = Code::Handle(zone);
      auto& dst_type = AbstractType::Handle(zone);
      for (auto& call : static_calls) {
        kind_type_and_offset = call.Get<Code::kSCallTableKindAndOffset>();
        function = call.Get<Code::kSCallTableFunctionTarget>();
        object = call.Get<Code::kSCallTableCodeOrTypeTarget>();

        dst_type = AbstractType::null();
        if (object.IsAbstractType()) {
          dst_type = AbstractType::Cast(object).ptr();
        } else if (object.IsCode()) {
          code = Code::Cast(object).ptr();
        }

        auto kind = Code::KindField::decode(kind_type_and_offset.Value());
        auto offset = Code::OffsetField::decode(kind_type_and_offset.Value());
        auto entry_point =
            Code::EntryPointField::decode(kind_type_and_offset.Value());

        const char* s_entry_point =
            entry_point == Code::kUncheckedEntry ? " <unchecked-entry>" : "";
        const char* skind = nullptr;
        switch (kind) {
          case Code::kPcRelativeCall:
            skind = "pc-relative-call";
            break;
          case Code::kPcRelativeTTSCall:
            skind = "pc-relative-tts-call";
            break;
          case Code::kPcRelativeTailCall:
            skind = "pc-relative-tail-call";
            break;
          case Code::kCallViaCode:
            skind = "call-via-code";
            break;
          default:
            UNREACHABLE();
        }
        if (!dst_type.IsNull()) {
          THR_Print("  0x%" Px ": type testing stub %s, (%s)%s\n",
                    base + offset, dst_type.ToCString(), skind, s_entry_point);
        } else if (function.IsNull()) {
          cls ^= code.owner();
          if (cls.IsNull()) {
            THR_Print(
                "  0x%" Px ": %s, (%s)%s\n", base + offset,
                code.QualifiedName(NameFormattingParams(
                    Object::kScrubbedName, Object::NameDisambiguation::kYes)),
                skind, s_entry_point);
          } else {
            THR_Print("  0x%" Px ": allocation stub for %s, (%s)%s\n",
                      base + offset, cls.ToCString(), skind, s_entry_point);
          }
        } else {
          THR_Print("  0x%" Px ": %s, (%s)%s\n", base + offset,
                    function.ToFullyQualifiedCString(), skind, s_entry_point);
        }
      }
      THR_Print("}\n");
    }
  }
#endif  // defined(DART_PRECOMPILED_RUNTIME)

#if !defined(DART_PRECOMPILED_RUNTIME)
  if (optimized && FLAG_trace_inlining_intervals) {
    code.DumpInlineIntervals();
  }
#endif

  if (FLAG_trace_source_positions) {
    code.DumpSourcePositions();
  }
}

void Disassembler::DisassembleCode(const Function& function,
                                   const Code& code,
                                   bool optimized) {
  if (code.IsUnknownDartCode()) {
    return;
  }
  if (Log::Current() == Log::NoOpLog()) {
    // Output for this isolate will be shallowed, so don't bother generating it.
    return;
  }
  TextBuffer buffer(128);
  const char* function_fullname = function.ToFullyQualifiedCString();
  buffer.Printf("%s", Function::KindToCString(function.kind()));
  if (function.HasSavedArgumentsDescriptor()) {
    const auto& args_desc_array = Array::Handle(function.saved_args_desc());
    const ArgumentsDescriptor args_desc(args_desc_array);
    buffer.AddString(", ");
    args_desc.PrintTo(&buffer);
  }
  LogBlock lb;
  DisassembleCodeHelper(function_fullname, buffer.buffer(), code, optimized);
}

void Disassembler::DisassembleStub(const char* name, const Code& code) {
  if (Log::Current() == Log::NoOpLog()) {
    // Output for this isolate will be shallowed, so don't bother generating it.
    return;
  }
  LogBlock lb;
  THR_Print("Code for stub '%s': {\n", name);
  DisassembleToStdout formatter;
  code.Disassemble(&formatter);
  THR_Print("}\n");
  const ObjectPool& object_pool = ObjectPool::Handle(code.object_pool());
  if (FLAG_precompiled_mode) {
    THR_Print("(No object pool for bare instructions.)\n");
  } else if (!object_pool.IsNull() && object_pool.Length() > 0) {
    object_pool.DebugPrint();
  }
}

#else   // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)

void Disassembler::DisassembleCode(const Function& function,
                                   const Code& code,
                                   bool optimized) {}
#endif  // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)

#if !defined(PRODUCT)
void DisassembleToJSONStream::ConsumeInstruction(char* hex_buffer,
                                                 intptr_t hex_size,
                                                 char* human_buffer,
                                                 intptr_t human_size,
                                                 Object* object,
                                                 uword pc) {
  // Instructions are represented as four consecutive values in a JSON array.
  // The first is the address of the instruction, the second is the hex string,
  // of the code, and the third is a human readable string, and the fourth is
  // the object loaded by the instruction.
  jsarr_.AddValueF("%" Pp "", pc);
  jsarr_.AddValue(hex_buffer);
  jsarr_.AddValue(human_buffer);

  if (object != nullptr) {
    jsarr_.AddValue(*object);
  } else {
    jsarr_.AddValueNull();  // Not a reference to null.
  }
}

void DisassembleToJSONStream::Print(const char* format, ...) {
  va_list measure_args;
  va_start(measure_args, format);
  intptr_t len = Utils::VSNPrint(nullptr, 0, format, measure_args);
  va_end(measure_args);

  char* p = reinterpret_cast<char*>(malloc(len + 1));
  va_list print_args;
  va_start(print_args, format);
  intptr_t len2 = Utils::VSNPrint(p, len, format, print_args);
  va_end(print_args);
  ASSERT(len == len2);
  for (intptr_t i = 0; i < len; i++) {
    if (p[i] == '\n' || p[i] == '\r') {
      p[i] = ' ';
    }
  }
  // Instructions are represented as four consecutive values in a JSON array.
  // Comments only use the third slot. See above comment for more information.
  jsarr_.AddValueNull();
  jsarr_.AddValueNull();
  jsarr_.AddValue(p);
  jsarr_.AddValueNull();
  free(p);
}
#endif  // !defined(PRODUCT)

}  // namespace dart
