/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef builtin_TypedObject_h
#define builtin_TypedObject_h

#include "jsobj.h"
#include "jsweakmap.h"

#include "builtin/TypedObjectConstants.h"
#include "js/Conversions.h"
#include "vm/ArrayBufferObject.h"
#include "vm/ShapedObject.h"

/*
 * -------------
 * Typed Objects
 * -------------
 *
 * Typed objects are a special kind of JS object where the data is
 * given well-structured form. To use a typed object, users first
 * create *type objects* (no relation to the type objects used in TI)
 * that define the type layout. For example, a statement like:
 *
 *    var PointType = new StructType({x: uint8, y: uint8});
 *
 * would create a type object PointType that is a struct with
 * two fields, each of uint8 type.
 *
 * This comment typically assumes familiary with the API.  For more
 * info on the API itself, see the Harmony wiki page at
 * http://wiki.ecmascript.org/doku.php?id=harmony:typed_objects or the
 * ES6 spec (not finalized at the time of this writing).
 *
 * - Initialization:
 *
 * Currently, all "globals" related to typed objects are packaged
 * within a single "module" object `TypedObject`. This module has its
 * own js::Class and when that class is initialized, we also create
 * and define all other values (in `js::InitTypedObjectModuleClass()`).
 *
 * - Type objects, meta type objects, and type representations:
 *
 * There are a number of pre-defined type objects, one for each
 * scalar type (`uint8` etc). Each of these has its own class_,
 * defined in `DefineNumericClass()`.
 *
 * There are also meta type objects (`ArrayType`, `StructType`).
 * These constructors are not themselves type objects but rather the
 * means for the *user* to construct new typed objects.
 *
 * Each type object is associated with a *type representation* (see
 * TypeRepresentation.h). Type representations are canonical versions
 * of type objects. We attach them to TI type objects and (eventually)
 * use them for shape guards etc. They are purely internal to the
 * engine and are not exposed to end users (though self-hosted code
 * sometimes accesses them).
 *
 * - Typed objects:
 *
 * A typed object is an instance of a *type object* (note the past participle).
 * Typed objects can be either transparent or opaque, depending on whether
 * their underlying buffer can be accessed. Transparent and opaque typed
 * objects have different classes, and can have different physical layouts.
 * The following layouts are possible:
 *
 * InlineTypedObject: Typed objects whose data immediately follows the object's
 *   header are inline typed objects. The buffer for these objects is created
 *   lazily and stored via the compartment's LazyArrayBufferTable, and points
 *   back into the object's internal data.
 *
 * OutlineTypedObject: Typed objects whose data is owned by another object,
 *   which can be either an array buffer or an inline typed object. Outline
 *   typed objects may be attached or unattached. An unattached typed object
 *   has no data associated with it. When first created, objects are always
 *   attached, but they can become unattached if their buffer becomes detached.
 *
 * Note that whether a typed object is opaque is not directly
 * connected to its type. That is, opaque types are *always*
 * represented by opaque typed objects, but you may have opaque typed
 * objects for transparent types too. This can occur for two reasons:
 * (1) a transparent type may be embedded within an opaque type or (2)
 * users can choose to convert transparent typed objects into opaque
 * ones to avoid giving access to the buffer itself.
 *
 * Typed objects (no matter their class) are non-native objects that
 * fully override the property accessors etc. The overridden accessor
 * methods are the same in each and are defined in methods of
 * TypedObject.
 */

namespace js {

/*
 * Helper method for converting a double into other scalar
 * types in the same way that JavaScript would. In particular,
 * simple C casting from double to int32_t gets things wrong
 * for values like 0xF0000000.
 */
template <typename T>
static T ConvertScalar(double d)
{
    if (TypeIsFloatingPoint<T>())
        return T(d);
    if (TypeIsUnsigned<T>()) {
        uint32_t n = JS::ToUint32(d);
        return T(n);
    }
    int32_t n = JS::ToInt32(d);
    return T(n);
}

namespace type {

enum Kind {
    Scalar = JS_TYPEREPR_SCALAR_KIND,
    Reference = JS_TYPEREPR_REFERENCE_KIND,
    Simd = JS_TYPEREPR_SIMD_KIND,
    Struct = JS_TYPEREPR_STRUCT_KIND,
    Array = JS_TYPEREPR_ARRAY_KIND
};

} // namespace type

///////////////////////////////////////////////////////////////////////////
// Typed Prototypes

class SimpleTypeDescr;
class ComplexTypeDescr;
class SimdTypeDescr;
class StructTypeDescr;
class TypedProto;

/*
 * The prototype for a typed object.
 */
class TypedProto : public NativeObject
{
  public:
    static const Class class_;
};

class TypeDescr : public NativeObject
{
  public:
    TypedProto& typedProto() const {
        return getReservedSlot(JS_DESCR_SLOT_TYPROTO).toObject().as<TypedProto>();
    }

