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

#include <iomanip>

#include "src/execution/arguments-inl.h"
#include "src/execution/frames-inl.h"
#include "src/execution/isolate-inl.h"
#include "src/interpreter/bytecode-array-iterator.h"
#include "src/interpreter/bytecode-decoder.h"
#include "src/interpreter/bytecode-flags-and-tokens.h"
#include "src/interpreter/bytecode-register.h"
#include "src/interpreter/bytecodes.h"
#include "src/interpreter/interpreter.h"
#include "src/logging/counters.h"
#include "src/runtime/runtime-utils.h"
#include "src/snapshot/snapshot.h"
#ifdef V8_DUMPLING
#include "src/dumpling/dumpling-manager.h"
#endif
#include "src/utils/ostreams.h"

namespace v8 {
namespace internal {

namespace {

#if defined(V8_TRACE_UNOPTIMIZED) || defined(V8_DUMPLING)

void AdvanceToOffsetForTracing(
    interpreter::BytecodeArrayIterator& bytecode_iterator, int offset) {
  while (bytecode_iterator.current_offset() +
             bytecode_iterator.current_bytecode_size() <=
         offset) {
    bytecode_iterator.Advance();
  }
  DCHECK(bytecode_iterator.current_offset() == offset ||
         ((bytecode_iterator.current_offset() + 1) == offset &&
          bytecode_iterator.current_operand_scale() >
              interpreter::OperandScale::kSingle));
}

#endif  // V8_TRACE_UNOPTIMIZED || V8_DUMPLING

#ifdef V8_TRACE_UNOPTIMIZED

void PrintRegisterRange(UnoptimizedJSFrame* frame, std::ostream& os,
                        interpreter::BytecodeArrayIterator& bytecode_iterator,
                        const int& reg_field_width, const char* arrow_direction,
                        interpreter::Register first_reg, int range) {
  for (int reg_index = first_reg.index(); reg_index < first_reg.index() + range;
       reg_index++) {
    Tagged<Object> reg_object = frame->ReadInterpreterRegister(reg_index);
    os << "      [ " << std::setw(reg_field_width)
       << interpreter::Register(reg_index).ToString() << arrow_direction;
    ShortPrint(reg_object, os);
    os << " ]" << std::endl;
  }
}

void PrintRegisters(UnoptimizedJSFrame* frame, std::ostream& os, bool is_input,
                    interpreter::BytecodeArrayIterator& bytecode_iterator,
                    Handle<Object> accumulator) {
  static const char kAccumulator[] = "accumulator";
  static const int kRegFieldWidth = static_cast<int>(sizeof(kAccumulator) - 1);
  static const char* kInputColourCode = "\033[0;36m";
  static const char* kOutputColourCode = "\033[0;35m";
  static const char* kNormalColourCode = "\033[0;m";
  const char* kArrowDirection = is_input ? " -> " : " <- ";
  if (v8_flags.log_colour) {
    os << (is_input ? kInputColourCode : kOutputColourCode);
  }

  interpreter::Bytecode bytecode = bytecode_iterator.current_bytecode();

  // Print accumulator.
  if ((is_input && interpreter::Bytecodes::ReadsAccumulator(bytecode)) ||
      (!is_input &&
       interpreter::Bytecodes::WritesOrClobbersAccumulator(bytecode))) {
    os << "      [ " << kAccumulator << kArrowDirection;
    ShortPrint(*accumulator, os);
    os << " ]" << std::endl;
  }

  // Print the registers.
  int operand_count = interpreter::Bytecodes::NumberOfOperands(bytecode);
  for (int operand_index = 0; operand_index < operand_count; operand_index++) {
    interpreter::OperandType operand_type =
        interpreter::Bytecodes::GetOperandType(bytecode, operand_index);
    bool should_print =
        is_input
            ? interpreter::Bytecodes::IsRegisterInputOperandType(operand_type)
            : interpreter::Bytecodes::IsRegisterOutputOperandType(operand_type);
    if (should_print) {
      interpreter::Register first_reg =
          bytecode_iterator.GetRegisterOperand(operand_index);
      int range = bytecode_iterator.GetRegisterOperandRange(operand_index);
      PrintRegisterRange(frame, os, bytecode_iterator, kRegFieldWidth,
                         kArrowDirection, first_reg, range);
    }
  }
  if (!is_input && interpreter::Bytecodes::IsShortStar(bytecode)) {
    PrintRegisterRange(frame, os, bytecode_iterator, kRegFieldWidth,
                       kArrowDirection,
                       interpreter::Register::FromShortStar(bytecode), 1);
  }
  if (v8_flags.log_colour) {
    os << kNormalColourCode;
  }
}

#endif  // V8_TRACE_UNOPTIMIZED

}  // namespace

#ifdef V8_TRACE_UNOPTIMIZED

RUNTIME_FUNCTION(Runtime_TraceUnoptimizedBytecodeEntry) {
  if (!v8_flags.trace_ignition && !v8_flags.trace_baseline_exec) {
    return ReadOnlyRoots(isolate).undefined_value();
  }

  JavaScriptStackFrameIterator frame_iterator(isolate);
  UnoptimizedJSFrame* frame =
      reinterpret_cast<UnoptimizedJSFrame*>(frame_iterator.frame());

  if (frame->is_interpreted() && !v8_flags.trace_ignition) {
    return ReadOnlyRoots(isolate).undefined_value();
  }
  if (frame->is_baseline() && !v8_flags.trace_baseline_exec) {
    return ReadOnlyRoots(isolate).undefined_value();
  }

  SealHandleScope shs(isolate);
  DCHECK_EQ(3, args.length());
  Handle<BytecodeArray> bytecode_array = CheckedCast<BytecodeArray>(args.at(0));
  int bytecode_offset = args.smi_value_at(1);
  Handle<Object> accumulator = args.at(2);

  int offset = bytecode_offset - BytecodeArray::kHeaderSize + kHeapObjectTag;
  interpreter::BytecodeArrayIterator bytecode_iterator(bytecode_array);
  AdvanceToOffsetForTracing(bytecode_iterator, offset);
  if (offset == bytecode_iterator.current_offset()) {
    StdoutStream os;

    // Print bytecode.
    const uint8_t* bytecode_address = bytecode_iterator.current_address();

    if (frame->is_baseline()) {
      os << "B-> ";
    } else {
      os << " -> ";
    }
    os << static_cast<const void*>(bytecode_address) << " @ " << std::setw(4)
       << offset << " : ";
    bytecode_iterator.PrintCurrentBytecodeTo(os);
    os << std::endl;
    // Print all input registers and accumulator.
    PrintRegisters(frame, os, true, bytecode_iterator, accumulator);

    os << std::flush;
  }
  return ReadOnlyRoots(isolate).undefined_value();
}

RUNTIME_FUNCTION(Runtime_TraceUnoptimizedBytecodeExit) {
  if (!v8_flags.trace_ignition && !v8_flags.trace_baseline_exec) {
    return ReadOnlyRoots(isolate).undefined_value();
  }

  JavaScriptStackFrameIterator frame_iterator(isolate);
  UnoptimizedJSFrame* frame =
      reinterpret_cast<UnoptimizedJSFrame*>(frame_iterator.frame());

  if (frame->is_interpreted() && !v8_flags.trace_ignition) {
    return ReadOnlyRoots(isolate).undefined_value();
  }
  if (frame->is_baseline() && !v8_flags.trace_baseline_exec) {
    return ReadOnlyRoots(isolate).undefined_value();
  }

  SealHandleScope shs(isolate);
  DCHECK_EQ(3, args.length());
  Handle<BytecodeArray> bytecode_array = CheckedCast<BytecodeArray>(args.at(0));
  int bytecode_offset = args.smi_value_at(1);
  Handle<Object> accumulator = args.at(2);

  int offset = bytecode_offset - BytecodeArray::kHeaderSize + kHeapObjectTag;
  interpreter::BytecodeArrayIterator bytecode_iterator(bytecode_array);
  AdvanceToOffsetForTracing(bytecode_iterator, offset);
  // The offset comparison here ensures registers only printed when the
  // (potentially) widened bytecode has completed. The iterator reports
  // the offset as the offset of the prefix bytecode.
  if (bytecode_iterator.current_operand_scale() ==
          interpreter::OperandScale::kSingle ||
      offset > bytecode_iterator.current_offset()) {
    StdoutStream os;

    // Print all output registers and accumulator.
    PrintRegisters(frame, os, false, bytecode_iterator, accumulator);
    os << std::flush;
  }
  return ReadOnlyRoots(isolate).undefined_value();
}

#endif  // V8_TRACE_UNOPTIMIZED

#ifdef V8_TRACE_FEEDBACK_UPDATES

RUNTIME_FUNCTION(Runtime_TraceUpdateFeedback) {
  if (!v8_flags.trace_feedback_updates) {
    return ReadOnlyRoots(isolate).undefined_value();
  }

  SealHandleScope shs(isolate);
  DCHECK_EQ(3, args.length());
  Handle<FeedbackVector> vector = args.at<FeedbackVector>(0);
  int slot = args.smi_value_at(1);
  auto reason = Cast<String>(args[2]);

  FeedbackVector::TraceFeedbackChange(isolate, *vector, FeedbackSlot(slot),
                                      reason->ToCString().get());

  return ReadOnlyRoots(isolate).undefined_value();
}

#endif  // V8_TRACE_FEEDBACK_UPDATES

#ifdef V8_DUMPLING

RUNTIME_FUNCTION(Runtime_DumpExecutionFrame) {
  if (!isolate->dumpling_manager()->IsDumpingEnabled()) {
    return ReadOnlyRoots(isolate).undefined_value();
  }
  DCHECK_EQ(3, args.length());

  SealHandleScope shs(isolate);

  DisallowGarbageCollection no_gc;

  JavaScriptStackFrameIterator frame_iterator(isolate);
  UnoptimizedJSFrame* frame =
      reinterpret_cast<UnoptimizedJSFrame*>(frame_iterator.frame());

  Tagged<JSFunction> function = frame->function();
  bool is_sparkplug = frame->is_baseline();
  bool is_interpreter = !is_sparkplug;

  CHECK(!is_sparkplug && is_interpreter && v8_flags.interpreter_dumping);

  Handle<BytecodeArray> bytecode_array = CheckedCast<BytecodeArray>(args.at(0));
  int bytecode_offset = args.smi_value_at(1);

  Handle<Object> accumulator = args.at(2);

  int offset = bytecode_offset - BytecodeArray::kHeaderSize + kHeapObjectTag;
  interpreter::BytecodeArrayIterator bytecode_iterator(bytecode_array);
  AdvanceToOffsetForTracing(bytecode_iterator, offset);

  if (offset == bytecode_iterator.current_offset()) {
    int function_local_bytecode_offset = bytecode_iterator.current_offset();
    DCHECK_GE(function_local_bytecode_offset, 0);
    DumpFrameType frame_dump_type = kInterpreterFrame;
    isolate->dumpling_manager()->DoPrint(frame, function,
                                         function_local_bytecode_offset,
                                         frame_dump_type, accumulator);
  }

  return ReadOnlyRoots(isolate).undefined_value();
}

#endif  //  V8_DUMPLING

}  // namespace internal
}  // namespace v8