/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * 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/. */

#ifndef vm_UnboxedObject_inl_h
#define vm_UnboxedObject_inl_h

#include "vm/UnboxedObject.h"

#include "vm/ArrayObject-inl.h"
#include "vm/NativeObject-inl.h"

namespace js {

static inline Value
GetUnboxedValue(uint8_t* p, JSValueType type, bool maybeUninitialized)
{
    switch (type) {
      case JSVAL_TYPE_BOOLEAN:
        return BooleanValue(*p != 0);

      case JSVAL_TYPE_INT32:
        return Int32Value(*reinterpret_cast<int32_t*>(p));

      case JSVAL_TYPE_DOUBLE: {
        // During unboxed plain object creation, non-GC thing properties are
        // left uninitialized. This is normally fine, since the properties will
        // be filled in shortly, but if they are read before that happens we
        // need to make sure that doubles are canonical.
        double d = *reinterpret_cast<double*>(p);
        if (maybeUninitialized)
            return DoubleValue(JS::CanonicalizeNaN(d));
        return DoubleValue(d);
      }

      case JSVAL_TYPE_STRING:
        return StringValue(*reinterpret_cast<JSString**>(p));

      case JSVAL_TYPE_OBJECT:
        return ObjectOrNullValue(*reinterpret_cast<JSObject**>(p));

      default:
        MOZ_CRASH("Invalid type for unboxed value");
    }
}

static inline void
SetUnboxedValueNoTypeChange(JSObject* unboxedObject,
                            uint8_t* p, JSValueType type, const Value& v,
                            bool preBarrier)
{
    switch (type) {
      case JSVAL_TYPE_BOOLEAN:
        *p = v.toBoolean();
        return;

      case JSVAL_TYPE_INT32:
        *reinterpret_cast<int32_t*>(p) = v.toInt32();
        return;

      case JSVAL_TYPE_DOUBLE:
        *reinterpret_cast<double*>(p) = v.toNumber();
        return;

      case JSVAL_TYPE_STRING: {
        MOZ_ASSERT(!IsInsideNursery(v.toString()));
        JSString** np = reinterpret_cast<JSString**>(p);
        if (preBarrier)
            JSString::writeBarrierPre(*np);
        *np = v.toString();
        return;
      }

      case JSVAL_TYPE_OBJECT: {
        JSObject** np = reinterpret_cast<JSObject**>(p);

        // Manually trigger post barriers on the whole object. If we treat
        // the pointer as a HeapPtrObject we will get confused later if the
        // object is converted to its native representation.
        JSObject* obj = v.toObjectOrNull();
        if (IsInsideNursery(obj) && !IsInsideNursery(unboxedObject)) {
            JSRuntime* rt = unboxedObject->runtimeFromMainThread();
            rt->gc.storeBuffer.putWholeCellFromMainThread(unboxedObject);
        }

        if (preBarrier)
            JSObject::writeBarrierPre(*np);
        *np = obj;
        return;
      }

      default:
        MOZ_CRASH("Invalid type for unboxed value");
    }
}

static inline bool
SetUnboxedValue(ExclusiveContext* cx, JSObject* unboxedObject, jsid id,
                uint8_t* p, JSValueType type, const Value& v, bool preBarrier)
{
    switch (type) {
      case JSVAL_TYPE_BOOLEAN:
        if (v.isBoolean()) {
            *p = v.toBoolean();
            return true;
        }
        return false;

      case JSVAL_TYPE_INT32:
        if (v.isInt32()) {
            *reinterpret_cast<int32_t*>(p) = v.toInt32();
            return true;
        }
        return false;

      case JSVAL_TYPE_DOUBLE:
        if (v.isNumber()) {
            *reinterpret_cast<double*>(p) = v.toNumber();
            return true;
        }
        return false;

      case JSVAL_TYPE_STRING:
        if (v.isString()) {
            MOZ_ASSERT(!IsInsideNursery(v.toString()));
            JSString** np = reinterpret_cast<JSString**>(p);
            if (preBarrier)
                JSString::writeBarrierPre(*np);
            *np = v.toString();
            return true;
        }
        return false;

      case JSVAL_TYPE_OBJECT:
        if (v.isObjectOrNull()) {
            JSObject** np = reinterpret_cast<JSObject**>(p);

            // Update property types when writing object properties. Types for
            // other properties were captured when the unboxed layout was
            // created.
            AddTypePropertyId(cx, unboxedObject, id, v);

            // As above, trigger post barriers on the whole object.
            JSObject* obj = v.toObjectOrNull();
            if (IsInsideNursery(v.toObjectOrNull()) && !IsInsideNursery(unboxedObject)) {
                JSRuntime* rt = unboxedObject->runtimeFromMainThread();
                rt->gc.storeBuffer.putWholeCellFromMainThread(unboxedObject);
            }

            if (preBarrier)
                JSObject::writeBarrierPre(*np);
            *np = obj;
            return true;
        }
        return false;

      default:
        MOZ_CRASH("Invalid type for unboxed value");
    }
}

/////////////////////////////////////////////////////////////////////
// UnboxedPlainObject
/////////////////////////////////////////////////////////////////////

inline const UnboxedLayout&
UnboxedPlainObject::layout() const
{
    return group()->unboxedLayout();
}

/////////////////////////////////////////////////////////////////////
// UnboxedArrayObject
/////////////////////////////////////////////////////////////////////

inline const UnboxedLayout&
UnboxedArrayObject::layout() const
{
    return group()->unboxedLayout();
}

inline void
UnboxedArrayObject::setLength(ExclusiveContext* cx, uint32_t length)
{
    if (length > INT32_MAX) {
        // Track objects with overflowing lengths in type information.
        MarkObjectGroupFlags(cx, this, OBJECT_FLAG_LENGTH_OVERFLOW);
    }

    length_ = length;
}

template <JSValueType Type>
inline bool
UnboxedArrayObject::setElementSpecific(ExclusiveContext* cx, size_t index, const Value& v)
{
    MOZ_ASSERT(index < initializedLength());
    MOZ_ASSERT(Type == elementType());
    uint8_t* p = elements() + index * UnboxedTypeSize(Type);
    return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ true);
}

