Title: [196956] trunk/Source/_javascript_Core
Revision
196956
Author
keith_mil...@apple.com
Date
2016-02-22 14:07:16 -0800 (Mon, 22 Feb 2016)

Log Message

Bound functions should use the prototype of the function being bound
https://bugs.webkit.org/show_bug.cgi?id=154195

Reviewed by Geoffrey Garen.

Per ES6, the result of Function.prototype.bind should have the same
prototype as the the function being bound. In order to avoid creating
a new structure each time a function is bound we store the new
structure in our structure map. However, we cannot currently store
structures that have a different GlobalObject than their prototype.
In the rare case that the GlobalObject differs or the prototype of
the bindee is null we create a new structure each time. To further
minimize new structures, as well as making structure lookup faster,
we also store the structure in the RareData of the function we
are binding.

* runtime/FunctionRareData.cpp:
(JSC::FunctionRareData::visitChildren):
* runtime/FunctionRareData.h:
(JSC::FunctionRareData::getBoundFunctionStructure):
(JSC::FunctionRareData::setBoundFunctionStructure):
* runtime/JSBoundFunction.cpp:
(JSC::getBoundFunctionStructure):
(JSC::JSBoundFunction::create):
* tests/es6.yaml:
* tests/stress/bound-function-uses-prototype.js: Added.
(testChangeProto.foo):
(testChangeProto):
(testBuiltins):
* tests/stress/class-subclassing-function.js:

Modified Paths

Added Paths

Diff

Modified: trunk/Source/_javascript_Core/ChangeLog (196955 => 196956)


--- trunk/Source/_javascript_Core/ChangeLog	2016-02-22 22:02:24 UTC (rev 196955)
+++ trunk/Source/_javascript_Core/ChangeLog	2016-02-22 22:07:16 UTC (rev 196956)
@@ -1,5 +1,38 @@
 2016-02-22  Keith Miller  <keith_mil...@apple.com>
 
+        Bound functions should use the prototype of the function being bound
+        https://bugs.webkit.org/show_bug.cgi?id=154195
+
+        Reviewed by Geoffrey Garen.
+
+        Per ES6, the result of Function.prototype.bind should have the same
+        prototype as the the function being bound. In order to avoid creating
+        a new structure each time a function is bound we store the new
+        structure in our structure map. However, we cannot currently store
+        structures that have a different GlobalObject than their prototype.
+        In the rare case that the GlobalObject differs or the prototype of
+        the bindee is null we create a new structure each time. To further
+        minimize new structures, as well as making structure lookup faster,
+        we also store the structure in the RareData of the function we
+        are binding.
+
+        * runtime/FunctionRareData.cpp:
+        (JSC::FunctionRareData::visitChildren):
+        * runtime/FunctionRareData.h:
+        (JSC::FunctionRareData::getBoundFunctionStructure):
+        (JSC::FunctionRareData::setBoundFunctionStructure):
+        * runtime/JSBoundFunction.cpp:
+        (JSC::getBoundFunctionStructure):
+        (JSC::JSBoundFunction::create):
+        * tests/es6.yaml:
+        * tests/stress/bound-function-uses-prototype.js: Added.
+        (testChangeProto.foo):
+        (testChangeProto):
+        (testBuiltins):
+        * tests/stress/class-subclassing-function.js:
+
+2016-02-22  Keith Miller  <keith_mil...@apple.com>
+
         Unreviewed, fix stress test to not print on success.
 
         * tests/stress/call-apply-builtin-functions-dont-use-iterators.js:

Modified: trunk/Source/_javascript_Core/runtime/FunctionRareData.cpp (196955 => 196956)


--- trunk/Source/_javascript_Core/runtime/FunctionRareData.cpp	2016-02-22 22:02:24 UTC (rev 196955)
+++ trunk/Source/_javascript_Core/runtime/FunctionRareData.cpp	2016-02-22 22:07:16 UTC (rev 196956)
@@ -56,6 +56,7 @@
 
     rareData->m_objectAllocationProfile.visitAggregate(visitor);
     rareData->m_internalFunctionAllocationProfile.visitAggregate(visitor);
+    visitor.append(&rareData->m_boundFunctionStructure);
 }
 
 FunctionRareData::FunctionRareData(VM& vm)

Modified: trunk/Source/_javascript_Core/runtime/FunctionRareData.h (196955 => 196956)


