Title: [283167] trunk
Revision
283167
Author
[email protected]
Date
2021-09-28 06:37:17 -0700 (Tue, 28 Sep 2021)

Log Message

Speed up setting JSFunction's "prototype" property
https://bugs.webkit.org/show_bug.cgi?id=230864

Reviewed by Yusuke Suzuki.

JSTests:

* microbenchmarks/class-creation.js: Added.
* microbenchmarks/function-prototype-put-non-reified.js: Added.
* microbenchmarks/function-prototype-put-reified.js: Added.

Source/_javascript_Core:

This patch:

  1. Removes "prototype" lookup from put(), which was non-inlineable and created an
     interim object with "constructor" field. Instead, the new value is put directly,
     with correct attributes, and respecting an altered receiver. This results in 2.8X
     perf boost for very common case of setting "prototype": `Cat.prototype = new Animal`.

  2. Simplifies initialization of class constructor's prototype object, removing tentacles
     from getOwnPropertySlot() and extracting constructPrototypeObject() helper. Provides
     a fast path for op_define_data_property, speeding up `class` creation by ~40%, while
     reifies normal function's prototype with correct attributes for Object.defineProperty().

* runtime/JSFunction.cpp:
(JSC::constructPrototypeObject):
(JSC::JSFunction::getOwnPropertySlot):
(JSC::JSFunction::put):
(JSC::JSFunction::defineOwnProperty):

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (283166 => 283167)


--- trunk/JSTests/ChangeLog	2021-09-28 13:21:15 UTC (rev 283166)
+++ trunk/JSTests/ChangeLog	2021-09-28 13:37:17 UTC (rev 283167)
@@ -1,3 +1,14 @@
+2021-09-28  Alexey Shvayka  <[email protected]>
+
+        Speed up setting JSFunction's "prototype" property
+        https://bugs.webkit.org/show_bug.cgi?id=230864
+
+        Reviewed by Yusuke Suzuki.
+
+        * microbenchmarks/class-creation.js: Added.
+        * microbenchmarks/function-prototype-put-non-reified.js: Added.
+        * microbenchmarks/function-prototype-put-reified.js: Added.
+
 2021-09-27  Yusuke Suzuki  <[email protected]>
 
         [WTF] Add ApproximateTime

Added: trunk/JSTests/microbenchmarks/class-creation.js (0 => 283167)


--- trunk/JSTests/microbenchmarks/class-creation.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/class-creation.js	2021-09-28 13:37:17 UTC (rev 283167)
@@ -0,0 +1,7 @@
+(function() {
+    var A;
+    for (var i = 0; i < 2e5; ++i)
+        A = class {};
+    if (typeof A !== "function")
+        throw new Error("Bad assertion!");
+})();

Added: trunk/JSTests/microbenchmarks/function-prototype-put-non-reified.js (0 => 283167)


--- trunk/JSTests/microbenchmarks/function-prototype-put-non-reified.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/function-prototype-put-non-reified.js	2021-09-28 13:37:17 UTC (rev 283167)
@@ -0,0 +1,10 @@
+(function() {
+    var fn;
+    var obj = {};
+    for (var i = 0; i < 4e5; ++i) {
+        fn = function() {};
+        fn.prototype = obj;
+    }
+    if (fn.prototype !== obj)
+        throw new Error("Bad assertion!");
+})();

Added: trunk/JSTests/microbenchmarks/function-prototype-put-reified.js (0 => 283167)


--- trunk/JSTests/microbenchmarks/function-prototype-put-reified.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/function-prototype-put-reified.js	2021-09-28 13:37:17 UTC (rev 283167)
@@ -0,0 +1,8 @@
+(function() {
+    var fn = function() {};
+    var obj = {};
+    for (var i = 0; i < 4e5; ++i)
+        fn.prototype = obj;
+    if (fn.prototype !== obj)
+        throw new Error("Bad assertion!");
+})();

Modified: trunk/Source/_javascript_Core/ChangeLog (283166 => 283167)


