Title: [205848] trunk
Revision
205848
Author
sbar...@apple.com
Date
2016-09-12 19:57:37 -0700 (Mon, 12 Sep 2016)

Log Message

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.

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:

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (205847 => 205848)


--- trunk/JSTests/ChangeLog	2016-09-13 02:12:51 UTC (rev 205847)
+++ trunk/JSTests/ChangeLog	2016-09-13 02:57:37 UTC (rev 205848)
@@ -1,5 +1,23 @@
 2016-09-12  Saam Barati  <sbar...@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  <sbar...@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
 

Added: trunk/JSTests/microbenchmarks/function-bind-inlining.js (0 => 205848)


--- trunk/JSTests/microbenchmarks/function-bind-inlining.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/function-bind-inlining.js	2016-09-13 02:57:37 UTC (rev 205848)
@@ -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);

Added: trunk/JSTests/microbenchmarks/function-bind-no-inlining.js (0 => 205848)


--- trunk/JSTests/microbenchmarks/function-bind-no-inlining.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/function-bind-no-inlining.js	2016-09-13 02:57:37 UTC (rev 205848)
@@ -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);

Modified: trunk/LayoutTests/ChangeLog (205847 => 205848)


--- trunk/LayoutTests/ChangeLog	2016-09-13 02:12:51 UTC (rev 205847)
+++ trunk/LayoutTests/ChangeLog	2016-09-13 02:57:37 UTC (rev 205848)
@@ -1,3 +1,12 @@
+2016-09-12  Saam Barati  <sbar...@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_w...@apple.com>
 
         AX: Crash at WebCore::Range::compareBoundaryPoints(WebCore::Range::CompareHow, WebCore::Range const&, int&) const + 23

Modified: trunk/LayoutTests/js/dom/function-bind-expected.txt (205847 => 205848)


--- trunk/LayoutTests/js/dom/function-bind-expected.txt	2016-09-13 02:12:51 UTC (rev 205847)
+++ trunk/LayoutTests/js/dom/function-bind-expected.txt	2016-09-13 02:57:37 UTC (rev 205848)
@@ -23,7 +23,7 @@
 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

Modified: trunk/Source/_javascript_Core/builtins/BuiltinNames.h (205847 => 205848)


--- trunk/Source/_javascript_Core/builtins/BuiltinNames.h	2016-09-13 02:12:51 UTC (rev 205847)
+++ trunk/Source/_javascript_Core/builtins/BuiltinNames.h	2016-09-13 02:57:37 UTC (rev 205848)
@@ -160,7 +160,9 @@
     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);

Modified: trunk/Source/_javascript_Core/builtins/FunctionPrototype.js (205847 => 205848)


--- trunk/Source/_javascript_Core/builtins/FunctionPrototype.js	2016-09-13 02:12:51 UTC (rev 205847)
+++ trunk/Source/_javascript_Core/builtins/FunctionPrototype.js	2016-09-13 02:57:37 UTC (rev 205848)
@@ -56,3 +56,40 @@
     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);
+}

Modified: trunk/Source/_javascript_Core/runtime/FunctionPrototype.cpp (205847 => 205848)


--- trunk/Source/_javascript_Core/runtime/FunctionPrototype.cpp	2016-09-13 02:12:51 UTC (rev 205847)
+++ trunk/Source/_javascript_Core/runtime/FunctionPrototype.cpp	2016-09-13 02:57:37 UTC (rev 205848)
@@ -40,7 +40,6 @@
 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 @@
     *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 @@
     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

Modified: trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp (205847 => 205848)


--- trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp	2016-09-13 02:12:51 UTC (rev 205847)
+++ trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp	2016-09-13 02:57:37 UTC (rev 205848)
@@ -199,6 +199,28 @@
     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 @@
         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));
     
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to