/* -*- 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/. */

#include "builtin/TypedObject.h"

#include "mozilla/Casting.h"
#include "mozilla/CheckedInt.h"

#include "jscompartment.h"
#include "jsfun.h"
#include "jsutil.h"

#include "builtin/SIMD.h"
#include "gc/Marking.h"
#include "js/Vector.h"
#include "vm/GlobalObject.h"
#include "vm/String.h"
#include "vm/StringBuffer.h"
#include "vm/TypedArrayObject.h"

#include "jsatominlines.h"
#include "jsobjinlines.h"

#include "gc/StoreBuffer-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/Shape-inl.h"

using mozilla::AssertedCast;
using mozilla::CheckedInt32;
using mozilla::DebugOnly;
using mozilla::IsPowerOfTwo;
using mozilla::PodCopy;
using mozilla::PointerRangeSize;

using namespace js;

const Class js::TypedObjectModuleObject::class_ = {
    "TypedObject",
    JSCLASS_HAS_RESERVED_SLOTS(SlotCount) |
    JSCLASS_HAS_CACHED_PROTO(JSProto_TypedObject)
};

static const JSFunctionSpec TypedObjectMethods[] = {
    JS_SELF_HOSTED_FN("objectType", "TypeOfTypedObject", 1, 0),
    JS_SELF_HOSTED_FN("storage", "StorageOfTypedObject", 1, 0),
    JS_FS_END
};

static void
ReportCannotConvertTo(JSContext* cx, HandleValue fromValue, const char* toType)
{
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
                              InformalValueTypeName(fromValue), toType);
}

template<class T>
static inline T*
ToObjectIf(HandleValue value)
{
    if (!value.isObject())
        return nullptr;

    if (!value.toObject().is<T>())
        return nullptr;

    return &value.toObject().as<T>();
}

static inline CheckedInt32
RoundUpToAlignment(CheckedInt32 address, uint32_t align)
{
    MOZ_ASSERT(IsPowerOfTwo(align));

    // Note: Be careful to order operators such that we first make the
    // value smaller and then larger, so that we don't get false
    // overflow errors due to (e.g.) adding `align` and then
    // subtracting `1` afterwards when merely adding `align-1` would
    // not have overflowed. Note that due to the nature of two's
    // complement representation, if `address` is already aligned,
    // then adding `align-1` cannot itself cause an overflow.

    return ((address + (align - 1)) / align) * align;
}

/*
 * Overwrites the contents of `typedObj` at offset `offset` with `val`
 * converted to the type `typeObj`. This is done by delegating to
 * self-hosted code. This is used for assignments and initializations.
 *
 * For example, consider the final assignment in this snippet:
 *
 *    var Point = new StructType({x: float32, y: float32});
 *    var Line = new StructType({from: Point, to: Point});
 *    var line = new Line();
 *    line.to = {x: 22, y: 44};
 *
 * This would result in a call to `ConvertAndCopyTo`
 * where:
 * - typeObj = Point
 * - typedObj = line
 * - offset = sizeof(Point) == 8
 * - val = {x: 22, y: 44}
 * This would result in loading the value of `x`, converting
 * it to a float32, and hen storing it at the appropriate offset,
 * and then doing the same for `y`.
 *
 * Note that the type of `typeObj` may not be the
 * type of `typedObj` but rather some subcomponent of `typedObj`.
 */
static bool
ConvertAndCopyTo(JSContext* cx,
                 HandleTypeDescr typeObj,
                 HandleTypedObject typedObj,
                 int32_t offset,
                 HandleAtom name,
                 HandleValue val)
{
    RootedFunction func(cx, SelfHostedFunction(cx, cx->names().ConvertAndCopyTo));
    if (!func)
        return false;

    FixedInvokeArgs<5> args(cx);

    args[0].setObject(*typeObj);
    args[1].setObject(*typedObj);
    args[2].setInt32(offset);
    if (name)
        args[3].setString(name);
    else
        args[3].setNull();
    args[4].set(val);

    RootedValue fval(cx, ObjectValue(*func));
    RootedValue dummy(cx); // ignored by ConvertAndCopyTo
    return js::Call(cx, fval, dummy, args, &dummy);
}

static bool
ConvertAndCopyTo(JSContext* cx, HandleTypedObject typedObj, HandleValue val)
{
    Rooted<TypeDescr*> type(cx, &typedObj->typeDescr());
    return ConvertAndCopyTo(cx, type, typedObj, 0, nullptr, val);
}

/*
 * Overwrites the contents of `typedObj` at offset `offset` with `val`
 * converted to the type `typeObj`
 */
static bool
Reify(JSContext* cx,
      HandleTypeDescr type,
      HandleTypedObject typedObj,
      size_t offset,
      MutableHandleValue to)
{
    RootedFunction func(cx, SelfHostedFunction(cx, cx->names().Reify));
    if (!func)
        return false;

    FixedInvokeArgs<3> args(cx);

    args[0].setObject(*type);
    args[1].setObject(*typedObj);
    args[2].setInt32(offset);

    RootedValue fval(cx, ObjectValue(*func));
    return js::Call(cx, fval, UndefinedHandleValue, args, to);
}

// Extracts the `prototype` property from `obj`, throwing if it is
// missing or not an object.
static JSObject*
GetPrototype(JSContext* cx, HandleObject obj)
{
    RootedValue prototypeVal(cx);
    if (!GetProperty(cx, obj, obj, cx->names().prototype,
                               &prototypeVal))
    {
        return nullptr;
    }
    if (!prototypeVal.isObject()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_PROTOTYPE);
        return nullptr;
    }
    return &prototypeVal.toObject();
}

/***************************************************************************
 * Typed Prototypes
 *
 * Every type descriptor has an associated prototype. Instances of
 * that type descriptor use this as their prototype. Per the spec,
 * typed object prototypes cannot be mutated.
 */

const Class js::TypedProto::class_ = {
    "TypedProto",
    JSCLASS_HAS_RESERVED_SLOTS(JS_TYPROTO_SLOTS)
};

/***************************************************************************
 * Scalar type objects
 *
 * Scalar type objects like `uint8`, `uint16`, are all instances of
 * the ScalarTypeDescr class. Like all type objects, they have a reserved
 * slot pointing to a TypeRepresentation object, which is used to
 * distinguish which scalar type object this actually is.
 */

static const ClassOps ScalarTypeDescrClassOps = {
    nullptr, /* addProperty */
    nullptr, /* delProperty */
    nullptr, /* getProperty */
    nullptr, /* setProperty */
    nullptr, /* enumerate */
    nullptr, /* resolve */
    nullptr, /* mayResolve */
    TypeDescr::finalize,
    ScalarTypeDescr::call
};

const Class js::ScalarTypeDescr::class_ = {
    "Scalar",
    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
    &ScalarTypeDescrClassOps
};

const JSFunctionSpec js::ScalarTypeDescr::typeObjectMethods[] = {
    JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
    JS_SELF_HOSTED_FN("array", "ArrayShorthand", 1, JSFUN_HAS_REST),
    JS_SELF_HOSTED_FN("equivalent", "TypeDescrEquivalent", 1, 0),
    JS_FS_END
};

uint32_t
ScalarTypeDescr::size(Type t)
{
    return AssertedCast<uint32_t>(Scalar::byteSize(t));
}

uint32_t
ScalarTypeDescr::alignment(Type t)
{
    return AssertedCast<uint32_t>(Scalar::byteSize(t));
}

/*static*/ const char*
ScalarTypeDescr::typeName(Type type)
{
    switch (type) {
#define NUMERIC_TYPE_TO_STRING(constant_, type_, name_) \
        case constant_: return #name_;
        JS_FOR_EACH_SCALAR_TYPE_REPR(NUMERIC_TYPE_TO_STRING)
#undef NUMERIC_TYPE_TO_STRING
      case Scalar::Int64:
      case Scalar::Float32x4:
      case Scalar::Int8x16:
      case Scalar::Int16x8:
      case Scalar::Int32x4:
      case Scalar::MaxTypedArrayViewType:
        break;
    }
    MOZ_CRASH("Invalid type");
}

bool
ScalarTypeDescr::call(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    if (args.length() < 1) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
                                  args.callee().getClass()->name, "0", "s");
        return false;
    }

    Rooted<ScalarTypeDescr*> descr(cx, &args.callee().as<ScalarTypeDescr>());
    ScalarTypeDescr::Type type = descr->type();

    double number;
    if (!ToNumber(cx, args[0], &number))
        return false;

    if (type == Scalar::Uint8Clamped)
        number = ClampDoubleToUint8(number);

    switch (type) {
#define SCALARTYPE_CALL(constant_, type_, name_)                             \
      case constant_: {                                                       \
          type_ converted = ConvertScalar<type_>(number);                     \
          args.rval().setNumber((double) converted);                          \
          return true;                                                        \
      }

        JS_FOR_EACH_SCALAR_TYPE_REPR(SCALARTYPE_CALL)
#undef SCALARTYPE_CALL
      case Scalar::Int64:
      case Scalar::Float32x4:
      case Scalar::Int8x16:
      case Scalar::Int16x8:
      case Scalar::Int32x4:
      case Scalar::MaxTypedArrayViewType:
        MOZ_CRASH();
    }
    return true;
}

/***************************************************************************
 * Reference type objects
 *
 * Reference type objects like `Any` or `Object` basically work the
 * same way that the scalar type objects do. There is one class with
 * many instances, and each instance has a reserved slot with a
 * TypeRepresentation object, which is used to distinguish which
 * reference type object this actually is.
 */

static const ClassOps ReferenceTypeDescrClassOps = {
    nullptr, /* addProperty */
    nullptr, /* delProperty */
    nullptr, /* getProperty */
    nullptr, /* setProperty */
    nullptr, /* enumerate */
    nullptr, /* resolve */
    nullptr, /* mayResolve */
    TypeDescr::finalize,
    ReferenceTypeDescr::call
};

const Class js::ReferenceTypeDescr::class_ = {
    "Reference",
    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
    &ReferenceTypeDescrClassOps
};

const JSFunctionSpec js::ReferenceTypeDescr::typeObjectMethods[] = {
    JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
    {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
    {"equivalent", {nullptr, nullptr}, 1, 0, "TypeDescrEquivalent"},
    JS_FS_END
};

static const uint32_t ReferenceSizes[] = {
#define REFERENCE_SIZE(_kind, _type, _name)                        \
    sizeof(_type),
    JS_FOR_EACH_REFERENCE_TYPE_REPR(REFERENCE_SIZE) 0
#undef REFERENCE_SIZE
};

uint32_t
ReferenceTypeDescr::size(Type t)
{
    return ReferenceSizes[t];
}

uint32_t
ReferenceTypeDescr::alignment(Type t)
{
    return ReferenceSizes[t];
}

/*static*/ const char*
ReferenceTypeDescr::typeName(Type type)
{
    switch (type) {
#define NUMERIC_TYPE_TO_STRING(constant_, type_, name_) \
        case constant_: return #name_;
        JS_FOR_EACH_REFERENCE_TYPE_REPR(NUMERIC_TYPE_TO_STRING)
#undef NUMERIC_TYPE_TO_STRING
    }
    MOZ_CRASH("Invalid type");
}

bool
js::ReferenceTypeDescr::call(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    MOZ_ASSERT(args.callee().is<ReferenceTypeDescr>());
    Rooted<ReferenceTypeDescr*> descr(cx, &args.callee().as<ReferenceTypeDescr>());

    if (args.length() < 1) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
                                  descr->typeName(), "0", "s");
        return false;
    }

    switch (descr->type()) {
      case ReferenceTypeDescr::TYPE_ANY:
        args.rval().set(args[0]);
        return true;

      case ReferenceTypeDescr::TYPE_OBJECT:
      {
        RootedObject obj(cx, ToObject(cx, args[0]));
        if (!obj)
            return false;
        args.rval().setObject(*obj);
        return true;
      }

      case ReferenceTypeDescr::TYPE_STRING:
      {
        RootedString obj(cx, ToString<CanGC>(cx, args[0]));
        if (!obj)
            return false;
        args.rval().setString(&*obj);
        return true;
      }
    }

    MOZ_CRASH("Unhandled Reference type");
}