    JSAtom& stringRepr() const {
        return getReservedSlot(JS_DESCR_SLOT_STRING_REPR).toString()->asAtom();
    }

    type::Kind kind() const {
        return (type::Kind) getReservedSlot(JS_DESCR_SLOT_KIND).toInt32();
    }

    bool opaque() const {
        return getReservedSlot(JS_DESCR_SLOT_OPAQUE).toBoolean();
    }

    bool transparent() const {
        return !opaque();
    }

    uint32_t alignment() const {
        int32_t i = getReservedSlot(JS_DESCR_SLOT_ALIGNMENT).toInt32();
        MOZ_ASSERT(i >= 0);
        return uint32_t(i);
    }

    uint32_t size() const {
        int32_t i = getReservedSlot(JS_DESCR_SLOT_SIZE).toInt32();
        MOZ_ASSERT(i >= 0);
        return uint32_t(i);
    }

    // Whether id is an 'own' property of objects with this descriptor.
    MOZ_MUST_USE bool hasProperty(const JSAtomState& names, jsid id);

    // Type descriptors may contain a list of their references for use during
    // scanning. Marking code is optimized to use this list to mark inline
    // typed objects, rather than the slower trace hook. This list is only
    // specified when (a) the descriptor is short enough that it can fit in an
    // InlineTypedObject, and (b) the descriptor contains at least one
    // reference. Otherwise its value is undefined.
    //
    // The list is three consecutive arrays of int32_t offsets, with each array
    // terminated by -1. The arrays store offsets of string, object, and value
    // references in the descriptor, in that order.
    MOZ_MUST_USE bool hasTraceList() const {
        return !getFixedSlot(JS_DESCR_SLOT_TRACE_LIST).isUndefined();
    }
    const int32_t* traceList() const {
        MOZ_ASSERT(hasTraceList());
        return reinterpret_cast<int32_t*>(getFixedSlot(JS_DESCR_SLOT_TRACE_LIST).toPrivate());
    }

    void initInstances(const JSRuntime* rt, uint8_t* mem, size_t length);
    void traceInstances(JSTracer* trace, uint8_t* mem, size_t length);

    static void finalize(FreeOp* fop, JSObject* obj);
};

typedef Handle<TypeDescr*> HandleTypeDescr;

class SimpleTypeDescr : public TypeDescr
{
};

// Type for scalar type constructors like `uint8`. All such type
// constructors share a common js::Class and JSFunctionSpec. Scalar
// types are non-opaque (their storage is visible unless combined with
// an opaque reference type.)
class ScalarTypeDescr : public SimpleTypeDescr
{
  public:
    typedef Scalar::Type Type;

    static const type::Kind Kind = type::Scalar;
    static const bool Opaque = false;
    static uint32_t size(Type t);
    static uint32_t alignment(Type t);
    static const char* typeName(Type type);

    static const Class class_;
    static const JSFunctionSpec typeObjectMethods[];

    Type type() const {
        // Make sure the values baked into TypedObjectConstants.h line up with
        // the Scalar::Type enum. We don't define Scalar::Type directly in
        // terms of these constants to avoid making TypedObjectConstants.h a
        // public header file.
        static_assert(Scalar::Int8 == JS_SCALARTYPEREPR_INT8,
                      "TypedObjectConstants.h must be consistent with Scalar::Type");
        static_assert(Scalar::Uint8 == JS_SCALARTYPEREPR_UINT8,
                      "TypedObjectConstants.h must be consistent with Scalar::Type");
        static_assert(Scalar::Int16 == JS_SCALARTYPEREPR_INT16,
                      "TypedObjectConstants.h must be consistent with Scalar::Type");
        static_assert(Scalar::Uint16 == JS_SCALARTYPEREPR_UINT16,
                      "TypedObjectConstants.h must be consistent with Scalar::Type");
        static_assert(Scalar::Int32 == JS_SCALARTYPEREPR_INT32,
                      "TypedObjectConstants.h must be consistent with Scalar::Type");
        static_assert(Scalar::Uint32 == JS_SCALARTYPEREPR_UINT32,
                      "TypedObjectConstants.h must be consistent with Scalar::Type");
        static_assert(Scalar::Float32 == JS_SCALARTYPEREPR_FLOAT32,
                      "TypedObjectConstants.h must be consistent with Scalar::Type");
        static_assert(Scalar::Float64 == JS_SCALARTYPEREPR_FLOAT64,
                      "TypedObjectConstants.h must be consistent with Scalar::Type");
        static_assert(Scalar::Uint8Clamped == JS_SCALARTYPEREPR_UINT8_CLAMPED,
                      "TypedObjectConstants.h must be consistent with Scalar::Type");
        static_assert(Scalar::Float32x4 == JS_SCALARTYPEREPR_FLOAT32X4,
                      "TypedObjectConstants.h must be consistent with Scalar::Type");
        static_assert(Scalar::Int8x16 == JS_SCALARTYPEREPR_INT8X16,
                      "TypedObjectConstants.h must be consistent with Scalar::Type");
        static_assert(Scalar::Int16x8 == JS_SCALARTYPEREPR_INT16X8,
                      "TypedObjectConstants.h must be consistent with Scalar::Type");
        static_assert(Scalar::Int32x4 == JS_SCALARTYPEREPR_INT32X4,
                      "TypedObjectConstants.h must be consistent with Scalar::Type");

        return Type(getReservedSlot(JS_DESCR_SLOT_TYPE).toInt32());
    }