template <JSValueType Type>
inline void
UnboxedArrayObject::setElementNoTypeChangeSpecific(size_t index, const Value& v)
{
    MOZ_ASSERT(index < initializedLength());
    MOZ_ASSERT(Type == elementType());
    uint8_t* p = elements() + index * UnboxedTypeSize(Type);
    return SetUnboxedValueNoTypeChange(this, p, elementType(), v, /* preBarrier = */ true);
}

template <JSValueType Type>
inline bool
UnboxedArrayObject::initElementSpecific(ExclusiveContext* cx, size_t index, const Value& v)
{
    MOZ_ASSERT(index < initializedLength());
    MOZ_ASSERT(Type == elementType());
    uint8_t* p = elements() + index * UnboxedTypeSize(Type);
    return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ false);
}

template <JSValueType Type>
inline void
UnboxedArrayObject::initElementNoTypeChangeSpecific(size_t index, const Value& v)
{
    MOZ_ASSERT(index < initializedLength());
    MOZ_ASSERT(Type == elementType());
    uint8_t* p = elements() + index * UnboxedTypeSize(Type);
    return SetUnboxedValueNoTypeChange(this, p, elementType(), v, /* preBarrier = */ false);
}

template <JSValueType Type>
inline Value
UnboxedArrayObject::getElementSpecific(size_t index)
{
    MOZ_ASSERT(index < initializedLength());
    MOZ_ASSERT(Type == elementType());
    uint8_t* p = elements() + index * UnboxedTypeSize(Type);
    return GetUnboxedValue(p, Type, /* maybeUninitialized = */ false);
}

template <JSValueType Type>
inline void
UnboxedArrayObject::triggerPreBarrier(size_t index)
{
    MOZ_ASSERT(UnboxedTypeNeedsPreBarrier(Type));

    uint8_t* p = elements() + index * UnboxedTypeSize(Type);

    switch (Type) {
      case JSVAL_TYPE_STRING: {
        JSString** np = reinterpret_cast<JSString**>(p);
        JSString::writeBarrierPre(*np);
        break;
      }

      case JSVAL_TYPE_OBJECT: {
        JSObject** np = reinterpret_cast<JSObject**>(p);
        JSObject::writeBarrierPre(*np);
        break;
      }

      default:
        MOZ_CRASH("Bad type");
    }
}

/////////////////////////////////////////////////////////////////////
// Combined methods for NativeObject and UnboxedArrayObject accesses.
/////////////////////////////////////////////////////////////////////

