Speed up Function.prototype.bind a bit by making it a builtin
authorsbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 13 Sep 2016 02:57:37 +0000 (02:57 +0000)
committersbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 13 Sep 2016 02:57:37 +0000 (02:57 +0000)
https://bugs.webkit.org/show_bug.cgi?id=161879

Reviewed by Filip Pizlo.

JSTests:

* microbenchmarks/function-bind-inlining.js: Added.
(assert):
(test):
(test2):
(foo):
* microbenchmarks/function-bind-no-inlining.js: Added.
(assert):
(test):
(test2):
(foo):

LayoutTests:

* js/dom/function-bind-expected.txt:

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@205848 268f45cc-cd09-0410-ab3c-d52691b4dbfc

JSTests/ChangeLog
JSTests/microbenchmarks/function-bind-inlining.js [new file with mode: 0644]
JSTests/microbenchmarks/function-bind-no-inlining.js [new file with mode: 0644]
LayoutTests/ChangeLog
LayoutTests/js/dom/function-bind-expected.txt
Source/JavaScriptCore/builtins/BuiltinNames.h
Source/JavaScriptCore/builtins/FunctionPrototype.js
Source/JavaScriptCore/runtime/FunctionPrototype.cpp
Source/JavaScriptCore/runtime/JSGlobalObject.cpp

index 6733d48..9b5c7fa 100644 (file)
@@ -1,5 +1,23 @@
 2016-09-12  Saam Barati  <sbarati@apple.com>
 
+        Speed up Function.prototype.bind a bit by making it a builtin
+        https://bugs.webkit.org/show_bug.cgi?id=161879
+
+        Reviewed by Filip Pizlo.
+
+        * microbenchmarks/function-bind-inlining.js: Added.
+        (assert):
+        (test):
+        (test2):
+        (foo):
+        * microbenchmarks/function-bind-no-inlining.js: Added.
+        (assert):
+        (test):
+        (test2):
+        (foo):
+
+2016-09-12  Saam Barati  <sbarati@apple.com>
+
         HashMapImpl should take into account m_deleteCount in its load factor and it should be able to rehash the table to be smaller
         https://bugs.webkit.org/show_bug.cgi?id=161640
 
diff --git a/JSTests/microbenchmarks/function-bind-inlining.js b/JSTests/microbenchmarks/function-bind-inlining.js
new file mode 100644 (file)
index 0000000..f60900b
--- /dev/null
@@ -0,0 +1,28 @@
+function assert(b) {
+    if (!b)
+        throw new Error("Bad")
+}
+noInline(assert);
+
+function test(f, v, c, d) {
+    return f.bind(v, c, d);
+}
+
+function test2(f, v) {
+    return f.bind(v);
+}
+
+function foo(a,b,c,d,e,f) { return this; }
+let thisValue = {};
+let start = Date.now();
+for (let i = 0; i < 1000000; i++) {
+    let f = test(foo, thisValue, 20, 30);
+    assert(f(foo, thisValue, 20, 30) === thisValue);
+}
+for (let i = 0; i < 1000000; i++) {
+    let f = test2(foo, thisValue);
+    assert(f(foo, thisValue, 20, 30) === thisValue);
+}
+const verbose = false;
+if (verbose)
+    print(Date.now() - start);
diff --git a/JSTests/microbenchmarks/function-bind-no-inlining.js b/JSTests/microbenchmarks/function-bind-no-inlining.js
new file mode 100644 (file)
index 0000000..0f6ba22
--- /dev/null
@@ -0,0 +1,31 @@
+
+function assert(b) {
+    if (!b)
+        throw new Error("Bad")
+}
+noInline(assert);
+
+function test(f, v, c, d) {
+    return f.bind(v, c, d);
+}
+noInline(test);
+
+function test2(f, v) {
+    return f.bind(v);
+}
+noInline(test);
+
+function foo(a,b,c,d,e,f) { return this; }
+let thisValue = {};
+let start = Date.now();
+for (let i = 0; i < 1000000; i++) {
+    let f = test(foo, thisValue, 20, 30);
+    assert(f(foo, thisValue, 20, 30) === thisValue);
+}
+for (let i = 0; i < 1000000; i++) {
+    let f = test2(foo, thisValue);
+    assert(f(foo, thisValue, 20, 30) === thisValue);
+}
+const verbose = false;
+if (verbose)
+    print(Date.now() - start);
index 2b4e53e..bdf4176 100644 (file)
@@ -1,3 +1,12 @@
+2016-09-12  Saam Barati  <sbarati@apple.com>
+
+        Speed up Function.prototype.bind a bit by making it a builtin
+        https://bugs.webkit.org/show_bug.cgi?id=161879
+
+        Reviewed by Filip Pizlo.
+
+        * js/dom/function-bind-expected.txt:
+
 2016-09-12  Nan Wang  <n_wang@apple.com>
 
         AX: Crash at WebCore::Range::compareBoundaryPoints(WebCore::Range::CompareHow, WebCore::Range const&, int&) const + 23
