Bug 1411774 - Optimize Object.assign with unboxed objects. r=jandem
--- 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();