/***************************************************************************
 * SIMD type objects
 *
 * Note: these are partially defined in SIMD.cpp
 */

SimdType
SimdTypeDescr::type() const {
    uint32_t t = uint32_t(getReservedSlot(JS_DESCR_SLOT_TYPE).toInt32());
    MOZ_ASSERT(t < uint32_t(SimdType::Count));
    return SimdType(t);
}

uint32_t
SimdTypeDescr::size(SimdType t)
{
    MOZ_ASSERT(unsigned(t) < unsigned(SimdType::Count));
    switch (t) {
      case SimdType::Int8x16:
      case SimdType::Int16x8:
      case SimdType::Int32x4:
      case SimdType::Uint8x16:
      case SimdType::Uint16x8:
      case SimdType::Uint32x4:
      case SimdType::Float32x4:
      case SimdType::Float64x2:
      case SimdType::Bool8x16:
      case SimdType::Bool16x8:
      case SimdType::Bool32x4:
      case SimdType::Bool64x2:
        return 16;
      case SimdType::Count:
        break;
    }
    MOZ_CRASH("unexpected SIMD type");
}

uint32_t
SimdTypeDescr::alignment(SimdType t)
{
    MOZ_ASSERT(unsigned(t) < unsigned(SimdType::Count));
    return size(t);
}

/***************************************************************************
 * ArrayMetaTypeDescr class
 */

/*
 * For code like:
 *
 *   var A = new TypedObject.ArrayType(uint8, 10);
 *   var S = new TypedObject.StructType({...});
 *
 * As usual, the [[Prototype]] of A is
 * TypedObject.ArrayType.prototype.  This permits adding methods to
 * all ArrayType types, by setting
 * TypedObject.ArrayType.prototype.methodName = function() { ... }.
 * The same holds for S with respect to TypedObject.StructType.
 *
 * We may also want to add methods to *instances* of an ArrayType:
 *
 *   var a = new A();
 *   var s = new S();
 *
 * As usual, the [[Prototype]] of a is A.prototype.  What's
 * A.prototype?  It's an empty object, and you can set
 * A.prototype.methodName = function() { ... } to add a method to all
 * A instances.  (And the same with respect to s and S.)
 *
 * But what if you want to add a method to all ArrayType instances,
 * not just all A instances?  (Or to all StructType instances.)  The
 * [[Prototype]] of the A.prototype empty object is
 * TypedObject.ArrayType.prototype.prototype (two .prototype levels!).
 * So just set TypedObject.ArrayType.prototype.prototype.methodName =
 * function() { ... } to add a method to all ArrayType instances.
 * (And, again, same with respect to s and S.)
 *
 * This function creates the A.prototype/S.prototype object. It returns an
 * empty object with the .prototype.prototype object as its [[Prototype]].
 */
static TypedProto*
CreatePrototypeObjectForComplexTypeInstance(JSContext* cx, HandleObject ctorPrototype)
{
    RootedObject ctorPrototypePrototype(cx, GetPrototype(cx, ctorPrototype));
    if (!ctorPrototypePrototype)
        return nullptr;

    return NewObjectWithGivenProto<TypedProto>(cx, ctorPrototypePrototype, SingletonObject);
}

static const ClassOps ArrayTypeDescrClassOps = {
    nullptr, /* addProperty */
    nullptr, /* delProperty */
    nullptr, /* getProperty */
    nullptr, /* setProperty */
    nullptr, /* enumerate */
    nullptr, /* resolve */
    nullptr, /* mayResolve */
    TypeDescr::finalize,
    nullptr, /* call */
    nullptr, /* hasInstance */
    TypedObject::construct
};

const Class ArrayTypeDescr::class_ = {
    "ArrayType",
    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
    &ArrayTypeDescrClassOps
};

const JSPropertySpec ArrayMetaTypeDescr::typeObjectProperties[] = {
    JS_PS_END
};

