Bug 987508 - Create array buffers lazily for small typed arrays, r=sfink.

This commit is contained in:
Brian Hackett 2014-04-07 13:04:37 -07:00
parent ded5e5b4b0
commit c273031592
13 changed files with 244 additions and 119 deletions

View File

@ -197,7 +197,7 @@ StealJSArrayDataIntoThreadSharedFloatArrayBufferList(JSContext* aJSContext,
new ThreadSharedFloatArrayBufferList(aJSArrays.Length());
for (uint32_t i = 0; i < aJSArrays.Length(); ++i) {
JS::Rooted<JSObject*> arrayBuffer(aJSContext,
JS_GetArrayBufferViewBuffer(aJSArrays[i]));
JS_GetArrayBufferViewBuffer(aJSContext, aJSArrays[i]));
uint8_t* stolenData = arrayBuffer
? (uint8_t*) JS_StealArrayBufferContents(aJSContext, arrayBuffer)
: nullptr;

View File

@ -30,6 +30,8 @@
#include "jsgcinlines.h"
#include "vm/ObjectImpl-inl.h"
using namespace js;
using namespace gc;
using namespace mozilla;
@ -365,6 +367,15 @@ GetObjectAllocKindForCopy(JSRuntime *rt, JSObject *obj)
if (obj->is<JSFunction>())
return obj->as<JSFunction>().getAllocKind();
/*
* Typed arrays in the nursery may have a lazily allocated buffer, make
* sure there is room for the array's fixed data when moving the array.
*/
if (obj->is<TypedArrayObject>() && !obj->as<TypedArrayObject>().buffer()) {
size_t nbytes = obj->as<TypedArrayObject>().byteLength();
return GetBackgroundAllocKind(TypedArrayObject::AllocKindForLazyBuffer(nbytes));
}
AllocKind kind = GetGCObjectFixedSlotsKind(obj->numFixedSlots());
JS_ASSERT(!IsBackgroundFinalized(kind));
JS_ASSERT(CanBeFinalizedInBackground(kind, obj->getClass()));
@ -561,6 +572,9 @@ js::Nursery::moveObjectToTenured(JSObject *dst, JSObject *src, AllocKind dstKind
tenuredSize += moveSlotsToTenured(dst, src, dstKind);
tenuredSize += moveElementsToTenured(dst, src, dstKind);
if (src->is<TypedArrayObject>())
dst->setPrivate(dst->fixedData(TypedArrayObject::FIXED_DATA_START));
/* The shape's list head may point into the old object. */
if (&src->shape_ == dst->shape_->listp)
dst->shape_->listp = &dst->shape_;

View File

@ -109,7 +109,7 @@ TestArrayFromBuffer(JSContext *cx)
CHECK_EQUAL(JS_GetTypedArrayLength(array), elts);
CHECK_EQUAL(JS_GetTypedArrayByteOffset(array), 0);
CHECK_EQUAL(JS_GetTypedArrayByteLength(array), nbytes);
CHECK_EQUAL(JS_GetArrayBufferViewBuffer(array), (JSObject*) buffer);
CHECK_EQUAL(JS_GetArrayBufferViewBuffer(cx, array), (JSObject*) buffer);
Element *data;
CHECK(data = GetData(array));

View File

@ -1432,7 +1432,7 @@ JS_GetArrayBufferViewData(JSObject *obj);
* object that would return true for JS_IsArrayBufferViewObject().
*/
extern JS_FRIEND_API(JSObject *)
JS_GetArrayBufferViewBuffer(JSObject *obj);
JS_GetArrayBufferViewBuffer(JSContext *cx, JSObject *obj);
/*
* Set an ArrayBuffer's length to 0 and neuter all of its views.

View File

@ -1253,17 +1253,12 @@ NewObject(ExclusiveContext *cx, types::TypeObject *type_, JSObject *parent, gc::
if (!NewObjectMetadata(cx, &metadata))
return nullptr;
// Normally, the number of fixed slots given an object is the maximum
// permitted for its size class. For array buffers we only use enough to
// cover the class reservd slots, so that the remaining space in the
// object's allocation is available for the buffer's data.
size_t nfixed;
if (clasp == &ArrayBufferObject::class_) {
JS_STATIC_ASSERT(ArrayBufferObject::RESERVED_SLOTS == 4);
nfixed = ArrayBufferObject::RESERVED_SLOTS;
} else {
nfixed = GetGCKindSlots(kind, clasp);
}
// For objects which can have fixed data following the object, only use
// enough fixed slots to cover the number of reserved slots in the object,
// regardless of the allocation kind specified.
size_t nfixed = ClassCanHaveFixedData(clasp)
? GetGCKindSlots(gc::GetGCObjectKind(clasp), clasp)
: GetGCKindSlots(kind, clasp);
RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, type->proto(),
parent, metadata, nfixed));

View File

@ -475,7 +475,8 @@ JSObject::setProto(JSContext *cx, JS::HandleObject obj, JS::HandleObject proto,
return SetClassAndProto(cx, obj, obj->getClass(), proto, succeeded);
}
inline bool JSObject::isVarObj()
inline bool
JSObject::isVarObj()
{
if (is<js::DebugScopeObject>())
return as<js::DebugScopeObject>().scope().isVarObj();
@ -495,7 +496,7 @@ JSObject::create(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::Initi
JS_ASSERT(shape && type);
JS_ASSERT(type->clasp() == shape->getObjectClass());
JS_ASSERT(type->clasp() != &js::ArrayObject::class_);
JS_ASSERT_IF(type->clasp() != &js::ArrayBufferObject::class_,
JS_ASSERT_IF(!ClassCanHaveFixedData(type->clasp()),
js::gc::GetGCKindSlots(kind, type->clasp()) == shape->numFixedSlots());
JS_ASSERT_IF(type->clasp()->flags & JSCLASS_BACKGROUND_FINALIZE, IsBackgroundFinalized(kind));
JS_ASSERT_IF(type->clasp()->finalize, heap == js::gc::TenuredHeap);

View File

@ -631,7 +631,7 @@ ArrayBufferObject::create(JSContext *cx, uint32_t nbytes, void *data /* = nullpt
if (data) {
obj->initialize(nbytes, data, OwnsData);
} else {
void *data = &obj->fixedSlots()[reservedSlots];
void *data = obj->fixedData(reservedSlots);
memset(data, 0, nbytes);
obj->initialize(nbytes, data, DoesntOwnData);
}
@ -929,6 +929,17 @@ ArrayBufferViewObject::neuter(void *newData)
as<TypedObject>().neuter(newData);
}
/* static */ ArrayBufferObject *
ArrayBufferViewObject::bufferObject(JSContext *cx, Handle<ArrayBufferViewObject *> thisObject)
{
if (thisObject->is<TypedArrayObject>()) {
Rooted<TypedArrayObject *> typedArray(cx, &thisObject->as<TypedArrayObject>());
if (!TypedArrayObject::ensureHasBuffer(cx, typedArray))
return nullptr;
}
return &thisObject->getFixedSlot(BUFFER_SLOT).toObject().as<ArrayBufferObject>();
}
/* JS Friend API */
JS_FRIEND_API(bool)
@ -1047,12 +1058,13 @@ JS_GetArrayBufferViewData(JSObject *obj)
}
JS_FRIEND_API(JSObject *)
JS_GetArrayBufferViewBuffer(JSObject *obj)
JS_GetArrayBufferViewBuffer(JSContext *cx, JSObject *obj)
{
obj = CheckedUnwrap(obj);
if (!obj)
return nullptr;
return obj->as<ArrayBufferViewObject>().bufferObject();
Rooted<ArrayBufferViewObject *> viewObject(cx, &obj->as<ArrayBufferViewObject>());
return ArrayBufferViewObject::bufferObject(cx, viewObject);
}
JS_FRIEND_API(uint32_t)

View File

@ -240,9 +240,7 @@ class ArrayBufferViewObject : public JSObject
static const size_t NEXT_VIEW_SLOT = JS_TYPEDOBJ_SLOT_NEXT_VIEW;
public:
JSObject *bufferObject() const {
return &getFixedSlot(BUFFER_SLOT).toObject();
}
static ArrayBufferObject *bufferObject(JSContext *cx, Handle<ArrayBufferViewObject *> obj);
ArrayBufferViewObject *nextView() const {
return static_cast<ArrayBufferViewObject*>(getFixedSlot(NEXT_VIEW_SLOT).toPrivate());

View File

@ -13,9 +13,12 @@
#include "jsproxy.h"
#include "vm/ProxyObject.h"
#include "vm/TypedArrayObject.h"
namespace js {
/* static */ inline bool
js::ObjectImpl::isExtensible(ExclusiveContext *cx, js::Handle<ObjectImpl*> obj, bool *extensible)
ObjectImpl::isExtensible(ExclusiveContext *cx, Handle<ObjectImpl*> obj, bool *extensible)
{
if (obj->asObjectPtr()->is<ProxyObject>()) {
if (!cx->shouldBeJSContext())
@ -29,4 +32,24 @@ js::ObjectImpl::isExtensible(ExclusiveContext *cx, js::Handle<ObjectImpl*> obj,
return true;
}
inline bool
ClassCanHaveFixedData(const Class *clasp)
{
// Normally, the number of fixed slots given an object is the maximum
// permitted for its size class. For array buffers and typed arrays we only
// use enough to cover the class reserved slots, so that the remaining
// space in the object's allocation is available for the buffer's data.
return clasp == &ArrayBufferObject::class_ || IsTypedArrayClass(clasp);
}
inline void *
ObjectImpl::fixedData(size_t nslots) const
{
JS_ASSERT(ClassCanHaveFixedData(getClass()));
JS_ASSERT(nslots == numFixedSlots() + (hasPrivate() ? 1 : 0));
return &fixedSlots()[nslots];
}
} // namespace js
#endif /* vm_ObjectImpl_inl_h */

View File

@ -94,13 +94,12 @@ ArraySetLength(typename ExecutionModeTraits<mode>::ContextType cx,
unsigned attrs, HandleValue value, bool setterIsStrict);
/*
* Elements header used for all objects other than non-native objects (except
* for ArrayBufferObjects!!!) and typed arrays. The elements component of such
* objects offers an efficient representation for all or some of the indexed
* properties of the object, using a flat array of Values rather than a shape
* hierarchy stored in the object's slots. This structure is immediately
* followed by an array of elements, with the elements member in an object
* pointing to the beginning of that array (the end of this structure).
* Elements header used for all objects. The elements component of such objects
* offers an efficient representation for all or some of the indexed properties
* of the object, using a flat array of Values rather than a shape hierarchy
* stored in the object's slots. This structure is immediately followed by an
* array of elements, with the elements member in an object pointing to the
* beginning of that array (the end of this structure).
* See below for usage of this structure.
*
* The sets of properties represented by an object's elements and slots
@ -149,14 +148,14 @@ ArraySetLength(typename ExecutionModeTraits<mode>::ContextType cx,
* value less than or equal to both the object's length and the object's
* capacity.
*
* With inference enabled, there is flexibility in exactly the value the
* initialized length must hold, e.g. if an array has length 5, capacity 10,
* completely empty, it is valid for the initialized length to be any value
* between zero and 5, as long as the in memory values below the initialized
* length have been initialized with a hole value. However, in such cases we
* want to keep the initialized length as small as possible: if the object is
* known to have no hole values below its initialized length, then it is
* "packed" and can be accessed much faster by JIT code.
* There is flexibility in exactly the value the initialized length must hold,
* e.g. if an array has length 5, capacity 10, completely empty, it is valid
* for the initialized length to be any value between zero and 5, as long as
* the in memory values below the initialized length have been initialized with
* a hole value. However, in such cases we want to keep the initialized length
* as small as possible: if the object is known to have no hole values below
* its initialized length, then it is "packed" and can be accessed much faster
* by JIT code.
*
* Elements do not track property creation order, so enumerating the elements
* of an object does not necessarily visit indexes in the order they were
@ -288,7 +287,7 @@ IsObjectValueInCompartment(js::Value v, JSCompartment *comp);
* variable-sized array of values for inline storage, which may be used by
* either properties of native objects (fixed slots), by elements (fixed
* elements), or by other data for certain kinds of objects, such as
* ArrayBufferObjects.
* ArrayBufferObjects and TypedArrayObjects.
*
* Two native objects with the same shape are guaranteed to have the same
* number of fixed slots.
@ -827,6 +826,13 @@ class ObjectImpl : public gc::BarrieredCell<ObjectImpl>
return elements == emptyObjectElements;
}
/*
* Get a pointer to the unused data in the object's allocation immediately
* following this object, for use with objects which allocate a larger size
* class than they need and store non-elements data inline.
*/
inline void *fixedData(size_t nslots) const;
/* GC support. */
static ThingRootKind rootKind() { return THING_ROOT_OBJECT; }

View File

@ -813,6 +813,10 @@ bool
JSStructuredCloneWriter::writeTypedArray(HandleObject obj)
{
Rooted<TypedArrayObject*> tarr(context(), &obj->as<TypedArrayObject>());
if (!TypedArrayObject::ensureHasBuffer(context(), tarr))
return false;
if (!out.writePair(SCTAG_TYPED_ARRAY_OBJECT, tarr->length()))
return false;
uint64_t type = tarr->type();

View File

@ -106,6 +106,25 @@ TypedArrayObject::sharedBuffer() const
return &bufferValue(const_cast<TypedArrayObject*>(this)).toObject().as<SharedArrayBufferObject>();
}
/* static */ bool
TypedArrayObject::ensureHasBuffer(JSContext *cx, Handle<TypedArrayObject *> tarray)
{
if (tarray->buffer())
return true;
Rooted<ArrayBufferObject *> buffer(cx, ArrayBufferObject::create(cx, tarray->byteLength()));
if (!buffer)
return false;
buffer->addView(tarray);
memcpy(buffer->dataPointer(), tarray->viewData(), tarray->byteLength());
InitArrayBufferViewDataPointer(tarray, buffer, 0);
tarray->setSlot(BUFFER_SLOT, ObjectValue(*buffer));
return true;
}
/* static */ int
TypedArrayObject::lengthOffset()
{
@ -187,13 +206,13 @@ class TypedArrayObjectTemplate : public TypedArrayObject
return &TypedArrayObject::protoClasses[ArrayTypeID()];
}
static inline const Class *fastClass()
static inline const Class *instanceClass()
{
return &TypedArrayObject::classes[ArrayTypeID()];
}
static bool is(HandleValue v) {
return v.isObject() && v.toObject().hasClass(fastClass());
return v.isObject() && v.toObject().hasClass(instanceClass());
}
static void
@ -222,11 +241,11 @@ class TypedArrayObjectTemplate : public TypedArrayObject
}
static TypedArrayObject *
makeProtoInstance(JSContext *cx, HandleObject proto)
makeProtoInstance(JSContext *cx, HandleObject proto, AllocKind allocKind)
{
JS_ASSERT(proto);
RootedObject obj(cx, NewBuiltinClassInstance(cx, fastClass()));
RootedObject obj(cx, NewBuiltinClassInstance(cx, instanceClass(), allocKind));
if (!obj)
return nullptr;
@ -239,19 +258,19 @@ class TypedArrayObjectTemplate : public TypedArrayObject
}
static TypedArrayObject *
makeTypedInstance(JSContext *cx, uint32_t len)
makeTypedInstance(JSContext *cx, uint32_t len, AllocKind allocKind)
{
if (len * sizeof(NativeType) >= TypedArrayObject::SINGLETON_TYPE_BYTE_LENGTH) {
return &NewBuiltinClassInstance(cx, fastClass(),
return &NewBuiltinClassInstance(cx, instanceClass(), allocKind,
SingletonObject)->as<TypedArrayObject>();
}
jsbytecode *pc;
RootedScript script(cx, cx->currentScript(&pc));
NewObjectKind newKind = script
? UseNewTypeForInitializer(script, pc, fastClass())
? UseNewTypeForInitializer(script, pc, instanceClass())
: GenericObject;
RootedObject obj(cx, NewBuiltinClassInstance(cx, fastClass(), newKind));
RootedObject obj(cx, NewBuiltinClassInstance(cx, instanceClass(), allocKind, newKind));
if (!obj)
return nullptr;
@ -264,56 +283,61 @@ class TypedArrayObjectTemplate : public TypedArrayObject
}
static JSObject *
makeInstance(JSContext *cx, HandleObject bufobj, uint32_t byteOffset, uint32_t len,
makeInstance(JSContext *cx, Handle<ArrayBufferObject *> buffer, uint32_t byteOffset, uint32_t len,
HandleObject proto)
{
JS_ASSERT_IF(!buffer, byteOffset == 0);
gc::AllocKind allocKind = buffer
? GetGCObjectKind(instanceClass())
: AllocKindForLazyBuffer(len * sizeof(NativeType));
Rooted<TypedArrayObject*> obj(cx);
if (proto)
obj = makeProtoInstance(cx, proto);
obj = makeProtoInstance(cx, proto, allocKind);
else
obj = makeTypedInstance(cx, len);
obj = makeTypedInstance(cx, len, allocKind);
if (!obj)
return nullptr;
JS_ASSERT_IF(obj->isTenured(),
obj->tenuredGetAllocKind() == gc::FINALIZE_OBJECT8_BACKGROUND);
obj->setSlot(TYPE_SLOT, Int32Value(ArrayTypeID()));
obj->setSlot(BUFFER_SLOT, ObjectValue(*bufobj));
obj->setSlot(BUFFER_SLOT, ObjectOrNullValue(buffer));
Rooted<ArrayBufferObject *> buffer(cx, &AsArrayBuffer(bufobj));
if (buffer) {
InitArrayBufferViewDataPointer(obj, buffer, byteOffset);
} else {
void *data = obj->fixedData(FIXED_DATA_START);
obj->initPrivate(data);
memset(data, 0, len * sizeof(NativeType));
}
InitArrayBufferViewDataPointer(obj, buffer, byteOffset);
obj->setSlot(LENGTH_SLOT, Int32Value(len));
obj->setSlot(BYTEOFFSET_SLOT, Int32Value(byteOffset));
obj->setSlot(BYTELENGTH_SLOT, Int32Value(len * sizeof(NativeType)));
obj->setSlot(NEXT_VIEW_SLOT, PrivateValue(nullptr));
js::Shape *empty = EmptyShape::getInitialShape(cx, fastClass(),
obj->getProto(), obj->getParent(), obj->getMetadata(),
gc::FINALIZE_OBJECT8_BACKGROUND);
if (!empty)
return nullptr;
obj->setLastPropertyInfallible(empty);
#ifdef DEBUG
uint32_t bufferByteLength = buffer->byteLength();
uint32_t arrayByteLength = obj->byteLength();
uint32_t arrayByteOffset = obj->byteOffset();
JS_ASSERT_IF(!buffer->isNeutered(), buffer->dataPointer() <= obj->viewData());
JS_ASSERT(bufferByteLength - arrayByteOffset >= arrayByteLength);
JS_ASSERT(arrayByteOffset <= bufferByteLength);
if (buffer) {
uint32_t arrayByteLength = obj->byteLength();
uint32_t arrayByteOffset = obj->byteOffset();
uint32_t bufferByteLength = buffer->byteLength();
JS_ASSERT_IF(!buffer->isNeutered(), buffer->dataPointer() <= obj->viewData());
JS_ASSERT(bufferByteLength - arrayByteOffset >= arrayByteLength);
JS_ASSERT(arrayByteOffset <= bufferByteLength);
}
// Verify that the private slot is at the expected place
JS_ASSERT(obj->numFixedSlots() == DATA_SLOT);
#endif
buffer->addView(obj);
if (buffer)
buffer->addView(obj);
return obj;
}
static JSObject *
makeInstance(JSContext *cx, HandleObject bufobj, uint32_t byteOffset, uint32_t len)
makeInstance(JSContext *cx, Handle<ArrayBufferObject *> bufobj, uint32_t byteOffset, uint32_t len)
{
RootedObject nullproto(cx, nullptr);
return makeInstance(cx, bufobj, byteOffset, len, nullproto);
@ -328,7 +352,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject
static bool
class_constructor(JSContext *cx, unsigned argc, Value *vp)
{
/* N.B. this is a constructor for protoClass, not fastClass! */
/* N.B. this is a constructor for protoClass, not instanceClass! */
CallArgs args = CallArgsFromVp(argc, vp);
JSObject *obj = create(cx, args);
if (!obj)
@ -396,7 +420,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject
}
static bool IsThisClass(HandleValue v) {
return v.isObject() && v.toObject().hasClass(fastClass());
return v.isObject() && v.toObject().hasClass(instanceClass());
}
template<Value ValueGetter(TypedArrayObject *tarr)>
@ -420,16 +444,36 @@ class TypedArrayObjectTemplate : public TypedArrayObject
ThisTypedArrayObject::GetterImpl<ValueGetter> >(cx, args);
}
// Define an accessor for a read-only property that invokes a native getter
template<Value ValueGetter(TypedArrayObject *tarr)>
static bool
DefineGetter(JSContext *cx, PropertyName *name, HandleObject proto)
BufferGetterImpl(JSContext *cx, CallArgs args)
{
JS_ASSERT(IsThisClass(args.thisv()));
Rooted<TypedArrayObject *> tarray(cx, &args.thisv().toObject().as<TypedArrayObject>());
if (!ensureHasBuffer(cx, tarray))
return false;
args.rval().set(bufferValue(tarray));
return true;
}
// BufferGetter is a function that lazily constructs the array buffer for a
// typed array before fetching it.
static bool
BufferGetter(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<ThisTypedArrayObject::IsThisClass,
ThisTypedArrayObject::BufferGetterImpl>(cx, args);
}
// Define an accessor for a read-only property that invokes a native getter
static bool
DefineGetter(JSContext *cx, HandleObject proto, PropertyName *name, Native native)
{
RootedId id(cx, NameToId(name));
unsigned flags = JSPROP_SHARED | JSPROP_GETTER | JSPROP_PERMANENT;
Rooted<GlobalObject*> global(cx, cx->compartment()->maybeGlobal());
JSObject *getter = NewFunction(cx, NullPtr(), Getter<ValueGetter>, 0,
JSObject *getter = NewFunction(cx, NullPtr(), native, 0,
JSFunction::NATIVE_FUN, global, NullPtr());
if (!getter)
return false;
@ -442,16 +486,16 @@ class TypedArrayObjectTemplate : public TypedArrayObject
static
bool defineGetters(JSContext *cx, HandleObject proto)
{
if (!DefineGetter<lengthValue>(cx, cx->names().length, proto))
if (!DefineGetter(cx, proto, cx->names().length, Getter<lengthValue>))
return false;
if (!DefineGetter<bufferValue>(cx, cx->names().buffer, proto))
if (!DefineGetter(cx, proto, cx->names().buffer, BufferGetter))
return false;
if (!DefineGetter<byteLengthValue>(cx, cx->names().byteLength, proto))
if (!DefineGetter(cx, proto, cx->names().byteLength, Getter<byteLengthValue>))
return false;
if (!DefineGetter<byteOffsetValue>(cx, cx->names().byteOffset, proto))
if (!DefineGetter(cx, proto, cx->names().byteOffset, Getter<byteOffsetValue>))
return false;
return true;
@ -671,7 +715,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject
*/
Rooted<JSObject*> proto(cx);
if (!GetBuiltinPrototype(cx, JSCLASS_CACHED_PROTO_KEY(fastClass()), &proto))
if (!GetBuiltinPrototype(cx, JSCLASS_CACHED_PROTO_KEY(instanceClass()), &proto))
return nullptr;
InvokeArgs args(cx);
@ -695,17 +739,17 @@ class TypedArrayObjectTemplate : public TypedArrayObject
return nullptr; // must be arrayBuffer
}
ArrayBufferObject &buffer = AsArrayBuffer(bufobj);
Rooted<ArrayBufferObject *> buffer(cx, &AsArrayBuffer(bufobj));
if (byteOffset > buffer.byteLength() || byteOffset % sizeof(NativeType) != 0) {
if (byteOffset > buffer->byteLength() || byteOffset % sizeof(NativeType) != 0) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
return nullptr; // invalid byteOffset
}
uint32_t len;
if (lengthInt == -1) {
len = (buffer.byteLength() - byteOffset) / sizeof(NativeType);
if (len * sizeof(NativeType) != buffer.byteLength() - byteOffset) {
len = (buffer->byteLength() - byteOffset) / sizeof(NativeType);
if (len * sizeof(NativeType) != buffer->byteLength() - byteOffset) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_BAD_ARGS);
return nullptr; // given byte array doesn't map exactly to sizeof(NativeType) * N
@ -721,19 +765,41 @@ class TypedArrayObjectTemplate : public TypedArrayObject
return nullptr; // overflow when calculating byteOffset + len * sizeof(NativeType)
}
if (arrayByteLength + byteOffset > buffer.byteLength()) {
if (arrayByteLength + byteOffset > buffer->byteLength()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
return nullptr; // byteOffset + len is too big for the arraybuffer
}
return makeInstance(cx, bufobj, byteOffset, len, proto);
return makeInstance(cx, buffer, byteOffset, len, proto);
}
static bool
maybeCreateArrayBuffer(JSContext *cx, uint32_t nelements, MutableHandle<ArrayBufferObject *> buffer)
{
// Make sure that array elements evenly divide into the inline buffer's
// size, for the test below.
JS_STATIC_ASSERT((INLINE_BUFFER_LIMIT / sizeof(NativeType)) * sizeof(NativeType) == INLINE_BUFFER_LIMIT);
if (nelements <= INLINE_BUFFER_LIMIT / sizeof(NativeType)) {
// The array's data can be inline, and the buffer created lazily.
return true;
}
if (nelements >= INT32_MAX / sizeof(NativeType)) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
JSMSG_NEED_DIET, "size and count");
return false;
}
buffer.set(ArrayBufferObject::create(cx, nelements * sizeof(NativeType)));
return !!buffer;
}
static JSObject *
fromLength(JSContext *cx, uint32_t nelements)
{
RootedObject buffer(cx, createBufferWithSizeAndCount(cx, nelements));
if (!buffer)
Rooted<ArrayBufferObject *> buffer(cx);
if (!maybeCreateArrayBuffer(cx, nelements, &buffer))
return nullptr;
return makeInstance(cx, buffer, 0, nelements);
}
@ -748,11 +814,11 @@ class TypedArrayObjectTemplate : public TypedArrayObject
return nullptr;
}
RootedObject bufobj(cx, createBufferWithSizeAndCount(cx, len));
if (!bufobj)
Rooted<ArrayBufferObject *> buffer(cx);
if (!maybeCreateArrayBuffer(cx, len, &buffer))
return nullptr;
RootedObject obj(cx, makeInstance(cx, bufobj, 0, len));
RootedObject obj(cx, makeInstance(cx, buffer, 0, len));
if (!obj || !copyFromArray(cx, obj, other, len))
return nullptr;
return obj;
@ -781,7 +847,10 @@ class TypedArrayObjectTemplate : public TypedArrayObject
JS_ASSERT(begin <= tarray->length());
JS_ASSERT(end <= tarray->length());
RootedObject bufobj(cx, tarray->buffer());
if (!ensureHasBuffer(cx, tarray))
return nullptr;
Rooted<ArrayBufferObject *> bufobj(cx, tarray->buffer());
JS_ASSERT(bufobj);
JS_ASSERT(begin <= end);
@ -1065,20 +1134,6 @@ class TypedArrayObjectTemplate : public TypedArrayObject
js_free(srcbuf);
return true;
}
static JSObject *
createBufferWithSizeAndCount(JSContext *cx, uint32_t count)
{
size_t size = sizeof(NativeType);
if (size != 0 && count >= INT32_MAX / size) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
JSMSG_NEED_DIET, "size and count");
return nullptr;
}
uint32_t bytelen = size * count;
return ArrayBufferObject::create(cx, bytelen);
}
};
class Int8ArrayObject : public TypedArrayObjectTemplate<int8_t> {

View File

@ -40,6 +40,22 @@ class TypedArrayObject : public ArrayBufferViewObject
static const Class classes[ScalarTypeDescr::TYPE_MAX];
static const Class protoClasses[ScalarTypeDescr::TYPE_MAX];
static const size_t FIXED_DATA_START = DATA_SLOT + 1;
// For typed arrays which can store their data inline, the array buffer
// object is created lazily.
static const uint32_t INLINE_BUFFER_LIMIT =
(JSObject::MAX_FIXED_SLOTS - FIXED_DATA_START) * sizeof(Value);
static gc::AllocKind
AllocKindForLazyBuffer(size_t nbytes)
{
JS_ASSERT(nbytes <= INLINE_BUFFER_LIMIT);
int dataSlots = (nbytes - 1) / sizeof(Value) + 1;
JS_ASSERT(int(nbytes) <= dataSlots * int(sizeof(Value)));
return gc::GetGCObjectKind(FIXED_DATA_START + dataSlots);
}
static Value bufferValue(TypedArrayObject *tarr) {
return tarr->getFixedSlot(BUFFER_SLOT);
}
@ -53,11 +69,16 @@ class TypedArrayObject : public ArrayBufferViewObject
return tarr->getFixedSlot(LENGTH_SLOT);
}
static bool
ensureHasBuffer(JSContext *cx, Handle<TypedArrayObject *> tarray);
ArrayBufferObject *sharedBuffer() const;
ArrayBufferObject *buffer() const {
JSObject &obj = bufferValue(const_cast<TypedArrayObject*>(this)).toObject();
if (obj.is<ArrayBufferObject>())
return &obj.as<ArrayBufferObject>();
JSObject *obj = bufferValue(const_cast<TypedArrayObject*>(this)).toObjectOrNull();
if (!obj)
return nullptr;
if (obj->is<ArrayBufferObject>())
return &obj->as<ArrayBufferObject>();
return sharedBuffer();
}
uint32_t byteOffset() const {
@ -228,6 +249,10 @@ class DataViewObject : public ArrayBufferViewObject
return v;
}
static Value bufferValue(DataViewObject *view) {
return view->getReservedSlot(BUFFER_SLOT);
}
uint32_t byteOffset() const {
return byteOffsetValue(const_cast<DataViewObject*>(this)).toInt32();
}
@ -236,16 +261,8 @@ class DataViewObject : public ArrayBufferViewObject
return byteLengthValue(const_cast<DataViewObject*>(this)).toInt32();
}
bool hasBuffer() const {
return getReservedSlot(BUFFER_SLOT).isObject();
}
ArrayBufferObject &arrayBuffer() const {
return getReservedSlot(BUFFER_SLOT).toObject().as<ArrayBufferObject>();
}
static Value bufferValue(DataViewObject *view) {
return view->hasBuffer() ? ObjectValue(view->arrayBuffer()) : UndefinedValue();
return bufferValue(const_cast<DataViewObject*>(this)).toObject().as<ArrayBufferObject>();
}
void *dataPointer() const {