summaryrefslogtreecommitdiff
path: root/js/src/builtin/TypedObject.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /js/src/builtin/TypedObject.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloaduxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
Add m-esr52 at 52.6.0
Diffstat (limited to 'js/src/builtin/TypedObject.cpp')
-rw-r--r--js/src/builtin/TypedObject.cpp3008
1 files changed, 3008 insertions, 0 deletions
diff --git a/js/src/builtin/TypedObject.cpp b/js/src/builtin/TypedObject.cpp
new file mode 100644
index 0000000000..b7297c8944
--- /dev/null
+++ b/js/src/builtin/TypedObject.cpp
@@ -0,0 +1,3008 @@
+/* -*- 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()));
+}