const JSFunctionSpec ArrayMetaTypeDescr::typeObjectMethods[] = {
    {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
    JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
    {"equivalent", {nullptr, nullptr}, 1, 0, "TypeDescrEquivalent"},
    JS_SELF_HOSTED_FN("build",    "TypedObjectArrayTypeBuild", 3, 0),
    JS_SELF_HOSTED_FN("from",     "TypedObjectArrayTypeFrom", 3, 0),
    JS_FS_END
};

const JSPropertySpec ArrayMetaTypeDescr::typedObjectProperties[] = {
    JS_PS_END
};

const JSFunctionSpec ArrayMetaTypeDescr::typedObjectMethods[] = {
    {"forEach", {nullptr, nullptr}, 1, 0, "ArrayForEach"},
    {"redimension", {nullptr, nullptr}, 1, 0, "TypedObjectArrayRedimension"},
    JS_SELF_HOSTED_FN("map",        "TypedObjectArrayMap",        2, 0),
    JS_SELF_HOSTED_FN("reduce",     "TypedObjectArrayReduce",     2, 0),
    JS_SELF_HOSTED_FN("filter",     "TypedObjectArrayFilter",     1, 0),
    JS_FS_END
};

bool
js::CreateUserSizeAndAlignmentProperties(JSContext* cx, HandleTypeDescr descr)
{
    // If data is transparent, also store the public slots.
    if (descr->transparent()) {
        // byteLength
        RootedValue typeByteLength(cx, Int32Value(AssertedCast<int32_t>(descr->size())));
        if (!DefineProperty(cx, descr, cx->names().byteLength, typeByteLength,
                            nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
        {
            return false;
        }

        // byteAlignment
        RootedValue typeByteAlignment(cx, Int32Value(descr->alignment()));
        if (!DefineProperty(cx, descr, cx->names().byteAlignment, typeByteAlignment,
                            nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
        {
            return false;
        }
    } else {
        // byteLength
        if (!DefineProperty(cx, descr, cx->names().byteLength, UndefinedHandleValue,
                            nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
        {
            return false;
        }

        // byteAlignment
        if (!DefineProperty(cx, descr, cx->names().byteAlignment, UndefinedHandleValue,
                            nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
        {
            return false;
        }
    }

    return true;
}

static bool
CreateTraceList(JSContext* cx, HandleTypeDescr descr);

ArrayTypeDescr*
ArrayMetaTypeDescr::create(JSContext* cx,
                           HandleObject arrayTypePrototype,
                           HandleTypeDescr elementType,
                           HandleAtom stringRepr,
                           int32_t size,
                           int32_t length)
{
    MOZ_ASSERT(arrayTypePrototype);
    Rooted<ArrayTypeDescr*> obj(cx);
    obj = NewObjectWithGivenProto<ArrayTypeDescr>(cx, arrayTypePrototype, SingletonObject);
    if (!obj)
        return nullptr;

    obj->initReservedSlot(JS_DESCR_SLOT_KIND, Int32Value(ArrayTypeDescr::Kind));
    obj->initReservedSlot(JS_DESCR_SLOT_STRING_REPR, StringValue(stringRepr));
    obj->initReservedSlot(JS_DESCR_SLOT_ALIGNMENT, Int32Value(elementType->alignment()));
    obj->initReservedSlot(JS_DESCR_SLOT_SIZE, Int32Value(size));
    obj->initReservedSlot(JS_DESCR_SLOT_OPAQUE, BooleanValue(elementType->opaque()));
    obj->initReservedSlot(JS_DESCR_SLOT_ARRAY_ELEM_TYPE, ObjectValue(*elementType));
    obj->initReservedSlot(JS_DESCR_SLOT_ARRAY_LENGTH, Int32Value(length));

    RootedValue elementTypeVal(cx, ObjectValue(*elementType));
    if (!DefineProperty(cx, obj, cx->names().elementType, elementTypeVal,
                        nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
    {
        return nullptr;
    }

    RootedValue lengthValue(cx, NumberValue(length));
    if (!DefineProperty(cx, obj, cx->names().length, lengthValue,
                        nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
    {
        return nullptr;
    }

    if (!CreateUserSizeAndAlignmentProperties(cx, obj))
        return nullptr;

    // All arrays with the same element type have the same prototype. This
    // prototype is created lazily and stored in the element type descriptor.
    Rooted<TypedProto*> prototypeObj(cx);
    if (elementType->getReservedSlot(JS_DESCR_SLOT_ARRAYPROTO).isObject()) {
        prototypeObj = &elementType->getReservedSlot(JS_DESCR_SLOT_ARRAYPROTO).toObject().as<TypedProto>();
    } else {
        prototypeObj = CreatePrototypeObjectForComplexTypeInstance(cx, arrayTypePrototype);
        if (!prototypeObj)
            return nullptr;
        elementType->setReservedSlot(JS_DESCR_SLOT_ARRAYPROTO, ObjectValue(*prototypeObj));
    }

    obj->initReservedSlot(JS_DESCR_SLOT_TYPROTO, ObjectValue(*prototypeObj));

    if (!LinkConstructorAndPrototype(cx, obj, prototypeObj))
        return nullptr;

    if (!CreateTraceList(cx, obj))
        return nullptr;

    if (!cx->zone()->typeDescrObjects.put(obj)) {
        ReportOutOfMemory(cx);
        return nullptr;
    }

    return obj;
}

bool
ArrayMetaTypeDescr::construct(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    if (!ThrowIfNotConstructing(cx, args, "ArrayType"))
        return false;

    RootedObject arrayTypeGlobal(cx, &args.callee());

    // Expect two arguments. The first is a type object, the second is a length.
    if (args.length() < 2) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
                                  "ArrayType", "1", "");
        return false;
    }

    if (!args[0].isObject() || !args[0].toObject().is<TypeDescr>()) {
        ReportCannotConvertTo(cx, args[0], "ArrayType element specifier");
        return false;
    }

    if (!args[1].isInt32() || args[1].toInt32() < 0) {
        ReportCannotConvertTo(cx, args[1], "ArrayType length specifier");
        return false;
    }

    Rooted<TypeDescr*> elementType(cx, &args[0].toObject().as<TypeDescr>());

    int32_t length = args[1].toInt32();

    // Compute the byte size.
    CheckedInt32 size = CheckedInt32(elementType->size()) * length;
    if (!size.isValid()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_TOO_BIG);
        return false;
    }

    // Construct a canonical string `new ArrayType(<elementType>, N)`:
    StringBuffer contents(cx);
    if (!contents.append("new ArrayType("))
        return false;
    if (!contents.append(&elementType->stringRepr()))
        return false;
    if (!contents.append(", "))
        return false;
    if (!NumberValueToStringBuffer(cx, NumberValue(length), contents))
        return false;
    if (!contents.append(")"))
        return false;
    RootedAtom stringRepr(cx, contents.finishAtom());
    if (!stringRepr)
        return false;

    // Extract ArrayType.prototype
    RootedObject arrayTypePrototype(cx, GetPrototype(cx, arrayTypeGlobal));
    if (!arrayTypePrototype)
        return false;

    // Create the instance of ArrayType
    Rooted<ArrayTypeDescr*> obj(cx);
    obj = create(cx, arrayTypePrototype, elementType, stringRepr, size.value(), length);
    if (!obj)
        return false;

    args.rval().setObject(*obj);
    return true;
}

bool
js::IsTypedObjectArray(JSObject& obj)
{
    if (!obj.is<TypedObject>())
        return false;
    TypeDescr& d = obj.as<TypedObject>().typeDescr();
    return d.is<ArrayTypeDescr>();
}

/*********************************
 * StructType class
 */

static const ClassOps StructTypeDescrClassOps = {
    nullptr, /* addProperty */
    nullptr, /* delProperty */
    nullptr, /* getProperty */
    nullptr, /* setProperty */
    nullptr, /* enumerate */
    nullptr, /* resolve */
    nullptr, /* mayResolve */
    TypeDescr::finalize,
    nullptr, /* call */
    nullptr, /* hasInstance */
    TypedObject::construct
};

const Class StructTypeDescr::class_ = {
    "StructType",
    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
    &StructTypeDescrClassOps
};

const JSPropertySpec StructMetaTypeDescr::typeObjectProperties[] = {
    JS_PS_END
};

const JSFunctionSpec StructMetaTypeDescr::typeObjectMethods[] = {
    {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
    JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
    {"equivalent", {nullptr, nullptr}, 1, 0, "TypeDescrEquivalent"},
    JS_FS_END
};

const JSPropertySpec StructMetaTypeDescr::typedObjectProperties[] = {
    JS_PS_END
};

const JSFunctionSpec StructMetaTypeDescr::typedObjectMethods[] = {
    JS_FS_END
};

JSObject*
StructMetaTypeDescr::create(JSContext* cx,
                            HandleObject metaTypeDescr,
                            HandleObject fields)
{
    // Obtain names of fields, which are the own properties of `fields`
    AutoIdVector ids(cx);
    if (!GetPropertyKeys(cx, fields, JSITER_OWNONLY | JSITER_SYMBOLS, &ids))
        return nullptr;

    // Iterate through each field. Collect values for the various
    // vectors below and also track total size and alignment. Be wary
    // of overflow!
    StringBuffer stringBuffer(cx);     // Canonical string repr
    AutoValueVector fieldNames(cx);    // Name of each field.
    AutoValueVector fieldTypeObjs(cx); // Type descriptor of each field.
    AutoValueVector fieldOffsets(cx);  // Offset of each field field.
    RootedObject userFieldOffsets(cx); // User-exposed {f:offset} object
    RootedObject userFieldTypes(cx);   // User-exposed {f:descr} object.
    CheckedInt32 sizeSoFar(0);         // Size of struct thus far.
    uint32_t alignment = 1;            // Alignment of struct.
    bool opaque = false;               // Opacity of struct.

    userFieldOffsets = NewBuiltinClassInstance<PlainObject>(cx, TenuredObject);
    if (!userFieldOffsets)
        return nullptr;

    userFieldTypes = NewBuiltinClassInstance<PlainObject>(cx, TenuredObject);
    if (!userFieldTypes)
        return nullptr;

    if (!stringBuffer.append("new StructType({"))
        return nullptr;

    RootedValue fieldTypeVal(cx);
    RootedId id(cx);
    Rooted<TypeDescr*> fieldType(cx);
    for (unsigned int i = 0; i < ids.length(); i++) {
        id = ids[i];

        // Check that all the property names are non-numeric strings.
        uint32_t unused;
        if (!JSID_IS_ATOM(id) || JSID_TO_ATOM(id)->isIndex(&unused)) {
            RootedValue idValue(cx, IdToValue(id));
            ReportCannotConvertTo(cx, idValue, "StructType field name");
            return nullptr;
        }

        // Load the value for the current field from the `fields` object.
        // The value should be a type descriptor.
        if (!GetProperty(cx, fields, fields, id, &fieldTypeVal))
            return nullptr;
        fieldType = ToObjectIf<TypeDescr>(fieldTypeVal);
        if (!fieldType) {
            ReportCannotConvertTo(cx, fieldTypeVal, "StructType field specifier");
            return nullptr;
        }

        // Collect field name and type object
        RootedValue fieldName(cx, IdToValue(id));
        if (!fieldNames.append(fieldName))
            return nullptr;
        if (!fieldTypeObjs.append(ObjectValue(*fieldType)))
            return nullptr;

        // userFieldTypes[id] = typeObj
        if (!DefineProperty(cx, userFieldTypes, id, fieldTypeObjs[i], nullptr, nullptr,
                            JSPROP_READONLY | JSPROP_PERMANENT))
        {
            return nullptr;
        }

        // Append "f:Type" to the string repr
        if (i > 0 && !stringBuffer.append(", "))
            return nullptr;
        if (!stringBuffer.append(JSID_TO_ATOM(id)))
            return nullptr;
        if (!stringBuffer.append(": "))
            return nullptr;
        if (!stringBuffer.append(&fieldType->stringRepr()))
            return nullptr;

        // Offset of this field is the current total size adjusted for
        // the field's alignment.
        CheckedInt32 offset = RoundUpToAlignment(sizeSoFar, fieldType->alignment());
        if (!offset.isValid()) {
            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_TOO_BIG);
            return nullptr;
        }
        MOZ_ASSERT(offset.value() >= 0);
        if (!fieldOffsets.append(Int32Value(offset.value())))
            return nullptr;

        // userFieldOffsets[id] = offset
        RootedValue offsetValue(cx, Int32Value(offset.value()));
        if (!DefineProperty(cx, userFieldOffsets, id, offsetValue, nullptr, nullptr,
                            JSPROP_READONLY | JSPROP_PERMANENT))
        {
            return nullptr;
        }

        // Add space for this field to the total struct size.
        sizeSoFar = offset + fieldType->size();
        if (!sizeSoFar.isValid()) {
            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_TOO_BIG);
            return nullptr;
        }

        // Struct is opaque if any field is opaque
        if (fieldType->opaque())
            opaque = true;

        // Alignment of the struct is the max of the alignment of its fields.
        alignment = js::Max(alignment, fieldType->alignment());
    }

    // Complete string representation.
    if (!stringBuffer.append("})"))
        return nullptr;

    RootedAtom stringRepr(cx, stringBuffer.finishAtom());
    if (!stringRepr)
        return nullptr;

    // Adjust the total size to be a multiple of the final alignment.
    CheckedInt32 totalSize = RoundUpToAlignment(sizeSoFar, alignment);
    if (!totalSize.isValid()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_TOO_BIG);
        return nullptr;
    }

    // Now create the resulting type descriptor.
    RootedObject structTypePrototype(cx, GetPrototype(cx, metaTypeDescr));
    if (!structTypePrototype)
        return nullptr;

    Rooted<StructTypeDescr*> descr(cx);
    descr = NewObjectWithGivenProto<StructTypeDescr>(cx, structTypePrototype, SingletonObject);
    if (!descr)
        return nullptr;

    descr->initReservedSlot(JS_DESCR_SLOT_KIND, Int32Value(type::Struct));
    descr->initReservedSlot(JS_DESCR_SLOT_STRING_REPR, StringValue(stringRepr));
    descr->initReservedSlot(JS_DESCR_SLOT_ALIGNMENT, Int32Value(AssertedCast<int32_t>(alignment)));
    descr->initReservedSlot(JS_DESCR_SLOT_SIZE, Int32Value(totalSize.value()));
    descr->initReservedSlot(JS_DESCR_SLOT_OPAQUE, BooleanValue(opaque));

    // Construct for internal use an array with the name for each field.
    {
        RootedObject fieldNamesVec(cx);
        fieldNamesVec = NewDenseCopiedArray(cx, fieldNames.length(),
                                            fieldNames.begin(), nullptr,
                                            TenuredObject);
        if (!fieldNamesVec)
            return nullptr;
        descr->initReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_NAMES, ObjectValue(*fieldNamesVec));
    }

    // Construct for internal use an array with the type object for each field.
    RootedObject fieldTypeVec(cx);
    fieldTypeVec = NewDenseCopiedArray(cx, fieldTypeObjs.length(),
                                       fieldTypeObjs.begin(), nullptr,
                                       TenuredObject);
    if (!fieldTypeVec)
        return nullptr;
    descr->initReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_TYPES, ObjectValue(*fieldTypeVec));

    // Construct for internal use an array with the offset for each field.
    {
        RootedObject fieldOffsetsVec(cx);
        fieldOffsetsVec = NewDenseCopiedArray(cx, fieldOffsets.length(),
                                              fieldOffsets.begin(), nullptr,
                                              TenuredObject);
        if (!fieldOffsetsVec)
            return nullptr;
        descr->initReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_OFFSETS, ObjectValue(*fieldOffsetsVec));
    }

    // Create data properties fieldOffsets and fieldTypes
    if (!FreezeObject(cx, userFieldOffsets))
        return nullptr;
    if (!FreezeObject(cx, userFieldTypes))
        return nullptr;
    RootedValue userFieldOffsetsValue(cx, ObjectValue(*userFieldOffsets));
    if (!DefineProperty(cx, descr, cx->names().fieldOffsets, userFieldOffsetsValue,
                        nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
    {
        return nullptr;
    }
    RootedValue userFieldTypesValue(cx, ObjectValue(*userFieldTypes));
    if (!DefineProperty(cx, descr, cx->names().fieldTypes, userFieldTypesValue,
                        nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
    {
        return nullptr;
    }

    if (!CreateUserSizeAndAlignmentProperties(cx, descr))
        return nullptr;

    Rooted<TypedProto*> prototypeObj(cx);
    prototypeObj = CreatePrototypeObjectForComplexTypeInstance(cx, structTypePrototype);
    if (!prototypeObj)
        return nullptr;

    descr->initReservedSlot(JS_DESCR_SLOT_TYPROTO, ObjectValue(*prototypeObj));

    if (!LinkConstructorAndPrototype(cx, descr, prototypeObj))
        return nullptr;

    if (!CreateTraceList(cx, descr))
        return nullptr;

    if (!cx->zone()->typeDescrObjects.put(descr) ||
        !cx->zone()->typeDescrObjects.put(fieldTypeVec))
    {
        ReportOutOfMemory(cx);
        return nullptr;
    }

    return descr;
}

bool
StructMetaTypeDescr::construct(JSContext* cx, unsigned int argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    if (!ThrowIfNotConstructing(cx, args, "StructType"))
        return false;

    if (args.length() >= 1 && args[0].isObject()) {
        RootedObject metaTypeDescr(cx, &args.callee());
        RootedObject fields(cx, &args[0].toObject());
        RootedObject obj(cx, create(cx, metaTypeDescr, fields));
        if (!obj)
            return false;
        args.rval().setObject(*obj);
        return true;
    }

    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_STRUCTTYPE_BAD_ARGS);
    return false;
}

size_t
StructTypeDescr::fieldCount() const
{
    return fieldInfoObject(JS_DESCR_SLOT_STRUCT_FIELD_NAMES).getDenseInitializedLength();
}

bool
StructTypeDescr::fieldIndex(jsid id, size_t* out) const
{
    ArrayObject& fieldNames = fieldInfoObject(JS_DESCR_SLOT_STRUCT_FIELD_NAMES);
    size_t l = fieldNames.getDenseInitializedLength();
    for (size_t i = 0; i < l; i++) {
        JSAtom& a = fieldNames.getDenseElement(i).toString()->asAtom();
        if (JSID_IS_ATOM(id, &a)) {
            *out = i;
            return true;
        }
    }
    return false;
}

JSAtom&
StructTypeDescr::fieldName(size_t index) const
{
    return fieldInfoObject(JS_DESCR_SLOT_STRUCT_FIELD_NAMES).getDenseElement(index).toString()->asAtom();
}

size_t
StructTypeDescr::fieldOffset(size_t index) const
{
    ArrayObject& fieldOffsets = fieldInfoObject(JS_DESCR_SLOT_STRUCT_FIELD_OFFSETS);
    MOZ_ASSERT(index < fieldOffsets.getDenseInitializedLength());
    return AssertedCast<size_t>(fieldOffsets.getDenseElement(index).toInt32());
}

TypeDescr&
StructTypeDescr::fieldDescr(size_t index) const
{
    ArrayObject& fieldDescrs = fieldInfoObject(JS_DESCR_SLOT_STRUCT_FIELD_TYPES);
    MOZ_ASSERT(index < fieldDescrs.getDenseInitializedLength());
    return fieldDescrs.getDenseElement(index).toObject().as<TypeDescr>();
}

/******************************************************************************
 * Creating the TypedObject "module"
 *
 * We create one global, `TypedObject`, which contains the following
 * members:
 *
 * 1. uint8, uint16, etc
 * 2. ArrayType
 * 3. StructType
 *
 * Each of these is a function and hence their prototype is
 * `Function.__proto__` (in terms of the JS Engine, they are not
 * JSFunctions but rather instances of their own respective JSClasses
 * which override the call and construct operations).
 *
 * Each type object also has its own `prototype` field. Therefore,
 * using `StructType` as an example, the basic setup is:
 *
 *   StructType --__proto__--> Function.__proto__
 *        |
 *    prototype -- prototype --> { }
 *        |
 *        v
 *       { } -----__proto__--> Function.__proto__
 *
 * When a new type object (e.g., an instance of StructType) is created,
 * it will look as follows:
 *
 *   MyStruct -__proto__-> StructType.prototype -__proto__-> Function.__proto__
 *        |                          |
 *        |                     prototype
 *        |                          |
 *        |                          v
 *    prototype -----__proto__----> { }
 *        |
 *        v
 *       { } --__proto__-> Object.prototype
 *
 * Finally, when an instance of `MyStruct` is created, its
 * structure is as follows:
 *
 *    object -__proto__->
 *      MyStruct.prototype -__proto__->
 *        StructType.prototype.prototype -__proto__->
 *          Object.prototype
 */

// Here `T` is either `ScalarTypeDescr` or `ReferenceTypeDescr`
template<typename T>
static bool
DefineSimpleTypeDescr(JSContext* cx,
                      Handle<GlobalObject*> global,
                      HandleObject module,
                      typename T::Type type,
                      HandlePropertyName className)
{
    RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
    if (!objProto)
        return false;

    RootedObject funcProto(cx, global->getOrCreateFunctionPrototype(cx));
    if (!funcProto)
        return false;

    Rooted<T*> descr(cx);
    descr = NewObjectWithGivenProto<T>(cx, funcProto, SingletonObject);
    if (!descr)
        return false;

    descr->initReservedSlot(JS_DESCR_SLOT_KIND, Int32Value(T::Kind));
    descr->initReservedSlot(JS_DESCR_SLOT_STRING_REPR, StringValue(className));
    descr->initReservedSlot(JS_DESCR_SLOT_ALIGNMENT, Int32Value(T::alignment(type)));
    descr->initReservedSlot(JS_DESCR_SLOT_SIZE, Int32Value(AssertedCast<int32_t>(T::size(type))));
    descr->initReservedSlot(JS_DESCR_SLOT_OPAQUE, BooleanValue(T::Opaque));
    descr->initReservedSlot(JS_DESCR_SLOT_TYPE, Int32Value(type));

    if (!CreateUserSizeAndAlignmentProperties(cx, descr))
        return false;

    if (!JS_DefineFunctions(cx, descr, T::typeObjectMethods))
        return false;

    // Create the typed prototype for the scalar type. This winds up
    // not being user accessible, but we still create one for consistency.
    Rooted<TypedProto*> proto(cx);
    proto = NewObjectWithGivenProto<TypedProto>(cx, objProto, TenuredObject);
    if (!proto)
        return false;
    descr->initReservedSlot(JS_DESCR_SLOT_TYPROTO, ObjectValue(*proto));

    RootedValue descrValue(cx, ObjectValue(*descr));
    if (!DefineProperty(cx, module, className, descrValue, nullptr, nullptr, 0))
        return false;

    if (!CreateTraceList(cx, descr))
        return false;

    if (!cx->zone()->typeDescrObjects.put(descr)) {
        ReportOutOfMemory(cx);
        return false;
    }

    return true;
}

///////////////////////////////////////////////////////////////////////////

template<typename T>
static JSObject*
DefineMetaTypeDescr(JSContext* cx,
                    const char* name,
                    Handle<GlobalObject*> global,
                    Handle<TypedObjectModuleObject*> module,
                    TypedObjectModuleObject::Slot protoSlot)
{
    RootedAtom className(cx, Atomize(cx, name, strlen(name)));
    if (!className)
        return nullptr;

    RootedObject funcProto(cx, global->getOrCreateFunctionPrototype(cx));
    if (!funcProto)
        return nullptr;

    // Create ctor.prototype, which inherits from Function.__proto__

    RootedObject proto(cx, NewObjectWithGivenProto<PlainObject>(cx, funcProto, SingletonObject));
    if (!proto)
        return nullptr;

    // Create ctor.prototype.prototype, which inherits from Object.__proto__

    RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
    if (!objProto)
        return nullptr;
    RootedObject protoProto(cx);
    protoProto = NewObjectWithGivenProto<PlainObject>(cx, objProto, SingletonObject);
    if (!protoProto)
        return nullptr;

    RootedValue protoProtoValue(cx, ObjectValue(*protoProto));
    if (!DefineProperty(cx, proto, cx->names().prototype, protoProtoValue,
                        nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
    {
        return nullptr;
    }

    // Create ctor itself

    const int constructorLength = 2;
    RootedFunction ctor(cx);
    ctor = global->createConstructor(cx, T::construct, className, constructorLength);
    if (!ctor ||
        !LinkConstructorAndPrototype(cx, ctor, proto) ||
        !DefinePropertiesAndFunctions(cx, proto,
                                      T::typeObjectProperties,
                                      T::typeObjectMethods) ||
        !DefinePropertiesAndFunctions(cx, protoProto,
                                      T::typedObjectProperties,
                                      T::typedObjectMethods))
    {
        return nullptr;
    }

    module->initReservedSlot(protoSlot, ObjectValue(*proto));

    return ctor;
}

/*  The initialization strategy for TypedObjects is mildly unusual
 * compared to other classes. Because all of the types are members
 * of a single global, `TypedObject`, we basically make the
 * initializer for the `TypedObject` class populate the
 * `TypedObject` global (which is referred to as "module" herein).
 */
bool
GlobalObject::initTypedObjectModule(JSContext* cx, Handle<GlobalObject*> global)
{
    RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
    if (!objProto)
        return false;

    Rooted<TypedObjectModuleObject*> module(cx);
    module = NewObjectWithGivenProto<TypedObjectModuleObject>(cx, objProto);
    if (!module)
        return false;

    if (!JS_DefineFunctions(cx, module, TypedObjectMethods))
        return false;

    // uint8, uint16, any, etc

#define BINARYDATA_SCALAR_DEFINE(constant_, type_, name_)                       \
    if (!DefineSimpleTypeDescr<ScalarTypeDescr>(cx, global, module, constant_,      \
                                            cx->names().name_))                 \
        return false;
    JS_FOR_EACH_SCALAR_TYPE_REPR(BINARYDATA_SCALAR_DEFINE)
#undef BINARYDATA_SCALAR_DEFINE

#define BINARYDATA_REFERENCE_DEFINE(constant_, type_, name_)                    \
    if (!DefineSimpleTypeDescr<ReferenceTypeDescr>(cx, global, module, constant_,   \
                                               cx->names().name_))              \
        return false;
    JS_FOR_EACH_REFERENCE_TYPE_REPR(BINARYDATA_REFERENCE_DEFINE)
#undef BINARYDATA_REFERENCE_DEFINE

    // ArrayType.

    RootedObject arrayType(cx);
    arrayType = DefineMetaTypeDescr<ArrayMetaTypeDescr>(
        cx, "ArrayType", global, module, TypedObjectModuleObject::ArrayTypePrototype);
    if (!arrayType)
        return false;

    RootedValue arrayTypeValue(cx, ObjectValue(*arrayType));
    if (!DefineProperty(cx, module, cx->names().ArrayType, arrayTypeValue,
                        nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
    {
        return false;
    }

    // StructType.

    RootedObject structType(cx);
    structType = DefineMetaTypeDescr<StructMetaTypeDescr>(
        cx, "StructType", global, module, TypedObjectModuleObject::StructTypePrototype);
    if (!structType)
        return false;

    RootedValue structTypeValue(cx, ObjectValue(*structType));
    if (!DefineProperty(cx, module, cx->names().StructType, structTypeValue,
                        nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
    {
        return false;
    }

    // Everything is setup, install module on the global object:
    RootedValue moduleValue(cx, ObjectValue(*module));
    if (!DefineProperty(cx, global, cx->names().TypedObject, moduleValue, nullptr, nullptr,
                        JSPROP_RESOLVING))
    {
        return false;
    }

    global->setConstructor(JSProto_TypedObject, moduleValue);

    return module;
}

JSObject*
js::InitTypedObjectModuleObject(JSContext* cx, HandleObject obj)
{
    MOZ_ASSERT(obj->is<GlobalObject>());
    Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
    return global->getOrCreateTypedObjectModule(cx);
}

/******************************************************************************
 * Typed objects
 */

uint32_t
TypedObject::offset() const
{
    if (is<InlineTypedObject>())
        return 0;
    return PointerRangeSize(typedMemBase(), typedMem());
}

uint32_t
TypedObject::length() const
{
    return typeDescr().as<ArrayTypeDescr>().length();
}

uint8_t*
TypedObject::typedMem() const
{
    MOZ_ASSERT(isAttached());

    if (is<InlineTypedObject>())
        return as<InlineTypedObject>().inlineTypedMem();
    return as<OutlineTypedObject>().outOfLineTypedMem();
}

uint8_t*
TypedObject::typedMemBase() const
{
    MOZ_ASSERT(isAttached());
    MOZ_ASSERT(is<OutlineTypedObject>());

    JSObject& owner = as<OutlineTypedObject>().owner();
    if (owner.is<ArrayBufferObject>())
        return owner.as<ArrayBufferObject>().dataPointer();
    return owner.as<InlineTypedObject>().inlineTypedMem();
}

bool
TypedObject::isAttached() const
{
    if (is<InlineTransparentTypedObject>()) {
        ObjectWeakMap* table = compartment()->lazyArrayBuffers;
        if (table) {
            JSObject* buffer = table->lookup(this);
            if (buffer)
                return !buffer->as<ArrayBufferObject>().isDetached();
        }
        return true;
    }
    if (is<InlineOpaqueTypedObject>())
        return true;
    if (!as<OutlineTypedObject>().outOfLineTypedMem())
        return false;
    JSObject& owner = as<OutlineTypedObject>().owner();
    if (owner.is<ArrayBufferObject>() && owner.as<ArrayBufferObject>().isDetached())
        return false;
    return true;
}

/* static */ bool
TypedObject::GetBuffer(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    JSObject& obj = args[0].toObject();
    ArrayBufferObject* buffer;
    if (obj.is<OutlineTransparentTypedObject>())
        buffer = obj.as<OutlineTransparentTypedObject>().getOrCreateBuffer(cx);
    else
        buffer = obj.as<InlineTransparentTypedObject>().getOrCreateBuffer(cx);
    if (!buffer)
        return false;
    args.rval().setObject(*buffer);
    return true;
}

/* static */ bool
TypedObject::GetByteOffset(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    args.rval().setInt32(AssertedCast<int32_t>(args[0].toObject().as<TypedObject>().offset()));
    return true;
}

/******************************************************************************
 * Outline typed objects
 */

/*static*/ OutlineTypedObject*
OutlineTypedObject::createUnattached(JSContext* cx,
                                     HandleTypeDescr descr,
                                     int32_t length,
                                     gc::InitialHeap heap)
{
    if (descr->opaque())
        return createUnattachedWithClass(cx, &OutlineOpaqueTypedObject::class_, descr, length, heap);
    else
        return createUnattachedWithClass(cx, &OutlineTransparentTypedObject::class_, descr, length, heap);
}

void
OutlineTypedObject::setOwnerAndData(JSObject* owner, uint8_t* data)
{
    // Make sure we don't associate with array buffers whose data is from an
    // inline typed object, see obj_trace.
    MOZ_ASSERT_IF(owner && owner->is<ArrayBufferObject>(),
                  !owner->as<ArrayBufferObject>().forInlineTypedObject());

    // Typed objects cannot move from one owner to another, so don't worry
    // about pre barriers during this initialization.
    owner_ = owner;
    data_ = data;

    // Trigger a post barrier when attaching an object outside the nursery to
    // one that is inside it.
    if (owner && !IsInsideNursery(this) && IsInsideNursery(owner))
        runtimeFromMainThread()->gc.storeBuffer.putWholeCell(this);
}

/*static*/ OutlineTypedObject*
OutlineTypedObject::createUnattachedWithClass(JSContext* cx,
                                              const Class* clasp,
                                              HandleTypeDescr descr,
                                              int32_t length,
                                              gc::InitialHeap heap)
{
    MOZ_ASSERT(clasp == &OutlineTransparentTypedObject::class_ ||
               clasp == &OutlineOpaqueTypedObject::class_);

    RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, clasp,
                                                             TaggedProto(&descr->typedProto()),
                                                             descr));
    if (!group)
        return nullptr;

    NewObjectKind newKind = (heap == gc::TenuredHeap) ? TenuredObject : GenericObject;
    OutlineTypedObject* obj = NewObjectWithGroup<OutlineTypedObject>(cx, group,
                                                                     gc::AllocKind::OBJECT0,
                                                                     newKind);
    if (!obj)
        return nullptr;

    obj->setOwnerAndData(nullptr, nullptr);
    return obj;
}

void
OutlineTypedObject::attach(JSContext* cx, ArrayBufferObject& buffer, uint32_t offset)
{
    MOZ_ASSERT(!isAttached());
    MOZ_ASSERT(offset <= buffer.byteLength());
    MOZ_ASSERT(size() <= buffer.byteLength() - offset);

    // If the owner's data is from an inline typed object, associate this with
    // the inline typed object instead, to simplify tracing.
    if (buffer.forInlineTypedObject()) {
        InlineTypedObject& realOwner = buffer.firstView()->as<InlineTypedObject>();
        attach(cx, realOwner, offset);
        return;
    }

    buffer.setHasTypedObjectViews();

    {
        AutoEnterOOMUnsafeRegion oomUnsafe;
        if (!buffer.addView(cx, this))
            oomUnsafe.crash("TypedObject::attach");
    }

    setOwnerAndData(&buffer, buffer.dataPointer() + offset);
}

void
OutlineTypedObject::attach(JSContext* cx, TypedObject& typedObj, uint32_t offset)
{
    MOZ_ASSERT(!isAttached());
    MOZ_ASSERT(typedObj.isAttached());

    JSObject* owner = &typedObj;
    if (typedObj.is<OutlineTypedObject>()) {
        owner = &typedObj.as<OutlineTypedObject>().owner();
        MOZ_ASSERT(typedObj.offset() <= UINT32_MAX - offset);
        offset += typedObj.offset();
    }

    if (owner->is<ArrayBufferObject>()) {
        attach(cx, owner->as<ArrayBufferObject>(), offset);
    } else {
        MOZ_ASSERT(owner->is<InlineTypedObject>());
        JS::AutoCheckCannotGC nogc(cx);
        setOwnerAndData(owner, owner->as<InlineTypedObject>().inlineTypedMem(nogc) + offset);
    }
}

// Returns a suitable JS_TYPEDOBJ_SLOT_LENGTH value for an instance of
// the type `type`.
static uint32_t
TypedObjLengthFromType(TypeDescr& descr)
{
    switch (descr.kind()) {
      case type::Scalar:
      case type::Reference:
      case type::Struct:
      case type::Simd:
        return 0;

      case type::Array:
        return descr.as<ArrayTypeDescr>().length();
    }
    MOZ_CRASH("Invalid kind");
}

/*static*/ OutlineTypedObject*
OutlineTypedObject::createDerived(JSContext* cx, HandleTypeDescr type,
                                  HandleTypedObject typedObj, uint32_t offset)
{
    MOZ_ASSERT(offset <= typedObj->size());
    MOZ_ASSERT(offset + type->size() <= typedObj->size());

    int32_t length = TypedObjLengthFromType(*type);

    const js::Class* clasp = typedObj->opaque()
                             ? &OutlineOpaqueTypedObject::class_
                             : &OutlineTransparentTypedObject::class_;
    Rooted<OutlineTypedObject*> obj(cx);
    obj = createUnattachedWithClass(cx, clasp, type, length);
    if (!obj)
        return nullptr;

    obj->attach(cx, *typedObj, offset);
    return obj;
}

/*static*/ TypedObject*
TypedObject::createZeroed(JSContext* cx, HandleTypeDescr descr, int32_t length, gc::InitialHeap heap)
{
    // If possible, create an object with inline data.
    if (descr->size() <= InlineTypedObject::MaximumSize) {
        AutoSetNewObjectMetadata metadata(cx);

        InlineTypedObject* obj = InlineTypedObject::create(cx, descr, heap);
        if (!obj)
            return nullptr;
        JS::AutoCheckCannotGC nogc(cx);
        descr->initInstances(cx->runtime(), obj->inlineTypedMem(nogc), 1);
        return obj;
    }

    // Create unattached wrapper object.
    Rooted<OutlineTypedObject*> obj(cx, OutlineTypedObject::createUnattached(cx, descr, length, heap));
    if (!obj)
        return nullptr;

    // Allocate and initialize the memory for this instance.
    size_t totalSize = descr->size();
    Rooted<ArrayBufferObject*> buffer(cx);
    buffer = ArrayBufferObject::create(cx, totalSize);
    if (!buffer)
        return nullptr;
    descr->initInstances(cx->runtime(), buffer->dataPointer(), 1);
    obj->attach(cx, *buffer, 0);
    return obj;
}

static bool
ReportTypedObjTypeError(JSContext* cx,
                        const unsigned errorNumber,
                        HandleTypedObject obj)
{
    // Serialize type string of obj
    RootedAtom typeReprAtom(cx, &obj->typeDescr().stringRepr());
    UniqueChars typeReprStr(JS_EncodeStringToUTF8(cx, typeReprAtom));
    if (!typeReprStr)
        return false;

    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber, typeReprStr.get());
    return false;
}

/* static */ void
OutlineTypedObject::obj_trace(JSTracer* trc, JSObject* object)
{
    OutlineTypedObject& typedObj = object->as<OutlineTypedObject>();

    TraceEdge(trc, &typedObj.shape_, "OutlineTypedObject_shape");

    if (!typedObj.owner_)
        return;

    TypeDescr& descr = typedObj.typeDescr();

    // Mark the owner, watching in case it is moved by the tracer.
    JSObject* oldOwner = typedObj.owner_;
    TraceManuallyBarrieredEdge(trc, &typedObj.owner_, "typed object owner");
    JSObject* owner = typedObj.owner_;

    uint8_t* oldData = typedObj.outOfLineTypedMem();
    uint8_t* newData = oldData;

    // Update the data pointer if the owner moved and the owner's data is
    // inline with it. Note that an array buffer pointing to data in an inline
    // typed object will never be used as an owner for another outline typed
    // object. In such cases, the owner will be the inline typed object itself.
    MakeAccessibleAfterMovingGC(owner);
    MOZ_ASSERT_IF(owner->is<ArrayBufferObject>(),
                  !owner->as<ArrayBufferObject>().forInlineTypedObject());
    if (owner != oldOwner &&
        (owner->is<InlineTypedObject>() ||
         owner->as<ArrayBufferObject>().hasInlineData()))
    {
        newData += reinterpret_cast<uint8_t*>(owner) - reinterpret_cast<uint8_t*>(oldOwner);
        typedObj.setData(newData);

        trc->runtime()->gc.nursery.maybeSetForwardingPointer(trc, oldData, newData, /* direct = */ false);
    }

    if (!descr.opaque() || !typedObj.isAttached())
        return;

    descr.traceInstances(trc, newData, 1);
}

bool
TypeDescr::hasProperty(const JSAtomState& names, jsid id)
{
    switch (kind()) {
      case type::Scalar:
      case type::Reference:
      case type::Simd:
        return false;

      case type::Array:
      {
        uint32_t index;
        return IdIsIndex(id, &index) || JSID_IS_ATOM(id, names.length);
      }

      case type::Struct:
      {
        size_t index;
        return as<StructTypeDescr>().fieldIndex(id, &index);
      }
    }

    MOZ_CRASH("Unexpected kind");
}

/* static */ bool
TypedObject::obj_lookupProperty(JSContext* cx, HandleObject obj, HandleId id,
                                MutableHandleObject objp, MutableHandleShape propp)
{
    if (obj->as<TypedObject>().typeDescr().hasProperty(cx->names(), id)) {
        MarkNonNativePropertyFound<CanGC>(propp);
        objp.set(obj);
        return true;
    }

    RootedObject proto(cx, obj->staticPrototype());
    if (!proto) {
        objp.set(nullptr);
        propp.set(nullptr);
        return true;
    }

    return LookupProperty(cx, proto, id, objp, propp);
}

static bool
ReportPropertyError(JSContext* cx,
                    const unsigned errorNumber,
                    HandleId id)
{
    RootedValue idVal(cx, IdToValue(id));
    RootedString str(cx, ValueToSource(cx, idVal));
    if (!str)
        return false;

    UniqueChars propName(JS_EncodeStringToUTF8(cx, str));
    if (!propName)
        return false;

    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber, propName.get());
    return false;
}

bool
TypedObject::obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
                                Handle<PropertyDescriptor> desc,
                                ObjectOpResult& result)
{
    Rooted<TypedObject*> typedObj(cx, &obj->as<TypedObject>());
    return ReportTypedObjTypeError(cx, JSMSG_OBJECT_NOT_EXTENSIBLE, typedObj);
}

bool
TypedObject::obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
{
    Rooted<TypedObject*> typedObj(cx, &obj->as<TypedObject>());
    switch (typedObj->typeDescr().kind()) {
      case type::Scalar:
      case type::Reference:
      case type::Simd:
        break;

      case type::Array: {
        if (JSID_IS_ATOM(id, cx->names().length)) {
            *foundp = true;
            return true;
        }
        uint32_t index;
        // Elements are not inherited from the prototype.
        if (IdIsIndex(id, &index)) {
            *foundp = (index < uint32_t(typedObj->length()));
            return true;
        }
        break;
      }

      case type::Struct:
        size_t index;
        if (typedObj->typeDescr().as<StructTypeDescr>().fieldIndex(id, &index)) {
            *foundp = true;
            return true;
        }
    }

    RootedObject proto(cx, obj->staticPrototype());
    if (!proto) {
        *foundp = false;
        return true;
    }

    return HasProperty(cx, proto, id, foundp);
}

bool
TypedObject::obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver,
                             HandleId id, MutableHandleValue vp)
{
    Rooted<TypedObject*> typedObj(cx, &obj->as<TypedObject>());

    // Dispatch elements to obj_getElement:
    uint32_t index;
    if (IdIsIndex(id, &index))
        return obj_getElement(cx, obj, receiver, index, vp);

    // Handle everything else here:

    switch (typedObj->typeDescr().kind()) {
      case type::Scalar:
      case type::Reference:
        break;

      case type::Simd:
        break;

      case type::Array:
        if (JSID_IS_ATOM(id, cx->names().length)) {
            if (!typedObj->isAttached()) {
                JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                          JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED);
                return false;
            }

            vp.setInt32(typedObj->length());
            return true;
        }
        break;

      case type::Struct: {
        Rooted<StructTypeDescr*> descr(cx, &typedObj->typeDescr().as<StructTypeDescr>());

        size_t fieldIndex;
        if (!descr->fieldIndex(id, &fieldIndex))
            break;

        size_t offset = descr->fieldOffset(fieldIndex);
        Rooted<TypeDescr*> fieldType(cx, &descr->fieldDescr(fieldIndex));
        return Reify(cx, fieldType, typedObj, offset, vp);
      }
    }

    RootedObject proto(cx, obj->staticPrototype());
    if (!proto) {
        vp.setUndefined();
        return true;
    }

    return GetProperty(cx, proto, receiver, id, vp);
}

bool
TypedObject::obj_getElement(JSContext* cx, HandleObject obj, HandleValue receiver,
                            uint32_t index, MutableHandleValue vp)
{
    MOZ_ASSERT(obj->is<TypedObject>());
    Rooted<TypedObject*> typedObj(cx, &obj->as<TypedObject>());
    Rooted<TypeDescr*> descr(cx, &typedObj->typeDescr());

    switch (descr->kind()) {
      case type::Scalar:
      case type::Reference:
      case type::Simd:
      case type::Struct:
        break;

      case type::Array:
        return obj_getArrayElement(cx, typedObj, descr, index, vp);
    }

    RootedObject proto(cx, obj->staticPrototype());
    if (!proto) {
        vp.setUndefined();
        return true;
    }

    return GetElement(cx, proto, receiver, index, vp);
}

/*static*/ bool
TypedObject::obj_getArrayElement(JSContext* cx,
                                 Handle<TypedObject*> typedObj,
                                 Handle<TypeDescr*> typeDescr,
                                 uint32_t index,
                                 MutableHandleValue vp)
{
    // Elements are not inherited from the prototype.
    if (index >= (size_t) typedObj->length()) {
        vp.setUndefined();
        return true;
    }

    Rooted<TypeDescr*> elementType(cx, &typeDescr->as<ArrayTypeDescr>().elementType());
    size_t offset = elementType->size() * index;
    return Reify(cx, elementType, typedObj, offset, vp);
}

bool
TypedObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
                             HandleValue receiver, ObjectOpResult& result)
{
    Rooted<TypedObject*> typedObj(cx, &obj->as<TypedObject>());

    switch (typedObj->typeDescr().kind()) {
      case type::Scalar:
      case type::Reference:
        break;

      case type::Simd:
        break;

      case type::Array: {
        if (JSID_IS_ATOM(id, cx->names().length)) {
            if (receiver.isObject() && obj == &receiver.toObject()) {
                JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                          JSMSG_CANT_REDEFINE_ARRAY_LENGTH);
                return false;
            }
            return result.failReadOnly();
        }

        uint32_t index;
        if (IdIsIndex(id, &index)) {
            if (!receiver.isObject() || obj != &receiver.toObject())
                return SetPropertyByDefining(cx, id, v, receiver, result);

            if (index >= uint32_t(typedObj->length())) {
                JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                          JSMSG_TYPEDOBJECT_BINARYARRAY_BAD_INDEX);
                return false;
            }

            Rooted<TypeDescr*> elementType(cx);
            elementType = &typedObj->typeDescr().as<ArrayTypeDescr>().elementType();
            size_t offset = elementType->size() * index;
            if (!ConvertAndCopyTo(cx, elementType, typedObj, offset, nullptr, v))
                return false;
            return result.succeed();
        }
        break;
      }

      case type::Struct: {
        Rooted<StructTypeDescr*> descr(cx, &typedObj->typeDescr().as<StructTypeDescr>());

        size_t fieldIndex;
        if (!descr->fieldIndex(id, &fieldIndex))
            break;

        if (!receiver.isObject() || obj != &receiver.toObject())
            return SetPropertyByDefining(cx, id, v, receiver, result);

        size_t offset = descr->fieldOffset(fieldIndex);
        Rooted<TypeDescr*> fieldType(cx, &descr->fieldDescr(fieldIndex));
        RootedAtom fieldName(cx, &descr->fieldName(fieldIndex));
        if (!ConvertAndCopyTo(cx, fieldType, typedObj, offset, fieldName, v))
            return false;
        return result.succeed();
      }
    }

    return SetPropertyOnProto(cx, obj, id, v, receiver, result);
}

bool
TypedObject::obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
                                          MutableHandle<PropertyDescriptor> desc)
{
    Rooted<TypedObject*> typedObj(cx, &obj->as<TypedObject>());
    if (!typedObj->isAttached()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED);
        return false;
    }

    Rooted<TypeDescr*> descr(cx, &typedObj->typeDescr());
    switch (descr->kind()) {
      case type::Scalar:
      case type::Reference:
      case type::Simd:
        break;

      case type::Array:
      {
        uint32_t index;
        if (IdIsIndex(id, &index)) {
            if (!obj_getArrayElement(cx, typedObj, descr, index, desc.value()))
                return false;
            desc.setAttributes(JSPROP_ENUMERATE | JSPROP_PERMANENT);
            desc.object().set(obj);
            return true;
        }

        if (JSID_IS_ATOM(id, cx->names().length)) {
            desc.value().setInt32(typedObj->length());
            desc.setAttributes(JSPROP_READONLY | JSPROP_PERMANENT);
            desc.object().set(obj);
            return true;
        }
        break;
      }

      case type::Struct:
      {
        Rooted<StructTypeDescr*> descr(cx, &typedObj->typeDescr().as<StructTypeDescr>());

        size_t fieldIndex;
        if (!descr->fieldIndex(id, &fieldIndex))
            break;

        size_t offset = descr->fieldOffset(fieldIndex);
        Rooted<TypeDescr*> fieldType(cx, &descr->fieldDescr(fieldIndex));
        if (!Reify(cx, fieldType, typedObj, offset, desc.value()))
            return false;

        desc.setAttributes(JSPROP_ENUMERATE | JSPROP_PERMANENT);
        desc.object().set(obj);
        return true;
      }
    }

    desc.object().set(nullptr);
    return true;
}

static bool
IsOwnId(JSContext* cx, HandleObject obj, HandleId id)
{
    uint32_t index;
    Rooted<TypedObject*> typedObj(cx, &obj->as<TypedObject>());
    switch (typedObj->typeDescr().kind()) {
      case type::Scalar:
      case type::Reference:
      case type::Simd:
        return false;

      case type::Array:
        return IdIsIndex(id, &index) || JSID_IS_ATOM(id, cx->names().length);

      case type::Struct:
        size_t index;
        if (typedObj->typeDescr().as<StructTypeDescr>().fieldIndex(id, &index))
            return true;
    }

    return false;
}

bool
TypedObject::obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result)
{
    if (IsOwnId(cx, obj, id))
        return ReportPropertyError(cx, JSMSG_CANT_DELETE, id);

    RootedObject proto(cx, obj->staticPrototype());
    if (!proto)
        return result.succeed();

    return DeleteProperty(cx, proto, id, result);
}

bool
TypedObject::obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
                           bool enumerableOnly)
{
    MOZ_ASSERT(obj->is<TypedObject>());
    Rooted<TypedObject*> typedObj(cx, &obj->as<TypedObject>());
    Rooted<TypeDescr*> descr(cx, &typedObj->typeDescr());

    RootedId id(cx);
    switch (descr->kind()) {
      case type::Scalar:
      case type::Reference:
      case type::Simd: {
        // Nothing to enumerate.
        break;
      }

      case type::Array: {
        if (!properties.reserve(typedObj->length()))
            return false;

        for (uint32_t index = 0; index < typedObj->length(); index++) {
            id = INT_TO_JSID(index);
            properties.infallibleAppend(id);
        }
        break;
      }

      case type::Struct: {
        size_t fieldCount = descr->as<StructTypeDescr>().fieldCount();
        if (!properties.reserve(fieldCount))
            return false;

        for (size_t index = 0; index < fieldCount; index++) {
            id = AtomToId(&descr->as<StructTypeDescr>().fieldName(index));
            properties.infallibleAppend(id);
        }
        break;
      }
    }

    return true;
}

