Bug 1425687 - Don't use copy-on-write elements for small arrays. r=evilpie
authorJan de Mooij <jdemooij@mozilla.com>
Thu, 11 Jan 2018 12:22:22 +0100
changeset 398779 ed7d701658926c75bff77885d3258b2860d6dc3b
parent 398778 316e5fab18f1bd02f1159e64ed137aab927f73cd
child 398780 56f88b76d0fd6b0b207de727891610132e9a67a4
push id98820
push userjandemooij@gmail.com
push dateThu, 11 Jan 2018 11:22:40 +0000
treeherdermozilla-inbound@ed7d70165892 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersevilpie
bugs1425687
milestone59.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 1425687 - Don't use copy-on-write elements for small arrays. r=evilpie
js/src/frontend/BytecodeEmitter.cpp
js/src/jit-test/tests/ion/recover-arrays.js
js/src/jit-test/tests/ion/recover-cow-arrays.js
js/src/shell/js.cpp
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -9826,18 +9826,22 @@ BytecodeEmitter::emitArrayLiteral(ParseN
     if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head) {
         if (checkSingletonContext()) {
             // Bake in the object entirely if it will only be created once.
             return emitSingletonInitialiser(pn);
         }
 
         // If the array consists entirely of primitive values, make a
         // template object with copy on write elements that can be reused
-        // every time the initializer executes.
-        if (emitterMode != BytecodeEmitter::SelfHosting && pn->pn_count != 0) {
+        // every time the initializer executes. Don't do this if the array is
+        // small: copying the elements lazily is not worth it in that case.
+        static const size_t MinElementsForCopyOnWrite = 5;
+        if (emitterMode != BytecodeEmitter::SelfHosting &&
+            pn->pn_count >= MinElementsForCopyOnWrite)
+        {
             RootedValue value(cx);
             if (!pn->getConstantValue(cx, ParseNode::ForCopyOnWriteArray, &value))
                 return false;
             if (!value.isMagic(JS_GENERIC_MAGIC)) {
                 // Note: the group of the template object might not yet reflect
                 // that the object has copy on write elements. When the
                 // interpreter or JIT compiler fetches the template, it should
                 // use ObjectGroup::getOrFixupCopyOnWriteObject to make sure the
--- a/js/src/jit-test/tests/ion/recover-arrays.js
+++ b/js/src/jit-test/tests/ion/recover-arrays.js
@@ -264,17 +264,17 @@ function arrayAlloc2(i) {
     }
     assertRecoveredOnBailout(a, true);
     return 0;
 }
 
 function build(l) { var arr = []; for (var i = 0; i < l; i++) arr.push(i); return arr }
 var uceFault_arrayAlloc3 = eval(uneval(uceFault).replace('uceFault', 'uceFault_arrayAlloc3'));
 function arrayAlloc3(i) {
-    var a = [0,1,2,3];
+    var a = [0,1,2,3,4,5,6,7,8];
     if (uceFault_arrayAlloc3(i) || uceFault_arrayAlloc3(i)) {
         assertEq(a[0], 0);
         assertEq(a[3], 3);
         return a.length;
     }
     assertRecoveredOnBailout(a, true);
     return 0;
 };
--- a/js/src/jit-test/tests/ion/recover-cow-arrays.js
+++ b/js/src/jit-test/tests/ion/recover-cow-arrays.js
@@ -23,224 +23,190 @@ var uceFault = function (i) {
     return false;
 };
 
 // This function is used to ensure that we do escape the array, and thus prevent
 // any escape analysis.
 var global_arr;
 function escape(arr) { global_arr = arr; }
 
-// Check Array length defined by the literal.
-function array0Length(i) {
-    var a = [];
-    assertRecoveredOnBailout(a, true);
-    return a.length;
+function checkCOW() {
+    assertEq(hasCopyOnWriteElements([1, 2, 3, 4]), false);
+    // If this fails, we should probably update the tests below!
+    assertEq(hasCopyOnWriteElements([1, 2, 3, 4, 5, 6, 7]), true);
 }
+checkCOW();
 
-function array0LengthBail(i) {
-    var a = [];
-    resumeHere(i);
+function arrayLength(i) {
+    var a = [1, 2, 3, 4, 5, 6, 7];
     assertRecoveredOnBailout(a, true);
     return a.length;
 }
 
-function array1Length(i) {
-    var a = [1];
-    assertRecoveredOnBailout(a, true);
-    return a.length;
-}
-
-function array1LengthBail(i) {
-    var a = [1];
-    resumeHere(i);
-    assertRecoveredOnBailout(a, true);
-    return a.length;
-}
-
-function array2Length(i) {
-    var a = [1, 2];
-    assertRecoveredOnBailout(a, true);
-    return a.length;
-}
-
-function array2LengthBail(i) {
-    var a = [1, 2];
+function arrayLengthBail(i) {
+    var a = [1, 2, 3, 4, 5, 6, 7];
     resumeHere(i);
     assertRecoveredOnBailout(a, true);
     return a.length;
 }
 
-
-// Check Array content
-function array1Content(i) {
-    var a = [42];
-    assertEq(a[0], 42);
+function arrayContent(i) {
+    var a = [1, 2, 3, 4, 5, 6, 7];
+    assertEq(a[0], 1);
+    assertEq(a[1], 2);
+    assertEq(a[6], 7);
     assertRecoveredOnBailout(a, true);
     return a.length;
 }
-function array1ContentBail0(i) {
-    var a = [42];
+
+function arrayContentBail0(i) {
+    var a = [1, 2, 3, 4, 5, 6, 7];
     resumeHere(i);
-    assertEq(a[0], 42);
+    assertEq(a[0], 1);
+    assertEq(a[1], 2);
+    assertEq(a[6], 7);
     assertRecoveredOnBailout(a, true);
     return a.length;
 }
-function array1ContentBail1(i) {
-    var a = [42];
-    assertEq(a[0], 42);
+
+function arrayContentBail1(i) {
+    var a = [1, 2, 3, 4, 5, 6, 7];
+    assertEq(a[0], 1);
+    resumeHere(i);
+    assertEq(a[1], 2);
+    assertEq(a[6], 7);
+    assertRecoveredOnBailout(a, true);
+    return a.length;
+}
+
+function arrayContentBail2(i) {
+    var a = [1, 2, 3, 4, 5, 6, 7];
+    assertEq(a[0], 1);
+    assertEq(a[1], 2);
+    assertEq(a[6], 7);
     resumeHere(i);
     assertRecoveredOnBailout(a, true);
     return a.length;
 }
 
-function array2Content(i) {
-    var a = [1, 2];
-    assertEq(a[0], 1);
-    assertEq(a[1], 2);
-    assertRecoveredOnBailout(a, true);
-    return a.length;
-}
-
-function array2ContentBail0(i) {
-    var a = [1, 2];
-    resumeHere(i);
-    assertEq(a[0], 1);
-    assertEq(a[1], 2);
-    assertRecoveredOnBailout(a, true);
-    return a.length;
-}
-
-function array2ContentBail1(i) {
-    var a = [1, 2];
-    assertEq(a[0], 1);
-    resumeHere(i);
-    assertEq(a[1], 2);
-    assertRecoveredOnBailout(a, true);
-    return a.length;
-}
-
-function array2ContentBail2(i) {
-    var a = [1, 2];
-    assertEq(a[0], 1);
-    assertEq(a[1], 2);
+function arrayContentBail3(i) {
+    var a = ["a1", "a2", "a3", "a4", "a5", "a6", "a7"];
+    assertEq(a[0], "a1");
+    assertEq(a[1], "a2");
+    assertEq(a[6], "a7");
     resumeHere(i);
     assertRecoveredOnBailout(a, true);
     return a.length;
 }
 
 function arrayWrite1(i) {
-    var a = [1, 2];
+    var a = [1, 2, 3, 4, 5, 6, 7];
     a[0] = 42;
     assertEq(a[0], 42);
     assertEq(a[1], 2);
+    assertEq(a[5], 6);
     assertRecoveredOnBailout(a, true);
     return a.length;
 }
 
 // We don't handle length sets yet.
 function arrayWrite2(i) {
-    var a = [1, 2];
+    var a = [1, 2, 3, 4, 5, 6, 7];
     a.length = 1;
     assertEq(a[0], 1);
     assertEq(a[1], undefined);
     assertRecoveredOnBailout(a, false);
     return a.length;
 }
 
 function arrayWrite3(i) {
-    var a = [1, 2, 0];
+    var a = [1, 2, 0, 9, 8, 7, 6];
     if (i % 2 === 1)
 	a[0] = 2;
     assertEq(a[0], 1 + (i % 2));
     assertRecoveredOnBailout(a, true);
     if (i % 2 === 1)
 	bailout();
     assertEq(a[0], 1 + (i % 2));
     return a.length;
 }
 
 function arrayWrite4(i) {
-    var a = [1, 2, 0];
+    var a = [1, 2, 0, 9, 8, 7, 6];
     for (var x = 0; x < 2; x++) {
 	if (x % 2 === 1)
 	    bailout();
 	else
 	    a[0] = a[0] + 1;
     }
     assertEq(a[0], 2);
     assertEq(a[1], 2);
     assertRecoveredOnBailout(a, true);
     return a.length;
 }
 
 function arrayWriteDoubles(i) {
-    var a = [0, 0, 0];
+    var a = [0, 0, 0, 0, 0, 0, 0];
     a[0] = 3.14;
     // MConvertElementsToDoubles is only used for loads inside a loop.
     for (var x = 0; x < 2; x++) {
         assertEq(a[0], 3.14);
         assertEq(a[1], 0);
     }
     assertRecoveredOnBailout(a, true);
     return a.length;
 }
 
 // Check escape analysis in case of holes.
 function arrayHole0(i) {
-    var a = [1,,3];
+    var a = [1,,3,4,5,6,7];
     assertEq(a[0], 1);
     assertEq(a[1], undefined);
     assertEq(a[2], 3);
     // need to check for holes.
     assertRecoveredOnBailout(a, false);
     return a.length;
 }
 
 // Same test as the previous one, but the Array.prototype is changed to return
 // "100" when we request for the element "1".
 function arrayHole1(i) {
-    var a = [1,,3];
+    var a = [1,,3,4,5,6,7];
     assertEq(a[0], 1);
     assertEq(a[1], 100);
     assertEq(a[2], 3);
     // need to check for holes.
     assertRecoveredOnBailout(a, false);
     return a.length;
 }
 
 function build(l) { var arr = []; for (var i = 0; i < l; i++) arr.push(i); return arr }
 var uceFault_arrayAlloc3 = eval(uneval(uceFault).replace('uceFault', 'uceFault_arrayAlloc3'));
 function arrayAlloc(i) {
-    var a = [0,1,2,3];
+    var a = [0,1,2,3,4,5,6];
     if (uceFault_arrayAlloc3(i) || uceFault_arrayAlloc3(i)) {
         assertEq(a[0], 0);
         assertEq(a[3], 3);
         return a.length;
     }
     assertRecoveredOnBailout(a, true);
     return 0;
 };
 
 // Prevent compilation of the top-level
 eval(uneval(resumeHere));
 
 for (var i = 0; i < 100; i++) {
-    array0Length(i);
-    array0LengthBail(i);
-    array1Length(i);
-    array1LengthBail(i);
-    array2Length(i);
-    array2LengthBail(i);
-    array1Content(i);
-    array1ContentBail0(i);
-    array1ContentBail1(i);
-    array2Content(i);
-    array2ContentBail0(i);
-    array2ContentBail1(i);
-    array2ContentBail2(i);
+    arrayLength(i);
+    arrayLengthBail(i);
+    arrayContent(i);
+    arrayContentBail0(i);
+    arrayContentBail1(i);
+    arrayContentBail2(i);
+    arrayContentBail3(i);
     arrayWrite1(i);
     arrayWrite2(i);
     arrayWrite3(i);
     arrayWrite4(i);
     arrayWriteDoubles(i);
     arrayHole0(i);
     arrayAlloc(i);
 }
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -5557,16 +5557,26 @@ static bool
 IsUnboxedObject(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setBoolean(args.get(0).isObject() &&
                            args[0].toObject().is<UnboxedPlainObject>());
     return true;
 }
 
+static bool
+HasCopyOnWriteElements(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    args.rval().setBoolean(args.get(0).isObject() &&
+                           args[0].toObject().isNative() &&
+                           args[0].toObject().as<NativeObject>().denseElementsAreCopyOnWrite());
+    return true;
+}
+
 // Set the profiling stack for each cooperating context in a runtime.
 static bool
 EnsureAllContextProfilingStacks(JSContext* cx)
 {
     for (const CooperatingContext& target : cx->runtime()->cooperatingContexts()) {
         ShellContext* sc = GetShellContext(target.context());
         if (!EnsureGeckoProfilingStackInstalled(target.context(), sc))
             return false;
@@ -7051,16 +7061,20 @@ JS_FN_HELP("parseBin", BinParse, 1, 0,
     JS_FN_HELP("unboxedObjectsEnabled", UnboxedObjectsEnabled, 0, 0,
 "unboxedObjectsEnabled()",
 "  Return true if unboxed objects are enabled."),
 
     JS_FN_HELP("isUnboxedObject", IsUnboxedObject, 1, 0,
 "isUnboxedObject(o)",
 "  Return true iff the object is an unboxed object."),
 
+    JS_FN_HELP("hasCopyOnWriteElements", HasCopyOnWriteElements, 1, 0,
+"hasCopyOnWriteElements(o)",
+"  Return true iff the object has copy-on-write dense elements."),
+
     JS_FN_HELP("stackPointerInfo", StackPointerInfo, 0, 0,
 "stackPointerInfo()",
 "  Return an int32 value which corresponds to the offset of the latest stack\n"
 "  pointer, such that one can take the differences of 2 to estimate a frame-size."),
 
     JS_FN_HELP("entryPoints", EntryPoints, 1, 0,
 "entryPoints(params)",
 "Carry out some JSAPI operation as directed by |params|, and return an array of\n"