☠☠ backed out by 8b87a6adad14 ☠ ☠ | |
author | Brian Hackett <bhackett1024@gmail.com> |
Mon, 07 Apr 2014 11:46:54 -0700 | |
changeset 195818 | e35851f07b6703bee6830b4ebcd2990f41629238 |
parent 195817 | 14b5fbfa2163b634fcdad1249441e0fb789dc0ef |
child 195819 | aff859f63f703e6e450e81f539c8c70e18bdfa4f |
push id | 3624 |
push user | asasaki@mozilla.com |
push date | Mon, 09 Jun 2014 21:49:01 +0000 |
treeherder | mozilla-beta@b1a5da15899a [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | sfink |
bugs | 987508 |
milestone | 31.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/content/media/webaudio/AudioBuffer.cpp +++ b/content/media/webaudio/AudioBuffer.cpp @@ -192,17 +192,17 @@ AudioBuffer::GetChannelData(JSContext* a static already_AddRefed<ThreadSharedFloatArrayBufferList> StealJSArrayDataIntoThreadSharedFloatArrayBufferList(JSContext* aJSContext, const nsTArray<JSObject*>& aJSArrays) { nsRefPtr<ThreadSharedFloatArrayBufferList> result = 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; if (stolenData) { result->SetData(i, stolenData, reinterpret_cast<float*>(stolenData)); } else { return nullptr; }
--- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -360,16 +360,25 @@ GetObjectAllocKindForCopy(JSRuntime *rt, size_t nelements = obj->getDenseCapacity(); return GetBackgroundAllocKind(GetGCArrayKind(nelements)); } 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())); return GetBackgroundAllocKind(kind); } void * js::Nursery::allocateFromTenured(Zone *zone, AllocKind thingKind) @@ -556,16 +565,19 @@ js::Nursery::moveObjectToTenured(JSObjec */ if (src->is<ArrayObject>()) srcSize = sizeof(ObjectImpl); js_memcpy(dst, src, srcSize); 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_; return tenuredSize; } size_t
--- a/js/src/jsapi-tests/testTypedArrays.cpp +++ b/js/src/jsapi-tests/testTypedArrays.cpp @@ -104,17 +104,17 @@ TestArrayFromBuffer(JSContext *cx) RootedObject notArray(cx, CreateWithBuffer(cx, buffer, UINT32_MAX, -1)); CHECK(!notArray); } RootedObject array(cx, CreateWithBuffer(cx, buffer, 0, -1)); 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)); CHECK(bufdata = JS_GetStableArrayBufferData(cx, buffer)); CHECK_EQUAL((void*) data, (void*) bufdata); CHECK_EQUAL(*bufdata, 1); CHECK_EQUAL(*reinterpret_cast<uint8_t*>(data), 1);
--- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -1427,17 +1427,17 @@ extern JS_FRIEND_API(void *) JS_GetArrayBufferViewData(JSObject *obj); /* * Return the ArrayBuffer underlying an ArrayBufferView. If the buffer has been * neutered, this will still return the neutered buffer. |obj| must be an * 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. */ extern JS_FRIEND_API(bool) JS_NeuterArrayBuffer(JSContext *cx, JS::HandleObject obj); /*
--- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -1248,27 +1248,22 @@ NewObject(ExclusiveContext *cx, types::T JS_ASSERT_IF(parent, &parent->global() == cx->global()); RootedTypeObject type(cx, type_); JSObject *metadata = nullptr; 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)); if (!shape) return nullptr; gc::InitialHeap heap = GetInitialHeap(newKind, clasp); JSObject *obj = JSObject::create(cx, kind, heap, shape, type);
--- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -470,17 +470,18 @@ JSObject::setProto(JSContext *cx, JS::Ha if (!JSObject::getProto(cx, obj2, &obj2)) return false; } 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(); return lastProperty()->hasObjectFlag(js::BaseShape::VAROBJ); } /* static */ inline JSObject * JSObject::create(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::InitialHeap heap, @@ -490,17 +491,17 @@ JSObject::create(js::ExclusiveContext *c /* * Callers must use dynamicSlotsCount to size the initial slot array of the * object. We can't check the allocated capacity of the dynamic slots, but * make sure their presence is consistent with the shape. */ 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); JS_ASSERT_IF(extantSlots, dynamicSlotsCount(shape->numFixedSlots(), shape->slotSpan(), type->clasp())); const js::Class *clasp = type->clasp(); size_t nDynamicSlots = 0;
--- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -626,17 +626,17 @@ ArrayBufferObject::create(JSContext *cx, } JS_ASSERT(obj->getClass() == &class_); JS_ASSERT(!gc::IsInsideNursery(cx->runtime(), obj)); 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); } return obj; } JSObject * @@ -924,16 +924,27 @@ ArrayBufferViewObject::neuter(void *newD if (is<DataViewObject>()) as<DataViewObject>().neuter(newData); else if (is<TypedArrayObject>()) as<TypedArrayObject>().neuter(newData); else 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) JS_IsArrayBufferViewObject(JSObject *obj) { obj = CheckedUnwrap(obj); return obj ? obj->is<ArrayBufferViewObject>() : false; } @@ -1042,22 +1053,23 @@ JS_GetArrayBufferViewData(JSObject *obj) obj = CheckedUnwrap(obj); if (!obj) return nullptr; return obj->is<DataViewObject>() ? obj->as<DataViewObject>().dataPointer() : obj->as<TypedArrayObject>().viewData(); } 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) JS_GetArrayBufferViewByteLength(JSObject *obj) { obj = CheckedUnwrap(obj); if (!obj) return 0;
--- a/js/src/vm/ArrayBufferObject.h +++ b/js/src/vm/ArrayBufferObject.h @@ -235,19 +235,17 @@ class ArrayBufferViewObject : public JSO /* Underlying ArrayBufferObject */ static const size_t BUFFER_SLOT = JS_TYPEDOBJ_SLOT_OWNER; /* ArrayBufferObjects point to a linked list of views, chained through this slot */ 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()); } inline void setNextView(ArrayBufferViewObject *view); void neuter(void *newData);
--- a/js/src/vm/ObjectImpl-inl.h +++ b/js/src/vm/ObjectImpl-inl.h @@ -8,25 +8,48 @@ #define vm_ObjectImpl_inl_h #include "vm/ObjectImpl.h" #include "jscntxt.h" #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()) return false; HandleObject h = HandleObject::fromMarkedLocation(reinterpret_cast<JSObject* const*>(obj.address())); return Proxy::isExtensible(cx->asJSContext(), h, extensible); } *extensible = obj->nonProxyIsExtensible(); 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 */
--- a/js/src/vm/ObjectImpl.h +++ b/js/src/vm/ObjectImpl.h @@ -89,23 +89,22 @@ class ArrayObject; */ template <ExecutionMode mode> extern bool ArraySetLength(typename ExecutionModeTraits<mode>::ContextType cx, Handle<ArrayObject*> obj, HandleId id, 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 * are disjoint. The elements contain only indexed properties, while the slots * can contain both named and indexed properties; any indexes in the slots are * distinct from those in the elements. If isIndexed() is false for an object, * all indexed properties (if any) are stored in the dense elements. * @@ -144,24 +143,24 @@ ArraySetLength(typename ExecutionModeTra * * The initialized length of an object specifies the number of elements that * have been initialized. All elements above the initialized length are * holes in the object, and the memory for all elements between the initialized * length and capacity is left uninitialized. The initialized length is some * 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 * created. */ class ObjectElements { public: @@ -283,17 +282,17 @@ IsObjectValueInCompartment(js::Value v, * The |type_| member stores the type of the object, which contains its * prototype object and the possible types of its properties. * * The rest of the object stores its named properties and indexed elements. * These are stored separately from one another. Objects are followed by a * 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. * * Named property storage can be split between fixed slots and a dynamically * allocated array (the slots member). For an object with N fixed slots, shapes * with slots [0..N-1] are stored in the fixed slots, and the remainder are * stored in the dynamic array. If all properties fit in the fixed slots, the @@ -822,16 +821,23 @@ class ObjectImpl : public gc::BarrieredC inline bool hasFixedElements() const { return elements == fixedElements(); } inline bool hasEmptyElements() const { 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; } inline void privateWriteBarrierPre(void **oldval); void privateWriteBarrierPost(void **pprivate) { #ifdef JSGC_GENERATIONAL shadowRuntimeFromAnyThread()->gcStoreBufferPtr()->putCell(reinterpret_cast<js::gc::Cell **>(pprivate));
--- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -808,16 +808,20 @@ JSStructuredCloneWriter::checkStack() * Int16Array views of the same ArrayBuffer, should the data bytes be * byte-swapped when writing or not? The Int8Array requires them to not be * swapped; the Int16Array requires that they are. */ 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(); if (!out.write(type)) return false; // Write out the ArrayBuffer tag and contents RootedValue val(context(), TypedArrayObject::bufferValue(tarr));
--- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -101,16 +101,35 @@ TypedArrayObject::neuter(void *newData) } ArrayBufferObject * 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() { return JSObject::getFixedSlotOffset(LENGTH_SLOT); } /* static */ int TypedArrayObject::dataOffset() @@ -182,23 +201,23 @@ class TypedArrayObjectTemplate : public static const size_t BYTES_PER_ELEMENT = sizeof(ThisType); static inline const Class *protoClass() { 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 setIndexValue(TypedArrayObject &tarray, uint32_t index, double d) { // If the array is an integer array, we only handle up to // 32-bit ints from this point on. if we want to handle // 64-bit ints, we'll need some changes. @@ -217,123 +236,128 @@ class TypedArrayObjectTemplate : public } else { JS_ASSERT(sizeof(NativeType) <= 4); int32_t n = ToInt32(d); setIndex(tarray, index, NativeType(n)); } } 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; types::TypeObject *type = cx->getNewType(obj->getClass(), proto.get()); if (!type) return nullptr; obj->setType(type); return &obj->as<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; if (script) { if (!types::SetInitializerObjectType(cx, script, pc, obj, newKind)) return nullptr; } return &obj->as<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)); - - Rooted<ArrayBufferObject *> buffer(cx, &AsArrayBuffer(bufobj)); - - InitArrayBufferViewDataPointer(obj, buffer, byteOffset); + obj->setSlot(BUFFER_SLOT, ObjectOrNullValue(buffer)); + + if (buffer) { + InitArrayBufferViewDataPointer(obj, buffer, byteOffset); + } else { + void *data = obj->fixedData(FIXED_DATA_START); + obj->initPrivate(data); + memset(data, 0, len * sizeof(NativeType)); + } + 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); } /* * new [Type]Array(length) * new [Type]Array(otherTypedArray) * new [Type]Array(JSArray) * new [Type]Array(ArrayBuffer, [optional] byteOffset, [optional] length) */ 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) return false; args.rval().setObject(*obj); return true; } @@ -391,17 +415,17 @@ class TypedArrayObjectTemplate : public } } Rooted<JSObject*> proto(cx, nullptr); return fromBuffer(cx, dataObj, byteOffset, length, proto); } static bool IsThisClass(HandleValue v) { - return v.isObject() && v.toObject().hasClass(fastClass()); + return v.isObject() && v.toObject().hasClass(instanceClass()); } template<Value ValueGetter(TypedArrayObject *tarr)> static bool GetterImpl(JSContext *cx, CallArgs args) { JS_ASSERT(IsThisClass(args.thisv())); args.rval().set(ValueGetter(&args.thisv().toObject().as<TypedArrayObject>())); @@ -415,48 +439,68 @@ class TypedArrayObjectTemplate : public static bool Getter(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod<ThisTypedArrayObject::IsThisClass, ThisTypedArrayObject::GetterImpl<ValueGetter> >(cx, args); } + static bool + 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 - template<Value ValueGetter(TypedArrayObject *tarr)> static bool - DefineGetter(JSContext *cx, PropertyName *name, HandleObject proto) + 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; return DefineNativeProperty(cx, proto, id, UndefinedHandleValue, JS_DATA_TO_FUNC_PTR(PropertyOp, getter), nullptr, flags, 0, 0); } 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; } /* subarray(start[, end]) */ static bool fun_subarray_impl(JSContext *cx, CallArgs args) @@ -666,17 +710,17 @@ class TypedArrayObjectTemplate : public * Rather than hack some crazy solution together, implement * this all using a private helper function, created when * ArrayBufferObject was initialized and cached in the global. * This reuses all the existing cross-compartment crazy so we * don't have to do anything *uniquely* crazy here. */ 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); if (!args.init(3)) return nullptr; args.setCallee(cx->compartment()->maybeGlobal()->createArrayFromBuffer<NativeType>()); args.setThis(ObjectValue(*bufobj)); @@ -690,74 +734,96 @@ class TypedArrayObjectTemplate : public } } if (!IsArrayBuffer(bufobj)) { JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); return nullptr; // must be arrayBuffer } - ArrayBufferObject &buffer = AsArrayBuffer(bufobj); - - if (byteOffset > buffer.byteLength() || byteOffset % sizeof(NativeType) != 0) { + Rooted<ArrayBufferObject *> buffer(cx, &AsArrayBuffer(bufobj)); + + 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 } } else { len = uint32_t(lengthInt); } // Go slowly and check for overflow. uint32_t arrayByteLength = len * sizeof(NativeType); if (len >= INT32_MAX / sizeof(NativeType) || byteOffset >= INT32_MAX - arrayByteLength) { JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); 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); } static JSObject * fromArray(JSContext *cx, HandleObject other) { uint32_t len; if (other->is<TypedArrayObject>()) { len = other->as<TypedArrayObject>().length(); } else if (!GetLengthProperty(cx, other, &len)) { 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; } static const NativeType getIndex(JSObject *obj, uint32_t index) { @@ -776,17 +842,20 @@ class TypedArrayObjectTemplate : public static JSObject * createSubarray(JSContext *cx, HandleObject tarrayArg, uint32_t begin, uint32_t end) { Rooted<TypedArrayObject*> tarray(cx, &tarrayArg->as<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); uint32_t length = end - begin; JS_ASSERT(begin < UINT32_MAX / sizeof(NativeType)); uint32_t arrayByteOffset = tarray->byteOffset(); JS_ASSERT(UINT32_MAX - begin * sizeof(NativeType) >= arrayByteOffset); @@ -1060,30 +1129,16 @@ class TypedArrayObjectTemplate : public } default: MOZ_ASSUME_UNREACHABLE("copyFromWithOverlap with a TypedArrayObject of unknown type"); } 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> { public: enum { ACTUAL_TYPE = ScalarTypeDescr::TYPE_INT8 }; static const JSProtoKey key = JSProto_Int8Array; static const JSFunctionSpec jsfuncs[]; };
--- a/js/src/vm/TypedArrayObject.h +++ b/js/src/vm/TypedArrayObject.h @@ -35,34 +35,55 @@ class TypedArrayObject : public ArrayBuf static const size_t TYPE_SLOT = JS_TYPEDOBJ_SLOT_TYPE_DESCR; static const size_t RESERVED_SLOTS = JS_TYPEDOBJ_SLOTS; static const size_t DATA_SLOT = JS_TYPEDOBJ_SLOT_DATA; public: 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); } static Value byteOffsetValue(TypedArrayObject *tarr) { return tarr->getFixedSlot(BYTEOFFSET_SLOT); } static Value byteLengthValue(TypedArrayObject *tarr) { return tarr->getFixedSlot(BYTELENGTH_SLOT); } static Value lengthValue(TypedArrayObject *tarr) { 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 { return byteOffsetValue(const_cast<TypedArrayObject*>(this)).toInt32(); } uint32_t byteLength() const { return byteLengthValue(const_cast<TypedArrayObject*>(this)).toInt32(); } @@ -223,34 +244,30 @@ class DataViewObject : public ArrayBuffe } static Value byteLengthValue(DataViewObject *view) { Value v = view->getReservedSlot(BYTELENGTH_SLOT); JS_ASSERT(v.toInt32() >= 0); return v; } + static Value bufferValue(DataViewObject *view) { + return view->getReservedSlot(BUFFER_SLOT); + } + uint32_t byteOffset() const { return byteOffsetValue(const_cast<DataViewObject*>(this)).toInt32(); } uint32_t byteLength() const { 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 { return getPrivate(); } static bool class_constructor(JSContext *cx, unsigned argc, Value *vp); static bool constructWithProto(JSContext *cx, unsigned argc, Value *vp);