Bug 1411774 - Optimize Object.assign with unboxed objects. r=jandem
authorTom Schuster <evilpies@gmail.com>
Tue, 31 Oct 2017 18:34:05 +0100
changeset 689764 2aaa2d20e7aa91fc941973dbd6a1850c2ec0e5c7
parent 689763 4f4b24520f839dffc690af703b8c2cb56194b82a
child 689765 de7e487cbd59acf22948aedd4ac5fb6a609992af
child 689834 491939c123ec1ee2253d92dfd6f4b971d2ebc410
push id87097
push userdholbert@mozilla.com
push dateTue, 31 Oct 2017 22:39:07 +0000
reviewersjandem
bugs1411774
milestone58.0a1
Bug 1411774 - Optimize Object.assign with unboxed objects. r=jandem
js/src/builtin/Object.cpp
js/src/jit-test/tests/basic/object-assign-unboxed.js
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -19,16 +19,17 @@
 #include "vm/AsyncFunction.h"
 #include "vm/RegExpObject.h"
 #include "vm/StringBuffer.h"
 
 #include "jsobjinlines.h"
 
 #include "vm/NativeObject-inl.h"
 #include "vm/Shape-inl.h"
+#include "vm/UnboxedObject-inl.h"
 
 #ifdef FUZZING
 #include "builtin/TestingFunctions.h"
 #endif
 
 using namespace js;
 
 using js::frontend::IsIdentifier;
@@ -786,16 +787,69 @@ TryAssignNative(JSContext* cx, HandleObj
         if (MOZ_UNLIKELY(!result.checkStrict(cx, to, nextKey)))
             return false;
     }
 
     return true;
 }
 
 static bool
+TryAssignFromUnboxed(JSContext* cx, HandleObject to, HandleObject from, bool* optimized)
+{
+    *optimized = false;
+
+    if (!from->is<UnboxedPlainObject>() || !to->isNative())
+        return true;
+
+    // Don't use the fast path for unboxed objects with expandos.
+    UnboxedPlainObject* fromUnboxed = &from->as<UnboxedPlainObject>();
+    if (fromUnboxed->maybeExpando())
+        return true;
+
+    *optimized = true;
+
+    RootedObjectGroup fromGroup(cx, from->group());
+
+    RootedValue propValue(cx);
+    RootedId nextKey(cx);
+    RootedValue toReceiver(cx, ObjectValue(*to));
+
+    const UnboxedLayout& layout = fromUnboxed->layout();
+    for (size_t i = 0; i < layout.properties().length(); i++) {
+        const UnboxedLayout::Property& property = layout.properties()[i];
+        nextKey = NameToId(property.name);
+
+        // All unboxed properties are enumerable.
+        // Guard on the group to ensure that the object stays unboxed.
+        // We can ignore expando properties added after the loop starts.
+        if (MOZ_LIKELY(from->group() == fromGroup)) {
+            propValue = from->as<UnboxedPlainObject>().getValue(property);
+        } else {
+            // |from| changed so we have to do the slower enumerability check
+            // and GetProp.
+            bool enumerable;
+            if (!PropertyIsEnumerable(cx, from, nextKey, &enumerable))
+                return false;
+            if (!enumerable)
+                continue;
+            if (!GetProperty(cx, from, from, nextKey, &propValue))
+                return false;
+        }
+
+        ObjectOpResult result;
+        if (MOZ_UNLIKELY(!SetProperty(cx, to, nextKey, propValue, toReceiver, result)))
+            return false;
+        if (MOZ_UNLIKELY(!result.checkStrict(cx, to, nextKey)))
+            return false;
+    }
+
+    return true;
+}
+
+static bool
 AssignSlow(JSContext* cx, HandleObject to, HandleObject from)
 {
     // Step 4.b.ii.
     AutoIdVector keys(cx);
     if (!GetPropertyKeys(cx, from, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &keys))
         return false;
 
     // Step 4.c.
@@ -852,16 +906,21 @@ obj_assign(JSContext* cx, unsigned argc,
 
         // Steps 4.b.ii, 4.c.
         bool optimized;
         if (!TryAssignNative(cx, to, from, &optimized))
             return false;
         if (optimized)
             continue;
 
+        if (!TryAssignFromUnboxed(cx, to, from, &optimized))
+            return false;
+        if (optimized)
+            continue;
+
         if (!AssignSlow(cx, to, from))
             return false;
     }
 
     // Step 5.
     args.rval().setObject(*to);
     return true;
 }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/object-assign-unboxed.js
@@ -0,0 +1,84 @@
+load(libdir + "asserts.js");
+
+function Unboxed() {
+    this.a = 0;
+    this.b = true;
+}
+
+function tryCreateUnboxedObject() {
+    var obj;
+    for (var i = 0; i < 1000; ++i) {
+        obj = new Unboxed();
+    }
+
+    return obj;
+}
+
+function basic() {
+    var unboxed = tryCreateUnboxedObject();
+
+    var target = {};
+    Object.assign(target, unboxed);
+    assertDeepEq(target, {a: 0, b: true});
+
+    target = {a: 1, c: 3};
+    Object.assign(target, unboxed);
+    assertDeepEq(target, {a: 0, c: 3, b: true});
+}
+
+function expando() {
+    var unboxed = tryCreateUnboxedObject();
+    unboxed.c = 3.5;
+
+    var target = {};
+    Object.assign(target, unboxed);
+    assertDeepEq(target, {a: 0, b: true, c: 3.5});
+
+    target = {a: 1, d: 3};
+    Object.assign(target, unboxed);
+    assertDeepEq(target, {a: 0, d: 3, b: true, c: 3.5});
+}
+
+function addExpando() {
+    var unboxed = tryCreateUnboxedObject();
+
+    function setA(value) {
+        assertEq(value, 0);
+        unboxed.c = 3.5;
+    }
+
+    var target = {};
+    Object.defineProperty(target, "a", {set: setA});
+
+    var reference = {};
+    Object.defineProperty(reference, "a", {set: setA});
+    Object.defineProperty(reference, "b", {value: true, enumerable: true, configurable: true, writable: true});
+
+    Object.assign(target, unboxed);
+    assertDeepEq(target, reference);
+}
+
+function makeNative() {
+    var unboxed = tryCreateUnboxedObject();
+
+    function setA(value) {
+        assertEq(value, 0);
+        Object.defineProperty(unboxed, "a", {writable: false});
+    }
+
+    var target = {};
+    Object.defineProperty(target, "a", {set: setA});
+
+    var reference = {};
+    Object.defineProperty(reference, "a", {set: setA});
+    Object.defineProperty(reference, "b", {value: true, enumerable: true, configurable: true, writable: true});
+
+    Object.assign(target, unboxed);
+    assertDeepEq(target, reference);
+}
+
+
+basic();
+expando();
+addExpando();
+makeNative();