    static MOZ_MUST_USE bool call(JSContext* cx, unsigned argc, Value* vp);
};

// Enumerates the cases of ScalarTypeDescr::Type which have
// unique C representation. In particular, omits Uint8Clamped since it
// is just a Uint8.
#define JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(macro_)       \
    macro_(Scalar::Int8,    int8_t,   int8)                     \
    macro_(Scalar::Uint8,   uint8_t,  uint8)                    \
    macro_(Scalar::Int16,   int16_t,  int16)                    \
    macro_(Scalar::Uint16,  uint16_t, uint16)                   \
    macro_(Scalar::Int32,   int32_t,  int32)                    \
    macro_(Scalar::Uint32,  uint32_t, uint32)                   \
    macro_(Scalar::Float32, float,    float32)                  \
    macro_(Scalar::Float64, double,   float64)

// Must be in same order as the enum ScalarTypeDescr::Type:
#define JS_FOR_EACH_SCALAR_TYPE_REPR(macro_)                    \
    JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(macro_)           \
    macro_(Scalar::Uint8Clamped, uint8_t, uint8Clamped)

// Type for reference type constructors like `Any`, `String`, and
// `Object`. All such type constructors share a common js::Class and
// JSFunctionSpec. All these types are opaque.
class ReferenceTypeDescr : public SimpleTypeDescr
{
  public:
    // Must match order of JS_FOR_EACH_REFERENCE_TYPE_REPR below
    enum Type {
        TYPE_ANY = JS_REFERENCETYPEREPR_ANY,
        TYPE_OBJECT = JS_REFERENCETYPEREPR_OBJECT,
        TYPE_STRING = JS_REFERENCETYPEREPR_STRING,
    };
    static const int32_t TYPE_MAX = TYPE_STRING + 1;
    static const char* typeName(Type type);

    static const type::Kind Kind = type::Reference;
    static const bool Opaque = true;
    static const Class class_;
    static uint32_t size(Type t);
    static uint32_t alignment(Type t);
    static const JSFunctionSpec typeObjectMethods[];

    ReferenceTypeDescr::Type type() const {
        return (ReferenceTypeDescr::Type) getReservedSlot(JS_DESCR_SLOT_TYPE).toInt32();
    }

    const char* typeName() const {
        return typeName(type());
    }

    static MOZ_MUST_USE bool call(JSContext* cx, unsigned argc, Value* vp);
};

#define JS_FOR_EACH_REFERENCE_TYPE_REPR(macro_) \
    macro_(ReferenceTypeDescr::TYPE_ANY, GCPtrValue, Any) \
    macro_(ReferenceTypeDescr::TYPE_OBJECT, GCPtrObject, Object) \
    macro_(ReferenceTypeDescr::TYPE_STRING, GCPtrString, string)

// Type descriptors whose instances are objects and hence which have
// an associated `prototype` property.
class ComplexTypeDescr : public TypeDescr
{
  public:
    // Returns the prototype that instances of this type descriptor
    // will have.
    TypedProto& instancePrototype() const {
        return getReservedSlot(JS_DESCR_SLOT_TYPROTO).toObject().as<TypedProto>();
    }
};

enum class SimdType;

/*
 * SIMD Type descriptors.
 */
class SimdTypeDescr : public ComplexTypeDescr
{
  public:
    static const type::Kind Kind = type::Simd;
    static const bool Opaque = false;
    static const Class class_;
    static uint32_t size(SimdType t);
    static uint32_t alignment(SimdType t);
    static MOZ_MUST_USE bool call(JSContext* cx, unsigned argc, Value* vp);
    static bool is(const Value& v);

