Bug 1582348 - Remove |QueueEntry| and just store its elements in successive elements of a queue. r=arai
authorJeff Walden <jwalden@mit.edu>
Tue, 05 Nov 2019 05:34:50 +0000
changeset 500530 0695bdca115cc0ac2596e5edf90669d7c95be168
parent 500529 603e71d6a5d26b0c9ffc42315e040e9b9db27850
child 500531 61afdffa6c61f15c997c560fb966716c0f4c00ed
push id114166
push userapavel@mozilla.com
push dateThu, 07 Nov 2019 10:04:01 +0000
treeherdermozilla-inbound@d271c572a9bc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarai
bugs1582348
milestone72.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
Bug 1582348 - Remove |QueueEntry| and just store its elements in successive elements of a queue. r=arai Differential Revision: https://phabricator.services.mozilla.com/D51041
js/src/builtin/streams/QueueWithSizes-inl.h
js/src/builtin/streams/QueueWithSizes.cpp
js/src/builtin/streams/QueueWithSizes.h
js/src/builtin/streams/WritableStreamDefaultControllerOperations.cpp
js/src/builtin/streams/WritableStreamDefaultWriter-inl.h
js/src/builtin/streams/WritableStreamWriterOperations.h
js/src/vm/List-inl.h
js/src/vm/List.h
new file mode 100644
--- /dev/null
+++ b/js/src/builtin/streams/QueueWithSizes-inl.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Queue-with-sizes operations. */
+
+#ifndef builtin_streams_QueueWithSizes_inl_h
+#define builtin_streams_QueueWithSizes_inl_h
+
+#include "builtin/streams/QueueWithSizes.h"
+
+#include "mozilla/Assertions.h"  // MOZ_ASSERT
+
+#include "js/RootingAPI.h"  // JS::Handle
+#include "js/Value.h"       // JS::Value
+#include "vm/List.h"        // js::ListObject
+
+#include "vm/List-inl.h"  // js::ListObject::*
+
+struct JS_PUBLIC_API JSContext;
+
+namespace js {
+
+namespace detail {
+
+// The *internal* representation of a queue-with-sizes is a List of even length
+// where elements (2 * n, 2 * n + 1) represent the nth (value, size) element in
+// the queue.
+
+inline JS::Value QueueFirstValue(ListObject* unwrappedQueue) {
+  MOZ_ASSERT(!unwrappedQueue->isEmpty(),
+             "can't examine first value in an empty queue-with-sizes");
+  MOZ_ASSERT((unwrappedQueue->length() % 2) == 0,
+             "queue-with-sizes must consist of (value, size) element pairs and "
+             "so must have even length");
+  return unwrappedQueue->get(0);
+}
+
+inline double QueueFirstSize(ListObject* unwrappedQueue) {
+  MOZ_ASSERT(!unwrappedQueue->isEmpty(),
+             "can't examine first value in an empty queue-with-sizes");
+  MOZ_ASSERT((unwrappedQueue->length() % 2) == 0,
+             "queue-with-sizes must consist of (value, size) element pairs and "
+             "so must have even length");
+  return unwrappedQueue->get(1).toDouble();
+}
+
+inline void QueueRemoveFirstValueAndSize(ListObject* unwrappedQueue,
+                                         JSContext* cx) {
+  MOZ_ASSERT(!unwrappedQueue->isEmpty(),
+             "can't remove first value from an empty queue-with-sizes");
+  MOZ_ASSERT((unwrappedQueue->length() % 2) == 0,
+             "queue-with-sizes must consist of (value, size) element pairs and "
+             "so must have even length");
+  unwrappedQueue->popFirstPair(cx);
+}
+
+inline MOZ_MUST_USE bool QueueAppendValueAndSize(
+    JSContext* cx, JS::Handle<ListObject*> unwrappedQueue,
+    JS::Handle<JS::Value> value, double size) {
+  return unwrappedQueue->appendValueAndSize(cx, value, size);
+}
+
+}  // namespace detail
+
+}  // namespace js
+
+#endif  // builtin_streams_QueueWithSizes_inl_h
--- a/js/src/builtin/streams/QueueWithSizes.cpp
+++ b/js/src/builtin/streams/QueueWithSizes.cpp
@@ -1,17 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: set ts=8 sts=2 et sw=2 tw=80:
  * 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/. */
 
 /* Queue-with-sizes operations. */
 