--- trunk/Source/_javascript_Core/runtime/FunctionRareData.h	2016-02-22 22:02:24 UTC (rev 196955)
+++ trunk/Source/_javascript_Core/runtime/FunctionRareData.h	2016-02-22 22:07:16 UTC (rev 196956)
@@ -90,6 +90,9 @@
         return m_internalFunctionAllocationProfile.createAllocationStructureFromBase(vm, this, prototype, baseStructure);
     }
 
+    Structure* getBoundFunctionStructure() { return m_boundFunctionStructure.get(); }
+    void setBoundFunctionStructure(VM& vm, Structure* structure) { m_boundFunctionStructure.set(vm, this, structure); }
+
 protected:
     FunctionRareData(VM&);
     ~FunctionRareData();
@@ -114,6 +117,7 @@
     ObjectAllocationProfile m_objectAllocationProfile;
     InlineWatchpointSet m_objectAllocationProfileWatchpoint;
     InternalFunctionAllocationProfile m_internalFunctionAllocationProfile;
+    WriteBarrier<Structure> m_boundFunctionStructure;
 };
 
 } // namespace JSC

Modified: trunk/Source/_javascript_Core/runtime/JSBoundFunction.cpp (196955 => 196956)


--- trunk/Source/_javascript_Core/runtime/JSBoundFunction.cpp	2016-02-22 22:02:24 UTC (rev 196955)
+++ trunk/Source/_javascript_Core/runtime/JSBoundFunction.cpp	2016-02-22 22:07:16 UTC (rev 196956)
@@ -87,13 +87,44 @@
     return JSValue::encode(jsBoolean(boundObject->targetFunction()->hasInstance(exec, value)));
 }
 