    SimdType type() const;
};

bool IsTypedObjectClass(const Class* clasp); // Defined below
bool IsTypedObjectArray(JSObject& obj);

MOZ_MUST_USE bool CreateUserSizeAndAlignmentProperties(JSContext* cx, HandleTypeDescr obj);

class ArrayTypeDescr;

/*
 * Properties and methods of the `ArrayType` meta type object. There
 * is no `class_` field because `ArrayType` is just a native
 * constructor function.
 */
class ArrayMetaTypeDescr : public NativeObject
{
  private:
    // Helper for creating a new ArrayType object.
    //
    // - `arrayTypePrototype` - prototype for the new object to be created
    // - `elementType` - type object for the elements in the array
    // - `stringRepr` - canonical string representation for the array
    // - `size` - length of the array
    static ArrayTypeDescr* create(JSContext* cx,
                                  HandleObject arrayTypePrototype,
                                  HandleTypeDescr elementType,
                                  HandleAtom stringRepr,
                                  int32_t size,
                                  int32_t length);

  public:
    // Properties and methods to be installed on ArrayType.prototype,
    // and hence inherited by all array type objects:
    static const JSPropertySpec typeObjectProperties[];
    static const JSFunctionSpec typeObjectMethods[];

    // Properties and methods to be installed on ArrayType.prototype.prototype,
    // and hence inherited by all array *typed* objects:
    static const JSPropertySpec typedObjectProperties[];
    static const JSFunctionSpec typedObjectMethods[];

    // This is the function that gets called when the user
    // does `new ArrayType(elem)`. It produces an array type object.
    static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);
};

/*
 * Type descriptor created by `new ArrayType(type, n)`
 */
class ArrayTypeDescr : public ComplexTypeDescr
{
  public:
    static const Class class_;
    static const type::Kind Kind = type::Array;

    TypeDescr& elementType() const {
        return getReservedSlot(JS_DESCR_SLOT_ARRAY_ELEM_TYPE).toObject().as<TypeDescr>();
    }

    uint32_t length() const {
        int32_t i = getReservedSlot(JS_DESCR_SLOT_ARRAY_LENGTH).toInt32();
        MOZ_ASSERT(i >= 0);
        return uint32_t(i);
    }

    static int32_t offsetOfLength() {
        return getFixedSlotOffset(JS_DESCR_SLOT_ARRAY_LENGTH);
    }
};

/*
 * Properties and methods of the `StructType` meta type object. There
 * is no `class_` field because `StructType` is just a native
 * constructor function.
 */
class StructMetaTypeDescr : public NativeObject
{
  private:
    static JSObject* create(JSContext* cx, HandleObject structTypeGlobal,
                            HandleObject fields);

  public:
    // Properties and methods to be installed on StructType.prototype,
    // and hence inherited by all struct type objects:
    static const JSPropertySpec typeObjectProperties[];
    static const JSFunctionSpec typeObjectMethods[];

    // Properties and methods to be installed on StructType.prototype.prototype,
    // and hence inherited by all struct *typed* objects:
    static const JSPropertySpec typedObjectProperties[];
    static const JSFunctionSpec typedObjectMethods[];

    // This is the function that gets called when the user
    // does `new StructType(...)`. It produces a struct type object.
    static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);
};

class StructTypeDescr : public ComplexTypeDescr
{
  public:
    static const Class class_;

    // Returns the number of fields defined in this struct.
    size_t fieldCount() const;

    // Set `*out` to the index of the field named `id` and returns true,
    // or return false if no such field exists.
    MOZ_MUST_USE bool fieldIndex(jsid id, size_t* out) const;

    // Return the name of the field at index `index`.
    JSAtom& fieldName(size_t index) const;

    // Return the type descr of the field at index `index`.
    TypeDescr& fieldDescr(size_t index) const;

    // Return the offset of the field at index `index`.
    size_t fieldOffset(size_t index) const;

  private:
    ArrayObject& fieldInfoObject(size_t slot) const {
        return getReservedSlot(slot).toObject().as<ArrayObject>();
    }
};

typedef Handle<StructTypeDescr*> HandleStructTypeDescr;

/*
 * This object exists in order to encapsulate the typed object types
 * somewhat, rather than sticking them all into the global object.
 * Eventually it will go away and become a module.
 */
class TypedObjectModuleObject : public NativeObject {
  public:
    enum Slot {
        ArrayTypePrototype,
        StructTypePrototype,
        SlotCount
    };

    static const Class class_;
};

/* Base type for transparent and opaque typed objects. */
class TypedObject : public ShapedObject
{
    static const bool IsTypedObjectClass = true;

    static MOZ_MUST_USE bool obj_getArrayElement(JSContext* cx,
                                                 Handle<TypedObject*> typedObj,
                                                 Handle<TypeDescr*> typeDescr,
                                                 uint32_t index,
                                                 MutableHandleValue vp);

  protected:
    static const ObjectOps objectOps_;

    static MOZ_MUST_USE bool obj_lookupProperty(JSContext* cx, HandleObject obj,
                                                HandleId id, MutableHandleObject objp,
                                                MutableHandleShape propp);

