// 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 "src/interpreter/bytecode-array-iterator.h"

#include "src/codegen/bailout-reason.h"
#include "src/interpreter/bytecode-decoder.h"
#include "src/interpreter/interpreter-intrinsics.h"
#include "src/objects/feedback-vector.h"
#include "src/objects/objects-inl.h"

namespace v8 {
namespace internal {
namespace interpreter {

BytecodeArrayIterator::BytecodeArrayIterator(
    Handle<BytecodeArray> bytecode_array, int initial_offset)
    : bytecode_array_(bytecode_array),
      start_(reinterpret_cast<uint8_t*>(
          bytecode_array_->GetFirstBytecodeAddress())),
      end_(start_ + bytecode_array_->length()),
      cursor_(start_),
      operand_scale_(OperandScale::kSingle),
      prefix_size_(0),
      local_heap_(LocalHeap::Current()
                      ? LocalHeap::Current()
                      : Isolate::Current()->main_thread_local_heap()) {
  local_heap_->AddGCEpilogueCallback(UpdatePointersCallback, this);
  UpdateOperandScale();
  if (initial_offset != 0) {
    AdvanceTo(initial_offset);
  }
}

BytecodeArrayIterator::BytecodeArrayIterator(
    Handle<BytecodeArray> bytecode_array, int initial_offset,
    DisallowGarbageCollection& no_gc)
    : bytecode_array_(bytecode_array),
      start_(reinterpret_cast<uint8_t*>(
          bytecode_array_->GetFirstBytecodeAddress())),
      end_(start_ + bytecode_array_->length()),
      cursor_(start_),
      operand_scale_(OperandScale::kSingle),
      prefix_size_(0),
      local_heap_(nullptr) {
  // Don't add a GC callback, since we're in a no_gc scope.
  UpdateOperandScale();
  if (initial_offset != 0) {
    AdvanceTo(initial_offset);
  }
}

BytecodeArrayIterator::~BytecodeArrayIterator() {
  if (local_heap_) {
    local_heap_->RemoveGCEpilogueCallback(UpdatePointersCallback, this);
  }
}

void BytecodeArrayIterator::AdvanceTo(int offset) {
  DCHECK_GE(offset, current_offset());
  while (current_offset() != offset && cursor_ < end_) {
    Advance();
  }
  // Make sure we're always at a valid offset.
  CHECK_EQ(current_offset(), offset);
}

void BytecodeArrayIterator::SetOffset(int offset) {
  DCHECK_GE(offset, 0);
  if (offset < current_offset()) {
    Reset();
  }
  // Advance to the given offset instead of just setting cursor_.
  // This way, we can guarantee that the offset is always valid.
  AdvanceTo(offset);
}

void BytecodeArrayIterator::Reset() {
  cursor_ = start_;
  UpdateOperandScale();
}

// protected
void BytecodeArrayIterator::SetOffsetUnchecked(int offset) {
  DCHECK_GE(offset, 0);
  cursor_ = start_ + offset;
  UpdateOperandScale();
}

// static
bool BytecodeArrayIterator::IsValidOffset(Handle<BytecodeArray> bytecode_array,
                                          int offset) {
  for (BytecodeArrayIterator it(bytecode_array); !it.done(); it.Advance()) {
    if (it.current_offset() == offset) return true;
    if (it.current_offset() > offset) break;
  }
  return false;
}

// static
bool BytecodeArrayIterator::IsValidOSREntryOffset(
    Handle<BytecodeArray> bytecode_array, int offset) {
  BytecodeArrayIterator it(bytecode_array, offset);
  return it.CurrentBytecodeIsValidOSREntry();
}

bool BytecodeArrayIterator::CurrentBytecodeIsValidOSREntry() const {
  return current_bytecode() == interpreter::Bytecode::kJumpLoop;
}

void BytecodeArrayIterator::ApplyDebugBreak() {
  // Get the raw bytecode from the bytecode array. This may give us a
  // scaling prefix, which we can patch with the matching debug-break
  // variant.
  uint8_t* cursor = cursor_ - prefix_size_;
  interpreter::Bytecode bytecode = interpreter::Bytecodes::FromByte(*cursor);
  if (interpreter::Bytecodes::IsDebugBreak(bytecode)) return;
  interpreter::Bytecode debugbreak =
      interpreter::Bytecodes::GetDebugBreak(bytecode);
  *cursor = interpreter::Bytecodes::ToByte(debugbreak);
}

uint32_t BytecodeArrayIterator::GetUnsignedOperand(
    int operand_index, OperandType operand_type) const {
  DCHECK_GE(operand_index, 0);
  DCHECK_LT(operand_index, Bytecodes::NumberOfOperands(current_bytecode()));
  DCHECK_EQ(operand_type,
            Bytecodes::GetOperandType(current_bytecode(), operand_index));
  DCHECK(Bytecodes::IsUnsignedOperandType(operand_type));
  Address operand_start =
      reinterpret_cast<Address>(cursor_) +
      Bytecodes::GetOperandOffset(current_bytecode(), operand_index,
                                  current_operand_scale());
  return BytecodeDecoder::DecodeUnsignedOperand(operand_start, operand_type,
                                                current_operand_scale());
}

int32_t BytecodeArrayIterator::GetSignedOperand(
    int operand_index, OperandType operand_type) const {
  DCHECK_GE(operand_index, 0);
  DCHECK_LT(operand_index, Bytecodes::NumberOfOperands(current_bytecode()));
  DCHECK_EQ(operand_type,
            Bytecodes::GetOperandType(current_bytecode(), operand_index));
  DCHECK(!Bytecodes::IsUnsignedOperandType(operand_type));
  Address operand_start =
      reinterpret_cast<Address>(cursor_) +
      Bytecodes::GetOperandOffset(current_bytecode(), operand_index,
                                  current_operand_scale());
  return BytecodeDecoder::DecodeSignedOperand(operand_start, operand_type,
                                              current_operand_scale());
}

uint32_t BytecodeArrayIterator::GetFlag8Operand(int operand_index) const {
  DCHECK_EQ(Bytecodes::GetOperandType(current_bytecode(), operand_index),
            OperandType::kFlag8);
  return GetUnsignedOperand(operand_index, OperandType::kFlag8);
}

uint32_t BytecodeArrayIterator::GetFlag16Operand(int operand_index) const {
  DCHECK_EQ(Bytecodes::GetOperandType(current_bytecode(), operand_index),
            OperandType::kFlag16);
  return GetUnsignedOperand(operand_index, OperandType::kFlag16);
}

uint32_t BytecodeArrayIterator::GetUnsignedImmediateOperand(
    int operand_index) const {
  DCHECK_EQ(Bytecodes::GetOperandType(current_bytecode(), operand_index),
            OperandType::kUImm);
  return GetUnsignedOperand(operand_index, OperandType::kUImm);
}

int32_t BytecodeArrayIterator::GetImmediateOperand(int operand_index) const {
  DCHECK_EQ(Bytecodes::GetOperandType(current_bytecode(), operand_index),
            OperandType::kImm);
  return GetSignedOperand(operand_index, OperandType::kImm);
}

uint32_t BytecodeArrayIterator::GetRegisterCountOperand(
    int operand_index) const {
  DCHECK_EQ(Bytecodes::GetOperandType(current_bytecode(), operand_index),
            OperandType::kRegCount);
  return GetUnsignedOperand(operand_index, OperandType::kRegCount);
}

uint32_t BytecodeArrayIterator::GetConstantPoolIndexOperand(
    int operand_index) const {
  OperandType operand_type =
      Bytecodes::GetOperandType(current_bytecode(), operand_index);
  DCHECK_EQ(operand_type, OperandType::kConstantPoolIndex);
  return GetUnsignedOperand(operand_index, operand_type);
}

uint32_t BytecodeArrayIterator::GetFeedbackSlotOperand(
    int operand_index) const {
  OperandType operand_type =
      Bytecodes::GetOperandType(current_bytecode(), operand_index);
  DCHECK_EQ(operand_type, OperandType::kFeedbackSlot);
  return GetUnsignedOperand(operand_index, operand_type);
}

uint32_t BytecodeArrayIterator::GetContextSlotOperand(int operand_index) const {
  OperandType operand_type =
      Bytecodes::GetOperandType(current_bytecode(), operand_index);
  DCHECK_EQ(operand_type, OperandType::kContextSlot);
  return GetUnsignedOperand(operand_index, operand_type);
}

uint32_t BytecodeArrayIterator::GetCoverageSlotOperand(
    int operand_index) const {
  OperandType operand_type =
      Bytecodes::GetOperandType(current_bytecode(), operand_index);
  DCHECK_EQ(operand_type, OperandType::kCoverageSlot);
  return GetUnsignedOperand(operand_index, operand_type);
}

FeedbackSlot BytecodeArrayIterator::GetSlotOperand(int operand_index) const {
  int index = GetFeedbackSlotOperand(operand_index);
  return FeedbackVector::ToSlot(index);
}

Register BytecodeArrayIterator::GetParameter(int parameter_index) const {
  DCHECK_GE(parameter_index, 0);
  // The parameter indices are shifted by 1 (receiver is the
  // first entry).
  return Register::FromParameterIndex(parameter_index + 1);
}

Register BytecodeArrayIterator::GetRegisterOperand(int operand_index) const {
  OperandType operand_type =
      Bytecodes::GetOperandType(current_bytecode(), operand_index);
  Address operand_start =
      reinterpret_cast<Address>(cursor_) +
      Bytecodes::GetOperandOffset(current_bytecode(), operand_index,
                                  current_operand_scale());
  return BytecodeDecoder::DecodeRegisterOperand(operand_start, operand_type,
                                                current_operand_scale());
}

Register BytecodeArrayIterator::GetStarTargetRegister() const {
  Bytecode bytecode = current_bytecode();
  DCHECK(Bytecodes::IsAnyStar(bytecode));
  if (Bytecodes::IsShortStar(bytecode)) {
    return Register::FromShortStar(bytecode);
  } else {
    DCHECK_EQ(bytecode, Bytecode::kStar);
    DCHECK_EQ(Bytecodes::NumberOfOperands(bytecode), 1);
    DCHECK_EQ(Bytecodes::GetOperandTypes(bytecode)[0], OperandType::kRegOut);
    return GetRegisterOperand(0);
  }
}

std::pair<Register, Register> BytecodeArrayIterator::GetRegisterPairOperand(
    int operand_index) const {
  Register first = GetRegisterOperand(operand_index);
  Register second(first.index() + 1);
  return std::make_pair(first, second);
}

RegisterList BytecodeArrayIterator::GetRegisterListOperand(
    int operand_index) const {
  Register first = GetRegisterOperand(operand_index);
  uint32_t count = GetRegisterCountOperand(operand_index + 1);
  return RegisterList(first.index(), count);
}

int BytecodeArrayIterator::GetRegisterOperandRange(int operand_index) const {
  DCHECK_LE(operand_index, Bytecodes::NumberOfOperands(current_bytecode()));
  const OperandType* operand_types =
      Bytecodes::GetOperandTypes(current_bytecode());
  OperandType operand_type = operand_types[operand_index];
  DCHECK(Bytecodes::IsRegisterOperandType(operand_type));
  if (operand_type == OperandType::kRegList ||
      operand_type == OperandType::kRegOutList) {
    return GetRegisterCountOperand(operand_index + 1);
  } else {
    return Bytecodes::GetNumberOfRegistersRepresentedBy(operand_type);
  }
}

Runtime::FunctionId BytecodeArrayIterator::GetRuntimeIdOperand(
    int operand_index) const {
  OperandType operand_type =
      Bytecodes::GetOperandType(current_bytecode(), operand_index);
  DCHECK_EQ(operand_type, OperandType::kRuntimeId);
  uint32_t raw_id = GetUnsignedOperand(operand_index, operand_type);
  return static_cast<Runtime::FunctionId>(raw_id);
}

uint32_t BytecodeArrayIterator::GetNativeContextIndexOperand(
    int operand_index) const {
  OperandType operand_type =
      Bytecodes::GetOperandType(current_bytecode(), operand_index);
  DCHECK_EQ(operand_type, OperandType::kNativeContextIndex);
  return GetUnsignedOperand(operand_index, operand_type);
}

Runtime::FunctionId BytecodeArrayIterator::GetIntrinsicIdOperand(
    int operand_index) const {
  OperandType operand_type =
      Bytecodes::GetOperandType(current_bytecode(), operand_index);
  DCHECK_EQ(operand_type, OperandType::kIntrinsicId);
  uint32_t raw_id = GetUnsignedOperand(operand_index, operand_type);
  return IntrinsicsHelper::ToRuntimeId(
      static_cast<IntrinsicsHelper::IntrinsicId>(raw_id));
}

AbortReason BytecodeArrayIterator::GetAbortReasonOperand(
    int operand_index) const {
  OperandType operand_type =
      Bytecodes::GetOperandType(current_bytecode(), operand_index);
  DCHECK_EQ(operand_type, OperandType::kAbortReason);
  return static_cast<AbortReason>(
      GetUnsignedOperand(operand_index, operand_type));
}

Handle<Object> BytecodeArrayIterator::GetConstantAtIndex(
    int index, Isolate* isolate) const {
  return handle(bytecode_array()->constant_pool()->get(index), isolate);
}

Handle<Object> BytecodeArrayIterator::GetConstantAtIndex(
    int index, LocalIsolate* isolate) const {
  return handle(bytecode_array()->constant_pool()->get(index), isolate);
}

Tagged<Smi> BytecodeArrayIterator::GetConstantAtIndexAsSmi(int index) const {
  return Cast<Smi>(bytecode_array()->constant_pool()->get(index));
}

Handle<Object> BytecodeArrayIterator::GetConstantForOperand(
    int operand_index, Isolate* isolate) const {
  return GetConstantAtIndex(GetConstantPoolIndexOperand(operand_index),
                            isolate);
}

Handle<Object> BytecodeArrayIterator::GetConstantForOperand(
    int operand_index, LocalIsolate* isolate) const {
  return GetConstantAtIndex(GetConstantPoolIndexOperand(operand_index),
                            isolate);
}

int BytecodeArrayIterator::GetRelativeJumpTargetOffset() const {
  Bytecode bytecode = current_bytecode();
  if (interpreter::Bytecodes::IsJumpImmediate(bytecode)) {
    int relative_offset = GetUnsignedImmediateOperand(0);
    if (bytecode == Bytecode::kJumpLoop) {
      relative_offset = -relative_offset;
    }
    return relative_offset;
  } else if (interpreter::Bytecodes::IsJumpConstant(bytecode)) {
    Tagged<Smi> smi = GetConstantAtIndexAsSmi(GetConstantPoolIndexOperand(0));
    return smi.value();
  } else {
    UNREACHABLE();
  }
}

int BytecodeArrayIterator::GetJumpTargetOffset() const {
  return GetAbsoluteOffset(GetRelativeJumpTargetOffset());
}

JumpTableTargetOffsets BytecodeArrayIterator::GetJumpTableTargetOffsets()
    const {
  uint32_t table_start, table_size;
  int32_t case_value_base;
  if (current_bytecode() == Bytecode::kSwitchOnGeneratorState) {
    table_start = GetConstantPoolIndexOperand(1);
    table_size = GetUnsignedImmediateOperand(2);
    case_value_base = 0;
  } else {
    DCHECK_EQ(current_bytecode(), Bytecode::kSwitchOnSmiNoFeedback);
    table_start = GetConstantPoolIndexOperand(0);
    table_size = GetUnsignedImmediateOperand(1);
    case_value_base = GetImmediateOperand(2);
  }
  return JumpTableTargetOffsets(this, table_start, table_size, case_value_base);
}

int BytecodeArrayIterator::GetAbsoluteOffset(int relative_offset) const {
  return current_offset() + relative_offset + prefix_size_;
}

std::ostream& BytecodeArrayIterator::PrintCurrentBytecodeTo(
    std::ostream& os) const {
  return BytecodeDecoder::Decode(os, cursor_ - prefix_size_,
                                 bytecode_array_->constant_pool());
}

void BytecodeArrayIterator::UpdatePointers() {
  DisallowGarbageCollection no_gc;
  uint8_t* start =
      reinterpret_cast<uint8_t*>(bytecode_array_->GetFirstBytecodeAddress());
  if (start != start_) {
    start_ = start;
    uint8_t* end = start + bytecode_array_->length();
    size_t distance_to_end = end_ - cursor_;
    cursor_ = end - distance_to_end;
    end_ = end;
  }
}

uint32_t BytecodeArrayIterator::GetEmbeddedFeedback(int operand_index) const {
  DCHECK_GE(operand_index, 0);
  DCHECK_LT(operand_index, Bytecodes::NumberOfOperands(current_bytecode()));
  DCHECK_EQ(OperandType::kFlag16,
            Bytecodes::GetOperandType(current_bytecode(), operand_index));
  Address embedded_feedback_start = reinterpret_cast<Address>(cursor_) +
                                    current_operand_offset(operand_index);
  return BytecodeDecoder::RacyDecodeEmbeddedFeedback(embedded_feedback_start,
                                                     OperandSize::kShort);
}

CompareOperationHint BytecodeArrayIterator::GetEmbeddedCompareOperationHint() {
  DCHECK(Bytecodes::IsCompareWithEmbeddedFeedback(current_bytecode()));
  uint32_t type_feedback = GetEmbeddedFeedback(1);
  return v8::internal::CompareOperationHintFromFeedback(type_feedback);
}

int BytecodeArrayIterator::GetEmbeddedFeedbackOffset(int operand_index) const {
  DCHECK_EQ(Bytecodes::GetOperandType(current_bytecode(), operand_index),
            OperandType::kFlag16);
  return BytecodeArray::kHeaderSize - kHeapObjectTag + current_offset() +
         prefix_size_ + current_operand_offset(operand_index);
}

JumpTableTargetOffsets::JumpTableTargetOffsets(
    const BytecodeArrayIterator* iterator, int table_start, int table_size,
    int case_value_base)
    : iterator_(iterator),
      table_start_(table_start),
      table_size_(table_size),
      case_value_base_(case_value_base) {}

JumpTableTargetOffsets::iterator JumpTableTargetOffsets::begin() const {
  return iterator(case_value_base_, table_start_, table_start_ + table_size_,
                  iterator_);
}
JumpTableTargetOffsets::iterator JumpTableTargetOffsets::end() const {
  return iterator(case_value_base_ + table_size_, table_start_ + table_size_,
                  table_start_ + table_size_, iterator_);
}
int JumpTableTargetOffsets::size() const { return table_size_; }

JumpTableTargetOffsets::iterator::iterator(
    int case_value, int table_offset, int table_end,
    const BytecodeArrayIterator* iterator)
    : iterator_(iterator),
      index_(case_value),
      table_offset_(table_offset),
      table_end_(table_end) {}

JumpTableTargetOffset JumpTableTargetOffsets::iterator::operator*() {
  DCHECK_LT(table_offset_, table_end_);
  return {index_, iterator_->GetAbsoluteOffset(Smi::ToInt(
                      iterator_->GetConstantAtIndexAsSmi(table_offset_)))};
}

JumpTableTargetOffsets::iterator&
JumpTableTargetOffsets::iterator::operator++() {
  DCHECK_LT(table_offset_, table_end_);
  ++table_offset_;
  ++index_;
  return *this;
}

bool JumpTableTargetOffsets::iterator::operator!=(
    const JumpTableTargetOffsets::iterator& other) {
  DCHECK_EQ(iterator_, other.iterator_);
  DCHECK_EQ(table_end_, other.table_end_);
  DCHECK_EQ(index_ - other.index_, table_offset_ - other.table_offset_);
  return index_ != other.index_;
}

}  // namespace interpreter
}  // namespace internal
}  // namespace v8