static inline bool
HasAnyBoxedOrUnboxedDenseElements(JSObject* obj)
{
    return obj->isNative() || obj->is<UnboxedArrayObject>();
}

static inline size_t
GetAnyBoxedOrUnboxedInitializedLength(JSObject* obj)
{
    if (obj->isNative())
        return obj->as<NativeObject>().getDenseInitializedLength();
    if (obj->is<UnboxedArrayObject>())
        return obj->as<UnboxedArrayObject>().initializedLength();
    return 0;
}

static inline size_t
GetAnyBoxedOrUnboxedCapacity(JSObject* obj)
{
    if (obj->isNative())
        return obj->as<NativeObject>().getDenseCapacity();
    if (obj->is<UnboxedArrayObject>())
        return obj->as<UnboxedArrayObject>().capacity();
    return 0;
}

static inline Value
GetAnyBoxedOrUnboxedDenseElement(JSObject* obj, size_t index)
{
    if (obj->isNative())
        return obj->as<NativeObject>().getDenseElement(index);
    return obj->as<UnboxedArrayObject>().getElement(index);
}

static inline size_t
GetAnyBoxedOrUnboxedArrayLength(JSObject* obj)
{
    if (obj->is<ArrayObject>())
        return obj->as<ArrayObject>().length();
    return obj->as<UnboxedArrayObject>().length();
}

static inline void
SetAnyBoxedOrUnboxedArrayLength(JSContext* cx, JSObject* obj, size_t length)
{
    if (obj->is<ArrayObject>()) {
        MOZ_ASSERT(length >= obj->as<ArrayObject>().length());
        obj->as<ArrayObject>().setLength(cx, length);
    } else {
        MOZ_ASSERT(length >= obj->as<UnboxedArrayObject>().length());
        obj->as<UnboxedArrayObject>().setLength(cx, length);
    }
}

static inline bool
SetAnyBoxedOrUnboxedDenseElement(JSContext* cx, JSObject* obj, size_t index, const Value& value)
{
    if (obj->isNative()) {
        obj->as<NativeObject>().setDenseElementWithType(cx, index, value);
        return true;
    }
    return obj->as<UnboxedArrayObject>().setElement(cx, index, value);
}

static inline bool
InitAnyBoxedOrUnboxedDenseElement(JSContext* cx, JSObject* obj, size_t index, const Value& value)
{
    if (obj->isNative()) {
        obj->as<NativeObject>().initDenseElementWithType(cx, index, value);
        return true;
    }
    return obj->as<UnboxedArrayObject>().initElement(cx, index, value);
}

/////////////////////////////////////////////////////////////////////
// Template methods for NativeObject and UnboxedArrayObject accesses.
/////////////////////////////////////////////////////////////////////

static inline JSValueType
GetBoxedOrUnboxedType(JSObject* obj)
{
    if (obj->isNative())
        return JSVAL_TYPE_MAGIC;
    return obj->as<UnboxedArrayObject>().elementType();
}

template <JSValueType Type>
static inline bool
HasBoxedOrUnboxedDenseElements(JSObject* obj)
{
    if (Type == JSVAL_TYPE_MAGIC)
        return obj->isNative();
    return obj->is<UnboxedArrayObject>() && obj->as<UnboxedArrayObject>().elementType() == Type;
}

template <JSValueType Type>
static inline size_t
GetBoxedOrUnboxedInitializedLength(JSObject* obj)
{
    if (Type == JSVAL_TYPE_MAGIC)
        return obj->as<NativeObject>().getDenseInitializedLength();
    return obj->as<UnboxedArrayObject>().initializedLength();
}

template <JSValueType Type>
static inline DenseElementResult
SetBoxedOrUnboxedInitializedLength(JSContext* cx, JSObject* obj, size_t initlen)
{
    size_t oldInitlen = GetBoxedOrUnboxedInitializedLength<Type>(obj);
    if (Type == JSVAL_TYPE_MAGIC) {
        obj->as<NativeObject>().setDenseInitializedLength(initlen);
        if (initlen < oldInitlen)
            obj->as<NativeObject>().shrinkElements(cx, initlen);
    } else {
        obj->as<UnboxedArrayObject>().setInitializedLength(initlen);
        if (initlen < oldInitlen)
            obj->as<UnboxedArrayObject>().shrinkElements(cx, initlen);
    }
    return DenseElementResult::Success;
}