    static MOZ_MUST_USE bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
                                                Handle<PropertyDescriptor> desc,
                                                ObjectOpResult& result);

    static MOZ_MUST_USE bool obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id,
                                             bool* foundp);

    static MOZ_MUST_USE bool obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver,
                                             HandleId id, MutableHandleValue vp);

    static MOZ_MUST_USE bool obj_getElement(JSContext* cx, HandleObject obj, HandleValue receiver,
                                            uint32_t index, MutableHandleValue vp);

    static MOZ_MUST_USE bool obj_setProperty(JSContext* cx, HandleObject obj, HandleId id,
                                             HandleValue v, HandleValue receiver,
                                             ObjectOpResult& result);

    static MOZ_MUST_USE bool obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj,
                                                          HandleId id,
                                                          MutableHandle<PropertyDescriptor> desc);

    static MOZ_MUST_USE bool obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
                                                ObjectOpResult& result);

    static MOZ_MUST_USE bool obj_enumerate(JSContext* cx, HandleObject obj,
                                           AutoIdVector& properties, bool enumerableOnly);


    uint8_t* typedMem() const;
    uint8_t* typedMemBase() const;

  public:
    TypedProto& typedProto() const {
        // Typed objects' prototypes can't be modified.
        return staticPrototype()->as<TypedProto>();
    }

    TypeDescr& typeDescr() const {
        return group()->typeDescr();
    }

    uint32_t offset() const;
    uint32_t length() const;
    uint8_t* typedMem(const JS::AutoRequireNoGC&) const { return typedMem(); }
    bool isAttached() const;

    uint32_t size() const {
        return typeDescr().size();
    }

    uint8_t* typedMem(size_t offset, const JS::AutoRequireNoGC& nogc) const {
        // It seems a bit surprising that one might request an offset
        // == size(), but it can happen when taking the "address of" a
        // 0-sized value. (In other words, we maintain the invariant
        // that `offset + size <= size()` -- this is always checked in
        // the caller's side.)
        MOZ_ASSERT(offset <= (size_t) size());
        return typedMem(nogc) + offset;
    }

    inline MOZ_MUST_USE bool opaque() const;

    // Creates a new typed object whose memory is freshly allocated and
    // initialized with zeroes (or, in the case of references, an appropriate
    // default value).
    static TypedObject* createZeroed(JSContext* cx, HandleTypeDescr typeObj, int32_t length,
                                     gc::InitialHeap heap = gc::DefaultHeap);

    // User-accessible constructor (`new TypeDescriptor(...)`). Note that the
    // callee here is the type descriptor.
    static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);

    /* Accessors for self hosted code. */
    static MOZ_MUST_USE bool GetBuffer(JSContext* cx, unsigned argc, Value* vp);
    static MOZ_MUST_USE bool GetByteOffset(JSContext* cx, unsigned argc, Value* vp);

    Shape** addressOfShapeFromGC() { return shape_.unsafeUnbarrieredForTracing(); }
};

typedef Handle<TypedObject*> HandleTypedObject;

class OutlineTypedObject : public TypedObject
{
    // The object which owns the data this object points to. Because this
    // pointer is managed in tandem with |data|, this is not a GCPtr and
    // barriers are managed directly.
    JSObject* owner_;

    // Data pointer to some offset in the owner's contents.
    uint8_t* data_;

    void setOwnerAndData(JSObject* owner, uint8_t* data);

  public:
    // JIT accessors.
    static size_t offsetOfData() { return offsetof(OutlineTypedObject, data_); }
    static size_t offsetOfOwner() { return offsetof(OutlineTypedObject, owner_); }

    JSObject& owner() const {
        MOZ_ASSERT(owner_);
        return *owner_;
    }

    JSObject* maybeOwner() const {
        return owner_;
    }

    uint8_t* outOfLineTypedMem() const {
        return data_;
    }

    void setData(uint8_t* data) {
        data_ = data;
    }

    void resetOffset(size_t offset) {
        MOZ_ASSERT(offset <= (size_t) size());
        setData(typedMemBase() + offset);
    }

    // Helper for createUnattached()
    static OutlineTypedObject* createUnattachedWithClass(JSContext* cx,
                                                         const Class* clasp,
                                                         HandleTypeDescr type,
                                                         int32_t length,
                                                         gc::InitialHeap heap = gc::DefaultHeap);

    // Creates an unattached typed object or handle (depending on the
    // type parameter T). Note that it is only legal for unattached
    // handles to escape to the end user; for non-handles, the caller
    // should always invoke one of the `attach()` methods below.
    //
    // Arguments:
    // - type: type object for resulting object
    // - length: 0 unless this is an array, otherwise the length
    static OutlineTypedObject* createUnattached(JSContext* cx, HandleTypeDescr type,
                                                int32_t length, gc::InitialHeap heap = gc::DefaultHeap);