void
OutlineTypedObject::notifyBufferDetached(void* newData)
{
    setData(reinterpret_cast<uint8_t*>(newData));
}

/******************************************************************************
 * Inline typed objects
 */

/* static */ InlineTypedObject*
InlineTypedObject::create(JSContext* cx, HandleTypeDescr descr, gc::InitialHeap heap)
{
    gc::AllocKind allocKind = allocKindForTypeDescriptor(descr);

    const Class* clasp = descr->opaque()
                         ? &InlineOpaqueTypedObject::class_
                         : &InlineTransparentTypedObject::class_;

    RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, clasp,
                                                             TaggedProto(&descr->typedProto()),
                                                             descr));
    if (!group)
        return nullptr;

    NewObjectKind newKind = (heap == gc::TenuredHeap) ? TenuredObject : GenericObject;
    return NewObjectWithGroup<InlineTypedObject>(cx, group, allocKind, newKind);
}

/* static */ InlineTypedObject*
InlineTypedObject::createCopy(JSContext* cx, Handle<InlineTypedObject*> templateObject,
                              gc::InitialHeap heap)
{
    AutoSetNewObjectMetadata metadata(cx);

    Rooted<TypeDescr*> descr(cx, &templateObject->typeDescr());
    InlineTypedObject* res = create(cx, descr, heap);
    if (!res)
        return nullptr;

    memcpy(res->inlineTypedMem(), templateObject->inlineTypedMem(), templateObject->size());
    return res;
}

