diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/builtin/Function.js | 476 |
1 files changed, 476 insertions, 0 deletions
diff --git a/js/src/builtin/Function.js b/js/src/builtin/Function.js new file mode 100644 index 0000000000..e213b82cd1 --- /dev/null +++ b/js/src/builtin/Function.js @@ -0,0 +1,476 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// ES7 draft (January 21, 2016) 19.2.3.2 Function.prototype.bind +function FunctionBind(thisArg, ...boundArgs) { + // Step 1. + var target = this; + // Step 2. + if (!IsCallable(target)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "Function", "bind", target); + } + + // Step 3 (implicit). + // Step 4. + var F; + var argCount = boundArgs.length; + switch (argCount) { + case 0: + F = bind_bindFunction0(target, thisArg, boundArgs); + break; + case 1: + F = bind_bindFunction1(target, thisArg, boundArgs); + break; + case 2: + F = bind_bindFunction2(target, thisArg, boundArgs); + break; + default: + F = bind_bindFunctionN(target, thisArg, boundArgs); + } + + // Steps 5-11. + FinishBoundFunctionInit(F, target, argCount); + + // Ensure that the apply intrinsic has been cloned so it can be baked into + // JIT code. + void std_Function_apply; + + // Step 12. + return F; +} +/** + * bind_bindFunction{0,1,2} are special cases of the generic bind_bindFunctionN + * below. They avoid the need to merge the lists of bound arguments and call + * arguments to the bound function into a new list which is then used in a + * 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. + * + * Note: We use the '$' prefix on the function to force it to be an extended + * function so that `finishBoundFunctionInit` can track length. + */ +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); + case 1: + return constructContentFunction(fun, newTarget, SPREAD(arguments, 1)); + 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); + } + } + }; +} + +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); + case 1: + return constructContentFunction( + fun, + newTarget, + bound1, + SPREAD(arguments, 1) + ); + case 2: + return constructContentFunction( + fun, + newTarget, + bound1, + SPREAD(arguments, 2) + ); + case 3: + return constructContentFunction( + fun, + newTarget, + bound1, + SPREAD(arguments, 3) + ); + case 4: + return constructContentFunction( + fun, + newTarget, + bound1, + SPREAD(arguments, 4) + ); + case 5: + return constructContentFunction( + fun, + newTarget, + bound1, + SPREAD(arguments, 5) + ); + } + } else { + switch (arguments.length) { + case 0: + return callContentFunction(fun, thisArg, bound1); + case 1: + return callContentFunction( + fun, + thisArg, + bound1, + SPREAD(arguments, 1) + ); + case 2: + return callContentFunction( + fun, + thisArg, + bound1, + SPREAD(arguments, 2) + ); + 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) + ); + } + } + + 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); + case 1: + return constructContentFunction( + fun, + newTarget, + bound1, + bound2, + SPREAD(arguments, 1) + ); + case 2: + return constructContentFunction( + fun, + newTarget, + bound1, + bound2, + SPREAD(arguments, 2) + ); + case 3: + return constructContentFunction( + fun, + newTarget, + bound1, + bound2, + SPREAD(arguments, 3) + ); + case 4: + return constructContentFunction( + fun, + newTarget, + bound1, + bound2, + SPREAD(arguments, 4) + ); + case 5: + return constructContentFunction( + fun, + newTarget, + bound1, + bound2, + SPREAD(arguments, 5) + ); + } + } else { + switch (arguments.length) { + case 0: + return callContentFunction(fun, thisArg, bound1, bound2); + case 1: + return callContentFunction( + fun, + thisArg, + bound1, + bound2, + SPREAD(arguments, 1) + ); + case 2: + return callContentFunction( + fun, + thisArg, + bound1, + bound2, + SPREAD(arguments, 2) + ); + 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) + ); + } + } + + 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); + } + + 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_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)); + case 3: + return callContentFunction(fun, thisArg, SPREAD(args, 3)); + case 4: + return callContentFunction(fun, thisArg, SPREAD(args, 4)); + case 5: + return callContentFunction(fun, thisArg, SPREAD(args, 5)); + case 6: + return callContentFunction(fun, thisArg, SPREAD(args, 6)); + case 7: + return callContentFunction(fun, thisArg, SPREAD(args, 7)); + case 8: + return callContentFunction(fun, thisArg, SPREAD(args, 8)); + case 9: + return callContentFunction(fun, thisArg, SPREAD(args, 9)); + default: + return FUN_APPLY(fun, thisArg, args); + } +} + +function bind_constructFunctionN(fun, newTarget, args) { + switch (args.length) { + case 1: + return constructContentFunction(fun, newTarget, SPREAD(args, 1)); + case 2: + return constructContentFunction(fun, newTarget, SPREAD(args, 2)); + case 3: + return constructContentFunction(fun, newTarget, SPREAD(args, 3)); + case 4: + return constructContentFunction(fun, newTarget, SPREAD(args, 4)); + case 5: + return constructContentFunction(fun, newTarget, SPREAD(args, 5)); + case 6: + return constructContentFunction(fun, newTarget, SPREAD(args, 6)); + case 7: + return constructContentFunction(fun, newTarget, SPREAD(args, 7)); + case 8: + return constructContentFunction(fun, newTarget, SPREAD(args, 8)); + case 9: + return constructContentFunction(fun, newTarget, SPREAD(args, 9)); + case 10: + return constructContentFunction(fun, newTarget, SPREAD(args, 10)); + case 11: + return constructContentFunction(fun, newTarget, SPREAD(args, 11)); + case 12: + return constructContentFunction(fun, newTarget, SPREAD(args, 12)); + default: + assert( + args.length !== 0, + "bound function construction without args should be handled by caller" + ); + return ConstructFunction(fun, newTarget, args); + } +} |