Bug 1370195 - Use fewer array allocations when invoking bound functions with many arguments. r=till
authorAndré Bargull <andre.bargull@gmail.com>
Mon, 05 Jun 2017 13:49:29 +0200
changeset 410711 dbedc1b9a3cc6924c6ffbab4b85045aca8712054
parent 410710 d912b21f2ae6479ed294917909f0cfcbdcc0208a
child 410712 f781899dd0257002f806927a3474261aae3deafb
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill
bugs1370195
milestone55.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 1370195 - Use fewer array allocations when invoking bound functions with many arguments. r=till
js/src/builtin/Function.js
--- a/js/src/builtin/Function.js
+++ b/js/src/builtin/Function.js
@@ -45,21 +45,30 @@ function FunctionBind(thisArg, ...boundA
  * destructuring call of the bound function.
  *
  * All three of these functions again have special-cases for call argument
  * counts between 0 and 5. For calls with 6+ arguments, all - bound and call -
  * arguments are copied into an array before invoking the generic call and
  * construct helper functions. This avoids having to use rest parameters and
  * destructuring in the fast path.
  *
+ * Directly embedding the for-loop to combine bound and call arguments may
+ * inhibit inlining of the bound function, so we use a separate combiner
+ * function to perform this task. This combiner function is created lazily to
+ * ensure we only pay its construction cost when needed.
+ *
  * All bind_bindFunction{X} functions have the same signature to enable simple
  * reading out of closed-over state by debugging functions.
  */
 function bind_bindFunction0(fun, thisArg, boundArgs) {
     return function bound() {
+        // Ensure we allocate a call-object slot for |boundArgs|, so the
+        // debugger can access this value.
+        if (false) void boundArgs;
+
         var newTarget;
         if (_IsConstructing()) {
             newTarget = new.target;
             if (newTarget === bound)
                 newTarget = fun;
             switch (arguments.length) {
               case 0:
                 return constructContentFunction(fun, newTarget);
@@ -68,41 +77,49 @@ function bind_bindFunction0(fun, thisArg
               case 2:
                 return constructContentFunction(fun, newTarget, SPREAD(arguments, 2));
               case 3:
                 return constructContentFunction(fun, newTarget, SPREAD(arguments, 3));
               case 4:
                 return constructContentFunction(fun, newTarget, SPREAD(arguments, 4));
               case 5:
                 return constructContentFunction(fun, newTarget, SPREAD(arguments, 5));
+              default:
+                var args = FUN_APPLY(bind_mapArguments, null, arguments);
+                return bind_constructFunctionN(fun, newTarget, args);
             }
         } else {
             switch (arguments.length) {
               case 0:
                 return callContentFunction(fun, thisArg);
               case 1:
                 return callContentFunction(fun, thisArg, SPREAD(arguments, 1));
               case 2:
                 return callContentFunction(fun, thisArg, SPREAD(arguments, 2));
               case 3:
                 return callContentFunction(fun, thisArg, SPREAD(arguments, 3));
               case 4:
                 return callContentFunction(fun, thisArg, SPREAD(arguments, 4));
               case 5:
                 return callContentFunction(fun, thisArg, SPREAD(arguments, 5));
+              default:
+                return FUN_APPLY(fun, thisArg, arguments);
             }
         }
-        var callArgs = FUN_APPLY(bind_mapArguments, null, arguments);
-        return bind_invokeFunctionN(fun, thisArg, newTarget, boundArgs, callArgs);
     };
 }
 
 function bind_bindFunction1(fun, thisArg, boundArgs) {
     var bound1 = boundArgs[0];
+    var combiner = null;
     return function bound() {
+        // Ensure we allocate a call-object slot for |boundArgs|, so the
+        // debugger can access this value.
+        if (false) void boundArgs;
+
         var newTarget;
         if (_IsConstructing()) {
             newTarget = new.target;
             if (newTarget === bound)
                 newTarget = fun;
             switch (arguments.length) {
               case 0:
                 return constructContentFunction(fun, newTarget, bound1);
@@ -128,25 +145,44 @@ function bind_bindFunction1(fun, thisArg
               case 3:
                 return callContentFunction(fun, thisArg, bound1, SPREAD(arguments, 3));
               case 4:
                 return callContentFunction(fun, thisArg, bound1, SPREAD(arguments, 4));
               case 5:
                 return callContentFunction(fun, thisArg, bound1, SPREAD(arguments, 5));
             }
         }
-        var callArgs = FUN_APPLY(bind_mapArguments, null, arguments);
-        return bind_invokeFunctionN(fun, thisArg, newTarget, boundArgs, callArgs);
+
+        if (combiner === null) {
+            combiner = function() {
+                var callArgsCount = arguments.length;
+                var args = std_Array(1 + callArgsCount);
+                _DefineDataProperty(args, 0, bound1);
+                for (var i = 0; i < callArgsCount; i++)
+                    _DefineDataProperty(args, i + 1, arguments[i]);
+                return args;
+            };
+        }
+
+        var args = FUN_APPLY(combiner, null, arguments);
+        if (newTarget === undefined)
+            return bind_applyFunctionN(fun, thisArg, args);
+        return bind_constructFunctionN(fun, newTarget, args);
     };
 }
 
 function bind_bindFunction2(fun, thisArg, boundArgs) {
     var bound1 = boundArgs[0];
     var bound2 = boundArgs[1];
+    var combiner = null;
     return function bound() {
+        // Ensure we allocate a call-object slot for |boundArgs|, so the
+        // debugger can access this value.
+        if (false) void boundArgs;
+
         var newTarget;
         if (_IsConstructing()) {
             newTarget = new.target;
             if (newTarget === bound)
                 newTarget = fun;
             switch (arguments.length) {
               case 0:
                 return constructContentFunction(fun, newTarget, bound1, bound2);
@@ -172,62 +208,80 @@ function bind_bindFunction2(fun, thisArg
               case 3:
                 return callContentFunction(fun, thisArg, bound1, bound2, SPREAD(arguments, 3));
               case 4:
                 return callContentFunction(fun, thisArg, bound1, bound2, SPREAD(arguments, 4));
               case 5:
                 return callContentFunction(fun, thisArg, bound1, bound2, SPREAD(arguments, 5));
             }
         }
-        var callArgs = FUN_APPLY(bind_mapArguments, null, arguments);
-        return bind_invokeFunctionN(fun, thisArg, newTarget, boundArgs, callArgs);
+
+        if (combiner === null) {
+            combiner = function() {
+                var callArgsCount = arguments.length;
+                var args = std_Array(2 + callArgsCount);
+                _DefineDataProperty(args, 0, bound1);
+                _DefineDataProperty(args, 1, bound2);
+                for (var i = 0; i < callArgsCount; i++)
+                    _DefineDataProperty(args, i + 2, arguments[i]);
+                return args;
+            };
+        }
+
+        var args = FUN_APPLY(combiner, null, arguments);
+        if (newTarget === undefined)
+            return bind_applyFunctionN(fun, thisArg, args);
+        return bind_constructFunctionN(fun, newTarget, args);
     };
 }
 
 function bind_bindFunctionN(fun, thisArg, boundArgs) {
     assert(boundArgs.length > 2, "Fast paths should be used for few-bound-args cases.");
+    var combiner = null;
     return function bound() {
         var newTarget;
         if (_IsConstructing()) {
             newTarget = new.target;
             if (newTarget === bound)
                 newTarget = fun;
         }
         if (arguments.length === 0) {
             if (newTarget !== undefined)
                 return bind_constructFunctionN(fun, newTarget, boundArgs);
-
             return bind_applyFunctionN(fun, thisArg, boundArgs);
         }
-        var callArgs = FUN_APPLY(bind_mapArguments, null, arguments);
-        return bind_invokeFunctionN(fun, thisArg, newTarget, boundArgs, callArgs);
+
+        if (combiner === null) {
+            combiner = function() {
+                var boundArgsCount = boundArgs.length;
+                var callArgsCount = arguments.length;
+                var args = std_Array(boundArgsCount + callArgsCount);
+                for (var i = 0; i < boundArgsCount; i++)
+                    _DefineDataProperty(args, i, boundArgs[i]);
+                for (var i = 0; i < callArgsCount; i++)
+                    _DefineDataProperty(args, i + boundArgsCount, arguments[i]);
+                return args;
+            };
+        }
+
+        var args = FUN_APPLY(combiner, null, arguments);
+        if (newTarget !== undefined)
+            return bind_constructFunctionN(fun, newTarget, args);
+        return bind_applyFunctionN(fun, thisArg, args);
     };
 }
 
 function bind_mapArguments() {
     var len = arguments.length;
     var args = std_Array(len);
     for (var i = 0; i < len; i++)
         _DefineDataProperty(args, i, arguments[i]);
     return args;
 }
 
-function bind_invokeFunctionN(fun, thisArg, newTarget, boundArgs, callArgs) {
-    var boundArgsCount = boundArgs.length;
-    var callArgsCount = callArgs.length;
-    var args = std_Array(boundArgsCount + callArgsCount);
-    for (var i = 0; i < boundArgsCount; i++)
-        _DefineDataProperty(args, i, boundArgs[i]);
-    for (var i = 0; i < callArgsCount; i++)
-        _DefineDataProperty(args, i + boundArgsCount, callArgs[i]);
-    if (newTarget !== undefined)
-        return bind_constructFunctionN(fun, newTarget, args);
-    return bind_applyFunctionN(fun, thisArg, args);
-}
-
 function bind_applyFunctionN(fun, thisArg, args) {
     switch (args.length) {
       case 0:
         return callContentFunction(fun, thisArg);
       case 1:
         return callContentFunction(fun, thisArg, SPREAD(args, 1));
       case 2:
         return callContentFunction(fun, thisArg, SPREAD(args, 2));