/* static */ void
InlineTypedObject::obj_trace(JSTracer* trc, JSObject* object)
{
    InlineTypedObject& typedObj = object->as<InlineTypedObject>();

    TraceEdge(trc, &typedObj.shape_, "InlineTypedObject_shape");

    // Inline transparent objects do not have references and do not need more
    // tracing. If there is an entry in the compartment's LazyArrayBufferTable,
    // tracing that reference will be taken care of by the table itself.
    if (typedObj.is<InlineTransparentTypedObject>())
        return;

    typedObj.typeDescr().traceInstances(trc, typedObj.inlineTypedMem(), 1);
}

/* static */ void
InlineTypedObject::objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src)
{
    // Inline typed object element arrays can be preserved on the stack by Ion
    // and need forwarding pointers created during a minor GC. We can't do this
    // in the trace hook because we don't have any stale data to determine
    // whether this object moved and where it was moved from.
    TypeDescr& descr = dst->as<InlineTypedObject>().typeDescr();
    if (descr.kind() == type::Array) {
        // The forwarding pointer can be direct as long as there is enough
        // space for it. Other objects might point into the object's buffer,
        // but they will not set any direct forwarding pointers.
        uint8_t* oldData = reinterpret_cast<uint8_t*>(src) + offsetOfDataStart();
        uint8_t* newData = dst->as<InlineTypedObject>().inlineTypedMem();
        trc->runtime()->gc.nursery.maybeSetForwardingPointer(trc, oldData, newData,
                                                             descr.size() >= sizeof(uintptr_t));
    }
}