    // Creates a typedObj that aliases the memory pointed at by `owner`
    // at the given offset. The typedObj will be a handle iff type is a
    // handle and a typed object otherwise.
    static OutlineTypedObject* createDerived(JSContext* cx,
                                             HandleTypeDescr type,
                                             Handle<TypedObject*> typedContents,
                                             uint32_t offset);

    // Use this method when `buffer` is the owner of the memory.
    void attach(JSContext* cx, ArrayBufferObject& buffer, uint32_t offset);

    // Otherwise, use this to attach to memory referenced by another typedObj.
    void attach(JSContext* cx, TypedObject& typedObj, uint32_t offset);

    // Invoked when array buffer is transferred elsewhere
    void notifyBufferDetached(void* newData);

    static void obj_trace(JSTracer* trace, JSObject* object);
};

// Class for a transparent typed object whose owner is an array buffer.
class OutlineTransparentTypedObject : public OutlineTypedObject
{
  public:
    static const Class class_;

    ArrayBufferObject* getOrCreateBuffer(JSContext* cx);
};

// Class for an opaque typed object whose owner may be either an array buffer
// or an opaque inlined typed object.
class OutlineOpaqueTypedObject : public OutlineTypedObject
{
  public:
    static const Class class_;
};

// Class for a typed object whose data is allocated inline.
class InlineTypedObject : public TypedObject
{
    friend class TypedObject;

    // Start of the inline data, which immediately follows the shape and type.
    uint8_t data_[1];

  protected:
    uint8_t* inlineTypedMem() const {
        return (uint8_t*) &data_;
    }

  public:
    static const size_t MaximumSize = JSObject::MAX_BYTE_SIZE - sizeof(TypedObject);

    static gc::AllocKind allocKindForTypeDescriptor(TypeDescr* descr) {
        size_t nbytes = descr->size();
        MOZ_ASSERT(nbytes <= MaximumSize);

        return gc::GetGCObjectKindForBytes(nbytes + sizeof(TypedObject));
    }

    uint8_t* inlineTypedMem(const JS::AutoRequireNoGC&) const {
        return inlineTypedMem();
    }

    uint8_t* inlineTypedMemForGC() const {
        return inlineTypedMem();
    }

    static void obj_trace(JSTracer* trace, JSObject* object);
    static void objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src);

    static size_t offsetOfDataStart() {
        return offsetof(InlineTypedObject, data_);
    }

    static InlineTypedObject* create(JSContext* cx, HandleTypeDescr descr,
                                     gc::InitialHeap heap = gc::DefaultHeap);
    static InlineTypedObject* createCopy(JSContext* cx, Handle<InlineTypedObject*> templateObject,
                                         gc::InitialHeap heap);
};

// Class for a transparent typed object with inline data, which may have a
// lazily allocated array buffer.
class InlineTransparentTypedObject : public InlineTypedObject
{
  public:
    static const Class class_;

    ArrayBufferObject* getOrCreateBuffer(JSContext* cx);

    uint8_t* inlineTypedMem() const {
        return InlineTypedObject::inlineTypedMem();
    }
};

// Class for an opaque typed object with inline data and no array buffer.
class InlineOpaqueTypedObject : public InlineTypedObject
{
  public:
    static const Class class_;
};

// Class for the global SIMD object.
class SimdObject : public JSObject
{
  public:
    static const Class class_;
    static MOZ_MUST_USE bool toString(JSContext* cx, unsigned int argc, Value* vp);
    static MOZ_MUST_USE bool resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId,
                                     bool* resolved);
};

/*
 * Usage: NewOpaqueTypedObject(typeObj)
 *
 * Constructs a new, unattached instance of `Handle`.
 */
MOZ_MUST_USE bool NewOpaqueTypedObject(JSContext* cx, unsigned argc, Value* vp);

/*
 * Usage: NewDerivedTypedObject(typeObj, owner, offset)
 *
 * Constructs a new, unattached instance of `Handle`.
 */
MOZ_MUST_USE bool NewDerivedTypedObject(JSContext* cx, unsigned argc, Value* vp);

/*
 * Usage: AttachTypedObject(typedObj, newDatum, newOffset)
 *
 * Moves `typedObj` to point at the memory referenced by `newDatum` with
 * the offset `newOffset`.
 */
MOZ_MUST_USE bool AttachTypedObject(JSContext* cx, unsigned argc, Value* vp);

/*
 * Usage: SetTypedObjectOffset(typedObj, offset)
 *
 * Changes the offset for `typedObj` within its buffer to `offset`.
 * `typedObj` must already be attached.
 */
MOZ_MUST_USE bool SetTypedObjectOffset(JSContext*, unsigned argc, Value* vp);

/*
 * Usage: ObjectIsTypeDescr(obj)
 *
 * True if `obj` is a type object.
 */
MOZ_MUST_USE bool ObjectIsTypeDescr(JSContext* cx, unsigned argc, Value* vp);

