diff options
Diffstat (limited to 'js/src/builtin/TypedObject.cpp')
-rw-r--r-- | js/src/builtin/TypedObject.cpp | 3008 |
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 000000000..b7297c894 --- /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())); +} |