Bug 1312480 - Take the slow path for small typed arrays. r=jandem, a=abillings
authorHannes Verschore <hv1989@gmail.com>
Wed, 11 Jan 2017 15:04:04 -0500
changeset 353530 1abb4b193740653f1161d86f38dbb79287e7d69b
parent 353529 136db7f563141b459527a8259b4496ebd4685934
child 353531 0337b9667770768fdfec9c151b8c1e74fbbee051
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem, abillings
bugs1312480
milestone52.0a2
Bug 1312480 - Take the slow path for small typed arrays. r=jandem, a=abillings
js/src/jit/MCallOptimize.cpp
js/src/jit/MacroAssembler.cpp
js/src/vm/TypedArrayObject.cpp
js/src/vm/TypedArrayObject.h
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -2358,17 +2358,17 @@ IonBuilder::inlineTypedArray(CallInfo& c
         ins = MNewTypedArrayDynamicLength::New(alloc(), constraints(), templateObject,
                                                templateObject->group()->initialHeap(constraints()),
                                                arg);
     } else {
         // Negative lengths must throw a RangeError.  (We don't track that this
         // might have previously thrown, when determining whether to inline, so we
         // have to deal with this error case when inlining.)
         int32_t providedLen = arg->maybeConstantValue()->toInt32();
-        if (providedLen < 0)
+        if (providedLen <= 0)
             return InliningStatus_NotInlined;
 
         uint32_t len = AssertedCast<uint32_t>(providedLen);
 
         if (obj->length() != len)
             return InliningStatus_NotInlined;
 
         callInfo.setImplicitlyUsedUnchecked();
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -1032,24 +1032,19 @@ FindStartOfUninitializedAndUndefinedSlot
 
 static void
 AllocateObjectBufferWithInit(JSContext* cx, TypedArrayObject* obj, int32_t count)
 {
     JS::AutoCheckCannotGC nogc(cx);
 
     obj->initPrivate(nullptr);
 
-    // Typed arrays with a non-compile-time known size that have a count of zero
-    // eventually are essentially typed arrays with inline elements. The bounds
-    // check will make sure that no elements are read or written to that memory.
-    // Negative numbers will bail out to the slow path, which in turn will raise
-    // an invalid argument exception.
+    // Negative numbers or zero will bail out to the slow path, which in turn will raise
+    // an invalid argument exception or create a correct object with zero elements.
     if (count <= 0) {
-        if (count == 0)
-            obj->setInlineElements();
         obj->setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(0));
         return;
     }
 
     obj->setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(count));
     size_t nbytes;
 
     switch (obj->type()) {
@@ -1107,16 +1102,20 @@ MacroAssembler::initTypedArraySlots(Regi
         // data is a count of 8-byte HeapSlots (i.e. <= pointer size),
         // and we won't inline unless the desired memory fits in that
         // space.)
         static_assert(sizeof(HeapSlot) == 8, "Assumed 8 bytes alignment");
 
         size_t numZeroPointers = ((nbytes + 7) & ~0x7) / sizeof(char *);
         for (size_t i = 0; i < numZeroPointers; i++)
             storePtr(ImmWord(0), Address(obj, dataOffset + i * sizeof(char *)));
+#ifdef DEBUG
+        if (nbytes == 0)
+            store8(Imm32(TypedArrayObject::ZeroLengthArrayData), Address(obj, dataSlotOffset));
+#endif
     } else {
         if (lengthKind == TypedArrayLength::Fixed)
             move32(Imm32(length), lengthReg);
 
         // Allocate a buffer on the heap to store the data elements.
         liveRegs.addUnchecked(temp);
         liveRegs.addUnchecked(obj);
         liveRegs.addUnchecked(lengthReg);
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -133,29 +133,42 @@ TypedArrayObject::ensureHasBuffer(JSCont
     tarray->setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectValue(*buffer));
 
     // Notify compiled jit code that the base pointer has moved.
     MarkObjectStateChange(cx, tarray);
 
     return true;
 }
 
+#ifdef DEBUG
+void
+TypedArrayObject::assertZeroLengthArrayData() const
+{
+    if (length() == 0 && !hasBuffer()) {
+        uint8_t* end = fixedData(TypedArrayObject::FIXED_DATA_START);
+        MOZ_ASSERT(end[0] == ZeroLengthArrayData);
+    }
+}
+#endif
+
 /* static */ void
 TypedArrayObject::trace(JSTracer* trc, JSObject* objArg)
 {
     // Handle all tracing required when the object has a buffer.
     ArrayBufferViewObject::trace(trc, objArg);
 }
 
 void
 TypedArrayObject::finalize(FreeOp* fop, JSObject* obj)
 {
     MOZ_ASSERT(!IsInsideNursery(obj));
     TypedArrayObject* curObj = &obj->as<TypedArrayObject>();
 
+    curObj->assertZeroLengthArrayData();
+
     // Typed arrays with a buffer object do not need to be free'd
     if (curObj->hasBuffer())
         return;
 
     // Free the data slot pointer if it does not point into the old JSObject.
     if (!curObj->hasInlineElements())
         js_free(curObj->elements());
 }
@@ -176,17 +189,17 @@ TypedArrayObject::objectMoved(JSObject* 
 }
 
 /* static */ size_t
 TypedArrayObject::objectMovedDuringMinorGC(JSTracer* trc, JSObject* obj, const JSObject* old,
                                            gc::AllocKind newAllocKind)
 {
     TypedArrayObject* newObj = &obj->as<TypedArrayObject>();
     const TypedArrayObject* oldObj = &old->as<TypedArrayObject>();
-    MOZ_ASSERT(newObj->elements() == oldObj->elements());
+    MOZ_ASSERT(newObj->elementsRaw() == oldObj->elementsRaw());
     MOZ_ASSERT(obj->isTenured());
 
     // Typed arrays with a buffer object do not need an update.
     if (oldObj->hasBuffer())
         return 0;
 
     Nursery& nursery = trc->runtime()->gc.nursery;
     void* buf = oldObj->elements();
@@ -207,18 +220,28 @@ TypedArrayObject::objectMovedDuringMinor
         break;
 JS_FOR_EACH_TYPED_ARRAY(OBJECT_MOVED_TYPED_ARRAY)
 #undef OBJECT_MOVED_TYPED_ARRAY
       default:
         MOZ_CRASH("Unsupported TypedArray type");
     }
 
     size_t headerSize = dataOffset() + sizeof(HeapSlot);
+
+    // See AllocKindForLazyBuffer.
+    MOZ_ASSERT_IF(nbytes == 0, headerSize + sizeof(uint8_t) <= GetGCKindBytes(newAllocKind));
+
     if (headerSize + nbytes <= GetGCKindBytes(newAllocKind)) {
         MOZ_ASSERT(oldObj->hasInlineElements());
+#ifdef DEBUG
+        if (nbytes == 0) {
+            uint8_t* output = newObj->fixedData(TypedArrayObject::FIXED_DATA_START);
+            output[0] = ZeroLengthArrayData;
+        }
+#endif
         newObj->setInlineElements();
     } else {
         MOZ_ASSERT(!oldObj->hasInlineElements());
         AutoEnterOOMUnsafeRegion oomUnsafe;
         nbytes = JS_ROUNDUP(nbytes, sizeof(Value));
         void* data = newObj->zone()->pod_malloc<uint8_t>(nbytes);
         if (!data)
             oomUnsafe.crash("Failed to allocate typed array elements while tenuring.");
@@ -503,16 +526,22 @@ class TypedArrayObjectTemplate : public 
                 } else {
                     cx->runtime()->gc.storeBuffer.putWholeCell(obj);
                 }
             }
         } else {
             void* data = obj->fixedData(FIXED_DATA_START);
             obj->initPrivate(data);
             memset(data, 0, len * sizeof(NativeType));
+#ifdef DEBUG
+            if (len == 0) {
+                uint8_t* elements = static_cast<uint8_t*>(data);
+                elements[0] = ZeroLengthArrayData;
+            }
+#endif
         }
 
         obj->setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(len));
         obj->setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(byteOffset));
 
 #ifdef DEBUG
         if (buffer) {
             uint32_t arrayByteLength = obj->byteLength();
@@ -594,16 +623,23 @@ class TypedArrayObjectTemplate : public 
     {
         MOZ_ASSERT(len >= 0);
         tarray->setFixedSlot(TypedArrayObject::BUFFER_SLOT, NullValue());
         tarray->setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(AssertedCast<int32_t>(len)));
         tarray->setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(0));
 
         // Verify that the private slot is at the expected place.
         MOZ_ASSERT(tarray->numFixedSlots() == TypedArrayObject::DATA_SLOT);