template <JSValueType Type>
static inline size_t
GetBoxedOrUnboxedCapacity(JSObject* obj)
{
    if (Type == JSVAL_TYPE_MAGIC)
        return obj->as<NativeObject>().getDenseCapacity();
    return obj->as<UnboxedArrayObject>().capacity();
}

template <JSValueType Type>
static inline Value
GetBoxedOrUnboxedDenseElement(JSObject* obj, size_t index)
{
    if (Type == JSVAL_TYPE_MAGIC)
        return obj->as<NativeObject>().getDenseElement(index);
    return obj->as<UnboxedArrayObject>().getElementSpecific<Type>(index);
}

template <JSValueType Type>
static inline void
SetBoxedOrUnboxedDenseElementNoTypeChange(JSObject* obj, size_t index, const Value& value)
{
    if (Type == JSVAL_TYPE_MAGIC)
        obj->as<NativeObject>().setDenseElement(index, value);
    else
        obj->as<UnboxedArrayObject>().setElementNoTypeChangeSpecific<Type>(index, value);
}

template <JSValueType Type>
static inline bool
SetBoxedOrUnboxedDenseElement(JSContext* cx, JSObject* obj, size_t index, const Value& value)
{
    if (Type == JSVAL_TYPE_MAGIC) {
        obj->as<NativeObject>().setDenseElementWithType(cx, index, value);
        return true;
    }
    return obj->as<UnboxedArrayObject>().setElementSpecific<Type>(cx, index, value);
}

template <JSValueType Type>
static inline DenseElementResult
EnsureBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, size_t count)
{
    if (Type == JSVAL_TYPE_MAGIC) {
        if (!obj->as<ArrayObject>().ensureElements(cx, count))
            return DenseElementResult::Failure;
    } else {
        if (obj->as<UnboxedArrayObject>().capacity() < count) {
            if (!obj->as<UnboxedArrayObject>().growElements(cx, count))
                return DenseElementResult::Failure;
        }
    }
    return DenseElementResult::Success;
}

template <JSValueType Type>
static inline DenseElementResult
SetOrExtendBoxedOrUnboxedDenseElements(ExclusiveContext* cx, JSObject* obj,
                                       uint32_t start, const Value* vp, uint32_t count,
                                       ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update)
{
    if (Type == JSVAL_TYPE_MAGIC) {
        NativeObject* nobj = &obj->as<NativeObject>();

        if (obj->is<ArrayObject>() &&
            !obj->as<ArrayObject>().lengthIsWritable() &&
            start + count >= obj->as<ArrayObject>().length())
        {
            return DenseElementResult::Incomplete;
        }

        DenseElementResult result = nobj->ensureDenseElements(cx, start, count);
        if (result != DenseElementResult::Success)
            return result;

        if (obj->is<ArrayObject>() && start + count >= obj->as<ArrayObject>().length())
            obj->as<ArrayObject>().setLengthInt32(start + count);

        if (updateTypes == ShouldUpdateTypes::DontUpdate && !nobj->shouldConvertDoubleElements()) {
            nobj->copyDenseElements(start, vp, count);
        } else {
            for (size_t i = 0; i < count; i++)
                nobj->setDenseElementWithType(cx, start + i, vp[i]);
        }

        return DenseElementResult::Success;
    }

    UnboxedArrayObject* nobj = &obj->as<UnboxedArrayObject>();

    if (start > nobj->initializedLength())
        return DenseElementResult::Incomplete;

    if (start + count >= UnboxedArrayObject::MaximumCapacity)
        return DenseElementResult::Incomplete;

    if (start + count > nobj->capacity() && !nobj->growElements(cx, start + count))
        return DenseElementResult::Failure;

    size_t oldInitlen = nobj->initializedLength();

    // Overwrite any existing elements covered by the new range. If we fail
    // after this point due to some incompatible type being written to the
    // object's elements, afterwards the contents will be different from when
    // we started. The caller must retry the operation using a generic path,
    // which will overwrite the already-modified elements as well as the ones
    // that were left alone.
    size_t i = 0;
    if (updateTypes == ShouldUpdateTypes::DontUpdate) {
        for (size_t j = start; i < count && j < oldInitlen; i++, j++)
            nobj->setElementNoTypeChangeSpecific<Type>(j, vp[i]);
    } else {
        for (size_t j = start; i < count && j < oldInitlen; i++, j++) {
            if (!nobj->setElementSpecific<Type>(cx, j, vp[i]))
                return DenseElementResult::Incomplete;
        }
    }

    if (i != count) {
        obj->as<UnboxedArrayObject>().setInitializedLength(start + count);
        if (updateTypes == ShouldUpdateTypes::DontUpdate) {
            for (; i < count; i++)
                nobj->initElementNoTypeChangeSpecific<Type>(start + i, vp[i]);
        } else {
            for (; i < count; i++) {
                if (!nobj->initElementSpecific<Type>(cx, start + i, vp[i])) {
                    nobj->setInitializedLength(oldInitlen);
                    return DenseElementResult::Incomplete;
                }
            }
        }
    }

    if (start + count >= nobj->length())
        nobj->setLength(cx, start + count);

    return DenseElementResult::Success;
}

