Bug 1484370 - Optimize native property iteration for structured cloning. r=jandem
authorTom Schuster <evilpies@gmail.com>
Sat, 18 Aug 2018 00:02:04 +0200
changeset 432436 075bfbeee249
parent 432435 8047901cef0e
child 432437 ed3c662bb21d
push id106736
push userevilpies@gmail.com
push dateMon, 20 Aug 2018 18:27:56 +0000
treeherdermozilla-inbound@075bfbeee249 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1484370
milestone63.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 1484370 - Optimize native property iteration for structured cloning. r=jandem
js/src/jit-test/tests/structured-clone/roundtrip.js
js/src/vm/StructuredClone.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/structured-clone/roundtrip.js
@@ -0,0 +1,30 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+load(libdir + "asserts.js");
+
+const objects = [
+    {},
+    {a: 1, b: 2},
+    {0: 1, 1: 2},
+    {0: 1, 1: 2, a: 1},
+    {0: 1, 1: 2, a: 1, b: 2},
+    {1000000: 0, 1000001: 1},
+    {0: 0, 1: 0, 1000000: 0, 1000001: 1},
+
+    [],
+    [0, 1, 2],
+    [0, 15, 16],
+    [{a: 0, b: 0}, {b: 0, a: 0}],
+    [0, , , 1, 2],
+    [, 1],
+    [0,,],
+    [,,],
+]
+
+for (const obj of objects) {
+    assertDeepEq(deserialize(serialize(obj)), obj);
+    assertDeepEq(deserialize(serialize(wrapWithProto(obj, null))), obj);
+}
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -1370,35 +1370,99 @@ JSStructuredCloneWriter::startObject(Han
         JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_NEED_DIET,
                                   "object graph to serialize");
         return false;
     }
 
     return true;
 }
 
+static bool
+TryAppendNativeProperties(JSContext* cx, HandleObject obj, AutoValueVector& entries, size_t* properties,
+                          bool* optimized)
+{
+    *optimized = false;
+
+    if (!obj->isNative())
+        return true;
+
+    HandleNativeObject nobj = obj.as<NativeObject>();
+    if (nobj->isIndexed() ||
+        nobj->is<TypedArrayObject>() ||
+        nobj->getClass()->getNewEnumerate() ||
+        nobj->getClass()->getEnumerate())
+    {
+        return true;
+    }
+
+    *optimized = true;
+
+    size_t count = 0;
+    // We iterate from the last to the first shape, so the property names
+    // are already in reverse order.
+    RootedShape shape(cx, nobj->lastProperty());
+    for (Shape::Range<NoGC> r(shape); !r.empty(); r.popFront()) {
+        jsid id = r.front().propidRaw();
+
+        // Ignore symbols and non-enumerable properties.
+        if (!r.front().enumerable() || JSID_IS_SYMBOL(id))
+            continue;
+
+        MOZ_ASSERT(JSID_IS_STRING(id));
+        if (!entries.append(StringValue(JSID_TO_STRING(id))))
+            return false;
+
+        count++;
+    }
+
+    // Add dense element ids in reverse order.
+    for (uint32_t i = nobj->getDenseInitializedLength(); i > 0; --i) {
+        if (nobj->getDenseElement(i - 1).isMagic(JS_ELEMENTS_HOLE))
+            continue;
+
+        if (!entries.append(Int32Value(i - 1)))
+            return false;
+
+        count++;
+    }
+
+    *properties = count;
+    return true;
+}
+
 bool
 JSStructuredCloneWriter::traverseObject(HandleObject obj, ESClass cls)
 {
-    // Get enumerable property ids and put them in reverse order so that they
-    // will come off the stack in forward order.
-    AutoIdVector properties(context());
-    if (!GetPropertyKeys(context(), obj, JSITER_OWNONLY, &properties))
+    size_t count;
+    bool optimized = false;
+    if (!TryAppendNativeProperties(context(), obj, entries, &count, &optimized))
         return false;
 
-    for (size_t i = properties.length(); i > 0; --i) {
-        MOZ_ASSERT(JSID_IS_STRING(properties[i - 1]) || JSID_IS_INT(properties[i - 1]));
-        // JSStructuredCloneWriter::write relies on this.
-        RootedValue val(context(), IdToValue(properties[i - 1]));
-        if (!entries.append(val))
+    if (!optimized) {
+        // Get enumerable property ids and put them in reverse order so that they
+        // will come off the stack in forward order.
+        AutoIdVector properties(context());
+        if (!GetPropertyKeys(context(), obj, JSITER_OWNONLY, &properties))
             return false;
+
+        for (size_t i = properties.length(); i > 0; --i) {
+            MOZ_ASSERT(JSID_IS_STRING(properties[i - 1]) || JSID_IS_INT(properties[i - 1]));
+
+            // JSStructuredCloneWriter::write relies on this.
+            RootedValue val(context(), IdToValue(properties[i - 1]));
+            if (!entries.append(val))
+                return false;
+
+        }
+
+        count = properties.length();
     }
 
     // Push obj and count to the stack.
-    if (!objs.append(ObjectValue(*obj)) || !counts.append(properties.length()))
+    if (!objs.append(ObjectValue(*obj)) || !counts.append(count))
         return false;
 
     checkStack();
 
 #if DEBUG
     ESClass cls2;
     if (!GetBuiltinClass(context(), obj, &cls2))
         return false;