Bug 1686445 part 3 - Add structured clone support for large TypedArrays. r=sfink
authorJan de Mooij <jdemooij@mozilla.com>
Thu, 14 Jan 2021 21:43:05 +0000
changeset 563257 ad251c4f6d76433404649f6bad849e257e28e434
parent 563256 fae906026e24e59efa39e781932fc5c654d3ffe6
child 563258 2ca9f90369624e2626e4933eaf7f96237c9b8356
push id38108
push userncsoregi@mozilla.com
push dateFri, 15 Jan 2021 21:53:19 +0000
treeherdermozilla-central@0695ec653f99 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink
bugs1686445
milestone86.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 1686445 part 3 - Add structured clone support for large TypedArrays. r=sfink Instead of storing the length in the 32-bit data and the array type separately as 64-bit value, the new version just switches those. Depends on D101735 Differential Revision: https://phabricator.services.mozilla.com/D101736
js/src/jit-test/tests/structured-clone/array-buffers.js
js/src/vm/StructuredClone.cpp
--- a/js/src/jit-test/tests/structured-clone/array-buffers.js
+++ b/js/src/jit-test/tests/structured-clone/array-buffers.js
@@ -1,12 +1,14 @@
 // Tests for ArrayBuffer, TypedArray, and DataView encoding/decoding. 
 
 var clonebuffer = serialize("dummy");
 
+// ========= V2 =========
+
 function testV2Int32Array() {
     var buf = new Uint8Array([3,0,0,0,0,0,241,255,3,0,0,0,16,0,255,255,4,0,0,0,0,0,0,0,12,0,0,0,9,0,255,255,1,0,0,0,177,127,57,5,133,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0]);
     clonebuffer.clonebuffer = buf.buffer;
     var ta = deserialize(clonebuffer);
     assertEq(ta instanceof Int32Array, true);
     assertEq(ta.toString(), "1,87654321,-123");
 }
 testV2Int32Array();
@@ -33,16 +35,36 @@ function testV2ArrayBuffer() {
     var buf = new Uint8Array([3,0,0,0,0,0,241,255,4,0,0,0,9,0,255,255,33,44,55,66,0,0,0,0]);
     clonebuffer.clonebuffer = buf.buffer;
     var ab = deserialize(clonebuffer);
     assertEq(ab instanceof ArrayBuffer, true);
     assertEq(new Uint8Array(ab).toString(), "33,44,55,66");
 }
 testV2ArrayBuffer();
 