template <JSValueType Type>
static inline DenseElementResult
MoveBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, uint32_t dstStart, uint32_t srcStart,
                                uint32_t length)
{
    MOZ_ASSERT(HasBoxedOrUnboxedDenseElements<Type>(obj));

    if (Type == JSVAL_TYPE_MAGIC) {
        if (!obj->as<NativeObject>().maybeCopyElementsForWrite(cx))
            return DenseElementResult::Failure;
        obj->as<NativeObject>().moveDenseElements(dstStart, srcStart, length);
    } else {
        uint8_t* data = obj->as<UnboxedArrayObject>().elements();
        size_t elementSize = UnboxedTypeSize(Type);

        if (UnboxedTypeNeedsPreBarrier(Type)) {
            // Trigger pre barriers on any elements we are overwriting. See
            // moveDenseElements::moveDenseElements. No post barrier is needed
            // as only whole cell post barriers are used with unboxed objects.
            for (size_t i = 0; i < length; i++)
                obj->as<UnboxedArrayObject>().triggerPreBarrier<Type>(dstStart + i);
        }

        memmove(data + dstStart * elementSize,
                data + srcStart * elementSize,
                length * elementSize);
    }

    return DenseElementResult::Success;
}

template <JSValueType DstType, JSValueType SrcType>
static inline DenseElementResult
CopyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* dst, JSObject* src,
                                uint32_t dstStart, uint32_t srcStart, uint32_t length)
{
    MOZ_ASSERT(HasBoxedOrUnboxedDenseElements<SrcType>(src));
    MOZ_ASSERT(HasBoxedOrUnboxedDenseElements<DstType>(dst));
    MOZ_ASSERT(GetBoxedOrUnboxedInitializedLength<DstType>(dst) == dstStart);
    MOZ_ASSERT(GetBoxedOrUnboxedInitializedLength<DstType>(src) >= srcStart + length);
    MOZ_ASSERT(GetBoxedOrUnboxedCapacity<DstType>(dst) >= dstStart + length);

    SetBoxedOrUnboxedInitializedLength<DstType>(cx, dst, dstStart + length);

    if (DstType == JSVAL_TYPE_MAGIC) {
        if (SrcType == JSVAL_TYPE_MAGIC) {
            const Value* vp = src->as<NativeObject>().getDenseElements() + srcStart;
            dst->as<NativeObject>().initDenseElements(dstStart, vp, length);
        } else {
            for (size_t i = 0; i < length; i++) {
                Value v = GetBoxedOrUnboxedDenseElement<SrcType>(src, srcStart + i);
                dst->as<NativeObject>().initDenseElement(dstStart + i, v);
            }
        }
    } else if (DstType == SrcType) {
        uint8_t* dstData = dst->as<UnboxedArrayObject>().elements();
        uint8_t* srcData = src->as<UnboxedArrayObject>().elements();
        size_t elementSize = UnboxedTypeSize(DstType);

        memcpy(dstData + dstStart * elementSize,
               srcData + srcStart * elementSize,
               length * elementSize);

        // Add a store buffer entry if we might have copied a nursery pointer to dst.
        if (UnboxedTypeNeedsPostBarrier(DstType) && !IsInsideNursery(dst))
            dst->runtimeFromMainThread()->gc.storeBuffer.putWholeCellFromMainThread(dst);
    } else if (DstType == JSVAL_TYPE_DOUBLE && SrcType == JSVAL_TYPE_INT32) {
        uint8_t* dstData = dst->as<UnboxedArrayObject>().elements();
        uint8_t* srcData = src->as<UnboxedArrayObject>().elements();

        for (size_t i = 0; i < length; i++) {
            int32_t v = *reinterpret_cast<int32_t*>(srcData + (srcStart + i) * sizeof(int32_t));
            *reinterpret_cast<double*>(dstData + (dstStart + i) * sizeof(double)) = v;
        }
    } else {
        for (size_t i = 0; i < length; i++) {
            Value v = GetBoxedOrUnboxedDenseElement<SrcType>(src, srcStart + i);
            dst->as<UnboxedArrayObject>().initElementNoTypeChangeSpecific<DstType>(dstStart + i, v);
        }
    }

    return DenseElementResult::Success;
}