+
+#ifdef DEBUG
+        if (len == 0) {
+            uint8_t* output = tarray->fixedData(TypedArrayObject::FIXED_DATA_START);
+            output[0] = TypedArrayObject::ZeroLengthArrayData;
+        }
+#endif
     }
 
     static void
     initTypedArrayData(JSContext* cx, TypedArrayObject* tarray, int32_t len,
                        void* buf, AllocKind allocKind)
     {
         if (buf) {
 #ifdef DEBUG
--- a/js/src/vm/TypedArrayObject.h
+++ b/js/src/vm/TypedArrayObject.h
@@ -59,16 +59,20 @@ class TypedArrayObject : public NativeOb
     // Offset of view within underlying (Shared)ArrayBufferObject.
     static const size_t BYTEOFFSET_SLOT = 2;
     static_assert(BYTEOFFSET_SLOT == JS_TYPEDARRAYLAYOUT_BYTEOFFSET_SLOT,
                   "self-hosted code with burned-in constants must get the "
                   "right byteOffset slot");
 
     static const size_t RESERVED_SLOTS = 3;
 
+#ifdef DEBUG
+    static const uint8_t ZeroLengthArrayData = 0x4A;
+#endif
+
     static int lengthOffset();
     static int dataOffset();
 
     // The raw pointer to the buffer memory, the "private" value.
     //
     // This offset is exposed for performance reasons - so that it
     // need not be looked up on accesses.
     static const size_t DATA_SLOT = 3;
@@ -115,16 +119,18 @@ class TypedArrayObject : public NativeOb
     // object is created lazily.
     static const uint32_t INLINE_BUFFER_LIMIT =
         (NativeObject::MAX_FIXED_SLOTS - FIXED_DATA_START) * sizeof(Value);
 
     static gc::AllocKind
     AllocKindForLazyBuffer(size_t nbytes)
     {
         MOZ_ASSERT(nbytes <= INLINE_BUFFER_LIMIT);
+        if (nbytes == 0)
+            nbytes += sizeof(uint8_t);
         size_t dataSlots = AlignBytes(nbytes, sizeof(Value)) / sizeof(Value);
         MOZ_ASSERT(nbytes <= dataSlots * sizeof(Value));
         return gc::GetGCObjectKind(FIXED_DATA_START + dataSlots);
     }
 
     inline Scalar::Type type() const;
     inline size_t bytesPerElement() const;
 
@@ -159,19 +165,29 @@ class TypedArrayObject : public NativeOb
         return byteLengthValue(const_cast<TypedArrayObject*>(this)).toInt32();
     }
     uint32_t length() const {
         return lengthValue(const_cast<TypedArrayObject*>(this)).toInt32();
     }
 
     bool hasInlineElements() const;
     void setInlineElements();
-    uint8_t* elements() const {
+    uint8_t* elementsRaw() const {
         return *(uint8_t **)((((char *)this) + this->dataOffset()));
     }
+    uint8_t* elements() const {
+        assertZeroLengthArrayData();
+        return elementsRaw();
+    }
+
+#ifdef DEBUG
+    void assertZeroLengthArrayData() const;
+#else
+    void assertZeroLengthArrayData() const {};
+#endif
 
     Value getElement(uint32_t index);
     static void setElement(TypedArrayObject& obj, uint32_t index, double d);
 
     void notifyBufferDetached(JSContext* cx, void* newData);
 
     static bool
     GetTemplateObjectForNative(JSContext* cx, Native native, uint32_t len,