blob: 2c15052d72bad779231e457a97646c64cad6d368 [file] [log] [blame]
/* -*- 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) \