/*
 * Usage: ObjectIsTypedObject(obj)
 *
 * True if `obj` is a transparent or opaque typed object.
 */
MOZ_MUST_USE bool ObjectIsTypedObject(JSContext* cx, unsigned argc, Value* vp);

/*
 * Usage: ObjectIsOpaqueTypedObject(obj)
 *
 * True if `obj` is an opaque typed object.
 */
MOZ_MUST_USE bool ObjectIsOpaqueTypedObject(JSContext* cx, unsigned argc, Value* vp);

/*
 * Usage: ObjectIsTransparentTypedObject(obj)
 *
 * True if `obj` is a transparent typed object.
 */
MOZ_MUST_USE bool ObjectIsTransparentTypedObject(JSContext* cx, unsigned argc, Value* vp);

/* Predicates on type descriptor objects.  In all cases, 'obj' must be a type descriptor. */

MOZ_MUST_USE bool TypeDescrIsSimpleType(JSContext*, unsigned argc, Value* vp);

MOZ_MUST_USE bool TypeDescrIsArrayType(JSContext*, unsigned argc, Value* vp);

/*
 * Usage: TypedObjectIsAttached(obj)
 *
 * Given a TypedObject `obj`, returns true if `obj` is
 * "attached" (i.e., its data pointer is nullptr).
 */
MOZ_MUST_USE bool TypedObjectIsAttached(JSContext* cx, unsigned argc, Value* vp);

/*
 * Usage: TypedObjectTypeDescr(obj)
 *
 * Given a TypedObject `obj`, returns the object's type descriptor.
 */
MOZ_MUST_USE bool TypedObjectTypeDescr(JSContext* cx, unsigned argc, Value* vp);

/*
 * Usage: ClampToUint8(v)
 *
 * Same as the C function ClampDoubleToUint8. `v` must be a number.
 */
MOZ_MUST_USE bool ClampToUint8(JSContext* cx, unsigned argc, Value* vp);

/*
 * Usage: GetTypedObjectModule()
 *
 * Returns the global "typed object" module, which provides access
 * to the various builtin type descriptors. These are currently
 * exported as immutable properties so it is safe for self-hosted code
 * to access them; eventually this should be linked into the module
 * system.
 */
MOZ_MUST_USE bool GetTypedObjectModule(JSContext* cx, unsigned argc, Value* vp);

/*
 * Usage: GetSimdTypeDescr(simdTypeRepr)
 *
 * Returns one of the SIMD type objects, identified by `simdTypeRepr` which must
 * be one of the JS_SIMDTYPEREPR_* constants.
 *
 * The SIMD pseudo-module must have been initialized for this to be safe.
 */
MOZ_MUST_USE bool GetSimdTypeDescr(JSContext* cx, unsigned argc, Value* vp);

/*
 * Usage: Store_int8(targetDatum, targetOffset, value)
 *        ...
 *        Store_uint8(targetDatum, targetOffset, value)
 *        ...
 *        Store_float32(targetDatum, targetOffset, value)
 *        Store_float64(targetDatum, targetOffset, value)
 *
 * Intrinsic function. Stores `value` into the memory referenced by
 * `targetDatum` at the offset `targetOffset`.
 *
 * Assumes (and asserts) that:
 * - `targetDatum` is attached
 * - `targetOffset` is a valid offset within the bounds of `targetDatum`
 * - `value` is a number
 */
#define JS_STORE_SCALAR_CLASS_DEFN(_constant, T, _name)                       \
class StoreScalar##T {                                                        \
  public:                                                                     \
    static MOZ_MUST_USE bool Func(JSContext* cx, unsigned argc, Value* vp);   \
    static const JSJitInfo JitInfo;                                           \
};

/*
 * Usage: Store_Any(targetDatum, targetOffset, fieldName, value)
 *        Store_Object(targetDatum, targetOffset, fieldName, value)
 *        Store_string(targetDatum, targetOffset, fieldName, value)
 *
 * Intrinsic function. Stores `value` into the memory referenced by
 * `targetDatum` at the offset `targetOffset`.
 *
 * Assumes (and asserts) that:
 * - `targetDatum` is attached
 * - `targetOffset` is a valid offset within the bounds of `targetDatum`
 * - `value` is an object or null (`Store_Object`) or string (`Store_string`).
 */
#define JS_STORE_REFERENCE_CLASS_DEFN(_constant, T, _name)                    \
class StoreReference##_name {                                                 \
  private:                                                                    \
    static MOZ_MUST_USE bool store(JSContext* cx, T* heap, const Value& v,    \
                                   TypedObject* obj, jsid id);                \
                                                                              \
  public:                                                                     \
    static MOZ_MUST_USE bool Func(JSContext* cx, unsigned argc, Value* vp);   \
    static const JSJitInfo JitInfo;                                           \
};