-#include "builtin/streams/QueueWithSizes.h"
+#include "builtin/streams/QueueWithSizes-inl.h"
 
 #include "mozilla/Assertions.h"     // MOZ_ASSERT
 #include "mozilla/Attributes.h"     // MOZ_MUST_USE
 #include "mozilla/FloatingPoint.h"  // mozilla::Is{Infinite,NaN}
 
 #include "jsapi.h"  // JS_ReportErrorNumberASCII
 
 #include "builtin/streams/StreamController.h"  // js::StreamController
@@ -33,83 +33,80 @@
 using JS::Handle;
 using JS::MutableHandle;
 using JS::NumberValue;
 using JS::ObjectValue;
 using JS::Rooted;
 using JS::ToNumber;
 using JS::Value;
 
-class QueueEntry : public js::NativeObject {
- private:
-  enum Slots { Slot_Value = 0, Slot_Size, SlotCount };
-
- public:
-  static const JSClass class_;
-
-  Value value() { return getFixedSlot(Slot_Value); }
-  double size() { return getFixedSlot(Slot_Size).toNumber(); }
-
-  static QueueEntry* create(JSContext* cx, Handle<Value> value, double size) {
-    Rooted<QueueEntry*> entry(cx, js::NewBuiltinClassInstance<QueueEntry>(cx));
-    if (!entry) {
-      return nullptr;
-    }
-
-    entry->setFixedSlot(Slot_Value, value);
-    entry->setFixedSlot(Slot_Size, NumberValue(size));
-
-    return entry;
-  }
-};
-
-const JSClass QueueEntry::class_ = {"QueueEntry",
-                                    JSCLASS_HAS_RESERVED_SLOTS(SlotCount)};
-
 /*** 6.2. Queue-with-sizes operations ***************************************/
 
 /**
  * Streams spec, 6.2.1. DequeueValue ( container ) nothrow
  */
 MOZ_MUST_USE bool js::DequeueValue(JSContext* cx,
                                    Handle<StreamController*> unwrappedContainer,
                                    MutableHandle<Value> chunk) {
   // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
   //         slots (implicit).
   // Step 2: Assert: queue is not empty.
   Rooted<ListObject*> unwrappedQueue(cx, unwrappedContainer->queue());
-  MOZ_ASSERT(unwrappedQueue->length() > 0);
 
   // Step 3. Let pair be the first element of queue.
+  double chunkSize = detail::QueueFirstSize(unwrappedQueue);
+  chunk.set(detail::QueueFirstValue(unwrappedQueue));
+
   // Step 4. Remove pair from queue, shifting all other elements downward
   //         (so that the second becomes the first, and so on).
-  Rooted<QueueEntry*> unwrappedPair(
-      cx, &unwrappedQueue->popFirstAs<QueueEntry>(cx));
-  MOZ_ASSERT(unwrappedPair);
+  detail::QueueRemoveFirstValueAndSize(unwrappedQueue, cx);
 
   // Step 5: Set container.[[queueTotalSize]] to
   //         container.[[queueTotalSize]] − pair.[[size]].
   // Step 6: If container.[[queueTotalSize]] < 0, set
   //         container.[[queueTotalSize]] to 0.
   //         (This can occur due to rounding errors.)
   double totalSize = unwrappedContainer->queueTotalSize();
-  totalSize -= unwrappedPair->size();
+  totalSize -= chunkSize;
   if (totalSize < 0) {
     totalSize = 0;
   }
   unwrappedContainer->setQueueTotalSize(totalSize);
 
   // Step 7: Return pair.[[value]].
-  Rooted<Value> val(cx, unwrappedPair->value());
-  if (!cx->compartment()->wrap(cx, &val)) {
-    return false;
+  return cx->compartment()->wrap(cx, chunk);
+}
+
+void js::DequeueValue(StreamController* unwrappedContainer, JSContext* cx) {
+  // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
+  //         slots (implicit).
+  // Step 2: Assert: queue is not empty.
+  ListObject* unwrappedQueue = unwrappedContainer->queue();
+
+  // Step 3. Let pair be the first element of queue.
+  // (The value is being discarded, so all we must extract is the size.)
+  double chunkSize = detail::QueueFirstSize(unwrappedQueue);
+
+  // Step 4. Remove pair from queue, shifting all other elements downward
+  //         (so that the second becomes the first, and so on).
+  detail::QueueRemoveFirstValueAndSize(unwrappedQueue, cx);
+
+  // Step 5: Set container.[[queueTotalSize]] to
+  //         container.[[queueTotalSize]] − pair.[[size]].
+  // Step 6: If container.[[queueTotalSize]] < 0, set
+  //         container.[[queueTotalSize]] to 0.
+  //         (This can occur due to rounding errors.)
+  double totalSize = unwrappedContainer->queueTotalSize();
+  totalSize -= chunkSize;
+  if (totalSize < 0) {
+    totalSize = 0;
   }
+  unwrappedContainer->setQueueTotalSize(totalSize);
 
-  chunk.set(val);
-  return true;
+  // Step 7: Return pair.[[value]].  (omitted because not used)
 }
 
 /**
  * Streams spec, 6.2.2. EnqueueValueWithSize ( container, value, size ) throws
  */
 MOZ_MUST_USE bool js::EnqueueValueWithSize(
     JSContext* cx, Handle<StreamController*> unwrappedContainer,
     Handle<Value> value, Handle<Value> sizeVal) {
@@ -130,28 +127,24 @@ MOZ_MUST_USE bool js::EnqueueValueWithSi
                               JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, "size");
     return false;
   }
 
   // Step 4: Append Record {[[value]]: value, [[size]]: size} as the last
   //         element of container.[[queue]].
   {
     AutoRealm ar(cx, unwrappedContainer);
-    Rooted<ListObject*> queue(cx, unwrappedContainer->queue());
+    Rooted<ListObject*> unwrappedQueue(cx, unwrappedContainer->queue());
     Rooted<Value> wrappedVal(cx, value);
     if (!cx->compartment()->wrap(cx, &wrappedVal)) {
       return false;
     }
 
-    QueueEntry* entry = QueueEntry::create(cx, wrappedVal, size);
-    if (!entry) {
-      return false;
-    }
-    Rooted<Value> val(cx, ObjectValue(*entry));
-    if (!queue->append(cx, val)) {
+    if (!detail::QueueAppendValueAndSize(cx, unwrappedQueue, wrappedVal,
+                                         size)) {
       return false;
     }
   }
 
   // Step 5: Set container.[[queueTotalSize]] to
   //         container.[[queueTotalSize]] + size.
   unwrappedContainer->setQueueTotalSize(unwrappedContainer->queueTotalSize() +
                                         size);
--- a/js/src/builtin/streams/QueueWithSizes.h
+++ b/js/src/builtin/streams/QueueWithSizes.h
@@ -4,34 +4,43 @@
  * 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/. */
 
 /* Queue-with-sizes operations. */
 
 #ifndef builtin_streams_QueueWithSizes_h
 #define builtin_streams_QueueWithSizes_h
 
+#include "mozilla/Assertions.h"  // MOZ_ASSERT
 #include "mozilla/Attributes.h"  // MOZ_MUST_USE
 
 #include "jstypes.h"        // JS_PUBLIC_API
 #include "js/RootingAPI.h"  // JS::{,Mutable}Handle
 #include "js/Value.h"       // JS::Value
+#include "vm/List.h"        // js::ListObject
 
 struct JS_PUBLIC_API JSContext;
 
 namespace js {
 
 class StreamController;
 
 /**
  * Streams spec, 6.2.1. DequeueValue ( container ) nothrow
  */
 extern MOZ_MUST_USE bool DequeueValue(
     JSContext* cx, JS::Handle<StreamController*> unwrappedContainer,
     JS::MutableHandle<JS::Value> chunk);
+
+/**
+ * Streams spec, 6.2.1. DequeueValue ( container ) nothrow
+ * when the dequeued value is ignored.
+ */
+extern void DequeueValue(StreamController* unwrappedContainer, JSContext* cx);
+
 /**
  * Streams spec, 6.2.2. EnqueueValueWithSize ( container, value, size ) throws
  */
 extern MOZ_MUST_USE bool EnqueueValueWithSize(
     JSContext* cx, JS::Handle<StreamController*> unwrappedContainer,
     JS::Handle<JS::Value> value, JS::Handle<JS::Value> sizeVal);
 
 /**
--- a/js/src/builtin/streams/WritableStreamDefaultControllerOperations.cpp
+++ b/js/src/builtin/streams/WritableStreamDefaultControllerOperations.cpp
@@ -548,17 +548,18 @@ MOZ_MUST_USE bool WritableStreamDefaultC
   if (unwrappedStream->erroring()) {
     // Step 6a: Perform ! WritableStreamFinishErroring(stream).
     // Step 6b: Return.
     return WritableStreamFinishErroring(cx, unwrappedStream);
   }
 
   // Step 7: If controller.[[queue]] is empty, return.
   Rooted<ListObject*> unwrappedQueue(cx, unwrappedController->queue());
-  if (unwrappedQueue->length() == 0) {
+  MOZ_ASSERT((unwrappedQueue->length() % 2) == 0);
+  if (unwrappedQueue->isEmpty()) {
     return true;
   }
 
   // Step 8: Let writeRecord be ! PeekQueueValue(controller).
   // Step 9: If writeRecord is "close", perform
   //         ! WritableStreamDefaultControllerProcessClose(controller).
   // Step 10: Otherwise, perform
   //          ! WritableStreamDefaultControllerProcessWrite(
--- a/js/src/builtin/streams/WritableStreamDefaultWriter-inl.h
+++ b/js/src/builtin/streams/WritableStreamDefaultWriter-inl.h
@@ -17,17 +17,17 @@
 #include "builtin/Promise.h"                 // js::PromiseObject
 #include "builtin/streams/WritableStream.h"  // js::WritableStream
 #include "js/RootingAPI.h"                   // JS::Handle
 #include "js/Value.h"                        // JS::ObjectValue
 #include "vm/NativeObject.h"                 // js::NativeObject
 
 #include "vm/Compartment-inl.h"  // js::UnwrapInternalSlot
 
-struct JSContext;
+struct JS_PUBLIC_API JSContext;
 
 namespace js {
 
 /**
  * Returns the stream associated with the given reader.
  */
 inline MOZ_MUST_USE WritableStream* UnwrapStreamFromWriter(
     JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter) {
--- a/js/src/builtin/streams/WritableStreamWriterOperations.h
+++ b/js/src/builtin/streams/WritableStreamWriterOperations.h
@@ -9,18 +9,18 @@
 #ifndef builtin_streams_WritableStreamWriterOperations_h
 #define builtin_streams_WritableStreamWriterOperations_h
 
 #include "mozilla/Attributes.h"  // MOZ_MUST_USE
 
 #include "js/RootingAPI.h"  // JS::{,Mutable}Handle
 #include "js/Value.h"       // JS::Value
 
-struct JSContext;
-class JSObject;
+struct JS_PUBLIC_API JSContext;
+class JS_PUBLIC_API JSObject;
 
 namespace js {
 
 class WritableStreamDefaultWriter;
 
 extern JSObject* WritableStreamDefaultWriterClose(
     JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter);
 
--- a/js/src/vm/List-inl.h
+++ b/js/src/vm/List-inl.h
@@ -35,31 +35,60 @@ inline bool js::ListObject::append(JSCon
     return false;
   }
 
   ensureDenseInitializedLength(cx, len, 1);
   setDenseElementWithType(cx, len, value);
   return true;
 }
 
+inline bool js::ListObject::appendValueAndSize(JSContext* cx,
+                                               JS::Handle<JS::Value> value,
+                                               double size) {
+  uint32_t len = length();
+
+  if (!ensureElements(cx, len + 2)) {
+    return false;
+  }
+
+  ensureDenseInitializedLength(cx, len, 2);
+  setDenseElementWithType(cx, len, value);
+  setDenseElementWithType(cx, len + 1, JS::DoubleValue(size));
+  return true;
+}
+
 inline JS::Value js::ListObject::popFirst(JSContext* cx) {
   uint32_t len = length();
   MOZ_ASSERT(len > 0);
 
   JS::Value entry = get(0);
   if (!tryShiftDenseElements(1)) {
     moveDenseElements(0, 1, len - 1);
     setDenseInitializedLength(len - 1);
     shrinkElements(cx, len - 1);
   }
 
   MOZ_ASSERT(length() == len - 1);
   return entry;
 }
 
+inline void js::ListObject::popFirstPair(JSContext* cx) {
+  uint32_t len = length();
+  MOZ_ASSERT(len > 0);
+  MOZ_ASSERT((len % 2) == 0);
+
+  if (!tryShiftDenseElements(2)) {
+    moveDenseElements(0, 2, len - 2);
+    setDenseInitializedLength(len - 2);
+    shrinkElements(cx, len - 2);
+  }
+
+  MOZ_ASSERT(length() == len - 2);
+}
+
 template <class T>
 inline T& js::ListObject::popFirstAs(JSContext* cx) {
   return popFirst(cx).toObject().as<T>();
 }
 
 namespace js {
 
 /**
--- a/js/src/vm/List.h
+++ b/js/src/vm/List.h
@@ -32,36 +32,56 @@ namespace js {
 class ListObject : public NativeObject {
  public:
   static const JSClass class_;
 
   inline static MOZ_MUST_USE ListObject* create(JSContext* cx);
 
   uint32_t length() const { return getDenseInitializedLength(); }
 
+  bool isEmpty() const { return length() == 0; }
+
   const Value& get(uint32_t index) const { return getDenseElement(index); }
 
   template <class T>
   T& getAs(uint32_t index) const {
     return get(index).toObject().as<T>();
   }
 
   /**
    * Add an element to the end of the list. Returns false on OOM.
    */
   inline MOZ_MUST_USE bool append(JSContext* cx, HandleValue value);
 
   /**
+   * Adds |value| and |size| elements to a list consisting of (value, size)
+   * pairs stored in successive elements.
+   *
+   * This function is intended for use by streams code's queue-with-sizes data
+   * structure and related operations.  See builtin/streams/QueueWithSizes*.
+   * (You *could* use this on any list of even length without issue, but it's
+   * hard to imagine realistic situations where you'd want to...)
+   */
+  inline MOZ_MUST_USE bool appendValueAndSize(JSContext* cx, HandleValue value,
+                                              double size);
+
+  /**
    * Remove and return the first element of the list.
    *
    * Precondition: This list is not empty.
    */
   inline JS::Value popFirst(JSContext* cx);
 
   /**
+   * Remove the first two elements from a nonempty list of (value, size) pairs
+   * of elements.
+   */
+  inline void popFirstPair(JSContext* cx);
+
+  /**
    * Remove and return the first element of the list.
    *
    * Precondition: This list is not empty, and the first element
    * is an object of class T.
    */
   template <class T>
   inline T& popFirstAs(JSContext* cx);
 };