ArrayBufferObject*
InlineTransparentTypedObject::getOrCreateBuffer(JSContext* cx)
{
    ObjectWeakMap*& table = cx->compartment()->lazyArrayBuffers;
    if (!table) {
        table = cx->new_<ObjectWeakMap>(cx);
        if (!table || !table->init())
            return nullptr;
    }

    JSObject* obj = table->lookup(this);
    if (obj)
        return &obj->as<ArrayBufferObject>();

    ArrayBufferObject::BufferContents contents =
        ArrayBufferObject::BufferContents::createPlain(inlineTypedMem());
    size_t nbytes = typeDescr().size();

    // Prevent GC under ArrayBufferObject::create, which might move this object
    // and its contents.
    gc::AutoSuppressGC suppress(cx);

    ArrayBufferObject* buffer =
        ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::DoesntOwnData);
    if (!buffer)
        return nullptr;

    // The owning object must always be the array buffer's first view. This
    // both prevents the memory from disappearing out from under the buffer
    // (the first view is held strongly by the buffer) and is used by the
    // buffer marking code to detect whether its data pointer needs to be
    // relocated.
    JS_ALWAYS_TRUE(buffer->addView(cx, this));

    buffer->setForInlineTypedObject();
    buffer->setHasTypedObjectViews();

    if (!table->add(cx, this, buffer))
        return nullptr;

    if (IsInsideNursery(this)) {
        // Make sure the buffer is traced by the next generational collection,
        // so that its data pointer is updated after this typed object moves.
        cx->runtime()->gc.storeBuffer.putWholeCell(buffer);
    }

    return buffer;
}

ArrayBufferObject*
OutlineTransparentTypedObject::getOrCreateBuffer(JSContext* cx)
{
    if (owner().is<ArrayBufferObject>())
        return &owner().as<ArrayBufferObject>();
    return owner().as<InlineTransparentTypedObject>().getOrCreateBuffer(cx);
}

/******************************************************************************
 * Typed object classes
 */

const ObjectOps TypedObject::objectOps_ = {
    TypedObject::obj_lookupProperty,
    TypedObject::obj_defineProperty,
    TypedObject::obj_hasProperty,
    TypedObject::obj_getProperty,
    TypedObject::obj_setProperty,
    TypedObject::obj_getOwnPropertyDescriptor,
    TypedObject::obj_deleteProperty,
    nullptr, nullptr, /* watch/unwatch */
    nullptr,   /* getElements */
    TypedObject::obj_enumerate,
    nullptr, /* thisValue */
};

#define DEFINE_TYPEDOBJ_CLASS(Name, Trace, flag)         \
    static const ClassOps Name##ClassOps = {             \
        nullptr,        /* addProperty */                \
        nullptr,        /* delProperty */                \
        nullptr,        /* getProperty */                \
        nullptr,        /* setProperty */                \
        nullptr,        /* enumerate   */                \
        nullptr,        /* resolve     */                \
        nullptr,        /* mayResolve  */                \
        nullptr,        /* finalize    */                \
        nullptr,        /* call        */                \
        nullptr,        /* hasInstance */                \
        nullptr,        /* construct   */                \
        Trace,                                           \
    };                                                   \
    const Class Name::class_ = {                         \
        # Name,                                          \
        Class::NON_NATIVE | flag,                        \
        &Name##ClassOps,                                 \
        JS_NULL_CLASS_SPEC,                              \
        JS_NULL_CLASS_EXT,                               \
        &TypedObject::objectOps_                         \
    }

