// Copyright 2012 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/objects/elements-kind.h"

#include "src/base/lazy-instance.h"
#include "src/objects/elements.h"
#include "src/objects/objects-inl.h"
#include "src/objects/objects.h"

namespace v8 {
namespace internal {

int ElementsKindToShiftSize(ElementsKind elements_kind) {
  switch (elements_kind) {
    case UINT8_ELEMENTS:
    case INT8_ELEMENTS:
    case UINT8_CLAMPED_ELEMENTS:
      return 0;
    case UINT16_ELEMENTS:
    case INT16_ELEMENTS:
      return 1;
    case UINT32_ELEMENTS:
    case INT32_ELEMENTS:
    case FLOAT32_ELEMENTS:
      return 2;
    case PACKED_DOUBLE_ELEMENTS:
    case HOLEY_DOUBLE_ELEMENTS:
    case FLOAT64_ELEMENTS:
    case BIGINT64_ELEMENTS:
    case BIGUINT64_ELEMENTS:
      return 3;
    case PACKED_SMI_ELEMENTS:
    case PACKED_ELEMENTS:
    case PACKED_FROZEN_ELEMENTS:
    case PACKED_SEALED_ELEMENTS:
    case PACKED_NONEXTENSIBLE_ELEMENTS:
    case HOLEY_SMI_ELEMENTS:
    case HOLEY_ELEMENTS:
    case HOLEY_FROZEN_ELEMENTS:
    case HOLEY_SEALED_ELEMENTS:
    case HOLEY_NONEXTENSIBLE_ELEMENTS:
    case DICTIONARY_ELEMENTS:
    case FAST_SLOPPY_ARGUMENTS_ELEMENTS:
    case SLOW_SLOPPY_ARGUMENTS_ELEMENTS:
    case FAST_STRING_WRAPPER_ELEMENTS:
    case SLOW_STRING_WRAPPER_ELEMENTS:
      return kTaggedSizeLog2;
    case NO_ELEMENTS:
      UNREACHABLE();
  }
  UNREACHABLE();
}

int ElementsKindToByteSize(ElementsKind elements_kind) {
  return 1 << ElementsKindToShiftSize(elements_kind);
}

int GetDefaultHeaderSizeForElementsKind(ElementsKind elements_kind) {
  STATIC_ASSERT(FixedArray::kHeaderSize == FixedDoubleArray::kHeaderSize);

  if (IsTypedArrayElementsKind(elements_kind)) {
    return 0;
  } else {
    return FixedArray::kHeaderSize - kHeapObjectTag;
  }
}

const char* ElementsKindToString(ElementsKind kind) {
  switch (kind) {
    case PACKED_SMI_ELEMENTS:
      return "PACKED_SMI_ELEMENTS";
    case HOLEY_SMI_ELEMENTS:
      return "HOLEY_SMI_ELEMENTS";
    case PACKED_ELEMENTS:
      return "PACKED_ELEMENTS";
    case HOLEY_ELEMENTS:
      return "HOLEY_ELEMENTS";
    case PACKED_DOUBLE_ELEMENTS:
      return "PACKED_DOUBLE_ELEMENTS";
    case HOLEY_DOUBLE_ELEMENTS:
      return "HOLEY_DOUBLE_ELEMENTS";
    case PACKED_NONEXTENSIBLE_ELEMENTS:
      return "PACKED_NONEXTENSIBLE_ELEMENTS";
    case HOLEY_NONEXTENSIBLE_ELEMENTS:
      return "HOLEY_NONEXTENSIBLE_ELEMENTS";
    case PACKED_SEALED_ELEMENTS:
      return "PACKED_SEALED_ELEMENTS";
    case HOLEY_SEALED_ELEMENTS:
      return "HOLEY_SEALED_ELEMENTS";
    case PACKED_FROZEN_ELEMENTS:
      return "PACKED_FROZEN_ELEMENTS";
    case HOLEY_FROZEN_ELEMENTS:
      return "HOLEY_FROZEN_ELEMENTS";
    case DICTIONARY_ELEMENTS:
      return "DICTIONARY_ELEMENTS";
    case FAST_SLOPPY_ARGUMENTS_ELEMENTS:
      return "FAST_SLOPPY_ARGUMENTS_ELEMENTS";
    case SLOW_SLOPPY_ARGUMENTS_ELEMENTS:
      return "SLOW_SLOPPY_ARGUMENTS_ELEMENTS";
    case FAST_STRING_WRAPPER_ELEMENTS:
      return "FAST_STRING_WRAPPER_ELEMENTS";
    case SLOW_STRING_WRAPPER_ELEMENTS:
      return "SLOW_STRING_WRAPPER_ELEMENTS";

#define PRINT_NAME(Type, type, TYPE, _) \
  case TYPE##_ELEMENTS:                 \
    return #TYPE "ELEMENTS";

      TYPED_ARRAYS(PRINT_NAME);
#undef PRINT_NAME
    case NO_ELEMENTS:
      return "NO_ELEMENTS";
  }
}

const ElementsKind kFastElementsKindSequence[kFastElementsKindCount] = {
    PACKED_SMI_ELEMENTS,     // 0
    HOLEY_SMI_ELEMENTS,      // 1
    PACKED_DOUBLE_ELEMENTS,  // 2
    HOLEY_DOUBLE_ELEMENTS,   // 3
    PACKED_ELEMENTS,         // 4
    HOLEY_ELEMENTS           // 5
};
STATIC_ASSERT(PACKED_SMI_ELEMENTS == FIRST_FAST_ELEMENTS_KIND);
// Verify that kFastElementsKindPackedToHoley is correct.
STATIC_ASSERT(PACKED_SMI_ELEMENTS + kFastElementsKindPackedToHoley ==
              HOLEY_SMI_ELEMENTS);
STATIC_ASSERT(PACKED_DOUBLE_ELEMENTS + kFastElementsKindPackedToHoley ==
              HOLEY_DOUBLE_ELEMENTS);
STATIC_ASSERT(PACKED_ELEMENTS + kFastElementsKindPackedToHoley ==
              HOLEY_ELEMENTS);

ElementsKind GetFastElementsKindFromSequenceIndex(int sequence_number) {
  DCHECK(sequence_number >= 0 && sequence_number < kFastElementsKindCount);
  return kFastElementsKindSequence[sequence_number];
}

int GetSequenceIndexFromFastElementsKind(ElementsKind elements_kind) {
  for (int i = 0; i < kFastElementsKindCount; ++i) {
    if (kFastElementsKindSequence[i] == elements_kind) {
      return i;
    }
  }
  UNREACHABLE();
}

ElementsKind GetNextTransitionElementsKind(ElementsKind kind) {
  int index = GetSequenceIndexFromFastElementsKind(kind);
  return GetFastElementsKindFromSequenceIndex(index + 1);
}

static inline bool IsFastTransitionTarget(ElementsKind elements_kind) {
  return IsFastElementsKind(elements_kind) ||
         elements_kind == DICTIONARY_ELEMENTS;
}

bool IsMoreGeneralElementsKindTransition(ElementsKind from_kind,
                                         ElementsKind to_kind) {
  if (!IsFastElementsKind(from_kind)) return false;
  if (!IsFastTransitionTarget(to_kind)) return false;
  DCHECK(!IsTypedArrayElementsKind(from_kind));
  DCHECK(!IsTypedArrayElementsKind(to_kind));
  switch (from_kind) {
    case PACKED_SMI_ELEMENTS:
      return to_kind != PACKED_SMI_ELEMENTS;
    case HOLEY_SMI_ELEMENTS:
      return to_kind != PACKED_SMI_ELEMENTS && to_kind != HOLEY_SMI_ELEMENTS;
    case PACKED_DOUBLE_ELEMENTS:
      return to_kind != PACKED_SMI_ELEMENTS && to_kind != HOLEY_SMI_ELEMENTS &&
             to_kind != PACKED_DOUBLE_ELEMENTS;
    case HOLEY_DOUBLE_ELEMENTS:
      return to_kind == PACKED_ELEMENTS || to_kind == HOLEY_ELEMENTS;
    case PACKED_ELEMENTS:
      return to_kind == HOLEY_ELEMENTS;
    case HOLEY_ELEMENTS:
      return false;
    default:
      return false;
  }
}

bool UnionElementsKindUptoSize(ElementsKind* a_out, ElementsKind b) {
  // Assert that the union of two ElementKinds can be computed via std::max.
  static_assert(PACKED_SMI_ELEMENTS < HOLEY_SMI_ELEMENTS,
                "ElementsKind union not computable via std::max.");
  static_assert(HOLEY_SMI_ELEMENTS < PACKED_ELEMENTS,
                "ElementsKind union not computable via std::max.");
  static_assert(PACKED_ELEMENTS < HOLEY_ELEMENTS,
                "ElementsKind union not computable via std::max.");
  static_assert(PACKED_DOUBLE_ELEMENTS < HOLEY_DOUBLE_ELEMENTS,
                "ElementsKind union not computable via std::max.");
  ElementsKind a = *a_out;
  switch (a) {
    case PACKED_SMI_ELEMENTS:
      switch (b) {
        case PACKED_SMI_ELEMENTS:
        case HOLEY_SMI_ELEMENTS:
        case PACKED_ELEMENTS:
        case HOLEY_ELEMENTS:
          *a_out = b;
          return true;
        default:
          return false;
      }
    case HOLEY_SMI_ELEMENTS:
      switch (b) {
        case PACKED_SMI_ELEMENTS:
        case HOLEY_SMI_ELEMENTS:
          *a_out = HOLEY_SMI_ELEMENTS;
          return true;
        case PACKED_ELEMENTS:
        case HOLEY_ELEMENTS:
          *a_out = HOLEY_ELEMENTS;
          return true;
        default:
          return false;
      }
    case PACKED_ELEMENTS:
      switch (b) {
        case PACKED_SMI_ELEMENTS:
        case PACKED_ELEMENTS:
          *a_out = PACKED_ELEMENTS;
          return true;
        case HOLEY_SMI_ELEMENTS:
        case HOLEY_ELEMENTS:
          *a_out = HOLEY_ELEMENTS;
          return true;
        default:
          return false;
      }
    case HOLEY_ELEMENTS:
      switch (b) {
        case PACKED_SMI_ELEMENTS:
        case HOLEY_SMI_ELEMENTS:
        case PACKED_ELEMENTS:
        case HOLEY_ELEMENTS:
          *a_out = HOLEY_ELEMENTS;
          return true;
        default:
          return false;
      }
      break;
    case PACKED_DOUBLE_ELEMENTS:
      switch (b) {
        case PACKED_DOUBLE_ELEMENTS:
        case HOLEY_DOUBLE_ELEMENTS:
          *a_out = b;
          return true;
        default:
          return false;
      }
    case HOLEY_DOUBLE_ELEMENTS:
      switch (b) {
        case PACKED_DOUBLE_ELEMENTS:
        case HOLEY_DOUBLE_ELEMENTS:
          *a_out = HOLEY_DOUBLE_ELEMENTS;
          return true;
        default:
          return false;
      }

      break;
    default:
      break;
  }
  return false;
}

}  // namespace internal
}  // namespace v8