/////////////////////////////////////////////////////////////////////
// Dispatch to specialized methods based on the type of an object.
/////////////////////////////////////////////////////////////////////

// Goop to fix MSVC. See DispatchTraceKindTyped in TraceKind.h.
// The clang-cl front end defines _MSC_VER, but still requires the explicit
// template declaration, so we must test for __clang__ here as well.
#if defined(_MSC_VER) && !defined(__clang__)
# define DEPENDENT_TEMPLATE_HINT
#else
# define DEPENDENT_TEMPLATE_HINT template
#endif

// Function to dispatch a method specialized to whatever boxed or unboxed dense
// elements which an input object has.
template <typename F>
DenseElementResult
CallBoxedOrUnboxedSpecialization(F f, JSObject* obj)
{
    if (!HasAnyBoxedOrUnboxedDenseElements(obj))
        return DenseElementResult::Incomplete;
    switch (GetBoxedOrUnboxedType(obj)) {
      case JSVAL_TYPE_MAGIC:
        return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_MAGIC>();
      case JSVAL_TYPE_BOOLEAN:
        return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_BOOLEAN>();
      case JSVAL_TYPE_INT32:
        return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_INT32>();
      case JSVAL_TYPE_DOUBLE:
        return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_DOUBLE>();
      case JSVAL_TYPE_STRING:
        return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_STRING>();
      case JSVAL_TYPE_OBJECT:
        return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_OBJECT>();
      default:
        MOZ_CRASH();
    }
}

// As above, except the specialization can reflect the unboxed type of two objects.
template <typename F>
DenseElementResult
CallBoxedOrUnboxedSpecialization(F f, JSObject* obj1, JSObject* obj2)
{
    if (!HasAnyBoxedOrUnboxedDenseElements(obj1) || !HasAnyBoxedOrUnboxedDenseElements(obj2))
        return DenseElementResult::Incomplete;

#define SPECIALIZE_OBJ2(TYPE)                                                     \
    switch (GetBoxedOrUnboxedType(obj2)) {                                        \
      case JSVAL_TYPE_MAGIC:                                                      \
        return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_MAGIC>();   \
      case JSVAL_TYPE_BOOLEAN:                                                    \
        return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_BOOLEAN>(); \
      case JSVAL_TYPE_INT32:                                                      \
        return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_INT32>();   \
      case JSVAL_TYPE_DOUBLE:                                                     \
        return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_DOUBLE>();  \
      case JSVAL_TYPE_STRING:                                                     \
        return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_STRING>();  \
      case JSVAL_TYPE_OBJECT:                                                     \
        return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_OBJECT>();  \
      default:                                                                    \
        MOZ_CRASH();                                                              \
    }

    switch (GetBoxedOrUnboxedType(obj1)) {
      case JSVAL_TYPE_MAGIC:
        SPECIALIZE_OBJ2(JSVAL_TYPE_MAGIC)
      case JSVAL_TYPE_BOOLEAN:
        SPECIALIZE_OBJ2(JSVAL_TYPE_BOOLEAN)
      case JSVAL_TYPE_INT32:
        SPECIALIZE_OBJ2(JSVAL_TYPE_INT32)
      case JSVAL_TYPE_DOUBLE:
        SPECIALIZE_OBJ2(JSVAL_TYPE_DOUBLE)
      case JSVAL_TYPE_STRING:
        SPECIALIZE_OBJ2(JSVAL_TYPE_STRING)
      case JSVAL_TYPE_OBJECT:
        SPECIALIZE_OBJ2(JSVAL_TYPE_OBJECT)
      default:
        MOZ_CRASH();
    }

#undef SPECIALIZE_OBJ2
}

#undef DEPENDENT_TEMPLATE_HINT