DEFINE_TYPEDOBJ_CLASS(OutlineTransparentTypedObject, OutlineTypedObject::obj_trace, 0);
DEFINE_TYPEDOBJ_CLASS(OutlineOpaqueTypedObject,      OutlineTypedObject::obj_trace, 0);
DEFINE_TYPEDOBJ_CLASS(InlineTransparentTypedObject,  InlineTypedObject::obj_trace,
                      JSCLASS_DELAY_METADATA_BUILDER);
DEFINE_TYPEDOBJ_CLASS(InlineOpaqueTypedObject,       InlineTypedObject::obj_trace,
                      JSCLASS_DELAY_METADATA_BUILDER);

static int32_t
LengthForType(TypeDescr& descr)
{
    switch (descr.kind()) {
      case type::Scalar:
      case type::Reference:
      case type::Struct:
      case type::Simd:
        return 0;

      case type::Array:
        return descr.as<ArrayTypeDescr>().length();
    }

    MOZ_CRASH("Invalid kind");
}

static bool
CheckOffset(uint32_t offset, uint32_t size, uint32_t alignment, uint32_t bufferLength)
{
    // Offset (plus size) must be fully contained within the buffer.
    if (offset > bufferLength)
        return false;
    if (offset + size < offset)
        return false;
    if (offset + size > bufferLength)
        return false;

    // Offset must be aligned.
    if ((offset % alignment) != 0)
        return false;

    return true;
}

template<typename T, typename U, typename V, typename W>
inline bool CheckOffset(T, U, V, W) = delete;

/*static*/ bool
TypedObject::construct(JSContext* cx, unsigned int argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    MOZ_ASSERT(args.callee().is<TypeDescr>());
    Rooted<TypeDescr*> callee(cx, &args.callee().as<TypeDescr>());

    // Typed object constructors are overloaded in three ways, in order of
    // precedence:
    //
    //   new TypeObj()
    //   new TypeObj(buffer, [offset])
    //   new TypeObj(data)

    // Zero argument constructor:
    if (args.length() == 0) {
        int32_t length = LengthForType(*callee);
        Rooted<TypedObject*> obj(cx, createZeroed(cx, callee, length));
        if (!obj)
            return false;
        args.rval().setObject(*obj);
        return true;
    }

    // Buffer constructor.
    if (args[0].isObject() && args[0].toObject().is<ArrayBufferObject>()) {
        Rooted<ArrayBufferObject*> buffer(cx);
        buffer = &args[0].toObject().as<ArrayBufferObject>();

        if (callee->opaque() || buffer->isDetached()) {
            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS);
            return false;
        }

        uint32_t offset;
        if (args.length() >= 2 && !args[1].isUndefined()) {
            if (!args[1].isInt32() || args[1].toInt32() < 0) {
                JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS);
                return false;
            }

            offset = args[1].toInt32();
        } else {
            offset = 0;
        }

        if (args.length() >= 3 && !args[2].isUndefined()) {
            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS);
            return false;
        }

        if (!CheckOffset(offset, callee->size(), callee->alignment(),
                         buffer->byteLength()))
        {
            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS);
            return false;
        }

        Rooted<OutlineTypedObject*> obj(cx);
        obj = OutlineTypedObject::createUnattached(cx, callee, LengthForType(*callee));
        if (!obj)
            return false;

        obj->attach(cx, *buffer, offset);
        args.rval().setObject(*obj);
        return true;
    }

    // Data constructor.
    if (args[0].isObject()) {
        // Create the typed object.
        int32_t length = LengthForType(*callee);
        Rooted<TypedObject*> obj(cx, createZeroed(cx, callee, length));
        if (!obj)
            return false;

        // Initialize from `arg`.
        if (!ConvertAndCopyTo(cx, obj, args[0]))
            return false;
        args.rval().setObject(*obj);
        return true;
    }

    // Something bogus.
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS);
    return false;
}

/******************************************************************************
 * Intrinsics
 */

bool
js::NewOpaqueTypedObject(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    MOZ_ASSERT(args.length() == 1);
    MOZ_ASSERT(args[0].isObject() && args[0].toObject().is<TypeDescr>());

    Rooted<TypeDescr*> descr(cx, &args[0].toObject().as<TypeDescr>());
    int32_t length = TypedObjLengthFromType(*descr);
    Rooted<OutlineTypedObject*> obj(cx);
    obj = OutlineTypedObject::createUnattachedWithClass(cx, &OutlineOpaqueTypedObject::class_, descr, length);
    if (!obj)
        return false;
    args.rval().setObject(*obj);
    return true;
}

bool
js::NewDerivedTypedObject(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    MOZ_ASSERT(args.length() == 3);
    MOZ_ASSERT(args[0].isObject() && args[0].toObject().is<TypeDescr>());
    MOZ_ASSERT(args[1].isObject() && args[1].toObject().is<TypedObject>());
    MOZ_ASSERT(args[2].isInt32());

    Rooted<TypeDescr*> descr(cx, &args[0].toObject().as<TypeDescr>());
    Rooted<TypedObject*> typedObj(cx, &args[1].toObject().as<TypedObject>());
    uint32_t offset = AssertedCast<uint32_t>(args[2].toInt32());

    Rooted<TypedObject*> obj(cx);
    obj = OutlineTypedObject::createDerived(cx, descr, typedObj, offset);
    if (!obj)
        return false;

    args.rval().setObject(*obj);
    return true;
}

bool
js::AttachTypedObject(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    MOZ_ASSERT(args.length() == 3);
    MOZ_ASSERT(args[2].isInt32());

    OutlineTypedObject& handle = args[0].toObject().as<OutlineTypedObject>();
    TypedObject& target = args[1].toObject().as<TypedObject>();
    MOZ_ASSERT(!handle.isAttached());
    uint32_t offset = AssertedCast<uint32_t>(args[2].toInt32());

    handle.attach(cx, target, offset);

    return true;
}

bool
js::SetTypedObjectOffset(JSContext*, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    MOZ_ASSERT(args.length() == 2);
    MOZ_ASSERT(args[0].isObject() && args[0].toObject().is<TypedObject>());
    MOZ_ASSERT(args[1].isInt32());

    OutlineTypedObject& typedObj = args[0].toObject().as<OutlineTypedObject>();
    int32_t offset = args[1].toInt32();

    MOZ_ASSERT(typedObj.isAttached());
    typedObj.resetOffset(offset);
    args.rval().setUndefined();
    return true;
}

bool
js::ObjectIsTypeDescr(JSContext*, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    MOZ_ASSERT(args.length() == 1);
    MOZ_ASSERT(args[0].isObject());
    args.rval().setBoolean(args[0].toObject().is<TypeDescr>());
    return true;
}

bool
js::ObjectIsTypedObject(JSContext*, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    MOZ_ASSERT(args.length() == 1);
    MOZ_ASSERT(args[0].isObject());
    args.rval().setBoolean(args[0].toObject().is<TypedObject>());
    return true;
}

bool
js::ObjectIsOpaqueTypedObject(JSContext*, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    MOZ_ASSERT(args.length() == 1);
    JSObject& obj = args[0].toObject();
    args.rval().setBoolean(obj.is<TypedObject>() && obj.as<TypedObject>().opaque());
    return true;
}

bool
js::ObjectIsTransparentTypedObject(JSContext*, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    MOZ_ASSERT(args.length() == 1);
    JSObject& obj = args[0].toObject();
    args.rval().setBoolean(obj.is<TypedObject>() && !obj.as<TypedObject>().opaque());
    return true;
}

bool
js::TypeDescrIsSimpleType(JSContext*, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    MOZ_ASSERT(args.length() == 1);
    MOZ_ASSERT(args[0].isObject());
    MOZ_ASSERT(args[0].toObject().is<js::TypeDescr>());
    args.rval().setBoolean(args[0].toObject().is<js::SimpleTypeDescr>());
    return true;
}

bool
js::TypeDescrIsArrayType(JSContext*, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    MOZ_ASSERT(args.length() == 1);
    MOZ_ASSERT(args[0].isObject());
    MOZ_ASSERT(args[0].toObject().is<js::TypeDescr>());
    JSObject& obj = args[0].toObject();
    args.rval().setBoolean(obj.is<js::ArrayTypeDescr>());
    return true;
}

bool
js::TypedObjectIsAttached(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    TypedObject& typedObj = args[0].toObject().as<TypedObject>();
    args.rval().setBoolean(typedObj.isAttached());
    return true;
}

bool
js::TypedObjectTypeDescr(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    TypedObject& typedObj = args[0].toObject().as<TypedObject>();
    args.rval().setObject(typedObj.typeDescr());
    return true;
}

bool
js::ClampToUint8(JSContext*, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    MOZ_ASSERT(args.length() == 1);
    MOZ_ASSERT(args[0].isNumber());
    args.rval().setNumber(ClampDoubleToUint8(args[0].toNumber()));
    return true;
}

bool
js::GetTypedObjectModule(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    Rooted<GlobalObject*> global(cx, cx->global());
    MOZ_ASSERT(global);
    args.rval().setObject(global->getTypedObjectModule());
    return true;
}

bool
js::GetSimdTypeDescr(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    MOZ_ASSERT(args.length() == 1);
    MOZ_ASSERT(args[0].isInt32());
    // One of the JS_SIMDTYPEREPR_* constants / a SimdType enum value.
    // getOrCreateSimdTypeDescr() will do the range check.
    int32_t simdTypeRepr = args[0].toInt32();
    Rooted<GlobalObject*> global(cx, cx->global());
    MOZ_ASSERT(global);
    auto* obj = GlobalObject::getOrCreateSimdTypeDescr(cx, global, SimdType(simdTypeRepr));
    args.rval().setObject(*obj);
    return true;
}

#define JS_STORE_SCALAR_CLASS_IMPL(_constant, T, _name)                         \
bool                                                                            \
js::StoreScalar##T::Func(JSContext* cx, unsigned argc, Value* vp)               \
{                                                                               \
    CallArgs args = CallArgsFromVp(argc, vp);                                   \
    MOZ_ASSERT(args.length() == 3);                                             \
    MOZ_ASSERT(args[0].isObject() && args[0].toObject().is<TypedObject>());     \
    MOZ_ASSERT(args[1].isInt32());                                              \
    MOZ_ASSERT(args[2].isNumber());                                             \
                                                                                \
    TypedObject& typedObj = args[0].toObject().as<TypedObject>();               \
    int32_t offset = args[1].toInt32();                                         \
                                                                                \
    /* Should be guaranteed by the typed objects API: */                        \
    MOZ_ASSERT(offset % MOZ_ALIGNOF(T) == 0);                                   \
                                                                                \
    JS::AutoCheckCannotGC nogc(cx);                                             \
    T* target = reinterpret_cast<T*>(typedObj.typedMem(offset, nogc));          \
    double d = args[2].toNumber();                                              \
    *target = ConvertScalar<T>(d);                                              \
    args.rval().setUndefined();                                                 \
    return true;                                                                \
}

#define JS_STORE_REFERENCE_CLASS_IMPL(_constant, T, _name)                      \
bool                                                                            \
js::StoreReference##_name::Func(JSContext* cx, unsigned argc, Value* vp)        \
{                                                                               \
    CallArgs args = CallArgsFromVp(argc, vp);                                   \
    MOZ_ASSERT(args.length() == 4);                                             \
    MOZ_ASSERT(args[0].isObject() && args[0].toObject().is<TypedObject>());     \
    MOZ_ASSERT(args[1].isInt32());                                              \
    MOZ_ASSERT(args[2].isString() || args[2].isNull());                         \
                                                                                \
    TypedObject& typedObj = args[0].toObject().as<TypedObject>();               \
    int32_t offset = args[1].toInt32();                                         \
                                                                                \
    jsid id = args[2].isString()                                                \
              ? IdToTypeId(AtomToId(&args[2].toString()->asAtom()))             \
              : JSID_VOID;                                                      \
                                                                                \
    /* Should be guaranteed by the typed objects API: */                        \
    MOZ_ASSERT(offset % MOZ_ALIGNOF(T) == 0);                                   \
                                                                                \
    JS::AutoCheckCannotGC nogc(cx);                                             \
    T* target = reinterpret_cast<T*>(typedObj.typedMem(offset, nogc));          \
    if (!store(cx, target, args[3], &typedObj, id))                             \
        return false;                                                           \
    args.rval().setUndefined();                                                 \
    return true;                                                                \
}