index b3186fb..2c98104 100644 (file)
@@ -23,7 +23,7 @@ PASS h instanceof H is true
 PASS "prototype" in F is true
 PASS "prototype" in G is false
 PASS "prototype" in H is false
-PASS Function.bind.call(undefined) threw exception TypeError: Type error.
+PASS Function.bind.call(undefined) threw exception TypeError: |this| is not a function inside Function.prototype.bind.
 PASS abcAt(1) is "b"
 PASS new abcAt(1) threw exception TypeError: function is not a constructor (evaluating 'new abcAt(1)').
 PASS boundFunctionPrototypeAccessed is false
index 204cc3c..4f73403 100644 (file)
@@ -160,7 +160,9 @@ namespace JSC {
     macro(regExpTestFast) \
     macro(stringIncludesInternal) \
     macro(stringSplitFast) \
-    macro(stringSubstrInternal)
+    macro(stringSubstrInternal) \
+    macro(makeBoundFunction) \
+    macro(hasOwnLengthProperty) \
 
 
 #define INITIALIZE_PRIVATE_TO_PUBLIC_ENTRY(name) m_privateToPublicMap.add(m_##name##PrivateName.impl(), &m_##name);
index c9d5df3..a55e0f3 100644 (file)
@@ -56,3 +56,40 @@ function symbolHasInstance(value)
     let target = this.prototype;
     return @instanceOf(value, target);
 }
+
+function bind(thisValue)
+{
+    "use strict";
+
+    let target = this;
+    if (typeof target !== "function")
+        throw new @TypeError("|this| is not a function inside Function.prototype.bind");
+
+    let argumentCount = arguments.length;
+    let boundArgs = null;
+    let numBoundArgs = 0;
+    if (argumentCount > 1) {
+        numBoundArgs = argumentCount - 1;
+        boundArgs = @newArrayWithSize(numBoundArgs);
+        for (let i = 0; i < numBoundArgs; i++)
+            boundArgs[i] = arguments[i + 1];
+    }
+
+    let length = 0;
+    if (@hasOwnLengthProperty(target)) {
+        let lengthValue = target.length;
+        if (typeof lengthValue === "number") {
+            lengthValue = lengthValue | 0;
+            // Note that we only care about positive lengthValues, however, this comparision
+            // against numBoundArgs suffices to prove we're not a negative number.
+            if (lengthValue > numBoundArgs)
+                length = lengthValue - numBoundArgs;
+        }
+    }
+
+    let name = target.name;
+    if (typeof name !== "string")
+        name = "";
+
+    return @makeBoundFunction(target, arguments[0], boundArgs, length, name);
+}
index 0dbcb21..ce55e47 100644 (file)
@@ -40,7 +40,6 @@ STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(FunctionPrototype);
 const ClassInfo FunctionPrototype::s_info = { "Function", &Base::s_info, 0, CREATE_METHOD_TABLE(FunctionPrototype) };
 
 static EncodedJSValue JSC_HOST_CALL functionProtoFuncToString(ExecState*);
