| /* -*- 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 "vm/NativeObject-inl.h" |
| #include "vm/Shape-inl.h" |
| |
| using mozilla::AssertedCast; |
| using mozilla::CheckedInt32; |
| using mozilla::DebugOnly; |
| using mozilla::PodCopy; |
| |
| 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_ReportErrorNumber(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, int32_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; |
| |
| InvokeArgs args(cx); |
| if (!args.init(5)) |
| return false; |
| |
| args.setCallee(ObjectValue(*func)); |
| 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); |
| |
| return Invoke(cx, args); |
| } |
| |
| 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; |
| |
| InvokeArgs args(cx); |
| if (!args.init(3)) |
| return false; |
| |
| args.setCallee(ObjectValue(*func)); |
| args[0].setObject(*type); |
| args[1].setObject(*typedObj); |
| args[2].setInt32(offset); |
| |
| if (!Invoke(cx, args)) |
| return false; |
| |
| to.set(args.rval()); |
| return true; |
| } |
| |
| // 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_ReportErrorNumber(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. |
| */ |
| |
| const Class js::ScalarTypeDescr::class_ = { |
| "Scalar", |
| JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE, |
| nullptr, /* addProperty */ |
| nullptr, /* delProperty */ |
| nullptr, /* getProperty */ |
| nullptr, /* setProperty */ |
| nullptr, /* enumerate */ |
| nullptr, /* resolve */ |
| nullptr, /* mayResolve */ |
| TypeDescr::finalize, |
| ScalarTypeDescr::call |
| }; |
| |
| const JSFunctionSpec js::ScalarTypeDescr::typeObjectMethods[] = { |
| JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0), |
| {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"}, |
| {"equivalent", {nullptr, nullptr}, 1, 0, "TypeDescrEquivalent"}, |
| JS_FS_END |
| }; |
| |
| int32_t |
| ScalarTypeDescr::size(Type t) |
| { |
| return Scalar::byteSize(t); |
| } |
| |
| int32_t |
| ScalarTypeDescr::alignment(Type t) |
| { |
| return 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::Float32x4: |
| case Scalar::Int32x4: |
| case Scalar::MaxTypedArrayViewType: |
| MOZ_CRASH(); |
| } |
| MOZ_CRASH("Invalid type"); |
| } |
| |
| bool |
| ScalarTypeDescr::call(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() < 1) { |
| JS_ReportErrorNumber(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::Float32x4: |
| 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. |
| */ |
| |
| const Class js::ReferenceTypeDescr::class_ = { |
| "Reference", |
| JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE, |
| nullptr, /* addProperty */ |
| nullptr, /* delProperty */ |
| nullptr, /* getProperty */ |
| nullptr, /* setProperty */ |
| nullptr, /* enumerate */ |
| nullptr, /* resolve */ |
| nullptr, /* mayResolve */ |
| TypeDescr::finalize, |
| ReferenceTypeDescr::call |
| }; |
| |
| 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 int32_t ReferenceSizes[] = { |
| #define REFERENCE_SIZE(_kind, _type, _name) \ |
| sizeof(_type), |
| JS_FOR_EACH_REFERENCE_TYPE_REPR(REFERENCE_SIZE) 0 |
| #undef REFERENCE_SIZE |
| }; |
| |
| int32_t |
| ReferenceTypeDescr::size(Type t) |
| { |
| return ReferenceSizes[t]; |
| } |
| |
| int32_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_ReportErrorNumber(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 |
| */ |
| |
| int32_t |
| SimdTypeDescr::size(Type t) |
| { |
| MOZ_ASSERT(unsigned(t) <= SimdTypeDescr::Type::LAST_TYPE); |
| switch (t) { |
| case SimdTypeDescr::Int8x16: |
| case SimdTypeDescr::Int16x8: |
| case SimdTypeDescr::Int32x4: |
| case SimdTypeDescr::Float32x4: |
| case SimdTypeDescr::Float64x2: |
| return 16; |
| } |
| MOZ_CRASH("unexpected SIMD type"); |
| } |
| |
| int32_t |
| SimdTypeDescr::alignment(Type t) |
| { |
| MOZ_ASSERT(unsigned(t) <= SimdTypeDescr::Type::LAST_TYPE); |
| 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); |
| } |
| |
| const Class ArrayTypeDescr::class_ = { |
| "ArrayType", |
| JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE, |
| nullptr, /* addProperty */ |
| nullptr, /* delProperty */ |
| nullptr, /* getProperty */ |
| nullptr, /* setProperty */ |
| nullptr, /* enumerate */ |
| nullptr, /* resolve */ |
| nullptr, /* mayResolve */ |
| TypeDescr::finalize, |
| nullptr, /* call */ |
| nullptr, /* hasInstance */ |
| TypedObject::construct |
| }; |
| |
| 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(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; |
| |
| 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_ReportErrorNumber(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_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| JSMSG_TYPEDOBJECT_TOO_BIG); |
| return false; |
| } |
| |
| // Construct a canonical string `new ArrayType(<elementType>, N)`: |
| StringBuffer contents(cx); |
| contents.append("new ArrayType("); |
| contents.append(&elementType->stringRepr()); |
| contents.append(", "); |
| if (!NumberValueToStringBuffer(cx, NumberValue(length), contents)) |
| return false; |
| contents.append(")"); |
| 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 |
| */ |
| |
| const Class StructTypeDescr::class_ = { |
| "StructType", |
| JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE, |
| nullptr, /* addProperty */ |
| nullptr, /* delProperty */ |
| nullptr, /* getProperty */ |
| nullptr, /* setProperty */ |
| nullptr, /* enumerate */ |
| nullptr, /* resolve */ |
| nullptr, /* mayResolve */ |
| TypeDescr::finalize, |
| nullptr, /* call */ |
| nullptr, /* hasInstance */ |
| TypedObject::construct |
| }; |
| |
| 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. |
| int32_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({")) { |
| ReportOutOfMemory(cx); |
| 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)) { |
| ReportOutOfMemory(cx); |
| return nullptr; |
| } |
| if (!fieldTypeObjs.append(ObjectValue(*fieldType))) { |
| ReportOutOfMemory(cx); |
| 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(", ")) { |
| ReportOutOfMemory(cx); |
| return nullptr; |
| } |
| if (!stringBuffer.append(JSID_TO_ATOM(id))) { |
| ReportOutOfMemory(cx); |
| return nullptr; |
| } |
| if (!stringBuffer.append(": ")) { |
| ReportOutOfMemory(cx); |
| return nullptr; |
| } |
| if (!stringBuffer.append(&fieldType->stringRepr())) { |
| ReportOutOfMemory(cx); |
| 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_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| JSMSG_TYPEDOBJECT_TOO_BIG); |
| return nullptr; |
| } |
| MOZ_ASSERT(offset.value() >= 0); |
| if (!fieldOffsets.append(Int32Value(offset.value()))) { |
| ReportOutOfMemory(cx); |
| 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_ReportErrorNumber(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("})")) { |
| ReportOutOfMemory(cx); |
| 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_ReportErrorNumber(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(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; |
| |
| 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_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| JSMSG_TYPEDOBJECT_STRUCTTYPE_BAD_ARGS); |
| return false; |
| } |
| |
| size_t |
| StructTypeDescr::fieldCount() const |
| { |
| return fieldInfoObject(JS_DESCR_SLOT_STRUCT_FIELD_NAMES).getDenseInitializedLength(); |
| } |
| |
| size_t |
| StructTypeDescr::maybeForwardedFieldCount() const |
| { |
| ArrayObject& fieldInfo = *MaybeForwarded(&fieldInfoObject(JS_DESCR_SLOT_STRUCT_FIELD_NAMES)); |
| return fieldInfo.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()); |
| } |
| |
| size_t |
| StructTypeDescr::maybeForwardedFieldOffset(size_t index) const |
| { |
| ArrayObject& fieldOffsets = *MaybeForwarded(&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>(); |
| } |
| |
| TypeDescr& |
| StructTypeDescr::maybeForwardedFieldDescr(size_t index) const |
| { |
| ArrayObject& fieldDescrs = *MaybeForwarded(&fieldInfoObject(JS_DESCR_SLOT_STRUCT_FIELD_TYPES)); |
| MOZ_ASSERT(index < fieldDescrs.getDenseInitializedLength()); |
| JSObject& descr = |
| *MaybeForwarded(&fieldDescrs.getDenseElement(index).toObject()); |
| return descr.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(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; |
| |
| 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)); |
| global->setConstructor(JSProto_TypedObject, moduleValue); |
| if (!DefineProperty(cx, global, cx->names().TypedObject, moduleValue, nullptr, nullptr, |
| JSPROP_RESOLVING)) |
| { |
| return false; |
| } |
| |
| 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 |
| */ |
| |
| int32_t |
| TypedObject::offset() const |
| { |
| if (is<InlineTypedObject>()) |
| return 0; |
| return typedMem() - typedMemBase(); |
| } |
| |
| int32_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>().isNeutered(); |
| } |
| 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>().isNeutered()) |
| return false; |
| return true; |
| } |
| |
| bool |
| TypedObject::maybeForwardedIsAttached() const |
| { |
| if (is<InlineTypedObject>()) |
| return true; |
| if (!as<OutlineTypedObject>().outOfLineTypedMem()) |
| return false; |
| JSObject& owner = *MaybeForwarded(&as<OutlineTypedObject>().owner()); |
| if (owner.is<ArrayBufferObject>() && owner.as<ArrayBufferObject>().isNeutered()) |
| 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(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, int32_t offset) |
| { |
| MOZ_ASSERT(!isAttached()); |
| MOZ_ASSERT(offset >= 0); |
| MOZ_ASSERT((size_t) (offset + size()) <= buffer.byteLength()); |
| |
| // 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, int32_t offset) |
| { |
| MOZ_ASSERT(!isAttached()); |
| MOZ_ASSERT(typedObj.isAttached()); |
| |
| JSObject* owner = &typedObj; |
| if (typedObj.is<OutlineTypedObject>()) { |
| owner = &typedObj.as<OutlineTypedObject>().owner(); |
| offset += typedObj.offset(); |
| } |
| |
| if (owner->is<ArrayBufferObject>()) { |
| attach(cx, owner->as<ArrayBufferObject>(), offset); |
| } else { |
| MOZ_ASSERT(owner->is<InlineTypedObject>()); |
| setOwnerAndData(owner, owner->as<InlineTypedObject>().inlineTypedMem() + offset); |
| } |
| } |
| |
| // Returns a suitable JS_TYPEDOBJ_SLOT_LENGTH value for an instance of |
| // the type `type`. |
| static int32_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, int32_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 ((size_t) descr->size() <= InlineTypedObject::MaximumSize) { |
| InlineTypedObject* obj = InlineTypedObject::create(cx, descr, heap); |
| if (!obj) |
| return nullptr; |
| descr->initInstances(cx->runtime(), obj->inlineTypedMem(), 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 |
| char* typeReprStr = JS_EncodeString(cx, &obj->typeDescr().stringRepr()); |
| if (!typeReprStr) |
| return false; |
| |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| errorNumber, typeReprStr); |
| |
| JS_free(cx, (void*) typeReprStr); |
| 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; |
| |
| // When this is called for compacting GC, the related objects we touch here |
| // may not have had their slots updated yet. Note that this does not apply |
| // to generational GC because these objects (type descriptors and |
| // prototypes) are never allocated in the nursery. |
| TypeDescr& descr = *MaybeForwarded(&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. |
| 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.maybeForwardedIsAttached()) |
| 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->getProto()); |
| 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; |
| |
| char* propName = JS_EncodeString(cx, str); |
| if (!propName) |
| return false; |
| |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| errorNumber, propName); |
| |
| JS_free(cx, propName); |
| return false; |
| } |
| |
| bool |
| TypedObject::obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id, |
| Handle<JSPropertyDescriptor> 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->getProto()); |
| 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_ReportErrorNumber( |
| 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->getProto()); |
| 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->getProto()); |
| 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_ReportErrorNumber(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_ReportErrorNumber(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<JSPropertyDescriptor> desc) |
| { |
| Rooted<TypedObject*> typedObj(cx, &obj->as<TypedObject>()); |
| if (!typedObj->isAttached()) { |
| JS_ReportErrorNumber(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->getProto()); |
| 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 (int32_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::neuter(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) |
| { |
| 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; |
| |
| // When this is called for compacting GC, the related objects we touch here |
| // may not have had their slots updated yet. |
| TypeDescr& descr = *MaybeForwarded(&typedObj.typeDescr()); |
| |
| descr.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, |
| size_t(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 |
| */ |
| |
| #define DEFINE_TYPEDOBJ_CLASS(Name, Trace) \ |
| const Class Name::class_ = { \ |
| # Name, \ |
| Class::NON_NATIVE, \ |
| nullptr, /* addProperty */ \ |
| nullptr, /* delProperty */ \ |
| nullptr, /* getProperty */ \ |
| nullptr, /* setProperty */ \ |
| nullptr, /* enumerate */ \ |
| nullptr, /* resolve */ \ |
| nullptr, /* mayResolve */ \ |
| nullptr, /* finalize */ \ |
| nullptr, /* call */ \ |
| nullptr, /* hasInstance */ \ |
| nullptr, /* construct */ \ |
| Trace, \ |
| JS_NULL_CLASS_SPEC, \ |
| JS_NULL_CLASS_EXT, \ |
| { \ |
| 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_TYPEDOBJ_CLASS(OutlineTransparentTypedObject, OutlineTypedObject::obj_trace); |
| DEFINE_TYPEDOBJ_CLASS(OutlineOpaqueTypedObject, OutlineTypedObject::obj_trace); |
| DEFINE_TYPEDOBJ_CLASS(InlineTransparentTypedObject, InlineTypedObject::obj_trace); |
| DEFINE_TYPEDOBJ_CLASS(InlineOpaqueTypedObject, InlineTypedObject::obj_trace); |
| |
| 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(int32_t offset, |
| int32_t size, |
| int32_t alignment, |
| int32_t bufferLength) |
| { |
| MOZ_ASSERT(size >= 0); |
| MOZ_ASSERT(alignment >= 0); |
| |
| // No negative offsets. |
| if (offset < 0) |
| return false; |
| |
| // 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; |
| } |
| |
| /*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->isNeutered()) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, |
| nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS); |
| return false; |
| } |
| |
| int32_t offset; |
| if (args.length() >= 2 && !args[1].isUndefined()) { |
| if (!args[1].isInt32()) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, |
| nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS); |
| return false; |
| } |
| |
| offset = args[1].toInt32(); |
| } else { |
| offset = 0; |
| } |
| |
| if (args.length() >= 3 && !args[2].isUndefined()) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, |
| nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS); |
| return false; |
| } |
| |
| if (!CheckOffset(offset, callee->size(), callee->alignment(), |
| buffer->byteLength())) |
| { |
| JS_ReportErrorNumber(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_ReportErrorNumber(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>()); |
| int32_t offset = 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()); |
| size_t offset = 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.setData(typedObj.typedMemBase() + 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::GetFloat32x4TypeDescr(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| Rooted<GlobalObject*> global(cx, cx->global()); |
| MOZ_ASSERT(global); |
| args.rval().setObject(*global->getOrCreateSimdTypeDescr<Float32x4>(cx)); |
| return true; |
| } |
| |
| bool |
| js::GetFloat64x2TypeDescr(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| Rooted<GlobalObject*> global(cx, cx->global()); |
| MOZ_ASSERT(global); |
| args.rval().setObject(*global->getOrCreateSimdTypeDescr<Float64x2>(cx)); |
| return true; |
| } |
| |
| bool |
| js::GetInt8x16TypeDescr(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| Rooted<GlobalObject*> global(cx, cx->global()); |
| MOZ_ASSERT(global); |
| args.rval().setObject(*global->getOrCreateSimdTypeDescr<Int8x16>(cx)); |
| return true; |
| } |
| |
| bool |
| js::GetInt16x8TypeDescr(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| Rooted<GlobalObject*> global(cx, cx->global()); |
| MOZ_ASSERT(global); |
| args.rval().setObject(*global->getOrCreateSimdTypeDescr<Int16x8>(cx)); |
| return true; |
| } |
| |
| bool |
| js::GetInt32x4TypeDescr(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| Rooted<GlobalObject*> global(cx, cx->global()); |
| MOZ_ASSERT(global); |
| args.rval().setObject(*global->getOrCreateSimdTypeDescr<Int32x4>(cx)); |
| return true; |
| } |
| |
| #define JS_STORE_SCALAR_CLASS_IMPL(_constant, T, _name) \ |
| bool \ |
| js::StoreScalar##T::Func(JSContext*, 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); \ |
| \ |
| T* target = reinterpret_cast<T*>(typedObj.typedMem(offset)); \ |
| 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##T::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); \ |
| \ |
| T* target = reinterpret_cast<T*>(typedObj.typedMem(offset)); \ |
| 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*, 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); \ |
| \ |
| T* target = reinterpret_cast<T*>(typedObj.typedMem(offset)); \ |
| args.rval().setNumber((double) *target); \ |
| return true; \ |
| } |
| |
| #define JS_LOAD_REFERENCE_CLASS_IMPL(_constant, T, _name) \ |
| bool \ |
| js::LoadReference##T::Func(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()); \ |
| \ |
| 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); \ |
| \ |
| T* target = reinterpret_cast<T*>(typedObj.typedMem(offset)); \ |
| 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 |
| StoreReferenceHeapValue::store(JSContext* cx, HeapValue* 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 |
| StoreReferenceHeapPtrObject::store(JSContext* cx, HeapPtrObject* 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 |
| StoreReferenceHeapPtrString::store(JSContext* cx, HeapPtrString* 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 |
| LoadReferenceHeapValue::load(HeapValue* heap, |
| MutableHandleValue v) |
| { |
| v.set(*heap); |
| } |
| |
| void |
| LoadReferenceHeapPtrObject::load(HeapPtrObject* heap, |
| MutableHandleValue v) |
| { |
| if (*heap) |
| v.setObject(**heap); |
| else |
| v.setNull(); |
| } |
| |
| void |
| LoadReferenceHeapPtrString::load(HeapPtrString* 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 = *MaybeForwarded(&arrayDescr.elementType()); |
| for (int32_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.maybeForwardedFieldCount(); i++) { |
| TypeDescr& descr = structDescr.maybeForwardedFieldDescr(i); |
| size_t offset = structDescr.maybeForwardedFieldOffset(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::HeapValue* heapValue = reinterpret_cast<js::HeapValue*>(mem); |
| heapValue->init(UndefinedValue()); |
| return; |
| } |
| |
| case ReferenceTypeDescr::TYPE_OBJECT: |
| { |
| js::HeapPtrObject* objectPtr = |
| reinterpret_cast<js::HeapPtrObject*>(mem); |
| objectPtr->init(nullptr); |
| return; |
| } |
| |
| case ReferenceTypeDescr::TYPE_STRING: |
| { |
| js::HeapPtrString* stringPtr = |
| reinterpret_cast<js::HeapPtrString*>(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 >= |