/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * GC-internal helper functions for getting the AllocKind used to allocate a
 * JSObject and related information.
 */

#ifndef gc_ObjectKind_inl_h
#define gc_ObjectKind_inl_h

#include "util/Memory.h"
#include "vm/NativeObject.h"

namespace js {
namespace gc {

inline constexpr AllocKind slotsToThingKind[] = {
    // clang-format off
    /*  0 */ AllocKind::OBJECT0,  AllocKind::OBJECT2,  AllocKind::OBJECT2,  AllocKind::OBJECT4,
    /*  4 */ AllocKind::OBJECT4,  AllocKind::OBJECT6,  AllocKind::OBJECT6,  AllocKind::OBJECT8,
    /*  8 */ AllocKind::OBJECT8,  AllocKind::OBJECT12, AllocKind::OBJECT12, AllocKind::OBJECT12,
    /* 12 */ AllocKind::OBJECT12, AllocKind::OBJECT16, AllocKind::OBJECT16, AllocKind::OBJECT16,
    /* 16 */ AllocKind::OBJECT16
    // clang-format on
};

extern const uint32_t slotsToAllocKindBytes[];

static constexpr uint32_t MaxGCObjectFixedSlots = std::size(slotsToThingKind);

/* Get the best kind to use when making an object with the given slot count. */
static constexpr AllocKind GetGCObjectKind(size_t numSlots) {
  if (numSlots >= std::size(slotsToThingKind)) {
    return AllocKind::OBJECT16;
  }
  return slotsToThingKind[numSlots];
}

static inline AllocKind GetGCObjectKind(const JSClass* clasp) {
  MOZ_ASSERT(!clasp->isProxyObject(),
             "Proxies should use GetProxyGCObjectKind");
  MOZ_ASSERT(!clasp->isJSFunction());

  uint32_t nslots = JSCLASS_RESERVED_SLOTS(clasp);
  return GetGCObjectKind(nslots);
}

static constexpr bool CanUseFixedElementsForArray(size_t numElements) {
  if (numElements > NativeObject::MAX_DENSE_ELEMENTS_COUNT) {
    return false;
  }
  size_t numSlots = numElements + ObjectElements::VALUES_PER_HEADER;
  return numSlots < std::size(slotsToThingKind);
}

/* As for GetGCObjectKind, but for dense array allocation. */
static constexpr AllocKind GetGCArrayKind(size_t numElements) {
  /*
   * Dense arrays can use their fixed slots to hold their elements array
   * (less two Values worth of ObjectElements header), but if more than the
   * maximum number of fixed slots is needed then the fixed slots will be
   * unused.
   */
  static_assert(ObjectElements::VALUES_PER_HEADER == 2);
  if (!CanUseFixedElementsForArray(numElements)) {
    return AllocKind::OBJECT2;
  }
  return slotsToThingKind[numElements + ObjectElements::VALUES_PER_HEADER];
}

static inline AllocKind GetGCObjectFixedSlotsKind(size_t numFixedSlots) {
  MOZ_ASSERT(numFixedSlots < std::size(slotsToThingKind));
  return slotsToThingKind[numFixedSlots];
}

// Get the best kind to use when allocating an object that needs a specific
// number of bytes.
static inline AllocKind GetGCObjectKindForBytes(size_t nbytes) {
  MOZ_ASSERT(nbytes <= JSObject::MAX_BYTE_SIZE);

  if (nbytes <= sizeof(NativeObject)) {
    return AllocKind::OBJECT0;
  }
  nbytes -= sizeof(NativeObject);

  size_t dataSlots = AlignBytes(nbytes, sizeof(Value)) / sizeof(Value);
  MOZ_ASSERT(nbytes <= dataSlots * sizeof(Value));
  return GetGCObjectKind(dataSlots);
}

/* Get the number of fixed slots and initial capacity associated with a kind. */
static constexpr size_t GetGCKindSlots(AllocKind thingKind) {
  // Using a switch in hopes that thingKind will usually be a compile-time
  // constant.
  switch (thingKind) {
    case AllocKind::OBJECT0:
    case AllocKind::OBJECT0_FOREGROUND:
    case AllocKind::OBJECT0_BACKGROUND:
      return 0;
    case AllocKind::OBJECT2:
    case AllocKind::OBJECT2_FOREGROUND:
    case AllocKind::OBJECT2_BACKGROUND:
      return 2;
    case AllocKind::FUNCTION:
    case AllocKind::OBJECT4:
    case AllocKind::OBJECT4_FOREGROUND:
    case AllocKind::OBJECT4_BACKGROUND:
      return 4;
    case AllocKind::OBJECT6:
    case AllocKind::OBJECT6_FOREGROUND:
    case AllocKind::OBJECT6_BACKGROUND:
      return 6;
    case AllocKind::FUNCTION_EXTENDED:
      return 7;
    case AllocKind::OBJECT8:
    case AllocKind::OBJECT8_FOREGROUND:
    case AllocKind::OBJECT8_BACKGROUND:
      return 8;
    case AllocKind::OBJECT12:
    case AllocKind::OBJECT12_FOREGROUND:
    case AllocKind::OBJECT12_BACKGROUND:
      return 12;
    case AllocKind::OBJECT16:
    case AllocKind::OBJECT16_FOREGROUND:
    case AllocKind::OBJECT16_BACKGROUND:
      return 16;
    default:
      MOZ_CRASH("Bad object alloc kind");
  }
}

static inline size_t GetGCKindBytes(AllocKind thingKind) {
  return sizeof(JSObject_Slots0) + GetGCKindSlots(thingKind) * sizeof(Value);
}

static inline FinalizeKind GetObjectFinalizeKind(const JSClass* clasp) {
  if (!clasp->hasFinalize()) {
    MOZ_ASSERT((clasp->flags & JSCLASS_FOREGROUND_FINALIZE) == 0);
    MOZ_ASSERT((clasp->flags & JSCLASS_BACKGROUND_FINALIZE) == 0);
    return FinalizeKind::None;
  }

  if (clasp->flags & JSCLASS_BACKGROUND_FINALIZE) {
    MOZ_ASSERT((clasp->flags & JSCLASS_FOREGROUND_FINALIZE) == 0);
    return FinalizeKind::Background;
  }

  MOZ_ASSERT(clasp->flags & JSCLASS_FOREGROUND_FINALIZE);
  return FinalizeKind::Foreground;
}

static inline AllocKind GetFinalizedAllocKind(AllocKind kind,
                                              FinalizeKind finalizeKind) {
  MOZ_ASSERT(kind != AllocKind::FUNCTION &&
             kind != AllocKind::FUNCTION_EXTENDED);
  MOZ_ASSERT(IsObjectAllocKind(kind));
  MOZ_ASSERT(!IsFinalizedKind(kind));

  AllocKind newKind = AllocKind(size_t(kind) + size_t(finalizeKind));
  MOZ_ASSERT(IsObjectAllocKind(newKind));
  MOZ_ASSERT(GetGCKindSlots(newKind) == GetGCKindSlots(kind));
  MOZ_ASSERT_IF(finalizeKind == FinalizeKind::None, !IsFinalizedKind(newKind));
  MOZ_ASSERT_IF(finalizeKind == FinalizeKind::Foreground,
                IsForegroundFinalized(newKind));
  MOZ_ASSERT_IF(finalizeKind == FinalizeKind::Background,
                IsBackgroundFinalized(newKind));

  return newKind;
}

static inline AllocKind GetFinalizedAllocKindForClass(AllocKind kind,
                                                      const JSClass* clasp) {
  return GetFinalizedAllocKind(kind, GetObjectFinalizeKind(clasp));
}

}  // namespace gc
}  // namespace js

#endif  // gc_ObjectKind_inl_h