-static EncodedJSValue JSC_HOST_CALL functionProtoFuncBind(ExecState*);
 
 FunctionPrototype::FunctionPrototype(VM& vm, Structure* structure)
     : InternalFunction(vm, structure)
@@ -63,9 +62,7 @@ void FunctionPrototype::addFunctionProperties(ExecState* exec, JSGlobalObject* g
     *applyFunction = putDirectBuiltinFunctionWithoutTransition(vm, globalObject, vm.propertyNames->builtinNames().applyPublicName(), functionPrototypeApplyCodeGenerator(vm), DontEnum);
     *callFunction = putDirectBuiltinFunctionWithoutTransition(vm, globalObject, vm.propertyNames->builtinNames().callPublicName(), functionPrototypeCallCodeGenerator(vm), DontEnum);
     *hasInstanceSymbolFunction = putDirectBuiltinFunction(vm, globalObject, vm.propertyNames->hasInstanceSymbol, functionPrototypeSymbolHasInstanceCodeGenerator(vm), DontDelete | ReadOnly | DontEnum);
-
-    JSFunction* bindFunction = JSFunction::create(vm, globalObject, 1, vm.propertyNames->bind.string(), functionProtoFuncBind);
-    putDirectWithoutTransition(vm, vm.propertyNames->bind, bindFunction, DontEnum);
+    putDirectBuiltinFunctionWithoutTransition(vm, globalObject, vm.propertyNames->bind, functionPrototypeBindCodeGenerator(vm), DontEnum);
 }
 
 static EncodedJSValue JSC_HOST_CALL callFunctionPrototype(ExecState*)
@@ -124,58 +121,4 @@ EncodedJSValue JSC_HOST_CALL functionProtoFuncToString(ExecState* exec)
     return throwVMTypeError(exec, scope);
 }
 
-// 15.3.4.5 Function.prototype.bind (thisArg [, arg1 [, arg2, ...]])
-EncodedJSValue JSC_HOST_CALL functionProtoFuncBind(ExecState* exec)
-{
-    VM& vm = exec->vm();
-    auto scope = DECLARE_THROW_SCOPE(vm);
-    JSGlobalObject* globalObject = exec->callee()->globalObject();
-
-    // Let Target be the this value.
-    JSValue target = exec->thisValue();
-
-    // If IsCallable(Target) is false, throw a TypeError exception.
-    CallData callData;
-    CallType callType = getCallData(target, callData);
-    if (callType == CallType::None)
-        return throwVMTypeError(exec, scope);
-    // Primitive values are not callable.
-    ASSERT(target.isObject());
-    JSObject* targetObject = asObject(target);
-
-    // Let A be a new (possibly empty) internal list of all of the argument values provided after thisArg (arg1, arg2 etc), in order.
-    size_t numBoundArgs = exec->argumentCount() > 1 ? exec->argumentCount() - 1 : 0;
-    JSArray* boundArgs;
-    if (numBoundArgs) {
-        boundArgs = JSArray::tryCreateUninitialized(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), numBoundArgs);
-        if (!boundArgs)
-            return JSValue::encode(throwOutOfMemoryError(exec, scope));
-        
-        for (size_t i = 0; i < numBoundArgs; ++i)
-            boundArgs->initializeIndex(vm, i, exec->argument(i + 1));
-    } else
-        boundArgs = nullptr;
-
-    // If the [[Class]] internal property of Target is "Function", then ...
-    // Else set the length own property of F to 0.
-    unsigned length = 0;
-    if (targetObject->hasOwnProperty(exec, exec->propertyNames().length)) {
-        if (UNLIKELY(scope.exception()))
-            return JSValue::encode(jsUndefined());
-
-        // a. Let L be the length property of Target minus the length of A.
-        // b. Set the length own property of F to either 0 or L, whichever is larger.
-        JSValue lengthValue = target.get(exec, exec->propertyNames().length);
-        if (lengthValue.isNumber()) {
-            unsigned targetLength = (unsigned)lengthValue.asNumber();
-            if (targetLength > numBoundArgs)
-                length = targetLength - numBoundArgs;
-        }
-    }
-
-    JSValue nameProp = target.get(exec, exec->propertyNames().name);
-    JSString* name = nameProp.isString() ? nameProp.toString(exec) : jsEmptyString(exec);
-    return JSValue::encode(JSBoundFunction::create(vm, exec, globalObject, targetObject, exec->argument(0), boundArgs, length, name->value(exec)));
-}
-
 } // namespace JSC