--- trunk/Source/_javascript_Core/ChangeLog	2021-09-28 13:21:15 UTC (rev 283166)
+++ trunk/Source/_javascript_Core/ChangeLog	2021-09-28 13:37:17 UTC (rev 283167)
@@ -1,3 +1,28 @@
+2021-09-28  Alexey Shvayka  <[email protected]>
+
+        Speed up setting JSFunction's "prototype" property
+        https://bugs.webkit.org/show_bug.cgi?id=230864
+
+        Reviewed by Yusuke Suzuki.
+
+        This patch:
+
+          1. Removes "prototype" lookup from put(), which was non-inlineable and created an
+             interim object with "constructor" field. Instead, the new value is put directly,
+             with correct attributes, and respecting an altered receiver. This results in 2.8X
+             perf boost for very common case of setting "prototype": `Cat.prototype = new Animal`.
+
+          2. Simplifies initialization of class constructor's prototype object, removing tentacles
+             from getOwnPropertySlot() and extracting constructPrototypeObject() helper. Provides
+             a fast path for op_define_data_property, speeding up `class` creation by ~40%, while
+             reifies normal function's prototype with correct attributes for Object.defineProperty().
+
+        * runtime/JSFunction.cpp:
+        (JSC::constructPrototypeObject):
+        (JSC::JSFunction::getOwnPropertySlot):
+        (JSC::JSFunction::put):
+        (JSC::JSFunction::defineOwnProperty):
+
 2021-09-27  Yusuke Suzuki  <[email protected]>
 
         [WTF] Add ApproximateTime

Modified: trunk/Source/_javascript_Core/runtime/JSFunction.cpp (283166 => 283167)


--- trunk/Source/_javascript_Core/runtime/JSFunction.cpp	2021-09-28 13:21:15 UTC (rev 283166)
+++ trunk/Source/_javascript_Core/runtime/JSFunction.cpp	2021-09-28 13:37:17 UTC (rev 283167)
@@ -296,6 +296,26 @@
     return callData;
 }
 
+static constexpr unsigned prototypeAttributesForNonClass = PropertyAttribute::DontEnum | PropertyAttribute::DontDelete;
+
+static inline JSObject* constructPrototypeObject(JSGlobalObject* globalObject, JSFunction* thisObject)
+{
+    VM& vm = globalObject->vm();
+    JSGlobalObject* scopeGlobalObject = thisObject->scope()->globalObject();
+    // Unlike Function instances, the prototype object of GeneratorFunction instances lacks own "constructor" property.
+    // https://tc39.es/ecma262/#sec-runtime-semantics-instantiategeneratorfunctionobject (step 6)
+    if (isGeneratorWrapperParseMode(thisObject->jsExecutable()->parseMode()))
+        return constructEmptyObject(globalObject, scopeGlobalObject->generatorPrototype());
+    // Unlike Function instances, the prototype object of AsyncGeneratorFunction instances lacks own "constructor" property.
+    // https://tc39.es/ecma262/#sec-runtime-semantics-instantiateasyncgeneratorfunctionobject (step 6)
+    if (isAsyncGeneratorWrapperParseMode(thisObject->jsExecutable()->parseMode()))
+        return constructEmptyObject(globalObject, scopeGlobalObject->asyncGeneratorPrototype());
+
+    JSObject* prototype = constructEmptyObject(globalObject, scopeGlobalObject->objectPrototype());
+    prototype->putDirect(vm, vm.propertyNames->constructor, thisObject, static_cast<unsigned>(PropertyAttribute::DontEnum));
+    return prototype;
+}
+
 bool JSFunction::getOwnPropertySlot(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot)
 {
     VM& vm = globalObject->vm();
@@ -308,32 +328,13 @@
         RELEASE_AND_RETURN(scope, Base::getOwnPropertySlot(thisObject, globalObject, propertyName, slot));
     }
 