#define JS_LOAD_SCALAR_CLASS_IMPL(_constant, T, _name)                                  \
bool                                                                                    \
js::LoadScalar##T::Func(JSContext* cx, unsigned argc, Value* vp)                        \
{                                                                                       \
    CallArgs args = CallArgsFromVp(argc, vp);                                           \
    MOZ_ASSERT(args.length() == 2);                                                     \
    MOZ_ASSERT(args[0].isObject() && args[0].toObject().is<TypedObject>());             \
    MOZ_ASSERT(args[1].isInt32());                                                      \
                                                                                        \
    TypedObject& typedObj = args[0].toObject().as<TypedObject>();                       \
    int32_t offset = args[1].toInt32();                                                 \
                                                                                        \
    /* Should be guaranteed by the typed objects API: */                                \
    MOZ_ASSERT(offset % MOZ_ALIGNOF(T) == 0);                                           \
                                                                                        \
    JS::AutoCheckCannotGC nogc(cx);                                                     \
    T* target = reinterpret_cast<T*>(typedObj.typedMem(offset, nogc));                  \
    args.rval().setNumber((double) *target);                                            \
    return true;                                                                        \
}

#define JS_LOAD_REFERENCE_CLASS_IMPL(_constant, T, _name)                       \
bool                                                                            \
js::LoadReference##_name::Func(JSContext* cx, unsigned argc, Value* vp)         \
{                                                                               \
    CallArgs args = CallArgsFromVp(argc, vp);                                   \
    MOZ_ASSERT(args.length() == 2);                                             \
    MOZ_ASSERT(args[0].isObject() && args[0].toObject().is<TypedObject>());     \
    MOZ_ASSERT(args[1].isInt32());                                              \
                                                                                \
    TypedObject& typedObj = args[0].toObject().as<TypedObject>();               \
    int32_t offset = args[1].toInt32();                                         \
                                                                                \
    /* Should be guaranteed by the typed objects API: */                        \
    MOZ_ASSERT(offset % MOZ_ALIGNOF(T) == 0);                                   \
                                                                                \
    JS::AutoCheckCannotGC nogc(cx);                                             \
    T* target = reinterpret_cast<T*>(typedObj.typedMem(offset, nogc));          \
    load(target, args.rval());                                                  \
    return true;                                                                \
}

// Because the precise syntax for storing values/objects/strings
// differs, we abstract it away using specialized variants of the
// private methods `store()` and `load()`.

bool
StoreReferenceAny::store(JSContext* cx, GCPtrValue* heap, const Value& v,
                         TypedObject* obj, jsid id)
{
    // Undefined values are not included in type inference information for
    // value properties of typed objects, as these properties are always
    // considered to contain undefined.
    if (!v.isUndefined()) {
        if (cx->isJSContext())
            AddTypePropertyId(cx->asJSContext(), obj, id, v);
        else if (!HasTypePropertyId(obj, id, v))
            return false;
    }

    *heap = v;
    return true;
}

bool
StoreReferenceObject::store(JSContext* cx, GCPtrObject* heap, const Value& v,
                            TypedObject* obj, jsid id)
{
    MOZ_ASSERT(v.isObjectOrNull()); // or else Store_object is being misused

    // Null pointers are not included in type inference information for
    // object properties of typed objects, as these properties are always
    // considered to contain null.
    if (v.isObject()) {
        if (cx->isJSContext())
            AddTypePropertyId(cx->asJSContext(), obj, id, v);
        else if (!HasTypePropertyId(obj, id, v))
            return false;
    }

    *heap = v.toObjectOrNull();
    return true;
}

bool
StoreReferencestring::store(JSContext* cx, GCPtrString* heap, const Value& v,
                            TypedObject* obj, jsid id)
{
    MOZ_ASSERT(v.isString()); // or else Store_string is being misused

    // Note: string references are not reflected in type information for the object.
    *heap = v.toString();

    return true;
}

void
LoadReferenceAny::load(GCPtrValue* heap, MutableHandleValue v)
{
    v.set(*heap);
}

void
LoadReferenceObject::load(GCPtrObject* heap, MutableHandleValue v)
{
    if (*heap)
        v.setObject(**heap);
    else
        v.setNull();
}

void
LoadReferencestring::load(GCPtrString* heap, MutableHandleValue v)
{
    v.setString(*heap);
}

// I was using templates for this stuff instead of macros, but ran
// into problems with the Unagi compiler.
JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(JS_STORE_SCALAR_CLASS_IMPL)
JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(JS_LOAD_SCALAR_CLASS_IMPL)
JS_FOR_EACH_REFERENCE_TYPE_REPR(JS_STORE_REFERENCE_CLASS_IMPL)
JS_FOR_EACH_REFERENCE_TYPE_REPR(JS_LOAD_REFERENCE_CLASS_IMPL)

///////////////////////////////////////////////////////////////////////////
// Walking memory

template<typename V>
static void
visitReferences(TypeDescr& descr,
                uint8_t* mem,
                V& visitor)
{
    if (descr.transparent())
        return;

    switch (descr.kind()) {
      case type::Scalar:
      case type::Simd:
        return;

      case type::Reference:
        visitor.visitReference(descr.as<ReferenceTypeDescr>(), mem);
        return;

      case type::Array:
      {
        ArrayTypeDescr& arrayDescr = descr.as<ArrayTypeDescr>();
        TypeDescr& elementDescr = arrayDescr.elementType();
        for (uint32_t i = 0; i < arrayDescr.length(); i++) {
            visitReferences(elementDescr, mem, visitor);
            mem += elementDescr.size();
        }
        return;
      }

      case type::Struct:
      {
        StructTypeDescr& structDescr = descr.as<StructTypeDescr>();
        for (size_t i = 0; i < structDescr.fieldCount(); i++) {
            TypeDescr& descr = structDescr.fieldDescr(i);
            size_t offset = structDescr.fieldOffset(i);
            visitReferences(descr, mem + offset, visitor);
        }
        return;
      }
    }

    MOZ_CRASH("Invalid type repr kind");
}

///////////////////////////////////////////////////////////////////////////
// Initializing instances

namespace {

class MemoryInitVisitor {
    const JSRuntime* rt_;

  public:
    explicit MemoryInitVisitor(const JSRuntime* rt)
      : rt_(rt)
    {}

    void visitReference(ReferenceTypeDescr& descr, uint8_t* mem);
};

} // namespace

void
MemoryInitVisitor::visitReference(ReferenceTypeDescr& descr, uint8_t* mem)
{
    switch (descr.type()) {
      case ReferenceTypeDescr::TYPE_ANY:
      {
        js::GCPtrValue* heapValue = reinterpret_cast<js::GCPtrValue*>(mem);
        heapValue->init(UndefinedValue());
        return;
      }

      case ReferenceTypeDescr::TYPE_OBJECT:
      {
        js::GCPtrObject* objectPtr =
            reinterpret_cast<js::GCPtrObject*>(mem);
        objectPtr->init(nullptr);
        return;
      }

      case ReferenceTypeDescr::TYPE_STRING:
      {
        js::GCPtrString* stringPtr =
            reinterpret_cast<js::GCPtrString*>(mem);
        stringPtr->init(rt_->emptyString);
        return;
      }
    }

    MOZ_CRASH("Invalid kind");
}

void
TypeDescr::initInstances(const JSRuntime* rt, uint8_t* mem, size_t length)
{
    MOZ_ASSERT(length >= 1);

    MemoryInitVisitor visitor(rt);

    // Initialize the 0th instance
    memset(mem, 0, size());
    if (opaque())
        visitReferences(*this, mem, visitor);

    // Stamp out N copies of later instances
    uint8_t* target = mem;
    for (size_t i = 1; i < length; i++) {
        target += size();
        memcpy(target, mem, size());
    }
}

///////////////////////////////////////////////////////////////////////////
// Tracing instances

namespace {

class MemoryTracingVisitor {
    JSTracer* trace_;

  public:

    explicit MemoryTracingVisitor(JSTracer* trace)
      : trace_(trace)
    {}

    void visitReference(ReferenceTypeDescr& descr, uint8_t* mem);
};

} // namespace

void
MemoryTracingVisitor::visitReference(ReferenceTypeDescr& descr, uint8_t* mem)
{
    switch (descr.type()) {
      case ReferenceTypeDescr::TYPE_ANY:
      {
        GCPtrValue* heapValue = reinterpret_cast<js::GCPtrValue*>(mem);
        TraceEdge(trace_, heapValue, "reference-val");
        return;
      }

      case ReferenceTypeDescr::TYPE_OBJECT:
      {
        GCPtrObject* objectPtr = reinterpret_cast<js::GCPtrObject*>(mem);
        TraceNullableEdge(trace_, objectPtr, "reference-obj");
        return;
      }

      case ReferenceTypeDescr::TYPE_STRING:
      {
        GCPtrString* stringPtr = reinterpret_cast<js::GCPtrString*>(mem);
        TraceNullableEdge(trace_, stringPtr, "reference-str");
        return;
      }
    }

    MOZ_CRASH("Invalid kind");
}

void
TypeDescr::traceInstances(JSTracer* trace, uint8_t* mem, size_t length)
{
    MemoryTracingVisitor visitor(trace);

    for (size_t i = 0; i < length; i++) {
        visitReferences(*this, mem, visitor);
        mem += size();
    }
}

namespace {

struct TraceListVisitor {
    typedef Vector<int32_t, 0, SystemAllocPolicy> VectorType;
    VectorType stringOffsets, objectOffsets, valueOffsets;

    void visitReference(ReferenceTypeDescr& descr, uint8_t* mem);

    bool fillList(Vector<int32_t>& entries);
};

} // namespace

void
TraceListVisitor::visitReference(ReferenceTypeDescr& descr, uint8_t* mem)
{
    VectorType* offsets;
    switch (descr.type()) {
      case ReferenceTypeDescr::TYPE_ANY: offsets = &valueOffsets; break;
      case ReferenceTypeDescr::TYPE_OBJECT: offsets = &objectOffsets; break;
      case ReferenceTypeDescr::TYPE_STRING: offsets = &stringOffsets; break;
      default: MOZ_CRASH("Invalid kind");
    }

    AutoEnterOOMUnsafeRegion oomUnsafe;
    if (!offsets->append((uintptr_t) mem))
        oomUnsafe.crash("TraceListVisitor::visitReference");
}

bool
TraceListVisitor::fillList(Vector<int32_t>& entries)
{
    return entries.appendAll(stringOffsets) &&
           entries.append(-1) &&
           entries.appendAll(objectOffsets) &&
           entries.append(-1) &&
           entries.appendAll(valueOffsets) &&
           entries.append(-1);
}

static bool
CreateTraceList(JSContext* cx, HandleTypeDescr descr)
{
    // Trace lists are only used for inline typed objects. We don't use them
    // for larger objects, both to limit the size of the trace lists and
    // because tracing outline typed objects is considerably more complicated
    // than inline ones.
    if (descr->size() > InlineTypedObject::MaximumSize || descr->transparent())
        return true;

    TraceListVisitor visitor;
    visitReferences(*descr, nullptr, visitor);

    Vector<int32_t> entries(cx);
    if (!visitor.fillList(entries))
        return false;

    // Trace lists aren't necessary for descriptors with no references.
    MOZ_ASSERT(entries.length() >= 3);
    if (entries.length() == 3)
        return true;

    int32_t* list = cx->pod_malloc<int32_t>(entries.length());
    if (!list)
        return false;

    PodCopy(list, entries.begin(), entries.length());

    descr->initReservedSlot(JS_DESCR_SLOT_TRACE_LIST, PrivateValue(list));
    return true;
}

/* static */ void
TypeDescr::finalize(FreeOp* fop, JSObject* obj)
{
    TypeDescr& descr = obj->as<TypeDescr>();
    if (descr.hasTraceList())
        js_free(const_cast<int32_t*>(descr.traceList()));
}