#define DefineBoxedOrUnboxedFunctor1(Signature, A)                      \
struct Signature ## Functor {                                           \
    A a;                                                                \
    explicit Signature ## Functor(A a)                                  \
      : a(a)                                                            \
    {}                                                                  \
    template <JSValueType Type>                                         \
    DenseElementResult operator()() {                                   \
        return Signature<Type>(a);                                      \
    }                                                                   \
}

#define DefineBoxedOrUnboxedFunctor3(Signature, A, B, C)                \
struct Signature ## Functor {                                           \
    A a; B b; C c;                                                      \
    Signature ## Functor(A a, B b, C c)                                 \
      : a(a), b(b), c(c)                                                \
    {}                                                                  \
    template <JSValueType Type>                                         \
    DenseElementResult operator()() {                                   \
        return Signature<Type>(a, b, c);                                \
    }                                                                   \
}

#define DefineBoxedOrUnboxedFunctor4(Signature, A, B, C, D)             \
struct Signature ## Functor {                                           \
    A a; B b; C c; D d;                                                 \
    Signature ## Functor(A a, B b, C c, D d)                            \
      : a(a), b(b), c(c), d(d)                                          \
    {}                                                                  \
    template <JSValueType Type>                                         \
    DenseElementResult operator()() {                                   \
        return Signature<Type>(a, b, c, d);                             \
    }                                                                   \
}

#define DefineBoxedOrUnboxedFunctorPair4(Signature, A, B, C, D)         \
struct Signature ## Functor {                                           \
    A a; B b; C c; D d;                                                 \
    Signature ## Functor(A a, B b, C c, D d)                            \
      : a(a), b(b), c(c), d(d)                                          \
    {}                                                                  \
    template <JSValueType TypeOne, JSValueType TypeTwo>                 \
    DenseElementResult operator()() {                                   \
        return Signature<TypeOne, TypeTwo>(a, b, c, d);                 \
    }                                                                   \
}

#define DefineBoxedOrUnboxedFunctor5(Signature, A, B, C, D, E)          \
struct Signature ## Functor {                                           \
    A a; B b; C c; D d; E e;                                            \
    Signature ## Functor(A a, B b, C c, D d, E e)                       \
      : a(a), b(b), c(c), d(d), e(e)                                    \
    {}                                                                  \
    template <JSValueType Type>                                         \
    DenseElementResult operator()() {                                   \
        return Signature<Type>(a, b, c, d, e);                          \
    }                                                                   \
}

#define DefineBoxedOrUnboxedFunctor6(Signature, A, B, C, D, E, F)       \
struct Signature ## Functor {                                           \
    A a; B b; C c; D d; E e; F f;                                       \
    Signature ## Functor(A a, B b, C c, D d, E e, F f)                  \
      : a(a), b(b), c(c), d(d), e(e), f(f)                              \
    {}                                                                  \
    template <JSValueType Type>                                         \
    DenseElementResult operator()() {                                   \
        return Signature<Type>(a, b, c, d, e, f);                       \
    }                                                                   \
}

#define DefineBoxedOrUnboxedFunctorPair6(Signature, A, B, C, D, E, F)   \
struct Signature ## Functor {                                           \
    A a; B b; C c; D d; E e; F f;                                       \
    Signature ## Functor(A a, B b, C c, D d, E e, F f)                  \
      : a(a), b(b), c(c), d(d), e(e), f(f)                              \
    {}                                                                  \
    template <JSValueType TypeOne, JSValueType TypeTwo>                 \
    DenseElementResult operator()() {                                   \
        return Signature<TypeOne, TypeTwo>(a, b, c, d, e, f);           \
    }                                                                   \
}

DenseElementResult
SetOrExtendAnyBoxedOrUnboxedDenseElements(ExclusiveContext* cx, JSObject* obj,
                                          uint32_t start, const Value* vp, uint32_t count,
                                          ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update);

DenseElementResult
MoveAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj,
                                   uint32_t dstStart, uint32_t srcStart, uint32_t length);

DenseElementResult
CopyAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* dst, JSObject* src,
                                   uint32_t dstStart, uint32_t srcStart, uint32_t length);

void
SetAnyBoxedOrUnboxedInitializedLength(JSContext* cx, JSObject* obj, size_t initlen);

DenseElementResult
EnsureAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, size_t count);

} // namespace js

#endif // vm_UnboxedObject_inl_h