-    if (propertyName == vm.propertyNames->prototype && thisObject->jsExecutable()->hasPrototypeProperty() && !thisObject->jsExecutable()->isClassConstructorFunction()) {
-        // NOTE: class constructors define the prototype property in bytecode using
-        // defineOwnProperty, which ends up calling into this code (see our defineOwnProperty
-        // implementation below). The bytecode will end up doing the proper definition
-        // with the property being non-writable/non-configurable. However, we must ignore
-        // the initial materialization of the property so that the defineOwnProperty call
-        // from bytecode succeeds. Otherwise, the materialization here would prevent the
-        // defineOwnProperty from being able to overwrite the property.
+    if (propertyName == vm.propertyNames->prototype && thisObject->jsExecutable()->hasPrototypeProperty()) {
         unsigned attributes;
         PropertyOffset offset = thisObject->getDirectOffset(vm, propertyName, attributes);
         if (!isValidOffset(offset)) {
-            JSGlobalObject* scopeGlobalObject = thisObject->scope()->globalObject();
-            JSObject* prototype = nullptr;
-            if (isGeneratorWrapperParseMode(thisObject->jsExecutable()->parseMode())) {
-                // Unlike function instances, the object that is the value of the a GeneratorFunction's prototype
-                // property does not have a constructor property whose value is the GeneratorFunction instance.
-                // https://tc39.github.io/ecma262/#sec-generatorfunction-instances-prototype
-                prototype = constructEmptyObject(globalObject, scopeGlobalObject->generatorPrototype());
-            } else if (isAsyncGeneratorWrapperParseMode(thisObject->jsExecutable()->parseMode()))
-                prototype = constructEmptyObject(globalObject, scopeGlobalObject->asyncGeneratorPrototype());
-            else {
-                prototype = constructEmptyObject(globalObject, scopeGlobalObject->objectPrototype());
-                prototype->putDirect(vm, vm.propertyNames->constructor, thisObject, static_cast<unsigned>(PropertyAttribute::DontEnum));
-            }
-
-            thisObject->putDirect(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontDelete | PropertyAttribute::DontEnum);
+            // For class constructors, prototype object is initialized from bytecode via defineOwnProperty().
+            ASSERT(!thisObject->jsExecutable()->isClassConstructorFunction());
+            thisObject->putDirect(vm, propertyName, constructPrototypeObject(globalObject, thisObject), prototypeAttributesForNonClass);
             offset = thisObject->getDirectOffset(vm, vm.propertyNames->prototype, attributes);
             ASSERT(isValidOffset(offset));
         }
@@ -385,16 +386,18 @@
         RELEASE_AND_RETURN(scope, Base::put(thisObject, globalObject, propertyName, value, slot));
     }
 
-    if (propertyName == vm.propertyNames->prototype) {
+    if (propertyName == vm.propertyNames->prototype && thisObject->jsExecutable()->hasPrototypeProperty()) {
         slot.disableCaching();
-        // Make sure prototype has been reified, such that it can only be overwritten
-        // following the rules set out in ECMA-262 8.12.9.
-        PropertySlot getSlot(thisObject, PropertySlot::InternalMethodType::VMInquiry, &vm);
-        thisObject->methodTable(vm)->getOwnPropertySlot(thisObject, globalObject, propertyName, getSlot);
-        RETURN_IF_EXCEPTION(scope, false);
         if (FunctionRareData* rareData = thisObject->rareData())
             rareData->clear("Store to prototype property of a function");
-        getSlot.disallowVMEntry.reset();
+        if (!isValidOffset(thisObject->getDirectOffset(vm, propertyName))) {
+            // For class constructors, prototype object is initialized from bytecode via defineOwnProperty().
+            ASSERT(!thisObject->jsExecutable()->isClassConstructorFunction());
+            if (UNLIKELY(slot.thisValue() != thisObject))
+                RELEASE_AND_RETURN(scope, JSObject::definePropertyOnReceiver(globalObject, propertyName, value, slot));
+            thisObject->putDirect(vm, propertyName, value, prototypeAttributesForNonClass);
+            return true;
+        }
         RELEASE_AND_RETURN(scope, Base::put(thisObject, globalObject, propertyName, value, slot));
     }
 
@@ -454,15 +457,18 @@
         RELEASE_AND_RETURN(scope, Base::defineOwnProperty(object, globalObject, propertyName, descriptor, throwException));
     }
 
-    if (propertyName == vm.propertyNames->prototype) {
-        // Make sure prototype has been reified, such that it can only be overwritten
-        // following the rules set out in ECMA-262 8.12.9.
-        PropertySlot slot(thisObject, PropertySlot::InternalMethodType::VMInquiry, &vm);
-        thisObject->methodTable(vm)->getOwnPropertySlot(thisObject, globalObject, propertyName, slot);
-        RETURN_IF_EXCEPTION(scope, false);
+    if (propertyName == vm.propertyNames->prototype && thisObject->jsExecutable()->hasPrototypeProperty()) {
         if (FunctionRareData* rareData = thisObject->rareData())
             rareData->clear("Store to prototype property of a function");
-        slot.disallowVMEntry.reset();
+        if (!isValidOffset(thisObject->getDirectOffset(vm, propertyName))) {
+            if (thisObject->jsExecutable()->isClassConstructorFunction()) {
+                // Fast path for prototype object initialization from bytecode that avoids calling into getOwnPropertySlot().
+                ASSERT(descriptor.isDataDescriptor());
+                thisObject->putDirect(vm, propertyName, descriptor.value(), descriptor.attributes());
+                return true;
+            }
+            thisObject->putDirect(vm, propertyName, constructPrototypeObject(globalObject, thisObject), prototypeAttributesForNonClass);
+        }
     } else {
         thisObject->reifyLazyPropertyIfNeeded(vm, globalObject, propertyName);
         RETURN_IF_EXCEPTION(scope, false);
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to