+// ========= Current =========
+
+function testInt32Array() {
+    var ta1 = new Int32Array([1, 87654321, -123]);
+    var clonebuf = serialize(ta1, undefined, {scope: "DifferentProcessForIndexedDB"});
+    var ta2 = deserialize(clonebuf);
+    assertEq(ta2 instanceof Int32Array, true);
+    assertEq(ta2.toString(), "1,87654321,-123");
+}
+testInt32Array();
+
+function testFloat64Array() {
+    var ta1 = new Float64Array([NaN, 3.14, 0, 0]);
+    var clonebuf = serialize(ta1, undefined, {scope: "DifferentProcessForIndexedDB"});
+    var ta2 = deserialize(clonebuf);
+    assertEq(ta2 instanceof Float64Array, true);
+    assertEq(ta2.toString(), "NaN,3.14,0,0");
+}
+testFloat64Array();
+
 function testArrayBuffer() {
     var ta = new Uint8Array([33, 44, 55, 66]);
     var clonebuf = serialize(ta.buffer, undefined, {scope: "DifferentProcessForIndexedDB"});
     var ab = deserialize(clonebuf);
     assertEq(ab instanceof ArrayBuffer, true);
     assertEq(new Uint8Array(ab).toString(), "33,44,55,66");
 }
 testArrayBuffer();
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -105,19 +105,19 @@ enum StructuredDataType : uint32_t {
   SCTAG_REGEXP_OBJECT,
   SCTAG_ARRAY_OBJECT,
   SCTAG_OBJECT_OBJECT,
   SCTAG_ARRAY_BUFFER_OBJECT_V2,  // Old version, for backwards compatibility.
   SCTAG_BOOLEAN_OBJECT,
   SCTAG_STRING_OBJECT,
   SCTAG_NUMBER_OBJECT,
   SCTAG_BACK_REFERENCE_OBJECT,
-  SCTAG_DO_NOT_USE_1,  // Required for backwards compatibility
-  SCTAG_DO_NOT_USE_2,  // Required for backwards compatibility
-  SCTAG_TYPED_ARRAY_OBJECT,
+  SCTAG_DO_NOT_USE_1,           // Required for backwards compatibility
+  SCTAG_DO_NOT_USE_2,           // Required for backwards compatibility
+  SCTAG_TYPED_ARRAY_OBJECT_V2,  // Old version, for backwards compatibility.
   SCTAG_MAP_OBJECT,
   SCTAG_SET_OBJECT,
   SCTAG_END_OF_KEYS,
   SCTAG_DO_NOT_USE_3,  // Required for backwards compatibility
   SCTAG_DATA_VIEW_OBJECT,
   SCTAG_SAVED_FRAME_OBJECT,
 
   // No new tags before principals.
@@ -128,16 +128,17 @@ enum StructuredDataType : uint32_t {
 
   SCTAG_SHARED_ARRAY_BUFFER_OBJECT,
   SCTAG_SHARED_WASM_MEMORY_OBJECT,
 
   SCTAG_BIGINT,
   SCTAG_BIGINT_OBJECT,
 
   SCTAG_ARRAY_BUFFER_OBJECT,
+  SCTAG_TYPED_ARRAY_OBJECT,
 
   SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100,
   SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int8,
   SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8,
   SCTAG_TYPED_ARRAY_V1_INT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int16,
   SCTAG_TYPED_ARRAY_V1_UINT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint16,
   SCTAG_TYPED_ARRAY_V1_INT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int32,
   SCTAG_TYPED_ARRAY_V1_UINT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint32,
@@ -422,17 +423,17 @@ struct JSStructuredCloneReader {
   bool readTransferMap();
 
   template <typename CharT>
   JSString* readStringImpl(uint32_t nchars, gc::InitialHeap heap);
   JSString* readString(uint32_t data, gc::InitialHeap heap = gc::DefaultHeap);
 
   BigInt* readBigInt(uint32_t data);
 
-  MOZ_MUST_USE bool readTypedArray(uint32_t arrayType, uint32_t nelems,
+  MOZ_MUST_USE bool readTypedArray(uint32_t arrayType, uint64_t nelems,
                                    MutableHandleValue vp, bool v1Read = false);
   MOZ_MUST_USE bool readDataView(uint32_t byteLength, MutableHandleValue vp);
   MOZ_MUST_USE bool readArrayBuffer(StructuredDataType type, uint32_t data,
                                     MutableHandleValue vp);
   MOZ_MUST_USE bool readSharedArrayBuffer(MutableHandleValue vp);
   MOZ_MUST_USE bool readSharedWasmMemory(uint32_t nbytes,
                                          MutableHandleValue vp);
   MOZ_MUST_USE bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems,
@@ -1262,32 +1263,33 @@ bool JSStructuredCloneWriter::writeTyped
   Rooted<TypedArrayObject*> tarr(context(),
                                  obj->maybeUnwrapAs<TypedArrayObject>());
   JSAutoRealm ar(context(), tarr);
 
   if (!TypedArrayObject::ensureHasBuffer(context(), tarr)) {
     return false;
   }
 
-  if (!out.writePair(SCTAG_TYPED_ARRAY_OBJECT,
-                     tarr->length().deprecatedGetUint32())) {
+  if (!out.writePair(SCTAG_TYPED_ARRAY_OBJECT, uint32_t(tarr->type()))) {
     return false;
   }
-  uint64_t type = tarr->type();
-  if (!out.write(type)) {
+
+  uint64_t nelems = tarr->length().get();
+  if (!out.write(nelems)) {
     return false;
   }
 
   // Write out the ArrayBuffer tag and contents
   RootedValue val(context(), tarr->bufferValue());
   if (!startWrite(val)) {
     return false;
   }
 
-  return out.write(tarr->byteOffset().deprecatedGetUint32());
+  uint64_t byteOffset = tarr->byteOffset().get();
+  return out.write(byteOffset);
 }
 
 bool JSStructuredCloneWriter::writeDataView(HandleObject obj) {
   Rooted<DataViewObject*> view(context(), obj->maybeUnwrapAs<DataViewObject>());
   JSAutoRealm ar(context(), view);
 
   if (!out.writePair(SCTAG_DATA_VIEW_OBJECT,
                      view->byteLength().deprecatedGetUint32())) {
@@ -2182,17 +2184,17 @@ BigInt* JSStructuredCloneReader::readBig
 
 static uint32_t TagToV1ArrayType(uint32_t tag) {
   MOZ_ASSERT(tag >= SCTAG_TYPED_ARRAY_V1_MIN &&
              tag <= SCTAG_TYPED_ARRAY_V1_MAX);
   return tag - SCTAG_TYPED_ARRAY_V1_MIN;
 }
 
 bool JSStructuredCloneReader::readTypedArray(uint32_t arrayType,
-                                             uint32_t nelems,
+                                             uint64_t nelems,
                                              MutableHandleValue vp,
                                              bool v1Read) {
   if (arrayType > (v1Read ? Scalar::Uint8Clamped : Scalar::BigUint64)) {
     JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
                               JSMSG_SC_BAD_SERIALIZED_DATA,
                               "unhandled typed array element type");
     return false;
   }
@@ -2201,42 +2203,44 @@ bool JSStructuredCloneReader::readTypedA
   uint32_t placeholderIndex = allObjs.length();
   Value dummy = UndefinedValue();
   if (!allObjs.append(dummy)) {
     return false;
   }
 
   // Read the ArrayBuffer object and its contents (but no properties)
   RootedValue v(context());
-  uint32_t byteOffset;
+  uint64_t byteOffset;
   if (v1Read) {
     if (!readV1ArrayBuffer(arrayType, nelems, &v)) {
       return false;
     }
     byteOffset = 0;
   } else {
     if (!startRead(&v)) {
       return false;
     }
-    uint64_t n;
-    if (!in.read(&n)) {
+    if (!in.read(&byteOffset)) {
       return false;
     }
-    byteOffset = n;
   }
   if (!v.isObject() || !v.toObject().is<ArrayBufferObjectMaybeShared>()) {
     JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
                               JSMSG_SC_BAD_SERIALIZED_DATA,
                               "typed array must be backed by an ArrayBuffer");
     return false;
   }
 
   RootedObject buffer(context(), &v.toObject());
   RootedObject obj(context(), nullptr);
 
+  // The length must be valid because we checked the ArrayBuffer length already.
+  uint64_t byteLength = nelems * Scalar::byteSize(Scalar::Type(arrayType));
+  MOZ_RELEASE_ASSERT(byteLength <= TypedArrayObject::maxByteLength());
+
   switch (arrayType) {
     case Scalar::Int8:
       obj = JS_NewInt8ArrayWithBuffer(context(), buffer, byteOffset, nelems);
       break;
     case Scalar::Uint8:
       obj = JS_NewUint8ArrayWithBuffer(context(), buffer, byteOffset, nelems);
       break;
     case Scalar::Int16:
@@ -2703,23 +2707,37 @@ bool JSStructuredCloneReader::startRead(
       break;
 
     case SCTAG_SHARED_WASM_MEMORY_OBJECT:
       if (!readSharedWasmMemory(data, vp)) {
         return false;
       }
       break;
 
-    case SCTAG_TYPED_ARRAY_OBJECT: {
+    case SCTAG_TYPED_ARRAY_OBJECT_V2: {
       // readTypedArray adds the array to allObjs.
+      // V2 stores the length (nelems) in |data| and the arrayType separately.
       uint64_t arrayType;
       if (!in.read(&arrayType)) {
         return false;
       }
-      return readTypedArray(arrayType, data, vp);
+      uint64_t nelems = data;
+      return readTypedArray(arrayType, nelems, vp);
+    }
+
+    case SCTAG_TYPED_ARRAY_OBJECT: {
+      // readTypedArray adds the array to allObjs.
+      // The current version stores the array type in |data| and the length
+      // (nelems) separately to support large TypedArrays.
+      uint32_t arrayType = data;
+      uint64_t nelems;
+      if (!in.read(&nelems)) {
+        return false;
+      }
+      return readTypedArray(arrayType, nelems, vp);
     }
 
     case SCTAG_DATA_VIEW_OBJECT: {
       // readDataView adds the array to allObjs.
       return readDataView(data, vp);
     }
 
     case SCTAG_MAP_OBJECT: {
@@ -3496,34 +3514,50 @@ JS_PUBLIC_API bool JS_ReadUint32Pair(JSS
 
 JS_PUBLIC_API bool JS_ReadBytes(JSStructuredCloneReader* r, void* p,
                                 size_t len) {
   return r->input().readBytes(p, len);
 }
 
 JS_PUBLIC_API bool JS_ReadTypedArray(JSStructuredCloneReader* r,
                                      MutableHandleValue vp) {
-  uint32_t tag, nelems;
-  if (!r->input().readPair(&tag, &nelems)) {
+  uint32_t tag, data;
+  if (!r->input().readPair(&tag, &data)) {
     return false;
   }
+
   if (tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
-    return r->readTypedArray(TagToV1ArrayType(tag), nelems, vp, true);
-  } else if (tag == SCTAG_TYPED_ARRAY_OBJECT) {
+    return r->readTypedArray(TagToV1ArrayType(tag), data, vp, true);
+  }
+
+  if (tag == SCTAG_TYPED_ARRAY_OBJECT_V2) {
+    // V2 stores the length (nelems) in |data| and the arrayType separately.
     uint64_t arrayType;
     if (!r->input().read(&arrayType)) {
       return false;
     }
+    uint64_t nelems = data;
     return r->readTypedArray(arrayType, nelems, vp);
-  } else {
-    JS_ReportErrorNumberASCII(r->context(), GetErrorMessage, nullptr,
-                              JSMSG_SC_BAD_SERIALIZED_DATA,
-                              "expected type array");
-    return false;
   }
+
+  if (tag == SCTAG_TYPED_ARRAY_OBJECT) {
+    // The current version stores the array type in |data| and the length
+    // (nelems) separately to support large TypedArrays.
+    uint32_t arrayType = data;
+    uint64_t nelems;
+    if (!r->input().read(&nelems)) {
+      return false;
+    }
+    return r->readTypedArray(arrayType, nelems, vp);
+  }
+
+  JS_ReportErrorNumberASCII(r->context(), GetErrorMessage, nullptr,
+                            JSMSG_SC_BAD_SERIALIZED_DATA,
+                            "expected type array");
+  return false;
 }
 
 JS_PUBLIC_API bool JS_WriteUint32Pair(JSStructuredCloneWriter* w, uint32_t tag,
                                       uint32_t data) {
   return w->output().writePair(tag, data);
 }
 
 JS_PUBLIC_API bool JS_WriteBytes(JSStructuredCloneWriter* w, const void* p,