+inline Structure* getBoundFunctionStructure(VM& vm, JSGlobalObject* globalObject, JSObject* targetFunction)
+{
+    JSValue prototype = targetFunction->structure(vm)->storedPrototype();
+    JSFunction* targetJSFunction = jsDynamicCast<JSFunction*>(targetFunction);
+
+    // We only cache the structure of the bound function if the bindee is a JSFunction since there
+    // isn't any good place to put the structure on Internal Functions.
+    if (targetJSFunction) {
+        Structure* structure = targetJSFunction->rareData(vm)->getBoundFunctionStructure();
+        if (structure && structure->storedPrototype() == prototype && structure->globalObject() == globalObject)
+            return structure;
+    }
+
+    Structure* result = globalObject->boundFunctionStructure();
+
+    // It would be nice if the structure map was keyed global objects in addition to the other things. Unfortunately, it is not
+    // currently. Whoever works on caching structure changes for prototype transistions should consider this problem as well.
+    // See: https://bugs.webkit.org/show_bug.cgi?id=152738
+    if (prototype.isObject() && prototype.getObject()->globalObject() == globalObject) {
+        result = vm.prototypeMap.emptyStructureForPrototypeFromBaseStructure(prototype.getObject(), result);
+        ASSERT_WITH_SECURITY_IMPLICATION(result->globalObject() == globalObject);
+    } else
+        result = Structure::create(vm, globalObject, prototype, result->typeInfo(), result->classInfo());
+
+    if (targetJSFunction)
+        targetJSFunction->rareData(vm)->setBoundFunctionStructure(vm, result);
+
+    return result;
+}
+
 JSBoundFunction* JSBoundFunction::create(VM& vm, JSGlobalObject* globalObject, JSObject* targetFunction, JSValue boundThis, JSValue boundArgs, int length, const String& name)
 {
     ConstructData constructData;
     ConstructType constructType = JSC::getConstructData(targetFunction, constructData);
     bool canConstruct = constructType != ConstructTypeNone;
     NativeExecutable* executable = vm.getHostFunction(boundFunctionCall, canConstruct ? boundFunctionConstruct : callHostFunctionAsConstructor, ASCIILiteral("Function.prototype.bind result"));
-    JSBoundFunction* function = new (NotNull, allocateCell<JSBoundFunction>(vm.heap)) JSBoundFunction(vm, globalObject, globalObject->boundFunctionStructure(), targetFunction, boundThis, boundArgs);
+    Structure* structure = getBoundFunctionStructure(vm, globalObject, targetFunction);
+    JSBoundFunction* function = new (NotNull, allocateCell<JSBoundFunction>(vm.heap)) JSBoundFunction(vm, globalObject, structure, targetFunction, boundThis, boundArgs);
 
     function->finishCreation(vm, executable, length, makeString("bound ", name));
     return function;

Modified: trunk/Source/_javascript_Core/tests/es6.yaml (196955 => 196956)


--- trunk/Source/_javascript_Core/tests/es6.yaml	2016-02-22 22:02:24 UTC (rev 196955)
+++ trunk/Source/_javascript_Core/tests/es6.yaml	2016-02-22 22:07:16 UTC (rev 196956)
@@ -787,7 +787,7 @@
 - path: es6/Function_is_subclassable_Function.prototype.apply.js
   cmd: runES6 :normal
 - path: es6/Function_is_subclassable_Function.prototype.bind.js
-  cmd: runES6 :fail
+  cmd: runES6 :normal
 - path: es6/Function_is_subclassable_Function.prototype.call.js
   cmd: runES6 :normal
 - path: es6/function_name_property_accessor_properties.js
@@ -897,15 +897,15 @@
 - path: es6/proper_tail_calls_tail_call_optimisation_mutual_recursion.js
   cmd: runES6 :normal
 - path: es6/prototype_of_bound_functions_arrow_functions.js
-  cmd: runES6 :fail
+  cmd: runES6 :normal
 - path: es6/prototype_of_bound_functions_basic_functions.js
-  cmd: runES6 :fail
+  cmd: runES6 :normal
 - path: es6/prototype_of_bound_functions_classes.js
-  cmd: runES6 :fail
+  cmd: runES6 :normal
 - path: es6/prototype_of_bound_functions_generator_functions.js
-  cmd: runES6 :fail
+  cmd: runES6 :normal
 - path: es6/prototype_of_bound_functions_subclasses.js
-  cmd: runES6 :fail
+  cmd: runES6 :normal
 - path: es6/Proxy_apply_handler.js
   cmd: runES6 :normal
 - path: es6/Proxy_Array.isArray_support.js

Added: trunk/Source/_javascript_Core/tests/stress/bound-function-uses-prototype.js (0 => 196956)


--- trunk/Source/_javascript_Core/tests/stress/bound-function-uses-prototype.js	                        (rev 0)
+++ trunk/Source/_javascript_Core/tests/stress/bound-function-uses-prototype.js	2016-02-22 22:07:16 UTC (rev 196956)
@@ -0,0 +1,31 @@
+// Test ES6 feature of using the bindee's prototype when binding a function.
+
+bind = Function.prototype.bind;
+
+function testChangeProto() {
+    function foo() { }
+
+    foo.__proto__ = Object.prototype;
+
+    let bar = bind.call(foo);
+    if (bar.__proto__ !== foo.__proto__)
+        throw "incorrect prototype";
+
+    foo.__proto__ = null;
+    bar = bind.call(foo);
+    if (bar.__proto__ !== foo.__proto__)
+        throw "cached prototype incorrectly"
+}
+testChangeProto();
+
+function testBuiltins() {
+    let bar = bind.call(Array);
+    if (bar.__proto__ !== Array.__proto__)
+        throw "builtin prototype incorrect";
+
+    Array.__proto__ = null;
+    bar = bind.call(Array);
+    if (bar.__proto__ !== Array.__proto__)
+        throw "builtin prototype did not change correctly.";
+}
+testBuiltins();

Modified: trunk/Source/_javascript_Core/tests/stress/class-subclassing-function.js (196955 => 196956)


--- trunk/Source/_javascript_Core/tests/stress/class-subclassing-function.js	2016-02-22 22:02:24 UTC (rev 196955)
+++ trunk/Source/_javascript_Core/tests/stress/class-subclassing-function.js	2016-02-22 22:07:16 UTC (rev 196956)
@@ -1,4 +1,5 @@
 F = class extends Function { }
+N = class extends null { }
 
 function test(i) {
 
@@ -20,12 +21,15 @@
         throw "function was not .callable";
 
     let g2 = g.bind({foo:1}, 1);
-    if (g2 instanceof F)
-        throw "the binding of a subclass should not inherit from the original function";
+    if (!(g2 instanceof F))
+        throw "the binding of a subclass should inherit from the bound function's class";
 
     if (g2(1) !== 3)
         throw "binding didn't work";
 
+    let bound = C.bind(null)
+    if (bound.__proto__ !== C.__proto__)
+        throw "binding with null as prototype didn't work";
 }
 noInline(test);
 
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to