index d30a224..d74292d 100644 (file)
@@ -199,6 +199,28 @@ static JSValue createConsoleProperty(VM& vm, JSObject* object)
     return ConsoleObject::create(vm, global, ConsoleObject::createStructure(vm, global, constructEmptyObject(global->globalExec())));
 }
 
+static EncodedJSValue JSC_HOST_CALL makeBoundFunction(ExecState* exec)
+{
+    VM& vm = exec->vm();
+    JSGlobalObject* globalObject = exec->lexicalGlobalObject();
+
+    JSObject* target = asObject(exec->uncheckedArgument(0));
+    JSValue boundThis = exec->uncheckedArgument(1);
+    JSValue boundArgs = exec->uncheckedArgument(2);
+    JSValue length = exec->uncheckedArgument(3);
+    JSString* name = asString(exec->uncheckedArgument(4));
+
+    return JSValue::encode(JSBoundFunction::create(
+        vm, exec, globalObject, target, boundThis, boundArgs.isCell() ? jsCast<JSArray*>(boundArgs) : nullptr, length.asInt32(), name->value(exec)));
+}
+
+static EncodedJSValue JSC_HOST_CALL hasOwnLengthProperty(ExecState* exec)
+{
+    VM& vm = exec->vm();
+    JSObject* target = asObject(exec->uncheckedArgument(0));
+    return JSValue::encode(jsBoolean(target->hasOwnProperty(exec, vm.propertyNames->length)));
+}
+
 } // namespace JSC
 
 #include "JSGlobalObject.lut.h"
@@ -768,6 +790,10 @@ putDirectWithoutTransition(vm, vm.propertyNames-> jsName, lowerName ## Construct
         GlobalPropertyInfo(vm.propertyNames->builtinNames().stringIncludesInternalPrivateName(), JSFunction::create(vm, this, 1, String(), builtinStringIncludesInternal), DontEnum | DontDelete | ReadOnly),
         GlobalPropertyInfo(vm.propertyNames->builtinNames().stringSplitFastPrivateName(), JSFunction::create(vm, this, 2, String(), stringProtoFuncSplitFast), DontEnum | DontDelete | ReadOnly),
         GlobalPropertyInfo(vm.propertyNames->builtinNames().stringSubstrInternalPrivateName(), JSFunction::create(vm, this, 2, String(), builtinStringSubstrInternal), DontEnum | DontDelete | ReadOnly),
+
+        // Function prototype helpers.
+        GlobalPropertyInfo(vm.propertyNames->builtinNames().makeBoundFunctionPrivateName(), JSFunction::create(vm, this, 5, String(), makeBoundFunction), DontEnum | DontDelete | ReadOnly),
+        GlobalPropertyInfo(vm.propertyNames->builtinNames().hasOwnLengthPropertyPrivateName(), JSFunction::create(vm, this, 1, String(), hasOwnLengthProperty), DontEnum | DontDelete | ReadOnly),
     };
     addStaticGlobals(staticGlobals, WTF_ARRAY_LENGTH(staticGlobals));