/*
 * Usage: LoadScalar(targetDatum, targetOffset, value)
 *
 * Intrinsic function. Loads value (which must be an int32 or uint32)
 * by `scalarTypeRepr` (which must be a type repr obj) and loads the
 * value at the memory for `targetDatum` at offset `targetOffset`.
 * `targetDatum` must be attached.
 */
#define JS_LOAD_SCALAR_CLASS_DEFN(_constant, T, _name)                        \
class LoadScalar##T {                                                         \
  public:                                                                     \
    static MOZ_MUST_USE bool Func(JSContext* cx, unsigned argc, Value* vp);   \
    static const JSJitInfo JitInfo;                                           \
};

/*
 * Usage: LoadReference(targetDatum, targetOffset, value)
 *
 * Intrinsic function. Stores value (which must be an int32 or uint32)
 * by `scalarTypeRepr` (which must be a type repr obj) and stores the
 * value at the memory for `targetDatum` at offset `targetOffset`.
 * `targetDatum` must be attached.
 */
#define JS_LOAD_REFERENCE_CLASS_DEFN(_constant, T, _name)                     \
class LoadReference##_name {                                                  \
  private:                                                                    \
    static void load(T* heap, MutableHandleValue v);                          \
                                                                              \
  public:                                                                     \
    static MOZ_MUST_USE bool Func(JSContext* cx, unsigned argc, Value* vp);   \
    static const JSJitInfo JitInfo;                                           \
};

// 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_DEFN)
JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(JS_LOAD_SCALAR_CLASS_DEFN)
JS_FOR_EACH_REFERENCE_TYPE_REPR(JS_STORE_REFERENCE_CLASS_DEFN)
JS_FOR_EACH_REFERENCE_TYPE_REPR(JS_LOAD_REFERENCE_CLASS_DEFN)

inline bool
IsTypedObjectClass(const Class* class_)
{
    return class_ == &OutlineTransparentTypedObject::class_ ||
           class_ == &InlineTransparentTypedObject::class_ ||
           class_ == &OutlineOpaqueTypedObject::class_ ||
           class_ == &InlineOpaqueTypedObject::class_;
}

inline bool
IsOpaqueTypedObjectClass(const Class* class_)
{
    return class_ == &OutlineOpaqueTypedObject::class_ ||
           class_ == &InlineOpaqueTypedObject::class_;
}

inline bool
IsOutlineTypedObjectClass(const Class* class_)
{
    return class_ == &OutlineOpaqueTypedObject::class_ ||
           class_ == &OutlineTransparentTypedObject::class_;
}

inline bool
IsInlineTypedObjectClass(const Class* class_)
{
    return class_ == &InlineOpaqueTypedObject::class_ ||
           class_ == &InlineTransparentTypedObject::class_;
}

inline const Class*
GetOutlineTypedObjectClass(bool opaque)
{
    return opaque ? &OutlineOpaqueTypedObject::class_ : &OutlineTransparentTypedObject::class_;
}

inline bool
IsSimpleTypeDescrClass(const Class* clasp)
{
    return clasp == &ScalarTypeDescr::class_ ||
           clasp == &ReferenceTypeDescr::class_;
}

inline bool
IsComplexTypeDescrClass(const Class* clasp)
{
    return clasp == &StructTypeDescr::class_ ||
           clasp == &ArrayTypeDescr::class_ ||
           clasp == &SimdTypeDescr::class_;
}

inline bool
IsTypeDescrClass(const Class* clasp)
{
    return IsSimpleTypeDescrClass(clasp) ||
           IsComplexTypeDescrClass(clasp);
}

inline bool
TypedObject::opaque() const
{
    return IsOpaqueTypedObjectClass(getClass());
}

JSObject*
InitTypedObjectModuleObject(JSContext* cx, JS::HandleObject obj);

} // namespace js

template <>
inline bool
JSObject::is<js::SimpleTypeDescr>() const
{
    return IsSimpleTypeDescrClass(getClass());
}

template <>
inline bool
JSObject::is<js::ComplexTypeDescr>() const
{
    return IsComplexTypeDescrClass(getClass());
}

template <>
inline bool
JSObject::is<js::TypeDescr>() const
{
    return IsTypeDescrClass(getClass());
}

template <>
inline bool
JSObject::is<js::TypedObject>() const
{
    return IsTypedObjectClass(getClass());
}

template <>
inline bool
JSObject::is<js::OutlineTypedObject>() const
{
    return getClass() == &js::OutlineTransparentTypedObject::class_ ||
           getClass() == &js::OutlineOpaqueTypedObject::class_;
}

template <>
inline bool
JSObject::is<js::InlineTypedObject>() const
{
    return getClass() == &js::InlineTransparentTypedObject::class_ ||
           getClass() == &js::InlineOpaqueTypedObject::class_;
}

#endif /* builtin_TypedObject_h */
